Saltar al contenido principal

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 plano
  • POST /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 un 400

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 veQué significa
200, status: SUBMITTED, output vacíoEntregada a un node. Resultado desconocido.
La consulta devuelve la transaction, output vacíoPendiente. Volver a consultar.
La consulta devuelve la transaction, output: SUCCESSConfirmada.
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 esperaNo 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 found y la consulta del TMS puede responder un error como 500. 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.