@arcanetech/privacy-sdk-stellar 0.2.3 → 0.3.0

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.d.ts CHANGED
@@ -252,6 +252,12 @@ interface StellarKytEnvironment {
252
252
  }
253
253
  interface StellarTransactEnvironment {
254
254
  network: StellarNetworkConfig;
255
+ /**
256
+ * Nonce (persistent storage key) of the pool's `ZkConfig` entry this
257
+ * environment's proofs/circuit shape target. Defaults to `0n` (the
258
+ * standard circuit) when omitted.
259
+ */
260
+ zkConfigNonce?: bigint;
255
261
  auditPublicKey?: AuditPublicKey;
256
262
  kyt: StellarKytEnvironment;
257
263
  signTransaction?: StellarSignTransaction;
package/dist/index.js CHANGED
@@ -486,7 +486,7 @@ function createInternalPoolContractClient(input) {
486
486
  networkPassphrase: input.networkPassphrase,
487
487
  walletPublicKey: input.walletPublicKey
488
488
  });
489
- return new PrivacyPoolContractClient({
489
+ const client = new PrivacyPoolContractClient({
490
490
  contractId: input.contractId,
491
491
  networkPassphrase: input.networkPassphrase,
492
492
  rpcUrl: input.sorobanRpcUrl,
@@ -494,6 +494,7 @@ function createInternalPoolContractClient(input) {
494
494
  publicKey: input.walletPublicKey,
495
495
  signTransaction: signBinding
496
496
  });
497
+ return client;
497
498
  }
498
499
 
499
500
  // src/contracts/registry/create-registry-client.ts
@@ -1693,7 +1694,7 @@ function pickRecordsForSpendAmount(input) {
1693
1694
  if (selectedTotal >= input.amount) {
1694
1695
  return selected;
1695
1696
  }
1696
- if (input.kind === "transfer" && selected.length >= 2) {
1697
+ if ((input.kind === "transfer" || input.kind === "withdraw") && selected.length >= 2) {
1697
1698
  throw insufficientStateError3(
1698
1699
  "missing_private_records",
1699
1700
  "Storage does not contain enough private records for this operation.",
@@ -6415,12 +6416,17 @@ function enrichWithdrawOutputRecords(prepared, proof) {
6415
6416
  precommitementHex: changeCoin.precommitementHex
6416
6417
  }));
6417
6418
  }
6418
- async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6419
- if (prepared.kind !== "withdraw") {
6420
- throw new Error("Withdraw finalize requires a withdraw operation.");
6421
- }
6422
- const withdrawIntent = prepared.intent;
6423
- const withdrawFrom = withdrawIntent.from;
6419
+ function buildWithdrawFinalizeArtifacts(proof, context) {
6420
+ return {
6421
+ proofHex: proof.proof_hex,
6422
+ publicHex: proof.public_hex,
6423
+ applicationIdsPlaintext: proof.applicationIdsPlaintext,
6424
+ tokenAddress: context.tokenAddress,
6425
+ walletPublicKey: context.walletPublicKey,
6426
+ executeFinalizeRequired: false
6427
+ };
6428
+ }
6429
+ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
6424
6430
  const context = await buildSpendProofContextAtExecute({
6425
6431
  prepared,
6426
6432
  environment,
@@ -6441,14 +6447,71 @@ async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6441
6447
  tokenAddress: context.tokenAddress
6442
6448
  });
6443
6449
  enrichWithdrawOutputRecords(prepared, proof);
6444
- return {
6445
- proofHex: proof.proof_hex,
6446
- publicHex: proof.public_hex,
6447
- applicationIdsPlaintext: proof.applicationIdsPlaintext,
6448
- tokenAddress: context.tokenAddress,
6450
+ return buildWithdrawFinalizeArtifacts(proof, context);
6451
+ }
6452
+ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
6453
+ const [, secondaryRecord] = prepared.consumedRecords;
6454
+ if (!secondaryRecord) {
6455
+ throw new Error("Dual withdraw execution requires two private input records.");
6456
+ }
6457
+ const context = await buildSpendProofContextAtExecute({
6458
+ prepared,
6459
+ environment,
6460
+ recipientPrivateAddressStpl1: withdrawFrom
6461
+ });
6462
+ const secondary = await readCoinEphemeralForRecord({
6463
+ record: secondaryRecord,
6464
+ environment,
6449
6465
  walletPublicKey: context.walletPublicKey,
6450
- executeFinalizeRequired: false
6451
- };
6466
+ commitments: context.commitments
6467
+ });
6468
+ const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
6469
+ const changeStroops = totalNotes - prepared.intent.amount;
6470
+ const proof = await poolService.prepareWithdrawTransactProofDual({
6471
+ coinA: context.coin,
6472
+ coinB: secondary.coin,
6473
+ state: { commitments: context.commitments },
6474
+ destinationStellarAddress: prepared.intent.to,
6475
+ privKeyScalarHex: context.senderPrivKeyScalarHex,
6476
+ depositorEphemeralAKey: serializeEphemeralKeyString({
6477
+ xHex: context.ephemeral.xHex,
6478
+ yHex: context.ephemeral.yHex
6479
+ }),
6480
+ depositorEphemeralBKey: serializeEphemeralKeyString({
6481
+ xHex: secondary.ephemeral.xHex,
6482
+ yHex: secondary.ephemeral.yHex
6483
+ }),
6484
+ withdrawAmountStroops: prepared.intent.amount,
6485
+ changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6486
+ tokenAddress: context.tokenAddress
6487
+ });
6488
+ enrichWithdrawOutputRecords(prepared, proof);
6489
+ return buildWithdrawFinalizeArtifacts(proof, context);
6490
+ }
6491
+ async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6492
+ if (prepared.kind !== "withdraw") {
6493
+ throw new Error("Withdraw finalize requires a withdraw operation.");
6494
+ }
6495
+ const withdrawIntent = prepared.intent;
6496
+ const withdrawFrom = withdrawIntent.from;
6497
+ const recordCount = prepared.consumedRecords.length;
6498
+ if (recordCount === 1) {
6499
+ return finalizeSingleWithdrawAtExecute(
6500
+ prepared,
6501
+ environment,
6502
+ poolService,
6503
+ withdrawFrom
6504
+ );
6505
+ }
6506
+ if (recordCount === 2) {
6507
+ return finalizeDualWithdrawAtExecute(
6508
+ prepared,
6509
+ environment,
6510
+ poolService,
6511
+ withdrawFrom
6512
+ );
6513
+ }
6514
+ throw new Error("Confidential withdraw execution supports at most two input notes.");
6452
6515
  }
