Artículo de Blog

Haciendo las Remesas Internacionales Fáciles con Pagar por Teléfono

Autor

Jeesun Kim and Howard Chen

Fecha de publicación

Remesas

Pagar por teléfono

Vibrante

Las remesas globales son una enorme oportunidad de mercado. Según el Foro Económico Mundial (WEF), las remesas globales totales valían 781 mil millones de dólares en 2021, “y han aumentado a 794 mil millones de dólares en 2022.” Además, las remesas tienen un impacto notable en países de ingresos bajos y medios debido a su capacidad para reducir la pobreza. Un estudio del Banco Mundial estudio, por ejemplo, encontró que un aumento del 10% en las remesas reducía los niveles de pobreza en un 3.5%.

Debido a este impacto en las personas de países en desarrollo, reducir el costo de enviar dinero ha estado en la agenda del WEF desde 2015, y la industria de tecnología financiera incluso más tiempo. Ahora, hay una plataforma como Wise que ha reducido el costo de transferencia de dinero internacional.**

Pero estas soluciones aún requieren que tanto el emisor como el receptor tengan una cuenta bancaria. En la mayoría de los casos, enviar dinero al extranjero sigue siendo costoso y engorroso para aquellos sin una cuenta bancaria. Aquí es donde entra Vibrant, como una billetera móvil que los no bancarizados pueden usar para facilitar pagos de remesas rápidos y baratos. Los usuarios de Vibrant pueden enviar activos de Stellar como la moneda digital dólar totalmente reservada de Circle, USDC, ARST y XLM a sus amigos y familiares dentro de 5 segundos a nivel nacional e internacional sin necesidad de una cuenta bancaria.

Recientemente agregamos una mejora a esta característica llamada “Enviar a Teléfono” que permite a los usuarios de Vibrant enviar USDC a sus amigos y familiares que no son usuarios de Vibrant (aún) simplemente enviando USDC a través de su número de teléfono. Cuesta cero tarifas y el destinatario puede recibir los fondos en cuestión de segundos siempre y cuando registre una cuenta en Vibrant con el mismo número de teléfono.

Esta es una característica popular entre los productos fintech — Chime’s Paga a Cualquiera y Cash App admiten una característica similar a esta.** Sin embargo, “Enviar a Teléfono” de Vibrant es un poco diferente.

Con Vibrant:

  • No se necesita cuenta bancaria ni tarjeta de débito/crédito para que el destinatario reciba USDC
  • Los destinatarios pueden retirar el dinero inmediatamente en ubicaciones de MoneyGram elegibles sin tarifas hasta junio de 2023
  • Los pagos internacionales son posibles

Vibrant habilitó esta característica usando SEP 30 (recuperación de cuentas de Stellar por múltiples partes). Esta publicación de blog va a mostrarte paso a paso cómo construimos esta característica usando SEP 30 con fragmentos de código frontend.

Nota: estos son ejemplos de código altamente simplificados. No utilizan límites de tiempo, no manejan la expiración de una transacción, ni manejan error. Si te gustaría construir uno para tu proyecto, asegúrate de consultar la documentación de SDF sobre cómo manejar errores con gracia. También hemos abstraído los códigos de backend, pero los detalles de lo que hacen están escritos en los comentarios sobre la declaración de las funciones para darte una idea de lo que hacen en el fondo.

¿Cómo funciona SEP-30?

Si estás familiarizado con Stellar, probablemente hayas visto una charla en Meridian sobre SEP-30 y la Importancia de la Gestión y Recuperación de Claves y los posts del blog de la serie SEP-30, enlazados abajo, escritos por Denelle y Leigh. Recomiendo encarecidamente leer primero la serie SEP-30 para tener una mejor comprensión de SEP-30 antes de leer esta publicación de blog si no lo has hecho.

  1. La Importancia de la Gestión de Claves & Recuperación
  2. Gestión de Claves 101
  3. SEP-30 y la Gestión de Claves Amigable para el Usuario

Los posts de blog anteriores ilustran el ejemplo más conocido de lo que las billeteras pueden hacer con SEP-30, “recuperación de cuenta.” Pero hay otra característica que SEP-30 habilita: Un usuario de Vibrant puede enviar fondos a un usuario no Vibrant usando solo su número de teléfono. Esto se refiere al caso de uso de “compartir”.

A diferencia de “recuperación de cuenta”, que solo asigna una identidad a los servidores de recuperación. Con “compartir”, asignamos dos identidades: “emisor” y “receptor”.

Enviando un pago a un número de teléfono

