> ## 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 Sandbox Playground

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

This page is **optional developer tooling**, not the primary integration guide. Read [Embedded Wallets](https://docs.etherfuse.com/guides/embedded-wallets.md) first for concepts and per-step detail. Run the interactive tester, then copy the minimal server-side script below it.

> [!NOTE]
>
> Embedded wallets run on **Stellar** today. Quoting and ordering follow the standard procedure, so this page links out to [Onramps](https://docs.etherfuse.com/guides/testing-onramps.md) and [Offramps](https://docs.etherfuse.com/guides/testing-offramps.md) rather than re-teaching them. The minimal script below is the fastest server-side path; the main guide covers concepts and signing detail.

## Sandbox tester

> [!WARNING]
>
> **Sandbox keys only.** Pasting an API key here sends authenticated requests from your browser to the Etherfuse API, the same pattern as the [Swaps](https://docs.etherfuse.com/guides/testing-swaps.md) tutorial and the API Reference "Try it" playground. Use a **sandbox** API key only. Production integrations must call these endpoints **server-side**; never embed a production API key in client-side code.

> [!NOTE]
>
> The tester stores your sandbox API key in **session storage** for the current browser tab only. It covers the embedded-specific sequence (provision → quote → order → approval signing). For quote and order mechanics shared with any wallet, see the standard ramp guides linked above.

Paste your sandbox API key below and the tester generates an owner keypair in the browser, provisions a wallet, quotes, orders, simulates the fiat deposit, watches for the approval, signs it with the generated owner key, and broadcasts. Every request and response is shown inline so you can check your own payloads.

### Interactive Embedded Wallet Tutorial

Sandbox only

Host:
`https://api.sand.etherfuse.com`

API Key

Find it under API Keys at
[sandbox.etherfuse.com/ramp](https://sandbox.etherfuse.com/ramp?view=manage)

Your API key is stored in session storage and cleared when you close the tab.

Direction

Stellar Asset

Amount ()

Bank account

## Minimal sandbox onramp

Copy-paste server-side script for a complete embedded-wallet onramp in sandbox: provision with PEM, quote with `walletAddress`, order with `cryptoWalletId`, simulate fiat, poll for the approval, sign, and broadcast. Set `ETHERFUSE_API_KEY`, `BANK_ACCOUNT_ID`, and `TARGET_ASSET` (a Stellar asset `identifier` from [GET /ramp/assets](https://docs.etherfuse.com/api-reference/assets/list-assets.md)).

```javascript theme={null}
import crypto from "node:crypto";

const sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
const id = () => crypto.randomUUID();
const api = async (method, path, body) => {
  const r = await fetch(`https://api.sand.etherfuse.com${path}`, {
    method,
    headers: { Authorization: process.env.ETHERFUSE_API_KEY, "Content-Type": "application/json" },
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });
  if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
  const text = await r.text();
  return text ? JSON.parse(text) : {};
};

// GET /ramp/me — customerId for quotes
const { id: customerId } = await api("GET", "/ramp/me");

// Generate owner P-256 keypair (public → provision; private → sign approvals)
const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
const pem = privateKey.export({ type: "pkcs8", format: "pem" });
const sign = (msg) => crypto.createSign("SHA256").update(msg).sign(pem).toString("hex");

async function embeddedOnramp() {
  // POST /ramp/wallet — provision embedded wallet (signerPublicKeyPem)
  const { walletId, publicKey: wallet } = await api("POST", "/ramp/wallet", {
    walletId: id(),
    signer: { signerPublicKeyPem: publicKey.export({ type: "spki", format: "pem" }) },
  });

  // POST /ramp/quote — onramp pricing; walletAddress folds reserve + trustline into fee
  const quote = {
    quoteId: id(), customerId, blockchain: "stellar", walletAddress: wallet, sourceAmount: "100",
    quoteAssets: { type: "onramp", sourceAsset: "MXN", targetAsset: process.env.TARGET_ASSET },
  };
  await api("POST", "/ramp/quote", quote);

  // POST /ramp/order — slim body with quoteId + cryptoWalletId
  const orderId = id();
  await api("POST", "/ramp/order", {
    orderId, quoteId: quote.quoteId, bankAccountId: process.env.BANK_ACCOUNT_ID, cryptoWalletId: walletId,
  });

  // POST /ramp/order/fiat_received — sandbox only (production: your fiat rail fires this)
  await api("POST", "/ramp/order/fiat_received", { orderId });

  // GET /ramp/order + POST .../approvals — poll until completed; sign when approval pending; production should use registered webhooks or WebSockets
  while (true) {
    const order = await api("GET", `/ramp/order/${orderId}`);
    if (order.status === "completed") break;
    if (order.approval?.approvalMessage) {
      const { approvalMessageId, approvalMessage } = order.approval;
      await api("POST", `/ramp/order/${orderId}/approvals`, {
        approvalMessageId, approvalMessage, signature: sign(approvalMessage),
      });
    }
    await sleep(15);
  }
}

embeddedOnramp().catch(console.error);
```

Each commented `api()` call links to the API reference:

| Call | Reference |
| - | - |
| `GET /ramp/me` | [Get organization identity](https://docs.etherfuse.com/api-reference/organizations/get-organization.md): use `id` as `customerId` |
| `POST /ramp/wallet` | [Register wallet](https://docs.etherfuse.com/api-reference/wallets/register-wallet.md): PEM, hex, or JWK |
| `POST /ramp/quote` | [Get quote](https://docs.etherfuse.com/api-reference/quotes/get-quote.md): pass `walletAddress` for reserve/trustline pricing |
| `POST /ramp/order` | [Create order](https://docs.etherfuse.com/api-reference/orders/create-order.md): `cryptoWalletId` resolves the embedded wallet |
| `POST /ramp/order/fiat_received` | [Simulate fiat received](https://docs.etherfuse.com/sandbox-api/fiat-received.md): **sandbox only**; production waits on your fiat rail |
| `GET /ramp/order/{orderId}` | [Get order](https://docs.etherfuse.com/api-reference/orders/get-order.md): re-read right before signing |
| `POST /ramp/order/{orderId}/approvals` | [Submit order approval](https://docs.etherfuse.com/api-reference/orders/submit-order-approval.md): hex signature over `approvalMessage` |

> [!NOTE]
>
> Offramps follow the same approval loop; there is no `fiat_received` step and the approval appears once Etherfuse builds the burn transaction. See [Offramps](https://docs.etherfuse.com/guides/testing-offramps.md).

## Worked example (test fixture)

The values below are a **documentation fixture only**; do not use this private key in production. They show the same `approvalMessage` signed once, yielding two common hex encodings Etherfuse accepts.

**Bytes to sign** (`approvalMessage`, copy verbatim, including quotes and escaping):

```json theme={null}
{"version":"1","timestamp":"2026-06-18T12:00:00Z","orderId":"4e9b2bd5-af60-4154-cd82-5ebf70913c44"}
```

**Owner private key** (PKCS#8 PEM):

```
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgl0AYSnGsUwCJzf/+
+B78kqY0U1J+0UUUgy2j/u6VWL2hRANCAATfJYawOOCqxGG8ope5LLHdES6LD7zB
n7Uc3rZTsAPHbM3HbOve4wstthHETWeGGD2wKgQz87uyVPnvB72arU9Z
-----END PRIVATE KEY-----
```

**Normalized `signerPublicKey`** (compressed SEC1 hex; what Etherfuse stores after provision, regardless of whether you sent hex, PEM, or JWK):

```
03df2586b038e0aac461bca297b92cb1dd112e8b0fbcc19fb51cdeb653b003c76c
```

**Corresponding public key PEM** (valid as `signerPublicKeyPem` at provision, or for offline verification):

```
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3yWGsDjgqsRhvKKXuSyx3REuiw+8
wZ+1HN62U7ADx2zNx2zr3uMLLbYRxE1nhhg9sCoEM/O7slT57we9mq1PWQ==
-----END PUBLIC KEY-----
```

**Signatures over that message** (submit either one; same underlying `r` and `s`, two hex layouts):

| Encoding | Typical source | Hex length | Value |
| - | - | - | - |
| DER-wrapped | Node, Java, Go, Erlang, OpenSSL | 142 | `304502205d5bfd6a52efdaadcd0ac2cff30fab2461b2484fd8e4dc4c894127aaa6678e86022100e814fc2404b1562a28d90121e540d6996e8484003790cd745fc78527ac07b766` |
| Raw `r‖s` | Browser WebCrypto, JWS ES256 (same bytes as above) | 128 | `5d5bfd6a52efdaadcd0ac2cff30fab2461b2484fd8e4dc4c894127aaa6678e86e814fc2404b1562a28d90121e540d6996e8484003790cd745fc78527ac07b766` |

Both verify the same ECDSA P-256 / SHA-256 signature. Etherfuse normalizes either layout before broadcast. ECDSA is randomized, so signing again yields different hex, but any valid signature over the same message is accepted.

> [!TIP]
>
> To verify the **DER-wrapped** fixture offline, paste the message, PEM public key, and DER signature into [EncodeAll ECDSA Verify](https://encodeall.github.io/ecdsa_verify.html) with curve **P-256** and algorithm **SHA256withECDSA**. The result should be `Valid`. Most online verifiers expect DER, not the 128-character raw layout.

**Submit with server-side hex** (DER-wrapped, typical Node/Java signer):

```json theme={null}
{
  "approvalMessageId": "5fac3ce6-b071-4265-de93-6fcf81a24d55",
  "approvalMessage": "{\"version\":\"1\",\"timestamp\":\"2026-06-18T12:00:00Z\",\"orderId\":\"4e9b2bd5-af60-4154-cd82-5ebf70913c44\"}",
  "publicKey": "03df2586b038e0aac461bca297b92cb1dd112e8b0fbcc19fb51cdeb653b003c76c",
  "signature": "304502205d5bfd6a52efdaadcd0ac2cff30fab2461b2484fd8e4dc4c894127aaa6678e86022100e814fc2404b1562a28d90121e540d6996e8484003790cd745fc78527ac07b766"
}
```

**Submit with browser hex** (raw `r‖s`, typical WebCrypto signer):

```json theme={null}
{
  "approvalMessageId": "5fac3ce6-b071-4265-de93-6fcf81a24d55",
  "approvalMessage": "{\"version\":\"1\",\"timestamp\":\"2026-06-18T12:00:00Z\",\"orderId\":\"4e9b2bd5-af60-4154-cd82-5ebf70913c44\"}",
  "publicKey": "03df2586b038e0aac461bca297b92cb1dd112e8b0fbcc19fb51cdeb653b003c76c",
  "signature": "5d5bfd6a52efdaadcd0ac2cff30fab2461b2484fd8e4dc4c894127aaa6678e86e814fc2404b1562a28d90121e540d6996e8484003790cd745fc78527ac07b766"
}
```

In production, `approvalMessage` and `approvalMessageId` come from a fresh [`GET /ramp/order/{orderId}`](https://docs.etherfuse.com/api-reference/orders/get-order) read; the fixture above uses a static message for illustration only.

## Related

> [card] [Embedded Wallets](https://docs.etherfuse.com/guides/embedded-wallets.md)

> [card] [Stellar](https://docs.etherfuse.com/guides/chains/stellar.md)

> [card] [Onramps](https://docs.etherfuse.com/guides/testing-onramps.md)

> [card] [Offramps](https://docs.etherfuse.com/guides/testing-offramps.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.