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().
| Member | injectedWeb3 | Wallet Standard |
|---|---|---|
signMessage | Once connect() enabled an extension that exposes signRaw | With polkadot:signMessage |
subscribe | Once connect() enabled an extension with accounts.subscribe | ✓ |
switchChain | None: an extension has no network to switch | 2+ 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.