@toon-protocol/client 0.18.0 → 0.20.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
@@ -3409,7 +3409,14 @@ async function buildMinaPaymentChannelProof(params) {
3409
3409
  }
3410
3410
 
3411
3411
  // src/channel/mina-deposit.ts
3412
- 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;
3413
3420
  async function readMinaDepositTotal(graphqlUrl, zkAppAddress, fetchImpl = fetch) {
3414
3421
  const query = "query($pk:String!){account(publicKey:$pk){zkappState}}";
3415
3422
  const res = await fetchImpl(graphqlUrl, {
@@ -3434,6 +3441,42 @@ async function readMinaDepositTotal(graphqlUrl, zkAppAddress, fetchImpl = fetch)
3434
3441
  }
3435
3442
  return BigInt(state[DEPOSIT_TOTAL_STATE_INDEX]);
3436
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
+ }
3437
3480
 
3438
3481
  // src/signing/mina-signer.ts
3439
3482
  var DEFAULT_MINA_TOKEN_ID = "MINA";
@@ -4634,6 +4677,7 @@ function buildSwapSettlements(params) {
4634
4677
  claims: [entryToAccumulatedClaim(entry)],
4635
4678
  signers: { [entry.chain]: signer },
4636
4679
  recipients: { [entry.chain]: entry.recipient },
4680
+ ...params.verifySignatures !== void 0 ? { verifySignatures: params.verifySignatures } : {},
4637
4681
  ...params.minaSignerClient ? { minaSignerClient: params.minaSignerClient } : {}
4638
4682
  });
4639
4683
  const firstRejected = result.rejected[0];
@@ -4681,7 +4725,9 @@ function decodeEvmSettlementTx(bundle) {
4681
4725
  const chainIdHex = fields[6];
4682
4726
  const chainId = Number.parseInt(chainIdHex === "0x" ? "0x0" : chainIdHex, 16);
4683
4727
  if (typeof to !== "string" || to.length !== 42) {
4684
- throw new Error(`settlement tx "to" is not a 20-byte address: ${String(to)}`);
4728
+ throw new Error(
4729
+ `settlement tx "to" is not a 20-byte address: ${String(to)}`
4730
+ );
4685
4731
  }
4686
4732
  return { to, data: data === "0x" ? "0x" : data, chainId };
4687
4733
  }
@@ -4698,7 +4744,10 @@ async function submitEvmSettlement(bundle, params) {
4698
4744
  nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
4699
4745
  rpcUrls: { default: { http: [params.rpcUrl] } }
4700
4746
  });
