Deploy y Verificación en Producción
Desplegar un smart contract a mainnet es un proceso que requiere precaución extrema. Una vez desplegado, el código es inmutable y cualquier error puede resultar en pérdida de fondos. En esta lección cubriremos todo el proceso de deploy profesional.
Checklist Pre-Deploy
ANTES de desplegar a mainnet:
☐ Código auditado por empresa externa
☐ Coverage de tests > 95%
☐ Tests de integración pasando
☐ Gas optimizado (gas reporter)
☐ Contrato verificado en testnet
☐ Frontend testeado con testnet
☐ Multisig para funciones admin
☐ Bug bounty program activo
☐ Documentación completa
☐ Plan de emergencia (pausability)
☐ Timelock en funciones críticas
Deploy a Testnet
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: "0.8.20",
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.DEPLOYER_PRIVATE_KEY],
gasPrice: "auto"
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
}
};
# .env (NUNCA commitear al repo)
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
DEPLOYER_PRIVATE_KEY=0xTU_CLAVE_PRIVADA
ETHERSCAN_API_KEY=TU_API_KEY
# Deploy a Sepolia
npx hardhat run scripts/deploy.js --network sepolia
# Output:
# Token desplegado en: 0x1234...
Verificación en Etherscan
# Verificar automáticamente con Hardhat
npx hardhat verify --network sepolia CONTRACT_ADDRESS "Arg1" "Arg2"
# Ejemplo para un token ERC-20:
npx hardhat verify --network sepolia \
0x1234567890abcdef... \
"Mi Token" "MTK" 1000000
Verificación programática
// scripts/deploy-and-verify.js
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Desplegando...");
const Token = await ethers.getContractFactory("MiToken");
const token = await Token.deploy("Mi Token", "MTK", 1000000);
await token.waitForDeployment();
const address = await token.getAddress();
console.log("Desplegado en:", address);
// Esperar 6 confirmaciones para Etherscan
console.log("Esperando confirmaciones...");
await token.deploymentTransaction().wait(6);
// Verificar
console.log("Verificando en Etherscan...");
try {
await hre.run("verify:verify", {
address: address,
constructorArguments: ["Mi Token", "MTK", 1000000],
});
console.log("Verificado exitosamente");
} catch (error) {
if (error.message.includes("Already Verified")) {
console.log("Ya estaba verificado");
} else {
throw error;
}
}
}
Deploy a Mainnet
// scripts/deploy-mainnet.js
async function main() {
const [deployer] = await ethers.getSigners();
const balance = await ethers.provider.getBalance(deployer.address);
console.log("=== DEPLOY A MAINNET ===");
console.log("Deployer:", deployer.address);
console.log("Balance:", ethers.formatEther(balance), "ETH");
// Estimar costos
const Token = await ethers.getContractFactory("MiToken");
const deployTx = await Token.getDeployTransaction("Mi Token", "MTK", 1000000);
const estimatedGas = await ethers.provider.estimateGas(deployTx);
const feeData = await ethers.provider.getFeeData();
const estimatedCost = estimatedGas * feeData.maxFeePerGas;
console.log("Gas estimado:", estimatedGas.toString());
console.log("Costo estimado:", ethers.formatEther(estimatedCost), "ETH");
// Confirmación manual
console.log("\n¿Continuar? (CTRL+C para cancelar, espera 10 segundos)");
await new Promise(r => setTimeout(r, 10000));
// Deploy
const token = await Token.deploy("Mi Token", "MTK", 1000000);
await token.waitForDeployment();
console.log("CONTRATO DESPLEGADO:", await token.getAddress());
}
Proxies: Contratos Actualizables
Para contratos que necesitan ser actualizables:
npm install @openzeppelin/hardhat-upgrades
// scripts/deploy-proxy.js
const { ethers, upgrades } = require("hardhat");
async function main() {
// Deploy con proxy (UUPS o Transparent)
const Token = await ethers.getContractFactory("MiTokenV1");
const proxy = await upgrades.deployProxy(Token, ["Mi Token", "MTK"], {
initializer: "initialize",
kind: "uups"
});
await proxy.waitForDeployment();
console.log("Proxy desplegado en:", await proxy.getAddress());
}
// scripts/upgrade.js
async function main() {
const proxyAddress = "0x... dirección del proxy ...";
const TokenV2 = await ethers.getContractFactory("MiTokenV2");
const upgraded = await upgrades.upgradeProxy(proxyAddress, TokenV2);
await upgraded.waitForDeployment();
console.log("Proxy actualizado a V2");
}
Gas Optimization
Técnicas para reducir costos de deploy y ejecución:
// ❌ Costoso: strings largos en require
require(balance > 0, "El balance del usuario debe ser mayor que cero");
// ✅ Eficiente: custom errors
error InsufficientBalance();
if (balance == 0) revert InsufficientBalance();
// ❌ Costoso: múltiples SSTOREs
contract Costoso {
uint public a;
uint public b;
function set(uint _a, uint _b) public {
a = _a; // SSTORE 1
b = _b; // SSTORE 2
}
}
// ✅ Eficiente: packing de variables
contract Eficiente {
uint128 public a; // Se empaquetan en
uint128 public b; // un solo slot de 32 bytes
// Usar unchecked cuando es seguro
function incrementar(uint x) public pure returns (uint) {
unchecked { return x + 1; } // Ahorra ~100 gas
}
}
Monitoreo Post-Deploy
Herramientas de monitoreo:
Tenderly → Simulación de transacciones, alertas
OpenZeppelin Defender → Monitoreo automatizado, relayers
Etherscan → Logs, eventos, transacciones
TheGraph → Indexación de eventos para queries
Dune Analytics → Dashboards y métricas on-chain
Configurar alertas para:
☐ Transacciones con alto valor
☐ Llamadas a funciones admin
☐ Cambios en ownership
☐ Pausas/unpauses del contrato
☐ Deploys de nuevas implementaciones (proxy)
Seguridad en Producción
Gestión de claves:
- NUNCA guardar claves privadas en código
- Usar hardware wallet para deploy a mainnet
- Implementar multisig (Gnosis Safe) para admin
- Separar wallet de deploy de wallet de admin
Timelock:
- Esperar 24-48h antes de ejecutar cambios críticos
- DAOs usan governance con timelock
- Permite a usuarios reaccionar antes del cambio
Pausability:
- Implementar función pause() para emergencias
- Solo admin/multisig puede pausar
- Diseñar qué funciones se pausan y cuáles no
Resumen
El deploy a producción requiere un proceso riguroso: auditoría, testing exhaustivo, deploy en testnet, verificación en Etherscan y monitoreo post-deploy. Para contratos actualizables, usar proxies de OpenZeppelin. La optimización de gas reduce costos, y el monitoreo continuo con alertas protege contra incidentes. Nunca apresures un deploy a mainnet: los fondos de los usuarios dependen de un código sin errores.