@ocash/sdk 0.1.4-rc.1 → 0.1.4-rc.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/browser.cjs +393 -350
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +18 -16
- package/dist/browser.d.ts +18 -16
- package/dist/browser.js +393 -350
- package/dist/browser.js.map +1 -1
- package/dist/{index-CI7UllxU.d.cts → index-Dvl0HZkw.d.cts} +66 -64
- package/dist/{index-CI7UllxU.d.ts → index-Dvl0HZkw.d.ts} +66 -64
- package/dist/index.cjs +333 -313
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +333 -313
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +439 -401
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +24 -23
- package/dist/node.d.ts +24 -23
- package/dist/node.js +439 -401
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
package/dist/browser.cjs
CHANGED
|
@@ -3290,8 +3290,8 @@ var MemoryStore = class {
|
|
|
3290
3290
|
this.utxos = /* @__PURE__ */ new Map();
|
|
3291
3291
|
this.operations = [];
|
|
3292
3292
|
this.merkleLeavesByChain = /* @__PURE__ */ new Map();
|
|
3293
|
-
this.
|
|
3294
|
-
this.
|
|
3293
|
+
this.chairmanMerkleVersionsByChain = /* @__PURE__ */ new Map();
|
|
3294
|
+
this.chairmanMerkleNodesByChain = /* @__PURE__ */ new Map();
|
|
3295
3295
|
this.entryMemosByChain = /* @__PURE__ */ new Map();
|
|
3296
3296
|
this.entryNullifiersByChain = /* @__PURE__ */ new Map();
|
|
3297
3297
|
const max = options?.maxOperations;
|
|
@@ -3307,8 +3307,8 @@ var MemoryStore = class {
|
|
|
3307
3307
|
this.utxos.clear();
|
|
3308
3308
|
this.operations = [];
|
|
3309
3309
|
this.merkleLeavesByChain.clear();
|
|
3310
|
-
this.
|
|
3311
|
-
this.
|
|
3310
|
+
this.chairmanMerkleVersionsByChain.clear();
|
|
3311
|
+
this.chairmanMerkleNodesByChain.clear();
|
|
3312
3312
|
this.entryMemosByChain.clear();
|
|
3313
3313
|
this.entryNullifiersByChain.clear();
|
|
3314
3314
|
}
|
|
@@ -3424,49 +3424,64 @@ var MemoryStore = class {
|
|
|
3424
3424
|
return { chainId, cid: row.cid, commitment: row.commitment };
|
|
3425
3425
|
}
|
|
3426
3426
|
/**
|
|
3427
|
-
* Get a
|
|
3427
|
+
* Get a chairmanMerkle tree node by id.
|
|
3428
3428
|
*/
|
|
3429
|
-
async
|
|
3430
|
-
return this.
|
|
3429
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
3430
|
+
return this.chairmanMerkleNodesByChain.get(chainId)?.get(id);
|
|
3431
3431
|
}
|
|
3432
3432
|
/**
|
|
3433
|
-
*
|
|
3433
|
+
* Put chairmanMerkle tree nodes for a chain.
|
|
3434
3434
|
*/
|
|
3435
|
-
async
|
|
3435
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
3436
3436
|
if (!nodes.length) return;
|
|
3437
|
-
let map = this.
|
|
3437
|
+
let map = this.chairmanMerkleNodesByChain.get(chainId);
|
|
3438
3438
|
if (!map) {
|
|
3439
3439
|
map = /* @__PURE__ */ new Map();
|
|
3440
|
-
this.
|
|
3440
|
+
this.chairmanMerkleNodesByChain.set(chainId, map);
|
|
3441
3441
|
}
|
|
3442
3442
|
for (const node of nodes) {
|
|
3443
3443
|
map.set(node.id, { ...node, chainId });
|
|
3444
3444
|
}
|
|
3445
3445
|
}
|
|
3446
3446
|
/**
|
|
3447
|
-
*
|
|
3447
|
+
* Get a chairmanMerkle version record by chain and version number.
|
|
3448
3448
|
*/
|
|
3449
|
-
async
|
|
3450
|
-
this.
|
|
3449
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
3450
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3451
|
+
const record = byVersion?.get(version);
|
|
3452
|
+
return record ? { ...record } : void 0;
|
|
3451
3453
|
}
|
|
3452
3454
|
/**
|
|
3453
|
-
* Get
|
|
3455
|
+
* Get the latest chairmanMerkle version record (highest version number) for a chain.
|
|
3454
3456
|
*/
|
|
3455
|
-
async
|
|
3456
|
-
const
|
|
3457
|
-
|
|
3457
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
3458
|
+
const byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3459
|
+
if (!byVersion || byVersion.size === 0) return void 0;
|
|
3460
|
+
let latest;
|
|
3461
|
+
for (const record of byVersion.values()) {
|
|
3462
|
+
if (!latest || record.version > latest.version) {
|
|
3463
|
+
latest = record;
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
return latest ? { ...latest } : void 0;
|
|
3458
3467
|
}
|
|
3459
3468
|
/**
|
|
3460
|
-
* Persist
|
|
3469
|
+
* Persist a chairmanMerkle version record.
|
|
3461
3470
|
*/
|
|
3462
|
-
async
|
|
3463
|
-
this.
|
|
3471
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
3472
|
+
let byVersion = this.chairmanMerkleVersionsByChain.get(chainId);
|
|
3473
|
+
if (!byVersion) {
|
|
3474
|
+
byVersion = /* @__PURE__ */ new Map();
|
|
3475
|
+
this.chairmanMerkleVersionsByChain.set(chainId, byVersion);
|
|
3476
|
+
}
|
|
3477
|
+
byVersion.set(record.version, { ...record, chainId });
|
|
3464
3478
|
}
|
|
3465
3479
|
/**
|
|
3466
|
-
* Clear
|
|
3480
|
+
* Clear all chairmanMerkle tree state (both nodes and versions) for a chain.
|
|
3467
3481
|
*/
|
|
3468
|
-
async
|
|
3469
|
-
this.
|
|
3482
|
+
async clearChairmanMerkleTree(chainId) {
|
|
3483
|
+
this.chairmanMerkleNodesByChain.delete(chainId);
|
|
3484
|
+
this.chairmanMerkleVersionsByChain.delete(chainId);
|
|
3470
3485
|
}
|
|
3471
3486
|
/**
|
|
3472
3487
|
* Upsert entry memos (raw EntryService cache).
|
|
@@ -3990,13 +4005,14 @@ var KeyValueStore = class {
|
|
|
3990
4005
|
this.utxoCache = /* @__PURE__ */ new Map();
|
|
3991
4006
|
this.operationCache = /* @__PURE__ */ new Map();
|
|
3992
4007
|
this.merkleLeafCids = {};
|
|
3993
|
-
this.
|
|
3994
|
-
this.
|
|
4008
|
+
this.chairmanMerkleLatestVersions = {};
|
|
4009
|
+
this.chairmanMerkleNodeIds = {};
|
|
4010
|
+
this.chairmanMerkleVersionNums = {};
|
|
3995
4011
|
this.entryMemoCids = {};
|
|
3996
4012
|
this.entryNullifierNids = {};
|
|
3997
4013
|
this.loadedMerkleLeaves = /* @__PURE__ */ new Set();
|
|
3998
|
-
this.
|
|
3999
|
-
this.
|
|
4014
|
+
this.loadedChairmanMerkleVersions = /* @__PURE__ */ new Set();
|
|
4015
|
+
this.loadedChairmanMerkleNodes = /* @__PURE__ */ new Set();
|
|
4000
4016
|
this.loadedEntryMemos = /* @__PURE__ */ new Set();
|
|
4001
4017
|
this.loadedEntryNullifiers = /* @__PURE__ */ new Set();
|
|
4002
4018
|
this.saveChain = Promise.resolve();
|
|
@@ -4030,9 +4046,6 @@ var KeyValueStore = class {
|
|
|
4030
4046
|
walletOperationKey(id) {
|
|
4031
4047
|
return `${this.walletBaseKey()}:operation:${id}`;
|
|
4032
4048
|
}
|
|
4033
|
-
sharedChainKey(part, chainId) {
|
|
4034
|
-
return `${this.keyPrefix()}:shared:${part}:${chainId}`;
|
|
4035
|
-
}
|
|
4036
4049
|
sharedChainMetaKey(part, chainId) {
|
|
4037
4050
|
return `${this.keyPrefix()}:shared:${part}:${chainId}:meta`;
|
|
4038
4051
|
}
|
|
@@ -4085,13 +4098,14 @@ var KeyValueStore = class {
|
|
|
4085
4098
|
this.operationCache.clear();
|
|
4086
4099
|
this.walletMetaLoaded = false;
|
|
4087
4100
|
this.merkleLeafCids = {};
|
|
4088
|
-
this.
|
|
4089
|
-
this.
|
|
4101
|
+
this.chairmanMerkleLatestVersions = {};
|
|
4102
|
+
this.chairmanMerkleNodeIds = {};
|
|
4103
|
+
this.chairmanMerkleVersionNums = {};
|
|
4090
4104
|
this.entryMemoCids = {};
|
|
4091
4105
|
this.entryNullifierNids = {};
|
|
4092
4106
|
this.loadedMerkleLeaves.clear();
|
|
4093
|
-
this.
|
|
4094
|
-
this.
|
|
4107
|
+
this.loadedChairmanMerkleVersions.clear();
|
|
4108
|
+
this.loadedChairmanMerkleNodes.clear();
|
|
4095
4109
|
this.loadedEntryMemos.clear();
|
|
4096
4110
|
this.loadedEntryNullifiers.clear();
|
|
4097
4111
|
}
|
|
@@ -4186,20 +4200,19 @@ var KeyValueStore = class {
|
|
|
4186
4200
|
this.merkleLeafCids[key] = new Set(this.parseNumberIndex(cidsRaw));
|
|
4187
4201
|
this.loadedMerkleLeaves.add(chainId);
|
|
4188
4202
|
}
|
|
4189
|
-
async
|
|
4190
|
-
if (this.
|
|
4203
|
+
async ensureChairmanMerkleVersionsLoaded(chainId) {
|
|
4204
|
+
if (this.loadedChairmanMerkleVersions.has(chainId)) return;
|
|
4191
4205
|
const key = String(chainId);
|
|
4192
|
-
const
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
this.loadedMerkleTrees.add(chainId);
|
|
4206
|
+
const numsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleVersions", chainId));
|
|
4207
|
+
this.chairmanMerkleVersionNums[key] = new Set(this.parseNumberIndex(numsRaw));
|
|
4208
|
+
this.loadedChairmanMerkleVersions.add(chainId);
|
|
4196
4209
|
}
|
|
4197
|
-
async
|
|
4198
|
-
if (this.
|
|
4210
|
+
async ensureChairmanMerkleNodesLoaded(chainId) {
|
|
4211
|
+
if (this.loadedChairmanMerkleNodes.has(chainId)) return;
|
|
4199
4212
|
const key = String(chainId);
|
|
4200
|
-
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("
|
|
4201
|
-
this.
|
|
4202
|
-
this.
|
|
4213
|
+
const idsRaw = await this.options.client.get(this.sharedChainMetaKey("chairmanMerkleNodes", chainId));
|
|
4214
|
+
this.chairmanMerkleNodeIds[key] = new Set(this.parseStringIndex(idsRaw));
|
|
4215
|
+
this.loadedChairmanMerkleNodes.add(chainId);
|
|
4203
4216
|
}
|
|
4204
4217
|
async ensureEntryMemosLoaded(chainId) {
|
|
4205
4218
|
if (this.loadedEntryMemos.has(chainId)) return;
|
|
@@ -4215,64 +4228,88 @@ var KeyValueStore = class {
|
|
|
4215
4228
|
this.entryNullifierNids[key] = new Set(this.parseNumberIndex(nidsRaw));
|
|
4216
4229
|
this.loadedEntryNullifiers.add(chainId);
|
|
4217
4230
|
}
|
|
4218
|
-
async
|
|
4219
|
-
await this.
|
|
4220
|
-
if (!this.
|
|
4221
|
-
const raw = await this.options.client.get(this.sharedRecordKey("
|
|
4231
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
4232
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4233
|
+
if (!this.chairmanMerkleNodeIds[String(chainId)]?.has(id)) return void 0;
|
|
4234
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleNodes", chainId, id));
|
|
4222
4235
|
const node = this.parseJson(raw, null);
|
|
4223
4236
|
if (!node) return void 0;
|
|
4224
4237
|
const hash = node.hash;
|
|
4225
4238
|
if (typeof hash !== "string" || !hash.startsWith("0x")) return void 0;
|
|
4226
4239
|
return { ...node, chainId };
|
|
4227
4240
|
}
|
|
4228
|
-
async
|
|
4241
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
4229
4242
|
if (!nodes.length) return;
|
|
4230
|
-
await this.
|
|
4243
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4231
4244
|
const key = String(chainId);
|
|
4232
|
-
const ids = this.
|
|
4245
|
+
const ids = this.chairmanMerkleNodeIds[key] ?? /* @__PURE__ */ new Set();
|
|
4233
4246
|
const beforeSize = ids.size;
|
|
4234
4247
|
for (const node of nodes) {
|
|
4235
4248
|
ids.add(node.id);
|
|
4236
4249
|
}
|
|
4237
|
-
this.
|
|
4250
|
+
this.chairmanMerkleNodeIds[key] = ids;
|
|
4238
4251
|
await this.enqueueWrite(async () => {
|
|
4239
|
-
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("
|
|
4252
|
+
await Promise.all(nodes.map((node) => this.writeJson(this.sharedRecordKey("chairmanMerkleNodes", chainId, node.id), { ...node, chainId })));
|
|
4240
4253
|
if (ids.size !== beforeSize) {
|
|
4241
|
-
await this.writeJson(this.sharedChainMetaKey("
|
|
4254
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), Array.from(ids));
|
|
4242
4255
|
}
|
|
4243
4256
|
});
|
|
4244
4257
|
}
|
|
4245
|
-
async
|
|
4246
|
-
await this.
|
|
4247
|
-
|
|
4248
|
-
|
|
4258
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
4259
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4260
|
+
if (!this.chairmanMerkleVersionNums[String(chainId)]?.has(version)) return void 0;
|
|
4261
|
+
const raw = await this.options.client.get(this.sharedRecordKey("chairmanMerkleVersions", chainId, version));
|
|
4262
|
+
const record = this.parseJson(raw, null);
|
|
4263
|
+
if (!record) return void 0;
|
|
4264
|
+
if (typeof record.rootHash !== "string" || !record.rootHash.startsWith("0x")) return void 0;
|
|
4265
|
+
if (typeof record.rootId !== "string") return void 0;
|
|
4266
|
+
const v = Number(record.version);
|
|
4267
|
+
if (!Number.isFinite(v) || v < 0) return void 0;
|
|
4268
|
+
return { chainId, version: Math.floor(v), rootId: record.rootId, rootHash: record.rootHash };
|
|
4269
|
+
}
|
|
4270
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
4271
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4272
|
+
const nums = this.chairmanMerkleVersionNums[String(chainId)];
|
|
4273
|
+
if (!nums || nums.size === 0) return void 0;
|
|
4274
|
+
const maxVersion = Math.max(...nums);
|
|
4275
|
+
return this.getChairmanMerkleVersion(chainId, maxVersion);
|
|
4276
|
+
}
|
|
4277
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
4278
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4279
|
+
const key = String(chainId);
|
|
4280
|
+
const nums = this.chairmanMerkleVersionNums[key] ?? /* @__PURE__ */ new Set();
|
|
4281
|
+
const beforeSize = nums.size;
|
|
4282
|
+
nums.add(record.version);
|
|
4283
|
+
this.chairmanMerkleVersionNums[key] = nums;
|
|
4284
|
+
const current = this.chairmanMerkleLatestVersions[key];
|
|
4285
|
+
if (!current || record.version >= current.version) {
|
|
4286
|
+
this.chairmanMerkleLatestVersions[key] = { ...record, chainId };
|
|
4287
|
+
}
|
|
4288
|
+
const row = { ...record, chainId };
|
|
4249
4289
|
await this.enqueueWrite(async () => {
|
|
4250
|
-
await
|
|
4251
|
-
|
|
4290
|
+
await this.writeJson(this.sharedRecordKey("chairmanMerkleVersions", chainId, record.version), row);
|
|
4291
|
+
if (nums.size !== beforeSize) {
|
|
4292
|
+
await this.writeJson(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), Array.from(nums).sort((a, b) => a - b));
|
|
4293
|
+
}
|
|
4252
4294
|
});
|
|
4253
4295
|
}
|
|
4254
|
-
async
|
|
4255
|
-
await this.
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
const
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
if (!Number.isFinite(totalElements) || totalElements < 0) return void 0;
|
|
4263
|
-
return { chainId, root, totalElements: Math.floor(totalElements), lastUpdated: Number.isFinite(lastUpdated) ? Math.floor(lastUpdated) : 0 };
|
|
4264
|
-
}
|
|
4265
|
-
async setMerkleTree(chainId, tree) {
|
|
4266
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4267
|
-
const row = { ...tree, chainId };
|
|
4268
|
-
this.merkleTrees[String(chainId)] = row;
|
|
4269
|
-
await this.enqueueWrite(() => this.writeJson(this.sharedChainKey("merkleTrees", chainId), row));
|
|
4270
|
-
}
|
|
4271
|
-
async clearMerkleTree(chainId) {
|
|
4272
|
-
await this.ensureMerkleTreeLoaded(chainId);
|
|
4273
|
-
delete this.merkleTrees[String(chainId)];
|
|
4296
|
+
async clearChairmanMerkleTree(chainId) {
|
|
4297
|
+
await this.ensureChairmanMerkleNodesLoaded(chainId);
|
|
4298
|
+
await this.ensureChairmanMerkleVersionsLoaded(chainId);
|
|
4299
|
+
const nodeIds = Array.from(this.chairmanMerkleNodeIds[String(chainId)] ?? []);
|
|
4300
|
+
const versionNums = Array.from(this.chairmanMerkleVersionNums[String(chainId)] ?? []);
|
|
4301
|
+
delete this.chairmanMerkleNodeIds[String(chainId)];
|
|
4302
|
+
delete this.chairmanMerkleVersionNums[String(chainId)];
|
|
4303
|
+
delete this.chairmanMerkleLatestVersions[String(chainId)];
|
|
4274
4304
|
await this.enqueueWrite(async () => {
|
|
4275
|
-
await
|
|
4305
|
+
await Promise.all([
|
|
4306
|
+
...nodeIds.map((id) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleNodes", chainId, id), null)),
|
|
4307
|
+
...versionNums.map((v) => this.deleteOrReset(this.sharedRecordKey("chairmanMerkleVersions", chainId, v), null))
|
|
4308
|
+
]);
|
|
4309
|
+
await Promise.all([
|
|
4310
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleNodes", chainId), []),
|
|
4311
|
+
this.deleteOrReset(this.sharedChainMetaKey("chairmanMerkleVersions", chainId), [])
|
|
4312
|
+
]);
|
|
4276
4313
|
});
|
|
4277
4314
|
}
|
|
4278
4315
|
async upsertEntryMemos(memos) {
|
|
@@ -7116,6 +7153,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7116
7153
|
this.hydrateInFlight = /* @__PURE__ */ new Map();
|
|
7117
7154
|
this.mode = options?.mode ?? "hybrid";
|
|
7118
7155
|
this.treeDepth = Math.max(1, Math.floor(options?.treeDepth ?? TREE_DEPTH_DEFAULT));
|
|
7156
|
+
this.readContractRoot = options?.readContractRoot;
|
|
7119
7157
|
}
|
|
7120
7158
|
/**
|
|
7121
7159
|
* Compute the current merkle root index from total elements.
|
|
@@ -7124,9 +7162,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7124
7162
|
if (totalElements <= tempArraySize) return 0;
|
|
7125
7163
|
return Math.floor((totalElements - 1) / tempArraySize);
|
|
7126
7164
|
}
|
|
7127
|
-
/**
|
|
7128
|
-
* Get or initialize the pending leaf buffer for a chain.
|
|
7129
|
-
*/
|
|
7130
7165
|
ensurePendingLeaves(chainId) {
|
|
7131
7166
|
let pending = this.pendingLeavesByChain.get(chainId);
|
|
7132
7167
|
if (!pending) {
|
|
@@ -7135,9 +7170,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7135
7170
|
}
|
|
7136
7171
|
return pending;
|
|
7137
7172
|
}
|
|
7138
|
-
/**
|
|
7139
|
-
* Get or initialize chain-level merkle state.
|
|
7140
|
-
*/
|
|
7141
7173
|
ensureChainState(chainId) {
|
|
7142
7174
|
let state = this.chainStateByChain.get(chainId);
|
|
7143
7175
|
if (!state) {
|
|
@@ -7146,15 +7178,32 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7146
7178
|
}
|
|
7147
7179
|
return state;
|
|
7148
7180
|
}
|
|
7149
|
-
|
|
7150
|
-
* Poseidon2 merkle hash for a left/right pair.
|
|
7151
|
-
*/
|
|
7181
|
+
// ── Hashing ──
|
|
7152
7182
|
static hashPair(left, right) {
|
|
7153
7183
|
return Poseidon2.hashToHex(BigInt(left), BigInt(right), Poseidon2Domain.Merkle);
|
|
7154
7184
|
}
|
|
7185
|
+
static normalizeHex32(value, name) {
|
|
7186
|
+
try {
|
|
7187
|
+
const bi = BigInt(value);
|
|
7188
|
+
if (bi < 0n) throw new Error("negative");
|
|
7189
|
+
const hex = bi.toString(16).padStart(64, "0");
|
|
7190
|
+
if (hex.length > 64) throw new Error("too_large");
|
|
7191
|
+
return `0x${hex}`;
|
|
7192
|
+
} catch (error) {
|
|
7193
|
+
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
// ── Static helpers ──
|
|
7197
|
+
static totalElementsInTree(totalElements, tempArraySize = TEMP_ARRAY_SIZE_DEFAULT) {
|
|
7198
|
+
if (tempArraySize <= 0) throw new SdkError("MERKLE", "tempArraySize must be greater than zero", { tempArraySize });
|
|
7199
|
+
if (totalElements <= 0n) return 0;
|
|
7200
|
+
const size = BigInt(tempArraySize);
|
|
7201
|
+
return Number((totalElements - 1n) / size * size);
|
|
7202
|
+
}
|
|
7203
|
+
// ── Subtree (levels 0-5, 32 leaves → 1 root) ──
|
|
7155
7204
|
/**
|
|
7156
7205
|
* Build a fixed-depth subtree from 32 contiguous leaves.
|
|
7157
|
-
* Returns the subtree root and all intermediate nodes for storage.
|
|
7206
|
+
* Returns the subtree root hash and all intermediate nodes for storage.
|
|
7158
7207
|
*/
|
|
7159
7208
|
static buildSubtree(leafCommitments, baseIndex) {
|
|
7160
7209
|
if (leafCommitments.length !== SUBTREE_SIZE) {
|
|
@@ -7175,71 +7224,71 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7175
7224
|
const position = basePos + i;
|
|
7176
7225
|
nodesToStore.push({
|
|
7177
7226
|
chainId: 0,
|
|
7178
|
-
id:
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7227
|
+
id: `st-${level}-${position}`,
|
|
7228
|
+
hash: next[i],
|
|
7229
|
+
leftId: null,
|
|
7230
|
+
rightId: null
|
|
7182
7231
|
});
|
|
7183
7232
|
}
|
|
7184
7233
|
currentLevel = next;
|
|
7185
7234
|
}
|
|
7186
7235
|
return { subtreeRoot: currentLevel[0], nodesToStore };
|
|
7187
7236
|
}
|
|
7237
|
+
// ── ChairmanMerkle tree (persistent segment tree, levels 5-32) ──
|
|
7188
7238
|
/**
|
|
7189
|
-
*
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
*
|
|
7197
|
-
*/
|
|
7198
|
-
async
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
const
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7239
|
+
* Insert a subtree root into the persistent main tree.
|
|
7240
|
+
*
|
|
7241
|
+
* Top-down recursive: descends from root (level treeDepth) to the target
|
|
7242
|
+
* leaf position (level SUBTREE_DEPTH). At each level only the node on the
|
|
7243
|
+
* update path is newly created; the sibling is shared from the previous
|
|
7244
|
+
* version's tree.
|
|
7245
|
+
*
|
|
7246
|
+
* @returns new root node ID/hash and all newly created nodes.
|
|
7247
|
+
*/
|
|
7248
|
+
async insertSubtreeRoot(chainId, prevRootId, subtreeRootHash, batchIndex, version) {
|
|
7249
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7250
|
+
const nodes = [];
|
|
7251
|
+
const descend = async (nodeId, depth) => {
|
|
7252
|
+
const originalLevel = this.treeDepth - depth;
|
|
7253
|
+
if (depth === MAIN_DEPTH) {
|
|
7254
|
+
const newId2 = `cm-${version}-${originalLevel}`;
|
|
7255
|
+
nodes.push({ chainId, id: newId2, hash: subtreeRootHash, leftId: null, rightId: null });
|
|
7256
|
+
return { id: newId2, hash: subtreeRootHash };
|
|
7257
|
+
}
|
|
7258
|
+
let prevLeftId = null;
|
|
7259
|
+
let prevRightId = null;
|
|
7260
|
+
if (nodeId) {
|
|
7261
|
+
const prevNode = await this.storage?.getChairmanMerkleNode?.(chainId, nodeId);
|
|
7262
|
+
if (prevNode) {
|
|
7263
|
+
prevLeftId = prevNode.leftId;
|
|
7264
|
+
prevRightId = prevNode.rightId;
|
|
7214
7265
|
}
|
|
7215
|
-
currentValue = _MerkleEngine.hashPair(currentValue, getZeroHash(level));
|
|
7216
|
-
} else {
|
|
7217
|
-
const leftHash = await this.getNodeHash(input.chainId, `frontier-${level}`) ?? getZeroHash(level);
|
|
7218
|
-
currentValue = _MerkleEngine.hashPair(leftHash, currentValue);
|
|
7219
7266
|
}
|
|
7220
|
-
const
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
hash:
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7267
|
+
const childLevel = originalLevel - 1;
|
|
7268
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7269
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7270
|
+
let leftResult;
|
|
7271
|
+
let rightResult;
|
|
7272
|
+
if (goRight) {
|
|
7273
|
+
const leftHash = prevLeftId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevLeftId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7274
|
+
leftResult = { id: prevLeftId, hash: leftHash };
|
|
7275
|
+
const right = await descend(prevRightId, depth + 1);
|
|
7276
|
+
rightResult = { id: right.id, hash: right.hash };
|
|
7277
|
+
} else {
|
|
7278
|
+
const left = await descend(prevLeftId, depth + 1);
|
|
7279
|
+
leftResult = { id: left.id, hash: left.hash };
|
|
7280
|
+
const rightHash = prevRightId ? (await this.storage?.getChairmanMerkleNode?.(chainId, prevRightId))?.hash ?? getZeroHash(childLevel) : getZeroHash(childLevel);
|
|
7281
|
+
rightResult = { id: prevRightId, hash: rightHash };
|
|
7282
|
+
}
|
|
7283
|
+
const hash = _MerkleEngine.hashPair(leftResult.hash, rightResult.hash);
|
|
7284
|
+
const newId = `cm-${version}-${originalLevel}`;
|
|
7285
|
+
nodes.push({ chainId, id: newId, hash, leftId: leftResult.id, rightId: rightResult.id });
|
|
7286
|
+
return { id: newId, hash };
|
|
7287
|
+
};
|
|
7288
|
+
const root = await descend(prevRootId, 0);
|
|
7289
|
+
return { rootId: root.id, rootHash: root.hash, nodes };
|
|
7239
7290
|
}
|
|
7240
|
-
|
|
7241
|
-
* Hydrate local merkle state from storage on first use.
|
|
7242
|
-
*/
|
|
7291
|
+
// ── Hydration ──
|
|
7243
7292
|
async hydrateFromStorage(chainId) {
|
|
7244
7293
|
if (this.mode === "remote") return;
|
|
7245
7294
|
if (this.hydratedChains.has(chainId)) return;
|
|
@@ -7248,31 +7297,17 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7248
7297
|
const task = (async () => {
|
|
7249
7298
|
try {
|
|
7250
7299
|
const state = this.ensureChainState(chainId);
|
|
7251
|
-
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7252
|
-
if (!leaves || leaves.length === 0) return;
|
|
7253
|
-
const sorted = [...leaves].map((l) => ({
|
|
7254
|
-
cid: l.cid,
|
|
7255
|
-
commitment: _MerkleEngine.normalizeHex32(l.commitment, "memo.commitment")
|
|
7256
|
-
})).sort((a, b) => a.cid - b.cid);
|
|
7257
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
7258
|
-
if (sorted[i].cid !== i) throw new Error(`Non-contiguous persisted merkle leaves: expected cid=${i}, got cid=${sorted[i].cid}`);
|
|
7259
|
-
}
|
|
7260
|
-
const storedTree = await this.storage?.getMerkleTree?.(chainId);
|
|
7261
|
-
const leafCount = sorted.length;
|
|
7262
|
-
const mergedFromLeaves = _MerkleEngine.totalElementsInTree(BigInt(leafCount));
|
|
7263
|
-
const mergedFromTree = typeof storedTree?.totalElements === "number" && storedTree.totalElements > 0 ? storedTree.totalElements : 0;
|
|
7264
|
-
const mergedElements = Math.max(mergedFromLeaves, mergedFromTree);
|
|
7265
7300
|
const pending = this.ensurePendingLeaves(chainId);
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7301
|
+
const latest = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7302
|
+
if (latest) {
|
|
7303
|
+
state.mergedElements = latest.version;
|
|
7304
|
+
state.root = _MerkleEngine.normalizeHex32(latest.rootHash, "chairmanMerkleVersion.rootHash");
|
|
7270
7305
|
}
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7306
|
+
const leaves = await this.storage?.getMerkleLeaves?.(chainId);
|
|
7307
|
+
if (leaves && leaves.length > state.mergedElements) {
|
|
7308
|
+
const sorted = [...leaves].sort((a, b) => a.cid - b.cid).slice(state.mergedElements);
|
|
7309
|
+
pending.length = 0;
|
|
7310
|
+
pending.push(...sorted.map((l) => _MerkleEngine.normalizeHex32(l.commitment, "leaf.commitment")));
|
|
7276
7311
|
}
|
|
7277
7312
|
} catch (error) {
|
|
7278
7313
|
if (this.mode === "hybrid") return;
|
|
@@ -7285,26 +7320,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7285
7320
|
this.hydrateInFlight.set(chainId, task);
|
|
7286
7321
|
return task;
|
|
7287
7322
|
}
|
|
7288
|
-
|
|
7289
|
-
* Normalize unknown values to a 32-byte hex string.
|
|
7290
|
-
*/
|
|
7291
|
-
static normalizeHex32(value, name) {
|
|
7292
|
-
try {
|
|
7293
|
-
const bi = BigInt(value);
|
|
7294
|
-
if (bi < 0n) throw new Error("negative");
|
|
7295
|
-
const hex = bi.toString(16).padStart(64, "0");
|
|
7296
|
-
if (hex.length > 64) throw new Error("too_large");
|
|
7297
|
-
return `0x${hex}`;
|
|
7298
|
-
} catch (error) {
|
|
7299
|
-
throw new SdkError("MERKLE", `Invalid ${name}`, { value }, error);
|
|
7300
|
-
}
|
|
7301
|
-
}
|
|
7302
|
-
/**
|
|
7303
|
-
* Feed contiguous (cid-ordered) memo leaves into the local merkle tree.
|
|
7304
|
-
*
|
|
7305
|
-
* This mirrors the client/app behavior: only after we have a full consecutive batch of 32 leaves
|
|
7306
|
-
* do we merge them into the main tree. Leaves that are still in the buffer do not get local proofs.
|
|
7307
|
-
*/
|
|
7323
|
+
// ── Ingestion ──
|
|
7308
7324
|
async ingestEntryMemos(chainId, memos) {
|
|
7309
7325
|
if (this.mode === "remote") return;
|
|
7310
7326
|
await this.hydrateFromStorage(chainId);
|
|
@@ -7332,26 +7348,42 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7332
7348
|
expected++;
|
|
7333
7349
|
while (pending.length >= SUBTREE_SIZE) {
|
|
7334
7350
|
const batch = pending.splice(0, SUBTREE_SIZE);
|
|
7335
|
-
const
|
|
7336
|
-
const subtree = _MerkleEngine.buildSubtree(batch,
|
|
7337
|
-
const
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
const
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7351
|
+
const batchIndex = state.mergedElements / SUBTREE_SIZE;
|
|
7352
|
+
const subtree = _MerkleEngine.buildSubtree(batch, state.mergedElements);
|
|
7353
|
+
const subtreeNodes = subtree.nodesToStore.map((n) => ({ ...n, chainId }));
|
|
7354
|
+
const prevVersion = await this.storage?.getLatestChairmanMerkleVersion?.(chainId);
|
|
7355
|
+
const prevRootId = prevVersion?.rootId ?? null;
|
|
7356
|
+
const newVersion = state.mergedElements + SUBTREE_SIZE;
|
|
7357
|
+
const result = await this.insertSubtreeRoot(chainId, prevRootId, subtree.subtreeRoot, batchIndex, newVersion);
|
|
7358
|
+
if (this.readContractRoot) {
|
|
7359
|
+
const rootIndex = newVersion / SUBTREE_SIZE;
|
|
7360
|
+
const onChainRoot = await this.readContractRoot(chainId, rootIndex).catch(() => null);
|
|
7361
|
+
if (onChainRoot !== null) {
|
|
7362
|
+
const onChainNorm = _MerkleEngine.normalizeHex32(onChainRoot, "onChainRoot");
|
|
7363
|
+
const isZero = BigInt(onChainNorm) === 0n;
|
|
7364
|
+
if (!isZero && onChainNorm !== result.rootHash) {
|
|
7365
|
+
const target = state.mergedElements;
|
|
7366
|
+
await this.rollback(chainId, target);
|
|
7367
|
+
throw new SdkError("MERKLE", "Local merkle root mismatch with on-chain root \u2014 rolled back", {
|
|
7368
|
+
chainId,
|
|
7369
|
+
rootIndex,
|
|
7370
|
+
localRoot: result.rootHash,
|
|
7371
|
+
onChainRoot: onChainNorm,
|
|
7372
|
+
version: newVersion,
|
|
7373
|
+
rollbackTarget: target
|
|
7374
|
+
});
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
}
|
|
7378
|
+
await this.storage?.putChairmanMerkleNodes?.(chainId, [...subtreeNodes, ...result.nodes]);
|
|
7379
|
+
await this.storage?.putChairmanMerkleVersion?.(chainId, {
|
|
7350
7380
|
chainId,
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7381
|
+
version: newVersion,
|
|
7382
|
+
rootId: result.rootId,
|
|
7383
|
+
rootHash: result.rootHash
|
|
7354
7384
|
});
|
|
7385
|
+
state.mergedElements = newVersion;
|
|
7386
|
+
state.root = result.rootHash;
|
|
7355
7387
|
}
|
|
7356
7388
|
}
|
|
7357
7389
|
this.hydratedChains.add(chainId);
|
|
@@ -7362,20 +7394,23 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7362
7394
|
throw new SdkError("MERKLE", "Failed to ingest local merkle leaves", { chainId, leafCount: leaves.length }, error);
|
|
7363
7395
|
}
|
|
7364
7396
|
}
|
|
7397
|
+
// ── Rollback (tree O(1) + sync cursor reset) ──
|
|
7365
7398
|
/**
|
|
7366
|
-
*
|
|
7399
|
+
* Unified rollback: rewind tree to a previous batch boundary AND reset the
|
|
7400
|
+
* sync cursor so memo sync restarts from the same point.
|
|
7367
7401
|
*
|
|
7368
|
-
*
|
|
7369
|
-
*
|
|
7402
|
+
* What gets rolled back:
|
|
7403
|
+
* - ChairmanMerkle tree version pointer (O(1) — old nodes still in storage)
|
|
7404
|
+
* - Pending leaves buffer (cleared)
|
|
7405
|
+
* - Sync cursor: memo + merkle fields (nullifier left unchanged — independent)
|
|
7370
7406
|
*
|
|
7371
|
-
* @param targetMergedElements Must be a non-negative multiple of 32
|
|
7407
|
+
* @param targetMergedElements Must be a non-negative multiple of 32.
|
|
7372
7408
|
* Pass 0 to reset to the empty tree.
|
|
7373
|
-
* @returns true if rollback succeeded, false if
|
|
7374
|
-
* subtree roots) is missing in storage.
|
|
7409
|
+
* @returns true if rollback succeeded, false if the target version doesn't exist.
|
|
7375
7410
|
*/
|
|
7376
|
-
async
|
|
7411
|
+
async rollback(chainId, targetMergedElements) {
|
|
7377
7412
|
if (targetMergedElements < 0 || targetMergedElements % SUBTREE_SIZE !== 0) {
|
|
7378
|
-
throw new SdkError("MERKLE", "
|
|
7413
|
+
throw new SdkError("MERKLE", "rollback target must be a non-negative multiple of 32", { targetMergedElements });
|
|
7379
7414
|
}
|
|
7380
7415
|
const state = this.ensureChainState(chainId);
|
|
7381
7416
|
const pending = this.ensurePendingLeaves(chainId);
|
|
@@ -7383,78 +7418,35 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7383
7418
|
state.mergedElements = 0;
|
|
7384
7419
|
state.root = getZeroHash(this.treeDepth);
|
|
7385
7420
|
pending.length = 0;
|
|
7386
|
-
await this.
|
|
7387
|
-
chainId,
|
|
7388
|
-
root: state.root,
|
|
7389
|
-
totalElements: 0,
|
|
7390
|
-
lastUpdated: Date.now()
|
|
7391
|
-
});
|
|
7421
|
+
await this.resetSyncCursor(chainId, 0);
|
|
7392
7422
|
return true;
|
|
7393
7423
|
}
|
|
7394
|
-
const
|
|
7395
|
-
if (!
|
|
7396
|
-
const numBatches = targetMergedElements / SUBTREE_SIZE;
|
|
7397
|
-
const subtreeRoots = [];
|
|
7398
|
-
for (let batch = 0; batch < numBatches; batch++) {
|
|
7399
|
-
const node = await this.storage?.getMerkleNode?.(chainId, `${SUBTREE_DEPTH}-${batch}`);
|
|
7400
|
-
if (!node) return false;
|
|
7401
|
-
subtreeRoots.push(node.hash);
|
|
7402
|
-
}
|
|
7403
|
-
const resetNodes = [];
|
|
7404
|
-
for (let level = SUBTREE_DEPTH; level < this.treeDepth; level++) {
|
|
7405
|
-
resetNodes.push({
|
|
7406
|
-
chainId,
|
|
7407
|
-
id: `frontier-${level}`,
|
|
7408
|
-
level,
|
|
7409
|
-
position: 0,
|
|
7410
|
-
hash: getZeroHash(level)
|
|
7411
|
-
});
|
|
7412
|
-
}
|
|
7413
|
-
await this.storage?.upsertMerkleNodes?.(chainId, resetNodes);
|
|
7414
|
-
let replayRoot = getZeroHash(this.treeDepth);
|
|
7415
|
-
for (let batch = 0; batch < numBatches; batch++) {
|
|
7416
|
-
const merged = await this.mergeSubtreeToMainTree({
|
|
7417
|
-
chainId,
|
|
7418
|
-
subtreeRoot: subtreeRoots[batch],
|
|
7419
|
-
newTotalElements: (batch + 1) * SUBTREE_SIZE
|
|
7420
|
-
});
|
|
7421
|
-
replayRoot = merged.finalRoot;
|
|
7422
|
-
await this.storage?.upsertMerkleNodes?.(chainId, merged.nodesToStore.map((n) => ({ ...n, chainId })));
|
|
7423
|
-
}
|
|
7424
|
-
const replayNorm = _MerkleEngine.normalizeHex32(replayRoot, "replay.root");
|
|
7425
|
-
const checkpointNorm = _MerkleEngine.normalizeHex32(checkpoint.hash, "checkpoint.root");
|
|
7426
|
-
if (replayNorm !== checkpointNorm) {
|
|
7427
|
-
return false;
|
|
7428
|
-
}
|
|
7424
|
+
const version = await this.storage?.getChairmanMerkleVersion?.(chainId, targetMergedElements);
|
|
7425
|
+
if (!version) return false;
|
|
7429
7426
|
state.mergedElements = targetMergedElements;
|
|
7430
|
-
state.root =
|
|
7427
|
+
state.root = _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash");
|
|
7431
7428
|
pending.length = 0;
|
|
7432
7429
|
this.hydratedChains.add(chainId);
|
|
7433
|
-
await this.
|
|
7434
|
-
chainId,
|
|
7435
|
-
root: state.root,
|
|
7436
|
-
totalElements: targetMergedElements,
|
|
7437
|
-
lastUpdated: Date.now()
|
|
7438
|
-
});
|
|
7439
|
-
const updatedCheckpoint = {
|
|
7440
|
-
chainId,
|
|
7441
|
-
id: `checkpoint-${targetMergedElements}`,
|
|
7442
|
-
level: -1,
|
|
7443
|
-
position: 0,
|
|
7444
|
-
hash: checkpointNorm
|
|
7445
|
-
};
|
|
7446
|
-
await this.storage?.upsertMerkleNodes?.(chainId, [updatedCheckpoint]);
|
|
7430
|
+
await this.resetSyncCursor(chainId, targetMergedElements);
|
|
7447
7431
|
return true;
|
|
7448
7432
|
}
|
|
7449
7433
|
/**
|
|
7450
|
-
*
|
|
7434
|
+
* Reset the sync cursor's memo field to `targetMemo` (and derive merkle cursor),
|
|
7435
|
+
* but only if the current cursor is ahead of the target.
|
|
7436
|
+
* Nullifier cursor is left unchanged — nullifiers are independent of tree state.
|
|
7451
7437
|
*/
|
|
7438
|
+
async resetSyncCursor(chainId, targetMemo) {
|
|
7439
|
+
if (!this.storage?.getSyncCursor || !this.storage?.setSyncCursor) return;
|
|
7440
|
+
const cursor = await this.storage.getSyncCursor(chainId);
|
|
7441
|
+
if (!cursor || cursor.memo <= targetMemo) return;
|
|
7442
|
+
cursor.memo = targetMemo;
|
|
7443
|
+
cursor.merkle = this.currentMerkleRootIndex(targetMemo);
|
|
7444
|
+
await this.storage.setSyncCursor(chainId, cursor);
|
|
7445
|
+
}
|
|
7446
|
+
// ── Proof generation ──
|
|
7452
7447
|
async getProofByCid(input) {
|
|
7453
7448
|
return this.getProofByCids({ chainId: input.chainId, cids: [input.cid], totalElements: input.totalElements });
|
|
7454
7449
|
}
|
|
7455
|
-
/**
|
|
7456
|
-
* Get merkle proofs for a set of cids using local/hybrid/remote logic.
|
|
7457
|
-
*/
|
|
7458
7450
|
async getProofByCids(input) {
|
|
7459
7451
|
const cids = [...input.cids];
|
|
7460
7452
|
if (cids.length === 0) throw new SdkError("MERKLE", "No cids provided", { chainId: input.chainId });
|
|
@@ -7469,15 +7461,16 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7469
7461
|
await this.hydrateFromStorage(input.chainId);
|
|
7470
7462
|
const canUseLocal = this.mode !== "remote";
|
|
7471
7463
|
if (canUseLocal) {
|
|
7472
|
-
const
|
|
7473
|
-
const hasDb = typeof this.storage?.getMerkleLeaf === "function" && typeof this.storage?.
|
|
7474
|
-
if (hasDb
|
|
7475
|
-
|
|
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) {
|
|
7467
|
+
const state = this.ensureChainState(input.chainId);
|
|
7468
|
+
if (contractTreeElements > 0 && state.mergedElements < contractTreeElements) {
|
|
7476
7469
|
if (this.mode === "local") {
|
|
7477
7470
|
throw new SdkError("MERKLE", "Local merkle db is behind contract", {
|
|
7478
7471
|
chainId: input.chainId,
|
|
7479
7472
|
cids,
|
|
7480
|
-
|
|
7473
|
+
localMergedElements: state.mergedElements,
|
|
7481
7474
|
contractTreeElements
|
|
7482
7475
|
});
|
|
7483
7476
|
}
|
|
@@ -7489,30 +7482,13 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7489
7482
|
proof.push({ leaf_index: cid, path: new Array(this.treeDepth + 1).fill("0") });
|
|
7490
7483
|
continue;
|
|
7491
7484
|
}
|
|
7492
|
-
const
|
|
7493
|
-
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7494
|
-
const path = [leaf.commitment];
|
|
7495
|
-
for (let level = 1; level <= this.treeDepth; level++) {
|
|
7496
|
-
const siblingIndex = cid >> level - 1 ^ 1;
|
|
7497
|
-
if (level === 1) {
|
|
7498
|
-
const siblingLeaf = await this.storage.getMerkleLeaf(input.chainId, siblingIndex);
|
|
7499
|
-
path.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7500
|
-
continue;
|
|
7501
|
-
}
|
|
7502
|
-
const targetLevel = level - 1;
|
|
7503
|
-
const siblingNode = await this.storage.getMerkleNode(input.chainId, `${targetLevel}-${siblingIndex}`);
|
|
7504
|
-
path.push(siblingNode?.hash ?? getZeroHash(targetLevel));
|
|
7505
|
-
}
|
|
7485
|
+
const path = await this.buildLocalProofPath(input.chainId, cid, version);
|
|
7506
7486
|
proof.push({ leaf_index: cid, path });
|
|
7507
7487
|
}
|
|
7508
|
-
|
|
7509
|
-
if (tree.totalElements > contractTreeElements && contractTreeElements > 0) {
|
|
7510
|
-
const checkpoint = await this.storage.getMerkleNode(input.chainId, `checkpoint-${contractTreeElements}`);
|
|
7511
|
-
if (checkpoint) effectiveRoot = checkpoint.hash;
|
|
7512
|
-
}
|
|
7488
|
+
const effectiveRoot = contractTreeElements > 0 ? _MerkleEngine.normalizeHex32(version.rootHash, "version.rootHash") : getZeroHash(this.treeDepth);
|
|
7513
7489
|
return {
|
|
7514
7490
|
proof,
|
|
7515
|
-
merkle_root:
|
|
7491
|
+
merkle_root: effectiveRoot,
|
|
7516
7492
|
latest_cid: totalElements > 0n ? Number(totalElements - 1n) : -1
|
|
7517
7493
|
};
|
|
7518
7494
|
} catch (error) {
|
|
@@ -7522,7 +7498,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7522
7498
|
}
|
|
7523
7499
|
}
|
|
7524
7500
|
} else if (this.mode === "local" && needsTreeProof.length) {
|
|
7525
|
-
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "
|
|
7501
|
+
throw new SdkError("MERKLE", "Local merkle db unavailable", { chainId: input.chainId, cids, reason: "missing_adapter_or_version" });
|
|
7526
7502
|
}
|
|
7527
7503
|
}
|
|
7528
7504
|
if (needsTreeProof.length === 0) {
|
|
@@ -7547,15 +7523,64 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7547
7523
|
};
|
|
7548
7524
|
}
|
|
7549
7525
|
/**
|
|
7550
|
-
*
|
|
7526
|
+
* Build a local proof path by traversing the chairmanMerkle tree.
|
|
7527
|
+
*
|
|
7528
|
+
* Levels 0-4: sibling hashes from subtree internal nodes (st-{level}-{pos}).
|
|
7529
|
+
* Levels 5-31: sibling hashes from chairmanMerkle tree traversal (top-down from version root).
|
|
7551
7530
|
*/
|
|
7531
|
+
async buildLocalProofPath(chainId, cid, version) {
|
|
7532
|
+
const leaf = await this.storage.getMerkleLeaf(chainId, cid);
|
|
7533
|
+
if (!leaf) throw new Error(`missing_leaf:${cid}`);
|
|
7534
|
+
const path = [leaf.commitment];
|
|
7535
|
+
for (let level = 1; level <= SUBTREE_DEPTH; level++) {
|
|
7536
|
+
const siblingPos = cid >> level - 1 ^ 1;
|
|
7537
|
+
if (level === 1) {
|
|
7538
|
+
const siblingLeaf = await this.storage.getMerkleLeaf(chainId, siblingPos);
|
|
7539
|
+
path.push(siblingLeaf?.commitment ?? getZeroHash(0));
|
|
7540
|
+
} else {
|
|
7541
|
+
const targetLevel = level - 1;
|
|
7542
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, `st-${targetLevel}-${siblingPos}`);
|
|
7543
|
+
path.push(node?.hash ?? getZeroHash(targetLevel));
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7546
|
+
const batchIndex = cid >> SUBTREE_DEPTH;
|
|
7547
|
+
const MAIN_DEPTH = this.treeDepth - SUBTREE_DEPTH;
|
|
7548
|
+
const mainSiblings = [];
|
|
7549
|
+
let nodeId = version.rootId;
|
|
7550
|
+
for (let depth = 0; depth < MAIN_DEPTH; depth++) {
|
|
7551
|
+
const childLevel = this.treeDepth - depth - 1;
|
|
7552
|
+
if (!nodeId) {
|
|
7553
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7554
|
+
continue;
|
|
7555
|
+
}
|
|
7556
|
+
const node = await this.storage.getChairmanMerkleNode(chainId, nodeId);
|
|
7557
|
+
if (!node) {
|
|
7558
|
+
mainSiblings.push(getZeroHash(childLevel));
|
|
7559
|
+
nodeId = null;
|
|
7560
|
+
continue;
|
|
7561
|
+
}
|
|
7562
|
+
const remainingDepth = MAIN_DEPTH - depth - 1;
|
|
7563
|
+
const goRight = (batchIndex >> remainingDepth & 1) === 1;
|
|
7564
|
+
if (goRight) {
|
|
7565
|
+
const leftNode = node.leftId ? await this.storage.getChairmanMerkleNode(chainId, node.leftId) : null;
|
|
7566
|
+
mainSiblings.push(leftNode?.hash ?? getZeroHash(childLevel));
|
|
7567
|
+
nodeId = node.rightId;
|
|
7568
|
+
} else {
|
|
7569
|
+
const rightNode = node.rightId ? await this.storage.getChairmanMerkleNode(chainId, node.rightId) : null;
|
|
7570
|
+
mainSiblings.push(rightNode?.hash ?? getZeroHash(childLevel));
|
|
7571
|
+
nodeId = node.leftId;
|
|
7572
|
+
}
|
|
7573
|
+
}
|
|
7574
|
+
for (let i = mainSiblings.length - 1; i >= 0; i--) {
|
|
7575
|
+
path.push(mainSiblings[i]);
|
|
7576
|
+
}
|
|
7577
|
+
return path;
|
|
7578
|
+
}
|
|
7579
|
+
// ── Remote helpers ──
|
|
7552
7580
|
async fetchRemoteRootOnly(chainId) {
|
|
7553
7581
|
const remote = await this.fetchRemoteProofFromService({ chainId, cids: [0] });
|
|
7554
7582
|
return _MerkleEngine.normalizeHex32(remote.merkle_root, "remote.merkle_root");
|
|
7555
7583
|
}
|
|
7556
|
-
/**
|
|
7557
|
-
* Fetch proofs from the remote merkle service.
|
|
7558
|
-
*/
|
|
7559
7584
|
async fetchRemoteProofFromService(input) {
|
|
7560
7585
|
const chain = this.getChain(input.chainId);
|
|
7561
7586
|
if (!chain.merkleProofUrl) {
|
|
@@ -7564,9 +7589,7 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7564
7589
|
const client = new MerkleClient(chain.merkleProofUrl);
|
|
7565
7590
|
return client.getProofByCids(input.cids);
|
|
7566
7591
|
}
|
|
7567
|
-
|
|
7568
|
-
* Build membership witnesses for provided UTXOs from a remote proof response.
|
|
7569
|
-
*/
|
|
7592
|
+
// ── Witness builders (unchanged) ──
|
|
7570
7593
|
buildAccMemberWitnesses(input) {
|
|
7571
7594
|
return input.utxos.map((utxo, idx) => {
|
|
7572
7595
|
const remoteProof = input.remote.proof[idx];
|
|
@@ -7580,9 +7603,6 @@ var MerkleEngine = class _MerkleEngine {
|
|
|
7580
7603
|
};
|
|
7581
7604
|
});
|
|
7582
7605
|
}
|
|
7583
|
-
/**
|
|
7584
|
-
* Convert UTXOs into circuit input secrets, decrypting memos and padding if needed.
|
|
7585
|
-
*/
|
|
7586
7606
|
async buildInputSecretsFromUtxos(input) {
|
|
7587
7607
|
if (!Array.isArray(input.utxos) || input.utxos.length === 0) {
|
|
7588
7608
|
throw new SdkError("MERKLE", "No utxos provided", { count: 0 });
|
|
@@ -8726,7 +8746,7 @@ var _IndexedDbStore = class _IndexedDbStore {
|
|
|
8726
8746
|
this.options = options;
|
|
8727
8747
|
this.cursors = /* @__PURE__ */ new Map();
|
|
8728
8748
|
this.operations = [];
|
|
8729
|
-
this.
|
|
8749
|
+
this.chairmanMerkleLatestVersions = {};
|
|
8730
8750
|
this.db = null;
|
|
8731
8751
|
const max = options.maxOperations;
|
|
8732
8752
|
this.maxOperations = max == null ? Number.POSITIVE_INFINITY : Math.max(0, Math.floor(max));
|
|
@@ -8770,8 +8790,8 @@ var _IndexedDbStore = class _IndexedDbStore {
|
|
|
8770
8790
|
{ name: `${base}:entryMemos`, keyPath: ["chainId", "cid"], indexes: [{ name: "chainId", keyPath: "chainId" }] },
|
|
8771
8791
|
{ name: `${base}:entryNullifiers`, keyPath: ["chainId", "nid"], indexes: [{ name: "chainId", keyPath: "chainId" }] },
|
|
8772
8792
|
{ name: `${base}:merkleLeaves`, keyPath: ["chainId", "cid"], indexes: [{ name: "chainId", keyPath: "chainId" }] },
|
|
8773
|
-
{ name: `${base}:
|
|
8774
|
-
{ name: `${base}:
|
|
8793
|
+
{ name: `${base}:chairmanMerkleNodes`, keyPath: ["chainId", "id"], indexes: [{ name: "chainId", keyPath: "chainId" }] },
|
|
8794
|
+
{ name: `${base}:chairmanMerkleVersions`, keyPath: ["chainId", "version"], indexes: [{ name: "chainId", keyPath: "chainId" }] }
|
|
8775
8795
|
];
|
|
8776
8796
|
}
|
|
8777
8797
|
async openDb() {
|
|
@@ -8785,6 +8805,12 @@ var _IndexedDbStore = class _IndexedDbStore {
|
|
|
8785
8805
|
req.onerror = () => reject(req.error ?? new Error("indexedDB open failed"));
|
|
8786
8806
|
req.onupgradeneeded = () => {
|
|
8787
8807
|
const db2 = req.result;
|
|
8808
|
+
const defNames = new Set(defs.map((d) => d.name));
|
|
8809
|
+
for (const storeName of Array.from(db2.objectStoreNames)) {
|
|
8810
|
+
if (!defNames.has(storeName)) {
|
|
8811
|
+
db2.deleteObjectStore(storeName);
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8788
8814
|
for (const def of defs) {
|
|
8789
8815
|
let store = null;
|
|
8790
8816
|
if (!db2.objectStoreNames.contains(def.name)) {
|
|
@@ -8816,8 +8842,8 @@ var _IndexedDbStore = class _IndexedDbStore {
|
|
|
8816
8842
|
entryMemos: `${base}:entryMemos`,
|
|
8817
8843
|
entryNullifiers: `${base}:entryNullifiers`,
|
|
8818
8844
|
merkleLeaves: `${base}:merkleLeaves`,
|
|
8819
|
-
|
|
8820
|
-
|
|
8845
|
+
chairmanMerkleNodes: `${base}:chairmanMerkleNodes`,
|
|
8846
|
+
chairmanMerkleVersions: `${base}:chairmanMerkleVersions`
|
|
8821
8847
|
};
|
|
8822
8848
|
}
|
|
8823
8849
|
async getAll(storeName) {
|
|
@@ -8954,7 +8980,7 @@ var _IndexedDbStore = class _IndexedDbStore {
|
|
|
8954
8980
|
const walletKey = this.walletKey();
|
|
8955
8981
|
this.cursors.clear();
|
|
8956
8982
|
this.operations = [];
|
|
8957
|
-
this.
|
|
8983
|
+
this.chairmanMerkleLatestVersions = {};
|
|
8958
8984
|
const cursorRows = await this.getAllByIndex(stores.cursors, "walletId", walletKey);
|
|
8959
8985
|
for (const row of cursorRows) {
|
|
8960
8986
|
this.cursors.set(row.chainId, { memo: row.memo, nullifier: row.nullifier, merkle: row.merkle });
|
|
@@ -8964,62 +8990,79 @@ var _IndexedDbStore = class _IndexedDbStore {
|
|
|
8964
8990
|
const { walletId: _walletId, ...operation } = row;
|
|
8965
8991
|
return operation;
|
|
8966
8992
|
}).sort((a, b) => b.createdAt - a.createdAt);
|
|
8967
|
-
const
|
|
8968
|
-
for (const row of
|
|
8969
|
-
|
|
8993
|
+
const allVersionRows = await this.getAll(stores.chairmanMerkleVersions);
|
|
8994
|
+
for (const row of allVersionRows) {
|
|
8995
|
+
const key = String(row.chainId);
|
|
8996
|
+
const existing = this.chairmanMerkleLatestVersions[key];
|
|
8997
|
+
if (!existing || row.version > existing.version) {
|
|
8998
|
+
this.chairmanMerkleLatestVersions[key] = { ...row };
|
|
8999
|
+
}
|
|
8970
9000
|
}
|
|
8971
9001
|
this.pruneOperations();
|
|
8972
9002
|
}
|
|
8973
9003
|
/**
|
|
8974
|
-
* Get a
|
|
9004
|
+
* Get a chairmanMerkle node by id.
|
|
8975
9005
|
*/
|
|
8976
|
-
async
|
|
8977
|
-
const node = await this.getByKey(this.storeNames().
|
|
9006
|
+
async getChairmanMerkleNode(chainId, id) {
|
|
9007
|
+
const node = await this.getByKey(this.storeNames().chairmanMerkleNodes, [chainId, id]);
|
|
8978
9008
|
if (!node) return void 0;
|
|
8979
9009
|
const hash = node.hash;
|
|
8980
9010
|
if (typeof hash !== "string" || !hash.startsWith("0x")) return void 0;
|
|
9011
|
+
if (typeof node.leftId !== "string" && node.leftId !== null) return void 0;
|
|
9012
|
+
if (typeof node.rightId !== "string" && node.rightId !== null) return void 0;
|
|
8981
9013
|
return { ...node, chainId };
|
|
8982
9014
|
}
|
|
8983
9015
|
/**
|
|
8984
|
-
*
|
|
9016
|
+
* Put chairmanMerkle nodes and persist.
|
|
8985
9017
|
*/
|
|
8986
|
-
async
|
|
9018
|
+
async putChairmanMerkleNodes(chainId, nodes) {
|
|
8987
9019
|
if (!nodes.length) return;
|
|
8988
9020
|
const rows = nodes.map((node) => ({ ...node, chainId }));
|
|
8989
|
-
await this.putMany(this.storeNames().
|
|
9021
|
+
await this.putMany(this.storeNames().chairmanMerkleNodes, rows);
|
|
8990
9022
|
}
|
|
8991
9023
|
/**
|
|
8992
|
-
*
|
|
9024
|
+
* Get a chairmanMerkle version record by chainId and version.
|
|
8993
9025
|
*/
|
|
8994
|
-
async
|
|
8995
|
-
|
|
9026
|
+
async getChairmanMerkleVersion(chainId, version) {
|
|
9027
|
+
return this.getByKey(this.storeNames().chairmanMerkleVersions, [chainId, version]);
|
|
8996
9028
|
}
|
|
8997
9029
|
/**
|
|
8998
|
-
* Get
|
|
9030
|
+
* Get the latest chairmanMerkle version for a chain from in-memory cache,
|
|
9031
|
+
* falling back to loading from the store if not cached.
|
|
8999
9032
|
*/
|
|
9000
|
-
async
|
|
9001
|
-
const
|
|
9002
|
-
if (
|
|
9003
|
-
const
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9033
|
+
async getLatestChairmanMerkleVersion(chainId) {
|
|
9034
|
+
const cached = this.chairmanMerkleLatestVersions[String(chainId)];
|
|
9035
|
+
if (cached) return { ...cached };
|
|
9036
|
+
const rows = await this.getAllByIndex(this.storeNames().chairmanMerkleVersions, "chainId", chainId);
|
|
9037
|
+
if (!rows.length) return void 0;
|
|
9038
|
+
let best = rows[0];
|
|
9039
|
+
for (const row of rows) {
|
|
9040
|
+
if (row.version > best.version) best = row;
|
|
9041
|
+
}
|
|
9042
|
+
this.chairmanMerkleLatestVersions[String(chainId)] = { ...best };
|
|
9043
|
+
return { ...best };
|
|
9009
9044
|
}
|
|
9010
9045
|
/**
|
|
9011
|
-
* Persist
|
|
9046
|
+
* Persist a chairmanMerkle version record and update the in-memory cache
|
|
9047
|
+
* if this is the latest version for the chain.
|
|
9012
9048
|
*/
|
|
9013
|
-
async
|
|
9014
|
-
this.
|
|
9015
|
-
|
|
9049
|
+
async putChairmanMerkleVersion(chainId, record) {
|
|
9050
|
+
await this.putMany(this.storeNames().chairmanMerkleVersions, [{ ...record, chainId }]);
|
|
9051
|
+
const key = String(chainId);
|
|
9052
|
+
const existing = this.chairmanMerkleLatestVersions[key];
|
|
9053
|
+
if (!existing || record.version >= existing.version) {
|
|
9054
|
+
this.chairmanMerkleLatestVersions[key] = { ...record, chainId };
|
|
9055
|
+
}
|
|
9016
9056
|
}
|
|
9017
9057
|
/**
|
|
9018
|
-
* Clear
|
|
9058
|
+
* Clear both chairmanMerkle nodes and versions for a chain and reset the cache.
|
|
9019
9059
|
*/
|
|
9020
|
-
async
|
|
9021
|
-
delete this.
|
|
9022
|
-
await
|
|
9060
|
+
async clearChairmanMerkleTree(chainId) {
|
|
9061
|
+
delete this.chairmanMerkleLatestVersions[String(chainId)];
|
|
9062
|
+
await Promise.all([
|
|
9063
|
+
this.deleteAllByIndex(this.storeNames().chairmanMerkleNodes, "chainId", chainId),
|
|
9064
|
+
this.deleteAllByIndex(this.storeNames().chairmanMerkleVersions, "chainId", chainId)
|
|
9065
|
+
]);
|
|
9023
9066
|
}
|
|
9024
9067
|
/**
|
|
9025
9068
|
* Upsert entry memos and persist.
|
|
@@ -9264,7 +9307,7 @@ var _IndexedDbStore = class _IndexedDbStore {
|
|
|
9264
9307
|
return applyOperationsQuery(this.operations, input);
|
|
9265
9308
|
}
|
|
9266
9309
|
};
|
|
9267
|
-
_IndexedDbStore.DB_VERSION =
|
|
9310
|
+
_IndexedDbStore.DB_VERSION = 3;
|
|
9268
9311
|
var IndexedDbStore = _IndexedDbStore;
|
|
9269
9312
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9270
9313
|
0 && (module.exports = {
|