@ocash/sdk 0.1.4-rc.2 → 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/browser.cjs CHANGED
@@ -3420,7 +3420,7 @@ var MemoryStore = class {
3420
3420
  async getMerkleLeaf(chainId, cid) {
3421
3421
  const rows = this.merkleLeavesByChain.get(chainId);
3422
3422
  const row = rows?.[cid];
3423
- if (!row) return void 0;
3423
+ if (!row || row.cid !== cid) return void 0;
3424
3424
  return { chainId, cid: row.cid, commitment: row.commitment };
3425
3425
  }
3426
3426
  /**
@@ -4437,7 +4437,7 @@ var KeyValueStore = class {
4437
4437
  if (!this.merkleLeafCids[String(chainId)]?.has(cid)) return void 0;
4438
4438
  const raw = await this.options.client.get(this.sharedRecordKey("merkleLeaves", chainId, cid));
4439
4439
  const row = this.parseJson(raw, null);
4440
- if (!row) return void 0;
4440
+ if (!row || row.cid !== cid) return void 0;
4441
4441
  return { chainId, cid: row.cid, commitment: row.commitment };
4442
4442
  }
4443
4443
  async appendMerkleLeaves(chainId, leaves) {
@@ -5199,10 +5199,10 @@ var ProofEngine = class {
5199
5199
  merkle_root_index: parsed.merkle_root_index ?? context.merkle_root_index,
5200
5200
  relayer: parsed.relayer ?? context.relayer,
5201
5201
  recipient: parsed.recipient ?? context.recipient,
5202
- withdraw_amount: parsed.withdraw_amount ? BigInt(parsed.withdraw_amount) : context.withdraw_amount,
5202
+ withdraw_amount: parsed.withdraw_amount != null ? BigInt(parsed.withdraw_amount) : context.withdraw_amount,
5203
5203
  extra_data: parsed.extra_data ?? context.extra_data,
5204
- relayer_fee: parsed.relayer_fee ? BigInt(parsed.relayer_fee) : context.relayer_fee,
5205
- gas_drop_value: parsed.gas_drop_value ? BigInt(parsed.gas_drop_value) : context.gas_drop_value,
5204
+ relayer_fee: parsed.relayer_fee != null ? BigInt(parsed.relayer_fee) : context.relayer_fee,
5205
+ gas_drop_value: parsed.gas_drop_value != null ? BigInt(parsed.gas_drop_value) : context.gas_drop_value,
5206
5206
  array_hash_digest: parsed.array_hash_digest ?? context.array_hash_digest,
5207
5207
  gnark_output: parsed.gnark_output,
5208
5208
  witness_json: parsed.witness_json,
@@ -6325,11 +6325,32 @@ var SyncEngine = class {
6325
6325
  };
6326
6326
 
6327
6327
  // src/planner/planner.ts
6328
+ var import_viem8 = require("viem");
6329
+
6330
+ // src/utils/validators.ts
6328
6331
  var import_viem7 = require("viem");
6329
6332
  var requireHex = (value, name) => {
6330
6333
  if (isHexStrict(value, { minBytes: 1 })) return value;
6331
6334
  throw new SdkError("CONFIG", `${name} must be a hex string starting with 0x`);
6332
6335
  };
6336
+ var requireNumber = (value, name) => {
6337
+ if (typeof value === "number" && Number.isFinite(value)) return value;
6338
+ throw new SdkError("CONFIG", `${name} must be a finite number`);
6339
+ };
6340
+ var requireAddress = (value, name) => {
6341
+ if (typeof value !== "string") {
6342
+ throw new SdkError("CONFIG", `${name} must be a string address`);
6343
+ }
6344
+ return (0, import_viem7.getAddress)(value);
6345
+ };
6346
+ var requireBigint = (value, name) => {
6347
+ if (typeof value === "bigint") return value;
6348
+ if (typeof value === "string" && value.length) return BigInt(value);
6349
+ if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
6350
+ throw new SdkError("CONFIG", `${name} must be a bigint-compatible value`);
6351
+ };
6352
+
6353
+ // src/planner/planner.ts
6333
6354
  var parsePlanInput = (input) => {
6334
6355
  const action = input.action;
6335
6356
  if (action !== "transfer" && action !== "withdraw") {
@@ -6358,7 +6379,7 @@ var parsePlanInput = (input) => {
6358
6379
  if (gasDropValue != null && typeof gasDropValue !== "bigint") throw new SdkError("CONFIG", "gasDropValue must be bigint");
6359
6380
  return { action, chainId, assetId, amount, recipient, gasDropValue, payIncludesFee, relayerUrl: relayerUrl ?? void 0 };
6360
6381
  };
6361
- var tokenFeeKey = (token) => (0, import_viem7.toHex)(BigInt(token.id), { size: 32 }).toLowerCase();
6382
+ var tokenFeeKey = (token) => (0, import_viem8.toHex)(BigInt(token.id), { size: 32 }).toLowerCase();
6362
6383
  var selectTransferInputs = (utxos, required, maxInputs = 3) => {
6363
6384
  const sorted = [...utxos].sort((a, b) => b.amount > a.amount ? 1 : b.amount < a.amount ? -1 : 0);
6364
6385
  const selected = [];
@@ -6398,7 +6419,7 @@ var recordsFee = (input, _records, expectedOutput, action, relayerFee, expectedI
6398
6419
  total = records.reduce((acc, cur) => acc + cur, 0n);
6399
6420
  fee = BigInt(feeCount) * relayerFee.transfer;
6400
6421
  relayFee = fee;
6401
- if (import_viem7.maxUint256 === expectedOutput) {
6422
+ if (import_viem8.maxUint256 === expectedOutput) {
6402
6423
  cost = total;
6403
6424
  outputAmount = total - fee;
6404
6425
  if (outputAmount < 0n) {
@@ -6407,9 +6428,12 @@ var recordsFee = (input, _records, expectedOutput, action, relayerFee, expectedI
6407
6428
  }
6408
6429
  } else {
6409
6430
  cost = expectedIsWithFee ? expectedOutput : expectedOutput + fee;
6431
+ outputAmount = expectedIsWithFee ? expectedOutput - fee : expectedOutput;
6432
+ if (outputAmount < 0n) outputAmount = 0n;
6410
6433
  }
6411
6434
  if (total < cost) {
6412
6435
  cost = 0n;
6436
+ outputAmount = 0n;
6413
6437
  }
6414
6438
  break;
6415
6439
  }
@@ -6420,7 +6444,7 @@ var recordsFee = (input, _records, expectedOutput, action, relayerFee, expectedI
6420
6444
  const withdrawFeeDenominator = bpsBase + withdrawFeeBps;
6421
6445
  total = records.reduce((acc, cur) => acc + cur, 0n);
6422
6446
  fee = BigInt(feeCount - 1) * relayerFee.transfer;
6423
- if (import_viem7.maxUint256 === expectedOutput) {
6447
+ if (import_viem8.maxUint256 === expectedOutput) {
6424
6448
  const withdrawBase = (total - fee) * bpsBase / withdrawFeeDenominator;
6425
6449
  outputAmount = withdrawBase - relayFeePay;
6426
6450
  relayFee = fee + relayFeePay;
@@ -6491,7 +6515,7 @@ var estimateRecords = (input) => {
6491
6515
  const payRecords = [];
6492
6516
  let payInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...payRecords], input.expectedOutput, input.action, input.relayerFee, input.expectedIsWithFee);
6493
6517
  const maxRecords = [];
6494
- let maxInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem7.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6518
+ let maxInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem8.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6495
6519
  for (const record of sorted) {
6496
6520
  const isExceedPay = input.expectedIsWithFee ? payInfo.cost >= input.expectedOutput : payInfo.outputAmount >= input.expectedOutput;
6497
6521
  if (payInfo.cost === 0n || !isExceedPay) {
@@ -6499,7 +6523,7 @@ var estimateRecords = (input) => {
6499
6523
  payInfo = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...payRecords], input.expectedOutput, input.action, input.relayerFee, input.expectedIsWithFee);
6500
6524
  }
6501
6525
  maxRecords.push(record);
6502
- const tempMax = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem7.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6526
+ const tempMax = recordsFee({ withdrawFeeBps: input.withdrawFeeBps }, [...maxRecords], import_viem8.maxUint256, input.action, input.relayerFee, input.expectedIsWithFee);
6503
6527
  if (maxInfo.cost === 0n || tempMax.outputAmount > maxInfo.outputAmount) {
6504
6528
  maxInfo = tempMax;
6505
6529
  }
@@ -6664,7 +6688,7 @@ var Planner = class {
6664
6688
  const records = utxos.map((u) => u.amount).filter((v) => v > 0n);
6665
6689
  const estimates = estimateRecords({
6666
6690
  records,
6667
- expectedOutput: import_viem7.maxUint256,
6691
+ expectedOutput: import_viem8.maxUint256,
6668
6692
  action: input.action,
6669
6693
  relayerFee: { transfer: transferFee, withdraw: relayerFee },
6670
6694
  withdrawFeeBps: token.withdrawFeeBps,
@@ -6895,27 +6919,6 @@ var Planner = class {
6895
6919
  };
6896
6920
 
6897
6921
  // src/tx/txBuilder.ts
6898
- var import_viem8 = require("viem");
6899
- var requireNumber = (value, name) => {
6900
- if (typeof value === "number" && Number.isFinite(value)) return value;
6901
- throw new SdkError("CONFIG", `Missing ${name}`);
6902
- };
6903
- var requireHex2 = (value, name) => {
6904
- if (isHexStrict(value, { minBytes: 1 })) return value;
6905
- throw new SdkError("CONFIG", `Missing ${name}`);
6906
- };
6907
- var requireAddress = (value, name) => {
6908
- if (typeof value !== "string") {
6909
- throw new SdkError("CONFIG", `Missing ${name}`);
6910
- }
6911
- return (0, import_viem8.getAddress)(value);
6912
- };
6913
- var requireBigint = (value, name) => {
6914
- if (typeof value === "bigint") return value;
6915
- if (typeof value === "string" && value.length) return BigInt(value);
6916
- if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
6917
- throw new SdkError("CONFIG", `Missing ${name}`);
6918
- };
6919
6922
  var TxBuilder = class {
6920
6923
  /**
6921
6924
  * Build relayer request for transfer proofs.
@@ -6929,7 +6932,7 @@ var TxBuilder = class {
6929
6932
  if (!Array.isArray(extraData) || extraData.length !== 3) {
6930
6933
  throw new SdkError("CONFIG", "Transfer requires extra_data as bytes[3]");
6931
6934
  }
6932
- extraData.forEach((entry, idx) => requireHex2(entry, `extra_data[${idx}]`));
6935
+ extraData.forEach((entry, idx) => requireHex(entry, `extra_data[${idx}]`));
6933
6936
  const request = {
6934
6937
  kind: "relayer",
6935
6938
  method: "POST",
@@ -6962,7 +6965,7 @@ var TxBuilder = class {
6962
6965
  if (Array.isArray(extraData)) {
6963
6966
  throw new SdkError("CONFIG", "Withdraw requires extra_data as bytes");
6964
6967
  }
6965
- const extraDataHex = requireHex2(extraData, "extra_data");
6968
+ const extraDataHex = requireHex(extraData, "extra_data");
6966
6969
  const request = {
6967
6970
  kind: "relayer",
6968
6971
  method: "POST",
@@ -7363,7 +7366,7 @@ var MerkleEngine = class _MerkleEngine {
7363
7366
  const isZero = BigInt(onChainNorm) === 0n;
7364
7367
  if (!isZero && onChainNorm !== result.rootHash) {
7365
7368
  const target = state.mergedElements;
7366
- await this.rollback(chainId, target);
7369
+ await this._rollback(chainId, target);
7367
7370
  throw new SdkError("MERKLE", "Local merkle root mismatch with on-chain root \u2014 rolled back", {
7368
7371
  chainId,
7369
7372
  rootIndex,
@@ -7396,21 +7399,31 @@ var MerkleEngine = class _MerkleEngine {
7396
7399
  }
7397
7400
  // ── Rollback (tree O(1) + sync cursor reset) ──
7398
7401
  /**
7399
- * Unified rollback: rewind tree to a previous batch boundary AND reset the
7400
- * sync cursor so memo sync restarts from the same point.
7402
+ * Public rollback: step back one batch (32 elements) from the current position.
7403
+ * Upper-layer code calls this on any error to reset and retry.
7401
7404
  *
7402
7405
  * What gets rolled back:
7403
7406
  * - ChairmanMerkle tree version pointer (O(1) — old nodes still in storage)
7404
7407
  * - Pending leaves buffer (cleared)
7405
7408
  * - Sync cursor: memo + merkle fields (nullifier left unchanged — independent)
7406
7409
  *
7410
+ * @returns true if rollback succeeded, false if already at 0 or target version doesn't exist.
7411
+ */
7412
+ async rollback(chainId) {
7413
+ const state = this.ensureChainState(chainId);
7414
+ const target = Math.max(0, state.mergedElements - SUBTREE_SIZE);
7415
+ return this._rollback(chainId, target);
7416
+ }
7417
+ /**
7418
+ * Internal rollback to an exact batch boundary.
7419
+ *
7407
7420
  * @param targetMergedElements Must be a non-negative multiple of 32.
7408
7421
  * Pass 0 to reset to the empty tree.
7409
7422
  * @returns true if rollback succeeded, false if the target version doesn't exist.
7410
7423
  */
7411
- async rollback(chainId, targetMergedElements) {
7424
+ async _rollback(chainId, targetMergedElements) {
7412
7425
  if (targetMergedElements < 0 || targetMergedElements % SUBTREE_SIZE !== 0) {
7413
- throw new SdkError("MERKLE", "rollback target must be a non-negative multiple of 32", { targetMergedElements });
7426
+ throw new SdkError("MERKLE", "_rollback target must be a non-negative multiple of 32", { targetMergedElements });
7414
7427
  }
7415
7428
  const state = this.ensureChainState(chainId);
7416
7429
  const pending = this.ensurePendingLeaves(chainId);
@@ -7461,44 +7474,64 @@ var MerkleEngine = class _MerkleEngine {
7461
7474
  await this.hydrateFromStorage(input.chainId);
7462
7475
  const canUseLocal = this.mode !== "remote";
7463
7476
  if (canUseLocal) {
7464
- const version = contractTreeElements > 0 ? await this.storage?.getChairmanMerkleVersion?.(input.chainId, contractTreeElements) : void 0;
7465
- const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.getChairmanMerkleNode === "function" && (contractTreeElements === 0 || !!version);
7466
- if (hasDb) {
7477
+ const hasMerkleLeaf = typeof this.storage?.getMerkleLeaf === "function";
7478
+ const hasChairmanNode = typeof this.storage?.getChairmanMerkleNode === "function";
7479
+ if (hasMerkleLeaf && hasChairmanNode) {
7467
7480
  const state = this.ensureChainState(input.chainId);
7468
- if (contractTreeElements > 0 && state.mergedElements < contractTreeElements) {
7469
- if (this.mode === "local") {
7470
- throw new SdkError("MERKLE", "Local merkle db is behind contract", {
7471
- chainId: input.chainId,
7472
- cids,
7473
- localMergedElements: state.mergedElements,
7474
- contractTreeElements
7475
- });
7476
- }
7477
- } else {
7478
- try {
7479
- const proof = [];
7480
- for (const cid of cids) {
7481
- if (cid >= contractTreeElements) {
7482
- proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
7483
- continue;
7484
- }
7485
- const path = await this.buildLocalProofPath(input.chainId, cid, version);
7486
- proof.push({ leaf_index: cid, path });
7481
+ let version = contractTreeElements > 0 ? await this.storage?.getChairmanMerkleVersion?.(input.chainId, contractTreeElements) : void 0;
7482
+ let effectiveTreeElements = contractTreeElements;
7483
+ if (!version && contractTreeElements > 0 && state.mergedElements > 0) {
7484
+ const latest = await this.storage?.getLatestChairmanMerkleVersion?.(input.chainId);
7485
+ if (latest && latest.version > 0) {
7486
+ const allCovered = needsTreeProof.every((cid) => cid < latest.version);
7487
+ if (allCovered) {
7488
+ version = latest;
7489
+ effectiveTreeElements = latest.version;
7487
7490
  }
7488
- const effectiveRoot = contractTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
7489
- return {
7490
- proof,
7491
- merkle_root: effectiveRoot,
7492
- latest_cid: totalElements > 0n ? Number(totalElements - 1n) : -1
7493
- };
7494
- } catch (error) {
7491
+ }
7492
+ }
7493
+ const hasDb = contractTreeElements === 0 || !!version;
7494
+ if (hasDb) {
7495
+ if (effectiveTreeElements > 0 && state.mergedElements < effectiveTreeElements) {
7495
7496
  if (this.mode === "local") {
7496
- throw new SdkError("MERKLE", "Local merkle proof build failed", { chainId: input.chainId, cids }, error);
7497
+ throw new SdkError("MERKLE", "Local merkle db is behind contract", {
7498
+ chainId: input.chainId,
7499
+ cids,
7500
+ localMergedElements: state.mergedElements,
7501
+ contractTreeElements: effectiveTreeElements
7502
+ });
7503
+ }
7504
+ } else {
7505
+ try {
7506
+ const proof = [];
7507
+ for (const cid of cids) {
7508
+ if (cid >= effectiveTreeElements) {
7509
+ proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
7510
+ continue;
7511
+ }
7512
+ const path = await this.buildLocalProofPath(input.chainId, cid, version);
7513
+ proof.push({ leaf_index: cid, path });
7514
+ }
7515
+ const effectiveRoot = effectiveTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
7516
+ const effectiveLatestCid = effectiveTreeElements > 0 ? effectiveTreeElements - 1 : -1;
7517
+ return {
7518
+ proof,
7519
+ merkle_root: effectiveRoot,
7520
+ latest_cid: effectiveLatestCid
7521
+ };
7522
+ } catch (error) {
7523
+ if (this.mode === "local") {
7524
+ throw new SdkError("MERKLE", "Local merkle proof build failed", { chainId: input.chainId, cids }, error);
7525
+ }
7497
7526
  }
7498
7527
  }
7528
+ } else {
7529
+ if (this.mode === "local" && needsTreeProof.length) {
7530
+ throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
7531
+ }
7499
7532
  }
7500
7533
  } else if (this.mode === "local" && needsTreeProof.length) {
7501
- throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
7534
+ throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter" });
7502
7535
  }
7503
7536
  }
7504
7537
  if (needsTreeProof.length === 0) {