Skip to main content
This guide walks the SELL ZEC to fiat happy path end to end. Read Overview first and API Reference for the full surface.

Prerequisites

  • Node >= 20; pure ESM, strictly typed TypeScript.
  • SDK v0.0.2 (@0xramp/sdk). Install from repo (see Quickstart).
  • Issued by 0xramp: partnerId, enabled corridor, API origin, pane origins.
  • Your app provides: ZEC wallet core (signs transparent-address sends), secure storage scoped to one unlocked wallet, a WebView with navigation control (desktop) or your platform’s auth-session opener (openAuthSessionAsync, mobile browser-hosted mode), and a return-link scheme you register and route yourself.
  • Attribution: render “Powered by 0xramp . P2P.me” at the ramp entry point.

Terminology

Environment configuration

Two environments: "production" and "staging".
Rules: apiBaseUrl/paneOrigins must be https origins (no credentials, path, query, fragment). All API requests use redirect: "error" and no automatic retries.

Durable send journal

Any bridge with a signing callback requires a sendStore. Construct one per unlocked wallet namespace:
The journal records an unresolved claim before invoking your wallet, persists the outcome before replying to the pane, and on restart replays known outcomes without re-invoking the wallet. Reuse one adapter object per wallet. createMemoryZecSendStore() is for tests only.

Happy path (SELL ZEC to fiat)

Step 1 — Create the session

Persist the creation intent before the POST, then create:
Response: { sessionUrl, sessionRef, statusTicket, expiresAt }. Persist the full session securely. Failures: ConfigError, ApiError (.status has HTTP code), PartnerQuotaExceeded (429), NetworkUnavailable (timeout — POST is unresolved), OriginLockViolation. The returnUrl scheme must be letter-first per RFC 3986. createSession rejects digit-first schemes and values without a scheme prefix with ConfigError before any POST; https: URLs and schemes without // are accepted. See Scheme rules. Lost create response: keep the persisted intent and reconcile with 0xramp before another create. partnerSessionId is correlation data, not a recovery credential.

Step 2 — Attach the bridge

Attach before loading the WebView, bound to this session:
Pass sessionRef explicitly whenever more than one session exists. Close the old bridge before switching sessions. psp/close closes the bridge before calling onClose.

Step 3 — Implement transport and load pane

The transport interface:
React Native: webViewRef.current.postMessage(JSON.stringify(message)) for host-to-pane; onMessage with ramp.isAllowedPaneUrl(event.nativeEvent.url) validation for pane-to-host. Electron: IPC in main process; verify event.sender, sender frame, and frame URL. Load the pane:
Enforce the same policy on every navigation. Display the pane origin, never the full ticket-bearing URL.

Step 4 — Handle the send request

The pane sends psp/zec-send-request with { requestId, address, amountZat, memo? }. Your handler returns: Preserve all transaction IDs as an array. Never join them into one string. Identify the deposit from wallet history, or stay pending.

Step 5 — Resolve uncertain sends

Pending means reconcile, do not send again. Once wallet history proves the deposit:
Only after proving no broadcast occurred:

Step 6 — Read authoritative status

Lifecycle: created -> opened -> user-active -> { settled | failed | expired | cancelled }. Refresh on advisory psp/result, app foreground, return link, and user request. expired is reversible. fiat.amountDisplay is a receipt string — never do math on it.

Step 7 — Restore on restart

Then attach a fresh bridge before loading the pane again. Validate scheme/host yourself, match sessionRef against your saved session, then:
The parsed outcome never proves payment. Reconcile via getStatus and your chain view.

Mobile: browser-hosted pane

