Liquidez en Stellar: SDEX y Fondos de Liquidez
Esta sección se enfoca específicamente en la liquidez relacionada con el AMM y el SDEX desarrollados en el protocolo Stellar y no incluye información sobre contratos inteligentes.
Los usuarios pueden comerciar y convertir activos en la red Stellar mediante pagos por rutas a través del exchange descentralizado Stellar y los fondos de liquidez.
En esta sección hablaremos sobre el SDEX y los fondos de liquidez. Para aprender cómo funcionan juntos para ejecutar transacciones, consulta nuestra Entrada del Enciclopedia de Pagos por Rutas.
SDEX
La red Stellar actúa como un exchange distribuido descentralizado que permite a los usuarios comerciar y convertir activos con las operaciones Manage Buy Offer y Manage Sell Offer. El ledger de Stellar almacena tanto los saldos de las cuentas de usuario como las órdenes que estas cuentas hacen para comprar o vender activos.
Libro de órdenes
Stellar utiliza libros de órdenes para operar su exchange descentralizado.
Un libro de órdenes es un registro de órdenes pendientes en una red, y cada registro se sitúa entre dos activos (trigo y ovejas, por ejemplo). El libro de órdenes de este par de activos registra cada cuenta que quiere vender trigo por ovejas y cada cuenta que quiere vender ovejas por trigo. En finanzas tradicionales, comprar se expresa como una orden de “compra” (bid), y vender se expresa como una orden de “venta” (ask) (las órdenes ask también se llaman ofertas).
Un par de notas sobre los libros de órdenes en Stellar:
- El término “ofertas” generalmente se refiere específicamente a las órdenes ask. Sin embargo, en Stellar, todas las órdenes se almacenan como órdenes de venta, es decir, el sistema convierte automáticamente las órdenes de compra en órdenes ask. Por eso, los términos “oferta” y “orden” se usan indistintamente en el ecosistema Stellar.
- Los libros de órdenes contienen todas las órdenes aceptables para las partes de cualquiera de los lados para realizar una operación.
- Algunos activos tendrán un libro de órdenes pequeño o inexistente entre ellos. En estos casos, Stellar facilita pagos por rutas, que discutiremos más adelante.
Para ver un gráfico de libro de órdenes, consulta la página de Wikipedia sobre libros de órdenes. Además, hay muchos tutoriales en vídeo y artículos que pueden ayudarte a entender en mayor detalle cómo funcionan los libros de órdenes.
Órdenes
Una cuenta puede crear órdenes para comprar o vender activos usando las operaciones Gestionar oferta de compra, Gestionar oferta de venta u Orden pasiva. La cuenta debe tener el activo que desea exchangear, y debe confiar en el emisor del activo que está intentando comprar.
Las órdenes en Stellar funcionan como órdenes límite en mercados tradicionales. Cuando una cuenta inicia una orden, esta se verifica contra el libro de órdenes existente para ese par de activos. Si la orden enviada es una orden marketable (para una orden límite de compra marketable, el precio límite está en o por encima del precio ask; para una orden límite de venta marketable, el precio límite está en o por debajo del precio bid), se cumple al precio existente de la orden para la cantidad disponible a ese precio. Si la orden no es marketable (es decir, no cruza una orden existente), la orden se guarda en el libro de órdenes hasta que sea consumida por otra orden, consumida por un pago por ruta, o cancelada por la cuenta que creó la orden.
Cada orden constituye una obligación de venta para el activo que se vende y una obligación de compra para el activo que se compra. Estas obligaciones se almacenan en la cuenta (para lumens) o en la trustline (para otros activos) propiedad de la cuenta que crea la orden. Cualquier operación que cause que una cuenta no pueda cumplir con sus obligaciones — como enviar un saldo demasiado alto — fallará. Esto garantiza que cualquier orden en el libro de órdenes pueda ejecutarse en su totalidad.
Las órdenes se ejecutan con prioridad precio-tiempo, lo que significa que las órdenes se ejecutan primero según el precio; para órdenes colocadas al mismo precio, se da prioridad a la orden que se ingresó antes y se ejecuta antes que la más reciente.
Precio y operaciones
Cada orden en Stellar se cotiza con un precio asociado y está representada como una proporción de los dos activos en la orden, siendo uno el “activo cotizado” y el otro el “activo base”. Esto asegura que no haya pérdida de precisión al representar el precio de la orden (en lugar de almacenar la fracción como un número de punto flotante).
Los precios se especifican como un par denominador con ambos componentes de la fracción representados como enteros con signo de 32 bits. El numerador se considera el activo base, y el denominador se considera el activo cotizado. Al expresar un precio de “Activo A en términos de Activo B”, la cantidad de B es el denominador (y por tanto el activo cotizado), y A es el numerador (y por tanto el activo base). Por regla general, es correcto pensar en el activo base que se está comprando/vendiendo (en términos del activo cotizado).
Gestionar oferta de compra
Al crear una orden de compra en Stellar mediante la operación Gestionar oferta de compra, el precio se especifica como 1 unidad de la moneda base (el activo que se compra), en términos del activo cotizado (el activo que se vende). Por ejemplo, si compras 100 XLM a cambio de 20 USD, especificarías el precio como 100, que equivale a 5 XLM por 1 USD (o $.20 por XLM).
Gestionar oferta de venta
Al crear una orden de venta en Stellar mediante la operación Gestionar oferta de venta, el precio se especifica como 1 unidad de la moneda base (el activo que se vende), en términos del activo cotizado (el activo que se compra). Por ejemplo, si vendes 100 XLM a cambio de 40 USD, especificarías el precio como 100, que equivale a 2.5 XLM por 1 USD (o $.40 por XLM).
Orden pasiva
Las órdenes pasivas permiten que los mercados tengan un spread cero. Si quieres exchangear USD de anchor A por USD de anchor B a un precio 1:1, puedes crear dos órdenes pasivas para que las dos órdenes no se ejecuten entre sí.
Una orden pasiva es una orden que no se ejecuta contra una orden contraria marketable con el mismo precio. Solo se ejecutará si los precios no son iguales. Por ejemplo, si la mejor orden para comprar BTC por XLM tiene un precio de 100XLM/BTC, y haces una oferta pasiva para vender BTC a 100XLM/BTC, tu oferta pasiva no toma esa oferta existente. Si en cambio haces una oferta pasiva para vender BTC a 99XLM/BTC, cruzaría la oferta existente y se ejecutaría a 100XLM/BTC.
Una cuenta puede colocar una orden de venta pasiva mediante la operación Crear oferta pasiva de venta.
Comisiones
El precio de la orden que estableces es independiente de la comisión que pagas por enviar esa orden en una transacción. Las comisiones siempre se pagan en XLM, y las especificas como un parámetro separado al enviar la orden a la red.
Para saber más sobre tarifas de transacción, consulta nuestra sección sobre tarifas.
Fondos de Liquidez
Los Fondos de Liquidez permiten la creación automática de mercados en la red Stellar. La liquidez se refiere a la facilidad y eficiencia con que un activo puede ser convertido en otro.
Creadores de Mercado Automatizados (AMMs)
En lugar de depender de las órdenes de compra y venta de los exchanges descentralizados, los AMMs mantienen activos líquidos dentro del ecosistema 24/7 usando fondos de liquidez.
Los creadores de mercado automatizados proporcionan liquidez usando una ecuación matemática. Los AMMs retienen dos activos diferentes en un fondo de liquidez, y las cantidades de esos activos (o reservas) son entradas para esa ecuación (Activo A * Activo B = k). Si un AMM tiene más reservas de los activos, los precios de activos se mueven menos en respuesta a una operación.
Fijación de precios en AMM
Los AMMs están dispuestos a hacer algunas operaciones y no a hacer otras. Por ejemplo, si 1 EUR = 1.17 USD, entonces el AMM podría estar dispuesto a vender 1 EUR por 1.18 USD y no dispuesto a vender 1 EUR por 1.16 USD. Para determinar qué operaciones son aceptables, el AMM aplica un invariante. Existen muchos invariantes posibles, y Stellar aplica un invariante de producto constante y por eso es conocido como un creador de mercado de producto constante. Esto significa que los AMMs en Stellar nunca deben permitir que el producto de las reservas disminuya.
Por ejemplo, supón que las reservas actuales en el fondo de liquidez son 1000 EUR y 1170 USD, lo que implica un producto de 1,170,000. Vender 1 EUR por 1.18 USD sería aceptable porque dejaría reservas de 999 EUR y 1171.18 USD, lo que implica un producto de 1,170,008.82. Pero vender 1 EUR por 1.16 USD no sería aceptable porque dejaría reservas de 999 EUR y 1171.16 USD, lo que implica un producto de 1,169,988.84.
Los AMMs deciden tasas de exchange basándose en la proporción de reservas en el fondo de liquidez. Si esta proporción es diferente de la tasa de exchange verdadera, los arbitrajistas entrarán y tradearán con el AMM a un precio favorable. Esta operación de arbitraje mueve la proporción de reservas de vuelta hacia la tasa de exchange verdadera.
Los AMMs cobran comisiones en cada operación, que es un porcentaje fijo de la cantidad comprada por el AMM. Por ejemplo, si un creador de mercado automatizado vende 100 EUR por 118 USD, la comisión se cobra sobre los USD. La comisión es de 30 bps, que equivale a 0.30%. Si realmente quisieras hacer esta operación, tendrías que pagar aproximadamente 118.355 USD por 100 EUR. El creador de mercado automatizado incorpora las comisiones en el invariante de producto constante, por lo que en realidad, el producto de las reservas crece después de cada operación.
Participación en fondos de liquidez
Cualquier participante elegible puede depositar activos en un fondo de liquidez y, a cambio, recibir participaciones del fondo que representan su propiedad sobre ese activo. Si hay 150 participaciones totales en el fondo y un usuario posee 30, tiene derecho a retirar el 20% del activo del fondo en cualquier momento.
Las participaciones del fondo son similares a otros activos en Stellar, pero no pueden transferirse. Solo puedes aumentar el número de participaciones que posees depositando en un fondo de liquidez con LiquidityPoolDepositOp
y disminuir el número de participaciones retirando del fondo con LiquidityPoolWithdrawOp
.
Una participación del fondo tiene dos representaciones. La representación completa se usa con ChangeTrustOp
, y la representación en hash se usa en todos los demás casos. Al construir la representación del activo de una participación del fondo, los activos deben estar en orden lexicográfico. Por ejemplo, A-B está en el orden correcto, pero B-A no. Esto resulta en una representación canónica de una participación del fondo.
Los AMMs cobran una comisión en todas las operaciones y los participantes en el fondo de liquidez reciben una parte proporcional de la comisión según su participación en los activos del fondo. Los participantes recogen estas comisiones cuando retiran sus activos del fondo. La tasa de comisión en Stellar es de 30 bps, que equivale a 0.30%. Estas comisiones son completamente independientes de las comisiones de la red.
Trustlines
Los usuarios necesitan establecer trustlines a tres activos diferentes para participar en un fondo de liquidez: ambos activos de reserva (a menos que uno de ellos sea XLM) y la participación del fondo en sí.
Una cuenta necesita una trustline para cada participación del fondo que quiera poseer. No es posible depositar en un fondo de liquidez sin una trustline para la participación del fondo correspondiente. Las trustlines de participaciones de fondos difieren de las trustlines de otros activos en algunos aspectos:
- Una trustline de participación de fondo no puede ser creada a menos que la cuenta ya tenga trustlines que estén autorizadas o autorizadas para mantener responsabilidades para los activos en el fondo de liquidez. Consulta más abajo para información adicional sobre cómo la autorización impacta las trustlines de participaciones de fondos.
- Una trustline de participación de fondo requiere 2 reservas base en lugar de 1. Por ejemplo, una cuenta (2 reservas base) con una trustline para el activo A (1 reserva base), una trustline para el activo B (1 reserva base), y una trustline para la participación A-B del fondo (2 reservas base) tendría un requerimiento de reservas de 6 reservas base.
Autorización
Las trustlines de participaciones de fondo no pueden autorizarse o desautorizarse de forma independiente. En cambio, la autorización de una trustline de participación de fondo se deriva de las trustlines para los activos en el fondo de liquidez. Este diseño es necesario porque un fondo de liquidez puede contener activos de dos emisores diferentes, y ambos emisores deben tener voz para decidir si la trustline de participación del fondo está autorizada.
Hay varias posibilidades respecto a la autorización. El comportamiento de la trustline de participación A-B se determina según la siguiente tabla:
ESCENARIO | COMPORTAMIENTO |
---|---|
Las trustlines para A y B están totalmente autorizadas | Sin restricciones para depósito y retirada de fondos |
La trustline para A está totalmente autorizada pero la trustline para B está autorizada para mantener responsabilidades | Las trustlines para A y B están autorizadas para mantener responsabilidades |
La trustline para B está totalmente autorizada pero la trustline para A está autorizada para mantener responsabilidades | Las trustlines para A y B están autorizadas para mantener responsabilidades |
Las trustlines para A y B están autorizadas para mantener responsabilidades | Las trustlines para A y B están autorizadas para mantener responsabilidades |
La trustline para A no está autorizada o no existe | La trustline de participación del fondo no existe |
La trustline para B no está autorizada o no existe | La trustline de participación del fondo no existe |
Si el emisor de A o B revoca la autorización, entonces la cuenta retirará automáticamente de todos los fondos de liquidez que contengan ese activo y esas trustlines de participación serán eliminadas. Decimos que estas participaciones han sido redimidas. Por ejemplo, si la cuenta participa en los fondos A-B, A-C y B-C, y el emisor de A revoca la autorización, la cuenta redimirá en A-B y A-C pero no en B-C. Para cada trustline de participación redimida, se creará un Balance Reclamable para cada activo contenido en el fondo si hay un saldo a retirar y el redimidor no es el emisor de ese activo. El reclamante del Balance Reclamable será el propietario de la trustline de participación eliminada, y el patrocinador del Balance Reclamable será el patrocinador de la trustline de participación eliminada. El BalanceID de cada Balance Reclamable es el hash SHA-256 del revokeID
.
Operaciones
Existen dos operaciones que facilitan la participación en un fondo de liquidez: LiquidityPoolDeposit
y LiquidityPoolWithdraw
. Usa LiquidityPoolDeposit
para empezar a proveer liquidez al mercado. Usa LiquidityPoolWithdraw
para dejar de proveer liquidez al mercado.
Sin embargo, los usuarios no necesitan participar en el fondo para aprovechar lo que ofrece: una forma fácil de exchangear dos activos. Para eso, solo utiliza PathPaymentStrictReceive
o PathPaymentStrictSend
. Si tu aplicación ya está usando pagos por rutas, entonces no necesitas cambiar nada para que los usuarios aprovechen los precios disponibles en los fondos de liquidez.
Ejemplos
Aquí cubriremos la participación básica en fondos de liquidez y cómo consultarlos.
Preámbulo
Para todos los siguientes ejemplos, trabajaremos con tres cuentas de Testnet con fondos. Si quieres seguir el ejemplo, genera algunos keypairs y fínáncialos mediante el friendbot.
El siguiente código prepara las cuentas y define algunas funciones auxiliares. Estas deberían ser familiares si has probado otros ejemplos como clawbacks.
- JavaScript
- Python
const sdk = require("stellar-sdk");
const BigNumber = require("bignumber.js");
let server = new sdk.Server("https://horizon-testnet.stellar.org");
/// Helps simplify creating & signing a transaction.
function buildTx(source, signer, ...ops) {
let tx = new sdk.TransactionBuilder(source, {
fee: sdk.BASE_FEE,
networkPassphrase: sdk.Networks.TESTNET,
});
ops.forEach((op) => tx.addOperation(op));
tx = tx.setTimeout(30).build();
tx.sign(signer);
return tx;
}
/// Returns the given asset pair in "protocol order."
function orderAssets(A, B) {
return sdk.Asset.compare(A, B) <= 0 ? [A, B] : [B, A];
}
/// Returns all of the accounts we'll be using.
function getAccounts() {
return Promise.all(kps.map((kp) => server.loadAccount(kp.publicKey())));
}
const kps = [
"SBGCD73TK2PTW2DQNWUYZSTCTHHVJPL4GZF3GVZMCDL6GYETYNAYOADN",
"SAAQFHI2FMSIC6OFPWZ3PDIIX3OF64RS3EB52VLYYZBX6GYB54TW3Q4U",
"SCJWYFTBDMDPAABHVJZE3DRMBRTEH4AIC5YUM54QGW57NUBM2XX6433P",
].map((s) => sdk.Keypair.fromSecret(s));
// kp0 issues the assets
const kp0 = kps[0];
const [A, B] = orderAssets(
...[new sdk.Asset("A", kp0.publicKey()), new sdk.Asset("B", kp0.publicKey())],
);
/// Establishes trustlines and funds `recipientKps` for all `assets`.
function distributeAssets(issuerKp, recipientKps, ...assets) {
return server.loadAccount(issuerKp.publicKey()).then((issuer) => {
const ops = recipientKps
.map((recipientKp) =>
assets.map((asset) => [
sdk.Operation.changeTrust({
source: recipientKp.publicKey(),
limit: "100000",
asset: asset,
}),
sdk.Operation.payment({
source: issuerKp.publicKey(),
destination: recipientKp.publicKey(),
amount: "100000",
asset: asset,
}),
]),
)
.flat(2);
let tx = buildTx(issuer, issuerKp, ...ops);
tx.sign(...recipientKps);
return server.submitTransaction(tx);
});
}
function preamble() {
return distributeAssets(kp0, [kps[1], kps[2]], A, B);
}
from decimal import Decimal
from typing import List, Any, Dict
from stellar_sdk import *
server = Server("https://horizon-testnet.stellar.org")
# Preamble
def new_tx_builder(source: str) -> TransactionBuilder:
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE
base_fee = 100
source_account = server.load_account(source)
builder = TransactionBuilder(
source_account=source_account, network_passphrase=network_passphrase, base_fee=base_fee
).set_timeout(30)
return builder
# Returns the given asset pair in "protocol order."
def order_asset(a: Asset, b: Asset) -> List[Asset]:
return [a, b] if LiquidityPoolAsset.is_valid_lexicographic_order(a, b) else [b, a]
secrets = [
"SBGCD73TK2PTW2DQNWUYZSTCTHHVJPL4GZF3GVZMCDL6GYETYNAYOADN",
"SAAQFHI2FMSIC6OFPWZ3PDIIX3OF64RS3EB52VLYYZBX6GYB54TW3Q4U",
"SCJWYFTBDMDPAABHVJZE3DRMBRTEH4AIC5YUM54QGW57NUBM2XX6433P",
]
kps = [Keypair.from_secret(secret=secret) for secret in secrets]
# kp0 issues the assets
kp0 = kps[0]
asset_a, asset_b = order_asset(Asset("A", kp0.public_key), Asset("B", kp0.public_key))
def distribute_assets(
issuer_kp: Keypair, recipient_kp: Keypair, assets: List[Asset]
) -> Dict[str, Any]:
builder = new_tx_builder(issuer_kp.public_key)
for asset in assets:
builder.append_change_trust_op(
asset=asset, limit="100000", source=recipient_kp.public_key
).append_payment_op(
destination=recipient_kp.public_key,
asset=asset,
amount="100000",
source=issuer_kp.public_key,
)
tx = builder.build()
tx.sign(issuer_kp)
tx.sign(recipient_kp)
resp = server.submit_transaction(tx)
return resp
def preamble() -> None:
resp1 = distribute_assets(kp0, kps[1], [asset_a, asset_b])
resp2 = distribute_assets(kp0, kps[2], [asset_a, asset_b])
# ...
Aquí usamos distributeAssets()
para establecer trustlines y configurar saldos iniciales de dos activos personalizados (A
y B
, emitidos por kp0
) para dos cuentas (kp2
y kp3
). Para que alguien participe en el fondo, debe establecer trustlines hacia cada emisor de activo y hacia el activo de la participación del fondo (explicado abajo).
Observa la función auxiliar orderAssets()
aquí. Las operaciones relacionadas con fondos de liquidez se refieren al par de activos arbitrariamente como A
y B
; sin embargo, deben estar “ordenados” tal que A
< B
. Este orden lo define el protocolo, pero sus detalles no deberían ser relevantes (si tienes curiosidad, es esencialmente orden lexicográfico por tipo de activo, código y emisor). Podemos usar los métodos de comparación integrados en los SDKs (como Asset.compare
) para asegurarnos de pasarlos en el orden correcto y evitar errores.
Participación: Creación
Primero, vamos a crear un fondo de liquidez para el par de activos definido en el preámbulo. Esto implica establecer una trustline hacia el fondo en sí:
- JavaScript
- Python
const poolShareAsset = new sdk.LiquidityPoolAsset(
A,
B,
sdk.LiquidityPoolFeeV18,
);
function establishPoolTrustline(account, keypair, poolAsset) {
return server.submitTransaction(
buildTx(
account,
keypair,
sdk.Operation.changeTrust({
asset: poolAsset,
limit: "100000",
}),
),
);
}
pool_share_asset = LiquidityPoolAsset(asset_a=asset_a, asset_b=asset_b)
def establish_pool_trustline(source: Keypair, pool_asset: LiquidityPoolAsset) -> Dict[str, Any]:
tx = (
new_tx_builder(source.public_key)
.append_change_trust_op(asset=pool_asset, limit="100000")
.build()
)
tx.sign(source)
return server.submit_transaction(tx)
Esto permite que los participantes posean participaciones del fondo, lo que significa que ahora pueden realizar depósitos y retiradas de fondos.
Participación: Depósitos
Para trabajar con un fondo de liquidez, necesitas conocer su ID de antemano. Es un valor determinista, y solo puede existir un fondo de liquidez para un par de activos particular, así que puedes calcularlo localmente usando los parámetros del fondo.
- JavaScript
- Python
const poolId = sdk
.getLiquidityPoolId(
"constant_product",
poolShareAsset.getLiquidityPoolParameters(),
)
.toString("hex");
function addLiquidity(source, signer, poolId, maxReserveA, maxReserveB) {
const exactPrice = maxReserveA / maxReserveB;
const minPrice = exactPrice - exactPrice * 0.1;
const maxPrice = exactPrice + exactPrice * 0.1;
return server.submitTransaction(
buildTx(
source,
signer,
sdk.Operation.liquidityPoolDeposit({
liquidityPoolId: poolId,
maxAmountA: maxReserveA,
maxAmountB: maxReserveB,
minPrice: minPrice.toFixed(7),
maxPrice: maxPrice.toFixed(7),
}),
),
);
}
pool_id = pool_share_asset.liquidity_pool_id
def add_liquidity(
source: Keypair,
pool_id: str,
max_reserve_a: Decimal,
max_reserve_b: Decimal,
) -> dict[str, Any]:
exact_price = max_reserve_a / max_reserve_b
min_price = exact_price - exact_price * Decimal("0.1")
max_price = exact_price + exact_price * Decimal("0.1")
tx = (
new_tx_builder(source.public_key)
.append_liquidity_pool_deposit_op(
liquidity_pool_id=pool_id,
max_amount_a=f"{max_reserve_a:.7f}",
max_amount_b=f"{max_reserve_b:.7f}",
min_price=min_price,
max_price=max_price,
)
.build()
)
tx.sign(source)
return server.submit_transaction(tx)
Cuando depositas activos en un fondo de liquidez, necesitas definir los límites de precio aceptables. En la función anterior, permitimos un margen de error de +/-10% respecto al “precio spot”. Este margen no es una recomendación, solo se eligió para demostración.
También especificamos la cantidad máxima de cada reserva que estás dispuesto a depositar. Esto, junto con los precios mínimos y máximos, ayuda a definir los límites para el depósito, ya que siempre puede haber un cambio en la tasa de exchange entre enviar la operación y que ésta sea aceptada por la red.
Participación: Retiradas de fondos
Si posees participaciones de un fondo en particular, puedes retirar reservas de él. La estructura de la operación es similar a la del depósito:
- JavaScript
- Python
function removeLiquidity(source, signer, poolId, sharesAmount) {
return server
.liquidityPools()
.liquidityPoolId(poolId)
.call()
.then((poolInfo) => {
let totalShares = poolInfo.total_shares;
let minReserveA =
(sharesAmount / totalShares) * poolInfo.reserves[0].amount * 0.95;
let minReserveB =
(sharesAmount / totalShares) * poolInfo.reserves[1].amount * 0.95;
return server.submitTransaction(
buildTx(
source,
signer,
sdk.Operation.liquidityPoolWithdraw({
liquidityPoolId: poolId,
amount: sharesAmount,
minAmountA: minReserveA.toFixed(7),
minAmountB: minReserveB.toFixed(7),
}),
),
);
});
}
def remove_liquidity(
source: Keypair, pool_id: str, shares_amount: Decimal
) -> dict[str, Any]:
pool_info = server.liquidity_pools().liquidity_pool(pool_id).call()
total_shares = Decimal(pool_info["total_shares"])
min_reserve_a = (
shares_amount
/ total_shares
* Decimal(pool_info["reserves"][0]["amount"])
* Decimal("0.95")
) #
min_reserve_b = (
shares_amount
/ total_shares
* Decimal(pool_info["reserves"][1]["amount"])
* Decimal("0.95")
)
tx = (
new_tx_builder(source.public_key)
.append_liquidity_pool_withdraw_op(
liquidity_pool_id=pool_id,
amount=f"{shares_amount:.7f}",
min_amount_a=f"{min_reserve_a:.7f}",
min_amount_b=f"{min_reserve_b:.7f}",
)
.build()
)
tx.sign(source)
return server.submit_transaction(tx)
Aquí especificamos la cantidad mínima. Al igual que con un pago por ruta de recepción estricta, especificamos que no estamos dispuestos a recibir menos que esta cantidad de cada activo del fondo. Esto define efectivamente un precio mínimo de retirada.
Uniéndolo todo
Finalmente, podemos combinar estas piezas para simular cierta participación en un fondo de liquidez. Haremos que todos depositen cantidades crecientes en el fondo, luego un participante retirará sus participaciones. Entre cada paso, recuperaremos el precio spot.
- JavaScript
- Python
function main() {
return getAccounts()
.then((accounts) => {
return Promise.all(
kps.map((kp, i) => {
const acc = accounts[i];
const depositA = ((i + 1) * 1000).toString();
const depositB = ((i + 1) * 3000).toString(); // maintain a 1:3 ratio
return establishPoolTrustline(acc, kp, poolShareAsset)
.then(() => addLiquidity(acc, kp, poolId, depositA, depositB))
.then(() => getSpotPrice());
}),
).then(() => accounts);
})
.then((accounts) => {
// kp1 takes all his/her shares out
return server
.accounts()
.accountId(kps[1].publicKey())
.call()
.then(({ balances }) => {
let balance = 0;
balances.every((bal) => {
if (
bal.asset_type === "liquidity_pool_shares" &&
bal.liquidity_pool_id === poolId
) {
balance = bal.balance;
return false;
}
return true;
});
return balance;
})
.then((balance) =>
removeLiquidity(accounts[1], kps[1], poolId, balance),
);
})
.then(() => getSpotPrice());
}
function getSpotPrice() {
return server
.liquidityPools()
.liquidityPoolId(poolId)
.call()
.then((pool) => {
const [a, b] = pool.reserves.map((r) => r.amount);
const spotPrice = new BigNumber(a).div(b);
console.log(`Price: ${a}/${b} = ${spotPrice.toFormat(2)}`);
});
}
preamble().then(main);
def main():
deposit_a = Decimal(1000)
deposit_b = Decimal(3000) # maintain a 1:3 ratio
establish_pool_trustline(kps[1], pool_share_asset)
add_liquidity(kps[1], pool_id, deposit_a, deposit_b)
get_spot_price()
deposit_a = Decimal(2000)
deposit_b = Decimal(6000) # maintain a 1:3 ratio
establish_pool_trustline(kps[2], pool_share_asset)
add_liquidity(kps[2], pool_id, deposit_a, deposit_b)
get_spot_price()
# kp1 takes all his/her shares out
balance = 0
for b in server.accounts().account_id(kps[1].public_key).call()["balances"]:
if (
b["asset_type"] == "liquidity_pool_shares"
and b["liquidity_pool_id"] == pool_id
):
balance = Decimal(b["balance"])
break
if not balance:
raise
remove_liquidity(kps[1], pool_id, balance)
get_spot_price()
def get_spot_price():
resp = server.liquidity_pools().liquidity_pool(pool_id).call()
amount_a = resp["reserves"][0]["amount"]
amount_b = resp["reserves"][1]["amount"]
spot_price = Decimal(amount_a) / Decimal(amount_b)
print(f"Price: {amount_a}/{amount_b} = {spot_price:.7f}")
if __name__ == '__main__':
preamble()
main()
Vigilando la actividad del Fondo de Liquidez
Puedes acceder a las transacciones, operaciones y efectos relacionados con un fondo de liquidez si deseas seguir su actividad. Veamos cómo podemos rastrear los últimos depósitos en un fondo (supongamos que poolId
está definido como antes):
- JavaScript
- Python
server
.operations()
.forLiquidityPool(poolId)
.call()
.then((ops) => {
ops.records
.filter((op) => op.type == "liquidity_pool_deposit")
.forEach((op) => {
console.log("Reserves deposited:");
op.reserves_deposited.forEach((r) =>
console.log(` ${r.amount} of ${r.asset}`),
);
console.log(" for pool shares: ", op.shares_received);
});
});
def watch_liquidity_pool_activity():
for op in (
server.operations()
.for_liquidity_pool(liquidity_pool_id=pool_id)
.cursor("now")
.stream()
):
if op["type"] == "liquidity_pool_deposit":
print("Reserves deposited:")
for r in op["reserves_deposited"]:
print(f" {r['amount']} of {r['asset']}")
print(f" for pool shares: {op['shares_received']}")
# ...