Sandbox
Test mode on the Anvil fork — APIs, wallet, RPC, SDK, CLI, MCP
Sandbox (seller Test mode) lets you create payable APIs and settle x402 payments without real funds. Settlements run on an Anvil fork of Base mainnet (eip155:845320402), not public Base or Base Sepolia.
In the seller dashboard, switch the environment toggle to Sandbox. The same mode is available from SDKs, CLI, MCP, and the control plane API.
What you get
| Piece | Detail |
|---|---|
| Environment | Control plane development (UI label: Sandbox) |
| Network | CAIP-2 eip155:845320402 (Anvil --chain-id 845320402) |
| Payment token | Base mainnet USDC at 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (forked state) |
| Facilitator | Dev facilitator (facilitator-dev) — not the public testnet facilitator |
| Billing | Development traffic is typically fee-exempt |
| Persistence | Fork state is ephemeral — restarts wipe balances |
Sandbox is for iterating APIs, endpoints, and payment flows. Use Production for live mainnet settlement.
Chain and RPC (chain-dev)
Launa runs chain-dev: Anvil fork + chain-manager faucet API + facilitator-dev.
Public endpoints (hosted)
| Service | URL |
|---|---|
| Anvil RPC | https://rpc-dev.launa.io |
| Chain manager | https://chain-manager-dev.launa.io:8099 |
| Dev facilitator | https://facilitator-dev.launa.io |
Add the network in MetaMask (or another wallet):
- Network name: Launa Sandbox Fork
- RPC URL:
https://rpc-dev.launa.io(or therpc_urlfromGET /config/local-devon your control plane) - Chain ID:
845320402 - Currency symbol: ETH
- USDC:
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Control plane config for clients:
GET /config/local-devReturns enabled, chain_id, network, rpc_url, faucet_enabled, and the sandbox payment token. SDKs: client.config.localDev() (TypeScript), LocalDevConfig (Go), config.local_dev() (Python).
Local full stack
With the launa compose stack:
| Service | Host URL |
|---|---|
| Anvil RPC | http://127.0.0.1:18545 |
| Chain manager | http://127.0.0.1:8099 |
| Dev facilitator | http://127.0.0.1:8089 |
cd launa && make up
make restart-chain # fresh Anvil forkAuth and environment selection
| Method | How Sandbox is selected |
|---|---|
| Dashboard | Environment toggle → Sandbox (X-Launa-Environment: development) |
| API key | launa_sandbox_... keys (created while in Sandbox) — environment is baked into the key; use LAUNA_BASE_URL=https://api.staging.launa.io |
| JWT | Header X-Launa-Environment: development (or CLI --sandbox) |
| Env vars | LAUNA_ENVIRONMENT=development or LAUNA_SANDBOX=1 (JWT / CLI only — not for API keys) |
launa_live_... keys are production-only (https://api.launa.io). Combining them with --sandbox / development fails fast. API key prefix and base URL must match — wrong pairing returns 401 invalid token.
Sandbox wallet routes require a JWT (same as keys commands), not an API key.
Sandbox wallet (private key)
Each seller account can have a platform-managed EVM keypair for Sandbox payments. GET /sandbox-wallet get-or-creates the wallet, returns the private key, and kicks an async faucet (ETH + USDC on the fork).
CLI (recommended)
export LAUNA_BASE_URL=https://api.launa.io # JWT + --sandbox works on prod or staging
export LAUNA_JWT=$(launa auth login --email you@example.com --password '...')
# Full private key in JSON; --wait polls until balances look funded
export LAUNA_EVM_PRIVATE_KEY=$(
launa --sandbox sandbox-wallet get --json --wait | jq -r .private_key
)
launa pay url --url "https://your-api.dev….launa.io/v1/hello"launa --sandbox sandbox-wallet get [--wait|--no-wait] [--json]
launa --sandbox sandbox-wallet reset [--wait|--no-wait] [--json]Human output redacts the key; --json includes private_key. --wait is on by default for human mode and off for --json unless you pass --wait.
TypeScript SDK
import { LaunaClient } from "@launa/sdk";
const client = new LaunaClient({
baseUrl: process.env.LAUNA_BASE_URL!,
token: process.env.LAUNA_JWT!,
environment: "development",
});
const wallet = await client.sandboxWallet.get();
// wallet.address, wallet.private_key, wallet.private_key_redacted
const balances = await client.config.devBalances(wallet.address);
// balances.eth_wei, balances.usdc_base (hex)
await client.config.devFaucet(wallet.address, "all"); // optional manual refillGo
cl := launa.NewClient(baseURL, launa.Credentials{Token: jwt},
launa.WithEnvironment("development"))
wallet, err := cl.GetSandboxWallet(ctx)
balances, err := cl.DevBalances(ctx, wallet.Address)Python
from launa_sdk import LaunaClient
client = LaunaClient(base_url, token=jwt, environment="development")
wallet = client.sandbox_wallet.get()
balances = client.config.dev_balances(wallet["address"])Plain HTTP
GET /sandbox-wallet
Authorization: Bearer <JWT>
X-Launa-Environment: developmentPOST /sandbox-wallet/reset
Authorization: Bearer <JWT>
X-Launa-Environment: developmentPOST /dev/balances
Authorization: Bearer <JWT>
X-Launa-Environment: development
Content-Type: application/json
{"address":"0x..."}POST /dev/faucet
Authorization: Bearer <JWT>
X-Launa-Environment: development
Content-Type: application/json
{"address":"0x...","asset":"all"}Response shape for wallet routes: address, private_key, private_key_redacted.
Create APIs and endpoints in Sandbox
Development APIs must use accepts on eip155:845320402 only. Default SDK/CLI helpers use Base USDC on that network.
CLI
export LAUNA_BASE_URL=https://api.staging.launa.io # required for launa_sandbox_... keys
export LAUNA_API_KEY=launa_sandbox_... # or LAUNA_JWT + --sandbox on either host
launa --sandbox apis create \
--name "Demo" --slug demo \
--upstream https://api.example.com \
--pay-to 0xYourWallet --json
launa --sandbox endpoints create \
--api-id API_ID --path "/v1/*" --price-usdc 0.10 --json--sandbox (or LAUNA_ENVIRONMENT / LAUNA_SANDBOX) sets the development environment and defaults endpoint pricing to the fork. Explicit --network / --accepts still win.
TypeScript
import {
LaunaClient,
buildSandboxUsdcAccept,
usdcToAtomic,
} from "@launa/sdk";
const client = new LaunaClient({
baseUrl: process.env.LAUNA_BASE_URL!,
apiKey: process.env.LAUNA_API_KEY!, // launa_sandbox_...
environment: "development",
});
const api = await client.apis.create({
name: "Demo",
slug: "demo",
upstream_base_url: "https://api.example.com",
pay_to_mode: "user_wallet",
pay_to_address: "0x...",
});
await client.endpoints.create(api.id, {
method: "GET",
path_pattern: "/v1/*",
accepts: [buildSandboxUsdcAccept("exact", usdcToAtomic("0.10"))],
});Go
cl := launa.NewClient(baseURL, launa.Credentials{APIKey: testKey},
launa.WithEnvironment("development"))
api, _ := cl.CreateAPI(ctx, launa.CreateAPIInput{ /* ... */ })
accept, _ := launa.BuildSandboxUsdcAccept("exact", "100000")
cl.CreateEndpoint(ctx, api.ID, launa.CreateEndpointInput{
Method: "GET", PathPattern: "/v1/*", Accepts: []launa.AcceptOption{accept},
})Python
from launa_sdk import LaunaClient, build_sandbox_usdc_accept
client = LaunaClient.from_env() # LAUNA_ENVIRONMENT=development or launa_sandbox_ key
api = client.apis.create({...})
client.endpoints.create(api.id, {
"method": "GET",
"path_pattern": "/v1/*",
"accepts": [build_sandbox_usdc_accept("100000")],
})MCP
Set LAUNA_API_KEY to an launa_sandbox_... key and LAUNA_BASE_URL=https://api.staging.launa.io (API key environment is fixed by prefix — LAUNA_ENVIRONMENT does not apply). Default launa_add_endpoint / OpenAPI import pricing uses sandbox-fork USDC accepts.
MCP is API-key only — it cannot call /sandbox-wallet. Use the CLI + JWT for the funded private key, then set LAUNA_EVM_PRIVATE_KEY for launa_pay_url.
Plain API
POST /apis
X-API-Key: launa_sandbox_...
# or Authorization: Bearer <JWT>
# X-Launa-Environment: development
Content-Type: application/json
{"name":"Demo","slug":"demo","upstream_base_url":"https://api.example.com","pay_to_mode":"user_wallet","pay_to_address":"0x..."}POST /apis/{id}/endpoints
Content-Type: application/json
{
"method": "GET",
"path_pattern": "/v1/*",
"accepts": [{
"scheme": "exact",
"network": "eip155:845320402",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "100000",
"max_timeout_seconds": 300,
"extra": {"name": "USDC", "version": "2", "assetTransferMethod": "eip3009"}
}]
}Pay a Sandbox gateway URL
- Create API + endpoint in Sandbox (above).
- Load the sandbox wallet private key into
LAUNA_EVM_PRIVATE_KEY. - Ensure the wallet has fork ETH + USDC (
sandbox-wallet get --waitor faucet). - Pay the gateway hostname (not the control plane):
launa pay url --url "https://demo.dev.<platform-domain>/v1/hello" --jsonOr TypeScript @launa/sdk/buyer, Go/Python buyer modules, or MCP launa_pay_url.
Shared demo gateway (when provisioned): check the seller dashboard Sandbox onboarding flow — useful for smoke tests; create your own API for real work.
Environment variables
| Variable | Purpose |
|---|---|
LAUNA_BASE_URL | https://api.staging.launa.io with launa_sandbox_... keys; https://api.launa.io for launa_live_... or JWT + --sandbox |
LAUNA_API_KEY | launa_sandbox_... for Sandbox automation (pair with staging URL) |
LAUNA_JWT | Required for sandbox-wallet and keys |
LAUNA_ENVIRONMENT | development selects Sandbox |
LAUNA_SANDBOX | 1 / true — alias for development |
LAUNA_EVM_PRIVATE_KEY | Buyer signer (export from sandbox-wallet get) |
Limitations
- Only the sandbox fork network — no Hedera / Base Sepolia / mainnet accepts on development APIs
- Fork balances reset when chain-dev restarts
- Shared staging Anvil is multi-tenant — do not rely on sticky balances
- Batch-settlement is not supported on the sandbox fork helpers (
exactonly) - Sandbox wallet is JWT-only (not available via MCP API keys)