@orbinum/sdk 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -443
- package/dist/index.d.mts +2005 -845
- package/dist/index.d.ts +2005 -845
- package/dist/index.js +1729 -426
- package/dist/index.mjs +1713 -425
- package/package.json +22 -20
package/dist/index.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_generator2.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,45 @@ __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_generator2.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
|
+
buildDisclosurePublicSignals: () => buildDisclosurePublicSignals,
|
|
63
|
+
buildDummyTransferInput: () => buildDummyTransferInput,
|
|
54
64
|
bytesToBigintLE: () => bytesToBigintLE,
|
|
65
|
+
computeNullifier: () => computeNullifier,
|
|
55
66
|
computePathIndices: () => computePathIndices,
|
|
56
67
|
connectInjectedExtension: () => import_pjs_signer.connectInjectedExtension,
|
|
57
68
|
decodePrecompileCalldata: () => decodePrecompileCalldata,
|
|
69
|
+
decryptDisclosureSignals: () => decryptDisclosureSignals,
|
|
58
70
|
decryptJson: () => decryptJson,
|
|
71
|
+
decryptNoteRecord: () => decryptNoteRecord,
|
|
72
|
+
deriveBabyJubjubKeypair: () => deriveBabyJubjubKeypair,
|
|
73
|
+
deriveMasterKeyBytes: () => deriveMasterKeyBytes,
|
|
59
74
|
deriveOwnerPk: () => deriveOwnerPk,
|
|
60
75
|
deriveSpendingKeyFromSignature: () => deriveSpendingKeyFromSignature,
|
|
61
76
|
deriveSpendingKeyMessage: () => deriveSpendingKeyMessage,
|
|
77
|
+
deriveStealthOwnerPk: () => deriveStealthOwnerPk,
|
|
78
|
+
deriveStealthSk: () => deriveStealthSk,
|
|
62
79
|
deriveVaultKey: () => deriveVaultKey,
|
|
63
|
-
|
|
80
|
+
deriveViewingPublicKey: () => deriveViewingPublicKey,
|
|
81
|
+
deriveViewingSecretKey: () => deriveViewingSecretKey,
|
|
64
82
|
encryptJson: () => encryptJson,
|
|
83
|
+
encryptNote: () => encryptNote,
|
|
65
84
|
ensureHexPrefix: () => ensureHexPrefix,
|
|
66
85
|
evmAddressToAccountId: () => evmAddressToAccountId,
|
|
67
86
|
evmToImplicitSubstrate: () => evmToImplicitSubstrate,
|
|
@@ -71,6 +90,10 @@ __export(index_exports, {
|
|
|
71
90
|
formatORB: () => formatORB,
|
|
72
91
|
fromBase64: () => fromBase64,
|
|
73
92
|
fromHex: () => fromHex,
|
|
93
|
+
generateDisclosureProof: () => import_proof_generator.generateDisclosureProof,
|
|
94
|
+
generateFeeClaimProof: () => generateFeeClaimProof,
|
|
95
|
+
generateTransferProof: () => generateTransferProof,
|
|
96
|
+
generateUnshieldProof: () => generateUnshieldProof,
|
|
74
97
|
getInjectedExtensions: () => import_pjs_signer.getInjectedExtensions,
|
|
75
98
|
getPolkadotSigner: () => import_signer.getPolkadotSigner,
|
|
76
99
|
getPolkadotSignerFromPjs: () => import_pjs_signer.getPolkadotSignerFromPjs,
|
|
@@ -88,6 +111,9 @@ __export(index_exports, {
|
|
|
88
111
|
mapExtrinsicArgs: () => mapExtrinsicArgs,
|
|
89
112
|
mapZkEventData: () => mapZkEventData,
|
|
90
113
|
normalizeEvmAddress: () => normalizeEvmAddress,
|
|
114
|
+
randomBlinding: () => randomBlinding,
|
|
115
|
+
recoverOwnerPkPoint: () => recoverOwnerPkPoint,
|
|
116
|
+
selectNotes: () => selectNotes,
|
|
91
117
|
shortHash: () => shortHash,
|
|
92
118
|
substrateSs58ToAccountIdHex: () => substrateSs58ToAccountIdHex,
|
|
93
119
|
substrateToEvm: () => substrateToEvm,
|
|
@@ -96,6 +122,7 @@ __export(index_exports, {
|
|
|
96
122
|
toTxResult: () => toTxResult,
|
|
97
123
|
truncateMiddle: () => truncateMiddle,
|
|
98
124
|
tryDecryptNote: () => tryDecryptNote,
|
|
125
|
+
tryDecryptNoteVerbose: () => tryDecryptNoteVerbose,
|
|
99
126
|
u128: () => import_substrate_bindings3.u128,
|
|
100
127
|
u64: () => import_substrate_bindings3.u64,
|
|
101
128
|
vaultReplacer: () => vaultReplacer,
|
|
@@ -143,6 +170,7 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
143
170
|
constructor(_papi) {
|
|
144
171
|
this._papi = _papi;
|
|
145
172
|
}
|
|
173
|
+
_papi;
|
|
146
174
|
_dynamicBuilder = null;
|
|
147
175
|
_extDecoder = null;
|
|
148
176
|
/**
|
|
@@ -342,6 +370,14 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
342
370
|
submitAndWatch(signedHex) {
|
|
343
371
|
return this._papi.submitAndWatch(signedHex);
|
|
344
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Submits a bare (unsigned) extrinsic hex and waits for finalization.
|
|
375
|
+
* Used for gasless private_transfer and unshield transactions.
|
|
376
|
+
* The bare tx hex is produced by `tx.getBareTx()` from polkadot-api.
|
|
377
|
+
*/
|
|
378
|
+
async submitUnsignedAndWatch(bareTxHex) {
|
|
379
|
+
return this._papi.submit(bareTxHex);
|
|
380
|
+
}
|
|
345
381
|
/**
|
|
346
382
|
* Convenience: wrap raw call bytes and sign+submit in one step.
|
|
347
383
|
*/
|
|
@@ -393,19 +429,25 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
393
429
|
}
|
|
394
430
|
static _buildDataProxy(value) {
|
|
395
431
|
const formatValue = (v) => {
|
|
396
|
-
if (v instanceof Uint8Array) return
|
|
432
|
+
if (v instanceof Uint8Array) return toHex(v);
|
|
397
433
|
if (typeof v === "bigint") return v.toString();
|
|
398
434
|
return String(v);
|
|
399
435
|
};
|
|
400
436
|
const jsonifyValue = (v) => {
|
|
401
437
|
if (v === null || v === void 0) return v;
|
|
402
438
|
if (typeof v === "bigint") return v.toString();
|
|
403
|
-
if (v instanceof Uint8Array)
|
|
404
|
-
return Array.from(v).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
439
|
+
if (v instanceof Uint8Array) return toHex(v);
|
|
405
440
|
if (Array.isArray(v)) return v.map(jsonifyValue);
|
|
406
441
|
if (typeof v === "object") {
|
|
442
|
+
const obj = v;
|
|
443
|
+
if (typeof obj["asHex"] === "function") {
|
|
444
|
+
try {
|
|
445
|
+
return obj["asHex"]();
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
}
|
|
407
449
|
return Object.fromEntries(
|
|
408
|
-
Object.entries(
|
|
450
|
+
Object.entries(obj).filter(([, val]) => typeof val !== "function").map(([k, val]) => [k, jsonifyValue(val)])
|
|
409
451
|
);
|
|
410
452
|
}
|
|
411
453
|
return v;
|
|
@@ -498,11 +540,14 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
498
540
|
|
|
499
541
|
// src/evm/EvmClient.ts
|
|
500
542
|
var EvmClient = class {
|
|
543
|
+
/** @param rpcUrl - HTTP URL of the EVM JSON-RPC endpoint (e.g. `"http://localhost:9933"`). */
|
|
501
544
|
constructor(rpcUrl) {
|
|
502
545
|
this.rpcUrl = rpcUrl;
|
|
503
546
|
}
|
|
547
|
+
rpcUrl;
|
|
504
548
|
/**
|
|
505
|
-
* Performs a single JSON-RPC call.
|
|
549
|
+
* Performs a single JSON-RPC call and returns the typed result.
|
|
550
|
+
* Throws on HTTP errors, RPC-level errors, or a `null` result.
|
|
506
551
|
*/
|
|
507
552
|
async request(method, params = []) {
|
|
508
553
|
const res = await fetch(this.rpcUrl, {
|
|
@@ -522,6 +567,7 @@ var EvmClient = class {
|
|
|
522
567
|
}
|
|
523
568
|
/**
|
|
524
569
|
* Performs multiple JSON-RPC calls in a single HTTP request (batch).
|
|
570
|
+
* Results are returned in the same order as `calls`, as a typed tuple.
|
|
525
571
|
*/
|
|
526
572
|
async batchRequest(calls) {
|
|
527
573
|
const body = calls.map((c, i) => ({
|
|
@@ -566,30 +612,22 @@ var EvmClient = class {
|
|
|
566
612
|
const hex = await this.request("eth_gasPrice", []);
|
|
567
613
|
return hexToBigint(hex);
|
|
568
614
|
}
|
|
569
|
-
/**
|
|
570
|
-
* Submits a signed raw transaction. Returns the transaction hash.
|
|
571
|
-
*/
|
|
615
|
+
/** Submits a signed raw transaction. Returns the transaction hash. */
|
|
572
616
|
async sendRawTransaction(signedHex) {
|
|
573
617
|
return this.request("eth_sendRawTransaction", [signedHex]);
|
|
574
618
|
}
|
|
575
|
-
/**
|
|
576
|
-
* Executes a read-only call without creating a transaction.
|
|
577
|
-
*/
|
|
619
|
+
/** Executes a read-only call without creating a transaction. Returns the raw ABI-encoded response. */
|
|
578
620
|
async call(to, data, from) {
|
|
579
621
|
const txObj = { to, data };
|
|
580
622
|
if (from) txObj["from"] = from;
|
|
581
623
|
return this.request("eth_call", [txObj, "latest"]);
|
|
582
624
|
}
|
|
583
|
-
/**
|
|
584
|
-
* Estimates the gas for a transaction.
|
|
585
|
-
*/
|
|
625
|
+
/** Estimates the gas required for a transaction. Returns the estimate in wei as a `bigint`. */
|
|
586
626
|
async estimateGas(params) {
|
|
587
627
|
const hex = await this.request("eth_estimateGas", [params]);
|
|
588
628
|
return hexToBigint(hex);
|
|
589
629
|
}
|
|
590
|
-
/**
|
|
591
|
-
* Returns a transaction receipt by hash, or null if not yet mined.
|
|
592
|
-
*/
|
|
630
|
+
/** Returns a transaction receipt by hash, or `null` if the transaction has not been mined yet. */
|
|
593
631
|
async getTransactionReceipt(txHash) {
|
|
594
632
|
const res = await fetch(this.rpcUrl, {
|
|
595
633
|
method: "POST",
|
|
@@ -608,6 +646,56 @@ var EvmClient = class {
|
|
|
608
646
|
}
|
|
609
647
|
return json.result ?? null;
|
|
610
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Polls `eth_getTransactionReceipt` until the transaction is included in a block.
|
|
651
|
+
*
|
|
652
|
+
* @param txHash - The transaction hash to wait for.
|
|
653
|
+
* @param intervalMs - Polling interval in milliseconds (default: 500).
|
|
654
|
+
* @param timeoutMs - Maximum time to wait in milliseconds (default: 60_000).
|
|
655
|
+
* @returns The transaction receipt once mined.
|
|
656
|
+
* @throws If the transaction is not mined within `timeoutMs` or if it reverted (`status == 0x0`).
|
|
657
|
+
*/
|
|
658
|
+
async waitForReceipt(txHash, intervalMs = 500, timeoutMs = 6e4) {
|
|
659
|
+
const deadline = Date.now() + timeoutMs;
|
|
660
|
+
while (Date.now() < deadline) {
|
|
661
|
+
const receipt = await this.getTransactionReceipt(txHash);
|
|
662
|
+
if (receipt !== null) {
|
|
663
|
+
if (receipt["status"] === "0x0") {
|
|
664
|
+
let revertDetail = "";
|
|
665
|
+
const nodeReason = receipt["revertReason"];
|
|
666
|
+
if (nodeReason) revertDetail = ` | revertReason: ${nodeReason}`;
|
|
667
|
+
if (!revertDetail) {
|
|
668
|
+
try {
|
|
669
|
+
const blockParam = receipt["blockNumber"] ?? "latest";
|
|
670
|
+
const rawTx = await this.request(
|
|
671
|
+
"eth_getTransactionByHash",
|
|
672
|
+
[txHash]
|
|
673
|
+
).catch(() => null);
|
|
674
|
+
if (rawTx) {
|
|
675
|
+
const calldata = rawTx["input"] ?? rawTx["data"];
|
|
676
|
+
if (calldata) {
|
|
677
|
+
const revertData = await this.request("eth_call", [
|
|
678
|
+
{ from: rawTx["from"], to: rawTx["to"], data: calldata },
|
|
679
|
+
blockParam
|
|
680
|
+
]).catch(
|
|
681
|
+
(err) => err instanceof Error ? err.message : String(err)
|
|
682
|
+
);
|
|
683
|
+
revertDetail = ` | eth_call: ${revertData}`;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
throw new Error(
|
|
690
|
+
`Transaction reverted on-chain: ${txHash}${revertDetail} | receipt: ${JSON.stringify(receipt)}`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
return receipt;
|
|
694
|
+
}
|
|
695
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
696
|
+
}
|
|
697
|
+
throw new Error(`Transaction not mined within ${timeoutMs}ms: ${txHash}`);
|
|
698
|
+
}
|
|
611
699
|
};
|
|
612
700
|
|
|
613
701
|
// src/utils/format.ts
|
|
@@ -680,10 +768,13 @@ function formatORB(raw, precision = 6) {
|
|
|
680
768
|
|
|
681
769
|
// src/evm-explorer/EvmExplorer.ts
|
|
682
770
|
var EvmExplorer = class _EvmExplorer {
|
|
771
|
+
/** @param evm - Underlying `EvmClient` used for all RPC calls. */
|
|
683
772
|
constructor(evm) {
|
|
684
773
|
this.evm = evm;
|
|
685
774
|
}
|
|
775
|
+
evm;
|
|
686
776
|
// --- Blocks ---
|
|
777
|
+
/** Returns the `count` most recent blocks in descending order (latest first). */
|
|
687
778
|
async getLatestBlocks(count = 10) {
|
|
688
779
|
const latest = await this.evm.getBlockNumber();
|
|
689
780
|
const nums = Array.from({ length: Math.min(count, latest + 1) }, (_, i) => latest - i);
|
|
@@ -697,10 +788,12 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
697
788
|
);
|
|
698
789
|
return results.filter((b) => b !== null && !!b.hash).map((b) => this.parseBlock(b));
|
|
699
790
|
}
|
|
791
|
+
/** Returns a single block by number or hash, or `null` if not found. */
|
|
700
792
|
async getBlock(hashOrNumber) {
|
|
701
793
|
const b = await this.fetchBlock(hashOrNumber, false);
|
|
702
794
|
return b ? this.parseBlock(b) : null;
|
|
703
795
|
}
|
|
796
|
+
/** Returns all transactions in a block (with receipts), or `[]` if the block is not found. */
|
|
704
797
|
async getBlockTransactions(hashOrNumber) {
|
|
705
798
|
try {
|
|
706
799
|
const b = await this.fetchBlock(hashOrNumber, true);
|
|
@@ -713,6 +806,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
713
806
|
}
|
|
714
807
|
}
|
|
715
808
|
// --- Transactions ---
|
|
809
|
+
/** Returns a single transaction with its receipt, or `null` if not found. */
|
|
716
810
|
async getTransaction(hash) {
|
|
717
811
|
try {
|
|
718
812
|
const [tx, receipt] = await Promise.all([
|
|
@@ -725,6 +819,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
725
819
|
return null;
|
|
726
820
|
}
|
|
727
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* Returns lightweight summaries of all transactions sent from or to `address`
|
|
824
|
+
* within the last `maxBlocks` blocks, sorted by block number descending.
|
|
825
|
+
*/
|
|
728
826
|
async getTransactionsByAddress(address, maxBlocks = 300) {
|
|
729
827
|
const addr = address.toLowerCase();
|
|
730
828
|
const latest = await this.evm.getBlockNumber();
|
|
@@ -776,6 +874,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
776
874
|
return results;
|
|
777
875
|
}
|
|
778
876
|
// --- Address ---
|
|
877
|
+
/**
|
|
878
|
+
* Returns aggregated on-chain data for an EVM address: balance, nonce,
|
|
879
|
+
* bytecode (truncated), and up to 50 recent logs from the last 5 000 blocks.
|
|
880
|
+
*/
|
|
779
881
|
async getAddressInfo(address) {
|
|
780
882
|
const latest = await this.evm.getBlockNumber().catch(() => 0);
|
|
781
883
|
const fromBlock = `0x${Math.max(0, latest - 5e3).toString(16)}`;
|
|
@@ -806,6 +908,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
806
908
|
recentLogs
|
|
807
909
|
};
|
|
808
910
|
}
|
|
911
|
+
/** Returns the native token balance of `address`, formatted as a decimal string (no symbol). */
|
|
809
912
|
async getBalance(address) {
|
|
810
913
|
try {
|
|
811
914
|
const val = await this.evm.getBalance(address);
|
|
@@ -814,6 +917,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
814
917
|
return "0";
|
|
815
918
|
}
|
|
816
919
|
}
|
|
920
|
+
/** Returns the current transaction count (nonce) for `address`, or `0` on error. */
|
|
817
921
|
async getNonce(address) {
|
|
818
922
|
try {
|
|
819
923
|
return await this.evm.getTransactionCount(address);
|
|
@@ -821,6 +925,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
821
925
|
return 0;
|
|
822
926
|
}
|
|
823
927
|
}
|
|
928
|
+
/** Returns `true` when `address` has non-empty deployed bytecode. */
|
|
824
929
|
async getIsContract(address) {
|
|
825
930
|
try {
|
|
826
931
|
const code = await this.evm.request("eth_getCode", [address, "latest"]);
|
|
@@ -830,6 +935,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
830
935
|
}
|
|
831
936
|
}
|
|
832
937
|
// --- Tokens ---
|
|
938
|
+
/**
|
|
939
|
+
* Fetches ERC-20 metadata for a token contract via ABI calls.
|
|
940
|
+
* Returns `null` when the address does not look like an ERC-20 token.
|
|
941
|
+
*/
|
|
833
942
|
async getTokenInfo(address) {
|
|
834
943
|
const addr = address.toLowerCase();
|
|
835
944
|
const [name, symbol, decimals, totalSupply] = await this.evm.batchRequest([
|
|
@@ -849,6 +958,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
849
958
|
isErc20
|
|
850
959
|
};
|
|
851
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* Returns ERC-20 `Transfer` events for `address` from the last 5 000 blocks.
|
|
963
|
+
* When `holderAddress` is provided, restricts results to transfers sent or received by that address.
|
|
964
|
+
*/
|
|
852
965
|
async getTokenTransfers(address, holderAddress) {
|
|
853
966
|
const TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
854
967
|
const latest = await this.evm.getBlockNumber().catch(() => 0);
|
|
@@ -876,12 +989,14 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
876
989
|
logIndex: hexToNumber(l.logIndex)
|
|
877
990
|
}));
|
|
878
991
|
}
|
|
992
|
+
/** Returns the raw ERC-20 balance of `holderAddress` for the token at `tokenAddress` (0x-prefixed hex). */
|
|
879
993
|
async getTokenBalance(tokenAddress, holderAddress) {
|
|
880
994
|
const padded = holderAddress.replace(/^0x/, "").toLowerCase().padStart(64, "0");
|
|
881
995
|
const result = await this.ethCall(tokenAddress, `0x70a08231${padded}`);
|
|
882
996
|
return result ?? "0x0";
|
|
883
997
|
}
|
|
884
998
|
// --- Private: parsers ---
|
|
999
|
+
/** Maps a raw RPC block object to the public `EvmBlock` shape. */
|
|
885
1000
|
parseBlock(b) {
|
|
886
1001
|
return {
|
|
887
1002
|
hash: b.hash,
|
|
@@ -894,6 +1009,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
894
1009
|
parentHash: b.parentHash
|
|
895
1010
|
};
|
|
896
1011
|
}
|
|
1012
|
+
/** Maps a raw RPC transaction + optional receipt to the public `EvmTransaction` shape. */
|
|
897
1013
|
parseTx(tx, receipt) {
|
|
898
1014
|
const parsed = {
|
|
899
1015
|
hash: tx.hash,
|
|
@@ -913,6 +1029,10 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
913
1029
|
return parsed;
|
|
914
1030
|
}
|
|
915
1031
|
// --- Private: fetch helpers ---
|
|
1032
|
+
/**
|
|
1033
|
+
* Fetches a raw block by number or hash. Accepts a plain integer, a decimal string,
|
|
1034
|
+
* or a 0x-prefixed hash. Returns `null` on any RPC error.
|
|
1035
|
+
*/
|
|
916
1036
|
async fetchBlock(hashOrNumber, withTxObjects) {
|
|
917
1037
|
try {
|
|
918
1038
|
if (typeof hashOrNumber === "number" || /^\d+$/.test(String(hashOrNumber))) {
|
|
@@ -930,6 +1050,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
930
1050
|
return null;
|
|
931
1051
|
}
|
|
932
1052
|
}
|
|
1053
|
+
/** Executes a read-only `eth_call` and returns the raw hex result, or `null` on error. */
|
|
933
1054
|
async ethCall(to, data) {
|
|
934
1055
|
try {
|
|
935
1056
|
return await this.evm.call(to, data);
|
|
@@ -938,6 +1059,7 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
938
1059
|
}
|
|
939
1060
|
}
|
|
940
1061
|
// --- Private static: ABI decoders ---
|
|
1062
|
+
/** Decodes an ABI-encoded `string` return value from a raw 0x-prefixed hex string. */
|
|
941
1063
|
static decodeAbiString(hex) {
|
|
942
1064
|
if (!hex || hex === "0x") return "";
|
|
943
1065
|
const data = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
@@ -952,11 +1074,13 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
952
1074
|
return "";
|
|
953
1075
|
}
|
|
954
1076
|
}
|
|
1077
|
+
/** Decodes an ABI-encoded `uint256` return value to a `bigint`. */
|
|
955
1078
|
static decodeAbiUint(hex) {
|
|
956
1079
|
if (!hex || hex === "0x") return 0n;
|
|
957
1080
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
958
1081
|
return BigInt(`0x${clean || "0"}`);
|
|
959
1082
|
}
|
|
1083
|
+
/** Converts a 0x-prefixed hex number to its decimal string representation. Returns `'0'` on parse error. */
|
|
960
1084
|
static hexToDecimalStr(hex) {
|
|
961
1085
|
try {
|
|
962
1086
|
return BigInt(hex).toString();
|
|
@@ -991,6 +1115,24 @@ var IndexerClient = class {
|
|
|
991
1115
|
}
|
|
992
1116
|
return res.json();
|
|
993
1117
|
}
|
|
1118
|
+
async post(path, body) {
|
|
1119
|
+
const controller = new AbortController();
|
|
1120
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1121
|
+
try {
|
|
1122
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
1123
|
+
method: "POST",
|
|
1124
|
+
headers: { "Content-Type": "application/json" },
|
|
1125
|
+
body: JSON.stringify(body),
|
|
1126
|
+
signal: controller.signal
|
|
1127
|
+
});
|
|
1128
|
+
if (!res.ok) {
|
|
1129
|
+
throw new Error(`IndexerClient: HTTP ${res.status} for POST ${path}`);
|
|
1130
|
+
}
|
|
1131
|
+
return res.json();
|
|
1132
|
+
} finally {
|
|
1133
|
+
clearTimeout(timer);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
994
1136
|
async getOrNull(path) {
|
|
995
1137
|
const res = await this._fetchResponse(path);
|
|
996
1138
|
if (res.status === 404) return null;
|
|
@@ -1026,6 +1168,21 @@ var IndexerClient = class {
|
|
|
1026
1168
|
`/shielded/commitments/${encodeURIComponent(hex)}`
|
|
1027
1169
|
);
|
|
1028
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Returns a paginated list of stealth scan hints ordered ascending by leafIndex.
|
|
1173
|
+
* Each hint contains only the fields required for ECDH triage and decryption:
|
|
1174
|
+
* leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo.
|
|
1175
|
+
*
|
|
1176
|
+
* Use `sinceLeafIndex` for incremental scans (cursor = last seen leafIndex + 1).
|
|
1177
|
+
*/
|
|
1178
|
+
async getScanHints(params) {
|
|
1179
|
+
const qs = this.buildQuery({
|
|
1180
|
+
page: params?.page,
|
|
1181
|
+
limit: params?.limit,
|
|
1182
|
+
since_leaf_index: params?.sinceLeafIndex
|
|
1183
|
+
});
|
|
1184
|
+
return this.get(`/shielded/scan-hints${qs}`);
|
|
1185
|
+
}
|
|
1029
1186
|
// ─── Nullifiers ────────────────────────────────────────────────────────────
|
|
1030
1187
|
/** Returns a paginated list of spent nullifiers. */
|
|
1031
1188
|
async getNullifiers(params) {
|
|
@@ -1038,11 +1195,50 @@ var IndexerClient = class {
|
|
|
1038
1195
|
`/shielded/nullifier/${encodeURIComponent(hex)}/status`
|
|
1039
1196
|
);
|
|
1040
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Batch-checks which of the given nullifiers are spent.
|
|
1200
|
+
* Returns only the nullifiers that exist in the spent set.
|
|
1201
|
+
* Accepts up to 100 nullifiers (0x-prefixed hex).
|
|
1202
|
+
*/
|
|
1203
|
+
async getNullifiersBatch(nullifiers) {
|
|
1204
|
+
if (nullifiers.length === 0) return [];
|
|
1205
|
+
const res = await this.post("/shielded/nullifiers/batch", {
|
|
1206
|
+
nullifiers: nullifiers.map((n) => n.toLowerCase())
|
|
1207
|
+
});
|
|
1208
|
+
return res.data;
|
|
1209
|
+
}
|
|
1041
1210
|
// ─── Private transfers ─────────────────────────────────────────────────────
|
|
1042
|
-
/**
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1211
|
+
/**
|
|
1212
|
+
* Returns temporal metadata for private transfers that spent any of the given nullifiers.
|
|
1213
|
+
* Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
|
|
1214
|
+
* between inputs and outputs to prevent graph reconstruction.
|
|
1215
|
+
* Accepts up to 50 nullifiers (0x-prefixed hex).
|
|
1216
|
+
*/
|
|
1217
|
+
async getTransfersByNullifiers(nullifiers) {
|
|
1218
|
+
if (nullifiers.length === 0) return [];
|
|
1219
|
+
const qs = this.buildQuery({
|
|
1220
|
+
nullifiers: nullifiers.map((n) => n.toLowerCase()).join(",")
|
|
1221
|
+
});
|
|
1222
|
+
const res = await this.get(
|
|
1223
|
+
`/shielded/transfers/by-nullifiers${qs}`
|
|
1224
|
+
);
|
|
1225
|
+
return res.data;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Returns temporal metadata for private transfers that produced any of the given commitments.
|
|
1229
|
+
* Only blockNumber, extrinsicIndex, timestampMs, and hash are returned — no cross-link
|
|
1230
|
+
* between outputs and inputs to prevent graph reconstruction.
|
|
1231
|
+
* Accepts up to 50 commitments (0x-prefixed hex).
|
|
1232
|
+
*/
|
|
1233
|
+
async getTransfersByCommitments(commitments) {
|
|
1234
|
+
if (commitments.length === 0) return [];
|
|
1235
|
+
const qs = this.buildQuery({
|
|
1236
|
+
commitments: commitments.map((c) => c.toLowerCase()).join(",")
|
|
1237
|
+
});
|
|
1238
|
+
const res = await this.get(
|
|
1239
|
+
`/shielded/transfers/by-commitments${qs}`
|
|
1240
|
+
);
|
|
1241
|
+
return res.data;
|
|
1046
1242
|
}
|
|
1047
1243
|
// ─── Unshields ─────────────────────────────────────────────────────────────
|
|
1048
1244
|
/** Returns a paginated list of unshield events. */
|
|
@@ -1102,6 +1298,16 @@ var IndexerClient = class {
|
|
|
1102
1298
|
`/address/${encodeURIComponent(address.toLowerCase())}/shielded${qs}`
|
|
1103
1299
|
);
|
|
1104
1300
|
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Returns a paginated list of unshield events where the given address is the recipient.
|
|
1303
|
+
* Accepts a 0x-prefixed EVM address or an SS58 Substrate address.
|
|
1304
|
+
*/
|
|
1305
|
+
async getAddressUnshields(address, params) {
|
|
1306
|
+
const qs = this.buildQuery({ page: params?.page, limit: params?.limit });
|
|
1307
|
+
return this.get(
|
|
1308
|
+
`/address/${encodeURIComponent(address)}/unshields${qs}`
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1105
1311
|
/**
|
|
1106
1312
|
* Returns a paginated list of all shielded activity (commitments, unshields,
|
|
1107
1313
|
* private transfers) associated with the given address.
|
|
@@ -1137,10 +1343,24 @@ var IndexerClient = class {
|
|
|
1137
1343
|
}
|
|
1138
1344
|
};
|
|
1139
1345
|
|
|
1140
|
-
// src/shielded-pool/ShieldedPoolModule.ts
|
|
1346
|
+
// src/shielded-pool/pallet/ShieldedPoolModule.ts
|
|
1141
1347
|
var import_polkadot_api2 = require("polkadot-api");
|
|
1142
1348
|
|
|
1143
1349
|
// src/utils/tx.ts
|
|
1350
|
+
function formatDispatchError(err) {
|
|
1351
|
+
if (err.type === "Module") {
|
|
1352
|
+
const inner = err.value;
|
|
1353
|
+
if (inner?.type) {
|
|
1354
|
+
return `Module(${inner.type})`;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
try {
|
|
1358
|
+
const detail = JSON.stringify(err.value);
|
|
1359
|
+
return detail && detail !== "null" ? `${err.type}(${detail})` : err.type;
|
|
1360
|
+
} catch {
|
|
1361
|
+
return err.type;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1144
1364
|
function toTxResult(payload) {
|
|
1145
1365
|
const base = {
|
|
1146
1366
|
txHash: payload.txHash,
|
|
@@ -1149,13 +1369,18 @@ function toTxResult(payload) {
|
|
|
1149
1369
|
ok: payload.ok
|
|
1150
1370
|
};
|
|
1151
1371
|
if (!payload.ok) {
|
|
1152
|
-
return { ...base, error: payload.dispatchError
|
|
1372
|
+
return { ...base, error: formatDispatchError(payload.dispatchError) };
|
|
1153
1373
|
}
|
|
1154
1374
|
return base;
|
|
1155
1375
|
}
|
|
1156
1376
|
function callUnsafeTx(txEntry, ...args) {
|
|
1157
1377
|
return txEntry(...args);
|
|
1158
1378
|
}
|
|
1379
|
+
async function submitBareTx(tx, client) {
|
|
1380
|
+
const bareTxHex = await tx.getBareTx();
|
|
1381
|
+
const payload = await client.submitUnsignedAndWatch(bareTxHex);
|
|
1382
|
+
return toTxResult(payload);
|
|
1383
|
+
}
|
|
1159
1384
|
function resolveTx(unsafe, pallet, call) {
|
|
1160
1385
|
const u = unsafe;
|
|
1161
1386
|
const p = u["tx"]?.[pallet];
|
|
@@ -1166,9 +1391,10 @@ function resolveTx(unsafe, pallet, call) {
|
|
|
1166
1391
|
return entry;
|
|
1167
1392
|
}
|
|
1168
1393
|
|
|
1169
|
-
// src/shielded-pool/EncryptedMemo.ts
|
|
1394
|
+
// src/shielded-pool/protocol/EncryptedMemo.ts
|
|
1170
1395
|
var import_chacha = require("@noble/ciphers/chacha.js");
|
|
1171
1396
|
var import_utils = require("@noble/ciphers/utils.js");
|
|
1397
|
+
var import_baby_jubjub = require("@zk-kit/baby-jubjub");
|
|
1172
1398
|
|
|
1173
1399
|
// src/utils/bytes.ts
|
|
1174
1400
|
function bigintTo32Le(n) {
|
|
@@ -1215,77 +1441,114 @@ function computePathIndices(leafIndex, depth) {
|
|
|
1215
1441
|
return indices;
|
|
1216
1442
|
}
|
|
1217
1443
|
function leHexToBigint(hex) {
|
|
1218
|
-
|
|
1219
|
-
const bytes = new Uint8Array(h.length / 2);
|
|
1220
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1221
|
-
bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
|
|
1222
|
-
}
|
|
1223
|
-
return bytesToBigintLE(bytes);
|
|
1444
|
+
return bytesToBigintLE(fromHex(hex));
|
|
1224
1445
|
}
|
|
1225
1446
|
|
|
1226
|
-
// src/
|
|
1447
|
+
// src/utils/crypto-constants.ts
|
|
1448
|
+
var BN254_R = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
1449
|
+
var BABYJUB_SUBORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
1450
|
+
|
|
1451
|
+
// src/shielded-pool/protocol/memo.ts
|
|
1227
1452
|
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
1228
1453
|
var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
|
|
1229
|
-
var MEMO_PLAINTEXT_SIZE =
|
|
1230
|
-
function serializeMemo(value, ownerPk, blinding, assetId) {
|
|
1454
|
+
var MEMO_PLAINTEXT_SIZE = 116;
|
|
1455
|
+
function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
|
|
1231
1456
|
const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
|
|
1232
1457
|
const view = new DataView(buf.buffer);
|
|
1233
1458
|
view.setBigUint64(0, value & 0xffffffffffffffffn, true);
|
|
1234
|
-
|
|
1235
|
-
buf.set(
|
|
1236
|
-
|
|
1459
|
+
view.setBigUint64(8, value >> 64n & 0xffffffffffffffffn, true);
|
|
1460
|
+
buf.set(ownerPk.slice(0, 32), 16);
|
|
1461
|
+
buf.set(blinding.slice(0, 32), 48);
|
|
1462
|
+
view.setUint32(80, assetId >>> 0, true);
|
|
1463
|
+
buf.set(counterpartyPk.slice(0, 32), 84);
|
|
1237
1464
|
return buf;
|
|
1238
1465
|
}
|
|
1239
|
-
function deriveEncryptionKey(
|
|
1466
|
+
function deriveEncryptionKey(sharedSecret, commitment) {
|
|
1240
1467
|
const h = import_sha2.sha256.create();
|
|
1241
|
-
h.update(
|
|
1468
|
+
h.update(sharedSecret);
|
|
1242
1469
|
h.update(commitment);
|
|
1243
1470
|
h.update(KEY_DOMAIN);
|
|
1244
1471
|
return h.digest();
|
|
1245
1472
|
}
|
|
1246
|
-
function toBase64(buf) {
|
|
1247
|
-
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
1248
|
-
let str = "";
|
|
1249
|
-
for (const b of bytes) str += String.fromCharCode(b);
|
|
1250
|
-
return btoa(str);
|
|
1251
|
-
}
|
|
1252
|
-
function fromBase64(b64) {
|
|
1253
|
-
const bin = atob(b64);
|
|
1254
|
-
const out = new Uint8Array(bin.length);
|
|
1255
|
-
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
1256
|
-
return out;
|
|
1257
|
-
}
|
|
1258
1473
|
|
|
1259
|
-
// src/shielded-pool/EncryptedMemo.ts
|
|
1474
|
+
// src/shielded-pool/protocol/EncryptedMemo.ts
|
|
1260
1475
|
var NONCE_SIZE = 12;
|
|
1261
|
-
var
|
|
1476
|
+
var CIPHERTEXT_SIZE = 132;
|
|
1477
|
+
var EPH_PK_SIZE = 32;
|
|
1478
|
+
var ENCRYPTED_MEMO_SIZE = NONCE_SIZE + CIPHERTEXT_SIZE + EPH_PK_SIZE;
|
|
1479
|
+
function bytesToBjjScalar(bytes) {
|
|
1480
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1481
|
+
return BigInt("0x" + hex) % BABYJUB_SUBORDER || 1n;
|
|
1482
|
+
}
|
|
1483
|
+
function parsePlaintext(nonce, ciphertextWithMac, encKey) {
|
|
1484
|
+
try {
|
|
1485
|
+
const cipher = (0, import_chacha.chacha20poly1305)(encKey, nonce);
|
|
1486
|
+
const plaintext = cipher.decrypt(ciphertextWithMac);
|
|
1487
|
+
const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);
|
|
1488
|
+
const valueLo = view.getBigUint64(0, true);
|
|
1489
|
+
const valueHi = view.getBigUint64(8, true);
|
|
1490
|
+
const value = valueLo | valueHi << 64n;
|
|
1491
|
+
const ownerPk = bytesToBigintLE(plaintext.slice(16, 48));
|
|
1492
|
+
const blinding = bytesToBigintLE(plaintext.slice(48, 80));
|
|
1493
|
+
const assetId = BigInt(view.getUint32(80, true));
|
|
1494
|
+
const counterpartyPk = bytesToBigintLE(plaintext.slice(84, 116));
|
|
1495
|
+
return { value, ownerPk, blinding, assetId, counterpartyPk };
|
|
1496
|
+
} catch {
|
|
1497
|
+
return null;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1262
1500
|
var EncryptedMemo = {
|
|
1263
1501
|
/**
|
|
1264
|
-
* Build and encrypt a memo for a note.
|
|
1502
|
+
* Build and encrypt a memo for a note using ECDH (v2, 168 bytes).
|
|
1265
1503
|
*
|
|
1266
|
-
* @param value
|
|
1267
|
-
* @param ownerPk
|
|
1268
|
-
* @param blinding
|
|
1269
|
-
* @param assetId
|
|
1270
|
-
* @param commitment
|
|
1271
|
-
* @param
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
|
|
1275
|
-
|
|
1504
|
+
* @param value Note value in planck.
|
|
1505
|
+
* @param ownerPk 32-byte owner public key (LE).
|
|
1506
|
+
* @param blinding 32-byte blinding scalar (LE).
|
|
1507
|
+
* @param assetId Asset identifier.
|
|
1508
|
+
* @param commitment 32-byte commitment bytes (LE).
|
|
1509
|
+
* @param recipientIvkPacked 32-byte LE-encoded packed BJJ viewing public key
|
|
1510
|
+
* (from PrivacyKeyManager.getViewingPublicKeyPacked() or
|
|
1511
|
+
* decoded from a privacy address).
|
|
1512
|
+
* Pass `new Uint8Array(32)` (all zeros) for a publicly-readable memo.
|
|
1513
|
+
* @param counterpartyPk 32-byte counterparty BJJ Ax. Default: all zeros.
|
|
1514
|
+
* @returns 168-byte encrypted memo: nonce(12) || ciphertext+MAC(124) || ephPk(32).
|
|
1515
|
+
*/
|
|
1516
|
+
encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), ephSkOverride) {
|
|
1276
1517
|
const nonce = (0, import_utils.randomBytes)(NONCE_SIZE);
|
|
1277
|
-
const
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1518
|
+
const plaintext = serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk);
|
|
1519
|
+
const isZeroKey = recipientIvkPacked.every((b) => b === 0);
|
|
1520
|
+
let sharedSecret;
|
|
1521
|
+
let ephPkPackedBytes;
|
|
1522
|
+
if (isZeroKey) {
|
|
1523
|
+
sharedSecret = new Uint8Array(32);
|
|
1524
|
+
ephPkPackedBytes = new Uint8Array(EPH_PK_SIZE);
|
|
1525
|
+
} else {
|
|
1526
|
+
const ephSkBytes = ephSkOverride ?? (0, import_utils.randomBytes)(32);
|
|
1527
|
+
if (ephSkBytes.length !== 32)
|
|
1528
|
+
throw new Error("EncryptedMemo.encrypt: ephSkOverride must be 32 bytes");
|
|
1529
|
+
const ephSkScalar = bytesToBjjScalar(ephSkBytes);
|
|
1530
|
+
const ephPkPoint = (0, import_baby_jubjub.mulPointEscalar)(import_baby_jubjub.Base8, ephSkScalar);
|
|
1531
|
+
ephPkPackedBytes = bigintTo32Le((0, import_baby_jubjub.packPoint)(ephPkPoint));
|
|
1532
|
+
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
1533
|
+
const ivkPoint = (0, import_baby_jubjub.unpackPoint)(ivkPackedBigint);
|
|
1534
|
+
if (!ivkPoint)
|
|
1535
|
+
throw new Error("EncryptedMemo.encrypt: invalid recipient viewing public key");
|
|
1536
|
+
const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ivkPoint, ephSkScalar);
|
|
1537
|
+
sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1538
|
+
}
|
|
1539
|
+
const encKey = deriveEncryptionKey(sharedSecret, commitment);
|
|
1540
|
+
const cipher = (0, import_chacha.chacha20poly1305)(encKey, nonce);
|
|
1280
1541
|
const ciphertext = cipher.encrypt(plaintext);
|
|
1281
|
-
const result = new Uint8Array(
|
|
1542
|
+
const result = new Uint8Array(ENCRYPTED_MEMO_SIZE);
|
|
1282
1543
|
result.set(nonce, 0);
|
|
1283
1544
|
result.set(ciphertext, NONCE_SIZE);
|
|
1545
|
+
result.set(ephPkPackedBytes, NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1284
1546
|
return result;
|
|
1285
1547
|
},
|
|
1286
1548
|
/**
|
|
1287
|
-
* Returns a
|
|
1288
|
-
*
|
|
1549
|
+
* Returns a 168-byte public memo encrypted with a zero viewing key.
|
|
1550
|
+
* Decryptable by anyone with `decrypt(memo, commitment, new Uint8Array(32))`.
|
|
1551
|
+
* Convenience alias for `encrypt(..., new Uint8Array(32))`.
|
|
1289
1552
|
*/
|
|
1290
1553
|
encryptPublic(value, ownerPk, blinding, assetId, commitment) {
|
|
1291
1554
|
return EncryptedMemo.encrypt(
|
|
@@ -1298,226 +1561,89 @@ var EncryptedMemo = {
|
|
|
1298
1561
|
);
|
|
1299
1562
|
},
|
|
1300
1563
|
/**
|
|
1301
|
-
* Returns a
|
|
1564
|
+
* Returns a 168-byte zeroed dummy memo (no information, always valid on-chain).
|
|
1302
1565
|
*/
|
|
1303
1566
|
dummy() {
|
|
1304
1567
|
return new Uint8Array(ENCRYPTED_MEMO_SIZE);
|
|
1305
1568
|
},
|
|
1306
1569
|
/**
|
|
1307
|
-
*
|
|
1570
|
+
* Validates that `bytes` is a properly-sized encrypted memo.
|
|
1571
|
+
* Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (168 bytes).
|
|
1308
1572
|
*
|
|
1309
|
-
*
|
|
1310
|
-
*
|
|
1573
|
+
* Call this at system boundaries (extrinsic builders, precompile encoders)
|
|
1574
|
+
* to catch malformed memos before they reach the chain and fail on-chain.
|
|
1311
1575
|
*
|
|
1312
|
-
* @param
|
|
1313
|
-
* @param
|
|
1314
|
-
* @param recipientVk 32-byte recipient viewing key.
|
|
1576
|
+
* @param bytes The memo bytes to validate.
|
|
1577
|
+
* @param context Optional context string included in the error (e.g. 'shield', 'output[0]').
|
|
1315
1578
|
*/
|
|
1316
|
-
|
|
1317
|
-
if (
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
const cipher = (0, import_chacha.chacha20poly1305)(key, nonce);
|
|
1323
|
-
const plaintext = cipher.decrypt(ciphertext);
|
|
1324
|
-
const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);
|
|
1325
|
-
const value = view.getBigUint64(0, true);
|
|
1326
|
-
const ownerPk = bytesToBigintLE(plaintext.slice(8, 40));
|
|
1327
|
-
const blinding = bytesToBigintLE(plaintext.slice(40, 72));
|
|
1328
|
-
const assetId = BigInt(view.getUint32(72, true));
|
|
1329
|
-
return { value, ownerPk, blinding, assetId };
|
|
1330
|
-
} catch {
|
|
1331
|
-
return null;
|
|
1579
|
+
validate(bytes, context) {
|
|
1580
|
+
if (bytes.length !== ENCRYPTED_MEMO_SIZE) {
|
|
1581
|
+
const ctx = context ? ` (${context})` : "";
|
|
1582
|
+
throw new Error(
|
|
1583
|
+
`EncryptedMemo: invalid size${ctx} \u2014 expected ${ENCRYPTED_MEMO_SIZE} bytes, got ${bytes.length}`
|
|
1584
|
+
);
|
|
1332
1585
|
}
|
|
1333
|
-
}
|
|
1334
|
-
};
|
|
1335
|
-
|
|
1336
|
-
// src/shielded-pool/NoteBuilder.ts
|
|
1337
|
-
var import_poseidon_lite = require("poseidon-lite");
|
|
1338
|
-
var NoteBuilder = class {
|
|
1586
|
+
},
|
|
1339
1587
|
/**
|
|
1340
|
-
*
|
|
1588
|
+
* Decrypt an on-chain EncryptedMemo using the recipient's viewing secret key.
|
|
1589
|
+
* Returns null if decryption fails — wrong key, bad MAC, or malformed memo.
|
|
1590
|
+
* Never throws; safe for scan loops.
|
|
1341
1591
|
*
|
|
1342
|
-
* @param
|
|
1343
|
-
* @param
|
|
1344
|
-
* @param
|
|
1345
|
-
* @param input.blinding Random scalar — defaults to BigInt(Date.now()).
|
|
1346
|
-
* @param input.spendingKey Secret key for nullifier — default 0n.
|
|
1592
|
+
* @param memoBytes 168-byte encrypted memo.
|
|
1593
|
+
* @param commitment 32-byte note commitment (LE).
|
|
1594
|
+
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1347
1595
|
*/
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const blinding = input.blinding ?? BigInt(Date.now());
|
|
1353
|
-
const spendingKey = input.spendingKey ?? 0n;
|
|
1354
|
-
const commitment = (0, import_poseidon_lite.poseidon4)([value, assetId, ownerPk, blinding]);
|
|
1355
|
-
const nullifier = (0, import_poseidon_lite.poseidon2)([commitment, spendingKey]);
|
|
1356
|
-
const commitmentBytes = bigintTo32Le(commitment);
|
|
1357
|
-
const nullifierBytes = bigintTo32Le(nullifier);
|
|
1358
|
-
const memo = input.viewingKey !== void 0 ? Array.from(
|
|
1359
|
-
EncryptedMemo.encrypt(
|
|
1360
|
-
value,
|
|
1361
|
-
bigintTo32Le(ownerPk),
|
|
1362
|
-
bigintTo32Le(blinding),
|
|
1363
|
-
Number(assetId),
|
|
1364
|
-
commitmentBytes,
|
|
1365
|
-
input.viewingKey
|
|
1366
|
-
)
|
|
1367
|
-
) : Array.from(EncryptedMemo.dummy());
|
|
1368
|
-
const note = {
|
|
1369
|
-
value,
|
|
1370
|
-
assetId,
|
|
1371
|
-
ownerPk,
|
|
1372
|
-
blinding,
|
|
1373
|
-
spendingKey,
|
|
1374
|
-
spent: false,
|
|
1375
|
-
spentAt: null,
|
|
1376
|
-
commitment,
|
|
1377
|
-
nullifier,
|
|
1378
|
-
commitmentHex: toHex(commitmentBytes),
|
|
1379
|
-
nullifierHex: toHex(nullifierBytes),
|
|
1380
|
-
memo
|
|
1381
|
-
};
|
|
1382
|
-
return note;
|
|
1383
|
-
}
|
|
1596
|
+
decrypt(memoBytes, commitment, viewingSecretKey) {
|
|
1597
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
|
|
1598
|
+
return EncryptedMemo._decrypt(memoBytes, commitment, viewingSecretKey);
|
|
1599
|
+
},
|
|
1384
1600
|
/**
|
|
1385
|
-
*
|
|
1601
|
+
* Extract the ECDH shared secret from an encrypted memo using the recipient's viewing secret key.
|
|
1386
1602
|
*
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
* @param note The ZkNote whose fields populate the plaintext.
|
|
1391
|
-
* @param recipientVk 32-byte recipient viewing key.
|
|
1392
|
-
* Pass `new Uint8Array(32)` (default) for a public/dummy memo.
|
|
1393
|
-
*/
|
|
1394
|
-
static buildMemo(note, recipientVk) {
|
|
1395
|
-
return EncryptedMemo.encrypt(
|
|
1396
|
-
note.value,
|
|
1397
|
-
bigintTo32Le(note.ownerPk),
|
|
1398
|
-
bigintTo32Le(note.blinding),
|
|
1399
|
-
Number(note.assetId),
|
|
1400
|
-
bigintTo32Le(note.commitment),
|
|
1401
|
-
recipientVk ?? new Uint8Array(32)
|
|
1402
|
-
);
|
|
1403
|
-
}
|
|
1404
|
-
};
|
|
1405
|
-
|
|
1406
|
-
// src/shielded-pool/ShieldedPoolModule.ts
|
|
1407
|
-
var ShieldedPoolModule = class {
|
|
1408
|
-
constructor(substrate) {
|
|
1409
|
-
this.substrate = substrate;
|
|
1410
|
-
}
|
|
1411
|
-
// ─── Extrinsics ────────────────────────────────────────────────────────────
|
|
1412
|
-
/**
|
|
1413
|
-
* Deposits tokens into the shielded pool.
|
|
1414
|
-
* Extrinsic: shieldedPool.shield(assetId, amount, commitment, encryptedMemo)
|
|
1415
|
-
*/
|
|
1416
|
-
async shield(params, signer) {
|
|
1417
|
-
const memo = params.encryptedMemo ?? EncryptedMemo.dummy();
|
|
1418
|
-
const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "shield");
|
|
1419
|
-
const tx = callUnsafeTx(
|
|
1420
|
-
entry,
|
|
1421
|
-
params.assetId,
|
|
1422
|
-
params.amount.toString(),
|
|
1423
|
-
import_polkadot_api2.Binary.fromHex(params.commitment),
|
|
1424
|
-
import_polkadot_api2.Binary.fromBytes(memo)
|
|
1425
|
-
);
|
|
1426
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1427
|
-
}
|
|
1428
|
-
/**
|
|
1429
|
-
* Build a ZkNote locally and submit shieldedPool.shield in one call.
|
|
1603
|
+
* Used by NoteDecryptor to obtain the shared secret needed for stealth address derivation
|
|
1604
|
+
* without re-running the full decrypt path. Safe to call on any 168-byte memo.
|
|
1430
1605
|
*
|
|
1431
|
-
* Returns
|
|
1432
|
-
*
|
|
1606
|
+
* Returns `new Uint8Array(32)` (all zeros) for public/dummy memos (zero ephPk).
|
|
1607
|
+
* Returns `null` if the memo is malformed or the ephPk is not a valid BJJ point.
|
|
1608
|
+
* Never throws; safe for scan loops.
|
|
1433
1609
|
*
|
|
1434
|
-
* @param
|
|
1435
|
-
* @param
|
|
1436
|
-
* @param params.ownerPk BabyJubJub Ax (default 0n).
|
|
1437
|
-
* @param params.blinding Random blinding scalar (default BigInt(Date.now())).
|
|
1438
|
-
* @param params.spendingKey Secret spending key (default 0n).
|
|
1439
|
-
*/
|
|
1440
|
-
async buildAndShield(params, signer) {
|
|
1441
|
-
const noteInput = {
|
|
1442
|
-
value: params.value,
|
|
1443
|
-
...params.assetId !== void 0 && { assetId: BigInt(params.assetId) },
|
|
1444
|
-
...params.ownerPk !== void 0 && { ownerPk: params.ownerPk },
|
|
1445
|
-
...params.blinding !== void 0 && { blinding: params.blinding },
|
|
1446
|
-
...params.spendingKey !== void 0 && { spendingKey: params.spendingKey }
|
|
1447
|
-
};
|
|
1448
|
-
const note = await NoteBuilder.build(noteInput);
|
|
1449
|
-
const memo = NoteBuilder.buildMemo(note);
|
|
1450
|
-
const txResult = await this.shield(
|
|
1451
|
-
{
|
|
1452
|
-
assetId: Number(note.assetId),
|
|
1453
|
-
amount: note.value,
|
|
1454
|
-
commitment: note.commitmentHex,
|
|
1455
|
-
encryptedMemo: memo
|
|
1456
|
-
},
|
|
1457
|
-
signer
|
|
1458
|
-
);
|
|
1459
|
-
return { txResult, note };
|
|
1460
|
-
}
|
|
1461
|
-
/**
|
|
1462
|
-
* Withdraws tokens from the shielded pool to a public address.
|
|
1463
|
-
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient)
|
|
1464
|
-
*/
|
|
1465
|
-
async unshield(params, signer) {
|
|
1466
|
-
const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "unshield");
|
|
1467
|
-
const tx = callUnsafeTx(
|
|
1468
|
-
entry,
|
|
1469
|
-
import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1470
|
-
import_polkadot_api2.Binary.fromHex(params.merkleRoot),
|
|
1471
|
-
import_polkadot_api2.Binary.fromHex(params.nullifier),
|
|
1472
|
-
params.assetId,
|
|
1473
|
-
params.amount.toString(),
|
|
1474
|
-
import_polkadot_api2.Binary.fromHex(params.recipientAddress)
|
|
1475
|
-
);
|
|
1476
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1477
|
-
}
|
|
1478
|
-
/**
|
|
1479
|
-
* Performs a private (shielded) transfer between two notes.
|
|
1480
|
-
* Extrinsic: shieldedPool.privateTransfer(inputs, outputs, proof, merkleRoot)
|
|
1610
|
+
* @param memoBytes 168-byte encrypted memo.
|
|
1611
|
+
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1481
1612
|
*/
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
const
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
);
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
const tx = callUnsafeTx(entry, operations);
|
|
1514
|
-
return toTxResult(await tx.signAndSubmit(signer));
|
|
1613
|
+
extractSharedSecret(memoBytes, viewingSecretKey) {
|
|
1614
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
|
|
1615
|
+
const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1616
|
+
const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
|
|
1617
|
+
if (ephPkPackedBigint === 0n) {
|
|
1618
|
+
return new Uint8Array(32);
|
|
1619
|
+
}
|
|
1620
|
+
const ephPkPoint = (0, import_baby_jubjub.unpackPoint)(ephPkPackedBigint);
|
|
1621
|
+
if (!ephPkPoint) return null;
|
|
1622
|
+
const ivskScalar = bytesToBjjScalar(viewingSecretKey);
|
|
1623
|
+
const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ephPkPoint, ivskScalar);
|
|
1624
|
+
return bigintTo32Le(sharedPoint[0]);
|
|
1625
|
+
},
|
|
1626
|
+
/** @internal */
|
|
1627
|
+
_decrypt(memoBytes, commitment, viewingSecretKey) {
|
|
1628
|
+
const nonce = memoBytes.slice(0, NONCE_SIZE);
|
|
1629
|
+
const ciphertextWithMac = memoBytes.slice(NONCE_SIZE, NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1630
|
+
const ephPkPackedBytes = memoBytes.slice(NONCE_SIZE + CIPHERTEXT_SIZE);
|
|
1631
|
+
const ephPkPackedBigint = bytesToBigintLE(ephPkPackedBytes);
|
|
1632
|
+
let sharedSecret;
|
|
1633
|
+
if (ephPkPackedBigint === 0n) {
|
|
1634
|
+
sharedSecret = new Uint8Array(32);
|
|
1635
|
+
} else {
|
|
1636
|
+
const ephPkPoint = (0, import_baby_jubjub.unpackPoint)(ephPkPackedBigint);
|
|
1637
|
+
if (!ephPkPoint) return null;
|
|
1638
|
+
const ivskScalar = bytesToBjjScalar(viewingSecretKey);
|
|
1639
|
+
const sharedPoint = (0, import_baby_jubjub.mulPointEscalar)(ephPkPoint, ivskScalar);
|
|
1640
|
+
sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1641
|
+
}
|
|
1642
|
+
const encKey = deriveEncryptionKey(sharedSecret, commitment);
|
|
1643
|
+
return parsePlaintext(nonce, ciphertextWithMac, encKey);
|
|
1515
1644
|
}
|
|
1516
1645
|
};
|
|
1517
1646
|
|
|
1518
|
-
// src/account-mapping/AccountMappingModule.ts
|
|
1519
|
-
var import_polkadot_api3 = require("polkadot-api");
|
|
1520
|
-
|
|
1521
1647
|
// src/utils/address.ts
|
|
1522
1648
|
var import_util_crypto = require("@polkadot/util-crypto");
|
|
1523
1649
|
function normalizeEvmAddress(addr) {
|
|
@@ -1654,6 +1780,242 @@ function addressToAccountIdHex(addr) {
|
|
|
1654
1780
|
return substrateSs58ToAccountIdHex(addr);
|
|
1655
1781
|
}
|
|
1656
1782
|
|
|
1783
|
+
// src/shielded-pool/pallet/ShieldedPoolModule.ts
|
|
1784
|
+
var ShieldedPoolModule = class {
|
|
1785
|
+
constructor(substrate) {
|
|
1786
|
+
this.substrate = substrate;
|
|
1787
|
+
}
|
|
1788
|
+
substrate;
|
|
1789
|
+
// ─── Extrinsics ────────────────────────────────────────────────────────────
|
|
1790
|
+
/**
|
|
1791
|
+
* Deposits tokens into the shielded pool.
|
|
1792
|
+
* Extrinsic: shieldedPool.shield(assetId, amount, commitment, encryptedMemo)
|
|
1793
|
+
*
|
|
1794
|
+
* Shield is always a signed (public) transaction — the caller's address
|
|
1795
|
+
* appears on-chain as the depositor.
|
|
1796
|
+
*/
|
|
1797
|
+
async shield(params, signer, txOptions) {
|
|
1798
|
+
EncryptedMemo.validate(params.encryptedMemo, "shield.encryptedMemo");
|
|
1799
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "shield");
|
|
1800
|
+
const tx = callUnsafeTx(entry, {
|
|
1801
|
+
asset_id: params.assetId,
|
|
1802
|
+
amount: params.amount,
|
|
1803
|
+
commitment: import_polkadot_api2.Binary.fromHex(params.commitment),
|
|
1804
|
+
encrypted_memo: import_polkadot_api2.Binary.fromBytes(params.encryptedMemo)
|
|
1805
|
+
});
|
|
1806
|
+
return toTxResult(
|
|
1807
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Withdraws tokens from the shielded pool to a public address.
|
|
1812
|
+
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
1813
|
+
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
1814
|
+
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee)
|
|
1815
|
+
*/
|
|
1816
|
+
async unshield(params, signer, txOptions) {
|
|
1817
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "unshield");
|
|
1818
|
+
const recipientSs58 = accountIdHexToSs58(params.recipientAddress);
|
|
1819
|
+
if (!recipientSs58) throw new Error(`Invalid recipientAddress: ${params.recipientAddress}`);
|
|
1820
|
+
const changeCommitment = params.changeCommitment ?? "0x" + "00".repeat(32);
|
|
1821
|
+
let changeEncryptedMemo;
|
|
1822
|
+
if (params.changeEncryptedMemo && params.changeEncryptedMemo.length > 0) {
|
|
1823
|
+
EncryptedMemo.validate(params.changeEncryptedMemo, "changeEncryptedMemo");
|
|
1824
|
+
changeEncryptedMemo = import_polkadot_api2.Binary.fromBytes(params.changeEncryptedMemo);
|
|
1825
|
+
} else {
|
|
1826
|
+
changeEncryptedMemo = import_polkadot_api2.Binary.fromBytes(new Uint8Array(0));
|
|
1827
|
+
}
|
|
1828
|
+
const tx = callUnsafeTx(entry, {
|
|
1829
|
+
proof: import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1830
|
+
merkle_root: import_polkadot_api2.Binary.fromHex(params.merkleRoot),
|
|
1831
|
+
nullifier: import_polkadot_api2.Binary.fromHex(params.nullifier),
|
|
1832
|
+
asset_id: params.assetId,
|
|
1833
|
+
amount: params.amount,
|
|
1834
|
+
recipient: recipientSs58,
|
|
1835
|
+
fee: params.fee ?? 0n,
|
|
1836
|
+
change_commitment: import_polkadot_api2.Binary.fromHex(changeCommitment),
|
|
1837
|
+
change_encrypted_memo: changeEncryptedMemo,
|
|
1838
|
+
relayer: void 0
|
|
1839
|
+
// Option<H160> — None for direct Substrate submissions
|
|
1840
|
+
});
|
|
1841
|
+
if (signer) {
|
|
1842
|
+
return toTxResult(
|
|
1843
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
return submitBareTx(tx, this.substrate);
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Performs a private (shielded) transfer between two notes.
|
|
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.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee)
|
|
1853
|
+
*/
|
|
1854
|
+
async privateTransfer(params, signer, txOptions) {
|
|
1855
|
+
const nullifiers = params.inputs.map((inp) => import_polkadot_api2.Binary.fromHex(inp.nullifier));
|
|
1856
|
+
const commitments = params.outputs.map((out) => import_polkadot_api2.Binary.fromHex(out.commitment));
|
|
1857
|
+
const memos = params.outputs.map((out, i) => {
|
|
1858
|
+
EncryptedMemo.validate(
|
|
1859
|
+
out.encryptedMemo,
|
|
1860
|
+
`privateTransfer.outputs[${i}].encryptedMemo`
|
|
1861
|
+
);
|
|
1862
|
+
return import_polkadot_api2.Binary.fromBytes(out.encryptedMemo);
|
|
1863
|
+
});
|
|
1864
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "private_transfer");
|
|
1865
|
+
const tx = callUnsafeTx(entry, {
|
|
1866
|
+
proof: import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1867
|
+
merkle_root: import_polkadot_api2.Binary.fromHex(params.merkleRoot),
|
|
1868
|
+
nullifiers,
|
|
1869
|
+
commitments,
|
|
1870
|
+
encrypted_memos: memos,
|
|
1871
|
+
asset_id: params.assetId,
|
|
1872
|
+
fee: params.fee ?? 0n,
|
|
1873
|
+
relayer: void 0
|
|
1874
|
+
// Option<H160> — None for direct Substrate submissions
|
|
1875
|
+
});
|
|
1876
|
+
if (signer) {
|
|
1877
|
+
return toTxResult(
|
|
1878
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
return submitBareTx(tx, this.substrate);
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Deposits multiple notes into the shielded pool in a single extrinsic.
|
|
1885
|
+
* Extrinsic: shieldedPool.shieldBatch(operations) — max 20 items.
|
|
1886
|
+
*/
|
|
1887
|
+
async shieldBatch(params, signer, txOptions) {
|
|
1888
|
+
const operations = params.items.map((item, i) => {
|
|
1889
|
+
EncryptedMemo.validate(item.encryptedMemo, `shieldBatch.items[${i}].encryptedMemo`);
|
|
1890
|
+
return {
|
|
1891
|
+
assetId: item.assetId,
|
|
1892
|
+
amount: item.amount.toString(),
|
|
1893
|
+
commitment: import_polkadot_api2.Binary.fromHex(item.commitment),
|
|
1894
|
+
encryptedMemo: import_polkadot_api2.Binary.fromBytes(item.encryptedMemo)
|
|
1895
|
+
};
|
|
1896
|
+
});
|
|
1897
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "shield_batch");
|
|
1898
|
+
const tx = callUnsafeTx(entry, operations);
|
|
1899
|
+
return toTxResult(
|
|
1900
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Claims accrued relay fees into the shielded pool.
|
|
1905
|
+
* This is a SIGNED transaction — the relayer must sign it with their wallet.
|
|
1906
|
+
* Before calling this, generate a ZK disclosure proof with generateFeeClaimProof().
|
|
1907
|
+
*
|
|
1908
|
+
* Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals)
|
|
1909
|
+
*/
|
|
1910
|
+
async claimShieldedFees(params, signer, txOptions) {
|
|
1911
|
+
EncryptedMemo.validate(params.encryptedMemo, "claimShieldedFees.encryptedMemo");
|
|
1912
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "claim_shielded_fees");
|
|
1913
|
+
const tx = callUnsafeTx(entry, {
|
|
1914
|
+
commitment: import_polkadot_api2.Binary.fromHex(params.commitment),
|
|
1915
|
+
amount: params.amount,
|
|
1916
|
+
asset_id: params.assetId,
|
|
1917
|
+
encrypted_memo: import_polkadot_api2.Binary.fromBytes(params.encryptedMemo),
|
|
1918
|
+
proof: import_polkadot_api2.Binary.fromBytes(params.proof),
|
|
1919
|
+
public_signals: import_polkadot_api2.Binary.fromBytes(params.publicSignals)
|
|
1920
|
+
});
|
|
1921
|
+
return toTxResult(
|
|
1922
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
// ─── Selective Disclosure ──────────────────────────────────────────────────
|
|
1926
|
+
/**
|
|
1927
|
+
* Requests a selective disclosure from a target account for a specific commitment.
|
|
1928
|
+
* The auditor's Baby Jubjub public key is included so the note owner knows
|
|
1929
|
+
* which key to encrypt to when generating the proof.
|
|
1930
|
+
* Extrinsic: shieldedPool.request_disclosure(target, commitment, required_fields,
|
|
1931
|
+
* reason, auditor_bjj_pk_x, auditor_bjj_pk_y)
|
|
1932
|
+
*/
|
|
1933
|
+
async requestDisclosure(params, signer, txOptions) {
|
|
1934
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "request_disclosure");
|
|
1935
|
+
const tx = callUnsafeTx(entry, {
|
|
1936
|
+
target: params.target,
|
|
1937
|
+
commitment: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.commitment)),
|
|
1938
|
+
required_fields: {
|
|
1939
|
+
value: params.requiredFields.value,
|
|
1940
|
+
asset_id: params.requiredFields.assetId,
|
|
1941
|
+
owner: params.requiredFields.owner
|
|
1942
|
+
},
|
|
1943
|
+
reason: import_polkadot_api2.Binary.fromText(params.reason),
|
|
1944
|
+
auditor_bjj_pk_x: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.auditorBjjPkX)),
|
|
1945
|
+
auditor_bjj_pk_y: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.auditorBjjPkY))
|
|
1946
|
+
});
|
|
1947
|
+
return toTxResult(
|
|
1948
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Submits a Groth16 ZK disclosure proof for a note commitment.
|
|
1953
|
+
* The proof reveals the selected fields (value, asset_id, owner_hash) on-chain.
|
|
1954
|
+
* Use generateDisclosureProof() + buildDisclosurePublicSignals() before calling this.
|
|
1955
|
+
* Extrinsic: shieldedPool.disclose(commitment, proof_bytes, public_signals, auditor)
|
|
1956
|
+
*/
|
|
1957
|
+
async disclose(params, signer, txOptions) {
|
|
1958
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "disclose");
|
|
1959
|
+
const tx = callUnsafeTx(entry, {
|
|
1960
|
+
commitment: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.commitment)),
|
|
1961
|
+
proof_bytes: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.proofBytes)),
|
|
1962
|
+
public_signals: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.publicSignals)),
|
|
1963
|
+
auditor: params.auditor
|
|
1964
|
+
});
|
|
1965
|
+
return toTxResult(
|
|
1966
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Rejects a pending disclosure request from an auditor for a specific commitment.
|
|
1971
|
+
* Extrinsic: shieldedPool.reject_disclosure(auditor, commitment, reason)
|
|
1972
|
+
*/
|
|
1973
|
+
async rejectDisclosure(params, signer, txOptions) {
|
|
1974
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "reject_disclosure");
|
|
1975
|
+
const tx = callUnsafeTx(entry, {
|
|
1976
|
+
auditor: params.auditor,
|
|
1977
|
+
commitment: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.commitment)),
|
|
1978
|
+
reason: import_polkadot_api2.Binary.fromText(params.reason)
|
|
1979
|
+
});
|
|
1980
|
+
return toTxResult(
|
|
1981
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Cleans up a disclosure request that has passed its expiration block.
|
|
1986
|
+
* Permissionless — any account can prune expired requests.
|
|
1987
|
+
* Extrinsic: shieldedPool.prune_expired_request(target, auditor, commitment)
|
|
1988
|
+
*/
|
|
1989
|
+
async pruneExpiredRequest(params, signer, txOptions) {
|
|
1990
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "prune_expired_request");
|
|
1991
|
+
const tx = callUnsafeTx(entry, {
|
|
1992
|
+
target: params.target,
|
|
1993
|
+
auditor: params.auditor,
|
|
1994
|
+
commitment: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.commitment))
|
|
1995
|
+
});
|
|
1996
|
+
return toTxResult(
|
|
1997
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Revokes a previously submitted voluntary disclosure record.
|
|
2002
|
+
* Only applies to self-disclosures (auditor = None). Auditor-requested records are permanent.
|
|
2003
|
+
* Extrinsic: shieldedPool.revoke_disclosure_record(commitment)
|
|
2004
|
+
*/
|
|
2005
|
+
async revokeDisclosureRecord(params, signer, txOptions) {
|
|
2006
|
+
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "revoke_disclosure_record");
|
|
2007
|
+
const tx = callUnsafeTx(entry, {
|
|
2008
|
+
commitment: import_polkadot_api2.Binary.fromBytes(new Uint8Array(params.commitment))
|
|
2009
|
+
});
|
|
2010
|
+
return toTxResult(
|
|
2011
|
+
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
|
|
2016
|
+
// src/account-mapping/AccountMappingModule.ts
|
|
2017
|
+
var import_polkadot_api3 = require("polkadot-api");
|
|
2018
|
+
|
|
1657
2019
|
// src/account-mapping/helpers.ts
|
|
1658
2020
|
function mapRawScheme(raw) {
|
|
1659
2021
|
if (raw === "Eip191" || raw === "eip191") return "Eip191";
|
|
@@ -1666,6 +2028,7 @@ var AccountMappingModule = class {
|
|
|
1666
2028
|
constructor(substrate) {
|
|
1667
2029
|
this.substrate = substrate;
|
|
1668
2030
|
}
|
|
2031
|
+
substrate;
|
|
1669
2032
|
// ─── Address resolution ─────────────────────────────────────────────────────
|
|
1670
2033
|
/**
|
|
1671
2034
|
* Returns the explicitly mapped (or fallback) Substrate AccountId32 hex for
|
|
@@ -2051,6 +2414,7 @@ var PrivacyModule = class {
|
|
|
2051
2414
|
constructor(substrate) {
|
|
2052
2415
|
this.substrate = substrate;
|
|
2053
2416
|
}
|
|
2417
|
+
substrate;
|
|
2054
2418
|
/** Returns the current Merkle tree root. */
|
|
2055
2419
|
async getMerkleRoot() {
|
|
2056
2420
|
return this.substrate.request("privacy_getMerkleRoot", []);
|
|
@@ -2068,14 +2432,23 @@ var PrivacyModule = class {
|
|
|
2068
2432
|
}
|
|
2069
2433
|
/**
|
|
2070
2434
|
* Returns the Merkle inclusion proof for a given commitment hex,
|
|
2071
|
-
* bundled with the
|
|
2435
|
+
* bundled with the Merkle root.
|
|
2436
|
+
*
|
|
2437
|
+
* Uses `privacy_getMerkleProofByCommitment` which resolves root and proof
|
|
2438
|
+
* under the **same block hash**, guaranteeing that the returned path is
|
|
2439
|
+
* consistent with the returned root.
|
|
2072
2440
|
*/
|
|
2073
2441
|
async getMerkleProofByCommitment(commitmentHex) {
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
return {
|
|
2442
|
+
const raw = await this.substrate.request(
|
|
2443
|
+
"privacy_getMerkleProofByCommitment",
|
|
2444
|
+
[commitmentHex]
|
|
2445
|
+
);
|
|
2446
|
+
return {
|
|
2447
|
+
path: raw.path,
|
|
2448
|
+
leafIndex: raw.leaf_index,
|
|
2449
|
+
treeDepth: raw.tree_depth,
|
|
2450
|
+
root: raw.root
|
|
2451
|
+
};
|
|
2079
2452
|
}
|
|
2080
2453
|
/** Returns the spend status of a nullifier. */
|
|
2081
2454
|
async getNullifierStatus(nullifier) {
|
|
@@ -2094,6 +2467,7 @@ var PrivacyModule = class {
|
|
|
2094
2467
|
return {
|
|
2095
2468
|
merkleRoot: raw.merkle_root,
|
|
2096
2469
|
commitmentCount: raw.commitment_count,
|
|
2470
|
+
nullifierCount: raw.nullifier_count,
|
|
2097
2471
|
totalBalance: raw.total_balance.toString(),
|
|
2098
2472
|
assetBalances: raw.asset_balances.map(mapAssetBalance),
|
|
2099
2473
|
treeDepth: raw.tree_depth
|
|
@@ -2119,6 +2493,7 @@ var ZkVerifierModule = class {
|
|
|
2119
2493
|
constructor(substrate) {
|
|
2120
2494
|
this.substrate = substrate;
|
|
2121
2495
|
}
|
|
2496
|
+
substrate;
|
|
2122
2497
|
/** Returns basic version info for all registered circuits. */
|
|
2123
2498
|
async getAllCircuitVersions() {
|
|
2124
2499
|
const raw = await this.substrate.request(
|
|
@@ -2137,6 +2512,48 @@ var ZkVerifierModule = class {
|
|
|
2137
2512
|
}
|
|
2138
2513
|
};
|
|
2139
2514
|
|
|
2515
|
+
// src/relayer/RelayerStatusModule.ts
|
|
2516
|
+
var RelayerStatusModule = class {
|
|
2517
|
+
constructor(substrate) {
|
|
2518
|
+
this.substrate = substrate;
|
|
2519
|
+
}
|
|
2520
|
+
substrate;
|
|
2521
|
+
/**
|
|
2522
|
+
* Returns true if the given SS58 address is a registered relayer.
|
|
2523
|
+
*/
|
|
2524
|
+
async isRelayer(ss58Address) {
|
|
2525
|
+
return this.substrate.request("relayer_isRelayer", [ss58Address]);
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Returns the pending fees (in planck) for the given account and asset.
|
|
2529
|
+
* The node returns the value as a decimal string to avoid u128 overflow.
|
|
2530
|
+
*/
|
|
2531
|
+
async pendingFees(ss58Address, assetId) {
|
|
2532
|
+
const raw = await this.substrate.request("relayer_pendingFees", [
|
|
2533
|
+
ss58Address,
|
|
2534
|
+
assetId
|
|
2535
|
+
]);
|
|
2536
|
+
return BigInt(raw);
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Returns the registered EVM address (0x-prefixed) for the given account,
|
|
2540
|
+
* or null if the account is not a registered relayer.
|
|
2541
|
+
*/
|
|
2542
|
+
async registeredEvmAddress(ss58Address) {
|
|
2543
|
+
return this.substrate.request("relayer_registeredEvmAddress", [ss58Address]);
|
|
2544
|
+
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Convenience method: returns relayer registry info for an account.
|
|
2547
|
+
*/
|
|
2548
|
+
async getRelayerInfo(ss58Address) {
|
|
2549
|
+
const [isRelayer, evmAddress] = await Promise.all([
|
|
2550
|
+
this.isRelayer(ss58Address),
|
|
2551
|
+
this.registeredEvmAddress(ss58Address)
|
|
2552
|
+
]);
|
|
2553
|
+
return { isRelayer, evmAddress };
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
|
|
2140
2557
|
// src/precompiles/helpers.ts
|
|
2141
2558
|
var STATIC_TYPES = /* @__PURE__ */ new Set(["uint", "bytes32", "address", "bool"]);
|
|
2142
2559
|
function concat(arrays) {
|
|
@@ -2280,10 +2697,6 @@ function decodeBytes(data, slotOffset = 0) {
|
|
|
2280
2697
|
function decodeString(data, slotOffset = 0) {
|
|
2281
2698
|
return new TextDecoder().decode(decodeBytes(data, slotOffset));
|
|
2282
2699
|
}
|
|
2283
|
-
function hexToBytes(hex) {
|
|
2284
|
-
if (hex === "0x" || hex === "") return new Uint8Array(0);
|
|
2285
|
-
return fromHex(hex.startsWith("0x") ? hex : "0x" + hex);
|
|
2286
|
-
}
|
|
2287
2700
|
|
|
2288
2701
|
// src/precompiles/addresses.ts
|
|
2289
2702
|
var PRECOMPILE_ADDR = {
|
|
@@ -2342,12 +2755,20 @@ var AM_SEL = {
|
|
|
2342
2755
|
SET_ACCOUNT_METADATA: new Uint8Array([119, 108, 249, 255])
|
|
2343
2756
|
};
|
|
2344
2757
|
var SP_SEL = {
|
|
2345
|
-
// shield(uint32,
|
|
2346
|
-
SHIELD: new Uint8Array([
|
|
2347
|
-
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[])
|
|
2348
|
-
PRIVATE_TRANSFER: new Uint8Array([
|
|
2349
|
-
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32)
|
|
2350
|
-
UNSHIELD: new Uint8Array([
|
|
2758
|
+
// shield(uint32,bytes32,bytes) → 0x9feb22ea (payable, amount = msg.value)
|
|
2759
|
+
SHIELD: new Uint8Array([159, 235, 34, 234]),
|
|
2760
|
+
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256) → 0x8c0f5d24
|
|
2761
|
+
PRIVATE_TRANSFER: new Uint8Array([140, 15, 93, 36]),
|
|
2762
|
+
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32) → 0xd21d9a79
|
|
2763
|
+
UNSHIELD: new Uint8Array([210, 29, 154, 121]),
|
|
2764
|
+
// requestDisclosure(bytes32,bytes32,bool,bool,bool,bytes,bytes32,bytes32) → 0xe7022933 (caller = auditor)
|
|
2765
|
+
REQUEST_DISCLOSURE: new Uint8Array([231, 2, 41, 51]),
|
|
2766
|
+
// disclose(bytes32,bytes,bytes,bytes32) → 0xea8a4165 (caller = note owner)
|
|
2767
|
+
DISCLOSE: new Uint8Array([234, 138, 65, 101]),
|
|
2768
|
+
// rejectDisclosure(bytes32,bytes32,bytes) → 0x72b895a9 (caller = target)
|
|
2769
|
+
REJECT_DISCLOSURE: new Uint8Array([114, 184, 149, 169]),
|
|
2770
|
+
// pruneExpiredRequest(bytes32,bytes32,bytes32) → 0x0c338dcf (permissionless)
|
|
2771
|
+
PRUNE_EXPIRED_REQUEST: new Uint8Array([12, 51, 141, 207])
|
|
2351
2772
|
};
|
|
2352
2773
|
var KNOWN_PRECOMPILES = {
|
|
2353
2774
|
// ── Ethereum standard (EIP) ─────────────────────────────────────────────
|
|
@@ -2390,9 +2811,13 @@ var KNOWN_PRECOMPILES = {
|
|
|
2390
2811
|
"0x0000000000000000000000000000000000000801": {
|
|
2391
2812
|
name: "ShieldedPool",
|
|
2392
2813
|
functions: {
|
|
2393
|
-
"
|
|
2394
|
-
|
|
2395
|
-
|
|
2814
|
+
"9feb22ea": "shield(uint32,bytes32,bytes)",
|
|
2815
|
+
"8c0f5d24": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)",
|
|
2816
|
+
d21d9a79: "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32)",
|
|
2817
|
+
e7022933: "requestDisclosure(bytes32,bytes32,bool,bool,bool,bytes,bytes32,bytes32)",
|
|
2818
|
+
ea8a4165: "disclose(bytes32,bytes,bytes,bytes32)",
|
|
2819
|
+
"72b895a9": "rejectDisclosure(bytes32,bytes32,bytes)",
|
|
2820
|
+
"0c338dcf": "pruneExpiredRequest(bytes32,bytes32,bytes32)"
|
|
2396
2821
|
}
|
|
2397
2822
|
}
|
|
2398
2823
|
};
|
|
@@ -2406,43 +2831,58 @@ var ShieldedPoolPrecompile = class {
|
|
|
2406
2831
|
constructor(evm) {
|
|
2407
2832
|
this.evm = evm;
|
|
2408
2833
|
}
|
|
2834
|
+
evm;
|
|
2409
2835
|
addr = PRECOMPILE_ADDR.SHIELDED_POOL;
|
|
2410
2836
|
// ─── shield ────────────────────────────────────────────────────────────────
|
|
2411
2837
|
/**
|
|
2412
|
-
* Returns the ABI-encoded calldata for `shield(uint32,
|
|
2413
|
-
*
|
|
2838
|
+
* Returns the ABI-encoded calldata for `shield(uint32, bytes32, bytes)`.
|
|
2839
|
+
* The token amount must be sent as `msg.value` (the `value` field of the EVM
|
|
2840
|
+
* transaction) — this is what MetaMask and other wallets display to the user.
|
|
2414
2841
|
*/
|
|
2415
2842
|
buildShieldCalldata(params) {
|
|
2416
|
-
|
|
2843
|
+
EncryptedMemo.validate(params.encryptedMemo, "buildShieldCalldata.encryptedMemo");
|
|
2417
2844
|
const commitment = fromHex(params.commitment);
|
|
2418
2845
|
return encodeHex(
|
|
2419
2846
|
SP_SEL.SHIELD,
|
|
2420
2847
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2421
|
-
{ type: "uint", value: params.amount },
|
|
2422
2848
|
{ type: "bytes32", value: commitment },
|
|
2423
|
-
{ type: "bytes", value:
|
|
2849
|
+
{ type: "bytes", value: params.encryptedMemo }
|
|
2424
2850
|
);
|
|
2425
2851
|
}
|
|
2426
2852
|
/**
|
|
2427
|
-
* Deposits tokens into the shielded pool from
|
|
2853
|
+
* Deposits tokens into the shielded pool from a payable EVM transaction.
|
|
2428
2854
|
*
|
|
2429
|
-
* The
|
|
2430
|
-
*
|
|
2855
|
+
* The token amount is sent as `msg.value` so EVM wallets (MetaMask, etc.) display
|
|
2856
|
+
* the correct amount on the confirmation screen. The precompile dispatches
|
|
2857
|
+
* `shieldedPool.shield` with its own address as origin, so the funds flow:
|
|
2858
|
+
* caller → precompile (via msg.value, handled by EVM)
|
|
2859
|
+
* precompile → pool (via pallet transfer)
|
|
2860
|
+
* This avoids double-deduction while keeping the displayed amount accurate.
|
|
2431
2861
|
*
|
|
2432
2862
|
* Extrinsic: `shieldedPool.shield(assetId, amount, commitment, encryptedMemo)`
|
|
2433
2863
|
*/
|
|
2434
2864
|
async shield(params, signer) {
|
|
2435
|
-
return signer({
|
|
2865
|
+
return signer({
|
|
2866
|
+
to: this.addr,
|
|
2867
|
+
data: this.buildShieldCalldata(params),
|
|
2868
|
+
value: params.amount
|
|
2869
|
+
});
|
|
2436
2870
|
}
|
|
2437
2871
|
// ─── privateTransfer ───────────────────────────────────────────────────────
|
|
2438
2872
|
/**
|
|
2439
2873
|
* Returns the ABI-encoded calldata for
|
|
2440
|
-
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[])`.
|
|
2874
|
+
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256)`.
|
|
2441
2875
|
*/
|
|
2442
2876
|
buildPrivateTransferCalldata(params) {
|
|
2443
2877
|
const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
|
|
2444
2878
|
const commitments = params.outputs.map((o) => fromHex(o.commitment));
|
|
2445
|
-
const memos = params.outputs.map((o) =>
|
|
2879
|
+
const memos = params.outputs.map((o, i) => {
|
|
2880
|
+
EncryptedMemo.validate(
|
|
2881
|
+
o.encryptedMemo,
|
|
2882
|
+
`buildPrivateTransferCalldata.outputs[${i}].encryptedMemo`
|
|
2883
|
+
);
|
|
2884
|
+
return o.encryptedMemo;
|
|
2885
|
+
});
|
|
2446
2886
|
const root = fromHex(params.merkleRoot);
|
|
2447
2887
|
return encodeHex(
|
|
2448
2888
|
SP_SEL.PRIVATE_TRANSFER,
|
|
@@ -2450,7 +2890,9 @@ var ShieldedPoolPrecompile = class {
|
|
|
2450
2890
|
{ type: "bytes32", value: root },
|
|
2451
2891
|
{ type: "bytes32[]", value: nullifiers },
|
|
2452
2892
|
{ type: "bytes32[]", value: commitments },
|
|
2453
|
-
{ type: "bytes[]", value: memos }
|
|
2893
|
+
{ type: "bytes[]", value: memos },
|
|
2894
|
+
{ type: "uint", value: BigInt(params.assetId) },
|
|
2895
|
+
{ type: "uint", value: params.fee ?? 0n }
|
|
2454
2896
|
);
|
|
2455
2897
|
}
|
|
2456
2898
|
/**
|
|
@@ -2478,6 +2920,9 @@ var ShieldedPoolPrecompile = class {
|
|
|
2478
2920
|
const recipientBytes = fromHex(
|
|
2479
2921
|
"0x" + (recipientRaw.length === 64 ? recipientRaw : recipientRaw.padEnd(64, "0"))
|
|
2480
2922
|
);
|
|
2923
|
+
const changeCommitmentHex = params.changeCommitment ?? "0x" + "00".repeat(32);
|
|
2924
|
+
const changeCommitment = fromHex(changeCommitmentHex);
|
|
2925
|
+
const changeEncryptedMemo = params.changeEncryptedMemo ?? new Uint8Array();
|
|
2481
2926
|
return encodeHex(
|
|
2482
2927
|
SP_SEL.UNSHIELD,
|
|
2483
2928
|
{ type: "bytes", value: proof },
|
|
@@ -2485,7 +2930,10 @@ var ShieldedPoolPrecompile = class {
|
|
|
2485
2930
|
{ type: "bytes32", value: nullifier },
|
|
2486
2931
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2487
2932
|
{ type: "uint", value: params.amount },
|
|
2488
|
-
{ type: "bytes32", value: recipientBytes }
|
|
2933
|
+
{ type: "bytes32", value: recipientBytes },
|
|
2934
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
2935
|
+
{ type: "bytes32", value: changeCommitment },
|
|
2936
|
+
{ type: "bytes", value: changeEncryptedMemo }
|
|
2489
2937
|
);
|
|
2490
2938
|
}
|
|
2491
2939
|
/**
|
|
@@ -2513,24 +2961,151 @@ var ShieldedPoolPrecompile = class {
|
|
|
2513
2961
|
});
|
|
2514
2962
|
}
|
|
2515
2963
|
/**
|
|
2516
|
-
* Estimates the EVM gas for a `privateTransfer` call.
|
|
2964
|
+
* Estimates the EVM gas for a `privateTransfer` call.
|
|
2965
|
+
*/
|
|
2966
|
+
async estimatePrivateTransferGas(params, from) {
|
|
2967
|
+
return this.evm.estimateGas({
|
|
2968
|
+
from,
|
|
2969
|
+
to: this.addr,
|
|
2970
|
+
data: this.buildPrivateTransferCalldata(params)
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
/**
|
|
2974
|
+
* Estimates the EVM gas for an `unshield` call.
|
|
2975
|
+
*/
|
|
2976
|
+
async estimateUnshieldGas(params, from) {
|
|
2977
|
+
return this.evm.estimateGas({
|
|
2978
|
+
from,
|
|
2979
|
+
to: this.addr,
|
|
2980
|
+
data: this.buildUnshieldCalldata(params)
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
// ─── requestDisclosure ─────────────────────────────────────────────────────
|
|
2984
|
+
/**
|
|
2985
|
+
* Returns the ABI-encoded calldata for
|
|
2986
|
+
* `requestDisclosure(bytes32,bytes32,bool,bool,bool,bytes,bytes32,bytes32)`.
|
|
2987
|
+
*
|
|
2988
|
+
* The **EVM caller** of the resulting transaction is treated as the **auditor**
|
|
2989
|
+
* on-chain. No explicit auditor argument is needed.
|
|
2990
|
+
*/
|
|
2991
|
+
buildRequestDisclosureCalldata(params) {
|
|
2992
|
+
if (params.auditorBjjPkX.length !== 32)
|
|
2993
|
+
throw new RangeError("requestDisclosure: auditorBjjPkX must be 32 bytes");
|
|
2994
|
+
if (params.auditorBjjPkY.length !== 32)
|
|
2995
|
+
throw new RangeError("requestDisclosure: auditorBjjPkY must be 32 bytes");
|
|
2996
|
+
const target = fromHex(params.target);
|
|
2997
|
+
const commitment = fromHex(params.commitment);
|
|
2998
|
+
const reasonBytes = new TextEncoder().encode(params.reason);
|
|
2999
|
+
return encodeHex(
|
|
3000
|
+
SP_SEL.REQUEST_DISCLOSURE,
|
|
3001
|
+
{ type: "bytes32", value: target },
|
|
3002
|
+
{ type: "bytes32", value: commitment },
|
|
3003
|
+
{ type: "bool", value: params.disclosedValue },
|
|
3004
|
+
{ type: "bool", value: params.disclosedAssetId },
|
|
3005
|
+
{ type: "bool", value: params.disclosedOwner },
|
|
3006
|
+
{ type: "bytes", value: reasonBytes },
|
|
3007
|
+
{ type: "bytes32", value: params.auditorBjjPkX },
|
|
3008
|
+
{ type: "bytes32", value: params.auditorBjjPkY }
|
|
3009
|
+
);
|
|
3010
|
+
}
|
|
3011
|
+
/**
|
|
3012
|
+
* Requests selective disclosure of a specific commitment.
|
|
3013
|
+
*
|
|
3014
|
+
* The EVM caller is recorded as the auditor on-chain. The note owner can
|
|
3015
|
+
* respond with `disclose()` or reject with `rejectDisclosure()`.
|
|
3016
|
+
*
|
|
3017
|
+
* Extrinsic: `shieldedPool.requestDisclosure(target, commitment, requiredFields, reason, bjjPkX, bjjPkY)`
|
|
3018
|
+
*/
|
|
3019
|
+
async requestDisclosure(params, signer) {
|
|
3020
|
+
return signer({ to: this.addr, data: this.buildRequestDisclosureCalldata(params) });
|
|
3021
|
+
}
|
|
3022
|
+
// ─── disclose ──────────────────────────────────────────────────────────────
|
|
3023
|
+
/**
|
|
3024
|
+
* Returns the ABI-encoded calldata for `disclose(bytes32,bytes,bytes,bytes32)`.
|
|
3025
|
+
*
|
|
3026
|
+
* The **EVM caller** is treated as the **note owner** on-chain.
|
|
3027
|
+
* `params.proofBytes` must be exactly 128 bytes; `params.publicSignals` exactly 256 bytes.
|
|
3028
|
+
*/
|
|
3029
|
+
buildDiscloseCalldata(params) {
|
|
3030
|
+
if (params.proofBytes.length !== 128)
|
|
3031
|
+
throw new RangeError("disclose: proofBytes must be exactly 128 bytes");
|
|
3032
|
+
if (params.publicSignals.length !== 256)
|
|
3033
|
+
throw new RangeError("disclose: publicSignals must be exactly 256 bytes");
|
|
3034
|
+
const commitment = fromHex(params.commitment);
|
|
3035
|
+
const auditor = fromHex(params.auditor);
|
|
3036
|
+
return encodeHex(
|
|
3037
|
+
SP_SEL.DISCLOSE,
|
|
3038
|
+
{ type: "bytes32", value: commitment },
|
|
3039
|
+
{ type: "bytes", value: params.proofBytes },
|
|
3040
|
+
{ type: "bytes", value: params.publicSignals },
|
|
3041
|
+
{ type: "bytes32", value: auditor }
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Submits a selective disclosure proof for a commitment.
|
|
3046
|
+
*
|
|
3047
|
+
* The EVM caller is treated as the note owner on-chain. The Groth16 proof is
|
|
3048
|
+
* verified by the runtime; on success the encrypted signals are stored for
|
|
3049
|
+
* the auditor to decrypt off-chain.
|
|
3050
|
+
*
|
|
3051
|
+
* Extrinsic: `shieldedPool.disclose(commitment, proofBytes, publicSignals, auditor)`
|
|
3052
|
+
*/
|
|
3053
|
+
async disclose(params, signer) {
|
|
3054
|
+
return signer({ to: this.addr, data: this.buildDiscloseCalldata(params) });
|
|
3055
|
+
}
|
|
3056
|
+
// ─── rejectDisclosure ──────────────────────────────────────────────────────
|
|
3057
|
+
/**
|
|
3058
|
+
* Returns the ABI-encoded calldata for `rejectDisclosure(bytes32,bytes32,bytes)`.
|
|
3059
|
+
*
|
|
3060
|
+
* The **EVM caller** is treated as the **target** (note owner) on-chain.
|
|
3061
|
+
*/
|
|
3062
|
+
buildRejectDisclosureCalldata(params) {
|
|
3063
|
+
const auditor = fromHex(params.auditor);
|
|
3064
|
+
const commitment = fromHex(params.commitment);
|
|
3065
|
+
const reasonBytes = new TextEncoder().encode(params.reason);
|
|
3066
|
+
return encodeHex(
|
|
3067
|
+
SP_SEL.REJECT_DISCLOSURE,
|
|
3068
|
+
{ type: "bytes32", value: auditor },
|
|
3069
|
+
{ type: "bytes32", value: commitment },
|
|
3070
|
+
{ type: "bytes", value: reasonBytes }
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Rejects a pending disclosure request.
|
|
3075
|
+
*
|
|
3076
|
+
* The EVM caller is treated as the note owner (target) on-chain.
|
|
3077
|
+
*
|
|
3078
|
+
* Extrinsic: `shieldedPool.rejectDisclosure(auditor, commitment, reason)`
|
|
3079
|
+
*/
|
|
3080
|
+
async rejectDisclosure(params, signer) {
|
|
3081
|
+
return signer({ to: this.addr, data: this.buildRejectDisclosureCalldata(params) });
|
|
3082
|
+
}
|
|
3083
|
+
// ─── pruneExpiredRequest ───────────────────────────────────────────────────
|
|
3084
|
+
/**
|
|
3085
|
+
* Returns the ABI-encoded calldata for `pruneExpiredRequest(bytes32,bytes32,bytes32)`.
|
|
3086
|
+
*
|
|
3087
|
+
* Permissionless: any EVM caller can prune an expired request.
|
|
2517
3088
|
*/
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
3089
|
+
buildPruneExpiredRequestCalldata(params) {
|
|
3090
|
+
const target = fromHex(params.target);
|
|
3091
|
+
const auditor = fromHex(params.auditor);
|
|
3092
|
+
const commitment = fromHex(params.commitment);
|
|
3093
|
+
return encodeHex(
|
|
3094
|
+
SP_SEL.PRUNE_EXPIRED_REQUEST,
|
|
3095
|
+
{ type: "bytes32", value: target },
|
|
3096
|
+
{ type: "bytes32", value: auditor },
|
|
3097
|
+
{ type: "bytes32", value: commitment }
|
|
3098
|
+
);
|
|
2524
3099
|
}
|
|
2525
3100
|
/**
|
|
2526
|
-
*
|
|
3101
|
+
* Removes an expired disclosure request from storage.
|
|
3102
|
+
*
|
|
3103
|
+
* Permissionless: any EVM account can call this once `expires_at` has passed.
|
|
3104
|
+
*
|
|
3105
|
+
* Extrinsic: `shieldedPool.pruneExpiredRequest(target, auditor, commitment)`
|
|
2527
3106
|
*/
|
|
2528
|
-
async
|
|
2529
|
-
return this.
|
|
2530
|
-
from,
|
|
2531
|
-
to: this.addr,
|
|
2532
|
-
data: this.buildUnshieldCalldata(params)
|
|
2533
|
-
});
|
|
3107
|
+
async pruneExpiredRequest(params, signer) {
|
|
3108
|
+
return signer({ to: this.addr, data: this.buildPruneExpiredRequestCalldata(params) });
|
|
2534
3109
|
}
|
|
2535
3110
|
};
|
|
2536
3111
|
|
|
@@ -2539,6 +3114,7 @@ var AccountMappingPrecompile = class {
|
|
|
2539
3114
|
constructor(evm) {
|
|
2540
3115
|
this.evm = evm;
|
|
2541
3116
|
}
|
|
3117
|
+
evm;
|
|
2542
3118
|
addr = PRECOMPILE_ADDR.ACCOUNT_MAPPING;
|
|
2543
3119
|
// ─── Read-only ─────────────────────────────────────────────────────────────
|
|
2544
3120
|
/**
|
|
@@ -2550,7 +3126,7 @@ var AccountMappingPrecompile = class {
|
|
|
2550
3126
|
async resolveAlias(alias) {
|
|
2551
3127
|
try {
|
|
2552
3128
|
const data = encodeHex(AM_SEL.RESOLVE_ALIAS, { type: "string", value: alias });
|
|
2553
|
-
const raw =
|
|
3129
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2554
3130
|
if (raw.length < 64) return null;
|
|
2555
3131
|
const owner = decodeAddress2(raw, 0);
|
|
2556
3132
|
const evm = decodeAddress2(raw, 32);
|
|
@@ -2573,7 +3149,7 @@ var AccountMappingPrecompile = class {
|
|
|
2573
3149
|
type: "address",
|
|
2574
3150
|
value: normalizeEvmAddress(evmAddress)
|
|
2575
3151
|
});
|
|
2576
|
-
const raw =
|
|
3152
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2577
3153
|
if (raw.length === 0) return null;
|
|
2578
3154
|
const alias = decodeString(raw, 0);
|
|
2579
3155
|
return alias.length > 0 ? alias : null;
|
|
@@ -2593,7 +3169,7 @@ var AccountMappingPrecompile = class {
|
|
|
2593
3169
|
{ type: "string", value: alias },
|
|
2594
3170
|
{ type: "bytes32", value: commitmentBytes }
|
|
2595
3171
|
);
|
|
2596
|
-
const raw =
|
|
3172
|
+
const raw = fromHex(await this.evm.call(this.addr, data));
|
|
2597
3173
|
if (raw.length < 32) return false;
|
|
2598
3174
|
return decodeBool(raw, 0);
|
|
2599
3175
|
} catch {
|
|
@@ -2798,6 +3374,7 @@ var CryptoPrecompiles = class {
|
|
|
2798
3374
|
constructor(evm) {
|
|
2799
3375
|
this.evm = evm;
|
|
2800
3376
|
}
|
|
3377
|
+
evm;
|
|
2801
3378
|
// ─── ECRecover (0x0001) ───────────────────────────────────────────────────
|
|
2802
3379
|
/**
|
|
2803
3380
|
* Recovers the Ethereum address from an ECDSA signature.
|
|
@@ -2813,7 +3390,7 @@ var CryptoPrecompiles = class {
|
|
|
2813
3390
|
input[63] = v;
|
|
2814
3391
|
input.set(r.slice(0, 32), 64);
|
|
2815
3392
|
input.set(s.slice(0, 32), 96);
|
|
2816
|
-
const raw =
|
|
3393
|
+
const raw = fromHex(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER, toHex(input)));
|
|
2817
3394
|
if (raw.length < 32) return "0x" + "00".repeat(20);
|
|
2818
3395
|
return "0x" + toHex(raw.slice(12, 32)).slice(2);
|
|
2819
3396
|
}
|
|
@@ -2829,7 +3406,7 @@ var CryptoPrecompiles = class {
|
|
|
2829
3406
|
input[63] = v;
|
|
2830
3407
|
input.set(r.slice(0, 32), 64);
|
|
2831
3408
|
input.set(s.slice(0, 32), 96);
|
|
2832
|
-
return
|
|
3409
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER_PUBKEY, toHex(input)));
|
|
2833
3410
|
}
|
|
2834
3411
|
// ─── SHA-256 (0x0002) ─────────────────────────────────────────────────────
|
|
2835
3412
|
/**
|
|
@@ -2837,7 +3414,7 @@ var CryptoPrecompiles = class {
|
|
|
2837
3414
|
* Returns a 32-byte digest.
|
|
2838
3415
|
*/
|
|
2839
3416
|
async sha256(data) {
|
|
2840
|
-
return
|
|
3417
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.SHA256, toHex(data)));
|
|
2841
3418
|
}
|
|
2842
3419
|
// ─── RIPEMD-160 (0x0003) ──────────────────────────────────────────────────
|
|
2843
3420
|
/**
|
|
@@ -2845,7 +3422,7 @@ var CryptoPrecompiles = class {
|
|
|
2845
3422
|
* Returns the 20-byte digest right-padded to 32 bytes (standard ABI output).
|
|
2846
3423
|
*/
|
|
2847
3424
|
async ripemd160(data) {
|
|
2848
|
-
const raw =
|
|
3425
|
+
const raw = fromHex(await this.evm.call(PRECOMPILE_ADDR.RIPEMD160, toHex(data)));
|
|
2849
3426
|
return raw.length >= 32 ? raw.slice(12, 32) : raw;
|
|
2850
3427
|
}
|
|
2851
3428
|
// ─── Identity (0x0004) ────────────────────────────────────────────────────
|
|
@@ -2854,7 +3431,7 @@ var CryptoPrecompiles = class {
|
|
|
2854
3431
|
* Mainly useful for gas benchmarking.
|
|
2855
3432
|
*/
|
|
2856
3433
|
async identity(data) {
|
|
2857
|
-
return
|
|
3434
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.IDENTITY, toHex(data)));
|
|
2858
3435
|
}
|
|
2859
3436
|
// ─── SHA3-FIPS-256 / Keccak-256 (0x0400) ─────────────────────────────────
|
|
2860
3437
|
/**
|
|
@@ -2862,7 +3439,7 @@ var CryptoPrecompiles = class {
|
|
|
2862
3439
|
* Returns a 32-byte digest.
|
|
2863
3440
|
*/
|
|
2864
3441
|
async keccak256(data) {
|
|
2865
|
-
return
|
|
3442
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.SHA3_FIPS256, toHex(data)));
|
|
2866
3443
|
}
|
|
2867
3444
|
// ─── Curve25519 / Ristretto (0x0402, 0x0403) ─────────────────────────────
|
|
2868
3445
|
/**
|
|
@@ -2885,7 +3462,7 @@ var CryptoPrecompiles = class {
|
|
|
2885
3462
|
}
|
|
2886
3463
|
input.set(pt, i * 32);
|
|
2887
3464
|
}
|
|
2888
|
-
return
|
|
3465
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_ADD, toHex(input)));
|
|
2889
3466
|
}
|
|
2890
3467
|
/**
|
|
2891
3468
|
* Multiplies a Ristretto compressed point by a scalar via EVM precompile.
|
|
@@ -2901,39 +3478,44 @@ var CryptoPrecompiles = class {
|
|
|
2901
3478
|
const input = new Uint8Array(64);
|
|
2902
3479
|
input.set(scalar, 0);
|
|
2903
3480
|
input.set(point, 32);
|
|
2904
|
-
return
|
|
3481
|
+
return fromHex(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_SCALAR_MUL, toHex(input)));
|
|
2905
3482
|
}
|
|
2906
3483
|
};
|
|
2907
3484
|
|
|
2908
3485
|
// src/client/OrbinumClient.ts
|
|
2909
3486
|
var OrbinumClient = class _OrbinumClient {
|
|
2910
|
-
/** Raw
|
|
3487
|
+
/** Raw Substrate WebSocket connection — use for custom RPC calls or low-level access. */
|
|
2911
3488
|
substrate;
|
|
2912
|
-
/** Raw
|
|
3489
|
+
/** Raw EVM HTTP JSON-RPC client. `null` when `evmRpc` is not configured. */
|
|
2913
3490
|
evm;
|
|
2914
3491
|
/**
|
|
2915
|
-
* High-level EVM block and transaction explorer
|
|
3492
|
+
* High-level EVM block and transaction explorer.
|
|
2916
3493
|
* Provides enriched queries for blocks, transactions, addresses, and token transfers.
|
|
3494
|
+
* `null` when `evmRpc` is not configured.
|
|
2917
3495
|
*/
|
|
2918
3496
|
evmExplorer;
|
|
2919
3497
|
/**
|
|
2920
|
-
* HTTP client for the Orbinum indexer REST API
|
|
3498
|
+
* HTTP client for the Orbinum indexer REST API.
|
|
2921
3499
|
* Provides paginated access to indexed blocks, extrinsics, shielded events, and nullifiers.
|
|
3500
|
+
* `null` when `indexerUrl` is not configured.
|
|
2922
3501
|
*/
|
|
2923
3502
|
indexer;
|
|
2924
|
-
/** Shielded-pool
|
|
3503
|
+
/** Shielded-pool extrinsics and Merkle tree queries (`shield`, `unshield`, `privateTransfer`, …). */
|
|
2925
3504
|
shieldedPool;
|
|
2926
|
-
/** Account
|
|
3505
|
+
/** Account-mapping extrinsics: aliases, chain links, metadata, marketplace, and identity. */
|
|
2927
3506
|
accountMapping;
|
|
2928
|
-
/** Typed access to
|
|
3507
|
+
/** Typed access to `privacy_*` custom RPC endpoints. */
|
|
2929
3508
|
privacy;
|
|
2930
|
-
/** Typed access to zkVerifier_
|
|
3509
|
+
/** Typed access to `zkVerifier_*` custom RPC endpoints. */
|
|
2931
3510
|
zkVerifier;
|
|
3511
|
+
/** Typed access to `relayer_*` RPC endpoints (registry lookup and pending fee queries). */
|
|
3512
|
+
relayerStatus;
|
|
2932
3513
|
/**
|
|
2933
|
-
*
|
|
2934
|
-
*
|
|
3514
|
+
* Precompile modules for interacting with Orbinum contracts from an EVM wallet.
|
|
3515
|
+
* `null` when `evmRpc` is not configured. Methods on each sub-module throw if `evm` is `null`.
|
|
2935
3516
|
*/
|
|
2936
3517
|
precompiles;
|
|
3518
|
+
/** @internal Use `OrbinumClient.connect()` to obtain an instance. */
|
|
2937
3519
|
constructor(substrate, evm, indexer) {
|
|
2938
3520
|
this.substrate = substrate;
|
|
2939
3521
|
this.evm = evm;
|
|
@@ -2943,6 +3525,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2943
3525
|
this.accountMapping = new AccountMappingModule(substrate);
|
|
2944
3526
|
this.privacy = new PrivacyModule(substrate);
|
|
2945
3527
|
this.zkVerifier = new ZkVerifierModule(substrate);
|
|
3528
|
+
this.relayerStatus = new RelayerStatusModule(substrate);
|
|
2946
3529
|
this.precompiles = evm ? {
|
|
2947
3530
|
shieldedPool: new ShieldedPoolPrecompile(evm),
|
|
2948
3531
|
accountMapping: new AccountMappingPrecompile(evm),
|
|
@@ -2950,8 +3533,10 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2950
3533
|
} : null;
|
|
2951
3534
|
}
|
|
2952
3535
|
/**
|
|
2953
|
-
*
|
|
2954
|
-
*
|
|
3536
|
+
* Creates and connects an `OrbinumClient` from the given configuration.
|
|
3537
|
+
*
|
|
3538
|
+
* Establishes the Substrate WebSocket connection and, if configured, instantiates
|
|
3539
|
+
* the EVM and indexer clients. Throws if the node is unreachable within `connectTimeoutMs`.
|
|
2955
3540
|
*/
|
|
2956
3541
|
static async connect(config) {
|
|
2957
3542
|
const substrate = await SubstrateClient.connect(
|
|
@@ -2962,7 +3547,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
2962
3547
|
const indexer = config.indexerUrl ? new IndexerClient({ baseUrl: config.indexerUrl }) : null;
|
|
2963
3548
|
return new _OrbinumClient(substrate, evm, indexer);
|
|
2964
3549
|
}
|
|
2965
|
-
/** Closes the WebSocket connection
|
|
3550
|
+
/** Closes the underlying Substrate WebSocket connection and releases all resources. */
|
|
2966
3551
|
destroy() {
|
|
2967
3552
|
this.substrate.destroy();
|
|
2968
3553
|
}
|
|
@@ -2991,6 +3576,7 @@ var OrbinumClientProvider = class {
|
|
|
2991
3576
|
_reconnectAttempt = 0;
|
|
2992
3577
|
// ─── Events ─────────────────────────────────────────────────────────────
|
|
2993
3578
|
_listeners = /* @__PURE__ */ new Set();
|
|
3579
|
+
/** Creates a new provider with the given configuration. Does not connect automatically — call `connect()` to initiate. */
|
|
2994
3580
|
constructor(config) {
|
|
2995
3581
|
this.config = config;
|
|
2996
3582
|
this.connectTimeoutMs = config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
@@ -3000,9 +3586,11 @@ var OrbinumClientProvider = class {
|
|
|
3000
3586
|
this.reconnectMaxMs = config.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
3001
3587
|
}
|
|
3002
3588
|
// ─── Status ─────────────────────────────────────────────────────────────
|
|
3589
|
+
/** Current connection status. Reflects the last state set by the provider internals. */
|
|
3003
3590
|
get status() {
|
|
3004
3591
|
return this._status;
|
|
3005
3592
|
}
|
|
3593
|
+
/** Updates internal status and notifies all registered listeners. Swallows listener exceptions to avoid cascading failures. */
|
|
3006
3594
|
setStatus(status, error) {
|
|
3007
3595
|
this._status = status;
|
|
3008
3596
|
const event = { status, ...error ? { error } : {} };
|
|
@@ -3013,6 +3601,10 @@ var OrbinumClientProvider = class {
|
|
|
3013
3601
|
}
|
|
3014
3602
|
});
|
|
3015
3603
|
}
|
|
3604
|
+
/**
|
|
3605
|
+
* Registers a listener that is called on every status transition.
|
|
3606
|
+
* Returns an unsubscribe function — call it to stop receiving events.
|
|
3607
|
+
*/
|
|
3016
3608
|
onStatusChange(listener) {
|
|
3017
3609
|
this._listeners.add(listener);
|
|
3018
3610
|
return () => {
|
|
@@ -3020,10 +3612,18 @@ var OrbinumClientProvider = class {
|
|
|
3020
3612
|
};
|
|
3021
3613
|
}
|
|
3022
3614
|
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
3615
|
+
/**
|
|
3616
|
+
* Initiates the first connection attempt. No-op if the provider is not in `'idle'` state.
|
|
3617
|
+
* Call this once after constructing the provider.
|
|
3618
|
+
*/
|
|
3023
3619
|
connect() {
|
|
3024
3620
|
if (this._status !== "idle") return;
|
|
3025
3621
|
this.startConnectAttempt();
|
|
3026
3622
|
}
|
|
3623
|
+
/**
|
|
3624
|
+
* Tears down the active client and any pending reconnect timers,
|
|
3625
|
+
* then resets the provider back to `'idle'` so `connect()` can be called again.
|
|
3626
|
+
*/
|
|
3027
3627
|
reset() {
|
|
3028
3628
|
this.cancelReconnect();
|
|
3029
3629
|
this.teardownClient();
|
|
@@ -3031,6 +3631,7 @@ var OrbinumClientProvider = class {
|
|
|
3031
3631
|
this.setStatus("idle");
|
|
3032
3632
|
}
|
|
3033
3633
|
// ─── Internal connection flow ───────────────────────────────────────────
|
|
3634
|
+
/** Transitions to `'connecting'`, kicks off `attemptConnect`, and schedules a reconnect if it fails. */
|
|
3034
3635
|
startConnectAttempt() {
|
|
3035
3636
|
this.setStatus("connecting");
|
|
3036
3637
|
this._connectingPromise = this.attemptConnect();
|
|
@@ -3038,6 +3639,11 @@ var OrbinumClientProvider = class {
|
|
|
3038
3639
|
if (this._status !== "idle") this.scheduleReconnect();
|
|
3039
3640
|
});
|
|
3040
3641
|
}
|
|
3642
|
+
/**
|
|
3643
|
+
* Performs a single connection attempt race against `connectTimeoutMs`.
|
|
3644
|
+
* On success: stores the client, starts the heartbeat, and returns it.
|
|
3645
|
+
* On failure: destroys any orphaned client and transitions to `'disconnected'`.
|
|
3646
|
+
*/
|
|
3041
3647
|
async attemptConnect() {
|
|
3042
3648
|
let timeoutId = null;
|
|
3043
3649
|
let orphanClient = null;
|
|
@@ -3085,6 +3691,7 @@ var OrbinumClientProvider = class {
|
|
|
3085
3691
|
}
|
|
3086
3692
|
}
|
|
3087
3693
|
// ─── Heartbeat ──────────────────────────────────────────────────────────
|
|
3694
|
+
/** Starts the periodic heartbeat loop. Replaces any existing timer. */
|
|
3088
3695
|
startHeartbeat() {
|
|
3089
3696
|
this.stopHeartbeat();
|
|
3090
3697
|
this._heartbeatTimer = setInterval(async () => {
|
|
@@ -3097,12 +3704,17 @@ var OrbinumClientProvider = class {
|
|
|
3097
3704
|
}
|
|
3098
3705
|
}, this.heartbeatIntervalMs);
|
|
3099
3706
|
}
|
|
3707
|
+
/** Clears the heartbeat interval timer if active. */
|
|
3100
3708
|
stopHeartbeat() {
|
|
3101
3709
|
if (this._heartbeatTimer) {
|
|
3102
3710
|
clearInterval(this._heartbeatTimer);
|
|
3103
3711
|
this._heartbeatTimer = null;
|
|
3104
3712
|
}
|
|
3105
3713
|
}
|
|
3714
|
+
/**
|
|
3715
|
+
* Sends a `system_health` RPC ping and waits up to `heartbeatTimeoutMs`.
|
|
3716
|
+
* Returns `true` if the node responds in time, `false` otherwise.
|
|
3717
|
+
*/
|
|
3106
3718
|
async probe() {
|
|
3107
3719
|
if (!this._orbinumClient) return false;
|
|
3108
3720
|
try {
|
|
@@ -3118,6 +3730,10 @@ var OrbinumClientProvider = class {
|
|
|
3118
3730
|
}
|
|
3119
3731
|
}
|
|
3120
3732
|
// ─── Reconnection ───────────────────────────────────────────────────────
|
|
3733
|
+
/**
|
|
3734
|
+
* Schedules the next connection attempt using exponential backoff
|
|
3735
|
+
* (capped at `reconnectMaxMs`), then transitions to `'reconnecting'`.
|
|
3736
|
+
*/
|
|
3121
3737
|
scheduleReconnect() {
|
|
3122
3738
|
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
|
|
3123
3739
|
const delay = Math.min(
|
|
@@ -3131,6 +3747,7 @@ var OrbinumClientProvider = class {
|
|
|
3131
3747
|
if (this._status !== "idle") this.startConnectAttempt();
|
|
3132
3748
|
}, delay);
|
|
3133
3749
|
}
|
|
3750
|
+
/** Clears any pending reconnect timer without triggering a new attempt. */
|
|
3134
3751
|
cancelReconnect() {
|
|
3135
3752
|
if (this._reconnectTimer) {
|
|
3136
3753
|
clearTimeout(this._reconnectTimer);
|
|
@@ -3138,6 +3755,7 @@ var OrbinumClientProvider = class {
|
|
|
3138
3755
|
}
|
|
3139
3756
|
}
|
|
3140
3757
|
// ─── Client teardown ────────────────────────────────────────────────────
|
|
3758
|
+
/** Stops the heartbeat, destroys the active client, and clears all in-progress promises. */
|
|
3141
3759
|
teardownClient() {
|
|
3142
3760
|
this.stopHeartbeat();
|
|
3143
3761
|
try {
|
|
@@ -3148,11 +3766,19 @@ var OrbinumClientProvider = class {
|
|
|
3148
3766
|
this._connectingPromise = null;
|
|
3149
3767
|
}
|
|
3150
3768
|
// ─── Client access ──────────────────────────────────────────────────────
|
|
3769
|
+
/**
|
|
3770
|
+
* Returns the active `OrbinumClient`, or awaits the in-progress connection attempt.
|
|
3771
|
+
* Throws if the provider is `'idle'`, `'disconnected'`, or `'reconnecting'`.
|
|
3772
|
+
*/
|
|
3151
3773
|
async getOrbinumClient() {
|
|
3152
3774
|
if (this._orbinumClient) return this._orbinumClient;
|
|
3153
3775
|
if (this._connectingPromise) return this._connectingPromise;
|
|
3154
3776
|
throw new Error(`OrbinumClientProvider: cannot get client in status '${this._status}'`);
|
|
3155
3777
|
}
|
|
3778
|
+
/**
|
|
3779
|
+
* Same as `getOrbinumClient()` but returns `null` instead of throwing.
|
|
3780
|
+
* Useful in contexts where a missing client is an acceptable no-op.
|
|
3781
|
+
*/
|
|
3156
3782
|
async tryGetOrbinumClient() {
|
|
3157
3783
|
try {
|
|
3158
3784
|
return await this.getOrbinumClient();
|
|
@@ -3161,15 +3787,28 @@ var OrbinumClientProvider = class {
|
|
|
3161
3787
|
}
|
|
3162
3788
|
}
|
|
3163
3789
|
// ─── Convenience RPC helpers ────────────────────────────────────────────
|
|
3790
|
+
/**
|
|
3791
|
+
* Sends a single Substrate JSON-RPC request and returns the typed result.
|
|
3792
|
+
* Waits for the client to be ready before dispatching.
|
|
3793
|
+
*/
|
|
3164
3794
|
async rpcSend(method, params = []) {
|
|
3165
3795
|
const client = await this.getOrbinumClient();
|
|
3166
3796
|
return client.substrate.request(method, params);
|
|
3167
3797
|
}
|
|
3798
|
+
/**
|
|
3799
|
+
* Sends a single EVM JSON-RPC request and returns the typed result.
|
|
3800
|
+
* Throws if `evmRpc` was not configured.
|
|
3801
|
+
*/
|
|
3168
3802
|
async evmRpc(method, params = []) {
|
|
3169
3803
|
const client = await this.getOrbinumClient();
|
|
3170
3804
|
if (!client.evm) throw new Error("EVM RPC not configured");
|
|
3171
3805
|
return client.evm.request(method, params);
|
|
3172
3806
|
}
|
|
3807
|
+
/**
|
|
3808
|
+
* Sends multiple EVM JSON-RPC calls as a single batch request.
|
|
3809
|
+
* Returns a tuple of typed results in the same order as `calls`.
|
|
3810
|
+
* Throws if `evmRpc` was not configured.
|
|
3811
|
+
*/
|
|
3173
3812
|
async evmRpcBatch(calls) {
|
|
3174
3813
|
const client = await this.getOrbinumClient();
|
|
3175
3814
|
if (!client.evm) throw new Error("EVM RPC not configured");
|
|
@@ -3177,102 +3816,469 @@ var OrbinumClientProvider = class {
|
|
|
3177
3816
|
}
|
|
3178
3817
|
};
|
|
3179
3818
|
|
|
3180
|
-
// src/
|
|
3819
|
+
// src/utils/stealth.ts
|
|
3820
|
+
var import_sha22 = require("@noble/hashes/sha2.js");
|
|
3821
|
+
var import_hkdf = require("@noble/hashes/hkdf.js");
|
|
3822
|
+
var import_baby_jubjub2 = require("@zk-kit/baby-jubjub");
|
|
3823
|
+
var STEALTH_INFO = new TextEncoder().encode("orbinum-stealth-v1");
|
|
3824
|
+
function deriveStealthScalar(sharedSecret, ownerPkBigint) {
|
|
3825
|
+
const salt = bigintTo32Le(ownerPkBigint);
|
|
3826
|
+
const stealthBytes = (0, import_hkdf.hkdf)(import_sha22.sha256, sharedSecret, salt, STEALTH_INFO, 32);
|
|
3827
|
+
return bytesToBigintLE(stealthBytes) % BABYJUB_SUBORDER || 1n;
|
|
3828
|
+
}
|
|
3829
|
+
function deriveStealthOwnerPk(sharedSecret, ownerPkBigint, ownerPkPoint) {
|
|
3830
|
+
const stealthScalar = deriveStealthScalar(sharedSecret, ownerPkBigint);
|
|
3831
|
+
const stealthPt = (0, import_baby_jubjub2.addPoint)((0, import_baby_jubjub2.mulPointEscalar)(import_baby_jubjub2.Base8, stealthScalar), ownerPkPoint);
|
|
3832
|
+
return stealthPt[0];
|
|
3833
|
+
}
|
|
3834
|
+
function deriveStealthSk(sharedSecret, ownerPkBigint, spendingKey) {
|
|
3835
|
+
const stealthScalar = deriveStealthScalar(sharedSecret, ownerPkBigint);
|
|
3836
|
+
return (stealthScalar + spendingKey) % BABYJUB_SUBORDER || 1n;
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
// src/utils/bjj.ts
|
|
3840
|
+
var import_baby_jubjub3 = require("@zk-kit/baby-jubjub");
|
|
3841
|
+
var BJJ_A = 168700n;
|
|
3842
|
+
var BJJ_D = 168696n;
|
|
3843
|
+
function _modpow(base, exp, mod) {
|
|
3844
|
+
let result = 1n;
|
|
3845
|
+
base = base % mod;
|
|
3846
|
+
while (exp > 0n) {
|
|
3847
|
+
if (exp & 1n) result = result * base % mod;
|
|
3848
|
+
exp >>= 1n;
|
|
3849
|
+
base = base * base % mod;
|
|
3850
|
+
}
|
|
3851
|
+
return result;
|
|
3852
|
+
}
|
|
3853
|
+
function _sqrtModP(y2) {
|
|
3854
|
+
if (y2 === 0n) return 0n;
|
|
3855
|
+
if (_modpow(y2, (BN254_R - 1n) / 2n, BN254_R) !== 1n) return null;
|
|
3856
|
+
let s = 0n;
|
|
3857
|
+
let q = BN254_R - 1n;
|
|
3858
|
+
while ((q & 1n) === 0n) {
|
|
3859
|
+
q >>= 1n;
|
|
3860
|
+
s++;
|
|
3861
|
+
}
|
|
3862
|
+
if (s === 1n) return _modpow(y2, (BN254_R + 1n) / 4n, BN254_R);
|
|
3863
|
+
let z = 2n;
|
|
3864
|
+
while (_modpow(z, (BN254_R - 1n) / 2n, BN254_R) === 1n) z++;
|
|
3865
|
+
let m = s;
|
|
3866
|
+
let c = _modpow(z, q, BN254_R);
|
|
3867
|
+
let t = _modpow(y2, q, BN254_R);
|
|
3868
|
+
let r = _modpow(y2, (q + 1n) / 2n, BN254_R);
|
|
3869
|
+
for (; ; ) {
|
|
3870
|
+
if (t === 1n) return r;
|
|
3871
|
+
let i = 1n;
|
|
3872
|
+
let tmp = t * t % BN254_R;
|
|
3873
|
+
while (tmp !== 1n) {
|
|
3874
|
+
tmp = tmp * tmp % BN254_R;
|
|
3875
|
+
i++;
|
|
3876
|
+
}
|
|
3877
|
+
const b = _modpow(c, 1n << m - i - 1n, BN254_R);
|
|
3878
|
+
m = i;
|
|
3879
|
+
c = b * b % BN254_R;
|
|
3880
|
+
t = t * c % BN254_R;
|
|
3881
|
+
r = r * b % BN254_R;
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
function recoverOwnerPkPoint(ax) {
|
|
3885
|
+
const x2 = ax * ax % BN254_R;
|
|
3886
|
+
const num = ((1n - BJJ_A * x2) % BN254_R + BN254_R) % BN254_R;
|
|
3887
|
+
const den = ((1n - BJJ_D * x2) % BN254_R + BN254_R) % BN254_R;
|
|
3888
|
+
if (den === 0n) return null;
|
|
3889
|
+
const denInv = _modpow(den, BN254_R - 2n, BN254_R);
|
|
3890
|
+
const y2 = num * denInv % BN254_R;
|
|
3891
|
+
const y = _sqrtModP(y2);
|
|
3892
|
+
if (y === null) return null;
|
|
3893
|
+
const yAlt = BN254_R - y;
|
|
3894
|
+
try {
|
|
3895
|
+
const check = (0, import_baby_jubjub3.mulPointEscalar)([ax, y], BABYJUB_SUBORDER);
|
|
3896
|
+
return check[0] === 0n && check[1] === 1n ? [ax, y] : [ax, yAlt];
|
|
3897
|
+
} catch {
|
|
3898
|
+
return [ax, yAlt];
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
// src/shielded-pool/protocol/NoteBuilder.ts
|
|
3903
|
+
var import_baby_jubjub4 = require("@zk-kit/baby-jubjub");
|
|
3904
|
+
var import_utils2 = require("@noble/ciphers/utils.js");
|
|
3905
|
+
var import_poseidon_lite = require("poseidon-lite");
|
|
3906
|
+
var NoteBuilder = class {
|
|
3907
|
+
/**
|
|
3908
|
+
* Build a ZkNote from the given inputs.
|
|
3909
|
+
*
|
|
3910
|
+
* @param input.value Amount in planck (required).
|
|
3911
|
+
* @param input.assetId Asset ID — default 0n (native ORB-Privacy).
|
|
3912
|
+
* @param input.ownerPk Sender's or recipient's global BabyJubJub Ax — default 0n.
|
|
3913
|
+
* @param input.blinding Random scalar — defaults to BigInt(Date.now()).
|
|
3914
|
+
* @param input.spendingKey Secret key for nullifier — default 0n.
|
|
3915
|
+
* @param input.viewingPublicKey Recipient's 32-byte LE packed BJJ ivk. Triggers memo encryption.
|
|
3916
|
+
* @param input.recipientOwnerPk Recipient's global ownerPk. Required with viewingPublicKey
|
|
3917
|
+
* to enable stealth address derivation. Without it, the
|
|
3918
|
+
* commitment uses ownerPk directly (no stealth).
|
|
3919
|
+
*/
|
|
3920
|
+
static async build(input) {
|
|
3921
|
+
const value = input.value;
|
|
3922
|
+
const assetId = input.assetId ?? 0n;
|
|
3923
|
+
const ownerPk = input.ownerPk ?? 0n;
|
|
3924
|
+
const blinding = input.blinding ?? BigInt(Date.now());
|
|
3925
|
+
const spendingKey = input.spendingKey ?? 0n;
|
|
3926
|
+
const counterpartyPk = input.counterpartyPk ?? 0n;
|
|
3927
|
+
const useStealth = input.viewingPublicKey !== void 0 && input.recipientOwnerPk !== void 0;
|
|
3928
|
+
let memo;
|
|
3929
|
+
if (useStealth) {
|
|
3930
|
+
const recipientOwnerPk = input.recipientOwnerPk;
|
|
3931
|
+
const recipientIvkPacked = input.viewingPublicKey;
|
|
3932
|
+
const ephSk = (0, import_utils2.randomBytes)(32);
|
|
3933
|
+
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
3934
|
+
const ivkPoint = (0, import_baby_jubjub4.unpackPoint)(ivkPackedBigint);
|
|
3935
|
+
if (!ivkPoint)
|
|
3936
|
+
throw new Error("NoteBuilder.build: invalid recipient viewing public key");
|
|
3937
|
+
const ephSkScalar = BigInt(toHex(ephSk)) % BABYJUB_SUBORDER || 1n;
|
|
3938
|
+
const sharedPoint = (0, import_baby_jubjub4.mulPointEscalar)(ivkPoint, ephSkScalar);
|
|
3939
|
+
const sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
3940
|
+
const recipientPkPoint = recoverOwnerPkPoint(recipientOwnerPk);
|
|
3941
|
+
if (!recipientPkPoint)
|
|
3942
|
+
throw new Error(
|
|
3943
|
+
"NoteBuilder.build: recipientOwnerPk is not a valid BJJ x-coordinate"
|
|
3944
|
+
);
|
|
3945
|
+
const effectiveOwnerPk = deriveStealthOwnerPk(
|
|
3946
|
+
sharedSecret,
|
|
3947
|
+
recipientOwnerPk,
|
|
3948
|
+
recipientPkPoint
|
|
3949
|
+
);
|
|
3950
|
+
const stealthCommitment = (0, import_poseidon_lite.poseidon4)([value, assetId, effectiveOwnerPk, blinding]);
|
|
3951
|
+
const stealthCommitmentBytes = bigintTo32Le(stealthCommitment);
|
|
3952
|
+
memo = Array.from(
|
|
3953
|
+
EncryptedMemo.encrypt(
|
|
3954
|
+
value,
|
|
3955
|
+
bigintTo32Le(effectiveOwnerPk),
|
|
3956
|
+
bigintTo32Le(blinding),
|
|
3957
|
+
Number(assetId),
|
|
3958
|
+
stealthCommitmentBytes,
|
|
3959
|
+
recipientIvkPacked,
|
|
3960
|
+
bigintTo32Le(counterpartyPk),
|
|
3961
|
+
ephSk
|
|
3962
|
+
)
|
|
3963
|
+
);
|
|
3964
|
+
const commitment2 = stealthCommitment;
|
|
3965
|
+
const nullifier2 = (0, import_poseidon_lite.poseidon2)([commitment2, spendingKey]);
|
|
3966
|
+
const commitmentBytes2 = stealthCommitmentBytes;
|
|
3967
|
+
const nullifierBytes2 = bigintTo32Le(nullifier2);
|
|
3968
|
+
if (memo.length !== ENCRYPTED_MEMO_SIZE)
|
|
3969
|
+
throw new Error(
|
|
3970
|
+
`NoteBuilder.build: invariant violated \u2014 memo must be ${ENCRYPTED_MEMO_SIZE} bytes, got ${memo.length}`
|
|
3971
|
+
);
|
|
3972
|
+
return {
|
|
3973
|
+
value,
|
|
3974
|
+
assetId,
|
|
3975
|
+
ownerPk: effectiveOwnerPk,
|
|
3976
|
+
blinding,
|
|
3977
|
+
spendingKey,
|
|
3978
|
+
spent: false,
|
|
3979
|
+
spentAt: null,
|
|
3980
|
+
commitment: commitment2,
|
|
3981
|
+
nullifier: nullifier2,
|
|
3982
|
+
commitmentHex: toHex(commitmentBytes2),
|
|
3983
|
+
nullifierHex: toHex(nullifierBytes2),
|
|
3984
|
+
memo,
|
|
3985
|
+
counterpartyPk
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
const commitment = (0, import_poseidon_lite.poseidon4)([value, assetId, ownerPk, blinding]);
|
|
3989
|
+
const nullifier = (0, import_poseidon_lite.poseidon2)([commitment, spendingKey]);
|
|
3990
|
+
const commitmentBytes = bigintTo32Le(commitment);
|
|
3991
|
+
const nullifierBytes = bigintTo32Le(nullifier);
|
|
3992
|
+
memo = input.viewingPublicKey !== void 0 ? Array.from(
|
|
3993
|
+
EncryptedMemo.encrypt(
|
|
3994
|
+
value,
|
|
3995
|
+
bigintTo32Le(ownerPk),
|
|
3996
|
+
bigintTo32Le(blinding),
|
|
3997
|
+
Number(assetId),
|
|
3998
|
+
commitmentBytes,
|
|
3999
|
+
input.viewingPublicKey,
|
|
4000
|
+
bigintTo32Le(counterpartyPk)
|
|
4001
|
+
)
|
|
4002
|
+
) : Array.from(EncryptedMemo.dummy());
|
|
4003
|
+
if (memo.length !== ENCRYPTED_MEMO_SIZE)
|
|
4004
|
+
throw new Error(
|
|
4005
|
+
`NoteBuilder.build: invariant violated \u2014 memo must be ${ENCRYPTED_MEMO_SIZE} bytes, got ${memo.length}`
|
|
4006
|
+
);
|
|
4007
|
+
return {
|
|
4008
|
+
value,
|
|
4009
|
+
assetId,
|
|
4010
|
+
ownerPk,
|
|
4011
|
+
blinding,
|
|
4012
|
+
spendingKey,
|
|
4013
|
+
spent: false,
|
|
4014
|
+
spentAt: null,
|
|
4015
|
+
commitment,
|
|
4016
|
+
nullifier,
|
|
4017
|
+
commitmentHex: toHex(commitmentBytes),
|
|
4018
|
+
nullifierHex: toHex(nullifierBytes),
|
|
4019
|
+
memo,
|
|
4020
|
+
counterpartyPk
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
/**
|
|
4024
|
+
* Build the 168-byte ECDH-encrypted memo for a note.
|
|
4025
|
+
*
|
|
4026
|
+
* Pure TypeScript implementation — no WASM dependency.
|
|
4027
|
+
* Uses ChaCha20-Poly1305 with ECDH key agreement (BabyJubJub ephemeral keypair).
|
|
4028
|
+
*
|
|
4029
|
+
* @param note The ZkNote whose fields populate the plaintext.
|
|
4030
|
+
* @param recipientIvkPacked 32-byte LE packed BJJ viewing public key of the recipient.
|
|
4031
|
+
* Pass `new Uint8Array(32)` (default) for a public/dummy memo.
|
|
4032
|
+
* @param counterpartyPk 32-byte counterparty BabyJubJub Ax.
|
|
4033
|
+
* Pass `new Uint8Array(32)` (default) for no counterparty.
|
|
4034
|
+
*/
|
|
4035
|
+
static buildMemo(note, recipientIvkPacked, counterpartyPk) {
|
|
4036
|
+
return EncryptedMemo.encrypt(
|
|
4037
|
+
note.value,
|
|
4038
|
+
bigintTo32Le(note.ownerPk),
|
|
4039
|
+
bigintTo32Le(note.blinding),
|
|
4040
|
+
Number(note.assetId),
|
|
4041
|
+
bigintTo32Le(note.commitment),
|
|
4042
|
+
recipientIvkPacked ?? new Uint8Array(32),
|
|
4043
|
+
counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n)
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
4046
|
+
};
|
|
4047
|
+
|
|
4048
|
+
// src/shielded-pool/protocol/NoteDecryptor.ts
|
|
3181
4049
|
var import_poseidon_lite2 = require("poseidon-lite");
|
|
3182
|
-
function
|
|
3183
|
-
|
|
4050
|
+
function computeNullifier(commitment, spendingKey) {
|
|
4051
|
+
return (0, import_poseidon_lite2.poseidon2)([commitment, spendingKey]);
|
|
4052
|
+
}
|
|
4053
|
+
function tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
|
|
4054
|
+
return tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk).note;
|
|
4055
|
+
}
|
|
4056
|
+
function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk = 0n) {
|
|
4057
|
+
if (!commitment.encryptedMemo) return { note: null, reason: "no_memo" };
|
|
3184
4058
|
let commitmentBytes;
|
|
3185
4059
|
let memoBytes;
|
|
3186
4060
|
try {
|
|
3187
4061
|
commitmentBytes = fromHex(commitment.commitmentHex);
|
|
3188
4062
|
memoBytes = fromHex(commitment.encryptedMemo);
|
|
3189
4063
|
} catch {
|
|
3190
|
-
return null;
|
|
4064
|
+
return { note: null, reason: "hex_parse_error" };
|
|
4065
|
+
}
|
|
4066
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) {
|
|
4067
|
+
return {
|
|
4068
|
+
note: null,
|
|
4069
|
+
reason: `memo_size_mismatch:got_${memoBytes.length}_expected_${ENCRYPTED_MEMO_SIZE}`
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingSecretKey);
|
|
4073
|
+
if (!plaintext) return { note: null, reason: "decrypt_failed:wrong_key_or_corrupt_mac" };
|
|
4074
|
+
let effectiveOwnerPk = plaintext.ownerPk;
|
|
4075
|
+
let effectiveSpendingKey = spendingKey;
|
|
4076
|
+
if (ownOwnerPk !== 0n && plaintext.ownerPk !== ownOwnerPk) {
|
|
4077
|
+
const sharedSecret = EncryptedMemo.extractSharedSecret(memoBytes, viewingSecretKey);
|
|
4078
|
+
if (!sharedSecret) return { note: null, reason: "stealth_shared_secret_failed" };
|
|
4079
|
+
const ownPkPoint = recoverOwnerPkPoint(ownOwnerPk);
|
|
4080
|
+
if (!ownPkPoint) return { note: null, reason: "stealth_invalid_own_owner_pk" };
|
|
4081
|
+
const stealthOwnerPk = deriveStealthOwnerPk(sharedSecret, ownOwnerPk, ownPkPoint);
|
|
4082
|
+
if (stealthOwnerPk !== plaintext.ownerPk) {
|
|
4083
|
+
return { note: null, reason: "commitment_mismatch" };
|
|
4084
|
+
}
|
|
4085
|
+
effectiveOwnerPk = stealthOwnerPk;
|
|
4086
|
+
effectiveSpendingKey = deriveStealthSk(sharedSecret, ownOwnerPk, spendingKey);
|
|
3191
4087
|
}
|
|
3192
|
-
const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingKey);
|
|
3193
|
-
if (!plaintext) return null;
|
|
3194
4088
|
const recomputed = (0, import_poseidon_lite2.poseidon4)([
|
|
3195
4089
|
plaintext.value,
|
|
3196
4090
|
plaintext.assetId,
|
|
3197
|
-
|
|
4091
|
+
effectiveOwnerPk,
|
|
3198
4092
|
plaintext.blinding
|
|
3199
4093
|
]);
|
|
3200
|
-
if (recomputed !== bytesToBigintLE(commitmentBytes))
|
|
3201
|
-
|
|
4094
|
+
if (recomputed !== bytesToBigintLE(commitmentBytes)) {
|
|
4095
|
+
return { note: null, reason: "commitment_mismatch" };
|
|
4096
|
+
}
|
|
4097
|
+
const nullifier = (0, import_poseidon_lite2.poseidon2)([recomputed, effectiveSpendingKey]);
|
|
3202
4098
|
return {
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
4099
|
+
note: {
|
|
4100
|
+
value: plaintext.value,
|
|
4101
|
+
assetId: plaintext.assetId,
|
|
4102
|
+
ownerPk: effectiveOwnerPk,
|
|
4103
|
+
blinding: plaintext.blinding,
|
|
4104
|
+
spendingKey: effectiveSpendingKey,
|
|
4105
|
+
spent: false,
|
|
4106
|
+
spentAt: null,
|
|
4107
|
+
commitment: recomputed,
|
|
4108
|
+
nullifier,
|
|
4109
|
+
commitmentHex: toHex(bigintTo32Le(recomputed)),
|
|
4110
|
+
nullifierHex: toHex(bigintTo32Le(nullifier)),
|
|
4111
|
+
memo: Array.from(memoBytes),
|
|
4112
|
+
counterpartyPk: plaintext.counterpartyPk
|
|
4113
|
+
}
|
|
3215
4114
|
};
|
|
3216
4115
|
}
|
|
3217
4116
|
|
|
3218
|
-
// src/shielded-pool/
|
|
3219
|
-
var
|
|
3220
|
-
|
|
3221
|
-
|
|
4117
|
+
// src/shielded-pool/protocol/coinSelection.ts
|
|
4118
|
+
var TRANSFER_TREE_DEPTH = 20;
|
|
4119
|
+
function selectNotes(notes, needed) {
|
|
4120
|
+
const unspent = notes.filter((n) => !n.spent && n.value > 0n);
|
|
4121
|
+
const sorted = [...unspent].sort((a, b) => a.value < b.value ? -1 : 1);
|
|
4122
|
+
const single = sorted.find((n) => n.value >= needed);
|
|
4123
|
+
if (single) return [single, null];
|
|
4124
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
4125
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
4126
|
+
const a = sorted[i];
|
|
4127
|
+
const b = sorted[j];
|
|
4128
|
+
if (a !== void 0 && b !== void 0 && a.value + b.value >= needed) {
|
|
4129
|
+
return [a, b];
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
return null;
|
|
4134
|
+
}
|
|
4135
|
+
function buildDummyTransferInput(assetId) {
|
|
4136
|
+
const zeroSibling = "0x" + "00".repeat(32);
|
|
4137
|
+
return {
|
|
4138
|
+
nullifier: 0n,
|
|
4139
|
+
// Constraint 9: nullifier * is_dummy.out === 0 → must be 0
|
|
4140
|
+
value: 0n,
|
|
4141
|
+
// triggers is_dummy[i].out = 1 in the circuit
|
|
4142
|
+
assetId,
|
|
4143
|
+
// must match real note (Constraint 7)
|
|
4144
|
+
ownerPk: 0n,
|
|
4145
|
+
blinding: 0n,
|
|
4146
|
+
spendingKey: 1n,
|
|
4147
|
+
// arbitrary; EdDSA is disabled (enabled = 0) for dummy inputs
|
|
4148
|
+
pathSiblings: Array(TRANSFER_TREE_DEPTH).fill(zeroSibling),
|
|
4149
|
+
leafIndex: 0
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
3222
4152
|
|
|
3223
|
-
// src/shielded-pool/
|
|
3224
|
-
var
|
|
4153
|
+
// src/shielded-pool/protocol/disclosure.ts
|
|
4154
|
+
var import_proof_generator = require("@orbinum/proof-generator");
|
|
4155
|
+
var import_baby_jubjub5 = require("@zk-kit/baby-jubjub");
|
|
4156
|
+
var import_poseidon_lite3 = require("poseidon-lite");
|
|
4157
|
+
function hexFieldToBytes32(hex) {
|
|
4158
|
+
const clean = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
|
|
4159
|
+
return fromHex(clean.padStart(64, "0"));
|
|
4160
|
+
}
|
|
4161
|
+
function deriveBabyJubjubKeypair(substrateSigningKey) {
|
|
4162
|
+
let keyBigInt = 0n;
|
|
4163
|
+
for (let i = 0; i < substrateSigningKey.length; i++) {
|
|
4164
|
+
keyBigInt = keyBigInt << 8n | BigInt(substrateSigningKey[i]);
|
|
4165
|
+
}
|
|
4166
|
+
const sk = (0, import_poseidon_lite3.poseidon1)([keyBigInt]);
|
|
4167
|
+
const pk = (0, import_baby_jubjub5.mulPointEscalar)(import_baby_jubjub5.Base8, sk);
|
|
4168
|
+
return { sk, pkX: pk[0], pkY: pk[1] };
|
|
4169
|
+
}
|
|
4170
|
+
function buildDisclosurePublicSignals(commitment, auditorPkX, auditorPkY, proofOutput) {
|
|
4171
|
+
const buf = new Uint8Array(256);
|
|
4172
|
+
buf.set(hexFieldToBytes32(commitment), 0);
|
|
4173
|
+
buf.set(bigintTo32Le(auditorPkX), 32);
|
|
4174
|
+
buf.set(bigintTo32Le(auditorPkY), 64);
|
|
4175
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.epkX), 96);
|
|
4176
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.epkY), 128);
|
|
4177
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.encValue), 160);
|
|
4178
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.encAssetId), 192);
|
|
4179
|
+
buf.set(hexFieldToBytes32(proofOutput.encryptedData.encOwnerHash), 224);
|
|
4180
|
+
return Array.from(buf);
|
|
4181
|
+
}
|
|
4182
|
+
function decryptDisclosureSignals(auditorBjjSk, enc) {
|
|
4183
|
+
const shared = (0, import_baby_jubjub5.mulPointEscalar)([enc.epkX, enc.epkY], auditorBjjSk);
|
|
4184
|
+
const sharedX = shared[0];
|
|
4185
|
+
const sharedY = shared[1];
|
|
4186
|
+
const k0 = (0, import_poseidon_lite3.poseidon3)([sharedX, sharedY, 0n]);
|
|
4187
|
+
const k1 = (0, import_poseidon_lite3.poseidon3)([sharedX, sharedY, 1n]);
|
|
4188
|
+
const k2 = (0, import_poseidon_lite3.poseidon3)([sharedX, sharedY, 2n]);
|
|
4189
|
+
return {
|
|
4190
|
+
value: (enc.encValue - k0 + BN254_R) % BN254_R,
|
|
4191
|
+
assetId: (enc.encAssetId - k1 + BN254_R) % BN254_R,
|
|
4192
|
+
ownerHash: (enc.encOwnerHash - k2 + BN254_R) % BN254_R
|
|
4193
|
+
};
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
// src/utils/blinding.ts
|
|
4197
|
+
function randomBlinding() {
|
|
4198
|
+
const buf = new Uint8Array(32);
|
|
4199
|
+
crypto.getRandomValues(buf);
|
|
4200
|
+
const n = bytesToBigintLE(buf);
|
|
4201
|
+
return n === 0n ? 1n : n % BN254_R;
|
|
4202
|
+
}
|
|
3225
4203
|
|
|
3226
|
-
// src/
|
|
4204
|
+
// src/privacy-keys/PrivacyKeys.ts
|
|
4205
|
+
var import_hkdf2 = require("@noble/hashes/hkdf.js");
|
|
4206
|
+
var import_sha23 = require("@noble/hashes/sha2.js");
|
|
4207
|
+
var import_baby_jubjub6 = require("@zk-kit/baby-jubjub");
|
|
3227
4208
|
var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
|
|
3228
4209
|
function deriveSpendingKeyMessage(chainId, address) {
|
|
3229
4210
|
return `orbinum-spending-key-v1
|
|
3230
4211
|
${chainId}
|
|
3231
4212
|
${address.toLowerCase()}`;
|
|
3232
4213
|
}
|
|
3233
|
-
async function
|
|
3234
|
-
const
|
|
3235
|
-
const sigBytes = new Uint8Array((hex.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
|
|
4214
|
+
async function deriveMasterKeyBytes(signatureHex, chainId, address) {
|
|
4215
|
+
const sigBytes = fromHex(signatureHex);
|
|
3236
4216
|
const info = new TextEncoder().encode(`orbinum-sk-v1:${chainId}:${address.toLowerCase()}`);
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
4217
|
+
return (0, import_hkdf2.hkdf)(import_sha23.sha256, sigBytes, new Uint8Array(0), info, 32);
|
|
4218
|
+
}
|
|
4219
|
+
async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
|
|
4220
|
+
const masterBytes = await deriveMasterKeyBytes(signatureHex, chainId, address);
|
|
4221
|
+
const skBigint = BigInt(toHex(masterBytes)) % BABYJUB_SUBORDER;
|
|
3241
4222
|
return skBigint === 0n ? 1n : skBigint;
|
|
3242
4223
|
}
|
|
3243
|
-
function
|
|
4224
|
+
function deriveViewingSecretKey(spendingKey) {
|
|
3244
4225
|
const ikm = bigintTo32Le(spendingKey);
|
|
3245
|
-
return (0,
|
|
4226
|
+
return (0, import_hkdf2.hkdf)(import_sha23.sha256, ikm, void 0, IVK_DOMAIN, 32);
|
|
4227
|
+
}
|
|
4228
|
+
function deriveViewingPublicKey(ivsk) {
|
|
4229
|
+
const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
|
|
4230
|
+
const ivkPoint = (0, import_baby_jubjub6.mulPointEscalar)(import_baby_jubjub6.Base8, ivskScalar);
|
|
4231
|
+
const packed = (0, import_baby_jubjub6.packPoint)(ivkPoint);
|
|
4232
|
+
return bigintTo32Le(packed);
|
|
3246
4233
|
}
|
|
3247
4234
|
function deriveOwnerPk(spendingKey) {
|
|
3248
4235
|
try {
|
|
3249
|
-
const pubPoint = (0,
|
|
4236
|
+
const pubPoint = (0, import_baby_jubjub6.mulPointEscalar)(import_baby_jubjub6.Base8, spendingKey);
|
|
3250
4237
|
return pubPoint[0];
|
|
3251
4238
|
} catch {
|
|
3252
4239
|
return 0n;
|
|
3253
4240
|
}
|
|
3254
4241
|
}
|
|
3255
4242
|
|
|
3256
|
-
// src/
|
|
4243
|
+
// src/privacy-keys/PrivacyKeyManager.ts
|
|
3257
4244
|
var PrivacyKeyManager = class {
|
|
3258
4245
|
_state = {
|
|
3259
4246
|
spendingKey: null,
|
|
3260
|
-
|
|
4247
|
+
masterBytes: null,
|
|
4248
|
+
viewingSecretKey: null,
|
|
4249
|
+
viewingPublicKeyPacked: null,
|
|
3261
4250
|
ownerPk: null
|
|
3262
4251
|
};
|
|
3263
4252
|
/**
|
|
3264
|
-
* Load a spending key into the in-memory session.
|
|
3265
|
-
* Derives
|
|
4253
|
+
* Load a spending key and its corresponding master bytes into the in-memory session.
|
|
4254
|
+
* Derives viewingSecretKey, viewingPublicKeyPacked, and ownerPk immediately.
|
|
3266
4255
|
* Replaces any previously loaded key.
|
|
4256
|
+
*
|
|
4257
|
+
* @param spendingKey Circuit scalar: BigInt(masterBytes) % BABYJUB_SUBORDER, clamped to [1, ∞).
|
|
4258
|
+
* @param masterBytes Raw 32-byte HKDF output before modular reduction. Used to derive
|
|
4259
|
+
* the stable vault key (HKDF(masterBytes, info="orbinum-vault-key-v1")).
|
|
3267
4260
|
*/
|
|
3268
|
-
async load(spendingKey) {
|
|
3269
|
-
const
|
|
4261
|
+
async load(spendingKey, masterBytes) {
|
|
4262
|
+
const viewingSecretKey = deriveViewingSecretKey(spendingKey);
|
|
4263
|
+
const viewingPublicKeyPacked = deriveViewingPublicKey(viewingSecretKey);
|
|
3270
4264
|
const ownerPk = deriveOwnerPk(spendingKey);
|
|
3271
|
-
this._state = {
|
|
4265
|
+
this._state = {
|
|
4266
|
+
spendingKey,
|
|
4267
|
+
masterBytes,
|
|
4268
|
+
viewingSecretKey,
|
|
4269
|
+
viewingPublicKeyPacked,
|
|
4270
|
+
ownerPk
|
|
4271
|
+
};
|
|
3272
4272
|
}
|
|
3273
4273
|
/** Clear all key material from memory. Call on vault lock / sign-out. */
|
|
3274
4274
|
clear() {
|
|
3275
|
-
this._state = {
|
|
4275
|
+
this._state = {
|
|
4276
|
+
spendingKey: null,
|
|
4277
|
+
masterBytes: null,
|
|
4278
|
+
viewingSecretKey: null,
|
|
4279
|
+
viewingPublicKeyPacked: null,
|
|
4280
|
+
ownerPk: null
|
|
4281
|
+
};
|
|
3276
4282
|
}
|
|
3277
4283
|
/** Returns true if a spending key has been loaded. */
|
|
3278
4284
|
isLoaded() {
|
|
@@ -3285,12 +4291,29 @@ var PrivacyKeyManager = class {
|
|
|
3285
4291
|
}
|
|
3286
4292
|
return this._state.spendingKey;
|
|
3287
4293
|
}
|
|
3288
|
-
/**
|
|
3289
|
-
|
|
3290
|
-
|
|
4294
|
+
/**
|
|
4295
|
+
* Returns the 32-byte viewing secret key (ivsk).
|
|
4296
|
+
* Used internally for decrypting received notes during rescan.
|
|
4297
|
+
* SECURITY: never expose this in addresses or network requests.
|
|
4298
|
+
* Throws if not loaded.
|
|
4299
|
+
*/
|
|
4300
|
+
getViewingSecretKey() {
|
|
4301
|
+
if (this._state.viewingSecretKey === null) {
|
|
4302
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
4303
|
+
}
|
|
4304
|
+
return this._state.viewingSecretKey;
|
|
4305
|
+
}
|
|
4306
|
+
/**
|
|
4307
|
+
* Returns the 32-byte LE-encoded packed BJJ viewing public key (ivk).
|
|
4308
|
+
* This is the component embedded in the privacy address and passed to senders
|
|
4309
|
+
* so they can encrypt memos only the recipient can decrypt.
|
|
4310
|
+
* Throws if not loaded.
|
|
4311
|
+
*/
|
|
4312
|
+
getViewingPublicKeyPacked() {
|
|
4313
|
+
if (this._state.viewingPublicKeyPacked === null) {
|
|
3291
4314
|
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
3292
4315
|
}
|
|
3293
|
-
return this._state.
|
|
4316
|
+
return this._state.viewingPublicKeyPacked;
|
|
3294
4317
|
}
|
|
3295
4318
|
/** Returns the BabyJubJub owner public key (x-coordinate). Throws if not loaded. */
|
|
3296
4319
|
getOwnerPk() {
|
|
@@ -3303,24 +4326,86 @@ var PrivacyKeyManager = class {
|
|
|
3303
4326
|
getSpendingKeyBytes() {
|
|
3304
4327
|
return bigintTo32Le(this.getSpendingKey());
|
|
3305
4328
|
}
|
|
3306
|
-
/**
|
|
4329
|
+
/**
|
|
4330
|
+
* Returns the 32-byte master key bytes (pre-modulus HKDF output).
|
|
4331
|
+
* Used to derive the stable vault AES key. Throws if not loaded.
|
|
4332
|
+
*/
|
|
4333
|
+
getMasterBytes() {
|
|
4334
|
+
if (this._state.masterBytes === null) {
|
|
4335
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
4336
|
+
}
|
|
4337
|
+
return this._state.masterBytes;
|
|
4338
|
+
}
|
|
4339
|
+
/**
|
|
4340
|
+
* Exports the master key bytes as a "mk:0x{hex}" string.
|
|
4341
|
+
* Storing masterBytes (not the sk scalar) ensures the vault key and
|
|
4342
|
+
* rescan can always be reconstructed regardless of any future modulus change.
|
|
4343
|
+
* Throws if not loaded.
|
|
4344
|
+
*/
|
|
3307
4345
|
exportHex() {
|
|
3308
|
-
|
|
4346
|
+
const mb = this.getMasterBytes();
|
|
4347
|
+
return "mk:0x" + Array.from(mb, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4348
|
+
}
|
|
4349
|
+
/**
|
|
4350
|
+
* Exports a shareable privacy address encoding the owner public key and
|
|
4351
|
+
* viewing PUBLIC key of the currently loaded identity.
|
|
4352
|
+
*
|
|
4353
|
+
* Format: `orbpriv1:{ownerPk_hex}:{viewingPublicKey_hex}`
|
|
4354
|
+
*
|
|
4355
|
+
* The recipient uses this address so the sender can:
|
|
4356
|
+
* 1. Embed `ownerPk` in the note commitment (Poseidon4 input).
|
|
4357
|
+
* 2. Encrypt the memo via ECDH with the recipient's `viewingPublicKey`.
|
|
4358
|
+
*
|
|
4359
|
+
* SECURITY: Only the viewing PUBLIC key is embedded — the viewing secret key
|
|
4360
|
+
* (used for decryption) is never exported. Holders of this address cannot
|
|
4361
|
+
* decrypt the recipient's notes.
|
|
4362
|
+
*
|
|
4363
|
+
* Throws if no key is loaded.
|
|
4364
|
+
*/
|
|
4365
|
+
encodePrivacyAddress() {
|
|
4366
|
+
const ownerPk = this.getOwnerPk();
|
|
4367
|
+
const ivkPacked = this.getViewingPublicKeyPacked();
|
|
4368
|
+
const ownerPkHex = "0x" + ownerPk.toString(16).padStart(64, "0");
|
|
4369
|
+
const ivkHex = "0x" + Array.from(ivkPacked, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4370
|
+
return `orbpriv1:${ownerPkHex}:${ivkHex}`;
|
|
4371
|
+
}
|
|
4372
|
+
/**
|
|
4373
|
+
* Decode a privacy address of the form `orbpriv1:{ownerPk_hex}:{viewingPublicKey_hex}`.
|
|
4374
|
+
* Returns `{ ownerPkHex, viewingPublicKeyHex }` on success, or `null` if the input
|
|
4375
|
+
* does not match the expected format.
|
|
4376
|
+
*/
|
|
4377
|
+
static decodePrivacyAddress(address) {
|
|
4378
|
+
if (!address.startsWith("orbpriv1:")) return null;
|
|
4379
|
+
const parts = address.split(":");
|
|
4380
|
+
if (parts.length !== 3) return null;
|
|
4381
|
+
const ownerPkHex = parts[1];
|
|
4382
|
+
const viewingPublicKeyHex = parts[2];
|
|
4383
|
+
if (!ownerPkHex || !viewingPublicKeyHex) return null;
|
|
4384
|
+
return { ownerPkHex, viewingPublicKeyHex };
|
|
3309
4385
|
}
|
|
3310
4386
|
/**
|
|
3311
|
-
* Load
|
|
3312
|
-
*
|
|
4387
|
+
* Load keys from a cached "mk:0x{masterBytes_hex}" string produced by exportHex().
|
|
4388
|
+
* Throws if the format is invalid or masterBytes length is not 32 bytes.
|
|
3313
4389
|
*/
|
|
3314
4390
|
async importFromHex(hex) {
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
4391
|
+
if (!hex.startsWith("mk:")) {
|
|
4392
|
+
throw new Error(
|
|
4393
|
+
'PrivacyKeyManager: invalid cache format. Expected "mk:0x{masterBytes_hex}".'
|
|
4394
|
+
);
|
|
3318
4395
|
}
|
|
3319
|
-
|
|
4396
|
+
const raw = hex.slice(3);
|
|
4397
|
+
const h = raw.startsWith("0x") ? raw.slice(2) : raw;
|
|
4398
|
+
const masterBytes = new Uint8Array((h.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
|
|
4399
|
+
if (masterBytes.length !== 32) {
|
|
4400
|
+
throw new Error("PrivacyKeyManager: invalid master bytes \u2014 expected 32 bytes.");
|
|
4401
|
+
}
|
|
4402
|
+
const masterBigint = BigInt("0x" + h);
|
|
4403
|
+
const sk = masterBigint % BABYJUB_SUBORDER || 1n;
|
|
4404
|
+
await this.load(sk, masterBytes);
|
|
3320
4405
|
}
|
|
3321
4406
|
};
|
|
3322
4407
|
|
|
3323
|
-
// src/
|
|
4408
|
+
// src/vault/VaultJson.ts
|
|
3324
4409
|
function vaultReplacer(_key, value) {
|
|
3325
4410
|
if (typeof value === "bigint") return { __bigint: value.toString() };
|
|
3326
4411
|
return value;
|
|
@@ -3331,16 +4416,28 @@ function vaultReviver(_key, value) {
|
|
|
3331
4416
|
}
|
|
3332
4417
|
return value;
|
|
3333
4418
|
}
|
|
4419
|
+
|
|
4420
|
+
// src/utils/encoding.ts
|
|
4421
|
+
function toBase64(buf) {
|
|
4422
|
+
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
4423
|
+
let str = "";
|
|
4424
|
+
for (const b of bytes) str += String.fromCharCode(b);
|
|
4425
|
+
return btoa(str);
|
|
4426
|
+
}
|
|
4427
|
+
function fromBase64(b64) {
|
|
4428
|
+
const bin = atob(b64);
|
|
4429
|
+
const out = new Uint8Array(bin.length);
|
|
4430
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
4431
|
+
return out;
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
// src/vault/VaultCrypto.ts
|
|
3334
4435
|
var VAULT_KEY_INFO = new TextEncoder().encode("orbinum-vault-key-v1");
|
|
3335
4436
|
var IV_BYTES = 12;
|
|
3336
|
-
async function deriveVaultKey(
|
|
3337
|
-
const keyMaterial = await crypto.subtle.importKey(
|
|
3338
|
-
"
|
|
3339
|
-
|
|
3340
|
-
"HKDF",
|
|
3341
|
-
false,
|
|
3342
|
-
["deriveKey"]
|
|
3343
|
-
);
|
|
4437
|
+
async function deriveVaultKey(masterBytes) {
|
|
4438
|
+
const keyMaterial = await crypto.subtle.importKey("raw", masterBytes.slice(0), "HKDF", false, [
|
|
4439
|
+
"deriveKey"
|
|
4440
|
+
]);
|
|
3344
4441
|
return crypto.subtle.deriveKey(
|
|
3345
4442
|
{
|
|
3346
4443
|
name: "HKDF",
|
|
@@ -3369,6 +4466,167 @@ async function decryptJson(key, iv, ciphertext) {
|
|
|
3369
4466
|
return JSON.parse(new TextDecoder().decode(plainBuf), vaultReviver);
|
|
3370
4467
|
}
|
|
3371
4468
|
|
|
4469
|
+
// src/vault/errors.ts
|
|
4470
|
+
var VaultLockedError = class extends Error {
|
|
4471
|
+
constructor(message = "Vault is locked. Connect your wallet to unlock it.") {
|
|
4472
|
+
super(message);
|
|
4473
|
+
this.name = "VaultLockedError";
|
|
4474
|
+
}
|
|
4475
|
+
};
|
|
4476
|
+
|
|
4477
|
+
// src/vault/noteOps.ts
|
|
4478
|
+
function applyNoteStatus(note, status) {
|
|
4479
|
+
return {
|
|
4480
|
+
...note,
|
|
4481
|
+
spent: status?.spent ?? note.spent ?? false,
|
|
4482
|
+
spentAt: status?.spentAt ?? note.spentAt ?? null
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
async function encryptNote(key, note) {
|
|
4486
|
+
const { iv, ciphertext } = await encryptJson(key, note);
|
|
4487
|
+
return {
|
|
4488
|
+
commitmentHex: note.commitmentHex,
|
|
4489
|
+
iv,
|
|
4490
|
+
ciphertext,
|
|
4491
|
+
nullifierHex: note.nullifierHex,
|
|
4492
|
+
assetId: note.assetId.toString(),
|
|
4493
|
+
spent: note.spent,
|
|
4494
|
+
spentAt: note.spentAt,
|
|
4495
|
+
updatedAt: Date.now()
|
|
4496
|
+
};
|
|
4497
|
+
}
|
|
4498
|
+
async function decryptNoteRecord(key, rec) {
|
|
4499
|
+
const note = await decryptJson(key, rec.iv, rec.ciphertext);
|
|
4500
|
+
return applyNoteStatus(note, {
|
|
4501
|
+
...rec.spent !== void 0 && { spent: rec.spent },
|
|
4502
|
+
spentAt: rec.spentAt ?? null
|
|
4503
|
+
});
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4506
|
+
// src/proof-generator/unshield.ts
|
|
4507
|
+
var import_proof_generator2 = require("@orbinum/proof-generator");
|
|
4508
|
+
var import_utils3 = require("@noble/ciphers/utils.js");
|
|
4509
|
+
var import_baby_jubjub7 = require("@zk-kit/baby-jubjub");
|
|
4510
|
+
var import_poseidon_lite4 = require("poseidon-lite");
|
|
4511
|
+
|
|
4512
|
+
// src/proof-generator/merkle.ts
|
|
4513
|
+
function merkleProofToCircuit(siblings, leafIndex) {
|
|
4514
|
+
const elements = siblings.map((h) => leHexToBigint(h).toString());
|
|
4515
|
+
const depth = siblings.length;
|
|
4516
|
+
const indices = computePathIndices(leafIndex, depth).map(String);
|
|
4517
|
+
return { elements, indices };
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
// src/proof-generator/unshield.ts
|
|
4521
|
+
async function generateUnshieldProof(inputs, options = {}) {
|
|
4522
|
+
const { elements, indices } = merkleProofToCircuit(inputs.pathSiblings, inputs.leafIndex);
|
|
4523
|
+
const fee = inputs.fee ?? 0n;
|
|
4524
|
+
const changeValue = inputs.changeValue ?? 0n;
|
|
4525
|
+
const noteValue = inputs.amount + fee + changeValue;
|
|
4526
|
+
if (inputs.amount <= 0n) {
|
|
4527
|
+
throw new Error("Unshield amount must be greater than zero.");
|
|
4528
|
+
}
|
|
4529
|
+
if (changeValue < 0n) {
|
|
4530
|
+
throw new Error("changeValue must be >= 0.");
|
|
4531
|
+
}
|
|
4532
|
+
const changeOwnerPubkey = inputs.changeOwnerPubkey ?? (0, import_baby_jubjub7.mulPointEscalar)(import_baby_jubjub7.Base8, inputs.spendingKey)[0];
|
|
4533
|
+
const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE((0, import_utils3.randomBytes)(32)) : 0n);
|
|
4534
|
+
const changeCommitment = changeValue > 0n ? (0, import_poseidon_lite4.poseidon4)([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
|
|
4535
|
+
const circuitInputs = {
|
|
4536
|
+
merkle_root: leHexToBigint(inputs.merkleRoot).toString(),
|
|
4537
|
+
nullifier: inputs.nullifier.toString(),
|
|
4538
|
+
amount: inputs.amount.toString(),
|
|
4539
|
+
recipient: inputs.recipient.toString(),
|
|
4540
|
+
asset_id: inputs.assetId.toString(),
|
|
4541
|
+
fee: fee.toString(),
|
|
4542
|
+
change_commitment: changeCommitment.toString(),
|
|
4543
|
+
note_value: noteValue.toString(),
|
|
4544
|
+
note_asset_id: inputs.assetId.toString(),
|
|
4545
|
+
note_blinding: inputs.blinding.toString(),
|
|
4546
|
+
spending_key: inputs.spendingKey.toString(),
|
|
4547
|
+
path_elements: elements,
|
|
4548
|
+
path_indices: indices,
|
|
4549
|
+
change_value: changeValue.toString(),
|
|
4550
|
+
change_blinding: changeBlinding.toString(),
|
|
4551
|
+
change_owner_pubkey: changeOwnerPubkey.toString()
|
|
4552
|
+
};
|
|
4553
|
+
const provider = options.provider ?? new import_proof_generator2.WebArtifactProvider();
|
|
4554
|
+
const opts = { provider };
|
|
4555
|
+
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4556
|
+
const proofResult = await (0, import_proof_generator2.generateProof)(import_proof_generator2.CircuitType.Unshield, circuitInputs, opts);
|
|
4557
|
+
return { ...proofResult, changeCommitment, changeValue, changeBlinding, changeOwnerPubkey };
|
|
4558
|
+
}
|
|
4559
|
+
|
|
4560
|
+
// src/proof-generator/transfer.ts
|
|
4561
|
+
var import_proof_generator3 = require("@orbinum/proof-generator");
|
|
4562
|
+
async function generateTransferProof(params, options = {}) {
|
|
4563
|
+
const root = leHexToBigint(params.merkleRoot).toString();
|
|
4564
|
+
const [i0, i1] = params.inputs;
|
|
4565
|
+
const [o0, o1] = params.outputs;
|
|
4566
|
+
const fee = params.fee ?? 0n;
|
|
4567
|
+
const path0 = merkleProofToCircuit(i0.pathSiblings, i0.leafIndex);
|
|
4568
|
+
const path1 = merkleProofToCircuit(i1.pathSiblings, i1.leafIndex);
|
|
4569
|
+
const circuitInputs = {
|
|
4570
|
+
merkle_root: root,
|
|
4571
|
+
nullifiers: [i0.nullifier.toString(), i1.nullifier.toString()],
|
|
4572
|
+
commitments: [o0.commitment.toString(), o1.commitment.toString()],
|
|
4573
|
+
asset_id: i0.assetId.toString(),
|
|
4574
|
+
fee: fee.toString(),
|
|
4575
|
+
input_values: [i0.value.toString(), i1.value.toString()],
|
|
4576
|
+
input_asset_ids: [i0.assetId.toString(), i1.assetId.toString()],
|
|
4577
|
+
input_blindings: [i0.blinding.toString(), i1.blinding.toString()],
|
|
4578
|
+
spending_keys: [i0.spendingKey.toString(), i1.spendingKey.toString()],
|
|
4579
|
+
input_path_elements: [path0.elements, path1.elements],
|
|
4580
|
+
input_path_indices: [path0.indices, path1.indices],
|
|
4581
|
+
output_values: [o0.value.toString(), o1.value.toString()],
|
|
4582
|
+
output_asset_ids: [o0.assetId.toString(), o1.assetId.toString()],
|
|
4583
|
+
output_owner_pubkeys: [o0.ownerPk.toString(), o1.ownerPk.toString()],
|
|
4584
|
+
output_blindings: [o0.blinding.toString(), o1.blinding.toString()]
|
|
4585
|
+
};
|
|
4586
|
+
const provider = options.provider ?? new import_proof_generator3.WebArtifactProvider();
|
|
4587
|
+
const opts = { provider };
|
|
4588
|
+
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4589
|
+
return (0, import_proof_generator3.generateProof)(import_proof_generator3.CircuitType.Transfer, circuitInputs, opts);
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
// src/proof-generator/fee-claim.ts
|
|
4593
|
+
var import_proof_generator4 = require("@orbinum/proof-generator");
|
|
4594
|
+
function hexSignalToBytes(hex) {
|
|
4595
|
+
const clean = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
|
|
4596
|
+
const padded = clean.padStart(64, "0");
|
|
4597
|
+
const bytes = new Uint8Array(32);
|
|
4598
|
+
for (let i = 0; i < 32; i++) {
|
|
4599
|
+
bytes[i] = parseInt(padded.slice(i * 2, i * 2 + 2), 16);
|
|
4600
|
+
}
|
|
4601
|
+
return bytes;
|
|
4602
|
+
}
|
|
4603
|
+
async function generateFeeClaimProof(inputs, options = {}) {
|
|
4604
|
+
const result = await (0, import_proof_generator4.generateDisclosureProof)(
|
|
4605
|
+
inputs.amount,
|
|
4606
|
+
inputs.ownerPubkey,
|
|
4607
|
+
inputs.blinding,
|
|
4608
|
+
inputs.assetId,
|
|
4609
|
+
inputs.commitment,
|
|
4610
|
+
// Baby Jubjub base point G — placeholder, no real auditor for fee claiming
|
|
4611
|
+
5299619240641551281634865583518297030282874472190772894086521144482721001553n,
|
|
4612
|
+
16950150798460657717958625567821834550301663161624707787222815936182638968203n,
|
|
4613
|
+
1n,
|
|
4614
|
+
// r — placeholder ephemeral scalar (NOT cryptographically secure)
|
|
4615
|
+
{ discloseValue: true, discloseAssetId: true, discloseOwner: false },
|
|
4616
|
+
options
|
|
4617
|
+
);
|
|
4618
|
+
const [, , sigEncValue, sigEncAssetId, sigEncOwnerHash, sigCommitment] = result.publicSignals.map(hexSignalToBytes);
|
|
4619
|
+
const compact = new Uint8Array(76);
|
|
4620
|
+
compact.set(sigCommitment);
|
|
4621
|
+
compact.set(sigEncValue.subarray(0, 8), 32);
|
|
4622
|
+
compact.set(sigEncAssetId.subarray(0, 4), 40);
|
|
4623
|
+
compact.set(sigEncOwnerHash, 44);
|
|
4624
|
+
return {
|
|
4625
|
+
proof: result.proof,
|
|
4626
|
+
publicSignals: Array.from(compact)
|
|
4627
|
+
};
|
|
4628
|
+
}
|
|
4629
|
+
|
|
3372
4630
|
// src/account-mapping/types/index.ts
|
|
3373
4631
|
var SignatureScheme = {
|
|
3374
4632
|
Eip191: "Eip191",
|
|
@@ -3385,7 +4643,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
3385
4643
|
if (!fnSig) return null;
|
|
3386
4644
|
if (fnSig.startsWith("registerAlias")) {
|
|
3387
4645
|
try {
|
|
3388
|
-
const data =
|
|
4646
|
+
const data = fromHex(input.slice(10));
|
|
3389
4647
|
const alias = decodeString(data, 0);
|
|
3390
4648
|
return { fnSig, args: { alias } };
|
|
3391
4649
|
} catch {
|
|
@@ -3394,37 +4652,43 @@ function decodePrecompileCalldata(address, input) {
|
|
|
3394
4652
|
}
|
|
3395
4653
|
if (fnSig.startsWith("shield(")) {
|
|
3396
4654
|
try {
|
|
3397
|
-
const data =
|
|
4655
|
+
const data = fromHex(input.slice(10));
|
|
3398
4656
|
const assetId = decodeUint(data, 0);
|
|
3399
|
-
const
|
|
3400
|
-
|
|
3401
|
-
return { fnSig, args: { assetId, amount, commitment } };
|
|
4657
|
+
const commitment = toHex(data.slice(32, 64));
|
|
4658
|
+
return { fnSig, args: { assetId, commitment } };
|
|
3402
4659
|
} catch {
|
|
3403
4660
|
return { fnSig, args: {} };
|
|
3404
4661
|
}
|
|
3405
4662
|
}
|
|
3406
4663
|
if (fnSig.startsWith("unshield(")) {
|
|
3407
4664
|
try {
|
|
3408
|
-
const data =
|
|
4665
|
+
const data = fromHex(input.slice(10));
|
|
3409
4666
|
const root = toHex(data.slice(32, 64));
|
|
3410
4667
|
const nullifier = toHex(data.slice(64, 96));
|
|
3411
4668
|
const assetId = decodeUint(data, 96);
|
|
3412
4669
|
const amount = decodeUint(data, 128);
|
|
3413
4670
|
const recipient = toHex(data.slice(160, 192));
|
|
3414
|
-
|
|
4671
|
+
const fee = decodeUint(data, 192);
|
|
4672
|
+
const changeCommitment = toHex(data.slice(224, 256));
|
|
4673
|
+
return {
|
|
4674
|
+
fnSig,
|
|
4675
|
+
args: { root, nullifier, assetId, amount, recipient, fee, changeCommitment }
|
|
4676
|
+
};
|
|
3415
4677
|
} catch {
|
|
3416
4678
|
return { fnSig, args: {} };
|
|
3417
4679
|
}
|
|
3418
4680
|
}
|
|
3419
4681
|
if (fnSig.startsWith("privateTransfer(")) {
|
|
3420
4682
|
try {
|
|
3421
|
-
const data =
|
|
4683
|
+
const data = fromHex(input.slice(10));
|
|
3422
4684
|
const root = toHex(data.slice(32, 64));
|
|
3423
4685
|
const nullOffset = Number(decodeUint(data, 64));
|
|
3424
4686
|
const commOffset = Number(decodeUint(data, 96));
|
|
3425
4687
|
const nullifiers = Number(decodeUint(data, nullOffset));
|
|
3426
4688
|
const commitments = Number(decodeUint(data, commOffset));
|
|
3427
|
-
|
|
4689
|
+
const assetId = decodeUint(data, 160);
|
|
4690
|
+
const fee = decodeUint(data, 192);
|
|
4691
|
+
return { fnSig, args: { root, nullifiers, commitments, assetId, fee } };
|
|
3428
4692
|
} catch {
|
|
3429
4693
|
return { fnSig, args: {} };
|
|
3430
4694
|
}
|
|
@@ -3637,7 +4901,11 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3637
4901
|
if (m_norm === "requestdisclosure") {
|
|
3638
4902
|
return {
|
|
3639
4903
|
target: get(0, "target"),
|
|
3640
|
-
|
|
4904
|
+
commitment: get(1, "commitment"),
|
|
4905
|
+
required_fields: get(2, "required_fields"),
|
|
4906
|
+
reason: get(3, "reason"),
|
|
4907
|
+
auditor_bjj_pk_x: get(4, "auditor_bjj_pk_x"),
|
|
4908
|
+
auditor_bjj_pk_y: get(5, "auditor_bjj_pk_y")
|
|
3641
4909
|
};
|
|
3642
4910
|
}
|
|
3643
4911
|
if (m_norm === "disclose") {
|
|
@@ -3651,7 +4919,8 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3651
4919
|
if (m_norm === "rejectdisclosure") {
|
|
3652
4920
|
return {
|
|
3653
4921
|
auditor: get(0, "auditor"),
|
|
3654
|
-
|
|
4922
|
+
commitment: get(1, "commitment"),
|
|
4923
|
+
reason: get(2, "reason")
|
|
3655
4924
|
};
|
|
3656
4925
|
}
|
|
3657
4926
|
if (m_norm === "registerasset") {
|
|
@@ -3674,7 +4943,8 @@ function mapExtrinsicArgs(section, method, args) {
|
|
|
3674
4943
|
if (m_norm === "pruneexpiredrequest") {
|
|
3675
4944
|
return {
|
|
3676
4945
|
target: get(0, "target"),
|
|
3677
|
-
auditor: get(1, "auditor")
|
|
4946
|
+
auditor: get(1, "auditor"),
|
|
4947
|
+
commitment: get(2, "commitment")
|
|
3678
4948
|
};
|
|
3679
4949
|
}
|
|
3680
4950
|
if (m_norm === "revokedisclosurerecord") {
|
|
@@ -3911,29 +5181,35 @@ function mapZkEventData(method, data) {
|
|
|
3911
5181
|
}
|
|
3912
5182
|
if (m_norm === "disclosed") {
|
|
3913
5183
|
return {
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
5184
|
+
target: get(0, "target"),
|
|
5185
|
+
auditor: get(1, "auditor"),
|
|
5186
|
+
commitment: get(2, "commitment"),
|
|
5187
|
+
signals: get(3, "signals")
|
|
3917
5188
|
};
|
|
3918
5189
|
}
|
|
3919
5190
|
if (m_norm === "disclosurerequested") {
|
|
3920
5191
|
return {
|
|
3921
5192
|
target: get(0, "target"),
|
|
3922
5193
|
auditor: get(1, "auditor"),
|
|
3923
|
-
|
|
5194
|
+
commitment: get(2, "commitment"),
|
|
5195
|
+
required_fields: get(3, "required_fields"),
|
|
5196
|
+
auditor_bjj_pk_x: get(4, "auditor_bjj_pk_x"),
|
|
5197
|
+
auditor_bjj_pk_y: get(5, "auditor_bjj_pk_y")
|
|
3924
5198
|
};
|
|
3925
5199
|
}
|
|
3926
5200
|
if (m_norm === "disclosurerejected") {
|
|
3927
5201
|
return {
|
|
3928
5202
|
target: get(0, "target"),
|
|
3929
5203
|
auditor: get(1, "auditor"),
|
|
3930
|
-
|
|
5204
|
+
commitment: get(2, "commitment"),
|
|
5205
|
+
reason: get(3, "reason")
|
|
3931
5206
|
};
|
|
3932
5207
|
}
|
|
3933
5208
|
if (m_norm === "disclosurerequestexpired") {
|
|
3934
5209
|
return {
|
|
3935
5210
|
target: get(0, "target"),
|
|
3936
|
-
auditor: get(1, "auditor")
|
|
5211
|
+
auditor: get(1, "auditor"),
|
|
5212
|
+
commitment: get(2, "commitment")
|
|
3937
5213
|
};
|
|
3938
5214
|
}
|
|
3939
5215
|
if (m_norm === "disclosurerecordrevoked") {
|
|
@@ -4185,9 +5461,13 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4185
5461
|
AccountId,
|
|
4186
5462
|
AccountMappingModule,
|
|
4187
5463
|
AccountMappingPrecompile,
|
|
5464
|
+
BABYJUB_SUBORDER,
|
|
5465
|
+
BN254_R,
|
|
4188
5466
|
Blake2256,
|
|
4189
5467
|
CircuitId,
|
|
5468
|
+
CircuitType,
|
|
4190
5469
|
CryptoPrecompiles,
|
|
5470
|
+
ENCRYPTED_MEMO_SIZE,
|
|
4191
5471
|
EncryptedMemo,
|
|
4192
5472
|
EvmClient,
|
|
4193
5473
|
EvmExplorer,
|
|
@@ -4200,30 +5480,45 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4200
5480
|
PRECOMPILE_ADDR,
|
|
4201
5481
|
PrivacyKeyManager,
|
|
4202
5482
|
PrivacyModule,
|
|
5483
|
+
RelayerStatusModule,
|
|
4203
5484
|
SLIP0044_NAMESPACE,
|
|
4204
5485
|
ShieldedPoolModule,
|
|
4205
5486
|
ShieldedPoolPrecompile,
|
|
4206
5487
|
SignatureScheme,
|
|
4207
5488
|
Storage,
|
|
4208
5489
|
SubstrateClient,
|
|
5490
|
+
VaultLockedError,
|
|
5491
|
+
WebArtifactProvider,
|
|
4209
5492
|
ZkVerifierModule,
|
|
4210
5493
|
accountIdHexToSs58,
|
|
4211
5494
|
addressToAccountIdHex,
|
|
5495
|
+
applyNoteStatus,
|
|
4212
5496
|
base58,
|
|
4213
5497
|
bigintTo32Be,
|
|
4214
5498
|
bigintTo32Le,
|
|
4215
5499
|
bigintTo32LeArr,
|
|
5500
|
+
buildDisclosurePublicSignals,
|
|
5501
|
+
buildDummyTransferInput,
|
|
4216
5502
|
bytesToBigintLE,
|
|
5503
|
+
computeNullifier,
|
|
4217
5504
|
computePathIndices,
|
|
4218
5505
|
connectInjectedExtension,
|
|
4219
5506
|
decodePrecompileCalldata,
|
|
5507
|
+
decryptDisclosureSignals,
|
|
4220
5508
|
decryptJson,
|
|
5509
|
+
decryptNoteRecord,
|
|
5510
|
+
deriveBabyJubjubKeypair,
|
|
5511
|
+
deriveMasterKeyBytes,
|
|
4221
5512
|
deriveOwnerPk,
|
|
4222
5513
|
deriveSpendingKeyFromSignature,
|
|
4223
5514
|
deriveSpendingKeyMessage,
|
|
5515
|
+
deriveStealthOwnerPk,
|
|
5516
|
+
deriveStealthSk,
|
|
4224
5517
|
deriveVaultKey,
|
|
4225
|
-
|
|
5518
|
+
deriveViewingPublicKey,
|
|
5519
|
+
deriveViewingSecretKey,
|
|
4226
5520
|
encryptJson,
|
|
5521
|
+
encryptNote,
|
|
4227
5522
|
ensureHexPrefix,
|
|
4228
5523
|
evmAddressToAccountId,
|
|
4229
5524
|
evmToImplicitSubstrate,
|
|
@@ -4233,6 +5528,10 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4233
5528
|
formatORB,
|
|
4234
5529
|
fromBase64,
|
|
4235
5530
|
fromHex,
|
|
5531
|
+
generateDisclosureProof,
|
|
5532
|
+
generateFeeClaimProof,
|
|
5533
|
+
generateTransferProof,
|
|
5534
|
+
generateUnshieldProof,
|
|
4236
5535
|
getInjectedExtensions,
|
|
4237
5536
|
getPolkadotSigner,
|
|
4238
5537
|
getPolkadotSignerFromPjs,
|
|
@@ -4250,6 +5549,9 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4250
5549
|
mapExtrinsicArgs,
|
|
4251
5550
|
mapZkEventData,
|
|
4252
5551
|
normalizeEvmAddress,
|
|
5552
|
+
randomBlinding,
|
|
5553
|
+
recoverOwnerPkPoint,
|
|
5554
|
+
selectNotes,
|
|
4253
5555
|
shortHash,
|
|
4254
5556
|
substrateSs58ToAccountIdHex,
|
|
4255
5557
|
substrateToEvm,
|
|
@@ -4258,6 +5560,7 @@ var import_polkadot_api4 = require("polkadot-api");
|
|
|
4258
5560
|
toTxResult,
|
|
4259
5561
|
truncateMiddle,
|
|
4260
5562
|
tryDecryptNote,
|
|
5563
|
+
tryDecryptNoteVerbose,
|
|
4261
5564
|
u128,
|
|
4262
5565
|
u64,
|
|
4263
5566
|
vaultReplacer,
|