En el artículo anterior, cubrimos el modelo declare-deploy de Starknet, incluidas las rutas de despliegue para contratos regulares y de cuentas. Este artículo pone en práctica esos conceptos. Utilizando el contrato ERC-20 del capítulo ERC-20 como nuestro ejemplo de despliegue, repasaremos el flujo de trabajo completo de despliegue tanto con Starknet Foundry (sncast) como con Starknet.js: configurar una cuenta para firmar transacciones, declarar la clase del contrato ERC-20, desplegar una instancia del contrato ERC-20 e interactuar con el contrato desplegado.
Configuración del contrato
Crea un nuevo proyecto de Scarb con scarb new erc20, navega al directorio del proyecto con cd erc20, y luego reemplaza el contenido de src/lib.cairo con el siguiente código:
use starknet::ContractAddress;
#[starknet::interface]
pub trait IERC20<TContractState> {
fn total_supply(self: @TContractState) -> u256;
fn balance_of(self: @TContractState, account: ContractAddress) -> u256;
fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256,
) -> bool;
fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool;
fn name(self: @TContractState) -> ByteArray;
fn symbol(self: @TContractState) -> ByteArray;
fn decimals(self: @TContractState) -> u8;
fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; // For testing purposes
}
#[starknet::contract]
pub mod ERC20 {
use starknet::{ContractAddress, get_caller_address};
use starknet::storage::{
Map, StoragePointerWriteAccess, StoragePointerReadAccess, StoragePathEntry,
};
#[storage]
pub struct Storage {
balances: Map<ContractAddress, u256>,
allowances: Map<
(ContractAddress, ContractAddress), u256,
>, // (owner, spender) -> amount
token_name: ByteArray,
symbol: ByteArray,
decimal: u8,
total_supply: u256,
owner: ContractAddress,
}
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
Transfer: Transfer,
Approval: Approval,
}
#[derive(Drop, starknet::Event)]
pub struct Transfer {
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
amount: u256,
}
#[derive(Drop, starknet::Event)]
pub struct Approval {
#[key]
owner: ContractAddress,
#[key]
spender: ContractAddress,
value: u256,
}
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.token_name.write("Rare Token");
self.symbol.write("RST");
self.decimal.write(18);
self.owner.write(owner);
}
#[abi(embed_v0)]
impl ERC20Impl of super::IERC20<ContractState> {
fn total_supply(self: @ContractState) -> u256 {
self.total_supply.read()
}
fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
let balance = self.balances.entry(account).read();
balance
}
fn name(self: @ContractState) -> ByteArray {
self.token_name.read()
}
fn symbol(self: @ContractState) -> ByteArray {
self.symbol.read()
}
fn decimals(self: @ContractState) -> u8 {
self.decimal.read()
}
fn allowance(
self: @ContractState, owner: ContractAddress, spender: ContractAddress,
) -> u256 {
let allowance = self.allowances.entry((owner, spender)).read();
allowance
}
fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let sender = get_caller_address();
let sender_prev_balance = self.balances.entry(sender).read();
let recipient_prev_balance = self.balances.entry(recipient).read();
assert(sender_prev_balance >= amount, 'Insufficient amount');
self.balances.entry(sender).write(sender_prev_balance - amount);
self.balances.entry(recipient).write(recipient_prev_balance + amount);
assert(
self.balances.entry(recipient).read() > recipient_prev_balance,
'Transaction failed',
);
self.emit(Transfer { from: sender, to: recipient, amount });
true
}
fn transfer_from(
ref self: ContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: u256,
) -> bool {
let spender = get_caller_address();
let spender_allowance = self.allowances.entry((sender, spender)).read();
let sender_balance = self.balances.entry(sender).read();
let recipient_balance = self.balances.entry(recipient).read();
assert(amount <= spender_allowance, 'amount exceeds allowance');
assert(amount <= sender_balance, 'amount exceeds balance');
self.allowances.entry((sender, spender)).write(spender_allowance - amount);
self.balances.entry(sender).write(sender_balance - amount);
self.balances.entry(recipient).write(recipient_balance + amount);
self.emit(Transfer { from: sender, to: recipient, amount });
true
}
fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool {
let caller = get_caller_address();
self.allowances.entry((caller, spender)).write(amount);
self.emit(Approval { owner: caller, spender, value: amount });
true
}
fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {
let caller = get_caller_address();
assert(caller == self.owner.read(), 'Call not owner');
let previous_total_supply = self.total_supply.read();
let previous_balance = self.balances.entry(recipient).read();
self.total_supply.write(previous_total_supply + amount);
self.balances.entry(recipient).write(previous_balance + amount);
let zero_address: ContractAddress = 0.try_into().unwrap();
self.emit(Transfer { from: zero_address, to: recipient, amount });
true
}
}
}
Desplegaremos este contrato utilizando dos enfoques: Starknet Foundry (sncast) y Starknet.js. Ambos enfoques siguen los mismos pasos de despliegue:
- Configurar una cuenta para firmar transacciones y pagar tarifas de gas
- Declarar una clase de contrato ERC-20 para registrar el código on-chain y obtener un class hash
- Desplegar la instancia del contrato ERC-20 para crear una instancia activa a partir de ese class hash
Desplegando con Starknet Foundry (sncast)
Configuración de la cuenta
Necesitamos un contrato de cuenta para firmar transacciones y pagar el despliegue. Si ya tienes una cuenta de sncast configurada, puedes saltar al paso de declaración. De lo contrario, vamos a crear una.
El comando para crear una cuenta usando sncast es:
sncast account create --network <NETWORK_NAME> --name <ACCOUNT_NAME>
Donde:
<NETWORK_NAME>es la red en la que deseas desplegar (por ejemplo,sepolia,mainnet)<ACCOUNT_NAME>es un identificador local arbitrario que eliges para hacer referencia a esta cuenta (por ejemplo,my_account,deployer_account)
Por ejemplo, para crear una cuenta en Sepolia:
sncast account create --network sepolia --name new_account_1
Cuando ejecutas el comando anterior, sncast:
- genera un par de claves privada/pública localmente
- calcula la dirección de la cuenta de forma determinista a partir de la clave pública y el salt
- guarda los detalles de la cuenta en un archivo JSON local (
~/.starknet_accounts/starknet_open_zeppelin_accounts.json). Inspecciona el archivo con:
cat ~/.starknet_accounts/starknet_open_zeppelin_accounts.json
- La dirección se vuelve conocida para la red (en nuestro caso, sepolia) pero aún no se ha desplegado ningún contrato de cuenta.
Nota: Aquí utilizamos
account create(en lugar dedeclare) ya que las clases de contratos de cuentas suelen ser pre-declaradas por sus proveedores. En este caso, OpenZeppelin ya ha declarado la clase de contrato de cuenta que estamos utilizando, por lo que solo estamos creando una instancia del mismo, no declarando uno nuevo.
Después de ejecutar el comando create, tu salida debería verse de la siguiente manera:

