@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.
package/dist/node.js CHANGED
@@ -466,13 +466,26 @@ function buildPrivateRecordFromDeposit(input) {
466
466
  };
467
467
  }
468
468
 
469
- // src/transact/pool/service.ts
470
- import {
471
- PrivacyPoolSDK as PrivacyPoolSDK2,
472
- decodeDecodedEphemeralKey,
473
- randomFrDecimal253,
474
- scalarHexToFrDecimal as scalarHexToFrDecimal2
475
- } from "@arcanetech/stellar-privacy-pool-zk-sdk";
469
+ // src/transact/merkle/lean-imt-api.ts
470
+ function asLeanImtSessionApi(sdk) {
471
+ const candidate = sdk;
472
+ if (typeof candidate.importLeanImtFromState !== "function") {
473
+ return void 0;
474
+ }
475
+ if (typeof candidate.insertTwoLeanImt !== "function") {
476
+ return void 0;
477
+ }
478
+ if (typeof candidate.exportLeanImt !== "function") {
479
+ return void 0;
480
+ }
481
+ if (typeof candidate.dropLeanImt !== "function") {
482
+ return void 0;
483
+ }
484
+ if (typeof candidate.buildWithdrawMerkleWitnessFromHandle !== "function") {
485
+ return void 0;
486
+ }
487
+ return candidate;
488
+ }
476
489
 
477
490
  // src/transact/merkle/field-decimal.ts
478
491
  import { Buffer as Buffer4 } from "buffer";
@@ -488,6 +501,230 @@ function merkleRootBufferToFrDecimal(root) {
488
501
  const hex = Buffer4.from(root).toString("hex");
489
502
  return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
490
503
  }
504
+ var MERKLE_ROOT_HEX_PATTERN = /^[0-9a-fA-F]{64}$/;
505
+ function merkleRootHexToFrDecimal(merkleRootHex) {
506
+ const trimmed = merkleRootHex.trim();
507
+ if (!MERKLE_ROOT_HEX_PATTERN.test(trimmed)) {
508
+ return void 0;
509
+ }
510
+ return merkleRootBufferToFrDecimal(Buffer4.from(trimmed, "hex"));
511
+ }
512
+
513
+ // src/transact/merkle/state-file.ts
514
+ function merkleSnapshotToStateFile(input) {
515
+ const root = merkleRootHexToFrDecimal(input.merkleRootHex);
516
+ const nodes = input.nodes;
517
+ if (!nodes || nodes.length === 0 || root === void 0) {
518
+ return { commitments: input.commitments };
519
+ }
520
+ return {
521
+ commitments: input.commitments,
522
+ nodes,
523
+ root
524
+ };
525
+ }
526
+
527
+ // src/transact/merkle/extend-snapshot.ts
528
+ function isEvenLeafCount(count) {
529
+ return count % 2 === 0;
530
+ }
531
+ function importHandle(sdk, state) {
532
+ try {
533
+ return sdk.importLeanImtFromState(state);
534
+ } catch {
535
+ return void 0;
536
+ }
537
+ }
538
+ function insertNewPairs(input) {
539
+ for (let index = input.prefixLength; index < input.commitments.length; index += 2) {
540
+ const leafA = input.commitments.at(index);
541
+ const leafB = input.commitments.at(index + 1);
542
+ if (leafA === void 0 || leafB === void 0) {
543
+ return false;
544
+ }
545
+ try {
546
+ input.sdk.insertTwoLeanImt(input.handle, leafA, leafB);
547
+ } catch {
548
+ return false;
549
+ }
550
+ }
551
+ return true;
552
+ }
553
+ function snapshotNodes(sdk, handle) {
554
+ try {
555
+ return sdk.exportLeanImt(handle).nodes;
556
+ } catch {
557
+ return void 0;
558
+ }
559
+ }
560
+ function prefixSnapshotState(input) {
561
+ const prefixLength = input.cachedState?.nodes ? input.cachedState.commitments.length : 0;
562
+ return merkleSnapshotToStateFile({
563
+ commitments: input.commitments.slice(0, prefixLength),
564
+ merkleRootHex: input.cachedState?.merkleRootHex ?? input.merkleRootHex,
565
+ ...input.cachedState?.nodes ? { nodes: input.cachedState.nodes } : {}
566
+ });
567
+ }
568
+ function importSnapshotState(input) {
569
+ if (input.prefixState.nodes && input.prefixState.commitments.length > 0) {
570
+ return input.prefixState;
571
+ }
572
+ return merkleSnapshotToStateFile({
573
+ commitments: input.commitments,
574
+ merkleRootHex: input.merkleRootHex
575
+ });
576
+ }
577
+ function exportExtendedNodes(input) {
578
+ const inserted = input.prefixState.nodes ? insertNewPairs({
579
+ sdk: input.sdk,
580
+ handle: input.handle,
581
+ prefixLength: input.prefixState.commitments.length,
582
+ commitments: input.commitments
583
+ }) : true;
584
+ if (!inserted) {
585
+ input.sdk.dropLeanImt(input.handle);
586
+ return void 0;
587
+ }
588
+ const nodes = snapshotNodes(input.sdk, input.handle);
589
+ if (!nodes) {
590
+ input.sdk.dropLeanImt(input.handle);
591
+ }
592
+ return nodes;
593
+ }
594
+ function extendMerkleSnapshotNodes(input) {
595
+ if (!isEvenLeafCount(input.commitments.length)) {
596
+ return {};
597
+ }
598
+ const prefixState = prefixSnapshotState(input);
599
+ const handle = importHandle(
600
+ input.sdk,
601
+ importSnapshotState({ ...input, prefixState })
602
+ );
603
+ if (handle === void 0) {
604
+ return {};
605
+ }
606
+ const nodes = exportExtendedNodes({
607
+ sdk: input.sdk,
608
+ handle,
609
+ prefixState,
610
+ commitments: input.commitments
611
+ });
612
+ if (!nodes) {
613
+ return {};
614
+ }
615
+ return { nodes, handle };
616
+ }
617
+
618
+ // src/transact/merkle/tree-session.ts
619
+ var merkleSdkHost;
620
+ function registerMerkleSessionSdkHost(host) {
621
+ merkleSdkHost = host;
622
+ }
623
+ async function loadSessionSdk() {
624
+ const host = merkleSdkHost;
625
+ if (typeof host?.getInitializedSdk !== "function") {
626
+ return void 0;
627
+ }
628
+ try {
629
+ return asLeanImtSessionApi(await host.getInitializedSdk());
630
+ } catch {
631
+ return void 0;
632
+ }
633
+ }
634
+ var sessionsByPool = /* @__PURE__ */ new Map();
635
+ var handlesByKey = /* @__PURE__ */ new Map();
636
+ function merkleSessionKey(state) {
637
+ const root = state.root?.trim();
638
+ if (root) {
639
+ return `${state.commitments.length}:${root}`;
640
+ }
641
+ const first = state.commitments[0] ?? "";
642
+ const last = state.commitments.at(-1) ?? "";
643
+ return `${state.commitments.length}:${first}:${last}`;
644
+ }
645
+ function rememberHandle(poolContract, key, handle) {
646
+ const existing = sessionsByPool.get(poolContract);
647
+ if (existing && existing.handle !== handle) {
648
+ handlesByKey.delete(existing.key);
649
+ }
650
+ sessionsByPool.set(poolContract, { handle, key });
651
+ handlesByKey.set(key, handle);
652
+ }
653
+ function importHandle2(sdk, state) {
654
+ try {
655
+ return sdk.importLeanImtFromState(state);
656
+ } catch {
657
+ return void 0;
658
+ }
659
+ }
660
+ function merkleTreeHandleForState(state) {
661
+ return handlesByKey.get(merkleSessionKey(state));
662
+ }
663
+ function bindImportedMerkleTree(input) {
664
+ rememberHandle(input.poolContract, merkleSessionKey(input.state), input.handle);
665
+ }
666
+ async function bindPoolMerkleTree(state) {
667
+ const sdk = await loadSessionSdk();
668
+ if (!sdk) {
669
+ return void 0;
670
+ }
671
+ const snapshot = merkleSnapshotToStateFile(state);
672
+ const key = merkleSessionKey(snapshot);
673
+ const existing = sessionsByPool.get(state.poolContract);
674
+ if (existing?.key === key) {
675
+ return existing.handle;
676
+ }
677
+ const handle = importHandle2(sdk, snapshot);
678
+ if (handle === void 0) {
679
+ return void 0;
680
+ }
681
+ if (existing) {
682
+ sdk.dropLeanImt(existing.handle);
683
+ }
684
+ rememberHandle(state.poolContract, key, handle);
685
+ return handle;
686
+ }
687
+ async function resolveExtendedMerkleNodes(input) {
688
+ const cached = input.cachedState;
689
+ if (cached && cached.commitments.length === input.commitments.length && cached.merkleRootHex === input.merkleRootHex) {
690
+ return cached.nodes;
691
+ }
692
+ const sdk = await loadSessionSdk();
693
+ if (!sdk) {
694
+ return void 0;
695
+ }
696
+ const extended = extendMerkleSnapshotNodes({
697
+ sdk,
698
+ commitments: input.commitments,
699
+ merkleRootHex: input.merkleRootHex,
700
+ ...cached ? { cachedState: cached } : {}
701
+ });
702
+ if (extended.handle !== void 0) {
703
+ bindImportedMerkleTree({
704
+ poolContract: input.poolContract,
705
+ handle: extended.handle,
706
+ state: merkleSnapshotToStateFile({
707
+ commitments: input.commitments,
708
+ merkleRootHex: input.merkleRootHex,
709
+ ...extended.nodes ? { nodes: extended.nodes } : {}
710
+ })
711
+ });
712
+ }
713
+ return extended.nodes;
714
+ }
715
+ function withdrawMerkleWitnessFromTree(input) {
716
+ const session = asLeanImtSessionApi(input.sdk);
717
+ const handle = merkleTreeHandleForState(input.state);
718
+ if (session && handle !== void 0) {
719
+ return session.buildWithdrawMerkleWitnessFromHandle(input.coin, handle);
720
+ }
721
+ return input.sdk.buildWithdrawMerkleWitness(input.coin, input.state);
722
+ }
723
+
724
+ // src/transact/pool/service.ts
725
+ import {
726
+ decodeDecodedEphemeralKey
727
+ } from "@arcanetech/stellar-privacy-pool-zk-sdk";
491
728
 
492
729
  // src/transact/private-address/codec.ts
493
730
  import {
@@ -700,7 +937,7 @@ var MIN_CONFIDENTIAL_TRANSFER_STROOPS = 1n;
700
937
  var ZERO_STROOPS = 0n;
701
938
  function withdrawWitnessForCoin(parameters) {
702
939
  const { sdk, coin, state } = parameters;
703
- const witness = sdk.buildWithdrawMerkleWitness(coin, state);
940
+ const witness = withdrawMerkleWitnessFromTree({ sdk, coin, state });
704
941
  const withdrawApplicationId = coin.application_id ?? parameters.applicationId;
705
942
  const withdrawObject = withdrawObjectFromMerkleWitness(
706
943
  witness,
@@ -742,14 +979,144 @@ function requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl
742
979
  }
743
980
  }
744
981
  }
