@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.
@@ -1,26 +1,8 @@
1
1
  // src/transact/pool/service.ts
2
2
  import {
3
- PrivacyPoolSDK as PrivacyPoolSDK2,
4
- decodeDecodedEphemeralKey,
5
- randomFrDecimal253,
6
- scalarHexToFrDecimal as scalarHexToFrDecimal2
3
+ decodeDecodedEphemeralKey
7
4
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
8
5
 
9
- // src/transact/merkle/field-decimal.ts
10
- import { Buffer as Buffer2 } from "buffer";
11
- var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
12
- var MERKLE_ROOT_BYTE_LENGTH = 32;
13
- var DECIMAL_STRING_RADIX = 10;
14
- function merkleRootBufferToFrDecimal(root) {
15
- if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
16
- throw new Error(
17
- `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
18
- );
19
- }
20
- const hex = Buffer2.from(root).toString("hex");
21
- return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
22
- }
23
-
24
6
  // src/transact/private-address/codec.ts
25
7
  import {
26
8
  buildStealthAddressSignMessage,
@@ -76,18 +58,18 @@ function privateAddressSdk(sdk) {
76
58
  import { withdrawObjectFromMerkleWitness as withdrawObjectFromMerkleWitness2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
77
59
 
78
60
  // src/transact/proofs/transaction-input.ts
79
- import { Buffer as Buffer4 } from "buffer";
61
+ import { Buffer as Buffer3 } from "buffer";
80
62
  import { StrKey as StrKey2 } from "@stellar/stellar-sdk";
81
63
 
82
64
  // src/transact/stellar/token-contract.ts
83
- import { Buffer as Buffer3 } from "buffer";
65
+ import { Buffer as Buffer2 } from "buffer";
84
66
  import { StrKey } from "@stellar/stellar-sdk";
85
67
  var CONTRACT_ID_BYTE_LENGTH = 32;
86
68
  var HEX_CHARS_PER_BYTE = 2;
87
69
  var CONTRACT_ID_HEX_CHAR_LENGTH = CONTRACT_ID_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
88
70
  var CONTRACT_ID_HEX = /^(?:0x)?([0-9a-f]{64})$/i;
89
71
  function encodeContractFromHex(hexPayload) {
90
- return StrKey.encodeContract(Buffer3.from(hexPayload, "hex"));
72
+ return StrKey.encodeContract(Buffer2.from(hexPayload, "hex"));
91
73
  }
92
74
  function normalizeSorobanContractId(raw) {
93
75
  const trimmed = raw.trim();
@@ -120,14 +102,14 @@ var ZERO_PUBLIC_WITHDRAW_ADDRESS = {
120
102
  var CONTRACT_ADDRESS_HALF_BYTES = 16;
121
103
  var CONTRACT_ADDRESS_BYTES = 32;
122
104
  function bytes16ToDecimal(bytes) {
123
- return BigInt(`0x${Buffer4.from(bytes).toString("hex")}`).toString();
105
+ return BigInt(`0x${Buffer3.from(bytes).toString("hex")}`).toString();
124
106
  }
125
107
  function decimalToBytes16(value) {
126
108
  const hex = BigInt(value).toString(16).padStart(CONTRACT_ADDRESS_HALF_BYTES * 2, "0");
127
109
  if (hex.length > CONTRACT_ADDRESS_HALF_BYTES * 2) {
128
110
  throw new Error("Asset leg does not fit into 16 bytes.");
129
111
  }
130
- return Buffer4.from(hex, "hex");
112
+ return Buffer3.from(hex, "hex");
131
113
  }
132
114
  function tokenAddressFrParts(tokenAddress) {
133
115
  const normalized = resolveTokenContractId(tokenAddress);
@@ -169,7 +151,7 @@ function tokenAddressToAssetLeg(tokenAddress) {
169
151
  return [hi, lo];
170
152
  }
171
153
  function assetLegToTokenAddress(assetHi, assetLo) {
172
- const payload = Buffer4.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
154
+ const payload = Buffer3.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
173
155
  return StrKey2.encodeContract(payload);
174
156
  }
175
157
  function buildZeroPublicLegs(publicNInputs = 1, publicNOutputs = 1) {
@@ -292,14 +274,144 @@ function requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl
292
274
  }
293
275
  }
294
276
  }
295
- function changeStroopsAfterDualTransfer(transferStroops, totalNotes) {
296
- if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
297
- throw new Error("Transfer amount must be positive");
277
+
278
+ // src/transact/fees/quote-request-body.ts
279
+ function presentInstructedAmount(amount) {
280
+ const trimmed = amount?.trim() ?? "";
281
+ if (!trimmed || trimmed === "0") {
282
+ return void 0;
283
+ }
284
+ return trimmed;
285
+ }
286
+ function feeQuoteRequestBody(request) {
287
+ const publicDepositAmount = presentInstructedAmount(request.publicDepositAmount);
288
+ const transferInstructedAmount = presentInstructedAmount(
289
+ request.transferInstructedAmount
290
+ );
291
+ const publicWithdrawalAmount = presentInstructedAmount(
292
+ request.publicWithdrawalAmount
293
+ );
294
+ return {
295
+ feeAsset: request.feeAsset,
296
+ spendsNotes: request.spendsNotes,
297
+ ...publicDepositAmount === void 0 ? {} : { publicDepositAmount },
298
+ ...transferInstructedAmount === void 0 ? {} : { transferInstructedAmount },
299
+ ...publicWithdrawalAmount === void 0 ? {} : { publicWithdrawalAmount }
300
+ };
301
+ }
302
+
303
+ // src/transact/fees/quote-required-fee.ts
304
+ function quoteHeaders(inspectAuthorization) {
305
+ const token = inspectAuthorization?.trim();
306
+ return {
307
+ "content-type": "application/json",
308
+ ...token ? { authorization: `Bearer ${token}` } : {}
309
+ };
310
+ }
311
+ function parseRequiredFee(raw) {
312
+ const text = `${raw ?? ""}`.trim();
313
+ if (!/^\d+$/u.test(text)) {
314
+ throw new Error("Fee Quote Required Fee must be an integer minor-unit string.");
315
+ }
316
+ return BigInt(text);
317
+ }
318
+ function requireQuoteString(raw, field) {
319
+ const text = `${raw ?? ""}`.trim();
320
+ if (!text) {
321
+ throw new Error(`Fee Quote is missing ${field}.`);
322
+ }
323
+ return text;
324
+ }
325
+ function parseQuotePayload(payload) {
326
+ return {
327
+ requiredFee: parseRequiredFee(payload.requiredFee),
328
+ feeAsset: requireQuoteString(payload.feeAsset, "Fee Asset"),
329
+ feeCollectorPrivateAddress: requireQuoteString(
330
+ payload.feeCollectorPrivateAddress,
331
+ "Fee Collector Private Address"
332
+ ),
333
+ feeRate: requireQuoteString(payload.feeRate, "Fee Rate")
334
+ };
335
+ }
336
+ async function quoteRequiredFee(input) {
337
+ const response = await fetch(
338
+ `${input.transactEnvironment.kyt.apiBaseUrl}/kyt/fees/quote`,
339
+ {
340
+ method: "POST",
341
+ headers: quoteHeaders(input.transactEnvironment.kyt.inspectAuthorization),
342
+ body: JSON.stringify(feeQuoteRequestBody(input.request))
343
+ }
344
+ );
345
+ const payload = await response.json();
346
+ if (!response.ok) {
347
+ throw new Error(`Fee Quote failed: ${JSON.stringify(payload)}`);
348
+ }
349
+ return parseQuotePayload(payload);
350
+ }
351
+
352
+ // src/transact/fees/should-attach-fee-output.ts
353
+ function shouldAttachFeeOutput(input) {
354
+ if (input.spendSource === "escrow" && input.escrowSend !== true) {
355
+ return false;
356
+ }
357
+ return input.kind === "deposit" || input.kind === "transfer" || input.kind === "withdraw";
358
+ }
359
+ function feeOutputKindFromPrepared(prepared) {
360
+ return {
361
+ kind: prepared.kind,
362
+ ...prepared.transactArtifacts?.spendSource ? { spendSource: prepared.transactArtifacts.spendSource } : {},
363
+ ...prepared.transactArtifacts?.escrowSend ? { escrowSend: true } : {}
364
+ };
365
+ }
366
+
367
+ // src/transact/fees/quote-fee-output-for-prepared.ts
368
+ function instructedMinorUnits(prepared) {
369
+ return prepared.intent.amount.toString();
370
+ }
371
+ function quoteRequestForPrepared(input) {
372
+ const amount = instructedMinorUnits(input.prepared);
373
+ if (input.prepared.kind === "deposit") {
374
+ return {
375
+ feeAsset: input.tokenAddress,
376
+ publicDepositAmount: amount,
377
+ spendsNotes: false
378
+ };
298
379
  }
299
- if (transferStroops > totalNotes) {
300
- throw new Error("Transfer amount exceeds combined note value");
380
+ if (input.prepared.kind === "withdraw") {
381
+ return {
382
+ feeAsset: input.tokenAddress,
383
+ publicWithdrawalAmount: amount,
384
+ spendsNotes: true
385
+ };
386
+ }
387
+ return {
388
+ feeAsset: input.tokenAddress,
389
+ transferInstructedAmount: amount,
390
+ spendsNotes: true
391
+ };
392
+ }
393
+ async function quoteFeeOutputForPrepared(input) {
394
+ if (!shouldAttachFeeOutput(feeOutputKindFromPrepared(input.prepared))) {
395
+ return void 0;
396
+ }
397
+ const quote = await quoteRequiredFee({
398
+ transactEnvironment: input.environment,
399
+ request: quoteRequestForPrepared(input)
400
+ });
401
+ if (quote.requiredFee <= 0n) {
402
+ return void 0;
301
403
  }
302
- return totalNotes - transferStroops;
404
+ return {
405
+ requiredFee: quote.requiredFee,
406
+ feeCollectorPrivateAddress: quote.feeCollectorPrivateAddress
407
+ };
408
+ }
409
+ function remainingAfterRequiredFee(input) {
410
+ const remaining = input.available - input.instructed - input.requiredFee;
411
+ if (remaining < 0n) {
412
+ throw new Error("Instructed amount and Required Fee exceed available note value");
413
+ }
414
+ return remaining;
303
415
  }
304
416
 
305
417
  // src/transact/audit/parameters.ts
@@ -307,7 +419,7 @@ import {
307
419
  DEMO_AUDIT_PUBLIC_KEY,
308
420
  resolveTransactionAuditParams
309
421
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
310
- import { Buffer as Buffer5 } from "buffer";
422
+ import { Buffer as Buffer4 } from "buffer";
311
423
  var UNCOMPRESSED_PREFIX = "04";
312
424
  var UNCOMPRESSED_HEX_LENGTH = 130;
313
425
  var XY_HEX_LENGTH = 128;
@@ -334,7 +446,7 @@ function parseAuditPublicKeyFromEnvHex(raw) {
334
446
  "Audit public key must be uncompressed 65-byte hex (04 + X + Y) or 64-byte X+Y hex"
335
447
  );
336
448
  }
337
- const bytes = Buffer5.from(payload, "hex");
449
+ const bytes = Buffer4.from(payload, "hex");
338
450
  const auditPublicKeyXDecimal = BigInt(
339
451
  `0x${bytes.subarray(0, X_OFFSET_END).toString("hex")}`
340
452
  ).toString(DECIMAL_RADIX);
@@ -377,13 +489,13 @@ import { ed25519PubkeyPayloadHexToWithdrawFrDecimals } from "@arcanetech/stellar
377
489
 
378
490
  // src/transact/stellar/account.ts
379
491
  import { StrKey as StrKey3 } from "@stellar/stellar-sdk";
380
- import { Buffer as Buffer6 } from "buffer";
492
+ import { Buffer as Buffer5 } from "buffer";
381
493
  var MUXED_ACCOUNT_ID_BYTE_LENGTH = 8;
382
494
  var ED25519_PUBLIC_KEY_BYTE_LENGTH = 32;
383
495
  function ed25519HexFromGAddress(trimmed) {
384
496
  try {
385
497
  const raw = StrKey3.decodeEd25519PublicKey(trimmed);
386
- return Buffer6.from(raw).toString("hex");
498
+ return Buffer5.from(raw).toString("hex");
387
499
  } catch {
388
500
  return void 0;
389
501
  }
@@ -440,6 +552,32 @@ function privKeyScalarDecimalFromRecipientScalarHex(hexInput) {
440
552
  return canonicalBabyJubScalarFromInteger(value).toString(DECIMAL_RADIX2);
441
553
  }
442
554
 
555
+ // src/transact/fees/append-fee-output.ts
556
+ function feeCoinFromSlot(slot) {
557
+ return {
558
+ commitment_hex: slot.commitment_hex,
559
+ coin: slot.coin,
560
+ depositScalarHex: slot.depositScalarHex,
561
+ precommitementHex: slot.precommitementHex
562
+ };
563
+ }
564
+ async function appendFeeOutputSlot(input) {
565
+ const requiredFee = input.feeOutput?.requiredFee ?? ZERO_STROOPS;
566
+ const collector = input.feeOutput?.feeCollectorPrivateAddress.trim() ?? "";
567
+ if (requiredFee <= ZERO_STROOPS || !collector) {
568
+ return { deposits: input.deposits };
569
+ }
570
+ const slot = await input.buildAlignedDepositSlot({
571
+ privateAddressStpl1: collector,
572
+ amountStroops: requiredFee,
573
+ tokenAddress: input.tokenAddress
574
+ });
575
+ return {
576
+ deposits: [...input.deposits, slot.deposit],
577
+ feeCoin: feeCoinFromSlot(slot)
578
+ };
579
+ }
580
+
443
581
  // src/transact/proofs/withdraw/helpers.ts
444
582
  function destinationWithdrawFrAndScalar(parameters) {
445
583
  const pkHex = ed25519PublicKeyHexFromStellarAccount(
@@ -494,14 +632,15 @@ async function resolveWithdrawChangeDeposits(parameters) {
494
632
  }
495
633
  };
496
634
  }
497
- function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1) {
635
+ function validateDualWithdrawAmounts(withdrawAmountStroops, totalNotes, changePrivateAddressStpl1, requiredFee) {
498
636
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
499
637
  throw new Error("Withdraw amount must be positive");
500
638
  }
501
- if (withdrawAmountStroops > totalNotes) {
502
- throw new Error("Withdraw amount exceeds combined note value");
503
- }
504
- const changeStroops = totalNotes - withdrawAmountStroops;
639
+ const changeStroops = remainingAfterRequiredFee({
640
+ available: totalNotes,
641
+ instructed: withdrawAmountStroops,
642
+ requiredFee
643
+ });
505
644
  requireChangeRecipientWhenPartial(changeStroops, changePrivateAddressStpl1);
506
645
  return changeStroops;
507
646
  }
@@ -514,11 +653,31 @@ function buildDualWithdrawPublicInput(parameters) {
514
653
  publicWithdrawalAmount: parameters.publicWithdrawalAmount
515
654
  });
516
655
  }
656
+ async function withdrawOutputsWithOptionalFee(parameters) {
657
+ const { deposits: changeDeposits, changeCoin } = await resolveWithdrawChangeDeposits({
658
+ changeStroops: parameters.changeStroops,
659
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
660
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
661
+ tokenAddress: parameters.tokenAddress
662
+ });
663
+ const withFee = await appendFeeOutputSlot({
664
+ deposits: changeDeposits,
665
+ tokenAddress: parameters.tokenAddress,
666
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
667
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
668
+ });
669
+ return {
670
+ deposits: withFee.deposits,
671
+ ...changeCoin ? { changeCoin } : {},
672
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
673
+ };
674
+ }
517
675
  async function prepareDualWithdrawProofInputs(parameters) {
518
676
  const changeStroops = validateDualWithdrawAmounts(
519
677
  parameters.withdrawAmountStroops,
520
678
  BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value),
521
- parameters.changePrivateAddressStpl1
679
+ parameters.changePrivateAddressStpl1,
680
+ parameters.feeOutput?.requiredFee ?? 0n
522
681
  );
523
682
  const { legA, legB } = dualWithdrawLegsWithSharedRoot({
524
683
  sdk: parameters.sdk,
@@ -533,11 +692,12 @@ async function prepareDualWithdrawProofInputs(parameters) {
533
692
  ),
534
693
  applicationId: parameters.applicationId
535
694
  });
536
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
695
+ const { deposits, changeCoin, feeCoin } = await withdrawOutputsWithOptionalFee({
537
696
  changeStroops,
538
697
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
539
698
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
540
- tokenAddress: parameters.tokenAddress
699
+ tokenAddress: parameters.tokenAddress,
700
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
541
701
  });
542
702
  const publicInput = buildDualWithdrawPublicInput({
543
703
  destinationStellarAddress: parameters.destinationStellarAddress,
@@ -556,19 +716,55 @@ async function prepareDualWithdrawProofInputs(parameters) {
556
716
  audit,
557
717
  deposits,
558
718
  withdrawLegs: [legA.withdrawObject, legB.withdrawObject],
559
- ...changeCoin ? { changeCoin } : {}
719
+ ...changeCoin ? { changeCoin } : {},
720
+ ...feeCoin ? { feeCoin } : {}
560
721
  };
561
722
  }
562
723
 
724
+ // src/transact/proofs/withdraw/fee-deposits.ts
725
+ async function withdrawDepositsWithFee(parameters) {
726
+ const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
727
+ changeStroops: parameters.changeStroops,
728
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
729
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
730
+ tokenAddress: parameters.tokenAddress
731
+ });
732
+ const withFee = await appendFeeOutputSlot({
733
+ deposits,
734
+ tokenAddress: parameters.tokenAddress,
735
+ buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
736
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
737
+ });
738
+ return {
739
+ deposits: withFee.deposits,
740
+ ...changeCoin ? { changeCoin } : {},
741
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
742
+ };
743
+ }
744
+ function withdrawOutputApplicationIds(input) {
745
+ const outputIds = [];
746
+ if (input.hasChange) {
747
+ outputIds.push(input.applicationId);
748
+ }
749
+ if (input.hasFeeOutput) {
750
+ outputIds.push(input.applicationId);
751
+ }
752
+ if (outputIds.length === 0) {
753
+ outputIds.push("0");
754
+ }
755
+ return outputIds;
756
+ }
757
+
563
758
  // src/transact/proofs/withdraw/transact-proof.ts
564
- function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops) {
759
+ function validateSingleWithdrawAmounts(withdrawAmountStroops, noteStroops, requiredFee) {
565
760
  if (withdrawAmountStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
566
761
  throw new Error("Withdraw amount must be positive");
567
762
  }
568
- if (withdrawAmountStroops > noteStroops) {
569
- throw new Error("Withdraw amount exceeds note value");
570
- }
571
- return noteStroops - withdrawAmountStroops;
763
+ return remainingAfterRequiredFee({
764
+ available: noteStroops,
765
+ instructed: withdrawAmountStroops,
766
+ requiredFee
767
+ });
572
768
  }
573
769
  async function proveSingleWithdrawTransaction(parameters) {
574
770
  return parameters.sdk.proveTransaction(
@@ -607,7 +803,8 @@ function buildPrimaryWithdrawForCoin(parameters) {
607
803
  async function prepareSingleWithdrawProofInputs(parameters) {
608
804
  const changeStroops = validateSingleWithdrawAmounts(
609
805
  parameters.withdrawAmountStroops,
610
- BigInt(parameters.coin.value)
806
+ BigInt(parameters.coin.value),
807
+ parameters.feeOutput?.requiredFee ?? 0n
611
808
  );
612
809
  const { witness, primaryWithdraw } = buildPrimaryWithdrawForCoin({
613
810
  sdk: parameters.sdk,
@@ -616,11 +813,12 @@ async function prepareSingleWithdrawProofInputs(parameters) {
616
813
  state: parameters.state,
617
814
  privKeyScalarHex: parameters.privKeyScalarHex
618
815
  });
619
- const { deposits, changeCoin } = await resolveWithdrawChangeDeposits({
816
+ const { deposits, changeCoin, feeCoin } = await withdrawDepositsWithFee({
620
817
  changeStroops,
621
818
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
622
819
  buildAlignedDepositSlot: parameters.buildAlignedDepositSlot,
623
- tokenAddress: parameters.tokenAddress
820
+ tokenAddress: parameters.tokenAddress,
821
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
624
822
  });
625
823
  const publicInput = buildWithdrawPublicInput({
626
824
  stateRoot: witness.stateRoot,
@@ -633,6 +831,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
633
831
  primaryWithdraw,
634
832
  deposits,
635
833
  changeCoin,
834
+ feeCoin,
636
835
  publicInput,
637
836
  audit: buildPoolTransactionAuditParameters({
638
837
  applicationId: parameters.applicationId,
@@ -642,7 +841,7 @@ async function prepareSingleWithdrawProofInputs(parameters) {
642
841
  };
643
842
  }
644
843
  async function proveWithdrawTransact(parameters) {
645
- const { primaryWithdraw, deposits, changeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
844
+ const { primaryWithdraw, deposits, changeCoin, feeCoin, publicInput, audit } = await prepareSingleWithdrawProofInputs(parameters);
646
845
  const proof = await proveSingleWithdrawTransaction({
647
846
  sdk: parameters.sdk,
648
847
  publicInput,
@@ -657,13 +856,17 @@ async function proveWithdrawTransact(parameters) {
657
856
  applicationIdsPlaintext: buildApplicationIdHints({
658
857
  sdk: parameters.sdk,
659
858
  inputIds: [parameters.applicationId],
660
- outputIds: [changeCoin ? parameters.applicationId : "0"]
859
+ outputIds: withdrawOutputApplicationIds({
860
+ applicationId: parameters.applicationId,
861
+ hasChange: Boolean(changeCoin),
862
+ hasFeeOutput: Boolean(feeCoin)
863
+ })
661
864
  }),
662
865
  ...changeCoin ? { changeCoin } : {}
663
866
  };
664
867
  }
665
868
  async function proveWithdrawTransactDual(parameters) {
666
- const { publicInput, audit, withdrawLegs, deposits, changeCoin } = await prepareDualWithdrawProofInputs(parameters);
869
+ const { publicInput, audit, withdrawLegs, deposits, changeCoin, feeCoin } = await prepareDualWithdrawProofInputs(parameters);
667
870
  const proof = await parameters.sdk.proveTransaction(
668
871
  publicInput,
669
872
  padPublicLegsToLayout(
@@ -682,7 +885,11 @@ async function proveWithdrawTransactDual(parameters) {
682
885
  applicationIdsPlaintext: buildApplicationIdHints({
683
886
  sdk: parameters.sdk,
684
887
  inputIds: [audit.applicationId, audit.applicationId],
685
- outputIds: [changeCoin ? audit.applicationId : "0"]
888
+ outputIds: withdrawOutputApplicationIds({
889
+ applicationId: audit.applicationId,
890
+ hasChange: Boolean(changeCoin),
891
+ hasFeeOutput: Boolean(feeCoin)
892
+ })
686
893
  }),
687
894
  ...changeCoin ? { changeCoin } : {}
688
895
  };
@@ -706,6 +913,9 @@ var DEFAULT_ZK_CONFIG_NONCE = BINDING_ZK_NONCE;
706
913
  function resolveZkConfigNonce(transactEnvironment) {
707
914
  return transactEnvironment.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
708
915
  }
916
+ function transactNonceFromArtifacts(artifacts, transactEnvironment) {
917
+ return artifacts?.zkConfigNonce ?? resolveZkConfigNonce(transactEnvironment);
918
+ }
709
919
 
710
920
  // src/transact/zk/browser-artifact-cache.ts
711
921
  import { ZK_ARTIFACT_VERSION } from "@arcanetech/stellar-privacy-pool-zk-sdk";
@@ -958,6 +1168,122 @@ async function buildAlignedDepositSlotForSdk(sdk, applicationId, parameters) {
958
1168
  };
959
1169
  }
960
1170
 
1171
+ // src/transact/pool/initialize-sdk.ts
1172
+ import { PrivacyPoolSDK as PrivacyPoolSDK2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1173
+ async function initializePrivacyPoolSdk(input, nonce) {
1174
+ const zkConfigNonce = nonce ?? input.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1175
+ const zkCircuits = await materializeSelectedZkCircuit(
1176
+ input.zkCircuits,
1177
+ zkConfigNonce,
1178
+ input.zkArtifactBaseUrl
1179
+ );
1180
+ return PrivacyPoolSDK2.init({
1181
+ wasmBinary: input.assets.sdkWasm,
1182
+ zkCircuits,
1183
+ zkConfigNonce,
1184
+ ...optionalZkArtifactBaseUrl(input.zkArtifactBaseUrl)
1185
+ });
1186
+ }
1187
+
1188
+ // src/transact/fees/zk-config-nonce-for-kind.ts
1189
+ function configuredNonceSpread(zkConfigNonce) {
1190
+ if (zkConfigNonce === void 0) {
1191
+ return {};
1192
+ }
1193
+ return { configuredNonce: zkConfigNonce };
1194
+ }
1195
+ function zkConfigNonceForFeeBearingKind(input) {
1196
+ if (input.configuredNonce === SIX_BY_SIX_BINDING_ZK_NONCE) {
1197
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1198
+ }
1199
+ if (input.kind === "transfer" && shouldAttachFeeOutput(input)) {
1200
+ return SIX_BY_SIX_BINDING_ZK_NONCE;
1201
+ }
1202
+ return input.configuredNonce ?? BINDING_ZK_NONCE;
1203
+ }
1204
+
1205
+ // src/transact/pool/prepare-deposit-proof.ts
1206
+ import {
1207
+ randomFrDecimal253,
1208
+ scalarHexToFrDecimal as scalarHexToFrDecimal2
1209
+ } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1210
+
1211
+ // src/transact/merkle/field-decimal.ts
1212
+ import { Buffer as Buffer6 } from "buffer";
1213
+ var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1214
+ var MERKLE_ROOT_BYTE_LENGTH = 32;
1215
+ var DECIMAL_STRING_RADIX = 10;
1216
+ function merkleRootBufferToFrDecimal(root) {
1217
+ if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
1218
+ throw new Error(
1219
+ `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
1220
+ );
1221
+ }
1222
+ const hex = Buffer6.from(root).toString("hex");
1223
+ return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
1224
+ }
1225
+
1226
+ // src/transact/pool/prepare-deposit-proof.ts
1227
+ function userDepositSlot(input) {
1228
+ return {
1229
+ value: input.coin.value,
1230
+ nullifier: input.coin.nullifier,
1231
+ ephemeralKeyScalar: scalarHexToFrDecimal2(input.depositScalarHex),
1232
+ asset: [input.coin.asset_hi, input.coin.asset_lo],
1233
+ applicationId: input.applicationId,
1234
+ recipientPublicKeys: recipientPublicKeysDecimalFromPrivateAddress(
1235
+ input.privateAddressStpl1
1236
+ )
1237
+ };
1238
+ }
1239
+ function outputApplicationIds(input) {
1240
+ if (input.hasFeeOutput) {
1241
+ return [input.applicationId, input.applicationId];
1242
+ }
1243
+ return [input.applicationId];
1244
+ }
1245
+ async function proveDepositTransact(input) {
1246
+ const publicDeposit = (input.publicDepositStroops ?? BigInt(input.coin.value)).toString();
1247
+ const withFee = await appendFeeOutputSlot({
1248
+ deposits: [userDepositSlot(input)],
1249
+ tokenAddress: input.tokenAddress,
1250
+ buildAlignedDepositSlot: input.buildAlignedDepositSlot,
1251
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
1252
+ });
1253
+ const audit = buildPoolTransactionAuditParameters({
1254
+ applicationId: input.applicationId,
1255
+ nAuditSlots: input.sdk.getLayout().nAuditSlots,
1256
+ ...input.auditPublicKey ? { auditPublicKey: input.auditPublicKey } : {}
1257
+ });
1258
+ const proof = await input.sdk.proveTransaction(
1259
+ withTokenAddressPublicInputs(
1260
+ {
1261
+ stateRoot: merkleRootBufferToFrDecimal(input.merkleRootBytes),
1262
+ privKeyScalar: randomFrDecimal253()
1263
+ },
1264
+ input.tokenAddress
1265
+ ),
1266
+ padPublicLegsToLayout(
1267
+ input.sdk,
1268
+ buildPublicDepositLegs(input.tokenAddress, publicDeposit)
1269
+ ),
1270
+ padWithdrawSlotsToLayout(input.sdk, []),
1271
+ padDepositSlotsToLayout(input.sdk, withFee.deposits),
1272
+ audit
1273
+ );
1274
+ return {
1275
+ ...proof,
1276
+ applicationIdsPlaintext: buildApplicationIdHints({
1277
+ sdk: input.sdk,
1278
+ inputIds: [],
1279
+ outputIds: outputApplicationIds({
1280
+ applicationId: input.applicationId,
1281
+ hasFeeOutput: Boolean(withFee.feeCoin)
1282
+ })
1283
+ })
1284
+ };
1285
+ }
1286
+
961
1287
  // src/transact/pool/service.ts
