@orbinum/sdk 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1019,397 +1019,6 @@ var EvmExplorer = class _EvmExplorer {
1019
1019
  }
1020
1020
  };
1021
1021
 
1022
- // src/indexer/IndexerClient.ts
1023
- var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
1024
- function normalizeBaseUrl(baseUrl) {
1025
- let url;
1026
- try {
1027
- url = new URL(baseUrl);
1028
- } catch {
1029
- throw new Error(`IndexerClient: invalid baseUrl ${JSON.stringify(baseUrl)}`);
1030
- }
1031
- const isLocal = LOCAL_HOSTS.has(url.hostname);
1032
- if (url.protocol !== "https:" && !(url.protocol === "http:" && isLocal)) {
1033
- throw new Error(
1034
- `IndexerClient: baseUrl must use https:// (got ${url.protocol}//${url.hostname}). Plain http is only allowed for localhost.`
1035
- );
1036
- }
1037
- return baseUrl.replace(/\/$/, "");
1038
- }
1039
- var IndexerClient = class _IndexerClient {
1040
- baseUrl;
1041
- timeoutMs;
1042
- constructor(config) {
1043
- this.baseUrl = normalizeBaseUrl(config.baseUrl);
1044
- this.timeoutMs = config.timeoutMs ?? 1e4;
1045
- }
1046
- // ─── Internal helpers ──────────────────────────────────────────────────────
1047
- async _fetchResponse(path) {
1048
- const controller = new AbortController();
1049
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
1050
- try {
1051
- return await fetch(`${this.baseUrl}${path}`, { signal: controller.signal });
1052
- } finally {
1053
- clearTimeout(timer);
1054
- }
1055
- }
1056
- async get(path) {
1057
- const res = await this._fetchResponse(path);
1058
- if (!res.ok) {
1059
- throw new Error(`IndexerClient: HTTP ${res.status} for ${path}`);
1060
- }
1061
- return res.json();
1062
- }
1063
- async getOrNull(path) {
1064
- const res = await this._fetchResponse(path);
1065
- if (res.status === 404) return null;
1066
- if (!res.ok) {
1067
- throw new Error(`IndexerClient: HTTP ${res.status} for ${path}`);
1068
- }
1069
- return res.json();
1070
- }
1071
- buildQuery(params) {
1072
- const entries = Object.entries(params).filter(([, v]) => v !== void 0);
1073
- if (entries.length === 0) return "";
1074
- const qs = entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
1075
- return `?${qs}`;
1076
- }
1077
- // ─── Commitments ───────────────────────────────────────────────────────────
1078
- /** Returns the total count of shielded commitments. */
1079
- async getCommitmentsCount() {
1080
- const res = await this.get("/shielded/commitments/count");
1081
- return res.total;
1082
- }
1083
- /** Returns a paginated list of shielded commitments. */
1084
- async getCommitments(params) {
1085
- const qs = this.buildQuery({
1086
- page: params?.page,
1087
- limit: params?.limit,
1088
- since_leaf_index: params?.sinceLeafIndex
1089
- });
1090
- return this.get(`/shielded/commitments${qs}`);
1091
- }
1092
- /** Returns a single commitment by its hex string, or null if not found. */
1093
- async getCommitmentByHex(hex) {
1094
- return this.getOrNull(
1095
- `/shielded/commitments/${encodeURIComponent(hex)}`
1096
- );
1097
- }
1098
- /**
1099
- * Returns a paginated list of stealth scan hints ordered ascending by leafIndex.
1100
- * Each hint contains only the fields required for ECDH triage and decryption:
1101
- * leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo.
1102
- *
1103
- * Use `sinceLeafIndex` for incremental scans (cursor = last seen leafIndex + 1).
1104
- */
1105
- async getScanHints(params) {
1106
- const qs = this.buildQuery({
1107
- page: params?.page,
1108
- limit: params?.limit,
1109
- since_leaf_index: params?.sinceLeafIndex
1110
- });
1111
- return this.get(`/shielded/scan-hints${qs}`);
1112
- }
1113
- // ─── Nullifiers ────────────────────────────────────────────────────────────
1114
- /** Returns a paginated list of spent nullifiers. */
1115
- async getNullifiers(params) {
1116
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1117
- return this.get(`/shielded/nullifiers${qs}`);
1118
- }
1119
- /** Returns the spent/unspent status of a nullifier. */
1120
- async getNullifierStatus(hex) {
1121
- return this.get(
1122
- `/shielded/nullifier/${encodeURIComponent(hex)}/status`
1123
- );
1124
- }
1125
- /**
1126
- * Downloads the full spent nullifier set and returns it as a Set of lowercase hex strings.
1127
- *
1128
- * The server sees an identical GET request regardless of which notes the wallet holds —
1129
- * the intersection is computed locally (PIR-A privacy model).
1130
- *
1131
- * Kept as the FALLBACK for readers that don't serve sealed chunks yet (and for
1132
- * small sets). New integrations should prefer the incremental chunk flow:
1133
- * `getNullifierManifest` → `getNullifierChunk` for missing chunks →
1134
- * `getNullifierTail`, persisting the set locally between rescans.
1135
- */
1136
- async getAllSpentNullifiers() {
1137
- const res = await this.get("/shielded/nullifiers/all");
1138
- return new Set(res.data.map((h) => h.toLowerCase()));
1139
- }
1140
- /**
1141
- * Universal index of the sealed nullifier chunks — identical request and
1142
- * response for every caller (no client-supplied position: PIR-A preserved).
1143
- *
1144
- * Returns `null` when the reader does not serve chunks yet (404) — the
1145
- * caller should fall back to `getAllSpentNullifiers`.
1146
- */
1147
- async getNullifierManifest() {
1148
- return this.getOrNull("/shielded/nullifiers/manifest");
1149
- }
1150
- /**
1151
- * One sealed, immutable chunk of the spent-nullifier set (ascending hex,
1152
- * lowercased). The digest comes from the manifest and lives in the URL, so
1153
- * a corrected chunk is a different URL — safe to cache forever client-side.
1154
- */
1155
- async getNullifierChunk(idx, digest) {
1156
- const res = await this.get(
1157
- `/shielded/nullifiers/chunks/${idx}/${encodeURIComponent(digest)}`
1158
- );
1159
- return res.data.map((h) => h.toLowerCase());
1160
- }
1161
- /**
1162
- * The mutable remainder of the nullifier set after the last sealed chunk.
1163
- * Identical request for every caller (no input). `afterChunks` lets the
1164
- * client detect a chunk sealed between its manifest fetch and this one.
1165
- */
1166
- async getNullifierTail() {
1167
- const res = await this.get("/shielded/nullifiers/tail");
1168
- return { afterChunks: res.afterChunks, data: res.data.map((h) => h.toLowerCase()) };
1169
- }
1170
- // ─── Private transfers ─────────────────────────────────────────────────────
1171
- /** Server-enforced max items per by-nullifiers / by-commitments request. */
1172
- static TRANSFER_LOOKUP_CHUNK = 50;
1173
- /**
1174
- * Chunked fetch for the transfer timestamp lookups. The reader silently
1175
- * truncates each request to 50 items, so larger inputs MUST be split or
1176
- * results are silently lost. Responses are merged per extrinsic
1177
- * (`blockNumber:extrinsicIndex`), concatenating the matched arrays, and
1178
- * sorted by block descending.
1179
- *
1180
- * Privacy note: these lookups send the wallet's own note identifiers to
1181
- * the indexer — a bounded, documented linkage tradeoff for timestamp
1182
- * recovery. They are NEVER used for spent-STATUS checks (PIR-A: status
1183
- * comes from the anonymous full-set `/shielded/nullifiers/all` download).
1184
- */
1185
- async fetchTransfersChunked(path, param, items, matchedField) {
1186
- if (items.length === 0) return [];
1187
- const normalized = items.map((i) => i.toLowerCase());
1188
- const chunks = [];
1189
- for (let i = 0; i < normalized.length; i += _IndexerClient.TRANSFER_LOOKUP_CHUNK) {
1190
- chunks.push(normalized.slice(i, i + _IndexerClient.TRANSFER_LOOKUP_CHUNK));
1191
- }
1192
- const responses = await Promise.all(
1193
- chunks.map((chunk) => {
1194
- const qs = this.buildQuery({ [param]: chunk.join(",") });
1195
- return this.get(
1196
- `${path}${qs}`
1197
- );
1198
- })
1199
- );
1200
- const byExtrinsic = /* @__PURE__ */ new Map();
1201
- for (const res of responses) {
1202
- for (const transfer of res.data) {
1203
- const key = `${transfer.blockNumber}:${transfer.extrinsicIndex ?? "null"}`;
1204
- const existing = byExtrinsic.get(key);
1205
- if (!existing) {
1206
- byExtrinsic.set(key, transfer);
1207
- continue;
1208
- }
1209
- const merged = /* @__PURE__ */ new Set([
1210
- ...existing[matchedField] ?? [],
1211
- ...transfer[matchedField] ?? []
1212
- ]);
1213
- existing[matchedField] = [...merged];
1214
- }
1215
- }
1216
- return [...byExtrinsic.values()].sort((a, b) => b.blockNumber - a.blockNumber);
1217
- }
1218
- /**
1219
- * Returns temporal metadata for private transfers that spent any of the given nullifiers.
1220
- * Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
1221
- * between inputs and outputs to prevent graph reconstruction.
1222
- * Inputs of any size are transparently chunked into requests of 50 (the server cap)
1223
- * and merged per extrinsic.
1224
- */
1225
- async getTransfersByNullifiers(nullifiers) {
1226
- return this.fetchTransfersChunked(
1227
- "/shielded/transfers/by-nullifiers",
1228
- "nullifiers",
1229
- nullifiers,
1230
- "matchedNullifiers"
1231
- );
1232
- }
1233
- /**
1234
- * Returns temporal metadata for private transfers that produced any of the given commitments.
1235
- * Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
1236
- * between outputs and inputs to prevent graph reconstruction.
1237
- * Inputs of any size are transparently chunked into requests of 50 (the server cap)
1238
- * and merged per extrinsic.
1239
- */
1240
- async getTransfersByCommitments(commitments) {
1241
- return this.fetchTransfersChunked(
1242
- "/shielded/transfers/by-commitments",
1243
- "commitments",
1244
- commitments,
1245
- "matchedCommitments"
1246
- );
1247
- }
1248
- // ─── Unshields ─────────────────────────────────────────────────────────────
1249
- /** Returns a paginated list of unshield events. */
1250
- async getUnshields(params) {
1251
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1252
- return this.get(`/shielded/unshields${qs}`);
1253
- }
1254
- // ─── Merkle roots ──────────────────────────────────────────────────────────
1255
- /** Returns a paginated list of Merkle root checkpoints. */
1256
- async getMerkleRoots(params) {
1257
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1258
- return this.get(`/shielded/merkle-roots${qs}`);
1259
- }
1260
- /** Returns the latest Merkle root, or null if none exists. */
1261
- async getLatestMerkleRoot() {
1262
- return this.getOrNull("/shielded/merkle-roots/latest");
1263
- }
1264
- // ─── Address activity ──────────────────────────────────────────────────────
1265
- /** Returns a paginated list of extrinsics signed by the given address. */
1266
- async getAddressExtrinsics(address, params) {
1267
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1268
- return this.get(
1269
- `/address/${encodeURIComponent(address.toLowerCase())}/extrinsics${qs}`
1270
- );
1271
- }
1272
- /** Returns a paginated list of EVM transactions filtered by address and/or block number. */
1273
- async getEvmTransactions(params) {
1274
- const qs = this.buildQuery({
1275
- page: params?.page,
1276
- limit: params?.limit,
1277
- address: params?.address?.toLowerCase(),
1278
- blockNumber: params?.blockNumber
1279
- });
1280
- return this.get(`/evm/transactions${qs}`);
1281
- }
1282
- /** Returns a single EVM transaction by hash, or null if not found. */
1283
- async getEvmTransactionByHash(hash) {
1284
- return this.getOrNull(
1285
- `/evm/transactions/${encodeURIComponent(hash.toLowerCase())}`
1286
- );
1287
- }
1288
- // ─── Blocks ────────────────────────────────────────────────────────────────
1289
- /** Returns a paginated list of indexed blocks. */
1290
- async getBlocks(params) {
1291
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1292
- return this.get(`/blocks${qs}`);
1293
- }
1294
- /** Returns a single block by number or hash, or null if not found. */
1295
- async getBlock(numberOrHash) {
1296
- return this.getOrNull(`/blocks/${encodeURIComponent(String(numberOrHash))}`);
1297
- }
1298
- // ─── Address-scoped shielded activity ─────────────────────────────────────
1299
- /**
1300
- * Returns a paginated list of unshield events where the given address is the recipient.
1301
- * Accepts a 0x-prefixed EVM address or an SS58 Substrate address.
1302
- */
1303
- async getAddressUnshields(address, params) {
1304
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1305
- return this.get(
1306
- `/address/${encodeURIComponent(address)}/unshields${qs}`
1307
- );
1308
- }
1309
- /**
1310
- * Returns the shielded-pool BOUNDARY activity for an address: shields it
1311
- * deposited and unshields it received, tagged `kind: 'shield' | 'unshield'`.
1312
- * Only boundary fields are returned (block, asset, amount for unshields,
1313
- * timestamp, tx hash) — note internals are never served per-address, and
1314
- * private transfers carry no address at all (PIR-A).
1315
- */
1316
- async getAddressShieldedActivity(address, params) {
1317
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1318
- return this.get(
1319
- `/shielded/address/${encodeURIComponent(address)}${qs}`
1320
- );
1321
- }
1322
- // ─── Relayers ──────────────────────────────────────────────────────────────
1323
- /** Returns a paginated list of relayers. Filter by active status with `active`. */
1324
- async getRelayers(params) {
1325
- const qs = this.buildQuery({
1326
- page: params?.page,
1327
- limit: params?.limit,
1328
- active: params?.active === void 0 ? void 0 : params.active ? "true" : "false"
1329
- });
1330
- return this.get(`/relayers${qs}`);
1331
- }
1332
- /** Returns a single relayer by EVM address, or null if not found. */
1333
- async getRelayer(evmAddress) {
1334
- return this.getOrNull(`/relayers/${encodeURIComponent(evmAddress.toLowerCase())}`);
1335
- }
1336
- /** Returns a paginated list of relay fee events. */
1337
- async getRelayFees(params) {
1338
- const qs = this.buildQuery({
1339
- page: params?.page,
1340
- limit: params?.limit,
1341
- relayer: params?.relayer,
1342
- type: params?.type
1343
- });
1344
- return this.get(`/relayers/fees${qs}`);
1345
- }
1346
- /** Returns aggregated relay fee balances per asset for a given relayer account. */
1347
- async getRelayFeesSummary(relayer) {
1348
- return this.get(
1349
- `/relayers/fees/summary/${encodeURIComponent(relayer)}`
1350
- );
1351
- }
1352
- // ─── Registered assets ─────────────────────────────────────────────────────
1353
- /** Returns a paginated list of assets registered via register_asset. */
1354
- async getRegisteredAssets(params) {
1355
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1356
- return this.get(`/shielded/assets${qs}`);
1357
- }
1358
- /** Returns a single registered asset by its ID, or null if not found. */
1359
- async getRegisteredAsset(assetId) {
1360
- return this.getOrNull(`/shielded/assets/${encodeURIComponent(assetId)}`);
1361
- }
1362
- // ─── Validators ────────────────────────────────────────────────────────────
1363
- /** Returns a paginated list of validators. Filter by lifecycle status with `status`. */
1364
- async getValidators(params) {
1365
- const qs = this.buildQuery({
1366
- page: params?.page,
1367
- limit: params?.limit,
1368
- status: params?.status
1369
- });
1370
- return this.get(`/validators${qs}`);
1371
- }
1372
- /** Returns a single validator by account address, or null if not found. */
1373
- async getValidator(account) {
1374
- return this.getOrNull(`/validators/${encodeURIComponent(account)}`);
1375
- }
1376
- // ─── Sessions ──────────────────────────────────────────────────────────────
1377
- /** Returns a paginated list of session rotations, ordered by most recent first. */
1378
- async getSessions(params) {
1379
- const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
1380
- return this.get(`/sessions${qs}`);
1381
- }
1382
- // ─── Stats & Health ────────────────────────────────────────────────────────
1383
- /** Returns aggregated indexer statistics. */
1384
- async getStats() {
1385
- return this.get("/stats");
1386
- }
1387
- /**
1388
- * Returns transaction activity bucketed per hour over the last `hours` hours
1389
- * of chain time (default 24, max 168). For sparklines / activity charts.
1390
- */
1391
- async getActivity(hours = 24) {
1392
- return this.get(`/stats/activity?hours=${hours}`);
1393
- }
1394
- /** Returns true if the indexer health endpoint responds OK. */
1395
- async isHealthy() {
1396
- try {
1397
- const controller = new AbortController();
1398
- const timer = setTimeout(() => controller.abort(), this.timeoutMs);
1399
- try {
1400
- const res = await fetch(`${this.baseUrl}/health`, {
1401
- signal: controller.signal
1402
- });
1403
- return res.ok;
1404
- } finally {
1405
- clearTimeout(timer);
1406
- }
1407
- } catch {
1408
- return false;
1409
- }
1410
- }
1411
- };
1412
-
1413
1022
  // src/shielded-pool/pallet/ShieldedPoolModule.ts
