> ## Documentation Index
> Fetch the complete documentation index at: https://docs.etherfuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Wallets

> Provision non-custodial wallets and move on-chain assets for your users without anyone touching gas, trustlines, or transaction construction.

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.

<Info>
  **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](/guides/embedded-wallets-walkthrough).
</Info>

<a href="/guides/embedded-wallets-walkthrough" className="sandbox-playground-cta">
  <span className="sandbox-playground-cta__title">Sandbox playground →</span>

  <span className="sandbox-playground-cta__body">
    Interactive tester for the full embedded-wallet flow in sandbox.
  </span>

  <span className="sandbox-playground-cta__detail">
    Browser tester plus a minimal server-side onramp script with API reference links.
  </span>
</a>

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.

<Info>
  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](/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.
</Info>

## 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.

<Note>
  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.
</Note>

## 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`.

```bash theme={null}
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:

```json theme={null}
{
  "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](/api-reference/wallets/register-wallet) for the full schema.

<Warning>
  **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.
</Warning>

## 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`.

```bash theme={null}
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](/api-reference/quotes/get-quote) and `cryptoWalletId` (or `publicKey`) on the [order](/api-reference/orders/create-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](/guides/chains/stellar#wallet--gas-requirements)). 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](/guides/testing-onramps) and [Offramps](/guides/testing-offramps).

<Info>
  **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.
</Info>

## 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](/webhooks) 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/{orderId}](/api-reference/orders/get-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/{orderId}/approvals](/api-reference/orders/submit-order-approval).
4. Etherfuse accepts the approval and broadcasts the transaction.

<Warning>
  **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.
</Warning>

Two approvals are worth calling out by name, because they are the ones integrators meet first:

<Note>
  **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`.
</Note>

<Note>
  **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.
</Note>

### 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.

<CodeGroup>
  ```javascript Node theme={null}
  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");
  }
  ```

  ```javascript Browser theme={null}
  // WebCrypto: sign, then hex-encode the 64-byte result (128 hex characters).
  async function signApproval(approvalMessage, privateKey) {
    const sig = await crypto.subtle.sign(
      { name: "ECDSA", hash: "SHA-256" },
      privateKey,
      new TextEncoder().encode(approvalMessage),
    );
    return [...new Uint8Array(sig)]
      .map((b) => b.toString(16).padStart(2, "0"))
      .join("");
  }
  ```
</CodeGroup>

For a fixed test keypair, both signature encodings, sample submit bodies, and offline verification, see the [sandbox playground → Worked example](/guides/embedded-wallets-walkthrough#worked-example-test-fixture).

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).

| Status | Meaning                                                                                       |
| ------ | --------------------------------------------------------------------------------------------- |
| `200`  | Accepted; broadcast follows automatically                                                     |
| `400`  | Bad signature, `publicKey` mismatch, or `approvalMessage` does not match the pending proposal |
| `409`  | Stale `approvalMessageId`, or order is canceled/refunded; re-read the order                   |
| `410`  | Approval 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.

<Note>
  **Embedded wallet offramps** use this same approval flow, not `burnTransaction`. If you provisioned an [embedded wallet](/guides/embedded-wallets), wait for `approval` on the order and sign it; ignore the bring-your-own-wallet burn path in [Offramps](/guides/testing-offramps).
</Note>

## Related

<CardGroup cols={2}>
  <Card title="Sandbox playground" icon="flask" href="/guides/embedded-wallets-walkthrough">
    Interactive tester and minimal sandbox onramp script
  </Card>

  <Card title="Stellar" icon="star" href="/guides/chains/stellar" />

  <Card title="Onramps" icon="arrow-up-right-dots" href="/guides/testing-onramps" />

  <Card title="Offramps" icon="arrow-down" href="/guides/testing-offramps" />
</CardGroup>
