@toon-protocol/client 0.17.0 → 0.19.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)) {
@@ -3405,7 +3409,14 @@ async function buildMinaPaymentChannelProof(params) {
3405
3409
  }
3406
3410
 
3407
3411
  // src/channel/mina-deposit.ts
3408
- var DEPOSIT_TOTAL_STATE_INDEX = 4;
3412
+ var CHANNEL_STATE_INDEX = {
3413
+ channelHash: 0,
3414
+ balanceCommitment: 1,
3415
+ nonceField: 2,
3416
+ channelState: 3,
3417
+ depositTotal: 4
3418
+ };
3419
+ var DEPOSIT_TOTAL_STATE_INDEX = CHANNEL_STATE_INDEX.depositTotal;
3409
3420
  async function readMinaDepositTotal(graphqlUrl, zkAppAddress, fetchImpl = fetch) {
3410
3421
  const query = "query($pk:String!){account(publicKey:$pk){zkappState}}";
3411
3422
  const res = await fetchImpl(graphqlUrl, {
@@ -3430,6 +3441,42 @@ async function readMinaDepositTotal(graphqlUrl, zkAppAddress, fetchImpl = fetch)
3430
3441
  }
3431
3442
  return BigInt(state[DEPOSIT_TOTAL_STATE_INDEX]);
3432
3443
  }
3444
+ var MINA_CHANNEL_STATE = {
3445
+ UNINITIALIZED: 0,
3446
+ OPEN: 1,
3447
+ CLOSING: 2,
3448
+ SETTLED: 3
3449
+ };
3450
+ async function readMinaChannelState(graphqlUrl, zkAppAddress, fetchImpl = fetch) {
3451
+ const query = "query($pk:String!){account(publicKey:$pk){zkappState}}";
3452
+ const res = await fetchImpl(graphqlUrl, {
3453
+ method: "POST",
3454
+ headers: { "content-type": "application/json" },
3455
+ body: JSON.stringify({ query, variables: { pk: zkAppAddress } })
3456
+ });
3457
+ if (!res.ok) {
3458
+ throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);
3459
+ }
3460
+ const json = await res.json();
3461
+ if (json.errors && json.errors.length > 0) {
3462
+ throw new Error(
3463
+ `Mina GraphQL error: ${json.errors[0]?.message ?? "unknown"}`
3464
+ );
3465
+ }
3466
+ const state = json.data?.account?.zkappState;
3467
+ if (!state || state.length <= DEPOSIT_TOTAL_STATE_INDEX) {
3468
+ throw new Error(
3469
+ `Mina zkApp ${zkAppAddress} has no readable zkappState (account not found or not a zkApp)`
3470
+ );
3471
+ }
3472
+ return {
3473
+ channelHash: String(state[CHANNEL_STATE_INDEX.channelHash]),
3474
+ balanceCommitment: String(state[CHANNEL_STATE_INDEX.balanceCommitment]),
3475
+ nonceField: BigInt(state[CHANNEL_STATE_INDEX.nonceField]),
3476
+ channelState: Number(state[CHANNEL_STATE_INDEX.channelState]),
3477
+ depositTotal: BigInt(state[CHANNEL_STATE_INDEX.depositTotal])
3478
+ };
3479
+ }
3433
3480
 
3434
3481
  // src/signing/mina-signer.ts
3435
3482
  var DEFAULT_MINA_TOKEN_ID = "MINA";
@@ -4267,8 +4314,8 @@ async function requestBlobStorage(client, secretKey, params) {
4267
4314
  };
4268
4315
  }
