polkadot-api (PAPI)
Read state with a typed API client, sign messages, and submit extrinsics through the wallet on Paseo testnet.
polkadot-api (PAPI) is the modern Polkadot TypeScript SDK. butr discovers and
manages the wallet; PAPI owns the typed chain descriptor, the RPC client and
the extrinsic builder; the wallet's injectedWeb3 interface supplies signing.
Get the wallet
import { useSelectedWallet } from "@usebutr/react";
const wallet = useSelectedWallet("polkadot");Connect a PAPI client to the chain
Point PAPI at Paseo testnet and read balances without routing through the wallet:
import { paseo } from "@polkadot-api/descriptors";
import { createClient } from "polkadot-api";
import { getWsProvider } from "polkadot-api/ws";
const client = createClient(getWsProvider("wss://paseo.rpc.amforc.com"));
const api = client.getTypedApi(paseo);
const { data } = await api.query.System.Account.getValue(wallet.account.walletAddress);
// Paseo's PAS has 10 decimals.
const pas = `${Number(data.free) / 1e10} PAS`;Sign a message
signMessage appears once connect() has enabled an extension that exposes
signRaw (or, on Wallet Standard, when the wallet advertises
polkadot:signMessage), so check it on the connected wallet:
if (!wallet.connector.signMessage) {
throw new Error(`${wallet.connector.name} cannot sign messages`);
}
const message = new TextEncoder().encode("Hello from butr + polkadot-api");
const { signature, signedMessage } = await wallet.connector.signMessage(message, {
account: wallet.account,
});
// signedMessage is the input wrapped in <Bytes>…</Bytes>: verify against it.Build and submit an extrinsic
butr ships no Polkadot RPC, so a Polkadot adapter has no sendTx. Narrow the
signer to polkadot-injected, whose extensionName is what PAPI's
connectInjectedExtension takes, then sign as the connected wallet's account:
import { MultiAddress } from "@polkadot-api/descriptors";
import { connectInjectedExtension } from "polkadot-api/pjs-signer";
const signer = await wallet.connector.getSigner();
if (signer.kind !== "polkadot-injected") {
throw new Error("Extrinsics here go through an injectedWeb3 extension");
}
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 the active account");
}
// Self-transfer of 0.1 PAS (1_000_000_000 planck).
const tx = api.tx.Balances.transfer_keep_alive({
dest: MultiAddress.Id(wallet.account.walletAddress),
value: 1_000_000_000n,
});
tx.signSubmitAndWatch(account.polkadotSigner).subscribe({
complete: () => console.log("Finalized"),
error: (error) => console.error("Tx error:", error),
next: (event) => console.log("Tx:", event.type),
});
// Look it up on https://paseo.subscan.io once finalized.Polkadot extrinsics are SCALE-encoded and carry a mortal era; PAPI handles the encoding. Signing
always happens in the wallet extension; butr never touches the private key. A Wallet Standard
Polkadot wallet resolves { kind: "wallet-standard", wallet } instead, which this bridge does
not cover.
Source: apps/demo-with-polkadot/src/app.tsx in the butr
repository. Targets
Paseo testnet. Run pnpm dev --filter=demo-with-polkadot →
https://usebutr.demo-with-polkadot.localhost.