Digamos que el Usuario A (emisor) en Estados Unidos quiere enviar USDC a su amigo, Usuario B (receptor), que vive en Argentina. Para lograr este caso de uso, necesitamos crear una cuenta temporal (TA) que tanto el Emisor como el Receptor puedan controlar (una vez que ella registre una cuenta en Vibrant con su número de teléfono).

Nota: Los desarrolladores de Vibrant aprovechan js-stellar-sdk, una biblioteca de Javascript para comunicarse con un servidor Stellar Horizon, intensamente en nuestro proyecto. Hace que el proceso de desarrollo en Stellar sea mucho más fácil, así que te animo encarecidamente a usarlo si quieres desarrollar en Stellar.

Paso #1: El emisor registra la clave pública de la cuenta temporal con los dos servidores de recuperación

1. El emisor genera un nuevo par de claves para TA
2. El emisor obtiene un SEP-10 JWT (Token Web JSON) de Vibrant y el servidor de recuperación de Lobstr
3. El emisor llama a SEP-30 POST /accounts/<clave pública de TA> para registrar la TA con ambos servidores, proporcionando las siguientes identidades:

a. Una vez que los servidores verifican los JWTs de SEP-10, almacenan la dirección de la cuenta y las identidades alternativas
b. Los servidores generan una clave de firma aleatoria única para la cuenta y encriptan la clave secreta de firma y la almacenan
c. Los servidores responden a la billetera con la clave pública de firma
4. El emisor mantiene cada clave pública de firma para usarlas más tarde en una transacción como firmante de la cuenta TA en el siguiente paso

Paso #2: El emisor construye una transacción (tx) para backend y backend devuelve una tx parcialmente firmada

Una vez que la remitente recupera con éxito una clave pública de firma tanto de los servidores de recuperación de Vibrant como de Lobstr, ella construye una transacción para crear una TA que incluye toda la información que Vibrant necesita para la TA para enviar fondos al receptor.

Paso #3: La remitente firma y envía la transacción

1. La remitente firma la transacción con la clave del dispositivo y la clave privada de la TA
2. La remitente envía la transacción al backend para que se aumente la tarifa
3. La remitente envía la transacción con tarifa aumentada a Horizon
4. La clave privada de la TA es inútil después de este punto y no tiene uso futuro planeado. El dispositivo puede descartarla en este punto

import {
Operation,
} from "stellar-sdk";

