@ocash/sdk 0.1.4-rc.3 → 0.1.4-rc.4

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.cjs CHANGED
@@ -3421,7 +3421,7 @@ var MemoryStore = class {
3421
3421
  async getMerkleLeaf(chainId, cid) {
3422
3422
  const rows = this.merkleLeavesByChain.get(chainId);
3423
3423
  const row = rows?.[cid];
3424
- if (!row) return void 0;
3424
+ if (!row || row.cid !== cid) return void 0;
3425
3425
  return { chainId, cid: row.cid, commitment: row.commitment };
3426
3426
  }
3427
3427
  /**
@@ -4438,7 +4438,7 @@ var KeyValueStore = class {
4438
4438
  if (!this.merkleLeafCids[String(chainId)]?.has(cid)) return void 0;
4439
4439
  const raw = await this.options.client.get(this.sharedRecordKey("merkleLeaves", chainId, cid));
4440
4440
  const row = this.parseJson(raw, null);
4441
- if (!row) return void 0;
4441
+ if (!row || row.cid !== cid) return void 0;
4442
4442
  return { chainId, cid: row.cid, commitment: row.commitment };
4443
4443
  }
4444
4444
  async appendMerkleLeaves(chainId, leaves) {
@@ -5200,10 +5200,10 @@ var ProofEngine = class {
5200
5200
  merkle_root_index: parsed.merkle_root_index ?? context.merkle_root_index,
5201
5201
  relayer: parsed.relayer ?? context.relayer,
5202
5202
  recipient: parsed.recipient ?? context.recipient,
5203
- withdraw_amount: parsed.withdraw_amount ? BigInt(parsed.withdraw_amount) : context.withdraw_amount,
5203
+ withdraw_amount: parsed.withdraw_amount != null ? BigInt(parsed.withdraw_amount) : context.withdraw_amount,
5204
5204
  extra_data: parsed.extra_data ?? context.extra_data,
5205
- relayer_fee: parsed.relayer_fee ? BigInt(parsed.relayer_fee) : context.relayer_fee,
5206
- gas_drop_value: parsed.gas_drop_value ? BigInt(parsed.gas_drop_value) : context.gas_drop_value,
5205
+ relayer_fee: parsed.relayer_fee != null ? BigInt(parsed.relayer_fee) : context.relayer_fee,
5206
+ gas_drop_value: parsed.gas_drop_value != null ? BigInt(parsed.gas_drop_value) : context.gas_drop_value,
5207
5207
  array_hash_digest: parsed.array_hash_digest ?? context.array_hash_digest,
5208
5208
  gnark_output: parsed.gnark_output,
5209
5209
  witness_json: parsed.witness_json,
@@ -6326,11 +6326,32 @@ var SyncEngine = class {
6326
6326
  };
6327
6327
 
6328
6328
  // src/planner/planner.ts
6329
+ var import_viem8 = require("viem");
6330
+
6331
+ // src/utils/validators.ts
6329
6332
  var import_viem7 = require("viem");
6330
6333
  var requireHex = (value, name) => {
6331
6334
  if (isHexStrict(value, { minBytes: 1 })) return value;
6332
6335
  throw new SdkError("CONFIG", `${name} must be a hex string starting with 0x`);
6333
6336
  };
6337
+ var requireNumber = (value, name) => {
6338
+ if (typeof value === "number" && Number.isFinite(value)) return value;
6339
+ throw new SdkError("CONFIG", `${name} must be a finite number`);
6340
+ };
6341
+ var requireAddress = (value, name) => {
6342
+ if (typeof value !== "string") {
6343
+ throw new SdkError("CONFIG", `${name} must be a string address`);
6344
+ }
6345
+ return (0, import_viem7.getAddress)(value);
6346
+ };
6347
+ var requireBigint = (value, name) => {
6348
+ if (typeof value === "bigint") return value;
6349
+ if (typeof value === "string" && value.length) return BigInt(value);
6350
+ if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
6351
+ throw new SdkError("CONFIG", `${name} must be a bigint-compatible value`);
6352
+ };
6353
+
6354
+ // src/planner/planner.ts
6334
6355
  var parsePlanInput = (input) => {
6335
6356
  const action = input.action;
6336
6357
  if (action !== "transfer" && action !== "withdraw") {
@@ -6359,7 +6380,7 @@ var parsePlanInput = (input) => {
6359
6380
  if (gasDropValue != null && typeof gasDropValue !== "bigint") throw new SdkError("CONFIG", "gasDropValue must be bigint");
6360
6381
  return { action, chainId, assetId, amount, recipient, gasDropValue, payIncludesFee, relayerUrl: relayerUrl ?? void 0 };
6361
6382
  };
6362
- var tokenFeeKey = (token) => (0, import_viem7.toHex)(BigInt(token.id), { size: 32 }).toLowerCase();
6383
+ var tokenFeeKey = (token) => (0, import_viem8.toHex)(BigInt(token.id), { size: 32 }).toLowerCase();
6363
6384
  var selectTransferInputs = (utxos, required, maxInputs = 3) => {
6364
6385
  const sorted = [...utxos].sort((a, b) => b.amount > a.amount ? 1 : b.amount < a.amount ? -1 : 0);
6365
6386
  const selected = [];
@@ -6399,7 +6420,7 @@ var recordsFee = (input, _records, expectedOutput, action, relayerFee, expectedI
6399
6420
  total = records.reduce((acc, cur) => acc + cur, 0n);
6400
6421
  fee = BigInt(feeCount) * relayerFee.transfer;
6401
6422
  relayFee = fee;
6402
- if (import_viem7.maxUint256 === expectedOutput) {
6423
+ if (import_viem8.maxUint256 === expectedOutput) {
6403
6424
  cost = total;
6404
6425
  outputAmount = total - fee;
6405
6426
  if (outputAmount < 0n) {
@@ -6408,9 +6429,12 @@ var recordsFee = (input, _records, expectedOutput, action, relayerFee, expectedI
6408
6429
  }
6409
6430
  } else {
6410
6431
  cost = expectedIsWithFee ? expectedOutput : expectedOutput + fee;
6432
+ outputAmount = expectedIsWithFee ? expectedOutput - fee : expectedOutput;
6433
+ if (outputAmount < 0n) outputAmount = 0n;
6411
6434
  }
6412
6435
  if (total < cost) {
6413
6436
  cost = 0n;
6437
+ outputAmount = 0n;
6414
6438
  }
6415
6439
  break;
6416
6440
  }
@@ -6421,7 +6445,7 @@ var recordsFee = (input, _records, expectedOutput, action, relayerFee, expectedI
6421
6445
  const withdrawFeeDenominator = bpsBase + withdrawFeeBps;
6422
6446
  total = records.reduce((acc, cur) => acc + cur, 0n);
6423
6447
  fee = BigInt(feeCount - 1) * relayerFee.transfer;
6424
- if (import_viem7.maxUint256 === expectedOutput) {
6448
+ if (import_viem8.maxUint256 === expectedOutput) {
6425
6449
  const withdrawBase = (total - fee) * bpsBase / withdrawFeeDenominator;
6426
6450
  outputAmount = withdrawBase - relayFeePay;
6427
6451
  relayFee = fee + relayFeePay;
@@ -6492,7 +6516,7 @@ var estimateRecords = (input) => {
6492
6516
  const payRecords = [];
6493
6517
  let payInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...payRecords], input.expectedOutput, input.action, input.relayerFee, input.expectedIsWithFee);
6494
6518
  const maxRecords = [];
6495
- let maxInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem7.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6519
+ let maxInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem8.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6496
6520
  for (const record of sorted) {
6497
6521
  const isExceedPay = input.expectedIsWithFee ? payInfo.cost >= input.expectedOutput : payInfo.outputAmount >= input.expectedOutput;
6498
6522
  if (payInfo.cost === 0n || !isExceedPay) {
@@ -6500,7 +6524,7 @@ var estimateRecords = (input) => {
6500
6524
  payInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...payRecords], input.expectedOutput, input.action, input.relayerFee, input.expectedIsWithFee);
6501
6525
  }
6502
6526
  maxRecords.push(record);
6503
- const tempMax = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem7.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6527
+ const tempMax = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem8.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6504
6528
  if (maxInfo.cost === 0n || tempMax.outputAmount > maxInfo.outputAmount) {
6505
6529
  maxInfo = tempMax;
6506
6530
  }
@@ -6665,7 +6689,7 @@ var Planner = class {
6665
6689
  const records = utxos.map((u) => u.amount).filter((v) => v > 0n);
6666
6690
  const estimates = estimateRecords({
6667
6691
  records,
6668
- expectedOutput: import_viem7.maxUint256,
6692
+ expectedOutput: import_viem8.maxUint256,
6669
6693
  action: input.action,
6670
6694
  relayerFee: { transfer: transferFee, withdraw: relayerFee },
6671
6695
  withdrawFeeBps: token.withdrawFeeBps,
@@ -6896,27 +6920,6 @@ var Planner = class {
6896
6920
  };
6897
6921
 
6898
6922
  // src/tx/txBuilder.ts
6899
- var import_viem8 = require("viem");
6900
- var requireNumber = (value, name) => {
6901
- if (typeof value === "number" && Number.isFinite(value)) return value;
6902
- throw new SdkError("CONFIG", `Missing ${name}`);
6903
- };
6904
- var requireHex2 = (value, name) => {
6905
- if (isHexStrict(value, { minBytes: 1 })) return value;
6906
- throw new SdkError("CONFIG", `Missing ${name}`);
6907
- };
6908
- var requireAddress = (value, name) => {
6909
- if (typeof value !== "string") {
6910
- throw new SdkError("CONFIG", `Missing ${name}`);
6911
- }
6912
- return (0, import_viem8.getAddress)(value);
6913
- };
6914
- var requireBigint = (value, name) => {
6915
- if (typeof value === "bigint") return value;
6916
- if (typeof value === "string" && value.length) return BigInt(value);
6917
- if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
6918
- throw new SdkError("CONFIG", `Missing ${name}`);
6919
- };
6920
6923
  var TxBuilder = class {
6921
6924
  /**
6922
6925
  * Build relayer request for transfer proofs.
@@ -6930,7 +6933,7 @@ var TxBuilder = class {
6930
6933
  if (!Array.isArray(extraData) || extraData.length !== 3) {
6931
6934
  throw new SdkError("CONFIG", "Transfer requires extra_data as bytes[3]");
6932
6935
  }
6933
- extraData.forEach((entry, idx) => requireHex2(entry, `extra_data[${idx}]`));
6936
+ extraData.forEach((entry, idx) => requireHex(entry, `extra_data[${idx}]`));
6934
6937
  const request = {
6935
6938
  kind: "relayer",
6936
6939
  method: "POST",
@@ -6963,7 +6966,7 @@ var TxBuilder = class {
6963
6966
  if (Array.isArray(extraData)) {
6964
6967
  throw new SdkError("CONFIG", "Withdraw requires extra_data as bytes");
6965
6968
  }
6966
- const extraDataHex = requireHex2(extraData, "extra_data");
6969
+ const extraDataHex = requireHex(extraData, "extra_data");
6967
6970
  const request = {
6968
6971
  kind: "relayer",
6969
6972
  method: "POST",
@@ -7472,44 +7475,64 @@ var MerkleEngine = class _MerkleEngine {
7472
7475
  await this.hydrateFromStorage(input.chainId);
7473
7476
  const canUseLocal = this.mode !== "remote";
7474
7477
  if (canUseLocal) {
7475
- const version = contractTreeElements > 0 ? await this.storage?.getChairmanMerkleVersion?.(input.chainId, contractTreeElements) : void 0;
7476
- const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.getChairmanMerkleNode === "function" && (contractTreeElements === 0 || !!version);
7477
- if (hasDb) {
7478
+ const hasMerkleLeaf = typeof this.storage?.getMerkleLeaf === "function";
7479
+ const hasChairmanNode = typeof this.storage?.getChairmanMerkleNode === "function";
7480
+ if (hasMerkleLeaf && hasChairmanNode) {
7478
7481
  const state = this.ensureChainState(input.chainId);
7479
- if (contractTreeElements > 0 && state.mergedElements < contractTreeElements) {
7480
- if (this.mode === "local") {
7481
- throw new SdkError("MERKLE", "Local merkle db is behind contract", {
7482
- chainId: input.chainId,
7483
- cids,
7484
- localMergedElements: state.mergedElements,
7485
- contractTreeElements
7486
- });
7487
- }
7488
- } else {
7489
- try {
7490
- const proof = [];
7491
- for (const cid of cids) {
7492
- if (cid >= contractTreeElements) {
7493
- proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
7494
- continue;
7495
- }
7496
- const path2 = await this.buildLocalProofPath(input.chainId, cid, version);
7497
- proof.push({ leaf_index: cid, path: path2 });
7482
+ let version = contractTreeElements > 0 ? await this.storage?.getChairmanMerkleVersion?.(input.chainId, contractTreeElements) : void 0;
7483
+ let effectiveTreeElements = contractTreeElements;
7484
+ if (!version && contractTreeElements > 0 && state.mergedElements > 0) {
7485
+ const latest = await this.storage?.getLatestChairmanMerkleVersion?.(input.chainId);
7486
+ if (latest && latest.version > 0) {
7487
+ const allCovered = needsTreeProof.every((cid) => cid < latest.version);
7488
+ if (allCovered) {
7489
+ version = latest;
7490
+ effectiveTreeElements = latest.version;
7498
7491
  }
7499
- const effectiveRoot = contractTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
7500
- return {
7501
- proof,
7502
- merkle_root: effectiveRoot,
7503
- latest_cid: totalElements > 0n ? Number(totalElements - 1n) : -1
7504
- };
7505
- } catch (error) {
7492
+ }
7493
+ }
7494
+ const hasDb = contractTreeElements === 0 || !!version;
7495
+ if (hasDb) {
7496
+ if (effectiveTreeElements > 0 && state.mergedElements < effectiveTreeElements) {
7506
7497
  if (this.mode === "local") {
7507
- throw new SdkError("MERKLE", "Local merkle proof build failed", { chainId: input.chainId, cids }, error);
7498
+ throw new SdkError("MERKLE", "Local merkle db is behind contract", {
7499
+ chainId: input.chainId,
7500
+ cids,
7501
+ localMergedElements: state.mergedElements,
7502
+ contractTreeElements: effectiveTreeElements
7503
+ });
7508
7504
  }
7505
+ } else {
7506
+ try {
7507
+ const proof = [];
7508
+ for (const cid of cids) {
7509
+ if (cid >= effectiveTreeElements) {
7510
+ proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
7511
+ continue;
7512
+ }
7513
+ const path2 = await this.buildLocalProofPath(input.chainId, cid, version);
7514
+ proof.push({ leaf_index: cid, path: path2 });
7515
+ }
7516
+ const effectiveRoot = effectiveTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
7517
+ const effectiveLatestCid = effectiveTreeElements > 0 ? effectiveTreeElements - 1 : -1;
7518
+ return {
7519
+ proof,
7520
+ merkle_root: effectiveRoot,
7521
+ latest_cid: effectiveLatestCid
7522
+ };
7523
+ } catch (error) {
7524
+ if (this.mode === "local") {
7525
+ throw new SdkError("MERKLE", "Local merkle proof build failed", { chainId: input.chainId, cids }, error);
7526
+ }
7527
+ }
7528
+ }
7529
+ } else {
7530
+ if (this.mode === "local" && needsTreeProof.length) {
7531
+ throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
7509
7532
  }
7510
7533
  }
7511
7534
  } else if (this.mode === "local" && needsTreeProof.length) {
7512
- throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
7535
+ throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter" });
7513
7536
  }
7514
7537
  }
7515
7538
  if (needsTreeProof.length === 0) {
@@ -8788,6 +8811,9 @@ function hydrateWalletState(state) {
8788
8811
  }
8789
8812
 
8790
8813
  // src/store/fileStore.ts
8814
+ function isEnoent(err) {
8815
+ return !!err && typeof err === "object" && "code" in err && err.code === "ENOENT";
8816
+ }
8791
8817
  var FileStore = class {
8792
8818
  /**
8793
8819
  * Create a FileStore with a base directory and optional limits.
@@ -8850,26 +8876,28 @@ var FileStore = class {
8850
8876
  return import_node_path.default.join(this.options.baseDir, `shared.merkle.${chainId}.jsonl`);
8851
8877
  }
8852
8878
  async readMerkleFile(filePath) {
8879
+ let raw;
8853
8880
  try {
8854
- const raw = await (0, import_promises.readFile)(filePath, "utf8");
8855
- const out = [];
8856
- const lines = raw.split("\n").filter((l) => l.trim().length > 0);
8857
- for (const line of lines) {
8858
- try {
8859
- const row = JSON.parse(line);
8860
- const cid = Number(row?.cid);
8861
- const commitment2 = row?.commitment;
8862
- if (!Number.isFinite(cid) || cid < 0) continue;
8863
- if (typeof commitment2 !== "string" || !commitment2.startsWith("0x")) continue;
8864
- out.push({ cid: Math.floor(cid), commitment: commitment2 });
8865
- } catch {
8866
- }
8867
- }
8868
- out.sort((a, b) => a.cid - b.cid);
8869
- return out.length ? out : void 0;
8870
- } catch {
8881
+ raw = await (0, import_promises.readFile)(filePath, "utf8");
8882
+ } catch (err) {
8883
+ if (!isEnoent(err)) throw err;
8871
8884
  return void 0;
8872
8885
  }
8886
+ const out = [];
8887
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
8888
+ for (const line of lines) {
8889
+ try {
8890
+ const row = JSON.parse(line);
8891
+ const cid = Number(row?.cid);
8892
+ const commitment2 = row?.commitment;
8893
+ if (!Number.isFinite(cid) || cid < 0) continue;
8894
+ if (typeof commitment2 !== "string" || !commitment2.startsWith("0x")) continue;
8895
+ out.push({ cid: Math.floor(cid), commitment: commitment2 });
8896
+ } catch {
8897
+ }
8898
+ }
8899
+ out.sort((a, b) => a.cid - b.cid);
8900
+ return out.length ? out : void 0;
8873
8901
  }
8874
8902
  /**
8875
8903
  * Infer the next merkle cid from the tail of the jsonl file.
@@ -8886,10 +8914,14 @@ var FileStore = class {
8886
8914
  }
8887
8915
  const last = JSON.parse(lines[lines.length - 1]);
8888
8916
  const cid = Number(last?.cid);
8889
- const next = Number.isFinite(cid) ? Math.max(0, Math.floor(cid) + 1) : 0;
8917
+ if (!Number.isFinite(cid)) {
8918
+ throw new Error(`corrupted merkle jsonl: missing or non-numeric cid in tail of ${this.merkleFilePath(chainId)}`);
8919
+ }
8920
+ const next = Math.max(0, Math.floor(cid) + 1);
8890
8921
  this.merkleNextCid.set(chainId, next);
8891
8922
  return next;
8892
- } catch {
8923
+ } catch (err) {
8924
+ if (!isEnoent(err)) throw err;
8893
8925
  this.merkleNextCid.set(chainId, 0);
8894
8926
  return 0;
8895
8927
  }
@@ -8916,7 +8948,8 @@ var FileStore = class {
8916
8948
  for (const [k, v] of hydrated.utxos.entries()) this.utxos.set(k, v);
8917
8949
  const operations = Array.isArray(parsed.operations) ? parsed.operations : [];
8918
8950
  this.operations = operations;
8919
- } catch {
8951
+ } catch (err) {
8952
+ if (!isEnoent(err)) throw err;
8920
8953
  }
8921
8954
  try {
8922
8955
  const raw = await (0, import_promises.readFile)(this.sharedFilePath(), "utf8");
@@ -8948,7 +8981,8 @@ var FileStore = class {
8948
8981
  if (entryNullifiersRaw && typeof entryNullifiersRaw === "object") {
8949
8982
  this.entryNullifiers = entryNullifiersRaw;
8950
8983
  }
8951
- } catch {
8984
+ } catch (err) {
8985
+ if (!isEnoent(err)) throw err;
8952
8986
  }
8953
8987
  const pruned = this.pruneOperations();
8954
8988
  if (pruned) void this.saveWallet().catch(() => void 0);
@@ -9219,7 +9253,7 @@ var FileStore = class {
9219
9253
  async getMerkleLeaf(chainId, cid) {
9220
9254
  const rows = await this.getMerkleLeaves(chainId);
9221
9255
  const row = rows?.[cid];
9222
- if (!row) return void 0;
9256
+ if (!row || row.cid !== cid) return void 0;
9223
9257
  return { chainId, cid: row.cid, commitment: row.commitment };
9224
9258
  }
9225
9259
  /**