962
1288
  var PrivacyPoolService = class {
963
1289
  constructor(assets, applicationId, auditPublicKey, zkCircuits, zkConfigNonce, zkArtifactBaseUrl) {
@@ -974,42 +1300,50 @@ var PrivacyPoolService = class {
974
1300
  zkCircuits;
975
1301
  zkConfigNonce;
976
1302
  zkArtifactBaseUrl;
977
- sdk = void 0;
978
- initPromise = void 0;
979
- async ensureInit() {
980
- if (this.sdk) {
981
- return;
1303
+ sdks = /* @__PURE__ */ new Map();
1304
+ initPromises = /* @__PURE__ */ new Map();
1305
+ async getInitializedSdk(nonce) {
1306
+ const zkConfigNonce = nonce ?? this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
1307
+ const key = zkConfigNonce.toString();
1308
+ const existing = this.sdks.get(key);
1309
+ if (existing) {
1310
+ return existing;
982
1311
  }
983
- if (this.initPromise) {
984
- return this.initPromise;
1312
+ const pending = this.initPromises.get(key);
1313
+ if (pending) {
1314
+ return pending;
1315
+ }
1316
+ const started = this.initializeSdk(zkConfigNonce);
1317
+ this.initPromises.set(key, started);
1318
+ try {
1319
+ const sdk = await started;
1320
+ this.sdks.set(key, sdk);
1321
+ return sdk;
1322
+ } finally {
1323
+ this.initPromises.delete(key);
985
1324
  }
986
- this.initPromise = (async () => {
987
- if (!this.assets) {
988
- throw new Error(
989
- "PrivacyPoolService requires browser ZK assets before initialization."
990
- );
991
- }
992
- const zkConfigNonce = this.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
993
- const zkCircuits = await materializeSelectedZkCircuit(
994
- this.zkCircuits,
995
- zkConfigNonce,
996
- this.zkArtifactBaseUrl
997
- );
998
- this.sdk = await PrivacyPoolSDK2.init({
999
- wasmBinary: this.assets.sdkWasm,
1000
- zkCircuits,
1001
- zkConfigNonce,
1002
- ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1003
- });
1004
- })();
1005
- return this.initPromise;
1006
1325
  }
1007
- async getInitializedSdk() {
1008
- await this.ensureInit();
1009
- if (!this.sdk) {
1010
- throw new Error("SDK not initialized");
1326
+ feeBearingZkConfigNonce(input) {
1327
+ return zkConfigNonceForFeeBearingKind({
1328
+ ...input,
1329
+ ...configuredNonceSpread(this.zkConfigNonce)
1330
+ });
1331
+ }
1332
+ async initializeSdk(nonce) {
1333
+ if (!this.assets) {
1334
+ throw new Error(
1335
+ "PrivacyPoolService requires browser ZK assets before initialization."
1336
+ );
1011
1337
  }
1012
- return this.sdk;
1338
+ return initializePrivacyPoolSdk(
1339
+ {
1340
+ assets: this.assets,
1341
+ ...this.zkCircuits ? { zkCircuits: this.zkCircuits } : {},
1342
+ ...this.zkConfigNonce === void 0 ? {} : { zkConfigNonce: this.zkConfigNonce },
1343
+ ...optionalZkArtifactBaseUrl(this.zkArtifactBaseUrl)
1344
+ },
1345
+ nonce
1346
+ );
1013
1347
  }
1014
1348
  getAuditPublicKey() {
1015
1349
  return this.auditPublicKey;
@@ -1042,65 +1376,51 @@ var PrivacyPoolService = class {
1042
1376
  return { commitment_hex, coin, depositScalarHex, precommitementHex };
1043
1377
  }
1044
1378
  async prepareDepositTransactProof(parameters) {
1045
- const sdk = await this.getInitializedSdk();
1046
- const stateRoot = merkleRootBufferToFrDecimal(parameters.merkleRootBytes);
1047
- const recipientPublicKeys = recipientPublicKeysDecimalFromPrivateAddress(
1048
- parameters.privateAddressStpl1
1049
- );
1050
- const applicationId = this.getApplicationId();
1051
- const audit = buildPoolTransactionAuditParameters({
1052
- applicationId,
1053
- nAuditSlots: sdk.getLayout().nAuditSlots,
1054
- ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {}
1055
- });
1056
- const deposit = {
1057
- value: parameters.coin.value,
1058
- nullifier: parameters.coin.nullifier,
1059
- ephemeralKeyScalar: scalarHexToFrDecimal2(parameters.depositScalarHex),
1060
- asset: [parameters.coin.asset_hi, parameters.coin.asset_lo],
1061
- applicationId,
1062
- recipientPublicKeys
1063
- };
1064
- const publicInput = withTokenAddressPublicInputs(
1065
- {
1066
- stateRoot,
1067
- privKeyScalar: randomFrDecimal253()
1068
- },
1069
- parameters.tokenAddress
1070
- );
1071
- const proof = await sdk.proveTransaction(
1072
- publicInput,
1073
- padPublicLegsToLayout(
1074
- sdk,
1075
- buildPublicDepositLegs(parameters.tokenAddress, parameters.coin.value)
1076
- ),
1077
- padWithdrawSlotsToLayout(sdk, []),
1078
- padDepositSlotsToLayout(sdk, [deposit]),
1079
- audit
1379
+ const sdk = await this.getInitializedSdk(
1380
+ this.feeBearingZkConfigNonce({ kind: "deposit" })
1080
1381
  );
1081
- const applicationIdsPlaintext = buildApplicationIdHints({
1382
+ return proveDepositTransact({
1082
1383
  sdk,
1083
- inputIds: [],
1084
- outputIds: [applicationId]
1384
+ applicationId: this.getApplicationId(),
1385
+ privateAddressStpl1: parameters.privateAddressStpl1,
1386
+ coin: parameters.coin,
1387
+ depositScalarHex: parameters.depositScalarHex,
1388
+ merkleRootBytes: parameters.merkleRootBytes,
1389
+ tokenAddress: parameters.tokenAddress,
1390
+ buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1391
+ ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1392
+ ...parameters.publicDepositStroops === void 0 ? {} : { publicDepositStroops: parameters.publicDepositStroops },
1393
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1085
1394
  });
1086
- return { ...proof, applicationIdsPlaintext };
1087
1395
  }
1088
1396
  async calculateNullifierHash(nullifier, privKeyScalar) {
1089
1397
  const sdk = await this.getInitializedSdk();
1090
1398
  return sdk.calculateNullifierHash(nullifier, privKeyScalar);
1091
1399
  }
1092
1400
  async prepareWithdrawTransactProof(parameters) {
1093
- const sdk = await this.getInitializedSdk();
1401
+ const sdk = await this.getInitializedSdk(
1402
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1403
+ );
1094
1404
  return proveWithdrawTransact({
1095
1405
  sdk,
1096
1406
  applicationId: this.getApplicationId(),
1097
1407
  buildAlignedDepositSlot: (input) => this.buildAlignedDepositSlot(input),
1098
1408
  ...this.auditPublicKey ? { auditPublicKey: this.auditPublicKey } : {},
1099
- ...parameters
1409
+ coin: parameters.coin,
1410
+ state: parameters.state,
1411
+ destinationStellarAddress: parameters.destinationStellarAddress,
1412
+ privKeyScalarHex: parameters.privKeyScalarHex,
1413
+ depositorEphemeralKey: parameters.depositorEphemeralKey,
1414
+ withdrawAmountStroops: parameters.withdrawAmountStroops,
1415
+ changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1416
+ tokenAddress: parameters.tokenAddress,
1417
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1100
1418
  });
1101
1419
  }
1102
1420
  async prepareWithdrawTransactProofDual(parameters) {
1103
- const sdk = await this.getInitializedSdk();
1421
+ const sdk = await this.getInitializedSdk(
1422
+ this.feeBearingZkConfigNonce({ kind: "withdraw" })
1423
+ );
1104
1424
  return proveWithdrawTransactDual({
1105
1425
  sdk,
1106
1426
  applicationId: this.getApplicationId(),
@@ -1115,7 +1435,8 @@ var PrivacyPoolService = class {
1115
1435
  ephemeralBKey: parameters.depositorEphemeralBKey,
1116
1436
  withdrawAmountStroops: parameters.withdrawAmountStroops,
1117
1437
  changePrivateAddressStpl1: parameters.changePrivateAddressStpl1,
1118
- tokenAddress: parameters.tokenAddress
1438
+ tokenAddress: parameters.tokenAddress,
1439
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
1119
1440
  });
1120
1441
  }
1121
1442
  };
@@ -1820,7 +2141,7 @@ async function requestKytPassageForPoolInteraction(input) {
1820
2141
  const applicationId = resolvePoolApplicationId(
1821
2142
  input.transactEnvironment.network.applicationId
1822
2143
  );
1823
- const zkConfigNonce = resolveZkConfigNonce(input.transactEnvironment);
2144
+ const zkConfigNonce = input.zkConfigNonce ?? resolveZkConfigNonce(input.transactEnvironment);
1824
2145
  const applicationIdsPlaintext = input.applicationIdsPlaintext ?? buildApplicationIdHints2(
1825
2146
  applicationId,
1826
2147
  layoutForKnownNonce(zkConfigNonce).nAuditSlots
@@ -1933,6 +2254,7 @@ async function inspectRelayerSweepKyt(input) {
1933
2254
  ...input.prepared.transactArtifacts?.applicationIdsPlaintext ? {
1934
2255
  applicationIdsPlaintext: input.prepared.transactArtifacts.applicationIdsPlaintext
1935
2256
  } : {},
2257
+ ...input.prepared.transactArtifacts?.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.prepared.transactArtifacts.zkConfigNonce },
1936
2258
  networkPassphrase: environment.network.networkPassphrase,
1937
2259
  sorobanRpcUrl: environment.network.rpcUrl,
1938
2260
  transactEnvironment: environment
@@ -1945,7 +2267,10 @@ async function simulateSweepAuthEntries(input) {
1945
2267
  const operation = new Contract(environment.network.poolContract).call(
1946
2268
  "transact",
1947
2269
  new Address3(input.relayerPublicKey).toScVal(),
1948
- nativeToScVal2(resolveZkConfigNonce(environment), { type: "u64" }),
2270
+ nativeToScVal2(
2271
+ transactNonceFromArtifacts(input.prepared.transactArtifacts, environment),
2272
+ { type: "u64" }
2273
+ ),
1949
2274
  nativeToScVal2(hexToBytes(input.artifacts.proofHex), { type: "bytes" }),
1950
2275
  nativeToScVal2(hexToBytes(input.artifacts.publicHex), { type: "bytes" }),
1951
2276
  nativeToScVal2(hexToBytes(input.artifacts.ciphertextHex ?? ""), { type: "bytes" }),
@@ -1982,6 +2307,7 @@ async function unsignedEscrowAuthorizationForSweep(input) {
1982
2307
  });
1983
2308
  const auth = await simulateSweepAuthEntries({
1984
2309
  artifacts,
2310
+ prepared: input.prepared,
1985
2311
  transactEnvironment: input.transactEnvironment,
1986
2312
  relayerPublicKey,
1987
2313
  approval
@@ -2013,15 +2339,15 @@ async function loadDefaultStellarBrowserAssets(options) {
2013
2339
  }
2014
2340
 
2015
2341
  // src/transact/private-address/temporary-recipient.ts
2016
- import { PrivacyPoolSDK as PrivacyPoolSDK3 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
2342
+ import { PrivacyPoolSDK as PrivacyPoolSDK4 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
2017
2343
  var COORDINATE_HEX_LENGTH2 = 64;
2018
2344
  async function generateRandomDepositScalarHex() {
2019
2345
  await getPrivacyPoolService().getInitializedSdk();
2020
- return PrivacyPoolSDK3.generateRandomScalarHex32();
2346
+ return PrivacyPoolSDK4.generateRandomScalarHex32();
2021
2347
  }
2022
2348
  async function generateTemporaryRecipientPrivateAddress() {
2023
2349
  await getPrivacyPoolService().getInitializedSdk();
2024
- const temporaryScalarHex = PrivacyPoolSDK3.generateRandomScalarHex32();
2350
+ const temporaryScalarHex = PrivacyPoolSDK4.generateRandomScalarHex32();
2025
2351
  const sdk = await getPrivacyPoolService().getInitializedSdk();
2026
2352
  const point = sdk.ecdhEphemeralPublicKeyFromScalarHex(temporaryScalarHex);
2027
2353
  const publicKeyXHex = point.x.padStart(COORDINATE_HEX_LENGTH2, "0");
@@ -2290,6 +2616,10 @@ async function submitSorobanConfidentialTransfer(input) {
2290
2616
  networkPassphrase: input.networkPassphrase,
2291
2617
  sorobanRpcUrl: input.sorobanRpcUrl
2292
2618
  });
2619
+ const nonce = transactNonceFromArtifacts(
2620
+ input.zkConfigNonce === void 0 ? void 0 : { zkConfigNonce: input.zkConfigNonce },
2621
+ input.transactEnvironment
2622
+ );
2293
2623
  const approval = await requestKytPassageForPoolInteraction({
2294
2624
  owner: input.walletPublicKey,
2295
2625
  poolContract: input.contractId,
@@ -2298,6 +2628,7 @@ async function submitSorobanConfidentialTransfer(input) {
2298
2628
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
2299
2629
  ...input.outputNoteEphemeralScalars ? { outputNoteEphemeralScalars: input.outputNoteEphemeralScalars } : {},
2300
2630
  ...input.applicationIdsPlaintext ? { applicationIdsPlaintext: input.applicationIdsPlaintext } : {},
2631
+ ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
2301
2632
  networkPassphrase: input.networkPassphrase,
2302
2633
  sorobanRpcUrl: input.sorobanRpcUrl,
2303
2634
  transactEnvironment: input.transactEnvironment
@@ -2306,7 +2637,7 @@ async function submitSorobanConfidentialTransfer(input) {
2306
2637
  contractClient: poolClient,
2307
2638
  contractId: input.contractId,
2308
2639
  from: input.walletPublicKey,
2309
- nonce: resolveZkConfigNonce(input.transactEnvironment),
2640
+ nonce,
2310
2641
  proofHex: input.proofHex,
2311
2642
  publicHex: input.publicHex,
2312
2643
  ...input.ciphertextHex ? { ciphertextHex: input.ciphertextHex } : {},
@@ -2328,9 +2659,6 @@ function escrowOutputFields(input) {
2328
2659
  recipientLo: input.escrowSend.recipientLo
2329
2660
  };
2330
2661
  }
2331
- function paddingDepositPair(recipientDeposit) {
2332
- return [recipientDeposit, "dummy"];
2333
- }
2334
2662
  async function buildChangeDepositPair(parameters) {
2335
2663
  const changeSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
2336
2664
  privateAddressStpl1: parameters.selfPrivateAddressStpl1ForChange.trim(),
@@ -2347,34 +2675,54 @@ async function buildChangeDepositPair(parameters) {
2347
2675
  }
2348
2676
  };
2349
2677
  }
2678
+ function recipientOnlyDeposits(recipientDeposit, hasFeeOutput) {
2679
+ if (hasFeeOutput) {
2680
+ return [recipientDeposit];
2681
+ }
2682
+ return [recipientDeposit, "dummy"];
2683
+ }
2684
+ async function depositsWithoutFee(input) {
2685
+ if (input.changeStroops <= ZERO_STROOPS) {
2686
+ return {
2687
+ deposits: recipientOnlyDeposits(input.recipientDeposit, input.hasFeeOutput)
2688
+ };
2689
+ }
2690
+ return buildChangeDepositPair({
2691
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
2692
+ changeStroops: input.changeStroops,
2693
+ tokenAddress: input.tokenAddress,
2694
+ recipientDeposit: input.recipientDeposit
2695
+ });
2696
+ }
2350
2697
  async function buildRecipientAndOptionalChangeDeposits(parameters) {
2351
2698
  const escrow = {
2352
2699
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
2353
2700
  ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
2354
2701
  };
2355
- const recipientPrivateAddress = parameters.recipientPrivateAddressStpl1.trim();
2356
2702
  const recipientSlot = await getPrivacyPoolService().buildAlignedDepositSlot({
2357
- privateAddressStpl1: recipientPrivateAddress,
2703
+ privateAddressStpl1: parameters.recipientPrivateAddressStpl1.trim(),
2358
2704
  amountStroops: parameters.transferStroops,
2359
2705
  tokenAddress: parameters.tokenAddress,
2360
2706
  ...escrowOutputFields(escrow)
2361
2707
  });
2362
- if (parameters.changeStroops <= ZERO_STROOPS) {
2363
- return {
2364
- recipientSlot,
2365
- deposits: paddingDepositPair(recipientSlot.deposit)
2366
- };
2367
- }
2368
- const changeResult = await buildChangeDepositPair({
2369
- selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2708
+ const withoutFee = await depositsWithoutFee({
2709
+ recipientDeposit: recipientSlot.deposit,
2370
2710
  changeStroops: parameters.changeStroops,
2711
+ selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2371
2712
  tokenAddress: parameters.tokenAddress,
2372
- recipientDeposit: recipientSlot.deposit
2713
+ hasFeeOutput: (parameters.feeOutput?.requiredFee ?? ZERO_STROOPS) > ZERO_STROOPS
2714
+ });
2715
+ const withFee = await appendFeeOutputSlot({
2716
+ deposits: withoutFee.deposits,
2717
+ tokenAddress: parameters.tokenAddress,
2718
+ buildAlignedDepositSlot: (aligned) => getPrivacyPoolService().buildAlignedDepositSlot(aligned),
2719
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2373
2720
  });
2374
2721
  return {
2375
2722
  recipientSlot,
2376
- deposits: changeResult.deposits,
2377
- changeCoin: changeResult.changeCoin
2723
+ deposits: withFee.deposits,
2724
+ ..."changeCoin" in withoutFee ? { changeCoin: withoutFee.changeCoin } : {},
2725
+ ...withFee.feeCoin ? { feeCoin: withFee.feeCoin } : {}
2378
2726
  };
2379
2727
  }
2380
2728
 
@@ -2427,14 +2775,15 @@ function sweepOutputOwnerFields(parameters) {
2427
2775
  return { sweepOutputOwnerPubX, sweepOutputOwnerPubY };
2428
2776
  }
2429
2777
  async function buildTransferDepositsAndPublicInput(parameters) {
2430
- const { recipientSlot, deposits, changeCoin } = await buildRecipientAndOptionalChangeDeposits({
2778
+ const { recipientSlot, deposits, changeCoin, feeCoin } = await buildRecipientAndOptionalChangeDeposits({
2431
2779
  recipientPrivateAddressStpl1: parameters.recipientPrivateAddressStpl1,
2432
2780
  transferStroops: parameters.transferStroops,
2433
2781
  changeStroops: parameters.changeStroops,
2434
2782
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2435
2783
  tokenAddress: parameters.tokenAddress,
2436
2784
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
2437
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
2785
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
2786
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2438
2787
  });
2439
2788
  const publicInput = withTokenAddressPublicInputs(
2440
2789
  {
@@ -2449,6 +2798,7 @@ async function buildTransferDepositsAndPublicInput(parameters) {
2449
2798
  recipientSlot,
2450
2799
  deposits,
2451
2800
  ...changeCoin ? { changeCoin } : {},
2801
+ ...feeCoin ? { feeCoin } : {},
2452
2802
  publicInput
2453
2803
  };
2454
2804
  }
@@ -2464,7 +2814,8 @@ async function buildSenderTransferDepositsAndPublicInput(parameters) {
2464
2814
  parameters.senderPrivKeyScalarHex
2465
2815
  ),
2466
2816
  ...parameters.escrowSend ? { escrowSend: parameters.escrowSend } : {},
2467
- ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {}
2817
+ ...parameters.escrowClaimantLimbs ? { escrowClaimantLimbs: parameters.escrowClaimantLimbs } : {},
2818
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2468
2819
  });
2469
2820
  }
2470
2821
 
@@ -2488,11 +2839,20 @@ function stampDualWithdrawLegs(legs, parameters) {
2488
2839
  }
2489
2840
  };
2490
2841
  }
2491
- function buildDualTransferApplicationIds(sdk, applicationId, changeCoin) {
2842
+ function buildDualTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
2843
+ const outputIds = [applicationId];
2844
+ if (changeCoin) {
2845
+ outputIds.push(applicationId);
2846
+ } else if (!feeCoin) {
2847
+ outputIds.push("0");
2848
+ }
2849
+ if (feeCoin) {
2850
+ outputIds.push(applicationId);
2851
+ }
2492
2852
  return buildApplicationIdHints({
2493
2853
  sdk,
2494
2854
  inputIds: [applicationId, applicationId],
2495
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
2855
+ outputIds
2496
2856
  });
2497
2857
  }
2498
2858
  async function proveDualConfidentialTransfer(parameters) {
@@ -2512,16 +2872,24 @@ async function proveDualConfidentialTransfer(parameters) {
2512
2872
  audit
2513
2873
  );
2514
2874
  }
2515
- async function buildDualTransferProofContext(parameters, applicationId, sdk) {
2875
+ function dualTransferChangeStroops(parameters) {
2516
2876
  const totalNotes = BigInt(parameters.coinA.value) + BigInt(parameters.coinB.value);
2517
- const changeStroops = changeStroopsAfterDualTransfer(
2518
- parameters.transferStroops,
2519
- totalNotes
2520
- );
2877
+ if (parameters.transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
2878
+ throw new Error("Transfer amount must be positive");
2879
+ }
2880
+ const changeStroops = remainingAfterRequiredFee({
2881
+ available: totalNotes,
2882
+ instructed: parameters.transferStroops,
2883
+ requiredFee: parameters.feeOutput?.requiredFee ?? 0n
2884
+ });
2521
2885
  requireChangeRecipientWhenPartial(
2522
2886
  changeStroops,
2523
2887
  parameters.selfPrivateAddressStpl1ForChange
2524
2888
  );
2889
+ return changeStroops;
2890
+ }
2891
+ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
2892
+ const changeStroops = dualTransferChangeStroops(parameters);
2525
2893
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
2526
2894
  parameters.senderPrivKeyScalarHex
2527
2895
  );
@@ -2548,15 +2916,21 @@ async function buildDualTransferProofContext(parameters, applicationId, sdk) {
2548
2916
  selfPrivateAddressStpl1ForChange: parameters.selfPrivateAddressStpl1ForChange,
2549
2917
  tokenAddress: parameters.tokenAddress,
2550
2918
  stateRoot: legA.witness.stateRoot,
2551
- ...confidentialEscrowFields(parameters)
2919
+ ...confidentialEscrowFields(parameters),
2920
+ ...parameters.feeOutput ? { feeOutput: parameters.feeOutput } : {}
2552
2921
  });
2553
2922
  return { legA, legB, applicationId, ...transferInputs };
2554
2923
  }
2555
2924
  async function prepareConfidentialTransferProofDual(parameters) {
2556
2925
  const poolService = getPrivacyPoolService();
2557
- const sdk = await poolService.getInitializedSdk();
2926
+ const sdk = await poolService.getInitializedSdk(
2927
+ poolService.feeBearingZkConfigNonce({
2928
+ kind: "transfer",
2929
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
2930
+ })
2931
+ );
2558
2932
  const applicationId = poolService.getApplicationId();
2559
- const { legA, legB, recipientSlot, deposits, changeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
2933
+ const { legA, legB, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildDualTransferProofContext(parameters, applicationId, sdk);
2560
2934
  const auditPublicKey = poolService.getAuditPublicKey();
2561
2935
  const proof = await (auditPublicKey ? proveDualConfidentialTransfer({
2562
2936
  sdk,
@@ -2579,7 +2953,8 @@ async function prepareConfidentialTransferProofDual(parameters) {
2579
2953
  applicationIdsPlaintext: buildDualTransferApplicationIds(
2580
2954
  sdk,
2581
2955
  applicationId,
2582
- changeCoin
2956
+ changeCoin,
2957
+ feeCoin
2583
2958
  ),
2584
2959
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
2585
2960
  ...changeCoin ? { changeCoin } : {}
@@ -2587,22 +2962,32 @@ async function prepareConfidentialTransferProofDual(parameters) {
2587
2962
  }
2588
2963
 
2589
2964
  // src/transact/proofs/confidential/single.ts
2590
- function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange) {
2965
+ function validateSingleCoinTransferAmounts(transferStroops, noteStroops, selfPrivateAddressStpl1ForChange, requiredFee) {
2591
2966
  if (transferStroops < MIN_CONFIDENTIAL_TRANSFER_STROOPS) {
2592
2967
  throw new Error("Transfer amount must be positive");
2593
2968
  }
2594
- if (transferStroops > noteStroops) {
2595
- throw new Error("Transfer amount exceeds note value");
2596
- }
2597
- const changeStroops = noteStroops - transferStroops;
2969
+ const changeStroops = remainingAfterRequiredFee({
2970
+ available: noteStroops,
2971
+ instructed: transferStroops,
2972
+ requiredFee
2973
+ });
2598
2974
  requireChangeRecipientWhenPartial(changeStroops, selfPrivateAddressStpl1ForChange);
2599
2975
  return changeStroops;
2600
2976
  }
2601
- function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin) {
2977
+ function buildSingleTransferApplicationIds(sdk, applicationId, changeCoin, feeCoin) {
2978
+ const outputIds = [applicationId];
2979
+ if (changeCoin) {
2980
+ outputIds.push(applicationId);
2981
+ } else if (!feeCoin) {
2982
+ outputIds.push("0");
2983
+ }
2984
+ if (feeCoin) {
2985
+ outputIds.push(applicationId);
2986
+ }
2602
2987
  return buildApplicationIdHints({
2603
2988
  sdk,
2604
2989
  inputIds: [applicationId],
2605
- outputIds: [applicationId, changeCoin ? applicationId : "0"]
2990
+ outputIds
2606
2991
  });
2607
2992
  }
2608
2993
  function buildTransferAuditParameters(parameters) {
@@ -2628,11 +3013,25 @@ function confidentialEscrowFields2(input) {
2628
3013
  ...input.escrowClaimantLimbs ? { escrowClaimantLimbs: input.escrowClaimantLimbs } : {}
2629
3014
  };
2630
3015
  }
3016
+ function transferDepositInputFromProof(input, changeStroops, stateRoot) {
3017
+ return {
3018
+ senderPrivKeyScalarHex: input.senderPrivKeyScalarHex,
3019
+ recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1,
3020
+ transferStroops: input.transferStroops,
3021
+ changeStroops,
3022
+ selfPrivateAddressStpl1ForChange: input.selfPrivateAddressStpl1ForChange,
3023
+ tokenAddress: input.tokenAddress,
3024
+ stateRoot,
3025
+ ...confidentialEscrowFields2(input),
3026
+ ...input.feeOutput ? { feeOutput: input.feeOutput } : {}
3027
+ };
3028
+ }
2631
3029
  async function buildTransferProofInputs(parameters) {
2632
3030
  const changeStroops = validateSingleCoinTransferAmounts(
2633
3031
  parameters.input.transferStroops,
2634
3032
  parameters.noteStroops,
2635
- parameters.input.selfPrivateAddressStpl1ForChange
3033
+ parameters.input.selfPrivateAddressStpl1ForChange,
3034
+ parameters.input.feeOutput?.requiredFee ?? 0n
2636
3035
  );
2637
3036
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
2638
3037
  parameters.input.senderPrivKeyScalarHex
@@ -2651,31 +3050,30 @@ async function buildTransferProofInputs(parameters) {
2651
3050
  withdrawObject,
2652
3051
  sweepWithdrawStamp(confidentialEscrowFields2(parameters.input))
2653
3052
  );
2654
- const { recipientSlot, deposits, changeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput({
2655
- senderPrivKeyScalarHex: parameters.input.senderPrivKeyScalarHex,
2656
- recipientPrivateAddressStpl1: parameters.input.recipientPrivateAddressStpl1,
2657
- transferStroops: parameters.input.transferStroops,
2658
- changeStroops,
2659
- selfPrivateAddressStpl1ForChange: parameters.input.selfPrivateAddressStpl1ForChange,
2660
- tokenAddress: parameters.input.tokenAddress,
2661
- stateRoot: witness.stateRoot,
2662
- ...confidentialEscrowFields2(parameters.input)
2663
- });
3053
+ const { recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildSenderTransferDepositsAndPublicInput(
3054
+ transferDepositInputFromProof(parameters.input, changeStroops, witness.stateRoot)
3055
+ );
2664
3056
  return {
2665
3057
  witness,
2666
3058
  withdrawObject: stampedWithdraw,
2667
3059
  recipientSlot,
2668
3060
  deposits,
2669
3061
  changeCoin,
3062
+ feeCoin,
2670
3063
  publicInput
2671
3064
  };
2672
3065
  }
2673
3066
  async function prepareConfidentialTransferProof(parameters) {
2674
3067
  const poolService = getPrivacyPoolService();
2675
- const sdk = await poolService.getInitializedSdk();
3068
+ const sdk = await poolService.getInitializedSdk(
3069
+ poolService.feeBearingZkConfigNonce({
3070
+ kind: "transfer",
3071
+ ...parameters.escrowClaimantLimbs ? { spendSource: "escrow" } : {}
3072
+ })
3073
+ );
2676
3074
  const applicationId = poolService.getApplicationId();
2677
3075
  const noteStroops = BigInt(parameters.coin.value);
2678
- const { withdrawObject, recipientSlot, deposits, changeCoin, publicInput } = await buildTransferProofInputs({
3076
+ const { withdrawObject, recipientSlot, deposits, changeCoin, feeCoin, publicInput } = await buildTransferProofInputs({
2679
3077
  sdk,
2680
3078
  applicationId,
2681
3079
  input: parameters,
@@ -2698,7 +3096,8 @@ async function prepareConfidentialTransferProof(parameters) {
2698
3096
  applicationIdsPlaintext: buildSingleTransferApplicationIds(
2699
3097
  sdk,
2700
3098
  applicationId,
2701
- changeCoin
3099
+ changeCoin,
3100
+ feeCoin
2702
3101
  ),
2703
3102
  recipientCoin: generatedOutputCoinFromSlot(recipientSlot),
2704
3103
  ...changeCoin ? { changeCoin } : {}
@@ -5263,7 +5662,12 @@ function proofEscrowFields(input) {
5263
5662
  }
5264
5663
  async function buildSingleTransferProofAtExecute(input) {
5265
5664
  const context = await buildSpendProofContextAtExecute(input);
5266
- const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount;
5665
+ const feeOutput = await quoteFeeOutputForPrepared({
5666
+ prepared: input.prepared,
5667
+ environment: input.environment,
5668
+ tokenAddress: context.tokenAddress
5669
+ });
5670
+ const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5267
5671
  const proof = await prepareConfidentialTransferProof({
5268
5672
  coin: context.coin,
5269
5673
  state: { commitments: context.commitments },
@@ -5276,7 +5680,8 @@ async function buildSingleTransferProofAtExecute(input) {
5276
5680
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5277
5681
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5278
5682
  tokenAddress: context.tokenAddress,
5279
- ...proofEscrowFields(input)
5683
+ ...proofEscrowFields(input),
5684
+ ...feeOutput ? { feeOutput } : {}
5280
5685
  });
5281
5686
  return { context, proof };
5282
5687
  }
@@ -5293,7 +5698,12 @@ async function buildDualTransferProofAtExecute(input) {
5293
5698
  commitments: context.commitments
5294
5699
  });
5295
5700
  const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
5296
- const changeStroops = totalNotes - input.prepared.intent.amount;
5701
+ const feeOutput = await quoteFeeOutputForPrepared({
5702
+ prepared: input.prepared,
5703
+ environment: input.environment,
5704
+ tokenAddress: context.tokenAddress
5705
+ });
5706
+ const changeStroops = totalNotes - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
5297
5707
  const proof = await prepareConfidentialTransferProofDual({
5298
5708
  coinA: context.coin,
5299
5709
  coinB: secondary.coin,
@@ -5311,7 +5721,8 @@ async function buildDualTransferProofAtExecute(input) {
5311
5721
  recipientPrivateAddressStpl1: context.recipientPrivateAddressStpl1,
5312
5722
  selfPrivateAddressStpl1ForChange: readChangeRecipient(input, changeStroops),
5313
5723
  tokenAddress: context.tokenAddress,
5314
- ...proofEscrowFields(input)
5724
+ ...proofEscrowFields(input),
5725
+ ...feeOutput ? { feeOutput } : {}
5315
5726
  });
5316
5727
  return { context, proof };
5317
5728
  }
@@ -5443,11 +5854,17 @@ async function runTransferFinalizeSteps(input) {
5443
5854
  walletPublicKey: input.walletPublicKey,
5444
5855
  ...recipient.escrowSend ? { escrowSendPrivateAddress: recipient.recipientPrivateAddressStpl1 } : {}
5445
5856
  });
5446
- return buildTransferFinalizeArtifacts({
5447
- proof,
5448
- context,
5449
- ...sweepFinalizeStamp(input.prepared, recipient)
5450
- });
5857
+ return {
5858
+ ...buildTransferFinalizeArtifacts({
5859
+ proof,
5860
+ context,
5861
+ ...sweepFinalizeStamp(input.prepared, recipient)
5862
+ }),
5863
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
5864
+ ...feeOutputKindFromPrepared(input.prepared),
5865
+ ...configuredNonceSpread(input.environment.zkConfigNonce)
5866
+ })
5867
+ };
5451
5868
  }
5452
5869
  function sweepArtifactStamp(artifacts) {
5453
5870
  return {
@@ -5475,6 +5892,24 @@ async function finalizeTransferAtExecute(prepared, environment) {
5475
5892
  return runTransferFinalizeSteps({ prepared, environment, walletPublicKey });
5476
5893
  }
5477
5894
 
5895
+ // src/transact/engine/withdraw-fee.ts
5896
+ async function withdrawFeeProofFields(input) {
5897
+ const feeOutput = await quoteFeeOutputForPrepared({
5898
+ prepared: input.prepared,
5899
+ environment: input.environment,
5900
+ tokenAddress: input.tokenAddress
5901
+ });
5902
+ const changeStroops = remainingAfterRequiredFee({
5903
+ available: input.available,
5904
+ instructed: input.prepared.intent.amount,
5905
+ requiredFee: feeOutput?.requiredFee ?? 0n
5906
+ });
5907
+ return {
5908
+ ...feeOutput ? { feeOutput } : {},
5909
+ ...changeStroops > 0n ? { changePrivateAddressStpl1: input.withdrawFrom } : {}
5910
+ };
5911
+ }
5912
+
5478
5913
  // src/transact/engine/execute.ts
5479
5914
  function enrichWithdrawOutputRecords(prepared, proof) {
5480
5915
  if (!proof.changeCoin || prepared.outputRecords.length === 0) {
@@ -5490,7 +5925,7 @@ function enrichWithdrawOutputRecords(prepared, proof) {
5490
5925
  precommitementHex: changeCoin.precommitementHex
5491
5926
  }));
5492
5927
  }
5493
- function buildWithdrawFinalizeArtifacts(proof, context) {
5928
+ function buildWithdrawFinalizeArtifacts(proof, context, environment) {
5494
5929
  return {
5495
5930
  proofHex: proof.proof_hex,
5496
5931
  publicHex: proof.public_hex,
@@ -5498,7 +5933,11 @@ function buildWithdrawFinalizeArtifacts(proof, context) {
5498
5933
  applicationIdsPlaintext: proof.applicationIdsPlaintext,
5499
5934
  tokenAddress: context.tokenAddress,
5500
5935
  walletPublicKey: context.walletPublicKey,
5501
- executeFinalizeRequired: false
5936
+ executeFinalizeRequired: false,
5937
+ zkConfigNonce: zkConfigNonceForFeeBearingKind({
5938
+ kind: "withdraw",
5939
+ ...configuredNonceSpread(environment.zkConfigNonce)
5940
+ })
5502
5941
  };
5503
5942
  }
5504
5943
  async function finalizeSingleWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
@@ -5507,7 +5946,13 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
5507
5946
  environment,
5508
5947
  recipientPrivateAddressStpl1: withdrawFrom
5509
5948
  });
5510
- const changeStroops = BigInt(context.coin.value) - prepared.intent.amount;
5949
+ const feeFields = await withdrawFeeProofFields({
5950
+ prepared,
5951
+ environment,
5952
+ tokenAddress: context.tokenAddress,
5953
+ available: BigInt(context.coin.value),
5954
+ withdrawFrom
5955
+ });
5511
5956
  const proof = await poolService.prepareWithdrawTransactProof({
5512
5957
  coin: context.coin,
5513
5958
  state: { commitments: context.commitments },
@@ -5518,11 +5963,24 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
5518
5963
  yHex: context.ephemeral.yHex
5519
5964
  }),
5520
5965
  withdrawAmountStroops: prepared.intent.amount,
5521
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
5522
- tokenAddress: context.tokenAddress
5966
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
5967
+ tokenAddress: context.tokenAddress,
5968
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
5523
5969
  });
5524
5970
  enrichWithdrawOutputRecords(prepared, proof);
5525
- return buildWithdrawFinalizeArtifacts(proof, context);
5971
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
5972
+ }
5973
+ function dualWithdrawEphemeralKeys(input) {
5974
+ return {
5975
+ depositorEphemeralAKey: serializeEphemeralKeyString({
5976
+ xHex: input.context.ephemeral.xHex,
5977
+ yHex: input.context.ephemeral.yHex
5978
+ }),
5979
+ depositorEphemeralBKey: serializeEphemeralKeyString({
5980
+ xHex: input.secondary.ephemeral.xHex,
5981
+ yHex: input.secondary.ephemeral.yHex
5982
+ })
5983
+ };
5526
5984
  }
5527
5985
  async function finalizeDualWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
5528
5986
  const [, secondaryRecord] = prepared.consumedRecords;
@@ -5540,28 +5998,27 @@ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService,
5540
5998
  walletPublicKey: context.walletPublicKey,
5541
5999
  commitments: context.commitments
5542
6000
  });
5543
- const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
5544
- const changeStroops = totalNotes - prepared.intent.amount;
6001
+ const feeFields = await withdrawFeeProofFields({
6002
+ prepared,
6003
+ environment,
6004
+ tokenAddress: context.tokenAddress,
6005
+ available: BigInt(context.coin.value) + BigInt(secondary.coin.value),
6006
+ withdrawFrom
6007
+ });
5545
6008
  const proof = await poolService.prepareWithdrawTransactProofDual({
5546
6009
  coinA: context.coin,
5547
6010
  coinB: secondary.coin,
5548
6011
  state: { commitments: context.commitments },
5549
6012
  destinationStellarAddress: prepared.intent.to,
5550
6013
  privKeyScalarHex: context.senderPrivKeyScalarHex,
5551
- depositorEphemeralAKey: serializeEphemeralKeyString({
5552
- xHex: context.ephemeral.xHex,
5553
- yHex: context.ephemeral.yHex
5554
- }),
5555
- depositorEphemeralBKey: serializeEphemeralKeyString({
5556
- xHex: secondary.ephemeral.xHex,
5557
- yHex: secondary.ephemeral.yHex
5558
- }),
6014
+ ...dualWithdrawEphemeralKeys({ context, secondary }),
5559
6015
  withdrawAmountStroops: prepared.intent.amount,
5560
- changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
5561
- tokenAddress: context.tokenAddress
6016
+ changePrivateAddressStpl1: feeFields.changePrivateAddressStpl1,
6017
+ tokenAddress: context.tokenAddress,
6018
+ ...feeFields.feeOutput ? { feeOutput: feeFields.feeOutput } : {}
5562
6019
  });
5563
6020
  enrichWithdrawOutputRecords(prepared, proof);
5564
- return buildWithdrawFinalizeArtifacts(proof, context);
6021
+ return buildWithdrawFinalizeArtifacts(proof, context, environment);
5565
6022
  }
5566
6023
  async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
5567
6024
  if (prepared.kind !== "withdraw") {
@@ -5787,10 +6244,11 @@ function requirePreparedArtifacts(prepared) {
5787
6244
  };
5788
6245
  }
5789
6246
  function prepareRelayTransactPackageFromPrepared(input) {
6247
+ const zkConfigNonce = input.zkConfigNonce ?? input.prepared.transactArtifacts?.zkConfigNonce;
5790
6248
  return prepareRelayTransactPackage({
5791
6249
  poolSelector: input.poolSelector,
5792
6250
  ...requirePreparedArtifacts(input.prepared),
5793
- ...input.zkConfigNonce === void 0 ? {} : { zkConfigNonce: input.zkConfigNonce },
6251
+ ...zkConfigNonce === void 0 ? {} : { zkConfigNonce },
5794
6252
  ...input.keyVersionHints ? { keyVersionHints: input.keyVersionHints } : {}
5795
6253
  });
5796
6254
  }
@@ -5900,6 +6358,7 @@ function requiredSubmissionMethod(prepared, zkConfigNonce = DEFAULT_ZK_CONFIG_NO
5900
6358
  return result.method;
5901
6359
  }
5902
6360
  export {
6361
+ BINDING_ZK_NONCE,
5903
6362
  BundledZkCircuit2 as BundledZkCircuit,
5904
6363
  COMMITMENT_V2_ZK_NONCE,
5905
6364
  DEFAULT_STEALTH_SIGN_NONCE as DEFAULT_PRIVATE_ADDRESS_SIGN_NONCE,
@@ -5928,6 +6387,7 @@ export {
5928
6387
  RELAY_TRANSACT_PACKAGE_VERSION_V1,
5929
6388
  RELAY_TRANSACT_SIGNAL_PREFIX_BYTES,
5930
6389
  RELAY_TRANSACT_SUPPORTED_SIGNAL_COUNT,
6390
+ SIX_BY_SIX_BINDING_ZK_NONCE,
5931
6391
  SIX_BY_SIX_ZK_NONCE,
5932
6392
  STANDARD_ZK_CONFIG_NONCE,
5933
6393
  SUBMISSION_METHOD,