@orbinum/sdk 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Orbinum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,8 +2,6 @@
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: Substrate RPC, EVM JSON-RPC, shielded pool operations, ZK proof generation, encrypted note vault, account identity and mapping, relayer status, `rpc-v2`, ZK verifier, precompiles, and the indexer REST API.
6
-
7
5
  ## Installation
8
6
 
9
7
  ```bash
@@ -14,9 +12,8 @@ pnpm add @orbinum/sdk
14
12
 
15
13
  ## Requirements
16
14
 
17
- - Node.js 18 or later (WebCrypto API required for vault operations)
15
+ - Node.js 18 or later
18
16
  - A running Orbinum node (Substrate WebSocket endpoint)
19
- - An EVM JSON-RPC endpoint (optional — required for EVM and precompile operations)
20
17
 
21
18
  ## Quick Start
22
19
 
@@ -25,507 +22,27 @@ import { OrbinumClient } from '@orbinum/sdk';
25
22
 
26
23
  const client = await OrbinumClient.connect({
27
24
  substrateWs: 'ws://localhost:9944',
28
- evmRpc: 'http://localhost:9933',
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...');
34
- ```
35
-
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',
25
+ evmRpc: 'http://localhost:9933', // optional
57
26
  });
58
27
 
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
- ## Modules
68
-
69
- ### SubstrateClient
70
-
71
- Thin wrapper over [polkadot-api](https://github.com/polkadot-api/polkadot-api) (PAPI) for Substrate WebSocket communication.
72
-
73
- ```ts
74
- import { SubstrateClient } from '@orbinum/sdk';
75
-
76
- const substrate = await SubstrateClient.connect('ws://localhost:9944');
77
-
78
- const info = await substrate.getChainInfo();
79
- const health = await substrate.getHealth();
80
- const version = await substrate.getNodeVersion();
81
- const genesis = await substrate.getGenesisHash();
82
-
83
- // Block queries
84
- const header = await substrate.getBlockHeader('best');
85
- const hash = await substrate.getBlockHash(1000);
86
- const block = await substrate.getBlock('0xabc...');
87
-
88
- // Block events — decoded into EventRecord[]
89
- const events = await substrate.queryBlockEvents('0xabc...');
90
-
91
- // Block stream
92
- substrate.blocks$.subscribe(block => console.log(block.number));
93
-
94
- // Raw JSON-RPC call
95
- const result = await substrate.request('system_name', []);
96
-
97
- // Transaction helpers
98
- const tx = await substrate.txFromCallData(callBytes);
99
- const finalized = await tx.signAndSubmit(signer);
100
- const finalized2 = await substrate.submit(signedHex);
101
- substrate.submitAndWatch(signedHex).subscribe(event => console.log(event));
102
- ```
103
-
104
- ### EvmClient
105
-
106
- Stateless HTTP client following the Ethereum JSON-RPC specification.
107
-
108
- ```ts
109
- import { EvmClient } from '@orbinum/sdk';
110
-
111
- const evm = new EvmClient('http://localhost:9933');
112
-
113
- const balance = await evm.getBalance('0xYourAddress');
114
- const chainId = await evm.getChainId();
115
- const txHash = await evm.sendRawTransaction(signedHex);
116
-
117
- // Batch multiple calls in a single HTTP request
118
- const [bal, nonce] = await evm.batchRequest([
119
- { method: 'eth_getBalance', params: ['0xAddr', 'latest'] },
120
- { method: 'eth_getTransactionCount', params: ['0xAddr', 'latest'] },
121
- ]);
122
- ```
123
-
124
- ### EvmExplorer
125
-
126
- Read-only EVM block and transaction explorer.
127
-
128
- ```ts
129
- import { EvmExplorer } from '@orbinum/sdk';
130
-
131
- const explorer = new EvmExplorer('http://localhost:9933');
132
-
133
- const block = await explorer.getBlock(12345);
134
- const tx = await explorer.getTransaction('0xhash...');
135
- const logs = await explorer.getLogs({ fromBlock: 100, toBlock: 200, address: '0x...' });
136
- ```
137
-
138
- Types: `EvmBlock`, `EvmTransaction`, `EvmAddressInfo`, `EvmTxSummary`, `EvmLog`, `TokenInfo`, `TokenTransfer`.
139
-
140
- ### rpc-v2 / PrivacyModule
141
-
142
- Orbinum's `rpc-v2` is organized by namespace. In the SDK, it is exposed through `client.rpcV2`.
143
-
144
- ```ts
145
- const root = await client.rpcV2.privacy.getMerkleRoot();
146
- const proof = await client.rpcV2.privacy.getMerkleProof(12);
147
- // { path: string[]; leafIndex: number; treeDepth: number; }
148
-
149
- const status = await client.rpcV2.privacy.getNullifierStatus('0x...');
150
- // { nullifier: string; isSpent: boolean; }
151
-
152
- const stats = await client.rpcV2.privacy.getPoolStats();
153
- // { merkleRoot: string; commitmentCount: number; totalBalance: string; ... }
154
- ```
155
-
156
- Notes:
157
- - `rpc-v2` responses use `snake_case` from the node; the SDK normalizes them to `camelCase`.
158
- - `u128` values are exposed as decimal strings to avoid precision loss.
159
-
160
- ### ZkVerifierModule
161
-
162
- Typed access to the on-chain ZK verifier — circuit version info, VK hashes, and version history.
163
-
164
- ```ts
165
- import { ZkVerifierModule } from '@orbinum/sdk';
166
-
167
- const zkv = new ZkVerifierModule(substrate);
168
- const info = await zkv.getCircuitVersionInfo('circuit-id');
169
- const vkHash = await zkv.getVkHash('circuit-id', 1);
170
- const stats = await zkv.getVersionStats('circuit-id');
171
- const history = await zkv.getHistoricalVersions('circuit-id');
172
- ```
173
-
174
- ### ShieldedPoolModule
175
-
176
- High-level interface for shielded pool extrinsics. Transactions are built via polkadot-api's UnsafeApi (metadata-driven). Unshield and private transfer are submitted as **unsigned (gasless)** transactions by default — the fee is embedded in the ZK proof.
177
-
178
- ```ts
179
- import { ShieldedPoolModule } from '@orbinum/sdk';
180
-
181
- const pool = new ShieldedPoolModule(substrate);
182
-
183
- // Deposit tokens into the shielded pool (signed)
184
- await pool.shield(params, signer);
185
-
186
- // Withdraw tokens via ZK proof (unsigned, gasless)
187
- await pool.unshield(params);
188
-
189
- // Private transfer between shielded addresses (unsigned, gasless)
190
- await pool.privateTransfer(params);
191
-
192
- // Batch shield
193
- await pool.shieldBatch(params, signer);
194
-
195
- // Claim accumulated relayer fees
196
- await pool.claimShieldedFees(params, signer);
197
-
198
- // Selective disclosure
199
- await pool.requestDisclosure(args, signer);
200
- await pool.disclose(args, signer);
201
- await pool.rejectDisclosure(args, signer);
202
- ```
203
-
204
- ### NoteBuilder
205
-
206
- Builds ZK notes (commitment + nullifier + encrypted memo) off-chain, with optional stealth address support. No network calls are made.
207
-
208
- Hash scheme:
209
- ```
210
- commitment = Poseidon4(value, assetId, ownerPk, blinding)
211
- nullifier = Poseidon2(commitment, spendingKey)
212
- ```
213
-
214
- ```ts
215
- import { NoteBuilder } from '@orbinum/sdk';
216
-
217
- const note = await NoteBuilder.build({
218
- value: 1_000_000n,
219
- assetId: 0n,
220
- ownerPk: myOwnerPk,
221
- viewingPublicKey: recipientViewingPk,
222
- // Optional: provide recipientOwnerPk to derive a per-note stealthOwnerPk
223
- recipientOwnerPk: recipientOwnerPk,
224
- });
225
- // note.commitmentHex, note.nullifierHex, note.encryptedMemo
226
- ```
227
-
228
- When `recipientOwnerPk` is provided, the commitment uses a fresh `stealthOwnerPk` derived from an ephemeral ECDH key, making each transfer unlinkable even when the same privacy address is reused.
229
-
230
- ### NoteDecryptor
231
-
232
- Scans on-chain commitments and attempts to decrypt them using the viewer's viewing key.
233
-
234
- ```ts
235
- import { tryDecryptNote, tryDecryptNoteVerbose, computeNullifier } from '@orbinum/sdk';
236
-
237
- // Returns a ZkNote on success, null on key mismatch or commitment failure
238
- const note = tryDecryptNote(commitment, viewingSecretKey, spendingKey, ownOwnerPk);
239
-
240
- // Returns the note plus a human-readable failure reason (useful for debugging)
241
- const { note, reason } = tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwnerPk);
242
-
243
- // Compute nullifier directly
244
- const nullifier = computeNullifier(commitmentBigint, spendingKey);
245
- ```
246
-
247
- `ScanCommitment` shape: `{ commitmentHex: string; leafIndex: number; encryptedMemo: string | null }`.
248
-
249
- ### EncryptedMemo
250
-
251
- All memos attached to shielded notes are exactly **168 bytes** (ChaCha20-Poly1305 with ECDH ephemeral key):
252
-
253
- ```
254
- nonce(12) || ciphertext+MAC(124) || ephPk(32) = 168 bytes
255
- ```
256
-
257
- ```ts
258
- import { EncryptedMemo, ENCRYPTED_MEMO_SIZE } from '@orbinum/sdk';
259
- // ENCRYPTED_MEMO_SIZE === 168
260
-
261
- const memo = await EncryptedMemo.encrypt(payload, recipientViewingPk, commitment);
262
- const decrypted = await EncryptedMemo.decrypt(memoBytes, viewingSecretKey, commitment);
263
-
264
- // Validation is called automatically inside ShieldedPoolModule and ShieldedPoolPrecompile.
265
- // It can also be called explicitly:
266
- EncryptedMemo.validate(memoBytes, 'my-context'); // throws if length !== 168
267
- ```
268
-
269
- ### Coin Selection
270
-
271
- ```ts
272
- import { selectNotes, buildDummyTransferInput } from '@orbinum/sdk';
273
-
274
- // Select up to 2 unspent notes covering `needed` planck.
275
- // Priority: single note first, then smallest qualifying pair.
276
- const selected = selectNotes(myNotes, needed);
277
- // [ZkNote, ZkNote | null] | null
278
-
279
- // Build a zero-value dummy second input for single-note private transfers.
280
- // The circuit exempts inputs with value == 0 from Merkle membership and EdDSA checks.
281
- const dummy = buildDummyTransferInput(assetId);
282
- ```
283
-
284
- ### Proof Generator
285
-
286
- Wrappers around `@orbinum/proof-generator` for generating Groth16 ZK proofs. The `ArtifactProvider` controls where circuit `.wasm` and `.zkey` files are loaded from — use `WebArtifactProvider` in browser environments.
287
-
288
- ```ts
289
- import {
290
- generateUnshieldProof,
291
- generateTransferProof,
292
- generateFeeClaimProof,
293
- WebArtifactProvider,
294
- } from '@orbinum/sdk';
295
-
296
- const provider = new WebArtifactProvider('/circuits');
297
-
298
- // Unshield proof
299
- const result = await generateUnshieldProof({
300
- merkleRoot, nullifier, amount, assetId, recipient,
301
- blinding, spendingKey, pathSiblings, leafIndex,
302
- fee: 0n, // optional gasless fee
303
- changeValue: 0n, // optional change note value
304
- }, provider);
305
- // result.proof, result.publicSignals, result.changeCommitment
306
-
307
- // Private transfer proof (exactly 2 inputs and 2 outputs)
308
- const txProof = await generateTransferProof({
309
- merkleRoot,
310
- inputs: [inputNote1, inputNote2], // use buildDummyTransferInput for single-note transfers
311
- outputs: [outputNote1, outputNote2],
312
- fee: 0n,
313
- }, provider);
314
-
315
- // Fee-claim proof
316
- const feeProof = await generateFeeClaimProof({
317
- amount, assetId, ownerPubkey, blinding, commitment,
318
- }, provider);
319
- // feeProof.proof (128-byte 0x-prefixed hex), feeProof.publicSignals (76-byte number[])
320
- ```
321
-
322
- ### RelayerStatusModule
323
-
324
- Typed client for `relayer_*` JSON-RPC endpoints.
325
-
326
- ```ts
327
- import { RelayerStatusModule } from '@orbinum/sdk';
328
-
329
- const relayer = new RelayerStatusModule(substrate);
330
-
331
- const isRelayer = await relayer.isRelayer(ss58Address);
332
- const pending = await relayer.pendingFees(ss58Address, assetId); // bigint
333
- const evmAddress = await relayer.registeredEvmAddress(ss58Address); // string | null
334
- const info = await relayer.getRelayerInfo(ss58Address);
335
- // { isRelayer: boolean; evmAddress: string | null }
28
+ const info = await client.substrate.getChainInfo();
29
+ const root = await client.rpcV2.privacy.getMerkleRoot();
30
+ const block = await client.substrate.getBlock('best');
336
31
  ```
337
32
 
338
- ### Vault
339
-
340
- AES-GCM-256 encrypted local note storage. Works in browser (WebCrypto) and Node.js 18+.
341
-
342
- Key derivation uses HKDF-SHA-256 over 32-byte master material derived **before** modular reduction, so the vault key remains stable across circuit field changes.
343
-
344
- ```ts
345
- import {
346
- deriveVaultKey,
347
- encryptNote,
348
- decryptNoteRecord,
349
- applyNoteStatus,
350
- VaultLockedError,
351
- } from '@orbinum/sdk';
352
-
353
- // Derive the vault encryption key from master key bytes (pre-modulus)
354
- const vaultKey = await deriveVaultKey(masterBytes);
355
-
356
- // Encrypt a ZkNote into an EncryptedNoteRecord
357
- const record = await encryptNote(vaultKey, zkNote);
358
- // { commitmentHex, iv, ciphertext, nullifierHex, assetId, spent?, updatedAt }
359
-
360
- // Decrypt
361
- const note = await decryptNoteRecord(vaultKey, record); // ZkNote
362
-
363
- // Update spent status without re-encrypting the full ciphertext
364
- const updated = applyNoteStatus(record, { spent: true, spentAt: Date.now() });
365
- ```
366
-
367
- The `nullifierHex` and `assetId` fields are stored unencrypted so the host application can perform spent-checks and asset filtering without unlocking the vault.
368
-
369
- ### Privacy Keys
370
-
371
- Key derivation follows a deterministic chain from the user's wallet signature. No key material is ever stored by the SDK.
372
-
373
- ```ts
374
- import {
375
- PrivacyKeyManager,
376
- deriveOwnerPk,
377
- deriveSpendingKeyFromSignature,
378
- deriveSpendingKeyMessage,
379
- deriveViewingKey,
380
- } from '@orbinum/sdk';
381
-
382
- const message = deriveSpendingKeyMessage(chainId, evmAddress);
383
- const spendingKey = deriveSpendingKeyFromSignature(sigHex, chainId, evmAddress);
384
-
385
- const keyManager = new PrivacyKeyManager();
386
- keyManager.load(spendingKey);
387
-
388
- const viewingKey = deriveViewingKey(spendingKey);
389
- const ownerPk = deriveOwnerPk(spendingKey);
390
- ```
391
-
392
- ### AccountMappingModule
393
-
394
- Manage on-chain identity by linking Substrate and EVM accounts, registering aliases, and interacting with the alias marketplace.
395
-
396
- ```ts
397
- await client.accountMapping.registerAlias({ alias: 'myalias' }, signer);
398
- await client.accountMapping.addChainLink({ chainId: 1, address: '0xEvmAddr' }, signer);
399
-
400
- const alias = await client.accountMapping.getAliasOf(substrateHex);
401
- const linked = await client.accountMapping.getChainLinks(substrateHex);
402
- ```
33
+ `OrbinumClient` exposes the following modules:
403
34
 
404
- ### IndexerClient
35
+ | Property | Description |
36
+ |---|---|
37
+ | `client.substrate` | Substrate RPC — blocks, events, transactions |
38
+ | `client.evm` | EVM JSON-RPC client (`null` if `evmRpc` not set) |
39
+ | `client.rpcV2` | Orbinum `rpc-v2` namespaces (`privacy_*`, etc.) |
40
+ | `client.shieldedPool` | Shielded pool extrinsics |
41
+ | `client.accountMapping` | Alias, chain-link, and identity operations |
42
+ | `client.precompiles` | EVM precompile wrappers (`null` if `evmRpc` not set) |
405
43
 
406
- HTTP client for the Orbinum indexer REST API. All list endpoints are paginated.
407
-
408
- ```ts
409
- import { IndexerClient } from '@orbinum/sdk';
410
-
411
- const indexer = new IndexerClient({ baseUrl: 'https://indexer.orbinum.io' });
412
-
413
- const blocks = await indexer.getBlocks({ page: 1, limit: 20 });
414
- const extrinsics = await indexer.getExtrinsics({ address: '5F...' });
415
- const commitments = await indexer.getCommitments({ page: 1, limit: 20 });
416
- const nullifier = await indexer.getNullifierStatus('0xNullifier');
417
- const roots = await indexer.getMerkleRoots({ limit: 10 });
418
- const evmTxs = await indexer.getEvmTransactions({ address: '0xabc...' });
419
- const stats = await indexer.getStats();
420
- ```
421
-
422
- ### Precompiles
423
-
424
- Typed wrappers for Orbinum's EVM precompile contracts.
425
-
426
- | Precompile | Address | Description |
427
- |---|---|---|
428
- | `ShieldedPoolPrecompile` | `0x0801` | Shield, unshield, and private transfer from EVM |
429
- | `AccountMappingPrecompile` | `0x0800` | Identity and alias operations from EVM |
430
- | `CryptoPrecompiles` | `0x0400–0x0403` | Frontier crypto utilities |
431
-
432
- ```ts
433
- import { ShieldedPoolPrecompile, decodePrecompileCalldata } from '@orbinum/sdk';
434
-
435
- const precompile = new ShieldedPoolPrecompile(evmClient);
436
- await precompile.shield(amount, tokenAddress, commitment, walletSigner);
437
-
438
- // Decode raw EVM calldata for a known precompile
439
- const decoded = decodePrecompileCalldata('0x0800', calldata);
440
- ```
441
-
442
- ### Extrinsic & Event Decoders
443
-
444
- ```ts
445
- import { mapExtrinsicArgs, mapZkEventData } from '@orbinum/sdk';
446
-
447
- const decoded = mapExtrinsicArgs('shieldedPool', 'shield', rawArgs);
448
- const eventData = mapZkEventData('ProofVerified', rawEventData);
449
- ```
450
-
451
- ### Substrate SCALE Primitives
452
-
453
- SCALE codec primitives from `@polkadot-api/substrate-bindings` are re-exported directly. There is no need to install that package separately.
454
-
455
- ```ts
456
- import { Blake2256, AccountId, u128, u64, Storage, Keccak256 } from '@orbinum/sdk';
457
- import { base58, getSs58AddressInfo } from '@orbinum/sdk';
458
- ```
459
-
460
- ## Key Derivation Chain
461
-
462
- ```
463
- wallet.sign(message)
464
- |
465
- v
466
- deriveSpendingKeyFromSignature() HKDF-SHA256 over signature bytes, mod BN254_R
467
- |
468
- +-- deriveViewingKey() HKDF-SHA256 → 32-byte ChaCha20 symmetric key
469
- | Used to encrypt/decrypt note memos (EncryptedMemo)
470
- |
471
- +-- deriveOwnerPk() BabyJubJub scalar multiplication → Ax
472
- | Embedded in note commitments: Poseidon4(v, a, ownerPk, b)
473
- |
474
- +-- deriveVaultKey() HKDF-SHA256 (pre-modulus bytes) → AES-GCM-256 key
475
- Stable across circuit field changes
476
- ```
477
-
478
- ### Stealth Addresses
479
-
480
- When a sender builds a note for a recipient, a per-note stealth `ownerPk` is derived:
481
-
482
- ```
483
- ephSk ← random scalar
484
- sharedSecret ← ECDH(ephSk, recipientViewingPk)
485
- stealthScalar ← HKDF-SHA256(sharedSecret, salt=ownerPk_LE, info="orbinum-stealth-v1") % suborder
486
- stealthOwnerPk ← stealthScalar × Base8 + ownerPkPoint
487
- ```
488
-
489
- The recipient recovers the stealth spending key:
490
-
491
- ```
492
- stealthSk ← (stealthScalar + spendingKey) % BABYJUB_SUBORDER
493
- ```
494
-
495
- Each received note has a unique `stealthOwnerPk`, making transfers unlinkable even when the same privacy address is reused. The ZK circuit validates ownership without modification because `BabyPbk(stealthSk).Ax == stealthOwnerPk`.
496
-
497
- ## Development
498
-
499
- ### Type Layout
500
-
501
- The SDK organizes types by feature ownership.
502
-
503
- - `types/index.ts`: public shared types of the feature
504
- - `types/pallet-events.ts`: event payloads and discriminated unions
505
- - `types/pallet-extrinsics.ts`: extrinsic argument types and call unions
506
- - `types/raw.ts`: internal node or RPC response shapes used only for transport mapping
507
-
508
- Rules:
509
- 1. Public types belong in the feature's `types/index.ts`.
510
- 2. Transport-only types belong in `types/raw.ts`.
511
- 3. Do not reintroduce a global `src/types.ts` or `src/types/` directory.
512
- 4. Export public types from `src/index.ts`, keeping ownership at the feature level.
513
-
514
- ### Extending `rpc-v2`
515
-
516
- Add a new namespace by creating `src/rpc-v2/NetModule.ts`, defining raw response types, mapping them to SDK-facing types, and attaching the module in `RpcV2Module`. Add unit tests verifying RPC method names, params, and response mapping.
517
-
518
- ### Commands
519
-
520
- ```bash
521
- pnpm install
522
- pnpm build # compile to dist/ (ESM + CJS + types)
523
- pnpm test # run test suite
524
- pnpm typecheck:all # typecheck src and tests
525
- pnpm lint # eslint
526
- pnpm format # prettier
527
- ```
44
+ Each module can also be instantiated independently without `OrbinumClient`.
528
45
 
529
46
  ## License
530
47
 
531
- ISC
48
+ MIT