Skip to main content
Every release test moves real USDC twice. You pay Selo, then Selo pays your endpoint. Selo never starts the second payment until the first has settled, and it never spends more than the job’s budget, even when requests race or outcomes are unclear.

The two legs

Both legs are USDC on Algorand Mainnet (ASA 31566704), and both transaction ids are returned in the report: money.seloInboundTxId and money.downstreamTxIds. Anyone can look them up on an explorer at https://allo.info/tx/<txId>.

Incoming before outgoing

Selo’s paid route does not rely on middleware ordering. It composes each step explicitly:
1

Validate before any payment

Selo validates the request body and the Idempotency-Key header, then checks that the preflight exists, is eligible and unexpired, that its authorization is verified, and that the target’s price is within the job budget. Any failure here answers with an error and nothing is charged.
2

Verify the payment

The facilitator verifies your x402 payment. A request without a payment gets Selo’s 402 challenge.
3

Claim the job

Selo inserts the job row under an idempotency key before it settles anything, so a duplicate request can never settle twice.
4

Settle and record

Selo settles your payment through the facilitator and records the settlement transaction id, payer, amount and time on the job. The inbound transaction id is unique: one settlement can back only one job.
5

Run

The runner starts only a job whose status is INBOUND_SETTLED and whose settlement time is recorded. It refuses any other job and contacts no target.
The acceptance test A12: inbound settles before the first downstream request checks the recorded timestamps and the call order against a real database.

Inbound outcomes

The idempotent claim

The job’s idempotency key is derived from your request:
  • With an Idempotency-Key header (8 to 128 characters of A-Z, a-z, 0-9, _ or -), the key is a SHA-256 hash of the preflight id and your header value. The same header with two different payments settles once.
  • Without the header, the key is a SHA-256 hash of the canonical JSON of your payment payload, so only a resend of the same payment payload counts as a duplicate.
A second request with the same key does not create a job or settle again. It gets the existing job’s answer: The selo CLI sends a stable Idempotency-Key for each run, which is what makes recovering an interrupted run safe.

The downstream leg

Selo signs at most one downstream payment per job. The replay in retry_safety reuses that same signed payment; it is not a second payment.
  1. Read the price. handshake selects your exact requirement for Algorand Mainnet USDC.
  2. Check the wallet. Selo confirms its operating wallet can cover the price. If not, nothing is reserved or signed.
  3. Reserve. The spend guard and one conditional database update reserve the price. See the budget invariant.
  4. Sign exactly what was reserved. The x402 client signs only an entry whose network, asset, amount and payTo match the reservation, with its spend cap set to that amount. Selo records the id of the transfer it signed and the transfer’s last valid round.
  5. Send once and classify. The outcome moves the payment to SETTLED, RELEASED or UNRESOLVED, as described below.

Spend limits

The budget invariant

Before every downstream signature, this must hold:
Invariant
settled, reserved and unresolved are counters on the job row, in micro-USDC. Selo enforces the invariant in one database transaction:
  1. It evaluates the spend policy on the current job and authorization. The first failing rule becomes the denial reason.
  2. It claims capacity with one conditional UPDATE. There is no read-then-check-then-write in application code:
    reserve.ts (shape of the generated SQL)
  3. Zero rows returned means the reservation is denied. Selo re-reads the job to name the reason: JOB_NOT_RUNNING, PAYMENT_UNRESOLVED or JOB_BUDGET_EXCEEDED.
  4. In the same transaction, it inserts the downstream_payments row with status RESERVED.
Database CHECK constraints are the backstop, not the mechanism: settled + reserved + unresolved <= max_spend, max_spend <= 5000000, every counter non-negative, and every payment amount positive. A settlement transaction id is unique across all downstream payments.
The acceptance test A16 fires truly parallel reservations at a real PostgreSQL database: three parallel 0.05 USDC reservations against a 0.10 USDC job reserve exactly two, and thirty parallel mixed-size reservations against 0.50 USDC never overspend.

Spend denial reasons

The spend guard evaluates its rules in this order and reports the first that fails. A denied payment is never signed; paid_delivery reports SPEND_GUARD_DENIED and the run records the inconclusive reason shown.

Payment states

A downstream payment has four states. Each transition is a conditional UPDATE ... WHERE status = <from> on the payment row plus the matching counter move on the job, in one transaction. A transition that finds the payment already moved changes nothing.

What moves a payment

Why ambiguity stops spending

Once a signed payment has left Selo, a missing answer does not mean the money stayed put. A timeout can hide a settlement that lands a second later, and a signed transfer can be submitted by whoever holds it until its last valid round passes. If Selo released the capacity on a timeout and paid again, it could pay twice. So Selo treats every unclear outcome the same way:
  • The amount moves to unresolved and keeps counting against the budget.
  • The job stops spending: the spend guard refuses any further reservation while unresolved is above zero, and retry_safety does not replay.
  • The run records PAYMENT_UNRESOLVED, and the verdict is INCONCLUSIVE unless a check already proved a FAIL.
  • Only a definite failure before the payment left Selo releases capacity. A timeout never does.
Unresolved payments are later settled or released only on chain proof. See Held payments and reconciliation.

Money is integer micro-USDC

Inside Selo, every amount is an integer number of micro-USDC (USDC has 6 decimals). Floating point is never used for money. Decimal strings exist only on the wire and in output, and x402 amount fields use atomic units. Selo parses a decimal only when it has no sign or exponent and at most six fractional digits. It formats with at least two decimals and drops trailing zeros beyond that, so 1,234,500 micro-USDC prints as "1.2345".

Operation ids and Idempotency-Key

When a job starts, Selo plans one scenario per check. Each scenario gets an immutable operation id, <jobId>:<checkId>, unique within the job and enforced by a database unique index. A retry of the same logical operation reuses its id. When your challenge declares the x402 payment-identifier extension, Selo also sends a payment id derived from the operation id: selo_ followed by the operation id with every character outside A-Z, a-z, 0-9, _ and - replaced by _, padded or cut to the extension’s length limits. The report lists each scenario’s operation id and attempt count.

Job states

Every job transition is a conditional update from the expected state, so two processes can never move the same job twice. A job whose inbound settlement is unconfirmed stops at INCONCLUSIVE and never gets a report.

Scenario states

Held payments and reconciliation

How an UNRESOLVED payment is settled or released on chain proof.

Pricing and limits

Every price, cap, timeout and size limit in one place.

Recover an interrupted run

Resume a run whose inbound payment outcome is unconfirmed, without paying twice.

Pay with any x402 client

Pay Selo’s challenge without the CLI.