Saltar al contenido principal

Enviar y recibir pagos

La mayor parte del tiempo, estarás enviando dinero a alguien más que tiene su propia cuenta. Sin embargo, para este tutorial, necesitarás una segunda cuenta para realizar transacciones. Así que antes de continuar, sigue los pasos descritos en Crear una Cuenta para hacer dos cuentas: una para enviar y una para recibir.

Acerca de Operaciones y Transacciones

Las acciones que hacen cosas en Stellar — como enviar pagos o hacer ofertas de compra o venta — se llaman operaciones. Para enviar una operación a la red, la agrupas en una transacción, que es un grupo de entre 1 y 100 operaciones acompañadas de información adicional, como qué cuenta está realizando la transacción y una firma criptográfica para verificar que la transacción es auténtica.

Las transacciones son atómicas, lo que significa que si alguna operación en una transacción falla, todas fallan. Supongamos que tienes 100 lumens y realizas dos operaciones de pago de 60 lumens cada una. Si realizas dos transacciones (cada una con una operación), la primera tendrá éxito y la segunda fallará porque no tienes suficientes lumens. Te quedará con 40 lumens. Sin embargo, si agrupas los dos pagos en una única transacción, ambos fallarán y te quedarás con los 100 lumens completos todavía en tu cuenta.

Cada transacción también incurre en una pequeña tarifa. Al igual que el saldo mínimo en las cuentas, esta tarifa disuade el spam y evita que las personas sobrecarguen el sistema. Esta tarifa base es muy baja — 100 stroops por operación donde un stroop equivale a 1 * 10 ^-7 XLM — y se cobra por cada operación en una transacción. Una transacción con dos operaciones, por ejemplo, costaría 200 stroops.

información

En los siguientes ejemplos de código, se omite la verificación de errores adecuada por brevedad. Sin embargo, debes siempre validar tus resultados, ya que hay muchas formas en que las solicitudes pueden fallar. Debes consultar la guía sobre Manejo de Errores para consejos sobre estrategias de gestión de errores.

Enviar un Pago

Stellar almacena y comunica datos de transacciones en un formato binario llamado XDR, que está optimizado para el rendimiento de la red pero es ilegible para el ojo humano. Afortunadamente, Horizon, la API de Stellar, y los SDK de Stellar convierten los XDR en formatos más amigables. Así es como podrías enviar 10 lumens a una cuenta:

var StellarSdk = require("stellar-sdk");
var server = new StellarSdk.Horizon.Server(
"https://horizon-testnet.stellar.org",
);
var sourceKeys = StellarSdk.Keypair.fromSecret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4",
);
var destinationId = "GA2C5RFPE6GCKMY3US5PAB6UZLKIGSPIUKSLRB6Q723BM2OARMDUYEJ5";
// Transaction will hold a built transaction we can resubmit if the result is unknown.
var transaction;

// First, check to make sure that the destination account exists.
// You could skip this, but if the account does not exist, you will be charged
// the transaction fee when the transaction fails.
server
.loadAccount(destinationId)
// If the account is not found, surface a nicer error message for logging.
.catch(function (error) {
if (error instanceof StellarSdk.NotFoundError) {
throw new Error("The destination account does not exist!");
} else return error;
})
// If there was no error, load up-to-date information on your account.
.then(function () {
return server.loadAccount(sourceKeys.publicKey());
})
.then(function (sourceAccount) {
// Start building the transaction.
transaction = new StellarSdk.TransactionBuilder(sourceAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.payment({
destination: destinationId,
// Because Stellar allows transaction in many currencies, you must
// specify the asset type. The special "native" asset represents Lumens.
asset: StellarSdk.Asset.native(),
amount: "10",
}),
)
// A memo allows you to add your own metadata to a transaction. It's
// optional and does not affect how Stellar treats the transaction.
.addMemo(StellarSdk.Memo.text("Test Transaction"))
// Wait a maximum of three minutes for the transaction
.setTimeout(180)
.build();
// Sign the transaction to prove you are actually the person sending it.
transaction.sign(sourceKeys);
// And finally, send it off to Stellar!
return server.submitTransaction(transaction);
})
.then(function (result) {
console.log("Success! Results:", result);
})
.catch(function (error) {
console.error("Something went wrong!", error);
// If the result is unknown (no response body, timeout etc.) we simply resubmit
// already built transaction:
// server.submitTransaction(transaction);
});

