Quick Access
Check out example:GitHub
SPL Tokens Anchor Implementation
Live Example App
Try the SPL Tokens demo
Live Example App
Try private payments
Overview
Private Payments are the privacy use case built on top of SPL tokens on ER. The same deposit / transfer / withdraw primitive runs with private visibility, so the amount, destination, and timing of a payment are shielded rather than broadcast publicly.New to the primitive? Read the Ephemeral SPL Tokens
overview first — this guide
assumes the eATA / Global Vault / delegation model.
API reference
Endpoints for deposit, transfer, withdraw, balances, stealth pools, and auth.
Reference program
The on-chain Ephemeral SPL Token program.
Privacy model
Private payments rely on:- Private visibility — transfers execute inside the Ephemeral Rollup with
visibility: "private", so the transfer is not broadcast publicly. - Stealth handles — send to a human-readable name (e.g.
alice@magicblock.id) instead of a raw public key. The handle resolves to one or more destination keys via a stealth pool, breaking the direct sender → recipient link on-chain. - Queued settlement — private transfers can settle through the program’s transfer queue rather than a direct, immediately-linkable movement.
Authentication
Private reads and stealth-pool operations require a bearer token. Obtain one with the challenge/login flow before calling protected endpoints:1
Request a challenge
GET /v1/spl/challenge returns a message for the user to sign.2
Log in
POST /v1/spl/login exchanges the signed challenge for a bearer token.3
Call protected endpoints
Send
Authorization: Bearer <token> on GET /v1/spl/private-balance and
POST /v1/spl/stealth-pool.Deposits
Move tokens into the mint’s Global Vault and credit the depositor’s ephemeral ATA.POST /v1/spl/deposit— builds the deposit transaction. Setprivate: trueto keep the deposited balance private.
sendTo field (base or ephemeral). Sign it and
submit via POST /v1/transaction/send
or your own RPC.
Private transfers
POST /v1/spl/transferwithvisibility: "private".
To use a stealth handle, initialize it first:
POST /v1/spl/stealth-pool— map a handle (≤255 UTF-8 bytes; not normalized, soAlice@…≠alice@…) to 1–10 destination owner keys, optionally splitting payments across them.GET /v1/spl/stealth-pool?handle=…— check whether a handle’s pool exists.
Handles are stored as their exact UTF-8 bytes.
GET returns only whether the pool exists — never the
destination keys.Fees and gasless transfers
visibility and gasless are independent request fields: visibility controls how the transfer is
routed, while gasless controls who pays gas. The cost of a private transfer is the sum of a
privacy fee, which always applies, and the chosen gas payment mode.
Every private base → base transfer pays a 0.1% (10 bps) privacy fee, charged in the token being
transferred — not in SOL. Gas can be paid in either of two modes:
With
gasless: true, the configured sponsor becomes the transaction fee payer and co-signs, and the
API prepends a token-transfer instruction that reimburses the sponsor with a flat 0.2 USDC/USDT
relay fee from the sender’s balance. Because the relay fee is flat, gasless transfers require a
minimum of 0.5 USDC/USDT; the minimum applies only to the gasless mode, not to private transfers
in general. Amounts below it can still be sent with visibility: "private" by omitting gasless.
A first private transfer may also include a one-time ~0.00204 SOL of rent to set up the ephemeral
token account. All token-denominated fees are reported in the transfer response as fees.tokens, and
SOL-denominated costs as fees.lamports.
A
gasless: true request below the minimum, or with an unsupported mint, is rejected with a 400
error (INVALID_GASLESS_TRANSFER_AMOUNT / INVALID_GASLESS_TRANSFER_MINT). The API never changes
the requested visibility: a transfer is only public when the request says so. Clients that handle
gasless errors by rebuilding the request should preserve visibility: "private" if privacy is
intended.gasless: true is ignored when from is an off-curve PDA owner (gasless requires a wallet sender);
the transfer still executes with the requested visibility, with the sender as fee payer.
Withdrawals
Move a balance back out of the Global Vault to a standard base-layer SPL token account.POST /v1/spl/withdraw— builds the withdrawal transaction.
GET /v1/spl/balance— public balance.GET /v1/spl/private-balance— private balance (requires the bearer token).
Developer notes
- Privacy is a spectrum. Ephemeral SPL tokens reduce linkability; they do not hide amounts, timing, or protect against network-level analysis.
- Handles aren’t normalized. Casing/whitespace matter — display and store handles consistently.
- Match the flow. Stealth-handle transfers require
visibility: "private",fromBalance: "base",toBalance: "base"(these are also the omitted-field defaults). - Sign then send. Builder endpoints return unsigned transactions; submit them with
POST /v1/transaction/sendand honor the returnedsendTo.
Next steps
Ephemeral SPL Tokens — Overview
The primitive behind private payments.
SDK Quickstart
The on-chain/SDK integration path.

