Installation
Install via npm or yarn:- Version: 2.8.1
- Min Extension (Mainnet): v2.0.1+
- Min Extension (Devnet): v2.2.1+ (contract calls, privacy features)
- Private transfers and claims: Extension v2.4.0+
- Private primitives and framed message signing: Extension v2.5.5+
- RFC-O-1 support: Extension v2.4.3+ (for the
window.octraprovider andoctra#initializedevent) - Browser: Chrome 111+ required for the extension transport (uses
crypto+ structured-clone messaging) - License: MIT
Project Setup & Requirements
[!IMPORTANT] The SDK relies on Node.js globals (Buffer,process,crypto) for secure operations. Modern build tools like Vite or Webpack 5+ do not provide these by default. You MUST configure polyfills.
Vite Configuration
- Install the polyfill plugin:
- Update
vite.config.ts:
Webpack 5 Configuration
If using Create React App (CRA) v5 or custom Webpack, installreact-app-rewired or customize your config:
Quick Start
The fastest way to get started:createZeroXIOWallet handles initialization and optional auto-connect:
Amounts and Units
The wallet and the RFC-O-1 provider count in raw micro-OCT: one OCT is 1000000 units. The SDK never rescales a raw field, so what a dapp sends today keeps working. Where you would rather write OCT, use the OCT field and the SDK converts it with exact decimal arithmetic.
Pass one field or the other, never both. A number that cannot be written in six decimals (such as
0.1 + 0.2) is rejected with INVALID_AMOUNT; pass a string instead.
Permissions
Ask for what the dapp uses. The wallet enforces these scope names; the private ones show a warning in the connection dialog.
The older SDK names (
read_balance, send_transactions, sign_messages, view_private_balance, stealth_claim and the rest) still work. The SDK translates them when it connects, and the names you asked for come back in the granted list as aliases, so a check like permissions.includes('read_balance') keeps working. WALLET_PERMISSIONS, LEGACY_PERMISSION_MAP, toWalletPermissions and withLegacyAliases are exported.
The wallet refuses anything that signs, submits or changes state until the page has connected (NOT_CONNECTED), and opens its unlock screen at most once a minute for a page that is not connected (WALLET_LOCKED).
Security (v2.7.x)
The SDK includes several security hardening measures:- Origin-validated messaging:
postMessagecommunication validates origins against a strict trusted set. No wildcard origins. - Session-nonce binding: every bridge response must carry the session nonce issued at connect time. Same-origin impersonation and forged responses are rejected.
- No auto-trust for iframes: the SDK does not assume an iframe parent is a wallet. It requires a verified
walletReadysignal from a trusted origin. - Response binding: only responses matching a pending request ID are accepted.
- No retry on rejection: user-rejected transactions do not trigger automatic retries.
- Configurable trusted origins: set
trustedParentOriginsin the constructor config to restrict which origins are trusted as the parent iframe bridge (disables implicit localhost trust). - RPC URL validation:
http://RPC endpoints are rejected on non-testnet networks, blocking a malicious bridge from injecting an insecure endpoint.
Desktop & Mobile Support
The SDK automatically detects the wallet environment and uses the appropriate transport. No code changes are required.How It Works
- Auto Frame Detection: When
window.parent !== window, the SDK assumes a wallet bridge (Desktop or Mobile) is available and marks the wallet as detected. - Dual Posting:
postMessageToExtension()posts to bothwindow(for extension content script) andwindow.parent(for iframe/WebView bridge). - Frame-Aware Listener:
setupMessageListener()accepts messages fromwindow.parentin addition to same-window messages. - walletReady via postMessage: Extension detection recognizes
walletReadyevents sent viapostMessagefrom parent frames.
Cross-Origin Iframe Bridge (v2.4.4)
Starting in v2.4.4, the SDK supports cross-origin iframe communication for localhost development. This allows DApps running onlocalhost or 127.0.0.1 during development to communicate with the wallet through an iframe bridge, without requiring the extension content script to be injected into the development server origin. For production frames, pass trustedParentOrigins in the constructor config to allowlist the exact origins you trust:
RFC-O-1 Provider Standard (v2.7.0)
The SDK includes anOctraProviderAdapter that enables any RFC-O-1 compliant wallet (not just 0xio) to work with the SDK. This allows third-party wallets that implement window.octra to be used as a drop-in transport.
How It Works
The SDK’s adapter registry tries transports in priority order:
If the 0xio extension is installed, it takes priority. If not, any RFC-O-1 compliant wallet works automatically.
Using a Third-Party Wallet
No code changes are needed. The SDK auto-detects:Direct Provider Access
For advanced use cases, you can use the provider directly:RFC-O-1 Error Codes
Core API
Connection Methods
initialize()
Initializes the SDK and checks for extension presence.
- Returns:
Promise<boolean> - Throws:
ZeroXIOWalletErrorwith codeEXTENSION_NOT_FOUNDif 0xio is not installed
isReady()
Check if SDK is initialized and extension is available.
- Returns:
boolean
connect(options)
Requests connection to the extension. User must approve in popup.
- Returns:
Promise<ConnectEvent>with address, balance, networkInfo, and permissions - Throws:
ZeroXIOWalletErrorwith codeCONNECTION_REFUSEDif user declines
disconnect()
Disconnects from the extension.
- Returns:
Promise<void>
isConnected()
Checks current connection status.
- Returns:
boolean
getConnectionInfo()
Gets the cached connection info.
- Returns:
ConnectionInfo
getConnectionStatus()
Checks connection status with extension (async).
- Returns:
Promise<ConnectionInfo>
getAddress()
Gets the currently connected address.
- Returns:
string | null
getPublicKey()
The connected account’s Ed25519 public key, base64. Served from the session when the wallet reported it at connect, otherwise asked from the wallet. Use it with verifyMessage.
- Returns:
Promise<string>
switchNetwork(networkId)
Ask the wallet to switch its active network from a connected dapp. The wallet asks the user to confirm first (extension 2.5.6 and later, app 1.3.0 and later, desktop 0.4.1 and later); declining rejects with USER_REJECTED. The call is also refused while another request from the page is waiting for approval, so a transaction under review can never move to another network. The page must be connected first.
- Params:
networkId: string,'mainnet'or'devnet' - Returns:
Promise<{ network: string; switched: boolean }> - Requires: Extension v2.3.6+, and a connected page
getNetworkId()
Gets the extension’s current active network.
- Returns:
string | null
Balance Methods
getBalance(forceRefresh?)
Fetches current balances from blockchain or cache.
- Parameters:
forceRefresh: If true, queries blockchain; if false, uses cached values
- Returns:
Promise<Balance>with public, private, total (numbers), and currency
Transaction Methods
sendTransaction(txData)
Executes a standard public transfer.
- Parameters:
- Returns:
Promise<TransactionResult>withhash,accepted,status(and the oldertxHash,success)
signTransaction(txData) and submitTransaction(signedTx)
Sign without broadcasting, then broadcast later. signTransaction takes the same TransactionData and opens the same approval; submitTransaction sends the signed object to the network. The wallet only relays transactions signed by the connected address.
sendPrivateTransfer(txData)
Executes an FHE-encrypted stealth transfer. The extension handles PVAC ciphertext generation, range proofs, and ECDH key exchange. User must approve via the extension approval popup. Requires private_transfers permission.
Requires Extension v2.4.0+. After the approval the wallet generates FHE proofs, which takes from 17 seconds to a few minutes depending on the engine (Desktop native, then WASM with threads, then single-threaded WASM). The SDK waits up to 10 minutes for this call. The wallet checks the private balance first and refuses, with the reason, when it cannot be spent yet (over the layer cap or held for migration).
- Parameters:
- Returns:
Promise<TransactionResult>withhashandaccepted - Note: The amount is encrypted on your device before transmission
getTransactionHistory(page, limit)
Gets transaction history.
- Parameters:
page(default: 1),limit(default: 20) - Returns:
Promise<TransactionHistory>
Message Signing
signMessage(message)
Signs an arbitrary message with the wallet’s private key using Ed25519. The user will be prompted to approve the signature request in the extension popup.
- Parameters:
message: string- The text message to sign (must be non-empty) - Returns:
Promise<string>- Base64-encoded Ed25519 signature (64 bytes) - Throws:
SIGNATURE_FAILED- If signing fails or message is invalidUSER_REJECTED- If user rejects the signature requestWALLET_LOCKED- If wallet is locked
The 0xio Signed Message standard. The wallet never signs the raw message. It signs a framed payload,
"Octra Signed Message:\n" + utf8ByteLength(message) + "\n" + message, as an Ed25519 detached signature over the UTF-8 bytes of that string. This guarantees a signed message can never collide with a transaction pre-image (which is canonical JSON beginning with {). Verify with verifyMessage below, or reconstruct the bytes with getSignedMessageBytes and use any Ed25519 library.- Authentication: Prove wallet ownership for login/API access
- Attestation: Sign data for on-chain verification
- Authorization: Approve off-chain actions
- API Keys: Create authenticated API credentials for Oracle/Indexer
signAuthMessage(service, nonce)
Convenience wrapper that builds a domain-separated auth message and signs it. Includes the current origin for anti-phishing.
- Parameters:
service: string(service name),nonce: string(unique challenge) - Returns:
Promise<string>- Base64-encoded Ed25519 signature
buildAuthMessage(service, nonce, origin) and pass it to verifyMessage.
verifyMessage(message, signature, publicKey)
Verifies a 0xio message signature using the Web Crypto Ed25519 primitive (zero runtime dependencies). Available on Node 18+, Chrome 137+, Safari 17+, and Firefox 129+.
- Parameters:
message: string,signature: string(base64),publicKey: string(base64) - Returns:
Promise<boolean>
getSignedMessageBytes(message)
Returns the exact bytes the wallet signs, so you can verify with any Ed25519 library instead of Web Crypto (for example server-side, or on an older runtime).
- Parameters:
message: string - Returns:
Uint8Array
Smart Contract Methods
callContract(data)
Executes a state-changing smart contract call. The extension builds, signs, and submits the transaction via octra_submit. The user will see an approval popup.
- Parameters:
- Returns:
Promise<TransactionResult>withtxHash,success,finality
contractCallView(data)
Read-only contract query. Does not require wallet unlock, signing, or user approval. Only requires initialize() (not connect()). For a page that is not connected the wallet leaves the caller empty unless you pass caller.
- Parameters:
- Returns:
Promise<any>, the raw result from the contract method
getContractStorage(contract, key)
Read a single key from contract storage. No wallet unlock or approval needed.
- Parameters:
contract: string(contract address),key: string(storage key) - Returns:
Promise<string | null>, the storage value, ornullif not found
sendContractTransactionSequence(data)
Several contract calls under one approval, submitted in order with consecutive nonces. The approval window lists every step with its contract, attached value and fee.
- Returns:
Promise<unknown>withresults[], one hash per step
request(method, params) and rpcCall(method, params)
request sends any wallet method through the bridge, for primitives that have no typed helper yet. rpcCall reaches the wallet’s read-only node RPC allow-list (octra_balance, octra_transaction, contract_call and similar); writes are refused with METHOD_NOT_ALLOWED.
Privacy Operations
encryptBalance(amount)
Not served by the 0xio extension: the wallet answers NOT_AVAILABLE, and users encrypt from the extension’s Privacy screen. Kept for wallets that may implement it.
Moves funds from Public to Private balance (shielding). The extension generates the FHE cipher, bound proof, and submits the transaction.
- Parameters:
amount: number | string(OCT amount to shield) - Returns:
Promise<TransactionResult>withtxHash,success,finality
Amounts on
encryptBalance, decryptBalance, sendPrivateTransfer, and callContract accept string | number. A numeric amount that can’t be represented exactly in micro-OCT (6 decimals) throws INVALID_AMOUNT; pass a string (e.g. "0.300000") for exact control.decryptBalance(amount)
Not served by the 0xio extension (see encryptBalance).
Moves funds from Private to Public balance (unshielding). Includes range proof generation (17-300s depending on engine).
- Parameters:
amount: number | string(OCT amount to unshield) - Returns:
Promise<TransactionResult>withtxHash,success,finality
Approvals wait up to 3 minutes;
sendPrivateTransfer waits up to 10 minutes because proof generation follows the approval.getPrivateBalanceInfo()
Gets detailed private balance information.
- Returns:
Promise<PrivateBalanceInfo>
getPendingPrivateTransfers()
Gets pending incoming private transfers.
- Returns:
Promise<PendingPrivateTransfer[]>. The 0xio extension fillsidand a raw micro-OCTamount; the other fields depend on the wallet. - Requires:
private_balance_read
claimPrivateTransfer(transferId)
Claims a pending private transfer. The wallet opens an approval window showing the fee; a claim is a signed, fee-paying transaction.
- Parameters:
transferId: string - Returns:
Promise<TransactionResult> - Requires:
private_claims(orprivate_transfers)
Private Primitives (2.8.0)
Building blocks for private contracts. Keys never leave the wallet: the dapp receives ciphertexts, proofs and hashes. None of these opens a popup exceptregisterPrivateViewKey; proofs take from seconds to a couple of minutes, and the SDK waits up to 3 minutes.
Network Methods
getNetworkInfo()
Gets current network information.
- Returns:
Promise<NetworkInfo>
Event Listeners
The SDK extendsEventEmitter for real-time state updates.
Account Changed
Your site was moved to another wallet. A connection belongs to the wallet it was approved for: when the user switches wallets, 0xio asks whether to move connected sites to the new one, and only a site that is moved receives this event. A site that is not moved stays on its wallet, and while another wallet is active its wallet requests are refused withNOT_CONNECTED and getConnectionStatus reports it as not connected. It works again as soon as the user switches back or connects it again.
Network Changed
User switched networks (mainnet/testnet).Balance Changed
Balance updated after transaction confirmation.Connect
Wallet connected successfully.Disconnect
User disconnected from dApp or locked wallet.Transaction Confirmed
Transaction was confirmed on-chain.Transaction Failed
The wallet could not broadcast a queued transaction.Extension Lock Events
Removing Listeners
Cleanup
Clean up SDK resources when done.Error Handling
The SDK provides typed error codes for graceful error handling.Error Codes
Framework Integration
React Hook
Vue.js Composition API
TypeScript Definitions
The SDK exports comprehensive TypeScript types:Utility Functions
The SDK exports useful utility functions:SDK Compatibility
Best Practices
Connection
- Always check
isConnected()before operations - Handle
USER_REJECTEDerrors gracefully - Provide clear install prompts if extension not found
- Request minimum necessary permissions
- Use
getConnectionStatus()to restore existing connections
Transactions
- Validate addresses with
isValidAddress()before sending - Display full transaction details to users
- Handle all error cases explicitly
- Show loading states during confirmation
- Provide transaction hash for tracking
Message Signing
- Always include timestamps or nonces to prevent replay attacks
- Display the full message to users before signing
- Use the
public_transactionsscope for authentication flows - Verify signatures server-side using Ed25519
- Never sign messages that look like transaction data
Privacy
- Clearly indicate when using FHE features
- Explain gas cost differences
- Let users choose public vs private
- Don’t store sensitive data
Performance
- Cache balance data appropriately
- Use
forceRefreshsparingly - Batch multiple operations when possible
- Clean up event listeners on unmount with
cleanup()