@arcanetech/privacy-sdk-stellar 0.5.4 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -452,6 +452,261 @@ function buildPrivateRecordFromDeposit(input) {
452
452
  };
453
453
  }
454
454
 
455
+ // src/transact/merkle/lean-imt-api.ts
456
+ function asLeanImtSessionApi(sdk) {
457
+ const candidate = sdk;
458
+ if (typeof candidate.importLeanImtFromState !== "function") {
459
+ return void 0;
460
+ }
461
+ if (typeof candidate.insertTwoLeanImt !== "function") {
462
+ return void 0;
463
+ }
464
+ if (typeof candidate.exportLeanImt !== "function") {
465
+ return void 0;
466
+ }
467
+ if (typeof candidate.dropLeanImt !== "function") {
468
+ return void 0;
469
+ }
470
+ if (typeof candidate.buildWithdrawMerkleWitnessFromHandle !== "function") {
471
+ return void 0;
472
+ }
473
+ return candidate;
474
+ }
475
+
476
+ // src/transact/merkle/field-decimal.ts
477
+ import { Buffer as Buffer4 } from "buffer";
478
+ var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
479
+ var MERKLE_ROOT_BYTE_LENGTH = 32;
480
+ var DECIMAL_STRING_RADIX = 10;
481
+ function merkleRootBufferToFrDecimal(root) {
482
+ if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
483
+ throw new Error(
484
+ `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
485
+ );
486
+ }
487
+ const hex = Buffer4.from(root).toString("hex");
488
+ return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
489
+ }
490
+ var MERKLE_ROOT_HEX_PATTERN = /^[0-9a-fA-F]{64}$/;
491
+ function merkleRootHexToFrDecimal(merkleRootHex) {
492
+ const trimmed = merkleRootHex.trim();
493
+ if (!MERKLE_ROOT_HEX_PATTERN.test(trimmed)) {
494
+ return void 0;
495
+ }
496
+ return merkleRootBufferToFrDecimal(Buffer4.from(trimmed, "hex"));
497
+ }
498
+
499
+ // src/transact/merkle/state-file.ts
500
+ function merkleSnapshotToStateFile(input) {
501
+ const root = merkleRootHexToFrDecimal(input.merkleRootHex);
502
+ const nodes = input.nodes;
503
+ if (!nodes || nodes.length === 0 || root === void 0) {
504
+ return { commitments: input.commitments };
505
+ }
506
+ return {
507
+ commitments: input.commitments,
508
+ nodes,
509
+ root
510
+ };
511
+ }
512
+
513
+ // src/transact/merkle/extend-snapshot.ts
514
+ function isEvenLeafCount(count) {
515
+ return count % 2 === 0;
516
+ }
517
+ function importHandle(sdk, state) {
518
+ try {
519
+ return sdk.importLeanImtFromState(state);
520
+ } catch {
521
+ return void 0;
522
+ }
523
+ }
524
+ function insertNewPairs(input) {
525
+ for (let index = input.prefixLength; index < input.commitments.length; index += 2) {
526
+ const leafA = input.commitments.at(index);
527
+ const leafB = input.commitments.at(index + 1);
528
+ if (leafA === void 0 || leafB === void 0) {
529
+ return false;
530
+ }
531
+ try {
532
+ input.sdk.insertTwoLeanImt(input.handle, leafA, leafB);
533
+ } catch {
534
+ return false;
535
+ }
536
+ }
537
+ return true;
538
+ }
539
+ function snapshotNodes(sdk, handle) {
540
+ try {
541
+ return sdk.exportLeanImt(handle).nodes;
542
+ } catch {
543
+ return void 0;
544
+ }
545
+ }
546
+ function prefixSnapshotState(input) {
547
+ const prefixLength = input.cachedState?.nodes ? input.cachedState.commitments.length : 0;
548
+ return merkleSnapshotToStateFile({
549
+ commitments: input.commitments.slice(0, prefixLength),
550
+ merkleRootHex: input.cachedState?.merkleRootHex ?? input.merkleRootHex,
551
+ ...input.cachedState?.nodes ? { nodes: input.cachedState.nodes } : {}
552
+ });
553
+ }
554
+ function importSnapshotState(input) {
555
+ if (input.prefixState.nodes && input.prefixState.commitments.length > 0) {
556
+ return input.prefixState;
557
+ }
558
+ return merkleSnapshotToStateFile({
559
+ commitments: input.commitments,
560
+ merkleRootHex: input.merkleRootHex
561
+ });
562
+ }
563
+ function exportExtendedNodes(input) {
564
+ const inserted = input.prefixState.nodes ? insertNewPairs({
565
+ sdk: input.sdk,
566
+ handle: input.handle,
567
+ prefixLength: input.prefixState.commitments.length,
568
+ commitments: input.commitments
569
+ }) : true;
570
+ if (!inserted) {
571
+ input.sdk.dropLeanImt(input.handle);
572
+ return void 0;
573
+ }
574
+ const nodes = snapshotNodes(input.sdk, input.handle);
575
+ if (!nodes) {
576
+ input.sdk.dropLeanImt(input.handle);
577
+ }
578
+ return nodes;
579
+ }
580
+ function extendMerkleSnapshotNodes(input) {
581
+ if (!isEvenLeafCount(input.commitments.length)) {
582
+ return {};
583
+ }
584
+ const prefixState = prefixSnapshotState(input);
585
+ const handle = importHandle(
586
+ input.sdk,
587
+ importSnapshotState({ ...input, prefixState })
588
+ );
589
+ if (handle === void 0) {
590
+ return {};
591
+ }
592
+ const nodes = exportExtendedNodes({
593
+ sdk: input.sdk,
594
+ handle,
595
+ prefixState,
596
+ commitments: input.commitments
597
+ });
598
+ if (!nodes) {
599
+ return {};
600
+ }
601
+ return { nodes, handle };
602
+ }
603
+
604
+ // src/transact/merkle/tree-session.ts
605
+ var merkleSdkHost;
606
+ function registerMerkleSessionSdkHost(host) {
607
+ merkleSdkHost = host;
608
+ }
609
+ async function loadSessionSdk() {
610
+ const host = merkleSdkHost;
611
+ if (typeof host?.getInitializedSdk !== "function") {
612
+ return void 0;
613
+ }
614
+ try {
615
+ return asLeanImtSessionApi(await host.getInitializedSdk());
616
+ } catch {
617
+ return void 0;
618
+ }
619
+ }
620
+ var sessionsByPool = /* @__PURE__ */ new Map();
621
+ var handlesByKey = /* @__PURE__ */ new Map();
622
+ function merkleSessionKey(state) {
623
+ const root = state.root?.trim();
624
+ if (root) {
625
+ return `${state.commitments.length}:${root}`;
626
+ }
627
+ const first = state.commitments[0] ?? "";
628
+ const last = state.commitments.at(-1) ?? "";
629
+ return `${state.commitments.length}:${first}:${last}`;
630
+ }
631
+ function rememberHandle(poolContract, key, handle) {
632
+ const existing = sessionsByPool.get(poolContract);
633
+ if (existing && existing.handle !== handle) {
634
+ handlesByKey.delete(existing.key);
635
+ }
636
+ sessionsByPool.set(poolContract, { handle, key });
637
+ handlesByKey.set(key, handle);
638
+ }
639
+ function importHandle2(sdk, state) {
640
+ try {
641
+ return sdk.importLeanImtFromState(state);
642
+ } catch {
643
+ return void 0;
644
+ }
645
+ }
646
+ function merkleTreeHandleForState(state) {
647
+ return handlesByKey.get(merkleSessionKey(state));
648
+ }
649
+ function bindImportedMerkleTree(input) {
650
+ rememberHandle(input.poolContract, merkleSessionKey(input.state), input.handle);
651
+ }
652
+ async function bindPoolMerkleTree(state) {
653
+ const sdk = await loadSessionSdk();
654
+ if (!sdk) {
655
+ return void 0;
656
+ }
657
+ const snapshot = merkleSnapshotToStateFile(state);
658
+ const key = merkleSessionKey(snapshot);
659
+ const existing = sessionsByPool.get(state.poolContract);
660
+ if (existing?.key === key) {
661
+ return existing.handle;
662
+ }
663
+ const handle = importHandle2(sdk, snapshot);
664
+ if (handle === void 0) {
665
+ return void 0;
666
+ }
667
+ if (existing) {
668
+ sdk.dropLeanImt(existing.handle);
669
+ }
670
+ rememberHandle(state.poolContract, key, handle);
671
+ return handle;
672
+ }
673
+ async function resolveExtendedMerkleNodes(input) {
674
+ const cached = input.cachedState;
675
+ if (cached && cached.commitments.length === input.commitments.length && cached.merkleRootHex === input.merkleRootHex) {
676
+ return cached.nodes;
677
+ }
678
+ const sdk = await loadSessionSdk();
679
+ if (!sdk) {
680
+ return void 0;
681
+ }
682
+ const extended = extendMerkleSnapshotNodes({
683
+ sdk,
684
+ commitments: input.commitments,
685
+ merkleRootHex: input.merkleRootHex,
686
+ ...cached ? { cachedState: cached } : {}
687
+ });
688
+ if (extended.handle !== void 0) {
689
+ bindImportedMerkleTree({
690
+ poolContract: input.poolContract,
691
+ handle: extended.handle,
692
+ state: merkleSnapshotToStateFile({
693
+ commitments: input.commitments,
694
+ merkleRootHex: input.merkleRootHex,
695
+ ...extended.nodes ? { nodes: extended.nodes } : {}
696
+ })
697
+ });
698
+ }
699
+ return extended.nodes;
700
+ }
701
+ function withdrawMerkleWitnessFromTree(input) {
702
+ const session = asLeanImtSessionApi(input.sdk);
703
+ const handle = merkleTreeHandleForState(input.state);
704
+ if (session && handle !== void 0) {
705
+ return session.buildWithdrawMerkleWitnessFromHandle(input.coin, handle);
706
+ }
707
+ return input.sdk.buildWithdrawMerkleWitness(input.coin, input.state);
708
+ }
709
+
455
710
  // src/transact/pool/service.ts
456
711
  import {
457
712
  decodeDecodedEphemeralKey
@@ -512,18 +767,18 @@ function privateAddressSdk(sdk) {
512
767
  import { withdrawObjectFromMerkleWitness as withdrawObjectFromMerkleWitness2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
513
768
 
514
769
  // src/transact/proofs/transaction-input.ts
515
- import { Buffer as Buffer5 } from "buffer";
770
+ import { Buffer as Buffer6 } from "buffer";
516
771
  import { StrKey as StrKey2 } from "@stellar/stellar-sdk";
517
772
 
518
773
  // src/transact/stellar/token-contract.ts
519
- import { Buffer as Buffer4 } from "buffer";
774
+ import { Buffer as Buffer5 } from "buffer";
520
775
  import { StrKey } from "@stellar/stellar-sdk";
521
776
  var CONTRACT_ID_BYTE_LENGTH = 32;
522
777
  var HEX_CHARS_PER_BYTE = 2;
523
778
  var CONTRACT_ID_HEX_CHAR_LENGTH = CONTRACT_ID_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
524
779
  var CONTRACT_ID_HEX = /^(?:0x)?([0-9a-f]{64})$/i;
525
780
  function encodeContractFromHex(hexPayload) {
526
- return StrKey.encodeContract(Buffer4.from(hexPayload, "hex"));
781
+ return StrKey.encodeContract(Buffer5.from(hexPayload, "hex"));
527
782
  }
528
783
  function normalizeSorobanContractId(raw) {
529
784
  const trimmed = raw.trim();
@@ -556,14 +811,14 @@ var ZERO_PUBLIC_WITHDRAW_ADDRESS = {
556
811
  var CONTRACT_ADDRESS_HALF_BYTES = 16;
557
812
  var CONTRACT_ADDRESS_BYTES = 32;
558
813
  function bytes16ToDecimal(bytes) {
559
- return BigInt(`0x${Buffer5.from(bytes).toString("hex")}`).toString();
814
+ return BigInt(`0x${Buffer6.from(bytes).toString("hex")}`).toString();
560
815
  }
561
816
  function decimalToBytes16(value) {
562
817
  const hex = BigInt(value).toString(16).padStart(CONTRACT_ADDRESS_HALF_BYTES * 2, "0");
563
818
  if (hex.length > CONTRACT_ADDRESS_HALF_BYTES * 2) {
564
819
  throw new Error("Asset leg does not fit into 16 bytes.");
565
820
  }
566
- return Buffer5.from(hex, "hex");
821
+ return Buffer6.from(hex, "hex");
567
822
  }
568
823
  function tokenAddressFrParts(tokenAddress) {
569
824
  const normalized = resolveTokenContractId(tokenAddress);
@@ -605,7 +860,7 @@ function tokenAddressToAssetLeg(tokenAddress) {
605
860
  return [hi, lo];
606
861
  }
607
862
  function assetLegToTokenAddress(assetHi, assetLo) {
608
- const payload = Buffer5.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
863
+ const payload = Buffer6.concat([decimalToBytes16(assetHi), decimalToBytes16(assetLo)]);
609
864
  return StrKey2.encodeContract(payload);
610
865
  }
611
866
  function buildZeroPublicLegs(publicNInputs = 1, publicNOutputs = 1) {
@@ -686,7 +941,7 @@ var MIN_CONFIDENTIAL_TRANSFER_STROOPS = 1n;
686
941
  var ZERO_STROOPS = 0n;
687
942
  function withdrawWitnessForCoin(parameters) {
688
943
  const { sdk, coin, state } = parameters;
689
- const witness = sdk.buildWithdrawMerkleWitness(coin, state);
944
+ const witness = withdrawMerkleWitnessFromTree({ sdk, coin, state });
690
945
  const withdrawApplicationId = coin.application_id ?? parameters.applicationId;
691
946
  const withdrawObject = withdrawObjectFromMerkleWitness(
692
947
  witness,
@@ -873,7 +1128,7 @@ import {
873
1128
  DEMO_AUDIT_PUBLIC_KEY,
874
1129
  resolveTransactionAuditParams
875
1130
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
876
- import { Buffer as Buffer6 } from "buffer";
1131
+ import { Buffer as Buffer7 } from "buffer";
877
1132
  var UNCOMPRESSED_PREFIX = "04";
878
1133
  var UNCOMPRESSED_HEX_LENGTH = 130;
879
1134
  var XY_HEX_LENGTH = 128;
@@ -900,7 +1155,7 @@ function parseAuditPublicKeyFromEnvHex(raw) {
900
1155
  "Audit public key must be uncompressed 65-byte hex (04 + X + Y) or 64-byte X+Y hex"
901
1156
  );
902
1157
  }
903
- const bytes = Buffer6.from(payload, "hex");
1158
+ const bytes = Buffer7.from(payload, "hex");
904
1159
  const auditPublicKeyXDecimal = BigInt(
905
1160
  `0x${bytes.subarray(0, X_OFFSET_END).toString("hex")}`
906
1161
  ).toString(DECIMAL_RADIX);
@@ -943,13 +1198,13 @@ import { ed25519PubkeyPayloadHexToWithdrawFrDecimals } from "@arcanetech/stellar
943
1198
 
944
1199
  // src/transact/stellar/account.ts
945
1200
  import { StrKey as StrKey3 } from "@stellar/stellar-sdk";
946
- import { Buffer as Buffer7 } from "buffer";
1201
+ import { Buffer as Buffer8 } from "buffer";
947
1202
  var MUXED_ACCOUNT_ID_BYTE_LENGTH = 8;
948
1203
  var ED25519_PUBLIC_KEY_BYTE_LENGTH = 32;
949
1204
  function ed25519HexFromGAddress(trimmed) {
950
1205
  try {
951
1206
  const raw = StrKey3.decodeEd25519PublicKey(trimmed);
952
- return Buffer7.from(raw).toString("hex");
1207
+ return Buffer8.from(raw).toString("hex");
953
1208
  } catch {
954
1209
  return void 0;
955
1210
  }
@@ -1236,10 +1491,11 @@ async function proveSingleWithdrawTransaction(parameters) {
1236
1491
  );
1237
1492
  }
1238
1493
  function buildPrimaryWithdrawForCoin(parameters) {
1239
- const witness = parameters.sdk.buildWithdrawMerkleWitness(
1240
- parameters.coin,
1241
- parameters.state
1242
- );
1494
+ const witness = withdrawMerkleWitnessFromTree({
1495
+ sdk: parameters.sdk,
1496
+ coin: parameters.coin,
1497
+ state: parameters.state
1498
+ });
1243
1499
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
1244
1500
  parameters.privKeyScalarHex
1245
1501
  );
@@ -1661,23 +1917,6 @@ import {
1661
1917
  randomFrDecimal253,
1662
1918
  scalarHexToFrDecimal as scalarHexToFrDecimal2
1663
1919
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1664
-
1665
- // src/transact/merkle/field-decimal.ts
1666
- import { Buffer as Buffer8 } from "buffer";
1667
- var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1668
- var MERKLE_ROOT_BYTE_LENGTH = 32;
1669
- var DECIMAL_STRING_RADIX = 10;
1670
- function merkleRootBufferToFrDecimal(root) {
1671
- if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
1672
- throw new Error(
1673
- `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
1674
- );
1675
- }
1676
- const hex = Buffer8.from(root).toString("hex");
1677
- return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
1678
- }
1679
-
1680
- // src/transact/pool/prepare-deposit-proof.ts
1681
1920
  function userDepositSlot(input) {
1682
1921
  return {
1683
1922
  value: input.coin.value,
@@ -1909,6 +2148,7 @@ function createPrivacyPoolService(input) {
1909
2148
  var configuredPrivacyPoolService;
1910
2149
  function configurePrivacyPoolService(service) {
1911
2150
  configuredPrivacyPoolService = service;
2151
+ registerMerkleSessionSdkHost(service);
1912
2152
  }
1913
2153
  function getPrivacyPoolService() {
1914
2154
  if (!configuredPrivacyPoolService) {
@@ -1927,7 +2167,8 @@ function poolMerkleStateToCachedView(state) {
1927
2167
  return {
1928
2168
  commitments: state.commitments,
1929
2169
  updatedAt: state.updatedAt,
1930
- merkleRootHex: state.merkleRootHex
2170
+ merkleRootHex: state.merkleRootHex,
2171
+ ...state.nodes ? { nodes: state.nodes } : {}
1931
2172
  };
1932
2173
  }
1933
2174
 
@@ -2060,6 +2301,65 @@ function reuseCachedCommitments(input) {
2060
2301
  return cachedCommitments;
2061
2302
  }
2062
2303
 
2304
+ // src/transact/merkle/persist-fetched.ts
2305
+ function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
2306
+ if (!cachedState) {
2307
+ return false;
2308
+ }
2309
+ if (cachedState.commitments.length !== commitments.length) {
2310
+ return false;
2311
+ }
2312
+ return cachedState.merkleRootHex === merkleRootHex;
2313
+ }
2314
+ function viewFromCachedMerkle(cachedState, merkleRootHex) {
2315
+ return {
2316
+ commitments: cachedState.commitments,
2317
+ updatedAt: cachedState.updatedAt,
2318
+ merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
2319
+ commitmentCount: cachedState.commitments.length,
2320
+ ...cachedState.nodes ? { nodes: cachedState.nodes } : {}
2321
+ };
2322
+ }
2323
+ function merkleStateForPersist(input) {
2324
+ return {
2325
+ poolContract: input.poolContractId,
2326
+ commitments: input.commitments,
2327
+ commitmentCount: input.commitments.length,
2328
+ merkleRootHex: input.merkleRootHex,
2329
+ updatedAt: input.updatedAt,
2330
+ ...input.nodes ? { nodes: input.nodes } : {}
2331
+ };
2332
+ }
2333
+ async function persistFetchedMerkleState(input) {
2334
+ const cachedState = input.cachedState;
2335
+ if (cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex) && cachedState) {
2336
+ return viewFromCachedMerkle(cachedState, input.merkleRootHex);
2337
+ }
2338
+ const nodes = await resolveExtendedMerkleNodes({
2339
+ poolContract: input.poolContractId,
2340
+ commitments: input.commitments,
2341
+ merkleRootHex: input.merkleRootHex,
2342
+ ...cachedState ? { cachedState } : {}
2343
+ });
2344
+ const updatedAt = Date.now();
2345
+ await input.poolMerkleState?.set(
2346
+ merkleStateForPersist({
2347
+ poolContractId: input.poolContractId,
2348
+ commitments: input.commitments,
2349
+ merkleRootHex: input.merkleRootHex,
2350
+ updatedAt,
2351
+ ...nodes ? { nodes } : {}
2352
+ })
2353
+ );
2354
+ return {
2355
+ commitments: input.commitments,
2356
+ updatedAt,
2357
+ merkleRootHex: input.merkleRootHex,
2358
+ commitmentCount: input.commitments.length,
2359
+ ...nodes ? { nodes } : {}
2360
+ };
2361
+ }
2362
+
2063
2363
  // src/transact/merkle/fetch-contract.ts
2064
2364
  async function fetchAndMergeMerkleState(parameters) {
2065
2365
  const client = createPoolClient(parameters);
@@ -2152,43 +2452,6 @@ async function loadCommitments(input) {
2152
2452
  });
2153
2453
  return [...reused, ...fetched];
2154
2454
  }
2155
- async function persistFetchedMerkleState(input) {
2156
- const cachedState = input.cachedState;
2157
- if (cachedState && cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex)) {
2158
- return viewFromCachedMerkle(cachedState, input.merkleRootHex);
2159
- }
2160
- const updatedAt = Date.now();
2161
- await input.poolMerkleState?.set({
2162
- poolContract: input.poolContractId,
2163
- commitments: input.commitments,
2164
- commitmentCount: input.commitments.length,
2165
- merkleRootHex: input.merkleRootHex,
2166
- updatedAt
2167
- });
2168
- return {
2169
- commitments: input.commitments,
2170
- updatedAt,
2171
- merkleRootHex: input.merkleRootHex,
2172
- commitmentCount: input.commitments.length
2173
- };
2174
- }
2175
- function viewFromCachedMerkle(cachedState, merkleRootHex) {
2176
- return {
2177
- commitments: cachedState.commitments,
2178
- updatedAt: cachedState.updatedAt,
2179
- merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
2180
- commitmentCount: cachedState.commitments.length
2181
- };
2182
- }
2183
- function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
2184
- if (!cachedState) {
2185
- return false;
2186
- }
2187
- if ((cachedState.merkleRootHex ?? merkleRootHex) !== merkleRootHex) {
2188
- return false;
2189
- }
2190
- return cachedState.commitments.join(",") === commitments.join(",");
2191
- }
2192
2455
 
