77 lines
10 KiB
Markdown
Executable File
77 lines
10 KiB
Markdown
Executable File
# ADR 004 — Custodial NFT sale-lock candidate
|
|
|
|
Status: **offline executable candidate; NOT activated or independently reviewed**. Evidence: 2026-10-20. Related: [production plan](production-plan.md), [custodial model](custodial-market-design.md), [PostgreSQL storage](postgres-financial-storage.md).
|
|
|
|
## Purpose and activation boundary
|
|
|
|
Specify the missing asset-control rules before enabling real custodial purchases. An ordinary signed NFT transfer can survive a website refund deadline; balance reservations alone cannot prevent that late delivery. This candidate couples sale authorization to explicit canonical locks and terminal outcomes.
|
|
|
|
[Signed schemas/messages](../shared/sale-candidate.ts) and [deterministic state machine](../server/testnet/sale-candidate.ts) are deliberately separate from active consensus. The existing transaction schema rejects all candidate transactions. The candidate domain derives from the existing chain identity, explicit policy and candidate version; it is an offline signature domain, **not a reviewed network manifest/genesis**. No current chain identity, miner, peer endpoint, database balance or custody signing capability is changed.
|
|
|
|
The pure `termsForCustodyOrder()` bridge drafts terms from an actual awaiting-lock reservation and checks exact seller/author/host allocations. It does not sign, persist, relay or debit anything. Tests exercise this bridge against the current custody engine. Public custody orders continue stopping at `awaiting-lock`.
|
|
|
|
## Signed authorization
|
|
|
|
Terms bind candidate protocol, network, chain/policy digest, custodian/order IDs, exact card/sequence, seller, buyer, host signing identity, native-XMR gross, hosting rate, fixed author policy, lock-admission deadline and final expiry height. Independent downward commission rounding remains unchanged. No production threshold is invented: maturity/depth/window limits are explicit policy inputs committed by the candidate digest.
|
|
|
|
- **Lock:** both seller and buyer sign the complete terms. Host signing identity must differ from both trading identities. Distinct keys are role separation, not proof of independent humans/operators.
|
|
- **Completion:** the terms' exact host signs the sale ID plus a reservation digest, attesting that sufficiently confirmed/unlocked funds were reserved under the approved policy. Host cannot select another card/recipient/price. Validators would verify the host's authorization, **not Monero truth**. The reservation digest is an accounting commitment, not a cryptographic proof of backing or solvency.
|
|
- **Cancellation:** host and buyer sign the sale ID. Neither can unilaterally cancel early. Seller already consented to this branch in signed lock terms; seller retains card ownership on cancellation.
|
|
- **Expiry:** permissionless explicit transaction, without a private key, once the canonical expiry height is reached. Website clocks/RPC errors never cause implicit release.
|
|
|
|
## State transitions and exact heights
|
|
|
|
Let $h$ be the block executing a transaction, $L$ the accepted lock height, $A$ the last lock-admission height, $E$ the expiry height, $d_L$ the lock-depth policy and $d_O$ the outcome-depth policy.
|
|
|
|
| Transition | Validity / result |
|
|
| --- | --- |
|
|
| Unlocked → locked | Both signatures, mature current seller ownership/sequence, no existing lock, unused custodian/order; $h\le A$, $A+d_L<E$, and bounded remaining window. Ownership unchanged; sequence advances once. |
|
|
| Locked → completed | Exact host signature, $h-L\ge d_L$ and $h<E$. Ownership moves only to the terms' buyer; sequence advances again. |
|
|
| Locked → cancelled | Host+buyer signatures and $h<E$. Ownership remains seller; sequence advances again. |
|
|
| Locked → expired | Explicit permissionless transaction with $h\ge E$. Ownership remains seller; sequence advances again. |
|
|
| Free transfer/trade | Every affected NFT must be unlocked, correctly owned/sequenced, mature and signed. A locked card in any trade leg rejects the entire transition. |
|
|
|
|
Only one terminal branch can succeed on a canonical history. An invalid transaction rejects the entire candidate block, not just that transaction; two conflicting outcomes in one block therefore reject the block. Block assembly/mempool policy will eventually need to filter these conflicts before proposing a block.
|
|
|
|
Lock and terminal transitions both consume sequence numbers, invalidating ordinary transfers signed before/during a lock. Admission requires room for **two** increments so terminal release cannot overflow. Expired/cancelled order-use tombstones prevent a fresh signature from recycling the same custodian/order ID. A new order can relist at the fresh sequence. A withheld lock authorization cannot be accepted after $A$, closing the never-submitted-lock refund hazard.
|
|
|
|
Candidate state commitment includes NFT entries, active locks, full signed-term messages, lock/terminal heights, reservation commitments and permanent order-use tombstones. Ownership alone would be an insufficient state commitment. Snapshot restore/replay is deterministic. This uses full cloned state and tombstone retention for an auditable model, **not a production-optimized overlay or storage design**.
|
|
|
|
## Financial observation and reorg discipline
|
|
|
|
`saleDecision()` is a pure non-posting recommendation based on a caller-supplied canonical candidate state:
|
|
|
|
- Keep the gross reserved while a lock could still be admitted, while it is active, or before a terminal outcome reaches $h-\text{outcomeHeight}+1\ge d_O$.
|
|
- Recommend seller/commission settlement only after mature canonical completion.
|
|
- Recommend buyer release only after mature canonical cancellation/expiry. If no lock was ever canonical, require $h-A\ge d_O$ before recommending release.
|
|
- Already posted outcomes never post twice. A conflicting or immature/missing previously posted outcome requires `review`, not another settlement/refund. Existing review remains review pending evidence-backed resolution.
|
|
|
|
This does not independently prove the supplied canonical state or observe intermediate forks. Integration must consume the existing durable connect/disconnect stream and retain a review hold even if an outcome reappears before polling. Deep reorgs can invalidate previously mature results and external XMR withdrawals cannot be undone. Loss/reserve/compensation procedures remain mandatory.
|
|
|
|
An already mature terminal record remains usable evidence if the NFT is later gifted/traded; current owner alone must not erase completed sale evidence. Conversely, a gift is not payment evidence. Cancel/expiry can be followed by a freshly signed free transfer at the new sequence; this is ordinary owner authority, not resurrection of the cancelled sale.
|
|
|
|
## Failure behavior and trust
|
|
|
|
| Failure | Candidate behavior / remaining risk |
|
|
| --- | --- |
|
|
| Seller lists elsewhere or withholds lock | Competing canonical transfer/lock wins by ordinary sequence rules; funds remain reserved until safe cancellation/non-admission boundary. No cross-host database claim is treated as a chain lock. |
|
|
| Buyer disappears after signing lock | Host may complete only to the signed buyer; otherwise eventual expiry restores seller control. Financial operator still owes customer funds. |
|
|
| Host disappears or loses key | Anyone can submit expiry after the height boundary. A stalled chain delays expiry; this does not recover custodial XMR keys. |
|
|
| Host lies about funding | Valid host signature can authorize delivery despite absent reserves. This is explicit custody/attestation trust; the protocol cannot enforce the honesty of an internal balance claim. |
|
|
| Cancel and complete race | Only canonical sequence/lock state chooses a winner; no refund before terminal evidence matures. Mempool order is not settlement. |
|
|
| Reorg after posting or withdrawal | Review/quarantine, never imaginary reversal of XMR. Operator loss handling and funded limits needed. |
|
|
| Host key compromise/rotation | Existing locks bind an exact key. Rotation/revocation and emergency authority are NOT implemented; do not silently redirect existing authority. |
|
|
|
|
## Evidence and next release gates
|
|
|
|
TypeScript/Vite Docker build passed. **162 offline tests passed, zero failures/skips**, including 13 candidate tests in [tests/sale-candidate.test.ts](../tests/sale-candidate.test.ts). Coverage includes current-consensus rejection, field/signature tampering, exact height boundaries, sequence exhaustion, native transfer/trade lock bypass, competing-market/reused-order locks, atomic conflicting-outcome rejection, reorg financial decisions, deterministic snapshot restore, state commitments, actual custody-order drafting and enumerated adversarial histories through expiry. One initial enumeration-count expectation was corrected by extending its horizon through the expiry boundary; no protocol guarantee was inferred from an arbitrary test count.
|
|
|
|
**This does not close M3.** Required next steps:
|
|
|
|
1. Independent protocol/consensus review and operator approval of authority, cancellation and numeric policy parameters; final specification and test vectors.
|
|
2. Versioned network/manifest and explicit migration/activation strategy. No enabling the candidate under the existing v6 chain identity.
|
|
3. Integrate new transaction encodings/state commitments with mint/transfer/trade validation, canonical SQL undo/indexes/events, actual RandomX blocks, peer sync, restart and multi-host fork/partition tests. The current model supplies no PoW, issuance or fork-choice implementation.
|
|
4. Durable PostgreSQL sale witness/terms storage, financially reserved host-signing workflow, outbox fencing, canonical observer/inbox, atomic terminal postings and reorg holds. Existing sandbox signatures do not authorize the new sale protocol. No signing before final exact order consent.
|
|
5. Publication/review UI, key management/rotation, privacy and public-price disclosure, window/griefing/storage limits and operational recovery. Public terms would reveal sale linkage and amounts; privacy acceptance is a product decision.
|
|
6. Monero confirmation/unlock observation, real treasury controls, coordinated financial/node recovery and independently reviewed production release gates. No live wallet, funds or credentials were used in this increment.
|