Si buscas la dirección en Voyager, notarás que la dirección de la cuenta existe pero con un estado de “Uninitialized”. Esto demuestra que el contrato de la cuenta aún no se ha desplegado.

Para desplegar la cuenta recién creada, copia la dirección de la salida y fóndeala con tokens STRK desde la faucet de Starknet. Este es el proceso de despliegue contrafactual que discutimos en el artículo anterior: la cuenta paga por su propio despliegue desde su dirección pre-fondeada, resultando en una nueva cuenta que existe de manera independiente y no está vinculada a ninguna otra cuenta.
Una vez que hayas obtenido los tokens de prueba, despliega la cuenta usando:
sncast account deploy --network sepolia --name <ACCOUNT_NAME>
Reemplaza <ACCOUNT_NAME> con el mismo nombre utilizado durante la creación de la cuenta. Para este ejemplo, usaremos new_account_1 ya que eso es lo que usamos en el paso account create:
sncast account deploy --network sepolia --name new_account_1
Después de ejecutar el comando anterior, sncast te pedirá que conviertas esta cuenta en la predeterminada local o global. Una predeterminada local se aplica solo al proyecto actual, mientras que una predeterminada global se aplica a todos los proyectos de Scarb. Para este tutorial, elige local. Una vez que confirmes, la cuenta será desplegada:

