Skip to main content

Overview

use-wallet uses TanStack Store to provide a reactive, framework-agnostic state management system. The store tracks wallet connections, active accounts, network configuration, and automatically persists state to localStorage.

State structure

State interface

interface State {
  wallets: WalletStateMap
  activeWallet: WalletKey | null
  activeNetwork: string
  algodClient: algosdk.Algodv2
  managerStatus: ManagerStatus
  networkConfig: Record<string, NetworkConfig>
  customNetworkConfigs: Record<string, Partial<NetworkConfig>>
}

type WalletStateMap = Partial<Record<WalletKey, WalletState>>

type WalletState = {
  accounts: WalletAccount[]
  activeAccount: WalletAccount | null
}

type WalletAccount = {
  name: string
  address: string
}

type ManagerStatus = 'initializing' | 'ready'

Default state

const DEFAULT_STATE: State = {
  wallets: {},
  activeWallet: null,
  activeNetwork: 'testnet',
  algodClient: new algosdk.Algodv2('', 'https://testnet-api.4160.nodely.dev/'),
  managerStatus: 'initializing',
  networkConfig: DEFAULT_NETWORK_CONFIG,
  customNetworkConfigs: {}
}

Accessing state

From WalletManager

The WalletManager provides convenient property accessors for common state values:
const manager = new WalletManager({ /* ... */ })

// Wallet state
const wallets = manager.wallets              // BaseWallet[]
const activeWallet = manager.activeWallet    // BaseWallet | null
const activeAccount = manager.activeAccount  // WalletAccount | null
const activeAddress = manager.activeAddress  // string | null

// Network state
const network = manager.activeNetwork        // string
const config = manager.activeNetworkConfig   // NetworkConfig
const client = manager.algodClient           // algosdk.Algodv2

// Manager state
const isReady = manager.isReady              // boolean
const status = manager.status                // ManagerStatus

Direct store access

For advanced use cases, access the store directly:
const state = manager.store.state

// Access any state property
const allWallets = state.wallets
const activeWallet = state.activeWallet
const networkConfig = state.networkConfig

Subscribing to changes

Subscribe to entire state

Subscribe to be notified of any state change:
const unsubscribe = manager.subscribe((state) => {
  console.log('State updated:', state)
  console.log('Active wallet:', state.activeWallet)
  console.log('Active network:', state.activeNetwork)
})

// Later: stop listening
unsubscribe()

Subscribe to specific values

Use TanStack Store selectors to subscribe to specific state values:
import { useStore } from '@tanstack/react-store'

const activeWalletId = useStore(manager.store, (state) => state.activeWallet)
const activeNetwork = useStore(manager.store, (state) => state.activeNetwork)
Framework adapters (React, Vue, etc.) provide hooks that handle subscriptions automatically.

State mutations

The store provides action functions for mutating state:

Wallet mutations

import { addWallet, removeWallet, setActiveWallet, setActiveAccount, setAccounts } from '@txnlab/use-wallet'

// Add a wallet (typically done by wallet.connect())
addWallet(manager.store, {
  walletId: WalletId.PERA,
  wallet: {
    accounts: [{ name: 'Pera 1', address: 'ABC...' }],
    activeAccount: { name: 'Pera 1', address: 'ABC...' }
  }
})

// Set active wallet
setActiveWallet(manager.store, { walletId: WalletId.PERA })

// Set active account within a wallet
setActiveAccount(manager.store, {
  walletId: WalletId.PERA,
  address: 'ABC...'
})

// Update wallet's accounts (e.g., after account change in wallet)
setAccounts(manager.store, {
  walletId: WalletId.PERA,
  accounts: [
    { name: 'Pera 1', address: 'ABC...' },
    { name: 'Pera 2', address: 'DEF...' }
  ]
})

// Remove a wallet (typically done by wallet.disconnect())
removeWallet(manager.store, { walletId: WalletId.PERA })

Network mutations

import { setActiveNetwork } from '@txnlab/use-wallet'
import algosdk from 'algosdk'

const newClient = new algosdk.Algodv2('', 'https://mainnet-api.4160.nodely.dev/')

setActiveNetwork(manager.store, {
  networkId: 'mainnet',
  algodClient: newClient
})

Direct state updates

