Skip to main content

SDK & Developer Tools

Not the official developer launch yet

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:

LayerHoldsNeeds
foundationencoding, crypto primitives, text, errorsnothing
protocolnote, memo, ephemeral keys, spend, keys, provingpure & offline
chainclient, substrate, EVM, RPC, palleta connection
walletvault, scanner, ops, identity, workerprotocol + chain
adaptersIndexedDB storagea 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:

ImportFor
@orbinum/sdkeverything environment-agnostic (the default)
@orbinum/sdk/workerthe decrypt kernel, for a Web Worker with no chain client
@orbinum/sdk/storage/indexeddbbrowser 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

ModuleWhat it covers
client.substrateRaw Substrate WebSocket — custom RPC and low-level access
client.evmRaw EVM JSON-RPC (null without evmRpc)
client.evmExplorerEnriched block, transaction, address, and token-transfer queries
client.shieldedPoolShielded-pool extrinsics and Merkle queries
client.privacyprivacy_* RPC — Merkle proofs, nullifier status, pool stats
client.chainchain_* RPC — general chain state
client.zkVerifierzkVerifier_* RPC — circuit versions and VK hashes
client.circuitVersionResolverResolves a note's circuit version before spending it
client.relayerStatusrelayer_* RPC — registry lookup and pending fees
client.precompilesshieldedPool, 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';
FunctionCircuitUsed by
generateTransferProoftransferPrivate transfers between notes
generateUnshieldProofunshieldWithdrawals, total or partial
generateFeeClaimProofvalue proofValidators claiming relay fees

Circuit artifacts (.wasm and .zkey) are fetched through an ArtifactProvider. WebArtifactProvider is the browser implementation.

Resolve the circuit version before spending

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.

  1. Customer deposits — shields tokens from their public wallet into a private balance
  2. Purchase — the SDK generates a transfer proof and executes a private transfer carrying encrypted order details in the memo
  3. 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