2193
2456
  // src/transact/engine/prepare/shared.ts
2194
2457
  async function resolveTokenContractId2(environment, assetId) {
@@ -2263,7 +2526,11 @@ async function loadMerkleState(environment, walletPublicKey) {
2263
2526
  transactEnvironment: environment,
2264
2527
  ...environment.poolMerkleState ? { poolMerkleState: environment.poolMerkleState } : {}
2265
2528
  });
2266
- return { commitments: merged.commitments };
2529
+ return merkleSnapshotToStateFile({
2530
+ commitments: merged.commitments,
2531
+ merkleRootHex: merged.merkleRootHex,
2532
+ ...merged.nodes ? { nodes: merged.nodes } : {}
2533
+ });
2267
2534
  }
2268
2535
  function enrichPreparedOperation(prepared, outputRecords, artifacts) {
2269
2536
  return {
@@ -2772,13 +3039,19 @@ var walletPrivateAddressRecordSchema = z.object({
2772
3039
  privateAddress: z.string().min(1),
2773
3040
  createdAt: z.number()
2774
3041
  });
3042
+ var leanImtNodeSchema = z.object({
3043
+ level: z.number().int().nonnegative(),
3044
+ index: z.number().int().nonnegative(),
3045
+ value: z.string().min(1)
3046
+ });
2775
3047
  var poolMerkleStateSchema = z.object({
2776
3048
  poolContract: z.string().min(1),
2777
3049
  commitments: z.array(z.string()),
2778
3050
  commitmentCount: z.number().int().nonnegative(),
2779
3051
  merkleRootHex: z.string().min(1),
2780
3052
  updatedAt: z.number(),
2781
- syncedLedger: z.number().int().optional()
3053
+ syncedLedger: z.number().int().optional(),
3054
+ nodes: z.array(leanImtNodeSchema).optional()
2782
3055
  });
2783
3056
  var leafEphemeralSchema = z.object({
2784
3057
  poolContract: z.string().min(1),
@@ -4337,6 +4610,7 @@ function createPoolMerkleService(context) {
4337
4610
  async appendPoolCommitments(input) {
4338
4611
  const current = await this.getPoolMerkleState(input.poolContract);
4339
4612
  const mergedCommitments = [...current?.commitments ?? [], ...input.commitments];
4613
+ const nodes = input.nodes ?? (input.commitments.length === 0 ? current?.nodes : void 0);
4340
4614
  await this.setPoolMerkleState(
4341
4615
  withoutUndefinedFields({
4342
4616
  poolContract: input.poolContract,
@@ -4344,7 +4618,8 @@ function createPoolMerkleService(context) {
4344
4618
  commitmentCount: mergedCommitments.length,
4345
4619
  merkleRootHex: input.merkleRootHex,
4346
4620
  updatedAt: Date.now(),
4347
- syncedLedger: input.syncedLedger ?? current?.syncedLedger
4621
+ syncedLedger: input.syncedLedger ?? current?.syncedLedger,
4622
+ nodes
4348
4623
  })
4349
4624
  );
4350
4625
  }
@@ -5614,12 +5889,46 @@ function createDeliveriesStateDelegates(state) {
5614
5889
  };
5615
5890
  }
5616
5891
 
5892
+ // src/transact/merkle/write-snapshot.ts
5893
+ async function writePoolMerkleState(input) {
5894
+ await input.setPoolMerkleState(input.state);
5895
+ await bindPoolMerkleTree(input.state);
5896
+ }
5897
+ async function appendPoolMerkleCommitments(input) {
5898
+ const current = await input.getPoolMerkleState(input.payload.poolContract);
5899
+ const merged = [...current?.commitments ?? [], ...input.payload.commitments];
5900
+ const nodes = input.payload.nodes ?? await resolveExtendedMerkleNodes({
5901
+ poolContract: input.payload.poolContract,
5902
+ commitments: merged,
5903
+ merkleRootHex: input.payload.merkleRootHex,
5904
+ ...current ? { cachedState: poolMerkleStateToCachedView(current) } : {}
5905
+ });
5906
+ await input.appendPoolCommitments({
5907
+ poolContract: input.payload.poolContract,
5908
+ commitments: input.payload.commitments,
5909
+ merkleRootHex: input.payload.merkleRootHex,
5910
+ ...input.payload.syncedLedger === void 0 ? {} : { syncedLedger: input.payload.syncedLedger },
5911
+ ...nodes ? { nodes } : {}
5912
+ });
5913
+ const written = await input.getPoolMerkleState(input.payload.poolContract);
5914
+ if (written) {
5915
+ await bindPoolMerkleTree(written);
5916
+ }
5917
+ }
5918
+
5617
5919
  // src/client/state-delegates/pool.ts
5618
5920
  function createPoolStateDelegates(state) {
5619
5921
  return {
5620
5922
  getPoolMerkleState: (poolContract) => state.getPoolMerkleState(poolContract),
5621
- setPoolMerkleState: (poolState) => state.setPoolMerkleState(poolState),
5622
- appendPoolCommitments: (input) => state.appendPoolCommitments(input),
5923
+ setPoolMerkleState: (poolState) => writePoolMerkleState({
5924
+ setPoolMerkleState: (next) => state.setPoolMerkleState(next),
5925
+ state: poolState
5926
+ }),
5927
+ appendPoolCommitments: (input) => appendPoolMerkleCommitments({
5928
+ getPoolMerkleState: (poolContract) => state.getPoolMerkleState(poolContract),
5929
+ appendPoolCommitments: (payload) => state.appendPoolCommitments(payload),
5930
+ payload: input
5931
+ }),
5623
5932
  getLeafEphemeral: (input) => state.getLeafEphemeral(input),
5624
5933
  setLeafEphemeral: (ephemeral) => state.setLeafEphemeral(ephemeral)
5625
5934
  };
@@ -5672,12 +5981,42 @@ function createStateDelegates(state) {
5672
5981
  };
5673
5982
  }
5674
5983
 
5984
+ // src/client/hydrate-state.ts
5985
+ async function hydratePoolMerkleStates(client, states) {
5986
+ for (const poolState of states ?? []) {
5987
+ await client.setPoolMerkleState(poolState);
5988
+ }
5989
+ }
5990
+ async function hydrateWalletAddresses(client, snapshot) {
5991
+ for (const record of snapshot.walletPrivateAddressRecords ?? []) {
5992
+ await client.saveWalletPrivateAddressRecord(record);
5993
+ }
5994
+ for (const scalar of snapshot.walletPrivateAddressScalars ?? []) {
5995
+ await client.saveWalletPrivateAddressScalar(scalar);
5996
+ }
5997
+ for (const entry of snapshot.walletDefaultPrivateAddressNonces ?? []) {
5998
+ await client.setWalletDefaultPrivateAddressNonce(entry);
5999
+ }
6000
+ }
6001
+ async function hydrateStellarPrivacyState(client, snapshot) {
6002
+ if (snapshot.assets && snapshot.assets.length > 0) {
6003
+ await client.replaceAssets(snapshot.assets);
6004
+ }
6005
+ await hydratePoolMerkleStates(client, snapshot.poolMerkleStates);
6006
+ if (snapshot.privateRecords && snapshot.privateRecords.length > 0) {
6007
+ await client.upsertPrivateRecords(snapshot.privateRecords);
6008
+ }
6009
+ await hydrateWalletAddresses(client, snapshot);
6010
+ }
6011
+
5675
6012
  // src/client/client.ts
5676
6013
  function createStellarPrivacyClientInstance(config) {
5677
6014
  const composed = composeStellarPrivacyClient(config);
6015
+ const stateDelegates = createStateDelegates(composed.state);
5678
6016
  return {
5679
6017
  state: composed.state,
5680
- ...createStateDelegates(composed.state),
6018
+ ...stateDelegates,
6019
+ hydrateState: (snapshot) => hydrateStellarPrivacyState(stateDelegates, snapshot),
5681
6020
  deposit: (intent) => composed.operations.deposit(intent),
5682
6021
  transfer: (intent) => composed.operations.transfer(intent),
5683
6022
  withdraw: (intent) => composed.operations.withdraw(intent),
@@ -5998,7 +6337,8 @@ async function buildSpendProofContextAtExecute(input) {
5998
6337
  input.prepared.intent.asset
5999
6338
  );
6000
6339
  const coin = requireCoinNoteFromRecord(primaryRecord);
6001
- const { commitments } = await loadMerkleState(input.environment, walletPublicKey);
6340
+ const merkleState = await loadMerkleState(input.environment, walletPublicKey);
6341
+ const commitments = merkleState.commitments;
6002
6342
  const leafIndex = findCommitmentLeafIndex(commitments, coin.commitment);
6003
6343
  if (leafIndex < 0) {
6004
6344
  throw new Error("Coin commitment not found in on-chain Merkle state.");
@@ -6015,6 +6355,7 @@ async function buildSpendProofContextAtExecute(input) {
6015
6355
  tokenAddress,
6016
6356
  coin,
6017
6357
  commitments,
6358
+ merkleState,
6018
6359
  ephemeral,
6019
6360
  senderPrivKeyScalarHex,
6020
6361
  recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1
@@ -6516,7 +6857,7 @@ async function buildSingleTransferProofAtExecute(input) {
6516
6857
  const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
6517
6858
  const proof = await prepareConfidentialTransferProof({
6518
6859
  coin: context.coin,
6519
- state: { commitments: context.commitments },
6860
+ state: context.merkleState,
6520
6861
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
6521
6862
  depositorEphemeralKey: serializeEphemeralKeyString({
6522
6863
  xHex: context.ephemeral.xHex,
@@ -6553,7 +6894,7 @@ async function buildDualTransferProofAtExecute(input) {
6553
6894
  const proof = await prepareConfidentialTransferProofDual({
6554
6895
  coinA: context.coin,
6555
6896
  coinB: secondary.coin,
6556
- state: { commitments: context.commitments },
6897
+ state: context.merkleState,
6557
6898
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
6558
6899
  ephemeralAKey: serializeEphemeralKeyString({
6559
6900
  xHex: context.ephemeral.xHex,
@@ -6801,7 +7142,7 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6801
7142
  });
6802
7143
  const proof = await poolService.prepareWithdrawTransactProof({
6803
7144
  coin: context.coin,
6804
- state: { commitments: context.commitments },
7145
+ state: context.merkleState,
6805
7146
  destinationStellarAddress: prepared.intent.to,
6806
7147
  privKeyScalarHex: context.senderPrivKeyScalarHex,
6807
7148
  depositorEphemeralKey: serializeEphemeralKeyString({
@@ -6854,7 +7195,7 @@ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService,
6854
7195
  const proof = await poolService.prepareWithdrawTransactProofDual({
6855
7196
  coinA: context.coin,
6856
7197
  coinB: secondary.coin,
6857
- state: { commitments: context.commitments },
7198
+ state: context.merkleState,
6858
7199
  destinationStellarAddress: prepared.intent.to,
6859
7200
  privKeyScalarHex: context.senderPrivKeyScalarHex,
6860
7201
  ...dualWithdrawEphemeralKeys({ context, secondary }),