# ID Pointer & Event
### 參考資料
[Sui Move Book - 中文版](https://examples.sui-book.com/patterns/id-pointer.html)
[Suiet Wallet Kit 文件](https://kit.suiet.app/docs/QuickStart)
[Suiet Wallet Kit GitHub](https://github.com/suiet/wallet-kit)
[Mysten sui.js api文件](http://typescript-sdk-docs.s3-website-us-east-1.amazonaws.com/classes/JsonRpcProvider.html)
[Mysten sui.js GitHub](https://github.com/MystenLabs/sui)
[SUI 主網上線,前端開發者的DApp 開發指南](https://juejin.cn/post/7229984415329337400)
### ID Pointer
ID Pointer是一個Sui Move在撰寫智能合約時,可以使用的一種設計模型
該模式為將主要數據(一個物件)的其方法、權限分離。
可以想像A物件內的某個欄位ID是指向B物件的地址概念。
這種模式可以用於幾個不同的方向:
- 為共享對象提供轉讓(轉移)或修改的功能
(例如,利用TransferCap權限更改共享物件的資訊、或是轉移共享物件內容)
- 將動態數據與靜態數據分開(例如,單個NFT及NFT作品集的信息)
- 避免不必要的類型鏈接(以及見證的要求)(例如,流動性池中的質押代幣)
### Event
Event是用來追踪鏈上行為的主要方式。
主要會使用到Event物件(struct)及sui::event::emit方法
### 範例(CLI指令及Sui Explorer) :
首先先建立一個sui_demo新專案
```javascript
sui move new sui_demo
```
在sources新建立一個檔案 salary.move檔,並複製貼上下方程式碼
範例說明 :
這是一個簡易的教學網站發課程費用的模組,假如在發現老師錢包沒有SalaryBoxCap物件,則呼叫create_new_salary_box方法,建立一個SalaryBox並存入課程費用金額
假如已經有SalaryBoxCap物件,則查詢SalaryBoxCap物件對應的SalaryBox,並呼叫add_salary方法存入課程費用金額。
存入金額後,老師教完課後,系統會呼叫send_salary發送給老師薪水
```rust
module sui_demo::salary {
use sui::object::{Self, ID, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
use sui::event;
use std::option::{Self, Option};
use sui::coin::{Coin};
/// Cap does not match the Lock.
const ECapMismatch: u64 = 1;
const ESalaryNotYetSend: u64 = 2;
struct SalaryBox<phantom T> has key, store {
id: UID,
coin: Option<Coin<T>>,
sender: address,
is_send: bool,
}
struct SalaryBoxCap has key, store {
id: UID,
salary_box_id: ID,
}
/// ===== Events =====
struct SalaryAndCapEvent has copy, drop {
cap_id: address,
salary_box_id: address,
}
public entry fun create_new_salary_box<T> (
coin: Coin<T>,
sender: address,
ctx: &mut TxContext
) {
let salary_box = SalaryBox {
id: object::new(ctx),
coin: option::some(coin),
sender: sender,
is_send: false,
};
let salary_box_cap = SalaryBoxCap {
id: object::new(ctx),
salary_box_id: object::id(&salary_box),
};
let event = SalaryAndCapEvent {
cap_id: object::id_address(&salary_box_cap),
salary_box_id: object::id_address(&salary_box),
};
event::emit(event);
transfer::share_object(salary_box);
transfer::transfer(salary_box_cap, tx_context::sender(ctx));
}
public entry fun add_salary<T>(
coin: Coin<T>,
salary_box: &mut SalaryBox<T>,
salary_box_cap: &SalaryBoxCap,
) {
assert!(&salary_box_cap.salary_box_id == object::borrow_id(salary_box), ECapMismatch);
assert!(salary_box.is_send, ESalaryNotYetSend);
let event = SalaryAndCapEvent {
cap_id: object::id_address(salary_box_cap),
salary_box_id: object::id_address(salary_box),
};
event::emit(event);
option::fill(&mut salary_box.coin, coin);
salary_box.is_send = false;
}
public entry fun send_salary<T>(
salary_box: &mut SalaryBox<T>,
salary_box_cap: &SalaryBoxCap,
) {
assert!(&salary_box_cap.salary_box_id == object::borrow_id(salary_box), ECapMismatch);
let event = SalaryAndCapEvent {
cap_id: object::id_address(salary_box_cap),
salary_box_id: object::id_address(salary_box),
};
event::emit(event);
salary_box.is_send = true;
let coin = option::extract(&mut salary_box.coin);
transfer::public_transfer(coin, salary_box.sender);
}
}
```
我們先使用build指令,看智能合約是否能編譯成功
```javascript
PS D:\vscode\workspace\sui_demo> sui move build
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING sui_demo
```
接下來發布智能合約至Devnet
```javascript
PS D:\vscode\workspace\sui_demo> sui client publish --gas-budget 1000000000
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING sui_demo
Successfully verified dependencies on-chain against source.
----- Transaction Digest ----
4YQ3nPxBwZSP37fduwucDDvfqGAdE4J7WdfgViRZTLvK
----- Transaction Data ----
Transaction Signature: [Signature(Ed25519SuiSignature(Ed25519SuiSignature([0, 92, 226, 37, 38, 29, 80, 68, 51, 230, 189, 89, 52, 68, 28, 93, 153, 218, 193, 59, 12, 107, 230, 54, 125, 62, 18, 153, 90, 250, 252, 57, 61, 226, 109, 232, 220, 217, 185, 182, 112, 225, 246, 202, 82, 115, 10, 221, 86, 22, 96, 213, 138, 113, 181, 26, 201, 131, 15, 83, 234, 161, 83, 111, 12, 247, 100, 111, 221, 105, 109, 168, 119, 191, 214, 73, 60, 87, 207, 170, 217, 25, 125, 35, 32, 56, 114, 200, 3, 129, 122, 80, 229, 159, 146, 202, 222])))]
Transaction Kind : Programmable
Inputs: [Pure(SuiPureValue { value_type: Some(Address), value: "0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7" })]
Commands: [
Publish(<modules>,0x0000000000000000000000000000000000000000000000000000000000000001,0x0000000000000000000000000000000000000000000000000000000000000002),
TransferObjects([Result(0)],Input(0)),
]
Sender: 0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7
Gas Payment: Object ID: 0x2de89c4e046a575d54ac9565ba57f0e24cc723f3059d3b223e7350234af46a92, version: 0x51, digest: Dpa1vyJLX38vmqxRzMt3EVUhiLQZUE2T1P16jrSaJ94X
Gas Owner: 0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7
Gas Price: 1000
Gas Budget: 1000000000
----- Transaction Effects ----
Status : Success
Created Objects:
- ID: 0x314a2860d2eab7ec1e48eba179ab2779bbfcae31ee29fc3a7458e3d135eb328a , Owner: Account Address ( 0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7 )
- ID: 0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed , Owner: Immutable
Mutated Objects:
- ID: 0x2de89c4e046a575d54ac9565ba57f0e24cc723f3059d3b223e7350234af46a92 , Owner: Account Address ( 0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7 )
----- Events ----
Array []
----- Object changes ----
Array [
Object {
"type": String("mutated"),
"sender": String("0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7"),
"owner": Object {
"AddressOwner": String("0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7"),
},
"objectType": String("0x2::coin::Coin<0x2::sui::SUI>"),
"objectId": String("0x2de89c4e046a575d54ac9565ba57f0e24cc723f3059d3b223e7350234af46a92"),
"version": String("82"),
"previousVersion": String("81"),
"digest": String("9ZGSu94o6zisxa8uFn64DdbvVJSJzhGxLj5k7Sxij725"),
},
Object {
"type": String("created"),
"sender": String("0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7"),
"owner": Object {
"AddressOwner": String("0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7"),
},
"objectType": String("0x2::package::UpgradeCap"),
"objectId": String("0x314a2860d2eab7ec1e48eba179ab2779bbfcae31ee29fc3a7458e3d135eb328a"),
"version": String("82"),
"digest": String("8vBxHeGBxpndAsWSBPzjLSRtKY3Bg1D2JxvYgEHN2zep"),
},
Object {
"type": String("published"),
"packageId": String("0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed"),
"version": String("1"),
"digest": String("2ZSnVPws5G4AFiHYeuMQHpngC8vq9f3ZoJQA8te3FEiA"),
"modules": Array [
String("salary"),
],
},
]
----- Balance changes ----
Array [
Object {
"owner": Object {
"AddressOwner": String("0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7"),
},
"coinType": String("0x2::sui::SUI"),
"amount": String("-13040680"),
},
]
```
使用packageId去[Sui Explorer](https://suiexplorer.com/?network=devnet)查詢相關資料
首先呼叫create_new_salary_box
Type0帶入 0x2::sui::SUI
Arg0帶入 要被保管的Coin Object ID
Arg1帶入 要把保管的Coin發送的錢包地址(這邊我們用客戶端管理的另一個錢包地址)
呼叫成功後點Digest下方的超連結

選擇Events,然後點擊View Event Data,會看到出現我們設置的Event Object跟及欄位內容
可以發現跟我們智能合約所設置的SalaryAndCapEvent欄位一模一樣

```json
{
"id": {
"txDigest": "ArUfEd45mtZNWCwov1vd4y9jgsfmK1h6Q8a9njYCmjLC",
"eventSeq": "0"
},
"packageId": "0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed",
"transactionModule": "salary",
"sender": "0xd1c4d5d9115d8aa9de808eb59c0f2cf614a67ca26f9759b1d07b56fe9fc612c7",
"type": "0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed::salary::SalaryAndCapEvent",
"parsedJson": {
"cap_id": "0xeb392eabc297e4f66895f573051629b04459d07f5d9ef9d2fade8fa7efd9519b",
"salary_box_id": "0x56af0173f3cf67a5ad72f714d2fd507039bb8464dad5d81f1f59b18ec9db06c4"
},
"bcs": "5hmT65Ej9p24cTWHtjh1sxp5vkqtKpZGQLH8rDufK95m8zZirxTuRzBJwxhDajpysfPDPmSSyBktn8gzhGXKuTdu"
}
```
接下來我們去呼叫send_salary方法
Type0 帶入 0x2::sui::SUI
Arg0 帶入 SalaryBox Object ID
Arg1 帶入 SalaryBoxCap Object ID

執行成功後,我們用CLI指令切換另一個錢包地址看看,有沒有拿到被保管的Coin
```javascript
sui client switch --address 0xadb496783149470deef1069876266948d913d9ab5795be99bf9fbd96e8a24039
```
```javascript
PS D:\vscode\workspace\sui_demo> sui client gas
Object ID | Gas Value
----------------------------------------------------------------------------------
0x1e9a20ae56d0a9eeae29c1cee8a33b72d519b3a9e147c5352752bafe69a9f05a | 100000000
0xee7ecb4c6151e0b07c41871b4196a9e0dac31fbdf90a9eadff1824799e5a611f | 2000000000
```
可以發現0x1e9a20ae56d0a9eeae29c1cee8a33b72d519b3a9e147c5352752bafe69a9f05a就是我們剛剛放入保管的Coin Object ID
### Dapp 前端操作
建立一個React Vite專案
```react
npm create vite@latest
```
步驟如下:
1. Project name : dapp_demo
2. 選擇React
3. 選擇JavaScript
### Dapp 檔案
main.jsx
```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import '@suiet/wallet-kit/style.css';
import { WalletProvider } from "@suiet/wallet-kit";
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<WalletProvider>
<App />
</WalletProvider>
</React.StrictMode>
)
```
App.jsx
```javascript
import { useState, useEffect } from 'react'
import './App.css'
import { ConnectButton, useWallet } from '@suiet/wallet-kit'
import { devnetConnection, testnetConnection, mainnetConnection, JsonRpcProvider, TransactionBlock } from '@mysten/sui.js';
function App() {
const displayBlock = {
display: 'block'
}
const displayNone = {
display: 'none'
}
const wallet = useWallet();
/**
* sui fullnode rpc url
*/
const devRpcUrl = "https://fullnode.devtnet.sui.io";
const testRpcUrl = "https://fullnode.testnet.sui.io";
const mainRpcUrl = "https://fullnode.mainnet.sui.io";
// connect to Testnet
const provider = new JsonRpcProvider(devnetConnection);
// owend Objects
const [owend, setOwend] = useState("");
const [owendObjects, setOwendObjects] = useState("");
const [structType, setStructType] = useState("0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed::salary::SalaryBoxCap");
// object
const [object, setObject] = useState("");
const [objectId, setObjectId] = useState("");
// dynamic Fields
const [dynamicFields, setDynamicFields] = useState("");
const [parentId, setParentId] = useState("");
// dynamic Field Object
const [dynamicFieldObject, setDynamicFieldObject] = useState("");
const [type, setType] = useState("");
const [value, setValue] = useState("");
const [coins, setCoins] = useState("");
const [coinType, setCoinType] = useState("0x2::sui::SUI");
const [splitResult, setSplitResult] = useState("");
const [splitCoin, setSplitCoin] = useState("");
const [splitAmount, setSplitAmount] = useState(0);
const [mergeResult, setMergeResult] = useState("");
const [mainCoin, setMainCoin] = useState("");
const [mergeCoin, setMergeCoin] = useState("");
const [moveCallResult, setMoveCallResult] = useState("");
const [packageId, setPackageId] = useState("0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed");
const [moduleName, setModuleName] = useState("salary");
const [functionName, setFunctionName] = useState("create_new_salary_box");
const [salaryBox, setSalaryBox] = useState("");
const [salaryBoxCap, setSalaryBoxCap] = useState("");
const [sender, setSender] = useState("");
const [coin, setCoin] = useState("");
const [oneEventResult, setOneEventResult] = useState("");
const [allEventResult, setAllEventResult] = useState("");
useEffect(() => {
if (wallet.connected){
setOwend(wallet?.address);
}
}, [wallet.connected]);
// pretty json format
const jsonFormat = (str) => {
return JSON.stringify(str, null, 2);
}
const getOwnedObjectsFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setOwendObjects("");
let ownedObject = {
owner: owend,
options: {
showType: true,
showOwner: true,
showContent: true,
}
}
if (structType !== "") {
ownedObject.filter = {
MatchAny: [
{
StructType: structType
}
]
}
}
provider.getOwnedObjects(
ownedObject
).then(data => {
console.log("getOwnedObjectsFunction");
console.log(data);
setOwendObjects(jsonFormat(data.data));
});
}
}
const changeOwend = (e) => {
setOwend(e.target.value);
}
const changeStructType = (e) => {
setStructType(e.target.value);
}
const getObjectFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setObject("");
provider.getObject({
id: objectId,
options: {
showType: true,
showOwner: true,
showContent: true,
}
}).then(data => {
console.log("getObjectFunction");
console.log(data);
setObject(jsonFormat(data.data));
});
}
}
const changeObjectId = (e) => {
setObjectId(e.target.value);
}
const getDynamicFieldsFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setDynamicFields("");
provider.getDynamicFields({
parentId: parentId,
}).then(data => {
console.log("getDynamicFieldsFunction");
console.log(data);
setDynamicFields(jsonFormat(data.data));
});
}
}
const changeParentId = (e) => {
setParentId(e.target.value);
}
const getDynamicFieldObjectFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setDynamicFieldObject("");
provider.getDynamicFieldObject({
parentId: parentId,
name: {
type: type,
value: value
}
}).then(data => {
console.log("getDynamicFieldObjectFunction");
console.log(data);
setDynamicFieldObject(jsonFormat(data.data));
});
}
}
const changeType = (e) => {
setType(e.target.value);
}
const changeValue = (e) => {
setValue(e.target.value);
}
const getCoinsFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setCoins("");
provider.getCoins({
// coinType: coinType,
owner: owend,
}).then(data => {
console.log("getCoinsFunction");
console.log(data);
setCoins(jsonFormat(data.data));
});
}
}
const changeCoinType = (e) => {
setCoinType(e.target.value);
}
const splitCoinFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setSplitResult("");
let txObj = new TransactionBlock();
let main_coin = splitCoin === "" ? txObj.gas : txObj.object(splitCoin);
let [coins] = txObj.splitCoins(main_coin, [txObj.pure(Number(splitAmount) * 1000000000)]);
txObj.transferObjects([coins], txObj.pure(wallet.account.address));
wallet.signAndExecuteTransactionBlock({
transactionBlock: txObj,
options: {
showEffects: true,
showObjectChanges: true,
showType: true,
},
}).then(data => {
console.log("splitCoinFunction");
console.log(data);
setSplitResult(jsonFormat(data));
});
}
}
const changeSplitCoin = (e) => {
setSplitCoin(e.target.value);
}
const changeSplitAmount = (e) => {
setSplitAmount(e.target.value);
}
const mergeCoinFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setMergeResult("");
let txObj = new TransactionBlock();
let merge_coins = [];
let main_coin = mainCoin === "" ? txObj.gas : txObj.object(mainCoin);
for (let coin_object_id of mergeCoin.split(",")) {
merge_coins.push(txObj.object(coin_object_id));
}
txObj.mergeCoins(main_coin, merge_coins);
wallet.signAndExecuteTransactionBlock({
transactionBlock: txObj,
options: {
showEffects: true,
showObjectChanges: true,
showType: true,
},
}).then(data => {
console.log("mergeCoinFunction");
console.log(data);
setMergeResult(jsonFormat(data));
});
}
}
const changeMergeCoin = (e) => {
setMergeCoin(e.target.value);
}
const changeMainCoin = (e) => {
setMainCoin(e.target.value);
}
const moveCallFunction = () => {
console.log(wallet?.connected);
if (wallet?.connected) {
setMergeResult("");
setOneEventResult("");
let txObj = new TransactionBlock();
let type_args = [
coinType
];
let args = [];
if (functionName === 'add_salary') {
args.push(txObj.pure(coin));
args.push(txObj.pure(salaryBox));
args.push(txObj.pure(salaryBoxCap));
} else if (functionName === 'create_new_salary_box') {
args.push(txObj.pure(coin));
args.push(txObj.pure(sender));
} else if (functionName === 'send_salary') {
args.push(txObj.pure(salaryBox));
args.push(txObj.pure(salaryBoxCap));
}
console.log(args);
console.log(type_args);
// call sui move smart contract
txObj.moveCall({
target: `${packageId}::${moduleName}::${functionName}`,
typeArguments: type_args,
arguments: args,
})
wallet.signAndExecuteTransactionBlock({
transactionBlock: txObj,
options: {
showEffects: true,
showObjectChanges: true,
showType: true,
},
}).then(data => {
console.log("moveCallFunction");
console.log(data);
setMoveCallResult(jsonFormat(data));
setTimeout(() => {
queryOneEvent(data.digest);
}, 1000);
});
}
}
const queryAllEvents = () => {
console.log("starting queryAllEvents");
if (wallet?.connected) {
provider.queryEvents({
query: {
// Sender: wallet?.address,
MoveModule: {
package: packageId,
module: moduleName,
},
},
}).then(data => {
console.log(data);
setAllEventResult(jsonFormat(data));
});
}
};
const queryOneEvent = (trans) => {
console.log("starting queryOneEvents");
console.log("trans = "+trans);
provider.queryEvents({
query: {
Transaction: trans
},
}).then(data => {
console.log(data);
setOneEventResult(jsonFormat(data));
});
};
const changePackageId = (e) => {
setPackageId(e.target.value);
}
const changeModuleName = (e) => {
setModuleName(e.target.value);
}
const changeFunctionName = (e) => {
setFunctionName(e.target.value);
}
const changeSalaryBox = (e) => {
setSalaryBox(e.target.value);
}
const changeSalaryBoxCap = (e) => {
setSalaryBoxCap(e.target.value);
}
const changeSender = (e) => {
setSender(e.target.value);
}
const changeCoin = (e) => {
setCoin(e.target.value);
}
return (
<div style={{ textAlign: '-webkit-center' }}>
<ConnectButton />
<div style={{ ...wallet?.connected ? displayBlock : displayNone }}>
<p>Wallet Connection Status : {wallet?.status}</p>
<p>Connection Network Name : {wallet?.chain.name}</p>
<p>Wallet Address : {wallet?.account?.address}</p>
</div>
<div style={{ ...wallet?.connected ? displayBlock : displayNone }}>
{/* getOwnedObjects */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>get Owned Objects</h3>
<p>owner : <input onChange={changeOwend} style={{ width: '750px' }} value={owend} /></p>
<p>struct type filter : <input onChange={changeStructType} style={{ width: '750px' }} value={structType} /></p>
<p><button onClick={getOwnedObjectsFunction}>get Owned Objects</button></p>
<textarea
style={{ ...owendObjects ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={owendObjects}></textarea>
</div>
{/* getObject */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>get Object</h3>
<p>object id : <input onChange={changeObjectId} style={{ width: '500px' }} value={objectId} /></p>
<p><button onClick={getObjectFunction}>get Object</button></p>
<textarea
style={{ ...object ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={object}></textarea>
</div>
{/* getDynamicFields */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>get Dynamic Fields</h3>
<p>parent id : <input onChange={changeParentId} style={{ width: '500px' }} value={parentId} /></p>
<p><button onClick={getDynamicFieldsFunction}>get Dynamic Fields</button></p>
<textarea
style={{ ...dynamicFields ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={dynamicFields}></textarea>
</div>
{/* getDynamicFieldObject */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>get Dynamic Field Object</h3>
<p>parent id : <input onChange={changeParentId} style={{ width: '500px' }} value={parentId} /></p>
<p>type : <input onChange={changeType} style={{ width: '500px' }} value={type} /></p>
<p>value : <input onChange={changeValue} style={{ width: '500px' }} value={value} /></p>
<p><button onClick={getDynamicFieldObjectFunction}>get Dynamic Field Object</button></p>
<textarea
style={{ ...dynamicFieldObject ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={dynamicFieldObject}></textarea>
</div>
{/* getCoins */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>get Coins</h3>
<p>owner : <input onChange={changeOwend} style={{ width: '750px' }} value={owend} /></p>
<p>coin type : <input onChange={changeCoinType} style={{ width: '500px' }} value={coinType} /></p>
<p><button onClick={getCoinsFunction}>get Coins</button></p>
<textarea
style={{ ...coins ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={coins}></textarea>
</div>
{/* splitCoin */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>split Coin</h3>
<p>split coin object id : <input onChange={changeSplitCoin} style={{ width: '500px' }} value={splitCoin} /></p>
<p>split amount : <input onChange={changeSplitAmount} style={{ width: '500px' }} value={splitAmount} /></p>
<p><button onClick={splitCoinFunction}>split Coin</button></p>
<textarea
style={{ ...splitResult ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={splitResult}></textarea>
</div>
{/* mergeCoin */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>merge Coin</h3>
<p>main coin object id : <input onChange={changeMainCoin} style={{ width: '500px' }} value={mainCoin} /></p>
<p>merge coin object id : <input onChange={changeMergeCoin} style={{ width: '500px' }} value={mergeCoin} /></p>
<p><button onClick={mergeCoinFunction}>merge Coin</button></p>
<textarea
style={{ ...mergeResult ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={mergeResult}></textarea>
</div>
{/* moveCall */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>move call</h3>
<p>packageId : <input onChange={changePackageId} style={{ width: '500px' }} value={packageId} /></p>
<p>moduleName : <input onChange={changeModuleName} style={{ width: '500px' }} value={moduleName} /></p>
<p>functionName : <input onChange={changeFunctionName} style={{ width: '500px' }} value={functionName} /></p>
<br />
<p>Salary Box : <input onChange={changeSalaryBox} style={{ width: '500px' }} value={salaryBox} /></p>
<p>Salary Box Cap : <input onChange={changeSalaryBoxCap} style={{ width: '500px' }} value={salaryBoxCap} /></p>
<p>Sender : <input onChange={changeSender} style={{ width: '500px' }} value={sender} /></p>
<p>Coin : <input onChange={changeCoin} style={{ width: '500px' }} value={coin} /></p>
<p><button onClick={moveCallFunction}>move call</button></p>
<textarea
style={{ ...moveCallResult ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={moveCallResult}></textarea>
</div>
{/* queryOneEvent */}
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>One Event</h3>
<textarea
style={{ ...oneEventResult ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={oneEventResult}></textarea>
</div>
<div style={{ border: 'groove', marginBottom: '20px', width: '1010px' }}>
<h3>query All Event</h3>
<p><button onClick={queryAllEvents}>queryAllEvents</button></p>
<textarea
style={{ ...allEventResult ? displayBlock : displayNone, height: '400px', width: '1000px' }}
readOnly
value={allEventResult}></textarea>
</div>
</div>
</div >
)
}
export default App
```
先安裝所需要的lib
```cmd
npm install
```
接下來把Dapp專案跑起來
```cmd
npm run dev
```
我們先把Gas拆分出來,以利接下來的操作

拆分出來的Coin Object ID為0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22
接下來我們去跟智能合約做互動,呼叫Move Call
packageId : 0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed
moduleName : salary
functionName : create_new_salary_box
Sender : 0xadb496783149470deef1069876266948d913d9ab5795be99bf9fbd96e8a24039
Coin : 0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22

這個digest就是我們的Event ID
我們也可以看到下方one event區塊(呼叫 Moce Call自動執行)
有長出剛剛交易區塊的Event資訊,並且跟剛剛產生的資訊都一致

也可以使用query All Event來查詢該用戶在我們智能合約上所產生的所有Events

接下來我們一樣去跟智能合約互動,呼叫Move Call
我們要把保管的Coin發送出去
packageId : 0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed
moduleName : salary
functionName : send_salary
Salary Box : 0xd47e0889a72745fe24c3e1a4f829de2edfd8ff34ad6414351f8872a22d8731f4
Salary Box Cap : 0x78f8f8be9bb73243613851802e73c2bfa6c6bcf2bc3a50d2ca1f952d4f657207

我們看是否有把Coin Object ID : 0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22
發送給 0xadb496783149470deef1069876266948d913d9ab5795be99bf9fbd96e8a24039
我們去用get Coins來查詢用戶底下的Coin
發現的確把 0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22發送給該用戶了