745
- function changeStroopsAfterDualTransfer(transferStroops, totalNotes) {
746
- if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
747
- throw new Error("Transfer amount must be positive");
982
+
983
+ // src/transact/fees/quote-request-body.ts
984
+ function presentInstructedAmount(amount) {
985
+ const trimmed = amount?.trim() ?? "";
986
+ if (!trimmed || trimmed === "0") {
987
+ return void 0;
748
988
  }
749
- if (transferStroops > totalNotes) {
750
- throw new Error("Transfer amount exceeds combined note value");
989
+ return trimmed;
990
+ }
991
+ function feeQuoteRequestBody(request) {
992
+ const publicDepositAmount = presentInstructedAmount(request.publicDepositAmount);
993
+ const transferInstructedAmount = presentInstructedAmount(
994
+ request.transferInstructedAmount
995
+ );
996
+ const publicWithdrawalAmount = presentInstructedAmount(
997
+ request.publicWithdrawalAmount
998
+ );
999
+ return {
1000
+ feeAsset: request.feeAsset,
1001
+ spendsNotes: request.spendsNotes,
1002
+ ...publicDepositAmount === void 0 ? {} : { publicDepositAmount },
1003
+ ...transferInstructedAmount === void 0 ? {} : { transferInstructedAmount },
1004
+ ...publicWithdrawalAmount === void 0 ? {} : { publicWithdrawalAmount }
1005
+ };
1006
+ }
1007
+
1008
+ // src/transact/fees/quote-required-fee.ts
1009
+ function quoteHeaders(inspectAuthorization) {
1010
+ const token = inspectAuthorization?.trim();
1011
+ return {
1012
+ "content-type": "application/json",
1013
+ ...token ? { authorization: `Bearer ${token}` } : {}
1014
+ };
1015
+ }
1016
+ function parseRequiredFee(raw) {
1017
+ const text = `${raw ?? ""}`.trim();
1018
+ if (!/^\d+$/u.test(text)) {
1019
+ throw new Error("Fee Quote Required Fee must be an integer minor-unit string.");
751
1020
  }
752
- return totalNotes - transferStroops;
1021
+ return BigInt(text);
1022
+ }
1023
+ function requireQuoteString(raw, field) {
1024
+ const text = `${raw ?? ""}`.trim();
1025
+ if (!text) {
1026
+ throw new Error(`Fee Quote is missing ${field}.`);
1027
+ }
1028
+ return text;
1029
+ }
1030
+ function parseQuotePayload(payload) {
1031
+ return {
1032
+ requiredFee: parseRequiredFee(payload.requiredFee),
1033
+ feeAsset: requireQuoteString(payload.feeAsset, "Fee Asset"),
1034
+ feeCollectorPrivateAddress: requireQuoteString(
1035
+ payload.feeCollectorPrivateAddress,
1036
+ "Fee Collector Private Address"
1037
+ ),
1038
+ feeRate: requireQuoteString(payload.feeRate, "Fee Rate")
1039
+ };
1040
+ }
1041
+ async function quoteRequiredFee(input) {
1042
+ const response = await fetch(
1043
+ `${input.transactEnvironment.kyt.apiBaseUrl}/kyt/fees/quote`,
1044
+ {
1045
+ method: "POST",
1046
+ headers: quoteHeaders(input.transactEnvironment.kyt.inspectAuthorization),
1047
+ body: JSON.stringify(feeQuoteRequestBody(input.request))
1048
+ }
1049
+ );
1050
+ const payload = await response.json();
1051
+ if (!response.ok) {
1052
+ throw new Error(`Fee Quote failed: ${JSON.stringify(payload)}`);
1053
+ }
1054
+ return parseQuotePayload(payload);
1055
+ }
1056
+
1057
+ // src/transact/fees/should-attach-fee-output.ts
1058
+ function shouldAttachFeeOutput(input) {
1059
+ if (input.spendSource === "escrow" && input.escrowSend !== true) {
1060
+ return false;
1061
+ }
1062
+ return input.kind === "deposit" || input.kind === "transfer" || input.kind === "withdraw";
1063
+ }
1064
+ function feeOutputKindFromPrepared(prepared) {
1065
+ return {
1066
+ kind: prepared.kind,
1067
+ ...prepared.transactArtifacts?.spendSource ? { spendSource: prepared.transactArtifacts.spendSource } : {},
1068
+ ...prepared.transactArtifacts?.escrowSend ? { escrowSend: true } : {}
1069
+ };
1070
+ }
1071
+
1072
+ // src/transact/fees/quote-fee-output-for-prepared.ts
1073
+ function instructedMinorUnits(prepared) {
1074
+ return prepared.intent.amount.toString();
1075
+ }
1076
+ function quoteRequestForPrepared(input) {
1077
+ const amount = instructedMinorUnits(input.prepared);
1078
+ if (input.prepared.kind === "deposit") {
1079
+ return {
1080
+ feeAsset: input.tokenAddress,
1081
+ publicDepositAmount: amount,
1082
+ spendsNotes: false
1083
+ };
1084
+ }
1085
+ if (input.prepared.kind === "withdraw") {
1086
+ return {
1087
+ feeAsset: input.tokenAddress,
1088
+ publicWithdrawalAmount: amount,
1089
+ spendsNotes: true
1090
+ };
1091
+ }
1092
+ return {
1093
+ feeAsset: input.tokenAddress,
1094
+ transferInstructedAmount: amount,
1095
+ spendsNotes: true
1096
+ };
1097
+ }
1098
+ async function quoteFeeOutputForPrepared(input) {
1099
+ if (!shouldAttachFeeOutput(feeOutputKindFromPrepared(input.prepared))) {
1100
+ return void 0;
1101
+ }
1102
+ const quote = await quoteRequiredFee({
1103
+ transactEnvironment: input.environment,
1104
+ request: quoteRequestForPrepared(input)
1105
+ });
1106
+ if (quote.requiredFee <= 0n) {
1107
+ return void 0;
1108
+ }
1109
+ return {
1110
+ requiredFee: quote.requiredFee,
1111
+ feeCollectorPrivateAddress: quote.feeCollectorPrivateAddress
1112
+ };
1113
+ }
1114
+ function remainingAfterRequiredFee(input) {
1115
+ const remaining = input.available - input.instructed - input.requiredFee;
1116
+ if (remaining < 0n) {
1117
+ throw new Error("Instructed amount and Required Fee exceed available note value");
1118
+ }
1119
+ return remaining;
753
1120
  }
754
1121
 
755
1122
  // src/transact/audit/parameters.ts
@@ -890,6 +1257,32 @@ function privKeyScalarDecimalFromRecipientScalarHex(hexInput) {
890
1257
  return canonicalBabyJubScalarFromInteger(value).toString(DECIMAL_RADIX2);
891
1258
  }
892
1259
 
1260
+ // src/transact/fees/append-fee-output.ts
1261
+ function feeCoinFromSlot(slot) {
1262
+ return {
1263
+ commitment_hex: slot.commitment_hex,
1264
+ coin: slot.coin,
1265
+ depositScalarHex: slot.depositScalarHex,
1266
+ precommitementHex: slot.precommitementHex
1267
+ };
1268
+ }
1269
+ async function appendFeeOutputSlot(input) {
1270
+ const requiredFee = input.feeOutput?.requiredFee ?? ZERO_STROOPS;
1271
+ const collector = input.feeOutput?.feeCollectorPrivateAddress.trim() ?? "";
1272
+ if (requiredFee <= ZERO_STROOPS || !collector) {
1273
+ return { deposits: input.deposits };
1274
+ }
1275
+ const slot = await input.buildAlignedDepositSlot({
1276
+ privateAddressStpl1: collector,
1277
+ amountStroops: requiredFee,
1278
+ tokenAddress: input.tokenAddress
1279
+ });
1280
+ return {
1281
+ deposits: [...input.deposits, slot.deposit],
1282
+ feeCoin: feeCoinFromSlot(slot)
1283
+ };
1284
+ }
1285
+
893
1286
  // src/transact/proofs/withdraw/helpers.ts
894
1287
  function destinationWithdrawFrAndScalar(parameters) {
895
1288
  const pkHex = ed25519PublicKeyHexFromStellarAccount(
@@ -944,14 +1337,15 @@ async function resolveWithdrawChangeDeposits(parameters) {
944
1337
  }
945
1338
  };
946
1339
  }
947
- function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1) {
1340
+ function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1, requiredFee) {
948
1341
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
949
1342
  throw new Error("Withdraw amount must be positive");
950
1343
  }
951
- if (withdrawAmountStroops > totalNotes) {
952
- throw new Error("Withdraw amount exceeds combined note value");
953
- }
954
- const changeStroops = totalNotes - withdrawAmountStroops;
1344
+ const changeStroops = remainingAfterRequiredFee({
1345
+ available: totalNotes,
1346
+ instructed: withdrawAmountStroops,
1347
+ requiredFee
1348
+ });
955
1349
  requireChangeRecipientWhenPartial(changeStroops, changePrivateAddressStpl1);
956
1350
  return changeStroops;
957
1351
  }
@@ -964,11 +1358,31 @@ function buildDualWithdrawPublicInput(parameters) {
964
1358
  publicWithdrawalAmount: parameters.publicWithdrawalAmount
965
1359
  });
966
1360
  }
1361
+ async function withdrawOutputsWithOptionalFee(parameters) {
1362
+ const { deposits: changeDeposits, changeCoin } = await resolveWithdrawChangeDeposits({
1363
+ changeStroops: parameters.changeStroops,
1364
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1365
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1366
+ tokenAddress: parameters.tokenAddress
1367
+ });
1368
+ const withFee = await appendFeeOutputSlot({
1369
+ deposits: changeDeposits,
1370
+ tokenAddress: parameters.tokenAddress,
1371
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1372
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1373
+ });
1374
+ return {
1375
+ deposits: withFee.deposits,
1376
+ ...changeCoin ? { changeCoin } : {},
1377
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
1378
+ };
1379
+ }
967
1380
  async function prepareDualWithdrawProofInputs(parameters) {
968
1381
  const changeStroops = validateDualWithdrawAmounts(
969
1382
  parameters.withdrawAmountStroops,
970
1383
  BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value),
971
- parameters.changePrivateAddressStpl1
1384
+ parameters.changePrivateAddressStpl1,
1385
+ parameters.feeOutput?.requiredFee ?? 0n
972
1386
  );
973
1387
  const { legA, legB } = dualWithdrawLegsWithSharedRoot({
974
1388
  sdk: parameters.sdk,
@@ -983,11 +1397,12 @@ async function prepareDualWithdrawProofInputs(parameters) {
983
1397
  ),
984
1398
  applicationId: parameters.applicationId
985
1399
  });
986
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1400
+ const { deposits, changeCoin, feeCoin } = await withdrawOutputsWithOptionalFee({
987
1401
  changeStroops,
988
1402
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
989
1403
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
990
- tokenAddress: parameters.tokenAddress
1404
+ tokenAddress: parameters.tokenAddress,
1405
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
991
1406
  });
992
1407
  const publicInput = buildDualWithdrawPublicInput({
993
1408
  destinationStellarAddress: parameters.destinationStellarAddress,
@@ -1006,19 +1421,55 @@ async function prepareDualWithdrawProofInputs(parameters) {
1006
1421
  audit,
1007
1422
  deposits,
1008
1423
  withdrawLegs: [legA.withdrawObject, legB.withdrawObject],
1009
- ...changeCoin ? { changeCoin } : {}
1424
+ ...changeCoin ? { changeCoin } : {},
1425
+ ...feeCoin ? { feeCoin } : {}
1426
+ };
1427
+ }
1428
+
1429
+ // src/transact/proofs/withdraw/fee-deposits.ts
1430
+ async function withdrawDepositsWithFee(parameters) {
1431
+ const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1432
+ changeStroops: parameters.changeStroops,
1433
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1434
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1435
+ tokenAddress: parameters.tokenAddress
1436
+ });
1437
+ const withFee = await appendFeeOutputSlot({
1438
+ deposits,
1439
+ tokenAddress: parameters.tokenAddress,
1440
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1441
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1442
+ });
1443
+ return {
1444
+ deposits: withFee.deposits,
1445
+ ...changeCoin ? { changeCoin } : {},
1446
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
1010
1447
  };
