@orbinum/sdk 0.4.2 → 0.6.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/LICENSE +21 -0
- package/README.md +15 -724
- package/dist/index.d.mts +1310 -550
- package/dist/index.d.ts +1310 -550
- package/dist/index.js +1593 -496
- package/dist/index.mjs +1580 -496
- package/package.json +23 -21
package/dist/index.js
CHANGED
|
@@ -23,9 +23,13 @@ __export(index_exports, {
|
|
|
23
23
|
AccountId: () => import_substrate_bindings3.AccountId,
|
|
24
24
|
AccountMappingModule: () => AccountMappingModule,
|
|
25
25
|
AccountMappingPrecompile: () => AccountMappingPrecompile,
|
|
26
|
+
BABYJUB_SUBORDER: () => BABYJUB_SUBORDER,
|
|
27
|
+
BN254_R: () => BN254_R,
|
|
26
28
|
Blake2256: () => import_substrate_bindings3.Blake2256,
|
|
27
29
|
CircuitId: () => CircuitId,
|
|
30
|
+
CircuitType: () => import_proof_generator.CircuitType,
|
|
28
31
|
CryptoPrecompiles: () => CryptoPrecompiles,
|
|
32
|
+
ENCRYPTED_MEMO_SIZE: () => ENCRYPTED_MEMO_SIZE,
|
|
29
33
|
EncryptedMemo: () => EncryptedMemo,
|
|
30
34
|
EvmClient: () => EvmClient,
|
|
31
35
|
EvmExplorer: () => EvmExplorer,
|
|
@@ -38,30 +42,44 @@ __export(index_exports, {
|
|
|
38
42
|
PRECOMPILE_ADDR: () => PRECOMPILE_ADDR,
|
|
39
43
|
PrivacyKeyManager: () => PrivacyKeyManager,
|
|
40
44
|
PrivacyModule: () => PrivacyModule,
|
|
45
|
+
RelayerStatusModule: () => RelayerStatusModule,
|
|
41
46
|
SLIP0044_NAMESPACE: () => SLIP0044_NAMESPACE,
|
|
42
47
|
ShieldedPoolModule: () => ShieldedPoolModule,
|
|
43
48
|
ShieldedPoolPrecompile: () => ShieldedPoolPrecompile,
|
|
44
49
|
SignatureScheme: () => SignatureScheme,
|
|
45
50
|
Storage: () => import_substrate_bindings3.Storage,
|
|
46
51
|
SubstrateClient: () => SubstrateClient,
|
|
52
|
+
VaultLockedError: () => VaultLockedError,
|
|
53
|
+
WebArtifactProvider: () => import_proof_generator.WebArtifactProvider,
|
|
47
54
|
ZkVerifierModule: () => ZkVerifierModule,
|
|
48
55
|
accountIdHexToSs58: () => accountIdHexToSs58,
|
|
49
56
|
addressToAccountIdHex: () => addressToAccountIdHex,
|
|
57
|
+
applyNoteStatus: () => applyNoteStatus,
|
|
50
58
|
base58: () => import_base.base58,
|
|
51
59
|
bigintTo32Be: () => bigintTo32Be,
|
|
52
60
|
bigintTo32Le: () => bigintTo32Le,
|
|
53
61
|
bigintTo32LeArr: () => bigintTo32LeArr,
|
|
62
|
+
buildDummyTransferInput: () => buildDummyTransferInput,
|
|
54
63
|
bytesToBigintLE: () => bytesToBigintLE,
|
|
64
|
+
computeNullifier: () => computeNullifier,
|
|
55
65
|
computePathIndices: () => computePathIndices,
|
|
56
66
|
connectInjectedExtension: () => import_pjs_signer.connectInjectedExtension,
|
|
67
|
+
createNoteDisclosureKey: () => createNoteDisclosureKey,
|
|
68
|
+
decodeNoteDisclosureKey: () => decodeNoteDisclosureKey,
|
|
57
69
|
decodePrecompileCalldata: () => decodePrecompileCalldata,
|
|
58
70
|
decryptJson: () => decryptJson,
|
|
71
|
+
decryptNoteRecord: () => decryptNoteRecord,
|
|
72
|
+
deriveMasterKeyBytes: () => deriveMasterKeyBytes,
|
|
59
73
|
deriveOwnerPk: () => deriveOwnerPk,
|
|
60
74
|
deriveSpendingKeyFromSignature: () => deriveSpendingKeyFromSignature,
|
|
61
75
|
deriveSpendingKeyMessage: () => deriveSpendingKeyMessage,
|
|
76
|
+
deriveStealthOwnerPk: () => deriveStealthOwnerPk,
|
|
77
|
+
deriveStealthSk: () => deriveStealthSk,
|
|
62
78
|
deriveVaultKey: () => deriveVaultKey,
|
|
63
|
-
|
|
79
|
+
deriveViewingPublicKey: () => deriveViewingPublicKey,
|
|
80
|
+
deriveViewingSecretKey: () => deriveViewingSecretKey,
|
|
64
81
|
encryptJson: () => encryptJson,
|
|
82
|
+
encryptNote: () => encryptNote,
|
|
65
83
|
ensureHexPrefix: () => ensureHexPrefix,
|
|
66
84
|
evmAddressToAccountId: () => evmAddressToAccountId,
|
|
67
85
|
evmToImplicitSubstrate: () => evmToImplicitSubstrate,
|
|
@@ -71,6 +89,9 @@ __export(index_exports, {
|
|
|
71
89
|
formatORB: () => formatORB,
|
|
72
90
|
fromBase64: () => fromBase64,
|
|
73
91
|
fromHex: () => fromHex,
|
|
92
|
+
generateFeeClaimProof: () => generateFeeClaimProof,
|
|
93
|
+
generateTransferProof: () => generateTransferProof,
|
|
94
|
+
generateUnshieldProof: () => generateUnshieldProof,
|
|
74
95
|
getInjectedExtensions: () => import_pjs_signer.getInjectedExtensions,
|
|
75
96
|
getPolkadotSigner: () => import_signer.getPolkadotSigner,
|
|
76
97
|
getPolkadotSignerFromPjs: () => import_pjs_signer.getPolkadotSignerFromPjs,
|
|
@@ -88,6 +109,9 @@ __export(index_exports, {
|
|
|
88
109
|
mapExtrinsicArgs: () => mapExtrinsicArgs,
|
|
89
110
|
mapZkEventData: () => mapZkEventData,
|
|
90
111
|
normalizeEvmAddress: () => normalizeEvmAddress,
|
|
112
|
+
randomBlinding: () => randomBlinding,
|
|
113
|
+
recoverOwnerPkPoint: () => recoverOwnerPkPoint,
|
|
114
|
+
selectNotes: () => selectNotes,
|
|
91
115
|
shortHash: () => shortHash,
|
|
92
116
|
substrateSs58ToAccountIdHex: () => substrateSs58ToAccountIdHex,
|
|
93
117
|
substrateToEvm: () => substrateToEvm,
|
|
@@ -96,6 +120,7 @@ __export(index_exports, {
|
|
|
96
120
|
toTxResult: () => toTxResult,
|
|
97
121
|
truncateMiddle: () => truncateMiddle,
|
|
98
122
|
tryDecryptNote: () => tryDecryptNote,
|
|
123
|
+
tryDecryptNoteVerbose: () => tryDecryptNoteVerbose,
|
|
99
124
|
u128: () => import_substrate_bindings3.u128,
|
|
100
125
|
u64: () => import_substrate_bindings3.u64,
|
|
101
126
|
vaultReplacer: () => vaultReplacer,
|
|
@@ -143,6 +168,7 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
143
168
|
constructor(_papi) {
|
|
144
169
|
this._papi = _papi;
|
|
145
170
|
}
|
|
171
|
+
_papi;
|
|
146
172
|
_dynamicBuilder = null;
|
|
147
173
|
_extDecoder = null;
|
|
148
174
|
/**
|
|
@@ -342,6 +368,14 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
342
368
|
submitAndWatch(signedHex) {
|
|
343
369
|
return this._papi.submitAndWatch(signedHex);
|
|
344
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Submits a bare (unsigned) extrinsic hex and waits for finalization.
|
|
373
|
+
* Used for gasless private_transfer and unshield transactions.
|
|
374
|
+
* The bare tx hex is produced by `tx.getBareTx()` from polkadot-api.
|
|
375
|
+
*/
|
|
376
|
+
async submitUnsignedAndWatch(bareTxHex) {
|
|
377
|
+
return this._papi.submit(bareTxHex);
|
|
378
|
+
}
|
|
345
379
|
/**
|
|
346
380
|
* Convenience: wrap raw call bytes and sign+submit in one step.
|
|
347
381
|
*/
|
|
@@ -393,15 +427,14 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
393
427
|
}
|
|
394
428
|
static _buildDataProxy(value) {
|
|
395
429
|
const formatValue = (v) => {
|
|
396
|
-
if (v instanceof Uint8Array) return
|
|
430
|
+
if (v instanceof Uint8Array) return toHex(v);
|
|
397
431
|
if (typeof v === "bigint") return v.toString();
|
|
398
432
|
return String(v);
|
|
399
433
|
};
|
|
400
434
|
const jsonifyValue = (v) => {
|
|
401
435
|
if (v === null || v === void 0) return v;
|
|
402
436
|
if (typeof v === "bigint") return v.toString();
|
|
403
|
-
if (v instanceof Uint8Array)
|
|
404
|
-
return `0x${Array.from(v).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
437
|
+
if (v instanceof Uint8Array) return toHex(v);
|
|
405
438
|
if (Array.isArray(v)) return v.map(jsonifyValue);
|
|
406
439
|
if (typeof v === "object") {
|
|
407
440
|
const obj = v;
|
|
@@ -505,11 +538,14 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
505
538
|
|
|
506
539
|
// src/evm/EvmClient.ts
|
|
507
540
|
var EvmClient = class {
|
|
541
|
+
/** @param rpcUrl - HTTP URL of the EVM JSON-RPC endpoint (e.g. `"http://localhost:9933"`). */
|
|
508
542
|
constructor(rpcUrl) {
|
|
509
543
|
this.rpcUrl = rpcUrl;
|
|
510
544
|
}
|
|
545
|
+
rpcUrl;
|
|
511
546
|
/**
|
|
512
|
-
* Performs a single JSON-RPC call.
|
|
547
|
+
* Performs a single JSON-RPC call and returns the typed result.
|
|
548
|
+
* Throws on HTTP errors, RPC-level errors, or a `null` result.
|
|
513
549
|
*/
|
|
514
550
|
async request(method, params = []) {
|
|
515
551
|
const res = await fetch(this.rpcUrl, {
|
|
@@ -529,6 +565,7 @@ var EvmClient = class {
|
|
|
529
565
|
}
|
|
530
566
|
/**
|
|
531
567
|
* Performs multiple JSON-RPC calls in a single HTTP request (batch).
|
|
568
|
+
* Results are returned in the same order as `calls`, as a typed tuple.
|
|
532
569
|
*/
|
|
533
570
|
async batchRequest(calls) {
|
|
534
571
|
const body = calls.map((c, i) => ({
|
|
@@ -573,30 +610,22 @@ var EvmClient = class {
|
|
|
573
610
|
const hex = await this.request("eth_gasPrice", []);
|
|
574
611
|
return hexToBigint(hex);
|
|
575
612
|
}
|
|
576
|
-
/**
|
|
577
|
-
* Submits a signed raw transaction. Returns the transaction hash.
|
|
578
|
-
*/
|
|
613
|
+
/** Submits a signed raw transaction. Returns the transaction hash. */
|
|
579
614
|
async sendRawTransaction(signedHex) {
|
|
580
615
|
return this.request("eth_sendRawTransaction", [signedHex]);
|
|
581
616
|
}
|
|
582
|
-
/**
|
|
583
|
-
* Executes a read-only call without creating a transaction.
|
|
584
|
-
*/
|
|
617
|
+
/** Executes a read-only call without creating a transaction. Returns the raw ABI-encoded response. */
|
|
585
618
|
async call(to, data, from) {
|
|
586
619
|
const txObj = { to, data };
|
|
587
620
|
if (from) txObj["from"] = from;
|
|
588
621
|
return this.request("eth_call", [txObj, "latest"]);
|
|
589
622
|
}
|
|
590
|
-
/**
|
|
591
|
-
* Estimates the gas for a transaction.
|
|
592
|
-
*/
|
|
623
|
+
/** Estimates the gas required for a transaction. Returns the estimate in wei as a `bigint`. */
|
|
593
624
|
async estimateGas(params) {
|
|
594
625
|
const hex = await this.request("eth_estimateGas", [params]);
|
|
595
626
|
return hexToBigint(hex);
|
|
596
627
|
}
|
|
597
|
-
/**
|
|
598
|
-
* Returns a transaction receipt by hash, or null if not yet mined.
|
|
599
|
-
*/
|
|
628
|
+
/** Returns a transaction receipt by hash, or `null` if the transaction has not been mined yet. */
|
|
600
629
|
async getTransactionReceipt(txHash) {
|
|
601
630
|
const res = await fetch(this.rpcUrl, {
|
|
602
631
|
method: "POST",
|
|
@@ -615,6 +644,56 @@ var EvmClient = class {
|
|
|
615
644
|
}
|
|
616
645
|
return json.result ?? null;
|
|
617
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Polls `eth_getTransactionReceipt` until the transaction is included in a block.
|
|
649
|
+
*
|
|
650
|
+
* @param txHash - The transaction hash to wait for.
|
|
651
|
+
* @param intervalMs - Polling interval in milliseconds (default: 500).
|
|
652
|
+
* @param timeoutMs - Maximum time to wait in milliseconds (default: 60_000).
|
|
653
|
+
* @returns The transaction receipt once mined.
|
|
654
|
+
* @throws If the transaction is not mined within `timeoutMs` or if it reverted (`status == 0x0`).
|
|
655
|
+
*/
|
|
656
|
+
async waitForReceipt(txHash, intervalMs = 500, timeoutMs = 6e4) {
|
|
657
|
+
const deadline = Date.now() + timeoutMs;
|
|
658
|
+
while (Date.now() < deadline) {
|
|
659
|
+
const receipt = await this.getTransactionReceipt(txHash);
|
|
660
|
+
if (receipt !== null) {
|
|
661
|
+
if (receipt["status"] === "0x0") {
|
|
662
|
+
let revertDetail = "";
|
|
663
|
+
const nodeReason = receipt["revertReason"];
|
|
664
|
+
if (nodeReason) revertDetail = ` | revertReason: ${nodeReason}`;
|
|
665
|
+
if (!revertDetail) {
|
|
666
|
+
try {
|
|
667
|
+
const blockParam = receipt["blockNumber"] ?? "latest";
|
|
668
|
+
const rawTx = await this.request(
|
|
669
|
+
"eth_getTransactionByHash",
|
|
670
|
+
[txHash]
|
|
671
|
+
).catch(() => null);
|
|
672
|
+
if (rawTx) {
|
|
673
|
+
const calldata = rawTx["input"] ?? rawTx["data"];
|
|
674
|
+
if (calldata) {
|
|
675
|
+
const revertData = await this.request("eth_call", [
|
|
676
|
+
{ from: rawTx["from"], to: rawTx["to"], data: calldata },
|
|
677
|
+
blockParam
|
|
678
|
+
]).catch(
|
|
679
|
+
(err) => err instanceof Error ? err.message : String(err)
|
|
680
|
+
);
|
|
681
|
+
revertDetail = ` | eth_call: ${revertData}`;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
} catch {
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
throw new Error(
|
|
688
|
+
`Transaction reverted on-chain: ${txHash}${revertDetail} | receipt: ${JSON.stringify(receipt)}`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return receipt;
|
|
692
|
+
}
|
|
693
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
694
|
+
}
|
|
695
|
+
throw new Error(`Transaction not mined within ${timeoutMs}ms: ${txHash}`);
|
|
696
|
+
}
|
|
618
697
|
};
|
|
619
698
|
|
|
620
699
|
// src/utils/format.ts
|
|
@@ -687,10 +766,13 @@ function formatORB(raw, precision = 6) {
|
|
|
687
766
|
|
|
688
767
|
// src/evm-explorer/EvmExplorer.ts
|
|
689
768
|
var EvmExplorer = class _EvmExplorer {
|
|
769
|
+
/** @param evm - Underlying `EvmClient` used for all RPC calls. */
|
|
690
770
|
constructor(evm) {
|
|
691
771
|
this.evm = evm;
|
|
692
772
|
}
|
|
773
|
+
evm;
|
|
693
774
|
// --- Blocks ---
|
|
775
|
+
/** Returns the `count` most recent blocks in descending order (latest first). */
|
|
694
776
|
async getLatestBlocks(count = 10) {
|
|
695
777
|
const latest = await this.evm.getBlockNumber();
|
|
696
778
|
const nums = Array.from({ length: Math.min(count, latest + 1) }, (_, i) => latest - i);
|
|
@@ -704,10 +786,12 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
704
786
|
);
|
|
705
787
|
return results.filter((b) => b !== null && !!b.hash).map((b) => this.parseBlock(b));
|
|
706
788
|
}
|
|
789
|
+
/** Returns a single block by number or hash, or `null` if not found. */
|
|
707
790
|
async getBlock(hashOrNumber) {
|
|
708
791
|
const b = await this.fetchBlock(hashOrNumber, false);
|
|
709
792
|
return b ? this.parseBlock(b) : null;
|
|
710
793
|
}
|
|
794
|
+
/** Returns all transactions in a block (with receipts), or `[]` if the block is not found. */
|
|
711
795
|
async getBlockTransactions(hashOrNumber) {
|
|
712
796
|
try {
|
|
713
797
|
const b = await this.fetchBlock(hashOrNumber, true);
|
|
@@ -720,6 +804,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
720
804
|
}
|
|
721
805
|
}
|
|
722
806
|
// --- Transactions ---
|
|
807
|
+
/** Returns a single transaction with its receipt, or `null` if not found. */
|
|
723
808
|
async getTransaction(hash) {
|
|
724
809
|
try {
|
|
725
810
|
const [tx, receipt] = await Promise.all([
|
|
@@ -732,6 +817,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
732
817
|
return null;
|
|
733
818
|
}
|
|
734
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Returns lightweight summaries of all transactions sent from or to `address`
|
|
822
|
+
* within the last `maxBlocks` blocks, sorted by block number descending.
|
|
823
|
+
*/
|
|
735
824
|
async getTransactionsByAddress(address, maxBlocks = 300) {
|
|
736
825
|
const addr = address.toLowerCase();
|
|
737
826
|
const latest = await this.evm.getBlockNumber();
|
|
@@ -783,6 +872,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
783
872
|
return results;
|
|
784
873
|
}
|
|
785
874
|
// --- Address ---
|
|
875
|
+
/**
|
|
876
|
+
* Returns aggregated on-chain data for an EVM address: balance, nonce,
|
|
877
|
+
* bytecode (truncated), and up to 50 recent logs from the last 5 000 blocks.
|
|
878
|
+
*/
|
|
786
879
|
async getAddressInfo(address) {
|
|
787
880
|
const latest = await this.evm.getBlockNumber().catch(() => 0);
|
|
788
881
|
const fromBlock = `0x${Math.max(0, latest - 5e3).toString(16)}`;
|
|
@@ -813,6 +906,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
813
906
|
recentLogs
|
|
814
907
|
};
|
|
815
908
|
}
|
|
909
|
+
/** Returns the native token balance of `address`, formatted as a decimal string (no symbol). */
|
|
816
910
|
async getBalance(address) {
|
|
817
911
|
try {
|
|
818
912
|
const val = await this.evm.getBalance(address);
|
|
@@ -821,6 +915,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
821
915
|
return "0";
|
|
822
916
|
}
|
|
823
917
|
}
|
|
918
|
+
/** Returns the current transaction count (nonce) for `address`, or `0` on error. */
|
|
824
919
|
async getNonce(address) {
|
|
825
920
|
try {
|
|
826
921
|
return await this.evm.getTransactionCount(address);
|
|
@@ -828,6 +923,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
828
923
|
return 0;
|
|
829
924
|
}
|
|
830
925
|
}
|
|
926
|
+
/** Returns `true` when `address` has non-empty deployed bytecode. */
|
|
831
927
|
async getIsContract(address) {
|
|
832
928
|
try {
|
|
833
929
|
const code = await this.evm.request("eth_getCode", [address, "latest"]);
|
|
@@ -837,6 +933,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
837
933
|
}
|
|
838
934
|
}
|
|
839
935
|
// --- Tokens ---
|
|
936
|
+
/**
|
|
937
|
+
* Fetches ERC-20 metadata for a token contract via ABI calls.
|
|
938
|
+
* Returns `null` when the address does not look like an ERC-20 token.
|
|
939
|
+
*/
|
|
840
940
|
async getTokenInfo(address) {
|
|
841
941
|
const addr = address.toLowerCase();
|
|
842
942
|
const [name, symbol, decimals, totalSupply] = await this.evm.batchRequest([
|
|
@@ -856,6 +956,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
856
956
|
isErc20
|
|
857
957
|
};
|
|
858
958
|
}
|
|
959
|
+
/**
|
|
960
|
+
* Returns ERC-20 `Transfer` events for `address` from the last 5 000 blocks.
|
|
961
|
+
* When `holderAddress` is provided, restricts results to transfers sent or received by that address.
|
|
962
|
+
*/
|
|
859
963
|
async getTokenTransfers(address, holderAddress) {
|
|
860
964
|
const TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
861
965
|
const latest = await this.evm.getBlockNumber().catch(() => 0);
|
|
@@ -883,12 +987,14 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
883
987
|
logIndex: hexToNumber(l.logIndex)
|
|
884
988
|
}));
|
|
885
989
|
}
|
|
990
|
+
/** Returns the raw ERC-20 balance of `holderAddress` for the token at `tokenAddress` (0x-prefixed hex). */
|
|
886
991
|
async getTokenBalance(tokenAddress, holderAddress) {
|
|
887
992
|
const padded = holderAddress.replace(/^0x/, "").toLowerCase().padStart(64, "0");
|
|
888
993
|
const result = await this.ethCall(tokenAddress, `0x70a08231${padded}`);
|
|
889
994
|
return result ?? "0x0";
|
|
890
995
|
}
|
|
891
996
|
// --- Private: parsers ---
|
|
997
|
+
/** Maps a raw RPC block object to the public `EvmBlock` shape. */
|
|
892
998
|
parseBlock(b) {
|
|
893
999
|
return {
|
|
894
1000
|
hash: b.hash,
|
|
@@ -901,6 +1007,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
901
1007
|
parentHash: b.parentHash
|
|
902
1008
|
};
|
|
903
1009
|
}
|
|
1010
|
+
/** Maps a raw RPC transaction + optional receipt to the public `EvmTransaction` shape. */
|
|
904
1011
|
parseTx(tx, receipt) {
|
|
905
1012
|
const parsed = {
|
|
906
1013
|
hash: tx.hash,
|
|
@@ -920,6 +1027,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
920
1027
|
return parsed;
|
|
921
1028
|
}
|
|
922
1029
|
// --- Private: fetch helpers ---
|
|
1030
|
+
/**
|
|
1031
|
+
* Fetches a raw block by number or hash. Accepts a plain integer, a decimal string,
|
|
1032
|
+
* or a 0x-prefixed hash. Returns `null` on any RPC error.
|
|
1033
|
+
*/
|
|
923
1034
|
async fetchBlock(hashOrNumber, withTxObjects) {
|
|
924
1035
|
try {
|
|
925
1036
|
if (typeof hashOrNumber === "number" || /^\d+$/.test(String(hashOrNumber))) {
|
|
@@ -937,6 +1048,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
937
1048
|
return null;
|
|
938
1049
|
}
|
|
939
1050
|
}
|
|
1051
|
+
/** Executes a read-only `eth_call` and returns the raw hex result, or `null` on error. */
|
|
940
1052
|
async ethCall(to, data) {
|
|
941
1053
|
try {
|
|
942
1054
|
return await this.evm.call(to, data);
|
|
@@ -945,6 +1057,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
945
1057
|
}
|
|
946
1058
|
}
|
|
947
1059
|
// --- Private static: ABI decoders ---
|
|
1060
|
+
/** Decodes an ABI-encoded `string` return value from a raw 0x-prefixed hex string. */
|
|
948
1061
|
static decodeAbiString(hex) {
|
|
949
1062
|
if (!hex || hex === "0x") return "";
|
|
950
1063
|
const data = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
@@ -959,11 +1072,13 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
959
1072
|
return "";
|
|
960
1073
|
}
|
|
961
1074
|
}
|
|
1075
|
+
/** Decodes an ABI-encoded `uint256` return value to a `bigint`. */
|
|
962
1076
|
static decodeAbiUint(hex) {
|
|
963
1077
|
if (!hex || hex === "0x") return 0n;
|
|
964
1078
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
965
1079
|
return BigInt(`0x${clean || "0"}`);
|
|
966
1080
|
}
|
|
1081
|
+
/** Converts a 0x-prefixed hex number to its decimal string representation. Returns `'0'` on parse error. */
|
|
967
1082
|
static hexToDecimalStr(hex) {
|
|
968
1083
|
try {
|
|
969
1084
|
return BigInt(hex).toString();
|
|
@@ -998,6 +1113,24 @@ var IndexerClient = class {
|
|
|
998
1113
|
}
|
|
999
1114
|
return res.json();
|
|
1000
1115
|
}
|
|
1116
|
+
async post(path, body) {
|
|
1117
|
+
const controller = new AbortController();
|
|
1118
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1119
|
+
try {
|
|
1120
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
1121
|
+
method: "POST",
|
|
1122
|
+
headers: { "Content-Type": "application/json" },
|
|
1123
|
+
body: JSON.stringify(body),
|
|
1124
|
+
signal: controller.signal
|
|
1125
|
+
});
|
|
1126
|
+
if (!res.ok) {
|
|
1127
|
+
throw new Error(`IndexerClient: HTTP ${res.status} for POST ${path}`);
|
|
1128
|
+
}
|
|
1129
|
+
return res.json();
|
|
1130
|
+
} finally {
|
|
1131
|
+
clearTimeout(timer);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1001
1134
|
async getOrNull(path) {
|
|
1002
1135
|
const res = await this._fetchResponse(path);
|
|
1003
1136
|
if (res.status === 404) return null;
|
|
@@ -1033,6 +1166,21 @@ var IndexerClient = class {
|
|
|
1033
1166
|
`/shielded/commitments/${encodeURIComponent(hex)}`
|
|
1034
1167
|
);
|
|
1035
1168
|
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Returns a paginated list of stealth scan hints ordered ascending by leafIndex.
|
|
1171
|
+
* Each hint contains only the fields required for ECDH triage and decryption:
|
|
1172
|
+
* leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo.
|
|
1173
|
+
*
|
|
1174
|
+
* Use `sinceLeafIndex` for incremental scans (cursor = last seen leafIndex + 1).
|
|
1175
|
+
*/
|
|
1176
|
+
async getScanHints(params) {
|
|
1177
|
+
const qs = this.buildQuery({
|
|
1178
|
+
page: params?.page,
|
|
1179
|
+
limit: params?.limit,
|
|
1180
|
+
since_leaf_index: params?.sinceLeafIndex
|
|
1181
|
+
});
|
|
1182
|
+
return this.get(`/shielded/scan-hints${qs}`);
|
|
1183
|
+
}
|
|
1036
1184
|
// ─── Nullifiers ────────────────────────────────────────────────────────────
|
|
1037
1185
|
/** Returns a paginated list of spent nullifiers. */
|
|
1038
1186
|
async getNullifiers(params) {
|
|
@@ -1045,11 +1193,50 @@ var IndexerClient = class {
|
|
|
1045
1193
|
`/shielded/nullifier/${encodeURIComponent(hex)}/status`
|
|
1046
1194
|
);
|
|
1047
1195
|
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Batch-checks which of the given nullifiers are spent.
|
|
1198
|
+
* Returns only the nullifiers that exist in the spent set.
|
|
1199
|
+
* Accepts up to 100 nullifiers (0x-prefixed hex).
|
|
1200
|
+
*/
|
|
1201
|
+
async getNullifiersBatch(nullifiers) {
|
|
1202
|
+
if (nullifiers.length === 0) return [];
|
|
1203
|
+
const res = await this.post("/shielded/nullifiers/batch", {
|
|
1204
|
+
nullifiers: nullifiers.map((n) => n.toLowerCase())
|
|
1205
|
+
});
|
|
1206
|
+
return res.data;
|
|
1207
|
+
}
|
|
1048
1208
|
// ─── Private transfers ─────────────────────────────────────────────────────
|
|
1049
|
-
/**
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1209
|
+
/**
|
|
1210
|
+
* Returns temporal metadata for private transfers that spent any of the given nullifiers.
|
|
1211
|
+
* Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
|
|
1212
|
+
* between inputs and outputs to prevent graph reconstruction.
|
|
1213
|
+
* Accepts up to 50 nullifiers (0x-prefixed hex).
|
|
1214
|
+
*/
|
|
1215
|
+
async getTransfersByNullifiers(nullifiers) {
|
|
1216
|
+
if (nullifiers.length === 0) return [];
|
|
1217
|
+
const qs = this.buildQuery({
|
|
1218
|
+
nullifiers: nullifiers.map((n) => n.toLowerCase()).join(",")
|
|
1219
|
+
});
|
|
1220
|
+
const res = await this.get(
|
|
1221
|
+
`/shielded/transfers/by-nullifiers${qs}`
|
|
1222
|
+
);
|
|
1223
|
+
return res.data;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Returns temporal metadata for private transfers that produced any of the given commitments.
|
|
1227
|
+
* Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
|
|
1228
|
+
* between outputs and inputs to prevent graph reconstruction.
|
|
1229
|
+
* Accepts up to 50 commitments (0x-prefixed hex).
|
|
1230
|
+
*/
|
|
1231
|
+
async getTransfersByCommitments(commitments) {
|
|
1232
|
+
if (commitments.length === 0) return [];
|
|
1233
|
+
const qs = this.buildQuery({
|
|
1234
|
+
commitments: commitments.map((c) => c.toLowerCase()).join(",")
|
|
1235
|
+
});
|
|
1236
|
+
const res = await this.get(
|
|
1237
|
+
`/shielded/transfers/by-commitments${qs}`
|
|
1238
|
+
);
|
|
1239
|
+
return res.data;
|
|
1053
1240
|
}
|
|
1054
1241
|
// ─── Unshields ─────────────────────────────────────────────────────────────
|
|
1055
1242
|
/** Returns a paginated list of unshield events. */
|
|
@@ -1109,6 +1296,16 @@ var IndexerClient = class {
|
|
|
1109
1296
|
`/address/${encodeURIComponent(address.toLowerCase())}/shielded${qs}`
|
|
1110
1297
|
);
|
|
1111
1298
|
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Returns a paginated list of unshield events where the given address is the recipient.
|
|
1301
|
+
* Accepts a 0x-prefixed EVM address or an SS58 Substrate address.
|
|
1302
|
+
*/
|
|
1303
|
+
async getAddressUnshields(address, params) {
|
|
1304
|
+
const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
|
|
1305
|
+
return this.get(
|
|
1306
|
+
`/address/${encodeURIComponent(address)}/unshields${qs}`
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1112
1309
|
/**
|
|
1113
1310
|
* Returns a paginated list of all shielded activity (commitments, unshields,
|
|
1114
1311
|
* private transfers) associated with the given address.
|
|
@@ -1120,6 +1317,46 @@ var IndexerClient = class {
|
|
|
1120
1317
|
`/shielded/address/${encodeURIComponent(address.toLowerCase())}${qs}`
|
|
1121
1318
|
);
|
|
1122
1319
|
}
|
|
1320
|
+
// ─── Relayers ──────────────────────────────────────────────────────────────
|
|
1321
|
+
/** Returns a paginated list of relayers. Filter by active status with `active`. */
|
|
1322
|
+
async getRelayers(params) {
|
|
1323
|
+
const qs = this.buildQuery({
|
|
1324
|
+
page: params?.page,
|
|
1325
|
+
limit: params?.limit,
|
|
1326
|
+
active: params?.active === void 0 ? void 0 : params.active ? "true" : "false"
|
|
1327
|
+
});
|
|
1328
|
+
return this.get(`/relayers${qs}`);
|
|
1329
|
+
}
|
|
1330
|
+
/** Returns a single relayer by EVM address, or null if not found. */
|
|
1331
|
+
async getRelayer(evmAddress) {
|
|
1332
|
+
return this.getOrNull(`/relayers/${encodeURIComponent(evmAddress.toLowerCase())}`);
|
|
1333
|
+
}
|
|
1334
|
+
/** Returns a paginated list of relay fee events. */
|
|
1335
|
+
async getRelayFees(params) {
|
|
1336
|
+
const qs = this.buildQuery({
|
|
1337
|
+
page: params?.page,
|
|
1338
|
+
limit: params?.limit,
|
|
1339
|
+
relayer: params?.relayer,
|
|
1340
|
+
type: params?.type
|
|
1341
|
+
});
|
|
1342
|
+
return this.get(`/relayers/fees${qs}`);
|
|
1343
|
+
}
|
|
1344
|
+
/** Returns aggregated relay fee balances per asset for a given relayer account. */
|
|
1345
|
+
async getRelayFeesSummary(relayer) {
|
|
1346
|
+
return this.get(
|
|
1347
|
+
`/relayers/fees/summary/${encodeURIComponent(relayer)}`
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
// ─── Registered assets ─────────────────────────────────────────────────────
|
|
1351
|
+
/** Returns a paginated list of assets registered via register_asset. */
|
|
1352
|
+
async getRegisteredAssets(params) {
|
|
1353
|
+
const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
|
|
1354
|
+
return this.get(`/shielded/assets${qs}`);
|
|
1355
|
+
}
|
|
1356
|
+
/** Returns a single registered asset by its ID, or null if not found. */
|
|
1357
|
+
async getRegisteredAsset(assetId) {
|
|
1358
|
+
return this.getOrNull(`/shielded/assets/${encodeURIComponent(assetId)}`);
|
|
1359
|
+
}
|
|
1123
1360
|
// ─── Stats & Health ────────────────────────────────────────────────────────
|
|
1124
1361
|
/** Returns aggregated indexer statistics. */
|
|
1125
1362
|
async getStats() {
|
|
@@ -1144,10 +1381,24 @@ var IndexerClient = class {
|
|
|
1144
1381
|
}
|
|
1145
1382
|
};
|
|
1146
1383
|
|
|
1147
|
-
// src/shielded-pool/ShieldedPoolModule.ts
|
|
1384
|
+
// src/shielded-pool/pallet/ShieldedPoolModule.ts
|
|
1148
1385
|
var import_polkadot_api2 = require("polkadot-api");
|
|
1149
1386
|
|
|
1150
1387
|
// src/utils/tx.ts
|
|
1388
|
+
function formatDispatchError(err) {
|
|
1389
|
+
if (err.type === "Module") {
|
|
1390
|
+
const inner = err.value;
|
|
1391
|
+
if (inner?.type) {
|
|
1392
|
+
return `Module(${inner.type})`;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
const detail = JSON.stringify(err.value);
|
|
1397
|
+
return detail && detail !== "null" ? `${err.type}(${detail})` : err.type;
|
|
1398
|
+
} catch {
|
|
1399
|
+
return err.type;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1151
1402
|
function toTxResult(payload) {
|
|
1152
1403
|
const base = {
|
|
1153
1404
|
txHash: payload.txHash,
|
|
@@ -1156,13 +1407,18 @@ function toTxResult(payload) {
|
|
|
1156
1407
|
ok: payload.ok
|
|
1157
1408
|
};
|
|
1158
1409
|
if (!payload.ok) {
|
|
1159
|
-
return { ...base, error: payload.dispatchError
|
|
1410
|
+
return { ...base, error: formatDispatchError(payload.dispatchError) };
|
|
1160
1411
|
}
|
|
1161
1412
|
return base;
|
|
1162
1413
|
}
|
|
1163
1414
|
function callUnsafeTx(txEntry, ...args) {
|
|
1164
1415
|
return txEntry(...args);
|
|
1165
1416
|
}
|
|
1417
|
+
async function submitBareTx(tx, client) {
|
|
1418
|
+
const bareTxHex = await tx.getBareTx();
|
|
1419
|
+
const payload = await client.submitUnsignedAndWatch(bareTxHex);
|
|
1420
|
+
return toTxResult(payload);
|
|
1421
|
+
}
|
|
1166
1422
|
function resolveTx(unsafe, pallet, call) {
|
|
1167
1423
|
const u = unsafe;
|
|
1168
1424
|
const p = u["tx"]?.[pallet];
|
|
@@ -1173,9 +1429,10 @@ function resolveTx(unsafe, pallet, call) {
|
|
|
1173
1429
|
return entry;
|
|
1174
1430
|
}
|
|
1175
1431
|
|
|
1176
|
-
// src/shielded-pool/EncryptedMemo.ts
|
|
1432
|
+
// src/shielded-pool/protocol/EncryptedMemo.ts
|
|
1177
1433
|
var import_chacha = require("@noble/ciphers/chacha.js");
|
|
1178
1434
|
var import_utils = require("@noble/ciphers/utils.js");
|
|
1435
|
+
var import_baby_jubjub = require("@zk-kit/baby-jubjub");
|
|
1179
1436
|
|
|
1180
1437
|
// src/utils/bytes.ts
|
|
1181
1438
|
function bigintTo32Le(n) {
|
|
@@ -1222,77 +1479,114 @@ function computePathIndices(leafIndex, depth) {
|
|
|
1222
1479
|
return indices;
|
|
1223
1480
|
}
|
|
1224
1481
|
function leHexToBigint(hex) {
|
|
1225
|
-
|
|
1226
|
-
const bytes = new Uint8Array(h.length / 2);
|
|
1227
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1228
|
-
bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
|
1229
|
-
}
|
|
1230
|
-
return bytesToBigintLE(bytes);
|
|
1482
|
+
return bytesToBigintLE(fromHex(hex));
|
|
1231
1483
|
}
|
|
1232
1484
|
|
|
1233
|
-
// src/
|
|
1485
|
+
// src/utils/crypto-constants.ts
|
|
1486
|
+
var BN254_R = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1487
|
+
var BABYJUB_SUBORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
1488
|
+
|
|
1489
|
+
// src/shielded-pool/protocol/memo.ts
|
|
1234
1490
|
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
1235
1491
|
var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
|
|
1236
|
-
var MEMO_PLAINTEXT_SIZE =
|
|
1237
|
-
function serializeMemo(value, ownerPk, blinding, assetId) {
|
|
1492
|
+
var MEMO_PLAINTEXT_SIZE = 116;
|
|
1493
|
+
function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
|
|
1238
1494
|
const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
|
|
1239
1495
|
const view = new DataView(buf.buffer);
|
|
1240
1496
|
view.setBigUint64(0, value & 0xffffffffffffffffn, true);
|
|
1241
|
-
|
|
1242
|
-
buf.set(
|
|
1243
|
-
|
|
1497
|
+
view.setBigUint64(8, value >> 64n & 0xffffffffffffffffn, true);
|
|
1498
|
+
buf.set(ownerPk.slice(0, 32), 16);
|
|
1499
|
+
buf.set(blinding.slice(0, 32), 48);
|
|
1500
|
+
view.setUint32(80, assetId >>> 0, true);
|
|
1501
|
+
buf.set(counterpartyPk.slice(0, 32), 84);
|
|
1244
1502
|
return buf;
|
|
1245
1503
|
}
|
|
1246
|
-
function deriveEncryptionKey(
|
|
1504
|
+
function deriveEncryptionKey(sharedSecret, commitment) {
|
|
1247
1505
|
const h = import_sha2.sha256.create();
|
|
1248
|
-
h.update(
|
|
1506
|
+
h.update(sharedSecret);
|
|
1249
1507
|
h.update(commitment);
|
|
1250
1508
|
h.update(KEY_DOMAIN);
|
|
1251
1509
|
return h.digest();
|
|
1252
1510
|
}
|
|
1253
|
-
function toBase64(buf) {
|
|
1254
|
-
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
1255
|
-
let str = "";
|
|
1256
|
-
for (const b of bytes) str += String.fromCharCode(b);
|
|
1257
|
-
return btoa(str);
|
|
1258
|
-
}
|
|
1259
|
-
function fromBase64(b64) {
|
|
1260
|
-
const bin = atob(b64);
|
|
1261
|
-
const out = new Uint8Array(bin.length);
|
|
1262
|
-
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
1263
|
-
return out;
|
|
1264
|
-
}
|
|
1265
1511
|
|
|
1266
|
-
// src/shielded-pool/EncryptedMemo.ts
|
|
1512
|
+
// src/shielded-pool/protocol/EncryptedMemo.ts
|
|
1267
1513
|
var NONCE_SIZE = 12;
|
|
1268
|
-
var
|
|
1514
|
+
var CIPHERTEXT_SIZE = 132;
|
|
1515
|
+
var EPH_PK_SIZE = 32;
|
|
1516
|
+
var ENCRYPTED_MEMO_SIZE = NONCE_SIZE + CIPHERTEXT_SIZE + EPH_PK_SIZE;
|
|
1517
|
+
function bytesToBjjScalar(bytes) {
|
|
1518
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1519
|
+
return BigInt("0x" + hex) % BABYJUB_SUBORDER || 1n;
|
|
1520
|
+
}
|
|
1521
|
+
function parsePlaintext(nonce, ciphertextWithMac, encKey) {
|
|
1522
|
+
try {
|
|
1523
|
+
const cipher = (0, import_chacha.chacha20poly1305)(encKey, nonce);
|
|
1524
|
+
const plaintext = cipher.decrypt(ciphertextWithMac);
|
|
1525
|
+
const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);
|
|
1526
|
+
const valueLo = view.getBigUint64(0, true);
|
|
1527
|
+
const valueHi = view.getBigUint64(8, true);
|
|
1528
|
+
const value = valueLo | valueHi << 64n;
|
|
1529
|
+
const ownerPk = bytesToBigintLE(plaintext.slice(16, 48));
|
|
1530
|
+
const blinding = bytesToBigintLE(plaintext.slice(48, 80));
|
|
1531
|
+
const assetId = BigInt(view.getUint32(80, true));
|
|
1532
|
+
const counterpartyPk = bytesToBigintLE(plaintext.slice(84, 116));
|
|
1533
|
+
return { value, ownerPk, blinding, assetId, counterpartyPk };
|
|
1534
|
+
} catch {
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1269
1538
|
var EncryptedMemo = {
|
|
1270
1539
|
/**
|
|
1271
|
-
* Build and encrypt a memo for a note.
|
|
1540
|
+
* Build and encrypt a memo for a note using ECDH (v2, 168 bytes).
|
|
1272
1541
|
*
|
|
1273
|
-
* @param value
|
|
1274
|
-
* @param ownerPk
|
|
1275
|
-
* @param blinding
|
|
1276
|
-
* @param assetId
|
|
1277
|
-
* @param commitment
|
|
1278
|
-
* @param
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
|
|
1282
|
-
|
|
1542
|
+
* @param value Note value in planck.
|
|
1543
|
+
* @param ownerPk 32-byte owner public key (LE).
|
|
1544
|
+
* @param blinding 32-byte blinding scalar (LE).
|
|
1545
|
+
* @param assetId Asset identifier.
|
|
1546
|
+
* @param commitment 32-byte commitment bytes (LE).
|
|
1547
|
+
* @param recipientIvkPacked 32-byte LE-encoded packed BJJ viewing public key
|
|
1548
|
+
* (from PrivacyKeyManager.getViewingPublicKeyPacked() or
|
|
1549
|
+
* decoded from a privacy address).
|
|
1550
|
+
* Pass `new Uint8Array(32)` (all zeros) for a publicly-readable memo.
|
|
1551
|
+
* @param counterpartyPk 32-byte counterparty BJJ Ax. Default: all zeros.
|
|
1552
|
+
* @returns 168-byte encrypted memo: nonce(12) || ciphertext+MAC(124) || ephPk(32).
|
|
1553
|
+
*/
|
|
1554
|
+
encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), ephSkOverride) {
|
|
1283
1555
|
const nonce = (0, import_utils.randomBytes)(NONCE_SIZE);
|
|
1284
|
-
const
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1556
|
+
const plaintext = serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk);
|
|
1557
|
+
const isZeroKey = recipientIvkPacked.every((b) => b === 0);
|
|
1558
|
+
let sharedSecret;
|
|
1559
|
+
let ephPkPackedBytes;
|
|
1560
|
+
if (isZeroKey) {
|
|
1561
|
+
sharedSecret = new Uint8Array(32);
|
|
1562
|
+
ephPkPackedBytes = new Uint8Array(EPH_PK_SIZE);
|
|
1563
|
+
} else {
|
|
1564
|
+
const ephSkBytes = ephSkOverride ?? (0, import_utils.randomBytes)(32);
|
|
1565
|
+
if (ephSkBytes.length !== 32)
|
|
1566
|
+
throw new Error("EncryptedMemo.encrypt: ephSkOverride must be 32 bytes");
|
|
1567
|
+
const ephSkScalar = bytesToBjjScalar(ephSkBytes);
|
|
1568
|
+
const ephPkPoint = (0, import_baby_jubjub.mulPointEscalar)(import_baby_jubjub.Base8, ephSkScalar);
|
|
1569
|
+
ephPkPackedBytes = bigintTo32Le((0, import_baby_jubjub.packPoint)(ephPkPoint));
|
|
1570
|
+
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
1571
|
+
const ivkPoint = (0, import_baby_jubjub.unpackPoint)(ivkPackedBigint);
|
|
1572
|
+
if (!ivkPoint)
|
|
1573
|
+
throw new Error("EncryptedMemo.encrypt: invalid recipient viewing public key");
|
|
1574
|
+
const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ivkPoint, ephSkScalar);
|
|
1575
|
+
sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1576
|
+
}
|
|
1577
|
+
const encKey = deriveEncryptionKey(sharedSecret, commitment);
|
|
1578
|
+
const cipher = (0, import_chacha.chacha20poly1305)(encKey, nonce);
|
|
1287
1579
|
const ciphertext = cipher.encrypt(plaintext);
|
|
1288
|
-
const result = new Uint8Array(
|
|
1580
|
+
const result = new Uint8Array(ENCRYPTED_MEMO_SIZE);
|
|
1289
1581
|
result.set(nonce, 0);
|
|
1290
1582
|
result.set(ciphertext, NONCE_SIZE);
|
|
1583
|
+
result.set(ephPkPackedBytes, NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1291
1584
|
return result;
|
|
1292
1585
|
},
|
|
1293
1586
|
/**
|
|
1294
|
-
* Returns a
|
|
1295
|
-
*
|
|
1587
|
+
* Returns a 168-byte public memo encrypted with a zero viewing key.
|
|
1588
|
+
* Decryptable by anyone with `decrypt(memo, commitment, new Uint8Array(32))`.
|
|
1589
|
+
* Convenience alias for `encrypt(..., new Uint8Array(32))`.
|
|
1296
1590
|
*/
|
|
1297
1591
|
encryptPublic(value, ownerPk, blinding, assetId, commitment) {
|
|
1298
1592
|
return EncryptedMemo.encrypt(
|
|
@@ -1305,226 +1599,89 @@ var EncryptedMemo = {
|
|
|
1305
1599
|
);
|
|
1306
1600
|
},
|
|
1307
1601
|
/**
|
|
1308
|
-
* Returns a
|
|
1602
|
+
* Returns a 168-byte zeroed dummy memo (no information, always valid on-chain).
|
|
1309
1603
|
*/
|
|
1310
1604
|
dummy() {
|
|
1311
1605
|
return new Uint8Array(ENCRYPTED_MEMO_SIZE);
|
|
1312
1606
|
},
|
|
1313
1607
|
/**
|
|
1314
|
-
*
|
|
1608
|
+
* Validates that `bytes` is a properly-sized encrypted memo.
|
|
1609
|
+
* Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (168 bytes).
|
|
1315
1610
|
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1611
|
+
* Call this at system boundaries (extrinsic builders, precompile encoders)
|
|
1612
|
+
* to catch malformed memos before they reach the chain and fail on-chain.
|
|
1318
1613
|
*
|
|
1319
|
-
* @param
|
|
1320
|
-
* @param
|
|
1321
|
-
* @param recipientVk 32-byte recipient viewing key.
|
|
1614
|
+
* @param bytes The memo bytes to validate.
|
|
1615
|
+
* @param context Optional context string included in the error (e.g. 'shield', 'output[0]').
|
|
1322
1616
|
*/
|
|
1323
|
-
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
const cipher = (0, import_chacha.chacha20poly1305)(key, nonce);
|
|
1330
|
-
const plaintext = cipher.decrypt(ciphertext);
|
|
1331
|
-
const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);
|
|
1332
|
-
const value = view.getBigUint64(0, true);
|
|
1333
|
-
const ownerPk = bytesToBigintLE(plaintext.slice(8, 40));
|
|
1334
|
-
const blinding = bytesToBigintLE(plaintext.slice(40, 72));
|
|
1335
|
-
const assetId = BigInt(view.getUint32(72, true));
|
|
1336
|
-
return { value, ownerPk, blinding, assetId };
|
|
1337
|
-
} catch {
|
|
1338
|
-
return null;
|
|
1617
|
+
validate(bytes, context) {
|
|
1618
|
+
if (bytes.length !== ENCRYPTED_MEMO_SIZE) {
|
|
1619
|
+
const ctx = context ? ` (${context})` : "";
|
|
1620
|
+
throw new Error(
|
|
1621
|
+
`EncryptedMemo: invalid size${ctx} \u2014 expected ${ENCRYPTED_MEMO_SIZE} bytes, got ${bytes.length}`
|
|
1622
|
+
);
|
|
1339
1623
|
}
|
|
1340
|
-
}
|
|
1341
|
-
};
|
|
1342
|
-
|
|
1343
|
-
// src/shielded-pool/NoteBuilder.ts
|
|
1344
|
-
var import_poseidon_lite = require("poseidon-lite");
|
|
1345
|
-
var NoteBuilder = class {
|
|
1624
|
+
},
|
|
1346
1625
|
/**
|
|
1347
|
-
*
|
|
1626
|
+
* Decrypt an on-chain EncryptedMemo using the recipient's viewing secret key.
|
|
1627
|
+
* Returns null if decryption fails — wrong key, bad MAC, or malformed memo.
|
|
1628
|
+
* Never throws; safe for scan loops.
|
|
1348
1629
|
*
|
|
1349
|
-
* @param
|
|
1350
|
-
* @param
|
|
1351
|
-
* @param
|
|
1352
|
-
* @param input.blinding Random scalar — defaults to BigInt(Date.now()).
|
|
1353
|
-
* @param input.spendingKey Secret key for nullifier — default 0n.
|
|
1630
|
+
* @param memoBytes 168-byte encrypted memo.
|
|
1631
|
+
* @param commitment 32-byte note commitment (LE).
|
|
1632
|
+
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1354
1633
|
*/
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
const blinding = input.blinding ?? BigInt(Date.now());
|
|
1360
|
-
const spendingKey = input.spendingKey ?? 0n;
|
|
1361
|
-
const commitment = (0, import_poseidon_lite.poseidon4)([value, assetId, ownerPk, blinding]);
|
|
1362
|
-
const nullifier = (0, import_poseidon_lite.poseidon2)([commitment, spendingKey]);
|
|
1363
|
-
const commitmentBytes = bigintTo32Le(commitment);
|
|
1364
|
-
const nullifierBytes = bigintTo32Le(nullifier);
|
|
1365
|
-
const memo = input.viewingKey !== void 0 ? Array.from(
|
|
1366
|
-
EncryptedMemo.encrypt(
|
|
1367
|
-
value,
|
|
1368
|
-
bigintTo32Le(ownerPk),
|
|
1369
|
-
bigintTo32Le(blinding),
|
|
1370
|
-
Number(assetId),
|
|
1371
|
-
commitmentBytes,
|
|
1372
|
-
input.viewingKey
|
|
1373
|
-
)
|
|
1374
|
-
) : Array.from(EncryptedMemo.dummy());
|
|
1375
|
-
const note = {
|
|
1376
|
-
value,
|
|
1377
|
-
assetId,
|
|
1378
|
-
ownerPk,
|
|
1379
|
-
blinding,
|
|
1380
|
-
spendingKey,
|
|
1381
|
-
spent: false,
|
|
1382
|
-
spentAt: null,
|
|
1383
|
-
commitment,
|
|
1384
|
-
nullifier,
|
|
1385
|
-
commitmentHex: toHex(commitmentBytes),
|
|
1386
|
-
nullifierHex: toHex(nullifierBytes),
|
|
1387
|
-
memo
|
|
1388
|
-
};
|
|
1389
|
-
return note;
|
|
1390
|
-
}
|
|
1634
|
+
decrypt(memoBytes, commitment, viewingSecretKey) {
|
|
1635
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
|
|
1636
|
+
return EncryptedMemo._decrypt(memoBytes, commitment, viewingSecretKey);
|
|
1637
|
+
},
|
|
1391
1638
|
/**
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1394
|
-
* Pure TypeScript implementation — no WASM dependency.
|
|
1395
|
-
* Uses ChaCha20-Poly1305 with SHA-256 key derivation.
|
|
1639
|
+
* Extract the ECDH shared secret from an encrypted memo using the recipient's viewing secret key.
|
|
1396
1640
|
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
* Pass `new Uint8Array(32)` (default) for a public/dummy memo.
|
|
1400
|
-
*/
|
|
1401
|
-
static buildMemo(note, recipientVk) {
|
|
1402
|
-
return EncryptedMemo.encrypt(
|
|
1403
|
-
note.value,
|
|
1404
|
-
bigintTo32Le(note.ownerPk),
|
|
1405
|
-
bigintTo32Le(note.blinding),
|
|
1406
|
-
Number(note.assetId),
|
|
1407
|
-
bigintTo32Le(note.commitment),
|
|
1408
|
-
recipientVk ?? new Uint8Array(32)
|
|
1409
|
-
);
|
|
1410
|
-
}
|
|
1411
|
-
};
|
|
1412
|
-
|
|
1413
|
-
// src/shielded-pool/ShieldedPoolModule.ts
|
|
1414
|
-
var ShieldedPoolModule = class {
|
|
1415
|
-
constructor(substrate) {
|
|
1416
|
-
this.substrate = substrate;
|
|
1417
|
-
}
|
|
1418
|
-
// ─── Extrinsics ────────────────────────────────────────────────────────────
|
|
1419
|
-
/**
|
|
1420
|
-
* Deposits tokens into the shielded pool.
|
|
1421
|
-
* Extrinsic: shieldedPool.shield(assetId, amount, commitment, encryptedMemo)
|
|
1422
|
-
*/
|
|
1423
|
-
async shield(params, signer) {
|
|
1424
|
-
const memo = params.encryptedMemo ?? EncryptedMemo.dummy();
|
|
1425
|
-
const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "shield");
|
|
1426
|
-
const tx = callUnsafeTx(
|
|
1427
|
-
entry,
|
|
1428
|
-
params.assetId,
|
|
1429
|
-
params.amount.toString(),
|
|
1430
|
-
import_polkadot_api2.Binary.fromHex(params.commitment),
|
|
1431
|
-
import_polkadot_api2.Binary.fromBytes(memo)
|
|
1432
|
-
);
|
|
1433
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1434
|
-
}
|
|
1435
|
-
/**
|
|
1436
|
-
* Build a ZkNote locally and submit shieldedPool.shield in one call.
|
|
1641
|
+
* Used by NoteDecryptor to obtain the shared secret needed for stealth address derivation
|
|
1642
|
+
* without re-running the full decrypt path. Safe to call on any 168-byte memo.
|
|
1437
1643
|
*
|
|
1438
|
-
* Returns
|
|
1439
|
-
*
|
|
1644
|
+
* Returns `new Uint8Array(32)` (all zeros) for public/dummy memos (zero ephPk).
|
|
1645
|
+
* Returns `null` if the memo is malformed or the ephPk is not a valid BJJ point.
|
|
1646
|
+
* Never throws; safe for scan loops.
|
|
1440
1647
|
*
|
|
1441
|
-
* @param
|
|
1442
|
-
* @param
|
|
1443
|
-
* @param params.ownerPk BabyJubJub Ax (default 0n).
|
|
1444
|
-
* @param params.blinding Random blinding scalar (default BigInt(Date.now())).
|
|
1445
|
-
* @param params.spendingKey Secret spending key (default 0n).
|
|
1446
|
-
*/
|
|
1447
|
-
async buildAndShield(params, signer) {
|
|
1448
|
-
const noteInput = {
|
|
1449
|
-
value: params.value,
|
|
1450
|
-
...params.assetId !== void 0 && { assetId: BigInt(params.assetId) },
|
|
1451
|
-
...params.ownerPk !== void 0 && { ownerPk: params.ownerPk },
|
|
1452
|
-
...params.blinding !== void 0 && { blinding: params.blinding },
|
|
1453
|
-
...params.spendingKey !== void 0 && { spendingKey: params.spendingKey }
|
|
1454
|
-
};
|
|
1455
|
-
const note = await NoteBuilder.build(noteInput);
|
|
1456
|
-
const memo = NoteBuilder.buildMemo(note);
|
|
1457
|
-
const txResult = await this.shield(
|
|
1458
|
-
{
|
|
1459
|
-
assetId: Number(note.assetId),
|
|
1460
|
-
amount: note.value,
|
|
1461
|
-
commitment: note.commitmentHex,
|
|
1462
|
-
encryptedMemo: memo
|
|
1463
|
-
},
|
|
1464
|
-
signer
|
|
1465
|
-
);
|
|
1466
|
-
return { txResult, note };
|
|
1467
|
-
}
|
|
1468
|
-
/**
|
|
1469
|
-
* Withdraws tokens from the shielded pool to a public address.
|
|
1470
|
-
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient)
|
|
1471
|
-
*/
|
|
1472
|
-
async unshield(params, signer) {
|
|
1473
|
-
const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "unshield");
|
|
1474
|
-
const tx = callUnsafeTx(
|
|
1475
|
-
entry,
|
|
1476
|
-
import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1477
|
-
import_polkadot_api2.Binary.fromHex(params.merkleRoot),
|
|
1478
|
-
import_polkadot_api2.Binary.fromHex(params.nullifier),
|
|
1479
|
-
params.assetId,
|
|
1480
|
-
params.amount.toString(),
|
|
1481
|
-
import_polkadot_api2.Binary.fromHex(params.recipientAddress)
|
|
1482
|
-
);
|
|
1483
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1484
|
-
}
|
|
1485
|
-
/**
|
|
1486
|
-
* Performs a private (shielded) transfer between two notes.
|
|
1487
|
-
* Extrinsic: shieldedPool.privateTransfer(inputs, outputs, proof, merkleRoot)
|
|
1488
|
-
*/
|
|
1489
|
-
async privateTransfer(params, signer) {
|
|
1490
|
-
const inputs = params.inputs.map((inp) => ({
|
|
1491
|
-
nullifier: import_polkadot_api2.Binary.fromHex(inp.nullifier),
|
|
1492
|
-
commitment: import_polkadot_api2.Binary.fromHex(inp.commitment)
|
|
1493
|
-
}));
|
|
1494
|
-
const outputs = params.outputs.map((out) => ({
|
|
1495
|
-
commitment: import_polkadot_api2.Binary.fromHex(out.commitment),
|
|
1496
|
-
memo: import_polkadot_api2.Binary.fromBytes(out.encryptedMemo ?? EncryptedMemo.dummy())
|
|
1497
|
-
}));
|
|
1498
|
-
const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "privateTransfer");
|
|
1499
|
-
const tx = callUnsafeTx(
|
|
1500
|
-
entry,
|
|
1501
|
-
inputs,
|
|
1502
|
-
outputs,
|
|
1503
|
-
import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1504
|
-
import_polkadot_api2.Binary.fromHex(params.merkleRoot)
|
|
1505
|
-
);
|
|
1506
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1507
|
-
}
|
|
1508
|
-
/**
|
|
1509
|
-
* Deposits multiple notes into the shielded pool in a single extrinsic.
|
|
1510
|
-
* Extrinsic: shieldedPool.shieldBatch(operations) — max 20 items.
|
|
1648
|
+
* @param memoBytes 168-byte encrypted memo.
|
|
1649
|
+
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1511
1650
|
*/
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
}
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1651
|
+
extractSharedSecret(memoBytes, viewingSecretKey) {
|
|
1652
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
|
|
1653
|
+
const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1654
|
+
const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
|
|
1655
|
+
if (ephPkPackedBigint === 0n) {
|
|
1656
|
+
return new Uint8Array(32);
|
|
1657
|
+
}
|
|
1658
|
+
const ephPkPoint = (0, import_baby_jubjub.unpackPoint)(ephPkPackedBigint);
|
|
1659
|
+
if (!ephPkPoint) return null;
|
|
1660
|
+
const ivskScalar = bytesToBjjScalar(viewingSecretKey);
|
|
1661
|
+
const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ephPkPoint, ivskScalar);
|
|
1662
|
+
return bigintTo32Le(sharedPoint[0]);
|
|
1663
|
+
},
|
|
1664
|
+
/** @internal */
|
|
1665
|
+
_decrypt(memoBytes, commitment, viewingSecretKey) {
|
|
1666
|
+
const nonce = memoBytes.slice(0, NONCE_SIZE);
|
|
1667
|
+
const ciphertextWithMac = memoBytes.slice(NONCE_SIZE, NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1668
|
+
const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1669
|
+
const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
|
|
1670
|
+
let sharedSecret;
|
|
1671
|
+
if (ephPkPackedBigint === 0n) {
|
|
1672
|
+
sharedSecret = new Uint8Array(32);
|
|
1673
|
+
} else {
|
|
1674
|
+
const ephPkPoint = (0, import_baby_jubjub.unpackPoint)(ephPkPackedBigint);
|
|
1675
|
+
if (!ephPkPoint) return null;
|
|
1676
|
+
const ivskScalar = bytesToBjjScalar(viewingSecretKey);
|
|
1677
|
+
const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ephPkPoint, ivskScalar);
|
|
1678
|
+
sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1679
|
+
}
|
|
1680
|
+
const encKey = deriveEncryptionKey(sharedSecret, commitment);
|
|
1681
|
+
return parsePlaintext(nonce, ciphertextWithMac, encKey);
|
|
1522
1682
|
}
|
|
1523
1683
|
};
|
|
1524
1684
|
|
|
1525
|
-
// src/account-mapping/AccountMappingModule.ts
|
|
1526
|
-
var import_polkadot_api3 = require("polkadot-api");
|
|
1527
|
-
|
|
1528
1685
|
// src/utils/address.ts
|
|
1529
1686
|
var import_util_crypto = require("@polkadot/util-crypto");
|
|
1530
1687
|
function normalizeEvmAddress(addr) {
|
|
@@ -1661,23 +1818,171 @@ function addressToAccountIdHex(addr) {
|
|
|
1661
1818
|
return substrateSs58ToAccountIdHex(addr);
|
|
1662
1819
|
}
|
|
1663
1820
|
|
|
1664
|
-
// src/
|
|
1665
|
-
|
|
1666
|
-
if (raw === "Eip191" || raw === "eip191") return "Eip191";
|
|
1667
|
-
if (raw === "Ed25519" || raw === "ed25519") return "Ed25519";
|
|
1668
|
-
return raw;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
// src/account-mapping/AccountMappingModule.ts
|
|
1672
|
-
var AccountMappingModule = class {
|
|
1821
|
+
// src/shielded-pool/pallet/ShieldedPoolModule.ts
|
|
1822
|
+
var ShieldedPoolModule = class {
|
|
1673
1823
|
constructor(substrate) {
|
|
1674
1824
|
this.substrate = substrate;
|
|
1675
1825
|
}
|
|
1676
|
-
|
|
1826
|
+
substrate;
|
|
1827
|
+
// ─── Extrinsics ────────────────────────────────────────────────────────────
|
|
1677
1828
|
/**
|
|
1678
|
-
*
|
|
1679
|
-
*
|
|
1680
|
-
*
|
|
1829
|
+
* Deposits tokens into the shielded pool.
|
|
1830
|
+
* Extrinsic: shieldedPool.shield(assetId, amount, commitment, encryptedMemo)
|
|
1831
|
+
*
|
|
1832
|
+
* Shield is always a signed (public) transaction — the caller's address
|
|
1833
|
+
* appears on-chain as the depositor.
|
|
1834
|
+
*/
|
|
1835
|
+
async shield(params, signer, txOptions) {
|
|
1836
|
+
EncryptedMemo.validate(params.encryptedMemo, "shield.encryptedMemo");
|
|
1837
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "shield");
|
|
1838
|
+
const tx = callUnsafeTx(entry, {
|
|
1839
|
+
asset_id: params.assetId,
|
|
1840
|
+
amount: params.amount,
|
|
1841
|
+
commitment: import_polkadot_api2.Binary.fromHex(params.commitment),
|
|
1842
|
+
encrypted_memo: import_polkadot_api2.Binary.fromBytes(params.encryptedMemo)
|
|
1843
|
+
});
|
|
1844
|
+
return toTxResult(
|
|
1845
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Withdraws tokens from the shielded pool to a public address.
|
|
1850
|
+
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
1851
|
+
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
1852
|
+
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee)
|
|
1853
|
+
*/
|
|
1854
|
+
async unshield(params, signer, txOptions) {
|
|
1855
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "unshield");
|
|
1856
|
+
const recipientSs58 = accountIdHexToSs58(params.recipientAddress);
|
|
1857
|
+
if (!recipientSs58) throw new Error(`Invalid recipientAddress: ${params.recipientAddress}`);
|
|
1858
|
+
const changeCommitment = params.changeCommitment ?? "0x" + "00".repeat(32);
|
|
1859
|
+
let changeEncryptedMemo;
|
|
1860
|
+
if (params.changeEncryptedMemo && params.changeEncryptedMemo.length > 0) {
|
|
1861
|
+
EncryptedMemo.validate(params.changeEncryptedMemo, "changeEncryptedMemo");
|
|
1862
|
+
changeEncryptedMemo = import_polkadot_api2.Binary.fromBytes(params.changeEncryptedMemo);
|
|
1863
|
+
} else {
|
|
1864
|
+
changeEncryptedMemo = import_polkadot_api2.Binary.fromBytes(new Uint8Array(0));
|
|
1865
|
+
}
|
|
1866
|
+
const tx = callUnsafeTx(entry, {
|
|
1867
|
+
proof: import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1868
|
+
merkle_root: import_polkadot_api2.Binary.fromHex(params.merkleRoot),
|
|
1869
|
+
nullifier: import_polkadot_api2.Binary.fromHex(params.nullifier),
|
|
1870
|
+
asset_id: params.assetId,
|
|
1871
|
+
amount: params.amount,
|
|
1872
|
+
recipient: recipientSs58,
|
|
1873
|
+
fee: params.fee ?? 0n,
|
|
1874
|
+
change_commitment: import_polkadot_api2.Binary.fromHex(changeCommitment),
|
|
1875
|
+
change_encrypted_memo: changeEncryptedMemo,
|
|
1876
|
+
relayer: void 0
|
|
1877
|
+
// Option<H160> — None for direct Substrate submissions
|
|
1878
|
+
});
|
|
1879
|
+
if (signer) {
|
|
1880
|
+
return toTxResult(
|
|
1881
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
return submitBareTx(tx, this.substrate);
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Performs a private (shielded) transfer between two notes.
|
|
1888
|
+
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
1889
|
+
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
1890
|
+
* Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee)
|
|
1891
|
+
*/
|
|
1892
|
+
async privateTransfer(params, signer, txOptions) {
|
|
1893
|
+
const nullifiers = params.inputs.map((inp) => import_polkadot_api2.Binary.fromHex(inp.nullifier));
|
|
1894
|
+
const commitments = params.outputs.map((out) => import_polkadot_api2.Binary.fromHex(out.commitment));
|
|
1895
|
+
const memos = params.outputs.map((out, i) => {
|
|
1896
|
+
EncryptedMemo.validate(
|
|
1897
|
+
out.encryptedMemo,
|
|
1898
|
+
`privateTransfer.outputs[${i}].encryptedMemo`
|
|
1899
|
+
);
|
|
1900
|
+
return import_polkadot_api2.Binary.fromBytes(out.encryptedMemo);
|
|
1901
|
+
});
|
|
1902
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "private_transfer");
|
|
1903
|
+
const tx = callUnsafeTx(entry, {
|
|
1904
|
+
proof: import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1905
|
+
merkle_root: import_polkadot_api2.Binary.fromHex(params.merkleRoot),
|
|
1906
|
+
nullifiers,
|
|
1907
|
+
commitments,
|
|
1908
|
+
encrypted_memos: memos,
|
|
1909
|
+
asset_id: params.assetId,
|
|
1910
|
+
fee: params.fee ?? 0n,
|
|
1911
|
+
relayer: void 0
|
|
1912
|
+
// Option<H160> — None for direct Substrate submissions
|
|
1913
|
+
});
|
|
1914
|
+
if (signer) {
|
|
1915
|
+
return toTxResult(
|
|
1916
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
return submitBareTx(tx, this.substrate);
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Deposits multiple notes into the shielded pool in a single extrinsic.
|
|
1923
|
+
* Extrinsic: shieldedPool.shieldBatch(operations) — max 20 items.
|
|
1924
|
+
*/
|
|
1925
|
+
async shieldBatch(params, signer, txOptions) {
|
|
1926
|
+
const operations = params.items.map((item, i) => {
|
|
1927
|
+
EncryptedMemo.validate(item.encryptedMemo, `shieldBatch.items[${i}].encryptedMemo`);
|
|
1928
|
+
return {
|
|
1929
|
+
assetId: item.assetId,
|
|
1930
|
+
amount: item.amount.toString(),
|
|
1931
|
+
commitment: import_polkadot_api2.Binary.fromHex(item.commitment),
|
|
1932
|
+
encryptedMemo: import_polkadot_api2.Binary.fromBytes(item.encryptedMemo)
|
|
1933
|
+
};
|
|
1934
|
+
});
|
|
1935
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "shield_batch");
|
|
1936
|
+
const tx = callUnsafeTx(entry, operations);
|
|
1937
|
+
return toTxResult(
|
|
1938
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Claims accrued relay fees into the shielded pool.
|
|
1943
|
+
* This is a SIGNED transaction — the relayer must sign it with their wallet.
|
|
1944
|
+
* Before calling this, generate a ZK value proof with generateFeeClaimProof() (not yet implemented).
|
|
1945
|
+
*
|
|
1946
|
+
* Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals)
|
|
1947
|
+
*/
|
|
1948
|
+
async claimShieldedFees(params, signer, txOptions) {
|
|
1949
|
+
EncryptedMemo.validate(params.encryptedMemo, "claimShieldedFees.encryptedMemo");
|
|
1950
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "claim_shielded_fees");
|
|
1951
|
+
const tx = callUnsafeTx(entry, {
|
|
1952
|
+
commitment: import_polkadot_api2.Binary.fromHex(params.commitment),
|
|
1953
|
+
amount: params.amount,
|
|
1954
|
+
asset_id: params.assetId,
|
|
1955
|
+
encrypted_memo: import_polkadot_api2.Binary.fromBytes(params.encryptedMemo),
|
|
1956
|
+
proof: import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1957
|
+
public_signals: import_polkadot_api2.Binary.fromBytes(params.publicSignals)
|
|
1958
|
+
});
|
|
1959
|
+
return toTxResult(
|
|
1960
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
|
|
1965
|
+
// src/account-mapping/AccountMappingModule.ts
|
|
1966
|
+
var import_polkadot_api3 = require("polkadot-api");
|
|
1967
|
+
|
|
1968
|
+
// src/account-mapping/helpers.ts
|
|
1969
|
+
function mapRawScheme(raw) {
|
|
1970
|
+
if (raw === "Eip191" || raw === "eip191") return "Eip191";
|
|
1971
|
+
if (raw === "Ed25519" || raw === "ed25519") return "Ed25519";
|
|
1972
|
+
return raw;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// src/account-mapping/AccountMappingModule.ts
|
|
1976
|
+
var AccountMappingModule = class {
|
|
1977
|
+
constructor(substrate) {
|
|
1978
|
+
this.substrate = substrate;
|
|
1979
|
+
}
|
|
1980
|
+
substrate;
|
|
1981
|
+
// ─── Address resolution ─────────────────────────────────────────────────────
|
|
1982
|
+
/**
|
|
1983
|
+
* Returns the explicitly mapped (or fallback) Substrate AccountId32 hex for
|
|
1984
|
+
* an EVM address. `mapped` is set only when `map_account` was called.
|
|
1985
|
+
* `fallback` is always the EeSuffix rule: `H160 ++ [0x00; 12]`.
|
|
1681
1986
|
*/
|
|
1682
1987
|
async getAccountAddresses(accountId) {
|
|
1683
1988
|
try {
|
|
@@ -2058,6 +2363,7 @@ var PrivacyModule = class {
|
|
|
2058
2363
|
constructor(substrate) {
|
|
2059
2364
|
this.substrate = substrate;
|
|
2060
2365
|
}
|
|
2366
|
+
substrate;
|
|
2061
2367
|
/** Returns the current Merkle tree root. */
|
|
2062
2368
|
async getMerkleRoot() {
|
|
2063
2369
|
return this.substrate.request("privacy_getMerkleRoot", []);
|
|
@@ -2075,14 +2381,23 @@ var PrivacyModule = class {
|
|
|
2075
2381
|
}
|
|
2076
2382
|
/**
|
|
2077
2383
|
* Returns the Merkle inclusion proof for a given commitment hex,
|
|
2078
|
-
* bundled with the
|
|
2384
|
+
* bundled with the Merkle root.
|
|
2385
|
+
*
|
|
2386
|
+
* Uses `privacy_getMerkleProofByCommitment` which resolves root and proof
|
|
2387
|
+
* under the **same block hash**, guaranteeing that the returned path is
|
|
2388
|
+
* consistent with the returned root.
|
|
2079
2389
|
*/
|
|
2080
2390
|
async getMerkleProofByCommitment(commitmentHex) {
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
return {
|
|
2391
|
+
const raw = await this.substrate.request(
|
|
2392
|
+
"privacy_getMerkleProofByCommitment",
|
|
2393
|
+
[commitmentHex]
|
|
2394
|
+
);
|
|
2395
|
+
return {
|
|
2396
|
+
path: raw.path,
|
|
2397
|
+
leafIndex: raw.leaf_index,
|
|
2398
|
+
treeDepth: raw.tree_depth,
|
|
2399
|
+
root: raw.root
|
|
2400
|
+
};
|
|
2086
2401
|
}
|
|
2087
2402
|
/** Returns the spend status of a nullifier. */
|
|
2088
2403
|
async getNullifierStatus(nullifier) {
|
|
@@ -2101,6 +2416,7 @@ var PrivacyModule = class {
|
|
|
2101
2416
|
return {
|
|
2102
2417
|
merkleRoot: raw.merkle_root,
|
|
2103
2418
|
commitmentCount: raw.commitment_count,
|
|
2419
|
+
nullifierCount: raw.nullifier_count,
|
|
2104
2420
|
totalBalance: raw.total_balance.toString(),
|
|
2105
2421
|
assetBalances: raw.asset_balances.map(mapAssetBalance),
|
|
2106
2422
|
treeDepth: raw.tree_depth
|
|
@@ -2126,6 +2442,7 @@ var ZkVerifierModule = class {
|
|
|
2126
2442
|
constructor(substrate) {
|
|
2127
2443
|
this.substrate = substrate;
|
|
2128
2444
|
}
|
|
2445
|
+
substrate;
|
|
2129
2446
|
/** Returns basic version info for all registered circuits. */
|
|
2130
2447
|
async getAllCircuitVersions() {
|
|
2131
2448
|
const raw = await this.substrate.request(
|
|
@@ -2144,6 +2461,48 @@ var ZkVerifierModule = class {
|
|
|
2144
2461
|
}
|
|
2145
2462
|
};
|
|
2146
2463
|
|
|
2464
|
+
// src/relayer/RelayerStatusModule.ts
|
|
2465
|
+
var RelayerStatusModule = class {
|
|
2466
|
+
constructor(substrate) {
|
|
2467
|
+
this.substrate = substrate;
|
|
2468
|
+
}
|
|
2469
|
+
substrate;
|
|
2470
|
+
/**
|
|
2471
|
+
* Returns true if the given SS58 address is a registered relayer.
|
|
2472
|
+
*/
|
|
2473
|
+
async isRelayer(ss58Address) {
|
|
2474
|
+
return this.substrate.request("relayer_isRelayer", [ss58Address]);
|
|
2475
|
+
}
|
|
2476
|
+
/**
|
|
2477
|
+
* Returns the pending fees (in planck) for the given account and asset.
|
|
2478
|
+
* The node returns the value as a decimal string to avoid u128 overflow.
|
|
2479
|
+
*/
|
|
2480
|
+
async pendingFees(ss58Address, assetId) {
|
|
2481
|
+
const raw = await this.substrate.request("relayer_pendingFees", [
|
|
2482
|
+
ss58Address,
|
|
2483
|
+
assetId
|
|
2484
|
+
]);
|
|
2485
|
+
return BigInt(raw);
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Returns the registered EVM address (0x-prefixed) for the given account,
|
|
2489
|
+
* or null if the account is not a registered relayer.
|
|
2490
|
+
*/
|
|
2491
|
+
async registeredEvmAddress(ss58Address) {
|
|
2492
|
+
return this.substrate.request("relayer_registeredEvmAddress", [ss58Address]);
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* Convenience method: returns relayer registry info for an account.
|
|
2496
|
+
*/
|
|
2497
|
+
async getRelayerInfo(ss58Address) {
|
|
2498
|
+
const [isRelayer, evmAddress] = await Promise.all([
|
|
2499
|
+
this.isRelayer(ss58Address),
|
|
2500
|
+
this.registeredEvmAddress(ss58Address)
|
|
2501
|
+
]);
|
|
2502
|
+
return { isRelayer, evmAddress };
|
|
2503
|
+
}
|
|
2504
|
+
};
|
|
2505
|
+
|
|
2147
2506
|
// src/precompiles/helpers.ts
|
|
2148
2507
|
var STATIC_TYPES = /* @__PURE__ */ new Set(["uint", "bytes32", "address", "bool"]);
|
|
2149
2508
|
function concat(arrays) {
|
|
@@ -2287,10 +2646,6 @@ function decodeBytes(data, slotOffset = 0) {
|
|
|
2287
2646
|
function decodeString(data, slotOffset = 0) {
|
|
2288
2647
|
return new TextDecoder().decode(decodeBytes(data, slotOffset));
|
|
2289
2648
|
}
|
|
2290
|
-
function hexToBytes(hex) {
|
|
2291
|
-
if (hex === "0x" || hex === "") return new Uint8Array(0);
|
|
2292
|
-
return fromHex(hex.startsWith("0x") ? hex : "0x" + hex);
|
|
2293
|
-
}
|
|
2294
2649
|
|
|
2295
2650
|
// src/precompiles/addresses.ts
|
|
2296
2651
|
var PRECOMPILE_ADDR = {
|
|
@@ -2349,12 +2704,14 @@ var AM_SEL = {
|
|
|
2349
2704
|
SET_ACCOUNT_METADATA: new Uint8Array([119, 108, 249, 255])
|
|
2350
2705
|
};
|
|
2351
2706
|
var SP_SEL = {
|
|
2352
|
-
// shield(uint32,
|
|
2353
|
-
SHIELD: new Uint8Array([
|
|
2354
|
-
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[])
|
|
2355
|
-
PRIVATE_TRANSFER: new Uint8Array([
|
|
2356
|
-
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32)
|
|
2357
|
-
UNSHIELD: new Uint8Array([
|
|
2707
|
+
// shield(uint32,bytes32,bytes) → 0x9feb22ea (payable, amount = msg.value)
|
|
2708
|
+
SHIELD: new Uint8Array([159, 235, 34, 234]),
|
|
2709
|
+
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256) → 0x8c0f5d24
|
|
2710
|
+
PRIVATE_TRANSFER: new Uint8Array([140, 15, 93, 36]),
|
|
2711
|
+
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32) → 0xd21d9a79
|
|
2712
|
+
UNSHIELD: new Uint8Array([210, 29, 154, 121]),
|
|
2713
|
+
// claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes) → 0x42e1e74c
|
|
2714
|
+
CLAIM_SHIELDED_FEES: new Uint8Array([66, 225, 231, 76])
|
|
2358
2715
|
};
|
|
2359
2716
|
var KNOWN_PRECOMPILES = {
|
|
2360
2717
|
// ── Ethereum standard (EIP) ─────────────────────────────────────────────
|
|
@@ -2397,9 +2754,10 @@ var KNOWN_PRECOMPILES = {
|
|
|
2397
2754
|
"0x0000000000000000000000000000000000000801": {
|
|
2398
2755
|
name: "ShieldedPool",
|
|
2399
2756
|
functions: {
|
|
2400
|
-
"
|
|
2401
|
-
|
|
2402
|
-
|
|
2757
|
+
"9feb22ea": "shield(uint32,bytes32,bytes)",
|
|
2758
|
+
"8c0f5d24": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)",
|
|
2759
|
+
d21d9a79: "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32)",
|
|
2760
|
+
"42e1e74c": "claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)"
|
|
2403
2761
|
}
|
|
2404
2762
|
}
|
|
2405
2763
|
};
|
|
@@ -2413,43 +2771,58 @@ var ShieldedPoolPrecompile = class {
|
|
|
2413
2771
|
constructor(evm) {
|
|
2414
2772
|
this.evm = evm;
|
|
2415
2773
|
}
|
|
2774
|
+
evm;
|
|
2416
2775
|
addr = PRECOMPILE_ADDR.SHIELDED_POOL;
|
|
2417
2776
|
// ─── shield ────────────────────────────────────────────────────────────────
|
|
2418
2777
|
/**
|
|
2419
|
-
* Returns the ABI-encoded calldata for `shield(uint32,
|
|
2420
|
-
*
|
|
2778
|
+
* Returns the ABI-encoded calldata for `shield(uint32, bytes32, bytes)`.
|
|
2779
|
+
* The token amount must be sent as `msg.value` (the `value` field of the EVM
|
|
2780
|
+
* transaction) — this is what MetaMask and other wallets display to the user.
|
|
2421
2781
|
*/
|
|
2422
2782
|
buildShieldCalldata(params) {
|
|
2423
|
-
|
|
2783
|
+
EncryptedMemo.validate(params.encryptedMemo, "buildShieldCalldata.encryptedMemo");
|
|
2424
2784
|
const commitment = fromHex(params.commitment);
|
|
2425
2785
|
return encodeHex(
|
|
2426
2786
|
SP_SEL.SHIELD,
|
|
2427
2787
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2428
|
-
{ type: "uint", value: params.amount },
|
|
2429
2788
|
{ type: "bytes32", value: commitment },
|
|
2430
|
-
{ type: "bytes", value:
|
|
2789
|
+
{ type: "bytes", value: params.encryptedMemo }
|
|
2431
2790
|
);
|
|
2432
2791
|
}
|
|
2433
2792
|
/**
|
|
2434
|
-
* Deposits tokens into the shielded pool from
|
|
2793
|
+
* Deposits tokens into the shielded pool from a payable EVM transaction.
|
|
2435
2794
|
*
|
|
2436
|
-
* The
|
|
2437
|
-
*
|
|
2795
|
+
* The token amount is sent as `msg.value` so EVM wallets (MetaMask, etc.) display
|
|
2796
|
+
* the correct amount on the confirmation screen. The precompile dispatches
|
|
2797
|
+
* `shieldedPool.shield` with its own address as origin, so the funds flow:
|
|
2798
|
+
* caller → precompile (via msg.value, handled by EVM)
|
|
2799
|
+
* precompile → pool (via pallet transfer)
|
|
2800
|
+
* This avoids double-deduction while keeping the displayed amount accurate.
|
|
2438
2801
|
*
|
|
2439
2802
|
* Extrinsic: `shieldedPool.shield(assetId, amount, commitment, encryptedMemo)`
|
|
2440
2803
|
*/
|
|
2441
2804
|
async shield(params, signer) {
|
|
2442
|
-
return signer({
|
|
2805
|
+
return signer({
|
|
2806
|
+
to: this.addr,
|
|
2807
|
+
data: this.buildShieldCalldata(params),
|
|
2808
|
+
value: params.amount
|
|
2809
|
+
});
|
|
2443
2810
|
}
|
|
2444
2811
|
// ─── privateTransfer ───────────────────────────────────────────────────────
|
|
2445
2812
|
/**
|
|
2446
2813
|
* Returns the ABI-encoded calldata for
|
|
2447
|
-
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[])`.
|
|
2814
|
+
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256)`.
|
|
2448
2815
|
*/
|
|
2449
2816
|
buildPrivateTransferCalldata(params) {
|
|
2450
2817
|
const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
|
|
2451
2818
|
const commitments = params.outputs.map((o) => fromHex(o.commitment));
|
|
2452
|
-
const memos = params.outputs.map((o) =>
|
|
2819
|
+
const memos = params.outputs.map((o, i) => {
|
|
2820
|
+
EncryptedMemo.validate(
|
|
2821
|
+
o.encryptedMemo,
|
|
2822
|
+
`buildPrivateTransferCalldata.outputs[${i}].encryptedMemo`
|
|
2823
|
+
);
|
|
2824
|
+
return o.encryptedMemo;
|
|
2825
|
+
});
|
|
2453
2826
|
const root = fromHex(params.merkleRoot);
|
|
2454
2827
|
return encodeHex(
|
|
2455
2828
|
SP_SEL.PRIVATE_TRANSFER,
|
|
@@ -2457,7 +2830,9 @@ var ShieldedPoolPrecompile = class {
|
|
|
2457
2830
|
{ type: "bytes32", value: root },
|
|
2458
2831
|
{ type: "bytes32[]", value: nullifiers },
|
|
2459
2832
|
{ type: "bytes32[]", value: commitments },
|
|
2460
|
-
{ type: "bytes[]", value: memos }
|
|
2833
|
+
{ type: "bytes[]", value: memos },
|
|
2834
|
+
{ type: "uint", value: BigInt(params.assetId) },
|
|
2835
|
+
{ type: "uint", value: params.fee ?? 0n }
|
|
2461
2836
|
);
|
|
2462
2837
|
}
|
|
2463
2838
|
/**
|
|
@@ -2485,6 +2860,9 @@ var ShieldedPoolPrecompile = class {
|
|
|
2485
2860
|
const recipientBytes = fromHex(
|
|
2486
2861
|
"0x" + (recipientRaw.length === 64 ? recipientRaw : recipientRaw.padEnd(64, "0"))
|
|
2487
2862
|
);
|
|
2863
|
+
const changeCommitmentHex = params.changeCommitment ?? "0x" + "00".repeat(32);
|
|
2864
|
+
const changeCommitment = fromHex(changeCommitmentHex);
|
|
2865
|
+
const changeEncryptedMemo = params.changeEncryptedMemo ?? new Uint8Array();
|
|
2488
2866
|
return encodeHex(
|
|
2489
2867
|
SP_SEL.UNSHIELD,
|
|
2490
2868
|
{ type: "bytes", value: proof },
|
|
@@ -2492,7 +2870,10 @@ var ShieldedPoolPrecompile = class {
|
|
|
2492
2870
|
{ type: "bytes32", value: nullifier },
|
|
2493
2871
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2494
2872
|
{ type: "uint", value: params.amount },
|
|
2495
|
-
{ type: "bytes32", value: recipientBytes }
|
|
2873
|
+
{ type: "bytes32", value: recipientBytes },
|
|
2874
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
2875
|
+
{ type: "bytes32", value: changeCommitment },
|
|
2876
|
+
{ type: "bytes", value: changeEncryptedMemo }
|
|
2496
2877
|
);
|
|
2497
2878
|
}
|
|
2498
2879
|
/**
|
|
@@ -2539,6 +2920,78 @@ var ShieldedPoolPrecompile = class {
|
|
|
2539
2920
|
data: this.buildUnshieldCalldata(params)
|
|
2540
2921
|
});
|
|
2541
2922
|
}
|
|
2923
|
+
// ─── claimShieldedFees ───────────────────────────────────────────────────────────────────
|
|
2924
|
+
/**
|
|
2925
|
+
* Returns the ABI-encoded calldata for
|
|
2926
|
+
* `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)`.
|
|
2927
|
+
*
|
|
2928
|
+
* ABI layout (params after selector):
|
|
2929
|
+
* - `commitment` — bytes32 (fixed)
|
|
2930
|
+
* - `amount` — uint256 (fixed)
|
|
2931
|
+
* - `asset_id` — uint32 (fixed, right-aligned)
|
|
2932
|
+
* - `memo` — bytes (dynamic)
|
|
2933
|
+
* - `proof` — bytes (dynamic, 128 bytes Groth16)
|
|
2934
|
+
* - `publicSignals` — bytes (dynamic, 76 bytes)
|
|
2935
|
+
*
|
|
2936
|
+
* The validator identity is derived from `msg.sender` in the precompile —
|
|
2937
|
+
* do NOT include it in the calldata.
|
|
2938
|
+
*/
|
|
2939
|
+
buildClaimShieldedFeesCalldata(params) {
|
|
2940
|
+
EncryptedMemo.validate(
|
|
2941
|
+
params.encryptedMemo,
|
|
2942
|
+
"buildClaimShieldedFeesCalldata.encryptedMemo"
|
|
2943
|
+
);
|
|
2944
|
+
if (params.proof.length === 0) {
|
|
2945
|
+
throw new Error("claimShieldedFees: proof must not be empty");
|
|
2946
|
+
}
|
|
2947
|
+
if (params.publicSignals.length !== 76) {
|
|
2948
|
+
throw new Error(
|
|
2949
|
+
`claimShieldedFees: publicSignals must be 76 bytes, got ${params.publicSignals.length}`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
const commitment = fromHex(params.commitment);
|
|
2953
|
+
return encodeHex(
|
|
2954
|
+
SP_SEL.CLAIM_SHIELDED_FEES,
|
|
2955
|
+
{ type: "bytes32", value: commitment },
|
|
2956
|
+
{ type: "uint", value: params.amount },
|
|
2957
|
+
{ type: "uint", value: BigInt(params.assetId) },
|
|
2958
|
+
{ type: "bytes", value: params.encryptedMemo },
|
|
2959
|
+
{ type: "bytes", value: params.proof },
|
|
2960
|
+
{ type: "bytes", value: params.publicSignals }
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Claims accumulated relay fees as a private shielded note.
|
|
2965
|
+
*
|
|
2966
|
+
* This extrinsic is for **validators/relayers** who have accrued fees in
|
|
2967
|
+
* `pallet-relayer` and want to receive them privately inside the shielded pool
|
|
2968
|
+
* instead of as a public balance credit.
|
|
2969
|
+
*
|
|
2970
|
+
* The ZK `value_proof` binds `commitment` to `(amount, assetId, ownerPk, blinding)`
|
|
2971
|
+
* so the runtime can verify the note encodes exactly the claimed fee amount,
|
|
2972
|
+
* preventing a malicious relayer from inflating the withdrawal.
|
|
2973
|
+
*
|
|
2974
|
+
* The `msg.sender` EVM address is used as the validator identity; it must match
|
|
2975
|
+
* the address that has pending relay fees in `pallet-relayer`.
|
|
2976
|
+
*
|
|
2977
|
+
* Extrinsic: `shieldedPool.claim_shielded_fees(commitment, amount, assetId, memo, proof, publicSignals)`
|
|
2978
|
+
*/
|
|
2979
|
+
async claimShieldedFees(params, signer) {
|
|
2980
|
+
return signer({
|
|
2981
|
+
to: this.addr,
|
|
2982
|
+
data: this.buildClaimShieldedFeesCalldata(params)
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Estimates the EVM gas for a `claimShieldedFees` call.
|
|
2987
|
+
*/
|
|
2988
|
+
async estimateClaimShieldedFeesGas(params, from) {
|
|
2989
|
+
return this.evm.estimateGas({
|
|
2990
|
+
from,
|
|
2991
|
+
to: this.addr,
|
|
2992
|
+
data: this.buildClaimShieldedFeesCalldata(params)
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2542
2995
|
};
|
|
2543
2996
|
|
|
2544
2997
|
// src/precompiles/AccountMappingPrecompile.ts
|
|
@@ -2546,6 +2999,7 @@ var AccountMappingPrecompile = class {
|
|
|
2546
2999
|
constructor(evm) {
|
|
2547
3000
|
this.evm = evm;
|
|
2548
3001
|
}
|
|
3002
|
+
evm;
|
|
2549
3003
|
addr = PRECOMPILE_ADDR.ACCOUNT_MAPPING;
|
|
2550
3004
|
// ─── Read-only ─────────────────────────────────────────────────────────────
|
|
2551
3005
|
/**
|
|
@@ -2557,7 +3011,7 @@ var AccountMappingPrecompile = class {
|
|
|
2557
3011
|
async resolveAlias(alias) {
|
|
2558
3012
|
try {
|
|
2559
3013
|
const data = encodeHex(AM_SEL.RESOLVE_ALIAS, { type: "string", value: alias });
|
|
2560
|
-
const raw =
|
|
3014
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2561
3015
|
if (raw.length < 64) return null;
|
|
2562
3016
|
const owner = decodeAddress2(raw, 0);
|
|
2563
3017
|
const evm = decodeAddress2(raw, 32);
|
|
@@ -2580,7 +3034,7 @@ var AccountMappingPrecompile = class {
|
|
|
2580
3034
|
type: "address",
|
|
2581
3035
|
value: normalizeEvmAddress(evmAddress)
|
|
2582
3036
|
});
|
|
2583
|
-
const raw =
|
|
3037
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2584
3038
|
if (raw.length === 0) return null;
|
|
2585
3039
|
const alias = decodeString(raw, 0);
|
|
2586
3040
|
return alias.length > 0 ? alias : null;
|
|
@@ -2600,7 +3054,7 @@ var AccountMappingPrecompile = class {
|
|
|
2600
3054
|
{ type: "string", value: alias },
|
|
2601
3055
|
{ type: "bytes32", value: commitmentBytes }
|
|
2602
3056
|
);
|
|
2603
|
-
const raw =
|
|
3057
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2604
3058
|
if (raw.length < 32) return false;
|
|
2605
3059
|
return decodeBool(raw, 0);
|
|
2606
3060
|
} catch {
|
|
@@ -2805,6 +3259,7 @@ var CryptoPrecompiles = class {
|
|
|
2805
3259
|
constructor(evm) {
|
|
2806
3260
|
this.evm = evm;
|
|
2807
3261
|
}
|
|
3262
|
+
evm;
|
|
2808
3263
|
// ─── ECRecover (0x0001) ───────────────────────────────────────────────────
|
|
2809
3264
|
/**
|
|
2810
3265
|
* Recovers the Ethereum address from an ECDSA signature.
|
|
@@ -2820,7 +3275,7 @@ var CryptoPrecompiles = class {
|
|
|
2820
3275
|
input[63] = v;
|
|
2821
3276
|
input.set(r.slice(0, 32), 64);
|
|
2822
3277
|
input.set(s.slice(0, 32), 96);
|
|
2823
|
-
const raw =
|
|
3278
|
+
const raw = fromHex(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER, toHex(input)));
|
|
2824
3279
|
if (raw.length < 32) return "0x" + "00".repeat(20);
|
|
2825
3280
|
return "0x" + toHex(raw.slice(12, 32)).slice(2);
|
|
2826
3281
|
}
|
|
@@ -2836,7 +3291,7 @@ var CryptoPrecompiles = class {
|
|
|
2836
3291
|
input[63] = v;
|
|
2837
3292
|
input.set(r.slice(0, 32), 64);
|
|
2838
3293
|
input.set(s.slice(0, 32), 96);
|
|
2839
|
-
return
|
|
3294
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER_PUBKEY, toHex(input)));
|
|
2840
3295
|
}
|
|
2841
3296
|
// ─── SHA-256 (0x0002) ─────────────────────────────────────────────────────
|
|
2842
3297
|
/**
|
|
@@ -2844,7 +3299,7 @@ var CryptoPrecompiles = class {
|
|
|
2844
3299
|
* Returns a 32-byte digest.
|
|
2845
3300
|
*/
|
|
2846
3301
|
async sha256(data) {
|
|
2847
|
-
return
|
|
3302
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.SHA256, toHex(data)));
|
|
2848
3303
|
}
|
|
2849
3304
|
// ─── RIPEMD-160 (0x0003) ──────────────────────────────────────────────────
|
|
2850
3305
|
/**
|
|
@@ -2852,7 +3307,7 @@ var CryptoPrecompiles = class {
|
|
|
2852
3307
|
* Returns the 20-byte digest right-padded to 32 bytes (standard ABI output).
|
|
2853
3308
|
*/
|
|
2854
3309
|
async ripemd160(data) {
|
|
2855
|
-
const raw =
|
|
3310
|
+
const raw = fromHex(await this.evm.call(PRECOMPILE_ADDR.RIPEMD160, toHex(data)));
|
|
2856
3311
|
return raw.length >= 32 ? raw.slice(12, 32) : raw;
|
|
2857
3312
|
}
|
|
2858
3313
|
// ─── Identity (0x0004) ────────────────────────────────────────────────────
|
|
@@ -2861,7 +3316,7 @@ var CryptoPrecompiles = class {
|
|
|
2861
3316
|
* Mainly useful for gas benchmarking.
|
|
2862
3317
|
*/
|
|
2863
3318
|
async identity(data) {
|
|
2864
|
-
return
|
|
3319
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.IDENTITY, toHex(data)));
|
|
2865
3320
|
}
|
|
2866
3321
|
// ─── SHA3-FIPS-256 / Keccak-256 (0x0400) ─────────────────────────────────
|
|
2867
3322
|
/**
|
|
@@ -2869,7 +3324,7 @@ var CryptoPrecompiles = class {
|
|
|
2869
3324
|
* Returns a 32-byte digest.
|
|
2870
3325
|
*/
|
|
2871
3326
|
async keccak256(data) {
|
|
2872
|
-
return
|
|
3327
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.SHA3_FIPS256, toHex(data)));
|
|
2873
3328
|
}
|
|
2874
3329
|
// ─── Curve25519 / Ristretto (0x0402, 0x0403) ─────────────────────────────
|
|
2875
3330
|
/**
|
|
@@ -2892,7 +3347,7 @@ var CryptoPrecompiles = class {
|
|
|
2892
3347
|
}
|
|
2893
3348
|
input.set(pt, i * 32);
|
|
2894
3349
|
}
|
|
2895
|
-
return
|
|
3350
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_ADD, toHex(input)));
|
|
2896
3351
|
}
|
|
2897
3352
|
/**
|
|
2898
3353
|
* Multiplies a Ristretto compressed point by a scalar via EVM precompile.
|
|
@@ -2908,39 +3363,44 @@ var CryptoPrecompiles = class {
|
|
|
2908
3363
|
const input = new Uint8Array(64);
|
|
2909
3364
|
input.set(scalar, 0);
|
|
2910
3365
|
input.set(point, 32);
|
|
2911
|
-
return
|
|
3366
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_SCALAR_MUL, toHex(input)));
|
|
2912
3367
|
}
|
|
2913
3368
|
};
|
|
2914
3369
|
|
|
2915
3370
|
// src/client/OrbinumClient.ts
|
|
2916
3371
|
var OrbinumClient = class _OrbinumClient {
|
|
2917
|
-
/** Raw
|
|
3372
|
+
/** Raw Substrate WebSocket connection — use for custom RPC calls or low-level access. */
|
|
2918
3373
|
substrate;
|
|
2919
|
-
/** Raw
|
|
3374
|
+
/** Raw EVM HTTP JSON-RPC client. `null` when `evmRpc` is not configured. */
|
|
2920
3375
|
evm;
|
|
2921
3376
|
/**
|
|
2922
|
-
* High-level EVM block and transaction explorer
|
|
3377
|
+
* High-level EVM block and transaction explorer.
|
|
2923
3378
|
* Provides enriched queries for blocks, transactions, addresses, and token transfers.
|
|
3379
|
+
* `null` when `evmRpc` is not configured.
|
|
2924
3380
|
*/
|
|
2925
3381
|
evmExplorer;
|
|
2926
3382
|
/**
|
|
2927
|
-
* HTTP client for the Orbinum indexer REST API
|
|
3383
|
+
* HTTP client for the Orbinum indexer REST API.
|
|
2928
3384
|
* Provides paginated access to indexed blocks, extrinsics, shielded events, and nullifiers.
|
|
3385
|
+
* `null` when `indexerUrl` is not configured.
|
|
2929
3386
|
*/
|
|
2930
3387
|
indexer;
|
|
2931
|
-
/** Shielded-pool
|
|
3388
|
+
/** Shielded-pool extrinsics and Merkle tree queries (`shield`, `unshield`, `privateTransfer`, …). */
|
|
2932
3389
|
shieldedPool;
|
|
2933
|
-
/** Account
|
|
3390
|
+
/** Account-mapping extrinsics: aliases, chain links, metadata, marketplace, and identity. */
|
|
2934
3391
|
accountMapping;
|
|
2935
|
-
/** Typed access to
|
|
3392
|
+
/** Typed access to `privacy_*` custom RPC endpoints. */
|
|
2936
3393
|
privacy;
|
|
2937
|
-
/** Typed access to zkVerifier_
|
|
3394
|
+
/** Typed access to `zkVerifier_*` custom RPC endpoints. */
|
|
2938
3395
|
zkVerifier;
|
|
3396
|
+
/** Typed access to `relayer_*` RPC endpoints (registry lookup and pending fee queries). */
|
|
3397
|
+
relayerStatus;
|
|
2939
3398
|
/**
|
|
2940
|
-
*
|
|
2941
|
-
*
|
|
3399
|
+
* Precompile modules for interacting with Orbinum contracts from an EVM wallet.
|
|
3400
|
+
* `null` when `evmRpc` is not configured. Methods on each sub-module throw if `evm` is `null`.
|
|
2942
3401
|
*/
|
|
2943
3402
|
precompiles;
|
|
3403
|
+
/** @internal Use `OrbinumClient.connect()` to obtain an instance. */
|
|
2944
3404
|
constructor(substrate, evm, indexer) {
|
|
2945
3405
|
this.substrate = substrate;
|
|
2946
3406
|
this.evm = evm;
|
|
@@ -2950,6 +3410,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2950
3410
|
this.accountMapping = new AccountMappingModule(substrate);
|
|
2951
3411
|
this.privacy = new PrivacyModule(substrate);
|
|
2952
3412
|
this.zkVerifier = new ZkVerifierModule(substrate);
|
|
3413
|
+
this.relayerStatus = new RelayerStatusModule(substrate);
|
|
2953
3414
|
this.precompiles = evm ? {
|
|
2954
3415
|
shieldedPool: new ShieldedPoolPrecompile(evm),
|
|
2955
3416
|
accountMapping: new AccountMappingPrecompile(evm),
|
|
@@ -2957,8 +3418,10 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2957
3418
|
} : null;
|
|
2958
3419
|
}
|
|
2959
3420
|
/**
|
|
2960
|
-
*
|
|
2961
|
-
*
|
|
3421
|
+
* Creates and connects an `OrbinumClient` from the given configuration.
|
|
3422
|
+
*
|
|
3423
|
+
* Establishes the Substrate WebSocket connection and, if configured, instantiates
|
|
3424
|
+
* the EVM and indexer clients. Throws if the node is unreachable within `connectTimeoutMs`.
|
|
2962
3425
|
*/
|
|
2963
3426
|
static async connect(config) {
|
|
2964
3427
|
const substrate = await SubstrateClient.connect(
|
|
@@ -2969,7 +3432,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2969
3432
|
const indexer = config.indexerUrl ? new IndexerClient({ baseUrl: config.indexerUrl }) : null;
|
|
2970
3433
|
return new _OrbinumClient(substrate, evm, indexer);
|
|
2971
3434
|
}
|
|
2972
|
-
/** Closes the WebSocket connection
|
|
3435
|
+
/** Closes the underlying Substrate WebSocket connection and releases all resources. */
|
|
2973
3436
|
destroy() {
|
|
2974
3437
|
this.substrate.destroy();
|
|
2975
3438
|
}
|
|
@@ -2998,6 +3461,7 @@ var OrbinumClientProvider = class {
|
|
|
2998
3461
|
_reconnectAttempt = 0;
|
|
2999
3462
|
// ─── Events ─────────────────────────────────────────────────────────────
|
|
3000
3463
|
_listeners = /* @__PURE__ */ new Set();
|
|
3464
|
+
/** Creates a new provider with the given configuration. Does not connect automatically — call `connect()` to initiate. */
|
|
3001
3465
|
constructor(config) {
|
|
3002
3466
|
this.config = config;
|
|
3003
3467
|
this.connectTimeoutMs = config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
@@ -3007,9 +3471,11 @@ var OrbinumClientProvider = class {
|
|
|
3007
3471
|
this.reconnectMaxMs = config.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
3008
3472
|
}
|
|
3009
3473
|
// ─── Status ─────────────────────────────────────────────────────────────
|
|
3474
|
+
/** Current connection status. Reflects the last state set by the provider internals. */
|
|
3010
3475
|
get status() {
|
|
3011
3476
|
return this._status;
|
|
3012
3477
|
}
|
|
3478
|
+
/** Updates internal status and notifies all registered listeners. Swallows listener exceptions to avoid cascading failures. */
|
|
3013
3479
|
setStatus(status, error) {
|
|
3014
3480
|
this._status = status;
|
|
3015
3481
|
const event = { status, ...error ? { error } : {} };
|
|
@@ -3020,6 +3486,10 @@ var OrbinumClientProvider = class {
|
|
|
3020
3486
|
}
|
|
3021
3487
|
});
|
|
3022
3488
|
}
|
|
3489
|
+
/**
|
|
3490
|
+
* Registers a listener that is called on every status transition.
|
|
3491
|
+
* Returns an unsubscribe function — call it to stop receiving events.
|
|
3492
|
+
*/
|
|
3023
3493
|
onStatusChange(listener) {
|
|
3024
3494
|
this._listeners.add(listener);
|
|
3025
3495
|
return () => {
|
|
@@ -3027,10 +3497,18 @@ var OrbinumClientProvider = class {
|
|
|
3027
3497
|
};
|
|
3028
3498
|
}
|
|
3029
3499
|
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
3500
|
+
/**
|
|
3501
|
+
* Initiates the first connection attempt. No-op if the provider is not in `'idle'` state.
|
|
3502
|
+
* Call this once after constructing the provider.
|
|
3503
|
+
*/
|
|
3030
3504
|
connect() {
|
|
3031
3505
|
if (this._status !== "idle") return;
|
|
3032
3506
|
this.startConnectAttempt();
|
|
3033
3507
|
}
|
|
3508
|
+
/**
|
|
3509
|
+
* Tears down the active client and any pending reconnect timers,
|
|
3510
|
+
* then resets the provider back to `'idle'` so `connect()` can be called again.
|
|
3511
|
+
*/
|
|
3034
3512
|
reset() {
|
|
3035
3513
|
this.cancelReconnect();
|
|
3036
3514
|
this.teardownClient();
|
|
@@ -3038,6 +3516,7 @@ var OrbinumClientProvider = class {
|
|
|
3038
3516
|
this.setStatus("idle");
|
|
3039
3517
|
}
|
|
3040
3518
|
// ─── Internal connection flow ───────────────────────────────────────────
|
|
3519
|
+
/** Transitions to `'connecting'`, kicks off `attemptConnect`, and schedules a reconnect if it fails. */
|
|
3041
3520
|
startConnectAttempt() {
|
|
3042
3521
|
this.setStatus("connecting");
|
|
3043
3522
|
this._connectingPromise = this.attemptConnect();
|
|
@@ -3045,6 +3524,11 @@ var OrbinumClientProvider = class {
|
|
|
3045
3524
|
if (this._status !== "idle") this.scheduleReconnect();
|
|
3046
3525
|
});
|
|
3047
3526
|
}
|
|
3527
|
+
/**
|
|
3528
|
+
* Performs a single connection attempt race against `connectTimeoutMs`.
|
|
3529
|
+
* On success: stores the client, starts the heartbeat, and returns it.
|
|
3530
|
+
* On failure: destroys any orphaned client and transitions to `'disconnected'`.
|
|
3531
|
+
*/
|
|
3048
3532
|
async attemptConnect() {
|
|
3049
3533
|
let timeoutId = null;
|
|
3050
3534
|
let orphanClient = null;
|
|
@@ -3092,6 +3576,7 @@ var OrbinumClientProvider = class {
|
|
|
3092
3576
|
}
|
|
3093
3577
|
}
|
|
3094
3578
|
// ─── Heartbeat ──────────────────────────────────────────────────────────
|
|
3579
|
+
/** Starts the periodic heartbeat loop. Replaces any existing timer. */
|
|
3095
3580
|
startHeartbeat() {
|
|
3096
3581
|
this.stopHeartbeat();
|
|
3097
3582
|
this._heartbeatTimer = setInterval(async () => {
|
|
@@ -3104,12 +3589,17 @@ var OrbinumClientProvider = class {
|
|
|
3104
3589
|
}
|
|
3105
3590
|
}, this.heartbeatIntervalMs);
|
|
3106
3591
|
}
|
|
3592
|
+
/** Clears the heartbeat interval timer if active. */
|
|
3107
3593
|
stopHeartbeat() {
|
|
3108
3594
|
if (this._heartbeatTimer) {
|
|
3109
3595
|
clearInterval(this._heartbeatTimer);
|
|
3110
3596
|
this._heartbeatTimer = null;
|
|
3111
3597
|
}
|
|
3112
3598
|
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Sends a `system_health` RPC ping and waits up to `heartbeatTimeoutMs`.
|
|
3601
|
+
* Returns `true` if the node responds in time, `false` otherwise.
|
|
3602
|
+
*/
|
|
3113
3603
|
async probe() {
|
|
3114
3604
|
if (!this._orbinumClient) return false;
|
|
3115
3605
|
try {
|
|
@@ -3125,6 +3615,10 @@ var OrbinumClientProvider = class {
|
|
|
3125
3615
|
}
|
|
3126
3616
|
}
|
|
3127
3617
|
// ─── Reconnection ───────────────────────────────────────────────────────
|
|
3618
|
+
/**
|
|
3619
|
+
* Schedules the next connection attempt using exponential backoff
|
|
3620
|
+
* (capped at `reconnectMaxMs`), then transitions to `'reconnecting'`.
|
|
3621
|
+
*/
|
|
3128
3622
|
scheduleReconnect() {
|
|
3129
3623
|
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
|
|
3130
3624
|
const delay = Math.min(
|
|
@@ -3138,6 +3632,7 @@ var OrbinumClientProvider = class {
|
|
|
3138
3632
|
if (this._status !== "idle") this.startConnectAttempt();
|
|
3139
3633
|
}, delay);
|
|
3140
3634
|
}
|
|
3635
|
+
/** Clears any pending reconnect timer without triggering a new attempt. */
|
|
3141
3636
|
cancelReconnect() {
|
|
3142
3637
|
if (this._reconnectTimer) {
|
|
3143
3638
|
clearTimeout(this._reconnectTimer);
|
|
@@ -3145,6 +3640,7 @@ var OrbinumClientProvider = class {
|
|
|
3145
3640
|
}
|
|
3146
3641
|
}
|
|
3147
3642
|
// ─── Client teardown ────────────────────────────────────────────────────
|
|
3643
|
+
/** Stops the heartbeat, destroys the active client, and clears all in-progress promises. */
|
|
3148
3644
|
teardownClient() {
|
|
3149
3645
|
this.stopHeartbeat();
|
|
3150
3646
|
try {
|
|
@@ -3155,11 +3651,19 @@ var OrbinumClientProvider = class {
|
|
|
3155
3651
|
this._connectingPromise = null;
|
|
3156
3652
|
}
|
|
3157
3653
|
// ─── Client access ──────────────────────────────────────────────────────
|
|
3654
|
+
/**
|
|
3655
|
+
* Returns the active `OrbinumClient`, or awaits the in-progress connection attempt.
|
|
3656
|
+
* Throws if the provider is `'idle'`, `'disconnected'`, or `'reconnecting'`.
|
|
3657
|
+
*/
|
|
3158
3658
|
async getOrbinumClient() {
|
|
3159
3659
|
if (this._orbinumClient) return this._orbinumClient;
|
|
3160
3660
|
if (this._connectingPromise) return this._connectingPromise;
|
|
3161
3661
|
throw new Error(`OrbinumClientProvider: cannot get client in status '${this._status}'`);
|
|
3162
3662
|
}
|
|
3663
|
+
/**
|
|
3664
|
+
* Same as `getOrbinumClient()` but returns `null` instead of throwing.
|
|
3665
|
+
* Useful in contexts where a missing client is an acceptable no-op.
|
|
3666
|
+
*/
|
|
3163
3667
|
async tryGetOrbinumClient() {
|
|
3164
3668
|
try {
|
|
3165
3669
|
return await this.getOrbinumClient();
|
|
@@ -3168,15 +3672,28 @@ var OrbinumClientProvider = class {
|
|
|
3168
3672
|
}
|
|
3169
3673
|
}
|
|
3170
3674
|
// ─── Convenience RPC helpers ────────────────────────────────────────────
|
|
3675
|
+
/**
|
|
3676
|
+
* Sends a single Substrate JSON-RPC request and returns the typed result.
|
|
3677
|
+
* Waits for the client to be ready before dispatching.
|
|
3678
|
+
*/
|
|
3171
3679
|
async rpcSend(method, params = []) {
|
|
3172
3680
|
const client = await this.getOrbinumClient();
|
|
3173
3681
|
return client.substrate.request(method, params);
|
|
3174
3682
|
}
|
|
3683
|
+
/**
|
|
3684
|
+
* Sends a single EVM JSON-RPC request and returns the typed result.
|
|
3685
|
+
* Throws if `evmRpc` was not configured.
|
|
3686
|
+
*/
|
|
3175
3687
|
async evmRpc(method, params = []) {
|
|
3176
3688
|
const client = await this.getOrbinumClient();
|
|
3177
3689
|
if (!client.evm) throw new Error("EVM RPC not configured");
|
|
3178
3690
|
return client.evm.request(method, params);
|
|
3179
3691
|
}
|
|
3692
|
+
/**
|
|
3693
|
+
* Sends multiple EVM JSON-RPC calls as a single batch request.
|
|
3694
|
+
* Returns a tuple of typed results in the same order as `calls`.
|
|
3695
|
+
* Throws if `evmRpc` was not configured.
|
|
3696
|
+
*/
|
|
3180
3697
|
async evmRpcBatch(calls) {
|
|
3181
3698
|
const client = await this.getOrbinumClient();
|
|
3182
3699
|
if (!client.evm) throw new Error("EVM RPC not configured");
|
|
@@ -3184,102 +3701,476 @@ var OrbinumClientProvider = class {
|
|
|
3184
3701
|
}
|
|
3185
3702
|
};
|
|
3186
3703
|
|
|
3187
|
-
// src/
|
|
3704
|
+
// src/utils/stealth.ts
|
|
3705
|
+
var import_sha22 = require("@noble/hashes/sha2.js");
|
|
3706
|
+
var import_hkdf = require("@noble/hashes/hkdf.js");
|
|
3707
|
+
var import_baby_jubjub2 = require("@zk-kit/baby-jubjub");
|
|
3708
|
+
var STEALTH_INFO = new TextEncoder().encode("orbinum-stealth-v1");
|
|
3709
|
+
function deriveStealthScalar(sharedSecret, ownerPkBigint) {
|
|
3710
|
+
const salt = bigintTo32Le(ownerPkBigint);
|
|
3711
|
+
const stealthBytes = (0, import_hkdf.hkdf)(import_sha22.sha256, sharedSecret, salt, STEALTH_INFO, 32);
|
|
3712
|
+
return bytesToBigintLE(stealthBytes) % BABYJUB_SUBORDER || 1n;
|
|
3713
|
+
}
|
|
3714
|
+
function deriveStealthOwnerPk(sharedSecret, ownerPkBigint, ownerPkPoint) {
|
|
3715
|
+
const stealthScalar = deriveStealthScalar(sharedSecret, ownerPkBigint);
|
|
3716
|
+
const stealthPt = (0, import_baby_jubjub2.addPoint)((0, import_baby_jubjub2.mulPointEscalar)(import_baby_jubjub2.Base8, stealthScalar), ownerPkPoint);
|
|
3717
|
+
return stealthPt[0];
|
|
3718
|
+
}
|
|
3719
|
+
function deriveStealthSk(sharedSecret, ownerPkBigint, spendingKey) {
|
|
3720
|
+
const stealthScalar = deriveStealthScalar(sharedSecret, ownerPkBigint);
|
|
3721
|
+
return (stealthScalar + spendingKey) % BABYJUB_SUBORDER || 1n;
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
// src/utils/bjj.ts
|
|
3725
|
+
var import_baby_jubjub3 = require("@zk-kit/baby-jubjub");
|
|
3726
|
+
var BJJ_A = 168700n;
|
|
3727
|
+
var BJJ_D = 168696n;
|
|
3728
|
+
function _modpow(base, exp, mod) {
|
|
3729
|
+
let result = 1n;
|
|
3730
|
+
base = base % mod;
|
|
3731
|
+
while (exp > 0n) {
|
|
3732
|
+
if (exp & 1n) result = result * base % mod;
|
|
3733
|
+
exp >>= 1n;
|
|
3734
|
+
base = base * base % mod;
|
|
3735
|
+
}
|
|
3736
|
+
return result;
|
|
3737
|
+
}
|
|
3738
|
+
function _sqrtModP(y2) {
|
|
3739
|
+
if (y2 === 0n) return 0n;
|
|
3740
|
+
if (_modpow(y2, (BN254_R - 1n) / 2n, BN254_R) !== 1n) return null;
|
|
3741
|
+
let s = 0n;
|
|
3742
|
+
let q = BN254_R - 1n;
|
|
3743
|
+
while ((q & 1n) === 0n) {
|
|
3744
|
+
q >>= 1n;
|
|
3745
|
+
s++;
|
|
3746
|
+
}
|
|
3747
|
+
if (s === 1n) return _modpow(y2, (BN254_R + 1n) / 4n, BN254_R);
|
|
3748
|
+
let z = 2n;
|
|
3749
|
+
while (_modpow(z, (BN254_R - 1n) / 2n, BN254_R) === 1n) z++;
|
|
3750
|
+
let m = s;
|
|
3751
|
+
let c = _modpow(z, q, BN254_R);
|
|
3752
|
+
let t = _modpow(y2, q, BN254_R);
|
|
3753
|
+
let r = _modpow(y2, (q + 1n) / 2n, BN254_R);
|
|
3754
|
+
for (; ; ) {
|
|
3755
|
+
if (t === 1n) return r;
|
|
3756
|
+
let i = 1n;
|
|
3757
|
+
let tmp = t * t % BN254_R;
|
|
3758
|
+
while (tmp !== 1n) {
|
|
3759
|
+
tmp = tmp * tmp % BN254_R;
|
|
3760
|
+
i++;
|
|
3761
|
+
}
|
|
3762
|
+
const b = _modpow(c, 1n << m - i - 1n, BN254_R);
|
|
3763
|
+
m = i;
|
|
3764
|
+
c = b * b % BN254_R;
|
|
3765
|
+
t = t * c % BN254_R;
|
|
3766
|
+
r = r * b % BN254_R;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
function recoverOwnerPkPoint(ax) {
|
|
3770
|
+
const x2 = ax * ax % BN254_R;
|
|
3771
|
+
const num = ((1n - BJJ_A * x2) % BN254_R + BN254_R) % BN254_R;
|
|
3772
|
+
const den = ((1n - BJJ_D * x2) % BN254_R + BN254_R) % BN254_R;
|
|
3773
|
+
if (den === 0n) return null;
|
|
3774
|
+
const denInv = _modpow(den, BN254_R - 2n, BN254_R);
|
|
3775
|
+
const y2 = num * denInv % BN254_R;
|
|
3776
|
+
const y = _sqrtModP(y2);
|
|
3777
|
+
if (y === null) return null;
|
|
3778
|
+
const yAlt = BN254_R - y;
|
|
3779
|
+
try {
|
|
3780
|
+
const check = (0, import_baby_jubjub3.mulPointEscalar)([ax, y], BABYJUB_SUBORDER);
|
|
3781
|
+
return check[0] === 0n && check[1] === 1n ? [ax, y] : [ax, yAlt];
|
|
3782
|
+
} catch {
|
|
3783
|
+
return [ax, yAlt];
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
// src/shielded-pool/protocol/NoteBuilder.ts
|
|
3788
|
+
var import_baby_jubjub4 = require("@zk-kit/baby-jubjub");
|
|
3789
|
+
var import_utils2 = require("@noble/ciphers/utils.js");
|
|
3790
|
+
var import_poseidon_lite = require("poseidon-lite");
|
|
3791
|
+
var NoteBuilder = class {
|
|
3792
|
+
/**
|
|
3793
|
+
* Build a ZkNote from the given inputs.
|
|
3794
|
+
*
|
|
3795
|
+
* @param input.value Amount in planck (required).
|
|
3796
|
+
* @param input.assetId Asset ID — default 0n (native ORB-Privacy).
|
|
3797
|
+
* @param input.ownerPk Sender's or recipient's global BabyJubJub Ax — default 0n.
|
|
3798
|
+
* @param input.blinding Random scalar — defaults to BigInt(Date.now()).
|
|
3799
|
+
* @param input.spendingKey Secret key for nullifier — default 0n.
|
|
3800
|
+
* @param input.viewingPublicKey Recipient's 32-byte LE packed BJJ ivk. Triggers memo encryption.
|
|
3801
|
+
* @param input.recipientOwnerPk Recipient's global ownerPk. Required with viewingPublicKey
|
|
3802
|
+
* to enable stealth address derivation. Without it, the
|
|
3803
|
+
* commitment uses ownerPk directly (no stealth).
|
|
3804
|
+
*/
|
|
3805
|
+
static async build(input) {
|
|
3806
|
+
const value = input.value;
|
|
3807
|
+
const assetId = input.assetId ?? 0n;
|
|
3808
|
+
const ownerPk = input.ownerPk ?? 0n;
|
|
3809
|
+
const blinding = input.blinding ?? BigInt(Date.now());
|
|
3810
|
+
const spendingKey = input.spendingKey ?? 0n;
|
|
3811
|
+
const counterpartyPk = input.counterpartyPk ?? 0n;
|
|
3812
|
+
const useStealth = input.viewingPublicKey !== void 0 && input.recipientOwnerPk !== void 0;
|
|
3813
|
+
let memo;
|
|
3814
|
+
if (useStealth) {
|
|
3815
|
+
const recipientOwnerPk = input.recipientOwnerPk;
|
|
3816
|
+
const recipientIvkPacked = input.viewingPublicKey;
|
|
3817
|
+
const ephSk = (0, import_utils2.randomBytes)(32);
|
|
3818
|
+
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
3819
|
+
const ivkPoint = (0, import_baby_jubjub4.unpackPoint)(ivkPackedBigint);
|
|
3820
|
+
if (!ivkPoint)
|
|
3821
|
+
throw new Error("NoteBuilder.build: invalid recipient viewing public key");
|
|
3822
|
+
const ephSkScalar = BigInt(toHex(ephSk)) % BABYJUB_SUBORDER || 1n;
|
|
3823
|
+
const sharedPoint = (0, import_baby_jubjub4.mulPointEscalar)(ivkPoint, ephSkScalar);
|
|
3824
|
+
const sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
3825
|
+
const recipientPkPoint = recoverOwnerPkPoint(recipientOwnerPk);
|
|
3826
|
+
if (!recipientPkPoint)
|
|
3827
|
+
throw new Error(
|
|
3828
|
+
"NoteBuilder.build: recipientOwnerPk is not a valid BJJ x-coordinate"
|
|
3829
|
+
);
|
|
3830
|
+
const effectiveOwnerPk = deriveStealthOwnerPk(
|
|
3831
|
+
sharedSecret,
|
|
3832
|
+
recipientOwnerPk,
|
|
3833
|
+
recipientPkPoint
|
|
3834
|
+
);
|
|
3835
|
+
const stealthCommitment = (0, import_poseidon_lite.poseidon4)([value, assetId, effectiveOwnerPk, blinding]);
|
|
3836
|
+
const stealthCommitmentBytes = bigintTo32Le(stealthCommitment);
|
|
3837
|
+
memo = Array.from(
|
|
3838
|
+
EncryptedMemo.encrypt(
|
|
3839
|
+
value,
|
|
3840
|
+
bigintTo32Le(effectiveOwnerPk),
|
|
3841
|
+
bigintTo32Le(blinding),
|
|
3842
|
+
Number(assetId),
|
|
3843
|
+
stealthCommitmentBytes,
|
|
3844
|
+
recipientIvkPacked,
|
|
3845
|
+
bigintTo32Le(counterpartyPk),
|
|
3846
|
+
ephSk
|
|
3847
|
+
)
|
|
3848
|
+
);
|
|
3849
|
+
const commitment2 = stealthCommitment;
|
|
3850
|
+
const nullifier2 = (0, import_poseidon_lite.poseidon2)([commitment2, spendingKey]);
|
|
3851
|
+
const commitmentBytes2 = stealthCommitmentBytes;
|
|
3852
|
+
const nullifierBytes2 = bigintTo32Le(nullifier2);
|
|
3853
|
+
if (memo.length !== ENCRYPTED_MEMO_SIZE)
|
|
3854
|
+
throw new Error(
|
|
3855
|
+
`NoteBuilder.build: invariant violated \u2014 memo must be ${ENCRYPTED_MEMO_SIZE} bytes, got ${memo.length}`
|
|
3856
|
+
);
|
|
3857
|
+
return {
|
|
3858
|
+
value,
|
|
3859
|
+
assetId,
|
|
3860
|
+
ownerPk: effectiveOwnerPk,
|
|
3861
|
+
blinding,
|
|
3862
|
+
spendingKey,
|
|
3863
|
+
spent: false,
|
|
3864
|
+
spentAt: null,
|
|
3865
|
+
commitment: commitment2,
|
|
3866
|
+
nullifier: nullifier2,
|
|
3867
|
+
commitmentHex: toHex(commitmentBytes2),
|
|
3868
|
+
nullifierHex: toHex(nullifierBytes2),
|
|
3869
|
+
memo,
|
|
3870
|
+
counterpartyPk
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
const commitment = (0, import_poseidon_lite.poseidon4)([value, assetId, ownerPk, blinding]);
|
|
3874
|
+
const nullifier = (0, import_poseidon_lite.poseidon2)([commitment, spendingKey]);
|
|
3875
|
+
const commitmentBytes = bigintTo32Le(commitment);
|
|
3876
|
+
const nullifierBytes = bigintTo32Le(nullifier);
|
|
3877
|
+
memo = input.viewingPublicKey !== void 0 ? Array.from(
|
|
3878
|
+
EncryptedMemo.encrypt(
|
|
3879
|
+
value,
|
|
3880
|
+
bigintTo32Le(ownerPk),
|
|
3881
|
+
bigintTo32Le(blinding),
|
|
3882
|
+
Number(assetId),
|
|
3883
|
+
commitmentBytes,
|
|
3884
|
+
input.viewingPublicKey,
|
|
3885
|
+
bigintTo32Le(counterpartyPk)
|
|
3886
|
+
)
|
|
3887
|
+
) : Array.from(EncryptedMemo.dummy());
|
|
3888
|
+
if (memo.length !== ENCRYPTED_MEMO_SIZE)
|
|
3889
|
+
throw new Error(
|
|
3890
|
+
`NoteBuilder.build: invariant violated \u2014 memo must be ${ENCRYPTED_MEMO_SIZE} bytes, got ${memo.length}`
|
|
3891
|
+
);
|
|
3892
|
+
return {
|
|
3893
|
+
value,
|
|
3894
|
+
assetId,
|
|
3895
|
+
ownerPk,
|
|
3896
|
+
blinding,
|
|
3897
|
+
spendingKey,
|
|
3898
|
+
spent: false,
|
|
3899
|
+
spentAt: null,
|
|
3900
|
+
commitment,
|
|
3901
|
+
nullifier,
|
|
3902
|
+
commitmentHex: toHex(commitmentBytes),
|
|
3903
|
+
nullifierHex: toHex(nullifierBytes),
|
|
3904
|
+
memo,
|
|
3905
|
+
counterpartyPk
|
|
3906
|
+
};
|
|
3907
|
+
}
|
|
3908
|
+
/**
|
|
3909
|
+
* Build the 168-byte ECDH-encrypted memo for a note.
|
|
3910
|
+
*
|
|
3911
|
+
* Pure TypeScript implementation — no WASM dependency.
|
|
3912
|
+
* Uses ChaCha20-Poly1305 with ECDH key agreement (BabyJubJub ephemeral keypair).
|
|
3913
|
+
*
|
|
3914
|
+
* @param note The ZkNote whose fields populate the plaintext.
|
|
3915
|
+
* @param recipientIvkPacked 32-byte LE packed BJJ viewing public key of the recipient.
|
|
3916
|
+
* Pass `new Uint8Array(32)` (default) for a public/dummy memo.
|
|
3917
|
+
* @param counterpartyPk 32-byte counterparty BabyJubJub Ax.
|
|
3918
|
+
* Pass `new Uint8Array(32)` (default) for no counterparty.
|
|
3919
|
+
*/
|
|
3920
|
+
static buildMemo(note, recipientIvkPacked, counterpartyPk) {
|
|
3921
|
+
return EncryptedMemo.encrypt(
|
|
3922
|
+
note.value,
|
|
3923
|
+
bigintTo32Le(note.ownerPk),
|
|
3924
|
+
bigintTo32Le(note.blinding),
|
|
3925
|
+
Number(note.assetId),
|
|
3926
|
+
bigintTo32Le(note.commitment),
|
|
3927
|
+
recipientIvkPacked ?? new Uint8Array(32),
|
|
3928
|
+
counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n)
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3931
|
+
};
|
|
3932
|
+
|
|
3933
|
+
// src/shielded-pool/protocol/NoteDecryptor.ts
|
|
3188
3934
|
var import_poseidon_lite2 = require("poseidon-lite");
|
|
3189
|
-
function
|
|
3190
|
-
|
|
3935
|
+
function computeNullifier(commitment, spendingKey) {
|
|
3936
|
+
return (0, import_poseidon_lite2.poseidon2)([commitment, spendingKey]);
|
|
3937
|
+
}
|
|
3938
|
+
function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
|
|
3939
|
+
return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk).note;
|
|
3940
|
+
}
|
|
3941
|
+
function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
|
|
3942
|
+
if (!commitment.encryptedMemo) return { note: null, reason: "no_memo" };
|
|
3191
3943
|
let commitmentBytes;
|
|
3192
3944
|
let memoBytes;
|
|
3193
3945
|
try {
|
|
3194
3946
|
commitmentBytes = fromHex(commitment.commitmentHex);
|
|
3195
3947
|
memoBytes = fromHex(commitment.encryptedMemo);
|
|
3196
3948
|
} catch {
|
|
3197
|
-
return null;
|
|
3949
|
+
return { note: null, reason: "hex_parse_error" };
|
|
3950
|
+
}
|
|
3951
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) {
|
|
3952
|
+
return {
|
|
3953
|
+
note: null,
|
|
3954
|
+
reason: `memo_size_mismatch:got_${memoBytes.length}_expected_${ENCRYPTED_MEMO_SIZE}`
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
|
|
3958
|
+
if (!plaintext) return { note: null, reason: "decrypt_failed:wrong_key_or_corrupt_mac" };
|
|
3959
|
+
let effectiveOwnerPk = plaintext.ownerPk;
|
|
3960
|
+
let effectiveSpendingKey = spendingKey;
|
|
3961
|
+
if (ownOwnerPk !== 0n && plaintext.ownerPk !== ownOwnerPk) {
|
|
3962
|
+
const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
|
|
3963
|
+
if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
|
|
3964
|
+
const ownPkPoint = recoverOwnerPkPoint(ownOwnerPk);
|
|
3965
|
+
if (!ownPkPoint) return { note: null, reason: "stealth_invalid_own_owner_pk" };
|
|
3966
|
+
const stealthOwnerPk = deriveStealthOwnerPk(sharedSecret, ownOwnerPk, ownPkPoint);
|
|
3967
|
+
if (stealthOwnerPk !== plaintext.ownerPk) {
|
|
3968
|
+
return { note: null, reason: "commitment_mismatch" };
|
|
3969
|
+
}
|
|
3970
|
+
effectiveOwnerPk = stealthOwnerPk;
|
|
3971
|
+
effectiveSpendingKey = deriveStealthSk(sharedSecret, ownOwnerPk, spendingKey);
|
|
3198
3972
|
}
|
|
3199
|
-
const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingKey);
|
|
3200
|
-
if (!plaintext) return null;
|
|
3201
3973
|
const recomputed = (0, import_poseidon_lite2.poseidon4)([
|
|
3202
3974
|
plaintext.value,
|
|
3203
3975
|
plaintext.assetId,
|
|
3204
|
-
|
|
3976
|
+
effectiveOwnerPk,
|
|
3205
3977
|
plaintext.blinding
|
|
3206
3978
|
]);
|
|
3207
|
-
if (recomputed !== bytesToBigintLE(commitmentBytes))
|
|
3208
|
-
|
|
3979
|
+
if (recomputed !== bytesToBigintLE(commitmentBytes)) {
|
|
3980
|
+
return { note: null, reason: "commitment_mismatch" };
|
|
3981
|
+
}
|
|
3982
|
+
const nullifier = (0, import_poseidon_lite2.poseidon2)([recomputed, effectiveSpendingKey]);
|
|
3209
3983
|
return {
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3984
|
+
note: {
|
|
3985
|
+
value: plaintext.value,
|
|
3986
|
+
assetId: plaintext.assetId,
|
|
3987
|
+
ownerPk: effectiveOwnerPk,
|
|
3988
|
+
blinding: plaintext.blinding,
|
|
3989
|
+
spendingKey: effectiveSpendingKey,
|
|
3990
|
+
spent: false,
|
|
3991
|
+
spentAt: null,
|
|
3992
|
+
commitment: recomputed,
|
|
3993
|
+
nullifier,
|
|
3994
|
+
commitmentHex: toHex(bigintTo32Le(recomputed)),
|
|
3995
|
+
nullifierHex: toHex(bigintTo32Le(nullifier)),
|
|
3996
|
+
memo: Array.from(memoBytes),
|
|
3997
|
+
counterpartyPk: plaintext.counterpartyPk
|
|
3998
|
+
}
|
|
3222
3999
|
};
|
|
3223
4000
|
}
|
|
3224
4001
|
|
|
3225
|
-
// src/shielded-pool/
|
|
3226
|
-
var
|
|
3227
|
-
var
|
|
3228
|
-
var
|
|
4002
|
+
// src/shielded-pool/protocol/NoteDisclosure.ts
|
|
4003
|
+
var import_poseidon_lite3 = require("poseidon-lite");
|
|
4004
|
+
var PREFIX = "orbdisc:";
|
|
4005
|
+
var VERSION = 1;
|
|
4006
|
+
function toHex2(n) {
|
|
4007
|
+
return "0x" + n.toString(16);
|
|
4008
|
+
}
|
|
4009
|
+
function fromHex2(s) {
|
|
4010
|
+
return BigInt(s);
|
|
4011
|
+
}
|
|
4012
|
+
function createNoteDisclosureKey(note) {
|
|
4013
|
+
const payload = {
|
|
4014
|
+
v: VERSION,
|
|
4015
|
+
c: toHex2(note.commitment),
|
|
4016
|
+
val: toHex2(note.value),
|
|
4017
|
+
aid: toHex2(note.assetId),
|
|
4018
|
+
opk: toHex2(note.ownerPk),
|
|
4019
|
+
bld: toHex2(note.blinding)
|
|
4020
|
+
};
|
|
4021
|
+
const json = JSON.stringify(payload);
|
|
4022
|
+
const b64 = btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
4023
|
+
return PREFIX + b64;
|
|
4024
|
+
}
|
|
4025
|
+
function decodeNoteDisclosureKey(key) {
|
|
4026
|
+
try {
|
|
4027
|
+
if (!key.startsWith(PREFIX)) return null;
|
|
4028
|
+
const b64 = key.slice(PREFIX.length).replace(/-/g, "+").replace(/_/g, "/");
|
|
4029
|
+
const json = atob(b64);
|
|
4030
|
+
const payload = JSON.parse(json);
|
|
4031
|
+
if (payload.v !== VERSION) return null;
|
|
4032
|
+
const disclosure = {
|
|
4033
|
+
commitment: fromHex2(payload.c),
|
|
4034
|
+
value: fromHex2(payload.val),
|
|
4035
|
+
assetId: fromHex2(payload.aid),
|
|
4036
|
+
ownerPk: fromHex2(payload.opk),
|
|
4037
|
+
blinding: fromHex2(payload.bld)
|
|
4038
|
+
};
|
|
4039
|
+
const recomputed = (0, import_poseidon_lite3.poseidon4)([
|
|
4040
|
+
disclosure.value,
|
|
4041
|
+
disclosure.assetId,
|
|
4042
|
+
disclosure.ownerPk,
|
|
4043
|
+
disclosure.blinding
|
|
4044
|
+
]);
|
|
4045
|
+
if (recomputed !== disclosure.commitment) return null;
|
|
4046
|
+
return disclosure;
|
|
4047
|
+
} catch {
|
|
4048
|
+
return null;
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
3229
4051
|
|
|
3230
|
-
// src/shielded-pool/
|
|
3231
|
-
var
|
|
4052
|
+
// src/shielded-pool/protocol/coinSelection.ts
|
|
4053
|
+
var TRANSFER_TREE_DEPTH = 20;
|
|
4054
|
+
function selectNotes(notes, needed) {
|
|
4055
|
+
const unspent = notes.filter((n) => !n.spent && n.value > 0n);
|
|
4056
|
+
const sorted = [...unspent].sort((a, b) => a.value < b.value ? -1 : 1);
|
|
4057
|
+
const single = sorted.find((n) => n.value >= needed);
|
|
4058
|
+
if (single) return [single, null];
|
|
4059
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
4060
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
4061
|
+
const a = sorted[i];
|
|
4062
|
+
const b = sorted[j];
|
|
4063
|
+
if (a !== void 0 && b !== void 0 && a.value + b.value >= needed) {
|
|
4064
|
+
return [a, b];
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
return null;
|
|
4069
|
+
}
|
|
4070
|
+
function buildDummyTransferInput(assetId) {
|
|
4071
|
+
const zeroSibling = "0x" + "00".repeat(32);
|
|
4072
|
+
return {
|
|
4073
|
+
nullifier: 0n,
|
|
4074
|
+
// Constraint 9: nullifier * is_dummy.out === 0 → must be 0
|
|
4075
|
+
value: 0n,
|
|
4076
|
+
// triggers is_dummy[i].out = 1 in the circuit
|
|
4077
|
+
assetId,
|
|
4078
|
+
// must match real note (Constraint 7)
|
|
4079
|
+
ownerPk: 0n,
|
|
4080
|
+
blinding: 0n,
|
|
4081
|
+
spendingKey: 1n,
|
|
4082
|
+
// arbitrary; EdDSA is disabled (enabled = 0) for dummy inputs
|
|
4083
|
+
pathSiblings: Array(TRANSFER_TREE_DEPTH).fill(zeroSibling),
|
|
4084
|
+
leafIndex: 0
|
|
4085
|
+
};
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
// src/utils/blinding.ts
|
|
4089
|
+
function randomBlinding() {
|
|
4090
|
+
const buf = new Uint8Array(32);
|
|
4091
|
+
crypto.getRandomValues(buf);
|
|
4092
|
+
const n = bytesToBigintLE(buf);
|
|
4093
|
+
return n === 0n ? 1n : n % BN254_R;
|
|
4094
|
+
}
|
|
3232
4095
|
|
|
3233
|
-
// src/
|
|
4096
|
+
// src/privacy-keys/PrivacyKeys.ts
|
|
4097
|
+
var import_hkdf2 = require("@noble/hashes/hkdf.js");
|
|
4098
|
+
var import_sha23 = require("@noble/hashes/sha2.js");
|
|
4099
|
+
var import_baby_jubjub5 = require("@zk-kit/baby-jubjub");
|
|
3234
4100
|
var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
|
|
3235
4101
|
function deriveSpendingKeyMessage(chainId, address) {
|
|
3236
4102
|
return `orbinum-spending-key-v1
|
|
3237
4103
|
${chainId}
|
|
3238
4104
|
${address.toLowerCase()}`;
|
|
3239
4105
|
}
|
|
3240
|
-
async function
|
|
3241
|
-
const
|
|
3242
|
-
const sigBytes = new Uint8Array((hex.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
|
|
4106
|
+
async function deriveMasterKeyBytes(signatureHex, chainId, address) {
|
|
4107
|
+
const sigBytes = fromHex(signatureHex);
|
|
3243
4108
|
const info = new TextEncoder().encode(`orbinum-sk-v1:${chainId}:${address.toLowerCase()}`);
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
4109
|
+
return (0, import_hkdf2.hkdf)(import_sha23.sha256, sigBytes, new Uint8Array(0), info, 32);
|
|
4110
|
+
}
|
|
4111
|
+
async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
|
|
4112
|
+
const masterBytes = await deriveMasterKeyBytes(signatureHex, chainId, address);
|
|
4113
|
+
const skBigint = BigInt(toHex(masterBytes)) % BABYJUB_SUBORDER;
|
|
3248
4114
|
return skBigint === 0n ? 1n : skBigint;
|
|
3249
4115
|
}
|
|
3250
|
-
function
|
|
4116
|
+
function deriveViewingSecretKey(spendingKey) {
|
|
3251
4117
|
const ikm = bigintTo32Le(spendingKey);
|
|
3252
|
-
return (0,
|
|
4118
|
+
return (0, import_hkdf2.hkdf)(import_sha23.sha256, ikm, void 0, IVK_DOMAIN, 32);
|
|
4119
|
+
}
|
|
4120
|
+
function deriveViewingPublicKey(ivsk) {
|
|
4121
|
+
const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
|
|
4122
|
+
const ivkPoint = (0, import_baby_jubjub5.mulPointEscalar)(import_baby_jubjub5.Base8, ivskScalar);
|
|
4123
|
+
const packed = (0, import_baby_jubjub5.packPoint)(ivkPoint);
|
|
4124
|
+
return bigintTo32Le(packed);
|
|
3253
4125
|
}
|
|
3254
4126
|
function deriveOwnerPk(spendingKey) {
|
|
3255
4127
|
try {
|
|
3256
|
-
const pubPoint = (0,
|
|
4128
|
+
const pubPoint = (0, import_baby_jubjub5.mulPointEscalar)(import_baby_jubjub5.Base8, spendingKey);
|
|
3257
4129
|
return pubPoint[0];
|
|
3258
4130
|
} catch {
|
|
3259
4131
|
return 0n;
|
|
3260
4132
|
}
|
|
3261
4133
|
}
|
|
3262
4134
|
|
|
3263
|
-
// src/
|
|
4135
|
+
// src/privacy-keys/PrivacyKeyManager.ts
|
|
3264
4136
|
var PrivacyKeyManager = class {
|
|
3265
4137
|
_state = {
|
|
3266
4138
|
spendingKey: null,
|
|
3267
|
-
|
|
4139
|
+
masterBytes: null,
|
|
4140
|
+
viewingSecretKey: null,
|
|
4141
|
+
viewingPublicKeyPacked: null,
|
|
3268
4142
|
ownerPk: null
|
|
3269
4143
|
};
|
|
3270
4144
|
/**
|
|
3271
|
-
* Load a spending key into the in-memory session.
|
|
3272
|
-
* Derives
|
|
4145
|
+
* Load a spending key and its corresponding master bytes into the in-memory session.
|
|
4146
|
+
* Derives viewingSecretKey, viewingPublicKeyPacked, and ownerPk immediately.
|
|
3273
4147
|
* Replaces any previously loaded key.
|
|
4148
|
+
*
|
|
4149
|
+
* @param spendingKey Circuit scalar: BigInt(masterBytes) % BABYJUB_SUBORDER, clamped to [1, ∞).
|
|
4150
|
+
* @param masterBytes Raw 32-byte HKDF output before modular reduction. Used to derive
|
|
4151
|
+
* the stable vault key (HKDF(masterBytes, info="orbinum-vault-key-v1")).
|
|
3274
4152
|
*/
|
|
3275
|
-
async load(spendingKey) {
|
|
3276
|
-
const
|
|
4153
|
+
async load(spendingKey, masterBytes) {
|
|
4154
|
+
const viewingSecretKey = deriveViewingSecretKey(spendingKey);
|
|
4155
|
+
const viewingPublicKeyPacked = deriveViewingPublicKey(viewingSecretKey);
|
|
3277
4156
|
const ownerPk = deriveOwnerPk(spendingKey);
|
|
3278
|
-
this._state = {
|
|
4157
|
+
this._state = {
|
|
4158
|
+
spendingKey,
|
|
4159
|
+
masterBytes,
|
|
4160
|
+
viewingSecretKey,
|
|
4161
|
+
viewingPublicKeyPacked,
|
|
4162
|
+
ownerPk
|
|
4163
|
+
};
|
|
3279
4164
|
}
|
|
3280
4165
|
/** Clear all key material from memory. Call on vault lock / sign-out. */
|
|
3281
4166
|
clear() {
|
|
3282
|
-
this._state = {
|
|
4167
|
+
this._state = {
|
|
4168
|
+
spendingKey: null,
|
|
4169
|
+
masterBytes: null,
|
|
4170
|
+
viewingSecretKey: null,
|
|
4171
|
+
viewingPublicKeyPacked: null,
|
|
4172
|
+
ownerPk: null
|
|
4173
|
+
};
|
|
3283
4174
|
}
|
|
3284
4175
|
/** Returns true if a spending key has been loaded. */
|
|
3285
4176
|
isLoaded() {
|
|
@@ -3292,12 +4183,29 @@ var PrivacyKeyManager = class {
|
|
|
3292
4183
|
}
|
|
3293
4184
|
return this._state.spendingKey;
|
|
3294
4185
|
}
|
|
3295
|
-
/**
|
|
3296
|
-
|
|
3297
|
-
|
|
4186
|
+
/**
|
|
4187
|
+
* Returns the 32-byte viewing secret key (ivsk).
|
|
4188
|
+
* Used internally for decrypting received notes during rescan.
|
|
4189
|
+
* SECURITY: never expose this in addresses or network requests.
|
|
4190
|
+
* Throws if not loaded.
|
|
4191
|
+
*/
|
|
4192
|
+
getViewingSecretKey() {
|
|
4193
|
+
if (this._state.viewingSecretKey === null) {
|
|
4194
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
4195
|
+
}
|
|
4196
|
+
return this._state.viewingSecretKey;
|
|
4197
|
+
}
|
|
4198
|
+
/**
|
|
4199
|
+
* Returns the 32-byte LE-encoded packed BJJ viewing public key (ivk).
|
|
4200
|
+
* This is the component embedded in the privacy address and passed to senders
|
|
4201
|
+
* so they can encrypt memos only the recipient can decrypt.
|
|
4202
|
+
* Throws if not loaded.
|
|
4203
|
+
*/
|
|
4204
|
+
getViewingPublicKeyPacked() {
|
|
4205
|
+
if (this._state.viewingPublicKeyPacked === null) {
|
|
3298
4206
|
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
3299
4207
|
}
|
|
3300
|
-
return this._state.
|
|
4208
|
+
return this._state.viewingPublicKeyPacked;
|
|
3301
4209
|
}
|
|
3302
4210
|
/** Returns the BabyJubJub owner public key (x-coordinate). Throws if not loaded. */
|
|
3303
4211
|
getOwnerPk() {
|
|
@@ -3310,24 +4218,86 @@ var PrivacyKeyManager = class {
|
|
|
3310
4218
|
getSpendingKeyBytes() {
|
|
3311
4219
|
return bigintTo32Le(this.getSpendingKey());
|
|
3312
4220
|
}
|
|
3313
|
-
/**
|
|
4221
|
+
/**
|
|
4222
|
+
* Returns the 32-byte master key bytes (pre-modulus HKDF output).
|
|
4223
|
+
* Used to derive the stable vault AES key. Throws if not loaded.
|
|
4224
|
+
*/
|
|
4225
|
+
getMasterBytes() {
|
|
4226
|
+
if (this._state.masterBytes === null) {
|
|
4227
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
4228
|
+
}
|
|
4229
|
+
return this._state.masterBytes;
|
|
4230
|
+
}
|
|
4231
|
+
/**
|
|
4232
|
+
* Exports the master key bytes as a "mk:0x{hex}" string.
|
|
4233
|
+
* Storing masterBytes (not the sk scalar) ensures the vault key and
|
|
4234
|
+
* rescan can always be reconstructed regardless of any future modulus change.
|
|
4235
|
+
* Throws if not loaded.
|
|
4236
|
+
*/
|
|
3314
4237
|
exportHex() {
|
|
3315
|
-
|
|
4238
|
+
const mb = this.getMasterBytes();
|
|
4239
|
+
return "mk:0x" + Array.from(mb, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4240
|
+
}
|
|
4241
|
+
/**
|
|
4242
|
+
* Exports a shareable privacy address encoding the owner public key and
|
|
4243
|
+
* viewing PUBLIC key of the currently loaded identity.
|
|
4244
|
+
*
|
|
4245
|
+
* Format: `orbpriv1:{ownerPk_hex}:{viewingPublicKey_hex}`
|
|
4246
|
+
*
|
|
4247
|
+
* The recipient uses this address so the sender can:
|
|
4248
|
+
* 1. Embed `ownerPk` in the note commitment (Poseidon4 input).
|
|
4249
|
+
* 2. Encrypt the memo via ECDH with the recipient's `viewingPublicKey`.
|
|
4250
|
+
*
|
|
4251
|
+
* SECURITY: Only the viewing PUBLIC key is embedded — the viewing secret key
|
|
4252
|
+
* (used for decryption) is never exported. Holders of this address cannot
|
|
4253
|
+
* decrypt the recipient's notes.
|
|
4254
|
+
*
|
|
4255
|
+
* Throws if no key is loaded.
|
|
4256
|
+
*/
|
|
4257
|
+
encodePrivacyAddress() {
|
|
4258
|
+
const ownerPk = this.getOwnerPk();
|
|
4259
|
+
const ivkPacked = this.getViewingPublicKeyPacked();
|
|
4260
|
+
const ownerPkHex = "0x" + ownerPk.toString(16).padStart(64, "0");
|
|
4261
|
+
const ivkHex = "0x" + Array.from(ivkPacked, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4262
|
+
return `orbpriv1:${ownerPkHex}:${ivkHex}`;
|
|
3316
4263
|
}
|
|
3317
4264
|
/**
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
4265
|
+
* Decode a privacy address of the form `orbpriv1:{ownerPk_hex}:{viewingPublicKey_hex}`.
|
|
4266
|
+
* Returns `{ ownerPkHex, viewingPublicKeyHex }` on success, or `null` if the input
|
|
4267
|
+
* does not match the expected format.
|
|
4268
|
+
*/
|
|
4269
|
+
static decodePrivacyAddress(address) {
|
|
4270
|
+
if (!address.startsWith("orbpriv1:")) return null;
|
|
4271
|
+
const parts = address.split(":");
|
|
4272
|
+
if (parts.length !== 3) return null;
|
|
4273
|
+
const ownerPkHex = parts[1];
|
|
4274
|
+
const viewingPublicKeyHex = parts[2];
|
|
4275
|
+
if (!ownerPkHex || !viewingPublicKeyHex) return null;
|
|
4276
|
+
return { ownerPkHex, viewingPublicKeyHex };
|
|
4277
|
+
}
|
|
4278
|
+
/**
|
|
4279
|
+
* Load keys from a cached "mk:0x{masterBytes_hex}" string produced by exportHex().
|
|
4280
|
+
* Throws if the format is invalid or masterBytes length is not 32 bytes.
|
|
3320
4281
|
*/
|
|
3321
4282
|
async importFromHex(hex) {
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
4283
|
+
if (!hex.startsWith("mk:")) {
|
|
4284
|
+
throw new Error(
|
|
4285
|
+
'PrivacyKeyManager: invalid cache format. Expected "mk:0x{masterBytes_hex}".'
|
|
4286
|
+
);
|
|
3325
4287
|
}
|
|
3326
|
-
|
|
4288
|
+
const raw = hex.slice(3);
|
|
4289
|
+
const h = raw.startsWith("0x") ? raw.slice(2) : raw;
|
|
4290
|
+
const masterBytes = new Uint8Array((h.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
|
|
4291
|
+
if (masterBytes.length !== 32) {
|
|
4292
|
+
throw new Error("PrivacyKeyManager: invalid master bytes \u2014 expected 32 bytes.");
|
|
4293
|
+
}
|
|
4294
|
+
const masterBigint = BigInt("0x" + h);
|
|
4295
|
+
const sk = masterBigint % BABYJUB_SUBORDER || 1n;
|
|
4296
|
+
await this.load(sk, masterBytes);
|
|
3327
4297
|
}
|
|
3328
4298
|
};
|
|
3329
4299
|
|
|
3330
|
-
// src/
|
|
4300
|
+
// src/vault/VaultJson.ts
|
|
3331
4301
|
function vaultReplacer(_key, value) {
|
|
3332
4302
|
if (typeof value === "bigint") return { __bigint: value.toString() };
|
|
3333
4303
|
return value;
|
|
@@ -3338,16 +4308,28 @@ function vaultReviver(_key, value) {
|
|
|
3338
4308
|
}
|
|
3339
4309
|
return value;
|
|
3340
4310
|
}
|
|
4311
|
+
|
|
4312
|
+
// src/utils/encoding.ts
|
|
4313
|
+
function toBase64(buf) {
|
|
4314
|
+
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
4315
|
+
let str = "";
|
|
4316
|
+
for (const b of bytes) str += String.fromCharCode(b);
|
|
4317
|
+
return btoa(str);
|
|
4318
|
+
}
|
|
4319
|
+
function fromBase64(b64) {
|
|
4320
|
+
const bin = atob(b64);
|
|
4321
|
+
const out = new Uint8Array(bin.length);
|
|
4322
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
4323
|
+
return out;
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
// src/vault/VaultCrypto.ts
|
|
3341
4327
|
var VAULT_KEY_INFO = new TextEncoder().encode("orbinum-vault-key-v1");
|
|
3342
4328
|
var IV_BYTES = 12;
|
|
3343
|
-
async function deriveVaultKey(
|
|
3344
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
3345
|
-
"
|
|
3346
|
-
|
|
3347
|
-
"HKDF",
|
|
3348
|
-
false,
|
|
3349
|
-
["deriveKey"]
|
|
3350
|
-
);
|
|
4329
|
+
async function deriveVaultKey(masterBytes) {
|
|
4330
|
+
const keyMaterial = await crypto.subtle.importKey("raw", masterBytes.slice(0), "HKDF", false, [
|
|
4331
|
+
"deriveKey"
|
|
4332
|
+
]);
|
|
3351
4333
|
return crypto.subtle.deriveKey(
|
|
3352
4334
|
{
|
|
3353
4335
|
name: "HKDF",
|
|
@@ -3376,6 +4358,158 @@ async function decryptJson(key, iv, ciphertext) {
|
|
|
3376
4358
|
return JSON.parse(new TextDecoder().decode(plainBuf), vaultReviver);
|
|
3377
4359
|
}
|
|
3378
4360
|
|
|
4361
|
+
// src/vault/errors.ts
|
|
4362
|
+
var VaultLockedError = class extends Error {
|
|
4363
|
+
constructor(message = "Vault is locked. Connect your wallet to unlock it.") {
|
|
4364
|
+
super(message);
|
|
4365
|
+
this.name = "VaultLockedError";
|
|
4366
|
+
}
|
|
4367
|
+
};
|
|
4368
|
+
|
|
4369
|
+
// src/vault/noteOps.ts
|
|
4370
|
+
function applyNoteStatus(note, status) {
|
|
4371
|
+
return {
|
|
4372
|
+
...note,
|
|
4373
|
+
spent: status?.spent ?? note.spent ?? false,
|
|
4374
|
+
spentAt: status?.spentAt ?? note.spentAt ?? null
|
|
4375
|
+
};
|
|
4376
|
+
}
|
|
4377
|
+
async function encryptNote(key, note) {
|
|
4378
|
+
const { iv, ciphertext } = await encryptJson(key, note);
|
|
4379
|
+
return {
|
|
4380
|
+
commitmentHex: note.commitmentHex,
|
|
4381
|
+
iv,
|
|
4382
|
+
ciphertext,
|
|
4383
|
+
nullifierHex: note.nullifierHex,
|
|
4384
|
+
assetId: note.assetId.toString(),
|
|
4385
|
+
spent: note.spent,
|
|
4386
|
+
spentAt: note.spentAt,
|
|
4387
|
+
updatedAt: Date.now()
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
async function decryptNoteRecord(key, rec) {
|
|
4391
|
+
const note = await decryptJson(key, rec.iv, rec.ciphertext);
|
|
4392
|
+
return applyNoteStatus(note, {
|
|
4393
|
+
...rec.spent !== void 0 && { spent: rec.spent },
|
|
4394
|
+
spentAt: rec.spentAt ?? null
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
// src/proof-generator/unshield.ts
|
|
4399
|
+
var import_proof_generator = require("@orbinum/proof-generator");
|
|
4400
|
+
var import_utils3 = require("@noble/ciphers/utils.js");
|
|
4401
|
+
var import_baby_jubjub6 = require("@zk-kit/baby-jubjub");
|
|
4402
|
+
var import_poseidon_lite4 = require("poseidon-lite");
|
|
4403
|
+
|
|
4404
|
+
// src/proof-generator/merkle.ts
|
|
4405
|
+
function merkleProofToCircuit(siblings, leafIndex) {
|
|
4406
|
+
const elements = siblings.map((h) => leHexToBigint(h).toString());
|
|
4407
|
+
const depth = siblings.length;
|
|
4408
|
+
const indices = computePathIndices(leafIndex, depth).map(String);
|
|
4409
|
+
return { elements, indices };
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
// src/proof-generator/unshield.ts
|
|
4413
|
+
async function generateUnshieldProof(inputs, options = {}) {
|
|
4414
|
+
const { elements, indices } = merkleProofToCircuit(inputs.pathSiblings, inputs.leafIndex);
|
|
4415
|
+
const fee = inputs.fee ?? 0n;
|
|
4416
|
+
const changeValue = inputs.changeValue ?? 0n;
|
|
4417
|
+
const noteValue = inputs.amount + fee + changeValue;
|
|
4418
|
+
if (inputs.amount <= 0n) {
|
|
4419
|
+
throw new Error("Unshield amount must be greater than zero.");
|
|
4420
|
+
}
|
|
4421
|
+
if (changeValue < 0n) {
|
|
4422
|
+
throw new Error("changeValue must be >= 0.");
|
|
4423
|
+
}
|
|
4424
|
+
const changeOwnerPubkey = inputs.changeOwnerPubkey ?? (0, import_baby_jubjub6.mulPointEscalar)(import_baby_jubjub6.Base8, inputs.spendingKey)[0];
|
|
4425
|
+
const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE((0, import_utils3.randomBytes)(32)) : 0n);
|
|
4426
|
+
const changeCommitment = changeValue > 0n ? (0, import_poseidon_lite4.poseidon4)([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
|
|
4427
|
+
const circuitInputs = {
|
|
4428
|
+
merkle_root: leHexToBigint(inputs.merkleRoot).toString(),
|
|
4429
|
+
nullifier: inputs.nullifier.toString(),
|
|
4430
|
+
amount: inputs.amount.toString(),
|
|
4431
|
+
recipient: inputs.recipient.toString(),
|
|
4432
|
+
asset_id: inputs.assetId.toString(),
|
|
4433
|
+
fee: fee.toString(),
|
|
4434
|
+
change_commitment: changeCommitment.toString(),
|
|
4435
|
+
note_value: noteValue.toString(),
|
|
4436
|
+
note_asset_id: inputs.assetId.toString(),
|
|
4437
|
+
note_blinding: inputs.blinding.toString(),
|
|
4438
|
+
spending_key: inputs.spendingKey.toString(),
|
|
4439
|
+
path_elements: elements,
|
|
4440
|
+
path_indices: indices,
|
|
4441
|
+
change_value: changeValue.toString(),
|
|
4442
|
+
change_blinding: changeBlinding.toString(),
|
|
4443
|
+
change_owner_pubkey: changeOwnerPubkey.toString()
|
|
4444
|
+
};
|
|
4445
|
+
const provider = options.provider ?? new import_proof_generator.WebArtifactProvider();
|
|
4446
|
+
const opts = { provider };
|
|
4447
|
+
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4448
|
+
const proofResult = await (0, import_proof_generator.generateProof)(import_proof_generator.CircuitType.Unshield, circuitInputs, opts);
|
|
4449
|
+
return { ...proofResult, changeCommitment, changeValue, changeBlinding, changeOwnerPubkey };
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
// src/proof-generator/transfer.ts
|
|
4453
|
+
var import_proof_generator2 = require("@orbinum/proof-generator");
|
|
4454
|
+
async function generateTransferProof(params, options = {}) {
|
|
4455
|
+
const root = leHexToBigint(params.merkleRoot).toString();
|
|
4456
|
+
const [i0, i1] = params.inputs;
|
|
4457
|
+
const [o0, o1] = params.outputs;
|
|
4458
|
+
const fee = params.fee ?? 0n;
|
|
4459
|
+
const path0 = merkleProofToCircuit(i0.pathSiblings, i0.leafIndex);
|
|
4460
|
+
const path1 = merkleProofToCircuit(i1.pathSiblings, i1.leafIndex);
|
|
4461
|
+
const circuitInputs = {
|
|
4462
|
+
merkle_root: root,
|
|
4463
|
+
nullifiers: [i0.nullifier.toString(), i1.nullifier.toString()],
|
|
4464
|
+
commitments: [o0.commitment.toString(), o1.commitment.toString()],
|
|
4465
|
+
asset_id: i0.assetId.toString(),
|
|
4466
|
+
fee: fee.toString(),
|
|
4467
|
+
input_values: [i0.value.toString(), i1.value.toString()],
|
|
4468
|
+
input_asset_ids: [i0.assetId.toString(), i1.assetId.toString()],
|
|
4469
|
+
input_blindings: [i0.blinding.toString(), i1.blinding.toString()],
|
|
4470
|
+
spending_keys: [i0.spendingKey.toString(), i1.spendingKey.toString()],
|
|
4471
|
+
input_path_elements: [path0.elements, path1.elements],
|
|
4472
|
+
input_path_indices: [path0.indices, path1.indices],
|
|
4473
|
+
output_values: [o0.value.toString(), o1.value.toString()],
|
|
4474
|
+
output_asset_ids: [o0.assetId.toString(), o1.assetId.toString()],
|
|
4475
|
+
output_owner_pubkeys: [o0.ownerPk.toString(), o1.ownerPk.toString()],
|
|
4476
|
+
output_blindings: [o0.blinding.toString(), o1.blinding.toString()]
|
|
4477
|
+
};
|
|
4478
|
+
const provider = options.provider ?? new import_proof_generator2.WebArtifactProvider();
|
|
4479
|
+
const opts = { provider };
|
|
4480
|
+
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4481
|
+
return (0, import_proof_generator2.generateProof)(import_proof_generator2.CircuitType.Transfer, circuitInputs, opts);
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
// src/proof-generator/fee-claim.ts
|
|
4485
|
+
var import_proof_generator3 = require("@orbinum/proof-generator");
|
|
4486
|
+
async function generateFeeClaimProof(inputs, options = {}) {
|
|
4487
|
+
if (inputs.amount <= 0n) {
|
|
4488
|
+
throw new Error("Fee claim amount must be greater than zero.");
|
|
4489
|
+
}
|
|
4490
|
+
const circuitInputs = {
|
|
4491
|
+
commitment: inputs.commitment.toString(),
|
|
4492
|
+
value: inputs.amount.toString(),
|
|
4493
|
+
asset_id: inputs.assetId.toString(),
|
|
4494
|
+
owner_pubkey: inputs.ownerPubkey.toString(),
|
|
4495
|
+
blinding: inputs.blinding.toString()
|
|
4496
|
+
};
|
|
4497
|
+
const provider = options.provider ?? new import_proof_generator3.WebArtifactProvider();
|
|
4498
|
+
const opts = { provider };
|
|
4499
|
+
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4500
|
+
const proofResult = await (0, import_proof_generator3.generateProof)(import_proof_generator3.CircuitType.ValueProof, circuitInputs, opts);
|
|
4501
|
+
const [sigCommitment, sigValue, sigAssetId, sigOwnerHash] = proofResult.publicSignals.map(BigInt);
|
|
4502
|
+
const buf = new Uint8Array(76);
|
|
4503
|
+
buf.set(bigintTo32Le(sigCommitment), 0);
|
|
4504
|
+
buf.set(bigintTo32Le(sigValue).subarray(0, 8), 32);
|
|
4505
|
+
buf.set(bigintTo32Le(sigAssetId).subarray(0, 4), 40);
|
|
4506
|
+
buf.set(bigintTo32Le(sigOwnerHash), 44);
|
|
4507
|
+
return {
|
|
4508
|
+
proof: proofResult.proof,
|
|
4509
|
+
publicSignals: Array.from(buf)
|
|
4510
|
+
};
|
|
4511
|
+
}
|
|
4512
|
+
|
|
3379
4513
|
// src/account-mapping/types/index.ts
|
|
3380
4514
|
var SignatureScheme = {
|
|
3381
4515
|
Eip191: "Eip191",
|
|
@@ -3392,7 +4526,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
3392
4526
|
if (!fnSig) return null;
|
|
3393
4527
|
if (fnSig.startsWith("registerAlias")) {
|
|
3394
4528
|
try {
|
|
3395
|
-
const data =
|
|
4529
|
+
const data = fromHex(input.slice(10));
|
|
3396
4530
|
const alias = decodeString(data, 0);
|
|
3397
4531
|
return { fnSig, args: { alias } };
|
|
3398
4532
|
} catch {
|
|
@@ -3401,37 +4535,54 @@ function decodePrecompileCalldata(address, input) {
|
|
|
3401
4535
|
}
|
|
3402
4536
|
if (fnSig.startsWith("shield(")) {
|
|
3403
4537
|
try {
|
|
3404
|
-
const data =
|
|
4538
|
+
const data = fromHex(input.slice(10));
|
|
3405
4539
|
const assetId = decodeUint(data, 0);
|
|
3406
|
-
const
|
|
3407
|
-
|
|
3408
|
-
return { fnSig, args: { assetId, amount, commitment } };
|
|
4540
|
+
const commitment = toHex(data.slice(32, 64));
|
|
4541
|
+
return { fnSig, args: { assetId, commitment } };
|
|
3409
4542
|
} catch {
|
|
3410
4543
|
return { fnSig, args: {} };
|
|
3411
4544
|
}
|
|
3412
4545
|
}
|
|
3413
4546
|
if (fnSig.startsWith("unshield(")) {
|
|
3414
4547
|
try {
|
|
3415
|
-
const data =
|
|
4548
|
+
const data = fromHex(input.slice(10));
|
|
3416
4549
|
const root = toHex(data.slice(32, 64));
|
|
3417
4550
|
const nullifier = toHex(data.slice(64, 96));
|
|
3418
4551
|
const assetId = decodeUint(data, 96);
|
|
3419
4552
|
const amount = decodeUint(data, 128);
|
|
3420
4553
|
const recipient = toHex(data.slice(160, 192));
|
|
3421
|
-
|
|
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
|
+
};
|
|
3422
4560
|
} catch {
|
|
3423
4561
|
return { fnSig, args: {} };
|
|
3424
4562
|
}
|
|
3425
4563
|
}
|
|
3426
4564
|
if (fnSig.startsWith("privateTransfer(")) {
|
|
3427
4565
|
try {
|
|
3428
|
-
const data =
|
|
4566
|
+
const data = fromHex(input.slice(10));
|
|
3429
4567
|
const root = toHex(data.slice(32, 64));
|
|
3430
4568
|
const nullOffset = Number(decodeUint(data, 64));
|
|
3431
4569
|
const commOffset = Number(decodeUint(data, 96));
|
|
3432
4570
|
const nullifiers = Number(decodeUint(data, nullOffset));
|
|
3433
4571
|
const commitments = Number(decodeUint(data, commOffset));
|
|
3434
|
-
|
|
4572
|
+
const assetId = decodeUint(data, 160);
|
|
4573
|
+
const fee = decodeUint(data, 192);
|
|
4574
|
+
return { fnSig, args: { root, nullifiers, commitments, assetId, fee } };
|
|
4575
|
+
} catch {
|
|
4576
|
+
return { fnSig, args: {} };
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
if (fnSig.startsWith("claimShieldedFees(")) {
|
|
4580
|
+
try {
|
|
4581
|
+
const data = fromHex(input.slice(10));
|
|
4582
|
+
const commitment = toHex(data.slice(0, 32));
|
|
4583
|
+
const amount = decodeUint(data, 32);
|
|
4584
|
+
const assetId = decodeUint(data, 64);
|
|
4585
|
+
return { fnSig, args: { commitment, amount, assetId } };
|
|
3435
4586
|
} catch {
|
|
3436
4587
|
return { fnSig, args: {} };
|
|
3437
4588
|
}
|
|
@@ -3443,8 +4594,8 @@ function decodePrecompileCalldata(address, input) {
|
|
|
3443
4594
|
var CircuitId = {
|
|
3444
4595
|
Transfer: 1,
|
|
3445
4596
|
Unshield: 2,
|
|
3446
|
-
|
|
3447
|
-
PrivateLink:
|
|
4597
|
+
ValueProof: 4,
|
|
4598
|
+
PrivateLink: 5
|
|
3448
4599
|
};
|
|
3449
4600
|
|
|
3450
4601
|
// src/utils/string.ts
|
|
@@ -3633,34 +4784,6 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3633
4784
|
recipient: get(5, "recipient")
|
|
3634
4785
|
};
|
|
3635
4786
|
}
|
|
3636
|
-
if (m_norm === "setauditpolicy") {
|
|
3637
|
-
return {
|
|
3638
|
-
auditors: get(0, "auditors"),
|
|
3639
|
-
conditions: get(1, "conditions"),
|
|
3640
|
-
max_frequency: get(2, "max_frequency"),
|
|
3641
|
-
valid_until: get(3, "valid_until")
|
|
3642
|
-
};
|
|
3643
|
-
}
|
|
3644
|
-
if (m_norm === "requestdisclosure") {
|
|
3645
|
-
return {
|
|
3646
|
-
target: get(0, "target"),
|
|
3647
|
-
reason: get(1, "reason")
|
|
3648
|
-
};
|
|
3649
|
-
}
|
|
3650
|
-
if (m_norm === "disclose") {
|
|
3651
|
-
return {
|
|
3652
|
-
commitment: get(0, "commitment"),
|
|
3653
|
-
proof_bytes: get(1, "proof_bytes"),
|
|
3654
|
-
public_signals: get(2, "public_signals"),
|
|
3655
|
-
auditor: get(3, "auditor")
|
|
3656
|
-
};
|
|
3657
|
-
}
|
|
3658
|
-
if (m_norm === "rejectdisclosure") {
|
|
3659
|
-
return {
|
|
3660
|
-
auditor: get(0, "auditor"),
|
|
3661
|
-
reason: get(1, "reason")
|
|
3662
|
-
};
|
|
3663
|
-
}
|
|
3664
4787
|
if (m_norm === "registerasset") {
|
|
3665
4788
|
return {
|
|
3666
4789
|
name: get(0, "name"),
|
|
@@ -3675,18 +4798,6 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3675
4798
|
if (m_norm === "unverifyasset") {
|
|
3676
4799
|
return { asset_id: get(0, "asset_id") };
|
|
3677
4800
|
}
|
|
3678
|
-
if (m_norm === "batchsubmitdisclosureproofs") {
|
|
3679
|
-
return { submissions: get(0, "submissions") };
|
|
3680
|
-
}
|
|
3681
|
-
if (m_norm === "pruneexpiredrequest") {
|
|
3682
|
-
return {
|
|
3683
|
-
target: get(0, "target"),
|
|
3684
|
-
auditor: get(1, "auditor")
|
|
3685
|
-
};
|
|
3686
|
-
}
|
|
3687
|
-
if (m_norm === "revokedisclosurerecord") {
|
|
3688
|
-
return { commitment: get(0, "commitment") };
|
|
3689
|
-
}
|
|
3690
4801
|
}
|
|
3691
4802
|
if (s === "ethereum" && m === "transact") {
|
|
3692
4803
|
return { transaction: get(0, "transaction") };
|
|
@@ -3910,45 +5021,6 @@ function mapZkEventData(method, data) {
|
|
|
3910
5021
|
};
|
|
3911
5022
|
}
|
|
3912
5023
|
const m_norm = m.replace(/_/g, "");
|
|
3913
|
-
if (m_norm === "auditpolicyset") {
|
|
3914
|
-
return {
|
|
3915
|
-
account: get(0, "account"),
|
|
3916
|
-
version: get(1, "version")
|
|
3917
|
-
};
|
|
3918
|
-
}
|
|
3919
|
-
if (m_norm === "disclosed") {
|
|
3920
|
-
return {
|
|
3921
|
-
who: get(0, "who"),
|
|
3922
|
-
commitment: get(1, "commitment"),
|
|
3923
|
-
auditor: get(2, "auditor")
|
|
3924
|
-
};
|
|
3925
|
-
}
|
|
3926
|
-
if (m_norm === "disclosurerequested") {
|
|
3927
|
-
return {
|
|
3928
|
-
target: get(0, "target"),
|
|
3929
|
-
auditor: get(1, "auditor"),
|
|
3930
|
-
reason: get(2, "reason")
|
|
3931
|
-
};
|
|
3932
|
-
}
|
|
3933
|
-
if (m_norm === "disclosurerejected") {
|
|
3934
|
-
return {
|
|
3935
|
-
target: get(0, "target"),
|
|
3936
|
-
auditor: get(1, "auditor"),
|
|
3937
|
-
reason: get(2, "reason")
|
|
3938
|
-
};
|
|
3939
|
-
}
|
|
3940
|
-
if (m_norm === "disclosurerequestexpired") {
|
|
3941
|
-
return {
|
|
3942
|
-
target: get(0, "target"),
|
|
3943
|
-
auditor: get(1, "auditor")
|
|
3944
|
-
};
|
|
3945
|
-
}
|
|
3946
|
-
if (m_norm === "disclosurerecordrevoked") {
|
|
3947
|
-
return {
|
|
3948
|
-
who: get(0, "who"),
|
|
3949
|
-
commitment: get(1, "commitment")
|
|
3950
|
-
};
|
|
3951
|
-
}
|
|
3952
5024
|
if (m_norm === "assetregistered") {
|
|
3953
5025
|
return { asset_id: get(0, "asset_id") };
|
|
3954
5026
|
}
|
|
@@ -4192,9 +5264,13 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4192
5264
|
AccountId,
|
|
4193
5265
|
AccountMappingModule,
|
|
4194
5266
|
AccountMappingPrecompile,
|
|
5267
|
+
BABYJUB_SUBORDER,
|
|
5268
|
+
BN254_R,
|
|
4195
5269
|
Blake2256,
|
|
4196
5270
|
CircuitId,
|
|
5271
|
+
CircuitType,
|
|
4197
5272
|
CryptoPrecompiles,
|
|
5273
|
+
ENCRYPTED_MEMO_SIZE,
|
|
4198
5274
|
EncryptedMemo,
|
|
4199
5275
|
EvmClient,
|
|
4200
5276
|
EvmExplorer,
|
|
@@ -4207,30 +5283,44 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4207
5283
|
PRECOMPILE_ADDR,
|
|
4208
5284
|
PrivacyKeyManager,
|
|
4209
5285
|
PrivacyModule,
|
|
5286
|
+
RelayerStatusModule,
|
|
4210
5287
|
SLIP0044_NAMESPACE,
|
|
4211
5288
|
ShieldedPoolModule,
|
|
4212
5289
|
ShieldedPoolPrecompile,
|
|
4213
5290
|
SignatureScheme,
|
|
4214
5291
|
Storage,
|
|
4215
5292
|
SubstrateClient,
|
|
5293
|
+
VaultLockedError,
|
|
5294
|
+
WebArtifactProvider,
|
|
4216
5295
|
ZkVerifierModule,
|
|
4217
5296
|
accountIdHexToSs58,
|
|
4218
5297
|
addressToAccountIdHex,
|
|
5298
|
+
applyNoteStatus,
|
|
4219
5299
|
base58,
|
|
4220
5300
|
bigintTo32Be,
|
|
4221
5301
|
bigintTo32Le,
|
|
4222
5302
|
bigintTo32LeArr,
|
|
5303
|
+
buildDummyTransferInput,
|
|
4223
5304
|
bytesToBigintLE,
|
|
5305
|
+
computeNullifier,
|
|
4224
5306
|
computePathIndices,
|
|
4225
5307
|
connectInjectedExtension,
|
|
5308
|
+
createNoteDisclosureKey,
|
|
5309
|
+
decodeNoteDisclosureKey,
|
|
4226
5310
|
decodePrecompileCalldata,
|
|
4227
5311
|
decryptJson,
|
|
5312
|
+
decryptNoteRecord,
|
|
5313
|
+
deriveMasterKeyBytes,
|
|
4228
5314
|
deriveOwnerPk,
|
|
4229
5315
|
deriveSpendingKeyFromSignature,
|
|
4230
5316
|
deriveSpendingKeyMessage,
|
|
5317
|
+
deriveStealthOwnerPk,
|
|
5318
|
+
deriveStealthSk,
|
|
4231
5319
|
deriveVaultKey,
|
|
4232
|
-
|
|
5320
|
+
deriveViewingPublicKey,
|
|
5321
|
+
deriveViewingSecretKey,
|
|
4233
5322
|
encryptJson,
|
|
5323
|
+
encryptNote,
|
|
4234
5324
|
ensureHexPrefix,
|
|
4235
5325
|
evmAddressToAccountId,
|
|
4236
5326
|
evmToImplicitSubstrate,
|
|
@@ -4240,6 +5330,9 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4240
5330
|
formatORB,
|
|
4241
5331
|
fromBase64,
|
|
4242
5332
|
fromHex,
|
|
5333
|
+
generateFeeClaimProof,
|
|
5334
|
+
generateTransferProof,
|
|
5335
|
+
generateUnshieldProof,
|
|
4243
5336
|
getInjectedExtensions,
|
|
4244
5337
|
getPolkadotSigner,
|
|
4245
5338
|
getPolkadotSignerFromPjs,
|
|
@@ -4257,6 +5350,9 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4257
5350
|
mapExtrinsicArgs,
|
|
4258
5351
|
mapZkEventData,
|
|
4259
5352
|
normalizeEvmAddress,
|
|
5353
|
+
randomBlinding,
|
|
5354
|
+
recoverOwnerPkPoint,
|
|
5355
|
+
selectNotes,
|
|
4260
5356
|
shortHash,
|
|
4261
5357
|
substrateSs58ToAccountIdHex,
|
|
4262
5358
|
substrateToEvm,
|
|
@@ -4265,6 +5361,7 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4265
5361
|
toTxResult,
|
|
4266
5362
|
truncateMiddle,
|
|
4267
5363
|
tryDecryptNote,
|
|
5364
|
+
tryDecryptNoteVerbose,
|
|
4268
5365
|
u128,
|
|
4269
5366
|
u64,
|
|
4270
5367
|
vaultReplacer,
|