diff --git a/pages/Defi/_meta.json b/pages/Defi/_meta.json new file mode 100644 index 0000000..e69de29 diff --git a/pages/Defi/intro_liquidity_pool.mdx b/pages/Defi/intro_liquidity_pool.mdx new file mode 100644 index 0000000..8268750 --- /dev/null +++ b/pages/Defi/intro_liquidity_pool.mdx @@ -0,0 +1,169 @@ +# Liquidity pool là gì ? + +Trong nội dung bài học này, các bạn sẽ được tìm hiểu về defi trong Sui. Nội dung task 5 các bạn cũng thấy là yêu cầu chúng ta làm swap giữa 2 token. Chính vì vậy mà mình muốn cung cấp thêm các thông tin để các bạn hiểu về defi + +Liquidity pool trong Sui là một cơ chế cho phép người dùng gộp tài sản của họ vào một smart contract để cung cấp thanh khoản cho các trader swap tokens. Khác với các liquidity pool thông thường, Sui staking pool không dùng liquidity token mà thay vào đó dùng bảng tỷ giá toàn cục để theo dõi kế toán. + +Cách hoạt động là ta sẽ có users, những người gọi là **liquidity provider,** có thể cung cấp hai loại token theo một tỷ lệ nhất định. Tỷ lệ này phải giống với tỷ lệ trong pool để giữ giá trị ổn định giữa hai đồng, nếu không sẽ xảy ra chênh lệch giá. + +Liquidity provider sẽ nhận được token *LP* như receipt proofs (một biên nhận) , và chỉ với token này họ mới có thể r**út thanh khoản** (lấy lại hai đồng đã đầu tư). Trader có thể dùng một đồng để đổi lấy đồng khác trong pool. Mỗi giao dịch sẽ sinh ra một khoản fee (phí) , được chia cho các liquidity provider theo tỷ lệ đóng góp của họ. + + + +![](../assets/defi/dex_pool.png) + +Giả định hơn thì LP giống như một voucher và sẽ không store bất kì cái gì bên trong nó. Ví du: + +```rust +public struct LP has drop {} +``` + +Thành phần quan trọng nhất trong pool chính là **số lượng của hai loại token**, tương tự như mối quan hệ giữa `Coin` và `Balance`. Ta sẽ dùng `Balance ` để lưu trữ số lượng. +Vì chúng ta đã đặt ra nhiều ràng buộc cho pool này nên supply trở thành dữ liệu tùy chọn (optional), trong ví dụ bài viết mình viết thì nó phải bằng tổng số lượng của hai đồng. Tuy nhiên, để demo cách sử dụng các hàm liên quan, chúng ta vẫn sẽ thêm dữ liệu này vào đây. + +```rust +public struct Pool has key { + id: UID, + balance_a: Balance, + balance_b: Balance, + lp_supply: Supply>, +} +``` + +Để đảm bảo tính xác thực của token LP mà user nắm giữ, chúng ta sẽ dùng một cấu trúc để lưu trữ thông tin về token này, trong đó ID là khóa (key), `vector` là giá trị (value) để xây dựng một cấu trúc dữ liệu Table. Trong vector này, giá trị đầu tiên là số lượng của đồng thứ nhất và giá trị thứ hai là số lượng của đồng thứ hai. + +```rust +public struct Pocket has key { + id: UID, + id_to_vec: Table>, +} +``` + + +## Ta sẽ tạo các function sau: + +### **Create a liquidity pool:** + +Các hàm chứa generic cần được liệt kê trong dấu ngoặc nhọn sau tên hàm. Điều quan trọng của hàm này là **cách tạo một `balance` rỗng, tổng `supply`** và cuối cùng là **share object** dưới dạng có thể thay đổi được( mutable): + +```rust +entry fun create_pool(ctx: &mut TxContext) { + + let pool = Pool { + id: object::new(ctx), + balance_a: balance::zero(), + balance_b: balance::zero(), + lp_supply: balance::create_supply>(LP {}), + }; + + transfer::share_object(pool); +} +``` + +### Adding Liquidity + +Trong Sui Move, các biến thuộc một type có thể dùng dấu chấm (.) để gọi các function trong module của chúng. Nếu tham số đầu tiên là chính nó, nó sẽ tự động được tham chiếu (reference) hoặc tham chiếu có thể thay đổi (mutable reference) tùy theo ngữ cảnh. + +Hai loại token sẽ được đưa vào liquidity pool, và token LP (như một chứng nhận) sẽ được trả về cho liquidity provider. Thông tin liên quan sẽ được lưu trong Pocket. + +```rust + +entry fun add_liquidity(pool: &mut Pool, coin_a: Coin, coin_b: Coin, pocket: &mut Pocket, ctx: &mut TxContext) { + let coin_a_amount = coin_a.value(); + let coin_b_amount = coin_b.value(); + + pool.balance_a.join(coin_a.into_balance()); + pool.balance_b.join(coin_b.into_balance()); + let lp_balance = pool.lp_supply.increase_supply(coin_a_amount + coin_b_amount); + + let lp_coin = coin::from_balance(lp_balance, ctx); + + + let mut vec: vector = vector[coin_a_amount, coin_b_amount] + + pocket.id_to_vec.add(object::id(&lp_coin), vec); + + transfer::public_transfer(lp_coin, ctx.sender()); +} + +``` + + + +### Removing Liquidity + +Sử dụng chứng nhận LP mà bạn nắm giữ để rút số lượng tương ứng của hai đồng tiền từ pool. Tất nhiên, chứng nhận này cần được xác thực qua Pocket. Trong phiên bản mới của Sui Move, bạn có thể sử dụng [index] để lấy giá trị, nếu thêm tiền tố & hoặc &mut bạn có thể thực hiện chức năng borrow hoặc borrow_mut. + +```rust +entry fun remove_liquidity(pool: &mut Pool, lp: Coin>, pocket: &mut Pocket, ctx: &mut TxContext) { + let lp_id = object::id(&lp); + assert!(pocket.id_to_vec.contains(lp_id), ErrNotContainLP); + + let vec = pocket.id_to_vec.remove(lp_id); + let coin_a_amount = vec[0]; + let coin_b_amount = vec[1]; + assert!(coin_a_amount <= pool.balance_a.value() && coin_b_amount <= pool.balance_b.value(), ErrNotEnoughBalance); + + pool.lp_supply.decrease_supply(lp.into_balance()); + + let sender = tx_context::sender(ctx); + transfer::public_transfer(coin::take(&mut pool.balance_a, coin_a_amount, ctx), sender); + transfer::public_transfer(coin::take(&mut pool.balance_b, coin_b_amount, ctx), sender); +} +``` + +### hàm swap a_to_b coin + +Trong bài học này, mình muốn đơn giản về pool nên tỷ lệ sẽ là 1:1. Miễn là trong pool có đủ số lượng đồng tiền tương ứng, việc triển khai hàm rất đơn giản. + +```rust + +entry fun a_swap_b(pool: &mut Pool, coin_a: Coin, ctx: &mut TxContext) { + let amount = coin_a.value(); + assert!(amount <= pool.balance_b.value(), ErrNotEnoughBalance); + + pool.balance_a.join(coin_a.into_balance()); + transfer::public_transfer(coin::take(&mut pool.balance_b, amount, ctx), tx_context::sender(ctx)); +} + +``` + +# Deploy on chain + +Sau khi mình đã dùng lệnh `sui client publish` thì bây giờ mình có thể: + +• **Setting Environment Variables** + +```rust +export PACKAGE_ID=0xbf3da4e4174fda08f963abf9380e64aedec4d310dbd4a8e03ed4f653c05452ef +export COIN_A_TREASURY_CAP=0x587fbe3bbc2c349750a8f53e9211f6a8ac210191e77d1ef5d71c4b568ec56120 +export COIN_B_TREASURY_CAP=0x6c862ef9962fc9942738b32fa8fec7046c0013ece24452a050eae2f6b3409edc +export COIN_A_TYPE=0xbf3da4e4174fda08f963abf9380e64aedec4d310dbd4a8e03ed4f653c05452ef::coin_a::COIN_A +export COIN_B_TYPE=0xbf3da4e4174fda08f963abf9380e64aedec4d310dbd4a8e03ed4f653c05452ef::coin_b::COIN_B +export POCKET=0xdd96634447f2657e360955f7a2497d247288fb8c0dec92214563770361e562f0 +export OWNER=0x9e4092b6a894e6b168aa1c6c009f5c1c1fcb83fb95e5aa39144e1d2be4ee0d67(这是你自己的地址,用来接收铸造的币) +``` + +Đầu tiên liên quan đến tạo Coin: + +```rust +sui client call --package $PACKAGE_ID --module coin_a --function mint --args $COIN_A_TREASURY_CAP 100 $OWNER --gas-budget 10000000 +``` + +Sau khi mình đã tạo xong rồi, mình giờ có thể record đồng coin vào biến môi trường: + +```rust +export COIN_A=0xa9f409565a3a7dd7a58560eeee6865d76e1658c787232ff8d7e2f2fb1226d782 +``` + +Điều tương tự với coin_B: + +```rust +sui client call --package $PACKAGE_ID --module coin_b --function mint --args $COIN_B_TREASURY_CAP 200 $OWNER --gas-budget 10000000 +``` + +Giờ thì mình có thể tạo lệnh creating LP bằng: + +```rust +sui client call --package $PACKAGE_ID --module swap --function create_pool --type-args $COIN_A_TYPE $COIN_B_TYPE --gas-budget 10000000 +``` \ No newline at end of file diff --git a/pages/assets/defi/dex_pool.png b/pages/assets/defi/dex_pool.png new file mode 100644 index 0000000..396eafd Binary files /dev/null and b/pages/assets/defi/dex_pool.png differ diff --git a/pages/assets/image_sdk/architecture_sdk.png b/pages/assets/image_sdk/architecture_sdk.png new file mode 100644 index 0000000..0652619 Binary files /dev/null and b/pages/assets/image_sdk/architecture_sdk.png differ diff --git a/pages/assets/image_sdk/connect_wallet.png b/pages/assets/image_sdk/connect_wallet.png new file mode 100644 index 0000000..14e2907 Binary files /dev/null and b/pages/assets/image_sdk/connect_wallet.png differ diff --git a/pages/assets/image_sdk/list_account.png b/pages/assets/image_sdk/list_account.png new file mode 100644 index 0000000..7379d1c Binary files /dev/null and b/pages/assets/image_sdk/list_account.png differ diff --git a/pages/sui_dapp_kit/_meta.json b/pages/sui_dapp_kit/_meta.json new file mode 100644 index 0000000..980d301 --- /dev/null +++ b/pages/sui_dapp_kit/_meta.json @@ -0,0 +1,5 @@ +{ + "intro_ptb": "Giới thiệu về PTB" + } + + \ No newline at end of file diff --git a/pages/sui_dapp_kit/intro_ptb.mdx b/pages/sui_dapp_kit/intro_ptb.mdx new file mode 100644 index 0000000..b0d3484 --- /dev/null +++ b/pages/sui_dapp_kit/intro_ptb.mdx @@ -0,0 +1,81 @@ +# Giới thiệu về Programmable Transaction Block (PTB) + +Programmable Transaction Block (PTB) là công nghệ lõi của Sui, được thiết kế để nâng cao tính linh hoạt và khả năng lập trình của các giao dịch. PTB cho phép chúng ta kết hợp nhiều thao tác vào một giao dịch duy nhất, cho phép thực thi logic giao dịch phức tạp chỉ với một lần submit. + + +PTB giải quyết một số vấn đề chính trong giao dịch blockchain: + +1.**Độ phức tạp của giao dịch**: Blockchain truyền thống chỉ thực thi một thao tác cho mỗi giao dịch. Logic nghiệp vụ phức tạp thường yêu cầu nhiều giao dịch, dẫn đến thao tác rườm rà và hiệu suất thấp. PTB cho phép kết hợp nhiều thao tác vào một giao dịch duy nhất, cải thiện hiệu quả xử lý nghiệp vụ. + +2.**Tính nguyên tử của các thao tác**: Trong nhiều giao dịch, nếu một bước thất bại, cần phải thực hiện các thao tác rollback phức tạp. PTB đảm bảo tính nguyên tử của tất cả các thao tác, ngăn chặn vấn đề không nhất quán dữ liệu do thành công một phần. + +3.**Hiệu năng mạng**: Bằng cách giảm số lượng giao dịch, PTB giảm băng thông mạng tiêu thụ và thời gian xác nhận giao dịch, nâng cao hiệu suất tổng thể. + +## Giới thiệu về câu lệnh với ptb + +Nếu bạn đã làm việc với `sui client` thì còn có một sub-command mà bạn nên biết đó là `sui client ptb`. + +```rust +sui client ptb [OPTIONS] +``` + +trong đó phần `[OPTIONS]` chưa rất nhiều các functions cần thiết như là + +### --assign command +Mục đích của câu lệnh này là bạn sẽ assign một biến để được sử dụng trong PTB commands + +```rust +sui client ptb --assign MYVAR 100 +sui client ptb --assign X '[100, 5000]' +``` + +### --merge-coins command +Gộp nhiều đồng coin vào một đồng coin được chỉ định. Các ID của object cần được thêm ký hiệu @ ở phía trước. + +Syntax mà bạn cầnn truyền vào là: +```rust + <[COIN OBJECTS]> +``` + +Ví dụ: + +```rust +sui client ptb --merge-coins @coin_object_id '[@coin_obj_id1, @coin_obj_id2]' +``` + +### --slit-coins command + +Split thành nhiều coins khác nhau: + +```rust +--split-coins gas '[1000, 5000, 75000]' +--assign new_coins +--split-coins @coin_object_id [100] +``` + +### --move-call command + +Gọi một hàm Move được chỉ định. Đây là syntax: + +```rust + +``` + +Ví dụ: + +```bash +--move-call std::option::is_none none +--assign a none +--move-call std::option::is_none a +``` + +### --transfer-objects command + +Transfer các objects để một address cụ thể: + +```rust +--transfer-objects [obj1, obj2, obj3] @address +--split-coins gas [1000, 5000, 75000] +--assign new_coins +--transfer-objects [new_coins.0, new_coins.1, new_coins.2] @to_address +``` \ No newline at end of file diff --git a/pages/sui_dapp_kit/intro_to_dapp_kit.mdx b/pages/sui_dapp_kit/intro_to_dapp_kit.mdx new file mode 100644 index 0000000..7fcaca9 --- /dev/null +++ b/pages/sui_dapp_kit/intro_to_dapp_kit.mdx @@ -0,0 +1,165 @@ + +# Giới thiệu Sui-dapp kit + +> Làm thế nào để call on-chain method thông qua SDK để tương tác với contract ? + +Mình sẽ sử dụng câu lệnh sau: +```rust +pnpm create @mysten/dapp +``` + +Khi dùng lệnh trên thì bạn sẽ có có 2 option là: + +```rust +? Which starter template would you like to use? … +> react-client-dapp React Client dApp that reads data from wallet and the blockchain + react-e2e-counter React dApp with a move smart contract that implements a distributed counter +``` + +Giải thích đơn giản thì: + +- react-client-dapp: Một dApp React cơ bản để lấy danh sách các object mà ví đã kết nối sở hữu. +- react-e2e-counter: Một ví dụ end-to-end bao gồm cả code Move và UI cho một ứng dụng đếm số đơn giản. + +Trong ví dụ làm task 4, mình chỉ cần dùng `react-client-dapp` là đủ. Đây là cấu trúc + + + +![](../assets/image_sdk/architecture_sdk.png) + + +Sau khi cài đặt các package cần thiết, bạn cần setup một số providers trong ứng dụng để đảm bảo dApp Kit hoạt động trơn tru: + +```typescript +import React from "react"; +import ReactDOM from "react-dom/client"; +import "@mysten/dapp-kit/dist/index.css"; +import "@radix-ui/themes/styles.css"; + + +import { SuiClientProvider, WalletProvider } from "@mysten/dapp-kit"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Theme } from "@radix-ui/themes"; +import App from "./App.tsx"; +import { networkConfig } from "./networkConfig.ts"; + +const queryClient = new QueryClient(); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + + + + + , +); + + +``` + +## Interface Layout + +### Connecting to wallet + +Button này có thể được import từ package `@mysten/dapp-kit`. Bằng cách sử dụng component ConnectButton với cú pháp ``, nó sẽ render ra một button dùng để kết nối với ví trình duyệt. Để tùy chỉnh layout của button này, ta có thể wrap nó trong một thẻ `
` và styling thông qua className trong file css. + +```typescript +import { ConnectButton } from "@mysten/dapp-kit"; + +function App() { + const account = useCurrentAccount(); + + return ( +
+ + + {!account &&
No account connected
} + + {account && ( +
+
Hello, world
+

Current account:

+
Address: {account.address}
+ +
+ + )} +
+ ); +} + +``` + + +![](../assets/image_sdk/connect_wallet.png) + + +### Lấy Data + +dApp Kit cũng hỗ trợ việc lấy dữ liệu cho người dùng đang kết nối. Hook `useSuiClientQuery` có thể được sử dụng để gọi các phương thức RPC. Bạn có thể dùng `getOwnedObjects` để truy cập và hiển thị danh sách các object mà tài khoản đang kết nối sở hữu: + +```rust +export function OwnedObjects() { + const account = useCurrentAccount()!; + const { data } = useSuiClientQuery('getOwnedObjects', + { owner: account.address }); + + if (!data){ + return null; + } + + return ( +
    + {data.data.map((object) => ( +
  • {object.data?.objectId}
  • + ))} +
+ ); +} +``` + +Và đây là kết quả: + +![](../assets/image_sdk/list_account.png) + + +### Transaction Building + +Nhiều dApp cần có khả năng tạo và ký các transaction block. dApp Kit giúp đơn giản hóa quá trình này với hook `useSignAndExecuteTransactionBlock`. Mình sẽ tạo một button để gửi SUI đến một địa chỉ được định nghĩa trước: + +```typescript +import { useSignAndExecuteTransaction, useCurrentAccount } from '@mysten/dapp-kit'; +import { Transaction } from '@mysten/sui/transactions'; + +export function SendSui() { + const { mutateAsync: signAndExecuteTransactionBlock } = useSignAndExecuteTransaction(); + + function sendMessage() { + const txb = new Transaction(); + + const coin = txb.splitCoins(txb.gas, [10]); + txb.transferObjects([coin], '0x915c2d19ee5fde257693f25e6c2cabb04c25e7ae03932817d52e122258c88ddb'); + + signAndExecuteTransactionBlock({ + transaction: txb, + }).then(async (result) => { + alert('Sui sent successfully'); + }); + } + + return ; +} +``` + +Khi nhấn button, nó sẽ: + +- Tạo một `TransactionBlock` mới +- Thêm giao dịch `splitCoins` để tách một lượng SUI từ gas coin thành một coin mới +- Thêm giao dịch `transferObject` để chuyển coin mới đó tới một địa chỉ khác +- Ký và thực thi `TransactionBlock` thông qua ví đã kết nối +- Hiển thị một `alert` để thông báo khi giao dịch đã được thực hiện xong