aboutsummaryrefslogtreecommitdiffstats

coinosv3-server

The server-side services behind coinos v3, the bitcoin, Ark, Lightning and nostr wallet whose app lives in coinosv3. The wallet itself is self-custodial and runs in the browser. These services hold no keys to user funds; they give a wallet a payment address, wake it when a request arrives, and store its encrypted sync data.

Directory Service Public at
names/ Payment-name registrar. Gives each wallet a [email protected] address: BIP-353 DNS records (written through Cloudflare's API), LNURL-pay, and a BOLT 12 offer per name on the Ark server's Lightning node. Settled payments are forwarded to the name's Ark address from a small float wallet. Also serves zap receipts, point-of-sale invoices, CLINK, payment webhooks, link previews and translation. Writes are authorized with NIP-98. names.coinos.io
nwcpush/ NWC push notifier. Watches relays for Nostr Wallet Connect requests addressed to registered wallet-service keys and wakes the device with a Web Push, so a closed wallet can answer. (internal, behind the app)
blossom/ Blossom server for sync envelopes. Stores only the wallet's encrypted cross-device state blobs, which can outgrow a relay's event size. Refuses anything else. blossom.coinos.io
feed/ Public feed snapshot (/api/feed) and server-rendered note and profile pages. v3.coinos.io
live/ Live streams sent from the app: the browser's recording over a WebSocket, out as HLS (a live window and the recording), for NIP-53 streams. Senders own a coinos name. v3.coinos.io/live/
vmedia/ Media helper for the feed: video posters, H.264 fallbacks and resized pictures. v3.coinos.io/vmedia/
bridge/ Ark to Lightning swap bridge. Retired: the app uses the Ark server's Lightning. bridge/ln.js (a minimal CLN RPC client) is still used by names/. —

Shared code: the coinosv3 submodule

The services use the wallet's own code for Ark, BOLT 12 and nostr helpers, so the server speaks exactly the wire format and policy the app does. That code comes from the coinosv3/ submodule, pinned to a specific commit. Moving the pin is a deliberate, reviewable commit of its own:

git -C coinosv3 fetch && git -C coinosv3 checkout <commit>
git add coinosv3 && git commit -m "coinosv3 → <commit>"

Running

You need Bun (the deployment pins ghcr.io/coinos/bun:1.4.3, oven/bun with the distrusted emSign G3 root swapped out of its built-in store; see coinos-server docker/bun.Dockerfile) and, for names/, a Core Lightning node's RPC socket and a Cloudflare zone.

git clone --recurse-submodules https://git.coinos.io/coinosv3-server.git
cd coinosv3-server
bun install
cp names/config.example.json names/config.json      # fill it in
NAMES_CONFIG=names/config.json bun names/server.js

Each service reads a JSON config. names uses NAMES_CONFIG and nwcpush uses NWCPUSH_CONFIG; blossom reads BLOSSOM_* environment variables (see the header of blossom/server.js). Example configs: names/config.example.json, nwcpush/config.example.json, bridge/config.example.json.

Configs hold secrets and are never committed. They include the DNS provider's API key, admin tokens, the forwarder float's mnemonic, VAPID keys and nostr secret keys. .gitignore excludes every *config*.json except the examples, and all runtime state.

With Docker

compose.yml runs names, nwcpush and blossom from the checkout, mounted read-only. Configs and state live outside it, under a directory you name in a .env file:

# .env
STATE=/srv/coinosv3-state
CLN_RPC=/path/to/lightning-rpc
$STATE/names/config.json     $STATE/names/data/      (names.db)
$STATE/nwcpush/config.json   $STATE/nwcpush/data/
                             $STATE/blossom/data/
docker network create net   # once; the services share it with the reverse proxy
docker compose up -d

State

names keeps its state in SQLite (names.db, see names/store.js): one row per name, offer, invoice and so on. Each change is a single transaction that writes only the rows that changed. On its first start it imports the older JSON state file named by stateFile, and a corrupt file stops startup rather than starting empty. For a consistent backup while it runs, take a snapshot through SQLite, not by copying the file:

sqlite3 names.db "VACUUM INTO 'names-backup.db'"

Deploying

coinos deploys by pushing to a bare repository on the server. deploy/post-receive is its hook. A push to prod checks the commit out, installs dependencies, restarts the services whose files changed, and mirrors the commit to git.coinos.io and gittr. Don't copy files onto the server by hand: what runs should always be a commit.

git push prod HEAD:prod && git push origin HEAD:master

Rolling back

The previous deploy is the previous commit: git revert and push, or push an older commit to prod (git push -f prod <commit>:prod). names reads its SQLite state either way. Before the SQLite change, the JSON file it was imported from is still in the data directory, but it stops at the moment of the import.

Tests

bun test names/store.test.js

tools/ holds the older end-to-end and unit scripts for the registrar, feed and translation. They are run directly with bun tools/<file>.

License

MIT, see LICENSE.