En Ethereum, despliegas un contrato en una sola transacción. Starknet adopta un enfoque diferente: el despliegue se divide en dos transacciones separadas, la declaración y el despliegue.
La transacción de declaración registra el bytecode del contrato on-chain y produce un class hash, mientras que la transacción de despliegue utiliza ese class hash para crear una instancia del contrato con su propia dirección y almacenamiento. Nos referiremos a este proceso de dos pasos como el modelo declare-deploy a lo largo de esta serie.
En este artículo, aprenderás cómo:
- Funciona entre bastidores el modelo declare-deploy de Starknet
- Los contratos regulares se despliegan a través del Universal Deployer Contract (UDC), aunque el UDC también puede desplegar contratos de cuenta (account contracts), como se explica más adelante en este artículo
- Los contratos de cuenta se despliegan mediante el tipo de transacción
DEPLOY_ACCOUNT(después de que el bytecode del contrato se declare on-chain a través de una transacciónDECLARE)
Despliegue en Ethereum vs Starknet
Supongamos que deseas lanzar múltiples tokens ERC-20. En Ethereum, cada despliegue requiere subir el bytecode completo del contrato y pagar gas para almacenar todos esos datos. Repites este proceso para cada contrato de token, aunque el código sea casi idéntico. Esto significa que estás pagando para almacenar código duplicado repetidamente.
Starknet evita esto separando la clase del contrato (el bytecode) de la instancia del contrato. Declarar la clase del contrato almacena el bytecode on-chain una sola vez, luego puedes desplegar tantas instancias como necesites que hagan referencia a la clase declarada.
Esta separación también permite la actualizabilidad de los contratos (upgradeability): la lógica de un contrato desplegado se puede intercambiar sin cambiar su dirección y almacenamiento. Esto se cubre en detalle en el artículo “Upgrading Contracts”.
Para implementar esta separación, Starknet utiliza tipos de transacciones específicos que manejan la declaración y el despliegue como operaciones separadas.
Tipos de transacciones para el despliegue en Starknet
Starknet define actualmente cuatro tipos de transacciones a nivel de protocolo: DECLARE, DEPLOY_ACCOUNT, INVOKE y L1_HANDLER. Puedes verlos todos en el explorador, que también puede mostrar el tipo de transacción DEPLOY en desuso por razones de retrocompatibilidad. Nos centraremos en los tres relevantes para el despliegue de contratos (resaltados en cuadros rojos a continuación):

DECLARE: registra el código del contrato on-chainDEPLOY_ACCOUNT: despliega un contrato de cuenta.INVOKE: despliega contratos regulares y ejecuta llamadas a funciones en contratos ya desplegados. Es el tipo de transacción utilizado para enviar tokens, hacer swaps en DEXs o realizar cualquier otra interacción con contratos.
DECLARE se usa siempre para la declaración, mientras que el paso de despliegue usa INVOKE para contratos regulares o DEPLOY_ACCOUNT para contratos de cuenta.
Antes de detallar el proceso de declaración y despliegue, necesitamos entender los dos tipos de contratos en Starknet: los contratos regulares y los contratos de cuenta, ya que cada uno de ellos utiliza enfoques de despliegue distintos.
| Contrato regular | Contrato de cuenta |
|---|---|
| Un contrato regular es un contrato inteligente que implementa lógica de aplicación como tokens ERC-20, NFTs, etc. Los contratos regulares no pueden iniciar transacciones por sí mismos y deben ser llamados por contratos de cuenta. | Un contrato de cuenta, por otro lado, es un tipo de contrato inteligente que puede validar que una transacción está autorizada y ejecutar transacciones. Los contratos de cuenta sirven como punto de entrada para todas las transacciones en Starknet. Tu cuenta en una billetera Ready o Braavos es un contrato de cuenta desplegado on-chain. |
Con estos dos tipos de contratos en mente, repasemos cada paso del proceso declare-deploy.
Declarando una clase de contrato
Para declarar un contrato, primero necesitamos compilar nuestro código fuente en Cairo al formato que Starknet espera. El proceso de compilación tiene dos etapas. Primero, el compilador convierte el código Cairo a Sierra (Safe Intermediate Representation). Luego, durante la declaración, el secuenciador (el nodo que ordena las transacciones y construye los bloques en Starknet) compila Sierra a CASM (Cairo Assembly) on-chain.
Sierra y CASM
- Sierra es una representación intermedia entre el código Cairo y CASM. Garantiza que cada ejecución de contrato o programa de Cairo pueda ser probada, incluso si una transacción revierte.
- Una clase Sierra es el código de tu contrato en el formato Sierra. La distinción entre Sierra y una clase Sierra es análoga a cómo JSON es un formato y un archivo JSON es un documento específico en ese formato. La clase Sierra es lo que se declara on-chain porque es estable y verificable, asegurando que todo el código del contrato siempre pueda ser probado.
- CASM es el bytecode de bajo nivel que la Cairo VM interpreta para ejecutar el contrato. Es la forma compilada final de nuestro contrato, generada a partir de la clase Sierra.

