suprafx-agent-sdk 0.3.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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/bin/suprafx-mcp.d.ts +15 -0
  4. package/dist/bin/suprafx-mcp.js +238 -0
  5. package/dist/bin/suprafx-mcp.js.map +1 -0
  6. package/dist/src/asset-registry.d.ts +61 -0
  7. package/dist/src/asset-registry.js +118 -0
  8. package/dist/src/asset-registry.js.map +1 -0
  9. package/dist/src/client.d.ts +227 -0
  10. package/dist/src/client.js +282 -0
  11. package/dist/src/client.js.map +1 -0
  12. package/dist/src/derive-ids.d.ts +112 -0
  13. package/dist/src/derive-ids.js +361 -0
  14. package/dist/src/derive-ids.js.map +1 -0
  15. package/dist/src/event-bcs.d.ts +341 -0
  16. package/dist/src/event-bcs.js +767 -0
  17. package/dist/src/event-bcs.js.map +1 -0
  18. package/dist/src/index.d.ts +26 -0
  19. package/dist/src/index.js +26 -0
  20. package/dist/src/index.js.map +1 -0
  21. package/dist/src/mcp/config.d.ts +32 -0
  22. package/dist/src/mcp/config.js +101 -0
  23. package/dist/src/mcp/config.js.map +1 -0
  24. package/dist/src/mcp/lifecycle.d.ts +109 -0
  25. package/dist/src/mcp/lifecycle.js +170 -0
  26. package/dist/src/mcp/lifecycle.js.map +1 -0
  27. package/dist/src/mcp/preflight.d.ts +36 -0
  28. package/dist/src/mcp/preflight.js +291 -0
  29. package/dist/src/mcp/preflight.js.map +1 -0
  30. package/dist/src/mcp/server.d.ts +20 -0
  31. package/dist/src/mcp/server.js +235 -0
  32. package/dist/src/mcp/server.js.map +1 -0
  33. package/dist/src/mcp/tools.d.ts +51 -0
  34. package/dist/src/mcp/tools.js +1022 -0
  35. package/dist/src/mcp/tools.js.map +1 -0
  36. package/dist/src/sign-event.d.ts +185 -0
  37. package/dist/src/sign-event.js +331 -0
  38. package/dist/src/sign-event.js.map +1 -0
  39. package/dist/src/signer.d.ts +89 -0
  40. package/dist/src/signer.js +226 -0
  41. package/dist/src/signer.js.map +1 -0
  42. package/package.json +65 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Thin HTTP client over the SupraFX dApp's public endpoints.