4269
4316
  function extractArweaveTxId(base64Data) {
4270
- const http3 = parseFulfillHttp(base64Data);
4271
- if (!http3.isHttp) {
4317
+ const http4 = parseFulfillHttp(base64Data);
4318
+ if (!http4.isHttp) {
4272
4319
  const legacy = decodeUtf8(fromBase64(base64Data));
4273
4320
  if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {
4274
4321
  throw new Error(
@@ -4277,18 +4324,18 @@ function extractArweaveTxId(base64Data) {
4277
4324
  }
4278
4325
  return legacy;
4279
4326
  }
4280
- if (http3.status < 200 || http3.status >= 300) {
4281
- const detail = http3.body ? ` - ${http3.body}` : "";
4327
+ if (http4.status < 200 || http4.status >= 300) {
4328
+ const detail = http4.body ? ` - ${http4.body}` : "";
4282
4329
  throw new Error(
4283
- `Blob upload failed: DVM returned HTTP ${http3.status} ${http3.statusText}`.trimEnd() + detail
4330
+ `Blob upload failed: DVM returned HTTP ${http4.status} ${http4.statusText}`.trimEnd() + detail
4284
4331
  );
4285
4332
  }
4286
4333
  let parsed;
4287
4334
  try {
4288
- parsed = JSON.parse(http3.body);
4335
+ parsed = JSON.parse(http4.body);
4289
4336
  } catch {
4290
4337
  throw new Error(
4291
- `Blob upload response body was not valid JSON: "${http3.body}"`
4338
+ `Blob upload response body was not valid JSON: "${http4.body}"`
4292
4339
  );
4293
4340
  }
4294
4341
  const body = parsed;
@@ -4306,7 +4353,7 @@ function extractArweaveTxId(base64Data) {
4306
4353
  }
4307
4354
  }
4308
4355
  throw new Error(
4309
- `Blob upload response did not contain a valid Arweave tx ID: "${http3.body}"`
4356
+ `Blob upload response did not contain a valid Arweave tx ID: "${http4.body}"`
4310
4357
  );
4311
4358
  }
4312
4359
 
@@ -4561,7 +4608,431 @@ function parseHttpResponse(bytes) {
4561
4608
  );
4562
4609
  }
4563
4610
 
4611
+ // src/swap/settle-received-claims.ts
4612
+ import {
4613
+ buildSettlementTx,
4614
+ SettlementTxError
4615
+ } from "@toon-protocol/sdk";
4616
+ import {
4617
+ createPublicClient as createPublicClient3,
4618
+ defineChain as defineChain3,
4619
+ fromRlp,
4620
+ http as http3
4621
+ } from "viem";
4622
+ function entryToAccumulatedClaim(entry) {
4623
+ return {
4624
+ packetIndex: 0,
4625
+ sourceAmount: 0n,
4626
+ targetAmount: entry.cumulativeAmount,
4627
+ claimBytes: entry.claimBytes,
4628
+ swapEphemeralPubkey: "",
4629
+ ...entry.claimId !== void 0 ? { claimId: entry.claimId } : {},
4630
+ pair: entry.pair,
4631
+ receivedAt: entry.receivedAt,
4632
+ channelId: entry.channelId,
4633
+ nonce: entry.nonce.toString(),
4634
+ cumulativeAmount: entry.cumulativeAmount.toString(),
4635
+ recipient: entry.recipient,
4636
+ swapSignerAddress: entry.swapSignerAddress
4637
+ };
4638
+ }
4639
+ function parseEvmChainId2(chain) {
4640
+ const parts = chain.split(":");
4641
+ const raw = parts.length >= 3 ? parts[2] : parts[1];
4642
+ const chainId = Number.parseInt(raw ?? "", 10);
4643
+ return Number.isInteger(chainId) && chainId > 0 ? chainId : void 0;
4644
+ }
4645
+ function buildSwapSettlements(params) {
4646
+ return params.entries.map((entry) => {
4647
+ const base = { chain: entry.chain, channelId: entry.channelId };
4648
+ const signer = { address: entry.swapSignerAddress };
4649
+ const contract = params.tokenNetworks?.[entry.chain];
4650
+ if (entry.chain.startsWith("evm")) {
4651
+ const chainId = parseEvmChainId2(entry.chain);
4652
+ if (!contract || chainId === void 0) {
4653
+ return {
4654
+ ...base,
4655
+ error: {
4656
+ code: "MISSING_CHAIN_CONFIG",
4657
+ message: `EVM settlement for ${entry.chain} needs tokenNetworks["${entry.chain}"] (TokenNetwork contract) and a numeric chain id in the chain key.`
4658
+ }
4659
+ };
4660
+ }
4661
+ signer.contractAddress = contract;
4662
+ signer.chainId = chainId;
4663
+ } else if (entry.chain.startsWith("solana")) {
4664
+ if (!contract) {
4665
+ return {
4666
+ ...base,
4667
+ error: {
4668
+ code: "MISSING_CHAIN_CONFIG",
4669
+ message: `Solana settlement for ${entry.chain} needs tokenNetworks["${entry.chain}"] (programId).`
4670
+ }
4671
+ };
4672
+ }
4673
+ signer.programId = contract;
4674
+ }
4675
+ try {
4676
+ const result = buildSettlementTx({
4677
+ claims: [entryToAccumulatedClaim(entry)],
4678
+ signers: { [entry.chain]: signer },
4679
+ recipients: { [entry.chain]: entry.recipient },
4680
+ ...params.minaSignerClient ? { minaSignerClient: params.minaSignerClient } : {}
4681
+ });
4682
+ const firstRejected = result.rejected[0];
4683
+ if (firstRejected) {
4684
+ return {
4685
+ ...base,
4686
+ error: {
4687
+ code: firstRejected.reason,
4688
+ message: firstRejected.details ?? `stored claim for ${entry.chain}/${entry.channelId} failed settlement re-verification`
4689
+ }
4690
+ };
4691
+ }
4692
+ const bundle = result.bundles[0];
4693
+ if (!bundle) {
4694
+ return {
4695
+ ...base,
4696
+ error: {
4697
+ code: "NO_BUNDLE",
4698
+ message: `buildSettlementTx produced no bundle for ${entry.chain}/${entry.channelId}`
4699
+ }
4700
+ };
4701
+ }
4702
+ return { ...base, bundle };
4703
+ } catch (err) {
4704
+ const code = err instanceof SettlementTxError ? err.code : "BUILD_FAILED";
4705
+ return {
4706
+ ...base,
4707
+ error: {
4708
+ code,
4709
+ message: err instanceof Error ? err.message : String(err)
4710
+ }
4711
+ };
4712
+ }
4713
+ });
4714
+ }
4715
+ function decodeEvmSettlementTx(bundle) {
4716
+ const fields = fromRlp(bundle.unsignedTxBytes, "hex");
4717
+ if (!Array.isArray(fields) || fields.length !== 9) {
4718
+ throw new Error(
4719
+ `settlement bundle RLP is not a 9-field unsigned EVM tx (got ${Array.isArray(fields) ? fields.length : typeof fields})`
4720
+ );
4721
+ }
4722
+ const to = fields[3];
4723
+ const data = fields[5];
4724
+ const chainIdHex = fields[6];
4725
+ const chainId = Number.parseInt(chainIdHex === "0x" ? "0x0" : chainIdHex, 16);
4726
+ if (typeof to !== "string" || to.length !== 42) {
4727
+ throw new Error(`settlement tx "to" is not a 20-byte address: ${String(to)}`);
4728
+ }
4729
+ return { to, data: data === "0x" ? "0x" : data, chainId };
4730
+ }
4731
+ async function submitEvmSettlement(bundle, params) {
4732
+ if (bundle.chainKind !== "evm") {
4733
+ throw new Error(
4734
+ `submitEvmSettlement only submits evm bundles (got ${bundle.chainKind} for ${bundle.chain})`
4735
+ );
4736
+ }
4737
+ const { to, data, chainId } = decodeEvmSettlementTx(bundle);
4738
+ const chain = defineChain3({
4739
+ id: chainId,
4740
+ name: bundle.chain,
4741
+ nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
4742
+ rpcUrls: { default: { http: [params.rpcUrl] } }
4743
+ });
4744
+ const publicClient = createPublicClient3({ chain, transport: http3(params.rpcUrl) });
4745
+ const [nonce, gasPrice, gas] = await Promise.all([
4746
+ publicClient.getTransactionCount({
4747
+ address: params.account.address,
4748
+ blockTag: "pending"
4749
+ }),
4750
+ publicClient.getGasPrice(),
4751
+ publicClient.estimateGas({ account: params.account.address, to, data })
4752
+ ]);
4753
+ const signed = await params.account.signTransaction({
4754
+ type: "legacy",
4755
+ chainId,
4756
+ nonce,
4757
+ gasPrice,
4758
+ gas,
4759
+ to,
4760
+ value: 0n,
4761
+ data
4762
+ });
4763
+ const txHash = await publicClient.sendRawTransaction({
4764
+ serializedTransaction: signed
4765
+ });
4766
+ try {
4767
+ const receipt = await publicClient.waitForTransactionReceipt({
4768
+ hash: txHash,
4769
+ timeout: params.timeoutMs ?? 6e4
4770
+ });
4771
+ return { txHash, status: receipt.status };
4772
+ } catch {
4773
+ return { txHash };
4774
+ }
4775
+ }
4776
+
4777
+ // src/swap/mina-settlement.ts
4778
+ import { hexToMinaBase58PrivateKey as hexToMinaBase58PrivateKey4 } from "@toon-protocol/core";
4779
+ var MinaSettlementError = class extends Error {
4780
+ code;
4781
+ constructor(code, message) {
4782
+ super(`${code}: ${message}`);
4783
+ this.name = "MinaSettlementError";
4784
+ this.code = code;
4785
+ }
4786
+ };
4787
+ function parseProofSignature(proofJson) {
4788
+ const parsed = JSON.parse(proofJson);
4789
+ const r = parsed.signature?.r;
4790
+ const s = parsed.signature?.s;
4791
+ if (typeof r !== "string" || typeof s !== "string") {
4792
+ throw new Error("recipient co-signature proof did not carry string r/s");
4793
+ }
4794
+ return { r, s };
4795
+ }
4796
+ async function buildMinaCoSignedClaim(inputs) {
4797
+ const channelNonce = inputs.channelNonce ?? 0n;
4798
+ const salt = inputs.saltOverride ?? deriveMinaSalt(inputs.channelId, Number(inputs.nonce));
4799
+ if (inputs.cumulativeAmount > inputs.depositTotal) {
4800
+ throw new MinaSettlementError(
4801
+ "CUMULATIVE_EXCEEDS_DEPOSIT",
4802
+ `claim cumulativeAmount (${inputs.cumulativeAmount}) exceeds the channel depositTotal (${inputs.depositTotal}) \u2014 cannot conserve balances`
4803
+ );
4804
+ }
4805
+ const { Poseidon, PublicKey } = await loadMinaPaymentChannelBindings();
4806
+ const hashRecipientFirst = minaParticipantChannelHashField(
4807
+ Poseidon,
4808
+ PublicKey,
4809
+ inputs.recipient,
4810
+ inputs.swapSignerAddress,
4811
+ channelNonce
4812
+ ).toString();
4813
+ const hashMakerFirst = minaParticipantChannelHashField(
4814
+ Poseidon,
4815
+ PublicKey,
4816
+ inputs.swapSignerAddress,
4817
+ inputs.recipient,
4818
+ channelNonce
4819
+ ).toString();
4820
+ let participantA;
4821
+ let participantB;
4822
+ let recipientRole;
4823
+ if (hashRecipientFirst === inputs.onChainChannelHash) {
4824
+ participantA = inputs.recipient;
4825
+ participantB = inputs.swapSignerAddress;
4826
+ recipientRole = "A";
4827
+ } else if (hashMakerFirst === inputs.onChainChannelHash) {
4828
+ participantA = inputs.swapSignerAddress;
4829
+ participantB = inputs.recipient;
4830
+ recipientRole = "B";
4831
+ } else {
4832
+ throw new MinaSettlementError(
4833
+ "CHANNEL_HASH_MISMATCH",
4834
+ `neither ordering of recipient ${inputs.recipient} / maker ${inputs.swapSignerAddress} reproduces the on-chain channelHash ${inputs.onChainChannelHash} (channelNonce ${channelNonce})`
4835
+ );
4836
+ }
4837
+ const recipientBalance = inputs.cumulativeAmount;
4838
+ const makerBalance = inputs.depositTotal - inputs.cumulativeAmount;
4839
+ const balanceA = recipientRole === "A" ? recipientBalance : makerBalance;
4840
+ const balanceB = inputs.depositTotal - balanceA;
4841
+ const balanceCommitment = minaBalanceCommitment(
4842
+ Poseidon,
4843
+ balanceA,
4844
+ balanceB,
4845
+ salt
4846
+ ).toString();
4847
+ const recipientPrivateKeyBase58 = hexToMinaBase58PrivateKey4(
4848
+ inputs.recipientPrivateKey
4849
+ );
4850
+ const built = await buildMinaPaymentChannelProof({
4851
+ zkAppAddress: inputs.channelId,
4852
+ minaPrivateKeyBase58: recipientPrivateKeyBase58,
4853
+ signerPublicKey: inputs.recipient,
4854
+ balanceA,
4855
+ balanceB,
4856
+ salt,
4857
+ nonce: inputs.nonce,
4858
+ participantA,
4859
+ participantB,
4860
+ channelNonce,
4861
+ proofEncoding: "json"
4862
+ });
4863
+ if (built.balanceCommitment !== balanceCommitment) {
4864
+ throw new Error(
4865
+ `co-sign commitment drift: builder ${built.balanceCommitment} != ${balanceCommitment}`
4866
+ );
4867
+ }
4868
+ const recipientSignature = parseProofSignature(built.proof);
4869
+ const signatureA = recipientRole === "A" ? recipientSignature : inputs.makerSignature;
4870
+ const signatureB = recipientRole === "B" ? recipientSignature : inputs.makerSignature;
4871
+ return {
4872
+ channelId: inputs.channelId,
4873
+ balanceA,
4874
+ balanceB,
4875
+ salt,
4876
+ nonce: inputs.nonce,
4877
+ channelNonce,
4878
+ balanceCommitment,
4879
+ participantA,
4880
+ participantB,
4881
+ recipientRole,
4882
+ recipientSignature,
4883
+ ...signatureA ? { signatureA } : {},
4884
+ ...signatureB ? { signatureB } : {},
4885
+ makerSignatureMissing: inputs.makerSignature === void 0
4886
+ };
4887
+ }
4888
+ async function submitMinaSettlement(bundle, context) {
4889
+ if (bundle.chainKind !== "mina") {
4890
+ throw new MinaSettlementError(
4891
+ "NOT_MINA_BUNDLE",
4892
+ `submitMinaSettlement only settles mina bundles (got ${bundle.chainKind} for ${bundle.chain})`
4893
+ );
4894
+ }
4895
+ if (!context.graphqlUrl) {
4896
+ throw new MinaSettlementError(
4897
+ "NO_GRAPHQL_CONFIGURED",
4898
+ `no Mina graphqlUrl configured for "${bundle.chain}" \u2014 set minaChannel.graphqlUrl to enable receive-side settlement.`
4899
+ );
4900
+ }
4901
+ const graphqlUrl = context.graphqlUrl;
4902
+ const channelNonce = context.channelNonce ?? 0n;
4903
+ const nonce = BigInt(bundle.nonce);
4904
+ const cumulativeAmount = BigInt(bundle.cumulativeAmount);
4905
+ const read = context.reader ?? readMinaChannelState;
4906
+ const state = await read(graphqlUrl, bundle.channelId);
4907
+ if (state.channelState !== MINA_CHANNEL_STATE.OPEN) {
4908
+ throw new MinaSettlementError(
4909
+ "CHANNEL_NOT_OPEN",
4910
+ `channel ${bundle.channelId} is not OPEN (channelState=${state.channelState}); claimFromChannel only applies to an OPEN channel.`
4911
+ );
4912
+ }
4913
+ if (nonce <= state.nonceField) {
4914
+ throw new MinaSettlementError(
4915
+ "NONCE_NOT_ADVANCING",
4916
+ `claim nonce ${nonce} does not advance the on-chain nonceField ${state.nonceField} for ${bundle.channelId} (already claimed).`
4917
+ );
4918
+ }
4919
+ const claim = await buildMinaCoSignedClaim({
4920
+ channelId: bundle.channelId,
4921
+ nonce,
4922
+ cumulativeAmount,
4923
+ recipient: bundle.recipient,
4924
+ swapSignerAddress: bundle.swapSignerAddress,
4925
+ depositTotal: state.depositTotal,
4926
+ onChainChannelHash: state.channelHash,
4927
+ recipientPrivateKey: context.recipientPrivateKey,
4928
+ channelNonce,
4929
+ ...context.makerSignature ? { makerSignature: context.makerSignature } : {}
4930
+ });
4931
+ if (claim.makerSignatureMissing || !claim.signatureA || !claim.signatureB) {
4932
+ throw new MinaSettlementError(
4933
+ "MINA_MAKER_COSIGN_REQUIRED",
4934
+ `on-chain claimFromChannel is dual-party: it needs the maker's payment-channel-commitment signature over [commitment, nonce, channelHash] in addition to the recipient's co-signature. The swap-wire claim only carries the maker's balanceProofFieldsMina signature (a different message), so the maker must additionally deliver an on-chain-form co-signature. Recipient co-signature assembled and ready (${bundle.channelId} nonce ${nonce}).`
4935
+ );
4936
+ }
4937
+ const submitter = context.submitter ?? createO1jsMinaClaimSubmitter();
4938
+ const { txHash } = await submitter.claimFromChannel({
4939
+ graphqlUrl,
4940
+ channelId: bundle.channelId,
4941
+ balanceA: claim.balanceA,
4942
+ balanceB: claim.balanceB,
4943
+ salt: claim.salt,
4944
+ nonce: claim.nonce,
4945
+ participantA: claim.participantA,
4946
+ participantB: claim.participantB,
4947
+ channelNonce: claim.channelNonce,
4948
+ signatureA: claim.signatureA,
4949
+ signatureB: claim.signatureB,
4950
+ feePayerPrivateKey: context.feePayerPrivateKey ?? context.recipientPrivateKey,
4951
+ ...context.txFeeNanomina !== void 0 ? { txFeeNanomina: context.txFeeNanomina } : {}
4952
+ });
4953
+ return { txHash };
4954
+ }
4955
+ var DEFAULT_MINA_TX_FEE_NANOMINA = 100000000n;
4956
+ function createO1jsMinaClaimSubmitter() {
4957
+ return {
4958
+ async claimFromChannel(args) {
4959
+ try {
4960
+ const o1js = await import("o1js");
4961
+ const {
4962
+ Mina,
4963
+ PrivateKey,
4964
+ PublicKey,
4965
+ Field,
4966
+ Poseidon,
4967
+ Signature,
4968
+ fetchAccount
4969
+ } = o1js;
4970
+ const zkAppMod = await import("@toon-protocol/mina-zkapp");
4971
+ const PaymentChannel = zkAppMod.PaymentChannel;
4972
+ Mina.setActiveInstance(Mina.Network(args.graphqlUrl));
4973
+ const feePayerKey = PrivateKey.fromBase58(
4974
+ hexToMinaBase58PrivateKey4(args.feePayerPrivateKey)
4975
+ );
4976
+ const feePayerPub = feePayerKey.toPublicKey();
4977
+ await PaymentChannel.compile();
4978
+ const zkAppPub = PublicKey.fromBase58(args.channelId);
4979
+ await fetchAccount({ publicKey: zkAppPub });
4980
+ await fetchAccount({ publicKey: feePayerPub });
4981
+ const zkApp = new PaymentChannel(zkAppPub);
4982
+ const balA = Field(args.balanceA);
4983
+ const balB = Field(args.balanceB);
4984
+ const saltField = Field(args.salt);
4985
+ const newNonce = Field(args.nonce);
4986
+ const channelNonce = Field(args.channelNonce);
4987
+ const newBalanceCommitment = Poseidon.hash([balA, balB, saltField]);
4988
+ const sigA = Signature.fromJSON({
4989
+ r: args.signatureA.r,
4990
+ s: args.signatureA.s
4991
+ });
4992
+ const sigB = Signature.fromJSON({
4993
+ r: args.signatureB.r,
4994
+ s: args.signatureB.s
4995
+ });
4996
+ const partA = PublicKey.fromBase58(args.participantA);
4997
+ const partB = PublicKey.fromBase58(args.participantB);
4998
+ const fee = (args.txFeeNanomina ?? DEFAULT_MINA_TX_FEE_NANOMINA).toString();
4999
+ const txn = await Mina.transaction(
5000
+ { sender: feePayerPub, fee },
5001
+ async () => {
5002
+ await zkApp.claimFromChannel(
5003
+ balA,
5004
+ balB,
5005
+ saltField,
5006
+ sigA,
5007
+ sigB,
5008
+ partA,
5009
+ partB,
5010
+ channelNonce,
5011
+ newBalanceCommitment,
5012
+ newNonce
5013
+ );
5014
+ }
5015
+ );
5016
+ await txn.prove();
5017
+ const sent = await txn.sign([feePayerKey]).send();
5018
+ return { txHash: sent.hash ?? "" };
5019
+ } catch (err) {
5020
+ throw new MinaSettlementError(
5021
+ "PROVING_FAILED",
5022
+ err instanceof Error ? err.message : String(err)
5023
+ );
5024
+ }
5025
+ }
5026
+ };
5027
+ }
5028
+
4564
5029
  // src/ToonClient.ts
5030
+ function parseMakerMinaSignature(raw) {
5031
+ if (raw && typeof raw.r === "string" && raw.r.length > 0 && typeof raw.s === "string" && raw.s.length > 0) {
5032
+ return { r: raw.r, s: raw.s };
5033
+ }
5034
+ return void 0;
5035
+ }
4565
5036
  var ToonClient = class {
4566
5037
  config;
4567
5038
  state = null;
@@ -4846,34 +5317,11 @@ var ToonClient = class {
4846
5317
  const writeData = buildStoreWriteEnvelope(event, options?.proxyPath);
4847
5318
  const destination = options?.destination ?? this.config.destinationAddress;
4848
5319
  const transport = this.getClaimTransport();
4849
- let claimMessage;
4850
- if (options?.claim) {
4851
- claimMessage = this.buildClaimMessageForProof(options.claim);
4852
- } else if (this.channelManager) {
4853
- const peerId = this.resolvePeerId(destination);
4854
- const negotiation = this.peerNegotiations.get(peerId);
4855
- if (!negotiation) {
4856
- throw new ToonClientError(
4857
- `No negotiation metadata for peer "${peerId}" \u2014 was bootstrap completed?`,
4858
- "PEER_NOT_NEGOTIATED"
4859
- );
4860
- }
4861
- const channelId = await this.channelManager.ensureChannel(
4862
- peerId,
4863
- negotiation
4864
- );
4865
- const proof = await this.channelManager.signBalanceProof(
4866
- channelId,
4867
- BigInt(amount)
4868
- );
4869
- const signer = this.channelManager.getSignerForChannel(channelId);
4870
- claimMessage = signer.buildClaimMessage(proof, this.getPublicKey());
4871
- } else {
4872
- throw new ToonClientError(
4873
- "No claim provided and no channel manager configured",
4874
- "MISSING_CLAIM"
4875
- );
4876
- }
5320
+ const claimMessage = await this.resolveClaimForDestination(
5321
+ destination,
5322
+ BigInt(amount),
5323
+ options?.claim
5324
+ );
4877
5325
  const response = await transport.sendIlpPacketWithClaim(
4878
5326
  {
4879
5327
  destination,
@@ -5080,8 +5528,6 @@ var ToonClient = class {
5080
5528
  }
5081
5529
  /**
5082
5530
  * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
5083
- * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
5084
- * to call this helper. Kept duplicated for now to minimize regression risk.
5085
5531
  */
5086
5532
  async resolveClaimForDestination(destination, amount, explicitClaim) {
5087
5533
  if (explicitClaim) {
@@ -5277,6 +5723,54 @@ var ToonClient = class {
5277
5723
  this.channelManager.setChannelSettled(channelId, nowSec);
5278
5724
  return { channelId, ...r.txHash ? { txHash: r.txHash } : {} };
5279
5725
  }
5726
+ /**
5727
+ * Submit a receive-side swap settlement bundle on-chain (toon-client#352).
5728
+ * The bundle comes from the sdk's `buildSettlementTx` over persisted,
5729
+ * verified chain-B claims (`buildSwapSettlements`); this signs it with the
5730
+ * client's EVM account (the claim recipient) and broadcasts it.
5731
+ *
5732
+ * Env-gated seam: EVM only, and only when `chainRpcUrls[bundle.chain]` is
5733
+ * configured — otherwise this throws a clear config error and callers
5734
+ * surface a built-not-submitted result. Solana submission and the Mina
5735
+ * receive-side co-sign path are explicit follow-ups (see
5736
+ * swap/settle-received-claims.ts module doc).
5737
+ */
5738
+ async settleSwapBundle(bundle) {
5739
+ if (bundle.chainKind === "mina") {
5740
+ if (!this.minaPrivateKey) {
5741
+ throw new Error(
5742
+ "Mina signer not configured (no mnemonic / mina-signer) \u2014 cannot co-sign the receive-side claim."
5743
+ );
5744
+ }
5745
+ const makerSignature = parseMakerMinaSignature(
5746
+ this.config.swapMinaMakerSignatures?.[bundle.channelId]
5747
+ );
5748
+ const { txHash } = await submitMinaSettlement(bundle, {
5749
+ recipientPrivateKey: this.minaPrivateKey,
5750
+ ...this.config.minaChannel?.graphqlUrl ? { graphqlUrl: this.config.minaChannel.graphqlUrl } : {},
5751
+ ...makerSignature ? { makerSignature } : {}
5752
+ });
5753
+ return { txHash };
5754
+ }
5755
+ if (bundle.chainKind !== "evm") {
5756
+ throw new Error(
5757
+ `Swap settlement submission for ${bundle.chainKind} (${bundle.chain}) is not wired yet \u2014 EVM only today.`
5758
+ );
5759
+ }
5760
+ const rpcUrl = this.config.chainRpcUrls?.[bundle.chain];
5761
+ if (!rpcUrl) {
5762
+ throw new Error(
5763
+ `No RPC URL configured for chain "${bundle.chain}" \u2014 add it to chainRpcUrls to enable swap settlement submission.`
5764
+ );
5765
+ }
5766
+ if (!this.evmSigner) {
5767
+ throw new Error("EVM signer not configured (no evmPrivateKey/mnemonic).");
5768
+ }
5769
+ return submitEvmSettlement(bundle, {
5770
+ rpcUrl,
5771
+ account: this.evmSigner.account
5772
+ });
5773
+ }
5280
5774
  /** Where a tracked channel sits in the withdraw journey. */
5281
5775
  getChannelCloseState(channelId) {
5282
5776
  if (!this.channelManager) throw new Error("ChannelManager not initialized");
@@ -5875,6 +6369,328 @@ var HttpConnectorAdmin = class {
5875
6369
  }
5876
6370
  };
5877
6371
 
6372
+ // src/channel/ReceivedClaimStore.ts
6373
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync } from "fs";
6374
+ import { dirname } from "path";
6375
+ function key(chain, channelId) {
6376
+ return `${chain}|${channelId}`;
6377
+ }
6378
+ var JsonFileReceivedClaimStore = class {
6379
+ filePath;
6380
+ constructor(filePath) {
6381
+ this.filePath = filePath;
6382
+ }
6383
+ save(entry) {
6384
+ const data = this.readFile();
6385
+ data[key(entry.chain, entry.channelId)] = {
6386
+ chain: entry.chain,
6387
+ channelId: entry.channelId,
6388
+ nonce: entry.nonce.toString(),
6389
+ cumulativeAmount: entry.cumulativeAmount.toString(),
6390
+ recipient: entry.recipient,
6391
+ swapSignerAddress: entry.swapSignerAddress,
6392
+ claimBytes: Buffer.from(entry.claimBytes).toString("base64"),
6393
+ ...entry.claimId !== void 0 ? { claimId: entry.claimId } : {},
6394
+ pair: entry.pair,
6395
+ receivedAt: entry.receivedAt,
6396
+ updatedAt: entry.updatedAt,
6397
+ ...entry.settledAt !== void 0 ? { settledAt: entry.settledAt } : {},
6398
+ ...entry.settledNonce !== void 0 ? { settledNonce: entry.settledNonce.toString() } : {},
6399
+ ...entry.settleTxHash !== void 0 ? { settleTxHash: entry.settleTxHash } : {}
6400
+ };
6401
+ this.writeFile(data);
6402
+ }
6403
+ load(chain, channelId) {
6404
+ const entry = this.readFile()[key(chain, channelId)];
6405
+ return entry ? fromJson(entry) : void 0;
6406
+ }
6407
+ list() {
6408
+ return Object.values(this.readFile()).map(fromJson);
6409
+ }
6410
+ delete(chain, channelId) {
6411
+ const data = this.readFile();
6412
+ const { [key(chain, channelId)]: _, ...rest } = data;
6413
+ this.writeFile(rest);
6414
+ }
6415
+ readFile() {
6416
+ if (!existsSync2(this.filePath)) {
6417
+ return {};
6418
+ }
6419
+ const raw = readFileSync2(this.filePath, "utf-8");
6420
+ return JSON.parse(raw);
6421
+ }
6422
+ writeFile(data) {
6423
+ mkdirSync(dirname(this.filePath), { recursive: true });
6424
+ writeFileSync2(this.filePath, JSON.stringify(data, null, 2), "utf-8");
6425
+ }
6426
+ };
6427
+ var InMemoryReceivedClaimStore = class {
6428
+ entries = /* @__PURE__ */ new Map();
6429
+ save(entry) {
6430
+ this.entries.set(key(entry.chain, entry.channelId), { ...entry });
6431
+ }
6432
+ load(chain, channelId) {
6433
+ const entry = this.entries.get(key(chain, channelId));
6434
+ return entry ? { ...entry } : void 0;
6435
+ }
6436
+ list() {
6437
+ return [...this.entries.values()].map((e) => ({ ...e }));
6438
+ }
6439
+ delete(chain, channelId) {
6440
+ this.entries.delete(key(chain, channelId));
6441
+ }
6442
+ };
6443
+ function fromJson(entry) {
6444
+ return {
6445
+ chain: entry.chain,
6446
+ channelId: entry.channelId,
6447
+ nonce: BigInt(entry.nonce),
6448
+ cumulativeAmount: BigInt(entry.cumulativeAmount),
6449
+ recipient: entry.recipient,
6450
+ swapSignerAddress: entry.swapSignerAddress,
6451
+ claimBytes: new Uint8Array(Buffer.from(entry.claimBytes, "base64")),
6452
+ ...entry.claimId !== void 0 ? { claimId: entry.claimId } : {},
6453
+ pair: entry.pair,
6454
+ receivedAt: entry.receivedAt,
6455
+ updatedAt: entry.updatedAt,
6456
+ ...entry.settledAt !== void 0 ? { settledAt: entry.settledAt } : {},
6457
+ ...entry.settledNonce !== void 0 ? { settledNonce: BigInt(entry.settledNonce) } : {},
6458
+ ...entry.settleTxHash !== void 0 ? { settleTxHash: entry.settleTxHash } : {}
6459
+ };
6460
+ }
6461
+
6462
+ // src/swap/received-claims.ts
6463
+ import {
6464
+ verifyAccumulatedClaim
6465
+ } from "@toon-protocol/sdk";
6466
+ function hasSettlementMetadata(claim) {
6467
+ return claim.channelId !== void 0 && claim.nonce !== void 0 && claim.cumulativeAmount !== void 0 && claim.recipient !== void 0 && claim.swapSignerAddress !== void 0;
6468
+ }
6469
+ function sameAddress(chain, a, b) {
6470
+ return chain.startsWith("evm") ? a.toLowerCase() === b.toLowerCase() : a === b;
6471
+ }
6472
+ function signatureRejectionCode(reason) {
6473
+ if (reason.startsWith("SIGNER_MISMATCH")) return "SIGNER_MISMATCH";
6474
+ if (reason.startsWith("MINA_VERIFICATION_UNSUPPORTED"))
6475
+ return "MINA_VERIFICATION_UNSUPPORTED";
6476
+ if (reason.startsWith("UNSUPPORTED_CHAIN")) return "UNSUPPORTED_CHAIN";
6477
+ return "SIGNATURE_INVALID";
6478
+ }
6479
+ function ingestReceivedClaims(params) {
6480
+ const now = params.now ?? Date.now;
6481
+ const verified = [];
6482
+ const rejected = [];
6483
+ const legacy = [];
6484
+ let valueReceived = 0n;
6485
+ const watermarks = /* @__PURE__ */ new Map();
6486
+ const reject = (claim, code, message) => {
6487
+ rejected.push({ claim, code, message });
6488
+ };
6489
+ for (const claim of params.claims) {
6490
+ if (!hasSettlementMetadata(claim)) {
6491
+ legacy.push(claim);
6492
+ continue;
6493
+ }
6494
+ const chain = claim.pair.to.chain;
6495
+ if (chain !== params.expectedChain) {
6496
+ reject(
6497
+ claim,
6498
+ "CHAIN_MISMATCH",
6499
+ `claim settles on "${chain}", session expected "${params.expectedChain}"`
6500
+ );
6501
+ continue;
6502
+ }
6503
+ if (!sameAddress(chain, claim.recipient, params.chainRecipient)) {
6504
+ reject(
6505
+ claim,
6506
+ "RECIPIENT_MISMATCH",
6507
+ `claim recipient "${claim.recipient}" is not the session chainRecipient "${params.chainRecipient}"`
6508
+ );
6509
+ continue;
6510
+ }
6511
+ if (params.expectedSignerAddress !== void 0 && !sameAddress(chain, claim.swapSignerAddress, params.expectedSignerAddress)) {
6512
+ reject(
6513
+ claim,
6514
+ "SWAP_SIGNER_MISMATCH",
6515
+ `claim swapSignerAddress "${claim.swapSignerAddress}" does not match the maker's advertised signer "${params.expectedSignerAddress}"`
6516
+ );
6517
+ continue;
6518
+ }
6519
+ const expectedSigner = params.expectedSignerAddress ?? claim.swapSignerAddress;
6520
+ const sig = verifyAccumulatedClaim(
6521
+ claim,
6522
+ { address: expectedSigner },
6523
+ params.minaSignerClient
6524
+ );
6525
+ if (!sig.valid) {
6526
+ reject(claim, signatureRejectionCode(sig.reason), sig.reason);
6527
+ continue;
6528
+ }
6529
+ let nonce;
6530
+ let cumulativeAmount;
6531
+ try {
6532
+ nonce = BigInt(claim.nonce);
6533
+ cumulativeAmount = BigInt(claim.cumulativeAmount);
6534
+ } catch {
6535
+ reject(
6536
+ claim,
6537
+ "MALFORMED_METADATA",
6538
+ `nonce "${claim.nonce}" / cumulativeAmount "${claim.cumulativeAmount}" are not decimal integers`
6539
+ );
6540
+ continue;
6541
+ }
6542
+ const wmKey = `${chain}|${claim.channelId}`;
6543
+ const watermark = watermarks.get(wmKey) ?? params.store.load(chain, claim.channelId);
6544
+ if (watermark) {
6545
+ if (!sameAddress(chain, watermark.swapSignerAddress, expectedSigner)) {
6546
+ reject(
6547
+ claim,
6548
+ "SWAP_SIGNER_MISMATCH",
6549
+ `channel ${claim.channelId} watermark was signed by "${watermark.swapSignerAddress}"; a claim signed by "${expectedSigner}" may not rotate it`
6550
+ );
6551
+ continue;
6552
+ }
6553
+ if (nonce <= watermark.nonce) {
6554
+ reject(
6555
+ claim,
6556
+ "NON_MONOTONIC_NONCE",
6557
+ `nonce ${nonce} does not advance the persisted watermark nonce ${watermark.nonce} for channel ${claim.channelId}`
6558
+ );
6559
+ continue;
6560
+ }
6561
+ if (cumulativeAmount <= watermark.cumulativeAmount) {
6562
+ reject(
6563
+ claim,
6564
+ "NON_MONOTONIC_CUMULATIVE",
6565
+ `cumulativeAmount ${cumulativeAmount} does not advance the persisted watermark ${watermark.cumulativeAmount} for channel ${claim.channelId}`
6566
+ );
6567
+ continue;
6568
+ }
6569
+ }
6570
+ const advance = cumulativeAmount - (watermark?.cumulativeAmount ?? 0n);
6571
+ if (advance < claim.targetAmount) {
6572
+ reject(
6573
+ claim,
6574
+ "CUMULATIVE_SHORTFALL",
6575
+ `cumulative advance ${advance} is less than the packet's targetAmount ${claim.targetAmount} for channel ${claim.channelId}`
6576
+ );
6577
+ continue;
6578
+ }
6579
+ const entry = {
6580
+ chain,
6581
+ channelId: claim.channelId,
6582
+ nonce,
6583
+ cumulativeAmount,
6584
+ recipient: claim.recipient,
6585
+ swapSignerAddress: expectedSigner,
6586
+ claimBytes: claim.claimBytes,
6587
+ ...claim.claimId !== void 0 ? { claimId: claim.claimId } : {},
6588
+ pair: claim.pair,
6589
+ receivedAt: claim.receivedAt,
6590
+ updatedAt: now(),
6591
+ // Settlement bookkeeping survives watermark advances.
6592
+ ...watermark?.settledAt !== void 0 ? { settledAt: watermark.settledAt } : {},
6593
+ ...watermark?.settledNonce !== void 0 ? { settledNonce: watermark.settledNonce } : {},
6594
+ ...watermark?.settleTxHash !== void 0 ? { settleTxHash: watermark.settleTxHash } : {}
6595
+ };
6596
+ params.store.save(entry);
6597
+ watermarks.set(wmKey, entry);
6598
+ verified.push({ claim, watermarkAdvance: advance });
6599
+ valueReceived += advance;
6600
+ }
6601
+ return { verified, rejected, legacy, valueReceived };
6602
+ }
6603
+
6604
+ // src/swap/preimage-retention.ts
6605
+ function copy(entry) {
6606
+ return {
6607
+ packetIndex: entry.packetIndex,
6608
+ preimage: Uint8Array.from(entry.preimage),
6609
+ condition: Uint8Array.from(entry.condition),
6610
+ retainedAt: entry.retainedAt
6611
+ };
6612
+ }
6613
+ var InMemoryPreimageRetentionStore = class {
6614
+ entries = /* @__PURE__ */ new Map();
6615
+ retain(entry) {
6616
+ this.entries.set(entry.packetIndex, copy(entry));
6617
+ }
6618
+ get(packetIndex) {
6619
+ const entry = this.entries.get(packetIndex);
6620
+ return entry ? copy(entry) : void 0;
6621
+ }
6622
+ take(packetIndex) {
6623
+ const entry = this.entries.get(packetIndex);
6624
+ if (!entry) return void 0;
6625
+ this.entries.delete(packetIndex);
6626
+ return copy(entry);
6627
+ }
6628
+ size() {
6629
+ return this.entries.size;
6630
+ }
6631
+ clear() {
6632
+ this.entries.clear();
6633
+ }
6634
+ };
6635
+
6636
+ // src/swap/atomic-reveal.ts
6637
+ function rollbackWatermark(store, chain, channelId, prior) {
6638
+ if (prior === void 0) {
6639
+ store.delete(chain, channelId);
6640
+ } else {
6641
+ store.save(prior);
6642
+ }
6643
+ }
6644
+ async function ingestAndReveal(params) {
6645
+ const { reveal, preimages, claims, store, ...shared } = params;
6646
+ const revealed = [];
6647
+ const rolledBack = [];
6648
+ const rejected = [];
6649
+ const legacy = [];
6650
+ let valueRevealed = 0n;
6651
+ for (const claim of claims) {
6652
+ const hasMeta = hasSettlementMetadata(claim);
6653
+ const chain = hasMeta ? claim.pair.to.chain : void 0;
6654
+ const channelId = hasMeta ? claim.channelId : void 0;
6655
+ const prior = chain !== void 0 && channelId !== void 0 ? store.load(chain, channelId) : void 0;
6656
+ const ingest = ingestReceivedClaims({ ...shared, claims: [claim], store });
6657
+ if (ingest.legacy.length > 0) {
6658
+ legacy.push(claim);
6659
+ continue;
6660
+ }
6661
+ const rejection = ingest.rejected[0];
6662
+ if (rejection) {
6663
+ rejected.push(rejection);
6664
+ continue;
6665
+ }
6666
+ const verified = ingest.verified[0];
6667
+ if (!verified || chain === void 0 || channelId === void 0) continue;
6668
+ const advance = verified.watermarkAdvance;
6669
+ const preimage = preimages?.take(claim.packetIndex);
6670
+ let outcome;
6671
+ try {
6672
+ outcome = await reveal(claim, preimage);
6673
+ } catch (err) {
6674
+ outcome = {
6675
+ decision: "withheld",
6676
+ reason: err instanceof Error ? err.message : String(err)
6677
+ };
6678
+ }
6679
+ if (outcome.decision === "revealed") {
6680
+ revealed.push({ claim, watermarkAdvance: advance });
6681
+ valueRevealed += advance;
6682
+ continue;
6683
+ }
6684
+ rollbackWatermark(store, chain, channelId, prior);
6685
+ rolledBack.push({
6686
+ claim,
6687
+ watermarkAdvance: advance,
6688
+ reason: outcome.reason ?? "reveal withheld"
6689
+ });
6690
+ }
6691
+ return { revealed, rolledBack, rejected, legacy, valueRevealed };
6692
+ }
6693
+
5878
6694
  // src/faucet.ts
5879
6695
  function defaultFaucetTimeout(chain) {
5880
6696
  return chain === "mina" ? 12e4 : 3e4;
@@ -6854,7 +7670,7 @@ import {
6854
7670
  createDecipheriv,
6855
7671
  randomBytes as randomBytes2
6856
7672
  } from "crypto";
6857
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "fs";
7673
+ import { writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
6858
7674
  var SCRYPT_N = 2 ** 17;
6859
7675
  var SCRYPT_R = 8;
6860
7676
  var SCRYPT_P = 1;
@@ -6881,14 +7697,14 @@ function encryptMnemonic2(mnemonic, password) {
6881
7697
  }
6882
7698
  const salt = randomBytes2(SALT_LEN);
6883
7699
  const iv = randomBytes2(IV_LEN);
6884
- const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {
7700
+ const key2 = scryptSync(password, salt, SCRYPT_KEY_LEN, {
6885
7701
  N: SCRYPT_N,
6886
7702
  r: SCRYPT_R,
6887
7703
  p: SCRYPT_P,
6888
7704
  maxmem: SCRYPT_MAXMEM
6889
7705
  });
6890
7706
  try {
6891
- const cipher = createCipheriv("aes-256-gcm", key, iv, {
7707
+ const cipher = createCipheriv("aes-256-gcm", key2, iv, {
6892
7708
  authTagLength: AUTH_TAG_LEN
6893
7709
  });
6894
7710
  const ciphertext = Buffer.concat([
@@ -6904,7 +7720,7 @@ function encryptMnemonic2(mnemonic, password) {
6904
7720
  version: 1
6905
7721
  };
6906
7722
  } finally {
6907
- key.fill(0);
7723
+ key2.fill(0);
6908
7724
  }
6909
7725
  }
6910
7726
  function decryptMnemonic2(encrypted, password) {
@@ -6919,14 +7735,14 @@ function decryptMnemonic2(encrypted, password) {
6919
7735
  const iv = Buffer.from(encrypted.iv, "base64");
6920
7736
  const ciphertext = Buffer.from(encrypted.ciphertext, "base64");
6921
7737
  const tag = Buffer.from(encrypted.tag, "base64");
6922
- const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {
7738
+ const key2 = scryptSync(password, salt, SCRYPT_KEY_LEN, {
6923
7739
  N: SCRYPT_N,
6924
7740
  r: SCRYPT_R,
6925
7741
  p: SCRYPT_P,
6926
7742
  maxmem: SCRYPT_MAXMEM
6927
7743
  });
6928
7744
  try {
6929
- const decipher = createDecipheriv("aes-256-gcm", key, iv, {
7745
+ const decipher = createDecipheriv("aes-256-gcm", key2, iv, {
6930
7746
  authTagLength: AUTH_TAG_LEN
6931
7747
  });
6932
7748
  decipher.setAuthTag(tag);
@@ -6942,7 +7758,7 @@ function decryptMnemonic2(encrypted, password) {
6942
7758
  );
6943
7759
  }
6944
7760
  } finally {
6945
- key.fill(0);
7761
+ key2.fill(0);
6946
7762
  }
6947
7763
  }
6948
7764
  function generateKeystore(path, password) {
@@ -6965,7 +7781,7 @@ function importKeystore(path, mnemonic, password) {
6965
7781
  }
6966
7782
  function loadKeystore(path, password) {
6967
7783
  assertNode();
6968
- const raw = readFileSync2(path, "utf8");
7784
+ const raw = readFileSync3(path, "utf8");
6969
7785
  let parsed;
6970
7786
  try {
6971
7787
  parsed = JSON.parse(raw);
@@ -6976,7 +7792,7 @@ function loadKeystore(path, password) {
6976
7792
  }
6977
7793
  function writeKeystoreFile(path, keystore) {
6978
7794
  assertNode();
6979
- writeFileSync2(path, JSON.stringify(keystore, null, 2), {
7795
+ writeFileSync3(path, JSON.stringify(keystore, null, 2), {
6980
7796
  encoding: "utf8",
6981
7797
  mode: 384
6982
7798
  });
@@ -6998,10 +7814,15 @@ export {
6998
7814
  ILP_CLAIM_HEADER,
6999
7815
  ILP_CLAIM_WRAPPED_HEADER,
7000
7816
  ILP_PEER_ID_HEADER,
7817
+ InMemoryPreimageRetentionStore,
7818
+ InMemoryReceivedClaimStore,
7819
+ JsonFileReceivedClaimStore,
7001
7820
  KeyManager,
7002
7821
  KindRegistry,
7003
7822
  MIME_A2UI,
7004
7823
  MIME_MCP_APP,
7824
+ MINA_CHANNEL_STATE,
7825
+ MinaSettlementError,
7005
7826
  MinaSigner,
7006
7827
  NetworkError,
7007
7828
  OnChainChannelClient,
@@ -7018,11 +7839,15 @@ export {
7018
7839
  buildBackupEvent,
7019
7840
  buildBackupFilter,
7020
7841
  buildConsentRequest,
7842
+ buildMinaCoSignedClaim,
7021
7843
  buildRendererEventTemplate,
7022
7844
  buildSettlementInfo,
7023
7845
  buildStoreWriteEnvelope,
7846
+ buildSwapSettlements,
7024
7847
  buildUiCoordinate,
7025
7848
  classifyIntent,
7849
+ createO1jsMinaClaimSubmitter,
7850
+ decodeEvmSettlementTx,
7026
7851
  decryptMnemonic2 as decryptMnemonic,
7027
7852
  defaultFaucetTimeout,
7028
7853
  deriveFromNsec,
@@ -7030,6 +7855,7 @@ export {
7030
7855
  deriveNostrKeyFromMnemonic,
7031
7856
  deterministicGenerator,
7032
7857
  encryptMnemonic2 as encryptMnemonic,
7858
+ entryToAccumulatedClaim,
7033
7859
  extractArweaveTxId,
7034
7860
  extractUiResource,
7035
7861
  fulfillmentMatchesCondition,
@@ -7040,8 +7866,11 @@ export {
7040
7866
  getNetworkStatus,
7041
7867
  getUiCoordinate,
7042
7868
  guardedRenderDispatch,
7869
+ hasSettlementMetadata,
7043
7870
  httpEndpointToBtpUrl,
7044
7871
  importKeystore,
7872
+ ingestAndReveal,
7873
+ ingestReceivedClaims,
7045
7874
  isInsufficientGasError,
7046
7875
  isPrfSupported,
7047
7876
  isTrustDowngrade,
@@ -7049,6 +7878,7 @@ export {
7049
7878
  loadKeystore,
7050
7879
  mintExecutionCondition,
7051
7880
  parseBackupPayload,
7881
+ parseEvmChainId2 as parseEvmChainId,
7052
7882
  parseFulfillHttp,
7053
7883
  parseFulfillHttpBytes,
7054
7884
  parseHttpResponse,
@@ -7061,6 +7891,7 @@ export {
7061
7891
  readEvmNativeBalance,
7062
7892
  readEvmTokenBalance,
7063
7893
  readMinaBalance,
7894
+ readMinaChannelState,
7064
7895
  readMinaDepositTotal,
7065
7896
  readSolanaNativeBalance,
7066
7897
  readSolanaTokenBalance,
@@ -7074,6 +7905,8 @@ export {
7074
7905
  selectIlpTransport,
7075
7906
  selectLatestAddressable,
7076
7907
  serializeHttpRequest,
7908
+ submitEvmSettlement,
7909
+ submitMinaSettlement,
7077
7910
  validateConfig,
7078
7911
  validateMnemonic,
7079
7912
  verifyRendererTrust,