docs(arch): capture RN integration constraints and design implications
Four payment-flow concerns surfaced during the RN integration that need explicit design decisions before scaling: 1. Rabby wallet unsupported by RN's hosted UI - mitigated by bringing the checkout screen in-house. 2. RN auto-bridges cross-chain payments via LiFi, costing someone money - mitigated by gating chain selection in our own UI based on seller-accepted chains. 3. Single shared escrow wallet exposes the whole platform to sanctioned-funds taint - needs per-escrow ephemeral wallets and a wallet-abstraction layer. 4. The above pushes RN into a notification-only role - viable but needs validation tests (webhook reliability, custom destinations, API-only pricing) before commitment.
This commit is contained in:
157
01 - Architecture/Request Network Integration Constraints.md
Normal file
157
01 - Architecture/Request Network Integration Constraints.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# Request Network Integration — Constraints and Design Implications
|
||||||
|
|
||||||
|
**Date:** 2026-05-27
|
||||||
|
**Status:** Active concerns; mitigations partially designed, partially blocked on RN clarifications
|
||||||
|
**Owners:** Backend payments (Amanat), product
|
||||||
|
|
||||||
|
This document captures four payment-flow issues that surfaced while integrating Request Network (RN) into the Amanat escrow stack. Each one is either a show-stopper or a non-trivial architectural constraint. Listed in priority order.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. RN does not support Rabby — show-stopper for our wallet user base
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
RN's hosted payment page (the `pay.request.network/?token=…` UI returned by `/v2/secure-payments`) does not detect / connect to Rabby. A meaningful slice of Amanat's user base pays from Rabby. Sending them to a screen that won't even let them connect is a hard block.
|
||||||
|
|
||||||
|
### Mitigation (designed, not yet implemented)
|
||||||
|
|
||||||
|
Skip the RN-hosted UI. We already call `/v2/secure-payments` and receive a `securePaymentUrl`, but we also receive `requestIds` and `token` — that's everything we need to know what the merchant request is. Behind that token there is a contract on the destination chain that anyone can fulfill.
|
||||||
|
|
||||||
|
So the new flow becomes:
|
||||||
|
|
||||||
|
1. Backend calls RN `/v2/secure-payments` (same as today) and stores the `requestIds[0]` + destination wallet + amount + token on our `Payment` doc.
|
||||||
|
2. **We render our own checkout screen** that:
|
||||||
|
- Shows the buyer the wallet address to pay to (the destination resolved from the merchant reference / chain / token).
|
||||||
|
- Lets the buyer connect *any* wallet — Rabby, MetaMask, OKX, Phantom-bridged, WalletConnect.
|
||||||
|
- Builds the transfer transaction client-side (standard ERC-20 transfer) and asks the wallet to sign.
|
||||||
|
3. RN's webhook (`/v2/request/{id}`-style polling fallback) tells us when the payment lands.
|
||||||
|
|
||||||
|
### Why this is acceptable
|
||||||
|
|
||||||
|
- RN's value to us at that point is the *settlement bookkeeping*, not the UI. We use them as "did this address receive the expected amount before timeout?" — the wallet UX stays in our control.
|
||||||
|
- Buyer never sees a third-party brand mid-checkout, which is a UX win regardless of Rabby.
|
||||||
|
|
||||||
|
### Open
|
||||||
|
|
||||||
|
- Need to confirm RN actually settles a payment that arrives from a *transaction we built*, not from their hosted page. Their pricing/fees may be tied to going through their UI. **Test required** before committing to this path.
|
||||||
|
- Need a fallback for the buyer who insists on the RN hosted UI (some users will already have the link copied). Keep `securePaymentUrl` exposed as a "advanced / pay with RN" link.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. RN's multi-chain routing forces an expensive LiFi bridge
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
When we configure a destination route (e.g. BSC + USDC), RN's hosted UI still lets the buyer pick *any* chain where they hold funds (e.g. ARB). To honor that, RN routes the buyer's funds through **LiFi**, which charges bridging fees that **someone has to pay**, and it's not clearly disclosed who.
|
||||||
|
|
||||||
|
The visible costs:
|
||||||
|
- Buyer over-pays vs. nominal invoice amount (bad UX).
|
||||||
|
- Or we eat the spread (bad margin).
|
||||||
|
- Or seller gets less than they expected (worst — they'll dispute).
|
||||||
|
- Plus settlement latency goes from seconds to minutes-hours depending on the bridge.
|
||||||
|
|
||||||
|
### Mitigation (designed)
|
||||||
|
|
||||||
|
Take the chain choice away from RN's UI and bring it into ours, gated by what the *seller* will accept.
|
||||||
|
|
||||||
|
Two-step UX:
|
||||||
|
|
||||||
|
1. **At offer creation (seller side):** seller specifies which chain(s) they accept payouts on. We persist this as `acceptedChains: [bsc, arb, base, …]` on the offer / merchant configuration.
|
||||||
|
2. **At checkout (buyer side, before any RN call):** we show the buyer the seller's accepted chains. Buyer picks one. *Then* we call RN with that exact chain pinned as the destination. No LiFi bridge — same-chain transfer.
|
||||||
|
|
||||||
|
### Side benefit
|
||||||
|
|
||||||
|
This composes cleanly with #1 (own checkout screen): we already have to render the wallet picker, so adding a chain selector before the wallet step costs almost nothing.
|
||||||
|
|
||||||
|
### Open
|
||||||
|
|
||||||
|
- We need a per-seller config table for accepted chains. Today the env-level `REQUEST_NETWORK_MERCHANT_REFERENCE` hard-codes a single chain (`bsc`). Needs to become per-seller, per-offer.
|
||||||
|
- Does RN's API support creating a secure-payment that *rejects* off-chain payments rather than auto-bridging? Or do we have to enforce this purely on our side by never offering the cross-chain option to the buyer? **Confirm with RN docs/support.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Sanctioned-funds risk — single escrow wallet poisons the entire platform
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
Today the entire escrow stack receives funds into one (or a handful of) wallets — `REQUEST_NETWORK_MERCHANT_REFERENCE` resolves to a single destination address. If a buyer pays with funds tied to a sanctioned source / mixer / known-bad address:
|
||||||
|
|
||||||
|
- That destination wallet gets tagged non-compliant by Chainalysis / TRM / Elliptic.
|
||||||
|
- Downstream exchanges and OTC desks won't accept transfers from it.
|
||||||
|
- One bad buyer can effectively brick the entire platform's settlement layer.
|
||||||
|
|
||||||
|
This is a show-stopper for going live at scale. Same class of issue we already considered around SHKeeper.
|
||||||
|
|
||||||
|
### Mitigation (designed; needs RN feasibility check)
|
||||||
|
|
||||||
|
Per-`(buyer, merchant)`-pair ephemeral wallets. Each new escrow gets a freshly-generated address that only ever receives that one transaction. If those funds turn out to be dirty:
|
||||||
|
|
||||||
|
- Only that wallet is tainted.
|
||||||
|
- We never sweep it into our main treasury (or sweep only after the payment passes screening).
|
||||||
|
- Risk is **siloed to the individual escrow**, not platform-wide.
|
||||||
|
|
||||||
|
### What this requires (architectural work)
|
||||||
|
|
||||||
|
1. **Wallet abstraction layer** — service that on demand generates a fresh address (HD wallet derivation from a master seed kept in a hardware module / KMS) and returns it to the payment-intent flow.
|
||||||
|
2. **Address book / registry** — maps `(paymentId, chainId)` → derived address. Persists derivation path + sequence number so we can reproduce keys for sweeps later.
|
||||||
|
3. **Sweep job** — once a payment is confirmed AND has passed an on-chain screening check (Chainalysis API or similar), sweep the ephemeral wallet to the main treasury. If screening fails, the ephemeral wallet is quarantined and the payment refunded out of band.
|
||||||
|
4. **Key custody policy** — these are still our funds in custody briefly; need clear policy on who can sign sweeps, hot-key vs cold-key separation.
|
||||||
|
|
||||||
|
### Critical open question
|
||||||
|
|
||||||
|
**Does RN support creating a secure-payment with a destination wallet we specify per-request, rather than a static merchant reference?** If yes, this is straightforward — we generate a wallet, register it as the destination for one specific `/v2/secure-payments` call, done. If no (RN only allows pre-registered destinations), we have to either:
|
||||||
|
|
||||||
|
- Pre-register a large pool of addresses with RN and rotate through them, or
|
||||||
|
- Bypass RN's destination model and go full self-host (which is most of issue #4).
|
||||||
|
|
||||||
|
**Action: confirm with RN support whether per-request destinations are supported on the same API key.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. RN reduced to a notification service — viable, but not yet validated
|
||||||
|
|
||||||
|
### Problem statement
|
||||||
|
|
||||||
|
If we adopt #1 (own checkout UI), #2 (own chain selection), and #3 (own ephemeral wallets), RN's role in the flow collapses to:
|
||||||
|
|
||||||
|
> "Tell me when wallet X receives Y tokens (or doesn't, before timeout)."
|
||||||
|
|
||||||
|
Which is a *notification* primitive, not a payment platform. We'd be paying for a feature we're using maybe 5% of.
|
||||||
|
|
||||||
|
### Why this might still be worth it
|
||||||
|
|
||||||
|
- We get RN's chain watchers + reorg handling + webhook reliability for free.
|
||||||
|
- We don't have to run our own indexer on n chains.
|
||||||
|
- Their screening (if they do any) is one more compliance layer.
|
||||||
|
|
||||||
|
### Why this might NOT be worth it
|
||||||
|
|
||||||
|
- Pricing built around hosted-UI usage, not API-only. May not be cost-effective at API-only volumes.
|
||||||
|
- We're outsourcing the *one thing* RN is good at (settlement) and keeping the parts they don't help with (UX, wallet generation, compliance).
|
||||||
|
- Alternative: do the same with our own chain watcher (Alchemy webhooks / Tenderly / Goldsky) and skip RN entirely.
|
||||||
|
|
||||||
|
### What needs testing before we commit
|
||||||
|
|
||||||
|
1. **Webhook reliability at our volume.** What's RN's SLA for "address received funds → webhook delivered"? P50? P99?
|
||||||
|
2. **Custom destination support.** See open question in #3.
|
||||||
|
3. **Per-API-key rate limits.** If we end up calling `/v2/secure-payments` once per escrow, do we hit ceilings?
|
||||||
|
4. **Pricing for the notification-only flow** — is there a tier, or is it the same as the full-stack price?
|
||||||
|
5. **What happens when the payment arrives from a transaction WE built** (not theirs)? Does the webhook still fire? Is settlement still recognized? — this is the load-bearing test for the whole strategy.
|
||||||
|
|
||||||
|
Until #5 is confirmed, the rest is just paper architecture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-cutting next actions
|
||||||
|
|
||||||
|
| # | Action | Blocker / Owner |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | Test: payment via wallet-built transfer triggers RN webhook | Backend payments |
|
||||||
|
| 2 | Test: `/v2/secure-payments` accepts a per-request destination wallet | Backend payments |
|
||||||
|
| 3 | Confirm RN doesn't auto-bridge when buyer pays on the destination chain natively | Backend payments |
|
||||||
|
| 4 | Get RN's webhook P99 latency + delivery guarantees in writing | Product / RN account manager |
|
||||||
|
| 5 | Spec the wallet-abstraction layer (HD derivation + sweep job + key policy) | Backend, before going live |
|
||||||
|
| 6 | Spec the seller-side accepted-chains config | Backend + frontend |
|
||||||
|
|
||||||
|
Actions 1–4 are *information-gathering* and should run in parallel before any more architectural commitment to RN. Actions 5–6 are blocked on 1–3 confirming RN can actually support this shape.
|
||||||
Reference in New Issue
Block a user