@toon-protocol/client 0.21.0 → 0.21.1

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.
@@ -55,6 +55,72 @@ async function getCompiledPaymentChannel() {
55
55
  function getCompiledVerificationKeyHash() {
56
56
  return compiledVerificationKeyHash;
57
57
  }
58
+ function abandonLeakedMinaTransaction(o1js) {
59
+ const ctx = o1js.Mina.currentTransaction;
60
+ const c = ctx;
61
+ if (!c || typeof c.has !== "function" || typeof c.leave !== "function")
62
+ return;
63
+ for (let i = 0; i < 64 && c.has(); i += 1) {
64
+ try {
65
+ const id = typeof c.id === "function" ? c.id() : void 0;
66
+ c.leave(id);
67
+ } catch {
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ async function buildMinaTransaction(o1js, feePayer, f) {
73
+ try {
74
+ return await o1js.Mina.transaction(feePayer, f);
75
+ } catch (err) {
76
+ abandonLeakedMinaTransaction(o1js);
77
+ throw err;
78
+ }
79
+ }
80
+ var MINA_ACCOUNT_CREATION_FEE_NANOMINA = 1000000000n;
81
+ var MinaFeePayerUnfundedError = class extends Error {
82
+ constructor(address, requiredNanomina, haveNanomina, graphqlUrl) {
83
+ const mina = (n) => `${(Number(n) / 1e9).toFixed(3)} MINA`;
84
+ const state = haveNanomina === void 0 ? `does not exist on-chain (0 MINA)` : `holds only ${mina(haveNanomina)}`;
85
+ super(
86
+ `Mina fee-payer wallet ${address} ${state} but needs ~${mina(
87
+ requiredNanomina
88
+ )} to open a payment channel (\u22481 MINA account-creation fee + tx fees) on ${graphqlUrl}. Fund ${address} and retry \u2014 no circuit was compiled (this check runs before the ~1-3 min compile).`
89
+ );
90
+ this.address = address;
91
+ this.requiredNanomina = requiredNanomina;
92
+ this.haveNanomina = haveNanomina;
93
+ this.graphqlUrl = graphqlUrl;
94
+ this.name = "MinaFeePayerUnfundedError";
95
+ }
96
+ address;
97
+ requiredNanomina;
98
+ haveNanomina;
99
+ graphqlUrl;
100
+ };
101
+ async function assertMinaFeePayerFunded(params) {
102
+ const { o1js, payerPublicKey, requiredNanomina, graphqlUrl } = params;
103
+ const address = payerPublicKey.toBase58();
104
+ const res = await o1js.fetchAccount({ publicKey: payerPublicKey });
105
+ if (res.error || !res.account) {
106
+ throw new MinaFeePayerUnfundedError(
107
+ address,
108
+ requiredNanomina,
109
+ void 0,
110
+ graphqlUrl
111
+ );
112
+ }
113
+ const raw = res.account.balance?.toString();
114
+ const have = raw !== void 0 ? BigInt(raw) : 0n;
115
+ if (have < requiredNanomina) {
116
+ throw new MinaFeePayerUnfundedError(
117
+ address,
118
+ requiredNanomina,
119
+ have,
120
+ graphqlUrl
121
+ );
122
+ }
123
+ }
58
124
  var MINA_CHANNEL_STATE_OPEN = 1n;
59
125
  var MINA_CHANNEL_STATE_UNINITIALIZED = 0n;
60
126
  async function openMinaChannelOnChain(params) {
@@ -113,7 +179,8 @@ async function openMinaChannelOnChain(params) {
113
179
  const tokenIdField = Field(params.tokenId ?? "1");
114
180
  await fetchAccount({ publicKey: zkAppPublicKey });
115
181
  await fetchAccount({ publicKey: payerPublicKey });
116
- const initTx = await Mina.transaction(
182
+ const initTx = await buildMinaTransaction(
183
+ await getO1js(),
117
184
  { sender: payerPublicKey, fee: Number(txFee) },
118
185
  async () => {
119
186
  await channel.initializeChannel(
@@ -142,7 +209,8 @@ async function openMinaChannelOnChain(params) {
142
209
  const channel = await getZkApp();
143
210
  await fetchAccount({ publicKey: zkAppPublicKey });
144
211
  const amountField = Field(params.deposit.amount.toString());
145
- const depositTx = await Mina.transaction(
212
+ const depositTx = await buildMinaTransaction(
213
+ await getO1js(),
146
214
  { sender: payerPublicKey, fee: Number(txFee) },
147
215
  async () => {
148
216
  await channel.deposit(amountField, payerPublicKey);
@@ -186,31 +254,48 @@ var APPSTATE_CHANNEL_HASH = 0;
186
254
  var APPSTATE_CHANNEL_STATE = 3;
187
255
  var STATE_UNINITIALIZED = 0n;
188
256
  var STATE_OPEN = 1n;
257
+ function requiredDeployBalanceNanomina(feeNanomina) {
258
+ return MINA_ACCOUNT_CREATION_FEE_NANOMINA + feeNanomina * 2n;
259
+ }
189
260
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
190
261
  async function deployMinaChannelZkApp(params) {
191
262
  const { o1js } = await loadMinaRuntime();
192
263
  const { Mina, PrivateKey, AccountUpdate, fetchAccount } = o1js;
193
- const progress = params.onProgress ?? (() => {
194
- });
264
+ const progress = params.onProgress ?? (() => void 0);
195
265
  const network = Mina.Network(params.graphqlUrl);
196
266
  Mina.setActiveInstance(network);
197
267
  const payerKeyBase58 = hexToMinaBase58PrivateKey2(params.payerPrivateKey);
198
268
  const payerPrivateKey = PrivateKey.fromBase58(payerKeyBase58);
199
269
  const payerPublicKey = payerPrivateKey.toPublicKey();
270
+ const feeNanomina = params.feeNanomina ?? 100000000n;
271
+ await assertMinaFeePayerFunded({
272
+ o1js,
273
+ payerPublicKey,
274
+ requiredNanomina: requiredDeployBalanceNanomina(feeNanomina),
275
+ graphqlUrl: params.graphqlUrl
276
+ });
277
+ const zkAppPrivateKey = params.zkAppPrivateKey ? PrivateKey.fromBase58(params.zkAppPrivateKey) : PrivateKey.random();
278
+ const zkAppPublicKey = zkAppPrivateKey.toPublicKey();
279
+ const zkAppAddress = zkAppPublicKey.toBase58();
280
+ if (!params.zkAppPrivateKey) {
281
+ await params.onDeploying?.({
282
+ zkAppAddress,
283
+ zkAppPrivateKey: zkAppPrivateKey.toBase58(),
284
+ feePayer: payerPublicKey.toBase58()
285
+ });
286
+ }
200
287
  progress(
201
288
  "compiling the PaymentChannel circuit (one-time, can take 1-3 minutes)\u2026"
202
289
  );
203
290
  const PaymentChannel = await getCompiledPaymentChannel();
204
- const zkAppPrivateKey = PrivateKey.random();
205
- const zkAppPublicKey = zkAppPrivateKey.toPublicKey();
206
- const zkAppAddress = zkAppPublicKey.toBase58();
207
291
  const zkApp = new PaymentChannel(zkAppPublicKey);
208
292
  await fetchAccount({ publicKey: payerPublicKey });
209
293
  progress(
210
294
  `deploying a dedicated PaymentChannel zkApp ${zkAppAddress} (costs the 1 MINA account-creation fee + tx fee)\u2026`
211
295
  );
212
- const fee = Number(params.feeNanomina ?? 100000000n);
213
- const deployTx = await Mina.transaction(
296
+ const fee = Number(feeNanomina);
297
+ const deployTx = await buildMinaTransaction(
298
+ o1js,
214
299
  { sender: payerPublicKey, fee },
215
300
  async () => {
216
301
  AccountUpdate.fundNewAccount(payerPublicKey);
@@ -252,8 +337,7 @@ async function deployMinaChannelZkApp(params) {
252
337
  async function ensureOwnedMinaZkApp(params) {
253
338
  const { o1js } = await loadMinaRuntime();
254
339
  const { Mina, PrivateKey, PublicKey, Field, Poseidon, fetchAccount } = o1js;
255
- const progress = params.onProgress ?? (() => {
256
- });
340
+ const progress = params.onProgress ?? (() => void 0);
257
341
  const network = Mina.Network(params.graphqlUrl);
258
342
  Mina.setActiveInstance(network);
259
343
  const payerKeyBase58 = hexToMinaBase58PrivateKey2(params.payerPrivateKey);
@@ -279,13 +363,19 @@ async function ensureOwnedMinaZkApp(params) {
279
363
  ownRecord: false
280
364
  });
281
365
  }
366
+ let pendingOwnKey;
282
367
  for (const candidate of candidates) {
283
368
  let appState;
284
369
  try {
285
370
  const res = await fetchAccount({
286
371
  publicKey: PublicKey.fromBase58(candidate.address)
287
372
  });
288
- if (res.error || !res.account) continue;
373
+ if (res.error || !res.account) {
374
+ if (candidate.ownRecord && params.deployed?.zkAppPrivateKey) {
375
+ pendingOwnKey = params.deployed.zkAppPrivateKey;
376
+ }
377
+ continue;
378
+ }
289
379
  appState = res.account.zkapp?.appState;
290
380
  } catch {
291
381
  continue;
@@ -305,7 +395,11 @@ async function ensureOwnedMinaZkApp(params) {
305
395
  return { zkAppAddress: candidate.address, deployed: false };
306
396
  }
307
397
  }
308
- const record = await deployMinaChannelZkApp(params);
398
+ const record = await deployMinaChannelZkApp({
399
+ ...params,
400
+ ...pendingOwnKey ? { zkAppPrivateKey: pendingOwnKey } : {},
401
+ ...params.onDeploying ? { onDeploying: params.onDeploying } : {}
402
+ });
309
403
  await params.onDeployed?.(record);
310
404
  return { zkAppAddress: record.zkAppAddress, deployed: true, record };
311
405
  }
@@ -315,4 +409,4 @@ export {
315
409
  deployMinaChannelZkApp,
316
410
  ensureOwnedMinaZkApp
317
411
  };
318
- //# sourceMappingURL=chunk-OM3MHJHC.js.map
412
+ //# sourceMappingURL=chunk-7TDAU43L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/channel/mina-channel-deploy.ts","../src/channel/mina-channel-open.ts"],"sourcesContent":["/**\n * Per-pair Mina `PaymentChannel` zkApp deployment — the missing zero-config\n * piece of the Mina channel-open path.\n *\n * The `PaymentChannel` zkApp is SINGLE-PAIR: `initializeChannel` bakes\n * `channelHash = Poseidon([participantA.x, participantB.x, nonce])` into the\n * zkApp's on-chain state, and the zkApp address IS the channel id. One\n * deployment therefore serves exactly one client↔connector pair — a fresh\n * client cannot reuse the pair-bound zkApp another identity opened (its claim\n * would fail `mina_claim_verification_failed`: wrong on-chain channelHash).\n *\n * {@link ensureOwnedMinaZkApp} makes the open path self-sufficient:\n *\n * 1. Check the candidates (a previously recorded own deployment first, then\n * the announce/preset-resolved address) for a zkApp that is ALREADY OURS\n * — on-chain OPEN with channelHash == Poseidon([client.x, peer.x, 0]),\n * or our own recorded deployment still awaiting initialization.\n * 2. Otherwise deploy a FRESH zkApp (new random zkApp key) from the same\n * npm `@toon-protocol/mina-zkapp` + o1js build the claim verifier uses —\n * the deployed verification key is the locally compiled one BY\n * CONSTRUCTION, so the \"vk drift\" class of failure cannot occur.\n *\n * The connector accepts claims against ANY zkApp it can read on-chain (its\n * inbound verify resolves the claim's `zkAppAddress` on-chain and registers\n * unknown channels dynamically), so no connector-side registration step is\n * needed.\n *\n * Deploy and initialize are SEPARATE transactions (connector Issue #128:\n * `initializeChannel`'s `getAndRequireEquals` precondition fails while the\n * account does not exist yet) — this module only DEPLOYS; the normal\n * {@link openMinaChannelOnChain} initialize+deposit flow runs afterwards.\n *\n * Reuses the `createRequire`-anchored o1js loader from `mina-channel-open.ts`\n * (one shared CJS o1js instance — the ESM/CJS split instance bug) and stays\n * lazy so npm consumers who never touch Mina never load the WASM runtime.\n *\n * @module\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\nimport {\n MINA_ACCOUNT_CREATION_FEE_NANOMINA,\n assertMinaFeePayerFunded,\n buildMinaTransaction,\n getCompiledPaymentChannel,\n getCompiledVerificationKeyHash,\n loadMinaRuntime,\n} from './mina-channel-open.js';\n\n/** PaymentChannel appState indices (see mina-channel-open.ts). */\nconst APPSTATE_CHANNEL_HASH = 0;\nconst APPSTATE_CHANNEL_STATE = 3;\n/** CHANNEL_STATE values. */\nconst STATE_UNINITIALIZED = 0n;\nconst STATE_OPEN = 1n;\n\n/** Everything worth persisting about an auto-deployed zkApp. */\nexport interface MinaZkAppDeployRecord {\n /** The deployed zkApp B62 address (== the channel id). */\n zkAppAddress: string;\n /** The zkApp's `EK…` base58 private key (needed to co-sign future txs). */\n zkAppPrivateKey: string;\n /** The fee payer (client Mina B62) that funded the deployment. */\n feePayer: string;\n /** Deploy tx hash, when the send surfaced one. */\n deployTxHash?: string;\n /** Verification-key hash of the compiled contract that was deployed. */\n vkHash?: string;\n}\n\nexport interface DeployMinaZkAppParams {\n /** Mina GraphQL endpoint to deploy through. */\n graphqlUrl: string;\n /** Fee payer private key (hex scalar or `EK…` base58 — same as the opener). */\n payerPrivateKey: string;\n /**\n * Deploy fee in nanomina. Default 100_000_000 (0.1 MINA); the new zkApp\n * account additionally costs the protocol's 1 MINA account-creation fee,\n * charged to the payer via `AccountUpdate.fundNewAccount`.\n */\n feeNanomina?: bigint;\n /**\n * Reuse THIS zkApp key (`EK…` base58) instead of generating a fresh one.\n * Used to re-attempt a deployment whose key was already recorded (so a\n * crashed/pending first attempt does not orphan a NEW ~1.1-MINA zkApp on\n * every retry — the SAME address is redeployed).\n */\n zkAppPrivateKey?: string;\n /**\n * Called with the zkApp address + key IMMEDIATELY after the key is known and\n * the fee-payer preflight passes — BEFORE the circuit compiles or the deploy\n * tx is sent. The rig store persists this so a crash between send and\n * confirmation reuses the SAME zkApp next run rather than deploying (and\n * paying for) a second one. Fired only for a freshly-generated key (a\n * redeploy of a recorded key is already persisted).\n */\n onDeploying?: (record: {\n zkAppAddress: string;\n zkAppPrivateKey: string;\n feePayer: string;\n }) => void | Promise<void>;\n /** Progress lines (compile/deploy/inclusion phases take minutes). */\n onProgress?: (line: string) => void;\n /** Inclusion poll interval ms (default 15_000; tests shrink it). */\n pollIntervalMs?: number;\n /** Inclusion poll budget ms (default 540_000 ≈ 9 min). */\n pollTimeoutMs?: number;\n}\n\nexport interface EnsureOwnedMinaZkAppParams extends DeployMinaZkAppParams {\n /** The connector peer's Mina settlement B62 (participantB of the pair). */\n peerPublicKey: string;\n /** A previously recorded own deployment for this identity/pair, if any. */\n deployed?: { zkAppAddress: string; zkAppPrivateKey: string };\n /** The announce/preset-resolved zkApp address (checked after `deployed`). */\n candidateZkAppAddress?: string;\n /**\n * Called with the record IMMEDIATELY after a fresh deployment is confirmed\n * on-chain — BEFORE this function returns — so the zkApp key is persisted\n * even if the caller's subsequent initialize fails.\n */\n onDeployed?: (record: MinaZkAppDeployRecord) => void | Promise<void>;\n}\n\n/** Required fee-payer balance to deploy: 1 MINA account creation + tx fees. */\nfunction requiredDeployBalanceNanomina(feeNanomina: bigint): bigint {\n // account-creation (1 MINA) + the deploy tx fee + a buffer for the follow-up\n // initializeChannel tx (same fee) the opener submits right after.\n return MINA_ACCOUNT_CREATION_FEE_NANOMINA + feeNanomina * 2n;\n}\n\nexport interface EnsureOwnedMinaZkAppResult {\n /** The zkApp to open the channel on (ours — reused or freshly deployed). */\n zkAppAddress: string;\n /** True when this call deployed a fresh zkApp. */\n deployed: boolean;\n /** The deploy record (fresh deploys only). */\n record?: MinaZkAppDeployRecord;\n}\n\nconst sleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Deploy a fresh `PaymentChannel` zkApp (BARE — no initialization) and wait\n * for the account to exist on-chain. Returns the full deploy record; the\n * caller persists it (losing the zkApp key would strand the ~1.1 MINA the\n * deployment cost).\n */\nexport async function deployMinaChannelZkApp(\n params: DeployMinaZkAppParams\n): Promise<MinaZkAppDeployRecord> {\n const { o1js } = await loadMinaRuntime();\n const { Mina, PrivateKey, AccountUpdate, fetchAccount } = o1js;\n const progress = params.onProgress ?? ((): void => undefined);\n\n const network = Mina.Network(params.graphqlUrl);\n Mina.setActiveInstance(network);\n\n const payerKeyBase58 = hexToMinaBase58PrivateKey(params.payerPrivateKey);\n const payerPrivateKey = PrivateKey.fromBase58(payerKeyBase58);\n const payerPublicKey = payerPrivateKey.toPublicKey();\n\n const feeNanomina = params.feeNanomina ?? 100_000_000n;\n\n // ── Preflight (bug #1) ───────────────────────────────────────────────────\n // Fail fast if the fee payer cannot fund the deploy — BEFORE the ~1-3 min\n // circuit compile. Without this the compile runs first and only then does\n // Mina.transaction throw `getAccount: Could not find account …`, wasting the\n // compile AND leaking the o1js transaction context (see buildMinaTransaction).\n await assertMinaFeePayerFunded({\n o1js,\n payerPublicKey,\n requiredNanomina: requiredDeployBalanceNanomina(feeNanomina),\n graphqlUrl: params.graphqlUrl,\n });\n\n // Reuse a recorded key (redeploy of a pending/crashed attempt) or mint a\n // fresh one. A fresh key is PERSISTED via onDeploying BEFORE any spend so a\n // crash between send and confirmation never orphans a new zkApp (bug #3).\n const zkAppPrivateKey = params.zkAppPrivateKey\n ? PrivateKey.fromBase58(params.zkAppPrivateKey)\n : PrivateKey.random();\n const zkAppPublicKey = zkAppPrivateKey.toPublicKey();\n const zkAppAddress: string = zkAppPublicKey.toBase58();\n if (!params.zkAppPrivateKey) {\n await params.onDeploying?.({\n zkAppAddress,\n zkAppPrivateKey: zkAppPrivateKey.toBase58(),\n feePayer: payerPublicKey.toBase58(),\n });\n }\n\n progress(\n 'compiling the PaymentChannel circuit (one-time, can take 1-3 minutes)…'\n );\n const PaymentChannel = await getCompiledPaymentChannel();\n\n const zkApp = new PaymentChannel(zkAppPublicKey);\n\n // The payer account must be in the active-instance cache for the fee/nonce.\n await fetchAccount({ publicKey: payerPublicKey });\n\n progress(\n `deploying a dedicated PaymentChannel zkApp ${zkAppAddress} ` +\n '(costs the 1 MINA account-creation fee + tx fee)…'\n );\n const fee = Number(feeNanomina);\n // Deploy ONLY — initialize is a separate tx (Issue #128: the initialize\n // precondition needs the account to already exist). Built through\n // buildMinaTransaction so a failure cannot leak the o1js transaction context\n // and poison a retry (bug #2).\n const deployTx = await buildMinaTransaction(\n o1js,\n { sender: payerPublicKey, fee },\n async () => {\n AccountUpdate.fundNewAccount(payerPublicKey);\n await zkApp.deploy();\n }\n );\n await deployTx.prove();\n const sent = await deployTx.sign([payerPrivateKey, zkAppPrivateKey]).send();\n const deployTxHash: string | undefined = sent.hash ?? undefined;\n\n progress(\n `deploy tx sent${deployTxHash ? ` (${deployTxHash})` : ''} — waiting for ` +\n 'inclusion (devnet blocks are ~3 minutes)…'\n );\n // Poll fetchAccount rather than trusting `.wait()` alone: what the opener\n // needs is the ACCOUNT resolvable through this GraphQL endpoint.\n const interval = params.pollIntervalMs ?? 15_000;\n const budget = params.pollTimeoutMs ?? 540_000;\n const started = Date.now();\n // Best-effort `.wait()` first — on some o1js versions it returns promptly\n // after inclusion, which shortcuts the polling below.\n try {\n await sent.wait();\n } catch {\n // fall through to polling — some endpoints reject the wait subscription\n }\n for (;;) {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (!res.error && res.account) break;\n if (Date.now() - started > budget) {\n throw new Error(\n `Mina zkApp ${zkAppAddress} did not appear on-chain within ` +\n `${Math.round(budget / 60_000)} minutes of the deploy tx` +\n `${deployTxHash ? ` (${deployTxHash})` : ''} — check the tx on ` +\n 'minascan before retrying (the zkApp key has been recorded)'\n );\n }\n await sleep(interval);\n }\n progress(`zkApp ${zkAppAddress} is on-chain (bare — initialize follows).`);\n\n return {\n zkAppAddress,\n zkAppPrivateKey: zkAppPrivateKey.toBase58(),\n feePayer: payerPublicKey.toBase58(),\n ...(deployTxHash !== undefined ? { deployTxHash } : {}),\n ...(getCompiledVerificationKeyHash() !== undefined\n ? { vkHash: getCompiledVerificationKeyHash() }\n : {}),\n };\n}\n\n/**\n * Resolve the zkApp this (client, peer) pair should open its channel on:\n * reuse one that is provably ours, else deploy fresh. See the module doc for\n * the decision table.\n */\nexport async function ensureOwnedMinaZkApp(\n params: EnsureOwnedMinaZkAppParams\n): Promise<EnsureOwnedMinaZkAppResult> {\n const { o1js } = await loadMinaRuntime();\n const { Mina, PrivateKey, PublicKey, Field, Poseidon, fetchAccount } = o1js;\n const progress = params.onProgress ?? ((): void => undefined);\n\n const network = Mina.Network(params.graphqlUrl);\n Mina.setActiveInstance(network);\n\n const payerKeyBase58 = hexToMinaBase58PrivateKey(params.payerPrivateKey);\n const clientPublicKey = PrivateKey.fromBase58(payerKeyBase58).toPublicKey();\n const peerPublicKey = PublicKey.fromBase58(params.peerPublicKey);\n\n // The participant-form pair hash `initializeChannel` bakes on-chain (and the\n // off-chain claim signer binds to): Poseidon([A.x, B.x, nonce=0]).\n if (!Poseidon) {\n throw new Error(\n 'the loaded o1js runtime does not expose Poseidon — cannot derive the pair hash'\n );\n }\n const expectedChannelHash: string = Poseidon.hash([\n clientPublicKey.x,\n peerPublicKey.x,\n Field(0),\n ]).toString();\n\n interface Candidate {\n address: string;\n ownRecord: boolean;\n }\n const candidates: Candidate[] = [];\n if (params.deployed?.zkAppAddress) {\n candidates.push({ address: params.deployed.zkAppAddress, ownRecord: true });\n }\n if (\n params.candidateZkAppAddress &&\n params.candidateZkAppAddress !== params.deployed?.zkAppAddress\n ) {\n candidates.push({\n address: params.candidateZkAppAddress,\n ownRecord: false,\n });\n }\n\n // A recorded own key whose account is not (yet) on-chain: the previous\n // deploy attempt was persisted BEFORE its tx confirmed (bug #3), then\n // crashed/never landed. Redeploy the SAME key rather than minting a new\n // zkApp — otherwise every retry orphans another ~1.1-MINA deployment.\n let pendingOwnKey: string | undefined;\n\n for (const candidate of candidates) {\n let appState: { toString(): string }[] | undefined;\n try {\n const res = await fetchAccount({\n publicKey: PublicKey.fromBase58(candidate.address),\n });\n if (res.error || !res.account) {\n // Not on-chain. If it is OUR recorded deployment, remember its key so\n // the deploy below reuses the SAME address instead of orphaning a new\n // zkApp on every retry.\n if (candidate.ownRecord && params.deployed?.zkAppPrivateKey) {\n pendingOwnKey = params.deployed.zkAppPrivateKey;\n }\n continue;\n }\n appState = res.account.zkapp?.appState;\n } catch {\n continue;\n }\n const channelHash = appState?.[APPSTATE_CHANNEL_HASH]?.toString() ?? '';\n const channelState = BigInt(\n appState?.[APPSTATE_CHANNEL_STATE]?.toString() ?? '0'\n );\n if (channelState === STATE_OPEN && channelHash === expectedChannelHash) {\n // OPEN for exactly our pair — ours, reuse (open is idempotent on it).\n progress(`reusing our open Mina channel zkApp ${candidate.address}.`);\n return { zkAppAddress: candidate.address, deployed: false };\n }\n if (candidate.ownRecord && channelState === STATE_UNINITIALIZED) {\n // Our own recorded deployment that never got initialized (crash between\n // deploy and init) — reuse it; the normal open initializes it.\n progress(\n `reusing our recorded (uninitialized) Mina zkApp ${candidate.address}.`\n );\n return { zkAppAddress: candidate.address, deployed: false };\n }\n // Anything else — a foreign pair's channel, the shared announce zkApp\n // someone already initialized, CLOSING/SETTLED remnants — is not ours.\n }\n\n // No usable candidate: deploy a dedicated zkApp for this pair. Reuse the\n // recorded pending key when present (redeploy the same address, no orphan).\n const record = await deployMinaChannelZkApp({\n ...params,\n ...(pendingOwnKey ? { zkAppPrivateKey: pendingOwnKey } : {}),\n ...(params.onDeploying ? { onDeploying: params.onDeploying } : {}),\n });\n // Persist BEFORE returning: if the caller's initialize fails, the zkApp key\n // (and the ~1.1 MINA it cost) must not be lost.\n await params.onDeployed?.(record);\n return { zkAppAddress: record.zkAppAddress, deployed: true, record };\n}\n","/**\n * On-chain Mina payment-channel open — connector-parity.\n *\n * Opens (initializes) — and optionally deposits into — a REAL on-chain Mina\n * payment channel on the deployed `PaymentChannel` zkApp, so the connector's\n * `MinaPaymentChannelSDK.getChannelState(zkAppAddress)` finds a channel whose\n * on-chain `channelState == OPEN` (status `'opened'`) and the Mina claim\n * verifies + stores. This is the Mina analog of `openSolanaChannel`\n * (`solana-payment-channel.ts`, connector#105): the client opens its own\n * per-channel on-chain state rather than relying on a pre-initialized channel.\n *\n * ## Why this is separate from `mina-payment-channel.ts`\n *\n * `mina-payment-channel.ts` builds the OFF-chain balance-proof claim with\n * `mina-signer` (no o1js — keeps the client lightweight). But INITIALIZING a\n * zkApp channel requires producing a zkApp method proof, which is heavyweight\n * o1js WASM circuit work. So this module lazily imports `o1js` +\n * `@toon-protocol/mina-zkapp` ONLY when an on-chain open is actually requested\n * (the e2e client / Node settlement path), mirroring the connector's own\n * `getO1js()` lazy-require. Both are OPTIONAL dependencies and are kept out of\n * the bundle via `tsup` `external` so npm consumers that never open a Mina\n * channel don't pay the o1js cost.\n *\n * ## Contract call (must match the connector's `MinaPaymentChannelSDK.openChannel`)\n *\n * `PaymentChannel.initializeChannel(participantA, participantB, nonce, timeout, tokenId)`\n * sets `channelState = OPEN` (1). The deployed zkApp address IS the channel id\n * (`MinaClaimMessage.zkAppAddress`), identical to the claim's channel-hash\n * preimage in `mina-payment-channel.ts`. `deposit(amount, depositor)` then\n * bumps `depositTotal` (only valid while `channelState == OPEN`).\n *\n * The zkApp is deployed out-of-band (the operator/e2e harness deploys it\n * deterministically and advertises its B62 address); this module assumes the\n * account exists and only INITIALIZES the channel on it (idempotent — if the\n * channel is already `OPEN`, it returns without re-initializing).\n *\n * @module\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { hexToMinaBase58PrivateKey } from '@toon-protocol/core';\n\n/** Result shape of o1js `fetchAccount` (the bits we read). */\ninterface FetchAccountResult {\n error?: unknown;\n account?: {\n zkapp?: { appState?: { toString(): string }[] };\n /** Account MINA balance (o1js `UInt64`) — the preflight reads its total. */\n balance?: { toString(): string };\n };\n}\n\n/** Minimal o1js surface this module uses (lazy-loaded). */\nexport interface O1jsLike {\n Mina: any;\n PrivateKey: any;\n PublicKey: any;\n Field: any;\n AccountUpdate: any;\n /** Present on the real o1js; the deploy/ownership path uses it. */\n Poseidon?: any;\n fetchAccount: (args: { publicKey: any }) => Promise<FetchAccountResult>;\n}\n\nlet cachedO1js: O1jsLike | null = null;\nlet cachedPaymentChannel: any | null = null;\nlet compiledContract: any | null = null;\n\n/**\n * Test-only override for the o1js + contract loader. When set, `loadMinaRuntime`\n * returns this instead of doing the `createRequire` resolution — so unit tests\n * can inject fakes WITHOUT pulling the real o1js WASM runtime (vitest's\n * `vi.mock` cannot intercept the CJS `require` path the production loader uses).\n */\nlet runtimeOverride:\n | (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>)\n | null = null;\n\n/** Test hook: inject a fake o1js + PaymentChannel runtime. */\nexport function _setMinaRuntimeForTests(\n loader: (() => Promise<{ o1js: O1jsLike; PaymentChannel: any }>) | null\n): void {\n runtimeOverride = loader;\n}\n\n/**\n * Resolve `o1js` AND the `PaymentChannel` contract through ONE shared module\n * instance.\n *\n * ⚠️ o1js keeps its \"active Mina instance\" in a module-level closure\n * (`mina-instance.js`). `@toon-protocol/mina-zkapp` ships as CommonJS, so its\n * internal `import {Mina}` is emitted as `require('o1js')` → o1js's CJS build\n * (`dist/node/index.cjs`). A bare ESM `import('o1js')` from this module resolves\n * o1js's DIFFERENT ESM build (`dist/node/index.js`) — a SEPARATE module instance\n * with a SEPARATE `activeInstance` closure. Calling `setActiveInstance` on the\n * ESM instance while `PaymentChannel.initializeChannel` reads the CJS instance\n * throws `channelState.get() failed … Must call Mina.setActiveInstance first`\n * (observed in the local-HS Mina e2e on the FIRST publish). The connector's own\n * settlement path and `scripts/deploy-mina-zkapp.ts` both work around this by\n * requiring o1js through the same anchor the zkApp uses.\n *\n * Fix: anchor a `createRequire` at the `@toon-protocol/mina-zkapp` package and\n * `require('o1js')` + the contract from there, so both share the CJS o1js\n * instance and `setActiveInstance` is visible inside the contract method. Kept\n * lazy (and `external` in tsup) so the multi-hundred-MB WASM runtime is only\n * loaded when a Mina channel is actually opened.\n */\nexport async function loadMinaRuntime(): Promise<{\n o1js: O1jsLike;\n PaymentChannel: any;\n}> {\n if (cachedO1js && cachedPaymentChannel) {\n return { o1js: cachedO1js, PaymentChannel: cachedPaymentChannel };\n }\n if (runtimeOverride) {\n const injected = await runtimeOverride();\n cachedO1js = injected.o1js;\n cachedPaymentChannel = injected.PaymentChannel;\n return injected;\n }\n const { createRequire } = await import('node:module');\n const nodePath = await import('node:path');\n // Anchor resolution at this module so the consumer's node_modules graph (where\n // both o1js and @toon-protocol/mina-zkapp are installed) resolves them, then\n // re-anchor at the mina-zkapp package so its CJS `require('o1js')` and ours are\n // the SAME physical module instance (shared active-instance closure).\n const requireHere = createRequire(import.meta.url);\n const mzkPkgPath = requireHere.resolve(\n '@toon-protocol/mina-zkapp/package.json'\n );\n const requireFromMzk = createRequire(mzkPkgPath);\n // o1js resolved from the mina-zkapp anchor → the SAME (CJS) instance the\n // contract's `require('o1js')` uses.\n const o1js = requireFromMzk('o1js') as O1jsLike;\n // ⚠️ A pnpm workspace package has NO self-referential symlink, so\n // `requireFromMzk('@toon-protocol/mina-zkapp')` fails with MODULE_NOT_FOUND.\n // Load the contract by PATH from the package's own `main` entry instead (the\n // same approach scripts/deploy-mina-zkapp.ts uses). This works in both the\n // workspace (no self-symlink) and the flat consumer node_modules layouts.\n const mzkPkgJson: { main?: string } = requireFromMzk(mzkPkgPath);\n const mzkDir = nodePath.dirname(mzkPkgPath);\n const mzkEntry = nodePath.join(mzkDir, mzkPkgJson.main ?? 'dist/index.js');\n const mzk: any = requireFromMzk(mzkEntry);\n const PaymentChannel = mzk.PaymentChannel ?? mzk.default?.PaymentChannel;\n if (!PaymentChannel) {\n throw new Error(\n '@toon-protocol/mina-zkapp does not export PaymentChannel — cannot open a Mina channel'\n );\n }\n cachedO1js = o1js;\n cachedPaymentChannel = PaymentChannel;\n return { o1js, PaymentChannel };\n}\n\n/** Lazily resolve `o1js` (shared CJS instance with the contract). */\nasync function getO1js(): Promise<O1jsLike> {\n return (await loadMinaRuntime()).o1js;\n}\n\nlet compiledVerificationKeyHash: string | undefined;\n\n/**\n * Lazily resolve + compile the `PaymentChannel` contract. Compilation is the\n * expensive o1js step; cache the compiled artifact so repeated opens in the\n * same process don't recompile.\n */\nexport async function getCompiledPaymentChannel(): Promise<any> {\n const { PaymentChannel } = await loadMinaRuntime();\n if (!compiledContract) {\n const compiled = await PaymentChannel.compile();\n // Record the vk hash for the deploy path's provenance record (drift\n // debugging: the deployed vk IS this locally compiled one by\n // construction, but keeping the hash makes that checkable later).\n compiledVerificationKeyHash =\n compiled?.verificationKey?.hash?.toString() ?? undefined;\n compiledContract = PaymentChannel;\n }\n return compiledContract;\n}\n\n/** The vk hash of the last {@link getCompiledPaymentChannel} compile, if any. */\nexport function getCompiledVerificationKeyHash(): string | undefined {\n return compiledVerificationKeyHash;\n}\n\n/** Test hook: reset the cached o1js + compiled-contract state. */\nexport function _resetMinaChannelOpenCache(): void {\n cachedO1js = null;\n cachedPaymentChannel = null;\n compiledContract = null;\n compiledVerificationKeyHash = undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Transaction-nesting safety (o1js currentTransaction leak)\n// ---------------------------------------------------------------------------\n\n/**\n * o1js tracks the \"current transaction\" in a module-level stack\n * (`Mina.currentTransaction`). `Mina.transaction(feePayer, f)` ENTERS that\n * context, runs the circuit `f`, then LEAVES it — but the fee-payer nonce read\n * (`getAccount(sender)`) happens AFTER the enter and OUTSIDE the try/finally\n * that would leave it. So when the fee payer's account is not in the cache\n * (e.g. an unfunded / nonexistent wallet — `getAccount: Could not find account\n * for public key …`), that read THROWS with the context still entered: the\n * transaction is leaked. The very next `Mina.transaction` anywhere in the\n * SAME process then hits `if (currentTransaction.has()) throw 'Cannot start\n * new transaction within another transaction'` — which is exactly what the\n * cache-invalidation retry path re-triggered after a first failed deploy.\n *\n * This pops any leaked contexts so a subsequent transaction (a retry, or the\n * next zkApp tx) starts clean. Best-effort and defensive: unknown/absent\n * context shapes are ignored.\n */\nexport function abandonLeakedMinaTransaction(o1js: O1jsLike): void {\n const ctx = (o1js.Mina as { currentTransaction?: unknown })\n .currentTransaction;\n const c = ctx as\n | { has?: () => boolean; id?: () => unknown; leave?: (id: unknown) => void }\n | undefined;\n if (!c || typeof c.has !== 'function' || typeof c.leave !== 'function')\n return;\n // Bounded loop — never spin if leave() does not shrink the stack.\n for (let i = 0; i < 64 && c.has(); i += 1) {\n try {\n const id = typeof c.id === 'function' ? c.id() : undefined;\n c.leave(id);\n } catch {\n break; // inconsistent context — stop rather than throw over the caller's error\n }\n }\n}\n\n/**\n * `Mina.transaction(feePayer, f)` that cannot leak the o1js currentTransaction\n * context on failure (see {@link abandonLeakedMinaTransaction}). Every Mina tx\n * this package builds goes through here so ONE failed build (unfunded fee\n * payer, prove error, …) can never poison the next attempt in the process.\n */\nexport async function buildMinaTransaction(\n o1js: O1jsLike,\n feePayer: unknown,\n f: () => Promise<void>\n): Promise<any> {\n try {\n return await o1js.Mina.transaction(feePayer, f);\n } catch (err) {\n abandonLeakedMinaTransaction(o1js);\n throw err;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fee-payer preflight (fail fast BEFORE the multi-minute circuit compile)\n// ---------------------------------------------------------------------------\n\n/** The protocol's account-creation fee for a brand-new Mina account (1 MINA). */\nexport const MINA_ACCOUNT_CREATION_FEE_NANOMINA = 1_000_000_000n;\n\n/**\n * The Mina fee payer cannot fund an on-chain operation: either the account\n * does not exist on-chain yet (never received MINA) or its balance is below\n * the required minimum. Carries the address, the shortfall and the network so\n * the message is directly actionable (\"send N MINA to <addr> on <network>\").\n */\nexport class MinaFeePayerUnfundedError extends Error {\n constructor(\n readonly address: string,\n readonly requiredNanomina: bigint,\n readonly haveNanomina: bigint | undefined,\n readonly graphqlUrl: string\n ) {\n const mina = (n: bigint) => `${(Number(n) / 1e9).toFixed(3)} MINA`;\n const state =\n haveNanomina === undefined\n ? `does not exist on-chain (0 MINA)`\n : `holds only ${mina(haveNanomina)}`;\n super(\n `Mina fee-payer wallet ${address} ${state} but needs ~${mina(\n requiredNanomina\n )} to open a payment channel (≈1 MINA account-creation fee + tx fees) ` +\n `on ${graphqlUrl}. Fund ${address} and retry — no circuit was ` +\n `compiled (this check runs before the ~1-3 min compile).`\n );\n this.name = 'MinaFeePayerUnfundedError';\n }\n}\n\n/**\n * Read the fee payer's on-chain MINA balance and throw\n * {@link MinaFeePayerUnfundedError} when the account is missing or under\n * `requiredNanomina`. Runs BEFORE any circuit compile / zkApp deploy so an\n * unfunded wallet fails in seconds, not after minutes of wasted compilation.\n * The active o1js Mina instance must already be set to `graphqlUrl`.\n */\nexport async function assertMinaFeePayerFunded(params: {\n o1js: O1jsLike;\n payerPublicKey: { toBase58(): string };\n requiredNanomina: bigint;\n graphqlUrl: string;\n}): Promise<void> {\n const { o1js, payerPublicKey, requiredNanomina, graphqlUrl } = params;\n const address = payerPublicKey.toBase58();\n const res = await o1js.fetchAccount({ publicKey: payerPublicKey });\n if (res.error || !res.account) {\n // No account on-chain → 0 MINA, cannot even pay the account-creation fee.\n throw new MinaFeePayerUnfundedError(\n address,\n requiredNanomina,\n undefined,\n graphqlUrl\n );\n }\n const raw = res.account.balance?.toString();\n const have = raw !== undefined ? BigInt(raw) : 0n;\n if (have < requiredNanomina) {\n throw new MinaFeePayerUnfundedError(\n address,\n requiredNanomina,\n have,\n graphqlUrl\n );\n }\n}\n\n/** CHANNEL_STATE.OPEN from `@toon-protocol/mina-zkapp` constants. */\nconst MINA_CHANNEL_STATE_OPEN = 1n;\n/** CHANNEL_STATE.UNINITIALIZED. */\nconst MINA_CHANNEL_STATE_UNINITIALIZED = 0n;\n\nexport interface OpenMinaChannelParams {\n /** Mina GraphQL endpoint of the network the zkApp is deployed on. */\n graphqlUrl: string;\n /** Deployed payment-channel zkApp B62 address (the channel id). */\n zkAppAddress: string;\n /**\n * Fee-payer / participantA `EK…` base58 private key — the client's Mina key\n * (same key the off-chain claim is signed with). Pays the Mina tx fee and\n * authorizes the `initializeChannel` (+ `deposit`) transaction.\n */\n payerPrivateKey: string;\n /**\n * participantB B62 public key — the apex's Mina settlement address. When\n * omitted, the payer is used for both participants (single-party dev channel).\n */\n peerPublicKey?: string;\n /** Channel settlement timeout in slots. Default 86400. */\n timeout?: bigint;\n /** Mina token id field (decimal string). Default '1' (native MINA). */\n tokenId?: string;\n /** Optional on-chain deposit amount (base units) after initialization. */\n deposit?: { amount: bigint };\n /** Per-call network id for the Schnorr/account prefix. Default 'devnet'. */\n networkId?: 'devnet' | 'mainnet';\n /**\n * Transaction fee in nanomina for the `initializeChannel` + `deposit` zkApp\n * method calls. Lightnet/devnet REJECTS fee-less zkApp commands with\n * \"Insufficient fee\", so a non-zero fee is REQUIRED. Default 100_000_000\n * (0.1 MINA), matching scripts/deploy-mina-zkapp.ts.\n */\n feeNanomina?: bigint;\n}\n\nexport interface OpenMinaChannelResult {\n /** The zkApp address (channel id) — echoed for parity with the Solana opener. */\n zkAppAddress: string;\n /** True when a fresh `initializeChannel` tx was submitted this call. */\n opened: boolean;\n /** `initializeChannel` tx hash (absent when the channel was already OPEN). */\n initTxHash?: string;\n /** `deposit` tx hash, when a deposit was requested + submitted. */\n depositTxHash?: string;\n /** On-chain channelState after the call (0=UNINIT,1=OPEN,2=CLOSING,3=SETTLED). */\n channelState: number;\n /**\n * On-chain `depositTotal` (base units), read from the zkApp appState after the\n * open/deposit settled. The Mina balance-proof signer needs this so it can bind\n * `balanceB = depositTotal − balanceA` (toon-protocol/connector#133). A channel\n * can be re-deposited, so this is the CURRENT on-chain value, not a config one.\n */\n depositTotal: bigint;\n}\n\n/**\n * Open (initialize) — and optionally deposit into — a real on-chain Mina\n * payment channel on the already-deployed `PaymentChannel` zkApp.\n *\n * Idempotent: if the on-chain `channelState` is already `OPEN`, returns without\n * re-initializing (mirrors `openSolanaChannel`'s \"channel already exists\" path).\n * Throws if the zkApp account does not exist on-chain.\n */\nexport async function openMinaChannelOnChain(\n params: OpenMinaChannelParams\n): Promise<OpenMinaChannelResult> {\n const { Mina, PrivateKey, PublicKey, Field, AccountUpdate, fetchAccount } =\n await getO1js();\n\n // Use the plain-string `Mina.Network(graphqlUrl)` form (matching the\n // connector's MinaPaymentChannelSDK._setNetwork). The object form\n // (`{ networkId, mina }`) behaves inconsistently across o1js versions and left\n // the active-instance ledger unable to resolve fetched accounts\n // (`channelState.get()` → \"we can't find this zkapp account\"). The Schnorr\n // network prefix is governed by the off-chain signer (`networkId`), not this\n // on-chain endpoint binding.\n const network = Mina.Network(params.graphqlUrl);\n Mina.setActiveInstance(network);\n\n // zkApp method txs MUST carry a fee on lightnet/devnet (\"Insufficient fee\"\n // otherwise). 0.1 MINA matches scripts/deploy-mina-zkapp.ts.\n const txFee = params.feeNanomina ?? 100_000_000n;\n\n // The client's mnemonic-derived Mina key is a big-endian hex scalar (the form\n // `deriveFullIdentity()` emits); o1js `PrivateKey.fromBase58` needs the Mina\n // `EK…` base58check form. Convert (idempotent — passes an already-`EK…` key\n // through unchanged), matching what the off-chain MinaSigner does.\n const payerKeyBase58 = hexToMinaBase58PrivateKey(params.payerPrivateKey);\n const payerPrivateKey = PrivateKey.fromBase58(payerKeyBase58);\n const payerPublicKey = payerPrivateKey.toPublicKey();\n const zkAppPublicKey = PublicKey.fromBase58(params.zkAppAddress);\n\n // Read channelState (appState index 3) straight from the `fetchAccount`\n // result rather than `zkApp.channelState.get()`. `.get()` outside a\n // transaction is fragile here (it can throw \"Must call Mina.setActiveInstance\n // first\" / \"can't find this zkapp account\" even right after a successful\n // fetch); the network-fetched appState array is the reliable source. A\n // missing account is a hard error — the zkApp must be deployed out-of-band.\n const readChannelState = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n // PaymentChannel state field order: [channelHash, balanceCommitment,\n // nonceField, channelState, depositTotal, ...] → channelState is index 3.\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[3]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n // Read the on-chain `depositTotal` (appState index 4). The signer must bind\n // `balanceB = depositTotal − balanceA` against this CURRENT on-chain value\n // (connector#133); a channel can be re-deposited, so a stale config value\n // would fail the signatureA verification on settle. A missing account is a\n // hard error (same as readChannelState).\n const readDepositTotal = async (): Promise<bigint> => {\n const res = await fetchAccount({ publicKey: zkAppPublicKey });\n if (res.error || !res.account) {\n throw new Error(\n `Mina zkApp account ${params.zkAppAddress} not found on-chain (${String(\n res.error\n )}) — deploy the PaymentChannel zkApp before opening a channel`\n );\n }\n const appState = res.account.zkapp?.appState;\n const raw = appState?.[4]?.toString() ?? '0';\n return BigInt(raw);\n };\n\n const currentState = await readChannelState();\n await fetchAccount({ publicKey: payerPublicKey });\n let opened = false;\n let initTxHash: string | undefined;\n let zkApp: any;\n const getZkApp = async () => {\n if (!zkApp) {\n const PaymentChannel = await getCompiledPaymentChannel();\n zkApp = new PaymentChannel(zkAppPublicKey);\n }\n return zkApp;\n };\n\n if (currentState === MINA_CHANNEL_STATE_UNINITIALIZED) {\n const channel = await getZkApp();\n const participantA = payerPublicKey;\n const participantB = params.peerPublicKey\n ? PublicKey.fromBase58(params.peerPublicKey)\n : payerPublicKey;\n const nonce = Field(0);\n const timeoutField = Field((params.timeout ?? 86400n).toString());\n const tokenIdField = Field(params.tokenId ?? '1');\n\n // `initializeChannel` reads `this.channelState.getAndRequireEquals()` as a\n // precondition, which needs the zkApp account in o1js's active-instance\n // cache. Re-fetch BOTH the zkApp and the fee-payer immediately before\n // building the transaction so the precondition read resolves (a stale or\n // missing cache surfaces as \"channelState.get() failed / Must call\n // setActiveInstance first\" — even though the network IS set).\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n\n const initTx = await buildMinaTransaction(\n await getO1js(),\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.initializeChannel(\n participantA,\n participantB,\n nonce,\n timeoutField,\n tokenIdField\n );\n }\n );\n await initTx.prove();\n const sentInit = await initTx.sign([payerPrivateKey]).send();\n initTxHash = sentInit.hash ?? undefined;\n opened = true;\n // ALWAYS wait for the init tx to be INCLUDED in a block (and re-fetch the\n // account) before returning — NOT only when a deposit follows.\n //\n // Why this matters (issue #158): the two-party `channelHash =\n // Poseidon([client.x, apex.x, 0])` is only written to the zkApp's on-chain\n // state once `initializeChannel` is included in a block. If we fire-and-forget\n // the init tx, the publish proceeds immediately and the connector reads the\n // STILL-BARE zkApp (channelState=0, channelHash empty → `participants:[\"\",\"\"]`)\n // before the init lands, so its participant-form balance-proof reconstruction\n // mismatches → `mina_claim_verification_failed: \"Invalid balance proof\n // signature\"`. The EVM (`waitForTransactionReceipt`) and Solana\n // (`waitForConfirmation`) openers both confirm their open tx before returning;\n // Mina must do the same for parity. `.wait()` blocks until inclusion (lightnet\n // block time can be a few minutes).\n await sentInit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n } else if (currentState !== MINA_CHANNEL_STATE_OPEN) {\n // CLOSING (2) or SETTLED (3): cannot (re)open. Surface clearly.\n throw new Error(\n `Mina channel ${params.zkAppAddress} is in state ${currentState} (not UNINITIALIZED/OPEN) — cannot open`\n );\n }\n\n // Optional deposit (only valid while OPEN — which it now is).\n let depositTxHash: string | undefined;\n if (params.deposit && params.deposit.amount > 0n) {\n const channel = await getZkApp();\n // Re-fetch so the deposit tx sees the post-init state.\n await fetchAccount({ publicKey: zkAppPublicKey });\n const amountField = Field(params.deposit.amount.toString());\n const depositTx = await buildMinaTransaction(\n await getO1js(),\n { sender: payerPublicKey, fee: Number(txFee) },\n async () => {\n await channel.deposit(amountField, payerPublicKey);\n }\n );\n await depositTx.prove();\n const sentDeposit = await depositTx.sign([payerPrivateKey]).send();\n depositTxHash = sentDeposit.hash ?? undefined;\n // ALWAYS wait for the deposit tx to be INCLUDED before returning — same\n // confirmation discipline as initializeChannel above (issue #158). The\n // connector's claimFromChannel runs a #126 balance-conservation gate that\n // reads the on-chain `depositTotal`; if we fire-and-forget the deposit, the\n // publish + claim race ahead and the connector reads depositTotal=0 (deposit\n // not yet in a block) → `PROOF_GENERATION_FAILED: Claim violates balance\n // conservation` and the settle aborts non-retryably. Blocking on inclusion\n // (and re-fetching) guarantees the funded depositTotal is on-chain before any\n // claim settles against it.\n await sentDeposit.wait();\n await fetchAccount({ publicKey: zkAppPublicKey });\n await fetchAccount({ publicKey: payerPublicKey });\n }\n\n // Read the resulting state from the network-fetched appState (best-effort —\n // may still reflect the pre-confirmation value on a slow node; the connector\n // re-reads at verification time). If we just opened, optimistically report\n // OPEN even if the node hasn't surfaced the new state yet.\n let finalState: number;\n try {\n finalState = Number(await readChannelState());\n } catch {\n finalState = opened\n ? Number(MINA_CHANNEL_STATE_OPEN)\n : Number(currentState);\n }\n if (opened && finalState === Number(MINA_CHANNEL_STATE_UNINITIALIZED)) {\n finalState = Number(MINA_CHANNEL_STATE_OPEN);\n }\n\n // Touch AccountUpdate so the (lazy) import is retained even if a future\n // refactor stops referencing it directly above; harmless no-op.\n void AccountUpdate;\n\n // Read the resulting on-chain depositTotal (post init+deposit confirmation, so\n // a fresh re-deposit is reflected). Best-effort: fall back to 0n if the read\n // throws on a slow node — the connector re-reads at verification time.\n let depositTotal: bigint;\n try {\n depositTotal = await readDepositTotal();\n } catch {\n depositTotal = 0n;\n }\n\n return {\n zkAppAddress: params.zkAppAddress,\n opened,\n initTxHash,\n depositTxHash,\n channelState: finalState,\n depositTotal,\n };\n}\n"],"mappings":"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","names":["hexToMinaBase58PrivateKey","hexToMinaBase58PrivateKey"]}
package/dist/index.d.ts CHANGED
@@ -198,6 +198,17 @@ interface MinaChannelClientOptions {
198
198
  zkAppAddress: string;
199
199
  zkAppPrivateKey: string;
200
200
  };
201
+ /**
202
+ * Persist hook — called with the zkApp address + key BEFORE the deploy tx
203
+ * is sent (and before the circuit compiles), so a crash between send and
204
+ * on-chain confirmation reuses the SAME zkApp next run instead of
205
+ * deploying (and paying ~1.1 MINA for) a second one.
206
+ */
207
+ onDeploying?: (record: {
208
+ zkAppAddress: string;
209
+ zkAppPrivateKey: string;
210
+ feePayer: string;
211
+ }) => void | Promise<void>;
201
212
  /** Persist hook — called BEFORE the open proceeds on a fresh deploy. */
202
213
  onDeployed?: (record: {
203
214
  zkAppAddress: string;
@@ -2623,6 +2634,17 @@ interface MinaChannelConfig {
2623
2634
  zkAppAddress: string;
2624
2635
  zkAppPrivateKey: string;
2625
2636
  };
2637
+ /**
2638
+ * Persist hook — called with the zkApp address + key BEFORE the deploy tx
2639
+ * is sent (and before the circuit compiles), so a crash between send and
2640
+ * on-chain confirmation reuses the SAME zkApp next run instead of
2641
+ * deploying (and paying ~1.1 MINA for) a second one.
2642
+ */
2643
+ onDeploying?: (record: {
2644
+ zkAppAddress: string;
2645
+ zkAppPrivateKey: string;
2646
+ feePayer: string;
2647
+ }) => void | Promise<void>;
2626
2648
  /** Persist hook — called BEFORE the open proceeds on a fresh deploy. */
2627
2649
  onDeployed?: (record: {
2628
2650
  zkAppAddress: string;
@@ -4468,6 +4490,26 @@ interface DeployMinaZkAppParams {
4468
4490
  * charged to the payer via `AccountUpdate.fundNewAccount`.
4469
4491
  */
4470
4492
  feeNanomina?: bigint;
4493
+ /**
4494
+ * Reuse THIS zkApp key (`EK…` base58) instead of generating a fresh one.
4495
+ * Used to re-attempt a deployment whose key was already recorded (so a
4496
+ * crashed/pending first attempt does not orphan a NEW ~1.1-MINA zkApp on
4497
+ * every retry — the SAME address is redeployed).
4498
+ */
4499
+ zkAppPrivateKey?: string;
4500
+ /**
4501
+ * Called with the zkApp address + key IMMEDIATELY after the key is known and
4502
+ * the fee-payer preflight passes — BEFORE the circuit compiles or the deploy
4503
+ * tx is sent. The rig store persists this so a crash between send and
4504
+ * confirmation reuses the SAME zkApp next run rather than deploying (and
4505
+ * paying for) a second one. Fired only for a freshly-generated key (a
4506
+ * redeploy of a recorded key is already persisted).
4507
+ */
4508
+ onDeploying?: (record: {
4509
+ zkAppAddress: string;
4510
+ zkAppPrivateKey: string;
4511
+ feePayer: string;
4512
+ }) => void | Promise<void>;
4471
4513
  /** Progress lines (compile/deploy/inclusion phases take minutes). */
4472
4514
  onProgress?: (line: string) => void;
4473
4515
  /** Inclusion poll interval ms (default 15_000; tests shrink it). */
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  deployMinaChannelZkApp,
30
30
  ensureOwnedMinaZkApp,
31
31
  openMinaChannelOnChain
32
- } from "./chunk-OM3MHJHC.js";
32
+ } from "./chunk-7TDAU43L.js";
33
33
 
34
34
  // src/ToonClient.ts
35
35
  import { generateSecretKey as generateSecretKey3, getPublicKey as getPublicKey2, finalizeEvent } from "nostr-tools/pure";
@@ -2415,7 +2415,9 @@ var OnChainChannelClient = class {
2415
2415
  return this.depositSolana(channelId, amount, opts.currentDeposit);
2416
2416
  }
2417
2417
  if (chainPrefix === "mina") {
2418
- throw new Error("Deposit on mina is not yet supported (EVM + Solana today; Mina follow-up).");
2418
+ throw new Error(
2419
+ "Deposit on mina is not yet supported (EVM + Solana today; Mina follow-up)."
2420
+ );
2419
2421
  }
2420
2422
  return this.depositEvm(channelId, amount, opts.currentDeposit, ctx);
2421
2423
  }
@@ -2431,7 +2433,9 @@ var OnChainChannelClient = class {
2431
2433
  }
2432
2434
  const cfg = this.solanaConfig;
2433
2435
  const payerSeed = cfg.keypair.slice(0, 32);
2434
- const payerPubkey = base58Encode2(new Uint8Array(ed255192.getPublicKey(payerSeed)));
2436
+ const payerPubkey = base58Encode2(
2437
+ new Uint8Array(ed255192.getPublicKey(payerSeed))
2438
+ );
2435
2439
  let payerTokenAccount = cfg.deposit?.payerTokenAccount;
2436
2440
  if (!payerTokenAccount) {
2437
2441
  if (!cfg.tokenMint) {
@@ -2439,7 +2443,10 @@ var OnChainChannelClient = class {
2439
2443
  "Solana deposit requires solanaConfig.deposit.payerTokenAccount or solanaConfig.tokenMint to derive the payer ATA."
2440
2444
  );
2441
2445
  }
2442
- payerTokenAccount = deriveAssociatedTokenAccount(payerPubkey, cfg.tokenMint);
2446
+ payerTokenAccount = deriveAssociatedTokenAccount(
2447
+ payerPubkey,
2448
+ cfg.tokenMint
2449
+ );
2443
2450
  }
2444
2451
  const { depositTxSignature } = await depositSolanaChannel({
2445
2452
  rpcUrl: cfg.rpcUrl,
@@ -2450,7 +2457,10 @@ var OnChainChannelClient = class {
2450
2457
  payerTokenAccount,
2451
2458
  amount
2452
2459
  });
2453
- return { txHash: depositTxSignature, depositTotal: currentDeposit + amount };
2460
+ return {
2461
+ txHash: depositTxSignature,
2462
+ depositTotal: currentDeposit + amount
2463
+ };
2454
2464
  }
2455
2465
  /**
2456
2466
  * EVM deposit: approve the token-network for the delta if the allowance is
@@ -2519,7 +2529,11 @@ var OnChainChannelClient = class {
2519
2529
  args: [channelId]
2520
2530
  });
2521
2531
  await publicClient.waitForTransactionReceipt({ hash: closeHash });
2522
- const info = await this.readEvmChannel(publicClient, tokenNetworkAddr, channelId);
2532
+ const info = await this.readEvmChannel(
2533
+ publicClient,
2534
+ tokenNetworkAddr,
2535
+ channelId
2536
+ );
2523
2537
  return {
2524
2538
  txHash: closeHash,
2525
2539
  closedAt: info.closedAt,
@@ -2563,13 +2577,20 @@ var OnChainChannelClient = class {
2563
2577
  */
2564
2578
  async getChannelCloseInfo(channelId) {
2565
2579
  const ctx = this.channelContext.get(channelId);
2566
- if (!ctx) throw new Error(`No on-chain context for channel "${channelId}".`);
2580
+ if (!ctx)
2581
+ throw new Error(`No on-chain context for channel "${channelId}".`);
2567
2582
  const chainPrefix = ctx.chain.split(":")[0];
2568
2583
  if (chainPrefix === "solana" || chainPrefix === "mina") {
2569
- throw new Error(`getChannelCloseInfo on ${chainPrefix} is not supported.`);
2584
+ throw new Error(
2585
+ `getChannelCloseInfo on ${chainPrefix} is not supported.`
2586
+ );
2570
2587
  }
2571
2588
  const { publicClient } = this.createClients(ctx.chain);
2572
- const info = await this.readEvmChannel(publicClient, ctx.tokenNetworkAddress, channelId);
2589
+ const info = await this.readEvmChannel(
2590
+ publicClient,
2591
+ ctx.tokenNetworkAddress,
2592
+ channelId
2593
+ );
2573
2594
  return {
2574
2595
  status: STATE_MAP2[info.state] ?? "open",
2575
2596
  closedAt: info.closedAt,
@@ -2585,7 +2606,11 @@ var OnChainChannelClient = class {
2585
2606
  functionName: "channels",
2586
2607
  args: [channelId]
2587
2608
  });
2588
- return { settlementTimeout: res[0], state: Number(res[1]), closedAt: res[2] };
2609
+ return {
2610
+ settlementTimeout: res[0],
2611
+ state: Number(res[1]),
2612
+ closedAt: res[2]
2613
+ };
2589
2614
  }
2590
2615
  /**
2591
2616
  * Read a participant's on-chain channel state — `deposit` (locked collateral),
@@ -2707,13 +2732,14 @@ var OnChainChannelClient = class {
2707
2732
  }
2708
2733
  let zkAppAddress = this.minaConfig.zkAppAddress;
2709
2734
  if (this.minaConfig.autoDeploy) {
2710
- const { ensureOwnedMinaZkApp: ensureOwnedMinaZkApp2 } = await import("./mina-channel-deploy-CO2LMPPB.js");
2735
+ const { ensureOwnedMinaZkApp: ensureOwnedMinaZkApp2 } = await import("./mina-channel-deploy-5GPMFBGR.js");
2711
2736
  const ensured = await ensureOwnedMinaZkApp2({
2712
2737
  graphqlUrl: this.minaConfig.graphqlUrl,
2713
2738
  payerPrivateKey: this.minaConfig.privateKey,
2714
2739
  peerPublicKey: params.peerAddress,
2715
2740
  ...this.minaConfig.autoDeploy.deployed ? { deployed: this.minaConfig.autoDeploy.deployed } : {},
2716
2741
  ...zkAppAddress ? { candidateZkAppAddress: zkAppAddress } : {},
2742
+ ...this.minaConfig.autoDeploy.onDeploying ? { onDeploying: this.minaConfig.autoDeploy.onDeploying } : {},
2717
2743
  ...this.minaConfig.autoDeploy.onDeployed ? { onDeployed: this.minaConfig.autoDeploy.onDeployed } : {},
2718
2744
  ...this.minaConfig.autoDeploy.onProgress ? { onProgress: this.minaConfig.autoDeploy.onProgress } : {}
2719
2745
  });