Browser Extension
The Monolythium Browser Extension is a Chrome MV3 wallet that provides both an EIP-1193 EVM provider and a Keplr-compatible Cosmos provider. It lets users manage wallets, connect to dApps, sign transactions, and sign messages -- all from the browser toolbar.
Overview
| Property | Details |
|---|---|
| Stack | React 19, TypeScript 5, Vite 6, TailwindCSS 4, Radix UI |
| Extension Format | Chrome Manifest V3 (service worker) |
| Crypto | @noble/curves, @noble/hashes, @scure/bip32, bip39 (no ethers.js, no web3.js, no CosmJS) |
| Build Tool | Vite + @crxjs/vite-plugin |
| Provider APIs | EIP-1193 (window.monolythium), EIP-6963, Keplr-compatible (window.keplr) |
| Encryption | PBKDF2 (600,000 iterations, SHA-256) + AES-256-GCM |
| Auto-lock | 15 minutes (via chrome.alarms) |
Installation
From the Chrome Web Store
- Navigate to the Monolythium Wallet listing on the Chrome Web Store.
- Click Add to Chrome.
- The extension icon will appear in the toolbar.
Manual Installation (Developer Mode)
- Clone and build the extension:
git clone https://github.com/mono-labs-org/browser-wallet.git
cd browser-wallet
pnpm install
pnpm build
- Open
chrome://extensionsin Chrome. - Enable Developer mode (toggle in the top-right corner).
- Click Load unpacked and select the
dist/folder.
Creating a Wallet
Click the extension icon in the toolbar to open the popup (360x600 viewport).
Create New Wallet
- On the Welcome screen, click Create New Wallet.
- Set a password (this encrypts your mnemonic in the browser's local storage).
- The extension generates a 24-word BIP39 mnemonic.
- Write down your seed phrase on paper. Do not take a screenshot.
- Verify your seed phrase by selecting the words in the correct order.
- You are taken to the Dashboard, where your bech32 and EVM addresses are displayed.
Import Existing Wallet
- On the Welcome screen, click Import Wallet.
- Enter your 12- or 24-word BIP39 mnemonic.
- Set a password.
- Your addresses are derived and displayed on the Dashboard.
Using the Extension
Dashboard
The Dashboard shows your active account's:
- Bech32 address (
mono1...) - EVM address (
0x...) - LYTH balance
Switching Networks
Click the network selector to switch between available networks (Sprintnet, Testnet, Mainnet, or custom).
Contacts
The extension includes a contact book for saving frequently used addresses with labels and network associations.
Settings
- Auto-lock: The extension automatically locks after 15 minutes of inactivity. On lock, the decrypted mnemonic is cleared from the service worker's memory.
- Lock manually: Click the lock icon to lock immediately.
Unlocking
When the extension is locked, you will see the Unlock screen. Enter your password to decrypt the vault and restore your accounts.
Connecting to dApps
When a dApp requests a connection (e.g., by calling window.monolythium.request({ method: "eth_requestAccounts" })), the extension opens an Approval Popup.
- The popup displays the requesting site's origin (e.g.,
https://monohub.xyz). - The user sees the message: "This site wants to view your account address and suggest transactions."
- Click Connect to approve or Reject to deny.
Once approved, the dApp can read your account address and request transaction signatures. Permissions are stored per-origin and persist across sessions.
Revoking Permissions
You can revoke a dApp's access from the extension's settings, which removes the origin from the permission store.
Permission Model
The extension tracks permissions per origin with two types:
- EVM chain IDs: Which EVM chains the origin has been granted access to.
- Cosmos chain IDs: Which Cosmos chains the origin has been granted access to.
Each permission records the origin URL and the timestamp when access was granted. When a dApp makes a request, the extension checks whether the origin has an active permission. If not, an approval popup is shown.
Phishing Protection
The extension includes a built-in phishing domain blocklist. Known malicious domains that impersonate Monolythium or popular DeFi sites are flagged and blocked. The blocklist is extensible and checked before processing dApp requests.
For Developers
The Monolythium Browser Extension exposes three provider interfaces that dApps can use to interact with user wallets.
EIP-1193 Provider (window.monolythium)
The extension injects a MonolythiumProvider object at window.monolythium. This is an EIP-1193-compliant Ethereum provider.
// Check if Monolythium is installed
if (window.monolythium) {
console.log("Monolythium Wallet detected");
}
// Request accounts
const accounts = await window.monolythium.request({
method: "eth_requestAccounts",
});
console.log("Connected:", accounts[0]);
// Get chain ID
const chainId = await window.monolythium.request({
method: "eth_chainId",
});
console.log("Chain ID:", chainId); // "0x40002" for Sprintnet
Supported EVM Methods:
| Method | Description |
|---|---|
eth_chainId | Returns the current chain ID (hex) |
eth_accounts | Returns connected accounts (empty if not connected) |
eth_requestAccounts | Requests account access (triggers approval popup) |
net_version | Returns the network version (decimal string) |
eth_sendTransaction | Sends a transaction (triggers signing popup) |
eth_signTransaction | Signs a transaction without broadcasting |
personal_sign | EIP-191 personal message signing |
eth_signTypedData_v4 | EIP-712 structured data signing |
eth_getBalance | Returns the balance of an address |
eth_getTransactionCount | Returns the nonce of an address |
eth_call | Executes a read-only contract call |
eth_estimateGas | Estimates gas for a transaction |
eth_blockNumber | Returns the latest block number |
eth_getBlockByNumber | Returns block data |
eth_getTransactionReceipt | Returns a transaction receipt |
eth_getCode | Returns the bytecode at an address |
wallet_switchEthereumChain | Requests a chain switch |
wallet_addEthereumChain | Requests adding a new chain |
Events:
// Listen for account changes
window.monolythium.on("accountsChanged", (accounts) => {
console.log("Accounts changed:", accounts);
});
// Listen for chain changes
window.monolythium.on("chainChanged", (chainId) => {
console.log("Chain changed:", chainId);
});
EIP-6963 Provider Discovery
The extension announces itself via the EIP-6963 standard, which allows dApps to discover available providers without relying on window.ethereum.
// Listen for provider announcements
window.addEventListener("eip6963:announceProvider", (event) => {
const { info, provider } = event.detail;
console.log("Provider found:", info.name); // "Monolythium Wallet"
console.log("RDNS:", info.rdns); // "xyz.monolythium.wallet"
console.log("UUID:", info.uuid);
});
// Request providers
window.dispatchEvent(new Event("eip6963:requestProvider"));
Provider Info:
| Field | Value |
|---|---|
uuid | b2e3c4a0-mono-lyth-ext0-wallet00000001 |
name | Monolythium Wallet |
rdns | xyz.monolythium.wallet |
icon | SVG data URI |
EIP-6963 is the recommended discovery mechanism. It avoids conflicts with other extensions that may also inject into window.ethereum.
Keplr-Compatible Cosmos Provider
If no existing Keplr extension is detected, the wallet also injects a Keplr-compatible provider at window.keplr and window.getOfflineSigner.
// Enable the chain
await window.keplr.enable("mono-sprint-1");
// Get key info
const key = await window.keplr.getKey("mono-sprint-1");
console.log("Address:", key.bech32Address);
console.log("Algorithm:", key.algo); // "ethsecp256k1" or "secp256k1"
// Get an offline signer for CosmJS
const signer = window.getOfflineSigner("mono-sprint-1");
const accounts = await signer.getAccounts();
Supported Keplr Methods:
| Method | Description |
|---|---|
enable(chainId) | Prompts user to authorize the chain (triggers approval popup) |
getKey(chainId) | Returns account info: name, algo, pubKey, bech32Address, isNanoLedger |
signDirect(chainId, signer, signDoc) | Signs a Protobuf-encoded transaction (SIGN_MODE_DIRECT) |
signAmino(chainId, signer, signDoc) | Signs a JSON-encoded transaction (SIGN_MODE_LEGACY_AMINO_JSON) |
experimentalSuggestChain(chainInfo) | Suggests a new chain configuration |
getOfflineSigner(chainId) | Returns an offline signer with getAccounts, signDirect, and signAmino |
The extension only sets window.keplr if no existing Keplr extension is detected. If the user has Keplr installed, the Monolythium wallet will not override it. Users can still access Monolythium's Cosmos provider through the window.monolythium EIP-1193 interface.
EIP-191 Personal Message Signing
dApps can request personal message signatures (commonly used for wallet authentication and off-chain signing).
const accounts = await window.monolythium.request({
method: "eth_requestAccounts",
});
const message = "Sign in to MyDApp at 2026-02-07T12:00:00Z";
const signature = await window.monolythium.request({
method: "personal_sign",
params: [
"0x" + Buffer.from(message).toString("hex"),
accounts[0],
],
});
console.log("Signature:", signature);
The extension prepends the standard \x19Ethereum Signed Message:\n{length} prefix, hashes with Keccak-256, and signs with the user's private key.
EIP-712 Structured Data Signing
The extension supports EIP-712 typed data signing, used for typed structured data (e.g., Permit2, ERC-2612 permits, order signing).
const typedData = {
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
},
primaryType: "Permit",
domain: {
name: "MyToken",
version: "1",
chainId: 262146,
verifyingContract: "0x...",
},
message: {
owner: "0x...",
spender: "0x...",
value: "1000000000000000000",
nonce: "0",
deadline: "1738886400",
},
};
const signature = await window.monolythium.request({
method: "eth_signTypedData_v4",
params: [accounts[0], JSON.stringify(typedData)],
});
The extension computes the domain separator and message hash according to the EIP-712 specification, then signs with secp256k1.
Full dApp Integration Example
// 1. Discover the provider via EIP-6963
let provider = null;
window.addEventListener("eip6963:announceProvider", (event) => {
if (event.detail.info.rdns === "xyz.monolythium.wallet") {
provider = event.detail.provider;
}
});
window.dispatchEvent(new Event("eip6963:requestProvider"));
// 2. Fallback to window.monolythium
if (!provider && window.monolythium) {
provider = window.monolythium;
}
// 3. Connect
const accounts = await provider.request({
method: "eth_requestAccounts",
});
// 4. Send a transaction
const txHash = await provider.request({
method: "eth_sendTransaction",
params: [{
from: accounts[0],
to: "0xRecipientAddress...",
value: "0xDE0B6B3A7640000", // 1 LYTH in wei (hex)
gas: "0x5208", // 21000
}],
});
console.log("TX Hash:", txHash);
// 5. Listen for events
provider.on("chainChanged", (chainId) => {
console.log("User switched to chain:", chainId);
// Reload your app state
});
provider.on("accountsChanged", (accounts) => {
if (accounts.length === 0) {
console.log("User disconnected");
} else {
console.log("Active account:", accounts[0]);
}
});
Security Model
Encrypted Vault
The extension encrypts the user's mnemonic using the Web Crypto API:
- PBKDF2 derives a 256-bit key from the user's password with 600,000 iterations and SHA-256.
- AES-256-GCM encrypts the mnemonic with a random 12-byte IV and 32-byte salt.
- The ciphertext, salt, and IV are stored in
chrome.storage.local.
No plaintext mnemonic or private key is ever persisted to disk.
Service Worker Lifecycle
The background service worker holds the decrypted mnemonic in memory only while the wallet is unlocked. When the wallet locks (manually or via auto-lock), the mnemonic is set to null and all derived accounts are cleared from memory.
Because Chrome MV3 service workers can be terminated by the browser at any time, the extension restores state from chrome.storage.local on startup. Only the encrypted vault persists; the decrypted state must be re-derived by unlocking with the password.
Auto-Lock
The extension creates a chrome.alarms alarm that fires after 15 minutes of inactivity. Any user interaction (message from a content script, popup open) resets the timer. When the alarm fires, the wallet is locked automatically.
Content Security Policy
The extension enforces script-src 'self'; object-src 'self' for all extension pages, preventing inline script injection and external script loading.
Phishing Blocklist
A built-in blocklist of known phishing domains is checked before processing dApp requests. Domains that impersonate Monolythium or popular DeFi protocols are blocked.
Architecture
The extension consists of four isolated execution contexts:
| Context | File | Role |
|---|---|---|
| Background (Service Worker) | src/background/index.ts | Keyring management, vault encryption/decryption, message routing, auto-lock timer |
| Popup (Extension UI) | src/popup/ | React SPA for wallet management, transaction approval, settings |
| Content Script (Bridge) | src/content/bridge.ts | Relays messages between the inpage script and the background service worker via chrome.runtime |
| Inpage Script (Page World) | src/inpage/index.ts | Injects window.monolythium (EIP-1193), EIP-6963 announcements, and window.keplr (Cosmos provider) |
The data flow for a dApp request:
- The dApp calls
window.monolythium.request(...). - The inpage script sends a
postMessageto the page. - The content script (bridge) receives the message and forwards it to the background service worker via
chrome.runtime.sendMessage. - The background processes the request (checking permissions, signing, etc.) and returns a response.
- The response flows back through the bridge to the inpage script, which resolves the original Promise.
For requests that require user approval (connect, sign), the background opens the popup with the approval screen before responding.
Troubleshooting
"No Monolythium provider" in my dApp
Ensure the extension is installed and enabled. Check that your content scripts are running by looking for [Monolythium Wallet] Provider injected in the browser console.
Popup shows "Invalid password"
The password must match the one used to create the vault. If you have forgotten your password, you will need to reset the extension and import your wallet from the mnemonic seed phrase.
dApp not detecting the wallet
Use EIP-6963 discovery instead of checking window.ethereum. The Monolythium extension does not set window.ethereum to avoid conflicts with other wallets. Check for window.monolythium directly or listen for the eip6963:announceProvider event.
Keplr methods not available
The extension only sets window.keplr if no existing Keplr extension is detected. If Keplr is installed, the Monolythium wallet defers to it. Disable Keplr temporarily if you want to test the Monolythium Cosmos provider.