Quick Access
Check out example:GitHub
SPL Tokens Anchor Implementation
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.

