Files
nick-doc/PRD - Request Network In-House Checkout.md
Siavash Sameni 0060b16912 docs: ship in-house RN checkout, scope 5 follow-up tasks (#7-11)
In-house Request Network checkout went fully end-to-end on dev today.
A real 0.01 USDC payment flowed through wallet connect -> approve ->
ERC20FeeProxy.transferFromWithReferenceAndFee -> RN webhook ->
TransactionSafetyProvider -> Payment.status=completed -> page success
state. Tx 0x494c77a29161b5100d8e0b1ac675f1822955d0bb3633ecdbfafb886f84f2f320.

Docs:
- New PRD: Wallet, Multichain, Confirmations, AML, Trezor
  (5 follow-ups, each sized for an independent contributor)
- Updated PRD: Request Network In-House Checkout (phases 0..3 done,
  phase 4 partial, phases 5-6 not started)
- Updated handoff: deployed versions, what is working end-to-end,
  follow-up tasks index

Taskmaster: 5 new top-level tasks (#7..#11) covering ephemeral
destination wallets, multichain proxy registry + USDC/USDT, runtime
confirmation thresholds, optional seller-paid AML screening, and
Trezor signing for admin actions. Tasks are scoped fine-grained so
each is independent enough for kimi to pick up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 15:50:24 +04:00

128 lines
8.8 KiB
Markdown

# PRD: Request Network In-House Checkout
> Source spec: `.taskmaster/docs/prd-request-network-in-house-checkout.md`
> Status: **Draft — updated after 2026-05-28 dev webhook probe**
> Related: `01 - Architecture/Request Network Integration Constraints.md` §1
---
## Problem
Buyers paying through Request Network are redirected to `pay.request.network/?token=…`. That page doesn't support **Rabby** (a meaningful slice of our user base), takes the buyer off `amn.gg` mid-flow, and wraps the payment in Safe + ERC-4337 + Pimlico paymaster + public BSC RPC — a stack we already saw rate-limit in real use.
The RN *protocol* is fine. Their *UI* is the problem.
## 2026-05-28 reality update
The dev BSC probe changed the risk model:
- Test transaction: `0x3a23febd9abd43d7e0851c1ea86c4ceaf08c11098852cb0425fa074e9c88350b`.
- On-chain result: successful BSC USDC transfer to Amanat's configured destination wallet.
- RN webhook result: RN did call `POST /api/payment/request-network/webhook` on `dev.amn.gg` four times from `34.34.233.192`.
- Application result: backend returned `404` because the handler only correlated one local provider id shape and missed the RN request/payment-reference fields stored on the `Payment` record.
- Frontend result: callback stayed on "processing payment" and the 3-second polling loop later hit `429`.
So the original "webhook silence" risk is no longer the observed failure. RN delivered the webhook; Amanat dropped it. Before another paid probe, dev must run the correlation fix and the callback polling fix, then pass the Request Network webhook smoke test.
## Core idea
RN's webhook detects payments by listening for a single on-chain event — `TransferWithReferenceAndFee` — emitted by their `ERC20FeeProxy` contract. We confirmed this by cold-inspecting an actual `$12` payment on their hosted page: under the Safe/4337 wrapper, the real work is two calls — `approve(proxy, amount)` and `transferFromWithReferenceAndFee(...)`.
If we make those same two calls from our own UI, RN's webhook fires. The hosted page is decorative.
However, webhook delivery alone is not enough to credit escrow. The backend now needs a **Transaction Safety Provider** gate between "provider says paid" and "Amanat marks funded". That provider approves only after configured checks pass: transaction hash present, chain confirmations deep enough, transfer recipient/token/amount matched on-chain, and any external AML/sanctions provider response is acceptable.
## What we replace
| Today | Proposed |
|---|---|
| Click "Pay with RN" → `window.location = securePaymentUrl` | Click "Pay with RN" → `/checkout/request-network/:paymentId` (Amanat-rendered) |
| RN's wallet picker (no Rabby) | wagmi `injected()` + `metaMask()` — Rabby works via EIP-6963 |
| Safe + 4337 + paymaster | Buyer's EOA, two direct txs, buyer pays own gas |
| Public BSC RPC | Whatever the buyer's wallet uses (under their control) |
## What does NOT change
- Backend webhook route stays the same, but the handler must correlate every RN reference shape (`providerPaymentId`, request id, payment reference, nested raw data) and pass the Transaction Safety Provider before marking a payment funded.
- `Payment` lifecycle is unchanged.
- Settlement, refunds, dispute paths are unchanged.
- Non-RN payment providers (SHKeeper, etc.) are unaffected.
- Buyer can still opt out: hosted-page link stays available as an escape hatch.
## Hard-known protocol facts (from inspection)
| Item | Value |
|---|---|
| RN ERC20FeeProxy (BSC + Arb, likely all EVMs) | `0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9` |
| Function | `transferFromWithReferenceAndFee(token, to, amount, ref, feeAmount, feeAddress)` |
| Selector | `0xc219a14d` |
| `paymentReference` derivation | `last8Bytes(keccak256(requestId + salt + destination))` |
| Fee config currently used | `feeAmount = 0`, `feeAddress = 0x…dEaD` |
## Backend deliverables (sketch)
Extend `POST /payment/request-network/intents` response with an `inHouseCheckout` object containing: `destination`, `tokenAddress`, `decimals`, `chainId`, `proxyAddress`, `paymentReference`, `feeAmount`, `feeAddress`, `amountWei`. Full field sourcing in the spec.
## Frontend deliverables (sketch)
New page `/checkout/request-network/:paymentId` with a wagmi-driven state machine: connect → switch-chain → check-allowance → approve → pay → wait-for-webhook → done. Reuses layout chrome from `web3/components/manual-payment.tsx` (SHKeeper). Escape-hatch link to the hosted page stays visible.
## Acceptance criteria (short list)
1. A Rabby user completes an RN payment without leaving `amn.gg`.
2. RN's webhook fires from a transaction built and submitted by our UI.
3. The `Payment` doc transitions to `completed` / `escrowState='funded'` exactly as it does today, but only after safety checks pass.
4. Wrong-chain → one-click switch. Sufficient allowance → approve step skipped. Abandoned page → resumable.
5. Hosted-page link still works and is documented as the fallback.
Full list in the spec.
## Open questions for the review
1. Is the proxy address identical on every chain we plan to support, or only some?
2. RN's pricing — are we still in their terms if we never use their hosted UI?
3. `approve(MAX_UINT256)` (RN's pattern, best UX) vs exact-amount approve (better security)?
4. Cancel / timeout semantics for abandoned intents — TTL, explicit cancel endpoint, or both?
5. Feature flag + A/B in dev for one cycle before prod, or hard-cut once acceptance passes?
## Risks
- **Webhook handling failure.** RN did deliver the 2026-05-28 dev webhook, but Amanat returned `404`. A deployed correlation fix and smoke test are now the gate before any more paid probes.
- **Webhook durability.** The main app is too unstable to be the only webhook landing zone. Roadmap item: put a Cloudflare Worker in front of RN webhooks, store raw delivery metadata durably, then forward/replay into the backend.
- **False-positive payment credit.** A signed webhook must not be enough to credit escrow. Transaction Safety Provider gates payment completion on on-chain transfer verification, confirmation depth, and future AML/sanctions checks.
- Chain-specific proxy address divergence (mitigation: per-chain constants, fall back to hosted page for unknown chains).
## Implementation phases
| # | Phase | Gate |
|---|---|---|
| 0 | Probe — real dev BSC payment | ✅ Completed: RN webhook reached nginx/backend, but app returned `404` |
| 0A | Confirmation repair — deploy correlation fix, callback polling fix, and Transaction Safety Provider | ✅ Completed: smoke test passes; unsigned/test webhooks rejected |
| 1 | Backend: expand intent payload + helpers | ✅ Completed (2.6.32 → 2.6.38) |
| 2 | Frontend: page skeleton + wallet connect | ✅ Completed (2.6.30 → 2.6.41) |
| 3 | On-chain wiring (approve + proxy call) | ✅ Completed: real 0.01 USDC payment on dev BSC 2026-05-28 (tx `0x494c77a2…`), Payment.status=completed, safety approved |
| 4 | Hardening: errors, timer, persistence, telemetry, escape hatch | 🟡 Partial — copy/BscScan links, pay-button trap, 10s status poll, template-tc routing fix shipped 2.6.41. Timer + persistence + telemetry left. |
| 5 | Durable ingress — Cloudflare Worker receives RN webhooks, stores delivery record, forwards to backend, exposes replay | ⏳ Not started (Taskmaster `3.13`) |
| 6 | Rollout: flag + A/B | ⏳ Not started |
Five follow-up workstreams scoped 2026-05-28 in `PRD - Wallet, Multichain, Confirmations, AML, Trezor.md` (Taskmaster `#7…#11`) cover ephemeral destination wallets, multichain proxy registry, runtime confirmation thresholds, optional AML screening, and Trezor signing for admin actions.
## Durable webhook ingress roadmap
Add a small Cloudflare Worker as the public Request Network webhook target:
1. Receive RN webhook with raw body, headers, request id, delivery id, source IP, timestamp.
2. Verify RN signature at the edge if secret handling/raw-body verification is compatible; otherwise store first and let backend perform canonical verification.
3. Write an immutable delivery record to durable storage (Cloudflare Queues + D1/R2/KV, final choice TBD).
4. Forward to the primary backend and optionally a secondary backend endpoint.
5. Return `2xx` only after durable enqueue/store succeeds.
6. Provide operator replay by delivery id or time window.
The Worker is an ingress and evidence buffer, not the trust oracle. The backend still owns signature verification, idempotency, Transaction Safety Provider checks, ledger updates, and marketplace state transitions.
## See also
- `.taskmaster/docs/prd-request-network-in-house-checkout.md` — full spec with field sources, file lists, state-machine details, telemetry events.
- `01 - Architecture/Request Network Integration Constraints.md` — §1 (this PRD addresses), §2/§3/§4 are explicitly out of scope here.
- `PRD - Request Network Migration and Funds Management.md` — the broader RN context.