Browser Extensions

RougeChain provides browser extensions that serve as quantum-resistant wallets, similar to how MetaMask works for Ethereum — but using post-quantum cryptography.

Available Extensions

ExtensionDescriptionStore
RougeChain WalletPrimary browser extensionChrome Web Store

Features

  • Wallet — View balances, send/receive XRGE, claim faucet, custom token support
  • Encrypted Messenger — E2E encrypted chat using ML-KEM-768 + ML-DSA-65
  • PQC Mail — Encrypted email with @rouge.quant addresses
  • Vault Lock — AES-256-GCM encryption with PBKDF2 key derivation and auto-lock timer
  • Cross-browser — Chrome, Edge, Brave, Opera, Arc, Firefox (Manifest V3)

Installation

From Chrome Web Store

  1. Visit the extension page on the Chrome Web Store
  2. Click Add to Chrome
  3. The extension icon appears in your toolbar

From Source

cd apps/extension
npm install
npm run build
  1. Open chrome://extensions (or edge://extensions, brave://extensions)
  2. Enable Developer mode
  3. Click Load unpacked
  4. Select the apps/extension/dist folder

Firefox

cd apps/extension
npm install
npm run build
  1. Open about:debugging#/runtime/this-firefox
  2. Click Load Temporary Add-on
  3. Select apps/extension/dist/manifest.json

DApp Integration

The extension (and the Qwalla mobile app's dApp browser) inject a window.rougechain provider object, similar to MetaMask's window.ethereum — but the API is RougeChain-specific, not the EVM/ethers API. In particular there is no getAddress() and no getNetwork(); you obtain the user's public key by calling connect().

Which sites get the provider: from extension 1.6.0, every https:// site and http://localhost / http://127.0.0.1 — you don't need to ask us to add your domain. The first connect() from a site opens a "Connect to this site?" prompt; nothing is shared until the user approves, and every signature or send is approved separately. Users can review and disconnect sites under Settings → Connected Sites. Plain http:// public sites are not supported. (Extension 1.5.x and earlier injected only on rougechain.io, rougee.app and localhost.)

⚠️ Common mistake: calling window.rougechain.getAddress() throws e.getAddress is not a function. That method does not exist — RougeChain is not EVM-compatible. Use connect() (below) and read .publicKey.

Detecting the extension and connecting

// 1. Detect
if (!window.rougechain?.isRougeChain) {
  throw new Error("RougeChain wallet not found — install the extension or open in Qwalla.");
}

// 2. Connect (prompts the user, returns their public key)
const { publicKey, displayName, encryptionPublicKey } = await window.rougechain.connect();
console.log("Connected:", publicKey);

The provider may not be injected the instant your script runs. Either check for it after the rougechain#initialized event, or poll briefly:

function getProvider(timeoutMs = 3000) {
  return new Promise((resolve, reject) => {
    if (window.rougechain?.isRougeChain) return resolve(window.rougechain);
    const onReady = () => resolve(window.rougechain);
    window.addEventListener("rougechain#initialized", onReady, { once: true });
    setTimeout(() => reject(new Error("RougeChain wallet not detected")), timeoutMs);
  });
}

Deriving a rouge1… address

connect() returns the public key (hex). If you need the short Bech32m rouge1… address, derive it with the SDK:

import { pubkeyToAddress } from "@rougechain/sdk";
const { publicKey } = await window.rougechain.connect();
const address = await pubkeyToAddress(publicKey); // rouge1...

Provider API

MethodSignatureDescription
isRougeChainbooleanProperty — true on the authentic provider. Use it to detect the wallet.
connect()→ { publicKey, displayName?, encryptionPublicKey? }Prompt the user to connect. Returns their public key. This replaces getAddress().
getBalance()→ { balance, tokens }Get the connected wallet's XRGE balance and token balances.
signTransaction(payload)→ { signature, signedPayload, publicKey, payload }Sign a transaction payload with ML-DSA-65 (key never leaves the wallet). signedPayload is the exact JSON signed.
signMessage({ message })→ { signature, publicKey, address }Extension 1.8.0+; Qwalla after its next update. Sign a text message (at most 4,096 bytes) to prove control of the wallet: logins and token gating. Never a transaction. The user approves every request. See Wallet Authentication.
sendTransaction(payload)→ { txId, fee?, address?, preview? }Sign and broadcast. A transfer by default; contract_call / contract_deploy payloads go to the contract endpoints.
on(event, cb)voidSubscribe to events (e.g. account/connection changes).
removeListener(event, cb)voidRemove an event listener.

Signing a message (v1.8.0+)

signMessage proves that the visitor controls the connected wallet without sending anything to the chain. Feature-detect it, because older extension versions and Qwalla builds do not have it:

if (typeof window.rougechain.signMessage !== "function") {
  throw new Error("Update your RougeChain wallet to sign in.");
}
const { signature, publicKey, address } = await window.rougechain.signMessage({
  message: "I agree to the rules of this raffle.",
});
// Server: verifyMessage(publicKey, message, signature) from @rougechain/sdk 1.13.0+

The site must be connected, and the wallet asks every time, showing your site and the whole message. The signature is over "\x19RougeChain Signed Message:\n" + byte length + "\n" + message, which a node never accepts as a transaction; a message that looks like a transaction payload (JSON with a type / tx_type field) is refused, so keep using signTransaction for those. For logins use the sign-in message format, which lets the wallet show the domain, nonce and expiry and warn when the domain is not your site: Wallet Authentication has the format and a complete token-gating server.

Smart contract transactions (v1.4.0+)

The extension signs contract_call and contract_deploy payloads and shows a contract-specific approval: the contract address, method, arguments, gas limit and maximum fee for a call; the WASM size, predicted contract address and 10 XRGE fee for a deployment. Missing from, timestamp and nonce are filled in, and the filled payload is returned so you can submit it:

const { payload, signature, publicKey } = await window.rougechain.signTransaction({
  type: "contract_call",
  contractAddr: "<40-hex address>",
  method: "move",
  args: { x: 1, y: 2 },
  gasLimit: 50000,          // fee = gasLimit × 0.000001 XRGE
});
await fetch("https://api.rougechain.io/api/v2/contract/execute", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ payload, signature, public_key: publicKey }),
});

