# 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下方的超連結 ![](https://hackmd.io/_uploads/H1yi-fU3n.png) 選擇Events,然後點擊View Event Data,會看到出現我們設置的Event Object跟及欄位內容 可以發現跟我們智能合約所設置的SalaryAndCapEvent欄位一模一樣 ![](https://hackmd.io/_uploads/rJB6-ML22.png) ```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 ![](https://hackmd.io/_uploads/r1EDzML3h.png) 執行成功後,我們用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拆分出來,以利接下來的操作 ![](https://hackmd.io/_uploads/HJlSDp7U22.png) 拆分出來的Coin Object ID為0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22 接下來我們去跟智能合約做互動,呼叫Move Call packageId : 0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed moduleName : salary functionName : create_new_salary_box Sender : 0xadb496783149470deef1069876266948d913d9ab5795be99bf9fbd96e8a24039 Coin : 0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22 ![](https://hackmd.io/_uploads/S1_gA7Uh2.png) 這個digest就是我們的Event ID 我們也可以看到下方one event區塊(呼叫 Moce Call自動執行) 有長出剛剛交易區塊的Event資訊,並且跟剛剛產生的資訊都一致 ![](https://hackmd.io/_uploads/rykOCQIh3.png) 也可以使用query All Event來查詢該用戶在我們智能合約上所產生的所有Events ![](https://hackmd.io/_uploads/SydPyNLnh.png) 接下來我們一樣去跟智能合約互動,呼叫Move Call 我們要把保管的Coin發送出去 packageId : 0xb15a33a3e0767840e9311ef5173420b69bead7177c80021656a3cac290bab2ed moduleName : salary functionName : send_salary Salary Box : 0xd47e0889a72745fe24c3e1a4f829de2edfd8ff34ad6414351f8872a22d8731f4 Salary Box Cap : 0x78f8f8be9bb73243613851802e73c2bfa6c6bcf2bc3a50d2ca1f952d4f657207 ![](https://hackmd.io/_uploads/rJj0JELhh.png) 我們看是否有把Coin Object ID : 0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22 發送給 0xadb496783149470deef1069876266948d913d9ab5795be99bf9fbd96e8a24039 我們去用get Coins來查詢用戶底下的Coin 發現的確把 0xfcf5843ab8b2328b14678de125e1469cbf5c916c19d197a6b8110d85a1bcfa22發送給該用戶了 ![](https://hackmd.io/_uploads/BkqOgN82n.png)