Skip to content

fix(x402): rank Privy payment options and skip methods it cannot sign - #822

Merged
kome12 merged 3 commits into
nansen-ai:mainfrom
erkancamli:fix/privy-x402-option-order
Nov 10, 2026
Merged

kome12 merged 3 commits into
nansen-ai:mainfrom
erkancamli:fix/privy-x402-option-order

Conversation

@erkancamli

Copy link
Copy Markdown
Contributor

x402 auto-payment with a Privy default wallet does not follow the rules the local wallet (#700) and WalletConnect (#745) paths already follow.

createPrivyPaymentSignatures (src/privy.js):

  • signs the EVM options in the order the server lists them, then the Solana ones, and never calls rankRequirements. With a 402 listing [Base USDC 0.90, Base USDC 0.01] (the case from fix(x402): pay the cheapest viable option a server offers #700), a Privy wallet signs and pays 900000 base units, while a local wallet pays 10000.
  • signs every EVM option as an EIP-3009 TransferWithAuthorization, whatever its extra.assetTransferMethod is. For a permit2-exact option such as BNB Chain USDT (feat(x402): BSC multi-asset payments + Permit2 signing #457), the payload claims Permit2 but carries an EIP-3009 authorization for a token that has no transferWithAuthorization, so it cannot be settled and the request ends in "Payment required". The local wallet signs the same option with Permit2.

Fix

  • One loop over rankRequirements(requirements), so EVM and Solana options are tried cheapest first. The per-option signing code moves unchanged into two helpers, one for EVM and one for Solana; each wallet is still looked up once.
  • EVM options whose assetTransferMethod is not EIP-3009 are skipped with the reason, the same rule as the WalletConnect path, and the next option is tried.
  • Changeset added.

Tests

  • 4 new tests in src/__tests__/privy.test.js (ranking across EVM options, Solana before a pricier EVM option, skipping permit2-exact, skipping an unknown method). All 4 fail on main and pass with this change.
  • I also ran nansen research smart-money netflow end to end with a temp HOME and a mocked API, RPC and Privy client. On main the Privy wallet signs value=900000 for the ranking case and an EIP-3009 payload for the permit2-exact case. With this change it pays 10000, and it skips the permit2-exact option with the reason.
  • npm run lint passes.

npm test:

 Test Files  1 failed | 128 passed (129)
      Tests  5 failed | 5942 passed | 17 skipped (5964)

The 5 failures are the src/__tests__/doctor.test.js cases for unreadable wallets and config files. My sandbox runs as root, where chmod 000 does not block reads; they fail the same way on unmodified main there.

createPrivyPaymentSignatures signed the EVM options in the order the
server listed them, then Solana, without rankRequirements, so a Privy
wallet could pay a more expensive option than the local wallet (nansen-ai#700)
or WalletConnect (nansen-ai#745) would. It also signed every EVM option as an
EIP-3009 TransferWithAuthorization, including permit2-exact options
such as BNB Chain USDT, which the facilitator then rejects.

Go through rankRequirements' order and skip options whose
assetTransferMethod is not EIP-3009, with a reason, as the
WalletConnect path does.
Comment thread src/privy.js Resolved
@nansen-pr-reviewer

nansen-pr-reviewer Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

nansen-pr-reviewer summary

The change consistently ranks Privy payment options by policy-approved USD cost, skips transfer methods Privy cannot sign, scopes wallet lookup failures to their chain, and includes focused tests plus a changeset. I found no clear correctness or security defect in the reviewed diff.

Deterministic check: success — No issues found

Risk: 3/5 (Elevated)

✅ Auto-approved — Deterministic gates passed: severity thresholds met, recommendation "approve", risk level 3/5 within threshold 3.


Token usage: 36,609 input, 2,103 output, 35,350 cache read | Usage Guide

Cooldown: for the next 10 minutes (counting from when this review finished), new pushes to this PR will not trigger another review — the next push after the window expires will. Need a fresh review sooner? Comment @nansen-pr-reviewer[bot] re-review.

With options ranked across chains, a failed wallet lookup for the
cheapest option's chain (e.g. a Privy API error) rejected the generator
before options on the other chain were tried. Log the failure, skip that
chain's options and keep going, with a test where the Solana lookup
fails and the EVM option is still signed.
@erkancamli

Copy link
Copy Markdown
Contributor Author

Good catch, fixed in the latest commit. A failed wallet lookup now logs the error and skips only that chain's options, so the remaining ranked options on the other chain are still tried. Added a test where the Solana lookup fails (Privy 503) and the EVM option is still signed; it fails without the change. privy.test.js 43/43, the x402/Privy/API test files 697 passed, and npm run lint is clean.

nansen-pr-reviewer[bot]
nansen-pr-reviewer Bot previously approved these changes Nov 6, 2026

@nansen-pr-reviewer nansen-pr-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Auto-approved by Nansen review

Deterministic gates passed: severity thresholds met, recommendation "approve", risk level 2/5 within threshold 3.

  • The deterministic severity check passed: No issues found
  • Risk level 2/5 (Low)
  • The model recommended: approve

Approval was decided by deterministic gates, not by the model. The model's recommendation can block auto-approval but never cause it.

@kome12

kome12 commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

@erkancamli Thanks, this works as described. I ran the CLI against a mocked 402 for the ranking, permit2-exact, Solana-first and lookup-failure cases, and all of them behave correctly now. The new tests fail on main and pass here.

One regression from the second commit: when a wallet lookup fails and no other option gets signed, the JSON error used to be x402 Privy payment failed: (for example Invalid app ID or app secret). Now it is just Payment required and the reason appears only on stderr. Could you keep the lookup error and if the loop finishes without signing anything, rethrow it? The fallback to the other chain would still work, and users who get the Privy setup wrong would see the reason in the error again. A test with an EVM-only 402 where the EVM lookup fails, asserting the generator rejects with that message, would cover it.

Skipping a chain whose wallet lookup failed hid the reason when no
other option could be signed: the CLI ended with "Payment required"
and the Privy error only reached stderr. Keep the first lookup error
and rethrow it when the loop finishes without signing anything, so
the JSON error reads "x402 Privy payment failed: <reason>" again. The
fallback to an option on the other chain is unchanged.
@erkancamli

Copy link
Copy Markdown
Contributor Author

@kome12 thanks for testing it. Done in the latest commit: the first wallet lookup error is kept, and if the loop finishes without signing anything it is rethrown, so the JSON error reads x402 Privy payment failed: <reason> again. The fallback to an option on the other chain is unchanged.

Added the test you described: an EVM-only 402 whose EVM lookup fails with a 401 Invalid app ID or app secret now rejects with that message. It fails without the change. privy.test.js 44/44, the x402, Privy and API test files 698 passed, and lint is clean. I also added the lookup behaviour to the changeset.

@nansen-pr-reviewer nansen-pr-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Auto-approved by Nansen review

Deterministic gates passed: severity thresholds met, recommendation "approve", risk level 3/5 within threshold 3.

  • The deterministic severity check passed: No issues found
  • Risk level 3/5 (Elevated)
  • The model recommended: approve

Approval was decided by deterministic gates, not by the model. The model's recommendation can block auto-approval but never cause it.

@kome12
kome12 merged commit 2e1b15e into nansen-ai:main Nov 10, 2026
13 checks passed
@github-actions github-actions Bot mentioned this pull request Nov 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