Si revisas la transacción en Voyager, notarás que el tipo de transacción es DEPLOY_ACCOUNT. Este es el tipo de transacción a nivel de protocolo dedicado a la creación de una cuenta por primera vez, donde no se necesita ninguna cuenta existente para patrocinar el despliegue.

Durante esta transacción, el secuenciador valida el despliegue, ejecuta el constructor de la cuenta con tu clave pública, y cobra las tarifas de despliegue de la dirección pre-fondeada. Tu contrato de cuenta ya está activo en Starknet.
Declarando la clase de contrato ERC-20
A diferencia de los contratos de cuenta que son pre-declarados por sus proveedores, los contratos regulares como nuestro ERC-20 deben ser declarados por nosotros antes del despliegue.
La sintaxis para declarar un contrato regular es:
sncast --account <ACCOUNT_NAME> \
declare \
--url <URL> \
--contract-name <CONTRACT_NAME>
Donde:
<ACCOUNT_NAME>: el nombre de la cuenta que creaste y desplegaste anteriormente<URL>: la URL del endpoint RPC para la red en la que estás desplegando<CONTRACT_NAME>: el nombre del módulo del contrato, por ejemploERC20demod ERC20 {}
Para declarar nuestro contrato ERC-20 en Sepolia, ejecuta el siguiente comando, reemplazando {apiKey} con tu clave API de Alchemy y new_account_1 con el nombre de tu cuenta:
sncast --account new_account_1 \
declare \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/{apiKey} \
--contract-name ERC20
Recuerda que la declaración registra el código de tu contrato on-chain y devuelve un class hash. Esto es lo que sucede durante este proceso:
- Compilación:
sncastlee tu archivo Sierra, lo compila a CASM (Cairo Assembly) localmente y calcula el class hash compilado a partir de ese CASM - Envío de transacción:
sncastenvía tanto la clase Sierra como el class hash compilado localmente al secuenciador, firmado por tu cuenta - Verificación del secuenciador: El secuenciador compila el Sierra a CASM y verifica que su class hash compilado coincida con lo que proporcionaste
- Almacenamiento en la red: Si los hashes coinciden, tanto la clase Sierra como el class hash compilado se almacenan en Starknet
- Retorno del class hash: El secuenciador devuelve el class hash (calculado a partir de Sierra), que identifica esta clase de contrato.
La salida muestra tanto el hash de la transacción como el class hash. Copia el class hash ya que lo necesitaremos para desplegar la instancia del contrato:

