@orbinum/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2397 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AccountMappingModule: () => AccountMappingModule,
24
+ AccountMappingPrecompile: () => AccountMappingPrecompile,
25
+ ChainModule: () => ChainModule,
26
+ CryptoPrecompiles: () => CryptoPrecompiles,
27
+ EncryptedMemo: () => EncryptedMemo,
28
+ EvmClient: () => EvmClient,
29
+ MerkleModule: () => MerkleModule,
30
+ NoteBuilder: () => NoteBuilder,
31
+ OrbinumClient: () => OrbinumClient,
32
+ PRECOMPILE_ADDR: () => PRECOMPILE_ADDR,
33
+ PrivacyKeyManager: () => PrivacyKeyManager,
34
+ SLIP0044_NAMESPACE: () => SLIP0044_NAMESPACE,
35
+ ShieldedPoolModule: () => ShieldedPoolModule,
36
+ ShieldedPoolPrecompile: () => ShieldedPoolPrecompile,
37
+ SubstrateClient: () => SubstrateClient,
38
+ accountIdHexToSs58: () => accountIdHexToSs58,
39
+ addressToAccountIdHex: () => addressToAccountIdHex,
40
+ bigintTo32Be: () => bigintTo32Be,
41
+ bigintTo32Le: () => bigintTo32Le,
42
+ bigintTo32LeArr: () => bigintTo32LeArr,
43
+ bytesToBigintLE: () => bytesToBigintLE,
44
+ computePathIndices: () => computePathIndices,
45
+ decryptJson: () => decryptJson,
46
+ deriveOwnerPk: () => deriveOwnerPk,
47
+ deriveSpendingKeyFromSignature: () => deriveSpendingKeyFromSignature,
48
+ deriveSpendingKeyMessage: () => deriveSpendingKeyMessage,
49
+ deriveVaultKey: () => deriveVaultKey,
50
+ deriveViewingKey: () => deriveViewingKey,
51
+ encryptJson: () => encryptJson,
52
+ ensureHexPrefix: () => ensureHexPrefix,
53
+ evmAddressToAccountId: () => evmAddressToAccountId,
54
+ evmToImplicitSubstrate: () => evmToImplicitSubstrate,
55
+ evmToSubstrate: () => evmToSubstrate,
56
+ fromHex: () => fromHex,
57
+ getPolkadotSigner: () => import_signer.getPolkadotSigner,
58
+ getPolkadotSignerFromPjs: () => import_pjs_signer.getPolkadotSignerFromPjs,
59
+ implicitSubstrateToEvm: () => implicitSubstrateToEvm,
60
+ isEvmAddress: () => isEvmAddress,
61
+ isImplicitEvmAccount: () => isImplicitEvmAccount,
62
+ isSs58: () => isSs58,
63
+ isSubstrateAddress: () => isSubstrateAddress,
64
+ isUnifiedAddress: () => isUnifiedAddress,
65
+ leHexToBigint: () => leHexToBigint,
66
+ normalizeEvmAddress: () => normalizeEvmAddress,
67
+ substrateSs58ToAccountIdHex: () => substrateSs58ToAccountIdHex,
68
+ substrateToEvm: () => substrateToEvm,
69
+ toHex: () => toHex,
70
+ tryDecryptNote: () => tryDecryptNote,
71
+ vaultReplacer: () => vaultReplacer,
72
+ vaultReviver: () => vaultReviver
73
+ });
74
+ module.exports = __toCommonJS(index_exports);
75
+
76
+ // src/substrate/SubstrateClient.ts
77
+ var import_polkadot_api = require("polkadot-api");
78
+ var import_ws_provider = require("polkadot-api/ws-provider");
79
+ var SubstrateClient = class _SubstrateClient {
80
+ constructor(_papi) {
81
+ this._papi = _papi;
82
+ }
83
+ /**
84
+ * Connects to the Orbinum node via WebSocket.
85
+ * Throws if the node does not respond within `timeoutMs`.
86
+ */
87
+ static async connect(wsUrl, timeoutMs = 15e3) {
88
+ const provider = (0, import_ws_provider.getWsProvider)(wsUrl);
89
+ const papi = (0, import_polkadot_api.createClient)(provider);
90
+ await Promise.race([
91
+ papi._request("system_name", []),
92
+ new Promise(
93
+ (_, reject) => setTimeout(
94
+ () => reject(new Error(`Connection timeout (${timeoutMs}ms) to ${wsUrl}`)),
95
+ timeoutMs
96
+ )
97
+ )
98
+ ]);
99
+ return new _SubstrateClient(papi);
100
+ }
101
+ /**
102
+ * Performs a raw JSON-RPC request. Use this for custom Orbinum RPCs
103
+ * (shieldedPool_*, accountMapping_*, privacy_*, etc.).
104
+ */
105
+ async request(method, params = []) {
106
+ return this._papi._request(method, params);
107
+ }
108
+ /**
109
+ * Returns the underlying PolkadotClient instance.
110
+ * Use for block subscriptions (`blocks$`), raw metadata access, and advanced SCALE operations.
111
+ */
112
+ get polkadotClient() {
113
+ return this._papi;
114
+ }
115
+ /**
116
+ * Returns the PAPI UnsafeApi for dynamic, metadata-driven transaction building.
117
+ * The first access triggers a metadata fetch from the node.
118
+ *
119
+ * Usage:
120
+ * ```ts
121
+ * const tx = client.unsafe.tx.shieldedPool.shield(...);
122
+ * const result = await tx.signAndSubmit(signer);
123
+ * ```
124
+ */
125
+ get unsafe() {
126
+ return this._papi.getUnsafeApi();
127
+ }
128
+ /**
129
+ * Wraps pre-built SCALE call bytes (from protocol-core TransactionBuilder)
130
+ * into a PAPI UnsafeTransaction that can be signed and submitted.
131
+ */
132
+ async txFromCallData(callData) {
133
+ return this._papi.getUnsafeApi().txFromCallData(import_polkadot_api.Binary.fromBytes(callData));
134
+ }
135
+ /**
136
+ * Submits a pre-signed extrinsic (hex string) and waits for finalization.
137
+ */
138
+ async submit(signedHex) {
139
+ return this._papi.submit(signedHex);
140
+ }
141
+ /**
142
+ * Submits a pre-signed extrinsic and returns an Observable of tx lifecycle events.
143
+ * Events: TxSigned → TxBroadcasted → TxBestBlocksState → TxFinalized
144
+ */
145
+ submitAndWatch(signedHex) {
146
+ return this._papi.submitAndWatch(signedHex);
147
+ }
148
+ /**
149
+ * Convenience: wrap raw call bytes and sign+submit in one step.
150
+ */
151
+ async signAndSubmit(callData, signer) {
152
+ const tx = await this.txFromCallData(callData);
153
+ return tx.signAndSubmit(signer);
154
+ }
155
+ /** Closes the WebSocket connection. */
156
+ destroy() {
157
+ this._papi.destroy();
158
+ }
159
+ };
160
+
161
+ // src/utils/hex.ts
162
+ function toHex(bytes) {
163
+ return "0x" + Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
164
+ }
165
+ function fromHex(hex) {
166
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
167
+ if (clean.length % 2 !== 0) {
168
+ throw new Error(`Invalid hex string \u2014 odd length: "${hex}"`);
169
+ }
170
+ const bytes = new Uint8Array(clean.length / 2);
171
+ for (let i = 0; i < bytes.length; i++) {
172
+ const byte = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
173
+ if (isNaN(byte)) throw new Error(`Invalid hex character at position ${i * 2}`);
174
+ bytes[i] = byte;
175
+ }
176
+ return bytes;
177
+ }
178
+ function ensureHexPrefix(hex) {
179
+ return hex.startsWith("0x") ? hex : `0x${hex}`;
180
+ }
181
+ function hexToNumber(hex) {
182
+ return parseInt(hex, 16);
183
+ }
184
+ function hexToBigint(hex) {
185
+ return BigInt(hex);
186
+ }
187
+
188
+ // src/evm/EvmClient.ts
189
+ var EvmClient = class {
190
+ constructor(rpcUrl) {
191
+ this.rpcUrl = rpcUrl;
192
+ }
193
+ /**
194
+ * Performs a single JSON-RPC call.
195
+ */
196
+ async request(method, params = []) {
197
+ const res = await fetch(this.rpcUrl, {
198
+ method: "POST",
199
+ headers: { "Content-Type": "application/json" },
200
+ body: JSON.stringify({ id: 1, jsonrpc: "2.0", method, params })
201
+ });
202
+ if (!res.ok) throw new Error(`EVM HTTP ${res.status}: ${res.statusText}`);
203
+ const json = await res.json();
204
+ if (json.error) {
205
+ throw new Error(`EVM RPC [${json.error.code}]: ${json.error.message}`);
206
+ }
207
+ if (json.result === void 0 || json.result === null) {
208
+ throw new Error(`EVM RPC returned null result for method "${method}"`);
209
+ }
210
+ return json.result;
211
+ }
212
+ /**
213
+ * Performs multiple JSON-RPC calls in a single HTTP request (batch).
214
+ */
215
+ async batchRequest(calls) {
216
+ const body = calls.map((c, i) => ({
217
+ id: i + 1,
218
+ jsonrpc: "2.0",
219
+ method: c.method,
220
+ params: c.params ?? []
221
+ }));
222
+ const res = await fetch(this.rpcUrl, {
223
+ method: "POST",
224
+ headers: { "Content-Type": "application/json" },
225
+ body: JSON.stringify(body)
226
+ });
227
+ if (!res.ok) throw new Error(`EVM HTTP ${res.status}: ${res.statusText}`);
228
+ const arr = await res.json();
229
+ arr.sort((a, b) => (a.id ?? 0) - (b.id ?? 0));
230
+ return arr.map((r) => r.result ?? null);
231
+ }
232
+ // ─── Convenience wrappers ─────────────────────────────────────────────────
233
+ /** Returns the native token balance (in wei) for an EVM address. */
234
+ async getBalance(address) {
235
+ const hex = await this.request("eth_getBalance", [address, "latest"]);
236
+ return hexToBigint(hex);
237
+ }
238
+ /** Returns the latest block number. */
239
+ async getBlockNumber() {
240
+ const hex = await this.request("eth_blockNumber", []);
241
+ return hexToNumber(hex);
242
+ }
243
+ /** Returns the current chain ID. */
244
+ async getChainId() {
245
+ const hex = await this.request("eth_chainId", []);
246
+ return hexToNumber(hex);
247
+ }
248
+ /** Returns the transaction count (nonce) for an EVM address. */
249
+ async getTransactionCount(address) {
250
+ const hex = await this.request("eth_getTransactionCount", [address, "latest"]);
251
+ return hexToNumber(hex);
252
+ }
253
+ /** Returns the current gas price in wei. */
254
+ async getGasPrice() {
255
+ const hex = await this.request("eth_gasPrice", []);
256
+ return hexToBigint(hex);
257
+ }
258
+ /**
259
+ * Submits a signed raw transaction. Returns the transaction hash.
260
+ */
261
+ async sendRawTransaction(signedHex) {
262
+ return this.request("eth_sendRawTransaction", [signedHex]);
263
+ }
264
+ /**
265
+ * Executes a read-only call without creating a transaction.
266
+ */
267
+ async call(to, data, from) {
268
+ const txObj = { to, data };
269
+ if (from) txObj["from"] = from;
270
+ return this.request("eth_call", [txObj, "latest"]);
271
+ }
272
+ /**
273
+ * Estimates the gas for a transaction.
274
+ */
275
+ async estimateGas(params) {
276
+ const hex = await this.request("eth_estimateGas", [params]);
277
+ return hexToBigint(hex);
278
+ }
279
+ /**
280
+ * Returns a transaction receipt by hash, or null if not yet mined.
281
+ */
282
+ async getTransactionReceipt(txHash) {
283
+ return this.request("eth_getTransactionReceipt", [txHash]);
284
+ }
285
+ };
286
+
287
+ // src/shielded-pool/MerkleModule.ts
288
+ var MerkleModule = class {
289
+ constructor(substrate) {
290
+ this.substrate = substrate;
291
+ }
292
+ /**
293
+ * Returns the current Merkle tree state: root, number of leaves, and depth.
294
+ */
295
+ async getTreeInfo() {
296
+ const raw = await this.substrate.request(
297
+ "shieldedPool_getMerkleTreeInfo",
298
+ []
299
+ );
300
+ return {
301
+ root: raw.root,
302
+ treeSize: raw.tree_size,
303
+ depth: raw.depth
304
+ };
305
+ }
306
+ /**
307
+ * Returns the Merkle inclusion proof for a leaf at `leafIndex`.
308
+ */
309
+ async getProof(leafIndex) {
310
+ const raw = await this.substrate.request("shieldedPool_getMerkleProof", [
311
+ leafIndex
312
+ ]);
313
+ return {
314
+ root: raw.root,
315
+ leafIndex: raw.leaf_index,
316
+ siblings: raw.siblings
317
+ };
318
+ }
319
+ /**
320
+ * Returns the Merkle inclusion proof for a given commitment (0x-prefixed hex).
321
+ * Searches the tree for the commitment and returns its proof.
322
+ */
323
+ async getProofByCommitment(commitmentHex) {
324
+ const raw = await this.substrate.request("shieldedPool_getMerkleProof", [
325
+ commitmentHex
326
+ ]);
327
+ return {
328
+ root: raw.root,
329
+ leafIndex: raw.leaf_index,
330
+ siblings: raw.siblings
331
+ };
332
+ }
333
+ /**
334
+ * Returns the current Merkle root without fetching the full tree info.
335
+ */
336
+ async getRoot() {
337
+ const info = await this.getTreeInfo();
338
+ return info.root;
339
+ }
340
+ /**
341
+ * Returns an array of commitment leaves from index `from` to `to` (inclusive).
342
+ * Defaults to returning all leaves.
343
+ */
344
+ async getLeaves(from = 0, to) {
345
+ return this.substrate.request("shieldedPool_getMerkleLeaves", [from, to ?? null]);
346
+ }
347
+ };
348
+
349
+ // src/shielded-pool/ShieldedPoolModule.ts
350
+ var import_polkadot_api2 = require("polkadot-api");
351
+
352
+ // src/shielded-pool/EncryptedMemo.ts
353
+ var import_sha2 = require("@noble/hashes/sha2.js");
354
+ var import_chacha = require("@noble/ciphers/chacha.js");
355
+ var import_utils = require("@noble/ciphers/utils.js");
356
+
357
+ // src/utils/bytes.ts
358
+ function bigintTo32Le(n) {
359
+ const buf = new Uint8Array(32);
360
+ let v = n;
361
+ for (let i = 0; i < 32; i++) {
362
+ buf[i] = Number(v & 0xffn);
363
+ v >>= 8n;
364
+ }
365
+ return buf;
366
+ }
367
+ function bytesToBigintLE(bytes) {
368
+ let result = 0n;
369
+ for (let i = bytes.length - 1; i >= 0; i--) {
370
+ result = result << 8n | BigInt(bytes[i] ?? 0);
371
+ }
372
+ return result;
373
+ }
374
+ function bigintTo32Be(n) {
375
+ const buf = new Uint8Array(32);
376
+ let v = n;
377
+ for (let i = 31; i >= 0 && v > 0n; i--) {
378
+ buf[i] = Number(v & 0xffn);
379
+ v >>= 8n;
380
+ }
381
+ return buf;
382
+ }
383
+ function bigintTo32LeArr(n) {
384
+ const out = new Array(32).fill(0);
385
+ let v = n;
386
+ for (let i = 0; i < 32; i++) {
387
+ out[i] = Number(v & 0xffn);
388
+ v >>= 8n;
389
+ }
390
+ return out;
391
+ }
392
+ function computePathIndices(leafIndex, depth) {
393
+ const indices = [];
394
+ let idx = leafIndex;
395
+ for (let i = 0; i < depth; i++) {
396
+ indices.push(idx & 1);
397
+ idx >>= 1;
398
+ }
399
+ return indices;
400
+ }
401
+ function leHexToBigint(hex) {
402
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
403
+ const bytes = new Uint8Array(h.length / 2);
404
+ for (let i = 0; i < bytes.length; i++) {
405
+ bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
406
+ }
407
+ return bytesToBigintLE(bytes);
408
+ }
409
+
410
+ // src/shielded-pool/EncryptedMemo.ts
411
+ var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
412
+ var NONCE_SIZE = 12;
413
+ var MEMO_PLAINTEXT_SIZE = 76;
414
+ var ENCRYPTED_MEMO_SIZE = 104;
415
+ function serializeMemo(value, ownerPk, blinding, assetId) {
416
+ const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
417
+ const view = new DataView(buf.buffer);
418
+ view.setBigUint64(0, value & 0xffffffffffffffffn, true);
419
+ buf.set(ownerPk.slice(0, 32), 8);
420
+ buf.set(blinding.slice(0, 32), 40);
421
+ view.setUint32(72, assetId >>> 0, true);
422
+ return buf;
423
+ }
424
+ function deriveEncryptionKey(viewingKey, commitment) {
425
+ const h = import_sha2.sha256.create();
426
+ h.update(viewingKey);
427
+ h.update(commitment);
428
+ h.update(KEY_DOMAIN);
429
+ return h.digest();
430
+ }
431
+ var EncryptedMemo = {
432
+ /**
433
+ * Build and encrypt a memo for a note.
434
+ *
435
+ * @param value Note value in planck.
436
+ * @param ownerPk 32-byte owner public key (little-endian).
437
+ * @param blinding 32-byte blinding scalar (little-endian).
438
+ * @param assetId Asset identifier.
439
+ * @param commitment 32-byte commitment bytes (little-endian).
440
+ * @param recipientVk 32-byte recipient viewing key — pass `new Uint8Array(32)`
441
+ * for a publicly-readable (dummy) memo.
442
+ * @returns 104-byte encrypted memo (nonce || ciphertext).
443
+ */
444
+ encrypt(value, ownerPk, blinding, assetId, commitment, recipientVk) {
445
+ const nonce = (0, import_utils.randomBytes)(NONCE_SIZE);
446
+ const key = deriveEncryptionKey(recipientVk, commitment);
447
+ const plaintext = serializeMemo(value, ownerPk, blinding, assetId);
448
+ const cipher = (0, import_chacha.chacha20poly1305)(key, nonce);
449
+ const ciphertext = cipher.encrypt(plaintext);
450
+ const result = new Uint8Array(NONCE_SIZE + ciphertext.length);
451
+ result.set(nonce, 0);
452
+ result.set(ciphertext, NONCE_SIZE);
453
+ return result;
454
+ },
455
+ /**
456
+ * Returns a 104-byte public memo with a zero recipient viewing key.
457
+ * The memo is still readable by anyone who holds the viewing key (zeros).
458
+ */
459
+ encryptPublic(value, ownerPk, blinding, assetId, commitment) {
460
+ return EncryptedMemo.encrypt(
461
+ value,
462
+ ownerPk,
463
+ blinding,
464
+ assetId,
465
+ commitment,
466
+ new Uint8Array(32)
467
+ );
468
+ },
469
+ /**
470
+ * Returns a 104-byte zeroed dummy memo (no information, always valid on-chain).
471
+ */
472
+ dummy() {
473
+ return new Uint8Array(ENCRYPTED_MEMO_SIZE);
474
+ },
475
+ /**
476
+ * Decrypt an on-chain EncryptedMemo.
477
+ *
478
+ * Returns `null` if decryption fails — wrong key, bad MAC, or malformed memo.
479
+ * Never throws; safe for scan loops.
480
+ *
481
+ * @param memoBytes 104-byte encrypted memo.
482
+ * @param commitment 32-byte note commitment (little-endian).
483
+ * @param recipientVk 32-byte recipient viewing key.
484
+ */
485
+ decrypt(memoBytes, commitment, recipientVk) {
486
+ if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
487
+ try {
488
+ const nonce = memoBytes.slice(0, NONCE_SIZE);
489
+ const ciphertext = memoBytes.slice(NONCE_SIZE);
490
+ const key = deriveEncryptionKey(recipientVk, commitment);
491
+ const cipher = (0, import_chacha.chacha20poly1305)(key, nonce);
492
+ const plaintext = cipher.decrypt(ciphertext);
493
+ const view = new DataView(plaintext.buffer, plaintext.byteOffset, plaintext.byteLength);
494
+ const value = view.getBigUint64(0, true);
495
+ const ownerPk = bytesToBigintLE(plaintext.slice(8, 40));
496
+ const blinding = bytesToBigintLE(plaintext.slice(40, 72));
497
+ const assetId = BigInt(view.getUint32(72, true));
498
+ return { value, ownerPk, blinding, assetId };
499
+ } catch {
500
+ return null;
501
+ }
502
+ }
503
+ };
504
+
505
+ // src/shielded-pool/NoteBuilder.ts
506
+ var import_poseidon_lite = require("poseidon-lite");
507
+ var NoteBuilder = class {
508
+ /**
509
+ * Build a ZkNote from the given inputs.
510
+ *
511
+ * @param input.value Amount in planck (required).
512
+ * @param input.assetId Asset ID — default 0n (native ORB-Privacy).
513
+ * @param input.ownerPk BabyJubJub Ax — default 0n.
514
+ * @param input.blinding Random scalar — defaults to BigInt(Date.now()).
515
+ * @param input.spendingKey Secret key for nullifier — default 0n.
516
+ */
517
+ static async build(input) {
518
+ const value = input.value;
519
+ const assetId = input.assetId ?? 0n;
520
+ const ownerPk = input.ownerPk ?? 0n;
521
+ const blinding = input.blinding ?? BigInt(Date.now());
522
+ const spendingKey = input.spendingKey ?? 0n;
523
+ const commitment = (0, import_poseidon_lite.poseidon4)([value, assetId, ownerPk, blinding]);
524
+ const nullifier = (0, import_poseidon_lite.poseidon2)([commitment, spendingKey]);
525
+ const commitmentBytes = bigintTo32Le(commitment);
526
+ const nullifierBytes = bigintTo32Le(nullifier);
527
+ const memo = input.viewingKey !== void 0 ? Array.from(
528
+ EncryptedMemo.encrypt(
529
+ value,
530
+ bigintTo32Le(ownerPk),
531
+ bigintTo32Le(blinding),
532
+ Number(assetId),
533
+ commitmentBytes,
534
+ input.viewingKey
535
+ )
536
+ ) : Array.from(EncryptedMemo.dummy());
537
+ const note = {
538
+ value,
539
+ assetId,
540
+ ownerPk,
541
+ blinding,
542
+ spendingKey,
543
+ spent: false,
544
+ spentAt: null,
545
+ commitment,
546
+ nullifier,
547
+ commitmentHex: toHex(commitmentBytes),
548
+ nullifierHex: toHex(nullifierBytes),
549
+ memo
550
+ };
551
+ return note;
552
+ }
553
+ /**
554
+ * Build the 104-byte encrypted memo for a note.
555
+ *
556
+ * Pure TypeScript implementation — no WASM dependency.
557
+ * Uses ChaCha20-Poly1305 with SHA-256 key derivation.
558
+ *
559
+ * @param note The ZkNote whose fields populate the plaintext.
560
+ * @param recipientVk 32-byte recipient viewing key.
561
+ * Pass `new Uint8Array(32)` (default) for a public/dummy memo.
562
+ */
563
+ static buildMemo(note, recipientVk) {
564
+ return EncryptedMemo.encrypt(
565
+ note.value,
566
+ bigintTo32Le(note.ownerPk),
567
+ bigintTo32Le(note.blinding),
568
+ Number(note.assetId),
569
+ bigintTo32Le(note.commitment),
570
+ recipientVk ?? new Uint8Array(32)
571
+ );
572
+ }
573
+ };
574
+
575
+ // src/shielded-pool/ShieldedPoolModule.ts
576
+ function toTxResult(payload) {
577
+ const base = {
578
+ txHash: payload.txHash,
579
+ blockHash: payload.block.hash,
580
+ blockNumber: payload.block.number,
581
+ ok: payload.ok
582
+ };
583
+ if (!payload.ok) {
584
+ return { ...base, error: payload.dispatchError.type };
585
+ }
586
+ return base;
587
+ }
588
+ function callUnsafeTx(txEntry, ...args) {
589
+ return txEntry(...args);
590
+ }
591
+ function resolveTx(unsafe, pallet, call) {
592
+ const u = unsafe;
593
+ const p = u["tx"]?.[pallet];
594
+ if (p === void 0) throw new Error(`Pallet "${pallet}" not found in runtime metadata`);
595
+ const entry = p[call];
596
+ if (entry === void 0)
597
+ throw new Error(`Call "${pallet}.${call}" not found in runtime metadata`);
598
+ return entry;
599
+ }
600
+ var ShieldedPoolModule = class {
601
+ constructor(substrate, merkle) {
602
+ this.substrate = substrate;
603
+ this.merkle = merkle;
604
+ }
605
+ // ─── Extrinsics ────────────────────────────────────────────────────────────
606
+ /**
607
+ * Deposits tokens into the shielded pool.
608
+ * Extrinsic: shieldedPool.shield(assetId, amount, commitment, encryptedMemo)
609
+ */
610
+ async shield(params, signer) {
611
+ const memo = params.encryptedMemo ?? EncryptedMemo.dummy();
612
+ const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "shield");
613
+ const tx = callUnsafeTx(
614
+ entry,
615
+ params.assetId,
616
+ params.amount.toString(),
617
+ import_polkadot_api2.Binary.fromHex(params.commitment),
618
+ import_polkadot_api2.Binary.fromBytes(memo)
619
+ );
620
+ return toTxResult(await tx.signAndSubmit(signer));
621
+ }
622
+ /**
623
+ * Build a ZkNote locally and submit shieldedPool.shield in one call.
624
+ *
625
+ * Returns both the on-chain result and the note — **save the note locally**,
626
+ * it cannot be recovered after the fact.
627
+ *
628
+ * @param params.value Amount in planck (required).
629
+ * @param params.assetId Asset ID — default 0 (native ORB-Privacy).
630
+ * @param params.ownerPk BabyJubJub Ax (default 0n).
631
+ * @param params.blinding Random blinding scalar (default BigInt(Date.now())).
632
+ * @param params.spendingKey Secret spending key (default 0n).
633
+ */
634
+ async buildAndShield(params, signer) {
635
+ const noteInput = {
636
+ value: params.value,
637
+ ...params.assetId !== void 0 && { assetId: BigInt(params.assetId) },
638
+ ...params.ownerPk !== void 0 && { ownerPk: params.ownerPk },
639
+ ...params.blinding !== void 0 && { blinding: params.blinding },
640
+ ...params.spendingKey !== void 0 && { spendingKey: params.spendingKey }
641
+ };
642
+ const note = await NoteBuilder.build(noteInput);
643
+ const memo = NoteBuilder.buildMemo(note);
644
+ const txResult = await this.shield(
645
+ {
646
+ assetId: Number(note.assetId),
647
+ amount: note.value,
648
+ commitment: note.commitmentHex,
649
+ encryptedMemo: memo
650
+ },
651
+ signer
652
+ );
653
+ return { txResult, note };
654
+ }
655
+ /**
656
+ * Withdraws tokens from the shielded pool to a public address.
657
+ * Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient)
658
+ */
659
+ async unshield(params, signer) {
660
+ const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "unshield");
661
+ const tx = callUnsafeTx(
662
+ entry,
663
+ import_polkadot_api2.Binary.fromBytes(params.proof),
664
+ import_polkadot_api2.Binary.fromHex(params.merkleRoot),
665
+ import_polkadot_api2.Binary.fromHex(params.nullifier),
666
+ params.assetId,
667
+ params.amount.toString(),
668
+ import_polkadot_api2.Binary.fromHex(params.recipientAddress)
669
+ );
670
+ return toTxResult(await tx.signAndSubmit(signer));
671
+ }
672
+ /**
673
+ * Performs a private (shielded) transfer between two notes.
674
+ * Extrinsic: shieldedPool.privateTransfer(inputs, outputs, proof, merkleRoot)
675
+ */
676
+ async privateTransfer(params, signer) {
677
+ const inputs = params.inputs.map((inp) => ({
678
+ nullifier: import_polkadot_api2.Binary.fromHex(inp.nullifier),
679
+ commitment: import_polkadot_api2.Binary.fromHex(inp.commitment)
680
+ }));
681
+ const outputs = params.outputs.map((out) => ({
682
+ commitment: import_polkadot_api2.Binary.fromHex(out.commitment),
683
+ memo: import_polkadot_api2.Binary.fromBytes(out.encryptedMemo ?? EncryptedMemo.dummy())
684
+ }));
685
+ const entry = resolveTx(this.substrate.unsafe, "shieldedPool", "privateTransfer");
686
+ const tx = callUnsafeTx(
687
+ entry,
688
+ inputs,
689
+ outputs,
690
+ import_polkadot_api2.Binary.fromBytes(params.proof),
691
+ import_polkadot_api2.Binary.fromHex(params.merkleRoot)
692
+ );
693
+ return toTxResult(await tx.signAndSubmit(signer));
694
+ }
695
+ // ─── Queries ───────────────────────────────────────────────────────────────
696
+ /** Returns whether a nullifier has already been spent. */
697
+ async isNullifierSpent(nullifierHex) {
698
+ const raw = await this.substrate.request(
699
+ "privacy_getNullifierStatus",
700
+ [nullifierHex]
701
+ );
702
+ return raw.is_spent;
703
+ }
704
+ /** Returns the full nullifier status object. */
705
+ async getNullifierStatus(nullifierHex) {
706
+ const raw = await this.substrate.request(
707
+ "privacy_getNullifierStatus",
708
+ [nullifierHex]
709
+ );
710
+ return { nullifier: raw.nullifier, isSpent: raw.is_spent };
711
+ }
712
+ /** Returns the total locked balance in the pool for a given asset. */
713
+ async getPoolBalance(assetId) {
714
+ const raw = await this.substrate.request(
715
+ "shieldedPool_getPoolBalance",
716
+ [assetId]
717
+ );
718
+ return { assetId, balance: BigInt(raw.balance) };
719
+ }
720
+ /**
721
+ * Returns Merkle tree info and pool balance for a given asset in a single call.
722
+ * Convenience wrapper used by both `app` and `privacy-explorer`.
723
+ */
724
+ async getPoolStats(assetId = 0) {
725
+ const [merkle, balance] = await Promise.all([
726
+ this.merkle.getTreeInfo(),
727
+ this.getPoolBalance(assetId)
728
+ ]);
729
+ return { merkle, balance };
730
+ }
731
+ };
732
+
733
+ // src/utils/address.ts
734
+ var import_util_crypto = require("@polkadot/util-crypto");
735
+ function normalizeEvmAddress(addr) {
736
+ const hex = addr.startsWith("0x") ? addr.slice(2) : addr;
737
+ return "0x" + hex.toLowerCase();
738
+ }
739
+ function isSs58(addr) {
740
+ return !addr.startsWith("0x") && addr.length >= 46 && addr.length <= 50;
741
+ }
742
+ function isEvmAddress(addr) {
743
+ return /^0x[0-9a-fA-F]{40}$/.test(addr);
744
+ }
745
+ function evmAddressToAccountId(evmAddr) {
746
+ const clean = evmAddr.startsWith("0x") ? evmAddr.slice(2) : evmAddr;
747
+ if (clean.length !== 40) {
748
+ throw new Error(`Expected 20-byte EVM address, got: ${evmAddr}`);
749
+ }
750
+ const bytes = new Uint8Array(32);
751
+ for (let i = 0; i < 20; i++) {
752
+ bytes[i + 12] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
753
+ }
754
+ return bytes;
755
+ }
756
+ function evmToImplicitSubstrate(evmAddr) {
757
+ const clean = evmAddr.startsWith("0x") ? evmAddr.slice(2) : evmAddr;
758
+ if (clean.length !== 40) {
759
+ throw new Error(`Expected 20-byte EVM address, got: ${evmAddr}`);
760
+ }
761
+ return "0x" + clean.toLowerCase() + "0".repeat(24);
762
+ }
763
+ function isImplicitEvmAccount(accountHex) {
764
+ const clean = accountHex.startsWith("0x") ? accountHex.slice(2) : accountHex;
765
+ if (clean.length !== 64) return false;
766
+ return clean.slice(40).toLowerCase() === "0".repeat(24);
767
+ }
768
+ function implicitSubstrateToEvm(accountHex) {
769
+ if (!isImplicitEvmAccount(accountHex)) {
770
+ throw new Error(`AccountId32 is not an implicit EVM-derived account: ${accountHex}`);
771
+ }
772
+ const clean = accountHex.startsWith("0x") ? accountHex.slice(2) : accountHex;
773
+ return "0x" + clean.slice(0, 40).toLowerCase();
774
+ }
775
+ var ACCOUNT_ID_BYTES = 32;
776
+ var EVM_BYTES = 20;
777
+ function isSubstrateAddress(addr) {
778
+ if (!addr || typeof addr !== "string" || isEvmAddress(addr)) return false;
779
+ if (addr.length < 40 || addr.length > 60) return false;
780
+ try {
781
+ const bytes = (0, import_util_crypto.decodeAddress)(addr);
782
+ return bytes.length === ACCOUNT_ID_BYTES;
783
+ } catch {
784
+ return false;
785
+ }
786
+ }
787
+ function isUnifiedAddress(addr) {
788
+ if (!addr || isEvmAddress(addr)) return false;
789
+ try {
790
+ const bytes = (0, import_util_crypto.decodeAddress)(addr);
791
+ if (bytes.length !== ACCOUNT_ID_BYTES) return false;
792
+ return bytes.slice(EVM_BYTES, ACCOUNT_ID_BYTES).every((b) => b === 0);
793
+ } catch {
794
+ return false;
795
+ }
796
+ }
797
+ function substrateToEvm(addr) {
798
+ if (!addr) return null;
799
+ if (isEvmAddress(addr)) return normalizeEvmAddress(addr);
800
+ try {
801
+ const bytes = (0, import_util_crypto.decodeAddress)(addr);
802
+ if (bytes.length !== ACCOUNT_ID_BYTES) return null;
803
+ const isUnified = bytes.slice(EVM_BYTES, ACCOUNT_ID_BYTES).every((b) => b === 0);
804
+ if (!isUnified) return null;
805
+ return "0x" + Array.from(bytes.slice(0, EVM_BYTES)).map((b) => b.toString(16).padStart(2, "0")).join("");
806
+ } catch {
807
+ return null;
808
+ }
809
+ }
810
+ function evmToSubstrate(addr) {
811
+ const normalized = normalizeEvmAddress(addr);
812
+ if (!normalized) return null;
813
+ const hex = normalized.slice(2);
814
+ if (hex.length !== 40) return null;
815
+ const mapped = new Uint8Array(ACCOUNT_ID_BYTES);
816
+ for (let i = 0; i < EVM_BYTES; i++) {
817
+ mapped[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
818
+ }
819
+ try {
820
+ return (0, import_util_crypto.encodeAddress)(mapped);
821
+ } catch {
822
+ return null;
823
+ }
824
+ }
825
+ function accountIdHexToSs58(hex) {
826
+ if (!hex) return null;
827
+ try {
828
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
829
+ if (h.length !== 64) return null;
830
+ const bytes = new Uint8Array(32);
831
+ for (let i = 0; i < 32; i++) {
832
+ bytes[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
833
+ }
834
+ return (0, import_util_crypto.encodeAddress)(bytes);
835
+ } catch {
836
+ return null;
837
+ }
838
+ }
839
+ function substrateSs58ToAccountIdHex(addr) {
840
+ if (!addr) return null;
841
+ try {
842
+ const bytes = (0, import_util_crypto.decodeAddress)(addr);
843
+ if (bytes.length !== ACCOUNT_ID_BYTES) return null;
844
+ return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
845
+ } catch {
846
+ return null;
847
+ }
848
+ }
849
+ function addressToAccountIdHex(addr) {
850
+ if (!addr) return null;
851
+ if (isEvmAddress(addr)) {
852
+ const hex = addr.startsWith("0x") ? addr.slice(2) : addr;
853
+ const mapped = new Uint8Array(ACCOUNT_ID_BYTES);
854
+ for (let i = 0; i < EVM_BYTES; i++) {
855
+ mapped[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
856
+ }
857
+ return "0x" + Array.from(mapped).map((b) => b.toString(16).padStart(2, "0")).join("");
858
+ }
859
+ if (/^0x[0-9a-fA-F]{64}$/.test(addr)) {
860
+ return addr.toLowerCase();
861
+ }
862
+ return substrateSs58ToAccountIdHex(addr);
863
+ }
864
+
865
+ // src/chain/ChainModule.ts
866
+ var ChainModule = class {
867
+ constructor(substrate, evm) {
868
+ this.substrate = substrate;
869
+ this.evm = evm;
870
+ }
871
+ // ─── Node info ─────────────────────────────────────────────────────────────
872
+ /**
873
+ * Returns basic chain information from the node.
874
+ */
875
+ async getChainInfo() {
876
+ const [name, version] = await Promise.all([
877
+ this.substrate.request("system_name", []),
878
+ this.substrate.request("state_getRuntimeVersion", [])
879
+ ]);
880
+ return {
881
+ name,
882
+ version: String(version.specVersion),
883
+ ss58Prefix: version.ss58Prefix ?? 42
884
+ };
885
+ }
886
+ /**
887
+ * Returns the node's peer count and sync status.
888
+ */
889
+ async getHealth() {
890
+ return this.substrate.request("system_health", []);
891
+ }
892
+ /**
893
+ * Returns the node's software version string.
894
+ */
895
+ async getNodeVersion() {
896
+ return this.substrate.request("system_version", []);
897
+ }
898
+ /**
899
+ * Returns the genesis hash hex.
900
+ */
901
+ async getGenesisHash() {
902
+ return this.substrate.request("chain_getBlockHash", [0]);
903
+ }
904
+ // ─── Account mapping ───────────────────────────────────────────────────────
905
+ /**
906
+ * Resolves the full identity (Substrate + EVM addresses, alias) for an account.
907
+ * Accepts an EVM address (0x...) or a Substrate account hex (0x...32bytes).
908
+ */
909
+ async getFullIdentity(address) {
910
+ try {
911
+ const raw = await this.substrate.request(
912
+ "accountMapping_resolveFullIdentity",
913
+ [address]
914
+ );
915
+ return {
916
+ substrateAddress: raw.substrate_address ?? null,
917
+ evmAddress: raw.evm_address ? normalizeEvmAddress(raw.evm_address) : null,
918
+ alias: raw.alias ?? null
919
+ };
920
+ } catch {
921
+ return null;
922
+ }
923
+ }
924
+ /**
925
+ * Returns the mapped Substrate account hex for a given EVM address, or null.
926
+ */
927
+ async getMappedAccountByEvm(evmAddress) {
928
+ try {
929
+ return await this.substrate.request("accountMapping_getMappedAccount", [
930
+ normalizeEvmAddress(evmAddress)
931
+ ]);
932
+ } catch {
933
+ return null;
934
+ }
935
+ }
936
+ /**
937
+ * Returns the alias registered for a Substrate account, or null.
938
+ */
939
+ async getAliasOf(accountHex) {
940
+ try {
941
+ return await this.substrate.request("accountMapping_getAliasOf", [
942
+ accountHex
943
+ ]);
944
+ } catch {
945
+ return null;
946
+ }
947
+ }
948
+ // ─── EVM helpers ───────────────────────────────────────────────────────────
949
+ /**
950
+ * Returns estimated EVM chain ID from the EVM RPC endpoint. Requires evmRpc
951
+ * to have been provided in `OrbinumClientConfig`.
952
+ */
953
+ async getEvmChainId() {
954
+ if (!this.evm)
955
+ throw new Error("No EVM RPC URL configured. Set evmRpc in OrbinumClientConfig.");
956
+ return this.evm.getChainId();
957
+ }
958
+ /**
959
+ * Returns the current EVM block number.
960
+ */
961
+ async getEvmBlockNumber() {
962
+ if (!this.evm) throw new Error("No EVM RPC URL configured.");
963
+ return this.evm.getBlockNumber();
964
+ }
965
+ };
966
+
967
+ // src/account-mapping/AccountMappingModule.ts
968
+ var import_polkadot_api3 = require("polkadot-api");
969
+ function toTxResult2(payload) {
970
+ const base = {
971
+ txHash: payload.txHash,
972
+ blockHash: payload.block.hash,
973
+ blockNumber: payload.block.number,
974
+ ok: payload.ok
975
+ };
976
+ if (!payload.ok) {
977
+ return { ...base, error: payload.dispatchError.type };
978
+ }
979
+ return base;
980
+ }
981
+ function callUnsafeTx2(txEntry, ...args) {
982
+ return txEntry(...args);
983
+ }
984
+ function resolveTx2(unsafe, pallet, call) {
985
+ const u = unsafe;
986
+ const p = u["tx"]?.[pallet];
987
+ if (p === void 0) throw new Error(`Pallet "${pallet}" not found in runtime metadata`);
988
+ const entry = p[call];
989
+ if (entry === void 0)
990
+ throw new Error(`Call "${pallet}.${call}" not found in runtime metadata`);
991
+ return entry;
992
+ }
993
+ function mapRawScheme(raw) {
994
+ if (raw === "Eip191" || raw === "eip191") return "Eip191";
995
+ if (raw === "Ed25519" || raw === "ed25519") return "Ed25519";
996
+ return raw;
997
+ }
998
+ var AccountMappingModule = class {
999
+ constructor(substrate) {
1000
+ this.substrate = substrate;
1001
+ }
1002
+ // ─── Address resolution ─────────────────────────────────────────────────────
1003
+ /**
1004
+ * Returns the explicitly mapped (or fallback) Substrate AccountId32 hex for
1005
+ * an EVM address. `mapped` is set only when `map_account` was called.
1006
+ * `fallback` is always the EeSuffix rule: `H160 ++ [0x00; 12]`.
1007
+ */
1008
+ async getAccountAddresses(accountId) {
1009
+ try {
1010
+ const raw = await this.substrate.request(
1011
+ "accountMapping_getAccountAddresses",
1012
+ [accountId]
1013
+ );
1014
+ return { mapped: raw.mapped ?? null, fallback: raw.fallback ?? null };
1015
+ } catch {
1016
+ return { mapped: null, fallback: null };
1017
+ }
1018
+ }
1019
+ /**
1020
+ * Returns the explicitly mapped Substrate AccountId32 hex for a given EVM
1021
+ * address, or null if no explicit mapping exists.
1022
+ */
1023
+ async getMappedAccount(evmAddress) {
1024
+ try {
1025
+ return await this.substrate.request("accountMapping_getMappedAccount", [
1026
+ normalizeEvmAddress(evmAddress)
1027
+ ]);
1028
+ } catch {
1029
+ return null;
1030
+ }
1031
+ }
1032
+ // ─── Alias queries ──────────────────────────────────────────────────────────
1033
+ /**
1034
+ * Resolves "@alias" to basic info (owner, optional EVM address, link count).
1035
+ * Accepts the alias with or without the leading "@".
1036
+ */
1037
+ async resolveAlias(alias) {
1038
+ try {
1039
+ const raw = await this.substrate.request(
1040
+ "accountMapping_resolveAlias",
1041
+ [alias]
1042
+ );
1043
+ if (!raw) return null;
1044
+ return {
1045
+ owner: raw.substrate_account,
1046
+ evmAddress: raw.evm_address ? normalizeEvmAddress(raw.evm_address) : null,
1047
+ chainLinksCount: raw.chain_links_count
1048
+ };
1049
+ } catch {
1050
+ return null;
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Returns the alias registered for the given Substrate AccountId32 hex, or null.
1055
+ */
1056
+ async getAliasOf(accountId) {
1057
+ try {
1058
+ return await this.substrate.request("accountMapping_getAliasOf", [
1059
+ accountId
1060
+ ]);
1061
+ } catch {
1062
+ return null;
1063
+ }
1064
+ }
1065
+ // ─── Full identity ──────────────────────────────────────────────────────────
1066
+ /**
1067
+ * Resolves "@alias" to its full identity: owner, EVM address, all public
1068
+ * chain links, and profile metadata.
1069
+ */
1070
+ async resolveFullIdentity(alias) {
1071
+ try {
1072
+ const raw = await this.substrate.request(
1073
+ "accountMapping_resolveFullIdentity",
1074
+ [alias]
1075
+ );
1076
+ if (!raw) return null;
1077
+ return {
1078
+ owner: raw.owner,
1079
+ evmAddress: raw.evm_address ? normalizeEvmAddress(raw.evm_address) : null,
1080
+ chainLinks: raw.chain_links.map((l) => ({
1081
+ chainId: l.chain_id,
1082
+ address: l.address
1083
+ })),
1084
+ metadata: raw.metadata ? {
1085
+ displayName: raw.metadata.display_name ?? null,
1086
+ bio: raw.metadata.bio ?? null,
1087
+ avatar: raw.metadata.avatar ?? null
1088
+ } : null
1089
+ };
1090
+ } catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+ /**
1095
+ * Returns the profile metadata for a given Substrate AccountId32 hex, or null.
1096
+ */
1097
+ async getAccountMetadata(accountId) {
1098
+ try {
1099
+ const raw = await this.substrate.request(
1100
+ "accountMapping_getAccountMetadata",
1101
+ [accountId]
1102
+ );
1103
+ if (!raw) return null;
1104
+ return {
1105
+ displayName: raw.display_name ?? null,
1106
+ bio: raw.bio ?? null,
1107
+ avatar: raw.avatar ?? null
1108
+ };
1109
+ } catch {
1110
+ return null;
1111
+ }
1112
+ }
1113
+ // ─── Chain links ────────────────────────────────────────────────────────────
1114
+ /**
1115
+ * Returns the owner AccountId32 hex of a verified multichain link, or null.
1116
+ */
1117
+ async getLinkOwner(chainId, address) {
1118
+ try {
1119
+ return await this.substrate.request("accountMapping_getLinkOwner", [
1120
+ chainId,
1121
+ address
1122
+ ]);
1123
+ } catch {
1124
+ return null;
1125
+ }
1126
+ }
1127
+ /**
1128
+ * Returns all blockchain networks supported for verified cross-chain links.
1129
+ */
1130
+ async getSupportedChains() {
1131
+ try {
1132
+ const raw = await this.substrate.request(
1133
+ "accountMapping_getSupportedChains",
1134
+ []
1135
+ );
1136
+ return raw.map(([chainId, scheme]) => ({
1137
+ chainId,
1138
+ scheme: mapRawScheme(scheme)
1139
+ }));
1140
+ } catch {
1141
+ return [];
1142
+ }
1143
+ }
1144
+ // ─── Private links ──────────────────────────────────────────────────────────
1145
+ /**
1146
+ * Returns the private link commitments registered for an alias.
1147
+ * Real addresses are never exposed. Returns null if the alias does not exist.
1148
+ */
1149
+ async getPrivateLinks(alias) {
1150
+ try {
1151
+ const raw = await this.substrate.request(
1152
+ "accountMapping_getPrivateLinks",
1153
+ [alias]
1154
+ );
1155
+ if (!raw) return null;
1156
+ return raw.map((r) => ({ chainId: r.chain_id, commitment: r.commitment }));
1157
+ } catch {
1158
+ return null;
1159
+ }
1160
+ }
1161
+ /**
1162
+ * Returns true if the given commitment is registered as a private link for the alias.
1163
+ */
1164
+ async hasPrivateLink(alias, commitment) {
1165
+ try {
1166
+ return await this.substrate.request("accountMapping_hasPrivateLink", [
1167
+ alias,
1168
+ commitment
1169
+ ]);
1170
+ } catch {
1171
+ return false;
1172
+ }
1173
+ }
1174
+ // ─── Marketplace ────────────────────────────────────────────────────────────
1175
+ /**
1176
+ * Returns listing info if the alias is currently for sale, or null.
1177
+ */
1178
+ async getListingInfo(alias) {
1179
+ try {
1180
+ const raw = await this.substrate.request(
1181
+ "accountMapping_getListingInfo",
1182
+ [alias]
1183
+ );
1184
+ if (!raw) return null;
1185
+ return {
1186
+ price: BigInt(raw.price),
1187
+ private: raw.private,
1188
+ whitelistCount: raw.whitelist_count
1189
+ };
1190
+ } catch {
1191
+ return null;
1192
+ }
1193
+ }
1194
+ /**
1195
+ * Returns the alias and its listing if the given account currently has an
1196
+ * alias listed for sale. Returns null otherwise.
1197
+ */
1198
+ async getAccountListing(accountId) {
1199
+ try {
1200
+ const raw = await this.substrate.request(
1201
+ "accountMapping_getAccountListing",
1202
+ [accountId]
1203
+ );
1204
+ if (!raw) return null;
1205
+ return {
1206
+ alias: raw.alias,
1207
+ listing: {
1208
+ price: BigInt(raw.price),
1209
+ private: raw.private,
1210
+ whitelistCount: raw.whitelist_count
1211
+ }
1212
+ };
1213
+ } catch {
1214
+ return null;
1215
+ }
1216
+ }
1217
+ /**
1218
+ * Returns whether a specific buyer can purchase the given alias right now.
1219
+ */
1220
+ async canBuy(alias, buyerAccountId) {
1221
+ try {
1222
+ return await this.substrate.request("accountMapping_canBuy", [
1223
+ alias,
1224
+ buyerAccountId
1225
+ ]);
1226
+ } catch {
1227
+ return false;
1228
+ }
1229
+ }
1230
+ // ─── Extrinsics ─────────────────────────────────────────────────────────────
1231
+ /**
1232
+ * Creates an explicit EVM → Substrate account mapping.
1233
+ * Stores an explicit `MappedAccounts` entry for the caller's H160.
1234
+ * Extrinsic: accountMapping.mapAccount()
1235
+ */
1236
+ async mapAccount(signer) {
1237
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "mapAccount");
1238
+ const tx = callUnsafeTx2(entry);
1239
+ return toTxResult2(await tx.signAndSubmit(signer));
1240
+ }
1241
+ /**
1242
+ * Removes the EVM → Substrate mapping for the caller.
1243
+ * Extrinsic: accountMapping.unmapAccount()
1244
+ */
1245
+ async unmapAccount(signer) {
1246
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "unmapAccount");
1247
+ const tx = callUnsafeTx2(entry);
1248
+ return toTxResult2(await tx.signAndSubmit(signer));
1249
+ }
1250
+ /**
1251
+ * Registers a unique @alias for the caller.
1252
+ * Requires a deposit. The alias must be 3–32 ASCII lowercase alphanumeric chars + hyphens.
1253
+ * Extrinsic: accountMapping.registerAlias(alias)
1254
+ */
1255
+ async registerAlias(alias, signer) {
1256
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "registerAlias");
1257
+ const tx = callUnsafeTx2(entry, import_polkadot_api3.Binary.fromText(alias));
1258
+ return toTxResult2(await tx.signAndSubmit(signer));
1259
+ }
1260
+ /**
1261
+ * Releases the caller's alias and recovers the deposit.
1262
+ * Extrinsic: accountMapping.releaseAlias()
1263
+ */
1264
+ async releaseAlias(signer) {
1265
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "releaseAlias");
1266
+ const tx = callUnsafeTx2(entry);
1267
+ return toTxResult2(await tx.signAndSubmit(signer));
1268
+ }
1269
+ /**
1270
+ * Transfers the caller's alias to another account.
1271
+ * Extrinsic: accountMapping.transferAlias(newOwner)
1272
+ */
1273
+ async transferAlias(newOwnerHex, signer) {
1274
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "transferAlias");
1275
+ const tx = callUnsafeTx2(entry, newOwnerHex);
1276
+ return toTxResult2(await tx.signAndSubmit(signer));
1277
+ }
1278
+ /**
1279
+ * Adds a verified public link to an external-chain wallet.
1280
+ *
1281
+ * `params.signature` must be produced by the external wallet over the caller's
1282
+ * AccountId32 bytes:
1283
+ * - EIP-191 (EVM): sign(keccak256("\x19Ethereum Signed Message:\n32" + accountId32))
1284
+ * - Ed25519 (Solana): sign(accountId32 bytes)
1285
+ *
1286
+ * Extrinsic: accountMapping.addChainLink(chainId, address, signature)
1287
+ */
1288
+ async addChainLink(params, signer) {
1289
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "addChainLink");
1290
+ const tx = callUnsafeTx2(
1291
+ entry,
1292
+ params.chainId,
1293
+ import_polkadot_api3.Binary.fromBytes(params.address),
1294
+ import_polkadot_api3.Binary.fromBytes(params.signature)
1295
+ );
1296
+ return toTxResult2(await tx.signAndSubmit(signer));
1297
+ }
1298
+ /**
1299
+ * Removes the external-chain link for the given chain ID.
1300
+ * Extrinsic: accountMapping.removeChainLink(chainId)
1301
+ */
1302
+ async removeChainLink(chainId, signer) {
1303
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "removeChainLink");
1304
+ const tx = callUnsafeTx2(entry, chainId);
1305
+ return toTxResult2(await tx.signAndSubmit(signer));
1306
+ }
1307
+ /**
1308
+ * Updates the caller's public profile metadata.
1309
+ * Extrinsic: accountMapping.setAccountMetadata(displayName, bio, avatar)
1310
+ */
1311
+ async setAccountMetadata(params, signer) {
1312
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "setAccountMetadata");
1313
+ const encode2 = (v) => v != null ? import_polkadot_api3.Binary.fromText(v) : void 0;
1314
+ const tx = callUnsafeTx2(
1315
+ entry,
1316
+ encode2(params.displayName),
1317
+ encode2(params.bio),
1318
+ encode2(params.avatar)
1319
+ );
1320
+ return toTxResult2(await tx.signAndSubmit(signer));
1321
+ }
1322
+ /**
1323
+ * Lists the caller's alias for sale on the alias marketplace.
1324
+ * Extrinsic: accountMapping.putAliasOnSale(price, isPrivate)
1325
+ */
1326
+ async putAliasOnSale(params, signer) {
1327
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "putAliasOnSale");
1328
+ const tx = callUnsafeTx2(entry, params.price.toString(), params.isPrivate);
1329
+ return toTxResult2(await tx.signAndSubmit(signer));
1330
+ }
1331
+ /**
1332
+ * Cancels an active alias sale listing.
1333
+ * Extrinsic: accountMapping.cancelSale()
1334
+ */
1335
+ async cancelSale(signer) {
1336
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "cancelSale");
1337
+ const tx = callUnsafeTx2(entry);
1338
+ return toTxResult2(await tx.signAndSubmit(signer));
1339
+ }
1340
+ /**
1341
+ * Purchases an alias listed for sale.
1342
+ * Extrinsic: accountMapping.buyAlias(alias)
1343
+ */
1344
+ async buyAlias(alias, signer) {
1345
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "buyAlias");
1346
+ const tx = callUnsafeTx2(entry, import_polkadot_api3.Binary.fromText(alias));
1347
+ return toTxResult2(await tx.signAndSubmit(signer));
1348
+ }
1349
+ /**
1350
+ * Dispatches an arbitrary call on behalf of a linked external-chain wallet.
1351
+ *
1352
+ * This is the "Universal Proxy" feature that allows EVM/Solana wallets to
1353
+ * authorize on-chain actions without holding a Substrate private key.
1354
+ *
1355
+ * The relayer (who pays gas) calls this with the external wallet's signature
1356
+ * over the encoded call payload and the owner's AccountId32.
1357
+ *
1358
+ * Extrinsic: accountMapping.dispatchAsLinkedAccount(owner, chainId, address, signature, call)
1359
+ */
1360
+ async dispatchAsLinkedAccount(params, signer) {
1361
+ const entry = resolveTx2(this.substrate.unsafe, "accountMapping", "dispatchAsLinkedAccount");
1362
+ const tx = callUnsafeTx2(
1363
+ entry,
1364
+ params.owner,
1365
+ params.chainId,
1366
+ import_polkadot_api3.Binary.fromBytes(params.address),
1367
+ import_polkadot_api3.Binary.fromBytes(params.signature),
1368
+ import_polkadot_api3.Binary.fromBytes(params.callData)
1369
+ );
1370
+ return toTxResult2(await tx.signAndSubmit(signer));
1371
+ }
1372
+ };
1373
+
1374
+ // src/precompiles/abi.ts
1375
+ function concat(arrays) {
1376
+ const total = arrays.reduce((s, a) => s + a.length, 0);
1377
+ const out = new Uint8Array(total);
1378
+ let o = 0;
1379
+ for (const a of arrays) {
1380
+ out.set(a, o);
1381
+ o += a.length;
1382
+ }
1383
+ return out;
1384
+ }
1385
+ function padTo32Multiple(data) {
1386
+ const rem = data.length % 32;
1387
+ if (rem === 0) return data;
1388
+ const padded = new Uint8Array(data.length + (32 - rem));
1389
+ padded.set(data);
1390
+ return padded;
1391
+ }
1392
+ var STATIC_TYPES = /* @__PURE__ */ new Set(["uint", "bytes32", "address", "bool"]);
1393
+ function encodeStaticParam(param) {
1394
+ const buf = new Uint8Array(32);
1395
+ switch (param.type) {
1396
+ case "uint": {
1397
+ return bigintTo32Be(param.value);
1398
+ }
1399
+ case "bytes32": {
1400
+ buf.set(param.value.slice(0, 32));
1401
+ return buf;
1402
+ }
1403
+ case "address": {
1404
+ const clean = param.value.startsWith("0x") ? param.value.slice(2) : param.value;
1405
+ const bytes = fromHex("0x" + clean.padStart(40, "0"));
1406
+ buf.set(bytes, 12);
1407
+ return buf;
1408
+ }
1409
+ case "bool": {
1410
+ buf[31] = param.value ? 1 : 0;
1411
+ return buf;
1412
+ }
1413
+ default:
1414
+ throw new Error(
1415
+ `encodeStatic: not a static ABI type: ${param.type}`
1416
+ );
1417
+ }
1418
+ }
1419
+ function encodeDynamicParam(param) {
1420
+ switch (param.type) {
1421
+ case "bytes": {
1422
+ const data = param.value;
1423
+ return concat([bigintTo32Be(BigInt(data.length)), padTo32Multiple(data)]);
1424
+ }
1425
+ case "string": {
1426
+ const data = new TextEncoder().encode(param.value);
1427
+ return concat([bigintTo32Be(BigInt(data.length)), padTo32Multiple(data)]);
1428
+ }
1429
+ case "bytes32[]": {
1430
+ const n = param.value.length;
1431
+ const parts = [bigintTo32Be(BigInt(n))];
1432
+ for (const b32 of param.value) {
1433
+ const slot = new Uint8Array(32);
1434
+ slot.set(b32.slice(0, 32));
1435
+ parts.push(slot);
1436
+ }
1437
+ return concat(parts);
1438
+ }
1439
+ case "address[]": {
1440
+ const n = param.value.length;
1441
+ const parts = [bigintTo32Be(BigInt(n))];
1442
+ for (const addr of param.value) {
1443
+ const slot = new Uint8Array(32);
1444
+ const clean = addr.startsWith("0x") ? addr.slice(2) : addr;
1445
+ const bytes = fromHex("0x" + clean.padStart(40, "0"));
1446
+ slot.set(bytes, 12);
1447
+ parts.push(slot);
1448
+ }
1449
+ return concat(parts);
1450
+ }
1451
+ case "bytes[]": {
1452
+ const n = param.value.length;
1453
+ const offsets = [];
1454
+ const datas = [];
1455
+ let offset = n * 32;
1456
+ for (const item of param.value) {
1457
+ offsets.push(bigintTo32Be(BigInt(offset)));
1458
+ const itemBlock = concat([
1459
+ bigintTo32Be(BigInt(item.length)),
1460
+ padTo32Multiple(item)
1461
+ ]);
1462
+ datas.push(itemBlock);
1463
+ offset += itemBlock.length;
1464
+ }
1465
+ return concat([bigintTo32Be(BigInt(n)), ...offsets, ...datas]);
1466
+ }
1467
+ default:
1468
+ throw new Error(
1469
+ `encodeDynamic: not a dynamic ABI type: ${param.type}`
1470
+ );
1471
+ }
1472
+ }
1473
+ function encode(selector, ...params) {
1474
+ const n = params.length;
1475
+ const headSize = n * 32;
1476
+ const heads = [];
1477
+ const tails = [];
1478
+ let tailOffset = headSize;
1479
+ for (const param of params) {
1480
+ if (STATIC_TYPES.has(param.type)) {
1481
+ heads.push(encodeStaticParam(param));
1482
+ } else {
1483
+ heads.push(bigintTo32Be(BigInt(tailOffset)));
1484
+ const tail = encodeDynamicParam(param);
1485
+ tails.push(tail);
1486
+ tailOffset += tail.length;
1487
+ }
1488
+ }
1489
+ return concat([selector, ...heads, ...tails]);
1490
+ }
1491
+ function encodeHex(selector, ...params) {
1492
+ return toHex(encode(selector, ...params));
1493
+ }
1494
+ function decodeUint(data, offset = 0) {
1495
+ let result = 0n;
1496
+ for (let i = 0; i < 32; i++) {
1497
+ result = result << 8n | BigInt(data[offset + i] ?? 0);
1498
+ }
1499
+ return result;
1500
+ }
1501
+ function decodeAddress2(data, offset = 0) {
1502
+ return "0x" + toHex(data.slice(offset + 12, offset + 32)).slice(2);
1503
+ }
1504
+ function decodeBool(data, offset = 0) {
1505
+ return data[offset + 31] !== 0;
1506
+ }
1507
+ function decodeBytes(data, slotOffset = 0) {
1508
+ const dataOffset = Number(decodeUint(data, slotOffset));
1509
+ const length = Number(decodeUint(data, dataOffset));
1510
+ return data.slice(dataOffset + 32, dataOffset + 32 + length);
1511
+ }
1512
+ function decodeString(data, slotOffset = 0) {
1513
+ return new TextDecoder().decode(decodeBytes(data, slotOffset));
1514
+ }
1515
+ function hexToBytes(hex) {
1516
+ if (hex === "0x" || hex === "") return new Uint8Array(0);
1517
+ return fromHex(hex.startsWith("0x") ? hex : "0x" + hex);
1518
+ }
1519
+
1520
+ // src/precompiles/addresses.ts
1521
+ var PRECOMPILE_ADDR = {
1522
+ // ── Ethereum standard (EIP) ─────────────────────────────────────────────
1523
+ EC_RECOVER: "0x0000000000000000000000000000000000000001",
1524
+ SHA256: "0x0000000000000000000000000000000000000002",
1525
+ RIPEMD160: "0x0000000000000000000000000000000000000003",
1526
+ IDENTITY: "0x0000000000000000000000000000000000000004",
1527
+ MODEXP: "0x0000000000000000000000000000000000000005",
1528
+ // ── Frontier / non-standard ─────────────────────────────────────────────
1529
+ SHA3_FIPS256: "0x0000000000000000000000000000000000000400",
1530
+ EC_RECOVER_PUBKEY: "0x0000000000000000000000000000000000000401",
1531
+ CURVE25519_ADD: "0x0000000000000000000000000000000000000402",
1532
+ CURVE25519_SCALAR_MUL: "0x0000000000000000000000000000000000000403",
1533
+ // ── Orbinum custom ───────────────────────────────────────────────────────
1534
+ ACCOUNT_MAPPING: "0x0000000000000000000000000000000000000800",
1535
+ SHIELDED_POOL: "0x0000000000000000000000000000000000000801"
1536
+ };
1537
+ var AM_SEL = {
1538
+ // ── Read-only ─────────────────────────────────────────────────────────────
1539
+ // resolveAlias(string) → 0xd03149ab
1540
+ RESOLVE_ALIAS: new Uint8Array([208, 49, 73, 171]),
1541
+ // getAliasOf(address) → 0x7a0ed62c
1542
+ GET_ALIAS_OF: new Uint8Array([122, 14, 214, 44]),
1543
+ // hasPrivateLink(string,bytes32) → 0x47e05c6c
1544
+ HAS_PRIVATE_LINK: new Uint8Array([71, 224, 92, 108]),
1545
+ // ── No-argument writes ────────────────────────────────────────────────────
1546
+ // mapAccount() → 0xdca49d0e
1547
+ MAP_ACCOUNT: new Uint8Array([220, 164, 157, 14]),
1548
+ // unmapAccount() → 0x08f57367
1549
+ UNMAP_ACCOUNT: new Uint8Array([8, 245, 115, 103]),
1550
+ // releaseAlias() → 0x7fac359e
1551
+ RELEASE_ALIAS: new Uint8Array([127, 172, 53, 158]),
1552
+ // cancelSale() → 0x4d023ab9
1553
+ CANCEL_SALE: new Uint8Array([77, 2, 58, 185]),
1554
+ // ── Writes with arguments ─────────────────────────────────────────────────
1555
+ // registerAlias(string) → 0x2f8839c3
1556
+ REGISTER_ALIAS: new Uint8Array([47, 136, 57, 195]),
1557
+ // transferAlias(address) → 0x5ac998e7
1558
+ TRANSFER_ALIAS: new Uint8Array([90, 201, 152, 231]),
1559
+ // buyAlias(string) → 0x1625df3a
1560
+ BUY_ALIAS: new Uint8Array([22, 37, 223, 58]),
1561
+ // putAliasOnSale(uint256,address[]) → 0x32091192
1562
+ PUT_ALIAS_ON_SALE: new Uint8Array([50, 9, 17, 146]),
1563
+ // removeChainLink(uint32) → 0x6f579c0c
1564
+ REMOVE_CHAIN_LINK: new Uint8Array([111, 87, 156, 12]),
1565
+ // addChainLink(uint32,bytes,bytes) → 0x5f3e837c
1566
+ ADD_CHAIN_LINK: new Uint8Array([95, 62, 131, 124]),
1567
+ // registerPrivateLink(uint32,bytes32) → 0xc04e98f4
1568
+ REGISTER_PRIVATE_LINK: new Uint8Array([192, 78, 152, 244]),
1569
+ // removePrivateLink(bytes32) → 0xdfd8b57e
1570
+ REMOVE_PRIVATE_LINK: new Uint8Array([223, 216, 181, 126]),
1571
+ // revealPrivateLink(bytes32,bytes,bytes32,bytes) → 0x4df1f33d
1572
+ REVEAL_PRIVATE_LINK: new Uint8Array([77, 241, 243, 61]),
1573
+ // setAccountMetadata(bytes,bytes,bytes) → 0x776cf9ff
1574
+ SET_ACCOUNT_METADATA: new Uint8Array([119, 108, 249, 255])
1575
+ };
1576
+ var SP_SEL = {
1577
+ // shield(uint32,uint256,bytes32,bytes) → 0x781442b9
1578
+ SHIELD: new Uint8Array([120, 20, 66, 185]),
1579
+ // privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[]) → 0xdcd5b898
1580
+ PRIVATE_TRANSFER: new Uint8Array([220, 213, 184, 152]),
1581
+ // unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32) → 0xdcf1bff2
1582
+ UNSHIELD: new Uint8Array([220, 241, 191, 242])
1583
+ };
1584
+
1585
+ // src/precompiles/ShieldedPoolPrecompile.ts
1586
+ var ShieldedPoolPrecompile = class {
1587
+ constructor(evm) {
1588
+ this.evm = evm;
1589
+ }
1590
+ addr = PRECOMPILE_ADDR.SHIELDED_POOL;
1591
+ // ─── shield ────────────────────────────────────────────────────────────────
1592
+ /**
1593
+ * Returns the ABI-encoded calldata for `shield(uint32, uint256, bytes32, bytes)`.
1594
+ * Useful when you need to inspect or batch the calldata before sending.
1595
+ */
1596
+ buildShieldCalldata(params) {
1597
+ const memo = params.encryptedMemo ?? EncryptedMemo.dummy();
1598
+ const commitment = fromHex(params.commitment);
1599
+ return encodeHex(
1600
+ SP_SEL.SHIELD,
1601
+ { type: "uint", value: BigInt(params.assetId) },
1602
+ { type: "uint", value: params.amount },
1603
+ { type: "bytes32", value: commitment },
1604
+ { type: "bytes", value: memo }
1605
+ );
1606
+ }
1607
+ /**
1608
+ * Deposits tokens into the shielded pool from an EVM transaction.
1609
+ *
1610
+ * The EVM caller's address is deterministically mapped to a Substrate
1611
+ * AccountId32 (`H160 ++ [0x00; 12]`). The pool deducts from that account.
1612
+ *
1613
+ * Extrinsic: `shieldedPool.shield(assetId, amount, commitment, encryptedMemo)`
1614
+ */
1615
+ async shield(params, signer) {
1616
+ return signer({ to: this.addr, data: this.buildShieldCalldata(params) });
1617
+ }
1618
+ // ─── privateTransfer ───────────────────────────────────────────────────────
1619
+ /**
1620
+ * Returns the ABI-encoded calldata for
1621
+ * `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[])`.
1622
+ */
1623
+ buildPrivateTransferCalldata(params) {
1624
+ const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
1625
+ const commitments = params.outputs.map((o) => fromHex(o.commitment));
1626
+ const memos = params.outputs.map((o) => o.encryptedMemo ?? EncryptedMemo.dummy());
1627
+ const root = fromHex(params.merkleRoot);
1628
+ return encodeHex(
1629
+ SP_SEL.PRIVATE_TRANSFER,
1630
+ { type: "bytes", value: params.proof },
1631
+ { type: "bytes32", value: root },
1632
+ { type: "bytes32[]", value: nullifiers },
1633
+ { type: "bytes32[]", value: commitments },
1634
+ { type: "bytes[]", value: memos }
1635
+ );
1636
+ }
1637
+ /**
1638
+ * Submits a private transfer within the shielded pool from an EVM transaction.
1639
+ *
1640
+ * The EVM caller identity is **irrelevant to the ZK proof** — the sender is
1641
+ * hidden by design. Any EVM address (including a relayer) can submit a valid proof.
1642
+ *
1643
+ * Extrinsic: `shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos)`
1644
+ */
1645
+ async privateTransfer(params, signer) {
1646
+ return signer({ to: this.addr, data: this.buildPrivateTransferCalldata(params) });
1647
+ }
1648
+ // ─── unshield ──────────────────────────────────────────────────────────────
1649
+ /**
1650
+ * Params for an `unshield` call via the EVM precompile.
1651
+ * The `recipient` is a full 32-byte AccountId32 (Substrate account or
1652
+ * EeSuffix-derived: `H160 ++ [0x00; 12]`).
1653
+ */
1654
+ buildUnshieldCalldata(params) {
1655
+ const proof = params.proof;
1656
+ const root = fromHex(params.merkleRoot);
1657
+ const nullifier = fromHex(params.nullifier);
1658
+ const recipientRaw = params.recipientAddress.startsWith("0x") ? params.recipientAddress.slice(2) : params.recipientAddress;
1659
+ const recipientBytes = fromHex(
1660
+ "0x" + (recipientRaw.length === 64 ? recipientRaw : recipientRaw.padEnd(64, "0"))
1661
+ );
1662
+ return encodeHex(
1663
+ SP_SEL.UNSHIELD,
1664
+ { type: "bytes", value: proof },
1665
+ { type: "bytes32", value: root },
1666
+ { type: "bytes32", value: nullifier },
1667
+ { type: "uint", value: BigInt(params.assetId) },
1668
+ { type: "uint", value: params.amount },
1669
+ { type: "bytes32", value: recipientBytes }
1670
+ );
1671
+ }
1672
+ /**
1673
+ * Withdraws tokens from the shielded pool to a recipient account.
1674
+ *
1675
+ * `params.recipientAddress` must be a 0x-prefixed 64-hex-char AccountId32.
1676
+ * To send to an EVM address, use `evmToImplicitSubstrate(evmAddr)` from
1677
+ * `@orbinum/sdk` to derive the AccountId32 first.
1678
+ *
1679
+ * Extrinsic: `shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient)`
1680
+ */
1681
+ async unshield(params, signer) {
1682
+ return signer({ to: this.addr, data: this.buildUnshieldCalldata(params) });
1683
+ }
1684
+ // ─── Gas estimation ────────────────────────────────────────────────────────
1685
+ /**
1686
+ * Estimates the EVM gas for a `shield` call without submitting.
1687
+ * Requires `from` to be set to the actual sender address.
1688
+ */
1689
+ async estimateShieldGas(params, from) {
1690
+ return this.evm.estimateGas({
1691
+ from,
1692
+ to: this.addr,
1693
+ data: this.buildShieldCalldata(params)
1694
+ });
1695
+ }
1696
+ /**
1697
+ * Estimates the EVM gas for a `privateTransfer` call.
1698
+ */
1699
+ async estimatePrivateTransferGas(params, from) {
1700
+ return this.evm.estimateGas({
1701
+ from,
1702
+ to: this.addr,
1703
+ data: this.buildPrivateTransferCalldata(params)
1704
+ });
1705
+ }
1706
+ /**
1707
+ * Estimates the EVM gas for an `unshield` call.
1708
+ */
1709
+ async estimateUnshieldGas(params, from) {
1710
+ return this.evm.estimateGas({
1711
+ from,
1712
+ to: this.addr,
1713
+ data: this.buildUnshieldCalldata(params)
1714
+ });
1715
+ }
1716
+ };
1717
+
1718
+ // src/precompiles/AccountMappingPrecompile.ts
1719
+ var AccountMappingPrecompile = class {
1720
+ constructor(evm) {
1721
+ this.evm = evm;
1722
+ }
1723
+ addr = PRECOMPILE_ADDR.ACCOUNT_MAPPING;
1724
+ // ─── Read-only ─────────────────────────────────────────────────────────────
1725
+ /**
1726
+ * Resolves `@alias` to its owner EVM address (and optionally a secondary EVM address).
1727
+ *
1728
+ * Returns `(address owner, address evmAddress)` — two 32-byte ABI-encoded slots.
1729
+ * `evmAddress` is zero-address (`0x000...0`) if the owner has no explicit EVM address.
1730
+ */
1731
+ async resolveAlias(alias) {
1732
+ try {
1733
+ const data = encodeHex(AM_SEL.RESOLVE_ALIAS, { type: "string", value: alias });
1734
+ const raw = hexToBytes(await this.evm.call(this.addr, data));
1735
+ if (raw.length < 64) return null;
1736
+ const owner = decodeAddress2(raw, 0);
1737
+ const evm = decodeAddress2(raw, 32);
1738
+ const ZERO = "0x0000000000000000000000000000000000000000";
1739
+ return {
1740
+ owner,
1741
+ evmAddress: evm === ZERO ? null : normalizeEvmAddress(evm)
1742
+ };
1743
+ } catch {
1744
+ return null;
1745
+ }
1746
+ }
1747
+ /**
1748
+ * Returns the alias registered for the given EVM address, or null.
1749
+ * The precompile ABI encodes the alias as `bytes` (UTF-8).
1750
+ */
1751
+ async getAliasOf(evmAddress) {
1752
+ try {
1753
+ const data = encodeHex(AM_SEL.GET_ALIAS_OF, {
1754
+ type: "address",
1755
+ value: normalizeEvmAddress(evmAddress)
1756
+ });
1757
+ const raw = hexToBytes(await this.evm.call(this.addr, data));
1758
+ if (raw.length === 0) return null;
1759
+ const alias = decodeString(raw, 0);
1760
+ return alias.length > 0 ? alias : null;
1761
+ } catch {
1762
+ return null;
1763
+ }
1764
+ }
1765
+ /**
1766
+ * Returns true if the given Poseidon commitment is registered as a private
1767
+ * link for the given alias.
1768
+ */
1769
+ async hasPrivateLink(alias, commitment) {
1770
+ try {
1771
+ const commitmentBytes = fromHex(ensureHexPrefix(commitment));
1772
+ const data = encodeHex(
1773
+ AM_SEL.HAS_PRIVATE_LINK,
1774
+ { type: "string", value: alias },
1775
+ { type: "bytes32", value: commitmentBytes }
1776
+ );
1777
+ const raw = hexToBytes(await this.evm.call(this.addr, data));
1778
+ if (raw.length < 32) return false;
1779
+ return decodeBool(raw, 0);
1780
+ } catch {
1781
+ return false;
1782
+ }
1783
+ }
1784
+ // ─── No-arg writes ─────────────────────────────────────────────────────────
1785
+ /**
1786
+ * Creates an explicit EVM → Substrate account mapping for the signer's address.
1787
+ * Extrinsic: `accountMapping.mapAccount()`
1788
+ */
1789
+ async mapAccount(signer) {
1790
+ return signer({ to: this.addr, data: encodeHex(AM_SEL.MAP_ACCOUNT) });
1791
+ }
1792
+ /**
1793
+ * Removes the EVM → Substrate mapping for the signer's address.
1794
+ * Extrinsic: `accountMapping.unmapAccount()`
1795
+ */
1796
+ async unmapAccount(signer) {
1797
+ return signer({ to: this.addr, data: encodeHex(AM_SEL.UNMAP_ACCOUNT) });
1798
+ }
1799
+ /**
1800
+ * Releases the signer's registered alias, recovering the deposit.
1801
+ * Extrinsic: `accountMapping.releaseAlias()`
1802
+ */
1803
+ async releaseAlias(signer) {
1804
+ return signer({ to: this.addr, data: encodeHex(AM_SEL.RELEASE_ALIAS) });
1805
+ }
1806
+ /**
1807
+ * Cancels an active alias sale listing.
1808
+ * Extrinsic: `accountMapping.cancelSale()`
1809
+ */
1810
+ async cancelSale(signer) {
1811
+ return signer({ to: this.addr, data: encodeHex(AM_SEL.CANCEL_SALE) });
1812
+ }
1813
+ // ─── Writes with arguments ─────────────────────────────────────────────────
1814
+ /**
1815
+ * Registers a unique @alias for the signer's account.
1816
+ * Requires a deposit. The alias must be 3–32 ASCII lowercase alphanumeric chars + hyphens.
1817
+ * Extrinsic: `accountMapping.registerAlias(alias)`
1818
+ */
1819
+ async registerAlias(alias, signer) {
1820
+ const data = encodeHex(AM_SEL.REGISTER_ALIAS, { type: "string", value: alias });
1821
+ return signer({ to: this.addr, data });
1822
+ }
1823
+ /**
1824
+ * Transfers the signer's alias to a new EVM `owner` address.
1825
+ * Extrinsic: `accountMapping.transferAlias(newOwner)`
1826
+ */
1827
+ async transferAlias(newOwnerEvmAddress, signer) {
1828
+ const data = encodeHex(AM_SEL.TRANSFER_ALIAS, {
1829
+ type: "address",
1830
+ value: normalizeEvmAddress(newOwnerEvmAddress)
1831
+ });
1832
+ return signer({ to: this.addr, data });
1833
+ }
1834
+ /**
1835
+ * Purchases an alias currently listed for sale.
1836
+ * Extrinsic: `accountMapping.buyAlias(alias)`
1837
+ */
1838
+ async buyAlias(alias, signer) {
1839
+ const data = encodeHex(AM_SEL.BUY_ALIAS, { type: "string", value: alias });
1840
+ return signer({ to: this.addr, data });
1841
+ }
1842
+ /**
1843
+ * Lists the signer's alias for sale on the alias marketplace.
1844
+ *
1845
+ * @param price Asking price in planck (ORB).
1846
+ * @param allowedBuyers Whitelist of EVM addresses allowed to buy.
1847
+ * Pass an empty array for a public (open) listing.
1848
+ * Extrinsic: `accountMapping.putAliasOnSale(price, allowedBuyers)`
1849
+ */
1850
+ async putAliasOnSale(price, allowedBuyers, signer) {
1851
+ const data = encodeHex(
1852
+ AM_SEL.PUT_ALIAS_ON_SALE,
1853
+ { type: "uint", value: price },
1854
+ { type: "address[]", value: allowedBuyers.map(normalizeEvmAddress) }
1855
+ );
1856
+ return signer({ to: this.addr, data });
1857
+ }
1858
+ /**
1859
+ * Removes the external-chain link for the given chain ID.
1860
+ * Extrinsic: `accountMapping.removeChainLink(chainId)`
1861
+ */
1862
+ async removeChainLink(chainId, signer) {
1863
+ const data = encodeHex(AM_SEL.REMOVE_CHAIN_LINK, { type: "uint", value: BigInt(chainId) });
1864
+ return signer({ to: this.addr, data });
1865
+ }
1866
+ /**
1867
+ * Adds a verified public link to an external-chain wallet.
1868
+ *
1869
+ * @param chainId Orbinum chain ID (use `SLIP0044_NAMESPACE | coinType` for SLIP-0044).
1870
+ * @param externalAddr External wallet address bytes (20 bytes for EVM, 32 for Solana).
1871
+ * @param signature Signature over the caller's AccountId32:
1872
+ * - EIP-191 (EVM): 65 bytes over keccak256("\x19Ethereum Signed Message:\n32" + accountId32)
1873
+ * - Ed25519 (Solana): 64 bytes over the raw accountId32 bytes
1874
+ *
1875
+ * Extrinsic: `accountMapping.addChainLink(chainId, address, signature)`
1876
+ */
1877
+ async addChainLink(chainId, externalAddr, signature, signer) {
1878
+ const data = encodeHex(
1879
+ AM_SEL.ADD_CHAIN_LINK,
1880
+ { type: "uint", value: BigInt(chainId) },
1881
+ { type: "bytes", value: externalAddr },
1882
+ { type: "bytes", value: signature }
1883
+ );
1884
+ return signer({ to: this.addr, data });
1885
+ }
1886
+ /**
1887
+ * Registers a private chain link — only the Poseidon commitment is stored.
1888
+ * The real external address is never revealed on-chain.
1889
+ *
1890
+ * @param chainId External chain ID.
1891
+ * @param commitment 0x-prefixed 32-byte Poseidon commitment hex.
1892
+ *
1893
+ * Extrinsic: `accountMapping.registerPrivateLink(chainId, commitment)`
1894
+ */
1895
+ async registerPrivateLink(chainId, commitment, signer) {
1896
+ const commitmentBytes = fromHex(
1897
+ commitment.startsWith("0x") ? commitment : "0x" + commitment
1898
+ );
1899
+ const data = encodeHex(
1900
+ AM_SEL.REGISTER_PRIVATE_LINK,
1901
+ { type: "uint", value: BigInt(chainId) },
1902
+ { type: "bytes32", value: commitmentBytes }
1903
+ );
1904
+ return signer({ to: this.addr, data });
1905
+ }
1906
+ /**
1907
+ * Removes a private link by its commitment.
1908
+ * Extrinsic: `accountMapping.removePrivateLink(commitment)`
1909
+ */
1910
+ async removePrivateLink(commitment, signer) {
1911
+ const commitmentBytes = fromHex(
1912
+ commitment.startsWith("0x") ? commitment : "0x" + commitment
1913
+ );
1914
+ const data = encodeHex(AM_SEL.REMOVE_PRIVATE_LINK, {
1915
+ type: "bytes32",
1916
+ value: commitmentBytes
1917
+ });
1918
+ return signer({ to: this.addr, data });
1919
+ }
1920
+ /**
1921
+ * Reveals a private link publicly by providing the real address and blinding.
1922
+ * After this call the link becomes a public chain link.
1923
+ *
1924
+ * @param commitment 32-byte commitment hex.
1925
+ * @param address External address bytes (the actual wallet address).
1926
+ * @param blinding 32-byte blinding factor used when computing the commitment.
1927
+ * @param signature Signature over the AccountId32 bytes (same rules as `addChainLink`).
1928
+ *
1929
+ * Extrinsic: `accountMapping.revealPrivateLink(commitment, address, blinding, signature)`
1930
+ */
1931
+ async revealPrivateLink(commitment, address, blinding, signature, signer) {
1932
+ const commitmentBytes = fromHex(
1933
+ commitment.startsWith("0x") ? commitment : "0x" + commitment
1934
+ );
1935
+ const blindingBytes = fromHex(blinding.startsWith("0x") ? blinding : "0x" + blinding);
1936
+ const data = encodeHex(
1937
+ AM_SEL.REVEAL_PRIVATE_LINK,
1938
+ { type: "bytes32", value: commitmentBytes },
1939
+ { type: "bytes", value: address },
1940
+ { type: "bytes32", value: blindingBytes },
1941
+ { type: "bytes", value: signature }
1942
+ );
1943
+ return signer({ to: this.addr, data });
1944
+ }
1945
+ /**
1946
+ * Updates the signer's public profile metadata.
1947
+ * Pass `null` for any field to leave it unchanged.
1948
+ *
1949
+ * Extrinsic: `accountMapping.setAccountMetadata(displayName, bio, avatar)`
1950
+ */
1951
+ async setAccountMetadata(displayName, bio, avatar, signer) {
1952
+ const enc = (v) => v != null ? new TextEncoder().encode(v) : new Uint8Array(0);
1953
+ const data = encodeHex(
1954
+ AM_SEL.SET_ACCOUNT_METADATA,
1955
+ { type: "bytes", value: enc(displayName) },
1956
+ { type: "bytes", value: enc(bio) },
1957
+ { type: "bytes", value: enc(avatar) }
1958
+ );
1959
+ return signer({ to: this.addr, data });
1960
+ }
1961
+ // ─── Calldata builders (for custom signing / batching) ─────────────────────
1962
+ /** Returns the raw ABI-encoded calldata for `registerAlias`. */
1963
+ buildRegisterAliasCalldata(alias) {
1964
+ return encodeHex(AM_SEL.REGISTER_ALIAS, { type: "string", value: alias });
1965
+ }
1966
+ /** Returns the raw ABI-encoded calldata for `mapAccount`. */
1967
+ buildMapAccountCalldata() {
1968
+ return encodeHex(AM_SEL.MAP_ACCOUNT);
1969
+ }
1970
+ };
1971
+
1972
+ // src/precompiles/CryptoPrecompiles.ts
1973
+ var CryptoPrecompiles = class {
1974
+ constructor(evm) {
1975
+ this.evm = evm;
1976
+ }
1977
+ // ─── ECRecover (0x0001) ───────────────────────────────────────────────────
1978
+ /**
1979
+ * Recovers the Ethereum address from an ECDSA signature.
1980
+ *
1981
+ * Classic Ethereum ECRecover (EIP-spec): input is always 128 bytes:
1982
+ * hash(32) + v_padded(32, v=27 or 28) + r(32) + s(32)
1983
+ *
1984
+ * Returns a 0x-prefixed lowercase 20-byte EVM address.
1985
+ */
1986
+ async ecRecover(hash, v, r, s) {
1987
+ const input = new Uint8Array(128);
1988
+ input.set(hash.slice(0, 32), 0);
1989
+ input[63] = v;
1990
+ input.set(r.slice(0, 32), 64);
1991
+ input.set(s.slice(0, 32), 96);
1992
+ const raw = hexToBytes(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER, toHex(input)));
1993
+ if (raw.length < 32) return "0x" + "00".repeat(20);
1994
+ return "0x" + toHex(raw.slice(12, 32)).slice(2);
1995
+ }
1996
+ /**
1997
+ * Recovers the **full uncompressed public key** (64 bytes, no 0x04 prefix)
1998
+ * from an ECDSA signature.
1999
+ *
2000
+ * Same input format as `ecRecover`. Output is 64 bytes (32-byte X + 32-byte Y).
2001
+ */
2002
+ async ecRecoverPublicKey(hash, v, r, s) {
2003
+ const input = new Uint8Array(128);
2004
+ input.set(hash.slice(0, 32), 0);
2005
+ input[63] = v;
2006
+ input.set(r.slice(0, 32), 64);
2007
+ input.set(s.slice(0, 32), 96);
2008
+ return hexToBytes(await this.evm.call(PRECOMPILE_ADDR.EC_RECOVER_PUBKEY, toHex(input)));
2009
+ }
2010
+ // ─── SHA-256 (0x0002) ─────────────────────────────────────────────────────
2011
+ /**
2012
+ * Computes SHA-256 of arbitrary bytes via EVM precompile.
2013
+ * Returns a 32-byte digest.
2014
+ */
2015
+ async sha256(data) {
2016
+ return hexToBytes(await this.evm.call(PRECOMPILE_ADDR.SHA256, toHex(data)));
2017
+ }
2018
+ // ─── RIPEMD-160 (0x0003) ──────────────────────────────────────────────────
2019
+ /**
2020
+ * Computes RIPEMD-160 of arbitrary bytes via EVM precompile.
2021
+ * Returns the 20-byte digest right-padded to 32 bytes (standard ABI output).
2022
+ */
2023
+ async ripemd160(data) {
2024
+ const raw = hexToBytes(await this.evm.call(PRECOMPILE_ADDR.RIPEMD160, toHex(data)));
2025
+ return raw.length >= 32 ? raw.slice(12, 32) : raw;
2026
+ }
2027
+ // ─── Identity (0x0004) ────────────────────────────────────────────────────
2028
+ /**
2029
+ * Data copy via EVM precompile (identity). Returns the input unchanged.
2030
+ * Mainly useful for gas benchmarking.
2031
+ */
2032
+ async identity(data) {
2033
+ return hexToBytes(await this.evm.call(PRECOMPILE_ADDR.IDENTITY, toHex(data)));
2034
+ }
2035
+ // ─── SHA3-FIPS-256 / Keccak-256 (0x0400) ─────────────────────────────────
2036
+ /**
2037
+ * Computes Keccak-256 (= SHA3-FIPS-256 as used by Ethereum) of arbitrary bytes.
2038
+ * Returns a 32-byte digest.
2039
+ */
2040
+ async keccak256(data) {
2041
+ return hexToBytes(await this.evm.call(PRECOMPILE_ADDR.SHA3_FIPS256, toHex(data)));
2042
+ }
2043
+ // ─── Curve25519 / Ristretto (0x0402, 0x0403) ─────────────────────────────
2044
+ /**
2045
+ * Adds up to 10 Ristretto (Curve25519) compressed points via EVM precompile.
2046
+ *
2047
+ * Input: N × 32-byte CompressedRistretto points concatenated (N ≤ 10).
2048
+ * Output: 32-byte CompressedRistretto sum.
2049
+ *
2050
+ * Useful for ZK protocols that require verifiable Pedersen commitments.
2051
+ */
2052
+ async curve25519Add(points) {
2053
+ if (points.length === 0 || points.length > 10) {
2054
+ throw new Error(`curve25519Add: expected 1\u201310 points, got ${points.length}`);
2055
+ }
2056
+ const input = new Uint8Array(points.length * 32);
2057
+ for (let i = 0; i < points.length; i++) {
2058
+ const pt = points[i];
2059
+ if (!pt || pt.length !== 32) {
2060
+ throw new Error(`curve25519Add: point[${i}] must be exactly 32 bytes`);
2061
+ }
2062
+ input.set(pt, i * 32);
2063
+ }
2064
+ return hexToBytes(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_ADD, toHex(input)));
2065
+ }
2066
+ /**
2067
+ * Multiplies a Ristretto compressed point by a scalar via EVM precompile.
2068
+ *
2069
+ * Input: 32-byte scalar (little-endian) + 32-byte CompressedRistretto point.
2070
+ * Output: 32-byte CompressedRistretto result.
2071
+ *
2072
+ * Useful for computing key images and Pedersen commitments in ZK protocols.
2073
+ */
2074
+ async curve25519ScalarMul(scalar, point) {
2075
+ if (scalar.length !== 32) throw new Error("curve25519ScalarMul: scalar must be 32 bytes");
2076
+ if (point.length !== 32) throw new Error("curve25519ScalarMul: point must be 32 bytes");
2077
+ const input = new Uint8Array(64);
2078
+ input.set(scalar, 0);
2079
+ input.set(point, 32);
2080
+ return hexToBytes(await this.evm.call(PRECOMPILE_ADDR.CURVE25519_SCALAR_MUL, toHex(input)));
2081
+ }
2082
+ };
2083
+
2084
+ // src/client.ts
2085
+ var OrbinumClient = class _OrbinumClient {
2086
+ /** Raw access to the Substrate WebSocket connection and RPC. */
2087
+ substrate;
2088
+ /** Raw access to the EVM HTTP JSON-RPC endpoint (if configured). */
2089
+ evm;
2090
+ /** Shielded-pool operations: shield, unshield, privateTransfer, and merkle queries. */
2091
+ shieldedPool;
2092
+ /** General chain queries: node info, identity resolution. */
2093
+ chain;
2094
+ /** Account mapping: aliases, chain links, metadata, marketplace, and identity extrinsics. */
2095
+ accountMapping;
2096
+ /**
2097
+ * EVM precompiles: shielded pool + account mapping callable from an EVM wallet.
2098
+ * Only available when `evmRpc` is configured. Methods throw if `evm` is null.
2099
+ */
2100
+ precompiles;
2101
+ constructor(substrate, evm) {
2102
+ this.substrate = substrate;
2103
+ this.evm = evm;
2104
+ const merkle = new MerkleModule(substrate);
2105
+ this.shieldedPool = new ShieldedPoolModule(substrate, merkle);
2106
+ this.chain = new ChainModule(substrate, evm);
2107
+ this.accountMapping = new AccountMappingModule(substrate);
2108
+ this.precompiles = evm ? {
2109
+ shieldedPool: new ShieldedPoolPrecompile(evm),
2110
+ accountMapping: new AccountMappingPrecompile(evm),
2111
+ crypto: new CryptoPrecompiles(evm)
2112
+ } : null;
2113
+ }
2114
+ /**
2115
+ * Connects to an Orbinum node and returns a ready-to-use `OrbinumClient`.
2116
+ * Throws if the Substrate node is unreachable within `connectTimeoutMs`.
2117
+ */
2118
+ static async connect(config) {
2119
+ const substrate = await SubstrateClient.connect(
2120
+ config.substrateWs,
2121
+ config.connectTimeoutMs ?? 15e3
2122
+ );
2123
+ const evm = config.evmRpc ? new EvmClient(config.evmRpc) : null;
2124
+ return new _OrbinumClient(substrate, evm);
2125
+ }
2126
+ /**
2127
+ * Convenience getter for the Merkle module (shortcut for `shieldedPool.merkle`).
2128
+ */
2129
+ get merkle() {
2130
+ return this.shieldedPool.merkle;
2131
+ }
2132
+ /** Closes the WebSocket connection to the Substrate node. */
2133
+ destroy() {
2134
+ this.substrate.destroy();
2135
+ }
2136
+ };
2137
+
2138
+ // src/shielded-pool/NoteDecryptor.ts
2139
+ var import_poseidon_lite2 = require("poseidon-lite");
2140
+ function tryDecryptNote(commitment, viewingKey, spendingKey) {
2141
+ if (!commitment.encryptedMemo) return null;
2142
+ let commitmentBytes;
2143
+ let memoBytes;
2144
+ try {
2145
+ commitmentBytes = fromHex(commitment.commitmentHex);
2146
+ memoBytes = fromHex(commitment.encryptedMemo);
2147
+ } catch {
2148
+ return null;
2149
+ }
2150
+ const plaintext = EncryptedMemo.decrypt(memoBytes, commitmentBytes, viewingKey);
2151
+ if (!plaintext) return null;
2152
+ const recomputed = (0, import_poseidon_lite2.poseidon4)([
2153
+ plaintext.value,
2154
+ plaintext.assetId,
2155
+ plaintext.ownerPk,
2156
+ plaintext.blinding
2157
+ ]);
2158
+ if (recomputed !== bytesToBigintLE(commitmentBytes)) return null;
2159
+ const nullifier = (0, import_poseidon_lite2.poseidon2)([recomputed, spendingKey]);
2160
+ return {
2161
+ value: plaintext.value,
2162
+ assetId: plaintext.assetId,
2163
+ ownerPk: plaintext.ownerPk,
2164
+ blinding: plaintext.blinding,
2165
+ spendingKey,
2166
+ spent: false,
2167
+ spentAt: null,
2168
+ commitment: recomputed,
2169
+ nullifier,
2170
+ commitmentHex: toHex(bigintTo32Le(recomputed)),
2171
+ nullifierHex: toHex(bigintTo32Le(nullifier)),
2172
+ memo: Array.from(memoBytes)
2173
+ };
2174
+ }
2175
+
2176
+ // src/shielded-pool/PrivacyKeys.ts
2177
+ var import_hkdf = require("@noble/hashes/hkdf.js");
2178
+ var import_sha22 = require("@noble/hashes/sha2.js");
2179
+ var import_baby_jubjub = require("@zk-kit/baby-jubjub");
2180
+ var BN254_R = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
2181
+ var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
2182
+ function deriveSpendingKeyMessage(chainId, address) {
2183
+ return `orbinum-spending-key-v1
2184
+ ${chainId}
2185
+ ${address.toLowerCase()}`;
2186
+ }
2187
+ async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
2188
+ const hex = signatureHex.startsWith("0x") ? signatureHex.slice(2) : signatureHex;
2189
+ const sigBytes = new Uint8Array((hex.match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
2190
+ const info = new TextEncoder().encode(`orbinum-sk-v1:${chainId}:${address.toLowerCase()}`);
2191
+ const skBytes = (0, import_hkdf.hkdf)(import_sha22.sha256, sigBytes, new Uint8Array(0), info, 32);
2192
+ const skBigint = BigInt(
2193
+ "0x" + Array.from(skBytes).map((b) => b.toString(16).padStart(2, "0")).join("")
2194
+ ) % BN254_R;
2195
+ return skBigint === 0n ? 1n : skBigint;
2196
+ }
2197
+ function deriveViewingKey(spendingKey) {
2198
+ const ikm = bigintTo32Le(spendingKey);
2199
+ return (0, import_hkdf.hkdf)(import_sha22.sha256, ikm, void 0, IVK_DOMAIN, 32);
2200
+ }
2201
+ function deriveOwnerPk(spendingKey) {
2202
+ try {
2203
+ const pubPoint = (0, import_baby_jubjub.mulPointEscalar)(import_baby_jubjub.Base8, spendingKey);
2204
+ return pubPoint[0];
2205
+ } catch {
2206
+ return 0n;
2207
+ }
2208
+ }
2209
+
2210
+ // src/shielded-pool/PrivacyKeyManager.ts
2211
+ var BN254_R2 = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
2212
+ var _state = {
2213
+ spendingKey: null,
2214
+ viewingKey: null,
2215
+ ownerPk: null
2216
+ };
2217
+ var PrivacyKeyManager = {
2218
+ /**
2219
+ * Load a spending key into the in-memory session.
2220
+ * Derives viewingKey and ownerPk immediately.
2221
+ * Replaces any previously loaded key.
2222
+ */
2223
+ async load(spendingKey) {
2224
+ const viewingKey = deriveViewingKey(spendingKey);
2225
+ const ownerPk = deriveOwnerPk(spendingKey);
2226
+ _state = { spendingKey, viewingKey, ownerPk };
2227
+ },
2228
+ /** Clear all key material from memory. Call on vault lock / sign-out. */
2229
+ clear() {
2230
+ _state = { spendingKey: null, viewingKey: null, ownerPk: null };
2231
+ },
2232
+ /** Returns true if a spending key has been loaded. */
2233
+ isLoaded() {
2234
+ return _state.spendingKey !== null;
2235
+ },
2236
+ /** Returns the spending key. Throws if not loaded. */
2237
+ getSpendingKey() {
2238
+ if (_state.spendingKey === null) {
2239
+ throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
2240
+ }
2241
+ return _state.spendingKey;
2242
+ },
2243
+ /** Returns the 32-byte viewing key. Throws if not loaded. */
2244
+ getViewingKey() {
2245
+ if (_state.viewingKey === null) {
2246
+ throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
2247
+ }
2248
+ return _state.viewingKey;
2249
+ },
2250
+ /** Returns the BabyJubJub owner public key (x-coordinate). Throws if not loaded. */
2251
+ getOwnerPk() {
2252
+ if (_state.ownerPk === null) {
2253
+ throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
2254
+ }
2255
+ return _state.ownerPk;
2256
+ },
2257
+ /** Returns the spending key as a 32-byte little-endian Uint8Array. Throws if not loaded. */
2258
+ getSpendingKeyBytes() {
2259
+ return bigintTo32Le(this.getSpendingKey());
2260
+ },
2261
+ /** Exports the spending key as a 0x-prefixed 64-char hex string. Throws if not loaded. */
2262
+ exportHex() {
2263
+ return "0x" + this.getSpendingKey().toString(16).padStart(64, "0");
2264
+ },
2265
+ /**
2266
+ * Load a spending key from a 0x-prefixed or bare hex string.
2267
+ * Validates the key is in the valid range [1, BN254_R).
2268
+ */
2269
+ async importFromHex(hex) {
2270
+ const key = BigInt(hex.startsWith("0x") ? hex : "0x" + hex);
2271
+ if (key === 0n || key >= BN254_R2) {
2272
+ throw new Error("PrivacyKeyManager: invalid spending key \u2014 out of BN254 range.");
2273
+ }
2274
+ await this.load(key);
2275
+ }
2276
+ };
2277
+
2278
+ // src/shielded-pool/VaultCrypto.ts
2279
+ function vaultReplacer(_key, value) {
2280
+ if (typeof value === "bigint") return { __bigint: value.toString() };
2281
+ return value;
2282
+ }
2283
+ function vaultReviver(_key, value) {
2284
+ if (value !== null && typeof value === "object" && "__bigint" in value) {
2285
+ return BigInt(value.__bigint);
2286
+ }
2287
+ return value;
2288
+ }
2289
+ function toBase64(buf) {
2290
+ const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
2291
+ let str = "";
2292
+ for (const b of bytes) str += String.fromCharCode(b);
2293
+ return btoa(str);
2294
+ }
2295
+ function fromBase64(b64) {
2296
+ const bin = atob(b64);
2297
+ const out = new Uint8Array(bin.length);
2298
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
2299
+ return out;
2300
+ }
2301
+ var VAULT_KEY_INFO = new TextEncoder().encode("orbinum-vault-key-v1");
2302
+ var IV_BYTES = 12;
2303
+ async function deriveVaultKey(spendingKeyBytes) {
2304
+ const keyMaterial = await crypto.subtle.importKey(
2305
+ "raw",
2306
+ spendingKeyBytes.slice(0),
2307
+ "HKDF",
2308
+ false,
2309
+ ["deriveKey"]
2310
+ );
2311
+ return crypto.subtle.deriveKey(
2312
+ {
2313
+ name: "HKDF",
2314
+ hash: "SHA-256",
2315
+ salt: new Uint8Array(0),
2316
+ info: VAULT_KEY_INFO
2317
+ },
2318
+ keyMaterial,
2319
+ { name: "AES-GCM", length: 256 },
2320
+ false,
2321
+ ["encrypt", "decrypt"]
2322
+ );
2323
+ }
2324
+ async function encryptJson(key, payload) {
2325
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
2326
+ const plaintext = new TextEncoder().encode(JSON.stringify(payload, vaultReplacer));
2327
+ const ciphertextBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext);
2328
+ return { iv: toBase64(iv), ciphertext: toBase64(ciphertextBuf) };
2329
+ }
2330
+ async function decryptJson(key, iv, ciphertext) {
2331
+ const plainBuf = await crypto.subtle.decrypt(
2332
+ { name: "AES-GCM", iv: new Uint8Array(fromBase64(iv)) },
2333
+ key,
2334
+ new Uint8Array(fromBase64(ciphertext))
2335
+ );
2336
+ return JSON.parse(new TextDecoder().decode(plainBuf), vaultReviver);
2337
+ }
2338
+
2339
+ // src/types.ts
2340
+ var SLIP0044_NAMESPACE = 2147483648;
2341
+
2342
+ // src/index.ts
2343
+ var import_signer = require("polkadot-api/signer");
2344
+ var import_pjs_signer = require("polkadot-api/pjs-signer");
2345
+ // Annotate the CommonJS export names for ESM import in node:
2346
+ 0 && (module.exports = {
2347
+ AccountMappingModule,
2348
+ AccountMappingPrecompile,
2349
+ ChainModule,
2350
+ CryptoPrecompiles,
2351
+ EncryptedMemo,
2352
+ EvmClient,
2353
+ MerkleModule,
2354
+ NoteBuilder,
2355
+ OrbinumClient,
2356
+ PRECOMPILE_ADDR,
2357
+ PrivacyKeyManager,
2358
+ SLIP0044_NAMESPACE,
2359
+ ShieldedPoolModule,
2360
+ ShieldedPoolPrecompile,
2361
+ SubstrateClient,
2362
+ accountIdHexToSs58,
2363
+ addressToAccountIdHex,
2364
+ bigintTo32Be,
2365
+ bigintTo32Le,
2366
+ bigintTo32LeArr,
2367
+ bytesToBigintLE,
2368
+ computePathIndices,
2369
+ decryptJson,
2370
+ deriveOwnerPk,
2371
+ deriveSpendingKeyFromSignature,
2372
+ deriveSpendingKeyMessage,
2373
+ deriveVaultKey,
2374
+ deriveViewingKey,
2375
+ encryptJson,
2376
+ ensureHexPrefix,
2377
+ evmAddressToAccountId,
2378
+ evmToImplicitSubstrate,
2379
+ evmToSubstrate,
2380
+ fromHex,
2381
+ getPolkadotSigner,
2382
+ getPolkadotSignerFromPjs,
2383
+ implicitSubstrateToEvm,
2384
+ isEvmAddress,
2385
+ isImplicitEvmAccount,
2386
+ isSs58,
2387
+ isSubstrateAddress,
2388
+ isUnifiedAddress,
2389
+ leHexToBigint,
2390
+ normalizeEvmAddress,
2391
+ substrateSs58ToAccountIdHex,
2392
+ substrateToEvm,
2393
+ toHex,
2394
+ tryDecryptNote,
2395
+ vaultReplacer,
2396
+ vaultReviver
2397
+ });