Payment channels make agentic payments practical by escrowing a spending ceiling on-chain and settling actual usage from signed vouchers; this program backs MPP
sessionfor streaming or repeated payments and x402uptofor one metered request.
Status — live on mainnet: CHNLxYvVA28MJP9PrFuDXccuoGXAx7jBacfLEkahyGsX
One on-chain open and one settle replace a transaction per payment. The payer escrows a ceiling; the merchant claims only what off-chain vouchers authorize; the payer recovers the rest. That makes metered, streamed, or many-small payments viable where settling every request on-chain is too slow and too expensive.
flowchart LR
A["open<br/>(create PDA + escrow deposit)"] --> B["off-chain vouchers<br/>(cumulative spend)"]
B --> C["Ed25519 precompile + settle<br/>(advance settled watermark)"]
C --> D["settle_and_seal<br/>(lock final watermark)"]
D --> E["distribute<br/>(payouts + refund + close escrow)"]
E --> F["reclaim, when needed<br/>(recover channel-PDA rent)"]
stateDiagram-v2
[*] --> Open: open
Open --> Open: settle / top_up / distribute (partial)
Open --> Sealed: settle_and_seal (cooperative)
Open --> Closing: request_close (forced)
Closing --> Sealed: settle_and_seal (mid-grace) / seal (after grace)
Sealed --> Distributed: distribute (pay out, close escrow)
Distributed --> [*]: reclaim (after epoch window)
Sealed --> [*]: distribute (fast path, window already elapsed)
Vouchers are signed off-chain (Ed25519) and carry a cumulative amount, so a newer voucher supersedes older ones and the program never settles more than the deposit. distribute pays the settled amount; once the channel is sealed, distribute or withdraw_payer can refund the unspent remainder immediately—no token movement waits on the epoch window. The channel account itself is then fully deallocated (directly, or by a later reclaim once the window elapses), returning 100% of its rent: a closed channel leaves nothing on chain.
- The Pinocchio program stores each channel in a 256-byte PDA derived from
b"channel",payer,payee,mint,authorized_signer,salt, andopen_slot. Its escrow is the canonical ATA owned by that PDA. open_slotmakes every channel incarnation land at a new address.openaccepts only a current-or-recent slot (OPEN_SLOT_WINDOW = 1,500), and the program keeps the PDA allocated until that window has elapsed. An old voucher therefore cannot target a later incarnation.- Channel fields—not raw token or lamport balances—are authoritative. Third-party token prefunds are uncredited and swept to treasury by the terminal
distribute; surplus PDA lamports return torent_payerwhen the account is deallocated. openstores only the SHA-256 commitment to the canonical distribution preimage. Any permissionlessdistributecaller supplies the preimage and recipient ATAs.rent_payercan differ from the token payer, allowing an operator to fund SOL rent for a stablecoin-only client and recover that SOL at close.
The Ed25519-signed message is exactly 50 bytes:
| Offset | Size | Field | Encoding |
|---|---|---|---|
0..2 |
2 | magic |
[0x56, 0x01] ('V', format version 1) |
2..34 |
32 | channel_id |
Channel PDA bytes |
34..42 |
8 | cumulative_amount |
u64, little-endian |
42..50 |
8 | expires_at |
Unix timestamp as i64, little-endian; 0 disables expiry |
settle carries no voucher copy in its own instruction data. The transaction places a canonical single-signature Ed25519 precompile instruction immediately before settle (or before voucher-bearing settle_and_seal); the program reads the verified 50-byte message through the Instructions sysvar. See the state-machine voucher contract for replay, expiry, and signer checks.
This program is the on-chain settlement layer behind two pay.sh payment primitives. Both deposit a ceiling here, meter off-chain, and settle the actual amount on this program:
- x402
upto— a single metered call: the operator settles one voucher for the actual amount and refunds the rest. - MPP
session— a streamed channel: many cumulative vouchers, settled once when the session idle-closes.
See Payment channels on pay.sh for the protocol handshakes and when to pick each.
| Instruction | Role |
|---|---|
open |
Create the channel PDA and escrow the deposit. |
settle |
Advance the on-chain settled amount from the preceding Ed25519-verified voucher. |
settle_and_seal |
Settle a final voucher and seal in one step (cooperative). |
top_up |
Add funds to an open channel. |
request_close |
Payer-initiated forced close — starts the grace period. |
seal |
Seal a forced-closing channel once the grace period elapses. |
distribute |
Pay cumulative recipient/payee shares while open; when sealed, refund the payer, sweep residuals, and close the escrow. |
withdraw_payer |
Payer recovers the unspent remainder. |
reclaim |
Deallocate a distributed channel and recover its rent (batchable). |
just setup
just build-program
just generate-client
just test-programCluster builds (just build-mainnet-beta, just build-devnet, …) require that cluster's real TREASURY_OWNER in program/payment_channels/src/constants.rs and refuse to compile with the placeholder. No production keypair is committed — pass the program-id keypair explicitly when deploying.
- State machine
- HTTP protocol
- Instruction reference
- Generated clients: TypeScript, Rust.
MIT. See LICENSE.
A proposed settleBatch instruction would let one Ed25519 voucher authorize cumulative targets for many channels that share an authorized_signer.
The signed message would commit to the ordered channel-account list and carry one amount per channel, avoiding repeated signatures and channel addresses.
A version-0 transaction with an address lookup table could then settle roughly 59 channels instead of about five, while preserving per-channel caps and replay checks.
This is not implemented and requires a new voucher wire format; see ADR-004 for the adversarial analysis and trade-offs.
The program was audited by Cantina. Read the July 2026 security audit.