@orbinum/sdk 0.4.1 → 0.5.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/README.md +217 -443
- package/dist/index.d.mts +2005 -845
- package/dist/index.d.ts +2005 -845
- package/dist/index.js +1729 -426
- package/dist/index.mjs +1713 -425
- package/package.json +22 -20
package/dist/index.mjs
CHANGED
|
@@ -41,6 +41,7 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
41
41
|
constructor(_papi) {
|
|
42
42
|
this._papi = _papi;
|
|
43
43
|
}
|
|
44
|
+
_papi;
|
|
44
45
|
_dynamicBuilder = null;
|
|
45
46
|
_extDecoder = null;
|
|
46
47
|
/**
|
|
@@ -240,6 +241,14 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
240
241
|
submitAndWatch(signedHex) {
|
|
241
242
|
return this._papi.submitAndWatch(signedHex);
|
|
242
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Submits a bare (unsigned) extrinsic hex and waits for finalization.
|
|
246
|
+
* Used for gasless private_transfer and unshield transactions.
|
|
247
|
+
* The bare tx hex is produced by `tx.getBareTx()` from polkadot-api.
|
|
248
|
+
*/
|
|
249
|
+
async submitUnsignedAndWatch(bareTxHex) {
|
|
250
|
+
return this._papi.submit(bareTxHex);
|
|
251
|
+
}
|
|
243
252
|
/**
|
|
244
253
|
* Convenience: wrap raw call bytes and sign+submit in one step.
|
|
245
254
|
*/
|
|
@@ -291,19 +300,25 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
291
300
|
}
|
|
292
301
|
static _buildDataProxy(value) {
|
|
293
302
|
const formatValue = (v) => {
|
|
294
|
-
if (v instanceof Uint8Array) return
|
|
303
|
+
if (v instanceof Uint8Array) return toHex(v);
|
|
295
304
|
if (typeof v === "bigint") return v.toString();
|
|
296
305
|
return String(v);
|
|
297
306
|
};
|
|
298
307
|
const jsonifyValue = (v) => {
|
|
299
308
|
if (v === null || v === void 0) return v;
|
|
300
309
|
if (typeof v === "bigint") return v.toString();
|
|
301
|
-
if (v instanceof Uint8Array)
|
|
302
|
-
return Array.from(v).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
310
|
+
if (v instanceof Uint8Array) return toHex(v);
|
|
303
311
|
if (Array.isArray(v)) return v.map(jsonifyValue);
|
|
304
312
|
if (typeof v === "object") {
|
|
313
|
+
const obj = v;
|
|
314
|
+
if (typeof obj["asHex"] === "function") {
|
|
315
|
+
try {
|
|
316
|
+
return obj["asHex"]();
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
305
320
|
return Object.fromEntries(
|
|
306
|
-
Object.entries(
|
|
321
|
+
Object.entries(obj).filter(([, val]) => typeof val !== "function").map(([k, val]) => [k, jsonifyValue(val)])
|
|
307
322
|
);
|
|
308
323
|
}
|
|
309
324
|
return v;
|
|
@@ -396,11 +411,14 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
396
411
|
|
|
397
412
|
// src/evm/EvmClient.ts
|
|
398
413
|
var EvmClient = class {
|
|
414
|
+
/** @param rpcUrl - HTTP URL of the EVM JSON-RPC endpoint (e.g. `"http://localhost:9933"`). */
|
|
399
415
|
constructor(rpcUrl) {
|
|
400
416
|
this.rpcUrl = rpcUrl;
|
|
401
417
|
}
|
|
418
|
+
rpcUrl;
|
|
402
419
|
/**
|
|
403
|
-
* Performs a single JSON-RPC call.
|
|
420
|
+
* Performs a single JSON-RPC call and returns the typed result.
|
|
421
|
+
* Throws on HTTP errors, RPC-level errors, or a `null` result.
|
|
404
422
|
*/
|
|
405
423
|
async request(method, params = []) {
|
|
406
424
|
const res = await fetch(this.rpcUrl, {
|
|
@@ -420,6 +438,7 @@ var EvmClient = class {
|
|
|
420
438
|
}
|
|
421
439
|
/**
|
|
422
440
|
* Performs multiple JSON-RPC calls in a single HTTP request (batch).
|
|
441
|
+
* Results are returned in the same order as `calls`, as a typed tuple.
|
|
423
442
|
*/
|
|
424
443
|
async batchRequest(calls) {
|
|
425
444
|
const body = calls.map((c, i) => ({
|
|
@@ -464,30 +483,22 @@ var EvmClient = class {
|
|
|
464
483
|
const hex = await this.request("eth_gasPrice", []);
|
|
465
484
|
return hexToBigint(hex);
|
|
466
485
|
}
|
|
467
|
-
/**
|
|
468
|
-
* Submits a signed raw transaction. Returns the transaction hash.
|
|
469
|
-
*/
|
|
486
|
+
/** Submits a signed raw transaction. Returns the transaction hash. */
|
|
470
487
|
async sendRawTransaction(signedHex) {
|
|
471
488
|
return this.request("eth_sendRawTransaction", [signedHex]);
|
|
472
489
|
}
|
|
473
|
-
/**
|
|
474
|
-
* Executes a read-only call without creating a transaction.
|
|
475
|
-
*/
|
|
490
|
+
/** Executes a read-only call without creating a transaction. Returns the raw ABI-encoded response. */
|
|
476
491
|
async call(to, data, from) {
|
|
477
492
|
const txObj = { to, data };
|
|
478
493
|
if (from) txObj["from"] = from;
|
|
479
494
|
return this.request("eth_call", [txObj, "latest"]);
|
|
480
495
|
}
|
|
481
|
-
/**
|
|
482
|
-
* Estimates the gas for a transaction.
|
|
483
|
-
*/
|
|
496
|
+
/** Estimates the gas required for a transaction. Returns the estimate in wei as a `bigint`. */
|
|
484
497
|
async estimateGas(params) {
|
|
485
498
|
const hex = await this.request("eth_estimateGas", [params]);
|
|
486
499
|
return hexToBigint(hex);
|
|
487
500
|
}
|
|
488
|
-
/**
|
|
489
|
-
* Returns a transaction receipt by hash, or null if not yet mined.
|
|
490
|
-
*/
|
|
501
|
+
/** Returns a transaction receipt by hash, or `null` if the transaction has not been mined yet. */
|
|
491
502
|
async getTransactionReceipt(txHash) {
|
|
492
503
|
const res = await fetch(this.rpcUrl, {
|
|
493
504
|
method: "POST",
|
|
@@ -506,6 +517,56 @@ var EvmClient = class {
|
|
|
506
517
|
}
|
|
507
518
|
return json.result ?? null;
|
|
508
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Polls `eth_getTransactionReceipt` until the transaction is included in a block.
|
|
522
|
+
*
|
|
523
|
+
* @param txHash - The transaction hash to wait for.
|
|
524
|
+
* @param intervalMs - Polling interval in milliseconds (default: 500).
|
|
525
|
+
* @param timeoutMs - Maximum time to wait in milliseconds (default: 60_000).
|
|
526
|
+
* @returns The transaction receipt once mined.
|
|
527
|
+
* @throws If the transaction is not mined within `timeoutMs` or if it reverted (`status == 0x0`).
|
|
528
|
+
*/
|
|
529
|
+
async waitForReceipt(txHash, intervalMs = 500, timeoutMs = 6e4) {
|
|
530
|
+
const deadline = Date.now() + timeoutMs;
|
|
531
|
+
while (Date.now() < deadline) {
|
|
532
|
+
const receipt = await this.getTransactionReceipt(txHash);
|
|
533
|
+
if (receipt !== null) {
|
|
534
|
+
if (receipt["status"] === "0x0") {
|
|
535
|
+
let revertDetail = "";
|
|
536
|
+
const nodeReason = receipt["revertReason"];
|
|
537
|
+
if (nodeReason) revertDetail = ` | revertReason: ${nodeReason}`;
|
|
538
|
+
if (!revertDetail) {
|
|
539
|
+
try {
|
|
540
|
+
const blockParam = receipt["blockNumber"] ?? "latest";
|
|
541
|
+
const rawTx = await this.request(
|
|
542
|
+
"eth_getTransactionByHash",
|
|
543
|
+
[txHash]
|
|
544
|
+
).catch(() => null);
|
|
545
|
+
if (rawTx) {
|
|
546
|
+
const calldata = rawTx["input"] ?? rawTx["data"];
|
|
547
|
+
if (calldata) {
|
|
548
|
+
const revertData = await this.request("eth_call", [
|
|
549
|
+
{ from: rawTx["from"], to: rawTx["to"], data: calldata },
|
|
550
|
+
blockParam
|
|
551
|
+
]).catch(
|
|
552
|
+
(err) => err instanceof Error ? err.message : String(err)
|
|
553
|
+
);
|
|
554
|
+
revertDetail = ` | eth_call: ${revertData}`;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
throw new Error(
|
|
561
|
+
`Transaction reverted on-chain: ${txHash}${revertDetail} | receipt: ${JSON.stringify(receipt)}`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return receipt;
|
|
565
|
+
}
|
|
566
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
567
|
+
}
|
|
568
|
+
throw new Error(`Transaction not mined within ${timeoutMs}ms: ${txHash}`);
|
|
569
|
+
}
|
|
509
570
|
};
|
|
510
571
|
|
|
511
572
|
// src/utils/format.ts
|
|
@@ -578,10 +639,13 @@ function formatORB(raw, precision = 6) {
|
|
|
578
639
|
|
|
579
640
|
// src/evm-explorer/EvmExplorer.ts
|
|
580
641
|
var EvmExplorer = class _EvmExplorer {
|
|
642
|
+
/** @param evm - Underlying `EvmClient` used for all RPC calls. */
|
|
581
643
|
constructor(evm) {
|
|
582
644
|
this.evm = evm;
|
|
583
645
|
}
|
|
646
|
+
evm;
|
|
584
647
|
// --- Blocks ---
|
|
648
|
+
/** Returns the `count` most recent blocks in descending order (latest first). */
|
|
585
649
|
async getLatestBlocks(count = 10) {
|
|
586
650
|
const latest = await this.evm.getBlockNumber();
|
|
587
651
|
const nums = Array.from({ length: Math.min(count, latest + 1) }, (_, i) => latest - i);
|
|
@@ -595,10 +659,12 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
595
659
|
);
|
|
596
660
|
return results.filter((b) => b !== null && !!b.hash).map((b) => this.parseBlock(b));
|
|
597
661
|
}
|
|
662
|
+
/** Returns a single block by number or hash, or `null` if not found. */
|
|
598
663
|
async getBlock(hashOrNumber) {
|
|
599
664
|
const b = await this.fetchBlock(hashOrNumber, false);
|
|
600
665
|
return b ? this.parseBlock(b) : null;
|
|
601
666
|
}
|
|
667
|
+
/** Returns all transactions in a block (with receipts), or `[]` if the block is not found. */
|
|
602
668
|
async getBlockTransactions(hashOrNumber) {
|
|
603
669
|
try {
|
|
604
670
|
const b = await this.fetchBlock(hashOrNumber, true);
|
|
@@ -611,6 +677,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
611
677
|
}
|
|
612
678
|
}
|
|
613
679
|
// --- Transactions ---
|
|
680
|
+
/** Returns a single transaction with its receipt, or `null` if not found. */
|
|
614
681
|
async getTransaction(hash) {
|
|
615
682
|
try {
|
|
616
683
|
const [tx, receipt] = await Promise.all([
|
|
@@ -623,6 +690,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
623
690
|
return null;
|
|
624
691
|
}
|
|
625
692
|
}
|
|
693
|
+
/**
|
|
694
|
+
* Returns lightweight summaries of all transactions sent from or to `address`
|
|
695
|
+
* within the last `maxBlocks` blocks, sorted by block number descending.
|
|
696
|
+
*/
|
|
626
697
|
async getTransactionsByAddress(address, maxBlocks = 300) {
|
|
627
698
|
const addr = address.toLowerCase();
|
|
628
699
|
const latest = await this.evm.getBlockNumber();
|
|
@@ -674,6 +745,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
674
745
|
return results;
|
|
675
746
|
}
|
|
676
747
|
// --- Address ---
|
|
748
|
+
/**
|
|
749
|
+
* Returns aggregated on-chain data for an EVM address: balance, nonce,
|
|
750
|
+
* bytecode (truncated), and up to 50 recent logs from the last 5 000 blocks.
|
|
751
|
+
*/
|
|
677
752
|
async getAddressInfo(address) {
|
|
678
753
|
const latest = await this.evm.getBlockNumber().catch(() => 0);
|
|
679
754
|
const fromBlock = `0x${Math.max(0, latest - 5e3).toString(16)}`;
|
|
@@ -704,6 +779,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
704
779
|
recentLogs
|
|
705
780
|
};
|
|
706
781
|
}
|
|
782
|
+
/** Returns the native token balance of `address`, formatted as a decimal string (no symbol). */
|
|
707
783
|
async getBalance(address) {
|
|
708
784
|
try {
|
|
709
785
|
const val = await this.evm.getBalance(address);
|
|
@@ -712,6 +788,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
712
788
|
return "0";
|
|
713
789
|
}
|
|
714
790
|
}
|
|
791
|
+
/** Returns the current transaction count (nonce) for `address`, or `0` on error. */
|
|
715
792
|
async getNonce(address) {
|
|
716
793
|
try {
|
|
717
794
|
return await this.evm.getTransactionCount(address);
|
|
@@ -719,6 +796,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
719
796
|
return 0;
|
|
720
797
|
}
|
|
721
798
|
}
|
|
799
|
+
/** Returns `true` when `address` has non-empty deployed bytecode. */
|
|
722
800
|
async getIsContract(address) {
|
|
723
801
|
try {
|
|
724
802
|
const code = await this.evm.request("eth_getCode", [address, "latest"]);
|
|
@@ -728,6 +806,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
728
806
|
}
|
|
729
807
|
}
|
|
730
808
|
// --- Tokens ---
|
|
809
|
+
/**
|
|
810
|
+
* Fetches ERC-20 metadata for a token contract via ABI calls.
|
|
811
|
+
* Returns `null` when the address does not look like an ERC-20 token.
|
|
812
|
+
*/
|
|
731
813
|
async getTokenInfo(address) {
|
|
732
814
|
const addr = address.toLowerCase();
|
|
733
815
|
const [name, symbol, decimals, totalSupply] = await this.evm.batchRequest([
|
|
@@ -747,6 +829,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
747
829
|
isErc20
|
|
748
830
|
};
|
|
749
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Returns ERC-20 `Transfer` events for `address` from the last 5 000 blocks.
|
|
834
|
+
* When `holderAddress` is provided, restricts results to transfers sent or received by that address.
|
|
835
|
+
*/
|
|
750
836
|
async getTokenTransfers(address, holderAddress) {
|
|
751
837
|
const TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
752
838
|
const latest = await this.evm.getBlockNumber().catch(() => 0);
|
|
@@ -774,12 +860,14 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
774
860
|
logIndex: hexToNumber(l.logIndex)
|
|
775
861
|
}));
|
|
776
862
|
}
|
|
863
|
+
/** Returns the raw ERC-20 balance of `holderAddress` for the token at `tokenAddress` (0x-prefixed hex). */
|
|
777
864
|
async getTokenBalance(tokenAddress, holderAddress) {
|
|
778
865
|
const padded = holderAddress.replace(/^0x/, "").toLowerCase().padStart(64, "0");
|
|
779
866
|
const result = await this.ethCall(tokenAddress, `0x70a08231${padded}`);
|
|
780
867
|
return result ?? "0x0";
|
|
781
868
|
}
|
|
782
869
|
// --- Private: parsers ---
|
|
870
|
+
/** Maps a raw RPC block object to the public `EvmBlock` shape. */
|
|
783
871
|
parseBlock(b) {
|
|
784
872
|
return {
|
|
785
873
|
hash: b.hash,
|
|
@@ -792,6 +880,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
792
880
|
parentHash: b.parentHash
|
|
793
881
|
};
|
|
794
882
|
}
|
|
883
|
+
/** Maps a raw RPC transaction + optional receipt to the public `EvmTransaction` shape. */
|
|
795
884
|
parseTx(tx, receipt) {
|
|
796
885
|
const parsed = {
|
|
797
886
|
hash: tx.hash,
|
|
@@ -811,6 +900,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
811
900
|
return parsed;
|
|
812
901
|
}
|
|
813
902
|
// --- Private: fetch helpers ---
|
|
903
|
+
/**
|
|
904
|
+
* Fetches a raw block by number or hash. Accepts a plain integer, a decimal string,
|
|
905
|
+
* or a 0x-prefixed hash. Returns `null` on any RPC error.
|
|
906
|
+
*/
|
|
814
907
|
async fetchBlock(hashOrNumber, withTxObjects) {
|
|
815
908
|
try {
|
|
816
909
|
if (typeof hashOrNumber === "number" || /^\d+$/.test(String(hashOrNumber))) {
|
|
@@ -828,6 +921,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
828
921
|
return null;
|
|
829
922
|
}
|
|
830
923
|
}
|
|
924
|
+
/** Executes a read-only `eth_call` and returns the raw hex result, or `null` on error. */
|
|
831
925
|
async ethCall(to, data) {
|
|
832
926
|
try {
|
|
833
927
|
return await this.evm.call(to, data);
|
|
@@ -836,6 +930,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
836
930
|
}
|
|
837
931
|
}
|
|
838
932
|
// --- Private static: ABI decoders ---
|
|
933
|
+
/** Decodes an ABI-encoded `string` return value from a raw 0x-prefixed hex string. */
|
|
839
934
|
static decodeAbiString(hex) {
|
|
840
935
|
if (!hex || hex === "0x") return "";
|
|
841
936
|
const data = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
@@ -850,11 +945,13 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
850
945
|
return "";
|
|
851
946
|
}
|
|
852
947
|
}
|
|
948
|
+
/** Decodes an ABI-encoded `uint256` return value to a `bigint`. */
|
|
853
949
|
static decodeAbiUint(hex) {
|
|
854
950
|
if (!hex || hex === "0x") return 0n;
|
|
855
951
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
856
952
|
return BigInt(`0x${clean || "0"}`);
|
|
857
953
|
}
|
|
954
|
+
/** Converts a 0x-prefixed hex number to its decimal string representation. Returns `'0'` on parse error. */
|
|
858
955
|
static hexToDecimalStr(hex) {
|
|
859
956
|
try {
|
|
860
957
|
return BigInt(hex).toString();
|
|
@@ -889,6 +986,24 @@ var IndexerClient = class {
|
|
|
889
986
|
}
|
|
890
987
|
return res.json();
|
|
891
988
|
}
|
|
989
|
+
async post(path, body) {
|
|
990
|
+
const controller = new AbortController();
|
|
991
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
992
|
+
try {
|
|
993
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
994
|
+
method: "POST",
|
|
995
|
+
headers: { "Content-Type": "application/json" },
|
|
996
|
+
body: JSON.stringify(body),
|
|
997
|
+
signal: controller.signal
|
|
998
|
+
});
|
|
999
|
+
if (!res.ok) {
|
|
1000
|
+
throw new Error(`IndexerClient: HTTP ${res.status} for POST ${path}`);
|
|
1001
|
+
}
|
|
1002
|
+
return res.json();
|
|
1003
|
+
} finally {
|
|
1004
|
+
clearTimeout(timer);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
892
1007
|
async getOrNull(path) {
|
|
893
1008
|
const res = await this._fetchResponse(path);
|
|
894
1009
|
if (res.status === 404) return null;
|
|
@@ -924,6 +1039,21 @@ var IndexerClient = class {
|
|
|
924
1039
|
`/shielded/commitments/${encodeURIComponent(hex)}`
|
|
925
1040
|
);
|
|
926
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Returns a paginated list of stealth scan hints ordered ascending by leafIndex.
|
|
1044
|
+
* Each hint contains only the fields required for ECDH triage and decryption:
|
|
1045
|
+
* leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo.
|
|
1046
|
+
*
|
|
1047
|
+
* Use `sinceLeafIndex` for incremental scans (cursor = last seen leafIndex + 1).
|
|
1048
|
+
*/
|
|
1049
|
+
async getScanHints(params) {
|
|
1050
|
+
const qs = this.buildQuery({
|
|
1051
|
+
page: params?.page,
|
|
1052
|
+
limit: params?.limit,
|
|
1053
|
+
since_leaf_index: params?.sinceLeafIndex
|
|
1054
|
+
});
|
|
1055
|
+
return this.get(`/shielded/scan-hints${qs}`);
|
|
1056
|
+
}
|
|
927
1057
|
// ─── Nullifiers ────────────────────────────────────────────────────────────
|
|
928
1058
|
/** Returns a paginated list of spent nullifiers. */
|
|
929
1059
|
async getNullifiers(params) {
|
|
@@ -936,11 +1066,50 @@ var IndexerClient = class {
|
|
|
936
1066
|
`/shielded/nullifier/${encodeURIComponent(hex)}/status`
|
|
937
1067
|
);
|
|
938
1068
|
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Batch-checks which of the given nullifiers are spent.
|
|
1071
|
+
* Returns only the nullifiers that exist in the spent set.
|
|
1072
|
+
* Accepts up to 100 nullifiers (0x-prefixed hex).
|
|
1073
|
+
*/
|
|
1074
|
+
async getNullifiersBatch(nullifiers) {
|
|
1075
|
+
if (nullifiers.length === 0) return [];
|
|
1076
|
+
const res = await this.post("/shielded/nullifiers/batch", {
|
|
1077
|
+
nullifiers: nullifiers.map((n) => n.toLowerCase())
|
|
1078
|
+
});
|
|
1079
|
+
return res.data;
|
|
1080
|
+
}
|
|
939
1081
|
// ─── Private transfers ─────────────────────────────────────────────────────
|
|
940
|
-
/**
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1082
|
+
/**
|
|
1083
|
+
* Returns temporal metadata for private transfers that spent any of the given nullifiers.
|
|
1084
|
+
* Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
|
|
1085
|
+
* between inputs and outputs to prevent graph reconstruction.
|
|
1086
|
+
* Accepts up to 50 nullifiers (0x-prefixed hex).
|
|
1087
|
+
*/
|
|
1088
|
+
async getTransfersByNullifiers(nullifiers) {
|
|
1089
|
+
if (nullifiers.length === 0) return [];
|
|
1090
|
+
const qs = this.buildQuery({
|
|
1091
|
+
nullifiers: nullifiers.map((n) => n.toLowerCase()).join(",")
|
|
1092
|
+
});
|
|
1093
|
+
const res = await this.get(
|
|
1094
|
+
`/shielded/transfers/by-nullifiers${qs}`
|
|
1095
|
+
);
|
|
1096
|
+
return res.data;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Returns temporal metadata for private transfers that produced any of the given commitments.
|
|
1100
|
+
* Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
|
|
1101
|
+
* between outputs and inputs to prevent graph reconstruction.
|
|
1102
|
+
* Accepts up to 50 commitments (0x-prefixed hex).
|
|
1103
|
+
*/
|
|
1104
|
+
async getTransfersByCommitments(commitments) {
|
|
1105
|
+
if (commitments.length === 0) return [];
|
|
1106
|
+
const qs = this.buildQuery({
|
|
1107
|
+
commitments: commitments.map((c) => c.toLowerCase()).join(",")
|
|
1108
|
+
});
|
|
1109
|
+
const res = await this.get(
|
|
1110
|
+
`/shielded/transfers/by-commitments${qs}`
|
|
1111
|
+
);
|
|
1112
|
+
return res.data;
|
|
944
1113
|
}
|
|
945
1114
|
// ─── Unshields ─────────────────────────────────────────────────────────────
|
|
946
1115
|
/** Returns a paginated list of unshield events. */
|
|
@@ -1000,6 +1169,16 @@ var IndexerClient = class {
|
|
|
1000
1169
|
`/address/${encodeURIComponent(address.toLowerCase())}/shielded${qs}`
|
|
1001
1170
|
);
|
|
1002
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Returns a paginated list of unshield events where the given address is the recipient.
|
|
1174
|
+
* Accepts a 0x-prefixed EVM address or an SS58 Substrate address.
|
|
1175
|
+
*/
|
|
1176
|
+
async getAddressUnshields(address, params) {
|
|
1177
|
+
const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
|
|
1178
|
+
return this.get(
|
|
1179
|
+
`/address/${encodeURIComponent(address)}/unshields${qs}`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1003
1182
|
/**
|
|
1004
1183
|
* Returns a paginated list of all shielded activity (commitments, unshields,
|
|
1005
1184
|
* private transfers) associated with the given address.
|
|
@@ -1035,10 +1214,24 @@ var IndexerClient = class {
|
|
|
1035
1214
|
}
|
|
1036
1215
|
};
|
|
1037
1216
|
|
|
1038
|
-
// src/shielded-pool/ShieldedPoolModule.ts
|
|
1217
|
+
// src/shielded-pool/pallet/ShieldedPoolModule.ts
|
|
1039
1218
|
import { Binary as Binary2 } from "polkadot-api";
|
|
1040
1219
|
|
|
1041
1220
|
// src/utils/tx.ts
|
|
1221
|
+
function formatDispatchError(err) {
|
|
1222
|
+
if (err.type === "Module") {
|
|
1223
|
+
const inner = err.value;
|
|
1224
|
+
if (inner?.type) {
|
|
1225
|
+
return `Module(${inner.type})`;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
const detail = JSON.stringify(err.value);
|
|
1230
|
+
return detail && detail !== "null" ? `${err.type}(${detail})` : err.type;
|
|
1231
|
+
} catch {
|
|
1232
|
+
return err.type;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1042
1235
|
function toTxResult(payload) {
|
|
1043
1236
|
const base = {
|
|
1044
1237
|
txHash: payload.txHash,
|
|
@@ -1047,13 +1240,18 @@ function toTxResult(payload) {
|
|
|
1047
1240
|
ok: payload.ok
|
|
1048
1241
|
};
|
|
1049
1242
|
if (!payload.ok) {
|
|
1050
|
-
return { ...base, error: payload.dispatchError
|
|
1243
|
+
return { ...base, error: formatDispatchError(payload.dispatchError) };
|
|
1051
1244
|
}
|
|
1052
1245
|
return base;
|
|
1053
1246
|
}
|
|
1054
1247
|
function callUnsafeTx(txEntry, ...args) {
|
|
1055
1248
|
return txEntry(...args);
|
|
1056
1249
|
}
|
|
1250
|
+
async function submitBareTx(tx, client) {
|
|
1251
|
+
const bareTxHex = await tx.getBareTx();
|
|
1252
|
+
const payload = await client.submitUnsignedAndWatch(bareTxHex);
|
|
1253
|
+
return toTxResult(payload);
|
|
1254
|
+
}
|
|
1057
1255
|
function resolveTx(unsafe, pallet, call) {
|
|
1058
1256
|
const u = unsafe;
|
|
1059
1257
|
const p = u["tx"]?.[pallet];
|
|
@@ -1064,9 +1262,10 @@ function resolveTx(unsafe, pallet, call) {
|
|
|
1064
1262
|
return entry;
|
|
1065
1263
|
}
|
|
1066
1264
|
|
|
1067
|
-
// src/shielded-pool/EncryptedMemo.ts
|
|
1265
|
+
// src/shielded-pool/protocol/EncryptedMemo.ts
|
|
1068
1266
|
import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
1069
1267
|
import { randomBytes } from "@noble/ciphers/utils.js";
|
|
1268
|
+
import { mulPointEscalar, Base8, packPoint, unpackPoint } from "@zk-kit/baby-jubjub";
|
|
1070
1269
|
|
|
1071
1270
|
// src/utils/bytes.ts
|
|
1072
1271
|
function bigintTo32Le(n) {
|
|
@@ -1113,77 +1312,114 @@ function computePathIndices(leafIndex, depth) {
|
|
|
1113
1312
|
return indices;
|
|
1114
1313
|
}
|
|
1115
1314
|
function leHexToBigint(hex) {
|
|
1116
|
-
|
|
1117
|
-
const bytes = new Uint8Array(h.length / 2);
|
|
1118
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1119
|
-
bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
|
1120
|
-
}
|
|
1121
|
-
return bytesToBigintLE(bytes);
|
|
1315
|
+
return bytesToBigintLE(fromHex(hex));
|
|
1122
1316
|
}
|
|
1123
1317
|
|
|
1124
|
-
// src/
|
|
1318
|
+
// src/utils/crypto-constants.ts
|
|
1319
|
+
var BN254_R = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1320
|
+
var BABYJUB_SUBORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
1321
|
+
|
|
1322
|
+
// src/shielded-pool/protocol/memo.ts
|
|
1125
1323
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
1126
1324
|
var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
|
|
1127
|
-
var MEMO_PLAINTEXT_SIZE =
|
|
1128
|
-
function serializeMemo(value, ownerPk, blinding, assetId) {
|
|
1325
|
+
var MEMO_PLAINTEXT_SIZE = 116;
|
|
1326
|
+
function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
|
|
1129
1327
|
const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
|
|
1130
1328
|
const view = new DataView(buf.buffer);
|
|
1131
1329
|
view.setBigUint64(0, value & 0xffffffffffffffffn, true);
|
|
1132
|
-
|
|
1133
|
-
buf.set(
|
|
1134
|
-
|
|
1330
|
+
view.setBigUint64(8, value >> 64n & 0xffffffffffffffffn, true);
|
|
1331
|
+
buf.set(ownerPk.slice(0, 32), 16);
|
|
1332
|
+
buf.set(blinding.slice(0, 32), 48);
|
|
1333
|
+
view.setUint32(80, assetId >>> 0, true);
|
|
1334
|
+
buf.set(counterpartyPk.slice(0, 32), 84);
|
|
1135
1335
|
return buf;
|
|
1136
1336
|
}
|
|
1137
|
-
function deriveEncryptionKey(
|
|
1337
|
+
function deriveEncryptionKey(sharedSecret, commitment) {
|
|
1138
1338
|
const h = sha256.create();
|
|
1139
|
-
h.update(
|
|
1339
|
+
h.update(sharedSecret);
|
|
1140
1340
|
h.update(commitment);
|
|
1141
1341
|
h.update(KEY_DOMAIN);
|
|
1142
1342
|
return h.digest();
|
|
1143
1343
|
}
|
|
1144
|
-
function toBase64(buf) {
|
|
1145
|
-
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
1146
|
-
let str = "";
|
|
1147
|
-
for (const b of bytes) str += String.fromCharCode(b);
|
|
1148
|
-
return btoa(str);
|
|
1149
|
-
}
|
|
1150
|
-
function fromBase64(b64) {
|
|
1151
|
-
const bin = atob(b64);
|
|
1152
|
-
const out = new Uint8Array(bin.length);
|
|
1153
|
-
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
1154
|
-
return out;
|
|
1155
|
-
}
|
|
1156
1344
|
|
|
1157
|
-
// src/shielded-pool/EncryptedMemo.ts
|
|
1345
|
+
// src/shielded-pool/protocol/EncryptedMemo.ts
|
|
1158
1346
|
var NONCE_SIZE = 12;
|
|
1159
|
-
var
|
|
1347
|
+
var CIPHERTEXT_SIZE = 132;
|
|
1348
|
+
var EPH_PK_SIZE = 32;
|
|
1349
|
+
var ENCRYPTED_MEMO_SIZE = NONCE_SIZE + CIPHERTEXT_SIZE + EPH_PK_SIZE;
|
|
1350
|
+
function bytesToBjjScalar(bytes) {
|
|
1351
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1352
|
+
return BigInt("0x" + hex) % BABYJUB_SUBORDER || 1n;
|
|
1353
|
+
}
|
|
1354
|
+
function parsePlaintext(nonce, ciphertextWithMac, encKey) {
|
|
1355
|
+
try {
|
|
1356
|
+
const cipher = chacha20poly1305(encKey, nonce);
|
|
1357
|
+
const plaintext = cipher.decrypt(ciphertextWithMac);
|
|
1358
|
+
const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);
|
|
1359
|
+
const valueLo = view.getBigUint64(0, true);
|
|
1360
|
+
const valueHi = view.getBigUint64(8, true);
|
|
1361
|
+
const value = valueLo | valueHi << 64n;
|
|
1362
|
+
const ownerPk = bytesToBigintLE(plaintext.slice(16, 48));
|
|
1363
|
+
const blinding = bytesToBigintLE(plaintext.slice(48, 80));
|
|
1364
|
+
const assetId = BigInt(view.getUint32(80, true));
|
|
1365
|
+
const counterpartyPk = bytesToBigintLE(plaintext.slice(84, 116));
|
|
1366
|
+
return { value, ownerPk, blinding, assetId, counterpartyPk };
|
|
1367
|
+
} catch {
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1160
1371
|
var EncryptedMemo = {
|
|
1161
1372
|
/**
|
|
1162
|
-
* Build and encrypt a memo for a note.
|
|
1373
|
+
* Build and encrypt a memo for a note using ECDH (v2, 168 bytes).
|
|
1163
1374
|
*
|
|
1164
|
-
* @param value
|
|
1165
|
-
* @param ownerPk
|
|
1166
|
-
* @param blinding
|
|
1167
|
-
* @param assetId
|
|
1168
|
-
* @param commitment
|
|
1169
|
-
* @param
|
|
1170
|
-
*
|
|
1171
|
-
*
|
|
1172
|
-
|
|
1173
|
-
|
|
1375
|
+
* @param value Note value in planck.
|
|
1376
|
+
* @param ownerPk 32-byte owner public key (LE).
|
|
1377
|
+
* @param blinding 32-byte blinding scalar (LE).
|
|
1378
|
+
* @param assetId Asset identifier.
|
|
1379
|
+
* @param commitment 32-byte commitment bytes (LE).
|
|
1380
|
+
* @param recipientIvkPacked 32-byte LE-encoded packed BJJ viewing public key
|
|
1381
|
+
* (from PrivacyKeyManager.getViewingPublicKeyPacked() or
|
|
1382
|
+
* decoded from a privacy address).
|
|
1383
|
+
* Pass `new Uint8Array(32)` (all zeros) for a publicly-readable memo.
|
|
1384
|
+
* @param counterpartyPk 32-byte counterparty BJJ Ax. Default: all zeros.
|
|
1385
|
+
* @returns 168-byte encrypted memo: nonce(12) || ciphertext+MAC(124) || ephPk(32).
|
|
1386
|
+
*/
|
|
1387
|
+
encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), ephSkOverride) {
|
|
1174
1388
|
const nonce = randomBytes(NONCE_SIZE);
|
|
1175
|
-
const
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1389
|
+
const plaintext = serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk);
|
|
1390
|
+
const isZeroKey = recipientIvkPacked.every((b) => b === 0);
|
|
1391
|
+
let sharedSecret;
|
|
1392
|
+
let ephPkPackedBytes;
|
|
1393
|
+
if (isZeroKey) {
|
|
1394
|
+
sharedSecret = new Uint8Array(32);
|
|
1395
|
+
ephPkPackedBytes = new Uint8Array(EPH_PK_SIZE);
|
|
1396
|
+
} else {
|
|
1397
|
+
const ephSkBytes = ephSkOverride ?? randomBytes(32);
|
|
1398
|
+
if (ephSkBytes.length !== 32)
|
|
1399
|
+
throw new Error("EncryptedMemo.encrypt: ephSkOverride must be 32 bytes");
|
|
1400
|
+
const ephSkScalar = bytesToBjjScalar(ephSkBytes);
|
|
1401
|
+
const ephPkPoint = mulPointEscalar(Base8, ephSkScalar);
|
|
1402
|
+
ephPkPackedBytes = bigintTo32Le(packPoint(ephPkPoint));
|
|
1403
|
+
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
1404
|
+
const ivkPoint = unpackPoint(ivkPackedBigint);
|
|
1405
|
+
if (!ivkPoint)
|
|
1406
|
+
throw new Error("EncryptedMemo.encrypt: invalid recipient viewing public key");
|
|
1407
|
+
const sharedPoint = mulPointEscalar(ivkPoint, ephSkScalar);
|
|
1408
|
+
sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1409
|
+
}
|
|
1410
|
+
const encKey = deriveEncryptionKey(sharedSecret, commitment);
|
|
1411
|
+
const cipher = chacha20poly1305(encKey, nonce);
|
|
1178
1412
|
const ciphertext = cipher.encrypt(plaintext);
|
|
1179
|
-
const result = new Uint8Array(
|
|
1413
|
+
const result = new Uint8Array(ENCRYPTED_MEMO_SIZE);
|
|
1180
1414
|
result.set(nonce, 0);
|
|
1181
1415
|
result.set(ciphertext, NONCE_SIZE);
|
|
1416
|
+
result.set(ephPkPackedBytes, NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1182
1417
|
return result;
|
|
1183
1418
|
},
|
|
1184
1419
|
/**
|
|
1185
|
-
* Returns a
|
|
1186
|
-
*
|
|
1420
|
+
* Returns a 168-byte public memo encrypted with a zero viewing key.
|
|
1421
|
+
* Decryptable by anyone with `decrypt(memo, commitment, new Uint8Array(32))`.
|
|
1422
|
+
* Convenience alias for `encrypt(..., new Uint8Array(32))`.
|
|
1187
1423
|
*/
|
|
1188
1424
|
encryptPublic(value, ownerPk, blinding, assetId, commitment) {
|
|
1189
1425
|
return EncryptedMemo.encrypt(
|
|
@@ -1196,226 +1432,89 @@ var EncryptedMemo = {
|
|
|
1196
1432
|
);
|
|
1197
1433
|
},
|
|
1198
1434
|
/**
|
|
1199
|
-
* Returns a
|
|
1435
|
+
* Returns a 168-byte zeroed dummy memo (no information, always valid on-chain).
|
|
1200
1436
|
*/
|
|
1201
1437
|
dummy() {
|
|
1202
1438
|
return new Uint8Array(ENCRYPTED_MEMO_SIZE);
|
|
1203
1439
|
},
|
|
1204
1440
|
/**
|
|
1205
|
-
*
|
|
1441
|
+
* Validates that `bytes` is a properly-sized encrypted memo.
|
|
1442
|
+
* Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (168 bytes).
|
|
1206
1443
|
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1444
|
+
* Call this at system boundaries (extrinsic builders, precompile encoders)
|
|
1445
|
+
* to catch malformed memos before they reach the chain and fail on-chain.
|
|
1209
1446
|
*
|
|
1210
|
-
* @param
|
|
1211
|
-
* @param
|
|
1212
|
-
* @param recipientVk 32-byte recipient viewing key.
|
|
1447
|
+
* @param bytes The memo bytes to validate.
|
|
1448
|
+
* @param context Optional context string included in the error (e.g. 'shield', 'output[0]').
|
|
1213
1449
|
*/
|
|
1214
|
-
|
|
1215
|
-
if (
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
const cipher = chacha20poly1305(key, nonce);
|
|
1221
|
-
const plaintext = cipher.decrypt(ciphertext);
|
|
1222
|
-
const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);
|
|
1223
|
-
const value = view.getBigUint64(0, true);
|
|
1224
|
-
const ownerPk = bytesToBigintLE(plaintext.slice(8, 40));
|
|
1225
|
-
const blinding = bytesToBigintLE(plaintext.slice(40, 72));
|
|
1226
|
-
const assetId = BigInt(view.getUint32(72, true));
|
|
1227
|
-
return { value, ownerPk, blinding, assetId };
|
|
1228
|
-
} catch {
|
|
1229
|
-
return null;
|
|
1450
|
+
validate(bytes, context) {
|
|
1451
|
+
if (bytes.length !== ENCRYPTED_MEMO_SIZE) {
|
|
1452
|
+
const ctx = context ? ` (${context})` : "";
|
|
1453
|
+
throw new Error(
|
|
1454
|
+
`EncryptedMemo: invalid size${ctx} \u2014 expected ${ENCRYPTED_MEMO_SIZE} bytes, got ${bytes.length}`
|
|
1455
|
+
);
|
|
1230
1456
|
}
|
|
1231
|
-
}
|
|
1232
|
-
};
|
|
1233
|
-
|
|
1234
|
-
// src/shielded-pool/NoteBuilder.ts
|
|
1235
|
-
import { poseidon2, poseidon4 } from "poseidon-lite";
|
|
1236
|
-
var NoteBuilder = class {
|
|
1457
|
+
},
|
|
1237
1458
|
/**
|
|
1238
|
-
*
|
|
1459
|
+
* Decrypt an on-chain EncryptedMemo using the recipient's viewing secret key.
|
|
1460
|
+
* Returns null if decryption fails — wrong key, bad MAC, or malformed memo.
|
|
1461
|
+
* Never throws; safe for scan loops.
|
|
1239
1462
|
*
|
|
1240
|
-
* @param
|
|
1241
|
-
* @param
|
|
1242
|
-
* @param
|
|
1243
|
-
* @param input.blinding Random scalar — defaults to BigInt(Date.now()).
|
|
1244
|
-
* @param input.spendingKey Secret key for nullifier — default 0n.
|
|
1463
|
+
* @param memoBytes 168-byte encrypted memo.
|
|
1464
|
+
* @param commitment 32-byte note commitment (LE).
|
|
1465
|
+
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1245
1466
|
*/
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const blinding = input.blinding ?? BigInt(Date.now());
|
|
1251
|
-
const spendingKey = input.spendingKey ?? 0n;
|
|
1252
|
-
const commitment = poseidon4([value, assetId, ownerPk, blinding]);
|
|
1253
|
-
const nullifier = poseidon2([commitment, spendingKey]);
|
|
1254
|
-
const commitmentBytes = bigintTo32Le(commitment);
|
|
1255
|
-
const nullifierBytes = bigintTo32Le(nullifier);
|
|
1256
|
-
const memo = input.viewingKey !== void 0 ? Array.from(
|
|
1257
|
-
EncryptedMemo.encrypt(
|
|
1258
|
-
value,
|
|
1259
|
-
bigintTo32Le(ownerPk),
|
|
1260
|
-
bigintTo32Le(blinding),
|
|
1261
|
-
Number(assetId),
|
|
1262
|
-
commitmentBytes,
|
|
1263
|
-
input.viewingKey
|
|
1264
|
-
)
|
|
1265
|
-
) : Array.from(EncryptedMemo.dummy());
|
|
1266
|
-
const note = {
|
|
1267
|
-
value,
|
|
1268
|
-
assetId,
|
|
1269
|
-
ownerPk,
|
|
1270
|
-
blinding,
|
|
1271
|
-
spendingKey,
|
|
1272
|
-
spent: false,
|
|
1273
|
-
spentAt: null,
|
|
1274
|
-
commitment,
|
|
1275
|
-
nullifier,
|
|
1276
|
-
commitmentHex: toHex(commitmentBytes),
|
|
1277
|
-
nullifierHex: toHex(nullifierBytes),
|
|
1278
|
-
memo
|
|
1279
|
-
};
|
|
1280
|
-
return note;
|
|
1281
|
-
}
|
|
1467
|
+
decrypt(memoBytes, commitment, viewingSecretKey) {
|
|
1468
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
|
|
1469
|
+
return EncryptedMemo._decrypt(memoBytes, commitment, viewingSecretKey);
|
|
1470
|
+
},
|
|
1282
1471
|
/**
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1285
|
-
* Pure TypeScript implementation — no WASM dependency.
|
|
1286
|
-
* Uses ChaCha20-Poly1305 with SHA-256 key derivation.
|
|
1472
|
+
* Extract the ECDH shared secret from an encrypted memo using the recipient's viewing secret key.
|
|
1287
1473
|
*
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
* Pass `new Uint8Array(32)` (default) for a public/dummy memo.
|
|
1291
|
-
*/
|
|
1292
|
-
static buildMemo(note, recipientVk) {
|
|
1293
|
-
return EncryptedMemo.encrypt(
|
|
1294
|
-
note.value,
|
|
1295
|
-
bigintTo32Le(note.ownerPk),
|
|
1296
|
-
bigintTo32Le(note.blinding),
|
|
1297
|
-
Number(note.assetId),
|
|
1298
|
-
bigintTo32Le(note.commitment),
|
|
1299
|
-
recipientVk ?? new Uint8Array(32)
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
1302
|
-
};
|
|
1303
|
-
|
|
1304
|
-
// src/shielded-pool/ShieldedPoolModule.ts
|
|
1305
|
-
var ShieldedPoolModule = class {
|
|
1306
|
-
constructor(substrate) {
|
|
1307
|
-
this.substrate = substrate;
|
|
1308
|
-
}
|
|
1309
|
-
// ─── Extrinsics ────────────────────────────────────────────────────────────
|
|
1310
|
-
/**
|
|
1311
|
-
* Deposits tokens into the shielded pool.
|
|
1312
|
-
* Extrinsic: shieldedPool.shield(assetId, amount, commitment, encryptedMemo)
|
|
1313
|
-
*/
|
|
1314
|
-
async shield(params, signer) {
|
|
1315
|
-
const memo = params.encryptedMemo ?? EncryptedMemo.dummy();
|
|
1316
|
-
const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "shield");
|
|
1317
|
-
const tx = callUnsafeTx(
|
|
1318
|
-
entry,
|
|
1319
|
-
params.assetId,
|
|
1320
|
-
params.amount.toString(),
|
|
1321
|
-
Binary2.fromHex(params.commitment),
|
|
1322
|
-
Binary2.fromBytes(memo)
|
|
1323
|
-
);
|
|
1324
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* Build a ZkNote locally and submit shieldedPool.shield in one call.
|
|
1474
|
+
* Used by NoteDecryptor to obtain the shared secret needed for stealth address derivation
|
|
1475
|
+
* without re-running the full decrypt path. Safe to call on any 168-byte memo.
|
|
1328
1476
|
*
|
|
1329
|
-
* Returns
|
|
1330
|
-
*
|
|
1477
|
+
* Returns `new Uint8Array(32)` (all zeros) for public/dummy memos (zero ephPk).
|
|
1478
|
+
* Returns `null` if the memo is malformed or the ephPk is not a valid BJJ point.
|
|
1479
|
+
* Never throws; safe for scan loops.
|
|
1331
1480
|
*
|
|
1332
|
-
* @param
|
|
1333
|
-
* @param
|
|
1334
|
-
* @param params.ownerPk BabyJubJub Ax (default 0n).
|
|
1335
|
-
* @param params.blinding Random blinding scalar (default BigInt(Date.now())).
|
|
1336
|
-
* @param params.spendingKey Secret spending key (default 0n).
|
|
1337
|
-
*/
|
|
1338
|
-
async buildAndShield(params, signer) {
|
|
1339
|
-
const noteInput = {
|
|
1340
|
-
value: params.value,
|
|
1341
|
-
...params.assetId !== void 0 && { assetId: BigInt(params.assetId) },
|
|
1342
|
-
...params.ownerPk !== void 0 && { ownerPk: params.ownerPk },
|
|
1343
|
-
...params.blinding !== void 0 && { blinding: params.blinding },
|
|
1344
|
-
...params.spendingKey !== void 0 && { spendingKey: params.spendingKey }
|
|
1345
|
-
};
|
|
1346
|
-
const note = await NoteBuilder.build(noteInput);
|
|
1347
|
-
const memo = NoteBuilder.buildMemo(note);
|
|
1348
|
-
const txResult = await this.shield(
|
|
1349
|
-
{
|
|
1350
|
-
assetId: Number(note.assetId),
|
|
1351
|
-
amount: note.value,
|
|
1352
|
-
commitment: note.commitmentHex,
|
|
1353
|
-
encryptedMemo: memo
|
|
1354
|
-
},
|
|
1355
|
-
signer
|
|
1356
|
-
);
|
|
1357
|
-
return { txResult, note };
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
|
-
* Withdraws tokens from the shielded pool to a public address.
|
|
1361
|
-
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient)
|
|
1362
|
-
*/
|
|
1363
|
-
async unshield(params, signer) {
|
|
1364
|
-
const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "unshield");
|
|
1365
|
-
const tx = callUnsafeTx(
|
|
1366
|
-
entry,
|
|
1367
|
-
Binary2.fromBytes(params.proof),
|
|
1368
|
-
Binary2.fromHex(params.merkleRoot),
|
|
1369
|
-
Binary2.fromHex(params.nullifier),
|
|
1370
|
-
params.assetId,
|
|
1371
|
-
params.amount.toString(),
|
|
1372
|
-
Binary2.fromHex(params.recipientAddress)
|
|
1373
|
-
);
|
|
1374
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1375
|
-
}
|
|
1376
|
-
/**
|
|
1377
|
-
* Performs a private (shielded) transfer between two notes.
|
|
1378
|
-
* Extrinsic: shieldedPool.privateTransfer(inputs, outputs, proof, merkleRoot)
|
|
1481
|
+
* @param memoBytes 168-byte encrypted memo.
|
|
1482
|
+
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1379
1483
|
*/
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
);
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
const tx = callUnsafeTx(entry, operations);
|
|
1412
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1484
|
+
extractSharedSecret(memoBytes, viewingSecretKey) {
|
|
1485
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
|
|
1486
|
+
const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1487
|
+
const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
|
|
1488
|
+
if (ephPkPackedBigint === 0n) {
|
|
1489
|
+
return new Uint8Array(32);
|
|
1490
|
+
}
|
|
1491
|
+
const ephPkPoint = unpackPoint(ephPkPackedBigint);
|
|
1492
|
+
if (!ephPkPoint) return null;
|
|
1493
|
+
const ivskScalar = bytesToBjjScalar(viewingSecretKey);
|
|
1494
|
+
const sharedPoint = mulPointEscalar(ephPkPoint, ivskScalar);
|
|
1495
|
+
return bigintTo32Le(sharedPoint[0]);
|
|
1496
|
+
},
|
|
1497
|
+
/** @internal */
|
|
1498
|
+
_decrypt(memoBytes, commitment, viewingSecretKey) {
|
|
1499
|
+
const nonce = memoBytes.slice(0, NONCE_SIZE);
|
|
1500
|
+
const ciphertextWithMac = memoBytes.slice(NONCE_SIZE, NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1501
|
+
const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1502
|
+
const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
|
|
1503
|
+
let sharedSecret;
|
|
1504
|
+
if (ephPkPackedBigint === 0n) {
|
|
1505
|
+
sharedSecret = new Uint8Array(32);
|
|
1506
|
+
} else {
|
|
1507
|
+
const ephPkPoint = unpackPoint(ephPkPackedBigint);
|
|
1508
|
+
if (!ephPkPoint) return null;
|
|
1509
|
+
const ivskScalar = bytesToBjjScalar(viewingSecretKey);
|
|
1510
|
+
const sharedPoint = mulPointEscalar(ephPkPoint, ivskScalar);
|
|
1511
|
+
sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1512
|
+
}
|
|
1513
|
+
const encKey = deriveEncryptionKey(sharedSecret, commitment);
|
|
1514
|
+
return parsePlaintext(nonce, ciphertextWithMac, encKey);
|
|
1413
1515
|
}
|
|
1414
1516
|
};
|
|
1415
1517
|
|
|
1416
|
-
// src/account-mapping/AccountMappingModule.ts
|
|
1417
|
-
import { Binary as Binary3 } from "polkadot-api";
|
|
1418
|
-
|
|
1419
1518
|
// src/utils/address.ts
|
|
1420
1519
|
import { decodeAddress, encodeAddress } from "@polkadot/util-crypto";
|
|
1421
1520
|
function normalizeEvmAddress(addr) {
|
|
@@ -1552,6 +1651,242 @@ function addressToAccountIdHex(addr) {
|
|
|
1552
1651
|
return substrateSs58ToAccountIdHex(addr);
|
|
1553
1652
|
}
|
|
1554
1653
|
|
|
1654
|
+
// src/shielded-pool/pallet/ShieldedPoolModule.ts
|
|
1655
|
+
var ShieldedPoolModule = class {
|
|
1656
|
+
constructor(substrate) {
|
|
1657
|
+
this.substrate = substrate;
|
|
1658
|
+
}
|
|
1659
|
+
substrate;
|
|
1660
|
+
// ─── Extrinsics ────────────────────────────────────────────────────────────
|
|
1661
|
+
/**
|
|
1662
|
+
* Deposits tokens into the shielded pool.
|
|
1663
|
+
* Extrinsic: shieldedPool.shield(assetId, amount, commitment, encryptedMemo)
|
|
1664
|
+
*
|
|
1665
|
+
* Shield is always a signed (public) transaction — the caller's address
|
|
1666
|
+
* appears on-chain as the depositor.
|
|
1667
|
+
*/
|
|
1668
|
+
async shield(params, signer, txOptions) {
|
|
1669
|
+
EncryptedMemo.validate(params.encryptedMemo, "shield.encryptedMemo");
|
|
1670
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "shield");
|
|
1671
|
+
const tx = callUnsafeTx(entry, {
|
|
1672
|
+
asset_id: params.assetId,
|
|
1673
|
+
amount: params.amount,
|
|
1674
|
+
commitment: Binary2.fromHex(params.commitment),
|
|
1675
|
+
encrypted_memo: Binary2.fromBytes(params.encryptedMemo)
|
|
1676
|
+
});
|
|
1677
|
+
return toTxResult(
|
|
1678
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Withdraws tokens from the shielded pool to a public address.
|
|
1683
|
+
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
1684
|
+
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
1685
|
+
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee)
|
|
1686
|
+
*/
|
|
1687
|
+
async unshield(params, signer, txOptions) {
|
|
1688
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "unshield");
|
|
1689
|
+
const recipientSs58 = accountIdHexToSs58(params.recipientAddress);
|
|
1690
|
+
if (!recipientSs58) throw new Error(`Invalid recipientAddress: ${params.recipientAddress}`);
|
|
1691
|
+
const changeCommitment = params.changeCommitment ?? "0x" + "00".repeat(32);
|
|
1692
|
+
let changeEncryptedMemo;
|
|
1693
|
+
if (params.changeEncryptedMemo && params.changeEncryptedMemo.length > 0) {
|
|
1694
|
+
EncryptedMemo.validate(params.changeEncryptedMemo, "changeEncryptedMemo");
|
|
1695
|
+
changeEncryptedMemo = Binary2.fromBytes(params.changeEncryptedMemo);
|
|
1696
|
+
} else {
|
|
1697
|
+
changeEncryptedMemo = Binary2.fromBytes(new Uint8Array(0));
|
|
1698
|
+
}
|
|
1699
|
+
const tx = callUnsafeTx(entry, {
|
|
1700
|
+
proof: Binary2.fromBytes(params.proof),
|
|
1701
|
+
merkle_root: Binary2.fromHex(params.merkleRoot),
|
|
1702
|
+
nullifier: Binary2.fromHex(params.nullifier),
|
|
1703
|
+
asset_id: params.assetId,
|
|
1704
|
+
amount: params.amount,
|
|
1705
|
+
recipient: recipientSs58,
|
|
1706
|
+
fee: params.fee ?? 0n,
|
|
1707
|
+
change_commitment: Binary2.fromHex(changeCommitment),
|
|
1708
|
+
change_encrypted_memo: changeEncryptedMemo,
|
|
1709
|
+
relayer: void 0
|
|
1710
|
+
// Option<H160> — None for direct Substrate submissions
|
|
1711
|
+
});
|
|
1712
|
+
if (signer) {
|
|
1713
|
+
return toTxResult(
|
|
1714
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
return submitBareTx(tx, this.substrate);
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Performs a private (shielded) transfer between two notes.
|
|
1721
|
+
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
1722
|
+
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
1723
|
+
* Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee)
|
|
1724
|
+
*/
|
|
1725
|
+
async privateTransfer(params, signer, txOptions) {
|
|
1726
|
+
const nullifiers = params.inputs.map((inp) => Binary2.fromHex(inp.nullifier));
|
|
1727
|
+
const commitments = params.outputs.map((out) => Binary2.fromHex(out.commitment));
|
|
1728
|
+
const memos = params.outputs.map((out, i) => {
|
|
1729
|
+
EncryptedMemo.validate(
|
|
1730
|
+
out.encryptedMemo,
|
|
1731
|
+
`privateTransfer.outputs[${i}].encryptedMemo`
|
|
1732
|
+
);
|
|
1733
|
+
return Binary2.fromBytes(out.encryptedMemo);
|
|
1734
|
+
});
|
|
1735
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "private_transfer");
|
|
1736
|
+
const tx = callUnsafeTx(entry, {
|
|
1737
|
+
proof: Binary2.fromBytes(params.proof),
|
|
1738
|
+
merkle_root: Binary2.fromHex(params.merkleRoot),
|
|
1739
|
+
nullifiers,
|
|
1740
|
+
commitments,
|
|
1741
|
+
encrypted_memos: memos,
|
|
1742
|
+
asset_id: params.assetId,
|
|
1743
|
+
fee: params.fee ?? 0n,
|
|
1744
|
+
relayer: void 0
|
|
1745
|
+
// Option<H160> — None for direct Substrate submissions
|
|
1746
|
+
});
|
|
1747
|
+
if (signer) {
|
|
1748
|
+
return toTxResult(
|
|
1749
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
return submitBareTx(tx, this.substrate);
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Deposits multiple notes into the shielded pool in a single extrinsic.
|
|
1756
|
+
* Extrinsic: shieldedPool.shieldBatch(operations) — max 20 items.
|
|
1757
|
+
*/
|
|
1758
|
+
async shieldBatch(params, signer, txOptions) {
|
|
1759
|
+
const operations = params.items.map((item, i) => {
|
|
1760
|
+
EncryptedMemo.validate(item.encryptedMemo, `shieldBatch.items[${i}].encryptedMemo`);
|
|
1761
|
+
return {
|
|
1762
|
+
assetId: item.assetId,
|
|
1763
|
+
amount: item.amount.toString(),
|
|
1764
|
+
commitment: Binary2.fromHex(item.commitment),
|
|
1765
|
+
encryptedMemo: Binary2.fromBytes(item.encryptedMemo)
|
|
1766
|
+
};
|
|
1767
|
+
});
|
|
1768
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "shield_batch");
|
|
1769
|
+
const tx = callUnsafeTx(entry, operations);
|
|
1770
|
+
return toTxResult(
|
|
1771
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Claims accrued relay fees into the shielded pool.
|
|
1776
|
+
* This is a SIGNED transaction — the relayer must sign it with their wallet.
|
|
1777
|
+
* Before calling this, generate a ZK disclosure proof with generateFeeClaimProof().
|
|
1778
|
+
*
|
|
1779
|
+
* Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals)
|
|
1780
|
+
*/
|
|
1781
|
+
async claimShieldedFees(params, signer, txOptions) {
|
|
1782
|
+
EncryptedMemo.validate(params.encryptedMemo, "claimShieldedFees.encryptedMemo");
|
|
1783
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "claim_shielded_fees");
|
|
1784
|
+
const tx = callUnsafeTx(entry, {
|
|
1785
|
+
commitment: Binary2.fromHex(params.commitment),
|
|
1786
|
+
amount: params.amount,
|
|
1787
|
+
asset_id: params.assetId,
|
|
1788
|
+
encrypted_memo: Binary2.fromBytes(params.encryptedMemo),
|
|
1789
|
+
proof: Binary2.fromBytes(params.proof),
|
|
1790
|
+
public_signals: Binary2.fromBytes(params.publicSignals)
|
|
1791
|
+
});
|
|
1792
|
+
return toTxResult(
|
|
1793
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
// ─── Selective Disclosure ──────────────────────────────────────────────────
|
|
1797
|
+
/**
|
|
1798
|
+
* Requests a selective disclosure from a target account for a specific commitment.
|
|
1799
|
+
* The auditor's Baby Jubjub public key is included so the note owner knows
|
|
1800
|
+
* which key to encrypt to when generating the proof.
|
|
1801
|
+
* Extrinsic: shieldedPool.request_disclosure(target, commitment, required_fields,
|
|
1802
|
+
* reason, auditor_bjj_pk_x, auditor_bjj_pk_y)
|
|
1803
|
+
*/
|
|
1804
|
+
async requestDisclosure(params, signer, txOptions) {
|
|
1805
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "request_disclosure");
|
|
1806
|
+
const tx = callUnsafeTx(entry, {
|
|
1807
|
+
target: params.target,
|
|
1808
|
+
commitment: Binary2.fromBytes(new Uint8Array(params.commitment)),
|
|
1809
|
+
required_fields: {
|
|
1810
|
+
value: params.requiredFields.value,
|
|
1811
|
+
asset_id: params.requiredFields.assetId,
|
|
1812
|
+
owner: params.requiredFields.owner
|
|
1813
|
+
},
|
|
1814
|
+
reason: Binary2.fromText(params.reason),
|
|
1815
|
+
auditor_bjj_pk_x: Binary2.fromBytes(new Uint8Array(params.auditorBjjPkX)),
|
|
1816
|
+
auditor_bjj_pk_y: Binary2.fromBytes(new Uint8Array(params.auditorBjjPkY))
|
|
1817
|
+
});
|
|
1818
|
+
return toTxResult(
|
|
1819
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Submits a Groth16 ZK disclosure proof for a note commitment.
|
|
1824
|
+
* The proof reveals the selected fields (value, asset_id, owner_hash) on-chain.
|
|
1825
|
+
* Use generateDisclosureProof() + buildDisclosurePublicSignals() before calling this.
|
|
1826
|
+
* Extrinsic: shieldedPool.disclose(commitment, proof_bytes, public_signals, auditor)
|
|
1827
|
+
*/
|
|
1828
|
+
async disclose(params, signer, txOptions) {
|
|
1829
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "disclose");
|
|
1830
|
+
const tx = callUnsafeTx(entry, {
|
|
1831
|
+
commitment: Binary2.fromBytes(new Uint8Array(params.commitment)),
|
|
1832
|
+
proof_bytes: Binary2.fromBytes(new Uint8Array(params.proofBytes)),
|
|
1833
|
+
public_signals: Binary2.fromBytes(new Uint8Array(params.publicSignals)),
|
|
1834
|
+
auditor: params.auditor
|
|
1835
|
+
});
|
|
1836
|
+
return toTxResult(
|
|
1837
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Rejects a pending disclosure request from an auditor for a specific commitment.
|
|
1842
|
+
* Extrinsic: shieldedPool.reject_disclosure(auditor, commitment, reason)
|
|
1843
|
+
*/
|
|
1844
|
+
async rejectDisclosure(params, signer, txOptions) {
|
|
1845
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "reject_disclosure");
|
|
1846
|
+
const tx = callUnsafeTx(entry, {
|
|
1847
|
+
auditor: params.auditor,
|
|
1848
|
+
commitment: Binary2.fromBytes(new Uint8Array(params.commitment)),
|
|
1849
|
+
reason: Binary2.fromText(params.reason)
|
|
1850
|
+
});
|
|
1851
|
+
return toTxResult(
|
|
1852
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Cleans up a disclosure request that has passed its expiration block.
|
|
1857
|
+
* Permissionless — any account can prune expired requests.
|
|
1858
|
+
* Extrinsic: shieldedPool.prune_expired_request(target, auditor, commitment)
|
|
1859
|
+
*/
|
|
1860
|
+
async pruneExpiredRequest(params, signer, txOptions) {
|
|
1861
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "prune_expired_request");
|
|
1862
|
+
const tx = callUnsafeTx(entry, {
|
|
1863
|
+
target: params.target,
|
|
1864
|
+
auditor: params.auditor,
|
|
1865
|
+
commitment: Binary2.fromBytes(new Uint8Array(params.commitment))
|
|
1866
|
+
});
|
|
1867
|
+
return toTxResult(
|
|
1868
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Revokes a previously submitted voluntary disclosure record.
|
|
1873
|
+
* Only applies to self-disclosures (auditor = None). Auditor-requested records are permanent.
|
|
1874
|
+
* Extrinsic: shieldedPool.revoke_disclosure_record(commitment)
|
|
1875
|
+
*/
|
|
1876
|
+
async revokeDisclosureRecord(params, signer, txOptions) {
|
|
1877
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "revoke_disclosure_record");
|
|
1878
|
+
const tx = callUnsafeTx(entry, {
|
|
1879
|
+
commitment: Binary2.fromBytes(new Uint8Array(params.commitment))
|
|
1880
|
+
});
|
|
1881
|
+
return toTxResult(
|
|
1882
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1883
|
+
);
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
// src/account-mapping/AccountMappingModule.ts
|
|
1888
|
+
import { Binary as Binary3 } from "polkadot-api";
|
|
1889
|
+
|
|
1555
1890
|
// src/account-mapping/helpers.ts
|
|
1556
1891
|
function mapRawScheme(raw) {
|
|
1557
1892
|
if (raw === "Eip191" || raw === "eip191") return "Eip191";
|
|
@@ -1564,6 +1899,7 @@ var AccountMappingModule = class {
|
|
|
1564
1899
|
constructor(substrate) {
|
|
1565
1900
|
this.substrate = substrate;
|
|
1566
1901
|
}
|
|
1902
|
+
substrate;
|
|
1567
1903
|
// ─── Address resolution ─────────────────────────────────────────────────────
|
|
1568
1904
|
/**
|
|
1569
1905
|
* Returns the explicitly mapped (or fallback) Substrate AccountId32 hex for
|
|
@@ -1949,6 +2285,7 @@ var PrivacyModule = class {
|
|
|
1949
2285
|
constructor(substrate) {
|
|
1950
2286
|
this.substrate = substrate;
|
|
1951
2287
|
}
|
|
2288
|
+
substrate;
|
|
1952
2289
|
/** Returns the current Merkle tree root. */
|
|
1953
2290
|
async getMerkleRoot() {
|
|
1954
2291
|
return this.substrate.request("privacy_getMerkleRoot", []);
|
|
@@ -1966,14 +2303,23 @@ var PrivacyModule = class {
|
|
|
1966
2303
|
}
|
|
1967
2304
|
/**
|
|
1968
2305
|
* Returns the Merkle inclusion proof for a given commitment hex,
|
|
1969
|
-
* bundled with the
|
|
2306
|
+
* bundled with the Merkle root.
|
|
2307
|
+
*
|
|
2308
|
+
* Uses `privacy_getMerkleProofByCommitment` which resolves root and proof
|
|
2309
|
+
* under the **same block hash**, guaranteeing that the returned path is
|
|
2310
|
+
* consistent with the returned root.
|
|
1970
2311
|
*/
|
|
1971
2312
|
async getMerkleProofByCommitment(commitmentHex) {
|
|
1972
|
-
const
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
return {
|
|
2313
|
+
const raw = await this.substrate.request(
|
|
2314
|
+
"privacy_getMerkleProofByCommitment",
|
|
2315
|
+
[commitmentHex]
|
|
2316
|
+
);
|
|
2317
|
+
return {
|
|
2318
|
+
path: raw.path,
|
|
2319
|
+
leafIndex: raw.leaf_index,
|
|
2320
|
+
treeDepth: raw.tree_depth,
|
|
2321
|
+
root: raw.root
|
|
2322
|
+
};
|
|
1977
2323
|
}
|
|
1978
2324
|
/** Returns the spend status of a nullifier. */
|
|
1979
2325
|
async getNullifierStatus(nullifier) {
|
|
@@ -1992,6 +2338,7 @@ var PrivacyModule = class {
|
|
|
1992
2338
|
return {
|
|
1993
2339
|
merkleRoot: raw.merkle_root,
|
|
1994
2340
|
commitmentCount: raw.commitment_count,
|
|
2341
|
+
nullifierCount: raw.nullifier_count,
|
|
1995
2342
|
totalBalance: raw.total_balance.toString(),
|
|
1996
2343
|
assetBalances: raw.asset_balances.map(mapAssetBalance),
|
|
1997
2344
|
treeDepth: raw.tree_depth
|
|
@@ -2017,6 +2364,7 @@ var ZkVerifierModule = class {
|
|
|
2017
2364
|
constructor(substrate) {
|
|
2018
2365
|
this.substrate = substrate;
|
|
2019
2366
|
}
|
|
2367
|
+
substrate;
|
|
2020
2368
|
/** Returns basic version info for all registered circuits. */
|
|
2021
2369
|
async getAllCircuitVersions() {
|
|
2022
2370
|
const raw = await this.substrate.request(
|
|
@@ -2035,6 +2383,48 @@ var ZkVerifierModule = class {
|
|
|
2035
2383
|
}
|
|
2036
2384
|
};
|
|
2037
2385
|
|
|
2386
|
+
// src/relayer/RelayerStatusModule.ts
|
|
2387
|
+
var RelayerStatusModule = class {
|
|
2388
|
+
constructor(substrate) {
|
|
2389
|
+
this.substrate = substrate;
|
|
2390
|
+
}
|
|
2391
|
+
substrate;
|
|
2392
|
+
/**
|
|
2393
|
+
* Returns true if the given SS58 address is a registered relayer.
|
|
2394
|
+
*/
|
|
2395
|
+
async isRelayer(ss58Address) {
|
|
2396
|
+
return this.substrate.request("relayer_isRelayer", [ss58Address]);
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Returns the pending fees (in planck) for the given account and asset.
|
|
2400
|
+
* The node returns the value as a decimal string to avoid u128 overflow.
|
|
2401
|
+
*/
|
|
2402
|
+
async pendingFees(ss58Address, assetId) {
|
|
2403
|
+
const raw = await this.substrate.request("relayer_pendingFees", [
|
|
2404
|
+
ss58Address,
|
|
2405
|
+
assetId
|
|
2406
|
+
]);
|
|
2407
|
+
return BigInt(raw);
|
|
2408
|
+
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Returns the registered EVM address (0x-prefixed) for the given account,
|
|
2411
|
+
* or null if the account is not a registered relayer.
|
|
2412
|
+
*/
|
|
2413
|
+
async registeredEvmAddress(ss58Address) {
|
|
2414
|
+
return this.substrate.request("relayer_registeredEvmAddress", [ss58Address]);
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Convenience method: returns relayer registry info for an account.
|
|
2418
|
+
*/
|
|
2419
|
+
async getRelayerInfo(ss58Address) {
|
|
2420
|
+
const [isRelayer, evmAddress] = await Promise.all([
|
|
2421
|
+
this.isRelayer(ss58Address),
|
|
2422
|
+
this.registeredEvmAddress(ss58Address)
|
|
2423
|
+
]);
|
|
2424
|
+
return { isRelayer, evmAddress };
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
|
|
2038
2428
|
// src/precompiles/helpers.ts
|
|
2039
2429
|
var STATIC_TYPES = /* @__PURE__ */ new Set(["uint", "bytes32", "address", "bool"]);
|
|
2040
2430
|
function concat(arrays) {
|
|
@@ -2178,10 +2568,6 @@ function decodeBytes(data, slotOffset = 0) {
|
|
|
2178
2568
|
function decodeString(data, slotOffset = 0) {
|
|
2179
2569
|
return new TextDecoder().decode(decodeBytes(data, slotOffset));
|
|
2180
2570
|
}
|
|
2181
|
-
function hexToBytes(hex) {
|
|
2182
|
-
if (hex === "0x" || hex === "") return new Uint8Array(0);
|
|
2183
|
-
return fromHex(hex.startsWith("0x") ? hex : "0x" + hex);
|
|
2184
|
-
}
|
|
2185
2571
|
|
|
2186
2572
|
// src/precompiles/addresses.ts
|
|
2187
2573
|
var PRECOMPILE_ADDR = {
|
|
@@ -2240,12 +2626,20 @@ var AM_SEL = {
|
|
|
2240
2626
|
SET_ACCOUNT_METADATA: new Uint8Array([119, 108, 249, 255])
|
|
2241
2627
|
};
|
|
2242
2628
|
var SP_SEL = {
|
|
2243
|
-
// shield(uint32,
|
|
2244
|
-
SHIELD: new Uint8Array([
|
|
2245
|
-
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[])
|
|
2246
|
-
PRIVATE_TRANSFER: new Uint8Array([
|
|
2247
|
-
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32)
|
|
2248
|
-
UNSHIELD: new Uint8Array([
|
|
2629
|
+
// shield(uint32,bytes32,bytes) → 0x9feb22ea (payable, amount = msg.value)
|
|
2630
|
+
SHIELD: new Uint8Array([159, 235, 34, 234]),
|
|
2631
|
+
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256) → 0x8c0f5d24
|
|
2632
|
+
PRIVATE_TRANSFER: new Uint8Array([140, 15, 93, 36]),
|
|
2633
|
+
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32) → 0xd21d9a79
|
|
2634
|
+
UNSHIELD: new Uint8Array([210, 29, 154, 121]),
|
|
2635
|
+
// requestDisclosure(bytes32,bytes32,bool,bool,bool,bytes,bytes32,bytes32) → 0xe7022933 (caller = auditor)
|
|
2636
|
+
REQUEST_DISCLOSURE: new Uint8Array([231, 2, 41, 51]),
|
|
2637
|
+
// disclose(bytes32,bytes,bytes,bytes32) → 0xea8a4165 (caller = note owner)
|
|
2638
|
+
DISCLOSE: new Uint8Array([234, 138, 65, 101]),
|
|
2639
|
+
// rejectDisclosure(bytes32,bytes32,bytes) → 0x72b895a9 (caller = target)
|
|
2640
|
+
REJECT_DISCLOSURE: new Uint8Array([114, 184, 149, 169]),
|
|
2641
|
+
// pruneExpiredRequest(bytes32,bytes32,bytes32) → 0x0c338dcf (permissionless)
|
|
2642
|
+
PRUNE_EXPIRED_REQUEST: new Uint8Array([12, 51, 141, 207])
|
|
2249
2643
|
};
|
|
2250
2644
|
var KNOWN_PRECOMPILES = {
|
|
2251
2645
|
// ── Ethereum standard (EIP) ─────────────────────────────────────────────
|
|
@@ -2288,9 +2682,13 @@ var KNOWN_PRECOMPILES = {
|
|
|
2288
2682
|
"0x0000000000000000000000000000000000000801": {
|
|
2289
2683
|
name: "ShieldedPool",
|
|
2290
2684
|
functions: {
|
|
2291
|
-
"
|
|
2292
|
-
|
|
2293
|
-
|
|
2685
|
+
"9feb22ea": "shield(uint32,bytes32,bytes)",
|
|
2686
|
+
"8c0f5d24": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)",
|
|
2687
|
+
d21d9a79: "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32)",
|
|
2688
|
+
e7022933: "requestDisclosure(bytes32,bytes32,bool,bool,bool,bytes,bytes32,bytes32)",
|
|
2689
|
+
ea8a4165: "disclose(bytes32,bytes,bytes,bytes32)",
|
|
2690
|
+
"72b895a9": "rejectDisclosure(bytes32,bytes32,bytes)",
|
|
2691
|
+
"0c338dcf": "pruneExpiredRequest(bytes32,bytes32,bytes32)"
|
|
2294
2692
|
}
|
|
2295
2693
|
}
|
|
2296
2694
|
};
|
|
@@ -2304,43 +2702,58 @@ var ShieldedPoolPrecompile = class {
|
|
|
2304
2702
|
constructor(evm) {
|
|
2305
2703
|
this.evm = evm;
|
|
2306
2704
|
}
|
|
2705
|
+
evm;
|
|
2307
2706
|
addr = PRECOMPILE_ADDR.SHIELDED_POOL;
|
|
2308
2707
|
// ─── shield ────────────────────────────────────────────────────────────────
|
|
2309
2708
|
/**
|
|
2310
|
-
* Returns the ABI-encoded calldata for `shield(uint32,
|
|
2311
|
-
*
|
|
2709
|
+
* Returns the ABI-encoded calldata for `shield(uint32, bytes32, bytes)`.
|
|
2710
|
+
* The token amount must be sent as `msg.value` (the `value` field of the EVM
|
|
2711
|
+
* transaction) — this is what MetaMask and other wallets display to the user.
|
|
2312
2712
|
*/
|
|
2313
2713
|
buildShieldCalldata(params) {
|
|
2314
|
-
|
|
2714
|
+
EncryptedMemo.validate(params.encryptedMemo, "buildShieldCalldata.encryptedMemo");
|
|
2315
2715
|
const commitment = fromHex(params.commitment);
|
|
2316
2716
|
return encodeHex(
|
|
2317
2717
|
SP_SEL.SHIELD,
|
|
2318
2718
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2319
|
-
{ type: "uint", value: params.amount },
|
|
2320
2719
|
{ type: "bytes32", value: commitment },
|
|
2321
|
-
{ type: "bytes", value:
|
|
2720
|
+
{ type: "bytes", value: params.encryptedMemo }
|
|
2322
2721
|
);
|
|
2323
2722
|
}
|
|
2324
2723
|
/**
|
|
2325
|
-
* Deposits tokens into the shielded pool from
|
|
2724
|
+
* Deposits tokens into the shielded pool from a payable EVM transaction.
|
|
2326
2725
|
*
|
|
2327
|
-
* The
|
|
2328
|
-
*
|
|
2726
|
+
* The token amount is sent as `msg.value` so EVM wallets (MetaMask, etc.) display
|
|
2727
|
+
* the correct amount on the confirmation screen. The precompile dispatches
|
|
2728
|
+
* `shieldedPool.shield` with its own address as origin, so the funds flow:
|
|
2729
|
+
* caller → precompile (via msg.value, handled by EVM)
|
|
2730
|
+
* precompile → pool (via pallet transfer)
|
|
2731
|
+
* This avoids double-deduction while keeping the displayed amount accurate.
|
|
2329
2732
|
*
|
|
2330
2733
|
* Extrinsic: `shieldedPool.shield(assetId, amount, commitment, encryptedMemo)`
|
|
2331
2734
|
*/
|
|
2332
2735
|
async shield(params, signer) {
|
|
2333
|
-
return signer({
|
|
2736
|
+
return signer({
|
|
2737
|
+
to: this.addr,
|
|
2738
|
+
data: this.buildShieldCalldata(params),
|
|
2739
|
+
value: params.amount
|
|
2740
|
+
});
|
|
2334
2741
|
}
|
|
2335
2742
|
// ─── privateTransfer ───────────────────────────────────────────────────────
|
|
2336
2743
|
/**
|
|
2337
2744
|
* Returns the ABI-encoded calldata for
|
|
2338
|
-
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[])`.
|
|
2745
|
+
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256)`.
|
|
2339
2746
|
*/
|
|
2340
2747
|
buildPrivateTransferCalldata(params) {
|
|
2341
2748
|
const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
|
|
2342
2749
|
const commitments = params.outputs.map((o) => fromHex(o.commitment));
|
|
2343
|
-
const memos = params.outputs.map((o) =>
|
|
2750
|
+
const memos = params.outputs.map((o, i) => {
|
|
2751
|
+
EncryptedMemo.validate(
|
|
2752
|
+
o.encryptedMemo,
|
|
2753
|
+
`buildPrivateTransferCalldata.outputs[${i}].encryptedMemo`
|
|
2754
|
+
);
|
|
2755
|
+
return o.encryptedMemo;
|
|
2756
|
+
});
|
|
2344
2757
|
const root = fromHex(params.merkleRoot);
|
|
2345
2758
|
return encodeHex(
|
|
2346
2759
|
SP_SEL.PRIVATE_TRANSFER,
|
|
@@ -2348,7 +2761,9 @@ var ShieldedPoolPrecompile = class {
|
|
|
2348
2761
|
{ type: "bytes32", value: root },
|
|
2349
2762
|
{ type: "bytes32[]", value: nullifiers },
|
|
2350
2763
|
{ type: "bytes32[]", value: commitments },
|
|
2351
|
-
{ type: "bytes[]", value: memos }
|
|
2764
|
+
{ type: "bytes[]", value: memos },
|
|
2765
|
+
{ type: "uint", value: BigInt(params.assetId) },
|
|
2766
|
+
{ type: "uint", value: params.fee ?? 0n }
|
|
2352
2767
|
);
|
|
2353
2768
|
}
|
|
2354
2769
|
/**
|
|
@@ -2376,6 +2791,9 @@ var ShieldedPoolPrecompile = class {
|
|
|
2376
2791
|
const recipientBytes = fromHex(
|
|
2377
2792
|
"0x" + (recipientRaw.length === 64 ? recipientRaw : recipientRaw.padEnd(64, "0"))
|
|
2378
2793
|
);
|
|
2794
|
+
const changeCommitmentHex = params.changeCommitment ?? "0x" + "00".repeat(32);
|
|
2795
|
+
const changeCommitment = fromHex(changeCommitmentHex);
|
|
2796
|
+
const changeEncryptedMemo = params.changeEncryptedMemo ?? new Uint8Array();
|
|
2379
2797
|
return encodeHex(
|
|
2380
2798
|
SP_SEL.UNSHIELD,
|
|
2381
2799
|
{ type: "bytes", value: proof },
|
|
@@ -2383,7 +2801,10 @@ var ShieldedPoolPrecompile = class {
|
|
|
2383
2801
|
{ type: "bytes32", value: nullifier },
|
|
2384
2802
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2385
2803
|
{ type: "uint", value: params.amount },
|
|
2386
|
-
{ type: "bytes32", value: recipientBytes }
|
|
2804
|
+
{ type: "bytes32", value: recipientBytes },
|
|
2805
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
2806
|
+
{ type: "bytes32", value: changeCommitment },
|
|
2807
|
+
{ type: "bytes", value: changeEncryptedMemo }
|
|
2387
2808
|
);
|
|
2388
2809
|
}
|
|
2389
2810
|
/**
|
|
@@ -2411,24 +2832,151 @@ var ShieldedPoolPrecompile = class {
|
|
|
2411
2832
|
});
|
|
2412
2833
|
}
|
|
2413
2834
|
/**
|
|
2414
|
-
* Estimates the EVM gas for a `privateTransfer` call.
|
|
2835
|
+
* Estimates the EVM gas for a `privateTransfer` call.
|
|
2836
|
+
*/
|
|
2837
|
+
async estimatePrivateTransferGas(params, from) {
|
|
2838
|
+
return this.evm.estimateGas({
|
|
2839
|
+
from,
|
|
2840
|
+
to: this.addr,
|
|
2841
|
+
data: this.buildPrivateTransferCalldata(params)
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Estimates the EVM gas for an `unshield` call.
|
|
2846
|
+
*/
|
|
2847
|
+
async estimateUnshieldGas(params, from) {
|
|
2848
|
+
return this.evm.estimateGas({
|
|
2849
|
+
from,
|
|
2850
|
+
to: this.addr,
|
|
2851
|
+
data: this.buildUnshieldCalldata(params)
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
// ─── requestDisclosure ─────────────────────────────────────────────────────
|
|
2855
|
+
/**
|
|
2856
|
+
* Returns the ABI-encoded calldata for
|
|
2857
|
+
* `requestDisclosure(bytes32,bytes32,bool,bool,bool,bytes,bytes32,bytes32)`.
|
|
2858
|
+
*
|
|
2859
|
+
* The **EVM caller** of the resulting transaction is treated as the **auditor**
|
|
2860
|
+
* on-chain. No explicit auditor argument is needed.
|
|
2861
|
+
*/
|
|
2862
|
+
buildRequestDisclosureCalldata(params) {
|
|
2863
|
+
if (params.auditorBjjPkX.length !== 32)
|
|
2864
|
+
throw new RangeError("requestDisclosure: auditorBjjPkX must be 32 bytes");
|
|
2865
|
+
if (params.auditorBjjPkY.length !== 32)
|
|
2866
|
+
throw new RangeError("requestDisclosure: auditorBjjPkY must be 32 bytes");
|
|
2867
|
+
const target = fromHex(params.target);
|
|
2868
|
+
const commitment = fromHex(params.commitment);
|
|
2869
|
+
const reasonBytes = new TextEncoder().encode(params.reason);
|
|
2870
|
+
return encodeHex(
|
|
2871
|
+
SP_SEL.REQUEST_DISCLOSURE,
|
|
2872
|
+
{ type: "bytes32", value: target },
|
|
2873
|
+
{ type: "bytes32", value: commitment },
|
|
2874
|
+
{ type: "bool", value: params.disclosedValue },
|
|
2875
|
+
{ type: "bool", value: params.disclosedAssetId },
|
|
2876
|
+
{ type: "bool", value: params.disclosedOwner },
|
|
2877
|
+
{ type: "bytes", value: reasonBytes },
|
|
2878
|
+
{ type: "bytes32", value: params.auditorBjjPkX },
|
|
2879
|
+
{ type: "bytes32", value: params.auditorBjjPkY }
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Requests selective disclosure of a specific commitment.
|
|
2884
|
+
*
|
|
2885
|
+
* The EVM caller is recorded as the auditor on-chain. The note owner can
|
|
2886
|
+
* respond with `disclose()` or reject with `rejectDisclosure()`.
|
|
2887
|
+
*
|
|
2888
|
+
* Extrinsic: `shieldedPool.requestDisclosure(target, commitment, requiredFields, reason, bjjPkX, bjjPkY)`
|
|
2889
|
+
*/
|
|
2890
|
+
async requestDisclosure(params, signer) {
|
|
2891
|
+
return signer({ to: this.addr, data: this.buildRequestDisclosureCalldata(params) });
|
|
2892
|
+
}
|
|
2893
|
+
// ─── disclose ──────────────────────────────────────────────────────────────
|
|
2894
|
+
/**
|
|
2895
|
+
* Returns the ABI-encoded calldata for `disclose(bytes32,bytes,bytes,bytes32)`.
|
|
2896
|
+
*
|
|
2897
|
+
* The **EVM caller** is treated as the **note owner** on-chain.
|
|
2898
|
+
* `params.proofBytes` must be exactly 128 bytes; `params.publicSignals` exactly 256 bytes.
|
|
2899
|
+
*/
|
|
2900
|
+
buildDiscloseCalldata(params) {
|
|
2901
|
+
if (params.proofBytes.length !== 128)
|
|
2902
|
+
throw new RangeError("disclose: proofBytes must be exactly 128 bytes");
|
|
2903
|
+
if (params.publicSignals.length !== 256)
|
|
2904
|
+
throw new RangeError("disclose: publicSignals must be exactly 256 bytes");
|
|
2905
|
+
const commitment = fromHex(params.commitment);
|
|
2906
|
+
const auditor = fromHex(params.auditor);
|
|
2907
|
+
return encodeHex(
|
|
2908
|
+
SP_SEL.DISCLOSE,
|
|
2909
|
+
{ type: "bytes32", value: commitment },
|
|
2910
|
+
{ type: "bytes", value: params.proofBytes },
|
|
2911
|
+
{ type: "bytes", value: params.publicSignals },
|
|
2912
|
+
{ type: "bytes32", value: auditor }
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Submits a selective disclosure proof for a commitment.
|
|
2917
|
+
*
|
|
2918
|
+
* The EVM caller is treated as the note owner on-chain. The Groth16 proof is
|
|
2919
|
+
* verified by the runtime; on success the encrypted signals are stored for
|
|
2920
|
+
* the auditor to decrypt off-chain.
|
|
2921
|
+
*
|
|
2922
|
+
* Extrinsic: `shieldedPool.disclose(commitment, proofBytes, publicSignals, auditor)`
|
|
2923
|
+
*/
|
|
2924
|
+
async disclose(params, signer) {
|
|
2925
|
+
return signer({ to: this.addr, data: this.buildDiscloseCalldata(params) });
|
|
2926
|
+
}
|
|
2927
|
+
// ─── rejectDisclosure ──────────────────────────────────────────────────────
|
|
2928
|
+
/**
|
|
2929
|
+
* Returns the ABI-encoded calldata for `rejectDisclosure(bytes32,bytes32,bytes)`.
|
|
2930
|
+
*
|
|
2931
|
+
* The **EVM caller** is treated as the **target** (note owner) on-chain.
|
|
2932
|
+
*/
|
|
2933
|
+
buildRejectDisclosureCalldata(params) {
|
|
2934
|
+
const auditor = fromHex(params.auditor);
|
|
2935
|
+
const commitment = fromHex(params.commitment);
|
|
2936
|
+
const reasonBytes = new TextEncoder().encode(params.reason);
|
|
2937
|
+
return encodeHex(
|
|
2938
|
+
SP_SEL.REJECT_DISCLOSURE,
|
|
2939
|
+
{ type: "bytes32", value: auditor },
|
|
2940
|
+
{ type: "bytes32", value: commitment },
|
|
2941
|
+
{ type: "bytes", value: reasonBytes }
|
|
2942
|
+
);
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Rejects a pending disclosure request.
|
|
2946
|
+
*
|
|
2947
|
+
* The EVM caller is treated as the note owner (target) on-chain.
|
|
2948
|
+
*
|
|
2949
|
+
* Extrinsic: `shieldedPool.rejectDisclosure(auditor, commitment, reason)`
|
|
2950
|
+
*/
|
|
2951
|
+
async rejectDisclosure(params, signer) {
|
|
2952
|
+
return signer({ to: this.addr, data: this.buildRejectDisclosureCalldata(params) });
|
|
2953
|
+
}
|
|
2954
|
+
// ─── pruneExpiredRequest ───────────────────────────────────────────────────
|
|
2955
|
+
/**
|
|
2956
|
+
* Returns the ABI-encoded calldata for `pruneExpiredRequest(bytes32,bytes32,bytes32)`.
|
|
2957
|
+
*
|
|
2958
|
+
* Permissionless: any EVM caller can prune an expired request.
|
|
2415
2959
|
*/
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2960
|
+
buildPruneExpiredRequestCalldata(params) {
|
|
2961
|
+
const target = fromHex(params.target);
|
|
2962
|
+
const auditor = fromHex(params.auditor);
|
|
2963
|
+
const commitment = fromHex(params.commitment);
|
|
2964
|
+
return encodeHex(
|
|
2965
|
+
SP_SEL.PRUNE_EXPIRED_REQUEST,
|
|
2966
|
+
{ type: "bytes32", value: target },
|
|
2967
|
+
{ type: "bytes32", value: auditor },
|
|
2968
|
+
{ type: "bytes32", value: commitment }
|
|
2969
|
+
);
|
|
2422
2970
|
}
|
|
2423
2971
|
/**
|
|
2424
|
-
*
|
|
2972
|
+
* Removes an expired disclosure request from storage.
|
|
2973
|
+
*
|
|
2974
|
+
* Permissionless: any EVM account can call this once `expires_at` has passed.
|
|
2975
|
+
*
|
|
2976
|
+
* Extrinsic: `shieldedPool.pruneExpiredRequest(target, auditor, commitment)`
|
|
2425
2977
|
*/
|
|
2426
|
-
async
|
|
2427
|
-
return this.
|
|
2428
|
-
from,
|
|
2429
|
-
to: this.addr,
|
|
2430
|
-
data: this.buildUnshieldCalldata(params)
|
|
2431
|
-
});
|
|
2978
|
+
async pruneExpiredRequest(params, signer) {
|
|
2979
|
+
return signer({ to: this.addr, data: this.buildPruneExpiredRequestCalldata(params) });
|
|
2432
2980
|
}
|
|
2433
2981
|
};
|
|
2434
2982
|
|
|
@@ -2437,6 +2985,7 @@ var AccountMappingPrecompile = class {
|
|
|
2437
2985
|
constructor(evm) {
|
|
2438
2986
|
this.evm = evm;
|
|
2439
2987
|
}
|
|
2988
|
+
evm;
|
|
2440
2989
|
addr = PRECOMPILE_ADDR.ACCOUNT_MAPPING;
|
|
2441
2990
|
// ─── Read-only ─────────────────────────────────────────────────────────────
|
|
2442
2991
|
/**
|
|
@@ -2448,7 +2997,7 @@ var AccountMappingPrecompile = class {
|
|
|
2448
2997
|
async resolveAlias(alias) {
|
|
2449
2998
|
try {
|
|
2450
2999
|
const data = encodeHex(AM_SEL.RESOLVE_ALIAS, { type: "string", value: alias });
|
|
2451
|
-
const raw =
|
|
3000
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2452
3001
|
if (raw.length < 64) return null;
|
|
2453
3002
|
const owner = decodeAddress2(raw, 0);
|
|
2454
3003
|
const evm = decodeAddress2(raw, 32);
|
|
@@ -2471,7 +3020,7 @@ var AccountMappingPrecompile = class {
|
|
|
2471
3020
|
type: "address",
|
|
2472
3021
|
value: normalizeEvmAddress(evmAddress)
|
|
2473
3022
|
});
|
|
2474
|
-
const raw =
|
|
3023
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2475
3024
|
if (raw.length === 0) return null;
|
|
2476
3025
|
const alias = decodeString(raw, 0);
|
|
2477
3026
|
return alias.length > 0 ? alias : null;
|
|
@@ -2491,7 +3040,7 @@ var AccountMappingPrecompile = class {
|
|
|
2491
3040
|
{ type: "string", value: alias },
|
|
2492
3041
|
{ type: "bytes32", value: commitmentBytes }
|
|
2493
3042
|
);
|
|
2494
|
-
const raw =
|
|
3043
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2495
3044
|
if (raw.length < 32) return false;
|
|
2496
3045
|
return decodeBool(raw, 0);
|
|
2497
3046
|
} catch {
|
|
@@ -2696,6 +3245,7 @@ var CryptoPrecompiles = class {
|
|
|
2696
3245
|
constructor(evm) {
|
|
2697
3246
|
this.evm = evm;
|
|
2698
3247
|
}
|
|
3248
|
+
evm;
|
|
2699
3249
|
// ─── ECRecover (0x0001) ───────────────────────────────────────────────────
|
|
2700
3250
|
/**
|
|
2701
3251
|
* Recovers the Ethereum address from an ECDSA signature.
|
|
@@ -2711,7 +3261,7 @@ var CryptoPrecompiles = class {
|
|
|
2711
3261
|
input[63] = v;
|
|
2712
3262
|
input.set(r.slice(0, 32), 64);
|
|
2713
3263
|
input.set(s.slice(0, 32), 96);
|
|
2714
|
-
const raw =
|
|
3264
|
+
const raw = fromHex(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER, toHex(input)));
|
|
2715
3265
|
if (raw.length < 32) return "0x" + "00".repeat(20);
|
|
2716
3266
|
return "0x" + toHex(raw.slice(12, 32)).slice(2);
|
|
2717
3267
|
}
|
|
@@ -2727,7 +3277,7 @@ var CryptoPrecompiles = class {
|
|
|
2727
3277
|
input[63] = v;
|
|
2728
3278
|
input.set(r.slice(0, 32), 64);
|
|
2729
3279
|
input.set(s.slice(0, 32), 96);
|
|
2730
|
-
return
|
|
3280
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER_PUBKEY, toHex(input)));
|
|
2731
3281
|
}
|
|
2732
3282
|
// ─── SHA-256 (0x0002) ─────────────────────────────────────────────────────
|
|
2733
3283
|
/**
|
|
@@ -2735,7 +3285,7 @@ var CryptoPrecompiles = class {
|
|
|
2735
3285
|
* Returns a 32-byte digest.
|
|
2736
3286
|
*/
|
|
2737
3287
|
async sha256(data) {
|
|
2738
|
-
return
|
|
3288
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.SHA256, toHex(data)));
|
|
2739
3289
|
}
|
|
2740
3290
|
// ─── RIPEMD-160 (0x0003) ──────────────────────────────────────────────────
|
|
2741
3291
|
/**
|
|
@@ -2743,7 +3293,7 @@ var CryptoPrecompiles = class {
|
|
|
2743
3293
|
* Returns the 20-byte digest right-padded to 32 bytes (standard ABI output).
|
|
2744
3294
|
*/
|
|
2745
3295
|
async ripemd160(data) {
|
|
2746
|
-
const raw =
|
|
3296
|
+
const raw = fromHex(await this.evm.call(PRECOMPILE_ADDR.RIPEMD160, toHex(data)));
|
|
2747
3297
|
return raw.length >= 32 ? raw.slice(12, 32) : raw;
|
|
2748
3298
|
}
|
|
2749
3299
|
// ─── Identity (0x0004) ────────────────────────────────────────────────────
|
|
@@ -2752,7 +3302,7 @@ var CryptoPrecompiles = class {
|
|
|
2752
3302
|
* Mainly useful for gas benchmarking.
|
|
2753
3303
|
*/
|
|
2754
3304
|
async identity(data) {
|
|
2755
|
-
return
|
|
3305
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.IDENTITY, toHex(data)));
|
|
2756
3306
|
}
|
|
2757
3307
|
// ─── SHA3-FIPS-256 / Keccak-256 (0x0400) ─────────────────────────────────
|
|
2758
3308
|
/**
|
|
@@ -2760,7 +3310,7 @@ var CryptoPrecompiles = class {
|
|
|
2760
3310
|
* Returns a 32-byte digest.
|
|
2761
3311
|
*/
|
|
2762
3312
|
async keccak256(data) {
|
|
2763
|
-
return
|
|
3313
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.SHA3_FIPS256, toHex(data)));
|
|
2764
3314
|
}
|
|
2765
3315
|
// ─── Curve25519 / Ristretto (0x0402, 0x0403) ─────────────────────────────
|
|
2766
3316
|
/**
|
|
@@ -2783,7 +3333,7 @@ var CryptoPrecompiles = class {
|
|
|
2783
3333
|
}
|
|
2784
3334
|
input.set(pt, i * 32);
|
|
2785
3335
|
}
|
|
2786
|
-
return
|
|
3336
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_ADD, toHex(input)));
|
|
2787
3337
|
}
|
|
2788
3338
|
/**
|
|
2789
3339
|
* Multiplies a Ristretto compressed point by a scalar via EVM precompile.
|
|
@@ -2799,39 +3349,44 @@ var CryptoPrecompiles = class {
|
|
|
2799
3349
|
const input = new Uint8Array(64);
|
|
2800
3350
|
input.set(scalar, 0);
|
|
2801
3351
|
input.set(point, 32);
|
|
2802
|
-
return
|
|
3352
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_SCALAR_MUL, toHex(input)));
|
|
2803
3353
|
}
|
|
2804
3354
|
};
|
|
2805
3355
|
|
|
2806
3356
|
// src/client/OrbinumClient.ts
|
|
2807
3357
|
var OrbinumClient = class _OrbinumClient {
|
|
2808
|
-
/** Raw
|
|
3358
|
+
/** Raw Substrate WebSocket connection — use for custom RPC calls or low-level access. */
|
|
2809
3359
|
substrate;
|
|
2810
|
-
/** Raw
|
|
3360
|
+
/** Raw EVM HTTP JSON-RPC client. `null` when `evmRpc` is not configured. */
|
|
2811
3361
|
evm;
|
|
2812
3362
|
/**
|
|
2813
|
-
* High-level EVM block and transaction explorer
|
|
3363
|
+
* High-level EVM block and transaction explorer.
|
|
2814
3364
|
* Provides enriched queries for blocks, transactions, addresses, and token transfers.
|
|
3365
|
+
* `null` when `evmRpc` is not configured.
|
|
2815
3366
|
*/
|
|
2816
3367
|
evmExplorer;
|
|
2817
3368
|
/**
|
|
2818
|
-
* HTTP client for the Orbinum indexer REST API
|
|
3369
|
+
* HTTP client for the Orbinum indexer REST API.
|
|
2819
3370
|
* Provides paginated access to indexed blocks, extrinsics, shielded events, and nullifiers.
|
|
3371
|
+
* `null` when `indexerUrl` is not configured.
|
|
2820
3372
|
*/
|
|
2821
3373
|
indexer;
|
|
2822
|
-
/** Shielded-pool
|
|
3374
|
+
/** Shielded-pool extrinsics and Merkle tree queries (`shield`, `unshield`, `privateTransfer`, …). */
|
|
2823
3375
|
shieldedPool;
|
|
2824
|
-
/** Account
|
|
3376
|
+
/** Account-mapping extrinsics: aliases, chain links, metadata, marketplace, and identity. */
|
|
2825
3377
|
accountMapping;
|
|
2826
|
-
/** Typed access to
|
|
3378
|
+
/** Typed access to `privacy_*` custom RPC endpoints. */
|
|
2827
3379
|
privacy;
|
|
2828
|
-
/** Typed access to zkVerifier_
|
|
3380
|
+
/** Typed access to `zkVerifier_*` custom RPC endpoints. */
|
|
2829
3381
|
zkVerifier;
|
|
3382
|
+
/** Typed access to `relayer_*` RPC endpoints (registry lookup and pending fee queries). */
|
|
3383
|
+
relayerStatus;
|
|
2830
3384
|
/**
|
|
2831
|
-
*
|
|
2832
|
-
*
|
|
3385
|
+
* Precompile modules for interacting with Orbinum contracts from an EVM wallet.
|
|
3386
|
+
* `null` when `evmRpc` is not configured. Methods on each sub-module throw if `evm` is `null`.
|
|
2833
3387
|
*/
|
|
2834
3388
|
precompiles;
|
|
3389
|
+
/** @internal Use `OrbinumClient.connect()` to obtain an instance. */
|
|
2835
3390
|
constructor(substrate, evm, indexer) {
|
|
2836
3391
|
this.substrate = substrate;
|
|
2837
3392
|
this.evm = evm;
|
|
@@ -2841,6 +3396,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2841
3396
|
this.accountMapping = new AccountMappingModule(substrate);
|
|
2842
3397
|
this.privacy = new PrivacyModule(substrate);
|
|
2843
3398
|
this.zkVerifier = new ZkVerifierModule(substrate);
|
|
3399
|
+
this.relayerStatus = new RelayerStatusModule(substrate);
|
|
2844
3400
|
this.precompiles = evm ? {
|
|
2845
3401
|
shieldedPool: new ShieldedPoolPrecompile(evm),
|
|
2846
3402
|
accountMapping: new AccountMappingPrecompile(evm),
|
|
@@ -2848,8 +3404,10 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2848
3404
|
} : null;
|
|
2849
3405
|
}
|
|
2850
3406
|
/**
|
|
2851
|
-
*
|
|
2852
|
-
*
|
|
3407
|
+
* Creates and connects an `OrbinumClient` from the given configuration.
|
|
3408
|
+
*
|
|
3409
|
+
* Establishes the Substrate WebSocket connection and, if configured, instantiates
|
|
3410
|
+
* the EVM and indexer clients. Throws if the node is unreachable within `connectTimeoutMs`.
|
|
2853
3411
|
*/
|
|
2854
3412
|
static async connect(config) {
|
|
2855
3413
|
const substrate = await SubstrateClient.connect(
|
|
@@ -2860,7 +3418,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2860
3418
|
const indexer = config.indexerUrl ? new IndexerClient({ baseUrl: config.indexerUrl }) : null;
|
|
2861
3419
|
return new _OrbinumClient(substrate, evm, indexer);
|
|
2862
3420
|
}
|
|
2863
|
-
/** Closes the WebSocket connection
|
|
3421
|
+
/** Closes the underlying Substrate WebSocket connection and releases all resources. */
|
|
2864
3422
|
destroy() {
|
|
2865
3423
|
this.substrate.destroy();
|
|
2866
3424
|
}
|
|
@@ -2889,6 +3447,7 @@ var OrbinumClientProvider = class {
|
|
|
2889
3447
|
_reconnectAttempt = 0;
|
|
2890
3448
|
// ─── Events ─────────────────────────────────────────────────────────────
|
|
2891
3449
|
_listeners = /* @__PURE__ */ new Set();
|
|
3450
|
+
/** Creates a new provider with the given configuration. Does not connect automatically — call `connect()` to initiate. */
|
|
2892
3451
|
constructor(config) {
|
|
2893
3452
|
this.config = config;
|
|
2894
3453
|
this.connectTimeoutMs = config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
@@ -2898,9 +3457,11 @@ var OrbinumClientProvider = class {
|
|
|
2898
3457
|
this.reconnectMaxMs = config.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
2899
3458
|
}
|
|
2900
3459
|
// ─── Status ─────────────────────────────────────────────────────────────
|
|
3460
|
+
/** Current connection status. Reflects the last state set by the provider internals. */
|
|
2901
3461
|
get status() {
|
|
2902
3462
|
return this._status;
|
|
2903
3463
|
}
|
|
3464
|
+
/** Updates internal status and notifies all registered listeners. Swallows listener exceptions to avoid cascading failures. */
|
|
2904
3465
|
setStatus(status, error) {
|
|
2905
3466
|
this._status = status;
|
|
2906
3467
|
const event = { status, ...error ? { error } : {} };
|
|
@@ -2911,6 +3472,10 @@ var OrbinumClientProvider = class {
|
|
|
2911
3472
|
}
|
|
2912
3473
|
});
|
|
2913
3474
|
}
|
|
3475
|
+
/**
|
|
3476
|
+
* Registers a listener that is called on every status transition.
|
|
3477
|
+
* Returns an unsubscribe function — call it to stop receiving events.
|
|
3478
|
+
*/
|
|
2914
3479
|
onStatusChange(listener) {
|
|
2915
3480
|
this._listeners.add(listener);
|
|
2916
3481
|
return () => {
|
|
@@ -2918,10 +3483,18 @@ var OrbinumClientProvider = class {
|
|
|
2918
3483
|
};
|
|
2919
3484
|
}
|
|
2920
3485
|
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
3486
|
+
/**
|
|
3487
|
+
* Initiates the first connection attempt. No-op if the provider is not in `'idle'` state.
|
|
3488
|
+
* Call this once after constructing the provider.
|
|
3489
|
+
*/
|
|
2921
3490
|
connect() {
|
|
2922
3491
|
if (this._status !== "idle") return;
|
|
2923
3492
|
this.startConnectAttempt();
|
|
2924
3493
|
}
|
|
3494
|
+
/**
|
|
3495
|
+
* Tears down the active client and any pending reconnect timers,
|
|
3496
|
+
* then resets the provider back to `'idle'` so `connect()` can be called again.
|
|
3497
|
+
*/
|
|
2925
3498
|
reset() {
|
|
2926
3499
|
this.cancelReconnect();
|
|
2927
3500
|
this.teardownClient();
|
|
@@ -2929,6 +3502,7 @@ var OrbinumClientProvider = class {
|
|
|
2929
3502
|
this.setStatus("idle");
|
|
2930
3503
|
}
|
|
2931
3504
|
// ─── Internal connection flow ───────────────────────────────────────────
|
|
3505
|
+
/** Transitions to `'connecting'`, kicks off `attemptConnect`, and schedules a reconnect if it fails. */
|
|
2932
3506
|
startConnectAttempt() {
|
|
2933
3507
|
this.setStatus("connecting");
|
|
2934
3508
|
this._connectingPromise = this.attemptConnect();
|
|
@@ -2936,6 +3510,11 @@ var OrbinumClientProvider = class {
|
|
|
2936
3510
|
if (this._status !== "idle") this.scheduleReconnect();
|
|
2937
3511
|
});
|
|
2938
3512
|
}
|
|
3513
|
+
/**
|
|
3514
|
+
* Performs a single connection attempt race against `connectTimeoutMs`.
|
|
3515
|
+
* On success: stores the client, starts the heartbeat, and returns it.
|
|
3516
|
+
* On failure: destroys any orphaned client and transitions to `'disconnected'`.
|
|
3517
|
+
*/
|
|
2939
3518
|
async attemptConnect() {
|
|
2940
3519
|
let timeoutId = null;
|
|
2941
3520
|
let orphanClient = null;
|
|
@@ -2983,6 +3562,7 @@ var OrbinumClientProvider = class {
|
|
|
2983
3562
|
}
|
|
2984
3563
|
}
|
|
2985
3564
|
// ─── Heartbeat ──────────────────────────────────────────────────────────
|
|
3565
|
+
/** Starts the periodic heartbeat loop. Replaces any existing timer. */
|
|
2986
3566
|
startHeartbeat() {
|
|
2987
3567
|
this.stopHeartbeat();
|
|
2988
3568
|
this._heartbeatTimer = setInterval(async () => {
|
|
@@ -2995,12 +3575,17 @@ var OrbinumClientProvider = class {
|
|
|
2995
3575
|
}
|
|
2996
3576
|
}, this.heartbeatIntervalMs);
|
|
2997
3577
|
}
|
|
3578
|
+
/** Clears the heartbeat interval timer if active. */
|
|
2998
3579
|
stopHeartbeat() {
|
|
2999
3580
|
if (this._heartbeatTimer) {
|
|
3000
3581
|
clearInterval(this._heartbeatTimer);
|
|
3001
3582
|
this._heartbeatTimer = null;
|
|
3002
3583
|
}
|
|
3003
3584
|
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Sends a `system_health` RPC ping and waits up to `heartbeatTimeoutMs`.
|
|
3587
|
+
* Returns `true` if the node responds in time, `false` otherwise.
|
|
3588
|
+
*/
|
|
3004
3589
|
async probe() {
|
|
3005
3590
|
if (!this._orbinumClient) return false;
|
|
3006
3591
|
try {
|
|
@@ -3016,6 +3601,10 @@ var OrbinumClientProvider = class {
|
|
|
3016
3601
|
}
|
|
3017
3602
|
}
|
|
3018
3603
|
// ─── Reconnection ───────────────────────────────────────────────────────
|
|
3604
|
+
/**
|
|
3605
|
+
* Schedules the next connection attempt using exponential backoff
|
|
3606
|
+
* (capped at `reconnectMaxMs`), then transitions to `'reconnecting'`.
|
|
3607
|
+
*/
|
|
3019
3608
|
scheduleReconnect() {
|
|
3020
3609
|
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
|
|
3021
3610
|
const delay = Math.min(
|
|
@@ -3029,6 +3618,7 @@ var OrbinumClientProvider = class {
|
|
|
3029
3618
|
if (this._status !== "idle") this.startConnectAttempt();
|
|
3030
3619
|
}, delay);
|
|
3031
3620
|
}
|
|
3621
|
+
/** Clears any pending reconnect timer without triggering a new attempt. */
|
|
3032
3622
|
cancelReconnect() {
|
|
3033
3623
|
if (this._reconnectTimer) {
|
|
3034
3624
|
clearTimeout(this._reconnectTimer);
|
|
@@ -3036,6 +3626,7 @@ var OrbinumClientProvider = class {
|
|
|
3036
3626
|
}
|
|
3037
3627
|
}
|
|
3038
3628
|
// ─── Client teardown ────────────────────────────────────────────────────
|
|
3629
|
+
/** Stops the heartbeat, destroys the active client, and clears all in-progress promises. */
|
|
3039
3630
|
teardownClient() {
|
|
3040
3631
|
this.stopHeartbeat();
|
|
3041
3632
|
try {
|
|
@@ -3046,11 +3637,19 @@ var OrbinumClientProvider = class {
|
|
|
3046
3637
|
this._connectingPromise = null;
|
|
3047
3638
|
}
|
|
3048
3639
|
// ─── Client access ──────────────────────────────────────────────────────
|
|
3640
|
+
/**
|
|
3641
|
+
* Returns the active `OrbinumClient`, or awaits the in-progress connection attempt.
|
|
3642
|
+
* Throws if the provider is `'idle'`, `'disconnected'`, or `'reconnecting'`.
|
|
3643
|
+
*/
|
|
3049
3644
|
async getOrbinumClient() {
|
|
3050
3645
|
if (this._orbinumClient) return this._orbinumClient;
|
|
3051
3646
|
if (this._connectingPromise) return this._connectingPromise;
|
|
3052
3647
|
throw new Error(`OrbinumClientProvider: cannot get client in status '${this._status}'`);
|
|
3053
3648
|
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Same as `getOrbinumClient()` but returns `null` instead of throwing.
|
|
3651
|
+
* Useful in contexts where a missing client is an acceptable no-op.
|
|
3652
|
+
*/
|
|
3054
3653
|
async tryGetOrbinumClient() {
|
|
3055
3654
|
try {
|
|
3056
3655
|
return await this.getOrbinumClient();
|
|
@@ -3059,15 +3658,28 @@ var OrbinumClientProvider = class {
|
|
|
3059
3658
|
}
|
|
3060
3659
|
}
|
|
3061
3660
|
// ─── Convenience RPC helpers ────────────────────────────────────────────
|
|
3661
|
+
/**
|
|
3662
|
+
* Sends a single Substrate JSON-RPC request and returns the typed result.
|
|
3663
|
+
* Waits for the client to be ready before dispatching.
|
|
3664
|
+
*/
|
|
3062
3665
|
async rpcSend(method, params = []) {
|
|
3063
3666
|
const client = await this.getOrbinumClient();
|
|
3064
3667
|
return client.substrate.request(method, params);
|
|
3065
3668
|
}
|
|
3669
|
+
/**
|
|
3670
|
+
* Sends a single EVM JSON-RPC request and returns the typed result.
|
|
3671
|
+
* Throws if `evmRpc` was not configured.
|
|
3672
|
+
*/
|
|
3066
3673
|
async evmRpc(method, params = []) {
|
|
3067
3674
|
const client = await this.getOrbinumClient();
|
|
3068
3675
|
if (!client.evm) throw new Error("EVM RPC not configured");
|
|
3069
3676
|
return client.evm.request(method, params);
|
|
3070
3677
|
}
|
|
3678
|
+
/**
|
|
3679
|
+
* Sends multiple EVM JSON-RPC calls as a single batch request.
|
|
3680
|
+
* Returns a tuple of typed results in the same order as `calls`.
|
|
3681
|
+
* Throws if `evmRpc` was not configured.
|
|
3682
|
+
*/
|
|
3071
3683
|
async evmRpcBatch(calls) {
|
|
3072
3684
|
const client = await this.getOrbinumClient();
|
|
3073
3685
|
if (!client.evm) throw new Error("EVM RPC not configured");
|
|
@@ -3075,102 +3687,471 @@ var OrbinumClientProvider = class {
|
|
|
3075
3687
|
}
|
|
3076
3688
|
};
|
|
3077
3689
|
|
|
3078
|
-
// src/
|
|
3690
|
+
// src/utils/stealth.ts
|
|
3691
|
+
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
3692
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
3693
|
+
import { mulPointEscalar as mulPointEscalar2, Base8 as Base82, addPoint } from "@zk-kit/baby-jubjub";
|
|
3694
|
+
var STEALTH_INFO = new TextEncoder().encode("orbinum-stealth-v1");
|
|
3695
|
+
function deriveStealthScalar(sharedSecret, ownerPkBigint) {
|
|
3696
|
+
const salt = bigintTo32Le(ownerPkBigint);
|
|
3697
|
+
const stealthBytes = hkdf(sha2562, sharedSecret, salt, STEALTH_INFO, 32);
|
|
3698
|
+
return bytesToBigintLE(stealthBytes) % BABYJUB_SUBORDER || 1n;
|
|
3699
|
+
}
|
|
3700
|
+
function deriveStealthOwnerPk(sharedSecret, ownerPkBigint, ownerPkPoint) {
|
|
3701
|
+
const stealthScalar = deriveStealthScalar(sharedSecret, ownerPkBigint);
|
|
3702
|
+
const stealthPt = addPoint(mulPointEscalar2(Base82, stealthScalar), ownerPkPoint);
|
|
3703
|
+
return stealthPt[0];
|
|
3704
|
+
}
|
|
3705
|
+
function deriveStealthSk(sharedSecret, ownerPkBigint, spendingKey) {
|
|
3706
|
+
const stealthScalar = deriveStealthScalar(sharedSecret, ownerPkBigint);
|
|
3707
|
+
return (stealthScalar + spendingKey) % BABYJUB_SUBORDER || 1n;
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
// src/utils/bjj.ts
|
|
3711
|
+
import { mulPointEscalar as mulPointEscalar3 } from "@zk-kit/baby-jubjub";
|
|
3712
|
+
var BJJ_A = 168700n;
|
|
3713
|
+
var BJJ_D = 168696n;
|
|
3714
|
+
function _modpow(base, exp, mod) {
|
|
3715
|
+
let result = 1n;
|
|
3716
|
+
base = base % mod;
|
|
3717
|
+
while (exp > 0n) {
|
|
3718
|
+
if (exp & 1n) result = result * base % mod;
|
|
3719
|
+
exp >>= 1n;
|
|
3720
|
+
base = base * base % mod;
|
|
3721
|
+
}
|
|
3722
|
+
return result;
|
|
3723
|
+
}
|
|
3724
|
+
function _sqrtModP(y2) {
|
|
3725
|
+
if (y2 === 0n) return 0n;
|
|
3726
|
+
if (_modpow(y2, (BN254_R - 1n) / 2n, BN254_R) !== 1n) return null;
|
|
3727
|
+
let s = 0n;
|
|
3728
|
+
let q = BN254_R - 1n;
|
|
3729
|
+
while ((q & 1n) === 0n) {
|
|
3730
|
+
q >>= 1n;
|
|
3731
|
+
s++;
|
|
3732
|
+
}
|
|
3733
|
+
if (s === 1n) return _modpow(y2, (BN254_R + 1n) / 4n, BN254_R);
|
|
3734
|
+
let z = 2n;
|
|
3735
|
+
while (_modpow(z, (BN254_R - 1n) / 2n, BN254_R) === 1n) z++;
|
|
3736
|
+
let m = s;
|
|
3737
|
+
let c = _modpow(z, q, BN254_R);
|
|
3738
|
+
let t = _modpow(y2, q, BN254_R);
|
|
3739
|
+
let r = _modpow(y2, (q + 1n) / 2n, BN254_R);
|
|
3740
|
+
for (; ; ) {
|
|
3741
|
+
if (t === 1n) return r;
|
|
3742
|
+
let i = 1n;
|
|
3743
|
+
let tmp = t * t % BN254_R;
|
|
3744
|
+
while (tmp !== 1n) {
|
|
3745
|
+
tmp = tmp * tmp % BN254_R;
|
|
3746
|
+
i++;
|
|
3747
|
+
}
|
|
3748
|
+
const b = _modpow(c, 1n << m - i - 1n, BN254_R);
|
|
3749
|
+
m = i;
|
|
3750
|
+
c = b * b % BN254_R;
|
|
3751
|
+
t = t * c % BN254_R;
|
|
3752
|
+
r = r * b % BN254_R;
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
function recoverOwnerPkPoint(ax) {
|
|
3756
|
+
const x2 = ax * ax % BN254_R;
|
|
3757
|
+
const num = ((1n - BJJ_A * x2) % BN254_R + BN254_R) % BN254_R;
|
|
3758
|
+
const den = ((1n - BJJ_D * x2) % BN254_R + BN254_R) % BN254_R;
|
|
3759
|
+
if (den === 0n) return null;
|
|
3760
|
+
const denInv = _modpow(den, BN254_R - 2n, BN254_R);
|
|
3761
|
+
const y2 = num * denInv % BN254_R;
|
|
3762
|
+
const y = _sqrtModP(y2);
|
|
3763
|
+
if (y === null) return null;
|
|
3764
|
+
const yAlt = BN254_R - y;
|
|
3765
|
+
try {
|
|
3766
|
+
const check = mulPointEscalar3([ax, y], BABYJUB_SUBORDER);
|
|
3767
|
+
return check[0] === 0n && check[1] === 1n ? [ax, y] : [ax, yAlt];
|
|
3768
|
+
} catch {
|
|
3769
|
+
return [ax, yAlt];
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
// src/shielded-pool/protocol/NoteBuilder.ts
|
|
3774
|
+
import { mulPointEscalar as mulPointEscalar4, unpackPoint as unpackPoint2 } from "@zk-kit/baby-jubjub";
|
|
3775
|
+
import { randomBytes as randomBytes2 } from "@noble/ciphers/utils.js";
|
|
3776
|
+
import { poseidon2, poseidon4 } from "poseidon-lite";
|
|
3777
|
+
var NoteBuilder = class {
|
|
3778
|
+
/**
|
|
3779
|
+
* Build a ZkNote from the given inputs.
|
|
3780
|
+
*
|
|
3781
|
+
* @param input.value Amount in planck (required).
|
|
3782
|
+
* @param input.assetId Asset ID — default 0n (native ORB-Privacy).
|
|
3783
|
+
* @param input.ownerPk Sender's or recipient's global BabyJubJub Ax — default 0n.
|
|
3784
|
+
* @param input.blinding Random scalar — defaults to BigInt(Date.now()).
|
|
3785
|
+
* @param input.spendingKey Secret key for nullifier — default 0n.
|
|
3786
|
+
* @param input.viewingPublicKey Recipient's 32-byte LE packed BJJ ivk. Triggers memo encryption.
|
|
3787
|
+
* @param input.recipientOwnerPk Recipient's global ownerPk. Required with viewingPublicKey
|
|
3788
|
+
* to enable stealth address derivation. Without it, the
|
|
3789
|
+
* commitment uses ownerPk directly (no stealth).
|
|
3790
|
+
*/
|
|
3791
|
+
static async build(input) {
|
|
3792
|
+
const value = input.value;
|
|
3793
|
+
const assetId = input.assetId ?? 0n;
|
|
3794
|
+
const ownerPk = input.ownerPk ?? 0n;
|
|
3795
|
+
const blinding = input.blinding ?? BigInt(Date.now());
|
|
3796
|
+
const spendingKey = input.spendingKey ?? 0n;
|
|
3797
|
+
const counterpartyPk = input.counterpartyPk ?? 0n;
|
|
3798
|
+
const useStealth = input.viewingPublicKey !== void 0 && input.recipientOwnerPk !== void 0;
|
|
3799
|
+
let memo;
|
|
3800
|
+
if (useStealth) {
|
|
3801
|
+
const recipientOwnerPk = input.recipientOwnerPk;
|
|
3802
|
+
const recipientIvkPacked = input.viewingPublicKey;
|
|
3803
|
+
const ephSk = randomBytes2(32);
|
|
3804
|
+
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
3805
|
+
const ivkPoint = unpackPoint2(ivkPackedBigint);
|
|
3806
|
+
if (!ivkPoint)
|
|
3807
|
+
throw new Error("NoteBuilder.build: invalid recipient viewing public key");
|
|
3808
|
+
const ephSkScalar = BigInt(toHex(ephSk)) % BABYJUB_SUBORDER || 1n;
|
|
3809
|
+
const sharedPoint = mulPointEscalar4(ivkPoint, ephSkScalar);
|
|
3810
|
+
const sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
3811
|
+
const recipientPkPoint = recoverOwnerPkPoint(recipientOwnerPk);
|
|
3812
|
+
if (!recipientPkPoint)
|
|
3813
|
+
throw new Error(
|
|
3814
|
+
"NoteBuilder.build: recipientOwnerPk is not a valid BJJ x-coordinate"
|
|
3815
|
+
);
|
|
3816
|
+
const effectiveOwnerPk = deriveStealthOwnerPk(
|
|
3817
|
+
sharedSecret,
|
|
3818
|
+
recipientOwnerPk,
|
|
3819
|
+
recipientPkPoint
|
|
3820
|
+
);
|
|
3821
|
+
const stealthCommitment = poseidon4([value, assetId, effectiveOwnerPk, blinding]);
|
|
3822
|
+
const stealthCommitmentBytes = bigintTo32Le(stealthCommitment);
|
|
3823
|
+
memo = Array.from(
|
|
3824
|
+
EncryptedMemo.encrypt(
|
|
3825
|
+
value,
|
|
3826
|
+
bigintTo32Le(effectiveOwnerPk),
|
|
3827
|
+
bigintTo32Le(blinding),
|
|
3828
|
+
Number(assetId),
|
|
3829
|
+
stealthCommitmentBytes,
|
|
3830
|
+
recipientIvkPacked,
|
|
3831
|
+
bigintTo32Le(counterpartyPk),
|
|
3832
|
+
ephSk
|
|
3833
|
+
)
|
|
3834
|
+
);
|
|
3835
|
+
const commitment2 = stealthCommitment;
|
|
3836
|
+
const nullifier2 = poseidon2([commitment2, spendingKey]);
|
|
3837
|
+
const commitmentBytes2 = stealthCommitmentBytes;
|
|
3838
|
+
const nullifierBytes2 = bigintTo32Le(nullifier2);
|
|
3839
|
+
if (memo.length !== ENCRYPTED_MEMO_SIZE)
|
|
3840
|
+
throw new Error(
|
|
3841
|
+
`NoteBuilder.build: invariant violated \u2014 memo must be ${ENCRYPTED_MEMO_SIZE} bytes, got ${memo.length}`
|
|
3842
|
+
);
|
|
3843
|
+
return {
|
|
3844
|
+
value,
|
|
3845
|
+
assetId,
|
|
3846
|
+
ownerPk: effectiveOwnerPk,
|
|
3847
|
+
blinding,
|
|
3848
|
+
spendingKey,
|
|
3849
|
+
spent: false,
|
|
3850
|
+
spentAt: null,
|
|
3851
|
+
commitment: commitment2,
|
|
3852
|
+
nullifier: nullifier2,
|
|
3853
|
+
commitmentHex: toHex(commitmentBytes2),
|
|
3854
|
+
nullifierHex: toHex(nullifierBytes2),
|
|
3855
|
+
memo,
|
|
3856
|
+
counterpartyPk
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
const commitment = poseidon4([value, assetId, ownerPk, blinding]);
|
|
3860
|
+
const nullifier = poseidon2([commitment, spendingKey]);
|
|
3861
|
+
const commitmentBytes = bigintTo32Le(commitment);
|
|
3862
|
+
const nullifierBytes = bigintTo32Le(nullifier);
|
|
3863
|
+
memo = input.viewingPublicKey !== void 0 ? Array.from(
|
|
3864
|
+
EncryptedMemo.encrypt(
|
|
3865
|
+
value,
|
|
3866
|
+
bigintTo32Le(ownerPk),
|
|
3867
|
+
bigintTo32Le(blinding),
|
|
3868
|
+
Number(assetId),
|
|
3869
|
+
commitmentBytes,
|
|
3870
|
+
input.viewingPublicKey,
|
|
3871
|
+
bigintTo32Le(counterpartyPk)
|
|
3872
|
+
)
|
|
3873
|
+
) : Array.from(EncryptedMemo.dummy());
|
|
3874
|
+
if (memo.length !== ENCRYPTED_MEMO_SIZE)
|
|
3875
|
+
throw new Error(
|
|
3876
|
+
`NoteBuilder.build: invariant violated \u2014 memo must be ${ENCRYPTED_MEMO_SIZE} bytes, got ${memo.length}`
|
|
3877
|
+
);
|
|
3878
|
+
return {
|
|
3879
|
+
value,
|
|
3880
|
+
assetId,
|
|
3881
|
+
ownerPk,
|
|
3882
|
+
blinding,
|
|
3883
|
+
spendingKey,
|
|
3884
|
+
spent: false,
|
|
3885
|
+
spentAt: null,
|
|
3886
|
+
commitment,
|
|
3887
|
+
nullifier,
|
|
3888
|
+
commitmentHex: toHex(commitmentBytes),
|
|
3889
|
+
nullifierHex: toHex(nullifierBytes),
|
|
3890
|
+
memo,
|
|
3891
|
+
counterpartyPk
|
|
3892
|
+
};
|
|
3893
|
+
}
|
|
3894
|
+
/**
|
|
3895
|
+
* Build the 168-byte ECDH-encrypted memo for a note.
|
|
3896
|
+
*
|
|
3897
|
+
* Pure TypeScript implementation — no WASM dependency.
|
|
3898
|
+
* Uses ChaCha20-Poly1305 with ECDH key agreement (BabyJubJub ephemeral keypair).
|
|
3899
|
+
*
|
|
3900
|
+
* @param note The ZkNote whose fields populate the plaintext.
|
|
3901
|
+
* @param recipientIvkPacked 32-byte LE packed BJJ viewing public key of the recipient.
|
|
3902
|
+
* Pass `new Uint8Array(32)` (default) for a public/dummy memo.
|
|
3903
|
+
* @param counterpartyPk 32-byte counterparty BabyJubJub Ax.
|
|
3904
|
+
* Pass `new Uint8Array(32)` (default) for no counterparty.
|
|
3905
|
+
*/
|
|
3906
|
+
static buildMemo(note, recipientIvkPacked, counterpartyPk) {
|
|
3907
|
+
return EncryptedMemo.encrypt(
|
|
3908
|
+
note.value,
|
|
3909
|
+
bigintTo32Le(note.ownerPk),
|
|
3910
|
+
bigintTo32Le(note.blinding),
|
|
3911
|
+
Number(note.assetId),
|
|
3912
|
+
bigintTo32Le(note.commitment),
|
|
3913
|
+
recipientIvkPacked ?? new Uint8Array(32),
|
|
3914
|
+
counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n)
|
|
3915
|
+
);
|
|
3916
|
+
}
|
|
3917
|
+
};
|
|
3918
|
+
|
|
3919
|
+
// src/shielded-pool/protocol/NoteDecryptor.ts
|
|
3079
3920
|
import { poseidon2 as poseidon22, poseidon4 as poseidon42 } from "poseidon-lite";
|
|
3080
|
-
function
|
|
3081
|
-
|
|
3921
|
+
function computeNullifier(commitment, spendingKey) {
|
|
3922
|
+
return poseidon22([commitment, spendingKey]);
|
|
3923
|
+
}
|
|
3924
|
+
function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
|
|
3925
|
+
return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk).note;
|
|
3926
|
+
}
|
|
3927
|
+
function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
|
|
3928
|
+
if (!commitment.encryptedMemo) return { note: null, reason: "no_memo" };
|
|
3082
3929
|
let commitmentBytes;
|
|
3083
3930
|
let memoBytes;
|
|
3084
3931
|
try {
|
|
3085
3932
|
commitmentBytes = fromHex(commitment.commitmentHex);
|
|
3086
3933
|
memoBytes = fromHex(commitment.encryptedMemo);
|
|
3087
3934
|
} catch {
|
|
3088
|
-
return null;
|
|
3935
|
+
return { note: null, reason: "hex_parse_error" };
|
|
3936
|
+
}
|
|
3937
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) {
|
|
3938
|
+
return {
|
|
3939
|
+
note: null,
|
|
3940
|
+
reason: `memo_size_mismatch:got_${memoBytes.length}_expected_${ENCRYPTED_MEMO_SIZE}`
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
|
|
3944
|
+
if (!plaintext) return { note: null, reason: "decrypt_failed:wrong_key_or_corrupt_mac" };
|
|
3945
|
+
let effectiveOwnerPk = plaintext.ownerPk;
|
|
3946
|
+
let effectiveSpendingKey = spendingKey;
|
|
3947
|
+
if (ownOwnerPk !== 0n && plaintext.ownerPk !== ownOwnerPk) {
|
|
3948
|
+
const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
|
|
3949
|
+
if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
|
|
3950
|
+
const ownPkPoint = recoverOwnerPkPoint(ownOwnerPk);
|
|
3951
|
+
if (!ownPkPoint) return { note: null, reason: "stealth_invalid_own_owner_pk" };
|
|
3952
|
+
const stealthOwnerPk = deriveStealthOwnerPk(sharedSecret, ownOwnerPk, ownPkPoint);
|
|
3953
|
+
if (stealthOwnerPk !== plaintext.ownerPk) {
|
|
3954
|
+
return { note: null, reason: "commitment_mismatch" };
|
|
3955
|
+
}
|
|
3956
|
+
effectiveOwnerPk = stealthOwnerPk;
|
|
3957
|
+
effectiveSpendingKey = deriveStealthSk(sharedSecret, ownOwnerPk, spendingKey);
|
|
3089
3958
|
}
|
|
3090
|
-
const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingKey);
|
|
3091
|
-
if (!plaintext) return null;
|
|
3092
3959
|
const recomputed = poseidon42([
|
|
3093
3960
|
plaintext.value,
|
|
3094
3961
|
plaintext.assetId,
|
|
3095
|
-
|
|
3962
|
+
effectiveOwnerPk,
|
|
3096
3963
|
plaintext.blinding
|
|
3097
3964
|
]);
|
|
3098
|
-
if (recomputed !== bytesToBigintLE(commitmentBytes))
|
|
3099
|
-
|
|
3965
|
+
if (recomputed !== bytesToBigintLE(commitmentBytes)) {
|
|
3966
|
+
return { note: null, reason: "commitment_mismatch" };
|
|
3967
|
+
}
|
|
3968
|
+
const nullifier = poseidon22([recomputed, effectiveSpendingKey]);
|
|
3100
3969
|
return {
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3970
|
+
note: {
|
|
3971
|
+
value: plaintext.value,
|
|
3972
|
+
assetId: plaintext.assetId,
|
|
3973
|
+
ownerPk: effectiveOwnerPk,
|
|
3974
|
+
blinding: plaintext.blinding,
|
|
3975
|
+
spendingKey: effectiveSpendingKey,
|
|
3976
|
+
spent: false,
|
|
3977
|
+
spentAt: null,
|
|
3978
|
+
commitment: recomputed,
|
|
3979
|
+
nullifier,
|
|
3980
|
+
commitmentHex: toHex(bigintTo32Le(recomputed)),
|
|
3981
|
+
nullifierHex: toHex(bigintTo32Le(nullifier)),
|
|
3982
|
+
memo: Array.from(memoBytes),
|
|
3983
|
+
counterpartyPk: plaintext.counterpartyPk
|
|
3984
|
+
}
|
|
3113
3985
|
};
|
|
3114
3986
|
}
|
|
3115
3987
|
|
|
3116
|
-
// src/shielded-pool/
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3988
|
+
// src/shielded-pool/protocol/coinSelection.ts
|
|
3989
|
+
var TRANSFER_TREE_DEPTH = 20;
|
|
3990
|
+
function selectNotes(notes, needed) {
|
|
3991
|
+
const unspent = notes.filter((n) => !n.spent && n.value > 0n);
|
|
3992
|
+
const sorted = [...unspent].sort((a, b) => a.value < b.value ? -1 : 1);
|
|
3993
|
+
const single = sorted.find((n) => n.value >= needed);
|
|
3994
|
+
if (single) return [single, null];
|
|
3995
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
3996
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
3997
|
+
const a = sorted[i];
|
|
3998
|
+
const b = sorted[j];
|
|
3999
|
+
if (a !== void 0 && b !== void 0 && a.value + b.value >= needed) {
|
|
4000
|
+
return [a, b];
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
return null;
|
|
4005
|
+
}
|
|
4006
|
+
function buildDummyTransferInput(assetId) {
|
|
4007
|
+
const zeroSibling = "0x" + "00".repeat(32);
|
|
4008
|
+
return {
|
|
4009
|
+
nullifier: 0n,
|
|
4010
|
+
// Constraint 9: nullifier * is_dummy.out === 0 → must be 0
|
|
4011
|
+
value: 0n,
|
|
4012
|
+
// triggers is_dummy[i].out = 1 in the circuit
|
|
4013
|
+
assetId,
|
|
4014
|
+
// must match real note (Constraint 7)
|
|
4015
|
+
ownerPk: 0n,
|
|
4016
|
+
blinding: 0n,
|
|
4017
|
+
spendingKey: 1n,
|
|
4018
|
+
// arbitrary; EdDSA is disabled (enabled = 0) for dummy inputs
|
|
4019
|
+
pathSiblings: Array(TRANSFER_TREE_DEPTH).fill(zeroSibling),
|
|
4020
|
+
leafIndex: 0
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
3120
4023
|
|
|
3121
|
-
// src/shielded-pool/
|
|
3122
|
-
|
|
4024
|
+
// src/shielded-pool/protocol/disclosure.ts
|
|
4025
|
+
import {
|
|
4026
|
+
generateDisclosureProof
|
|
4027
|
+
} from "@orbinum/proof-generator";
|
|
4028
|
+
import { mulPointEscalar as mulPointEscalar5, Base8 as Base83 } from "@zk-kit/baby-jubjub";
|
|
4029
|
+
import { poseidon1, poseidon3 } from "poseidon-lite";
|
|
4030
|
+
function hexFieldToBytes32(hex) {
|
|
4031
|
+
const clean = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
|
|
4032
|
+
return fromHex(clean.padStart(64, "0"));
|
|
4033
|
+
}
|
|
4034
|
+
function deriveBabyJubjubKeypair(substrateSigningKey) {
|
|
4035
|
+
let keyBigInt = 0n;
|
|
4036
|
+
for (let i = 0; i < substrateSigningKey.length; i++) {
|
|
4037
|
+
keyBigInt = keyBigInt << 8n | BigInt(substrateSigningKey[i]);
|
|
4038
|
+
}
|
|
4039
|
+
const sk = poseidon1([keyBigInt]);
|
|
4040
|
+
const pk = mulPointEscalar5(Base83, sk);
|
|
4041
|
+
return { sk, pkX: pk[0], pkY: pk[1] };
|
|
4042
|
+
}
|
|
4043
|
+
function buildDisclosurePublicSignals(commitment, auditorPkX, auditorPkY, proofOutput) {
|
|
4044
|
+
const buf = new Uint8Array(256);
|
|
4045
|
+
buf.set(hexFieldToBytes32(commitment), 0);
|
|
4046
|
+
buf.set(bigintTo32Le(auditorPkX), 32);
|
|
4047
|
+
buf.set(bigintTo32Le(auditorPkY), 64);
|
|
4048
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.epkX), 96);
|
|
4049
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.epkY), 128);
|
|
4050
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.encValue), 160);
|
|
4051
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.encAssetId), 192);
|
|
4052
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.encOwnerHash), 224);
|
|
4053
|
+
return Array.from(buf);
|
|
4054
|
+
}
|
|
4055
|
+
function decryptDisclosureSignals(auditorBjjSk, enc) {
|
|
4056
|
+
const shared = mulPointEscalar5([enc.epkX, enc.epkY], auditorBjjSk);
|
|
4057
|
+
const sharedX = shared[0];
|
|
4058
|
+
const sharedY = shared[1];
|
|
4059
|
+
const k0 = poseidon3([sharedX, sharedY, 0n]);
|
|
4060
|
+
const k1 = poseidon3([sharedX, sharedY, 1n]);
|
|
4061
|
+
const k2 = poseidon3([sharedX, sharedY, 2n]);
|
|
4062
|
+
return {
|
|
4063
|
+
value: (enc.encValue - k0 + BN254_R) % BN254_R,
|
|
4064
|
+
assetId: (enc.encAssetId - k1 + BN254_R) % BN254_R,
|
|
4065
|
+
ownerHash: (enc.encOwnerHash - k2 + BN254_R) % BN254_R
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
3123
4068
|
|
|
3124
|
-
// src/
|
|
4069
|
+
// src/utils/blinding.ts
|
|
4070
|
+
function randomBlinding() {
|
|
4071
|
+
const buf = new Uint8Array(32);
|
|
4072
|
+
crypto.getRandomValues(buf);
|
|
4073
|
+
const n = bytesToBigintLE(buf);
|
|
4074
|
+
return n === 0n ? 1n : n % BN254_R;
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
// src/privacy-keys/PrivacyKeys.ts
|
|
4078
|
+
import { hkdf as hkdf2 } from "@noble/hashes/hkdf.js";
|
|
4079
|
+
import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
|
|
4080
|
+
import { mulPointEscalar as mulPointEscalar6, Base8 as Base84, packPoint as packPoint2 } from "@zk-kit/baby-jubjub";
|
|
3125
4081
|
var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
|
|
3126
4082
|
function deriveSpendingKeyMessage(chainId, address) {
|
|
3127
4083
|
return `orbinum-spending-key-v1
|
|
3128
4084
|
${chainId}
|
|
3129
4085
|
${address.toLowerCase()}`;
|
|
3130
4086
|
}
|
|
3131
|
-
async function
|
|
3132
|
-
const
|
|
3133
|
-
const sigBytes = new Uint8Array((hex.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
|
|
4087
|
+
async function deriveMasterKeyBytes(signatureHex, chainId, address) {
|
|
4088
|
+
const sigBytes = fromHex(signatureHex);
|
|
3134
4089
|
const info = new TextEncoder().encode(`orbinum-sk-v1:${chainId}:${address.toLowerCase()}`);
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
4090
|
+
return hkdf2(sha2563, sigBytes, new Uint8Array(0), info, 32);
|
|
4091
|
+
}
|
|
4092
|
+
async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
|
|
4093
|
+
const masterBytes = await deriveMasterKeyBytes(signatureHex, chainId, address);
|
|
4094
|
+
const skBigint = BigInt(toHex(masterBytes)) % BABYJUB_SUBORDER;
|
|
3139
4095
|
return skBigint === 0n ? 1n : skBigint;
|
|
3140
4096
|
}
|
|
3141
|
-
function
|
|
4097
|
+
function deriveViewingSecretKey(spendingKey) {
|
|
3142
4098
|
const ikm = bigintTo32Le(spendingKey);
|
|
3143
|
-
return
|
|
4099
|
+
return hkdf2(sha2563, ikm, void 0, IVK_DOMAIN, 32);
|
|
4100
|
+
}
|
|
4101
|
+
function deriveViewingPublicKey(ivsk) {
|
|
4102
|
+
const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
|
|
4103
|
+
const ivkPoint = mulPointEscalar6(Base84, ivskScalar);
|
|
4104
|
+
const packed = packPoint2(ivkPoint);
|
|
4105
|
+
return bigintTo32Le(packed);
|
|
3144
4106
|
}
|
|
3145
4107
|
function deriveOwnerPk(spendingKey) {
|
|
3146
4108
|
try {
|
|
3147
|
-
const pubPoint =
|
|
4109
|
+
const pubPoint = mulPointEscalar6(Base84, spendingKey);
|
|
3148
4110
|
return pubPoint[0];
|
|
3149
4111
|
} catch {
|
|
3150
4112
|
return 0n;
|
|
3151
4113
|
}
|
|
3152
4114
|
}
|
|
3153
4115
|
|
|
3154
|
-
// src/
|
|
4116
|
+
// src/privacy-keys/PrivacyKeyManager.ts
|
|
3155
4117
|
var PrivacyKeyManager = class {
|
|
3156
4118
|
_state = {
|
|
3157
4119
|
spendingKey: null,
|
|
3158
|
-
|
|
4120
|
+
masterBytes: null,
|
|
4121
|
+
viewingSecretKey: null,
|
|
4122
|
+
viewingPublicKeyPacked: null,
|
|
3159
4123
|
ownerPk: null
|
|
3160
4124
|
};
|
|
3161
4125
|
/**
|
|
3162
|
-
* Load a spending key into the in-memory session.
|
|
3163
|
-
* Derives
|
|
4126
|
+
* Load a spending key and its corresponding master bytes into the in-memory session.
|
|
4127
|
+
* Derives viewingSecretKey, viewingPublicKeyPacked, and ownerPk immediately.
|
|
3164
4128
|
* Replaces any previously loaded key.
|
|
4129
|
+
*
|
|
4130
|
+
* @param spendingKey Circuit scalar: BigInt(masterBytes) % BABYJUB_SUBORDER, clamped to [1, ∞).
|
|
4131
|
+
* @param masterBytes Raw 32-byte HKDF output before modular reduction. Used to derive
|
|
4132
|
+
* the stable vault key (HKDF(masterBytes, info="orbinum-vault-key-v1")).
|
|
3165
4133
|
*/
|
|
3166
|
-
async load(spendingKey) {
|
|
3167
|
-
const
|
|
4134
|
+
async load(spendingKey, masterBytes) {
|
|
4135
|
+
const viewingSecretKey = deriveViewingSecretKey(spendingKey);
|
|
4136
|
+
const viewingPublicKeyPacked = deriveViewingPublicKey(viewingSecretKey);
|
|
3168
4137
|
const ownerPk = deriveOwnerPk(spendingKey);
|
|
3169
|
-
this._state = {
|
|
4138
|
+
this._state = {
|
|
4139
|
+
spendingKey,
|
|
4140
|
+
masterBytes,
|
|
4141
|
+
viewingSecretKey,
|
|
4142
|
+
viewingPublicKeyPacked,
|
|
4143
|
+
ownerPk
|
|
4144
|
+
};
|
|
3170
4145
|
}
|
|
3171
4146
|
/** Clear all key material from memory. Call on vault lock / sign-out. */
|
|
3172
4147
|
clear() {
|
|
3173
|
-
this._state = {
|
|
4148
|
+
this._state = {
|
|
4149
|
+
spendingKey: null,
|
|
4150
|
+
masterBytes: null,
|
|
4151
|
+
viewingSecretKey: null,
|
|
4152
|
+
viewingPublicKeyPacked: null,
|
|
4153
|
+
ownerPk: null
|
|
4154
|
+
};
|
|
3174
4155
|
}
|
|
3175
4156
|
/** Returns true if a spending key has been loaded. */
|
|
3176
4157
|
isLoaded() {
|
|
@@ -3183,12 +4164,29 @@ var PrivacyKeyManager = class {
|
|
|
3183
4164
|
}
|
|
3184
4165
|
return this._state.spendingKey;
|
|
3185
4166
|
}
|
|
3186
|
-
/**
|
|
3187
|
-
|
|
3188
|
-
|
|
4167
|
+
/**
|
|
4168
|
+
* Returns the 32-byte viewing secret key (ivsk).
|
|
4169
|
+
* Used internally for decrypting received notes during rescan.
|
|
4170
|
+
* SECURITY: never expose this in addresses or network requests.
|
|
4171
|
+
* Throws if not loaded.
|
|
4172
|
+
*/
|
|
4173
|
+
getViewingSecretKey() {
|
|
4174
|
+
if (this._state.viewingSecretKey === null) {
|
|
4175
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
4176
|
+
}
|
|
4177
|
+
return this._state.viewingSecretKey;
|
|
4178
|
+
}
|
|
4179
|
+
/**
|
|
4180
|
+
* Returns the 32-byte LE-encoded packed BJJ viewing public key (ivk).
|
|
4181
|
+
* This is the component embedded in the privacy address and passed to senders
|
|
4182
|
+
* so they can encrypt memos only the recipient can decrypt.
|
|
4183
|
+
* Throws if not loaded.
|
|
4184
|
+
*/
|
|
4185
|
+
getViewingPublicKeyPacked() {
|
|
4186
|
+
if (this._state.viewingPublicKeyPacked === null) {
|
|
3189
4187
|
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
3190
4188
|
}
|
|
3191
|
-
return this._state.
|
|
4189
|
+
return this._state.viewingPublicKeyPacked;
|
|
3192
4190
|
}
|
|
3193
4191
|
/** Returns the BabyJubJub owner public key (x-coordinate). Throws if not loaded. */
|
|
3194
4192
|
getOwnerPk() {
|
|
@@ -3201,24 +4199,86 @@ var PrivacyKeyManager = class {
|
|
|
3201
4199
|
getSpendingKeyBytes() {
|
|
3202
4200
|
return bigintTo32Le(this.getSpendingKey());
|
|
3203
4201
|
}
|
|
3204
|
-
/**
|
|
4202
|
+
/**
|
|
4203
|
+
* Returns the 32-byte master key bytes (pre-modulus HKDF output).
|
|
4204
|
+
* Used to derive the stable vault AES key. Throws if not loaded.
|
|
4205
|
+
*/
|
|
4206
|
+
getMasterBytes() {
|
|
4207
|
+
if (this._state.masterBytes === null) {
|
|
4208
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
4209
|
+
}
|
|
4210
|
+
return this._state.masterBytes;
|
|
4211
|
+
}
|
|
4212
|
+
/**
|
|
4213
|
+
* Exports the master key bytes as a "mk:0x{hex}" string.
|
|
4214
|
+
* Storing masterBytes (not the sk scalar) ensures the vault key and
|
|
4215
|
+
* rescan can always be reconstructed regardless of any future modulus change.
|
|
4216
|
+
* Throws if not loaded.
|
|
4217
|
+
*/
|
|
3205
4218
|
exportHex() {
|
|
3206
|
-
|
|
4219
|
+
const mb = this.getMasterBytes();
|
|
4220
|
+
return "mk:0x" + Array.from(mb, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4221
|
+
}
|
|
4222
|
+
/**
|
|
4223
|
+
* Exports a shareable privacy address encoding the owner public key and
|
|
4224
|
+
* viewing PUBLIC key of the currently loaded identity.
|
|
4225
|
+
*
|
|
4226
|
+
* Format: `orbpriv1:{ownerPk_hex}:{viewingPublicKey_hex}`
|
|
4227
|
+
*
|
|
4228
|
+
* The recipient uses this address so the sender can:
|
|
4229
|
+
* 1. Embed `ownerPk` in the note commitment (Poseidon4 input).
|
|
4230
|
+
* 2. Encrypt the memo via ECDH with the recipient's `viewingPublicKey`.
|
|
4231
|
+
*
|
|
4232
|
+
* SECURITY: Only the viewing PUBLIC key is embedded — the viewing secret key
|
|
4233
|
+
* (used for decryption) is never exported. Holders of this address cannot
|
|
4234
|
+
* decrypt the recipient's notes.
|
|
4235
|
+
*
|
|
4236
|
+
* Throws if no key is loaded.
|
|
4237
|
+
*/
|
|
4238
|
+
encodePrivacyAddress() {
|
|
4239
|
+
const ownerPk = this.getOwnerPk();
|
|
4240
|
+
const ivkPacked = this.getViewingPublicKeyPacked();
|
|
4241
|
+
const ownerPkHex = "0x" + ownerPk.toString(16).padStart(64, "0");
|
|
4242
|
+
const ivkHex = "0x" + Array.from(ivkPacked, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4243
|
+
return `orbpriv1:${ownerPkHex}:${ivkHex}`;
|
|
3207
4244
|
}
|
|
3208
4245
|
/**
|
|
3209
|
-
*
|
|
3210
|
-
*
|
|
4246
|
+
* Decode a privacy address of the form `orbpriv1:{ownerPk_hex}:{viewingPublicKey_hex}`.
|
|
4247
|
+
* Returns `{ ownerPkHex, viewingPublicKeyHex }` on success, or `null` if the input
|
|
4248
|
+
* does not match the expected format.
|
|
4249
|
+
*/
|
|
4250
|
+
static decodePrivacyAddress(address) {
|
|
4251
|
+
if (!address.startsWith("orbpriv1:")) return null;
|
|
4252
|
+
const parts = address.split(":");
|
|
4253
|
+
if (parts.length !== 3) return null;
|
|
4254
|
+
const ownerPkHex = parts[1];
|
|
4255
|
+
const viewingPublicKeyHex = parts[2];
|
|
4256
|
+
if (!ownerPkHex || !viewingPublicKeyHex) return null;
|
|
4257
|
+
return { ownerPkHex, viewingPublicKeyHex };
|
|
4258
|
+
}
|
|
4259
|
+
/**
|
|
4260
|
+
* Load keys from a cached "mk:0x{masterBytes_hex}" string produced by exportHex().
|
|
4261
|
+
* Throws if the format is invalid or masterBytes length is not 32 bytes.
|
|
3211
4262
|
*/
|
|
3212
4263
|
async importFromHex(hex) {
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
4264
|
+
if (!hex.startsWith("mk:")) {
|
|
4265
|
+
throw new Error(
|
|
4266
|
+
'PrivacyKeyManager: invalid cache format. Expected "mk:0x{masterBytes_hex}".'
|
|
4267
|
+
);
|
|
4268
|
+
}
|
|
4269
|
+
const raw = hex.slice(3);
|
|
4270
|
+
const h = raw.startsWith("0x") ? raw.slice(2) : raw;
|
|
4271
|
+
const masterBytes = new Uint8Array((h.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
|
|
4272
|
+
if (masterBytes.length !== 32) {
|
|
4273
|
+
throw new Error("PrivacyKeyManager: invalid master bytes \u2014 expected 32 bytes.");
|
|
3216
4274
|
}
|
|
3217
|
-
|
|
4275
|
+
const masterBigint = BigInt("0x" + h);
|
|
4276
|
+
const sk = masterBigint % BABYJUB_SUBORDER || 1n;
|
|
4277
|
+
await this.load(sk, masterBytes);
|
|
3218
4278
|
}
|
|
3219
4279
|
};
|
|
3220
4280
|
|
|
3221
|
-
// src/
|
|
4281
|
+
// src/vault/VaultJson.ts
|
|
3222
4282
|
function vaultReplacer(_key, value) {
|
|
3223
4283
|
if (typeof value === "bigint") return { __bigint: value.toString() };
|
|
3224
4284
|
return value;
|
|
@@ -3229,16 +4289,28 @@ function vaultReviver(_key, value) {
|
|
|
3229
4289
|
}
|
|
3230
4290
|
return value;
|
|
3231
4291
|
}
|
|
4292
|
+
|
|
4293
|
+
// src/utils/encoding.ts
|
|
4294
|
+
function toBase64(buf) {
|
|
4295
|
+
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
4296
|
+
let str = "";
|
|
4297
|
+
for (const b of bytes) str += String.fromCharCode(b);
|
|
4298
|
+
return btoa(str);
|
|
4299
|
+
}
|
|
4300
|
+
function fromBase64(b64) {
|
|
4301
|
+
const bin = atob(b64);
|
|
4302
|
+
const out = new Uint8Array(bin.length);
|
|
4303
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
4304
|
+
return out;
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
// src/vault/VaultCrypto.ts
|
|
3232
4308
|
var VAULT_KEY_INFO = new TextEncoder().encode("orbinum-vault-key-v1");
|
|
3233
4309
|
var IV_BYTES = 12;
|
|
3234
|
-
async function deriveVaultKey(
|
|
3235
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
3236
|
-
"
|
|
3237
|
-
|
|
3238
|
-
"HKDF",
|
|
3239
|
-
false,
|
|
3240
|
-
["deriveKey"]
|
|
3241
|
-
);
|
|
4310
|
+
async function deriveVaultKey(masterBytes) {
|
|
4311
|
+
const keyMaterial = await crypto.subtle.importKey("raw", masterBytes.slice(0), "HKDF", false, [
|
|
4312
|
+
"deriveKey"
|
|
4313
|
+
]);
|
|
3242
4314
|
return crypto.subtle.deriveKey(
|
|
3243
4315
|
{
|
|
3244
4316
|
name: "HKDF",
|
|
@@ -3267,6 +4339,177 @@ async function decryptJson(key, iv, ciphertext) {
|
|
|
3267
4339
|
return JSON.parse(new TextDecoder().decode(plainBuf), vaultReviver);
|
|
3268
4340
|
}
|
|
3269
4341
|
|
|
4342
|
+
// src/vault/errors.ts
|
|
4343
|
+
var VaultLockedError = class extends Error {
|
|
4344
|
+
constructor(message = "Vault is locked. Connect your wallet to unlock it.") {
|
|
4345
|
+
super(message);
|
|
4346
|
+
this.name = "VaultLockedError";
|
|
4347
|
+
}
|
|
4348
|
+
};
|
|
4349
|
+
|
|
4350
|
+
// src/vault/noteOps.ts
|
|
4351
|
+
function applyNoteStatus(note, status) {
|
|
4352
|
+
return {
|
|
4353
|
+
...note,
|
|
4354
|
+
spent: status?.spent ?? note.spent ?? false,
|
|
4355
|
+
spentAt: status?.spentAt ?? note.spentAt ?? null
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4358
|
+
async function encryptNote(key, note) {
|
|
4359
|
+
const { iv, ciphertext } = await encryptJson(key, note);
|
|
4360
|
+
return {
|
|
4361
|
+
commitmentHex: note.commitmentHex,
|
|
4362
|
+
iv,
|
|
4363
|
+
ciphertext,
|
|
4364
|
+
nullifierHex: note.nullifierHex,
|
|
4365
|
+
assetId: note.assetId.toString(),
|
|
4366
|
+
spent: note.spent,
|
|
4367
|
+
spentAt: note.spentAt,
|
|
4368
|
+
updatedAt: Date.now()
|
|
4369
|
+
};
|
|
4370
|
+
}
|
|
4371
|
+
async function decryptNoteRecord(key, rec) {
|
|
4372
|
+
const note = await decryptJson(key, rec.iv, rec.ciphertext);
|
|
4373
|
+
return applyNoteStatus(note, {
|
|
4374
|
+
...rec.spent !== void 0 && { spent: rec.spent },
|
|
4375
|
+
spentAt: rec.spentAt ?? null
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
// src/proof-generator/unshield.ts
|
|
4380
|
+
import {
|
|
4381
|
+
CircuitType,
|
|
4382
|
+
generateProof,
|
|
4383
|
+
WebArtifactProvider
|
|
4384
|
+
} from "@orbinum/proof-generator";
|
|
4385
|
+
import { randomBytes as randomBytes3 } from "@noble/ciphers/utils.js";
|
|
4386
|
+
import { mulPointEscalar as mulPointEscalar7, Base8 as Base85 } from "@zk-kit/baby-jubjub";
|
|
4387
|
+
import { poseidon4 as poseidon43 } from "poseidon-lite";
|
|
4388
|
+
|
|
4389
|
+
// src/proof-generator/merkle.ts
|
|
4390
|
+
function merkleProofToCircuit(siblings, leafIndex) {
|
|
4391
|
+
const elements = siblings.map((h) => leHexToBigint(h).toString());
|
|
4392
|
+
const depth = siblings.length;
|
|
4393
|
+
const indices = computePathIndices(leafIndex, depth).map(String);
|
|
4394
|
+
return { elements, indices };
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
// src/proof-generator/unshield.ts
|
|
4398
|
+
async function generateUnshieldProof(inputs, options = {}) {
|
|
4399
|
+
const { elements, indices } = merkleProofToCircuit(inputs.pathSiblings, inputs.leafIndex);
|
|
4400
|
+
const fee = inputs.fee ?? 0n;
|
|
4401
|
+
const changeValue = inputs.changeValue ?? 0n;
|
|
4402
|
+
const noteValue = inputs.amount + fee + changeValue;
|
|
4403
|
+
if (inputs.amount <= 0n) {
|
|
4404
|
+
throw new Error("Unshield amount must be greater than zero.");
|
|
4405
|
+
}
|
|
4406
|
+
if (changeValue < 0n) {
|
|
4407
|
+
throw new Error("changeValue must be >= 0.");
|
|
4408
|
+
}
|
|
4409
|
+
const changeOwnerPubkey = inputs.changeOwnerPubkey ?? mulPointEscalar7(Base85, inputs.spendingKey)[0];
|
|
4410
|
+
const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE(randomBytes3(32)) : 0n);
|
|
4411
|
+
const changeCommitment = changeValue > 0n ? poseidon43([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
|
|
4412
|
+
const circuitInputs = {
|
|
4413
|
+
merkle_root: leHexToBigint(inputs.merkleRoot).toString(),
|
|
4414
|
+
nullifier: inputs.nullifier.toString(),
|
|
4415
|
+
amount: inputs.amount.toString(),
|
|
4416
|
+
recipient: inputs.recipient.toString(),
|
|
4417
|
+
asset_id: inputs.assetId.toString(),
|
|
4418
|
+
fee: fee.toString(),
|
|
4419
|
+
change_commitment: changeCommitment.toString(),
|
|
4420
|
+
note_value: noteValue.toString(),
|
|
4421
|
+
note_asset_id: inputs.assetId.toString(),
|
|
4422
|
+
note_blinding: inputs.blinding.toString(),
|
|
4423
|
+
spending_key: inputs.spendingKey.toString(),
|
|
4424
|
+
path_elements: elements,
|
|
4425
|
+
path_indices: indices,
|
|
4426
|
+
change_value: changeValue.toString(),
|
|
4427
|
+
change_blinding: changeBlinding.toString(),
|
|
4428
|
+
change_owner_pubkey: changeOwnerPubkey.toString()
|
|
4429
|
+
};
|
|
4430
|
+
const provider = options.provider ?? new WebArtifactProvider();
|
|
4431
|
+
const opts = { provider };
|
|
4432
|
+
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4433
|
+
const proofResult = await generateProof(CircuitType.Unshield, circuitInputs, opts);
|
|
4434
|
+
return { ...proofResult, changeCommitment, changeValue, changeBlinding, changeOwnerPubkey };
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4437
|
+
// src/proof-generator/transfer.ts
|
|
4438
|
+
import {
|
|
4439
|
+
CircuitType as CircuitType2,
|
|
4440
|
+
generateProof as generateProof2,
|
|
4441
|
+
WebArtifactProvider as WebArtifactProvider2
|
|
4442
|
+
} from "@orbinum/proof-generator";
|
|
4443
|
+
async function generateTransferProof(params, options = {}) {
|
|
4444
|
+
const root = leHexToBigint(params.merkleRoot).toString();
|
|
4445
|
+
const [i0, i1] = params.inputs;
|
|
4446
|
+
const [o0, o1] = params.outputs;
|
|
4447
|
+
const fee = params.fee ?? 0n;
|
|
4448
|
+
const path0 = merkleProofToCircuit(i0.pathSiblings, i0.leafIndex);
|
|
4449
|
+
const path1 = merkleProofToCircuit(i1.pathSiblings, i1.leafIndex);
|
|
4450
|
+
const circuitInputs = {
|
|
4451
|
+
merkle_root: root,
|
|
4452
|
+
nullifiers: [i0.nullifier.toString(), i1.nullifier.toString()],
|
|
4453
|
+
commitments: [o0.commitment.toString(), o1.commitment.toString()],
|
|
4454
|
+
asset_id: i0.assetId.toString(),
|
|
4455
|
+
fee: fee.toString(),
|
|
4456
|
+
input_values: [i0.value.toString(), i1.value.toString()],
|
|
4457
|
+
input_asset_ids: [i0.assetId.toString(), i1.assetId.toString()],
|
|
4458
|
+
input_blindings: [i0.blinding.toString(), i1.blinding.toString()],
|
|
4459
|
+
spending_keys: [i0.spendingKey.toString(), i1.spendingKey.toString()],
|
|
4460
|
+
input_path_elements: [path0.elements, path1.elements],
|
|
4461
|
+
input_path_indices: [path0.indices, path1.indices],
|
|
4462
|
+
output_values: [o0.value.toString(), o1.value.toString()],
|
|
4463
|
+
output_asset_ids: [o0.assetId.toString(), o1.assetId.toString()],
|
|
4464
|
+
output_owner_pubkeys: [o0.ownerPk.toString(), o1.ownerPk.toString()],
|
|
4465
|
+
output_blindings: [o0.blinding.toString(), o1.blinding.toString()]
|
|
4466
|
+
};
|
|
4467
|
+
const provider = options.provider ?? new WebArtifactProvider2();
|
|
4468
|
+
const opts = { provider };
|
|
4469
|
+
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4470
|
+
return generateProof2(CircuitType2.Transfer, circuitInputs, opts);
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
// src/proof-generator/fee-claim.ts
|
|
4474
|
+
import {
|
|
4475
|
+
generateDisclosureProof as generateDisclosureProof2
|
|
4476
|
+
} from "@orbinum/proof-generator";
|
|
4477
|
+
function hexSignalToBytes(hex) {
|
|
4478
|
+
const clean = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
|
|
4479
|
+
const padded = clean.padStart(64, "0");
|
|
4480
|
+
const bytes = new Uint8Array(32);
|
|
4481
|
+
for (let i = 0; i < 32; i++) {
|
|
4482
|
+
bytes[i] = parseInt(padded.slice(i * 2, i * 2 + 2), 16);
|
|
4483
|
+
}
|
|
4484
|
+
return bytes;
|
|
4485
|
+
}
|
|
4486
|
+
async function generateFeeClaimProof(inputs, options = {}) {
|
|
4487
|
+
const result = await generateDisclosureProof2(
|
|
4488
|
+
inputs.amount,
|
|
4489
|
+
inputs.ownerPubkey,
|
|
4490
|
+
inputs.blinding,
|
|
4491
|
+
inputs.assetId,
|
|
4492
|
+
inputs.commitment,
|
|
4493
|
+
// Baby Jubjub base point G — placeholder, no real auditor for fee claiming
|
|
4494
|
+
5299619240641551281634865583518297030282874472190772894086521144482721001553n,
|
|
4495
|
+
16950150798460657717958625567821834550301663161624707787222815936182638968203n,
|
|
4496
|
+
1n,
|
|
4497
|
+
// r — placeholder ephemeral scalar (NOT cryptographically secure)
|
|
4498
|
+
{ discloseValue: true, discloseAssetId: true, discloseOwner: false },
|
|
4499
|
+
options
|
|
4500
|
+
);
|
|
4501
|
+
const [, , sigEncValue, sigEncAssetId, sigEncOwnerHash, sigCommitment] = result.publicSignals.map(hexSignalToBytes);
|
|
4502
|
+
const compact = new Uint8Array(76);
|
|
4503
|
+
compact.set(sigCommitment);
|
|
4504
|
+
compact.set(sigEncValue.subarray(0, 8), 32);
|
|
4505
|
+
compact.set(sigEncAssetId.subarray(0, 4), 40);
|
|
4506
|
+
compact.set(sigEncOwnerHash, 44);
|
|
4507
|
+
return {
|
|
4508
|
+
proof: result.proof,
|
|
4509
|
+
publicSignals: Array.from(compact)
|
|
4510
|
+
};
|
|
4511
|
+
}
|
|
4512
|
+
|
|
3270
4513
|
// src/account-mapping/types/index.ts
|
|
3271
4514
|
var SignatureScheme = {
|
|
3272
4515
|
Eip191: "Eip191",
|
|
@@ -3283,7 +4526,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
3283
4526
|
if (!fnSig) return null;
|
|
3284
4527
|
if (fnSig.startsWith("registerAlias")) {
|
|
3285
4528
|
try {
|
|
3286
|
-
const data =
|
|
4529
|
+
const data = fromHex(input.slice(10));
|
|
3287
4530
|
const alias = decodeString(data, 0);
|
|
3288
4531
|
return { fnSig, args: { alias } };
|
|
3289
4532
|
} catch {
|
|
@@ -3292,37 +4535,43 @@ function decodePrecompileCalldata(address, input) {
|
|
|
3292
4535
|
}
|
|
3293
4536
|
if (fnSig.startsWith("shield(")) {
|
|
3294
4537
|
try {
|
|
3295
|
-
const data =
|
|
4538
|
+
const data = fromHex(input.slice(10));
|
|
3296
4539
|
const assetId = decodeUint(data, 0);
|
|
3297
|
-
const
|
|
3298
|
-
|
|
3299
|
-
return { fnSig, args: { assetId, amount, commitment } };
|
|
4540
|
+
const commitment = toHex(data.slice(32, 64));
|
|
4541
|
+
return { fnSig, args: { assetId, commitment } };
|
|
3300
4542
|
} catch {
|
|
3301
4543
|
return { fnSig, args: {} };
|
|
3302
4544
|
}
|
|
3303
4545
|
}
|
|
3304
4546
|
if (fnSig.startsWith("unshield(")) {
|
|
3305
4547
|
try {
|
|
3306
|
-
const data =
|
|
4548
|
+
const data = fromHex(input.slice(10));
|
|
3307
4549
|
const root = toHex(data.slice(32, 64));
|
|
3308
4550
|
const nullifier = toHex(data.slice(64, 96));
|
|
3309
4551
|
const assetId = decodeUint(data, 96);
|
|
3310
4552
|
const amount = decodeUint(data, 128);
|
|
3311
4553
|
const recipient = toHex(data.slice(160, 192));
|
|
3312
|
-
|
|
4554
|
+
const fee = decodeUint(data, 192);
|
|
4555
|
+
const changeCommitment = toHex(data.slice(224, 256));
|
|
4556
|
+
return {
|
|
4557
|
+
fnSig,
|
|
4558
|
+
args: { root, nullifier, assetId, amount, recipient, fee, changeCommitment }
|
|
4559
|
+
};
|
|
3313
4560
|
} catch {
|
|
3314
4561
|
return { fnSig, args: {} };
|
|
3315
4562
|
}
|
|
3316
4563
|
}
|
|
3317
4564
|
if (fnSig.startsWith("privateTransfer(")) {
|
|
3318
4565
|
try {
|
|
3319
|
-
const data =
|
|
4566
|
+
const data = fromHex(input.slice(10));
|
|
3320
4567
|
const root = toHex(data.slice(32, 64));
|
|
3321
4568
|
const nullOffset = Number(decodeUint(data, 64));
|
|
3322
4569
|
const commOffset = Number(decodeUint(data, 96));
|
|
3323
4570
|
const nullifiers = Number(decodeUint(data, nullOffset));
|
|
3324
4571
|
const commitments = Number(decodeUint(data, commOffset));
|
|
3325
|
-
|
|
4572
|
+
const assetId = decodeUint(data, 160);
|
|
4573
|
+
const fee = decodeUint(data, 192);
|
|
4574
|
+
return { fnSig, args: { root, nullifiers, commitments, assetId, fee } };
|
|
3326
4575
|
} catch {
|
|
3327
4576
|
return { fnSig, args: {} };
|
|
3328
4577
|
}
|
|
@@ -3535,7 +4784,11 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3535
4784
|
if (m_norm === "requestdisclosure") {
|
|
3536
4785
|
return {
|
|
3537
4786
|
target: get(0, "target"),
|
|
3538
|
-
|
|
4787
|
+
commitment: get(1, "commitment"),
|
|
4788
|
+
required_fields: get(2, "required_fields"),
|
|
4789
|
+
reason: get(3, "reason"),
|
|
4790
|
+
auditor_bjj_pk_x: get(4, "auditor_bjj_pk_x"),
|
|
4791
|
+
auditor_bjj_pk_y: get(5, "auditor_bjj_pk_y")
|
|
3539
4792
|
};
|
|
3540
4793
|
}
|
|
3541
4794
|
if (m_norm === "disclose") {
|
|
@@ -3549,7 +4802,8 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3549
4802
|
if (m_norm === "rejectdisclosure") {
|
|
3550
4803
|
return {
|
|
3551
4804
|
auditor: get(0, "auditor"),
|
|
3552
|
-
|
|
4805
|
+
commitment: get(1, "commitment"),
|
|
4806
|
+
reason: get(2, "reason")
|
|
3553
4807
|
};
|
|
3554
4808
|
}
|
|
3555
4809
|
if (m_norm === "registerasset") {
|
|
@@ -3572,7 +4826,8 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3572
4826
|
if (m_norm === "pruneexpiredrequest") {
|
|
3573
4827
|
return {
|
|
3574
4828
|
target: get(0, "target"),
|
|
3575
|
-
auditor: get(1, "auditor")
|
|
4829
|
+
auditor: get(1, "auditor"),
|
|
4830
|
+
commitment: get(2, "commitment")
|
|
3576
4831
|
};
|
|
3577
4832
|
}
|
|
3578
4833
|
if (m_norm === "revokedisclosurerecord") {
|
|
@@ -3809,29 +5064,35 @@ function mapZkEventData(method, data) {
|
|
|
3809
5064
|
}
|
|
3810
5065
|
if (m_norm === "disclosed") {
|
|
3811
5066
|
return {
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
5067
|
+
target: get(0, "target"),
|
|
5068
|
+
auditor: get(1, "auditor"),
|
|
5069
|
+
commitment: get(2, "commitment"),
|
|
5070
|
+
signals: get(3, "signals")
|
|
3815
5071
|
};
|
|
3816
5072
|
}
|
|
3817
5073
|
if (m_norm === "disclosurerequested") {
|
|
3818
5074
|
return {
|
|
3819
5075
|
target: get(0, "target"),
|
|
3820
5076
|
auditor: get(1, "auditor"),
|
|
3821
|
-
|
|
5077
|
+
commitment: get(2, "commitment"),
|
|
5078
|
+
required_fields: get(3, "required_fields"),
|
|
5079
|
+
auditor_bjj_pk_x: get(4, "auditor_bjj_pk_x"),
|
|
5080
|
+
auditor_bjj_pk_y: get(5, "auditor_bjj_pk_y")
|
|
3822
5081
|
};
|
|
3823
5082
|
}
|
|
3824
5083
|
if (m_norm === "disclosurerejected") {
|
|
3825
5084
|
return {
|
|
3826
5085
|
target: get(0, "target"),
|
|
3827
5086
|
auditor: get(1, "auditor"),
|
|
3828
|
-
|
|
5087
|
+
commitment: get(2, "commitment"),
|
|
5088
|
+
reason: get(3, "reason")
|
|
3829
5089
|
};
|
|
3830
5090
|
}
|
|
3831
5091
|
if (m_norm === "disclosurerequestexpired") {
|
|
3832
5092
|
return {
|
|
3833
5093
|
target: get(0, "target"),
|
|
3834
|
-
auditor: get(1, "auditor")
|
|
5094
|
+
auditor: get(1, "auditor"),
|
|
5095
|
+
commitment: get(2, "commitment")
|
|
3835
5096
|
};
|
|
3836
5097
|
}
|
|
3837
5098
|
if (m_norm === "disclosurerecordrevoked") {
|
|
@@ -4093,9 +5354,13 @@ export {
|
|
|
4093
5354
|
AccountId2 as AccountId,
|
|
4094
5355
|
AccountMappingModule,
|
|
4095
5356
|
AccountMappingPrecompile,
|
|
5357
|
+
BABYJUB_SUBORDER,
|
|
5358
|
+
BN254_R,
|
|
4096
5359
|
Blake2256,
|
|
4097
5360
|
CircuitId,
|
|
5361
|
+
CircuitType,
|
|
4098
5362
|
CryptoPrecompiles,
|
|
5363
|
+
ENCRYPTED_MEMO_SIZE,
|
|
4099
5364
|
EncryptedMemo,
|
|
4100
5365
|
EvmClient,
|
|
4101
5366
|
EvmExplorer,
|
|
@@ -4108,30 +5373,45 @@ export {
|
|
|
4108
5373
|
PRECOMPILE_ADDR,
|
|
4109
5374
|
PrivacyKeyManager,
|
|
4110
5375
|
PrivacyModule,
|
|
5376
|
+
RelayerStatusModule,
|
|
4111
5377
|
SLIP0044_NAMESPACE,
|
|
4112
5378
|
ShieldedPoolModule,
|
|
4113
5379
|
ShieldedPoolPrecompile,
|
|
4114
5380
|
SignatureScheme,
|
|
4115
5381
|
Storage,
|
|
4116
5382
|
SubstrateClient,
|
|
5383
|
+
VaultLockedError,
|
|
5384
|
+
WebArtifactProvider,
|
|
4117
5385
|
ZkVerifierModule,
|
|
4118
5386
|
accountIdHexToSs58,
|
|
4119
5387
|
addressToAccountIdHex,
|
|
5388
|
+
applyNoteStatus,
|
|
4120
5389
|
base58,
|
|
4121
5390
|
bigintTo32Be,
|
|
4122
5391
|
bigintTo32Le,
|
|
4123
5392
|
bigintTo32LeArr,
|
|
5393
|
+
buildDisclosurePublicSignals,
|
|
5394
|
+
buildDummyTransferInput,
|
|
4124
5395
|
bytesToBigintLE,
|
|
5396
|
+
computeNullifier,
|
|
4125
5397
|
computePathIndices,
|
|
4126
5398
|
connectInjectedExtension,
|
|
4127
5399
|
decodePrecompileCalldata,
|
|
5400
|
+
decryptDisclosureSignals,
|
|
4128
5401
|
decryptJson,
|
|
5402
|
+
decryptNoteRecord,
|
|
5403
|
+
deriveBabyJubjubKeypair,
|
|
5404
|
+
deriveMasterKeyBytes,
|
|
4129
5405
|
deriveOwnerPk,
|
|
4130
5406
|
deriveSpendingKeyFromSignature,
|
|
4131
5407
|
deriveSpendingKeyMessage,
|
|
5408
|
+
deriveStealthOwnerPk,
|
|
5409
|
+
deriveStealthSk,
|
|
4132
5410
|
deriveVaultKey,
|
|
4133
|
-
|
|
5411
|
+
deriveViewingPublicKey,
|
|
5412
|
+
deriveViewingSecretKey,
|
|
4134
5413
|
encryptJson,
|
|
5414
|
+
encryptNote,
|
|
4135
5415
|
ensureHexPrefix,
|
|
4136
5416
|
evmAddressToAccountId,
|
|
4137
5417
|
evmToImplicitSubstrate,
|
|
@@ -4141,6 +5421,10 @@ export {
|
|
|
4141
5421
|
formatORB,
|
|
4142
5422
|
fromBase64,
|
|
4143
5423
|
fromHex,
|
|
5424
|
+
generateDisclosureProof,
|
|
5425
|
+
generateFeeClaimProof,
|
|
5426
|
+
generateTransferProof,
|
|
5427
|
+
generateUnshieldProof,
|
|
4144
5428
|
getInjectedExtensions,
|
|
4145
5429
|
getPolkadotSigner,
|
|
4146
5430
|
getPolkadotSignerFromPjs,
|
|
@@ -4158,6 +5442,9 @@ export {
|
|
|
4158
5442
|
mapExtrinsicArgs,
|
|
4159
5443
|
mapZkEventData,
|
|
4160
5444
|
normalizeEvmAddress,
|
|
5445
|
+
randomBlinding,
|
|
5446
|
+
recoverOwnerPkPoint,
|
|
5447
|
+
selectNotes,
|
|
4161
5448
|
shortHash,
|
|
4162
5449
|
substrateSs58ToAccountIdHex,
|
|
4163
5450
|
substrateToEvm,
|
|
@@ -4166,6 +5453,7 @@ export {
|
|
|
4166
5453
|
toTxResult,
|
|
4167
5454
|
truncateMiddle,
|
|
4168
5455
|
tryDecryptNote,
|
|
5456
|
+
tryDecryptNoteVerbose,
|
|
4169
5457
|
u128,
|
|
4170
5458
|
u64,
|
|
4171
5459
|
vaultReplacer,
|