Skip to main content
The 0xio Wallet SDK connects your dApp to the 0xio Wallet on every platform (the browser extension, the desktop app’s built-in browser and the mobile app’s built-in browser), with support for Octra’s FHE privacy features. For EVM sites, see EVM Provider. Building with an AI assistant: Build with AI.

Installation

Install via npm or yarn:
Package Details:
  • Version: 2.12.2
  • Names (lookupAddress, resolveName): see Names; needs the contract_views permission
  • Request lifecycle (request ids, status, confirmed waits): see Request lifecycle; full support from extension 3.0.6, 0xio Desktop 0.6.2 and 0xio app 1.3.5
  • Watch-only signal (canSign, WATCH_ONLY): Extension v3.0.3+
  • Contract deployment (deployContract): Extension v3.0.0+
  • 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.octra provider and octra#initialized event)
  • 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

  1. Install the polyfill plugin:
  1. Update vite.config.ts:

Webpack 5 Configuration

If using Create React App (CRA) v5 or custom Webpack, install react-app-rewired or customize your config:

Quick Start

The fastest way to get started: createZeroXIOWallet handles initialization and optional auto-connect:
Or with manual control:

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). While the wallet is locked, getBalance and the private balance reads also return WALLET_LOCKED (extension 3.0.0 and newer).

What the Review Shows

The user approves exactly what is signed. The wallet reduces each request to the fields it signs, and the review shows those same fields, so a request cannot show one thing and sign another.
  • One value per field. A request may name its recipient as to, to_ or contract, its method as method or encrypted_data, and its arguments as params or message. When two of them are set and differ, the wallet refuses the request with INVALID_PARAMS before any review opens. Send each value once.
  • Arguments are signed as given. A contract call signs its arguments as JSON array text. An array in params is written out as JSON. When the request carries the text itself, the extension signs it byte for byte, so integers above 2^53 keep every digit and the review shows them exactly. A JavaScript number above 2^53 has already lost digits before it reaches the wallet, so send such a call as text:
  • Sequences show every step. sendContractTransactionSequence lists each step with its contract, method, exact arguments and attached amount.
  • Fees. The wallet uses the fee you pass only when it is a whole number at or above the network’s live minimum; otherwise it shows and signs the live recommended fee. With no live fee the request is refused before the review. Private operations have floors (5,000 OU, claims 3,000 OU) and fees stop at 10,000,000 OU. See Network Fees.
  • Resends. When the node answers that a transaction was not accepted for now, the wallet sends the same signed bytes again, up to 3 tries. A used nonce goes back to the user for a new review. Nothing is re-signed without the user. See What Happens After You Send.
  • Hash. The hash returned is computed by the wallet the same way the network computes it, so you can look it up even when the node’s reply was lost.
0xio Desktop adds a few limits of its own: signTransaction signs standard transfers only, messages and memos over 4,000 characters are refused (never cut), and contract params must be an array. Each is INVALID_PARAMS before any review.

Security (v2.7.x)

The SDK includes several security hardening measures:
  • Origin-validated messaging: postMessage communication 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 walletReady signal 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 trustedParentOrigins in 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 both window (for extension content script) and window.parent (for iframe/WebView bridge).
  • Frame-Aware Listener: setupMessageListener() accepts messages from window.parent in addition to same-window messages.
  • walletReady via postMessage: Extension detection recognizes walletReady events sent via postMessage from parent frames.
DApp developers do not need to change any code. Just use the SDK as normal: it auto-detects the environment and selects the correct transport.

0xio Desktop without the extension (2.11.0)

