@arcanetech/privacy-sdk-stellar 0.5.2 → 0.5.4

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
@@ -468,27 +468,9 @@ function buildPrivateRecordFromDeposit(input) {
468
468
 
469
469
  // src/transact/pool/service.ts
470
470
  import {
471
- PrivacyPoolSDK as PrivacyPoolSDK2,
472
- decodeDecodedEphemeralKey,
473
- randomFrDecimal253,
474
- scalarHexToFrDecimal as scalarHexToFrDecimal2
471
+ decodeDecodedEphemeralKey
475
472
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
476
473
 
477
- // src/transact/merkle/field-decimal.ts
478
- import { Buffer as Buffer4 } from "buffer";
479
- var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
480
- var MERKLE_ROOT_BYTE_LENGTH = 32;
481
- var DECIMAL_STRING_RADIX = 10;
482
- function merkleRootBufferToFrDecimal(root) {
483
- if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
484
- throw new Error(
485
- `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
486
- );
487
- }
488
- const hex = Buffer4.from(root).toString("hex");
489
- return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
490
- }
491
-
492
474
  // src/transact/private-address/codec.ts
493
475
  import {
494
476
  buildStealthAddressSignMessage,
@@ -537,18 +519,18 @@ function privateAddressSdk(sdk) {
537
519
  import { withdrawObjectFromMerkleWitness as withdrawObjectFromMerkleWitness2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
538
520
 
539
521
  // src/transact/proofs/transaction-input.ts
540
- import { Buffer as Buffer6 } from "buffer";
522
+ import { Buffer as Buffer5 } from "buffer";
541
523
  import { StrKey as StrKey2 } from "@stellar/stellar-sdk";
542
524
 
543
525
  // src/transact/stellar/token-contract.ts
544
- import { Buffer as Buffer5 } from "buffer";
526
+ import { Buffer as Buffer4 } from "buffer";
545
527
  import { StrKey } from "@stellar/stellar-sdk";
546
528
  var CONTRACT_ID_BYTE_LENGTH = 32;
547
529
  var HEX_CHARS_PER_BYTE = 2;
548
530
  var CONTRACT_ID_HEX_CHAR_LENGTH = CONTRACT_ID_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
549
531
  var CONTRACT_ID_HEX = /^(?:0x)?([0-9a-f]{64})$/i;
550
532
  function encodeContractFromHex(hexPayload) {
551
- return StrKey.encodeContract(Buffer5.from(hexPayload, "hex"));
533
+ return StrKey.encodeContract(Buffer4.from(hexPayload, "hex"));
552
534
  }
553
535
  function normalizeSorobanContractId(raw) {
554
536
  const trimmed = raw.trim();
@@ -581,7 +563,7 @@ var ZERO_PUBLIC_WITHDRAW_ADDRESS = {
581
563
  var CONTRACT_ADDRESS_HALF_BYTES = 16;
582
564
  var CONTRACT_ADDRESS_BYTES = 32;
583
565
  function bytes16ToDecimal(bytes) {
584
- return BigInt(`0x${Buffer6.from(bytes).toString("hex")}`).toString();
566
+ return BigInt(`0x${Buffer5.from(bytes).toString("hex")}`).toString();
585
567
  }
586
568
  function tokenAddressFrParts(tokenAddress) {
587
569
  const normalized = resolveTokenContractId(tokenAddress);
@@ -742,14 +724,144 @@ function requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl
742
724
  }
743
725
  }
744
726
  }
745
- function changeStroopsAfterDualTransfer(transferStroops, totalNotes) {
746
- if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
747
- throw new Error("Transfer amount must be positive");
727
+
728
+ // src/transact/fees/quote-request-body.ts
729
+ function presentInstructedAmount(amount) {
730
+ const trimmed = amount?.trim() ?? "";
731
+ if (!trimmed || trimmed === "0") {
732
+ return void 0;
733
+ }
734
+ return trimmed;
735
+ }
736
+ function feeQuoteRequestBody(request) {
737
+ const publicDepositAmount = presentInstructedAmount(request.publicDepositAmount);
738
+ const transferInstructedAmount = presentInstructedAmount(
739
+ request.transferInstructedAmount
740
+ );
741
+ const publicWithdrawalAmount = presentInstructedAmount(
742
+ request.publicWithdrawalAmount
743
+ );
744
+ return {
745
+ feeAsset: request.feeAsset,
746
+ spendsNotes: request.spendsNotes,
747
+ ...publicDepositAmount === void 0 ? {} : { publicDepositAmount },
748
+ ...transferInstructedAmount === void 0 ? {} : { transferInstructedAmount },
749
+ ...publicWithdrawalAmount === void 0 ? {} : { publicWithdrawalAmount }
750
+ };
751
+ }
752
+
753
+ // src/transact/fees/quote-required-fee.ts
754
+ function quoteHeaders(inspectAuthorization) {
755
+ const token = inspectAuthorization?.trim();
756
+ return {
757
+ "content-type": "application/json",
758
+ ...token ? { authorization: `Bearer ${token}` } : {}
759
+ };
760
+ }
761
+ function parseRequiredFee(raw) {
762
+ const text = `${raw ?? ""}`.trim();
763
+ if (!/^\d+$/u.test(text)) {
764
+ throw new Error("Fee Quote Required Fee must be an integer minor-unit string.");
765
+ }
766
+ return BigInt(text);
767
+ }
768
+ function requireQuoteString(raw, field) {
769
+ const text = `${raw ?? ""}`.trim();
770
+ if (!text) {
771
+ throw new Error(`Fee Quote is missing ${field}.`);
772
+ }
773
+ return text;
774
+ }
775
+ function parseQuotePayload(payload) {
776
+ return {
777
+ requiredFee: parseRequiredFee(payload.requiredFee),
778
+ feeAsset: requireQuoteString(payload.feeAsset, "Fee Asset"),
779
+ feeCollectorPrivateAddress: requireQuoteString(
780
+ payload.feeCollectorPrivateAddress,
781
+ "Fee Collector Private Address"
782
+ ),
783
+ feeRate: requireQuoteString(payload.feeRate, "Fee Rate")
784
+ };
785
+ }
786
+ async function quoteRequiredFee(input) {
787
+ const response = await fetch(
788
+ `${input.transactEnvironment.kyt.apiBaseUrl}/kyt/fees/quote`,
789
+ {
790
+ method: "POST",
791
+ headers: quoteHeaders(input.transactEnvironment.kyt.inspectAuthorization),
792
+ body: JSON.stringify(feeQuoteRequestBody(input.request))
793
+ }
794
+ );
795
+ const payload = await response.json();
796
+ if (!response.ok) {
797
+ throw new Error(`Fee Quote failed: ${JSON.stringify(payload)}`);
798
+ }
799
+ return parseQuotePayload(payload);
800
+ }
801
+
802
+ // src/transact/fees/should-attach-fee-output.ts
803
+ function shouldAttachFeeOutput(input) {
804
+ if (input.spendSource === "escrow" && input.escrowSend !== true) {
805
+ return false;
806
+ }
807
+ return input.kind === "deposit" || input.kind === "transfer" || input.kind === "withdraw";
808
+ }
809
+ function feeOutputKindFromPrepared(prepared) {
810
+ return {
811
+ kind: prepared.kind,
812
+ ...prepared.transactArtifacts?.spendSource ? { spendSource: prepared.transactArtifacts.spendSource } : {},
813
+ ...prepared.transactArtifacts?.escrowSend ? { escrowSend: true } : {}
814
+ };
815
+ }
816
+
817
+ // src/transact/fees/quote-fee-output-for-prepared.ts
818
+ function instructedMinorUnits(prepared) {
819
+ return prepared.intent.amount.toString();
820
+ }
821
+ function quoteRequestForPrepared(input) {
822
+ const amount = instructedMinorUnits(input.prepared);
823
+ if (input.prepared.kind === "deposit") {
824
+ return {
825
+ feeAsset: input.tokenAddress,
826
+ publicDepositAmount: amount,
827
+ spendsNotes: false
828
+ };
748
829
  }
749
- if (transferStroops > totalNotes) {
750
- throw new Error("Transfer amount exceeds combined note value");
830
+ if (input.prepared.kind === "withdraw") {
831
+ return {
832
+ feeAsset: input.tokenAddress,
833
+ publicWithdrawalAmount: amount,
834
+ spendsNotes: true
835
+ };
836
+ }
837
+ return {
838
+ feeAsset: input.tokenAddress,
839
+ transferInstructedAmount: amount,
840
+ spendsNotes: true
841
+ };
842
+ }
843
+ async function quoteFeeOutputForPrepared(input) {
844
+ if (!shouldAttachFeeOutput(feeOutputKindFromPrepared(input.prepared))) {
845
+ return void 0;
846
+ }
847
+ const quote = await quoteRequiredFee({
848
+ transactEnvironment: input.environment,
849
+ request: quoteRequestForPrepared(input)
850
+ });
851
+ if (quote.requiredFee <= 0n) {
852
+ return void 0;
751
853
  }
752
- return totalNotes - transferStroops;
854
+ return {
855
+ requiredFee: quote.requiredFee,
856
+ feeCollectorPrivateAddress: quote.feeCollectorPrivateAddress
857
+ };
858
+ }
859
+ function remainingAfterRequiredFee(input) {
860
+ const remaining = input.available - input.instructed - input.requiredFee;
861
+ if (remaining < 0n) {
862
+ throw new Error("Instructed amount and Required Fee exceed available note value");
863
+ }
864
+ return remaining;
753
865
  }
754
866
 
755
867
  // src/transact/audit/parameters.ts
@@ -757,7 +869,7 @@ import {
757
869
  DEMO_AUDIT_PUBLIC_KEY,
758
870
  resolveTransactionAuditParams
759
871
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
760
- import { Buffer as Buffer7 } from "buffer";
872
+ import { Buffer as Buffer6 } from "buffer";
761
873
  var UNCOMPRESSED_PREFIX = "04";
762
874
  var UNCOMPRESSED_HEX_LENGTH = 130;
763
875
  var XY_HEX_LENGTH = 128;
@@ -784,7 +896,7 @@ function parseAuditPublicKeyFromEnvHex(raw) {
784
896
  "Audit public key must be uncompressed 65-byte hex (04 + X + Y) or 64-byte X+Y hex"
785
897
  );
786
898
  }
787
- const bytes = Buffer7.from(payload, "hex");
899
+ const bytes = Buffer6.from(payload, "hex");
788
900
  const auditPublicKeyXDecimal = BigInt(
789
901
  `0x${bytes.subarray(0, X_OFFSET_END).toString("hex")}`
790
902
  ).toString(DECIMAL_RADIX);
@@ -827,13 +939,13 @@ import { ed25519PubkeyPayloadHexToWithdrawFrDecimals } from "@arcanetech/stellar
827
939
 
828
940
  // src/transact/stellar/account.ts
829
941
  import { StrKey as StrKey3 } from "@stellar/stellar-sdk";
830
- import { Buffer as Buffer8 } from "buffer";
942
+ import { Buffer as Buffer7 } from "buffer";
831
943
  var MUXED_ACCOUNT_ID_BYTE_LENGTH = 8;
832
944
  var ED25519_PUBLIC_KEY_BYTE_LENGTH = 32;
833
945
  function ed25519HexFromGAddress(trimmed) {
834
946
  try {
835
947
  const raw = StrKey3.decodeEd25519PublicKey(trimmed);
836
- return Buffer8.from(raw).toString("hex");
948
+ return Buffer7.from(raw).toString("hex");
837
949
  } catch {
838
950
  return void 0;
839
951
  }
@@ -890,6 +1002,32 @@ function privKeyScalarDecimalFromRecipientScalarHex(hexInput) {
890
1002
  return canonicalBabyJubScalarFromInteger(value).toString(DECIMAL_RADIX2);
891
1003
  }
892
1004
 
1005
+ // src/transact/fees/append-fee-output.ts
1006
+ function feeCoinFromSlot(slot) {
1007
+ return {
1008
+ commitment_hex: slot.commitment_hex,
1009
+ coin: slot.coin,
1010
+ depositScalarHex: slot.depositScalarHex,
1011
+ precommitementHex: slot.precommitementHex
1012
+ };
1013
+ }
1014
+ async function appendFeeOutputSlot(input) {
1015
+ const requiredFee = input.feeOutput?.requiredFee ?? ZERO_STROOPS;
1016
+ const collector = input.feeOutput?.feeCollectorPrivateAddress.trim() ?? "";
1017
+ if (requiredFee <= ZERO_STROOPS || !collector) {
1018
+ return { deposits: input.deposits };
1019
+ }
1020
+ const slot = await input.buildAlignedDepositSlot({
1021
+ privateAddressStpl1: collector,
1022
+ amountStroops: requiredFee,
1023
+ tokenAddress: input.tokenAddress
1024
+ });
1025
+ return {
1026
+ deposits: [...input.deposits, slot.deposit],
1027
+ feeCoin: feeCoinFromSlot(slot)
1028
+ };
1029
+ }
1030
+
893
1031
  // src/transact/proofs/withdraw/helpers.ts
894
1032
  function destinationWithdrawFrAndScalar(parameters) {
895
1033
  const pkHex = ed25519PublicKeyHexFromStellarAccount(
@@ -944,14 +1082,15 @@ async function resolveWithdrawChangeDeposits(parameters) {
944
1082
  }
945
1083
  };
946
1084
  }
947
- function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1) {
1085
+ function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1, requiredFee) {
948
1086
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
949
1087
  throw new Error("Withdraw amount must be positive");
950
1088
  }
951
- if (withdrawAmountStroops > totalNotes) {
952
- throw new Error("Withdraw amount exceeds combined note value");
953
- }
954
- const changeStroops = totalNotes - withdrawAmountStroops;
1089
+ const changeStroops = remainingAfterRequiredFee({
1090
+ available: totalNotes,
1091
+ instructed: withdrawAmountStroops,
1092
+ requiredFee
1093
+ });
955
1094
  requireChangeRecipientWhenPartial(changeStroops, changePrivateAddressStpl1);
956
1095
  return changeStroops;
957
1096
  }
@@ -964,11 +1103,31 @@ function buildDualWithdrawPublicInput(parameters) {
964
1103
  publicWithdrawalAmount: parameters.publicWithdrawalAmount
965
1104
  });
966
1105
  }
1106
+ async function withdrawOutputsWithOptionalFee(parameters) {
1107
+ const { deposits: changeDeposits, changeCoin } = await resolveWithdrawChangeDeposits({
1108
+ changeStroops: parameters.changeStroops,
1109
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1110
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1111
+ tokenAddress: parameters.tokenAddress
1112
+ });
1113
+ const withFee = await appendFeeOutputSlot({
1114
+ deposits: changeDeposits,
1115
+ tokenAddress: parameters.tokenAddress,
1116
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1117
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1118
+ });
1119
+ return {
1120
+ deposits: withFee.deposits,
1121
+ ...changeCoin ? { changeCoin } : {},
1122
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
1123
+ };
1124
+ }
967
1125
  async function prepareDualWithdrawProofInputs(parameters) {
968
1126
  const changeStroops = validateDualWithdrawAmounts(
969
1127
  parameters.withdrawAmountStroops,
970
1128
  BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value),
971
- parameters.changePrivateAddressStpl1
1129
+ parameters.changePrivateAddressStpl1,
1130
+ parameters.feeOutput?.requiredFee ?? 0n
972
1131
  );
973
1132
  const { legA, legB } = dualWithdrawLegsWithSharedRoot({
974
1133
  sdk: parameters.sdk,
@@ -983,11 +1142,12 @@ async function prepareDualWithdrawProofInputs(parameters) {
983
1142
  ),
984
1143
  applicationId: parameters.applicationId
985
1144
  });
986
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1145
+ const { deposits, changeCoin, feeCoin } = await withdrawOutputsWithOptionalFee({
987
1146
  changeStroops,
988
1147
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
989
1148
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
990
- tokenAddress: parameters.tokenAddress
1149
+ tokenAddress: parameters.tokenAddress,
1150
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
991
1151
  });
992
1152
  const publicInput = buildDualWithdrawPublicInput({
993
1153
  destinationStellarAddress: parameters.destinationStellarAddress,
@@ -1006,19 +1166,55 @@ async function prepareDualWithdrawProofInputs(parameters) {
1006
1166
  audit,
1007
1167
  deposits,
1008
1168
  withdrawLegs: [legA.withdrawObject, legB.withdrawObject],
1009
- ...changeCoin ? { changeCoin } : {}
1169
+ ...changeCoin ? { changeCoin } : {},
1170
+ ...feeCoin ? { feeCoin } : {}
1171
+ };
1172
+ }
1173
+
1174
+ // src/transact/proofs/withdraw/fee-deposits.ts
1175
+ async function withdrawDepositsWithFee(parameters) {
1176
+ const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1177
+ changeStroops: parameters.changeStroops,
1178
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1179
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1180
+ tokenAddress: parameters.tokenAddress
1181
+ });
1182
+ const withFee = await appendFeeOutputSlot({
1183
+ deposits,
1184
+ tokenAddress: parameters.tokenAddress,
1185
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1186
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1187
+ });
1188
+ return {
1189
+ deposits: withFee.deposits,
1190
+ ...changeCoin ? { changeCoin } : {},
1191
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
1010
1192
  };
1011
1193
  }
1194
+ function withdrawOutputApplicationIds(input) {
1195
+ const outputIds = [];
1196
+ if (input.hasChange) {
1197
+ outputIds.push(input.applicationId);
1198
+ }
1199
+ if (input.hasFeeOutput) {
1200
+ outputIds.push(input.applicationId);
1201
+ }
1202
+ if (outputIds.length === 0) {
1203
+ outputIds.push("0");
1204
+ }
1205
+ return outputIds;
1206
+ }
1012
1207
 
1013
1208
  // src/transact/proofs/withdraw/transact-proof.ts
1014
- function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops) {
1209
+ function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops, requiredFee) {
1015
1210
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
1016
1211
  throw new Error("Withdraw amount must be positive");
1017
1212
  }
1018
- if (withdrawAmountStroops > noteStroops) {
1019
- throw new Error("Withdraw amount exceeds note value");
1020
- }
1021
- return noteStroops - withdrawAmountStroops;
1213
+ return remainingAfterRequiredFee({
1214
+ available: noteStroops,
1215
+ instructed: withdrawAmountStroops,
1216
+ requiredFee
1217
+ });
1022
1218
  }
1023
1219
  async function proveSingleWithdrawTransaction(parameters) {
1024
1220
  return parameters.sdk.proveTransaction(
@@ -1057,7 +1253,8 @@ function buildPrimaryWithdrawForCoin(parameters) {
1057
1253
  async function prepareSingleWithdrawProofInputs(parameters) {
1058
1254
  const changeStroops = validateSingleWithdrawAmounts(
1059
1255
  parameters.withdrawAmountStroops,
1060
- BigInt(parameters.coin.value)
1256
+ BigInt(parameters.coin.value),
1257
+ parameters.feeOutput?.requiredFee ?? 0n
1061
1258
  );
1062
1259
  const { witness, primaryWithdraw } = buildPrimaryWithdrawForCoin({
1063
1260
  sdk: parameters.sdk,
@@ -1066,11 +1263,12 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1066
1263
  state: parameters.state,
1067
1264
  privKeyScalarHex: parameters.privKeyScalarHex
1068
1265
  });
1069
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1266
+ const { deposits, changeCoin, feeCoin } = await withdrawDepositsWithFee({
1070
1267
  changeStroops,
1071
1268
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1072
1269
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1073
- tokenAddress: parameters.tokenAddress
1270
+ tokenAddress: parameters.tokenAddress,
1271
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1074
1272
  });
1075
1273
  const publicInput = buildWithdrawPublicInput({
1076
1274
  stateRoot: witness.stateRoot,
@@ -1083,6 +1281,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1083
1281
  primaryWithdraw,
1084
1282
  deposits,
1085
1283
  changeCoin,
1284
+ feeCoin,
1086
1285
  publicInput,
1087
1286
  audit: buildPoolTransactionAuditParameters({
1088
1287
  applicationId: parameters.applicationId,
@@ -1092,7 +1291,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1092
1291
  };
1093
1292
  }
1094
1293
  async function proveWithdrawTransact(parameters) {
1095
- const { primaryWithdraw, deposits, changeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
1294
+ const { primaryWithdraw, deposits, changeCoin, feeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
1096
1295
  const proof = await proveSingleWithdrawTransaction({
1097
1296
  sdk: parameters.sdk,
1098
1297
  publicInput,
@@ -1107,13 +1306,17 @@ async function proveWithdrawTransact(parameters) {
1107
1306
  applicationIdsPlaintext: buildApplicationIdHints({
1108
1307
  sdk: parameters.sdk,
1109
1308
  inputIds: [parameters.applicationId],
1110
- outputIds: [changeCoin ? parameters.applicationId : "0"]
1309
+ outputIds: withdrawOutputApplicationIds({
1310
+ applicationId: parameters.applicationId,
1311
+ hasChange: Boolean(changeCoin),
1312
+ hasFeeOutput: Boolean(feeCoin)
1313
+ })
1111
1314
  }),
1112
1315
  ...changeCoin ? { changeCoin } : {}
1113
1316
  };
1114
1317
  }
1115
1318
  async function proveWithdrawTransactDual(parameters) {
1116
- const { publicInput, audit, withdrawLegs, deposits, changeCoin } = await prepareDualWithdrawProofInputs(parameters);
1319
+ const { publicInput, audit, withdrawLegs, deposits, changeCoin, feeCoin } = await prepareDualWithdrawProofInputs(parameters);
1117
1320
  const proof = await parameters.sdk.proveTransaction(
1118
1321
  publicInput,
1119
1322
  padPublicLegsToLayout(
@@ -1132,7 +1335,11 @@ async function proveWithdrawTransactDual(parameters) {
1132
1335
  applicationIdsPlaintext: buildApplicationIdHints({
1133
1336
  sdk: parameters.sdk,
1134
1337
  inputIds: [audit.applicationId, audit.applicationId],
1135
- outputIds: [changeCoin ? audit.applicationId : "0"]
1338
+ outputIds: withdrawOutputApplicationIds({
1339
+ applicationId: audit.applicationId,
1340
+ hasChange: Boolean(changeCoin),
1341
+ hasFeeOutput: Boolean(feeCoin)
1342
+ })
1136
1343
  }),
1137
1344
  ...changeCoin ? { changeCoin } : {}
1138
1345
  };
@@ -1153,6 +1360,9 @@ var DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
1153
1360
  function resolveZkConfigNonce(transactEnvironment) {
1154
1361
  return transactEnvironment.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1155
1362
  }
1363
+ function transactNonceFromArtifacts(artifacts, transactEnvironment) {
1364
+ return artifacts?.zkConfigNonce ?? resolveZkConfigNonce(transactEnvironment);
1365
+ }
1156
1366
 
1157
1367
  // src/transact/zk/browser-artifact-cache.ts
1158
1368
  import { ZK_ARTIFACT_VERSION } from "@arcanetech/stellar-privacy-pool-zk-sdk";
@@ -1405,6 +1615,122 @@ async function buildAlignedDepositSlotForSdk(sdk, applicationId, parameters) {
1405
1615
  };
1406
1616
  }
1407
1617
 
1618
+ // src/transact/pool/initialize-sdk.ts
1619
+ import { PrivacyPoolSDK as PrivacyPoolSDK2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1620
+ async function initializePrivacyPoolSdk(input, nonce) {
1621
+ const zkConfigNonce = nonce ?? input.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1622
+ const zkCircuits = await materializeSelectedZkCircuit(
1623
+ input.zkCircuits,
1624
+ zkConfigNonce,
1625
+ input.zkArtifactBaseUrl
1626
+ );
1627
+ return PrivacyPoolSDK2.init({
1628
+ wasmBinary: input.assets.sdkWasm,
1629
+ zkCircuits,
1630
+ zkConfigNonce,
1631
+ ...optionalZkArtifactBaseUrl(input.zkArtifactBaseUrl)
1632
+ });
1633
+ }
1634
+
1635
+ // src/transact/fees/zk-config-nonce-for-kind.ts
1636
+ function configuredNonceSpread(zkConfigNonce) {
1637
+ if (zkConfigNonce === void 0) {
1638
+ return {};
1639
+ }
1640
+ return { configuredNonce: zkConfigNonce };
1641
+ }
1642
+ function zkConfigNonceForFeeBearingKind(input) {
1643
+ if (input.configuredNonce === SIX_BY_SIX_BINDING_ZK_NONCE) {
1644
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1645
+ }
1646
+ if (input.kind === "transfer" && shouldAttachFeeOutput(input)) {
1647
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1648
+ }
1649
+ return input.configuredNonce ?? BINDING_ZK_NONCE;
1650
+ }
1651
+
1652
+ // src/transact/pool/prepare-deposit-proof.ts
1653
+ import {
1654
+ randomFrDecimal253,
1655
+ scalarHexToFrDecimal as scalarHexToFrDecimal2
1656
+ } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1657
+
1658
+ // src/transact/merkle/field-decimal.ts
1659
+ import { Buffer as Buffer8 } from "buffer";
1660
+ var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1661
+ var MERKLE_ROOT_BYTE_LENGTH = 32;
1662
+ var DECIMAL_STRING_RADIX = 10;
1663
+ function merkleRootBufferToFrDecimal(root) {
1664
+ if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
1665
+ throw new Error(
1666
+ `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
1667
+ );
1668
+ }
1669
+ const hex = Buffer8.from(root).toString("hex");
1670
+ return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
1671
+ }
1672
+
1673
+ // src/transact/pool/prepare-deposit-proof.ts
1674
+ function userDepositSlot(input) {
1675
+ return {
1676
+ value: input.coin.value,
1677
+ nullifier: input.coin.nullifier,
1678
+ ephemeralKeyScalar: scalarHexToFrDecimal2(input.depositScalarHex),
1679
+ asset: [input.coin.asset_hi, input.coin.asset_lo],
1680
+ applicationId: input.applicationId,
1681
+ recipientPublicKeys: recipientPublicKeysDecimalFromPrivateAddress(
1682
+ input.privateAddressStpl1
1683
+ )
1684
+ };
1685
+ }
1686
+ function outputApplicationIds(input) {
1687
+ if (input.hasFeeOutput) {
1688
+ return [input.applicationId, input.applicationId];
1689
+ }
1690
+ return [input.applicationId];
1691
+ }
1692
+ async function proveDepositTransact(input) {
1693
+ const publicDeposit = (input.publicDepositStroops ?? BigInt(input.coin.value)).toString();
1694
+ const withFee = await appendFeeOutputSlot({
1695
+ deposits: [userDepositSlot(input)],
1696
+ tokenAddress: input.tokenAddress,
1697
+ buildAlignedDepositSlot: input.buildAlignedDepositSlot,
1698
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
1699
+ });
1700
+ const audit = buildPoolTransactionAuditParameters({
1701
+ applicationId: input.applicationId,
1702
+ nAuditSlots: input.sdk.getLayout().nAuditSlots,
1703
+ ...input.auditPublicKey ? { auditPublicKey: input.auditPublicKey } : {}
1704
+ });
1705
+ const proof = await input.sdk.proveTransaction(
1706
+ withTokenAddressPublicInputs(
1707
+ {
1708
+ stateRoot: merkleRootBufferToFrDecimal(input.merkleRootBytes),
1709
+ privKeyScalar: randomFrDecimal253()
1710
+ },
1711
+ input.tokenAddress
1712
+ ),
1713
+ padPublicLegsToLayout(
1714
+ input.sdk,
1715
+ buildPublicDepositLegs(input.tokenAddress, publicDeposit)
1716
+ ),
1717
+ padWithdrawSlotsToLayout(input.sdk, []),
1718
+ padDepositSlotsToLayout(input.sdk, withFee.deposits),
1719
+ audit
1720
+ );
1721
+ return {
1722
+ ...proof,
1723
+ applicationIdsPlaintext: buildApplicationIdHints({
1724
+ sdk: input.sdk,
1725
+ inputIds: [],
1726
+ outputIds: outputApplicationIds({
1727
+ applicationId: input.applicationId,
1728
+ hasFeeOutput: Boolean(withFee.feeCoin)
1729
+ })
1730
+ })
1731
+ };
1732
+ }
1733
+
1408
1734
  // src/transact/pool/service.ts
1409
1735
  var PrivacyPoolService = class {
1410
1736
  constructor(assets, applicationId, auditPublicKey, zkCircuits, zkConfigNonce, zkArtifactBaseUrl) {
@@ -1421,42 +1747,50 @@ var PrivacyPoolService = class {
1421
1747
  zkCircuits;
1422
1748
  zkConfigNonce;
1423
1749
  zkArtifactBaseUrl;
1424
- sdk = void 0;
1425
- initPromise = void 0;
1426
- async ensureInit() {
1427
- if (this.sdk) {
1428
- return;
1750
+ sdks = /* @__PURE__ */ new Map();
1751
+ initPromises = /* @__PURE__ */ new Map();
1752
+ async getInitializedSdk(nonce) {
1753
+ const zkConfigNonce = nonce ?? this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1754
+ const key = zkConfigNonce.toString();
1755
+ const existing = this.sdks.get(key);
1756
+ if (existing) {
1757
+ return existing;
1429
1758
  }
1430
- if (this.initPromise) {
1431
- return this.initPromise;
1759
+ const pending = this.initPromises.get(key);
1760
+ if (pending) {
1761
+ return pending;
1762
+ }
1763
+ const started = this.initializeSdk(zkConfigNonce);
1764
+ this.initPromises.set(key, started);
1765
+ try {
1766
+ const sdk = await started;
1767
+ this.sdks.set(key, sdk);
1768
+ return sdk;
1769
+ } finally {
1770
+ this.initPromises.delete(key);
1432
1771
  }
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;
1453
1772
  }
1454
- async getInitializedSdk() {
1455
- await this.ensureInit();
1456
- if (!this.sdk) {
1457
- throw new Error("SDK not initialized");
1773
+ feeBearingZkConfigNonce(input) {
1774
+ return zkConfigNonceForFeeBearingKind({
1775
+ ...input,
1776
+ ...configuredNonceSpread(this.zkConfigNonce)
1777
+ });
1778
+ }
1779
+ async initializeSdk(nonce) {
1780
+ if (!this.assets) {
1781
+ throw new Error(
1782
+ "PrivacyPoolService requires browser ZK assets before initialization."
1783
+ );
1458
1784
  }
1459
- return this.sdk;
1785
+ return initializePrivacyPoolSdk(
1786
+ {
1787
+ assets: this.assets,
1788
+ ...this.zkCircuits ? { zkCircuits: this.zkCircuits } : {},
1789
+ ...this.zkConfigNonce === void 0 ? {} : { zkConfigNonce: this.zkConfigNonce },
1790
+ ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1791
+ },
1792
+ nonce
1793
+ );
1460
1794
  }
1461
1795
  getAuditPublicKey() {
1462
1796
  return this.auditPublicKey;
@@ -1489,65 +1823,51 @@ var PrivacyPoolService = class {
1489
1823
  return { commitment_hex, coin, depositScalarHex, precommitementHex };
1490
1824
  }
1491
1825
  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
1517
- );
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
1826
+ const sdk = await this.getInitializedSdk(
1827
+ this.feeBearingZkConfigNonce({ kind: "deposit" })
1527
1828
  );
1528
- const applicationIdsPlaintext = buildApplicationIdHints({
1829
+ return proveDepositTransact({
1529
1830
  sdk,
1530
- inputIds: [],
1531
- outputIds: [applicationId]
1831
+ applicationId: this.getApplicationId(),
1832
+ privateAddressStpl1: parameters.privateAddressStpl1,
1833
+ coin: parameters.coin,
1834
+ depositScalarHex: parameters.depositScalarHex,
1835
+ merkleRootBytes: parameters.merkleRootBytes,
1836
+ tokenAddress: parameters.tokenAddress,
1837
+ buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1838
+ ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1839
+ ...parameters.publicDepositStroops === void 0 ? {} : { publicDepositStroops: parameters.publicDepositStroops },
1840
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1532
1841
  });
1533
- return { ...proof, applicationIdsPlaintext };
1534
1842
  }
1535
1843
  async calculateNullifierHash(nullifier, privKeyScalar) {
1536
1844
  const sdk = await this.getInitializedSdk();
1537
1845
  return sdk.calculateNullifierHash(nullifier, privKeyScalar);
1538
1846
  }
1539
1847
  async prepareWithdrawTransactProof(parameters) {
1540
- const sdk = await this.getInitializedSdk();
1848
+ const sdk = await this.getInitializedSdk(
1849
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1850
+ );
1541
1851
  return proveWithdrawTransact({
1542
1852
  sdk,
1543
1853
  applicationId: this.getApplicationId(),
1544
1854
  buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1545
1855
  ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1546
- ...parameters
1856
+ coin: parameters.coin,
1857
+ state: parameters.state,
1858
+ destinationStellarAddress: parameters.destinationStellarAddress,
1859
+ privKeyScalarHex: parameters.privKeyScalarHex,
1860
+ depositorEphemeralKey: parameters.depositorEphemeralKey,
1861
+ withdrawAmountStroops: parameters.withdrawAmountStroops,
1862
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1863
+ tokenAddress: parameters.tokenAddress,
1864
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1547
1865
  });
1548
1866
  }
1549
1867
  async prepareWithdrawTransactProofDual(parameters) {
1550
- const sdk = await this.getInitializedSdk();
1868
+ const sdk = await this.getInitializedSdk(
1869
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1870
+ );
1551
1871
  return proveWithdrawTransactDual({
1552
1872
  sdk,
1553
1873
  applicationId: this.getApplicationId(),
@@ -1562,7 +1882,8 @@ var PrivacyPoolService = class {
1562
1882
  ephemeralBKey: parameters.depositorEphemeralBKey,
1563
1883
  withdrawAmountStroops: parameters.withdrawAmountStroops,
1564
1884
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1565
- tokenAddress: parameters.tokenAddress
1885
+ tokenAddress: parameters.tokenAddress,
1886
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1566
1887
  });
1567
1888
  }
1568
1889
  };
@@ -2150,6 +2471,13 @@ async function filterUnspentPrivateRecords(input) {
2150
2471
  }
2151
2472
  return unspent;
2152
2473
  }
2474
+ function spendAmountCoveringProtocolFee(amount) {
2475
+ if (amount <= 0n) {
2476
+ return amount;
2477
+ }
2478
+ const hundredths = (amount + 99n) / 100n;
2479
+ return amount + (hundredths === 0n ? 1n : hundredths);
2480
+ }
2153
2481
  function pickRecordsForSpendAmount(input) {
2154
2482
  let selectedTotal = 0n;
2155
2483
  const selected = [];
@@ -2203,7 +2531,7 @@ async function selectPrivateRecords(input) {
2203
2531
  return pickRecordsForSpendAmount({
2204
2532
  available,
2205
2533
  kind: input.kind,
2206
- amount: input.intent.amount
2534
+ amount: spendAmountCoveringProtocolFee(input.intent.amount)
2207
2535
  });
2208
2536
  }
2209
2537
  function sumRecordAmounts(records) {
@@ -5312,7 +5640,7 @@ function createStellarPrivacyClientFromResolvedConfig(config) {
5312
5640
  }
5313
5641
 
5314
5642
  // src/transact/engine/default-factory.ts
5315
- import { PrivacyPoolSDK as PrivacyPoolSDK3 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
5643
+ import { PrivacyPoolSDK as PrivacyPoolSDK4 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
5316
5644
 
5317
5645
  // src/transact/pool/proof-types.ts
5318
5646
  function ciphertextArtifactsFromProof(proof) {
@@ -5327,9 +5655,15 @@ function ciphertextArtifactsFromProof(proof) {
5327
5655
  // src/transact/engine/prepare/deposit.ts
5328
5656
  var STROOPS_PER_UNIT = 10000000n;
5329
5657
  async function createAlignedDepositWithProof(parameters) {
5658
+ const feeOutput = await quoteFeeOutputForPrepared(parameters);
5659
+ const instructed = parameters.prepared.intent.amount;
5660
+ const userAmount = instructed - (feeOutput?.requiredFee ?? 0n);
5661
+ if (userAmount <= 0n) {
5662
+ throw new Error("Required Fee leaves no spendable deposit note");
5663
+ }
5330
5664
  const aligned = await parameters.poolService.createAlignedShieldCoinData({
5331
5665
  privateAddressStpl1: parameters.prepared.intent.to,
5332
- amountStroops: parameters.prepared.intent.amount,
5666
+ amountStroops: userAmount,
5333
5667
  tokenAddress: parameters.tokenAddress
5334
5668
  });
5335
5669
  const poolClient = readPoolClientFactory(parameters.environment)({
@@ -5344,7 +5678,9 @@ async function createAlignedDepositWithProof(parameters) {
5344
5678
  coin: aligned.coin,
5345
5679
  depositScalarHex: aligned.depositScalarHex,
5346
5680
  merkleRootBytes: merkleTx.result,
5347
- tokenAddress: parameters.tokenAddress
5681
+ tokenAddress: parameters.tokenAddress,
5682
+ publicDepositStroops: instructed,
5683
+ ...feeOutput ? { feeOutput } : {}
5348
5684
  });
5349
5685
  return { ...aligned, proof };
5350
5686
  }
@@ -5363,8 +5699,8 @@ async function prepareDepositOperation(prepared, environment, poolService) {
5363
5699
  privateAddress: prepared.intent.to,
5364
5700
  assetId: prepared.intent.asset,
5365
5701
  poolContract: environment.network.poolContract,
5366
- amount: prepared.intent.amount,
5367
- amountDisplay: Number(prepared.intent.amount) / Number(STROOPS_PER_UNIT),
5702
+ amount: BigInt(depositProof.coin.value),
5703
+ amountDisplay: Number(depositProof.coin.value) / Number(STROOPS_PER_UNIT),
5368
5704
  commitmentHex: depositProof.commitment_hex,
5369
5705
  coin: depositProof.coin,
5370
5706
  depositScalarHex: depositProof.depositScalarHex,
@@ -5380,7 +5716,11 @@ async function prepareDepositOperation(prepared, environment, poolService) {
5380
5716
  depositScalarHex: depositProof.depositScalarHex,
5381
5717
  precommitementHex: depositProof.precommitementHex,
5382
5718
  commitmentHex: depositProof.commitment_hex,
5383
- walletPublicKey
5719
+ walletPublicKey,
5720
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
5721
+ kind: "deposit",
5722
+ ...configuredNonceSpread(environment.zkConfigNonce)
5723
+ })
5384
5724
  });
5385
5725
  }
5386
5726
 
@@ -5554,9 +5894,6 @@ function escrowOutputFields(input) {
5554
5894
  recipientLo: input.escrowSend.recipientLo
5555
5895
  };
5556
5896
  }
5557
- function paddingDepositPair(recipientDeposit) {
5558
- return [recipientDeposit, "dummy"];
5559
- }
5560
5897
  async function buildChangeDepositPair(parameters) {
5561
5898
  const changeSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
5562
5899
  privateAddressStpl1: parameters.selfPrivateAddressStpl1ForChange.trim(),
@@ -5573,34 +5910,54 @@ async function buildChangeDepositPair(parameters) {
5573
5910
  }
5574
5911
  };
5575
5912
  }
5913
+ function recipientOnlyDeposits(recipientDeposit, hasFeeOutput) {
5914
+ if (hasFeeOutput) {
5915
+ return [recipientDeposit];
5916
+ }
5917
+ return [recipientDeposit, "dummy"];
5918
+ }
5919
+ async function depositsWithoutFee(input) {
5920
+ if (input.changeStroops <= ZERO_STROOPS) {
5921
+ return {
5922
+ deposits: recipientOnlyDeposits(input.recipientDeposit, input.hasFeeOutput)
5923
+ };
5924
+ }
5925
+ return buildChangeDepositPair({
5926
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
5927
+ changeStroops: input.changeStroops,
5928
+ tokenAddress: input.tokenAddress,
5929
+ recipientDeposit: input.recipientDeposit
5930
+ });
5931
+ }
5576
5932
  async function buildRecipientAndOptionalChangeDeposits(parameters) {
5577
5933
  const escrow = {
5578
5934
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5579
5935
  ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
5580
5936
  };
5581
- const recipientPrivateAddress = parameters.recipientPrivateAddressStpl1.trim();
5582
5937
  const recipientSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
5583
- privateAddressStpl1: recipientPrivateAddress,
5938
+ privateAddressStpl1: parameters.recipientPrivateAddressStpl1.trim(),
5584
5939
  amountStroops: parameters.transferStroops,
5585
5940
  tokenAddress: parameters.tokenAddress,
5586
5941
  ...escrowOutputFields(escrow)
5587
5942
  });
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,
5943
+ const withoutFee = await depositsWithoutFee({
5944
+ recipientDeposit: recipientSlot.deposit,
5596
5945
  changeStroops: parameters.changeStroops,
5946
+ selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
5947
+ tokenAddress: parameters.tokenAddress,
5948
+ hasFeeOutput: (parameters.feeOutput?.requiredFee ?? ZERO_STROOPS) > ZERO_STROOPS
5949
+ });
5950
+ const withFee = await appendFeeOutputSlot({
5951
+ deposits: withoutFee.deposits,
5597
5952
  tokenAddress: parameters.tokenAddress,
5598
- recipientDeposit: recipientSlot.deposit
5953
+ buildAlignedDepositSlot: (aligned) => getPrivacyPoolService().buildAlignedDepositSlot(aligned),
5954
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5599
5955
  });
5600
5956
  return {
5601
5957
  recipientSlot,
5602
- deposits: changeResult.deposits,
5603
- changeCoin: changeResult.changeCoin
5958
+ deposits: withFee.deposits,
5959
+ ..."changeCoin" in withoutFee ? { changeCoin: withoutFee.changeCoin } : {},
5960
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
5604
5961
  };
5605
5962
  }
5606
5963
 
@@ -5653,14 +6010,15 @@ function sweepOutputOwnerFields(parameters) {
5653
6010
  return { sweepOutputOwnerPubX, sweepOutputOwnerPubY };
5654
6011
  }
5655
6012
  async function buildTransferDepositsAndPublicInput(parameters) {
5656
- const { recipientSlot, deposits, changeCoin } = await buildRecipientAndOptionalChangeDeposits({
6013
+ const { recipientSlot, deposits, changeCoin, feeCoin } = await buildRecipientAndOptionalChangeDeposits({
5657
6014
  recipientPrivateAddressStpl1: parameters.recipientPrivateAddressStpl1,
5658
6015
  transferStroops: parameters.transferStroops,
5659
6016
  changeStroops: parameters.changeStroops,
5660
6017
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
5661
6018
  tokenAddress: parameters.tokenAddress,
5662
6019
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5663
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
6020
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
6021
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5664
6022
  });
5665
6023
  const publicInput = withTokenAddressPublicInputs(
5666
6024
  {
@@ -5675,6 +6033,7 @@ async function buildTransferDepositsAndPublicInput(parameters) {
5675
6033
  recipientSlot,
5676
6034
  deposits,
5677
6035
  ...changeCoin ? { changeCoin } : {},
6036
+ ...feeCoin ? { feeCoin } : {},
5678
6037
  publicInput
5679
6038
  };
5680
6039
  }
@@ -5690,7 +6049,8 @@ async function buildSenderTransferDepositsAndPublicInput(parameters) {
5690
6049
  parameters.senderPrivKeyScalarHex
5691
6050
  ),
5692
6051
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5693
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
6052
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
6053
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5694
6054
  });
5695
6055
  }
5696
6056
 
@@ -5714,11 +6074,20 @@ function stampDualWithdrawLegs(legs, parameters) {
5714
6074
  }
5715
6075
  };
5716
6076
  }
5717
- function buildDualTransferApplicationIds(sdk, applicationId, changeCoin) {
6077
+ function buildDualTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
6078
+ const outputIds = [applicationId];
6079
+ if (changeCoin) {
6080
+ outputIds.push(applicationId);
6081
+ } else if (!feeCoin) {
6082
+ outputIds.push("0");
6083
+ }
6084
+ if (feeCoin) {
6085
+ outputIds.push(applicationId);
6086
+ }
5718
6087
  return buildApplicationIdHints({
5719
6088
  sdk,
5720
6089
  inputIds: [applicationId, applicationId],
5721
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
6090
+ outputIds
5722
6091
  });
5723
6092
  }
5724
6093
  async function proveDualConfidentialTransfer(parameters) {
@@ -5738,16 +6107,24 @@ async function proveDualConfidentialTransfer(parameters) {
5738
6107
  audit
5739
6108
  );
5740
6109
  }
5741
- async function buildDualTransferProofContext(parameters, applicationId, sdk) {
6110
+ function dualTransferChangeStroops(parameters) {
5742
6111
  const totalNotes = BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value);
5743
- const changeStroops = changeStroopsAfterDualTransfer(
5744
- parameters.transferStroops,
5745
- totalNotes
5746
- );
6112
+ if (parameters.transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
6113
+ throw new Error("Transfer amount must be positive");
6114
+ }
6115
+ const changeStroops = remainingAfterRequiredFee({
6116
+ available: totalNotes,
6117
+ instructed: parameters.transferStroops,
6118
+ requiredFee: parameters.feeOutput?.requiredFee ?? 0n
6119
+ });
5747
6120
  requireChangeRecipientWhenPartial(
5748
6121
  changeStroops,
5749
6122
  parameters.selfPrivateAddressStpl1ForChange
5750
6123
  );
6124
+ return changeStroops;
6125
+ }
6126
+ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
6127
+ const changeStroops = dualTransferChangeStroops(parameters);
5751
6128
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
5752
6129
  parameters.senderPrivKeyScalarHex
5753
6130
  );
@@ -5774,15 +6151,21 @@ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
5774
6151
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
5775
6152
  tokenAddress: parameters.tokenAddress,
5776
6153
  stateRoot: legA.witness.stateRoot,
5777
- ...confidentialEscrowFields(parameters)
6154
+ ...confidentialEscrowFields(parameters),
6155
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5778
6156
  });
5779
6157
  return { legA, legB, applicationId, ...transferInputs };
5780
6158
  }
5781
6159
  async function prepareConfidentialTransferProofDual(parameters) {
5782
6160
  const poolService = getPrivacyPoolService();
5783
- const sdk = await poolService.getInitializedSdk();
6161
+ const sdk = await poolService.getInitializedSdk(
6162
+ poolService.feeBearingZkConfigNonce({
6163
+ kind: "transfer",
6164
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
6165
+ })
6166
+ );
5784
6167
  const applicationId = poolService.getApplicationId();
5785
- const { legA, legB, recipientSlot, deposits, changeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
6168
+ const { legA, legB, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
5786
6169
  const auditPublicKey = poolService.getAuditPublicKey();
5787
6170
  const proof = await (auditPublicKey ? proveDualConfidentialTransfer({
5788
6171
  sdk,
@@ -5805,7 +6188,8 @@ async function prepareConfidentialTransferProofDual(parameters) {
5805
6188
  applicationIdsPlaintext: buildDualTransferApplicationIds(
5806
6189
  sdk,
5807
6190
  applicationId,
5808
- changeCoin
6191
+ changeCoin,
6192
+ feeCoin
5809
6193
  ),
5810
6194
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
5811
6195
  ...changeCoin ? { changeCoin } : {}
@@ -5813,22 +6197,32 @@ async function prepareConfidentialTransferProofDual(parameters) {
5813
6197
  }
5814
6198
 
5815
6199
  // src/transact/proofs/confidential/single.ts
5816
- function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange) {
6200
+ function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange, requiredFee) {
5817
6201
  if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
5818
6202
  throw new Error("Transfer amount must be positive");
5819
6203
  }
5820
- if (transferStroops > noteStroops) {
5821
- throw new Error("Transfer amount exceeds note value");
5822
- }
5823
- const changeStroops = noteStroops - transferStroops;
6204
+ const changeStroops = remainingAfterRequiredFee({
6205
+ available: noteStroops,
6206
+ instructed: transferStroops,
6207
+ requiredFee
6208
+ });
5824
6209
  requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl1ForChange);
5825
6210
  return changeStroops;
5826
6211
  }
5827
- function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin) {
6212
+ function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
6213
+ const outputIds = [applicationId];
6214
+ if (changeCoin) {
6215
+ outputIds.push(applicationId);
6216
+ } else if (!feeCoin) {
6217
+ outputIds.push("0");
6218
+ }
6219
+ if (feeCoin) {
6220
+ outputIds.push(applicationId);
6221
+ }
5828
6222
  return buildApplicationIdHints({
5829
6223
  sdk,
5830
6224
  inputIds: [applicationId],
5831
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
6225
+ outputIds
5832
6226
  });
5833
6227
  }
5834
6228
  function buildTransferAuditParameters(parameters) {
@@ -5854,11 +6248,25 @@ function confidentialEscrowFields2(input) {
5854
6248
  ...input.escrowClaimantLimbs ? { escrowClaimantLimbs: input.escrowClaimantLimbs } : {}
5855
6249
  };
5856
6250
  }
6251
+ function transferDepositInputFromProof(input, changeStroops, stateRoot) {
6252
+ return {
6253
+ senderPrivKeyScalarHex: input.senderPrivKeyScalarHex,
6254
+ recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1,
6255
+ transferStroops: input.transferStroops,
6256
+ changeStroops,
6257
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
6258
+ tokenAddress: input.tokenAddress,
6259
+ stateRoot,
6260
+ ...confidentialEscrowFields2(input),
6261
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
6262
+ };
6263
+ }
5857
6264
  async function buildTransferProofInputs(parameters) {
5858
6265
  const changeStroops = validateSingleCoinTransferAmounts(
5859
6266
  parameters.input.transferStroops,
5860
6267
  parameters.noteStroops,
5861
- parameters.input.selfPrivateAddressStpl1ForChange
6268
+ parameters.input.selfPrivateAddressStpl1ForChange,
6269
+ parameters.input.feeOutput?.requiredFee ?? 0n
5862
6270
  );
5863
6271
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
5864
6272
  parameters.input.senderPrivKeyScalarHex
@@ -5877,31 +6285,30 @@ async function buildTransferProofInputs(parameters) {
5877
6285
  withdrawObject,
5878
6286
  sweepWithdrawStamp(confidentialEscrowFields2(parameters.input))
5879
6287
  );
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
- });
6288
+ const { recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput(
6289
+ transferDepositInputFromProof(parameters.input, changeStroops, witness.stateRoot)
6290
+ );
5890
6291
  return {
5891
6292
  witness,
5892
6293
  withdrawObject: stampedWithdraw,
5893
6294
  recipientSlot,
5894
6295
  deposits,
5895
6296
  changeCoin,
6297
+ feeCoin,
5896
6298
  publicInput
5897
6299
  };
5898
6300
  }
5899
6301
  async function prepareConfidentialTransferProof(parameters) {
5900
6302
  const poolService = getPrivacyPoolService();
5901
- const sdk = await poolService.getInitializedSdk();
6303
+ const sdk = await poolService.getInitializedSdk(
6304
+ poolService.feeBearingZkConfigNonce({
6305
+ kind: "transfer",
6306
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
6307
+ })
6308
+ );
5902
6309
  const applicationId = poolService.getApplicationId();
5903
6310
  const noteStroops = BigInt(parameters.coin.value);
5904
- const { withdrawObject, recipientSlot, deposits, changeCoin, publicInput } = await buildTransferProofInputs({
6311
+ const { withdrawObject, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildTransferProofInputs({
5905
6312
  sdk,
5906
6313
  applicationId,
5907
6314
  input: parameters,
@@ -5924,7 +6331,8 @@ async function prepareConfidentialTransferProof(parameters) {
5924
6331
  applicationIdsPlaintext: buildSingleTransferApplicationIds(
5925
6332
  sdk,
5926
6333
  applicationId,
5927
- changeCoin
6334
+ changeCoin,
6335
+ feeCoin
5928
6336
  ),
5929
6337
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
5930
6338
  ...changeCoin ? { changeCoin } : {}
@@ -5947,7 +6355,12 @@ function proofEscrowFields(input) {
5947
6355
  }
5948
6356
  async function buildSingleTransferProofAtExecute(input) {
5949
6357
  const context = await buildSpendProofContextAtExecute(input);
5950
- const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount;
6358
+ const feeOutput = await quoteFeeOutputForPrepared({
6359
+ prepared: input.prepared,
6360
+ environment: input.environment,
6361
+ tokenAddress: context.tokenAddress
6362
+ });
6363
+ const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5951
6364
  const proof = await prepareConfidentialTransferProof({
5952
6365
  coin: context.coin,
5953
6366
  state: { commitments: context.commitments },
@@ -5960,7 +6373,8 @@ async function buildSingleTransferProofAtExecute(input) {
5960
6373
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5961
6374
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5962
6375
  tokenAddress: context.tokenAddress,
5963
- ...proofEscrowFields(input)
6376
+ ...proofEscrowFields(input),
6377
+ ...feeOutput ? { feeOutput } : {}
5964
6378
  });
5965
6379
  return { context, proof };
5966
6380
  }
@@ -5977,7 +6391,12 @@ async function buildDualTransferProofAtExecute(input) {
5977
6391
  commitments: context.commitments
5978
6392
  });
5979
6393
  const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
5980
- const changeStroops = totalNotes - input.prepared.intent.amount;
6394
+ const feeOutput = await quoteFeeOutputForPrepared({
6395
+ prepared: input.prepared,
6396
+ environment: input.environment,
6397
+ tokenAddress: context.tokenAddress
6398
+ });
6399
+ const changeStroops = totalNotes - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5981
6400
  const proof = await prepareConfidentialTransferProofDual({
5982
6401
  coinA: context.coin,
5983
6402
  coinB: secondary.coin,
@@ -5995,7 +6414,8 @@ async function buildDualTransferProofAtExecute(input) {
5995
6414
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5996
6415
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5997
6416
  tokenAddress: context.tokenAddress,
5998
- ...proofEscrowFields(input)
6417
+ ...proofEscrowFields(input),
6418
+ ...feeOutput ? { feeOutput } : {}
5999
6419
  });
6000
6420
  return { context, proof };
6001
6421
  }
@@ -6127,11 +6547,17 @@ async function runTransferFinalizeSteps(input) {
6127
6547
  walletPublicKey: input.walletPublicKey,
6128
6548
  ...recipient.escrowSend ? { escrowSendPrivateAddress: recipient.recipientPrivateAddressStpl1 } : {}
6129
6549
  });
6130
- return buildTransferFinalizeArtifacts({
6131
- proof,
6132
- context,
6133
- ...sweepFinalizeStamp(input.prepared, recipient)
6134
- });
6550
+ return {
6551
+ ...buildTransferFinalizeArtifacts({
6552
+ proof,
6553
+ context,
6554
+ ...sweepFinalizeStamp(input.prepared, recipient)
6555
+ }),
6556
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6557
+ ...feeOutputKindFromPrepared(input.prepared),
6558
+ ...configuredNonceSpread(input.environment.zkConfigNonce)
6559
+ })
6560
+ };
6135
6561
  }
6136
6562
  function sweepArtifactStamp(artifacts) {
6137
6563
  return {
@@ -6159,6 +6585,24 @@ async function finalizeTransferAtExecute(prepared, environment) {
6159
6585
  return runTransferFinalizeSteps({ prepared, environment, walletPublicKey });
6160
6586
  }
6161
6587
 
6588
+ // src/transact/engine/withdraw-fee.ts
6589
+ async function withdrawFeeProofFields(input) {
6590
+ const feeOutput = await quoteFeeOutputForPrepared({
6591
+ prepared: input.prepared,
6592
+ environment: input.environment,
6593
+ tokenAddress: input.tokenAddress
6594
+ });
6595
+ const changeStroops = remainingAfterRequiredFee({
6596
+ available: input.available,
6597
+ instructed: input.prepared.intent.amount,
6598
+ requiredFee: feeOutput?.requiredFee ?? 0n
6599
+ });
6600
+ return {
6601
+ ...feeOutput ? { feeOutput } : {},
6602
+ ...changeStroops > 0n ? { changePrivateAddressStpl1: input.withdrawFrom } : {}
6603
+ };
6604
+ }
6605
+
6162
6606
  // src/transact/engine/execute.ts
6163
6607
  function enrichWithdrawOutputRecords(prepared, proof) {
6164
6608
  if (!proof.changeCoin || prepared.outputRecords.length === 0) {
@@ -6174,7 +6618,7 @@ function enrichWithdrawOutputRecords(prepared, proof) {
6174
6618
  precommitementHex: changeCoin.precommitementHex
6175
6619
  }));
6176
6620
  }
6177
- function buildWithdrawFinalizeArtifacts(proof, context) {
6621
+ function buildWithdrawFinalizeArtifacts(proof, context, environment) {
6178
6622
  return {
6179
6623
  proofHex: proof.proof_hex,
6180
6624
  publicHex: proof.public_hex,
@@ -6182,7 +6626,11 @@ function buildWithdrawFinalizeArtifacts(proof, context) {
6182
6626
  applicationIdsPlaintext: proof.applicationIdsPlaintext,
6183
6627
  tokenAddress: context.tokenAddress,
6184
6628
  walletPublicKey: context.walletPublicKey,
6185
- executeFinalizeRequired: false
6629
+ executeFinalizeRequired: false,
6630
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6631
+ kind: "withdraw",
6632
+ ...configuredNonceSpread(environment.zkConfigNonce)
6633
+ })
6186
6634
  };
6187
6635
  }
6188
6636
  async function finalizeSingleWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
@@ -6191,7 +6639,13 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6191
6639
  environment,
6192
6640
  recipientPrivateAddressStpl1: withdrawFrom
6193
6641
  });
6194
- const changeStroops = BigInt(context.coin.value) - prepared.intent.amount;
6642
+ const feeFields = await withdrawFeeProofFields({
6643
+ prepared,
6644
+ environment,
6645
+ tokenAddress: context.tokenAddress,
6646
+ available: BigInt(context.coin.value),
6647
+ withdrawFrom
6648
+ });
6195
6649
  const proof = await poolService.prepareWithdrawTransactProof({
6196
6650
  coin: context.coin,
6197
6651
  state: { commitments: context.commitments },
@@ -6202,11 +6656,24 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6202
6656
  yHex: context.ephemeral.yHex
6203
6657
  }),
6204
6658
  withdrawAmountStroops: prepared.intent.amount,
6205
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6206
- tokenAddress: context.tokenAddress
6659
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
6660
+ tokenAddress: context.tokenAddress,
6661
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
6207
6662
  });
6208
6663
  enrichWithdrawOutputRecords(prepared, proof);
6209
- return buildWithdrawFinalizeArtifacts(proof, context);
6664
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
6665
+ }
6666
+ function dualWithdrawEphemeralKeys(input) {
6667
+ return {
6668
+ depositorEphemeralAKey: serializeEphemeralKeyString({
6669
+ xHex: input.context.ephemeral.xHex,
6670
+ yHex: input.context.ephemeral.yHex
6671
+ }),
6672
+ depositorEphemeralBKey: serializeEphemeralKeyString({
6673
+ xHex: input.secondary.ephemeral.xHex,
6674
+ yHex: input.secondary.ephemeral.yHex
6675
+ })
6676
+ };
6210
6677
  }
6211
6678
  async function finalizeDualWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
6212
6679
  const [, secondaryRecord] = prepared.consumedRecords;
@@ -6224,28 +6691,27 @@ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService,
6224
6691
  walletPublicKey: context.walletPublicKey,
6225
6692
  commitments: context.commitments
6226
6693
  });
6227
- const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
6228
- const changeStroops = totalNotes - prepared.intent.amount;
6694
+ const feeFields = await withdrawFeeProofFields({
6695
+ prepared,
6696
+ environment,
6697
+ tokenAddress: context.tokenAddress,
6698
+ available: BigInt(context.coin.value) + BigInt(secondary.coin.value),
6699
+ withdrawFrom
6700
+ });
6229
6701
  const proof = await poolService.prepareWithdrawTransactProofDual({
6230
6702
  coinA: context.coin,
6231
6703
  coinB: secondary.coin,
6232
6704
  state: { commitments: context.commitments },
6233
6705
  destinationStellarAddress: prepared.intent.to,
6234
6706
  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
- }),
6707
+ ...dualWithdrawEphemeralKeys({ context, secondary }),
6243
6708
  withdrawAmountStroops: prepared.intent.amount,
6244
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6245
- tokenAddress: context.tokenAddress
6709
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
6710
+ tokenAddress: context.tokenAddress,
6711
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
6246
6712
  });
6247
6713
  enrichWithdrawOutputRecords(prepared, proof);
6248
- return buildWithdrawFinalizeArtifacts(proof, context);
6714
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
6249
6715
  }
