BitShares Wallet Browser Extension
July 19, 2026 · View on GitHub
A secure browser extension for the BitShares blockchain - similar to MetaMask but for BitShares DEX. Supports Chrome, Brave, and Firefox.
Features
Wallet Management
- Create new wallet with password-protected brainkey
- Import existing wallets via:
- Account name + password (keys verified against chain before import)
- Brainkey phrase
- WIF private keys
- Premium / non-standard account names supported on import
- Secure AES-256-GCM encryption
- Auto-lock functionality (configurable timer or disabled)
- Backup brainkey for recovery
- Multi-account support with watch-only accounts
Asset Management
- View BTS and all BitShares assets
- Real-time balance updates
- USD value display with market prices
- Full transaction history with filtering
- QR code generation for receiving
Transactions
- Send BTS and other assets
- Recipient account validation
- Optional encrypted memos
- Transaction fee calculation
- Confirmation modal for all transactions
Full Operation Signing (All 78 BitShares Operations)
The wallet supports signing all 78 BitShares blockchain operation types, not just transfers. Every operation shows a human-readable confirmation dialog before signing.
Supported Operation Categories
| Category | Operations |
|---|---|
| Account | account_create, account_update, account_upgrade, account_whitelist, account_transfer |
| Assets | asset_create, asset_update, asset_update_bitasset, asset_update_feed_producers, asset_issue, asset_reserve, asset_fund_fee_pool, asset_settle, asset_global_settle, asset_publish_feed, asset_claim_fees, asset_update_issuer, asset_claim_pool |
| Trading (DEX) | limit_order_create, limit_order_cancel, limit_order_update, call_order_update, fill_order, bid_collateral, execute_bid |
| Transfers | transfer, transfer_to_blind, transfer_from_blind, blind_transfer, override_transfer, balance_claim, asset_settle_cancel |
| Proposals | proposal_create, proposal_update, proposal_delete |
| Witnesses | witness_create, witness_update |
| Committee | committee_member_create, committee_member_update, committee_member_update_global_parameters |
| Workers | worker_create |
| Vesting | vesting_balance_create, vesting_balance_withdraw |
| Withdraw Permissions | withdraw_permission_create, withdraw_permission_update, withdraw_permission_claim, withdraw_permission_delete |
| Custom / Misc | custom, assert, fba_distribute |
| Liquidity Pools | liquidity_pool_create, liquidity_pool_delete, liquidity_pool_deposit, liquidity_pool_withdraw, liquidity_pool_exchange, liquidity_pool_update |
| Tickets | ticket_create, ticket_update |
| HTLC | htlc_create, htlc_redeem, htlc_redeemed, htlc_extend, htlc_refund |
| Custom Authority | custom_authority_create, custom_authority_update, custom_authority_delete |
| Credit | credit_offer_create, credit_offer_delete, credit_offer_update, credit_offer_accept, credit_deal_repay, credit_deal_expired, credit_deal_update |
| Samet Fund | samet_fund_create, samet_fund_delete, samet_fund_update, samet_fund_borrow, samet_fund_repay |
Human-Readable Operation Display
The signing confirmation dialog renders each operation in a readable format with clearly labeled fields. For example:
- Transfer: Shows From / To / Amount / Memo
- Limit Order Create: Shows Account / Sell Amount / Buy Amount / Expiration
- Limit Order Update: Shows Order ID / New Price / New Expiration
- Account Create: Shows Name / Registrar / Referrer / Keys
- Asset Create: Shows Symbol / Precision / Max Supply / Issuer
Unknown or future operations gracefully fall back to a formatted JSON display.
dApp Integration
- Connect to BitShares dApps
- Sign transactions for connected sites
- Manage site permissions
- Per-site auto-approve for trusted dApps (approve once, skip confirmation on subsequent requests)
- BeetEOS/Scatter API compatibility
- Event-based communication
Network Support
- BitShares Mainnet and BitShares Testnet selectable at any point
- Network selector on the welcome screen (before wallet creation or import)
- Network selector on the dashboard
- Network persisted across popup sessions
- Network-aware nodes, faucet URLs, and key prefixes (
BTS/TEST) - Service worker stays in sync with the popup network —
ensureConnected()reconnects to the stored network before every dApp request
Settings
- Auto-lock timer configuration
- Network selection (Mainnet/Testnet)
- Custom node configuration per network
- Connected sites management
- Change wallet password
- Biometric unlock (Face ID / fingerprint via WebAuthn)
- Retrieve private keys (watch-only accounts clearly identified)
Testing
The extension includes a Jest test suite (89 tests) covering core cryptographic, wallet management, and biometric authentication logic.
Running Tests
npm test
Test Coverage
tests/crypto-utils.test.js
Tests for src/lib/crypto-utils.js:
| Function | Tests |
|---|---|
generateBrainkey() | Returns string, 16 words, uppercase, non-deterministic |
normalizeBrainkey() | Trims whitespace, normalizes case, handles edge cases |
generateKeysFromBrainkey() | Returns active/owner/memo keys |
generateKeysFromPassword() | Returns keys from account + password |
deriveKey() | PBKDF2 derivation produces correct-length key |
encrypt() + decrypt() | Round-trip encryption/decryption |
generateSalt() | Produces non-empty, unique salts |
| Edge cases | Null/empty inputs, malformed data |
tests/wallet-manager.test.js
Tests for src/lib/wallet-manager.js:
| Function | Tests |
|---|---|
hasWallet() | False when empty, true after save, false after clear |
createWallet() | Resolves true, stores wallet, correct structure, unlocked on creation, brainkey encrypted |
unlock() | True with correct password, false with wrong, state changes, decryptedKeys populated |
lock() | Clears state, clears keys, idempotent, re-unlock works, sends WALLET_LOCKED message |
isUnlocked() | Reflects locked/unlocked state accurately |
Test Infrastructure
- Mocks:
tests/__mocks__/chrome.js— in-memorychrome.storage.localmock with__resetStorage()helper - Mocks:
tests/__mocks__/bitshares-api.js— stub for BitShares API calls - Timer cleanup:
afterAll()callsjest.clearAllTimers()to prevent open handle warnings from auto-lock timers - State isolation: Each test resets storage via
resetStorage()and tears down manager state viasilentLock()
Browser Support
| Browser | Status | Manifest |
|---|---|---|
| Chrome | Supported | MV3 |
| Brave | Supported | MV3 (same as Chrome) |
| Firefox | Supported | MV2 |
Installation
From Release
- Download the latest release zip for your browser from Releases
- Follow the browser-specific instructions below
Chrome / Brave
- Open
chrome://extensions/(orbrave://extensions/) - Enable "Developer mode" (toggle in top right)
- Click "Load unpacked"
- Select the extracted
distfolder
Firefox
- Open
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on"
- Select any file inside the extracted
dist-firefoxfolder
Build from Source
git clone https://github.com/pi314x/bitshares-wallet-browser-extension.git
cd bitshares-wallet-browser-extension
# Chrome / Brave
npm run build
# Firefox
npm run build:firefox
# Both
npm run build:all
Output goes to dist/ (Chrome/Brave) and dist-firefox/ (Firefox).
After building, load the extension:
- Chrome/Brave: "Load unpacked" → select the
dist/folder - Firefox:
about:debugging#/runtime/this-firefox→ "Load Temporary Add-on" → selectdist-firefox/manifest.json
Firefox note: Firefox reads
manifest.jsonin whichever directory you load. The root source directory contains the Chrome MV3 manifest and will fail with "background.service_worker is currently disabled". Always load from the builtdist-firefox/directory.
Project Structure
bitshares-wallet-browser-extension/
├── manifest.json # Chrome/Brave manifest (MV3)
├── manifest.firefox.json # Firefox manifest (MV2)
├── package.json
├── scripts/
│ └── build.js # Build script (chrome/firefox/all)
├── docs/
│ └── index.html # GitHub Pages website
├── .github/
│ └── workflows/
│ └── release.yml # Auto-build & release on tag push
├── src/
│ ├── assets/
│ │ └── icons/ # Extension icons (16, 32, 48, 128px)
│ ├── background/
│ │ ├── service-worker.js # Background service worker
│ │ └── background-firefox.html # Firefox background page
│ ├── content/
│ │ ├── inject.js # Content script injector
│ │ └── inpage.js # Page-context provider API
│ ├── lib/
│ │ ├── bitshares-api.js # BitShares blockchain API
│ │ ├── crypto-utils.js # Cryptographic utilities
│ │ ├── identicon.js # Account identicon generator
│ │ ├── qr-generator.js # QR code generator
│ │ └── wallet-manager.js # Wallet state management
│ └── popup/
│ ├── popup.html # Main popup UI
│ ├── popup.css # Styling
│ └── popup.js # Popup logic (operation signing + display)
├── tests/
│ ├── __mocks__/
│ │ ├── chrome.js # chrome.storage.local / runtime mock
│ │ └── bitshares-api.js # BitShares API stub
│ ├── crypto-utils.test.js # Tests for CryptoUtils
│ └── wallet-manager.test.js # Tests for WalletManager
├── dist/ # Chrome/Brave build output
└── dist-firefox/ # Firefox build output
API for dApp Developers
Detecting the Wallet
// Wait for wallet to be ready
window.addEventListener('bitsharesWalletReady', (event) => {
const provider = event.detail.provider;
console.log('BitShares Wallet detected!');
});
// Or check directly
if (window.bitsharesWallet) {
console.log('BitShares Wallet is available');
}
Connecting to the Wallet
// Connect — prompts the user to approve your site.
// Returns the active account and its balances.
const { account, balances } = await window.bitsharesWallet.connect();
console.log('Connected:', account.name, account.id);
console.log('Balances:', balances);
// balances: [{ asset_id: '1.3.0', amount: 123456, symbol: 'BTS', precision: 5 }, ...]
Checking / Restoring an Existing Connection
If the user already approved your site in a previous session, you can silently restore the connection without showing a popup:
const { connected, account } = await window.bitsharesWallet.checkConnection();
if (connected) {
console.log('Already connected as', account.name);
} else {
// Not yet approved — call connect() to prompt the user
const { account, balances } = await window.bitsharesWallet.connect();
}
Checking Lock State
Before calling connect() you can check whether the wallet is currently
locked so you can show a targeted message instead of waiting for the
60-second connection timeout:
const { locked } = await window.bitsharesWallet.request('isLocked', {});
if (locked) {
// Tell the user to open the extension and unlock it first
showError('Your wallet is locked. Please open the BitShares extension and unlock it.');
} else {
const { account, balances } = await window.bitsharesWallet.connect();
}
isLocked never throws — it always returns { locked: boolean }.
This makes it safe to call before any interaction that requires an
unlocked wallet (connect, transfer, signTransaction, getBalance, etc.).
Verifying the Chain ID
Always verify the chain ID before submitting transactions to make sure the wallet is connected to the expected network:
const chainId = await window.bitsharesWallet.getChainId();
// BitShares Mainnet
const BTS_MAINNET = '4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8';
// BitShares Testnet
const BTS_TESTNET = '39f5e2ede1f8bc1a3a54a7914414e3779e33193f1f5693510e73cb7a87617447';
if (chainId !== BTS_MAINNET) {
throw new Error('Please switch to BitShares Mainnet');
}
Tip: Pass chain_id directly in the connect() call so the extension
rejects the connection immediately if the user is on the wrong network:
const { account, balances } = await window.bitsharesWallet.connect({
chain_id: BTS_TESTNET // connection is rejected if wallet is not on testnet
});
Disconnecting
await window.bitsharesWallet.disconnect();
// window.bitsharesWallet.isConnected is now false
Getting Account Information
const account = await window.bitsharesWallet.getAccount();
console.log('Account name:', account.name);
console.log('Account ID:', account.id);
Signing & Broadcasting Transactions
signTransaction signs and broadcasts the transaction in one call.
The wallet fills required fees, resolves account names / asset symbols
to object IDs, refreshes block headers, signs with the user's active
key, and broadcasts to the network.
Each operation triggers a human-readable confirmation dialog in the extension popup. The user has 2 minutes to approve or reject before the request times out.
// Transfer (operation type 0)
const result = await window.bitsharesWallet.signTransaction({
operations: [
[0, {
fee: { amount: 0, asset_id: '1.3.0' },
from: account.id, // or account name, e.g. 'my-account'
to: '1.2.yyyyy', // or account name
amount: { amount: 100000, asset_id: '1.3.0' },
memo: { message: 'hello' } // optional — plain text, hex, or full memo_data
}]
]
});
// result: { success: true, result: <broadcast callback result> }
// or: { success: false, error: 'User rejected transaction' }
// Limit Order (operation type 1)
const orderResult = await window.bitsharesWallet.signTransaction({
operations: [
[1, {
fee: { amount: 0, asset_id: '1.3.0' },
seller: account.id,
amount_to_sell: { amount: 500000, asset_id: '1.3.0' },
min_to_receive: { amount: 1000, asset_id: '1.3.861' },
expiration: '2026-12-31T00:00:00'
}]
]
});
Notes
feecan be{ amount: 0, asset_id: '1.3.0' }— the wallet fills the real fee.- Account fields (
from,to,seller, …) accept either object IDs (1.2.xxxxx) or account names (my-account).- Asset fields accept either object IDs (
1.3.0) or symbols (BTS).- Multi-operation transactions are supported — each operation is shown as a separate labeled section in the confirmation dialog.
Transfers (Convenience Method)
const result = await window.bitsharesWallet.transfer({
to: 'recipient-name',
amount: { amount: 100000, asset_id: '1.3.0' },
memo: 'Thanks!'
});
Event Listeners
// Listen for account changes (user switches active account)
window.bitsharesWallet.on('accountChanged', (account) => {
console.log('Account changed to:', account.name);
});
// Listen for lock events
window.bitsharesWallet.on('locked', () => {
console.log('Wallet was locked');
});
// Listen for unlock events
window.bitsharesWallet.on('unlocked', () => {
console.log('Wallet was unlocked');
});
// Remove a specific listener
window.bitsharesWallet.off('accountChanged', myHandler);
// Remove all listeners for an event
window.bitsharesWallet.removeAllListeners('accountChanged');
BeetEOS Compatibility
The wallet also exposes window.beet and window.scatter for
compatibility with existing BeetEOS / Scatter dApps:
// Using Beet-style API
const identity = await window.beet.requestIdentity();
console.log('Account:', identity.accounts[0].name);
// Sign transaction
const result = await window.beet.requestSignature({
transaction: myTransaction
});
// Disconnect
await window.beet.forgetIdentity();
Security
- Private keys are encrypted using AES-256-GCM with PBKDF2-derived keys
- Brainkey is never stored unencrypted
- Session storage is used for temporary unlock state (cleared when browser closes)
- Auto-lock prevents unauthorized access after inactivity
- dApp connections require explicit user approval (with optional per-site auto-approve)
- Biometric unlock via WebAuthn (Face ID / fingerprint) for quick and secure access
- All transactions must be confirmed in the popup with human-readable operation details
Best Practices
- Always backup your brainkey in a secure location
- Use a strong wallet password
- Enable auto-lock with a short timer
- Review transaction details before confirming
- Only connect to trusted dApps
- Enable auto-approve only for sites you fully trust
- Disconnect from sites when not in use
BitShares Network
Mainnet Nodes
wss://node.xbts.io/wswss://cloud.xbts.io/wswss://public.xbts.io/wswss://btsws.roelandp.nl/wswss://dex.iobanker.com/wswss://api.bitshares.dev/ws
Testnet Nodes
wss://testnet.xbts.io/wswss://testnet.dex.trading/
Chain IDs
| Network | Chain ID |
|---|---|
| Mainnet | 4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8 |
| Testnet | 39f5e2ede1f8bc1a3a54a7914414e3779e33193f1f5693510e73cb7a87617447 |
Building for Distribution
npm run build:all
On tag push (git tag v1.0.0 && git push origin v1.0.0), GitHub Actions automatically builds and creates a release with both browser zips attached.
Support the Project
If this wallet saves you time or helps you trade on BitShares, consider buying a beer 🍺
Send any amount of BTS (or any asset) directly on-chain to the BitShares account:
buy-me-a-beer
You can also use the wallet website — if the extension is installed, a one-click send form is shown right on the page.
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see LICENSE file for details.
Acknowledgments
Website
The project website is hosted on GitHub Pages from the docs/ folder:
https://pi314x.github.io/bitshares-wallet-browser-extension
To deploy changes, push to the main branch. GitHub Pages serves from Settings > Pages > Source: main branch, /docs folder.
Support
- Website: pi314x.github.io/bitshares-wallet-browser-extension
- Telegram: BitShares DEV
- GitHub Issues: Report bugs
- BitShares Forum: bitsharestalk.org
Security Notice: Never share your brainkey or private keys. This wallet stores keys locally on your device only. Always verify you're using the official extension.