Pragma es un protocolo de oráculo construido para Starknet que lleva datos de precios fuera de la cadena (off-chain) a la cadena (on-chain). Proporciona feeds de precios y feeds computacionales (como curvas de rendimiento y datos de volatilidad).
En este artículo, aprenderás cómo integrar el oráculo de feeds de precios de Pragma en tu contrato de Cairo.
Una descripción general de cómo funciona Pragma
Antes de integrar los feeds de precios, expliquemos cómo funciona Pragma a alto nivel.
Pragma recopila datos de precios de múltiples fuentes independientes y los agrega en la cadena para proporcionar feeds de precios confiables y resistentes a la manipulación. La diferencia clave es que Pragma realiza toda la agregación directamente en Starknet, haciendo que todo el proceso sea transparente y verificable.
Pragma agrega datos a través de un sistema de dos niveles:
-
Las Sources (fuentes) son los exchanges, proveedores de datos y mercados reales donde se originan los precios, como Binance, Coinbase, OKX, Ekubo, Chainlink, etc.
-
Los Publishers (publicadores) son entidades incluidas en la lista blanca por el registro controlado por administradores de Pragma. Monitorean los precios de varias fuentes y envían estos datos al contrato del oráculo. Puedes encontrar la lista completa de publicadores en el contrato de registro de publicadores de Pragma. Al momento de escribir esto, los publishers actuales incluyen los siguientes:

Cada publicador monitorea de forma independiente múltiples fuentes y envía datos de precios al contrato del oráculo. El oráculo de Pragma luego agrega los envíos de todos los publicadores. Si un publicador reporta un precio atípico, la agregación de Pragma lo excluye.
Por ejemplo, aquí están las fuentes que monitorea el publicador ARGENT:

Entonces, cuando ARGENT (un publicador) quiere reportar un precio, monitorea las múltiples fuentes resaltadas en el recuadro rojo de arriba y envía los datos de precios observados al contrato del oráculo de Pragma.
Cómo fluyen los datos de precios desde las fuentes hasta tu contrato
-
Recopilación de datos
Los publicadores monitorean sus fuentes asignadas (exchanges, oráculos, proveedores de datos) y recopilan las observaciones de precios actuales.
-
Envío en la cadena (On-Chain Submission)
Los publicadores añaden marcas de tiempo a los datos recopilados y luego los envían directamente al contrato del oráculo de Pragma en Starknet. No existe una capa de agregación centralizada fuera de la cadena; cada publicador envía sus datos de forma independiente a la cadena, donde pueden ser verificados públicamente. Por ejemplo, si 10 publicadores envían el precio de ETH, Pragma tiene 10 puntos de datos en la cadena para trabajar.
-
Cálculo del precio
Pragma utiliza un proceso de agregación de dos pasos para calcular el precio final:
- Agregación por fuente: Para cada fuente individual (como Binance), Pragma calcula el precio mediano de todos los publicadores que reportaron datos para esa fuente. Por ejemplo, si tres publicadores envían cada uno el precio de ETH/USD que observaron en Binance, Pragma toma la mediana de esos tres valores para obtener un único precio de consenso para Binance.
- Agregación entre fuentes: Después de obtener precios de consenso de cada fuente (Binance, Coinbase, OKX, etc.), tu contrato inteligente puede elegir cómo combinar estos precios de origen en un valor final. Puedes usar la mediana, la media, el precio promedio ponderado en el tiempo (TWAP) u otros métodos de agregación según tus necesidades.
-
Consulta desde el contrato inteligente
Tu contrato consulta el oráculo de Pragma por el precio actual de ETH/USD y recibe el precio agregado con una marca de tiempo.
Aquí tienes un resumen de todo este flujo:

Construyendo un Vault Simple con Condición de Precio
Construyamos un contrato de bóveda (vault) básico donde los usuarios puedan depositar tokens STRK, pero solo puedan retirar cuando el precio de STRK alcance cierto umbral.
Aquí tienes un flujo visual de cómo funcionan los depósitos y retiros en este vault:

Configurando el proyecto
Crea un nuevo proyecto en Scarb y navega hasta el directorio:
scarb new simple_vault
cd simple_vault
Abre Scarb.toml y añade la biblioteca del oráculo de Pragma como una dependencia en la sección [dependencies]:
[dependencies]
pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" }

