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,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
|