butr
Core concepts

Platforms and chains

Every adapter is evm, svm, sui, bitcoin, or polkadot. Platforms are tracked independently. Chains use CAIP-2 identifiers.

ChainPlatform

const CHAIN_PLATFORMS = ["evm", "svm", "sui", "bitcoin", "polkadot"] as const;
type ChainPlatform = (typeof CHAIN_PLATFORMS)[number];

Every adapter carries a chainPlatform. butr tracks each platform independently: a user can connect MetaMask (evm), Phantom Solana (svm), Slush Sui (sui), and Phantom Bitcoin (bitcoin) simultaneously, and each platform has its own selected wallet. A single multi-chain wallet (Phantom EVM + Phantom SVM + Phantom BTC) appears as separate adapters with different chainPlatform values; useDiscoveredWalletsByPlatform() groups them.

ChainBase: the CAIP-2 shape

butr only needs four fields per chain. It follows CAIP-2 and never inspects beyond these.

type ChainBase = {
  id: string; // "eip155:1", "solana:mainnet"
  name: string; // "Ethereum", "Solana Mainnet"
  namespace: string; // "eip155", "solana"
  reference: string; // "1", "mainnet"
};

name is always the chain's name, never the wallet's. Adapters name a chain with resolveChain(id, knownChains), which falls back to the CAIP-2 id as the name for a chain outside the registry. You can extend ChainBase with app-specific fields (logos, explorers, RPC URLs) via structural typing; butr passes the whole object through untouched.

Chain registries

Every registry is plain data in @usebutr/core, so any transport names a chain the same way, and an app that imports none of them bundles none of them. You don't have to use them: any ChainBase-shaped object works.

ExportContents
EVM_CHAINS, EVM_CHAINS_LISTEthereum, Sepolia, Arbitrum One, Optimism, Base, Polygon, BNB Smart Chain.
SVM_CHAINS, SVM_CHAINS_LISTSolana mainnet / testnet / devnet.
SUI_CHAINS, SUI_CHAINS_LISTSui mainnet / testnet / devnet / localnet.
BITCOIN_CHAINS, BITCOIN_CHAINS_LISTBitcoin mainnet / testnet / signet.
POLKADOT_CHAINS, POLKADOT_CHAINS_LISTPolkadot, Kusama, Westend, Paseo.
CHAINS_BY_PLATFORMEvery *_CHAINS_LIST, keyed by platform.

CHAINS_BY_PLATFORM[wallet.connector.chainPlatform] is the idiomatic way to get the right chain list for a connected wallet; see multi-chain switching. A single-platform app imports only its own list, such as EVM_CHAINS_LIST.

Platform differences

EVMSVMSuiBitcoinPolkadot
DiscoveryEIP-6963 (+ injected fallback)Wallet StandardWallet StandardWallet Standard (+ injected fallback)injectedWeb3 (+ Wallet Standard)
Accounts exposedone or many (MetaMask multi-account)all at onceall at onceone or many (per address format)all at once (SS58-encoded)
requestAccountswallet_requestPermissions pickerabsent: no pickerabsent: no pickerabsent: no pickerabsent: the wallet lists every account
Chain switchingreal, via wallet_switchEthereumChainre-points butr's view; chain routes a callre-points butr's view; chain routes a callWallet Standard routes per call; injected wallets switch their network or rejectnone on injectedWeb3; an account can pin its network via genesisHash
Signature formatEIP-191 personal_sign, input echoedSolana format, may re-encode signedMessagesui:signPersonalMessageBitcoin Signed Message; not interchangeablesignRaw over raw bytes
sendTx inputEvmTransactionRequestserialized transaction bytesTransaction, its JSON, or BCS bytesBitcoinTransfer ({ amount, recipient }, satoshis)no sendTx: build extrinsics with polkadot-api
Sign without send—signTransaction → signed bytessignTransaction → { bytes, signature }signTransaction on PSBT bytes → signed PSBTthrough getSigner()

Source: packages/core/src/types/chain.ts, packages/core/src/types/platform.ts, packages/core/src/chains.ts.