# Connectors and wallets (/concepts/connectors-and-wallets)



A `WalletAdapter` is `Connector & Wallet`. The split is documentary: it makes
the orchestration / capability seam explicit.

## Connector — what butr calls [#connector--what-butr-calls]

butr's runtime calls these during connect / disconnect / hydrate. You rarely
touch them directly.

```ts
type Connector = {
  capabilities: WalletCapabilities;
  chainPlatform: "evm" | "svm";
  connect(): Promise<void>;
  disconnect?(): Promise<void>;
  getAccount(): Promise<Account | null>;
  getAccounts?(): Promise<Array<Account>>;
  icon?: string;
  id: string; // stable key: "metamask", "phantom" — pool entries keyed by this
  name: string; // UI-facing: "MetaMask", "Phantom"
  requestAccounts?(): Promise<void>;
  subscribe?(listener: (event: ConnectorEvent) => void): () => void;
};
```

`subscribe` is how wallet-side events (`accountChanged`, `disconnected`) get
bridged into butr's store; you never wire `accountsChanged` / `chainChanged`
yourself.

## Wallet — what your app calls [#wallet--what-your-app-calls]

These exist purely to give you a typed surface to a connected wallet. butr
never invokes them itself.

```ts
type Wallet = {
  getBalance(mint?: string): Promise<Balance>;
  getSigner(): Promise<unknown>; // cast to viem WalletClient, WalletStandardWallet, …
  getTransactionReceipt(tx: string): Promise<{ status: "Success" | "Error" | "Pending" }>;
  sendTx(tx: unknown, account?: Account): Promise<string>;
  sendTxToChain(
    tx: unknown,
    targetChainId: string,
    account?: Account,
    cb?: () => void,
  ): Promise<string>;
  signMessage(
    msg: Uint8Array,
    account?: Account,
  ): Promise<{ signature: Uint8Array; signedMessage: Uint8Array }>;
  switchAccount?(address: string): Promise<void>;
  switchChain(chain: ChainBase): Promise<void>;
};
```

<Callout type="warn">
  `signMessage` returns **both** `signature` and `signedMessage`. Solana Wallet Standard wallets may
  prefix or re-encode the message internally. Verify the signature against `signedMessage`, not your
  input bytes. EVM wallets echo the input.
</Callout>

## The seam: `discovery` and `createConnector` [#the-seam-discovery-and-createconnector]

`WalletManagerProvider` accepts two orthogonal ways to supply adapters, and
they compose: when an id is requested, discovered adapters are checked first,
then `createConnector`.

```ts
// WalletManagerProvider props (simplified)
type WalletManagerProviderProps = {
  /** Auto-discovery source. Omit to skip auto-discovery. */
  discovery?: WalletSource;
  /** Explicit adapter factory — resolved after `discovery`. */
  createConnector?: (id: string) => WalletAdapter | null;
  connectors?: Array<ConnectorMeta>;
  // …lifecycle callbacks
};
```

Pass `discovery` to let the provider subscribe to wallet announcements
automatically. Pass `createConnector` to register explicit adapters such as
[WalletConnect](/connectors/walletconnect) or [Ledger](/connectors/ledger).
Both can be present at the same time. A hand-written adapter supplements the
discovered ones, and an id resolves from the discovered set first.

`connectors` is an optional array of `ConnectorMeta` — `{ id, name, chainPlatform, icon?, url?, available? }` — that registers display metadata for explicit adapters so the UI can list a wallet before it has connected; discovered adapters populate their own metadata and don't need an entry here.

This is why "how do I add wallet X" always has the same shape: injected,
WalletConnect, Ledger, or a hand-written adapter all arrive through the same
`createConnector` seam.

## ConnectedWallet [#connectedwallet]

What the hooks hand back:

```ts
type ConnectedWallet = {
  account: Account; // currently-active account
  accounts: Array<Account>; // every account this wallet exposes (≥ 1)
  connector: WalletAdapter;
};
```

`Account` is `{ chain: ChainBase; id: string; walletAddress: string }`. The
chain travels *inside* the account. Switching chains re-wraps the address with
new chain data.

**Source:** `packages/core/src/types/wallet.ts`.
