Skip to content
docs

Quickstart

This page builds a wallet on Ethereum, Base, Bitcoin, Solana, TON, Tron and NEAR from one seed phrase. Every snippet comes from examples/quickstart in the SDK repository, which our CI compiles and runs on every change.

import { Wallet, type Policies } from '@anywalletkit/kit'
import { getRandomSeedPhrase, isValidSeedPhrase } from '@anywalletkit/core'
import { WalletManagerEvm } from '@anywalletkit/wallet-evm'
import { WalletManagerBtc } from '@anywalletkit/wallet-btc'
import { WalletManagerSolana } from '@anywalletkit/wallet-solana'
import { WalletManagerTon } from '@anywalletkit/wallet-ton'
import { WalletManagerTron } from '@anywalletkit/wallet-tron'
import { WalletManagerNear } from '@anywalletkit/wallet-near'

The SDK names chains with CAIP-2 ids: one string format for every ecosystem.

// CAIP-2 chain ids: one string names a chain on any ecosystem.
export const ETHEREUM = 'eip155:1'
export const BASE = 'eip155:8453'
export const BITCOIN = 'bip122:000000000019d6689c085ae165831e93'
export const SOLANA = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'
export const TON = 'ton:-239'
export const TRON = 'tron:0x2b6653dc'
export const NEAR = 'near:mainnet'
export function newSeedPhrase(): string {
return getRandomSeedPhrase(12) // or 24
}
export function checkSeedPhrase(words: string): boolean {
return isValidSeedPhrase(words) // BIP-39 wordlist + checksum
}

Wallet is the orchestrator from @anywalletkit/kit. Register one chain package per chain (or per chain family), each with its own RPC endpoint. Managers are built lazily, on first use.

export function createWallet(seedPhrase: string, policies?: Policies): Wallet {
const wallet = new Wallet(seedPhrase, policies ? { policies } : {})
// One EVM registration per network: each has its own RPC.
wallet.registerWallet(ETHEREUM, WalletManagerEvm, { provider: 'https://ethereum-rpc.publicnode.com', chainId: 1 })
wallet.registerWallet(BASE, WalletManagerEvm, { provider: 'https://base-rpc.publicnode.com', chainId: 8453 })
// Other ecosystems register by namespace.
wallet.registerWallet('bip122', WalletManagerBtc, {
client: { type: 'esplora-http', clientConfig: { url: 'https://mempool.space/api' } },
})
wallet.registerWallet('solana', WalletManagerSolana, { provider: 'https://api.mainnet-beta.solana.com' })
wallet.registerWallet('ton', WalletManagerTon, { tonClient: { url: 'https://toncenter.com/api/v2/jsonRPC' } })
wallet.registerWallet('tron', WalletManagerTron, { provider: 'https://api.trongrid.io' })
wallet.registerWallet('near', WalletManagerNear, { provider: 'https://free.rpc.fastnear.com' })
return wallet
}
export async function addresses(wallet: Wallet): Promise<Record<string, string>> {
const out: Record<string, string> = {}
for (const chain of [ETHEREUM, BASE, BITCOIN, SOLANA, TON, TRON, NEAR]) {
const account = await wallet.getAccount(chain) // account #0; getAccount(chain, 1) for the next one
out[chain] = await account.getAddress()
}
return out
}

Addresses are derived on the device, with no network calls. Every EVM network shares the same address. The default derivation paths are the common ones (MetaMask on EVM, Phantom on Solana and so on), so a user who imports a seed phrase from another wallet usually finds their funds. The paths are listed in Packages.

Amounts are always bigint in the chain’s base units (wei, satoshi, lamports, nanotons…). Format them for display yourself; never do money math with floating point numbers.

export async function ethBalance(wallet: Wallet): Promise<bigint> {
const account = await wallet.getAccount(ETHEREUM)
return account.getBalance() // wei, as bigint
}
export async function usdtBalance(wallet: Wallet): Promise<bigint> {
const account = await wallet.getAccount(ETHEREUM)
return account.getTokenBalance('0xdAC17F958D2ee523a2206206994597C13D831ec7') // USDT, 6 decimals
}

Every account has the same transfer call on every chain. Quote the fee first, show it to the user, then send:

export async function sendUsdt(wallet: Wallet, recipient: string, amount: bigint) {
const account = await wallet.getAccount(ETHEREUM)
const transfer = { token: '0xdAC17F958D2ee523a2206206994597C13D831ec7', recipient, amount }
const { fee } = await account.quoteTransfer(transfer) // network fee in wei, nothing is signed
console.log('fee', fee)
const { hash } = await account.transfer(transfer) // simulate → sign → broadcast
const receipt = await account.getTransactionReceipt(hash) // undefined until mined
return { hash, receipt }
}

transfer simulates the transaction before signing it. A transaction that would revert is refused before anything is signed.

export async function signIn(wallet: Wallet, message: string) {
const account = await wallet.getAccount(ETHEREUM)
const signature = await account.sign(message) // EIP-191 personal_sign
const valid = await account.verify(message, signature)
return { signature, valid }
}

Policies run before every signature and broadcast, including those made by protocol modules (swaps, staking…) through the same account. Pass them when you create the wallet:

export const policies: Policies = {
// Refuse any Ethereum write whose quoted fee is above 0.005 ETH.
maxFee: { [ETHEREUM]: 5_000_000_000_000_000n },
// Never send to these addresses.
blocklist: { [ETHEREUM]: ['0x000000000000000000000000000000000000dEaD'] },
// The last word before anything is signed: show your confirmation sheet here.
confirm: async (intent) => {
console.log(`${intent.method} on ${intent.chain} → ${intent.to ?? 'contract'}`)
return true
},
}
const wallet = createWallet(seedPhrase, policies)

More in Policies.

wallet.dispose() overwrites the seed and every derived key in memory with zeros. Call it when the wallet locks or the user signs out, and build a new Wallet when they unlock.

wallet.dispose()

The same code runs on a testnet: register the testnet’s chain id with its RPC and chainId, and fund the address from a faucet.

wallet.registerWallet('eip155:11155111', WalletManagerEvm, { provider: 'https://sepolia.drpc.org', chainId: 11155111 }) // Sepolia
const account = await wallet.getAccount('eip155:11155111')
await account.getAddress() // the same address as on mainnet: fund it from a Sepolia faucet

Base Sepolia, Arbitrum Sepolia, OP Sepolia, Polygon Amoy, BSC and Monad testnets work the same way. The Networks table ships every supported testnet (Solana devnet, Bitcoin testnet4, TON testnet, Tron Nile, NEAR testnet too) switched off, with its RPC list; show a clear “testnet” badge in the UI whenever one is on.