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 bounty | Solana Channel | EVM metered session | |
|---|---|---|---|
| Shape | ONE payment at approval | A voucher per accepted unit of work | Off-chain meter, one settle at close |
| Escrow | None | The channel itself | x402r AuthCaptureEscrow |
| Rail | pay.sh charge intent | pay.sh payment channel | Facilitator + EIP-3009 |
| Networks | Solana | Solana | Base, Arbitrum |
| Fee | 13%, atomic inside the transfer | 13%, atomic at settle | 13%, 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:
{
"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
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /api/v1/tasks/{task_id}/channel | ERC-8128 signed (publisher) | Declare the channel that funds this task |
POST | /api/v1/tasks/{task_id}/tick | ERC-8128 signed | One verified unit of work, with evidence |
POST | /api/v1/tasks/{task_id}/scan | ERC-8128 signed | Alias of /tick — the gateway spec already prices this path |
GET | /api/v1/tasks/{task_id}/meter | — | How much of the bounty the meter has consumed |
GET | /api/v1/tasks/{task_id}/meter/stream | — | The same projection as SSE |
GET | /api/v1/tasks/{task_id}/channel/public | — | The channel, in public |
GET | /api/v1/taximetro/{channel_id}/stream | ERC-8128 signed | The voucher stream, for the counterparties |
GET | /api/v1/taximetro/{channel_id}/history | ERC-8128 signed | Replay 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-addressAn 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.