Skip to main content

2. On-Ramp → Merchant (Top-Up Flow)

How a user tops up a merchant account by on-ramping fiat directly through to the merchant's stealth address — in a single integrated flow.

Actors

ActorRole
UserEnd user on the merchant's site wanting to top up
MerchantOperates the merchant platform; generates stealth addresses and confirms payments
On-Ramp ProviderOperates the on-ramp service; handles KYC, payment processing, and token delivery

Components

ComponentDescription
Merchant SiteUser-facing merchant frontend where the top-up flow begins
Merchant BackendCustodial wallet access, stealth address generation, payment scanning
On-Ramp Web AppUser-facing frontend — KYC UI, payment forms, wallet creation (client-side), consent screens
On-Ramp BackendProcesses payments, sends tokens to user's public wallet address
On-Chain (Contract)Manages encrypted balances, redirect registration, and transfers

Flow Overview

Detailed Flow

ZKP/EB Components and Features Used

Component / FeatureUsed?How
Stealth AddressesYesMerchant generates a one-time stealth address per user/session from its stealth meta-address
Encrypted Balance (EB)YesFunds forwarded to merchant's stealth address are held as encrypted balance — amount hidden on-chain
Autoshielding to zk addressYesRequired for self-custodial transfer user-merchant. The auto-redirect converts a public transfer into EB — an observer cannot distinguish whether the user encrypted funds for themselves or transferred to another party. This is the key privacy boundary.
Custodial wallet infraYesMerchant backend needs access to private key to generate stealth addresses

Privacy Properties

PropertyHidden?How
Merchant receives funds at unlinkable addressYesStealth address prevents correlation
User's on-chain identity hidden from merchant's other customersYesStealth address prevents correlation
User has a self-custodial walletYesKeys held by user, backup via email
Payment amount hidden on-chainNoPublic transfer from OnRamp
Auto-redirect indistinguishable from self-encryptionYesOnce public tokens enter EB via auto-redirect, an observer cannot tell if the user encrypted for themselves or transferred to someone else — self-shield and transfer look identical on-chain

Requirements

#RequirementNotes
R1On-ramp web app creates self-custodial wallets client-sideKeys never leave the browser; backend only receives the public address
R2User gives explicit consent before redirect registrationRegulatory and UX requirement
R3On-chain redirect mechanism auto-forwards fundsNo manual step after funding
R4On-Ramp must send public transfer to userRequired by VISA
R5High-risk merchant (Visa classification): on-ramp cannot be embedded directly into the merchant's payment flowFlow must be separated into 3 distinct steps: (1) top-up initiation on merchant site, (2) self-custodial wallet creation, (3) on-ramp on a separate domain. The on-ramp must not appear as part of the merchant's checkout.
R6Low-risk merchant: entire flow can happen in a single integrated experienceTop-up, wallet creation, KYC, payment, and redirect registration can all occur within one site / embedded flow

High-Risk Merchant: Multi-Page Architecture (R5)

When the merchant is classified as high-risk by Visa, the on-ramp must not be embedded in the merchant's payment flow. The user journey is split across 3 separate web pages that coordinate via a WebSocket Wallet Connector service.

Pages

#PageDomain / OwnerResponsibility
0Merchant SiteMerchantInitiates top-up, generates stealth address, opens page 1 via link/redirect
1Self-Custodial WalletWallet ProviderCreates/loads the user's self-custodial wallet (client-side), registers redirect, signs consent
2On-RampOn-Ramp ProviderRuns KYC, processes fiat payment, sends tokens to the user's public wallet address received from page 1

Wallet Connector Service

A lightweight WebSocket relay that enables the three pages to exchange messages without sharing a domain or embedding each other.

AspectDetail
TransportWebSocket (persistent, bidirectional)
SessionShort-lived session ID created by page 0, passed as URL param to pages 1 & 2
MessagesTyped JSON envelopes (e.g. stealth_address, public_wallet, redirect_registered, tx_complete)
Trust modelConnector is a dumb relay — each page independently verifies on-chain state
Hosted byWallet Provider or neutral infra (not the merchant)

Architecture

Sequence (High-Risk Flow)

Low-Risk Merchant Alternative

When the merchant is not high-risk, pages 0, 1, and 2 collapse into a single integrated experience (embedded SDK or same-domain flow). The Wallet Connector is not needed — all coordination happens in-process.

Assumptions

#AssumptionImpact if Wrong
A1On-ramp web app is like standard payment gate (płatności24), merchant can link to, outside of their appCould be embedded SDK instead
A2Auto-redirect registration happens via relayerUser account has to be a smart account, with paymaster
A3Wallet is part of the on-ramp web appIf self-custody needs better separation, it would be another redirect in the process or an iframe
A4On-Ramp maintains pre-funded accounts with public amountsIf onramp can initiate payment from EB, it changes design slightly
A5Merchant can generate unique stealth addresses per user/session, and then L3 support in the merchant offramp stepIf not possible, graph is visible on-chain
A6On-ramp web app accepts merchant stealth address as parameterIf not supported, integrated flow requires a different handoff mechanism
A7Wallet backup delivered to user's emailIf no backup path, user risks losing access to self-custodial wallet, alternative backup options are possible
A8Merchant can scan stealth addresses and decrypt amountsIf not possible, merchant cannot detect or attribute payments

Open Questions

#QuestionContextAnswer
Q1Can Merchant go from public amount -> L3?If yes, then the autoredirected transfer could go to public amount. Payment-merchant link is relying on L3.
Q2Do we need to provide interface for user to manage their account and redirect?They should be able to cancel redirect, it's self-custody
Q3How exactly does the user recover/access their self-custodial wallet later?Shards? Email backup?
Q4Can the whole flow happen on the merchant app (via sdk)?Top-up, never leaving the merchant
Q5How does the merchant attribute a stealth address payment to a specific user?Merchant generated the stealth address for a specific user session — needs to maintain a mapping (stealthAddress → userId)
Q6What if KYC fails or payment is reversed after funds are forwarded to the merchant?Chargeback/clawback mechanism needed. Who bears the risk?
Q7Can this flow support recurring top-ups?User might want to top up monthly without re-doing the full flow. Persistent redirect + saved payment method?
Q8How are chargebacks handled once funds have been redirected to the merchant's stealth address?Card chargebacks can occur days/weeks later. By then, tokens are in the merchant's encrypted wallet. Who bears the loss — On-Ramp or merchant? Is there an escrow period before redirect?
Q9When exactly is the merchant informed that payment has been made?Is it when the on-ramp confirms fiat? When tokens land in the user's public wallet? When the auto-redirect delivers to the stealth address? Merchant needs a clear "payment finality" signal — what triggers it and who sends it?
Q10Is "Merchant Backend" actually a payment-rails backend (middleware) that communicates with the merchant's own backend?Current doc treats the merchant backend as a single actor. In practice, the stealth address generation and payment scanning may live in a shared payment-rails service, with the actual merchant backend behind it. If so, this part of the system needs a separate design doc.