4701
- const publicClient = createPublicClient3({ chain, transport: http3(params.rpcUrl) });
4747
+ const publicClient = createPublicClient3({
4748
+ chain,
4749
+ transport: http3(params.rpcUrl)
4750
+ });
4702
4751
  const [nonce, gasPrice, gas] = await Promise.all([
4703
4752
  publicClient.getTransactionCount({
4704
4753
  address: params.account.address,
@@ -4731,7 +4780,265 @@ async function submitEvmSettlement(bundle, params) {
4731
4780
  }
4732
4781
  }
4733
4782
 
4783
+ // src/swap/mina-settlement.ts
4784
+ import { hexToMinaBase58PrivateKey as hexToMinaBase58PrivateKey4 } from "@toon-protocol/core";
4785
+ var MinaSettlementError = class extends Error {
4786
+ code;
4787
+ constructor(code, message) {
4788
+ super(`${code}: ${message}`);
4789
+ this.name = "MinaSettlementError";
4790
+ this.code = code;
4791
+ }
4792
+ };
4793
+ function parseProofSignature(proofJson) {
4794
+ const parsed = JSON.parse(proofJson);
4795
+ const r = parsed.signature?.r;
4796
+ const s = parsed.signature?.s;
4797
+ if (typeof r !== "string" || typeof s !== "string") {
4798
+ throw new Error("recipient co-signature proof did not carry string r/s");
4799
+ }
4800
+ return { r, s };
4801
+ }
4802
+ async function buildMinaCoSignedClaim(inputs) {
4803
+ const channelNonce = inputs.channelNonce ?? 0n;
4804
+ const salt = inputs.saltOverride ?? deriveMinaSalt(inputs.channelId, Number(inputs.nonce));
4805
+ if (inputs.cumulativeAmount > inputs.depositTotal) {
4806
+ throw new MinaSettlementError(
4807
+ "CUMULATIVE_EXCEEDS_DEPOSIT",
4808
+ `claim cumulativeAmount (${inputs.cumulativeAmount}) exceeds the channel depositTotal (${inputs.depositTotal}) \u2014 cannot conserve balances`
4809
+ );
4810
+ }
4811
+ const { Poseidon, PublicKey } = await loadMinaPaymentChannelBindings();
4812
+ const hashRecipientFirst = minaParticipantChannelHashField(
4813
+ Poseidon,
4814
+ PublicKey,
4815
+ inputs.recipient,
4816
+ inputs.swapSignerAddress,
4817
+ channelNonce
4818
+ ).toString();
4819
+ const hashMakerFirst = minaParticipantChannelHashField(
4820
+ Poseidon,
4821
+ PublicKey,
4822
+ inputs.swapSignerAddress,
4823
+ inputs.recipient,
4824
+ channelNonce
4825
+ ).toString();
4826
+ let participantA;
4827
+ let participantB;
4828
+ let recipientRole;
4829
+ if (hashRecipientFirst === inputs.onChainChannelHash) {
4830
+ participantA = inputs.recipient;
4831
+ participantB = inputs.swapSignerAddress;
4832
+ recipientRole = "A";
4833
+ } else if (hashMakerFirst === inputs.onChainChannelHash) {
4834
+ participantA = inputs.swapSignerAddress;
4835
+ participantB = inputs.recipient;
4836
+ recipientRole = "B";
4837
+ } else {
4838
+ throw new MinaSettlementError(
4839
+ "CHANNEL_HASH_MISMATCH",
4840
+ `neither ordering of recipient ${inputs.recipient} / maker ${inputs.swapSignerAddress} reproduces the on-chain channelHash ${inputs.onChainChannelHash} (channelNonce ${channelNonce})`
4841
+ );
4842
+ }
4843
+ const recipientBalance = inputs.cumulativeAmount;
4844
+ const makerBalance = inputs.depositTotal - inputs.cumulativeAmount;
4845
+ const balanceA = recipientRole === "A" ? recipientBalance : makerBalance;
4846
+ const balanceB = inputs.depositTotal - balanceA;
4847
+ const balanceCommitment = minaBalanceCommitment(
4848
+ Poseidon,
4849
+ balanceA,
4850
+ balanceB,
4851
+ salt
4852
+ ).toString();
4853
+ const recipientPrivateKeyBase58 = hexToMinaBase58PrivateKey4(
4854
+ inputs.recipientPrivateKey
4855
+ );
4856
+ const built = await buildMinaPaymentChannelProof({
4857
+ zkAppAddress: inputs.channelId,
4858
+ minaPrivateKeyBase58: recipientPrivateKeyBase58,
4859
+ signerPublicKey: inputs.recipient,
4860
+ balanceA,
4861
+ balanceB,
4862
+ salt,
4863
+ nonce: inputs.nonce,
4864
+ participantA,
4865
+ participantB,
4866
+ channelNonce,
4867
+ proofEncoding: "json"
4868
+ });
4869
+ if (built.balanceCommitment !== balanceCommitment) {
4870
+ throw new Error(
4871
+ `co-sign commitment drift: builder ${built.balanceCommitment} != ${balanceCommitment}`
4872
+ );
4873
+ }
4874
+ const recipientSignature = parseProofSignature(built.proof);
4875
+ const signatureA = recipientRole === "A" ? recipientSignature : inputs.makerSignature;
4876
+ const signatureB = recipientRole === "B" ? recipientSignature : inputs.makerSignature;
4877
+ return {
4878
+ channelId: inputs.channelId,
4879
+ balanceA,
4880
+ balanceB,
4881
+ salt,
4882
+ nonce: inputs.nonce,
4883
+ channelNonce,
4884
+ balanceCommitment,
4885
+ participantA,
4886
+ participantB,
4887
+ recipientRole,
4888
+ recipientSignature,
4889
+ ...signatureA ? { signatureA } : {},
4890
+ ...signatureB ? { signatureB } : {},
4891
+ makerSignatureMissing: inputs.makerSignature === void 0
4892
+ };
4893
+ }
4894
+ async function submitMinaSettlement(bundle, context) {
4895
+ if (bundle.chainKind !== "mina") {
4896
+ throw new MinaSettlementError(
4897
+ "NOT_MINA_BUNDLE",
4898
+ `submitMinaSettlement only settles mina bundles (got ${bundle.chainKind} for ${bundle.chain})`
4899
+ );
4900
+ }
4901
+ if (!context.graphqlUrl) {
4902
+ throw new MinaSettlementError(
4903
+ "NO_GRAPHQL_CONFIGURED",
4904
+ `no Mina graphqlUrl configured for "${bundle.chain}" \u2014 set minaChannel.graphqlUrl to enable receive-side settlement.`
4905
+ );
4906
+ }
4907
+ const graphqlUrl = context.graphqlUrl;
4908
+ const channelNonce = context.channelNonce ?? 0n;
4909
+ const nonce = BigInt(bundle.nonce);
4910
+ const cumulativeAmount = BigInt(bundle.cumulativeAmount);
4911
+ const read = context.reader ?? readMinaChannelState;
4912
+ const state = await read(graphqlUrl, bundle.channelId);
4913
+ if (state.channelState !== MINA_CHANNEL_STATE.OPEN) {
4914
+ throw new MinaSettlementError(
4915
+ "CHANNEL_NOT_OPEN",
4916
+ `channel ${bundle.channelId} is not OPEN (channelState=${state.channelState}); claimFromChannel only applies to an OPEN channel.`
4917
+ );
4918
+ }
4919
+ if (nonce <= state.nonceField) {
4920
+ throw new MinaSettlementError(
4921
+ "NONCE_NOT_ADVANCING",
4922
+ `claim nonce ${nonce} does not advance the on-chain nonceField ${state.nonceField} for ${bundle.channelId} (already claimed).`
4923
+ );
4924
+ }
4925
+ const claim = await buildMinaCoSignedClaim({
4926
+ channelId: bundle.channelId,
4927
+ nonce,
4928
+ cumulativeAmount,
4929
+ recipient: bundle.recipient,
4930
+ swapSignerAddress: bundle.swapSignerAddress,
4931
+ depositTotal: state.depositTotal,
4932
+ onChainChannelHash: state.channelHash,
4933
+ recipientPrivateKey: context.recipientPrivateKey,
4934
+ channelNonce,
4935
+ ...context.makerSignature ? { makerSignature: context.makerSignature } : {}
4936
+ });
4937
+ if (claim.makerSignatureMissing || !claim.signatureA || !claim.signatureB) {
4938
+ throw new MinaSettlementError(
4939
+ "MINA_MAKER_COSIGN_REQUIRED",
4940
+ `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}).`
4941
+ );
4942
+ }
4943
+ const submitter = context.submitter ?? createO1jsMinaClaimSubmitter();
4944
+ const { txHash } = await submitter.claimFromChannel({
4945
+ graphqlUrl,
4946
+ channelId: bundle.channelId,
4947
+ balanceA: claim.balanceA,
4948
+ balanceB: claim.balanceB,
4949
+ salt: claim.salt,
4950
+ nonce: claim.nonce,
4951
+ participantA: claim.participantA,
4952
+ participantB: claim.participantB,
4953
+ channelNonce: claim.channelNonce,
4954
+ signatureA: claim.signatureA,
4955
+ signatureB: claim.signatureB,
4956
+ feePayerPrivateKey: context.feePayerPrivateKey ?? context.recipientPrivateKey,
4957
+ ...context.txFeeNanomina !== void 0 ? { txFeeNanomina: context.txFeeNanomina } : {}
4958
+ });
4959
+ return { txHash };
4960
+ }
4961
+ var DEFAULT_MINA_TX_FEE_NANOMINA = 100000000n;
4962
+ function createO1jsMinaClaimSubmitter() {
4963
+ return {
4964
+ async claimFromChannel(args) {
4965
+ try {
4966
+ const o1js = await import("o1js");
4967
+ const {
4968
+ Mina,
4969
+ PrivateKey,
4970
+ PublicKey,
4971
+ Field,
4972
+ Poseidon,
4973
+ Signature,
4974
+ fetchAccount
4975
+ } = o1js;
4976
+ const zkAppMod = await import("@toon-protocol/mina-zkapp");
4977
+ const PaymentChannel = zkAppMod.PaymentChannel;
4978
+ Mina.setActiveInstance(Mina.Network(args.graphqlUrl));
4979
+ const feePayerKey = PrivateKey.fromBase58(
4980
+ hexToMinaBase58PrivateKey4(args.feePayerPrivateKey)
4981
+ );
4982
+ const feePayerPub = feePayerKey.toPublicKey();
4983
+ await PaymentChannel.compile();
4984
+ const zkAppPub = PublicKey.fromBase58(args.channelId);
4985
+ await fetchAccount({ publicKey: zkAppPub });
4986
+ await fetchAccount({ publicKey: feePayerPub });
4987
+ const zkApp = new PaymentChannel(zkAppPub);
4988
+ const balA = Field(args.balanceA);
4989
+ const balB = Field(args.balanceB);
4990
+ const saltField = Field(args.salt);
4991
+ const newNonce = Field(args.nonce);
4992
+ const channelNonce = Field(args.channelNonce);
4993
+ const newBalanceCommitment = Poseidon.hash([balA, balB, saltField]);
4994
+ const sigA = Signature.fromJSON({
4995
+ r: args.signatureA.r,
4996
+ s: args.signatureA.s
4997
+ });
4998
+ const sigB = Signature.fromJSON({
4999
+ r: args.signatureB.r,
5000
+ s: args.signatureB.s
5001
+ });
5002
+ const partA = PublicKey.fromBase58(args.participantA);
5003
+ const partB = PublicKey.fromBase58(args.participantB);
5004
+ const fee = (args.txFeeNanomina ?? DEFAULT_MINA_TX_FEE_NANOMINA).toString();
5005
+ const txn = await Mina.transaction(
5006
+ { sender: feePayerPub, fee },
5007
+ async () => {
5008
+ await zkApp.claimFromChannel(
5009
+ balA,
5010
+ balB,
5011
+ saltField,
5012
+ sigA,
5013
+ sigB,
5014
+ partA,
5015
+ partB,
5016
+ channelNonce,
5017
+ newBalanceCommitment,
5018
+ newNonce
5019
+ );
5020
+ }
5021
+ );
5022
+ await txn.prove();
5023
+ const sent = await txn.sign([feePayerKey]).send();
5024
+ return { txHash: sent.hash ?? "" };
5025
+ } catch (err) {
5026
+ throw new MinaSettlementError(
5027
+ "PROVING_FAILED",
5028
+ err instanceof Error ? err.message : String(err)
5029
+ );
5030
+ }
5031
+ }
5032
+ };
5033
+ }
5034
+
4734
5035
  // src/ToonClient.ts
5036
+ function parseMakerMinaSignature(raw) {
5037
+ if (raw && typeof raw.r === "string" && raw.r.length > 0 && typeof raw.s === "string" && raw.s.length > 0) {
5038
+ return { r: raw.r, s: raw.s };
5039
+ }
5040
+ return void 0;
5041
+ }
4735
5042
  var ToonClient = class {
4736
5043
  config;
4737
5044
  state = null;
@@ -5016,34 +5323,11 @@ var ToonClient = class {
5016
5323
  const writeData = buildStoreWriteEnvelope(event, options?.proxyPath);
5017
5324
  const destination = options?.destination ?? this.config.destinationAddress;
5018
5325
  const transport = this.getClaimTransport();
5019
- let claimMessage;
5020
- if (options?.claim) {
5021
- claimMessage = this.buildClaimMessageForProof(options.claim);
5022
- } else if (this.channelManager) {
5023
- const peerId = this.resolvePeerId(destination);
5024
- const negotiation = this.peerNegotiations.get(peerId);
5025
- if (!negotiation) {
5026
- throw new ToonClientError(
5027
- `No negotiation metadata for peer "${peerId}" \u2014 was bootstrap completed?`,
5028
- "PEER_NOT_NEGOTIATED"
5029
- );
5030
- }
5031
- const channelId = await this.channelManager.ensureChannel(
5032
- peerId,
5033
- negotiation
5034
- );
5035
- const proof = await this.channelManager.signBalanceProof(
5036
- channelId,
5037
- BigInt(amount)
5038
- );
5039
- const signer = this.channelManager.getSignerForChannel(channelId);
5040
- claimMessage = signer.buildClaimMessage(proof, this.getPublicKey());
5041
- } else {
5042
- throw new ToonClientError(
5043
- "No claim provided and no channel manager configured",
5044
- "MISSING_CLAIM"
5045
- );
5046
- }
5326
+ const claimMessage = await this.resolveClaimForDestination(
5327
+ destination,
5328
+ BigInt(amount),
5329
+ options?.claim
5330
+ );
5047
5331
  const response = await transport.sendIlpPacketWithClaim(
5048
5332
  {
5049
5333
  destination,
@@ -5250,8 +5534,6 @@ var ToonClient = class {
5250
5534
  }
5251
5535
  /**
5252
5536
  * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
5253
- * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
5254
- * to call this helper. Kept duplicated for now to minimize regression risk.
5255
5537
  */
5256
5538
  async resolveClaimForDestination(destination, amount, explicitClaim) {
5257
5539
  if (explicitClaim) {
@@ -5460,6 +5742,22 @@ var ToonClient = class {
5460
5742
  * swap/settle-received-claims.ts module doc).
5461
5743
  */
5462
5744
  async settleSwapBundle(bundle) {
5745
+ if (bundle.chainKind === "mina") {
5746
+ if (!this.minaPrivateKey) {
5747
+ throw new Error(
5748
+ "Mina signer not configured (no mnemonic / mina-signer) \u2014 cannot co-sign the receive-side claim."
5749
+ );
5750
+ }
5751
+ const makerSignature = parseMakerMinaSignature(
5752
+ this.config.swapMinaMakerSignatures?.[bundle.channelId]
5753
+ );
5754
+ const { txHash } = await submitMinaSettlement(bundle, {
5755
+ recipientPrivateKey: this.minaPrivateKey,
5756
+ ...this.config.minaChannel?.graphqlUrl ? { graphqlUrl: this.config.minaChannel.graphqlUrl } : {},
5757
+ ...makerSignature ? { makerSignature } : {}
5758
+ });
5759
+ return { txHash };
5760
+ }
5463
5761
  if (bundle.chainKind !== "evm") {
5464
5762
  throw new Error(
5465
5763
  `Swap settlement submission for ${bundle.chainKind} (${bundle.chain}) is not wired yet \u2014 EVM only today.`
@@ -6171,6 +6469,116 @@ function fromJson(entry) {
6171
6469
  import {
6172
6470
  verifyAccumulatedClaim
6173
6471
  } from "@toon-protocol/sdk";
6472
+
6473
+ // src/swap/evm-claim-digest.ts
6474
+ import {
6475
+ hashTypedData
6476
+ } from "viem";
6477
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
6478
+ import { keccak_256 } from "@noble/hashes/sha3.js";
6479
+ import { hexToBytes, bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
6480
+ var ROLLING_SWAP_DOMAIN_NAME = "RollingSwapChannel";
6481
+ var ROLLING_SWAP_DOMAIN_VERSION = "2";
6482
+ var CLAIM_TYPEHASH = "0xa0c8262c1a8615f7674d3af796b14d19672d3634f89c6093502ab35c0afe2d91";
6483
+ var COOP_CLOSE_TYPEHASH = "0xa5753389755fea51cd5016d7b02b508ac03f2e822d9a7ee345ec45b36574ff9f";
6484
+ var CLAIM_TYPES = {
6485
+ ClaimBalanceProof: [
6486
+ { name: "channelId", type: "bytes32" },
6487
+ { name: "cumulativeAmount", type: "uint256" },
6488
+ { name: "nonce", type: "uint256" },
6489
+ { name: "recipient", type: "address" }
6490
+ ]
6491
+ };
6492
+ var COOP_CLOSE_TYPES = {
6493
+ CooperativeClose: [
6494
+ { name: "channelId", type: "bytes32" },
6495
+ { name: "cumulativeAmount", type: "uint256" },
6496
+ { name: "nonce", type: "uint256" }
6497
+ ]
6498
+ };
6499
+ function normalizeAddress(addr) {
6500
+ return addr.toLowerCase();
6501
+ }
6502
+ function domainOf(ctx) {
6503
+ return {
6504
+ name: ROLLING_SWAP_DOMAIN_NAME,
6505
+ version: ROLLING_SWAP_DOMAIN_VERSION,
6506
+ chainId: ctx.chainId,
6507
+ verifyingContract: normalizeAddress(ctx.verifyingContract)
6508
+ };
6509
+ }
6510
+ function evmClaimDigest(ctx, message) {
6511
+ return hashTypedData({
6512
+ domain: domainOf(ctx),
6513
+ types: CLAIM_TYPES,
6514
+ primaryType: "ClaimBalanceProof",
6515
+ message: {
6516
+ channelId: message.channelId,
6517
+ cumulativeAmount: message.cumulativeAmount,
6518
+ nonce: message.nonce,
6519
+ recipient: normalizeAddress(message.recipient)
6520
+ }
6521
+ });
6522
+ }
6523
+ function evmCooperativeCloseDigest(ctx, message) {
6524
+ return hashTypedData({
6525
+ domain: domainOf(ctx),
6526
+ types: COOP_CLOSE_TYPES,
6527
+ primaryType: "CooperativeClose",
6528
+ message: {
6529
+ channelId: message.channelId,
6530
+ cumulativeAmount: message.cumulativeAmount,
6531
+ nonce: message.nonce
6532
+ }
6533
+ });
6534
+ }
6535
+ var SIG_BYTES = 65;
6536
+ function signatureToBytes(sig) {
6537
+ if (typeof sig !== "string") return sig;
6538
+ const hex = sig.startsWith("0x") ? sig.slice(2) : sig;
6539
+ return hexToBytes(hex);
6540
+ }
6541
+ function recoverEvmClaimSigner(ctx, message, signature) {
6542
+ const bytes = signatureToBytes(signature);
6543
+ if (bytes.length !== SIG_BYTES) {
6544
+ throw new Error(
6545
+ `EVM signature must be 65 bytes (r||s||v), got ${bytes.length}`
6546
+ );
6547
+ }
6548
+ const v = bytes[64];
6549
+ if (v !== 27 && v !== 28) {
6550
+ throw new Error(`EVM signature v must be 27 or 28, got ${v}`);
6551
+ }
6552
+ const digest = evmClaimDigest(ctx, message).slice(2);
6553
+ const recovered = secp256k1.Signature.fromBytes(bytes.slice(0, 64), "compact").addRecoveryBit(v - 27).recoverPublicKey(hexToBytes(digest));
6554
+ const uncompressed = recovered.toBytes(false);
6555
+ const addrHash = keccak_256(uncompressed.slice(1));
6556
+ return `0x${bytesToHex2(addrHash.slice(-20))}`;
6557
+ }
6558
+ function verifyEvmClaimSignature(params) {
6559
+ let recovered;
6560
+ try {
6561
+ recovered = recoverEvmClaimSigner(
6562
+ params.ctx,
6563
+ params.message,
6564
+ params.signature
6565
+ );
6566
+ } catch (err) {
6567
+ return {
6568
+ valid: false,
6569
+ reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`
6570
+ };
6571
+ }
6572
+ if (recovered.toLowerCase() !== params.expectedSigner.toLowerCase()) {
6573
+ return {
6574
+ valid: false,
6575
+ reason: `SIGNER_MISMATCH: recovered ${recovered}, expected ${params.expectedSigner}`
6576
+ };
6577
+ }
6578
+ return { valid: true, recovered };
6579
+ }
6580
+
6581
+ // src/swap/received-claims.ts
6174
6582
  function hasSettlementMetadata(claim) {
6175
6583
  return claim.channelId !== void 0 && claim.nonce !== void 0 && claim.cumulativeAmount !== void 0 && claim.recipient !== void 0 && claim.swapSignerAddress !== void 0;
6176
6584
  }
@@ -6225,11 +6633,49 @@ function ingestReceivedClaims(params) {
6225
6633
  continue;
6226
6634
  }
6227
6635
  const expectedSigner = params.expectedSignerAddress ?? claim.swapSignerAddress;
6228
- const sig = verifyAccumulatedClaim(
6229
- claim,
6230
- { address: expectedSigner },
6231
- params.minaSignerClient
6232
- );
6636
+ let sig;
6637
+ if (chain.startsWith("evm")) {
6638
+ const chainId = parseEvmChainId2(chain);
6639
+ const verifyingContract = params.tokenNetworks?.[chain];
6640
+ if (chainId === void 0 || !verifyingContract) {
6641
+ reject(
6642
+ claim,
6643
+ "MISSING_CHAIN_CONFIG",
6644
+ `EVM v2 balance-proof verification for ${chain} needs a numeric chain id in the chain key AND tokenNetworks["${chain}"] (the RollingSwapChannel / verifyingContract) to reconstruct the EIP-712 domain; supply it from the connector/swap session context.`
6645
+ );
6646
+ continue;
6647
+ }
6648
+ let msgNonce;
6649
+ let msgCumulative;
6650
+ try {
6651
+ msgNonce = BigInt(claim.nonce);
6652
+ msgCumulative = BigInt(claim.cumulativeAmount);
6653
+ } catch {
6654
+ reject(
6655
+ claim,
6656
+ "MALFORMED_METADATA",
6657
+ `nonce "${claim.nonce}" / cumulativeAmount "${claim.cumulativeAmount}" are not decimal integers`
6658
+ );
6659
+ continue;
6660
+ }
6661
+ sig = verifyEvmClaimSignature({
6662
+ ctx: { chainId, verifyingContract },
6663
+ message: {
6664
+ channelId: claim.channelId,
6665
+ cumulativeAmount: msgCumulative,
6666
+ nonce: msgNonce,
6667
+ recipient: claim.recipient
6668
+ },
6669
+ signature: claim.claimBytes,
6670
+ expectedSigner
6671
+ });
6672
+ } else {
6673
+ sig = verifyAccumulatedClaim(
6674
+ claim,
6675
+ { address: expectedSigner },
6676
+ params.minaSignerClient
6677
+ );
6678
+ }
6233
6679
  if (!sig.valid) {
6234
6680
  reject(claim, signatureRejectionCode(sig.reason), sig.reason);
6235
6681
  continue;
@@ -6309,6 +6755,96 @@ function ingestReceivedClaims(params) {
6309
6755
  return { verified, rejected, legacy, valueReceived };
6310
6756
  }
6311
6757
 
6758
+ // src/swap/preimage-retention.ts
6759
+ function copy(entry) {
6760
+ return {
6761
+ packetIndex: entry.packetIndex,
6762
+ preimage: Uint8Array.from(entry.preimage),
6763
+ condition: Uint8Array.from(entry.condition),
6764
+ retainedAt: entry.retainedAt
6765
+ };
6766
+ }
6767
+ var InMemoryPreimageRetentionStore = class {
6768
+ entries = /* @__PURE__ */ new Map();
6769
+ retain(entry) {
6770
+ this.entries.set(entry.packetIndex, copy(entry));
6771
+ }
6772
+ get(packetIndex) {
6773
+ const entry = this.entries.get(packetIndex);
6774
+ return entry ? copy(entry) : void 0;
6775
+ }
6776
+ take(packetIndex) {
6777
+ const entry = this.entries.get(packetIndex);
6778
+ if (!entry) return void 0;
6779
+ this.entries.delete(packetIndex);
6780
+ return copy(entry);
6781
+ }
6782
+ size() {
6783
+ return this.entries.size;
6784
+ }
6785
+ clear() {
6786
+ this.entries.clear();
6787
+ }
6788
+ };
6789
+
6790
+ // src/swap/atomic-reveal.ts
6791
+ function rollbackWatermark(store, chain, channelId, prior) {
6792
+ if (prior === void 0) {
6793
+ store.delete(chain, channelId);
6794
+ } else {
6795
+ store.save(prior);
6796
+ }
6797
+ }
6798
+ async function ingestAndReveal(params) {
6799
+ const { reveal, preimages, claims, store, ...shared } = params;
6800
+ const revealed = [];
6801
+ const rolledBack = [];
6802
+ const rejected = [];
6803
+ const legacy = [];
6804
+ let valueRevealed = 0n;
6805
+ for (const claim of claims) {
6806
+ const hasMeta = hasSettlementMetadata(claim);
6807
+ const chain = hasMeta ? claim.pair.to.chain : void 0;
6808
+ const channelId = hasMeta ? claim.channelId : void 0;
6809
+ const prior = chain !== void 0 && channelId !== void 0 ? store.load(chain, channelId) : void 0;
6810
+ const ingest = ingestReceivedClaims({ ...shared, claims: [claim], store });
6811
+ if (ingest.legacy.length > 0) {
6812
+ legacy.push(claim);
6813
+ continue;
6814
+ }
6815
+ const rejection = ingest.rejected[0];
6816
+ if (rejection) {
6817
+ rejected.push(rejection);
6818
+ continue;
6819
+ }
6820
+ const verified = ingest.verified[0];
6821
+ if (!verified || chain === void 0 || channelId === void 0) continue;
6822
+ const advance = verified.watermarkAdvance;
6823
+ const preimage = preimages?.take(claim.packetIndex);
6824
+ let outcome;
6825
+ try {
6826
+ outcome = await reveal(claim, preimage);
6827
+ } catch (err) {
6828
+ outcome = {
6829
+ decision: "withheld",
6830
+ reason: err instanceof Error ? err.message : String(err)
6831
+ };
6832
+ }
6833
+ if (outcome.decision === "revealed") {
6834
+ revealed.push({ claim, watermarkAdvance: advance });
6835
+ valueRevealed += advance;
6836
+ continue;
6837
+ }
6838
+ rollbackWatermark(store, chain, channelId, prior);
6839
+ rolledBack.push({
6840
+ claim,
6841
+ watermarkAdvance: advance,
6842
+ reason: outcome.reason ?? "reveal withheld"
6843
+ });
6844
+ }
6845
+ return { revealed, rolledBack, rejected, legacy, valueRevealed };
6846
+ }
6847
+
6312
6848
  // src/faucet.ts
6313
6849
  function defaultFaucetTimeout(chain) {
6314
6850
  return chain === "mina" ? 12e4 : 3e4;
@@ -6507,14 +7043,14 @@ function fromBase642(b64) {
6507
7043
  }
6508
7044
  return bytes;
6509
7045
  }
6510
- function hexToBytes(hex) {
7046
+ function hexToBytes2(hex) {
6511
7047
  const bytes = new Uint8Array(hex.length / 2);
6512
7048
  for (let i = 0; i < hex.length; i += 2) {
6513
7049
  bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
6514
7050
  }
6515
7051
  return bytes;
6516
7052
  }
6517
- function bytesToHex2(bytes) {
7053
+ function bytesToHex3(bytes) {
6518
7054
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
6519
7055
  }
6520
7056
 
@@ -6846,7 +7382,7 @@ var KeyManager = class {
6846
7382
  const mnemonic = generateMnemonic();
6847
7383
  const identity = await deriveFullIdentity(mnemonic);
6848
7384
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
6849
- const userIdBytes = hexToBytes(identity.nostr.pubkey);
7385
+ const userIdBytes = hexToBytes2(identity.nostr.pubkey);
6850
7386
  const registration = await registerPasskey({
6851
7387
  rpId: this.config.rpId,
6852
7388
  rpName: this.config.rpName,
@@ -6888,7 +7424,7 @@ var KeyManager = class {
6888
7424
  "Passkey did not return a userHandle. Cannot determine Nostr pubkey for recovery."
6889
7425
  );
6890
7426
  }
6891
- const pubkey = bytesToHex2(discovery.userHandle);
7427
+ const pubkey = bytesToHex3(discovery.userHandle);
6892
7428
  const vault = await fetchBackupFromRelays(pubkey, this.config.relayUrls);
6893
7429
  if (!vault) {
6894
7430
  throw new Error(
@@ -6926,7 +7462,7 @@ var KeyManager = class {
6926
7462
  }
6927
7463
  const identity = await deriveFullIdentity(mnemonic);
6928
7464
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
6929
- const userIdBytes = hexToBytes(identity.nostr.pubkey);
7465
+ const userIdBytes = hexToBytes2(identity.nostr.pubkey);
6930
7466
  const registration = await registerPasskey({
6931
7467
  rpId: this.config.rpId,
6932
7468
  rpName: this.config.rpName,
@@ -6957,7 +7493,7 @@ var KeyManager = class {
6957
7493
  const identity = deriveFromNsec(secretKey);
6958
7494
  if (isPrfSupported()) {
6959
7495
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
6960
- const userIdBytes = hexToBytes(identity.nostr.pubkey);
7496
+ const userIdBytes = hexToBytes2(identity.nostr.pubkey);
6961
7497
  try {
6962
7498
  const registration = await registerPasskey({
6963
7499
  rpId: this.config.rpId,
@@ -6968,7 +7504,7 @@ var KeyManager = class {
6968
7504
  });
6969
7505
  const kek = await deriveKek(registration.prfOutput);
6970
7506
  const credIdHash = await hashCredentialId(registration.credentialId);
6971
- const hexKey = bytesToHex2(secretKey);
7507
+ const hexKey = bytesToHex3(secretKey);
6972
7508
  this.vault = await createVault(hexKey, kek, credIdHash, prfSalt);
6973
7509
  this.activeCredentialIdHash = credIdHash;
6974
7510
  await this.saveToLocalStorage();
@@ -6987,7 +7523,7 @@ var KeyManager = class {
6987
7523
  throw new Error("No active identity \u2014 call create() or recover() first");
6988
7524
  }
6989
7525
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
6990
- const userIdBytes = hexToBytes(this.identity.nostr.pubkey);
7526
+ const userIdBytes = hexToBytes2(this.identity.nostr.pubkey);
6991
7527
  const registration = await registerPasskey({
6992
7528
  rpId: this.config.rpId,
6993
7529
  rpName: this.config.rpName,
@@ -7417,7 +7953,9 @@ function writeKeystoreFile(path, keystore) {
7417
7953
  }
7418
7954
  export {
7419
7955
  BtpRuntimeClient,
7956
+ CLAIM_TYPEHASH,
7420
7957
  CONDITION_LENGTH,
7958
+ COOP_CLOSE_TYPEHASH,
7421
7959
  ChannelFundingError,
7422
7960
  ChannelManager,
7423
7961
  ConnectorError,
@@ -7432,15 +7970,20 @@ export {
7432
7970
  ILP_CLAIM_HEADER,
7433
7971
  ILP_CLAIM_WRAPPED_HEADER,
7434
7972
  ILP_PEER_ID_HEADER,
7973
+ InMemoryPreimageRetentionStore,
7435
7974
  InMemoryReceivedClaimStore,
7436
7975
  JsonFileReceivedClaimStore,
7437
7976
  KeyManager,
7438
7977
  KindRegistry,
7439
7978
  MIME_A2UI,
7440
7979
  MIME_MCP_APP,
7980
+ MINA_CHANNEL_STATE,
7981
+ MinaSettlementError,
7441
7982
  MinaSigner,
7442
7983
  NetworkError,
7443
7984
  OnChainChannelClient,
7985
+ ROLLING_SWAP_DOMAIN_NAME,
7986
+ ROLLING_SWAP_DOMAIN_VERSION,
7444
7987
  RendererPinStore,
7445
7988
  SolanaSigner,
7446
7989
  ToonClient,
@@ -7454,12 +7997,14 @@ export {
7454
7997
  buildBackupEvent,
7455
7998
  buildBackupFilter,
7456
7999
  buildConsentRequest,
8000
+ buildMinaCoSignedClaim,
7457
8001
  buildRendererEventTemplate,
7458
8002
  buildSettlementInfo,
7459
8003
  buildStoreWriteEnvelope,
7460
8004
  buildSwapSettlements,
7461
8005
  buildUiCoordinate,
7462
8006
  classifyIntent,
8007
+ createO1jsMinaClaimSubmitter,
7463
8008
  decodeEvmSettlementTx,
7464
8009
  decryptMnemonic2 as decryptMnemonic,
7465
8010
  defaultFaucetTimeout,
@@ -7469,6 +8014,8 @@ export {
7469
8014
  deterministicGenerator,
7470
8015
  encryptMnemonic2 as encryptMnemonic,
7471
8016
  entryToAccumulatedClaim,
8017
+ evmClaimDigest,
8018
+ evmCooperativeCloseDigest,
7472
8019
  extractArweaveTxId,
7473
8020
  extractUiResource,
7474
8021
  fulfillmentMatchesCondition,
@@ -7482,6 +8029,7 @@ export {
7482
8029
  hasSettlementMetadata,
7483
8030
  httpEndpointToBtpUrl,
7484
8031
  importKeystore,
8032
+ ingestAndReveal,
7485
8033
  ingestReceivedClaims,
7486
8034
  isInsufficientGasError,
7487
8035
  isPrfSupported,
@@ -7503,10 +8051,12 @@ export {
7503
8051
  readEvmNativeBalance,
7504
8052
  readEvmTokenBalance,
7505
8053
  readMinaBalance,
8054
+ readMinaChannelState,
7506
8055
  readMinaDepositTotal,
7507
8056
  readSolanaNativeBalance,
7508
8057
  readSolanaTokenBalance,
7509
8058
  readWalletBalances,
8059
+ recoverEvmClaimSigner,
7510
8060
  renderDeterministicHtml,
7511
8061
  renderDispatch,
7512
8062
  requestBlobStorage,
@@ -7517,8 +8067,10 @@ export {
7517
8067
  selectLatestAddressable,
7518
8068
  serializeHttpRequest,
7519
8069
  submitEvmSettlement,
8070
+ submitMinaSettlement,
7520
8071
  validateConfig,
7521
8072
  validateMnemonic,
8073
+ verifyEvmClaimSignature,
7522
8074
  verifyRendererTrust,
7523
8075
  withRetry,
7524
8076
  writeKeystoreFile