Signer Interface
Learn how to integrate hardware wallets, browser extensions, and other signing mechanisms using the pluggable Signer interface.
Overview
The Signer interface allows you to use different signing mechanisms beyond simple key pairs:
- Hardware Wallets - Ledger, Trezor, etc.
- Browser Extensions - MetaMask-style wallets
- HSM - Hardware Security Modules for enterprise
- Mobile Wallets - iOS/Android wallet apps
- Remote Signers - Cloud-based signing services
Signer Interface
interface Signer {
// Get the public key (synchronous, cached)
getPublicKey(): Uint8Array;
// Sign a message (asynchronous, may require user interaction)
sign(message: Uint8Array): Promise<Uint8Array>;
// Sign for a specific public key (for multi-key wallets)
signFor(publicKey: string, message: Uint8Array): Promise<Uint8Array>;
}
Built-in Signers
KeyPairSigner
The default signer that wraps a KeyPair:
import { KeyPair, KeyPairSigner } from '@convex-world/convex-ts';
const keyPair = KeyPair.fromSeed(seed);
const signer = new KeyPairSigner(keyPair);
// Use with Convex client
convex.setSigner(signer);
convex.setAddress('#1678');
// Or use KeyPair directly (automatically wrapped)
convex.setAccount('#1678', keyPair);
Custom Signer Implementation
Basic Custom Signer
import { Signer } from '@convex-world/convex-ts';
class CustomSigner implements Signer {
private publicKey: Uint8Array;
constructor(publicKey: Uint8Array) {
this.publicKey = publicKey;
}
getPublicKey(): Uint8Array {
return this.publicKey;
}
async sign(message: Uint8Array): Promise<Uint8Array> {
// Implement your signing logic
const signature = await yourSigningFunction(message);
return signature;
}
async signFor(publicKey: string, message: Uint8Array): Promise<Uint8Array> {
// Verify the public key matches
const ourPubKeyHex = Buffer.from(this.publicKey).toString('hex');
if (publicKey !== ourPubKeyHex) {
throw new Error('Public key mismatch');
}
return this.sign(message);
}
}
// Use it
const signer = new CustomSigner(publicKeyBytes);
convex.setSigner(signer);
convex.setAddress('#1678');
Hardware Wallet Signer
Example for a hardware wallet that requires user confirmation:
class HardwareWalletSigner implements Signer {
private wallet: HardwareWalletDevice;
private publicKey: Uint8Array;
constructor(wallet: HardwareWalletDevice) {
this.wallet = wallet;
// Get and cache public key
this.publicKey = wallet.getPublicKeySync();
}
getPublicKey(): Uint8Array {
// Return cached value (synchronous)
return this.publicKey;
}
async sign(message: Uint8Array): Promise<Uint8Array> {
// This will prompt user on hardware device
console.log('Please confirm transaction on your hardware wallet...');
try {
const signature = await this.wallet.signMessage(message);
return signature;
} catch (error) {
if (error.code === 'USER_REJECTED') {
throw new Error('Transaction rejected by user');
}
throw error;
}
}
async signFor(publicKey: string, message: Uint8Array): Promise<Uint8Array> {
const ourPubKeyHex = Buffer.from(this.publicKey).toString('hex');
if (publicKey !== ourPubKeyHex) {
throw new Error(`This hardware wallet does not control ${publicKey}`);
}
return this.sign(message);
}
}
// Usage
const wallet = await HardwareWalletDevice.connect();
const signer = new HardwareWalletSigner(wallet);
convex.setSigner(signer);
convex.setAddress('#1678');
// User will be prompted on device
await convex.transfer('#456', 1_000_000_000);