@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/node.d.ts CHANGED
@@ -229,6 +229,12 @@ interface StellarKytEnvironment {
229
229
  }
230
230
  interface StellarTransactEnvironment {
231
231
  network: StellarNetworkConfig;
232
+ /**
233
+ * Nonce (persistent storage key) of the pool's `ZkConfig` entry this
234
+ * environment's proofs/circuit shape target. Defaults to `0n` (the
235
+ * standard circuit) when omitted.
236
+ */
237
+ zkConfigNonce?: bigint;
232
238
  auditPublicKey?: AuditPublicKey;
233
239
  kyt: StellarKytEnvironment;
234
240
  signTransaction?: StellarSignTransaction;
package/dist/node.js CHANGED
@@ -504,7 +504,7 @@ function createInternalPoolContractClient(input) {
504
504
  networkPassphrase: input.networkPassphrase,
505
505
  walletPublicKey: input.walletPublicKey
506
506
  });
507
- return new PrivacyPoolContractClient({
507
+ const client = new PrivacyPoolContractClient({
508
508
  contractId: input.contractId,
509
509
  networkPassphrase: input.networkPassphrase,
510
510
  rpcUrl: input.sorobanRpcUrl,
@@ -512,6 +512,7 @@ function createInternalPoolContractClient(input) {
512
512
  publicKey: input.walletPublicKey,
513
513
  signTransaction: signBinding
514
514
  });
515
+ return client;
515
516
  }
516
517
 
517
518
  // src/contracts/registry/create-registry-client.ts
@@ -1696,7 +1697,7 @@ function pickRecordsForSpendAmount(input) {
1696
1697
  if (selectedTotal >= input.amount) {
1697
1698
  return selected;
1698
1699
  }
1699
- if (input.kind === "transfer" && selected.length >= 2) {
1700
+ if ((input.kind === "transfer" || input.kind === "withdraw") && selected.length >= 2) {
1700
1701
  throw insufficientStateError3(
1701
1702
  "missing_private_records",
1702
1703
  "Storage does not contain enough private records for this operation.",
@@ -6272,12 +6273,17 @@ function enrichWithdrawOutputRecords(prepared, proof) {
6272
6273
  precommitementHex: changeCoin.precommitementHex
6273
6274
  }));
6274
6275
  }
6275
- async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6276
- if (prepared.kind !== "withdraw") {
6277
- throw new Error("Withdraw finalize requires a withdraw operation.");
6278
- }
6279
- const withdrawIntent = prepared.intent;
6280
- const withdrawFrom = withdrawIntent.from;
6276
+ function buildWithdrawFinalizeArtifacts(proof, context) {
6277
+ return {
6278
+ proofHex: proof.proof_hex,
6279
+ publicHex: proof.public_hex,
6280
+ applicationIdsPlaintext: proof.applicationIdsPlaintext,
6281
+ tokenAddress: context.tokenAddress,
6282
+ walletPublicKey: context.walletPublicKey,
6283
+ executeFinalizeRequired: false
6284
+ };
6285
+ }
6286
+ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
6281
6287
  const context = await buildSpendProofContextAtExecute({
6282
6288
  prepared,
6283
6289
  environment,
@@ -6298,14 +6304,71 @@ async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6298
6304
  tokenAddress: context.tokenAddress
6299
6305
  });
6300
6306
  enrichWithdrawOutputRecords(prepared, proof);
6301
- return {
6302
- proofHex: proof.proof_hex,
6303
- publicHex: proof.public_hex,
6304
- applicationIdsPlaintext: proof.applicationIdsPlaintext,
6305
- tokenAddress: context.tokenAddress,
6307
+ return buildWithdrawFinalizeArtifacts(proof, context);
6308
+ }
6309
+ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService, withdrawFrom) {
6310
+ const [, secondaryRecord] = prepared.consumedRecords;
6311
+ if (!secondaryRecord) {
6312
+ throw new Error("Dual withdraw execution requires two private input records.");
6313
+ }
6314
+ const context = await buildSpendProofContextAtExecute({
6315
+ prepared,
6316
+ environment,
6317
+ recipientPrivateAddressStpl1: withdrawFrom
6318
+ });
6319
+ const secondary = await readCoinEphemeralForRecord({
6320
+ record: secondaryRecord,
6321
+ environment,
6306
6322
  walletPublicKey: context.walletPublicKey,
6307
- executeFinalizeRequired: false
6308
- };
6323
+ commitments: context.commitments
6324
+ });
6325
+ const totalNotes = BigInt(context.coin.value) + BigInt(secondary.coin.value);
6326
+ const changeStroops = totalNotes - prepared.intent.amount;
6327
+ const proof = await poolService.prepareWithdrawTransactProofDual({
6328
+ coinA: context.coin,
6329
+ coinB: secondary.coin,
6330
+ state: { commitments: context.commitments },
6331
+ destinationStellarAddress: prepared.intent.to,
6332
+ privKeyScalarHex: context.senderPrivKeyScalarHex,
6333
+ depositorEphemeralAKey: serializeEphemeralKeyString({
6334
+ xHex: context.ephemeral.xHex,
6335
+ yHex: context.ephemeral.yHex
6336
+ }),
6337
+ depositorEphemeralBKey: serializeEphemeralKeyString({
6338
+ xHex: secondary.ephemeral.xHex,
6339
+ yHex: secondary.ephemeral.yHex
6340
+ }),
6341
+ withdrawAmountStroops: prepared.intent.amount,
6342
+ changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6343
+ tokenAddress: context.tokenAddress
6344
+ });
6345
+ enrichWithdrawOutputRecords(prepared, proof);
6346
+ return buildWithdrawFinalizeArtifacts(proof, context);
6347
+ }
6348
+ async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6349
+ if (prepared.kind !== "withdraw") {
6350
+ throw new Error("Withdraw finalize requires a withdraw operation.");
6351
+ }
6352
+ const withdrawIntent = prepared.intent;
6353
+ const withdrawFrom = withdrawIntent.from;
6354
+ const recordCount = prepared.consumedRecords.length;
6355
+ if (recordCount === 1) {
6356
+ return finalizeSingleWithdrawAtExecute(
6357
+ prepared,
6358
+ environment,
6359
+ poolService,
6360
+ withdrawFrom
6361
+ );
6362
+ }
6363
+ if (recordCount === 2) {
6364
+ return finalizeDualWithdrawAtExecute(
6365
+ prepared,
6366
+ environment,
6367
+ poolService,
6368
+ withdrawFrom
6369
+ );
6370
+ }
6371
+ throw new Error("Confidential withdraw execution supports at most two input notes.");
6309
6372
  }
