Fallback liquidity
How 0Pools handles unavailable pool liquidity and how partners should build fallback UX.
Fallback liquidity is what happens when the preferred pool path cannot safely fill a request. Partners should not depend on the internal fallback mechanism. They should build against public outcomes: unavailable quote, reservation conflict, pending trade, terminal status, and support ids.
Outcome model
Partner fallback choices
| Outcome | Good product behavior |
|---|---|
pool_dry | Offer a later retry or another entitled pool. |
engine_unavailable | Show temporary unavailable state. |
rate_unavailable | Ask user to refresh later. |
| Reservation failed | Request a fresh quote; preserve support ids. |
| Trade failed | Follow refund/support policy. |
| Released | Let user retry from quote step. |
What not to do
- Do not invent a different route in the browser.
- Do not submit the same expired quote repeatedly.
- Do not expose internal fallback provider names.
- Do not credit users from an unavailable quote.
- Do not turn unavailable responses into tight retry loops.
Before and after
| Before 0Pools | With 0Pools |
|---|---|
| Product team writes custom fallback route code. | Product handles stable unavailable/status outcomes. |
| Internal provider outage leaks to user. | User sees product-safe route unavailable state. |
| Retry can duplicate execution. | Idempotent transact and quote freshness control retries. |
Support packet
For fallback issues, provide pool id, quote id if any, request id, unavailable reason or error code, environment, amount, side, network, and timestamp.
Fallback UX matrix
| User moment | Product should do | Product should not do |
|---|---|---|
| User has not accepted a quote yet. | Ask for a fresh quote, reduce amount, or show another entitled pool. | Hide the error and keep showing stale terms. |
| Firm quote expired. | Refresh and require the user to accept new terms. | Execute the expired quote id. |
| Quote unavailable. | Show a temporary unavailable state with retry. | Tell the user which private liquidity source failed. |
| Trade reserved for longer than expected. | Keep funds pending and escalate with ids. | Credit final balance early. |
| Trade failed/released. | Follow refund/release policy and let user restart from quote. | Retry execution blindly with the same expired quote. |
Fallback decision tree
The tree keeps fallback decisions at the product-contract layer. It does not ask the partner to choose an internal provider, inspect reserve source, or run a treasury playbook.
Before 0Pools in fallback design
Without a shared pool abstraction, fallback often becomes a product engineering problem: code tries provider A, then provider B, then a manual desk, each with different status names and reconciliation exports. That creates duplicated executions, hidden stale prices, and support tickets that only engineering can resolve.
With 0Pools, fallback is represented as stable outcomes:
- no quote
- quote expired
- reservation failed
- trade pending
- trade settled
- trade failed
- trade released or returned
Those outcomes are enough for a partner product to build a reliable UX while 0Bit operations handle the private liquidity layer.
Logging example
{
"event": "pool_fallback_outcome",
"pool_id": "EUR-USDT",
"side": "on_ramp",
"amount": "500.00",
"reason": "pool_dry",
"request_id": "req_abc",
"user_message": "This route is unavailable right now."
}Log unavailable reasons for analytics and support, but keep user copy generic and avoid private operating detail.
Fallback decision matrix
Fallback should protect the user from stale prices and unclear settlement states.
| Failure point | Safe fallback | Unsafe fallback |
|---|---|---|
| Discovery unavailable | Hide action or show maintenance state. | Show cached pool as available. |
| Indicative quote unavailable | Let user retry later or change amount/network. | Invent a price from last quote. |
| Firm quote expired | Request fresh quote and confirmation. | Execute old quote. |
| Transact timeout | Retry with same idempotency key. | Create a new execution attempt blindly. |
| Trade stuck reserved | Keep pending, poll status, alert support. | Mark failed without terminal evidence. |
Fallback log record
{
"partner_order_id": "order_7841",
"pool_id": "EUR-USDT",
"failure_point": "firm_quote_expired",
"user_action": "requested_new_quote",
"previous_quote_id": "pq_live_01J4",
"new_quote_required": true
}Retry policy
Use short retries for transient reads, idempotent retries for transact uncertainty, and no automatic retry for expired quote acceptance. Anything that changes accepted economics requires user confirmation again.
Implementation depth
This page is about building clear fallback UX when liquidity is unavailable or delayed. For developers, the durable boundary is simple: your product should depend on unavailable reasons, retry policy, alternate enabled pools, and existing trade status; it should not depend on which liquidity source failed or recovered. That boundary is what lets 0Pools improve liquidity operations without forcing every partner to rebuild product code.
Product scenario
A pool returns available:false. The app offers a smaller amount, a different entitled lane, or a retry later instead of repeatedly forcing the same quote.
Before and after in practice
| Before 0Pools | With 0Pools |
|---|---|
| The product masks failures as generic errors and users retry until they create support noise. | The product treats unavailable liquidity as a normal state with clear options and no hidden execution attempts. |
| 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 |
|---|---|---|
unavailable_reason | Machine branch for the fallback. | rate_unavailable |
attempt_count | Prevents retry loops. | 2 |
alternate_pool_id | Another entitled lane if available. | EUR-USDC |
user_message | Copy shown to the user. | This lane is temporarily unavailable. |
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": "fallback-liquidity-review",
"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 |
|---|---|
| Retry loop | Cap attempts and back off. |
| Silent route switch | Ask user to confirm different terms or asset. |
| Existing reserved trade lost | Continue status tracking for accepted quotes. |
| Private outage exposed | Use product-safe unavailable language. |
Use-case patterns
| Pattern | How 0Pools helps |
|---|---|
| Amount reduction | Let user try smaller amount if limits allow. |
| Alternate stablecoin | Offer another entitled asset. |
| Delayed retry | Notify when the lane becomes available. |
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.