@arcanetech/privacy-sdk-stellar 0.2.0 → 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.js CHANGED
@@ -291,11 +291,14 @@ async function commitStorageChanges(input, eventId, receipt) {
291
291
  });
292
292
  if (input.prepared.outputRecords.length > 0) {
293
293
  await input.storage.savePrivateRecords(
294
- input.prepared.outputRecords.map((record) => ({
295
- ...record,
296
- txHash: receipt.operationId,
297
- status: receipt.confirmed ? "finalized" : record.status
298
- }))
294
+ input.prepared.outputRecords.map((record) => {
295
+ const nextStatus = receipt.confirmed ? "finalized" : record.status;
296
+ return {
297
+ ...record,
298
+ txHash: receipt.operationId,
299
+ ...nextStatus === void 0 ? {} : { status: nextStatus }
300
+ };
301
+ })
299
302
  );
300
303
  }
301
304
  if (input.prepared.consumedRecords.length > 0) {
@@ -351,7 +354,10 @@ import { insufficientStateError as insufficientStateError3 } from "@arcanetech/p
351
354
 
352
355
  // src/transact/transfer-source/resolve.ts
353
356
  import { Buffer as Buffer2 } from "buffer";
354
- import { insufficientStateError, invalidIntentError } from "@arcanetech/privacy-sdk-core";
357
+ import {
358
+ insufficientStateError,
359
+ invalidIntentError
360
+ } from "@arcanetech/privacy-sdk-core";
355
361
  var FIELD_HEX_LENGTH = 64;
356
362
  var SCALAR_HEX_PATTERN = new RegExp(`^(0x)?[0-9a-f]{${FIELD_HEX_LENGTH}}$`, "iu");
357
363
  function hexToDecimal(hex) {
@@ -384,6 +390,7 @@ function pendingClaimToCoin(claim) {
384
390
  }
385
391
  function pendingClaimToConsumedRecord(input) {
386
392
  const coin = pendingClaimToCoin(input.claim);
393
+ const nullifierHex = input.claim.nullifierHex ?? input.claim.futureNullifierHashHex;
387
394
  return {
388
395
  id: `pending-claim-${input.claim.id}`,
389
396
  owner: input.walletPublicKey.trim(),
@@ -392,9 +399,9 @@ function pendingClaimToConsumedRecord(input) {
392
399
  consumed: false,
393
400
  status: "finalized",
394
401
  poolContract: input.poolContract,
395
- commitmentHex: input.claim.commitmentHex,
396
- nullifierHex: input.claim.nullifierHex ?? input.claim.futureNullifierHashHex,
397
- coinNote: coin
402
+ coinNote: coin,
403
+ ...input.claim.commitmentHex ? { commitmentHex: input.claim.commitmentHex } : {},
404
+ ...nullifierHex ? { nullifierHex } : {}
398
405
  };
399
406
  }
400
407
  function readPendingClaimFromSource(source) {
@@ -497,7 +504,7 @@ function createInternalPoolContractClient(input) {
497
504
  networkPassphrase: input.networkPassphrase,
498
505
  walletPublicKey: input.walletPublicKey
499
506
  });
500
- return new PrivacyPoolContractClient({
507
+ const client = new PrivacyPoolContractClient({
501
508
  contractId: input.contractId,
502
509
  networkPassphrase: input.networkPassphrase,
503
510
  rpcUrl: input.sorobanRpcUrl,
@@ -505,6 +512,7 @@ function createInternalPoolContractClient(input) {
505
512
  publicKey: input.walletPublicKey,
506
513
  signTransaction: signBinding
507
514
  });
515
+ return client;
508
516
  }
509
517
 
510
518
  // src/contracts/registry/create-registry-client.ts
@@ -1689,7 +1697,7 @@ function pickRecordsForSpendAmount(input) {
1689
1697
  if (selectedTotal >= input.amount) {
1690
1698
  return selected;
1691
1699
  }
1692
- if (input.kind === "transfer" && selected.length >= 2) {
1700
+ if ((input.kind === "transfer" || input.kind === "withdraw") && selected.length >= 2) {
1693
1701
  throw insufficientStateError3(
1694
1702
  "missing_private_records",
1695
1703
  "Storage does not contain enough private records for this operation.",
@@ -1787,7 +1795,7 @@ async function resolvePendingClaimSpend(intent, deps, walletPublicKey) {
1787
1795
  await assertPendingClaimNullifierAvailable({
1788
1796
  claim,
1789
1797
  walletPublicKey,
1790
- checkNullifierSpent: deps.checkNullifierSpent
1798
+ ...deps.checkNullifierSpent ? { checkNullifierSpent: deps.checkNullifierSpent } : {}
1791
1799
  });
1792
1800
  validatePendingClaimIntentMatches(claim, intent);
1793
1801
  return {
@@ -1813,12 +1821,12 @@ async function resolvePrivateAddressSpend(intent, deps) {
1813
1821
  kind: "transfer",
1814
1822
  intent,
1815
1823
  walletPublicKey,
1816
- checkNullifierSpent: deps.checkNullifierSpent
1824
+ ...deps.checkNullifierSpent ? { checkNullifierSpent: deps.checkNullifierSpent } : {}
1817
1825
  });
1818
1826
  await assertPrivateRecordsNullifiersAvailable({
1819
1827
  records: consumedRecords,
1820
1828
  walletPublicKey: walletAddress.trim(),
1821
- checkNullifierSpent: deps.checkNullifierSpent
1829
+ ...deps.checkNullifierSpent ? { checkNullifierSpent: deps.checkNullifierSpent } : {}
1822
1830
  });
1823
1831
  return {
1824
1832
  consumedRecords,
@@ -1900,7 +1908,7 @@ async function resolvePrepareConsumedRecords(input) {
1900
1908
  storage: input.storage,
1901
1909
  intent: input.intent,
1902
1910
  walletPublicKey: await loadSpendSelectionWalletPublicKey(input.wallet),
1903
- checkNullifierSpent: input.checkNullifierSpent
1911
+ ...input.checkNullifierSpent ? { checkNullifierSpent: input.checkNullifierSpent } : {}
1904
1912
  });
1905
1913
  }
1906
1914
  function buildDepositOrWithdrawPrepared(input) {
@@ -5470,7 +5478,8 @@ async function resolveWalletPublicKey(environment, prepared) {
5470
5478
  return environment.resolveWalletPublicKey();
5471
5479
  }
5472
5480
  if (prepared.kind === "deposit") {
5473
- return prepared.intent.from;
5481
+ const depositIntent = prepared.intent;
5482
+ return depositIntent.from;
5474
5483
  }
5475
5484
  throw new Error("Wallet public key resolver is not configured.");
5476
5485
  }
@@ -6264,11 +6273,21 @@ function enrichWithdrawOutputRecords(prepared, proof) {
6264
6273
  precommitementHex: changeCoin.precommitementHex
6265
6274
  }));
6266
6275
  }
6267
- async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
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) {
6268
6287
  const context = await buildSpendProofContextAtExecute({
6269
6288
  prepared,
6270
6289
  environment,
6271
- recipientPrivateAddressStpl1: prepared.intent.from
6290
+ recipientPrivateAddressStpl1: withdrawFrom
6272
6291
  });
6273
6292
  const changeStroops = BigInt(context.coin.value) - prepared.intent.amount;
6274
6293
  const proof = await poolService.prepareWithdrawTransactProof({
@@ -6281,18 +6300,75 @@ async function finalizeWithdrawAtExecute(prepared, environment, poolService) {
6281
6300
  yHex: context.ephemeral.yHex
6282
6301
  }),
6283
6302
  withdrawAmountStroops: prepared.intent.amount,
6284
- changePrivateAddressStpl1: changeStroops > 0n ? prepared.intent.from : void 0,
6303
+ changePrivateAddressStpl1: changeStroops > 0n ? withdrawFrom : void 0,
6285
6304
  tokenAddress: context.tokenAddress
6286
6305
  });
6287
6306
  enrichWithdrawOutputRecords(prepared, proof);
6288
- return {
6289
- proofHex: proof.proof_hex,
6290
- publicHex: proof.public_hex,
6291
- applicationIdsPlaintext: proof.applicationIdsPlaintext,
6292
- 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,
6293
6322
  walletPublicKey: context.walletPublicKey,
6294
- executeFinalizeRequired: false
6295
- };
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.");
6296
6372
  }
6297
6373
  async function finalizeSpendOperationAtExecute(prepared, environment, poolService) {
6298
6374
  if (prepared.kind !== "transfer" && prepared.kind !== "withdraw") {
@@ -6450,6 +6526,12 @@ async function registerKytPassage(parameters) {
6450
6526
  await waitForKytRegistration({ server, hash: result.hash });
6451
6527
  }
6452
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
+
6453
6535
  // src/transact/kyt/passage/inspect.ts
6454
6536
  function buildApplicationIdHints(applicationId) {
6455
6537
  return [applicationId, applicationId, applicationId, applicationId];
@@ -6503,6 +6585,7 @@ async function requestKytPassageForPoolInteraction(input) {
6503
6585
  decryptedAuditSlots: input.decryptedAuditSlots,
6504
6586
  onboarding: input.onboarding,
6505
6587
  nonce: buildNonce(),
6588
+ zkConfigNonce: resolveZkConfigNonce(input.transactEnvironment),
6506
6589
  currentLedger
6507
6590
  },
6508
6591
  kytJsonReplacer
@@ -6525,11 +6608,123 @@ async function requestKytPassageForPoolInteraction(input) {
6525
6608
  return body;
6526
6609
  }
6527
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
+
6528
6714
  // src/transact/submit/pool-transact.ts
6529
6715
  import { Buffer as Buffer17 } from "buffer";
6530
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
+ });
6531
6725
  const assembledTransaction = await parameters.contractClient.transact({
6532
6726
  from: parameters.from,
6727
+ nonce: parameters.nonce,
6533
6728
  proof_bytes: Buffer17.from(parameters.proofHex.replace(/^0x/iu, ""), "hex"),
6534
6729
  pub_signals_bytes: Buffer17.from(parameters.publicHex.replace(/^0x/iu, ""), "hex"),
6535
6730
  onboarding: parameters.onboarding,
@@ -6568,11 +6763,16 @@ async function submitSorobanConfidentialTransfer(input) {
6568
6763
  });
6569
6764
  return submitPoolTransact({
6570
6765
  contractClient: poolClient,
6766
+ contractId: input.contractId,
6571
6767
  from: input.walletPublicKey,
6768
+ nonce: resolveZkConfigNonce(input.transactEnvironment),
6572
6769
  proofHex: input.proofHex,
6573
6770
  publicHex: input.publicHex,
6574
6771
  ...input.onboarding ? { onboarding: input.onboarding } : {},
6575
- approval
6772
+ approval,
6773
+ networkPassphrase: input.networkPassphrase,
6774
+ sorobanRpcUrl: input.sorobanRpcUrl,
6775
+ transactEnvironment: input.transactEnvironment
6576
6776
  });
6577
6777
  }
6578
6778
 
@@ -6601,10 +6801,15 @@ async function submitPreparedDeposit(prepared, environment) {
6601
6801
  });
6602
6802
  return submitPoolTransact({
6603
6803
  contractClient: poolClient,
6804
+ contractId: environment.network.poolContract,
6604
6805
  from: artifacts.walletPublicKey,
6806
+ nonce: resolveZkConfigNonce(environment),
6605
6807
  proofHex: artifacts.proofHex,
6606
6808
  publicHex: artifacts.publicHex,
6607
- approval
6809
+ approval,
6810
+ networkPassphrase: environment.network.networkPassphrase,
6811
+ sorobanRpcUrl: environment.network.rpcUrl,
6812
+ transactEnvironment: environment
6608
6813
  });
6609
6814
  }
6610
6815
  async function submitPreparedPoolTransact(prepared, environment) {