6250
6716
  async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6251
6717
  if (prepared.kind !== "withdraw") {
@@ -6507,7 +6973,7 @@ async function requestKytPassageForPoolInteraction(input) {
6507
6973
  const applicationId = resolvePoolApplicationId(
6508
6974
  input.transactEnvironment.network.applicationId
6509
6975
  );
6510
- const zkConfigNonce = resolveZkConfigNonce(input.transactEnvironment);
6976
+ const zkConfigNonce = input.zkConfigNonce ?? resolveZkConfigNonce(input.transactEnvironment);
6511
6977
  const applicationIdsPlaintext = input.applicationIdsPlaintext ?? buildApplicationIdHints2(
6512
6978
  applicationId,
6513
6979
  layoutForKnownNonce(zkConfigNonce).nAuditSlots
@@ -6679,6 +7145,10 @@ async function submitSorobanConfidentialTransfer(input) {
6679
7145
  networkPassphrase: input.networkPassphrase,
6680
7146
  sorobanRpcUrl: input.sorobanRpcUrl
6681
7147
  });
7148
+ const nonce = transactNonceFromArtifacts(
7149
+ input.zkConfigNonce === void 0 ? void 0 : { zkConfigNonce: input.zkConfigNonce },
7150
+ input.transactEnvironment
7151
+ );
6682
7152
  const approval = await requestKytPassageForPoolInteraction({
6683
7153
  owner: input.walletPublicKey,
6684
7154
  poolContract: input.contractId,
@@ -6687,6 +7157,7 @@ async function submitSorobanConfidentialTransfer(input) {
6687
7157
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
6688
7158
  ...input.outputNoteEphemeralScalars ? { outputNoteEphemeralScalars: input.outputNoteEphemeralScalars } : {},
6689
7159
  ...input.applicationIdsPlaintext ? { applicationIdsPlaintext: input.applicationIdsPlaintext } : {},
7160
+ ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
6690
7161
  networkPassphrase: input.networkPassphrase,
6691
7162
  sorobanRpcUrl: input.sorobanRpcUrl,
6692
7163
  transactEnvironment: input.transactEnvironment
@@ -6695,7 +7166,7 @@ async function submitSorobanConfidentialTransfer(input) {
6695
7166
  contractClient: poolClient,
6696
7167
  contractId: input.contractId,
6697
7168
  from: input.walletPublicKey,
6698
- nonce: resolveZkConfigNonce(input.transactEnvironment),
7169
+ nonce,
6699
7170
  proofHex: input.proofHex,
6700
7171
  publicHex: input.publicHex,
6701
7172
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
@@ -6726,12 +7197,14 @@ async function submitPreparedDeposit(prepared, environment) {
6726
7197
  networkPassphrase: environment.network.networkPassphrase,
6727
7198
  sorobanRpcUrl: environment.network.rpcUrl
6728
7199
  });
7200
+ const nonce = transactNonceFromArtifacts(artifacts, environment);
6729
7201
  const approval = await requestKytPassageForPoolInteraction({
6730
7202
  owner: artifacts.walletPublicKey,
6731
7203
  poolContract: environment.network.poolContract,
6732
7204
  proofHex: artifacts.proofHex,
6733
7205
  publicHex: artifacts.publicHex,
6734
7206
  ...optionalInspectCiphertext(artifacts),
7207
+ ...artifacts.zkConfigNonce === void 0 ? {} : { zkConfigNonce: artifacts.zkConfigNonce },
6735
7208
  networkPassphrase: environment.network.networkPassphrase,
6736
7209
  sorobanRpcUrl: environment.network.rpcUrl,
6737
7210
  transactEnvironment: environment
@@ -6740,7 +7213,7 @@ async function submitPreparedDeposit(prepared, environment) {
6740
7213
  contractClient: poolClient,
6741
7214
  contractId: environment.network.poolContract,
6742
7215
  from: artifacts.walletPublicKey,
6743
- nonce: resolveZkConfigNonce(environment),
7216
+ nonce,
6744
7217
  proofHex: artifacts.proofHex,
6745
7218
  publicHex: artifacts.publicHex,
6746
7219
  ...artifacts.ciphertextHex ? { ciphertextHex: artifacts.ciphertextHex } : {},
@@ -6761,6 +7234,7 @@ async function submitPreparedPoolTransact(prepared, environment) {
6761
7234
  proofHex: artifacts.proofHex,
6762
7235
  publicHex: artifacts.publicHex,
6763
7236
  ...optionalInspectCiphertext(artifacts),
7237
+ ...artifacts.zkConfigNonce === void 0 ? {} : { zkConfigNonce: artifacts.zkConfigNonce },
6764
7238
  networkPassphrase: environment.network.networkPassphrase,
6765
7239
  sorobanRpcUrl: environment.network.rpcUrl,
6766
7240
  transactEnvironment: environment
@@ -6816,7 +7290,7 @@ async function privacyPoolInitOptions(assets, transactEnvironment) {
6816
7290
  };
6817
7291
  }
6818
7292
  async function createDefaultTransactEngine(assets, transactEnvironment) {
6819
- await PrivacyPoolSDK3.init(await privacyPoolInitOptions(assets, transactEnvironment));
7293
+ await PrivacyPoolSDK4.init(await privacyPoolInitOptions(assets, transactEnvironment));
6820
7294
  if (transactEnvironment) {
6821
7295
  configurePrivacyPoolService(
6822
7296
  createPrivacyPoolService({