@lit-protocol/lit-venues 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,63 +1,32 @@
1
1
  # @lit-protocol/lit-venues
2
2
 
3
- Native exchange venue connectors for Lit Actions / Flows. Binance (+ binance.us),
4
- Coinbase Advanced Trade, and Hyperliquid perps behind one small, auditable,
5
- ccxt-shaped interface.
6
-
7
- > Ported from the Chipotle/lit-node-express runtime repo, where it was developed
8
- > against the Lit Action sandbox. The connectors are **plain library code** — not
9
- > a runtime primitive — so they live as a standalone package here in flows. The
10
- > two TEE-side primitives they can use (`Lit.Actions.proxiedFetch` for egress and
11
- > the email-approval ops) are part of the **Chipotle runtime**, not this package;
12
- > a flow reaches them by executing on a Chipotle environment that has them.
3
+ Native exchange venue connectors for Lit Actions. Binance (+ binance.us), Coinbase Advanced Trade, and Hyperliquid perps behind one small, auditable, ccxt-shaped interface. Part of `plans/ccxt-venue-layer-and-email-approval.md` (D1, D8).
13
4
 
14
5
  Design constraints, by construction:
15
6
 
16
- - **Action-runtime portable.** Only `fetch` + plain JS — no Node built-ins, no
17
- `Buffer`, no WebCrypto assumptions. Request signing (HMAC-SHA256, Ed25519,
18
- ES256 JWT, EIP-712/secp256k1) uses bundled `@noble/hashes` + `@noble/curves`.
19
- - **Quota-aware.** Single-symbol market lookups (`fetchMarket`), no full-market
20
- loads, small responses — designed for the 50-outbound-fetch / 1MB-response
21
- action limits. Pre-fetched rules can be injected via `markets` to spend zero
22
- fetches.
23
- - **Exact decimals.** Amounts/prices are decimal strings end to end; `addDec` /
24
- `subDec` / `roundDownToIncrement` do scaled-BigInt math. Hyperliquid's signed
25
- action hash commits to `wireDecimal`-normalized strings — no float ever
26
- touches an order.
27
- - **Egress-ready.** A `proxy` config routes through the in-TEE
28
- `Lit.Actions.proxiedFetch` op (provided by the Chipotle runtime); inert
29
- elsewhere.
30
-
31
- ## Build
32
-
33
- This package builds to an IIFE bundle (for inlining into a flow's action code,
34
- which runs as a plain script, not a module) and an ESM build:
35
-
36
- ```sh
37
- npm install
38
- npm test # vitest — signing pinned to Binance docs / RFC 8032 / Hyperliquid SDK vectors
39
- npm run build # esbuild → dist/lit-venues.iife.js (+ .mjs), prints size + sha384
40
- npm run typecheck
41
- ```
7
+ - **Action-runtime portable.** Only `fetch` + plain JS — no Node built-ins, no `Buffer`, no WebCrypto assumptions. Request signing (HMAC-SHA256, Ed25519, ES256 JWT, EIP-712/secp256k1) uses bundled `@noble/hashes` + `@noble/curves`.
8
+ - **Quota-aware.** Single-symbol market lookups (`fetchMarket`), no full-market loads, small responses — designed for the 50-outbound-fetch / 1MB-response action limits. Pre-fetched rules can be injected via `markets` to spend zero fetches.
9
+ - **Exact decimals.** Amounts/prices are decimal strings end to end; `addDec` / `subDec` / `roundDownToIncrement` do scaled-BigInt math. Hyperliquid's signed action hash commits to `wireDecimal`-normalized strings — no float ever touches an order.
10
+ - **Egress-ready.** A `proxy` config routes through the in-TEE `Lit.Actions.proxiedFetch` op (plan D4/M2); inert elsewhere.
42
11
 
43
- ## Usage inside a flow's Lit Action (inline bundle, v0)
12
+ ## Usage inside a Lit Action (inline bundle, v0)
44
13
 
