# SHKeeper integration prototype — stage 5, disabled For the participant-facing explanation of what is protected today and what is not, see [Market payment transparency](market-transparency.md). This technical prototype does not create a live buyer-protection, refund or arbitration service. **This is an offline, fixture-only implementation, not live checkout.** The running marketplace remains mock-only on every profile. `paymentMode` still rejects non-mock settings, `createLiveProcessor()` always throws, and mainnet/production approval gates remain unchanged. No real invoice, callback, payout, refund or credential exchange was performed. The operator-provided SHKeeper instance was **not contacted**. Only the public API documentation was read. ## What is implemented - [XMR validation](../server/market/xmr.ts): Monero block-Base58 decoding, mainnet standard/subaddress prefix and Keccak-256 checksum checking, integer atomic-unit amount parsing (12 decimal places), bounded USD decimal parsing. Integrated/payment-ID and testnet addresses are rejected. A valid address/checksum is not proof of curve-key correctness, ownership or recipient control. Monero has tail emission; no invented fixed-supply cap is used as a validator. - [SHKeeper wire-contract harness](../server/market/shkeeper.ts): explicit injected transport only, fixed HTTPS-shaped processor origin, correct API-key versus Basic authentication, no redirects, five-second deadline, 256 KiB streamed response limit, bounded schemas/arrays and secret-safe request failures. There is no global/default `fetch`, production network transport, credential loader, environment bypass or usable callback route. Tests supply ephemeral fixture credentials and fake `Response` objects. The factory name deliberately says **Harness**. - [Durable settlement lab](../server/market/settlement-lab.ts): a separate SQLite/WAL journal, serialized operations, strict network identity and exclusive single-process lock; seller authorization of payout address/sale terms; buyer authorization of the exact invoice/address/amount/expiry and both fee allocations; authoritative invoice polling; exact confirmed payment verification; trusted canonical-delivery-reader boundary; durable payout intents; separate seller and hardcoded 1% payout legs; task/transaction allocation uniqueness; partial-success and ambiguous-outcome holds; restart reconciliation. - No web UI/action, callback handler, background worker or production `Market` path constructs the harness/lab. The prototype must not be connected to real money by simply injecting global `fetch` or changing an environment variable. ## API contract evidence Read from [public SHKeeper API docs](https://shkeeper.io/api/) version **2.1.47** on 2026-10-20, including the embedded OpenAPI definitions for `Invoice`, `Transaction` and `PayoutResultObj`: | Operation | Contract used | | --- | --- | | Create invoice | `POST /api/v1/XMR/payment_request`, `X-Shkeeper-Api-Key`, USD `amount`, unique `external_id`, required `callback_url`. Response has `id`, XMR `amount`, `wallet`, `exchange_rate`, `recalculate_after`. | | Invoice lookup | `GET /api/v1/invoices/{external_id}`, API key, `status: success`, `invoices` array. Invoice fields: `external_id`, `fiat`, `amount_fiat`, `balance_fiat`, `status` in UNPAID/PARTIAL/PAID/OVERPAID, `txs`. | | Invoice transaction | `addr`, `amount`, `crypto`, `status` CONFIRMED/UNCONFIRMED, `txid`. No numeric confirmation count is documented in this schema. | | XMR payout | `POST /api/v1/XMR/payout`, Basic authentication, `amount`, `destination`, `fee` priority 1–4. Prototype chooses 2. XMR does not support the multipayout endpoint. | | Payout status | `GET /api/v1/XMR/task/{task_id}`, Basic authentication, PENDING/FAILURE/SUCCESS; result entries contain `amount`, `dest`, success/error, optional message and `txids`. | The harness accepts numeric/string invoice IDs as documented examples differ, but requires UUID payout task IDs. It accepts quote `recalculate_after` only 0 or "0"; a floating/recalculating quote is refused rather than silently repriced. The required callback URL uses the reserved **market.invalid** domain and has no receiving route; it is a deliberate fixture placeholder that makes the create operation unsuitable for live use. Documentation alone cannot prove the deployed processor returns identical shapes/semantics. The actual instance/version, XMR enablement, precision, quote locking, fees, confirmation policy, task success meaning and failure/partial-send behavior require an explicitly authorized future integration review. A processor's `CONFIRMED` flag is not proof of an agreed XMR confirmation depth until its configured policy is independently established. Callback signing was not established by these schemas; **callback payloads never authorize payment** in this prototype. ## Authorization and accounting Seller signs the lab's domain-separated network/chain/order/card/sequence/buyer terms, USD price, hosting basis points, expiry, NFT delivery depth, payout public key/address, and fixed **1% recipient/rate**. This proof links their NFT identity to the requested XMR destination; it does not prove that they control its private spend key. Invalid checksum/network destinations fail before quote creation. After invoice creation, buyer signs the exact invoice ID, currency, address, XMR amount, exchange rate, USD terms, expiry, hosting retention, seller net/destination and fixed-fee amount/destination. Wrong buyer, modified quote and wrong signature are refused. This second authorization is distinct from the existing mock market's USD reservation command; the public marketplace has not been migrated to this protocol. Both fees are computed independently from gross XMR in integer atomic units, rounded down. Seller net = gross − hosting retention − 1% allocation. **Hosting stays in the merchant wallet**, as designed; it is not a missing third payout or burned funds. Seller net and 1% recipient require separate XMR payouts because XMR multipayout is unsupported. For 0.1 XMR and 2.5% hosting: 0.0025 retained, 0.0965 seller, 0.001 fixed recipient. A sub-atomic fixed fee rounds to zero and its leg is skipped, not inflated. Processor fees included in an invoice can alter actual gross/fiat semantics. The prototype requires exact expected fiat and crypto totals and will hold inconsistent fee-bearing cases rather than assuming compatibility. Network fees, overpayment handling, processor-fee policy and reserves must be designed/disclosed before connecting live. This is a custodial gross-receipt/net-payout design, **not** a trustless XMR/NFT exchange. ## State machine and failure rules 1. **Draft → invoice intent:** persist `invoice-submitting` before calling the fixture create endpoint. A response loss, invalid response or restart with unresolved intent enters review. The POST is not automatically repeated, even though invoice external IDs exist, because create/update behavior and outcome reconciliation must be reviewed. 2. **Quote → awaiting payment:** buyer explicitly authorizes the exact quote before payment can be recognized. Expired signatures/quotes fail. No public payable address is exposed by the running app. 3. **Payment polling:** only server-to-server-shaped lookup data is used; exactly one external-ID invoice is required. It must have the expected fiat amount, currency and transaction addresses/currency; no duplicate tx IDs; exact confirmed gross and fiat balance. Partial/unconfirmed payments wait. Overpaid, contradictory, unknown, changed or late observations enter review; there is no blind refund or automatic invoice reuse. 4. **Paid identity:** save the exact incoming tx-ID set and enforce global incoming allocation uniqueness in the lab journal. Later changes to the paid set or amounts suspend settlement. The same observed deposit cannot fund two lab orders. 5. **NFT delivery:** the reader is a trusted consensus-library boundary, never a buyer-supplied HTTP boolean. It must return the exact transfer identity (chain/card/from/to/sequence), required canonical depth and a well-formed tip. Before any payout, missing depth waits; after payout begins, changed delivery enters permanent review. Recheck invoice and delivery before each separate payout. These reads cannot make off-chain money and on-chain delivery atomic; a reorg can occur after any check. 6. **Durable payout intent:** mark a leg `submitting` and commit **before** its create POST. Save its task ID under a unique constraint. Transport timeout, malformed result, task-ID collision, interrupted persistence or restart with no acknowledged task ID becomes `ambiguous`/review. Never retry that payout automatically. 7. **Known tasks:** repeat status GETs only. PENDING waits. SUCCESS must contain exactly one successful result matching amount/destination, at least one well-formed distinct transaction ID and no transaction ID already allocated to another leg/order. FAILURE, malformed success, amount/address mismatch or duplicate evidence enters review. A known task query failure does not create another POST. 8. **Partial success:** seller completion is durably recorded before the fee leg. If fee fails, preserve the seller's success and hold the remainder. There is no rollback, re-payment of the seller or assumption that the entire sale was unpaid. Reorg after completion holds without paying again. `expiresAt` is an invoice/payment-authorization deadline, not a deadline that cancels a correctly paid sale while NFT delivery is being mined. Refusing payout solely because a legitimate invoice later expires would not be a safe general settlement policy. Delivery timeouts/disputes and reserves remain unimplemented live-policy requirements. ## Journal, recovery and activation holds The lab uses a **separate settlement-lab.sqlite** and lock under a caller-supplied disposable directory. It is **not** inside the production two-database backup contract, not exposed by current operations diagnostics and not an authoritative production accounting system. Current node backups do not protect it. Test fixtures reopen the lab to verify intent/task persistence; this is not a production processor reconciliation tool. Do not delete a stale lab lock unless its owning process is verified stopped. There is no operator override/retry/refund endpoint. `review` is intentionally not auto-recovered. The lab retains privacy-sensitive participant/payout/transaction data and signatures but **not processor credentials**. Trusted local filesystem ownership and external encryption remain necessary. Schema inspection validates identity, but this is not tamper-resistant accounting against an operator who controls the DB. Before live integration can be reviewed/activated, the following remain required: - Production network readiness and independent custody/consensus/application review; explicit operator payment activation, separate from network startup. **No live approval variable is implemented in this release.** - A production adapter with restricted server-only secret loading, approved fixed origin/DNS/egress controls, no redirects, TLS review, least-privilege credentials and a verified deployed API contract. Never put credentials in Vite/browser variables or code. - Real-market journal/schema, backup/restore/monitoring integration and migrations; typed authenticity validation of persisted authorization/amount records; trusted canonical delivery lookup; exposure only through participant-authorized APIs, not direct lab methods. - Actual quote presentation/authorization UX, seller payout-address registration/change authorization, minimum order/payout policy, exact fees and network-cost/reserve treatment. - Verified XMR confirmation policy and task success/failure semantics; bounded callback hints only; late/partial/overpayment/refund and non-delivery rules. Previously signed NFT transfers may arrive after a proposed refund—no auto-refund atomicity claim is made. - Durable operator reconciliation tooling for ambiguous create/payout requests, partial sends, known task failures, reorgs and external wallet/task history. Timeout is **not** failure/no-send evidence. Never erase an intent or re-submit to make tests/operations appear healthy. - Controlled end-to-end processor tests only after specific approval; this work did not request secrets, create invoices, transfer XMR or access the configured instance. No fund movement is authorized by this document. ## Tests Final Node 24 TypeScript/Vite build passed. The full suite ran in a Docker container with **external networking disabled**: **119 tests passed, 0 failed, 0 skipped**, including existing native RandomX loopback integration. No real SHKeeper endpoint or credential was used; only public documentation retrieval occurred outside those offline tests. Offline fake transports cover endpoint auth separation, bounded bytes/redirect/type failures, five-second abort, fixed quote policy, XMR checksum/network/decimal rejection, seller/buyer quote binding, confirmed exact payment/depth, duplicate inbound allocation, duplicate payout tasks/tx IDs, concurrent advancement, response-loss ambiguity, restart quarantine, known-task polling, seller-success/fee-failure, mismatched payouts, changed paid evidence and post-payout reorg hold. Existing mock marketplace/backup/lock/launch and native RandomX tests remain in the full suite. This is partial engineering groundwork for SEC-04/SEC-05, not closure of those production findings. Live payments remain hard-disabled.