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".
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 asendStore. Construct one per unlocked wallet namespace:
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:{ 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: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: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:
Step 4 — Handle the send request
The pane sendspsp/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:Step 6 — Read authoritative status
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
Step 8 — Handle return deep-link
Validate scheme/host yourself, matchsessionRef against your saved session, then:
getStatus and your chain view.
Objects to persist
Treat
sessionRef + statusTicket as bearer data: read-only status, nothing more.