Build a 0Gate swap flow
Build a hosted 0Gate swap flow without implying a public headless swap API or exposing liquidity internals.
Use 0Gate for public swap journeys when the customer can complete the hosted 0Bit experience. The source repos show swap as a hosted iframe/kit-block flow; partner-level headless swap quote and execute APIs are not the public default.
Keep public swaps inside hosted 0Gate
Public swap flows should stay inside 0Gate unless your account is explicitly approved for another product surface. Use this guide for session locking, browser handoff, signed events, and reconciliation; use the API reference for approved request and response fields.
Flow boundary
| Boundary | Owned by | What it means |
|---|---|---|
| Swap intent | Your app | You know why the customer wants a swap and what account/action it belongs to. |
| Hosted swap flow | 0Gate | The iframe handles customer-facing swap UX and any product-approved quote confirmation. |
| Browser callback | Browser | UX only; show processing or return status. |
| Backend terminal event | Your backend | The only path that can close your internal swap intent. |
Implementation shape
- Create a swap intent before opening 0Gate.
- Lock the server-created session to
swap. - Mount the swap kit block or the hosted widget with a session-bound flow.
- Store the 0Gate session id, your swap intent id, and every webhook event id.
- Reconcile from trusted server state if the callback fires before webhook delivery.
async function startSwap(input: SwapIntent) {
const intent = await swapIntents.create({
accountId: input.accountId,
fromAsset: input.fromAsset,
toAsset: input.toAsset,
amountIntent: input.amountIntent,
status: 'pending_session',
});
const session = await gateSessions.createForAttempt({
attemptId: intent.id,
flow: 'swap',
userReference: intent.id,
idempotencyKey: `swap:${intent.id}`,
});
await swapIntents.attachGateSession(intent.id, session.id);
return { intentId: intent.id, clientSecret: session.clientSecret };
}Swap state model
| State | What changed | Your action |
|---|---|---|
| pending_session | Intent exists only in your app. | Allow safe retry of session creation. |
| requires_action | Hosted swap session is attached. | Open the hosted swap flow. |
| processing | Browser says the hosted flow completed. | Show processing and wait for backend truth. |
| fulfilled | Verified backend state confirms completion. | Close the swap intent exactly once. |
| failed | Hosted swap cannot complete. | Keep the customer-safe failure reason and allow a new attempt. |
| expired | Session lifetime ended. | Create a new intent/session if the customer retries. |
| cancelled | Customer or backend cancelled. | Preserve audit state and do not reuse the session. |
Quote expectations
Swap pricing is sensitive because it touches liquidity and execution correctness. Keep this public guide to user-flow behavior; exact quote surfaces, fields, and execution semantics belong in approved API reference pages.
| Scenario | Show before checkout | Confirm after checkout |
|---|---|---|
| Indicative preview available | Estimated output, fees, and a clear “may change” label. | The final trusted state from backend events. |
| No preview available | Asset pair, entered amount, and hosted-flow handoff. | The final trusted state from backend events. |
| Quote changed or expired | Ask the customer to restart from a fresh hosted flow. | Do not reuse stale displayed pricing. |
Webhook handling
async function applySwapEvent(event: GateSessionEvent) {
const intent = await swapIntents.findByGateSession(event.data.id);
if (!intent) return;
const firstSeen = await webhookEvents.insertOnce(event.id);
if (!firstSeen) return;
if (event.type === 'gate_session.completed') {
await swapIntents.fulfillOnce(intent.id, { sourceEventId: event.id });
}
if (['gate_session.failed', 'gate_session.expired', 'gate_session.cancelled'].includes(event.type)) {
await swapIntents.closeWithoutFulfillment(intent.id, event.type);
}
}Production checklist
- Keep public swap UX on hosted 0Gate unless a different product entitlement is approved.
- Keep provider, treasury, and routing internals out of public-facing swap UX.
- Treat preview pricing as indicative unless the API reference says it is locked.
- Make browser success a processing state only.
- Fulfill from signed backend state and dedupe duplicate events.
Related pages
Embed a single flow with kit blocks
Use the swap kit block for a hosted swap-only surface.
Preview quotes before checkout
Show pre-checkout pricing without promising final execution before checkout.
Product boundaries
Keep Gate, Pools, Base, and Link integration surfaces separate.
Handle failed or cancelled flows
Recover safely from expired, cancelled, failed, and interrupted sessions.