@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/node.js CHANGED
@@ -466,6 +466,261 @@ function buildPrivateRecordFromDeposit(input) {
466
466
  };
467
467
  }
468
468
 
469
+ // src/transact/merkle/lean-imt-api.ts
470
+ function asLeanImtSessionApi(sdk) {
471
+ const candidate = sdk;
472
+ if (typeof candidate.importLeanImtFromState !== "function") {
473
+ return void 0;
474
+ }
475
+ if (typeof candidate.insertTwoLeanImt !== "function") {
476
+ return void 0;
477
+ }
478
+ if (typeof candidate.exportLeanImt !== "function") {
479
+ return void 0;
480
+ }
481
+ if (typeof candidate.dropLeanImt !== "function") {
482
+ return void 0;
483
+ }
484
+ if (typeof candidate.buildWithdrawMerkleWitnessFromHandle !== "function") {
485
+ return void 0;
486
+ }
487
+ return candidate;
488
+ }
489
+
490
+ // src/transact/merkle/field-decimal.ts
491
+ import { Buffer as Buffer4 } from "buffer";
492
+ var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
493
+ var MERKLE_ROOT_BYTE_LENGTH = 32;
494
+ var DECIMAL_STRING_RADIX = 10;
495
+ function merkleRootBufferToFrDecimal(root) {
496
+ if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
497
+ throw new Error(
498
+ `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
499
+ );
500
+ }
501
+ const hex = Buffer4.from(root).toString("hex");
502
+ return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
503
+ }
504
+ var MERKLE_ROOT_HEX_PATTERN = /^[0-9a-fA-F]{64}$/;
505
+ function merkleRootHexToFrDecimal(merkleRootHex) {
506
+ const trimmed = merkleRootHex.trim();
507
+ if (!MERKLE_ROOT_HEX_PATTERN.test(trimmed)) {
508
+ return void 0;
509
+ }
510
+ return merkleRootBufferToFrDecimal(Buffer4.from(trimmed, "hex"));
511
+ }
512
+
513
+ // src/transact/merkle/state-file.ts
514
+ function merkleSnapshotToStateFile(input) {
515
+ const root = merkleRootHexToFrDecimal(input.merkleRootHex);
516
+ const nodes = input.nodes;
517
+ if (!nodes || nodes.length === 0 || root === void 0) {
518
+ return { commitments: input.commitments };
519
+ }
520
+ return {
521
+ commitments: input.commitments,
522
+ nodes,
523
+ root
524
+ };
525
+ }
526
+
527
+ // src/transact/merkle/extend-snapshot.ts
528
+ function isEvenLeafCount(count) {
529
+ return count % 2 === 0;
530
+ }
531
+ function importHandle(sdk, state) {
532
+ try {
533
+ return sdk.importLeanImtFromState(state);
534
+ } catch {
535
+ return void 0;
536
+ }
537
+ }
538
+ function insertNewPairs(input) {
539
+ for (let index = input.prefixLength; index < input.commitments.length; index += 2) {
540
+ const leafA = input.commitments.at(index);
541
+ const leafB = input.commitments.at(index + 1);
542
+ if (leafA === void 0 || leafB === void 0) {
543
+ return false;
544
+ }
545
+ try {
546
+ input.sdk.insertTwoLeanImt(input.handle, leafA, leafB);
547
+ } catch {
548
+ return false;
549
+ }
550
+ }
551
+ return true;
552
+ }
553
+ function snapshotNodes(sdk, handle) {
554
+ try {
555
+ return sdk.exportLeanImt(handle).nodes;
556
+ } catch {
557
+ return void 0;
558
+ }
559
+ }
560
+ function prefixSnapshotState(input) {
561
+ const prefixLength = input.cachedState?.nodes ? input.cachedState.commitments.length : 0;
562
+ return merkleSnapshotToStateFile({
563
+ commitments: input.commitments.slice(0, prefixLength),
564
+ merkleRootHex: input.cachedState?.merkleRootHex ?? input.merkleRootHex,
565
+ ...input.cachedState?.nodes ? { nodes: input.cachedState.nodes } : {}
566
+ });
567
+ }
568
+ function importSnapshotState(input) {
569
+ if (input.prefixState.nodes && input.prefixState.commitments.length > 0) {
570
+ return input.prefixState;
571
+ }
572
+ return merkleSnapshotToStateFile({
573
+ commitments: input.commitments,
574
+ merkleRootHex: input.merkleRootHex
575
+ });
576
+ }
577
+ function exportExtendedNodes(input) {
578
+ const inserted = input.prefixState.nodes ? insertNewPairs({
579
+ sdk: input.sdk,
580
+ handle: input.handle,
581
+ prefixLength: input.prefixState.commitments.length,
582
+ commitments: input.commitments
583
+ }) : true;
584
+ if (!inserted) {
585
+ input.sdk.dropLeanImt(input.handle);
586
+ return void 0;
587
+ }
588
+ const nodes = snapshotNodes(input.sdk, input.handle);
589
+ if (!nodes) {
590
+ input.sdk.dropLeanImt(input.handle);
591
+ }
592
+ return nodes;
593
+ }
594
+ function extendMerkleSnapshotNodes(input) {
595
+ if (!isEvenLeafCount(input.commitments.length)) {
596
+ return {};
597
+ }
598
+ const prefixState = prefixSnapshotState(input);
599
+ const handle = importHandle(
600
+ input.sdk,
601
+ importSnapshotState({ ...input, prefixState })
602
+ );
603
+ if (handle === void 0) {
604
+ return {};
605
+ }
606
+ const nodes = exportExtendedNodes({
607
+ sdk: input.sdk,
608
+ handle,
609
+ prefixState,
610
+ commitments: input.commitments
611
+ });
612
+ if (!nodes) {
613
+ return {};
614
+ }
615
+ return { nodes, handle };
616
+ }
617
+
618
+ // src/transact/merkle/tree-session.ts
619
+ var merkleSdkHost;
620
+ function registerMerkleSessionSdkHost(host) {
621
+ merkleSdkHost = host;
622
+ }
623
+ async function loadSessionSdk() {
624
+ const host = merkleSdkHost;
625
+ if (typeof host?.getInitializedSdk !== "function") {
626
+ return void 0;
627
+ }
628
+ try {
629
+ return asLeanImtSessionApi(await host.getInitializedSdk());
630
+ } catch {
631
+ return void 0;
632
+ }
633
+ }
634
+ var sessionsByPool = /* @__PURE__ */ new Map();
635
+ var handlesByKey = /* @__PURE__ */ new Map();
636
+ function merkleSessionKey(state) {
637
+ const root = state.root?.trim();
638
+ if (root) {
639
+ return `${state.commitments.length}:${root}`;
640
+ }
641
+ const first = state.commitments[0] ?? "";
642
+ const last = state.commitments.at(-1) ?? "";
643
+ return `${state.commitments.length}:${first}:${last}`;
644
+ }
645
+ function rememberHandle(poolContract, key, handle) {
646
+ const existing = sessionsByPool.get(poolContract);
647
+ if (existing && existing.handle !== handle) {
648
+ handlesByKey.delete(existing.key);
649
+ }
650
+ sessionsByPool.set(poolContract, { handle, key });
651
+ handlesByKey.set(key, handle);
652
+ }
653
+ function importHandle2(sdk, state) {
654
+ try {
655
+ return sdk.importLeanImtFromState(state);
656
+ } catch {
657
+ return void 0;
658
+ }
659
+ }
660
+ function merkleTreeHandleForState(state) {
661
+ return handlesByKey.get(merkleSessionKey(state));
662
+ }
663
+ function bindImportedMerkleTree(input) {
664
+ rememberHandle(input.poolContract, merkleSessionKey(input.state), input.handle);
665
+ }
666
+ async function bindPoolMerkleTree(state) {
667
+ const sdk = await loadSessionSdk();
668
+ if (!sdk) {
669
+ return void 0;
670
+ }
671
+ const snapshot = merkleSnapshotToStateFile(state);
672
+ const key = merkleSessionKey(snapshot);
673
+ const existing = sessionsByPool.get(state.poolContract);
674
+ if (existing?.key === key) {
675
+ return existing.handle;
676
+ }
677
+ const handle = importHandle2(sdk, snapshot);
678
+ if (handle === void 0) {
679
+ return void 0;
680
+ }
681
+ if (existing) {
682
+ sdk.dropLeanImt(existing.handle);
683
+ }
684
+ rememberHandle(state.poolContract, key, handle);
685
+ return handle;
686
+ }
687
+ async function resolveExtendedMerkleNodes(input) {
688
+ const cached = input.cachedState;
689
+ if (cached && cached.commitments.length === input.commitments.length && cached.merkleRootHex === input.merkleRootHex) {
690
+ return cached.nodes;
691
+ }
692
+ const sdk = await loadSessionSdk();
693
+ if (!sdk) {
694
+ return void 0;
695
+ }
696
+ const extended = extendMerkleSnapshotNodes({
697
+ sdk,
698
+ commitments: input.commitments,
699
+ merkleRootHex: input.merkleRootHex,
700
+ ...cached ? { cachedState: cached } : {}
701
+ });
702
+ if (extended.handle !== void 0) {
703
+ bindImportedMerkleTree({
704
+ poolContract: input.poolContract,
705
+ handle: extended.handle,
706
+ state: merkleSnapshotToStateFile({
707
+ commitments: input.commitments,
708
+ merkleRootHex: input.merkleRootHex,
709
+ ...extended.nodes ? { nodes: extended.nodes } : {}
710
+ })
711
+ });
712
+ }
713
+ return extended.nodes;
714
+ }
715
+ function withdrawMerkleWitnessFromTree(input) {
716
+ const session = asLeanImtSessionApi(input.sdk);
717
+ const handle = merkleTreeHandleForState(input.state);
718
+ if (session && handle !== void 0) {
719
+ return session.buildWithdrawMerkleWitnessFromHandle(input.coin, handle);
720
+ }
721
+ return input.sdk.buildWithdrawMerkleWitness(input.coin, input.state);
722
+ }
723
+
469
724
  // src/transact/pool/service.ts
470
725
  import {
471
726
  decodeDecodedEphemeralKey
@@ -519,18 +774,18 @@ function privateAddressSdk(sdk) {
519
774
  import { withdrawObjectFromMerkleWitness as withdrawObjectFromMerkleWitness2 } from "@arcanetech/stellar-privacy-pool-zk-sdk";
520
775
 
521
776
  // src/transact/proofs/transaction-input.ts
522
- import { Buffer as Buffer5 } from "buffer";
777
+ import { Buffer as Buffer6 } from "buffer";
523
778
  import { StrKey as StrKey2 } from "@stellar/stellar-sdk";
524
779
 
525
780
  // src/transact/stellar/token-contract.ts
526
- import { Buffer as Buffer4 } from "buffer";
781
+ import { Buffer as Buffer5 } from "buffer";
527
782
  import { StrKey } from "@stellar/stellar-sdk";
528
783
  var CONTRACT_ID_BYTE_LENGTH = 32;
529
784
  var HEX_CHARS_PER_BYTE = 2;
530
785
  var CONTRACT_ID_HEX_CHAR_LENGTH = CONTRACT_ID_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
531
786
  var CONTRACT_ID_HEX = /^(?:0x)?([0-9a-f]{64})$/i;
532
787
  function encodeContractFromHex(hexPayload) {
533
- return StrKey.encodeContract(Buffer4.from(hexPayload, "hex"));
788
+ return StrKey.encodeContract(Buffer5.from(hexPayload, "hex"));
534
789
  }
535
790
  function normalizeSorobanContractId(raw) {
536
791
  const trimmed = raw.trim();
@@ -563,7 +818,7 @@ var ZERO_PUBLIC_WITHDRAW_ADDRESS = {
563
818
  var CONTRACT_ADDRESS_HALF_BYTES = 16;
564
819
  var CONTRACT_ADDRESS_BYTES = 32;
565
820
  function bytes16ToDecimal(bytes) {
566
- return BigInt(`0x${Buffer5.from(bytes).toString("hex")}`).toString();
821
+ return BigInt(`0x${Buffer6.from(bytes).toString("hex")}`).toString();
567
822
  }
568
823
  function tokenAddressFrParts(tokenAddress) {
569
824
  const normalized = resolveTokenContractId(tokenAddress);
@@ -682,7 +937,7 @@ var MIN_CONFIDENTIAL_TRANSFER_STROOPS = 1n;
682
937
  var ZERO_STROOPS = 0n;
683
938
  function withdrawWitnessForCoin(parameters) {
684
939
  const { sdk, coin, state } = parameters;
685
- const witness = sdk.buildWithdrawMerkleWitness(coin, state);
940
+ const witness = withdrawMerkleWitnessFromTree({ sdk, coin, state });
686
941
  const withdrawApplicationId = coin.application_id ?? parameters.applicationId;
687
942
  const withdrawObject = withdrawObjectFromMerkleWitness(
688
943
  witness,
@@ -869,7 +1124,7 @@ import {
869
1124
  DEMO_AUDIT_PUBLIC_KEY,
870
1125
  resolveTransactionAuditParams
871
1126
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
872
- import { Buffer as Buffer6 } from "buffer";
1127
+ import { Buffer as Buffer7 } from "buffer";
873
1128
  var UNCOMPRESSED_PREFIX = "04";
874
1129
  var UNCOMPRESSED_HEX_LENGTH = 130;
875
1130
  var XY_HEX_LENGTH = 128;
@@ -896,7 +1151,7 @@ function parseAuditPublicKeyFromEnvHex(raw) {
896
1151
  "Audit public key must be uncompressed 65-byte hex (04 + X + Y) or 64-byte X+Y hex"
897
1152
  );
898
1153
  }
899
- const bytes = Buffer6.from(payload, "hex");
1154
+ const bytes = Buffer7.from(payload, "hex");
900
1155
  const auditPublicKeyXDecimal = BigInt(
901
1156
  `0x${bytes.subarray(0, X_OFFSET_END).toString("hex")}`
902
1157
  ).toString(DECIMAL_RADIX);
@@ -939,13 +1194,13 @@ import { ed25519PubkeyPayloadHexToWithdrawFrDecimals } from "@arcanetech/stellar
939
1194
 
940
1195
  // src/transact/stellar/account.ts
941
1196
  import { StrKey as StrKey3 } from "@stellar/stellar-sdk";
942
- import { Buffer as Buffer7 } from "buffer";
1197
+ import { Buffer as Buffer8 } from "buffer";
943
1198
  var MUXED_ACCOUNT_ID_BYTE_LENGTH = 8;
944
1199
  var ED25519_PUBLIC_KEY_BYTE_LENGTH = 32;
945
1200
  function ed25519HexFromGAddress(trimmed) {
946
1201
  try {
947
1202
  const raw = StrKey3.decodeEd25519PublicKey(trimmed);
948
- return Buffer7.from(raw).toString("hex");
1203
+ return Buffer8.from(raw).toString("hex");
949
1204
  } catch {
950
1205
  return void 0;
951
1206
  }
@@ -1232,10 +1487,11 @@ async function proveSingleWithdrawTransaction(parameters) {
1232
1487
  );
1233
1488
  }
1234
1489
  function buildPrimaryWithdrawForCoin(parameters) {
1235
- const witness = parameters.sdk.buildWithdrawMerkleWitness(
1236
- parameters.coin,
1237
- parameters.state
1238
- );
1490
+ const witness = withdrawMerkleWitnessFromTree({
1491
+ sdk: parameters.sdk,
1492
+ coin: parameters.coin,
1493
+ state: parameters.state
1494
+ });
1239
1495
  const privKeyScalar = privKeyScalarDecimalFromRecipientScalarHex(
1240
1496
  parameters.privKeyScalarHex
1241
1497
  );
@@ -1654,23 +1910,6 @@ import {
1654
1910
  randomFrDecimal253,
1655
1911
  scalarHexToFrDecimal as scalarHexToFrDecimal2
1656
1912
  } from "@arcanetech/stellar-privacy-pool-zk-sdk";
1657
-
1658
- // src/transact/merkle/field-decimal.ts
1659
- import { Buffer as Buffer8 } from "buffer";
1660
- var BN254 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
1661
- var MERKLE_ROOT_BYTE_LENGTH = 32;
1662
- var DECIMAL_STRING_RADIX = 10;
1663
- function merkleRootBufferToFrDecimal(root) {
1664
- if (root.length !== MERKLE_ROOT_BYTE_LENGTH) {
1665
- throw new Error(
1666
- `expected ${MERKLE_ROOT_BYTE_LENGTH}-byte merkle root, got ${root.length}`
1667
- );
1668
- }
1669
- const hex = Buffer8.from(root).toString("hex");
1670
- return (BigInt(`0x${hex}`) % BN254).toString(DECIMAL_STRING_RADIX);
1671
- }
1672
-
1673
- // src/transact/pool/prepare-deposit-proof.ts
1674
1913
  function userDepositSlot(input) {
1675
1914
  return {
1676
1915
  value: input.coin.value,
@@ -1902,6 +2141,7 @@ function createPrivacyPoolService(input) {
1902
2141
  var configuredPrivacyPoolService;
1903
2142
  function configurePrivacyPoolService(service) {
1904
2143
  configuredPrivacyPoolService = service;
2144
+ registerMerkleSessionSdkHost(service);
1905
2145
  }
1906
2146
  function getPrivacyPoolService() {
1907
2147
  if (!configuredPrivacyPoolService) {
@@ -1920,7 +2160,8 @@ function poolMerkleStateToCachedView(state) {
1920
2160
  return {
1921
2161
  commitments: state.commitments,
1922
2162
  updatedAt: state.updatedAt,
1923
- merkleRootHex: state.merkleRootHex
2163
+ merkleRootHex: state.merkleRootHex,
2164
+ ...state.nodes ? { nodes: state.nodes } : {}
1924
2165
  };
1925
2166
  }
1926
2167
 
@@ -2053,6 +2294,65 @@ function reuseCachedCommitments(input) {
2053
2294
  return cachedCommitments;
2054
2295
  }
2055
2296
 
2297
+ // src/transact/merkle/persist-fetched.ts
2298
+ function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
2299
+ if (!cachedState) {
2300
+ return false;
2301
+ }
2302
+ if (cachedState.commitments.length !== commitments.length) {
2303
+ return false;
2304
+ }
2305
+ return cachedState.merkleRootHex === merkleRootHex;
2306
+ }
2307
+ function viewFromCachedMerkle(cachedState, merkleRootHex) {
2308
+ return {
2309
+ commitments: cachedState.commitments,
2310
+ updatedAt: cachedState.updatedAt,
2311
+ merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
2312
+ commitmentCount: cachedState.commitments.length,
2313
+ ...cachedState.nodes ? { nodes: cachedState.nodes } : {}
2314
+ };
2315
+ }
2316
+ function merkleStateForPersist(input) {
2317
+ return {
2318
+ poolContract: input.poolContractId,
2319
+ commitments: input.commitments,
2320
+ commitmentCount: input.commitments.length,
2321
+ merkleRootHex: input.merkleRootHex,
2322
+ updatedAt: input.updatedAt,
2323
+ ...input.nodes ? { nodes: input.nodes } : {}
2324
+ };
2325
+ }
2326
+ async function persistFetchedMerkleState(input) {
2327
+ const cachedState = input.cachedState;
2328
+ if (cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex) && cachedState) {
2329
+ return viewFromCachedMerkle(cachedState, input.merkleRootHex);
2330
+ }
2331
+ const nodes = await resolveExtendedMerkleNodes({
2332
+ poolContract: input.poolContractId,
2333
+ commitments: input.commitments,
2334
+ merkleRootHex: input.merkleRootHex,
2335
+ ...cachedState ? { cachedState } : {}
2336
+ });
2337
+ const updatedAt = Date.now();
2338
+ await input.poolMerkleState?.set(
2339
+ merkleStateForPersist({
2340
+ poolContractId: input.poolContractId,
2341
+ commitments: input.commitments,
2342
+ merkleRootHex: input.merkleRootHex,
2343
+ updatedAt,
2344
+ ...nodes ? { nodes } : {}
2345
+ })
2346
+ );
2347
+ return {
2348
+ commitments: input.commitments,
2349
+ updatedAt,
2350
+ merkleRootHex: input.merkleRootHex,
2351
+ commitmentCount: input.commitments.length,
2352
+ ...nodes ? { nodes } : {}
2353
+ };
2354
+ }
2355
+
2056
2356
  // src/transact/merkle/fetch-contract.ts
2057
2357
  async function fetchAndMergeMerkleState(parameters) {
2058
2358
  const client = createPoolClient(parameters);
@@ -2145,43 +2445,6 @@ async function loadCommitments(input) {
2145
2445
  });
2146
2446
  return [...reused, ...fetched];
2147
2447
  }
2148
- async function persistFetchedMerkleState(input) {
2149
- const cachedState = input.cachedState;
2150
- if (cachedState && cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex)) {
2151
- return viewFromCachedMerkle(cachedState, input.merkleRootHex);
2152
- }
2153
- const updatedAt = Date.now();
2154
- await input.poolMerkleState?.set({
2155
- poolContract: input.poolContractId,
2156
- commitments: input.commitments,
2157
- commitmentCount: input.commitments.length,
2158
- merkleRootHex: input.merkleRootHex,
2159
- updatedAt
2160
- });
2161
- return {
2162
- commitments: input.commitments,
2163
- updatedAt,
2164
- merkleRootHex: input.merkleRootHex,
2165
- commitmentCount: input.commitments.length
2166
- };
2167
- }
2168
- function viewFromCachedMerkle(cachedState, merkleRootHex) {
2169
- return {
2170
- commitments: cachedState.commitments,
2171
- updatedAt: cachedState.updatedAt,
2172
- merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
2173
- commitmentCount: cachedState.commitments.length
2174
- };
2175
- }
2176
- function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
2177
- if (!cachedState) {
2178
- return false;
2179
- }
2180
- if ((cachedState.merkleRootHex ?? merkleRootHex) !== merkleRootHex) {
2181
- return false;
2182
- }
2183
- return cachedState.commitments.join(",") === commitments.join(",");
2184
- }
2185
2448
 
2186
2449
  // src/transact/engine/prepare/shared.ts
2187
2450
  async function resolveTokenContractId2(environment, assetId) {
@@ -2256,7 +2519,11 @@ async function loadMerkleState(environment, walletPublicKey) {
2256
2519
  transactEnvironment: environment,
2257
2520
  ...environment.poolMerkleState ? { poolMerkleState: environment.poolMerkleState } : {}
2258
2521
  });
2259
- return { commitments: merged.commitments };
2522
+ return merkleSnapshotToStateFile({
2523
+ commitments: merged.commitments,
2524
+ merkleRootHex: merged.merkleRootHex,
2525
+ ...merged.nodes ? { nodes: merged.nodes } : {}
2526
+ });
2260
2527
  }
2261
2528
  function enrichPreparedOperation(prepared, outputRecords, artifacts) {
2262
2529
  return {
@@ -2765,13 +3032,19 @@ var walletPrivateAddressRecordSchema = z.object({
2765
3032
  privateAddress: z.string().min(1),
2766
3033
  createdAt: z.number()
2767
3034
  });
3035
+ var leanImtNodeSchema = z.object({
3036
+ level: z.number().int().nonnegative(),
3037
+ index: z.number().int().nonnegative(),
3038
+ value: z.string().min(1)
3039
+ });
2768
3040
  var poolMerkleStateSchema = z.object({
2769
3041
  poolContract: z.string().min(1),
2770
3042
  commitments: z.array(z.string()),
2771
3043
  commitmentCount: z.number().int().nonnegative(),
2772
3044
  merkleRootHex: z.string().min(1),
2773
3045
  updatedAt: z.number(),
2774
- syncedLedger: z.number().int().optional()
3046
+ syncedLedger: z.number().int().optional(),
3047
+ nodes: z.array(leanImtNodeSchema).optional()
2775
3048
  });
2776
3049
  var leafEphemeralSchema = z.object({
2777
3050
  poolContract: z.string().min(1),
@@ -4184,6 +4457,7 @@ function createPoolMerkleService(context) {
4184
4457
  async appendPoolCommitments(input) {
4185
4458
  const current = await this.getPoolMerkleState(input.poolContract);
4186
4459
  const mergedCommitments = [...current?.commitments ?? [], ...input.commitments];
4460
+ const nodes = input.nodes ?? (input.commitments.length === 0 ? current?.nodes : void 0);
4187
4461
  await this.setPoolMerkleState(
4188
4462
  withoutUndefinedFields({
4189
4463
  poolContract: input.poolContract,
@@ -4191,7 +4465,8 @@ function createPoolMerkleService(context) {
4191
4465
  commitmentCount: mergedCommitments.length,
4192
4466
  merkleRootHex: input.merkleRootHex,
4193
4467
  updatedAt: Date.now(),
4194
- syncedLedger: input.syncedLedger ?? current?.syncedLedger
4468
+ syncedLedger: input.syncedLedger ?? current?.syncedLedger,
4469
+ nodes
4195
4470
  })
4196
4471
  );
4197
4472
  }
@@ -5461,12 +5736,46 @@ function createDeliveriesStateDelegates(state) {
5461
5736
  };
5462
5737
  }
5463
5738
 
5739
+ // src/transact/merkle/write-snapshot.ts
5740
+ async function writePoolMerkleState(input) {
5741
+ await input.setPoolMerkleState(input.state);
5742
+ await bindPoolMerkleTree(input.state);
5743
+ }
5744
+ async function appendPoolMerkleCommitments(input) {
5745
+ const current = await input.getPoolMerkleState(input.payload.poolContract);
5746
+ const merged = [...current?.commitments ?? [], ...input.payload.commitments];
5747
+ const nodes = input.payload.nodes ?? await resolveExtendedMerkleNodes({
5748
+ poolContract: input.payload.poolContract,
5749
+ commitments: merged,
5750
+ merkleRootHex: input.payload.merkleRootHex,
5751
+ ...current ? { cachedState: poolMerkleStateToCachedView(current) } : {}
5752
+ });
5753
+ await input.appendPoolCommitments({
5754
+ poolContract: input.payload.poolContract,
5755
+ commitments: input.payload.commitments,
5756
+ merkleRootHex: input.payload.merkleRootHex,
5757
+ ...input.payload.syncedLedger === void 0 ? {} : { syncedLedger: input.payload.syncedLedger },
5758
+ ...nodes ? { nodes } : {}
5759
+ });
5760
+ const written = await input.getPoolMerkleState(input.payload.poolContract);
5761
+ if (written) {
5762
+ await bindPoolMerkleTree(written);
5763
+ }
5764
+ }
5765
+
5464
5766
  // src/client/state-delegates/pool.ts
5465
5767
  function createPoolStateDelegates(state) {
5466
5768
  return {
5467
5769
  getPoolMerkleState: (poolContract) => state.getPoolMerkleState(poolContract),
5468
- setPoolMerkleState: (poolState) => state.setPoolMerkleState(poolState),
5469
- appendPoolCommitments: (input) => state.appendPoolCommitments(input),
5770
+ setPoolMerkleState: (poolState) => writePoolMerkleState({
5771
+ setPoolMerkleState: (next) => state.setPoolMerkleState(next),
5772
+ state: poolState
5773
+ }),
5774
+ appendPoolCommitments: (input) => appendPoolMerkleCommitments({
5775
+ getPoolMerkleState: (poolContract) => state.getPoolMerkleState(poolContract),
5776
+ appendPoolCommitments: (payload) => state.appendPoolCommitments(payload),
5777
+ payload: input
5778
+ }),
5470
5779
  getLeafEphemeral: (input) => state.getLeafEphemeral(input),
5471
5780
  setLeafEphemeral: (ephemeral) => state.setLeafEphemeral(ephemeral)
5472
5781
  };
@@ -5519,12 +5828,42 @@ function createStateDelegates(state) {
5519
5828
  };
5520
5829
  }
5521
5830
 
5831
+ // src/client/hydrate-state.ts
5832
+ async function hydratePoolMerkleStates(client, states) {
5833
+ for (const poolState of states ?? []) {
5834
+ await client.setPoolMerkleState(poolState);
5835
+ }
5836
+ }
5837
+ async function hydrateWalletAddresses(client, snapshot) {
5838
+ for (const record of snapshot.walletPrivateAddressRecords ?? []) {
5839
+ await client.saveWalletPrivateAddressRecord(record);
5840
+ }
5841
+ for (const scalar of snapshot.walletPrivateAddressScalars ?? []) {
5842
+ await client.saveWalletPrivateAddressScalar(scalar);
5843
+ }
5844
+ for (const entry of snapshot.walletDefaultPrivateAddressNonces ?? []) {
5845
+ await client.setWalletDefaultPrivateAddressNonce(entry);
5846
+ }
5847
+ }
5848
+ async function hydrateStellarPrivacyState(client, snapshot) {
5849
+ if (snapshot.assets && snapshot.assets.length > 0) {
5850
+ await client.replaceAssets(snapshot.assets);
5851
+ }
5852
+ await hydratePoolMerkleStates(client, snapshot.poolMerkleStates);
5853
+ if (snapshot.privateRecords && snapshot.privateRecords.length > 0) {
5854
+ await client.upsertPrivateRecords(snapshot.privateRecords);
5855
+ }
5856
+ await hydrateWalletAddresses(client, snapshot);
5857
+ }
5858
+
5522
5859
  // src/client/client.ts
5523
5860
  function createStellarPrivacyClientInstance(config) {
5524
5861
  const composed = composeStellarPrivacyClient(config);
5862
+ const stateDelegates = createStateDelegates(composed.state);
5525
5863
  return {
5526
5864
  state: composed.state,
5527
- ...createStateDelegates(composed.state),
5865
+ ...stateDelegates,
5866
+ hydrateState: (snapshot) => hydrateStellarPrivacyState(stateDelegates, snapshot),
5528
5867
  deposit: (intent) => composed.operations.deposit(intent),
5529
5868
  transfer: (intent) => composed.operations.transfer(intent),
5530
5869
  withdraw: (intent) => composed.operations.withdraw(intent),
@@ -5845,7 +6184,8 @@ async function buildSpendProofContextAtExecute(input) {
5845
6184
  input.prepared.intent.asset
5846
6185
  );
5847
6186
  const coin = requireCoinNoteFromRecord(primaryRecord);
5848
- const { commitments } = await loadMerkleState(input.environment, walletPublicKey);
6187
+ const merkleState = await loadMerkleState(input.environment, walletPublicKey);
6188
+ const commitments = merkleState.commitments;
5849
6189
  const leafIndex = findCommitmentLeafIndex(commitments, coin.commitment);
5850
6190
  if (leafIndex < 0) {
5851
6191
  throw new Error("Coin commitment not found in on-chain Merkle state.");
@@ -5862,6 +6202,7 @@ async function buildSpendProofContextAtExecute(input) {
5862
6202
  tokenAddress,
5863
6203
  coin,
5864
6204
  commitments,
6205
+ merkleState,
5865
6206
  ephemeral,
5866
6207
  senderPrivKeyScalarHex,
5867
6208
  recipientPrivateAddressStpl1: input.recipientPrivateAddressStpl1
@@ -6363,7 +6704,7 @@ async function buildSingleTransferProofAtExecute(input) {
6363
6704
  const changeStroops = BigInt(context.coin.value) - input.prepared.intent.amount - (feeOutput?.requiredFee ?? 0n);
6364
6705
  const proof = await prepareConfidentialTransferProof({
6365
6706
  coin: context.coin,
6366
- state: { commitments: context.commitments },
6707
+ state: context.merkleState,
6367
6708
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
6368
6709
  depositorEphemeralKey: serializeEphemeralKeyString({
6369
6710
  xHex: context.ephemeral.xHex,
@@ -6400,7 +6741,7 @@ async function buildDualTransferProofAtExecute(input) {
6400
6741
  const proof = await prepareConfidentialTransferProofDual({
6401
6742
  coinA: context.coin,
6402
6743
  coinB: secondary.coin,
6403
- state: { commitments: context.commitments },
6744
+ state: context.merkleState,
6404
6745
  senderPrivKeyScalarHex: context.senderPrivKeyScalarHex,
6405
6746
  ephemeralAKey: serializeEphemeralKeyString({
6406
6747
  xHex: context.ephemeral.xHex,
@@ -6648,7 +6989,7 @@ async function finalizeSingleWithdrawAtExecute(prepared, environment, poolServic
6648
6989
  });
6649
6990
  const proof = await poolService.prepareWithdrawTransactProof({
6650
6991
  coin: context.coin,
6651
- state: { commitments: context.commitments },
6992
+ state: context.merkleState,
6652
6993
  destinationStellarAddress: prepared.intent.to,
6653
6994
  privKeyScalarHex: context.senderPrivKeyScalarHex,
6654
6995
  depositorEphemeralKey: serializeEphemeralKeyString({
@@ -6701,7 +7042,7 @@ async function finalizeDualWithdrawAtExecute(prepared, environment, poolService,
6701
7042
  const proof = await poolService.prepareWithdrawTransactProofDual({
6702
7043
  coinA: context.coin,
6703
7044
  coinB: secondary.coin,
6704
- state: { commitments: context.commitments },
7045
+ state: context.merkleState,
6705
7046
  destinationStellarAddress: prepared.intent.to,
6706
7047
  privKeyScalarHex: context.senderPrivKeyScalarHex,
6707
7048
  ...dualWithdrawEphemeralKeys({ context, secondary }),