The connector, by hand
Get paid
A node anyone on Base or Solana can open a channel to and pay, with no gas.
A price makes a route cost something. A settlement backend is what lets anyone actually pay it. There are two chains, and a node may carry either table or both; with both, it accepts vouchers on both at once. Every channel is an x402 batch-settlement channel: on EVM that is x402's audited x402BatchSettlement contract, and on Solana the Solana Foundation's payment-channels program. Both are constants of the binary, the same address on every network, so there is nothing chain-specific to name beyond your own RPC, token and key.
The short version: add the eight-line settlement block below to connector.toml, give its key a little gas, and restart. connector init --devnet writes it for you. The rest of this page is what each line commits you to.
The shape
| Step | Who acts | What touches the chain |
|---|---|---|
| Terms | client reads | Nothing: GET /ilp, or the 402 greeting on an unpaid request. |
| Open | client | EVM: one deposit, relayed by an x402 facilitator. Solana: one open, co-signed by your node. |
| Pay | client | Nothing: one signed voucher per paid packet, carried inside ILP. |
| Land | your node | Batched claim + settle (EVM), settle (Solana), every ten minutes. |
| Exit | client | EVM: initiateWithdraw, then finalizeWithdraw. Solana: request_close, then the payer's withdrawal. |
| Exit race | your node | Lands its latest voucher as soon as it sees the exit start. |
Value moves one way on a channel, client to you. Claims are frequent and free, and settlement is rare. The chain sees the deposit, the sweeps, and the exit.
The settlement block
# EVM, Base Sepolia. These are live devnet addresses, not placeholders. [settlement.evm] rpc_url = "https://base-sepolia-rpc.publicnode.com" token_address = "0x0C996d7c934c79a6255254875607Fe69df25C0E1" # the token every price is in decimals = 6 # units per token: 6 means 1,000,000 = 1.00 asset_eip712_name = "USDC" # the token's own EIP-712 domain: asset_eip712_version = "2" # cast call <token> 'name()(string)' / 'version()(string)' # min_withdraw_delay_secs = 86400 # default one day; 900 at least, 30 days at most # asset_transfer_method = "eip3009" # "permit2" for a token without ERC-3009 # facilitator_url = "https://..." # the x402 facilitator payers relay deposits through [settlement.evm.key] key_file = "/app/data/settlement.key" # Solana, public devnet. [settlement.solana] rpc_url = "https://api.devnet.solana.com" token_address = "34eSxY7qxQ4GzyhDJ8GpUcTz1WWzruGbJbR8q6TtxfQU" # an SPL mint; Token-2022 is refused decimals = 6 min_sponsored_deposit = 1000000 # base units: the smallest deposit whose open you will pay rent for # min_grace_period_secs = 86400 # default one day; 900 at least [settlement.solana.key] key_file = "/app/data/settlement-solana.key"
Copy the token address rather than picking your own: a voucher resolves against one token, so every node that might accept a given voucher has to name the same one. The two asset_eip712_* keys and min_sponsored_deposit have no default and are required wherever their table exists. Read the EIP-712 name and version off the token itself: Base mainnet's native USDC is "USD Coin" / "2", not "USDC". A wrong name shows up only as every client's deposit failing.
decimals is what turns a price into money. The node reads the token's own decimals at boot and refuses to start if the config disagrees, because a wrong value misprices every route by a factor of ten or more. Every other settlement value is verified against the chain the same way, which is why there is no --network flag: which chain a node is on is these values. The node confirms x402BatchSettlement's code is present at its fixed address before it will serve.
Chains
| EVM | Solana | |
|---|---|---|
| Devnet | Base Sepolia, chain id 84532 | public devnet |
| Mainnet | Base, chain id 8453 | mainnet-beta |
| Channels live in | x402BatchSettlement, the same address on every network | payment-channels, the same program id on every cluster |
| Devnet token | 0x0C996d7c…C0E1, devnet USDC (Circle FiatToken v2.2, ERC-3009), 6 dp | 34eSxY7q…xfQU, mock USDC mint, 6 dp |
| Funding the key | Base Sepolia ETH for gas; devnet USDC from the faucet | devnet SOL (solana airdrop); mock USDC from the same faucet |
There is no Solana testnet deployment: payment-channels is on devnet and mainnet-beta, nowhere else. Mainnet exists on both chains and the live node at toon.ar.io settles there. Because the contract and program are the same address everywhere, the one thing that has to match the network is the token: on mainnet, point token_address at Base mainnet's USDC or Solana mainnet-beta's, never the devnet one. The x402 Foundation lists x402BatchSettlement at one address on Base, Arbitrum, Polygon, Optimism, Avalanche, Celo, Linea, Unichain, Monad and World Chain; the node will serve on any of them where the code is present.
How a channel is admitted
You do not list the channels your payers will use, and you could not: a client's channel does not exist until that client opens it toward you. A voucher naming a channel you have never heard of is resolved from chain and admitted, which is what makes paying you permissionless rather than an arrangement. Your node only admits a channel whose terms are the ones it published:
| Chain | The channel must have |
|---|---|
| EVM | receiver and receiverAuthorizer = your settlement address; token = your token_address; withdrawDelay ≥ your minimum; a nonzero payerAuthorizer. That last rule is stricter than x402. The client's deposit is relayed by the facilitator you name; a payer holding ETH can deposit directly instead. The first voucher on a channel your node has not seen carries the full channelConfig. |
| Solana | The client builds an open in which your settlement key is fee payer, rent_payer and payee, signs it, and posts it to POST /ilp/batch-settlement/solana/open, which the greeting names as extra.sponsorEndpoint. The node checks every field, co-signs, submits, waits for confirmation, and admits it. The endpoint is public, bounded by a cap of 8 sponsorships in flight, 1 per payer, and an hourly budget of failed opens. Only SPL Token mints; Token-2022 is refused. |
How a voucher is checked. A voucher has no nonce. It is accepted only if its amount is strictly greater than the last one accepted on that channel, by at least the route's charge. A replayed voucher buys nothing. A packet to a free route carries no voucher at all. A client that lost track asks POST /ilp/claim-state to learn the amount it has to beat.
How you get paid
Automatically, with no operator write, for what lands on chain:
| Chain | Cadence |
|---|---|
| EVM | Every 5 s, read new WithdrawInitiated logs and claim that channel's latest voucher at once, because only the amount already claimed survives the withdrawal. Every 10 min, claim every held voucher above what the chain records, many channels in one transaction, then settle to your address. The sweep also runs at boot. |
| Solana | Every 10 s, rediscover every channel you sponsored from the chain. Open: settle the latest voucher every 10 min. Closing: settle_and_seal inside the grace period. Sealed: distribute. Distributed: reclaim the channel's rent. |
The one-day default minimum for withdrawDelay and grace_period is the window a delayed or censored landing transaction still has. POST /channels/:id/land is the manual lever for planned maintenance.
Who pays gas, and why
x402BatchSettlement has no fee field. Nothing on chain pays a facilitator, so gas is paid by whoever wants the transaction to happen. In TOON that is the seller: your node names the facilitator it relays deposits through, and deposit gas is a cost of the sale. Your node publishes that URL and never calls it; the payer does.
| Step | Who pays |
|---|---|
| EVM deposit or top-up | The facilitator you name, off chain at your expense; or the payer, from its own ETH. |
| EVM Permit2 approval (once) | The facilitator, if it sponsors approvals for this token; otherwise the payer's own ETH. |
Solana open | You: the transaction fee and 4,711,920 lamports (about 0.0047 SOL) of rent per channel, returned at reclaim. |
| A voucher | Nobody: a signature. |
EVM claim and settle | You, from the EVM settlement key's ETH. |
Solana settle, seal, distribute, reclaim | You, from the Solana settlement key's SOL. |
| The payer's exit | The payer. |
| Your own outbound channels | You: when your node pays a peer, it deposits directly and pays its own gas. |
Choosing a facilitator
receiverAuthorizer. That key can refund to the payer anything you have earned and not yet claimed. Your node always names its own settlement address, so a facilitator that advertises its own authorizer is simply not used as one.- Run a stock x402 facilitator yourself. TOON's Onboarder is one; the devnet runs it at
onboard.devnet.toonprotocol.dev. It is the published@x402/evmfacilitator with noreceiverAuthorizer. Its gas key needs ETH, and that ETH is the cost of sale. - Use a hosted one. Several hosted x402 facilitators serve Base mainnet. Check that the one you pick lets you keep your own receiver authorizer, and check its current terms with the provider.
- Name none. Then only a payer that brings its own facilitator, or holds ETH, can deposit. The client falls back, in order, to the payer's own facilitator, then yours, then the devnet Onboarder on Base Sepolia only.
Solana needs none of this. The payer posts its open to your sponsor endpoint, and your node is the fee payer.
Tokens
| Token | asset_transfer_method | Is the payer's first deposit gasless? |
|---|---|---|
ERC-3009 (receiveWithAuthorization): Circle's USDC, the devnet USDC | "eip3009" (default) | Yes, through any facilitator. One signature, no approval. |
EIP-2612 permit, no ERC-3009 | "permit2" | Yes, if the facilitator offers eip2612GasSponsoring. Otherwise the payer sends a one-time Permit2 approval from its own ETH. |
| Any other ERC-20 | "permit2" | Yes, if the facilitator offers erc20ApprovalGasSponsoring for this token. Otherwise the payer's own ETH. |
| Solana: an SPL Token mint | n/a | Yes. Your node sponsors the open. Token-2022 mints are refused. |
Checking it
curl -s https://your-node.example/ilp | jq '.batchSettlements, .voucherSigners'
Check that there is one batchSettlements entry per chain you configured, with your settlement address as payTo (EVM) or your key as feePayer (Solana), and the name, version, assetTransferMethod and facilitator you meant to publish. After a client has paid, read the journal with your bearer token:
curl -s -H "Authorization: Bearer $TOKEN" https://your-node.example/claims | jq
Each paying channel shows "scheme": "batch-settlement" and a cumulative_amount that rises with every packet. Watch that watermark, not the count of claims.
| Symptom | Likely cause |
|---|---|
| Every client's EVM deposit fails | Wrong asset_eip712_name/version, or "eip3009" on a token without ERC-3009. |
| Permit2 payers are told they need ETH | Your facilitator does not sponsor approvals for this token. |
| First voucher refused: "does not admit" | The client's ChannelConfig names a zero payerAuthorizer, a short withdrawDelay, another token, or another receiver. |
| "does not strictly exceed this channel's watermark" | The client lost its store: it should ask POST /ilp/claim-state. |
Solana open: receiving_account_unusable | Your own token account for the mint does not exist, or is frozen: create it. |
Solana open: sponsor_paused | Eight sponsored opens failed on chain in the last hour. |
| "could not look up the channel's counterparty" | Your settlement RPC is not answering; not the client's fault. |
Before you rely on it
- One way per channel. To pay a client back, open a channel of your own toward the terms it publishes (
POST /channels). - Solana costs SOL. Each sponsored channel locks about 0.0047 SOL of rent until reclaimed. Your node's token account for the mint must already exist.
- Third-party code. The EVM contract is ownerless and immutable; nothing can rescue an escrow that USDC's blacklist has frozen.
payment-channelscan be upgraded, on mainnet-beta by a 3-of-5 multisig with no time lock. What you risk as a receiver is whatever you have accepted in vouchers and not yet landed on chain. - An ILP outcome is never an HTTP one. A
FULFILLand aREJECTboth come back at HTTP 200. A caller with no voucher on a priced route gets402with the greeting.
Written from connector/docs/guide/get-paid.md and connector/docs/operators/x402-batch-settlement.md. The wire rules are client-edge-spec.md §1.3, §1.4, §1.10 and §1.11; the keys are configuration-spec.md.