Skip to main content
An embedded wallet lets you and your users hold and move on-chain assets without learning the chain underneath. Nobody constructs a transaction, manages a sequence number, holds gas, or opens a trustline. Etherfuse builds every transaction, and the network gas and account reserves are bundled into the onramp or offramp fee: they come out of the assets you ramp and are shown as clear amounts in the quote, so the wallet never has to hold a balance for fees.
Integrate server-side. Run provisioning, quoting, ordering, and approval signing from your backend with your API key. The sections below explain each step. For an interactive tester and a copy-paste sandbox script, see the sandbox playground.
Sandbox playground →Interactive tester for the full embedded-wallet flow in sandbox.Browser tester plus a minimal server-side onramp script with API reference links. An embedded wallet is non-custodial. Each wallet has a single owner key, a P-256 key you provision, and Etherfuse can only propose transactions for that wallet to authorize and reject (refuse to relay) them. Etherfuse can never sign or move funds on its own. The owner key signs one approval per transaction, and only then does Etherfuse broadcast.
Embedded wallets run on Stellar today, so the wallet’s chain is Stellar. All endpoints use the raw Authorization header (no Bearer prefix); see Authentication. You do not need the wallet’s per-chain rules (trustlines, reserves, transaction expiry) to use an embedded wallet; the embedded wallet handles them for you.

Who holds the key

You, the integrating partner, choose who holds the owner key:
  • You hold the owner key yourself, on behalf of your program; or
  • Your end customer holds and controls the owner key, so the customer alone can authorize transactions and you never hold or see the customer’s private key.
Control of the funds follows control of the key. A party that cannot move funds without the keyholder is not exercising independent control over them, so whoever holds the owner key is the only party that can authorize moving the wallet’s assets.
Whether holding the key makes you a custodian or money transmitter depends on your own facts, activities, and jurisdiction. This is not legal advice; determine your own obligations with your legal and compliance advisors.

Provision a wallet

Provision an embedded wallet by sending the public half of a P-256 key you generated. Submit exactly one of:
  • signerPublicKey — compressed SEC1 hex (66 characters, 02/03 prefix)
  • signerPublicKeyPem — SPKI PEM (-----BEGIN PUBLIC KEY-----)
  • signerPublicKeyJwk — EC JWK with crv: P-256 (typical WebCrypto export)
Etherfuse normalizes to compressed SEC1 hex before provisioning. The wallet’s Stellar address is returned in publicKey.
curl -X POST https://api.sand.etherfuse.com/ramp/wallet \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "walletId": "<uuid-you-generate>",
    "signer": { "signerPublicKeyPem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----" }
  }'
A 200 returns the wallet, including the address and the signer you registered:
{
  "walletId": "1b6e98a2-...",
  "publicKey": "GDUKMGUGD3V6VXTU2RLAUM7A2FABLMHCPWTMDHKP7HHJ6FCZKEY4PVWL",
  "blockchain": "stellar",
  "signerPublicKey": "02a1...",
  "claimedOwnership": true
}
Use the publicKey as walletAddress in your quote and walletId as cryptoWalletId in your order. See POST /ramp/wallet for the full schema.
The signerPublicKey you provision is the only key that can ever sign for this wallet. Sign with the matching private key. A signature from a different key is rejected.

See your wallets

To reuse a wallet you already provisioned, list an organization’s wallets and reuse the existing publicKey; there is no need to re-provision. Embedded wallets carry a signerPublicKey and claimedOwnership: true.
curl -X POST https://api.sand.etherfuse.com/ramp/customer/<customer_id>/wallets \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{ "pageNumber": 0, "pageSize": 25 }'

Quoting and ordering

An embedded wallet is quoted and ordered exactly like any other wallet, so the quote and order follow the standard procedure. Pass the wallet’s publicKey as walletAddress on the quote and cryptoWalletId (or publicKey) on the order. The direction, blockchain, and amounts ride on the quote; for the wallet’s chain, blockchain is stellar today. Quote with walletAddress so the account reserve and trustline a new wallet needs are priced into the fee (Stellar onramps only; see Stellar → Wallet & gas). This is how most embedded wallets are first populated: a brand-new wallet is bootstrapped by its first onramp, with reserve and trustline costs folded into the quote fee; the partner never sends XLM. Without walletAddress, order creation will ask you to re-quote. The standard flows cover both directions end to end: Onramps and Offramps.
The create response carries no approval. Order creation only opens the order. The approval is minted later (after fiat is received for an onramp, or after Etherfuse builds the burn transaction for an offramp) and surfaces on the order read.

