@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.d.mts +174 -579
- package/dist/index.d.ts +174 -579
- package/dist/index.js +186 -460
- package/dist/index.mjs +190 -462
- package/package.json +2 -2
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 =
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
-
* @
|
|
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(
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
2750
|
-
PRIVATE_TRANSFER: new Uint8Array([
|
|
2751
|
-
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32)
|
|
2752
|
-
UNSHIELD: new Uint8Array([
|
|
2753
|
-
// claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)
|
|
2754
|
-
CLAIM_SHIELDED_FEES: new Uint8Array([
|
|
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
|
-
"
|
|
2799
|
-
|
|
2800
|
-
"
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
4284
|
+
CircuitType as CircuitType3,
|
|
4570
4285
|
generateProof as generateProof2,
|
|
4571
|
-
WebArtifactProvider as
|
|
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
|
|
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(
|
|
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
|
|
4320
|
+
CircuitType as CircuitType4,
|
|
4606
4321
|
generateProof as generateProof3,
|
|
4607
|
-
WebArtifactProvider as
|
|
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
|
|
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(
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4721
|
-
|
|
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,
|