Wallet System

The Pact Toolbox wallet system provides a complete solution for wallet integration in Kadena applications. It includes wallet adapters for all major Kadena wallets, a built-in development wallet with UI, and core wallet functionality.

Packages Overview

  • @pact-toolbox/wallet-adapters - Unified interface for all Kadena wallets
  • @pact-toolbox/wallet-core - Base wallet functionality and interfaces
  • @pact-toolbox/wallet-ui - Wallet UI components
  • @pact-toolbox/dev-wallet - Built-in development wallet with web UI

Installation

# For wallet integration in your app
pnpm add @pact-toolbox/wallet-adapters

# For development wallet
pnpm add -D @pact-toolbox/dev-wallet

# For building custom wallets
pnpm add @pact-toolbox/wallet-core

Quick Start

Using Wallet Adapters

import { 
  ChainweaverWallet,
  EckoWallet,
  ZelcoreWallet,
  WalletConnectWallet,
  MagicWallet
} from '@pact-toolbox/wallet-adapters';

// Create wallet instance
const ecko = new EckoWallet();

// Check availability
if (await ecko.isInstalled()) {
  // Connect to wallet
  await ecko.connect();
  
  // Get accounts
  const accounts = await ecko.getAccounts();
  console.log('Connected accounts:', accounts);
  
  // Sign transaction
  const signedTx = await ecko.signTransaction(transaction);
}

Using Development Wallet

import { DevWallet } from '@pact-toolbox/dev-wallet';

// Create development wallet
const devWallet = new DevWallet({
  networkId: 'development',
  port: 9467 // UI port
});

// Start wallet UI
await devWallet.start();

// The wallet UI is now available at http://localhost:9467

Supported Wallets

Chainweaver

Desktop wallet by Kadena:

import { ChainweaverWallet } from '@pact-toolbox/wallet-adapters';

const chainweaver = new ChainweaverWallet();

// Check if installed (looks for local server)
if (await chainweaver.isInstalled()) {
  await chainweaver.connect();
  
  // Chainweaver-specific features
  const accounts = await chainweaver.getAccounts();
  const signedTx = await chainweaver.signTransaction(tx);
}

Ecko Wallet

Popular browser extension wallet:

import { EckoWallet } from '@pact-toolbox/wallet-adapters';

const ecko = new EckoWallet();

// Check if extension is installed
if (await ecko.isInstalled()) {
  // Request connection (prompts user)
  await ecko.connect();
  
  // Get connected account
  const account = await ecko.getAccount();
  console.log('Connected to:', account.account);
  
  // Sign and send transaction
  const result = await ecko.signAndSend(transaction);
}

Zelcore

Multi-asset wallet with Kadena support:

import { ZelcoreWallet } from '@pact-toolbox/wallet-adapters';

const zelcore = new ZelcoreWallet();

if (await zelcore.isInstalled()) {
  await zelcore.connect();
  
  // Zelcore returns multiple accounts
  const accounts = await zelcore.getAccounts();
  
  // Sign with specific account
  const signedTx = await zelcore.signTransaction(tx, {
    account: accounts[0]
  });
}

WalletConnect

Mobile wallet connection via QR code:

import { WalletConnectWallet } from '@pact-toolbox/wallet-adapters';

const walletConnect = new WalletConnectWallet({
  projectId: 'your-project-id',
  metadata: {
    name: 'My dApp',
    description: 'My Kadena dApp',
    url: 'https://mydapp.com',
    icons: ['https://mydapp.com/icon.png']
  }
});

// Initialize and show QR code
await walletConnect.connect();

// Listen for connection
walletConnect.on('connected', (accounts) => {
  console.log('Connected accounts:', accounts);
});

Magic

Email/social login wallet:

import { MagicWallet } from '@pact-toolbox/wallet-adapters';

const magic = new MagicWallet({
  apiKey: 'your-magic-api-key',
  network: 'mainnet' // or 'testnet'
});

// Login with email
await magic.loginWithEmail('user@example.com');

// Or login with social
await magic.loginWithSocial('google');

// Sign transaction
const signedTx = await magic.signTransaction(transaction);

Development Wallet

The development wallet provides a full-featured wallet UI for testing:

import { DevWallet } from '@pact-toolbox/dev-wallet';