Artefactos de construcción (Build Artifacts)
Cuando compilamos cualquier contrato con scarb build, por ejemplo, nuestro contrato ERC‑20, este produce dos artefactos de construcción en el directorio (target/dev):
1. Archivo Sierra (llamado ...contract_class.json)
Este es el archivo que contiene la clase Sierra, la cual sirve como el plano (blueprint) que la red utiliza para la declaración. Contiene cuatro campos clave:
sierra_program: la lógica del contrato compilada en bytecode Sierraentry_points_by_type: los puntos de entrada (entry points) invocables del contrato, agrupados por tipo. Un punto de entrada es un par compuesto por unselector(el hashstarknet_keccakdel nombre de la función, utilizado para identificar qué función llamar) y unfunction_idx(la posición de la implementación de esa función en el arraysierra_program) que le indica a la red qué función invocar y dónde encontrarla en el programa Sierra. Los tres tipos son:- constructor
- funciones externas del contrato (external functions)
- l1 handler: funciones que manejan mensajes enviados de Ethereum a Starknet. Este campo forma parte de la estructura del archivo Sierra para todos los contratos, pero está vacío para contratos como ERC-20 que no interactúan con L1.
abi: la interfaz del contrato, incluyendo las firmas de funciones, tipos de parámetros, tipos de retorno, eventos y structs.contract_class_version: la versión del formato de la clase del contrato.
2. Archivo de artefactos de Starknet (llamado ...starknet_artifacts.json)
Este archivo contiene los metadatos del contrato y lo vincula con su archivo Sierra compilado. Es utilizado localmente por herramientas como sncast para localizar el archivo Sierra correcto para un nombre de contrato dado:

Como puedes ver en el archivo de artefactos en la imagen anterior, el campo "casm" es null en el momento de la compilación. Esto se debe a que el CASM se generará a partir de la clase Sierra durante el proceso de declaración on-chain.
Qué sucede durante una transacción DECLARE
Con la clase Sierra compilada lista, utilizamos una transacción DECLARE para registrarla en la red. Herramientas como sncast o Starknet.js leen el archivo starknet_artifacts.json para ubicar el archivo de la clase Sierra del contrato, y luego envían esa clase Sierra en la transacción DECLARE, la cual nuestra cuenta firma. Posteriormente, el secuenciador compila Sierra a CASM y calcula dos hashes:
- Class hash: calculado a partir de los cuatro campos en el archivo Sierra utilizando la siguiente fórmula:
Dondeclass_hash = h( contract_class_version, external_entry_points, l1_handler_entry_points, constructor_entry_points, abi_hash, sierra_program_hash )hes la función hash Poseidon: una función hash optimizada para su uso dentro de pruebas STARK. Ten en cuenta queentry_points_by_typeaporta tres entradas separadas a la fórmula:external_entry_points,l1_handler_entry_pointsyconstructor_entry_points
Antes de que el ABI y el programa Sierra se integren en el cálculo, cada uno es hasheado primero:- El ABI es hasheado usando
starknet_keccak(bytes(ABI, "UTF-8"))para producir el hash del ABI (abi_hash), - El programa Sierra es hasheado para producir el hash del programa Sierra (
sierra_program_hash)
Dado que el nombre del contrato y del paquete aparecen a lo largo del ABI, en nombres de tipos de eventos comoerc20::ERC20::Transfery nombres de interfaces comoerc20::IERC20, dos contratos con idéntico código en Cairo pero diferentes nombres de contrato o paquete producirán diferentes hashes de ABI y, por lo tanto, diferentes class hashes, lo que significa que ambos pueden ser declarados con éxito. De manera similar, dos contratos compilados con diferentes versiones del compilador producirán valoressierra_program_hashdiferentes y, en consecuencia, class hashes diferentes, incluso si el código fuente en Cairo es idéntico. Sin embargo, si ninguno de estos factores difiere, el class hash resultante será idéntico a uno que ya está on-chain, y la red rechazará la declaración con un error indicando que el contrato ya ha sido declarado (“contract has already been declared”).
- El ABI es hasheado usando
- Compiled class hash: calculado a partir del código CASM generado desde la clase Sierra. Fija el código máquina exacto que ejecutará la Cairo VM.
Una vez que el secuenciador procesa la transacción DECLARE, la clase Sierra, el class hash y el compiled class hash se almacenan on-chain, haciendo que la clase del contrato esté disponible para su despliegue.
Desplegando una instancia del contrato
Una vez que se declara una clase de contrato, podemos crear múltiples instancias del mismo. Cada instancia comparte el mismo código, pero tiene su propia dirección y almacenamiento. Por ejemplo, implementaciones de cuentas como Ready (anteriormente Argent) se declaran una sola vez en Starknet. Cuando creas una nueva cuenta Ready, no se necesita una nueva declaración porque la clase Ready ya existe on-chain. Solo pagas por el despliegue de la cuenta, no por almacenar el código de nuevo. Tu nueva cuenta desplegada obtiene su propia dirección y almacenamiento únicos, pero utiliza el mismo código subyacente de Ready que todas las demás cuentas Ready.
La dirección de cada instancia de contrato desplegada se calcula a partir de la siguiente fórmula:
contract_address = pedersen(
"STARKNET_CONTRACT_ADDRESS",
deployer_address,
salt,
class_hash,
constructor_calldata_hash)
Donde pedersen es una función hash criptográfica compatible con pruebas STARK. Toma cinco entradas: un prefijo constante, la dirección del desplegador (deployer’s address), un valor salt, el class hash y un hash de los argumentos del constructor. Cambiar cualquiera de estas entradas produce una dirección diferente, que es la razón por la que puedes desplegar múltiples instancias del mismo class hash variando el salt, los argumentos del constructor o la dirección del desplegador.
El despliegue de una instancia de contrato requiere cuatro componentes clave:
- Referencia: Qué clase de contrato utilizar
- Entrada (Input): Qué datos pasar al constructor
- Pago de tarifas (Fee Payment): Qué cuenta paga por el despliegue
- Creación (Creates): Qué dirección obtiene la nueva instancia del contrato
Sin embargo, estos componentes funcionan de manera diferente dependiendo de si estás desplegando un contrato regular o un contrato de cuenta:
Contratos regulares (INVOKE vía Universal Deployer Contract)
├── Reference: Class hash del contrato (ya declarado)
├── Input: Constructor calldata + salt
├── Fee Payment: Desde la cuenta del remitente
└── Creates: Nueva instancia de contrato en una dirección determinista
Contratos de cuenta (DEPLOY_ACCOUNT)
├── Reference: Class hash de la cuenta (ya declarado)
├── Input: Constructor calldata + salt
├── Fee Payment: Desde la dirección pre-financiada de la propia cuenta desplegada
└── Creates: Nuevo contrato de cuenta en una dirección contrafáctica (calculada antes del despliegue)
Contratos de cuenta (INVOKE vía Universal Deployer Contract)
├── Reference: Class hash de la cuenta (ya declarado)
├── Input: Constructor calldata + salt
├── Fee Payment: Desde la cuenta del desplegador
└── Creates: Nuevo contrato de cuenta en una dirección determinista, vinculada al desplegador
Los contratos regulares se despliegan a través del Universal Deployer Contract (UDC) mediante una transacción INVOKE. Un contrato de cuenta existente llama al UDC, que a su vez llama a deploy_syscall para crear el nuevo contrato. El llamador (caller) paga la tarifa de la transacción. (Cubriremos el UDC en detalle en la siguiente sección).
El siguiente diagrama ilustra el flujo de despliegue completo para contratos regulares. Los pasos numerados muestran la secuencia de operaciones:
- La transacción
DECLAREregistra la clase del contrato on-chain y produce un class hash. - Tu cuenta envía una transacción
INVOKEal UDC, pasando el class hash, salt, calldata ynot_from_zerocomo parámetros.not_from_zeroes un booleano que determina si la dirección del desplegador se incluye en el cálculo de la dirección del contrato, lo cual se cubre en detalle en la sección del UDC. - Luego, el UDC llama internamente a
deploy_syscallpara crear las instancias del contrato, obteniendo cada una su propia dirección única.
El lado izquierdo del diagrama muestra el paso de declaración, mientras que el lado derecho muestra cómo el mismo class hash puede ser reutilizado para desplegar múltiples instancias independientes:

