@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/index.js CHANGED
@@ -454,27 +454,9 @@ function buildPrivateRecordFromDeposit(input) {
454
454
 
455
455
  // src/transact/pool/service.ts
456
456
  import {
457
- PrivacyPoolSDK as PrivacyPoolSDK2,
458
- decodeDecodedEphemeralKey,
459
- randomFrDecimal253,
460
- scalarHexToFrDecimal as scalarHexToFrDecimal2
457
+ decodeDecodedEphemeralKey
461
458
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
462
459
 
463
- // src/transact/merkle/field-decimal.ts
464
- import { Buffer as Buffer4 } from "buffer";
465
- var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
466
- var MERKLE_ROOT_BYTE_LENGTH = 32;
467
- var DECIMAL_STRING_RADIX = 10;
468
- function merkleRootBufferToFrDecimal(root) {
469
- if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
470
- throw new Error(
471
- `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
472
- );
473
- }
474
- const hex = Buffer4.from(root).toString("hex");
475
- return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
476
- }
477
-
478
460
  // src/transact/private-address/codec.ts
479
461
  import {
480
462
  buildStealthAddressSignMessage,
@@ -530,18 +512,18 @@ function privateAddressSdk(sdk) {
530
512
  import { withdrawObjectFromMerkleWitness as withdrawObjectFromMerkleWitness2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
531
513
 
532
514
  // src/transact/proofs/transaction-input.ts
533
- import { Buffer as Buffer6 } from "buffer";
515
+ import { Buffer as Buffer5 } from "buffer";
534
516
  import { StrKey as StrKey2 } from "@stellar/stellar-sdk";
535
517
 
536
518
  // src/transact/stellar/token-contract.ts
537
- import { Buffer as Buffer5 } from "buffer";
519
+ import { Buffer as Buffer4 } from "buffer";
538
520
  import { StrKey } from "@stellar/stellar-sdk";
539
521
  var CONTRACT_ID_BYTE_LENGTH = 32;
540
522
  var HEX_CHARS_PER_BYTE = 2;
541
523
  var CONTRACT_ID_HEX_CHAR_LENGTH = CONTRACT_ID_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
542
524
  var CONTRACT_ID_HEX = /^(?:0x)?([0-9a-f]{64})$/i;
543
525
  function encodeContractFromHex(hexPayload) {
544
- return StrKey.encodeContract(Buffer5.from(hexPayload, "hex"));
526
+ return StrKey.encodeContract(Buffer4.from(hexPayload, "hex"));
545
527
  }
546
528
  function normalizeSorobanContractId(raw) {
547
529
  const trimmed = raw.trim();
@@ -574,14 +556,14 @@ var ZERO_PUBLIC_WITHDRAW_ADDRESS = {
574
556
  var CONTRACT_ADDRESS_HALF_BYTES = 16;
575
557
  var CONTRACT_ADDRESS_BYTES = 32;
576
558
  function bytes16ToDecimal(bytes) {
577
- return BigInt(`0x${Buffer6.from(bytes).toString("hex")}`).toString();
559
+ return BigInt(`0x${Buffer5.from(bytes).toString("hex")}`).toString();
578
560
  }
579
561
  function decimalToBytes16(value) {
580
562
  const hex = BigInt(value).toString(16).padStart(CONTRACT_ADDRESS_HALF_BYTES * 2, "0");
581
563
  if (hex.length > CONTRACT_ADDRESS_HALF_BYTES * 2) {
582
564
  throw new Error("Asset leg does not fit into 16 bytes.");
583
565
  }
584
- return Buffer6.from(hex, "hex");
566
+ return Buffer5.from(hex, "hex");
585
567
  }
586
568
  function tokenAddressFrParts(tokenAddress) {
587
569
  const normalized = resolveTokenContractId(tokenAddress);
@@ -623,7 +605,7 @@ function tokenAddressToAssetLeg(tokenAddress) {
623
605
  return [hi, lo];
624
606
  }
625
607
  function assetLegToTokenAddress(assetHi, assetLo) {
626
- const payload = Buffer6.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
608
+ const payload = Buffer5.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
627
609
  return StrKey2.encodeContract(payload);
628
610
  }
629
611
  function buildZeroPublicLegs(publicNInputs = 1, publicNOutputs = 1) {
@@ -746,14 +728,144 @@ function requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl
746
728
  }
747
729
  }
748
730
  }
749
- function changeStroopsAfterDualTransfer(transferStroops, totalNotes) {
750
- if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
751
- throw new Error("Transfer amount must be positive");
731
+
732
+ // src/transact/fees/quote-request-body.ts
733
+ function presentInstructedAmount(amount) {
734
+ const trimmed = amount?.trim() ?? "";
735
+ if (!trimmed || trimmed === "0") {
736
+ return void 0;
752
737
  }
753
- if (transferStroops > totalNotes) {
754
- throw new Error("Transfer amount exceeds combined note value");
738
+ return trimmed;
739
+ }
740
+ function feeQuoteRequestBody(request) {
741
+ const publicDepositAmount = presentInstructedAmount(request.publicDepositAmount);
742
+ const transferInstructedAmount = presentInstructedAmount(
743
+ request.transferInstructedAmount
744
+ );
745
+ const publicWithdrawalAmount = presentInstructedAmount(
746
+ request.publicWithdrawalAmount
747
+ );
748
+ return {
749
+ feeAsset: request.feeAsset,
750
+ spendsNotes: request.spendsNotes,
751
+ ...publicDepositAmount === void 0 ? {} : { publicDepositAmount },
752
+ ...transferInstructedAmount === void 0 ? {} : { transferInstructedAmount },
753
+ ...publicWithdrawalAmount === void 0 ? {} : { publicWithdrawalAmount }
754
+ };
755
+ }
756
+
757
+ // src/transact/fees/quote-required-fee.ts
758
+ function quoteHeaders(inspectAuthorization) {
759
+ const token = inspectAuthorization?.trim();
760
+ return {
761
+ "content-type": "application/json",
762
+ ...token ? { authorization: `Bearer ${token}` } : {}
763
+ };
764
+ }
765
+ function parseRequiredFee(raw) {
766
+ const text = `${raw ?? ""}`.trim();
767
+ if (!/^\d+$/u.test(text)) {
768
+ throw new Error("Fee Quote Required Fee must be an integer minor-unit string.");
755
769
  }
756
- return totalNotes - transferStroops;
770
+ return BigInt(text);
771
+ }
772
+ function requireQuoteString(raw, field) {
773
+ const text = `${raw ?? ""}`.trim();
774
+ if (!text) {
775
+ throw new Error(`Fee Quote is missing ${field}.`);
776
+ }
777
+ return text;
778
+ }
779
+ function parseQuotePayload(payload) {
780
+ return {
781
+ requiredFee: parseRequiredFee(payload.requiredFee),
782
+ feeAsset: requireQuoteString(payload.feeAsset, "Fee Asset"),
783
+ feeCollectorPrivateAddress: requireQuoteString(
784
+ payload.feeCollectorPrivateAddress,
785
+ "Fee Collector Private Address"
786
+ ),
787
+ feeRate: requireQuoteString(payload.feeRate, "Fee Rate")
788
+ };
789
+ }
790
+ async function quoteRequiredFee(input) {
791
+ const response = await fetch(
792
+ `${input.transactEnvironment.kyt.apiBaseUrl}/kyt/fees/quote`,
793
+ {
794
+ method: "POST",
795
+ headers: quoteHeaders(input.transactEnvironment.kyt.inspectAuthorization),
796
+ body: JSON.stringify(feeQuoteRequestBody(input.request))
797
+ }
798
+ );
799
+ const payload = await response.json();
800
+ if (!response.ok) {
801
+ throw new Error(`Fee Quote failed: ${JSON.stringify(payload)}`);
802
+ }
803
+ return parseQuotePayload(payload);
804
+ }
805
+
806
+ // src/transact/fees/should-attach-fee-output.ts
807
+ function shouldAttachFeeOutput(input) {
808
+ if (input.spendSource === "escrow" && input.escrowSend !== true) {
809
+ return false;
810
+ }
811
+ return input.kind === "deposit" || input.kind === "transfer" || input.kind === "withdraw";
812
+ }
813
+ function feeOutputKindFromPrepared(prepared) {
814
+ return {
815
+ kind: prepared.kind,
816
+ ...prepared.transactArtifacts?.spendSource ? { spendSource: prepared.transactArtifacts.spendSource } : {},
817
+ ...prepared.transactArtifacts?.escrowSend ? { escrowSend: true } : {}
818
+ };
819
+ }
820
+
821
+ // src/transact/fees/quote-fee-output-for-prepared.ts
822
+ function instructedMinorUnits(prepared) {
823
+ return prepared.intent.amount.toString();
824
+ }
825
+ function quoteRequestForPrepared(input) {
826
+ const amount = instructedMinorUnits(input.prepared);
827
+ if (input.prepared.kind === "deposit") {
828
+ return {
829
+ feeAsset: input.tokenAddress,
830
+ publicDepositAmount: amount,
831
+ spendsNotes: false
832
+ };
833
+ }
834
+ if (input.prepared.kind === "withdraw") {
835
+ return {
836
+ feeAsset: input.tokenAddress,
837
+ publicWithdrawalAmount: amount,
838
+ spendsNotes: true
839
+ };
840
+ }
841
+ return {
842
+ feeAsset: input.tokenAddress,
843
+ transferInstructedAmount: amount,
844
+ spendsNotes: true
845
+ };
846
+ }
847
+ async function quoteFeeOutputForPrepared(input) {
848
+ if (!shouldAttachFeeOutput(feeOutputKindFromPrepared(input.prepared))) {
849
+ return void 0;
850
+ }
851
+ const quote = await quoteRequiredFee({
852
+ transactEnvironment: input.environment,
853
+ request: quoteRequestForPrepared(input)
854
+ });
855
+ if (quote.requiredFee <= 0n) {
856
+ return void 0;
857
+ }
858
+ return {
859
+ requiredFee: quote.requiredFee,
860
+ feeCollectorPrivateAddress: quote.feeCollectorPrivateAddress
861
+ };
862
+ }
863
+ function remainingAfterRequiredFee(input) {
864
+ const remaining = input.available - input.instructed - input.requiredFee;
865
+ if (remaining < 0n) {
866
+ throw new Error("Instructed amount and Required Fee exceed available note value");
867
+ }
868
+ return remaining;
757
869
  }
758
870
 
759
871
  // src/transact/audit/parameters.ts
@@ -761,7 +873,7 @@ import {
761
873
  DEMO_AUDIT_PUBLIC_KEY,
762
874
  resolveTransactionAuditParams
763
875
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
764
- import { Buffer as Buffer7 } from "buffer";
876
+ import { Buffer as Buffer6 } from "buffer";
765
877
  var UNCOMPRESSED_PREFIX = "04";
766
878
  var UNCOMPRESSED_HEX_LENGTH = 130;
767
879
  var XY_HEX_LENGTH = 128;
@@ -788,7 +900,7 @@ function parseAuditPublicKeyFromEnvHex(raw) {
788
900
  "Audit public key must be uncompressed 65-byte hex (04 + X + Y) or 64-byte X+Y hex"
789
901
  );
790
902
  }
791
- const bytes = Buffer7.from(payload, "hex");
903
+ const bytes = Buffer6.from(payload, "hex");
792
904
  const auditPublicKeyXDecimal = BigInt(
793
905
  `0x${bytes.subarray(0, X_OFFSET_END).toString("hex")}`
794
906
  ).toString(DECIMAL_RADIX);
@@ -831,13 +943,13 @@ import { ed25519PubkeyPayloadHexToWithdrawFrDecimals } from "@arcanetech/stellar
831
943
 
832
944
  // src/transact/stellar/account.ts
833
945
  import { StrKey as StrKey3 } from "@stellar/stellar-sdk";
834
- import { Buffer as Buffer8 } from "buffer";
946
+ import { Buffer as Buffer7 } from "buffer";
835
947
  var MUXED_ACCOUNT_ID_BYTE_LENGTH = 8;
836
948
  var ED25519_PUBLIC_KEY_BYTE_LENGTH = 32;
837
949
  function ed25519HexFromGAddress(trimmed) {
838
950
  try {
839
951
  const raw = StrKey3.decodeEd25519PublicKey(trimmed);
840
- return Buffer8.from(raw).toString("hex");
952
+ return Buffer7.from(raw).toString("hex");
841
953
  } catch {
842
954
  return void 0;
843
955
  }
@@ -894,6 +1006,32 @@ function privKeyScalarDecimalFromRecipientScalarHex(hexInput) {
894
1006
  return canonicalBabyJubScalarFromInteger(value).toString(DECIMAL_RADIX2);
895
1007
  }
896
1008
 
1009
+ // src/transact/fees/append-fee-output.ts
1010
+ function feeCoinFromSlot(slot) {
1011
+ return {
1012
+ commitment_hex: slot.commitment_hex,
1013
+ coin: slot.coin,
1014
+ depositScalarHex: slot.depositScalarHex,
1015
+ precommitementHex: slot.precommitementHex
1016
+ };
1017
+ }
1018
+ async function appendFeeOutputSlot(input) {
1019
+ const requiredFee = input.feeOutput?.requiredFee ?? ZERO_STROOPS;
1020
+ const collector = input.feeOutput?.feeCollectorPrivateAddress.trim() ?? "";
1021
+ if (requiredFee <= ZERO_STROOPS || !collector) {
1022
+ return { deposits: input.deposits };
1023
+ }
1024
+ const slot = await input.buildAlignedDepositSlot({
1025
+ privateAddressStpl1: collector,
1026
+ amountStroops: requiredFee,
1027
+ tokenAddress: input.tokenAddress
1028
+ });
1029
+ return {
1030
+ deposits: [...input.deposits, slot.deposit],
1031
+ feeCoin: feeCoinFromSlot(slot)
1032
+ };
1033
+ }
1034
+
897
1035
  // src/transact/proofs/withdraw/helpers.ts
898
1036
  function destinationWithdrawFrAndScalar(parameters) {
899
1037
  const pkHex = ed25519PublicKeyHexFromStellarAccount(
@@ -948,14 +1086,15 @@ async function resolveWithdrawChangeDeposits(parameters) {
948
1086
  }
949
1087
  };
950
1088
  }
951
- function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1) {
1089
+ function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1, requiredFee) {
952
1090
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
953
1091
  throw new Error("Withdraw amount must be positive");
954
1092
  }
955
- if (withdrawAmountStroops > totalNotes) {
956
- throw new Error("Withdraw amount exceeds combined note value");
957
- }
958
- const changeStroops = totalNotes - withdrawAmountStroops;
1093
+ const changeStroops = remainingAfterRequiredFee({
1094
+ available: totalNotes,
1095
+ instructed: withdrawAmountStroops,
1096
+ requiredFee
1097
+ });
959
1098
  requireChangeRecipientWhenPartial(changeStroops, changePrivateAddressStpl1);
960
1099
  return changeStroops;
961
1100
  }
@@ -968,11 +1107,31 @@ function buildDualWithdrawPublicInput(parameters) {
968
1107
  publicWithdrawalAmount: parameters.publicWithdrawalAmount
969
1108
  });
970
1109
  }
1110
+ async function withdrawOutputsWithOptionalFee(parameters) {
1111
+ const { deposits: changeDeposits, changeCoin } = await resolveWithdrawChangeDeposits({
1112
+ changeStroops: parameters.changeStroops,
1113
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1114
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1115
+ tokenAddress: parameters.tokenAddress
1116
+ });
1117
+ const withFee = await appendFeeOutputSlot({
1118
+ deposits: changeDeposits,
1119
+ tokenAddress: parameters.tokenAddress,
1120
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1121
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1122
+ });
1123
+ return {
1124
+ deposits: withFee.deposits,
1125
+ ...changeCoin ? { changeCoin } : {},
1126
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
1127
+ };
1128
+ }
971
1129
  async function prepareDualWithdrawProofInputs(parameters) {
972
1130
  const changeStroops = validateDualWithdrawAmounts(
973
1131
  parameters.withdrawAmountStroops,
974
1132
  BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value),
975
- parameters.changePrivateAddressStpl1
1133
+ parameters.changePrivateAddressStpl1,
1134
+ parameters.feeOutput?.requiredFee ?? 0n
976
1135
  );
977
1136
  const { legA, legB } = dualWithdrawLegsWithSharedRoot({
978
1137
  sdk: parameters.sdk,
@@ -987,11 +1146,12 @@ async function prepareDualWithdrawProofInputs(parameters) {
987
1146
  ),
988
1147
  applicationId: parameters.applicationId
989
1148
  });
990
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1149
+ const { deposits, changeCoin, feeCoin } = await withdrawOutputsWithOptionalFee({
991
1150
  changeStroops,
992
1151
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
993
1152
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
994
- tokenAddress: parameters.tokenAddress
1153
+ tokenAddress: parameters.tokenAddress,
1154
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
995
1155
  });
996
1156
  const publicInput = buildDualWithdrawPublicInput({
997
1157
  destinationStellarAddress: parameters.destinationStellarAddress,
@@ -1010,19 +1170,55 @@ async function prepareDualWithdrawProofInputs(parameters) {
1010
1170
  audit,
1011
1171
  deposits,
1012
1172
  withdrawLegs: [legA.withdrawObject, legB.withdrawObject],
1013
- ...changeCoin ? { changeCoin } : {}
1173
+ ...changeCoin ? { changeCoin } : {},
1174
+ ...feeCoin ? { feeCoin } : {}
1014
1175
  };
1015
1176
  }
1016
1177
 
1178
+ // src/transact/proofs/withdraw/fee-deposits.ts
1179
+ async function withdrawDepositsWithFee(parameters) {
1180
+ const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1181
+ changeStroops: parameters.changeStroops,
1182
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1183
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1184
+ tokenAddress: parameters.tokenAddress
1185
+ });
1186
+ const withFee = await appendFeeOutputSlot({
1187
+ deposits,
1188
+ tokenAddress: parameters.tokenAddress,
1189
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1190
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1191
+ });
1192
+ return {
1193
+ deposits: withFee.deposits,
1194
+ ...changeCoin ? { changeCoin } : {},
1195
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
1196
+ };
1197
+ }
1198
+ function withdrawOutputApplicationIds(input) {
1199
+ const outputIds = [];
1200
+ if (input.hasChange) {
1201
+ outputIds.push(input.applicationId);
1202
+ }
1203
+ if (input.hasFeeOutput) {
1204
+ outputIds.push(input.applicationId);
1205
+ }
1206
+ if (outputIds.length === 0) {
1207
+ outputIds.push("0");
1208
+ }
1209
+ return outputIds;
1210
+ }
1211
+
1017
1212
  // src/transact/proofs/withdraw/transact-proof.ts
1018
- function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops) {
1213
+ function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops, requiredFee) {
1019
1214
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
1020
1215
  throw new Error("Withdraw amount must be positive");
1021
1216
  }
1022
- if (withdrawAmountStroops > noteStroops) {
1023
- throw new Error("Withdraw amount exceeds note value");
1024
- }
1025
- return noteStroops - withdrawAmountStroops;
1217
+ return remainingAfterRequiredFee({
1218
+ available: noteStroops,
1219
+ instructed: withdrawAmountStroops,
1220
+ requiredFee
1221
+ });
1026
1222
  }
1027
1223
  async function proveSingleWithdrawTransaction(parameters) {
1028
1224
  return parameters.sdk.proveTransaction(
@@ -1061,7 +1257,8 @@ function buildPrimaryWithdrawForCoin(parameters) {
1061
1257
  async function prepareSingleWithdrawProofInputs(parameters) {
1062
1258
  const changeStroops = validateSingleWithdrawAmounts(
1063
1259
  parameters.withdrawAmountStroops,
1064
- BigInt(parameters.coin.value)
1260
+ BigInt(parameters.coin.value),
1261
+ parameters.feeOutput?.requiredFee ?? 0n
1065
1262
  );
1066
1263
  const { witness, primaryWithdraw } = buildPrimaryWithdrawForCoin({
1067
1264
  sdk: parameters.sdk,
@@ -1070,11 +1267,12 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1070
1267
  state: parameters.state,
1071
1268
  privKeyScalarHex: parameters.privKeyScalarHex
1072
1269
  });
1073
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
1270
+ const { deposits, changeCoin, feeCoin } = await withdrawDepositsWithFee({
1074
1271
  changeStroops,
1075
1272
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1076
1273
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
1077
- tokenAddress: parameters.tokenAddress
1274
+ tokenAddress: parameters.tokenAddress,
1275
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1078
1276
  });
1079
1277
  const publicInput = buildWithdrawPublicInput({
1080
1278
  stateRoot: witness.stateRoot,
@@ -1087,6 +1285,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1087
1285
  primaryWithdraw,
1088
1286
  deposits,
1089
1287
  changeCoin,
1288
+ feeCoin,
1090
1289
  publicInput,
1091
1290
  audit: buildPoolTransactionAuditParameters({
1092
1291
  applicationId: parameters.applicationId,
@@ -1096,7 +1295,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
1096
1295
  };
1097
1296
  }
1098
1297
  async function proveWithdrawTransact(parameters) {
1099
- const { primaryWithdraw, deposits, changeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
1298
+ const { primaryWithdraw, deposits, changeCoin, feeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
1100
1299
  const proof = await proveSingleWithdrawTransaction({
1101
1300
  sdk: parameters.sdk,
1102
1301
  publicInput,
@@ -1111,13 +1310,17 @@ async function proveWithdrawTransact(parameters) {
1111
1310
  applicationIdsPlaintext: buildApplicationIdHints({
1112
1311
  sdk: parameters.sdk,
1113
1312
  inputIds: [parameters.applicationId],
1114
- outputIds: [changeCoin ? parameters.applicationId : "0"]
1313
+ outputIds: withdrawOutputApplicationIds({
1314
+ applicationId: parameters.applicationId,
1315
+ hasChange: Boolean(changeCoin),
1316
+ hasFeeOutput: Boolean(feeCoin)
1317
+ })
1115
1318
  }),
1116
1319
  ...changeCoin ? { changeCoin } : {}
1117
1320
  };
1118
1321
  }
1119
1322
  async function proveWithdrawTransactDual(parameters) {
1120
- const { publicInput, audit, withdrawLegs, deposits, changeCoin } = await prepareDualWithdrawProofInputs(parameters);
1323
+ const { publicInput, audit, withdrawLegs, deposits, changeCoin, feeCoin } = await prepareDualWithdrawProofInputs(parameters);
1121
1324
  const proof = await parameters.sdk.proveTransaction(
1122
1325
  publicInput,
1123
1326
  padPublicLegsToLayout(
@@ -1136,7 +1339,11 @@ async function proveWithdrawTransactDual(parameters) {
1136
1339
  applicationIdsPlaintext: buildApplicationIdHints({
1137
1340
  sdk: parameters.sdk,
1138
1341
  inputIds: [audit.applicationId, audit.applicationId],
1139
- outputIds: [changeCoin ? audit.applicationId : "0"]
1342
+ outputIds: withdrawOutputApplicationIds({
1343
+ applicationId: audit.applicationId,
1344
+ hasChange: Boolean(changeCoin),
1345
+ hasFeeOutput: Boolean(feeCoin)
1346
+ })
1140
1347
  }),
1141
1348
  ...changeCoin ? { changeCoin } : {}
1142
1349
  };
@@ -1160,6 +1367,9 @@ var DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
1160
1367
  function resolveZkConfigNonce(transactEnvironment) {
1161
1368
  return transactEnvironment.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1162
1369
  }
1370
+ function transactNonceFromArtifacts(artifacts, transactEnvironment) {
1371
+ return artifacts?.zkConfigNonce ?? resolveZkConfigNonce(transactEnvironment);
1372
+ }
1163
1373
 
1164
1374
  // src/transact/zk/browser-artifact-cache.ts
1165
1375
  import { ZK_ARTIFACT_VERSION } from "@arcanetech/stellar-privacy-pool-zk-sdk";
@@ -1412,6 +1622,122 @@ async function buildAlignedDepositSlotForSdk(sdk, applicationId, parameters) {
1412
1622
  };
1413
1623
  }
1414
1624
 
1625
+ // src/transact/pool/initialize-sdk.ts
1626
+ import { PrivacyPoolSDK as PrivacyPoolSDK2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1627
+ async function initializePrivacyPoolSdk(input, nonce) {
1628
+ const zkConfigNonce = nonce ?? input.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1629
+ const zkCircuits = await materializeSelectedZkCircuit(
1630
+ input.zkCircuits,
1631
+ zkConfigNonce,
1632
+ input.zkArtifactBaseUrl
1633
+ );
1634
+ return PrivacyPoolSDK2.init({
1635
+ wasmBinary: input.assets.sdkWasm,
1636
+ zkCircuits,
1637
+ zkConfigNonce,
1638
+ ...optionalZkArtifactBaseUrl(input.zkArtifactBaseUrl)
1639
+ });
1640
+ }
1641
+
1642
+ // src/transact/fees/zk-config-nonce-for-kind.ts
1643
+ function configuredNonceSpread(zkConfigNonce) {
1644
+ if (zkConfigNonce === void 0) {
1645
+ return {};
1646
+ }
1647
+ return { configuredNonce: zkConfigNonce };
1648
+ }
1649
+ function zkConfigNonceForFeeBearingKind(input) {
1650
+ if (input.configuredNonce === SIX_BY_SIX_BINDING_ZK_NONCE) {
1651
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1652
+ }
1653
+ if (input.kind === "transfer" && shouldAttachFeeOutput(input)) {
1654
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1655
+ }
1656
+ return input.configuredNonce ?? BINDING_ZK_NONCE;
1657
+ }
1658
+
1659
+ // src/transact/pool/prepare-deposit-proof.ts
1660
+ import {
1661
+ randomFrDecimal253,
1662
+ scalarHexToFrDecimal as scalarHexToFrDecimal2
1663
+ } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1664
+
1665
+ // src/transact/merkle/field-decimal.ts
1666
+ import { Buffer as Buffer8 } from "buffer";
1667
+ var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1668
+ var MERKLE_ROOT_BYTE_LENGTH = 32;
1669
+ var DECIMAL_STRING_RADIX = 10;
1670
+ function merkleRootBufferToFrDecimal(root) {
1671
+ if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
1672
+ throw new Error(
1673
+ `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
1674
+ );
1675
+ }
1676
+ const hex = Buffer8.from(root).toString("hex");
1677
+ return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
1678
+ }
1679
+
1680
+ // src/transact/pool/prepare-deposit-proof.ts
1681
+ function userDepositSlot(input) {
1682
+ return {
1683
+ value: input.coin.value,
1684
+ nullifier: input.coin.nullifier,
1685
+ ephemeralKeyScalar: scalarHexToFrDecimal2(input.depositScalarHex),
1686
+ asset: [input.coin.asset_hi, input.coin.asset_lo],
1687
+ applicationId: input.applicationId,
1688
+ recipientPublicKeys: recipientPublicKeysDecimalFromPrivateAddress(
1689
+ input.privateAddressStpl1
1690
+ )
1691
+ };
1692
+ }
1693
+ function outputApplicationIds(input) {
1694
+ if (input.hasFeeOutput) {
1695
+ return [input.applicationId, input.applicationId];
1696
+ }
1697
+ return [input.applicationId];
1698
+ }
1699
+ async function proveDepositTransact(input) {
1700
+ const publicDeposit = (input.publicDepositStroops ?? BigInt(input.coin.value)).toString();
1701
+ const withFee = await appendFeeOutputSlot({
1702
+ deposits: [userDepositSlot(input)],
1703
+ tokenAddress: input.tokenAddress,
1704
+ buildAlignedDepositSlot: input.buildAlignedDepositSlot,
1705
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
1706
+ });
1707
+ const audit = buildPoolTransactionAuditParameters({
1708
+ applicationId: input.applicationId,
1709
+ nAuditSlots: input.sdk.getLayout().nAuditSlots,
1710
+ ...input.auditPublicKey ? { auditPublicKey: input.auditPublicKey } : {}
1711
+ });
1712
+ const proof = await input.sdk.proveTransaction(
1713
+ withTokenAddressPublicInputs(
1714
+ {
1715
+ stateRoot: merkleRootBufferToFrDecimal(input.merkleRootBytes),
1716
+ privKeyScalar: randomFrDecimal253()
1717
+ },
1718
+ input.tokenAddress
1719
+ ),
1720
+ padPublicLegsToLayout(
1721
+ input.sdk,
1722
+ buildPublicDepositLegs(input.tokenAddress, publicDeposit)
1723
+ ),
1724
+ padWithdrawSlotsToLayout(input.sdk, []),
1725
+ padDepositSlotsToLayout(input.sdk, withFee.deposits),
1726
+ audit
1727
+ );
1728
+ return {
1729
+ ...proof,
1730
+ applicationIdsPlaintext: buildApplicationIdHints({
1731
+ sdk: input.sdk,
1732
+ inputIds: [],
1733
+ outputIds: outputApplicationIds({
1734
+ applicationId: input.applicationId,
1735
+ hasFeeOutput: Boolean(withFee.feeCoin)
1736
+ })
1737
+ })
1738
+ };
1739
+ }
1740
+
1415
1741
  // src/transact/pool/service.ts
1416
1742
  var PrivacyPoolService = class {
1417
1743
  constructor(assets, applicationId, auditPublicKey, zkCircuits, zkConfigNonce, zkArtifactBaseUrl) {
@@ -1428,42 +1754,50 @@ var PrivacyPoolService = class {
1428
1754
  zkCircuits;
1429
1755
  zkConfigNonce;
1430
1756
  zkArtifactBaseUrl;
1431
- sdk = void 0;
1432
- initPromise = void 0;
1433
- async ensureInit() {
1434
- if (this.sdk) {
1435
- return;
1757
+ sdks = /* @__PURE__ */ new Map();
1758
+ initPromises = /* @__PURE__ */ new Map();
1759
+ async getInitializedSdk(nonce) {
1760
+ const zkConfigNonce = nonce ?? this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1761
+ const key = zkConfigNonce.toString();
1762
+ const existing = this.sdks.get(key);
1763
+ if (existing) {
1764
+ return existing;
1436
1765
  }
1437
- if (this.initPromise) {
1438
- return this.initPromise;
1766
+ const pending = this.initPromises.get(key);
1767
+ if (pending) {
1768
+ return pending;
1439
1769
  }
1440
- this.initPromise = (async () => {
1441
- if (!this.assets) {
1442
- throw new Error(
1443
- "PrivacyPoolService requires browser ZK assets before initialization."
1444
- );
1445
- }
1446
- const zkConfigNonce = this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1447
- const zkCircuits = await materializeSelectedZkCircuit(
1448
- this.zkCircuits,
1449
- zkConfigNonce,
1450
- this.zkArtifactBaseUrl
1451
- );
1452
- this.sdk = await PrivacyPoolSDK2.init({
1453
- wasmBinary: this.assets.sdkWasm,
1454
- zkCircuits,
1455
- zkConfigNonce,
1456
- ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1457
- });
1458
- })();
1459
- return this.initPromise;
1770
+ const started = this.initializeSdk(zkConfigNonce);
1771
+ this.initPromises.set(key, started);
1772
+ try {
1773
+ const sdk = await started;
1774
+ this.sdks.set(key, sdk);
1775
+ return sdk;
1776
+ } finally {
1777
+ this.initPromises.delete(key);
1778
+ }
1779
+ }
1780
+ feeBearingZkConfigNonce(input) {
1781
+ return zkConfigNonceForFeeBearingKind({
1782
+ ...input,
1783
+ ...configuredNonceSpread(this.zkConfigNonce)
1784
+ });
1460
1785
  }
1461
- async getInitializedSdk() {
1462
- await this.ensureInit();
1463
- if (!this.sdk) {
1464
- throw new Error("SDK not initialized");
1786
+ async initializeSdk(nonce) {
1787
+ if (!this.assets) {
1788
+ throw new Error(
1789
+ "PrivacyPoolService requires browser ZK assets before initialization."
1790
+ );
1465
1791
  }
1466
- return this.sdk;
1792
+ return initializePrivacyPoolSdk(
1793
+ {
1794
+ assets: this.assets,
1795
+ ...this.zkCircuits ? { zkCircuits: this.zkCircuits } : {},
1796
+ ...this.zkConfigNonce === void 0 ? {} : { zkConfigNonce: this.zkConfigNonce },
1797
+ ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1798
+ },
1799
+ nonce
1800
+ );
1467
1801
  }
1468
1802
  getAuditPublicKey() {
1469
1803
  return this.auditPublicKey;
@@ -1496,65 +1830,51 @@ var PrivacyPoolService = class {
1496
1830
  return { commitment_hex, coin, depositScalarHex, precommitementHex };
1497
1831
  }
1498
1832
  async prepareDepositTransactProof(parameters) {
1499
- const sdk = await this.getInitializedSdk();
1500
- const stateRoot = merkleRootBufferToFrDecimal(parameters.merkleRootBytes);
1501
- const recipientPublicKeys = recipientPublicKeysDecimalFromPrivateAddress(
1502
- parameters.privateAddressStpl1
1833
+ const sdk = await this.getInitializedSdk(
1834
+ this.feeBearingZkConfigNonce({ kind: "deposit" })
1503
1835
  );
1504
- const applicationId = this.getApplicationId();
1505
- const audit = buildPoolTransactionAuditParameters({
1506
- applicationId,
1507
- nAuditSlots: sdk.getLayout().nAuditSlots,
1508
- ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {}
1509
- });
1510
- const deposit = {
1511
- value: parameters.coin.value,
1512
- nullifier: parameters.coin.nullifier,
1513
- ephemeralKeyScalar: scalarHexToFrDecimal2(parameters.depositScalarHex),
1514
- asset: [parameters.coin.asset_hi, parameters.coin.asset_lo],
1515
- applicationId,
1516
- recipientPublicKeys
1517
- };
1518
- const publicInput = withTokenAddressPublicInputs(
1519
- {
1520
- stateRoot,
1521
- privKeyScalar: randomFrDecimal253()
1522
- },
1523
- parameters.tokenAddress
1524
- );
1525
- const proof = await sdk.proveTransaction(
1526
- publicInput,
1527
- padPublicLegsToLayout(
1528
- sdk,
1529
- buildPublicDepositLegs(parameters.tokenAddress, parameters.coin.value)
1530
- ),
1531
- padWithdrawSlotsToLayout(sdk, []),
1532
- padDepositSlotsToLayout(sdk, [deposit]),
1533
- audit
1534
- );
1535
- const applicationIdsPlaintext = buildApplicationIdHints({
1836
+ return proveDepositTransact({
1536
1837
  sdk,
1537
- inputIds: [],
1538
- outputIds: [applicationId]
1838
+ applicationId: this.getApplicationId(),
1839
+ privateAddressStpl1: parameters.privateAddressStpl1,
1840
+ coin: parameters.coin,
1841
+ depositScalarHex: parameters.depositScalarHex,
1842
+ merkleRootBytes: parameters.merkleRootBytes,
1843
+ tokenAddress: parameters.tokenAddress,
1844
+ buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1845
+ ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1846
+ ...parameters.publicDepositStroops === void 0 ? {} : { publicDepositStroops: parameters.publicDepositStroops },
1847
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1539
1848
  });
1540
- return { ...proof, applicationIdsPlaintext };
1541
1849
  }
1542
1850
  async calculateNullifierHash(nullifier, privKeyScalar) {
1543
1851
  const sdk = await this.getInitializedSdk();
1544
1852
  return sdk.calculateNullifierHash(nullifier, privKeyScalar);
1545
1853
  }
1546
1854
  async prepareWithdrawTransactProof(parameters) {
1547
- const sdk = await this.getInitializedSdk();
1855
+ const sdk = await this.getInitializedSdk(
1856
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1857
+ );
1548
1858
  return proveWithdrawTransact({
1549
1859
  sdk,
1550
1860
  applicationId: this.getApplicationId(),
1551
1861
  buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1552
1862
  ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1553
- ...parameters
1863
+ coin: parameters.coin,
1864
+ state: parameters.state,
1865
+ destinationStellarAddress: parameters.destinationStellarAddress,
1866
+ privKeyScalarHex: parameters.privKeyScalarHex,
1867
+ depositorEphemeralKey: parameters.depositorEphemeralKey,
1868
+ withdrawAmountStroops: parameters.withdrawAmountStroops,
1869
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1870
+ tokenAddress: parameters.tokenAddress,
1871
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1554
1872
  });
1555
1873
  }
1556
1874
  async prepareWithdrawTransactProofDual(parameters) {
1557
- const sdk = await this.getInitializedSdk();
1875
+ const sdk = await this.getInitializedSdk(
1876
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1877
+ );
1558
1878
  return proveWithdrawTransactDual({
1559
1879
  sdk,
1560
1880
  applicationId: this.getApplicationId(),
@@ -1569,7 +1889,8 @@ var PrivacyPoolService = class {
1569
1889
  ephemeralBKey: parameters.depositorEphemeralBKey,
1570
1890
  withdrawAmountStroops: parameters.withdrawAmountStroops,
1571
1891
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1572
- tokenAddress: parameters.tokenAddress
1892
+ tokenAddress: parameters.tokenAddress,
1893
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1573
1894
  });
1574
1895
  }
1575
1896
  };
@@ -2157,6 +2478,13 @@ async function filterUnspentPrivateRecords(input) {
2157
2478
  }
2158
2479
  return unspent;
2159
2480
  }
2481
+ function spendAmountCoveringProtocolFee(amount) {
2482
+ if (amount <= 0n) {
2483
+ return amount;
2484
+ }
2485
+ const hundredths = (amount + 99n) / 100n;
2486
+ return amount + (hundredths === 0n ? 1n : hundredths);
2487
+ }
2160
2488
  function pickRecordsForSpendAmount(input) {
2161
2489
  let selectedTotal = 0n;
2162
2490
  const selected = [];
@@ -2210,7 +2538,7 @@ async function selectPrivateRecords(input) {
2210
2538
  return pickRecordsForSpendAmount({
2211
2539
  available,
2212
2540
  kind: input.kind,
2213
- amount: input.intent.amount
2541
+ amount: spendAmountCoveringProtocolFee(input.intent.amount)
2214
2542
  });
2215
2543
  }
2216
2544
  function sumRecordAmounts(records) {
@@ -5465,7 +5793,7 @@ function createStellarPrivacyClientFromResolvedConfig(config) {
5465
5793
  }
5466
5794
 
5467
5795
  // src/transact/engine/default-factory.ts
5468
- import { PrivacyPoolSDK as PrivacyPoolSDK3 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
5796
+ import { PrivacyPoolSDK as PrivacyPoolSDK4 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
5469
5797
 
5470
5798
  // src/transact/pool/proof-types.ts
5471
5799
  function ciphertextArtifactsFromProof(proof) {
@@ -5480,9 +5808,15 @@ function ciphertextArtifactsFromProof(proof) {
5480
5808
  // src/transact/engine/prepare/deposit.ts
5481
5809
  var STROOPS_PER_UNIT = 10000000n;
5482
5810
  async function createAlignedDepositWithProof(parameters) {
5811
+ const feeOutput = await quoteFeeOutputForPrepared(parameters);
5812
+ const instructed = parameters.prepared.intent.amount;
5813
+ const userAmount = instructed - (feeOutput?.requiredFee ?? 0n);
5814
+ if (userAmount <= 0n) {
5815
+ throw new Error("Required Fee leaves no spendable deposit note");
5816
+ }
5483
5817
  const aligned = await parameters.poolService.createAlignedShieldCoinData({
5484
5818
  privateAddressStpl1: parameters.prepared.intent.to,
5485
- amountStroops: parameters.prepared.intent.amount,
5819
+ amountStroops: userAmount,
5486
5820
  tokenAddress: parameters.tokenAddress
5487
5821
  });
5488
5822
  const poolClient = readPoolClientFactory(parameters.environment)({
@@ -5497,7 +5831,9 @@ async function createAlignedDepositWithProof(parameters) {
5497
5831
  coin: aligned.coin,
5498
5832
  depositScalarHex: aligned.depositScalarHex,
5499
5833
  merkleRootBytes: merkleTx.result,
5500
- tokenAddress: parameters.tokenAddress
5834
+ tokenAddress: parameters.tokenAddress,
5835
+ publicDepositStroops: instructed,
5836
+ ...feeOutput ? { feeOutput } : {}
5501
5837
  });
5502
5838
  return { ...aligned, proof };
5503
5839
  }
@@ -5516,8 +5852,8 @@ async function prepareDepositOperation(prepared, environment, poolService) {
5516
5852
  privateAddress: prepared.intent.to,
5517
5853
  assetId: prepared.intent.asset,
5518
5854
  poolContract: environment.network.poolContract,
5519
- amount: prepared.intent.amount,
5520
- amountDisplay: Number(prepared.intent.amount) / Number(STROOPS_PER_UNIT),
5855
+ amount: BigInt(depositProof.coin.value),
5856
+ amountDisplay: Number(depositProof.coin.value) / Number(STROOPS_PER_UNIT),
5521
5857
  commitmentHex: depositProof.commitment_hex,
5522
5858
  coin: depositProof.coin,
5523
5859
  depositScalarHex: depositProof.depositScalarHex,
@@ -5533,7 +5869,11 @@ async function prepareDepositOperation(prepared, environment, poolService) {
5533
5869
  depositScalarHex: depositProof.depositScalarHex,
5534
5870
  precommitementHex: depositProof.precommitementHex,
5535
5871
  commitmentHex: depositProof.commitment_hex,
5536
- walletPublicKey
5872
+ walletPublicKey,
5873
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
5874
+ kind: "deposit",
5875
+ ...configuredNonceSpread(environment.zkConfigNonce)
5876
+ })
5537
5877
  });
5538
5878
  }
5539
5879
 
@@ -5707,9 +6047,6 @@ function escrowOutputFields(input) {
5707
6047
  recipientLo: input.escrowSend.recipientLo
5708
6048
  };
5709
6049
  }
5710
- function paddingDepositPair(recipientDeposit) {
5711
- return [recipientDeposit, "dummy"];
5712
- }
5713
6050
  async function buildChangeDepositPair(parameters) {
5714
6051
  const changeSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
5715
6052
  privateAddressStpl1: parameters.selfPrivateAddressStpl1ForChange.trim(),
@@ -5726,34 +6063,54 @@ async function buildChangeDepositPair(parameters) {
5726
6063
  }
5727
6064
  };
5728
6065
  }
6066
+ function recipientOnlyDeposits(recipientDeposit, hasFeeOutput) {
6067
+ if (hasFeeOutput) {
6068
+ return [recipientDeposit];
6069
+ }
6070
+ return [recipientDeposit, "dummy"];
6071
+ }
6072
+ async function depositsWithoutFee(input) {
6073
+ if (input.changeStroops <= ZERO_STROOPS) {
6074
+ return {
6075
+ deposits: recipientOnlyDeposits(input.recipientDeposit, input.hasFeeOutput)
6076
+ };
6077
+ }
6078
+ return buildChangeDepositPair({
6079
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
6080
+ changeStroops: input.changeStroops,
6081
+ tokenAddress: input.tokenAddress,
6082
+ recipientDeposit: input.recipientDeposit
6083
+ });
6084
+ }
5729
6085
  async function buildRecipientAndOptionalChangeDeposits(parameters) {
5730
6086
  const escrow = {
5731
6087
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5732
6088
  ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
5733
6089
  };
5734
- const recipientPrivateAddress = parameters.recipientPrivateAddressStpl1.trim();
5735
6090
  const recipientSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
5736
- privateAddressStpl1: recipientPrivateAddress,
6091
+ privateAddressStpl1: parameters.recipientPrivateAddressStpl1.trim(),
5737
6092
  amountStroops: parameters.transferStroops,
5738
6093
  tokenAddress: parameters.tokenAddress,
5739
6094
  ...escrowOutputFields(escrow)
5740
6095
  });
5741
- if (parameters.changeStroops <= ZERO_STROOPS) {
5742
- return {
5743
- recipientSlot,
5744
- deposits: paddingDepositPair(recipientSlot.deposit)
5745
- };
5746
- }
5747
- const changeResult = await buildChangeDepositPair({
5748
- selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
6096
+ const withoutFee = await depositsWithoutFee({
6097
+ recipientDeposit: recipientSlot.deposit,
5749
6098
  changeStroops: parameters.changeStroops,
6099
+ selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
6100
+ tokenAddress: parameters.tokenAddress,
6101
+ hasFeeOutput: (parameters.feeOutput?.requiredFee ?? ZERO_STROOPS) > ZERO_STROOPS
6102
+ });
6103
+ const withFee = await appendFeeOutputSlot({
6104
+ deposits: withoutFee.deposits,
5750
6105
  tokenAddress: parameters.tokenAddress,
5751
- recipientDeposit: recipientSlot.deposit
6106
+ buildAlignedDepositSlot: (aligned) => getPrivacyPoolService().buildAlignedDepositSlot(aligned),
6107
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5752
6108
  });
5753
6109
  return {
5754
6110
  recipientSlot,
5755
- deposits: changeResult.deposits,
5756
- changeCoin: changeResult.changeCoin
6111
+ deposits: withFee.deposits,
6112
+ ..."changeCoin" in withoutFee ? { changeCoin: withoutFee.changeCoin } : {},
6113
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
5757
6114
  };
5758
6115
  }
5759
6116
 
@@ -5806,14 +6163,15 @@ function sweepOutputOwnerFields(parameters) {
5806
6163
  return { sweepOutputOwnerPubX, sweepOutputOwnerPubY };
5807
6164
  }
5808
6165
  async function buildTransferDepositsAndPublicInput(parameters) {
5809
- const { recipientSlot, deposits, changeCoin } = await buildRecipientAndOptionalChangeDeposits({
6166
+ const { recipientSlot, deposits, changeCoin, feeCoin } = await buildRecipientAndOptionalChangeDeposits({
5810
6167
  recipientPrivateAddressStpl1: parameters.recipientPrivateAddressStpl1,
5811
6168
  transferStroops: parameters.transferStroops,
5812
6169
  changeStroops: parameters.changeStroops,
5813
6170
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
5814
6171
  tokenAddress: parameters.tokenAddress,
5815
6172
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5816
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
6173
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
6174
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5817
6175
  });
5818
6176
  const publicInput = withTokenAddressPublicInputs(
5819
6177
  {
@@ -5828,6 +6186,7 @@ async function buildTransferDepositsAndPublicInput(parameters) {
5828
6186
  recipientSlot,
5829
6187
  deposits,
5830
6188
  ...changeCoin ? { changeCoin } : {},
6189
+ ...feeCoin ? { feeCoin } : {},
5831
6190
  publicInput
5832
6191
  };
5833
6192
  }
@@ -5843,7 +6202,8 @@ async function buildSenderTransferDepositsAndPublicInput(parameters) {
5843
6202
  parameters.senderPrivKeyScalarHex
5844
6203
  ),
5845
6204
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
5846
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
6205
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
6206
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5847
6207
  });
5848
6208
  }
5849
6209
 
@@ -5867,11 +6227,20 @@ function stampDualWithdrawLegs(legs, parameters) {
5867
6227
  }
5868
6228
  };
5869
6229
  }
5870
- function buildDualTransferApplicationIds(sdk, applicationId, changeCoin) {
6230
+ function buildDualTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
6231
+ const outputIds = [applicationId];
6232
+ if (changeCoin) {
6233
+ outputIds.push(applicationId);
6234
+ } else if (!feeCoin) {
6235
+ outputIds.push("0");
6236
+ }
6237
+ if (feeCoin) {
6238
+ outputIds.push(applicationId);
6239
+ }
5871
6240
  return buildApplicationIdHints({
5872
6241
  sdk,
5873
6242
  inputIds: [applicationId, applicationId],
5874
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
6243
+ outputIds
5875
6244
  });
5876
6245
  }
5877
6246
  async function proveDualConfidentialTransfer(parameters) {
@@ -5891,16 +6260,24 @@ async function proveDualConfidentialTransfer(parameters) {
5891
6260
  audit
5892
6261
  );
5893
6262
  }
5894
- async function buildDualTransferProofContext(parameters, applicationId, sdk) {
6263
+ function dualTransferChangeStroops(parameters) {
5895
6264
  const totalNotes = BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value);
5896
- const changeStroops = changeStroopsAfterDualTransfer(
5897
- parameters.transferStroops,
5898
- totalNotes
5899
- );
6265
+ if (parameters.transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
6266
+ throw new Error("Transfer amount must be positive");
6267
+ }
6268
+ const changeStroops = remainingAfterRequiredFee({
6269
+ available: totalNotes,
6270
+ instructed: parameters.transferStroops,
6271
+ requiredFee: parameters.feeOutput?.requiredFee ?? 0n
6272
+ });
5900
6273
  requireChangeRecipientWhenPartial(
5901
6274
  changeStroops,
5902
6275
  parameters.selfPrivateAddressStpl1ForChange
5903
6276
  );
6277
+ return changeStroops;
6278
+ }
6279
+ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
6280
+ const changeStroops = dualTransferChangeStroops(parameters);
5904
6281
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
5905
6282
  parameters.senderPrivKeyScalarHex
5906
6283
  );
@@ -5927,15 +6304,21 @@ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
5927
6304
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
5928
6305
  tokenAddress: parameters.tokenAddress,
5929
6306
  stateRoot: legA.witness.stateRoot,
5930
- ...confidentialEscrowFields(parameters)
6307
+ ...confidentialEscrowFields(parameters),
6308
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
5931
6309
  });
5932
6310
  return { legA, legB, applicationId, ...transferInputs };
5933
6311
  }
5934
6312
  async function prepareConfidentialTransferProofDual(parameters) {
5935
6313
  const poolService = getPrivacyPoolService();
5936
- const sdk = await poolService.getInitializedSdk();
6314
+ const sdk = await poolService.getInitializedSdk(
6315
+ poolService.feeBearingZkConfigNonce({
6316
+ kind: "transfer",
6317
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
6318
+ })
6319
+ );
5937
6320
  const applicationId = poolService.getApplicationId();
5938
- const { legA, legB, recipientSlot, deposits, changeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
6321
+ const { legA, legB, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
5939
6322
  const auditPublicKey = poolService.getAuditPublicKey();
5940
6323
  const proof = await (auditPublicKey ? proveDualConfidentialTransfer({
5941
6324
  sdk,
@@ -5958,7 +6341,8 @@ async function prepareConfidentialTransferProofDual(parameters) {
5958
6341
  applicationIdsPlaintext: buildDualTransferApplicationIds(
5959
6342
  sdk,
5960
6343
  applicationId,
5961
- changeCoin
6344
+ changeCoin,
6345
+ feeCoin
5962
6346
  ),
5963
6347
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
5964
6348
  ...changeCoin ? { changeCoin } : {}
@@ -5966,22 +6350,32 @@ async function prepareConfidentialTransferProofDual(parameters) {
5966
6350
  }
5967
6351
 
5968
6352
  // src/transact/proofs/confidential/single.ts
5969
- function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange) {
6353
+ function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange, requiredFee) {
5970
6354
  if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
5971
6355
  throw new Error("Transfer amount must be positive");
5972
6356
  }
5973
- if (transferStroops > noteStroops) {
5974
- throw new Error("Transfer amount exceeds note value");
5975
- }
5976
- const changeStroops = noteStroops - transferStroops;
6357
+ const changeStroops = remainingAfterRequiredFee({
6358
+ available: noteStroops,
6359
+ instructed: transferStroops,
6360
+ requiredFee
6361
+ });
5977
6362
  requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl1ForChange);
5978
6363
  return changeStroops;
5979
6364
  }
5980
- function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin) {
6365
+ function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
6366
+ const outputIds = [applicationId];
6367
+ if (changeCoin) {
6368
+ outputIds.push(applicationId);
6369
+ } else if (!feeCoin) {
6370
+ outputIds.push("0");
6371
+ }
6372
+ if (feeCoin) {
6373
+ outputIds.push(applicationId);
6374
+ }
5981
6375
  return buildApplicationIdHints({
5982
6376
  sdk,
5983
6377
  inputIds: [applicationId],
5984
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
6378
+ outputIds
5985
6379
  });
5986
6380
  }
5987
6381
  function buildTransferAuditParameters(parameters) {
@@ -6007,11 +6401,25 @@ function confidentialEscrowFields2(input) {
6007
6401
  ...input.escrowClaimantLimbs ? { escrowClaimantLimbs: input.escrowClaimantLimbs } : {}
6008
6402
  };
6009
6403
  }
6404
+ function transferDepositInputFromProof(input, changeStroops, stateRoot) {
6405
+ return {
6406
+ senderPrivKeyScalarHex: input.senderPrivKeyScalarHex,
6407
+ recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1,
6408
+ transferStroops: input.transferStroops,
6409
+ changeStroops,
6410
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
6411
+ tokenAddress: input.tokenAddress,
6412
+ stateRoot,
6413
+ ...confidentialEscrowFields2(input),
6414
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
6415
+ };
6416
+ }
6010
6417
  async function buildTransferProofInputs(parameters) {
6011
6418
  const changeStroops = validateSingleCoinTransferAmounts(
6012
6419
  parameters.input.transferStroops,
6013
6420
  parameters.noteStroops,
6014
- parameters.input.selfPrivateAddressStpl1ForChange
6421
+ parameters.input.selfPrivateAddressStpl1ForChange,
6422
+ parameters.input.feeOutput?.requiredFee ?? 0n
6015
6423
  );
6016
6424
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
6017
6425
  parameters.input.senderPrivKeyScalarHex
@@ -6030,31 +6438,30 @@ async function buildTransferProofInputs(parameters) {
6030
6438
  withdrawObject,
6031
6439
  sweepWithdrawStamp(confidentialEscrowFields2(parameters.input))
6032
6440
  );
6033
- const { recipientSlot, deposits, changeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput({
6034
- senderPrivKeyScalarHex: parameters.input.senderPrivKeyScalarHex,
6035
- recipientPrivateAddressStpl1: parameters.input.recipientPrivateAddressStpl1,
6036
- transferStroops: parameters.input.transferStroops,
6037
- changeStroops,
6038
- selfPrivateAddressStpl1ForChange: parameters.input.selfPrivateAddressStpl1ForChange,
6039
- tokenAddress: parameters.input.tokenAddress,
6040
- stateRoot: witness.stateRoot,
6041
- ...confidentialEscrowFields2(parameters.input)
6042
- });
6441
+ const { recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput(
6442
+ transferDepositInputFromProof(parameters.input, changeStroops, witness.stateRoot)
6443
+ );
6043
6444
  return {
6044
6445
  witness,
6045
6446
  withdrawObject: stampedWithdraw,
6046
6447
  recipientSlot,
6047
6448
  deposits,
6048
6449
  changeCoin,
6450
+ feeCoin,
6049
6451
  publicInput
6050
6452
  };
6051
6453
  }
6052
6454
  async function prepareConfidentialTransferProof(parameters) {
6053
6455
  const poolService = getPrivacyPoolService();
6054
- const sdk = await poolService.getInitializedSdk();
6456
+ const sdk = await poolService.getInitializedSdk(
6457
+ poolService.feeBearingZkConfigNonce({
6458
+ kind: "transfer",
6459
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
6460
+ })
6461
+ );
6055
6462
  const applicationId = poolService.getApplicationId();
6056
6463
  const noteStroops = BigInt(parameters.coin.value);
6057
- const { withdrawObject, recipientSlot, deposits, changeCoin, publicInput } = await buildTransferProofInputs({
6464
+ const { withdrawObject, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildTransferProofInputs({
6058
6465
  sdk,
6059
6466
  applicationId,
6060
6467
  input: parameters,
@@ -6077,7 +6484,8 @@ async function prepareConfidentialTransferProof(parameters) {
6077
6484
  applicationIdsPlaintext: buildSingleTransferApplicationIds(
6078
6485
  sdk,
6079
6486
  applicationId,
6080
- changeCoin
6487
+ changeCoin,
6488
+ feeCoin
6081
6489
  ),
6082
6490
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
6083
6491
  ...changeCoin ? { changeCoin } : {}
@@ -6100,7 +6508,12 @@ function proofEscrowFields(input) {
6100
6508
  }
6101
6509
  async function buildSingleTransferProofAtExecute(input) {
6102
6510
  const context = await buildSpendProofContextAtExecute(input);
6103
- const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount;
6511
+ const feeOutput = await quoteFeeOutputForPrepared({
6512
+ prepared: input.prepared,
6513
+ environment: input.environment,
6514
+ tokenAddress: context.tokenAddress
6515
+ });
6516
+ const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
6104
6517
  const proof = await prepareConfidentialTransferProof({
6105
6518
  coin: context.coin,
6106
6519
  state: { commitments: context.commitments },
@@ -6113,7 +6526,8 @@ async function buildSingleTransferProofAtExecute(input) {
6113
6526
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
6114
6527
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
6115
6528
  tokenAddress: context.tokenAddress,
6116
- ...proofEscrowFields(input)
6529
+ ...proofEscrowFields(input),
6530
+ ...feeOutput ? { feeOutput } : {}
6117
6531
  });
6118
6532
  return { context, proof };
6119
6533
  }
@@ -6130,7 +6544,12 @@ async function buildDualTransferProofAtExecute(input) {
6130
6544
  commitments: context.commitments
6131
6545
  });
6132
6546
  const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
6133
- const changeStroops = totalNotes - input.prepared.intent.amount;
6547
+ const feeOutput = await quoteFeeOutputForPrepared({
6548
+ prepared: input.prepared,
6549
+ environment: input.environment,
6550
+ tokenAddress: context.tokenAddress
6551
+ });
6552
+ const changeStroops = totalNotes - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
6134
6553
  const proof = await prepareConfidentialTransferProofDual({
6135
6554
  coinA: context.coin,
6136
6555
  coinB: secondary.coin,
@@ -6148,7 +6567,8 @@ async function buildDualTransferProofAtExecute(input) {
6148
6567
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
6149
6568
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
6150
6569
  tokenAddress: context.tokenAddress,
6151
- ...proofEscrowFields(input)
6570
+ ...proofEscrowFields(input),
6571
+ ...feeOutput ? { feeOutput } : {}
6152
6572
  });
6153
6573
  return { context, proof };
6154
6574
  }
@@ -6280,11 +6700,17 @@ async function runTransferFinalizeSteps(input) {
6280
6700
  walletPublicKey: input.walletPublicKey,
6281
6701
  ...recipient.escrowSend ? { escrowSendPrivateAddress: recipient.recipientPrivateAddressStpl1 } : {}
6282
6702
  });
6283
- return buildTransferFinalizeArtifacts({
6284
- proof,
6285
- context,
6286
- ...sweepFinalizeStamp(input.prepared, recipient)
6287
- });
6703
+ return {
6704
+ ...buildTransferFinalizeArtifacts({
6705
+ proof,
6706
+ context,
6707
+ ...sweepFinalizeStamp(input.prepared, recipient)
6708
+ }),
6709
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6710
+ ...feeOutputKindFromPrepared(input.prepared),
6711
+ ...configuredNonceSpread(input.environment.zkConfigNonce)
6712
+ })
6713
+ };
6288
6714
  }
6289
6715
  function sweepArtifactStamp(artifacts) {
6290
6716
  return {
@@ -6312,6 +6738,24 @@ async function finalizeTransferAtExecute(prepared, environment) {
6312
6738
  return runTransferFinalizeSteps({ prepared, environment, walletPublicKey });
6313
6739
  }
6314
6740
 
6741
+ // src/transact/engine/withdraw-fee.ts
6742
+ async function withdrawFeeProofFields(input) {
6743
+ const feeOutput = await quoteFeeOutputForPrepared({
6744
+ prepared: input.prepared,
6745
+ environment: input.environment,
6746
+ tokenAddress: input.tokenAddress
6747
+ });
6748
+ const changeStroops = remainingAfterRequiredFee({
6749
+ available: input.available,
6750
+ instructed: input.prepared.intent.amount,
6751
+ requiredFee: feeOutput?.requiredFee ?? 0n
6752
+ });
6753
+ return {
6754
+ ...feeOutput ? { feeOutput } : {},
6755
+ ...changeStroops > 0n ? { changePrivateAddressStpl1: input.withdrawFrom } : {}
6756
+ };
6757
+ }
6758
+
6315
6759
  // src/transact/engine/execute.ts
6316
6760
  function enrichWithdrawOutputRecords(prepared, proof) {
6317
6761
  if (!proof.changeCoin || prepared.outputRecords.length === 0) {
@@ -6327,7 +6771,7 @@ function enrichWithdrawOutputRecords(prepared, proof) {
6327
6771
  precommitementHex: changeCoin.precommitementHex
6328
6772
  }));
6329
6773
  }
6330
- function buildWithdrawFinalizeArtifacts(proof, context) {
6774
+ function buildWithdrawFinalizeArtifacts(proof, context, environment) {
6331
6775
  return {
6332
6776
  proofHex: proof.proof_hex,
6333
6777
  publicHex: proof.public_hex,
@@ -6335,7 +6779,11 @@ function buildWithdrawFinalizeArtifacts(proof, context) {
6335
6779
  applicationIdsPlaintext: proof.applicationIdsPlaintext,
6336
6780
  tokenAddress: context.tokenAddress,
6337
6781
  walletPublicKey: context.walletPublicKey,
6338
- executeFinalizeRequired: false
6782
+ executeFinalizeRequired: false,
6783
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
6784
+ kind: "withdraw",
6785
+ ...configuredNonceSpread(environment.zkConfigNonce)
6786
+ })
6339
6787
  };
6340
6788
  }
6341
6789
  async function finalizeSingleWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
@@ -6344,7 +6792,13 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6344
6792
  environment,
6345
6793
  recipientPrivateAddressStpl1: withdrawFrom
6346
6794
  });
6347
- const changeStroops = BigInt(context.coin.value) - prepared.intent.amount;
6795
+ const feeFields = await withdrawFeeProofFields({
6796
+ prepared,
6797
+ environment,
6798
+ tokenAddress: context.tokenAddress,
6799
+ available: BigInt(context.coin.value),
6800
+ withdrawFrom
6801
+ });
6348
6802
  const proof = await poolService.prepareWithdrawTransactProof({
6349
6803
  coin: context.coin,
6350
6804
  state: { commitments: context.commitments },
@@ -6355,11 +6809,24 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6355
6809
  yHex: context.ephemeral.yHex
6356
6810
  }),
6357
6811
  withdrawAmountStroops: prepared.intent.amount,
6358
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6359
- tokenAddress: context.tokenAddress
6812
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
6813
+ tokenAddress: context.tokenAddress,
6814
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
6360
6815
  });
6361
6816
  enrichWithdrawOutputRecords(prepared, proof);
6362
- return buildWithdrawFinalizeArtifacts(proof, context);
6817
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
6818
+ }
6819
+ function dualWithdrawEphemeralKeys(input) {
6820
+ return {
6821
+ depositorEphemeralAKey: serializeEphemeralKeyString({
6822
+ xHex: input.context.ephemeral.xHex,
6823
+ yHex: input.context.ephemeral.yHex
6824
+ }),
6825
+ depositorEphemeralBKey: serializeEphemeralKeyString({
6826
+ xHex: input.secondary.ephemeral.xHex,
6827
+ yHex: input.secondary.ephemeral.yHex
6828
+ })
6829
+ };
6363
6830
  }
6364
6831
  async function finalizeDualWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
6365
6832
  const [, secondaryRecord] = prepared.consumedRecords;
@@ -6377,28 +6844,27 @@ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService,
6377
6844
  walletPublicKey: context.walletPublicKey,
6378
6845
  commitments: context.commitments
6379
6846
  });
6380
- const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
6381
- const changeStroops = totalNotes - prepared.intent.amount;
6847
+ const feeFields = await withdrawFeeProofFields({
6848
+ prepared,
6849
+ environment,
6850
+ tokenAddress: context.tokenAddress,
6851
+ available: BigInt(context.coin.value) + BigInt(secondary.coin.value),
6852
+ withdrawFrom
6853
+ });
6382
6854
  const proof = await poolService.prepareWithdrawTransactProofDual({
6383
6855
  coinA: context.coin,
6384
6856
  coinB: secondary.coin,
6385
6857
  state: { commitments: context.commitments },
6386
6858
  destinationStellarAddress: prepared.intent.to,
6387
6859
  privKeyScalarHex: context.senderPrivKeyScalarHex,
6388
- depositorEphemeralAKey: serializeEphemeralKeyString({
6389
- xHex: context.ephemeral.xHex,
6390
- yHex: context.ephemeral.yHex
6391
- }),
6392
- depositorEphemeralBKey: serializeEphemeralKeyString({
6393
- xHex: secondary.ephemeral.xHex,
6394
- yHex: secondary.ephemeral.yHex
6395
- }),
6860
+ ...dualWithdrawEphemeralKeys({ context, secondary }),
6396
6861
  withdrawAmountStroops: prepared.intent.amount,
6397
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6398
- tokenAddress: context.tokenAddress
6862
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
6863
+ tokenAddress: context.tokenAddress,
6864
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
6399
6865
  });
6400
6866
  enrichWithdrawOutputRecords(prepared, proof);
6401
- return buildWithdrawFinalizeArtifacts(proof, context);
6867
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
6402
6868
  }
6403
6869
  async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6404
6870
  if (prepared.kind !== "withdraw") {
@@ -6660,7 +7126,7 @@ async function requestKytPassageForPoolInteraction(input) {
6660
7126
  const applicationId = resolvePoolApplicationId(
6661
7127
  input.transactEnvironment.network.applicationId
6662
7128
  );
6663
- const zkConfigNonce = resolveZkConfigNonce(input.transactEnvironment);
7129
+ const zkConfigNonce = input.zkConfigNonce ?? resolveZkConfigNonce(input.transactEnvironment);
6664
7130
  const applicationIdsPlaintext = input.applicationIdsPlaintext ?? buildApplicationIdHints2(
6665
7131
  applicationId,
6666
7132
  layoutForKnownNonce(zkConfigNonce).nAuditSlots
@@ -6832,6 +7298,10 @@ async function submitSorobanConfidentialTransfer(input) {
6832
7298
  networkPassphrase: input.networkPassphrase,
6833
7299
  sorobanRpcUrl: input.sorobanRpcUrl
6834
7300
  });
7301
+ const nonce = transactNonceFromArtifacts(
7302
+ input.zkConfigNonce === void 0 ? void 0 : { zkConfigNonce: input.zkConfigNonce },
7303
+ input.transactEnvironment
7304
+ );
6835
7305
  const approval = await requestKytPassageForPoolInteraction({
6836
7306
  owner: input.walletPublicKey,
6837
7307
  poolContract: input.contractId,
@@ -6840,6 +7310,7 @@ async function submitSorobanConfidentialTransfer(input) {
6840
7310
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
6841
7311
  ...input.outputNoteEphemeralScalars ? { outputNoteEphemeralScalars: input.outputNoteEphemeralScalars } : {},
6842
7312
  ...input.applicationIdsPlaintext ? { applicationIdsPlaintext: input.applicationIdsPlaintext } : {},
7313
+ ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
6843
7314
  networkPassphrase: input.networkPassphrase,
6844
7315
  sorobanRpcUrl: input.sorobanRpcUrl,
6845
7316
  transactEnvironment: input.transactEnvironment
@@ -6848,7 +7319,7 @@ async function submitSorobanConfidentialTransfer(input) {
6848
7319
  contractClient: poolClient,
6849
7320
  contractId: input.contractId,
6850
7321
  from: input.walletPublicKey,
6851
- nonce: resolveZkConfigNonce(input.transactEnvironment),
7322
+ nonce,
6852
7323
  proofHex: input.proofHex,
6853
7324
  publicHex: input.publicHex,
6854
7325
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
@@ -6879,12 +7350,14 @@ async function submitPreparedDeposit(prepared, environment) {
6879
7350
  networkPassphrase: environment.network.networkPassphrase,
6880
7351
  sorobanRpcUrl: environment.network.rpcUrl
6881
7352
  });
7353
+ const nonce = transactNonceFromArtifacts(artifacts, environment);
6882
7354
  const approval = await requestKytPassageForPoolInteraction({
6883
7355
  owner: artifacts.walletPublicKey,
6884
7356
  poolContract: environment.network.poolContract,
6885
7357
  proofHex: artifacts.proofHex,
6886
7358
  publicHex: artifacts.publicHex,
6887
7359
  ...optionalInspectCiphertext(artifacts),
7360
+ ...artifacts.zkConfigNonce === void 0 ? {} : { zkConfigNonce: artifacts.zkConfigNonce },
6888
7361
  networkPassphrase: environment.network.networkPassphrase,
6889
7362
  sorobanRpcUrl: environment.network.rpcUrl,
6890
7363
  transactEnvironment: environment
@@ -6893,7 +7366,7 @@ async function submitPreparedDeposit(prepared, environment) {
6893
7366
  contractClient: poolClient,
6894
7367
  contractId: environment.network.poolContract,
6895
7368
  from: artifacts.walletPublicKey,
6896
- nonce: resolveZkConfigNonce(environment),
7369
+ nonce,
6897
7370
  proofHex: artifacts.proofHex,
6898
7371
  publicHex: artifacts.publicHex,
6899
7372
  ...artifacts.ciphertextHex ? { ciphertextHex: artifacts.ciphertextHex } : {},
@@ -6914,6 +7387,7 @@ async function submitPreparedPoolTransact(prepared, environment) {
6914
7387
  proofHex: artifacts.proofHex,
6915
7388
  publicHex: artifacts.publicHex,
6916
7389
  ...optionalInspectCiphertext(artifacts),
7390
+ ...artifacts.zkConfigNonce === void 0 ? {} : { zkConfigNonce: artifacts.zkConfigNonce },
6917
7391
  networkPassphrase: environment.network.networkPassphrase,
6918
7392
  sorobanRpcUrl: environment.network.rpcUrl,
6919
7393
  transactEnvironment: environment
@@ -6969,7 +7443,7 @@ async function privacyPoolInitOptions(assets, transactEnvironment) {
6969
7443
  };
6970
7444
  }
6971
7445
  async function createDefaultTransactEngine(assets, transactEnvironment) {
6972
- await PrivacyPoolSDK3.init(await privacyPoolInitOptions(assets, transactEnvironment));
7446
+ await PrivacyPoolSDK4.init(await privacyPoolInitOptions(assets, transactEnvironment));
6973
7447
  if (transactEnvironment) {
6974
7448
  configurePrivacyPoolService(
6975
7449
  createPrivacyPoolService({
@@ -7465,6 +7939,7 @@ async function inspectRelayerSweepKyt(input) {
7465
7939
  ...input.prepared.transactArtifacts?.applicationIdsPlaintext ? {
7466
7940
  applicationIdsPlaintext: input.prepared.transactArtifacts.applicationIdsPlaintext
7467
7941
  } : {},
7942
+ ...input.prepared.transactArtifacts?.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.prepared.transactArtifacts.zkConfigNonce },
7468
7943
  networkPassphrase: environment.network.networkPassphrase,
7469
7944
  sorobanRpcUrl: environment.network.rpcUrl,
7470
7945
  transactEnvironment: environment
@@ -7477,7 +7952,10 @@ async function simulateSweepAuthEntries(input) {
7477
7952
  const operation = new Contract(environment.network.poolContract).call(
7478
7953
  "transact",
7479
7954
  new Address5(input.relayerPublicKey).toScVal(),
7480
- nativeToScVal2(resolveZkConfigNonce(environment), { type: "u64" }),
7955
+ nativeToScVal2(
7956
+ transactNonceFromArtifacts(input.prepared.transactArtifacts, environment),
7957
+ { type: "u64" }
7958
+ ),
7481
7959
  nativeToScVal2(hexToBytes(input.artifacts.proofHex), { type: "bytes" }),
7482
7960
  nativeToScVal2(hexToBytes(input.artifacts.publicHex), { type: "bytes" }),
7483
7961
  nativeToScVal2(hexToBytes(input.artifacts.ciphertextHex ?? ""), { type: "bytes" }),
@@ -7514,6 +7992,7 @@ async function unsignedEscrowAuthorizationForSweep(input) {
7514
7992
  });
7515
7993
  const auth = await simulateSweepAuthEntries({
7516
7994
  artifacts,
7995
+ prepared: input.prepared,
7517
7996
  transactEnvironment: input.transactEnvironment,
7518
7997
  relayerPublicKey,
7519
7998
  approval
@@ -7545,15 +8024,15 @@ async function loadDefaultStellarBrowserAssets(options) {
7545
8024
  }
7546
8025
 
7547
8026
  // src/transact/private-address/temporary-recipient.ts
7548
- import { PrivacyPoolSDK as PrivacyPoolSDK4 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
8027
+ import { PrivacyPoolSDK as PrivacyPoolSDK5 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
7549
8028
  var COORDINATE_HEX_LENGTH3 = 64;
7550
8029
  async function generateRandomDepositScalarHex() {
7551
8030
  await getPrivacyPoolService().getInitializedSdk();
7552
- return PrivacyPoolSDK4.generateRandomScalarHex32();
8031
+ return PrivacyPoolSDK5.generateRandomScalarHex32();
7553
8032
  }
7554
8033
  async function generateTemporaryRecipientPrivateAddress() {
7555
8034
  await getPrivacyPoolService().getInitializedSdk();
7556
- const temporaryScalarHex = PrivacyPoolSDK4.generateRandomScalarHex32();
8035
+ const temporaryScalarHex = PrivacyPoolSDK5.generateRandomScalarHex32();
7557
8036
  const sdk = await getPrivacyPoolService().getInitializedSdk();
7558
8037
  const point = sdk.ecdhEphemeralPublicKeyFromScalarHex(temporaryScalarHex);
7559
8038
  const publicKeyXHex = point.x.padStart(COORDINATE_HEX_LENGTH3, "0");
@@ -7809,10 +8288,11 @@ function requirePreparedArtifacts(prepared) {
7809
8288
  };
7810
8289
  }
7811
8290
  function prepareRelayTransactPackageFromPrepared(input) {
8291
+ const zkConfigNonce = input.zkConfigNonce ?? input.prepared.transactArtifacts?.zkConfigNonce;
7812
8292
  return prepareRelayTransactPackage({
7813
8293
  poolSelector: input.poolSelector,
7814
8294
  ...requirePreparedArtifacts(input.prepared),
7815
- ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
8295
+ ...zkConfigNonce === void 0 ? {} : { zkConfigNonce },
7816
8296
  ...input.keyVersionHints ? { keyVersionHints: input.keyVersionHints } : {}
7817
8297
  });
7818
8298
  }
@@ -7922,6 +8402,7 @@ function requiredSubmissionMethod(prepared, zkConfigNonce = DEFAULT_ZK_CONFIG_NO
7922
8402
  return result.method;
7923
8403
  }
7924
8404
  export {
8405
+ BINDING_ZK_NONCE,
7925
8406
  BundledZkCircuit2 as BundledZkCircuit,
7926
8407
  COMMITMENT_V2_ZK_NONCE,
7927
8408
  DEFAULT_STEALTH_SIGN_NONCE as DEFAULT_PRIVATE_ADDRESS_SIGN_NONCE,
@@ -7950,6 +8431,7 @@ export {
7950
8431
  RELAY_TRANSACT_PACKAGE_VERSION_V1,
7951
8432
  RELAY_TRANSACT_SIGNAL_PREFIX_BYTES,
7952
8433
  RELAY_TRANSACT_SUPPORTED_SIGNAL_COUNT,
8434
+ SIX_BY_SIX_BINDING_ZK_NONCE,
7953
8435
  SIX_BY_SIX_ZK_NONCE,
7954
8436
  STANDARD_ZK_CONFIG_NONCE,
7955
8437
  SUBMISSION_METHOD,