@orbinum/sdk 0.3.0 → 0.4.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Official TypeScript SDK for Orbinum — a privacy-focused blockchain built on Substrate with an EVM compatibility layer.
4
4
 
5
- The SDK provides typed modules for interacting with the Orbinum protocol: shielded pool operations, account identity and mapping, chain queries, EVM JSON-RPC, and the indexer REST API.
5
+ The SDK provides typed modules for interacting with the Orbinum protocol: Substrate RPC, EVM JSON-RPC, shielded pool operations, account identity and mapping, `rpc-v2`, ZK verifier, precompiles, and the indexer REST API.
6
6
 
7
7
  ## Installation
8
8
 
@@ -27,9 +27,78 @@ const client = await OrbinumClient.connect({
27
27
  substrateWs: 'ws://localhost:9944',
28
28
  evmRpc: 'http://localhost:9933',
29
29
  });
30
+
31
+ const chainInfo = await client.substrate.getChainInfo();
32
+ const root = await client.rpcV2.privacy.getMerkleRoot();
33
+ const events = await client.substrate.queryBlockEvents('0xabc...');
30
34
  ```
31
35
 
32
- `OrbinumClient` wires together all modules and exposes them under a single interface. Each module can also be instantiated independently.
36
+ `OrbinumClient` wires together the SDK surface under a single interface.
37
+
38
+ - `client.substrate`: Substrate WebSocket RPC, block queries, event decoding, and transaction helpers
39
+ - `client.evm`: EVM JSON-RPC client, or `null` if `evmRpc` was not configured
40
+ - `client.rpcV2`: typed Orbinum `rpc-v2` namespaces
41
+ - `client.shieldedPool`: shielded-pool extrinsics and note helpers
42
+ - `client.accountMapping`: alias, chain-link, metadata, and marketplace operations
43
+ - `client.precompiles`: EVM precompile wrappers, or `null` if `evmRpc` was not configured
44
+
45
+ Each module can also be instantiated independently.
46
+
47
+ ## OrbinumClientProvider
48
+
49
+ `OrbinumClientProvider` wraps `OrbinumClient` with automatic WebSocket reconnection, typed connection status events, and a React-friendly lifecycle.
50
+
51
+ ```ts
52
+ import { OrbinumClientProvider } from '@orbinum/sdk';
53
+
54
+ const provider = new OrbinumClientProvider({
55
+ substrateWs: 'ws://localhost:9944',
56
+ evmRpc: 'http://localhost:9933',
57
+ });
58
+
59
+ provider.on('statusChange', (event) => {
60
+ console.log(event.status); // 'connecting' | 'connected' | 'disconnected' | 'error'
61
+ });
62
+
63
+ await provider.connect();
64
+ const client = provider.client; // OrbinumClient once connected
65
+ ```
66
+
67
+ ## rpc-v2
68
+
69
+ Orbinum's `rpc-v2` is organized by namespace.
70
+
71
+ - Standard Frontier namespaces: `eth_*`, `net_*`, `web3_*`, `txpool_*`, `debug_*`
72
+ - Orbinum-specific namespace: `privacy_*`
73
+
74
+ In the SDK, `rpc-v2` is modeled as a dedicated top-level module, exposed through `client.rpcV2`. Each namespace lives under `src/rpc-v2/`.
75
+
76
+ Current typed coverage:
77
+
78
+ - `client.rpcV2.privacy.getMerkleRoot()`
79
+ - `client.rpcV2.privacy.getMerkleProof(leafIndex)`
80
+ - `client.rpcV2.privacy.getNullifierStatus(nullifier)`
81
+ - `client.rpcV2.privacy.getPoolStats()`
82
+
83
+ Example:
84
+
85
+ ```ts
86
+ const root = await client.rpcV2.privacy.getMerkleRoot();
87
+
88
+ const proof = await client.rpcV2.privacy.getMerkleProof(12);
89
+ // { path: string[]; leafIndex: number; treeDepth: number; }
90
+
91
+ const status = await client.rpcV2.privacy.getNullifierStatus('0x...');
92
+ // { nullifier: string; isSpent: boolean; }
93
+
94
+ const stats = await client.rpcV2.privacy.getPoolStats();
95
+ // { merkleRoot: string; commitmentCount: number; totalBalance: string; ... }
96
+ ```
97
+
98
+ Notes:
99
+
100
+ - `rpc-v2` responses use `snake_case` from the node; the SDK normalizes them to `camelCase`.
101
+ - `u128` values are exposed as decimal strings to avoid precision loss.
33
102
 
34
103
  ## Modules
35
104
 
@@ -42,6 +111,399 @@ import { SubstrateClient } from '@orbinum/sdk';
42
111
 
43
112
  const substrate = await SubstrateClient.connect('ws://localhost:9944');
44
113
 
114
+ const info = await substrate.getChainInfo();
115
+ const health = await substrate.getHealth();
116
+ const version = await substrate.getNodeVersion();
117
+ const genesis = await substrate.getGenesisHash();
118
+
119
+ // Block queries
120
+ const header = await substrate.getBlockHeader('best');
121
+ const hash = await substrate.getBlockHash(1000);
122
+ const block = await substrate.getBlock('0xabc...');
123
+
124
+ // Block events — decoded into EventRecord[]
125
+ const events = await substrate.queryBlockEvents('0xabc...');
126
+
127
+ // Block stream
128
+ substrate.blocks$.subscribe(block => console.log(block.number));
129
+
130
+ // Raw JSON-RPC call
131
+ const result = await substrate.request('system_name', []);
132
+
133
+ // Transaction helpers
134
+ const tx = await substrate.txFromCallData(callBytes);
135
+ const finalized = await tx.signAndSubmit(signer);
136
+ const finalized2 = await substrate.submit(signedHex);
137
+ substrate.submitAndWatch(signedHex).subscribe(event => console.log(event));
138
+ ```
139
+
140
+ ### EvmClient
141
+
142
+ Stateless HTTP client following the Ethereum JSON-RPC specification.
143
+
144
+ ```ts
145
+ import { EvmClient } from '@orbinum/sdk';
146
+
147
+ const evm = new EvmClient('http://localhost:9933');
148
+
149
+ const balance = await evm.getBalance('0xYourAddress');
150
+ const chainId = await evm.getChainId();
151
+ const txHash = await evm.sendRawTransaction(signedHex);
152
+
153
+ // Batch multiple calls in a single HTTP request
154
+ const [bal, nonce] = await evm.batchRequest([
155
+ { method: 'eth_getBalance', params: ['0xAddr', 'latest'] },
156
+ { method: 'eth_getTransactionCount', params: ['0xAddr', 'latest'] },
157
+ ]);
158
+ ```
159
+
160
+ ### EvmExplorer
161
+
162
+ Read-only EVM block and transaction explorer.
163
+
164
+ ```ts
165
+ import { EvmExplorer } from '@orbinum/sdk';
166
+
167
+ const explorer = new EvmExplorer('http://localhost:9933');
168
+
169
+ const block = await explorer.getBlock(12345);
170
+ const tx = await explorer.getTransaction('0xhash...');
171
+ const logs = await explorer.getLogs({ fromBlock: 100, toBlock: 200, address: '0x...' });
172
+ ```
173
+
174
+ Types: `EvmBlock`, `EvmTransaction`, `EvmAddressInfo`, `EvmTxSummary`, `EvmLog`, `TokenInfo`, `TokenTransfer`.
175
+
176
+ ### ZkVerifierModule
177
+
178
+ Typed access to the on-chain ZK verifier — circuit version info, VK hashes, and version history.
179
+
180
+ ```ts
181
+ import { ZkVerifierModule } from '@orbinum/sdk';
182
+
183
+ const zkv = new ZkVerifierModule(substrate);
184
+
185
+ const info = await zkv.getCircuitVersionInfo('circuit-id');
186
+ const vkHash = await zkv.getVkHash('circuit-id', 1);
187
+ const stats = await zkv.getVersionStats('circuit-id');
188
+ const history = await zkv.getHistoricalVersions('circuit-id');
189
+ ```
190
+
191
+ ### ShieldedPoolModule
192
+
193
+ High-level interface for shielded pool extrinsics and note workflows. Requires a loaded `PrivacyKeyManager` and ZK proofs for unshield and private transfer.
194
+
195
+ ```ts
196
+ // Shield (deposit) tokens into the shielded pool
197
+ const { txResult, note } = await client.shieldedPool.buildAndShield({
198
+ amount: 1_000_000n,
199
+ assetId: 1,
200
+ tokenAddress: '0xTokenAddress',
201
+ }, signer);
202
+
203
+ // Private transfer between notes
204
+ await client.shieldedPool.privateTransfer({ ...params }, signer);
205
+
206
+ // Unshield (withdraw) tokens to a public address
207
+ await client.shieldedPool.unshield({ ...params }, signer);
208
+ ```
209
+
210
+ ### PrivacyModule (`rpc-v2`)
211
+
212
+ Typed wrapper for Orbinum `privacy_*` endpoints from `rpc-v2`.
213
+
214
+ ```ts
215
+ import { PrivacyModule } from '@orbinum/sdk';
216
+
217
+ const privacy = new PrivacyModule(substrate);
218
+
219
+ const merkleRoot = await privacy.getMerkleRoot();
220
+ const merkleProof = await privacy.getMerkleProof(4);
221
+ const nullifier = await privacy.getNullifierStatus('0xNullifier');
222
+ const stats = await privacy.getPoolStats();
223
+ ```
224
+
225
+ ### Privacy Keys
226
+
227
+ Key derivation follows a deterministic chain from the user's wallet signature. No key material is ever stored by the SDK.
228
+
229
+ ```ts
230
+ import {
231
+ PrivacyKeyManager,
232
+ deriveOwnerPk,
233
+ deriveSpendingKeyFromSignature,
234
+ deriveSpendingKeyMessage,
235
+ deriveViewingKey,
236
+ } from '@orbinum/sdk';
237
+
238
+ const message = deriveSpendingKeyMessage(chainId, evmAddress);
239
+ const spendingKey = deriveSpendingKeyFromSignature(sigHex, chainId, evmAddress);
240
+
241
+ const keyManager = new PrivacyKeyManager();
242
+ keyManager.load(spendingKey);
243
+
244
+ const viewingKey = deriveViewingKey(spendingKey);
245
+ const ownerPk = deriveOwnerPk(spendingKey);
246
+ ```
247
+
248
+ ### AccountMappingModule
249
+
250
+ Manage on-chain identity by linking Substrate and EVM accounts, registering aliases, and interacting with the alias marketplace.
251
+
252
+ ```ts
253
+ await client.accountMapping.registerAlias({ alias: 'myalias' }, signer);
254
+ await client.accountMapping.addChainLink({ chainId: 1, address: '0xEvmAddr' }, signer);
255
+
256
+ const alias = await client.accountMapping.getAliasOf(substrateHex);
257
+ const linked = await client.accountMapping.getChainLinks(substrateHex);
258
+ ```
259
+
260
+ ### IndexerClient
261
+
262
+ HTTP client for the Orbinum indexer REST API. All list endpoints are paginated.
263
+
264
+ ```ts
265
+ import { IndexerClient } from '@orbinum/sdk';
266
+
267
+ const indexer = new IndexerClient({ baseUrl: 'https://indexer.orbinum.io' });
268
+
269
+ const blocks = await indexer.getBlocks({ page: 1, limit: 20 });
270
+ const extrinsics = await indexer.getExtrinsics({ address: '5F...' });
271
+ const commitments = await indexer.getCommitments({ page: 1, limit: 20 });
272
+ const nullifier = await indexer.getNullifierStatus('0xNullifier');
273
+ const roots = await indexer.getMerkleRoots({ limit: 10 });
274
+ const evmTxs = await indexer.getEvmTransactions({ address: '0xabc...' });
275
+ const stats = await indexer.getStats();
276
+ ```
277
+
278
+ ### Precompiles
279
+
280
+ Typed wrappers for Orbinum's EVM precompile contracts.
281
+
282
+ | Precompile | Address | Description |
283
+ |---|---|---|
284
+ | `ShieldedPoolPrecompile` | `0x0801` | Shield, unshield, and private transfer from EVM |
285
+ | `AccountMappingPrecompile` | `0x0800` | Identity and alias operations from EVM |
286
+ | `CryptoPrecompiles` | `0x0400–0x0403` | Frontier crypto utilities |
287
+
288
+ ```ts
289
+ import { ShieldedPoolPrecompile, decodePrecompileCalldata } from '@orbinum/sdk';
290
+
291
+ const precompile = new ShieldedPoolPrecompile(evmClient);
292
+ await precompile.shield(amount, tokenAddress, commitment, walletSigner);
293
+
294
+ // Decode raw EVM calldata for a known precompile
295
+ const decoded = decodePrecompileCalldata('0x0800', calldata);
296
+ ```
297
+
298
+ ## Extrinsic & Event Decoders
299
+
300
+ The `mapExtrinsicArgs` and `mapZkEventData` helpers decode raw pallet call data from the indexer or block scanner into typed objects.
301
+
302
+ ```ts
303
+ import { mapExtrinsicArgs, mapZkEventData } from '@orbinum/sdk';
304
+
305
+ const decoded = mapExtrinsicArgs('shieldedPool', 'shield', rawArgs);
306
+ // DecodedShieldArgs | DecodedUnshieldArgs | ...
307
+
308
+ const eventData = mapZkEventData('ProofVerified', rawEventData);
309
+ ```
310
+
311
+ ## Substrate SCALE Primitives
312
+
313
+ SCALE codec primitives from `@polkadot-api/substrate-bindings` are re-exported directly from the SDK. There is no need to install that package separately.
314
+
315
+ ```ts
316
+ import { Blake2256, AccountId, u128, u64, Storage, Keccak256 } from '@orbinum/sdk';
317
+ import { base58, getSs58AddressInfo } from '@orbinum/sdk';
318
+ ```
319
+
320
+ ## Key Derivation Chain
321
+
322
+ ```
323
+ wallet.sign(message)
324
+ |
325
+ v
326
+ deriveSpendingKeyFromSignature() -- HKDF-SHA256 over signature bytes, mod BN254_R
327
+ |
328
+ +-- deriveViewingKey() -- HKDF-SHA256 -> 32-byte ChaCha20 symmetric key
329
+ | (used to encrypt/decrypt note memos)
330
+ |
331
+ +-- deriveOwnerPk() -- BabyJubJub scalar multiplication -> Ax
332
+ (embedded in shielded note commitments)
333
+ ```
334
+
335
+ ## Development
336
+
337
+ ### Typing Layout
338
+
339
+ The SDK organizes types by feature ownership.
340
+
341
+ - `types/index.ts`: public shared types of the feature
342
+ - `types/pallet-events.ts`: event payloads and discriminated unions for that pallet
343
+ - `types/pallet-extrinsics.ts`: extrinsic argument types and call unions for that pallet
344
+ - `types/raw.ts`: internal node or RPC response shapes used only for transport mapping
345
+
346
+ Examples:
347
+
348
+ - `src/shielded-pool/types/index.ts`
349
+ - `src/shielded-pool/types/pallet-events.ts`
350
+ - `src/shielded-pool/types/pallet-extrinsics.ts`
351
+ - `src/rpc-v2/types/raw.ts`
352
+ - `src/indexer/types/index.ts`
353
+ - `src/account-mapping/types/index.ts`
354
+
355
+ Rules:
356
+
357
+ 1. If a type is part of the public API of a feature, keep it in that feature's `types/index.ts`.
358
+ 2. If a type only mirrors a transport payload from RPC, keep it in `types/raw.ts`.
359
+ 3. Do not reintroduce a global `src/types.ts` or `src/types/` directory.
360
+ 4. Export public types from `src/index.ts`, but keep ownership at the feature level.
361
+
362
+ ### Extending `rpc-v2`
363
+
364
+ The intended extension pattern is namespace-oriented and centralized under `RpcV2Module`.
365
+
366
+ For example, to add `net_*` support:
367
+
368
+ 1. Create `src/rpc-v2/NetModule.ts`
369
+ 2. Define raw response types that match the node's JSON exactly
370
+ 3. Map raw `snake_case` or hex-shaped values into stable SDK-facing TypeScript types
371
+ 4. Export the module from `src/rpc-v2/index.ts`
372
+ 5. Attach it in `RpcV2Module`, for example as `client.rpcV2.net`
373
+ 6. Add unit tests that verify RPC method names, params, and response mapping
374
+
375
+ ```ts
376
+ export class NetModule {
377
+ constructor(private readonly substrate: SubstrateClient) {}
378
+
379
+ async version(): Promise<string> {
380
+ return this.substrate.request<string>('net_version', []);
381
+ }
382
+ }
383
+ ```
384
+
385
+ ### Commands
386
+
387
+ ```bash
388
+ pnpm install
389
+ pnpm build # compile to dist/ (ESM + CJS + types)
390
+ pnpm test # run test suite (769 tests)
391
+ pnpm typecheck:all # typecheck src and tests
392
+ pnpm lint # eslint
393
+ pnpm format # prettier
394
+ ```
395
+
396
+ ## License
397
+
398
+ ISC
399
+
400
+
401
+ ## Installation
402
+
403
+ ```bash
404
+ npm install @orbinum/sdk
405
+ # or
406
+ pnpm add @orbinum/sdk
407
+ ```
408
+
409
+ ## Requirements
410
+
411
+ - Node.js 18 or later
412
+ - A running Orbinum node (Substrate WebSocket endpoint)
413
+ - An EVM JSON-RPC endpoint (optional, required for EVM and precompile operations)
414
+
415
+ ## Quick Start
416
+
417
+ ```ts
418
+ import { OrbinumClient } from '@orbinum/sdk';
419
+
420
+ const client = await OrbinumClient.connect({
421
+ substrateWs: 'ws://localhost:9944',
422
+ evmRpc: 'http://localhost:9933',
423
+ });
424
+
425
+ const chainInfo = await client.substrate.getChainInfo();
426
+ const root = await client.rpcV2.privacy.getMerkleRoot();
427
+ const tree = await client.shieldedPool.merkle.getTreeInfo();
428
+ ```
429
+
430
+ `OrbinumClient` wires together the current SDK surface under a single interface.
431
+
432
+ - `client.substrate`: Substrate WebSocket RPC and transaction helpers
433
+ - `client.evm`: EVM JSON-RPC client, or `null` if `evmRpc` was not configured
434
+ - `client.rpcV2`: typed Orbinum `rpc-v2` namespaces
435
+ - `client.shieldedPool`: shielded-pool extrinsics, note helpers, and Merkle queries
436
+ - `client.accountMapping`: alias, chain-link, metadata, and marketplace operations
437
+ - `client.precompiles`: EVM precompile wrappers, or `null` if `evmRpc` was not configured
438
+
439
+ Each module can also be instantiated independently.
440
+
441
+ ## rpc-v2
442
+
443
+ Orbinum's `rpc-v2` is organized by namespace.
444
+
445
+ - Standard Frontier namespaces: `eth_*`, `net_*`, `web3_*`, `txpool_*`, `debug_*`
446
+ - Orbinum-specific namespace: `privacy_*`
447
+
448
+ In the SDK, `rpc-v2` is modeled as a dedicated top-level module, exposed through `client.rpcV2`. Each namespace lives under `src/rpc-v2/` and is grouped there instead of being mixed into legacy protocol modules.
449
+
450
+ Current typed coverage:
451
+
452
+ - `client.rpcV2.privacy.getMerkleRoot()`
453
+ - `client.rpcV2.privacy.getMerkleProof(leafIndex)`
454
+ - `client.rpcV2.privacy.getNullifierStatus(nullifier)`
455
+ - `client.rpcV2.privacy.getPoolStats()`
456
+
457
+ Example:
458
+
459
+ ```ts
460
+ const root = await client.rpcV2.privacy.getMerkleRoot();
461
+
462
+ const proof = await client.rpcV2.privacy.getMerkleProof(12);
463
+ // {
464
+ // path: string[];
465
+ // leafIndex: number;
466
+ // treeDepth: number;
467
+ // }
468
+
469
+ const status = await client.rpcV2.privacy.getNullifierStatus('0x...');
470
+ // {
471
+ // nullifier: string;
472
+ // isSpent: boolean;
473
+ // }
474
+
475
+ const stats = await client.rpcV2.privacy.getPoolStats();
476
+ // {
477
+ // merkleRoot: string;
478
+ // commitmentCount: number;
479
+ // totalBalance: string;
480
+ // assetBalances: { assetId: number; balance: string }[];
481
+ // treeDepth: number;
482
+ // }
483
+ ```
484
+
485
+ Notes:
486
+
487
+ - `rpc-v2` responses from the node use `snake_case`; the SDK maps them to `camelCase`.
488
+ - `u128` values are exposed as decimal strings to avoid precision loss in JavaScript.
489
+ - Privacy queries belong to `client.rpcV2.privacy.*`; they are no longer described as part of `ShieldedPoolModule`.
490
+
491
+ ## Modules
492
+
493
+ ### SubstrateClient
494
+
495
+ Thin wrapper over [polkadot-api](https://github.com/polkadot-api/polkadot-api) (PAPI) for Substrate WebSocket communication.
496
+
497
+ ```ts
498
+ import { SubstrateClient } from '@orbinum/sdk';
499
+
500
+ const substrate = await SubstrateClient.connect('ws://localhost:9944');
501
+
502
+ const info = await substrate.getChainInfo();
503
+ const health = await substrate.getHealth();
504
+ const version = await substrate.getNodeVersion();
505
+ const genesis = await substrate.getGenesisHash();
506
+
45
507
  // Raw JSON-RPC call
46
508
  const result = await substrate.request('system_name', []);
47
509
 
@@ -76,25 +538,52 @@ const [balance, nonce] = await evm.batchRequest([
76
538
  ]);
77
539
  ```
78
540
 
541
+ ### RpcV2Module
542
+
543
+ Top-level typed entry point for Orbinum `rpc-v2` namespaces.
544
+
545
+ ```ts
546
+ const root = await client.rpcV2.privacy.getMerkleRoot();
547
+ const proof = await client.rpcV2.privacy.getMerkleProof(4);
548
+ const nullifier = await client.rpcV2.privacy.getNullifierStatus('0xNullifier');
549
+ const stats = await client.rpcV2.privacy.getPoolStats();
550
+ ```
551
+
79
552
  ### ShieldedPoolModule
80
553
 
81
- High-level interface for shielded pool operations. Requires a loaded `PrivacyKeyManager` and ZK proofs for unshield and private transfer.
554
+ High-level interface for shielded pool extrinsics and note workflows. Requires a loaded `PrivacyKeyManager` and ZK proofs for unshield and private transfer.
82
555
 
83
556
  ```ts
84
557
  // Shield (deposit) tokens into the shielded pool
85
558
  const { txResult, note } = await client.shieldedPool.buildAndShield({
86
559
  amount: 1_000_000n,
560
+ assetId: 1,
87
561
  tokenAddress: '0xTokenAddress',
88
562
  }, signer);
89
563
 
564
+ const treeInfo = await client.shieldedPool.merkle.getTreeInfo();
565
+
90
566
  // Private transfer between notes
91
567
  await client.shieldedPool.privateTransfer({ ...params }, signer);
92
568
 
93
569
  // Unshield (withdraw) tokens to a public address
94
570
  await client.shieldedPool.unshield({ ...params }, signer);
95
571
 
96
- // Check if a nullifier has been spent
97
- const spent = await client.shieldedPool.isNullifierSpent('0xNullifier');
572
+ ```
573
+
574
+ ### PrivacyModule (`rpc-v2`)
575
+
576
+ Typed wrapper for Orbinum `privacy_*` endpoints from `rpc-v2`.
577
+
578
+ ```ts
579
+ import { PrivacyModule } from '@orbinum/sdk';
580
+
581
+ const privacy = new PrivacyModule(client.substrate);
582
+
583
+ const merkleRoot = await privacy.getMerkleRoot();
584
+ const merkleProof = await privacy.getMerkleProof(4);
585
+ const nullifier = await privacy.getNullifierStatus('0xNullifier');
586
+ const stats = await privacy.getPoolStats();
98
587
  ```
99
588
 
100
589
  ### Privacy Keys
@@ -102,13 +591,19 @@ const spent = await client.shieldedPool.isNullifierSpent('0xNullifier');
102
591
  Key derivation follows a deterministic chain from the user's wallet signature. No key material is ever stored by the SDK.
103
592
 
104
593
  ```ts
105
- import { PrivacyKeys, PrivacyKeyManager } from '@orbinum/sdk';
594
+ import {
595
+ PrivacyKeyManager,
596
+ deriveOwnerPk,
597
+ deriveSpendingKeyFromSignature,
598
+ deriveSpendingKeyMessage,
599
+ deriveViewingKey,
600
+ } from '@orbinum/sdk';
106
601
 
107
602
  // 1. Get the message the user must sign
108
- const message = PrivacyKeys.deriveSpendingKeyMessage(chainId, evmAddress);
603
+ const message = deriveSpendingKeyMessage(chainId, evmAddress);
109
604
 
110
605
  // 2. Derive the spending key from the wallet signature
111
- const spendingKey = PrivacyKeys.deriveSpendingKeyFromSignature(sigHex, chainId, evmAddress);
606
+ const spendingKey = deriveSpendingKeyFromSignature(sigHex, chainId, evmAddress);
112
607
 
113
608
  // 3. Load into the key manager
114
609
  const keyManager = new PrivacyKeyManager();
@@ -116,8 +611,8 @@ keyManager.load(spendingKey);
116
611
 
117
612
  // The manager derives the viewing key (ChaCha20 memo decryption)
118
613
  // and owner public key (BabyJubJub, used in note commitments)
119
- const viewingKey = keyManager.getViewingKey();
120
- const ownerPk = keyManager.getOwnerPk();
614
+ const viewingKey = deriveViewingKey(spendingKey);
615
+ const ownerPk = deriveOwnerPk(spendingKey);
121
616
  ```
122
617
 
123
618
  ### AccountMappingModule
@@ -136,17 +631,6 @@ const alias = await client.accountMapping.getAliasOf(substrateHex);
136
631
  const linked = await client.accountMapping.getChainLinks(substrateHex);
137
632
  ```
138
633
 
139
- ### ChainModule
140
-
141
- Query chain information bridging Substrate and EVM endpoints.
142
-
143
- ```ts
144
- const info = await client.chain.getChainInfo(); // name, spec, SS58 prefix
145
- const identity = await client.chain.getFullIdentity(address);
146
- const health = await client.chain.getHealth();
147
- const evmBlock = await client.chain.getEvmBlockNumber();
148
- ```
149
-
150
634
  ### IndexerClient
151
635
 
152
636
  HTTP client for the Orbinum indexer REST API. The indexer runs as a separate service indexed from node events.
@@ -154,11 +638,15 @@ HTTP client for the Orbinum indexer REST API. The indexer runs as a separate ser
154
638
  ```ts
155
639
  import { IndexerClient } from '@orbinum/sdk';
156
640
 
157
- const indexer = new IndexerClient('https://indexer.orbinum.io');
641
+ const indexer = new IndexerClient({
642
+ baseUrl: 'https://indexer.orbinum.io',
643
+ });
158
644
 
159
645
  const commitments = await indexer.getCommitments({ page: 1, limit: 20 });
160
646
  const status = await indexer.getNullifierStatus('0xNullifier');
161
647
  const roots = await indexer.getMerkleRoots({ limit: 10 });
648
+ const extrinsics = await indexer.getAddressExtrinsics('5F...');
649
+ const evmTxs = await indexer.getEvmTransactions({ address: '0xabc...' });
162
650
  ```
163
651
 
164
652
  ### Precompiles
@@ -195,6 +683,66 @@ deriveSpendingKeyFromSignature() -- HKDF-SHA256 over signature bytes, mod BN25
195
683
 
196
684
  ## Development
197
685
 
686
+ ### Typing Layout
687
+
688
+ The SDK organizes types by feature ownership.
689
+
690
+ - `types/index.ts`: public shared types of the feature
691
+ - `types/pallet-events.ts`: event payloads and discriminated unions for that pallet
692
+ - `types/pallet-extrinsics.ts`: extrinsic argument types and call unions for that pallet
693
+ - `types/raw.ts`: internal node or RPC response shapes used only for transport mapping
694
+
695
+ Examples:
696
+
697
+ - `src/shielded-pool/types/index.ts`
698
+ - `src/shielded-pool/types/pallet-events.ts`
699
+ - `src/shielded-pool/types/pallet-extrinsics.ts`
700
+ - `src/rpc-v2/types/raw.ts`
701
+ - `src/indexer/types/index.ts`
702
+ - `src/precompiles/types/index.ts`
703
+
704
+ Rules:
705
+
706
+ 1. If a type is part of the public API of a feature, keep it in that feature's `types/index.ts`.
707
+ 2. If a type only mirrors a transport payload from RPC, keep it in `types/raw.ts`.
708
+ 3. Do not reintroduce a global `src/types.ts` or `src/types/` directory.
709
+ 4. Export public types from `src/index.ts`, but keep ownership at the feature level.
710
+
711
+ ### Extending `rpc-v2`
712
+
713
+ The intended extension pattern is namespace-oriented and centralized under `RpcV2Module`.
714
+
715
+ For example, to add `net_*` support:
716
+
717
+ 1. Create `src/rpc-v2/NetModule.ts`
718
+ 2. Define raw response types that match the node's JSON exactly
719
+ 3. Map raw `snake_case` or hex-shaped values into stable SDK-facing TypeScript types
720
+ 4. Export the module from `src/rpc-v2/index.ts`
721
+ 5. Attach it in `RpcV2Module`, for example as `client.rpcV2.net`
722
+ 6. Add unit tests that verify RPC method names, params, and response mapping
723
+
724
+ Minimal shape example:
725
+
726
+ ```ts
727
+ export class NetModule {
728
+ constructor(private readonly substrate: SubstrateClient) {}
729
+
730
+ async version(): Promise<string> {
731
+ return this.substrate.request<string>('net_version', []);
732
+ }
733
+
734
+ async peerCount(): Promise<string> {
735
+ return this.substrate.request<string>('net_peerCount', []);
736
+ }
737
+
738
+ async listening(): Promise<boolean> {
739
+ return this.substrate.request<boolean>('net_listening', []);
740
+ }
741
+ }
742
+ ```
743
+
744
+ The same approach applies to `web3_*`, `txpool_*`, `eth_*`, and any future Orbinum-specific namespaces.
745
+
198
746
  ```bash
199
747
  pnpm install
200
748
  pnpm build # compile to dist/ (ESM + CJS + types)