@arcanetech/privacy-sdk-stellar 0.5.2 → 0.6.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.
@@ -1,26 +1,8 @@
1
1
  // src/transact/pool/service.ts
2
2
  import {
3
- PrivacyPoolSDK as PrivacyPoolSDK2,
4
- decodeDecodedEphemeralKey,
5
- randomFrDecimal253,
6
- scalarHexToFrDecimal as scalarHexToFrDecimal2
3
+ decodeDecodedEphemeralKey
7
4
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
8
5
 
9
- // src/transact/merkle/field-decimal.ts
10
- import { Buffer as Buffer2 } from "buffer";
11
- var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
12
- var MERKLE_ROOT_BYTE_LENGTH = 32;
13
- var DECIMAL_STRING_RADIX = 10;
14
- function merkleRootBufferToFrDecimal(root) {
15
- if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
16
- throw new Error(
17
- `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
18
- );
19
- }
20
- const hex = Buffer2.from(root).toString("hex");
21
- return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
22
- }
23
-
24
6
  // src/transact/private-address/codec.ts
25
7
  import {
26
8
  buildStealthAddressSignMessage,
@@ -76,18 +58,18 @@ function privateAddressSdk(sdk) {
76
58
  import { withdrawObjectFromMerkleWitness as withdrawObjectFromMerkleWitness2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
77
59
 
78
60
  // src/transact/proofs/transaction-input.ts
79
- import { Buffer as Buffer4 } from "buffer";
61
+ import { Buffer as Buffer3 } from "buffer";
80
62
  import { StrKey as StrKey2 } from "@stellar/stellar-sdk";
81
63
 
82
64
  // src/transact/stellar/token-contract.ts
83
- import { Buffer as Buffer3 } from "buffer";
65
+ import { Buffer as Buffer2 } from "buffer";
84
66
  import { StrKey } from "@stellar/stellar-sdk";
85
67
  var CONTRACT_ID_BYTE_LENGTH = 32;
86
68
  var HEX_CHARS_PER_BYTE = 2;
87
69
  var CONTRACT_ID_HEX_CHAR_LENGTH = CONTRACT_ID_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
88
70
  var CONTRACT_ID_HEX = /^(?:0x)?([0-9a-f]{64})$/i;
89
71
  function encodeContractFromHex(hexPayload) {
90
- return StrKey.encodeContract(Buffer3.from(hexPayload, "hex"));
72
+ return StrKey.encodeContract(Buffer2.from(hexPayload, "hex"));
91
73
  }
92
74
  function normalizeSorobanContractId(raw) {
93
75
  const trimmed = raw.trim();
@@ -120,14 +102,14 @@ var ZERO_PUBLIC_WITHDRAW_ADDRESS = {
120
102
  var CONTRACT_ADDRESS_HALF_BYTES = 16;
121
103
  var CONTRACT_ADDRESS_BYTES = 32;
122
104
  function bytes16ToDecimal(bytes) {
123
- return BigInt(`0x${Buffer4.from(bytes).toString("hex")}`).toString();
105
+ return BigInt(`0x${Buffer3.from(bytes).toString("hex")}`).toString();
124
106
  }
125
107
  function decimalToBytes16(value) {
126
108
  const hex = BigInt(value).toString(16).padStart(CONTRACT_ADDRESS_HALF_BYTES * 2, "0");
127
109
  if (hex.length > CONTRACT_ADDRESS_HALF_BYTES * 2) {
128
110
  throw new Error("Asset leg does not fit into 16 bytes.");
129
111
  }
130
- return Buffer4.from(hex, "hex");
112
+ return Buffer3.from(hex, "hex");
131
113
  }
132
114
  function tokenAddressFrParts(tokenAddress) {
133
115
  const normalized = resolveTokenContractId(tokenAddress);
@@ -169,7 +151,7 @@ function tokenAddressToAssetLeg(tokenAddress) {
169
151
  return [hi, lo];
170
152
  }
171
153
  function assetLegToTokenAddress(assetHi, assetLo) {
172
- const payload = Buffer4.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
154
+ const payload = Buffer3.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
173
155
  return StrKey2.encodeContract(payload);
174
156
  }
175
157
  function buildZeroPublicLegs(publicNInputs = 1, publicNOutputs = 1) {
@@ -246,11 +228,240 @@ function uniformApplicationIdHints(applicationId, nAuditSlots) {
246
228
 
247
229
  // src/transact/proofs/confidential/helpers.ts
248
230
  import { withdrawObjectFromMerkleWitness } from "@arcanetech/stellar-privacy-pool-zk-sdk";
231
+
232
+ // src/transact/merkle/lean-imt-api.ts
233
+ function asLeanImtSessionApi(sdk) {
234
+ const candidate = sdk;
235
+ if (typeof candidate.importLeanImtFromState !== "function") {
236
+ return void 0;
237
+ }
238
+ if (typeof candidate.insertTwoLeanImt !== "function") {
239
+ return void 0;
240
+ }
241
+ if (typeof candidate.exportLeanImt !== "function") {
242
+ return void 0;
243
+ }
244
+ if (typeof candidate.dropLeanImt !== "function") {
245
+ return void 0;
246
+ }
247
+ if (typeof candidate.buildWithdrawMerkleWitnessFromHandle !== "function") {
248
+ return void 0;
249
+ }
250
+ return candidate;
251
+ }
252
+
253
+ // src/transact/merkle/field-decimal.ts
254
+ import { Buffer as Buffer4 } from "buffer";
255
+ var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
256
+ var MERKLE_ROOT_BYTE_LENGTH = 32;
257
+ var DECIMAL_STRING_RADIX = 10;
258
+ function merkleRootBufferToFrDecimal(root) {
259
+ if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
260
+ throw new Error(
261
+ `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
262
+ );
263
+ }
264
+ const hex = Buffer4.from(root).toString("hex");
265
+ return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
266
+ }
267
+ var MERKLE_ROOT_HEX_PATTERN = /^[0-9a-fA-F]{64}$/;
268
+ function merkleRootHexToFrDecimal(merkleRootHex) {
269
+ const trimmed = merkleRootHex.trim();
270
+ if (!MERKLE_ROOT_HEX_PATTERN.test(trimmed)) {
271
+ return void 0;
272
+ }
273
+ return merkleRootBufferToFrDecimal(Buffer4.from(trimmed, "hex"));
274
+ }
275
+
276
+ // src/transact/merkle/state-file.ts
277
+ function merkleSnapshotToStateFile(input) {
278
+ const root = merkleRootHexToFrDecimal(input.merkleRootHex);
279
+ const nodes = input.nodes;
280
+ if (!nodes || nodes.length === 0 || root === void 0) {
281
+ return { commitments: input.commitments };
282
+ }
283
+ return {
284
+ commitments: input.commitments,
285
+ nodes,
286
+ root
287
+ };
288
+ }
289
+
290
+ // src/transact/merkle/extend-snapshot.ts
291
+ function isEvenLeafCount(count) {
292
+ return count % 2 === 0;
293
+ }
294
+ function importHandle(sdk, state) {
295
+ try {
296
+ return sdk.importLeanImtFromState(state);
297
+ } catch {
298
+ return void 0;
299
+ }
300
+ }
301
+ function insertNewPairs(input) {
302
+ for (let index = input.prefixLength; index < input.commitments.length; index += 2) {
303
+ const leafA = input.commitments.at(index);
304
+ const leafB = input.commitments.at(index + 1);
305
+ if (leafA === void 0 || leafB === void 0) {
306
+ return false;
307
+ }
308
+ try {
309
+ input.sdk.insertTwoLeanImt(input.handle, leafA, leafB);
310
+ } catch {
311
+ return false;
312
+ }
313
+ }
314
+ return true;
315
+ }
316
+ function snapshotNodes(sdk, handle) {
317
+ try {
318
+ return sdk.exportLeanImt(handle).nodes;
319
+ } catch {
320
+ return void 0;
321
+ }
322
+ }
323
+ function prefixSnapshotState(input) {
324
+ const prefixLength = input.cachedState?.nodes ? input.cachedState.commitments.length : 0;
325
+ return merkleSnapshotToStateFile({
326
+ commitments: input.commitments.slice(0, prefixLength),
327
+ merkleRootHex: input.cachedState?.merkleRootHex ?? input.merkleRootHex,
328
+ ...input.cachedState?.nodes ? { nodes: input.cachedState.nodes } : {}
329
+ });
330
+ }
331
+ function importSnapshotState(input) {
332
+ if (input.prefixState.nodes && input.prefixState.commitments.length > 0) {
333
+ return input.prefixState;
334
+ }
335
+ return merkleSnapshotToStateFile({
336
+ commitments: input.commitments,
337
+ merkleRootHex: input.merkleRootHex
338
+ });
339
+ }
340
+ function exportExtendedNodes(input) {
341
+ const inserted = input.prefixState.nodes ? insertNewPairs({
342
+ sdk: input.sdk,
343
+ handle: input.handle,
344
+ prefixLength: input.prefixState.commitments.length,
345
+ commitments: input.commitments
346
+ }) : true;
347
+ if (!inserted) {
348
+ input.sdk.dropLeanImt(input.handle);
349
+ return void 0;
350
+ }
351
+ const nodes = snapshotNodes(input.sdk, input.handle);
352
+ if (!nodes) {
353
+ input.sdk.dropLeanImt(input.handle);
354
+ }
355
+ return nodes;
356
+ }
357
+ function extendMerkleSnapshotNodes(input) {
358
+ if (!isEvenLeafCount(input.commitments.length)) {
359
+ return {};
360
+ }
361
+ const prefixState = prefixSnapshotState(input);
362
+ const handle = importHandle(
363
+ input.sdk,
364
+ importSnapshotState({ ...input, prefixState })
365
+ );
366
+ if (handle === void 0) {
367
+ return {};
368
+ }
369
+ const nodes = exportExtendedNodes({
370
+ sdk: input.sdk,
371
+ handle,
372
+ prefixState,
373
+ commitments: input.commitments
374
+ });
375
+ if (!nodes) {
376
+ return {};
377
+ }
378
+ return { nodes, handle };
379
+ }
380
+
381
+ // src/transact/merkle/tree-session.ts
382
+ var merkleSdkHost;
383
+ function registerMerkleSessionSdkHost(host) {
384
+ merkleSdkHost = host;
385
+ }
386
+ async function loadSessionSdk() {
387
+ const host = merkleSdkHost;
388
+ if (typeof host?.getInitializedSdk !== "function") {
389
+ return void 0;
390
+ }
391
+ try {
392
+ return asLeanImtSessionApi(await host.getInitializedSdk());
393
+ } catch {
394
+ return void 0;
395
+ }
396
+ }
397
+ var sessionsByPool = /* @__PURE__ */ new Map();
398
+ var handlesByKey = /* @__PURE__ */ new Map();
399
+ function merkleSessionKey(state) {
400
+ const root = state.root?.trim();
401
+ if (root) {
402
+ return `${state.commitments.length}:${root}`;
403
+ }
404
+ const first = state.commitments[0] ?? "";
405
+ const last = state.commitments.at(-1) ?? "";
406
+ return `${state.commitments.length}:${first}:${last}`;
407
+ }
408
+ function rememberHandle(poolContract, key, handle) {
409
+ const existing = sessionsByPool.get(poolContract);
410
+ if (existing && existing.handle !== handle) {
411
+ handlesByKey.delete(existing.key);
412
+ }
413
+ sessionsByPool.set(poolContract, { handle, key });
414
+ handlesByKey.set(key, handle);
415
+ }
416
+ function merkleTreeHandleForState(state) {
417
+ return handlesByKey.get(merkleSessionKey(state));
418
+ }
419
+ function bindImportedMerkleTree(input) {
420
+ rememberHandle(input.poolContract, merkleSessionKey(input.state), input.handle);
421
+ }
422
+ async function resolveExtendedMerkleNodes(input) {
423
+ const cached = input.cachedState;
424
+ if (cached && cached.commitments.length === input.commitments.length && cached.merkleRootHex === input.merkleRootHex) {
425
+ return cached.nodes;
426
+ }
427
+ const sdk = await loadSessionSdk();
428
+ if (!sdk) {
429
+ return void 0;
430
+ }
431
+ const extended = extendMerkleSnapshotNodes({
432
+ sdk,
433
+ commitments: input.commitments,
434
+ merkleRootHex: input.merkleRootHex,
435
+ ...cached ? { cachedState: cached } : {}
436
+ });
437
+ if (extended.handle !== void 0) {
438
+ bindImportedMerkleTree({
439
+ poolContract: input.poolContract,
440
+ handle: extended.handle,
441
+ state: merkleSnapshotToStateFile({
442
+ commitments: input.commitments,
443
+ merkleRootHex: input.merkleRootHex,
444
+ ...extended.nodes ? { nodes: extended.nodes } : {}
445
+ })
446
+ });
447
+ }
448
+ return extended.nodes;
449
+ }
450
+ function withdrawMerkleWitnessFromTree(input) {
451
+ const session = asLeanImtSessionApi(input.sdk);
452
+ const handle = merkleTreeHandleForState(input.state);
453
+ if (session && handle !== void 0) {
454
+ return session.buildWithdrawMerkleWitnessFromHandle(input.coin, handle);
455
+ }
456
+ return input.sdk.buildWithdrawMerkleWitness(input.coin, input.state);
457
+ }
458
+
459
+ // src/transact/proofs/confidential/helpers.ts
249
460
  var MIN_CONFIDENTIAL_TRANSFER_STROOPS = 1n;
