Installation
Install via npm or yarn:- Version: 2.12.2
- Names (
lookupAddress,resolveName): see Names; needs thecontract_viewspermission - 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.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). 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_orcontract, its method asmethodorencrypted_data, and its arguments asparamsormessage. When two of them are set and differ, the wallet refuses the request withINVALID_PARAMSbefore 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
paramsis 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.
sendContractTransactionSequencelists 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.
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:
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.
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').
- 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 withtransportnullanddesktopAvailableOnConnecttrue, and the nextconnect()opens the desktop. Keep the Connect button enabled in that state. The SDK emitsconnectProgresswithphase: 'local-network-prompt'while the prompt is open. A block ends the attempt at once withEXTENSION_NOT_FOUNDanddetails.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.
- Tried only on
https:pages that are a secure context, or onhttp://localhost,127.0.0.1and[::1]during development; never withdesktop: falseor an adapter for another wallet. getBalance()reportsprivate: 0; read private balances withgetPrivateBalance().- 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_CHANGEDorNOT_AVAILABLE. getPendingPrivateTransfers()fails withUNSUPPORTED_METHOD; usegetPrivateBalance({ include_claimable: true })andclaimPrivateTransfer().- At most 3 requests from a site wait at once, and 60 a minute.
- If the desktop quits, the SDK emits
disconnectwith reasonnetwork_error; the next call reconnects. CallgetConnectionStatus()orconnect()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’slocalStorage(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 approvesconnect()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 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:
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
accountChangedandnetworkChangedand re-read state. - Connections lapse. A connection is held per wallet and network and expires after 24 hours; call
connect()again whenisConnected()is false. - Raw RPC cannot submit. Pass-through RPC methods that submit or send are refused; use
sendTransaction,callContractorsubmitTransactionso 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_contractCallsheets 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_contractCallandoctra_signMessageopen the same sheets as the in-app browser, with the same slide;octra_getAddressandoctra_getBalanceare 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 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
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.ethereumonly when the page has none, so it never replaces another wallet.
Methods
Behavior to Expect
- One address:
eth_accountsanswers 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:
accountsChangedwhen the site moves to another of its wallets,chainChangedwhen 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
approveorincreaseAllowanceon the review, or pick Unlimited. Never assume the signed amount equals the one you sent. - Contract creation: a transaction with no
toopens 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
-32005beyond the limit. - Reload: when the extension reloads, pending requests reject with
4900and the provider emitsdisconnectonce.
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”.
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.
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> - Errors:
WATCH_ONLYfor 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 | undefinedtrue: the wallet signs.false: watch-only. Every signing call fails withWATCH_ONLY.undefined: not connected, or an extension older than v3.0.3 that does not report it.
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>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 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>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, 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>withresults[], 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
outhe 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_CHANGEDwhen another transaction moved the nonce after the review and the address would differ (nothing is sent);DEPLOY_FAILEDwhen 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>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 review exceptregisterPrivateViewKey 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 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.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 whileconnect() 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, userequestUpdate 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.canSignisfalse: show the read-only state.sendTransaction,signMessage,signAuthMessage,signTransaction,callContract,deployContract,sendContractTransactionSequence,sendPrivateTransfer,claimPrivateTransfer, the private primitives andgetPublicKeyreject withWATCH_ONLY.canSignisundefined: the extension is older than v3.0.3. Treat a missingpublicKeyas a hint of watch-only; those versions refuse signing withPERMISSION_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_REJECTEDerrors gracefully - Provide clear install prompts if extension not found
- Request minimum necessary permissions
- Use
getConnectionStatus()to restore existing connections - Check
canSign()after connecting and onaccountChanged; 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_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()