Cuentas agrupadas: cuentas muxed y memos
Al desarrollar una aplicación o servicio en Stellar, una de las primeras decisiones que debes tomar es cómo manejar las cuentas de los usuarios.
Puedes crear una cuenta Stellar para cada usuario, pero la mayoría de los servicios custodiados, incluyendo los exchanges de criptomonedas, optan por usar una única cuenta Stellar agrupada para gestionar transacciones en nombre de sus usuarios. En estos casos, la función de cuentas muxed puede mapear las transacciones a cuentas individuales a través de una base de datos interna de clientes.
Antes usábamos memos para este propósito; sin embargo, usar cuentas muxed es mejor a largo plazo. Actualmente, no todas las billeteras, exchanges y anchors soportan cuentas muxed, por lo que probablemente quieras soportar tanto memos como cuentas muxed, al menos por un tiempo.
Cuentas agrupadas
Una cuenta agrupada permite que un único ID de cuenta Stellar se comparta entre muchos usuarios. Generalmente, los servicios que usan cuentas agrupadas rastrean a sus clientes en una base de datos interna separada y usan la función de cuentas muxed para asociar un pago entrante o saliente con el cliente interno correspondiente.
Los beneficios de usar una cuenta agrupada son costos más bajos — no se necesitan reservas base por cada cuenta — y menor complejidad de claves — solo necesitas administrar un keypair de cuenta. Sin embargo, con una única cuenta agrupada, ahora te corresponde gestionar todos los saldos y pagos de los clientes individuales. Ya no puedes confiar en el ledger de Stellar para acumular valor, gestionar errores y atomicidad, o administrar transacciones en base a cada cuenta individual.
Cuentas muxed
Las cuentas muxed están integradas en el protocolo por conveniencia y estandarización. Distinguen cuentas individuales que existen bajo una sola cuenta tradicional de Stellar. Combinan la dirección familiar GABC…
con un ID entero de 64 bits.
Las cuentas muxed no existen en el ledger, pero sí su cuenta base GABC…
compartida.
Las cuentas muxed están definidas en CAP-0027, introducidas en el Protocolo 13, y su representación en cadena está descrita en SEP-0023.
Es seguro que todas las billeteras implementen el envío a cuentas muxed.
Si deseas recibir depósitos a cuentas muxed, ten en cuenta que aún no todas las billeteras ni exchanges las soportan.
Formato de dirección
Las cuentas muxed tienen su propio formato de dirección que empieza con el prefijo M. Por ejemplo, partiendo de una dirección tradicional de Stellar: GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
, podemos crear nuevas cuentas muxed con diferentes IDs. Los IDs están incrustados en la propia dirección — al analizar las direcciones de cuentas muxed, obtendrás la dirección G mencionada más arriba, más un número adicional.
MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ
tiene el ID 0, mientras queMA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4
tiene el ID 420.
Ambas direcciones actuarán sobre la dirección subyacente GA7Q…
cuando se usen con una de las operaciones compatibles.
Operaciones compatibles
No todas las operaciones se pueden usar con cuentas muxed. Estas son las que puedes usar:
- La cuenta fuente de cualquier operación o transacción;
- La fuente de pago de una transacción con fee-bump;
- El destino de los tres tipos de pagos:
Payment
,PathPaymentStrictSend
, yPathPaymentStrictReceive
;
- El destino de un AccountMerge; y
- El objetivo de una operación
Clawback
(es decir, el campo from).
Demostraremos algunos de estos en la sección de ejemplos.
No hay validación en los IDs y, para la red Stellar, todas las operaciones soportadas funcionan exactamente como si no usaras una cuenta muxed. Por ejemplo, si haces dos pagos desde dos cuentas muxed que comparten una cuenta Stellar subyacente (la misma dirección G… pero diferentes direcciones M… estas direcciones) esto es exactamente lo mismo que esa única cuenta Stellar enviando dos pagos según el ledger. Aunque solo la cuenta G…
subyacente existe realmente en el ledger de Stellar, la API Horizon hará un esfuerzo por interpretar y rastrear las cuentas muxed responsables de ciertas acciones.
Ejemplos
En esta sección, mostraremos cómo crear cuentas muxed y cómo interactúan con sus operaciones compatibles. Dado que los métodos alternativos para cuentas custodiadas basados en memos de transacción ya no son necesarios, usaremos eso como base para nuestra estructura de ejemplo.
Después de preparar algo de código auxiliar, mostraremos tres ejemplos:
Ejemplo 1: Pagos normales entre cuentas Stellar “completas” (es decir, de G a G)
Ejemplo 2: Pagos mixtos (es decir, de M a G)
Ejemplo 3: Pagos totalmente muxed (es decir, de M a M) G a G)
Ejemplo 2: Pagos mixtos (es decir, M a G)
Ejemplo 3: Pagos completamente multiplexados (es decir, M to M)
Pero usaremos una función compartida para todos ellos que hace el trabajo real, destacando la facilidad de implementar soporte para cuentas muxed.
Preámbulo
Primero, crearemos dos cuentas y luego varias cuentas virtuales que representan a “clientes custodiados” que maneja la cuenta principal:
- JavaScript
const sdk = require("stellar-sdk");
const passphrase = "Test SDF Network ; September 2015";
const url = "https://horizon-testnet.stellar.org";
let server = new sdk.Server(url);
const custodian = sdk.Keypair.fromSecret(
"SAQLZCQA6AYUXK6JSKVPJ2MZ5K5IIABJOEQIG4RVBHX4PG2KMRKWXCHJ",
);
const outsider = sdk.Keypair.fromSecret(
"SAAY2H7SANIS3JLFBFPLJRTYNLUYH4UTROIKRVFI4FEYV4LDW5Y7HDZ4",
);
async function preamble() {
[custodianAcc, outsiderAcc] = await Promise.all([
server.loadAccount(custodian.publicKey()),
server.loadAccount(outsider.publicKey()),
]);
customers = ["1", "22", "333", "4444"].map(
(id) => new sdk.MuxedAccount(custodianAcc, id),
);
console.log("Custodian:\n ", custodian.publicKey());
console.log("Customers:");
customers.forEach((customer) => {
console.log(
" " + customer.id().padStart(4, " ") + ":",
customer.accountId(),
);
});
console.log();
}
Asumimos que estas cuentas existen en la testnet; puedes reemplazarlas con tus propias claves y usar friendbot si quieres.
Cuando ejecutemos esta función, veremos la similitud en las direcciones de cuentas muxed entre los clientes, destacando que comparten una clave pública:
Custodian:
GCIHAQVWZH2AB5BB5NP63FBSIREG77LQZZNUVKD2LN2IOCLOT6N72MJN
Customers:
1: MCIHAQVWZH2AB5BB5NP63FBSIREG77LQZZNUVKD2LN2IOCLOT6N72AAAAAAAAAAAAEDB4
22: MCIHAQVWZH2AB5BB5NP63FBSIREG77LQZZNUVKD2LN2IOCLOT6N72AAAAAAAAAAAC3IHY
333: MCIHAQVWZH2AB5BB5NP63FBSIREG77LQZZNUVKD2LN2IOCLOT6N72AAAAAAAAAABJV72I
4444: MCIHAQVWZH2AB5BB5NP63FBSIREG77LQZZNUVKD2LN2IOCLOT6N72AAAAAAAAAARLQOKK
Con las cuentas fuera del camino, veamos cómo podemos manejar la diferencia entre cuentas tradicionales de Stellar (G...) y estas cuentas virtuales muxed (M...).
Modelo de operaciones con muxed
La introducción de direcciones muxed como una abstracción de mayor nivel — y su naturaleza experimental y optativa — significa que hay ramas de código ligeramente divergentes dependiendo de si la fuente es una cuenta muxed o no. Por ejemplo, todavía necesitamos cargar cuentas por su dirección subyacente, porque las versiones muxed no existen realmente en el ledger de Stellar:
- JavaScript
function loadAccount(account) {
if (StellarSdk.StrKey.isValidMed25519Address(account.accountId())) {
return loadAccount(account.baseAccount());
} else {
return server.loadAccount(account.accountId());
}
}
function showBalance(acc) {
console.log(`${acc.accountId().substring(0, 5)}: ${acc.balances[0].balance}`);
}
Para los pagos — nuestro enfoque para este conjunto de ejemplos — la divergencia solo importa porque queremos mostrar los saldos de la cuenta custodia.
Pagos
El código real para construir pagos es casi exactamente el mismo que sin la situación muxed:
- JavaScript
function doPayment(source, dest) {
return loadAccount(source)
.then((accountBeforePayment) => {
showBalance(accountBeforePayment);
let payment = sdk.Operation.payment({
source: source.accountId(),
destination: dest.accountId(),
asset: sdk.Asset.native(),
amount: "10",
});
let tx = new sdk.TransactionBuilder(accountBeforePayment, {
networkPassphrase: StellarSdk.Networks.TESTNET,
fee: StellarSdk.BASE_FEE,
})
.addOperation(payment)
.setTimeout(30)
.build();
tx.sign(custodian);
return server.submitTransaction(tx);
})
.then(() => loadAccount(source))
.then(showBalance);
}
Podemos usar este bloque para hacer un pago entre cuentas normales de Stellar con facilidad: doPayment("GCIHA...", "GDS5N..."). La principal diferencia respecto al código estándar de pagos, aparte de los fragmentos que muestran los saldos de XLM antes y después, es la inclusión de la bandera optativa withMuxing.
Muxed a No muxed
El bloque de código anterior cubre todas las operaciones de pago, abstrayendo cualquier necesidad de diferenciar entre direcciones muxed (M...) y no muxed (G...) de direcciones. Desde un nivel alto, entonces, sigue siendo trivial hacer pagos entre uno de nuestros “clientes” y alguien fuera de la organización del “custodio”.
- JavaScript
preamble.then(() => {
const src = customers[0];
console.log(
`Sending 10 XLM from Customer ${src.id()} to ${outsiderAcc
.accountId()
.substring(0, 5)}.`,
);
return doPayment(src, outsiderAcc);
});
Ten en cuenta que aún firmamos la transacción con las claves del custodio, porque las cuentas muxed no tienen concepto de clave secreta. En última instancia, todo pasa por la cuenta principal, por lo que deberíamos ver que su saldo disminuye en 10 XLM en consecuencia:
Sending 10 XLM from Customer 1 to GDS5N.
GCIHA: 9519.9997700 XLM
GCIHA: 9509.9997600 XLM
Por supuesto, también se cobra una tarifa por la propia transacción.
Muxed a Muxed
Como hemos mencionado, las acciones con cuentas muxed no se representan explícitamente en el ledger de Stellar. Cuando dos cuentas muxed que comparten una cuenta Stellar subyacente se comunican, es como si la cuenta subyacente estuviera hablando consigo misma. Un pago entre dos cuentas así es esencialmente una no-operación.
- JavaScript
preamble().then(() => {
const [src, dst] = customers.slice(0, 2);
console.log(
`Sending 10 XLM from Customer ${src.id()} to Customer ${dst.id()}.`,
);
return doPayment(src, dst);
});
El resultado debería ser algo como lo siguiente:
Sending 10 XLM from Customer 1 to Customer 22.
GCIHA: 9579.9999800 XLM
GCIHA: 9579.9999700 XLM
Observa que el saldo de la cuenta se mantiene básicamente igual, aunque se cobró una tarifa ya que esta transacción aún se registra en el ledger (a pesar de hacer casi nada). Quizás quieras detectar este tipo de transacciones en tu aplicación para evitar pagar tarifas de transacción innecesarias.
Si hiciéramos un pago entre dos cuentas muxed con diferentes cuentas Stellar subyacentes, sería equivalente a un pago entre esas dos cuentas respectivas G... cuentas.
Más ejemplos
Como es común en la mayoría de las características a nivel de protocolo, puedes encontrar más ejemplos de uso e inspiración en la suite de pruebas correspondiente para tu SDK favorito. Por ejemplo, aquí están algunos casos de prueba en JavaScript.
Preguntas frecuentes
¿Qué pasa si pago a una dirección muxed, pero el destinatario no las soporta?
En general, no deberías enviar pagos a direcciones muxed en plataformas que no las soporten. Estas plataformas ni siquiera podrán proporcionar direcciones de destino muxed desde un principio.
Aún así, si esto ocurre, analizar una transacción con un parámetro muxed sin manejarlo puede llevar a una de dos situaciones:
- Si tu SDK está desactualizado, el análisis fallará. Deberías actualizar tu SDK. Por ejemplo, el SDK de JavaScript mostrará un mensaje útil:
“destination is invalid; did you forget to enable muxing?”
- Si tu SDK está actualizado, verás la dirección muxed (
M...
) analizada correctamente. Lo que sucede a continuación depende de tu aplicación.
No obstante, la operación se ejecutará con éxito en la red. En el caso de los pagos, por ejemplo, la dirección principal del destino seguirá recibiendo los fondos.
¿Qué pasa si quiero pagar a una cuenta muxed, pero mi plataforma no las soporta?
En este caso, no uses una dirección muxed. Es probable que la plataforma falle al crear la operación. Probablemente quieras usar el método legado de incluir un memo en la transacción, en su lugar.
¿Qué hago si recibo una transacción con direcciones muxed y un ID de memo?
En un mundo ideal, esta situación nunca sucedería. Puedes determinar si los IDs subyacentes son iguales; si no lo son, se trata de una transacción malformada y recomendamos no enviarla a la red.
¿Qué pasa si obtengo errores al usar cuentas muxed?
En versiones actualizadas de los SDKs de Stellar, las cuentas muxed son admitidas de forma nativa por defecto. Sin embargo, si estás usando una versión antigua de un SDK, pueden seguir estando ocultas detrás de una característica flag.
Si obtienes errores al usar direcciones muxed en operaciones admitidas como: “destino es inválido; ¿habilitaste muxing?”
Recomendamos actualizar a la última versión de todos los SDKs de Stellar que uses. Sin embargo, si eso no es posible por alguna razón, necesitarás habilitar la característica flag antes de interactuar con cuentas muxed. Consulta la documentación de tu SDK para detalles.
¿Qué sucede si paso una dirección muxed a una operación incompatible?
Solo ciertas operaciones permiten cuentas muxed, como se describió anteriormente. Pasar una dirección muxed a un parámetro incompatible con un SDK actualizado debería resultar en un error de compilación o de tiempo de ejecución en el momento de uso.
Por ejemplo, al usar incorrectamente el SDK de JavaScript:
- JavaScript
const mAddress =
"MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4";
transactionBuilder.addOperation(
Operation.setTrustLineFlags({
trustor: mAddress, // wrong!
asset: someAsset,
flags: { clawbackEnabled: false },
}),
);
El resultado en tiempo de ejecución sería:
“Error: byte de versión inválido. se esperaba 48, se obtuvo 96”
Este mensaje de error indica que el trustor
no se pudo analizar como un ID de cuenta Stellar (G...
). En otras palabras, tu código fallará y la operación inválida nunca llegará a la red.
¿Cómo valido direcciones Stellar?
Debes usar los métodos de validación proporcionados por tu SDK o seguir cuidadosamente el SEP-23. Por ejemplo, el SDK de JavaScript proporciona los siguientes métodos para validar direcciones Stellar:
namespace StrKey {
function isValidEd25519PublicKey(publicKey: string): boolean;
function isValidMed25519PublicKey(publicKey: string): boolean;
}
También hay abstracciones para construir y gestionar tanto cuentas muxed como regulares; consulta la documentación de tu SDK para detalles.
Memo - cuentas diferenciadas
Antes de la introducción de las cuentas muxed, productos y servicios que dependían de cuentas agrupadas a menudo usaban memos de transacción para diferenciar entre usuarios. Admitir cuentas muxed es mejor a largo plazo, pero por ahora puedes querer admitir tanto memos como cuentas muxed ya que no todos los exchanges, anchors y billeteras admiten cuentas muxed.
Para aprender sobre otros propósitos para los cuales se pueden usar los memos, consulta nuestra sección de Memos.
¿Por qué son mejores las cuentas muxed a largo plazo?
Las cuentas muxed son una mejor forma de diferenciar entre individuos en una cuenta agrupada porque tienen mejor:
- Compartibilidad — en lugar de preocuparte por cosas propensas a errores como copiar y pegar IDs de memo, simplemente puedes compartir tu dirección M... address.
- Soporte en SDK — los distintos SDKs admiten esta abstracción de forma nativa, permitiéndote crear, gestionar y trabajar con cuentas muxed fácilmente. Esto significa que pueden aparecer direcciones muxed al analizar cualquiera de los campos que las admiten, por lo que debes estar preparado para manejarlas. Consulta la documentación de tu SDK para detalles; por ejemplo, la v7.0.0 de la biblioteca stellar-base del SDK de JavaScript describe todos los campos y funciones relacionados con las cuentas muxed.
- Eficiencia — al combinar cuentas virtuales relacionadas bajo el paraguas de una sola cuenta, puedes evitar mantener reservas y pagar tarifas por cada una de ellas individualmente en el ledger. También puedes combinar varios pagos a múltiples destinos en una sola transacción ya que ya no necesitas el campo memo por transacción.
Guías en esta categoría:
📄️ Crear una cuenta
Aprende sobre cómo crear cuentas Stellar, pares de llaves, financiamiento y conceptos básicos de las cuentas.
📄️ Enviar y recibir pagos
Aprende a enviar pagos y estar atento a los pagos recibidos en la red Stellar.
📄️ Cuentas canalizadas
Crea cuentas canalizadas para enviar transacciones a la red a una alta velocidad.
📄️ Saldos reclamables
Divide un pago en dos partes creando un saldo reclamable.
📄️ Recuperaciones
Usa las recuperaciones para quemar una cantidad específica de un activo habilitado para recuperación desde una trustline o un saldo reclamable.
📄️ Transacciones de suplemento de tarifa
Usa transacciones fee-bump para pagar las comisiones de transacción en nombre de otra cuenta sin volver a firmar la transacción.
📄️ Reservas patrocinadas
Utiliza las reservas patrocinadas para pagar las reservas base en nombre de otra cuenta.
📄️ Pagos con rutas
Enviar un pago donde el activo recibido sea diferente del activo enviado.
📄️ Cuentas agrupadas: cuentas muxed y memos
Usa cuentas muxed para diferenciar entre cuentas individuales dentro de una cuenta agrupada.
📄️ Instalar y desplegar un contrato inteligente con código
Instalar y desplegar un contrato inteligente con código.
📄️ Instalar WebAssembly (Wasm) bytecode usando código
Instala el Wasm del contrato usando js-stellar-sdk.
📄️ Invocar una función de contrato en una transacción Stellar utilizando SDKs
Usa el Stellar SDK para crear, simular y ensamblar una transacción.
📄️ guía del método RPC simulateTransaction
Guía de ejemplos y tutoriales de simulateTransaction.
📄️ Enviar una transacción a Stellar RPC utilizando el SDK de JavaScript
Utiliza un mecanismo de repetición para enviar una transacción al RPC.