250
461
  var ZERO_STROOPS = 0n;
251
462
  function withdrawWitnessForCoin(parameters) {
252
463
  const { sdk, coin, state } = parameters;
253
- const witness = sdk.buildWithdrawMerkleWitness(coin, state);
464
+ const witness = withdrawMerkleWitnessFromTree({ sdk, coin, state });
254
465
  const withdrawApplicationId = coin.application_id ?? parameters.applicationId;
255
466
  const withdrawObject = withdrawObjectFromMerkleWitness(
256
467
  witness,
@@ -292,14 +503,144 @@ function requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl
292
503
  }
293
504
  }
294
505
  }
295
- function changeStroopsAfterDualTransfer(transferStroops, totalNotes) {
296
- if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
297
- throw new Error("Transfer amount must be positive");
506
+
507
+ // src/transact/fees/quote-request-body.ts
508
+ function presentInstructedAmount(amount) {
509
+ const trimmed = amount?.trim() ?? "";
510
+ if (!trimmed || trimmed === "0") {
511
+ return void 0;
298
512
  }
299
- if (transferStroops > totalNotes) {
300
- throw new Error("Transfer amount exceeds combined note value");
513
+ return trimmed;
514
+ }
515
+ function feeQuoteRequestBody(request) {
516
+ const publicDepositAmount = presentInstructedAmount(request.publicDepositAmount);
517
+ const transferInstructedAmount = presentInstructedAmount(
518
+ request.transferInstructedAmount
519
+ );
520
+ const publicWithdrawalAmount = presentInstructedAmount(
521
+ request.publicWithdrawalAmount
522
+ );
523
+ return {
524
+ feeAsset: request.feeAsset,
525
+ spendsNotes: request.spendsNotes,
526
+ ...publicDepositAmount === void 0 ? {} : { publicDepositAmount },
527
+ ...transferInstructedAmount === void 0 ? {} : { transferInstructedAmount },
528
+ ...publicWithdrawalAmount === void 0 ? {} : { publicWithdrawalAmount }
529
+ };
530
+ }
531
+
532
+ // src/transact/fees/quote-required-fee.ts
533
+ function quoteHeaders(inspectAuthorization) {
534
+ const token = inspectAuthorization?.trim();
535
+ return {
536
+ "content-type": "application/json",
537
+ ...token ? { authorization: `Bearer ${token}` } : {}
538
+ };
539
+ }
540
+ function parseRequiredFee(raw) {
541
+ const text = `${raw ?? ""}`.trim();
542
+ if (!/^\d+$/u.test(text)) {
543
+ throw new Error("Fee Quote Required Fee must be an integer minor-unit string.");
544
+ }
545
+ return BigInt(text);
546
+ }
547
+ function requireQuoteString(raw, field) {
548
+ const text = `${raw ?? ""}`.trim();
549
+ if (!text) {
550
+ throw new Error(`Fee Quote is missing ${field}.`);
551
+ }
552
+ return text;
553
+ }
554
+ function parseQuotePayload(payload) {
555
+ return {
556
+ requiredFee: parseRequiredFee(payload.requiredFee),
557
+ feeAsset: requireQuoteString(payload.feeAsset, "Fee Asset"),
558
+ feeCollectorPrivateAddress: requireQuoteString(
559
+ payload.feeCollectorPrivateAddress,
560
+ "Fee Collector Private Address"
561
+ ),
562
+ feeRate: requireQuoteString(payload.feeRate, "Fee Rate")
563
+ };
564
+ }
565
+ async function quoteRequiredFee(input) {
566
+ const response = await fetch(
567
+ `${input.transactEnvironment.kyt.apiBaseUrl}/kyt/fees/quote`,
568
+ {
569
+ method: "POST",
570
+ headers: quoteHeaders(input.transactEnvironment.kyt.inspectAuthorization),
571
+ body: JSON.stringify(feeQuoteRequestBody(input.request))
572
+ }
573
+ );
574
+ const payload = await response.json();
575
+ if (!response.ok) {
576
+ throw new Error(`Fee Quote failed: ${JSON.stringify(payload)}`);
577
+ }
578
+ return parseQuotePayload(payload);
579
+ }
580
+
581
+ // src/transact/fees/should-attach-fee-output.ts
582
+ function shouldAttachFeeOutput(input) {
583
+ if (input.spendSource === "escrow" && input.escrowSend !== true) {
584
+ return false;
301
585
  }
302
- return totalNotes - transferStroops;
586
+ return input.kind === "deposit" || input.kind === "transfer" || input.kind === "withdraw";
587
+ }
588
+ function feeOutputKindFromPrepared(prepared) {
589
+ return {
590
+ kind: prepared.kind,
591
+ ...prepared.transactArtifacts?.spendSource ? { spendSource: prepared.transactArtifacts.spendSource } : {},
592
+ ...prepared.transactArtifacts?.escrowSend ? { escrowSend: true } : {}
593
+ };
594
+ }
595
+
596
+ // src/transact/fees/quote-fee-output-for-prepared.ts
597
+ function instructedMinorUnits(prepared) {
598
+ return prepared.intent.amount.toString();
599
+ }
600
+ function quoteRequestForPrepared(input) {
601
+ const amount = instructedMinorUnits(input.prepared);
602
+ if (input.prepared.kind === "deposit") {
603
+ return {
604
+ feeAsset: input.tokenAddress,
605
+ publicDepositAmount: amount,
606
+ spendsNotes: false
607
+ };
608
+ }
609
+ if (input.prepared.kind === "withdraw") {
610
+ return {
611
+ feeAsset: input.tokenAddress,
612
+ publicWithdrawalAmount: amount,
613
+ spendsNotes: true
614
+ };
615
+ }
616
+ return {
617
+ feeAsset: input.tokenAddress,
618
+ transferInstructedAmount: amount,
619
+ spendsNotes: true
620
+ };
621
+ }
622
+ async function quoteFeeOutputForPrepared(input) {
623
+ if (!shouldAttachFeeOutput(feeOutputKindFromPrepared(input.prepared))) {
624
+ return void 0;
625
+ }
626
+ const quote = await quoteRequiredFee({
627
+ transactEnvironment: input.environment,
628
+ request: quoteRequestForPrepared(input)
629
+ });
630
+ if (quote.requiredFee <= 0n) {
631
+ return void 0;
632
+ }
633
+ return {
634
+ requiredFee: quote.requiredFee,
635
+ feeCollectorPrivateAddress: quote.feeCollectorPrivateAddress
636
+ };
637
+ }
638
+ function remainingAfterRequiredFee(input) {
639
+ const remaining = input.available - input.instructed - input.requiredFee;
640
+ if (remaining < 0n) {
641
+ throw new Error("Instructed amount and Required Fee exceed available note value");
642
+ }
643
+ return remaining;
303
644
  }
304
645
 
305
646
  // src/transact/audit/parameters.ts
@@ -440,6 +781,32 @@ function privKeyScalarDecimalFromRecipientScalarHex(hexInput) {
440
781
  return canonicalBabyJubScalarFromInteger(value).toString(DECIMAL_RADIX2);
441
782
  }
442
783
 
784
+ // src/transact/fees/append-fee-output.ts
785
+ function feeCoinFromSlot(slot) {
786
+ return {
787
+ commitment_hex: slot.commitment_hex,
788
+ coin: slot.coin,
789
+ depositScalarHex: slot.depositScalarHex,
790
+ precommitementHex: slot.precommitementHex
791
+ };
792
+ }
793
+ async function appendFeeOutputSlot(input) {
794
+ const requiredFee = input.feeOutput?.requiredFee ?? ZERO_STROOPS;
795
+ const collector = input.feeOutput?.feeCollectorPrivateAddress.trim() ?? "";
796
+ if (requiredFee <= ZERO_STROOPS || !collector) {
797
+ return { deposits: input.deposits };
798
+ }
799
+ const slot = await input.buildAlignedDepositSlot({
800
+ privateAddressStpl1: collector,
801
+ amountStroops: requiredFee,
802
+ tokenAddress: input.tokenAddress
803
+ });
804
+ return {
805
+ deposits: [...input.deposits, slot.deposit],
806
+ feeCoin: feeCoinFromSlot(slot)
807
+ };
808
+ }
809
+
443
810
  // src/transact/proofs/withdraw/helpers.ts
444
811
  function destinationWithdrawFrAndScalar(parameters) {
445
812
  const pkHex = ed25519PublicKeyHexFromStellarAccount(
@@ -494,14 +861,15 @@ async function resolveWithdrawChangeDeposits(parameters) {
494
861
  }
495
862
  };
496
863
  }
497
- function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1) {
864
+ function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1, requiredFee) {
498
865
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
499
866
  throw new Error("Withdraw amount must be positive");
500
867
  }
501
- if (withdrawAmountStroops > totalNotes) {
502
- throw new Error("Withdraw amount exceeds combined note value");
503
- }
504
- const changeStroops = totalNotes - withdrawAmountStroops;
868
+ const changeStroops = remainingAfterRequiredFee({
869
+ available: totalNotes,
870
+ instructed: withdrawAmountStroops,
871
+ requiredFee
872
+ });
505
873
  requireChangeRecipientWhenPartial(changeStroops, changePrivateAddressStpl1);
506
874
  return changeStroops;
507
875
  }
