
Replanteando el Desarrollo en Web3
Por que la mayoría de los desarrolladores tratan los smart contracts como un backend, y que podemos hacer para arreglarlo

Web3: El problema de las "Ghost Chains" y la burbuja de los Tokens
Por qué la mayoría de los productos en Web3 siguen pareciendo humo y qué podemos hacer (como desarrolladores) para resolverlo

Aprendiendo sobre NEAR Intents!
Los NEAR Intents representan una evolución fascinante en la Payment Abstraction: un nuevo tipo de transacción donde la 'intención' es lo que importa

Replanteando el Desarrollo en Web3
Por que la mayoría de los desarrolladores tratan los smart contracts como un backend, y que podemos hacer para arreglarlo

Web3: El problema de las "Ghost Chains" y la burbuja de los Tokens
Por qué la mayoría de los productos en Web3 siguen pareciendo humo y qué podemos hacer (como desarrolladores) para resolverlo

Aprendiendo sobre NEAR Intents!
Los NEAR Intents representan una evolución fascinante en la Payment Abstraction: un nuevo tipo de transacción donde la 'intención' es lo que importa

Subscribe to rabuawad

Subscribe to rabuawad
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers


Bienvenidos. En este artículo cubriremos algo muy simple: cómo crear una criptomoneda usando Vyper y Python. Esta guía está diseñada para principiantes o para entusiastas que ya conocen un poco de blockchain y quieren profundizar.
No es una introducción a la programación (no veremos variables o funciones básicas) ni a conceptos fundamentales de blockchain. Se asume que ya entiendes qué es una criptomoneda y una dirección (wallet address). Al final, tendremos una criptomoneda escrita en Vyper lista para la red de Arbitrum.
A lo largo de este artículo, usaré los términos "criptomoneda" y "ERC20" como sinónimos.
Un ERC20 es el estándar técnico utilizado para contratos inteligentes en la Ethereum Virtual Machine (EVM). El EIP-20 define las reglas que un contrato debe seguir para ser considerado una criptomoneda y ser compatible con exchanges y billeteras.
Esencialmente, necesitamos implementar 6 funciones básicas:
Función | Descripción |
|---|---|
| El número total de monedas en circulación. |
| Muestra el balance de una dirección específica. |
| Envía monedas desde el propietario hacia un destino. |
| Cantidad que un tercero tiene permitido mover en nombre del dueño. |
| Autoriza a una dirección externa a mover monedas por parte del dueño. |
| Permite a una dirección autorizada ejecutar el movimiento de fondos. |
Además, implementaremos 3 funciones opcionales que mejoran la experiencia de usuario:
Función | Descripción |
|---|---|
| Nombre de la criptomoneda (ej. Bitcoin). |
| Símbolo de la criptomoneda (ej. BTC). |
| Cantidad de decimales (usualmente 18). |
Usaremos una configuración mínima con uv como manejador de dependencias y Titanoboa para compilar Vyper.
Instalar UV.
Crear el proyecto: uv init <nombre_del_proyecto>
Crear entorno virtual: uv venv
Activar el entorno:
Linux/macOS: source .venv/bin/activate
Windows: .venv\Scripts\activate
Instalar Titanoboa: uv add titanoboa
📂 contracts/: Donde almacenaremos el contrato inteligente.
📂 scripts/: Scripts de Python para el despliegue.
📂 tests/: Pruebas unitarias para nuestro ERC20.
Crea el archivo contracts/ERC20.vy.
El pragma indica qué versión del compilador se usó. Usaremos la 0.4.3.
# pragma version ==0.4.3Definiremos el nombre y símbolo como variables de estado. Para el suministro total, usaremos una variable pública.
name: public(String[32])
symbol: public(String[10])
decimals: public(uint8)
totalSupply: public(uint256)
@deploy
def __init__(_name: String[32], _symbol: String[10]):
self.name = _name
self.symbol = _symbol
self.decimals = 18Al usar public(), Vyper crea automáticamente la función de lectura para nosotros.
Usaremos un HashMap para rastrear cuánto tiene cada billetera.
balanceOf: public(HashMap[address, uint256])Dato: Es buena idea marcar variables internas con
_(guion bajo), pero al usarpublic(), Vyper maneja la visibilidad externa por nosotros de forma eficiente.
La función transfer es el corazón de nuestra moneda. Es la que permite mover valor de un punto A a un punto B.
En Vyper, la lógica es directa: restamos del emisor y sumamos al receptor.
@external
def transfer(_to: address, _value: uint256) -> bool:
"""
@dev Transfiere tokens a una dirección especificada.
@param _to La dirección a la que se transfiere.
@param _value La cantidad a transferir.
"""
self.balanceOf[msg.sender] -= _value
self.balanceOf[_to] += _value
return True@external: Permite que cualquier persona o contrato llame a esta función.
msg.sender: Es una variable global que representa la dirección de quien está ejecutando la transacción.
Seguridad: A diferencia de versiones antiguas de otros lenguajes, Vyper 0.4.3 maneja nativamente el desbordamiento (overflow). Si intentas enviar más de lo que tienes, la transacción fallará automáticamente sin necesidad de código extra.
Perfecto, vamos a cerrar el estándar. Estas funciones son las que permiten que tu moneda interactúe con el ecosistema DeFi (como Uniswap), permitiendo que otros contratos muevan fondos por ti bajo tu permiso.
El concepto de Allowance (concesión) es lo que hace a los ERC20 tan potentes. Permite que autorices a una dirección (por ejemplo, un Exchange Descentralizado) a retirar una cantidad específica de tokens de tu billetera.
Primero necesitamos una estructura para guardar quién tiene permiso de quién. Usaremos un HashMap anidado:
# Propietario -> (Gasta por mí -> Cantidad)
allowance: public(HashMap[address, HashMap[address, uint256]])Con esta función, el usuario dice: "Autorizo a esta aplicación a gastar X cantidad de mis tokens".
@external
def approve(_spender: address, _value: uint256) -> bool:
"""
@dev Autoriza a `_spender` a transferir hasta `_value` tokens.
"""
self.allowance[msg.sender][_spender] = _value
return TrueEsta es la función que llama la aplicación autorizada. Nota que aquí el remitente no es el dueño de los tokens, sino el "spender".
@external
def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
"""
@dev Mueve tokens de `_from` a `_to` usando el mecanismo de allowance.
"""
# Si no hay suficiente permiso, Vyper lanzará error por overflow
self.allowance[_from][msg.sender] -= _value
# 2. Mover los balances
self.balanceOf[_from] -= _value
self.balanceOf[_to] += _value
return TruePara que las interfaces como MetaMask o Etherscan muestren tus transacciones en tiempo real, necesitas Eventos. Sin ellos, el contrato funciona, pero será "invisible" para muchas aplicaciones.
Añade esto al inicio de tu archivo, debajo del pragma:
event Transfer:
sender: indexed(address)
receiver: indexed(address)
value: uint256
event Approval:
owner: indexed(address)
spender: indexed(address)
value: uint256Importante: Asegúrate de llamar a
log Transfer(...)ylog Approval(...)dentro de sus respectivas funciones para que el estándar sea 100% oficial.
@external
def transfer(...) -> bool:
# ...
log Transfer(_from=msg.sender, _to=_to, _value=_value)
return True
@external
def approve(...) -> bool:
# ...
log Approval(_owner=msg.sender, _spender=_spender, _value=_value)
return True
@external
def transferFrom(...) -> bool:
# ...
log Transfer(_from=_from, _to=_to, _value=_value)
return TrueCon esto ya tienes un contrato ERC20 completo y funcional. Es minimalista, seguro y sigue las mejores prácticas de Vyper 0.4.3.
Si vas a lanzar tu moneda en una red como ** **, cada unidad de gas cuenta. En la versión que escribimos arriba, el name, symbol y decimals se guardan en el almacenamiento de la blockchain. Leer del almacenamiento (o Storage) es una de las operaciones más caras en términos de gas.
Para valores que nunca cambian después de desplegar el contrato, Vyper nos ofrece el envoltorio immutable.
Al marcar una variable como inmutable, Vyper no la guarda en un espacio de almacenamiento costoso. En su lugar, el valor se "incrusta" directamente en el código (bytecode) del contrato durante el despliegue. Esto hace que leer estos datos sea extremadamente barato (casi gratuito).
Así es como se ve la implementación usando inmutables en Vyper 0.4.3:
# Declaramos las variables inmutables (se recomienda usar MAYÚSCULAS)
NAME: immutable(String[10])
SYMBOL: immutable(String[5])
DECIMALS: immutable(uint8)
@deploy
def __init__(_name: String[10], _symbol: String[5]):
# Se asignan una sola vez en el constructor
NAME = _name
SYMBOL = _symbol
DECIMALS = 18
# Creamos funciones externas para cumplir con el estándar ERC20
@external
@view
def name() -> String[10]:
return NAME
@external
@view
def symbol() -> String[5]:
return SYMBOL
@external
@view
def decimals() -> uint8:
return DECIMALS SÍ: Para el nombre, símbolo, decimales o una dirección de un dueño inicial que no cambiará.
NO: Para balances o cualquier valor que necesites actualizar en el futuro.
Con este pequeño cambio, tu contrato no solo es más profesional, sino que tus usuarios te lo agradecerán al pagar menos comisiones por interactuar con tu moneda.
En el código, implementamos esta lógica dividiéndola en partes: variable inmutable para saber quien es el creador del smart contract, una función externa para el control de acceso y una interna para la lógica contable.
OWNER: immutable(address)
@deploy
def __init__(...):
# ...
OWNER = msg.sender
@external
def mint(_to: address, _value: uint256) -> bool:
assert msg.sender == OWNER, "Solo el OWNER puede acuñar"
self._mint(_to, _value)
return True
@internal
def _mint(_to: address, _value: uint256):
self.balanceOf[_to] += _value
self.totalSupply += _value
log Transfer(_from=empty(address), _to=_to, _value=_value)Puntos clave de esta implementación:
Control de Acceso: Utilizamos assert msg.sender == OWNER para asegurar que nadie más pueda inflar el suministro de tokens de manera arbitraria.
Gestión del Total Supply: A diferencia de una transferencia común, el mint aumenta la variable self.totalSupply, manteniendo la integridad del balance global del contrato.
Convención de Emisión: Siguiendo las buenas prácticas, emitimos el evento Transfer utilizando empty(address) (la dirección 0x00...) como origen. Esto indica a los exploradores de bloques que los tokens no provienen de otro usuario, sino que acaban de ser creados.
Separación de Lógica: Definir _mint como una función @internal es una práctica recomendada. Esto nos permite, en versiones futuras, reutilizar la lógica de acuñación desde otras funciones del contrato sin repetir código ni exponer el acceso públicamente.
Dato clave: Añadir funciones como mint no rompe la compatibilidad con el estándar. El ERC20 define una interfaz mínima obligatoria; mientras esas funciones base existan y se comporten como se espera, puedes añadir toda la lógica extra que necesites. Tu contrato seguirá siendo un ERC20 legítimo ante cualquier billetera o exchange.
Aquí tienes el contrato final optimizado y listo para desplegar:
# pragma version ==0.4.3
event Transfer:
_from: indexed(address)
_to: indexed(address)
_value: uint256
event Approval:
_owner: indexed(address)
_spender: indexed(address)
_value: uint256
# Inmutables
NAME: immutable(String[10])
SYMBOL: immutable(String[5])
DECIMALS: immutable(uint8)
OWNER: immutable(address)
# Variables de almacenamiento
balanceOf: public(HashMap[address, uint256])
allowance: public(HashMap[address, HashMap[address, uint256]])
totalSupply: public(uint256)
@deploy
def __init__(_name: String[10], _symbol: String[5]):
NAME = _name
SYMBOL = _symbol
DECIMALS = 18
OWNER = msg.sender
@external
def transfer(_to: address, _value: uint256) -> bool:
"""
@dev Transfiere tokens a una dirección especificada.
@param _to La dirección a la que se transfiere.
@param _value La cantidad a transferir.
"""
self.balanceOf[msg.sender] -= _value
self.balanceOf[_to] += _value
log Transfer(_from=msg.sender, _to=_to, _value=_value)
return True
@external
def approve(_spender: address, _value: uint256) -> bool:
"""
@dev Autoriza a `_spender` a transferir hasta `_value` tokens.
"""
self.allowance[msg.sender][_spender] = _value
log Approval(_owner=msg.sender, _spender=_spender, _value=_value)
return True
@external
def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
"""
@dev Mueve tokens de `_from` a `_to` usando el mecanismo de allowance.
"""
self.allowance[_from][msg.sender] -= _value
self.balanceOf[_from] -= _value
self.balanceOf[_to] += _value
log Transfer(_from=_from, _to=_to, _value=_value)
return True
@external
def mint(_to: address, _value: uint256) -> bool:
"""
@dev Acuña `_value` cantidad de monedas a `_to`
"""
assert msg.sender == OWNER, "Solo el OWNER puede acuñar"
self._mint(_to, _value)
return True
@external
@view
def name() -> String[10]:
return NAME
@external
@view
def symbol() -> String[5]:
return SYMBOL
@external
@view
def decimals() -> uint8:
return DECIMALS
@internal
def _mint(_to: address, _value: uint256):
self.balanceOf[_to] += _value
self.totalSupply += _value
log Transfer(_from=empty(address), _to=_to, _value=_value)Esta es la revisión final de la sección de despliegue. He corregido los errores de dedo, unificado la red a Base (para mantener la coherencia con el inicio del artículo) y optimizado el código para que sea un script profesional y seguro.
Para el toque final, usaremos Titanoboa (o boa), un framework extremadamente ligero y potente para desplegar y testear contratos de Vyper usando Python puro.
Crea una carpeta llamada scripts/ y dentro un archivo llamado deploy.py.
Instalaremos eth-account para manejar nuestras llaves de forma segura: uv add eth-account
Regla de oro: Nunca dejes tu llave privada en texto plano en el código o en archivos .env. Es la forma más fácil de que te roben tus fondos.
Usaremos un archivo Keystore JSON (un archivo cifrado con contraseña). Si usas herramientas como Ape o Foundry, ya los tienes; si no, asegúrate de generar uno con eth-account siguiendo esta pequeña guia (el codigo para generar un keystore son menos de 35 lineas de Python!).
Aquí tienes el script completo. He configurado el RPC de Arbitrum Mainnet, pero puedes cambiarlo a Sepia si prefieres testear primero.
import getpass
import boa
from eth_account import Account
def load_keystore_account():
"""Carga una cuenta desde un archivo keystore de forma segura."""
with open("account.keystore.json", "r") as acc:
password = getpass.getpass("\t Ingrese su contraseña del Keystore: ")
encrypted_account = acc.read()
account_pk = Account.decrypt(encrypted_account, password)
return Account.from_key(account_pk)
def main():
# 1. Definimos la red (Arbitrum en este caso)
# Si quieres puedes un provider como Alchemy/Infura
rpc_url = "https://arb1.arbitrum.io/rpc"
with boa.set_network_env(rpc_url):
# 2. Cargamos la cuenta y la añadimos al entorno de boa
account = load_keystore_account()
boa.env.add_account(account)
print(f"Desplegando contrato con la cuenta: {account.address}...")
# 3. Desplegamos el contrato
# boa.load compila y envía la transacción de despliegue
erc20_contract = boa.load(
"contracts/ERC20.vy",
"Mi Token", # _name
"MT" # _symbol
)
print(f"¡Éxito! Contrato desplegado en: {erc20_contract.address}")
return erc20_contract
if __name__ == "__main__":
main()Para ejecutarlo, simplemente corre en tu terminal: uv run scripts/deploy.py
Titanoboa se encargará de compilar tu código Vyper al vuelo, firmar la transacción con tu cuenta cifrada y enviarla a la red de Arbitrum.
¡Felicidades! Has pasado de una carpeta vacía a tener una criptomoneda optimizada y desplegada en una Layer 2 usando el stack más moderno de Python y Vyper. Puedes buscar el address de tu contrato en arbiscan.io.
Bienvenidos. En este artículo cubriremos algo muy simple: cómo crear una criptomoneda usando Vyper y Python. Esta guía está diseñada para principiantes o para entusiastas que ya conocen un poco de blockchain y quieren profundizar.
No es una introducción a la programación (no veremos variables o funciones básicas) ni a conceptos fundamentales de blockchain. Se asume que ya entiendes qué es una criptomoneda y una dirección (wallet address). Al final, tendremos una criptomoneda escrita en Vyper lista para la red de Arbitrum.
A lo largo de este artículo, usaré los términos "criptomoneda" y "ERC20" como sinónimos.
Un ERC20 es el estándar técnico utilizado para contratos inteligentes en la Ethereum Virtual Machine (EVM). El EIP-20 define las reglas que un contrato debe seguir para ser considerado una criptomoneda y ser compatible con exchanges y billeteras.
Esencialmente, necesitamos implementar 6 funciones básicas:
Función | Descripción |
|---|---|
| El número total de monedas en circulación. |
| Muestra el balance de una dirección específica. |
| Envía monedas desde el propietario hacia un destino. |
| Cantidad que un tercero tiene permitido mover en nombre del dueño. |
| Autoriza a una dirección externa a mover monedas por parte del dueño. |
| Permite a una dirección autorizada ejecutar el movimiento de fondos. |
Además, implementaremos 3 funciones opcionales que mejoran la experiencia de usuario:
Función | Descripción |
|---|---|
| Nombre de la criptomoneda (ej. Bitcoin). |
| Símbolo de la criptomoneda (ej. BTC). |
| Cantidad de decimales (usualmente 18). |
Usaremos una configuración mínima con uv como manejador de dependencias y Titanoboa para compilar Vyper.
Instalar UV.
Crear el proyecto: uv init <nombre_del_proyecto>
Crear entorno virtual: uv venv
Activar el entorno:
Linux/macOS: source .venv/bin/activate
Windows: .venv\Scripts\activate
Instalar Titanoboa: uv add titanoboa
📂 contracts/: Donde almacenaremos el contrato inteligente.
📂 scripts/: Scripts de Python para el despliegue.
📂 tests/: Pruebas unitarias para nuestro ERC20.
Crea el archivo contracts/ERC20.vy.
El pragma indica qué versión del compilador se usó. Usaremos la 0.4.3.
# pragma version ==0.4.3Definiremos el nombre y símbolo como variables de estado. Para el suministro total, usaremos una variable pública.
name: public(String[32])
symbol: public(String[10])
decimals: public(uint8)
totalSupply: public(uint256)
@deploy
def __init__(_name: String[32], _symbol: String[10]):
self.name = _name
self.symbol = _symbol
self.decimals = 18Al usar public(), Vyper crea automáticamente la función de lectura para nosotros.
Usaremos un HashMap para rastrear cuánto tiene cada billetera.
balanceOf: public(HashMap[address, uint256])Dato: Es buena idea marcar variables internas con
_(guion bajo), pero al usarpublic(), Vyper maneja la visibilidad externa por nosotros de forma eficiente.
La función transfer es el corazón de nuestra moneda. Es la que permite mover valor de un punto A a un punto B.
En Vyper, la lógica es directa: restamos del emisor y sumamos al receptor.
@external
def transfer(_to: address, _value: uint256) -> bool:
"""
@dev Transfiere tokens a una dirección especificada.
@param _to La dirección a la que se transfiere.
@param _value La cantidad a transferir.
"""
self.balanceOf[msg.sender] -= _value
self.balanceOf[_to] += _value
return True@external: Permite que cualquier persona o contrato llame a esta función.
msg.sender: Es una variable global que representa la dirección de quien está ejecutando la transacción.
Seguridad: A diferencia de versiones antiguas de otros lenguajes, Vyper 0.4.3 maneja nativamente el desbordamiento (overflow). Si intentas enviar más de lo que tienes, la transacción fallará automáticamente sin necesidad de código extra.
Perfecto, vamos a cerrar el estándar. Estas funciones son las que permiten que tu moneda interactúe con el ecosistema DeFi (como Uniswap), permitiendo que otros contratos muevan fondos por ti bajo tu permiso.
El concepto de Allowance (concesión) es lo que hace a los ERC20 tan potentes. Permite que autorices a una dirección (por ejemplo, un Exchange Descentralizado) a retirar una cantidad específica de tokens de tu billetera.
Primero necesitamos una estructura para guardar quién tiene permiso de quién. Usaremos un HashMap anidado:
# Propietario -> (Gasta por mí -> Cantidad)
allowance: public(HashMap[address, HashMap[address, uint256]])Con esta función, el usuario dice: "Autorizo a esta aplicación a gastar X cantidad de mis tokens".
@external
def approve(_spender: address, _value: uint256) -> bool:
"""
@dev Autoriza a `_spender` a transferir hasta `_value` tokens.
"""
self.allowance[msg.sender][_spender] = _value
return TrueEsta es la función que llama la aplicación autorizada. Nota que aquí el remitente no es el dueño de los tokens, sino el "spender".
@external
def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
"""
@dev Mueve tokens de `_from` a `_to` usando el mecanismo de allowance.
"""
# Si no hay suficiente permiso, Vyper lanzará error por overflow
self.allowance[_from][msg.sender] -= _value
# 2. Mover los balances
self.balanceOf[_from] -= _value
self.balanceOf[_to] += _value
return TruePara que las interfaces como MetaMask o Etherscan muestren tus transacciones en tiempo real, necesitas Eventos. Sin ellos, el contrato funciona, pero será "invisible" para muchas aplicaciones.
Añade esto al inicio de tu archivo, debajo del pragma:
event Transfer:
sender: indexed(address)
receiver: indexed(address)
value: uint256
event Approval:
owner: indexed(address)
spender: indexed(address)
value: uint256Importante: Asegúrate de llamar a
log Transfer(...)ylog Approval(...)dentro de sus respectivas funciones para que el estándar sea 100% oficial.
@external
def transfer(...) -> bool:
# ...
log Transfer(_from=msg.sender, _to=_to, _value=_value)
return True
@external
def approve(...) -> bool:
# ...
log Approval(_owner=msg.sender, _spender=_spender, _value=_value)
return True
@external
def transferFrom(...) -> bool:
# ...
log Transfer(_from=_from, _to=_to, _value=_value)
return TrueCon esto ya tienes un contrato ERC20 completo y funcional. Es minimalista, seguro y sigue las mejores prácticas de Vyper 0.4.3.
Si vas a lanzar tu moneda en una red como ** **, cada unidad de gas cuenta. En la versión que escribimos arriba, el name, symbol y decimals se guardan en el almacenamiento de la blockchain. Leer del almacenamiento (o Storage) es una de las operaciones más caras en términos de gas.
Para valores que nunca cambian después de desplegar el contrato, Vyper nos ofrece el envoltorio immutable.
Al marcar una variable como inmutable, Vyper no la guarda en un espacio de almacenamiento costoso. En su lugar, el valor se "incrusta" directamente en el código (bytecode) del contrato durante el despliegue. Esto hace que leer estos datos sea extremadamente barato (casi gratuito).
Así es como se ve la implementación usando inmutables en Vyper 0.4.3:
# Declaramos las variables inmutables (se recomienda usar MAYÚSCULAS)
NAME: immutable(String[10])
SYMBOL: immutable(String[5])
DECIMALS: immutable(uint8)
@deploy
def __init__(_name: String[10], _symbol: String[5]):
# Se asignan una sola vez en el constructor
NAME = _name
SYMBOL = _symbol
DECIMALS = 18
# Creamos funciones externas para cumplir con el estándar ERC20
@external
@view
def name() -> String[10]:
return NAME
@external
@view
def symbol() -> String[5]:
return SYMBOL
@external
@view
def decimals() -> uint8:
return DECIMALS SÍ: Para el nombre, símbolo, decimales o una dirección de un dueño inicial que no cambiará.
NO: Para balances o cualquier valor que necesites actualizar en el futuro.
Con este pequeño cambio, tu contrato no solo es más profesional, sino que tus usuarios te lo agradecerán al pagar menos comisiones por interactuar con tu moneda.
En el código, implementamos esta lógica dividiéndola en partes: variable inmutable para saber quien es el creador del smart contract, una función externa para el control de acceso y una interna para la lógica contable.
OWNER: immutable(address)
@deploy
def __init__(...):
# ...
OWNER = msg.sender
@external
def mint(_to: address, _value: uint256) -> bool:
assert msg.sender == OWNER, "Solo el OWNER puede acuñar"
self._mint(_to, _value)
return True
@internal
def _mint(_to: address, _value: uint256):
self.balanceOf[_to] += _value
self.totalSupply += _value
log Transfer(_from=empty(address), _to=_to, _value=_value)Puntos clave de esta implementación:
Control de Acceso: Utilizamos assert msg.sender == OWNER para asegurar que nadie más pueda inflar el suministro de tokens de manera arbitraria.
Gestión del Total Supply: A diferencia de una transferencia común, el mint aumenta la variable self.totalSupply, manteniendo la integridad del balance global del contrato.
Convención de Emisión: Siguiendo las buenas prácticas, emitimos el evento Transfer utilizando empty(address) (la dirección 0x00...) como origen. Esto indica a los exploradores de bloques que los tokens no provienen de otro usuario, sino que acaban de ser creados.
Separación de Lógica: Definir _mint como una función @internal es una práctica recomendada. Esto nos permite, en versiones futuras, reutilizar la lógica de acuñación desde otras funciones del contrato sin repetir código ni exponer el acceso públicamente.
Dato clave: Añadir funciones como mint no rompe la compatibilidad con el estándar. El ERC20 define una interfaz mínima obligatoria; mientras esas funciones base existan y se comporten como se espera, puedes añadir toda la lógica extra que necesites. Tu contrato seguirá siendo un ERC20 legítimo ante cualquier billetera o exchange.
Aquí tienes el contrato final optimizado y listo para desplegar:
# pragma version ==0.4.3
event Transfer:
_from: indexed(address)
_to: indexed(address)
_value: uint256
event Approval:
_owner: indexed(address)
_spender: indexed(address)
_value: uint256
# Inmutables
NAME: immutable(String[10])
SYMBOL: immutable(String[5])
DECIMALS: immutable(uint8)
OWNER: immutable(address)
# Variables de almacenamiento
balanceOf: public(HashMap[address, uint256])
allowance: public(HashMap[address, HashMap[address, uint256]])
totalSupply: public(uint256)
@deploy
def __init__(_name: String[10], _symbol: String[5]):
NAME = _name
SYMBOL = _symbol
DECIMALS = 18
OWNER = msg.sender
@external
def transfer(_to: address, _value: uint256) -> bool:
"""
@dev Transfiere tokens a una dirección especificada.
@param _to La dirección a la que se transfiere.
@param _value La cantidad a transferir.
"""
self.balanceOf[msg.sender] -= _value
self.balanceOf[_to] += _value
log Transfer(_from=msg.sender, _to=_to, _value=_value)
return True
@external
def approve(_spender: address, _value: uint256) -> bool:
"""
@dev Autoriza a `_spender` a transferir hasta `_value` tokens.
"""
self.allowance[msg.sender][_spender] = _value
log Approval(_owner=msg.sender, _spender=_spender, _value=_value)
return True
@external
def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
"""
@dev Mueve tokens de `_from` a `_to` usando el mecanismo de allowance.
"""
self.allowance[_from][msg.sender] -= _value
self.balanceOf[_from] -= _value
self.balanceOf[_to] += _value
log Transfer(_from=_from, _to=_to, _value=_value)
return True
@external
def mint(_to: address, _value: uint256) -> bool:
"""
@dev Acuña `_value` cantidad de monedas a `_to`
"""
assert msg.sender == OWNER, "Solo el OWNER puede acuñar"
self._mint(_to, _value)
return True
@external
@view
def name() -> String[10]:
return NAME
@external
@view
def symbol() -> String[5]:
return SYMBOL
@external
@view
def decimals() -> uint8:
return DECIMALS
@internal
def _mint(_to: address, _value: uint256):
self.balanceOf[_to] += _value
self.totalSupply += _value
log Transfer(_from=empty(address), _to=_to, _value=_value)Esta es la revisión final de la sección de despliegue. He corregido los errores de dedo, unificado la red a Base (para mantener la coherencia con el inicio del artículo) y optimizado el código para que sea un script profesional y seguro.
Para el toque final, usaremos Titanoboa (o boa), un framework extremadamente ligero y potente para desplegar y testear contratos de Vyper usando Python puro.
Crea una carpeta llamada scripts/ y dentro un archivo llamado deploy.py.
Instalaremos eth-account para manejar nuestras llaves de forma segura: uv add eth-account
Regla de oro: Nunca dejes tu llave privada en texto plano en el código o en archivos .env. Es la forma más fácil de que te roben tus fondos.
Usaremos un archivo Keystore JSON (un archivo cifrado con contraseña). Si usas herramientas como Ape o Foundry, ya los tienes; si no, asegúrate de generar uno con eth-account siguiendo esta pequeña guia (el codigo para generar un keystore son menos de 35 lineas de Python!).
Aquí tienes el script completo. He configurado el RPC de Arbitrum Mainnet, pero puedes cambiarlo a Sepia si prefieres testear primero.
import getpass
import boa
from eth_account import Account
def load_keystore_account():
"""Carga una cuenta desde un archivo keystore de forma segura."""
with open("account.keystore.json", "r") as acc:
password = getpass.getpass("\t Ingrese su contraseña del Keystore: ")
encrypted_account = acc.read()
account_pk = Account.decrypt(encrypted_account, password)
return Account.from_key(account_pk)
def main():
# 1. Definimos la red (Arbitrum en este caso)
# Si quieres puedes un provider como Alchemy/Infura
rpc_url = "https://arb1.arbitrum.io/rpc"
with boa.set_network_env(rpc_url):
# 2. Cargamos la cuenta y la añadimos al entorno de boa
account = load_keystore_account()
boa.env.add_account(account)
print(f"Desplegando contrato con la cuenta: {account.address}...")
# 3. Desplegamos el contrato
# boa.load compila y envía la transacción de despliegue
erc20_contract = boa.load(
"contracts/ERC20.vy",
"Mi Token", # _name
"MT" # _symbol
)
print(f"¡Éxito! Contrato desplegado en: {erc20_contract.address}")
return erc20_contract
if __name__ == "__main__":
main()Para ejecutarlo, simplemente corre en tu terminal: uv run scripts/deploy.py
Titanoboa se encargará de compilar tu código Vyper al vuelo, firmar la transacción con tu cuenta cifrada y enviarla a la red de Arbitrum.
¡Felicidades! Has pasado de una carpeta vacía a tener una criptomoneda optimizada y desplegada en una Layer 2 usando el stack más moderno de Python y Vyper. Puedes buscar el address de tu contrato en arbiscan.io.
Rafael Abuawad
Rafael Abuawad
No activity yet