# Actions & Signing

An action is how a ChainRender UI lets a user sign a transaction. Actions are **declared, not arbitrary** — which
is what makes it impossible for an app to hide a malicious transaction behind an innocent button.

## Declared actions

A button carries an action *declaration*: a target address, a function signature, and an argument shape. It does
**not** carry raw calldata. At sign time, the runtime *constructs* the calldata from the declaration:

```ts
// the button declares:
{ to: "0x…", sig: "claim(uint256)", args: "$amount" }
// the runtime constructs exactly:
claim(<the user's amount>)   // nothing else is expressible
```

The constructed transaction can **only** be the declared selector with the declared argument shape. There is no
path to smuggle a different call, a different target, or a hidden value transfer. This is called
**construct-for-sign**.

## Why not just carry calldata?

Because raw calldata is opaque. A button labeled "Claim" could carry calldata for *any* call — drain a wallet, call
a different contract, move 100× the displayed amount. By declaring the action structurally and constructing the
calldata from the declaration, the transaction is *provably* what the button says it is. The enforcer's rails
(R3–R5) guarantee the declaration is honest and in-envelope.

## Batches, signing, and value

- **Batches** — a button can carry an ordered list of actions submitted atomically under one signature; each member
  is individually gated. One signature, N verified actions, no authority beyond their union.
- **Typed signing** — EIP-712 typed-data signing is a declared capability (`SIGN_MESSAGE`), gated like any other.
- **Value** — moving ETH requires the `VALUE` capability to be openly declared (rail R4). An app can't move value
  it never said it could.

## Related
- **[The Capability Enforcer](./enforcer.md)** — the rails that keep actions honest.
- **[Guides → Declare a Safe Action](../guides/declare-action.md)** — build one.


## Human-readable action decoding ("you see what you sign")

Before a user signs an action, ChainRender renders a **plain-English decode of exactly what the transaction will
do** -- derived from the *declared* action `{to, sig, value, args}`, not guessed from calldata. For example, a mint
button decodes to:

> **This button will:** Mint 3 -> 0x36a4...c3c9 - sends 0.03 ETH
> function `mint(uint256)` on `0x36a4...C3c9` - **sends 0.03 ETH**

Because the runtime can only execute *exactly* the declared action, the decode is a guarantee, not an inference -- the
button cannot call anything else or send undeclared value. The panel states this explicitly.

**Swaps and multi-arg financial actions** carry an honesty flag: because the raw parameters don't label which value
is the slippage minimum or which addresses are the token path, the decode flags *"verify which value is your
minimum-received and which token addresses are in the path."* This is deliberate -- a decode that looked complete
while hiding the important half would be worse than none. ChainRender decodes *and* admits the limits of the decode.

## Payments build on actions

A crypto **payment** is just a declared action (`pay(...)` / `transfer(...)`), decoded before signing like any other.
To also serve users without crypto, an app can add an optional off-chain card on-ramp — honestly graded off-chain so
the on-chain guarantee is never overstated. See [Payments](./payments.md) for the full pattern (crypto floor + card
enhancement + honest grading).