Ahora ejecuta scarb build para descargar la dependencia y verificar que el proyecto esté configurado correctamente. Esto puede tardar un minuto en la primera ejecución mientras descarga las dependencias. Una vez que la compilación sea exitosa, estamos listos para comenzar a escribir el contrato del vault.
Definiendo la interfaz del Vault
Primero, definimos la interfaz para nuestro contrato del vault. Esto especifica todas las funciones que nuestro vault expondrá:
deposit(amount): Bloquea tokens STRK en el vaultwithdraw(): Recupera tokens STRK (solo si se cumple la condición del precio)get_balance(user): Comprueba cuánto STRK ha depositado un usuario específicoget_strk_price(): Consulta el precio actual de STRK/USD desde el oráculo de Pragma
use starknet::ContractAddress;
#[starknet::interface]
trait ISimpleVault<TContractState> {
fn deposit(ref self: TContractState, amount: u256);
fn withdraw(ref self: TContractState);
fn get_balance(self: @TContractState, user: ContractAddress) -> u256;
fn get_strk_price(self: @TContractState) -> u128;
}
Definiendo la Interfaz ERC20
Dado que el vault necesita transferir tokens STRK durante los depósitos y retiros, necesitamos definir una interfaz ERC-20 con transfer_from para mover tokens de los usuarios al vault y transfer para devolver tokens del vault a los usuarios:
#[starknet::interface]
trait IERC20<TContractState> {
fn transfer_from(
ref self: TContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: u256
) -> bool;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
}
Importaciones del Contrato
Comienza declarando el módulo del contrato e importando los tipos, traits y funciones requeridos:
#[starknet::contract]
mod SimpleVault {
use starknet::{ContractAddress, get_caller_address};
use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait};
use pragma_lib::types::{DataType, PragmaPricesResponse};
use starknet::storage::{Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess};
use super::{IERC20Dispatcher, IERC20DispatcherTrait};
Las importaciones clave a tener en cuenta desde pragma_lib son:
IPragmaABIDispatcheryIPragmaABIDispatcherTrait: despachador del contrato (dispatcher) y trait para llamar funciones en el contrato del oráculo de PragmaDataType: especifica el tipo de dato que queremos (precio al contado o spot para el precio de mercado actual)PragmaPricesResponse: contiene la información del precio devuelta por Pragma
Definiendo Constantes del Contrato
Definimos dos constantes clave:
// Constants
const STRK_USD_PAIR_ID: felt252 = 6004514686061859652; // STRK/USD pair ID from Pragma
const PRICE_THRESHOLD: u128 = 16000000; // $0.16 in 8 decimals (0.16 * 10^8)
STRK_USD_PAIR_ID identifica qué feed de precios de Pragma consultar. En este caso, estamos consultando el feed de STRK/USD. El ID del par 6004514686061859652 corresponde a la codificación UTF-8 de la cadena del ticker en mayúsculas 'STRK/USD'. Esto significa que la constante del ID del par se puede escribir de forma más legible como:
const STRK_USD_PAIR_ID: felt252 = 'STRK/USD';
El mismo patrón se aplica a otros pares de precios. Cada ID de par es simplemente la codificación UTF-8 de su cadena de ticker en mayúsculas. Por ejemplo, 'ETH/USD', puede escribirse como la cadena 'ETH/USD' o su valor numérico felt252 19514442401534788, ambos son equivalentes en Cairo. Otros IDs de pares de activos están disponibles en la documentación de feeds de precios de Pragma. Cada par de activos tiene su propio ID único y precisión decimal; asegúrate de revisar la columna de Decimales (Decimals) al seleccionar un par diferente. Se utiliza el mismo ID de par tanto para la testnet Sepolia como para la mainnet.

