@toon-protocol/client 0.17.0 → 0.18.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 +289 -6
- package/dist/index.js +475 -32
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -153,7 +153,7 @@ async function deriveSolanaKey(seed, accountIndex = 0) {
|
|
|
153
153
|
const { ed25519: ed255194 } = await import("@noble/curves/ed25519.js");
|
|
154
154
|
const encoder = new TextEncoder();
|
|
155
155
|
let I = hmac(sha512, encoder.encode("ed25519 seed"), seed);
|
|
156
|
-
let
|
|
156
|
+
let key2 = I.slice(0, 32);
|
|
157
157
|
let chainCode = I.slice(32);
|
|
158
158
|
const indices = [
|
|
159
159
|
2147483692,
|
|
@@ -168,18 +168,18 @@ async function deriveSolanaKey(seed, accountIndex = 0) {
|
|
|
168
168
|
for (const index of indices) {
|
|
169
169
|
const data = new Uint8Array(37);
|
|
170
170
|
data[0] = 0;
|
|
171
|
-
data.set(
|
|
171
|
+
data.set(key2, 1);
|
|
172
172
|
data[33] = index >>> 24 & 255;
|
|
173
173
|
data[34] = index >>> 16 & 255;
|
|
174
174
|
data[35] = index >>> 8 & 255;
|
|
175
175
|
data[36] = index & 255;
|
|
176
176
|
I = hmac(sha512, chainCode, data);
|
|
177
|
-
|
|
177
|
+
key2 = I.slice(0, 32);
|
|
178
178
|
chainCode = I.slice(32);
|
|
179
179
|
}
|
|
180
|
-
const publicKeyBytes = ed255194.getPublicKey(
|
|
180
|
+
const publicKeyBytes = ed255194.getPublicKey(key2);
|
|
181
181
|
const keypair = new Uint8Array(64);
|
|
182
|
-
keypair.set(
|
|
182
|
+
keypair.set(key2, 0);
|
|
183
183
|
keypair.set(publicKeyBytes, 32);
|
|
184
184
|
const publicKey = toBase58(publicKeyBytes);
|
|
185
185
|
return { secretKey: keypair, publicKey };
|
|
@@ -1309,6 +1309,10 @@ function fulfillmentMatchesCondition(fulfillment, condition) {
|
|
|
1309
1309
|
}
|
|
1310
1310
|
|
|
1311
1311
|
// src/adapters/ilp-send.ts
|
|
1312
|
+
function resolveExpiresAt(expiresAt, timeoutMs) {
|
|
1313
|
+
if (expiresAt === void 0) return new Date(Date.now() + timeoutMs);
|
|
1314
|
+
return expiresAt instanceof Date ? new Date(expiresAt.getTime()) : new Date(expiresAt);
|
|
1315
|
+
}
|
|
1312
1316
|
var FULFILLMENT_MISMATCH_CODE = "F99";
|
|
1313
1317
|
var FULFILLMENT_MISMATCH_MESSAGE = "FULFILL fulfillment does not match execution condition (sha256(fulfillment) != executionCondition) \u2014 packet counted failed";
|
|
1314
1318
|
function mapIlpResponse(packet, sentCondition) {
|
|
@@ -1478,7 +1482,7 @@ var BtpRuntimeClient = class {
|
|
|
1478
1482
|
amount: BigInt(params.amount),
|
|
1479
1483
|
destination: params.destination,
|
|
1480
1484
|
executionCondition: condition ?? new Uint8Array(32),
|
|
1481
|
-
expiresAt: params.expiresAt
|
|
1485
|
+
expiresAt: resolveExpiresAt(params.expiresAt, params.timeout ?? 3e4),
|
|
1482
1486
|
data: fromBase64(params.data)
|
|
1483
1487
|
},
|
|
1484
1488
|
condition
|
|
@@ -1630,7 +1634,7 @@ var HttpIlpClient = class {
|
|
|
1630
1634
|
amount: BigInt(params.amount),
|
|
1631
1635
|
destination: params.destination,
|
|
1632
1636
|
executionCondition: condition ?? new Uint8Array(32),
|
|
1633
|
-
expiresAt: params.expiresAt
|
|
1637
|
+
expiresAt: resolveExpiresAt(params.expiresAt, requestTimeout),
|
|
1634
1638
|
data: fromBase64(params.data)
|
|
1635
1639
|
});
|
|
1636
1640
|
const headers = {
|
|
@@ -1734,17 +1738,17 @@ function readDiscoveredIlpPeer(peer) {
|
|
|
1734
1738
|
}
|
|
1735
1739
|
function selectIlpTransport(peer, options = {}) {
|
|
1736
1740
|
const needsDuplex = options.needsDuplex ?? false;
|
|
1737
|
-
const
|
|
1741
|
+
const http4 = peer.httpEndpoint?.trim() || void 0;
|
|
1738
1742
|
const btp = peer.btpEndpoint?.trim() || void 0;
|
|
1739
1743
|
const canUpgrade = peer.supportsUpgrade === true;
|
|
1740
1744
|
if (needsDuplex) {
|
|
1741
1745
|
if (btp) return { kind: "btp", btpEndpoint: btp };
|
|
1742
|
-
if (
|
|
1746
|
+
if (http4 && canUpgrade) return { kind: "http-upgradable", httpEndpoint: http4 };
|
|
1743
1747
|
throw new Error(
|
|
1744
1748
|
"Duplex transport required but peer exposes neither a btpEndpoint nor an upgradable httpEndpoint"
|
|
1745
1749
|
);
|
|
1746
1750
|
}
|
|
1747
|
-
if (
|
|
1751
|
+
if (http4) return { kind: "http", httpEndpoint: http4, canUpgrade };
|
|
1748
1752
|
if (btp) return { kind: "btp", btpEndpoint: btp };
|
|
1749
1753
|
throw new Error("Peer exposes neither an httpEndpoint nor a btpEndpoint");
|
|
1750
1754
|
}
|
|
@@ -1965,13 +1969,13 @@ async function buildAndSendTransaction(rpcUrl, feePayer, instructions, additiona
|
|
|
1965
1969
|
isWritable: true
|
|
1966
1970
|
});
|
|
1967
1971
|
for (const ix of instructions) {
|
|
1968
|
-
for (const
|
|
1969
|
-
const existing = accountMap.get(
|
|
1972
|
+
for (const key2 of ix.keys) {
|
|
1973
|
+
const existing = accountMap.get(key2.pubkey);
|
|
1970
1974
|
if (existing) {
|
|
1971
|
-
existing.isSigner = existing.isSigner ||
|
|
1972
|
-
existing.isWritable = existing.isWritable ||
|
|
1975
|
+
existing.isSigner = existing.isSigner || key2.isSigner;
|
|
1976
|
+
existing.isWritable = existing.isWritable || key2.isWritable;
|
|
1973
1977
|
} else {
|
|
1974
|
-
accountMap.set(
|
|
1978
|
+
accountMap.set(key2.pubkey, { ...key2 });
|
|
1975
1979
|
}
|
|
1976
1980
|
}
|
|
1977
1981
|
if (!accountMap.has(ix.programId)) {
|
|
@@ -4267,8 +4271,8 @@ async function requestBlobStorage(client, secretKey, params) {
|
|
|
4267
4271
|
};
|
|
4268
4272
|
}
|
|
4269
4273
|
function extractArweaveTxId(base64Data) {
|
|
4270
|
-
const
|
|
4271
|
-
if (!
|
|
4274
|
+
const http4 = parseFulfillHttp(base64Data);
|
|
4275
|
+
if (!http4.isHttp) {
|
|
4272
4276
|
const legacy = decodeUtf8(fromBase64(base64Data));
|
|
4273
4277
|
if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {
|
|
4274
4278
|
throw new Error(
|
|
@@ -4277,18 +4281,18 @@ function extractArweaveTxId(base64Data) {
|
|
|
4277
4281
|
}
|
|
4278
4282
|
return legacy;
|
|
4279
4283
|
}
|
|
4280
|
-
if (
|
|
4281
|
-
const detail =
|
|
4284
|
+
if (http4.status < 200 || http4.status >= 300) {
|
|
4285
|
+
const detail = http4.body ? ` - ${http4.body}` : "";
|
|
4282
4286
|
throw new Error(
|
|
4283
|
-
`Blob upload failed: DVM returned HTTP ${
|
|
4287
|
+
`Blob upload failed: DVM returned HTTP ${http4.status} ${http4.statusText}`.trimEnd() + detail
|
|
4284
4288
|
);
|
|
4285
4289
|
}
|
|
4286
4290
|
let parsed;
|
|
4287
4291
|
try {
|
|
4288
|
-
parsed = JSON.parse(
|
|
4292
|
+
parsed = JSON.parse(http4.body);
|
|
4289
4293
|
} catch {
|
|
4290
4294
|
throw new Error(
|
|
4291
|
-
`Blob upload response body was not valid JSON: "${
|
|
4295
|
+
`Blob upload response body was not valid JSON: "${http4.body}"`
|
|
4292
4296
|
);
|
|
4293
4297
|
}
|
|
4294
4298
|
const body = parsed;
|
|
@@ -4306,7 +4310,7 @@ function extractArweaveTxId(base64Data) {
|
|
|
4306
4310
|
}
|
|
4307
4311
|
}
|
|
4308
4312
|
throw new Error(
|
|
4309
|
-
`Blob upload response did not contain a valid Arweave tx ID: "${
|
|
4313
|
+
`Blob upload response did not contain a valid Arweave tx ID: "${http4.body}"`
|
|
4310
4314
|
);
|
|
4311
4315
|
}
|
|
4312
4316
|
|
|
@@ -4561,6 +4565,172 @@ function parseHttpResponse(bytes) {
|
|
|
4561
4565
|
);
|
|
4562
4566
|
}
|
|
4563
4567
|
|
|
4568
|
+
// src/swap/settle-received-claims.ts
|
|
4569
|
+
import {
|
|
4570
|
+
buildSettlementTx,
|
|
4571
|
+
SettlementTxError
|
|
4572
|
+
} from "@toon-protocol/sdk";
|
|
4573
|
+
import {
|
|
4574
|
+
createPublicClient as createPublicClient3,
|
|
4575
|
+
defineChain as defineChain3,
|
|
4576
|
+
fromRlp,
|
|
4577
|
+
http as http3
|
|
4578
|
+
} from "viem";
|
|
4579
|
+
function entryToAccumulatedClaim(entry) {
|
|
4580
|
+
return {
|
|
4581
|
+
packetIndex: 0,
|
|
4582
|
+
sourceAmount: 0n,
|
|
4583
|
+
targetAmount: entry.cumulativeAmount,
|
|
4584
|
+
claimBytes: entry.claimBytes,
|
|
4585
|
+
swapEphemeralPubkey: "",
|
|
4586
|
+
...entry.claimId !== void 0 ? { claimId: entry.claimId } : {},
|
|
4587
|
+
pair: entry.pair,
|
|
4588
|
+
receivedAt: entry.receivedAt,
|
|
4589
|
+
channelId: entry.channelId,
|
|
4590
|
+
nonce: entry.nonce.toString(),
|
|
4591
|
+
cumulativeAmount: entry.cumulativeAmount.toString(),
|
|
4592
|
+
recipient: entry.recipient,
|
|
4593
|
+
swapSignerAddress: entry.swapSignerAddress
|
|
4594
|
+
};
|
|
4595
|
+
}
|
|
4596
|
+
function parseEvmChainId2(chain) {
|
|
4597
|
+
const parts = chain.split(":");
|
|
4598
|
+
const raw = parts.length >= 3 ? parts[2] : parts[1];
|
|
4599
|
+
const chainId = Number.parseInt(raw ?? "", 10);
|
|
4600
|
+
return Number.isInteger(chainId) && chainId > 0 ? chainId : void 0;
|
|
4601
|
+
}
|
|
4602
|
+
function buildSwapSettlements(params) {
|
|
4603
|
+
return params.entries.map((entry) => {
|
|
4604
|
+
const base = { chain: entry.chain, channelId: entry.channelId };
|
|
4605
|
+
const signer = { address: entry.swapSignerAddress };
|
|
4606
|
+
const contract = params.tokenNetworks?.[entry.chain];
|
|
4607
|
+
if (entry.chain.startsWith("evm")) {
|
|
4608
|
+
const chainId = parseEvmChainId2(entry.chain);
|
|
4609
|
+
if (!contract || chainId === void 0) {
|
|
4610
|
+
return {
|
|
4611
|
+
...base,
|
|
4612
|
+
error: {
|
|
4613
|
+
code: "MISSING_CHAIN_CONFIG",
|
|
4614
|
+
message: `EVM settlement for ${entry.chain} needs tokenNetworks["${entry.chain}"] (TokenNetwork contract) and a numeric chain id in the chain key.`
|
|
4615
|
+
}
|
|
4616
|
+
};
|
|
4617
|
+
}
|
|
4618
|
+
signer.contractAddress = contract;
|
|
4619
|
+
signer.chainId = chainId;
|
|
4620
|
+
} else if (entry.chain.startsWith("solana")) {
|
|
4621
|
+
if (!contract) {
|
|
4622
|
+
return {
|
|
4623
|
+
...base,
|
|
4624
|
+
error: {
|
|
4625
|
+
code: "MISSING_CHAIN_CONFIG",
|
|
4626
|
+
message: `Solana settlement for ${entry.chain} needs tokenNetworks["${entry.chain}"] (programId).`
|
|
4627
|
+
}
|
|
4628
|
+
};
|
|
4629
|
+
}
|
|
4630
|
+
signer.programId = contract;
|
|
4631
|
+
}
|
|
4632
|
+
try {
|
|
4633
|
+
const result = buildSettlementTx({
|
|
4634
|
+
claims: [entryToAccumulatedClaim(entry)],
|
|
4635
|
+
signers: { [entry.chain]: signer },
|
|
4636
|
+
recipients: { [entry.chain]: entry.recipient },
|
|
4637
|
+
...params.minaSignerClient ? { minaSignerClient: params.minaSignerClient } : {}
|
|
4638
|
+
});
|
|
4639
|
+
const firstRejected = result.rejected[0];
|
|
4640
|
+
if (firstRejected) {
|
|
4641
|
+
return {
|
|
4642
|
+
...base,
|
|
4643
|
+
error: {
|
|
4644
|
+
code: firstRejected.reason,
|
|
4645
|
+
message: firstRejected.details ?? `stored claim for ${entry.chain}/${entry.channelId} failed settlement re-verification`
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
}
|
|
4649
|
+
const bundle = result.bundles[0];
|
|
4650
|
+
if (!bundle) {
|
|
4651
|
+
return {
|
|
4652
|
+
...base,
|
|
4653
|
+
error: {
|
|
4654
|
+
code: "NO_BUNDLE",
|
|
4655
|
+
message: `buildSettlementTx produced no bundle for ${entry.chain}/${entry.channelId}`
|
|
4656
|
+
}
|
|
4657
|
+
};
|
|
4658
|
+
}
|
|
4659
|
+
return { ...base, bundle };
|
|
4660
|
+
} catch (err) {
|
|
4661
|
+
const code = err instanceof SettlementTxError ? err.code : "BUILD_FAILED";
|
|
4662
|
+
return {
|
|
4663
|
+
...base,
|
|
4664
|
+
error: {
|
|
4665
|
+
code,
|
|
4666
|
+
message: err instanceof Error ? err.message : String(err)
|
|
4667
|
+
}
|
|
4668
|
+
};
|
|
4669
|
+
}
|
|
4670
|
+
});
|
|
4671
|
+
}
|
|
4672
|
+
function decodeEvmSettlementTx(bundle) {
|
|
4673
|
+
const fields = fromRlp(bundle.unsignedTxBytes, "hex");
|
|
4674
|
+
if (!Array.isArray(fields) || fields.length !== 9) {
|
|
4675
|
+
throw new Error(
|
|
4676
|
+
`settlement bundle RLP is not a 9-field unsigned EVM tx (got ${Array.isArray(fields) ? fields.length : typeof fields})`
|
|
4677
|
+
);
|
|
4678
|
+
}
|
|
4679
|
+
const to = fields[3];
|
|
4680
|
+
const data = fields[5];
|
|
4681
|
+
const chainIdHex = fields[6];
|
|
4682
|
+
const chainId = Number.parseInt(chainIdHex === "0x" ? "0x0" : chainIdHex, 16);
|
|
4683
|
+
if (typeof to !== "string" || to.length !== 42) {
|
|
4684
|
+
throw new Error(`settlement tx "to" is not a 20-byte address: ${String(to)}`);
|
|
4685
|
+
}
|
|
4686
|
+
return { to, data: data === "0x" ? "0x" : data, chainId };
|
|
4687
|
+
}
|
|
4688
|
+
async function submitEvmSettlement(bundle, params) {
|
|
4689
|
+
if (bundle.chainKind !== "evm") {
|
|
4690
|
+
throw new Error(
|
|
4691
|
+
`submitEvmSettlement only submits evm bundles (got ${bundle.chainKind} for ${bundle.chain})`
|
|
4692
|
+
);
|
|
4693
|
+
}
|
|
4694
|
+
const { to, data, chainId } = decodeEvmSettlementTx(bundle);
|
|
4695
|
+
const chain = defineChain3({
|
|
4696
|
+
id: chainId,
|
|
4697
|
+
name: bundle.chain,
|
|
4698
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
4699
|
+
rpcUrls: { default: { http: [params.rpcUrl] } }
|
|
4700
|
+
});
|
|
4701
|
+
const publicClient = createPublicClient3({ chain, transport: http3(params.rpcUrl) });
|
|
4702
|
+
const [nonce, gasPrice, gas] = await Promise.all([
|
|
4703
|
+
publicClient.getTransactionCount({
|
|
4704
|
+
address: params.account.address,
|
|
4705
|
+
blockTag: "pending"
|
|
4706
|
+
}),
|
|
4707
|
+
publicClient.getGasPrice(),
|
|
4708
|
+
publicClient.estimateGas({ account: params.account.address, to, data })
|
|
4709
|
+
]);
|
|
4710
|
+
const signed = await params.account.signTransaction({
|
|
4711
|
+
type: "legacy",
|
|
4712
|
+
chainId,
|
|
4713
|
+
nonce,
|
|
4714
|
+
gasPrice,
|
|
4715
|
+
gas,
|
|
4716
|
+
to,
|
|
4717
|
+
value: 0n,
|
|
4718
|
+
data
|
|
4719
|
+
});
|
|
4720
|
+
const txHash = await publicClient.sendRawTransaction({
|
|
4721
|
+
serializedTransaction: signed
|
|
4722
|
+
});
|
|
4723
|
+
try {
|
|
4724
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
4725
|
+
hash: txHash,
|
|
4726
|
+
timeout: params.timeoutMs ?? 6e4
|
|
4727
|
+
});
|
|
4728
|
+
return { txHash, status: receipt.status };
|
|
4729
|
+
} catch {
|
|
4730
|
+
return { txHash };
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4564
4734
|
// src/ToonClient.ts
|
|
4565
4735
|
var ToonClient = class {
|
|
4566
4736
|
config;
|
|
@@ -5277,6 +5447,38 @@ var ToonClient = class {
|
|
|
5277
5447
|
this.channelManager.setChannelSettled(channelId, nowSec);
|
|
5278
5448
|
return { channelId, ...r.txHash ? { txHash: r.txHash } : {} };
|
|
5279
5449
|
}
|
|
5450
|
+
/**
|
|
5451
|
+
* Submit a receive-side swap settlement bundle on-chain (toon-client#352).
|
|
5452
|
+
* The bundle comes from the sdk's `buildSettlementTx` over persisted,
|
|
5453
|
+
* verified chain-B claims (`buildSwapSettlements`); this signs it with the
|
|
5454
|
+
* client's EVM account (the claim recipient) and broadcasts it.
|
|
5455
|
+
*
|
|
5456
|
+
* Env-gated seam: EVM only, and only when `chainRpcUrls[bundle.chain]` is
|
|
5457
|
+
* configured — otherwise this throws a clear config error and callers
|
|
5458
|
+
* surface a built-not-submitted result. Solana submission and the Mina
|
|
5459
|
+
* receive-side co-sign path are explicit follow-ups (see
|
|
5460
|
+
* swap/settle-received-claims.ts module doc).
|
|
5461
|
+
*/
|
|
5462
|
+
async settleSwapBundle(bundle) {
|
|
5463
|
+
if (bundle.chainKind !== "evm") {
|
|
5464
|
+
throw new Error(
|
|
5465
|
+
`Swap settlement submission for ${bundle.chainKind} (${bundle.chain}) is not wired yet \u2014 EVM only today.`
|
|
5466
|
+
);
|
|
5467
|
+
}
|
|
5468
|
+
const rpcUrl = this.config.chainRpcUrls?.[bundle.chain];
|
|
5469
|
+
if (!rpcUrl) {
|
|
5470
|
+
throw new Error(
|
|
5471
|
+
`No RPC URL configured for chain "${bundle.chain}" \u2014 add it to chainRpcUrls to enable swap settlement submission.`
|
|
5472
|
+
);
|
|
5473
|
+
}
|
|
5474
|
+
if (!this.evmSigner) {
|
|
5475
|
+
throw new Error("EVM signer not configured (no evmPrivateKey/mnemonic).");
|
|
5476
|
+
}
|
|
5477
|
+
return submitEvmSettlement(bundle, {
|
|
5478
|
+
rpcUrl,
|
|
5479
|
+
account: this.evmSigner.account
|
|
5480
|
+
});
|
|
5481
|
+
}
|
|
5280
5482
|
/** Where a tracked channel sits in the withdraw journey. */
|
|
5281
5483
|
getChannelCloseState(channelId) {
|
|
5282
5484
|
if (!this.channelManager) throw new Error("ChannelManager not initialized");
|
|
@@ -5875,6 +6077,238 @@ var HttpConnectorAdmin = class {
|
|
|
5875
6077
|
}
|
|
5876
6078
|
};
|
|
5877
6079
|
|
|
6080
|
+
// src/channel/ReceivedClaimStore.ts
|
|
6081
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync } from "fs";
|
|
6082
|
+
import { dirname } from "path";
|
|
6083
|
+
function key(chain, channelId) {
|
|
6084
|
+
return `${chain}|${channelId}`;
|
|
6085
|
+
}
|
|
6086
|
+
var JsonFileReceivedClaimStore = class {
|
|
6087
|
+
filePath;
|
|
6088
|
+
constructor(filePath) {
|
|
6089
|
+
this.filePath = filePath;
|
|
6090
|
+
}
|
|
6091
|
+
save(entry) {
|
|
6092
|
+
const data = this.readFile();
|
|
6093
|
+
data[key(entry.chain, entry.channelId)] = {
|
|
6094
|
+
chain: entry.chain,
|
|
6095
|
+
channelId: entry.channelId,
|
|
6096
|
+
nonce: entry.nonce.toString(),
|
|
6097
|
+
cumulativeAmount: entry.cumulativeAmount.toString(),
|
|
6098
|
+
recipient: entry.recipient,
|
|
6099
|
+
swapSignerAddress: entry.swapSignerAddress,
|
|
6100
|
+
claimBytes: Buffer.from(entry.claimBytes).toString("base64"),
|
|
6101
|
+
...entry.claimId !== void 0 ? { claimId: entry.claimId } : {},
|
|
6102
|
+
pair: entry.pair,
|
|
6103
|
+
receivedAt: entry.receivedAt,
|
|
6104
|
+
updatedAt: entry.updatedAt,
|
|
6105
|
+
...entry.settledAt !== void 0 ? { settledAt: entry.settledAt } : {},
|
|
6106
|
+
...entry.settledNonce !== void 0 ? { settledNonce: entry.settledNonce.toString() } : {},
|
|
6107
|
+
...entry.settleTxHash !== void 0 ? { settleTxHash: entry.settleTxHash } : {}
|
|
6108
|
+
};
|
|
6109
|
+
this.writeFile(data);
|
|
6110
|
+
}
|
|
6111
|
+
load(chain, channelId) {
|
|
6112
|
+
const entry = this.readFile()[key(chain, channelId)];
|
|
6113
|
+
return entry ? fromJson(entry) : void 0;
|
|
6114
|
+
}
|
|
6115
|
+
list() {
|
|
6116
|
+
return Object.values(this.readFile()).map(fromJson);
|
|
6117
|
+
}
|
|
6118
|
+
delete(chain, channelId) {
|
|
6119
|
+
const data = this.readFile();
|
|
6120
|
+
const { [key(chain, channelId)]: _, ...rest } = data;
|
|
6121
|
+
this.writeFile(rest);
|
|
6122
|
+
}
|
|
6123
|
+
readFile() {
|
|
6124
|
+
if (!existsSync2(this.filePath)) {
|
|
6125
|
+
return {};
|
|
6126
|
+
}
|
|
6127
|
+
const raw = readFileSync2(this.filePath, "utf-8");
|
|
6128
|
+
return JSON.parse(raw);
|
|
6129
|
+
}
|
|
6130
|
+
writeFile(data) {
|
|
6131
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
6132
|
+
writeFileSync2(this.filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
6133
|
+
}
|
|
6134
|
+
};
|
|
6135
|
+
var InMemoryReceivedClaimStore = class {
|
|
6136
|
+
entries = /* @__PURE__ */ new Map();
|
|
6137
|
+
save(entry) {
|
|
6138
|
+
this.entries.set(key(entry.chain, entry.channelId), { ...entry });
|
|
6139
|
+
}
|
|
6140
|
+
load(chain, channelId) {
|
|
6141
|
+
const entry = this.entries.get(key(chain, channelId));
|
|
6142
|
+
return entry ? { ...entry } : void 0;
|
|
6143
|
+
}
|
|
6144
|
+
list() {
|
|
6145
|
+
return [...this.entries.values()].map((e) => ({ ...e }));
|
|
6146
|
+
}
|
|
6147
|
+
delete(chain, channelId) {
|
|
6148
|
+
this.entries.delete(key(chain, channelId));
|
|
6149
|
+
}
|
|
6150
|
+
};
|
|
6151
|
+
function fromJson(entry) {
|
|
6152
|
+
return {
|
|
6153
|
+
chain: entry.chain,
|
|
6154
|
+
channelId: entry.channelId,
|
|
6155
|
+
nonce: BigInt(entry.nonce),
|
|
6156
|
+
cumulativeAmount: BigInt(entry.cumulativeAmount),
|
|
6157
|
+
recipient: entry.recipient,
|
|
6158
|
+
swapSignerAddress: entry.swapSignerAddress,
|
|
6159
|
+
claimBytes: new Uint8Array(Buffer.from(entry.claimBytes, "base64")),
|
|
6160
|
+
...entry.claimId !== void 0 ? { claimId: entry.claimId } : {},
|
|
6161
|
+
pair: entry.pair,
|
|
6162
|
+
receivedAt: entry.receivedAt,
|
|
6163
|
+
updatedAt: entry.updatedAt,
|
|
6164
|
+
...entry.settledAt !== void 0 ? { settledAt: entry.settledAt } : {},
|
|
6165
|
+
...entry.settledNonce !== void 0 ? { settledNonce: BigInt(entry.settledNonce) } : {},
|
|
6166
|
+
...entry.settleTxHash !== void 0 ? { settleTxHash: entry.settleTxHash } : {}
|
|
6167
|
+
};
|
|
6168
|
+
}
|
|
6169
|
+
|
|
6170
|
+
// src/swap/received-claims.ts
|
|
6171
|
+
import {
|
|
6172
|
+
verifyAccumulatedClaim
|
|
6173
|
+
} from "@toon-protocol/sdk";
|
|
6174
|
+
function hasSettlementMetadata(claim) {
|
|
6175
|
+
return claim.channelId !== void 0 && claim.nonce !== void 0 && claim.cumulativeAmount !== void 0 && claim.recipient !== void 0 && claim.swapSignerAddress !== void 0;
|
|
6176
|
+
}
|
|
6177
|
+
function sameAddress(chain, a, b) {
|
|
6178
|
+
return chain.startsWith("evm") ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
6179
|
+
}
|
|
6180
|
+
function signatureRejectionCode(reason) {
|
|
6181
|
+
if (reason.startsWith("SIGNER_MISMATCH")) return "SIGNER_MISMATCH";
|
|
6182
|
+
if (reason.startsWith("MINA_VERIFICATION_UNSUPPORTED"))
|
|
6183
|
+
return "MINA_VERIFICATION_UNSUPPORTED";
|
|
6184
|
+
if (reason.startsWith("UNSUPPORTED_CHAIN")) return "UNSUPPORTED_CHAIN";
|
|
6185
|
+
return "SIGNATURE_INVALID";
|
|
6186
|
+
}
|
|
6187
|
+
function ingestReceivedClaims(params) {
|
|
6188
|
+
const now = params.now ?? Date.now;
|
|
6189
|
+
const verified = [];
|
|
6190
|
+
const rejected = [];
|
|
6191
|
+
const legacy = [];
|
|
6192
|
+
let valueReceived = 0n;
|
|
6193
|
+
const watermarks = /* @__PURE__ */ new Map();
|
|
6194
|
+
const reject = (claim, code, message) => {
|
|
6195
|
+
rejected.push({ claim, code, message });
|
|
6196
|
+
};
|
|
6197
|
+
for (const claim of params.claims) {
|
|
6198
|
+
if (!hasSettlementMetadata(claim)) {
|
|
6199
|
+
legacy.push(claim);
|
|
6200
|
+
continue;
|
|
6201
|
+
}
|
|
6202
|
+
const chain = claim.pair.to.chain;
|
|
6203
|
+
if (chain !== params.expectedChain) {
|
|
6204
|
+
reject(
|
|
6205
|
+
claim,
|
|
6206
|
+
"CHAIN_MISMATCH",
|
|
6207
|
+
`claim settles on "${chain}", session expected "${params.expectedChain}"`
|
|
6208
|
+
);
|
|
6209
|
+
continue;
|
|
6210
|
+
}
|
|
6211
|
+
if (!sameAddress(chain, claim.recipient, params.chainRecipient)) {
|
|
6212
|
+
reject(
|
|
6213
|
+
claim,
|
|
6214
|
+
"RECIPIENT_MISMATCH",
|
|
6215
|
+
`claim recipient "${claim.recipient}" is not the session chainRecipient "${params.chainRecipient}"`
|
|
6216
|
+
);
|
|
6217
|
+
continue;
|
|
6218
|
+
}
|
|
6219
|
+
if (params.expectedSignerAddress !== void 0 && !sameAddress(chain, claim.swapSignerAddress, params.expectedSignerAddress)) {
|
|
6220
|
+
reject(
|
|
6221
|
+
claim,
|
|
6222
|
+
"SWAP_SIGNER_MISMATCH",
|
|
6223
|
+
`claim swapSignerAddress "${claim.swapSignerAddress}" does not match the maker's advertised signer "${params.expectedSignerAddress}"`
|
|
6224
|
+
);
|
|
6225
|
+
continue;
|
|
6226
|
+
}
|
|
6227
|
+
const expectedSigner = params.expectedSignerAddress ?? claim.swapSignerAddress;
|
|
6228
|
+
const sig = verifyAccumulatedClaim(
|
|
6229
|
+
claim,
|
|
6230
|
+
{ address: expectedSigner },
|
|
6231
|
+
params.minaSignerClient
|
|
6232
|
+
);
|
|
6233
|
+
if (!sig.valid) {
|
|
6234
|
+
reject(claim, signatureRejectionCode(sig.reason), sig.reason);
|
|
6235
|
+
continue;
|
|
6236
|
+
}
|
|
6237
|
+
let nonce;
|
|
6238
|
+
let cumulativeAmount;
|
|
6239
|
+
try {
|
|
6240
|
+
nonce = BigInt(claim.nonce);
|
|
6241
|
+
cumulativeAmount = BigInt(claim.cumulativeAmount);
|
|
6242
|
+
} catch {
|
|
6243
|
+
reject(
|
|
6244
|
+
claim,
|
|
6245
|
+
"MALFORMED_METADATA",
|
|
6246
|
+
`nonce "${claim.nonce}" / cumulativeAmount "${claim.cumulativeAmount}" are not decimal integers`
|
|
6247
|
+
);
|
|
6248
|
+
continue;
|
|
6249
|
+
}
|
|
6250
|
+
const wmKey = `${chain}|${claim.channelId}`;
|
|
6251
|
+
const watermark = watermarks.get(wmKey) ?? params.store.load(chain, claim.channelId);
|
|
6252
|
+
if (watermark) {
|
|
6253
|
+
if (!sameAddress(chain, watermark.swapSignerAddress, expectedSigner)) {
|
|
6254
|
+
reject(
|
|
6255
|
+
claim,
|
|
6256
|
+
"SWAP_SIGNER_MISMATCH",
|
|
6257
|
+
`channel ${claim.channelId} watermark was signed by "${watermark.swapSignerAddress}"; a claim signed by "${expectedSigner}" may not rotate it`
|
|
6258
|
+
);
|
|
6259
|
+
continue;
|
|
6260
|
+
}
|
|
6261
|
+
if (nonce <= watermark.nonce) {
|
|
6262
|
+
reject(
|
|
6263
|
+
claim,
|
|
6264
|
+
"NON_MONOTONIC_NONCE",
|
|
6265
|
+
`nonce ${nonce} does not advance the persisted watermark nonce ${watermark.nonce} for channel ${claim.channelId}`
|
|
6266
|
+
);
|
|
6267
|
+
continue;
|
|
6268
|
+
}
|
|
6269
|
+
if (cumulativeAmount <= watermark.cumulativeAmount) {
|
|
6270
|
+
reject(
|
|
6271
|
+
claim,
|
|
6272
|
+
"NON_MONOTONIC_CUMULATIVE",
|
|
6273
|
+
`cumulativeAmount ${cumulativeAmount} does not advance the persisted watermark ${watermark.cumulativeAmount} for channel ${claim.channelId}`
|
|
6274
|
+
);
|
|
6275
|
+
continue;
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
const advance = cumulativeAmount - (watermark?.cumulativeAmount ?? 0n);
|
|
6279
|
+
if (advance < claim.targetAmount) {
|
|
6280
|
+
reject(
|
|
6281
|
+
claim,
|
|
6282
|
+
"CUMULATIVE_SHORTFALL",
|
|
6283
|
+
`cumulative advance ${advance} is less than the packet's targetAmount ${claim.targetAmount} for channel ${claim.channelId}`
|
|
6284
|
+
);
|
|
6285
|
+
continue;
|
|
6286
|
+
}
|
|
6287
|
+
const entry = {
|
|
6288
|
+
chain,
|
|
6289
|
+
channelId: claim.channelId,
|
|
6290
|
+
nonce,
|
|
6291
|
+
cumulativeAmount,
|
|
6292
|
+
recipient: claim.recipient,
|
|
6293
|
+
swapSignerAddress: expectedSigner,
|
|
6294
|
+
claimBytes: claim.claimBytes,
|
|
6295
|
+
...claim.claimId !== void 0 ? { claimId: claim.claimId } : {},
|
|
6296
|
+
pair: claim.pair,
|
|
6297
|
+
receivedAt: claim.receivedAt,
|
|
6298
|
+
updatedAt: now(),
|
|
6299
|
+
// Settlement bookkeeping survives watermark advances.
|
|
6300
|
+
...watermark?.settledAt !== void 0 ? { settledAt: watermark.settledAt } : {},
|
|
6301
|
+
...watermark?.settledNonce !== void 0 ? { settledNonce: watermark.settledNonce } : {},
|
|
6302
|
+
...watermark?.settleTxHash !== void 0 ? { settleTxHash: watermark.settleTxHash } : {}
|
|
6303
|
+
};
|
|
6304
|
+
params.store.save(entry);
|
|
6305
|
+
watermarks.set(wmKey, entry);
|
|
6306
|
+
verified.push({ claim, watermarkAdvance: advance });
|
|
6307
|
+
valueReceived += advance;
|
|
6308
|
+
}
|
|
6309
|
+
return { verified, rejected, legacy, valueReceived };
|
|
6310
|
+
}
|
|
6311
|
+
|
|
5878
6312
|
// src/faucet.ts
|
|
5879
6313
|
function defaultFaucetTimeout(chain) {
|
|
5880
6314
|
return chain === "mina" ? 12e4 : 3e4;
|
|
@@ -6854,7 +7288,7 @@ import {
|
|
|
6854
7288
|
createDecipheriv,
|
|
6855
7289
|
randomBytes as randomBytes2
|
|
6856
7290
|
} from "crypto";
|
|
6857
|
-
import { writeFileSync as
|
|
7291
|
+
import { writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
|
|
6858
7292
|
var SCRYPT_N = 2 ** 17;
|
|
6859
7293
|
var SCRYPT_R = 8;
|
|
6860
7294
|
var SCRYPT_P = 1;
|
|
@@ -6881,14 +7315,14 @@ function encryptMnemonic2(mnemonic, password) {
|
|
|
6881
7315
|
}
|
|
6882
7316
|
const salt = randomBytes2(SALT_LEN);
|
|
6883
7317
|
const iv = randomBytes2(IV_LEN);
|
|
6884
|
-
const
|
|
7318
|
+
const key2 = scryptSync(password, salt, SCRYPT_KEY_LEN, {
|
|
6885
7319
|
N: SCRYPT_N,
|
|
6886
7320
|
r: SCRYPT_R,
|
|
6887
7321
|
p: SCRYPT_P,
|
|
6888
7322
|
maxmem: SCRYPT_MAXMEM
|
|
6889
7323
|
});
|
|
6890
7324
|
try {
|
|
6891
|
-
const cipher = createCipheriv("aes-256-gcm",
|
|
7325
|
+
const cipher = createCipheriv("aes-256-gcm", key2, iv, {
|
|
6892
7326
|
authTagLength: AUTH_TAG_LEN
|
|
6893
7327
|
});
|
|
6894
7328
|
const ciphertext = Buffer.concat([
|
|
@@ -6904,7 +7338,7 @@ function encryptMnemonic2(mnemonic, password) {
|
|
|
6904
7338
|
version: 1
|
|
6905
7339
|
};
|
|
6906
7340
|
} finally {
|
|
6907
|
-
|
|
7341
|
+
key2.fill(0);
|
|
6908
7342
|
}
|
|
6909
7343
|
}
|
|
6910
7344
|
function decryptMnemonic2(encrypted, password) {
|
|
@@ -6919,14 +7353,14 @@ function decryptMnemonic2(encrypted, password) {
|
|
|
6919
7353
|
const iv = Buffer.from(encrypted.iv, "base64");
|
|
6920
7354
|
const ciphertext = Buffer.from(encrypted.ciphertext, "base64");
|
|
6921
7355
|
const tag = Buffer.from(encrypted.tag, "base64");
|
|
6922
|
-
const
|
|
7356
|
+
const key2 = scryptSync(password, salt, SCRYPT_KEY_LEN, {
|
|
6923
7357
|
N: SCRYPT_N,
|
|
6924
7358
|
r: SCRYPT_R,
|
|
6925
7359
|
p: SCRYPT_P,
|
|
6926
7360
|
maxmem: SCRYPT_MAXMEM
|
|
6927
7361
|
});
|
|
6928
7362
|
try {
|
|
6929
|
-
const decipher = createDecipheriv("aes-256-gcm",
|
|
7363
|
+
const decipher = createDecipheriv("aes-256-gcm", key2, iv, {
|
|
6930
7364
|
authTagLength: AUTH_TAG_LEN
|
|
6931
7365
|
});
|
|
6932
7366
|
decipher.setAuthTag(tag);
|
|
@@ -6942,7 +7376,7 @@ function decryptMnemonic2(encrypted, password) {
|
|
|
6942
7376
|
);
|
|
6943
7377
|
}
|
|
6944
7378
|
} finally {
|
|
6945
|
-
|
|
7379
|
+
key2.fill(0);
|
|
6946
7380
|
}
|
|
6947
7381
|
}
|
|
6948
7382
|
function generateKeystore(path, password) {
|
|
@@ -6965,7 +7399,7 @@ function importKeystore(path, mnemonic, password) {
|
|
|
6965
7399
|
}
|
|
6966
7400
|
function loadKeystore(path, password) {
|
|
6967
7401
|
assertNode();
|
|
6968
|
-
const raw =
|
|
7402
|
+
const raw = readFileSync3(path, "utf8");
|
|
6969
7403
|
let parsed;
|
|
6970
7404
|
try {
|
|
6971
7405
|
parsed = JSON.parse(raw);
|
|
@@ -6976,7 +7410,7 @@ function loadKeystore(path, password) {
|
|
|
6976
7410
|
}
|
|
6977
7411
|
function writeKeystoreFile(path, keystore) {
|
|
6978
7412
|
assertNode();
|
|
6979
|
-
|
|
7413
|
+
writeFileSync3(path, JSON.stringify(keystore, null, 2), {
|
|
6980
7414
|
encoding: "utf8",
|
|
6981
7415
|
mode: 384
|
|
6982
7416
|
});
|
|
@@ -6998,6 +7432,8 @@ export {
|
|
|
6998
7432
|
ILP_CLAIM_HEADER,
|
|
6999
7433
|
ILP_CLAIM_WRAPPED_HEADER,
|
|
7000
7434
|
ILP_PEER_ID_HEADER,
|
|
7435
|
+
InMemoryReceivedClaimStore,
|
|
7436
|
+
JsonFileReceivedClaimStore,
|
|
7001
7437
|
KeyManager,
|
|
7002
7438
|
KindRegistry,
|
|
7003
7439
|
MIME_A2UI,
|
|
@@ -7021,8 +7457,10 @@ export {
|
|
|
7021
7457
|
buildRendererEventTemplate,
|
|
7022
7458
|
buildSettlementInfo,
|
|
7023
7459
|
buildStoreWriteEnvelope,
|
|
7460
|
+
buildSwapSettlements,
|
|
7024
7461
|
buildUiCoordinate,
|
|
7025
7462
|
classifyIntent,
|
|
7463
|
+
decodeEvmSettlementTx,
|
|
7026
7464
|
decryptMnemonic2 as decryptMnemonic,
|
|
7027
7465
|
defaultFaucetTimeout,
|
|
7028
7466
|
deriveFromNsec,
|
|
@@ -7030,6 +7468,7 @@ export {
|
|
|
7030
7468
|
deriveNostrKeyFromMnemonic,
|
|
7031
7469
|
deterministicGenerator,
|
|
7032
7470
|
encryptMnemonic2 as encryptMnemonic,
|
|
7471
|
+
entryToAccumulatedClaim,
|
|
7033
7472
|
extractArweaveTxId,
|
|
7034
7473
|
extractUiResource,
|
|
7035
7474
|
fulfillmentMatchesCondition,
|
|
@@ -7040,8 +7479,10 @@ export {
|
|
|
7040
7479
|
getNetworkStatus,
|
|
7041
7480
|
getUiCoordinate,
|
|
7042
7481
|
guardedRenderDispatch,
|
|
7482
|
+
hasSettlementMetadata,
|
|
7043
7483
|
httpEndpointToBtpUrl,
|
|
7044
7484
|
importKeystore,
|
|
7485
|
+
ingestReceivedClaims,
|
|
7045
7486
|
isInsufficientGasError,
|
|
7046
7487
|
isPrfSupported,
|
|
7047
7488
|
isTrustDowngrade,
|
|
@@ -7049,6 +7490,7 @@ export {
|
|
|
7049
7490
|
loadKeystore,
|
|
7050
7491
|
mintExecutionCondition,
|
|
7051
7492
|
parseBackupPayload,
|
|
7493
|
+
parseEvmChainId2 as parseEvmChainId,
|
|
7052
7494
|
parseFulfillHttp,
|
|
7053
7495
|
parseFulfillHttpBytes,
|
|
7054
7496
|
parseHttpResponse,
|
|
@@ -7074,6 +7516,7 @@ export {
|
|
|
7074
7516
|
selectIlpTransport,
|
|
7075
7517
|
selectLatestAddressable,
|
|
7076
7518
|
serializeHttpRequest,
|
|
7519
|
+
submitEvmSettlement,
|
|
7077
7520
|
validateConfig,
|
|
7078
7521
|
validateMnemonic,
|
|
7079
7522
|
verifyRendererTrust,
|