For advanced use cases, update state directly:
manager.store.setState((state) => ({
  ...state,
  managerStatus: 'ready'
}))
Direct state mutations should be rare. Use action functions when possible.

State persistence

Persisted state

A subset of state is automatically persisted to localStorage:
type PersistedState = {
  wallets: WalletStateMap
  activeWallet: WalletKey | null
  activeNetwork: string
  customNetworkConfigs: Record<string, Partial<NetworkConfig>>
}
Storage key: @txnlab/use-wallet:v4

What is persisted

  • ✅ Connected wallets and their accounts
  • ✅ Active wallet and active account
  • ✅ Active network selection
  • ✅ Custom network configuration overrides
  • ❌ Algod client (reconstructed on load)
  • ❌ Manager status (always starts as ‘initializing’)

Custom network configs

Custom algod configurations are tracked separately and merged with base configs:
// Base network config (from WalletManager constructor)
const baseConfig = {
  testnet: {
    algod: { token: '', baseServer: 'https://testnet-api.4160.nodely.dev' },
    // ...
  }
}

// User customizes algod config
manager.updateAlgodConfig('testnet', {
  baseServer: 'https://custom-node.com'
})

// Persisted state tracks the delta
{
  customNetworkConfigs: {
    testnet: {
      algod: { baseServer: 'https://custom-node.com' }
    }
  }
}

// On next load, custom config is merged with base config
const finalConfig = {
  ...baseConfig.testnet,
  algod: {
    ...baseConfig.testnet.algod,
    baseServer: 'https://custom-node.com'  // Custom override
  }
}

Disabling persistence

Persistence is automatic and cannot be disabled, but you can:
  1. Reset network on each load:
    new WalletManager({
      options: { resetNetwork: true }
    })
    
  2. Clear persisted state programmatically:
    localStorage.removeItem('@txnlab/use-wallet:v4')
    

Reactive framework integration

use-wallet’s framework adapters leverage TanStack Store’s reactive capabilities:
import { useWallet } from '@txnlab/use-wallet-react'

function Component() {
  const { 
    wallets,           // Reactive: BaseWallet[]
    activeWallet,      // Reactive: BaseWallet | null
    activeAccount,     // Reactive: WalletAccount | null
    activeAddress,     // Reactive: string | null
    activeNetwork,     // Reactive: string
    algodClient        // Reactive: algosdk.Algodv2
  } = useWallet()

  // Component re-renders when any of these values change
  return <div>{activeAddress}</div>
}

Wallet state isolation

Each wallet instance has isolated state within the store:
const state = manager.store.state

// Each wallet has its own state
state.wallets[WalletId.PERA] // { accounts: [...], activeAccount: {...} }
state.wallets[WalletId.DEFLY] // { accounts: [...], activeAccount: {...} }

// Only one wallet can be active
state.activeWallet // WalletId.PERA | null

// Each wallet maintains its own active account
const peraActiveAccount = state.wallets[WalletId.PERA]?.activeAccount
const deflyActiveAccount = state.wallets[WalletId.DEFLY]?.activeAccount

Composite wallet keys

WalletConnect skins use composite keys for state isolation:
// Two WalletConnect instances with different skins
const wallets = [
  {
    id: WalletId.WALLETCONNECT,
    options: { projectId: 'abc', skin: 'biatec' }
  },
  {
    id: WalletId.WALLETCONNECT,
    options: { projectId: 'abc', skin: 'voiwallet' }
  }
]

// Each has a unique key in state
state.wallets['walletconnect:biatec']    // Biatec wallet state
state.wallets['walletconnect:voiwallet'] // Voi wallet state

Type guards

use-wallet provides type guards for runtime validation:
import {
  isValidWalletId,
  isValidWalletKey,
  isValidWalletAccount,
  isValidWalletState,
  isValidPersistedState
} from '@txnlab/use-wallet'

if (isValidWalletId(value)) {
  // value is WalletId
}

if (isValidWalletKey(key)) {
  // key is WalletKey (WalletId or composite key)
}

if (isValidWalletAccount(account)) {
  // account is WalletAccount
}

if (isValidWalletState(wallet)) {
  // wallet is WalletState
}

if (isValidPersistedState(state)) {
  // state is PersistedState
}

Wallet manager

Learn about the WalletManager class

Network configuration

Configure networks and algod clients

Build docs developers (and LLMs) love