A visitor who has 0xio Desktop but not the extension can use your site as is. initialize() looks for the extension first, for about 300 ms (detectTimeoutMs), and the extension always wins. When none answers, the SDK reaches 0xio Desktop on this computer and wallet.transport reads 'desktop' (otherwise 'extension').
What the visitor sees:
  • A Chrome prompt the first time, after Connect. Chrome asks once to let the site “access other apps and services on this device”. The SDK never triggers it on page load: when Chrome would ask, initialize() resolves with transport null and desktopAvailableOnConnect true, and the next connect() opens the desktop. Keep the Connect button enabled in that state. The SDK emits connectProgress with phase: 'local-network-prompt' while the prompt is open. A block ends the attempt at once with EXTENSION_NOT_FOUND and details.desktop: 'blocked', a desktop that is not running with 'not-running'; only an ignored prompt waits the full 20 seconds.
  • Approvals in the menu bar. Connect, signing, sends and network switches are reviewed in the 0xio Desktop menu bar (Reject or the action), not in a popup. The SDK waits up to ten minutes for an answer.
Rules and differences:
  • Tried only on https: pages that are a secure context, or on http://localhost, 127.0.0.1 and [::1] during development; never with desktop: false or an adapter for another wallet.
  • getBalance() reports private: 0; read private balances with getPrivateBalance().
  • Private methods run only for the wallet open in the desktop and while it is on the site’s network; otherwise they fail with WALLET_CHANGED or NOT_AVAILABLE.
  • getPendingPrivateTransfers() fails with UNSUPPORTED_METHOD; use getPrivateBalance({ include_claimable: true }) and claimPrivateTransfer().
  • At most 3 requests from a site wait at once, and 60 a minute.
  • If the desktop quits, the SDK emits disconnect with reason network_error; the next call reconnects. Call getConnectionStatus() or connect() to restore the session.
  • Site token (2.12.0). Any local program can send your site’s Origin, so an approved connect() gives the page a site token. The SDK keeps it in the page’s localStorage (0xio:desktop-site-token), shows it on every new link, and never hands it to your code. Without it (cleared storage, a private window) the visitor approves connect() again. disconnect() forgets it.

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 on localhost 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:

In the 0xio Mobile App

The app’s built-in browser injects the same providers (window.wallet0xio and window.octra) and answers the SDK over its WebView bridge, so the calls below work unchanged. What the user sees for each request:
  • https only. The wallet answers only pages served over https. A plain http page loads but cannot connect. Development builds of the app also accept http://localhost.
  • One request at a time. A second request while one is open is refused; wait for the first to settle.
  • Biometrics or PIN after the slide. With the user’s Ask for PIN before signing setting on (the default), Face ID or the PIN follows the slide. Allow for a few seconds of user time on every call.
  • Bound to a wallet and network. A request is held to the wallet and network it was reviewed under. If the user switches either before it is signed, nothing is signed or sent and your call rejects. Listen for accountChanged and networkChanged and re-read state.
  • Connections lapse. A connection is held per wallet and network and expires after 24 hours; call connect() again when isConnected() is false.
  • Raw RPC cannot submit. Pass-through RPC methods that submit or send are refused; use sendTransaction, callContract or submitTransaction so the user sees an approval.
  • Lookalike warning. If your host is spelt close to an app listed in the 0xio directory, or uses another alphabet, the user sees a warning before your site can connect, sign, send or switch network, and your request rejects if they close it. Serve your dApp from its listed host to avoid it.

WalletConnect (0xio Mobile)

