# 快速建立 Sui Move Dapp 並實現贊助交易 我們這次要在 Sui Move Dapp 實現贊助交易(Sponsored Transaction) Sui 現在有提供 很好用的快速建立 Dapp 的工具 (Sui Dapp Kit) [Sui TypeScript Docs](https://sdk.mystenlabs.com/dapp-kit/create-dapp) 所以這次我們要順便來練習在 VSCode 用 Sui Dapp Kit 快速建立 Dapp 來玩玩看 ## 1. 使用 Sui Dapp Kit 快速建立 Dapp 打開 VSCode 終端機,輸入下方指令 ```javascript npm create @mysten/dapp ``` 接下來會問你要執行那種Dapp模組 目前 Sui 提供 2種範本 react-client-dapp:一個基本的 React Dapp,用於取得連接的錢包擁有的物件清單。 react-e2e-counter:帶有 Move 程式碼和簡單計數器應用程式 UI 的端到端範例。 最後輸入專案名稱 sui-dapp-demo 這次我們會使用 react-client-dapp ```javascript PS D:\vscode\SideProject> npm create @mysten/dapp ? Which starter template would you like to use? ... > react-client-dapp React Client dApp that reads data from wallet and the blockchain √ Which starter template would you like to use? · react-client-dapp √ What is the name of your dApp? (this will be used as the directory name) · sui-dapp-demo ``` 接下來我們進入到 sui-dapp-demo 目錄,並 install 依賴項及 Run 起來試試看 ```javascript PS D:\vscode\SideProject> cd sui-dapp-demo PS D:\vscode\SideProject\sui-dapp-demo> npm install added 321 packages, and audited 322 packages in 44s found 0 vulnerabilities PS D:\vscode\SideProject\sui-dapp-demo> npm run dev > sui-dapp-demo@0.0.0 dev > vite VITE v4.5.3 ready in 363 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help ``` ![image](https://hackmd.io/_uploads/HkZUCvobA.png) ## 2. 跟智能合約做互動,並使用贊助交易 如果還沒部署擲骰子的智能合約,可以先去參考之前實做的 [使用 Sui Move Random](https://hackmd.io/4oGEgONRRLSFfPIkv-xcNA?view) ```javascript= /// Module: dice_demo module dice_demo::dice_demo { use sui::random::{Self, Random}; // 骰子的物件,是一個 Shared Object public struct Dice has key, store { id: UID, value: u8 } // 部署合約時,初始化一個骰子物件,裡面value給0 // 並且變成一個Shared Object fun init(ctx: &mut sui::tx_context::TxContext) { let uid = object::new(ctx); let dice = Dice { id:uid, value:0 }; transfer::share_object(dice); } // 擲骰子,傳入參數為 // 1. Random物件(sui在devnet提供的0x8) // 2. Shared Object Dice 骰子物件 entry fun roll_dice(r: &Random, dice: &mut Dice, ctx: &mut TxContext) { // 建立一個新的 generator let mut generator = random::new_generator(r, ctx); // 產生一個 1~6 範圍的隨機數,並回傳u8值 let random_num = random::generate_u8_in_range(&mut generator, 1, 6); // 把隨機數存入 Shared Object Dice 骰子物件 dice.value = random_num; } } ``` 或是直接使用下方範例內的智能合約地址,但是因開發環境過一段時間,Sui會清除開發環境內的資料,所以有可能之後會找不到部署過的智能合約 這邊有已經做好的 Demo Code [Demo Code GitHub](https://github.com/ryan19910912/my-first-sui-dapp) 主要會修改 WalletStatus.tsx,以及新增一個 SponsoredTransaction.tsx 主要流程是 1. 產生一個贊助交易區塊,裡面需設定好交易執行者、Gas Owner等等資訊 2. 把這個贊助交易區塊給用戶做簽名,拿到用戶的簽名後,就執行這個贊助交易區塊,讓礦工驗證並上鏈 WalletStatus.tsx ```javascript= import { useCurrentAccount, useSignTransactionBlock } from "@mysten/dapp-kit"; import { Button, Container, Flex, Heading, Text } from "@radix-ui/themes"; import { OwnedObjects } from "./OwnedObjects"; import { getRollDiceSponsoredTransactionBlock, executeRollDiceSponsoredTransactionBlock } from "./SponsoredTransaction"; export function WalletStatus() { const account = useCurrentAccount(); const { mutate: signTransactionBlock } = useSignTransactionBlock(); return ( <Container my="2"> <Heading mb="2">Wallet Status</Heading> {account ? ( <Flex direction="column"> <Text>Wallet connected</Text> <Text>Address: {account.address}</Text> // 這邊新增贊助交易按鈕 <Button // 發送贊助交易 onClick={() => { // 取得 擲骰子贊助交易區塊 getRollDiceSponsoredTransactionBlock(account.address).then(txb => { if (txb) { // 發送交易區塊給用戶簽名 signTransactionBlock( { transactionBlock: txb, chain: 'sui:devnet', }, { onSuccess: (result) => { console.log('sign transaction block', result); // 成功後執行該贊助交易區塊,並進行上鏈 executeRollDiceSponsoredTransactionBlock(txb, result.signature, result.transactionBlockBytes); }, }, ); } else { alert("Get Roll Dice Transaction Block Fail"); } }); }} > Sign and execute transaction block </Button> </Flex> ) : ( <Text>Wallet not connected</Text> )} <OwnedObjects /> </Container> ); } ``` SponsoredTransaction.tsx ```javascript= import { TransactionBlock } from '@mysten/sui.js/transactions'; import { getFullnodeUrl, SuiClient } from '@mysten/sui.js/client'; import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519'; // 骰子 Shared Object ID const dice_shared_object_id = "0x9a1586f13af7fc4af8cb5978b12b769c8a260cf9409cf6c3d76761108adef4fe"; // 智能合約 Package ID const contract_package_id = "0x31599f857ee6721afa26ece8ab992ba51af7c732390900d3a4834908606be223"; // 智能合約 module 名稱 const contract_module_name = "dice_demo"; // 智能合約 呼叫方法 名稱 const contract_method_name = "roll_dice"; // Sui Devnet Random Object ID const sui_random_object_id = "0x8"; // 幫忙付 Gas Fee 的 Owner 地址 const gas_owner = "0xe20abce08a16e397ec368979b03bb6323d42605b38c6bd9b6a983c6ebcc45e11"; // 幫忙付 Gas Fee 的 Owner 註記詞 const word_list = import.meta.env.VITE_SPONSORED_WORD_LIST; const suiClient = new SuiClient({ url: getFullnodeUrl('devnet'), }); /** * 取得 呼叫擲骰子方法的 贊助交易區塊 * @param address // 用戶的錢包地址 * @returns */ export async function getRollDiceSponsoredTransactionBlock(address: string) { // 產生一個新的交易區塊 const sponsoredTxb = new TransactionBlock(); // 設定 交易者(用戶的錢包地址) sponsoredTxb.setSender(address); // 設定 支付 Gas Fee 的 Owner(贊助商地址) sponsoredTxb.setGasOwner(gas_owner); // 取得贊助商的錢包 Coin Data let coinData = await suiClient.getCoins({ owner: gas_owner, }); let coins = coinData.data; if (coins) { // 如果 Coin 存在 // 抓取第一個 Coin let coin = coins[0]; // Gas Payment 物件 let payment = { digest: coin.digest, objectId: coin.coinObjectId, version: coin.version }; // 設定 Gas Payment sponsoredTxb.setGasPayment([payment]); // 呼叫智能合約 擲骰子方法 sponsoredTxb.moveCall({ // target 要帶入 PageckId::moduleName::methodName // ex: 0x31599f857ee6721afa26ece8ab992ba51af7c732390900d3a4834908606be223::dice_demo::roll_dice target: `${contract_package_id}::${contract_module_name}::${contract_method_name}`, // 傳遞2個參數,第1個為 Sui Devnet上的Random Object ID (0x8),第2個為 骰子的Shared Object ID arguments: [sponsoredTxb.object(sui_random_object_id), sponsoredTxb.object(dice_shared_object_id)], }); // 回傳這個贊助交易區塊 return sponsoredTxb; } } /** * 執行 呼叫擲骰子方法的 贊助交易區塊 * @param txb // 由 getRollDiceSponsoredTransactionBlock 取得的贊助交易區塊 * @param userSignature // 用戶對交易區塊的簽名 * @param transactionBlockBytes // 用戶所簽名的交易區塊 Bytes */ export async function executeRollDiceSponsoredTransactionBlock(txb: TransactionBlock, userSignature: string, transactionBlockBytes: string) { // 取得 交易區塊的 Bytes let txBuild = await txb.build({ client: suiClient }); // 取得贊助商的密鑰 By 註記詞 let sponsoredKeypair = Ed25519Keypair.deriveKeypair(word_list); // 使用贊助商的密鑰 對 交易區塊 進行簽名,並產生簽章 let sponsoredKSign = await sponsoredKeypair.signTransactionBlock(txBuild); console.log("贊助商簽名 : "+ sponsoredKSign.signature); console.log("贊助商簽名交易區塊 : "+ sponsoredKSign.bytes); console.log("用戶簽名 : "+ userSignature); console.log("用戶簽名交易區塊 : "+ transactionBlockBytes); // 執行 贊助交易區塊 上鏈 suiClient.executeTransactionBlock({ // 贊助交易區塊 Bytes 字串 transactionBlock: transactionBlockBytes, // 簽名者 [用戶的簽名, 贊助商的簽名] signature: [userSignature, sponsoredKSign.signature], // 選項(可放可不放),可以顯示一些想要看的資料 options: { showEffects: true, showEvents: true, showObjectChanges: true }, }).then(result => { console.log(JSON.stringify(result)); }); } ``` ## 3. 執行贊助交易 首先,先查看 目前骰子 Shared Object (0x9a1586f13af7fc4af8cb5978b12b769c8a260cf9409cf6c3d76761108adef4fe) 裡面的數字目前是 5 https://suiscan.xyz/devnet/object/0x9a1586f13af7fc4af8cb5978b12b769c8a260cf9409cf6c3d76761108adef4fe ![image](https://hackmd.io/_uploads/rktyhus-C.png) 再來查看 贊助商 (0xe20abce08a16e397ec368979b03bb6323d42605b38c6bd9b6a983c6ebcc45e11) 目前的 Sui Coin 數量是 9.967479224 https://suiscan.xyz/devnet/account/0xe20abce08a16e397ec368979b03bb6323d42605b38c6bd9b6a983c6ebcc45e11 ![image](https://hackmd.io/_uploads/SkMm2_i-C.png) 接下來按下 發送贊助交易按鈕 ![image](https://hackmd.io/_uploads/rJ8Q6OsWA.png) 會看到執行交易的是 0x3f6d...a175 且扣除的Sui為 0.001022876,扣除的Owner是 0xe20a...5e11 也就是贊助商的錢包地址 這個 0x3f6d...a175 錢包裡面目前是沒有 Sui Coin的 ![image](https://hackmd.io/_uploads/SJbPT_iWA.png) 往下拉看他的Detail 可以看到 Sponsored 區塊 我付費 0 個 SUI,贊助商幫我付了 Gas Fee ![image](https://hackmd.io/_uploads/Hyq3TOo-R.png) 按下 Approve 後,我們可以在頁面的開發者工具視窗(F12)的Console看到下方這些資訊 ![image](https://hackmd.io/_uploads/ryIA1Ks-A.png) 這邊要注意,贊助商簽名的交易區塊跟用戶簽名的交易區塊,要一樣,代表他們是簽同個交易區塊,如果不一樣則會失敗 ## 驗證執行結果 當執行完後,首先我們去查看骰子Shared Object的值是否有變動 我們可以發現,從原先的 5 -> 4 https://suiscan.xyz/devnet/object/0x9a1586f13af7fc4af8cb5978b12b769c8a260cf9409cf6c3d76761108adef4fe ![image](https://hackmd.io/_uploads/rksultsbR.png) 我們也可以查看他的Transaction Blocks Details 可以看到 User Signature 跟 Sponsor Signature https://suiscan.xyz/devnet/tx/j4hE5XzzZDVxDaU5LG1WLWSMYbsankpU7kD3TAS8Fuv ![image](https://hackmd.io/_uploads/SyemYFibC.png) 接下來查看贊助商的 SUI Coin有無變少 從 9.967479224 -> 9.966456348 https://suiscan.xyz/devnet/account/0xe20abce08a16e397ec368979b03bb6323d42605b38c6bd9b6a983c6ebcc45e11 ![image](https://hackmd.io/_uploads/rkGcttjZC.png) 這次的 Sui Move Dapp 實現贊助交易就到這邊 可以好好利用這種贊助交易實現很多不同的商業邏輯 ## 參考資料 [Sui TypeScript Docs](https://sdk.mystenlabs.com/dapp-kit/create-dapp) [Sui TypeDoc](https://sdk.mystenlabs.com/typedoc/index.html) [Sui Sponsored Transactions GitHub](https://github.com/MystenLabs/sui/tree/main/dapps/sponsored-transactions)