External wallets
Use an external wallet when your application already manages a blockchain wallet and you want to pay from it. You connect a signer—the component that authorizes payments for that wallet—to your payment client. Your application remains responsible for protecting the wallet’s credentials and controlling its spending.
This guide covers blockchain wallets for x402 payments. Start with Pay with x402 for the HTTP request flow. Stripe payments covers Link payments; Visa CARD covers that separate card-payment integration.
Choose how to fund payments
InFlow-managed payments use your InFlow account and its supported payment options. External-wallet payments use signatures from a wallet your application controls. That signer can hold a private key locally or ask a wallet provider to sign. Applications using only InFlow-managed funds do not need an external signer.
A combined client supports both sources. It first looks for a seller offer that InFlow can sign. If none matches, it asks a registered external signer to sign a compatible offer. Registering a signer does not force the client to use that wallet, and a failed InFlow payment does not automatically retry with it.
Use an external-only client when every payment must come from your wallet. The Node.js, Go and Python examples below configure combined clients, which need an InFlow account credential for capability discovery. For external-only use, compose the upstream x402 clients directly. Rust also supports external-only operation through HttpBuyer::new(None), without an InFlow account lookup.
Your wallet needs the payment token on the seller’s advertised network. A token balance on another network is not automatically transferred or converted. Use Networks and currencies to identify networks and test-token sources. External signing also depends on the selected signer and the seller’s facilitator, not just InFlow’s managed-payment support list.
Connect your wallet
Supply a signer from your application’s wallet configuration. Keep private keys out of source files, logs and HTTP requests. Registering a signer configures the client in your application; it does not register a wallet with InFlow, deposit funds or send InFlow the private key.
These setup fragments add an Ethereum-compatible wallet for exact payments. They do not send a paid request. The Go, Node.js and Python fragments register Base Sepolia, eip155:84532. The Rust fragment requires an application selector to restrict networks, tokens and amounts. Configure InFlow credentials for Sandbox separately; an environment setting does not change a wallet’s blockchain network.
go get github.com/inflowpayai/inflow-go@v0.3.2
package walletbuyer
import (
inflow "github.com/inflowpayai/inflow-go"
"github.com/inflowpayai/inflow-go/x402/buyer"
foundation "github.com/x402-foundation/x402/go/v2"
"github.com/x402-foundation/x402/go/v2/mechanisms/evm"
exact "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/client"
)
func NewBuyer(options inflow.Options, signer evm.ClientEvmSigner) (*buyer.Client, error) {
external := foundation.Newx402Client().Register(
"eip155:84532", exact.NewExactEvmScheme(signer, nil),
)
return buyer.New(buyer.Options{Options: options, External: external})
}Pass Sandbox inflow.Options and your EVM signer to NewBuyer. The returned client can use Do for an HTTP request or Sign for explicit payment creation. The upstream client supplied as External retains its spending controls. See the Go buyer guide for options and HTTP integration.
npm install @inflowpayai/x402-buyer@0.11.2 @x402/core@^2.27.0 @x402/evm@^2.27.0
import { createInflowClient } from '@inflowpayai/x402-buyer';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
type EvmSigner = Parameters<typeof registerExactEvmScheme>[1]['signer'];
export async function combinedBuyer(apiKey: string, signer: EvmSigner) {
const client = await createInflowClient({ apiKey, environment: 'sandbox' });
registerExactEvmScheme(client, { signer, networks: ['eip155:84532'] });
return client;
}Pass your Sandbox InFlow API key and wallet signer. Supply the returned client to x402HTTPClient from @x402/core/client, as in the payment-request guide. registerExactSvmScheme from @x402/svm/exact/client provides the separate Solana registration. See the Node.js buyer guide for both signer interfaces.
pip install 'inflowpay[evm]==0.3.2'
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from inflowpay import ClientOptions
from inflowpay.x402.buyer import Buyer
from x402.mechanisms.evm.exact.client import ExactEvmScheme
from x402.mechanisms.evm.signer import ClientEvmSigner
@asynccontextmanager
async def combined_buyer(
options: ClientOptions, signer: ClientEvmSigner,
) -> AsyncIterator[Buyer]:
async with await Buyer.create(options) as buyer:
buyer.register('eip155:84532', ExactEvmScheme(signer))
yield buyerOpen combined_buyer with an async with block, passing Sandbox ClientOptions and your signer. Keep that block open while making requests. Supply the yielded buyer to the upstream HTTP transport described in the payment-request guide. The context manager closes the InFlow client when you leave the block.
Solana uses the svm extra and its own signing scheme. Its upstream signer makes synchronous blockchain requests, which can block other work on the same event loop. Account for that in an asynchronous application; see the Python external-wallet guide for the runtime limitation.
cargo add inflow-x402-buyer@0.4.1 --features evm cargo add alloy-signer-local@2
use alloy_signer_local::PrivateKeySigner;
use inflow_x402_buyer::{evm, Buyer, Error, HttpBuyer, PaymentSelector};
pub fn wallet_buyer(
managed: Option<Buyer>,
signer: PrivateKeySigner,
selector: impl PaymentSelector + 'static,
) -> Result<HttpBuyer, Error> {
Ok(HttpBuyer::new(managed)?
.register(evm::V2Eip155ExactClient::new(signer))
.with_selector(selector))
}Pass Some(buyer) for combined payments or None for external-only payments. Supply a PaymentSelector that accepts only your intended network, token, recipient and amount. Use HttpBuyer::execute for the paid HTTP request; Buyer::prepare is the managed-payment API, not the external-wallet path.
The evm feature enables the Ethereum-compatible signer. Solana uses the solana feature, a Solana signer and a blockchain client. Neither chain feature is enabled by default. See the Rust buyer guide for the public interfaces.
Control what the wallet can spend
Apply spending rules before the signer authorizes payment. Check the seller’s destination, payment recipient, network, token contract and amount. Token names alone are insufficient: two contracts can use the same name. Choose payment options shows how to constrain the advertised offers.
| Client | External-wallet default | Where to configure it |
|---|---|---|
| Go | Recognized tokens; $1 per-payment cap. | SetSpendControls on the upstream client supplied as External. |
| Node.js | Recognized tokens; $1 per-payment cap. | setSpendControls on the x402 client. |
| Python | Recognized tokens; $1 per-payment cap. | set_spend_controls on the buyer. |
| Rust | First matching offer; no built-in token list or payment cap. | with_selector on HttpBuyer. The example above requires you to supply that selector. |
“Recognized” means the registered scheme’s built-in token list identifies the token on that network. These defaults are client safeguards, not x402 price limits. Configure a token-specific cap when allowing another token; do not disable all controls just to make a request succeed. An atomic amount is an integer in the token’s smallest units: 1000000 means one USDC for a six-decimal USDC contract.
A per-payment cap does not limit total spending across many requests. Your application must enforce its own cumulative budget and owner-consent rules for external wallets, including concurrent requests. InFlow account policies and approvals govern managed payments; they do not automatically authorize or restrict signatures from your external wallet.
Prepare for network fees
The purchase amount and blockchain transaction fees are separate costs. For Ethereum-compatible exact payments using EIP-3009, the buyer signs a token-transfer authorization and the facilitator submits the transaction. The buyer still needs the payment tokens, but does not submit a gas-paying transaction for that transfer. The x402 exact specification describes this flow.
Permit2 is another transfer mechanism for Ethereum-compatible tokens. It can need an existing token allowance: permission for the Permit2 contract to spend those tokens. Establishing that allowance can require a separate on-chain transaction. A compatible sponsorship extension lets the facilitator cover that setup instead. Sponsorship does not supply the tokens for your purchase.
Token approval with a signature
With EIP-2612 sponsorship, a compatible token allows the owner to sign the approval rather than submit it separately. The seller must advertise the extension, and the signer needs the token and chain-reading capabilities required by its SDK. Use the selected SDK’s sponsorship instructions rather than assuming registration alone enables this flow.
Rust’s upstream exact signer in x402-chain-eip155 2.0.2 does not create an EIP-2612 allowance permit. Arrange sufficient allowance separately, supply a compatible custom signer, or use a separately supported sponsorship path. The Rust sponsorship notes explain this limitation and the token metadata that its Permit2 signer requires.
Optional InFlow sponsorship
InFlow also provides an optional EIP-7702 extension for compatible external wallets. It prepares token approval and payment together when Permit2 allowance is insufficient. The seller must advertise inflowEip7702GasSponsoring, and the buyer must register the corresponding extension. The wallet, network and InFlow environment must support that flow.
EIP-7702 can delegate wallet execution to a specific contract. Show the owner the network and delegation address, and request consent before signing a required delegation. Delegation can persist even if the payment fails; the payment preparation’s expiry does not revoke it. This is a separate decision from approving the purchase amount.
Follow the extension setup for Go, Node.js, Python or Rust. The setup examples on this page do not enable sponsorship extensions. A failed preparation or rejected signature must stop that payment rather than silently changing wallets.
Make a payment and check the result
Use the configured client in your x402 request flow. The seller returns its payment requirements; the client selects an acceptable offer and asks the signer to authorize it. Send the resulting payment header with the same destination, method, body and required application headers. Keep redirects disabled so credentials are not forwarded to another destination.
A signature authorizes payment; it does not prove settlement or successful delivery. Check the paid HTTP response and decode the x402 settlement response when supplied. Confirm that you received the requested resource, not another payment challenge or an application error. Check payment status and recover explains how to distinguish payment outcomes from request failures.
An external signer does not necessarily create an InFlow buyer transaction ID. Keep the seller response, settlement transaction reference and your application’s request identifier. Use the seller or facilitator’s evidence to investigate an uncertain outcome. An InFlow account-status lookup cannot find an external payment merely from a wallet signature.
After a timeout, do not automatically sign a replacement payment. Preserve the original payment material securely while determining whether it settled and whether the seller can replay the result. Keep signatures and payment headers out of general logs; use redacted diagnostics and transaction references instead.
Test your integration
Start with a dedicated test wallet, a test network and a seller endpoint you control. For a combined client, confirm which signer was used. A successful managed payment does not test your external wallet; use an external-only client or a compatible offer with no managed match for that check.
- Complete a small payment and confirm the resource, settlement response and token movement belong to the intended wallet and recipient.
- Offer an unapproved token, network or recipient, and an amount above your cap. Confirm rejection before the wallet signs.
- Test insufficient token funds and missing token allowance separately. Neither should produce a false success result.
- Reject a wallet signature or sponsorship delegation. Confirm that no other wallet or payment option is silently charged.
- Make a managed payment fail while an external signer is registered. Confirm that failure does not start an external payment.
- Interrupt the connection after submitting payment. Confirm that your application reports an uncertain outcome and does not create a replacement purchase.
- Send concurrent requests near your application’s spending budget. Confirm that separate successful checks cannot collectively exceed that budget.
Keep policy and failure tests alongside your project’s unit and integration tests. Also test one complete request with your actual signer and a running seller. A mock signer or a separate CLI payment does not establish that your application’s wallet integration works.
Continue your integration
Choose payment options
Restrict the offers your buyer can select before authorizing payment.
Check payment status and recover
Separate a failed request from a failed payment and avoid replacement purchases.
Test your integration
Verify the complete buyer and seller flow before accepting live traffic.