Los contratos de cuenta pueden ser desplegados de dos formas:
- Usando la transacción
DEPLOY_ACCOUNT: En este tipo de despliegue, el contrato de cuenta paga por su propio despliegue. La dirección resultante se calcula de antemano y se financia con tokens STRK. Posteriormente, una transacciónDEPLOY_ACCOUNTdespliega el contrato y cobra las tarifas de esa dirección pre-financiada. - INVOKE vía UDC: En este tipo de despliegue, un contrato de cuenta existente llama al UDC para desplegar el nuevo contrato de cuenta a través de una transacción
INVOKE, al igual que los contratos regulares. El desplegador paga la tarifa de la transacción.
Ahora que hemos cubierto el modelo declare-deploy a alto nivel, examinemos cada método de despliegue en detalle.
Desplegando contratos a través del Universal Deployer Contract (UDC)
Un UDC es un contrato singleton creado por OpenZeppelin que envuelve la función deploy_syscall, exponiéndola como una interfaz invocable que los contratos de cuenta pueden llamar. Sirve como una fábrica genérica estandarizada para contratos de Starknet. Está desplegado en la dirección 0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125 tanto en Starknet sepolia como en mainnet.
El UDC proporciona esta interfaz:
#[starknet::interface]
pub trait IUniversalDeployer {
fn deploy_contract(
class_hash: ClassHash,
salt: felt252,
not_from_zero: bool, // Determines deployment type
calldata: Span<felt252> // Constructor parameters
) -> ContractAddress;
}
Con los parámetros:
class_hash: El class hash del contrato a desplegarsalt: El valor salt para el cálculo de la direcciónnot_from_zero: Un booleano que determina si la dirección del desplegador se incluye en el cálculo de la dirección del contrato. Cuando estrue, se incluye. Cuando esfalse, no.calldata: Parámetros del constructor para el contrato recién desplegado.
Nota: La versión actual del UDC incluye cambios con respecto a versiones anteriores:
deployContractfue reemplazado pordeploy_contracten formato snake_case- El parámetro
uniquefue reemplazado pornot_from_zero(con semántica invertida)
Tipos de despliegue del UDC
El UDC proporciona dos tipos de despliegue que determinan cómo se calculan las direcciones de los contratos, controlados por el parámetro not_from_zero:
- Despliegue dependiente del origen (Origin-Dependent Deployment)
- Despliegue independiente del origen (Origin-Independent Deployment)
El UDC utiliza calculate_contract_address_from_udc, una función de utilidad de la biblioteca de contratos de OpenZeppelin, que asigna a not_from_zero la dirección del UDC o 0 antes de llamar al cálculo de dirección estándar:

- Despliegue dependiente del origen (
not_from_zero = true)
Cuando se utiliza el despliegue dependiente del origen, la dirección del desplegador (udc_address) pasa a formar parte del cálculo de la dirección. Esto crea un “espacio de direcciones reservado” donde solo el propietario (caller_address) de la cuenta que realiza el despliegue puede desplegar contratos en esas direcciones específicas.
El UDC modifica el salt proporcionado hasheándolo con la dirección del llamador (caller's address) usando el hash de Pedersen: hashed_salt = pedersen(caller_address, salt), y luego utiliza este salt modificado (hashed_salt) junto con:
class_hash(la clase del contrato que se está desplegando)constructor_calldata(parámetros del constructor)deployer_info.udc_address(la dirección del contrato UDC como desplegador)
en el cálculo de la dirección del contrato estándar.
Este enfoque proporciona reserva de direcciones, evitando que otros desplieguen en “tus” direcciones. Cada desplegador obtiene su propio espacio de direcciones, y solo tú puedes desplegar en direcciones derivadas de tu cuenta. Elegirías este método cuando deseas asegurar que nadie más pueda desplegar en las direcciones que tienes previstas.
- Despliegue independiente del origen (
not_from_zero = false)
Con el despliegue independiente del origen, las direcciones de los contratos se calculan independientemente de quién los despliegue. La dirección depende únicamente del salt, el class hash y los parámetros del constructor.
El UDC utiliza el salt original que se pasó sin cambios y transmite 0 como desplegador en el cálculo de la dirección del contrato estándar.
Esto hace que los despliegues sean deterministas entre los desplegadores: cualquiera que despliegue el mismo contrato con parámetros idénticos obtiene la misma dirección. Sin embargo, solo el primer despliegue tendrá éxito; los intentos posteriores con los mismos parámetros no se procesarán ya que la dirección ya está ocupada. Este método funciona bien cuando deseas que las direcciones de los contratos sean completamente predecibles independientemente de quién los despliegue, como al desplegar contratos estándar en múltiples redes.
Cómo funciona el despliegue con UDC
El contrato de tu cuenta realiza una llamada INVOKE a la función deployContract del UDC para crear el nuevo contrato:

La función deployContract (mostrada en el cuadro azul en la parte inferior) es un envoltorio (wrapper) en camelCase que llama a la función subyacente deploy_contract (mostrada en el bloque de código principal de arriba), la cual recibe los parámetros: class_hash, salt, not_from_zero, calldata.
El secuenciador valida la transacción INVOKE de tu cuenta, luego el UDC captura la dirección del llamador utilizando get_caller_address(), que es la dirección de tu cuenta que inició la transacción INVOKE, y modifica el salt basándose en el tipo de despliegue:
let final_salt = if not_from_zero {
pedersen(caller_address.into(), salt) // Origin-dependent: hash caller + salt
} else {
salt // Origin-independent: use original salt
};
Con el salt modificado (final) listo, el UDC envuelve deploy_syscall() (resaltado en rojo) utilizando el class hash, el salt final, el calldata del constructor y el tipo de despliegue para crear la instancia real del contrato.
El secuenciador ejecuta el constructor del contrato con los argumentos proporcionados, y cobra las tarifas de despliegue a la cuenta que realizó la llamada (no al contrato recién desplegado).
Tras un despliegue exitoso, el UDC emite un evento ContractDeployed (resaltado en rosa) que contiene la información del despliegue, incluyendo la nueva dirección del contrato, el desplegador, el tipo de despliegue, el class hash, el calldata y el salt original con fines de seguimiento. La función devuelve entonces la dirección del contrato recién desplegado al llamador.
Nota: Al desplegar contratos que utilizan get_caller_address() en su constructor, recuerda que el UDC es quien despliega el contrato, no tu cuenta directamente. Por lo tanto, get_caller_address() devuelve la dirección del UDC, no la dirección de tu cuenta. Es por eso que pasamos la dirección del propietario como parámetro en nuestro ejemplo de contrato de token.

UDC para contratos de cuenta
Aunque el UDC se usa principalmente para desplegar contratos regulares, también puede desplegar contratos de cuenta, como se mencionó anteriormente. En este caso, la cuenta que realiza el despliegue paga las tarifas y el nuevo contrato de cuenta se vincula al desplegador on-chain. Este enfoque es útil cuando no necesitas que la nueva cuenta sea prístina (sin historial previo).
Desplegando contratos de cuenta: La transacción DEPLOY_ACCOUNT
Desplegar tu primer contrato de cuenta presenta un problema de arranque (bootstrap problem): ¿cómo pagas por el despliegue si aún no tienes una cuenta?
Starknet resuelve esto mediante un despliegue contrafáctico (counterfactual deployment), donde la dirección de la cuenta se calcula y se financia antes de que se despliegue el contrato. “Contrafáctico” significa que la dirección se trata como si ya existiera antes de que lo haga realmente on-chain. Usando una fórmula determinista basada en el salt, el class hash y el calldata del constructor, puedes calcular la dirección por adelantado, financiarla y luego desplegar el contrato en esa dirección exacta.
El proceso:
- Calcular cuál será la dirección de la cuenta antes del despliegue
- Financiar la dirección precalculada con tokens (vía faucet para testnet)
- Enviar una transacción
DEPLOY_ACCOUNT
Cuando el secuenciador recibe esta transacción:
- Verifica la firma del despliegue
- Ejecuta el constructor con los argumentos proporcionados
- Cobra las tarifas de la dirección de la cuenta recién desplegada (¡completando la solución al problema de arranque!)
Este método es ideal para la creación de cuentas por primera vez, ya que la cuenta paga por su propio despliegue utilizando el saldo pre-financiado, eliminando la necesidad de un contrato de cuenta existente que patrocine el despliegue. Este es el método preferido para desplegar contratos de cuenta cuando deseas que la cuenta sea prístina, sin vinculación a ninguna otra cuenta, como las cuentas de Ready o Braavos.
La siguiente tabla resume las diferencias clave entre los dos métodos de despliegue de contratos:
| Aspecto | Transacción DEPLOY_ACCOUNT |
Método UDC |
|---|---|---|
| Caso de uso | Despliegue de contratos de cuenta (independiente, no vinculada a ningún desplegador) | Despliegue de contratos regulares, o despliegue de contratos de cuenta (vinculada al desplegador) |
| Pagador de tarifas (Fee payer) | La cuenta que se está desplegando (pre-financiada) | Tu cuenta existente |
| Tipo de transacción | DEPLOY_ACCOUNT |
INVOKE |
| Acceso a deploy_syscall | Mecanismo a nivel de protocolo | A través del wrapper UDC |
| Problema de arranque (Bootstrap problem) | Lo resuelve (no se necesita cuenta previa) | Requiere una cuenta existente |
| Tipo de dirección | Contrafáctica | Determinista |
| Cuenta resultante | Prístina, no vinculada a ninguna otra cuenta | Vinculada al desplegador on-chain |
Conclusión
En este artículo, hemos explorado cómo el modelo declare-deploy de Starknet separa el código del contrato del estado, permitiendo un despliegue eficiente a través de los tipos de transacciones DECLARE, INVOKE y DEPLOY_ACCOUNT. Hemos examinado cómo Sierra y CASM trabajan juntos durante la declaración, y cómo los dos métodos de despliegue (DEPLOY_ACCOUNT para cuentas que existen independientemente y el Universal Deployer Contract para contratos regulares) manejan diferentes casos de uso.
En el próximo artículo, explicaremos cómo desplegar el contrato ERC-20 de nuestra guía anterior usando tanto sncast como Starknet.js, y te mostraremos cómo interactuar con tus contratos desplegados.