- Introduction
- Features
- Installation
- Quick Start
- API Reference
- Types
- Supported Wallets
- Network Configuration
- Theme Customization
- MLDSA Signatures
- Event Handling
- Examples
- Adding Custom Wallets
- Error Handling
- Migration Guide
- Development
- Contributing
- License
The OP_NET WalletConnect library is a React-based TypeScript library that provides a unified interface for connecting Bitcoin wallets to your decentralized applications (dApps). It enables seamless wallet connections, transaction signing, balance retrieval, and network management through a simple React context and hooks API.
Built specifically for the OP_NET Bitcoin L1 smart contract ecosystem, this library supports quantum-resistant MLDSA signatures and provides automatic RPC provider configuration for OP_NET networks.
- Multi-Wallet Support: Connect to OP_WALLET and UniSat wallets with a unified API
- React Integration: Easy-to-use React Provider and Hook pattern
- Auto-Reconnect: Automatically reconnects to previously connected wallets
- Theme Support: Built-in light, dark, and moto themes for the connection modal
- Network Detection: Automatic network detection and switching support
- MLDSA Signatures: Quantum-resistant ML-DSA signature support (OP_WALLET only)
- Balance Tracking: Real-time wallet balance updates including CSV-locked amounts
- TypeScript: Full TypeScript support with comprehensive type definitions
- Browser & Node: Works in both browser and Node.js environments
- Node.js version 24.x or higher
- React 19+
- npm or yarn
npm install @btc-vision/walletconnectyarn add @btc-vision/walletconnectThis library requires React 19+ as a peer dependency:
npm install react@^19 react-dom@^19import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { WalletConnectProvider } from '@btc-vision/walletconnect';
import App from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<WalletConnectProvider theme="dark">
<App />
</WalletConnectProvider>
</StrictMode>
);import { useWalletConnect } from '@btc-vision/walletconnect';
function WalletButton() {
const {
openConnectModal,
disconnect,
walletAddress,
publicKey,
connecting,
network,
} = useWalletConnect();
if (connecting) {
return <button disabled>Connecting...</button>;
}
if (walletAddress) {
return (
<div>
<p>Connected: {walletAddress}</p>
<p>Network: {network?.network}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}
return <button onClick={openConnectModal}>Connect Wallet</button>;
}The main provider component that wraps your application and provides wallet context to all child components.
| Prop | Type | Default | Description |
|---|---|---|---|
theme |
'light' | 'dark' | 'moto' |
'light' |
Theme for the connection modal |
children |
ReactNode |
required | Child components to render |
<WalletConnectProvider theme="dark">
<App />
</WalletConnectProvider>The primary hook for accessing wallet state and methods. Must be used within a WalletConnectProvider.
const {
// State
allWallets, // List of all supported wallets with installation status
walletType, // Current wallet type (e.g., 'OP_WALLET', 'UNISAT')
walletAddress, // Connected wallet's Bitcoin address
walletInstance, // Raw wallet instance for advanced operations
network, // Current network configuration
publicKey, // Connected wallet's public key (hex)
address, // Address object with MLDSA support
connecting, // Boolean indicating connection in progress
provider, // OP_NET RPC provider for blockchain queries
signer, // Transaction signer (UnisatSigner)
walletBalance, // Detailed wallet balance information
mldsaPublicKey, // MLDSA public key (OP_WALLET only)
hashedMLDSAKey, // SHA256 hash of MLDSA public key
// Methods
openConnectModal, // Opens the wallet selection modal
connectToWallet, // Connects to a specific wallet
disconnect, // Disconnects from current wallet
signMLDSAMessage, // Signs a message with MLDSA (quantum-resistant)
verifyMLDSASignature // Verifies an MLDSA signature
} = useWalletConnect();| Property | Type | Description |
|---|---|---|
allWallets |
WalletInformation[] |
Array of all supported wallets with their status |
walletType |
string | null |
Identifier of the connected wallet type |
walletAddress |
string | null |
Bitcoin address of the connected wallet |
walletInstance |
Unisat | null |
Raw wallet instance for direct API calls |
network |
WalletConnectNetwork | null |
Current network with chain type |
publicKey |
string | null |
Public key of connected account (hex string) |
address |
Address | null |
Address object combining publicKey and MLDSA key |
connecting |
boolean |
True while connection is in progress |
provider |
AbstractRpcProvider | null |
OP_NET JSON-RPC provider |
signer |
UnisatSigner | null |
Signer for transaction signing |
walletBalance |
WalletBalance | null |
Detailed balance breakdown |
mldsaPublicKey |
string | null |
MLDSA public key for quantum-resistant signatures |
hashedMLDSAKey |
string | null |
SHA256 hash of MLDSA public key |
| Method | Signature | Description |
|---|---|---|
openConnectModal |
() => void |
Opens the wallet selection modal |
connectToWallet |
(wallet: SupportedWallets) => void |
Connects directly to a specific wallet |
disconnect |
() => void |
Disconnects from the current wallet |
signMLDSAMessage |
(message: string) => Promise<MLDSASignature | null> |
Signs a message using MLDSA |
verifyMLDSASignature |
(message: string, signature: MLDSASignature) => Promise<boolean> |
Verifies an MLDSA signature |
The raw React context for advanced use cases. Prefer using the useWalletConnect hook.
import { WalletConnectContext } from '@btc-vision/walletconnect';
import { useContext } from 'react';
const context = useContext(WalletConnectContext);Extended network configuration with chain type information.
interface WalletConnectNetwork extends Network {
chainType: UnisatChainType; // Enum: BITCOIN_MAINNET, BITCOIN_TESTNET, BITCOIN_REGTEST
network: string; // Human-readable: 'mainnet', 'testnet', 'regtest'
}Information about a supported wallet.
interface WalletInformation {
name: SupportedWallets; // Wallet identifier
icon: string; // Base64 or URL of wallet icon
isInstalled: boolean; // Whether wallet extension is detected
isConnected: boolean; // Whether wallet is currently connected
}Detailed breakdown of wallet balance.
interface WalletBalance {
total: number; // Total balance in satoshis
confirmed: number; // Confirmed balance
unconfirmed: number; // Unconfirmed/pending balance
csv75_total: number; // Total CSV-75 locked amount
csv75_unlocked: number; // Unlocked CSV-75 amount
csv75_locked: number; // Currently locked CSV-75 amount
csv1_total: number; // Total CSV-1 locked amount
csv1_unlocked: number; // Unlocked CSV-1 amount
csv1_locked: number; // Currently locked CSV-1 amount
p2wda_total_amount: number; // Total P2WDA amount
p2wda_pending_amount: number; // Pending P2WDA amount
usd_value: string; // USD value as string
}Enum of supported wallet types.
enum SupportedWallets {
OP_WALLET = 'OP_WALLET',
UNISAT = 'UNISAT',
}The native OP_NET wallet with full feature support including MLDSA signatures.
Features:
- Full OP_NET integration
- MLDSA (quantum-resistant) signature support
- Network switching
- Account change detection
Installation: Chrome Web Store
Popular Bitcoin wallet with broad ecosystem support.
Features:
- Wide adoption
- Network switching
- Account change detection
- Transaction signing via UnisatSigner
Limitations:
- No MLDSA signature support
Installation: Chrome Web Store
The library automatically configures OP_NET RPC providers based on the connected network:
| Chain Type | Network | RPC Endpoint |
|---|---|---|
BITCOIN_MAINNET |
mainnet | https://mainnet.opnet.org |
BITCOIN_TESTNET |
testnet | https://testnet.opnet.org |
BITCOIN_REGTEST |
regtest | https://regtest.opnet.org |
const { provider, network } = useWalletConnect();
// Check current network
console.log(`Connected to: ${network?.network}`);
// Use provider for blockchain queries
if (provider) {
const balance = await provider.getBalance('bc1q...');
const blockNumber = await provider.getBlockNumber();
}The library includes three built-in themes for the connection modal:
| Theme | Description |
|---|---|
light |
Light background with dark text |
dark |
Dark background with light text |
moto |
MotoSwap branded theme |
// Light theme (default)
<WalletConnectProvider theme="light">
// Dark theme
<WalletConnectProvider theme="dark">
// Moto theme
<WalletConnectProvider theme="moto">The modal uses CSS classes that can be overridden:
/* Modal backdrop */
.wallet-connect-modal-backdrop { }
/* Modal container */
.wallet-connect-modal { }
/* Header */
.wallet-connect-header { }
/* Wallet list */
.wallet-list { }
/* Individual wallet button */
.wallet-button { }
/* Wallet icon */
.wallet-icon { }
/* Error message */
.wallet-connect-error { }ML-DSA (Module-Lattice Digital Signature Algorithm) provides quantum-resistant cryptographic signatures. This feature is currently only available with OP_WALLET.
const { mldsaPublicKey, walletType } = useWalletConnect();
const hasMLDSASupport = walletType === 'OP_WALLET' && mldsaPublicKey !== null;const { signMLDSAMessage, mldsaPublicKey } = useWalletConnect();
async function signMessage(message: string) {
if (!mldsaPublicKey) {
console.error('MLDSA not supported by current wallet');
return;
}
const signature = await signMLDSAMessage(message);
if (signature) {
console.log('Signature:', signature);
}
}const { verifyMLDSASignature } = useWalletConnect();
async function verify(message: string, signature: MLDSASignature) {
const isValid = await verifyMLDSASignature(message, signature);
console.log('Signature valid:', isValid);
}The address property combines both traditional public key and MLDSA public key:
const { address, publicKey, mldsaPublicKey } = useWalletConnect();
// address is created as:
// Address.fromString(mldsaPublicKey, publicKey)The library automatically handles wallet events:
When the user switches accounts in their wallet, the library automatically updates:
walletAddresspublicKeywalletBalance
When the user switches networks:
networkis updatedprovideris reconfigured for the new network- Balance is refreshed
When the wallet disconnects:
- All state is cleared
- Local storage is cleaned
- UI updates to disconnected state
import { useWalletConnect, SupportedWallets } from '@btc-vision/walletconnect';
import { useEffect, useState } from 'react';
function WalletManager() {
const {
openConnectModal,
connectToWallet,
disconnect,
walletAddress,
publicKey,
network,
walletBalance,
provider,
connecting,
allWallets,
} = useWalletConnect();
// Check which wallets are installed
const installedWallets = allWallets.filter(w => w.isInstalled);
// Connect directly to a specific wallet
const connectOP = () => connectToWallet(SupportedWallets.OP_WALLET);
const connectUnisat = () => connectToWallet(SupportedWallets.UNISAT);
if (connecting) {
return <div>Connecting to wallet...</div>;
}
if (!walletAddress) {
return (
<div>
<h2>Connect Your Wallet</h2>
{/* Option 1: Open modal to choose */}
<button onClick={openConnectModal}>
Choose Wallet
</button>
{/* Option 2: Direct connection buttons */}
<div>
{installedWallets.map(wallet => (
<button
key={wallet.name}
onClick={() => connectToWallet(wallet.name)}
>
Connect {wallet.name}
</button>
))}
</div>
</div>
);
}
return (
<div>
<h2>Wallet Connected</h2>
<p><strong>Address:</strong> {walletAddress}</p>
<p><strong>Public Key:</strong> {publicKey}</p>
<p><strong>Network:</strong> {network?.network}</p>
<p><strong>Balance:</strong> {walletBalance?.total} sats</p>
<p><strong>USD Value:</strong> ${walletBalance?.usd_value}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}import { useWalletConnect } from '@btc-vision/walletconnect';
import { useEffect, useState } from 'react';
function BlockchainInfo() {
const { provider, network } = useWalletConnect();
const [blockNumber, setBlockNumber] = useState<number | null>(null);
useEffect(() => {
if (!provider) return;
const fetchBlockNumber = async () => {
try {
const block = await provider.getBlockNumber();
setBlockNumber(block);
} catch (error) {
console.error('Failed to fetch block number:', error);
}
};
fetchBlockNumber();
// Poll for updates
const interval = setInterval(fetchBlockNumber, 10000);
return () => clearInterval(interval);
}, [provider]);
if (!provider) {
return <p>Connect wallet to view blockchain info</p>;
}
return (
<div>
<p>Network: {network?.network}</p>
<p>Current Block: {blockNumber}</p>
</div>
);
}import { useWalletConnect } from '@btc-vision/walletconnect';
function TransactionSigner() {
const { signer, walletInstance, publicKey } = useWalletConnect();
const signTransaction = async () => {
if (!signer || !walletInstance) {
console.error('Wallet not connected');
return;
}
try {
// Use the signer for OP_NET transactions
// The signer handles interaction with the wallet
console.log('Signer ready for transactions');
} catch (error) {
console.error('Transaction failed:', error);
}
};
const signMessage = async (message: string) => {
if (!walletInstance) return;
try {
const signature = await walletInstance.signMessage(message);
console.log('Message signed:', signature);
return signature;
} catch (error) {
console.error('Signing failed:', error);
}
};
return (
<div>
<button onClick={() => signMessage('Hello OP_NET!')}>
Sign Message
</button>
</div>
);
}import { useWalletConnect } from '@btc-vision/walletconnect';
function QuantumSafeSigning() {
const {
mldsaPublicKey,
hashedMLDSAKey,
signMLDSAMessage,
verifyMLDSASignature,
walletType,
} = useWalletConnect();
const [message, setMessage] = useState('');
const [signature, setSignature] = useState<MLDSASignature | null>(null);
const isMLDSASupported = walletType === 'OP_WALLET' && mldsaPublicKey;
const handleSign = async () => {
if (!message) return;
const sig = await signMLDSAMessage(message);
if (sig) {
setSignature(sig);
console.log('MLDSA Signature created');
}
};
const handleVerify = async () => {
if (!signature || !message) return;
const isValid = await verifyMLDSASignature(message, signature);
alert(isValid ? 'Signature is valid!' : 'Signature is invalid!');
};
if (!isMLDSASupported) {
return <p>MLDSA signatures require OP_WALLET</p>;
}
return (
<div>
<p>MLDSA Public Key: {mldsaPublicKey?.slice(0, 20)}...</p>
<p>Hashed Key: {hashedMLDSAKey}</p>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Message to sign"
/>
<button onClick={handleSign}>Sign with MLDSA</button>
{signature && (
<button onClick={handleVerify}>Verify Signature</button>
)}
</div>
);
}To add support for a new wallet, follow these steps:
Create a new directory in src/wallets/ for your wallet:
src/wallets/mywallet/
├── controller.ts
└── interface.ts
Create interface.ts with your wallet's browser API types:
// src/wallets/mywallet/interface.ts
import type { Unisat } from '@btc-vision/transaction';
export interface MyWalletInterface extends Unisat {
// Add any wallet-specific methods
customMethod(): Promise<string>;
}
// Export wallet icon (base64 or URL)
export const logo = 'data:image/svg+xml;base64,...';Create controller.ts implementing the WalletBase interface:
// src/wallets/mywallet/controller.ts
import type { MLDSASignature, Unisat, UnisatChainType } from '@btc-vision/transaction';
import { AbstractRpcProvider, JSONRpcProvider } from 'opnet';
import type { WalletBase } from '../types';
import type { MyWalletInterface } from './interface';
interface MyWalletWindow extends Window {
myWallet?: MyWalletInterface;
}
class MyWallet implements WalletBase {
private walletBase: MyWalletWindow['myWallet'];
private _isConnected: boolean = false;
isInstalled(): boolean {
if (typeof window === 'undefined') return false;
this.walletBase = (window as unknown as MyWalletWindow).myWallet;
return !!this.walletBase;
}
isConnected(): boolean {
return !!this.walletBase && this._isConnected;
}
async canAutoConnect(): Promise<boolean> {
const accounts = await this.walletBase?.getAccounts() || [];
return accounts.length > 0;
}
getWalletInstance(): Unisat | null {
return this._isConnected && this.walletBase || null;
}
async getProvider(): Promise<AbstractRpcProvider | null> {
// Return appropriate provider based on network
return new JSONRpcProvider('https://mainnet.opnet.org', networks.bitcoin);
}
async getSigner(): Promise<UnisatSigner | null> {
// Return signer if supported
return null;
}
async connect(): Promise<string[]> {
if (!this.walletBase) throw new Error('Wallet not installed');
const accounts = await this.walletBase.requestAccounts();
this._isConnected = accounts.length > 0;
return accounts;
}
async disconnect(): Promise<void> {
await this.walletBase?.disconnect();
this._isConnected = false;
}
async getPublicKey(): Promise<string | null> {
return this.walletBase?.getPublicKey() || null;
}
async getNetwork(): Promise<UnisatChainType> {
const chain = await this.walletBase?.getChain();
return chain?.enum || UnisatChainType.BITCOIN_MAINNET;
}
// Implement event hooks
setAccountsChangedHook(fn: (accounts: string[]) => void): void {
this.walletBase?.on('accountsChanged', fn);
}
removeAccountsChangedHook(): void {
// Remove listener
}
setDisconnectHook(fn: () => void): void {
this.walletBase?.on('disconnect', fn);
}
removeDisconnectHook(): void {
// Remove listener
}
setChainChangedHook(fn: (network: UnisatChainType) => void): void {
this.walletBase?.on('chainChanged', (info) => fn(info.enum));
}
removeChainChangedHook(): void {
// Remove listener
}
getChainId(): void {
throw new Error('Method not implemented.');
}
// MLDSA methods (implement if supported)
async getMLDSAPublicKey(): Promise<string | null> {
return null;
}
async getHashedMLDSAKey(): Promise<string | null> {
return null;
}
async signMLDSAMessage(message: string): Promise<MLDSASignature | null> {
return null;
}
async verifyMLDSASignature(message: string, signature: MLDSASignature): Promise<boolean> {
return false;
}
}
export default MyWallet;Update src/wallets/supported-wallets.ts:
export enum SupportedWallets {
OP_WALLET = 'OP_WALLET',
UNISAT = 'UNISAT',
MY_WALLET = 'MY_WALLET', // Add your wallet
}Update src/wallets/index.ts:
import { WalletController } from './controller';
import MyWallet from './mywallet/controller';
import { logo as MyWalletLogo } from './mywallet/interface';
import { SupportedWallets } from './supported-wallets';
// ... existing registrations ...
WalletController.registerWallet({
name: SupportedWallets.MY_WALLET,
icon: MyWalletLogo,
controller: new MyWallet(),
});The library includes built-in error handling with internationalization support.
Connection errors are displayed in the modal and can be accessed via error state:
// Errors are automatically displayed in the connection modal
// They auto-clear after 5 seconds| Error | Cause | Solution |
|---|---|---|
| "Wallet not found" | Wallet extension not detected | Install the wallet extension |
| "UNISAT is not installed" | UniSat extension missing | Install UniSat from Chrome Web Store |
| "OP_WALLET is not installed" | OP_WALLET extension missing | Install OP_WALLET from Chrome Web Store |
| "Failed to retrieve chain information" | Network query failed | Check wallet connection |
const { connectToWallet } = useWalletConnect();
const handleConnect = async (wallet: SupportedWallets) => {
try {
await connectToWallet(wallet);
} catch (error) {
console.error('Connection failed:', error);
// Handle error appropriately
}
};Old version --> New version
{ {
allWallets
openConnectModal
connect connectToWallet
disconnect disconnect
walletType walletType
walletWindowInstance walletInstance
account -
- isConnected publicKey != null
- signer signer
- address address (Address.fromString(publicKey))
publicKey (account publicKey)
walletAddress (account address)
- addressTyped
- network network
- provider provider
connecting
} = useWallet() } = useWalletConnect()
- Hook rename:
useWallet()→useWalletConnect() - Provider rename:
WalletProvider→WalletConnectProvider - Flattened state: Account properties moved to top level
- New features:
allWallets,openConnectModal,connecting, theme support - MLDSA support: New quantum-resistant signature methods
git clone https://github.com/btc-vision/walletconnect.git
cd walletconnect
npm install# Build for Node.js
npm run build
# Build for browser
npm run browserBuild
# Build both
npm run setupnpm run watchnpm run lintnpm run check:circularContributions are welcome! Please read through the CONTRIBUTING.md file for guidelines on how to submit issues, feature requests, and pull requests.
This project is open source and available under the Apache-2.0 License.
For more information, visit docs.opnet.org or the OP_NET GitHub organization.