Demo images / Build & push demo images (push) Failing after 2m22s
Replaces the registry host across 86 files: 309 references, covering all 40 app manifests, the orchestrator and container crates, the release and catalog scripts, both demo-images workflows, the ISO builder, demo-deploy, and the frontend marketplace data. Verified the domain actually serves the registry before rewriting anything, rather than assuming the web host implies the registry: - TLS verifies clean, HTTP/2 on the web root - an anonymous token grants a manifest fetch (HTTP 200) with no credentials - skopeo inspect --no-creds resolves an image and lists its tags That last check is the one that matters: an outside developer with no account can now pull, which was the functional blocker for publishing at all. Plain-HTTP references become HTTPS in the same pass, so OTA downloads stop crossing the network in the clear. Deliberately NOT rewritten: - The public FIPS anchor on port 8444. It is a functional network endpoint every node dials to bootstrap the mesh — closer to Bitcoin Core's hardcoded seeds than to leaked infrastructure. The domain does resolve to the same host, so it could become a hostname, but that adds a DNS dependency to the path used precisely when things are broken. Worth a deliberate decision, not a side effect of this change. - The companion APK on port 2100. The domain returns 404 for that path, so rewriting it would swap a working URL for a broken one. The Releases page does serve (200), which is where the plan already wants those binaries. - releases/app-catalog.json, releases/manifest.json and release-manifest.json. These carry `signature` and `signed_by`; editing their contents invalidates the signature and the fleet refuses artifacts that fail verification. They were rewritten in a first pass and reverted — they must be regenerated and re-signed through the signing ceremony instead, which needs the mnemonic. So the catalog still advertises the old host until that ceremony runs. Nodes resolve images through the signed catalog, not the on-disk manifests, so this commit alone does not change what a node pulls. Verified: archipelago-container 75/75; every manifest still parses with a top-level app block; no signed artifact modified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
136 lines
7.9 KiB
Markdown
136 lines
7.9 KiB
Markdown
# Dual-ecash: Cashu + Fedimint, seamlessly
|
|
|
|
Status: **in progress** (2026-06-17). FE scaffolding + Fedimint HTTP bridge landed and
|
|
compile-checked; live federation round-trip and networking-sats routing are not yet validated.
|
|
|
|
## Why
|
|
|
|
Today the node's wallet (`core/archipelago/src/wallet/ecash.rs`, `mint_client.rs`, `cashu.rs`)
|
|
speaks **only** the Cashu NUT HTTP protocol (BDHKE, `cashuA…` tokens). There is **no** Fedimint
|
|
*client* — `apps/fedimint` is only the guardian server, and the "local Fedimint" default mint at
|
|
`127.0.0.1:8175` is just the guardian UI nginx, which does not expose the Cashu NUT API. So:
|
|
|
|
- The node can hold/spend generic Cashu tokens, but cannot hold Fedimint ecash or join federations.
|
|
- "Networking sats" (streaming/seeding revenue) is hardcoded to the Cashu wallet.
|
|
|
|
Goal: support **both** ecash protocols seamlessly — hold balances in either, join arbitrary
|
|
federations, and let networking-sats be paid/received over whichever protocol the peer accepts.
|
|
|
|
## Architecture decision
|
|
|
|
**Containerized `fedimint-clientd` + thin HTTP bridge** (chosen over linking the native
|
|
`fedimint-client` Rust SDK into the binary, and over a Lightning-only bridge).
|
|
|
|
```
|
|
archipelago binary
|
|
├─ CashuMintClient ──HTTP (NUT /v1/*)──▶ cashu mint
|
|
└─ FedimintClient ──REST (/v2/*)─────▶ fedimint-clientd container ──▶ federation guardians
|
|
```
|
|
|
|
Rationale: keeps the heavy, fast-moving Fedimint SDK **out** of the main binary (no compile-time
|
|
coupling, no rebuild bloat, OTA-friendly), and fits the existing app/container architecture
|
|
(`apps/fedimint`, `apps/fedimint-gateway`). The Rust side is just a `reqwest` client, mirroring
|
|
`MintClient`.
|
|
|
|
### fedimint-clientd REST surface (v0.3.x)
|
|
|
|
- Auth: `Authorization: Bearer <password>`. Default port 8080 (we map it to host **8178** because
|
|
8080 is LND REST). Base path `/v2/...`.
|
|
- `GET /v2/admin/info` — per-federation balances (`totalAmountMsat`, denominations, meta).
|
|
- `POST /v2/admin/join` — `{ "inviteCode": "fed1…", "useManualSecret": false }` → joins / returns `federationId`.
|
|
- `POST /v2/mint/spend` — `{ federationId, amountMsat }` → serialized notes (ecash to send).
|
|
- `POST /v2/mint/reissue` — `{ federationId, notes }` → redeem received notes; returns reissued amount.
|
|
- `POST /v2/ln/invoice` / `POST /v2/ln/pay` — Lightning in/out (used for cross-protocol swaps).
|
|
- `GET /health`.
|
|
|
|
Multi-federation: requests carry a `federationId`; clientd's `multimint` manages many clients.
|
|
**Exact JSON field names must be pinned to the clientd image tag we vendor** — code defensively.
|
|
|
|
## Components
|
|
|
|
### 1. Container app — `apps/fedimint-clientd/manifest.yml`
|
|
Sidecar running `fedimint-clientd`, mirroring `apps/fedimint-gateway`. Host port 8178 → container
|
|
8080. Password from node secret `fedimint-clientd-password`. State volume at
|
|
`/var/lib/archipelago/fedimint-clientd`. Added to `RESERVED_PORTS` (port_allocator.rs) and
|
|
`fallback_package_port()` (server.rs).
|
|
|
|
### 2. Rust bridge — `core/archipelago/src/wallet/fedimint_client.rs`
|
|
Thin `reqwest` client: `info()`, `join()`, `spend()`, `reissue()`, `ln_invoice()`, `ln_pay()`.
|
|
`from_node(data_dir)` resolves base URL + password (env `FEDIMINT_CLIENTD_URL` /
|
|
`FEDIMINT_CLIENTD_PASSWORD`, else defaults + secret file). Tor-proxy support via `with_client`,
|
|
mirroring `MintClient`.
|
|
|
|
### 3. RPCs — `core/archipelago/src/api/rpc/fedimint.rs`
|
|
- `wallet.fedimint-list` → joined federations + balances (`{federation_id, name, balance_sats}[]`).
|
|
- `wallet.fedimint-join` `{invite_code}` → joins via clientd, persists to
|
|
`wallet/fedimint_federations.json`, returns `{federation_id}`.
|
|
- `wallet.fedimint-leave` `{federation_id}` → untracks locally.
|
|
- `wallet.fedimint-balance` → total sats across federations (from clientd `info`).
|
|
|
|
Local registry `wallet/fedimint_federations.json` = `{ federations: [{federation_id, name}] }` so
|
|
the list survives clientd being temporarily down; balances are live from clientd.
|
|
|
|
### 4. Frontend — `WalletSettingsModal.vue`
|
|
Tabbed: **Cashu Mints** (live: `streaming.list-mints` / `streaming.configure-mints`) and
|
|
**Fedimint Federations** (`wallet.fedimint-list` / `-join` / `-leave`). Gear icon on
|
|
`HomeWalletCard`. `fedimintBackendReady` flips to `true` once the RPCs ship; join degrades
|
|
gracefully with a clear error if the clientd app isn't installed.
|
|
|
|
### 4b. Default federation (zero-touch)
|
|
clientd auto-joins a default federation at boot via `FEDIMINT_CLIENTD_INVITE_CODE` (manifest), and
|
|
the Rust bridge `ensure_default_federation()` idempotently joins + tracks it (called from
|
|
`wallet.fedimint-list`) so already-running nodes pick it up too. Constant
|
|
`DEFAULT_FEDERATION_INVITE` in `fedimint_client.rs` is the single source on the Rust side; keep it
|
|
in sync with the manifest env. clientd is a **client, not the guardian** — it needs no local
|
|
`fedimintd`, so it bundles standalone.
|
|
|
|
### 4c. Bundling on every node
|
|
- **Bundled ISO:** add image to `scripts/image-versions.sh`, the ISO bundle `.tar` list, and a
|
|
core-create block in `scripts/first-boot-containers.sh`; mark `tier: "core"` in
|
|
`app-catalog/catalog.json`.
|
|
- **Unbundled ISO:** `first-boot-containers.sh` exits after FileBrowser only — add clientd to that
|
|
early-exit block so unbundled nodes also get it out of the box.
|
|
- **CAVEAT:** confirm the *current* ISO assembler before editing the bundle list — the one found is
|
|
under `image-recipe/_archived/` (likely stale); `first-boot-containers.sh`/`image-versions.sh`
|
|
are current.
|
|
- Image: build from source (no official image; `flake.nix` only) → push to vps2
|
|
`source.archipelago-foundation.org/lfg2025/fedimint-clientd:v0.4.0`.
|
|
|
|
### 5. Unified balance
|
|
`HomeWalletCard` ecash row = Cashu `wallet.ecash-balance` + Fedimint `wallet.fedimint-balance`.
|
|
(Home already calls `wallet.ecash-balance`; add fedimint and sum.)
|
|
|
|
## Networking sats — dual protocol (phase 6, NOT yet wired)
|
|
|
|
The economic layer (`streaming.rs`, `streaming/gate.rs`, sessions, pricing, metering) is already
|
|
protocol-agnostic — it just calls into the wallet. The injection points are:
|
|
|
|
1. **Protocol-tag accepted mints.** `accepted_mints: Vec<String>` → carry protocol, e.g.
|
|
`cashu:https://mint…` / `fedimint:<federation_id>`. Migrate `wallet/accepted_mints.json` with a
|
|
back-compat reader (bare URL ⇒ `cashu:`).
|
|
2. **`MintClient::new()` is the bottleneck** (~10 call sites). Introduce a `MintBackend` trait with
|
|
`CashuBackend` (wraps current code) and `FedimintBackend` (calls `FedimintClient`).
|
|
3. **`Token` enum** `Cashu(CashuToken) | Fedimint(notes)`; serialize/verify by variant.
|
|
4. `build_payment_token()` picks a `(backend, id)` the peer accepts; `verify_and_receive_payment()`
|
|
auto-detects the token variant and reissues/swaps on the right backend.
|
|
5. Cross-protocol settlement (Cashu↔Fedimint) bridges over Lightning (BOLT11) — both sides already
|
|
have mint/melt (Cashu) and `ln/invoice`+`ln/pay` (Fedimint).
|
|
6. `Web5NetworkingProfitsSettings.vue`: per-service payout protocol/mint selector.
|
|
|
|
## Phases
|
|
|
|
- [x] **P0** FE tabbed Wallet Settings modal + gear (Cashu live, Fedimint tab structured).
|
|
- [x] **P1** `fedimint-clientd` container manifest + ports.
|
|
- [x] **P2** `FedimintClient` HTTP bridge + `wallet.fedimint-*` RPCs (compiles).
|
|
- [ ] **P3** Validate join / balance / spend / reissue against a live clientd + real federation on a scratch node.
|
|
- [ ] **P4** Unified ecash balance in the wallet card (Cashu + Fedimint).
|
|
- [ ] **P5** Flip FE fully live; surface "install Fedimint client app" when clientd unreachable.
|
|
- [ ] **P6** Networking-sats dual-protocol routing (the `MintBackend`/`Token` refactor above).
|
|
|
|
## Validation (per project testing discipline)
|
|
|
|
clientd image + a real federation are required; cannot be validated from the dev tree. Validate on
|
|
a scratch node: install Fedimint app + clientd, join a known test federation, confirm
|
|
`wallet.fedimint-balance`, then a spend→reissue round-trip between two nodes, then networking-sats
|
|
payment over Fedimint. Heavy/iterative work belongs in a worktree (see CLAUDE.md memory).
|