1414
1023
  import "polkadot-api";
1415
1024
 
@@ -1518,8 +1127,8 @@ var BABYJUB_SUBORDER = 273603035897990940278080071815715938607681397215856725920
1518
1127
  // src/shielded-pool/protocol/memo.ts
1519
1128
  import { sha256 } from "@noble/hashes/sha2.js";
1520
1129
  var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
1521
- var MEMO_PLAINTEXT_SIZE = 116;
1522
- function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
1130
+ var MEMO_PLAINTEXT_SIZE = 120;
1131
+ function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion) {
1523
1132
  const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
1524
1133
  const view = new DataView(buf.buffer);
1525
1134
  view.setBigUint64(0, value & 0xffffffffffffffffn, true);
@@ -1528,6 +1137,7 @@ function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
1528
1137
  buf.set(blinding.slice(0, 32), 48);
1529
1138
  view.setUint32(80, assetId >>> 0, true);
1530
1139
  buf.set(counterpartyPk.slice(0, 32), 84);
1140
+ view.setUint32(116, circuitVersion >>> 0, true);
1531
1141
  return buf;
1532
1142
  }
1533
1143
  function deriveEncryptionKey(sharedSecret, commitment) {
@@ -1540,7 +1150,7 @@ function deriveEncryptionKey(sharedSecret, commitment) {
1540
1150
 
1541
1151
  // src/shielded-pool/protocol/EncryptedMemo.ts
1542
1152
  var NONCE_SIZE = 12;
1543
- var CIPHERTEXT_SIZE = 132;
1153
+ var CIPHERTEXT_SIZE = 136;
1544
1154
  var EPH_PK_SIZE = 32;
1545
1155
  var ENCRYPTED_MEMO_SIZE = NONCE_SIZE + CIPHERTEXT_SIZE + EPH_PK_SIZE;
1546
1156
  function bytesToBjjScalar(bytes) {
@@ -1559,14 +1169,15 @@ function parsePlaintext(nonce, ciphertextWithMac, encKey) {
1559
1169
  const blinding = bytesToBigintLE(plaintext.slice(48, 80));
1560
1170
  const assetId = BigInt(view.getUint32(80, true));
1561
1171
  const counterpartyPk = bytesToBigintLE(plaintext.slice(84, 116));
1562
- return { value, ownerPk, blinding, assetId, counterpartyPk };
1172
+ const circuitVersion = view.getUint32(116, true);
1173
+ return { value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion };
1563
1174
  } catch {
1564
1175
  return null;
1565
1176
  }
1566
1177
  }
1567
1178
  var EncryptedMemo = {
1568
1179
  /**
1569
- * Build and encrypt a memo for a note using ECDH (v2, 168 bytes).
1180
+ * Build and encrypt a memo for a note using ECDH (v2, 180 bytes).
1570
1181
  *
1571
1182
  * @param value Note value in planck.
1572
1183
  * @param ownerPk 32-byte owner public key (LE).
@@ -1578,11 +1189,20 @@ var EncryptedMemo = {
1578
1189
  * decoded from a privacy address).
1579
1190
  * Pass `new Uint8Array(32)` (all zeros) for a publicly-readable memo.
1580
1191
  * @param counterpartyPk 32-byte counterparty BJJ Ax. Default: all zeros.
1581
- * @returns 168-byte encrypted memo: nonce(12) || ciphertext+MAC(124) || ephPk(32).
1192
+ * @param circuitVersion ZK circuit version the note is spent under. Default: 0.
1193
+ * @param ephSkOverride 32-byte ephemeral secret key (stealth coordination). Optional.
1194
+ * @returns 180-byte encrypted memo: nonce(12) || ciphertext+MAC(136) || ephPk(32).
1582
1195
  */
1583
- encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), ephSkOverride) {
1196
+ encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), circuitVersion = 0, ephSkOverride) {
1584
1197
  const nonce = randomBytes(NONCE_SIZE);
1585
- const plaintext = serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk);
1198
+ const plaintext = serializeMemo(
1199
+ value,
1200
+ ownerPk,
1201
+ blinding,
1202
+ assetId,
1203
+ counterpartyPk,
1204
+ circuitVersion
1205
+ );
1586
1206
  const isZeroKey = recipientIvkPacked.every((b) => b === 0);
