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,767 @@
1
+ /**
2
+ * BCS encoders for user-originated `Event` variants.
3
+ *
4
+ * Mirrors the Rust struct schemas in
5
+ * `council-rust/crates/protocol/src/events.rs` byte-for-byte.
6
+ * Cross-language fixture
7
+ * (`lib/council/__fixtures__/user-event-envelope-fixtures.json`)
8
+ * proves equivalence; any drift is caught by
9
+ * `tests/unit/council-event-bcs.test.ts`.
10
+ *
11
+ * ## Why we encode client-side (not server-side)
12
+ *
13
+ * The user signs `event_bcs` directly. If the server encoded those
14
+ * bytes, a malicious or compromised server could mutate the event
15
+ * (UI shows "Withdraw 100 USDC", server encodes "Withdraw 1M USDC")
16
+ * and the user's wallet would sign the malicious bytes without the
17
+ * user knowing — chain accepts the sig as valid because the math
18
+ * works. Client-side encoding makes the bytes-the-user-signs equal
19
+ * the bytes-the-UI-rendered, since both come from the same auditable
20
+ * open-source TS code.
21
+ *
22
+ * Standard practice — Aptos, Sui, Solana, Ethereum all encode user
23
+ * transactions client-side for the same reason.
24
+ *
25
+ * ## Variant index (must match Rust `Event` enum order)
26
+ *
27
+ * BCS encodes a Rust enum as `ULEB128(discriminant) + fields`. The
28
+ * discriminant is the variant's declaration order in the enum:
29
+ *
30
+ * 0 DepositCredited (validator-attested; not signed by user)
31
+ * 1 WithdrawRequested ← user
32
+ * 2 WithdrawFinalized (validator-attested)
33
+ * 3 WithdrawCancelled (lifecycle)
34
+ * 4 SubmitRfq ← user
35
+ * 5 CancelRfq ← user
36
+ * 6 PlaceQuote ← user (maker)
37
+ * 7 AcceptQuote ← user (taker)
38
+ * 8 WithdrawQuote ← user (maker)
39
+ * 9 SlashMaker (lifecycle)
40
+ * 10..19 validator/governance
41
+ * 20 DelegatePolicyCreated ← user (master)
42
+ * 21 DelegatePolicyUpdated ← user (master)
43
+ * 22 DelegatePolicyRevoked ← user (master)
44
+ * 23..30 other (not user-signed)
45
+ *
46
+ * If any of these indices change in Rust, the cross-language fixture
47
+ * will fail at the next `cargo run --example
48
+ * gen_user_event_envelope_fixture` and the TS test will catch it.
49
+ *
50
+ * ## Type sizes (locked at the protocol level)
51
+ *
52
+ * AccountId [u8; 32]
53
+ * AssetId [u8; 32]
54
+ * PairId [u8; 32]
55
+ * RfqId [u8; 16]
56
+ * QuoteId [u8; 16]
57
+ * TradeId [u8; 16]
58
+ * Hash [u8; 32]
59
+ * Amount u128 (16 LE bytes; BCS does NOT use ULEB128 for u128)
60
+ * SequenceNumber u64 (8 LE bytes)
61
+ */
62
+ // ─── BCS primitive writers ────────────────────────────────────────
63
+ class BcsWriter {
64
+ chunks = [];
65
+ len = 0;
66
+ bytes(buf) {
67
+ this.chunks.push(buf);
68
+ this.len += buf.length;
69
+ }
70
+ /** Fixed-size byte array (no length prefix). */
71
+ fixed(buf, expectedLen, fieldName) {
72
+ if (buf.length !== expectedLen) {
73
+ throw new Error(`${fieldName}: expected ${expectedLen} bytes, got ${buf.length}`);
74
+ }
75
+ this.bytes(buf);
76
+ }
77
+ /** Variable-length `Vec<u8>` — ULEB128 length prefix + bytes. */
78
+ varBytes(buf) {
79
+ this.uleb128(buf.length);
80
+ this.bytes(buf);
81
+ }
82
+ /** UTF-8 string — same wire shape as `Vec<u8>`. */
83
+ str(s) {
84
+ this.varBytes(new TextEncoder().encode(s));
85
+ }
86
+ /** ULEB128 (variable-length unsigned int). */
87
+ uleb128(n) {
88
+ if (n < 0 || !Number.isInteger(n)) {
89
+ throw new Error(`uleb128: invalid value ${n}`);
90
+ }
91
+ let v = n;
92
+ const out = [];
93
+ while (v >= 0x80) {
94
+ out.push((v & 0x7f) | 0x80);
95
+ v >>>= 7;
96
+ }
97
+ out.push(v & 0x7f);
98
+ this.bytes(new Uint8Array(out));
99
+ }
100
+ /** u8 — 1 byte. */
101
+ u8(n) {
102
+ if (n < 0 || n > 0xff || !Number.isInteger(n)) {
103
+ throw new Error(`u8: invalid value ${n}`);
104
+ }
105
+ this.bytes(new Uint8Array([n & 0xff]));
106
+ }
107
+ /** u32 little-endian — 4 bytes. */
108
+ u32(n) {
109
+ if (n < 0 || n > 0xffffffff || !Number.isInteger(n)) {
110
+ throw new Error(`u32: invalid value ${n}`);
111
+ }
112
+ const buf = new Uint8Array(4);
113
+ new DataView(buf.buffer).setUint32(0, n, true);
114
+ this.bytes(buf);
115
+ }
116
+ /** u64 little-endian — 8 bytes. Accepts `bigint` or safe-integer `number`. */
117
+ u64(n) {
118
+ let v = typeof n === "bigint" ? n : BigInt(n);
119
+ if (v < BigInt(0) || v > BigInt("0xffffffffffffffff")) {
120
+ throw new Error(`u64: out of range ${v}`);
121
+ }
122
+ const buf = new Uint8Array(8);
123
+ new DataView(buf.buffer).setBigUint64(0, v, true);
124
+ this.bytes(buf);
125
+ }
126
+ /** u128 little-endian — 16 bytes. Used by `Amount`. */
127
+ u128(n) {
128
+ let v;
129
+ if (typeof n === "bigint")
130
+ v = n;
131
+ else if (typeof n === "number")
132
+ v = BigInt(n);
133
+ else
134
+ v = BigInt(n);
135
+ if (v < BigInt(0) || v > (BigInt(1) << BigInt(128)) - BigInt(1)) {
136
+ throw new Error(`u128: out of range ${v}`);
137
+ }
138
+ const buf = new Uint8Array(16);
139
+ const view = new DataView(buf.buffer);
140
+ const lo = v & BigInt("0xffffffffffffffff");
141
+ const hi = v >> BigInt(64);
142
+ view.setBigUint64(0, lo, true);
143
+ view.setBigUint64(8, hi, true);
144
+ this.bytes(buf);
145
+ }
146
+ /** bool — 1 byte (0 = false, 1 = true). */
147
+ bool(b) {
148
+ this.u8(b ? 1 : 0);
149
+ }
150
+ /** Vec<T> — ULEB128 length + repeated `writeFn(item)`. */
151
+ vec(items, writeFn) {
152
+ this.uleb128(items.length);
153
+ for (const item of items)
154
+ writeFn(this, item);
155
+ }
156
+ finish() {
157
+ const out = new Uint8Array(this.len);
158
+ let off = 0;
159
+ for (const c of this.chunks) {
160
+ out.set(c, off);
161
+ off += c.length;
162
+ }
163
+ return out;
164
+ }
165
+ }
166
+ // ─── Variant tag constants ────────────────────────────────────────
167
+ const TAG_DEPOSIT_CREDITED = 0;
168
+ const TAG_WITHDRAW_REQUESTED = 1;
169
+ const TAG_SUBMIT_RFQ = 4;
170
+ const TAG_CANCEL_RFQ = 5;
171
+ const TAG_PLACE_QUOTE = 6;
172
+ const TAG_ACCEPT_QUOTE = 7;
173
+ const TAG_WITHDRAW_QUOTE = 8;
174
+ // On-chain-settlement claim events. Bump per-user (taker / maker)
175
+ // sequence numbers; included so projection-from-mirror endpoints
176
+ // (e.g. /api/council/sequence-number) can decode them.
177
+ const TAG_TAKER_TX_PENDING = 14;
178
+ const TAG_MAKER_TX_PENDING = 16;
179
+ const TAG_DELEGATE_POLICY_CREATED = 20;
180
+ const TAG_DELEGATE_POLICY_UPDATED = 21;
181
+ const TAG_DELEGATE_POLICY_REVOKED = 22;
182
+ // LinkedAddressRevoked = enum index 25 (Rust Event variant order:
183
+ // see council-rust/crates/protocol/src/events.rs).
184
+ const TAG_LINKED_ADDRESS_REVOKED = 25;
185
+ function writeSettlementMode(w, m) {
186
+ w.uleb128(m === "Platform" ? 0 : 1);
187
+ }
188
+ function writeDelegateRole(w, r) {
189
+ if (r === "Maker")
190
+ w.uleb128(0);
191
+ else if (r === "Taker")
192
+ w.uleb128(1);
193
+ else if (r === "Agent")
194
+ w.uleb128(2);
195
+ else
196
+ throw new Error(`unknown DelegateRole: ${r}`);
197
+ }
198
+ /**
199
+ * The canonical "effectively unlimited" cap sentinel: `u64::MAX`.
200
+ *
201
+ * NOT `u128::MAX` — the validator's `checked_add(earmarks, size)`
202
+ * overflows there. NOT `0n`, which is fail-closed (see above).
203
+ */
204
+ export const MAX_CAP = (1n << 64n) - 1n;
205
+ // ─── Per-variant encoders ─────────────────────────────────────────
206
+ function writeSubmitRfq(w, e) {
207
+ w.fixed(e.user, 32, "user");
208
+ w.fixed(e.pair, 32, "pair");
209
+ w.fixed(e.base_asset, 32, "base_asset");
210
+ w.fixed(e.quote_asset, 32, "quote_asset");
211
+ w.u128(e.size);
212
+ w.u128(e.reference_price);
213
+ w.bool(e.auto_accept);
214
+ w.u128(e.auto_accept_target_rate);
215
+ w.bool(e.allow_partial_fills);
216
+ w.u128(e.min_fill_size);
217
+ w.u64(e.expires_at_ms);
218
+ w.fixed(e.rfq_id, 16, "rfq_id");
219
+ w.u64(e.user_sequence_number);
220
+ writeSettlementMode(w, e.settlement_mode);
221
+ }
222
+ function writeCancelRfq(w, e) {
223
+ w.fixed(e.user, 32, "user");
224
+ w.fixed(e.rfq_id, 16, "rfq_id");
225
+ w.str(e.reason);
226
+ }
227
+ function writePlaceQuote(w, e) {
228
+ w.fixed(e.maker, 32, "maker");
229
+ w.fixed(e.rfq_id, 16, "rfq_id");
230
+ w.fixed(e.quote_id, 16, "quote_id");
231
+ w.u128(e.rate);
232
+ w.u128(e.fill_size);
233
+ w.u64(e.user_sequence_number);
234
+ }
235
+ function writeAcceptQuote(w, e) {
236
+ w.fixed(e.taker, 32, "taker");
237
+ w.fixed(e.quote_id, 16, "quote_id");
238
+ w.fixed(e.trade_id, 16, "trade_id");
239
+ w.u64(e.user_sequence_number);
240
+ }
241
+ function writeWithdrawQuote(w, e) {
242
+ w.fixed(e.maker, 32, "maker");
243
+ w.fixed(e.quote_id, 16, "quote_id");
244
+ }
245
+ function writeWithdrawRequested(w, e) {
246
+ w.fixed(e.user, 32, "user");
247
+ w.fixed(e.asset, 32, "asset");
248
+ w.u128(e.amount);
249
+ w.fixed(e.fee_asset, 32, "fee_asset");
250
+ w.u128(e.fee_amount);
251
+ w.u64(e.withdrawal_id);
252
+ w.str(e.dest_chain);
253
+ w.varBytes(e.dest_address);
254
+ w.u64(e.deadline_ms);
255
+ w.u64(e.user_sequence_number);
256
+ }
257
+ /** Lexicographic compare of two byte arrays (BCS map-key ordering). */
258
+ function compareBytes(a, b) {
259
+ const n = Math.min(a.length, b.length);
260
+ for (let i = 0; i < n; i++) {
261
+ if (a[i] !== b[i])
262
+ return a[i] < b[i] ? -1 : 1;
263
+ }
264
+ return a.length - b.length;
265
+ }
266
+ /**
267
+ * BCS-encode `asset_caps` as a `BTreeMap<AssetId, AssetCap>`:
268
+ * `ULEB128(count)` then entries **sorted by AssetId bytes ascending**
269
+ * — BCS map canonicality. Rust `bcs` produces exactly this; a wrong
270
+ * sort yields a valid-but-non-canonical envelope the validator
271
+ * rejects. Duplicate keys are rejected (a `BTreeMap` has none).
272
+ */
273
+ function writeAssetCaps(w, caps) {
274
+ const sorted = [...caps].sort((a, b) => compareBytes(a.asset, b.asset));
275
+ for (let i = 1; i < sorted.length; i++) {
276
+ if (compareBytes(sorted[i - 1].asset, sorted[i].asset) === 0) {
277
+ throw new Error("writeAssetCaps: duplicate AssetId in asset_caps");
278
+ }
279
+ }
280
+ w.uleb128(sorted.length);
281
+ for (const entry of sorted) {
282
+ w.fixed(entry.asset, 32, "asset_cap.asset");
283
+ w.u128(entry.maxTradeSize);
284
+ w.u128(entry.maxEarmarkTotal);
285
+ }
286
+ }
287
+ function writeDelegatePolicyCreated(w, e) {
288
+ w.fixed(e.master, 32, "master");
289
+ w.fixed(e.delegate, 32, "delegate");
290
+ w.str(e.label);
291
+ w.vec(e.allowed_roles, writeDelegateRole);
292
+ w.vec(e.allowed_pairs, (ww, p) => ww.fixed(p, 32, "pair"));
293
+ writeAssetCaps(w, e.asset_caps);
294
+ // #25 H10: on-chain session expiry. u64 LE, AFTER asset_caps and
295
+ // BEFORE user_sequence_number — matches the Rust field order.
296
+ w.u64(e.expires_at_batch);
297
+ w.u64(e.user_sequence_number);
298
+ }
299
+ /** Master-signed `DelegatePolicyRevoked` writer. Symmetric to
300
+ * `readDelegatePolicyRevoked` further down. Was missing until
301
+ * 2026-06-01 — every revoke envelope sent before this date threw
302
+ * "encoder not implemented" and the on-chain delegate kept its
303
+ * authorization. Wire shape: master[32] || delegate[32] ||
304
+ * user_sequence_number[u64 LE]. Matches Rust
305
+ * `council_protocol::events::DelegatePolicyRevoked` exactly. */
306
+ function writeDelegatePolicyRevoked(w, e) {
307
+ w.fixed(e.master, 32, "master");
308
+ w.fixed(e.delegate, 32, "delegate");
309
+ w.u64(e.user_sequence_number);
310
+ }
311
+ function writeDelegatePolicyUpdated(w, e) {
312
+ w.fixed(e.master, 32, "master");
313
+ w.fixed(e.delegate, 32, "delegate");
314
+ w.str(e.label);
315
+ w.vec(e.allowed_roles, writeDelegateRole);
316
+ w.vec(e.allowed_pairs, (ww, p) => ww.fixed(p, 32, "pair"));
317
+ writeAssetCaps(w, e.asset_caps);
318
+ w.bool(e.active);
319
+ w.u64(e.user_sequence_number);
320
+ }
321
+ function writeLinkedAddressRevoked(w, e) {
322
+ w.fixed(e.user, 32, "user");
323
+ w.str(e.chain);
324
+ w.varBytes(e.address);
325
+ w.u64(e.user_sequence_number);
326
+ }
327
+ /**
328
+ * BCS-encode a user-originated `Event`. Returns the bytes the user
329
+ * signs (becomes `event_bcs` in the `SignedEventEnvelope`).
330
+ *
331
+ * Pure function. Same input → same bytes, every time.
332
+ */
333
+ export function encodeUserEvent(ev) {
334
+ const w = new BcsWriter();
335
+ switch (ev.kind) {
336
+ case "SubmitRfq":
337
+ w.uleb128(TAG_SUBMIT_RFQ);
338
+ writeSubmitRfq(w, ev.payload);
339
+ break;
340
+ case "CancelRfq":
341
+ w.uleb128(TAG_CANCEL_RFQ);
342
+ writeCancelRfq(w, ev.payload);
343
+ break;
344
+ case "PlaceQuote":
345
+ w.uleb128(TAG_PLACE_QUOTE);
346
+ writePlaceQuote(w, ev.payload);
347
+ break;
348
+ case "AcceptQuote":
349
+ w.uleb128(TAG_ACCEPT_QUOTE);
350
+ writeAcceptQuote(w, ev.payload);
351
+ break;
352
+ case "WithdrawQuote":
353
+ w.uleb128(TAG_WITHDRAW_QUOTE);
354
+ writeWithdrawQuote(w, ev.payload);
355
+ break;
356
+ case "WithdrawRequested":
357
+ w.uleb128(TAG_WITHDRAW_REQUESTED);
358
+ writeWithdrawRequested(w, ev.payload);
359
+ break;
360
+ case "DelegatePolicyCreated":
361
+ w.uleb128(TAG_DELEGATE_POLICY_CREATED);
362
+ writeDelegatePolicyCreated(w, ev.payload);
363
+ break;
364
+ case "DelegatePolicyUpdated":
365
+ w.uleb128(TAG_DELEGATE_POLICY_UPDATED);
366
+ writeDelegatePolicyUpdated(w, ev.payload);
367
+ break;
368
+ case "LinkedAddressRevoked":
369
+ w.uleb128(TAG_LINKED_ADDRESS_REVOKED);
370
+ writeLinkedAddressRevoked(w, ev.payload);
371
+ break;
372
+ case "DelegatePolicyRevoked":
373
+ w.uleb128(TAG_DELEGATE_POLICY_REVOKED);
374
+ writeDelegatePolicyRevoked(w, ev.payload);
375
+ break;
376
+ case "TakerTxPending":
377
+ case "MakerTxPending":
378
+ // Decode-only support today: these variants are added so the
379
+ // projection-from-mirror endpoints can READ them out of
380
+ // `council_event_chain`, but no UI flow signs them yet. If/when
381
+ // a UI flow needs to sign one, add the encoder symmetrically
382
+ // (the Rust struct shape is documented in events.rs).
383
+ throw new Error(`encodeUserEvent: ${ev.kind} is decode-only; encoder not implemented`);
384
+ }
385
+ return w.finish();
386
+ }
387
+ // ─── BCS reader + decoders ────────────────────────────────────────
388
+ //
389
+ // Inverse of BcsWriter + encodeUserEvent. Used by the explorer +
390
+ // projection logic to surface decoded fields from `payload_bcs_hex`
391
+ // in council_event_chain. Decoders are paired with the encoders
392
+ // above; any change to a struct field order MUST be reflected in
393
+ // both, and the cross-language fixture round-trips guard against
394
+ // drift (`tests/unit/council-sign-event.test.ts`).
395
+ class BcsReader {
396
+ buf;
397
+ off = 0;
398
+ constructor(buf) {
399
+ this.buf = buf;
400
+ }
401
+ take(n) {
402
+ if (this.off + n > this.buf.length) {
403
+ throw new Error(`BcsReader: out of bounds (need ${n} bytes at off ${this.off}, len ${this.buf.length})`);
404
+ }
405
+ const out = this.buf.subarray(this.off, this.off + n);
406
+ this.off += n;
407
+ return out;
408
+ }
409
+ fixed(n) {
410
+ return new Uint8Array(this.take(n));
411
+ }
412
+ varBytes() {
413
+ const len = this.uleb128();
414
+ return new Uint8Array(this.take(len));
415
+ }
416
+ str() {
417
+ return new TextDecoder("utf-8", { fatal: true }).decode(this.varBytes());
418
+ }
419
+ uleb128() {
420
+ let result = 0;
421
+ let shift = 0;
422
+ while (true) {
423
+ if (this.off >= this.buf.length) {
424
+ throw new Error("BcsReader: truncated ULEB128");
425
+ }
426
+ const byte = this.buf[this.off++];
427
+ result |= (byte & 0x7f) << shift;
428
+ if ((byte & 0x80) === 0)
429
+ return result;
430
+ shift += 7;
431
+ if (shift >= 35)
432
+ throw new Error("BcsReader: ULEB128 exceeds u32");
433
+ }
434
+ }
435
+ u8() {
436
+ return this.take(1)[0];
437
+ }
438
+ u64() {
439
+ const b = this.take(8);
440
+ return new DataView(b.buffer, b.byteOffset, 8).getBigUint64(0, true);
441
+ }
442
+ u128() {
443
+ const b = this.take(16);
444
+ const view = new DataView(b.buffer, b.byteOffset, 16);
445
+ const lo = view.getBigUint64(0, true);
446
+ const hi = view.getBigUint64(8, true);
447
+ return (hi << BigInt(64)) | lo;
448
+ }
449
+ bool() {
450
+ const b = this.u8();
451
+ if (b !== 0 && b !== 1) {
452
+ throw new Error(`BcsReader: invalid bool byte ${b}`);
453
+ }
454
+ return b === 1;
455
+ }
456
+ /** Bytes consumed so far. Useful for debug + length asserts. */
457
+ position() {
458
+ return this.off;
459
+ }
460
+ /** Bytes remaining. Should be 0 after a complete decode. */
461
+ remaining() {
462
+ return this.buf.length - this.off;
463
+ }
464
+ }
465
+ // ─── Per-variant decoders (mirror order of writeXxx above) ────────
466
+ function readDepositCredited(r) {
467
+ const user = r.fixed(32);
468
+ const asset = r.fixed(32);
469
+ const amount = r.u128();
470
+ const source_chain = r.str();
471
+ const source_tx = r.fixed(32);
472
+ const deposit_id = r.u64();
473
+ const source_block = r.u64();
474
+ const validatorsCount = r.uleb128();
475
+ const attesting_validators = [];
476
+ for (let i = 0; i < validatorsCount; i++) {
477
+ attesting_validators.push(r.fixed(32));
478
+ }
479
+ const sigsCount = r.uleb128();
480
+ const attestations = [];
481
+ for (let i = 0; i < sigsCount; i++) {
482
+ attestations.push(r.fixed(64));
483
+ }
484
+ return {
485
+ user,
486
+ asset,
487
+ amount,
488
+ source_chain,
489
+ source_tx,
490
+ deposit_id,
491
+ source_block,
492
+ attesting_validators,
493
+ attestations,
494
+ };
495
+ }
496
+ function readSubmitRfq(r) {
497
+ return {
498
+ user: r.fixed(32),
499
+ pair: r.fixed(32),
500
+ base_asset: r.fixed(32),
501
+ quote_asset: r.fixed(32),
502
+ size: r.u128(),
503
+ reference_price: r.u128(),
504
+ auto_accept: r.bool(),
505
+ auto_accept_target_rate: r.u128(),
506
+ allow_partial_fills: r.bool(),
507
+ min_fill_size: r.u128(),
508
+ expires_at_ms: r.u64(),
509
+ rfq_id: r.fixed(16),
510
+ user_sequence_number: r.u64(),
511
+ settlement_mode: readSettlementMode(r),
512
+ };
513
+ }
514
+ function readCancelRfq(r) {
515
+ return {
516
+ user: r.fixed(32),
517
+ rfq_id: r.fixed(16),
518
+ reason: r.str(),
519
+ };
520
+ }
521
+ function readPlaceQuote(r) {
522
+ return {
523
+ maker: r.fixed(32),
524
+ rfq_id: r.fixed(16),
525
+ quote_id: r.fixed(16),
526
+ rate: r.u128(),
527
+ fill_size: r.u128(),
528
+ user_sequence_number: r.u64(),
529
+ };
530
+ }
531
+ function readAcceptQuote(r) {
532
+ return {
533
+ taker: r.fixed(32),
534
+ quote_id: r.fixed(16),
535
+ trade_id: r.fixed(16),
536
+ user_sequence_number: r.u64(),
537
+ };
538
+ }
539
+ function readWithdrawQuote(r) {
540
+ return {
541
+ maker: r.fixed(32),
542
+ quote_id: r.fixed(16),
543
+ };
544
+ }
545
+ function readWithdrawRequested(r) {
546
+ return {
547
+ user: r.fixed(32),
548
+ asset: r.fixed(32),
549
+ amount: r.u128(),
550
+ fee_asset: r.fixed(32),
551
+ fee_amount: r.u128(),
552
+ withdrawal_id: r.u64(),
553
+ dest_chain: r.str(),
554
+ dest_address: r.varBytes(),
555
+ deadline_ms: r.u64(),
556
+ user_sequence_number: r.u64(),
557
+ };
558
+ }
559
+ /** Exact inverse of `writeLinkedAddressRevoked`. */
560
+ function readLinkedAddressRevoked(r) {
561
+ return {
562
+ user: r.fixed(32),
563
+ chain: r.str(),
564
+ address: r.varBytes(),
565
+ user_sequence_number: r.u64(),
566
+ };
567
+ }
568
+ /** Inverse of `writeAssetCaps` — reads the BCS `BTreeMap` entries. */
569
+ function readAssetCaps(r) {
570
+ const count = r.uleb128();
571
+ const out = [];
572
+ for (let i = 0; i < count; i++) {
573
+ out.push({
574
+ asset: r.fixed(32),
575
+ maxTradeSize: r.u128(),
576
+ maxEarmarkTotal: r.u128(),
577
+ });
578
+ }
579
+ return out;
580
+ }
581
+ function readDelegatePolicyCreated(r) {
582
+ const master = r.fixed(32);
583
+ const delegate = r.fixed(32);
584
+ const label = r.str();
585
+ // Vec<DelegateRole>
586
+ const allowed_roles_len = r.uleb128();
587
+ const allowed_roles = [];
588
+ for (let i = 0; i < allowed_roles_len; i++) {
589
+ allowed_roles.push(readDelegateRole(r));
590
+ }
591
+ // Vec<PairId>
592
+ const allowed_pairs_len = r.uleb128();
593
+ const allowed_pairs = [];
594
+ for (let i = 0; i < allowed_pairs_len; i++) {
595
+ allowed_pairs.push(r.fixed(32));
596
+ }
597
+ // Read in strict wire order — `asset_caps` then `expires_at_batch`
598
+ // then `user_sequence_number` (#25 H10). Pulled into statements so
599
+ // the read order does not depend on object-literal evaluation order.
600
+ const asset_caps = readAssetCaps(r);
601
+ const expires_at_batch = r.u64();
602
+ const user_sequence_number = r.u64();
603
+ return {
604
+ master,
605
+ delegate,
606
+ label,
607
+ allowed_roles,
608
+ allowed_pairs,
609
+ asset_caps,
610
+ expires_at_batch,
611
+ user_sequence_number,
612
+ };
613
+ }
614
+ function readTakerTxPending(r) {
615
+ return {
616
+ trade_id: r.fixed(16),
617
+ taker: r.fixed(32),
618
+ source_chain: r.str(),
619
+ source_tx: r.fixed(32),
620
+ user_sequence_number: r.u64(),
621
+ };
622
+ }
623
+ function readMakerTxPending(r) {
624
+ return {
625
+ trade_id: r.fixed(16),
626
+ maker: r.fixed(32),
627
+ source_chain: r.str(),
628
+ source_tx: r.fixed(32),
629
+ user_sequence_number: r.u64(),
630
+ };
631
+ }
632
+ function readDelegatePolicyUpdated(r) {
633
+ const master = r.fixed(32);
634
+ const delegate = r.fixed(32);
635
+ const label = r.str();
636
+ const allowed_roles_len = r.uleb128();
637
+ const allowed_roles = [];
638
+ for (let i = 0; i < allowed_roles_len; i++) {
639
+ allowed_roles.push(readDelegateRole(r));
640
+ }
641
+ const allowed_pairs_len = r.uleb128();
642
+ const allowed_pairs = [];
643
+ for (let i = 0; i < allowed_pairs_len; i++) {
644
+ allowed_pairs.push(r.fixed(32));
645
+ }
646
+ return {
647
+ master,
648
+ delegate,
649
+ label,
650
+ allowed_roles,
651
+ allowed_pairs,
652
+ asset_caps: readAssetCaps(r),
653
+ active: r.bool(),
654
+ user_sequence_number: r.u64(),
655
+ };
656
+ }
657
+ function readDelegatePolicyRevoked(r) {
658
+ return {
659
+ master: r.fixed(32),
660
+ delegate: r.fixed(32),
661
+ user_sequence_number: r.u64(),
662
+ };
663
+ }
664
+ function readSettlementMode(r) {
665
+ const tag = r.uleb128();
666
+ if (tag === 0)
667
+ return "Platform";
668
+ if (tag === 1)
669
+ return "OnChain";
670
+ throw new Error(`unknown SettlementMode tag: ${tag}`);
671
+ }
672
+ function readDelegateRole(r) {
673
+ const tag = r.uleb128();
674
+ if (tag === 0)
675
+ return "Maker";
676
+ if (tag === 1)
677
+ return "Taker";
678
+ if (tag === 2)
679
+ return "Agent";
680
+ throw new Error(`unknown DelegateRole tag: ${tag}`);
681
+ }
682
+ /**
683
+ * Decode a BCS-encoded `Event` into the corresponding `UserEvent`
684
+ * variant. Reads the variant tag (ULEB128) from the front of the
685
+ * buffer + dispatches.
686
+ *
687
+ * Returns `null` for variants this module doesn't know about
688
+ * (validator-attested events like `DepositCredited`, governance, etc.) —
689
+ * callers should treat that as "decoding deferred to a future
690
+ * version" rather than an error.
691
+ *
692
+ * Throws on truly malformed BCS bytes (truncated, invalid bools, etc).
693
+ *
694
+ * After decoding, `reader.remaining()` should be 0; if not, the
695
+ * payload had trailing bytes (caller-supplied buffer too long, or
696
+ * a struct-field-mismatch bug).
697
+ */
698
+ export function decodeUserEvent(bcs) {
699
+ const r = new BcsReader(bcs);
700
+ const tag = r.uleb128();
701
+ let result = null;
702
+ switch (tag) {
703
+ case TAG_DEPOSIT_CREDITED:
704
+ result = { kind: "DepositCredited", payload: readDepositCredited(r) };
705
+ break;
706
+ case TAG_SUBMIT_RFQ:
707
+ result = { kind: "SubmitRfq", payload: readSubmitRfq(r) };
708
+ break;
709
+ case TAG_CANCEL_RFQ:
710
+ result = { kind: "CancelRfq", payload: readCancelRfq(r) };
711
+ break;
712
+ case TAG_PLACE_QUOTE:
713
+ result = { kind: "PlaceQuote", payload: readPlaceQuote(r) };
714
+ break;
715
+ case TAG_ACCEPT_QUOTE:
716
+ result = { kind: "AcceptQuote", payload: readAcceptQuote(r) };
717
+ break;
718
+ case TAG_WITHDRAW_QUOTE:
719
+ result = { kind: "WithdrawQuote", payload: readWithdrawQuote(r) };
720
+ break;
721
+ case TAG_WITHDRAW_REQUESTED:
722
+ result = {
723
+ kind: "WithdrawRequested",
724
+ payload: readWithdrawRequested(r),
725
+ };
726
+ break;
727
+ case TAG_TAKER_TX_PENDING:
728
+ result = { kind: "TakerTxPending", payload: readTakerTxPending(r) };
729
+ break;
730
+ case TAG_MAKER_TX_PENDING:
731
+ result = { kind: "MakerTxPending", payload: readMakerTxPending(r) };
732
+ break;
733
+ case TAG_DELEGATE_POLICY_CREATED:
734
+ result = {
735
+ kind: "DelegatePolicyCreated",
736
+ payload: readDelegatePolicyCreated(r),
737
+ };
738
+ break;
739
+ case TAG_DELEGATE_POLICY_UPDATED:
740
+ result = {
741
+ kind: "DelegatePolicyUpdated",
742
+ payload: readDelegatePolicyUpdated(r),
743
+ };
744
+ break;
745
+ case TAG_DELEGATE_POLICY_REVOKED:
746
+ result = {
747
+ kind: "DelegatePolicyRevoked",
748
+ payload: readDelegatePolicyRevoked(r),
749
+ };
750
+ break;
751
+ case TAG_LINKED_ADDRESS_REVOKED:
752
+ result = {
753
+ kind: "LinkedAddressRevoked",
754
+ payload: readLinkedAddressRevoked(r),
755
+ };
756
+ break;
757
+ default:
758
+ // Validator-attested / governance / consensus-internal —
759
+ // outside the user-event scope of this module.
760
+ return null;
761
+ }
762
+ if (r.remaining() !== 0) {
763
+ throw new Error(`decodeUserEvent: ${r.remaining()} trailing bytes after ${result.kind}`);
764
+ }
765
+ return result;
766
+ }
767
+ //# sourceMappingURL=event-bcs.js.map