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, WebView with navigation control, return-link scheme.
  • 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. 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.

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.