Streaming & Usage Billing
Bill a stream in segments and settle its purchases periodically.
How streaming payments work#
The application sells service credit in increments and consumes it in units such as tokens, bytes or time slices. The buyer signs increasing cumulative purchase amounts. The merchant periodically claims its highest accepted voucher.
The contract needs transactions to settle; it does not send money by itself every second. Your application measures usage and executes the service. A payment does not prove that a model response or other provider work completed.
Choose a billing unit#
Define your unit, digest codec and price in the server's billing rules. Give each segment or batch a stable request ID. Its request signature authorizes that resource separately from the purchase voucher.
If the unit price is smaller than one atomic token unit, carry the remainder and round at purchase boundaries. Rounding each unit upward overcharges the stream. Signed payment amounts must remain unsigned integers in atomic units.
| Record | What it tells you |
|---|---|
| Provider usage receipt | The provider's measured usage claim |
| Signed SessionRequest and debit | One resource unit was authorized and debited |
| Purchase Voucher | Cumulative earnings authorized for the merchant |
| Settled Credit | Value settled and backed by the payment rail |
| Pool settlement receipt | Returned assets reserved for a private note |
Pause and reconnect#
Pause new billable segments when credit runs out, policy expires or closing starts. Ask for another purchase authorization. If capacity is too small, wait for a separately approved top-up to confirm first.
On SSE reconnect, reuse the accepted segment's billing identity. Resume its job from cached output or a saved cursor so a connection failure does not create another charge.
Accept a segment#
Wrap the buyer and merchant managers in your transport. The example accounts for one segment. Your application still computes the digest, checks server pricing and keeps a job ledger to prevent repeated execution.
const requestId = `${streamId}:segment:${segmentIndex}`;
const bundle = await buyer.prepare({
requestId, requestDigest: segmentDigest, amount: segmentPrice,
});
const debit = await merchant.receive(bundle, {
requestId,
requestDigest: independentlyComputedSegmentDigest,
amount: independentlyComputedSegmentPrice,
});
// Commit or resume the durable segment job, then deliver cached/new bytes.
// A debit receipt is not proof that delivery succeeded.Monitor settlement#
Leave time to claim before the close window ends, accounting for stale RPC reads, confirmation lag and worker outages. All transports serving one merchant session must share its accounting ledger, including request deduplication records.
The SDK has no HTTP/SSE gateway or Tempo/MPP adapter. If you need those protocols, implement and test their challenges, credentials and receipts against the payment backend. Changing a contract address alone will not make them compatible.
Save the stream state#
Before the first segment, configure a funded session and saved manager policy, then create a durable stream job. Its ID and segment indices or batch boundaries must survive reconnects. Store the digest and price for each boundary.
The buyer authorizes a segment and the merchant checks its content and price before delivery. After the debit commits, save a resumable job state. If delivery then fails, resume that segment rather than assigning it another billing ID.
| Phase | Payment state | Application state |
|---|---|---|
| Ready | A funded open session with saved policy | The stream job is saved |
| Prepared | Buyer budget reserved and authorization saved | Content and price set for the segment |
| Accepted | Merchant debit committed once | The segment job can run or resume |
| Delivered | No additional debit | Output and delivery cursor saved |
| Paused | Credit ran out, policy expired or closing began | New billable segments stop |
| Settled | The highest accepted voucher was claimed | The service receipt is kept separately |
Carry fractional charges#
Meter sub-atomic prices in an agreed finer scale, keeping the remainder until a purchase boundary produces an integer payable amount. Include that scale in the service terms and the request data the server checks.
The read-only function below shows that arithmetic. The SDK requires positive SessionRequest amounts, so collect a large enough batch before authorizing it; a zero-priced result from the function cannot be sent as a request.
function meterBatch({ units, priceNumerator, priceDenominator, remainder = 0n }) {
if (units < 0n || priceNumerator < 0n || priceDenominator <= 0n) {
throw new Error("Invalid agreed billing scale");
}
const scaled = units * priceNumerator + remainder;
return {
amountAtomic: scaled / priceDenominator,
remainder: scaled % priceDenominator,
};
}
// 3 units at 1/10 atomic unit each retain 3/10 as a remainder.
const first = meterBatch({ units: 3n, priceNumerator: 1n, priceDenominator: 10n });
// A later 7-unit batch reaches exactly one atomic unit.
const next = meterBatch({
units: 7n, priceNumerator: 1n, priceDenominator: 10n,
remainder: first.remainder,
});
console.log(next.amountAtomic.toString(), next.remainder.toString());Record usage and purchase terms#
A provider receipt can include the session, service-terms digest, cumulative units, price and issuer. Its signature confirms the provider's billing claim. The payer still needs to authorize the purchase, and the receipt cannot prove the output was correct.
Apply the customer's configured budget and refund terms before each increment. Track unused service credit separately from unclaimed funding. Once a Voucher settles, it is merchant revenue even if the customer has not consumed all the service credit it bought.
Coordinate workers and shutdown#
Use the same merchant store to serialize acceptance across stream and API workers. Otherwise, two workers can both see the same credit as available. The chain tracks cumulative Voucher amounts, not which resource request has already used that credit.
At shutdown, stop new segments and save active cursors before stopping the settlement worker. Arrange for a supervised process or restart to continue watching the close window. A timer in a stopped process cannot submit a claim, and passing onchain time does not run a keeper.
Receive settled value privately#
Settled Credit is an ERC-20 backed by assets in the payment rail. Deposit it through the Pool's existing guarded admission flow to obtain a Credit note. An admitted redemption adapter can return underlying assets to the Pool for a measured settlement note.
Create the Deferred Note only from assets actually returned by that route. Usage receipts and future earnings cannot fund it. Shared-Pool route admission and HTTP integration still need their own deployment and funded end-to-end tests; contract tests cover only the contract part.