# 快速建立 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
```

## 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

再來查看 贊助商
(0xe20abce08a16e397ec368979b03bb6323d42605b38c6bd9b6a983c6ebcc45e11)
目前的 Sui Coin 數量是 9.967479224
https://suiscan.xyz/devnet/account/0xe20abce08a16e397ec368979b03bb6323d42605b38c6bd9b6a983c6ebcc45e11

接下來按下 發送贊助交易按鈕

會看到執行交易的是 0x3f6d...a175
且扣除的Sui為 0.001022876,扣除的Owner是 0xe20a...5e11
也就是贊助商的錢包地址
這個 0x3f6d...a175 錢包裡面目前是沒有 Sui Coin的

往下拉看他的Detail
可以看到 Sponsored 區塊
我付費 0 個 SUI,贊助商幫我付了 Gas Fee

按下 Approve 後,我們可以在頁面的開發者工具視窗(F12)的Console看到下方這些資訊

這邊要注意,贊助商簽名的交易區塊跟用戶簽名的交易區塊,要一樣,代表他們是簽同個交易區塊,如果不一樣則會失敗
## 驗證執行結果
當執行完後,首先我們去查看骰子Shared Object的值是否有變動
我們可以發現,從原先的 5 -> 4
https://suiscan.xyz/devnet/object/0x9a1586f13af7fc4af8cb5978b12b769c8a260cf9409cf6c3d76761108adef4fe

我們也可以查看他的Transaction Blocks Details
可以看到 User Signature 跟 Sponsor Signature
https://suiscan.xyz/devnet/tx/j4hE5XzzZDVxDaU5LG1WLWSMYbsankpU7kD3TAS8Fuv

接下來查看贊助商的 SUI Coin有無變少
從 9.967479224 -> 9.966456348
https://suiscan.xyz/devnet/account/0xe20abce08a16e397ec368979b03bb6323d42605b38c6bd9b6a983c6ebcc45e11

這次的 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)