On mobile, open the pane in the system browser (an OS auth-session) instead of a WebView. This is a supported hosting mode, not a fallback rung — passkey-first products need it. Only a real browser engine can create and assert the pane’s WebAuthn credential: Android WebView has no WebAuthn support, and iOS WKWebView would need a webcredentials association file for 0xramp.app that 0xramp does not serve. Web sessions do not cross WebView and browser contexts, so pick one surface per session. Pane readiness (current state). The deployed pane does not yet implement this mode’s browser path: outside a native WebView, the deposit step shows a disabled action and the message “Open this session inside your partner wallet” (no QR, no copy fallback), and nothing in the pane navigates to your returnUrl — the value is validated at session create only, so the browser sheet never closes itself at flow end. QR/copy without a bridge and end-of-flow return navigation are planned pane-side work. The sections below describe the target design and the integration shape to build against. There is no bridge in this mode. The flow is create, open, return deep link, authoritative status:
Refresh getStatus(session.sessionRef) on the return link and on app foreground; the status endpoint stays the only authoritative receipt. TODO: untested — device behaviors in this section need real iOS/Android verification before go-live (see the Readiness Checklist); simulations cannot prove them.

Scheme rules

  • Letter-first per RFC 3986 (^[a-zA-Z][a-zA-Z0-9+.-]*:). createSession rejects anything else with ConfigError before the POST — digit-first schemes fail, and so does a value with no scheme prefix at all. Accepted values include custom schemes with or without // (mywallet://ramp, zingo:ramp) and https: URLs (universal/app links). Register the exact string — the server’s return-link allowlist compares the full value exactly: any added query string or fragment is rejected with 403 at session create.
  • Register and route the scheme in your own app (Linking config, intent filters). TODO: untested — confirm the exact scheme string your device build registers on iOS and Android; config-time validation can differ from runtime registration.
  • Keep zcash: (ZIP-321 payment URIs) registered separately and unchanged; it serves a different purpose. Do not reuse it as the ramp return scheme.

iOS notes

  • preferEphemeralSession: false keeps the user signed in to 0xramp.app across ramps; passkey autofill works.
  • iOS shows its website-data-sharing consent prompt each time the sheet opens — expected behavior, not a bug.
  • The sheet is designed to close itself when the pane navigates to your return scheme; the deployed pane does not yet perform this navigation (see Pane readiness above).
  • TODO: untested — verify passkey create/assert inside the auth-session browser on real devices, including returning-user login and the consent prompt, before rollout.

Stage 1 — SELL without any bridge (planned)

Stage 1 describes the intended pane behavior once pane-side support ships. It is not yet available in the deployed pane (see Pane readiness above):
  1. The pane will show the transparent deposit address and the exact amount (display, QR, copy). The user sends from their wallet — your app is not in the signing path at all in this stage.
  2. The pane will detect the deposit on-chain and the flow continues.
  3. At flow end the pane will navigate to your return scheme, the sheet closes, and your app refreshes status from the ticketed status endpoint — the authoritative receipt for the fiat leg.
BUY in this mode is not currently available end to end: the Partner API serves SELL sessions in this flow, and the pane’s browser path does not yet complete BUY either. It remains the designed direction: ZEC lands on the transparent receiver you registered in the create call, and your wallet sees it natively. Attribution (“Powered by 0xramp . P2P.me”) is required at the ramp entry point in this mode too. A draft, unfrozen codec (parseZecSendHandoffUrl, buildZecSendResumeUrl) removes the copy/paste step: the pane emits a handoff link carrying the payment request, your wallet confirms natively and signs with its own wallet core, and the resume link carries advisory txid evidence back to the pane. The encoding may change and pane-side conformance is not claimed. Deep links are advisory by design — your wallet’s native confirmation sheet stays the approval gate, and the status endpoint stays authoritative. Do not build production flows on it before it freezes. See API Reference for the draft functions.

Objects to persist

Treat sessionRef + statusTicket as bearer data: read-only status, nothing more.

Not in v0

No embedded Base/EVM signer, no passkey/identity outside 0xramp.app, no fiat catalog, no payout-key handling, no limits caching, no custody. QR-PAY, swaps, referrals, social verification: not in v0. BUY requires a transparent receiving address; shielded addresses are rejected.