45
14
  ```js
46
- // dist/lit-venues.iife.js concatenated above the action → global `LitVenues`
15
+ // dist/lit-venues.iife.js pasted/concatenated above this line → global `LitVenues`
47
16
  async function main({ sealedCreds }) {
48
17
  const creds = JSON.parse(await Lit.Actions.Decrypt(sealedCreds)); // venue-credentials-v1
49
18
  const binance = LitVenues.createVenue({
50
19
  venueId: 'binance',
51
20
  sandbox: true, // spot testnet
52
21
  credentials: { apiKey: creds.apiKey, secret: creds.secret, keyType: creds.keyType },
53
- proxy: creds.egress?.proxyUrl, // routes via Lit.Actions.proxiedFetch; inert without it
22
+ proxy: creds.egress?.proxyUrl,
54
23
  });
55
- return { balances: await binance.fetchBalances() };
24
+ const balances = await binance.fetchBalances();
25
+ return { balances };
56
26
  }
57
27
  ```
58
28
 
59
- PKP-native venues need no sealed credential — the signing key is the
60
- action-bound TEE key:
29
+ PKP-native venues need no sealed credential at all — the signing key is the action-bound TEE key:
61
30
 
62
31
  ```js
63
32
  async function main() {
@@ -73,50 +42,33 @@ async function main() {
73
42
  }
74
43
  ```
75
44
 
76
- Once published, the ESM build (`dist/lit-venues.mjs`) becomes importable via an
77
- integrity-pinned CDN import path instead of inlining.
45
+ Once published, the ESM build (`dist/lit-venues.mjs`) becomes importable via the integrity-pinned jsDelivr import path instead of inlining.
46
+
47
+ ## Commands
48
+
49
+ ```sh
50
+ npm install
51
+ npm test # vitest — signing pinned to Binance docs / RFC 8032 / Hyperliquid SDK vectors
52
+ npm run build # esbuild → dist/lit-venues.iife.js (+ .mjs), prints size + sha384
53
+ npm run typecheck
54
+ node scripts/verify-live.mjs # live conformance against REAL exchange APIs (public always; authed when keys present)
55
+ ```
56
+
57
+ The M0 spike (`e2e/tests/api/lit-venues-spike.spec.ts`) executes the IIFE bundle inside a real Lit Action and fetches public tickers — including a Binance-testnet probe that doubles as the egress-geography measurement (HTTP 451 ⇒ US egress, route via proxy per plan D4).
78
58
 
79
59
  ## Surface
80
60
 
81
61
  `createVenue({ venueId, credentials?, sandbox?, proxy?, fetchImpl?, nowMs?, markets?, signFn?, slippageBps?, builder? })` → `VenueClient`:
82
62
  `fetchTicker`, `fetchMarket`, `fetchBalances`, `createOrder`, `cancelOrder`, `fetchOpenOrders`, `fetchMyTrades`,
83
- plus the optional perp surface: `fetchPositions`, `setLeverage`, `fetchFundingRate`.
63
+ plus the optional perp surface (plan D8): `fetchPositions`, `setLeverage`, `fetchFundingRate`.
84
64
 
85
- Errors are `VenueError` with a unified taxonomy: `auth`, `insufficient_funds`,
86
- `bad_symbol`, `rate_limited`, `venue_unavailable`, `invalid_request`, `unknown`
87
- (plus `httpStatus` / raw `venueCode`).
65
+ Errors are `VenueError` with a unified taxonomy: `auth`, `insufficient_funds`, `bad_symbol`, `rate_limited`, `venue_unavailable`, `invalid_request`, `unknown` (plus `httpStatus` / raw `venueCode`).
88
66
 
89
- Amounts/prices are **decimal strings** end to end — use `LitVenues.addDec` /
90
- `subDec` / `roundDownToIncrement`, never floats.
67
+ Venue notes:
91
68
 
