diff --git a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json
index e1dd413634..8a09760ae9 100644
--- a/apps/remix-ide/src/app/tabs/locales/en/filePanel.json
+++ b/apps/remix-ide/src/app/tabs/locales/en/filePanel.json
@@ -29,6 +29,8 @@
"filePanel.tssoltestghaction": "Mocha Chai Test Workflow",
"filePanel.workspace.slitherghaction": "Adds a preset yml file to run slither analysis on github actions CI",
"filePanel.slitherghaction": "Slither Workflow",
+ "filePanel.workspace.helperscripts": "Adds convenient scripts to the 'scripts' directory",
+ "filePanel.helperscripts": "Web3 Scripts",
"filePanel.newFile": "New File",
"filePanel.newFolder": "New Folder",
"filePanel.rename": "Rename",
diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts
index 0e23965cdb..f83ace346b 100644
--- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts
+++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts
@@ -13,6 +13,8 @@ import { ROOT_PATH, slitherYml, solTestYml, tsSolTestYml } from '../utils/consta
import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB'
import { getUncommittedFiles } from '../utils/gitStatusFilter'
import { AppModal, ModalTypes } from '@remix-ui/app'
+import { contractDeployerScripts } from '../scripts/contract-deployer'
+import { etherscanScripts } from '../scripts/etherscan'
declare global {
interface Window { remixFileSystemCallback: IndexedDBStorage; }
@@ -677,6 +679,12 @@ export const createSlitherGithubAction = async () => {
plugin.call('fileManager', 'open', path)
}
+export const createHelperScripts = async () => {
+ await contractDeployerScripts(plugin)
+ await etherscanScripts(plugin)
+ plugin.call('notification', 'toast', 'scripts added in the "scripts" folder')
+}
+
export const checkoutRemoteBranch = async (branch: string, remote: string) => {
const localChanges = await hasLocalChanges()
diff --git a/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx b/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx
index 9941dee29a..b2400613b6 100644
--- a/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx
+++ b/libs/remix-ui/workspace/src/lib/components/workspace-hamburger.tsx
@@ -15,6 +15,7 @@ export interface HamburgerMenuProps {
addGithubAction: () => void,
addTsSolTestGithubAction: () => void,
addSlitherGithubAction: () => void,
+ addHelperScripts: () => void,
showIconsMenu: boolean,
hideWorkspaceOptions: boolean,
hideLocalhostOptions: boolean
@@ -70,6 +71,11 @@ export function HamburgerMenu (props: HamburgerMenuProps) {
props.addSlitherGithubAction()
props.hideIconsMenu(!showIconsMenu)
}}>
+
+ {
+ props.addHelperScripts()
+ props.hideIconsMenu(!showIconsMenu)
+ }}>
>
)
}
\ No newline at end of file
diff --git a/libs/remix-ui/workspace/src/lib/contexts/index.ts b/libs/remix-ui/workspace/src/lib/contexts/index.ts
index 44783948be..9af5f8e19d 100644
--- a/libs/remix-ui/workspace/src/lib/contexts/index.ts
+++ b/libs/remix-ui/workspace/src/lib/contexts/index.ts
@@ -45,6 +45,7 @@ export const FileSystemContext = createContext<{
dispatchCreateSolidityGithubAction: () => Promise,
dispatchCreateTsSolGithubAction: () => Promise,
dispatchCreateSlitherGithubAction: () => Promise
+ dispatchCreateHelperScripts: () => Promise
}>(null)
\ No newline at end of file
diff --git a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
index ae038142a7..8fb509e37a 100644
--- a/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
+++ b/libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
@@ -8,7 +8,7 @@ import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, uploadFolder, handleDownloadWorkspace, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder,
- showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction
+ showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction, createHelperScripts
} from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -183,6 +183,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await createSlitherGithubAction()
}
+ const dispatchCreateHelperScripts = async () => {
+ await createHelperScripts()
+ }
+
useEffect(() => {
dispatchInitWorkspace()
}, [])
@@ -299,7 +303,8 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchCheckoutRemoteBranch,
dispatchCreateSolidityGithubAction,
dispatchCreateTsSolGithubAction,
- dispatchCreateSlitherGithubAction
+ dispatchCreateSlitherGithubAction,
+ dispatchCreateHelperScripts
}
return (
diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
index de26194620..615180fd93 100644
--- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
+++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
@@ -209,6 +209,10 @@ export function Workspace () {
global.dispatchCreateSlitherGithubAction()
}
+ const addHelperScripts = () => {
+ global.dispatchCreateHelperScripts()
+ }
+
const downloadWorkspaces = async () => {
try {
await global.dispatchHandleDownloadFiles()
@@ -726,6 +730,7 @@ export function Workspace () {
hideIconsMenu={hideIconsMenu}
addGithubAction={addGithubAction}
addSlitherGithubAction={addSlitherGithubAction}
+ addHelperScripts={addHelperScripts}
addTsSolTestGithubAction={addTsSolTestGithubAction}
showIconsMenu={showIconsMenu}
hideWorkspaceOptions={ currentWorkspace === LOCALHOST }
diff --git a/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/create2-factory-deploy.ts b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/create2-factory-deploy.ts
new file mode 100644
index 0000000000..35a7c1b4e7
--- /dev/null
+++ b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/create2-factory-deploy.ts
@@ -0,0 +1,258 @@
+import { ethers } from 'ethers'
+
+// https://etherscan.io/address/0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2#code
+export const CREATE2_DEPLOYER_ADDRESS =
+ "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2";
+
+/**
+ * Deploy the given contract
+ * @param {string} address of the factory contract
+ * @param {string} contractName name of the contract to deploy
+ * @param {Array} args list of constructor' parameters
+ * @param {number} salt (using during address generation)
+ * @param {number} accountIndex account index from the exposed account
+ * @return {string} deployed contract address
+ */
+export const deploy = async (contractName: string, args: Array, salt: string, accountIndex?: number): Promise => {
+
+ console.log(`deploying ${contractName}`)
+
+ const signer = (new ethers.providers.Web3Provider(web3Provider)).getSigner(accountIndex)
+
+ const factory = new ethers.Contract(CREATE2_DEPLOYER_ADDRESS, contractDeployerAbi, signer);
+
+ const contract = await ethers.getContractFactory(contractName)
+ const initCode = contract.getDeployTransaction(args)
+
+ const codeHash = ethers.utils.keccak256(initCode.data)
+ const saltBytes = ethers.utils.id(salt)
+ const deployedAddress = await factory.computeAddress(saltBytes, codeHash)
+ try {
+ const tx = await factory.deploy(0, saltBytes, initCode.data)
+ await tx.wait()
+ return deployedAddress
+ } catch (e) {
+ console.error(e.message)
+ console.error(`Please check a contract isn't already deployed at that address`)
+ throw e
+ }
+}
+
+export const contractDeployerAbi = [
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "previousOwner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "OwnershipTransferred",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "Paused",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": false,
+ "internalType": "address",
+ "name": "account",
+ "type": "address"
+ }
+ ],
+ "name": "Unpaused",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "codeHash",
+ "type": "bytes32"
+ }
+ ],
+ "name": "computeAddress",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "codeHash",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "address",
+ "name": "deployer",
+ "type": "address"
+ }
+ ],
+ "name": "computeAddressWithDeployer",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "pure",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ },
+ {
+ "internalType": "bytes",
+ "name": "code",
+ "type": "bytes"
+ }
+ ],
+ "name": "deploy",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes32",
+ "name": "salt",
+ "type": "bytes32"
+ }
+ ],
+ "name": "deployERC1820Implementer",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address payable",
+ "name": "payoutAddress",
+ "type": "address"
+ }
+ ],
+ "name": "killCreate2Deployer",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "owner",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "pause",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "paused",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "renounceOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "newOwner",
+ "type": "address"
+ }
+ ],
+ "name": "transferOwnership",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "unpause",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "stateMutability": "payable",
+ "type": "receive"
+ }
+]
\ No newline at end of file
diff --git a/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/index.ts b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/index.ts
new file mode 100644
index 0000000000..bcf3e80186
--- /dev/null
+++ b/libs/remix-ui/workspace/src/lib/scripts/contract-deployer/index.ts
@@ -0,0 +1,6 @@
+export const contractDeployerScripts = async (plugin) => {
+ await plugin.call('fileManager', 'writeFile',
+ 'scripts/contract-deployer/create2-factory-deploy.ts' ,
+ // @ts-ignore
+ (await import('!!raw-loader!./create2-factory-deploy.ts')).default)
+}
\ No newline at end of file
diff --git a/libs/remix-ui/workspace/src/lib/scripts/etherscan/index.ts b/libs/remix-ui/workspace/src/lib/scripts/etherscan/index.ts
new file mode 100644
index 0000000000..34000b7ca2
--- /dev/null
+++ b/libs/remix-ui/workspace/src/lib/scripts/etherscan/index.ts
@@ -0,0 +1,11 @@
+export const etherscanScripts = async (plugin) => {
+ await plugin.call('fileManager', 'writeFile',
+ 'scripts/etherscan/verifyScript.ts' ,
+ // @ts-ignore
+ (await import('!!raw-loader!./verifyScript.ts')).default)
+
+ await plugin.call('fileManager', 'writeFile',
+ 'scripts/etherscan/receiptGuidScript.ts' ,
+ // @ts-ignore
+ (await import('!!raw-loader!./receiptGuidScript.ts')).default)
+}
\ No newline at end of file
diff --git a/libs/remix-ui/workspace/src/lib/scripts/etherscan/receiptGuidScript.ts b/libs/remix-ui/workspace/src/lib/scripts/etherscan/receiptGuidScript.ts
new file mode 100644
index 0000000000..a09a9fb780
--- /dev/null
+++ b/libs/remix-ui/workspace/src/lib/scripts/etherscan/receiptGuidScript.ts
@@ -0,0 +1,8 @@
+/**
+ * @param {string} apikey - etherscan api key.
+ * @param {string} guid - receipt id.
+ * @returns {{ status, message, succeed }} receiptStatus
+ */
+export const receiptStatus = async (apikey: string, guid: string) => {
+ return await remix.call('etherscan' as any, 'receiptStatus', guid, apikey)
+}
\ No newline at end of file
diff --git a/libs/remix-ui/workspace/src/lib/scripts/etherscan/verifyScript.ts b/libs/remix-ui/workspace/src/lib/scripts/etherscan/verifyScript.ts
new file mode 100644
index 0000000000..3531595239
--- /dev/null
+++ b/libs/remix-ui/workspace/src/lib/scripts/etherscan/verifyScript.ts
@@ -0,0 +1,13 @@
+/**
+ * @param {string} apikey - etherscan api key.
+ * @param {string} contractAddress - Address of the contract to verify.
+ * @param {string} contractArguments - Parameters used in the contract constructor during the initial deployment. It should be the hex encoded value.
+ * @param {string} contractName - Name of the contract
+ * @param {string} contractFile - File where the contract is located
+ * @returns {{ guid, status, message, succeed }} verification result
+ */
+export const verify = async (apikey: string, contractAddress: string, contractArguments: string, contractName: string, contractFile: string) => {
+ const compilationResultParam = await remix.call('compilerArtefacts' as any, 'getCompilerAbstract', contractFile)
+ console.log('verifying.. ' + contractName)
+ return await remix.call('etherscan' as any, 'verify', apikey, contractAddress, contractArguments, contractName, compilationResultParam)
+}
\ No newline at end of file