Signing is the different part

Everything above is shared with any wallet. The signing is what makes an embedded wallet an embedded wallet, and it is the same propose-then-approve loop in both directions:
  1. Etherfuse mints an approval on the order asynchronously: for an onramp after fiat is received, for an offramp after the burn transaction is built. It is never returned when you create the order.
  2. The order_updated webhook and the WebSocket stream both deliver the full order including the pending approval, so you are notified the moment one is ready (polling GET /ramp/order/ is the fallback).
  3. You re-read the order, take the approval object { approvalMessageId, approvalMessage, summary }, sign the exact approvalMessage bytes with the owner key, and submit the signature to POST /ramp/order//approvals.
  4. Etherfuse accepts the approval and broadcasts the transaction.
Re-read the order immediately before signing. The approvalMessage embeds a timestamp that is rebuilt fresh on every read, so a signature over bytes from an earlier read is stale and rejected. Re-read, then sign the bytes from that very response, byte for byte. Any re-serialization (key reordering, whitespace, re-encoding) changes the bytes and breaks the signature.
Two approvals are worth calling out by name, because they are the ones integrators meet first:
First-funds acceptance. This one is a quirk of the chain, not of Etherfuse. The first time an embedded wallet receives funds it has no trustline, so the tokens arrive as a Stellar claimable balance, and the owner has to sign an acceptance that both opens the trustline and claims the funds (a ChangeTrust plus ClaimClaimableBalance transaction). For an embedded wallet that acceptance is delivered as an owner approval to sign, exactly like any other approval; you do not build it. The order then carries stellarClaimableBalanceId.
Offramp approval. An offramp authorizes a burn rather than a claim. The approval (an Offramp <amount> <code> summary) appears once Etherfuse builds the burn transaction, with no incoming-fiat step first, so it is available earlier in the lifecycle than an onramp’s. Sign and submit it exactly the same way.

Sign the approval

The approvalMessage is an opaque authorization payload: sign the string verbatim; you do not parse or construct it. Show users what they are approving with summary (for example Claim 99.61 CETES or Offramp 50 USDC). Return the ECDSA signature as a hex string in the signature field. Everything is hex in JSON. What varies is the byte layout inside: server-side signers typically output ~142 characters (DER-wrapped r and s); browser WebCrypto outputs 128 characters (raw r‖s). Etherfuse accepts both.
import crypto from "node:crypto";

// Sign the EXACT approvalMessage bytes from a fresh order read.
function signApproval(approvalMessage, privateKeyPem) {
  return crypto
    .createSign("SHA256")
    .update(approvalMessage)
    .sign(privateKeyPem)
    .toString("hex");
}
For a fixed test keypair, both signature encodings, sample submit bodies, and offline verification, see the sandbox playground → Worked example. Submit { approvalMessageId, approvalMessage, signature }. Include publicKey (your signerPublicKey) when you want the request self-describing; when omitted, Etherfuse uses the key registered on the wallet. A 200 means the approval was accepted and broadcast follows automatically. The response includes completed: true when the transaction is fully approved (today every embedded wallet uses a single owner key, so this is always true on success).
StatusMeaning
200Accepted; broadcast follows automatically
400Bad signature, publicKey mismatch, or approvalMessage does not match the pending proposal
409Stale approvalMessageId, or order is canceled/refunded; re-read the order
410Approval expired; a replacement is minted; re-read and sign the fresh one
Expired or failed transactions are re-proposed automatically and surface as a new approval, so there is usually no recovery procedure: re-read and sign the fresh one.
Embedded wallet offramps use this same approval flow, not burnTransaction. If you provisioned an embedded wallet, wait for approval on the order and sign it; ignore the bring-your-own-wallet burn path in Offramps.

Sandbox playground

Interactive tester and minimal sandbox onramp script

Stellar

Onramps

Offramps

Embedded Wallets Sandbox Playground

Sandbox interactive tester and minimal server-side onramp script for embedded wallets