SDK & Developer Tools
This page documents the SDK as it exists today, but Orbinum has not opened formally to third-party builders. Expect gaps and rough edges while we get there.
Coming next:
- Solidity guides — deploying contracts, calling the precompiles from your own code, and the interface files to import
- dApp guides for the shielded pool — building applications that shield, transfer privately, and unshield on behalf of your users
- Wider reference coverage across the whole developer surface
Building now is welcome — just expect APIs and docs to keep moving. If something is missing or wrong, tell us; that feedback shapes what lands first.
@orbinum/sdk is the official TypeScript SDK, and since 1.0.0 it is the
complete wallet: ZK proof generation, note management, Merkle synchronisation,
the encrypted vault, chain scanning, and spending all live in the package, so an
application supplies UI, transport, and platform adapters rather than
reimplementing cryptography.
It runs in the browser and in Node.js, and is published as ESM and CJS with bundled type definitions.
npm install @orbinum/sdk
Layered architecture (1.0.0)
Internally the source is organised into layers that each depend only downward —
foundation ← protocol ← chain ← wallet, plus browser adapters:
| Layer | Holds | Needs |
|---|---|---|
foundation | encoding, crypto primitives, text, errors | nothing |
protocol | note, memo, ephemeral keys, spend, keys, proving | pure & offline |
chain | client, substrate, EVM, RPC, pallet | a connection |
wallet | vault, scanner, ops, identity, worker | protocol + chain |
adapters | IndexedDB storage | a browser |
You import everything from the package root; the layering is an internal guarantee, not something callers navigate. Two extra subpaths exist for environment-specific code:
| Import | For |
|---|---|
@orbinum/sdk | everything environment-agnostic (the default) |
@orbinum/sdk/worker | the decrypt kernel, for a Web Worker with no chain client |
@orbinum/sdk/storage/indexeddb | browser persistence a Node consumer should not carry |
polkadot-api and @polkadot/util-crypto are peer dependencies — install
them alongside the SDK.
import { OrbinumClient, generateTransferProof } from '@orbinum/sdk';
Connecting
OrbinumClient.connect() opens the Substrate WebSocket connection and, if an EVM RPC URL is
supplied, the EVM client too.
import { OrbinumClient } from '@orbinum/sdk';
const client = await OrbinumClient.connect({
substrateWs: 'wss://testnet-rpc.orbinum.io',
evmRpc: 'https://testnet-rpc.orbinum.io',
});
const stats = await client.privacy.getPoolStats();
console.log('root:', stats.merkleRoot, 'leaves:', stats.commitmentCount);
client.destroy();
Client modules
| Module | What it covers |
|---|---|
client.substrate | Raw Substrate WebSocket — custom RPC and low-level access |
client.evm | Raw EVM JSON-RPC (null without evmRpc) |
client.evmExplorer | Enriched block, transaction, address, and token-transfer queries |
client.shieldedPool | Shielded-pool extrinsics and Merkle queries |
client.privacy | privacy_* RPC — Merkle proofs, nullifier status, pool stats |
client.chain | chain_* RPC — general chain state |
client.zkVerifier | zkVerifier_* RPC — circuit versions and VK hashes |
client.circuitVersionResolver | Resolves a note's circuit version before spending it |
client.relayerStatus | relayer_* RPC — registry lookup and pending fees |
client.precompiles | shieldedPool, crypto via an EVM wallet |
Proof generation
Three proof types, one function each. All return a ProofResult.
import {
generateTransferProof,
generateUnshieldProof,
generateFeeClaimProof,
WebArtifactProvider,
} from '@orbinum/sdk';
| Function | Circuit | Used by |
|---|---|---|
generateTransferProof | transfer | Private transfers between notes |
generateUnshieldProof | unshield | Withdrawals, total or partial |
generateFeeClaimProof | value proof | Validators claiming relay fees |
Circuit artifacts (.wasm and .zkey) are fetched through an ArtifactProvider.
WebArtifactProvider is the browser implementation.
A note carries the circuit version it was created under, and its proof must be verified against that version's verification key — not merely the currently active one.
client.circuitVersionResolver pins the right prover for a note and fails closed if the version is
unsupported or the VK does not match. Use it rather than assuming the active version. See
On-Chain Verification.
Notes and keys
Privacy keys
Spending and viewing keys are derived from a wallet signature, so there is no separate seed to back up.
import {
PrivacyKeyManager,
deriveViewingSecretKey,
deriveViewingPublicKey,
deriveOwnerPk,
deriveSpendingKeyFromSignature,
} from '@orbinum/sdk';
Privacy addresses (orbpriv2, 1.0.1)
A PrivacyKeyManager produces a shareable privacy address — the public half
of an identity, safe to hand to a sender. Since 1.0.1 the format is
checksummed:
orbpriv2:{ownerPk}:{ivk}:{checksum}
The checksum is the first 4 bytes of sha256("orbpriv2:{ownerPk}:{ivk}"), as 8
lowercase hex chars, so a mistyped or truncated address fails to decode instead of
silently sending to a note nobody can spend.
const pkm = new PrivacyKeyManager();
await pkm.load(spendingKey, masterBytes);
const address = pkm.encodePrivacyAddress(); // "orbpriv2:0x…:0x…:deadbeef"
// Static, and null on a bad checksum. Legacy `orbpriv1` addresses still decode.
const decoded = PrivacyKeyManager.decodePrivacyAddress(address);
// → { ownerPkHex, viewingPublicKeyHex } | null
Only the viewing public key travels in the address; the viewing secret is never
exported. encodePrivacyAddress is an instance method; decodePrivacyAddress is
static.
Note primitives
import {
NoteBuilder,
computeNoteCommitment,
computeNullifier,
EncryptedMemo,
tryDecryptNote,
deriveViewTag,
selectNotes,
treeIdOf,
} from '@orbinum/sdk';
deriveViewTag makes scanning cheap: it filters candidate commitments before attempting full
decryption. selectNotes picks inputs for a transfer. treeIdOf maps a leaf index to its tree in
the Merkle forest.
Encrypted vault
Notes are stored locally, encrypted with a key derived from the user's wallet.
Two keys come from the same master bytes via HKDF: deriveVaultKey
(AES-GCM-256, encrypts the note body) and deriveVaultBlindKey (HMAC, blinds the
commitment/nullifier/asset identifiers so a raw dump reveals no on-chain link). A
stored record (EncryptedNoteRecord) is therefore { commitmentTag, iv, ciphertext, nullifierTag, assetTag, spent?, spentAt?, updatedAt } — every
identifier a blinded tag, never plain hex. VaultStore is the higher-level API;
VaultStorage is the backend contract (its updateConfig must be atomic — two
concurrent writes to the ephemeral counter would publish the same ephemeral key).
import {
VaultStore,
deriveVaultKey,
deriveVaultBlindKey,
encryptNote,
decryptNoteRecord,
applyNoteStatus,
VaultLockedError,
} from '@orbinum/sdk';
Note backup (closed JSON, 1.1.0)
Move a user's notes between their own devices as a JSON file — no chain re-scan. The backup is closed: each entry carries only public data (the commitment and the encrypted memo), never a spending key.
import {
encodeNoteBackup,
decodeNoteBackup,
importNotesFromBackup,
} from '@orbinum/sdk';
// Export: public data only — { v, ts, notes: [{ commitmentHex, encryptedMemo, leafIndex? }] }
const backup = encodeNoteBackup(notes);
const json = JSON.stringify(backup);
// Import: prove ownership by DECRYPTING each memo with the importer's own keys.
const entries = decodeNoteBackup(json);
const mine = importNotesFromBackup(entries, {
viewingSecretKey,
spendingKey,
ownerPk,
});
// `mine` are fully spendable ZkNotes; foreign notes are silently skipped.
A note that decrypts is reconstructed spendable (the stealth spending key is
derived from the importer's identity); one that does not is dropped. Only the
backup's own memos are tried, so this is not a chain scan, and importing someone
else's backup recovers nothing. The Merkle proof is re-fetched by commitment at
spend time, so leafIndex need not travel.
For scanner-based device-to-device transfer, the SDK also ships a paginated QR
format — encodeNoteTransferPages / decodeNoteTransferPage /
assembleNoteTransfer, using the orbinum://notes/… URI scheme.
Note disclosure
Prove a note's contents to an auditor without revealing spending keys or transaction history:
import { createNoteDisclosureKey, decodeNoteDisclosureKey } from '@orbinum/sdk';
See Note Disclosure for the key format and verification rules.
Precompiles from an EVM wallet
When the user holds an EVM wallet rather than a Substrate account, the precompile modules build and send the calls:
import {
ShieldedPoolPrecompile,
PRECOMPILE_ADDR,
decodePrecompileCalldata,
} from '@orbinum/sdk';
decodePrecompileCalldata is the inverse — it turns raw calldata back into a labelled call, which
is what the explorer uses to render precompile transactions.
Address helpers
Orbinum accounts have both an H160 and an AccountId32 form. The SDK converts between them:
import {
evmToSubstrate,
substrateToEvm,
isUnifiedAddress,
isImplicitEvmAccount,
accountIdHexToSs58,
} from '@orbinum/sdk';
See EVM ↔ Substrate accounts for the derivation rules.
Use Case: Private Payment Gateway
A privacy-preserving checkout, where customers pay merchants without exposing balances or history.
- Customer deposits — shields tokens from their public wallet into a private balance
- Purchase — the SDK generates a transfer proof and executes a private transfer carrying encrypted order details in the memo
- Merchant settles — verifies the payment and fulfils the order without seeing the customer's balance or history
For compliance, the customer can issue a note disclosure key covering only the notes in question, proving what was paid without exposing anything else.
Learn More
📄️ SDK & Developer Tools
The TypeScript SDK — client modules, proof generation, and note management.