@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.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 key = I.slice(0, 32);
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(key, 1);
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
- key = I.slice(0, 32);
177
+ key2 = I.slice(0, 32);
178
178
  chainCode = I.slice(32);
179
179
  }
180
- const publicKeyBytes = ed255194.getPublicKey(key);
180
+ const publicKeyBytes = ed255194.getPublicKey(key2);
181
181
  const keypair = new Uint8Array(64);
182
- keypair.set(key, 0);
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 ?? new Date(Date.now() + (params.timeout ?? 3e4)),
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 ?? new Date(Date.now() + requestTimeout),
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 http3 = peer.httpEndpoint?.trim() || void 0;
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 (http3 && canUpgrade) return { kind: "http-upgradable", httpEndpoint: http3 };
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 (http3) return { kind: "http", httpEndpoint: http3, canUpgrade };
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 key of ix.keys) {
1969
- const existing = accountMap.get(key.pubkey);
1972
+ for (const key2 of ix.keys) {
1973
+ const existing = accountMap.get(key2.pubkey);
1970
1974
  if (existing) {
1971
- existing.isSigner = existing.isSigner || key.isSigner;
1972
- existing.isWritable = existing.isWritable || key.isWritable;
1975
+ existing.isSigner = existing.isSigner || key2.isSigner;
1976
+ existing.isWritable = existing.isWritable || key2.isWritable;
1973
1977
  } else {
1974
- accountMap.set(key.pubkey, { ...key });
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 http3 = parseFulfillHttp(base64Data);
4271
- if (!http3.isHttp) {
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 (http3.status < 200 || http3.status >= 300) {
4281
- const detail = http3.body ? ` - ${http3.body}` : "";
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 ${http3.status} ${http3.statusText}`.trimEnd() + detail
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(http3.body);
4292
+ parsed = JSON.parse(http4.body);
4289
4293
  } catch {
4290
4294
  throw new Error(
4291
- `Blob upload response body was not valid JSON: "${http3.body}"`
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: "${http3.body}"`
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 writeFileSync2, readFileSync as readFileSync2 } from "fs";
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 key = scryptSync(password, salt, SCRYPT_KEY_LEN, {
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", key, iv, {
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
- key.fill(0);
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 key = scryptSync(password, salt, SCRYPT_KEY_LEN, {
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", key, iv, {
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
- key.fill(0);
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 = readFileSync2(path, "utf8");
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
- writeFileSync2(path, JSON.stringify(keystore, null, 2), {
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,