function createVibrantTempAccount({
 senderStellarAddress,
 amount, // 3
‍ asset,
 phoneNumber,
}: {
 senderStellarAddress: string;
 amount: string;
 asset: Currency; // custom type
‍ phoneNumber: string;
}) {

/**
 * Step 1: Sender registers the temp account public key with the two recovery servers
 */
// generateStellarAccount() uses stellar-sdk’s Keypair to
// return temporary account’s public and private key
// make sure to follow the best practice to secure a private key
// https://stellar.github.io/js-stellar-sdk/Keypair.html
‍ const receiverTempAccount = generateStellarAccount();
 const USDC = asset;
// recoverysignerSigningKeys includes both recovery servers’ signatures
// that Sender registered earlier by calling
// SEP-30 POST /accounts/<TA's public key> with
// two identities “sender” and “receiver”
‍ let recoverysignerSigningKeys: Signer[] = [{...vibrant recovery server info}, {...lobstr recovery server info}];
// vibrant distribution account is the sponsor of the TA we’re creating
‍ let vibrantDistributionAccountPubkey: string = "GDEZ...";
‍
/**
 * Step 2: Sender constructs a transaction (tx) for backend and backend returns a partially signed tx
 */

// signerSetOptions will be used within operations = [] later
‍ const signerSetOptions = recoverysignerSigningKeys.map((signer) =>
 Operation.setOptions({
 signer: {
 ed25519PublicKey: signer.publicKey,
 weight: 10, // each recovery server to have weight of 10
‍ },
 source: receiverTempAccount.publicKey,
 }),
 );

 const operations = [
// beginSponsoringFutureReserves is needed to cover the initial funding needed for creating a TA, its trustline, and setting signers to the TA
//https://developers.stellar.org/docs/encyclopedia/sponsored-reserves#begin-and-end-sponsorships
‍ Operation.beginSponsoringFutureReserves({
 sponsoredId: receiverTempAccount.publicKey,
 source: vibrantDistributionAccountPubkey,
 }),

 Operation.createAccount({
 destination: receiverTempAccount.publicKey,
 startingBalance: "0",
 source: vibrantDistributionAccountPubkey,
 }),

 Operation.changeTrust({
 source: receiverTempAccount.publicKey,
 asset: USDC,
 }),

 ...signerSetOptions,

// endSponsoringFutureReserves allows the sponsored account (TA) to accept the sponsorship
// both Begin Sponsoring Future Reserves and End Sponsoring Future Reserves operations must appear in the sponsorship transaction, guaranteeing that both accounts agree to the sponsorship
‍ Operation.endSponsoringFutureReserves({
 source: receiverTempAccount.publicKey,
 }),

// setOptions to set TA account’s own signature weight and threshold values. // masterWeight to 0 so the TA account can’t do any kind of transaction on its own
// Setting all the thresholds to 20 so that once both recovery signers sign (10 weight + 10 weight = 20), they can access the account
// list of operations with thresholds: https://developers.stellar.org/docs/fundamentals-and-concepts/list-of-operations
‍ Operation.setOptions({
 masterWeight: 20,
 lowThreshold: 20,
 medThreshold: 20,
 highThreshold: 20,
 source: receiverTempAccount.publicKey,
 }),

// payment operation:
// sending 3 USDC from Sender to TA
‍ Operation.payment({
 destination: receiverTempAccount.publicKey,
 asset: USDC, // any Stellar asset works
‍ amount, // “3”
‍ source: senderStellarAddress,
 }),
 ];

// building a tx with operations we set from the above
// we use TransactionBuilder from js-stellar-sdk in this custom function
‍ const transaction = await buildTransaction(
 {
 publicKey: senderStellarAddress,
 operations,
 }
 );

/**
 * Step 3: Sender signs and submits the transaction
 */
‍ try {
 // Sender signs the tx with Sender's device key
‍ // and the TA’s private key
‍ await signWithKeyManager({
 transaction,
 signer: SENDER_DEVICE_KEY,
 });
 await signWithKeyManager({
 transaction,
 signer: TEMP_ACCOUNT_PRIVATE_KEY,
 });
 }

 try {
 // call the backend endpoint that validates and signs the tx,
‍ // fee-bumps it, and submits it to Horizon
‍ // TA account’s now created on Stellar network
‍ await appApi.createTempAccountAndSendPayment({
 transaction: transaction.toXDR(),
 phoneNumber,
 });
 }


Paso #4: La remitente envía un mensaje de texto al receptor con la aplicación nativa de SMS en su teléfono

Recibir un pago con un número de teléfono

Después de que la Usuario B recibe un mensaje de texto sobre sus fondos de Usuario A (remitente), Usuario B descarga Vibrant e ingresa su número de teléfono para registrar una cuenta Vibrant con el fin de recibir los fondos. En el lado de la UX, parece un flujo normal de inicio de sesión o registro, pero bajo el capó, tenemos pasos adicionales.

Paso #1: La receptora descubre las TA(s) a las que tiene acceso y consulta una TA

1. La receptora obtiene un token JWT SEP-10 de ambos servidores después de registrar una cuenta Vibrant con su número de teléfono que fue registrado con ambos servidores de recuperación de Vibrant y Lobstr por la Remitente
2. La remitente llama a un endpoint del backend que verifica si el número de teléfono de la Receptora coincide con el número de teléfono de los pagos de TA
3. Si hay una coincidencia, la Receptora consulta Horizon para obtener el saldo de una TA para obtener la información de pago para usarla en la transacción del siguiente paso para reclamar sus fondos

Paso #2: La receptora construye una transacción y la hace firmar

1. La receptora usa los tokens JWT SEP-10 para acceder al SEP-30 POST /accounts/<TA’s pubkey>/sign/<signing-address> en ambos firmantes de recuperación
2. El receptor añade ambas firmas a la transacción
3. Esta transacción ahora está firmada por ambos firmantes de recuperación

Paso #3: El receptor envía la transacción

El receptor envía la transacción al backend para que se aumente la tarifa

El receptor envía la transacción con tarifa aumentada a Horizon

El receptor llama a SEP-30 DELETE /accounts/<TA’s address> a ambos servidores de recuperación de Vibrant y Lobstr para eliminar el TA porque ya no existe

import {
 Operation,
} from "stellar-sdk";

// esta función se llama después del Paso #1
// consultando el TA y resulta que el Receptor sí tiene un TA pendiente
‍const createClaimSendToPhoneTransaction = async ({
 receiverStellarAddress,
 tempPayment,
 jwtTokens, // jwt token recibido de vibrant y lobstr recovery server
‍}: {
 receiverStellarAddress: string;
 tempPayment: Payment; // tipo personalizado
‍ jwtTokens: { [key: string]: string };
}) => {

/**
 * Paso 2: El receptor construye una transacción y la firma
 */
‍
// el pago que el remitente envió al TA
‍ const { currency, amount, asset: USDC, tempAccountPubkey } = tempPayment;

// devuelve un endpoint de horizon
‍ const horizon = getHorizon();

// devuelve un objeto de detalle del servidor de recuperación de vibrant
‍ const localRecoveryServer = getLocalRecoveryServer();

// devuelve un objeto de detalle del servidor de recuperación de lobstr
‍ const lobstrRecoveryServer = getLobstrRecoveryServer();

 const operations = [
 Operation.payment({
 destination: receiverStellarAddress,
 asset: USDC,
 amount,
 }),
 Operation.changeTrust({
 asset: USDC,
 limit: "0", // significa que estamos eliminando la línea de confianza
‍ }),

 // accountMerge significa que estamos fusionando el TA a la cuenta del Receptor
‍ // en otras palabras, eliminar la cuenta del TA
‍ Operation.accountMerge({
 destination: receiverStellarAddress,
 }),
 ];

// construyendo una transacción con operaciones que establecimos desde arriba
‍ const transaction = await buildTransaction(
 {
 publicKey: tempAccountPubkey,
 operations,
 },
 );

 try {
// addRecoveryServerSignature llama
// SEP-30 POST /accounts/<TA’s pubkey>/sign/<signing-address>
// firma una transacción que tiene operaciones para el TA usando la llave de firma de servidores de recuperación
‍ await addRecoveryServerSignature({
 publicKey: tempAccountPubkey,
 transaction,
 idToken: jwtTokens[localRecoveryServer.id],
 recoveryServer: localRecoveryServer,
 signerKey: localRecoveryServer.publicKey,
 });

 await addRecoveryServerSignature({
 publicKey: tempAccountPubkey,
 transaction,
 idToken: jwtTokens[lobstrRecoveryServer.id],
 recoveryServer: lobstrRecoveryServer,
 signerKey: lobstrRecoveryServer.publicKey,
 });
 }

/**
 * Paso 3: El receptor envía la transacción
 */
‍ try {
 // aumentar la tarifa de la tx para poder enviar la tx
‍ const feeBumpedTransaction = await feeBumpTransaction({ transaction });

 // estamos enviando la tx que incluye una operación de pago
‍ // que transfiere los fondos del TA al Receptor
‍ // la tx ha sido aumentada de tarifa y firmada por ambos firmantes de recuperación
‍ await horizon.submitTransaction(feeBumpedTransaction);

 // Una vez que se envía, llamamos a removeTempAcctFromRecoverySigner
‍ // que llama a SEP-30 DELETE /accounts/<TA’s address> a cada
‍ // servidor de recuperación para eliminar el registro del TA ya que
‍ // ya no existe en la red de Stellar
‍ await removeTempAcctFromRecoverySigner({
 publicKey: tempAccountPubkey,
 idToken: jwtTokens[localRecoveryServer.id],
 recoveryServer: localRecoveryServer,
 });

 await removeAccountFromRecoverySignerWithToken({
 publicKey: tempAccountPubkey,
 idToken: jwtTokens[lobstrRecoveryServer.id],
 recoveryServer: lobstrRecoveryServer,
 });
 }
};

Paso #4: El receptor repite el paso 1 ~ 4 para reclamar todos los pagos retenidos en TAs

Una vez que el Receptor completa el paso #1 al paso #4, puede utilizar los fondos que el Usuario A envió. Ahora puede mantener los fondos en la red de Stellar y usarlos para reservar un viaje, comprar una tarjeta de regalo, simplemente mantenerlos en USDC para luchar contra la inflación, o convertirlos en su moneda local para su uso en la vida cotidiana.

Caso de uso

Usando SEP-30, Vibrant ha habilitado una de las transferencias sin fronteras más fáciles que existen.

Con esta poderosa función habilitada a través de SEP-30, por ejemplo, los trabajadores migrantes en EE. UU. ahora pueden enviar fondos inmediatamente a su país de origen. Los usuarios de Vibrant también pueden enviar ayuda a aquellos en un país que enfrenta una crisis humanitaria simplemente ingresando su número de teléfono. Si deseas saber más sobre SEP-30, por favor visita nuestro documentación de SEP-30.

Si tienes alguna pregunta, no dudes en enviarme un mensaje en twitter a @codeandfood.

*Países con soporte para retirar efectivo: EE. UU., México, Argentina, Ucrania, Kenia, Colombia, Perú, Guatemala, República Dominicana, Honduras, Paraguay, Nicaragua, El Salvador, Costa Rica, Uruguay

**Stellar y Vibrant no están afiliados con estas compañías de ninguna manera.