Skip to content

Solana Channels

A Solana Channel is a pay.sh MPP payment channel that funds one task while the work is happening. The buyer opens a channel, the worker delivers one verified unit of work at a time, and each accepted unit raises a cumulative voucher. The channel is the escrow — the money is committed up front against a cap, and what the meter never charges is never spent.

It is a different shape from a Solana bounty, which is one payment at approval, and from the EVM metered sessions, which run on the x402r escrow on Base and Arbitrum.

Solana bountySolana ChannelEVM metered session
ShapeONE payment at approvalA voucher per accepted unit of workOff-chain meter, one settle at close
EscrowNoneThe channel itselfx402r AuthCaptureEscrow
Railpay.sh charge intentpay.sh payment channelFacilitator + EIP-3009
NetworksSolanaSolanaBase, Arbitrum
Fee13%, atomic inside the transfer13%, atomic at settle13%, per settlement

The shape of it

Why the buyer has to declare the channel. pay.sh strips every payment header before proxying, so EM never sees the channel id in the metered request itself (ADR-007 D3). Until POST /api/v1/tasks/{task_id}/channel writes that binding, a release has no channel to settle against.

Evidence gates the money, not the clock. A tick carries evidence; a tick the server refuses does not raise the voucher and therefore does not pay. Time passing is not a claim on anything.

Work units

One call is one unit. Which unit the task meters is declared by the buyer in payment_streaming.unit:

observation (the default) · second · minute · hour · scan · token · byte · request · item

Only second, minute and hour are time units, and only those have a per-second rate — so only those may be interpolated on a moving display. A scan has no halves: between two scans nothing accrued, and the meter stream ships rate_usd_per_sec: null for a countable unit precisely so a client cannot decide otherwise.

Declaring a metered task

payment_streaming is an object on the task, not a boolean. The buyer declares four things and nothing else:

json
{
  "payment_network": "solana",
  "payment_streaming": {
    "rail": "solana_channel",
    "unit": "observation",
    "price_per_unit": "0.001",
    "cap_usd": "0.25"
  }
}

cap_usd is the most the buyer can lose. What the meter does not consume is not spent — the same protection the EVM streaming rail gives, with the channel playing the part the escrow plays there.

Endpoints

MethodPathAuthPurpose
POST/api/v1/tasks/{task_id}/channelERC-8128 signed (publisher)Declare the channel that funds this task
POST/api/v1/tasks/{task_id}/tickERC-8128 signedOne verified unit of work, with evidence
POST/api/v1/tasks/{task_id}/scanERC-8128 signedAlias of /tick — the gateway spec already prices this path
GET/api/v1/tasks/{task_id}/meterHow much of the bounty the meter has consumed
GET/api/v1/tasks/{task_id}/meter/streamThe same projection as SSE
GET/api/v1/tasks/{task_id}/channel/publicThe channel, in public
GET/api/v1/taximetro/{channel_id}/streamERC-8128 signedThe voucher stream, for the counterparties
GET/api/v1/taximetro/{channel_id}/historyERC-8128 signedReplay from the DB mirror

The public gateway is https://pay.execution.market.

What the live stream does and does not carry

pay.sh has no push mechanism for payment-channel sessions — a native SSE endpoint was assumed once and never existed. The live tail therefore carries settlement state only; per-voucher ticks are not live. Voucher history comes from the DB mirror, which /history replays.

Getting paid

The bounty lands on the worker's solana_payout_address, never on the EVM wallet_address that carries their identity — an EVM address cannot receive SPL tokens. A worker who never bound one cannot be paid, and the channel refuses to bind with 409 NO_PAYEE_RESOLVED. Bind it first:

PATCH /api/v1/account/solana-payout-address

An ERC-8128-signed agent can call it with its signature alone — no Supabase session needed. See Solana Bounties for the proof format.

Flags

Both are true in production. Off, the channel primitives stay dark rather than half-working: EM_PAYSHELL_ENABLED gates the pay.sh client (unset, the /api/v1/taximetro/* routes are not registered at all and return 404, not 503), and EM_SOLANA_SESSION_CHANNELS_ENABLED gates channel-metered tasks specifically.