¿Qué exactamente sucedió allí? Desglosemoslo.

  1. Confirma que la ID de cuenta (también conocida como la clave pública) a la que envías realmente existe cargando los datos de la cuenta asociada desde la red Stellar. Está bien omitir este paso, pero te da la oportunidad de evitar hacer una transacción que inevitablemente fallará.
server.loadAccount(destinationId).then(function (account) {
/* validate the account */
});
  1. Carga los datos de la cuenta desde la que estás enviando. Una cuenta solo puede realizar una transacción a la vez y tiene algo llamado un número de secuencia, que ayuda a Stellar a verificar el orden de las transacciones. El número de secuencia de una transacción debe coincidir con el número de secuencia de la cuenta, así que necesitas obtener el número de secuencia actual de la cuenta de la red.
.then(function() {
return server.loadAccount(sourceKeys.publicKey());
})

El SDK incrementará automáticamente el número de secuencia de la cuenta cuando construyas una transacción, así que no necesitarás recuperar esta información nuevamente si deseas realizar una segunda transacción.

  1. Empieza a construir una transacción. Esto requiere un objeto de cuenta, no solo una ID de cuenta, porque incrementará el número de secuencia de la cuenta.
var transaction = new StellarSdk.TransactionBuilder(sourceAccount);
  1. Agrega la operación de pago a la cuenta. Ten en cuenta que necesitas especificar el tipo de activo que estás enviando: la moneda de la red Stellar es el lumen, pero puedes enviar cualquier activo emitido en la red. Cubriré el envío de activos no lumen abajo. Por ahora, sin embargo, nos mantendremos en lumens, que se llaman activos “nativos” en el SDK:
.addOperation(StellarSdk.Operation.payment({
destination: destinationId,
asset: StellarSdk.Asset.native(),
amount: "10"
}))

También debes notar que la cantidad es una cadena en lugar de un número. Al trabajar con fracciones extremadamente pequeñas o grandes valores, la matemática de punto flotante puede introducir pequeñas inexactitudes. Dado que no todos los sistemas tienen una forma nativa de representar con precisión decimales extremadamente pequeños o grandes, Stellar utiliza cadenas como una forma confiable de representar la cantidad exacta en cualquier sistema.

  1. Opcionalmente, puedes agregar tus propios metadatos, llamados memo, a una transacción. Stellar no hace nada con estos datos, pero puedes usarlos para cualquier propósito que desees. Muchos exchanges requieren memos para transacciones entrantes porque utilizan una sola cuenta Stellar para todos sus usuarios y dependen del memo para diferenciar entre cuentas internas de usuarios.
.addMemo(StellarSdk.Memo.text('Test Transaction'))
  1. Ahora que la transacción tiene todos los datos que necesita, debes firmarla criptográficamente utilizando tu clave secreta. Esto demuestra que los datos realmente provinieron de ti y no de alguien que te imita.
transaction.sign(sourceKeys);
  1. Y finalmente, ¡envíala a la red Stellar!
server.submitTransaction(transaction);

En este ejemplo, estamos enviando la transacción a la instancia pública del testnet de Horizon, la API de Stellar mantenida por la SDF. Al enviar transacciones a un servidor Horizon — que es lo que la mayoría de la gente hace — es posible que no recibas una respuesta del servidor debido a un error, condiciones de red, etc. En tal situación, es imposible determinar el estado de tu transacción. Por eso deberías siempre guardar una transacción construida (o una transacción codificada en formato XDR) en una variable o base de datos y reenviarla si no conoces su estado. Si la transacción ya se ha aplicado con éxito al ledger, Horizon simplemente devolverá el resultado guardado y no intentará enviar la transacción nuevamente. Solo en casos donde el estado de una transacción es desconocido (y por lo tanto tendrá una oportunidad de ser incluida en un ledger) se producirá un reenvío a la red.

Recibir un Pago

En realidad, no necesitas hacer nada para recibir pagos en una cuenta Stellar: si un pagador realiza una transacción exitosa para enviarte activos, esos activos se agregarán automáticamente a tu cuenta.

