Skip to content
docs

Accounts and signers

Piece Package Job
Wallet kit The orchestrator. Holds one seed phrase or signer, knows which chain package serves which chain, hands out accounts wrapped in your policies.
Chain manager (WalletManagerEvm, WalletManagerSolana…) wallet-* Derives accounts for one chain family and shares the RPC configuration between them.
Account wallet-* One address on one chain: balances, fee quotes, transfers, signatures.
Signer core The only place a private key exists. It turns bytes into a signature and does nothing else.

Accounts build transactions and talk to the network. The signer never touches the network and the account never touches the key. Because of that split, the key can live somewhere else entirely: a hardware wallet, an MPC node, a backend.

The signer is not reachable from an account: it is a private field (#signer), so code the account is handed to cannot sign around the account’s checks or read a key. An account tells you only what holds its key, in signerKind (seed, key or external). A local signer’s key is not reachable from JavaScript at all, not even through a TypeScript-private method.

Every account on every chain implements the same interface (IWalletAccount from @anywalletkit/core). Protocol modules are written against it, which is why one swap or staking module works with any wallet built on the SDK.

Method What it does
chain The account’s CAIP-2 chain id
getAddress() The address in the chain’s usual format
getBalance() / getTokenBalance(token) Balance in base units, as bigint
quoteTransfer(options) / quoteSendTransaction(tx) The network fee, with nothing signed
transfer({ recipient, amount, token? }) Build, check, sign and broadcast a native or token transfer
sendTransaction(tx) Sign and broadcast a transaction in the chain’s own format
signTransaction(tx) Sign without broadcasting
sign(message) / verify(message, signature) Message signatures the chain’s usual way (EIP-191, BIP-137, ed25519…)
getTransactionReceipt(hash) { status: 1 } once confirmed, { status: 0 } if it failed, undefined while pending
toReadOnlyAccount() The same address without signing ability
dispose() Overwrite this account’s key material with zeros

Chain packages add chain-specific methods on top (EIP-712 typed data on EVM, jetton transfers on TON, PSBTs on Bitcoin…). Those are listed in each package’s API reference.

A read-only account can read balances, quote fees and check receipts, but has no key. Use one for watch-only addresses, or to pass a protocol module an account that can only quote. Every chain manager has getReadOnlyAccount(address), and every account has toReadOnlyAccount().

There are three kinds:

kind Where the key is Made with
seed Derived on the device from the seed phrase new Wallet(seedPhrase) does this for you
key One imported private key: a single account, nothing to derive privateKeySigner(curve, key) from core
external Outside the app: hardware wallet, Safe, MPC, backend externalSigner() from core

An external signer receives exactly what it must sign (a 32-byte digest for secp256k1 chains, message bytes for ed25519 chains) plus a context such as evm-tx or solana-message, so a device can show the right prompt.

import { externalSigner, type SignOptions } from '@anywalletkit/core'
import { Wallet } from '@anywalletkit/kit'
import { WalletManagerEvm } from '@anywalletkit/wallet-evm'
/** Your device / MPC node / backend: it holds the key, the SDK never sees it. */
export interface RemoteKey {
publicKey: Uint8Array // 33-byte compressed secp256k1
sign(digest: Uint8Array, options: SignOptions): Promise<Uint8Array> // 65 bytes r‖s‖v
}
export function walletOnRemoteKey(remote: RemoteKey): Wallet {
const signer = externalSigner({
curve: 'secp256k1',
publicKey: remote.publicKey,
// Throw UserRejectedError from here when the user declines on the device.
sign: (payload, options) => remote.sign(payload, options),
})
const wallet = new Wallet(signer) // no seed phrase anywhere
wallet.registerWallet('eip155:1', WalletManagerEvm, { provider: 'https://ethereum-rpc.publicnode.com', chainId: 1 })
return wallet
}

A signer works on one curve: secp256k1 (EVM, Bitcoin, Tron, Zcash) or ed25519 (Solana, TON, NEAR). A Wallet built on an external signer serves the chains of that curve. An external signer also holds a single key, so it gives you one account (index 0).

Everything that holds a key has dispose(). It overwrites the key bytes in memory with zeros, and the object is unusable afterwards. wallet.dispose() disposes every manager, every account and the signer. Call it when the app locks.