1587
1207
  let sharedSecret;
1588
1208
  let ephPkPackedBytes;
@@ -1613,29 +1233,31 @@ var EncryptedMemo = {
1613
1233
  return result;
1614
1234
  },
1615
1235
  /**
1616
- * Returns a 168-byte public memo encrypted with a zero viewing key.
1236
+ * Returns a 180-byte public memo encrypted with a zero viewing key.
1617
1237
  * Decryptable by anyone with `decrypt(memo, commitment, new Uint8Array(32))`.
1618
1238
  * Convenience alias for `encrypt(..., new Uint8Array(32))`.
1619
1239
  */
1620
- encryptPublic(value, ownerPk, blinding, assetId, commitment) {
1240
+ encryptPublic(value, ownerPk, blinding, assetId, commitment, circuitVersion = 0) {
1621
1241
  return EncryptedMemo.encrypt(
1622
1242
  value,
1623
1243
  ownerPk,
1624
1244
  blinding,
1625
1245
  assetId,
1626
1246
  commitment,
1627
- new Uint8Array(32)
1247
+ new Uint8Array(32),
1248
+ new Uint8Array(32),
1249
+ circuitVersion
1628
1250
  );
1629
1251
  },
1630
1252
  /**
1631
- * Returns a 168-byte zeroed dummy memo (no information, always valid on-chain).
1253
+ * Returns a 180-byte zeroed dummy memo (no information, always valid on-chain).
1632
1254
  */
1633
1255
  dummy() {
1634
1256
  return new Uint8Array(ENCRYPTED_MEMO_SIZE);
1635
1257
  },
1636
1258
  /**
1637
1259
  * Validates that `bytes` is a properly-sized encrypted memo.
1638
- * Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (168 bytes).
1260
+ * Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (180 bytes).
1639
1261
  *
1640
1262
  * Call this at system boundaries (extrinsic builders, precompile encoders)
1641
1263
  * to catch malformed memos before they reach the chain and fail on-chain.
@@ -1656,7 +1278,7 @@ var EncryptedMemo = {
1656
1278
  * Returns null if decryption fails — wrong key, bad MAC, or malformed memo.
1657
1279
  * Never throws; safe for scan loops.
1658
1280
  *
1659
- * @param memoBytes 168-byte encrypted memo.
1281
+ * @param memoBytes 180-byte encrypted memo.
1660
1282
  * @param commitment 32-byte note commitment (LE).
1661
1283
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
1662
1284
  */
@@ -1668,13 +1290,13 @@ var EncryptedMemo = {
1668
1290
  * Extract the ECDH shared secret from an encrypted memo using the recipient's viewing secret key.
1669
1291
  *
1670
1292
  * Used by NoteDecryptor to obtain the shared secret needed for stealth address derivation
1671
- * without re-running the full decrypt path. Safe to call on any 168-byte memo.
1293
+ * without re-running the full decrypt path. Safe to call on any 180-byte memo.
1672
1294
  *
1673
1295
  * Returns `new Uint8Array(32)` (all zeros) for public/dummy memos (zero ephPk).
1674
1296
  * Returns `null` if the memo is malformed or the ephPk is not a valid BJJ point.
1675
1297
  * Never throws; safe for scan loops.
1676
1298
  *
1677
- * @param memoBytes 168-byte encrypted memo.
1299
+ * @param memoBytes 180-byte encrypted memo.
1678
1300
  * @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
1679
1301
  */
1680
1302
  extractSharedSecret(memoBytes, viewingSecretKey) {
@@ -1878,7 +1500,7 @@ var ShieldedPoolModule = class {
1878
1500
  * Withdraws tokens from the shielded pool to a public address.
1879
1501
  * Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
1880
1502
  * Pass a `signer` to fall back to signed submission (e.g. for testing).
1881
- * Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee)
1503
+ * Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee, changeCommitment, changeEncryptedMemo, relayer, circuitVersion)
1882
1504
  */
1883
1505
  async unshield(params, signer, txOptions) {
1884
1506
  const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "unshield");
@@ -1902,8 +1524,9 @@ var ShieldedPoolModule = class {
1902
1524
  fee: params.fee ?? 0n,
1903
1525
  change_commitment: changeCommitment,
1904
1526
  change_encrypted_memo: changeEncryptedMemo,
1905
- relayer: void 0
1527
+ relayer: void 0,
1906
1528
  // Option<H160> — None for direct Substrate submissions
1529
+ circuit_version: params.circuitVersion
1907
1530
  });
1908
1531
  if (signer) {
1909
1532
  return toTxResult(
@@ -1916,7 +1539,7 @@ var ShieldedPoolModule = class {
1916
1539
  * Performs a private (shielded) transfer between two notes.
1917
1540
  * Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
1918
1541
  * Pass a `signer` to fall back to signed submission (e.g. for testing).
1919
- * Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee)
1542
+ * Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee, relayer, circuitVersion)
1920
1543
  */
1921
1544
  async privateTransfer(params, signer, txOptions) {
1922
1545
  const nullifiers = params.inputs.map((inp) => inp.nullifier);
@@ -1937,8 +1560,9 @@ var ShieldedPoolModule = class {
1937
1560
  encrypted_memos: memos,
1938
1561
  asset_id: params.assetId,
1939
1562
  fee: params.fee ?? 0n,
1940
- relayer: void 0
1563
+ relayer: void 0,
1941
1564
  // Option<H160> — None for direct Substrate submissions
1565
+ circuit_version: params.circuitVersion
1942
1566
  });
1943
1567
  if (signer) {
1944
1568
  return toTxResult(
@@ -1972,7 +1596,7 @@ var ShieldedPoolModule = class {
1972
1596
  * This is a SIGNED transaction — the relayer must sign it with their wallet.
1973
1597
  * Before calling this, generate a ZK value proof with generateFeeClaimProof() (not yet implemented).
1974
1598
  *
1975
- * Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals)
1599
+ * Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals, circuit_version)
1976
1600
  */
1977
1601
  async claimShieldedFees(params, signer, txOptions) {
1978
1602
  EncryptedMemo.validate(params.encryptedMemo, "claimShieldedFees.encryptedMemo");
@@ -1983,7 +1607,8 @@ var ShieldedPoolModule = class {
1983
1607
  asset_id: params.assetId,
1984
1608
  encrypted_memo: params.encryptedMemo,
1985
1609
  proof: params.proof,
1986
- public_signals: params.publicSignals
1610
+ public_signals: params.publicSignals,
1611
+ circuit_version: params.circuitVersion
1987
1612
  });
1988
1613
  return toTxResult(
1989
1614
  await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
@@ -2501,6 +2126,77 @@ var ZkVerifierModule = class {
2501
2126
  }
2502
2127
  };
2503
2128
 
2129
+ // src/shielded-pool/CircuitVersionResolver.ts
2130
+ import {
2131
+ WebArtifactProvider,
2132
+ circuitTypeToId
2133
+ } from "@orbinum/proof-generator";
2134
+ var CircuitVersionResolver = class {
2135
+ constructor(zkVerifier, baseUrl, providerFactory) {
2136
+ this.zkVerifier = zkVerifier;
2137
+ this.makeProvider = providerFactory ?? ((circuit, noteVersion) => new WebArtifactProvider({
2138
+ ...baseUrl ? { baseUrl } : {},
2139
+ circuitVersions: { [circuit]: noteVersion }
2140
+ }));
2141
+ }
2142
+ zkVerifier;
2143
+ makeProvider;
2144
+ /**
2145
+ * Resolves the prover + on-chain version for spending a note of `circuit`
2146
+ * created under `noteVersion`. Fail-closed: throws on unsupported version or
2147
+ * VK-hash mismatch (CDN vs chain), before generating any proof.
2148
+ */
2149
+ async resolve(circuit, noteVersion) {
2150
+ if (!Number.isInteger(noteVersion) || noteVersion <= 0) {
2151
+ throw new Error(
2152
+ `CircuitVersionResolver: invalid note circuitVersion ${noteVersion} for ${circuit}`
2153
+ );
2154
+ }
2155
+ const provider = this.makeProvider(circuit, noteVersion);
2156
+ const resolved = await provider.getResolvedVersion(circuit);
2157
+ if (resolved.version !== noteVersion) {
2158
+ throw new Error(
2159
+ `CircuitVersionResolver: prover resolved ${circuit} to v${resolved.version}, expected v${noteVersion}`
2160
+ );
2161
+ }
2162
+ const circuitId = circuitTypeToId(circuit);
2163
+ const info = await this.zkVerifier.getCircuitVersionInfo(circuitId);
2164
+ if (!info) {
2165
+ throw new Error(
2166
+ `CircuitVersionResolver: chain reports no version info for ${circuit} (id ${circuitId})`
2167
+ );
2168
+ }
2169
+ if (!info.supportedVersions.includes(noteVersion)) {
2170
+ throw new Error(
2171
+ `CircuitVersionResolver: chain does not support ${circuit} v${noteVersion} (supported: ${info.supportedVersions.join(", ")})`
2172
+ );
2173
+ }
2174
+ const chainVk = info.vkHashes.find((h) => h.version === noteVersion);
2175
+ if (!chainVk) {
2176
+ throw new Error(
2177
+ `CircuitVersionResolver: chain has no VK hash for ${circuit} v${noteVersion}`
2178
+ );
2179
+ }
2180
+ if (!vkHashEquals(chainVk.vkHash, resolved.vkHash)) {
2181
+ throw new Error(
2182
+ `CircuitVersionResolver: VK hash mismatch for ${circuit} v${noteVersion} \u2014 prover ${resolved.vkHash}, chain ${chainVk.vkHash}`
2183
+ );
2184
+ }
2185
+ return { provider, version: noteVersion };
2186
+ }
2187
+ };
2188
+ function vkHashEquals(a, b) {
2189
+ const na = normalizeHash(a);
2190
+ const nb = normalizeHash(b);
2191
+ if (na === null || nb === null) return false;
2192
+ return na === nb;
2193
+ }
2194
+ function normalizeHash(h) {
2195
+ if (typeof h !== "string") return null;
2196
+ const s = h.toLowerCase().startsWith("0x") ? h.toLowerCase().slice(2) : h.toLowerCase();
2197
+ return /^[0-9a-f]{64}$/.test(s) ? s : null;
2198
+ }
2199
+
2504
2200
  // src/relayer/RelayerStatusModule.ts
2505
2201
  var RelayerStatusModule = class {
2506
2202
  constructor(substrate) {
@@ -2746,12 +2442,12 @@ var AM_SEL = {
2746
2442
  var SP_SEL = {
2747
2443
  // shield(uint32,bytes32,bytes) → 0x9feb22ea (payable, amount = msg.value)
2748
2444
  SHIELD: new Uint8Array([159, 235, 34, 234]),
2749
- // privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256) 0x8c0f5d24
2750
- PRIVATE_TRANSFER: new Uint8Array([140, 15, 93, 36]),
2751
- // unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32) 0xd21d9a79
2752
- UNSHIELD: new Uint8Array([210, 29, 154, 121]),
2753
- // claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes) 0x42e1e74c
2754
- CLAIM_SHIELDED_FEES: new Uint8Array([66, 225, 231, 76])
2445
+ // privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32) 0x66ed2cd4
2446
+ PRIVATE_TRANSFER: new Uint8Array([102, 237, 44, 212]),
2447
+ // unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32) 0x4e505348
2448
+ UNSHIELD: new Uint8Array([78, 80, 83, 72]),
2449
+ // claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32) 0x88d9deba
2450
+ CLAIM_SHIELDED_FEES: new Uint8Array([136, 217, 222, 186])
2755
2451
  };
2756
2452
  var KNOWN_PRECOMPILES = {
2757
2453
  // ── Ethereum standard (EIP) ─────────────────────────────────────────────
@@ -2795,9 +2491,9 @@ var KNOWN_PRECOMPILES = {
2795
2491
  name: "ShieldedPool",
2796
2492
  functions: {
2797
2493
  "9feb22ea": "shield(uint32,bytes32,bytes)",
2798
- "8c0f5d24": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)",
2799
- d21d9a79: "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32)",
2800
- "42e1e74c": "claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)"
2494
+ "66ed2cd4": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32)",
2495
+ "4e505348": "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)",
2496
+ "88d9deba": "claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)"
2801
2497
  }
2802
2498
  }
2803
2499
  };
@@ -2851,7 +2547,8 @@ var ShieldedPoolPrecompile = class {
2851
2547
  // ─── privateTransfer ───────────────────────────────────────────────────────
2852
2548
  /**
2853
2549
  * Returns the ABI-encoded calldata for
2854
- * `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256)`.
2550
+ * `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256, uint32)`.
2551
+ * The trailing `uint32` is the circuit version the input notes were created under.
2855
2552
  */
2856
2553
  buildPrivateTransferCalldata(params) {
2857
2554
  const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
@@ -2872,7 +2569,8 @@ var ShieldedPoolPrecompile = class {
2872
2569
  { type: "bytes32[]", value: commitments },
2873
2570
  { type: "bytes[]", value: memos },
2874
2571
  { type: "uint", value: BigInt(params.assetId) },
2875
- { type: "uint", value: params.fee ?? 0n }
2572
+ { type: "uint", value: params.fee ?? 0n },
2573
+ { type: "uint", value: BigInt(params.circuitVersion) }
2876
2574
  );
2877
2575
  }
2878
2576
  /**
@@ -2913,7 +2611,8 @@ var ShieldedPoolPrecompile = class {
2913
2611
  { type: "bytes32", value: recipientBytes },
2914
2612
  { type: "uint", value: params.fee ?? 0n },
2915
2613
  { type: "bytes32", value: changeCommitment },
2916
- { type: "bytes", value: changeEncryptedMemo }
2614
+ { type: "bytes", value: changeEncryptedMemo },
2615
+ { type: "uint", value: BigInt(params.circuitVersion) }
2917
2616
  );
2918
2617
  }
2919
2618
  /**
@@ -2963,7 +2662,7 @@ var ShieldedPoolPrecompile = class {
2963
2662
  // ─── claimShieldedFees ───────────────────────────────────────────────────────────────────
2964
2663
  /**
2965
2664
  * Returns the ABI-encoded calldata for
2966
- * `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)`.
2665
+ * `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)`.
2967
2666
  *
2968
2667
  * ABI layout (params after selector):
2969
2668
  * - `commitment` — bytes32 (fixed)
@@ -2972,6 +2671,7 @@ var ShieldedPoolPrecompile = class {
2972
2671
  * - `memo` — bytes (dynamic)
2973
2672
  * - `proof` — bytes (dynamic, 128 bytes Groth16)
2974
2673
  * - `publicSignals` — bytes (dynamic, 76 bytes)
2674
+ * - `circuitVersion` — uint32 (fixed, right-aligned)
2975
2675
  *
2976
2676
  * The validator identity is derived from `msg.sender` in the precompile —
2977
2677
  * do NOT include it in the calldata.
@@ -2997,7 +2697,8 @@ var ShieldedPoolPrecompile = class {
2997
2697
  { type: "uint", value: BigInt(params.assetId) },
2998
2698
  { type: "bytes", value: params.encryptedMemo },
2999
2699
  { type: "bytes", value: params.proof },
3000
- { type: "bytes", value: params.publicSignals }
2700
+ { type: "bytes", value: params.publicSignals },
2701
+ { type: "uint", value: BigInt(params.circuitVersion) }
3001
2702
  );
3002
2703
  }
3003
2704
  /**
@@ -3419,12 +3120,6 @@ var OrbinumClient = class _OrbinumClient {
3419
3120
  * `null` when `evmRpc` is not configured.
3420
3121
  */
3421
3122
  evmExplorer;
3422
- /**
3423
- * HTTP client for the Orbinum indexer REST API.
3424
- * Provides paginated access to indexed blocks, extrinsics, shielded events, and nullifiers.
3425
- * `null` when `indexerUrl` is not configured.
3426
- */
3427
- indexer;
3428
3123
  /** Shielded-pool extrinsics and Merkle tree queries (`shield`, `unshield`, `privateTransfer`, …). */
3429
3124
  shieldedPool;
3430
3125
  /** Account-mapping extrinsics: aliases, chain links, metadata, marketplace, and identity. */
@@ -3435,6 +3130,11 @@ var OrbinumClient = class _OrbinumClient {
3435
3130
  chain;
3436
3131
  /** Typed access to `zkVerifier_*` custom RPC endpoints. */
3437
3132
  zkVerifier;
3133
+ /**
3134
+ * Resolves a note's circuit version to a pinned prover + on-chain version
3135
+ * before spending it (fail-closed: throws on unsupported version / VK mismatch).
3136
+ */
3137
+ circuitVersionResolver;
3438
3138
  /** Typed access to `relayer_*` RPC endpoints (registry lookup and pending fee queries). */
3439
3139
  relayerStatus;
3440
3140
  /**
@@ -3443,16 +3143,16 @@ var OrbinumClient = class _OrbinumClient {
3443
3143
  */
3444
3144
  precompiles;
3445
3145
  /** @internal Use `OrbinumClient.connect()` to obtain an instance. */
3446
- constructor(substrate, evm, indexer) {
3146
+ constructor(substrate, evm, circuitsBaseUrl) {
3447
3147
  this.substrate = substrate;
3448
3148
  this.evm = evm;
3449
3149
  this.evmExplorer = evm ? new EvmExplorer(evm) : null;
3450
- this.indexer = indexer;
3451
3150
  this.shieldedPool = new ShieldedPoolModule(substrate);
3452
3151
  this.accountMapping = new AccountMappingModule(substrate);
3453
3152
  this.privacy = new PrivacyModule(substrate);
3454
3153
  this.chain = new ChainModule(substrate);
3455
3154
  this.zkVerifier = new ZkVerifierModule(substrate);
3155
+ this.circuitVersionResolver = new CircuitVersionResolver(this.zkVerifier, circuitsBaseUrl);
3456
3156
  this.relayerStatus = new RelayerStatusModule(substrate);
3457
3157
  this.precompiles = evm ? {
3458
3158
  shieldedPool: new ShieldedPoolPrecompile(evm),
@@ -3472,8 +3172,7 @@ var OrbinumClient = class _OrbinumClient {
3472
3172
  config.connectTimeoutMs ?? 15e3
3473
3173
  );
3474
3174
  const evm = config.evmRpc ? new EvmClient(config.evmRpc) : null;
3475
- const indexer = config.indexerUrl ? new IndexerClient({ baseUrl: config.indexerUrl }) : null;
3476
- return new _OrbinumClient(substrate, evm, indexer);
3175
+ return new _OrbinumClient(substrate, evm, config.circuitsBaseUrl);
3477
3176
  }
3478
3177
  /** Closes the underlying Substrate WebSocket connection and releases all resources. */
3479
3178
  destroy() {
@@ -3594,7 +3293,8 @@ var OrbinumClientProvider = class {
3594
3293
  substrateWs: this.config.substrateWs
3595
3294
  };
3596
3295
  if (this.config.evmRpc) connectConfig.evmRpc = this.config.evmRpc;
3597
- if (this.config.indexerUrl) connectConfig.indexerUrl = this.config.indexerUrl;
3296
+ if (this.config.circuitsBaseUrl)
3297
+ connectConfig.circuitsBaseUrl = this.config.circuitsBaseUrl;
3598
3298
  const clientPromise = OrbinumClient.connect(connectConfig);
3599
3299
  const client = await Promise.race([clientPromise, timeoutPromise]);
3600
3300
  orphanClient = client;
@@ -3781,6 +3481,9 @@ var OrbinumClientProvider = class {
3781
3481
  }
3782
3482
  };
3783
3483
 
3484
+ // src/shielded-pool/protocol/types.ts
3485
+ var CURRENT_CIRCUIT_VERSION = 1;
3486
+
3784
3487
  // src/utils/stealth.ts
3785
3488
  import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
3786
3489
  import { hkdf } from "@noble/hashes/hkdf.js";
@@ -3889,6 +3592,7 @@ var NoteBuilder = class {
3889
3592
  const blinding = input.blinding ?? BigInt(Date.now());
3890
3593
  const spendingKey = input.spendingKey ?? 0n;
3891
3594
  const counterpartyPk = input.counterpartyPk ?? 0n;
3595
+ const circuitVersion = input.circuitVersion ?? CURRENT_CIRCUIT_VERSION;
3892
3596
  const useStealth = input.viewingPublicKey !== void 0 && input.recipientOwnerPk !== void 0;
3893
3597
  let memo;
3894
3598
  if (useStealth) {
@@ -3923,6 +3627,7 @@ var NoteBuilder = class {
3923
3627
  stealthCommitmentBytes,
3924
3628
  recipientIvkPacked,
3925
3629
  bigintTo32Le(counterpartyPk),
3630
+ circuitVersion,
3926
3631
  ephSk
3927
3632
  )
3928
3633
  );
@@ -3940,6 +3645,7 @@ var NoteBuilder = class {
3940
3645
  ownerPk: effectiveOwnerPk,
3941
3646
  blinding,
3942
3647
  spendingKey,
3648
+ circuitVersion,
3943
3649
  spent: false,
3944
3650
  spentAt: null,
3945
3651
  commitment: commitment2,
@@ -3962,7 +3668,8 @@ var NoteBuilder = class {
3962
3668
  Number(assetId),
3963
3669
  commitmentBytes,
3964
3670
  input.viewingPublicKey,
3965
- bigintTo32Le(counterpartyPk)
3671
+ bigintTo32Le(counterpartyPk),
3672
+ circuitVersion
3966
3673
  )
3967
3674
  ) : Array.from(EncryptedMemo.dummy());
3968
3675
  if (memo.length !== ENCRYPTED_MEMO_SIZE)
@@ -3975,6 +3682,7 @@ var NoteBuilder = class {
3975
3682
  ownerPk,
3976
3683
  blinding,
3977
3684
  spendingKey,
3685
+ circuitVersion,
3978
3686
  spent: false,
3979
3687
  spentAt: null,
3980
3688
  commitment,
@@ -3986,7 +3694,7 @@ var NoteBuilder = class {
3986
3694
  };
3987
3695
  }
3988
3696
  /**
3989
- * Build the 168-byte ECDH-encrypted memo for a note.
3697
+ * Build the 180-byte ECDH-encrypted memo for a note.
3990
3698
  *
3991
3699
  * Pure TypeScript implementation — no WASM dependency.
3992
3700
  * Uses ChaCha20-Poly1305 with ECDH key agreement (BabyJubJub ephemeral keypair).
@@ -4005,7 +3713,8 @@ var NoteBuilder = class {
4005
3713
  Number(note.assetId),
4006
3714
  bigintTo32Le(note.commitment),
4007
3715
  recipientIvkPacked ?? new Uint8Array(32),
4008
- counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n)
3716
+ counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n),
3717
+ note.circuitVersion
4009
3718
  );
4010
3719
  }
4011
3720
  };
@@ -4067,6 +3776,7 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
4067
3776
  ownerPk: effectiveOwnerPk,
4068
3777
  blinding: plaintext.blinding,
4069
3778
  spendingKey: effectiveSpendingKey,
3779
+ circuitVersion: plaintext.circuitVersion,
4070
3780
  spent: false,
4071
3781
  spentAt: null,
4072
3782
  commitment: recomputed,
@@ -4140,7 +3850,7 @@ function selectNotes(notes, needed) {
4140
3850
  for (let j = i + 1; j < sorted.length; j++) {
4141
3851
  const a = sorted[i];
4142
3852
  const b = sorted[j];
4143
- if (a !== void 0 && b !== void 0 && a.value + b.value >= needed) {
3853
+ if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
4144
3854
  return [a, b];
4145
3855
  }
4146
3856
  }
@@ -4497,6 +4207,11 @@ async function encryptNote(key, blindKey, note) {
4497
4207
  }
4498
4208
  async function decryptNoteRecord(key, rec) {
4499
4209
  const note = await decryptJson(key, rec.iv, rec.ciphertext);
4210
+ if (typeof note.circuitVersion !== "number") {
4211
+ throw new Error(
4212
+ "decryptNoteRecord: note is missing circuitVersion (invalid or corrupt record)"
4213
+ );
4214
+ }
4500
4215
  return applyNoteStatus(note, {
4501
4216
  ...rec.spent !== void 0 && { spent: rec.spent },
4502
4217
  spentAt: rec.spentAt ?? null
@@ -4508,9 +4223,9 @@ async function noteBlindTag(blindKey, hex) {
4508
4223
 
4509
4224
  // src/proof-generator/unshield.ts
4510
4225
  import {
4511
- CircuitType,
4226
+ CircuitType as CircuitType2,
4512
4227
  generateProof,
4513
- WebArtifactProvider
4228
+ WebArtifactProvider as WebArtifactProvider2
4514
4229
  } from "@orbinum/proof-generator";
4515
4230
  import { randomBytes as randomBytes3 } from "@noble/ciphers/utils.js";
4516
4231
  import { mulPointEscalar as mulPointEscalar6, Base8 as Base84 } from "@zk-kit/baby-jubjub";
@@ -4557,18 +4272,18 @@ async function generateUnshieldProof(inputs, options = {}) {
4557
4272
  change_blinding: changeBlinding.toString(),
4558
4273
  change_owner_pubkey: changeOwnerPubkey.toString()
4559
4274
  };
4560
- const provider = options.provider ?? new WebArtifactProvider();
4275
+ const provider = options.provider ?? new WebArtifactProvider2();
4561
4276
  const opts = { provider };
4562
4277
  if (options.verbose !== void 0) opts.verbose = options.verbose;
4563
- const proofResult = await generateProof(CircuitType.Unshield, circuitInputs, opts);
4278
+ const proofResult = await generateProof(CircuitType2.Unshield, circuitInputs, opts);
4564
4279
  return { ...proofResult, changeCommitment, changeValue, changeBlinding, changeOwnerPubkey };
4565
4280
  }
4566
4281
 
4567
4282
  // src/proof-generator/transfer.ts
4568
4283
  import {
4569
- CircuitType as CircuitType2,
4284
+ CircuitType as CircuitType3,
4570
4285
  generateProof as generateProof2,
4571
- WebArtifactProvider as WebArtifactProvider2
4286
+ WebArtifactProvider as WebArtifactProvider3
4572
4287
  } from "@orbinum/proof-generator";
4573
4288
  async function generateTransferProof(params, options = {}) {
4574
4289
  const root = leHexToBigint(params.merkleRoot).toString();
@@ -4594,17 +4309,17 @@ async function generateTransferProof(params, options = {}) {
4594
4309
  output_owner_pubkeys: [o0.ownerPk.toString(), o1.ownerPk.toString()],
4595
4310
  output_blindings: [o0.blinding.toString(), o1.blinding.toString()]
4596
4311
  };
4597
- const provider = options.provider ?? new WebArtifactProvider2();
4312
+ const provider = options.provider ?? new WebArtifactProvider3();
4598
4313
  const opts = { provider };
4599
4314
  if (options.verbose !== void 0) opts.verbose = options.verbose;
4600
- return generateProof2(CircuitType2.Transfer, circuitInputs, opts);
4315
+ return generateProof2(CircuitType3.Transfer, circuitInputs, opts);
4601
4316
  }
4602
4317
 
4603
4318
  // src/proof-generator/fee-claim.ts
4604
4319
  import {
4605
- CircuitType as CircuitType3,
4320
+ CircuitType as CircuitType4,
4606
4321
  generateProof as generateProof3,
4607
- WebArtifactProvider as WebArtifactProvider3
4322
+ WebArtifactProvider as WebArtifactProvider4
4608
4323
  } from "@orbinum/proof-generator";
4609
4324
  async function generateFeeClaimProof(inputs, options = {}) {
4610
4325
  if (inputs.amount <= 0n) {
@@ -4617,10 +4332,10 @@ async function generateFeeClaimProof(inputs, options = {}) {
4617
4332
  owner_pubkey: inputs.ownerPubkey.toString(),
4618
4333
  blinding: inputs.blinding.toString()
4619
4334
  };
4620
- const provider = options.provider ?? new WebArtifactProvider3();
4335
+ const provider = options.provider ?? new WebArtifactProvider4();
4621
4336
  const opts = { provider };
4622
4337
  if (options.verbose !== void 0) opts.verbose = options.verbose;
4623
- const proofResult = await generateProof3(CircuitType3.ValueProof, circuitInputs, opts);
4338
+ const proofResult = await generateProof3(CircuitType4.ValueProof, circuitInputs, opts);
4624
4339
  const [sigCommitment, sigValue, sigAssetId, sigOwnerHash] = proofResult.publicSignals.map(BigInt);
4625
4340
  const buf = new Uint8Array(76);
4626
4341
  buf.set(bigintTo32Le(sigCommitment), 0);
@@ -4676,9 +4391,19 @@ function decodePrecompileCalldata(address, input) {
4676
4391
  const recipient = toHex(data.slice(160, 192));
4677
4392
  const fee = decodeUint(data, 192);
4678
4393
  const changeCommitment = toHex(data.slice(224, 256));
4394
+ const circuitVersion = decodeUint(data, 288);
4679
4395
  return {
4680
4396
  fnSig,
4681
- args: { root, nullifier, assetId, amount, recipient, fee, changeCommitment }
4397
+ args: {
4398
+ root,
4399
+ nullifier,
4400
+ assetId,
4401
+ amount,
4402
+ recipient,
4403
+ fee,
4404
+ changeCommitment,
4405
+ circuitVersion
4406
+ }
4682
4407
  };
4683
4408
  } catch {
4684
4409
  return { fnSig, args: {} };
@@ -4694,7 +4419,8 @@ function decodePrecompileCalldata(address, input) {
4694
4419
  const commitments = Number(decodeUint(data, commOffset));
4695
4420
  const assetId = decodeUint(data, 160);
4696
4421
  const fee = decodeUint(data, 192);
4697
- return { fnSig, args: { root, nullifiers, commitments, assetId, fee } };
4422
+ const circuitVersion = decodeUint(data, 224);
4423
+ return { fnSig, args: { root, nullifiers, commitments, assetId, fee, circuitVersion } };
4698
4424
  } catch {
4699
4425
  return { fnSig, args: {} };
4700
4426
  }
@@ -4705,7 +4431,8 @@ function decodePrecompileCalldata(address, input) {
4705
4431
  const commitment = toHex(data.slice(0, 32));
4706
4432
  const amount = decodeUint(data, 32);
4707
4433
  const assetId = decodeUint(data, 64);
4708
- return { fnSig, args: { commitment, amount, assetId } };
4434
+ const circuitVersion = decodeUint(data, 192);
4435
+ return { fnSig, args: { commitment, amount, assetId, circuitVersion } };
4709
4436
  } catch {
4710
4437
  return { fnSig, args: {} };
4711
4438
  }
@@ -4717,8 +4444,8 @@ function decodePrecompileCalldata(address, input) {
4717
4444
  var CircuitId = {
4718
4445
  Transfer: 1,
4719
4446
  Unshield: 2,
4720
- ValueProof: 4,
4721
- PrivateLink: 5
4447
+ PrivateLink: 5,
4448
+ ValueProof: 6
4722
4449
  };
4723
4450
 
4724
4451
  // src/utils/string.ts
@@ -5400,14 +5127,15 @@ export {
5400
5127
  BABYJUB_SUBORDER,
5401
5128
  BN254_R,
5402
5129
  Blake2256,
5130
+ CURRENT_CIRCUIT_VERSION,
5403
5131
  CircuitId,
5404
- CircuitType,
5132
+ CircuitType2 as CircuitType,
5133
+ CircuitVersionResolver,
5405
5134
  CryptoPrecompiles,
5406
5135
  ENCRYPTED_MEMO_SIZE,
5407
5136
  EncryptedMemo,
5408
5137
  EvmClient,
5409
5138
  EvmExplorer,
5410
- IndexerClient,
5411
5139
  KNOWN_PRECOMPILES,
5412
5140
  Keccak256,
5413
5141
  NoteBuilder,
@@ -5424,7 +5152,7 @@ export {
5424
5152
  Storage,
5425
5153
  SubstrateClient,
5426
5154
  VaultLockedError,
5427
- WebArtifactProvider,
5155
+ WebArtifactProvider2 as WebArtifactProvider,
5428
5156
  ZkVerifierModule,
5429
5157
  accountIdHexToSs58,
5430
5158
  addressToAccountIdHex,