Sin embargo, es posible que desees estar atento a los pagos entrantes. Un programa simple que vigila la red en busca de pagos e imprime cada uno podría verse así:

var StellarSdk = require("stellar-sdk");

var server = new StellarSdk.Horizon.Server(
"https://horizon-testnet.stellar.org",
);
var accountId = "GC2BKLYOOYPDEFJKLKY6FNNRQMGFLVHJKQRGNSSRRGSMPGF32LHCQVGF";

// Create an API call to query payments involving the account.
var payments = server.payments().forAccount(accountId);

// If some payments have already been handled, start the results from the
// last seen payment. (See below in `handlePayment` where it gets saved.)
var lastToken = loadLastPagingToken();
if (lastToken) {
payments.cursor(lastToken);
}

// `stream` will send each recorded payment, one by one, then keep the
// connection open and continue to send you new payments as they occur.
payments.stream({
onmessage: function (payment) {
// Record the paging token so we can start from here next time.
savePagingToken(payment.paging_token);

// The payments stream includes both sent and received payments. We only
// want to process received payments here.
if (payment.to !== accountId) {
return;
}

// In Stellar’s API, Lumens are referred to as the “native” type. Other
// asset types have more detailed information.
var asset;
if (payment.asset_type === "native") {
asset = "lumens";
} else {
asset = payment.asset_code + ":" + payment.asset_issuer;
}

console.log(payment.amount + " " + asset + " from " + payment.from);
},

onerror: function (error) {
console.error("Error in payment stream");
},
});

function savePagingToken(token) {
// In most cases, you should save this to a local database or file so that
// you can load it next time you stream new payments.
}

function loadLastPagingToken() {
// Get the last paging token from a local database or file
}

Hay dos partes principales en este programa. Primero, creas una consulta para pagos que involucran una cuenta dada. Como la mayoría de las consultas en Stellar, esto podría devolver un gran número de elementos, así que la API devuelve tokens de paginación, que puedes usar más tarde para comenzar tu consulta desde el mismo punto donde la dejaste anteriormente. En el ejemplo anterior, las funciones para guardar y cargar tokens de paginación se dejaron en blanco, pero en una aplicación real, querrías guardar los tokens de paginación en un archivo o base de datos para poder continuar donde te quedaste en caso de que el programa se bloquee o el usuario lo cierre.

var payments = server.payments().forAccount(accountId);
var lastToken = loadLastPagingToken();
if (lastToken) {
payments.cursor(lastToken);
}

En segundo lugar, los resultados de la consulta se transmiten. Esta es la forma más fácil de vigilar los pagos u otras transacciones. Cada pago existente se envía a través de la transmisión, uno por uno. Una vez que se han enviado todos los pagos existentes, la transmisión se mantiene abierta y se envían nuevos pagos a medida que se realizan.

Pruébalo: Ejecuta este programa y luego, en otra ventana, crea y envía un pago. Deberías ver que este programa registra el pago.

payments.stream({
onmessage: function (payment) {
// handle a payment
},
});

También puedes solicitar pagos en grupos o páginas. Una vez que hayas procesado cada página de pagos, necesitarás solicitar la siguiente hasta que no quede ninguna.

payments.call().then(function handlePage(paymentsPage) {
paymentsPage.records.forEach(function (payment) {
// handle a payment
});
return paymentsPage.next().then(handlePage);
});

Transaccionar en Otras Monedas

Una de las cosas sorprendentes de la red Stellar es que puedes crear, mantener, enviar, recibir e intercambiar cualquier tipo de activo. Muchas organizaciones emiten activos en Stellar que representan monedas del mundo real, como dólares estadounidenses o nairas nigerianas, o criptomonedas como bitcoin o ether.

Cada uno de estos activos redimibles — anclado en la jerga Stellar — es esencialmente un crédito emitido por una cuenta particular que representa las reservas que esas cuentas tienen fuera de la red. Por eso los activos en el ejemplo anterior tenían tanto un código como un emisor: el emisor es la clave pública de la cuenta que creó el activo, una cuenta propiedad de la organización que finalmente honra el crédito que representa ese activo.