สร้าง dapp บน Tezos (ฉบับปี 2023)
ส่วนที่ 4 จาก 4-> การเพิ่มและลบสภาพคล่อง
สิ่งนี้จะเป็นเรื่องใหญ่ แต่มีส่วนร่วมน้อยกว่าการแลกเปลี่ยนโทเค็นเล็กน้อย!
ส่วนที่ซับซ้อนที่สุดเกี่ยวกับการเพิ่มสภาพคล่องในสัญญา Liquidity Baking คือการได้รับจำนวนโทเค็นที่ถูกต้อง! หลังจากนั้นจะเป็นการเดินเล่นในสวนสาธารณะ
อันดับแรก มาทำความเข้าใจว่าเรากำลังทำอะไรที่นี่: LB DEX ช่วยให้คุณสามารถจัดหาโทเค็นคู่หนึ่ง (มีเพียง 2 ตัวเลือกในที่นี้ XTZ และ tzBTC) เป็นสภาพคล่องเพื่อเปิดใช้งานคุณลักษณะการแลกเปลี่ยน ในการแลกเปลี่ยน คุณจะได้รับโทเค็น SIRS เพื่อเป็นตัวแทนการลงทุนของคุณ โทเค็นเหล่านี้มีมูลค่าเพิ่มขึ้นเมื่อเวลาผ่านไป ดังนั้นหากคุณรอนานพอ คุณสามารถทำกำไรได้เมื่อคุณลบสภาพคล่องออก ซึ่งจะอธิบายด้านล่าง
อินเทอร์เฟซที่นี่จะดูคล้ายกับอินเทอร์เฟซสำหรับการแลกเปลี่ยน โดยมีข้อแตกต่างที่สำคัญบางประการ:
เหมือนก่อนหน้านี้ เรามีช่องอินพุต 2 ช่อง แต่คราวนี้ไม่มีปุ่มตรงกลางสำหรับสลับระหว่าง 2 โทเค็น และอินพุตทั้งสองสามารถแก้ไขได้
เมื่อป้อนตัวเลขในช่องใดช่องหนึ่ง dapp จะต้องคำนวณจำนวนที่สอดคล้องกันของโทเค็นอื่นๆ รวมถึงจำนวนที่คาดว่าจะได้รับใน SIRS
ทีนี้มาดูกันว่าทั้งหมดนั้นทำอย่างไร!
การแปลงอินพุต
เมื่อผู้ใช้กำลังจะป้อนตัวเลขในฟิลด์ใดฟิลด์หนึ่ง การป้อนข้อมูลจะส่งเหตุการณ์ใหม่ไปยังส่วนประกอบอินเทอร์เฟซพร้อมกับชื่อของโทเค็นที่เกี่ยวข้องและจำนวนเงินที่ป้อน ข้อมูลนี้จะถูกอ่านโดยsaveInputฟังก์ชัน:
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 = "";
}
...
}
ค่าจะถูกส่งไปยังสตริงและเก็บไว้ในinputXtzตัวแปรเพื่อใช้ในภายหลัง จำนวน tzBTC ที่สอดคล้องกันนั้นคำนวณด้วยaddLiquidityTokenInฟังก์ชัน ซึ่งเป็นอีกหนึ่งฟังก์ชันที่มีประโยชน์ในการคำนวณจำนวนโทเค็นต่างๆ สำหรับ LB DEX นี่คือข้อมูลอ้างอิง:
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;
}
};
หากผู้ใช้ป้อนจำนวนเงินใน tzBTC ขั้นตอนจะคล้ายกันมากในการคำนวณจำนวนเงินที่สอดคล้องกันใน 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;
}
};
การคำนวณจำนวน SIRS ที่คาดหวัง
ตอนนี้ เราต้องคำนวณจำนวน SIRS ที่สอดคล้องกันซึ่งจะถูกสร้างขึ้นหากinputXtzและinputTzbtcถูกระบุเป็นพารามิเตอร์เพื่อเพิ่มสภาพคล่อง ฟังaddLiquidityLiquidityCreatedก์ชั่นทำงานหนักทั้งหมดสำหรับเรา:
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;
}
};
- จำนวน XTZ ที่คุณต้องการเพิ่มเป็นสภาพคล่อง
- สถานะปัจจุบันของพูล XTZ
- จำนวนสภาพคล่องทั้งหมดที่มีอยู่ในสัญญา (เช่น โทเค็น SIRS)
การส่งโทเค็น
หลังจากที่เราคำนวณมูลค่าทั้งหมดที่เราต้องการเพื่อเพิ่มสภาพคล่องให้กับสัญญา Liquidity Baking แล้ว ก็ถึงเวลาที่จะปลอมแปลงธุรกรรม!
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);
...
}
หลังจากนั้น เราจะแปลงจำนวน tzBTC ที่เราได้รับเป็นมูลค่า "จริง" นั่นคือมูลค่าที่ไม่มีจุดทศนิยมตามที่จัดเก็บไว้ในสัญญา
จากนั้น เราสร้างContractAbstractionสำหรับ LB DEX และContractAbstractionสำหรับสัญญา tzBTC เนื่องจากเราจะโต้ตอบกับทั้งสองอย่าง
หมายเหตุ: โปรดจำไว้ว่า ทุกครั้งที่ผู้ใช้ของคุณต้องการใช้ tzBTC กับ LB DEX จำนวนโทเค็นที่จะใช้จะต้องได้รับการอนุมัติในระดับสัญญา tzBTC ซึ่งต้องมีการดำเนินการ 3 แบบที่แตกต่างกัน
ณ จุดนี้ คุณอาจเดาได้ว่าเราต้องสร้างธุรกรรมเป็นชุด แต่ลองทำด้วยวิธีที่แตกต่างจากบทที่แล้ว เพื่อที่คุณจะได้เลือกวิธีที่คุณต้องการ:
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();
การดำเนินการครั้งแรก:
{
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
}
- ที่อยู่ของบัญชีที่จะรับโทเค็น SIRS
- จำนวนโทเค็น SIRS ขั้นต่ำที่คาดว่าจะได้รับ
- จำนวน tzBTC ที่ฝาก
- กำหนดเส้นตาย
การดำเนินการที่สี่:
{
kind: OpKind.TRANSACTION,
...tzBtcContract.methods.approve(dexAddress, 0).toTransferParams()
}
จากนั้น เช่นเดียวกับธุรกรรมอื่นๆ ปลอมแปลงผ่าน Taquito คุณเรียก.send()และ.confirmation()ไปที่วัตถุการดำเนินการเพื่อรอการยืนยันหนึ่งครั้ง
เมื่อธุรกรรมได้รับการยืนยัน คุณจะล้าง UI ก่อนที่จะเรียกยอดคงเหลือใหม่ของ XTZ, tzBTC และ SIRS
หากการทำธุรกรรมล้มเหลว คุณจะอัปเดต UI และให้ข้อเสนอแนะแก่ผู้ใช้:
addLiquidityStatus = TxStatus.Error;
store.updateToast(true, "An error has occurred");
setTimeout(() => {
addLiquidityStatus = TxStatus.NoTransaction;
store.showToast(false);
}, 3000);
ขจัดสภาพคล่อง
การนำสภาพคล่องออกจากสัญญา Liquidity Baking นั้นเป็นงานที่ง่ายที่สุดในบรรดางานทั้งหมดที่ทำได้โดยอินเทอร์เฟซของเรา อินเทอร์เฟซต้องการเพียงหนึ่งอินพุตเพื่อรับจำนวน SIRS ที่ผู้ใช้ต้องการแกะเพื่อรับ XTZ และ tzBTC
จากนั้น Dapp จะคำนวณจำนวน XTZ และ tzBTC ที่สอดคล้องกันที่คาดว่าจะได้รับสำหรับจำนวน SIRS ในช่องป้อนข้อมูล
ในlbUtils.tsไฟล์ คุณจะพบremoveLiquidityXtzTzbtcOutฟังก์ชันสำหรับคำนวณจำนวนเหล่านี้:
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-> จำนวน SIRS ที่จะเผาผลาญtotalLiquidity-> จำนวนโทเค็น SIRS ทั้งหมดในสัญญาxtzPool-> จำนวนโทเค็น XTZ ทั้งหมดในสัญญาtokenPool-> จำนวนโทเค็น tzBTC ทั้งหมดในสัญญา
ตอนนี้เรามาดูวิธีการโต้ตอบกับremoveLiquidityจุดเริ่มต้นของสัญญา ขั้นแรก เราสร้างremoveLiquidityฟังก์ชันภายในโค้ด TypeScript ซึ่งจะถูกเรียกใช้เมื่อผู้ใช้คลิกที่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 ContractAbstractionถูกสร้างขึ้นสำหรับ LB DEX เพื่อโต้ตอบกับ Taquito
ตอนนี้ เราสามารถปลอมธุรกรรมจริงได้:
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-> บัญชีที่จะได้รับ XTZ และ tzBTClqtBurned-> จำนวน SIRS ที่จะเผาผลาญminXtzWithdrawn-> จำนวนขั้นต่ำของ XTZ ที่คาดว่าจะได้รับminTokensWithdrawn-> จำนวนขั้นต่ำของ tzBTC ที่คาดว่าจะได้รับdeadline-> เช่นเดียวกับจุดเริ่มต้นอื่น ๆ จะต้องระบุกำหนดเวลาสำหรับการทำธุรกรรม
หากการทำธุรกรรมสำเร็จ เราจะอัปเดต UI และรีเซ็ตค่าโทเค็นเพื่อแจ้งให้ผู้ใช้ทราบ:
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");
บทสรุป
คุณทำจนจบบทช่วยสอนนี้แล้ว
Dapp ที่เรียบง่ายนี้แนะนำแนวคิดต่างๆ มากมายที่เป็นพื้นฐานในการพัฒนาแอปพลิเคชันบน Tezos แต่ยังรวมถึงการทำความเข้าใจว่า Tezos ทำงานอย่างไรโดยทั่วไป
Taquito เป็นไลบรารีที่น่าทึ่งสำหรับการพัฒนาบน Tezos ไม่ว่าคุณจะต้องการสร้างต้นแบบแนวคิดอย่างรวดเร็วหรือต้องการสร้างแอปพลิเคชันแบบกระจายศูนย์แบบฟูลสแตก มีไลบรารีหลักพร้อมทุกสิ่งที่คุณต้องการในการอ่านจาก Tezos blockchain โต้ตอบกับสัญญาอัจฉริยะและใช้กระเป๋าเงิน และแพ็คเกจขนาดเล็กจำนวนมากสำหรับการใช้งานเฉพาะ เช่น การอ่านข้อมูลเมตาของโทเค็นหรือการดำเนินการแบทช์
ไม่ว่าคุณจะต้องการสร้างแอปส่วนหน้า แอปส่วนหลัง หรือแม้แต่แอปเดสก์ท็อป ตราบใดที่คุณใช้ JavaScript/NodeJS คุณจะสามารถใช้ Taquito ได้!
บทช่วยสอนนี้ยังแนะนำเครื่องมือต่างๆ ที่คุณอาจต้องใช้ในการพัฒนา dapps บน Tezos, Beacon SDK เพื่อโต้ตอบกับกระเป๋าเงิน, TzKT API เพื่อรับข้อมูลเพิ่มเติมจากบล็อกเชน เป็นต้น
แม้ว่าบทช่วยสอนนี้จะใช้ Svelte เป็นเฟรมเวิร์กที่เลือก แต่ทักษะที่คุณเรียนรู้นั้นสามารถถ่ายโอนไปยังเฟรมเวิร์กอื่นๆ ได้เนื่องจากอิงตามแนวคิดเดียวกันจำนวนมาก (วงจรชีวิตของคอมโพเนนต์จะคล้ายกันมาก เป็นต้น) มันให้ทุกสิ่งที่คุณต้องการในการสร้าง Dapps ที่น่าทึ่งบน Tezos และฉันอดใจรอไม่ไหวแล้วว่าคุณจะสร้างอะไรต่อไป!





































![รายการที่เชื่อมโยงคืออะไร? [ส่วนที่ 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)