Transacciones
Las transacciones son el material de construcción del blockchain. Cada payload que se envía es firmado por la billetera y almacenado en bloques que luego se comparten entre todos los nodes de la red, creando el ledger inmutable.
Registrar la billetera — la primera transaction
Antes de poder enviar cualquier transaction, la billetera debe estar registrada en el blockchain, lo cual es en sí misma una transaction.
Esto se hace una vez por billetera por blockchain.
const res = await fetch('https://your-tms-url/api/v1/transactions/register-wallet', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
walletAddress: 'your-wallet-address',
blockchain: '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2',
passphrase: 'my-secure-passphrase', // omit if wallet has no passphrase
}),
});
const result = await res.json();
console.log('Wallet registered:', result);
Después de esto, la billetera está activa en la cadena y lista para usarse.
Enviar una transaction
Con la billetera registrada, se puede escribir cualquier cosa en el blockchain. El TMS selecciona un node disponible de los nodes permitidos por la access key y reenvía la transaction, encargándose del enrutamiento y la cola.
const res = await fetch('https://your-tms-url/api/v1/transactions', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
blockchainId: '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2',
from: 'your-wallet-address',
to: 'your-wallet-address',
walletAddress: 'your-wallet-address',
passphrase: 'my-secure-passphrase',
payload: 'anything you want to store permanently',
payloadType: 'text',
metadata: {
source: 'my-app',
version: '1.0',
},
}),
});
const tx = await res.json();
console.log('Transaction ID:', tx.transactions.transactionId);
Si ya se sabe de antemano que el payload es datos estructurados, se recomienda usar los endpoints tipados, porque hacen la intención más clara y manejan la serialización correctamente:
POST /api/v1/transactions/text— para payloads de cadena de texto planoPOST /api/v1/transactions/json— solo para objetos JSON estructurados, p. ej.{ "event": "invoice_paid", "amount": 1500 }, mientras que los escalares y los arrays se rechazan con un400
Ambos aceptan los mismos campos que el endpoint genérico.
:::warning Enviar los enteros grandes como cadenas
Los números JSON se manejan como doubles IEEE-754, por lo que cualquier entero mayor que 2^53 − 1
(9007199254740991) se redondea al almacenarse. La escritura igualmente tiene éxito, se obtiene un 200
y una transaction confirmada, pero el valor en la chain no es el que se envió:
// sent
{ "orderId": 1234567890123456789 }
// stored
{ "orderId": 1234567890123456800 }
Esto afecta a los valores con más probabilidad de aparecer en un payload: IDs de 19 dígitos, timestamps en nanosegundos y montos de tokens en unidades base (un token entero con 18 decimales es 10^18).
Se deben enviar los enteros grandes como cadenas, { "orderId": "1234567890123456789" }, y se conservan
exactamente. Los valores iguales o menores que 2^53 − 1 no se ven afectados.
:::
Recuperar una transaction
Una vez enviada, se debe verificar que la transaction haya sido procesada:
const blockchainId = '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2';
const transactionId = 'your-transaction-id';
const res = await fetch(
`https://your-tms-url/api/v1/transactions/${blockchainId}/${transactionId}`,
{
headers: {
'X-AccessKey': 'your-entity-access-key',
'Accept': 'application/json',
},
}
);
const confirmed = await res.json();
console.log('Status:', confirmed.status);
console.log('Payload:', confirmed.payload);
Las transacciones en ULedger son increíblemente rápidas. Consultar las transacciones inmediatamente después de enviarlas permite observar con qué rapidez se confirman.
:::warning Confirmar siempre: "enviada" no significa "aceptada"
Un 200 de cualquier endpoint de escritura significa que el TMS entregó la transaction a un node. No
significa que la red la aceptó. La respuesta ya contiene un transactionId, un blockHeight y
status: "SUBMITTED", y ninguno confirma el resultado: la altura es donde estaba la chain cuando se
envió la transaction. Una transaction que la red luego rechaza (una billetera sin registrar, un permiso
faltante, un key type que no coincide con la chain, una regla de validación) se descarta. Nunca llega a un
bloque y no se envía ninguna notificación.
Una transaction está confirmada solo cuando la consulta la devuelve con un blockHeight y output es
SUCCESS. Lo que se recibe debe interpretarse así:
| Lo que se ve | Qué significa |
|---|---|
200, status: SUBMITTED, output vacío | Entregada a un node. Resultado desconocido. |
La consulta devuelve la transaction, output vacío | Pendiente. Volver a consultar. |
La consulta devuelve la transaction, output: SUCCESS | Confirmada. |
La consulta devuelve la transaction, output es un error (por ejemplo TRANSACTION_ERROR) | Se registró, pero falló. Leer el error. |
| La consulta no la devuelve una vez pasada la ventana de espera | No aceptada. Tratarla como fallida, no como pendiente. |
status no es el resultado. Sigue en ACCEPTED tanto si la transaction tuvo éxito como si falló. Se debe leer
output.
Se debe consultar con un límite de tiempo en lugar de asumir el éxito:
async function confirmTransaction(blockchainId: string, transactionId: string, timeoutMs = 60_000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(
`https://your-tms-url/api/v1/transactions/${blockchainId}/${transactionId}`,
{ headers: { 'X-AccessKey': 'your-entity-access-key', Accept: 'application/json' } },
);
if (res.ok) {
const tx = await res.json();
if (tx.blockHeight && tx.output === 'SUCCESS') return tx; // confirmed
if (tx.output && tx.output !== 'SUCCESS') { // committed, but failed
throw new Error(`transaction failed: ${tx.output}`);
}
}
await new Promise((r) => setTimeout(r, 2000));
}
throw new Error(`transaction ${transactionId} was not confirmed within ${timeoutMs / 1000}s`);
}
Dos cosas más a tener en cuenta:
- Una consulta que no devuelve nada no es "aún pendiente". Para una transaction que la red rechazó,
el node responde
404 transaction not foundy la consulta del TMS puede responder un error como500. Pasada la ventana de espera, se debe tratar como no aceptada. - Se debe consultar antes de reintentar. Un timeout de gateway (
504) al enviar no prueba que la transaction haya fallado, ya que pudo haberse aceptado. Reintentar sin consultar puede escribir el mismo dato dos veces.
Si se habla directamente con un node, GET {node}/blockchains/{blockchainId}/transactions/{transactionId}/status
devuelve un state de committed (con blockHeight), pending, rejected o unknown. Solo
committed es una confirmación. Se debe preguntar al node que recibió la transaction. Ese node guarda en su
propia memoria, por un tiempo limitado, el motivo de un rechazo, así que responde rejected con un
reason (por ejemplo rejected by unauthorized wallet, rejected due to unexisting wallet,
rejected due to disabled wallet), mientras que cualquier otro node responde unknown. La respuesta del
envío no trae el veredicto. A través del TMS no se elige el node, así que se debe usar la consulta de arriba.
:::
Bloques y el ledger
Las transacciones se empaquetan en bloques con hasta 200 por bloque. Una vez que un bloque se sella, se distribuye entre todos los nodes de la red. Ese es el registro inmutable formándose en tiempo real.
Se lee un bloque para ver todo lo que contiene:
const res = await fetch('https://your-tms-url/api/v1/nodes/blockchain-block', {
method: 'POST',
headers: {
'X-AccessKey': 'your-entity-access-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
nodeId: 'your-node-id',
blockchain: '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2',
blockHeight: 2301,
}),
});
const block = await res.json();
console.log('Transactions in block:', block.transactions.length);
block.transactions.forEach(tx => console.log(tx.transactionId));
Ese es el ledger. Las billeteras lo firman, las transacciones lo llenan, los bloques lo sellan, los nodes lo conservan.
Consultar el historial de transactions
El TMS lleva un registro de cada transaction que la entidad ha enviado. Se puede consultar para auditar actividad, depurar un flujo fallido o construir un dashboard:
const params = new URLSearchParams({
status: 'confirmed',
blockchain_id: '08c28f29a62819120958984b761ddf8ccb45951612731409873994958fd150a2',
page: '0',
pageSize: '20',
});
const res = await fetch(
`https://your-tms-url/api/v1/transactions/history?${params}`,
{
headers: {
'X-AccessKey': 'your-entity-access-key',
'Accept': 'application/json',
},
}
);
const history = await res.json();
console.log(`${history.total} transacciones encontradas`);
history.items.forEach(tx => console.log(tx.transactionId, tx.status));
Filtros disponibles: status, flow_type, blockchain_id, from, to, page, pageSize. Todos son opcionales, así que se pueden omitir para devolver todo.
Si se tiene un ID de registro interno específico, se recupera directamente en GET /api/v1/transactions/history/{id}.
A continuación
Ahora podemos explorar los smart contracts. Continuar en Smart Contracts para ver cómo desplegar e interactuar con smart contracts en ULedger.