Skip to content

feat: move the Lighter signer to web-wasm (ORD-2040) - #617

Open
aaronmboyd wants to merge 4 commits into
mainfrom
feature/ord-2040
Open

aaronmboyd wants to merge 4 commits into
mainfrom
feature/ord-2040

Conversation

@aaronmboyd

@aaronmboyd aaronmboyd commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Closes ORD-2040
Linear: https://linear.app/lifi-linear/issue/ORD-2040

Intent

This PR replaces the vendored Lighter signer binary with a reproducible build of lighter-go web-wasm/ at 9d38261 (v1.0.10). LighterSigner moves to the web-wasm call shapes, so cancel and modify sign an order_index above 2^53 exactly.

What changed

  • wasm/lighter-signer.wasm: a clean build of lighter-go web-wasm/ at 9d38261 (approximately 7.5 MB, sha256 ad5ca504bc97140a87423b0ef3a8a16fd0497081c88383b36d9552545a996826). The toolchain is go1.25.6, the same as Lighter's own build, in golang:1.25.6-bookworm@sha256:f4490d7b261d73af4543c46ac6597d7d101b6e1755bcdd8c5159fda7046b6b3e. The build uses the committed tree only and -trimpath -buildvcs=false -ldflags="-s -w", so the binary holds no build paths or git state.
  • wasm/wasm_exec.js: stock go1.25.6 (sha256 0c949f4996f9a89698e4b5c586de32249c3b69b7baadb64d220073cc04acba14), to match the toolchain of the binary. src/signers/generated/wasmExecRuntime.ts is regenerated from it.
  • src/signers/wasmLoader.ts:
    • LighterWasmExports types the 18 _ exports.
    • New result types: LighterWasmTx, LighterWasmClient, LighterWasmL1Body, LighterWasmAuthToken, LighterWasmError and LighterWasmPending.
    • callLighterWasm resolves both return shapes (a function that returns a Promise, or a Promise) and turns { error } into a PerpsError.
    • The loader deletes the exports that the SDK does not call from globalThis.
  • src/signers/LighterSigner.ts:
    • TX_TYPE_BY_ACTION gives the tx type.
    • int64StringField reads order_index and the other int64 arguments.
    • createSignerSeed makes a 32-byte seed from globalThis.crypto.getRandomValues.
    • createApiKey calls _createClient and stores 0x + prv.
    • ensureClient keys the per-account WASM client by chain id, api key index, skip-nonce and private key. It creates the client again when one of these changes.
    • A promise queue for each WASM instance serializes the calls, because Go reads the per-account client in the outer call and again in its goroutine.
    • dispatch validates parameters before it loads WASM and returns a deferred call.
    • L1 actions use getL1Message and then signL1Countersigned. embedL1Signature is deleted.
    • createAuthToken takes no deadline.
  • src/signers/signActions.ts, src/signers/createAuthToken.ts: the two-step L1 flow, and no lifetimeSeconds.
  • src/LighterProvider.ts: the tokenLifetimeSeconds option and the signer apiUrl are removed. The standard-token cache expiry uses a 1h constant that matches the Go deadline.
  • src/types/action.ts: order_index is string | number on cancel and modify.
  • src/index.ts: exports the new WASM result types.
  • scripts/verify-package-assets.mjs: the minimum binary size is 5 MB, because the new binary is approximately 7.5 MB.
  • scripts/verify-consumer-bundlers.mjs, test/consumer/probe.js: the probe uses the new export names. The Node targets parse the last stdout line, because the Go runtime writes "Webassembly Started" before the probe result.

Assumptions

  • Modify does not sign integrator fee, self-trade or order-version fields, because web-wasm SignModifyOrder takes none. The order keeps the integrator fee from create time.
  • The token for token-auth mutations (account tier change, referral use) now has the fixed 1h WASM deadline. Before, it had a 5-minute deadline. Lighter accepts tokens up to 8h, so this is in range.
  • fastwithdraw memo: the reader accepts 32 raw bytes (the backend form), or 32 bytes as 64 hex chars with or without 0x. Other values give a ValidationError.
  • order_index above 2^60-1 fails in Go. The SDK reader accepts the full int64 range, and the Go error surfaces as a PerpsError.
  • On start, the Go runtime writes one "Webassembly Started" line to the console in each consumer. The line comes from lighter-go web-wasm/main.go, and this PR does not change lighter-go.
  • Lighter's committed web-wasm/main.wasm (sha256 5372ac1d…bf6b) has the same source and toolchain, but it embeds the build paths and git state of a developer machine. This PR does not ship it. Two builds of the clean binary, one from a fresh clone with no Docker cache, give the same sha256. The build script stays outside this repo. The committed binary is the reviewed artifact.
  • The removal of unused exports from globalThis keeps the SDK surface to the 18 names that it calls.

Tests

New:

  • src/signers/LighterSigner.legacy.unit.spec.ts + test/legacySignerGolden.ts: for each signed action, the new binary gives the same txType, txHash and txInfo (without Sig) as golden output from the old wasm/ binary at a fixed time, key and nonce. The specs also check that the wallet messages for ChangePubKey, transfer and approve-integrator do not change, that an old-format key loads through _createClientByPrv with the same public key, and the auth token deadline. Cancel and modify with order index 9570149379440277 contain that exact index.

