A robust, production-ready library for connecting BEAM dApps to various BEAM wallet environments.
The original dapp-utils library has several issues:
- No connection timeout - Hangs forever if wallet extension isn't installed
- No automatic reconnection - Users must manually refresh after disconnect
- Memory leaks - Event listeners never cleaned up
- Race conditions - Multiple reconnect attempts create duplicate listeners
- Poor error handling - No distinction between "not installed" vs "user rejected"
This library solves all these issues with a modern, Promise-based API.
- Automatic Reconnection - Exponential backoff with configurable attempts
- Connection State Machine - Clear states: disconnected → connecting → connected
- Event-Driven Architecture - Subscribe to connection state changes
- Promise-Based API - All methods return Promises with proper timeouts
- Heartbeat Monitoring - Detect stale connections automatically
- Memory Safe - Proper cleanup of event listeners and pending calls
- TypeScript Ready - Full type declarations included
- Zero Dependencies - Pure JavaScript, ~15KB minified
<script src="BeamDappConnector.js"></script>
<script>
const connector = new BeamDappConnector({
appName: 'My BEAM DApp',
debug: true
});
connector.on('connected', () => {
console.log('Connected to BEAM wallet!');
});
connector.connect()
.then(() => connector.getWalletStatus())
.then(status => console.log('Balance:', status.available));
</script>import { BeamDappConnector, ConnectionState } from './BeamDappConnector.js';
const connector = new BeamDappConnector({
appName: 'My BEAM DApp',
apiVersion: 'current',
autoReconnect: true,
debug: true
});
// Listen to events
connector.on('connected', () => console.log('Connected!'));
connector.on('disconnected', () => console.log('Disconnected!'));
connector.on('locked', () => console.log('Wallet is locked'));
// Connect
await connector.connect();
// Call contract
const result = await connector.invokeContract(
'action=view_params,cid=729fe098d9fd2b57705db1a05a74103dd4b891f535aef2ae69b47bcfdeef9cbf'
);| Environment | How it Works | Detection |
|---|---|---|
| Desktop Wallet | Qt WebChannel injection | QtWebEngine user agent |
| Web Extension | postMessage API |
Chrome browser, not mobile |
| Mobile App | Document events (Android) / Callback (iOS) | Mobile user agent |
| Headless | WebAssembly client | Manual headless: true option |
const connector = new BeamDappConnector({
// App identification
appName: 'My DApp', // Required: shown in wallet
apiVersion: 'current', // API version to request
minApiVersion: '', // Minimum API version
// Timeouts
connectionTimeout: 30000, // Connection timeout (30s)
callTimeout: 60000, // API call timeout (60s)
// Reconnection
autoReconnect: true, // Auto-reconnect on disconnect
reconnectDelay: 1000, // Initial reconnect delay (1s)
maxReconnectDelay: 30000, // Max reconnect delay (30s)
maxReconnectAttempts: 10, // Max attempts (0 = infinite)
// Heartbeat
heartbeatInterval: 15000, // Check connection every 15s
// Headless mode
headlessNode: 'eu-node01.masternet.beam.mw:8200',
// UI
showLoader: true, // Show connection overlay
// Debug
debug: false // Enable console logging
});// Connect to wallet (auto-detects environment)
await connector.connect();
// Connect in headless mode (WebAssembly)
await connector.connect({ headless: true });
// Disconnect
await connector.disconnect();
// Check connection state
connector.isConnected(); // boolean
connector.getState(); // 'connected' | 'disconnected' | 'connecting' | ...
connector.getEnvironment(); // 'desktop' | 'web' | 'mobile' | 'headless'// Connection events
connector.on('connected', () => {});
connector.on('disconnected', () => {});
connector.on('connecting', () => {});
connector.on('reconnecting', () => {});
connector.on('reconnected', () => {});
connector.on('reconnectFailed', () => {});
// Wallet state events
connector.on('locked', () => {});
connector.on('unlocked', () => {});
// Error events
connector.on('error', (err) => console.error(err));
// State change (all states)
connector.on('stateChange', (newState, oldState) => {
console.log(`State: ${oldState} → ${newState}`);
});
// Unsubscribe
const unsubscribe = connector.on('connected', handler);
unsubscribe(); // Remove listener// Get wallet status (balance, sync state)
const status = await connector.getWalletStatus();
console.log('Available BEAM:', status.available);
console.log('Height:', status.current_height);
// Get addresses
const addresses = await connector.getAddressList();
// Get UTXOs
const utxos = await connector.getUtxoList(0); // 0 = BEAM
// Get transactions
const txs = await connector.getTxList({ count: 10 });
// Get assets
const assets = await connector.getAssetsList();// View contract state (no transaction)
const params = await connector.invokeContract(
'action=view_params,cid=729fe098...'
);
// Call contract with transaction
const result = await connector.invokeContract(
'action=trade,cid=729fe098...,aid1=0,aid2=174,amount1=1000000000',
null, // No bytecode needed
true // createTx = true
);
// Process transaction data (confirm)
if (result.raw_data) {
await connector.processInvokeData(result.raw_data);
}
// Load shader bytecode
const shaderBytes = await connector.downloadShader('/shaders/my_contract.wasm');
const result = await connector.invokeContract(
'action=my_action',
shaderBytes
);// Any wallet API method
const result = await connector.callApi('tx_send', {
address: 'recipient_address',
value: 100000000, // 1 BEAM in groth
asset_id: 0
});// Environment detection
BeamDappConnector.isChrome(); // true/false
BeamDappConnector.isMobile(); // true/false
BeamDappConnector.isDesktop(); // true/false
// Amount conversion
BeamDappConnector.beamToGroth(1.5); // 150000000
BeamDappConnector.grothToBeam(150000000); // "1.50000000"
// Validation
BeamDappConnector.validateAmount("1.23"); // true
BeamDappConnector.validateAmount("abc"); // false
// Formatting
BeamDappConnector.formatAmount(1234567890); // "1,234,567,890"DISCONNECTED ──connect()──→ CONNECTING
│
┌────────────┼────────────┐
↓ ↓ ↓
CONNECTED ERROR RECONNECTING
│ │ │
│ └────────────┤
↓ ↓
LOCKED ←───────────── (auto-reconnect)
| State | Description |
|---|---|
disconnected |
Not connected to any wallet |
connecting |
Connection in progress |
connected |
Successfully connected |
reconnecting |
Attempting to reconnect |
locked |
Wallet is locked (needs password) |
error |
Connection failed |
try {
await connector.connect();
} catch (err) {
if (err.message.includes('timeout')) {
console.log('Connection timed out - is wallet installed?');
} else if (err.message.includes('rejected')) {
console.log('User rejected connection');
} else if (err.message.includes('Chrome')) {
console.log('Please use Chrome browser');
}
}
// Handle locked wallet
connector.on('locked', () => {
alert('Please unlock your BEAM wallet');
});
// Handle API errors
try {
await connector.invokeContract('action=invalid');
} catch (err) {
console.error('Contract error:', err.message);
}// OLD (dapp-utils)
Utils.initialize({
appname: 'My App',
apiResultHandler: handleResult
}, (err) => {
if (err) return console.error(err);
Utils.invokeContract('action=view', (err, res) => {
// callback-based
});
});
// NEW (BeamDappConnector)
const connector = new BeamDappConnector({ appName: 'My App' });
await connector.connect();
const res = await connector.invokeContract('action=view');
// Promise-based!import {
BeamDappConnector,
ConnectionState,
WalletEnvironment,
WalletStatus,
ConnectorConfig
} from './BeamDappConnector';
const config: ConnectorConfig = {
appName: 'My TypeScript DApp',
debug: true
};
const connector = new BeamDappConnector(config);
connector.on('stateChange', (newState, oldState) => {
if (newState === ConnectionState.CONNECTED) {
console.log('Ready!');
}
});
const status: WalletStatus = await connector.getWalletStatus();- Chrome/Chromium 80+ (required for Web Extension)
- Safari 14+ (headless mode only)
- Firefox 78+ (headless mode only)
- Edge 80+
DappConnector/
├── BeamDappConnector.js # Main library (~15KB)
├── BeamDappConnector.d.ts # TypeScript declarations
└── README.md # This file
MIT License - Free for commercial and personal use.