@@ -514,11 +882,31 @@ function buildDualWithdrawPublicInput(parameters) {
514
882
  publicWithdrawalAmount: parameters.publicWithdrawalAmount
515
883
  });
516
884
  }
885
+ async function withdrawOutputsWithOptionalFee(parameters) {
886
+ const { deposits: changeDeposits, changeCoin } = await resolveWithdrawChangeDeposits({
887
+ changeStroops: parameters.changeStroops,
888
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
889
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
890
+ tokenAddress: parameters.tokenAddress
891
+ });
892
+ const withFee = await appendFeeOutputSlot({
893
+ deposits: changeDeposits,
894
+ tokenAddress: parameters.tokenAddress,
895
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
896
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
897
+ });
898
+ return {
899
+ deposits: withFee.deposits,
900
+ ...changeCoin ? { changeCoin } : {},
901
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
902
+ };
903
+ }
517
904
  async function prepareDualWithdrawProofInputs(parameters) {
518
905
  const changeStroops = validateDualWithdrawAmounts(
519
906
  parameters.withdrawAmountStroops,
520
907
  BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value),
521
- parameters.changePrivateAddressStpl1
908
+ parameters.changePrivateAddressStpl1,
909
+ parameters.feeOutput?.requiredFee ?? 0n
522
910
  );
523
911
  const { legA, legB } = dualWithdrawLegsWithSharedRoot({
524
912
  sdk: parameters.sdk,
@@ -533,11 +921,12 @@ async function prepareDualWithdrawProofInputs(parameters) {
533
921
  ),
534
922
  applicationId: parameters.applicationId
535
923
  });
536
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
924
+ const { deposits, changeCoin, feeCoin } = await withdrawOutputsWithOptionalFee({
537
925
  changeStroops,
538
926
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
539
927
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
540
- tokenAddress: parameters.tokenAddress
928
+ tokenAddress: parameters.tokenAddress,
929
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
541
930
  });
542
931
  const publicInput = buildDualWithdrawPublicInput({
543
932
  destinationStellarAddress: parameters.destinationStellarAddress,
@@ -556,19 +945,55 @@ async function prepareDualWithdrawProofInputs(parameters) {
556
945
  audit,
557
946
  deposits,
558
947
  withdrawLegs: [legA.withdrawObject, legB.withdrawObject],
559
- ...changeCoin ? { changeCoin } : {}
948
+ ...changeCoin ? { changeCoin } : {},
949
+ ...feeCoin ? { feeCoin } : {}
560
950
  };
561
951
  }
562
952
 
953
+ // src/transact/proofs/withdraw/fee-deposits.ts
954
+ async function withdrawDepositsWithFee(parameters) {
955
+ const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
956
+ changeStroops: parameters.changeStroops,
957
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
958
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
959
+ tokenAddress: parameters.tokenAddress
960
+ });
961
+ const withFee = await appendFeeOutputSlot({
962
+ deposits,
963
+ tokenAddress: parameters.tokenAddress,
964
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
965
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
966
+ });
967
+ return {
968
+ deposits: withFee.deposits,
969
+ ...changeCoin ? { changeCoin } : {},
970
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
971
+ };
972
+ }
973
+ function withdrawOutputApplicationIds(input) {
974
+ const outputIds = [];
975
+ if (input.hasChange) {
976
+ outputIds.push(input.applicationId);
977
+ }
978
+ if (input.hasFeeOutput) {
979
+ outputIds.push(input.applicationId);
980
+ }
981
+ if (outputIds.length === 0) {
982
+ outputIds.push("0");
983
+ }
984
+ return outputIds;
985
+ }
986
+
563
987
  // src/transact/proofs/withdraw/transact-proof.ts
564
- function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops) {
988
+ function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops, requiredFee) {
565
989
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
566
990
  throw new Error("Withdraw amount must be positive");
567
991
  }
568
- if (withdrawAmountStroops > noteStroops) {
569
- throw new Error("Withdraw amount exceeds note value");
570
- }
571
- return noteStroops - withdrawAmountStroops;
992
+ return remainingAfterRequiredFee({
993
+ available: noteStroops,
994
+ instructed: withdrawAmountStroops,
995
+ requiredFee
996
+ });
572
997
  }
