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.
- package/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/bin/suprafx-mcp.d.ts +15 -0
- package/dist/bin/suprafx-mcp.js +238 -0
- package/dist/bin/suprafx-mcp.js.map +1 -0
- package/dist/src/asset-registry.d.ts +61 -0
- package/dist/src/asset-registry.js +118 -0
- package/dist/src/asset-registry.js.map +1 -0
- package/dist/src/client.d.ts +227 -0
- package/dist/src/client.js +282 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/derive-ids.d.ts +112 -0
- package/dist/src/derive-ids.js +361 -0
- package/dist/src/derive-ids.js.map +1 -0
- package/dist/src/event-bcs.d.ts +341 -0
- package/dist/src/event-bcs.js +767 -0
- package/dist/src/event-bcs.js.map +1 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.js +26 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/config.d.ts +32 -0
- package/dist/src/mcp/config.js +101 -0
- package/dist/src/mcp/config.js.map +1 -0
- package/dist/src/mcp/lifecycle.d.ts +109 -0
- package/dist/src/mcp/lifecycle.js +170 -0
- package/dist/src/mcp/lifecycle.js.map +1 -0
- package/dist/src/mcp/preflight.d.ts +36 -0
- package/dist/src/mcp/preflight.js +291 -0
- package/dist/src/mcp/preflight.js.map +1 -0
- package/dist/src/mcp/server.d.ts +20 -0
- package/dist/src/mcp/server.js +235 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools.d.ts +51 -0
- package/dist/src/mcp/tools.js +1022 -0
- package/dist/src/mcp/tools.js.map +1 -0
- package/dist/src/sign-event.d.ts +185 -0
- package/dist/src/sign-event.js +331 -0
- package/dist/src/sign-event.js.map +1 -0
- package/dist/src/signer.d.ts +89 -0
- package/dist/src/signer.js +226 -0
- package/dist/src/signer.js.map +1 -0
- 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;
|