92
- ## Venue notes
93
-
94
- - **binance** — `sandbox: true` → `testnet.binance.vision`. HMAC by default; set
95
- `keyType: 'ed25519'` for Binance's recommended self-generated keys (PKCS8 PEM,
96
- hex, or base64). binance.com geo-blocks US egress (451) — route through the
97
- egress proxy or use the spot testnet.
69
+ - **binance** — `sandbox: true` → `testnet.binance.vision`. HMAC by default; set `keyType: 'ed25519'` for Binance's recommended self-generated keys (PKCS8 PEM, hex, or base64 accepted). binance.com geo-blocks US egress (451) — the error message says so explicitly.
98
70
  - **binanceus** — separate venue id, no testnet.
99
- - **coinbase** — Advanced Trade with CDP keys (ES256 JWT; no Ed25519 here). PEMs
100
- accepted with real newlines OR the literal `\n` of the CDP JSON download. **No
101
- sandbox** — `sandbox: true` throws. Market BUY orders take `quoteAmount`.
102
- - **hyperliquid** PKP-native perps: no API key; every trading action is an
103
- EIP-712 signature (msgpack action hash → phantom agent, chainId 1337), pinned
104
- byte-for-byte to the official SDK's test vectors in
105
- `test/hyperliquid-signing.test.ts`. `sandbox: true` →
106
- `api.hyperliquid-testnet.xyz`. Reads work with `accountAddress` alone; trading
107
- needs `privateKey` (or a custom `signFn`). When the key is a registered agent
108
- (API wallet), reads auto-resolve its MASTER. `approveAgent()` lets a master key
109
- grant the PKP trade-only powers — agents structurally cannot withdraw.
110
- `fetchBalances` merges perp equity + spot USDC. Market orders are aggressive IOC
111
- limits (`slippageBps`, default 5%) obeying 5-sig-fig / szDecimals rules.
112
-
113
- Withdrawal endpoints are deliberately absent (policy-gated sweeps go through the
114
- Chipotle runtime's email-approval primitive).
115
-
116
- ## What stayed in the Chipotle runtime repo
117
-
118
- - `op_lit_proxied_fetch` (`Lit.Actions.proxiedFetch`) — in-TEE egress op (Rust).
119
- - The email-approval ops + `ApprovalService` (in-TEE attestation verify).
120
- - The chipotle SDK quickstart examples (CID-pinned PKP setup scripts).
121
-
122
- Those are runtime/SDK concerns, not portable library code, so they remain there.
71
+ - **coinbase** — Advanced Trade with CDP keys (ES256 JWT; Coinbase does not support Ed25519 here). PEMs are accepted with real newlines OR the literal `\n` sequences of the CDP JSON download. No sandbox exists; `sandbox: true` throws rather than pretending. Market BUY orders take `quoteAmount` (quote-asset size) per Advanced Trade semantics.
72
+ - **hyperliquid** — PKP-native perps (plan D8): no API key; every trading action is an EIP-712 signature (msgpack action hash → phantom agent, chainId 1337), pinned byte-for-byte to the official SDK's test vectors in `test/hyperliquid-signing.test.ts`. `sandbox: true` → `api.hyperliquid-testnet.xyz` (full lifecycle testable). Reads work with `accountAddress` alone; trading needs `privateKey` (or a custom `signFn`). When the key is a registered agent (API wallet), reads auto-resolve its MASTER via the venue's `userRole` lookup (cached) — agent accounts themselves are always empty. `approveAgent()` lets a master key grant the PKP trade-only powers — agents structurally cannot withdraw, transfer, or move USDC between balance classes. On **unified accounts** (the venue's newer default) perps margin directly from the unified/spot balance and manual spot↔perp transfers are disabled; on legacy split accounts the master moves margin via the user-signed `usdClassTransfer`. `fetchBalances` therefore merges both pools: one USDC row (perp equity + spot USDC, `free` preferring the venue's available-after-maintenance figure) plus any other spot coins; positions read the perp clearinghouse. Market orders are aggressive IOC limits (`slippageBps`, default 5%); prices obey the 5-sig-fig and `MAX_DECIMALS − szDecimals` rules (integers always valid); sizes are quantized to `szDecimals`. Optional `builder` attaches a builder-code fee to orders.
73
+
74
+ Withdrawal endpoints are deliberately absent (plan: policy-gated sweeps go through the email-approval primitive).
@@ -3693,6 +3693,7 @@ var LitVenues = (() => {
3693
3693
  const bytes = b64decode(s);
3694
3694
  if (bytes.length === 32) return bytes;
3695
3695
  if (bytes.length === 48 && findSeq(bytes, PKCS8_ED25519_PREFIX) === 0) return bytes.subarray(16);
3696
+ if (bytes.length === 64) return bytes.subarray(0, 32);
3696
3697
  throw new Error("lit-venues: unsupported Ed25519 private key format");
3697
3698
  }
3698
3699
  var EC_KEY_MARKER = [2, 1, 1, 4, 32];
@@ -3752,6 +3753,26 @@ var LitVenues = (() => {
3752
3753
  const sig = p2562.sign(sha2562(utf8ToBytes(signingInput)), priv);
3753
3754
  return `${signingInput}.${b64urlEncode(sig.toCompactRawBytes())}`;
3754
3755
  }
3756
+ function edDsaJwt(input) {
3757
+ const nowSec = Math.floor((input.nowMs ?? Date.now()) / 1e3);
3758
+ const header = {
3759
+ alg: "EdDSA",
3760
+ kid: input.keyName,
3761
+ nonce: input.nonce ?? randomHex(16),
3762
+ typ: "JWT"
3763
+ };
3764
+ const payload = {
3765
+ iss: "cdp",
3766
+ nbf: nowSec,
3767
+ exp: nowSec + (input.ttlSec ?? 120),
3768
+ sub: input.keyName,
3769
+ uri: input.uri
3770
+ };
3771
+ const signingInput = b64urlEncode(utf8ToBytes(JSON.stringify(header))) + "." + b64urlEncode(utf8ToBytes(JSON.stringify(payload)));
3772
+ const seed = parseEd25519PrivateKey(input.privateKey);
3773
+ const sig = ed25519.sign(utf8ToBytes(signingInput), seed);
3774
+ return `${signingInput}.${b64urlEncode(sig)}`;
3775
+ }
3755
3776
 
3756
3777
  // src/venues/binance.ts
3757
3778
  var BASES = {
@@ -4007,12 +4028,14 @@ var LitVenues = (() => {
4007
4028
  if (!creds?.apiKey || !creds.secret) {
4008
4029
  throw new VenueError(this.venueId, "auth", "this call requires credentials (CDP key name + private key)");
4009
4030
  }
4010
- headers.Authorization = `Bearer ${es256Jwt({
4031
+ const jwtInput = {
4011
4032
  keyName: creds.apiKey,
4012
4033
  privateKey: creds.secret,
4013
4034
  uri: `${method} ${HOST}${path}`,
4014
4035
  nowMs: this.now()
4015
- })}`;
4036
+ };
4037
+ const token = creds.keyType === "eddsa-jwt" ? edDsaJwt(jwtInput) : es256Jwt(jwtInput);
4038
+ headers.Authorization = `Bearer ${token}`;
4016
4039
  }
4017
4040
  let body;
4018
4041
  if (opts.body !== void 0) {
@@ -3628,6 +3628,7 @@ function parseEd25519PrivateKey(input) {
3628
3628
  const bytes = b64decode(s);
3629
3629
  if (bytes.length === 32) return bytes;
3630
3630
  if (bytes.length === 48 && findSeq(bytes, PKCS8_ED25519_PREFIX) === 0) return bytes.subarray(16);
3631
+ if (bytes.length === 64) return bytes.subarray(0, 32);
3631
3632
  throw new Error("lit-venues: unsupported Ed25519 private key format");
3632
3633
  }
3633
3634
  var EC_KEY_MARKER = [2, 1, 1, 4, 32];
@@ -3687,6 +3688,26 @@ function es256Jwt(input) {
3687
3688
  const sig = p2562.sign(sha2562(utf8ToBytes(signingInput)), priv);
3688
3689
  return `${signingInput}.${b64urlEncode(sig.toCompactRawBytes())}`;
3689
3690
  }
3691
+ function edDsaJwt(input) {
3692
+ const nowSec = Math.floor((input.nowMs ?? Date.now()) / 1e3);
3693
+ const header = {
3694
+ alg: "EdDSA",
3695
+ kid: input.keyName,
3696
+ nonce: input.nonce ?? randomHex(16),
3697
+ typ: "JWT"
3698
+ };
3699
+ const payload = {
3700
+ iss: "cdp",
3701
+ nbf: nowSec,
3702
+ exp: nowSec + (input.ttlSec ?? 120),
3703
+ sub: input.keyName,
3704
+ uri: input.uri
3705
+ };
3706
+ const signingInput = b64urlEncode(utf8ToBytes(JSON.stringify(header))) + "." + b64urlEncode(utf8ToBytes(JSON.stringify(payload)));
3707
+ const seed = parseEd25519PrivateKey(input.privateKey);
3708
+ const sig = ed25519.sign(utf8ToBytes(signingInput), seed);
3709
+ return `${signingInput}.${b64urlEncode(sig)}`;
3710
+ }
3690
3711
 
3691
3712
  // src/venues/binance.ts
3692
3713
  var BASES = {
@@ -3942,12 +3963,14 @@ var CoinbaseClient = class {
3942
3963
  if (!creds?.apiKey || !creds.secret) {
3943
3964
  throw new VenueError(this.venueId, "auth", "this call requires credentials (CDP key name + private key)");
3944
3965
  }
3945
- headers.Authorization = `Bearer ${es256Jwt({
3966
+ const jwtInput = {
3946
3967
  keyName: creds.apiKey,
3947
3968
  privateKey: creds.secret,
3948
3969
  uri: `${method} ${HOST}${path}`,
3949
3970
  nowMs: this.now()
3950
- })}`;
3971
+ };
3972
+ const token = creds.keyType === "eddsa-jwt" ? edDsaJwt(jwtInput) : es256Jwt(jwtInput);
3973
+ headers.Authorization = `Bearer ${token}`;
3951
3974
  }
3952
3975
  let body;
3953
3976
  if (opts.body !== void 0) {
package/package.json CHANGED
@@ -1,45 +1,24 @@
1
1
  {
2
2
  "name": "@lit-protocol/lit-venues",
3
- "version": "0.2.0",
4
- "description": "Native exchange venue connectors for Lit Actions (Binance, Coinbase, Hyperliquid). REST-only, fetch-based, noble crypto inlined — no Node built-ins.",
3
+ "version": "0.2.1",
4
+ "description": "Native exchange venue connectors for Lit Actions (Binance, Coinbase). REST-only, fetch-based, noble crypto inlined — no Node built-ins.",
5
5
  "type": "module",
6
6
  "main": "./dist/lit-venues.mjs",
7
+ "files": ["dist"],
8
+ "publishConfig": { "access": "public" },
7
9
  "scripts": {
8
10
  "build": "node scripts/bundle.mjs",
9
11
  "test": "vitest run",
10
12
  "typecheck": "tsc --noEmit"
11
13
  },
12
- "keywords": [
13
- "lit-protocol",
14
- "flows",
15
- "venues",
16
- "binance",
17
- "coinbase",
18
- "hyperliquid",
19
- "ccxt"
20
- ],
21
- "license": "MIT",
22
- "repository": {
23
- "type": "git",
24
- "url": "https://github.com/LIT-Protocol/flows"
25
- },
26
- "engines": {
27
- "node": ">=20"
28
- },
29
- "publishConfig": {
30
- "access": "public"
31
- },
32
- "files": [
33
- "dist"
34
- ],
35
14
  "dependencies": {
36
15
  "@noble/curves": "^1.9.0",
37
16
  "@noble/hashes": "^1.8.0"
38
17
  },
39
18
  "devDependencies": {
40
- "@types/node": "^22.0.0",
19
+ "@types/node": "^25.9.3",
41
20
  "esbuild": "^0.25.0",
42
- "typescript": "^5.7.0",
21
+ "typescript": "^5.6.3",
43
22
  "undici": "^8.4.1",
44
23
  "vitest": "^3.0.0"
45
24
  }