Repository navigation
feat(arc): Arc testnet payment rail with Solana|Arc selector (flagged) - #538
Merged
Merged
Conversation
Adds a payment rail abstraction (lib/rails) and an Arc testnet rail behind NEXT_PUBLIC_ENABLE_ARC (default off): x402 exact on eip155:5042002 settled by Circle's Facilitator Service (keyless-trial seller proof or API key), with settlement_pending handled so nothing is delivered or charged twice; a separate per-browser Arc agents' wallet in its own sealed cookie, funded from MetaMask with a USDC transfer and withdrawn with a gas reserve; receipts with network arc-testnet in their own list and an /explorer network filter; and "Red de pago: Solana | Arc" in the Wallet tab. Solana stays the default and unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The explorer intro and API hint mention Arc testnet only while the flag is on; the Wallet tab's Solana panels load in the same chunk as the selector; the selector e2e waits for the dev server to compile lazy chunks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Derive the Arc payment-identifier only from signed data (keccak of authorization.from + nonce) and always overwrite the client's extension, so a forged id can no longer take over a pending payment or slip a settled one past the "already used" check. - Store a hash of the signed payment in the pending record and skip verify on retry only when the retried payment matches it exactly. - Treat a /settle timeout, 5xx or pending without a status id as pending (409) so the buyer resends the same payment instead of being charged with nothing delivered. Keep the settled mark at least as long as the authorization's validBefore. - Sign the network into approval tokens; an approval raised on Arc is refused when approved with Solana selected (and vice versa), and the chat sends the card's network, not the current selection. - Arc wallet panel: offer MetaMask only when a browser wallet exists, hide "Retirar todo" when only the gas reserve is left, Spanish errors. - Document that NEXT_PUBLIC_ENABLE_ARC must be set before the build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings new-code coverage back over the SonarCloud gate (80%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
Phase 1 of the Arc integration plan (
docs/arc/PLAN_INTEGRACION_ARC.mdin the Open-Stellar workspace), adapted to the owner's decision of 2026-10-14: same repo, same Vercel project and domain, and an in-app selector "Red de pago: Solana | Arc" that each browser chooses. Everything Arc is behindNEXT_PUBLIC_ENABLE_ARC(default off), so production keeps behaving exactly as today until the owner turns it on.How
lib/rails):RailInfofor Solana devnet and Arc testnet (CAIP-2, receipt network, label, asset, decimals, explorer links, tx/address validators),requestedRail()(Arc refused while the flag is off) and a per-browser preference (lib/rails/preference.ts, localStorage).lib/rails/arc/x402.ts):exactoneip155:5042002with@x402/evm@2.28.0, USDC0x3600...0000(6 decimals), settled by Circle's Facilitator Service. Order kept: verify, settle, then the LLM.lib/rails/arc/facilitator.ts): keyless-trial seller proof (EIP-712 "Circle Facilitator Seller Request" v1 over keccak256 of the exact body, signed by the payTo key), optionalCIRCLE_API_KEYBearer,payment-identifierderived from the EIP-3009 nonce, andsettlement_pendinghandled by polling/status(core 2.28 does not retry a pending result without a tx hash).ac:arc:x402:pending:*); the buyer resends the same signed payment (never re-signs), the retry skips verify and settles once; a settled payment is claimed (ac:arc:x402:settled:*) so a replayed header cannot buy a second task.ac_agent_wallet_arc, purposeagent-wallet:arc, envelopev:2, chain:"arc"), never derived from the Solana seed; same browser id, so caps and approvals stay per browser. Balance via ERC-20balanceOf(6 decimals only); withdraw signed by the agent, leaving a 0.01 USDC gas reserve.defineChainfrom the documented RPC/explorer) with a plainUSDC.transfer; faucet link to faucet.circle.com.createArcChatRunDepspays the task endpoint with?network=arc; prompts, approval card and receipts say "Arc testnet".network: "arc-testnet", 0x hashes accepted (they were dropped before), own listac:arc:receipts:v1,/api/receipts?network=solana|arc, network filter and per-receipt explorer links in/explorer, network badge in chat receipts. Arc USDC logs are read from the ERC-20 emitter only (the system emitter's 18-decimal duplicate is ignored).viemmoves from 2.47.6 to 2.57.5 in the lockfile (required by@x402/evm@2.28.0).Out of scope (later phases): CCTP from Solana, ERC-8004 on Arc (nothing here blocks it: the payment binding is already stored under
ac:arc:x402:paid:*).Env vars (only needed when the flag is turned on)
NEXT_PUBLIC_ENABLE_ARC=true,X402_ARC_PAY_TO,ARC_SELLER_PROOF_KEY(the key that controlsX402_ARC_PAY_TO), optionalCIRCLE_API_KEY,X402_ARC_FACILITATOR_URL,ARC_RPC_URL,NEXT_PUBLIC_ARC_RPC_URL,NEXT_PUBLIC_ARC_EXPLORER_URL. Nothing was changed in Vercel.Checks
npx tsc --noEmit: clean.npx vitest run: 185 files, 1372 tests passed (existing suites unchanged and green; 10 new test files: rail registry, Circle facilitator client with mocked HTTP for supported/verify/settle/settlement_pending/failed/403, seller-proof signature recovery, x402 seller+buyer end to end with a fake facilitator (402, pay once, replay refused, pending then single settle on retry), receipt parsing with the two Transfer events filtered by emitter, Arc cookie sealing and isolation from the Solana cookie, withdrawal with gas reserve, Arc chat deps, Arc receipts and network filter, task route with?network=arc).npm run build+npm run size-limit: 1.37 MB brotlied of 2 MB, with the flag off and with it on (the Arc panel is a lazy chunk).npm run secretlint: clean.payment-network,agent-wallet,sidebar-tabs,onboarding-modalspecs: 12 passed withNEXT_PUBLIC_ENABLE_ARC=true, 10 passed (2 Arc specs skipped) with the flag off. Network mocked in the new spec.Testnet end to end
Run locally against
next startbuilt with the flag on, keyless trial (no Circle account, no API key), test keys only. LLM calls were mocked in the test harness; the payments were real:GET /api/agent-wallet?network=arc:0xA54f364a8BA1A70fbA0f4191915798E022910984.network: "arc": the orchestrator hired a teammate; the hire paid 0.01 USDC agents' wallet to treasury, settled by Circle: 0x3bf24ad0...2066d (block 67117434, status success, ERC-20 Transfer of 10000 base units found by emitter). The receipt showed up in/api/receipts?network=arcand/explorerwith the Arc explorer link.Known gaps
/statuson its own.PaidTaskPanel) and 8004 identity stay Solana-only; ERC-8004 on Arc is phase 2.CIRCLE_API_KEYis set.🤖 Generated with Claude Code