From a7ab99e1006f5a3b7b2f830f2ab6e15203dede73 Mon Sep 17 00:00:00 2001 From: altpd13 <69383768+altpd13@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:44:57 +0900 Subject: [PATCH] Injective/feature/deploy to in evm (#55) --- package-lock.json | 61 ++- package.json | 1 + src/components/injective/AtAddress.tsx | 5 - src/components/injective/Compiler.tsx | 161 ++++-- src/components/injective/DeployInEVM.tsx | 470 ++++++++++++++++++ src/components/injective/MetaMaskConnect.tsx | 89 +++- src/components/injective/Project.tsx | 138 ++--- src/components/injective/StoreCode.tsx | 2 +- .../injective/WalletContextProvider.tsx | 67 ++- src/components/injective/injective-helper.ts | 46 ++ 10 files changed, 905 insertions(+), 135 deletions(-) create mode 100644 src/components/injective/DeployInEVM.tsx diff --git a/package-lock.json b/package-lock.json index a528dd0..1729700 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "cosmwasm": "^1.1.1", "customize-cra": "^1.0.0", "eslint-config-react-app": "^7.0.1", + "ethers": "^6.13.4", "fs": "^0.0.1-security", "fzstd": "^0.1.0", "hash-wasm": "^4.9.0", @@ -23589,9 +23590,9 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/ethers": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.1.tgz", - "integrity": "sha512-hdJ2HOxg/xx97Lm9HdCWk949BfYqYWpyw4//78SiwOLgASyfrNszfMUNB2joKjvGUdwhHfaiMMFFwacVVoLR9A==", + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", "funding": [ { "type": "individual", @@ -23606,9 +23607,9 @@ "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", - "@types/node": "18.15.13", + "@types/node": "22.7.5", "aes-js": "4.0.0-beta.5", - "tslib": "2.4.0", + "tslib": "2.7.0", "ws": "8.17.1" }, "engines": { @@ -23638,14 +23639,22 @@ } }, "node_modules/ethers/node_modules/@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/ethers/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/ethers/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/ethers/node_modules/ws": { "version": "8.17.1", @@ -62965,16 +62974,16 @@ } }, "ethers": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.1.tgz", - "integrity": "sha512-hdJ2HOxg/xx97Lm9HdCWk949BfYqYWpyw4//78SiwOLgASyfrNszfMUNB2joKjvGUdwhHfaiMMFFwacVVoLR9A==", + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", "requires": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", - "@types/node": "18.15.13", + "@types/node": "22.7.5", "aes-js": "4.0.0-beta.5", - "tslib": "2.4.0", + "tslib": "2.7.0", "ws": "8.17.1" }, "dependencies": { @@ -62992,14 +63001,22 @@ "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" }, "@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "requires": { + "undici-types": "~6.19.2" + } }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "ws": { "version": "8.17.1", diff --git a/package.json b/package.json index fa69ba9..c142f37 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "cosmwasm": "^1.1.1", "customize-cra": "^1.0.0", "eslint-config-react-app": "^7.0.1", + "ethers": "^6.13.4", "fs": "^0.0.1-security", "fzstd": "^0.1.0", "hash-wasm": "^4.9.0", diff --git a/src/components/injective/AtAddress.tsx b/src/components/injective/AtAddress.tsx index 7fbfbac..5b82ade 100644 --- a/src/components/injective/AtAddress.tsx +++ b/src/components/injective/AtAddress.tsx @@ -18,11 +18,6 @@ import { Form, InputGroup, Button } from 'react-bootstrap'; import { useWalletStore } from './WalletContextProvider'; import { log } from '../../utils/logger'; -interface AtAddressProps { - isAtAddr: boolean; - setItAtAddr: Dispatch>; -} - const AtAddress = () => { // At Address const [queryFunctionNames, setQueryFunctionNames] = useState([]); diff --git a/src/components/injective/Compiler.tsx b/src/components/injective/Compiler.tsx index 81b5d60..d16231c 100644 --- a/src/components/injective/Compiler.tsx +++ b/src/components/injective/Compiler.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, useEffect, useState } from 'react'; +import React, { Dispatch, useEffect, useMemo, useState } from 'react'; import { log } from '../../utils/logger'; import { isEmptyList, isNotEmptyList } from '../../utils/ListUtil'; import { FileInfo, FileUtil } from '../../utils/FileUtil'; @@ -31,6 +31,12 @@ import { FaSyncAlt } from 'react-icons/fa'; import AlertCloseButton from '../common/AlertCloseButton'; import { StoreCode } from './StoreCode'; import { useWalletStore } from './WalletContextProvider'; +import DeployInEVM from './DeployInEVM'; +import { + ABIDescription, + CompilationFileSources, + CompilationResult, +} from '@remixproject/plugin-api'; interface InterfaceProps { fileName: string; @@ -39,6 +45,17 @@ interface InterfaceProps { client: any; } +//For solidity +export interface CompilationDetails { + contractList: { file: string; name: string }[]; + contractsDetails: Record; + target?: string; + input?: Record; +} +export interface ContractsFile { + [currentFile: string]: CompilationDetails; +} + const RCV_EVENT_LOG_PREFIX = `[==> EVENT_RCV]`; const SEND_EVENT_LOG_PREFIX = `[EVENT_SEND ==>]`; @@ -60,9 +77,14 @@ export const Compiler: React.FunctionComponent = ({ const [schemaExec, setSchemaExec] = useState({}); const [schemaQuery, setSchemaQuery] = useState({}); + const [isSolidity, setIsSolidity] = useState(false); + const [currentSolidityFile, setCurrentSolidityFile] = useState(''); + const [abi, setAbi] = useState([]); + const [bytecode, setBytecode] = useState(''); + const [uploadCodeChecked, setUploadCodeChecked] = useState(true); - const { injectiveAddress, chainId, walletType } = useWalletStore(); + const { injectiveAddress, chainId, walletType, isInEVM } = useWalletStore(); useEffect(() => { exists(); @@ -599,6 +621,48 @@ export const Compiler: React.FunctionComponent = ({ } }; + const getFileExtension = (path: string) => { + const part = path.split('.'); + + return part[part.length - 1]; + }; + + //inEVM Compile via Remix Client + useEffect(() => { + client.on('fileManager', 'currentFileChanged', async (currentFile: string) => { + if (getFileExtension(currentFile) === 'sol') { + setIsSolidity(true); + setCurrentSolidityFile(currentFile); + } else { + setIsSolidity(false); + setCurrentSolidityFile(''); + } + }); + + client.on( + 'solidity', + 'compilationFinished', + async ( + fileName: string, + source: CompilationFileSources, + languageVersion: string, + data: CompilationResult, + ) => { + const selectedCompiledContract = data.contracts[fileName]; + const contractName = Object.keys(selectedCompiledContract)[0]; + const bytecode = data.contracts[fileName][contractName].evm.bytecode.object; + const abi = data.contracts[fileName][contractName].abi; + setAbi(abi); + setBytecode(bytecode); + }, + ); + + // return () => { + // client.off('fileManager', 'currentFileChanged'); + // client.off('solidity', 'compilationFinished'); + // }; + }, []); + const handleAlertClose = () => { setCompileError(''); client.call('editor', 'discardHighlight'); @@ -607,36 +671,58 @@ export const Compiler: React.FunctionComponent = ({ return ( <> -
- - - +
+ )} + {/* If Network is inEVM and wallet is MetaMask Compile solidity contract and deploy it */} {walletType === 'metamask' ? ( - + isInEVM ? ( + + ) : ( + + ) ) : ( + + + {deploymentResult && ( +
+ + Deployment Successful! + Address + + Transaction Hash + + + {/* Contract Interaction */} + + + + {`${deploymentResult.address!.substring( + 0, + 6, + )}...${deploymentResult.address!.substring(38)}`} + + + {sortedAbi.map((abiItem, id) => ( + + + {abiItem.name} + + + + + + + + ))} + + +
+ )} + + ); +}; + +const DrawMethod = ({ + abi, + contractAddr, + client, +}: { + abi: any; + contractAddr: string; + client: any; +}) => { + const [error, setError] = React.useState(''); + const [value, setValue] = React.useState(''); + const [args, setArgs] = React.useState<{ [key: string]: string }>({}); + const [result, setResult] = React.useState<{ [key: string]: string }>({}); + + const { ethAddress, chainId } = useWalletStore(); + + const buttonVariant = (stateMutability: string | undefined): string => { + switch (stateMutability) { + case 'view': + case 'pure': + return 'primary'; + case 'nonpayable': + return 'warning'; + case 'payable': + return 'danger'; + default: + break; + } + return ''; + }; + + useEffect(() => { + const temp: { [key: string]: string } = {}; + abi.inputs?.forEach((element: { name: string | number }) => { + temp[element.name] = ''; + }); + setArgs(temp); + }, [abi.inputs]); + + return ( + <> + { + args[name] = value2; + }} + /> + + {/* setSuccess('')} dismissible hidden={success === ''}> + {success} + */} +
+ + + + + + ); +}; + +const Method = ({ abi, setArgs }: any) => { + const [inputs, setInputs] = useState([]); + useEffect(() => { + setInputs(abi && abi.inputs ? abi.inputs : []); + }, [abi]); + function DrawInputs() { + const items = inputs.map((item: AbiInput, index: number) => ( + + + {item.name} + + ) => { + if (event.target.value[0] === '[') { + setArgs(event.target.name, JSON.parse(event.target.value)); + } else { + setArgs(event.target.name, event.target.value); + } + }} + /> + + )); + return {items}; + } + + return
{DrawInputs()}
; +}; + +const CustomToggle = ({ children, eventKey }: any) => { + const decoratedOnClick = useAccordionButton(eventKey, () => {}); + + return ( +
+ {children} +
+ ); +}; + +export default DeployInEVM; diff --git a/src/components/injective/MetaMaskConnect.tsx b/src/components/injective/MetaMaskConnect.tsx index b969403..0370e49 100644 --- a/src/components/injective/MetaMaskConnect.tsx +++ b/src/components/injective/MetaMaskConnect.tsx @@ -6,6 +6,8 @@ import { Alert, Button, Form, InputGroup } from 'react-bootstrap'; import AlertCloseButton from '../common/AlertCloseButton'; import { MsgSend } from '@injectivelabs/sdk-ts'; import { BigNumberInBase } from '@injectivelabs/utils'; +import { log } from '../../utils/logger'; +import { ethers } from 'ethers'; const MetaMaskConnect: React.FunctionComponent = () => { const [error, setError] = useState(''); @@ -17,11 +19,17 @@ const MetaMaskConnect: React.FunctionComponent = () => { init, injectiveBroadcastMsg, walletStrategy, + isInEVM, + inEVMBalance, + ethAddress, } = useWalletStore(); + const networks = useMemo( () => [ { name: 'Mainnet', value: ChainId.Mainnet }, { name: 'Testnet', value: ChainId.Testnet }, + { name: 'inEVM Mainnet', value: '2525' }, + { name: 'inEVM Testnet', value: '2424' }, ], [], ); @@ -30,8 +38,81 @@ const MetaMaskConnect: React.FunctionComponent = () => { init(Wallet.Metamask); }, []); - const handleNetwork = (e: any) => { - setChainId(e.target.value); + const handleNetwork = async (e: any) => { + if (!window.ethereum) { + log.error('Something is wrong with MetaMask'); + return; + } + //Check if it's inEVM + if (e.target.value === '2525' || e.target.value === '2424') { + setChainId(e.target.value); + try { + switch (e.target.value) { + case '2525': { + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x9DD' }], + }); + break; + } + case '2424': { + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0x978' }], + }); + break; + } + } + } catch (switchError: any) { + // This error code indicates that the chain has not been added to MetaMask. + if (switchError.code === 4902) { + try { + switch (e.target.value) { + case '2525': { + await window.ethereum.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: '0x9DD', + chainName: 'inEVM', + rpcUrls: ['https://mainnet.rpc.inevm.com/http'], + nativeCurrency: { + name: 'INJ', + symbol: 'INJ', + decimals: 18, + }, + }, + ], + }); + break; + } + case '2424': { + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [ + { + chainId: '0x978', + chainName: 'inEVM testnet', + rpcUrls: ['https://testnet.rpc.inevm.com/http'], + nativeCurrency: { + name: 'INJ', + symbol: 'INJ', + decimals: 18, + }, + }, + ], + }); + break; + } + } + } catch (addError) { + log.error(addError); + } + } + } + } else { + setChainId(e.target.value); + } }; return (
@@ -57,7 +138,7 @@ const MetaMaskConnect: React.FunctionComponent = () => { @@ -69,7 +150,7 @@ const MetaMaskConnect: React.FunctionComponent = () => { diff --git a/src/components/injective/Project.tsx b/src/components/injective/Project.tsx index f40d308..213656e 100644 --- a/src/components/injective/Project.tsx +++ b/src/components/injective/Project.tsx @@ -32,6 +32,7 @@ export const Project: React.FunctionComponent = ({ client }) => const [contractAddress, setContractAddress] = useState(''); const [contractAddressInputDraft, setContractAddressInputDraft] = useState(''); const [contractAddressError, setContractAddressError] = useState(''); + const { isInEVM } = useWalletStore(); useEffect(() => { getList(); @@ -107,7 +108,7 @@ export const Project: React.FunctionComponent = ({ client }) => value: projectName + ' is created successfully.', }); } catch (e: any) { - console.error(e); + log.error(e); await client.terminal.log({ type: 'error', value: e.message, @@ -160,7 +161,7 @@ export const Project: React.FunctionComponent = ({ client }) => value: template + ' is created successfully.', }); } catch (e: any) { - console.error(e); + log.error(e); await client.terminal.log({ type: 'error', value: e.message, @@ -201,67 +202,76 @@ export const Project: React.FunctionComponent = ({ client }) => return (
-
- - - NEW PROJECT - - - - - - - - - SELECT A TEMPLATE - - - - {templateList.map((temp, idx) => { - return ( - - ); - })} - - - - - - - TARGET PROJECT - - - - - - - {projectList.map((projectName, idx) => { - return ( - - ); - })} - - - -
+ {isInEVM ? ( + <> + ) : ( +
+ + + NEW PROJECT + + + + + + + + + SELECT A TEMPLATE + + + + {templateList.map((temp, idx) => { + return ( + + ); + })} + + + + + + + TARGET PROJECT + + + + + + + {projectList.map((projectName, idx) => { + return ( + + ); + })} + + + +
+ )} = ({ client }) => compileTarget={compileTarget} client={client} /> - + {isInEVM ? <> : }
); }; diff --git a/src/components/injective/StoreCode.tsx b/src/components/injective/StoreCode.tsx index f6386a9..dbe7016 100644 --- a/src/components/injective/StoreCode.tsx +++ b/src/components/injective/StoreCode.tsx @@ -119,7 +119,7 @@ export const StoreCode: React.FunctionComponent = ({ disabled={true} className="btn btn-primary btn-block d-block w-100 text-break remixui_disabled mb-1 mt-3" > - Ethereum Native Wallets Can't Deploy Smart Contracts on Injective + Contract Deployment Not Supported on MetaMask ) : (