跳到主要内容

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

PropertyDetails
StackReact 19, TypeScript 5, Vite 6, TailwindCSS 4, Radix UI
Extension FormatChrome Manifest V3 (service worker)
Crypto@noble/curves, @noble/hashes, @scure/bip32, bip39 (no ethers.js, no web3.js, no CosmJS)
Build ToolVite + @crxjs/vite-plugin
Provider APIsEIP-1193 (window.monolythium), EIP-6963, Keplr-compatible (window.keplr)
EncryptionPBKDF2 (600,000 iterations, SHA-256) + AES-256-GCM
Auto-lock15 minutes (via chrome.alarms)

Installation

From the Chrome Web Store

  1. Navigate to the Monolythium Wallet listing on the Chrome Web Store.
  2. Click Add to Chrome.
  3. The extension icon will appear in the toolbar.

Manual Installation (Developer Mode)

  1. Clone and build the extension:
git clone https://github.com/mono-labs-org/browser-wallet.git
cd browser-wallet
pnpm install
pnpm build
  1. Open chrome://extensions in Chrome.
  2. Enable Developer mode (toggle in the top-right corner).
  3. 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

  1. On the Welcome screen, click Create New Wallet.
  2. Set a password (this encrypts your mnemonic in the browser's local storage).
  3. The extension generates a 24-word BIP39 mnemonic.
  4. Write down your seed phrase on paper. Do not take a screenshot.
  5. Verify your seed phrase by selecting the words in the correct order.
  6. You are taken to the Dashboard, where your bech32 and EVM addresses are displayed.

Import Existing Wallet

  1. On the Welcome screen, click Import Wallet.
  2. Enter your 12- or 24-word BIP39 mnemonic.
  3. Set a password.
  4. 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.

  1. The popup displays the requesting site's origin (e.g., https://monohub.xyz).
  2. The user sees the message: "This site wants to view your account address and suggest transactions."
  3. 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:

MethodDescription
eth_chainIdReturns the current chain ID (hex)
eth_accountsReturns connected accounts (empty if not connected)
eth_requestAccountsRequests account access (triggers approval popup)
net_versionReturns the network version (decimal string)
eth_sendTransactionSends a transaction (triggers signing popup)
eth_signTransactionSigns a transaction without broadcasting
personal_signEIP-191 personal message signing
eth_signTypedData_v4EIP-712 structured data signing
eth_getBalanceReturns the balance of an address
eth_getTransactionCountReturns the nonce of an address
eth_callExecutes a read-only contract call
eth_estimateGasEstimates gas for a transaction
eth_blockNumberReturns the latest block number
eth_getBlockByNumberReturns block data
eth_getTransactionReceiptReturns a transaction receipt
eth_getCodeReturns the bytecode at an address
wallet_switchEthereumChainRequests a chain switch
wallet_addEthereumChainRequests 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:

FieldValue
uuidb2e3c4a0-mono-lyth-ext0-wallet00000001
nameMonolythium Wallet
rdnsxyz.monolythium.wallet
iconSVG 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:

MethodDescription
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
Keplr Fallback Behavior

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:

  1. PBKDF2 derives a 256-bit key from the user's password with 600,000 iterations and SHA-256.
  2. AES-256-GCM encrypts the mnemonic with a random 12-byte IV and 32-byte salt.
  3. 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:

ContextFileRole
Background (Service Worker)src/background/index.tsKeyring 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.tsRelays messages between the inpage script and the background service worker via chrome.runtime
Inpage Script (Page World)src/inpage/index.tsInjects window.monolythium (EIP-1193), EIP-6963 announcements, and window.keplr (Cosmos provider)

The data flow for a dApp request:

  1. The dApp calls window.monolythium.request(...).
  2. The inpage script sends a postMessage to the page.
  3. The content script (bridge) receives the message and forwards it to the background service worker via chrome.runtime.sendMessage.
  4. The background processes the request (checking permissions, signing, etc.) and returns a response.
  5. 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.

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.