From 75ac57132c025c9ab26255b515850aa77db2c444 Mon Sep 17 00:00:00 2001 From: Le Khac Thanh Tung Date: Sat, 23 Mar 2024 15:57:13 +0700 Subject: [PATCH 1/5] feat: sui binary file --- src/extension.ts | 6 +++--- src/suiCommand.ts | 22 ++++++++++++++++------ src/webview/App.tsx | 15 ++++++++++----- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index fff25c5..4454f5c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -64,15 +64,15 @@ export function activate(context: vscode.ExtensionContext) { break; case "SUI_TERMINAL": - executeCommand(payload.command); + executeCommand(payload.command, payload.suiPath); break; case "BUILD": - build(payload.path); + build(payload.packagePath, payload.suiPath); break; case "PUBLISH": - publish(payload.path); + publish(payload.packagePath, payload.suiPath); break; case "SAVE_ALIASES": diff --git a/src/suiCommand.ts b/src/suiCommand.ts index 38f8ae4..973844f 100644 --- a/src/suiCommand.ts +++ b/src/suiCommand.ts @@ -1,19 +1,29 @@ import * as vscode from 'vscode'; -export const build = (path: string) => { +const handleSuiPathEmpty = (suiPath: string) => { + if (!suiPath) { + return "sui"; + } + return suiPath; +} + +export const build = (path: string, suiPath: string) => { + suiPath = handleSuiPathEmpty(suiPath); const terminal = vscode.window.createTerminal("Sui Simulator"); - terminal.sendText(`sui move build -p ${path}`); + terminal.sendText(`${suiPath} move build -p ${path}`); terminal.show(); }; -export const publish = (path: string) => { +export const publish = (path: string, suiPath: string) => { + suiPath = handleSuiPathEmpty(suiPath); const terminal = vscode.window.createTerminal("Sui Simulator"); - terminal.sendText(`sui client publish --gas-budget 100000000 ${path}`); + terminal.sendText(`${suiPath} client publish --gas-budget 100000000 ${path}`); terminal.show(); }; -export const executeCommand = (command: string) => { +export const executeCommand = (command: string, suiPath: string) => { + suiPath = handleSuiPathEmpty(suiPath); const terminal = vscode.window.createTerminal("Sui Simulator"); - terminal.sendText(command); + terminal.sendText(`${suiPath} ${command}`); terminal.show(); }; \ No newline at end of file diff --git a/src/webview/App.tsx b/src/webview/App.tsx index a949b2f..5aed4a2 100644 --- a/src/webview/App.tsx +++ b/src/webview/App.tsx @@ -28,6 +28,7 @@ export const App: React.FunctionComponent = ({ }: React.PropsWithChil const [buildPath, setBuildPath] = useState(""); const [publishPath, setPublishPath] = useState(""); + const [suiPath, setSuiPath] = useState(""); let keypair: Ed25519Keypair | null = null; @@ -132,20 +133,24 @@ export const App: React.FunctionComponent = ({ }: React.PropsWithChil <>

Sui Simulator


+

+ Setup Sui +

+ setSuiPath(e.target.value)} />

Network


-

Buikd

- setBuildPath(e.target.value)} /> -

Publish

- setPublishPath(e.target.value)} /> -
From 5da671fbe0ec6a6530144c58e524cc287beb33ef Mon Sep 17 00:00:00 2001 From: Le Khac Thanh Tung Date: Sun, 24 Mar 2024 10:29:38 +0700 Subject: [PATCH 2/5] feat: enter package id -> choose module -> choose function --- src/enums/index.ts | 19 ++ src/types/index.ts | 23 +++ src/webview/App.tsx | 226 +++++++-------------- src/webview/features/moveCall/v1/index.tsx | 151 ++++++++++++++ src/webview/features/moveCall/v2/index.tsx | 167 +++++++++++++++ src/webview/style.css | 14 -- src/webview/utils/suiPackage.ts | 13 -- 7 files changed, 435 insertions(+), 178 deletions(-) create mode 100644 src/enums/index.ts create mode 100644 src/types/index.ts create mode 100644 src/webview/features/moveCall/v1/index.tsx create mode 100644 src/webview/features/moveCall/v2/index.tsx delete mode 100644 src/webview/utils/suiPackage.ts diff --git a/src/enums/index.ts b/src/enums/index.ts new file mode 100644 index 0000000..63d039d --- /dev/null +++ b/src/enums/index.ts @@ -0,0 +1,19 @@ +export enum MoveCallStatus { + ENTER_PACKAGE_ID, + CHOOSE_MODULE, + CHOOSE_FUNCTION, + ENTER_ARGS, + CALL, + FINISH, + ERROR, +}; + +export enum MoveCallActionType { + SET_MNEMONICS = "SET_MNEMONICS", + SET_PACKAGE_ID = "SET_PACKAGE_ID", + SET_MODULES = "SET_MODULES", + SET_ERROR = "SET_ERROR", + SET_CURRENT_MODULE = "SET_CURRENT_MODULE", + SET_FUNCTIONS = "SET_FUNCTIONS", + SET_CURRENT_FUNCTION = "SET_CURRENT_FUNCTION", +}; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..b0967ec --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,23 @@ +import { SuiMoveNormalizedFunction, SuiMoveNormalizedType } from "@mysten/sui.js/client"; +import { MoveCallStatus } from "../enums"; + +export interface ActionType { + type: string, + payload: any +}; + +export interface MoveCallState { + mnemonics: string, + status: MoveCallStatus, + packageId: string, + modules: string[], + currentModule: string, + functions: { + [key: string]: SuiMoveNormalizedFunction; + }, + currentFunction: string; + args: SuiMoveNormalizedType[], + argsUserInput: [], + error: string, + response: string, +} \ No newline at end of file diff --git a/src/webview/App.tsx b/src/webview/App.tsx index 5aed4a2..57d048b 100644 --- a/src/webview/App.tsx +++ b/src/webview/App.tsx @@ -1,13 +1,81 @@ -import React, { useEffect, useState } from "react"; +import React, { useReducer, useState } from "react"; import "./style.css"; import { useSuiClient, useSuiClientContext } from "@mysten/dapp-kit"; -import { DEFAULT_ED25519_DERIVATION_PATH, Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519"; -import { TransactionArgument, TransactionBlock } from "@mysten/sui.js/transactions"; import { Input } from "./components/Input"; import { Button } from "./components/Button"; import { Aliases } from './components/Aliases'; import { sendMessage } from "./utils/wv_communicate_ext"; -import { getDetailPackage } from "./utils/suiPackage"; +import { ActionType, MoveCallState } from "../types"; +import { MoveCallActionType, MoveCallStatus } from "../enums"; +import { MoveCall } from "./features/moveCall/v2"; + +const initialState: MoveCallState = { + mnemonics: "mouse hood crucial soup report axis awful point stairs guess scrap winter", + status: MoveCallStatus.ENTER_PACKAGE_ID, + packageId: "0xcab68c8cd7e80f3dd06466da6b2c083d1fd50ab3e9be8e32395c19b53021c064", + modules: [], + currentModule: "", + functions: {}, + currentFunction: "", + args: [], + argsUserInput: [], + error: "", + response: "", +}; + +const reducer = (state: MoveCallState, action: ActionType): MoveCallState => { + const { type, payload } = action; + switch (type) { + case MoveCallActionType.SET_MNEMONICS: + return { + ...state, + mnemonics: payload, + }; + case MoveCallActionType.SET_PACKAGE_ID: + return { + ...state, + status: MoveCallStatus.ENTER_PACKAGE_ID, + packageId: payload, + }; + case MoveCallActionType.SET_MODULES: + return { + ...state, + status: MoveCallStatus.CHOOSE_MODULE, + modules: payload, + }; + + case MoveCallActionType.SET_CURRENT_MODULE: + return { + ...state, + status: MoveCallStatus.CHOOSE_FUNCTION, + currentModule: payload, + }; + + case MoveCallActionType.SET_FUNCTIONS: + return { + ...state, + status: MoveCallStatus.ENTER_ARGS, + functions: payload, + }; + + case MoveCallActionType.SET_ERROR: + return { + ...state, + status: MoveCallStatus.ERROR, + error: payload, + }; + + case MoveCallActionType.SET_CURRENT_FUNCTION: + return { + ...state, + status: MoveCallStatus.CALL, + currentFunction: payload, + }; + + default: + throw new Error(`Unhandled action type: ${type}`); + } +}; export interface IAppProps { } @@ -15,120 +83,16 @@ export const App: React.FunctionComponent = ({ }: React.PropsWithChil const suiClient = useSuiClient(); const { network, selectNetwork } = useSuiClientContext(); - const [mnemonics, setMnemonics] = useState("mouse hood crucial soup report axis awful point stairs guess scrap winter"); - - const [packageId, setPackageId] = useState("0xcab68c8cd7e80f3dd06466da6b2c083d1fd50ab3e9be8e32395c19b53021c064"); - const [module, setModule] = useState("counter"); - const [functionName, setFunctionName] = useState("create"); - const [args, setArgs] = useState([]); - - const [error, setError] = useState(""); - const [isError, setIsError] = useState(false); - const [response, setResponse] = useState(""); + const [state, dispatch] = useReducer(reducer, initialState); const [buildPath, setBuildPath] = useState(""); const [publishPath, setPublishPath] = useState(""); const [suiPath, setSuiPath] = useState(""); - let keypair: Ed25519Keypair | null = null; - const handleNetworkChange = (e: React.ChangeEvent) => { selectNetwork(e.target.value); }; - const handleAddArgs = () => { - setArgs((prevArgs) => [...prevArgs, { - index: null, - kind: "Input", - value: null, - type: null - }]); - }; - - const handleDeleteArgs = (index: number) => { - setArgs((prevArgs) => prevArgs.filter((_, i) => i !== index)); - }; - - const handleMnemonicsChange = (e: React.ChangeEvent) => { - setMnemonics(e.target.value); - }; - - const handleCall = async () => { - try { - keypair = Ed25519Keypair.deriveKeypair( - mnemonics, - DEFAULT_ED25519_DERIVATION_PATH, - ); - const privateKey = keypair.getSecretKey(); - const publicKey = keypair.getPublicKey(); - const address = publicKey.toSuiAddress(); - - const txb = new TransactionBlock(); - txb.setSender(address); - txb.setGasOwner(address); - txb.setGasPrice(10000); - - // add arguments to the transaction - for (const arg of args) { - if (arg.type === "object") { - txb.object(arg.value); - } else { - txb.pure(arg.value); - } - } - - txb.moveCall({ - arguments: args, - target: `${packageId}::${module}::${functionName}` - }); - - const txBytes = await txb.build({ client: suiClient }); - - const serializedSignature = (await keypair.signTransactionBlock(txBytes)) - .signature; - - const response = await suiClient.executeTransactionBlock({ - transactionBlock: txBytes, - signature: [serializedSignature], - options: { - showEffects: true, - showObjectChanges: true, - showBalanceChanges: true, - showEvents: true, - showInput: true, - showRawInput: true, - }, - }); - - setIsError(false); - - const executionStatus = response.effects?.status; - if (executionStatus?.status === "failure") { - setError(executionStatus?.error); - } else { - setResponse(JSON.stringify(response.digest)); - } - return response; - } catch (err: any) { - setIsError(true); - setError(err.message); - return err; - } - }; - - useEffect(() => { - getDetailPackage(suiClient, "0xcab68c8cd7e80f3dd06466da6b2c083d1fd50ab3e9be8e32395c19b53021c064").then((data) => { - const modules = Object.keys(data as {}); - - if (data) { - for (const module of modules) { - const { exposedFunctions } = data[module]; - - } - } - }).catch(err => console.log(err)); - }, []); - return ( <>

Sui Simulator

@@ -154,49 +118,9 @@ export const App: React.FunctionComponent = ({ }: React.PropsWithChil Publish
-

Call

-
- Mnemonics: - -
-
- Package: - setPackageId(e.target.value)} /> -
-
- Module: - setModule(e.target.value)} /> -
-
- Function: - setFunctionName(e.target.value)} /> -
-

Args

- {args.map((arg, index) => { - return <> { - const newArgs = [...args]; - newArgs[index] = { - ...newArgs[index], - index, - value: e.target.value, - type: e.target.value.startsWith("0x") ? "object" : "pure" - }; - setArgs((newArgs)); - }} /> - - ; - } - )} - - - {!isError ?

Result: {response}

:

Error: {error}

} + +
diff --git a/src/webview/features/moveCall/v1/index.tsx b/src/webview/features/moveCall/v1/index.tsx new file mode 100644 index 0000000..c5f0817 --- /dev/null +++ b/src/webview/features/moveCall/v1/index.tsx @@ -0,0 +1,151 @@ +import React, { useState } from 'react'; +import { Button } from '../../../components/Button'; +import { Input } from '../../../components/Input'; +import { DEFAULT_ED25519_DERIVATION_PATH, Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519'; +import { TransactionArgument, TransactionBlock } from '@mysten/sui.js/transactions'; +import { useSuiClient } from '@mysten/dapp-kit'; + +export default function index() { + const suiClient = useSuiClient(); + + const [mnemonics, setMnemonics] = useState("mouse hood crucial soup report axis awful point stairs guess scrap winter"); + + const [packageId, setPackageId] = useState("0xcab68c8cd7e80f3dd06466da6b2c083d1fd50ab3e9be8e32395c19b53021c064"); + const [module, setModule] = useState("counter"); + const [functionName, setFunctionName] = useState("create"); + const [args, setArgs] = useState([]); + + const [error, setError] = useState(""); + const [isError, setIsError] = useState(false); + const [response, setResponse] = useState(""); + + let keypair: Ed25519Keypair | null = null; + + const handleAddArgs = () => { + setArgs((prevArgs) => [...prevArgs, { + index: null, + kind: "Input", + value: null, + type: null + }]); + }; + + const handleDeleteArgs = (index: number) => { + setArgs((prevArgs) => prevArgs.filter((_, i) => i !== index)); + }; + + const handleMnemonicsChange = (e: React.ChangeEvent) => { + setMnemonics(e.target.value); + }; + + const handleCall = async () => { + try { + keypair = Ed25519Keypair.deriveKeypair( + mnemonics, + DEFAULT_ED25519_DERIVATION_PATH, + ); + const privateKey = keypair.getSecretKey(); + const publicKey = keypair.getPublicKey(); + const address = publicKey.toSuiAddress(); + + const txb = new TransactionBlock(); + txb.setSender(address); + txb.setGasOwner(address); + txb.setGasPrice(10000); + + // add arguments to the transaction + for (const arg of args) { + if (arg.type === "object") { + txb.object(arg.value); + } else { + txb.pure(arg.value); + } + } + + txb.moveCall({ + arguments: args, + target: `${packageId}::${module}::${functionName}` + }); + + const txBytes = await txb.build({ client: suiClient }); + + const serializedSignature = (await keypair.signTransactionBlock(txBytes)) + .signature; + + const response = await suiClient.executeTransactionBlock({ + transactionBlock: txBytes, + signature: [serializedSignature], + options: { + showEffects: true, + showObjectChanges: true, + showBalanceChanges: true, + showEvents: true, + showInput: true, + showRawInput: true, + }, + }); + + setIsError(false); + + const executionStatus = response.effects?.status; + if (executionStatus?.status === "failure") { + setError(executionStatus?.error); + } else { + setResponse(JSON.stringify(response.digest)); + } + return response; + } catch (err: any) { + setIsError(true); + setError(err.message); + return err; + } + }; + + return ( + <> +

Call

+
+ Mnemonics: + +
+
+ Package: + setPackageId(e.target.value)} /> +
+
+ Module: + setModule(e.target.value)} /> +
+
+ Function: + setFunctionName(e.target.value)} /> +
+

