Skip to content

feat(x402): BSC multi-asset payments + Permit2 signing#457

Merged
gulshngill merged 2 commits into
mainfrom
feat/x402-bsc-permit2-multi-asset
Jun 11, 2026
Merged

feat(x402): BSC multi-asset payments + Permit2 signing#457
gulshngill merged 2 commits into
mainfrom
feat/x402-bsc-permit2-multi-asset

Conversation

@gulshngill

Copy link
Copy Markdown
Contributor

What

The Nansen API now advertises four stablecoins on BNB Smart Chain — U (United Stables), USD1, USDT, and USDC. This PR makes the CLI able to pay with all of them.

U and USD1 already work with the CLI's existing EIP-3009 flow (the server lists them first for exactly that reason). USDT and USDC on BSC predate EIP-3009, so facilitators settle them through Uniswap's canonical Permit2 contract — which needs a different signature type.

Changes

src/x402-evm.js — Permit2 signing (permit2-exact)

  • Payment creation routes on the 402's extra.assetTransferMethod: absent/eip3009 → existing gasless flow; permit2-exact → new createPermit2ExactPayload; anything else throws so the fallback loop moves to the next accepts entry.
  • Signs a PermitWitnessTransferFrom against the spender contract advertised in the 402 (extra.spenderAddress — never hardcoded), witness bound to the merchant wallet. Nested-struct EIP-712 hashing and the 3-field Permit2 domain are implemented in the repo's existing zero-dependency style.
  • Cross-validated: the digest test vector was computed with an independent EIP-712 implementation (Python eth_account) over identical input — the two agree byte-for-byte.

src/x402.js — allowance preflight + multi-asset registry

  • Permit2 requires a one-time on-chain token.approve(PERMIT2_ADDRESS, …) from the payer. Entries are skipped with an actionable message when the allowance is missing, instead of burning a doomed verify round-trip. RPC failures fail open (server decides).
  • EVM_X402_TOKENS becomes an ordered per-network token list (BSC: U, USD1, USDT, USDC — all 18-decimal BEP-20 deployments); RPC URLs split into EVM_X402_RPCS.
  • checkX402Balance(network, asset) checks the token actually paid with, so low-balance warnings use the right symbol and decimals.

src/api.js — threads the paid asset through to the balance warning.

Testing

  • New x402-permit2.test.js (7 tests): reference digest vector, payload wire shape (decimal-string numerics, no EIP-3009 fields), missing-spender error, method routing (permit2-exact / eip3009 / absent / unsupported).
  • Registry tests updated for the multi-asset shape.
  • Full suite: 1498 passed.

Notes

  • Wallets holding U or USD1 pay on BSC with no extra steps. Wallets paying USDT/USDC on BSC need the one-time Permit2 approval (requires a little BNB for that single transaction; every payment after is a gasless signature).
  • permit2-upto is intentionally unsupported for now — the CLI only uses exact-amount payments.

🤖 Generated with Claude Code

The Nansen API now advertises four stablecoins on BNB Smart Chain
(U, USD1, USDT, USDC). Two of them (USDT, USDC) predate EIP-3009 on
BSC and settle via Uniswap's canonical Permit2 contract instead.

- x402-evm: route payment creation on extra.assetTransferMethod —
  eip3009/absent keeps the existing gasless flow; permit2-exact signs
  a Permit2 PermitWitnessTransferFrom (nested-struct EIP-712, 3-field
  Permit2 domain) against the spender contract advertised in the 402.
  Unsupported methods throw so the fallback loop tries the next entry.
  The digest implementation is cross-validated against an independent
  EIP-712 implementation via a fixed test vector.
- x402: skip permit2 entries with an actionable message when the wallet
  has not made the one-time approve(Permit2, ...) for the token
  (allowance preflight via eth_call); EVM_X402_TOKENS becomes an
  ordered per-network token list; checkX402Balance takes the asset
  actually paid with so warnings use the right token and decimals.
- api: thread the paid asset through to the balance warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gulshngill gulshngill requested a review from kome12 June 11, 2026 09:53
Comment thread src/x402.js Outdated
});
const data = await resp.json();
if (typeof data.result !== 'string') return true;
return BigInt(data.result) > 0n;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: this preflight only detects the zero-allowance case (> 0n), but the skip message below tells users to approve(Permit2, <amount>). A wallet that approved a small finite allowance now exhausted (or below the payment amount) would pass this check and then fail server-side instead of getting the actionable skip message.

Since it fails open on RPC error anyway, the heuristic is defensible — but consider either a one-line comment noting it only catches the never-approved case, or threading the payment amount through to compare allowance >= amount. Non-blocking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — went with the real fix rather than the comment: hasPermit2Allowance now takes the payment amount and compares allowance >= amount, so the exhausted/insufficient-finite-approval case gets the actionable skip message too. Skip message updated to name both cases. Fail-open on RPC error unchanged. Fixed in d02572c.

kome12
kome12 previously approved these changes Jun 11, 2026
The preflight only caught the never-approved case (allowance > 0); a
finite approval now exhausted or below the payment amount passed the
check and failed server-side instead of getting the actionable skip
message. Compare allowance >= requirement.amount and say so in the
message. Fail-open on RPC errors unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gulshngill gulshngill requested a review from kome12 June 11, 2026 11:43
@gulshngill gulshngill merged commit 507c0ca into main Jun 11, 2026
7 checks passed
@gulshngill gulshngill deleted the feat/x402-bsc-permit2-multi-asset branch June 11, 2026 11:43
@github-actions github-actions Bot mentioned this pull request Jun 11, 2026
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.

2 participants