A web bridge for moving OCT between Octra Chain and Ethereum (as wOCT).
⚠️ Experimental — use at your own risk. This bridge is under active development.
OctWa Bridge connects native OCT on Octra with wOCT (Wrapped OCT) on Ethereum using a 1:1 lock/mint model — no liquidity pools, no swaps.
| Octra Bridge Contract | oct5MrNfjiXFNRDLwsodn8Zm9hDKNGAYt3eQDCQ52bSpCHq |
| ETH Bridge Contract | 0xE7eD69b852fd2a1406080B26A37e8E04e7dA4caE |
| wOCT Token | 0x4647e1fE715c9e23959022C2416C71867F5a6E80 |
| Denomination | 1 OCT = 1 wOCT = 1,000,000 raw units (6 decimals) |
| SDK | @octwa/sdk@2.1.0 (RFC-O-1) |
- Call
lock_to_eth(eth_address)on the Octra bridge contract usingsdk.sendContractTransaction(...). - Wait for epoch confirmation on Octra (~10 s per epoch).
- Wait for the epoch header to be indexed by the Ethereum lightClient (~30–40 min lag).
- Call
verifyAndMint(epochId, message, [], 0)on the Ethereum contract usingsdk.evm.sendTransaction(...). - wOCT is minted to the recipient.
- Approve the bridge contract to spend wOCT (
wOCT.approve(bridge, amount)). - Call
burnToOctra(octraRecipient, amount)on the bridge contract — emitsBurnInitiated. - The bridge relayer detects
BurnInitiatedand callsunlock_trustedon Octra. - OCT arrives in the recipient address (~2 minutes).
- OctWa wallet extension — install OctWa in Chrome / Edge.
- ETH on your EVM address — about 0.0005 ETH covers
verifyAndMintgas (~131 k gas). - Octra mainnet RPC access (or your own HTTPS proxy — see below).
Your EVM address is derived automatically from your Octra private key (secp256k1 over the same BIP39 seed). You do not need MetaMask.
cd bridge
npm install
npm run build # output → dist/Copy .env.example to .env:
cp .env.example .envVITE_INFURA_API_KEY=your_infura_api_key_here
VITE_OCTRA_RPC=https://bridge.octwa.pwVITE_OCTRA_RPC is the base URL — the app appends /rpc automatically. Set VITE_INFURA_API_KEY only if you need higher rate limits; otherwise the bridge falls back to https://ethereum.publicnode.com.
The Octra node RPC runs on plain HTTP (http://46.101.86.250:8080). Browsers block HTTP requests from HTTPS pages (Mixed Content), so you must proxy through your HTTPS domain.
Add to your Nginx server block (e.g. /etc/nginx/sites-available/bridge.octwa.pw):
server {
listen 443 ssl;
server_name bridge.octwa.pw;
# ... your existing SSL config ...
root /path/to/bridge/dist;
index index.html;
try_files $uri $uri/ /index.html;
# Proxy Octra RPC — avoids Mixed Content errors
location /rpc {
proxy_pass http://46.101.86.250:8080/rpc;
proxy_http_version 1.1;
proxy_set_header Content-Type application/json;
proxy_set_header X-Real-IP $remote_addr;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type";
if ($request_method = OPTIONS) {
return 204;
}
}
}After changing .env, rebuild and reload Nginx:
npm run build
sudo nginx -t && sudo systemctl reload nginxThe bridge uses RFC-O-1 methods only — there is no direct call to window.octra.invoke or any legacy capability API.
Connect — single approval for everything the bridge needs:
const accounts = await sdk.connect({
permissions: [
'read_address',
'read_balance',
'contract_calls',
'send_transactions',
],
})
const evmAddress = await sdk.evm.getDerivedAddress()Lock OCT (Octra) — opens the contract approval popup:
const result = await sdk.sendContractTransaction({
address: OCTRA_BRIDGE_CONTRACT,
method: 'lock_to_eth',
params: [ethRecipient],
amount: rawOuString,
})Mint wOCT (Ethereum) — opens the EVM transaction popup:
const result = await sdk.evm.sendTransaction({
to: WOCT_CONTRACT_ADDRESS,
data: encodedVerifyAndMintCalldata,
value: '0',
})Burn wOCT (Ethereum) — two transactions: approve the bridge to pull wOCT, then burn:
// Step 1 — approve
await sdk.evm.sendTransaction({
to: WOCT_TOKEN_ADDRESS,
data: encodeApprove(BRIDGE, amount),
value: '0',
})
// Step 2 — burn
const result = await sdk.evm.sendTransaction({
to: BRIDGE,
data: encodeBurnToOctra(octraRecipient, amount),
value: '0',
})The relayer reacts to the BurnInitiated event emitted by the burn transaction. Without the approve, the burn reverts on-chain and the relayer never sees a valid event — OCT stays locked.
The bridge does not request evm_send_transactions as a separate scope — the wallet accepts the broader send_transactions grant for EVM signing operations.
The history view is powered by on-chain reads — no localStorage-backed cache.
octra_transactionsByAddress— last 20lock_to_ethcalls.contract_receiptfor each tx →Lockedevent → derived message hash.processedMessages(msgHash)on ETH contract →claimed/unclaimed.lightClient.latestEpoch()→epoch_pendingif not yet indexed.
For wOCT → OCT history, the panel scans BurnInitiated events emitted by the user's EVM address, in 49 k-block chunks back ~200 k blocks.
Paste any Octra tx hash in the History panel to inspect its claim status, even if it was created from a different machine.
Default gas for verifyAndMint:
- Gas Limit: 150,000
- Max Fee: 3 Gwei
These can be overridden in the OctWa transaction approval popup. Actual usage on mainnet is ~131,500 gas.
- React 18 + TypeScript
- Vite
- Tailwind CSS (sharp edges, Fira Code font)
- Framer Motion
- ethers.js v6
@octwa/sdk@2.1.0(RFC-O-1)
bridge/src/
├── components/
│ ├── BridgePanel.tsx # Main bridge UI
│ ├── HistoryPanel.tsx # On-chain history + manual lookup
│ ├── AboutPanel.tsx # Contract info
│ ├── SettingsPanel.tsx # RPC config
│ ├── Header.tsx
│ ├── Sidebar.tsx
│ └── Footer.tsx
├── lib/
│ ├── bridge-service.ts # lockOctOnOctra, waitForLockedEvent, claimWoctOnEthereum, burnWoctToOctra
│ ├── on-chain-history.ts # fetchBridgeHistory, fetchBurnHistory, lookupTxHash
│ ├── pending-claims.ts # localStorage tracker for the in-flight ETH claim tx
│ ├── octra-rpc.ts # Read-only Octra JSON-RPC helpers (balance, receipts, pause check)
│ ├── constants.ts # Contract addresses, fixed bridge message fields
│ └── types.ts
└── hooks/
└── useWallets.ts # OctraSDK lifecycle + balances
MIT