@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 +32 -80
- package/dist/lit-venues.iife.js +25 -2
- package/dist/lit-venues.mjs +25 -2
- package/package.json +6 -27
package/README.md
CHANGED
|
@@ -1,63 +1,32 @@
|
|
|
1
1
|
# @lit-protocol/lit-venues
|
|
2
2
|
|
|
3
|
-
Native exchange venue connectors for Lit Actions
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
- **
|
|
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
|
|
12
|
+
## Usage inside a Lit Action (inline bundle, v0)
|
|
44
13
|
|
|
45
14
|
```js
|
|
46
|
-
// dist/lit-venues.iife.js concatenated above
|
|
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,
|
|
22
|
+
proxy: creds.egress?.proxyUrl,
|
|
54
23
|
});
|
|
55
|
-
|
|
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
|
|
77
|
-
|
|
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
|
-
|
|
90
|
-
`subDec` / `roundDownToIncrement`, never floats.
|
|
67
|
+
Venue notes:
|
|
91
68
|
|
|
92
|
-
|
|
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;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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).
|
package/dist/lit-venues.iife.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|
package/dist/lit-venues.mjs
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
4
|
-
"description": "Native exchange venue connectors for Lit Actions (Binance, Coinbase
|
|
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": "^
|
|
19
|
+
"@types/node": "^25.9.3",
|
|
41
20
|
"esbuild": "^0.25.0",
|
|
42
|
-
"typescript": "^5.
|
|
21
|
+
"typescript": "^5.6.3",
|
|
43
22
|
"undici": "^8.4.1",
|
|
44
23
|
"vitest": "^3.0.0"
|
|
45
24
|
}
|