A dApp on another device can reach the mobile wallet through WalletConnect v2 (v1 links are refused). The user scans your code from the scan icon on Home, or pastes the connection link.
  • Connect: the sheet shows your site, the wallet and the network, each with Change, and warns when you asked for a different network than the one chosen. The user confirms with Slide to connect. Watch-only wallets cannot connect.
  • Site identity: the app names your site by the origin WalletConnect’s Verify service confirms, only when Verify reports it valid. Otherwise it shows the name your metadata gives, tagged Unverified (or Lookalike when it is spelt like a listed app). A domain Verify reports invalid, or one flagged as a scam, is refused before any sheet. Serve your dApp from the domain registered for your WalletConnect project so it shows as verified.
  • Arguments: octra_contractCall sheets show the arguments exactly as signed. A request that expires on your side while the transaction is being sent is still answered with its real result.
  • Session scope: the session is bound to the wallet and network chosen at connect. Choosing them does not switch the app; if the app is on another wallet or network later, requests fail and the user is asked to switch back or reconnect.
  • Methods: octra_sendTransaction, octra_contractCall and octra_signMessage open the same sheets as the in-app browser, with the same slide; octra_getAddress and octra_getBalance are answered without a prompt.
  • Timing: connect requests that arrive after the user leaves the WalletConnect screen are refused. Requests from a session already connected are answered anywhere in the app once it is unlocked.
  • Disconnect: the user can end a session from the WalletConnect screen or from Connected sites; it is removed for that wallet and network.

RFC-O-1 Provider Standard (v2.7.0)

The SDK includes an OctraProviderAdapter 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


EVM Provider

The SDK covers Octra. For Ethereum and other EVM networks, the 0xio browser extension (3.0 and later) is a standard EIP-1193 provider, so existing EVM libraries work with it unchanged. Users manage these connections as described in Connecting to Sites.

Discovery

  • The extension announces itself through EIP-6963 with the name 0xio Wallet and the rdns xyz.0xio.wallet.
  • It defines window.ethereum only when the page has none, so it never replaces another wallet.

Methods

Behavior to Expect

  • One address: eth_accounts answers only the address the site uses, even when several of the user’s wallets are connected to it. A request naming another address is refused. While the wallet is locked it answers [], and the same account comes back after unlock without a prompt.
  • Events: accountsChanged when the site moves to another of its wallets, chainChanged when the user moves it to another network. Events go only to the tabs and frames that used the provider.
  • Fees: transactions are signed with the wallet’s live fee. The fee your site sends is used only when the network gives none.
  • Approvals: the user can lower the amount of an approve or increaseAllowance on the review, or pick Unlimited. Never assume the signed amount equals the one you sent.
  • Contract creation: a transaction with no to opens a deploy review with the new contract’s address; signing is refused if the nonce moved after the review.
  • Adding a network: a chain the user already has is never changed by wallet_addEthereumChain; the request only asks to switch. A chain on the other side of the user’s Mainnet or Testnet setting asks to switch all of 0xio first.
  • Repeats and limits: the same connect, switch or add request sent again while its review is open waits on that review. Reads are rate limited per page and per site and answer -32005 beyond the limit.
  • Reload: when the extension reloads, pending requests reject with 4900 and the provider emits disconnect once.

Request lifecycle (2.12.0)

Every review call takes { requestId, timeoutMs, onStatus }, and every write also wait: 'submitted' | 'confirmed'. A timeout rejects with STATUS_UNKNOWN and never cancels the review; it never means “send again”.
Detection, statuses, receipts, reloads and error reasons: Request lifecycle.

Names (2.12.2)

lookupAddress() gives the primary name of the connected address in the Octra Name System, or null, so a connected button can show 0xio.oct or the address. resolveName(name) gives what a name points to. Both are reads.
Sending to a name, the name rules and where the name contract comes from: Names.

Core API

Connection Methods

initialize()

Initializes the SDK and checks for extension presence.
  • Returns: Promise<boolean>
  • Throws: ZeroXIOWalletError with code EXTENSION_NOT_FOUND if 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: ZeroXIOWalletError with code CONNECTION_REFUSED if 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>
  • Errors: WATCH_ONLY for a watch-only wallet (it has no key)

canSign()

Whether the connected wallet can sign (v2.10.0). A watch-only wallet connects and reads balances and history, but holds no key.
  • Returns: boolean | undefined
    • true: the wallet signs.
    • false: watch-only. Every signing call fails with WATCH_ONLY.
    • undefined: not connected, or an extension older than v3.0.3 that does not report it.