6453
6516
  async function finalizeSpendOperationAtExecute(prepared, environment, poolService) {
6454
6517
  if (prepared.kind !== "transfer" && prepared.kind !== "withdraw") {
@@ -6606,6 +6669,12 @@ async function registerKytPassage(parameters) {
6606
6669
  await waitForKytRegistration({ server, hash: result.hash });
6607
6670
  }
6608
6671
 
6672
+ // src/transact/environment/zk-config-nonce.ts
6673
+ var DEFAULT_ZK_CONFIG_NONCE = 0n;
6674
+ function resolveZkConfigNonce(transactEnvironment) {
6675
+ return transactEnvironment.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
6676
+ }
6677
+
6609
6678
  // src/transact/kyt/passage/inspect.ts
6610
6679
  function buildApplicationIdHints(applicationId) {
6611
6680
  return [applicationId, applicationId, applicationId, applicationId];
@@ -6659,6 +6728,7 @@ async function requestKytPassageForPoolInteraction(input) {
6659
6728
  decryptedAuditSlots: input.decryptedAuditSlots,
6660
6729
  onboarding: input.onboarding,
6661
6730
  nonce: buildNonce(),
6731
+ zkConfigNonce: resolveZkConfigNonce(input.transactEnvironment),
6662
6732
  currentLedger
6663
6733
  },
6664
6734
  kytJsonReplacer
@@ -6681,11 +6751,123 @@ async function requestKytPassageForPoolInteraction(input) {
6681
6751
  return body;
6682
6752
  }
6683
6753
 
6754
+ // src/transact/submit/zk-config-ttl.ts
6755
+ import {
6756
+ Address as Address2,
6757
+ Operation as Operation2,
6758
+ SorobanDataBuilder,
6759
+ TransactionBuilder as TransactionBuilder2,
6760
+ rpc as rpc4,
6761
+ xdr as xdr3
6762
+ } from "@stellar/stellar-sdk";
6763
+ var ZK_CONFIG_TTL_THRESHOLD_LEDGERS = 1e5;
6764
+ var ZK_CONFIG_TTL_EXTEND_TO_LEDGERS = 5e5;
6765
+ var EXTEND_TTL_FEE_STROOPS = "1000000";
6766
+ var EXTEND_TTL_TIMEOUT_SECONDS = 300;
6767
+ var EXTEND_TTL_MAX_POLLS = 60;
6768
+ var EXTEND_TTL_POLL_INTERVAL_MS = 1e3;
6769
+ async function waitForExtendFootprintTtlConfirmation(parameters) {
6770
+ for (let attempt = 0; attempt < EXTEND_TTL_MAX_POLLS; attempt += 1) {
6771
+ const response = await parameters.server.getTransaction(parameters.hash);
6772
+ if (response.status === "SUCCESS") {
6773
+ return;
6774
+ }
6775
+ if (response.status === "FAILED") {
6776
+ throw new Error("ZK config TTL extension failed on-chain");
6777
+ }
6778
+ await new Promise((resolve) => {
6779
+ setTimeout(resolve, EXTEND_TTL_POLL_INTERVAL_MS);
6780
+ });
6781
+ }
6782
+ throw new Error("ZK config TTL extension was not confirmed in time");
6783
+ }
6784
+ function zkConfigLedgerKey(contractId, nonce) {
6785
+ const configKeyScValue = xdr3.ScVal.scvVec([
6786
+ xdr3.ScVal.scvSymbol("Config"),
6787
+ xdr3.ScVal.scvU64(xdr3.Uint64.fromString(nonce.toString()))
6788
+ ]);
6789
+ return xdr3.LedgerKey.contractData(
6790
+ new xdr3.LedgerKeyContractData({
6791
+ contract: new Address2(contractId).toScAddress(),
6792
+ key: configKeyScValue,
6793
+ durability: xdr3.ContractDataDurability.persistent()
6794
+ })
6795
+ );
6796
+ }
6797
+ async function needsTtlExtension(parameters) {
6798
+ const [entriesResponse, latestLedger] = await Promise.all([
6799
+ parameters.server.getLedgerEntries(parameters.ledgerKey),
6800
+ parameters.server.getLatestLedger()
6801
+ ]);
6802
+ const entry = entriesResponse.entries.at(0);
6803
+ if (!entry || entry.liveUntilLedgerSeq === void 0) {
6804
+ return false;
6805
+ }
6806
+ const remainingLedgers = entry.liveUntilLedgerSeq - latestLedger.sequence;
6807
+ return remainingLedgers < ZK_CONFIG_TTL_THRESHOLD_LEDGERS;
6808
+ }
6809
+ async function submitExtendFootprintTtlTransaction(parameters) {
6810
+ const sourceAccount = await parameters.server.getAccount(parameters.sourcePublicKey);
6811
+ const transaction = new TransactionBuilder2(sourceAccount, {
6812
+ fee: EXTEND_TTL_FEE_STROOPS,
6813
+ networkPassphrase: parameters.networkPassphrase
6814
+ }).addOperation(
6815
+ Operation2.extendFootprintTtl({ extendTo: ZK_CONFIG_TTL_EXTEND_TO_LEDGERS })
6816
+ ).setSorobanData(
6817
+ new SorobanDataBuilder().setReadOnly([parameters.ledgerKey]).build()
6818
+ ).setTimeout(EXTEND_TTL_TIMEOUT_SECONDS).build();
6819
+ const preparedTransaction = await parameters.server.prepareTransaction(transaction);
6820
+ const { signedTxXdr } = await parameters.signTransaction({
6821
+ xdr: preparedTransaction.toXDR(),
6822
+ networkPassphrase: parameters.networkPassphrase,
6823
+ address: parameters.sourcePublicKey
6824
+ });
6825
+ const signedTransaction = TransactionBuilder2.fromXDR(
6826
+ signedTxXdr,
6827
+ parameters.networkPassphrase
6828
+ );
6829
+ const result = await parameters.server.sendTransaction(signedTransaction);
6830
+ const status = String(result.status);
6831
+ if (status !== "PENDING" && status !== "SUCCESS") {
6832
+ throw new Error(`ZK config TTL extension submit failed: ${status}`);
6833
+ }
6834
+ await waitForExtendFootprintTtlConfirmation({
6835
+ server: parameters.server,
6836
+ hash: result.hash
6837
+ });
6838
+ }
6839
+ async function extendZkConfigTtlIfNeeded(parameters) {
6840
+ if (!parameters.signTransaction) {
6841
+ return;
6842
+ }
6843
+ const server = new rpc4.Server(parameters.sorobanRpcUrl, { allowHttp: true });
6844
+ const ledgerKey = zkConfigLedgerKey(parameters.contractId, parameters.nonce);
6845
+ if (!await needsTtlExtension({ server, ledgerKey })) {
6846
+ return;
6847
+ }
6848
+ await submitExtendFootprintTtlTransaction({
6849
+ server,
6850
+ ledgerKey,
6851
+ networkPassphrase: parameters.networkPassphrase,
6852
+ sourcePublicKey: parameters.sourcePublicKey,
6853
+ signTransaction: parameters.signTransaction
6854
+ });
6855
+ }
6856
+
6684
6857
  // src/transact/submit/pool-transact.ts
6685
6858
  import { Buffer as Buffer17 } from "buffer";
6686
6859
  async function submitPoolTransact(parameters) {
6860
+ await extendZkConfigTtlIfNeeded({
6861
+ sorobanRpcUrl: parameters.sorobanRpcUrl,
6862
+ networkPassphrase: parameters.networkPassphrase,
6863
+ contractId: parameters.contractId,
6864
+ nonce: parameters.nonce,
6865
+ sourcePublicKey: parameters.from,
6866
+ signTransaction: parameters.transactEnvironment.signTransaction
6867
+ });
6687
6868
  const assembledTransaction = await parameters.contractClient.transact({
6688
6869
  from: parameters.from,
6870
+ nonce: parameters.nonce,
6689
6871
  proof_bytes: Buffer17.from(parameters.proofHex.replace(/^0x/iu, ""), "hex"),
6690
6872
  pub_signals_bytes: Buffer17.from(parameters.publicHex.replace(/^0x/iu, ""), "hex"),
6691
6873
  onboarding: parameters.onboarding,
@@ -6724,11 +6906,16 @@ async function submitSorobanConfidentialTransfer(input) {
6724
6906
  });
6725
6907
  return submitPoolTransact({
6726
6908
  contractClient: poolClient,
6909
+ contractId: input.contractId,
6727
6910
  from: input.walletPublicKey,
6911
+ nonce: resolveZkConfigNonce(input.transactEnvironment),
6728
6912
  proofHex: input.proofHex,
6729
6913
  publicHex: input.publicHex,
6730
6914
  ...input.onboarding ? { onboarding: input.onboarding } : {},
6731
- approval
6915
+ approval,
6916
+ networkPassphrase: input.networkPassphrase,
6917
+ sorobanRpcUrl: input.sorobanRpcUrl,
6918
+ transactEnvironment: input.transactEnvironment
6732
6919
  });
6733
6920
  }
6734
6921
 
@@ -6757,10 +6944,15 @@ async function submitPreparedDeposit(prepared, environment) {
6757
6944
  });
6758
6945
  return submitPoolTransact({
6759
6946
  contractClient: poolClient,
6947
+ contractId: environment.network.poolContract,
6760
6948
  from: artifacts.walletPublicKey,
6949
+ nonce: resolveZkConfigNonce(environment),
6761
6950
  proofHex: artifacts.proofHex,
6762
6951
  publicHex: artifacts.publicHex,
6763
- approval
6952
+ approval,
6953
+ networkPassphrase: environment.network.networkPassphrase,
6954
+ sorobanRpcUrl: environment.network.rpcUrl,
6955
+ transactEnvironment: environment
6764
6956
  });
6765
6957
  }
6766
6958
  async function submitPreparedPoolTransact(prepared, environment) {
@@ -7009,10 +7201,15 @@ async function submitSorobanDeposit(input) {
7009
7201
  });
7010
7202
  return submitPoolTransact({
7011
7203
  contractClient: poolClient,
7204
+ contractId: input.contractId,
7012
7205
  from: input.walletPublicKey,
7206
+ nonce: resolveZkConfigNonce(input.transactEnvironment),
7013
7207
  proofHex: proof_hex,
7014
7208
  publicHex: public_hex,
7015
- approval
7209
+ approval,
7210
+ networkPassphrase: input.networkPassphrase,
7211
+ sorobanRpcUrl: input.sorobanRpcUrl,
7212
+ transactEnvironment: input.transactEnvironment
7016
7213
  });
7017
7214
  }
7018
7215
  export {