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>
This commit is contained in:
Siavash Sameni
2026-05-28 15:50:24 +04:00
parent 37f946fc23
commit 0060b16912
69 changed files with 1513 additions and 147 deletions

View File

@@ -0,0 +1,280 @@
# PRD: Request Network In-House Checkout
**Status:** Draft — updated after 2026-05-28 dev webhook probe
**Date:** 2026-05-27
**Related:** `01 - Architecture/Request Network Integration Constraints.md` §1; `PRD - Request Network Migration and Funds Management.md`
**Owner:** Backend payments + Frontend
---
## 1. Problem
Buyers paying through Request Network (RN) are redirected from Amanat to RN's hosted page at `pay.request.network/?token=…`. That page has three concrete problems:
1. **Rabby is not supported.** A meaningful portion of our user base pays from Rabby. The hosted page does not detect it (no EIP-6963 enumeration, hard-coded MetaMask/WalletConnect/Coinbase). For those users, checkout fails before a transaction is even attempted.
2. **The page is brand-divergent.** Buyers leave the Amanat domain mid-flow, which is a trust and conversion cost we can avoid.
3. **The page relies on RN's choice of infrastructure.** Observed in our own probe: their UI wraps the payment in Safe smart accounts + ERC-4337 + a Pimlico paymaster, hitting public BSC RPCs that already rate-limited us once ("RPC endpoint returned too many errors"). We have no control to fix that.
The core RN protocol is sound. The hosted UI on top of it is the problem.
### 2026-05-28 reality update
The first dev BSC probe did not fail because RN stayed silent. It failed because Amanat dropped the provider callback:
- Test transaction: `0x3a23febd9abd43d7e0851c1ea86c4ceaf08c11098852cb0425fa074e9c88350b`.
- On-chain result: successful BSC USDC transfer to Amanat's configured destination wallet.
- RN webhook result: RN called `POST /api/payment/request-network/webhook` on `dev.amn.gg` four times from `34.34.233.192`.
- Backend result: nginx/backend returned `404` because the handler did not correlate all Request Network identifiers stored on the `Payment` record.
- Frontend result: the callback page stayed on the processing state and later hit `429` from 3-second polling.
The pre-implementation gate therefore changes from "prove RN calls us" to "deploy and smoke-test the webhook correlation repair, callback polling repair, and transaction-safety gate before another paid probe".
## 2. Goal
Replace the redirect to RN's hosted page with an Amanat-rendered checkout that:
- Connects any EVM wallet (Rabby, MetaMask, OKX, Trust, WalletConnect — anything wagmi's `injected()` connector enumerates).
- Builds and submits the exact same on-chain calls RN's hosted page makes, so RN's existing webhook fires unchanged.
- Stays on `dev.amn.gg` (or prod equivalent) for the entire flow.
- Falls back gracefully to the hosted page for buyers who explicitly request it.
Settlement, webhook handling, and the backend `Payment` lifecycle do not change. Only the buyer-facing checkout surface changes.
Webhook delivery alone is not a sufficient trust signal. Amanat must run a **Transaction Safety Provider** gate before marking escrow funded. The initial provider checks: transaction hash present, configured confirmation depth, transfer recipient/token/amount match on-chain evidence, and future AML/sanctions provider approval.
## 3. Non-goals
- Per-seller multi-chain configuration (covered in `Request Network Integration Constraints.md` §2 — separate PRD).
- Per-`(buyer, merchant)` ephemeral wallets / wallet abstraction layer (§3 — separate PRD).
- Replacing RN entirely with our own chain watcher (§4 — depends on this PRD's outcome).
- Gasless / sponsored transactions for the buyer. Buyer pays own gas via their EOA. We can revisit paymaster integration in a follow-up.
## 4. Background: what RN's protocol actually requires
Cold-inspected from a real `$12` BSC payment on RN's hosted page:
| Item | Value |
|---|---|
| Proxy contract (BSC, and same address on most EVMs via deterministic CREATE2) | `0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9` |
| Function | `transferFromWithReferenceAndFee(address token, address to, uint256 amount, bytes reference, uint256 feeAmount, address feeAddress)` |
| Selector | `0xc219a14d` |
| Emitted event | `TransferWithReferenceAndFee(token, to, amount, reference, feeAmount, feeAddress)` |
| `paymentReference` derivation | `last8Bytes(keccak256(lowercase(requestId + salt + destinationAddress)))` |
| Fee config seen on RN's UI | `feeAmount = 0`, `feeAddress = 0x…dEaD` |
RN's webhook listens for `TransferWithReferenceAndFee`. If we emit it ourselves with the correct `paymentReference`, the webhook fires. The hosted UI is a UI-only convenience — not a settlement requirement.
## 5. Proposed flow (buyer perspective)
1. Buyer clicks "Pay with Request Network" on the request page.
2. Frontend calls existing `POST /payment/request-network/intents` (unchanged endpoint, expanded response — see §6).
3. Frontend navigates to new in-house page: `/checkout/request-network/:paymentId`.
4. Page shows: amount, token, chain, recipient (truncated, copyable), countdown timer.
5. "Connect wallet" — wagmi `injected()` + `metaMask()` (already configured). Rabby works automatically via EIP-6963.
6. Once connected, page checks the buyer's wallet is on the correct chain. If not, prompts `switchChain` (wagmi).
7. Page also checks current allowance. If `allowance(buyer, RN_PROXY) >= amount`, skip approve.
8. Buyer signs **transaction 1: `USDC.approve(RN_PROXY, amount)`**. UI shows "Approving…" → "Approved".
9. Buyer signs **transaction 2: `RN_PROXY.transferFromWithReferenceAndFee(token, dest, amount, ref, 0, 0x…dEaD)`**. UI shows "Submitting payment…" → "Confirmed on-chain".
10. Page waits for backend `payment-confirmed` socket event (already emitted on RN webhook → `Payment.status='paid'`). Shows success state.
11. Redirect to existing payment-success route.
Escape hatches surfaced as secondary links on the page:
- **"Continue on Request Network's hosted page"** → falls through to `securePaymentUrl` (unchanged behavior).
- **"Copy payment details"** → exposes destination, amount, token, chain, paymentReference for manual entry into any wallet.
## 6. Backend changes
### 6.1 Intent response payload
Expand `PayInIntentResult` for RN. Today it returns `paymentUrl`, `paymentId`, `providerPaymentId`, `amount`, `config.networks`, `config.allowedTokens`, `raw`. Add:
```ts
inHouseCheckout: {
destination: '0x05E280d7f3cA954f37afA8B1E4d2a51D167c573e',
tokenAddress: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d',
tokenSymbol: 'USDC',
decimals: 18, // BSC USDC quirk; varies per token+chain
chainId: 56,
proxyAddress: '0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9',
paymentReference: '0xa826e248f5de5463', // 8-byte hex, computed from RN's requestId + salt
feeAmount: '0',
feeAddress: '0x000000000000000000000000000000000000dEaD',
amountWei: '12000000000000000000', // pre-multiplied by decimals, frontend doesn't compute
}
```
### 6.2 Sources for each field
| Field | Source |
|---|---|
| `destination`, `tokenAddress`, `chainId` | Parse `REQUEST_NETWORK_MERCHANT_REFERENCE` env (`address@eip155:chainId#ref:tokenAddr`). Today done once at startup; extract into a helper. |
| `decimals` | Token registry lookup. Hardcode the few we care about (USDC/USDT × BSC/ETH/Arb/Polygon) in a constant map; fall back to on-chain `decimals()` if missing. |
| `proxyAddress` | Constant per chain. Use canonical `0x0DfbEe14…0aC9` for all EVMs unless RN deploys a non-canonical address (open question — see §10). |
| `paymentReference` | Computed from RN's `/v2/secure-payments` response. `salt` is in their `raw` payload. Use `keccak256` + slice-to-8-bytes. |
| `feeAmount`, `feeAddress` | Constants matching what RN's hosted UI submits. Verify via cold inspection on each new chain we enable. |
| `amountWei` | `BigInt(amount) * 10n ** BigInt(decimals)`. Done backend-side to avoid frontend rounding bugs. |
### 6.3 Files touched
- `backend/src/services/payment/requestNetwork/contract.ts` — extend `PayInIntentResult` (or a new `inHouseCheckout` sub-object).
- `backend/src/services/payment/requestNetwork/requestNetworkPayInService.ts` — populate the new fields.
- `backend/src/services/payment/requestNetwork/merchantReference.ts` (new) — parser for the `address@chain#ref:token` format. Currently this is implicit in env lookups; promote to a named helper.
- `backend/src/services/payment/requestNetwork/tokens.ts` (new) — `{ chainId, tokenAddr } → { symbol, decimals }` lookup.
- `backend/src/services/payment/requestNetwork/paymentReference.ts` (new) — `computePaymentReference(requestId, salt, destination)`.
- Unit tests for each new helper.
### 6.4 Webhook handler
The route remains `/api/payment/request-network/webhook`, but the handler must be hardened before more paid probes:
- Correlate against every RN identifier we persist: `providerPaymentId`, `metadata.requestNetworkRequestId`, `metadata.requestNetworkPaymentReference`, and nested `metadata.requestNetworkData` ids/references.
- Reject unsigned or test-header callbacks unless explicit test mode is enabled.
- Store raw webhook evidence on the `Payment` record for support/replay.
- Call the Transaction Safety Provider before marking `Payment.status='completed'` / `escrowState='funded'`.
- Return a successful pending response when safety is not yet satisfied, so the delivery is not lost but escrow is not credited.
## 7. Frontend changes
### 7.1 New page
`frontend/src/pages/checkout/request-network/[paymentId].tsx` (or whatever the router convention is).
State machine (one component, reducer or zustand):
```
idle
→ connecting (wallet not connected)
→ wrong-chain (connected, chain mismatch)
→ ready (correct chain, allowance unknown)
→ checking-allowance
→ needs-approve → approving → approve-confirming → ready-to-pay
→ ready-to-pay
→ paying → pay-confirming → confirmed
→ error (with retry)
→ expired (timer ran out)
```
### 7.2 Reusable parts from SHKeeper
`frontend/src/web3/components/manual-payment.tsx` already implements: address-with-QR, copy-to-clipboard, countdown timer, localStorage persistence per request, socket-driven status update. Reuse the layout chrome. The wallet-interaction half is new.
### 7.3 Wagmi calls
Two contracts. The ERC-20 ABI is already in viem/wagmi. RN proxy ABI is a single function — define it inline.
```ts
const RN_PROXY_ABI = [{
inputs: [
{ name: 'tokenAddress', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'paymentReference', type: 'bytes' },
{ name: 'feeAmount', type: 'uint256' },
{ name: 'feeAddress', type: 'address' },
],
name: 'transferFromWithReferenceAndFee',
outputs: [], stateMutability: 'nonpayable', type: 'function',
}] as const;
```
Hooks used:
- `useAccount`, `useChainId`, `useSwitchChain`
- `useReadContract` for `allowance`
- `useWriteContract` + `useWaitForTransactionReceipt` for approve and proxy call
### 7.4 Routing change
Today, `createRequestNetworkIntent` consumers do `window.location = response.paymentUrl`. Replace with `router.push('/checkout/request-network/' + response.paymentId)`. Keep `response.paymentUrl` available so the in-house page can render the escape-hatch link.
### 7.5 Files touched
- `frontend/src/pages/checkout/request-network/[paymentId].tsx` — new page.
- `frontend/src/web3/components/rn-in-house-checkout.tsx` — new component holding the state machine.
- `frontend/src/web3/contracts/rn-fee-proxy.ts` — ABI + address-per-chain constants.
- `frontend/src/sections/request/components/buyer-steps/…` — change the existing RN button handler from `window.location` to `router.push`.
- Tests/Storybook for the new component.
## 8. Acceptance criteria
1. A buyer using **Rabby** can complete an RN payment end-to-end on `dev.amn.gg` without leaving the domain.
2. The two on-chain transactions emit a `TransferWithReferenceAndFee` event with the same `paymentReference` RN's hosted page would have produced.
3. RN's existing webhook fires on the event and the `Payment` doc transitions to `completed` / `escrowState='funded'` after safety approval.
4. If buyer connects on the wrong chain, they see a one-click "Switch network" CTA.
5. If buyer already has sufficient allowance, the approve step is skipped.
6. If the buyer abandons the page, returning to it restores their state from localStorage and resumes from the correct step.
7. "Continue on Request Network's hosted page" link is visible on the in-house page and works exactly like today's redirect.
8. Page handles tx failure (user reject, insufficient gas, RPC error) with a clear retry path that does not corrupt the `Payment` doc.
9. Existing non-RN payment flows (SHKeeper, others) are untouched.
## 9. Out of scope (explicit non-decisions)
- Multi-chain at checkout (BSC + Arb + ETH together) — needs the per-seller `acceptedChains` config; separate PRD.
- WalletConnect integration — currently disabled in `web3/config.ts` for SSR reasons; not regressed but not added either.
- Migration plan to remove RN entirely — kept as `§4` open option in the architecture doc.
- Mobile Mini App rendering of the checkout (different surface — handled in Task 5.4).
## 10. Open questions for review
These are the items we should discuss with the second developer before starting implementation.
1. **Proxy address universality.** Is `0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9` the RN ERC20FeeProxy address on **every** chain we plan to support (BSC, Arb, ETH mainnet, Polygon, Base)? We confirmed BSC + Arbitrum from RN's payments-subgraph repo. The other chains need explicit confirmation — preferably an on-chain probe per chain.
2. **API pricing for hosted-UI vs API-only.** RN's pricing model may assume hosted-UI usage. If we use them as a notification primitive only, are we still in their pricing terms? Worth a direct question to RN account management.
3. **Approval UX.** RN's hosted UI uses `approve(spender, MAX_UINT256)` for gas efficiency. Some Amanat users have voiced concern in past audits about unbounded approvals. Options:
- Mirror RN: `MAX_UINT256` (best UX, repeat payments don't need re-approve).
- Exact-amount: `approve(spender, amount)`. Slightly worse UX (every payment requires an approve), better security posture.
- Recommend: exact-amount by default, opt-in "remember this approval" for power users. Discuss.
4. **Tokens with non-standard ERC-20 behavior.** USDT on Ethereum mainnet famously requires `approve(0)` before changing a non-zero allowance. We need to handle this if we add USDT-ETH later. Not relevant on day one (BSC config is USDC), but worth flagging now.
5. **Cancel and timeout semantics.** Today the `Payment` doc transitions on the webhook. If the buyer signs the approve but bails on the proxy call, the doc sits in `pending` forever. Options:
- 30-minute TTL on the `pending` doc (existing partial unique index would let a re-attempt succeed).
- "Cancel intent" button on the in-house page that calls a new backend endpoint to mark the doc `cancelled`.
- Both. Discuss.
6. **Manual reconciliation.** If a buyer pays the correct amount to the correct destination but bypasses the proxy (e.g., raw `transfer`), no event fires and no webhook arrives. They'll think they paid but our system won't know. Today RN's hosted UI prevents this; our in-house UI inherits the same risk only if we add a "raw transfer" escape hatch — which we won't. But: should we proactively detect orphan transfers (chain watcher) and surface them in support? Out of scope for v1.
7. **Feature flag and rollout.** Ship behind a flag and A/B against the current redirect for one week of dev usage? Or hard-cut once acceptance criteria pass? Recommend A/B for at least one full release cycle.
8. **Telemetry.** What events do we instrument so we can prove the in-house page outperforms the redirect on conversion? Suggested: `rn_checkout_opened`, `rn_wallet_connected`, `rn_approve_signed`, `rn_pay_signed`, `rn_pay_confirmed`, `rn_pay_failed{reason}`, `rn_escape_to_hosted_clicked`.
## 11. Risks
- **Webhook handling failure.** RN delivered the 2026-05-28 dev webhook, but Amanat returned `404`. A deployed correlation fix and smoke test are now the load-bearing gate before another paid probe.
- **Webhook durability.** The main app is too unstable to be the only callback landing zone. Put a Cloudflare Worker in front of Request Network webhooks to durably store raw delivery evidence, forward to the backend, and replay after outages.
- **False-positive payment credit.** A signed provider event must not be enough to fund escrow. Transaction Safety Provider gates completion on on-chain evidence, confirmation depth, transfer matching, and future AML/sanctions checks.
- **Chain-specific divergences.** RN may have deployed non-canonical proxy contracts on some chains. We mitigate by hard-coding per-chain proxy addresses and treating "unknown chain → fall back to hosted page" as the safe default.
- **Wallet support drift.** EIP-6963 is mature but not universal. We rely on wagmi's `injected()` connector enumeration; if a wallet doesn't implement EIP-6963, it falls into the generic `window.ethereum` slot. Acceptable.
- **Buyer signs approve, then we change ABI before they sign the proxy call.** Not really a risk if both txs are in the same session, but worth a smoke test on a long-lived session.
## 12. Implementation phases
Roughly two weeks of checkout work, now gated on confirmation reliability repair.
| Phase | Work | Gate |
|---|---|---|
| 0 — Probe | Fire one real dev BSC payment and inspect nginx/backend logs | Completed: RN webhook reached nginx/backend; app returned `404` |
| 0A — Confirmation repair | Deploy correlation fix, callback polling fix, signed-webhook smoke test, and Transaction Safety Provider | Unsigned/test callbacks rejected unless explicitly enabled; real webhook can find the `Payment` |
| 1 — Backend | Expand intent response with `inHouseCheckout` fields; add helpers and tests | Existing RN smoke test still passes; new unit tests pass |
| 2 — Frontend skeleton | New page + state machine + wallet-connect; no on-chain calls yet, mocked confirmations | Local clickthrough on `dev.amn.gg` with Rabby connected |
| 3 — On-chain wiring | Real `approve` + `transferFromWithReferenceAndFee` calls; allowance check; chain-switch | One real end-to-end payment on dev BSC, webhook fires, `Payment.status='completed'`, safety approved |
| 4 — Hardening | Error handling, timer, persistence, telemetry, escape-hatch link | Acceptance criteria 19 demonstrably pass |
| 5 — Durable ingress | Cloudflare Worker receives RN webhooks, stores delivery records, forwards to backend, supports replay | Backend outage no longer loses webhook evidence |
| 6 — Rollout | Behind feature flag, A/B in dev for one cycle, then prod | Conversion telemetry; no regressions in non-RN flows |
## 13. Durable webhook ingress roadmap
Add a Cloudflare Worker as the public Request Network webhook target:
1. Receive the raw RN webhook body, headers, request id, delivery id, source IP, and timestamp.
2. Verify the RN signature at the edge if raw-body secret handling is straightforward; otherwise store first and let the backend perform canonical verification.
3. Write an immutable delivery record to durable storage. Candidate stack: Cloudflare Queues for handoff plus D1/R2/KV for indexed replay metadata and raw payload retention.
4. Forward to the primary backend and optionally a secondary endpoint.
5. Return `2xx` only after durable enqueue/store succeeds.
6. Provide operator replay by delivery id, payment reference, request id, or time window.
The Worker is only ingress/evidence buffering. The backend remains the trust boundary for signature verification, idempotency, Transaction Safety Provider checks, ledger updates, and marketplace state transitions.
## 14. References
- Cold-inspected transaction (real `$12` BSC payment on RN hosted page): in conversation history, dated 2026-05-27.
- RN ERC20FeeProxy spec: <https://github.com/RequestNetwork/requestNetwork/blob/master/packages/advanced-logic/specs/payment-network-erc20-fee-proxy-contract-0.1.0.md>
- Arbitrum proxy deployment: <https://github.com/RequestNetwork/payments-subgraph/blob/main/subgraph.arbitrum-one.yaml>
- Architecture constraints doc: `01 - Architecture/Request Network Integration Constraints.md`
- Previous RN work: `PRD - Request Network Migration and Funds Management.md`

View File

@@ -2,7 +2,7 @@
"master": {
"tasks": [
{
"id": "1",
"id": 1,
"title": "Stabilize Mermaid diagram rendering across documentation vault",
"description": "Correct Mermaid syntax/rendering issues across the documentation vault and validate all Mermaid blocks.",
"details": "Source PRD: .taskmaster/docs/prd-mermaid-diagram-rendering-stabilization.md. Scope covered 57 Mermaid blocks and 11 failing blocks. The source PRD records that all targeted files now pass mmdc parse validation and the full vault sweep passes.",
@@ -47,7 +47,7 @@
]
},
{
"id": "2",
"id": 2,
"title": "Implement platform audit remediation plan",
"description": "Address the code-backed security and consistency issues identified in the 2026-05-24 platform audit remediation PRD.",
"details": "Source PRD: .taskmaster/docs/prd-platform-audit-remediation-plan-2026-05-24.md. Target backend hardening first, then documentation/runtime alignment. Delivery order suggested by PRD: security/auth, rate limiting, passkeys, Web3 verification, socket hardening, dispute hold controls, docs/API alignment.",
@@ -154,7 +154,7 @@
]
},
{
"id": "3",
"id": 3,
"title": "Migrate payment architecture toward Request Network and internal funds management",
"description": "Plan and implement provider-neutral payment flows, Request Network pay-in support, funds ledger, webhook reconciliation, release/refund orchestration, UI migration, and SHKeeper decommissioning.",
"details": "Source PRD: .taskmaster/docs/prd-request-network-migration-and-funds-management.md. The PRD recommends phased migration behind a provider adapter, Secure Payment Pages first, platform-controlled escrow/payee destination, and a first-class internal funds ledger before release/refund enforcement.\n\nPost-completion update: Task 3 now includes a CI-safe focused verification command for the provider-neutral payment migration plus optional Trezor safekeeping. Trezor safekeeping is optional by default via TREZOR_SAFEKEEPING_REQUIRED=false and only gates release/refund confirmation when explicitly enabled. Vault references: 04 - Flows/Trezor Safekeeping Flow.md, 03 - API Reference/Trezor API.md, and 08 - Operations/Payment and Trezor Verification Report.md.",
@@ -326,12 +326,22 @@
"parentTaskId": 3,
"parentId": "undefined",
"updatedAt": "2026-05-24T06:51:00.615Z"
},
{
"id": 13,
"title": "Add durable RN webhook ingress and transaction safety",
"description": "Roadmap follow-up from the 2026-05-28 dev payment probe: Request Network delivered the webhook but Amanat returned 404. Add Cloudflare Worker durable webhook ingress with storage/replay and keep backend Transaction Safety Provider checks as the trust boundary before marking escrow funded.",
"details": "",
"status": "pending",
"dependencies": [],
"parentTaskId": 3,
"parentId": "undefined"
}
],
"updatedAt": "2026-05-24T07:04:01.906Z"
},
{
"id": "4",
"id": 4,
"title": "Define backend security and refactor strategy from latest audit",
"description": "Convert the backend stack security/refactor assessment into concrete architecture decisions, documentation deliverables, and developer handoff criteria.",
"details": "Source audit: .taskmaster/docs/audit-backend-stack-security-and-refactor-assessment-2026-05-24.md. This task is advisory/architecture-focused and should run in parallel with immediate hardening. It should produce the decision artifacts needed before any backend-core rewrite or provider migration is started.",
@@ -473,7 +483,7 @@
"updatedAt": "2026-05-24T07:23:44.643Z"
},
{
"id": "5",
"id": 5,
"title": "Deliver Telegram-native app, bot, and wallet experience",
"description": "Create a Telegram bot plus Mini App surface so users can complete Amanat buyer, seller, escrow, chat, dispute, payment, release/refund, and support workflows from inside Telegram.",
"details": "Source PRD: .taskmaster/docs/prd-telegram-native-app-bot-wallet.md. Keep this as a separate delivery track from security remediation and Request Network migration. Identity, bot navigation, Mini App shell, and notifications can start behind flags; wallet/payment crediting and release/refund actions must use canonical backend authorization, provider adapter, funds ledger, escrow state machine, idempotency, and dispute holds.",
@@ -635,16 +645,98 @@
}
],
"updatedAt": "2026-05-24T13:46:14.458Z"
},
{
"id": 6,
"title": "Request Network in-house checkout (Rabby-supporting)",
"description": "Replace the redirect to pay.request.network with an Amanat-rendered checkout page that submits the same on-chain calls as RN's hosted UI, so RN's webhook fires unchanged but buyers stay on amn.gg and Rabby works.",
"details": "See PRD: nick-doc/.taskmaster/docs/prd-request-network-in-house-checkout.md (summary at nick-doc/PRD - Request Network In-House Checkout.md). Status: draft, pending review with second developer. Approach: replicate the two on-chain calls (approve + RN_FEE_PROXY.transferFromWithReferenceAndFee) using wagmi v2 with existing injected()/metaMask() connectors (Rabby works via EIP-6963). Hard-known: proxy 0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9, selector 0xc219a14d, paymentRef = last8Bytes(keccak256(requestId+salt+dest)), feeAmount=0, feeAddress=0x...dEaD. Backend: extend POST /payment/request-network/intents response with inHouseCheckout object (destination, tokenAddress, decimals, chainId, proxyAddress, paymentReference, feeAmount, feeAddress, amountWei). Frontend: new page /checkout/request-network/:paymentId with state machine reusing manual-payment.tsx layout chrome, hosted-page link kept as escape hatch. Implementation gated on a $0.50 cold probe on dev BSC to confirm RN's webhook fires for an externally-built tx. Out of scope: per-seller multi-chain config (§2), ephemeral wallets (§3), full RN removal (§4), gasless. Open questions in PRD §10.",
"testStrategy": "",
"status": "done",
"dependencies": [],
"priority": "high",
"subtasks": [
{
"id": 1,
"title": "Deploy confirmation repair before next paid probe",
"description": "2026-05-28 dev BSC transaction succeeded and RN delivered four webhooks, but Amanat returned 404 due Request Network reference-correlation mismatch. Before another paid payment test, deploy the backend correlation fix, callback polling fix, signed-webhook smoke test, and Transaction Safety Provider gate; then repeat the probe and inspect safety decision state.",
"details": "",
"status": "done",
"dependencies": [],
"parentTaskId": 6,
"updatedAt": "2026-05-28T07:34:40.368Z",
"parentId": "undefined"
}
],
"updatedAt": "2026-05-28T07:34:40.368Z"
},
{
"id": 7,
"title": "Per-(buyer, sellerOffer) ephemeral RN destination wallets",
"description": "Replace the single shared Amanat destination wallet with a per-(buyerId, sellerOfferId) HD-derived address sent to Request Network on intent creation, plus sweep-on-approval and an admin UI.",
"details": "See PRD - Wallet, Multichain, Confirmations, AML, Trezor.md §1. Files: new backend/src/services/payment/wallets/derivedDestinations.ts (getDestinationFor(buyerId, sellerOfferId) → {address, derivationPath, chainId}); Payment schema add metadata.derivedDestination; requestNetworkPayInService.ts override destinationId before POST /v2/secure-payments (we confirmed RN accepts different destinations per intent); new sweep cron + admin manual-trigger endpoint gated on Transaction Safety Provider; admin UI at /dashboard/admin/derived-destinations with address, balance, last sweep tx (BscScan link), ownership status. Open questions to settle first: HD vs disposable EOAs vs smart-forwarder (recommended HD); sweep cadence (recommended immediate); granularity (recommended per-(buyer, seller), not per-payment); re-use vs rotate after sweep. KMS-rooted seed; backend never holds raw private keys; signing via KMS API (Task #11 Trezor flow is the longer-term replacement). Acceptance: two payments from one buyer to two sellers land on two different addresses; RN webhook fires for both; sweep is idempotent; master seed never leaves KMS.",
"testStrategy": "",
"status": "pending",
"dependencies": [],
"priority": "high",
"subtasks": []
},
{
"id": 8,
"title": "Multichain RN proxy registry + USDC/USDT support",
"description": "Probe and persist RN ERC20FeeProxy addresses on BSC/Arb/ETH/Polygon/Base, add USDC + USDT token entries with correct decimals per chain, and surface an admin networks page. Include the USDT-mainnet approve(0) reset quirk in the frontend approve step.",
"details": "See PRD - Wallet, Multichain, Confirmations, AML, Trezor.md §2. Tasks: new backend/scripts/probe-rn-chains.ts that walks each chain in supported-chains.json and verifies the canonical 0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9 proxy is the real RN proxy via a known view fn (CREATE2 is deterministic, but verify); promote backend/src/services/payment/requestNetwork/tokens.ts to load from JSON + admin override; add USDT entries on all 5 chains (BSC USDT 18-dec quirk, mainnet/Arb/Polygon/Base USDT 6-dec); buildInHouseCheckoutBlock returns reason='unsupported_chain:<id>' for unknowns; new admin route GET /api/admin/rn/networks + frontend page /dashboard/admin/networks rendering the registry with per-row 'probe again'. Frontend approve flow: if buyer is on Ethereum mainnet AND token is USDT AND current allowance > 0, do approve(spender, 0) first then approve(spender, amount). Acceptance: probe succeeds on at least BSC/Arb/Polygon/ETH/Base; one paid probe on BSC USDT end-to-end; mainnet USDT approve(0) reset works; admin page reflects registry. Dependencies: none — runs in parallel with #9. This is task #8 in the PRD.",
"testStrategy": "",
"status": "pending",
"dependencies": [],
"priority": "high",
"subtasks": []
},
{
"id": 9,
"title": "Per-chain confirmation thresholds + admin UI",
"description": "Make TransactionSafetyProvider's confirmation threshold tunable at runtime per chain via admin UI, with an awaiting-confirmation payments view that shows live confirmations vs threshold.",
"details": "See PRD - Wallet, Multichain, Confirmations, AML, Trezor.md §3. Today TRANSACTION_SAFETY_MIN_CONFIRMATIONS is a global env var, default 12, baked in until redeploy. Move to runtime config: new Setting docs keyed 'confirmation_threshold:<chainId>' or extend existing model; cache reads in transactionSafetyProvider.ts for 30s; GET/PATCH /api/admin/settings/confirmation-thresholds (auth: admin); new admin page /dashboard/admin/confirmation-thresholds (table: chain, current, recommended default, edit-in-place with confirm dialog, audit log of changes); new admin page /dashboard/admin/payments/awaiting-confirmation (payments where escrowState !== 'funded' AND metadata.transactionSafety.lastCheck.status === 'pending'; for each show tx hash linked to explorer, current confirmations via 12s poll on BSC, threshold, ETA). Acceptance: admin lowers BSC threshold from 12 to 3 on dev, next webhook honors new value within 30s; awaiting-confirmation table updates live; audit log records every change. Non-goals: per-asset, per-seller thresholds. Dependencies: none. This is task #9 in the PRD.",
"testStrategy": "",
"status": "pending",
"dependencies": [],
"priority": "medium",
"subtasks": []
},
{
"id": 10,
"title": "Optional AML screening on incoming payments (seller-paid)",
"description": "Turn the existing aml_screening placeholder in TransactionSafetyProvider into a real Chainalysis (or equivalent) Address Screening call that the seller opts into per-offer and pays the per-check cost for.",
"details": "See PRD - Wallet, Multichain, Confirmations, AML, Trezor.md §4. Default provider recommendation: Chainalysis Address Screening (cheapest, simplest). Files: new backend/src/services/payment/safety/amlProvider.ts interface + chainalysisProvider.ts impl behind env TRANSACTION_SAFETY_AML_PROVIDER=chainalysis with API_KEY in KMS; transactionSafetyProvider's evaluateAmlPlaceholder() becomes real, persists raw provider response on Payment.metadata.amlResult; Offer schema add requireAmlCheck + amlBlockOnFailure booleans; offer-edit UI toggle 'Require AML on incoming payments ($X per payment, paid by you)'; admin global config UI for provider selection + API key rotation + per-chain enabled flag; cost accounting: deduct per-check cost from seller's escrow on completion as a separate ledger line item, surfaced on payment-details. Open questions before code: pick provider (Chainalysis vs TRM vs Elliptic — need 1-page comparison of cost/latency/coverage); failure mode (fail-closed only when seller opted in AND amlBlockOnFailure=true, else warn/log); cost batching cadence. Acceptance: seller toggles AML on an offer; incoming payment triggers a real Chainalysis call; sanctions verdict blocks the safety gate; clean verdict passes; seller's settled amount reduced by check cost; admin can rotate API key without redeploy; provider-down + amlBlockOnFailure=true keeps payment pending with provider_unavailable reason. Dependencies: none. This is task #10 in the PRD.",
"testStrategy": "",
"status": "pending",
"dependencies": [],
"priority": "medium",
"subtasks": []
},
{
"id": 11,
"title": "Trezor signing for admin actions (release/refund/sweep)",
"description": "Replace the hot-key admin signing flow with a WebUSB-based Trezor flow so the backend never holds a private key. All admin-side txes are built backend, signed via Trezor in the browser, broadcast from the browser.",
"details": "See PRD - Wallet, Multichain, Confirmations, AML, Trezor.md §5. Lib: @trezor/connect-web (WebUSB; Chromium-only — Firefox users need Trezor Bridge native helper). Files: new frontend/src/web3/trezor/trezorConnector.ts wrapping @trezor/connect-web; existing admin actions (release/refund/sweep when #7 lands) get a 'Sign with Trezor' button that flows: POST /api/admin/actions/build-tx → returns unsigned tx bytes → send to Trezor → sign → wagmi sendTransaction broadcasts → POST /api/admin/actions/confirm-tx with hash; admin settings page to register Trezor address(es) (backend rejects signatures from unauthorized devices); audit log on every Trezor-signed action; break-glass hot-key path requires explicit admin toggle, expires after 1h, fires Telegram alarm. Open questions: m-of-n multi-admin signing — default single-signer for v1; Trezor One vs Model T — lib abstracts; fallback when Trezor unavailable — break-glass with alarm. Acceptance: admin registers Trezor address; release flow uses Trezor end-to-end; backend rejects signatures from unregistered devices; audit log captures admin user + Trezor addr + tx hash + before/after escrow state; break-glass works and alarms. Non-goals: mobile Trezor flow, buyer-side Trezor (buyer uses wagmi injected). Dependencies: task #7 (ephemeral wallets) for the sweep step — but task #11 can ship the release/refund flows first. This is task #11 in the PRD.",
"testStrategy": "",
"status": "pending",
"dependencies": [],
"priority": "high",
"subtasks": []
}
],
"metadata": {
"version": "1.0.0",
"lastModified": "2026-05-24T13:46:14.458Z",
"taskCount": 5,
"completedCount": 4,
"lastModified": "2026-05-28T07:34:40.369Z",
"taskCount": 6,
"completedCount": 5,
"tags": [
"master"
]
],
"created": "2026-05-28T11:47:32.273Z",
"description": "Tasks for master context",
"updated": "2026-05-28T11:48:22.144Z"
}
}
}