Saltar al contenido
TrautsLablab

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ó.

  1. 1Preflight
  2. 2Anvil
  3. 3Lectura inicial
  4. 4Transferencia
  5. 5Receipt
  6. 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

  1. 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.mjs
    Terminal A: Anvil
    anvil --host 127.0.0.1 --port 8545 --chain-id 31337

    Comprueba

    • — 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.

  2. 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_URL

    Comprueba

    • — 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.

  3. 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_URL
    Validar
    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_URL

    Comprueba

    • — 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 .

tools/labs/ethereum-local-anvil-wallet/runbook.md
# 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`.
tools/labs/ethereum-local-anvil-wallet/preflight.mjs
#!/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

Variables
unset RPC_URL FROM TO

Anvil: Ctrl+C en la terminal A; todo el estado de la cadena se pierde.

Si algo falla

Síntoma, causa probable y cómo corregirlo
SíntomaCausaQué hacer
El preflight marca ✗ puerto 8545 libreYa 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 0Reutilizas un Anvil que ya tenía transacciones.Reinicia Anvil para comparar contra un estado limpio.