PRICE_THRESHOLD establece el precio mínimo de STRK ($0.16) requerido para los retiros. Como Pragma devuelve los precios de STRK con una precisión de 8 decimales, representamos $0.16 como 16,000,000 (0.16 × 10^8).
Definiendo el Almacenamiento (Storage) del Contrato
El struct de almacenamiento define qué datos almacena nuestro contrato:
#[storage]
struct Storage {
pragma_oracle: ContractAddress, //pragma_oracle contract address
strk_token: ContractAddress, // strk token contract address
balances: Map<ContractAddress, u256>, // user balances
}
Lo que estamos almacenando:
pragma_oracle: dirección del contrato del oráculo de Pragma en Starknet, usado para obtener los precios de los activos en tiempo realstrk_token: dirección del contrato del token STRK, usado para gestionar depósitos y retirosbalances: mapeo de las direcciones de los usuarios a las cantidades de STRK que han depositado
Inicializando el Contrato
Con el almacenamiento definido, inicializamos el contrato pasando las direcciones del oráculo de Pragma y del token STRK al constructor durante el despliegue:
#[constructor]
fn constructor(
ref self: ContractState,
pragma_oracle: ContractAddress,
strk_token: ContractAddress
) {
self.pragma_oracle.write(pragma_oracle);
self.strk_token.write(strk_token);
}
Estas direcciones permiten que el contrato del vault consulte precios desde el oráculo de Pragma y gestione las transferencias del token STRK, como veremos cuando discutamos las funciones de depósito y retiro.
Definiendo Eventos del Contrato
A continuación, definimos eventos para rastrear los depósitos (Deposit) y retiros (Withdrawal) en el vault:
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Deposit: Deposit,
Withdrawal: Withdrawal,
}
#[derive(Drop, starknet::Event)]
struct Deposit {
user: ContractAddress,
amount: u256,
}
#[derive(Drop, starknet::Event)]
struct Withdrawal {
user: ContractAddress,
amount: u256,
}
Cada evento registra la dirección del usuario y cuánto STRK se depositó o retiró.
Implementando las Funciones de la Interfaz
Ahora, implementemos las funciones de la interfaz del vault que definimos anteriormente:
Función deposit
Cuando un usuario llama a deposit(amount), la función verifica que la cantidad sea mayor a cero, luego usa transfer_from para mover tokens STRK desde la cuenta del usuario al vault. Esto requiere que el usuario haya aprobado previamente el contrato del vault. Después de que la transferencia sea exitosa, el saldo del usuario se actualiza en el almacenamiento y se emite un evento Deposit.
fn deposit(ref self: ContractState, amount: u256) {
let caller = get_caller_address();
assert(amount > 0, 'Amount must be greater than 0');
// Transfer STRK from user to this contract
let strk = IERC20Dispatcher { contract_address: self.strk_token.read() };
let success = strk.transfer_from(caller, starknet::get_contract_address(), amount);
assert(success, 'Transfer failed');
// Update user balance
let current_balance = self.balances.entry(caller).read();
self.balances.entry(caller).write(current_balance + amount);
// Emit deposit event
self.emit(Deposit { user: caller, amount });
}
Función withdraw
La función de retiro asegura que el usuario tenga un saldo distinto de cero, luego obtiene el precio actual de STRK desde Pragma. Si el precio alcanza o supera los $0.16, restablece el saldo del usuario (siguiendo el patrón checks-effects-interactions), transfiere sus tokens STRK de vuelta a su cuenta y emite un evento Withdrawal.
fn withdraw(ref self: ContractState) {
let caller = get_caller_address();
let balance = self.balances.entry(caller).read();
assert(balance > 0, 'No balance to withdraw');
// Get current STRK price
let current_price = self.get_strk_price();
// Check if price threshold is met
assert(current_price >= PRICE_THRESHOLD, 'Price threshold not met');
// Reset balance before transfer (CEI pattern)
self.balances.entry(caller).write(0);
// Transfer STRK back to user
let strk = IERC20Dispatcher { contract_address: self.strk_token.read() };
let success = strk.transfer(caller, balance);
assert(success, 'Transfer failed');
// Emit withdrawal event
self.emit(Withdrawal { user: caller, amount: balance });
}
Función get_balance
Devuelve cuánto STRK ha depositado un usuario específico en el vault:
fn get_balance(self: @ContractState, user: ContractAddress) -> u256 {
self.balances.entry(user).read()
}
Función get_strk_price
Obtiene el precio actual de STRK/USD desde el oráculo de Pragma:
fn get_strk_price(self: @ContractState) -> u128 {
let oracle = IPragmaABIDispatcher { contract_address: self.pragma_oracle.read() };
let response: PragmaPricesResponse = oracle.get_data_median(DataType::SpotEntry(STRK_USD_PAIR_ID));
response.price
}
Primero, crea un despachador del contrato para interactuar con el contrato del oráculo de Pragma utilizando la dirección almacenada:
let oracle = IPragmaABIDispatcher { contract_address: self.pragma_oracle.read() };
Luego llama a get_data_median(), pasando DataType::SpotEntry(STRK_USD_PAIR_ID) para solicitar el precio al contado actual para STRK/USD. El método agrega datos de múltiples publicadores y devuelve la mediana, lo que evita que cualquier fuente individual manipule el resultado:
let response: PragmaPricesResponse = oracle.get_data_median(DataType::SpotEntry(STRK_USD_PAIR_ID));
get_data_median() devuelve un struct PragmaPricesResponse con los siguientes campos:
struct PragmaPricesResponse {
price: u128, // The aggregated price
decimals: u32, // Number of decimal places
last_updated_timestamp: u64, // When the price was last updated
num_sources_aggregated: u32, // Number of sources used in aggregation
expiration_timestamp: Option<u64>, // Optional expiration time
}
get_strk_price devuelve solo el campo price de este struct:
response.price
El precio se devuelve como un u128 con precisión de 8 decimales. Por ejemplo, si STRK se cotiza a $0.15, esto devuelve 15000000 (ya que Pragma usa 8 lugares decimales: 0.15 × 10^8).
Al usar
get_data_median(), estamos aceptando la agregación inicial predeterminada de Pragma (la mediana de todos los precios enviados por los publicadores). Dado que estamos consultando un solo par, la mediana de todos los publicadores para STRK/USD es nuestro precio final.
Copia el contrato del vault completo en lib.cairo y compílalo usando scarb build:
use starknet::ContractAddress;
#[starknet::interface]
trait IERC20<TContractState> {
fn transfer_from(
ref self: TContractState,
sender: ContractAddress,
recipient: ContractAddress,
amount: u256
) -> bool;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
}
#[starknet::interface]
trait ISimpleVault<TContractState> {
fn deposit(ref self: TContractState, amount: u256);
fn withdraw(ref self: TContractState);
fn get_balance(self: @TContractState, user: ContractAddress) -> u256;
fn get_strk_price(self: @TContractState) -> u128;
}
#[starknet::contract]
mod SimpleVault {
use starknet::{ContractAddress, get_caller_address};
use pragma_lib::abi::{IPragmaABIDispatcher, IPragmaABIDispatcherTrait};
use pragma_lib::types::{DataType, PragmaPricesResponse};
use starknet::storage::{Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess};
// Add this import for the ERC20 dispatcher
use super::{IERC20Dispatcher, IERC20DispatcherTrait};
// Constants
const STRK_USD_PAIR_ID: felt252 = 6004514686061859652; // STRK/USD pair ID
const PRICE_THRESHOLD: u128 = 16000000; // $0.16 in 8 decimals(0.16 * 10^8)
#[storage]
struct Storage {
pragma_oracle: ContractAddress,
strk_token: ContractAddress,
balances: Map<ContractAddress, u256>,
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Deposit: Deposit,
Withdrawal: Withdrawal,
}
#[derive(Drop, starknet::Event)]
struct Deposit {
user: ContractAddress,
amount: u256,
}
#[derive(Drop, starknet::Event)]
struct Withdrawal {
user: ContractAddress,
amount: u256,
}
#[constructor]
fn constructor(
ref self: ContractState,
pragma_oracle: ContractAddress,
strk_token: ContractAddress
) {
self.pragma_oracle.write(pragma_oracle);
self.strk_token.write(strk_token);
}
#[abi(embed_v0)]
impl SimpleVaultImpl of super::ISimpleVault<ContractState> {
fn deposit(ref self: ContractState, amount: u256) {
let caller = get_caller_address();
assert(amount > 0, 'Amount must be greater than 0');
// Transfer STRK from user to this contract
let strk = IERC20Dispatcher { contract_address: self.strk_token.read() };
let success = strk.transfer_from(caller, starknet::get_contract_address(), amount);
assert(success, 'Transfer failed');
// Update user balance
let current_balance = self.balances.entry(caller).read();
self.balances.entry(caller).write(current_balance + amount);
// Emit deposit event
self.emit(Deposit { user: caller, amount });
}
fn withdraw(ref self: ContractState) {
let caller = get_caller_address();
let balance = self.balances.entry(caller).read();
assert(balance > 0, 'No balance to withdraw');
// Get current STRK price
let current_price = self.get_strk_price();
// Check if price threshold is met
assert(current_price >= PRICE_THRESHOLD, 'Price threshold not met');
// Reset balance before transfer (CEI pattern)
self.balances.entry(caller).write(0);
// Transfer STRK back to user
let strk = IERC20Dispatcher { contract_address: self.strk_token.read() };
let success = strk.transfer(caller, balance);
assert(success, 'Transfer failed');
// Emit withdrawal event
self.emit(Withdrawal { user: caller, amount: balance });
}
fn get_balance(self: @ContractState, user: ContractAddress) -> u256 {
self.balances.entry(user).read()
}
fn get_strk_price(self: @ContractState) -> u128 {
let oracle = IPragmaABIDispatcher { contract_address: self.pragma_oracle.read() };
let response: PragmaPricesResponse = oracle.get_data_median(DataType::SpotEntry(STRK_USD_PAIR_ID));
response.price
}
}
}
Desplegando el Contrato del Vault
Primero, declara el contrato usando sncast:
sncast --account <YOUR_ACCOUNT_NAME> \
declare \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/<YOUR_API_KEY> \
--contract-name SimpleVault

