# 4.2 Alternative Access Methods
Users must be able to interact through multiple means.
## Success Criteria
### 4.2.1 CLI Alternatives (Level AA)
**Requirement**: Critical operations (send, swap, stake) should have command-line interface alternatives for users who cannot use graphical interfaces.
**Intent**: Some users with severe motor impairments, blindness, or automation needs prefer CLI access.
**Benefits**:
- **Severe motor impairments**: Voice-to-CLI is often easier than GUI
- **Blind users**: CLI can be faster than web navigation
- **Developers**: Scriptable, automatable access
**Techniques**:
```bash
# Example CLI for DeFi operations
# Check balances
$ defi-cli balance
ETH: 2.5 ($4,875.00)
USDC: 1,000.00
WETH: 0.5 ($975.00)
# Send tokens
$ defi-cli send --to vitalik.eth --amount 0.1 --token ETH
Preparing transaction...
To: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 (vitalik.eth)
Amount: 0.1 ETH (~$195.00)
Network fee: ~$2.50
Total: 0.10128 ETH
Confirm? [y/N]: y
Transaction submitted: 0xabc123...
Waiting for confirmation...
✓ Transaction confirmed in block 18,234,567
# Swap tokens
$ defi-cli swap --from ETH --to USDC --amount 1.0 --slippage 0.5
Fetching quote...
You send: 1.0 ETH (~$1,950)
You receive: ~1,945 USDC (0.26% price impact)
Route: ETH → WETH → USDC (Uniswap V3)
Network fee: ~$5.00
Confirm? [y/N]: y
Transaction submitted...
✓ Swapped 1.0 ETH for 1,943.27 USDC
# View positions with accessible output
$ defi-cli positions --format accessible
You have 3 active DeFi positions:
1. Aave USDC Lending
Deposited: 5,000 USDC
Earning: 3.2% APY
Earned so far: $12.50
2. Uniswap ETH/USDC LP
Value: $2,500
Share: 0.001%
Fees earned: $45.00
3. Lido Staked ETH
Staked: 2.0 stETH
Rewards: 3.8% APY
```
```typescript
// CLI implementation (Node.js)
import { Command } from 'commander';
import { ethers } from 'ethers';
const program = new Command();
program
.name('defi-cli')
.description('Accessible CLI for DeFi operations')
.version('1.0.0');
program
.command('send')
.description('Send tokens to an address')
.requiredOption('--to <address>', 'Recipient address or ENS name')
.requiredOption('--amount <amount>', 'Amount to send')
.requiredOption('--token <symbol>', 'Token symbol (ETH, USDC, etc.)')
.option('--gas-price <gwei>', 'Gas price in gwei')
.action(async (options) => {
// Resolve ENS
const toAddress = await resolveAddress(options.to);
// Format amount
const amount = parseAmount(options.amount, options.token);
// Display confirmation
console.log('\nPreparing transaction...');
console.log(`To: ${toAddress}${options.to.includes('.') ? ` (${options.to})` : ''}`);
console.log(`Amount: ${options.amount} ${options.token}`);
console.log(`Network fee: ~$${estimateFee()}`);
// Confirm
const confirmed = await confirm('Confirm? [y/N]: ');
if (!confirmed) {
console.log('Transaction cancelled.');
return;
}
// Execute
const tx = await sendTransaction(toAddress, amount, options.token);
console.log(`Transaction submitted: ${tx.hash}`);
console.log('Waiting for confirmation...');
await tx.wait();
console.log(`✓ Transaction confirmed in block ${tx.blockNumber}`);
});
program.parse();
```
**Failures**:
- No CLI alternative available
- CLI lacks critical operations
- CLI output not screen reader friendly
- No confirmation prompts in CLI
---
### 4.2.2 Non-Visual CAPTCHA (Level AA)
**Requirement**: API access and bot protection must not require visual CAPTCHA. Audio alternatives or proof-of-personhood through wallet signing must be available.
**Intent**: Visual CAPTCHAs exclude blind users. Alternative verification methods must exist.
**Benefits**:
- **Blind users**: Can verify without visual test
- **Cognitive disabilities**: May struggle with visual puzzles
- **All users**: Wallet-based verification is often easier
**Techniques**:
```tsx
// Wallet-based proof of personhood
<BotProtection>
<p>Please verify you're human to continue.</p>
<div className="verification-options">
{/* Wallet signature option */}
<button onClick={signMessage}>
Verify with wallet signature
<span className="sr-only">
This will ask you to sign a message with your wallet.
No transaction will be sent.
</span>
</button>
{/* Audio CAPTCHA option */}
<button onClick={startAudioChallenge}>
Audio verification
<span className="sr-only">
You'll hear a series of numbers to type.
</span>
</button>
</div>
</BotProtection>
// Wallet signature verification
async function signMessage() {
const message = `Verify I'm human\nTimestamp: ${Date.now()}\nNonce: ${generateNonce()}`;
try {
const signature = await wallet.signMessage(message);
// Verify signature server-side
const result = await verifySignature(message, signature);
if (result.valid) {
announceToScreenReader('Verification successful');
onVerified();
}
} catch (error) {
announceToScreenReader('Verification failed. Please try again.');
}
}
// Audio CAPTCHA implementation
function AudioCaptcha({ onComplete }) {
const [digits, setDigits] = useState('');
const [isPlaying, setIsPlaying] = useState(false);
const audioRef = useRef<HTMLAudioElement>(null);
const playAudio = () => {
setIsPlaying(true);
audioRef.current?.play();
};
return (
<div role="group" aria-label="Audio verification">
<audio
ref={audioRef}
src={captchaAudioUrl}
onEnded={() => setIsPlaying(false)}
/>
<button
onClick={playAudio}
disabled={isPlaying}
aria-label={isPlaying ? 'Audio playing' : 'Play audio challenge'}
>
{isPlaying ? 'Playing...' : 'Play Audio'}
</button>
<button onClick={playAudio} aria-label="Replay audio">
Replay
</button>
<label>
Enter the numbers you hear:
<input
type="text"
value={digits}
onChange={(e) => setDigits(e.target.value)}
pattern="[0-9]*"
inputMode="numeric"
aria-describedby="captcha-help"
/>
</label>
<p id="captcha-help">
You'll hear 6 digits spoken. Type them in order.
</p>
<button onClick={() => onComplete(digits)}>
Verify
</button>
</div>
);
}
```
**Failures**:
- Visual CAPTCHA only
- Audio CAPTCHA is distorted/unclear
- No wallet-based alternative
- CAPTCHA timeout too short
---
### 4.2.3 Voice Transaction Initiation (Level AAA)
**Requirement**: Transactions can be initiated and confirmed through voice commands with spoken confirmation of details.
**Intent**: Users with severe motor impairments may rely entirely on voice. Critical actions should be voice-accessible.
**Benefits**:
- **Severe motor impairments**: Full voice access
- **Blind users**: Alternative to keyboard navigation
- **Hands-free operation**: Use while multitasking
**Techniques**:
```tsx
// Voice command system for DeFi
const VOICE_COMMANDS = {
'send': /send (\d+(?:\.\d+)?) (\w+) to (.+)/i,
'swap': /swap (\d+(?:\.\d+)?) (\w+) (?:for|to) (\w+)/i,
'check balance': /(?:check |what's my |show )?balance/i,
'confirm': /confirm|yes|approve/i,
'cancel': /cancel|no|reject/i,
};
function VoiceCommandHandler() {
const [isListening, setIsListening] = useState(false);
const [pendingAction, setPendingAction] = useState(null);
const recognition = useRef<SpeechRecognition>(null);
useEffect(() => {
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
recognition.current = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
recognition.current.continuous = true;
recognition.current.onresult = handleVoiceResult;
}
}, []);
const handleVoiceResult = async (event: SpeechRecognitionEvent) => {
const transcript = event.results[event.results.length - 1][0].transcript.trim();
// Parse command
if (VOICE_COMMANDS.send.test(transcript)) {
const [, amount, token, recipient] = transcript.match(VOICE_COMMANDS.send);
// Speak confirmation request
speak(`Send ${amount} ${token} to ${recipient}. Say confirm to proceed or cancel to abort.`);
setPendingAction({ type: 'send', amount, token, recipient });
}
else if (VOICE_COMMANDS.confirm.test(transcript) && pendingAction) {
speak('Processing transaction. Please wait.');
await executePendingAction(pendingAction);
speak('Transaction submitted successfully.');
setPendingAction(null);
}
else if (VOICE_COMMANDS.cancel.test(transcript)) {
speak('Transaction cancelled.');
setPendingAction(null);
}
else if (VOICE_COMMANDS['check balance'].test(transcript)) {
const balances = await getBalances();
speak(`Your balances are: ${formatBalancesForSpeech(balances)}`);
}
};
return (
<div className="voice-control">
<button
onClick={() => {
setIsListening(!isListening);
if (!isListening) {
recognition.current?.start();
speak('Voice control activated. Say a command.');
} else {
recognition.current?.stop();
speak('Voice control deactivated.');
}
}}
aria-pressed={isListening}
>
{isListening ? '🎤 Listening...' : '🎤 Start Voice Control'}
</button>
{pendingAction && (
<div role="alert" aria-live="assertive">
Pending: {describePendingAction(pendingAction)}
Say "confirm" or "cancel"
</div>
)}
<details>
<summary>Voice commands</summary>
<ul>
<li>"Send [amount] [token] to [address/ENS]"</li>
<li>"Swap [amount] [token] for [token]"</li>
<li>"Check balance"</li>
<li>"Confirm" or "Cancel"</li>
</ul>
</details>
</div>
);
}
```
**Failures**:
- No voice input support
- Voice commands don't require confirmation
- No spoken feedback
- Complex command syntax
---
## Testing Checklist
- [ ] CLI available for critical operations
- [ ] CLI output is screen reader friendly
- [ ] CLI has confirmation prompts
- [ ] Non-visual CAPTCHA alternative exists
- [ ] Wallet signing can replace CAPTCHA
- [ ] Audio CAPTCHA is clear and replayable
- [ ] Voice commands available
- [ ] Voice requires confirmation for transactions
- [ ] Spoken feedback provided
## Related Tools
- [defi-cli](../../tools/cli/README.md) - Command-line interface
- [VoiceCommandProvider.tsx](../../components/VoiceCommandProvider.tsx)
- [AccessibleCaptcha.tsx](../../components/AccessibleCaptcha.tsx)