Files
nick-doc/08 - Operations/Handoff - Request Network In-House Checkout - 2026-05-28.md
Siavash Sameni 85cb439ce2 docs: Task #8 probe results + handoff + PRD AC updates
- Add Handoff - RN Multichain Probe - 2026-05-28.md
- Update Handoff - Request Network In-House Checkout with Task #8 status
- Update Activity Log with backend@ae17b18, frontend@0ebb2f1
- Update PRD §2 acceptance criteria for Task #8
- Update Payment API.md with /api/admin/rn/networks endpoints
2026-05-28 19:53:06 +04:00

18 KiB

Handoff: Request Network In-House Checkout — 2026-05-28

Status: fully end-to-end working on dev.amn.gg as of 2.6.38 backend / 2.6.41 frontend. A 0.01 USDC payment (tx 0x494c77a2…) flowed: page render → wallet connect (Rabby/injected) → approve → transferFromWithReferenceAndFee → RN webhook → backend marks completed → page flips to "پرداخت تأیید شد ✓" → continue → /dashboard/payment/<id>.

What's live

  • Backend 2.6.38/api/payment/request-network/intents returns an inHouseCheckout block (destination, tokenAddress, decimals, chainId, proxyAddress, paymentReference 8-byte hex, feeAmount, feeAddress, amountWei). GET /api/payment/request-network/:paymentId/checkout rehydrates the block for an existing Payment record (lazy-enriches legacy records that pre-date 2.6.34 by calling RN's GET /v2/request/:id). Public GET /api/version for the version badge. PaymentCoordinator.updatePurchaseRequestStatus guards both template-checkout- and template-tc- prefixes (plus regex fallback for any non-ObjectId) — earlier the template-tc- blindspot crashed webhook processing on template-checkout payments and stranded escrow.
  • Frontend 2.6.41/checkout/request-network/[paymentId] page with wagmi state machine: connect → switch-chain → check-allowance → approve → pay → wait-for-webhook. Destination + payment-reference + approve-tx + pay-tx hashes are copyable and click through to BscScan. Once a pay tx is in flight the page no longer reverts to "approve" even though the proxy call consumed the allowance. A 10-second GET /api/payment/:id poll runs as a fallback when the socket misses payment-update. Success-state continue button handles synthetic purchaseRequestId prefixes (template-checkout-, template-tc-) by routing to /dashboard/payment/<id> instead of the 404-prone /dashboard/request/<syntheticId>. WagmiProvider is now rendered unconditionally + the checkout page also self-wraps in its own WagmiProvider for defensive isolation.

Verify which versions are running by hovering the version chip at bottom-left of any page on dev.amn.gg, or curl https://dev.amn.gg/api/version.

Where things stand

A real 0.01 USDC payment ran clean through the in-house path on 2026-05-28. Webhook delivery is durable enough for dev usage; durability for prod is Phase 5 (Cloudflare Worker ingress, not started). Five follow-up tasks were scoped immediately after — see PRD - Wallet, Multichain, Confirmations, AML, Trezor.md and Taskmaster #7..#11.

Known issues / open work

  • TypeScript-error CI false-success: pipelines #40 and #41 reported green in Woodpecker while yarn build was actually failing at the TS step and no image was pushed. Memory entry: woodpecker_silent_build_fail.md. Always verify dev-<version> exists in git.manko.yoga before trusting CI green. The wagmi chainId field requires as any because of its literal-union type — keep that pattern when adding new wagmi calls.
  • Existing/legacy Payment records (created before backend 2.6.34) don't have RN's request details cached. The GET endpoint lazy-enriches them via GET /v2/request/:requestId on first visit, then persists. If RN's API is down at that moment, falls back to the hosted-page link.
  • Mongo access is denied to the auto-mode classifier on dev — debugging payment records currently requires either the user's mongo creds or relying on the 409 debug block surfaced through the frontend.
  • Wagmi provider isolation (2.6.39): The checkout page wraps itself in its own WagmiProvider. The root Web3Provider also renders WagmiProvider unconditionally as of 2.6.38. The doubling is intentional defensiveness — if one provider has an issue, the other still serves the checkout flow. Can be simplified later if both prove stable.
  • PRD Phase 5 — Cloudflare Worker durable webhook ingress — not started. Taskmaster 3.13. Current dev relies on dev.amn.gg being up at the moment RN's webhook fires. For prod, RN webhooks need to land in a durable Cloudflare Worker that buffers + replays into the backend.

Files changed (recent)

Backend (/Users/manwe/CascadeProjects/escrow/backend):

  • src/services/payment/requestNetwork/contract.ts — spreads full RN response into raw
  • src/services/payment/requestNetwork/inHouseCheckout.ts — block builder, reads paymentReference from rnRaw.requestDetails.paymentReference
  • src/services/payment/requestNetwork/merchantReference.ts, tokens.ts, proxyAddresses.ts, paymentReference.ts — helpers
  • src/services/payment/requestNetwork/requestNetworkPayInService.ts — calls GET /v2/request after intent creation
  • src/services/payment/requestNetwork/requestNetworkRoutes.tsGET /:paymentId/checkout + lazy enrichment + debug response
  • src/services/payment/requestNetwork/networkClient.ts — already had getRequestStatus
  • src/app.tsGET /api/version, exempt from rate limit
  • __tests__/rn-in-house-checkout.test.ts — 12 unit tests, all green

Frontend (/Users/manwe/CascadeProjects/escrow/frontend):

  • src/web3/contracts/rn-fee-proxy.ts — RN proxy + ERC20 ABIs
  • src/web3/context/wagmi-provider.tsx — removed the mount-gate that caused WagmiProviderNotFoundError
  • src/web3/components/provider-payment.tsxrouter.push to in-house page + sessionStorage stash
  • src/sections/payment/checkout/types.ts + rn-in-house-checkout-view.tsx — state machine, local WagmiProvider wrap
  • src/app/checkout/request-network/[paymentId]/page.tsx — app router entry
  • src/components/version-logger.tsx — version chip + tooltip showing backend version

Memory entries added

  • MEMORY.md index updated with:
    • arcane_dev_stack.md (env/project IDs, two-step deploy note)
    • woodpecker_silent_build_fail.md (CI green ≠ image pushed)
    • and existing rn_webhook_event_field.md, backend_rate_limits.md, telegram_notify_no_parse_mode.md, devEscrow_nginx_after_redeploy.md, parallel_agents_on_escrow.md

Open PRD questions still to decide

From PRD - Request Network In-House Checkout.md §10:

  • Proxy address universality across chains (currently BSC + Arb confirmed; Task #8 will probe Polygon/ETH/Base)
  • API pricing for hosted-UI-less usage (need RN account-mgmt question)
  • Approval UX — exact-amount vs MAX_UINT256 (current: exact-amount)
  • Cancel / timeout semantics for abandoned intents
  • Telemetry events for in-house vs hosted A/B

Follow-up tasks (Taskmaster + PRD)

Five follow-ups scoped for kimi to pick up independently. Full spec in PRD - Wallet, Multichain, Confirmations, AML, Trezor.md. Quick index:

# Task Priority Status
7 Per-(buyer, sellerOffer) ephemeral RN destination wallets high 🟡 In progress — backend + admin UI + cart-aware UX + tests shipped in 2.6.45/2.6.46; live RN-accepts-divergent-destination probe remains manual
8 Multichain RN proxy registry + USDC/USDT support high 🟡 In progress — backend registry + probe script + frontend admin page + USDT-mainnet reset shipped in 2.6.46; BSC USDT paid probe + Base proxy address hunt remain
9 Per-chain confirmation thresholds + admin UI medium Not started
10 Optional AML screening on incoming payments (seller-paid) medium Not started
11 Trezor signing for admin actions (release/refund/sweep) high Not started

Task #7 — what landed in 2.6.42

Backend (backend/src/services/payment/wallets/ + plumbing)

  • DerivedDestination model: (buyerId, sellerOfferId, chainId) → address, derivation path, status, sweep history.
  • derivedDestinations.ts: xpub-driven HD address derivation, atomic counter-based index allocation, idempotent getDestinationFor, race-safe upsert. Backend holds DERIVED_DESTINATION_XPUB only — master seed lives in KMS / Trezor (Task #11).
  • sweepService.ts: pluggable signer abstraction (build-only default; hot-key for dev), ERC-20 balance queries, sweep orchestration, interval-based cron.
  • derivedDestinationRoutes.ts: admin-only REST endpoints (list, sweep-all, sweep-one, config health, cron start/stop/status). Mounted at /api/payment/derived-destinations.
  • requestNetworkPayInService.ts now calls getDestinationFor(buyer, sellerOffer, chainId), builds the per-payment merchant reference via buildMerchantReference, persists metadata.derivedDestination, and passes the override to RN.
  • inHouseCheckout.ts accepts a destinationOverride; the on-chain paymentReference compute-fallback now uses the actual destination (previously read parsed.recipient — hidden bug because RN's response provides the ref directly, but the fallback was broken for derived destinations).
  • TransactionSafetyProvider.resolveExpectedRecipient checks metadata.derivedDestination.address first, then legacy fallback.

Frontend (admin only)

  • /dashboard/admin/derived-destinations page (table view, filters by status/chain/address, pagination, sweep-all, cron start/stop).
  • Per-row UI: address with copy + BscScan link, status chip, derivation path, balance, sweep count, last sweep tx link.

Env additions (see backend/.env.example):

  • DERIVED_DESTINATION_XPUB — required for address derivation.
  • DERIVED_DESTINATION_XPRIV — only when DERIVED_DESTINATION_SWEEP_SIGNER=hot-key (dev shortcut).
  • DERIVED_DESTINATION_BASE_PATH=m/44'/60'/0'
  • DERIVED_DESTINATION_CHAIN_ID=56
  • DERIVED_DESTINATION_SWEEP_SIGNER=build-only
  • DERIVED_DESTINATION_MIN_SWEEP_AMOUNT=0
  • DERIVED_DESTINATION_SWEEP_INTERVAL_MS=300000

Task #7 — completion status

Item Status Notes
Backend model + HD derivation DerivedDestination, derivedDestinations.ts, counter-based index
Sweep service + cron sweepService.ts, build-only/hot-key signers, auto-start in app.ts
Admin API + UI /api/payment/derived-destinations/*, admin dashboard page
RN intent integration requestNetworkPayInService.ts passes per-seller destination
Transaction Safety Provider resolveExpectedRecipient checks metadata.derivedDestination first
A — Cart-aware buyer UX MultiSellerProviderPayment + RnMultiCheckoutView + template checkout wiring
D — Auto-start sweep cron app.ts boots cron when DERIVED_DESTINATION_SWEEP_AUTOSTART=true
E — recordSweep accumulation fix $inc: { totalSwept } instead of $setOnInsert
F — API docs Derived-destination endpoints added to Payment API.md
B — Unit tests 46 tests across 3 files (see below)
C — Live divergent-destination probe 🔄 Protocol prepared; requires manual browser + wallet execution (see §Live multi-seller probe below). Dev is running 2.6.46 with all backend/frontend code deployed.

Task #8 — completion status

Item Status Notes
Probe script scripts/probe-rn-chains.ts — 4/5 chains verified (BSC, Arbitrum, Ethereum, Polygon). Base proxy not deployed at canonical address.
Token registry JSON tokens.json — USDC + USDT on 4 verified chains with correct decimals
Chain registry JSON supportedChains.json — 4 verified chains + Base in _unverified
Admin route GET /api/admin/rn/networks, POST /api/admin/rn/networks/reload
Frontend admin page /dashboard/admin/networks renders registry with reload button
unsupported_chain reason buildInHouseCheckoutBlock returns unsupported_chain:<id>
Frontend wagmi multichain Added arbitrum + base to wagmi config with RPC transports
Per-chain explorers Checkout view uses correct explorer per chainId
USDT-mainnet approve reset Implemented but unverified — see note in Handoff - RN Multichain Probe - 2026-05-28
BSC USDT paid probe 🔄 Pending manual execution — needs real wallet + test BSC USDT
Base proxy hunt 🔄 Canonical address has no code on Base. Need RN official docs for actual address.

B — Unit tests (backend@34f542e, 2.6.45)

__tests__/derived-destinations.test.ts (26 tests)

  • validateXpub rejects xpriv, tprv, garbage, empty, null/undefined
  • deriveAddressAtIndex is deterministic; different indices → different addresses; checksummed; rejects negative/non-integer
  • getDestinationFor idempotency: same (buyer, sellerOffer, chainId) returns same row, counter increments exactly once
  • getDestinationFor E11000 race fallback: simulates concurrent insert, verify second caller re-reads racer's row
  • getDestinationFor non-E11000 errors are re-thrown
  • recordSweep $inc accumulation: run twice, assert totalSwept equals sum (regression lock-in for item E)
  • recordSweep handles string and negative amounts
  • resolveExpectedRecipientForPayment prefers metadata.derivedDestination, falls back to blockchain.receiver
  • listDerivedDestinations pagination
  • verifyDerivedDestinationConfig ok / missing xpub / invalid xpub

__tests__/sweep-service.test.ts (18 tests)

  • getSweepSigner returns build-only by default, hot-key when configured
  • queryTokenBalance parses bigint, returns 0n for empty balance, null on RPC failure, null for unsupported chain+token
  • sweepDerivedDestinations skips below-threshold balances, dry-run returns amount without broadcasting, build-only signer returns error without updating record, handles balance query failure, respects destinationIds filter, throws when master wallet missing
  • Cron lifecycle: start/stop/idempotent/zero-interval

__tests__/request-template-orphan-cleanup.test.ts (2 tests) — non-negotiable regression lock-in for Gap 2 fix

  • Asserts Payment.find during orphan cleanup is scoped to provider: 'shkeeper'
  • Asserts a pending provider: 'request.network' Payment is NOT deleted when a shkeeper orphan exists for the same buyer
  • Asserts request.network orphan is untouched even when no shkeeper orphan is present

C — Live multi-seller probe protocol

Goal: Prove RN accepts divergent destinationId across consecutive POST /v2/secure-payments from the same buyer session, and that the multi-checkout cart UX creates two Payments landing on two different derived addresses.

Prerequisites:

  • Dev backend running ≥ 2.6.45 with DERIVED_DESTINATION_XPUB configured
  • Dev frontend running ≥ 2.6.44
  • Two seller accounts on dev.amn.gg with wallet addresses set
  • Buyer account with a Rabby/Metamask wallet holding ≥ 0.02 testnet BSC USDC

Steps:

  1. Create two template offers (one from each seller):

    • Seller A: https://dev.amn.gg/dashboard/shops/templates/new → fill title, price 0.01 USDC, publish
    • Seller B: same, price 0.01 USDC, publish
    • Capture both shareableLink values
  2. As buyer, add both to cart:

    • Visit Seller A's shareable link → Add to cart
    • Visit Seller B's shareable link → Add to cart
    • Go to /dashboard/shops/checkout/?step=2
    • Confirm cart shows 2 items from 2 different sellers
  3. Select crypto payment and proceed:

    • Choose "پرداخت با Request Network"
    • Click the multi-seller button (should show ۲ فروشنده)
    • Browser navigates to /checkout/request-network/multi?session=<id>
  4. Pay Seller A (first checkout page):

    • Connect wallet
    • Approve 0.01 USDC for RN proxy
    • Call transferFromWithReferenceAndFee
    • Wait for "پرداخت تأیید شد ✓"
    • Click "ادامه به پرداخت بعدی"
  5. Pay Seller B (second checkout page):

    • Approve 0.01 USDC (or reuse allowance if proxy unchanged)
    • Call transferFromWithReferenceAndFee
    • Wait for confirmation
    • Click "پایان"
  6. Capture evidence:

    • Derived addresses: Check GET /api/payment/derived-destinations?buyerId=<buyerId> — expect 2 rows with different address and derivationIndex
    • Tx hashes: Copy both approve and both pay tx hashes from the UI; verify on https://testnet.bscscan.com (or mainnet BscScan if dev uses mainnet)
    • Transfer events: On BscScan, verify both TransferWithReferenceAndFee events show different recipient addresses
    • Webhooks: Check backend logs for payment-update socket emissions or POST /api/payment/request-network/webhook handling for both payments
    • Payment records: Query Mongo for the two Payment docs — both should have status: 'completed' and different metadata.derivedDestination.address
    • PurchaseRequests: Verify convertTemplatesToRequests created 2 PurchaseRequest docs with status: 'payment'
  7. Document: Paste the full evidence block below under "Live multi-seller probe — execution record".


Live multi-seller probe — execution record

To be filled after manual execution of the protocol above.

Field Value
Date
Buyer ID
Seller A ID / Offer
Seller B ID / Offer
Derived address A
Derived address B
Approve tx A
Pay tx A
Approve tx B
Pay tx B
Payment ID A
Payment ID B
PurchaseRequest ID A
PurchaseRequest ID B
RN webhook fired for both?
Both Payments completed?
Escrow funded for both?

Remaining in task #7:

  • Item C: execute the live probe protocol above and fill the execution record table.
  • After C passes: flip PRD §1 acceptance criteria #1 and #2 from and mark Task #7 done in Taskmaster.