sncast proporciona un comando de despliegue listo para usar en la salida. Puedes copiar ese comando generado directamente, o usar el formato a continuación para una mejor legibilidad. Ambos enfoques funcionan de manera idéntica.
Desplegando instancias de contratos a través del UDC
Con la clase de nuestro contrato declarada, ahora podemos desplegar cualquier número de instancias a partir de ese class hash. El comando sncast deploy abstrae la interacción con el UDC y maneja el despliegue internamente.
La sintaxis para desplegar un contrato es:
sncast \
--account <ACCOUNT_NAME> \
deploy \
--class-hash <CLASS_HASH> \
--arguments "<CONSTRUCTOR_ARGS>" \
--url <URL>
Por defecto, sncast genera un salt aleatorio para asegurar una dirección de contrato única. Si necesitas predecir o reproducir la dirección del contrato resultante, puedes pasar --salt con un valor específico, por ejemplo --salt 0x146. También puedes pasar --unique, lo cual modifica el salt usando tu dirección de desplegador para asegurar que la dirección sea única para tu cuenta. Para este tutorial, omitimos ambas banderas y dejamos que sncast genere el salt automáticamente.
Para desplegar una instancia de nuestro contrato ERC-20, ejecuta el siguiente comando. Reemplaza <OWNER_ADDRESS> con la dirección real de tu cuenta y {apiKey} con tu clave API de Alchemy. Puedes usar el class hash de tu propia declaración o el que se proporciona a continuación:
sncast \
--account new_account_1 \
deploy \
--class-hash 0x23a5a4819dcac3a6b5fe724596647d3fc1f176ca565a0fb908c4457f1cc875b \
--arguments "<OWNER_ADDRESS>" \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/{apiKey}
La dirección del propietario puede estar envuelta entre comillas simples o dobles. Esta dirección se establecerá como la propietaria del contrato, lo que significa que solo esta cuenta estará autorizada para mintear tokens, así que asegúrate de que sea la dirección que planeas usar para interactuar con el contrato.
Recibirás un recibo de la transacción en tu terminal con la dirección del contrato:

Puedes verificar el despliegue en Voyager. Nota que el tipo de transacción es INVOKE y la operación es deployContract. Esto confirma que sncast desplegó el contrato a través del UDC:

Haz clic en el hash de la transacción para abrir los detalles de la misma. Esto muestra los parámetros exactos pasados a la función deployContract del UDC:

