@toon-protocol/client-mcp 0.18.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.
@@ -13533,7 +13533,14 @@ async function buildMinaPaymentChannelProof(params) {
13533
13533
  signerPublicKey
13534
13534
  };
13535
13535
  }
13536
- var DEPOSIT_TOTAL_STATE_INDEX = 4;
13536
+ var CHANNEL_STATE_INDEX = {
13537
+ channelHash: 0,
13538
+ balanceCommitment: 1,
13539
+ nonceField: 2,
13540
+ channelState: 3,
13541
+ depositTotal: 4
13542
+ };
13543
+ var DEPOSIT_TOTAL_STATE_INDEX = CHANNEL_STATE_INDEX.depositTotal;
13537
13544
  async function readMinaDepositTotal(graphqlUrl, zkAppAddress, fetchImpl = fetch) {
13538
13545
  const query = "query($pk:String!){account(publicKey:$pk){zkappState}}";
13539
13546
  const res = await fetchImpl(graphqlUrl, {
@@ -13558,6 +13565,42 @@ async function readMinaDepositTotal(graphqlUrl, zkAppAddress, fetchImpl = fetch)
13558
13565
  }
13559
13566
  return BigInt(state[DEPOSIT_TOTAL_STATE_INDEX]);
13560
13567
  }
13568
+ var MINA_CHANNEL_STATE = {
13569
+ UNINITIALIZED: 0,
13570
+ OPEN: 1,
13571
+ CLOSING: 2,
13572
+ SETTLED: 3
13573
+ };
13574
+ async function readMinaChannelState(graphqlUrl, zkAppAddress, fetchImpl = fetch) {
13575
+ const query = "query($pk:String!){account(publicKey:$pk){zkappState}}";
13576
+ const res = await fetchImpl(graphqlUrl, {
13577
+ method: "POST",
13578
+ headers: { "content-type": "application/json" },
13579
+ body: JSON.stringify({ query, variables: { pk: zkAppAddress } })
13580
+ });
13581
+ if (!res.ok) {
13582
+ throw new Error(`Mina GraphQL request failed: HTTP ${res.status}`);
13583
+ }
13584
+ const json = await res.json();
13585
+ if (json.errors && json.errors.length > 0) {
13586
+ throw new Error(
13587
+ `Mina GraphQL error: ${json.errors[0]?.message ?? "unknown"}`
13588
+ );
13589
+ }
13590
+ const state = json.data?.account?.zkappState;
13591
+ if (!state || state.length <= DEPOSIT_TOTAL_STATE_INDEX) {
13592
+ throw new Error(
13593
+ `Mina zkApp ${zkAppAddress} has no readable zkappState (account not found or not a zkApp)`
13594
+ );
13595
+ }
13596
+ return {
13597
+ channelHash: String(state[CHANNEL_STATE_INDEX.channelHash]),
13598
+ balanceCommitment: String(state[CHANNEL_STATE_INDEX.balanceCommitment]),
13599
+ nonceField: BigInt(state[CHANNEL_STATE_INDEX.nonceField]),
13600
+ channelState: Number(state[CHANNEL_STATE_INDEX.channelState]),
13601
+ depositTotal: BigInt(state[CHANNEL_STATE_INDEX.depositTotal])
13602
+ };
13603
+ }
13561
13604
  var DEFAULT_MINA_TOKEN_ID = "MINA";
13562
13605
  var MINA_CLAIM_NETWORK = "devnet";
13563
13606
  function deriveMinaSalt(zkAppAddress, nonce) {
@@ -14827,6 +14870,261 @@ async function submitEvmSettlement(bundle, params) {
14827
14870
  return { txHash };
14828
14871
  }
14829
14872
  }
14873
+ var MinaSettlementError = class extends Error {
14874
+ code;
14875
+ constructor(code, message) {
14876
+ super(`${code}: ${message}`);
14877
+ this.name = "MinaSettlementError";
14878
+ this.code = code;
14879
+ }
14880
+ };
14881
+ function parseProofSignature(proofJson) {
14882
+ const parsed = JSON.parse(proofJson);
14883
+ const r = parsed.signature?.r;
14884
+ const s = parsed.signature?.s;
14885
+ if (typeof r !== "string" || typeof s !== "string") {
14886
+ throw new Error("recipient co-signature proof did not carry string r/s");
14887
+ }
14888
+ return { r, s };
14889
+ }
14890
+ async function buildMinaCoSignedClaim(inputs) {
14891
+ const channelNonce = inputs.channelNonce ?? 0n;
14892
+ const salt = inputs.saltOverride ?? deriveMinaSalt(inputs.channelId, Number(inputs.nonce));
14893
+ if (inputs.cumulativeAmount > inputs.depositTotal) {
14894
+ throw new MinaSettlementError(
14895
+ "CUMULATIVE_EXCEEDS_DEPOSIT",
14896
+ `claim cumulativeAmount (${inputs.cumulativeAmount}) exceeds the channel depositTotal (${inputs.depositTotal}) \u2014 cannot conserve balances`
14897
+ );
14898
+ }
14899
+ const { Poseidon, PublicKey } = await loadMinaPaymentChannelBindings();
14900
+ const hashRecipientFirst = minaParticipantChannelHashField(
14901
+ Poseidon,
14902
+ PublicKey,
14903
+ inputs.recipient,
14904
+ inputs.swapSignerAddress,
14905
+ channelNonce
14906
+ ).toString();
14907
+ const hashMakerFirst = minaParticipantChannelHashField(
14908
+ Poseidon,
14909
+ PublicKey,
14910
+ inputs.swapSignerAddress,
14911
+ inputs.recipient,
14912
+ channelNonce
14913
+ ).toString();
14914
+ let participantA;
14915
+ let participantB;
14916
+ let recipientRole;
14917
+ if (hashRecipientFirst === inputs.onChainChannelHash) {
14918
+ participantA = inputs.recipient;
14919
+ participantB = inputs.swapSignerAddress;
14920
+ recipientRole = "A";
14921
+ } else if (hashMakerFirst === inputs.onChainChannelHash) {
14922
+ participantA = inputs.swapSignerAddress;
14923
+ participantB = inputs.recipient;
14924
+ recipientRole = "B";
14925
+ } else {
14926
+ throw new MinaSettlementError(
14927
+ "CHANNEL_HASH_MISMATCH",
14928
+ `neither ordering of recipient ${inputs.recipient} / maker ${inputs.swapSignerAddress} reproduces the on-chain channelHash ${inputs.onChainChannelHash} (channelNonce ${channelNonce})`
14929
+ );
14930
+ }
14931
+ const recipientBalance = inputs.cumulativeAmount;
14932
+ const makerBalance = inputs.depositTotal - inputs.cumulativeAmount;
14933
+ const balanceA = recipientRole === "A" ? recipientBalance : makerBalance;
14934
+ const balanceB = inputs.depositTotal - balanceA;
14935
+ const balanceCommitment = minaBalanceCommitment(
14936
+ Poseidon,
14937
+ balanceA,
14938
+ balanceB,
14939
+ salt
14940
+ ).toString();
14941
+ const recipientPrivateKeyBase58 = hexToMinaBase58PrivateKey(
14942
+ inputs.recipientPrivateKey
14943
+ );
14944
+ const built = await buildMinaPaymentChannelProof({
14945
+ zkAppAddress: inputs.channelId,
14946
+ minaPrivateKeyBase58: recipientPrivateKeyBase58,
14947
+ signerPublicKey: inputs.recipient,
14948
+ balanceA,
14949
+ balanceB,
14950
+ salt,
14951
+ nonce: inputs.nonce,
14952
+ participantA,
14953
+ participantB,
14954
+ channelNonce,
14955
+ proofEncoding: "json"
14956
+ });
14957
+ if (built.balanceCommitment !== balanceCommitment) {
14958
+ throw new Error(
14959
+ `co-sign commitment drift: builder ${built.balanceCommitment} != ${balanceCommitment}`
14960
+ );
14961
+ }
14962
+ const recipientSignature = parseProofSignature(built.proof);
14963
+ const signatureA = recipientRole === "A" ? recipientSignature : inputs.makerSignature;
14964
+ const signatureB = recipientRole === "B" ? recipientSignature : inputs.makerSignature;
14965
+ return {
14966
+ channelId: inputs.channelId,
14967
+ balanceA,
14968
+ balanceB,
14969
+ salt,
14970
+ nonce: inputs.nonce,
14971
+ channelNonce,
14972
+ balanceCommitment,
14973
+ participantA,
14974
+ participantB,
14975
+ recipientRole,
14976
+ recipientSignature,
14977
+ ...signatureA ? { signatureA } : {},
14978
+ ...signatureB ? { signatureB } : {},
14979
+ makerSignatureMissing: inputs.makerSignature === void 0
14980
+ };
14981
+ }
14982
+ async function submitMinaSettlement(bundle, context) {
14983
+ if (bundle.chainKind !== "mina") {
14984
+ throw new MinaSettlementError(
14985
+ "NOT_MINA_BUNDLE",
14986
+ `submitMinaSettlement only settles mina bundles (got ${bundle.chainKind} for ${bundle.chain})`
14987
+ );
14988
+ }
14989
+ if (!context.graphqlUrl) {
14990
+ throw new MinaSettlementError(
14991
+ "NO_GRAPHQL_CONFIGURED",
14992
+ `no Mina graphqlUrl configured for "${bundle.chain}" \u2014 set minaChannel.graphqlUrl to enable receive-side settlement.`
14993
+ );
14994
+ }
14995
+ const graphqlUrl = context.graphqlUrl;
14996
+ const channelNonce = context.channelNonce ?? 0n;
14997
+ const nonce = BigInt(bundle.nonce);
14998
+ const cumulativeAmount = BigInt(bundle.cumulativeAmount);
14999
+ const read = context.reader ?? readMinaChannelState;
15000
+ const state = await read(graphqlUrl, bundle.channelId);
15001
+ if (state.channelState !== MINA_CHANNEL_STATE.OPEN) {
15002
+ throw new MinaSettlementError(
15003
+ "CHANNEL_NOT_OPEN",
15004
+ `channel ${bundle.channelId} is not OPEN (channelState=${state.channelState}); claimFromChannel only applies to an OPEN channel.`
15005
+ );
15006
+ }
15007
+ if (nonce <= state.nonceField) {
15008
+ throw new MinaSettlementError(
15009
+ "NONCE_NOT_ADVANCING",
15010
+ `claim nonce ${nonce} does not advance the on-chain nonceField ${state.nonceField} for ${bundle.channelId} (already claimed).`
15011
+ );
15012
+ }
15013
+ const claim = await buildMinaCoSignedClaim({
15014
+ channelId: bundle.channelId,
15015
+ nonce,
15016
+ cumulativeAmount,
15017
+ recipient: bundle.recipient,
15018
+ swapSignerAddress: bundle.swapSignerAddress,
15019
+ depositTotal: state.depositTotal,
15020
+ onChainChannelHash: state.channelHash,
15021
+ recipientPrivateKey: context.recipientPrivateKey,
15022
+ channelNonce,
15023
+ ...context.makerSignature ? { makerSignature: context.makerSignature } : {}
15024
+ });
15025
+ if (claim.makerSignatureMissing || !claim.signatureA || !claim.signatureB) {
15026
+ throw new MinaSettlementError(
15027
+ "MINA_MAKER_COSIGN_REQUIRED",
15028
+ `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}).`
15029
+ );
15030
+ }
15031
+ const submitter = context.submitter ?? createO1jsMinaClaimSubmitter();
15032
+ const { txHash } = await submitter.claimFromChannel({
15033
+ graphqlUrl,
15034
+ channelId: bundle.channelId,
15035
+ balanceA: claim.balanceA,
15036
+ balanceB: claim.balanceB,
15037
+ salt: claim.salt,
15038
+ nonce: claim.nonce,
15039
+ participantA: claim.participantA,
15040
+ participantB: claim.participantB,
15041
+ channelNonce: claim.channelNonce,
15042
+ signatureA: claim.signatureA,
15043
+ signatureB: claim.signatureB,
15044
+ feePayerPrivateKey: context.feePayerPrivateKey ?? context.recipientPrivateKey,
15045
+ ...context.txFeeNanomina !== void 0 ? { txFeeNanomina: context.txFeeNanomina } : {}
15046
+ });
15047
+ return { txHash };
15048
+ }
15049
+ var DEFAULT_MINA_TX_FEE_NANOMINA = 100000000n;
15050
+ function createO1jsMinaClaimSubmitter() {
15051
+ return {
15052
+ async claimFromChannel(args) {
15053
+ try {
15054
+ const o1js = await import("o1js");
15055
+ const {
15056
+ Mina,
15057
+ PrivateKey,
15058
+ PublicKey,
15059
+ Field: Field3,
15060
+ Poseidon,
15061
+ Signature,
15062
+ fetchAccount
15063
+ } = o1js;
15064
+ const zkAppMod = await import("@toon-protocol/mina-zkapp");
15065
+ const PaymentChannel = zkAppMod.PaymentChannel;
15066
+ Mina.setActiveInstance(Mina.Network(args.graphqlUrl));
15067
+ const feePayerKey = PrivateKey.fromBase58(
15068
+ hexToMinaBase58PrivateKey(args.feePayerPrivateKey)
15069
+ );
15070
+ const feePayerPub = feePayerKey.toPublicKey();
15071
+ await PaymentChannel.compile();
15072
+ const zkAppPub = PublicKey.fromBase58(args.channelId);
15073
+ await fetchAccount({ publicKey: zkAppPub });
15074
+ await fetchAccount({ publicKey: feePayerPub });
15075
+ const zkApp = new PaymentChannel(zkAppPub);
15076
+ const balA = Field3(args.balanceA);
15077
+ const balB = Field3(args.balanceB);
15078
+ const saltField = Field3(args.salt);
15079
+ const newNonce = Field3(args.nonce);
15080
+ const channelNonce = Field3(args.channelNonce);
15081
+ const newBalanceCommitment = Poseidon.hash([balA, balB, saltField]);
15082
+ const sigA = Signature.fromJSON({
15083
+ r: args.signatureA.r,
15084
+ s: args.signatureA.s
15085
+ });
15086
+ const sigB = Signature.fromJSON({
15087
+ r: args.signatureB.r,
15088
+ s: args.signatureB.s
15089
+ });
15090
+ const partA = PublicKey.fromBase58(args.participantA);
15091
+ const partB = PublicKey.fromBase58(args.participantB);
15092
+ const fee = (args.txFeeNanomina ?? DEFAULT_MINA_TX_FEE_NANOMINA).toString();
15093
+ const txn = await Mina.transaction(
15094
+ { sender: feePayerPub, fee },
15095
+ async () => {
15096
+ await zkApp.claimFromChannel(
15097
+ balA,
15098
+ balB,
15099
+ saltField,
15100
+ sigA,
15101
+ sigB,
15102
+ partA,
15103
+ partB,
15104
+ channelNonce,
15105
+ newBalanceCommitment,
15106
+ newNonce
15107
+ );
15108
+ }
15109
+ );
15110
+ await txn.prove();
15111
+ const sent = await txn.sign([feePayerKey]).send();
15112
+ return { txHash: sent.hash ?? "" };
15113
+ } catch (err) {
15114
+ throw new MinaSettlementError(
15115
+ "PROVING_FAILED",
15116
+ err instanceof Error ? err.message : String(err)
15117
+ );
15118
+ }
15119
+ }
15120
+ };
15121
+ }
15122
+ function parseMakerMinaSignature(raw) {
15123
+ if (raw && typeof raw.r === "string" && raw.r.length > 0 && typeof raw.s === "string" && raw.s.length > 0) {
15124
+ return { r: raw.r, s: raw.s };
15125
+ }
15126
+ return void 0;
15127
+ }
14830
15128
  var ToonClient = class {
14831
15129
  config;
14832
15130
  state = null;
@@ -15111,34 +15409,11 @@ var ToonClient = class {
15111
15409
  const writeData = buildStoreWriteEnvelope(event, options?.proxyPath);
15112
15410
  const destination = options?.destination ?? this.config.destinationAddress;
15113
15411
  const transport = this.getClaimTransport();
15114
- let claimMessage;
15115
- if (options?.claim) {
15116
- claimMessage = this.buildClaimMessageForProof(options.claim);
15117
- } else if (this.channelManager) {
15118
- const peerId = this.resolvePeerId(destination);
15119
- const negotiation = this.peerNegotiations.get(peerId);
15120
- if (!negotiation) {
15121
- throw new ToonClientError(
15122
- `No negotiation metadata for peer "${peerId}" \u2014 was bootstrap completed?`,
15123
- "PEER_NOT_NEGOTIATED"
15124
- );
15125
- }
15126
- const channelId = await this.channelManager.ensureChannel(
15127
- peerId,
15128
- negotiation
15129
- );
15130
- const proof = await this.channelManager.signBalanceProof(
15131
- channelId,
15132
- BigInt(amount)
15133
- );
15134
- const signer = this.channelManager.getSignerForChannel(channelId);
15135
- claimMessage = signer.buildClaimMessage(proof, this.getPublicKey());
15136
- } else {
15137
- throw new ToonClientError(
15138
- "No claim provided and no channel manager configured",
15139
- "MISSING_CLAIM"
15140
- );
15141
- }
15412
+ const claimMessage = await this.resolveClaimForDestination(
15413
+ destination,
15414
+ BigInt(amount),
15415
+ options?.claim
15416
+ );
15142
15417
  const response = await transport.sendIlpPacketWithClaim(
15143
15418
  {
15144
15419
  destination,
@@ -15345,8 +15620,6 @@ var ToonClient = class {
15345
15620
  }
15346
15621
  /**
15347
15622
  * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
15348
- * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
15349
- * to call this helper. Kept duplicated for now to minimize regression risk.
15350
15623
  */
15351
15624
  async resolveClaimForDestination(destination, amount, explicitClaim) {
15352
15625
  if (explicitClaim) {
@@ -15555,6 +15828,22 @@ var ToonClient = class {
15555
15828
  * swap/settle-received-claims.ts module doc).
15556
15829
  */
15557
15830
  async settleSwapBundle(bundle) {
15831
+ if (bundle.chainKind === "mina") {
15832
+ if (!this.minaPrivateKey) {
15833
+ throw new Error(
15834
+ "Mina signer not configured (no mnemonic / mina-signer) \u2014 cannot co-sign the receive-side claim."
15835
+ );
15836
+ }
15837
+ const makerSignature = parseMakerMinaSignature(
15838
+ this.config.swapMinaMakerSignatures?.[bundle.channelId]
15839
+ );
15840
+ const { txHash } = await submitMinaSettlement(bundle, {
15841
+ recipientPrivateKey: this.minaPrivateKey,
15842
+ ...this.config.minaChannel?.graphqlUrl ? { graphqlUrl: this.config.minaChannel.graphqlUrl } : {},
15843
+ ...makerSignature ? { makerSignature } : {}
15844
+ });
15845
+ return { txHash };
15846
+ }
15558
15847
  if (bundle.chainKind !== "evm") {
15559
15848
  throw new Error(
15560
15849
  `Swap settlement submission for ${bundle.chainKind} (${bundle.chain}) is not wired yet \u2014 EVM only today.`
@@ -16085,6 +16374,92 @@ function ingestReceivedClaims(params) {
16085
16374
  }
16086
16375
  return { verified, rejected, legacy, valueReceived };
16087
16376
  }
16377
+ function copy(entry) {
16378
+ return {
16379
+ packetIndex: entry.packetIndex,
16380
+ preimage: Uint8Array.from(entry.preimage),
16381
+ condition: Uint8Array.from(entry.condition),
16382
+ retainedAt: entry.retainedAt
16383
+ };
16384
+ }
16385
+ var InMemoryPreimageRetentionStore = class {
16386
+ entries = /* @__PURE__ */ new Map();
16387
+ retain(entry) {
16388
+ this.entries.set(entry.packetIndex, copy(entry));
16389
+ }
16390
+ get(packetIndex) {
16391
+ const entry = this.entries.get(packetIndex);
16392
+ return entry ? copy(entry) : void 0;
16393
+ }
16394
+ take(packetIndex) {
16395
+ const entry = this.entries.get(packetIndex);
16396
+ if (!entry) return void 0;
16397
+ this.entries.delete(packetIndex);
16398
+ return copy(entry);
16399
+ }
16400
+ size() {
16401
+ return this.entries.size;
16402
+ }
16403
+ clear() {
16404
+ this.entries.clear();
16405
+ }
16406
+ };
16407
+ function rollbackWatermark(store, chain, channelId, prior) {
16408
+ if (prior === void 0) {
16409
+ store.delete(chain, channelId);
16410
+ } else {
16411
+ store.save(prior);
16412
+ }
16413
+ }
16414
+ async function ingestAndReveal(params) {
16415
+ const { reveal, preimages, claims, store, ...shared } = params;
16416
+ const revealed = [];
16417
+ const rolledBack = [];
16418
+ const rejected = [];
16419
+ const legacy = [];
16420
+ let valueRevealed = 0n;
16421
+ for (const claim of claims) {
16422
+ const hasMeta = hasSettlementMetadata(claim);
16423
+ const chain = hasMeta ? claim.pair.to.chain : void 0;
16424
+ const channelId = hasMeta ? claim.channelId : void 0;
16425
+ const prior = chain !== void 0 && channelId !== void 0 ? store.load(chain, channelId) : void 0;
16426
+ const ingest = ingestReceivedClaims({ ...shared, claims: [claim], store });
16427
+ if (ingest.legacy.length > 0) {
16428
+ legacy.push(claim);
16429
+ continue;
16430
+ }
16431
+ const rejection = ingest.rejected[0];
16432
+ if (rejection) {
16433
+ rejected.push(rejection);
16434
+ continue;
16435
+ }
16436
+ const verified = ingest.verified[0];
16437
+ if (!verified || chain === void 0 || channelId === void 0) continue;
16438
+ const advance = verified.watermarkAdvance;
16439
+ const preimage = preimages?.take(claim.packetIndex);
16440
+ let outcome;
16441
+ try {
16442
+ outcome = await reveal(claim, preimage);
16443
+ } catch (err) {
16444
+ outcome = {
16445
+ decision: "withheld",
16446
+ reason: err instanceof Error ? err.message : String(err)
16447
+ };
16448
+ }
16449
+ if (outcome.decision === "revealed") {
16450
+ revealed.push({ claim, watermarkAdvance: advance });
16451
+ valueRevealed += advance;
16452
+ continue;
16453
+ }
16454
+ rollbackWatermark(store, chain, channelId, prior);
16455
+ rolledBack.push({
16456
+ claim,
16457
+ watermarkAdvance: advance,
16458
+ reason: outcome.reason ?? "reveal withheld"
16459
+ });
16460
+ }
16461
+ return { revealed, rolledBack, rejected, legacy, valueRevealed };
16462
+ }
16088
16463
  function defaultFaucetTimeout(chain) {
16089
16464
  return chain === "mina" ? 12e4 : 3e4;
16090
16465
  }
@@ -16804,7 +17179,8 @@ export {
16804
17179
  ToonClient,
16805
17180
  JsonFileReceivedClaimStore,
16806
17181
  InMemoryReceivedClaimStore,
16807
- ingestReceivedClaims,
17182
+ InMemoryPreimageRetentionStore,
17183
+ ingestAndReveal,
16808
17184
  fundWallet,
16809
17185
  generateKeystore,
16810
17186
  ARWEAVE_GATEWAYS,
@@ -16844,4 +17220,4 @@ export {
16844
17220
  @scure/bip32/lib/esm/index.js:
16845
17221
  (*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) *)
16846
17222
  */
16847
- //# sourceMappingURL=chunk-LSPAGZXL.js.map
17223
+ //# sourceMappingURL=chunk-OGMD7Z2F.js.map