# 3.2 Predictable Behavior
Web3 interfaces must behave predictably.
## Success Criteria
### 3.2.1 No Auto-Triggered Transactions (Level A)
**Requirement**: Connecting a wallet must never automatically trigger transaction signing. Wallet connection and transaction initiation must be separate, explicit user actions.
**Intent**: Users should feel safe connecting wallets for browsing. Auto-triggered transactions are unexpected and potentially malicious.
**Benefits**:
- **All users**: Safety and trust
- **Cognitive disabilities**: Clear separation of actions
- **Security**: Prevents malicious auto-execution
**Techniques**:
```tsx
// ✅ Correct: Connection and action are separate
function App() {
const { connect, isConnected } = useWallet();
return (
<div>
{!isConnected ? (
<button onClick={connect}>
Connect Wallet
</button>
) : (
<>
<p>Wallet connected!</p>
{/* User must explicitly click to start transaction */}
<button onClick={openSwapModal}>
Start Swap
</button>
</>
)}
</div>
);
}
// ❌ Wrong: Auto-trigger on connect
function BadApp() {
const { connect, isConnected } = useWallet();
useEffect(() => {
if (isConnected) {
// DON'T DO THIS
initiateTransaction();
}
}, [isConnected]);
}
```
**Failures**:
- Transaction prompt appears on wallet connect
- Auto-approve requests on page load
- Hidden transactions in connect flow
---
### 3.2.2 Explicit Network Switching (Level A)
**Requirement**: Network/chain switching must require explicit user confirmation with clear indication of the target network.
**Intent**: Wrong-network transactions lose funds. Users must knowingly choose networks.
**Benefits**:
- **All users**: Prevent cross-chain errors
- **Cognitive disabilities**: Clear network awareness
- **Security**: Prevent network phishing
**Techniques**:
```tsx
<NetworkSwitchDialog
isOpen={pendingSwitch}
onClose={cancelSwitch}
aria-labelledby="network-switch-title"
>
<h2 id="network-switch-title">Switch Network?</h2>
<div className="network-comparison">
<div className="current-network">
<span className="label">Current:</span>
<NetworkBadge network={currentNetwork} />
</div>
<span aria-hidden="true">→</span>
<div className="target-network">
<span className="label">Switch to:</span>
<NetworkBadge network={targetNetwork} />
</div>
</div>
<p>
This will change your active network from {currentNetwork.name}
to {targetNetwork.name}.
</p>
<div className="actions">
<button onClick={cancelSwitch}>Cancel</button>
<button onClick={confirmSwitch}>Switch Network</button>
</div>
</NetworkSwitchDialog>
// Always show current network prominently
<Header>
<NetworkIndicator
network={currentNetwork}
aria-label={`Connected to ${currentNetwork.name}`}
/>
</Header>
```
**Failures**:
- Silent network switching
- Network indicated only by small icon
- No confirmation for network changes
---
### 3.2.3 Clear Approval Explanations (Level AA)
**Requirement**: Token approval requests must clearly explain what permissions are being granted, to whom, and for how long.
**Intent**: Approvals are dangerous—they grant ongoing access. Users must understand the implications.
**Benefits**:
- **All users**: Informed consent
- **Security**: Understand exposure
- **Cognitive disabilities**: Clear permission model
**Techniques**:
```tsx
<ApprovalRequest>
<h2>Permission Request</h2>
<div className="approval-details">
<dl>
<dt>App requesting access:</dt>
<dd>
<AppIdentity app={requestingApp} />
<a href={requestingApp.website} target="_blank">
Verify on website
</a>
</dd>
<dt>Token:</dt>
<dd>{token.name} ({token.symbol})</dd>
<dt>Permission type:</dt>
<dd>
{isUnlimited
? 'Unlimited spending'
: `Up to ${formatAmount(amount)} ${token.symbol}`}
</dd>
<dt>Duration:</dt>
<dd>Until you revoke this permission</dd>
</dl>
</div>
<div className="explanation" role="note">
<h3>What this means:</h3>
<ul>
<li>
{requestingApp.name} will be able to transfer {token.symbol}
from your wallet without asking again.
</li>
<li>
This permission stays active until you explicitly revoke it.
</li>
<li>
You can revoke this permission anytime in your wallet settings.
</li>
</ul>
</div>
{isUnlimited && (
<div role="alert" className="warning">
<strong>⚠️ Unlimited approval requested</strong>
<p>
This app is asking for permission to transfer any amount of
your {token.symbol}. Consider setting a specific limit instead.
</p>
<button onClick={showLimitOptions}>
Set a limit
</button>
</div>
)}
<div className="actions">
<button onClick={reject}>Reject</button>
<button onClick={approve}>Approve</button>
</div>
</ApprovalRequest>
```
**Failures**:
- Generic "Approve" without explanation
- Unlimited approval without warning
- No indication of what's being approved
---
### 3.2.4 Position Change Previews (Level AAA)
**Requirement**: Before confirming DeFi position changes (deposits, withdrawals, leverage changes), users see a preview comparing current state to projected state.
**Intent**: Position changes can have complex effects. Previews make outcomes concrete.
**Benefits**:
- **All users**: Understand impact before committing
- **Cognitive disabilities**: Visual comparison aids understanding
- **Risk reduction**: Catch unintended changes
**Techniques**:
```tsx
<PositionChangePreview>
<h2>Preview Position Change</h2>
<table aria-label="Position comparison">
<thead>
<tr>
<th scope="col">Metric</th>
<th scope="col">Current</th>
<th scope="col">After Change</th>
<th scope="col">Difference</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Collateral</th>
<td>$10,000</td>
<td>$15,000</td>
<td className="positive">+$5,000</td>
</tr>
<tr>
<th scope="row">Borrowed</th>
<td>$5,000</td>
<td>$5,000</td>
<td>No change</td>
</tr>
<tr>
<th scope="row">Health Factor</th>
<td>1.8</td>
<td>2.7</td>
<td className="positive">+0.9 (safer)</td>
</tr>
<tr>
<th scope="row">Liquidation Price</th>
<td>$1,200 ETH</td>
<td>$800 ETH</td>
<td className="positive">-$400 (safer)</td>
</tr>
</tbody>
</table>
<div className="summary" aria-live="polite">
<p>
Adding $5,000 collateral will improve your health factor from
1.8 to 2.7, making your position safer. Your liquidation price
will drop from $1,200 to $800.
</p>
</div>
</PositionChangePreview>
```
**Failures**:
- No preview before position changes
- Preview only shows final state
- No explanation of what changes mean
---
## Testing Checklist
- [ ] Wallet connect never triggers transactions
- [ ] Network switching requires confirmation
- [ ] Current network always visible
- [ ] Approval permissions clearly explained
- [ ] Unlimited approvals warned
- [ ] Position changes have before/after preview
- [ ] All confirmations are explicit
## Related Components
- [NetworkSwitch.tsx](../../components/NetworkSwitch.tsx)
- [ApprovalRequest.tsx](../../components/ApprovalRequest.tsx)
- [PositionPreview.tsx](../../components/PositionPreview.tsx)