Ejecutable local con Anvil
Wallet local con Anvil
Levantar una cadena local, leer balance y nonce de dos cuentas, enviar 1 ETH de prueba y comprobar con el receipt qué cambió en el estado.
- Duración
- 25–40 min
- Nivel
- Base práctica Ethereum
- Pasos
- 3
- Versión
- 3.29.0-alpha.1
Modelo mental
Una wallet no guarda ETH: controla una clave que firma transiciones de estado. Cada transacción confirmada sube el nonce del emisor, mueve valor y cobra gas, y el receipt es la evidencia de que ocurrió.
- 1Preflight
- 2Anvil
- 3Lectura inicial
- 4Transferencia
- 5Receipt
- 6Cleanup
Antes de empezar
- Foundry instalado: anvil y cast en el PATH (https://getfoundry.sh).
- Node.js 20 o superior para el preflight.
- Puerto local 8545 libre.
- Terminal Bash, WSL o PowerShell (el runbook trae ambas variantes).
Pasos
Paso 1Preflight y cadena local
Confirmar herramientas y puerto antes de arrancar, y levantar Anvil en una terminal aparte.
Preflight node tools/labs/ethereum-local-anvil-wallet/preflight.mjsTerminal A: Anvil anvil --host 127.0.0.1 --port 8545 --chain-id 31337Comprueba
- — El preflight marca ✓ en anvil, cast, puerto 8545 y secretos, y termina con "Preflight OK".
- — Anvil lista 10 cuentas con 10000 ETH cada una y queda escuchando en 127.0.0.1:8545.
Evidencia esperada
Salida del preflight y las primeras líneas de Anvil con las cuentas.
Paso 2Leer el estado inicial
Anotar chainId, altura, balances y nonce antes de tocar nada.
Terminal B: lectura export RPC_URL="http://127.0.0.1:8545" export FROM="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" export TO="0x70997970c51812dc3a010c7d01b50e0d17dc79c8" cast chain-id --rpc-url $RPC_URL cast block-number --rpc-url $RPC_URL cast balance $FROM --ether --rpc-url $RPC_URL cast balance $TO --ether --rpc-url $RPC_URL cast nonce $FROM --rpc-url $RPC_URLComprueba
- — chain-id devuelve 31337.
- — block-number devuelve 0 en una cadena recién iniciada.
- — Ambos balances muestran 10000.000000000000000000.
- — El nonce de FROM es 0.
Evidencia esperada
Tabla antes: chainId, bloque, balances y nonce.
Paso 3Enviar 1 ETH y leer el receipt
Producir una transición de estado y comprobarla con el receipt, no con la intuición.
Transferencia cast send $TO --value 1ether --from $FROM --unlocked --rpc-url $RPC_URLValidar cast receipt <TX_HASH> status --rpc-url $RPC_URL cast receipt <TX_HASH> gasUsed --rpc-url $RPC_URL cast balance $FROM --ether --rpc-url $RPC_URL cast balance $TO --ether --rpc-url $RPC_URL cast nonce $FROM --rpc-url $RPC_URL cast block-number --rpc-url $RPC_URLComprueba
- — cast send imprime status 1 (success), blockNumber 1 y un transactionHash.
- — gasUsed es 21000, el costo fijo de una transferencia simple.
- — TO pasa a 10001.000000000000000000.
- — FROM queda en 9998.999978999999979000: 1 ETH más 21000 × effectiveGasPrice de gas.
- — El nonce de FROM sube de 0 a 1 y la altura de 0 a 1.
Evidencia esperada
Hash de la transacción, status, gasUsed y la tabla después.
El código del laboratorio
Estos son los archivos que ejecutan los pasos, tal como están en el repositorio .
# Lab: Ethereum local con Anvil, wallet y primera transacción
SemVer: `1.0.51-alpha.1`
Este archivo acompaña la página pública `/labs/ethereum-local-anvil-wallet`. La regla del lab es simple: mirar una transición de estado local antes/después y guardar evidencia verificable.
## Límite de seguridad
- Red local con Anvil.
- Sin private keys reales.
- Sin RPC de mainnet.
- Sin testnets con fondos.
- Sin seed phrases personales.
## Preflight
```bash
node tools/labs/ethereum-local-anvil-wallet/preflight.mjs
```
Debe confirmar:
- `anvil` disponible;
- `cast` disponible;
- puerto `8545` libre;
- no hay variables obvias con secretos reales.
## Terminal A: cadena local
```bash
anvil --host 127.0.0.1 --port 8545 --chain-id 31337
```
## Terminal B: lectura inicial
PowerShell:
```powershell
$env:RPC_URL="http://127.0.0.1:8545"
$env:FROM="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
$env:TO="0x70997970c51812dc3a010c7d01b50e0d17dc79c8"
cast chain-id --rpc-url $env:RPC_URL
cast block-number --rpc-url $env:RPC_URL
cast balance $env:FROM --ether --rpc-url $env:RPC_URL
cast balance $env:TO --ether --rpc-url $env:RPC_URL
cast nonce $env:FROM --rpc-url $env:RPC_URL
```
Bash:
```bash
export RPC_URL="http://127.0.0.1:8545"
export FROM="0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
export TO="0x70997970c51812dc3a010c7d01b50e0d17dc79c8"
cast chain-id --rpc-url $RPC_URL
cast block-number --rpc-url $RPC_URL
cast balance $FROM --ether --rpc-url $RPC_URL
cast balance $TO --ether --rpc-url $RPC_URL
cast nonce $FROM --rpc-url $RPC_URL
```
## Enviar transferencia
```powershell
cast send $env:TO --value 1ether --from $env:FROM --unlocked --rpc-url $env:RPC_URL
```
En Bash el comando es el mismo con `$TO`, `$FROM` y `$RPC_URL`. Guarda el `transactionHash`.
## Validar
```powershell
cast receipt <TX_HASH> status --rpc-url $env:RPC_URL
cast receipt <TX_HASH> gasUsed --rpc-url $env:RPC_URL
cast balance $env:FROM --ether --rpc-url $env:RPC_URL
cast balance $env:TO --ether --rpc-url $env:RPC_URL
cast nonce $env:FROM --rpc-url $env:RPC_URL
```
## Reporte mínimo
```markdown
# Lab: Anvil wallet
- chainId:
- from:
- to:
- nonce antes/después:
- balance antes/después:
- tx hash:
- receipt status:
- observación:
- cleanup realizado:
```
## Cleanup
PowerShell:
```powershell
Remove-Item Env:RPC_URL, Env:FROM, Env:TO -ErrorAction SilentlyContinue
```
Bash:
```bash
unset RPC_URL FROM TO
```
Detén Anvil con `Ctrl+C`.#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { createServer } from 'node:net';
const checks = [];
function commandExists(command, args = ['--version']) {
const result = spawnSync(command, args, {
encoding: 'utf8',
shell: process.platform === 'win32',
});
return {
ok: result.status === 0,
output: `${result.stdout ?? ''}${result.stderr ?? ''}`.trim().split(/\r?\n/)[0] ?? '',
};
}
function checkPort(port) {
return new Promise((resolve) => {
const server = createServer();
server.once('error', () => resolve(false));
server.once('listening', () => {
server.close(() => resolve(true));
});
server.listen(port, '127.0.0.1');
});
}
const anvil = commandExists('anvil');
checks.push({
label: 'anvil disponible',
ok: anvil.ok,
detail: anvil.output || 'No se pudo ejecutar anvil --version',
});
const cast = commandExists('cast');
checks.push({
label: 'cast disponible',
ok: cast.ok,
detail: cast.output || 'No se pudo ejecutar cast --version',
});
const portFree = await checkPort(8545);
checks.push({
label: 'puerto 8545 libre',
ok: portFree,
detail: portFree ? '127.0.0.1:8545 está libre' : '127.0.0.1:8545 ya está ocupado',
});
const suspiciousEnv = ['PRIVATE_KEY', 'MNEMONIC', 'SEED_PHRASE'].filter((key) => process.env[key]);
checks.push({
label: 'sin secretos reales obvios en entorno',
ok: suspiciousEnv.length === 0,
detail:
suspiciousEnv.length === 0
? 'No se detectaron variables PRIVATE_KEY, MNEMONIC o SEED_PHRASE'
: `Revisa estas variables antes del lab: ${suspiciousEnv.join(', ')}`,
});
console.log('TrautsLab Ethereum local lab preflight\n');
for (const check of checks) {
console.log(`${check.ok ? '✓' : '✗'} ${check.label}`);
console.log(` ${check.detail}`);
}
const failures = checks.filter((check) => !check.ok);
if (failures.length > 0) {
console.log('\nPreflight con bloqueantes. Corrige lo marcado antes de iniciar Anvil.');
process.exit(1);
}
console.log('\nPreflight OK. Puedes iniciar: anvil --host 127.0.0.1 --port 8545 --chain-id 31337');Criterios de aceptación
- El receipt tiene status 1.
- Puedes explicar la diferencia exacta entre el balance de FROM y 9999 ETH.
- El nonce subió exactamente en 1.
- Anvil quedó detenido al final.
Limpieza
unset RPC_URL FROM TOAnvil: Ctrl+C en la terminal A; todo el estado de la cadena se pierde.
Si algo falla
| Síntoma | Causa | Qué hacer |
|---|---|---|
| El preflight marca ✗ puerto 8545 libre | Ya hay un Anvil u otro proceso en ese puerto. | Cierra el proceso anterior o arranca Anvil con --port 8546 y usa ese RPC_URL. |
| cast falla con "Connection refused" | Anvil no está corriendo o RPC_URL apunta a otro puerto. | Revisa la terminal A y que RPC_URL sea http://127.0.0.1:8545. |
| block-number no empieza en 0 o el nonce no es 0 | Reutilizas un Anvil que ya tenía transacciones. | Reinicia Anvil para comparar contra un estado limpio. |