3
+ *
4
+ * Wraps the read + write surfaces documented in
5
+ * `docs/INTEGRATING-AGENTS.md`. No auth required for reads; writes
6
+ * carry their own ed25519 signature inside the BCS envelope (see
7
+ * `./signer.ts`).
8
+ *
9
+ * Pure fetch — no global state, no caching beyond the chain-info
10
+ * lookup. Safe to use from the MCP server, from a cookbook script,
11
+ * or as a library inside a larger agent codebase.
12
+ */
13
+ const DEFAULT_BASE = "https://suprafx.ai";
14
+ /** An error carrying a stable category and the action that clears it. */
15
+ export class SupraFxError extends Error {
16
+ code;
17
+ action;
18
+ status;
19
+ detail;
20
+ constructor(code, action, message, opts = {}) {
21
+ super(message);
22
+ this.name = "SupraFxError";
23
+ this.code = code;
24
+ this.action = action;
25
+ this.status = opts.status;
26
+ this.detail = opts.detail;
27
+ }
28
+ /** The JSON body an MCP handler returns on failure. */
29
+ toEnvelope() {
30
+ return {
31
+ error: this.code,
32
+ action: this.action,
33
+ message: this.message,
34
+ ...(this.status != null ? { status: this.status } : {}),
35
+ ...(this.detail !== undefined ? { detail: this.detail } : {}),
36
+ };
37
+ }
38
+ }
39
+ /** Map an HTTP status onto the stable category + its clearing action. */
40
+ function classifyStatus(status) {
41
+ if (status === 401 || status === 403)
42
+ return { code: "auth", action: "authenticate" };
43
+ if (status === 404)
44
+ return { code: "not_found", action: "fix_input" };
45
+ if (status === 429)
46
+ return { code: "rate_limit", action: "backoff" };
47
+ if (status >= 400 && status < 500)
48
+ return { code: "validation", action: "fix_input" };
49
+ return { code: "api", action: "retry" };
50
+ }
51
+ export class SupraFxClient {
52
+ baseUrl;
53
+ timeoutMs;
54
+ cachedChainInfo = null;
55
+ cachedClockOffset = null;
56
+ constructor(opts = {}) {
57
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
58
+ this.timeoutMs = opts.timeoutMs ?? 15000;
59
+ }
60
+ // ─── Reads ─────────────────────────────────────────────────────
61
+ /**
62
+ * Fetch the canonical chain-id hash and validator threshold.
63
+ * Cached for the lifetime of the client — these values are
64
+ * constant per chain genesis.
65
+ */
66
+ async getChainInfo() {
67
+ if (this.cachedChainInfo)
68
+ return this.cachedChainInfo;
69
+ const j = await this.get("/api/council/chain-info");
70
+ if (!j.chainIdHashHex) {
71
+ throw new Error("getChainInfo: response missing chainIdHashHex");
72
+ }
73
+ this.cachedChainInfo = j;
74
+ return j;
75
+ }
76
+ /**
77
+ * Difference between the venue's HTTP clock and the local clock.
78
+ * Best-effort only: expiry calculation must remain available when the
79
+ * header or endpoint is unavailable.
80
+ */
81
+ async getVenueClockOffsetMs() {
82
+ const now = Date.now();
83
+ if (this.cachedClockOffset && now < this.cachedClockOffset.expiresAtMs) {
84
+ return this.cachedClockOffset.offsetMs;
85
+ }
86
+ let offsetMs = 0;
87
+ try {
88
+ const ctrl = new AbortController();
89
+ const t = setTimeout(() => ctrl.abort(), this.timeoutMs);
90
+ try {
91
+ const r = await fetch(this.baseUrl + "/api/council/chain-info", {
92
+ signal: ctrl.signal,
93
+ headers: { accept: "application/json" },
94
+ });
95
+ const serverDateMs = Date.parse(r.headers.get("date") ?? "");
96
+ if (Number.isFinite(serverDateMs)) {
97
+ offsetMs = serverDateMs - Date.now();
98
+ }
99
+ }
100
+ finally {
101
+ clearTimeout(t);
102
+ }
103
+ }
104
+ catch {
105
+ // Clock alignment is advisory; never prevent a trade on failure.
106
+ }
107
+ this.cachedClockOffset = {
108
+ offsetMs,
109
+ expiresAtMs: Date.now() + 5 * 60_000,
110
+ };
111
+ return offsetMs;
112
+ }
113
+ /** Current committed batch height. Useful for `expires_at_batch` math. */
114
+ async getCurrentBatch() {
115
+ const j = await this.get("/api/council/current-batch");
116
+ return j.current_batch;
117
+ }
118
+ /**
119
+ * Next strictly-monotonic sequence number this address must use
120
+ * for its next signed event. `0` for a brand-new account.
121
+ */
122
+ async getSequenceNumber(address) {
123
+ const a = address.startsWith("0x") ? address : "0x" + address;
124
+ const j = await this.get("/api/council/sequence-number?address=" + encodeURIComponent(a));
125
+ return j.next_sequence_number;
126
+ }
127
+ /** All supported assets with canonical chain id + decimals. */
128
+ async listAssets() {
129
+ const j = await this.get("/api/assets");
130
+ return j.assets ?? [];
131
+ }
132
+ /**
133
+ * Available + locked balances for `address` (a master Supra account).
134
+ * Returns an empty array if the address has no balance rows.
135
+ */
136
+ async getBalances(address) {
137
+ const a = address.startsWith("0x") ? address : "0x" + address;
138
+ const j = await this.get("/api/platform/balances?address=" + encodeURIComponent(a));
139
+ return j.balances ?? [];
140
+ }
141
+ /**
142
+ * Status of one deposit claim, keyed on the chain and the L1 transaction
143
+ * hash. Answers "still crediting, or failed?" — see `state`, `stale` and
144
+ * `next_step`. An unknown transaction is `found:false`, not an error: a
145
+ * deposit made without recording a claim still credits through the
146
+ * validator bridge, it is just not visible here.
147
+ */
148
+ async getDepositStatus(chain, txHash) {
149
+ return await this.get("/api/platform/deposit?chain=" + encodeURIComponent(chain) +
150
+ "&tx_hash=" + encodeURIComponent(txHash));
151
+ }
152
+ /** Every deposit claim recorded by `address` (a master account), newest first. */
153
+ async listDepositClaims(address, limit = 20) {
154
+ const a = address.startsWith("0x") ? address : "0x" + address;
155
+ const j = await this.get("/api/platform/deposit?address=" + encodeURIComponent(a) +
156
+ "&limit=" + encodeURIComponent(String(limit)));
157
+ return {
158
+ address: j.address ?? a,
159
+ claims: j.claims ?? [],
160
+ counts: j.counts
161
+ ?? { pending: 0, stale: 0, credited: 0, rejected: 0, expired: 0, reconciled_from_ledger: 0 },
162
+ truncated: j.truncated ?? false,
163
+ };
164
+ }
165
+ /** On-chain policy currently associated with a delegate address. */
166
+ async getDelegatePolicy(address) {
167
+ const a = address.startsWith("0x") ? address : "0x" + address;
168
+ const j = await this.get("/api/delegate-policy?delegate=" + encodeURIComponent(a));
169
+ return j.policy ?? null;
170
+ }
171
+ /**
172
+ * Public orderbook: open RFQs. Filters as documented in
173
+ * `INTEGRATING-AGENTS.md` §2.
174
+ */
175
+ async getOrderbook(filters = {}) {
176
+ const qs = new URLSearchParams({ scope: "platform" });
177
+ if (filters.pair)
178
+ qs.set("pair", filters.pair);
179
+ if (filters.status)
180
+ qs.set("status", filters.status);
181
+ if (filters.limit)
182
+ qs.set("limit", String(filters.limit));
183
+ // RESPONSE SHAPE. `/api/suprafx/rfqs` answers
184
+ // `{ success, data, count, hasMore, ... }` — the rows are under
185
+ // `data`, NOT `rfqs`. Reading only `rfqs` made this silently return
186
+ // an EMPTY array for every call (verified live 2026-09-14: the API
187
+ // returned 3 matched RFQs, this method returned 0). That blinded
188
+ // `get_orderbook` and made `place_quote` unable to find any parent
189
+ // RFQ. Accept `data` first, keep `rfqs` as a fallback so an older or
190
+ // proxied deployment still works.
191
+ const j = await this.get("/api/suprafx/rfqs?" + qs.toString());
192
+ return j.data ?? j.rfqs ?? [];
193
+ }
194
+ /**
195
+ * Venue oracle quote for `pair` (e.g. `"ETH/USDC"`), with the quote's
196
+ * age computed at read time. Quote against THIS, never an external
197
+ * price — and never against a stale one (see `ORACLE_STALE_MS`).
198
+ */
199
+ async getOracle(pair) {
200
+ const j = await this.get("/api/oracle?pair=" + encodeURIComponent(pair));
201
+ const updatedAt = typeof j.updatedAt === "number" ? j.updatedAt : null;
202
+ return {
203
+ pair: j.pair ?? pair,
204
+ conversionRate: typeof j.conversionRate === "number" ? j.conversionRate : null,
205
+ updatedAt,
206
+ ageMs: updatedAt != null ? Date.now() - updatedAt : null,
207
+ };
208
+ }
209
+ // ─── Writes ────────────────────────────────────────────────────
210
+ async submitEnvelope(endpoint, bodyFieldName, envelopeBcsHex) {
211
+ return await this.post("/api/council/" + endpoint, {
212
+ [bodyFieldName]: envelopeBcsHex,
213
+ });
214
+ }
215
+ // ─── Plumbing ──────────────────────────────────────────────────
216
+ async get(path) {
217
+ const ctrl = new AbortController();
218
+ const t = setTimeout(() => ctrl.abort(), this.timeoutMs);
219
+ try {
220
+ let r;
221
+ try {
222
+ r = await fetch(this.baseUrl + path, {
223
+ signal: ctrl.signal,
224
+ headers: { accept: "application/json" },
225
+ });
226
+ }
227
+ catch (e) {
228
+ // An AbortError here is our own timeout firing, not a caller cancel.
229
+ if (ctrl.signal.aborted) {
230
+ throw new SupraFxError("timeout", "retry", `GET ${path} timed out after ${this.timeoutMs}ms`);
231
+ }
232
+ throw new SupraFxError("network", "retry", `GET ${path} failed: ${e instanceof Error ? e.message : String(e)}`);
233
+ }
234
+ if (!r.ok) {
235
+ const { code, action } = classifyStatus(r.status);
236
+ const body = await r.text().catch(() => "");
237
+ throw new SupraFxError(code, action, `GET ${path} → ${r.status} ${r.statusText}`, { status: r.status, detail: body.slice(0, 400) || undefined });
238
+ }
239
+ return (await r.json());
240
+ }
241
+ finally {
242
+ clearTimeout(t);
243
+ }
244
+ }
245
+ async post(path, body) {
246
+ const ctrl = new AbortController();
247
+ const t = setTimeout(() => ctrl.abort(), this.timeoutMs);
248
+ try {
249
+ let r;
250
+ try {
251
+ r = await fetch(this.baseUrl + path, {
252
+ method: "POST",
253
+ signal: ctrl.signal,
254
+ headers: { "content-type": "application/json" },
255
+ body: JSON.stringify(body),
256
+ });
257
+ }
258
+ catch (e) {
259
+ if (ctrl.signal.aborted) {
260
+ throw new SupraFxError("timeout", "report", `POST ${path} timed out after ${this.timeoutMs}ms — the write may ` +
261
+ `or may not have reached the venue; READ STATE BACK before retrying`);
262
+ }
263
+ throw new SupraFxError("network", "report", `POST ${path} failed: ${e instanceof Error ? e.message : String(e)} — ` +
264
+ `the write may or may not have landed; READ STATE BACK before retrying`);
265
+ }
266
+ if (r.status === 429) {
267
+ throw new SupraFxError("rate_limit", "backoff", `POST ${path} → 429`, {
268
+ status: 429,
269
+ });
270
+ }
271
+ // Truth signal is body.ok per INTEGRATING-AGENTS §2 — Cloudflare
272
+ // may strip 5xx bodies, so we don't trust status alone. Parse
273
+ // both 2xx and 4xx bodies and let the caller inspect the code.
274
+ const j = (await r.json().catch(() => ({})));
275
+ return j;
276
+ }
277
+ finally {
278
+ clearTimeout(t);
279
+ }
280
+ }
281
+ }
282
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Asset + pair ID derivation for council `SubmitRfq` events.
3
+ *
4
+ * The chain treats asset_id and pair_id as opaque 32-byte values —
5
+ * it doesn't validate the derivation, just routes events by ID. So
6
+ * the only invariant is **consistency across calls**: SupraFX must
7
+ * derive the same ID for the same (chain, token) tuple every time,
8
+ * across all users + sessions, otherwise orderbook state diverges
9
+ * (two users referring to "ETH on Sepolia" via different IDs would
10
+ * fail to match).
11
+ *
12
+ * ## Convention (V2 — chain-canonical)
13
+ *
14
+ * `asset_id = BLAKE3-keyed(key=BLAKE3("SUPRAFX-ASSET-V1"),
15
+ * message=[chain_id_len_u8, ...chain_id_utf8,
16
+ * ...token_address_bytes])`
17
+ * `pair_id = BLAKE3("suprafx-pair-v1\n" + base_asset_hex
18
+ * + "\n" + quote_asset_hex)`
19
+ *
20
+ * V2 mirrors `council_bridge::identity::asset_id_from_chain_and_token`
21
+ * byte-for-byte. Required because the bridge credits L1 deposits
22
+ * under THIS asset_id; if the dApp's RFQ uses a different derivation
23
+ * (V1), the chain says "no balance" and the validator gate rejects
24
+ * the trade. The migration is one-time — open RFQs from V1 days
25
+ * remain in the legacy table but are unmatchable on the new orderbook.
26
+ *
27
+ * ## Token address registry
28
+ *
29
+ * The chain stores asset_id under `(canonicalChainId, tokenBytes)`,
30
+ * where tokenBytes is:
31
+ * - `[0u8; 20]` for the native EVM coin (`EVM_NATIVE_TOKEN_BYTES`)
32
+ * - the 20-byte ERC-20 contract address otherwise
33
+ * - `[0u8; 32]` for native Supra
34
+ * The dApp UI thinks in `(uiChain, symbol)` strings. The registry
35
+ * below bridges the two; new tokens MUST be added here AND in the
36
+ * validator's `bridge.eth_chains[*].asset_decimals` operator config
37
+ * before they can be deposited.
38
+ */
39
+ /**
40
+ * Chain-canonical asset_id. Mirrors
41
+ * `council_bridge::identity::asset_id_from_chain_and_token`.
42
+ *
43
+ * asset_id = blake3-keyed(
44
+ * key = blake3("SUPRAFX-ASSET-V1"),
45
+ * msg = [chain_id.len() as u8, ...chain_id_utf8, ...token_bytes],
46
+ * )
47
+ *
48
+ * `chainId` is the canonical bridge chain identifier (`eth-sepolia`,
49
+ * `supra-testnet`, etc). `tokenBytes` is the L1 token address — 20
50
+ * bytes for EVM (zero for native), 32 bytes for Supra (zero for native).
51
+ */
52
+ export declare function assetIdFromChainAndToken(chainId: string, tokenBytes: Uint8Array): Uint8Array;
53
+ /**
54
+ * Bridge the SHORT chain ids the venue's `/api/assets` returns
55
+ * (`"ethereum"`, `"supra"`, `"sepolia"`) to the CANONICAL bridge chain
56
+ * ids the on-chain AssetId derivation is keyed on (`"eth-mainnet"`,
57
+ * `"supra-mainnet"`, `"eth-sepolia"`).
58
+ *
59
+ * WHY THIS EXISTS (verified against live `https://suprafx.ai/api/assets`
60
+ * on 2026-09-14). An agent that reads `list_assets` and passes the
61
+ * `chain_id` it got back straight into a trade — the obvious thing to do
62
+ * — used to derive the WRONG AssetId, two different ways, both silent:
63
+ *
64
+ * - `"ethereum"` is in no registry key, so `deriveAssetId` fell through
65
+ * to the legacy V1 hash and produced a phantom id the validator gate
66
+ * rejects.
67
+ * - `"supra"` WAS a registry key — pointing at `supra-testnet`. So a
68
+ * mainnet SUPRA trade derived the TESTNET SUPRA asset: a well-formed
69
+ * id for the wrong chain. Worse than a phantom, because nothing about
70
+ * it looks wrong.
71
+ *
72
+ * Canonical ids map to themselves, so this is an identity for every
73
+ * caller that already passes the long form.
74
+ */
75
+ export declare function canonicalChain(chain: string): string;
76
+ /**
77
+ * Derive a 32-byte AssetId from a UI-friendly `(chain, symbol)` tuple.
78
+ *
79
+ * Resolves through TOKEN_REGISTRY to the canonical
80
+ * `(chainId, tokenBytes)` then hashes per
81
+ * [`assetIdFromChainAndToken`]. For tokens not in the registry
82
+ * (test/explorer use cases), falls back to the V1 hash so
83
+ * downstream code that doesn't actually trade these IDs keeps
84
+ * working — but the return value is NOT what the chain expects
85
+ * for the unregistered token, so RFQ submission against it WILL
86
+ * fail at the validator gate.
87
+ */
88
+ export declare function deriveAssetId(chain: string, symbol: string): Uint8Array;
89
+ /**
90
+ * Like `deriveAssetId`, but returns `null` for a `(chain, symbol)`
91
+ * NOT in `TOKEN_REGISTRY` — instead of silently falling through to
92
+ * the V1 hash.
93
+ *
94
+ * Callers that need the REAL chain-canonical AssetId — delegate
95
+ * `asset_caps` (Option B), deposit crediting — MUST use this and
96
+ * treat `null` as "not a tradeable asset yet". Embedding a V1 hash in
97
+ * a delegate's cap map authorizes a phantom id, and the moment the
98
+ * asset is later registered every old session's map goes stale.
99
+ */
100
+ export declare function registeredAssetId(chain: string, symbol: string): Uint8Array | null;
101
+ /**
102
+ * Derive a 32-byte PairId from base + quote AssetId values.
103
+ *
104
+ * Order matters: `(ETH, USDC)` produces a different pair than
105
+ * `(USDC, ETH)`. SubmitRfq's `base_asset` is what the user gives
106
+ * up; `quote_asset` is what they receive.
107
+ */
108
+ export declare function derivePairId(baseAssetId: Uint8Array, quoteAssetId: Uint8Array): Uint8Array;
109
+ /**
110
+ * Convenience: one-call `(chain, symbol)` × 2 → 32-byte PairId.
111
+ */
112
+ export declare function derivePairIdFromTokens(baseChain: string, baseSymbol: string, quoteChain: string, quoteSymbol: string): Uint8Array;