Skip to content
LogoLogo

Channel

High-frequency off-chain payments

The channel intent enables high-frequency, pay-as-you-go payments over unidirectional payment channels on Stellar. Clients deposit tokens into an on-chain contract reserve and sign off-chain cumulative commitments as they consume resources. The server verifies commitments via contract simulation–no per-request on-chain transactions–and closes the channel to settle the final balance.

Payment channels reduce payment verification to a single contract simulation per request, making it possible to meter and bill at the granularity of individual LLM tokens, API calls, or bytes transferred.

How it works

A payment channel has four phases:

Open

The client deploys a one-way-channel smart contract with a commitment key and an asset deposit. This creates a payment channel between the client (funder) and server (recipient). The channel contract holds the deposited tokens on-chain.

Session

The client signs ed25519 cumulative commitment amounts as service is consumed. Each commitment authorizes "I have now consumed up to X total." The server verifies the commitment signature by simulating prepare_commitment on the channel contract and checks that the cumulative amount is higher than the previous commitment.

Commitment verification requires a single contract simulation per request. This is what enables per-token LLM billing without significant latency overhead.

Top up

If the channel runs low on funds, the client deposits additional tokens via the top_up function without closing the channel. The session continues uninterrupted.

Close

Either party can close the channel. The server calls close() on the channel contract with the highest commitment amount and signature, settling the final balance on-chain and refunding any unused deposit to the funder.

Integration

Closing the channel

The server closes the channel by submitting the highest cumulative commitment amount and signature on-chain. The close function is exported from the server package:

import { close } from '@stellar/mpp/channel/server'
import { Keypair } from '@stellar/stellar-sdk'
 
const txHash = await close({
  channel: 'CABC...', // channel contract address
  amount: 2000000n, // cumulative amount in base units (bigint)
  signature: lastCommitmentSignature, // Uint8Array
  feePayer: { envelopeSigner: Keypair.fromSecret('S...') },
  network: 'stellar:testnet',
})

Monitoring channels

Use getChannelState to query the on-chain state of a channel, and watchChannel to poll for contract events:

import { getChannelState, watchChannel } from '@stellar/mpp/channel/server'
 
// Query current state
const state = await getChannelState({
  channel: 'CABC...',
  rpcUrl: 'https://soroban-testnet.stellar.org',
})
 
// Watch for events (close, refund, top_up)
const stop = watchChannel({
  channel: 'CABC...',
  rpcUrl: 'https://soroban-testnet.stellar.org',
  onEvent(event) {
    console.log(event.type, event.data)
  },
})
 
// Stop watching
stop()

Channel contract

Payment channels use the one-way-channel smart contract for on-chain deposits, commitment verification, and settlement.

The contract lifecycle: Open (deploy + deposit) -> Off-chain payments (signed commitments) -> Settle (partial withdrawal) -> Close (final settlement) or Close Start -> Refund (funder reclaims after waiting period).

Commitment signatures use ed25519 over XDR-encoded ScVal::Map containing the amount, channel address, domain separator (chancmmt), and network ID–preventing replay across channels and networks.