573
998
  async function proveSingleWithdrawTransaction(parameters) {
574
999
  return parameters.sdk.proveTransaction(
@@ -586,10 +1011,11 @@ async function proveSingleWithdrawTransaction(parameters) {
586
1011
  );
587
1012
  }
588
1013
  function buildPrimaryWithdrawForCoin(parameters) {
589
- const witness = parameters.sdk.buildWithdrawMerkleWitness(
590
- parameters.coin,
591
- parameters.state
592
- );
1014
+ const witness = withdrawMerkleWitnessFromTree({
1015
+ sdk: parameters.sdk,
1016
+ coin: parameters.coin,
1017
+ state: parameters.state
1018
+ });
593
1019
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
594
1020
  parameters.privKeyScalarHex
595
1021
  );
@@ -607,7 +1033,8 @@ function buildPrimaryWithdrawForCoin(parameters) {
607
1033
  async function prepareSingleWithdrawProofInputs(parameters) {
608
1034
  const changeStroops = validateSingleWithdrawAmounts(
609
1035
  parameters.withdrawAmountStroops,
610
- BigInt(parameters.coin.value)
1036
+ BigInt(parameters.coin.value),
1037
+ parameters.feeOutput?.requiredFee ?? 0n
611
1038
  );
612
1039
  const { witness, primaryWithdraw } = buildPrimaryWithdrawForCoin({
613
1040
  sdk: parameters.sdk,
@@ -616,11 +1043,12 @@ async function prepareSingleWithdrawProofInputs(parameters) {
616
1043
  state: parameters.state,
617
1044
  privKeyScalarHex: parameters.privKeyScalarHex
618
1045
  });
619
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1046
+ const { deposits, changeCoin, feeCoin } = await withdrawDepositsWithFee({
620
1047
  changeStroops,
621
1048
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
622
1049
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
623
- tokenAddress: parameters.tokenAddress
1050
+ tokenAddress: parameters.tokenAddress,
1051
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
624
1052
  });
625
1053
  const publicInput = buildWithdrawPublicInput({
626
1054
  stateRoot: witness.stateRoot,
@@ -633,6 +1061,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
633
1061
  primaryWithdraw,
634
1062
  deposits,
635
1063
  changeCoin,
1064
+ feeCoin,
636
1065
  publicInput,
637
1066
  audit: buildPoolTransactionAuditParameters({
638
1067
  applicationId: parameters.applicationId,
@@ -642,7 +1071,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
642
1071
  };
643
1072
  }
644
1073
  async function proveWithdrawTransact(parameters) {
645
- const { primaryWithdraw, deposits, changeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
1074
+ const { primaryWithdraw, deposits, changeCoin, feeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
646
1075
  const proof = await proveSingleWithdrawTransaction({
647
1076
  sdk: parameters.sdk,
648
1077
  publicInput,
@@ -657,13 +1086,17 @@ async function proveWithdrawTransact(parameters) {
657
1086
  applicationIdsPlaintext: buildApplicationIdHints({
658
1087
  sdk: parameters.sdk,
659
1088
  inputIds: [parameters.applicationId],
660
- outputIds: [changeCoin ? parameters.applicationId : "0"]
1089
+ outputIds: withdrawOutputApplicationIds({
1090
+ applicationId: parameters.applicationId,
1091
+ hasChange: Boolean(changeCoin),
1092
+ hasFeeOutput: Boolean(feeCoin)
1093
+ })
661
1094
  }),
662
1095
  ...changeCoin ? { changeCoin } : {}
663
1096
  };
664
1097
  }
665
1098
  async function proveWithdrawTransactDual(parameters) {
666
- const { publicInput, audit, withdrawLegs, deposits, changeCoin } = await prepareDualWithdrawProofInputs(parameters);
1099
+ const { publicInput, audit, withdrawLegs, deposits, changeCoin, feeCoin } = await prepareDualWithdrawProofInputs(parameters);
667
1100
  const proof = await parameters.sdk.proveTransaction(
668
1101
  publicInput,
669
1102
  padPublicLegsToLayout(
@@ -682,7 +1115,11 @@ async function proveWithdrawTransactDual(parameters) {
682
1115
  applicationIdsPlaintext: buildApplicationIdHints({
683
1116
  sdk: parameters.sdk,
684
1117
  inputIds: [audit.applicationId, audit.applicationId],
685
- outputIds: [changeCoin ? audit.applicationId : "0"]
1118
+ outputIds: withdrawOutputApplicationIds({
1119
+ applicationId: audit.applicationId,
1120
+ hasChange: Boolean(changeCoin),
1121
+ hasFeeOutput: Boolean(feeCoin)
1122
+ })
686
1123
  }),
687
1124
  ...changeCoin ? { changeCoin } : {}
688
1125
  };
@@ -706,6 +1143,9 @@ var DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
706
1143
  function resolveZkConfigNonce(transactEnvironment) {
707
1144
  return transactEnvironment.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
708
1145
  }
1146
+ function transactNonceFromArtifacts(artifacts, transactEnvironment) {
1147
+ return artifacts?.zkConfigNonce ?? resolveZkConfigNonce(transactEnvironment);
1148
+ }
709
1149
 
710
1150
  // src/transact/zk/browser-artifact-cache.ts
711
1151
  import { ZK_ARTIFACT_VERSION } from "@arcanetech/stellar-privacy-pool-zk-sdk";
@@ -958,6 +1398,105 @@ async function buildAlignedDepositSlotForSdk(sdk, applicationId, parameters) {
958
1398
  };
959
1399
  }
960
1400
 
1401
+ // src/transact/pool/initialize-sdk.ts
1402
+ import { PrivacyPoolSDK as PrivacyPoolSDK2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1403
+ async function initializePrivacyPoolSdk(input, nonce) {
1404
+ const zkConfigNonce = nonce ?? input.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1405
+ const zkCircuits = await materializeSelectedZkCircuit(
1406
+ input.zkCircuits,
1407
+ zkConfigNonce,
1408
+ input.zkArtifactBaseUrl
1409
+ );
1410
+ return PrivacyPoolSDK2.init({
1411
+ wasmBinary: input.assets.sdkWasm,
1412
+ zkCircuits,
1413
+ zkConfigNonce,
1414
+ ...optionalZkArtifactBaseUrl(input.zkArtifactBaseUrl)
1415
+ });
1416
+ }
1417
+
1418
+ // src/transact/fees/zk-config-nonce-for-kind.ts
1419
+ function configuredNonceSpread(zkConfigNonce) {
1420
+ if (zkConfigNonce === void 0) {
1421
+ return {};
1422
+ }
1423
+ return { configuredNonce: zkConfigNonce };
1424
+ }
1425
+ function zkConfigNonceForFeeBearingKind(input) {
1426
+ if (input.configuredNonce === SIX_BY_SIX_BINDING_ZK_NONCE) {
1427
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1428
+ }
1429
+ if (input.kind === "transfer" && shouldAttachFeeOutput(input)) {
1430
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1431
+ }
1432
+ return input.configuredNonce ?? BINDING_ZK_NONCE;
1433
+ }
1434
+
1435
+ // src/transact/pool/prepare-deposit-proof.ts
1436
+ import {
1437
+ randomFrDecimal253,
1438
+ scalarHexToFrDecimal as scalarHexToFrDecimal2
1439
+ } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1440
+ function userDepositSlot(input) {
1441
+ return {
1442
+ value: input.coin.value,
1443
+ nullifier: input.coin.nullifier,
1444
+ ephemeralKeyScalar: scalarHexToFrDecimal2(input.depositScalarHex),
1445
+ asset: [input.coin.asset_hi, input.coin.asset_lo],
1446
+ applicationId: input.applicationId,
1447
+ recipientPublicKeys: recipientPublicKeysDecimalFromPrivateAddress(
1448
+ input.privateAddressStpl1
1449
+ )
1450
+ };
1451
+ }
1452
+ function outputApplicationIds(input) {
1453
+ if (input.hasFeeOutput) {
1454
+ return [input.applicationId, input.applicationId];
1455
+ }
1456
+ return [input.applicationId];
1457
+ }
1458
+ async function proveDepositTransact(input) {
1459
+ const publicDeposit = (input.publicDepositStroops ?? BigInt(input.coin.value)).toString();
1460
+ const withFee = await appendFeeOutputSlot({
1461
+ deposits: [userDepositSlot(input)],
1462
+ tokenAddress: input.tokenAddress,
1463
+ buildAlignedDepositSlot: input.buildAlignedDepositSlot,
1464
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
1465
+ });
1466
+ const audit = buildPoolTransactionAuditParameters({
1467
+ applicationId: input.applicationId,
1468
+ nAuditSlots: input.sdk.getLayout().nAuditSlots,
1469
+ ...input.auditPublicKey ? { auditPublicKey: input.auditPublicKey } : {}
1470
+ });
1471
+ const proof = await input.sdk.proveTransaction(
1472
+ withTokenAddressPublicInputs(
1473
+ {
1474
+ stateRoot: merkleRootBufferToFrDecimal(input.merkleRootBytes),
1475
+ privKeyScalar: randomFrDecimal253()
1476
+ },
1477
+ input.tokenAddress
1478
+ ),
1479
+ padPublicLegsToLayout(
1480
+ input.sdk,
1481
+ buildPublicDepositLegs(input.tokenAddress, publicDeposit)
1482
+ ),
1483
+ padWithdrawSlotsToLayout(input.sdk, []),
1484
+ padDepositSlotsToLayout(input.sdk, withFee.deposits),
1485
+ audit
1486
+ );
1487
+ return {
1488
+ ...proof,
1489
+ applicationIdsPlaintext: buildApplicationIdHints({
1490
+ sdk: input.sdk,
1491
+ inputIds: [],
1492
+ outputIds: outputApplicationIds({
1493
+ applicationId: input.applicationId,
1494
+ hasFeeOutput: Boolean(withFee.feeCoin)
1495
+ })
1496
+ })
1497
+ };
1498
+ }
1499
+
961
1500
  // src/transact/pool/service.ts
962
1501
  var PrivacyPoolService = class {
963
1502
  constructor(assets, applicationId, auditPublicKey, zkCircuits, zkConfigNonce, zkArtifactBaseUrl) {
@@ -974,42 +1513,50 @@ var PrivacyPoolService = class {
974
1513
  zkCircuits;
975
1514
  zkConfigNonce;
976
1515
  zkArtifactBaseUrl;
977
- sdk = void 0;
978
- initPromise = void 0;
979
- async ensureInit() {
980
- if (this.sdk) {
981
- return;
1516
+ sdks = /* @__PURE__ */ new Map();
1517
+ initPromises = /* @__PURE__ */ new Map();
1518
+ async getInitializedSdk(nonce) {
1519
+ const zkConfigNonce = nonce ?? this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1520
+ const key = zkConfigNonce.toString();
1521
+ const existing = this.sdks.get(key);
1522
+ if (existing) {
1523
+ return existing;
982
1524
  }
983
- if (this.initPromise) {
984
- return this.initPromise;
1525
+ const pending = this.initPromises.get(key);
1526
+ if (pending) {
1527
+ return pending;
985
1528
  }
986
- this.initPromise = (async () => {
987
- if (!this.assets) {
988
- throw new Error(
989
- "PrivacyPoolService requires browser ZK assets before initialization."
990
- );
991
- }
992
- const zkConfigNonce = this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
993
- const zkCircuits = await materializeSelectedZkCircuit(
994
- this.zkCircuits,
995
- zkConfigNonce,
996
- this.zkArtifactBaseUrl
997
- );
998
- this.sdk = await PrivacyPoolSDK2.init({
999
- wasmBinary: this.assets.sdkWasm,
1000
- zkCircuits,
1001
- zkConfigNonce,
1002
- ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1003
- });
1004
- })();
1005
- return this.initPromise;
1529
+ const started = this.initializeSdk(zkConfigNonce);
1530
+ this.initPromises.set(key, started);
1531
+ try {
1532
+ const sdk = await started;
1533
+ this.sdks.set(key, sdk);
1534
+ return sdk;
1535
+ } finally {
1536
+ this.initPromises.delete(key);
1537
+ }
1538
+ }
1539
+ feeBearingZkConfigNonce(input) {
1540
+ return zkConfigNonceForFeeBearingKind({
1541
+ ...input,
1542
+ ...configuredNonceSpread(this.zkConfigNonce)
1543
+ });
1006
1544
  }
1007
- async getInitializedSdk() {
1008
- await this.ensureInit();
1009
- if (!this.sdk) {
1010
- throw new Error("SDK not initialized");
1545
+ async initializeSdk(nonce) {
1546
+ if (!this.assets) {
1547
+ throw new Error(
1548
+ "PrivacyPoolService requires browser ZK assets before initialization."
1549
+ );
1011
1550
  }
1012
- return this.sdk;
1551
+ return initializePrivacyPoolSdk(
1552
+ {
1553
+ assets: this.assets,
1554
+ ...this.zkCircuits ? { zkCircuits: this.zkCircuits } : {},
1555
+ ...this.zkConfigNonce === void 0 ? {} : { zkConfigNonce: this.zkConfigNonce },
1556
+ ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1557
+ },
1558
+ nonce
1559
+ );
1013
1560
  }
1014
1561
  getAuditPublicKey() {
1015
1562
  return this.auditPublicKey;
@@ -1042,65 +1589,51 @@ var PrivacyPoolService = class {
1042
1589
  return { commitment_hex, coin, depositScalarHex, precommitementHex };
1043
1590
  }
1044
1591
  async prepareDepositTransactProof(parameters) {
1045
- const sdk = await this.getInitializedSdk();
1046
- const stateRoot = merkleRootBufferToFrDecimal(parameters.merkleRootBytes);
1047
- const recipientPublicKeys = recipientPublicKeysDecimalFromPrivateAddress(
1048
- parameters.privateAddressStpl1
1049
- );
1050
- const applicationId = this.getApplicationId();
1051
- const audit = buildPoolTransactionAuditParameters({
1052
- applicationId,
1053
- nAuditSlots: sdk.getLayout().nAuditSlots,
1054
- ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {}
1055
- });
1056
- const deposit = {
1057
- value: parameters.coin.value,
1058
- nullifier: parameters.coin.nullifier,
1059
- ephemeralKeyScalar: scalarHexToFrDecimal2(parameters.depositScalarHex),
1060
- asset: [parameters.coin.asset_hi, parameters.coin.asset_lo],
1061
- applicationId,
1062
- recipientPublicKeys
1063
- };
1064
- const publicInput = withTokenAddressPublicInputs(
1065
- {
1066
- stateRoot,
1067
- privKeyScalar: randomFrDecimal253()
1068
- },
1069
- parameters.tokenAddress
1070
- );
1071
- const proof = await sdk.proveTransaction(
1072
- publicInput,
1073
- padPublicLegsToLayout(
1074
- sdk,
1075
- buildPublicDepositLegs(parameters.tokenAddress, parameters.coin.value)
1076
- ),
1077
- padWithdrawSlotsToLayout(sdk, []),
1078
- padDepositSlotsToLayout(sdk, [deposit]),
1079
- audit
1592
+ const sdk = await this.getInitializedSdk(
1593
+ this.feeBearingZkConfigNonce({ kind: "deposit" })
1080
1594
  );
1081
- const applicationIdsPlaintext = buildApplicationIdHints({
1595
+ return proveDepositTransact({
1082
1596
  sdk,
1083
- inputIds: [],
1084
- outputIds: [applicationId]
1597
+ applicationId: this.getApplicationId(),
1598
+ privateAddressStpl1: parameters.privateAddressStpl1,
1599
+ coin: parameters.coin,
1600
+ depositScalarHex: parameters.depositScalarHex,
1601
+ merkleRootBytes: parameters.merkleRootBytes,
1602
+ tokenAddress: parameters.tokenAddress,
1603
+ buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1604
+ ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1605
+ ...parameters.publicDepositStroops === void 0 ? {} : { publicDepositStroops: parameters.publicDepositStroops },
1606
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1085
1607
  });
1086
- return { ...proof, applicationIdsPlaintext };
1087
1608
  }
1088
1609
  async calculateNullifierHash(nullifier, privKeyScalar) {
1089
1610
  const sdk = await this.getInitializedSdk();
1090
1611
  return sdk.calculateNullifierHash(nullifier, privKeyScalar);
1091
1612
  }
1092
1613
  async prepareWithdrawTransactProof(parameters) {
1093
- const sdk = await this.getInitializedSdk();
1614
+ const sdk = await this.getInitializedSdk(
1615
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1616
+ );
1094
1617
  return proveWithdrawTransact({
1095
1618
  sdk,
1096
1619
  applicationId: this.getApplicationId(),
1097
1620
  buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1098
1621
  ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1099
- ...parameters
1622
+ coin: parameters.coin,
1623
+ state: parameters.state,
1624
+ destinationStellarAddress: parameters.destinationStellarAddress,
1625
+ privKeyScalarHex: parameters.privKeyScalarHex,
1626
+ depositorEphemeralKey: parameters.depositorEphemeralKey,
1627
+ withdrawAmountStroops: parameters.withdrawAmountStroops,
1628
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1629
+ tokenAddress: parameters.tokenAddress,
1630
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1100
1631
  });
1101
1632
  }
1102
1633
  async prepareWithdrawTransactProofDual(parameters) {
1103
- const sdk = await this.getInitializedSdk();
1634
+ const sdk = await this.getInitializedSdk(
1635
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1636
+ );
1104
1637
  return proveWithdrawTransactDual({
1105
1638
  sdk,
1106
1639
  applicationId: this.getApplicationId(),
@@ -1115,7 +1648,8 @@ var PrivacyPoolService = class {
1115
1648
  ephemeralBKey: parameters.depositorEphemeralBKey,
1116
1649
  withdrawAmountStroops: parameters.withdrawAmountStroops,
1117
1650
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1118
- tokenAddress: parameters.tokenAddress
1651
+ tokenAddress: parameters.tokenAddress,
1652
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1119
1653
  });
1120
1654
  }
1121
1655
  };
@@ -1134,6 +1668,7 @@ function createPrivacyPoolService(input) {
1134
1668
  var configuredPrivacyPoolService;
1135
1669
  function configurePrivacyPoolService(service) {
1136
1670
  configuredPrivacyPoolService = service;
1671
+ registerMerkleSessionSdkHost(service);
1137
1672
  }
1138
1673
  function getPrivacyPoolService() {
1139
1674
  if (!configuredPrivacyPoolService) {
@@ -1820,7 +2355,7 @@ async function requestKytPassageForPoolInteraction(input) {
1820
2355
  const applicationId = resolvePoolApplicationId(
1821
2356
  input.transactEnvironment.network.applicationId
1822
2357
  );
1823
- const zkConfigNonce = resolveZkConfigNonce(input.transactEnvironment);
2358
+ const zkConfigNonce = input.zkConfigNonce ?? resolveZkConfigNonce(input.transactEnvironment);
1824
2359
  const applicationIdsPlaintext = input.applicationIdsPlaintext ?? buildApplicationIdHints2(
1825
2360
  applicationId,
1826
2361
  layoutForKnownNonce(zkConfigNonce).nAuditSlots
@@ -1933,6 +2468,7 @@ async function inspectRelayerSweepKyt(input) {
1933
2468
  ...input.prepared.transactArtifacts?.applicationIdsPlaintext ? {
1934
2469
  applicationIdsPlaintext: input.prepared.transactArtifacts.applicationIdsPlaintext
1935
2470
  } : {},
2471
+ ...input.prepared.transactArtifacts?.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.prepared.transactArtifacts.zkConfigNonce },
1936
2472
  networkPassphrase: environment.network.networkPassphrase,
1937
2473
  sorobanRpcUrl: environment.network.rpcUrl,
1938
2474
  transactEnvironment: environment
@@ -1945,7 +2481,10 @@ async function simulateSweepAuthEntries(input) {
1945
2481
  const operation = new Contract(environment.network.poolContract).call(
1946
2482
  "transact",
1947
2483
  new Address3(input.relayerPublicKey).toScVal(),
1948
- nativeToScVal2(resolveZkConfigNonce(environment), { type: "u64" }),
2484
+ nativeToScVal2(
2485
+ transactNonceFromArtifacts(input.prepared.transactArtifacts, environment),
2486
+ { type: "u64" }
2487
+ ),
1949
2488
  nativeToScVal2(hexToBytes(input.artifacts.proofHex), { type: "bytes" }),
1950
2489
  nativeToScVal2(hexToBytes(input.artifacts.publicHex), { type: "bytes" }),
1951
2490
  nativeToScVal2(hexToBytes(input.artifacts.ciphertextHex ?? ""), { type: "bytes" }),
@@ -1982,6 +2521,7 @@ async function unsignedEscrowAuthorizationForSweep(input) {
1982
2521
  });
1983
2522
  const auth = await simulateSweepAuthEntries({
1984
2523
  artifacts,
2524
+ prepared: input.prepared,
1985
2525
  transactEnvironment: input.transactEnvironment,
1986
2526
  relayerPublicKey,
1987
2527
  approval
@@ -2013,15 +2553,15 @@ async function loadDefaultStellarBrowserAssets(options) {
2013
2553
  }
2014
2554
 
2015
2555
  // src/transact/private-address/temporary-recipient.ts
2016
- import { PrivacyPoolSDK as PrivacyPoolSDK3 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
2556
+ import { PrivacyPoolSDK as PrivacyPoolSDK4 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
2017
2557
  var COORDINATE_HEX_LENGTH2 = 64;
2018
2558
  async function generateRandomDepositScalarHex() {
2019
2559
  await getPrivacyPoolService().getInitializedSdk();
2020
- return PrivacyPoolSDK3.generateRandomScalarHex32();
2560
+ return PrivacyPoolSDK4.generateRandomScalarHex32();
2021
2561
  }
2022
2562
  async function generateTemporaryRecipientPrivateAddress() {
2023
2563
  await getPrivacyPoolService().getInitializedSdk();
2024
- const temporaryScalarHex = PrivacyPoolSDK3.generateRandomScalarHex32();
2564
+ const temporaryScalarHex = PrivacyPoolSDK4.generateRandomScalarHex32();
2025
2565
  const sdk = await getPrivacyPoolService().getInitializedSdk();
2026
2566
  const point = sdk.ecdhEphemeralPublicKeyFromScalarHex(temporaryScalarHex);
2027
2567
  const publicKeyXHex = point.x.padStart(COORDINATE_HEX_LENGTH2, "0");
@@ -2290,6 +2830,10 @@ async function submitSorobanConfidentialTransfer(input) {
2290
2830
  networkPassphrase: input.networkPassphrase,
2291
2831
  sorobanRpcUrl: input.sorobanRpcUrl
2292
2832
  });
2833
+ const nonce = transactNonceFromArtifacts(
2834
+ input.zkConfigNonce === void 0 ? void 0 : { zkConfigNonce: input.zkConfigNonce },
2835
+ input.transactEnvironment
2836
+ );
2293
2837
  const approval = await requestKytPassageForPoolInteraction({
2294
2838
  owner: input.walletPublicKey,
2295
2839
  poolContract: input.contractId,
@@ -2298,6 +2842,7 @@ async function submitSorobanConfidentialTransfer(input) {
2298
2842
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
2299
2843
  ...input.outputNoteEphemeralScalars ? { outputNoteEphemeralScalars: input.outputNoteEphemeralScalars } : {},
2300
2844
  ...input.applicationIdsPlaintext ? { applicationIdsPlaintext: input.applicationIdsPlaintext } : {},
2845
+ ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
2301
2846
  networkPassphrase: input.networkPassphrase,
2302
2847
  sorobanRpcUrl: input.sorobanRpcUrl,
2303
2848
  transactEnvironment: input.transactEnvironment
@@ -2306,7 +2851,7 @@ async function submitSorobanConfidentialTransfer(input) {
2306
2851
  contractClient: poolClient,
2307
2852
  contractId: input.contractId,
2308
2853
  from: input.walletPublicKey,
2309
- nonce: resolveZkConfigNonce(input.transactEnvironment),
2854
+ nonce,
2310
2855
  proofHex: input.proofHex,
2311
2856
  publicHex: input.publicHex,
2312
2857
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
@@ -2328,9 +2873,6 @@ function escrowOutputFields(input) {
2328
2873
  recipientLo: input.escrowSend.recipientLo
2329
2874
  };
2330
2875
  }
2331
- function paddingDepositPair(recipientDeposit) {
2332
- return [recipientDeposit, "dummy"];
2333
- }
2334
2876
  async function buildChangeDepositPair(parameters) {
2335
2877
  const changeSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
2336
2878
  privateAddressStpl1: parameters.selfPrivateAddressStpl1ForChange.trim(),
@@ -2347,34 +2889,54 @@ async function buildChangeDepositPair(parameters) {
2347
2889
  }
2348
2890
  };
2349
2891
  }
2892
+ function recipientOnlyDeposits(recipientDeposit, hasFeeOutput) {
2893
+ if (hasFeeOutput) {
2894
+ return [recipientDeposit];
2895
+ }
2896
+ return [recipientDeposit, "dummy"];
2897
+ }
2898
+ async function depositsWithoutFee(input) {
2899
+ if (input.changeStroops <= ZERO_STROOPS) {
2900
+ return {
2901
+ deposits: recipientOnlyDeposits(input.recipientDeposit, input.hasFeeOutput)
2902
+ };
2903
+ }
2904
+ return buildChangeDepositPair({
2905
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
2906
+ changeStroops: input.changeStroops,
2907
+ tokenAddress: input.tokenAddress,
2908
+ recipientDeposit: input.recipientDeposit
2909
+ });
2910
+ }
2350
2911
  async function buildRecipientAndOptionalChangeDeposits(parameters) {
2351
2912
  const escrow = {
2352
2913
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
2353
2914
  ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
2354
2915
  };
2355
- const recipientPrivateAddress = parameters.recipientPrivateAddressStpl1.trim();
2356
2916
  const recipientSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
2357
- privateAddressStpl1: recipientPrivateAddress,
2917
+ privateAddressStpl1: parameters.recipientPrivateAddressStpl1.trim(),
2358
2918
  amountStroops: parameters.transferStroops,
2359
2919
  tokenAddress: parameters.tokenAddress,
2360
2920
  ...escrowOutputFields(escrow)
2361
2921
  });
2362
- if (parameters.changeStroops <= ZERO_STROOPS) {
2363
- return {
2364
- recipientSlot,
2365
- deposits: paddingDepositPair(recipientSlot.deposit)
2366
- };
2367
- }
2368
- const changeResult = await buildChangeDepositPair({
2369
- selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2922
+ const withoutFee = await depositsWithoutFee({
2923
+ recipientDeposit: recipientSlot.deposit,
2370
2924
  changeStroops: parameters.changeStroops,
2925
+ selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2926
+ tokenAddress: parameters.tokenAddress,
2927
+ hasFeeOutput: (parameters.feeOutput?.requiredFee ?? ZERO_STROOPS) > ZERO_STROOPS
2928
+ });
2929
+ const withFee = await appendFeeOutputSlot({
2930
+ deposits: withoutFee.deposits,
2371
2931
  tokenAddress: parameters.tokenAddress,
2372
- recipientDeposit: recipientSlot.deposit
2932
+ buildAlignedDepositSlot: (aligned) => getPrivacyPoolService().buildAlignedDepositSlot(aligned),
2933
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2373
2934
  });
2374
2935
  return {
2375
2936
  recipientSlot,
2376
- deposits: changeResult.deposits,
2377
- changeCoin: changeResult.changeCoin
2937
+ deposits: withFee.deposits,
2938
+ ..."changeCoin" in withoutFee ? { changeCoin: withoutFee.changeCoin } : {},
2939
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
2378
2940
  };
2379
2941
  }
2380
2942
 
@@ -2427,14 +2989,15 @@ function sweepOutputOwnerFields(parameters) {
2427
2989
  return { sweepOutputOwnerPubX, sweepOutputOwnerPubY };
2428
2990
  }
2429
2991
  async function buildTransferDepositsAndPublicInput(parameters) {
2430
- const { recipientSlot, deposits, changeCoin } = await buildRecipientAndOptionalChangeDeposits({
2992
+ const { recipientSlot, deposits, changeCoin, feeCoin } = await buildRecipientAndOptionalChangeDeposits({
2431
2993
  recipientPrivateAddressStpl1: parameters.recipientPrivateAddressStpl1,
2432
2994
  transferStroops: parameters.transferStroops,
2433
2995
  changeStroops: parameters.changeStroops,
2434
2996
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2435
2997
  tokenAddress: parameters.tokenAddress,
2436
2998
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
2437
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
2999
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
3000
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2438
3001
  });
2439
3002
  const publicInput = withTokenAddressPublicInputs(
2440
3003
  {
@@ -2449,6 +3012,7 @@ async function buildTransferDepositsAndPublicInput(parameters) {
2449
3012
  recipientSlot,
2450
3013
  deposits,
2451
3014
  ...changeCoin ? { changeCoin } : {},
3015
+ ...feeCoin ? { feeCoin } : {},
2452
3016
  publicInput
2453
3017
  };
2454
3018
  }
@@ -2464,7 +3028,8 @@ async function buildSenderTransferDepositsAndPublicInput(parameters) {
2464
3028
  parameters.senderPrivKeyScalarHex
2465
3029
  ),
2466
3030
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
2467
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
3031
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
3032
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2468
3033
  });
2469
3034
  }
2470
3035
 
@@ -2488,11 +3053,20 @@ function stampDualWithdrawLegs(legs, parameters) {
2488
3053
  }
2489
3054
  };
2490
3055
  }
2491
- function buildDualTransferApplicationIds(sdk, applicationId, changeCoin) {
3056
+ function buildDualTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
3057
+ const outputIds = [applicationId];
3058
+ if (changeCoin) {
3059
+ outputIds.push(applicationId);
3060
+ } else if (!feeCoin) {
3061
+ outputIds.push("0");
3062
+ }
3063
+ if (feeCoin) {
3064
+ outputIds.push(applicationId);
3065
+ }
2492
3066
  return buildApplicationIdHints({
2493
3067
  sdk,
2494
3068
  inputIds: [applicationId, applicationId],
2495
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
3069
+ outputIds
2496
3070
  });
2497
3071
  }
2498
3072
  async function proveDualConfidentialTransfer(parameters) {
@@ -2512,16 +3086,24 @@ async function proveDualConfidentialTransfer(parameters) {
2512
3086
  audit
2513
3087
  );
2514
3088
  }
2515
- async function buildDualTransferProofContext(parameters, applicationId, sdk) {
3089
+ function dualTransferChangeStroops(parameters) {
2516
3090
  const totalNotes = BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value);
2517
- const changeStroops = changeStroopsAfterDualTransfer(
2518
- parameters.transferStroops,
2519
- totalNotes
2520
- );
3091
+ if (parameters.transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
3092
+ throw new Error("Transfer amount must be positive");
3093
+ }
3094
+ const changeStroops = remainingAfterRequiredFee({
3095
+ available: totalNotes,
3096
+ instructed: parameters.transferStroops,
3097
+ requiredFee: parameters.feeOutput?.requiredFee ?? 0n
3098
+ });
2521
3099
  requireChangeRecipientWhenPartial(
2522
3100
  changeStroops,
2523
3101
  parameters.selfPrivateAddressStpl1ForChange
2524
3102
  );
3103
+ return changeStroops;
3104
+ }
3105
+ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
3106
+ const changeStroops = dualTransferChangeStroops(parameters);
2525
3107
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
2526
3108
  parameters.senderPrivKeyScalarHex
2527
3109
  );
@@ -2548,15 +3130,21 @@ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
2548
3130
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2549
3131
  tokenAddress: parameters.tokenAddress,
2550
3132
  stateRoot: legA.witness.stateRoot,
2551
- ...confidentialEscrowFields(parameters)
3133
+ ...confidentialEscrowFields(parameters),
3134
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2552
3135
  });
2553
3136
  return { legA, legB, applicationId, ...transferInputs };
2554
3137
  }
2555
3138
  async function prepareConfidentialTransferProofDual(parameters) {
2556
3139
  const poolService = getPrivacyPoolService();
2557
- const sdk = await poolService.getInitializedSdk();
3140
+ const sdk = await poolService.getInitializedSdk(
3141
+ poolService.feeBearingZkConfigNonce({
3142
+ kind: "transfer",
3143
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
3144
+ })
3145
+ );
2558
3146
  const applicationId = poolService.getApplicationId();
2559
- const { legA, legB, recipientSlot, deposits, changeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
3147
+ const { legA, legB, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
2560
3148
  const auditPublicKey = poolService.getAuditPublicKey();
2561
3149
  const proof = await (auditPublicKey ? proveDualConfidentialTransfer({
2562
3150
  sdk,
@@ -2579,7 +3167,8 @@ async function prepareConfidentialTransferProofDual(parameters) {
2579
3167
  applicationIdsPlaintext: buildDualTransferApplicationIds(
2580
3168
  sdk,
2581
3169
  applicationId,
2582
- changeCoin
3170
+ changeCoin,
3171
+ feeCoin
2583
3172
  ),
2584
3173
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
2585
3174
  ...changeCoin ? { changeCoin } : {}
@@ -2587,22 +3176,32 @@ async function prepareConfidentialTransferProofDual(parameters) {
2587
3176
  }
2588
3177
 
2589
3178
  // src/transact/proofs/confidential/single.ts
2590
- function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange) {
3179
+ function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange, requiredFee) {
2591
3180
  if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
2592
3181
  throw new Error("Transfer amount must be positive");
2593
3182
  }
2594
- if (transferStroops > noteStroops) {
2595
- throw new Error("Transfer amount exceeds note value");
2596
- }
2597
- const changeStroops = noteStroops - transferStroops;
3183
+ const changeStroops = remainingAfterRequiredFee({
3184
+ available: noteStroops,
3185
+ instructed: transferStroops,
3186
+ requiredFee
3187
+ });
2598
3188
  requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl1ForChange);
2599
3189
  return changeStroops;
2600
3190
  }
2601
- function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin) {
3191
+ function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
3192
+ const outputIds = [applicationId];
3193
+ if (changeCoin) {
3194
+ outputIds.push(applicationId);
3195
+ } else if (!feeCoin) {
3196
+ outputIds.push("0");
3197
+ }
3198
+ if (feeCoin) {
3199
+ outputIds.push(applicationId);
3200
+ }
2602
3201
  return buildApplicationIdHints({
2603
3202
  sdk,
2604
3203
  inputIds: [applicationId],
2605
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
3204
+ outputIds
2606
3205
  });
2607
3206
  }
2608
3207
  function buildTransferAuditParameters(parameters) {
@@ -2628,11 +3227,25 @@ function confidentialEscrowFields2(input) {
2628
3227
  ...input.escrowClaimantLimbs ? { escrowClaimantLimbs: input.escrowClaimantLimbs } : {}
2629
3228
  };
2630
3229
  }
3230
+ function transferDepositInputFromProof(input, changeStroops, stateRoot) {
3231
+ return {
3232
+ senderPrivKeyScalarHex: input.senderPrivKeyScalarHex,
3233
+ recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1,
3234
+ transferStroops: input.transferStroops,
3235
+ changeStroops,
3236
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
3237
+ tokenAddress: input.tokenAddress,
3238
+ stateRoot,
3239
+ ...confidentialEscrowFields2(input),
3240
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
3241
+ };
3242
+ }
2631
3243
  async function buildTransferProofInputs(parameters) {
2632
3244
  const changeStroops = validateSingleCoinTransferAmounts(
2633
3245
  parameters.input.transferStroops,
2634
3246
  parameters.noteStroops,
2635
- parameters.input.selfPrivateAddressStpl1ForChange
3247
+ parameters.input.selfPrivateAddressStpl1ForChange,
3248
+ parameters.input.feeOutput?.requiredFee ?? 0n
2636
3249
  );
2637
3250
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
2638
3251
  parameters.input.senderPrivKeyScalarHex
@@ -2651,31 +3264,30 @@ async function buildTransferProofInputs(parameters) {
2651
3264
  withdrawObject,
2652
3265
  sweepWithdrawStamp(confidentialEscrowFields2(parameters.input))
2653
3266
  );
2654
- const { recipientSlot, deposits, changeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput({
2655
- senderPrivKeyScalarHex: parameters.input.senderPrivKeyScalarHex,
2656
- recipientPrivateAddressStpl1: parameters.input.recipientPrivateAddressStpl1,
2657
- transferStroops: parameters.input.transferStroops,
2658
- changeStroops,
2659
- selfPrivateAddressStpl1ForChange: parameters.input.selfPrivateAddressStpl1ForChange,
2660
- tokenAddress: parameters.input.tokenAddress,
2661
- stateRoot: witness.stateRoot,
2662
- ...confidentialEscrowFields2(parameters.input)
2663
- });
3267
+ const { recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput(
3268
+ transferDepositInputFromProof(parameters.input, changeStroops, witness.stateRoot)
3269
+ );
2664
3270
  return {
2665
3271
  witness,
2666
3272
  withdrawObject: stampedWithdraw,
2667
3273
  recipientSlot,
2668
3274
  deposits,
2669
3275
  changeCoin,
3276
+ feeCoin,
2670
3277
  publicInput
2671
3278
  };
2672
3279
  }
2673
3280
  async function prepareConfidentialTransferProof(parameters) {
2674
3281
  const poolService = getPrivacyPoolService();
2675
- const sdk = await poolService.getInitializedSdk();
3282
+ const sdk = await poolService.getInitializedSdk(
3283
+ poolService.feeBearingZkConfigNonce({
3284
+ kind: "transfer",
3285
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
3286
+ })
3287
+ );
2676
3288
  const applicationId = poolService.getApplicationId();
2677
3289
  const noteStroops = BigInt(parameters.coin.value);
2678
- const { withdrawObject, recipientSlot, deposits, changeCoin, publicInput } = await buildTransferProofInputs({
3290
+ const { withdrawObject, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildTransferProofInputs({
2679
3291
  sdk,
2680
3292
  applicationId,
2681
3293
  input: parameters,
@@ -2698,7 +3310,8 @@ async function prepareConfidentialTransferProof(parameters) {
2698
3310
  applicationIdsPlaintext: buildSingleTransferApplicationIds(
2699
3311
  sdk,
2700
3312
  applicationId,
2701
- changeCoin
3313
+ changeCoin,
3314
+ feeCoin
2702
3315
  ),
2703
3316
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
2704
3317
  ...changeCoin ? { changeCoin } : {}
@@ -2713,7 +3326,8 @@ function poolMerkleStateToCachedView(state) {
2713
3326
  return {
2714
3327
  commitments: state.commitments,
2715
3328
  updatedAt: state.updatedAt,
2716
- merkleRootHex: state.merkleRootHex
3329
+ merkleRootHex: state.merkleRootHex,
3330
+ ...state.nodes ? { nodes: state.nodes } : {}
2717
3331
  };
2718
3332
  }
2719
3333
 
@@ -2846,6 +3460,65 @@ function reuseCachedCommitments(input) {
2846
3460
  return cachedCommitments;
2847
3461
  }
2848
3462
 
3463
+ // src/transact/merkle/persist-fetched.ts
3464
+ function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
3465
+ if (!cachedState) {
3466
+ return false;
3467
+ }
3468
+ if (cachedState.commitments.length !== commitments.length) {
3469
+ return false;
3470
+ }
3471
+ return cachedState.merkleRootHex === merkleRootHex;
3472
+ }
3473
+ function viewFromCachedMerkle(cachedState, merkleRootHex) {
3474
+ return {
3475
+ commitments: cachedState.commitments,
3476
+ updatedAt: cachedState.updatedAt,
3477
+ merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
3478
+ commitmentCount: cachedState.commitments.length,
3479
+ ...cachedState.nodes ? { nodes: cachedState.nodes } : {}
3480
+ };
3481
+ }
3482
+ function merkleStateForPersist(input) {
3483
+ return {
3484
+ poolContract: input.poolContractId,
3485
+ commitments: input.commitments,
3486
+ commitmentCount: input.commitments.length,
3487
+ merkleRootHex: input.merkleRootHex,
3488
+ updatedAt: input.updatedAt,
3489
+ ...input.nodes ? { nodes: input.nodes } : {}
3490
+ };
3491
+ }
3492
+ async function persistFetchedMerkleState(input) {
3493
+ const cachedState = input.cachedState;
3494
+ if (cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex) && cachedState) {
3495
+ return viewFromCachedMerkle(cachedState, input.merkleRootHex);
3496
+ }
3497
+ const nodes = await resolveExtendedMerkleNodes({
3498
+ poolContract: input.poolContractId,
3499
+ commitments: input.commitments,
3500
+ merkleRootHex: input.merkleRootHex,
3501
+ ...cachedState ? { cachedState } : {}
3502
+ });
3503
+ const updatedAt = Date.now();
3504
+ await input.poolMerkleState?.set(
3505
+ merkleStateForPersist({
3506
+ poolContractId: input.poolContractId,
3507
+ commitments: input.commitments,
3508
+ merkleRootHex: input.merkleRootHex,
3509
+ updatedAt,
3510
+ ...nodes ? { nodes } : {}
3511
+ })
3512
+ );
3513
+ return {
3514
+ commitments: input.commitments,
3515
+ updatedAt,
3516
+ merkleRootHex: input.merkleRootHex,
3517
+ commitmentCount: input.commitments.length,
3518
+ ...nodes ? { nodes } : {}
3519
+ };
3520
+ }
3521
+
2849
3522
  // src/transact/merkle/fetch-contract.ts
2850
3523
  async function fetchAndMergeMerkleState(parameters) {
2851
3524
  const client = createPoolClient(parameters);
@@ -2938,43 +3611,6 @@ async function loadCommitments(input) {
2938
3611
  });
2939
3612
  return [...reused, ...fetched];
2940
3613
  }
2941
- async function persistFetchedMerkleState(input) {
2942
- const cachedState = input.cachedState;
2943
- if (cachedState && cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex)) {
2944
- return viewFromCachedMerkle(cachedState, input.merkleRootHex);
2945
- }
2946
- const updatedAt = Date.now();
2947
- await input.poolMerkleState?.set({
2948
- poolContract: input.poolContractId,
2949
- commitments: input.commitments,
2950
- commitmentCount: input.commitments.length,
2951
- merkleRootHex: input.merkleRootHex,
2952
- updatedAt
2953
- });
2954
- return {
2955
- commitments: input.commitments,
2956
- updatedAt,
2957
- merkleRootHex: input.merkleRootHex,
2958
- commitmentCount: input.commitments.length
2959
- };
2960
- }
2961
- function viewFromCachedMerkle(cachedState, merkleRootHex) {
2962
- return {
2963
- commitments: cachedState.commitments,
2964
- updatedAt: cachedState.updatedAt,
2965
- merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
2966
- commitmentCount: cachedState.commitments.length
2967
- };
2968
- }
2969
- function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
2970
- if (!cachedState) {
2971
- return false;
2972
- }
2973
- if ((cachedState.merkleRootHex ?? merkleRootHex) !== merkleRootHex) {
2974
- return false;
2975
- }
2976
- return cachedState.commitments.join(",") === commitments.join(",");
2977
- }
2978
3614
 
2979
3615
  // src/transact/encoding/fr-decimal-to-bytes.ts
2980
3616
  var BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
@@ -3087,13 +3723,19 @@ var walletPrivateAddressRecordSchema = z.object({
3087
3723
  privateAddress: z.string().min(1),
3088
3724
  createdAt: z.number()
3089
3725
  });
3726
+ var leanImtNodeSchema = z.object({
3727
+ level: z.number().int().nonnegative(),
3728
+ index: z.number().int().nonnegative(),
3729
+ value: z.string().min(1)
3730
+ });
3090
3731
  var poolMerkleStateSchema = z.object({
3091
3732
  poolContract: z.string().min(1),
3092
3733
  commitments: z.array(z.string()),
3093
3734
  commitmentCount: z.number().int().nonnegative(),
3094
3735
  merkleRootHex: z.string().min(1),
3095
3736
  updatedAt: z.number(),
3096
- syncedLedger: z.number().int().optional()
3737
+ syncedLedger: z.number().int().optional(),
3738
+ nodes: z.array(leanImtNodeSchema).optional()
3097
3739
  });
3098
3740
  var leafEphemeralSchema = z.object({
3099
3741
  poolContract: z.string().min(1),
@@ -4506,6 +5148,7 @@ function createPoolMerkleService(context) {
4506
5148
  async appendPoolCommitments(input) {
4507
5149
  const current = await this.getPoolMerkleState(input.poolContract);
4508
5150
  const mergedCommitments = [...current?.commitments ?? [], ...input.commitments];
5151
+ const nodes = input.nodes ?? (input.commitments.length === 0 ? current?.nodes : void 0);
4509
5152
  await this.setPoolMerkleState(
4510
5153
  withoutUndefinedFields({
4511
5154
  poolContract: input.poolContract,
@@ -4513,7 +5156,8 @@ function createPoolMerkleService(context) {
4513
5156
  commitmentCount: mergedCommitments.length,
4514
5157
  merkleRootHex: input.merkleRootHex,
4515
5158
  updatedAt: Date.now(),
4516
- syncedLedger: input.syncedLedger ?? current?.syncedLedger
5159
+ syncedLedger: input.syncedLedger ?? current?.syncedLedger,
5160
+ nodes
4517
5161
  })
4518
5162
  );
4519
5163
  }
@@ -5042,7 +5686,11 @@ async function loadMerkleState(environment, walletPublicKey) {
5042
5686
  transactEnvironment: environment,
5043
5687
  ...environment.poolMerkleState ? { poolMerkleState: environment.poolMerkleState } : {}
5044
5688
  });
5045
- return { commitments: merged.commitments };
5689
+ return merkleSnapshotToStateFile({
5690
+ commitments: merged.commitments,
5691
+ merkleRootHex: merged.merkleRootHex,
5692
+ ...merged.nodes ? { nodes: merged.nodes } : {}
5693
+ });
5046
5694
  }
5047
5695
 
5048
5696
  // src/transact/escrow/require-escrow-spend-scalar.ts
@@ -5189,7 +5837,8 @@ async function buildSpendProofContextAtExecute(input) {
5189
5837
  input.prepared.intent.asset
5190
5838
  );
5191
5839
  const coin = requireCoinNoteFromRecord(primaryRecord);
5192
- const { commitments } = await loadMerkleState(input.environment, walletPublicKey);
5840
+ const merkleState = await loadMerkleState(input.environment, walletPublicKey);
5841
+ const commitments = merkleState.commitments;
5193
5842
  const leafIndex = findCommitmentLeafIndex(commitments, coin.commitment);
5194
5843
  if (leafIndex < 0) {
5195
5844
  throw new Error("Coin commitment not found in on-chain Merkle state.");
@@ -5206,6 +5855,7 @@ async function buildSpendProofContextAtExecute(input) {
5206
5855
  tokenAddress,
5207
5856
  coin,
5208
5857
  commitments,
5858
+ merkleState,
5209
5859
  ephemeral,
5210
5860
  senderPrivKeyScalarHex,
5211
5861
  recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1
@@ -5263,10 +5913,15 @@ function proofEscrowFields(input) {
5263
5913
  }
5264
5914
  async function buildSingleTransferProofAtExecute(input) {
5265
5915
  const context = await buildSpendProofContextAtExecute(input);
5266
- const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount;
5916
+ const feeOutput = await quoteFeeOutputForPrepared({
5917
+ prepared: input.prepared,
5918
+ environment: input.environment,
5919
+ tokenAddress: context.tokenAddress
5920
+ });
5921
+ const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5267
5922
  const proof = await prepareConfidentialTransferProof({
5268
5923
  coin: context.coin,
5269
- state: { commitments: context.commitments },
5924
+ state: context.merkleState,
5270
5925
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
5271
5926
  depositorEphemeralKey: serializeEphemeralKeyString({
5272
5927
  xHex: context.ephemeral.xHex,
@@ -5276,7 +5931,8 @@ async function buildSingleTransferProofAtExecute(input) {
5276
5931
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5277
5932
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5278
5933
  tokenAddress: context.tokenAddress,
5279
- ...proofEscrowFields(input)
5934
+ ...proofEscrowFields(input),
5935
+ ...feeOutput ? { feeOutput } : {}
5280
5936
  });
5281
5937
  return { context, proof };
5282
5938
  }
@@ -5293,11 +5949,16 @@ async function buildDualTransferProofAtExecute(input) {
5293
5949
  commitments: context.commitments
5294
5950
  });
5295
5951
  const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
5296
- const changeStroops = totalNotes - input.prepared.intent.amount;
5952
+ const feeOutput = await quoteFeeOutputForPrepared({
5953
+ prepared: input.prepared,
5954
+ environment: input.environment,
5955
+ tokenAddress: context.tokenAddress
5956
+ });
5957
+ const changeStroops = totalNotes - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5297
5958
  const proof = await prepareConfidentialTransferProofDual({
5298
5959
  coinA: context.coin,
5299
5960
  coinB: secondary.coin,
5300
- state: { commitments: context.commitments },
5961
+ state: context.merkleState,
5301
5962
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
5302
5963
  ephemeralAKey: serializeEphemeralKeyString({
5303
5964
  xHex: context.ephemeral.xHex,
@@ -5311,7 +5972,8 @@ async function buildDualTransferProofAtExecute(input) {
5311
5972
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5312
5973
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5313
5974
  tokenAddress: context.tokenAddress,
5314
- ...proofEscrowFields(input)
5975
+ ...proofEscrowFields(input),
5976
+ ...feeOutput ? { feeOutput } : {}
5315
5977
  });
5316
5978
  return { context, proof };
5317
5979
  }
@@ -5443,11 +6105,17 @@ async function runTransferFinalizeSteps(input) {
5443
6105
  walletPublicKey: input.walletPublicKey,
5444
6106
  ...recipient.escrowSend ? { escrowSendPrivateAddress: recipient.recipientPrivateAddressStpl1 } : {}
5445
6107
  });
5446
- return buildTransferFinalizeArtifacts({
5447
- proof,
5448
- context,
5449
- ...sweepFinalizeStamp(input.prepared, recipient)
5450
- });
6108
+ return {
6109
+ ...buildTransferFinalizeArtifacts({
6110
+ proof,
6111
+ context,
6112
+ ...sweepFinalizeStamp(input.prepared, recipient)
6113
+ }),
6114
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6115
+ ...feeOutputKindFromPrepared(input.prepared),
6116
+ ...configuredNonceSpread(input.environment.zkConfigNonce)
6117
+ })
6118
+ };
5451
6119
  }
5452
6120
  function sweepArtifactStamp(artifacts) {
5453
6121
  return {
@@ -5475,6 +6143,24 @@ async function finalizeTransferAtExecute(prepared, environment) {
5475
6143
  return runTransferFinalizeSteps({ prepared, environment, walletPublicKey });
5476
6144
  }
5477
6145
 
6146
+ // src/transact/engine/withdraw-fee.ts
6147
+ async function withdrawFeeProofFields(input) {
6148
+ const feeOutput = await quoteFeeOutputForPrepared({
6149
+ prepared: input.prepared,
6150
+ environment: input.environment,
6151
+ tokenAddress: input.tokenAddress
6152
+ });
6153
+ const changeStroops = remainingAfterRequiredFee({
6154
+ available: input.available,
6155
+ instructed: input.prepared.intent.amount,
6156
+ requiredFee: feeOutput?.requiredFee ?? 0n
6157
+ });
6158
+ return {
6159
+ ...feeOutput ? { feeOutput } : {},
6160
+ ...changeStroops > 0n ? { changePrivateAddressStpl1: input.withdrawFrom } : {}
6161
+ };
6162
+ }
6163
+
5478
6164
  // src/transact/engine/execute.ts
5479
6165
  function enrichWithdrawOutputRecords(prepared, proof) {
5480
6166
  if (!proof.changeCoin || prepared.outputRecords.length === 0) {
@@ -5490,7 +6176,7 @@ function enrichWithdrawOutputRecords(prepared, proof) {
5490
6176
  precommitementHex: changeCoin.precommitementHex
5491
6177
  }));
5492
6178
  }
5493
- function buildWithdrawFinalizeArtifacts(proof, context) {
6179
+ function buildWithdrawFinalizeArtifacts(proof, context, environment) {
5494
6180
  return {
5495
6181
  proofHex: proof.proof_hex,
5496
6182
  publicHex: proof.public_hex,
@@ -5498,7 +6184,11 @@ function buildWithdrawFinalizeArtifacts(proof, context) {
5498
6184
  applicationIdsPlaintext: proof.applicationIdsPlaintext,
5499
6185
  tokenAddress: context.tokenAddress,
5500
6186
  walletPublicKey: context.walletPublicKey,
5501
- executeFinalizeRequired: false
6187
+ executeFinalizeRequired: false,
6188
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6189
+ kind: "withdraw",
6190
+ ...configuredNonceSpread(environment.zkConfigNonce)
6191
+ })
5502
6192
  };
5503
6193
  }
5504
6194
  async function finalizeSingleWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
@@ -5507,10 +6197,16 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
5507
6197
  environment,
5508
6198
  recipientPrivateAddressStpl1: withdrawFrom
5509
6199
  });
5510
- const changeStroops = BigInt(context.coin.value) - prepared.intent.amount;
6200
+ const feeFields = await withdrawFeeProofFields({
6201
+ prepared,
6202
+ environment,
6203
+ tokenAddress: context.tokenAddress,
6204
+ available: BigInt(context.coin.value),
6205
+ withdrawFrom
6206
+ });
5511
6207
  const proof = await poolService.prepareWithdrawTransactProof({
5512
6208
  coin: context.coin,
5513
- state: { commitments: context.commitments },
6209
+ state: context.merkleState,
5514
6210
  destinationStellarAddress: prepared.intent.to,
5515
6211
  privKeyScalarHex: context.senderPrivKeyScalarHex,
5516
6212
  depositorEphemeralKey: serializeEphemeralKeyString({
@@ -5518,11 +6214,24 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
5518
6214
  yHex: context.ephemeral.yHex
5519
6215
  }),
5520
6216
  withdrawAmountStroops: prepared.intent.amount,
5521
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
5522
- tokenAddress: context.tokenAddress
6217
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
6218
+ tokenAddress: context.tokenAddress,
6219
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
5523
6220
  });
5524
6221
  enrichWithdrawOutputRecords(prepared, proof);
5525
- return buildWithdrawFinalizeArtifacts(proof, context);
6222
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
6223
+ }
6224
+ function dualWithdrawEphemeralKeys(input) {
6225
+ return {
6226
+ depositorEphemeralAKey: serializeEphemeralKeyString({
6227
+ xHex: input.context.ephemeral.xHex,
6228
+ yHex: input.context.ephemeral.yHex
6229
+ }),
6230
+ depositorEphemeralBKey: serializeEphemeralKeyString({
6231
+ xHex: input.secondary.ephemeral.xHex,
6232
+ yHex: input.secondary.ephemeral.yHex
6233
+ })
6234
+ };
5526
6235
  }
5527
6236
  async function finalizeDualWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
5528
6237
  const [, secondaryRecord] = prepared.consumedRecords;
@@ -5540,28 +6249,27 @@ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService,
5540
6249
  walletPublicKey: context.walletPublicKey,
5541
6250
  commitments: context.commitments
5542
6251
  });
5543
- const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
5544
- const changeStroops = totalNotes - prepared.intent.amount;
6252
+ const feeFields = await withdrawFeeProofFields({
6253
+ prepared,
6254
+ environment,
6255
+ tokenAddress: context.tokenAddress,
6256
+ available: BigInt(context.coin.value) + BigInt(secondary.coin.value),
6257
+ withdrawFrom
6258
+ });
5545
6259
  const proof = await poolService.prepareWithdrawTransactProofDual({
5546
6260
  coinA: context.coin,
5547
6261
  coinB: secondary.coin,
5548
- state: { commitments: context.commitments },
6262
+ state: context.merkleState,
5549
6263
  destinationStellarAddress: prepared.intent.to,
5550
6264
  privKeyScalarHex: context.senderPrivKeyScalarHex,
5551
- depositorEphemeralAKey: serializeEphemeralKeyString({
5552
- xHex: context.ephemeral.xHex,
5553
- yHex: context.ephemeral.yHex
5554
- }),
5555
- depositorEphemeralBKey: serializeEphemeralKeyString({
5556
- xHex: secondary.ephemeral.xHex,
5557
- yHex: secondary.ephemeral.yHex
5558
- }),
6265
+ ...dualWithdrawEphemeralKeys({ context, secondary }),
5559
6266
  withdrawAmountStroops: prepared.intent.amount,
5560
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
5561
- tokenAddress: context.tokenAddress
6267
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
6268
+ tokenAddress: context.tokenAddress,
6269
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
5562
6270
  });
5563
6271
  enrichWithdrawOutputRecords(prepared, proof);
5564
- return buildWithdrawFinalizeArtifacts(proof, context);
6272
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
5565
6273
  }
5566
6274
  async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
5567
6275
  if (prepared.kind !== "withdraw") {
@@ -5787,10 +6495,11 @@ function requirePreparedArtifacts(prepared) {
5787
6495
  };
5788
6496
  }
5789
6497
  function prepareRelayTransactPackageFromPrepared(input) {
6498
+ const zkConfigNonce = input.zkConfigNonce ?? input.prepared.transactArtifacts?.zkConfigNonce;
5790
6499
  return prepareRelayTransactPackage({
5791
6500
  poolSelector: input.poolSelector,
5792
6501
  ...requirePreparedArtifacts(input.prepared),
5793
- ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
6502
+ ...zkConfigNonce === void 0 ? {} : { zkConfigNonce },
5794
6503
  ...input.keyVersionHints ? { keyVersionHints: input.keyVersionHints } : {}
5795
6504
  });
5796
6505
  }
@@ -5900,6 +6609,7 @@ function requiredSubmissionMethod(prepared, zkConfigNonce = DEFAULT_ZK_CONFIG_NO
5900
6609
  return result.method;
5901
6610
  }
5902
6611
  export {
6612
+ BINDING_ZK_NONCE,
5903
6613
  BundledZkCircuit2 as BundledZkCircuit,
5904
6614
  COMMITMENT_V2_ZK_NONCE,
5905
6615
  DEFAULT_STEALTH_SIGN_NONCE as DEFAULT_PRIVATE_ADDRESS_SIGN_NONCE,
@@ -5928,6 +6638,7 @@ export {
5928
6638
  RELAY_TRANSACT_PACKAGE_VERSION_V1,
5929
6639
  RELAY_TRANSACT_SIGNAL_PREFIX_BYTES,
5930
6640
  RELAY_TRANSACT_SUPPORTED_SIGNAL_COUNT,
6641
+ SIX_BY_SIX_BINDING_ZK_NONCE,
5931
6642
  SIX_BY_SIX_ZK_NONCE,
5932
6643
  STANDARD_ZK_CONFIG_NONCE,
5933
6644
  SUBMISSION_METHOD,