@pact-toolbox/wallet-core
v0.0.0
Published
Core wallet interfaces and base implementations for Pact Toolbox
Maintainers
Readme
@pact-toolbox/wallet-core
Core wallet interfaces and base implementations for the Pact Toolbox ecosystem. This package provides the foundational types and abstractions for wallet integrations.
Installation
npm install @pact-toolbox/wallet-core
# or
pnpm add @pact-toolbox/wallet-core
# or
yarn add @pact-toolbox/wallet-coreOverview
@pact-toolbox/wallet-core defines the standard interfaces and base implementations that all wallet providers must implement. It ensures consistent behavior across different wallet types (browser extensions, mobile wallets, hardware wallets, etc.).
Key Features
- 🔌 Unified Wallet Interface - Standard API for all wallet types
- 🔐 Transaction Signing - Support for single and batch transaction signing using Pact's
PartiallySignedTransactionformat - 🌐 Multi-Network Support - Handle different Kadena networks (mainnet, testnet, devnet)
- 📱 Multiple Wallet Types - Browser extensions, mobile, hardware, built-in, desktop, and web wallets
- ⚡ Event System - React to wallet state changes
- 🛡️ Type Safety - Full TypeScript support with comprehensive types
Core Interfaces
Wallet
The main interface that all wallets must implement:
interface Wallet {
// Check if wallet is installed/available
isInstalled(): boolean;
// Connection management
connect(networkId?: string): Promise<WalletAccount>;
disconnect(networkId?: string): Promise<void>;
isConnected(networkId?: string): Promise<boolean>;
// Account information
getAccount(networkId?: string): Promise<WalletAccount>;
// Network information
getNetwork(): Promise<WalletNetwork>;
// Transaction signing (with overloads)
sign(tx: PartiallySignedTransaction): Promise<SignedTransaction>;
sign(txs: PartiallySignedTransaction[]): Promise<SignedTransaction[]>;
}WalletProvider
Factory interface for creating wallet instances:
interface WalletProvider {
readonly metadata: WalletMetadata;
isAvailable(): Promise<boolean>;
createWallet(): Promise<Wallet>;
}WalletAccount
Account information structure:
interface WalletAccount {
address: string; // Public key address (k:publicKey format)
publicKey: string; // Public key
balance?: number; // Account balance (optional)
connectedSites?: string[]; // Connected sites/dApps (optional)
connectedAt?: Date; // Connection timestamp (optional)
}WalletNetwork
Network configuration with enhanced details:
interface WalletNetwork {
id: string; // Network unique identifier
networkId: string; // Kadena network ID (mainnet01, testnet04, etc.)
name: string; // Human-readable network name
url: string; // RPC endpoint URL
explorer?: string; // Network explorer URL (optional)
isDefault?: boolean; // Whether this is the default network (optional)
}Base Implementation
The package provides BaseWallet, an abstract class that implements common functionality:
import { BaseWallet } from "@pact-toolbox/wallet-core";
import type { PartiallySignedTransaction, SignedTransaction } from "@pact-toolbox/types";
class MyCustomWallet extends BaseWallet {
isInstalled(): boolean {
// Check if wallet is available
return true;
}
async connect(networkId?: string): Promise<WalletAccount> {
// Implement connection logic
this.connected = true;
this.account = {
address: "k:public-key-here",
publicKey: "public-key-here",
};
this.network = {
id: networkId || "development",
networkId: networkId || "development",
name: "Development",
url: "http://localhost:8080",
};
return this.account;
}
// Method overloads for TypeScript
sign(tx: PartiallySignedTransaction): Promise<SignedTransaction>;
sign(txs: PartiallySignedTransaction[]): Promise<SignedTransaction[]>;
async sign(
txOrTxs: PartiallySignedTransaction | PartiallySignedTransaction[],
): Promise<SignedTransaction | SignedTransaction[]> {
// Implement signing logic for both single and batch
if (Array.isArray(txOrTxs)) {
// Batch signing
return txOrTxs.map((tx) => ({ ...tx, sigs: [{ sig: "signature" }] }));
} else {
// Single signing
return { ...txOrTxs, sigs: [{ sig: "signature" }] };
}
}
}The base class provides:
- Connection state management (
connected,account,networkproperties) - Default implementations for
getAccount()andgetNetwork()that auto-connect if needed - Default implementations for
isConnected()anddisconnect()
Error Handling
The package provides a standardized error system:
import { WalletError } from '@pact-toolbox/wallet-core';
// Create errors with specific types
throw new WalletError('CONNECTION_FAILED', 'Failed to connect to wallet');
// Available error types:
type WalletErrorType =
| "NOT_FOUND" // Wallet not found or not installed
| "NOT_CONNECTED" // Wallet is not connected
| "CONNECTION_FAILED" // Connection attempt failed
| "USER_REJECTED" // User rejected the request
| "SIGNING_FAILED" // Transaction signing failed
| "NETWORK_MISMATCH" // Network mismatch between wallet and dApp
| "TIMEOUT" // Operation timed out
| "UNKNOWN" // Unknown error
// WalletError includes:
class WalletError extends Error {
type: WalletErrorType;
cause?: unknown; // Original error if any
}
// Static factory methods for common errors:
WalletError.notFound(walletId: string)
WalletError.notConnected(walletId: string)
WalletError.connectionFailed(reason: string)
WalletError.userRejected(operation: string)
WalletError.signingFailed(reason: string)
WalletError.networkMismatch(expected: string, actual: string)
WalletError.timeout(operation: string, timeout: number)
WalletError.unknown(message: string, cause?: unknown)Utility Functions
detectBrowserExtension
Detect if a browser extension wallet is available:
import { detectBrowserExtension } from "@pact-toolbox/wallet-core";
// Check if Ecko wallet is installed
const isEckoAvailable = await detectBrowserExtension("kadena", 3000);
// Check if Chainweaver is installed
const isChainweaverAvailable = await detectBrowserExtension("kadenaSpireKey", 3000);Events
Wallets can emit events for state changes through the WalletService:
interface WalletEvents {
accountChanged: (account: WalletAccount) => void;
networkChanged: (network: WalletNetwork) => void;
connected: (wallet: Wallet) => void;
disconnected: (walletId: string) => void;
error: (error: WalletError) => void;
}Connection Options
Configure wallet connections:
interface ConnectOptions {
networkId?: string; // Target network ID
force?: boolean; // Force reconnection even if already connected
timeout?: number; // Connection timeout in milliseconds
}
interface AutoConnectOptions extends ConnectOptions {
preferredWallets?: string[]; // Wallet IDs in order of preference
skipUnavailable?: boolean; // Skip wallets that aren't available (default: true)
}Transaction Types
The package works with Pact transaction types from @pact-toolbox/types:
import type { PartiallySignedTransaction, SignedTransaction } from "@pact-toolbox/wallet-core";
// PartiallySignedTransaction structure:
interface PartiallySignedTransaction {
cmd: string; // Stringified Pact command
hash: string; // Transaction hash
sigs: Array<{
// Existing signatures (if any)
sig: string;
}>;
}
// SignedTransaction adds signatures:
interface SignedTransaction extends PartiallySignedTransaction {
sigs: Array<{
sig: string; // Cryptographic signature
}>;
}Examples
Creating a Custom Wallet Provider
import { BaseWallet, WalletProvider, WalletMetadata, detectBrowserExtension } from "@pact-toolbox/wallet-core";
import type { Wallet } from "@pact-toolbox/wallet-core";
class MyWallet extends BaseWallet {
isInstalled(): boolean {
return typeof window !== "undefined" && !!(window as any).myWallet;
}
async connect(networkId?: string): Promise<WalletAccount> {
// Implementation details...
const account = await (window as any).myWallet.connect();
this.connected = true;
this.account = {
address: `k:${account.publicKey}`,
publicKey: account.publicKey,
};
this.network = {
id: networkId || "mainnet01",
networkId: networkId || "mainnet01",
name: "Mainnet",
url: "https://api.chainweb.com",
};
return this.account;
}
sign(tx: PartiallySignedTransaction): Promise<SignedTransaction>;
sign(txs: PartiallySignedTransaction[]): Promise<SignedTransaction[]>;
async sign(
txOrTxs: PartiallySignedTransaction | PartiallySignedTransaction[],
): Promise<SignedTransaction | SignedTransaction[]> {
// Delegate to wallet extension
return (window as any).myWallet.sign(txOrTxs);
}
}
class MyWalletProvider implements WalletProvider {
readonly metadata: WalletMetadata = {
id: "my-wallet",
name: "My Custom Wallet",
description: "A custom Kadena wallet",
icon: "https://example.com/icon.png",
type: "browser-extension",
features: ["sign", "batch-sign"],
};
async isAvailable(): Promise<boolean> {
return detectBrowserExtension("myWallet", 3000);
}
async createWallet(): Promise<Wallet> {
return new MyWallet();
}
}Using Error Handling
try {
const account = await wallet.connect();
} catch (error) {
if (error instanceof WalletError) {
switch (error.type) {
case "USER_REJECTED":
console.log("User rejected connection");
break;
case "NETWORK_MISMATCH":
console.log("Wrong network");
break;
case "NOT_FOUND":
console.log("Wallet not installed");
break;
case "CONNECTION_FAILED":
console.log("Failed to connect:", error.message);
break;
default:
console.error("Wallet error:", error.message);
}
}
}
// Using static factory methods
try {
await wallet.sign(transaction);
} catch (error) {
throw WalletError.signingFailed("User declined transaction");
}Best Practices
- Always check availability before creating wallet instances
- Handle errors gracefully using the provided error types
- Implement all required methods when extending BaseWallet
- Emit appropriate events for state changes
- Support multiple networks when possible
- Validate transactions before signing
Related Packages
@pact-toolbox/wallet-adapters- Wallet provider implementations@pact-toolbox/wallet-ui- UI components for wallet selection@pact-toolbox/transaction- Transaction building with wallet integration
License
MIT
