Skip to content

feat: MoonPay fiat on-ramp with tracked buy rows and automatic Agglayer bridge to Miden - #704

Draft
0xnullifier wants to merge 4 commits into
mainfrom
feat/transak-fiat-ramp
Draft

feat: MoonPay fiat on-ramp with tracked buy rows and automatic Agglayer bridge to Miden#704
0xnullifier wants to merge 4 commits into
mainfrom
feat/transak-fiat-ramp

Conversation

@0xnullifier

Copy link
Copy Markdown
Collaborator

What

Adds a fiat on-ramp (Buy) to the wallet: a MoonPay Buy widget embedded as a plain signed-URL iframe, purchases tracked as first-class buy activity rows, and the delivered token bridged automatically from the account's derived EVM address to the user's Miden account over Agglayer.

How it works

Widget (/buy)src/lib/fiat-ramp/moonpay.ts builds the widget URL (sandbox buy-sandbox.moonpay.com for pk_test_ keys, prod otherwise) with the derived EVM address pre-filled; MoonPay requires HMAC-signing such URLs, so the signature comes from a local sign server (../moonpay-sign-server.mjs, holds the sk). No SDK, no remote script — MV3-safe.

Two extension-specific unblockers:

  • credentialless on the iframe (extension pages run COEP require-corp; MoonPay sends no CORP header).
  • A declarativeNetRequest session rule (moonpay-frame-rules.ts, installed at SW start) stripping MoonPay's frame-ancestors * CSP for extension-initiated MoonPay sub_frames only — per spec * never matches chrome-extension:// origins, so every extension is otherwise blocked regardless of dashboard allowlisting.

Purchase tracking — each /buy mount mints a session uuid (localStorage slot) carried as MoonPay's externalTransactionId. The app-root BuyBridgeManager (20s tick, outlives the /buy screen) polls the sign server's /tx-status proxy (sk-only endpoint); a completed purchase inserts a tracking-only BuyTransaction Dexie row (born Completed, extraInputs.bridgeProgress owns live state — same pattern as bridged-receive).

Auto-bridge — once the delivered token shows a balance on the derived address, the watcher gasses it via a local paymaster (../miden-paymaster, drips 0.0005 Sepolia ETH), then broadcasts approve + bridgeAsset on the lxly bridge (vault-signed via the restored lib/agglayer/vault-evm.ts, addressed to the Miden account via midenAddrToEvmAddr). Lifecycle: not-initiated → initiated → processed | failed, with delivery reconciled against the bridge indexer (reconcileBuyBridges, mirroring reconcileAgglayerBridgedReceives, wired into the AllHistory poll). The Agglayer note auto-consumes on arrival like any other.

UI — dedicated Activity row ("Buy / Via MoonPay → Miden", progress-driven status chip) and a HistoryDetails "Buy Details" card (route, bridged amount, Etherscan-linked EVM tx hash, error on failure) with live reload while non-terminal.

Misc — all vault-signed EVM paths moved off viem's default Sepolia RPC (thirdweb, 429s under light write traffic) to PublicNode.

Local pieces (not in this repo)

  • ../moonpay-sign-server.mjs — URL signing + /tx-status proxy + webhook receiver (dev aid).
  • ../miden-paymaster — minimal Sepolia gas drip server. Interim: the target architecture is EIP-7702 sponsorship (relayer pays gas, EOA delegates to a smart-account implementation); the drip unblocks the end-to-end flow now.

Testing

  • Sandbox purchase end-to-end with pk_test_ keys (EUR→USDC, Banxa unavailable on the sandbox key per Onramper/MoonPay sandbox constraints; MoonPay test card).
  • MoonPay sandbox never settles on-chain, so the bridge leg was exercised by planting the test ERC-20 (0x699cFE…4A17, Sepolia) on the derived address: paymaster drip → approve → bridgeAsset broadcast → indexer-confirmed processed.
  • yarn lint, tsc --noEmit, and the chrome extension build are green.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant