How 0Pools works
How 0Pools turns a partner liquidity request into an entitled quote, idempotent trade, status record, and reconciliation trail.
0Pools works like a headless checkout for liquidity. The partner backend prepares the order, 0Pools prices and locks terms, the partner accepts once, and both sides reconcile from a trade lifecycle.
Workflow overview
Step 1: entitlement
Entitlement is the first filter. If the partner is not active, verified, tiered, and allowed for a pool, execution never starts.
This is why 0Pools can be headless without being public. The API contract is server-side, but the account review decides whether that server is allowed to draw liquidity.
Step 2: availability
Availability is checked twice:
| Check | What it answers |
|---|---|
| Discovery availability | Is this pool generally usable by this partner right now? |
| Quote availability | Can this exact side, pair, network, amount, and market condition be quoted now? |
An unavailable quote is a valid response. It avoids issuing stale locks when the pool cannot safely execute.
Step 3: quote lock
The firm quote is the contract the user accepts.
{
"available": true,
"type": "firm",
"executable": true,
"quoteId": "pq_test_123",
"rate": "1.0731",
"spreadBps": 25,
"feeBps": 30,
"expiresAt": "2026-01-01T00:00:15Z"
}This is the piece that reduces exposed slippage for the partner UX: the user is accepting a bounded quote with an expiry, not an open-ended promise. After expiry, get a new quote.
Step 4: execution
transact consumes a firm quote and creates a trade. It is idempotent so duplicate user clicks or retrying a timeout do not create duplicate trades.
Step 5: status
The trade status is the durable business signal.
| Status | Product handling |
|---|---|
reserved | Pending. Do not final-credit the user. |
settled | Success. Finalize ledger. |
failed | Failure. Trigger product refund/support flow. |
released | Clean unwind. Let user retry. |
returned | Post-settlement return. Reconcile and alert support. |
Step 6: reconciliation
Every production record should join:
- Partner order id.
- Pool id.
- Quote id.
- Transact id.
- Request id.
- Webhook event id.
- Balance ledger ref.
- Status timestamps.
That join is the infrastructure value: support and finance can reason about a money movement without guessing which internal system caused it.
Full lifecycle contract
The complete 0Pools lifecycle has three clocks: the quote clock, the execution clock, and the settlement clock. Production code should keep them separate.
| Clock | Starts when | Ends when | Developer responsibility |
|---|---|---|---|
| Quote clock | A firm quote is created. | expiresAt is reached or the quote is accepted. | Disable accept after expiry and never silently change accepted terms. |
| Execution clock | The server calls transact. | A trade id is returned or an idempotent prior result is returned. | Use one idempotency key per accept attempt. |
| Settlement clock | Trade is reserved. | Status reaches settled, failed, released, or returned. | Update user and ledger state only from trusted status/webhook data. |
End-to-end trace example
{
"order_id": "order_7841",
"pool_id": "EUR-USDT",
"quote_id": "pq_live_01J4",
"quote_expires_at": "2026-06-28T20:14:30Z",
"idempotency_key": "order_7841:accept:1",
"transact_id": "pt_live_9A2",
"status_history": [
{ "status": "reserved", "at": "2026-06-28T20:13:11Z" },
{ "status": "settled", "at": "2026-06-28T20:15:03Z" }
],
"ledger_entry_id": "ledger_0pools_772"
}Retry behavior
| Situation | Correct behavior |
|---|---|
| Network timeout after transact request. | Retry with the same idempotency key, then render the returned trade. |
| User clicks accept twice. | Backend reuses or rejects based on the same order accept version. |
| Webhook arrives before the UI refreshes. | Backend updates the ledger; UI reads server state. |
| Status remains reserved longer than expected. | Keep pending state, alert operations, and do not credit as settled. |
Implementation depth
This page is about following the request from entitlement to reconciliation. For developers, the durable boundary is simple: your product should depend on the lifecycle a partner can depend on in product code; it should not depend on the internal liquidity decision that fulfills the lifecycle. That boundary is what lets 0Pools improve liquidity operations without forcing every partner to rebuild product code.
Product scenario
A partner receives a user request, checks whether the requested lane is entitled, previews availability, locks terms, executes once, and waits for terminal status before posting final balance changes.
Before and after in practice
| Before 0Pools | With 0Pools |
|---|---|
| The app asks several liquidity providers for prices and tries to normalize incompatible statuses after the fact. | The app talks to one 0Pools lifecycle and records one set of ids, statuses, and amounts. |
| Product teams infer liquidity behavior from provider-specific screens or manual notes. | Product teams branch on 0Pools objects: pool, quote, trade, balance, webhook, and ledger. |
| Support asks engineering to reconstruct state from logs. | Support starts from request id, quote id, trade id, event id, and ledger id. |
System flow
Records to keep
| Record | Why it matters | Example |
|---|---|---|
entitlement_snapshot | Explains why the lane was shown. | active EUR-USDT |
quote_terms | The accepted amount, rate, fees, and expiry. | 100.00 EUR |
execution_ack | The reserved trade response. | pt_test_456 |
terminal_event | The final state used by the ledger. | settled |
A useful implementation stores these records before adding optional analytics. Analytics can be rebuilt from durable state; missing ids usually cannot be reconstructed after a customer-impacting failure.
Example product record
{
"product_area": "how-0pools-works",
"partner_reference": "partner_order_123",
"pool_id": "EUR-USDT",
"quote_id": "pq_test_123",
"transact_id": "pt_test_456",
"status": "reserved",
"request_id": "req_01HZY0POOLS",
"reconciliation_state": "open"
}This record is intentionally partner-facing. It references 0Pools objects and your own product ids, but it does not include private liquidity routes, treasury balances, raw provider payloads, or customer secrets.
Failure modes and recovery
| Failure mode | Product response |
|---|---|
| Access denied | Stop the flow and refresh account capabilities. |
| Available false | Show a clear unavailable state without promising delivery. |
| Quote consumed | Retrieve existing trade or create a new quote. |
| Webhook missing | Poll by quote id and reconcile from the API. |
Use-case patterns
| Pattern | How 0Pools helps |
|---|---|
| First backend integration | Implement the lifecycle in order. |
| Product state machine | Map API states to user screens. |
| Runbook creation | Define what support checks at each step. |
Developer checklist
- Read runtime discovery and capabilities before rendering enabled actions.
- Create firm quotes only when the user is ready to accept the terms.
- Execute from your server with an idempotency key.
- Treat webhooks and status reads as the source of truth for settlement state.
- Reconcile by durable ids first, then by amount and timestamp.
- Do not branch product behavior on provider names, route names, reserve balances, or treasury assumptions.