butr
Connectors

Polkadot

Discover Polkadot extensions without prompting, then bridge their signer into your chain client.

@usebutr/polkadot reads window.injectedWeb3. Inside autoDiscovery() it is the primary Polkadot channel; Wallet Standard polkadot:* discovery runs as a fallback only when the first injectedWeb3 scan found nothing, so a wallet on both channels lists once.

Register it

import type { WalletManagerConfig } from "@usebutr/core";
import { discoverInjectedPolkadotAdapters } from "@usebutr/polkadot";
import { WalletManagerProvider } from "@usebutr/react";

const config: WalletManagerConfig = {
  sources: [discoverInjectedPolkadotAdapters],
  storageKeyPrefix: "my-app",
};

export const Providers = ({ children }: { children: React.ReactNode }) => (
  <WalletManagerProvider config={config}>{children}</WalletManagerProvider>
);

autoDiscovery(["polkadot"]) from @usebutr/wallets adds the Wallet Standard fallback, which needs @wallet-standard/app.

Discovery scans at once and again at 250 ms and 750 ms for late-injecting extensions, and never prompts: only connect() calls enable("butr"). Known display names cover Enkrypt, Nova Wallet, Polkadot{.js}, SubWallet and Talisman; other registry keys get a name derived from the key. The adapter id is injected:polkadot:<extension key>.

What the adapter defines

Building an extrinsic needs chain metadata over RPC, which butr does not ship, so no Polkadot adapter has sendTx, signTransaction, getBalance or getTransactionReceipt. Transactions go through getSigner().

MemberinjectedWeb3Wallet Standard
signMessageOnce connect() enabled an extension that exposes signRawWith polkadot:signMessage
subscribeOnce connect() enabled an extension with accounts.subscribe✓
switchChainNone: an extension has no network to switch2+ advertised chains; re-points butr's view
disconnect✓With standard:disconnect

Check signMessage on the connected wallet, not on the discovered adapter: before connect() it is not there yet. Injected signing wraps the bytes in <Bytes>…</Bytes> before signRaw; verify against the returned signedMessage, which contains the wrapper. { account } must be an account the extension exposes. An empty account notification ends the session and disconnects the wallet.

Chains

An injected account works on any Substrate chain unless the extension pins it to one through genesisHash; pinned accounts carry that chain, the rest are labelled Polkadot. POLKADOT_CHAINS in @usebutr/core holds Polkadot, Kusama, Paseo and Westend; a chain id is polkadot: plus the first 32 hex characters of the genesis hash. None of this changes your chain client's RPC endpoint.

Bridge the signer

Injected adapters resolve { kind: "polkadot-injected", extension, extensionName }. extensionName is what polkadot-api's connectInjectedExtension takes; sign as the connected wallet's account:

import type { ConnectedWallet } from "@usebutr/core";
import { connectInjectedExtension } from "polkadot-api/pjs-signer";

export const getPapiSigner = async (wallet: ConnectedWallet<"polkadot">) => {
  const signer = await wallet.connector.getSigner();
  if (signer.kind !== "polkadot-injected") {
    throw new Error("Select an injectedWeb3 wallet for this PAPI bridge");
  }
  const extension = await connectInjectedExtension(signer.extensionName);
  const account = extension
    .getAccounts()
    .find((candidate) => candidate.address === wallet.account.walletAddress);
  if (account === undefined) {
    throw new Error("The extension no longer exposes this account");
  }
  return account.polkadotSigner;
};

Wallet Standard adapters resolve { kind: "wallet-standard", wallet } instead. The PAPI integration submits a complete Paseo transaction.

Source: packages/polkadot/src (injected/, wallet-standard-adapter.ts, discoverer.ts). See the API reference.