6310
6373
  async function finalizeSpendOperationAtExecute(prepared, environment, poolService) {
6311
6374
  if (prepared.kind !== "transfer" && prepared.kind !== "withdraw") {
@@ -6463,6 +6526,12 @@ async function registerKytPassage(parameters) {
6463
6526
  await waitForKytRegistration({ server, hash: result.hash });
6464
6527
  }
6465
6528
 
6529
+ // src/transact/environment/zk-config-nonce.ts
6530
+ var DEFAULT_ZK_CONFIG_NONCE = 0n;
6531
+ function resolveZkConfigNonce(transactEnvironment) {
6532
+ return transactEnvironment.zkConfigNonce ?? DEFAULT_ZK_CONFIG_NONCE;
6533
+ }
6534
+
6466
6535
  // src/transact/kyt/passage/inspect.ts
6467
6536
  function buildApplicationIdHints(applicationId) {
6468
6537
  return [applicationId, applicationId, applicationId, applicationId];
@@ -6516,6 +6585,7 @@ async function requestKytPassageForPoolInteraction(input) {
6516
6585
  decryptedAuditSlots: input.decryptedAuditSlots,
6517
6586
  onboarding: input.onboarding,
6518
6587
  nonce: buildNonce(),
6588
+ zkConfigNonce: resolveZkConfigNonce(input.transactEnvironment),
6519
6589
  currentLedger
6520
6590
  },
6521
6591
  kytJsonReplacer
@@ -6538,11 +6608,123 @@ async function requestKytPassageForPoolInteraction(input) {
6538
6608
  return body;
6539
6609
  }
6540
6610
 
6611
+ // src/transact/submit/zk-config-ttl.ts
6612
+ import {
6613
+ Address as Address2,
6614
+ Operation as Operation2,
6615
+ SorobanDataBuilder,
6616
+ TransactionBuilder as TransactionBuilder2,
6617
+ rpc as rpc4,
6618
+ xdr as xdr3
6619
+ } from "@stellar/stellar-sdk";
6620
+ var ZK_CONFIG_TTL_THRESHOLD_LEDGERS = 1e5;
6621
+ var ZK_CONFIG_TTL_EXTEND_TO_LEDGERS = 5e5;
6622
+ var EXTEND_TTL_FEE_STROOPS = "1000000";
6623
+ var EXTEND_TTL_TIMEOUT_SECONDS = 300;
6624
+ var EXTEND_TTL_MAX_POLLS = 60;
6625
+ var EXTEND_TTL_POLL_INTERVAL_MS = 1e3;
6626
+ async function waitForExtendFootprintTtlConfirmation(parameters) {
6627
+ for (let attempt = 0; attempt < EXTEND_TTL_MAX_POLLS; attempt += 1) {
6628
+ const response = await parameters.server.getTransaction(parameters.hash);
6629
+ if (response.status === "SUCCESS") {
6630
+ return;
6631
+ }
6632
+ if (response.status === "FAILED") {
6633
+ throw new Error("ZK config TTL extension failed on-chain");
6634
+ }
6635
+ await new Promise((resolve) => {
6636
+ setTimeout(resolve, EXTEND_TTL_POLL_INTERVAL_MS);
6637
+ });
6638
+ }
6639
+ throw new Error("ZK config TTL extension was not confirmed in time");
6640
+ }
6641
+ function zkConfigLedgerKey(contractId, nonce) {
6642
+ const configKeyScValue = xdr3.ScVal.scvVec([
6643
+ xdr3.ScVal.scvSymbol("Config"),
6644
+ xdr3.ScVal.scvU64(xdr3.Uint64.fromString(nonce.toString()))
6645
+ ]);
6646
+ return xdr3.LedgerKey.contractData(
6647
+ new xdr3.LedgerKeyContractData({
6648
+ contract: new Address2(contractId).toScAddress(),
6649
+ key: configKeyScValue,
6650
+ durability: xdr3.ContractDataDurability.persistent()
6651
+ })
6652
+ );
6653
+ }
6654
+ async function needsTtlExtension(parameters) {
6655
+ const [entriesResponse, latestLedger] = await Promise.all([
6656
+ parameters.server.getLedgerEntries(parameters.ledgerKey),
6657
+ parameters.server.getLatestLedger()
6658
+ ]);
6659
+ const entry = entriesResponse.entries.at(0);
6660
+ if (!entry || entry.liveUntilLedgerSeq === void 0) {
6661
+ return false;
6662
+ }
6663
+ const remainingLedgers = entry.liveUntilLedgerSeq - latestLedger.sequence;
6664
+ return remainingLedgers < ZK_CONFIG_TTL_THRESHOLD_LEDGERS;
6665
+ }
6666
+ async function submitExtendFootprintTtlTransaction(parameters) {
6667
+ const sourceAccount = await parameters.server.getAccount(parameters.sourcePublicKey);
6668
+ const transaction = new TransactionBuilder2(sourceAccount, {
6669
+ fee: EXTEND_TTL_FEE_STROOPS,
6670
+ networkPassphrase: parameters.networkPassphrase
6671
+ }).addOperation(
6672
+ Operation2.extendFootprintTtl({ extendTo: ZK_CONFIG_TTL_EXTEND_TO_LEDGERS })
6673
+ ).setSorobanData(
6674
+ new SorobanDataBuilder().setReadOnly([parameters.ledgerKey]).build()
6675
+ ).setTimeout(EXTEND_TTL_TIMEOUT_SECONDS).build();
6676
+ const preparedTransaction = await parameters.server.prepareTransaction(transaction);
6677
+ const { signedTxXdr } = await parameters.signTransaction({
6678
+ xdr: preparedTransaction.toXDR(),
6679
+ networkPassphrase: parameters.networkPassphrase,
6680
+ address: parameters.sourcePublicKey
6681
+ });
6682
+ const signedTransaction = TransactionBuilder2.fromXDR(
6683
+ signedTxXdr,
6684
+ parameters.networkPassphrase
6685
+ );
6686
+ const result = await parameters.server.sendTransaction(signedTransaction);
6687
+ const status = String(result.status);
6688
+ if (status !== "PENDING" && status !== "SUCCESS") {
6689
+ throw new Error(`ZK config TTL extension submit failed: ${status}`);
6690
+ }
6691
+ await waitForExtendFootprintTtlConfirmation({
6692
+ server: parameters.server,
6693
+ hash: result.hash
6694
+ });
6695
+ }
6696
+ async function extendZkConfigTtlIfNeeded(parameters) {
6697
+ if (!parameters.signTransaction) {
6698
+ return;
6699
+ }
6700
+ const server = new rpc4.Server(parameters.sorobanRpcUrl, { allowHttp: true });
6701
+ const ledgerKey = zkConfigLedgerKey(parameters.contractId, parameters.nonce);
6702
+ if (!await needsTtlExtension({ server, ledgerKey })) {
6703
+ return;
6704
+ }
6705
+ await submitExtendFootprintTtlTransaction({
6706
+ server,
6707
+ ledgerKey,
6708
+ networkPassphrase: parameters.networkPassphrase,
6709
+ sourcePublicKey: parameters.sourcePublicKey,
6710
+ signTransaction: parameters.signTransaction
6711
+ });
6712
+ }
6713
+
6541
6714
  // src/transact/submit/pool-transact.ts
6542
6715
  import { Buffer as Buffer17 } from "buffer";
6543
6716
  async function submitPoolTransact(parameters) {
6717
+ await extendZkConfigTtlIfNeeded({
6718
+ sorobanRpcUrl: parameters.sorobanRpcUrl,
6719
+ networkPassphrase: parameters.networkPassphrase,
6720
+ contractId: parameters.contractId,
6721
+ nonce: parameters.nonce,
6722
+ sourcePublicKey: parameters.from,
6723
+ signTransaction: parameters.transactEnvironment.signTransaction
6724
+ });
6544
6725
  const assembledTransaction = await parameters.contractClient.transact({
6545
6726
  from: parameters.from,
6727
+ nonce: parameters.nonce,
6546
6728
  proof_bytes: Buffer17.from(parameters.proofHex.replace(/^0x/iu, ""), "hex"),
6547
6729
  pub_signals_bytes: Buffer17.from(parameters.publicHex.replace(/^0x/iu, ""), "hex"),
6548
6730
  onboarding: parameters.onboarding,
@@ -6581,11 +6763,16 @@ async function submitSorobanConfidentialTransfer(input) {
6581
6763
  });
6582
6764
  return submitPoolTransact({
6583
6765
  contractClient: poolClient,
6766
+ contractId: input.contractId,
6584
6767
  from: input.walletPublicKey,
6768
+ nonce: resolveZkConfigNonce(input.transactEnvironment),
6585
6769
  proofHex: input.proofHex,
6586
6770
  publicHex: input.publicHex,
6587
6771
  ...input.onboarding ? { onboarding: input.onboarding } : {},
6588
- approval
6772
+ approval,
6773
+ networkPassphrase: input.networkPassphrase,
6774
+ sorobanRpcUrl: input.sorobanRpcUrl,
6775
+ transactEnvironment: input.transactEnvironment
6589
6776
  });
6590
6777
  }
6591
6778
 
@@ -6614,10 +6801,15 @@ async function submitPreparedDeposit(prepared, environment) {
6614
6801
  });
6615
6802
  return submitPoolTransact({
6616
6803
  contractClient: poolClient,
6804
+ contractId: environment.network.poolContract,
6617
6805
  from: artifacts.walletPublicKey,
6806
+ nonce: resolveZkConfigNonce(environment),
6618
6807
  proofHex: artifacts.proofHex,
6619
6808
  publicHex: artifacts.publicHex,
6620
- approval
6809
+ approval,
6810
+ networkPassphrase: environment.network.networkPassphrase,
6811
+ sorobanRpcUrl: environment.network.rpcUrl,
6812
+ transactEnvironment: environment
6621
6813
  });
6622
6814
  }
6623
6815
  async function submitPreparedPoolTransact(prepared, environment) {