A wagmi v3 config with one TwinKey line: transports: { [chain.id]: custom(twinKeyProvider()) }. The hooks below are the framework's own — useReadContract, useWriteContract, useWaitForTransactionReceipt. The write becomes a native revive.call extrinsic signed with an sr25519 key, and the receipt comes back from the Substrate archive because this chain's EVM log index never sees that call.
Read this honestly: the demo account signs server-side, so this runs in any browser with no extension. A wallet signing in the browser is what the main app does with your own key.
import { createConfig, WagmiProvider, useReadContract, useWriteContract } from "wagmi";
import { custom, defineChain, erc20Abi } from "viem";
const chain = defineChain({ id: 420420777, name: "Portaldot V3 Test", … });
const config = createConfig({
chains: [chain],
connectors: [twinKeyConnector()], // reports the ss58 account
transports: { [chain.id]: custom(twinKeyProvider()) }, // <- the only TwinKey line
});
// below this point it is a normal wagmi app
const { data: balance } = useReadContract({ address: TOKEN, abi: erc20Abi, functionName: "balanceOf", args: [H160] });
const { writeContractAsync } = useWriteContract();
const hash = await writeContractAsync({ address: TOKEN, abi: erc20Abi, functionName: "transfer", args: [TO, 10n ** 18n] });