Pragma proporciona las dos direcciones requeridas para el despliegue. Las pasaremos como parámetros al constructor:
- Pragma Oracle:
0x036031daa264c24520b11d93af622c848b2499b66b41d611bac95e13cfca131a - STRK Token:
0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d
Puedes encontrar las últimas direcciones de despliegue en el repositorio de GitHub de Pragma.
Despliega el contrato con los parámetros del constructor:
sncast \
--account new_account1 \
deploy \
--url https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_10/<YOUR_API_KEY> \
--class-hash <CLASS_HASH> \
--constructor-calldata "0x036031daa264c24520b11d93af622c848b2499b66b41d611bac95e13cfca131a" "0x04718f5a0Fc34cC1AF16A1cdee98fFB20C31f5cD61D6Ab07201858f4287c938D"
Copia el <CLASS_HASH> de la salida de declare y pégalo en el comando deploy. La terminal mostrará un recibo de la transacción que contiene la dirección del contrato del vault desplegado.

Guarda la dirección para los siguientes pasos.
Interactuando con el Vault
Con el vault desplegado, vamos a revisar cómo depositar y retirar tokens STRK utilizando Voyager.
Aprobando el Vault
Antes de depositar, se debe aprobar el vault para que pueda transferir tokens STRK. Navega a la pestaña “Write Contract” (Escribir Contrato) del contrato del token STRK en Voyager y llama a la función approve con la dirección del contrato del vault como spender (gastador) y la cantidad de STRK que pretendes depositar. Una vez hecho esto, ejecuta la transacción y espera la confirmación.