Edited:

  • src/signers/LighterSigner.unit.spec.ts:
    • New: createSignerSeed (32 bytes from getRandomValues, a different seed on each call).
    • New: per-account client reuse and recreation (another key, another api key index).
    • New: int64 order_index acceptance and rejection, memo forms, and getL1Message messages.
    • Edited: the signChangePubKey and createAuthToken specs use the new API.
    • Removed: the embedL1Signature specs.
  • src/signers/signActions.unit.spec.ts: the stubs use the two-step L1 flow. The ACCOUNT_TYPE token call has no lifetime.
  • src/signers/createAuthToken.unit.spec.ts: the two deadline cases become one case that checks the forwarded context.
  • src/signers/wasmLoader.unit.spec.ts: export names, both return shapes and { error } mapping.
  • src/signers/wasmExecRuntime.unit.spec.ts: binary size floor.
  • src/LighterProvider.unit.spec.ts: the fake WASM uses web-wasm shapes, and tokenLifetimeSeconds is removed from the option keys.

Gates: pnpm check, pnpm check:types, pnpm build, pnpm test:unit (lighter: 1279 tests pass), pnpm test:consumers (8 consumer toolchains), pnpm knip:check.

Follow-ups

  • ORD-2041 (already filed): lifi-perps-backend carries the Lighter order index as a decimal string. Until it lands, the SDK accepts both the number and the string form.

Dependencies / upstream branches

  • perps-sdk: feature/ord-2039 (ORD-2039, PR 616). This branch builds on it, so the diff shows its commit too. Merge PR 616 first.
  • The changeset is a major bump of @lifi/perps-sdk-provider-lighter. A human runs the release after merge.

@aaronmboyd aaronmboyd added release-preview Publish a 0.0.0-preview-<sha> build of this PR to npm Agent Review Request labels Oct 9, 2026
Comment thread packages/perps-sdk-provider-lighter/scripts/signer-wasm.Dockerfile Outdated
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

📦 Preview published under the preview dist-tag.

Install the exact version(s) — @preview moves with the newest preview across PRs:

npm i @lifi/perps-sdk-provider-hyperliquid@0.0.0-preview-47b7357
npm i @lifi/perps-sdk-provider-lighter@0.0.0-preview-47b7357
npm i @lifi/perps-sdk@0.0.0-preview-47b7357

@github-actions github-actions Bot removed the release-preview Publish a 0.0.0-preview-<sha> build of this PR to npm label Oct 9, 2026
@lifi-qa-agent

lifi-qa-agent Bot commented Oct 9, 2026

Copy link
Copy Markdown

Zeus QA Review — ORD-2040 · #617 · head 47b7357

Verdict: ✅ Approve — 0 Critical · 0 High · 0 Medium · 0 Low findings. Route: broad (score 100); focused direct pass of the signer, loader, signActions and build files (no dimension fan-out).

Summary

Replaces the vendored Lighter signer binary with a pinned web-wasm build of lighter-go (9d38261). LighterSigner moves to the web-wasm call shapes so cancel/modify sign an order_index above 2^53 exactly.

Verified

  • Build is reproducible by design: base image pinned by digest, lighter-go fetched at the full commit sha and HEAD verified, GOTOOLCHAIN=local, go mod verify, -trimpath -buildvcs=false; wasm_exec.js copied from the same GOROOT.
  • int64StringField accepts only safe-integer numbers or decimal strings inside the int64 range, so no precision is lost before Go; both cancel and modify use it.
  • Per-runtime promise queue serializes client changes and signing. The L1 flow (getL1Message → wallet signature → signL1Countersigned) re-runs ensureClient inside the queue, so a different client set while the wallet prompt is open is recreated before signing.
  • Client cache key covers chain id, api key index, skip-nonce and private key. createApiKey records the client it created.
  • validate-before-load in dispatch; { error } results map to PerpsError(SignatureInvalid); unused Go exports are removed from globalThis.
  • signActions two-step L1 flow and the skip_nonce boolean change are consistent with the signer. tokenLifetimeSeconds removal is disclosed and 1h constant matches the Go deadline.
  • Golden-output spec compares the new binary against old-binary output for each signed action.

🔵 Notes (not findings)

  • CI checks conclusion was empty at review time; local gates (check, types, build, 1279 unit tests, 8 consumer toolchains, knip) not re-run.
  • The 6.8 MB wasm binary was not rebuilt or byte-verified from source; reproducibility rests on the pinned Dockerfile.
  • Public-contract changes (removed tokenLifetimeSeconds, signer apiUrl, embedL1Signature) ship as a major changeset, as disclosed.
  • Stacked on PR 616 (ORD-2039): merge 616 first.
  • Modify not signing integrator fee fields (web-wasm limitation) and the "Webassembly Started" console line are author-disclosed.

@lifi-qa-agent lifi-qa-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zeus QA: approved — no gating findings. See the QA report comment.

Replace the local go1.23.2 Docker build with the upstream
web-wasm/main.wasm from lighter-go v1.0.10 (9d38261). A Docker
rebuild with go1.25.6 and the build paths from the binary gives
the same sha256 (5372ac1d...bf6b).

Update wasm_exec.js to stock go1.25.6 (sha256 0c949f49...a14) to
match the toolchain of the binary, and regenerate wasmExecRuntime.ts.

Remove signer-wasm.Dockerfile and build-signer-wasm.sh.
Replace Lighter's hand-built web-wasm/main.wasm with a clean build of
lighter-go v1.0.10 (9d38261): go1.25.6 in
golang:1.25.6-bookworm@sha256:f4490d7b..., from the committed tree, with
-trimpath -buildvcs=false -ldflags="-s -w". The binary holds no build
paths or git state. Two builds, one from a fresh clone with no cache,
give sha256 ad5ca504bc97140a87423b0ef3a8a16fd0497081c88383b36d9552545a996826.

wasm_exec.js stays stock go1.25.6.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant