Skip to content

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 14 Sep 07:50
· 409 commits to main since this release
108f619

Changed

  • Latest SDKs — @arkade-os/sdk 0.4.68 → 0.4.72 (@arkade-os/solver-discovery stays at 0.2.4, already latest). The offline-receive corridor client moves off the vendored src/vendor/arkade-swap/ copy onto the published @arkade-os/swap@0.0.18, which now ships the lightning:BTC -> arkade:BTC receive corridors. Adapts to the SDK's watch-only contract events (isContractVtxoEvent gate in src/covenant-watcher.ts).

Added

  • Production runtime and card-only solver discovery — uses @arkade-os/solver-discovery across multiple registries, cached indexes, startup cards, and cards pasted into the admin UI. Direct SOLVER_URL / pubkey / relay configuration is removed. Accepted offline swaps atomically persist solver and VHTLC recovery state for restart-safe polling/self-claim. Adds liveness/readiness endpoints, resource caps, redacted structured logs, graceful shutdown, a non-root image, reference Compose deployment, and an operations runbook.
  • Self-claim (OFFLINE_SELF_CLAIM, default off) — the server can push the solver-funded lockup's nonInteractiveClaim leaf itself, while the covenant and emulator still constrain payment to the user's registered address. Claims skip underfunded lockups, are safe to retry, and never block status polling. Versioned VHTLC reconstruction data is now persisted atomically with accepted swaps, so self-claim resumes after restart.
  • Per-payment Arkade destinations (OFFLINE_COVENANT_DESTINATIONS, default off) — the arkade rail handed every payer the same registered address, so concurrent payments were indistinguishable on the wire and the watcher correlated them by amount and arrival window; two payments of the same amount could not be told apart at all, and a greedy first-fit could let an older, smaller record consume a larger one's payment. With the flag set each callback derives its own address (src/covenant-destination.ts) and the script becomes the identifier: a VTXO there belongs to exactly one record, so src/covenant-watcher.ts settles it on the contract event instead of guessing (the arrival window, the 15s skew and the oldest-first assignment all drop away; the amount check stays, since an under-payment must not flip settled). src/arkade-watcher.ts skips these records and keeps correlating the static-address rail only, where amount and arrival time remain all there is. The address is three Taproot leaves — condition(H(P)) + operator + a cosigner key committing to enforcePayTo(static address); user + operator; and user alone after a CSV (OFFLINE_COVENANT_RECOVERY_DELAY_SECONDS, default 86528 — BIP68 counts seconds in 512s units, so 24h exactly is not expressible). Only the first carries H(P), so a fresh preimage moves the address while the covenant bytes stay fixed, and the preimage is not a secret: the covenant means anyone holding it can only pay the user, which is why the two recovery leaves are keyed to the user and need neither P nor this server. A sweeper (src/covenant-sweeper.ts) moves each funded destination on to the static address, so the receiving wallet sees an ordinary arrival and needs to know nothing about the covenant. Costs one extra Arkade transaction per payment and a dependency on the emulator co-signing the sweep; requires OFFLINE_EMULATOR_URL, COVCLAIMD_URL and ARK_SERVER_URL, all checked at startup rather than when a payer is already holding an address. Derivation failure at request time falls back to the static address — ambiguous again for that payment, which beats refusing to be paid. Tracking, watching and spending are the SDK's: the covenant is registered as a contract type (src/covenant-contract.ts) so ContractManager owns its lifecycle — a subscription instead of a poll, awaiting-funds demoting itself once a payment lands, and getSpendablePaths choosing the leaf. Adds migration 007 (covenant_script with a unique index) as the join key; everything else about a covenant lives in the SDK's own ark_contracts.
  • ClaimPacket TLV codec (src/claim-packet.ts) — encode/decode for the Arkade extension packet body covclaimd matches on (type 0x04; TLVs 0x01 ciphertext, 0x02 arkade_script, 0x03 covclaimd_pub_key), transcribed from covclaimd's pkg/preimage/packet.go. Groundwork for the non-interactive claim path in which the funder stamps the packet into the lockup's funding transaction instead of revealing it to covclaimd over HTTP (arkade-os/intent-solver#46); Wired behind OFFLINE_STAMP_CLAIM_PACKET (default off): with it set, the callback sends 0x01 ciphertext + 0x03 covclaimd_pub_key instead of the bare ciphertext, so the solver stamps the packet on chain and the covclaimd is ours to choose rather than something both sides must have been configured with. Off by default because a solver predating that PR forwards the packet as a ciphertext, cannot decrypt it, and the swap funds and refunds.
  • LUD-21 (verify) — LNURL-pay callback responses now carry a verify URL, and a new GET /lnurl/verify/:paymentHash lets payers poll settlement status ({ status, settled, preimage, pr }). Because this server is a relay with no Lightning node, wallets report settlement via the new authenticated POST /lnurl/session/:id/settled with { preimage } — the server checks sha256(preimage) against a payment hash the session issued (decoded locally from the bolt11, no new dependency) before flipping the record to settled. Settlement records live in a new settlements table (migration 003) when DB_PATH is set, or in an in-memory store with a VERIFY_TTL_MS (default 24h) lifetime otherwise, so a payer can still poll verify after the wallet disconnects. Available on both the ephemeral LNURL and the LN-address (LUD-16) flows.
  • Offline receive (opt-in) — with solver cards plus COVCLAIMD_URL and ARK_SERVER_URL, an LN address can register an Arkade receive identity and receive while its wallet is offline. The server obtains a Nostr RFQ through the ranked card candidates, returns the verified hold invoice, and tracks the pinned solver through LUD-21 settlement without holding user keys or funds.
  • Payment options (LUD-XX, paymentOptions) — an LN address with a registered Arkade identity now advertises paymentOptions: [lightning, arkade] in its LUD-06 payRequest. A payer selects a rail with ?paymentOption=<id>: lightning (or absent) keeps the existing BOLT11 / offline-swap flow; arkade returns the user's Arkade address as a paymentDestination (direct receive, no swap) plus a verify URL that reports the non-pr LUD-21 shape ({ settled, paymentOption, paymentDestination, paymentReference }). Unknown/unavailable options return { status: "ERROR", reason: "Unsupported paymentOption" }. Settlement records carry the option + destination + agreed amount (migration 006, incl. amount_msat so the future watcher can correlate observed payments). Because the payer pays the Arkade address directly, the server isn't in the payment path, so arkade settled flips only when the server-side Arkade watcher observes the payment — which ships in this same release, below. The resolver is a small registry (src/payment-options.ts) so future rails/assets slot in.
  • Payment quote (LUD-XX, paymentQuote) — when a QuoteProvider is injected, the LN-address payRequest advertises units (e.g. USD), and a callback with ?unit=<code> (optionally &receiveUnit=) is quoted: the provider converts the unit amount to the effective msat amount used for the bolt11 / offline swap, and the response echoes paymentQuote ({ requested, payment, receive?, fees? }). This server has no rate oracle, so without a provider units is omitted and any unit= request returns Unsupported unit. Framework only — real rate sourcing, assets, and swap-backed quotes plug in behind QuoteProvider (src/quote-provider.ts). Applies to the amount-denominated Lightning path (relay + offline swap); quoting the arkade destination rail is a follow-up.
  • Arkade settlement watcher — when ARK_SERVER_URL is set, a background poller watches the Arkade indexer for payments to destination-rail records (paymentOptions: arkade) and flips verify to settled: true with paymentReference = the observed Arkade txid. Matching is by destination script + agreed amount (amount_msat, never under-paid) + arrival window; each observed VTXO settles at most one record, oldest first. Correlation fuzziness (an unrelated same-amount payment in the same window) is inherent to reference-less address payments and is documented in the README.
  • Solver discovery — SOLVER_REGISTRY_URLS, a startup card file, and admin-pasted cards merge into one atomically refreshed, fee-ranked discovery snapshot with bounded registry caching.
  • Admin settlements view — GET /admin/api/settlements (filter: settled, option, limit) lists LUD-21 settlement records (relay invoices, offline corridor swaps, destination-rail payments) newest-first, never exposing preimages or invoices; plus a Settlements tab in the admin SPA with a 5s live poll.

Security

  • Patched express's transitive dependencies — path-to-regexp 8.3.0 → 8.4.2 (high severity), qs 6.15.0 → 6.16.0, and body-parser 2.2.2 → 2.3.0, which between them clear every advisory open against code this server actually ships. Lockfile only: express 5.2.1 is already the newest release, and the ranges it and router declare already allowed the patched versions, so nothing in package.json changed. The advisories left open after this are all dev-only (vite, postcss, nanoid, esbuild, and the form-data under supertest) and none reach the published image, whose final stage installs --prod and copies only dist.