Skip to main content

mock-server

Talk to the DMK device mock server (apps/device-mock-server, default http://127.0.0.1:9752) with curl. Make fake devices, connect them, add APDU mocks, send APDUs, break commands on purpose. ALWAYS read this skill when the user says "mock server", makes/changes a mock device, adds mocks, forces an APDU error, or pokes a device Ledger Live/DMK is on.

Source facts

Repository
LedgerHQ/device-sdk-ts
Last source activity
August 28, 2026 at 14:03
Detected SKILL.md language
English
Stars
48
Forks
38

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
mock-server
description
Talk to the DMK device mock server (apps/device-mock-server, default http://127.0.0.1:9752) with curl. Make fake devices, connect them, add APDU mocks, send APDUs, break commands on purpose. ALWAYS read this skill when the user says "mock server", makes/changes a mock device, adds mocks, forces an APDU error, or pokes a device Ledger Live/DMK is on.
# Mock server Fake Ledger devices over REST. You do curl. Server hands back JSON. Big book of every route: `apps/device-mock-server/openapi.yaml`. Read it when this file is not enough. ## Rules to remember - URL is `http://127.0.0.1:9752`. - Every call needs `Authorization: Bearer <token>`. Only `POST /auth` and `GET /health` go free. - Get token: `POST /auth`. Or steal the one Ledger Live/DMK uses. - Curl to `127.0.0.1` needs `required_permissions: ["all"]`. - Server forgets everything on restart. No disk. Set once per shell: ```bash BASE=http://127.0.0.1:9752 TOKEN=<bearer-token> AUTH="Authorization: Bearer $TOKEN" JSON="Content-Type: application/json" ``` ## Client device (Ledger Live / DMK) Client makes its own device when it connects. To touch it: - Ask user for the FULL token. Logs show only first 8 letters. - Find device with `GET /devices`. Change it with `PATCH`. - NEVER delete then remake it. New id = client lost forever. ## The curl commands ```bash # Token + health curl -s -X POST $BASE/auth # -> { token, expires_at } curl -s $BASE/health # Devices curl -s $BASE/devices -H "$AUTH" curl -s $BASE/devices/<id> -H "$AUTH" curl -s -X POST $BASE/devices -H "$AUTH" -H "$JSON" \ -d '{"name":"Ledger Flex","device_type":"flex","firmware_version":"1.9.1"}' curl -s -X PATCH $BASE/devices/<id> -H "$AUTH" -H "$JSON" \ -d '{"firmware_version":"1.10.0"}' curl -s -X DELETE $BASE/devices/<id> -H "$AUTH" # Plug / unplug curl -s -X POST $BASE/devices/<id>/connect -H "$AUTH" # or /disconnect # Send APDU curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" -d '{"apdu":"e001000000"}' # Mocks curl -s $BASE/devices/<id>/mocks -H "$AUTH" curl -s -X POST $BASE/devices/<id>/mocks -H "$AUTH" -H "$JSON" -d '{"prefix":"b001","response":"5515"}' curl -s -X PATCH $BASE/devices/<id>/mocks/<mockId> -H "$AUTH" -H "$JSON" -d '{"prefix":"b001","response":"9000"}' curl -s -X DELETE $BASE/devices/<id>/mocks/<mockId> -H "$AUTH" curl -s -X DELETE $BASE/devices/<id>/mocks -H "$AUTH" # kill all # Save / load session curl -s $BASE/export -H "$AUTH" curl -s -X POST $BASE/import -H "$AUTH" -H "$JSON" -d '<SessionExport json>' # Speculos seed (⚠️ plaintext — test mnemonics only, never real keys) curl -s -X PUT $BASE/sessions/current/seed -H "$AUTH" -H "$JSON" \ -d '{"seed":"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"}' ``` `device_type`: `nanoS`, `nanoSP`, `nanoX`, `stax`, `flex`, `apexp`. `PATCH` keeps the id and only changes fields you send. ## Open App / Close App Format: `e0 d8 00 00 <len> <app-name-ascii-hex>` — Close: `b0 a7 00 00 00`. ```bash # Open Bitcoin (7 chars → 07, "Bitcoin" = 426974636f696e) curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" \ -d '{"apdu":"e0d8000007426974636f696e"}' # Open Ethereum (8 chars → 08, "Ethereum" = 457468657265756d) curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" \ -d '{"apdu":"e0d8000008457468657265756d"}' # Close App (any app) curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" \ -d '{"apdu":"b0a7000000"}' ``` All three return `{"response":"9000"}` on success. Open App returns `6807` if the app is not in the device's `apps` list (add it via `POST /devices` or `PATCH /devices/<id>`). Build the hex for any app on the fly: ```bash app="Solana" hex=$(printf '%s' "$app" | xxd -p | tr -d '\n') len=$(printf '%02x' ${#app}) curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" \ -d "{\"apdu\":\"e0d800${len}${hex}\"}" ``` ## Mocks: how they work - Mock catches any APDU that STARTS WITH `prefix`. - One `response`, or a list `responses` (used one by one, then loops). - Longest prefix wins. Newest wins ties. Mock beats the server's own answer. - Answer = `<data><status>`. `9000` = ok. `6d00` = no/unknown. `5515` = locked. ```bash # Say device is locked: curl -s -X POST $BASE/devices/<id>/mocks -H "$AUTH" -H "$JSON" -d '{"prefix":"b001","response":"5515"}' # Break a command on purpose: curl -s -X POST $BASE/devices/<id>/mocks -H "$AUTH" -H "$JSON" -d '{"prefix":"e00300","response":"6d00"}' # Ok, ok, then fail, on repeat: curl -s -X POST $BASE/devices/<id>/mocks -H "$AUTH" -H "$JSON" \ -d '{"prefix":"e0aa0000","responses":["cafe9000","cafe9000","6d00"]}' ``` Add mock = new behavior. Delete mock = old behavior back. ## Speculos seed Each session starts with the default test mnemonic. Override it per-session before opening an app (the seed is forwarded to Speculinho on every `/acquire`): ```bash curl -s -X PUT $BASE/sessions/current/seed -H "$AUTH" -H "$JSON" \ -d '{"seed":"glory promote mansion idle axis finger extend february uncover one trip resolve toe"}' ``` ⚠️ **Not secure.** Stored in plaintext in server memory and sent over plain HTTP. Use only test/dummy mnemonics — never a real production key. ## Onboarding Make a fresh, not-onboarded device and walk it through Ledger Live's SyncOnboarding steps. Ask for `onboarded: false` at create time (omit it, or `true`, for a normal onboarded device): ```bash curl -s -X POST $BASE/devices -H "$AUTH" -H "$JSON" \ -d '{"name":"Ledger Nano X","device_type":"nanoX","firmware_version":"1.3.0","onboarded":false}' ``` State lives in the GetOsVersion (`e001000000`) answer's `seFlags`: byte 0 has the onboarded bit, byte 3 the step. Poll `e001000000` to read it and to move the flow along. - Starts at WELCOME (step `00`), not onboarded. Dwells here until you send `e0030000` (enter early check). - Now at EARLY_CHECK (step `0f`). Dwells here until you send `e0030001` (exit early check). - After exit, each `e001000000` poll auto-advances one step: ChooseName `0c` -> Pin `06` -> SetupChoice `05` -> NewDevice `07` -> NewDeviceConfirming `08` -> SafetyWarning `0a` -> Ready `0b`. - At Ready, byte 0 flips to `e6` (onboarded set) and `GET /devices/<id>` now says `onboarded: true`. ```bash # Walk it: enter, exit, then poll until Ready. curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" -d '{"apdu":"e0030000"}' # enter early check curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" -d '{"apdu":"e0030001"}' # exit early check curl -s -X POST $BASE/devices/<id>/apdu -H "$AUTH" -H "$JSON" -d '{"apdu":"e001000000"}' # poll (repeat to advance) ``` ## Newest firmware Server has no "latest" list. Read the real version from Ledger, then `PATCH`: - Page: https://support.ledger.com/article/7103926130845-zd (Stax, Flex, Nano Gen5, Nano X, Nano S Plus). - Read the page fresh each time. Numbers change. ## Touch the screen (Speculos) Some APDUs wait for a screen tap (e.g. Web3 Check `e032010000` on Ethereum). No tap = hang until timeout (`6d00`). Tap while APDU is flying: ```bash # Stax 400x672: accept = x=200,y=537 ; reject = x=200,y=604 curl -s -X POST $BASE/devices/<id>/speculos/finger -H "$AUTH" -H "$JSON" \ -d '{"action":"press-and-release","x":200,"y":537}' ```
View on GitHub