// or sign and submit in one step:
const { txId, preview } = await window.rougechain.sendTransaction({
  type: "contract_call", contractAddr, method: "move", args: { x: 1 }, gasLimit: 50000,
});

Size the gas limit from a free query (POST /api/contract/<addr>/query) first. See WASM Smart Contracts.

Payable calls (v1.5.0+; the node accepts them from block 190). A contract_call may carry attach: { symbol, amount }. amount is a positive integer JSON number: quanta for XRGE (1 XRGE = 1,000,000,000 quanta), raw units for tokens. The approval view shows "Pays amount SYMBOL to the contract (only if the call succeeds)", with XRGE formatted from quanta. For XRGE it also shows the max total cost, which is the gas fee plus the payment. A non-integer, zero, negative or unsafe (> 2^53 − 1) amount is refused before the approval opens.

await window.rougechain.sendTransaction({
  type: "contract_call", contractAddr, method: "roll", args: {}, gasLimit: 50000,
  attach: { symbol: "XRGE", amount: 500000000 },   // 0.5 XRGE, in quanta
});

Authenticity: the genuine provider also sets a non-enumerable Symbol.for("rougechain:authentic") to true, so a dApp can guard against a page that pre-defines a fake window.rougechain:

const authentic = window.rougechain?.[Symbol.for("rougechain:authentic")] === true;

Security

FeatureImplementation
Vault encryptionAES-256-GCM with a PBKDF2-derived key (600k iterations); a password (min 8 chars) is mandatory at wallet creation
Auto-lockConfigurable timer via background service worker
Key storageEncrypted blob in chrome.storage.local; the decrypted key is held only in chrome.storage.session (memory) and never written to disk. Legacy plaintext wallets are force-migrated to encrypted storage.
SigningML-DSA-65 (FIPS 204) — quantum-resistant
EncryptionML-KEM-768 (FIPS 203) — quantum-resistant

Permissions

The extensions request minimal permissions:

PermissionPurpose
storageStore encrypted wallet data
alarmsAuto-lock timer
notificationsTransaction alerts
Host permissionsConnect to RougeChain nodes (rougechain.io, testnet.rougechain.io, localhost)

Why Not MetaMask?

MetaMask and all EVM wallets use secp256k1 / ECDSA cryptography. RougeChain uses ML-DSA-65 / ML-KEM-768 (post-quantum). The key formats, signature schemes, and transaction structures are fundamentally incompatible. RougeChain's extensions are purpose-built for quantum-safe operations.

See PQC Cryptography for details on the algorithms used.