Guide: Wallet Connection & Signing
ChainRender apps read from the chain for free, but actions (claiming, swapping, minting) require a wallet and a
signed transaction. This guide shows the wallet + signing flow using @chainrender/client/runtime.
The model
An action is declared on a component (target, signature, arg shape). The runtime turns that declaration into a transaction request — the calldata can only be what was declared (construct-for-sign). You then hand that request to the user's wallet.
Build the transaction from a declared action
import { buildActionRequest, describeAction, splitArgs } from "@chainrender/client/runtime";
// the action the component declared:
const action = {
to: "0xYourContract",
sig: "claim(uint256)",
args: splitArgs("$amount"), // parse the declared arg template
};
// show the user exactly what they're signing, in plain language:
console.log(describeAction(action, { amount: "1000" }));
// → "Call claim(1000) on 0xYourContract"
// construct the exact calldata — nothing else is expressible:
const tx = buildActionRequest(action, { amount: "1000" });
// → { to: "0xYourContract", data: "0x…", value: 0n }
Send it with the user's wallet
buildActionRequest returns a standard { to, data, value } — use it with any wallet library (viem, ethers,
EIP-1193 provider):
import { createWalletClient, custom } from "viem";
const wallet = createWalletClient({ transport: custom(window.ethereum) });
const [account] = await wallet.getAddresses();
const hash = await wallet.sendTransaction({ account, ...tx });
Reading actions off a rendered component
If you're driving the flow from a rendered app, readAction / readActions extract the declared actions from the
component so you can wire buttons to buildActionRequest:
import { readActions } from "@chainrender/client/runtime";
const actions = readActions(componentHtmlOrAst); // the declared, gated actions
Guarantees
- The transaction can only be the declared
{to, selector, args}— a button can't smuggle a different call (enforced by rails R3–R5). See Actions & Signing. - Moving ETH requires the app to have declared
VALUE; typed-data signing requiresSIGN_MESSAGE. - All runtime hooks fail closed: no wallet present → safe no-ops, never a broken or misleading state.