// Create with options
const wallet = new DevWallet({
  networkId: 'development',
  port: 9467,
  accounts: [
    {
      name: 'Alice',
      keys: ['alice-public-key'],
      balance: 1000
    },
    {
      name: 'Bob', 
      keys: ['bob-public-key'],
      balance: 500
    }
  ]
});

// Start the wallet UI
await wallet.start();

// Access wallet API
const accounts = await wallet.getAccounts();
const signedTx = await wallet.signTransaction(tx);

// Stop when done
await wallet.stop();

Development Wallet Features

  • Web UI - Full-featured wallet interface at http://localhost:9467
  • Account Management - Create, import, and manage accounts
  • Transaction Signing - Visual transaction approval
  • Network Switching - Easy network selection
  • Key Management - Generate and store keys securely
  • Transaction History - View past transactions

Unified Wallet Interface

All wallet adapters implement the same interface:

interface WalletAdapter {
  // Connection
  isInstalled(): Promise<boolean>;
  connect(): Promise<void>;
  disconnect(): Promise<void>;
  isConnected(): boolean;
  
  // Account Management
  getAccounts(): Promise<Account[]>;
  getAccount(): Promise<Account | null>;
  
  // Signing
  signTransaction(tx: Transaction): Promise<SignedTransaction>;
  signAndSend(tx: Transaction): Promise<TransactionResult>;
  
  // Events
  on(event: 'connected' | 'disconnected' | 'accountsChanged', handler: Function): void;
  off(event: string, handler: Function): void;
}

Integration with Transaction Builder

The wallet adapters integrate seamlessly with the transaction builder:

import { createTransaction } from '@pact-toolbox/transaction';
import { EckoWallet } from '@pact-toolbox/wallet-adapters';

const ecko = new EckoWallet();
await ecko.connect();

// Build transaction
const tx = createTransaction()
  .code('(coin.transfer "alice" "bob" 1.0)')
  .addCap('coin.TRANSFER', 'alice', 'bob', 1.0)
  .setMeta({ chainId: '0', sender: 'alice' })
  .build();

// Sign with wallet
const signedTx = await ecko.signTransaction(tx);

// Or use the transaction builder's wallet integration
const result = await createTransaction()
  .code('(coin.transfer "alice" "bob" 1.0)')
  .sign({ wallet: ecko })
  .submitAndListen();

Advanced Usage

Multi-Wallet Support

Support multiple wallets in your app:

import { 
  ChainweaverWallet, 
  EckoWallet, 
  ZelcoreWallet 
} from '@pact-toolbox/wallet-adapters';

class WalletManager {
  private wallets: Map<string, WalletAdapter> = new Map();
  private currentWallet: WalletAdapter | null = null;
  
  constructor() {
    this.registerWallet('chainweaver', new ChainweaverWallet());
    this.registerWallet('ecko', new EckoWallet());
    this.registerWallet('zelcore', new ZelcoreWallet());
  }
  
  registerWallet(id: string, wallet: WalletAdapter) {
    this.wallets.set(id, wallet);
  }
  
  async getAvailableWallets() {
    const available = [];
    for (const [id, wallet] of this.wallets) {
      if (await wallet.isInstalled()) {
        available.push({ id, wallet });
      }
    }
    return available;
  }
  
  async connect(walletId: string) {
    const wallet = this.wallets.get(walletId);
    if (!wallet) throw new Error(`Wallet ${walletId} not found`);
    
    await wallet.connect();
    this.currentWallet = wallet;
    return wallet;
  }
  
  getCurrentWallet() {
    return this.currentWallet;
  }
}

Custom Wallet Adapter

Create your own wallet adapter:

import { WalletAdapter, Account, Transaction } from '@pact-toolbox/wallet-core';

class CustomWallet implements WalletAdapter {
  private connected = false;
  
  async isInstalled(): Promise<boolean> {
    return typeof window.customWallet !== 'undefined';
  }
  
  async connect(): Promise<void> {
    if (!await this.isInstalled()) {
      throw new Error('Custom wallet not installed');
    }
    
    // Your connection logic
    await window.customWallet.requestAccess();
    this.connected = true;
  }
  
  async disconnect(): Promise<void> {
    await window.customWallet.disconnect();
    this.connected = false;
  }
  
  isConnected(): boolean {
    return this.connected;
  }
  
  async getAccounts(): Promise<Account[]> {
    return window.customWallet.getAccounts();
  }
  
