@toon-protocol/client 0.15.0 → 0.17.0
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/index.d.ts +272 -26
- package/dist/index.js +352 -69
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -894,9 +894,13 @@ function deserializeIlpPacket(buf) {
|
|
|
894
894
|
}
|
|
895
895
|
function deserializeIlpFulfill(buf) {
|
|
896
896
|
let offset = 1;
|
|
897
|
+
if (buf.length < offset + 32) {
|
|
898
|
+
throw new Error("Buffer underflow reading FULFILL fulfillment");
|
|
899
|
+
}
|
|
900
|
+
const fulfillment = buf.slice(offset, offset + 32);
|
|
897
901
|
offset += 32;
|
|
898
902
|
const { value: data } = decodeVarOctetString(buf, offset);
|
|
899
|
-
return { type: ILPPacketType.FULFILL, data };
|
|
903
|
+
return { type: ILPPacketType.FULFILL, fulfillment, data };
|
|
900
904
|
}
|
|
901
905
|
function deserializeIlpReject(buf) {
|
|
902
906
|
let offset = 1;
|
|
@@ -1207,8 +1211,10 @@ var IsomorphicBtpClient = class {
|
|
|
1207
1211
|
this.pendingRequests.delete(id);
|
|
1208
1212
|
if (json["type"] === "FULFILL") {
|
|
1209
1213
|
const responseData = json["data"] ? this.base64ToUint8Array(json["data"]) : new Uint8Array(0);
|
|
1214
|
+
const fulfillment = json["fulfillment"] ? this.base64ToUint8Array(json["fulfillment"]) : new Uint8Array(32);
|
|
1210
1215
|
pending.resolve({
|
|
1211
1216
|
type: ILPPacketType.FULFILL,
|
|
1217
|
+
fulfillment,
|
|
1212
1218
|
data: responseData
|
|
1213
1219
|
});
|
|
1214
1220
|
} else {
|
|
@@ -1269,6 +1275,70 @@ var IsomorphicBtpClient = class {
|
|
|
1269
1275
|
}
|
|
1270
1276
|
};
|
|
1271
1277
|
|
|
1278
|
+
// src/utils/condition.ts
|
|
1279
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
1280
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
1281
|
+
var CONDITION_LENGTH = 32;
|
|
1282
|
+
function mintExecutionCondition() {
|
|
1283
|
+
const preimage = randomBytes(CONDITION_LENGTH);
|
|
1284
|
+
return { preimage, condition: sha256(preimage) };
|
|
1285
|
+
}
|
|
1286
|
+
function isZeroCondition(condition) {
|
|
1287
|
+
if (condition === void 0) return true;
|
|
1288
|
+
for (const byte of condition) {
|
|
1289
|
+
if (byte !== 0) return false;
|
|
1290
|
+
}
|
|
1291
|
+
return true;
|
|
1292
|
+
}
|
|
1293
|
+
function assertValidCondition(condition) {
|
|
1294
|
+
if (condition.length !== CONDITION_LENGTH) {
|
|
1295
|
+
throw new Error(
|
|
1296
|
+
`executionCondition must be exactly ${CONDITION_LENGTH} bytes, got ${condition.length}`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function fulfillmentMatchesCondition(fulfillment, condition) {
|
|
1301
|
+
if (fulfillment === void 0 || fulfillment.length !== CONDITION_LENGTH) {
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
const digest = sha256(fulfillment);
|
|
1305
|
+
for (let i = 0; i < CONDITION_LENGTH; i++) {
|
|
1306
|
+
if (digest[i] !== condition[i]) return false;
|
|
1307
|
+
}
|
|
1308
|
+
return true;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/adapters/ilp-send.ts
|
|
1312
|
+
var FULFILLMENT_MISMATCH_CODE = "F99";
|
|
1313
|
+
var FULFILLMENT_MISMATCH_MESSAGE = "FULFILL fulfillment does not match execution condition (sha256(fulfillment) != executionCondition) \u2014 packet counted failed";
|
|
1314
|
+
function mapIlpResponse(packet, sentCondition) {
|
|
1315
|
+
if (packet.type === ILPPacketType.FULFILL) {
|
|
1316
|
+
const dataField = packet.data.length > 0 ? { data: toBase64(packet.data) } : {};
|
|
1317
|
+
if (sentCondition === void 0 || isZeroCondition(sentCondition)) {
|
|
1318
|
+
return { accepted: true, ...dataField };
|
|
1319
|
+
}
|
|
1320
|
+
if (!fulfillmentMatchesCondition(packet.fulfillment, sentCondition)) {
|
|
1321
|
+
return {
|
|
1322
|
+
accepted: false,
|
|
1323
|
+
code: FULFILLMENT_MISMATCH_CODE,
|
|
1324
|
+
message: FULFILLMENT_MISMATCH_MESSAGE,
|
|
1325
|
+
...dataField
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
accepted: true,
|
|
1330
|
+
fulfillment: toBase64(packet.fulfillment),
|
|
1331
|
+
...dataField
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return {
|
|
1335
|
+
accepted: false,
|
|
1336
|
+
code: packet.code,
|
|
1337
|
+
message: packet.message,
|
|
1338
|
+
...packet.data.length > 0 ? { data: toBase64(packet.data) } : {}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1272
1342
|
// src/adapters/BtpRuntimeClient.ts
|
|
1273
1343
|
function isConnectionError(error) {
|
|
1274
1344
|
const msg = error.message.toLowerCase();
|
|
@@ -1324,6 +1394,12 @@ var BtpRuntimeClient = class {
|
|
|
1324
1394
|
/**
|
|
1325
1395
|
* Sends an ILP packet via BTP with auto-reconnect on connection errors.
|
|
1326
1396
|
* Satisfies IlpClient interface.
|
|
1397
|
+
*
|
|
1398
|
+
* `params` may carry a sender-chosen `executionCondition` and an explicit
|
|
1399
|
+
* `expiresAt` (toon-client#350); omitting both is the legacy zero-condition
|
|
1400
|
+
* path, unchanged. With a non-zero condition the FULFILL preimage is
|
|
1401
|
+
* verified (`sha256(fulfillment) == condition`) and a mismatch is surfaced
|
|
1402
|
+
* as a failed result — see `mapIlpResponse`.
|
|
1327
1403
|
*/
|
|
1328
1404
|
async sendIlpPacket(params) {
|
|
1329
1405
|
return withRetry(() => this._sendIlpPacketOnce(params), {
|
|
@@ -1339,6 +1415,9 @@ var BtpRuntimeClient = class {
|
|
|
1339
1415
|
/**
|
|
1340
1416
|
* Sends a balance proof claim via BTP protocol data, then sends an ILP packet.
|
|
1341
1417
|
* Auto-reconnects on connection errors.
|
|
1418
|
+
*
|
|
1419
|
+
* Sender-chosen `executionCondition` / explicit `expiresAt` semantics are
|
|
1420
|
+
* identical to {@link sendIlpPacket}.
|
|
1342
1421
|
*/
|
|
1343
1422
|
async sendIlpPacketWithClaim(params, claim) {
|
|
1344
1423
|
return withRetry(() => this._sendIlpPacketWithClaimOnce(params, claim), {
|
|
@@ -1381,6 +1460,30 @@ var BtpRuntimeClient = class {
|
|
|
1381
1460
|
encodeUtf8(JSON.stringify(claim))
|
|
1382
1461
|
);
|
|
1383
1462
|
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Build the ILP PREPARE for a send, applying the sender-chosen condition /
|
|
1465
|
+
* explicit expiry when provided (toon-client#350) and validating that a
|
|
1466
|
+
* non-zero condition is exactly 32 bytes (the OER serializer would
|
|
1467
|
+
* otherwise silently zero-fill it, downgrading the packet to the legacy
|
|
1468
|
+
* unverified class).
|
|
1469
|
+
*/
|
|
1470
|
+
buildPrepare(params) {
|
|
1471
|
+
const condition = params.executionCondition;
|
|
1472
|
+
if (condition !== void 0 && !isZeroCondition(condition)) {
|
|
1473
|
+
assertValidCondition(condition);
|
|
1474
|
+
}
|
|
1475
|
+
return {
|
|
1476
|
+
prepare: {
|
|
1477
|
+
type: 12,
|
|
1478
|
+
amount: BigInt(params.amount),
|
|
1479
|
+
destination: params.destination,
|
|
1480
|
+
executionCondition: condition ?? new Uint8Array(32),
|
|
1481
|
+
expiresAt: params.expiresAt ?? new Date(Date.now() + (params.timeout ?? 3e4)),
|
|
1482
|
+
data: fromBase64(params.data)
|
|
1483
|
+
},
|
|
1484
|
+
condition
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1384
1487
|
/**
|
|
1385
1488
|
* Single-attempt ILP packet send. Reconnects if not connected.
|
|
1386
1489
|
*/
|
|
@@ -1388,26 +1491,9 @@ var BtpRuntimeClient = class {
|
|
|
1388
1491
|
if (!this._isConnected) {
|
|
1389
1492
|
await this.reconnect();
|
|
1390
1493
|
}
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
destination: params.destination,
|
|
1395
|
-
executionCondition: new Uint8Array(32),
|
|
1396
|
-
expiresAt: new Date(Date.now() + (params.timeout ?? 3e4)),
|
|
1397
|
-
data: fromBase64(params.data)
|
|
1398
|
-
});
|
|
1399
|
-
if (response.type === ILPPacketType.FULFILL) {
|
|
1400
|
-
return {
|
|
1401
|
-
accepted: true,
|
|
1402
|
-
data: response.data.length > 0 ? toBase64(response.data) : void 0
|
|
1403
|
-
};
|
|
1404
|
-
}
|
|
1405
|
-
return {
|
|
1406
|
-
accepted: false,
|
|
1407
|
-
code: response.code,
|
|
1408
|
-
message: response.message,
|
|
1409
|
-
data: response.data.length > 0 ? toBase64(response.data) : void 0
|
|
1410
|
-
};
|
|
1494
|
+
const { prepare, condition } = this.buildPrepare(params);
|
|
1495
|
+
const response = await this.btpClient.sendPacket(prepare);
|
|
1496
|
+
return mapIlpResponse(response, condition);
|
|
1411
1497
|
}
|
|
1412
1498
|
/**
|
|
1413
1499
|
* Single-attempt claim + ILP packet send. Reconnects if not connected.
|
|
@@ -1426,29 +1512,9 @@ var BtpRuntimeClient = class {
|
|
|
1426
1512
|
data: encodeUtf8(JSON.stringify(claim))
|
|
1427
1513
|
}
|
|
1428
1514
|
];
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
amount: BigInt(params.amount),
|
|
1433
|
-
destination: params.destination,
|
|
1434
|
-
executionCondition: new Uint8Array(32),
|
|
1435
|
-
expiresAt: new Date(Date.now() + (params.timeout ?? 3e4)),
|
|
1436
|
-
data: fromBase64(params.data)
|
|
1437
|
-
},
|
|
1438
|
-
protocolData
|
|
1439
|
-
);
|
|
1440
|
-
if (response.type === ILPPacketType.FULFILL) {
|
|
1441
|
-
return {
|
|
1442
|
-
accepted: true,
|
|
1443
|
-
data: response.data.length > 0 ? toBase64(response.data) : void 0
|
|
1444
|
-
};
|
|
1445
|
-
}
|
|
1446
|
-
return {
|
|
1447
|
-
accepted: false,
|
|
1448
|
-
code: response.code,
|
|
1449
|
-
message: response.message,
|
|
1450
|
-
data: response.data.length > 0 ? toBase64(response.data) : void 0
|
|
1451
|
-
};
|
|
1515
|
+
const { prepare, condition } = this.buildPrepare(params);
|
|
1516
|
+
const response = await this.btpClient.sendPacket(prepare, protocolData);
|
|
1517
|
+
return mapIlpResponse(response, condition);
|
|
1452
1518
|
}
|
|
1453
1519
|
};
|
|
1454
1520
|
|
|
@@ -1480,6 +1546,12 @@ var HttpIlpClient = class {
|
|
|
1480
1546
|
* Send an ILP PREPARE via `POST /ilp` WITHOUT a claim. The connector accepts
|
|
1481
1547
|
* this only on free/zero-amount routes; paid writes must use
|
|
1482
1548
|
* {@link sendIlpPacketWithClaim}. Satisfies the IlpClient interface.
|
|
1549
|
+
*
|
|
1550
|
+
* `params` may carry a sender-chosen `executionCondition` and an explicit
|
|
1551
|
+
* `expiresAt` (toon-client#350); omitting both is the legacy zero-condition
|
|
1552
|
+
* path, unchanged. With a non-zero condition the FULFILL preimage is
|
|
1553
|
+
* verified (`sha256(fulfillment) == condition`) and a mismatch is surfaced
|
|
1554
|
+
* as a failed result — see {@link mapIlpResponse}.
|
|
1483
1555
|
*/
|
|
1484
1556
|
async sendIlpPacket(params) {
|
|
1485
1557
|
return withRetry(() => this.postPrepare(params), {
|
|
@@ -1494,6 +1566,9 @@ var HttpIlpClient = class {
|
|
|
1494
1566
|
* as the `ILP-Payment-Channel-Claim` header. `claim` is the SAME JSON object
|
|
1495
1567
|
* the BTP path attaches as the `payment-channel-claim` protocolData entry —
|
|
1496
1568
|
* we base64(JSON.stringify(claim)) it, byte-for-byte identical to BTP.
|
|
1569
|
+
*
|
|
1570
|
+
* Sender-chosen `executionCondition` / explicit `expiresAt` semantics are
|
|
1571
|
+
* identical to {@link sendIlpPacket}.
|
|
1497
1572
|
*/
|
|
1498
1573
|
async sendIlpPacketWithClaim(params, claim) {
|
|
1499
1574
|
return withRetry(() => this.postPrepare(params, claim), {
|
|
@@ -1546,12 +1621,16 @@ var HttpIlpClient = class {
|
|
|
1546
1621
|
*/
|
|
1547
1622
|
async postPrepare(params, claim) {
|
|
1548
1623
|
const requestTimeout = params.timeout ?? this.timeout;
|
|
1624
|
+
const condition = params.executionCondition;
|
|
1625
|
+
if (condition !== void 0 && !isZeroCondition(condition)) {
|
|
1626
|
+
assertValidCondition(condition);
|
|
1627
|
+
}
|
|
1549
1628
|
const prepare = serializeIlpPrepare({
|
|
1550
1629
|
type: ILPPacketType.PREPARE,
|
|
1551
1630
|
amount: BigInt(params.amount),
|
|
1552
1631
|
destination: params.destination,
|
|
1553
|
-
executionCondition: new Uint8Array(32),
|
|
1554
|
-
expiresAt: new Date(Date.now() + requestTimeout),
|
|
1632
|
+
executionCondition: condition ?? new Uint8Array(32),
|
|
1633
|
+
expiresAt: params.expiresAt ?? new Date(Date.now() + requestTimeout),
|
|
1555
1634
|
data: fromBase64(params.data)
|
|
1556
1635
|
});
|
|
1557
1636
|
const headers = {
|
|
@@ -1574,7 +1653,7 @@ var HttpIlpClient = class {
|
|
|
1574
1653
|
signal: controller.signal
|
|
1575
1654
|
});
|
|
1576
1655
|
clearTimeout(timeoutId);
|
|
1577
|
-
return await this.mapResponse(response);
|
|
1656
|
+
return await this.mapResponse(response, condition);
|
|
1578
1657
|
} catch (error) {
|
|
1579
1658
|
clearTimeout(timeoutId);
|
|
1580
1659
|
throw this.mapTransportError(error, requestTimeout);
|
|
@@ -1584,26 +1663,18 @@ var HttpIlpClient = class {
|
|
|
1584
1663
|
* Map a `200 OK` body (OER FULFILL/REJECT) to an IlpSendResult; map a non-2xx
|
|
1585
1664
|
* to a transport error. Per the wire contract, ILP-level rejects arrive as a
|
|
1586
1665
|
* 200 + REJECT body — only HTTP non-2xx means a transport-layer failure.
|
|
1666
|
+
*
|
|
1667
|
+
* When `sentCondition` is non-zero the FULFILL preimage is verified against
|
|
1668
|
+
* it; a mismatch yields `accepted: false` (shared `mapIlpResponse` logic,
|
|
1669
|
+
* identical to the BTP path).
|
|
1587
1670
|
*/
|
|
1588
|
-
async mapResponse(response) {
|
|
1671
|
+
async mapResponse(response, sentCondition) {
|
|
1589
1672
|
if (response.ok) {
|
|
1590
1673
|
const buf = new Uint8Array(await response.arrayBuffer());
|
|
1591
1674
|
if (buf.length === 0) {
|
|
1592
1675
|
throw new ConnectorError("Empty 200 body from /ilp (expected OER ILP response)");
|
|
1593
1676
|
}
|
|
1594
|
-
|
|
1595
|
-
if (ilp.type === ILPPacketType.FULFILL) {
|
|
1596
|
-
return {
|
|
1597
|
-
accepted: true,
|
|
1598
|
-
data: ilp.data.length > 0 ? toBase64(ilp.data) : void 0
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
return {
|
|
1602
|
-
accepted: false,
|
|
1603
|
-
code: ilp.code,
|
|
1604
|
-
message: ilp.message,
|
|
1605
|
-
data: ilp.data.length > 0 ? toBase64(ilp.data) : void 0
|
|
1606
|
-
};
|
|
1677
|
+
return mapIlpResponse(deserializeIlpPacket(buf), sentCondition);
|
|
1607
1678
|
}
|
|
1608
1679
|
const body = await response.text().catch(() => "");
|
|
1609
1680
|
const detail = body ? `: ${body}` : "";
|
|
@@ -1692,7 +1763,7 @@ import { base58Encode as base58Encode2 } from "@toon-protocol/core";
|
|
|
1692
1763
|
|
|
1693
1764
|
// src/channel/solana-payment-channel.ts
|
|
1694
1765
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
1695
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
1766
|
+
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
1696
1767
|
import { base58Encode, base58Decode } from "@toon-protocol/core";
|
|
1697
1768
|
var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
1698
1769
|
var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
|
|
@@ -1781,7 +1852,7 @@ function findProgramAddress(seeds, programId) {
|
|
|
1781
1852
|
input.set(programId, offset);
|
|
1782
1853
|
offset += programId.length;
|
|
1783
1854
|
input.set(PDA_MARKER, offset);
|
|
1784
|
-
const hash =
|
|
1855
|
+
const hash = sha2562(input);
|
|
1785
1856
|
if (!isOnCurve(hash)) return { pda: hash, bump };
|
|
1786
1857
|
}
|
|
1787
1858
|
throw new Error("Could not find a viable PDA bump seed");
|
|
@@ -3234,7 +3305,7 @@ var SolanaSigner = class {
|
|
|
3234
3305
|
|
|
3235
3306
|
// src/signing/mina-signer.ts
|
|
3236
3307
|
import { hexToMinaBase58PrivateKey as hexToMinaBase58PrivateKey3 } from "@toon-protocol/core";
|
|
3237
|
-
import { sha256 as
|
|
3308
|
+
import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
|
|
3238
3309
|
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
3239
3310
|
|
|
3240
3311
|
// src/channel/mina-payment-channel.ts
|
|
@@ -3365,7 +3436,7 @@ var DEFAULT_MINA_TOKEN_ID = "MINA";
|
|
|
3365
3436
|
var MINA_CLAIM_NETWORK = "devnet";
|
|
3366
3437
|
function deriveMinaSalt(zkAppAddress, nonce) {
|
|
3367
3438
|
const digestHex = bytesToHex(
|
|
3368
|
-
|
|
3439
|
+
sha2563(new TextEncoder().encode(`mina-pc-salt:${zkAppAddress}:${nonce}`))
|
|
3369
3440
|
);
|
|
3370
3441
|
const salt = BigInt("0x" + digestHex.slice(0, 60));
|
|
3371
3442
|
return salt === 0n ? 1n : salt;
|
|
@@ -3957,6 +4028,38 @@ async function readEvmTokenBalance(opts) {
|
|
|
3957
4028
|
if (decimals !== void 0) out.assetScale = Number(decimals);
|
|
3958
4029
|
return out;
|
|
3959
4030
|
}
|
|
4031
|
+
async function readEvmNativeBalance(opts) {
|
|
4032
|
+
const chainId = parseEvmChainId(opts.chainKey);
|
|
4033
|
+
const client = createPublicClient2({
|
|
4034
|
+
transport: http2(opts.rpcUrl),
|
|
4035
|
+
chain: defineChain2({
|
|
4036
|
+
id: chainId,
|
|
4037
|
+
name: opts.chainKey,
|
|
4038
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
4039
|
+
rpcUrls: { default: { http: [opts.rpcUrl] } }
|
|
4040
|
+
})
|
|
4041
|
+
});
|
|
4042
|
+
const wei = await client.getBalance({ address: opts.owner });
|
|
4043
|
+
return { symbol: "ETH", amount: wei.toString(), decimals: 18 };
|
|
4044
|
+
}
|
|
4045
|
+
async function readSolanaNativeBalance(opts) {
|
|
4046
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
4047
|
+
const res = await fetchImpl(opts.rpcUrl, {
|
|
4048
|
+
method: "POST",
|
|
4049
|
+
headers: { "content-type": "application/json" },
|
|
4050
|
+
body: JSON.stringify({
|
|
4051
|
+
jsonrpc: "2.0",
|
|
4052
|
+
id: 1,
|
|
4053
|
+
method: "getBalance",
|
|
4054
|
+
params: [opts.owner, { commitment: "confirmed" }]
|
|
4055
|
+
})
|
|
4056
|
+
});
|
|
4057
|
+
if (!res.ok) throw new Error(`Solana RPC request failed: HTTP ${res.status}`);
|
|
4058
|
+
const json = await res.json();
|
|
4059
|
+
if (json.error) throw new Error(`Solana RPC error: ${json.error.message ?? "unknown"}`);
|
|
4060
|
+
const lamports = BigInt(json.result?.value ?? 0);
|
|
4061
|
+
return { symbol: "SOL", amount: lamports.toString(), decimals: 9 };
|
|
4062
|
+
}
|
|
3960
4063
|
async function readSolanaTokenBalance(opts) {
|
|
3961
4064
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
3962
4065
|
const res = await fetchImpl(opts.rpcUrl, {
|
|
@@ -4004,6 +4107,95 @@ async function readMinaBalance(opts) {
|
|
|
4004
4107
|
assetScale: 9
|
|
4005
4108
|
};
|
|
4006
4109
|
}
|
|
4110
|
+
var errText = (e) => e instanceof Error ? e.message : String(e);
|
|
4111
|
+
function foldNative(out, settled, errors) {
|
|
4112
|
+
if (settled.status === "fulfilled") out.native = settled.value;
|
|
4113
|
+
else errors.push(errText(settled.reason));
|
|
4114
|
+
}
|
|
4115
|
+
function foldToken(out, settled, tokenAddress, errors) {
|
|
4116
|
+
if (settled.status === "rejected") {
|
|
4117
|
+
errors.push(errText(settled.reason));
|
|
4118
|
+
return;
|
|
4119
|
+
}
|
|
4120
|
+
const bal = settled.value;
|
|
4121
|
+
if (!bal) return;
|
|
4122
|
+
out.tokens.push({
|
|
4123
|
+
symbol: bal.asset,
|
|
4124
|
+
amount: bal.amount,
|
|
4125
|
+
decimals: bal.assetScale,
|
|
4126
|
+
...tokenAddress ? { address: tokenAddress } : {}
|
|
4127
|
+
});
|
|
4128
|
+
}
|
|
4129
|
+
function finalizeChain(out, errors) {
|
|
4130
|
+
if (errors.length > 0) out.error = errors[0];
|
|
4131
|
+
if (out.native === void 0 && out.tokens.length === 0) out.unreadable = true;
|
|
4132
|
+
}
|
|
4133
|
+
async function readWalletBalances(sources) {
|
|
4134
|
+
const { fetchImpl } = sources;
|
|
4135
|
+
const tasks = [];
|
|
4136
|
+
if (sources.evm) {
|
|
4137
|
+
const { chainKey, rpcUrl, owner, tokenAddress } = sources.evm;
|
|
4138
|
+
tasks.push(
|
|
4139
|
+
(async () => {
|
|
4140
|
+
const out = { chain: "evm", chainKey, address: owner, tokens: [] };
|
|
4141
|
+
const errors = [];
|
|
4142
|
+
const [nativeR, tokenR] = await Promise.allSettled([
|
|
4143
|
+
readEvmNativeBalance({ rpcUrl, chainKey, owner }),
|
|
4144
|
+
tokenAddress ? readEvmTokenBalance({ rpcUrl, chainKey, tokenAddress, owner }) : Promise.resolve(void 0)
|
|
4145
|
+
]);
|
|
4146
|
+
foldNative(out, nativeR, errors);
|
|
4147
|
+
foldToken(out, tokenR, tokenAddress, errors);
|
|
4148
|
+
finalizeChain(out, errors);
|
|
4149
|
+
return out;
|
|
4150
|
+
})()
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
if (sources.solana) {
|
|
4154
|
+
const { chainKey = "solana", rpcUrl, owner, tokenMint } = sources.solana;
|
|
4155
|
+
tasks.push(
|
|
4156
|
+
(async () => {
|
|
4157
|
+
const out = { chain: "solana", chainKey, address: owner, tokens: [] };
|
|
4158
|
+
const errors = [];
|
|
4159
|
+
const [nativeR, tokenR] = await Promise.allSettled([
|
|
4160
|
+
readSolanaNativeBalance({ rpcUrl, owner, fetchImpl }),
|
|
4161
|
+
tokenMint ? readSolanaTokenBalance({ rpcUrl, mint: tokenMint, owner, fetchImpl }) : Promise.resolve(void 0)
|
|
4162
|
+
]);
|
|
4163
|
+
foldNative(out, nativeR, errors);
|
|
4164
|
+
if (tokenR.status === "fulfilled" && tokenR.value && tokenR.value.asset === void 0) {
|
|
4165
|
+
tokenR.value.asset = "USDC";
|
|
4166
|
+
}
|
|
4167
|
+
foldToken(out, tokenR, tokenMint, errors);
|
|
4168
|
+
finalizeChain(out, errors);
|
|
4169
|
+
return out;
|
|
4170
|
+
})()
|
|
4171
|
+
);
|
|
4172
|
+
}
|
|
4173
|
+
if (sources.mina) {
|
|
4174
|
+
const { chainKey = "mina", graphqlUrl, owner } = sources.mina;
|
|
4175
|
+
tasks.push(
|
|
4176
|
+
(async () => {
|
|
4177
|
+
const out = { chain: "mina", chainKey, address: owner, tokens: [] };
|
|
4178
|
+
const errors = [];
|
|
4179
|
+
const nativeR = await Promise.allSettled([readMinaBalance({ graphqlUrl, owner, fetchImpl })]);
|
|
4180
|
+
foldNative(
|
|
4181
|
+
out,
|
|
4182
|
+
nativeR[0].status === "fulfilled" ? {
|
|
4183
|
+
status: "fulfilled",
|
|
4184
|
+
value: {
|
|
4185
|
+
symbol: nativeR[0].value.asset,
|
|
4186
|
+
amount: nativeR[0].value.amount,
|
|
4187
|
+
decimals: nativeR[0].value.assetScale
|
|
4188
|
+
}
|
|
4189
|
+
} : nativeR[0],
|
|
4190
|
+
errors
|
|
4191
|
+
);
|
|
4192
|
+
finalizeChain(out, errors);
|
|
4193
|
+
return out;
|
|
4194
|
+
})()
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4197
|
+
return Promise.all(tasks);
|
|
4198
|
+
}
|
|
4007
4199
|
|
|
4008
4200
|
// src/blob-storage.ts
|
|
4009
4201
|
import { buildBlobStorageRequest } from "@toon-protocol/core";
|
|
@@ -4776,6 +4968,14 @@ var ToonClient = class {
|
|
|
4776
4968
|
* matching `destination`,
|
|
4777
4969
|
* (c) neither -> throw MISSING_CLAIM.
|
|
4778
4970
|
*
|
|
4971
|
+
* A caller may supply a sender-chosen 32-byte `executionCondition`
|
|
4972
|
+
* (`C = sha256(P)`, one fresh preimage per packet — toon-client#350,
|
|
4973
|
+
* rolling-swap spec §3 R1/R2) and an explicit `expiresAt` (R7). Both are
|
|
4974
|
+
* set on the wire by either transport (HTTP `POST /ilp` and BTP), and on
|
|
4975
|
+
* FULFILL the transport verifies `sha256(fulfillment) == condition` —
|
|
4976
|
+
* a mismatch comes back as `accepted: false` (code F99), never a silent
|
|
4977
|
+
* accept. Omitting them keeps today's zero-condition legacy packet.
|
|
4978
|
+
*
|
|
4779
4979
|
* @throws {ToonClientError} INVALID_STATE / NO_ILP_TRANSPORT / MISSING_CLAIM
|
|
4780
4980
|
*/
|
|
4781
4981
|
async sendSwapPacket(params) {
|
|
@@ -4796,7 +4996,9 @@ var ToonClient = class {
|
|
|
4796
4996
|
destination: params.destination,
|
|
4797
4997
|
amount: String(params.amount),
|
|
4798
4998
|
data: toBase64(params.toonData),
|
|
4799
|
-
timeout: params.timeout ?? 3e4
|
|
4999
|
+
timeout: params.timeout ?? 3e4,
|
|
5000
|
+
...params.executionCondition ? { executionCondition: params.executionCondition } : {},
|
|
5001
|
+
...params.expiresAt ? { expiresAt: params.expiresAt } : {}
|
|
4800
5002
|
},
|
|
4801
5003
|
claimMessage
|
|
4802
5004
|
);
|
|
@@ -5154,6 +5356,74 @@ var ToonClient = class {
|
|
|
5154
5356
|
}
|
|
5155
5357
|
return out;
|
|
5156
5358
|
}
|
|
5359
|
+
/**
|
|
5360
|
+
* The FULL multi-chain wallet view (#299): for every chain the identity is
|
|
5361
|
+
* configured for, the native coin (ETH / SOL / MINA) AND every configured
|
|
5362
|
+
* token (USDC), grouped per chain with the identity's address on that chain.
|
|
5363
|
+
* A superset of {@link getBalances} — which stays scoped to the channel's
|
|
5364
|
+
* settlement token — kept as a separate reader so channel-settlement callers
|
|
5365
|
+
* are unaffected.
|
|
5366
|
+
*
|
|
5367
|
+
* FREE: read-only RPC, no signing, no payment. Works on an UNSTARTED client:
|
|
5368
|
+
* the Solana/Mina addresses (which the signers only register during
|
|
5369
|
+
* `start()`) are derived on demand from the retained mnemonic — the SAME keys
|
|
5370
|
+
* `start()` would register and that `rig fund` prints — so all configured
|
|
5371
|
+
* chains appear even before a start. Best-effort per chain: an unreachable
|
|
5372
|
+
* RPC yields `{ unreadable: true }` for that chain, never failing the others.
|
|
5373
|
+
*/
|
|
5374
|
+
async getWalletBalances() {
|
|
5375
|
+
const sources = {};
|
|
5376
|
+
let derived;
|
|
5377
|
+
let derivedTried = false;
|
|
5378
|
+
const ensureDerived = async () => {
|
|
5379
|
+
if (derivedTried) return derived;
|
|
5380
|
+
derivedTried = true;
|
|
5381
|
+
if (this.config.mnemonic) {
|
|
5382
|
+
derived = await deriveFullIdentity(
|
|
5383
|
+
this.config.mnemonic,
|
|
5384
|
+
this.config.mnemonicAccountIndex ?? 0
|
|
5385
|
+
);
|
|
5386
|
+
}
|
|
5387
|
+
return derived;
|
|
5388
|
+
};
|
|
5389
|
+
const evmAddress = this.getEvmAddress();
|
|
5390
|
+
const rpcUrls = this.config.chainRpcUrls;
|
|
5391
|
+
const tokens = this.config.preferredTokens;
|
|
5392
|
+
if (evmAddress && rpcUrls) {
|
|
5393
|
+
const usableEvm = (c) => c.startsWith("evm") && Boolean(rpcUrls[c]);
|
|
5394
|
+
const settlementKeys = Object.keys(this.config.settlementAddresses ?? {});
|
|
5395
|
+
const chainKeys = this.config.supportedChains ?? Object.keys(rpcUrls);
|
|
5396
|
+
const chainKey = settlementKeys.find(usableEvm) ?? chainKeys.find(usableEvm);
|
|
5397
|
+
if (chainKey && rpcUrls[chainKey]) {
|
|
5398
|
+
sources.evm = {
|
|
5399
|
+
chainKey,
|
|
5400
|
+
rpcUrl: rpcUrls[chainKey],
|
|
5401
|
+
owner: evmAddress,
|
|
5402
|
+
...tokens?.[chainKey] ? { tokenAddress: tokens[chainKey] } : {}
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
const sol = this.config.solanaChannel;
|
|
5407
|
+
if (sol?.rpcUrl) {
|
|
5408
|
+
const solAddress = this.getSolanaAddress() ?? (await ensureDerived())?.solana.publicKey;
|
|
5409
|
+
if (solAddress) {
|
|
5410
|
+
sources.solana = {
|
|
5411
|
+
chainKey: "solana",
|
|
5412
|
+
rpcUrl: sol.rpcUrl,
|
|
5413
|
+
owner: solAddress,
|
|
5414
|
+
...sol.tokenMint ? { tokenMint: sol.tokenMint } : {}
|
|
5415
|
+
};
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5418
|
+
const mina = this.config.minaChannel;
|
|
5419
|
+
if (mina?.graphqlUrl) {
|
|
5420
|
+
const minaAddress = this.getMinaAddress() ?? (await ensureDerived())?.mina.publicKey;
|
|
5421
|
+
if (minaAddress) {
|
|
5422
|
+
sources.mina = { chainKey: "mina", graphqlUrl: mina.graphqlUrl, owner: minaAddress };
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
return readWalletBalances(sources);
|
|
5426
|
+
}
|
|
5157
5427
|
/**
|
|
5158
5428
|
* Resolves an ILP destination address to a peer ID.
|
|
5159
5429
|
* Convention: destination "g.toon.peer1" → peerId "peer1" (last segment).
|
|
@@ -6582,7 +6852,7 @@ import {
|
|
|
6582
6852
|
scryptSync,
|
|
6583
6853
|
createCipheriv,
|
|
6584
6854
|
createDecipheriv,
|
|
6585
|
-
randomBytes
|
|
6855
|
+
randomBytes as randomBytes2
|
|
6586
6856
|
} from "crypto";
|
|
6587
6857
|
import { writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
|
|
6588
6858
|
var SCRYPT_N = 2 ** 17;
|
|
@@ -6609,8 +6879,8 @@ function encryptMnemonic2(mnemonic, password) {
|
|
|
6609
6879
|
if (typeof password !== "string" || password.length === 0) {
|
|
6610
6880
|
throw new Error("encryptMnemonic: password must be a non-empty string");
|
|
6611
6881
|
}
|
|
6612
|
-
const salt =
|
|
6613
|
-
const iv =
|
|
6882
|
+
const salt = randomBytes2(SALT_LEN);
|
|
6883
|
+
const iv = randomBytes2(IV_LEN);
|
|
6614
6884
|
const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {
|
|
6615
6885
|
N: SCRYPT_N,
|
|
6616
6886
|
r: SCRYPT_R,
|
|
@@ -6713,10 +6983,13 @@ function writeKeystoreFile(path, keystore) {
|
|
|
6713
6983
|
}
|
|
6714
6984
|
export {
|
|
6715
6985
|
BtpRuntimeClient,
|
|
6986
|
+
CONDITION_LENGTH,
|
|
6716
6987
|
ChannelFundingError,
|
|
6717
6988
|
ChannelManager,
|
|
6718
6989
|
ConnectorError,
|
|
6719
6990
|
EvmSigner,
|
|
6991
|
+
FULFILLMENT_MISMATCH_CODE,
|
|
6992
|
+
FULFILLMENT_MISMATCH_MESSAGE,
|
|
6720
6993
|
GenerativeFallbackRenderer,
|
|
6721
6994
|
Http402Client,
|
|
6722
6995
|
HttpConnectorAdmin,
|
|
@@ -6741,6 +7014,7 @@ export {
|
|
|
6741
7014
|
ValidationError,
|
|
6742
7015
|
applyDefaults,
|
|
6743
7016
|
applyNetworkPresets,
|
|
7017
|
+
assertValidCondition,
|
|
6744
7018
|
buildBackupEvent,
|
|
6745
7019
|
buildBackupFilter,
|
|
6746
7020
|
buildConsentRequest,
|
|
@@ -6758,6 +7032,7 @@ export {
|
|
|
6758
7032
|
encryptMnemonic2 as encryptMnemonic,
|
|
6759
7033
|
extractArweaveTxId,
|
|
6760
7034
|
extractUiResource,
|
|
7035
|
+
fulfillmentMatchesCondition,
|
|
6761
7036
|
fundWallet,
|
|
6762
7037
|
generateKeystore,
|
|
6763
7038
|
generateMnemonic,
|
|
@@ -6770,7 +7045,9 @@ export {
|
|
|
6770
7045
|
isInsufficientGasError,
|
|
6771
7046
|
isPrfSupported,
|
|
6772
7047
|
isTrustDowngrade,
|
|
7048
|
+
isZeroCondition,
|
|
6773
7049
|
loadKeystore,
|
|
7050
|
+
mintExecutionCondition,
|
|
6774
7051
|
parseBackupPayload,
|
|
6775
7052
|
parseFulfillHttp,
|
|
6776
7053
|
parseFulfillHttpBytes,
|
|
@@ -6781,7 +7058,13 @@ export {
|
|
|
6781
7058
|
proxyIlpEndpoint,
|
|
6782
7059
|
publishBackCoordinate,
|
|
6783
7060
|
readDiscoveredIlpPeer,
|
|
7061
|
+
readEvmNativeBalance,
|
|
7062
|
+
readEvmTokenBalance,
|
|
7063
|
+
readMinaBalance,
|
|
6784
7064
|
readMinaDepositTotal,
|
|
7065
|
+
readSolanaNativeBalance,
|
|
7066
|
+
readSolanaTokenBalance,
|
|
7067
|
+
readWalletBalances,
|
|
6785
7068
|
renderDeterministicHtml,
|
|
6786
7069
|
renderDispatch,
|
|
6787
7070
|
requestBlobStorage,
|