LayerHopDocsLaunch app (opens in a new tab)

Test mode

Build and test a full LayerHop integration without real funds. lh_test_ keys return sandbox quotes and placeholder transactions, run a simulated order lifecycle in about 10 seconds, send webhooks to test endpoints, and use magic amounts for refunds, failures and no-route errors.

Test mode lets you build and test a complete LayerHop integration, from quote to filled order and webhook, without real funds or a real wallet. Requests made with an lh_test_… key are served by the sandbox provider: quotes are realistic (priced from live USD prices), built transactions are placeholders that cannot move funds, and reported orders run a simulated lifecycle that fills in about 10 seconds. Test data is kept apart from live data everywhere, and it never counts towards volume or partner earnings.

Get a test key#

  1. Sign in to dashboard.layerhop.com and turn on Test mode (the switch in the header).
  2. Open API keys and choose Create API key. In test mode the key starts with lh_test_.
  3. Send it as x-api-key, exactly like a live key. Nothing else in your code changes.

You can also try every step below in the API playground: paste your test key and follow the Next step buttons.

Live key lh_live_… Test key lh_test_…
Quotes Every real provider The sandbox provider only, priced from live USD prices
POST /v1/orders The real transaction for the user's wallet A placeholder with tx.sandbox: true that moves no funds
POST /v1/simulate Simulated on chain Always ok: true, nothing is sent to an RPC
Order tracking Provider status and chain receipts Simulated: pending, then the outcome about 10 s later
Webhooks Live endpoints Test endpoints only
Dashboard data Live view Test view (toggle Test mode)
Volume and partner earnings Counted Never counted
Rate limit 120 requests/minute by default Same

Run the whole flow#

Terminal
export LAYERHOP_API_KEY=lh_test_…   # your test key

# 1. Quote: the sandbox answers, quoteId starts with q_test_
curl -H "x-api-key: $LAYERHOP_API_KEY" \
  "https://api.layerhop.com/v1/quote?srcChain=base&srcToken=USDC&amount=100000000&dstChain=solana&dstToken=SOL&sender=0x1111111111111111111111111111111111111111&recipient=9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"

# 2. Build: returns a placeholder transaction (tx.sandbox: true)
curl -X POST -H "x-api-key: $LAYERHOP_API_KEY" -H "content-type: application/json" \
  -d '{"quoteId":"q_test_…"}' https://api.layerhop.com/v1/orders

# 3. Report any well-formed transaction hash (0x + 64 hex characters for EVM chains)
curl -X POST -H "x-api-key: $LAYERHOP_API_KEY" -H "content-type: application/json" \
  -d '{"txHash":"0x1111111111111111111111111111111111111111111111111111111111111111"}' \
  https://api.layerhop.com/v1/orders/q_test_…/submitted

# 4. Track it: pending at first, filled about 10 seconds later
curl -H "x-api-key: $LAYERHOP_API_KEY" \
  https://api.layerhop.com/v1/orders/sandbox/0x1111111111111111111111111111111111111111111111111111111111111111

With the TypeScript SDK the only change is the key:

TypeScript
import { LayerHop } from "@layerhop/sdk";

const lh = new LayerHop({ apiKey: process.env.LAYERHOP_API_KEY }); // lh_test_…
const { best } = await lh.quote({
  srcChain: "base", srcToken: "USDC", amount: "100000000",
  dstChain: "solana", dstToken: "SOL",
  sender: "0x1111111111111111111111111111111111111111",
  recipient: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
});
const tx = await lh.buildTx(best.quoteId); // tx.sandbox === true: do not send it
await lh.reportSubmitted(best.quoteId, "0x" + "11".repeat(32));

Magic amounts#

The last digits of amount choose how a test order ends, so you can exercise every path of your integration:

amount ends in Result
4001 The order is refunded (order.refunded webhook)
4002 The order fails (order.failed webhook)
4004 GET /v1/quote returns NO_ROUTE
anything else The order is filled (order.filled webhook) with a generated destination transaction hash

For example amount=100004001 quotes 100.004001 USDC and ends in a refund.

Order lifecycle and webhooks#

After you report a hash, LayerHop's worker picks up the test order like any other:

  1. First check, within a few seconds: submitted → pending, and an order.submitted webhook.
  2. Second check, about 10 seconds later: pending → filled, refunded or failed, and the matching webhook.

Test-mode events are sent only to webhook endpoints created in test mode, and live events only to live endpoints, so a test order can never reach your production handler. Every event carries livemode (false for test events), and so does every order:

JSON
{
  "id": "evt_…",
  "type": "order.filled",
  "livemode": false,
  "createdAt": "2026-10-18T10:00:12.000Z",
  "data": { "order": { "quoteId": "q_test_…", "status": "filled", "provider": "sandbox", "livemode": false } }
}

Create a test endpoint with Test mode on in the dashboard (or POST /v1/dashboard/webhooks with "mode": "test"), then verify signatures exactly as in Webhooks.

Keeping test and live apart#

  • A quote, order or webhook endpoint of one mode is invisible to the other. Building a q_test_ quote with a live key (or no key) returns QUOTE_NOT_FOUND, and the reverse too.
  • GET /v1/orders?address= lists test orders for test keys and live orders for live keys or no key.
  • The dashboard's overview, usage, logs, orders, webhooks and earnings take ?mode=test (the Test mode switch sets it).
  • Placeholder transactions are safe by construction: EVM placeholders send 0 to 0x000000000000000000000000000000000000dEaD with a short memo as calldata; Solana placeholders use an all-zero blockhash that no cluster accepts. Still, never broadcast them.

Going live#

Swap lh_test_ for an lh_live_ key. The responses have the same shape (with livemode: true), so code that passes in test mode works unchanged. Remember to create live webhook endpoints too, since test endpoints never receive live events.

Last updated