paralith-mcp 0.4.1 → 0.4.2
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/dist/bridge.mjs +1 -1
- package/dist/client.mjs +12220 -12220
- package/dist/graph.json +37 -37
- package/dist/http.mjs +129 -20
- package/dist/server.mjs +129 -20
- package/package.json +52 -52
package/dist/graph.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"node": "https://hyperbeam.tylerw.ai",
|
|
3
|
-
"owner": "DA9qhP25ZPz6MHIhO-7aNHDN3LsTAL7yCKYIkqr13Z8",
|
|
4
|
-
"deployedAt": "2026-10-12T12:57:04.194Z",
|
|
5
|
-
"processes": {
|
|
6
|
-
"game": "AUitn972WGLDj0pMCVbXXacTtOk2PNchgP2qaP_kNoI",
|
|
7
|
-
"rune": "rYfYbQu9vIin_FKcpkxLtcF21pIQbdY-pOkkM5igyVg",
|
|
8
|
-
"pusd": "N4VZxxFb4qArB4SJawvqnLZSRPX9GFs7iGnRQ3Kr-4M",
|
|
9
|
-
"dao": "YAPCQK-3STKc-XND5-O4v-wEQBv7M4S-i__rkQKvQdk",
|
|
10
|
-
"elements": {
|
|
11
|
-
"earth": "wCtjJMScFI6jjioNQdRDUdyFWiqYubaRYyUeeMYYOjI",
|
|
12
|
-
"air": "1he9rnfufmEoPFeDYV_LzkA7e3AM98MXARal2Oq-4lc",
|
|
13
|
-
"fire": "4lffB0KjBEix1I7g1nBmeIdllhqhyHH75NiUQs5De2o",
|
|
14
|
-
"water": "EwiZp-92W7k8xZntMs2u3HAJ-RdDqTdAzvia_6KxlI0"
|
|
15
|
-
},
|
|
16
|
-
"internalVenue": "_ShRHGFliguTz51f7N_j69HDdgt-AI7U1LNG6c_uvwc",
|
|
17
|
-
"externalVenue": "DhI-4XuwkVxfbzp2tk7PgclUD7lVOJa-kNF2VVhLyDk",
|
|
18
|
-
"hunt": "kKpOdP--5hA3fy-i5q6-agoHFNKuYW4c_9Qg8NvAIew",
|
|
19
|
-
"huntWorkers": [
|
|
20
|
-
"kKpOdP--5hA3fy-i5q6-agoHFNKuYW4c_9Qg8NvAIew",
|
|
21
|
-
"NsIK9D0VScuNdf7-avycKxPSTEiHXPBlbFuCEv5ZEuw",
|
|
22
|
-
"JYtYuPA5weAbXykDnmDUhlU3LacxkY15qWppEDtTP4s",
|
|
23
|
-
"lJgsZ3GadofVafa8FI3C6fGTf6Aq0yfZFi44P6vRti0",
|
|
24
|
-
"GUMGAcSdCmKhb75M3NHUkLsdyoAHu7ixyuEldDsXA3c"
|
|
25
|
-
],
|
|
26
|
-
"battleWorkers": [
|
|
27
|
-
"MgxNZ60Ejc01E5MeuZ1s_8PeLNhExjOz9wUGo3utE1w",
|
|
28
|
-
"Ww_GXme0Sn3-jIcji0dPxV0nXLYqBesChXqqXQTC0Hc",
|
|
29
|
-
"4YYgYXTzS_PjbQMthDwf93zik-kvoXYTzrNHA30ijAY",
|
|
30
|
-
"EWlpRzX-waNNKj0bvT9pzj_N4_bv2ndYPMOJaiVkWlw",
|
|
31
|
-
"v5_n4eOw24s1wGjI8-fjoGTfQHwjcO-tcHFxIiO5Qrk"
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
"huntNode": "https://hyperbeam.tylerw.ai",
|
|
35
|
-
"marketNode": "https://hyperbeam.tylerw.ai",
|
|
36
|
-
"venueNode": "https://hyperbeam.tylerw.ai"
|
|
37
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"node": "https://hyperbeam.tylerw.ai",
|
|
3
|
+
"owner": "DA9qhP25ZPz6MHIhO-7aNHDN3LsTAL7yCKYIkqr13Z8",
|
|
4
|
+
"deployedAt": "2026-10-12T12:57:04.194Z",
|
|
5
|
+
"processes": {
|
|
6
|
+
"game": "AUitn972WGLDj0pMCVbXXacTtOk2PNchgP2qaP_kNoI",
|
|
7
|
+
"rune": "rYfYbQu9vIin_FKcpkxLtcF21pIQbdY-pOkkM5igyVg",
|
|
8
|
+
"pusd": "N4VZxxFb4qArB4SJawvqnLZSRPX9GFs7iGnRQ3Kr-4M",
|
|
9
|
+
"dao": "YAPCQK-3STKc-XND5-O4v-wEQBv7M4S-i__rkQKvQdk",
|
|
10
|
+
"elements": {
|
|
11
|
+
"earth": "wCtjJMScFI6jjioNQdRDUdyFWiqYubaRYyUeeMYYOjI",
|
|
12
|
+
"air": "1he9rnfufmEoPFeDYV_LzkA7e3AM98MXARal2Oq-4lc",
|
|
13
|
+
"fire": "4lffB0KjBEix1I7g1nBmeIdllhqhyHH75NiUQs5De2o",
|
|
14
|
+
"water": "EwiZp-92W7k8xZntMs2u3HAJ-RdDqTdAzvia_6KxlI0"
|
|
15
|
+
},
|
|
16
|
+
"internalVenue": "_ShRHGFliguTz51f7N_j69HDdgt-AI7U1LNG6c_uvwc",
|
|
17
|
+
"externalVenue": "DhI-4XuwkVxfbzp2tk7PgclUD7lVOJa-kNF2VVhLyDk",
|
|
18
|
+
"hunt": "kKpOdP--5hA3fy-i5q6-agoHFNKuYW4c_9Qg8NvAIew",
|
|
19
|
+
"huntWorkers": [
|
|
20
|
+
"kKpOdP--5hA3fy-i5q6-agoHFNKuYW4c_9Qg8NvAIew",
|
|
21
|
+
"NsIK9D0VScuNdf7-avycKxPSTEiHXPBlbFuCEv5ZEuw",
|
|
22
|
+
"JYtYuPA5weAbXykDnmDUhlU3LacxkY15qWppEDtTP4s",
|
|
23
|
+
"lJgsZ3GadofVafa8FI3C6fGTf6Aq0yfZFi44P6vRti0",
|
|
24
|
+
"GUMGAcSdCmKhb75M3NHUkLsdyoAHu7ixyuEldDsXA3c"
|
|
25
|
+
],
|
|
26
|
+
"battleWorkers": [
|
|
27
|
+
"MgxNZ60Ejc01E5MeuZ1s_8PeLNhExjOz9wUGo3utE1w",
|
|
28
|
+
"Ww_GXme0Sn3-jIcji0dPxV0nXLYqBesChXqqXQTC0Hc",
|
|
29
|
+
"4YYgYXTzS_PjbQMthDwf93zik-kvoXYTzrNHA30ijAY",
|
|
30
|
+
"EWlpRzX-waNNKj0bvT9pzj_N4_bv2ndYPMOJaiVkWlw",
|
|
31
|
+
"v5_n4eOw24s1wGjI8-fjoGTfQHwjcO-tcHFxIiO5Qrk"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"huntNode": "https://hyperbeam.tylerw.ai",
|
|
35
|
+
"marketNode": "https://hyperbeam.tylerw.ai",
|
|
36
|
+
"venueNode": "https://hyperbeam.tylerw.ai"
|
|
37
|
+
}
|
package/dist/http.mjs
CHANGED
|
@@ -306,7 +306,7 @@ import { z as z9 } from "zod";
|
|
|
306
306
|
import { z } from "zod";
|
|
307
307
|
|
|
308
308
|
// src/update.mjs
|
|
309
|
-
var VERSION = "0.
|
|
309
|
+
var VERSION = "0.4.2";
|
|
310
310
|
var CHECK_MS = 6 * 36e5;
|
|
311
311
|
var NOTE_MS = 36e5;
|
|
312
312
|
var latest = null;
|
|
@@ -1042,16 +1042,30 @@ function registerExchange(server) {
|
|
|
1042
1042
|
// src/bridge.mjs
|
|
1043
1043
|
import crypto3 from "node:crypto";
|
|
1044
1044
|
import { z as z4 } from "zod";
|
|
1045
|
-
import {
|
|
1045
|
+
import {
|
|
1046
|
+
createPublicClient,
|
|
1047
|
+
createWalletClient,
|
|
1048
|
+
encodeAbiParameters,
|
|
1049
|
+
erc20Abi,
|
|
1050
|
+
http,
|
|
1051
|
+
keccak256,
|
|
1052
|
+
parseAbi,
|
|
1053
|
+
formatEther,
|
|
1054
|
+
recoverMessageAddress,
|
|
1055
|
+
toBytes
|
|
1056
|
+
} from "viem";
|
|
1046
1057
|
import { base } from "viem/chains";
|
|
1047
1058
|
import {
|
|
1048
1059
|
Connection,
|
|
1060
|
+
Ed25519Program,
|
|
1049
1061
|
PublicKey,
|
|
1062
|
+
SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
1050
1063
|
SystemProgram,
|
|
1051
1064
|
Transaction,
|
|
1052
1065
|
TransactionInstruction,
|
|
1053
1066
|
sendAndConfirmTransaction
|
|
1054
1067
|
} from "@solana/web3.js";
|
|
1068
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
1055
1069
|
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
1056
1070
|
import { Transaction as SuiTransaction, coinWithBalance } from "@mysten/sui/transactions";
|
|
1057
1071
|
var DAILY_FAUCET = 100000000n;
|
|
@@ -1096,6 +1110,30 @@ function baseChain(w) {
|
|
|
1096
1110
|
const allowance = await pub.readContract({ address: token.address, abi: erc20Abi, functionName: "allowance", args: [me2, cfg.bridge] });
|
|
1097
1111
|
if (allowance < amount) await write({ address: token.address, abi: erc20Abi, functionName: "approve", args: [cfg.bridge, amount] });
|
|
1098
1112
|
return write({ address: cfg.bridge, abi: BRIDGE, functionName: "deposit", args: [token.address, amount, ao] });
|
|
1113
|
+
},
|
|
1114
|
+
/** Submit a signed withdrawal: the set's signatures, sorted by signer. */
|
|
1115
|
+
async claim(w2, set, namespace) {
|
|
1116
|
+
const RELEASE = parseAbi(["function release(uint64 withdrawalId, address token, address to, uint256 amount, bytes[] sigs)"]);
|
|
1117
|
+
const id = BigInt(namespace) << 32n | BigInt(w2.id);
|
|
1118
|
+
const inner = keccak256(encodeAbiParameters(
|
|
1119
|
+
[
|
|
1120
|
+
{ type: "bytes32" },
|
|
1121
|
+
{ type: "uint256" },
|
|
1122
|
+
{ type: "address" },
|
|
1123
|
+
{ type: "uint64" },
|
|
1124
|
+
{ type: "address" },
|
|
1125
|
+
{ type: "address" },
|
|
1126
|
+
{ type: "uint256" }
|
|
1127
|
+
],
|
|
1128
|
+
[keccak256(toBytes("PARALITH_RELEASE_V1")), BigInt(cfg.chainId), cfg.bridge, id, w2.token, w2.to, BigInt(w2.amount)]
|
|
1129
|
+
));
|
|
1130
|
+
const allowed = new Set(set.map((m) => m.base.toLowerCase()));
|
|
1131
|
+
const rows = await Promise.all(set.filter((m) => w2.sigs?.[m.ao]).map(async (m) => ({
|
|
1132
|
+
sig: w2.sigs[m.ao],
|
|
1133
|
+
who: (await recoverMessageAddress({ message: { raw: inner }, signature: w2.sigs[m.ao] })).toLowerCase()
|
|
1134
|
+
})));
|
|
1135
|
+
const sigs = rows.filter((x) => allowed.has(x.who)).sort((a, b) => BigInt(a.who) < BigInt(b.who) ? -1 : 1).map((x) => x.sig);
|
|
1136
|
+
return write({ address: cfg.bridge, abi: RELEASE, functionName: "release", args: [id, w2.token, w2.to, BigInt(w2.amount), sigs] });
|
|
1099
1137
|
}
|
|
1100
1138
|
};
|
|
1101
1139
|
}
|
|
@@ -1123,7 +1161,7 @@ function solanaChain(w) {
|
|
|
1123
1161
|
len.writeUInt32LE(b.length);
|
|
1124
1162
|
return Buffer.concat([len, b]);
|
|
1125
1163
|
};
|
|
1126
|
-
const send2 = (
|
|
1164
|
+
const send2 = (...instructions) => sendAndConfirmTransaction(conn, new Transaction().add(...instructions), [kp], { commitment: "confirmed" });
|
|
1127
1165
|
return {
|
|
1128
1166
|
token,
|
|
1129
1167
|
tokenTag: token.mint,
|
|
@@ -1168,7 +1206,55 @@ function solanaChain(w) {
|
|
|
1168
1206
|
{ pubkey: tokenProgram, isSigner: false, isWritable: false }
|
|
1169
1207
|
],
|
|
1170
1208
|
data: Buffer.concat([ix("deposit"), u64(amount), str(ao)])
|
|
1171
|
-
}))
|
|
1209
|
+
})),
|
|
1210
|
+
/** Submit a signed withdrawal: one Ed25519 precompile instruction holding the
|
|
1211
|
+
* members' signatures over the release message, then `release`. */
|
|
1212
|
+
claim(w2, set, namespace) {
|
|
1213
|
+
const id = BigInt(namespace) << 32n | BigInt(w2.id);
|
|
1214
|
+
const amount = BigInt(w2.amount);
|
|
1215
|
+
const to = new PublicKey(w2.to);
|
|
1216
|
+
const signed = set.filter((m) => w2.sigs?.[m.ao]);
|
|
1217
|
+
const msg = Buffer.concat([
|
|
1218
|
+
Buffer.from("PARALITH_RELEASE_V1"),
|
|
1219
|
+
program.toBuffer(),
|
|
1220
|
+
u64(id),
|
|
1221
|
+
mint.toBuffer(),
|
|
1222
|
+
to.toBuffer(),
|
|
1223
|
+
u64(amount)
|
|
1224
|
+
]);
|
|
1225
|
+
const head = 2 + 14 * signed.length;
|
|
1226
|
+
const msgOff = head + 96 * signed.length;
|
|
1227
|
+
const data = Buffer.alloc(msgOff + msg.length);
|
|
1228
|
+
data[0] = signed.length;
|
|
1229
|
+
signed.forEach((m, i) => {
|
|
1230
|
+
const at = head + 96 * i;
|
|
1231
|
+
[at + 32, 65535, at, 65535, msgOff, msg.length, 65535].forEach((v, k) => data.writeUInt16LE(v, 2 + 14 * i + 2 * k));
|
|
1232
|
+
data.set(new PublicKey(m.solana).toBytes(), at);
|
|
1233
|
+
data.set(Buffer.from(w2.sigs[m.ao].replace(/^0x/, ""), "hex"), at + 32);
|
|
1234
|
+
});
|
|
1235
|
+
data.set(msg, msgOff);
|
|
1236
|
+
const toAta = PublicKey.findProgramAddressSync([to.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()], ATA_PROGRAM)[0];
|
|
1237
|
+
return send2(
|
|
1238
|
+
new TransactionInstruction({ programId: Ed25519Program.programId, keys: [], data }),
|
|
1239
|
+
new TransactionInstruction({
|
|
1240
|
+
programId: program,
|
|
1241
|
+
keys: [
|
|
1242
|
+
{ pubkey: user, isSigner: true, isWritable: true },
|
|
1243
|
+
{ pubkey: pda(Buffer.from("config")), isSigner: false, isWritable: false },
|
|
1244
|
+
{ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
|
|
1245
|
+
{ pubkey: pda(Buffer.from("receipt"), u64(id)), isSigner: false, isWritable: true },
|
|
1246
|
+
{ pubkey: mint, isSigner: false, isWritable: false },
|
|
1247
|
+
{ pubkey: pda(Buffer.from("vault"), mint.toBuffer()), isSigner: false, isWritable: true },
|
|
1248
|
+
{ pubkey: to, isSigner: false, isWritable: false },
|
|
1249
|
+
{ pubkey: toAta, isSigner: false, isWritable: true },
|
|
1250
|
+
{ pubkey: tokenProgram, isSigner: false, isWritable: false },
|
|
1251
|
+
{ pubkey: ATA_PROGRAM, isSigner: false, isWritable: false },
|
|
1252
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
|
|
1253
|
+
],
|
|
1254
|
+
data: Buffer.concat([ix("release"), u64(id), u64(amount)])
|
|
1255
|
+
})
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1172
1258
|
};
|
|
1173
1259
|
}
|
|
1174
1260
|
function suiChain(w) {
|
|
@@ -1216,6 +1302,25 @@ function suiChain(w) {
|
|
|
1216
1302
|
arguments: [tx.object(cfg.bridge), coinWithBalance({ type: token.type, balance: amount }), tx.pure.string(ao)]
|
|
1217
1303
|
});
|
|
1218
1304
|
return exec(tx);
|
|
1305
|
+
},
|
|
1306
|
+
/** Submit a signed withdrawal: each signature with its signer's index in the set. */
|
|
1307
|
+
claim(w2, set, namespace) {
|
|
1308
|
+
const id = BigInt(namespace) << 32n | BigInt(w2.id);
|
|
1309
|
+
const signed = set.flatMap((m, at) => w2.sigs?.[m.ao] ? [{ at, sig: w2.sigs[m.ao] }] : []);
|
|
1310
|
+
const tx = new SuiTransaction();
|
|
1311
|
+
tx.moveCall({
|
|
1312
|
+
target: `${cfg.package}::para_bridge::release`,
|
|
1313
|
+
typeArguments: [w2.token],
|
|
1314
|
+
arguments: [
|
|
1315
|
+
tx.object(cfg.bridge),
|
|
1316
|
+
tx.pure.u64(id),
|
|
1317
|
+
tx.pure.u64(BigInt(w2.amount)),
|
|
1318
|
+
tx.pure.address(w2.to),
|
|
1319
|
+
tx.pure(bcs.vector(bcs.u64()).serialize(signed.map((x) => BigInt(x.at)))),
|
|
1320
|
+
tx.pure(bcs.vector(bcs.vector(bcs.u8())).serialize(signed.map((x) => Array.from(Buffer.from(x.sig.replace(/^0x/, ""), "hex")))))
|
|
1321
|
+
]
|
|
1322
|
+
});
|
|
1323
|
+
return exec(tx);
|
|
1219
1324
|
}
|
|
1220
1325
|
};
|
|
1221
1326
|
}
|
|
@@ -1241,7 +1346,7 @@ function registerBridge(server) {
|
|
|
1241
1346
|
tool(
|
|
1242
1347
|
server,
|
|
1243
1348
|
"bridge_info",
|
|
1244
|
-
"PUSD bridge configuration and ledger: fees (bps, in/out), supply, reserves per chain and token,
|
|
1349
|
+
"PUSD bridge configuration and ledger: fees (bps, in/out), supply, reserves per chain and token, the attestor set, and each chain's contract addresses. Free.",
|
|
1245
1350
|
{},
|
|
1246
1351
|
async () => ({
|
|
1247
1352
|
pusd: PUSD,
|
|
@@ -1290,17 +1395,12 @@ function registerBridge(server) {
|
|
|
1290
1395
|
},
|
|
1291
1396
|
WRITE
|
|
1292
1397
|
);
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
method: "POST",
|
|
1296
|
-
headers: { "content-type": "application/json" },
|
|
1297
|
-
body: JSON.stringify({ chain, tx }),
|
|
1298
|
-
signal: AbortSignal.timeout(3e4)
|
|
1299
|
-
}).then((r) => r.ok, () => false);
|
|
1398
|
+
const nudgeNodes = () => Promise.all((chains.nodes ?? []).map((n) => fetch(`${n.url}/~bridge@1.0/nudge`, { signal: AbortSignal.timeout(1e4) }).catch(() => {
|
|
1399
|
+
})));
|
|
1300
1400
|
tool(
|
|
1301
1401
|
server,
|
|
1302
1402
|
"bridge_deposit",
|
|
1303
|
-
"Bridge TEST-USDC from a chain into PUSD on your AO address: approve if needed, lock in the bridge contract, then wait until
|
|
1403
|
+
"Bridge TEST-USDC from a chain into PUSD on your AO address: approve if needed, lock in the bridge contract, then wait until k attestor nodes report it and PUSD credits you (inbound fee applies; see bridge_info). Needs native gas.",
|
|
1304
1404
|
{ chain: CHAIN, amount: z4.string(), wait: z4.boolean().optional().describe("Default true.") },
|
|
1305
1405
|
async ({ chain, amount, wait = true }) => {
|
|
1306
1406
|
const { wallets } = await runtime();
|
|
@@ -1308,8 +1408,8 @@ function registerBridge(server) {
|
|
|
1308
1408
|
const atoms = parseAmount(amount);
|
|
1309
1409
|
const before = await pusdBalance(wallets.ao.address);
|
|
1310
1410
|
const tx = await c.deposit(atoms, wallets.ao.address);
|
|
1311
|
-
await
|
|
1312
|
-
if (!wait) return { chain, tx, note: "Locked on the source chain. PUSD arrives when
|
|
1411
|
+
await nudgeNodes();
|
|
1412
|
+
if (!wait) return { chain, tx, note: "Locked on the source chain. PUSD arrives when k nodes attest; check bridge_balances." };
|
|
1313
1413
|
const after = await until("PUSD credit", () => pusdBalance(wallets.ao.address), (b) => b > before, 9e5);
|
|
1314
1414
|
return { chain, tx, credited: formatAtoms(after - before), pusd: formatAtoms(after) };
|
|
1315
1415
|
},
|
|
@@ -1318,11 +1418,12 @@ function registerBridge(server) {
|
|
|
1318
1418
|
tool(
|
|
1319
1419
|
server,
|
|
1320
1420
|
"bridge_redeem",
|
|
1321
|
-
"Redeem PUSD to TEST-USDC on a chain (outbound fee, see bridge_info; the destination reserve must cover it). Sends to your own address on that chain unless recipient is given
|
|
1421
|
+
"Redeem PUSD to TEST-USDC on a chain (outbound fee, see bridge_info; the destination reserve must cover it). Sends to your own address on that chain unless recipient is given. Unless wait is false, waits for k node signatures and submits the release from your wallet on that chain (needs its native gas).",
|
|
1322
1422
|
{ chain: CHAIN, amount: z4.string(), recipient: z4.string().optional(), wait: z4.boolean().optional() },
|
|
1323
1423
|
async ({ chain, amount, recipient, wait = true }) => {
|
|
1324
1424
|
const { api } = await runtime();
|
|
1325
1425
|
const c = await chainFor(chain);
|
|
1426
|
+
await nudgeNodes();
|
|
1326
1427
|
const to = recipient ?? c.recipient;
|
|
1327
1428
|
const own = to === c.recipient;
|
|
1328
1429
|
const before = own ? await c.balance() : null;
|
|
@@ -1336,13 +1437,21 @@ function registerBridge(server) {
|
|
|
1336
1437
|
if (!reply || reply.error) throw new Error(`Bridge-Redeem refused: ${reply?.error ?? "no reply"}`);
|
|
1337
1438
|
const out = { chain, withdrawalId: reply.WithdrawalId, net: formatAtoms(reply.Quantity ?? 0), fee: formatAtoms(reply.Fee ?? 0), recipient: to };
|
|
1338
1439
|
if (!wait) return out;
|
|
1339
|
-
await
|
|
1340
|
-
|
|
1440
|
+
const info = await readKey(PUSD, "bridgeinfo");
|
|
1441
|
+
const set = info?.attestors ?? [];
|
|
1442
|
+
const k = Math.floor((3 * set.length + 4) / 5);
|
|
1443
|
+
const signed = (w2) => set.filter((m) => w2?.sigs?.[m.ao]).length;
|
|
1444
|
+
const list = await until(
|
|
1445
|
+
`${k} node signatures`,
|
|
1341
1446
|
() => readKey(PUSD, "withdrawals"),
|
|
1342
|
-
(
|
|
1447
|
+
(l) => signed((l ?? []).find((w2) => w2.id === String(reply.WithdrawalId))) >= k,
|
|
1343
1448
|
9e5
|
|
1344
1449
|
);
|
|
1345
|
-
|
|
1450
|
+
const w = list.find((x) => x.id === String(reply.WithdrawalId));
|
|
1451
|
+
const tx = await c.claim(w, set, Number(info.namespace));
|
|
1452
|
+
if (!own) return { ...out, status: "released", tx };
|
|
1453
|
+
const after = await until("release", () => c.balance(), (b) => b >= before + BigInt(w.amount), 3e5);
|
|
1454
|
+
return { ...out, status: "released", tx, received: formatAtoms(after - before) };
|
|
1346
1455
|
},
|
|
1347
1456
|
WRITE
|
|
1348
1457
|
);
|
package/dist/server.mjs
CHANGED
|
@@ -529,7 +529,7 @@ var VERSION, CHECK_MS, NOTE_MS, latest, checkedAt, notedAt, newer;
|
|
|
529
529
|
var init_update = __esm({
|
|
530
530
|
"src/update.mjs"() {
|
|
531
531
|
"use strict";
|
|
532
|
-
VERSION = "0.
|
|
532
|
+
VERSION = "0.4.2";
|
|
533
533
|
CHECK_MS = 6 * 36e5;
|
|
534
534
|
NOTE_MS = 36e5;
|
|
535
535
|
latest = null;
|
|
@@ -1284,16 +1284,30 @@ var init_exchange = __esm({
|
|
|
1284
1284
|
// src/bridge.mjs
|
|
1285
1285
|
import crypto3 from "node:crypto";
|
|
1286
1286
|
import { z as z4 } from "zod";
|
|
1287
|
-
import {
|
|
1287
|
+
import {
|
|
1288
|
+
createPublicClient,
|
|
1289
|
+
createWalletClient,
|
|
1290
|
+
encodeAbiParameters,
|
|
1291
|
+
erc20Abi,
|
|
1292
|
+
http,
|
|
1293
|
+
keccak256,
|
|
1294
|
+
parseAbi,
|
|
1295
|
+
formatEther,
|
|
1296
|
+
recoverMessageAddress,
|
|
1297
|
+
toBytes
|
|
1298
|
+
} from "viem";
|
|
1288
1299
|
import { base } from "viem/chains";
|
|
1289
1300
|
import {
|
|
1290
1301
|
Connection,
|
|
1302
|
+
Ed25519Program,
|
|
1291
1303
|
PublicKey,
|
|
1304
|
+
SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
1292
1305
|
SystemProgram,
|
|
1293
1306
|
Transaction,
|
|
1294
1307
|
TransactionInstruction,
|
|
1295
1308
|
sendAndConfirmTransaction
|
|
1296
1309
|
} from "@solana/web3.js";
|
|
1310
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
1297
1311
|
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
1298
1312
|
import { Transaction as SuiTransaction, coinWithBalance } from "@mysten/sui/transactions";
|
|
1299
1313
|
function baseChain(w) {
|
|
@@ -1333,6 +1347,30 @@ function baseChain(w) {
|
|
|
1333
1347
|
const allowance = await pub.readContract({ address: token.address, abi: erc20Abi, functionName: "allowance", args: [me2, cfg.bridge] });
|
|
1334
1348
|
if (allowance < amount) await write({ address: token.address, abi: erc20Abi, functionName: "approve", args: [cfg.bridge, amount] });
|
|
1335
1349
|
return write({ address: cfg.bridge, abi: BRIDGE, functionName: "deposit", args: [token.address, amount, ao] });
|
|
1350
|
+
},
|
|
1351
|
+
/** Submit a signed withdrawal: the set's signatures, sorted by signer. */
|
|
1352
|
+
async claim(w2, set, namespace) {
|
|
1353
|
+
const RELEASE = parseAbi(["function release(uint64 withdrawalId, address token, address to, uint256 amount, bytes[] sigs)"]);
|
|
1354
|
+
const id = BigInt(namespace) << 32n | BigInt(w2.id);
|
|
1355
|
+
const inner = keccak256(encodeAbiParameters(
|
|
1356
|
+
[
|
|
1357
|
+
{ type: "bytes32" },
|
|
1358
|
+
{ type: "uint256" },
|
|
1359
|
+
{ type: "address" },
|
|
1360
|
+
{ type: "uint64" },
|
|
1361
|
+
{ type: "address" },
|
|
1362
|
+
{ type: "address" },
|
|
1363
|
+
{ type: "uint256" }
|
|
1364
|
+
],
|
|
1365
|
+
[keccak256(toBytes("PARALITH_RELEASE_V1")), BigInt(cfg.chainId), cfg.bridge, id, w2.token, w2.to, BigInt(w2.amount)]
|
|
1366
|
+
));
|
|
1367
|
+
const allowed = new Set(set.map((m) => m.base.toLowerCase()));
|
|
1368
|
+
const rows = await Promise.all(set.filter((m) => w2.sigs?.[m.ao]).map(async (m) => ({
|
|
1369
|
+
sig: w2.sigs[m.ao],
|
|
1370
|
+
who: (await recoverMessageAddress({ message: { raw: inner }, signature: w2.sigs[m.ao] })).toLowerCase()
|
|
1371
|
+
})));
|
|
1372
|
+
const sigs = rows.filter((x) => allowed.has(x.who)).sort((a, b) => BigInt(a.who) < BigInt(b.who) ? -1 : 1).map((x) => x.sig);
|
|
1373
|
+
return write({ address: cfg.bridge, abi: RELEASE, functionName: "release", args: [id, w2.token, w2.to, BigInt(w2.amount), sigs] });
|
|
1336
1374
|
}
|
|
1337
1375
|
};
|
|
1338
1376
|
}
|
|
@@ -1360,7 +1398,7 @@ function solanaChain(w) {
|
|
|
1360
1398
|
len.writeUInt32LE(b.length);
|
|
1361
1399
|
return Buffer.concat([len, b]);
|
|
1362
1400
|
};
|
|
1363
|
-
const send2 = (
|
|
1401
|
+
const send2 = (...instructions) => sendAndConfirmTransaction(conn, new Transaction().add(...instructions), [kp], { commitment: "confirmed" });
|
|
1364
1402
|
return {
|
|
1365
1403
|
token,
|
|
1366
1404
|
tokenTag: token.mint,
|
|
@@ -1405,7 +1443,55 @@ function solanaChain(w) {
|
|
|
1405
1443
|
{ pubkey: tokenProgram, isSigner: false, isWritable: false }
|
|
1406
1444
|
],
|
|
1407
1445
|
data: Buffer.concat([ix("deposit"), u64(amount), str(ao)])
|
|
1408
|
-
}))
|
|
1446
|
+
})),
|
|
1447
|
+
/** Submit a signed withdrawal: one Ed25519 precompile instruction holding the
|
|
1448
|
+
* members' signatures over the release message, then `release`. */
|
|
1449
|
+
claim(w2, set, namespace) {
|
|
1450
|
+
const id = BigInt(namespace) << 32n | BigInt(w2.id);
|
|
1451
|
+
const amount = BigInt(w2.amount);
|
|
1452
|
+
const to = new PublicKey(w2.to);
|
|
1453
|
+
const signed = set.filter((m) => w2.sigs?.[m.ao]);
|
|
1454
|
+
const msg = Buffer.concat([
|
|
1455
|
+
Buffer.from("PARALITH_RELEASE_V1"),
|
|
1456
|
+
program.toBuffer(),
|
|
1457
|
+
u64(id),
|
|
1458
|
+
mint.toBuffer(),
|
|
1459
|
+
to.toBuffer(),
|
|
1460
|
+
u64(amount)
|
|
1461
|
+
]);
|
|
1462
|
+
const head = 2 + 14 * signed.length;
|
|
1463
|
+
const msgOff = head + 96 * signed.length;
|
|
1464
|
+
const data = Buffer.alloc(msgOff + msg.length);
|
|
1465
|
+
data[0] = signed.length;
|
|
1466
|
+
signed.forEach((m, i) => {
|
|
1467
|
+
const at = head + 96 * i;
|
|
1468
|
+
[at + 32, 65535, at, 65535, msgOff, msg.length, 65535].forEach((v, k) => data.writeUInt16LE(v, 2 + 14 * i + 2 * k));
|
|
1469
|
+
data.set(new PublicKey(m.solana).toBytes(), at);
|
|
1470
|
+
data.set(Buffer.from(w2.sigs[m.ao].replace(/^0x/, ""), "hex"), at + 32);
|
|
1471
|
+
});
|
|
1472
|
+
data.set(msg, msgOff);
|
|
1473
|
+
const toAta = PublicKey.findProgramAddressSync([to.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()], ATA_PROGRAM)[0];
|
|
1474
|
+
return send2(
|
|
1475
|
+
new TransactionInstruction({ programId: Ed25519Program.programId, keys: [], data }),
|
|
1476
|
+
new TransactionInstruction({
|
|
1477
|
+
programId: program,
|
|
1478
|
+
keys: [
|
|
1479
|
+
{ pubkey: user, isSigner: true, isWritable: true },
|
|
1480
|
+
{ pubkey: pda(Buffer.from("config")), isSigner: false, isWritable: false },
|
|
1481
|
+
{ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
|
|
1482
|
+
{ pubkey: pda(Buffer.from("receipt"), u64(id)), isSigner: false, isWritable: true },
|
|
1483
|
+
{ pubkey: mint, isSigner: false, isWritable: false },
|
|
1484
|
+
{ pubkey: pda(Buffer.from("vault"), mint.toBuffer()), isSigner: false, isWritable: true },
|
|
1485
|
+
{ pubkey: to, isSigner: false, isWritable: false },
|
|
1486
|
+
{ pubkey: toAta, isSigner: false, isWritable: true },
|
|
1487
|
+
{ pubkey: tokenProgram, isSigner: false, isWritable: false },
|
|
1488
|
+
{ pubkey: ATA_PROGRAM, isSigner: false, isWritable: false },
|
|
1489
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
|
|
1490
|
+
],
|
|
1491
|
+
data: Buffer.concat([ix("release"), u64(id), u64(amount)])
|
|
1492
|
+
})
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1409
1495
|
};
|
|
1410
1496
|
}
|
|
1411
1497
|
function suiChain(w) {
|
|
@@ -1453,6 +1539,25 @@ function suiChain(w) {
|
|
|
1453
1539
|
arguments: [tx.object(cfg.bridge), coinWithBalance({ type: token.type, balance: amount }), tx.pure.string(ao)]
|
|
1454
1540
|
});
|
|
1455
1541
|
return exec(tx);
|
|
1542
|
+
},
|
|
1543
|
+
/** Submit a signed withdrawal: each signature with its signer's index in the set. */
|
|
1544
|
+
claim(w2, set, namespace) {
|
|
1545
|
+
const id = BigInt(namespace) << 32n | BigInt(w2.id);
|
|
1546
|
+
const signed = set.flatMap((m, at) => w2.sigs?.[m.ao] ? [{ at, sig: w2.sigs[m.ao] }] : []);
|
|
1547
|
+
const tx = new SuiTransaction();
|
|
1548
|
+
tx.moveCall({
|
|
1549
|
+
target: `${cfg.package}::para_bridge::release`,
|
|
1550
|
+
typeArguments: [w2.token],
|
|
1551
|
+
arguments: [
|
|
1552
|
+
tx.object(cfg.bridge),
|
|
1553
|
+
tx.pure.u64(id),
|
|
1554
|
+
tx.pure.u64(BigInt(w2.amount)),
|
|
1555
|
+
tx.pure.address(w2.to),
|
|
1556
|
+
tx.pure(bcs.vector(bcs.u64()).serialize(signed.map((x) => BigInt(x.at)))),
|
|
1557
|
+
tx.pure(bcs.vector(bcs.vector(bcs.u8())).serialize(signed.map((x) => Array.from(Buffer.from(x.sig.replace(/^0x/, ""), "hex")))))
|
|
1558
|
+
]
|
|
1559
|
+
});
|
|
1560
|
+
return exec(tx);
|
|
1456
1561
|
}
|
|
1457
1562
|
};
|
|
1458
1563
|
}
|
|
@@ -1477,7 +1582,7 @@ function registerBridge(server) {
|
|
|
1477
1582
|
tool(
|
|
1478
1583
|
server,
|
|
1479
1584
|
"bridge_info",
|
|
1480
|
-
"PUSD bridge configuration and ledger: fees (bps, in/out), supply, reserves per chain and token,
|
|
1585
|
+
"PUSD bridge configuration and ledger: fees (bps, in/out), supply, reserves per chain and token, the attestor set, and each chain's contract addresses. Free.",
|
|
1481
1586
|
{},
|
|
1482
1587
|
async () => ({
|
|
1483
1588
|
pusd: PUSD,
|
|
@@ -1526,17 +1631,12 @@ function registerBridge(server) {
|
|
|
1526
1631
|
},
|
|
1527
1632
|
WRITE
|
|
1528
1633
|
);
|
|
1529
|
-
const
|
|
1530
|
-
|
|
1531
|
-
method: "POST",
|
|
1532
|
-
headers: { "content-type": "application/json" },
|
|
1533
|
-
body: JSON.stringify({ chain, tx }),
|
|
1534
|
-
signal: AbortSignal.timeout(3e4)
|
|
1535
|
-
}).then((r) => r.ok, () => false);
|
|
1634
|
+
const nudgeNodes = () => Promise.all((chains.nodes ?? []).map((n) => fetch(`${n.url}/~bridge@1.0/nudge`, { signal: AbortSignal.timeout(1e4) }).catch(() => {
|
|
1635
|
+
})));
|
|
1536
1636
|
tool(
|
|
1537
1637
|
server,
|
|
1538
1638
|
"bridge_deposit",
|
|
1539
|
-
"Bridge TEST-USDC from a chain into PUSD on your AO address: approve if needed, lock in the bridge contract, then wait until
|
|
1639
|
+
"Bridge TEST-USDC from a chain into PUSD on your AO address: approve if needed, lock in the bridge contract, then wait until k attestor nodes report it and PUSD credits you (inbound fee applies; see bridge_info). Needs native gas.",
|
|
1540
1640
|
{ chain: CHAIN, amount: z4.string(), wait: z4.boolean().optional().describe("Default true.") },
|
|
1541
1641
|
async ({ chain, amount, wait = true }) => {
|
|
1542
1642
|
const { wallets } = await runtime();
|
|
@@ -1544,8 +1644,8 @@ function registerBridge(server) {
|
|
|
1544
1644
|
const atoms = parseAmount(amount);
|
|
1545
1645
|
const before = await pusdBalance(wallets.ao.address);
|
|
1546
1646
|
const tx = await c.deposit(atoms, wallets.ao.address);
|
|
1547
|
-
await
|
|
1548
|
-
if (!wait) return { chain, tx, note: "Locked on the source chain. PUSD arrives when
|
|
1647
|
+
await nudgeNodes();
|
|
1648
|
+
if (!wait) return { chain, tx, note: "Locked on the source chain. PUSD arrives when k nodes attest; check bridge_balances." };
|
|
1549
1649
|
const after = await until("PUSD credit", () => pusdBalance(wallets.ao.address), (b) => b > before, 9e5);
|
|
1550
1650
|
return { chain, tx, credited: formatAtoms(after - before), pusd: formatAtoms(after) };
|
|
1551
1651
|
},
|
|
@@ -1554,11 +1654,12 @@ function registerBridge(server) {
|
|
|
1554
1654
|
tool(
|
|
1555
1655
|
server,
|
|
1556
1656
|
"bridge_redeem",
|
|
1557
|
-
"Redeem PUSD to TEST-USDC on a chain (outbound fee, see bridge_info; the destination reserve must cover it). Sends to your own address on that chain unless recipient is given
|
|
1657
|
+
"Redeem PUSD to TEST-USDC on a chain (outbound fee, see bridge_info; the destination reserve must cover it). Sends to your own address on that chain unless recipient is given. Unless wait is false, waits for k node signatures and submits the release from your wallet on that chain (needs its native gas).",
|
|
1558
1658
|
{ chain: CHAIN, amount: z4.string(), recipient: z4.string().optional(), wait: z4.boolean().optional() },
|
|
1559
1659
|
async ({ chain, amount, recipient, wait = true }) => {
|
|
1560
1660
|
const { api } = await runtime();
|
|
1561
1661
|
const c = await chainFor(chain);
|
|
1662
|
+
await nudgeNodes();
|
|
1562
1663
|
const to = recipient ?? c.recipient;
|
|
1563
1664
|
const own = to === c.recipient;
|
|
1564
1665
|
const before = own ? await c.balance() : null;
|
|
@@ -1572,13 +1673,21 @@ function registerBridge(server) {
|
|
|
1572
1673
|
if (!reply || reply.error) throw new Error(`Bridge-Redeem refused: ${reply?.error ?? "no reply"}`);
|
|
1573
1674
|
const out = { chain, withdrawalId: reply.WithdrawalId, net: formatAtoms(reply.Quantity ?? 0), fee: formatAtoms(reply.Fee ?? 0), recipient: to };
|
|
1574
1675
|
if (!wait) return out;
|
|
1575
|
-
await
|
|
1576
|
-
|
|
1676
|
+
const info = await readKey(PUSD, "bridgeinfo");
|
|
1677
|
+
const set = info?.attestors ?? [];
|
|
1678
|
+
const k = Math.floor((3 * set.length + 4) / 5);
|
|
1679
|
+
const signed = (w2) => set.filter((m) => w2?.sigs?.[m.ao]).length;
|
|
1680
|
+
const list = await until(
|
|
1681
|
+
`${k} node signatures`,
|
|
1577
1682
|
() => readKey(PUSD, "withdrawals"),
|
|
1578
|
-
(
|
|
1683
|
+
(l) => signed((l ?? []).find((w2) => w2.id === String(reply.WithdrawalId))) >= k,
|
|
1579
1684
|
9e5
|
|
1580
1685
|
);
|
|
1581
|
-
|
|
1686
|
+
const w = list.find((x) => x.id === String(reply.WithdrawalId));
|
|
1687
|
+
const tx = await c.claim(w, set, Number(info.namespace));
|
|
1688
|
+
if (!own) return { ...out, status: "released", tx };
|
|
1689
|
+
const after = await until("release", () => c.balance(), (b) => b >= before + BigInt(w.amount), 3e5);
|
|
1690
|
+
return { ...out, status: "released", tx, received: formatAtoms(after - before) };
|
|
1582
1691
|
},
|
|
1583
1692
|
WRITE
|
|
1584
1693
|
);
|