A Multi-Party Computation (MPC) wallet browser extension built with WXT, Svelte, and Rust/WebAssembly. This extension enables secure distributed key generation and signing operations across multiple parties using WebRTC for peer-to-peer communication.
- Architecture Overview
- Components
- Message System
- Message Flow Patterns
- WebSocket Communication
- WebRTC Management
- Installation
- Development
- Usage
- API Reference
The MPC Wallet Extension follows a Chrome Extension Manifest V3 architecture with four main contexts that communicate via strongly-typed messages:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Popup Page │ │ Background Page │ │ Offscreen Page │
│ │ │ │ │ │
│ - UI Components │ │ - Service Worker│ │ - WebRTC Manager│
│ - State Display │◄──►│ - Message Router│◄──►│ - DOM Access │
│ - User Actions │ │ - WebSocket │ │ - Crypto Ops │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
└───────────────────────┼───────────────────────┘
│
┌─────────────────┐
│ Content Script │
│ │
│ - Web Page Hook │◄── Web Page
│ - JSON-RPC Proxy│ (window.ethereum)
└─────────────────┘
Location: /src/entrypoints/background/index.ts
Responsibilities:
- Central message router for all communication
- WebSocket client management for signaling server
- Account and network services
- Offscreen document lifecycle management
- RPC request handling for blockchain operations
Key Services:
AccountService
: Manages wallet accounts and addressesNetworkService
: Handles blockchain network configurationsWalletClientService
: Provides blockchain client functionalityWebSocketClient
: Manages connection to signaling server
Location: /src/entrypoints/popup/App.svelte
Responsibilities:
- User interface for wallet operations
- Display connection status and peer information
- Session management UI for MPC operations
- Crypto operations (signing, address generation)
Features:
- MPC-based distributed key generation (DKG)
- Multi-chain support (Ethereum/Solana)
- Threshold message signing via MPC protocol
- Real-time peer discovery and session management
- WebRTC connection status monitoring
Location: /src/entrypoints/offscreen/index.ts
Responsibilities:
- WebRTC connection management
- P2P communication handling
- MPC session coordination
- DOM-dependent operations
Key Components:
WebRTCManager
: Handles peer-to-peer connections- Session proposal and acceptance logic
- Data channel management for MPC communication
- ICE candidate exchange and connection establishment
Location: /src/entrypoints/content/index.ts
Responsibilities:
- Injects wallet API into web pages
- Provides
window.ethereum
compatibility - Proxies JSON-RPC requests to background script
- Manages web page wallet interactions
The extension uses a comprehensive type-safe message system defined in /src/types/messages.ts
. Understanding the message flow directions is crucial for proper implementation.
Popup ◄──────────► Background ◄──────────► Offscreen
│ │ │
│ │ │
│ ▼ │
│ WebSocket Server │
│ │ │
│ │ ▼
└─────────────────────┼──────────── WebRTC Peer Network
│
▼
Content Script
│
▼
Web Page API
File: /src/types/messages.ts
- BackgroundMessage
// User initiates actions from popup UI
export type BackgroundMessage = BaseMessage & (
// Core wallet operations
| { type: 'getState' } // Request current app state
| { type: 'listdevices' } // Request peer discovery
// Session management
| { type: 'proposeSession'; session_id: string; total: number; threshold: number; participants: string[] }
| { type: 'acceptSession'; session_id: string; accepted: boolean }
// Management operations
| { type: 'createOffscreen' } // Request offscreen creation
| { type: 'getOffscreenStatus' } // Check offscreen status
| { type: 'offscreenReady' } // Signal offscreen is ready
// Communication
| { type: 'relay'; to: string; data: WebSocketMessagePayload }
| { type: 'fromOffscreen'; payload: OffscreenMessage }
// RPC operations (for blockchain interactions)
| { type: string; payload: JsonRpcRequest; action?: string; method?: string; params?: unknown[] }
);
// Example usage in popup:
chrome.runtime.sendMessage({
type: 'proposeSession',
session_id: 'session-123',
total: 3,
threshold: 2,
participants: ['peer1', 'peer2', 'peer3']
});
File: /src/types/messages.ts
- PopupMessage
// Background responds and broadcasts state updates
export type PopupMessage = BaseMessage & (
| { type: 'wsStatus'; connected: boolean; reason?: string }
| { type: 'wsMessage'; message: ServerMsg }
| { type: 'deviceList'; devices: string[] }
| { type: 'wsError'; error: string }
| { type: 'fromOffscreen'; payload: OffscreenMessage }
| { type: 'sessionUpdate'; sessionInfo: SessionInfo | null; invites: SessionInfo[] }
| { type: 'meshStatusUpdate'; status: MeshStatus }
| { type: 'dkgStateUpdate'; state: DkgState }
| { type: 'webrtcConnectionUpdate'; deviceId: string; connected: boolean }
| { type: 'proposeSession'; session_id: string; total: number; threshold: number; participants: string[] }
| { type: 'acceptSession'; session_id: string; accepted: boolean }
| InitialStateMessage // Full state on popup connection
);
// InitialStateMessage contains complete app state
export interface InitialStateMessage extends AppState {
type: 'initialState';
deviceId: string;
connecteddevices: string[];
wsConnected: boolean;
sessionInfo: SessionInfo | null;
invites: SessionInfo[];
meshStatus: { type: number };
dkgState: number;
webrtcConnections: Record<string, boolean>;
}
// Example usage in background:
broadcastToPopupPorts({
type: 'sessionUpdate',
sessionInfo: currentSession,
invites: pendingInvites
});
File: /src/types/messages.ts
- BackgroundToOffscreenMessage
// Background forwards operations to offscreen
export type BackgroundToOffscreenMessage = {
type: 'fromBackground';
payload: OffscreenMessage;
}
// Where OffscreenMessage includes:
export type OffscreenMessage = BaseMessage & (
| { type: 'relayViaWs'; to: string; data: WebRTCSignal }
| { type: 'init'; deviceId: string; wsUrl: string }
| { type: 'relayMessage'; fromdeviceId: string; data: WebSocketMessagePayload }
| { type: 'meshStatusUpdate'; status: MeshStatus }
| { type: 'dkgStateUpdate'; state: DkgState }
| { type: 'webrtcConnectionUpdate'; deviceId: string; connected: boolean }
| { type: 'sessionUpdate'; sessionInfo: SessionInfo | null; invites: SessionInfo[] }
| { type: 'webrtcMessage'; fromdeviceId: string; message: DataChannelMessage }
);
// Example usage in background:
safelySendOffscreenMessage({
type: 'fromBackground',
payload: {
type: 'init',
deviceId: 'mpc-2',
wsUrl: 'wss://auto-life.tech'
}
});
File: /src/types/messages.ts
- Uses same OffscreenMessage
type
// Offscreen reports status and forwards peer messages
// Sent as: { type: 'fromOffscreen', payload: OffscreenMessage }
// Example usage in offscreen:
chrome.runtime.sendMessage({
type: 'fromOffscreen',
payload: {
type: 'webrtcConnectionUpdate',
deviceId: 'peer-123',
connected: true
}
});
export const MESSAGE_TYPES = {
GET_STATE: "getState",
LIST_devices: "listdevices",
PROPOSE_SESSION: "proposeSession",
ACCEPT_SESSION: "acceptSession",
RELAY: "relay",
FROM_OFFSCREEN: "fromOffscreen",
OFFSCREEN_READY: "offscreenReady",
CREATE_OFFSCREEN: "createOffscreen",
GET_OFFSCREEN_STATUS: "getOffscreenStatus",
// Legacy support
ACCOUNT_MANAGEMENT: "ACCOUNT_MANAGEMENT",
NETWORK_MANAGEMENT: "NETWORK_MANAGEMENT",
UI_REQUEST: "UI_REQUEST",
} as const;
// Runtime message validation functions
export function validateMessage(msg: unknown): msg is BackgroundMessage;
export function validateSessionProposal(msg: BackgroundMessage): boolean;
export function validateSessionAcceptance(msg: BackgroundMessage): boolean;
// Message type checking
export function isRpcMessage(msg: BackgroundMessage): boolean;
export function isAccountManagement(msg: BackgroundMessage): boolean;
export function isNetworkManagement(msg: BackgroundMessage): boolean;
export function isUIRequest(msg: BackgroundMessage): boolean;
Background Script Startup
│
├─► Initialize Services (Account, Network, Wallet)
│
├─► Connect to WebSocket Server
│ │
│ └─► Register Peer ID: "mpc-2"
│
├─► Create Offscreen Document
│ │
│ ├─► Offscreen sends: { type: 'offscreenReady' }
│ │
│ └─► Background sends: { type: 'fromBackground', payload: { type: 'init', deviceId, wsUrl } }
│
└─► Setup Popup Port Listeners
│
└─► On popup connect: Send InitialStateMessage with full app state
Popup UI (User clicks "Propose Session")
│
├─► Send: { type: 'proposeSession', session_id, total, threshold, participants }
│
▼
Background Script
│
├─► Validate using validateSessionProposal()
│
├─► Create WebSocket message: { websocket_msg_type: 'SessionProposal', ... }
│
├─► Send to each participant via WebSocket
│ │
│ └─► wsClient.relayMessage(deviceId, proposalData)
│
└─► Broadcast to popup: { type: 'sessionUpdate', sessionInfo, invites }
Meanwhile, for receiving devices:
WebSocket Server → Background Script (Receiving Peer)
│
├─► Process session proposal in handleSessionProposal()
│
├─► Add to appState.invites
│
└─► Broadcast: { type: 'sessionUpdate', invites: [...] }
│
└─► Popup shows invitation UI
Popup UI (User clicks "Accept Invitation")
│
├─► Send: { type: 'acceptSession', session_id, accepted: true }
│
▼
Background Script
│
├─► Validate using validateSessionAcceptance()
│
├─► Move session from invites to sessionInfo
│
├─► Send acceptance via WebSocket to other participants
│
├─► Forward to offscreen: { type: 'fromBackground', payload: { type: 'sessionUpdate', ... } }
│
▼
Offscreen Document
│
├─► Initialize WebRTC connections for all participants
│
├─► Create RTCPeerConnection for each peer
│
├─► Exchange ICE candidates via Background ↔ WebSocket ↔ devices
│
├─► Establish data channels
│
└─► Report status: { type: 'fromOffscreen', payload: { type: 'webrtcConnectionUpdate', deviceId, connected } }
│
▼
Background Script
│
├─► Update appState.webrtcConnections[deviceId] = connected
│
└─► Broadcast: { type: 'webrtcConnectionUpdate', deviceId, connected }
│
▼
Popup UI updates connection status indicators
Peer A (Offscreen) - Generate ICE candidate
│
├─► Create: { Candidate: { candidate, sdpMid, sdpMLineIndex } }
│
├─► Send: { type: 'fromOffscreen', payload: { type: 'relayViaWs', to: 'peer-B', data: signal } }
│
▼
Background Script (Peer A)
│
├─► Extract signal from OffscreenMessage
│
├─► Forward via WebSocket: wsClient.relayMessage('peer-B', signal)
│
▼
WebSocket Server
│
├─► Relay to Peer B as ServerMsg
│
▼
Background Script (Peer B)
│
├─► Receive in handleRelayMessage()
│
├─► Extract WebRTC signal: { websocket_msg_type, ...webrtcSignalData } = data
│
├─► Forward: { type: 'fromBackground', payload: { type: 'relayViaWs', to: fromdeviceId, data: webrtcSignalData } }
│
▼
Offscreen Document (Peer B)
│
├─► Process ICE candidate
│
└─► Add to appropriate RTCPeerConnection
Any State Change in Background
│
├─► Update appState object
│
├─► Broadcast to all popup ports via broadcastToPopupPorts()
│ │
│ ├─► Port-based communication (persistent connection)
│ │
│ └─► Popup receives via port.onMessage.addListener()
│
└─► Forward relevant updates to offscreen via safelySendOffscreenMessage()
│
└─► Wrapped in BackgroundToOffscreenMessage format
Popup State Updates:
│
├─► Receive via port messages (not runtime.sendMessage)
│
├─► Update Svelte reactive variables
│
└─► UI re-renders automatically
Error in Any Component
│
▼
Background Script (Error Handler)
│
├─► Log error details
│
├─► Update error state in appState
│
├─► Broadcast error: { type: 'wsError', error: errorMessage }
│
├─► Attempt recovery:
│ │
│ ├─► WebSocket reconnection
│ ├─► Offscreen document recreation
│ └─► Connection state reset
│
└─► Update UI with recovery status
Popup Error Display:
│
├─► Show error message in UI
│
├─► Provide user actions for recovery
│
└─► Clear error state when resolved
Location: Background Page (/src/entrypoints/background/websocket.ts
)
The WebSocket client connects to a signaling server for peer discovery and WebRTC signaling:
const WEBSOCKET_URL = "wss://auto-life.tech";
wsClient = new WebSocketClient(WEBSOCKET_URL);
- Registration: devices register with their unique ID
- Peer Discovery: List available devices for MPC sessions
- Relay: Forward WebRTC signaling data between devices
- Session Management: Coordinate MPC session proposals
- Automatic reconnection with exponential backoff
- Connection state monitoring and UI updates
- Error handling and recovery mechanisms
Location: Offscreen Page (/src/entrypoints/offscreen/webrtc.ts
)
The WebRTC manager handles:
- Peer Connection Creation: RTCPeerConnection instances for each participant
- Data Channel Setup: Reliable data channels for MPC communication
- ICE Handling: STUN/TURN server configuration and candidate exchange
- Connection State Monitoring: Track connection health and handle failures
// Session Proposal
webRTCManager.proposeSession(sessionId, total, threshold, participants);
// Session Acceptance
webRTCManager.acceptSession(sessionId);
// Mesh Status Tracking
enum MeshStatusType {
Incomplete,
PartiallyReady,
Ready
}
- Origin Validation: Verify message sources
- Encrypted Channels: Secure WebRTC data transmission
- Isolated Contexts: Separate WebRTC operations in offscreen context
- Node.js 18+ and npm/yarn
- Chrome/Chromium browser for testing
- Rust toolchain for WASM compilation
# Clone the repository
git clone <repository-url>
cd mpc-wallet
# Install dependencies
npm install
# Build WASM modules
npm run build:wasm
# Start development server
npm run dev
# Build for production
npm run build
- Build the extension:
npm run build
- Open Chrome and navigate to
chrome://extensions/
- Enable "Developer mode"
- Click "Load unpacked" and select the
dist
folder
When developing new features, follow these patterns:
- Define Message Types: Add to
/src/types/messages.ts
- Popup Actions: Send messages via
chrome.runtime.sendMessage()
- Background Processing: Handle in
chrome.runtime.onMessage.addListener()
- Offscreen Operations: Forward via
safelySendOffscreenMessage()
- State Updates: Broadcast to all components via
broadcastToPopupPorts()
- Background Console: Check Service Worker console for message routing
- Popup Console: Monitor UI-triggered messages
- Offscreen Console: Debug WebRTC and crypto operations
- Message Validation: Use TypeScript for compile-time message validation
src/
├── entrypoints/
│ ├── background/ # Service worker
│ ├── content/ # Content scripts
│ ├── offscreen/ # Offscreen document
│ └── popup/ # Extension popup UI
├── types/ # TypeScript type definitions
├── services/ # Business logic services
└── components/ # Svelte UI components
src/types/messages.ts
: Message type definitionssrc/types/appstate.ts
: Application state typessrc/entrypoints/background/index.ts
: Main background scriptsrc/entrypoints/offscreen/webrtc.ts
: WebRTC managementsrc/entrypoints/popup/App.svelte
: Main UI component
# Run type checking
npm run type-check
# Run linting
npm run lint
# Run tests
npm run test
- Generate Wallet: Click "Show Wallet Address" to create/display address
- Sign Messages: Enter message and click "Sign Message"
- Chain Support: Switch between Ethereum (secp256k1) and Solana (ed25519)
- Peer Discovery: Click "List devices" to find available participants
- Create Session: Click "Propose Session" with 3+ devices
- Join Session: Accept incoming session invitations
- Monitor Status: View connection and session state in real-time
- Network Switching: Change blockchain networks
- Account Management: Import/export private keys
- Connection Diagnostics: Debug WebRTC and WebSocket issues
// Account Management
handleAccountManagement(action: string, payload: any)
// Network Management
handleNetworkManagement(action: string, payload: any)
// RPC Handling
handleRpcRequest(request: JsonRpcRequest)
// Session Management
proposeSession(sessionId: string, total: number, threshold: number, participants: string[])
acceptSession(sessionId: string)
resetSession()
// Communication
sendWebRTCAppMessage(todeviceId: string, message: WebRTCAppMessage)
// Connection Management
connect()
disconnect()
register(deviceId: string)
// Communication
relayMessage(to: string, data: any)
listdevices()
- Background script ensures offscreen document exists before forwarding messages
- Creation is protected against concurrent attempts
- Ready signal confirms initialization before use
- Automatic reconnection with exponential backoff
- State synchronization on reconnection
- UI reflects connection status changes
- ICE connection state monitoring
- Automatic cleanup of failed connections
- Session reset capabilities for stuck states
- Message Validation: All messages are strongly typed and validated
- Origin Checking: Content scripts verify message sources
- Isolated Contexts: WebRTC operations isolated to offscreen context
- Secure Communication: All external communication via WebSocket/WebRTC
- Private Key Security: Keys stored securely in extension storage
- Fork the repository
- Create a feature branch
- Make changes with proper TypeScript typing
- Add tests for new functionality
- Submit a pull request
[Add your license information here]
For issues and questions:
- Create an issue in the repository
- Check the console logs for debugging information
- Use the built-in diagnostic tools in the popup UI