Local development & CI
Build and test a LayerHop integration offline with layerhop dev, a local mock API with deterministic outcomes and signed webhooks, optional Anvil forks, the setup-layerhop GitHub Action and a Postman collection.
layerhop dev runs a mock LayerHop API on your machine, so you can build and test an integration with no account, no API key, no network and no funds. It answers the core routes with the same JSON shapes as https://api.layerhop.com, which means the SDKs, the CLI and your own HTTP code work against it unchanged. In CI, the setup-layerhop GitHub Action starts it for you. For a hosted sandbox with your dashboard, webhooks endpoints and logs, use test mode instead.
How do I start the mock API?#
npx @layerhop/cli dev
# LayerHop mock API on http://127.0.0.1:8787
# export LAYERHOP_API_URL=http://127.0.0.1:8787Point your code at it:
import { LayerHop } from "@layerhop/sdk";
const lh = new LayerHop({ baseUrl: process.env.LAYERHOP_API_URL ?? "https://api.layerhop.com" });
const { best } = await lh.quote({
srcChain: "base", srcToken: "USDC", amount: "100000000",
dstChain: "solana", dstToken: "SOL",
sender: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
recipient: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
});
const tx = await lh.buildTx(best.quoteId); // a placeholder: never broadcast it
await lh.reportSubmitted(best.quoteId, `0x${"ab".repeat(32)}`);
for await (const s of lh.watch("mock", `0x${"ab".repeat(32)}`, { intervalMs: 500 })) console.log(s.state);The Python, Go and Rust SDKs take the same base URL, and every CLI command honours LAYERHOP_API_URL. The server allows any origin (CORS), so a local front end can call it from the browser.
| Option | Default | What it does |
|---|---|---|
--port <n> |
8787 |
Port to listen on (env LAYERHOP_DEV_PORT). |
--host <addr> |
127.0.0.1 |
Interface to bind. Use 0.0.0.0 inside Docker. |
--fill-after <s> |
5 |
Seconds between reporting a transaction and its final state. |
--webhook-url <url> |
POST signed order.* webhooks to this URL. |
|
--secret <whsec_…> |
random | Webhook signing secret (env LAYERHOP_WEBHOOK_SECRET). Printed at start when generated. |
--fork, --fork-chains <a,b> |
base,arbitrum |
Also start Anvil forks (below). |
--json |
Print one JSON line (url, webhookSecret, forks) instead of text, and no request log. |
What does the mock API cover?#
| Route | Behaviour |
|---|---|
GET /v1/health, GET /v1/status |
Always healthy; providers: ["mock"]. |
GET /v1/chains, GET /v1/tokens, GET /v1/prices |
The built-in chain and token lists, with fixed prices (USDC/USDT $1, ETH $3,000, SOL $150, BNB $600; others $1). |
GET /v1/quote |
One mock quote, priced at the fixed prices minus the LayerHop fee of the channel (20 bps on api). Quote ids are sequential (q_mock_1, …) and valid for 60 seconds. |
POST /v1/orders, POST /v1/simulate |
A placeholder transaction (sandbox: true). EVM: a call to 0x…dEaD with an approval for ERC-20 inputs. Solana: a memo transaction with an all-zero blockhash that no cluster accepts. |
POST /v1/orders/{quoteId}/submitted, POST /v1/orders/submit |
Records the order (any well-formed hash) and starts its lifecycle. |
GET /v1/orders/{provider}/{id}, GET /v1/orders |
pending until --fill-after has passed, then the final state, with a dstTxHash when filled. |
POST /v1/webhooks/trigger |
Sends a sample event of any type to --webhook-url. |
Anything else (checkout, invoices, subscriptions, deposit addresses, limit orders, the dashboard) returns NOT_FOUND with a pointer to test mode, which covers every endpoint. Errors use the real shape, { "error": { code, message, requestId, docsUrl } }.
How do I test refunds and failures?#
The last digits of amount pick the outcome, exactly like test mode's magic amounts:
amount ends in |
Outcome |
|---|---|
4001 |
The order is refunded. |
4002 |
The order is failed, and POST /v1/simulate returns ok: false. |
4004 |
GET /v1/quote returns NO_ROUTE (404). |
| anything else | The order is filled. |
How do I receive webhooks locally?#
npx @layerhop/cli dev --webhook-url http://localhost:3000/api/layerhop-webhooks --secret whsec_dev_only_secretEach reported order sends order.submitted and then order.filled, order.refunded or order.failed, signed with the LayerHop-Signature header exactly like real deliveries, so your verification code runs unchanged (see Webhooks). POST /v1/webhooks/trigger with {"type":"order.filled"} sends a sample on demand.
How do I use it in GitHub Actions?#
The layerhop/setup-layerhop action starts the mock API in the background, waits until it answers, and exports LAYERHOP_API_URL (and LAYERHOP_WEBHOOK_SECRET when you pass a webhook URL) for the following steps:
name: Integration tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: layerhop/setup-layerhop@v1
with:
fill-after: "1" # orders finish in 1 second
webhook-url: http://localhost:3000/api/layerhop-webhooks # optional
- run: npm ci
- run: npm test # reads process.env.LAYERHOP_API_URLlayerhop dev ships in the first @layerhop/cli release after 0.6.0, and the action is published as layerhop/setup-layerhop with it.
Inputs: port (8787), cli-version (latest), fill-after (1), webhook-url, webhook-secret (random when empty), fork and fork-chains, timeout (60). Outputs: api-url, webhook-secret. Without the action, any CI can run npx @layerhop/cli dev --port 8787 & and poll /v1/health.
Can I test against forked mainnet state?#
Yes, for EVM chains. With Foundry installed (curl -L https://foundry.paradigm.xyz | bash && foundryup), --fork starts one Anvil fork per chain next to the mock API:
npx @layerhop/cli dev --fork --fork-chains base,arbitrum
# Anvil fork of base (chain id 8453) on http://127.0.0.1:8545
# Anvil fork of arbitrum (chain id 42161) on http://127.0.0.1:8546Each fork keeps the real chain id and copies mainnet state on demand, and Anvil gives you prefunded test accounts. Use the forks to exercise wallet connection, signing and token-approval code against real token contracts without spending anything. GET /v1/health lists the running forks. The mock API's transactions stay placeholders: forks don't make provider routes executable locally, because the bridges and solvers behind them only run on mainnet.
Forks read state through public RPCs (rate limited). Set your own per chain with LAYERHOP_FORK_RPC_<CHAIN>, for example LAYERHOP_FORK_RPC_BASE=https://base-mainnet.example/your-key. Without Anvil, --fork prints how to install it and starts the mock API alone.
Can I import the API into Postman, Insomnia or Bruno?#
Yes. The Postman collection is generated from the OpenAPI spec with a folder per API area, example requests and example responses. In Postman choose Import → Link and paste https://docs.layerhop.com/layerhop.postman_collection.json; Insomnia and Bruno import the same file. Set the collection variables apiKey (an lh_test_ key) and baseUrl (https://api.layerhop.com, or http://127.0.0.1:8787 for layerhop dev). Code generators can use the spec itself: openapi.yaml or openapi.json.
What's not included yet?#
- A local Solana validator.
--forkcovers EVM chains only. Solana flows run against the mock API's placeholder transactions or test mode. - A testnet faucet. LayerHop's providers are mainnet-only, so there is no LayerHop testnet to fund. A faucet would also need wallets funded by LayerHop, which is an owner decision that hasn't been made. Use test mode or
layerhop devinstead: neither needs funds.
Last updated