π Unfamiliar terms? See the glossary for definitions.
Status: π’ Production-proven on-chain β first transaction 2026-09-01 (TXID 05301369β¦); Nostr transport working end-to-end 2026-09-16 (TXID 17cfed7fβ¦)
Flow version: Merchant-first (replaced recipient-first after architecture review, Aug 31 2026)
A recipient converts the BCH they hold into local currency at a merchant. From the merchantβs side, the merchant buys BCH and pays fiat. The two parties co-sign one transaction that spends the recipientβs covenant via the merchantCashout() path.
In Asgayaβs language the recipient sells BCH and the merchant buys BCH β never βmoney transfer.β This framing is deliberate (compliance): the two are trading an asset, not moving money.
The original design had the recipient pre-sign first. Architecture review found this was wrong:
Merchant-first fixes all three: the merchant fetches a fresh oracle when they quote, provides their own pubkey, and controls when to broadcast.
The three messages are transport-agnostic. Nostr is the primary transport (works remotely); a two-way QR exchange is the offline fallback (the recipient needs no connectivity). The same messages work over Telegram for testing.
Recipient (Elena) Merchant (Carlos)
βββββββββββββββββ βββββββββββββββββ
[BCH_FOR_SALE] βββββββββββββββββββββββββΊ
(covenant params, no signatures) fetches FRESH oracle,
builds + pre-signs
[BCH_PURCHASE_COSIGN] βββββββββββββββββββ
(partial tx, merchant pubkey,
oracle data, quoteId, expiry)
verifies freshness + floor, co-signs
[SIGNED_TX] ββββββββββββββββββββββββββββββΊ
broadcasts (BCH-first),
then hands cash
[BCH_FOR_SALE]. Only the covenant parameters β no signature, no oracle, no merchant info. The recipient can send it to any merchant (no pre-commitment).[BCH_PURCHASE_COSIGN]. The merchant fetches a fresh oracle, builds the transaction (output 0 = merchant, output 1 = funder) and pre-signs. The quote carries an expiry and a unique id, so a stale price canβt be reused later and only the live quote is honoured.[SIGNED_TX]. The recipient verifies and co-signs, then returns the fully-signed transaction.Liveness = response. There is no heartbeat or presence system: the merchantβs reply is proof it is online, and the recipient is online because it just sent the request. The exchange finalises in seconds.
The same three messages, shown as QR codes: recipient shows [BCH_FOR_SALE] β merchant shows [BCH_PURCHASE_COSIGN] β recipient shows [SIGNED_TX] β merchant broadcasts and hands cash. In this path the recipient needs no connectivity at all; only the merchant needs internet.
[BCH_FOR_SALE]:
[BCH_FOR_SALE]
covenantAddress=bchtest:p...
senderPubkey=...
recipientPubkey=...
funderPubkey=...
oraclePubkey=...
eurCents=900
expiryOracleTime=...
initialBchPriceInCents=65000
minPricePercent=93
[/BCH_FOR_SALE]
[BCH_PURCHASE_COSIGN] carries the partial transactionHex, merchantPubkey, merchantAddress, funderAddress, the fresh oracleSig/oracleMessage, the price/amount display fields, and a quoteId + expiresAt. [SIGNED_TX] carries covenantAddress, quoteId, and the signed transaction hex.
Legacy formats
[CASH_IN_PERSON]/[CASHOUT_REQUEST]come from the earlier recipient-first flow and are kept only for backward compatibility.
The recipientβs signature (SIGHASH_ALL) commits to the exact output amounts, and those amounts depend on the oracle price (paymentSats = eurCents Γ 1e8 / price, covenant-enforced). So:
The exchange is structurally required β not a UX inefficiency, but the covenant working as designed.
| Path | Recipient online? | Merchant online? | Use case |
|---|---|---|---|
| Nostr (primary) | Yes | Yes | Remote / low friction |
| QR two-way (offline fallback) | No | Yes | Face-to-face, offline recipient |
| Telegram | Yes | Yes | Testing / async fallback |
outputs[0] = paymentSats to merchantPubkey, outputs[1] = remainder to funder).merchantCashout()Status: Production-proven (on-chain); Nostr transport working end-to-end
Last Updated: 2026-09-16