Parte 4 de 4- > Adicionando e removendo liquidez
Acrescentar liquidez
Este vai ser um grande problema, mas um pouco menos complexo do que a troca de tokens!
A parte mais complexa de adicionar liquidez ao contrato Liquidity Baking é acertar a quantidade de tokens! Depois disso, será um passeio no parque.
Primeiro, vamos entender o que estamos fazendo aqui: o LB DEX dá a você a capacidade de fornecer um par de tokens (apenas 2 opções aqui, XTZ e tzBTC) como liquidez para habilitar o recurso de troca. Em troca, recebe tokens SIRS para representar o seu investimento. Esses tokens aumentam de valor ao longo do tempo, portanto, se você esperar o tempo suficiente, poderá obter lucro ao remover sua liquidez, o que será explicado abaixo.
A interface aqui vai se parecer muito com a interface para troca, com algumas diferenças importantes:
Como antes, temos 2 campos de entrada, mas desta vez não há botão do meio para alternar entre os 2 tokens e as duas entradas são editáveis.
Ao inserir um número em um dos campos, o dapp deve calcular a quantidade correspondente do outro token, bem como a quantidade esperada em SIRS que será recebida.
Agora, vamos ver como tudo isso é feito!
Convertendo a entrada
Quando o usuário digita um número em um dos campos, a entrada despacha um novo evento para o componente de interface com o nome do token envolvido e a quantidade inserida. Esses dados serão lidos pela função saveInput
:
const saveInput = ev => {
const { token, val }: { token: token; val: number | null } = ev.detail;
...
}
Em seguida, introduziremos uma condição com base no token, porque os cálculos serão diferentes para converter uma quantidade de XTZ em tzBTC e vice-versa. Vamos começar com o XTZ:
if (token === "XTZ" && val && val > 0) {
inputXtz = val.toString();
let tzbtcAmount = addLiquidityTokenIn({
xtzIn: val * 10 ** 6,
xtzPool: $store.dexInfo.xtzPool,
tokenPool: $store.dexInfo.tokenPool
});
if (tzbtcAmount) {
inputTzbtc = tzbtcAmount.dividedBy(10 ** 8).toPrecision(6);
} else {
inputTzbtc = "";
}
...
}
A condição também inclui uma verificação do valor, pois não há necessidade de processá-lo se o valor for null
ou 0
.
O valor é convertido em uma sequência e armazenado na variável inputXtz
a ser usada posteriormente. O valor correspondente de tzBTC é calculado com a função addLiquidityTokenIn
, outra daquelas funções úteis para calcular valores de token diferentes para o LB DEX, aqui está a referência:
const addLiquidityTokenIn = (p: {
xtzIn: BigNumber | number;
xtzPool: BigNumber | number;
tokenPool: BigNumber | number;
}): BigNumber | null => {
const { xtzIn, xtzPool, tokenPool } = p;
let xtzIn_ = new BigNumber(0);
let xtzPool_ = new BigNumber(0);
let tokenPool_ = new BigNumber(0);
try {
xtzIn_ = new BigNumber(xtzIn);
xtzPool_ = creditSubsidy(xtzPool);
tokenPool_ = new BigNumber(tokenPool);
} catch (err) {
return null;
}
if (
xtzIn_.isGreaterThan(0) &&
xtzPool_.isGreaterThan(0) &&
tokenPool_.isGreaterThan(0)
) {
return ceilingDiv(xtzIn_.times(tokenPool_), xtzPool_);
} else {
return null;
}
};
Verificamos a saída de addLiquidityTokenIn
e atualizamos a variável inputTzbtc
.
Se o usuário inserir um valor em tzBTC, as etapas serão muito semelhantes para calcular o valor correspondente em XTZ:
else if (token === "tzBTC" && val && val > 0) {
inputTzbtc = val.toString();
let xtzAmount = tokenToXtzXtzOutput({
tokenIn: val * 10 ** 8,
xtzPool: $store.dexInfo.xtzPool,
tokenPool: $store.dexInfo.tokenPool
});
if (xtzAmount) {
inputXtz = xtzAmount.dividedBy(10 ** 6).toPrecision(8);
...
} else {
inputXtz = "";
}
}
Também precisamos verificar se o valor fornecido está correto, depois do que usamos a função tokenToXtzXtzOutput
para obter o valor correspondente de XTZ para criar um par válido e fornecer liquidez:
const tokenToXtzXtzOutput = (p: {
tokenIn: BigNumber | number;
xtzPool: BigNumber | number;
tokenPool: BigNumber | number;
}): BigNumber | null => {
const { tokenIn, xtzPool: _xtzPool, tokenPool } = p;
let xtzPool = creditSubsidy(_xtzPool);
let tokenIn_ = new BigNumber(0);
let xtzPool_ = new BigNumber(0);
let tokenPool_ = new BigNumber(0);
try {
tokenIn_ = new BigNumber(tokenIn);
xtzPool_ = new BigNumber(xtzPool);
tokenPool_ = new BigNumber(tokenPool);
} catch (err) {
return null;
}
if (
tokenIn_.isGreaterThan(0) &&
xtzPool_.isGreaterThan(0) &&
tokenPool_.isGreaterThan(0)
) {
let numerator = new BigNumber(tokenIn)
.times(new BigNumber(xtzPool))
.times(new BigNumber(998001));
let denominator = new BigNumber(tokenPool)
.times(new BigNumber(1000000))
.plus(new BigNumber(tokenIn).times(new BigNumber(999000)));
return numerator.dividedBy(denominator);
} else {
return null;
}
};
Depois que isso é calculado, armazenamos o resultado na variável inputXtz para uso posterior.
Cálculo do valor esperado de SIRS
Agora, temos que calcular a quantidade correspondente de SIRS que será criada se inputXtz
e inputTzbtc
são fornecidos como parâmetros para adicionar liquidez. A função addLiquidityLiquidityCreated
faz todo o trabalho duro para nós:
const addLiquidityLiquidityCreated = (p: {
xtzIn: BigNumber | number;
xtzPool: BigNumber | number;
totalLiquidity: BigNumber | number;
}): BigNumber | null => {
const { xtzIn, xtzPool, totalLiquidity } = p;
let xtzIn_ = new BigNumber(0);
let xtzPool_ = new BigNumber(0);
let totalLiquidity_ = new BigNumber(0);
try {
xtzIn_ = new BigNumber(xtzIn);
xtzPool_ = new BigNumber(xtzPool);
totalLiquidity_ = new BigNumber(totalLiquidity);
} catch (err) {
return null;
}
xtzPool_ = creditSubsidy(xtzPool_);
if (xtzIn_.isGreaterThan(0) && xtzPool_.isGreaterThan(0)) {
if (totalLiquidity_.isEqualTo(0)) {
return new BigNumber(xtzIn)
.times(new BigNumber(totalLiquidity))
.dividedBy(new BigNumber(xtzPool));
} else if (totalLiquidity_.isGreaterThan(0)) {
return new BigNumber(xtzIn)
.times(new BigNumber(totalLiquidity))
.dividedBy(new BigNumber(xtzPool));
}
return null;
} else {
return null;
}
};
Esta função usa 3 parâmetros:
o valor do XTZ que você deseja adicionar como liquidez
o estado atual do pool XTZ
o montante total de liquidez disponível no contrato ( ou seja, os tokens do SIRS )
Ele produzirá a quantidade de SIRS criada após a transação. Esse valor é armazenado na variável sirsOutput
a ser exibida na interface.
Enviando os tokens
Enviando os tokens
Depois de calcularmos todos os valores necessários para adicionar liquidez ao contrato de Liquidity Baking, é hora de forjar a transação!
const addLiquidity = async () => {
try {
if (inputXtz && inputTzbtc && sirsOutput) {
addLiquidityStatus = TxStatus.Loading;
store.updateToast(
true,
"Adding liquidity, waiting for confirmation..."
);
const tzbtcForLiquidity = Math.floor(
+inputTzbtc * 10 ** tzBTC.decimals
);
const lbContract = await $store.Tezos.wallet.at(dexAddress);
const tzBtcContract = await $store.Tezos.wallet.at(tzbtcAddress);
...
}
Primeiro, verificamos se os 3 valores que precisamos, as quantidades de XTZ, tzBTC e SIRS estão disponíveis. Se for o caso, atualizamos a interface do usuário alternando a variável addLiquidityStatus
para TxStatus.Loading
e exibindo um brinde simples com uma mensagem.
Depois disso, convertemos a quantidade de tzBTC que obtivemos em seu valor “real”, ou seja, o valor sem pontos decimais, conforme armazenado em seu contrato.
Então, criamos o ContractAbstraction
para o LB DEX e o ContractAbstraction
para o contrato tzBTC, pois interagiremos com ambos.
Nota: lembre-se, toda vez que seus usuários desejam usar o tzBTC com o LB DEX, a quantidade de tokens que serão usados precisa ser aprovada no nível do contrato tzBTC, que requer 3 operações diferentes.
Nesse ponto, você pode ter adivinhado que precisamos criar uma transação em lote, mas vamos fazê-lo de uma maneira diferente do capítulo anterior, para que você possa escolher da maneira que preferir:
const batch = $store.Tezos.wallet.batch([
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods.approve(dexAddress, 0).toTransferParams()
},
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods
.approve(dexAddress, tzbtcForLiquidity)
.toTransferParams()
},
{
kind: OpKind.TRANSACTION,
...lbContract.methodsObject
.addLiquidity({
owner: $store.userAddress,
minLqtMinted: sirsOutput,
maxTokensDeposited: tzbtcForLiquidity,
deadline: calcDeadline()
})
.toTransferParams(),
amount: +inputXtz
},
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods.approve(dexAddress, 0).toTransferParams()
}
]);
const batchOp = await batch.send();
await batchOp.confirmation();
No capítulo anterior, a transação em lote foi criada usando o método withContractCall
disponível no método batch
. Aqui, na verdade, passaremos um parâmetro para o método batch()
, uma matriz contendo vários objetos que representam uma operação.
A primeira operação:
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods.approve(dexAddress, 0).toTransferParams()
}
É a transação necessária para definir o valor do tzBTC aprovado para o LB DEX como zero.
A segunda operação:
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods
.approve(dexAddress, tzbtcForLiquidity)
.toTransferParams()
}
Define o valor do tzBTC aprovado para o contrato LB DEX.
A terceira operação:
{
kind: OpKind.TRANSACTION,
...lbContract
.methodsObject
.addLiquidity({
owner: $store.userAddress,
minLqtMinted: sirsOutput,
maxTokensDeposited: tzbtcForLiquidity,
deadline: calcDeadline()
})
.toTransferParams(),
amount: +inputXtz
}
É a operação addLiquidity
para fornecer o par de tokens ao contrato e receber tokens SIRS em troca. O ponto de entrada espera 4 parâmetros ( representados aqui como um objeto graças ao método methodsObject
):
o endereço da conta que receberá os tokens do SIRS
o valor mínimo de tokens SIRS que se espera receber
o montante de tzBTC depositado
o prazo
Nota: veja como a quantidade anexada de tez é passada para a operação como a última propriedade do objeto de operação. É importante colocá-lo depois
.toTransferParams()
ou seria substituído com a quantidade padrão de tez, que é zero.
A quarta operação:
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods.approve(dexAddress, 0).toTransferParams()
}
Redefine a quantidade permitida de tzBTC a ser usada pelo LB DEX para zero.
Então, como qualquer outra transação forjada através do Taquito, você chama o .send()
e .confirmation()
no objeto de operação para aguardar uma confirmação.
Depois que a transação for confirmada, você limpará a interface do usuário antes de buscar os novos saldos do XTZ, tzBTC e SIRS.
Se a transação falhar, você atualiza a interface do usuário e fornece feedback visual aos usuários:
addLiquidityStatus = TxStatus.Error;
store.updateToast(true, "An error has occurred");
Depois de todas essas etapas, você pode redefinir a interface para o estado anterior, talvez o usuário queira adicionar mais liquidez!
setTimeout(() => {
addLiquidityStatus = TxStatus.NoTransaction;
store.showToast(false);
}, 3000);
E é isso! Seus usuários agora têm a capacidade de adicionar Liquidity Baking DEX e investir seus XTZ e tzBTC.
Remoção de liquidez
A remoção da liquidez do contrato de Liquidity Baking é sem dúvida a mais fácil de todas as tarefas realizadas por nossa interface. A interface precisa apenas de uma entrada para receber a quantidade de SIRS que o usuário deseja desembrulhar para obter XTZ e tzBTC.
O dapp calculará a quantidade correspondente de XTZ e tzBTC que se espera receber pela quantidade de SIRS no campo de entrada.
No arquivo lbUtils.ts
, você encontrará a função removeLiquidityXtzTzbtcOut
para calcular esses valores:
const outputRes = removeLiquidityXtzTzbtcOut({
liquidityBurned: val,
totalLiquidity: $store.dexInfo.lqtTotal.toNumber(),
xtzPool: $store.dexInfo.xtzPool.toNumber(),
tokenPool: $store.dexInfo.tokenPool.toNumber()
});
if (outputRes) {
const { xtzOut, tzbtcOut } = outputRes;
xtzOutput = xtzOut
.decimalPlaces(0, 1)
.dividedBy(10 ** 6)
.decimalPlaces(6)
.toNumber();
tzbtcOutput = tzbtcOut
.decimalPlaces(0, 1)
.dividedBy(10 ** 8)
.decimalPlaces(8)
.toNumber();
}
Esta função usa um objeto como parâmetro com 4 propriedades:
liquidityBurned
- > a quantidade de SIRS a queimartotalLiquidity
- > o valor total de tokens SIRS no contratoxtzPool
- > o valor total de tokens XTZ no contratotokenPool
- > o valor total de tkens tzBTC no contrato
Se a função foi capaz de calcular os valores de XTZ e tzBTC, eles serão retornados em um objeto, caso contrário uma resposta null
será devolvida. Depois disso, esses valores podem ser exibidos na interface.
Agora, vamos ver como interagir com o ponto de entrada do contrato removeLiquidity
. Primeiro, criamos uma função removeLiquidity
dentro do nosso código TypeScript que será acionado quando o usuário clicar no botão Remove liquidity
:
const removeLiquidity = async () => {
try {
if (inputSirs) {
removeLiquidityStatus = TxStatus.Loading;
store.updateToast(
true,
"Removing liquidity, waiting for confirmation..."
);
const lbContract = await $store.Tezos.wallet.at(dexAddress);
...
};
A função começa verificando se há uma quantidade de SIRS que foi inserida antes que a ação de liquidez de remoção fosse acionada. Se for esse o caso, o removeLiquidityStatus
está definido para o loading
, para atualizar a interface do usuário e informar ao usuário que a transação está se preparando. Um ícone de carregamento também será exibido.
Em seguida, um ContractAbstraction
é criado para o LB DEX para interagir com ele do Taquito.
Agora, podemos forjar a transação real:
const op = await lbContract.methodsObject
.removeLiquidity({
to: $store.userAddress,
lqtBurned: inputSirs,
minXtzWithdrawn: Math.floor(xtzOutput * 10 ** XTZ.decimals),
minTokensWithdrawn: Math.floor(tzbtcOutput * 10 ** tzBTC.decimals),
deadline: calcDeadline()
})
.send();
await op.confirmation();
O ponto de entrada removeLiquidity
espera 5 parâmetros:
to
- > a conta que receberá o XTZ e o tzBTClqtBurned
- > a quantidade de SIRS a queimarminXtzWithdrawn
- > o montante mínimo de XTZ que se espera receberminTokensWithdrawn
- > o montante mínimo de tzBTC que se espera receberdeadline
- > assim como o outro ponto de entrada, deve ser fornecido um prazo para a transação
Após a emissão da transação, chamamos o .confirmation()
no objeto de operação retornado por Taquito.
Se a transação foi bem-sucedida, atualizamos a interface do usuário e redefinimos os valores do token para informar o usuário:
removeLiquidityStatus = TxStatus.Success;
inputSirs = "";
xtzOutput = 0;
tzbtcOutput = 0;
// Obtém os saldos do usuário de XTZ, tzBTC e SIRS
const res = await fetchBalances($store.Tezos, $store.userAddress);
if (res) {
store.updateUserBalance("XTZ", res.xtzBalance);
store.updateUserBalance("tzBTC", res.tzbtcBalance);
store.updateUserBalance("SIRS", res.sirsBalance);
} else {
store.updateUserBalance("XTZ", null);
store.updateUserBalance("tzBTC", null);
store.updateUserBalance("SIRS", null);
}
store.updateToast(true, "Liquidity successfully removed!");
Se a transação falhar, também atualizamos a interface do usuário de acordo:
removeLiquidityStatus = TxStatus.Error;
store.updateToast(true, "An error has occurred");
E é isso, os usuários agora têm a possibilidade de remover tokens SIRS e obter tokens XTZ e tzBTC em troca!
Conclusão
Você chegou até o final deste tutorial 🙂
Esse dapp muito simples introduziu muitos conceitos diferentes que são fundamentais para o desenvolvimento de aplicativos no Tezos, mas também para entender como o Tezos funciona, em geral.
Taquito é uma biblioteca incrível para desenvolver no Tezos, se você deseja criar ideias rapidamente ou deseja criar aplicativos descentralizados de full-stack. Ele fornece uma biblioteca principal com tudo o que você precisa para acessar a blockchain Tezos, interagir com contratos inteligentes e usar carteiras e vários pacotes menores para uso específico, por exemplo, leitura de metadados ou operações de lote.
Se você deseja criar um aplicativo de front-end, back-end ou até um aplicativo de desktop, desde que esteja usando JavaScript / NodeJS, poderá usar o Taquito!
Este tutorial também introduziu diferentes ferramentas que você pode precisar em sua jornada para desenvolver dapps no Tezos, o Beacon SDK para interagir com carteiras, a API TzKT para obter mais dados da blockchain, etc.
Embora este tutorial use o Svelte como framework de escolha, as habilidades que você aprendeu são transferíveis para outros framework, pois são baseadas em muitos dos mesmos conceitos (os ciclos de vida dos componentes são muito semelhantes, etc.) ele oferece tudo o que você precisa para criar incríveis dapps em Tezos e mal posso esperar para ver o que você construirá a seguir!
Este artigo foi escrito por Claude Barde e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Oldest comments (0)