Haciendo un Depósito
Con la aprobación concedida, los tokens ya pueden ser depositados. En la pestaña “Write Contract” del contrato del vault, llama a la función deposit con la cantidad que aprobaste previamente. Después de ejecutar esta transacción, los tokens STRK quedarán bloqueados en el vault.

Verificando el Saldo
Para verificar que el depósito se realizó con éxito, navega a la pestaña “Read Contract” (Leer Contrato) y llama a get_balance con la dirección de la billetera. Debería devolver el saldo, confirmando el depósito de STRK.

Verificando el Precio Actual de STRK
Llama a get_strk_price en la pestaña “Read Contract” para verificar el precio actual de STRK desde Pragma. La función devuelve el precio como un número entero con 8 posiciones decimales. Para convertir esto a un valor en USD, divide el número devuelto por 100,000,000

Por ejemplo, aquí la función devuelve 14716561. Dividiendo por 100,000,000:
14716561 ÷ 100,000,000 = 0.14716561
Por lo tanto, el precio actual de STRK al momento de esta consulta es de $0.147.
Intentando un Retiro
Ahora intenta retirar el STRK depositado. En la pestaña “Write Contract”, llama a la función withdraw y ejecuta la transacción.
El resultado depende del precio actual. Si STRK está por debajo de $0.16, la transacción fallará con el mensaje de error “Price threshold not met.” (El umbral de precio no se cumplió). Si STRK ha alcanzado $0.16 o más, los tokens serán devueltos a la billetera.

Para probar un retiro exitoso de inmediato, el contrato puede ser declarado y desplegado nuevamente con un umbral más bajo como 10000000 (que representa $0.10), o uno puede esperar a que el precio de STRK suba naturalmente por encima de $0.16.
Conclusión
La integración de los feeds de precios de Pragma permite que protocolos de préstamos, mercados de predicción, NFTs dinámicos y otras aplicaciones accedan a datos de precios verificados en la cadena. En este momento, el vault simplemente bloquea y libera fondos en función del precio. No hay un mecanismo de rendimiento o recompensa más allá de la condición de precio. El contrato se puede ampliar con características que se adapten a casos de uso específicos: recompensas de staking para los depositantes, múltiples umbrales de precios para diferentes niveles de usuarios, o desbloqueos ponderados por el tiempo.
Los feeds computacionales de Pragma ofrecen más que precios al contado. Los feeds de volatilidad miden la turbulencia del mercado para protocolos ajustados al riesgo. TWAP proporciona precios promediados en el tiempo que son resistentes a la manipulación por ataques de préstamos flash (flashloans). Explorar estos feeds, probar diferentes pares de activos y combinar condiciones revela cómo los oráculos transforman los contratos para que respondan dinámicamente a las condiciones del mundo real.