Args

+ {args.map((arg, index) => { + return <> { + const newArgs = [...args]; + newArgs[index] = { + ...newArgs[index], + index, + value: e.target.value, + type: e.target.value.startsWith("0x") ? "object" : "pure" + }; + setArgs((newArgs)); + }} /> + + ; + } + + )} + + + {!isError ?

Result: {response}

:

Error: {error}

} + + ); +}; diff --git a/src/webview/features/moveCall/v2/index.tsx b/src/webview/features/moveCall/v2/index.tsx new file mode 100644 index 0000000..f05c447 --- /dev/null +++ b/src/webview/features/moveCall/v2/index.tsx @@ -0,0 +1,167 @@ +import React, { useEffect } from 'react'; +import { MoveCallState } from '../../../../types'; +import { Input } from '../../../components/Input'; +import { MoveCallActionType, MoveCallStatus } from '../../../../enums'; +import { useSuiClient } from '@mysten/dapp-kit'; +import { Button } from '../../../components/Button'; + +export interface IMoveCallProps { + state: MoveCallState; + dispatch: React.Dispatch; +} + +export const MoveCall = ({ state, dispatch }: IMoveCallProps) => { + const suiClient = useSuiClient(); + + const { mnemonics, packageId, args, argsUserInput, currentFunction, currentModule, error, functions, modules, response, status } = state; + + const handleMnemonicsChange = (e: React.ChangeEvent) => { + dispatch({ type: MoveCallActionType.SET_MNEMONICS, payload: e.target.value }); + }; + + const handlePackageIdChange = (e: React.ChangeEvent) => { + dispatch({ type: MoveCallActionType.SET_PACKAGE_ID, payload: e.target.value }); + }; + + const handleChooseModule = async (e: React.ChangeEvent) => { + if (e.target.value) { + dispatch({ type: MoveCallActionType.SET_CURRENT_MODULE, payload: e.target.value }); + + try { + const module = await suiClient.getNormalizedMoveModule({ package: packageId, module: e.target.value }); + const { exposedFunctions } = module; + dispatch({ type: MoveCallActionType.SET_FUNCTIONS, payload: exposedFunctions }); + } catch (err: any) { + dispatch({ type: MoveCallActionType.SET_ERROR, payload: err.message }); + } + } + }; + + const handleChooseFunction = async (e: React.ChangeEvent) => { + if (e.target.value) { + dispatch({ type: MoveCallActionType.SET_CURRENT_FUNCTION, payload: e.target.value }); + } + }; + + useEffect(() => { + async function getModules() { + try { + const modules = await suiClient.getNormalizedMoveModulesByPackage({ package: packageId }); + console.log(modules); + dispatch({ type: MoveCallActionType.SET_MODULES, payload: modules }); + } catch (err: any) { + dispatch({ type: MoveCallActionType.SET_ERROR, payload: err.message }); + } + } + + getModules(); + }, [packageId]); + + const modulesName = Object.keys(modules as {}); + const functionsName = Object.keys(functions as {}); + + // let keypair: Ed25519Keypair | null = null; + + // const handleCall = async () => { + // try { + // keypair = Ed25519Keypair.deriveKeypair( + // mnemonics, + // DEFAULT_ED25519_DERIVATION_PATH, + // ); + // const privateKey = keypair.getSecretKey(); + // const publicKey = keypair.getPublicKey(); + // const address = publicKey.toSuiAddress(); + + // const txb = new TransactionBlock(); + // txb.setSender(address); + // txb.setGasOwner(address); + // txb.setGasPrice(10000); + + // // add arguments to the transaction + // for (const arg of args) { + // if (arg.type === "object") { + // txb.object(arg.value); + // } else { + // txb.pure(arg.value); + // } + // } + + // txb.moveCall({ + // arguments: args, + // target: `${packageId}::${module}::${functionName}` + // }); + + // const txBytes = await txb.build({ client: suiClient }); + + // const serializedSignature = (await keypair.signTransactionBlock(txBytes)) + // .signature; + + // const response = await suiClient.executeTransactionBlock({ + // transactionBlock: txBytes, + // signature: [serializedSignature], + // options: { + // showEffects: true, + // showObjectChanges: true, + // showBalanceChanges: true, + // showEvents: true, + // showInput: true, + // showRawInput: true, + // }, + // }); + + // setIsError(false); + + // const executionStatus = response.effects?.status; + // if (executionStatus?.status === "failure") { + // setError(executionStatus?.error); + // } else { + // setResponse(JSON.stringify(response.digest)); + // } + // return response; + // } catch (err: any) { + // setIsError(true); + // setError(err.message); + // return err; + // } + // }; + + return ( + <> +

Call

+
+ Mnemonics: + +
+
+ Package: + +
+
+ Module: + {status !== MoveCallStatus.ERROR && } +
+
+ Function: + {currentModule.length > 0 && } +
+

Args

+ + + {status === MoveCallStatus.ERROR &&

Error: ${error}

} + + ); +}; diff --git a/src/webview/style.css b/src/webview/style.css index 48ec0d8..3944a4c 100644 --- a/src/webview/style.css +++ b/src/webview/style.css @@ -1,17 +1,3 @@ h1 { font-family: var(--vscode-editor-font-family); -} - -.app { - margin: 15px auto; - max-width: 80rem; -} - -.app__actions { - display: flex; - gap: 1rem; -} - -.app__error { - color: var(--vscode-editorError-foreground); } \ No newline at end of file diff --git a/src/webview/utils/suiPackage.ts b/src/webview/utils/suiPackage.ts deleted file mode 100644 index 09b730f..0000000 --- a/src/webview/utils/suiPackage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SuiClient } from "@mysten/sui.js/client"; - -export const getDetailPackage = async ( - suiClient: SuiClient, - packageId: string -) => { - try { - const data = await suiClient.getNormalizedMoveModulesByPackage({ - package: packageId, - }); - return data; - } catch (err) {} -}; From 774925c57a59451eab4b7464206b1cd8bf26c0bb Mon Sep 17 00:00:00 2001 From: Le Khac Thanh Tung Date: Sun, 24 Mar 2024 19:42:21 +0700 Subject: [PATCH 3/5] feat: show args of function => enter args => call --- src/enums/index.ts | 11 +- src/types/index.ts | 6 +- src/webview/App.tsx | 42 +++- src/webview/features/moveCall/v2/index.tsx | 221 +++++++++++++-------- 4 files changed, 180 insertions(+), 100 deletions(-) diff --git a/src/enums/index.ts b/src/enums/index.ts index 63d039d..99fb10b 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -1,9 +1,5 @@ export enum MoveCallStatus { - ENTER_PACKAGE_ID, - CHOOSE_MODULE, - CHOOSE_FUNCTION, - ENTER_ARGS, - CALL, + BEGIN, FINISH, ERROR, }; @@ -16,4 +12,9 @@ export enum MoveCallActionType { SET_CURRENT_MODULE = "SET_CURRENT_MODULE", SET_FUNCTIONS = "SET_FUNCTIONS", SET_CURRENT_FUNCTION = "SET_CURRENT_FUNCTION", + RESET_ARGS = "RESET_ARGS", + RESET_ARGS_USER_INPUT = "RESET_ARGS_USER_INPUT", + ADD_ARG = "ADD_ARG", + SET_VALUE_TO_ARG = "SET_VALUE_TO_ARG", + SET_RESPONSE = "SET_RESPONSE", }; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index b0967ec..42d384d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,4 @@ -import { SuiMoveNormalizedFunction, SuiMoveNormalizedType } from "@mysten/sui.js/client"; +import { SuiMoveNormalizedFunction } from "@mysten/sui.js/client"; import { MoveCallStatus } from "../enums"; export interface ActionType { @@ -16,8 +16,8 @@ export interface MoveCallState { [key: string]: SuiMoveNormalizedFunction; }, currentFunction: string; - args: SuiMoveNormalizedType[], - argsUserInput: [], + args: string[], + argsUserInput: string[], error: string, response: string, } \ No newline at end of file diff --git a/src/webview/App.tsx b/src/webview/App.tsx index 57d048b..cfd3e0d 100644 --- a/src/webview/App.tsx +++ b/src/webview/App.tsx @@ -11,8 +11,8 @@ import { MoveCall } from "./features/moveCall/v2"; const initialState: MoveCallState = { mnemonics: "mouse hood crucial soup report axis awful point stairs guess scrap winter", - status: MoveCallStatus.ENTER_PACKAGE_ID, - packageId: "0xcab68c8cd7e80f3dd06466da6b2c083d1fd50ab3e9be8e32395c19b53021c064", + status: MoveCallStatus.BEGIN, + packageId: "", modules: [], currentModule: "", functions: {}, @@ -34,27 +34,23 @@ const reducer = (state: MoveCallState, action: ActionType): MoveCallState => { case MoveCallActionType.SET_PACKAGE_ID: return { ...state, - status: MoveCallStatus.ENTER_PACKAGE_ID, packageId: payload, }; case MoveCallActionType.SET_MODULES: return { ...state, - status: MoveCallStatus.CHOOSE_MODULE, modules: payload, }; case MoveCallActionType.SET_CURRENT_MODULE: return { ...state, - status: MoveCallStatus.CHOOSE_FUNCTION, currentModule: payload, }; case MoveCallActionType.SET_FUNCTIONS: return { ...state, - status: MoveCallStatus.ENTER_ARGS, functions: payload, }; @@ -68,10 +64,42 @@ const reducer = (state: MoveCallState, action: ActionType): MoveCallState => { case MoveCallActionType.SET_CURRENT_FUNCTION: return { ...state, - status: MoveCallStatus.CALL, currentFunction: payload, }; + case MoveCallActionType.RESET_ARGS: + return { + ...state, + args: [], + }; + + case MoveCallActionType.RESET_ARGS_USER_INPUT: + return { + ...state, + argsUserInput: [], + }; + + case MoveCallActionType.ADD_ARG: + return { + ...state, + args: [...state.args, payload], + }; + + case MoveCallActionType.SET_VALUE_TO_ARG: + const argsUserInput = [...state.argsUserInput]; + argsUserInput[payload.index] = payload.value; + return { + ...state, + argsUserInput, + }; + + case MoveCallActionType.SET_RESPONSE: + return { + ...state, + status: MoveCallStatus.FINISH, + response: payload, + }; + default: throw new Error(`Unhandled action type: ${type}`); } diff --git a/src/webview/features/moveCall/v2/index.tsx b/src/webview/features/moveCall/v2/index.tsx index f05c447..c8e9ab6 100644 --- a/src/webview/features/moveCall/v2/index.tsx +++ b/src/webview/features/moveCall/v2/index.tsx @@ -4,6 +4,8 @@ import { Input } from '../../../components/Input'; import { MoveCallActionType, MoveCallStatus } from '../../../../enums'; import { useSuiClient } from '@mysten/dapp-kit'; import { Button } from '../../../components/Button'; +import { DEFAULT_ED25519_DERIVATION_PATH, Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519'; +import { TransactionBlock } from '@mysten/sui.js/transactions'; export interface IMoveCallProps { state: MoveCallState; @@ -40,90 +42,125 @@ export const MoveCall = ({ state, dispatch }: IMoveCallProps) => { const handleChooseFunction = async (e: React.ChangeEvent) => { if (e.target.value) { dispatch({ type: MoveCallActionType.SET_CURRENT_FUNCTION, payload: e.target.value }); + dispatch({ type: MoveCallActionType.RESET_ARGS }); + dispatch({ type: MoveCallActionType.RESET_ARGS_USER_INPUT }); + + const fn = functions[e.target.value]; + const { parameters } = fn; + for (const param of parameters) { + // handle typescript error by this way is suck => refactor later + if (typeof param === 'object') { + if ('MutableReference' in param) { + if (typeof param.MutableReference === "object" && 'Struct' in param.MutableReference) { + let { Struct: { + address, module, name + } } = param.MutableReference; + if (name !== "TxContext") { + dispatch({ type: MoveCallActionType.ADD_ARG, payload: `${address}::${module}::${name}` }); + } + } + } else if ('Reference' in param) { + + } else if ('Vector' in param) { + + } else if ('Struct' in param) { + + } else if ('TypeParameter' in param) { + + } + } + else { + dispatch({ type: MoveCallActionType.ADD_ARG, payload: param }); + } + }; } }; + const handleSetValueToArg = (index: number, value: string) => { + dispatch({ + type: MoveCallActionType.SET_VALUE_TO_ARG, + payload: { index, value }, + }); + }; + useEffect(() => { async function getModules() { try { const modules = await suiClient.getNormalizedMoveModulesByPackage({ package: packageId }); - console.log(modules); dispatch({ type: MoveCallActionType.SET_MODULES, payload: modules }); } catch (err: any) { dispatch({ type: MoveCallActionType.SET_ERROR, payload: err.message }); } } - getModules(); + if (packageId) { + getModules(); + } }, [packageId]); const modulesName = Object.keys(modules as {}); const functionsName = Object.keys(functions as {}); - // let keypair: Ed25519Keypair | null = null; - - // const handleCall = async () => { - // try { - // keypair = Ed25519Keypair.deriveKeypair( - // mnemonics, - // DEFAULT_ED25519_DERIVATION_PATH, - // ); - // const privateKey = keypair.getSecretKey(); - // const publicKey = keypair.getPublicKey(); - // const address = publicKey.toSuiAddress(); - - // const txb = new TransactionBlock(); - // txb.setSender(address); - // txb.setGasOwner(address); - // txb.setGasPrice(10000); - - // // add arguments to the transaction - // for (const arg of args) { - // if (arg.type === "object") { - // txb.object(arg.value); - // } else { - // txb.pure(arg.value); - // } - // } - - // txb.moveCall({ - // arguments: args, - // target: `${packageId}::${module}::${functionName}` - // }); - - // const txBytes = await txb.build({ client: suiClient }); - - // const serializedSignature = (await keypair.signTransactionBlock(txBytes)) - // .signature; - - // const response = await suiClient.executeTransactionBlock({ - // transactionBlock: txBytes, - // signature: [serializedSignature], - // options: { - // showEffects: true, - // showObjectChanges: true, - // showBalanceChanges: true, - // showEvents: true, - // showInput: true, - // showRawInput: true, - // }, - // }); - - // setIsError(false); - - // const executionStatus = response.effects?.status; - // if (executionStatus?.status === "failure") { - // setError(executionStatus?.error); - // } else { - // setResponse(JSON.stringify(response.digest)); - // } - // return response; - // } catch (err: any) { - // setIsError(true); - // setError(err.message); - // return err; - // } - // }; + let keypair: Ed25519Keypair | null = null; + + const handleCall = async () => { + try { + keypair = Ed25519Keypair.deriveKeypair( + mnemonics, + DEFAULT_ED25519_DERIVATION_PATH, + ); + const privateKey = keypair.getSecretKey(); + const publicKey = keypair.getPublicKey(); + const address = publicKey.toSuiAddress(); + + const txb = new TransactionBlock(); + txb.setSender(address); + txb.setGasOwner(address); + txb.setGasPrice(10000); + + const argsFinal = argsUserInput.map((ele) => { + if (ele.startsWith("0x")) { + return txb.object(ele); + } else { + return txb.pure(ele); + } + }); + + txb.moveCall({ + arguments: argsFinal, + target: `${packageId}::${currentModule}::${currentFunction}` + }); + + const txBytes = await txb.build({ client: suiClient }); + + const serializedSignature = (await keypair.signTransactionBlock(txBytes)) + .signature; + + const response = await suiClient.executeTransactionBlock({ + transactionBlock: txBytes, + signature: [serializedSignature], + options: { + showEffects: true, + showObjectChanges: true, + showBalanceChanges: true, + showEvents: true, + showInput: true, + showRawInput: true, + }, + }); + + const executionStatus = response.effects?.status; + if (executionStatus?.status === "failure") { + dispatch({ type: MoveCallActionType.SET_ERROR, payload: executionStatus?.error }); + } else { + dispatch({ type: MoveCallActionType.SET_RESPONSE, payload: JSON.stringify(response.digest) }); + } + return response; + } catch (err: any) { + dispatch({ type: MoveCallActionType.SET_ERROR, payload: err.message }); + return err; + } + }; return ( <> @@ -137,30 +174,44 @@ export const MoveCall = ({ state, dispatch }: IMoveCallProps) => {
- Module: - {status !== MoveCallStatus.ERROR && } + {modulesName.length > 0 && <> + Module: + + }
- Function: - {currentModule.length > 0 && } -
-

Args

- + {status === MoveCallStatus.FINISH &&

Result: ${response}

} {status === MoveCallStatus.ERROR &&

Error: ${error}

} ); From 7f3628d113c453ba176c617d63cec133a83048e6 Mon Sep 17 00:00:00 2001 From: Le Khac Thanh Tung Date: Sun, 24 Mar 2024 21:24:42 +0700 Subject: [PATCH 4/5] feat: add sidebar --- media/tools.svg | 1 + package.json | 22 +++++++++++- src/SidebarProvider.ts | 79 ++++++++++++++++++++++++++++++++++++++++++ src/extension.ts | 9 +++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 media/tools.svg create mode 100644 src/SidebarProvider.ts diff --git a/media/tools.svg b/media/tools.svg new file mode 100644 index 0000000..f1cd568 --- /dev/null +++ b/media/tools.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package.json b/package.json index c0dcbb5..ee2aba0 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,26 @@ "activationEvents": [], "main": "./dist/extension.js", "contributes": { + "viewsContainers": { + "activitybar": [ + { + "id": "sui-simulator-sidebar-view", + "title": "Sui Simulator", + "icon": "media/tools.svg" + } + ] + }, + "views": { + "sui-simulator-sidebar-view": [ + { + "type": "webview", + "id": "sui-simulator-sidebar", + "name": "Sui Simulator", + "icon": "media/tools.svg", + "contextualTitle": "Sui Simulator" + } + ] + }, "commands": [ { "command": "sui-simulator-vscode.helloWorld", @@ -75,4 +95,4 @@ "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" } -} +} \ No newline at end of file diff --git a/src/SidebarProvider.ts b/src/SidebarProvider.ts new file mode 100644 index 0000000..e0d6fe8 --- /dev/null +++ b/src/SidebarProvider.ts @@ -0,0 +1,79 @@ +import * as vscode from "vscode"; +import { build, executeCommand, publish } from "./suiCommand"; +import { join } from "path"; + +export class SidebarProvider implements vscode.WebviewViewProvider { + constructor(private readonly _extensionContext: vscode.ExtensionContext) { } + + public resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken) { + + webviewView.webview.options = { + // Allow scripts in the webview + enableScripts: true, + + localResourceRoots: [this._extensionContext.extensionUri], + }; + + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); + + webviewView.webview.onDidReceiveMessage((message) => { + const { command, requestId, payload } = message; + + switch (command) { + case "SUI_TERMINAL": + executeCommand(payload.command, payload.suiPath); + break; + + case "BUILD": + build(payload.packagePath, payload.suiPath); + break; + + case "PUBLISH": + publish(payload.packagePath, payload.suiPath); + break; + + case "SAVE_ALIASES": + this._extensionContext.workspaceState.update(payload.address, { + aliases: payload.aliases + }).then(() => { + vscode.window.showInformationMessage("Aliases saved successfully!"); + }); + + // use value as undefined to remove the key + // context.workspaceState.update("", undefined); + + default: + vscode.window.showInformationMessage(`Unknown command: ${command}`); + } + }); + } + + private _getHtmlForWebview(webview: vscode.Webview) { + const jsFile = "webview.js"; + const localServerUrl = "http://localhost:9999"; + + let scriptUrl = null; + let cssUrl = null; + + const isProduction = this._extensionContext.extensionMode === vscode.ExtensionMode.Production; + if (isProduction) { + scriptUrl = webview.asWebviewUri(vscode.Uri.file(join(this._extensionContext.extensionPath, 'dist', jsFile))).toString(); + } else { + scriptUrl = `${localServerUrl}/${jsFile}`; + } + + return ` + + + + + ${isProduction ? `` : ''} + + +
+ + + + `; + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 4454f5c..b70fa69 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,6 +4,7 @@ import * as vscode from 'vscode'; import { join } from 'path'; import { MessageHandlerData } from '@estruyf/vscode'; import { build, publish, executeCommand } from './suiCommand'; +import { SidebarProvider } from './SidebarProvider'; // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -22,6 +23,14 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.showInformationMessage('Hello World from sui-simulator-vscode!'); }); + const sidebarProvider = new SidebarProvider(context); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + "sui-simulator-sidebar", + sidebarProvider + ) + ); + context.subscriptions.push(disposable); context.subscriptions.push(vscode.commands.registerCommand("sui-simulator-vscode.webView", () => { From dedc9fcecc998f4bd5cd664d992c7210ce69c1f2 Mon Sep 17 00:00:00 2001 From: Le Khac Thanh Tung Date: Sun, 24 Mar 2024 21:30:28 +0700 Subject: [PATCH 5/5] chore: update version in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee2aba0..b154772 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "sui-simulator-vscode", "displayName": "sui-simulator-vscode", "description": "", - "version": "0.0.1", + "version": "0.2.0", "engines": { "vscode": "^1.87.0" },