The same value is canSign on ConnectionInfo, on the connect event and on accountChanged, so it follows a wallet switch. See Watch-only wallets.

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> with hash, accepted, status (and the older txHash, 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 without a second review, because the signTransaction review already showed what was signed. A transaction not signed by the connected wallet is refused with INVALID_PARAMS.

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> with hash and accepted
  • 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 invalid
    • USER_REJECTED - If user rejects the signature request
    • WALLET_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.
Use Cases:
  • 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
Always include timestamps or nonces in messages to prevent replay attacks. Never sign messages you don’t understand.

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
To verify an auth signature, reconstruct the signed string with 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> with txHash, success, finality
AML contract calls use flat arguments: params: [arg1, arg2], not params: [[arg1, arg2]]. The extension handles nonce, signing, and broadcasting automatically.
The review shows the method with its arguments exactly as they are signed. For integers above 2^53, see What the Review Shows.

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, or null if 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, method, exact arguments, attached value and fee. A step whose fields disagree (see What the Review Shows) refuses the whole sequence with INVALID_PARAMS, naming the step.
  • Returns: Promise<unknown> with results[], one hash per step

deployContract(data)

Deploys a compiled Octra contract from the connected wallet. Needs extension 3.0.0 or later. The review shows the code size, the contract address the node predicts, the constructor params, the network and the fee; the wallet cannot be switched in that review, because the address depends on the deployer.
  • Parameters: bytecode: string (strict base64, at most 512 KB decoded), params?: string | unknown[] (valid JSON, at most 64 KB), ou?: string (a whole number from 1 to 50,000,000)
  • Fee: without ou the wallet uses the node’s recommended deploy fee, never below 200,000 OU plus 1,000 OU per KiB of code
  • Returns: Promise<{ txHash, contractAddress, success }>
  • Errors: DEPLOY_CHANGED when another transaction moved the nonce after the review and the address would differ (nothing is sent); DEPLOY_FAILED when the node refuses it

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> with txHash, 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> with txHash, 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 fills id and a raw micro-OCT amount; 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 (or private_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 review except registerPrivateViewKey for an account that is not registered yet; proofs take from seconds to a couple of minutes, and the SDK waits up to 3 minutes. Proofs follow the proof rules the network states (see Network Proof Rules). When the network states none, or the wallet cannot reach it to check, every private method and private transfer is refused and nothing is sent. Public calls are not affected.

Network Methods

getNetworkInfo()

Gets current network information.
  • Returns: Promise<NetworkInfo>

Event Listeners

The SDK extends EventEmitter 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 with NOT_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.

Request Update (2.12.0)

Every status change of a request this page sent. Same meaning in every wallet.

Connect Progress (2.12.0)

The browser’s local network prompt is open while connect() tries 0xio Desktop.

Transaction Confirmed

Accepted by the node in the extension and 0xio Desktop, final on chain in the 0xio app. For one meaning everywhere, use requestUpdate or wait: 'confirmed'.

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.

Watch-only wallets

A watch-only wallet can connect, so a dapp should show a read-only state instead of buttons that will fail.
  • canSign is false: show the read-only state. sendTransaction, signMessage, signAuthMessage, signTransaction, callContract, deployContract, sendContractTransactionSequence, sendPrivateTransfer, claimPrivateTransfer, the private primitives and getPublicKey reject with WATCH_ONLY.
  • canSign is undefined: the extension is older than v3.0.3. Treat a missing publicKey as a hint of watch-only; those versions refuse signing with PERMISSION_DENIED.

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_REJECTED errors gracefully
  • Provide clear install prompts if extension not found
  • Request minimum necessary permissions
  • Use getConnectionStatus() to restore existing connections
  • Check canSign() after connecting and on accountChanged; show a read-only state for a watch-only wallet

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_transactions scope 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 forceRefresh sparingly
  • Batch multiple operations when possible
  • Clean up event listeners on unmount with cleanup()
For production dApps, implement comprehensive error handling and user feedback for all wallet operations.