paralith-mcp 0.3.2 → 0.4.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.
@@ -0,0 +1,898 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../paralith-bridge/src/cli.mjs
4
+ import crypto2 from "node:crypto";
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { createPublicClient, createWalletClient, encodeAbiParameters, http, keccak256, parseAbi, recoverMessageAddress, toBytes } from "viem";
9
+ import { privateKeyToAccount } from "viem/accounts";
10
+ import { base as baseChain } from "viem/chains";
11
+ import {
12
+ Connection,
13
+ Ed25519Program,
14
+ Keypair,
15
+ PublicKey,
16
+ SYSVAR_INSTRUCTIONS_PUBKEY,
17
+ Transaction,
18
+ TransactionInstruction,
19
+ sendAndConfirmTransaction
20
+ } from "@solana/web3.js";
21
+ import { SuiGrpcClient } from "@mysten/sui/grpc";
22
+ import { Transaction as SuiTransaction } from "@mysten/sui/transactions";
23
+ import { bcs } from "@mysten/sui/bcs";
24
+ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
25
+
26
+ // ../../backend/native/hbclient.mjs
27
+ import crypto from "node:crypto";
28
+ var CRLF = "\r\n";
29
+ var MAX_HEADER_LENGTH = 4096;
30
+ var DERIVED_COMPONENTS = /* @__PURE__ */ new Set([
31
+ "method",
32
+ "target-uri",
33
+ "authority",
34
+ "scheme",
35
+ "request-target",
36
+ "path",
37
+ "query",
38
+ "query-param"
39
+ ]);
40
+ var b64url = (buf) => Buffer.from(buf).toString("base64url");
41
+ var b64urlDec = (s) => Buffer.from(s, "base64url");
42
+ var b64std = (buf) => Buffer.from(buf).toString("base64");
43
+ var sha256 = (b) => crypto.createHash("sha256").update(b).digest();
44
+ function sfString(s) {
45
+ return '"' + String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
46
+ }
47
+ function sfBareItem(v) {
48
+ if (v && v.__sf === "binary") return ":" + b64std(v.value) + ":";
49
+ if (v && v.__sf === "token") return String(v.value);
50
+ if (v && v.__sf === "string") return sfString(v.value);
51
+ if (typeof v === "number") return String(v);
52
+ return sfString(v);
53
+ }
54
+ var sfStr = (s) => ({ __sf: "string", value: s });
55
+ function sfParams(params) {
56
+ return params.map(([k, v]) => ";" + k + "=" + sfBareItem(v)).join("");
57
+ }
58
+ function sfInnerList(items, params) {
59
+ return "(" + items.map((i) => sfBareItem(i)).join(" ") + ")" + sfParams(params || []);
60
+ }
61
+ function sfDict(entries) {
62
+ return entries.map(([k, v]) => k + "=" + v).join(", ");
63
+ }
64
+ function binCmp(a, b) {
65
+ return Buffer.compare(Buffer.from(a, "binary"), Buffer.from(b, "binary"));
66
+ }
67
+ var sortedKeys = (obj) => Object.keys(obj).sort(binCmp);
68
+ function encodeHeaderValue(v) {
69
+ const s = Buffer.isBuffer(v) ? v.toString("latin1") : String(v);
70
+ return s.replace(/\\/g, "\\\\").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
71
+ }
72
+ var URI_SAFE = /^[a-z0-9.\-_/?&]*$/;
73
+ function escapeKey(k) {
74
+ if (URI_SAFE.test(k)) return k;
75
+ let out = "";
76
+ for (const byte of Buffer.from(k, "utf8")) {
77
+ const c = String.fromCharCode(byte);
78
+ if (/[a-z0-9.\-_/?&]/.test(c)) out += c;
79
+ else out += "%" + byte.toString(16).toUpperCase().padStart(2, "0");
80
+ }
81
+ return out;
82
+ }
83
+ var IS_ID = (k) => /^[a-zA-Z0-9_-]{43}$/.test(k);
84
+ function isPlainObject(v) {
85
+ return v !== null && typeof v === "object" && !Buffer.isBuffer(v) && !Array.isArray(v);
86
+ }
87
+ function toTabm(value) {
88
+ if (Buffer.isBuffer(value)) return value;
89
+ if (typeof value === "string") return Buffer.from(value, "utf8");
90
+ if (Array.isArray(value)) {
91
+ const numbered = {};
92
+ value.forEach((v, i) => {
93
+ numbered[String(i + 1)] = v;
94
+ });
95
+ const m = toTabm(numbered);
96
+ const types = parseAoTypes(m["ao-types"]);
97
+ types["."] = "list";
98
+ m["ao-types"] = Buffer.from(encodeAoTypes(types), "utf8");
99
+ return m;
100
+ }
101
+ if (isPlainObject(value)) {
102
+ const types = {};
103
+ const out = {};
104
+ for (const key of sortedKeys(value)) {
105
+ if (key === "commitments" || key === "priv" || key.startsWith("priv/")) continue;
106
+ const v = value[key];
107
+ if (v === void 0) continue;
108
+ if (typeof v === "string" || Buffer.isBuffer(v)) {
109
+ out[key] = toTabm(v);
110
+ } else if (Array.isArray(v) || isPlainObject(v)) {
111
+ out[key] = toTabm(v);
112
+ } else if (typeof v === "number") {
113
+ if (Number.isInteger(v)) {
114
+ types[key] = "integer";
115
+ out[key] = Buffer.from(String(v));
116
+ } else {
117
+ types[key] = "float";
118
+ out[key] = Buffer.from(String(v));
119
+ }
120
+ } else if (typeof v === "boolean" || v === null) {
121
+ types[key] = "atom";
122
+ out[key] = Buffer.from(v === null ? "null" : String(v));
123
+ }
124
+ }
125
+ if (Object.keys(types).length) out["ao-types"] = Buffer.from(encodeAoTypes(types), "utf8");
126
+ return out;
127
+ }
128
+ return Buffer.from(String(value), "utf8");
129
+ }
130
+ function encodeAoTypes(types) {
131
+ return sfDict(sortedKeys(types).map((k) => [escapeKey(k), sfString(types[k])]));
132
+ }
133
+ function parseAoTypes(bin) {
134
+ if (!bin) return {};
135
+ const s = Buffer.isBuffer(bin) ? bin.toString("utf8") : String(bin);
136
+ const out = {};
137
+ for (const part of s.split(",")) {
138
+ const m = part.trim().match(/^([^=]+)="([^"]*)"$/);
139
+ if (m) out[m[1]] = m[2];
140
+ }
141
+ return out;
142
+ }
143
+ function inlineKey(msg) {
144
+ const explicit = msg["ao-body-key"];
145
+ if (explicit !== void 0) {
146
+ return { extra: {}, key: Buffer.isBuffer(explicit) ? explicit.toString() : String(explicit) };
147
+ }
148
+ if (Object.prototype.hasOwnProperty.call(msg, "body")) return { extra: {}, key: "body" };
149
+ if (Object.prototype.hasOwnProperty.call(msg, "data")) {
150
+ return { extra: { "ao-body-key": Buffer.from("data") }, key: "data" };
151
+ }
152
+ return { extra: {}, key: "body" };
153
+ }
154
+ function groupMaps(map, parent, top) {
155
+ const flattened = {};
156
+ for (const key of sortedKeys(map)) {
157
+ const value = map[key];
158
+ const normKey = key;
159
+ const flatK = parent === "" ? normKey : parent + "/" + normKey;
160
+ if (isPlainObject(value)) {
161
+ if (Object.keys(value).length === 0) {
162
+ top[flatK] = { "ao-types": Buffer.from("empty-message") };
163
+ } else {
164
+ top = groupMaps(value, flatK, top);
165
+ }
166
+ } else {
167
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(String(value));
168
+ if (normKey === "content-disposition" || buf.length > MAX_HEADER_LENGTH) {
169
+ top[flatK] = buf;
170
+ } else {
171
+ flattened[normKey] = buf;
172
+ }
173
+ }
174
+ }
175
+ if (Object.keys(flattened).length === 0) return top;
176
+ if (parent === "") return Object.assign({}, top, flattened);
177
+ top[parent] = flattened;
178
+ return top;
179
+ }
180
+ function encodeFlatMsg(msg) {
181
+ const headerKeys = sortedKeys(msg).filter((k) => k !== "body" && k !== "priv");
182
+ const headers = headerKeys.map((k) => Buffer.concat([Buffer.from(k + ": "), asBuf(msg[k])])).reduce((acc, b, i) => i === 0 ? [b] : acc.concat([Buffer.from(CRLF), b]), []);
183
+ const head = Buffer.concat(headers);
184
+ const body = msg.body === void 0 ? Buffer.alloc(0) : asBuf(msg.body);
185
+ if (body.length === 0) return head;
186
+ return Buffer.concat([head, Buffer.from(CRLF + CRLF), body]);
187
+ }
188
+ var asBuf = (v) => Buffer.isBuffer(v) ? v : Buffer.from(String(v));
189
+ function encodeBodyPart(partName, part, inlKey) {
190
+ const disposition = partName === inlKey ? "inline" : `form-data;name="${partName}"`;
191
+ if (isPlainObject(part)) {
192
+ return encodeFlatMsg(Object.assign({}, part, {
193
+ "content-disposition": Buffer.from(disposition)
194
+ }));
195
+ }
196
+ return Buffer.concat([
197
+ Buffer.from("content-disposition: " + disposition + CRLF + CRLF),
198
+ asBuf(part)
199
+ ]);
200
+ }
201
+ function tabmToHttp(tabm) {
202
+ const stripped = {};
203
+ for (const k of Object.keys(tabm)) {
204
+ if (["commitments", "signature", "signature-input", "priv"].includes(k)) continue;
205
+ stripped[IS_ID(k) ? escapeKey(k) : k] = tabm[k];
206
+ }
207
+ const { extra, key: inlKey } = inlineKey(stripped);
208
+ const headers = {};
209
+ const bodyMap = {};
210
+ for (const [k, v] of Object.entries(extra)) headers[k] = v;
211
+ for (const key of sortedKeys(stripped)) {
212
+ const value = stripped[key];
213
+ if (key === "body") {
214
+ bodyMap["body"] = value;
215
+ continue;
216
+ }
217
+ if (key === inlKey) {
218
+ bodyMap[inlKey] = value;
219
+ continue;
220
+ }
221
+ if (isPlainObject(value)) {
222
+ bodyMap[key] = value;
223
+ continue;
224
+ }
225
+ const buf = asBuf(value);
226
+ if (buf.length <= MAX_HEADER_LENGTH) headers[key] = buf;
227
+ else bodyMap[key] = buf;
228
+ }
229
+ const grouped = groupMaps(bodyMap, "", {});
230
+ const gKeys = sortedKeys(grouped);
231
+ let body = null;
232
+ if (gKeys.length === 0) {
233
+ body = null;
234
+ } else if (gKeys.length === 1 && gKeys[0] === inlKey && Buffer.isBuffer(grouped[inlKey])) {
235
+ body = grouped[inlKey];
236
+ } else {
237
+ const parts = gKeys.map((k) => {
238
+ const v = grouped[k];
239
+ if (isPlainObject(v) && Object.keys(v).length === 1 && Object.prototype.hasOwnProperty.call(v, "body")) {
240
+ return encodeBodyPart(k + "/body", v, "body");
241
+ }
242
+ return encodeBodyPart(k, v, inlKey);
243
+ });
244
+ const joined = parts.reduce(
245
+ (acc, p, i) => i === 0 ? [p] : acc.concat([Buffer.from(CRLF), p]),
246
+ []
247
+ );
248
+ const boundary = b64url(sha256(Buffer.concat(joined)));
249
+ const withMarkers = parts.reduce(
250
+ (acc, p, i) => acc.concat(
251
+ i === 0 ? [] : [Buffer.from(CRLF)],
252
+ [Buffer.from("--" + boundary + CRLF), p]
253
+ ),
254
+ []
255
+ );
256
+ body = Buffer.concat([
257
+ Buffer.concat(withMarkers),
258
+ Buffer.from(CRLF + "--" + boundary + "--")
259
+ ]);
260
+ headers["content-type"] = Buffer.from(`multipart/form-data; boundary="${boundary}"`);
261
+ }
262
+ const outHeaders = {};
263
+ for (const k of Object.keys(headers)) outHeaders[k] = encodeHeaderValue(headers[k]);
264
+ if (body && body.length) {
265
+ outHeaders["content-digest"] = "sha-256=:" + b64std(sha256(body)) + ":";
266
+ }
267
+ return { headers: outHeaders, body };
268
+ }
269
+ function addDerivedSpecifiers(keys) {
270
+ return keys.map((k) => {
271
+ const bare = k.startsWith("@") ? k.slice(1) : k;
272
+ return DERIVED_COMPONENTS.has(bare) ? "@" + bare : bare;
273
+ });
274
+ }
275
+ function signatureBase(encHeaders, committed, params) {
276
+ const lines = committed.map((name) => {
277
+ const v = encHeaders[name];
278
+ if (v === void 0) {
279
+ throw new Error("missing_key_for_signature_component_line: " + name);
280
+ }
281
+ return `"${name}": ${v}`;
282
+ });
283
+ const paramsLine = sfInnerList(
284
+ addDerivedSpecifiers(committed).map((k) => sfStr(k)),
285
+ params
286
+ );
287
+ return Buffer.from(lines.join("\n") + '\n"@signature-params": ' + paramsLine, "utf8");
288
+ }
289
+ function commitmentParams({ alg, bundle, created, expires, keyid, nonce, tag }) {
290
+ const out = [];
291
+ if (alg !== void 0) out.push(["alg", sfStr(alg)]);
292
+ if (bundle !== void 0) out.push(["bundle", sfStr(bundle)]);
293
+ if (created !== void 0) out.push(["created", created]);
294
+ if (expires !== void 0) out.push(["expires", expires]);
295
+ if (keyid !== void 0) out.push(["keyid", sfStr(keyid)]);
296
+ if (nonce !== void 0) out.push(["nonce", sfStr(nonce)]);
297
+ if (tag !== void 0) out.push(["tag", sfStr(tag)]);
298
+ return out;
299
+ }
300
+ function jwkToPrivateKey(jwk) {
301
+ return crypto.createPrivateKey({ key: { ...jwk, kty: "RSA" }, format: "jwk" });
302
+ }
303
+ function pubKeyBuf(jwk) {
304
+ return b64urlDec(jwk.n);
305
+ }
306
+ function ownerToAddress(nB64url) {
307
+ return b64url(sha256(b64urlDec(nB64url)));
308
+ }
309
+ function jwkToAddress(jwk) {
310
+ return ownerToAddress(jwk.n);
311
+ }
312
+ function rsaPssSign(jwk, data) {
313
+ return crypto.sign("sha512", data, {
314
+ key: jwkToPrivateKey(jwk),
315
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
316
+ saltLength: 64
317
+ // SHA-512 digest length; ar_wallet/rsa_pss uses saltLen == hashLen
318
+ });
319
+ }
320
+ function wireToMessageKeys(wire, bodyKeys, headers) {
321
+ let out = [];
322
+ for (const k of wire) {
323
+ if (k === "content-digest") out.push(...bodyKeys);
324
+ else out.push(k);
325
+ }
326
+ if (headers["ao-body-key"] !== void 0) {
327
+ const abk = String(headers["ao-body-key"]);
328
+ out = out.flatMap((k) => k === "body" ? [abk] : [k]).filter((k) => k !== "ao-body-key");
329
+ }
330
+ if (/^multipart\//.test(String(headers["content-type"] || ""))) {
331
+ out = out.filter((k) => k !== "content-type");
332
+ }
333
+ return out;
334
+ }
335
+ function isListTabm(sub) {
336
+ const t = parseAoTypes(sub["ao-types"]);
337
+ return t["."] === "list";
338
+ }
339
+ function hmacCommit(subTabm) {
340
+ const bare = { ...subTabm };
341
+ delete bare.commitments;
342
+ const { headers } = tabmToHttp(bare);
343
+ const wire = sortedKeys(headers).filter((k) => k !== "signature" && k !== "signature-input");
344
+ const params = commitmentParams({ alg: "hmac-sha256", keyid: "constant:ao" });
345
+ const base = signatureBase(headers, wire, params);
346
+ const mac = crypto.createHmac("sha256", "constant:ao").update(base).digest();
347
+ const id = b64url(mac);
348
+ const rawInputs = sortedKeys(bare);
349
+ const bodyKeys = rawInputs.filter((k) => headers[k] === void 0 && headers[k + "+link"] === void 0);
350
+ const committed = wireToMessageKeys(wire, bodyKeys, headers);
351
+ return {
352
+ ...subTabm,
353
+ commitments: {
354
+ [id]: {
355
+ "commitment-device": Buffer.from("httpsig@1.0"),
356
+ committed: toTabm(committed),
357
+ keyid: Buffer.from("constant:ao"),
358
+ signature: Buffer.from(id),
359
+ type: Buffer.from("hmac-sha256")
360
+ }
361
+ }
362
+ };
363
+ }
364
+ function withNestedHmacs(tabm) {
365
+ if (!isPlainObject(tabm)) return tabm;
366
+ const out = {};
367
+ for (const [k, v] of Object.entries(tabm)) {
368
+ if (k === "commitments") {
369
+ out[k] = v;
370
+ continue;
371
+ }
372
+ if (isPlainObject(v)) {
373
+ const inner = withNestedHmacs(v);
374
+ out[k] = isListTabm(v) || v.commitments ? inner : hmacCommit(inner);
375
+ } else out[k] = v;
376
+ }
377
+ return out;
378
+ }
379
+ var durMs = (from, to) => Number(to - from) / 1e6;
380
+ function commit(msg, jwk, opts = {}) {
381
+ const { bundle = "true", nestedHmacs = true, ...params0 } = opts;
382
+ const tEncode = process.hrtime.bigint();
383
+ const tabm = nestedHmacs ? withNestedHmacs(toTabm(msg)) : toTabm(msg);
384
+ const { headers, body } = tabmToHttp(tabm);
385
+ const committed = sortedKeys(headers).filter(
386
+ (k) => k !== "signature" && k !== "signature-input"
387
+ );
388
+ const keyid = "publickey:" + b64std(pubKeyBuf(jwk));
389
+ const params = commitmentParams({ alg: "rsa-pss-sha512", bundle, keyid, ...params0 });
390
+ const base = signatureBase(headers, committed, params);
391
+ const tSign = process.hrtime.bigint();
392
+ const sig = rsaPssSign(jwk, base);
393
+ const tSeal = process.hrtime.bigint();
394
+ const name = "comm-" + b64url(sha256(sig)).toLowerCase();
395
+ const outHeaders = { ...headers };
396
+ outHeaders["signature"] = sfDict([[name, ":" + b64std(sig) + ":"]]);
397
+ outHeaders["signature-input"] = sfDict([[name, sfInnerList(
398
+ addDerivedSpecifiers(committed).map((k) => sfStr(k)),
399
+ params
400
+ )]]);
401
+ return {
402
+ headers: outHeaders,
403
+ body,
404
+ signatureBase: base,
405
+ committed,
406
+ sigName: name,
407
+ timing: {
408
+ encodeMs: durMs(tEncode, tSign),
409
+ signMs: durMs(tSign, tSeal),
410
+ sealMs: durMs(tSeal, process.hrtime.bigint())
411
+ }
412
+ };
413
+ }
414
+ var httpRequests = 0;
415
+ async function send(node, path2, method, headers, body) {
416
+ const url = transportNode(node) + (path2.startsWith("/") ? path2 : "/" + path2);
417
+ httpRequests += 1;
418
+ const res = await fetch(url, {
419
+ method,
420
+ headers: { ...headers, "accept-bundle": "true" },
421
+ body: body && body.length ? body : void 0,
422
+ redirect: "follow"
423
+ });
424
+ const buf = Buffer.from(await res.arrayBuffer());
425
+ const h = {};
426
+ res.headers.forEach((v, k) => {
427
+ h[k] = v;
428
+ });
429
+ return { status: res.status, headers: h, body: buf, url };
430
+ }
431
+ function transportNode(node) {
432
+ return (process.env.NODE_TRANSPORT_URL || node).replace(/\/$/, "");
433
+ }
434
+ function hbError(res) {
435
+ const t = res.body.toString();
436
+ const m = t.match(/Error details:([\s\S]*?)<\/pre>/);
437
+ const dec = (x) => x.replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&#39;/g, "'");
438
+ if (m) return dec(m[1]).replace(/\s+/g, " ").trim();
439
+ return res.headers["details"] || t.slice(0, 400);
440
+ }
441
+ async function postSigned(node, path2, msg, jwk, opts = {}) {
442
+ const { headers, body, signatureBase: base, committed, timing } = commit(msg, jwk, opts);
443
+ const tPost = process.hrtime.bigint();
444
+ const res = await send(node, path2, "POST", headers, body);
445
+ const postMs = durMs(tPost, process.hrtime.bigint());
446
+ res.request = { headers, body, base, committed };
447
+ res.timing = { ...timing, postMs, bytes: body ? body.length : 0 };
448
+ return res;
449
+ }
450
+ var SCHEDULE_ROUTE = /* @__PURE__ */ new Map();
451
+ var sendObserver = null;
452
+ var sendRoles = {};
453
+ var unsettledSends = [];
454
+ function observeSend(timing, observer = sendObserver) {
455
+ if (!observer) return;
456
+ try {
457
+ observer(timing);
458
+ } catch {
459
+ }
460
+ }
461
+ async function sendMessage({
462
+ node,
463
+ jwk,
464
+ process: pid,
465
+ action,
466
+ tags = {},
467
+ data,
468
+ body,
469
+ path: path2,
470
+ push = true
471
+ }) {
472
+ const t0 = Date.now();
473
+ const started = process.hrtime.bigint();
474
+ const fields = /* @__PURE__ */ new Map();
475
+ const put = (k, v) => {
476
+ if (v === void 0 || v === null) return;
477
+ fields.set(String(k).toLowerCase(), typeof v === "string" ? v : String(v));
478
+ };
479
+ put("target", pid);
480
+ put("type", "Message");
481
+ put("subject", "self");
482
+ if (action) put("action", action);
483
+ for (const [k, v] of Object.entries(tags)) put(k, v);
484
+ if (data !== void 0) put("data", data);
485
+ put("random-seed", String(Math.floor(Math.random() * 1e9)));
486
+ const msg = {
487
+ ...Object.fromEntries(fields),
488
+ ...body === void 0 ? {} : { body }
489
+ };
490
+ const sentAction = fields.get("action")?.toLowerCase() ?? null;
491
+ const timing = (outcome, { res = null, slot = null, error: error2 } = {}) => {
492
+ const t = res?.timing;
493
+ return {
494
+ action: sentAction,
495
+ slot: slot === null || slot === void 0 ? null : Number(slot),
496
+ node,
497
+ pid,
498
+ pidRole: sentAction?.startsWith("admin.") ? "admin" : sendRoles[pid] ?? null,
499
+ t0,
500
+ t1: Date.now(),
501
+ buildMs: t ? t.encodeMs : 0,
502
+ signMs: t ? t.signMs + t.sealMs : 0,
503
+ postMs: t ? t.postMs : 0,
504
+ sendMs: durMs(started, process.hrtime.bigint()),
505
+ bytes: t ? t.bytes : 0,
506
+ // As in `TransportTiming`: a reply was read, which a refusal also is.
507
+ ok: outcome === "ok" || outcome === "rejected",
508
+ outcome,
509
+ ...error2 === void 0 ? {} : { error: error2 }
510
+ };
511
+ };
512
+ const report = (outcome, detail) => {
513
+ if (sendObserver) observeSend(timing(outcome, detail));
514
+ };
515
+ const candidates = path2 ? [path2] : [`/${pid}~process@1.0/schedule`, `/${pid}~scheduler@1.0/schedule`, `/${pid}/schedule`];
516
+ const known = SCHEDULE_ROUTE.get(node);
517
+ const ordered = known && !path2 ? [known, ...candidates.filter((p) => p !== known)] : candidates;
518
+ const tried = [];
519
+ let lastResponse = null;
520
+ for (const candidate of ordered) {
521
+ let res;
522
+ try {
523
+ res = await postSigned(node, candidate, msg, jwk);
524
+ } catch (error2) {
525
+ report("post-failed", { error: error2 instanceof Error ? `${error2.name}: ${error2.message}` : String(error2) });
526
+ throw error2;
527
+ }
528
+ lastResponse = res;
529
+ if (res.status === 200) {
530
+ if (!path2) SCHEDULE_ROUTE.set(node, candidate);
531
+ const slot = res.headers["slot"];
532
+ if (push && slot !== void 0 && slot !== null) {
533
+ queuePush({ node, process: pid, slot });
534
+ }
535
+ if (sendObserver && slot !== void 0 && slot !== null && Number.isSafeInteger(Number(slot))) {
536
+ unsettledSends.push({
537
+ node: transportNode(node),
538
+ pid,
539
+ slot: Number(slot),
540
+ timing: timing("accepted-unread", { res, slot }),
541
+ observer: sendObserver
542
+ });
543
+ } else {
544
+ report("accepted-unread", { res, slot });
545
+ }
546
+ return { slot, headers: res.headers };
547
+ }
548
+ tried.push(`${candidate} -> ${res.status} ${hbError(res)}`);
549
+ if (res.status !== 404) break;
550
+ }
551
+ const error = new Error(`send failed on ${node}:
552
+ ${tried.join("\n ")}`);
553
+ error.status = lastResponse?.status ?? null;
554
+ report(
555
+ error.status === 429 ? "rate-limited" : error.status === 408 ? "timeout" : "post-failed",
556
+ { res: lastResponse, error: error.message }
557
+ );
558
+ throw error;
559
+ }
560
+ async function pushSlot({ node, process: pid, slot }) {
561
+ try {
562
+ const res = await send(node, `/${pid}~process@1.0/push&slot=${slot}`, "GET", {});
563
+ return res.status === 200;
564
+ } catch {
565
+ return false;
566
+ }
567
+ }
568
+ var PUSH_QUEUES = /* @__PURE__ */ new Map();
569
+ function queuePush({ node, process: pid, slot }) {
570
+ const key = `${node}|${pid}`;
571
+ const tail = (PUSH_QUEUES.get(key) ?? Promise.resolve()).then(() => pushSlot({ node, process: pid, slot })).catch(() => false);
572
+ PUSH_QUEUES.set(key, tail);
573
+ return tail;
574
+ }
575
+ async function pendingPushes({ node, process: pid } = {}) {
576
+ const queues = node && pid ? [PUSH_QUEUES.get(`${node}|${pid}`)] : [...PUSH_QUEUES.values()];
577
+ await Promise.all(queues.filter(Boolean));
578
+ }
579
+ process.on("beforeExit", () => {
580
+ void pendingPushes();
581
+ });
582
+
583
+ // ../paralith-bridge/src/cli.mjs
584
+ var MANIFEST = "https://bridge.paralithmarkets.com/bridge.json";
585
+ var HELP = `paralith-bridge <command>
586
+
587
+ status the set, open changes, each chain's epoch
588
+ propose-add <node-url> propose adding a node (its keys are read from the node)
589
+ propose-remove <node-ao> propose removing one
590
+ propose-migrate <new-pusd> propose moving the set to a replacement PUSD; every node follows
591
+ approve <id> approve as operator; your nodes then vote by themselves
592
+ apply <id> carry the votes to Base, Solana and Sui (pays each chain's gas;
593
+ --chain base|solana|sui for one)
594
+ pause stop PUSD minting, redeeming and signing releases (any operator)
595
+ unpause start it again (the PUSD owner only)
596
+
597
+ Keys are read from ~/.paralith/bridge/ unless a flag names another file:
598
+ operator.json the operator's Arweave keyfile (--wallet <file>)
599
+ base.key Base gas payer, 0x-hex private key (--key <file>)
600
+ solana.json Solana gas payer, keypair JSON array (--key <file>)
601
+ sui.key Sui gas payer, suiprivkey1... (--key <file>)
602
+
603
+ --operator <addr> propose-add: the new node's operator (default: --wallet's address)
604
+ --rpc <url> apply's RPC (or PARALITH_BASE_RPC / _SOLANA_RPC / _SUI_RPC)
605
+ --pusd <id> act on this PUSD, not the manifest's (approve on a PUSD being replaced)
606
+ --manifest <url> the deployment description (default ${MANIFEST})`;
607
+ var CHAINS = ["base", "solana", "sui"];
608
+ var argv = process.argv.slice(2);
609
+ var opt = (name) => {
610
+ const i = argv.indexOf(`--${name}`);
611
+ return i >= 0 ? argv[i + 1] : void 0;
612
+ };
613
+ var positional = argv.filter((a, i) => !a.startsWith("--") && !argv[i - 1]?.startsWith("--"));
614
+ var [cmd, target] = positional;
615
+ var die = (msg) => {
616
+ console.error(`error: ${msg}`);
617
+ process.exit(1);
618
+ };
619
+ var KEYS = path.join(os.homedir(), ".paralith", "bridge");
620
+ var readFile = (file, what) => {
621
+ try {
622
+ return fs.readFileSync(file, "utf8").trim();
623
+ } catch {
624
+ die(`no ${what} at ${file}`);
625
+ }
626
+ };
627
+ var threshold = (n) => Math.floor((3 * n + 4) / 5);
628
+ async function getJson(url) {
629
+ const r = await fetch(url, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(15e3) });
630
+ const text = await r.text();
631
+ if (!r.ok) throw new Error(`${url}: HTTP ${r.status}`);
632
+ if (text.trimStart().startsWith("<")) throw new Error(`${url}: not found`);
633
+ return JSON.parse(text);
634
+ }
635
+ if (!["status", "propose-add", "propose-remove", "propose-migrate", "approve", "apply"].includes(cmd)) {
636
+ console.log(HELP);
637
+ process.exit(cmd ? 1 : 0);
638
+ }
639
+ var deployment = await getJson(opt("manifest") ?? MANIFEST).catch((e) => die(`manifest: ${e.message}`));
640
+ var NODE = deployment.node;
641
+ var PUSD = opt("pusd") ?? deployment.pusd;
642
+ async function bridgeInfo(pid = PUSD) {
643
+ let v = await getJson(`${NODE}/${pid}~process@1.0/now/bridgeinfo`);
644
+ if (v && typeof v === "object" && "body" in v) v = v.body;
645
+ return typeof v === "string" ? JSON.parse(v) : v;
646
+ }
647
+ var proposal = (info, id) => (info.proposals ?? []).find((p) => String(p.id) === String(id)) ?? die(`no proposal #${id}`);
648
+ function wallet() {
649
+ const jwk = JSON.parse(readFile(
650
+ opt("wallet") ?? process.env.PARALITH_AO_WALLET ?? path.join(KEYS, "operator.json"),
651
+ "operator wallet (your operator's Arweave keyfile)"
652
+ ));
653
+ return { jwk, address: jwkToAddress(jwk) };
654
+ }
655
+ async function pusd(tags) {
656
+ const w = wallet();
657
+ console.log(`signing as ${w.address}`);
658
+ const { slot } = await sendMessage({ node: NODE, jwk: w.jwk, process: PUSD, action: tags.Action, tags });
659
+ for (let i = 0; i < 40; i++) {
660
+ await new Promise((r) => setTimeout(r, 1500));
661
+ try {
662
+ const r = await fetch(
663
+ `${NODE}/${PUSD}~process@1.0/compute&slot=${slot}/results/output/data`,
664
+ { headers: { accept: "text/plain" }, signal: AbortSignal.timeout(3e4) }
665
+ );
666
+ const body = r.ok ? (await r.text()).trim() : "";
667
+ if (!body || body.startsWith("<")) continue;
668
+ const reply = JSON.parse(body);
669
+ if (reply.error) die(reply.error);
670
+ return reply;
671
+ } catch {
672
+ }
673
+ }
674
+ die(`sent (slot ${slot}) but no reply yet; run status before sending again`);
675
+ }
676
+ var RELEASE_ATTESTORS = "PARALITH_ATTESTORS_V1";
677
+ var hexBytes = (h) => toBytes(h);
678
+ var u64 = (n) => {
679
+ const b = Buffer.alloc(8);
680
+ b.writeBigUInt64LE(BigInt(n));
681
+ return b;
682
+ };
683
+ var utf8 = (s) => Buffer.from(s, "utf8");
684
+ function voters(p) {
685
+ const v = p.prior.flatMap((member, at) => p.votes?.[member.ao] ? [{ at, member, vote: p.votes[member.ao] }] : []);
686
+ if (v.length < threshold(p.prior.length)) die(`#${p.id} has ${v.length} of ${threshold(p.prior.length)} votes`);
687
+ return v;
688
+ }
689
+ var rpcFor = (chain) => opt("rpc") ?? process.env[`PARALITH_${chain.toUpperCase()}_RPC`] ?? deployment[chain].rpc;
690
+ var KEY_FILE = { base: "base.key", solana: "solana.json", sui: "sui.key" };
691
+ var keyFor = (chain) => readFile(opt("key") ?? process.env[`PARALITH_${chain.toUpperCase()}_KEY`] ?? path.join(KEYS, KEY_FILE[chain]), `${chain} gas payer key`);
692
+ var BASE_ABI = parseAbi([
693
+ "function setAttestors(address[] set, uint64 newEpoch, bytes[] sigs)",
694
+ "function epoch() view returns (uint64)"
695
+ ]);
696
+ function baseClients(withKey) {
697
+ const cfg = deployment.base;
698
+ const chain = { ...baseChain, id: cfg.chainId, rpcUrls: { default: { http: [rpcFor("base")] } } };
699
+ const pub = createPublicClient({ chain, transport: http(rpcFor("base")) });
700
+ if (!withKey) return { pub };
701
+ const account = privateKeyToAccount(keyFor("base"));
702
+ return { pub, account, wallet: createWalletClient({ account, chain, transport: http(rpcFor("base")) }) };
703
+ }
704
+ var solanaProgram = () => new PublicKey(deployment.solana.programId);
705
+ var solanaConfig = () => PublicKey.findProgramAddressSync([utf8("config")], solanaProgram())[0];
706
+ var anchorIx = (name) => crypto2.createHash("sha256").update(`global:${name}`).digest().subarray(0, 8);
707
+ function ed25519Ix(entries, msg) {
708
+ const head = 2 + 14 * entries.length;
709
+ const msgOff = head + 96 * entries.length;
710
+ const data = Buffer.alloc(msgOff + msg.length);
711
+ data[0] = entries.length;
712
+ entries.forEach(({ pubkey, sig }, i) => {
713
+ const at = head + 96 * i;
714
+ const e = 2 + 14 * i;
715
+ [at + 32, 65535, at, 65535, msgOff, msg.length, 65535].forEach((v, k) => data.writeUInt16LE(v, e + 2 * k));
716
+ data.set(pubkey, at);
717
+ data.set(sig, at + 32);
718
+ });
719
+ data.set(msg, msgOff);
720
+ return new TransactionInstruction({ programId: Ed25519Program.programId, keys: [], data });
721
+ }
722
+ var suiClient = () => new SuiGrpcClient({ network: deployment.sui.network, baseUrl: rpcFor("sui") });
723
+ var vecBytes = bcs.vector(bcs.vector(bcs.u8()));
724
+ var vecU64 = bcs.vector(bcs.u64());
725
+ var epochOf = {
726
+ async base() {
727
+ return Number(await baseClients(false).pub.readContract({ address: deployment.base.bridge, abi: BASE_ABI, functionName: "epoch" }));
728
+ },
729
+ async solana() {
730
+ const info = await new Connection(rpcFor("solana"), "confirmed").getAccountInfo(solanaConfig());
731
+ if (!info) throw new Error("config not found");
732
+ const n = info.data.readUInt32LE(40);
733
+ return Number(info.data.readBigUInt64LE(44 + 32 * n));
734
+ },
735
+ async sui() {
736
+ const tx = new SuiTransaction();
737
+ tx.moveCall({ target: `${deployment.sui.package}::para_bridge::epoch`, arguments: [tx.object(deployment.sui.bridge)] });
738
+ tx.setSender("0x0");
739
+ const r = await suiClient().simulateTransaction({ transaction: tx, include: { commandResults: true }, checksEnabled: false });
740
+ const bytes = r.commandResults?.[0]?.returnValues?.[0]?.bcs;
741
+ if (!bytes) throw new Error("epoch read returned nothing");
742
+ return Number(Buffer.from(bytes).readBigUInt64LE(0));
743
+ }
744
+ };
745
+ var applyOn = {
746
+ async base(p) {
747
+ const { pub, wallet: w, account } = baseClients(true);
748
+ console.log(`paying as ${account.address}`);
749
+ const set = p.set.map((m) => m.base);
750
+ const epoch = BigInt(p.epoch);
751
+ const inner = keccak256(encodeAbiParameters(
752
+ [{ type: "bytes32" }, { type: "uint256" }, { type: "address" }, { type: "uint64" }, { type: "address[]" }],
753
+ [keccak256(toBytes(RELEASE_ATTESTORS)), BigInt(deployment.base.chainId), deployment.base.bridge, epoch, set]
754
+ ));
755
+ const allowed = new Set(p.prior.map((m) => m.base.toLowerCase()));
756
+ const rows = await Promise.all(voters(p).map(async (v) => ({
757
+ sig: v.vote.base,
758
+ who: (await recoverMessageAddress({ message: { raw: inner }, signature: v.vote.base })).toLowerCase()
759
+ })));
760
+ const sigs = rows.filter((r2) => allowed.has(r2.who)).sort((a, b) => BigInt(a.who) < BigInt(b.who) ? -1 : 1).map((r2) => r2.sig);
761
+ const hash = await w.writeContract({ address: deployment.base.bridge, abi: BASE_ABI, functionName: "setAttestors", args: [set, epoch, sigs] });
762
+ const r = await pub.waitForTransactionReceipt({ hash });
763
+ if (r.status !== "success") die(`reverted ${hash}`);
764
+ return hash;
765
+ },
766
+ async solana(p) {
767
+ const payer = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(keyFor("solana"))));
768
+ console.log(`paying as ${payer.publicKey.toBase58()}`);
769
+ const program = solanaProgram();
770
+ const set = p.set.map((m) => new PublicKey(m.solana));
771
+ const msg = Buffer.concat([
772
+ utf8(RELEASE_ATTESTORS),
773
+ program.toBuffer(),
774
+ u64(p.epoch),
775
+ Buffer.from([set.length]),
776
+ ...set.map((k) => k.toBuffer())
777
+ ]);
778
+ const len = Buffer.alloc(4);
779
+ len.writeUInt32LE(set.length);
780
+ const tx = new Transaction().add(
781
+ ed25519Ix(voters(p).map((v) => ({ pubkey: new PublicKey(v.member.solana).toBytes(), sig: hexBytes(v.vote.solana) })), msg),
782
+ new TransactionInstruction({
783
+ programId: program,
784
+ keys: [
785
+ { pubkey: solanaConfig(), isSigner: false, isWritable: true },
786
+ { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false }
787
+ ],
788
+ data: Buffer.concat([anchorIx("set_attestors"), len, ...set.map((k) => k.toBuffer()), u64(p.epoch)])
789
+ })
790
+ );
791
+ return sendAndConfirmTransaction(new Connection(rpcFor("solana"), "confirmed"), tx, [payer], { commitment: "confirmed" });
792
+ },
793
+ async sui(p) {
794
+ const signer = Ed25519Keypair.fromSecretKey(keyFor("sui"));
795
+ console.log(`paying as ${signer.getPublicKey().toSuiAddress()}`);
796
+ const v = voters(p);
797
+ const tx = new SuiTransaction();
798
+ tx.moveCall({
799
+ target: `${deployment.sui.package}::para_bridge::set_attestors`,
800
+ arguments: [
801
+ tx.object(deployment.sui.bridge),
802
+ tx.pure(vecBytes.serialize(p.set.map((m) => Array.from(hexBytes(m.sui))))),
803
+ tx.pure.u64(BigInt(p.epoch)),
804
+ tx.pure(vecU64.serialize(v.map((x) => BigInt(x.at)))),
805
+ tx.pure(vecBytes.serialize(v.map((x) => Array.from(hexBytes(x.vote.sui)))))
806
+ ]
807
+ });
808
+ const client = suiClient();
809
+ const r = await client.signAndExecuteTransaction({ transaction: tx, signer, include: { effects: true } });
810
+ const t = r.Transaction ?? r.FailedTransaction;
811
+ if (!t.status.success) die(`failed ${t.digest}: ${JSON.stringify(t.status.error)}`);
812
+ await client.waitForTransaction({ digest: t.digest });
813
+ return t.digest;
814
+ }
815
+ };
816
+ var short = (s = "") => s.length > 18 ? `${s.slice(0, 8)}\u2026${s.slice(-6)}` : s;
817
+ async function status() {
818
+ const info = await bridgeInfo();
819
+ const set = info.attestors ?? [];
820
+ console.log(`PUSD ${PUSD} on ${NODE}`);
821
+ if (info.predecessor) console.log(`replaces ${info.predecessor}`);
822
+ if (info.pausedBy) console.log(`PAUSED by ${info.pausedBy}`);
823
+ if (info.successor) console.log(`MOVED to ${info.successor}: the set and every node follow it there`);
824
+ console.log(`set: ${set.length} node(s), k ${threshold(set.length)}, epoch ${info.epoch ?? 0}`);
825
+ for (const m of set) console.log(` ${m.ao} operator ${m.operator ?? "-"}`);
826
+ const epochs = await Promise.all(CHAINS.map((c) => epochOf[c]().catch((e) => `? (${e.message})`)));
827
+ console.log(`chains: ${CHAINS.map((c, i) => `${c} ${epochs[i]}`).join(", ")}`);
828
+ for (const p of info.proposals ?? []) {
829
+ if (p.status === "superseded") continue;
830
+ const need = threshold(p.prior.length);
831
+ const behind = CHAINS.filter((c, i) => typeof epochs[i] === "number" && epochs[i] < p.epoch);
832
+ if (p.status === "applied" && !behind.length) continue;
833
+ console.log(`#${p.id} ${p.op} ${p.op === "migrate" ? `to ${p.successor}` : `${short(p.member?.ao)} -> epoch ${p.epoch}`}: ${p.status}, approved ${Object.keys(p.approvals ?? {}).length}, votes ${Object.keys(p.votes ?? {}).length}/${need}` + (p.status === "applied" ? `, apply on: ${behind.join(" ")}` : ""));
834
+ }
835
+ }
836
+ async function main() {
837
+ if (cmd === "status") return status();
838
+ if (cmd === "propose-add") {
839
+ if (!target) die("propose-add <node-url>");
840
+ const url = target.replace(/\/+$/, "").replace(/^(?!https?:\/\/)/, "https://");
841
+ const k = await getJson(`${url}/~bridge@1.0/keys`);
842
+ if (!k.ao || !k.base || !k.solana || !k.sui) die(`${url} published incomplete keys`);
843
+ const operator = opt("operator") ?? wallet().address;
844
+ console.log(`node ${k.ao}
845
+ base ${k.base}
846
+ solana ${k.solana}
847
+ sui ${k.sui}
848
+ operator ${operator}`);
849
+ const r = await pusd({ Action: "Attestor-Propose", Op: "add", Ao: k.ao, Base: k.base, Solana: k.solana, Sui: k.sui, Operator: operator });
850
+ return console.log(`proposed #${r.ProposalId ?? "?"}; next: approve ${r.ProposalId ?? "<id>"}`);
851
+ }
852
+ if (cmd === "propose-remove") {
853
+ if (!target) die("propose-remove <node-ao>");
854
+ const r = await pusd({ Action: "Attestor-Propose", Op: "remove", Ao: target });
855
+ return console.log(`proposed #${r.ProposalId ?? "?"}; next: approve ${r.ProposalId ?? "<id>"}`);
856
+ }
857
+ if (cmd === "propose-migrate") {
858
+ if (!target) die("propose-migrate <new-pusd>");
859
+ const next = await bridgeInfo(target);
860
+ if (!next.predecessor) die(`${target} names no predecessor`);
861
+ PUSD = next.predecessor;
862
+ const here = await bridgeInfo();
863
+ const members = (i) => JSON.stringify((i.attestors ?? []).map((m) => [m.ao, m.base, m.solana, m.sui]));
864
+ if (members(next) !== members(here) || Number(next.epoch) !== Number(here.epoch)) {
865
+ die(`${target} does not hold ${PUSD}'s set at epoch ${here.epoch}; nodes would refuse to follow it`);
866
+ }
867
+ console.log(`moving the set of ${PUSD}
868
+ to ${target}`);
869
+ const r = await pusd({ Action: "Attestor-Propose", Op: "migrate", Successor: target });
870
+ return console.log(`proposed #${r.ProposalId ?? "?"}; next: approve ${r.ProposalId ?? "<id>"} --pusd ${PUSD}`);
871
+ }
872
+ if (cmd === "approve") {
873
+ if (!target) die("approve <id>");
874
+ await pusd({ Action: "Attestor-Approve", ProposalId: String(target) });
875
+ return console.log(`approved #${target}; your nodes vote on their next pass`);
876
+ }
877
+ if (cmd === "pause" || cmd === "unpause") {
878
+ await pusd({ Action: cmd === "pause" ? "Bridge-Pause" : "Bridge-Unpause" });
879
+ return console.log(cmd === "pause" ? "paused" : "running");
880
+ }
881
+ if (cmd === "apply") {
882
+ const chain = opt("chain");
883
+ if (!target || chain && !CHAINS.includes(chain)) die("apply <id> [--chain base|solana|sui]");
884
+ const p = proposal(await bridgeInfo(), target);
885
+ if (p.status !== "applied") die(`#${p.id} is ${p.status}; it needs its votes first`);
886
+ for (const c of chain ? [chain] : CHAINS) {
887
+ const now = await epochOf[c]();
888
+ if (now >= p.epoch) {
889
+ console.log(`${c}: already at epoch ${now}`);
890
+ continue;
891
+ }
892
+ if (now !== p.epoch - 1) die(`${c} is at epoch ${now}; apply the change to epoch ${now + 1} first`);
893
+ console.log(`${c}: epoch ${p.epoch}, ${await applyOn[c](p)}`);
894
+ }
895
+ return;
896
+ }
897
+ }
898
+ await main().catch((e) => die(e.shortMessage ?? e.message ?? String(e)));