Smart Contracts
Los smart contracts te permiten gestionar el estado del blockchain a través de código desplegado en el blockchain. Viven en el blockchain de forma permanente, se ejecutan en la ULVM — la máquina virtual WebAssembly de ULedger — y cada invocación es en sí misma una transaction en la cadena.
Los smart contracts se escriben en WebAssembly y se compilan en archivos .wat. Si aún no has escrito uno, comienza con la documentación de ULVM. Cubre los conceptos básicos sobre cómo escribir smart contracts.
Despliegue de un contrato
El despliegue es un POST multiparte. Envías el archivo .wat junto con los parámetros de la transaction. El wallet con el que realizas el despliegue debe estar ya registrado en el blockchain.
import fs from 'fs';
import FormData from 'form-data';
const form = new FormData();
form.append('walletAddress', 'your-wallet-address');
form.append('blockchainId', '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2');
form.append('from', 'your-wallet-address');
form.append('payload', 'erc20-token-contract');
form.append('file', fs.createReadStream('./contracts/erc20.wat'));
const res = await fetch('https://your-tms-url/api/v1/smartcontracts/deploy', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
...form.getHeaders(),
},
body: form,
});
const deployResult = await res.json();
console.log('Deploy transaction ID:', deployResult.transactionId);
El despliegue crea una transaction en la cadena. Esa dirección de destino es también la dirección de tu contrato. Guárdala para invocar el contrato más adelante. Deberías poder ver la transaction en un nuevo bloque inmediatamente después del despliegue.
Invocación de un contrato
Llama a cualquier función exportada por nombre junto con sus argumentos. El contrato se ejecuta en la ULVM y cada invocación es una transaction en la cadena. Puedes verificar el estado de la transaction, el bloque en el que se registró y el estado del contrato tras la invocación.
const res = await fetch('https://your-tms-url/api/v1/smartcontracts/invoke/payload', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
walletAddress: 'your-wallet-address',
blockchainId: '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2',
contractAddress: deployResult.transactionId,
functionName: 'mint',
arguments: [
{ type: 'string', value: 'your-wallet-address' },
{ type: 'int32', value: '1000' },
],
gasLimit: 0,
}),
});
const invokeResult = await res.json();
console.log('Invocation result:', invokeResult);
Tipos de argumento compatibles: int32, int64, float32, float64, bool, string, bytes.
Verificación del estado del contrato
Los smart contracts pueden almacenar estado. Después de invocar, comprueba qué cambió a través del TMS:
const res = await fetch(
`https://your-tms-url/api/v1/smartcontracts/${blockchainId}/${contractAddress}/state`,
{
headers: {
'X-AccessKey': 'your-entity-access-key',
'Accept': 'application/json',
},
}
);
const state = await res.json();
console.log('Estado del contrato:', state);
// p. ej. { "contract:version": "1", "counter": "42" }
Para rastrear una invocación específica y ver exactamente qué instrucciones se ejecutaron y cuánto gas consumieron:
const res = await fetch(
`https://your-tms-url/api/v1/smartcontracts/${blockchainId}/${contractAddress}/trace/${transactionId}`,
{
headers: {
'X-AccessKey': 'your-entity-access-key',
'Accept': 'application/json',
},
}
);
const trace = await res.json();
console.log('Gas utilizado:', trace.gasUsed);
console.log('Salida:', trace.output);
Actualización de un contrato
La dirección del contrato permanece igual entre actualizaciones — solo cambia el código. Cada versión anterior queda preservada en la cadena.
import fs from 'fs';
import FormData from 'form-data';
const form = new FormData();
form.append('walletAddress', 'your-wallet-address');
form.append('blockchainId', '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2');
form.append('contractAddress', deployResult.transactionId);
form.append('upgradeReason', 'added burn function');
form.append('file', fs.createReadStream('./contracts/erc20-v2.wat'));
const upgradeRes = await fetch('https://your-tms-url/api/v1/smartcontracts/upgrade', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
...form.getHeaders(),
},
body: form,
});
console.log('Upgrade status:', upgradeRes.status);
Tras la actualización, invoca las nuevas funciones de la misma manera — misma dirección de contrato, nuevos nombres de función disponibles:
const res = await fetch('https://your-tms-url/api/v1/smartcontracts/invoke/payload', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
walletAddress: 'your-wallet-address',
blockchainId: '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2',
contractAddress: deployResult.transactionId,
functionName: 'burn',
arguments: [
{ type: 'string', value: 'your-wallet-address' },
{ type: 'int32', value: '100' },
],
gasLimit: 0,
}),
});
Despliega una vez, invoca las veces que necesites, actualiza cuando la lógica cambie. El contrato siempre estará ahí, siempre en la cadena, siempre rastreable.
Revertir un contrato
Si una actualización introduce un error o rompe algo en producción, puedes revertir a cualquier versión anterior. Todas las versiones se conservan en la cadena — una reversión simplemente vuelve a activar el bytecode anterior.
const res = await fetch('https://your-tms-url/api/v1/smartcontracts/rollback', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
walletAddress: 'your-wallet-address',
blockchainId: '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2',
contractAddress: 'your-contract-address',
targetVersion: 1,
rollbackReason: 'Error crítico encontrado en V2',
}),
});
const result = await res.json();
console.log('Transaction de rollback:', result.transactionId);
La dirección del contrato no cambia. Tras la reversión, las invocaciones ejecutan nuevamente el bytecode de V1. Puedes actualizar de nuevo en cualquier momento — el historial de versiones sigue creciendo hacia adelante en la cadena.