feat: add x402 payment node for paid API calls#6389
Conversation
Add support for the x402 HTTP 402 payment protocol to Flowise: - X402Wallet credential type for storing wallet private keys (Solana/Base) - X402Payment tool that automatically handles 402 Payment Required responses - Supports Solana USDC payments via TransferChecked instruction - Supports Base (Ethereum L2) USDC payments - Includes proper error handling, max price validation, and payment retries - Added dependencies: @solana/web3.js, @solana/spl-token, ethers The node enables AI agent workflows to call paid external APIs that require payment via the x402 protocol. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a new x402 payment node for Flowise, enabling AI agents to interact with paid APIs using the x402 protocol on Solana and Base networks. The implementation includes a new credential type for wallets and a tool that handles HTTP 402 responses by signing and sending USDC payments. Feedback highlights critical issues in the Solana transaction logic, such as missing blockhashes and fee payers, as well as potential failures due to hardcoded RPC URLs and private key format assumptions. Additionally, improvements were suggested regarding inconsistent return types and the enforcement of output length constraints during the payment flow.
| const transaction = new Transaction().add(instruction) | ||
| const signature = await connection.sendTransaction(transaction, [keypair]) |
There was a problem hiding this comment.
The Solana transaction is being sent without a recentBlockhash or feePayer being explicitly set on the transaction object. While sendTransaction can sign the transaction, it does not automatically fetch a blockhash if it's missing, which will cause the transaction to fail on-chain. You should fetch the latest blockhash from the connection before sending.
const { blockhash } = await connection.getLatestBlockhash();
const transaction = new Transaction();
transaction.add(instruction);
transaction.recentBlockhash = blockhash;
transaction.feePayer = fromAddress;
const signature = await connection.sendTransaction(transaction, [keypair]);| throw new Error(`Unsupported currency: ${paymentReq.currency}`) | ||
| } | ||
|
|
||
| const keypair = Keypair.fromSecretKey(Buffer.from(this.privateKey, 'base64')) |
There was a problem hiding this comment.
Solana private keys (secret keys) are typically represented as Base58 strings or as a JSON array of numbers. Using Buffer.from(this.privateKey, 'base64') will fail for these standard formats. Validating the input format and throwing an error for invalid types promotes fail-fast behavior.
References
- When handling potentially invalid data from external sources, prefer throwing an error for invalid input types rather than silently returning a default or empty value.
| } | ||
|
|
||
| const keypair = Keypair.fromSecretKey(Buffer.from(this.privateKey, 'base64')) | ||
| const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed') |
There was a problem hiding this comment.
| const response: X402PaymentResponse = { | ||
| success: true, | ||
| data: text, | ||
| txHash: signature, | ||
| payment: { | ||
| amount: paymentReq.price, | ||
| currency: paymentReq.currency, | ||
| chain: paymentReq.chain, | ||
| txHash: signature | ||
| } | ||
| } | ||
|
|
||
| return JSON.stringify(response) |
There was a problem hiding this comment.
The tool returns a JSON string when a payment is processed, but returns raw text when no payment is required. This inconsistent return format makes it difficult for downstream nodes to parse the output reliably. Avoid overloading the return structure with different data types to prevent fragile logic.
References
- Avoid overloading a single property to hold different types of data if it can lead to fragile logic or incorrect UI reconstruction.
| const text = await res.text() | ||
| const response: X402PaymentResponse = { | ||
| success: true, | ||
| data: text, |
There was a problem hiding this comment.
…ication - Remove connection.sendTransaction() from signSolanaPayment - Remove contract.transfer() call from signBasePayment - Both methods now only sign and serialize transactions locally - Server receives signed transaction in X-Payment header for verification - Prevents spending funds before API validates payment proof - Remove CLAUDE.md from git tracking (accidentally committed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rice check - Fixed Solana amount calculation to use BigInt instead of Math.floor for precision - Changed 402 response parsing from HTTP headers to JSON body per x402 protocol - Updated X402PaymentRequired interface to match x402 spec (maxAmountRequired, asset, network, scheme, extra) - Fixed maxPrice validation to check maxAmountRequired instead of price - Used getAssociatedTokenAddressSync to avoid unnecessary async RPC calls - Removed memo field as it's not in x402 protocol spec Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Adds an x402 Payment Node to Flowise that enables AI agent workflows to call paid external APIs using the x402 HTTP 402 payment protocol.
What it does
402 Payment RequiredresponsesX-Paymentheaderx402 Protocol
x402 is an open payment protocol for AI agents — pay per API call with on-chain USDC. No API keys, no accounts, no subscriptions. Supported by the Linux Foundation.
Files Added
packages/components/nodes/tools/x402Payment/X402Payment.ts— Node UI definitionpackages/components/nodes/tools/x402Payment/core.ts— Payment logic (x402 flow)packages/components/nodes/tools/x402Payment/payment.svg— Node iconpackages/components/credentials/X402Wallet.credential.ts— Wallet credentialTesting
Related