  async signTransaction(tx: Transaction): Promise<SignedTransaction> {
    return window.customWallet.sign(tx);
  }
  
  // Implement other required methods...
}

React Integration

Use wallets in React applications:

import { useState, useEffect } from 'react';
import { EckoWallet } from '@pact-toolbox/wallet-adapters';

function useWallet() {
  const [wallet] = useState(() => new EckoWallet());
  const [connected, setConnected] = useState(false);
  const [account, setAccount] = useState(null);
  
  useEffect(() => {
    wallet.on('connected', async () => {
      setConnected(true);
      const acc = await wallet.getAccount();
      setAccount(acc);
    });
    
    wallet.on('disconnected', () => {
      setConnected(false);
      setAccount(null);
    });
    
    // Check if already connected
    if (wallet.isConnected()) {
      wallet.getAccount().then(setAccount);
      setConnected(true);
    }
  }, [wallet]);
  
  const connect = async () => {
    try {
      await wallet.connect();
    } catch (error) {
      console.error('Failed to connect:', error);
    }
  };
  
  const disconnect = async () => {
    await wallet.disconnect();
  };
  
  return { wallet, connected, account, connect, disconnect };
}

// Use in component
function WalletButton() {
  const { connected, account, connect, disconnect } = useWallet();
  
  if (connected) {
    return (
      <div>
        <p>Connected: {account?.account}</p>
        <button onClick={disconnect}>Disconnect</button>
      </div>
    );
  }
  
  return <button onClick={connect}>Connect Wallet</button>;
}

Error Handling

Handle wallet errors gracefully:

import { WalletError, ErrorCode } from '@pact-toolbox/wallet-adapters';

try {
  await wallet.connect();
} catch (error) {
  if (error instanceof WalletError) {
    switch (error.code) {
      case ErrorCode.NOT_INSTALLED:
        console.error('Wallet not installed');
        // Show installation instructions
        break;
        
      case ErrorCode.USER_REJECTED:
        console.error('User rejected connection');
        break;
        
      case ErrorCode.ALREADY_CONNECTED:
        console.error('Already connected');
        break;
        
      case ErrorCode.CHAIN_MISMATCH:
        console.error('Wrong network');
        break;
        
      default:
        console.error('Wallet error:', error.message);
    }
  }
}

Best Practices

1. Check Wallet Availability

Always check if a wallet is installed before trying to connect:

const wallet = new EckoWallet();

if (!await wallet.isInstalled()) {
  // Show message to install wallet
  showInstallPrompt('Ecko Wallet');
  return;
}

await wallet.connect();

2. Handle Network Mismatches

wallet.on('chainChanged', (chainId) => {
  if (chainId !== expectedChainId) {
    alert('Please switch to the correct network');
  }
});

3. Persist Wallet Selection

// Save user's wallet choice
localStorage.setItem('selectedWallet', 'ecko');

// Restore on app load
const savedWallet = localStorage.getItem('selectedWallet');
if (savedWallet) {
  await connectWallet(savedWallet);
}

4. Provide Wallet Options

Let users choose their preferred wallet:

const availableWallets = await getAvailableWallets();

if (availableWallets.length === 0) {
  showNoWalletsMessage();
} else if (availableWallets.length === 1) {
  // Auto-connect to the only available wallet
  await connectWallet(availableWallets[0]);
} else {
  // Show wallet selection UI
  showWalletSelector(availableWallets);
}

Testing with Dev Wallet

The development wallet is perfect for testing:

import { DevWallet } from '@pact-toolbox/dev-wallet';

describe('Token Transfer', () => {
  let wallet: DevWallet;
  
  beforeAll(async () => {
    wallet = new DevWallet({
      accounts: [
        { name: 'alice', balance: 1000 },
        { name: 'bob', balance: 0 }
      ]
    });
    await wallet.start();
  });
  
  afterAll(async () => {
    await wallet.stop();
  });
  
  test('transfer tokens', async () => {
    const tx = createTransaction()
      .code('(coin.transfer "alice" "bob" 100.0)')
      .build();
      
    const signed = await wallet.signTransaction(tx);
    expect(signed).toBeDefined();
    
    // Verify balances changed
    const aliceBalance = await wallet.getBalance('alice');
    expect(aliceBalance).toBe(900);
  });
});