@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 +21 -0
- package/README.md +16 -499
- package/dist/index.d.mts +355 -739
- package/dist/index.d.ts +355 -739
- package/dist/index.js +239 -424
- package/dist/index.mjs +231 -414
- package/package.json +16 -16
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
|
|
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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
+
MIT
|