# Public vs Private Balances
Source: https://docs.0xio.xyz/concepts/balances
Compare public and private balances in 0xio, how each balance type works on Octra, and when to use FHE-encrypted balances for privacy.
Every 0xio wallet holds two distinct balance types on the same Octra address, giving you full control over your financial privacy.
* **Public balance (OCT):** a normal, transparent on-chain balance.
* **Private balance (pOCT):** an FHE-encrypted balance whose amount is hidden from everyone except you.
## Public Balance
Your **Public Balance** (OCT) is visible to everyone on the blockchain.
### Characteristics
* **Transparency:** Visible to anyone viewing the blockchain
* **Speed:** Faster transaction processing (no proof generation required)
* **Compatibility:** Works with all protocols and services
### When to Use
* Standard interactions with public DeFi protocols
* Sending to exchanges that don't support FHE
* When transparency is beneficial or required
* Situations where speed and lower cost are priorities
## Private Balance
Your **Private Balance** (pOCT) is encrypted with Octra's FHE layer and only visible to you.
### Characteristics
* **Privacy:** Amount encrypted on-chain; completely confidential
* **Security:** Complete financial privacy
* **Validation:** Transfers are validated with zero-knowledge range proofs without revealing amounts
* **Cost:** Privacy operations carry a larger fee because the network must process ciphertexts and verify proofs
### When to Use
* Confidential transfers and payments
* Private trading and swaps
* Salary payments and business transactions
* Protecting wealth information from public view
> [!NOTE]
>
> Your 0xio wallet displays both balances simultaneously. You move funds between them at any time using **Encrypt** (public → private) and **Decrypt** (private → public), and receive incoming private transfers with **Claim**.
## Moving Between Balances
### Encrypting Funds (Public → Private)
This "shields" your funds by moving OCT into your encrypted pOCT balance so the amount is no longer visible to blockchain observers.
**Process:**
1. Open the Encrypt action
2. Enter the amount to shield
3. Review the fee
4. Confirm the transaction
5. Wait for blockchain confirmation
**Technical note:** Your device generates the FHE ciphertext and the supporting range proof locally before broadcasting. Proof generation can take anywhere from seconds (native desktop) to over a minute (mobile), depending on platform.
### Decrypting Funds (Private → Public)
This "unshields" funds by moving pOCT back into your public OCT balance, making the amount visible on the blockchain again. A zero proof attests that the remaining encrypted balance stays consistent.
**Process:**
1. Open the Decrypt action
2. Enter the amount to unshield
3. Review the fee
4. Confirm the transaction
### Claiming Incoming Private Transfers
When someone sends you a private (stealth) transfer, the funds arrive as a pending encrypted output rather than landing in your balance automatically. **Claim** scans incoming outputs for those addressed to you and adds them to your private balance.
## Network Fees
Octra denominates fees in **ou**, the network's base fee unit (1 OCT = 1,000,000 ou).
* Standard public transfers carry a small, flat fee.
* Stealth and other privacy operations require a minimum fee of **1,000,000 ou (1 OCT)** because the network must process ciphertexts and verify zero-knowledge proofs.
Exact fees are estimated by the wallet at the time of each transaction.
# Cryptographic Specifications
Source: https://docs.0xio.xyz/concepts/cryptography
Technical reference for 0xio cryptographic primitives including Ed25519 signatures, AES-GCM vault encryption, PBKDF2, and SHA-256 hashing.
0xio uses standard, widely reviewed cryptographic algorithms.
## Signing Algorithm
**Ed25519** (Twisted Edwards Curve)
* High-speed signature generation and verification
* 256-bit security level
* Implemented via TweetNaCl.js for browser compatibility
* Resistant to side-channel attacks
## Hashing
* **SHA-256:** Primary hashing algorithm for address generation
* **HMAC-SHA512:** Used in key derivation processes
## Symmetric Encryption
**AES-256-GCM** for local vault protection
* Authenticated encryption: guarantees both confidentiality and integrity
* Protects your private keys at rest on the device
* The vault key is derived from your password with **PBKDF2-HMAC-SHA256**
* In the extension the vault is sealed under a random vault key, which is stored only wrapped by the key derived from your password. While unlocked, the session holds that vault key, never your password, and changing the password re-wraps the vault key without touching the vault
The PBKDF2 iteration count is tuned per platform to balance security against device performance:
| Platform | Vault KDF iterations |
| - | - |
| Extension | 900,000 |
| Desktop | 600,000 |
| Mobile (PIN) | 100,000 |
## Key Derivation
### Mnemonic Standard
**BIP-39** recovery phrases (English wordlist)
* 0xio generates **12-word** phrases (128-bit entropy)
* Imports of longer phrases (e.g. 24-word, 256-bit) are also accepted
* Dictionary: the 2048-word official BIP-39 English wordlist
### Seed Generation
**PBKDF2-HMAC-SHA512** with 2048 iterations
* Converts the mnemonic to a 64-byte cryptographic seed
* Salt: the string `mnemonic` plus an optional passphrase (per BIP-39)
### Master Root Key Derivation
All 0xio platforms (extension, mobile, desktop) use the same canonical key derivation:
```
master_key = HMAC-SHA512("Octra seed", seed)[0:32]
```
* **No HD path:** Octra uses the master root key directly; there is no BIP-44 derivation path
* **Deterministic:** The same seed phrase always produces the same master key
* **Cross-platform:** Importing the same seed phrase produces the same address on all 0xio products (extension, mobile, desktop)
* **Secure:** HMAC-SHA512 provides cryptographic strength; the first 32 bytes become the Ed25519 seed (the next 32 bytes form the chain code)
### EVM Key Derivation (Extension)
With EVM on, the browser extension also derives a secp256k1 key from the same BIP-39 seed along the standard path `m/44'/60'/0'/0/i`, where `i` is the account index of the matching Octra account. The 0x address (EIP-55 checksummed) is the one other EVM wallets derive from that phrase. Only the address is stored; the key is derived for a single signature, checked against the address and wiped, together with the master key and every intermediate node. A wallet imported from an Octra private key has no EVM key.
## Address Format
0xio generates Octra addresses through a multi-step process:
**1. Generate Ed25519 Public Key**
Derive the 32-byte public key from the private key using Ed25519
**2. Hash Public Key**
Compute the SHA-256 hash of the raw public key bytes
**3. Base58 Encoding**
Encode the 32-byte hash using Base58 (Bitcoin alphabet)
**4. Add Prefix**
Prefix with `oct` for Octra Network identification
**Result:** A 47-character string (`oct` + 44 Base58 characters). During generation, 0xio "grinds" (regenerating any key whose encoded hash is shorter than 44 characters), so every address is exactly 47 characters.
## Security Architecture
| Component | Implementation |
| - | - |
| **Private Key Storage** | AES-256-GCM encrypted vault on device |
| **Session Management** | Auto-lock with configurable timeout |
| **Password Protection** | PBKDF2-HMAC-SHA256 with user-defined password |
| **Telemetry** | No tracking or analytics. Desktop and mobile send crash reports without keys or phrases |
| **Network Communication** | HTTPS only |
> [!NOTE]
>
> What each client sends, and to whom, is set out in the [Privacy Policy](https://0xio.xyz/privacy).
## Privacy Cryptography (PVAC)
Octra's private balances are powered by **PVAC**, an additively-homomorphic encryption scheme. All PVAC cryptography runs locally on your device through **pvac-rs**, a purpose-built Rust library built on the `curve25519-dalek` Ristretto255 group.
### Core Operations
1. **FHE Encrypt/Decrypt:** Additively-homomorphic encryption of token amounts, so ciphertexts can be added and subtracted on-chain (`ct_add` / `ct_sub`) without ever decrypting
2. **Range Proofs:** Bulletproofs-style zero-knowledge proofs (64-bit) that an encrypted amount is non-negative and within bounds, without revealing the value
3. **Pedersen Commitments:** Binding commitments to amounts using Ristretto255 points, used for transaction integrity
4. **Bound & Zero Proofs:** Used when decrypting and when validating balance updates to prove a ciphertext encrypts zero (or a bounded value)
5. **Stealth Transfers:** An X25519 ECDH shared secret derives a per-output stealth tag and claim keys, so private sends are unlinkable and the recipient scans for outputs addressed to them
### Stealth Transfer Detail
A stealth transfer derives its routing and claim material entirely from an ECDH shared secret:
* **Shared secret:** `SHA-256(X25519(sender_ephemeral_sk, recipient_view_pub))`
* **Stealth tag:** first 16 bytes of `SHA-256(shared_secret ‖ "OCTRA_STEALTH_TAG_V1")`, which the recipient looks for when scanning on-chain outputs
* **Amount:** encrypted to the recipient with AES-256-GCM keyed by the shared secret
* **Claim keys:** further domain-separated SHA-256 derivations bind the claim to the recipient's address
### pvac-rs
The **pvac-rs** library compiles to multiple targets to run natively on each platform:
| Platform | Target |
| - | - |
| Extension | WebAssembly (with multithreading via `wasm-bindgen-rayon`) |
| Desktop | Native Rust binary |
| iOS | Static library (`aarch64`) |
| Android | Shared library (`arm64`, `armv7`, `x86_64`) |
Range-proof generation time varies widely by platform, from seconds on native desktop builds to over a minute on mobile.
**Key dependencies:** `curve25519-dalek` (Ristretto255 group arithmetic and scalars) and `sha2` / `sha3` for hashing. The browser/mobile JavaScript layer separately uses `@noble/hashes` and `tweetnacl`.
# Cross-Chain Privacy Layer
Source: https://docs.0xio.xyz/concepts/privacy-layer
How 0xio uses Octra as a privacy backend for ETH, SOL, and any chain: swap to OCT, encrypt via FHE, transfer privately, swap back.
> [!NOTE]
>
> **Status: Research & Design Phase**
> This feature is planned for future development. The architecture builds on Octra's existing FHE (PVAC) primitives and 0xio's cross-chain swap infrastructure.
## The Vision
**Use Octra as a privacy middleware layer for any blockchain.** Users swap ETH, SOL, or USDC into OCT via the 0xio DEX, encrypt the OCT balance using Octra's FHE, transfer privately, then the recipient decrypts and swaps back to any token on any chain.
OCT is the **privacy denomination**: all assets flow through it. No new wrapped tokens needed.
```
ETH User → DEX swap to OCT → encrypt → private transfer → decrypt → DEX swap to ETH/SOL/USDC
```
## How It Differs from Umbra
### Umbra on Ethereum
Stealth addresses (ECDH). Every payment goes to a one-time address that only the recipient can link to their identity. Simple but **amounts are still visible** on-chain.
### Umbra on Solana
Full privacy via Arcium MPC: encrypted balances, mixer pool with ZK proofs, relayer network. Privacy on **both** the source and destination chain. End-to-end encryption.
### 0xio Privacy Layer
FHE-encrypted transfers on Octra as a **middle hop**. Privacy exists only on the Octra layer; the deposit and withdrawal on ETH/SOL are public.
| Aspect | Umbra (ETH) | Umbra (SOL) | 0xio (octra) |
| - | - | - | - |
| Source chain | Public (stealth addr) | Encrypted | **Public** |
| Transfer layer | Same chain | Same chain | **Encrypted (Octra FHE)** |
| Destination chain | Public (stealth addr) | Encrypted | **Public** |
| Amount hidden | No | Yes | **Yes (on Octra only)** |
| Cross-chain | No | No | **Yes** |
| Tech | ECDH | MPC + ZK | **FHE** |
## Two Privacy Modes
### Mode 1: Basic (Octra-Only Privacy)
The simplest version uses only the existing 0xio DEX + Octra FHE. Deposits and withdrawals are public, but the transfer itself is private.
```
[PUBLIC] User swaps 1 ETH → OCT via DEX ← visible on Etherscan
[PRIVATE] OCT encrypted on Octra via FHE ← amount hidden
[PRIVATE] Transfer encrypted OCT to recipient ← sender, receiver, amount all hidden
[PRIVATE] Recipient holds encrypted OCT ← invisible
[PUBLIC] Recipient swaps OCT → SOL via DEX ← visible on Solana
```
**What's private**: who sent to whom, how much, when the recipient cashes out.
**What's NOT private**: the deposit and withdrawal are visible on ETH/SOL. Timing + amount correlation can link them.
> [!WARNING]
>
> Basic mode is not end-to-end private. For maximum privacy, use Advanced mode or hold encrypted OCT for extended periods and withdraw in pieces to different addresses.
### Mode 2: Advanced (End-to-End Privacy)
Uses mature, live privacy tech on **every chain** in the pipeline. Each hop is shielded by a different technology optimized for that chain.
```
[PRIVATE] User shields ETH into RAILGUN pool ← sender unlinked from deposit
[PRIVATE] Unshield to 0xio bridge deposit ← looks like generic RAILGUN withdrawal
[PRIVATE] Bridge swaps to OCT on Octra ← encrypted by FHE
[PRIVATE] Transfer encrypted OCT to recipient ← amount + parties hidden
[PRIVATE] Recipient decrypts, swaps OCT out ← exit via stealth addr or Umbra
[PRIVATE] Funds arrive at fresh stealth address ← recipient fully unlinked
```
**Every hop is private.** No public link between sender and receiver on any chain.
### The Privacy Tech Stack (All Live, SDKs Ready)
| Hop | Chain | Technology | Status | What it hides |
| - | - | - | - | - |
| **Deposit** | Ethereum | RAILGUN v3 shielded pool | Live (\$4.5B volume) | Sender identity (deposit goes through RAILGUN's anonymity set) |
| **Deposit alt** | Ethereum | 0xbow Privacy Pools | Live (Vitalik-backed) | Sender + compliance proof (prove funds aren't sanctioned) |
| **Transfer** | Octra | FHE (PVAC) | Live | Sender, receiver, amount (all encrypted) |
| **Withdraw** | Ethereum | ERC-5564 stealth addresses (Umbra v2) | Live (77K+ addrs) | Recipient identity (one-time unlinkable address) |
| **Withdraw** | Solana | Umbra Privacy SDK (Arcium MPC) | Live since March 2026 | Sender, recipient, amount (fully shielded) |
| **Gas** | All | Relayer network | Planned | Funding pattern (gas deducted from balance, not paid separately) |
### How Each Technology Works
> [accordion] RAILGUN (ETH Deposits)
>
> RAILGUN is a shielded pool on Ethereum with \$4.5B cumulative volume. Users "shield" tokens into the pool, breaking the on-chain link to their address. From inside the pool, they can transfer to any address privately. 0xio uses RAILGUN for deposits: user shields ETH → unshields to the bridge contract. The bridge deposit looks like a generic RAILGUN withdrawal, not traceable to the original user.
>
> SDK: `@railgun-community/wallet` (npm, TypeScript, browser + mobile compatible)
> [accordion] 0xbow Privacy Pools (ETH Deposits with Compliance)
>
> Privacy Pools (backed by Vitalik, live since March 2025) add compliance to privacy. Users can prove their withdrawal belongs to a "clean" association set without revealing their full history. This lets regulated users use privacy features while proving they're not laundering funds.
>
> Useful for institutional users who need privacy + compliance simultaneously.
> [accordion] ERC-5564 Stealth Addresses (ETH Withdrawals)
>
> Stealth addresses (ERC-5564 + ERC-6538) let the solver derive a one-time address for the recipient. Funds arrive at a fresh address that only the recipient can link to their identity. 77,000+ stealth addresses generated on Ethereum mainnet. Umbra v2 (launching summer 2026) is the primary implementation.
>
> The stealth address gas problem (fresh address has no ETH for gas) is solved by EIP-7702 delegation or RAILGUN relayers.
> [accordion] Umbra Privacy SDK (Solana Withdrawals)
>
> Umbra on Solana (powered by Arcium MPC, public launch March 2026) provides full privacy: sender, recipient, AND amount are shielded. The TypeScript SDK handles all crypto internally. The solver deposits withdrawal funds into Umbra's shielded pool, transfers to the recipient, who withdraws whenever they want.
>
> SDK: Umbra Privacy TypeScript SDK (mainnet + devnet, browser + Node.js)
> [accordion] Solana Confidential Transfers (Complementary)
>
> Solana's native Token-2022 extension hides transfer amounts and balances using homomorphic encryption + ZK proofs. Already used by PayPal's PYUSD. Addresses remain public, so use in combination with Umbra for full coverage. Useful as a fallback when Umbra is unavailable.
## Privacy Enhancement Layers
Beyond the core tech stack, these mechanisms strengthen anonymity:
### 1. Amount Obfuscation (Partial Withdrawals)
User deposits 1 ETH → receives encrypted OCT → withdraws as 0.3 ETH + 0.4 SOL + 0.25 USDC at different times to different addresses on different chains. Each withdrawal goes to a fresh stealth address. The FHE encrypted balance supports partial withdrawals naturally.
### 2. Batched Withdrawals
The solver batches multiple users' withdrawals into single transactions. Instead of "Address B withdrew 1 ETH," it's "The solver sent 47 ETH to 30 addresses." Individual amounts are obscured in the batch.
### 3. Time Delay (Anonymity Set Growth)
The longer users hold encrypted OCT, the larger the anonymity set. If 100 users deposited today and 100 withdrew tomorrow, no observer can link specific deposits to withdrawals. The encrypted OCT balance acts as a privacy buffer.
### 4. Relayer Network (No Gas Linkage)
The relayer submits withdrawal transactions on behalf of the user. The gas cost is **deducted from the encrypted OCT balance itself**; the user never pays gas from the recipient address. This means the recipient address has zero prior on-chain activity, breaking the funding-pattern fingerprint.
Example: User withdraws 200 OCT as ETH. Relayer fee is \~5 OCT. User receives 195 OCT worth of ETH. The relayer is compensated from the withdrawal amount, not from a separate gas payment.
### 5. Volume
Privacy strength scales with usage. More users holding encrypted OCT on Octra → harder to correlate any single deposit to a withdrawal. Unlike mixer pools (Tornado Cash), there is no shared pool: each user has their own encrypted balance on their own Octra address. The anonymity comes from the volume of encrypted balances existing on-chain simultaneously.
## Why OCT as the Privacy Denomination
Instead of creating octaWETH, octaWSOL, octaUSDC (separate wrapped tokens on Octra), all assets swap through OCT:
| Approach | Pros | Cons |
| - | - | - |
| **Per-token wrapping** (octaWETH, etc.) | Same asset in/out, no price exposure | Fragmented anonymity sets, new bridge contracts per asset |
| **OCT as denomination** | Single asset = more encrypted balances on-chain, uses existing DEX, simpler infrastructure | Price exposure during hold period, extra swap fees |
We chose OCT because:
* **Larger anonymity set**: all privacy users hold encrypted OCT on Octra (more encrypted balances = harder to correlate)
* **Existing infrastructure**: DEX swap (live), encrypted OCT balances (live), private transfers (live)
* **No new contracts**: no octaWETH mint/burn, no per-asset bridge extensions
* **OCT utility**: every privacy transfer drives OCT demand
The trade-off is price exposure: if OCT price drops while the user holds encrypted OCT, they lose value. For short hold times (minutes to hours), this is negligible. For longer holds, users accept the risk for privacy.
## Why FHE Instead of ZK or MPC
| Approach | Examples | How it works | Limitation |
| - | - | - | - |
| **Stealth addresses** | Umbra (ETH) | One-time addresses via ECDH | Amounts visible |
| **ZK proofs** | Railgun, Aztec | Prove facts without revealing data | Custom circuits per operation |
| **MPC** | Umbra (SOL), Secret Network | Multi-party encrypted computation | Threshold trust assumptions |
| **FHE** | **Octra / 0xio** | Compute directly on encrypted data | Performance overhead |
Octra's FHE is uniquely powerful because:
* **Encrypted arithmetic**: `ct_add` and `ct_sub` operate directly on ciphertexts
* **No custom circuits**: Unlike ZK, no per-operation circuit design needed
* **Private swaps possible**: AMM logic can run on encrypted order data (future)
* **Any amount**: No fixed denominations like Tornado Cash, and ciphertexts support partial spends
## What Already Exists vs. What's Needed
| Component | Status | Notes |
| - | - | - |
| ETH ↔ OCT swap | **Live** (devnet) | EthEscrowV2 + SwapVault v7 |
| Encrypted OCT balances | **Live** (mainnet) | PVAC encrypt/decrypt |
| Private OCT transfers | **Live** (mainnet) | ct_sub/ct_add on chain |
| Cross-chain solver | **Live** (devnet) | Detects swaps, fills automatically |
| TWAP oracle pricing | **Live** | 5-min time-weighted average |
| SOL ↔ OCT swap | **Planned** | Needs Solana escrow + solver |
| "Private Send" UI | **Needed** | One-click deposit → encrypt → transfer |
| Relayer network | **Needed** | Gasless withdrawals for address unlinkability |
| Batched withdrawals | **Needed** | Solver aggregates multiple withdrawals |
| Compliance framework | **Needed** | Viewing key selective disclosure |
## The Product: "Private Send"
A single feature in the 0xio wallet/extension:
**1. User clicks 'Private Send'**
Inputs: amount (ETH/SOL/USDC), recipient's Octra address
**2. Auto-swap to OCT**
DEX solver swaps the source token to OCT on Octra (\~30s)
**3. Auto-encrypt**
OCT balance encrypted via PVAC on Octra (instant)
**4. Private transfer**
Encrypted OCT sent to recipient on Octra (\~10s). Amount hidden from everyone.
**5. Recipient holds encrypted OCT**
Recipient sees encrypted balance in their 0xio wallet. Can hold as long as they want.
**6. Recipient cashes out, all at once or in pieces**
When ready, withdraw any portion to any address on any chain. Hold the rest.
Steps 2-4 are automated by the solver. The user experience is: "Send 1 ETH privately" → done.
### Private Balance: Partial Withdrawals
The recipient's encrypted OCT balance works like a private bank account. They can withdraw any amount at any time to any address on any chain:
```
┌─────────────────────────────────────────────┐
│ Private Balance: ████████ OCT (encrypted) │
│ │
│ [Withdraw] │
│ Amount: 200 OCT │
│ To: 0xA1b2... on Ethereum │
│ Receive as: ETH │
│ Relayer fee: ~5 OCT │
│ You receive: ~195 OCT worth of ETH │
│ │
│ Remaining: ████████ OCT (encrypted) │
│ │
│ Recent withdrawals: │
│ • 200 OCT → 0xA1b2... (ETH), 2h ago │
│ • 150 OCT → 7xKp3... (SOL), yesterday │
│ • 300 OCT → 0xC4d5... (USDC), 3d ago │
└─────────────────────────────────────────────┘
```
Each withdrawal is a separate DEX swap at a different time to a different address. This is the **core privacy mechanism**. An observer sees unrelated transactions on different chains with different amounts and timing. The link between them is hidden inside Octra's FHE layer.
> [!TIP]
>
> **Maximum privacy**: Withdraw in small, irregular amounts over days/weeks to fresh addresses on different chains. The encrypted balance on Octra acts as a privacy buffer: the longer you hold and the more you split withdrawals, the harder correlation becomes.
## Development Phases
### Phase 1: Basic Privacy (Octra-only)
* "Private Send" UI in 0xio wallet/extension
* Auto-chain: DEX swap → encrypt → transfer
* Partial withdrawals to any chain/address
* Relayer fee deducted from encrypted balance
* Uses existing DEX + PVAC infrastructure, with **no new integrations**
### Phase 2: ETH-Side Privacy
* Integrate **RAILGUN SDK** (`@railgun-community/wallet`) for shielded deposits
* User shields ETH → unshields to bridge → breaks sender link
* Integrate **ERC-5564 stealth addresses** for withdrawals via Umbra v2
* Solver sends to one-time stealth address (recipient unlinked)
* EIP-7702 delegation to solve stealth address gas problem
### Phase 3: SOL-Side Privacy
* Integrate **Umbra Privacy TypeScript SDK** for Solana withdrawals
* Solver deposits into Umbra shielded pool → recipient withdraws privately
* Sender, recipient, amount all hidden on Solana side
* Complement with Solana Confidential Transfers (Token-2022) for amount hiding
### Phase 4: Full Stack
* **0xbow Privacy Pools** integration for compliance-friendly deposits
* Relayer network decentralization (multiple relayers compete)
* Batched withdrawal aggregation in solver
* Anonymity set monitoring dashboard
* Selective disclosure via PVAC viewing keys
* Noir ZK circuits for custom compliance proofs
* Privacy audit by external security firm
## Comparison with Competitors
| Feature | Tornado Cash | Railgun | Umbra (ETH) | Umbra (SOL) | **0xio Basic** | **0xio Advanced** |
| - | - | - | - | - | - | - |
| Privacy model | Mixer pool | ZK shielded pool | Stealth addresses | MPC + ZK | FHE only | **FHE + RAILGUN + Umbra + stealth** |
| Amount privacy | Fixed denominations | Yes | No | Yes | Yes (Octra only) | **Yes (all hops)** |
| Cross-chain | No | No (per-chain) | EVM only | Solana only | Any chain with DEX | **Any chain with DEX** |
| End-to-end private | Yes (same chain) | Yes (same chain) | Partially | Yes | Octra hop only | **Yes (all chains)** |
| Private swaps | No | Yes (v3) | No | No | Planned | **Planned (FHE AMM)** |
| Compliance | None (sanctioned) | Relayer whitelist | None | Auditor program | Viewing keys | **Viewing keys + Privacy Pools** |
| Status | Sanctioned | Live | Live | Live | Phase 1 | **Phase 2-4** |
## Performance Estimates
| Step | Latency | Notes |
| - | - | - |
| DEX swap (ETH→OCT) | \~30s | Solver fills on Octra |
| Encrypt OCT | \<1s | Client-side PVAC, cached |
| Private transfer | \~10s | FHE ciphertext processing |
| Decrypt OCT | \<30s | Client-side with cached context |
| DEX swap (OCT→ETH) | \~30s | Solver fills on Sepolia |
| **Total (no hold)** | **\~90s** | End-to-end with minimal hold |
| **Recommended hold** | **Hours to days** | For meaningful anonymity set |
## FAQ
> [accordion] Is this like Tornado Cash?
>
> Similar concept (privacy via intermediary), different implementation. Tornado Cash used fixed-denomination mixer pools on a single chain with no compliance features, and was sanctioned. 0xio uses FHE for any-amount transfers across multiple chains, with viewing key disclosure for compliance. The privacy model is also different: Tornado hid deposits in a pool, while 0xio uses homomorphic encryption on the Octra chain.
> [accordion] Can someone trace my deposit to my withdrawal?
>
> The Octra transfer itself is fully private (FHE encrypted). However, the deposit on ETH and withdrawal on ETH/SOL are public. If you deposit 1 ETH and immediately withdraw 1 ETH worth of OCT, timing and amount correlation could link them. For better privacy: hold encrypted OCT for longer, withdraw in different amounts, use multiple withdrawal addresses.
> [accordion] Why not full end-to-end encryption like Umbra on Solana?
>
> Umbra on Solana built a full MPC privacy infrastructure on a single chain. We're cross-chain: we don't control the source/destination chains (Ethereum, Solana). Their deposits and withdrawals are inherently public. Our FHE privacy lives on Octra as a middle layer. Full end-to-end would require privacy infrastructure on every supported chain, which defeats the purpose of using Octra as a unified backend.
> [accordion] What about price risk while holding OCT?
>
> Users are exposed to OCT price movement while holding encrypted OCT. For short holds (minutes), this is negligible. For longer holds (days), users accept the trade-off between privacy and price stability. Future iterations may support encrypted stablecoin-denominated balances on Octra to eliminate this risk.
> [accordion] Is this legal?
>
> Privacy is legal. Money laundering is not. The 0xio privacy layer includes selective disclosure (viewing keys shared with auditors) and relayer compliance, following models established by Railgun and Umbra. Users can prove transaction history to authorized parties without making it public. Unlike Tornado Cash, which had no compliance mechanism, the design prioritizes regulatory compatibility.
# Build with AI
Source: https://docs.0xio.xyz/developers/build-with-ai
Connect Claude Code, Cursor or VS Code to the 0xio docs, give it the SDK rules and start from a working dapp.
## Docs MCP server
These docs are an MCP server at `https://docs.0xio.xyz/mcp` (streamable HTTP). Your assistant can search them and read any page while it writes code.
```bash Claude Code theme={null}
claude mcp add --transport http 0xio-docs https://docs.0xio.xyz/mcp
```
```json title="Cursor (.cursor/mcp.json)" theme={null}
{
"mcpServers": {
"0xio-docs": {
"url": "https://docs.0xio.xyz/mcp"
}
}
}
```
```json title="VS Code (.vscode/mcp.json)" theme={null}
{
"servers": {
"0xio-docs": {
"type": "http",
"url": "https://docs.0xio.xyz/mcp"
}
}
}
```
* **Claude Code:** add `--scope project` to share it through the repo's `.mcp.json`, or `--scope user` for every project.
* **Cursor:** put the same file at `~/.cursor/mcp.json` for every project.
## Plain text docs
For assistants without MCP:
* [llms.txt](https://docs.0xio.xyz/llms.txt): an index of every page
* [llms-full.txt](https://docs.0xio.xyz/llms-full.txt): every page in one file
* Any page as Markdown: add `.md` to its URL, for example [request-lifecycle.md](https://docs.0xio.xyz/developers/request-lifecycle.md)
## SDK rules
`@0xio/sdk` 2.12.1 and newer ships `AGENTS.md`: the rules an assistant follows when it writes code against the SDK (amounts, request ids, never resending, watch-only, network scoping, private transfers, errors). After `npm install @0xio/sdk` it is at `node_modules/@0xio/sdk/AGENTS.md`.
Claude Code, Cursor and VS Code read an `AGENTS.md` at the project root. Point yours to the SDK's:
```markdown AGENTS.md theme={null}
Before writing wallet code, read node_modules/@0xio/sdk/AGENTS.md.
```
In Claude Code, a line `@node_modules/@0xio/sdk/AGENTS.md` imports the whole file.
## Starter
```bash theme={null}
npm create @0xio/dapp my-dapp
```
A Vite, React and TypeScript dapp that detects the wallet, connects, shows the address and balance per network, sends OCT with a request id and a confirmed wait, follows a send the SDK lost track of instead of sending again, and handles watch-only wallets. It comes with an `AGENTS.md` that loads the SDK rules and the docs MCP server set up for all three editors.
## First prompt
Paste this into your assistant from the project folder:
```text theme={null}
Read node_modules/@0xio/sdk/AGENTS.md and follow it. Use the 0xio-docs MCP server
for anything it does not cover. Add a page that lets a connected user call the
deposit method on contract
with an OCT amount, waits for confirmation,
shows the request status and never resends.
```
# Names
Source: https://docs.0xio.xyz/developers/names
Show a .oct name for an address and resolve a name to an address with @0xio/sdk 2.12.2.
Since SDK 2.12.2 a dapp can read the Octra Name System with two calls. `lookupAddress` gives the name to show for an address, such as `0xio.oct`. `resolveName` gives what a name points to. Both are reads: no review, no signature, no fee.
The Octra Name System is live on devnet. On a network without it, both calls resolve with `null`, so a dapp written today keeps working when a network gains names.
## Before you start
Ask for `contract_views` when you connect. The wallet refuses the reads without it.
```typescript theme={null}
import { ZeroXIOWallet } from '@0xio/sdk';
const wallet = new ZeroXIOWallet({ appName: 'My DApp' });
await wallet.initialize();
const { address } = await wallet.connect({
permissions: ['accounts', 'contract_views'],
});
```
If your page sets a Content Security Policy, add `https://raw.githubusercontent.com` to `connect-src`. The SDK reads the name contract's address from there (see [Where the contract comes from](#where-the-contract-comes-from)).
## Show a name on the connected button
`lookupAddress()` resolves with the primary name of the connected address, or `null` when it has none.
```typescript theme={null}
const short = (a: string) => `${a.slice(0, 6)}...${a.slice(-4)}`;
async function label(): Promise {
const { address } = wallet.getConnectionInfo();
if (!address) return 'Connect';
const name = await wallet.lookupAddress().catch(() => null);
return name ?? short(address);
}
button.textContent = await label();
wallet.on('accountChanged', async () => (button.textContent = await label()));
wallet.on('networkChanged', async () => (button.textContent = await label()));
```
A dapp can show the name or the address, whichever it prefers. If it shows the name, keep the address reachable in a tooltip or a second line.
| Call | Resolves with |
| - | - |
| `lookupAddress()` | the connected address's primary name, or `null` |
| `lookupAddress(address)` | that address's primary name, or `null` |
| `lookupAddress(address, networkId)` | the same on a named network, without a connection |
A primary name is set by its owner. The name service only reports one that is active, is held by that address and points back to it, so no second check is needed. An address whose owner pointed a name at it but did not make it primary has no name to show.
## Resolve a name to an address
`resolveName(name)` takes the name with or without `.oct`.
```typescript theme={null}
const hit = await wallet.resolveName('0xio.oct');
if (hit === null) {
// this network has no name service
} else if (hit.kind === 'wallet') {
await wallet.sendTransaction({ to: hit.wallet, amountOct: '1' });
} else if (hit.kind === 'circle') {
// the name points to a Circle: hit.circle is its id
} else {
// the name points to nothing: not registered, expired, or no target set
}
```
| `kind` | Meaning | Set field |
| - | - | - |
| `wallet` | the name points to a wallet | `wallet` (the address) |
| `circle` | the name points to a Circle whose link is confirmed | `circle` (the Circle id) |
| `none` | nothing to send to or open | |
Text that is not a valid name rejects with `INVALID_PARAMS`. A reply the SDK cannot read rejects with `NETWORK_ERROR`; it is never guessed.
## Rules for sending to a name
A name decides where money goes, so treat it as a label for an address and nothing more.
* Send to the address. Pass `hit.wallet` to `sendTransaction`, never the name.
* Show the address to the user before they confirm. The wallet's review shows it again.
* Resolve again right before sending. If the address differs from the one the user saw, stop and show the new one.
* Do not keep a resolution longer than a minute, or a looked up name longer than ten minutes. A name can be transferred or pointed elsewhere at any time.
* A name is not proof of who is connected. Use `signMessage` for that.
## Name rules
`normalizeOnsName(text)` returns the bare name or `null`. `isOnsName(text)` is true only for text that ends in `.oct` and passes the rules, which is the test to use on a recipient field.
* lower case letters `a` to `z`, digits and hyphens
* 1 to 32 characters
* no hyphen at the start or the end
```typescript theme={null}
import { isOnsName, normalizeOnsName } from '@0xio/sdk';
isOnsName('0xio.oct'); // true
isOnsName('0xio'); // false: no ending
normalizeOnsName('0XIO.oct'); // '0xio'
normalizeOnsName('a_b.oct'); // null
```
## Where the contract comes from
The name contract's address is not built into the SDK. It is read per network from the name list in the [token lists repository](https://github.com/0xio-xyz/Token-lists) (`ONS_LIST_URL`), kept for ten minutes, and the last list read is used when the list cannot be reached.
Before the first read, the SDK asks the contract what it is. It must answer as the Octra Name System with an interface version this SDK supports. A contract that does not is treated as no name service, so `lookupAddress` and `resolveName` resolve with `null`.
`onsContract(networkId)` resolves with the listed address or `null`. Never pass a name contract in from a page, a link or a QR code.
## Reference
| Export | What it is |
| - | - |
| `wallet.lookupAddress(address?, networkId?)` | primary name of an address, or `null` |
| `wallet.resolveName(name, networkId?)` | `{ name, kind, wallet, circle }`, or `null` |
| `onsContract(networkId)` | the listed name contract (async), or `null` |
| `normalizeOnsName(text)` | bare name, or `null` |
| `isOnsName(text)` | `true` for valid text ending in `.oct` |
| `ONS_SUFFIX` | `'.oct'` |
| `ONS_LIST_URL` | address of the name list |
| `ResolvedName` | type of a `resolveName` result |
# Oracle API
Source: https://docs.0xio.xyz/developers/oracle-api
REST API reference for the 0xio Oracle: OCT and token prices, swap quotes, price history, EVM address data and bridge analytics. Reads need no API key.
The **0xio Oracle** serves OCT and token prices, swap quotes, price history, EVM address data for the 0xio wallets, and bridge analytics. The OCT price follows the wOCT/ETH Uniswap v4 pool on Ethereum live, priced in USD from the ETH/USD trades of Coinbase, Kraken, Bitstamp, Binance and Bybit (stablecoin quotes converted at live rates), updated within 250 ms of a trade, and is cross-checked against CoinGecko and DexScreener. Price history is stored in PostgreSQL for charts.
## Base URL
```
https://oracle.0xio.xyz
```
## Authentication
**Reads need no API key.** Every `GET` route works without a key, limited per client IP. A valid key skips the IP limit and counts against its own monthly quota instead (10,000 calls a month unless your plan sets another). A retired or unknown key is treated as no key, so it falls back to the IP limit rather than failing.
Get a key from the [Developer Portal](https://devs.0xio.xyz) when you need more than the IP limit allows, and send it in the `x-api-key` header:
```http theme={null}
x-api-key: 0xio-<16 hex>-oracle
```
> [!WARNING]
>
> Keep your API key secret. Use it from your own server; never ship it in client-side code or a public repository.
### Route Access
| Endpoint | Access |
| - | - |
| `GET /health`, `GET /api/health`, `GET /api/metrics` | Public |
| `GET /api/price/:symbol`, `GET /api/prices`, `GET /api/price/oct/details`, `GET /api/price/twap` | No key (per-IP limit) or a key |
| `GET /api/quote`, `GET /api/reserves`, `GET /api/chart/history` | No key (per-IP limit) or a key |
| `GET /api/evm/history`, `GET /api/evm/nfts`, `GET /api/evm/tokens` | No key (per-IP limit) or a key |
| `GET /api/bridge/stats`, `GET /api/bridge/events`, `GET /api/bridge/chart` | No key (per-IP limit) or a key |
| `POST /api/bridge/event` | Public, rate limited (see [Bridge Analytics](#bridge-analytics)) |
| `POST /api/keys`, `GET /api/keys`, `DELETE /api/keys/:id`, `GET /api/usage` | Owner wallet signature |
| `DELETE /api/keys` | The key being revoked |
| `POST /api/simulate-trade`, `POST /api/liquidity` | Operator key only |
| `POST /api/keys/revoke-owner`, `GET /api/keys/usage`, `PATCH /api/keys/quota` | Operator key only |
Any other write without a valid key gets `401`. Quotas are per key, per calendar month (UTC), and reset on the 1st. "Operator key" routes are for the operator's own services: any other key gets `403`.
### Scopes
A key can be limited to scopes when it is created. Each route group needs its scope:
| Scope | Routes |
| - | - |
| `read:price` | `/api/price/...`, `/api/prices` |
| `read:quote` | `/api/quote` |
| `read:chart` | `/api/chart/history` |
| `read:reserves` | `/api/reserves` |
| `read:bridge` | `/api/bridge/...` |
| `read:evm` | `/api/evm/...` |
An empty or missing scope list means all scopes.
***
## Health Check
### Full Health Check
```http theme={null}
GET /health
```
Returns detailed service status including database and cache connectivity.
**Response:**
```json theme={null}
{
"status": "healthy",
"timestamp": "2025-01-26T12:00:00+00:00",
"version": "2.0.0",
"services": {
"database": "connected",
"redis": "connected"
},
"environment": "production"
}
```
### Simple Liveness Probe
```http theme={null}
GET /api/health
```
Returns a simple response for load balancer health checks.
**Response:**
```json theme={null}
{
"status": "ok",
"timestamp": 1706270400000
}
```
***
## Prices
### Get Token Price
```http theme={null}
GET /api/price/:symbol
```
Get the current spot price for a specific token.
**Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `symbol` | path | Token symbol (e.g., `OCT`, `USDC`, `SOL`, `ETH`) |
**Example:**
```bash theme={null}
curl https://oracle.0xio.xyz/api/price/OCT
```
**Response:**
```json theme={null}
{
"symbol": "OCT",
"price": 0.024816,
"source": "aggregated(uniswap+coingecko+dexscreener)"
}
```
The `source` field indicates where the price came from:
* `aggregated(...)`: OCT and wOCT. The names in brackets are the sources that agreed: `uniswap` (the live pool price) leads while it agrees with the polled `coingecko` and `dexscreener` prices; a source too far from the others is left out
* `spot`: ETH (from the live ETH/USD feed) and SOL (Coinbase, then Kraken)
* `static`: USDC and USDT, pegged at \$1
### Get All Prices
```http theme={null}
GET /api/prices
```
Get current prices for all supported tokens (OCT, WOCT, USDC, USDT, SOL, ETH).
**Example:**
```bash theme={null}
curl https://oracle.0xio.xyz/api/prices
```
**Response:**
```json theme={null}
{
"OCT": 0.024816,
"WOCT": 0.024816,
"USDC": 1.0,
"USDT": 1.0,
"SOL": 180.50,
"ETH": 3200.00
}
```
### Get OCT Detailed Market Data
```http theme={null}
GET /api/price/oct/details
```
Get full aggregated market data for OCT including 24h change, volume, liquidity, and source breakdown.
**Example:**
```bash theme={null}
curl https://oracle.0xio.xyz/api/price/oct/details
```
**Response:**
```json theme={null}
{
"symbol": "OCT",
"priceUsd": 0.024816,
"priceChange24h": -2.15,
"volume24h": 12500.00,
"liquidityUsd": 85000.00,
"source": "aggregated(uniswap+coingecko+dexscreener)",
"confidence": "high",
"spreadPct": 0.8,
"allSources": {
"uniswap": 0.024816,
"coingecko": 0.024800,
"dexscreener": 0.024832
},
"ethUsd": 1780.50,
"timestamp": 1745312400000
}
```
### Get TWAP Prices
```http theme={null}
GET /api/price/twap
```
Get 5-minute time-weighted average prices for OCT and ETH alongside the current spot, plus the OCT spot-vs-TWAP deviation.
**Example:**
```bash theme={null}
curl https://oracle.0xio.xyz/api/price/twap
```
**Response:**
```json theme={null}
{
"oct": {
"twap_5m": 0.024790,
"spot": 0.024816,
"deviation_pct": 0.10
},
"eth": {
"twap_5m": 1779.20,
"spot": 1780.50
},
"window_seconds": 300,
"timestamp": 1745312400000
}
```
***
## Trading
### Get Swap Quote
```http theme={null}
GET /api/quote
```
Get a swap quote with expected output amount and price impact.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `inputMint` | string | Input token symbol |
| `outputMint` | string | Output token symbol |
| `amount` | string | Input amount (as string) |
**Example:**
```bash theme={null}
curl "https://oracle.0xio.xyz/api/quote?inputMint=OCT&outputMint=USDC&amount=100"
```
**Response:**
```json theme={null}
{
"inputToken": "OCT",
"outputToken": "USDC",
"amountIn": 100,
"amountOut": 124.75,
"feeAmount": 0.25,
"priceImpact": 0.0005,
"spotPrice": 1.25,
"executionPrice": 1.2475,
"route": "Solver-AMM-v1"
}
```
### Get AMM Reserves
```http theme={null}
GET /api/reserves
```
Get current liquidity pool reserves.
**Example:**
```bash theme={null}
curl https://oracle.0xio.xyz/api/reserves
```
**Response:**
```json theme={null}
{
"reserves": {
"OCT": 1000000,
"USDC": 1250000,
"USDT": 500000
},
"timestamp": 1706270400000
}
```
### Simulate Trade (Operator)
```http theme={null}
POST /api/simulate-trade
```
Used by the solver to reserve liquidity or update local state before on-chain execution. Requires the operator key; any other key gets `403`.
**Request Body:**
```json theme={null}
{
"inputToken": "OCT",
"amountIn": 1000,
"outputToken": "USDC",
"amountOut": 1245.50
}
```
**Response:**
```json theme={null}
{
"success": true,
"newReserves": {
"OCT": 1001000,
"USDC": 1248754.50
}
}
```
### Update Liquidity (Operator)
```http theme={null}
POST /api/liquidity
```
Adjust liquidity reserves, for example after a rebalancing. Requires the operator key; any other key gets `403`.
**Request Body:**
```json theme={null}
{
"reserves": {
"OCT": 1000000,
"USDC": 1250000
}
}
```
**Response:**
```json theme={null}
{
"success": true,
"message": "Liquidity Updated",
"currentReserves": {
"OCT": 1000000,
"USDC": 1250000
},
"newPriceOctUsdt": 1.25
}
```
***
## Chart Data
### Get Price History
```http theme={null}
GET /api/chart/history
```
Get historical price data over a configurable time range. Points are downsampled to fit the range (e.g. 30d → \~1 point per 2h).
**Query Parameters:**
| Parameter | Type | Default | Description |
| - | - | - | - |
| `pair` | string | `OCT` | Token symbol |
| `range` | string | `1d` | Time window: `1h`, `1d`, `7d` (`1w`), `30d` (`1m`), `1y` (`365d`) |
**Example:**
```bash theme={null}
curl "https://oracle.0xio.xyz/api/chart/history?pair=OCT&range=30d"
```
**Response:**
```json theme={null}
[
{ "value": 1.24, "time": 1706270400 },
{ "value": 1.25, "time": 1706274000 },
{ "value": 1.26, "time": 1706277600 }
]
```
> [!NOTE]
>
> The `time` field on chart data points is a Unix timestamp in **seconds** (suitable for charting libraries like Lightweight Charts). Other endpoints (`/health`, `/api/price/oct/details`, `/api/reserves`) return timestamps in **milliseconds**.
***
## EVM Data
The 0xio wallets read EVM address data through these routes. They are read from Blockscout with the oracle's own key, server side. Each call covers one chain, so a client can query chains in parallel and keep per-chain failures apart.
### EVM History
```http theme={null}
GET /api/evm/history?chain=1&address=0x...
```
Latest transactions and ERC-20 transfers of an address.
**Query Parameters:**
| Parameter | Type | Description |
| - | - | - |
| `chain` | integer | `1` (Ethereum), `11155111` (Sepolia), `4663` (Robinhood Chain) or `46630` (Robinhood Chain Testnet) |
| `address` | string | `0x` and 40 hex characters |
**Response:** `{ transactions, token_transfers }`, Blockscout's v2 items trimmed to the fields wallets read, under Blockscout's own names. Every other field is dropped. Cached 60 seconds per chain and address.
* **`transactions` items:** `hash`, `timestamp`, `value`, `status` and `method`; `from`, `to` and `created_contract` as `{ hash }` or null; `fee` as `{ value }` (wei) or null; `raw_input`; `decoded_input` as `{ method_call, method_id, parameters: [{ name, type, value }] }` or null. Contract names are not passed on; wallets read them per address with the explorer's verified and scam flags.
* **`token_transfers` items:** `transaction_hash`, `timestamp` and `log_index`; `from` and `to` as `{ hash }` or null; `total` as `{ value, decimals }`; `token` as `{ address_hash }`. Token names, symbols and logos are left out, so a wallet names a token only from its own list.
* **Caps:** a scalar string is cut at 128 characters and a fee at 80. `raw_input` is kept whole up to 522 characters (a selector and eight words) and cut to its 10 character selector beyond that, and dropped unless it is `0x` and hex. `parameters` keeps the first 16. A parameter `name` or `type` is cut at 64 characters, `method_call` and a parameter `value` at 512 (a nested array or object longer than that becomes its JSON text, cut), and `method_id` at 10. A cut string ends with `…`.
### EVM NFTs
```http theme={null}
GET /api/evm/nfts?chain=1&address=0x...
```
ERC-721 and ERC-1155 tokens held by an address.
**Query Parameters:** `chain` (a positive integer chain ID) and `address` (`0x` and 40 hex characters).
**Response:**
```json theme={null}
{
"items": [
{
"contract": "0x...",
"tokenId": "42",
"standard": "ERC-721",
"amount": "1",
"name": "Piece #42",
"collection": "Collection",
"image": "https://..."
}
],
"complete": true
}
```
* `contract` is lowercase. `tokenId` and `amount` are decimal strings of at most 78 digits. `standard` is `ERC-721` or `ERC-1155`.
* `name` (token metadata) and `collection` (token name) are trimmed, stripped of control and bidi characters and capped at 80 characters, or null.
* `image` is an `https://` or `ipfs://` URL of at most 1024 characters, or null.
* Tokens Blockscout flags as scam or spam are dropped.
* **Paging:** follows Blockscout's pages up to 200 items (5 pages, 20 seconds in total). `complete` is false when more remain or a later page failed. Cached 60 seconds per chain and address (10 seconds for a result cut short by a failed page).
* **Errors:** `400` on a bad chain or address, or a chain Blockscout does not serve; `503` when Blockscout fails.
### EVM Tokens
```http theme={null}
GET /api/evm/tokens?chain=1&address=0x...
```
ERC-20 tokens held by an address, each with a spam verdict.
**Query Parameters:** `chain` (a positive integer chain ID) and `address` (`0x` and 40 hex characters).
**Response:** `{ items: [{ contract, symbol, name, decimals, balance, price, verified, spam, spamReason, logo }], complete }`, in Blockscout's order (highest USD value first).
* `contract` is lowercase. `decimals` is an integer from 0 to 36 (other items are dropped). `balance` is a decimal string of at most 78 digits, and zero balances are skipped.
* `symbol` (at most 11 characters) and `name` (at most 40) are trimmed and stripped of control, bidi and zero-width characters, or null when empty or longer.
* `price` is Blockscout's USD exchange rate when finite and above 0, else null.
* `logo` is kept only as an `https://` URL of at most 512 printable ASCII characters on `raw.githubusercontent.com`, `assets.coingecko.com` or `coin-images.coingecko.com`; null otherwise, and always null on a spam item. Never anything the token contract itself supplies.
* `verified` says whether the contract source is verified. It is looked up only where it decides the verdict (unpriced tokens no other rule caught), else null.
* `spam` is true when any rule below holds; `spamReason` is the first that matched, else null:
1. `flagged`: Blockscout marks it as scam or spam.
2. `link`: the name or symbol holds `http`, `www.` or a domain (a dot after a letter or digit, then 2 to 10 letters), such as `t.me` or `x.com`.
3. `bait`: the name or symbol holds claim, reward(s), airdrop, visit, free, voucher, bonus, gift, redeem or eligible as a word (camelCase split; airdrop, voucher and eligible anywhere).
4. `impersonation`: the symbol, with look-alike Cyrillic, Greek and fullwidth letters mapped to Latin and case and spaces ignored, is ETH, WETH, USDC, USDT, DAI or WBTC on a chain where that token is listed (1, 4663, 8453, 42161, 10, 137) at a contract that is not the canonical one. OCT and WOCT apply on every chain; the only canonical one is wOCT on Ethereum (`0x4647e1fe715c9e23959022c2416c71867f5a6e80`).
5. `unverified_unpriced`: the contract is not verified and has no price.
Rules 2 and 3 also see look-alike letters as Latin, and apply only to tokens with no market price, so a real token named after its site is not flagged.
* **Paging:** follows Blockscout's pages up to 200 items (5 pages, 20 seconds in total). `complete` is false when more remain or a later page failed. Cached 60 seconds per chain and address, 10 seconds when a page failed or verification lookups were left for the next call.
* **Errors:** `400` on a bad chain or address, or a chain Blockscout does not serve; `503` when Blockscout fails or its plan does not cover the chain.
***
## Bridge Analytics
The [bridge site](https://bridge.0xio.xyz) and the 0xio Extension report each step of a transfer to `POST /api/bridge/event`. The read routes count a row only after the oracle has found that transfer on chain.
### Report a Bridge Event
```http theme={null}
POST /api/bridge/event
```
Public, no key. The body is JSON of at most 4 KB, and unknown fields are refused.
```json theme={null}
{
"event_type": "lock",
"direction": "o2e",
"octra_addr": "oct...",
"eth_addr": "0x...",
"amount_raw": "1000000",
"amount_display": "1",
"tx_hash": "...",
"wallet_type": "0xio",
"source": "web"
}
```
| Field | Rule |
| - | - |
| `event_type` and `direction` | `lock` with `o2e` (to Ethereum; `tx_hash` is the Octra hash: 64 hex, no prefix), `claim` with `o2e`, `burn` with `e2o` (to Octra) and `any_swap` with `a2o` (`tx_hash` is the Relay request ID). The last three take `0x` and 64 hex. Any other type, or a direction that does not match the type, is refused |
| `octra_addr` | `oct` and 44 Base58 characters (43 accepted). Needed for `lock`, `burn` and `any_swap` |
| `eth_addr` | `0x` and 40 hex, not the zero address. Needed for every type (`claim`: the recipient) |
| `amount_raw` | Whole micro OCT, digits only, above 0 and at most 1 billion OCT |
| `amount_display` | Optional. The stored display is always derived from `amount_raw` |
| `wallet_type` | Null or one of `0xio`, `metamask`, `rabby`, `walletconnect`, `coinbase`, `rainbow`, `trust`, `phantom`, `okx`, `brave`, `injected` |
| `source` | Optional. `web` (the bridge site) or `extension` (the 0xio Extension); absent means `web`. Self-reported and not checked on chain; the first report of a transfer keeps its source |
**Response:** `{ "status": "ok" }`, or `{ "status": "duplicate" }` when the row is already known. `400` with a short `error` on any rule above; `429` past the rate limit.
* **Limits:** 20 posts a minute per client IP and 300 a minute in total.
* **Dedupe:** an event whose `(event_type, tx_hash)` already has a verified row, or a pending row with the same addresses and amount, is not stored again. At most 3 rows with different payloads may wait under one hash, and only one verified row is allowed per `(event_type, tx_hash)`, so a fake posted first with a real hash cannot block or double the real event.
### Verification
Every row starts unverified and is left out of every read. A background task checks rows newest first:
* **lock:** the Octra contract receipt shows a successful `lock_to_eth` on the bridge vault whose `Locked` event has `octra_addr` as sender, the same amount and `eth_addr` as recipient.
* **claim:** the Ethereum receipt succeeded, was sent to the bridge contract, is 12 blocks deep and holds a `Minted` event for the amount whose recipient (or the transaction's sender) is `eth_addr`.
* **burn:** the same receipt checks and a `BurnToOctra` event for the amount from `eth_addr` to `octra_addr`.
* **any_swap:** Relay reports the request as settled, and one of its Ethereum transactions sends wOCT to `octra_addr` through the bridge for the amount (within 1%).
* A row not on chain yet is checked again after 1, 2, 4 minutes and so on, at most 12 hours apart.
* Rows are never deleted. A row that breaks the rules, is contradicted by the chain twice, is under 7 days old and still not found after 8 checks, or whose hash another verified row already holds, is quarantined and read by no route. A row older than 7 days that is not found after 3 checks stays unverified instead.
### Bridge Stats
```http theme={null}
GET /api/bridge/stats
GET /api/bridge/events?limit=50&offset=0
GET /api/bridge/chart
```
* **`/api/bridge/stats`:** totals such as `total_events`, `total_locks`, `total_burns`, `locks_24h`, `burns_24h`, `volume_locked_oct`, `volume_locked_24h_oct`, `volume_burned_oct`, `volume_burned_24h_oct`, `unique_octra_wallets` and `unique_eth_wallets`, plus `by_source` with the same totals for `web` (the bridge site) and `extension` (the 0xio Extension). Both keys are always present, and wallet counts are per source, so the two can overlap. The top-level `source` field is a fixed `"bridge_frontend_only"`, kept for older readers: it only says these are client-reported transfers rather than full on-chain totals, and does not name a client; use `by_source` for the split. `note` describes what is counted. Cached 60 seconds.
* **`/api/bridge/events`:** `{ events, count }`, newest first. `limit` defaults to 50 (at most 500).
* **`/api/bridge/chart`:** `{ data }`, daily rows with `ts`, `direction`, `event_type`, `amount_oct` and `source`. Cached 120 seconds.
All three read rows found on chain, plus rows recorded before validation existed that were not quarantined. Volumes are in OCT, from `amount_raw`.
***
## Metrics
```http theme={null}
GET /api/metrics
```
Public. How live the OCT price is, for status displays, without price values: price updates in total and in the last minute, ETH/USD ticks, the venues feeding ETH/USD, whether the pool's swaps arrive live, the sources in use, the confidence and how long ago the price last changed.
***
## API Key Management
### List Your Keys
```http theme={null}
GET /api/keys
```
List all API keys associated with your wallet address. **Requires wallet signature for privacy.**
**Query Parameters:**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `owner` | string | Yes | Your wallet address |
| `timestamp` | number | Yes | Unix timestamp |
| `signature` | string | Yes | Base64-encoded signature of `0xio-list-keys:{owner}:{timestamp}` |
| `public_key` | string | Yes | Base64-encoded Ed25519 public key |
**Example:**
```bash theme={null}
curl "https://oracle.0xio.xyz/api/keys?owner=oct...×tamp=170...&signature=...&public_key=..."
```
**Response:**
```json theme={null}
[
{
"id": 42,
"key_masked": "0xio-<12 hex>...",
"label": "Production App",
"created_at": "2025-01-15 10:30:00",
"monthly_quota": 10000,
"usage": 1834,
"scopes": ["read:price", "read:quote"]
}
]
```
### Generate New Key
```http theme={null}
POST /api/keys
```
Generate a new API key. Requires a wallet signature, and the owner must be on the portal whitelist. Keys look like `0xio-<16 hex>-oracle`. An owner can create at most 5 keys an hour (`429` past that).
**Request Body:**
```json theme={null}
{
"owner": "oct...",
"public_key": "base64_encoded_public_key",
"label": "My App Production",
"scopes": ["read:price", "read:quote"],
"signature": "base64_encoded_signature",
"timestamp": 1706270400
}
```
| Field | Type | Required | Description |
| - | - | - | - |
| `owner` | string | Yes | Your wallet address (`oct...`) |
| `public_key` | string | Yes | The Ed25519 public key, Base64 |
| `label` | string | No | Descriptive label, at most 64 printable characters |
| `scopes` | string\[] | No | Limit the key to these [scopes](#scopes); empty or missing means all |
| `signature` | string | Yes | Base64-encoded ed25519 signature |
| `timestamp` | number | Yes | Unix timestamp (must be within 5 minutes) |
**Signature Message Format:**
```
0xio-api-key:{owner}:{timestamp}
```
Example: `0xio-api-key:oct...:1706270400`
**Response:**
```json theme={null}
{
"status": "created",
"id": 42,
"key": "0xio-<16 hex>-oracle"
}
```
> [!WARNING]
>
> Store your API key securely! It will only be shown once.
> [!TIP]
>
> Use the [Developer Portal](https://devs.0xio.xyz) to create keys easily - it handles wallet signing automatically.
### Revoke Key by ID
```http theme={null}
DELETE /api/keys/:id?owner=...&public_key=...&signature=...×tamp=...
```
Revoke one of your keys by the `id` from create or list. Signed by the owner wallet, so a lost or over-quota key can still be revoked.
**Signature Message Format:**
```
0xio-revoke:{owner}:{id}:{timestamp}
```
**Response:**
```json theme={null}
{
"status": "revoked",
"id": 42
}
```
Returns `404` when the id does not exist or belongs to another wallet, `401` on a bad signature and `400` on an expired or malformed one.
### Revoke Key with the Key Itself
```http theme={null}
DELETE /api/keys
```
Legacy revoke with the full key in the header. Kept for compatibility; prefer `DELETE /api/keys/:id`, which also works for a key that is over its quota.
**Headers:**
```http theme={null}
x-api-key: KEY_TO_REVOKE
```
**Response:**
```json theme={null}
{
"status": "deleted"
}
```
### Usage Report
```http theme={null}
GET /api/usage?owner=...&public_key=...&signature=...×tamp=...&days=30&logs=20
```
Your keys' usage over the last `days` (1 to 365, default 30) with the latest `logs` requests (1 to 500, default 20). Signed by the owner wallet over `0xio-usage:{owner}:{timestamp}`.
The key routes (`POST /api/keys`, `GET /api/keys`, `DELETE /api/keys/:id`) and `GET /api/usage` allow 60 calls a minute per client IP.
***
## Error Responses
| HTTP Status | Description |
| - | - |
| 400 | Bad Request: invalid parameters or signature payload |
| 401 | Unauthorized: a write without a valid key, or an invalid signature |
| 403 | Forbidden: address not whitelisted for key creation, a scope the key lacks, or an operator-only route |
| 404 | Not Found: token or resource not found, or the price is still initializing |
| 429 | Too Many Requests: the per-IP limit for calls without a key, a key over its monthly quota, or a route's own rate limit |
| 500 | Internal Server Error |
| 502 | Bad Gateway: upstream price source error |
| 503 | Service Unavailable: oracle data not yet initialized |
Error responses from the routes use the shape `{ "error": "message" }`; a route's own rate limit adds `retry_after` in seconds. A `401`, `403` or `429` from the key check itself (no key over the IP limit, a missing scope, a key over its quota) carries no body.
***
## Code Examples
### JavaScript/TypeScript
```typescript theme={null}
const ORACLE_URL = 'https://oracle.0xio.xyz';
// No key needed for reads. On your own server, pass a key to skip the per-IP limit:
// fetch(url, { headers: { 'x-api-key': process.env.ORACLE_API_KEY } })
async function getQuote(input: string, output: string, amount: number) {
const response = await fetch(
`${ORACLE_URL}/api/quote?inputMint=${input}&outputMint=${output}&amount=${amount}`
);
if (!response.ok) {
throw new Error(`Oracle error: ${response.status}`);
}
return response.json();
}
// Usage
const quote = await getQuote('OCT', 'USDC', 100);
console.log(`Expected output: ${quote.amountOut} USDC`);
console.log(`Price impact: ${(quote.priceImpact * 100).toFixed(2)}%`);
```
### Python
```python theme={null}
import requests
ORACLE_URL = 'https://oracle.0xio.xyz'
def get_price(symbol: str) -> dict:
# No key needed; add headers={'x-api-key': ...} on a server to skip the per-IP limit
response = requests.get(f'{ORACLE_URL}/api/price/{symbol}')
response.raise_for_status()
return response.json()
# Usage
price = get_price('OCT')
print(f"OCT Price: ${price['price']} (source: {price['source']})")
```
### Rust
```rust theme={null}
use serde::Deserialize;
const ORACLE_URL: &str = "https://oracle.0xio.xyz";
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct QuoteResponse {
input_token: String,
output_token: String,
amount_in: f64,
amount_out: f64,
price_impact: f64,
}
async fn get_quote(input: &str, output: &str, amount: f64)
-> Result
{
let client = reqwest::Client::new();
client
.get(format!("{}/api/quote", ORACLE_URL))
.query(&[
("inputMint", input),
("outputMint", output),
("amount", &amount.to_string()),
])
.send()
.await?
.json()
.await
}
```
# PVAC SDK (@0xio/pvac)
Source: https://docs.0xio.xyz/developers/pvac-guide
FHE privacy primitives for building privacy-aware DApps on Octra
## Overview
`@0xio/pvac` (v1.0.2) provides standalone FHE (Fully Homomorphic Encryption) primitives for DApps that need to work with encrypted balances on Octra Network. It runs entirely client-side via WASM: no wallet extension dependency for cryptographic operations.
The primitives are built on the **Ristretto255** group (curve25519-dalek): exponential-ElGamal ciphers, Bulletproofs-style range proofs, and Pedersen commitments.
> [!NOTE]
>
> PVAC's confidentiality rests on the discrete-log hardness assumption (Ristretto255). It is **not** lattice-based and is **not** post-quantum / quantum-safe.
Use `@0xio/pvac` for FHE crypto, and `@0xio/sdk` for wallet signing. They work together:
| Package | Purpose |
| - | - |
| `@0xio/pvac` | Encrypt, decrypt, proofs, cipher math |
| `@0xio/sdk` | Connect wallet, sign transactions, broadcast |
## Installation
```bash theme={null}
npm install @0xio/pvac
```
## Quick Start
```typescript theme={null}
import { PvacContext, initPvac } from '@0xio/pvac';
// 1. Initialize WASM module
const wasm = await initPvac();
// 2. Create context from wallet seed (seed must be exactly 32 bytes)
const seed = new Uint8Array(32); // derive from wallet key
const ctx = await PvacContext.create(seed, wasm);
// For parallel range proofs, opt into the rayon thread pool:
// const ctx = await PvacContext.create(seed, wasm, { preWarm: true, threads: 8 });
// 3. Encrypt an amount
const cipher = ctx.encrypt(100_000_000n); // 100 OCT in microOCT
// 4. Create proofs
const blinding = crypto.getRandomValues(new Uint8Array(32));
const boundProof = ctx.makeBoundProof(cipher, 100_000_000n, blinding);
const commitment = ctx.pedersenCommit(100_000_000n, blinding);
```
## API Reference
### Encryption
| Method | Description |
| - | - |
| `encrypt(value)` | Encrypt an amount (bigint) |
| `encryptSeeded(value, seed)` | Encrypt with deterministic seed |
| `encryptZero()` | Encrypt zero (for account initialization) |
| `decrypt(cipher)` | Decrypt a cipher to bigint |
### Cipher Arithmetic
| Method | Description |
| - | - |
| `ctAdd(a, b)` | Add two ciphers: `enc(a) + enc(b) = enc(a+b)` |
| `ctSub(a, b)` | Subtract: `enc(a) - enc(b) = enc(a-b)` |
### Proofs
| Method | Description | Time |
| - | - | - |
| `makeBoundProof(cipher, value, blinding)` | Prove cipher encrypts specific value | \~100ms |
| `makeRangeProof(cipher, value)` | Prove decrypted value ≥ 0 | 30-60s |
| `makeRangeProofParallel(cipher, value)` | Parallel range proof (rayon): requires `threads` in `create()` | 15-30s |
| `pedersenCommit(value, blinding)` | Create Pedersen commitment | \~1ms |
| `verifyRangeProof(cipher, proof)` | Verify a range proof | \~100ms |
| `verifyBoundProof(cipher, proof, commitment)` | Verify a bound proof | \~50ms |
## Private DEX Example
```typescript theme={null}
import { PvacContext, initPvac } from '@0xio/pvac';
import { ZeroXIOWallet } from '@0xio/sdk';
// Setup
const wasm = await initPvac();
const ctx = await PvacContext.create(seed, wasm);
const wallet = new ZeroXIOWallet({ appName: 'My DEX' });
await wallet.initialize();
await wallet.connect();
// Open private account on pool contract
const pubkey = ctx.serializePubkey();
const zeroCipher = ctx.encryptZero();
const blinding = crypto.getRandomValues(new Uint8Array(32));
const zeroProof = ctx.makeBoundProof(zeroCipher, 0n, blinding);
const rangeProof = ctx.makeRangeProof(zeroCipher, 0n);
await wallet.callContract({
contract: 'octYourPoolAddress...',
method: 'open_private_account',
params: [
btoa(String.fromCharCode(...pubkey)),
btoa(String.fromCharCode(...zeroCipher)),
btoa(String.fromCharCode(...zeroProof)),
btoa(String.fromCharCode(...rangeProof)),
],
amount: 0,
});
```
### Subtracting a Public Amount
To subtract a known amount from an encrypted balance (e.g., after a swap):
```typescript theme={null}
// Subtract 50 OCT from encrypted balance
const amountCipher = ctx.encrypt(50_000_000n);
const newBalance = ctx.ctSub(currentCipher, amountCipher);
// Prove remaining balance ≥ 0
const proof = ctx.makeRangeProof(newBalance, expectedNewValue);
```
This is mathematically identical to `fhe_sub_const`. Both produce `enc(balance - amount)`.
## Network Proof Rules
Each network states which proof rules it accepts. 0xio wallets read them before any private action and again right before signing.
* **Where:** `octra_pvacMigrationStatus` for the address. `proof_math` is `"wide"` or `"prior"` and sets the challenge arithmetic. `proof_key` (true or false) says whether proofs are bound to the registered encryption key.
* **Fallback:** when the answer has no `proof_math`, only the devnet runtime the Octra reference client lists (read from `octra_runtimeVersion`) counts, and it means wide arithmetic without key binding.
* **No rules:** a network that states neither gets no private operation from the wallet. The user sees that private actions are paused, and nothing is sent.
* **Rules changed:** when the rules read right before signing differ from the ones the proofs were built under, nothing is sent and the action is prepared again.
* **Key binding:** on a `proof_key` network every key switch carries `owner_cipher` and `owner_proof`, a proof that the sender owns the key being registered.
* **Migration:** when the network opens its cipher proof route, a balance held under an older key migrates with a `cipher_proof` payload instead of the older migration format.
A proof built under one setting fails under the other. If your dApp builds proofs itself, check the same status first: a proof that does not match the network's rules is refused.
## Platform Support
| Platform | Package | Notes |
| - | - | - |
| Browser / Web DApp | `@0xio/pvac` | Full support via WASM |
| Browser Extension | `@0xio/pvac` | Same WASM, runs in page context |
| React Native | `pvac-rs` FFI | Native ARM binary |
| Desktop (Tauri) | `pvac-rs` crate | Native Rust |
| Node.js | Not supported | No WASM threading |
## Approval Popup
When your DApp calls `wallet.callContract()`, the 0xio extension shows an approval popup:
* **Amount** = native value sent to the contract. `amount` is raw micro-OCT (1 OCT = 1000000) and is passed to the wallet as is; `amountOct` takes OCT and is converted by the SDK. Set `amount: 0` if none.
* **Fee** = OU (operational units / gas). Shown separately below the amount.
If you see a non-zero amount and expect 0, check your `amount` parameter.
# Request lifecycle
Source: https://docs.0xio.xyz/developers/request-lifecycle
Request ids, statuses, confirmed waits and wallet detection in @0xio/sdk 2.12.0.
Since SDK 2.12.0 every call that opens a review carries a request id. Your site can store it, follow the request, find it again after a reload, and wait for the chain without running its own RPC client. Nothing in the lifecycle ever signs or sends a second time.
Wallets that speak wallet protocol 2 keep each request's status for 24 hours. With an older wallet the SDK keeps its own record for the life of the page, and every call still works.
## Detect before Connect
`detectWallet()` (or `wallet.detect()`) tells you what the page can offer before any click. It needs no `initialize()`, answers in about 300 ms and never shows a browser prompt.
```typescript theme={null}
import { ZeroXIOWallet } from '@0xio/sdk';
const wallet = new ZeroXIOWallet({ appName: 'My DApp' });
const found = await wallet.detect();
if (found.next === 'open-in-app') showOpenInAppButton(found.openInAppUrl);
else if (found.next === 'install') showInstallLinks();
else showConnectButton();
```
| Field | Values |
| - | - |
| `injected` | `{ kind, version, protocol }` of the wallet in the page, or `null` |
| `desktop` | `available`, `unknown-until-connect`, `not-running`, `blocked` or `not-allowed` |
| `mobile` | `true` in a phone or tablet browser |
| `openInAppUrl` | A link that opens this page in the 0xio app, set on a phone with nothing injected |
| `next` | `connect`, `install` or `open-in-app` |
* **`unknown-until-connect`** means Chrome would ask for local network access before any page can reach 0xio Desktop. Asking would show that prompt to every visitor, so the SDK waits for a click. Show a Connect button.
* **`blocked`** means the visitor denied local network access for your site. **`not-allowed`** covers `desktop: false`, a page that is not a secure context, another wallet's adapter and phones.
* `isMobileBrowser()` is the check `detect()` uses; iPads that report a Mac count as mobile.
### Which wallet answered
After `initialize()`, `wallet.walletInfo` describes the wallet behind the transport:
```typescript theme={null}
wallet.walletInfo;
// { kind: 'extension', version: '3.0.6', protocol: 2, capabilities: ['request_status', ...] }
```
`kind` is `extension`, `desktop`, `app-expo`, `app-ios` or `app-android`. The 0xio app's browser speaks the extension channel, so `wallet.transport` still reads `extension` there; `walletInfo.kind` tells them apart. The SDK switches features on `capabilities`, never on `version`.
### Opening your site in the 0xio app
On a phone without a wallet, offer a link into the 0xio app:
```typescript theme={null}
wallet.openInAppUrl(); // https://0xio.xyz/open?url=
wallet.openInAppUrl(url, { scheme: true }); // oxio://open?url=, for QR codes inside the app
```
Only https pages without credentials in the URL get a link; anything else returns `null`. The SDK never navigates by itself: put the link on a button. The app shows your site's origin and asks before it opens it, and your site still has to call `connect()`.
## Request ids
The SDK makes a request id for every call that opens a review or signs. Make your own with `newRequestId()` when you want to store it before the call:
```typescript theme={null}
const requestId = wallet.newRequestId();
localStorage.setItem('deposit', requestId);
const result = await wallet.callContract(
{ contract: POOL, method: 'deposit', params: [], amountOct: '10' },
{ requestId, wait: 'confirmed', onStatus: (s) => setLabel(s.status) }
);
```
* A request id is 32 lowercase hex characters. Anything else is refused with `INVALID_PARAMS`.
* Calling again with the same id never opens a second review. It joins the call in flight or returns the stored outcome.
* The same id with another method, other params or another network is refused with `REQUEST_ID_REUSED`.
* A failed, rejected or expired request never runs again under its id. A new id is a new review the user sees.
Every review call takes `{ requestId, timeoutMs, onStatus }`. Writes also take `wait`.
## Following a request
| Method | What it does |
| - | - |
| `requestStatus(id)` | The request's current status. Works after a reload with a protocol 2 wallet |
| `waitForRequest(id, { until })` | Resolves when the request is final (or on chain with `until: 'submitted'`), whatever the outcome |
| `cancelRequest(id)` | Closes your own review that was not approved yet; an approved request is unchanged |
| `pendingRequest()` | The review of this page that blocks a new one, or `null` |
| `wallet.on('requestUpdate', fn)` | Every status change of a request this page sent |
```typescript theme={null}
const stored = localStorage.getItem('deposit');
if (stored) {
const status = await wallet.requestStatus(stored);
if (status.status === 'submitted') await wallet.waitForRequest(stored);
}
```
## Statuses
| Status | Meaning | Next |
| - | - | - |
| `pending` | The wallet has the request; it waits behind another review or prepares one | `locked`, `review_open`, `submitted`, `completed`, `failed`, `rejected` |
| `locked` | Waiting for the user to unlock | `review_open`, `rejected`, `expired`, `failed` |
| `review_open` | The review is on screen | `signing`, `completed`, `rejected`, `expired`, `failed` |
| `signing` | Approved; signing, proofs and submit | `submitted`, `failed` |
| `submitted` | The node accepted it; `hash` is the node's | `confirmed`, `failed` |
| `confirmed` | The chain executed it | final |
| `completed` | Finished without a transaction (connect, network switch, message signature) | final |
| `failed` | `error.stage` says where: `review`, `submit` or `execution` | final |
| `rejected` | The user declined, closed the review, or the page closed first | final |
| `expired` | The wallet's 5 minute review window ran out | final |
| `unknown` | Nobody knows this id (never seen, another site's id, or older than 24 hours) | |
* A final status never changes.
* A site that is not connected never learns the wallet is locked: its reads answer the same, its review requests get `NOT_CONNECTED` with no prompt, and its connect reads `review_open` with no update at unlock. It sees no address or network.
* Connect from a connected site and a switch to the current network go `pending` to `completed` with no review.
* A page that closes before the user answers closes its review. A request the user already approved finishes, and the reloaded page can read the outcome.
## Waiting for the chain
`wait: 'submitted'` (the default) resolves when the node accepted the transaction, as before. `wait: 'confirmed'` resolves when the chain executed it:
```typescript theme={null}
const result = await wallet.callContract(call, { wait: 'confirmed' });
result.requestStatus; // 'confirmed'
result.epoch;
result.receipt?.events; // [{ event, values }]
```
* Writes resolve with `requestId` and `requestStatus` on top of their usual result. The wallet's own `status` field is left as it sent it.
* Contract calls and deploys carry `receipt`: `success`, `events` and `error`. Octra's receipt has no return value, so a contract that needs to report a result should emit it as an event.
* `receipt` is `null` with `receiptUnavailable: 'refused'` when the wallet's node refuses receipts (the public scanner does), or `'error'` when the read failed.
* A chain rejection or drop rejects with `EXECUTION_FAILED`, with `details.hash` and `details.reason`.
* Protocol 2 wallets watch the chain for you. With an older wallet the SDK reads `octra_transaction` through the wallet's read relay, every 4 seconds for a minute and then every 10 seconds, for at most 10 minutes. If the wallet refuses those reads, the call rejects with `UNSUPPORTED_METHOD` and the hash.
## Timeouts
A timeout ends only the SDK's wait. It never cancels the review and never sends anything again.
* A review call whose wait runs out rejects with `STATUS_UNKNOWN`, with `details.requestId` and `details.lastStatus`. The user may still approve it. Ask `requestStatus()` or `waitForRequest()`; never treat it as a failure to retry.
* With a protocol 2 wallet the wait restarts on every status change, so a user who reads the review for several minutes does not time your site out.
* `NETWORK_ERROR` means the link to the wallet failed. On a review call it carries the request id and `details.status: 'unknown'`, because the wallet may still finish the request.
* `timeoutMs` on a call sets its wait. The default is 3 minutes, 10 minutes for private transfers and on 0xio Desktop.
> [!WARNING]
>
> `STATUS_UNKNOWN` never means "send again". A second call with a new request id is a second transaction the user has to approve.
## Errors
| Code | When |
| - | - |
| `STATUS_UNKNOWN` | The SDK stopped waiting; the wallet may still finish the request |
| `REQUEST_ID_REUSED` | The request id belongs to another request of your site |
| `EXECUTION_FAILED` | `wait: 'confirmed'` and the chain rejected or dropped the transaction |
| `PVAC_KEY_MISMATCH` | `registerPrivateViewKey`: another private key is registered for the account |
| `REGISTRATION_PENDING` | `registerPrivateViewKey`: this wallet's registration is not confirmed yet |
| `RATE_LIMIT_EXCEEDED` | See `details.reason` below |
Every error of a review call carries `error.requestId`. `RATE_LIMIT_EXCEEDED` names its cause in `details.reason`:
| `details.reason` | Cause |
| - | - |
| `review_pending` | Another review of this page is open; `details.requestId` and `details.status` name it. `switchNetwork` is refused the same way |
| `requests_per_second` | More than 20 requests a second from the page |
| `open_requests` | Your site already has the most requests the wallet holds open (8, or 3 on 0xio Desktop) |
## Pre-signed transactions
`submitTransaction(signedTx)` broadcasts a transaction from `signTransaction()` without a second review: the `signTransaction` review already showed exactly what was signed. It goes `pending` to `submitted`. A transaction not signed by the connected wallet is refused with `INVALID_PARAMS`.
## Registering a private receive key
`registerPrivateViewKey({ address })` makes the connected wallet able to receive private transfers (its view key on chain) and claim them (a confirmed private key).
| Account | What happens | Result |
| - | - | - |
| Already registered | Nothing is signed, no review; `pending` then `completed` | `{ status: 'registered', view_pubkey, pvac: true }` |
| Not registered | One review with the live fee, then a key switch: `submitted` with a hash, then `confirmed` | `{ hash, requestStatus }` |
| Another address | Refused before any review | `INVALID_PARAMS` |
| Another private key registered for the account | Refused before any review, because a key switch could strand its encrypted balance | `PVAC_KEY_MISMATCH` |
| A registration from this wallet not yet confirmed | Refused before any review | `REGISTRATION_PENDING` |
| Balance below the fee | Refused before any review | `INSUFFICIENT_BALANCE` |
There is no free registration. The node derives the view key from the public key a transaction carries, so a fresh account that never sent anything needs that one transaction.
## Which wallets speak protocol 2
| Wallet | Protocol 2 from |
| - | - |
| 0xio extension | 3.0.6 |
| 0xio Desktop | 0.6.2 |
| 0xio app | 1.3.5 |
Older wallets keep working through the SDK's fallbacks: `walletInfo.protocol` reads `1`, `requestStatus()` answers from the SDK's own record until a reload, and `wait: 'confirmed'` reads the chain through the wallet.
Building a wallet? See [Wallet protocol 2](/developers/wallet-protocol).
# SDK Integration Guide
Source: https://docs.0xio.xyz/developers/sdk-guide
Integrate the 0xio Wallet SDK to connect dApps to the browser extension, desktop and mobile apps with FHE privacy support, plus the extension's EVM provider.
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](#evm-provider). Building with an AI assistant: [Build with AI](/developers/build-with-ai).
## Installation
Install via npm or yarn:
```bash npm theme={null}
npm install @0xio/sdk
```
```bash yarn theme={null}
yarn add @0xio/sdk
```
```bash pnpm theme={null}
pnpm add @0xio/sdk
```
**Package Details:**
* **Version:** 2.12.2
* **Names (`lookupAddress`, `resolveName`):** see [Names](/developers/names); needs the `contract_views` permission
* **Request lifecycle (request ids, status, confirmed waits):** see [Request lifecycle](/developers/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:
```bash theme={null}
npm install vite-plugin-node-polyfills
```
2. Update `vite.config.ts`:
```typescript theme={null}
import { defineConfig } from 'vite';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
export default defineConfig({
plugins: [
nodePolyfills({
include: ['buffer', 'process', 'util', 'stream', 'events'],
globals: {
Buffer: true,
global: true,
process: true,
},
}),
],
});
```
### Webpack 5 Configuration
If using Create React App (CRA) v5 or custom Webpack, install `react-app-rewired` or customize your config:
```javascript theme={null}
// webpack.config.js
const webpack = require('webpack');
module.exports = {
// ...
resolve: {
fallback: {
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"buffer": require.resolve("buffer/"),
"process": require.resolve("process/browser"),
}
},
plugins: [
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser',
}),
]
};
```
## Quick Start
The fastest way to get started: `createZeroXIOWallet` handles initialization and optional auto-connect:
```typescript theme={null}
import { createZeroXIOWallet } from '@0xio/sdk';
// One-liner: create, initialize, and auto-connect
const wallet = await createZeroXIOWallet({
appName: 'My Privacy dApp',
appDescription: 'An encrypted DeFi protocol on Octra',
autoConnect: true, // Auto-reconnects if user previously approved
});
const balance = await wallet.getBalance(true);
console.log('Public:', balance.public, 'OCT');
console.log('Private:', balance.private, 'OCT');
```
Or with manual control:
```typescript theme={null}
import { ZeroXIOWallet } from '@0xio/sdk';
const wallet = new ZeroXIOWallet({
appName: 'My Privacy dApp',
appDescription: 'An encrypted DeFi protocol on Octra',
});
// Initialize: detects 0xio extension or any RFC-O-1 wallet
const isAvailable = await wallet.initialize();
if (!isAvailable) {
console.log("No compatible wallet detected.");
return;
}
// Connect: the user approves in the wallet popup
const result = await wallet.connect({
permissions: ['accounts', 'public_transactions']
});
console.log('Connected to:', result.address);
console.log('Network:', result.networkInfo.name);
```
## 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.
| Method | Raw field (sent unchanged) | OCT field (converted by the SDK) |
| - | - | - |
| `sendTransaction`, `signTransaction` | `amount` | `amountOct` |
| `callContract` | `amount` | `amountOct` |
| `sendPrivateTransfer` | `amountRaw` | `amount` |
| `getBalance` | | returns OCT |
| `getTransactionHistory` | rows carry raw micro-OCT, as the node returns them | |
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.
```typescript theme={null}
await wallet.sendTransaction({ to, amountOct: '10.5' }); // 10.5 OCT
await wallet.sendTransaction({ to, amount: '10500000' }); // the same, in raw units
```
## Permissions
Ask for what the dapp uses. The wallet enforces these scope names; the private ones show a warning in the connection dialog.
| Scope | Grants |
| - | - |
| `accounts` | address, balance, public key |
| `public_transactions` | sends and message signing |
| `contract_calls` | state-changing contract calls |
| `contract_views` | read-only contract calls |
| `private_balance_read` | private balance, decrypting values, pending transfers |
| `private_proofs` | encrypting values, zero and range proofs |
| `private_transfers` | sending private transfers |
| `private_claims` | claiming private transfers |
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:
```typescript theme={null}
await wallet.request('call_contract', {
contract: 'oct26LiaGUz78Jnby2TWEHcmboKHyupnu1NX1G3Evz28YHm',
method: 'set_limit',
message: '[18446744073709551615, true]', // signed exactly as written
});
```
* **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](/wallet/sending#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](/wallet/sending#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.
| Platform | Transport | Description |
| - | - | - |
| **0xio Wallet Extension** | `window.postMessage` | Standard browser extension communication |
| **RFC-O-1 Third-Party Wallet** | `window.octra.request()` | Any wallet implementing the RFC-O-1 standard |
| **0xio Desktop** | `window.parent.postMessage` (iframe) | DApp loaded in Desktop's built-in BrowserScreen |
| **0xio App** | `window.parent.postMessage` (WebView) | DApp loaded in App's built-in browser |
| **0xio Desktop, no extension** | WebSocket to `ws://127.0.0.1:19345` | Used only when no extension answers; approvals in the desktop menu bar (SDK 2.11.0) |
### 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.
> [!TIP]
>
> 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'`).
```typescript theme={null}
const wallet = new ZeroXIOWallet({ appName: 'My DApp' });
await wallet.initialize();
console.log(wallet.transport); // 'extension', 'desktop', or null until connect()
console.log(wallet.desktopAvailableOnConnect); // true: connect() will try 0xio Desktop
// Opt out for this site:
new ZeroXIOWallet({ appName: 'My DApp', desktop: false });
```
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:
```typescript theme={null}
const wallet = new ZeroXIOWallet({
appName: 'My DApp',
trustedParentOrigins: ['https://desktop.0xio.xyz'],
});
```
### 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:
| Request | What the app shows | How it is confirmed |
| - | - | - |
| `connect()` | Your site's listed name and host (never the page title), the wallet with **Change**, what the site can see (address and balance), what it can ask (signatures the user reviews), any private permissions you asked for, and that it can never move funds on its own | **Slide to connect** |
| `signMessage()` | The whole message in a scrolling box and "Signing moves no funds and costs no fee." | **Slide to sign** |
| `sendTransaction()`, `callContract()`, sequences | A receipt: the action, amount with USD, recipient, numbered steps for a sequence, fee, network and wallet, plus the memo | **Slide to approve** |
| `signTransaction()` | Titled "Sign a transaction", with a warning that the site can send what is signed. It is never broadcast by the wallet | **Slide to approve** |
| `submitTransaction()` | No second review: the `signTransaction()` review already showed what was signed. Refused unless signed by the connected wallet | none |
| `switchNetwork()` | A confirm sheet; only mainnet and devnet, and only for a connected site | Switch network or Cancel |
* **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:
| Priority | Adapter | Detects |
| - | - | - |
| 1 | `ZeroXIOAdapter` | `window.wallet0xio` or `window.ZeroXIOWallet` (0xio extension) |
| 2 | `OctraProviderAdapter` | `window.octra.isOctra` (any RFC-O-1 wallet) |
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:
```typescript theme={null}
import { ZeroXIOWallet } from '@0xio/sdk';
const wallet = new ZeroXIOWallet({ appName: 'My DApp' });
await wallet.initialize(); // Finds window.octra from any compliant wallet
await wallet.connect(); // Uses RFC-O-1 request() under the hood
```
### Direct Provider Access
For advanced use cases, you can use the provider directly:
```typescript theme={null}
import { OctraProviderAdapter } from '@0xio/sdk';
// Check if any RFC-O-1 wallet is available
if (OctraProviderAdapter.detect()) {
const accounts = await window.octra.request({ method: 'octra_requestAccounts' });
const balance = await window.octra.request({ method: 'octra_getEncryptedBalance' });
}
```
### RFC-O-1 Error Codes
| Code | Meaning | SDK Error Code |
| - | - | - |
| 4001 | User rejected | `USER_REJECTED` |
| 4100 | Unauthorized | `PERMISSION_DENIED`, or `WATCH_ONLY` when the error `data.code` is `WATCH_ONLY` |
| 4200 | Unsupported method | `UNKNOWN_ERROR` |
| 4900 | Disconnected | `CONNECTION_REFUSED` |
| 4901 | Network unavailable | `NETWORK_ERROR` |
***
## 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](/wallet/connecting-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.
```typescript theme={null}
window.addEventListener('eip6963:announceProvider', (event: any) => {
if (event.detail.info.rdns === 'xyz.0xio.wallet') {
const provider = event.detail.provider;
// use provider.request({ method, params })
}
});
window.dispatchEvent(new Event('eip6963:requestProvider'));
```
### Methods
| Kind | Methods |
| - | - |
| Open a review | `eth_requestAccounts`, `wallet_requestPermissions`, `eth_sendTransaction`, `personal_sign`, `eth_signTypedData_v4`, `wallet_switchEthereumChain`, `wallet_addEthereumChain`, `wallet_watchAsset` |
| Answered by the wallet | `eth_chainId`, `net_version`, `eth_accounts`, `wallet_getPermissions`, `wallet_getCapabilities`, `web3_sha3`, `net_listening` |
| Forwarded to the site's network | `eth_call`, `eth_getBalance`, `eth_estimateGas`, receipts, logs, blocks, filters, `eth_syncing` and the other read methods |
| Refused (`4200`) | `eth_sign`, `eth_signTransaction`, `eth_sendRawTransaction`, older typed-data versions, `wallet_sendCalls` and any other unknown method |
### 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".
```typescript theme={null}
const requestId = wallet.newRequestId();
const result = await wallet.sendTransaction(
{ to: 'oct1qv...', amountOct: '1' },
{ requestId, wait: 'confirmed', onStatus: (s) => console.log(s.status) }
);
console.log(result.requestStatus, result.hash);
```
Detection, statuses, receipts, reloads and error reasons: [Request lifecycle](/developers/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.
```typescript theme={null}
const name = await wallet.lookupAddress();
button.textContent = name ?? `${address.slice(0, 6)}...${address.slice(-4)}`;
```
Sending to a name, the name rules and where the name contract comes from: [Names](/developers/names).
## Core API
### Connection Methods
#### `initialize()`
Initializes the SDK and checks for extension presence.
```typescript theme={null}
const success = await wallet.initialize();
if (success) {
console.log('SDK ready!');
}
```
* **Returns:** `Promise`
* **Throws:** `ZeroXIOWalletError` with code `EXTENSION_NOT_FOUND` if 0xio is not installed
#### `isReady()`
Check if SDK is initialized and extension is available.
```typescript theme={null}
if (wallet.isReady()) {
// Safe to make requests
}
```
* **Returns:** `boolean`
#### `connect(options)`
Requests connection to the extension. User must approve in popup.
```typescript theme={null}
const result = await wallet.connect({
permissions: [
'accounts', // address, balance, public key
'public_transactions', // sends and message signing
'private_balance_read' // private balance (optional, shows a warning)
]
});
console.log('Connected:', result.address);
console.log('Balance:', result.balance.total, 'OCT');
console.log('Network:', result.networkInfo.name);
```
* **Returns:** `Promise` with address, balance, networkInfo, and permissions
* **Throws:** `ZeroXIOWalletError` with code `CONNECTION_REFUSED` if user declines
#### `disconnect()`
Disconnects from the extension.
```typescript theme={null}
await wallet.disconnect();
```
* **Returns:** `Promise`
#### `isConnected()`
Checks current connection status.
```typescript theme={null}
const connected = wallet.isConnected();
```
* **Returns:** `boolean`
#### `getConnectionInfo()`
Gets the cached connection info.
```typescript theme={null}
const info = wallet.getConnectionInfo();
console.log(info.address);
console.log(info.balance);
console.log(info.connectedAt);
```
* **Returns:** `ConnectionInfo`
#### `getConnectionStatus()`
Checks connection status with extension (async).
```typescript theme={null}
const status = await wallet.getConnectionStatus();
if (status.isConnected) {
console.log('Already connected to:', status.address);
}
```
* **Returns:** `Promise`
#### `getAddress()`
Gets the currently connected address.
```typescript theme={null}
const address = wallet.getAddress();
// Returns: "oct1abc123..." or null if not connected
```
* **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`.
```typescript theme={null}
const publicKey = await wallet.getPublicKey();
```
* **Returns:** `Promise`
* **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.
```typescript theme={null}
const canSign = wallet.canSign(); // true, false or undefined
```
* **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](#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.
```typescript theme={null}
// Detect wrong network
const networkId = wallet.getNetworkId(); // 'mainnet' or 'devnet'
if (networkId !== 'devnet') {
try {
const result = await wallet.switchNetwork('devnet');
console.log('Switched:', result.switched); // true once the user confirmed
} catch (e) {
// USER_REJECTED: the user kept the current network
}
}
```
* **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.
```typescript theme={null}
const networkId = wallet.getNetworkId();
// Returns: 'mainnet', 'devnet', or null
```
* **Returns:** `string | null`
### Balance Methods
#### `getBalance(forceRefresh?)`
Fetches current balances from blockchain or cache.
```typescript theme={null}
const balance = await wallet.getBalance(true); // Force refresh
console.log(balance.public); // 1250.5 (number)
console.log(balance.private); // 3420.25 (number)
console.log(balance.total); // 4670.75 (number)
console.log(balance.currency); // "OCT"
```
* **Parameters:**
* `forceRefresh`: If true, queries blockchain; if false, uses cached values
* **Returns:** `Promise` with public, private, total (numbers), and currency
```typescript theme={null}
interface Balance {
public: number; // Public balance in OCT
private: number; // Private (FHE) balance in OCT
total: number; // Combined balance
currency: 'OCT'; // Currency symbol
}
```
### Transaction Methods
#### `sendTransaction(txData)`
Executes a standard public transfer.
```typescript theme={null}
const result = await wallet.sendTransaction({
to: 'oct1qv...',
amountOct: '10.5', // 10.5 OCT, converted exactly by the SDK
message: 'Payment for services' // Optional
});
console.log('Transaction Hash:', result.hash);
console.log('Accepted:', result.accepted);
```
* **Parameters:**
```typescript theme={null}
interface TransactionData {
to: string; // Recipient address
amount?: string | number; // Raw micro-OCT, sent to the wallet as is
amountOct?: string | number; // Amount in OCT, converted by the SDK. One of the two.
message?: string; // Optional transaction memo
feeLevel?: 1 | 3; // 1 = standard, 3 = priority
isPrivate?: boolean; // Use private transfer
}
```
* **Returns:** `Promise` 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`.
```typescript theme={null}
const { signedTx } = await wallet.signTransaction({ to: 'oct1qv...', amountOct: '1' });
const result = await wallet.submitTransaction(signedTx);
```
#### `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.
```typescript theme={null}
const result = await wallet.sendPrivateTransfer({
to: 'oct1x...',
amount: 20 // OCT
});
console.log('TX Hash:', result.hash);
```
> [!NOTE]
>
> **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:**
```typescript theme={null}
interface PrivateTransferData {
to: string; // Recipient address
amount: number; // Amount in OCT
amountRaw?: string; // Exact amount in raw micro-OCT; when set, this is what the wallet uses
message?: string; // Not carried by the wallet; private transfers have no memo on-chain
}
```
* **Returns:** `Promise` with `hash` and `accepted`
* **Note:** The amount is encrypted on your device before transmission
#### `getTransactionHistory(page, limit)`
Gets transaction history.
```typescript theme={null}
const history = await wallet.getTransactionHistory(1, 20);
history.transactions.forEach(tx => {
console.log(`${tx.from} to ${tx.to}: ${Number(tx.amount) / 1_000_000} OCT`); // rows carry raw micro-OCT
});
console.log('Total:', history.totalCount);
console.log('Has more:', history.hasMore);
```
* **Parameters:** `page` (default: 1), `limit` (default: 20)
* **Returns:** `Promise`
### 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.
```typescript theme={null}
// Simple message signing
const signature = await wallet.signMessage('Hello, 0xio!');
console.log('Signature:', signature);
// Authentication challenge with timestamp
const timestamp = Date.now();
const challenge = `Sign in to MyDApp\nTimestamp: ${timestamp}\nAddress: ${wallet.getAddress()}`;
const authSignature = await wallet.signMessage(challenge);
// API key creation (for 0xio Oracle/Indexer)
const owner = wallet.getAddress();
const ts = Math.floor(Date.now() / 1000);
const apiKeyMessage = `0xio-api-key:${owner}:${ts}`;
const signature = await wallet.signMessage(apiKeyMessage);
```
* **Parameters:** `message: string` - The text message to sign (must be non-empty)
* **Returns:** `Promise` - 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
> [!NOTE]
>
> **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
> [!WARNING]
>
> 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.
```typescript theme={null}
const signature = await wallet.signAuthMessage('MyDApp', 'abc123');
// Signs: "0xio auth\nService: MyDApp\nNonce: abc123\nOrigin: https://mydapp.com"
```
* **Parameters:** `service: string` (service name), `nonce: string` (unique challenge)
* **Returns:** `Promise` - 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+.
```typescript theme={null}
import { verifyMessage } from '@0xio/sdk';
const publicKey = await wallet.getPublicKey(); // base64
const valid = await verifyMessage('Hello, 0xio!', signature, publicKey);
```
* **Parameters:** `message: string`, `signature: string` (base64), `publicKey: string` (base64)
* **Returns:** `Promise`
#### `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).
```typescript theme={null}
import { getSignedMessageBytes } from '@0xio/sdk';
import nacl from 'tweetnacl';
const ok = nacl.sign.detached.verify(
getSignedMessageBytes(message),
base64ToBytes(signature),
base64ToBytes(publicKey),
);
```
* **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.
```typescript theme={null}
import { ContractCallData } from '@0xio/sdk';
const result = await wallet.callContract({
contract: 'oct26LiaGUz78Jnby2TWEHcmboKHyupnu1NX1G3Evz28YHm',
method: 'swap',
params: [100000, true, 90000], // Flat primitives, not array-wrapped
amountOct: '0', // Value to attach in OCT (optional, default none); or amount in raw micro-OCT
ou: '10000', // Operational units (optional, default '10000')
});
console.log('TX Hash:', result.txHash);
console.log('Success:', result.success);
```
* **Parameters:**
```typescript theme={null}
type ContractParams = ReadonlyArray;
interface ContractCallData {
contract: string; // Contract address (oct-prefixed, 47 chars)
method: string; // AML method name
params: ContractParams; // Method arguments, flat primitives
amount?: string | number; // Value to attach in raw micro-OCT, sent as is (default '0')
amountOct?: string | number; // Value to attach in OCT, converted by the SDK. One of the two.
ou?: string | number; // Operational units (default '10000')
}
```
* **Returns:** `Promise` with `txHash`, `success`, `finality`
> [!WARNING]
>
> 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](#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`.
```typescript theme={null}
// Query contract state
const price = await wallet.contractCallView({
contract: 'oct26LiaGUz78Jnby2TWEHcmboKHyupnu1NX1G3Evz28YHm',
method: 'get_active_price',
params: [],
});
console.log('Active price:', price);
// Query with parameters
const reserves = await wallet.contractCallView({
contract: 'oct26LiaGUz78Jnby2TWEHcmboKHyupnu1NX1G3Evz28YHm',
method: 'get_bin_reserves',
params: [8388608],
caller: 'oct1youraddress...', // Optional caller context
});
console.log('Reserves:', reserves);
```
* **Parameters:**
```typescript theme={null}
interface ContractViewCallData {
contract: string; // Contract address (oct-prefixed, 47 chars)
method: string; // View method name
params: ContractParams; // Method arguments, flat primitives
caller?: string; // Optional caller address
}
```
* **Returns:** `Promise`, the raw result from the contract method
#### `getContractStorage(contract, key)`
Read a single key from contract storage. No wallet unlock or approval needed.
```typescript theme={null}
const totalSupply = await wallet.getContractStorage(
'oct7qrpW3T8mXYbZfAZrkLQKGAtD6ZswCf9Px5jPXnfmtiK',
'total_supply'
);
console.log('Total supply:', totalSupply);
// Returns null if key doesn't exist
const missing = await wallet.getContractStorage(contractAddr, 'nonexistent');
console.log(missing); // null
```
* **Parameters:** `contract: string` (contract address), `key: string` (storage key)
* **Returns:** `Promise`, 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](#what-the-review-shows)) refuses the whole sequence with `INVALID_PARAMS`, naming the step.
```typescript theme={null}
const result = await wallet.sendContractTransactionSequence({
transactions: [
{ contract: 'oct26Lia...', method: 'approve', params: [poolAddress, 1000000] },
{ contract: 'oct26Lia...', method: 'swap', params: [1000000, true, 900000] },
],
});
```
* **Returns:** `Promise` 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.
```typescript theme={null}
const { txHash, contractAddress } = await wallet.deployContract({
bytecode: compiled.deploy_payload, // base64 from octra_compileAml with program=true
params: ['0xio Token', 'OXT', 1000000], // optional constructor params (JSON)
});
```
* **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`.
```typescript theme={null}
const account = await wallet.rpcCall('octra_balance', [wallet.getAddress()]);
```
### 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.
```typescript theme={null}
const result = await wallet.encryptBalance(5.0);
console.log('TX Hash:', result.txHash);
console.log('Success:', result.success);
```
* **Parameters:** `amount: number | string` (OCT amount to shield)
* **Returns:** `Promise` with `txHash`, `success`, `finality`
> [!NOTE]
>
> 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).
```typescript theme={null}
const result = await wallet.decryptBalance(10.0);
console.log('TX Hash:', result.txHash);
console.log('Success:', result.success);
```
* **Parameters:** `amount: number | string` (OCT amount to unshield)
* **Returns:** `Promise` with `txHash`, `success`, `finality`
> [!NOTE]
>
> Approvals wait up to 3 minutes; `sendPrivateTransfer` waits up to 10 minutes because proof generation follows the approval.
#### `getPrivateBalanceInfo()`
Gets detailed private balance information.
```typescript theme={null}
const info = await wallet.getPrivateBalanceInfo();
console.log('Has private balance:', info.hasPrivateBalance);
console.log('Can decrypt:', info.canDecrypt);
```
* **Returns:** `Promise`
#### `getPendingPrivateTransfers()`
Gets pending incoming private transfers.
```typescript theme={null}
const pending = await wallet.getPendingPrivateTransfers();
pending.forEach(transfer => {
console.log(`Output ${transfer.id}: ${Number(transfer.amount) / 1_000_000} OCT`);
});
```
* **Returns:** `Promise`. 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.
```typescript theme={null}
const result = await wallet.claimPrivateTransfer('transfer_123');
console.log('Claimed! TX:', result.hash);
```
* **Parameters:** `transferId: string`
* **Returns:** `Promise`
* **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.
| Method | Scope | Returns |
| - | - | - |
| `getPrivateCapabilities()` | none | what the wallet supports, to render the right UI or fail closed |
| `encryptValue({ value_raw })` | `private_proofs` | `{ cipher, commitment, encoding }` |
| `decryptValue({ cipher })` | `private_balance_read` | `{ value_raw, display, decimals }` |
| `makeZeroProof({ cipher, value_raw })` | `private_proofs` | `{ proof, commitment, blinding, encoding }` |
| `makeRangeProof({ cipher, value_raw })` | `private_proofs` | `{ proof, encoding }` |
| `getPrivateBalance()` | `private_balance_read` | `{ address, private_oct_raw, private_oct, tokens }` |
| `registerPrivateViewKey({ address })` | `private_transfers` | `{ status: 'registered', view_pubkey, pvac: true }` at once when registered; otherwise one review with the fee and a key switch transaction. See [the contract](/developers/request-lifecycle#registering-a-private-receive-key) |
Proofs follow the proof rules the network states (see [Network Proof Rules](/developers/pvac-guide#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.
```typescript theme={null}
const caps = await wallet.getPrivateCapabilities();
if (caps.supports.encrypt_value) {
const { cipher } = await wallet.encryptValue({ value_raw: '1000000' }); // 1 OCT
const { proof, commitment, blinding } = await wallet.makeZeroProof({ cipher, value_raw: '1000000' });
}
```
### Network Methods
#### `getNetworkInfo()`
Gets current network information.
```typescript theme={null}
const network = await wallet.getNetworkInfo();
console.log('Network:', network.name);
console.log('RPC:', network.rpcUrl);
console.log('Explorer:', network.explorerUrl);
console.log('Is Testnet:', network.isTestnet);
```
* **Returns:** `Promise`
## 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.
```typescript theme={null}
wallet.on('accountChanged', (event) => {
console.log('New Address:', event.data.newAddress);
console.log('Previous Address:', event.previousAddress);
console.log('Balance:', event.data.balance);
// Re-fetch user data, update UI
updateUserInterface(event.data.newAddress);
});
```
### Network Changed
User switched networks (mainnet/testnet).
```typescript theme={null}
wallet.on('networkChanged', (event) => {
console.log('Previous:', event.previousNetwork?.name);
console.log('New:', event.data.newNetwork.name);
// Update RPC endpoint, refresh data
reconnectToNetwork(event.data.newNetwork);
});
```
### Balance Changed
Balance updated after transaction confirmation.
```typescript theme={null}
wallet.on('balanceChanged', (event) => {
console.log('Address:', event.data.address);
console.log('Previous:', event.data.previousBalance?.total);
console.log('New:', event.data.newBalance.total);
// Update UI with new amounts
updateBalanceDisplay(event.data.newBalance);
});
```
### Connect
Wallet connected successfully.
```typescript theme={null}
wallet.on('connect', (event) => {
console.log('Connected to:', event.data.address);
console.log('Balance:', event.data.balance.total);
console.log('Permissions:', event.data.permissions);
});
```
### Disconnect
User disconnected from dApp or locked wallet.
```typescript theme={null}
wallet.on('disconnect', (event) => {
console.log('Disconnect Reason:', event.data.reason);
// Possible reasons: 'user_action', 'extension_locked', 'network_error'
// Also fires when the wallet ends the session for your site (2.11.0).
// Clear user session, show connect button
clearUserSession();
showConnectModal();
});
```
### Request Update (2.12.0)
Every status change of a request this page sent. Same meaning in every wallet.
```typescript theme={null}
wallet.on('requestUpdate', (event) => {
console.log(event.data.requestId, event.data.status);
});
```
### Connect Progress (2.12.0)
The browser's local network prompt is open while `connect()` tries 0xio Desktop.
```typescript theme={null}
wallet.on('connectProgress', (event) => {
if (event.data.phase === 'local-network-prompt') showPromptHint();
});
```
### 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'`.
```typescript theme={null}
wallet.on('transactionConfirmed', (event) => {
console.log('TX Hash:', event.txHash);
console.log('Confirmations:', event.confirmations);
});
```
### Transaction Failed
The wallet could not broadcast a queued transaction.
```typescript theme={null}
wallet.on('transactionFailed', (event) => {
console.log('TX ID:', event.data.txId, 'reason:', event.data.error);
});
```
### Extension Lock Events
```typescript theme={null}
wallet.on('extensionLocked', () => {
console.log('Wallet locked - user needs to unlock');
});
wallet.on('extensionUnlocked', () => {
console.log('Wallet unlocked');
});
```
### Removing Listeners
```typescript theme={null}
// Remove specific listener
wallet.off('accountChanged', handlerFunction);
// Remove all listeners for an event
wallet.removeAllListeners('balanceChanged');
// Remove all listeners
wallet.removeAllListeners();
```
### Cleanup
Clean up SDK resources when done.
```typescript theme={null}
wallet.cleanup();
```
## Error Handling
The SDK provides typed error codes for graceful error handling.
```typescript theme={null}
import {
ZeroXIOWalletError,
ErrorCode
} from '@0xio/sdk';
try {
await wallet.sendTransaction({
to: 'oct1...',
amountOct: '100'
});
} catch (error) {
if (error instanceof ZeroXIOWalletError) {
switch (error.code) {
case ErrorCode.NOT_CONNECTED:
console.log('Connect first');
break;
case ErrorCode.USER_REJECTED:
console.log('User cancelled the transaction');
showNotification('Transaction cancelled');
break;
case ErrorCode.INSUFFICIENT_BALANCE:
console.log('Not enough balance');
showNotification('Insufficient funds');
break;
case ErrorCode.EXTENSION_NOT_FOUND:
console.log('0xio Extension not installed');
showInstallPrompt();
break;
case ErrorCode.STATUS_UNKNOWN:
// The wallet may still finish it: follow it, never send it again
await wallet.waitForRequest(error.requestId!);
break;
case ErrorCode.NETWORK_ERROR:
// A review call carries its request id: the wallet may still finish it
if (error.requestId) await wallet.waitForRequest(error.requestId);
else showRetryOption();
break;
case ErrorCode.INVALID_ADDRESS:
console.log('Invalid recipient address');
showError('Please check the address');
break;
case ErrorCode.WALLET_LOCKED:
console.log('Wallet is locked');
showError('Please unlock your wallet');
break;
case ErrorCode.PERMISSION_DENIED:
console.log('Permission not granted');
break;
case ErrorCode.TRANSACTION_FAILED:
console.log('Transaction failed:', error.message);
break;
case ErrorCode.SIGNATURE_FAILED:
console.log('Signing failed:', error.message);
showError('Failed to sign message');
break;
default:
console.error('Unexpected error:', error.message);
}
// Access error details
if (error.details) {
console.log('Details:', error.details);
}
} else {
console.error('Unknown error:', error);
}
}
```
### 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`.
```typescript theme={null}
import { ZeroXIOWallet, ZeroXIOWalletError, ErrorCode } from '@0xio/sdk';
function showSignState(canSign: boolean | undefined) {
const readOnly = canSign === false || (canSign === undefined && !wallet.getConnectionInfo().publicKey);
sendButton.disabled = readOnly;
readOnlyNote.hidden = !readOnly; // "This wallet is watch-only. Switch wallets in 0xio to send."
}
await wallet.connect();
showSignState(wallet.canSign());
wallet.on('accountChanged', (event) => showSignState(event.data.canSign));
try {
await wallet.sendTransaction({ to: recipient, amountOct: '1' });
} catch (error) {
if (error instanceof ZeroXIOWalletError && error.code === ErrorCode.WATCH_ONLY) {
showSignState(false);
}
}
```
### Error Codes
| Error Code | Description |
| - | - |
| `WATCH_ONLY` | The wallet is watch-only and cannot sign (v2.10.0, extension v3.0.3+) |
| `USER_REJECTED` | User declined transaction or connection |
| `INSUFFICIENT_BALANCE` | Not enough funds for transaction + gas |
| `EXTENSION_NOT_FOUND` | 0xio Wallet Extension not installed |
| `NETWORK_ERROR` | RPC connection or blockchain issue |
| `INVALID_ADDRESS` | Malformed or invalid Octra address |
| `INVALID_AMOUNT` | Negative amount, both amount fields given, or a number that can't be represented exactly in micro-OCT (6 decimals); pass a string like `"0.300000"` for exact control |
| `DEPLOY_CHANGED` | The deployer's nonce moved after the review, so the contract address would differ; nothing was sent |
| `DEPLOY_FAILED` | The node refused the deploy |
| `PERMISSION_DENIED` | Requested permission not granted |
| `TRANSACTION_FAILED` | Transaction reverted or rejected |
| `SIGNATURE_FAILED` | Message signing failed or invalid input |
| `WALLET_LOCKED` | Wallet is locked, user needs to unlock |
| `CONNECTION_REFUSED` | Connection was refused |
| `RATE_LIMIT_EXCEEDED` | Too many requests; `details.reason` is `review_pending`, `requests_per_second` or `open_requests` |
| `STATUS_UNKNOWN` | The SDK stopped waiting; the wallet may still finish the request. Never resend (2.12.0) |
| `REQUEST_ID_REUSED` | The request id belongs to another request of your site (2.12.0) |
| `EXECUTION_FAILED` | `wait: 'confirmed'` and the chain rejected or dropped the transaction (2.12.0) |
| `PVAC_KEY_MISMATCH` | `registerPrivateViewKey`: another private key is registered for the account (2.12.0) |
| `REGISTRATION_PENDING` | `registerPrivateViewKey`: this wallet's registration is not confirmed yet (2.12.0) |
| `MALFORMED_TRANSACTION` | Transaction is malformed |
| `SELF_TRANSFER` | Cannot send to yourself |
| `SENDER_NOT_FOUND` | Sender address not found |
| `INVALID_SIGNATURE` | Invalid transaction signature |
| `DUPLICATE_TRANSACTION` | Duplicate transaction detected |
| `NONCE_TOO_FAR` | Transaction nonce too far ahead |
| `INTERNAL_ERROR` | Internal server error |
| `NOT_CONNECTED` | The page has not connected; call `connect()` first |
| `INVALID_PARAMS` | The wallet rejected the request parameters, for example two fields that name different recipients, methods or arguments |
| `METHOD_NOT_ALLOWED` | `rpcCall` with a method outside the read-only allow-list |
| `NOT_AVAILABLE` | Not served by the wallet over the bridge (`encryptBalance`, `decryptBalance`), or a private method while 0xio Desktop is on another network |
| `PRIVATE_PROOF_FAILED` | A private primitive failed inside the wallet |
| `PRIVATE_TRANSFER_FAILED` | The private transfer was refused or failed |
| `CONTRACT_CALL_FAILED` | The contract call failed |
| `SIGN_FAILED` | Transaction signing failed |
| `RECIPIENT_NOT_REGISTERED` | The recipient has no private view key on-chain |
| `UNSUPPORTED_METHOD` | The wallet has no answer for this method (0xio Desktop: `getPendingPrivateTransfers`) |
| `NETWORK_CHANGED` | The site's network or wallet moved while the request waited for review; nothing was sent |
| `WALLET_CHANGED` | A private method while 0xio Desktop has another wallet open than the one this site uses |
| `UNKNOWN_ERROR` | Unexpected error occurred |
## Framework Integration
### React Hook
```typescript theme={null}
import { useEffect, useState, useCallback } from 'react';
import { ZeroXIOWallet, Balance, ConnectEvent } from '@0xio/sdk';
// Singleton instance to prevent multiple listeners
let walletInstance: ZeroXIOWallet | null = null;
export function useWallet() {
const [wallet, setWallet] = useState(walletInstance);
const [address, setAddress] = useState(null);
const [balance, setBalance] = useState(null);
const [isConnected, setIsConnected] = useState(false);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
let mounted = true;
async function init() {
// Return existing instance if ready
if (walletInstance) {
if (mounted) {
setWallet(walletInstance);
setIsReady(true);
walletInstance.getConnectionStatus().then(status => {
if (status.isConnected && mounted) {
setAddress(status.address || null);
setIsConnected(true);
}
});
}
return;
}
// Create new instance
const sdk = new ZeroXIOWallet({
appName: 'My DApp',
debug: true
});
try {
// Initialize (waits for extension)
const ready = await sdk.initialize();
if (ready && mounted) {
walletInstance = sdk;
setWallet(sdk);
setIsReady(true);
// Check existing connection
const status = await sdk.getConnectionStatus();
if (status.isConnected) {
setAddress(status.address || null);
setIsConnected(true);
}
}
} catch (e) {
console.error("SDK Init failed:", e);
}
}
init();
// Setup listeners if wallet exists
if (wallet) {
const handleConnect = (event: ConnectEvent) => {
if (!mounted) return;
setAddress(event.data.address);
setBalance(event.data.balance);
setIsConnected(true);
};
const handleDisconnect = () => {
if (!mounted) return;
setAddress(null);
setBalance(null);
setIsConnected(false);
};
const handleAccountChanged = (event: any) => {
if (!mounted) return;
setAddress(event.data.newAddress);
if (event.data.balance) setBalance(event.data.balance);
};
wallet.on('connect', handleConnect);
wallet.on('disconnect', handleDisconnect);
wallet.on('accountChanged', handleAccountChanged);
return () => {
wallet.off('connect', handleConnect);
wallet.off('disconnect', handleDisconnect);
wallet.off('accountChanged', handleAccountChanged);
};
}
}, [wallet]);
const connect = useCallback(async () => {
if (!wallet) throw new Error("Wallet SDK not initialized");
try {
const result = await wallet.connect({
permissions: ['accounts', 'public_transactions']
});
setAddress(result.address);
setBalance(result.balance);
setIsConnected(true);
} catch (error) {
console.error('Connection failed:', error);
throw error;
}
}, [wallet]);
// ... other methods (disconnect, signMessage, etc)
return {
wallet,
address,
balance,
isConnected,
isReady,
connect
};
}
```
### Vue.js Composition API
```typescript theme={null}
import { ref, onMounted, onUnmounted } from 'vue';
import { ZeroXIOWallet, Balance } from '@0xio/sdk';
export function useWallet() {
const wallet = new ZeroXIOWallet({ appName: 'My DApp' });
const address = ref(null);
const balance = ref(null);
const isConnected = ref(false);
const isReady = ref(false);
onMounted(async () => {
await wallet.initialize();
isReady.value = true;
// Check for existing connection
wallet.getConnectionStatus();
wallet.on('connect', (event) => {
address.value = event.data.address;
balance.value = event.data.balance;
isConnected.value = true;
});
wallet.on('disconnect', () => {
address.value = null;
balance.value = null;
isConnected.value = false;
});
wallet.on('balanceChanged', (event) => {
balance.value = event.data.newBalance;
});
});
onUnmounted(() => {
wallet.removeAllListeners();
});
const connect = async () => {
try {
const result = await wallet.connect({
permissions: ['accounts', 'public_transactions']
});
address.value = result.address;
balance.value = result.balance;
isConnected.value = true;
} catch (error) {
console.error('Connection failed:', error);
throw error;
}
};
const disconnect = async () => {
await wallet.disconnect();
};
const signMessage = async (message: string) => {
if (!isConnected.value) throw new Error('Not connected');
return await wallet.signMessage(message);
};
return { wallet, address, balance, isConnected, isReady, connect, disconnect, signMessage };
}
```
## TypeScript Definitions
The SDK exports comprehensive TypeScript types:
```typescript theme={null}
import type {
// Core types
WalletAddress,
Balance,
NetworkInfo,
ConnectionInfo,
ConnectOptions,
SDKConfig,
// Transaction types
TransactionData,
SignedTransaction,
TransactionResult,
TransactionFinality, // 'pending' | 'confirmed' | 'rejected' | 'dropped'
TransactionHistory,
Transaction,
// Event types
WalletEventType,
WalletEvent,
ConnectEvent,
DisconnectEvent,
AccountChangedEvent,
BalanceChangedEvent,
NetworkChangedEvent,
TransactionConfirmedEvent,
ErrorEvent,
// Contract types
ContractCallData,
ContractViewCallData,
ContractParams,
// Private balance types
PrivateBalanceInfo,
PrivateTransferData,
PendingPrivateTransfer,
// Permission types
Permission
} from '@0xio/sdk';
// Key type definitions
interface Balance {
public: number; // Public balance in OCT
private: number; // Private (FHE) balance in OCT
total: number; // Combined balance
currency: 'OCT'; // Currency symbol
}
interface TransactionData {
to: string; // Recipient address
amount?: string | number; // Raw micro-OCT, sent to the wallet as is
amountOct?: string | number; // Amount in OCT, converted by the SDK. One of the two.
message?: string; // Optional memo
feeLevel?: 1 | 3; // 1 = standard, 3 = priority
isPrivate?: boolean; // Use private transfer
}
interface NetworkInfo {
id: string; // 'mainnet' | 'devnet' | 'custom'
name: string; // Display name
rpcUrl: string; // RPC endpoint
explorerUrl?: string; // Transaction explorer base URL
explorerAddressUrl?: string; // Address explorer base URL
indexerUrl?: string; // Indexer/API base URL
supportsPrivacy: boolean; // FHE encrypted balance support
color: string; // Brand color hex
isTestnet: boolean; // Whether this is a test network
}
// Contract parameter types
type ContractParams = ReadonlyArray;
type Permission =
// Wallet scope names
| 'accounts' | 'public_transactions' | 'contract_calls' | 'contract_views'
| 'private_balance_read' | 'private_proofs' | 'private_transfers' | 'private_claims'
// Older names, translated at connect
| 'read_address' | 'read_balance' | 'read_public_key' | 'send_transactions' | 'sign_messages'
| 'view_private_balance' | 'view_encrypted_balance' | 'encrypt_balance' | 'decrypt_balance'
| 'stealth_scan' | 'stealth_claim';
```
## Utility Functions
The SDK exports useful utility functions:
```typescript theme={null}
import {
// Validation
isValidAddress,
isValidAmount,
isValidMessage,
isValidFeeLevel,
isValidNetworkId,
// Formatting
formatOCT,
formatAddress,
formatTimestamp,
formatTxHash,
// Conversion
toMicroOCT,
fromMicroOCT,
octToMicro, // exact string conversion, at most 6 decimals
// Permissions
toWalletPermissions,
withLegacyAliases,
// Address derivation
deriveOctraAddress, // async: base64 Ed25519 pubkey to an "oct..." address
// Error utilities
createErrorMessage,
isErrorType,
// Async utilities
delay,
retry,
withTimeout,
// Browser utilities
isBrowser,
checkBrowserSupport,
// Development utilities
generateMockData,
createLogger
} from '@0xio/sdk';
// Examples
if (isValidAddress('oct1...')) {
console.log('Valid address');
}
const formatted = formatOCT(123.456789, 2); // "123.46"
const shortAddr = formatAddress('oct1abc...xyz'); // "oct1ab...xyz"
const micro = toMicroOCT(1.5); // "1500000"
const exact = octToMicro('10.5'); // "10500000"
const oct = fromMicroOCT(1500000); // 1.5
// Derive an Octra address from a base64 Ed25519 public key
const addr = await deriveOctraAddress(publicKeyBase64); // "oct..."
await delay(1000); // Wait 1 second
```
## SDK Compatibility
```typescript theme={null}
import {
SDK_VERSION,
MIN_EXTENSION_VERSION,
MIN_EXTENSION_VERSION_DEVNET,
SUPPORTED_EXTENSION_VERSIONS,
checkSDKCompatibility
} from '@0xio/sdk';
console.log('SDK Version:', SDK_VERSION); // "2.12.0"
console.log('Min Extension (Mainnet):', MIN_EXTENSION_VERSION); // "2.0.1"
console.log('Min Extension (Devnet):', MIN_EXTENSION_VERSION_DEVNET); // "2.2.1"
console.log('Supported:', SUPPORTED_EXTENSION_VERSIONS); // ">=2.0.1"
// Check browser compatibility
const { compatible, issues, recommendations } = checkSDKCompatibility();
if (!compatible) {
console.log('Issues:', issues);
console.log('Recommendations:', recommendations);
}
```
## 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()`
> [!TIP]
>
> For production dApps, implement comprehensive error handling and user feedback for all wallet operations.
# Wallet protocol 2
Source: https://docs.0xio.xyz/developers/wallet-protocol
What a wallet implements to answer @0xio/sdk 2.12.0, and the conformance suite that checks it.
For teams building a wallet that sites reach through `@0xio/sdk`. Every 0xio wallet (extension, 0xio Desktop, the 0xio app) runs the same conformance suite against these rules.
## Messages
| | Extension and app (postMessage) | 0xio Desktop (local WebSocket) |
| - | - | - |
| Request | `{ id, method, params, timestamp, v: 2, rid? }` | `site_request { id, network, rid?, request }` |
| Hello | `wallet_hello { sdk, protocols: [1, 2] }`, answered `{ protocol: 2, kind, version, capabilities }` | `protocol` and `capabilities` in the `site_hello` reply |
| Status | `request_status { rid }` | `site_status { rid }` |
| Cancel | `cancel_request { rid }` | `site_cancel { id?, rid }` |
| Event | `requestUpdate` with the request's status | `site_event` kind `request` |
| Presence | Answer `0xio:requestWallet` with a `0xio:announceWallet` event: `{ protocol, kind, version, name }` | none |
* `rid` is 32 lowercase hex and rides only on methods that open a review or sign. Key records by the page's origin as the browser reports it, never by params.
* An answer to a request with a `rid` carries it. An error answer carries `rid` and `status` at the top level and again in `error.details` as `{ requestId, status }`.
* The announce and `wallet_hello` carry no user state: no address, network, lock state or connection.
* "Same request" is sha256 of the canonical JSON of `{ method, network, params }`. `network` is the frame's network on 0xio Desktop and `null` on postMessage channels.
## Rules
* **Idempotency.** The same request under a known `rid` joins it or gets the stored outcome; another request under it is `REQUEST_ID_REUSED`. A final failure never runs again.
* **Statuses.** Only the allowed moves; a final status never changes; records live 24 hours.
* **Scope.** Events go only to the tab and frame that sent the request. A site that is not connected sees `review_open` for `locked`, and no address or network.
* **Timeouts.** A page that stops waiting never cancels the review. The review window is 5 minutes, then `expired` with `USER_REJECTED`.
* **Page close.** An unanswered review closes with its page; an approved request finishes and keeps its outcome.
* **Limits.** Over 20 requests a second, answer `RATE_LIMITED` for each extra one, never drop it. Over the open request cap (8, or a lower cap you declare), answer `RATE_LIMIT_EXCEEDED`.
* **Pre-signed.** `broadcast_only` from the connected wallet is not reviewed again; from another wallet, `INVALID_PARAMS`.
* **Registration.** `register_private_view_key` follows the [contract on the request lifecycle page](/developers/request-lifecycle#registering-a-private-receive-key).
## Conformance suite
The suite lives in the SDK repository under [`conformance/`](https://github.com/0xio-xyz/0xio-sdk/tree/main/conformance). It speaks only the wire, so a wallet passes only by following the rules itself.
* `harness.ts`: the adapter your test harness implements (open a page, send, receive events, close the page, approve or reject the review, and optional clock, chain, lock and account hooks).
* `suite.ts`: the cases. Register them with `defineConformanceSuite({ name, features, create })` under Jest or Vitest.
* `vectors.json`: request id format, digest bytes, transitions, masking and limits for `cargo test`, XCTest and JUnit.
* `reference/`: a reference wallet that passes every case, to read as the executable spec.
The suite's README has the exact steps to run it from a wallet repository.
# The Octra Network
Source: https://docs.0xio.xyz/introduction/octra-network
Overview of the Octra Network, a Layer-1 blockchain that uses Hypergraph Fully Homomorphic Encryption to keep balances and state private.
Octra is a **privacy-preserving Layer-1 blockchain** built from the ground up to execute **encrypted computation on-chain**.
Unlike traditional blockchains, where data is either fully public or hidden using external techniques (ZK proofs, MPC, or trusted hardware), Octra allows:
* Transactions
* Smart contract logic
* Onchain state
to remain **encrypted at all times**, while still being executed and verified by the network.
> [!NOTE]
>
> **In short:** Octra computes on data it cannot see.
0xio is designed specifically to expose this capability safely to end users.
## Fully Homomorphic Encryption (FHE)
Fully Homomorphic Encryption (FHE) is a cryptographic primitive that allows arbitrary computation to be performed directly on encrypted data, without decryption.
On Octra, this enables:
* Validators to process transactions without seeing balances or inputs
* Smart contracts to execute logic over private state
* Verifiable outputs with no data leakage
* User data privacy enforced by cryptography, not trust
This is not privacy through obfuscation or offchain assumptions. It is **privacy enforced at the execution layer**.
## Hypergraph FHE (HFHE)
Traditional FHE is prohibitively slow. Octra addresses this with **HFHE (Hypergraph Fully Homomorphic Encryption)**.
HFHE differs fundamentally from lattice-based FHE approaches by:
* Mapping encrypted data onto **hypergraph incidence structures**
* Keeping encryption noise **localized**, not global
* Enabling massive parallel execution and bootstrapping
* Remaining CPU-friendly without specialized hardware
### What This Unlocks
* Parallel encrypted logic gate evaluation (AND / OR / XOR)
* Sub-10ms encrypted bootstrapping
* Practical encrypted throughput at Layer-1 scale
0xio abstracts these mechanics so users and developers benefit from encrypted execution **without interacting with cryptography directly**.
## Validator Work: Real Computation, Not Waste
Octra does not rely on hash-based mining.
Validators are rewarded for performing the **actual encrypted computation** required by the network:
* Executing encrypted transactions
* Running encrypted application logic
* Maintaining encrypted state
* Participating in validation and key management tasks
There is no expendable work.
> [!NOTE]
>
> **Execution itself is the work being secured.**
## Circles: Encrypted Execution Environments
Octra introduces **Circles**, isolated, on-chain execution environments that function like encrypted backend services.
Within a Circle, developers can:
* Deploy complex application logic
* Run code written in **Rust, C++, or WASM**
* Store and compute over encrypted state
* Control access without exposing raw data
Circles are best understood as:
**Private, verifiable, on-chain compute containers**
They scale independently, remain isolated, and inherit encrypted execution by default.
## Networks
Octra currently operates two public networks:
| Network | Purpose | FHE Support | Explorer |
| - | - | :-: | - |
| **Mainnet** | Production network | Yes | [octrascan.io](https://octrascan.io) |
| **Devnet** | Development and testing | Yes | [devnet.octrascan.io](https://devnet.octrascan.io) |
**Devnet** is the primary testing ground for FHE-powered features: encrypted balances, private transfers, smart contract development, and the full encrypt/decrypt/claim flow. All 0xio products (extension, mobile app, desktop) support switching between networks.
## How 0xio Fits In
0xio sits **above** Octra's execution layer.
* Octra provides encrypted computation
* 0xio provides usable interfaces, wallets, SDKs, and applications
The 0xio product suite includes:
* **[Browser Extension](/products/extensions)**: Primary gateway for web-based interaction
* **[Mobile Wallet](/products/mobile)**: Privacy on the go with biometric security
* **[Desktop Wallet](/products/desktop)**: Native PVAC engine that accelerates FHE operations
* **[DEX](/products/dex)**: Cross-chain swaps and same-chain CLMM pools
* **[Bridge](/products/bridge)**: Trustless OCT to wOCT bridge between Octra and Ethereum
* **[SDK](/developers/sdk-guide)**: TypeScript SDK for DApp integration
Users interact with 0xio. 0xio securely interfaces with Octra.
# Official Links
Source: https://docs.0xio.xyz/introduction/official-links
Official 0xio resources including the website, documentation, ecosystem page, privacy policy, and verified community channels for support.
## Website & Documentation
> [card] [0xio Website](https://0xio.xyz)
>
> Official website and landing page
> [card] [Documentation](https://docs.0xio.xyz)
>
> Official documentation (you are here)
> [card] [Ecosystem](https://0xio.xyz/ecosystem)
>
> Explore the 0xio ecosystem
> [card] [Privacy Policy](https://0xio.xyz/privacy)
>
> Our privacy policy and data practices
***
## Products & Downloads
> [card] [Install 0xio](https://0xio.xyz/install)
>
> Every client in one place: browser extension, desktop (macOS and Windows), mobile (iOS and Android) and the SDK
> [card] [Browser Extension](https://chromewebstore.google.com/detail/0xio-wallet/anknhjilldkeelailocijnfibefmepcc)
>
> Install 0xio Wallet for Chrome, Brave, Edge, Opera, and Firefox (the Firefox version is in review on Mozilla Add-ons)
> [card] [Telegram Bot](https://t.me/NullXio_bot)
>
> @NullXio_bot - Wallet monitoring and notifications
> [card] [0xio DEX](https://dex.0xio.xyz)
>
> Cross-chain swaps and same-chain CLMM pools
> [card] [0xio Bridge](https://bridge.0xio.xyz)
>
> Bridge between Octra and Ethereum, with stats at bridge.0xio.xyz/stats
> [card] [Atlas](https://atlas.0xio.xyz)
>
> Blockchain visualization and analytics
***
## Infrastructure
> [card] [RPC Proxy](https://rpc-proxy.0xio.xyz)
>
> rpc-proxy.0xio.xyz: optional Octra RPC proxy with history compression, used only when you turn it on
> [card] Push Server
>
> Internal push notification server for mobile wallet alerts
> [card] [Indexer](https://indexer.0xio.xyz)
>
> indexer.0xio.xyz: Chain indexing service for the 0xio ecosystem
***
## Developer Resources
> [card] [GitHub](https://github.com/0xio-xyz)
>
> Official GitHub organization
> [card] [0xio SDK](https://www.npmjs.com/package/@0xio/sdk)
>
> @0xio/sdk on npm
> [card] [0xio PVAC](https://www.npmjs.com/package/@0xio/pvac)
>
> @0xio/pvac: WASM privacy primitives on npm
> [card] [Token Lists](https://github.com/0xio-xyz/token-lists)
>
> Community-maintained token list repository
***
## Social & Community
> [card] [X (Twitter)](https://x.com/0xio_xyz)
>
> @0xio_xyz - Follow for updates and announcements
***
## Contact
> [card] General Inquiries
>
> [team@0xio.xyz](mailto:team@0xio.xyz)
> [card] Founder
>
> [gery@0xio.xyz](mailto:gery@0xio.xyz)
> [!WARNING]
>
> **Security Notice:** Only trust links from this official documentation. 0xio Labs members will never ask for your recovery phrase, private keys, or passwords.
***
## Quick Reference
| Resource | Link |
| - | - |
| Install | [0xio.xyz/install](https://0xio.xyz/install) |
| Website | [0xio.xyz](https://0xio.xyz) |
| Documentation | [docs.0xio.xyz](https://docs.0xio.xyz) |
| Browser Extension | [Chrome Web Store](https://chromewebstore.google.com/detail/0xio-wallet/anknhjilldkeelailocijnfibefmepcc) |
| Telegram Bot | [@NullXio_bot](https://t.me/NullXio_bot) |
| 0xio DEX | [dex.0xio.xyz](https://dex.0xio.xyz) |
| 0xio Bridge | [bridge.0xio.xyz](https://bridge.0xio.xyz) |
| Bridge Stats | [bridge.0xio.xyz/stats](https://bridge.0xio.xyz/stats) |
| 0xio Oracle | [oracle.0xio.xyz](https://oracle.0xio.xyz) |
| 0xio Indexer | [indexer.0xio.xyz](https://indexer.0xio.xyz) |
| Atlas | [atlas.0xio.xyz](https://atlas.0xio.xyz) |
| SDK | [@0xio/sdk](https://www.npmjs.com/package/@0xio/sdk) |
| PVAC | [@0xio/pvac](https://www.npmjs.com/package/@0xio/pvac) |
| GitHub | [github.com/0xio-xyz](https://github.com/0xio-xyz) |
| Twitter/X | [@0xio_xyz](https://x.com/0xio_xyz) |
| Ecosystem | [0xio.xyz/ecosystem](https://0xio.xyz/ecosystem) |
| Privacy Policy | [0xio.xyz/privacy](https://0xio.xyz/privacy) |
| Token Lists | [github.com/0xio-xyz/token-lists](https://github.com/0xio-xyz/token-lists) |
| RPC Proxy | [rpc-proxy.0xio.xyz](https://rpc-proxy.0xio.xyz) (optional) |
| General Email | [team@0xio.xyz](mailto:team@0xio.xyz) |
# What is 0xio?
Source: https://docs.0xio.xyz/introduction/overview
Learn about 0xio, the user-facing application layer for Octra Network that brings encrypted balances, private transfers, and FHE tools to users.
**0xio** is the **user-facing application layer** built for the **Octra Network**, designed to make encrypted blockchain computation usable, safe, and intuitive.
While Octra provides the underlying Layer-1 blockchain and cryptographic infrastructure, **0xio delivers the wallets, SDKs, and applications** that allow users and developers to interact with Octra's fully encrypted execution model without needing to understand or implement cryptography themselves.
## What is 0xio?
0xio is a **non-custodial Web3 ecosystem** built on top of the Octra Network.
It provides:
* Secure client-side applications (wallets, SDKs, interfaces)
* Abstractions over encrypted execution and fees
* Tooling for private transfers, private trading, and encrypted application logic
All sensitive data is encrypted **locally on the user's device** before ever touching the network. 0xio never has access to user keys, balances, or private transaction data.
> [!WARNING]
>
> **Important:** 0xio is an independent, community-built project. It is not operated by, nor formally affiliated with, Octra Labs.
## Why 0xio?
On most blockchains, privacy is optional and usually external:
* Balances are public
* Transactions are globally visible
* Activity can be trivially tracked and correlated
0xio takes a different approach.
By building directly on Octra's encrypted execution layer, 0xio enables:
* **Private balances whenever you want them**
* **Encrypted transactions validated without revealing amounts**
* **Applications that operate on private state, not public data**
The result is a blockchain experience closer to traditional finance privacy, **but without custodians, intermediaries, or trusted servers.**
## Core Value Proposition
### 1. Privacy by Default
0xio integrates Octra's **Hypergraph Fully Homomorphic Encryption (HFHE)** so balances, transfers, and application logic can remain encrypted throughout their lifecycle.
### 2. Non-Custodial Sovereignty
All keys are generated and stored locally. 0xio cannot freeze, access, or recover user funds.
### 3. Client-Side Encryption
Sensitive data is encrypted **before** it is broadcast to the network. Validators process encrypted payloads without learning their contents.
## Architecture
The 0xio ecosystem spans multiple client platforms unified by a shared SDK layer, backed by infrastructure services, all interfacing with the Octra Network's FHE-enabled blockchain.
### Client Applications
> [card] Extension v3.0.4
>
> React, Vite and WASM PVAC, with an EVM wallet
> [card] Mobile v1.3.3
>
> React Native, Expo 57 and native PVAC
> [card] Desktop v0.6.0
>
> Tauri 2, Rust and native PVAC
> [card] DEX
>
> React + Vite + CLMM + Cross-chain
### Developer SDK
> [card] [@0xio/sdk v2.12.1](https://www.npmjs.com/package/@0xio/sdk)
>
> TypeScript SDK on npm. Connects dApps to 0xio wallets and the Octra Network. Private MessageChannel transport, security-hardened postMessage, pluggable wallet adapters, full type definitions. Privacy primitives ship separately as `@0xio/pvac` (WASM).
### Infrastructure
> [card] RPC Proxy
>
> Optional caching proxy for Octra RPC that compresses heavy history responses. Wallets use it only when you turn it on. No key needed, with a per-IP limit.
> [card] [Oracle](/developers/oracle-api)
>
> OCT and token prices, EVM address data for the wallets and bridge analytics. Reads need no key, with a per-IP limit.
> [card] Push Server
>
> Real-time transaction notifications with signature authentication, replay protection, and per-address rate limiting.
> [card] [Install and updates](https://0xio.xyz/install)
>
> Every client from one page at 0xio.xyz/install. Desktop builds and minisign-verified updates are served through 0xio.xyz.
### Blockchain Layer
> [card] Octra Network
>
> FHE-enabled blockchain with AML smart contracts, BridgeVault for OCT-wOCT bridging, and on-chain contract verification. Supports both Mainnet and Devnet.
| Layer | Components |
| - | - |
| **Client Applications** | Browser Extension (React, Vite, WASM PVAC, EVM wallet), Mobile App (React Native, Expo 57, native PVAC), Desktop App (Tauri 2, Rust, native PVAC), DEX (React, Vite, CLMM and cross-chain swap), Bridge (bridge.0xio.xyz) |
| **Developer SDK** | @0xio/sdk v2.12.1 (TypeScript, npm, MessageChannel transport, postMessage security, request lifecycle); @0xio/pvac v1.0.2 WASM privacy primitives |
| **Infrastructure** | RPC Proxy (optional, caching, history compression), Oracle (prices, EVM data, bridge analytics), Push Server (signature-authenticated notifications), install page and desktop updates at 0xio.xyz |
| **Blockchain** | Octra Network: FHE encrypted balances, AML smart contracts, BridgeVault, wOCT (ERC-20), on-chain proof verification |
## The 0xio Ecosystem
> [card] [Browser Extension](/products/extensions)
>
> Non-custodial wallet for Chrome, Brave, Edge, Opera and Firefox. Public and private OCT, an EVM wallet and a built-in bridge.
> [card] [Desktop Wallet](/products/desktop)
>
> Native Tauri and Rust wallet with a menu bar panel and a native privacy engine for faster private operations.
> [card] [Mobile Wallet](/products/mobile)
>
> Privacy on the go with biometric security and mobile-optimized features.
> [card] [Developer SDK](/developers/sdk-guide)
>
> TypeScript SDK for integrating dApps with 0xio and Octra's FHE capabilities.
> [card] [0xio DEX](/products/dex)
>
> Cross-chain swap protocol and same-chain CLMM pools for the Octra Network.
> [card] [0xio Bridge](/products/bridge)
>
> Bridge to Ethereum, Bridge to Octra and Any chain to OCT, secured by a light client.
> [card] [Telegram Bot](/products/telegram-bot)
>
> Real-time wallet monitoring and notifications via Telegram.
# How 0xio and Octra Work
Source: https://docs.0xio.xyz/introduction/technology
Learn how Octra works with HFHE encryption, how 0xio signs and encrypts transactions locally, and the cryptographic specs behind the wallet.
0xio is built on **Octra**, so it helps to know how Octra works.
## What is Octra?
**Octra** is a Layer-1 blockchain network distinguished by its use of **Hypergraph Fully Homomorphic Encryption (HFHE)**. Unlike traditional blockchains where transparency is mandatory, Octra enables privacy while maintaining decentralized verification.
### Key Concepts
* **Fully Homomorphic Encryption (FHE):** Traditional encryption requires you to decrypt data to process it (exposing it to potential leaks). FHE allows the network to perform calculations on *encrypted* data without ever decrypting it. The result remains encrypted, and only you (the key holder) can decrypt the final state.
* **Compute-Coupled Validation:** Instead of solving arbitrary hash puzzles (like Bitcoin's Proof-of-Work), Octra validators utilize their computing power to process actual FHE tasks. They are rewarded for executing useful encrypted computations that advance the network state.
* **Isolated Execution Environments (Circles):** Octra allows developers to run "Circles": independent execution environments that function similarly to private servers on the blockchain, isolating state for performance and privacy.
## How 0xio Fits In
0xio acts as the **Client-Side Encryption & Signing Layer** for Octra.
Because Octra relies on FHE, data must be encrypted *before* it leaves your device. If raw data were sent to the network, privacy would be compromised.
1. **Local Encryption & Signing:** When you initiate a transaction, 0xio encrypts the payload locally using your specific encryption keys and Octra's public parameters. It then **signs** the transaction with your private key to prove authority without revealing the data.
2. **Broadcasting:** The encrypted blob (ciphertext) is signed and sent to the Octra network.
3. **Encrypted Execution:** Octra validators process the state change (e.g., updating a balance) on the encrypted data. They verify the transaction's validity mathematically without ever seeing the plaintext amounts.
4. **Decryption:** You use your 0xio wallet to view your new balance. The wallet decrypts the on-chain state locally to reveal the numbers to you.
## 0xio Security Specs
0xio is designed with strict client-side security standards to ensure non-custodial safety.
| Feature | Specification |
| :- | :- |
| **Recovery Phrase** | **BIP-39** 12-word mnemonic. The seed derives a master key via `HMAC-SHA512("Octra seed", seed)` (no BIP-44 path). |
| **Signing Algorithm** | **Ed25519** (tweetnacl) for high-speed, secure transaction signatures. Your address is `SHA-256(public key)` encoded as base58 with an `oct` prefix. |
| **Vault Encryption** | **AES-256-GCM**. Your keys are encrypted at rest on your device. |
| **Key Derivation** | **PBKDF2-HMAC-SHA256** with high iteration counts: 900,000 (extension), 600,000 (desktop), and 100,000 for the mobile PIN. |
| **Privacy Engine** | **PVAC** (curve25519-dalek / Ristretto255) for FHE encrypt/decrypt, Bulletproofs range proofs, Pedersen commitments, and stealth transfers via X25519 ECDH. |
| **EVM Keys** | The extension derives the EVM key from the same phrase at `m/44'/60'/0'/0/i` (secp256k1) for one signature at a time. |
| **Price Feeds** | **0xio Oracle**: the live wOCT/ETH Uniswap v4 pool price in USD, cross-checked against CoinGecko and DexScreener, with anomaly detection and PostgreSQL history. EVM token prices in the extension come from DefiLlama. |
| **Telemetry** | No analytics, advertising or tracking scripts. The extension has no crash reporting; the desktop and mobile apps send crash reports without keys, phrases or PINs. See the [Privacy Policy](https://0xio.xyz/privacy). |
> [!NOTE]
>
> 0xio is an independent project built by community developers. It is not owned by, nor formally affiliated with, Octra Labs, ensuring a separation between the network and the toolset.
# 0xio Bridge
Source: https://docs.0xio.xyz/products/bridge
Bridge between Octra and Ethereum: Bridge to Ethereum (OCT to wOCT), Bridge to Octra (wOCT to OCT) and Any chain to OCT, on bridge.0xio.xyz and inside the 0xio extension.
> [!NOTE]
>
> **Status: Live on Mainnet.** Use the bridge at [bridge.0xio.xyz](https://bridge.0xio.xyz) or from **Bridge** inside the [0xio browser extension](/products/extensions).
## Overview
The **0xio Bridge** moves OCT between the **Octra Network** and **Ethereum**, where it is held as **wOCT** (an ERC-20 token). 1 OCT always equals 1 wOCT.
| Route | From | To |
| - | - | - |
| **Bridge to Ethereum** | OCT on Octra | wOCT on Ethereum |
| **Bridge to Octra** | wOCT on Ethereum | OCT on Octra |
| **Any chain to OCT** | A token on any chain Relay supports | OCT on Octra |
The bridge uses a **light client**: an Octra light client contract on Ethereum verifies Merkle proofs of Octra transactions, so a claim on Ethereum does not depend on a trusted oracle.
## How It Works
### Bridge to Ethereum (OCT to wOCT)
**1. Connect wallets**
Connect your 0xio wallet (Octra) and an EVM wallet. In the 0xio extension both are the same wallet.
**2. Bridge to Ethereum**
Send OCT to the bridge on Octra with your Ethereum address. The bridge holds your OCT until wOCT is claimed.
**3. Wait for Octra's proof on Ethereum**
The bridge relayer posts Octra epoch roots to the light client on Ethereum. Your transfer becomes claimable once the light client reaches its epoch. There is no fixed waiting time; the bridge shows the live wait.
**4. Claim on Ethereum**
Claim wOCT on Ethereum with a Merkle proof. Anyone can submit the claim, but only the address you named receives the wOCT.
### Bridge to Octra (wOCT to OCT)
**1. Allow wOCT**
Allow the bridge contract on Ethereum to use the amount of wOCT you are bridging. This is skipped when the allowance already covers it.
**2. Bridge to Octra**
wOCT leaves Ethereum with your Octra address as the recipient.
**3. Arrives on Octra**
The bridge relayer sees the transfer and the same amount of OCT arrives at your Octra address, usually within a minute. The arrival is confirmed on chain, not assumed after a timer.
### Any Chain to OCT
Pay with a token on any EVM chain that Relay supports. Relay swaps it into wOCT on Ethereum and bridges it to your Octra address in one fill. No 0xio contract is involved.
* Search tokens and chains by name or symbol, or paste a contract address.
* Type what you bridge, or the OCT you want to get; the other field shows the live quote.
* Before your wallet signs, the order is checked against Relay's own chain list: the OCT goes to your Octra address for the amount shown, the payment stays within what you entered, and refunds come back to you. Anything else is refused.
* A 0.25% 0xio fee applies to this route only. It is shown in **Fees and gas** before you confirm.
## Bridge Inside the Extension
The 0xio browser extension bridges without leaving the wallet, using the wallet's own Octra and 0x addresses. The bridge works on mainnet only.

Bridge in the extension
* **Both directions and Any chain to OCT,** each step with its own review. Bridge to Ethereum signs the live Octra network fee shown on the review, and asks again if the network's minimum rises.
* **Claim page:** the **Claim** pill in the Bridge header opens your transfers to Ethereum in **Ready**, **In progress** and **Completed**, with a count on each and the wait shown as hours left. The pill counts the transfers ready to claim.

The Claim page (sample transfers)
* **Find my transfers** searches Octra and Ethereum for this wallet's bridge transfers. **Add by hash** takes an Octra or Ethereum transaction hash. The first time Bridge opens on a wallet, its transfers are read back from the chains.
* **A transfer to Octra that was allowed but not sent** is offered again from the form.
* **Wallets without an EVM key** (imported from an Octra private key) can bridge to Ethereum by typing a 0x address. The claim, Bridge to Octra and Any chain to OCT need an EVM wallet, so they are done on [bridge.0xio.xyz](https://bridge.0xio.xyz) with any EVM wallet; the Claim page says so and links there.
* **Watch-only wallets** cannot bridge.
* If a send may have reached the node but the reply was lost, the wallet follows it on Octra and asks you to check Claim before trying again, never that it was not sent.
## Bridge Stats
[bridge.0xio.xyz/stats](https://bridge.0xio.xyz/stats) shows transfers made on the bridge site and from **Bridge** in the 0xio extension:
* **Net to Ethereum:** OCT bridged to Ethereum minus OCT bridged back to Octra, over 7 days, 30 days or all time.
* **Weekly counts** of transfers to Ethereum, transfers to Octra and claims on Ethereum.
* **Totals:** total and 24 hour volume, transactions and wallets (Octra and Ethereum).
* **By client:** Web (the bridge site) and 0xio Extension, each with its total bridged and transactions.
* **By route:** Octra to Ethereum, Ethereum to Octra and Any chain to Octra, each with total bridged, the last 24 hours and transactions.
* **Recent bridge events** with time (UTC), type, from, to and amount.

bridge.0xio.xyz/stats
The stats count a transfer only after the 0xio oracle has found it on chain. See [Bridge Analytics](/developers/oracle-api#bridge-analytics) for the API.
## Features
* **Light client verification:** claims are checked on Ethereum against Octra epoch roots, with no trusted oracle
* **Multi-wallet support:** EIP-6963 detection for MetaMask, Rabby, Phantom and other EVM wallets
* **WalletConnect:** connect the 0xio mobile app by scanning the QR code
* **Resumable transfers:** the current transfer stays at the top of History with its Claim button and resumes after a refresh, so the page can be closed while you wait
* **Honest states:** a transfer reads Complete only after the claim receipt or the confirmed arrival on Octra
* **Bridge history:** claim actions, recovery from the relayer feed and lookup by hash
## Contract Addresses
| Contract | Network | Address |
| - | - | - |
| BridgeVault | Octra | `oct5MrNfjiXFNRDLwsodn8Zm9hDKNGAYt3eQDCQ52bSpCHq` |
| wOCT (ERC-20) | Ethereum | `0x4647e1fE715c9e23959022C2416C71867F5a6E80` |
| EthereumBridge | Ethereum | `0xE7eD69b852fd2a1406080B26A37e8E04e7dA4caE` |
| OctraLightClient | Ethereum | `0xC01cA57dc7f7C4B6f1B6b87B85D79e5ddf0dF55d` |
## Limits
| Parameter | Value |
| - | - |
| Minimum to Ethereum | 1 OCT |
| Rate | 1 OCT = 1 wOCT (always) |
| Per-transfer and daily limits | Set on the bridge contract and read before every review. A claim over the daily limit becomes claimable the next day |
| Pause | Either side can be paused; the bridge says so and keeps your OCT in your wallet |
## Fees
| Route | 0xio fee |
| - | - |
| Bridge to Ethereum and Bridge to Octra | None (network fees only) |
| Any chain to OCT | 0.25%, collected by Relay and shown in Fees and gas before you confirm |
## Architecture
```
User (0xio wallet + EVM wallet)
|
+-- Octra: BridgeVault (holds OCT bridged to Ethereum)
|
+-- Ethereum: EthereumBridge + wOCT ERC-20
|
+-- OctraLightClient (Merkle proof verification)
|
+-- Bridge relayer (posts epoch roots, delivers OCT to Octra)
```
## Services
| Service | URL | Role |
| - | - | - |
| Bridge | [bridge.0xio.xyz](https://bridge.0xio.xyz) | Bridge interface |
| Bridge Stats | [bridge.0xio.xyz/stats](https://bridge.0xio.xyz/stats) | Volume, wallets and activity |
| Oracle | [oracle.0xio.xyz](https://oracle.0xio.xyz) | Prices and bridge event tracking |
## Known Limitations
* **Epoch sync delay:** the light client on Ethereum can fall behind Octra. Until it catches up, new transfers to Ethereum are not claimable yet; the bridge keeps checking.
* **Pruned epochs:** old epoch roots are pruned from the light client contract, so claim your wOCT promptly.
## Security
* **No trusted oracle:** claims are verified on chain against the light client
* **Pause:** the bridge can be paused during emergencies
* **Limits:** per-transfer and daily limits slow any drain
* **Account change detection:** the site notices wallet account switches so you never send to the wrong address
> [!WARNING]
>
> Always check the full destination address on the review before bridging. The bridge shows the complete address, not a shortened one.
# Desktop Wallet
Source: https://docs.0xio.xyz/products/desktop
The 0xio Desktop wallet for macOS and Windows: a native Octra wallet with a menu bar panel, a native privacy engine that speeds up the browser extension, and contract tools.
The **0xio Desktop** is a native desktop application built with [Tauri](https://tauri.app/) (Rust backend and React frontend). It is a full wallet for the Octra Network, a menu bar panel for everyday sends and private operations, and a **native privacy engine** that builds proofs for the browser extension.
> [!NOTE]
>
> **Status: Live (v0.6.0).** Download it from [0xio.xyz/install](https://0xio.xyz/install) for macOS (Apple silicon and Intel, macOS 13 or later, signed and notarized by Apple) or Windows.
## Why Desktop?
Browser extensions run in a sandbox with limited access to the CPU and memory, and private proofs are heavy work. The desktop app runs **pvac-rs** natively with every CPU core, so private operations finish faster than in the browser, both in the app and for the extension when the two run together.
***
## Menu Bar Panel
Click the 0xio icon in the menu bar for your wallet at a glance. The panel opens over other apps, including full-screen ones, on every Space and on the display you clicked.
* **Balance** up front, hidden until you click it, with a live status line for the privacy engine.
* **Send**, **Receive** (with a QR code), **Private** and **Lock**, and a wallet switcher. The **•••** menu holds the network, Lock now, Open at login, Open and Quit.
* **Private:** encrypt or decrypt, claim private transfers, turn on a private balance, migrate it or update the encryption key, each with the live network fee.
* **Locked:** unlock in the panel with Touch ID or your password.
* The menu bar icon shows an outline while locked, a turning slash while a private proof runs, and the number of site requests waiting (else the transfers waiting to be claimed).
### Command Line
The panel has a command line that reads loose input, for example `send 10 to 0xio privately`, `pay bob half` or `5 to oct8Hk...`. It accepts any word order and small typos, amounts such as max, half, 25% or 1.5k, and recipients by contact name, wallet name or address prefix. A live line says what Return will do. A command only fills the form: every send still opens its review.

The menu bar panel with a command typed
### Themes
Choose the panel's look in **Settings > Appearance > Menu bar theme**: Match app, Original, Smoke, Frost, Octra, Clear, Phosphor, Ember or CLI.
The **CLI** theme turns the panel into a terminal with no buttons. Type `0xio --help` for the commands (send, receive, qr, encrypt, decrypt, private, claim, migrate, lock, unlock, balance, status, wallets, use, network, theme, open, quit and more). It keeps history and tab completion, shows a review before anything is signed, and signs only after you type `y`. A review lapses when the wallet, network, lock state or fee changes before you answer.

The CLI theme with a send review
### Reviews and Fees
* Every send, encrypt, decrypt, setup, migration and key update shows a review with the wallet, network and live network fee, then asks for Touch ID or your password.
* The fee on the review is the fee signed, as long as the network still accepts it. You are asked to review again only when the network's minimum fee rises above it.
* A fee that cannot be read blocks the action with **Network fee unavailable. Try again.** No default fee is ever signed.
* An approved request runs only on the wallet and network its review showed.
* A busy node gets the same signed transaction again (3 tries in all). A nonce that is already used sends the transaction back to review; nothing is re-signed on its own. A rejected or too large transaction says so. See [What Happens After You Send](/wallet/sending#what-happens-after-you-send).
* A private send is offered only when the private balance is ready. A balance that is due for a migration, needs a key update or setup, or is waiting for the network shows its status, with **Migrate**, **Update** or **Turn on** where the app can do it.
### Sites Through the Extension
A website can sign with the desktop wallet instead of the browser. In the extension's connect review, pick **Sign with: 0xio Desktop** and press **Continue**. The extension only carries the site's requests over the paired connection; every one is reviewed in the menu bar and signed by the desktop app.
* **Arrival:** when nothing is waiting and the panel is closed, the first request opens the panel on its review. Later requests never take focus; they raise the count on the menu bar icon.
* **Notifications:** while the panel is closed, a later request also shows one macOS notification for its site, for example "[https://app.octradex.xyz](https://app.octradex.xyz) wants you to sign a transaction". A site gets one at a time. Click it to open the panel on that request, or on the Requests list when more wait. macOS asks for permission the first time one is due. Turn them off in **Settings > Security & Privacy**, **Notify me about site requests**; if macOS has them off, the row says so with **Open settings**.
* **Requests:** two or more waiting show the Requests list, grouped by site with its browser and **Reject all** per site. Repeat connect requests from one site merge into one row (for example 2 tabs). A review has a way back to the list; answering lands on the list, or closes the panel when nothing is left. A click outside closes the panel without answering.
* **Every review:** Reject and the verb (Connect, Send, Sign, Switch, Claim, Deploy), the full site address with its scheme, the page around it when the site is in a frame, **Via 0xio extension (Chrome)** or **(Firefox)**, the wallet, and the live network fee for anything that pays one. Touch ID or your password follows for anything that signs.
* **Everything signed is shown:** a contract call shows its exact arguments, a deploy its constructor params, and any memo in full. A contract sequence lists each step (contract, method, exact arguments and amount) with the OCT total. A connect review lists every permission the site gets, private ones included.
* **Refused before review:** a sign-only request for anything but a standard transfer, a message or memo over 4,000 characters (never cut short) and contract params that are not an array.
* **Locked:** the panel asks you to unlock first, then shows the review.
* **Networks:** each site keeps its own network. A site's network switch is its own review and moves only that site.
* **Limits:** at most 3 requests per site wait at once and a site is rate limited; a request beyond that gets an error at once. After you reject a review, that site's next requests are refused for 5 seconds without a review. A tab that closes or reloads takes its requests with it.
Sites connected this way are listed in **Settings > Security & Privacy**, **Connected sites**, per network (Mainnet or Devnet), each with the browser it came through and the wallet it uses. **Disconnect** asks once more in place. Removing the extension in **Paired apps** first says how many sites it forgets and lists them in the panel.
### Sites Straight from the Browser
A website can also reach the desktop wallet with no extension at all. When the site offers **0xio Desktop** and you press Connect, it opens a connection to the app on this computer; the browser names the site and the site cannot pretend to be another one.
* **Same reviews:** every connect, sign, send and network switch opens in the menu bar exactly like a site through the extension, with **Reject** and the verb, the live fee, then Touch ID or your password. The review shows **Connection: Direct from browser** where a relayed site shows the extension. A locked wallet asks you to unlock first.
* **What a site learns:** nothing about you until you approve its connect. After that it sees the wallet it uses, as with the extension.
* **Chrome asks first:** the first time a website reaches the app, Chrome (and Edge) shows a prompt that the site wants to access apps and services on this device (**Apps on device**). Allow it for sites you use; Block stops that site from reaching the app. You can change it later in the site's settings in Chrome.
* **Limits:** the same as through the extension (3 waiting requests per site, a rate limit), and a cap on how many pages can be connected at once. Closing a tab ends its waiting requests; the site stays connected and picks up again when you come back.
* **Turn it off:** **Settings > Security & Privacy**, **Websites can connect without the extension** (on by default). Off refuses websites at once and closes the open ones; sites through the extension keep working.
These sites are listed under **Connected sites** with **Direct from browser** and **Disconnect**.
### Lives in the Menu Bar
Closing the window (the red button, Cmd+W or Cmd+Q) keeps 0xio Desktop running in the menu bar without a Dock icon. The wallet, auto-lock and proof work carry on. Open the window again from the menu bar, Finder, Spotlight or the Dock. **Quit** in the menu bar menu or panel quits the app. **Open at login** starts it quietly in the menu bar.
***
## Core Wallet Features
### Dashboard & Balances
The home screen displays your public and private (FHE-encrypted) balances, recent transactions, and token holdings, the same as the extension and mobile app.
* **Token page**: click a token on Home. The price (6 decimals), its change, the range tabs and a chart are on the left; move the pointer over the chart to read the price at that time. The panel on the right holds the balance, Send and Receive, and the token's own activity on this network and address. OCT shows its public and encrypted amounts as two rows under one total, with Encrypt and Decrypt. A token with no price shows its activity on the left and its contract (with Copy) and decimals in the panel; a token you added can be removed there
### Sending & Receiving
* **Send screen**: the form on the left and a live panel on the right. Pick a recipient from the address book, your wallets or recent sends (each shows when you last sent), enter the amount with Max and the token, and add a message on public OCT. The panel shows both balances, the live network fee, who the recipient is to you (saved in, sent before, last send) and what is left after the send
* **Review in place**: Review send locks the form and turns the panel into the review card (amount, from, to, network fee, total, balance after, message) with **Reject** and **Send**. Reject returns to editing with every value kept; Send reads the fee again, asks for Touch ID or your password, then shows Signing and Broadcasting, and Sent with the hash, View in History and New send
* **Standard transfers**: Public on-chain transactions with memo support
* **Private transfers**: FHE-encrypted stealth transfers (recipient must have a registered PVAC public key). The proofs run in the shared proof queue with five steps and the elapsed time of each; a private send pays its fee from the public balance, so the total shows a private line and a public line
* **Bulk send**: rows in the same frame (add one, paste a list or import a CSV), totals in the panel, then one review listing every send in order and the progress in the same card. Duplicate addresses and a total over the balance are flagged. Private (FHE) bulk sends are available only in the desktop app and run as one job in the proof queue
* **QR codes**: Generate and share receive addresses
* **Exact token amounts**: token amounts in Send, bulk send and their reviews are exact whole units, never rounded
* **OU control**: press the network fee to pick Slow, Normal, Fast or a custom fee. Every preset comes from the node's live quote, and Fast shows only when the node quotes it. Private operations have floors (5,000 ou, claims 3,000 ou); see [Network Fees](/wallet/sending#network-fees)
* **Private send readiness**: a private send is offered only when the private balance is ready; otherwise the panel shows what comes first (turn on, refresh the key, migrate, a key update on hold) with its own review
### Privacy Operations
Full encrypt, decrypt and claim support, same as the extension. The Privacy screen has a composer on the left with **Encrypt**, **Decrypt** and **Claim** tabs and a live **Proofs** panel on the right. Each operation is reviewed in the composer's place with **Reject** and the verb, then Touch ID or your password; the job then joins the proof queue and the composer is free for the next one.
* **Proof queue**: one job at a time, in the order confirmed, so nonces and the balance's layers stay in order. The running job shows its step dots (encrypt has four steps; decrypt, claim and private send five) with the elapsed time of each step, then Up next and what finished today. A failed proof stays on top with its error, **Remove** and **Retry**, and the jobs behind it wait as Paused
* **Claim**: waiting transfers oldest first, each with its own Claim. Any of them can be claimed, one per press, because each claim adds two layers; the review shows the sender, the live fee, the private balance and the layers after
* **Turn on private balance**, **Refresh key** (at four or more of eight layers), **Migrate balance** and a **key update on hold** each show in the composer with the wallet, network and live fee. A changed fee, wallet or network is refused before any proof work
* **Proof rules**: private operations follow the proof rules the network states, read again before each submit. A network that does not state them gets no private operation, and a change in between sends nothing. See [Network Proof Rules](/wallet/privacy-operations#network-proof-rules)
**1. Encrypt (Public → Private)**
Shield funds using FHE. Your device generates a Pedersen commitment and zero-knowledge bound proof locally.
**2. Decrypt (Private → Public)**
Unshield funds back to your public balance.
**3. Claim**
Scan the blockchain for incoming private transfers and decrypt them into your wallet.
### Multi-Wallet Support
Create or import multiple wallets using 12/24-word seed phrases. All wallets use the same master root key derivation (`HMAC-SHA512("Octra seed", seed)[0:32]`) as the extension and mobile app, so importing the same seed phrase produces the same address across all 0xio platforms.
***
## NFTs & Marketplace
### NFT Gallery
* Every collection the wallet holds shows without an import step, including collections on the 0xio NFT list that the node does not list. **Import** stays for any other contract.
* The newest piece leads, then one shelf per collection, newest first.
* Hide a collection or a single piece, with Undo for four seconds. Hidden ones wait on the **Hidden** shelf, kept per network and wallet.
* A piece opens as a page with its status, who holds it and two actions.
### Living NFTs
Living NFTs are on-chain programs that draw their own art. The detail view runs the program in an isolated frame with no access to the wallet. A program that reads the chain may reach only its own network's RPC; gallery thumbnails stay offline. See [NFT Standards](/wallet/nft-standards#living-nfts-euint-labs-geodesics-and-chain-state-collections).
### Xpectrum Marketplace
List and manage your NFTs on the Xpectrum marketplace from the wallet:
* **List for sale**: two-signature listing flow with on-chain verification
* **Change price**: cancels, approves again when needed and lists again, with every signature shown on the review first
* **Cancel listing**: delist an NFT at any time
***
## Companion Mode: Accelerating the Extension
The desktop app runs a **local WebSocket server** on `ws://127.0.0.1:19345` that the browser extension connects to automatically when both are running.
### How It Works
1. The extension detects the desktop app on the local machine, and both prove the pairing secret to each other (see Pairing with the Extension)
2. Only then the extension sends the PVAC master seed, a one-way hash of the wallet key, and the desktop derives the same PVAC keys from it. The key that signs transactions never leaves the extension
3. For heavy FHE operations, the extension delegates computation to the desktop app
4. Desktop computes using native Rust with all CPU cores, returns the result
5. Extension uses the result as if it computed locally
### What Gets Accelerated
Range proofs, bound proofs, commitments and PVAC key work for the extension's encrypt, decrypt, private send and claim all run natively in the desktop app.
### Security
* **Loopback only**: The WebSocket server binds to `127.0.0.1`, so no remote connections are accepted
* **Paired clients only**: The extension and local programs pair once, and the extension proves its pairing on every connection before any key material moves. A website that connects straight from the browser can only send site requests for its own address, each reviewed in the menu bar; it never reaches proofs, keys or pairing
* **Connection limit**: Maximum 8 concurrent connections
* **Message size cap**: 16 MiB per message for the extension and paired programs (range proofs can be 1 to 3MB), 1 MiB for web pages
* **Handshake timeout**: a connection that does not finish its handshake within 5 seconds is closed, and only finished handshakes count toward the connection limit
* **Idle timeout**: 300 seconds
* **No signing key**: The desktop can build proofs and read the private balance, but it cannot sign, because it never receives the wallet key (extension 2.5.6 and later)
* **Memory safety**: The master seed is zeroized after use on both sides
### Status Indicators
* **Extension**: the extension's status bar shows a **0xio Desktop** chip while the desktop app runs its proofs
* **Menu bar icon**: the slash turns while a private proof runs
* **Engine card**: the menu bar names the extension only for the extension's own proofs
***
## AI Contract Assistant
The desktop app includes an AI-powered assistant for smart contract development:
* **AML brainstorming**: describe what you want your contract to do and get AML code suggestions
* **Code generation**: generate complete contract templates from natural language descriptions
* **Error fixing**: paste compiler errors and get suggested fixes
***
## Contract Developer Tools
The desktop app includes a full smart contract development suite: compile, deploy, verify, and interact with contracts directly.
### Write & Compile
Write contracts in two languages:
| Language | Extension | Description |
| - | - | - |
| **AppliedML** | `.aml` | High-level language with automatic ABI generation. Recommended for most developers. |
| **Assembly** | `.oasm` | Low-level bytecodes for advanced users. |
* **File import**: Load `.aml` or `.oasm` source files
* **Compile**: Sends source to the network compiler, returns bytecode + ABI
* **Error reporting**: Line numbers and error messages from the compiler
### Deploy
* **Address preview**: Predict your contract's address before deploying (based on sender + nonce)
* **Constructor parameters**: JSON array input for initialization values
* **Deploy fee**: set by the size of the code, the way the Octra reference client sets it: 200,000 OU plus 1,000 OU per started KiB. A site may set its own deploy fee up to 10,000,000 OU
* **Source verification**: Verify deployed bytecode matches your source on-chain
### Interact
* **ABI-driven forms**: Load a contract's ABI to get typed input fields for each method
* **View vs Call**: Read-only queries (free) or state-changing transactions (costs OU)
* **FHE parameter encryption**: Use `enc(42)` syntax to encrypt integer parameters with FHE before sending
* **Auto-decrypt results**: Long return values are automatically tested for FHE decryption
### Contract Registry
Keep a local registry of your deployed contracts with name, address, language, and ABI for quick access.
***
## Built-in DApp Browser
Browse Octra ecosystem dApps without leaving the desktop wallet:
* **DApp directory**: Quick links to ecosystem sites
* **URL navigation**: Visit any URL directly
* **Open in window**: Launch dApps in a native window for sites that don't support iframe embedding
* **Connected DApp management**: View and manage permissions for connected dApps
***
## Networks
The desktop app supports switching between Octra networks:
| Network | Privacy (FHE) | Explorer |
| - | :-: | - |
| Mainnet | Yes | [octrascan.io](https://octrascan.io) |
| Devnet | Yes | [devnet.octrascan.io](https://devnet.octrascan.io) |
RPC endpoints are configured automatically by the app (mainnet connects directly to `octra.network`). An **RPC proxy toggle** in Settings routes mainnet and devnet requests through the 0xio proxy. It is off by default, it is never used as a silent fallback, and a network you add yourself never uses it.
Switch networks in **Settings > Network**, which also shows the latest epoch and how fast each network answers. Devnet is recommended for testing FHE operations and smart contract development.
***
## Security
* **Encrypted vault**: All private keys encrypted with AES-256-GCM at rest
* **Keys always need a password**: a wallet with keys is never stored without one. A vault that holds only watch addresses can stay without a password; adding a wallet with keys asks you to create one first, and an older vault with keys and no password asks for one at unlock
* **Crash-safe saves**: the vault is written to a new file and swapped in only once it is complete, and the previous copy is kept as a backup the app opens if the vault cannot be read. A file the app cannot read is never overwritten
* **PBKDF2 key derivation**: 600,000 iterations for password-to-key conversion
* **Biometric unlock**: Touch ID (macOS) / Windows Hello, with a password fallback. On macOS only a fingerprint opens it, never the Mac login password: the prompt's one button is **Cancel** on the lock screen, or **Use password** where a password field is waiting. If you add or remove a fingerprint, Touch ID turns off and **Settings > Security & Privacy** says so; turn it on again there. The password copy an older version kept for Touch ID is deleted
* **Auto-lock**: Configurable inactivity timeout
* **Recovery phrase and private key**: One screen shows either, opened as **Recovery phrase** or **Private key** from **Settings > Security & Privacy**, from **Wallets**, or from **Back up** on Home. It asks for the password or Touch ID first in a small sheet (Touch ID first when it is on; **Cancel** goes back). **Show** keeps the words or the key on screen until **Hide**; they are also hidden when you leave the screen, switch to another window, hide or minimise the app or it locks, and showing them again asks for the password or Touch ID. A first backup ends with **I wrote them down** and Home stops asking; a wallet you imported, by phrase or by private key, starts as backed up and closes with **Done**
* **Password guess limit**: Wrong passwords count against one limit everywhere a password is asked: unlock, showing a phrase or key, deleting a wallet, creating or deriving a wallet, and changing or removing the password. After five misses the app waits 30 seconds, doubling up to an hour
* **Address and public key**: Shown in the wallet's details in **Wallets**, each with **Copy**
* **Copy without leaks**: **Copy** on a recovery phrase or private key stays on this computer (no Universal Clipboard, no Windows clipboard history or cloud sync), is hidden from clipboard managers, and is cleared after 60 seconds unless you copied something else. A phrase or key you paste into import is cleared from the clipboard too
* **Deleting a wallet**: Asks for the password first
* **Input validation**: Address format, amount bounds, and JSON parsing checks
* **Sandboxed browser**: Restricted iframe permissions, blocks `javascript:`, `data:`, and `file:` schemes
* **Isolated frames**: Living NFTs and `oct://` pages load from a listener inside the app, bound to this computer only (`127.0.0.1`, a random port, a random 256-bit path per document), each with a security policy of its own and an opaque origin. The app treats them as remote content and its permission system refuses every command from them, so untrusted content cannot reach the wallet or other sites. Locking the wallet drops every staged document. A seed-only living NFT program gets no network access at all, and one that reads the chain may reach only its own network's RPC
* **dApp windows**: A dApp opened in its own window can only send wallet requests, which go through the same approvals as the in-app browser. It cannot reach any other part of the app
* **Menu bar approvals**: Every send, encrypt, decrypt, migration and key update from the menu bar signs only on the wallet and network its review showed, at the live network fee the review showed. A fee that cannot be read blocks the action with a retry; no default fee is ever signed
* **Sentry crash reporting**: Automated error reporting for stability improvements
***
## Debug Console
The desktop app includes a built-in debug console for developers and advanced users:
* **RPC logs**: View raw RPC requests and responses
* **PVAC timing**: Monitor FHE operation performance (proof generation, key decompression)
* **Network diagnostics**: Check connectivity, latency, and endpoint status
***
## Auto-Updater
The desktop app checks [0xio.xyz](https://0xio.xyz/install) for updates automatically and prompts you to install new versions when available. Update artifacts are **minisign-verified** against a bundled public key before installation.
***
## Navigation & Productivity
* **Command palette**: A keyboard-driven launcher to jump between screens and actions.
* **Sidebar wallet switcher**: Switch between your wallets directly from the sidebar.
* **Collapsible sidebar**: Collapse the sidebar to maximize screen real estate when working with contracts or browsing dApps.
* **Octra Circles browser**: Browse native `oct://` on-chain content via the Circle bridge. Files stored in a circle as chunks are put back together and checked against their hashes before they show, in the browser and NFT art (see [Chunked Circle Files](/wallet/nft-standards#chunked-circle-files)). The Circles page lists the circles the wallet deployed on the current network with the files it published to each (the wallet's own record: the network has no listing call). Several files can be added at once, each with an editable path, then one review lists every file with its transaction and fee and the files go out one transaction at a time: public files up to 2 MiB, encrypted files up to 32 MiB, and an encrypted file is listed with a keyed digest, not a plain hash of its content. A deploy or upload is shown as done only once the chain confirms it; one that is not confirmed can only be checked again, never sent twice. The read passphrase of an encrypted circle is never stored, so the page asks for it again before adding files, and only the owner wallet can open an encrypted circle's content.
***
## Getting Started
### Installation
1. Download the latest build from [0xio.xyz/install](https://0xio.xyz/install)
2. Follow the platform-specific installer instructions
3. Open the app. It also appears in your menu bar (macOS) or system tray
### Creating a Wallet
1. Set a **master password** to encrypt your vault. It cannot be skipped for a wallet with keys; watching an address needs none
2. Choose **Create New Wallet** or **Import** with an existing seed phrase
3. Your wallet address and balances will appear on the dashboard
### Pairing with the Extension
Pair once, with both the desktop app and the browser extension running:
1. In the extension, tap the desktop status or open **Settings**, **Connections**, **0xio Desktop** and tap **Pair**
2. 0xio Desktop shows a 6 digit code. The extension shows three codes: pick the one that matches
3. Approve the request in the desktop app. If you press Pair on the desktop before you pick in the extension, it shows Waiting until you do
4. The extension shows **Paired**, a **0xio Desktop** chip appears in its status bar, and private proofs now run in the desktop app
Nothing is saved or shared until you pick the right code and the desktop approves. A wrong pick stops the pairing (**Wrong code. Pairing stopped**), and you start again with **Try again**. Pairing also stops when the desktop refuses it, after two minutes or when the connection closes, and you can cancel it at any time. The desktop shows one pairing review at a time; the next one opens 3 seconds after the last closes.
Paired apps are listed in the desktop under **Settings > Security & Privacy**, **Paired apps**, each with when it was last used and **Remove**. The choices below sit in the **Connections** group on the same tab, each takes effect at once, and the panel on the right shows how each one is set.
* **Forget unused pairings after**: 7, 30 or 90 days, or Never (90 days by default). A pairing not used for that long is removed; the extension then shows Not paired with Pair
* **Websites can connect without the extension**: on by default. Sites you approve still need a review in the menu bar. Off refuses websites and closes the open ones at once
* **Notify me about site requests**: on by default. A site request that waits while the menu bar panel is closed shows a macOS notification
* **Allow older extensions without pairing**: on by default. The 0xio extension 3.0.3 and older cannot pair, so while this is on they can still run their own private proofs in the desktop app, and nothing else. Turn it off to refuse them; they then run proofs in the browser. Extension 3.0.4 and later always pair, and if the desktop app is too old to pair the extension asks you to update it and runs proofs in the browser
> [!TIP]
>
> Keep the desktop app running while you use the browser extension; it can stay in the menu bar with the window closed.
# 0xio DEX
Source: https://docs.0xio.xyz/products/dex
Cross-chain swap protocol for Octra Network: swap OCT and ETH with on-chain settlement and solver-based fills.
> [!NOTE]
>
> **Status: Live on Devnet**
> The 0xio DEX is live at [dex.0xio.xyz](https://dex.0xio.xyz). Cross-chain swaps between OCT and ETH are available on devnet.
## Overview
**0xio DEX** is a **cross-chain swap protocol** that enables users to swap native tokens across chains, starting with **OCT ↔ ETH** between Octra and Ethereum Sepolia. The protocol uses a **solver-based architecture** with on-chain escrow and oracle verification.
## How It Works
### OCT → ETH
**1. Connect Wallets**
Connect your 0xio wallet (Octra) and MetaMask (Sepolia)
**2. Get Quote**
Enter the OCT amount and the solver provides a real-time quote with TWAP pricing
**3. Lock OCT**
OCT is locked in the SwapVault contract on Octra with a minimum receive commitment
**4. Solver Fills**
The solver detects the lock, sends ETH to your Sepolia address (\~30s)
**5. Oracle Attests**
The oracle independently verifies the ETH was sent and attests on-chain
**6. Settlement**
After a challenge window, the solver claims the locked OCT
### ETH → OCT
**1. Connect Wallets**
Connect MetaMask (Sepolia) and 0xio wallet (Octra)
**2. Lock ETH**
ETH is locked in the EthEscrow contract on Sepolia
**3. Solver Sends OCT**
The solver detects the lock and sends OCT to your Octra address
**4. Solver Records the Fill**
The solver calls `fill()` on the escrow with the Octra transaction hash
**5. Oracle Attests + Settlement**
The oracle verifies the OCT payout and `attest()` is recorded; after the challenge window the solver settles and receives the locked ETH
> [!NOTE]
>
> You are paid first: the solver sends OCT before it claims anything on Ethereum. If the solver never pays, the ETH stays in the escrow and you refund it after the lock expiry. The timing risk sits with the solver: a lock that expires before `fill()` is recorded can no longer be claimed.
## Features
* **Cross-Chain Swaps**: OCT ↔ ETH between Octra and Ethereum Sepolia
* **TWAP Pricing**: 5-minute time-weighted average of the 0xio Oracle's OCT price (the live Uniswap pool price, cross-checked against CoinGecko and DexScreener)
* **On-Chain Rate Protection**: `min_receive` committed at lock time, so the solver can't underpay
* **Slippage Control**: Configurable slippage tolerance (0.1%, 0.5%, 1%, or a custom value up to 5%)
* **Liquidity Display**: Real-time solver balance shown before swap
* **Network Detection**: Auto-prompts MetaMask to switch to Sepolia
* **Live swap tracking**: Every swap shows three steps (sent from your wallet, on the way, arrived) with a timer, survives a page reload, and offers a one-click refund when a swap can't be filled
* **Activity**: Live-updating transaction list with on-chain state at [dex.0xio.xyz/history](https://dex.0xio.xyz/history)
* **DEX Stats**: Volume, fees, solver status at [dex.0xio.xyz/stats](https://dex.0xio.xyz/stats)
* **Price Chart**: Live OCT/USD chart next to the swap card (a compact price strip on phones)
## Security
The protocol is built around a layered defense model: at no point can the solver underpay or the user lose locked funds.
### 5-Layer Defense
| Layer | Protection |
| - | - |
| **Solver Bond** | 20K OCT staked as collateral, slashed on fraud |
| **On-Chain Rate Commitment** | `min_receive` prevents solver from underpaying |
| **Oracle Verification** | Independent fill verification before attestation |
| **Challenge Window** | Dispute period after attestation, before the solver can settle |
| **Stale Fill Refund** | User reclaims funds if oracle doesn't attest within timeout |
### Key Security Properties
* **Pay first, claim later**: The solver delivers the destination tokens before it records `fill()`, and claims the locked funds only after the oracle attests and the challenge window passes
* **Idempotent payouts**: Every solver transaction is stored before broadcast and retried with the same bytes, so a crash or timeout never pays twice; an unclear payout stops for manual review instead of being resent
* **Fee snapshot**: Fee rate locked at swap time, can't be changed retroactively
* **Reentrancy guard**: All ETH transfer functions protected
* **Emergency refund**: Owner can refund unfilled swaps (blocked after fill)
* **Reconciliation**: The solver periodically reconciles its database with on-chain state and only ever moves a swap forward
## Contract Addresses (Devnet)
| Contract | Network | Address |
| - | - | - |
| SwapVault v7 | Octra Devnet | `octHqBuw3TGsgeGpu6GiumRTjQwc8VCRAo1ANhy5zL4ZVPc` |
| EthEscrowV2 | Sepolia | `0x835454daE518108105BC2ac22cb11738A5D69554` |
| Solver Wallet | Octra | `oct4Ay2HKoKbVSndUVdDaf1gxtYhdXeRVpkHvv43rhRoxio` |
| Solver Wallet | Sepolia | `0x3ec4bd68e2E3190FB3050005D8B9C132aFd9D684` |
| Treasury | Octra | `octCWWvj4EKPk1QA9TZrNTXZ6dJEbcQaS3fWAcmobPnyuqt` |
| Treasury | Sepolia | `0x80961b1e16D483c7EB5CA32e1fFd8157E5188598` |
## Architecture
```
User (0xio Wallet + MetaMask)
│
▼
Frontend (dex.0xio.xyz)
│
├── Quote: Solver API (solver.0xio.xyz)
│ └── TWAP pricing from Oracle (oracle.0xio.xyz)
│
├── OCT→ETH: SwapVault v7 (Octra) → Solver sends ETH (Sepolia)
│
└── ETH→OCT: EthEscrowV2 (Sepolia) → Solver sends OCT (Octra)
```
### Swap Lifecycle (OCT → ETH)
```
lock() → fill() → attest() → [challenge window] → settle()
│ │ │ │
│ │ │ └── Solver gets OCT
│ │ └── Oracle verifies ETH was sent
│ └── Solver sends ETH + posts dest tx hash
└── User locks OCT with min_receive commitment
```
### Swap Lifecycle (ETH → OCT)
```
lock() → send OCT → fill() → attest() → [challenge window] → settle()
│ │ │ │ │
│ │ │ │ └── Solver gets ETH
│ │ │ └── Oracle verifies the OCT payout
│ │ └── Solver records the Octra tx hash on the escrow
│ └── Solver pays OCT to the user first
└── User locks ETH in EthEscrowV2
```
## Fee Structure
| Parameter | Value |
| - | - |
| Swap fee | 0.5% (50 bps) |
| Fee destination | Protocol treasury (per-chain) |
| Minimum swap (OCT→ETH) | 10 OCT (the vault's minimum fee makes smaller locks fail) |
| Minimum swap (ETH→OCT) | 0.001 ETH |
| Maximum swap | 10,000 OCT / 10 ETH |
## Services
| Service | URL | Role |
| - | - | - |
| DEX Frontend | [dex.0xio.xyz](https://dex.0xio.xyz) | Swap interface |
| Activity | [dex.0xio.xyz/history](https://dex.0xio.xyz/history) | Transaction list + on-chain state |
| DEX Stats | [dex.0xio.xyz/stats](https://dex.0xio.xyz/stats) | Volume, fees, solver status |
| Solver API | solver.0xio.xyz | Quote, swap tracking, health |
| Oracle | oracle.0xio.xyz | TWAP prices, fill verification |
## Solver API
### GET /api/quote
Get a swap quote with current TWAP pricing.
```
GET https://solver.0xio.xyz/api/quote?from=OCT&to=ETH&amount=100
```
Response includes: rate, receive amount, fee, liquidity availability, intent hash.
### GET /api/swap/:id
One swap by id: the quote `intent_hash` for OCT→ETH, or `escrow_` plus the lowercase keccak256 of the intent hash for ETH→OCT. Returns 404 when the solver has not seen it yet.
### GET /api/swaps
List recent swaps with status tracking. `?address=` filters by source or destination address (case-insensitive).
### GET /api/chains
Supported chains/tokens, the live contract addresses (SwapVault, EthEscrow), solver wallets and the lock limits (`min_lock_oct`, `min_lock_eth`). The DEX frontend locks only into its pinned contracts and pauses swaps if this endpoint reports different ones.
### GET /api/health
Solver status: RPC connectivity, bond amount, liquidity on both chains.
### GET /api/price
Proxied oracle price feed (no API key needed from frontend).
## Refund Mechanisms
| Scenario | Mechanism |
| - | - |
| Solver never fills | User taps Refund on the swap card or the Activity page once the lock expiry passes (`refund()`) |
| Solver fills but oracle never attests | Same Refund button after the stale-fill window (`refundStaleFill()` / `refund_stale_fill`) |
| Emergency | Owner calls `emergencyRefund()` (immediate, unfilled swaps only) |
## Related Repos
| Repo | Role |
| - | - |
| **0xio-solver** | Rust solver backend (Axum + Tokio) |
| **0xio-oracle** | Price feeds (TWAP) + fill verification |
| **@0xio/sdk** | Octra wallet SDK for DApp integration |
## Roadmap
### Near Term
* Solana support (SOL ↔ OCT)
* Multi-token support (wOCT, USDC)
* Multi-solver competition
* Mainnet deployment
### Medium Term
* Liquidity pools (CLMM)
* Limit orders
* Advanced routing (multi-hop, split orders)
### Long Term: Cross-Chain Privacy Layer
* **Accept wETH/wSOL on their native chains, encrypt via Octra's FHE, transfer privately, withdraw anywhere**
* Octra becomes a privacy middleware: users put in wETH, get out wETH, with full amount privacy in between
* FHE-powered private swaps (AMM operating on encrypted data)
* Relayer network for gasless, unlinkable withdrawals
* Compliance via selective disclosure (viewing keys)
> [card] [Cross-Chain Privacy Layer: Full Design](/concepts/privacy-layer)
>
> Deep dive into how 0xio uses Octra's FHE as a privacy backend for any blockchain. Architecture, phases, and comparison with Umbra, Railgun, and Aztec.
# Browser Extension
Source: https://docs.0xio.xyz/products/extensions
The 0xio browser extension: an Octra wallet with private balances and an EVM wallet, a built-in bridge, NFTs and site connections, for Chrome, Brave, Edge, Opera and Firefox.
The **0xio Browser Extension** is a non-custodial wallet for the Octra Network with an EVM wallet built in. It keeps public and private (FHE-encrypted) OCT balances, holds a 0x address on Ethereum, Robinhood Chain and any EVM network you add, bridges between Octra and Ethereum, and connects to Octra and EVM websites.
> [!NOTE]
>
> **Status: Live (v3.0.4) on the Chrome Web Store.** Install from the [Chrome Web Store](https://chromewebstore.google.com/detail/0xio-wallet/anknhjilldkeelailocijnfibefmepcc) or from [0xio.xyz/install](https://0xio.xyz/install). The Firefox version is in review on Mozilla Add-ons.
## Requirements
* Chromium browsers (Chrome, Brave, Edge, Opera) version 111 or later
* Firefox 140 or later (142 on Android)
* Manifest V3
## Getting Started
The welcome screen offers **Create a wallet** and **I already have a wallet**. The Terms of Service and Privacy Policy are linked below them.
* **Create a wallet:** create a password, confirm it on the next screen, optionally add a passkey, choose your networks and land on **Your wallet is ready**, which lists your Octra and EVM addresses. You back up the recovery phrase afterwards, from the **Back up** tag on Home.
* **I already have a wallet:** paste a recovery phrase (12 or 24 words), a private key or an address into one field. The extension tells which it is, offers a one-tap fix for a mistyped word, and shows the wallet it found with its balance. An address adds a watch-only wallet.
The password needs at least 8 characters and unlocks the extension in this browser. See [Installation & Setup](/wallet/installation) for each step.
### Adding More Wallets
In the wallet list, **Add wallet** offers:
* **Create wallet:** a new wallet and recovery phrase, saved at once.
* **Add account:** another address from one of your wallets' recovery phrases. It gets its own Octra and 0x address.
* **Import wallet:** a phrase, a private key or an address (watch-only).
The wallet list shows where each wallet comes from (Phrase, Private key or Watch-only) and both of its addresses, each with a copy button. Switching wallets says which wallet it switched to.
***
## Home

Home in the extension, amounts hidden
* **Header:** your wallet's picture and name (click for the wallet list), the network switch (**All networks**, **Octra** or **EVM**) and Settings.
* **Balance:** the total in USD over a chart of the day (green up, red down, grey when flat). Tags at the top right of the balance card ask for action, most urgent first, for example **Back up** or **Set up private**.
* **Actions:** **Send**, then **Receive**, **Bridge**, **Privacy** and **History**. On EVM, Privacy stays visible but disabled.
* **Assets:** OCT, the encrypted (private) OCT row, Octra tokens and EVM assets, each with its USD value. Tap one for its page with a 1D, 7D or 1M price chart, the balance, actions and recent activity.
* **Status bar:** the open site's connection chip, a **0xio Desktop** chip when the desktop app is running private proofs, and the **Mainnet** or **Testnet** chip.
Balances that are not read yet show `--`, never 0. Home reads balances lightly every 30 seconds while open and not at all while hidden or locked.
See [Wallet Interface](/wallet/interface) for a full tour.
***
## Sending
* **Octra:** public OCT, Octra tokens and private sends. **Bulk** sends OCT to up to 5 recipients from the public balance.
* **EVM:** ETH and tokens on any EVM network that is on. See [EVM Networks](/wallet/evm#sending).
* **Fees:** one live network fee per send, read from the node. It drives the shown fee, **Max**, every balance check and the signature. You can pick another tier or a custom fee up to 10,000,000 OU, never below the operation's floor or the network's minimum. **Fast** shows only when the node quotes it. If the fee cannot be read, Review is blocked with **Network fee unavailable**. On EVM networks the fee shows in USD first, with the native amount beside it. See [Network Fees](/wallet/sending#network-fees).
* **Review:** every send opens a review with **Back** and **Send**. After Send, a result screen shows **Sending**, then **Sent** once the network confirms it (**Queued** for private sends). A busy node gets the same signed transaction again, a used nonce asks for a new review, and nothing is ever re-signed on its own. See [What Happens After You Send](/wallet/sending#what-happens-after-you-send).
See [Sending Transactions](/wallet/sending).
***
## Privacy
The **Privacy** screen moves OCT between your public and private balances and claims private transfers sent to you:
* **Encrypt** (public to private), **Decrypt** (private to public) and **Claim**, each with a review and the live network fee.
* **Set up private:** a wallet with no encryption key on chain shows a **Set up private** tag on Home. Its sheet shows the one-time network fee, and **Turn on** registers the key.
* **Private on hold:** when the network holds a private balance (for example an older format waiting for a migration), Home shows a tag and the Encrypted row reads **On hold**. The sheet shows the node's reason.
* **Proof rules:** before any private action, and again right before signing, the extension reads the proof rules the network states. When the network states none or cannot be reached, private actions pause and nothing is sent. See [Network Proof Rules](/wallet/privacy-operations#network-proof-rules).
All proofs run on your device. With [0xio Desktop](/products/desktop) running and paired, they run in the desktop app instead, which is faster. See [Privacy Operations](/wallet/privacy-operations).
***
## EVM Wallet
Every wallet made from a recovery phrase also has a 0x address. The extension shows ETH and tokens, sends, receives, shows EVM NFTs and lists EVM history next to Octra. Networks can be turned on or off, given your own RPC, or added from a list of popular chains or the public chain list. See [EVM Networks](/wallet/evm).
***
## Bridge
**Bridge** on Home bridges between Octra and Ethereum without leaving the wallet:
* **Bridge to Ethereum:** OCT on Octra becomes wOCT on Ethereum, claimed with the wallet's 0x address.
* **Bridge to Octra:** wOCT on Ethereum becomes OCT on Octra.
* **Any chain to OCT:** pay with a token on another chain and receive OCT on Octra.
* **Claim:** a page for transfers to Ethereum, grouped as Ready, In progress and Completed, with **Find my transfers** and **Add by hash**.
Each step has its own review, and the Octra network fee is read live. See [0xio Bridge](/products/bridge#bridge-inside-the-extension).
***
## History
History lists Octra and EVM activity together, newest first, on two pages: **Mainnet** and **Testnet**. Contract calls read as actions (Swap, Approve, Bridge or the method in words) with the contract named. Every row opens a details card, and contract calls have a **View call data** page. See [Wallet Interface](/wallet/interface#history).
***
## NFTs
* Every collection the wallet holds shows without an import step; **Import** stays for other contracts.
* The three newest pieces come first, then one row per collection, then **Hidden**.
* Hide a piece or a whole collection with Undo. Hidden items are kept per network and wallet.
* A piece's page shows its art, traits, status, holder and contract, with **List for sale** and **Send**, or **Change price** and **Cancel listing** for a listed piece. Every step opens a review with the fee.
* Octra collections (SNS-1, Bionts and living NFTs) and EVM collections (ERC-721 and ERC-1155) share the list. See [NFT Standards](/wallet/nft-standards).
* Art stored in a circle as chunks is put back together and checked against its hashes before it shows. See [Chunked Circle Files](/wallet/nft-standards#chunked-circle-files).
***
## Connecting to Sites
The extension connects to Octra sites through the 0xio SDK and to EVM sites, which list it as **0xio Wallet**. EVM sites can be connected to several of your wallets, and a site only ever sees the wallet in use. The status bar shows the open site's connection, and **Settings > Connections > Connected sites** lists them all. See [Connecting to Sites](/wallet/connecting-sites).
***
## Settings
Settings opens on your wallet, then:
* **Security:** password, passkey unlock, auto-lock time, and Reset wallet under This browser.
* **Backup:** recovery phrase and private key (the EVM key too while EVM is on). Shows **Back up again** when a wallet needs a new backup. A wallet with no password creates one first, then goes on to what was asked for.
* **Connections:** connected sites and the address book.
* **Appearance:** theme (Dim, Day or Octra) and language (English, Chinese, Japanese, Korean or Indonesian).
* **Networks:** Octra (Mainnet, Devnet or a node you add, and the optional RPC proxy), then the EVM networks.
* **Tokens and NFTs:** a switch for every token and collection, shown or hidden as likely spam. Imported tokens carry an **Imported** tag.
* **EVM:** turn EVM on or off for the open wallet.
**Lock wallet** sits at the bottom, with links to these docs, the Terms and the [Privacy Policy](https://0xio.xyz/privacy).
***
## Locking
* The toolbar icon shows a padlock while the wallet is locked.
* Unlock with your password, or tap **Use passkey** where a passkey is set up. The passkey prompt never opens by itself.
* Locking shuts down the private balance engine and clears decrypted private balances and wallet data held in memory.
***
## Notices
Messages appear as one small pill above the status bar. Information fades after 4 seconds, errors and warnings after 6 seconds. Hovering pauses it. Copy buttons show **Copied** in place, and successful actions show in the screen itself rather than as a notice.
***
## RFC-O-1 Provider
The extension implements the **RFC-O-1** wallet provider standard and exposes `window.octra`:
```javascript theme={null}
const accounts = await window.octra.request({ method: 'octra_requestAccounts' });
const balance = await window.octra.request({ method: 'octra_getEncryptedBalance' });
window.octra.on('accountsChanged', (accounts) => { /* ... */ });
window.octra.on('connect', (info) => { /* ... */ });
```
The `@0xio/sdk` includes an `OctraProviderAdapter` that uses `window.octra` as a transport. For EVM sites the extension is a standard EIP-1193 provider, see [EVM Provider](/developers/sdk-guide#evm-provider).
***
## Networks
| Network | Privacy (FHE) | Explorer |
| - | :-: | - |
| Octra Mainnet | Yes | [octrascan.io](https://octrascan.io) |
| Octra Devnet | Yes | [devnet.octrascan.io](https://devnet.octrascan.io) |
| Your own Octra node | If you mark it as running privacy | The explorer you set, if any |
| EVM networks | No | Each network's explorer |
Octra Devnet is a test network with full FHE support. It sits on the **Testnet** side together with the EVM testnets.
***
## Desktop Companion
When [0xio Desktop](/products/desktop) runs on the same computer, the extension connects to it and runs private proofs there, which is faster than in the browser.
* **Pair once:** tap the desktop status or open **Settings**, **Connections**, **0xio Desktop** and tap **Pair**. 0xio Desktop shows a code, the extension shows three: pick the one that matches, then approve on the desktop. Nothing is saved or shared until the right code is picked and the desktop approves; a wrong pick stops the pairing. See [Pairing with the Extension](/products/desktop#pairing-with-the-extension).
* **Status:** the status bar shows a **0xio Desktop** chip, which tells whether proofs run in the desktop app or in this browser.
* **No signing key:** the desktop app builds proofs but never receives the key that signs your transactions.
# Mobile Wallet
Source: https://docs.0xio.xyz/products/mobile
The 0xio Mobile Wallet for iOS and Android with biometric unlock, PIN security, and the same FHE privacy features as the browser extension.
> [!NOTE]
>
> **Status: Live (v1.3.3).** Install from [0xio.xyz/install](https://0xio.xyz/install). v1.3.4 is in progress.
>
> * **iOS**: on TestFlight
> * **Android**: open testing on Google Play
The **0xio Mobile App** is the 0xio Octra wallet for iOS and Android. Built with React Native 0.86 and Expo SDK 57, it runs the same pvac-rs privacy engine as the other clients, natively on the phone.
## Mobile-Exclusive Features
### PIN & Biometric Security
The mobile app keeps your keys in the device's secure storage and unlocks with a PIN or biometrics.
* **PIN Code:** Set a 6-digit PIN with rate-limited retry protection.
* **Stays locked:** once the wallet locks (auto-lock, **Lock now** or too many wrong PINs), closing and reopening the app keeps it locked until you unlock it.
* **FaceID / TouchID:** Log in and sign transactions using your unique biometric signature.
* **Every transaction is confirmed:** each send, approval, signature and connection ends on a review you confirm with a slide, whether or not a PIN or biometrics are set. Tap outside the review to back out. With **Ask before sending** on (Settings, Security), the PIN or biometric check follows the slide, and turning it off asks for it first. No address skips this check.
* **Secure Storage:** Your encrypted keys are protected by the device's hardware-backed Keychain/Keystore.
### Push Notifications
Stay informed with real-time alerts for incoming transactions, claim-ready private transfers, and network events.
### DApp Browser
Browse and interact with Octra dApps directly from the mobile wallet without leaving the app. The start page lists the ecosystem's sites by category, with your recent sites below.
One bar at the top of the start page takes anything you type:
* **An app name or words:** matching apps, your recent sites, then a web search.
* **An Octra address** (or a payment request): Send to this address, Open in Octrascan, or Save as contact.
* **A transaction hash:** your matching transaction, and Open in Octrascan.
* **An `oct://` address:** a circle hosted on Octra, read from the chain. Public circle pages load with their images, scripts and stylesheets, and files stored as chunks are put back together and checked against their hashes first (see [Chunked Circle Files](/wallet/nft-standards#chunked-circle-files)). Sealed circles ask for their passphrase.
* **Anything else:** opened as a site.
Scan sits beside the bar. While you browse, the address sits at the bottom within reach of your thumb. A lock shows only on https pages, plain http is marked not secure, and a green dot means the site is connected to your wallet. Only https sites can reach the wallet; a plain http page can be read but cannot connect. The **...** menu next to the address reloads the page, shares its link, opens it in your phone's browser, switches the network (after asking) and disconnects the site.
Every request from a site opens in its own sheet naming the site and the wallet it acts for, one request at a time:
* **Connect** lists what the site can see (your address and balance), what it can ask (signatures you review), any private permissions, and that it can never move funds on its own. Tap **Change** to connect another wallet. Slide to connect.
* **Sign a message** shows the whole message. Signing moves no funds and costs no fee. Slide to sign.
* **A transaction** shows a receipt: what it does, the amount with its USD value, the recipient, the fee, the network and your wallet. A contract call shows its arguments exactly as they are signed. Slide to approve.

Connecting a site in the mobile app
With **Ask for PIN before signing** on, Face ID or the PIN follows the slide. A request to sign a transaction without sending it says the site can send what you sign, a transaction the site signed earlier is only sent after you approve it, and sites cannot submit through raw RPC calls. If you switch wallet or network before a request is signed, nothing is signed or sent. A site connection lasts 24 hours.
### Browser Settings
Tap the gear on the Browse start page:
* **Connected sites** for this wallet and network.
* **Ask for PIN before signing** (on by default). Turning it off asks for your PIN or biometrics.
* **Search with** DuckDuckGo, Brave or Google.
* **Outside links open in** 0xio or your phone's browser.
* **Keep recent sites** (on by default, 30 days).
* **Warn on lookalike sites** (on by default).
* **Clear recent sites** and **Clear site data**, each with two taps.
### Lookalike Site Warnings
A site whose name is spelt close to an app listed in the browser, or is written in another alphabet, is marked **Lookalike** next to its address. Before it can connect, sign, send or switch network, a red warning names the app it resembles. **Close** refuses the request; **Continue** goes on if you meant to open that site. Listed apps are never flagged.
### Connect a dApp (WalletConnect)
Use a dApp on another device from your phone: tap the scan icon on the home screen. The camera opens on the code; you can also paste a connection link. The connect sheet shows the site, the wallet and the network, each with **Change**, and warns when the site asked for another network. Slide to connect. A watch-only wallet cannot connect, since it cannot sign.
Sites are named by the address WalletConnect verified for them:
* **Verified:** the sheet shows the site's verified address.
* **Not verified:** the sheet shows the name the site gave, tagged **Unverified**. A name spelt like a listed app is tagged **Lookalike**.
* **Invalid or flagged:** a site whose address fails verification, or one flagged as a scam, is refused before any sheet opens.
Request sheets carry the same tag. Contract calls show their arguments exactly as they are signed.
Live sessions are listed on the same screen as wallet on network, each with Disconnect (two taps). A session stays with the wallet and network it was approved on: if the app is on another one later, its requests ask you to switch back or reconnect. A connection request that arrives after you leave the screen is refused, never approved unseen. Requests from a connected site use the same sheets and slide as the browser.
### Connected Sites
**Settings > Connected dApps** (or Browser settings) lists every site by wallet and network, the one you are on first. Tap a site to see what it sees, can ask and can never do, and Disconnect it (two taps) for that wallet and network only. WalletConnect sessions are listed below, and **Disconnect all** clears everything.
### Collectables
The NFTs tab opens on your newest piece, then a shelf per collection. Pull down to refresh. Tap a piece for its art, status, traits and more from its collection.
* **Send** a piece to an address, a contact or one of your wallets.
* **List for sale**, **Change price** or **Cancel listing** on a supported market, with the price in OCT or USD. The first listing approves the market for that piece only.
* Each step ends on a review you slide to confirm.
* **Hide** a piece or a whole collection from **More**, with Undo. **Hidden**, under the last shelf, shows them again. Hidden pieces are kept per wallet and network.
* A piece you send leaves the gallery at once and its page shows **Sent**.
* Tap **+** to import a collection.
* Art and living NFT programs stored in a circle as chunks are put back together and checked against their hashes before they show (see [Chunked Circle Files](/wallet/nft-standards#chunked-circle-files)).
### Address Book
Save and label frequently used addresses. Search by name or address, tap a contact to send to it or copy its address, and edit or delete it (two taps). The app says when an address is not valid or is already saved.
### Multi-Wallet Support
Create or import multiple wallets and switch between them from the wallet list (tap the wallet name on Home, or swipe from the left edge). All wallets use the same master root key derivation as the extension and desktop.
**Add a wallet** is one sheet: **Create new wallet**, **Derive account** from a recovery phrase already on the phone, and under **Already have one**: **Recovery phrase**, **Private key** or **Watch an address**.
**Rename** a wallet from the wallet list or Settings: a sheet with the wallet's picture, quick names (Savings, Daily, Trading, Cold) and its full address.
### Wallet Pictures
Every wallet has its own picture, drawn from its address: the 0xio braces around a composition of circles, in muted colours. It appears on the home screen, in the wallet list, in the address picker and on the Receive screen. The same address always draws the same picture, and the browser extension draws it identically, so a wallet looks the same on your phone and in your browser and the wrong one stands out before you read its address.
### History
History follows your wallet's address on every network the app knows (mainnet, devnet and your own nodes), newest first, with each entry marked with its network and chips to filter to one. It loads more as you scroll, and you can pull down to refresh.
Entries read as sentences: **Received from**, **Sent to** or **Sent privately**, with a contact or wallet name where the app knows one. NFT and token transfers show what actually moved.
Tap an entry for its receipt: the amount with its USD value and status, To and From with names and full addresses, the time, the network, the fee and the total, then the transaction hash to copy, **Send again** or **Send back**, **Save contact** for a new sender, and **View on Octrascan** on the transaction's own network.
### Your Own Node
Add a network in **Settings > Network** by giving the node's address. The app checks that the node answers as an Octra node as you type, fills in a name, and asks you to confirm you trust the node with your address and what you sign. You choose whether it supports private balances and can add an explorer. Removing a network asks first; removing the one you are on moves the app to Octra Mainnet.
**Settings > Advanced > Test connection** checks the network you are on: whether the node answers and how fast, whether it answers as Octra, its latest epoch, private balances, and the backup route.
### Stealth Transfers
Send private FHE-encrypted transfers where the amount is hidden from everyone except the recipient.
### Bulk Send
Bulk sends are available in [0xio Desktop](/products/desktop) and the browser extension. The mobile app sends to one recipient at a time.
### Optional RPC Proxy
By default, balances and history come straight from the Octra node. Turn on the **RPC proxy** in **Settings > Advanced** to route mainnet and devnet requests through the 0xio proxy, which compresses history responses for slow connections. The proxy is never used silently as a fallback, and a network you add yourself never uses it.
### Sentry Crash Reporting
Crash reports help fix problems quickly. They never include keys, recovery phrases or PINs, and you can turn them off under **Crash reports** in Settings; see the [Privacy Policy](https://0xio.xyz/privacy) for what is sent.
### Dynamic Recommended Fees
The app recommends different OU fee amounts per operation type (standard transfer, FHE encrypt, decrypt, etc.) based on current network conditions.
* Every fee is the network's live quote for that operation: a transfer, a token send or NFT action (a contract call), encrypt, decrypt, a private send or a claim. No default fee is ever shown or signed.
* A fee that cannot be read shows **Unavailable** and **Could not read the network fee. Try again.** The review and the slide stay off until a live fee exists.
* The fee on the review is the fee signed, as long as the network still accepts it. You are asked to review again only when the network's minimum fee rises above it: **The network fee changed. Review again.**
* A site, circle or WalletConnect request uses the site's fee only when the network accepts it, else the live recommended fee. Without a live fee the request is refused before the approval opens.
* **Fast** is offered only when the network quotes it; otherwise the fee sheet shows Standard and Custom.
* Private operations have floors: key switch, encrypt, decrypt and private send 5,000 OU, claim 3,000 OU.
* A custom fee below the network minimum or above 10,000,000 OU cannot be used. See [Network Fees](/wallet/sending#network-fees).
### Private Balance Status
When your private balance cannot be used as is (the network holds it for a migration, its key needs an update, or it was made by an older version), a tag sits beside your balance on Home. Tap it to see why and, when your account allows it, re-register your encryption key.
### Stealth Claim Caching
Previously claimed stealth transfers are cached locally, so the claim scan only processes new transfers on subsequent runs.
***
## Core Functionality
### Dashboard & Balances
The **Home Screen** shows your total balance over a faint chart of the day's worth. Drag across the chart to read the worth and change at any moment, and pull down on your assets to refresh. Send, Receive and Privacy sit right below, and only your asset list scrolls. Tap the eye beside the balance to hide amounts.
Each asset shows its name over its symbol, the amount and its USD value, or **No price yet**. Tap one for its page: **Octra** has Send, Receive and Encrypt with your public activity; **Private OCT** has Send, Receive and Decrypt with your private activity only; a custom token shows its contract and decimals, and one you imported can be removed.
The glass bar at the bottom switches between Wallet, History, Browse and NFTs: tap a tab, or hold the bar and slide your finger to carry the lens to another tab. While the app starts its FHE engine, a small **Initializing FHE** pill shows above the bar and you can keep using the wallet.

Home in the mobile app
### Receiving
**Receive** shows your QR code and address. Type an amount on its keypad to turn the QR into a payment request; **Share request** sends it as a link. Someone scanning it with 0xio gets your address and the amount filled in. See [Payment Requests](/wallet/receiving#payment-requests-mobile) for the format.
### Sending Assets
The **Send Flow** is optimized for one-handed use:
* **Pick a recipient:** type or paste an address, or choose a contact or a recent recipient. With nobody saved yet, your other wallets are listed.
* **Scan to Pay:** scan an address, or a payment request that fills in the amount too.
* **Private sends:** pick **Private OCT** to send privately.
* **Slide to send** on the review. You can close the progress screen while it runs; the result arrives as a notice.
* **Not confirmed:** when the app cannot tell whether a send went through, it checks the network first. If it still cannot tell, the send stays pending and says **Check History before sending again**. See [Not Confirmed](/wallet/sending#not-confirmed).
### Privacy Suite (FHE)
Tap **Privacy** on Home for a sheet over your wallet:
1. **Encrypt:** Move funds from Public -> Private.
2. **Decrypt:** Move funds from Private -> Public.
3. **Waiting for you:** incoming private transfers, each with **Claim**, and **Claim all**, which claims them one at a time with Pause and Stop.
A private balance can carry a limited number of encrypted layers. When an encrypt, decrypt or private send meets that limit, the review says the balance is refreshed first, shows the fee for both transactions, and one slide runs both.
Private actions follow the proof rules the network states, read before you prepare and again right before signing. When the network does not state them, private actions pause and nothing is sent. See [Network Proof Rules](/wallet/privacy-operations#network-proof-rules).
***
## Getting Started
### Creating a Wallet
1. Install the app from [0xio.xyz/install](https://0xio.xyz/install).
2. Tap **Create a wallet**.
3. **Set a PIN and biometrics** to protect the wallet on this phone. If the app closes before the PIN is set, it reopens on this step, never on Home.
4. Name your wallet and copy its address on **Your wallet is ready**.
5. **Back up your recovery phrase:** tap the **Back up** pill on Home. After your PIN, hold to show your 12 words, write them down in order offline, then tap **I wrote them down**. Until you do, Receive reminds you first.
### Importing
Already have the Browser Extension or another 0xio wallet?
1. Tap **I already have a wallet**.
2. Type, paste or scan your recovery phrase (12 or 24 words) or private key into the one field. The app tells which it is and flags typos.
3. Check the wallet it found, with its address and balance, then import it. Your public and private balances sync automatically.
### Watching an Address
From the wallet list, tap **Add a wallet**, then **Watch an address**, and paste or scan any `oct...` address. The app shows its balance and latest activity before you add it. A watched wallet holds no keys, so it can never send.
***
## Network Support
The mobile app supports multiple Octra networks:
| Network | Privacy (FHE) | Explorer |
| - | :-: | - |
| Mainnet | Yes | [octrascan.io](https://octrascan.io) |
| Devnet | Yes | [devnet.octrascan.io](https://devnet.octrascan.io) |
| Your own node | If you say it runs the FHE stack | The explorer you set, if any |
Switch networks in **Settings > Network**. Devnet is recommended for testing FHE features such as encrypted balances, private transfers, and the encrypt/decrypt flow.
> [card] [Install 0xio Mobile](https://0xio.xyz/install)
>
> Get the 0xio Mobile Wallet for iOS and Android.
# 0xio Bot
Source: https://docs.0xio.xyz/products/telegram-bot
Monitor Octra Network addresses from Telegram with the 0xio Bot, including real-time alerts for transfers, balance tracking, and wallet management.
# 0xio bot User Guide
Welcome to the official documentation for **0xio bot**, a Telegram bot designed to monitor your **Octra Network** addresses in real-time. Receive instant notifications for incoming and outgoing transactions, track balances across mainnet and devnet, query live 0xio DEX pool data, and manage multiple wallets directly from Telegram.
## Getting Started
To start using the bot, open [@NullXio_bot](https://t.me/NullXio_bot) in Telegram and tap the **Start** button or send the command:
```bash theme={null}
/start
```
You will be greeted by the main menu, which gives you quick access to your wallets, settings, and tools to add new addresses.
***
## Key Features
* **Real-Time Monitoring**: Get notified instantly when you send or receive OCT, including stealth payments, contract calls, and self-transfers (encrypt/decrypt/claim).
* **Multi-Network**: Track addresses on **mainnet and devnet** at the same time, with per-network toggles.
* **Multiple Wallets**: Track up to **5 different Octra addresses** simultaneously.
* **DEX Integration**: Query live price, pool state, and swap quotes from the 0xio DEX WOCT/0XIO CLMM pool with `/price`, `/pool`, and `/quote`.
* **Privacy Control**: Toggle balance visibility in notifications to keep your holdings private.
* **Detailed Insights**: View transaction hashes, counterparty addresses, and direct links to the Octra Explorer.
* **Address Validation**: Built-in checks ensure you only track valid Octra addresses, with auto-detection of addresses pasted in chat.
***
## Command Reference
Manage the bot efficiently using these commands:
| Command | Description |
| - | - |
| `/start` | Open the main menu and initialize the bot. |
| `/wallets` | View your tracked wallets, balances, and manage them. |
| `/add` | Quickly add a new Octra address to your watchlist. |
| `/settings` | Configure notifications, networks, and balance visibility. |
| `/help` | Display the help message and available commands. |
| `/price` | Current price of the 0xio DEX WOCT/0XIO pool (devnet). |
| `/pool` | Pool state: active bin, reserves, and fees (devnet). |
| `/quote {amt} {tok}` | Get a swap quote, e.g. `/quote 100 WOCT` (devnet). |
***
## Managing Wallets
### Adding a Wallet
1. Click **Add Wallet** in the main menu or send `/add`.
2. Paste your **Octra Network address**.
> **Note:** Addresses must start with `oct` and be 47 characters long.
3. Enter a **custom name** for the wallet (e.g., "Main", "Savings").
4. The bot will sync your balance and begin watching for new transactions immediately.
### Viewing & Editing
Access your wallet list by sending `/wallets`. Click on any wallet to:
* **Rename**: Change the display label.
* **Delete**: Stop tracking the address.
* **Explorer**: View the address on the official Octrascan explorer.
### Wallet Limits
Each user can watch a maximum of **5 wallets**. If you reach this limit, you must delete an existing wallet before adding a new one.
***
## Settings & Privacy
You can customize how notifications appear in your chat.
### Notification Balance
By default, the bot displays your updated wallet balance in every transaction notification.
* **Visible**: Shows `Balance: 150.0 OCT` in the alert.
* **Hidden**: Removes the balance line for privacy (useful if you share screenshots or use the bot in public).
**To toggle this setting:**
1. Send `/settings`.
2. Click the **Hide Balance** or **Show Balance** button.
### Network Selection
The bot monitors **mainnet** and **devnet** simultaneously. Use `/settings` to enable or disable per-network notifications. Each transaction alert and explorer link is tagged with its network.
> [!NOTE]
>
> An optional AI transaction-analysis feature can be unlocked with an activation code using `/ai {code}`.
***
## DEX Queries
The bot doubles as a companion for the [0xio DEX](https://dex.0xio.xyz). It reads the on-chain WOCT/0XIO concentrated-liquidity (CLMM) pool on devnet:
* `/price`: current pool price.
* `/pool`: active bin, reserves, and fee tier.
* `/quote {amount} {token}`: a swap quote, e.g. `/quote 100 WOCT`.
***
## Notification Types
When a transaction occurs on the Octra Network, you will receive a notification similar to this:
**Received Transaction**
> **RECEIVED** 100.0 OCT
> From: [oct...abcd](https://octrascan.io/address.html?addr=oct...abcd)
> Wallet: [Main](https://octrascan.io/address.html?addr=your-wallet-address)
> Balance: 1250.0 OCT
**Sent Transaction**
> **SENT** 50.0 OCT
> To: [oct...xyz1](https://octrascan.io/address.html?addr=oct...xyz1)
> Wallet: [Main](https://octrascan.io/address.html?addr=your-wallet-address)
> Balance: 1200.0 OCT
*Clicking the links in the notification will take you directly to the transaction details on the blockchain explorer.*
***
## FAQ
**Q: Why does the bot say "Invalid Address"?**
A: Ensure your address starts with `oct` and contains exactly 47 alphanumeric characters.
**Q: Can I track the same address on multiple accounts?**
A: Yes, the bot tracks public blockchain data, so an address can be monitored by multiple users.
**Q: How fast are notifications?**
A: The bot polls the Octra Network approximately every 30 seconds to check for new activity.
# Connecting to Sites
Source: https://docs.0xio.xyz/wallet/connecting-sites
Connect the 0xio browser extension to Octra and EVM websites, choose which wallet a site uses, and manage connected sites.
The 0xio browser extension connects to two kinds of websites:
* **Octra sites**, through the [0xio SDK](/developers/sdk-guide) and the `window.octra` provider.
* **EVM sites** (Ethereum and other EVM networks). Sites find the wallet through EIP-6963 and list it as **0xio Wallet**. The extension also sets `window.ethereum` when no other wallet has set it.
A site sees nothing until you approve its connection, and every transaction and signature it asks for opens a review first.
> [!NOTE]
>
> This page covers the browser extension. The mobile app connects to sites in its built-in browser and through WalletConnect, see [Mobile Wallet](/products/mobile#connect-a-dapp-walletconnect). The desktop app has its own built-in browser, see [Desktop Wallet](/products/desktop#built-in-dapp-browser).
## Connecting
Sites ask to connect from their own page. The connect review shows the site, the wallet it will see and the network:
* The site can view this address and its balances.
* Every transaction and signature needs your approval.
Choose **Reject** or **Connect**.
### When the Open Wallet Has No EVM Address
A wallet imported from an Octra private key has no 0x address. When such a wallet is open and an EVM site asks to connect, the review lists your wallets that have an EVM address, with the one you used last picked first. Watch-only wallets are never listed.

Choosing a wallet for an EVM site
If none of your wallets has an EVM address, the review offers **Add a wallet**: import a recovery phrase or create a new wallet. The review stays open and comes back with the new wallet picked.
## Several Wallets per Site
An EVM site can be connected to more than one of your wallets:
* When you open a wallet that is connected to the site, the site moves to it.
* When you open a wallet that is not connected to the site, the site keeps the wallet it had.
* Switching wallets never disconnects a site.
* A site only ever sees the wallet in use. It cannot learn which of your other wallets are connected to it, and a request naming another address is refused.
An Octra site keeps one wallet. Switching wallets does not move it, and its requests sign with its own wallet. When a site on another wallet asks for a private operation, the wallet asks first: switch to that wallet, or move the site to the open one.
Removing a wallet from 0xio lists its connected sites first, removes it from every site, and disconnects a site left with no wallet.
## The Site Chip
The status bar at the bottom of the wallet shows the open tab's site:
| Chip | Meaning |
| - | - |
| Host with a dot | The site uses the open wallet |
| Wallet picture and host | The site uses another of your connected wallets |
| **Not connected** | The open wallet is not connected to this site |

The site chip menu
Tap **Not connected** to open the connect review for the open wallet. On a connected site the chip's menu lists the site's wallets, **Connect another wallet**, the **Network** the site is on (choose another of your networks to move it) and **Disconnect**. Choosing another of the site's wallets opens a short review: both stay connected, and the site uses the new one until you open another connected wallet.
## Mainnet and Testnet
0xio is on Mainnet or Testnet as a whole (see [EVM Networks](/wallet/evm#mainnet-and-testnet)). When a site asks for a network on the other side, the review asks to **Switch 0xio to Mainnet** or **Switch 0xio to Testnet**. Approving moves Home, balances, History and every connected site to that side, then puts the site on the network it asked for. Rejecting changes nothing.

A site asks for a testnet
## Reviewing Requests
Every request from a site opens its own review, names the site, and shows the wallet that signs with its balance (EVM reviews label it **Signing with**). Each review ends with **Reject** and the action, for example **Connect**, **Sign**, **Send**, **Switch**, **Add and switch**, **Add token** or **Deploy**.
* **Messages:** the full message. A message that is not readable text is shown as hex with a warning. A token permit says that it lets the spender move your tokens without a separate approval.
* **Transactions:** what the call does (for example a token transfer or approval), the amount, the recipient or spender, and the network fee (in USD first on priced networks). A call 0xio cannot decode says so.
* **Octra contract calls:** the contract, the method and its arguments exactly as they are signed, large numbers included. A sequence lists every step with its arguments and the amount it attaches.
* **Token approvals:** tap the pencil on the Amount row to change the amount, or turn on Unlimited. The amount signed is the one shown, and never more than the site asked for unless you pick Unlimited.
* **Fees:** site transactions use the wallet's live network fee. The fee the site sent is used only when the network gives none, and the review marks it **Suggested by site**.
* **Contract deployment:** a review with the code size, the new contract's address and the live fee.
* **Add a network** and **Add token** show what will be added. Add networks only from sites you trust: the RPC sees your address and your activity on that network.
Three requests need no new review, because nothing new is signed: connecting a site that is already connected, a transaction you already signed in a review and the site now sends, and setting up private receiving on a wallet that is already set up. Setting it up for the first time is a transaction with a fee and has its own review.
What you review is what is signed: the review and the signature are built from the same fields. A request that names two different recipients, two different methods or two different argument lists is refused before any review opens.
After you approve, the wallet checks again that the signing wallet is still in 0xio, still connected to the site, and still has the same address. If anything changed, nothing is signed.
## Connected Sites
**Settings > Connections > Connected sites** lists every site once, with its first wallet, a **+N** count for more wallets and its network. Open a site to see its wallets (each with **Remove**), **Connect another wallet**, the network and **Disconnect**.

A connected site
> [!WARNING]
>
> Disconnecting stops a site from reading your address and asking for transactions. It does not cancel pending transactions or revoke token approvals already on chain.
## While Locked
While the wallet is locked, sites receive no address and no balances. The same connection comes back after you unlock, without a new prompt.
## For Developers
* Octra sites: [SDK Guide](/developers/sdk-guide).
* EVM sites: the provider supports the standard EIP-1193 methods. See [EVM Provider](/developers/sdk-guide#evm-provider) for the methods that open a review, those answered locally and those refused.
# EVM Networks
Source: https://docs.0xio.xyz/wallet/evm
Use Ethereum, Robinhood Chain and other EVM networks in the 0xio browser extension: the 0x address, networks, tokens, NFTs, sending and history.
The 0xio browser extension (3.0 and later) holds an EVM wallet next to your Octra wallet. One recovery phrase gives each wallet an Octra address (`oct...`) and an EVM address (`0x...`). Balances, tokens, NFTs, sends and history for both sit in the same wallet.
> [!NOTE]
>
> EVM networks are public. Private balances, encrypt, decrypt and private sends exist on Octra only.
## Your 0x Address
* The 0x address is derived from the same recovery phrase at `m/44'/60'/0'/0/i` (secp256k1), so it matches the address other EVM wallets derive from that phrase. Accounts added from a phrase get the 0x address at the same index as their Octra account.
* Only the address is stored. The EVM key is derived for one signature at a time, checked against the 0x address and wiped after.
* A wallet imported from an Octra private key has no 0x address. The wallet says why wherever EVM is needed.
* A phrase made by an older 0xio version gets a note that other EVM wallets will not accept it.
* **Settings > Backup > Private key** shows the EVM key as well, with its own QR code, while EVM is on.
### Turning EVM On or Off
After you create a wallet or import a recovery phrase, **Choose your networks** asks which networks to use. Octra is always on; EVM is on by default.
Later, **Settings > EVM** turns EVM on or off for the open wallet. Off keeps the same 0x address, so turning it back on shows the same address again. Nothing moves on chain either way.
## Networks
### Mainnet and Testnet
The **Mainnet** or **Testnet** chip in the status bar at the bottom of the wallet moves Octra and the EVM networks together:
| Side | Octra | EVM networks |
| - | - | - |
| Mainnet | Octra mainnet | Ethereum, Robinhood Chain and the mainnets you add |
| Testnet | Octra devnet | Sepolia, Robinhood Chain Testnet and the testnets you add |
### Managing Networks
**Settings > Networks** lists Octra (always on), then every EVM network of the side you are on, in one searchable list with a switch each. A network you turn off stays in the list but is never checked and never shows on Home.

Settings > Networks
Tap a network to open its page:
* **On or off**
* **RPCs:** the network's own RPC, plus any you add. A new RPC must answer with the network's chain ID before it is saved. Tap one to use it; remove the ones you added.
* **Explorer and symbol**
* **Remove network** for networks you added, or **Reset to default** for built-in ones. Reset brings back the network's own RPC and explorer, turns it on and removes the RPCs you added.
### Adding a Network
Tap **+ Add network** in Settings > Networks:
* **Popular:** 30 common chains (17 mainnets and 13 testnets), one tap each.
* **Search:** also searches the public chain list from chainlist.org, fetched on your first search and kept for a day.
* **Custom network:** enter an `https://` RPC URL; the chain ID and details are read from it.

Add a network
Whatever you pick, its RPC must answer with the expected chain ID before the review, and again when it is saved. A site can also ask to add a network; that opens a review (see [Connecting to Sites](/wallet/connecting-sites)), and a site can never change the RPC, name or symbol of a network you already have.
## Home
The switch in the header picks what Home shows: **All networks**, **Octra** or **EVM**.
* **All networks** adds Octra and EVM together in USD.
* **EVM** lists ETH and tokens on every EVM network that is on, with a network badge on each logo and an **All EVM** filter.
* EVM prices and the EVM chart come from DefiLlama. Testnet tokens have no price.
Tap an asset for its page: the price with a 1D, 7D or 1M chart, your balance, actions for that token and its recent activity.
## Tokens
* **Listed tokens** show on the built-in networks without any setup, for example USDC, USDT and wOCT on Ethereum.
* **Tokens you hold** are found through the 0xio oracle and join Home, unless they look like spam (links or bait words in the name, or a symbol that imitates ETH, USDC, USDT, OCT and similar). Listed tokens, tokens you imported and tokens you have sent are never treated as spam.
* **Import token:** paste a 0x contract. It is looked up on every known EVM network at once: the network it is on is filled in, several matches are listed, or a network search opens.
* **Settings > Tokens and NFTs** lists every token and collection under **Shown on Home** and **Hidden as likely spam**, with a switch each. Your switch always wins over the spam rules. Hidden tokens leave Home and its total and are not read.
## Receiving
**Receive** has **Octra** and **EVM** tabs. The EVM tab shows your 0x address with the logos of its networks. The same 0x address works on every EVM network; send only EVM assets to it.
## Sending
Pick **Send** with an EVM asset, or choose EVM when Send asks Octra or EVM on All networks.
**1. Pick the asset and recipient**
Choose the coin or token and type, paste or pick a 0x recipient. The address book takes 0x contacts. The zero address and the token's own contract are refused.
**2. Enter the amount**
The live network fee is read from the network. A fee far above the network's gas price, or above the cap of a built-in network, is refused.
**3. Review**
The review shows the amount, recipient, network and the **Network fee**: in USD first, with the native amount beside it. On testnets and coins with no price it shows the native amount only. **Back** returns to the form; **Send** signs. The review is bound to the wallet, address, network, RPC, recipient, amount and asset it showed, so any change asks for a new review.
**4. Status**
The screen follows the transfer until it confirms. A transfer still unconfirmed after 5 minutes ends as **Still pending** or **Replaced or dropped**, with a link to the explorer.
Once a signed transfer reaches the RPC it is never signed again. If the reply is lost, the wallet follows the transfer by its own hash instead of returning to the form.
## NFTs
* Octra and EVM collections share one NFTs list, with a network badge on each cover. The list follows the Home switch.
* EVM holdings (ERC-721 and ERC-1155) come from the 0xio oracle; images load through the 0xio proxy.
* **Import** an EVM NFT by contract and token ID. The network is detected the same way as for tokens, and the preview checks that this wallet holds it.
* A piece's page shows the art, network, standard, contract, token ID and an explorer link, with **Send** (through a review) and **Hide** with Undo.
## History
EVM activity shows in **History** next to Octra, newest first, on the same **Mainnet** and **Testnet** pages. See [Wallet Interface](/wallet/interface#history).
* Ethereum and Sepolia are read through the 0xio oracle. A network you added is read from its own explorer when that explorer is Blockscout.
* A network with no history source is named at the end of the list: **History not available on** that network.
* History is read when you open it and when you refresh, not on a timer.
* Only ETH, listed, imported and shown tokens appear. Zero-value transfers, spam-flagged and hidden tokens are left out, so address-poisoning transfers never show.
* Contract calls read as actions (Swap, Approve, Bridge and others). Tap a row for its details, and **View call data** for the method, every argument and the raw input.
## Bridging
wOCT on Ethereum can be bridged to Octra, and OCT to Ethereum, from inside the wallet with the same 0x address. See [0xio Bridge](/products/bridge#bridge-inside-the-extension).
# Installation & Setup
Source: https://docs.0xio.xyz/wallet/installation
Install and set up the 0xio browser extension on Chrome, Brave, Edge, Opera and Firefox: requirements, creating or importing a wallet, and backing up the recovery phrase.
The 0xio wallet ships as a browser extension, a desktop app and a mobile app. All three use the same key derivation, so a recovery phrase opens the same Octra wallet in every client. This guide focuses on the browser extension; for desktop and mobile, see [Desktop Wallet](/products/desktop) and [Mobile Wallet](/products/mobile).
## System Requirements
### Supported Browsers
The extension is a Manifest V3 build and runs on Chromium browsers and Firefox.
> [card] Chrome
>
> Version 111 or later
> [card] Brave, Edge and Opera
>
> Any version built on Chromium 111 or later
> [card] Firefox
>
> Version 140 or later (142 on Android). In review on Mozilla Add-ons
### Current Version
**Extension v3.0.4**, published on the Chrome Web Store.
> [!NOTE]
>
> Other clients version independently: **Desktop v0.6.0** and **Mobile v1.3.3** (v1.3.4 is in progress). Every client installs from [0xio.xyz/install](https://0xio.xyz/install).
## Installation Steps
**1. Open the Chrome Web Store listing**
Go to the [0xio Wallet listing](https://chromewebstore.google.com/detail/0xio-wallet/anknhjilldkeelailocijnfibefmepcc) on the Chrome Web Store.
**2. Add Extension**
Click "Add to Chrome" (the button adapts to Brave, Edge and Opera) and confirm.
**3. Pin to Toolbar**
Pin the extension to your browser toolbar for easy access. The icon shows a padlock while the wallet is locked.
**4. Launch Setup**
Click the 0xio extension icon to begin setup.
## Creating Your First Wallet
The welcome screen offers two ways in: **Create a wallet** and **I already have a wallet**. The Terms of Service and Privacy Policy are linked below them.

The welcome screen
**1. Create a password**
Choose a password of at least 8 characters. It unlocks 0xio in this browser and never leaves this device. Confirm it on the next screen.
**2. Passkey (optional)**
Where the browser supports it, you are offered to unlock with a passkey (Touch ID, Windows Hello or a security key). **Not now** skips it; you can add one later in Settings > Security. Passkeys are not available on Firefox.
**3. Choose your networks**
Octra is always on. EVM is on by default and gives the wallet a 0x address from the same recovery phrase. See [EVM Networks](/wallet/evm).

Choose your networks
**4. Your wallet is ready**
The last screen lists your Octra address and, with EVM on, your 0x address.
> [!NOTE]
>
> Your password derives a key with PBKDF2-SHA256 (900,000 iterations on the extension) that protects your vault with AES-256-GCM. It only locks the wallet on this device. Your recovery phrase is what controls your funds on every device.
### Back Up Your Recovery Phrase
A new wallet is usable at once; you back up its recovery phrase from Home. Until you do, Home shows a **Back up** tag and Receive asks you to back up first.
**1. Open the backup**
Tap the **Back up** tag on Home and enter your password.
**2. Show the words**
The words stay hidden until you tap **Show**, and stay shown until you tap **Hide**, leave the screen or the wallet locks. **Copy phrase** copies them; the extension clears the clipboard after 60 seconds when it can tell nothing else was copied since, which it cannot once the popup has closed.
**3. Write them down**
Write the words down in order, offline, then tap **I have written it down**.
> [!WARNING]
>
> **CRITICAL SECURITY INFORMATION**
>
> Your 12-word Recovery Phrase is the master key to your funds. 0xio generates this phrase **locally on your device** using the BIP-39 standard with cryptographic randomness. The Octra master key is derived as `HMAC-SHA512("Octra seed", seed)[0:32]` (no BIP-44 path), and your Octra address is `SHA-256(public key)` encoded as Base58 with an `oct` prefix. The EVM address comes from the same phrase at `m/44'/60'/0'/0/0`.
>
> **If you lose this phrase, your funds are permanently inaccessible.**
>
> 0xio cannot recover lost phrases as they never leave your device.
#### Best Practices - DO:
* Write it down on paper immediately
* Store in a fireproof safe
* Consider metal backup plates for long-term storage
* Split across multiple secure locations for large holdings
* Test your backup by importing on a second device
#### Best Practices - DON'T:
* Store in cloud services (Google Drive, Dropbox, iCloud and similar)
* Take photos of your phrase
* Store in email or messaging apps
* Store digitally on internet-connected devices
* Share with anyone, including support staff
## Importing an Existing Wallet
If you already have a 0xio wallet or another Octra wallet:
**1. Select Import**
Click **I already have a wallet** on the welcome screen.
**2. Paste your phrase or key**
Paste or type a 12 or 24 word recovery phrase, or an Octra private key, into the one field. The extension tells which it is and offers a one-tap fix for a mistyped word.
**3. Check the wallet it found**
Before you continue, the wallet shows the address it found with its balance. Check it is the one you expect.
**4. Password and networks**
Create a password for this device and choose your networks, as above.
> [!NOTE]
>
> A wallet imported from an Octra private key has no EVM address; only a recovery phrase gives both. An EVM private key or a 0x address pasted into the field is named as such and not imported as an Octra wallet.
## Watch-Only Wallets
Follow any Octra address without its keys: paste the `oct...` address into the same import field. Its balance shows before you add it.
**Use Cases:**
* Monitoring cold wallet balances
* Observing project treasury addresses
* Keeping an eye on another device's wallet
> [!NOTE]
>
> A watch-only wallet can view balances and history but cannot send, sign, bridge or run privacy operations.
## Next Steps
Once your wallet is set up:
> [card] [Fund Your Wallet](/wallet/receiving)
>
> Learn how to receive OCT and start transacting
> [card] [Explore Privacy Features](/wallet/privacy-operations)
>
> Discover how to encrypt and decrypt balances
> [card] [Send Your First Transaction](/wallet/sending)
>
> Learn how to send public and private transfers
> [card] [Security Settings](/wallet/security)
>
> Configure auto-lock, backup, and advanced security
# Wallet Interface
Source: https://docs.0xio.xyz/wallet/interface
Tour the 0xio browser extension: Home, the status bar, History with Mainnet and Testnet pages, transaction details and call data, Settings and locking.
> [!NOTE]
>
> This guide covers the **0xio browser extension** (v3.0). The desktop and mobile apps share the same ideas (a balance overview, Send, Receive, History and a Privacy hub) with layouts of their own; see [Desktop Wallet](/products/desktop) and [Mobile Wallet](/products/mobile).
## Home

Home on All networks, with amounts hidden
### Header
* **Wallet picture and name:** each wallet has its own picture drawn from its address (the 0xio braces around a composition of circles). The mobile app draws the same picture for the same wallet. Click it to open the wallet list.
* **Network switch:** three glyphs choose what Home shows: **All networks**, **Octra** or **EVM**. All networks adds Octra and EVM together in USD.
* **Settings**
### Balance Card
* **Total balance** in USD, over a chart of the day: green when up, red when down, grey when flat. Hover over the chart to read the value at any point.
* **Day's change** in percent. It follows the value you hold, so an empty wallet reads 0.0%.
* **Eye toggle** to hide amounts.
* **Tags** at the top right of the card, most urgent first. For example **Back up** (the recovery phrase is not backed up yet), **Set up private** (the wallet can turn on its private balance) or a private balance on hold. Tap a tag for its sheet.
Balances not read yet show `--`, never 0. If one network or token read fails, the total shows what is known and notes that some balances are missing.
### Actions
**Send** sits beside a row for **Receive**, **Bridge**, **Privacy** and **History**, each named on hover. On the EVM view, Privacy stays visible but disabled, since private balances exist on Octra only.
### Assets
Each asset shows its logo, name, amount and USD value. On Octra you see OCT, the **Encrypted** row (your private balance) and Octra tokens; on EVM you see ETH and tokens, each logo with a badge for its network.
Tap an asset for its page: the price with a 1D, 7D or 1M chart that shows price and time under the pointer, your balance, actions that keep that token selected, and its recent activity. An imported token can be removed from its page.
### Status Bar
A strip along the bottom of the wallet shows:
| Chip | What it shows |
| - | - |
| Site | The open tab's site and whether the open wallet is connected to it, or **Not connected**. See [Connecting to Sites](/wallet/connecting-sites#the-site-chip) |
| 0xio Desktop | Shown when the desktop app is running private proofs for the extension |
| Mainnet or Testnet | The side 0xio is on. Switching it moves Octra and the EVM networks together |
## Screens
| Screen | What it does |
| - | - |
| Home | Balance, actions and assets |
| Send | Send OCT, tokens, private transfers and EVM assets |
| Receive | Your Octra and EVM addresses with QR codes |
| Bridge | Bridge to Ethereum, Bridge to Octra, Any chain to OCT, and the Claim page |
| Privacy | Encrypt, Decrypt and Claim |
| History | Octra and EVM activity on Mainnet and Testnet pages |
| NFTs | Your collections, newest pieces first |
| Settings | Security, Backup, Connections, Appearance, Networks, Tokens and NFTs, EVM |
| Wallet list | Switch, add, rename and remove wallets |
## History
History lists everything your address did, newest first, Octra and EVM together. It has two pages:
* **Mainnet:** Octra mainnet and the EVM mainnets.
* **Testnet:** Octra devnet and the EVM testnets. Rows here carry a **Testnet** tag.
Each page shows how many entries it holds. History opens on the side 0xio is on. A network you added yourself is included while it is active.

The Testnet page of History
### Rows
* **Transfers** show what moved, with a sign: received or sent OCT, tokens and NFTs, encrypt, decrypt, private sends and claims, and key updates.
* **Contract calls** read as actions: **Swap**, **Approve**, **Bridge**, **Mint**, **Claim** and others, or the method in words when there is no common name. The contract is named (known contracts, listed tokens, verified explorer names) or shown as a shortened address. One row lists every asset the call moved. A call that moved nothing shows no 0 amount, and a failed call shows **Failed** with no sign.
* **Pending** transactions show above the list. **Cancel** works only before a transaction reaches a node; one already sent can be hidden on this device instead.
* **EVM networks with no history source** are named at the end of the list: **History not available on** that network. A network that could not be read says so, with Refresh to try again.
### Transaction Details
Tap any row, Octra or EVM, for its details: the amount, status, type, from and to, token, network and fee when known, with **Copy hash** and **View in explorer** (each network opens its own explorer). Contract calls also list the method and its arguments by name (for example Recipient, Minimum received or Expires), and an approval at the maximum reads **Unlimited**.

Details of a contract call
### View Call Data
For a contract call, **View call data** opens a page inside the details with:
* the method and its selector
* the contract
* every argument with its type and full value
* the raw input

The call data page
## Privacy Screen
The Privacy screen moves OCT between your public and private balances:
* **Encrypt:** public to private.
* **Decrypt:** private to public.
* **Claim:** incoming private transfers, each claimed after its own review.
Each operation shows the live network fee before you confirm. See [Privacy Operations](/wallet/privacy-operations).
## Settings
* **Security:** password (change or remove it), passkey unlock, auto-lock after 5, 15, 30 or 60 minutes or never, and Reset wallet under This browser.
* **Backup:** the recovery phrase and private key of the open wallet, behind your password. With EVM on, the EVM key is shown too.
* **Connections:** connected sites and the address book.
* **Appearance:** theme (Dim, Day or Octra) and language (English, Chinese, Japanese, Korean or Indonesian).
* **Networks:** Octra and every EVM network of the side you are on. See [EVM Networks](/wallet/evm#managing-networks).
* **Tokens and NFTs:** show or hide every token and collection.
* **EVM:** turn EVM on or off for the open wallet.
**Lock wallet** sits at the bottom.
## Lock Screen
* Enter your password, or tap **Use passkey** if you set one up. The passkey prompt does not open by itself.
* The wallet locks itself after the auto-lock time you set.
* While locked, the toolbar icon shows a padlock.
## Watch-Only Wallets
Watch-only wallets (added with an address only) show balances and history and can receive funds. They cannot send, sign, bridge or run privacy operations. History marks them **Watch-only**.
## Notices
Short messages appear as one pill above the status bar and fade on their own: information after 4 seconds, errors and warnings after 6. Hover to keep one on screen. Copy buttons say **Copied** in place.
# NFT Standards
Source: https://docs.0xio.xyz/wallet/nft-standards
Reference for NFT standards supported by 0xio Wallet on Octra, including SNS-1 collections and the single-token standard with view function details.
# NFT Standards
0xio Wallet supports two NFT standards on the Octra Network. The wallet automatically detects which standard a contract uses via on-chain ABI inspection and adapts its behavior accordingly.
## SNS-1 (Spectrum NFT Standard)
SNS-1 is the collection-based NFT standard on Octra, designed by [Spectrum](https://x.com/The_SpectrumX). It supports multiple tokens per contract with sequential token IDs, IPFS metadata, royalties, and operator approvals.
### View Functions
| Method | Signature | Description |
| - | - | - |
| `collection_name()` | `→ string` | Collection display name |
| `symbol()` | `→ string` | Collection ticker symbol |
| `total_supply()` | `→ int` | Total minted tokens |
| `token_uri(token_id)` | `int → string` | IPFS URI to metadata JSON |
| `owner_of(token_id)` | `int → address` | Current token owner |
| `creator_of(token_id)` | `int → address` | Original minter |
| `royalty_of(token_id)` | `int → int` | Royalty in basis points (e.g. 500 = 5%) |
| `balance_of(addr)` | `address → int` | Number of tokens owned by address |
| `get_token_info(token_id)` | `int → string` | Extended token information |
### State-Changing Functions
| Method | Signature | Description |
| - | - | - |
| `mint(to, collection, metadata_uri, royalty_bps)` | `address, string, string, int` | Mint a new token |
| `transfer(to, token_id)` | `address, int → bool` | Transfer token ownership |
| `transfer_from(from, to, token_id)` | `address, address, int → bool` | Transfer on behalf of owner |
| `approve(approved, token_id)` | `address, int → bool` | Approve address for token |
| `set_operator(operator, approved)` | `address, bool → bool` | Set operator for all tokens |
### Metadata JSON (IPFS)
```json theme={null}
{
"name": "Token Name",
"description": "Token description",
"image": "ipfs://bafybei...",
"attributes": [
{ "trait_type": "Network", "value": "Octra" },
{ "trait_type": "Edition", "value": "Genesis" }
],
"collection": "Collection Name",
"creator": "oct...",
"royalty_bps": 500
}
```
Token IDs are sequential starting from 0.
### Example Contract
* **Devnet**: `octFc9ynSen79FarWJqsLExjJTHbp27iQAfy3CG9218pr9z`
***
## Bionts (Single-Contract NFT)
Bionts are autonomous, single-contract NFTs created by [Euint Labs](https://biont.network), where each biont is its own deployed smart contract. Total supply is always 1, and the token ID is always 0. The `token_uri()` function returns full SVG markup directly from contract state. It is fully on-chain, with no IPFS or external servers.
### View Functions
| Method | Signature | Description |
| - | - | - |
| `name()` | `→ string` | Token name (also `biont_name()`) |
| `symbol()` | `→ string` | Token symbol |
| `total_supply()` | `→ int` | Always 1 |
| `token_uri()` | `→ string` | Raw SVG markup (on-chain generative art) |
| `biont_owner()` | `→ address` | Current owner |
| `balance_of(addr)` | `address → int` | Returns 1 if owner, 0 otherwise |
| `is_alive()` | `→ bool` | Whether the biont is alive |
| `defense_level()` | `→ int` | Current defense stat |
### State-Changing Functions
| Method | Signature | Description |
| - | - | - |
| `transfer_to(new_owner)` | `address → bool` | Transfer ownership (no token_id needed) |
| `wander()` | `→ bool` | Move the biont |
| `hunt()` | `→ bool` | Hunt another biont |
### On-Chain SVG
Unlike SNS-1 which uses IPFS for metadata, Bionts return full SVG markup from `token_uri()`. The SVG is generated from live contract state: name, wander count, defense stats, and alive/dead status all render dynamically.
0xio Wallet detects raw SVG responses (strings starting with `