Cuando ejecutaste el comando de despliegue del contrato, sncast manejó la interacción con el UDC automáticamente:
- Como no se proporcionó
--salt,sncastgenera un salt aleatorio (0xd815d280876b878ecomo se muestra en la segunda fila de la tabla de datos de entrada) para asegurar una dirección de contrato única - Tu cuenta envía una transacción
INVOKEal UDC con:classHash:0x23a5a4819dcac3a6b5fe724596647d3fc1f176ca565a0fb908c4457f1cc875b(primera fila en la tabla de datos de entrada)salt:0xd815d280876b878e(segunda fila en la tabla de datos de entrada)notFromZero:0x0(false); dado que no se pasó--unique, la dirección del contrato se deriva de la dirección del desplegador en lugar de la dirección cerocalldata:[0x14154fb6dd088b5ceb46df635ecce6e1a9b0455357931ac7df4263a7dbf39a9](cuarta fila en la tabla de datos de entrada - los argumentos de tu constructor, la dirección del propietario)
- El UDC llama a
deployContractpara crear la instancia de tu contrato devolviendo la dirección del contrato0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125(en el campo de datos de salida resaltado en amarillo)
Desplegando con Starknet.js
Como alternativa al uso del comando sncast de Starknet Foundry, también podemos desplegar nuestro contrato usando Starknet.js. Este enfoque te permite realizar el despliegue programáticamente, lo que facilita la automatización de los despliegues o su integración en tu flujo de trabajo de desarrollo.
Configurando el entorno
Dado que la clase del contrato ERC-20 ya se declaró on-chain utilizando el enfoque sncast, intentar declarar el mismo código nuevamente desde el mismo proyecto con la misma versión del compilador resultará en un error de “contract has already been declared”, porque ya existe un class hash idéntico on-chain. Para evitar este error, crearemos un nuevo proyecto de Scarb:
scarb new erc20_starknetjs
cd erc20_starknetjs
Copia el mismo código del contrato ERC-20 de la sección de Configuración del contrato a src/lib.cairo, y luego compila el proyecto usando scarb build. Esto te da una base limpia para la declaración y despliegue usando Starknet.js.
Ahora, crea una carpeta scripts para el código de despliegue:
mkdir scripts
Instala los paquetes requeridos para el script de despliegue:
npm install starknet dotenv
npm install -D typescript @types/node tsx
Propósitos de los paquetes:
- starknet es la biblioteca oficial para interactuar con los contratos de Starknet
- dotenv carga las variables de entorno desde un archivo
.env - typescript & @types/node proporcionan soporte de typescript para nuestro script
- tsx es el ejecutor moderno de typescript que maneja los módulos ES
Configura tu proyecto para usar la sintaxis de módulos ES (import/export) en lugar de CommonJS (require/module.exports) añadiendo el campo "type": "module" a tu package.json :
{
"dependencies": {
"dotenv": "^17.2.3",
"starknet": "^9.2.1"
},
"type": "module",
"devDependencies": {
"@types/node": "^25.0.3",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
Esto nos permite usar declaraciones import en lugar de la sintaxis más antigua require().
Crea un archivo .env en la raíz de tu proyecto:
PRIVATE_KEY=your_private_key_here
ALCHEMY_API_KEY=your_alchemy_api_key_here
OWNER_ADDRESS=your_owner_address_here
Dado que estaremos llamando funciones de escritura en el contrato desplegado a través de Voyager, necesitarás conectar una wallet como Ready o Braavos. Tienes dos opciones:
Opción 1: Importar la cuenta que creamos (Recomendado)
Importa la cuenta que creamos anteriormente en la wallet Ready usando la clave privada desde ~/.starknet_accounts/starknet_open_zeppelin_accounts.json. Sigue esta guía para importar tu cuenta:
Una vez importada, configura lo siguiente en tu archivo .env:
OWNER_ADDRESS: La dirección de la cuenta importada (la misma que en el archivo JSON)PRIVATE_KEY: La clave privada del archivo JSON
Opción 2: Usar tu cuenta existente
Si ya tienes una cuenta de Sepolia en Ready o Braavos, puedes usar esas credenciales en su lugar:
OWNER_ADDRESS: La dirección de tu wallet existentePRIVATE_KEY: La clave privada de tu wallet existente
Añade
.enva tu archivo.gitignorepara evitar enviar tu clave privada al control de versiones.
Generando el archivo CASM
Antes de ejecutar el script de despliegue, necesitas generar el archivo CASM a partir de tu archivo Sierra. Las versiones más recientes de Scarb no generan automáticamente los archivos CASM cuando ejecutas scarb build.
Esto requiere el compilador de Sierra, el cual puedes instalar ejecutando:
cargo install starknet-sierra-compile
Una vez instalado, compila tu archivo Sierra a CASM ejecutando
starknet-sierra-compile \
target/dev/erc20_starknetjs_ERC20.contract_class.json \
target/dev/erc20_starknetjs_ERC20.compiled_contract_class.json

Este comando toma dos argumentos:
- Input (Entrada):
target/dev/erc20_starknetjs_ERC20.contract_class.json- el archivo Sierra generado porscarb build - Output (Salida):
target/dev/erc20_starknetjs_ERC20.compiled_contract_class.json- el archivo de bytecode CASM que será creado
El nombre del archivo sigue el patrón
{package_name}_{contract_module_name}. Si el nombre de tu contrato difiere deerc20_starknetjs_ERC20, ajusta ambas rutas de archivo en consecuencia, y luego ejecuta el comando.
Verás el archivo .compiled_contract_class.json recién generado en tu directorio target/dev, que contiene el bytecode CASM necesario para el despliegue.

Escribiendo el script de despliegue
El script que estamos creando automatiza cada paso del proceso de despliegue. Se conecta a Starknet Sepolia, inicializa tu cuenta usando tu clave privada y recupera los archivos Sierra y CASM.
Crea scripts/deploy.ts con el siguiente código:
import { Account, RpcProvider, json, CallData } from "starknet";
import fs from "fs";
import * as dotenv from "dotenv";
dotenv.config();
async function main() {
// Setup provider with Alchemy RPC URL
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/${process.env.ALCHEMY_API_KEY}`,
});
// Setup account
const account = new Account({
provider: provider,
address: process.env.OWNER_ADDRESS!,
signer: process.env.PRIVATE_KEY!,
});
// Read the compiled contract files
const compiledSierra = json.parse(
fs
.readFileSync("./target/dev/erc20_starknetjs_ERC20.contract_class.json")
.toString("ascii")
);
const compiledCasm = json.parse(
fs
.readFileSync(
"./target/dev/erc20_starknetjs_ERC20.compiled_contract_class.json"
)
.toString("ascii")
);
// Declare the contract
console.log("\nDeclaring contract...");
const declareResponse = await account.declare({
contract: compiledSierra,
casm: compiledCasm,
});
console.log(
"Declaration transaction hash:",
declareResponse.transaction_hash
);
await provider.waitForTransaction(declareResponse.transaction_hash);
// Prepare constructor arguments
const myCallData = new CallData(compiledSierra.abi);
const constructorCalldata = myCallData.compile("constructor", {
owner: account.address,
});
// Deploy the contract
console.log("\nDeploying contract...");
const deployResponse = await account.deployContract({
classHash: declareResponse.class_hash,
constructorCalldata: constructorCalldata,
});
console.log("Deployment transaction hash:", deployResponse.transaction_hash);
await provider.waitForTransaction(deployResponse.transaction_hash);
console.log("\nDeployment Summary:");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("Class Hash:", declareResponse.class_hash);
console.log("Contract Address:", deployResponse.contract_address);
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error("\nDeployment failed:", error);
process.exit(1);
});
Actualiza el nombre del contrato en tu script de despliegue con el nombre correcto si el tuyo difiere de erc20_starknetjs_ERC20.
Para encontrar el nombre correcto para tu proyecto, ejecuta:
ls target/dev/*.contract_class.json
Por ejemplo, si ves my_token_MyERC20.contract_class.json, actualiza las rutas de los archivos de esta manera:
// Change from:
fs.readFileSync("./target/dev/erc20_starknetjs_ERC20.contract_class.json");
// To:
fs.readFileSync("./target/dev/my_token_MyERC20.contract_class.json");
Haz lo mismo para los archivos .contract_class.json y .compiled_contract_class.json.
Diferencias clave con sncast:
Starknet.js adopta un enfoque diferente en comparación con el enfoque de línea de comandos de Starknet Foundry. En lugar de ejecutar comandos de declaración y despliegue por separado, la función declareAndDeploy() maneja ambos pasos a la vez, lo que significa que no tienes que copiar class hashes entre comandos. Aunque ambos enfoques dependen del UDC para el despliegue, Starknet.js nos permite controlar programáticamente aspectos como la generación del salt y los tipos de despliegue, facilitando la construcción de scripts de despliegue automatizados con un manejo adecuado de errores.
Ejecuta el siguiente comando para desplegar tu contrato:
npx tsx scripts/deploy.ts
Si tiene éxito, verás la salida de la siguiente manera:

Interactuando con el contrato desplegado en Voyager
Ahora que el contrato está desplegado, podemos interactuar con él a través de la interfaz web de Voyager. Ve al contrato desplegado en Voyager Sepolia. Haz clic en la pestaña “Write Contract”. Esta interfaz muestra todas las funciones públicas del contrato.
Conecta tu wallet (Ready o Braavos) y asegúrate de estar utilizando la wallet que tiene la dirección que configuraste como owner. Esto es importante porque solo el owner puede llamar a funciones restringidas como mint.

Mintear tokens En la sección Write, encuentra la función mint y completa los parámetros:

Haz clic en “Write” y confirma la transacción en tu wallet. Después de la confirmación, recibirás el hash de la transacción.
Cambia a la sección Read y encuentra total_supply. Haz clic en "Query" para ver el resultado:

Verificar tu balance En la misma sección Read, usa balance_of con la dirección a la que acabas de mintear tokens. Haz clic en “Query” para confirmar que recibiste 1200000000000000000 (lo cual equivale a 1000 Raretokens minteados).

Puedes experimentar más transfiriendo tokens a otras direcciones, minteando más tokens, o verificando los allowances (permisos) para probar completamente la funcionalidad de tu contrato.
Interactuando con el ERC-20 usando Starknet.js
Hemos visto cómo interactuar con contratos a través de la interfaz web de Voyager. Ahora hagamos lo mismo usando Starknet.js en su lugar. El script a continuación mintea tokens para una cuenta y muestra la confirmación de la transacción con los detalles del evento. Crea scripts/interact.ts con el siguiente código:
import { Account, RpcProvider, Contract } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
async function interactWithERC20() {
// initialize provider for Sepolia testnet
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/${process.env.ALCHEMY_API_KEY}`,
});
// initialize account
const account = new Account({
provider: provider,
address: process.env.OWNER_ADDRESS!,
signer: process.env.PRIVATE_KEY!,
});
// REPLACE WITH YOUR DEPLOYED CONTRACT ADDRESS
const contractAddress =
"0x1416c331f53ddfc9cd022984a4ef6b9871a3c82e59904de2cd8214cfa59f00c";
// fetch contract ABI from deployed contract
const { abi } = await provider.getClassAt(contractAddress);
if (abi === undefined) {
throw new Error("ABI not found");
}
// create contract instance
const contract = new Contract({
abi,
address: contractAddress,
providerOrAccount: provider,
});
// prepare the mint call data
const call = contract.populate("mint", [
account.address,
3000000000000000000000n, // mint 3000 tokens (with 18 decimals)
]);
console.log("Minting tokens...");
// execute the transaction through the account
const { transaction_hash: mintTxHash } = await account.execute(call);
console.log("Transaction hash:", mintTxHash);
// wait for transaction confirmation
const receipt = await provider.waitForTransaction(mintTxHash);
if (receipt.isSuccess()) {
console.log("\nMint successful!");
// Parse and display emitted events
const events = contract.parseEvents(receipt);
console.log("\nEvents emitted:");
console.log(events);
} else {
console.error("Transaction failed");
}
}
// run the function and handle any errors
interactWithERC20().catch(console.error);
Reemplaza contractAddress con la dirección de tu contrato desplegado y ejecuta el script usando:
npx tsx scripts/interact.ts
Deberías ver una salida que muestre el minteo exitoso y el evento Transfer emitido desde la dirección cero hacia tu cuenta de la siguiente manera:

Interactuando con tokens existentes
También puedes interactuar con tokens ERC-20 existentes como STRK cambiando la dirección del contrato. Crea un nuevo archivo interact_existing.ts y pega el siguiente script para verificar tu balance de STRK:
import { RpcProvider, Contract } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
async function checkSTRKBalance() {
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/${alchemyApiKey}`,
});
// REPLACE WITH YOUR ACCOUNT ADDRESS
const accountAddress =
"0x014154fb6Dd088b5ceB46df635eCCe6e1a9B0455357931aC7Df4263A7dBf39a9";
const strkContractAddress =
"0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
const { abi } = await provider.getClassAt(strkContractAddress);
const strkContract = new Contract({
abi,
address: strkContractAddress,
providerOrAccount: provider,
});
const balance = await strkContract.balance_of(accountAddress);
console.log(`STRK Balance (raw): ${balance.toString()}`);
const balanceInSTRK = Number(balance) / 10 ** 18;
console.log(`STRK Balance: ${balanceInSTRK} STRK`);
}
checkSTRKBalance().catch(console.error);
Ejecuta con: npx tsx scripts/interact_existing.ts

Nota: Al interactuar con tokens existentes como STRK, puedes verificar balances y, si posees tokens, transferirlos o aprobarlos, pero el minteo requiere ser el owner del contrato.
Conclusión
En este artículo, cubrimos ejemplos prácticos de cómo desplegar e interactuar con tokens ERC-20 usando sncast y Starknet.js. En el próximo artículo, exploraremos los patrones de fábrica (factory patterns) utilizando directamente el deploy_syscall, mostrando cómo los contratos pueden desplegar otros contratos sin necesidad del UDC.