1011
1448
  }
1449
+ function withdrawOutputApplicationIds(input) {
1450
+ const outputIds = [];
1451
+ if (input.hasChange) {
1452
+ outputIds.push(input.applicationId);
1453
+ }
1454
+ if (input.hasFeeOutput) {
1455
+ outputIds.push(input.applicationId);
1456
+ }
1457
+ if (outputIds.length === 0) {
1458
+ outputIds.push("0");
1459
+ }
1460
+ return outputIds;
1461
+ }
1012
1462
 
1013
1463
  // src/transact/proofs/withdraw/transact-proof.ts
1014
- function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops) {
1464
+ function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops, requiredFee) {
1015
1465
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
1016
1466
  throw new Error("Withdraw amount must be positive");
1017
1467
  }
1018
- if (withdrawAmountStroops > noteStroops) {
1019
- throw new Error("Withdraw amount exceeds note value");
1020
- }
1021
- return noteStroops - withdrawAmountStroops;
1468
+ return remainingAfterRequiredFee({
1469
+ available: noteStroops,
1470
+ instructed: withdrawAmountStroops,
1471
+ requiredFee
1472
+ });
1022
1473
  }
1023
1474
  async function proveSingleWithdrawTransaction(parameters) {
1024
1475
  return parameters.sdk.proveTransaction(
@@ -1036,10 +1487,11 @@ async function proveSingleWithdrawTransaction(parameters) {
1036
1487
  );
1037
1488
  }
1038
1489
  function buildPrimaryWithdrawForCoin(parameters) {
1039
- const witness = parameters.sdk.buildWithdrawMerkleWitness(
1040
- parameters.coin,
1041
- parameters.state
1042
- );
1490
+ const witness = withdrawMerkleWitnessFromTree({
1491
+ sdk: parameters.sdk,
1492
+ coin: parameters.coin,
1493
+ state: parameters.state
1494
+ });
1043
1495
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
1044
1496
  parameters.privKeyScalarHex
1045
1497
  );
@@ -1057,7 +1509,8 @@ function buildPrimaryWithdrawForCoin(parameters) {
1057
1509
  async function prepareSingleWithdrawProofInputs(parameters) {
1058
1510
  const changeStroops = validateSingleWithdrawAmounts(
1059
1511
  parameters.withdrawAmountStroops,
1060
- BigInt(parameters.coin.value)
1512
+ BigInt(parameters.coin.value),
1513
+ parameters.feeOutput?.requiredFee ?? 0n
1061
1514
  );
1062
1515
  const { witness, primaryWithdraw } = buildPrimaryWithdrawForCoin({
1063
1516
  sdk: parameters.sdk,
@@ -1066,11 +1519,12 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1066
1519
  state: parameters.state,
1067
1520
  privKeyScalarHex: parameters.privKeyScalarHex
1068
1521
  });
1069
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1522
+ const { deposits, changeCoin, feeCoin } = await withdrawDepositsWithFee({
1070
1523
  changeStroops,
1071
1524
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1072
1525
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1073
- tokenAddress: parameters.tokenAddress
1526
+ tokenAddress: parameters.tokenAddress,
1527
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1074
1528
  });
1075
1529
  const publicInput = buildWithdrawPublicInput({
1076
1530
  stateRoot: witness.stateRoot,
@@ -1083,6 +1537,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1083
1537
  primaryWithdraw,
1084
1538
  deposits,
1085
1539
  changeCoin,
1540
+ feeCoin,
1086
1541
  publicInput,
1087
1542
  audit: buildPoolTransactionAuditParameters({
1088
1543
  applicationId: parameters.applicationId,
@@ -1092,7 +1547,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1092
1547
  };
1093
1548
  }
1094
1549
  async function proveWithdrawTransact(parameters) {
1095
- const { primaryWithdraw, deposits, changeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
1550
+ const { primaryWithdraw, deposits, changeCoin, feeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
1096
1551
  const proof = await proveSingleWithdrawTransaction({
1097
1552
  sdk: parameters.sdk,
1098
1553
  publicInput,
@@ -1107,13 +1562,17 @@ async function proveWithdrawTransact(parameters) {
1107
1562
  applicationIdsPlaintext: buildApplicationIdHints({
1108
1563
  sdk: parameters.sdk,
1109
1564
  inputIds: [parameters.applicationId],
1110
- outputIds: [changeCoin ? parameters.applicationId : "0"]
1565
+ outputIds: withdrawOutputApplicationIds({
1566
+ applicationId: parameters.applicationId,
1567
+ hasChange: Boolean(changeCoin),
1568
+ hasFeeOutput: Boolean(feeCoin)
1569
+ })
1111
1570
  }),
1112
1571
  ...changeCoin ? { changeCoin } : {}
1113
1572
  };
1114
1573
  }
1115
1574
  async function proveWithdrawTransactDual(parameters) {
1116
- const { publicInput, audit, withdrawLegs, deposits, changeCoin } = await prepareDualWithdrawProofInputs(parameters);
1575
+ const { publicInput, audit, withdrawLegs, deposits, changeCoin, feeCoin } = await prepareDualWithdrawProofInputs(parameters);
1117
1576
  const proof = await parameters.sdk.proveTransaction(
1118
1577
  publicInput,
1119
1578
  padPublicLegsToLayout(
@@ -1132,7 +1591,11 @@ async function proveWithdrawTransactDual(parameters) {
1132
1591
  applicationIdsPlaintext: buildApplicationIdHints({
1133
1592
  sdk: parameters.sdk,
1134
1593
  inputIds: [audit.applicationId, audit.applicationId],
1135
- outputIds: [changeCoin ? audit.applicationId : "0"]
1594
+ outputIds: withdrawOutputApplicationIds({
1595
+ applicationId: audit.applicationId,
1596
+ hasChange: Boolean(changeCoin),
1597
+ hasFeeOutput: Boolean(feeCoin)
1598
+ })
1136
1599
  }),
1137
1600
  ...changeCoin ? { changeCoin } : {}
1138
1601
  };
@@ -1153,6 +1616,9 @@ var DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
1153
1616
  function resolveZkConfigNonce(transactEnvironment) {
1154
1617
  return transactEnvironment.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1155
1618
  }
1619
+ function transactNonceFromArtifacts(artifacts, transactEnvironment) {
1620
+ return artifacts?.zkConfigNonce ?? resolveZkConfigNonce(transactEnvironment);
1621
+ }
1156
1622
 
1157
1623
  // src/transact/zk/browser-artifact-cache.ts
1158
1624
  import { ZK_ARTIFACT_VERSION } from "@arcanetech/stellar-privacy-pool-zk-sdk";
@@ -1387,21 +1853,120 @@ async function buildAlignedDepositSlotForSdk(sdk, applicationId, parameters) {
1387
1853
  const recipientPublicKeys = recipientPublicKeysDecimalFromPrivateAddress(
1388
1854
  parameters.privateAddressStpl1
1389
1855
  );
1390
- const deposit = {
1391
- value: generated.coin.value,
1392
- nullifier: generated.coin.nullifier,
1393
- ephemeralKeyScalar: scalarHexToFrDecimal(scalarHex),
1394
- asset: [generated.coin.asset_hi, generated.coin.asset_lo],
1395
- applicationId: resolvedApplicationId,
1396
- recipientPublicKeys,
1397
- ...escrowDepositFields(parameters)
1398
- };
1856
+ const deposit = {
1857
+ value: generated.coin.value,
1858
+ nullifier: generated.coin.nullifier,
1859
+ ephemeralKeyScalar: scalarHexToFrDecimal(scalarHex),
1860
+ asset: [generated.coin.asset_hi, generated.coin.asset_lo],
1861
+ applicationId: resolvedApplicationId,
1862
+ recipientPublicKeys,
1863
+ ...escrowDepositFields(parameters)
1864
+ };
1865
+ return {
1866
+ deposit,
1867
+ commitment_hex: generated.commitment_hex,
1868
+ coin: generated.coin,
1869
+ depositScalarHex: scalarHex,
1870
+ precommitementHex: generated.precommitementHex
1871
+ };
1872
+ }
1873
+
1874
+ // src/transact/pool/initialize-sdk.ts
1875
+ import { PrivacyPoolSDK as PrivacyPoolSDK2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1876
+ async function initializePrivacyPoolSdk(input, nonce) {
1877
+ const zkConfigNonce = nonce ?? input.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1878
+ const zkCircuits = await materializeSelectedZkCircuit(
1879
+ input.zkCircuits,
1880
+ zkConfigNonce,
1881
+ input.zkArtifactBaseUrl
1882
+ );
1883
+ return PrivacyPoolSDK2.init({
1884
+ wasmBinary: input.assets.sdkWasm,
1885
+ zkCircuits,
1886
+ zkConfigNonce,
1887
+ ...optionalZkArtifactBaseUrl(input.zkArtifactBaseUrl)
1888
+ });
1889
+ }
1890
+
1891
+ // src/transact/fees/zk-config-nonce-for-kind.ts
1892
+ function configuredNonceSpread(zkConfigNonce) {
1893
+ if (zkConfigNonce === void 0) {
1894
+ return {};
1895
+ }
1896
+ return { configuredNonce: zkConfigNonce };
1897
+ }
1898
+ function zkConfigNonceForFeeBearingKind(input) {
1899
+ if (input.configuredNonce === SIX_BY_SIX_BINDING_ZK_NONCE) {
1900
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1901
+ }
1902
+ if (input.kind === "transfer" && shouldAttachFeeOutput(input)) {
1903
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1904
+ }
1905
+ return input.configuredNonce ?? BINDING_ZK_NONCE;
1906
+ }
1907
+
1908
+ // src/transact/pool/prepare-deposit-proof.ts
1909
+ import {
1910
+ randomFrDecimal253,
1911
+ scalarHexToFrDecimal as scalarHexToFrDecimal2
1912
+ } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1913
+ function userDepositSlot(input) {
1914
+ return {
1915
+ value: input.coin.value,
1916
+ nullifier: input.coin.nullifier,
1917
+ ephemeralKeyScalar: scalarHexToFrDecimal2(input.depositScalarHex),
1918
+ asset: [input.coin.asset_hi, input.coin.asset_lo],
1919
+ applicationId: input.applicationId,
1920
+ recipientPublicKeys: recipientPublicKeysDecimalFromPrivateAddress(
1921
+ input.privateAddressStpl1
1922
+ )
1923
+ };
1924
+ }
1925
+ function outputApplicationIds(input) {
1926
+ if (input.hasFeeOutput) {
1927
+ return [input.applicationId, input.applicationId];
1928
+ }
1929
+ return [input.applicationId];
1930
+ }
1931
+ async function proveDepositTransact(input) {
1932
+ const publicDeposit = (input.publicDepositStroops ?? BigInt(input.coin.value)).toString();
1933
+ const withFee = await appendFeeOutputSlot({
1934
+ deposits: [userDepositSlot(input)],
1935
+ tokenAddress: input.tokenAddress,
1936
+ buildAlignedDepositSlot: input.buildAlignedDepositSlot,
1937
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
1938
+ });
1939
+ const audit = buildPoolTransactionAuditParameters({
1940
+ applicationId: input.applicationId,
1941
+ nAuditSlots: input.sdk.getLayout().nAuditSlots,
1942
+ ...input.auditPublicKey ? { auditPublicKey: input.auditPublicKey } : {}
1943
+ });
1944
+ const proof = await input.sdk.proveTransaction(
1945
+ withTokenAddressPublicInputs(
1946
+ {
1947
+ stateRoot: merkleRootBufferToFrDecimal(input.merkleRootBytes),
1948
+ privKeyScalar: randomFrDecimal253()
1949
+ },
1950
+ input.tokenAddress
1951
+ ),
1952
+ padPublicLegsToLayout(
1953
+ input.sdk,
1954
+ buildPublicDepositLegs(input.tokenAddress, publicDeposit)
1955
+ ),
1956
+ padWithdrawSlotsToLayout(input.sdk, []),
1957
+ padDepositSlotsToLayout(input.sdk, withFee.deposits),
1958
+ audit
1959
+ );
1399
1960
  return {
1400
- deposit,
1401
- commitment_hex: generated.commitment_hex,
1402
- coin: generated.coin,
1403
- depositScalarHex: scalarHex,
1404
- precommitementHex: generated.precommitementHex
1961
+ ...proof,
1962
+ applicationIdsPlaintext: buildApplicationIdHints({
1963
+ sdk: input.sdk,
1964
+ inputIds: [],
1965
+ outputIds: outputApplicationIds({
1966
+ applicationId: input.applicationId,
1967
+ hasFeeOutput: Boolean(withFee.feeCoin)
1968
+ })
1969
+ })
1405
1970
  };
1406
1971
  }
1407
1972
 
@@ -1421,42 +1986,50 @@ var PrivacyPoolService = class {
1421
1986
  zkCircuits;
1422
1987
  zkConfigNonce;
1423
1988
  zkArtifactBaseUrl;
1424
- sdk = void 0;
1425
- initPromise = void 0;
1426
- async ensureInit() {
1427
- if (this.sdk) {
1428
- return;
1989
+ sdks = /* @__PURE__ */ new Map();
1990
+ initPromises = /* @__PURE__ */ new Map();
1991
+ async getInitializedSdk(nonce) {
1992
+ const zkConfigNonce = nonce ?? this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1993
+ const key = zkConfigNonce.toString();
1994
+ const existing = this.sdks.get(key);
1995
+ if (existing) {
1996
+ return existing;
1429
1997
  }
1430
- if (this.initPromise) {
1431
- return this.initPromise;
1998
+ const pending = this.initPromises.get(key);
1999
+ if (pending) {
2000
+ return pending;
1432
2001
  }
1433
- this.initPromise = (async () => {
1434
- if (!this.assets) {
1435
- throw new Error(
1436
- "PrivacyPoolService requires browser ZK assets before initialization."
1437
- );
1438
- }
1439
- const zkConfigNonce = this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1440
- const zkCircuits = await materializeSelectedZkCircuit(
1441
- this.zkCircuits,
1442
- zkConfigNonce,
1443
- this.zkArtifactBaseUrl
1444
- );
1445
- this.sdk = await PrivacyPoolSDK2.init({
1446
- wasmBinary: this.assets.sdkWasm,
1447
- zkCircuits,
1448
- zkConfigNonce,
1449
- ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1450
- });
1451
- })();
1452
- return this.initPromise;
2002
+ const started = this.initializeSdk(zkConfigNonce);
2003
+ this.initPromises.set(key, started);
2004
+ try {
2005
+ const sdk = await started;
2006
+ this.sdks.set(key, sdk);
2007
+ return sdk;
2008
+ } finally {
2009
+ this.initPromises.delete(key);
2010
+ }
2011
+ }
2012
+ feeBearingZkConfigNonce(input) {
2013
+ return zkConfigNonceForFeeBearingKind({
2014
+ ...input,
2015
+ ...configuredNonceSpread(this.zkConfigNonce)
2016
+ });
1453
2017
  }
1454
- async getInitializedSdk() {
1455
- await this.ensureInit();
1456
- if (!this.sdk) {
1457
- throw new Error("SDK not initialized");
2018
+ async initializeSdk(nonce) {
2019
+ if (!this.assets) {
2020
+ throw new Error(
2021
+ "PrivacyPoolService requires browser ZK assets before initialization."
2022
+ );
1458
2023
  }
1459
- return this.sdk;
2024
+ return initializePrivacyPoolSdk(
2025
+ {
2026
+ assets: this.assets,
2027
+ ...this.zkCircuits ? { zkCircuits: this.zkCircuits } : {},
2028
+ ...this.zkConfigNonce === void 0 ? {} : { zkConfigNonce: this.zkConfigNonce },
2029
+ ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
2030
+ },
2031
+ nonce
2032
+ );
1460
2033
  }
1461
2034
  getAuditPublicKey() {
1462
2035
  return this.auditPublicKey;
@@ -1489,65 +2062,51 @@ var PrivacyPoolService = class {
1489
2062
  return { commitment_hex, coin, depositScalarHex, precommitementHex };
1490
2063
  }
1491
2064
  async prepareDepositTransactProof(parameters) {
1492
- const sdk = await this.getInitializedSdk();
1493
- const stateRoot = merkleRootBufferToFrDecimal(parameters.merkleRootBytes);
1494
- const recipientPublicKeys = recipientPublicKeysDecimalFromPrivateAddress(
1495
- parameters.privateAddressStpl1
1496
- );
1497
- const applicationId = this.getApplicationId();
1498
- const audit = buildPoolTransactionAuditParameters({
1499
- applicationId,
1500
- nAuditSlots: sdk.getLayout().nAuditSlots,
1501
- ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {}
1502
- });
1503
- const deposit = {
1504
- value: parameters.coin.value,
1505
- nullifier: parameters.coin.nullifier,
1506
- ephemeralKeyScalar: scalarHexToFrDecimal2(parameters.depositScalarHex),
1507
- asset: [parameters.coin.asset_hi, parameters.coin.asset_lo],
1508
- applicationId,
1509
- recipientPublicKeys
1510
- };
1511
- const publicInput = withTokenAddressPublicInputs(
1512
- {
1513
- stateRoot,
1514
- privKeyScalar: randomFrDecimal253()
1515
- },
1516
- parameters.tokenAddress
2065
+ const sdk = await this.getInitializedSdk(
2066
+ this.feeBearingZkConfigNonce({ kind: "deposit" })
1517
2067
  );
1518
- const proof = await sdk.proveTransaction(
1519
- publicInput,
1520
- padPublicLegsToLayout(
1521
- sdk,
1522
- buildPublicDepositLegs(parameters.tokenAddress, parameters.coin.value)
1523
- ),
1524
- padWithdrawSlotsToLayout(sdk, []),
1525
- padDepositSlotsToLayout(sdk, [deposit]),
1526
- audit
1527
- );
1528
- const applicationIdsPlaintext = buildApplicationIdHints({
2068
+ return proveDepositTransact({
1529
2069
  sdk,
1530
- inputIds: [],
1531
- outputIds: [applicationId]
2070
+ applicationId: this.getApplicationId(),
2071
+ privateAddressStpl1: parameters.privateAddressStpl1,
2072
+ coin: parameters.coin,
2073
+ depositScalarHex: parameters.depositScalarHex,
2074
+ merkleRootBytes: parameters.merkleRootBytes,
2075
+ tokenAddress: parameters.tokenAddress,
2076
+ buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
2077
+ ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
2078
+ ...parameters.publicDepositStroops === void 0 ? {} : { publicDepositStroops: parameters.publicDepositStroops },
2079
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1532
2080
  });
1533
- return { ...proof, applicationIdsPlaintext };
1534
2081
  }
1535
2082
  async calculateNullifierHash(nullifier, privKeyScalar) {
1536
2083
  const sdk = await this.getInitializedSdk();
1537
2084
  return sdk.calculateNullifierHash(nullifier, privKeyScalar);
1538
2085
  }
1539
2086
  async prepareWithdrawTransactProof(parameters) {
1540
- const sdk = await this.getInitializedSdk();
2087
+ const sdk = await this.getInitializedSdk(
2088
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
2089
+ );
1541
2090
  return proveWithdrawTransact({
1542
2091
  sdk,
1543
2092
  applicationId: this.getApplicationId(),
1544
2093
  buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1545
2094
  ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1546
- ...parameters
2095
+ coin: parameters.coin,
2096
+ state: parameters.state,
2097
+ destinationStellarAddress: parameters.destinationStellarAddress,
2098
+ privKeyScalarHex: parameters.privKeyScalarHex,
2099
+ depositorEphemeralKey: parameters.depositorEphemeralKey,
2100
+ withdrawAmountStroops: parameters.withdrawAmountStroops,
2101
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
2102
+ tokenAddress: parameters.tokenAddress,
2103
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1547
2104
  });
1548
2105
  }
1549
2106
  async prepareWithdrawTransactProofDual(parameters) {
1550
- const sdk = await this.getInitializedSdk();
2107
+ const sdk = await this.getInitializedSdk(
2108
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
2109
+ );
1551
2110
  return proveWithdrawTransactDual({
1552
2111
  sdk,
1553
2112
  applicationId: this.getApplicationId(),
@@ -1562,7 +2121,8 @@ var PrivacyPoolService = class {
1562
2121
  ephemeralBKey: parameters.depositorEphemeralBKey,
1563
2122
  withdrawAmountStroops: parameters.withdrawAmountStroops,
1564
2123
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1565
- tokenAddress: parameters.tokenAddress
2124
+ tokenAddress: parameters.tokenAddress,
2125
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1566
2126
  });
1567
2127
  }
1568
2128
  };
@@ -1581,6 +2141,7 @@ function createPrivacyPoolService(input) {
1581
2141
  var configuredPrivacyPoolService;
1582
2142
  function configurePrivacyPoolService(service) {
1583
2143
  configuredPrivacyPoolService = service;
2144
+ registerMerkleSessionSdkHost(service);
1584
2145
  }
1585
2146
  function getPrivacyPoolService() {
1586
2147
  if (!configuredPrivacyPoolService) {
@@ -1599,7 +2160,8 @@ function poolMerkleStateToCachedView(state) {
1599
2160
  return {
1600
2161
  commitments: state.commitments,
1601
2162
  updatedAt: state.updatedAt,
1602
- merkleRootHex: state.merkleRootHex
2163
+ merkleRootHex: state.merkleRootHex,
2164
+ ...state.nodes ? { nodes: state.nodes } : {}
1603
2165
  };
1604
2166
  }
1605
2167
 
@@ -1732,6 +2294,65 @@ function reuseCachedCommitments(input) {
1732
2294
  return cachedCommitments;
1733
2295
  }
1734
2296
 
2297
+ // src/transact/merkle/persist-fetched.ts
2298
+ function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
2299
+ if (!cachedState) {
2300
+ return false;
2301
+ }
2302
+ if (cachedState.commitments.length !== commitments.length) {
2303
+ return false;
2304
+ }
2305
+ return cachedState.merkleRootHex === merkleRootHex;
2306
+ }
2307
+ function viewFromCachedMerkle(cachedState, merkleRootHex) {
2308
+ return {
2309
+ commitments: cachedState.commitments,
2310
+ updatedAt: cachedState.updatedAt,
2311
+ merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
2312
+ commitmentCount: cachedState.commitments.length,
2313
+ ...cachedState.nodes ? { nodes: cachedState.nodes } : {}
2314
+ };
2315
+ }
2316
+ function merkleStateForPersist(input) {
2317
+ return {
2318
+ poolContract: input.poolContractId,
2319
+ commitments: input.commitments,
2320
+ commitmentCount: input.commitments.length,
2321
+ merkleRootHex: input.merkleRootHex,
2322
+ updatedAt: input.updatedAt,
2323
+ ...input.nodes ? { nodes: input.nodes } : {}
2324
+ };
2325
+ }
2326
+ async function persistFetchedMerkleState(input) {
2327
+ const cachedState = input.cachedState;
2328
+ if (cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex) && cachedState) {
2329
+ return viewFromCachedMerkle(cachedState, input.merkleRootHex);
2330
+ }
2331
+ const nodes = await resolveExtendedMerkleNodes({
2332
+ poolContract: input.poolContractId,
2333
+ commitments: input.commitments,
2334
+ merkleRootHex: input.merkleRootHex,
2335
+ ...cachedState ? { cachedState } : {}
2336
+ });
2337
+ const updatedAt = Date.now();
2338
+ await input.poolMerkleState?.set(
2339
+ merkleStateForPersist({
2340
+ poolContractId: input.poolContractId,
2341
+ commitments: input.commitments,
2342
+ merkleRootHex: input.merkleRootHex,
2343
+ updatedAt,
2344
+ ...nodes ? { nodes } : {}
2345
+ })
2346
+ );
2347
+ return {
2348
+ commitments: input.commitments,
2349
+ updatedAt,
2350
+ merkleRootHex: input.merkleRootHex,
2351
+ commitmentCount: input.commitments.length,
2352
+ ...nodes ? { nodes } : {}
2353
+ };
2354
+ }
2355
+
1735
2356
  // src/transact/merkle/fetch-contract.ts
1736
2357
  async function fetchAndMergeMerkleState(parameters) {
1737
2358
  const client = createPoolClient(parameters);
@@ -1824,43 +2445,6 @@ async function loadCommitments(input) {
1824
2445
  });
1825
2446
  return [...reused, ...fetched];
1826
2447
  }
1827
- async function persistFetchedMerkleState(input) {
1828
- const cachedState = input.cachedState;
1829
- if (cachedState && cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex)) {
1830
- return viewFromCachedMerkle(cachedState, input.merkleRootHex);
1831
- }
1832
- const updatedAt = Date.now();
1833
- await input.poolMerkleState?.set({
1834
- poolContract: input.poolContractId,
1835
- commitments: input.commitments,
1836
- commitmentCount: input.commitments.length,
1837
- merkleRootHex: input.merkleRootHex,
1838
- updatedAt
1839
- });
1840
- return {
1841
- commitments: input.commitments,
1842
- updatedAt,
1843
- merkleRootHex: input.merkleRootHex,
1844
- commitmentCount: input.commitments.length
1845
- };
1846
- }
1847
- function viewFromCachedMerkle(cachedState, merkleRootHex) {
1848
- return {
1849
- commitments: cachedState.commitments,
1850
- updatedAt: cachedState.updatedAt,
1851
- merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
1852
- commitmentCount: cachedState.commitments.length
1853
- };
1854
- }
1855
- function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
1856
- if (!cachedState) {
1857
- return false;
1858
- }
1859
- if ((cachedState.merkleRootHex ?? merkleRootHex) !== merkleRootHex) {
1860
- return false;
1861
- }
1862
- return cachedState.commitments.join(",") === commitments.join(",");
1863
- }
1864
2448
 
1865
2449
  // src/transact/engine/prepare/shared.ts
1866
2450
  async function resolveTokenContractId2(environment, assetId) {
@@ -1935,7 +2519,11 @@ async function loadMerkleState(environment, walletPublicKey) {
1935
2519
  transactEnvironment: environment,
1936
2520
  ...environment.poolMerkleState ? { poolMerkleState: environment.poolMerkleState } : {}
1937
2521
  });
1938
- return { commitments: merged.commitments };
2522
+ return merkleSnapshotToStateFile({
2523
+ commitments: merged.commitments,
2524
+ merkleRootHex: merged.merkleRootHex,
2525
+ ...merged.nodes ? { nodes: merged.nodes } : {}
2526
+ });
1939
2527
  }
1940
2528
  function enrichPreparedOperation(prepared, outputRecords, artifacts) {
1941
2529
  return {
@@ -2150,6 +2738,13 @@ async function filterUnspentPrivateRecords(input) {
2150
2738
  }
2151
2739
  return unspent;
2152
2740
  }
2741
+ function spendAmountCoveringProtocolFee(amount) {
2742
+ if (amount <= 0n) {
2743
+ return amount;
2744
+ }
2745
+ const hundredths = (amount + 99n) / 100n;
2746
+ return amount + (hundredths === 0n ? 1n : hundredths);
2747
+ }
2153
2748
  function pickRecordsForSpendAmount(input) {
2154
2749
  let selectedTotal = 0n;
2155
2750
  const selected = [];
@@ -2203,7 +2798,7 @@ async function selectPrivateRecords(input) {
2203
2798
  return pickRecordsForSpendAmount({
2204
2799
  available,
2205
2800
  kind: input.kind,
2206
- amount: input.intent.amount
2801
+ amount: spendAmountCoveringProtocolFee(input.intent.amount)
2207
2802
  });
2208
2803
  }
2209
2804
  function sumRecordAmounts(records) {
@@ -2437,13 +3032,19 @@ var walletPrivateAddressRecordSchema = z.object({
2437
3032
  privateAddress: z.string().min(1),
2438
3033
  createdAt: z.number()
2439
3034
  });
3035
+ var leanImtNodeSchema = z.object({
3036
+ level: z.number().int().nonnegative(),
3037
+ index: z.number().int().nonnegative(),
3038
+ value: z.string().min(1)
3039
+ });
2440
3040
  var poolMerkleStateSchema = z.object({
2441
3041
  poolContract: z.string().min(1),
2442
3042
  commitments: z.array(z.string()),
2443
3043
  commitmentCount: z.number().int().nonnegative(),
2444
3044
  merkleRootHex: z.string().min(1),
2445
3045
  updatedAt: z.number(),
2446
- syncedLedger: z.number().int().optional()
3046
+ syncedLedger: z.number().int().optional(),
3047
+ nodes: z.array(leanImtNodeSchema).optional()
2447
3048
  });
2448
3049
  var leafEphemeralSchema = z.object({
2449
3050
  poolContract: z.string().min(1),
@@ -3856,6 +4457,7 @@ function createPoolMerkleService(context) {
3856
4457
  async appendPoolCommitments(input) {
3857
4458
  const current = await this.getPoolMerkleState(input.poolContract);
3858
4459
  const mergedCommitments = [...current?.commitments ?? [], ...input.commitments];
4460
+ const nodes = input.nodes ?? (input.commitments.length === 0 ? current?.nodes : void 0);
3859
4461
  await this.setPoolMerkleState(
3860
4462
  withoutUndefinedFields({
3861
4463
  poolContract: input.poolContract,
@@ -3863,7 +4465,8 @@ function createPoolMerkleService(context) {
3863
4465
  commitmentCount: mergedCommitments.length,
3864
4466
  merkleRootHex: input.merkleRootHex,
3865
4467
  updatedAt: Date.now(),
3866
- syncedLedger: input.syncedLedger ?? current?.syncedLedger
4468
+ syncedLedger: input.syncedLedger ?? current?.syncedLedger,
4469
+ nodes
3867
4470
  })
3868
4471
  );
3869
4472
  }
@@ -5133,12 +5736,46 @@ function createDeliveriesStateDelegates(state) {
5133
5736
  };
5134
5737
  }
5135
5738
 
5739
+ // src/transact/merkle/write-snapshot.ts
5740
+ async function writePoolMerkleState(input) {
5741
+ await input.setPoolMerkleState(input.state);
5742
+ await bindPoolMerkleTree(input.state);
5743
+ }
5744
+ async function appendPoolMerkleCommitments(input) {
5745
+ const current = await input.getPoolMerkleState(input.payload.poolContract);
5746
+ const merged = [...current?.commitments ?? [], ...input.payload.commitments];
5747
+ const nodes = input.payload.nodes ?? await resolveExtendedMerkleNodes({
5748
+ poolContract: input.payload.poolContract,
5749
+ commitments: merged,
5750
+ merkleRootHex: input.payload.merkleRootHex,
5751
+ ...current ? { cachedState: poolMerkleStateToCachedView(current) } : {}
5752
+ });
5753
+ await input.appendPoolCommitments({
5754
+ poolContract: input.payload.poolContract,
5755
+ commitments: input.payload.commitments,
5756
+ merkleRootHex: input.payload.merkleRootHex,
5757
+ ...input.payload.syncedLedger === void 0 ? {} : { syncedLedger: input.payload.syncedLedger },
5758
+ ...nodes ? { nodes } : {}
5759
+ });
5760
+ const written = await input.getPoolMerkleState(input.payload.poolContract);
5761
+ if (written) {
5762
+ await bindPoolMerkleTree(written);
5763
+ }
5764
+ }
5765
+
5136
5766
  // src/client/state-delegates/pool.ts
5137
5767
  function createPoolStateDelegates(state) {
5138
5768
  return {
5139
5769
  getPoolMerkleState: (poolContract) => state.getPoolMerkleState(poolContract),
5140
- setPoolMerkleState: (poolState) => state.setPoolMerkleState(poolState),
5141
- appendPoolCommitments: (input) => state.appendPoolCommitments(input),
5770
+ setPoolMerkleState: (poolState) => writePoolMerkleState({
5771
+ setPoolMerkleState: (next) => state.setPoolMerkleState(next),
5772
+ state: poolState
5773
+ }),
5774
+ appendPoolCommitments: (input) => appendPoolMerkleCommitments({
5775
+ getPoolMerkleState: (poolContract) => state.getPoolMerkleState(poolContract),
5776
+ appendPoolCommitments: (payload) => state.appendPoolCommitments(payload),
5777
+ payload: input
5778
+ }),
5142
5779
  getLeafEphemeral: (input) => state.getLeafEphemeral(input),
5143
5780
  setLeafEphemeral: (ephemeral) => state.setLeafEphemeral(ephemeral)
5144
5781
  };
@@ -5191,12 +5828,42 @@ function createStateDelegates(state) {
5191
5828
  };
5192
5829
  }
5193
5830
 
5831
+ // src/client/hydrate-state.ts
5832
+ async function hydratePoolMerkleStates(client, states) {
5833
+ for (const poolState of states ?? []) {
5834
+ await client.setPoolMerkleState(poolState);
5835
+ }
5836
+ }
5837
+ async function hydrateWalletAddresses(client, snapshot) {
5838
+ for (const record of snapshot.walletPrivateAddressRecords ?? []) {
5839
+ await client.saveWalletPrivateAddressRecord(record);
5840
+ }
5841
+ for (const scalar of snapshot.walletPrivateAddressScalars ?? []) {
5842
+ await client.saveWalletPrivateAddressScalar(scalar);
5843
+ }
5844
+ for (const entry of snapshot.walletDefaultPrivateAddressNonces ?? []) {
5845
+ await client.setWalletDefaultPrivateAddressNonce(entry);
5846
+ }
5847
+ }
5848
+ async function hydrateStellarPrivacyState(client, snapshot) {
5849
+ if (snapshot.assets && snapshot.assets.length > 0) {
5850
+ await client.replaceAssets(snapshot.assets);
5851
+ }
5852
+ await hydratePoolMerkleStates(client, snapshot.poolMerkleStates);
5853
+ if (snapshot.privateRecords && snapshot.privateRecords.length > 0) {
5854
+ await client.upsertPrivateRecords(snapshot.privateRecords);
5855
+ }
5856
+ await hydrateWalletAddresses(client, snapshot);
5857
+ }
5858
+
5194
5859
  // src/client/client.ts
5195
5860
  function createStellarPrivacyClientInstance(config) {
5196
5861
  const composed = composeStellarPrivacyClient(config);
5862
+ const stateDelegates = createStateDelegates(composed.state);
5197
5863
  return {
5198
5864
  state: composed.state,
5199
- ...createStateDelegates(composed.state),
5865
+ ...stateDelegates,
5866
+ hydrateState: (snapshot) => hydrateStellarPrivacyState(stateDelegates, snapshot),
5200
5867
  deposit: (intent) => composed.operations.deposit(intent),
5201
5868
  transfer: (intent) => composed.operations.transfer(intent),
5202
5869
  withdraw: (intent) => composed.operations.withdraw(intent),
@@ -5312,7 +5979,7 @@ function createStellarPrivacyClientFromResolvedConfig(config) {
5312
5979
  }
5313
5980
 
5314
5981
  // src/transact/engine/default-factory.ts
5315
- import { PrivacyPoolSDK as PrivacyPoolSDK3 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
5982
+ import { PrivacyPoolSDK as PrivacyPoolSDK4 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
5316
5983
 
5317
5984
  // src/transact/pool/proof-types.ts
5318
5985
  function ciphertextArtifactsFromProof(proof) {
@@ -5327,9 +5994,15 @@ function ciphertextArtifactsFromProof(proof) {
5327
5994
  // src/transact/engine/prepare/deposit.ts
5328
5995
  var STROOPS_PER_UNIT = 10000000n;
5329
5996
  async function createAlignedDepositWithProof(parameters) {
5997
+ const feeOutput = await quoteFeeOutputForPrepared(parameters);
5998
+ const instructed = parameters.prepared.intent.amount;
5999
+ const userAmount = instructed - (feeOutput?.requiredFee ?? 0n);
6000
+ if (userAmount <= 0n) {
6001
+ throw new Error("Required Fee leaves no spendable deposit note");
6002
+ }
5330
6003
  const aligned = await parameters.poolService.createAlignedShieldCoinData({
5331
6004
  privateAddressStpl1: parameters.prepared.intent.to,
5332
- amountStroops: parameters.prepared.intent.amount,
6005
+ amountStroops: userAmount,
5333
6006
  tokenAddress: parameters.tokenAddress
5334
6007
  });
5335
6008
  const poolClient = readPoolClientFactory(parameters.environment)({
@@ -5344,7 +6017,9 @@ async function createAlignedDepositWithProof(parameters) {
5344
6017
  coin: aligned.coin,
5345
6018
  depositScalarHex: aligned.depositScalarHex,
5346
6019
  merkleRootBytes: merkleTx.result,
5347
- tokenAddress: parameters.tokenAddress
6020
+ tokenAddress: parameters.tokenAddress,
6021
+ publicDepositStroops: instructed,
6022
+ ...feeOutput ? { feeOutput } : {}
5348
6023
  });
5349
6024
  return { ...aligned, proof };
5350
6025
  }
@@ -5363,8 +6038,8 @@ async function prepareDepositOperation(prepared, environment, poolService) {
5363
6038
  privateAddress: prepared.intent.to,
5364
6039
  assetId: prepared.intent.asset,
5365
6040
  poolContract: environment.network.poolContract,
5366
- amount: prepared.intent.amount,
5367
- amountDisplay: Number(prepared.intent.amount) / Number(STROOPS_PER_UNIT),
6041
+ amount: BigInt(depositProof.coin.value),
6042
+ amountDisplay: Number(depositProof.coin.value) / Number(STROOPS_PER_UNIT),
5368
6043
  commitmentHex: depositProof.commitment_hex,
5369
6044
  coin: depositProof.coin,
5370
6045
  depositScalarHex: depositProof.depositScalarHex,
@@ -5380,7 +6055,11 @@ async function prepareDepositOperation(prepared, environment, poolService) {
5380
6055
  depositScalarHex: depositProof.depositScalarHex,
5381
6056
  precommitementHex: depositProof.precommitementHex,
5382
6057
  commitmentHex: depositProof.commitment_hex,
5383
- walletPublicKey
6058
+ walletPublicKey,
6059
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6060
+ kind: "deposit",
6061
+ ...configuredNonceSpread(environment.zkConfigNonce)
6062
+ })
5384
6063
  });
5385
6064
  }
5386
6065
 
@@ -5505,7 +6184,8 @@ async function buildSpendProofContextAtExecute(input) {
5505
6184
  input.prepared.intent.asset
5506
6185
  );
5507
6186
  const coin = requireCoinNoteFromRecord(primaryRecord);
5508
- const { commitments } = await loadMerkleState(input.environment, walletPublicKey);
6187
+ const merkleState = await loadMerkleState(input.environment, walletPublicKey);
6188
+ const commitments = merkleState.commitments;
5509
6189
  const leafIndex = findCommitmentLeafIndex(commitments, coin.commitment);
5510
6190
  if (leafIndex < 0) {
5511
6191
  throw new Error("Coin commitment not found in on-chain Merkle state.");
@@ -5522,6 +6202,7 @@ async function buildSpendProofContextAtExecute(input) {
5522
6202
  tokenAddress,
5523
6203
  coin,
5524
6204
  commitments,
6205
+ merkleState,
5525
6206
  ephemeral,
5526
6207
  senderPrivKeyScalarHex,
5527
6208
  recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1
@@ -5554,9 +6235,6 @@ function escrowOutputFields(input) {
5554
6235
  recipientLo: input.escrowSend.recipientLo
5555
6236
  };
5556
6237
  }
5557
- function paddingDepositPair(recipientDeposit) {
5558
- return [recipientDeposit, "dummy"];
5559
- }
5560
6238
  async function buildChangeDepositPair(parameters) {
5561
6239
  const changeSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
5562
6240
  privateAddressStpl1: parameters.selfPrivateAddressStpl1ForChange.trim(),
@@ -5573,34 +6251,54 @@ async function buildChangeDepositPair(parameters) {
5573
6251
  }
5574
6252
  };
5575
6253
  }
6254
+ function recipientOnlyDeposits(recipientDeposit, hasFeeOutput) {
6255
+ if (hasFeeOutput) {
6256
+ return [recipientDeposit];
6257
+ }
6258
+ return [recipientDeposit, "dummy"];
6259
+ }
6260
+ async function depositsWithoutFee(input) {
6261
+ if (input.changeStroops <= ZERO_STROOPS) {
6262
+ return {
6263
+ deposits: recipientOnlyDeposits(input.recipientDeposit, input.hasFeeOutput)
6264
+ };
6265
+ }
6266
+ return buildChangeDepositPair({
6267
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
6268
+ changeStroops: input.changeStroops,
6269
+ tokenAddress: input.tokenAddress,
6270
+ recipientDeposit: input.recipientDeposit
6271
+ });
6272
+ }
5576
6273
  async function buildRecipientAndOptionalChangeDeposits(parameters) {
5577
6274
  const escrow = {
5578
6275
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5579
6276
  ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
5580
6277
  };
5581
- const recipientPrivateAddress = parameters.recipientPrivateAddressStpl1.trim();
5582
6278
  const recipientSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
5583
- privateAddressStpl1: recipientPrivateAddress,
6279
+ privateAddressStpl1: parameters.recipientPrivateAddressStpl1.trim(),
5584
6280
  amountStroops: parameters.transferStroops,
5585
6281
  tokenAddress: parameters.tokenAddress,
5586
6282
  ...escrowOutputFields(escrow)
5587
6283
  });
5588
- if (parameters.changeStroops <= ZERO_STROOPS) {
5589
- return {
5590
- recipientSlot,
5591
- deposits: paddingDepositPair(recipientSlot.deposit)
5592
- };
5593
- }
5594
- const changeResult = await buildChangeDepositPair({
5595
- selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
6284
+ const withoutFee = await depositsWithoutFee({
6285
+ recipientDeposit: recipientSlot.deposit,
5596
6286
  changeStroops: parameters.changeStroops,
6287
+ selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
6288
+ tokenAddress: parameters.tokenAddress,
6289
+ hasFeeOutput: (parameters.feeOutput?.requiredFee ?? ZERO_STROOPS) > ZERO_STROOPS
6290
+ });
6291
+ const withFee = await appendFeeOutputSlot({
6292
+ deposits: withoutFee.deposits,
5597
6293
  tokenAddress: parameters.tokenAddress,
5598
- recipientDeposit: recipientSlot.deposit
6294
+ buildAlignedDepositSlot: (aligned) => getPrivacyPoolService().buildAlignedDepositSlot(aligned),
6295
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5599
6296
  });
5600
6297
  return {
5601
6298
  recipientSlot,
5602
- deposits: changeResult.deposits,
5603
- changeCoin: changeResult.changeCoin
6299
+ deposits: withFee.deposits,
6300
+ ..."changeCoin" in withoutFee ? { changeCoin: withoutFee.changeCoin } : {},
6301
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
5604
6302
  };
5605
6303
  }
5606
6304
 
@@ -5653,14 +6351,15 @@ function sweepOutputOwnerFields(parameters) {
5653
6351
  return { sweepOutputOwnerPubX, sweepOutputOwnerPubY };
5654
6352
  }
5655
6353
  async function buildTransferDepositsAndPublicInput(parameters) {
5656
- const { recipientSlot, deposits, changeCoin } = await buildRecipientAndOptionalChangeDeposits({
6354
+ const { recipientSlot, deposits, changeCoin, feeCoin } = await buildRecipientAndOptionalChangeDeposits({
5657
6355
  recipientPrivateAddressStpl1: parameters.recipientPrivateAddressStpl1,
5658
6356
  transferStroops: parameters.transferStroops,
5659
6357
  changeStroops: parameters.changeStroops,
5660
6358
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
5661
6359
  tokenAddress: parameters.tokenAddress,
5662
6360
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5663
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
6361
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
6362
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5664
6363
  });
5665
6364
  const publicInput = withTokenAddressPublicInputs(
5666
6365
  {
@@ -5675,6 +6374,7 @@ async function buildTransferDepositsAndPublicInput(parameters) {
5675
6374
  recipientSlot,
5676
6375
  deposits,
5677
6376
  ...changeCoin ? { changeCoin } : {},
6377
+ ...feeCoin ? { feeCoin } : {},
5678
6378
  publicInput
5679
6379
  };
5680
6380
  }
@@ -5690,7 +6390,8 @@ async function buildSenderTransferDepositsAndPublicInput(parameters) {
5690
6390
  parameters.senderPrivKeyScalarHex
5691
6391
  ),
5692
6392
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5693
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
6393
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
6394
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5694
6395
  });
5695
6396
  }
5696
6397
 
@@ -5714,11 +6415,20 @@ function stampDualWithdrawLegs(legs, parameters) {
5714
6415
  }
5715
6416
  };
5716
6417
  }
5717
- function buildDualTransferApplicationIds(sdk, applicationId, changeCoin) {
6418
+ function buildDualTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
6419
+ const outputIds = [applicationId];
6420
+ if (changeCoin) {
6421
+ outputIds.push(applicationId);
6422
+ } else if (!feeCoin) {
6423
+ outputIds.push("0");
6424
+ }
6425
+ if (feeCoin) {
6426
+ outputIds.push(applicationId);
6427
+ }
5718
6428
  return buildApplicationIdHints({
5719
6429
  sdk,
5720
6430
  inputIds: [applicationId, applicationId],
5721
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
6431
+ outputIds
5722
6432
  });
5723
6433
  }
5724
6434
  async function proveDualConfidentialTransfer(parameters) {
@@ -5738,16 +6448,24 @@ async function proveDualConfidentialTransfer(parameters) {
5738
6448
  audit
5739
6449
  );
5740
6450
  }
5741
- async function buildDualTransferProofContext(parameters, applicationId, sdk) {
6451
+ function dualTransferChangeStroops(parameters) {
5742
6452
  const totalNotes = BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value);
5743
- const changeStroops = changeStroopsAfterDualTransfer(
5744
- parameters.transferStroops,
5745
- totalNotes
5746
- );
6453
+ if (parameters.transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
6454
+ throw new Error("Transfer amount must be positive");
6455
+ }
6456
+ const changeStroops = remainingAfterRequiredFee({
6457
+ available: totalNotes,
6458
+ instructed: parameters.transferStroops,
6459
+ requiredFee: parameters.feeOutput?.requiredFee ?? 0n
6460
+ });
5747
6461
  requireChangeRecipientWhenPartial(
5748
6462
  changeStroops,
5749
6463
  parameters.selfPrivateAddressStpl1ForChange
5750
6464
  );
6465
+ return changeStroops;
6466
+ }
6467
+ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
6468
+ const changeStroops = dualTransferChangeStroops(parameters);
5751
6469
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
5752
6470
  parameters.senderPrivKeyScalarHex
5753
6471
  );
@@ -5774,15 +6492,21 @@ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
5774
6492
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
5775
6493
  tokenAddress: parameters.tokenAddress,
5776
6494
  stateRoot: legA.witness.stateRoot,
5777
- ...confidentialEscrowFields(parameters)
6495
+ ...confidentialEscrowFields(parameters),
6496
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5778
6497
  });
5779
6498
  return { legA, legB, applicationId, ...transferInputs };
5780
6499
  }
5781
6500
  async function prepareConfidentialTransferProofDual(parameters) {
5782
6501
  const poolService = getPrivacyPoolService();
5783
- const sdk = await poolService.getInitializedSdk();
6502
+ const sdk = await poolService.getInitializedSdk(
6503
+ poolService.feeBearingZkConfigNonce({
6504
+ kind: "transfer",
6505
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
6506
+ })
6507
+ );
5784
6508
  const applicationId = poolService.getApplicationId();
5785
- const { legA, legB, recipientSlot, deposits, changeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
6509
+ const { legA, legB, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
5786
6510
  const auditPublicKey = poolService.getAuditPublicKey();
5787
6511
  const proof = await (auditPublicKey ? proveDualConfidentialTransfer({
5788
6512
  sdk,
@@ -5805,7 +6529,8 @@ async function prepareConfidentialTransferProofDual(parameters) {
5805
6529
  applicationIdsPlaintext: buildDualTransferApplicationIds(
5806
6530
  sdk,
5807
6531
  applicationId,
5808
- changeCoin
6532
+ changeCoin,
6533
+ feeCoin
5809
6534
  ),
5810
6535
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
5811
6536
  ...changeCoin ? { changeCoin } : {}
@@ -5813,22 +6538,32 @@ async function prepareConfidentialTransferProofDual(parameters) {
5813
6538
  }
5814
6539
 
5815
6540
  // src/transact/proofs/confidential/single.ts
5816
- function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange) {
6541
+ function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange, requiredFee) {
5817
6542
  if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
5818
6543
  throw new Error("Transfer amount must be positive");
5819
6544
  }
5820
- if (transferStroops > noteStroops) {
5821
- throw new Error("Transfer amount exceeds note value");
5822
- }
5823
- const changeStroops = noteStroops - transferStroops;
6545
+ const changeStroops = remainingAfterRequiredFee({
6546
+ available: noteStroops,
6547
+ instructed: transferStroops,
6548
+ requiredFee
6549
+ });
5824
6550
  requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl1ForChange);
5825
6551
  return changeStroops;
5826
6552
  }
5827
- function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin) {
6553
+ function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
6554
+ const outputIds = [applicationId];
6555
+ if (changeCoin) {
6556
+ outputIds.push(applicationId);
6557
+ } else if (!feeCoin) {
6558
+ outputIds.push("0");
6559
+ }
6560
+ if (feeCoin) {
6561
+ outputIds.push(applicationId);
6562
+ }
5828
6563
  return buildApplicationIdHints({
5829
6564
  sdk,
5830
6565
  inputIds: [applicationId],
5831
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
6566
+ outputIds
5832
6567
  });
5833
6568
  }
5834
6569
  function buildTransferAuditParameters(parameters) {
@@ -5854,11 +6589,25 @@ function confidentialEscrowFields2(input) {
5854
6589
  ...input.escrowClaimantLimbs ? { escrowClaimantLimbs: input.escrowClaimantLimbs } : {}
5855
6590
  };
5856
6591
  }
6592
+ function transferDepositInputFromProof(input, changeStroops, stateRoot) {
6593
+ return {
6594
+ senderPrivKeyScalarHex: input.senderPrivKeyScalarHex,
6595
+ recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1,
6596
+ transferStroops: input.transferStroops,
6597
+ changeStroops,
6598
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
6599
+ tokenAddress: input.tokenAddress,
6600
+ stateRoot,
6601
+ ...confidentialEscrowFields2(input),
6602
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
6603
+ };
6604
+ }
5857
6605
  async function buildTransferProofInputs(parameters) {
5858
6606
  const changeStroops = validateSingleCoinTransferAmounts(
5859
6607
  parameters.input.transferStroops,
5860
6608
  parameters.noteStroops,
5861
- parameters.input.selfPrivateAddressStpl1ForChange
6609
+ parameters.input.selfPrivateAddressStpl1ForChange,
6610
+ parameters.input.feeOutput?.requiredFee ?? 0n
5862
6611
  );
5863
6612
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
5864
6613
  parameters.input.senderPrivKeyScalarHex
@@ -5877,31 +6626,30 @@ async function buildTransferProofInputs(parameters) {
5877
6626
  withdrawObject,
5878
6627
  sweepWithdrawStamp(confidentialEscrowFields2(parameters.input))
5879
6628
  );
5880
- const { recipientSlot, deposits, changeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput({
5881
- senderPrivKeyScalarHex: parameters.input.senderPrivKeyScalarHex,
5882
- recipientPrivateAddressStpl1: parameters.input.recipientPrivateAddressStpl1,
5883
- transferStroops: parameters.input.transferStroops,
5884
- changeStroops,
5885
- selfPrivateAddressStpl1ForChange: parameters.input.selfPrivateAddressStpl1ForChange,
5886
- tokenAddress: parameters.input.tokenAddress,
5887
- stateRoot: witness.stateRoot,
5888
- ...confidentialEscrowFields2(parameters.input)
5889
- });
6629
+ const { recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput(
6630
+ transferDepositInputFromProof(parameters.input, changeStroops, witness.stateRoot)
6631
+ );
5890
6632
  return {
5891
6633
  witness,
5892
6634
  withdrawObject: stampedWithdraw,
5893
6635
  recipientSlot,
5894
6636
  deposits,
5895
6637
  changeCoin,
6638
+ feeCoin,
5896
6639
  publicInput
5897
6640
  };
5898
6641
  }
5899
6642
  async function prepareConfidentialTransferProof(parameters) {
5900
6643
  const poolService = getPrivacyPoolService();
5901
- const sdk = await poolService.getInitializedSdk();
6644
+ const sdk = await poolService.getInitializedSdk(
6645
+ poolService.feeBearingZkConfigNonce({
6646
+ kind: "transfer",
6647
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
6648
+ })
6649
+ );
5902
6650
  const applicationId = poolService.getApplicationId();
5903
6651
  const noteStroops = BigInt(parameters.coin.value);
5904
- const { withdrawObject, recipientSlot, deposits, changeCoin, publicInput } = await buildTransferProofInputs({
6652
+ const { withdrawObject, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildTransferProofInputs({
5905
6653
  sdk,
5906
6654
  applicationId,
5907
6655
  input: parameters,
@@ -5924,7 +6672,8 @@ async function prepareConfidentialTransferProof(parameters) {
5924
6672
  applicationIdsPlaintext: buildSingleTransferApplicationIds(
5925
6673
  sdk,
5926
6674
  applicationId,
5927
- changeCoin
6675
+ changeCoin,
6676
+ feeCoin
5928
6677
  ),
5929
6678
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
5930
6679
  ...changeCoin ? { changeCoin } : {}
@@ -5947,10 +6696,15 @@ function proofEscrowFields(input) {
5947
6696
  }
5948
6697
  async function buildSingleTransferProofAtExecute(input) {
5949
6698
  const context = await buildSpendProofContextAtExecute(input);
5950
- const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount;
6699
+ const feeOutput = await quoteFeeOutputForPrepared({
6700
+ prepared: input.prepared,
6701
+ environment: input.environment,
6702
+ tokenAddress: context.tokenAddress
6703
+ });
6704
+ const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5951
6705
  const proof = await prepareConfidentialTransferProof({
5952
6706
  coin: context.coin,
5953
- state: { commitments: context.commitments },
6707
+ state: context.merkleState,
5954
6708
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
5955
6709
  depositorEphemeralKey: serializeEphemeralKeyString({
5956
6710
  xHex: context.ephemeral.xHex,
@@ -5960,7 +6714,8 @@ async function buildSingleTransferProofAtExecute(input) {
5960
6714
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5961
6715
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5962
6716
  tokenAddress: context.tokenAddress,
5963
- ...proofEscrowFields(input)
6717
+ ...proofEscrowFields(input),
6718
+ ...feeOutput ? { feeOutput } : {}
5964
6719
  });
5965
6720
  return { context, proof };
5966
6721
  }
@@ -5977,11 +6732,16 @@ async function buildDualTransferProofAtExecute(input) {
5977
6732
  commitments: context.commitments
5978
6733
  });
5979
6734
  const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
5980
- const changeStroops = totalNotes - input.prepared.intent.amount;
6735
+ const feeOutput = await quoteFeeOutputForPrepared({
6736
+ prepared: input.prepared,
6737
+ environment: input.environment,
6738
+ tokenAddress: context.tokenAddress
6739
+ });
6740
+ const changeStroops = totalNotes - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5981
6741
  const proof = await prepareConfidentialTransferProofDual({
5982
6742
  coinA: context.coin,
5983
6743
  coinB: secondary.coin,
5984
- state: { commitments: context.commitments },
6744
+ state: context.merkleState,
5985
6745
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
5986
6746
  ephemeralAKey: serializeEphemeralKeyString({
5987
6747
  xHex: context.ephemeral.xHex,
@@ -5995,7 +6755,8 @@ async function buildDualTransferProofAtExecute(input) {
5995
6755
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5996
6756
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5997
6757
  tokenAddress: context.tokenAddress,
5998
- ...proofEscrowFields(input)
6758
+ ...proofEscrowFields(input),
6759
+ ...feeOutput ? { feeOutput } : {}
5999
6760
  });
6000
6761
  return { context, proof };
6001
6762
  }
@@ -6127,11 +6888,17 @@ async function runTransferFinalizeSteps(input) {
6127
6888
  walletPublicKey: input.walletPublicKey,
6128
6889
  ...recipient.escrowSend ? { escrowSendPrivateAddress: recipient.recipientPrivateAddressStpl1 } : {}
6129
6890
  });
6130
- return buildTransferFinalizeArtifacts({
6131
- proof,
6132
- context,
6133
- ...sweepFinalizeStamp(input.prepared, recipient)
6134
- });
6891
+ return {
6892
+ ...buildTransferFinalizeArtifacts({
6893
+ proof,
6894
+ context,
6895
+ ...sweepFinalizeStamp(input.prepared, recipient)
6896
+ }),
6897
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6898
+ ...feeOutputKindFromPrepared(input.prepared),
6899
+ ...configuredNonceSpread(input.environment.zkConfigNonce)
6900
+ })
6901
+ };
6135
6902
  }
6136
6903
  function sweepArtifactStamp(artifacts) {
6137
6904
  return {
@@ -6159,6 +6926,24 @@ async function finalizeTransferAtExecute(prepared, environment) {
6159
6926
  return runTransferFinalizeSteps({ prepared, environment, walletPublicKey });
6160
6927
  }
6161
6928
 
6929
+ // src/transact/engine/withdraw-fee.ts
6930
+ async function withdrawFeeProofFields(input) {
6931
+ const feeOutput = await quoteFeeOutputForPrepared({
6932
+ prepared: input.prepared,
6933
+ environment: input.environment,
6934
+ tokenAddress: input.tokenAddress
6935
+ });
6936
+ const changeStroops = remainingAfterRequiredFee({
6937
+ available: input.available,
6938
+ instructed: input.prepared.intent.amount,
6939
+ requiredFee: feeOutput?.requiredFee ?? 0n
6940
+ });
6941
+ return {
6942
+ ...feeOutput ? { feeOutput } : {},
6943
+ ...changeStroops > 0n ? { changePrivateAddressStpl1: input.withdrawFrom } : {}
6944
+ };
6945
+ }
6946
+
6162
6947
  // src/transact/engine/execute.ts
6163
6948
  function enrichWithdrawOutputRecords(prepared, proof) {
6164
6949
  if (!proof.changeCoin || prepared.outputRecords.length === 0) {
@@ -6174,7 +6959,7 @@ function enrichWithdrawOutputRecords(prepared, proof) {
6174
6959
  precommitementHex: changeCoin.precommitementHex
6175
6960
  }));
6176
6961
  }
6177
- function buildWithdrawFinalizeArtifacts(proof, context) {
6962
+ function buildWithdrawFinalizeArtifacts(proof, context, environment) {
6178
6963
  return {
6179
6964
  proofHex: proof.proof_hex,
6180
6965
  publicHex: proof.public_hex,
@@ -6182,7 +6967,11 @@ function buildWithdrawFinalizeArtifacts(proof, context) {
6182
6967
  applicationIdsPlaintext: proof.applicationIdsPlaintext,
6183
6968
  tokenAddress: context.tokenAddress,
6184
6969
  walletPublicKey: context.walletPublicKey,
6185
- executeFinalizeRequired: false
6970
+ executeFinalizeRequired: false,
6971
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6972
+ kind: "withdraw",
6973
+ ...configuredNonceSpread(environment.zkConfigNonce)
6974
+ })
6186
6975
  };
6187
6976
  }
6188
6977
  async function finalizeSingleWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
@@ -6191,10 +6980,16 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6191
6980
  environment,
6192
6981
  recipientPrivateAddressStpl1: withdrawFrom
6193
6982
  });
6194
- const changeStroops = BigInt(context.coin.value) - prepared.intent.amount;
6983
+ const feeFields = await withdrawFeeProofFields({
6984
+ prepared,
6985
+ environment,
6986
+ tokenAddress: context.tokenAddress,
6987
+ available: BigInt(context.coin.value),
6988
+ withdrawFrom
6989
+ });
6195
6990
  const proof = await poolService.prepareWithdrawTransactProof({
6196
6991
  coin: context.coin,
6197
- state: { commitments: context.commitments },
6992
+ state: context.merkleState,
6198
6993
  destinationStellarAddress: prepared.intent.to,
6199
6994
  privKeyScalarHex: context.senderPrivKeyScalarHex,
6200
6995
  depositorEphemeralKey: serializeEphemeralKeyString({
@@ -6202,11 +6997,24 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6202
6997
  yHex: context.ephemeral.yHex
6203
6998
  }),
6204
6999
  withdrawAmountStroops: prepared.intent.amount,
6205
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6206
- tokenAddress: context.tokenAddress
7000
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
7001
+ tokenAddress: context.tokenAddress,
7002
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
6207
7003
  });
6208
7004
  enrichWithdrawOutputRecords(prepared, proof);
6209
- return buildWithdrawFinalizeArtifacts(proof, context);
7005
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
7006
+ }
7007
+ function dualWithdrawEphemeralKeys(input) {
7008
+ return {
7009
+ depositorEphemeralAKey: serializeEphemeralKeyString({
7010
+ xHex: input.context.ephemeral.xHex,
7011
+ yHex: input.context.ephemeral.yHex
7012
+ }),
7013
+ depositorEphemeralBKey: serializeEphemeralKeyString({
7014
+ xHex: input.secondary.ephemeral.xHex,
7015
+ yHex: input.secondary.ephemeral.yHex
7016
+ })
7017
+ };
6210
7018
  }
6211
7019
  async function finalizeDualWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
6212
7020
  const [, secondaryRecord] = prepared.consumedRecords;
@@ -6224,28 +7032,27 @@ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService,
6224
7032
  walletPublicKey: context.walletPublicKey,
6225
7033
  commitments: context.commitments
6226
7034
  });
6227
- const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
6228
- const changeStroops = totalNotes - prepared.intent.amount;
7035
+ const feeFields = await withdrawFeeProofFields({
7036
+ prepared,
7037
+ environment,
7038
+ tokenAddress: context.tokenAddress,
7039
+ available: BigInt(context.coin.value) + BigInt(secondary.coin.value),
7040
+ withdrawFrom
7041
+ });
6229
7042
  const proof = await poolService.prepareWithdrawTransactProofDual({
6230
7043
  coinA: context.coin,
6231
7044
  coinB: secondary.coin,
6232
- state: { commitments: context.commitments },
7045
+ state: context.merkleState,
6233
7046
  destinationStellarAddress: prepared.intent.to,
6234
7047
  privKeyScalarHex: context.senderPrivKeyScalarHex,
6235
- depositorEphemeralAKey: serializeEphemeralKeyString({
6236
- xHex: context.ephemeral.xHex,
6237
- yHex: context.ephemeral.yHex
6238
- }),
6239
- depositorEphemeralBKey: serializeEphemeralKeyString({
6240
- xHex: secondary.ephemeral.xHex,
6241
- yHex: secondary.ephemeral.yHex
6242
- }),
7048
+ ...dualWithdrawEphemeralKeys({ context, secondary }),
6243
7049
  withdrawAmountStroops: prepared.intent.amount,
6244
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6245
- tokenAddress: context.tokenAddress
7050
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
7051
+ tokenAddress: context.tokenAddress,
7052
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
6246
7053
  });
6247
7054
  enrichWithdrawOutputRecords(prepared, proof);
6248
- return buildWithdrawFinalizeArtifacts(proof, context);
7055
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
6249
7056
  }
6250
7057
  async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6251
7058
  if (prepared.kind !== "withdraw") {
@@ -6507,7 +7314,7 @@ async function requestKytPassageForPoolInteraction(input) {
6507
7314
  const applicationId = resolvePoolApplicationId(
6508
7315
  input.transactEnvironment.network.applicationId
6509
7316
  );
6510
- const zkConfigNonce = resolveZkConfigNonce(input.transactEnvironment);
7317
+ const zkConfigNonce = input.zkConfigNonce ?? resolveZkConfigNonce(input.transactEnvironment);
6511
7318
  const applicationIdsPlaintext = input.applicationIdsPlaintext ?? buildApplicationIdHints2(
6512
7319
  applicationId,
6513
7320
  layoutForKnownNonce(zkConfigNonce).nAuditSlots
@@ -6679,6 +7486,10 @@ async function submitSorobanConfidentialTransfer(input) {
6679
7486
  networkPassphrase: input.networkPassphrase,
6680
7487
  sorobanRpcUrl: input.sorobanRpcUrl
6681
7488
  });
7489
+ const nonce = transactNonceFromArtifacts(
7490
+ input.zkConfigNonce === void 0 ? void 0 : { zkConfigNonce: input.zkConfigNonce },
7491
+ input.transactEnvironment
7492
+ );
6682
7493
  const approval = await requestKytPassageForPoolInteraction({
6683
7494
  owner: input.walletPublicKey,
6684
7495
  poolContract: input.contractId,
@@ -6687,6 +7498,7 @@ async function submitSorobanConfidentialTransfer(input) {
6687
7498
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
6688
7499
  ...input.outputNoteEphemeralScalars ? { outputNoteEphemeralScalars: input.outputNoteEphemeralScalars } : {},
6689
7500
  ...input.applicationIdsPlaintext ? { applicationIdsPlaintext: input.applicationIdsPlaintext } : {},
7501
+ ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
6690
7502
  networkPassphrase: input.networkPassphrase,
6691
7503
  sorobanRpcUrl: input.sorobanRpcUrl,
6692
7504
  transactEnvironment: input.transactEnvironment
@@ -6695,7 +7507,7 @@ async function submitSorobanConfidentialTransfer(input) {
6695
7507
  contractClient: poolClient,
6696
7508
  contractId: input.contractId,
6697
7509
  from: input.walletPublicKey,
6698
- nonce: resolveZkConfigNonce(input.transactEnvironment),
7510
+ nonce,
6699
7511
  proofHex: input.proofHex,
6700
7512
  publicHex: input.publicHex,
6701
7513
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
@@ -6726,12 +7538,14 @@ async function submitPreparedDeposit(prepared, environment) {
6726
7538
  networkPassphrase: environment.network.networkPassphrase,
6727
7539
  sorobanRpcUrl: environment.network.rpcUrl
6728
7540
  });
7541
+ const nonce = transactNonceFromArtifacts(artifacts, environment);
6729
7542
  const approval = await requestKytPassageForPoolInteraction({
6730
7543
  owner: artifacts.walletPublicKey,
6731
7544
  poolContract: environment.network.poolContract,
6732
7545
  proofHex: artifacts.proofHex,
6733
7546
  publicHex: artifacts.publicHex,
6734
7547
  ...optionalInspectCiphertext(artifacts),
7548
+ ...artifacts.zkConfigNonce === void 0 ? {} : { zkConfigNonce: artifacts.zkConfigNonce },
6735
7549
  networkPassphrase: environment.network.networkPassphrase,
6736
7550
  sorobanRpcUrl: environment.network.rpcUrl,
6737
7551
  transactEnvironment: environment
@@ -6740,7 +7554,7 @@ async function submitPreparedDeposit(prepared, environment) {
6740
7554
  contractClient: poolClient,
6741
7555
  contractId: environment.network.poolContract,
6742
7556
  from: artifacts.walletPublicKey,
6743
- nonce: resolveZkConfigNonce(environment),
7557
+ nonce,
6744
7558
  proofHex: artifacts.proofHex,
6745
7559
  publicHex: artifacts.publicHex,
6746
7560
  ...artifacts.ciphertextHex ? { ciphertextHex: artifacts.ciphertextHex } : {},
@@ -6761,6 +7575,7 @@ async function submitPreparedPoolTransact(prepared, environment) {
6761
7575
  proofHex: artifacts.proofHex,
6762
7576
  publicHex: artifacts.publicHex,
6763
7577
  ...optionalInspectCiphertext(artifacts),
7578
+ ...artifacts.zkConfigNonce === void 0 ? {} : { zkConfigNonce: artifacts.zkConfigNonce },
6764
7579
  networkPassphrase: environment.network.networkPassphrase,
6765
7580
  sorobanRpcUrl: environment.network.rpcUrl,
6766
7581
  transactEnvironment: environment
@@ -6816,7 +7631,7 @@ async function privacyPoolInitOptions(assets, transactEnvironment) {
6816
7631
  };
6817
7632
  }
6818
7633
  async function createDefaultTransactEngine(assets, transactEnvironment) {
6819
- await PrivacyPoolSDK3.init(await privacyPoolInitOptions(assets, transactEnvironment));
7634
+ await PrivacyPoolSDK4.init(await privacyPoolInitOptions(assets, transactEnvironment));
6820
7635
  if (transactEnvironment) {
6821
7636
  configurePrivacyPoolService(
6822
7637
  createPrivacyPoolService({