Construire une dapp sur Tezos (édition 2023)
Partie 4 sur 4-> Ajouter et supprimer des liquidités
Celui-ci va être gros, mais un peu moins impliqué que l'échange de jetons !
La partie la plus complexe de l'ajout de liquidité au contrat Liquidity Baking est d'obtenir les bons montants de jetons ! Après cela, ce sera une promenade dans le parc.
Tout d'abord, comprenons ce que nous faisons ici : le LB DEX vous donne la possibilité de fournir une paire de jetons (seulement 2 choix ici, XTZ et tzBTC) comme liquidité pour activer la fonction d'échange. En échange, vous obtenez des jetons SIRS pour représenter votre investissement. Ces jetons prennent de la valeur avec le temps, donc si vous attendez assez longtemps, vous pouvez réaliser un profit lorsque vous retirez vos liquidités, ce qui sera expliqué ci-dessous.
L'interface ici va ressembler beaucoup à l'interface d'échange, avec quelques différences clés :
Comme avant, nous avons 2 champs de saisie, mais cette fois, il n'y a pas de bouton central pour basculer entre les 2 jetons et les deux entrées sont modifiables.
Lors de la saisie d'un nombre dans l'un des champs, la dapp doit calculer le montant correspondant de l'autre jeton, ainsi que le montant attendu dans SIRS qui sera reçu.
Maintenant, voyons comment tout cela est fait !
Conversion de l'entrée
Lorsque l'utilisateur va entrer un nombre dans l'un des champs, l'entrée enverra un nouvel événement au composant d'interface avec le nom du jeton impliqué et le montant qui a été entré. Ces données seront lues par la saveInputfonction :
const saveInput = ev => {
const { token, val }: { token: token; val: number | null } = ev.detail;
...
}
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 = "";
}
...
}
La valeur est convertie en chaîne et stockée dans la inputXtzvariable pour être utilisée ultérieurement. Le montant correspondant de tzBTC est calculé avec la addLiquidityTokenInfonction, une autre de ces fonctions utiles pour calculer différents montants de jetons pour le LB DEX, le voici pour référence :
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;
}
};
Si l'utilisateur saisit un montant en tzBTC, les étapes seront très similaires pour calculer le montant correspondant en 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 = "";
}
}
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;
}
};
Calcul du montant attendu de SIRS
Maintenant, nous devons calculer le montant correspondant de SIRS qui sera créé si inputXtzet inputTzbtcsont fournis comme paramètres pour ajouter de la liquidité. La addLiquidityLiquidityCreatedfonction fait tout le travail pour nous :
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;
}
};
- le montant de XTZ que vous souhaitez ajouter comme liquidité
- l'état actuel du pool XTZ
- le montant total de liquidité disponible dans le contrat (c'est-à-dire les jetons SIRS)
Envoi des jetons
Après avoir calculé toutes les valeurs dont nous avons besoin pour ajouter de la liquidité au contrat Liquidity Baking, il est temps de forger la transaction !
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);
...
}
Après cela, nous convertissons le montant de tzBTC que nous avons obtenu en sa valeur "réelle", c'est-à-dire la valeur sans décimales telle que stockée dans son contrat.
Ensuite, nous créons le ContractAbstractionpour le LB DEX et le ContractAbstractionpour le contrat tzBTC, car nous interagirons avec les deux.
Remarque : rappelez-vous, chaque fois que vos utilisateurs veulent utiliser tzBTC avec le LB DEX, la quantité de jetons qui sera utilisée doit être approuvée au niveau du contrat tzBTC, ce qui nécessite 3 opérations différentes.
À ce stade, vous avez peut-être deviné que nous devons créer une transaction par lots, mais faisons-le d'une manière différente du chapitre précédent, afin que vous puissiez choisir la manière que vous préférez :
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();
La première opération :
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods.approve(dexAddress, 0).toTransferParams()
}
La deuxième opération :
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods
.approve(dexAddress, tzbtcForLiquidity)
.toTransferParams()
}
La troisième opération :
{
kind: OpKind.TRANSACTION,
...lbContract
.methodsObject
.addLiquidity({
owner: $store.userAddress,
minLqtMinted: sirsOutput,
maxTokensDeposited: tzbtcForLiquidity,
deadline: calcDeadline()
})
.toTransferParams(),
amount: +inputXtz
}
- l'adresse du compte qui recevra les jetons SIRS
- le montant minimum de jetons SIRS que l'on s'attend à recevoir
- le montant de tzBTC déposé
- la date limite
La quatrième opération :
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods.approve(dexAddress, 0).toTransferParams()
}
Ensuite, comme toute autre transaction falsifiée via Taquito, vous appelez .send()et .confirmation()sur l'objet d'opération pour attendre une confirmation.
Une fois la transaction confirmée, vous effacez l'interface utilisateur avant de récupérer les nouveaux soldes de XTZ, tzBTC et SIRS.
Si la transaction a échoué, vous mettez à jour l'interface utilisateur et fournissez un retour visuel aux utilisateurs :
addLiquidityStatus = TxStatus.Error;
store.updateToast(true, "An error has occurred");
setTimeout(() => {
addLiquidityStatus = TxStatus.NoTransaction;
store.showToast(false);
}, 3000);
Retirer de la liquidité
La suppression de la liquidité du contrat Liquidity Baking est sans doute la plus simple de toutes les tâches accomplies par notre interface. L'interface n'a besoin que d'une seule entrée pour recevoir la quantité de SIRS que l'utilisateur souhaite déballer pour obtenir XTZ et tzBTC.
Le dapp calculera ensuite le montant correspondant de XTZ et de tzBTC qui devrait être reçu pour le montant de SIRS dans le champ de saisie.
Dans le lbUtils.tsfichier, vous trouverez la removeLiquidityXtzTzbtcOutfonction pour calculer ces montants :
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();
}
liquidityBurned-> la quantité de SIRS à brûlertotalLiquidity-> le montant total de jetons SIRS dans le contratxtzPool-> le montant total de jetons XTZ dans le contrattokenPool-> le montant total de jetons tzBTC dans le contrat
Voyons maintenant comment interagir avec le removeLiquiditypoint d'entrée du contrat. Premièrement, nous créons une removeLiquidityfonction dans notre code TypeScript qui sera déclenchée lorsque l'utilisateur cliquera sur le Remove liquiditybouton :
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);
...
};
Ensuite, un ContractAbstractionest créé pour le LB DEX afin d'interagir avec lui depuis Taquito.
Maintenant, nous pouvons falsifier la transaction réelle :
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();
to-> le compte qui recevra les XTZ et tzBTClqtBurned-> la quantité de SIRS à brûlerminXtzWithdrawn-> le montant minimum de XTZ que l'on s'attend à recevoirminTokensWithdrawn-> le montant minimum de tzBTC que l'on s'attend à recevoirdeadline-> tout comme l'autre point d'entrée, un délai pour la transaction doit être fourni
Si la transaction a réussi, nous mettons à jour l'interface utilisateur et réinitialisons les valeurs de jeton pour informer l'utilisateur :
removeLiquidityStatus = TxStatus.Success;
inputSirs = "";
xtzOutput = 0;
tzbtcOutput = 0;
// fetches user's XTZ, tzBTC and SIRS balances
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!");
removeLiquidityStatus = TxStatus.Error;
store.updateToast(true, "An error has occurred");
Conclusion
Vous avez réussi jusqu'à la fin de ce tutoriel
Cette application très simple a introduit de nombreux concepts différents qui sont fondamentaux pour développer des applications sur Tezos, mais aussi pour comprendre le fonctionnement de Tezos en général.
Taquito est une bibliothèque incroyable à développer sur Tezos, que vous souhaitiez prototyper rapidement des idées ou créer des applications décentralisées complètes. Il fournit une bibliothèque principale avec tout ce dont vous avez besoin pour lire à partir de la blockchain Tezos, interagir avec des contrats intelligents et utiliser des portefeuilles, ainsi que plusieurs packages plus petits pour une utilisation spécifique, par exemple, la lecture de métadonnées de jeton ou des opérations par lots.
Que vous souhaitiez créer une application front-end, un back-end ou même une application de bureau, tant que vous utilisez JavaScript/NodeJS, vous pourrez utiliser Taquito !
Ce didacticiel a également présenté différents outils dont vous pourriez avoir besoin lors de votre parcours de développement de dapps sur Tezos, le SDK Beacon pour interagir avec les portefeuilles, l'API TzKT pour obtenir plus de données de la blockchain, etc.
Bien que ce didacticiel utilise Svelte comme framework de choix, les compétences que vous avez acquises sont transférables à d'autres frameworks car elles sont basées sur un grand nombre des mêmes concepts (les cycles de vie des composants sont très similaires, etc.). Il vous donne tout ce dont vous avez besoin pour construire dapps incroyables sur Tezos et j'ai hâte de voir ce que vous allez construire ensuite !
![Qu'est-ce qu'une liste liée, de toute façon? [Partie 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































