Skip to content

feat: add x402 payment node for paid API calls#6389

Open
597226617 wants to merge 3 commits into
FlowiseAI:mainfrom
597226617:feat/x402-payment-node
Open

feat: add x402 payment node for paid API calls#6389
597226617 wants to merge 3 commits into
FlowiseAI:mainfrom
597226617:feat/x402-payment-node

Conversation

@597226617
Copy link
Copy Markdown

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

  1. Takes an endpoint URL, request params, and max price as input
  2. Calls the endpoint
  3. Handles HTTP 402 Payment Required responses
  4. Signs and sends USDC payment on Solana (SPL TransferChecked)
  5. Retries with X-Payment header
  6. Returns the paid API response + transaction metadata

x402 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 definition
  • packages/components/nodes/tools/x402Payment/core.ts — Payment logic (x402 flow)
  • packages/components/nodes/tools/x402Payment/payment.svg — Node icon
  • packages/components/credentials/X402Wallet.credential.ts — Wallet credential

Testing

  • Tested with local development setup
  • Payment envelope generation verified against @acedatacloud/x402-client spec

Related

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>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +135 to +136
const transaction = new Transaction().add(instruction)
const signature = await connection.sendTransaction(transaction, [keypair])
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.

critical

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'))
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.

high

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
  1. 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')
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.

medium

The Solana and Base RPC URLs are hardcoded to public endpoints (lines 114 and 165). These public services often have strict rate limits and may be unreliable for production workloads. It is recommended to allow users to provide their own RPC URLs via the node parameters or credentials.

Comment on lines +259 to +271
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)
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.

medium

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
  1. 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,
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.

medium

The maxOutputLength constraint is ignored when a payment is required. The response data should be truncated to respect the user's configuration, similar to how it is handled in the non-payment path on line 279.

Suggested change
data: text,
data: text.slice(0, this.maxOutputLength),

孙备 and others added 2 commits May 15, 2026 16:14
…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>
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