@orbinum/sdk 0.3.0 → 0.4.1
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 +570 -22
- package/dist/index.d.mts +1792 -987
- package/dist/index.d.ts +1792 -987
- package/dist/index.js +2229 -491
- package/dist/index.mjs +2214 -490
- package/package.json +5 -1
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
97
|
-
|
|
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 {
|
|
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 =
|
|
603
|
+
const message = deriveSpendingKeyMessage(chainId, evmAddress);
|
|
109
604
|
|
|
110
605
|
// 2. Derive the spending key from the wallet signature
|
|
111
|
-
const spendingKey =
|
|
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 =
|
|
120
|
-
const ownerPk
|
|
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(
|
|
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)
|