From b466ae3a2640fc91b4cb27fb2dbbd5c692f87e6c Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 17 Sep 2025 15:47:02 +0900 Subject: [PATCH 01/38] feat: verify on deploy --- .../app/ContractVerificationPluginClient.ts | 81 ++++++++++++++++++- .../run-tab/src/lib/actions/deploy.ts | 47 ++++++++++- .../remix-ui/run-tab/src/lib/actions/index.ts | 2 +- .../src/lib/components/contractDropdownUI.tsx | 48 ++++------- .../lib/components/verificationSettingsUI.tsx | 45 +++++++++++ libs/remix-ui/run-tab/src/lib/types/index.ts | 3 +- 6 files changed, 185 insertions(+), 41 deletions(-) create mode 100644 libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index c68ecf5cdbc..d6f56db456d 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -1,16 +1,18 @@ import { PluginClient } from '@remixproject/plugin' import { createClient } from '@remixproject/plugin-webview' + import EventManager from 'events' -import { VERIFIERS, type ChainSettings, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier } from './types' +import { VERIFIERS, type ChainSettings, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier, SubmittedContract } from './types' import { mergeChainSettingsWithDefaults, validConfiguration } from './utils' import { getVerifier } from './Verifiers' +import { CompilerAbstract } from '@remix-project/remix-solidity' export class ContractVerificationPluginClient extends PluginClient { public internalEvents: EventManager constructor() { super() - this.methods = ['lookupAndSave'] + this.methods = ['lookupAndSave', 'verifyOnDeploy'] this.internalEvents = new EventManager() createClient(this) this.onload() @@ -62,6 +64,81 @@ export class ContractVerificationPluginClient extends PluginClient { } } + verifyOnDeploy = async (data: any): Promise => { + try { + await this.call('terminal', 'log', { type: 'info', value: 'Verification process started...' }) + + const { chainId, contractAddress, contractName, compilationResult, constructorArgs, etherscanApiKey } = data + + if (!chainId) throw new Error("Chain ID was not provided.") + + const submittedContract: SubmittedContract = { + id: `${chainId}-${contractAddress}`, + address: contractAddress, + chainId: chainId, + filePath: Object.keys(compilationResult.data.contracts).find(path => path in compilationResult.source.sources), + contractName: contractName, + abiEncodedConstructorArgs: constructorArgs, + date: new Date().toISOString(), + receipts: [] + } + + const compilerAbstract: CompilerAbstract = compilationResult + + const userSettings = this.getUserSettingsFromLocalStorage() + const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings) + + await this._verifyWithProvider('Sourcify', submittedContract, compilerAbstract, chainId, chainSettings) + + if (etherscanApiKey) { + if (!chainSettings.verifiers.Etherscan) chainSettings.verifiers.Etherscan = {} + chainSettings.verifiers.Etherscan.apiKey = etherscanApiKey + await this._verifyWithProvider('Etherscan', submittedContract, compilerAbstract, chainId, chainSettings) + } else { + await this.call('terminal', 'log', { type: 'warn', value: 'Etherscan verification skipped: API key not found in global Settings.' }) + } + + await this._verifyWithProvider('Routescan', submittedContract, compilerAbstract, chainId, chainSettings) + await this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings) + + } catch (error) { + await this.call('terminal', 'log', { type: 'error', value: `An unexpected error occurred during verification: ${error.message}` }) + } + } + + private _verifyWithProvider = async ( + providerName: VerifierIdentifier, + submittedContract: SubmittedContract, + compilerAbstract: CompilerAbstract, + chainId: string, + chainSettings: ChainSettings + ): Promise => { + try { + if (validConfiguration(chainSettings, providerName)) { + await this.call('terminal', 'log', { type: 'info', value: `Verifying with ${providerName}...` }) + + const verifierSettings = chainSettings.verifiers[providerName] + const verifier = getVerifier(providerName, verifierSettings) + + if (verifier && typeof verifier.verify === 'function') { + const result = await verifier.verify(submittedContract, compilerAbstract) + + let successMessage = `${providerName} verification successful! Status: ${result.status}` + if (result.receiptId) successMessage += `, Receipt ID: ${result.receiptId}` + await this.call('terminal', 'log', { type: 'info', value: successMessage }) + + if (result.lookupUrl) { + await this.call('terminal', 'log', { type: 'html', value: `Check status: ${providerName} Link` }) + } + } else { + await this.call('terminal', 'log', { type: 'warn', value: `${providerName} verifier is not properly configured or does not support direct verification.` }) + } + } + } catch (e) { + await this.call('terminal', 'log', { type: 'error', value: `${providerName} verification failed: ${e.message}` }) + } + } + private getUserSettingsFromLocalStorage(): ContractVerificationSettings { const fallbackSettings = { chains: {} }; try { diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index f587450c9ed..353390bd5e5 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -161,7 +161,8 @@ export const createInstance = async ( mainnetPrompt: MainnetPrompt, isOverSizePrompt: (values: OverSizeLimit) => JSX.Element, args, - deployMode: DeployMode[]) => { + deployMode: DeployMode[], + isVerifyChecked: boolean) => { const isProxyDeployment = (deployMode || []).find(mode => mode === 'Deploy with Proxy') const isContractUpgrade = (deployMode || []).find(mode => mode === 'Upgrade with Proxy') const statusCb = (msg: string) => { @@ -180,12 +181,50 @@ export const createInstance = async ( const data = await plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) plugin.compilersArtefacts.addResolvedContract(addressToString(address), data) - if (plugin.REACT_API.ipfsChecked) { - _paq.push(['trackEvent', 'udapp', 'DeployAndPublish', plugin.REACT_API.networkName]) - publishToStorage('ipfs', selectedContract) + + if (isVerifyChecked) { + _paq.push(['trackEvent', 'udapp', 'DeployAndVerify', plugin.REACT_API.networkName]) + try { + await publishToStorage('ipfs', selectedContract) + } catch (e) { + const errorMsg = `Could not publish contract metadata to IPFS. Continuing with verification... (Error: ${e.message})` + const errorLog = logBuilder(errorMsg) + terminalLogger(plugin, errorLog) + } + + const msg = `Contract deployed successfully at ${addressToString(address)}. Starting verification process...` + const log = logBuilder(msg) + terminalLogger(plugin, log) + + const etherscanApiKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token') + + const status = plugin.blockchain.getCurrentNetworkStatus(); + if (status.error || !status.network) { + const errorMsg = `Could not get network status for verification: ${status.error || 'Unknown error'}` + const errorLog = logBuilder(errorMsg) + terminalLogger(plugin, errorLog) + return + } + + const chainId = status.network.id + + const verificationData = { + chainId: chainId, + contractAddress: addressToString(address), + contractName: selectedContract.name, + compilationResult: await plugin.compilersArtefacts.getCompilerAbstract(selectedContract.contract.file), + constructorArgs: args, + etherscanApiKey: etherscanApiKey + } + + console.log({verificationData}) + + plugin.call('contract-verification', 'verifyOnDeploy', verificationData) + } else { _paq.push(['trackEvent', 'udapp', 'DeployOnly', plugin.REACT_API.networkName]) } + if (isProxyDeployment) { const initABI = contractObject.abi.find(abi => abi.name === 'initialize') diff --git a/libs/remix-ui/run-tab/src/lib/actions/index.ts b/libs/remix-ui/run-tab/src/lib/actions/index.ts index 49e2bc6ff18..4b61741d7e6 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/index.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -45,7 +45,7 @@ export const setPassphraseModal = (passphrase: string) => setPassphrasePrompt(di export const setMatchPassphraseModal = (passphrase: string) => setMatchPassphrasePrompt(dispatch, passphrase) export const signMessage = (account: string, message: string, modalContent: (hash: string, data: string) => JSX.Element, passphrase?: string) => signMessageWithAddress(plugin, dispatch, account, message, modalContent, passphrase) export const fetchSelectedContract = (contractName: string, compiler: CompilerAbstractType) => getSelectedContract(contractName, compiler) -export const createNewInstance = async (selectedContract: ContractData, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void, mainnetPrompt: MainnetPrompt, isOverSizePrompt: (values: OverSizeLimit) => JSX.Element, args, deployMode: DeployMode[]) => createInstance(plugin, dispatch, selectedContract, gasEstimationPrompt, passphrasePrompt, publishToStorage, mainnetPrompt, isOverSizePrompt, args, deployMode) +export const createNewInstance = async (selectedContract: ContractData, gasEstimationPrompt: (msg: string) => JSX.Element, passphrasePrompt: (msg: string) => JSX.Element, publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void, mainnetPrompt: MainnetPrompt, isOverSizePrompt: (values: OverSizeLimit) => JSX.Element, args, deployMode: DeployMode[], isVerifyChecked: boolean) => createInstance(plugin, dispatch, selectedContract, gasEstimationPrompt, passphrasePrompt, publishToStorage, mainnetPrompt, isOverSizePrompt, args, deployMode, isVerifyChecked) export const setSendValue = (value: string) => setSendTransactionValue(dispatch, value) export const setBaseFeePerGas = (baseFee: string) => updateBaseFeePerGas(dispatch, baseFee) export const setConfirmSettings = (confirmation: boolean) => updateConfirmSettings(dispatch, confirmation) diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx index 6df5b4a8dfc..d82fdd78d67 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -6,6 +6,7 @@ import { ContractData, FuncABI, OverSizeLimit } from '@remix-project/core-plugin import * as ethJSUtil from '@ethereumjs/util' import { ContractGUI } from './contractGUI' import { CustomTooltip, deployWithProxyMsg, upgradeWithProxyMsg } from '@remix-ui/helper' +import { VerificationSettingsUI } from './verificationSettingsUI' const _paq = (window._paq = window._paq || []) export function ContractDropdownUI(props: ContractDropdownProps) { @@ -40,7 +41,8 @@ export function ContractDropdownUI(props: ContractDropdownProps) { const contractsRef = useRef(null) const atAddressValue = useRef(null) const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions } = props.contracts - + const [isVerifyChecked, setVerifyChecked] = useState(false) + useEffect(() => { enableContractNames(Object.keys(props.contracts.contractList).length > 0) }, [Object.keys(props.contracts.contractList).length]) @@ -213,7 +215,8 @@ export function ContractDropdownUI(props: ContractDropdownProps) { props.mainnetPrompt, isOverSizePrompt, args, - deployMode + deployMode, + isVerifyChecked ) }, intl.formatMessage({ id: 'udapp.cancel' }), @@ -233,14 +236,15 @@ export function ContractDropdownUI(props: ContractDropdownProps) { props.mainnetPrompt, isOverSizePrompt, args, - deployMode + deployMode, + isVerifyChecked ) }, intl.formatMessage({ id: 'udapp.cancel' }), () => {} ) } else { - props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode) + props.createInstance(loadedContractData, props.gasEstimationPrompt, props.passphrasePrompt, props.publishToStorage, props.mainnetPrompt, isOverSizePrompt, args, deployMode, isVerifyChecked) } } @@ -275,11 +279,9 @@ export function ContractDropdownUI(props: ContractDropdownProps) { setaddressIsValid(true) } - const handleCheckedIPFS = () => { - const checkedState = !props.ipfsCheckedState - - props.setIpfsCheckedState(checkedState) - window.localStorage.setItem(`ipfs/${props.exEnvironment}/${props.networkName}`, checkedState.toString()) + const handleVerifyCheckedChange = (isChecked: boolean) => { + setVerifyChecked(isChecked) + window.localStorage.setItem('deploy-verify-contract-checked', isChecked.toString()) } const updateCompilerName = () => { @@ -487,30 +489,10 @@ export function ContractDropdownUI(props: ContractDropdownProps) { plugin={props.plugin} runTabState={props.runTabState} /> -
- - - }} /> - - } - > - - -
+ )} diff --git a/libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx b/libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx new file mode 100644 index 00000000000..f977a1c6fb3 --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx @@ -0,0 +1,45 @@ +// eslint-disable-next-line no-use-before-define +import React from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { CustomTooltip } from '@remix-ui/helper' + +interface VerificationSettingsProps { + isVerifyChecked: boolean + onVerifyCheckedChange: (isChecked: boolean) => void +} + +export function VerificationSettingsUI(props: VerificationSettingsProps) { + const { isVerifyChecked, onVerifyCheckedChange } = props + const intl = useIntl() + + return ( +
+
+ onVerifyCheckedChange(e.target.checked)} + checked={isVerifyChecked} + /> + + + + } + > + + +
+
+ ) +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts index 343beed2a53..9b593c9d418 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -279,7 +279,8 @@ export interface ContractDropdownProps { mainnetPrompt: MainnetPrompt, isOverSizePrompt: (values: OverSizeLimit) => JSX.Element, args, - deployMode: DeployMode[]) => void, + deployMode: DeployMode[], + isVerifyChecked: boolean) => void, ipfsCheckedState: boolean, setIpfsCheckedState: (value: boolean) => void, publishToStorage: (storage: 'ipfs' | 'swarm', contract: ContractData) => void, From 2e6306d3e602d59dc243a8db9384303ad1c00b25 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Tue, 16 Sep 2025 13:51:01 +0100 Subject: [PATCH 02/38] fix context menu and model selection menu --- .../src/components/contextOptMenu.tsx | 3 ++- .../remix-ai-assistant/src/components/prompt.tsx | 4 ++-- .../src/components/remix-ui-remix-ai-assistant.tsx | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/libs/remix-ui/remix-ai-assistant/src/components/contextOptMenu.tsx b/libs/remix-ui/remix-ai-assistant/src/components/contextOptMenu.tsx index f82d125d8f3..58488d691b4 100644 --- a/libs/remix-ui/remix-ai-assistant/src/components/contextOptMenu.tsx +++ b/libs/remix-ui/remix-ai-assistant/src/components/contextOptMenu.tsx @@ -6,6 +6,7 @@ export interface GroupListMenuProps { choice: AiContextType | AiAssistantType | any setShowOptions: Dispatch> groupList: groupListType[] + themeTracker?: any } export default function GroupListMenu(props: GroupListMenuProps) { @@ -15,7 +16,7 @@ export default function GroupListMenu(props: GroupListMenuProps) { {props.groupList.map((item, index) => ( } diff --git a/libs/remix-ui/settings/src/types/index.ts b/libs/remix-ui/settings/src/types/index.ts index 03e938aab22..9106fddd571 100644 --- a/libs/remix-ui/settings/src/types/index.ts +++ b/libs/remix-ui/settings/src/types/index.ts @@ -47,6 +47,7 @@ export interface SettingsSection { name: keyof SettingsState, label: string, labelIcon?: string, + headerClass?: string, labelIconTooltip?: string, description?: string | JSX.Element, footnote?: { From 03f53b09bf4d12d6c05fb2078236ceeb8a23be76 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Mon, 15 Sep 2025 21:45:47 +0530 Subject: [PATCH 10/38] disable cookies on settings disable --- apps/remix-ide/src/app/tabs/locales/en/settings.json | 2 +- apps/remix-ide/src/app/tabs/settings-tab.tsx | 2 +- libs/remix-ui/settings/src/lib/settings-section.tsx | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/locales/en/settings.json b/apps/remix-ide/src/app/tabs/locales/en/settings.json index fa927ea6209..f473da1e0a1 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/settings.json +++ b/apps/remix-ide/src/app/tabs/locales/en/settings.json @@ -59,7 +59,7 @@ "settings.servicesDescription": "Configure the settings for connected services, including Github, IPFS, Swarm, Sindri and Etherscan.", "settings.matomoAnalyticsNoCookies": "Matomo Analytics (necessary, no cookies)", "settings.matomoAnalyticsNoCookiesDescription": "Help improve Remix with anonymous usage data.", - "settings.matomoAnalyticsWithCookies": "Matomo Analytics (with cookies)", + "settings.matomoAnalyticsWithCookies": "Matomo Performance Analytics (with cookies)", "settings.matomoAnalyticsWithCookiesDescription": "Enable tracking with cookies for more detailed insights.", "settings.aiCopilot": "AI Copilot", "settings.aiCopilotDescription": "AI Copilot assists with code suggestions and improvements.", diff --git a/apps/remix-ide/src/app/tabs/settings-tab.tsx b/apps/remix-ide/src/app/tabs/settings-tab.tsx index df531f04f61..abf6995a9d8 100644 --- a/apps/remix-ide/src/app/tabs/settings-tab.tsx +++ b/apps/remix-ide/src/app/tabs/settings-tab.tsx @@ -15,7 +15,7 @@ const _paq = (window._paq = window._paq || []) const profile = { name: 'settings', displayName: 'Settings', - methods: ['get', 'updateCopilotChoice', 'getCopilotSetting'], + methods: ['get', 'updateCopilotChoice', 'getCopilotSetting', 'updateMatomoPerfAnalyticsChoice'], events: [], icon: 'assets/img/settings.webp', description: 'Remix-IDE settings', diff --git a/libs/remix-ui/settings/src/lib/settings-section.tsx b/libs/remix-ui/settings/src/lib/settings-section.tsx index 3b93dbd3fe3..6079e0f4611 100644 --- a/libs/remix-ui/settings/src/lib/settings-section.tsx +++ b/libs/remix-ui/settings/src/lib/settings-section.tsx @@ -39,10 +39,8 @@ export const SettingsSectionUI: React.FC = ({ plugin, se const handleToggle = (name: string) => { if (state[name]) { const newValue = !state[name].value - dispatch({ type: 'SET_LOADING', payload: { name: name } }) dispatch({ type: 'SET_VALUE', payload: { name: name, value: newValue } }) - // _paq.push(['disableCookies']) if (!newValue && formUIData[name]) { Object.keys(formUIData[name]).forEach((key) => { dispatch({ type: 'SET_VALUE', payload: { name: key, value: '' } }) @@ -50,6 +48,7 @@ export const SettingsSectionUI: React.FC = ({ plugin, se dispatch({ type: 'SET_TOAST_MESSAGE', payload: { value: 'Credentials removed' } }) } if (name === 'copilot/suggest/activate') plugin.emit('copilotChoiceUpdated', newValue) + if (name === 'matomo-perf-analytics') plugin.call('settings', 'updateMatomoPerfAnalyticsChoice', newValue) if (name === 'text-wrap') plugin.emit('textWrapChoiceUpdated', newValue) } else { console.error('Setting does not exist: ', name) From fd89f8be9219b48564f6522995d2d8fa9ab6903c Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 10 Sep 2025 12:20:01 +0200 Subject: [PATCH 11/38] remove matomo keys --- apps/learneth/src/redux/models/workshop.ts | 5 ++++- apps/remix-ide-e2e/src/tests/matomo.test.ts | 4 +--- apps/remix-ide/src/app.ts | 2 +- apps/remix-ide/src/app/components/preload.tsx | 2 +- apps/remix-ide/src/app/plugins/storage.ts | 2 +- .../solidity-compiler/src/lib/compiler-container.tsx | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/learneth/src/redux/models/workshop.ts b/apps/learneth/src/redux/models/workshop.ts index 2dba514dca4..85bb522edd6 100644 --- a/apps/learneth/src/redux/models/workshop.ts +++ b/apps/learneth/src/redux/models/workshop.ts @@ -184,7 +184,10 @@ const Model: ModelType = { } } } - (window)._paq.push(['trackEvent', 'learneth', 'load_repo', payload.name]) + // we don't need to track the default repos + if (payload.name !== 'ethereum/remix-workshops' && payload.name !== 'remix-project-org/remix-workshops') { + (window)._paq.push(['trackEvent', 'learneth', 'load_repo', payload.name]) + } }, *resetAll({ payload }, { put }) { yield put({ diff --git a/apps/remix-ide-e2e/src/tests/matomo.test.ts b/apps/remix-ide-e2e/src/tests/matomo.test.ts index a0888a1dcd2..bd29499f706 100644 --- a/apps/remix-ide-e2e/src/tests/matomo.test.ts +++ b/apps/remix-ide-e2e/src/tests/matomo.test.ts @@ -325,9 +325,7 @@ module.exports = { return (window as any)._paq }, [], (res) => { const expectedEvents = [ - ["trackEvent", "App", "Preload", "start"], - ["trackEvent", "Storage", "activate", "indexedDB"], - ["trackEvent", "App", "load"], + ["trackEvent", "Storage", "activate", "indexedDB"] ]; const actualEvents = (res as any).value; diff --git a/apps/remix-ide/src/app.ts b/apps/remix-ide/src/app.ts index 2316b6929da..6f524ae840e 100644 --- a/apps/remix-ide/src/app.ts +++ b/apps/remix-ide/src/app.ts @@ -230,7 +230,7 @@ class AppComponent { '6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop } - _paq.push(['trackEvent', 'App', 'load']); + // _paq.push(['trackEvent', 'App', 'load']); this.matomoConfAlreadySet = Registry.getInstance().get('config').api.exists('settings/matomo-perf-analytics') this.matomoCurrentSetting = Registry.getInstance().get('config').api.get('settings/matomo-perf-analytics') diff --git a/apps/remix-ide/src/app/components/preload.tsx b/apps/remix-ide/src/app/components/preload.tsx index cb2101c9e4b..16f2d08cd8b 100644 --- a/apps/remix-ide/src/app/components/preload.tsx +++ b/apps/remix-ide/src/app/components/preload.tsx @@ -10,7 +10,7 @@ import './styles/preload.css' import isElectron from 'is-electron' const _paq = (window._paq = window._paq || []) -_paq.push(['trackEvent', 'App', 'Preload', 'start']) +// _paq.push(['trackEvent', 'App', 'Preload', 'start']) export const Preload = (props: any) => { const [tip, setTip] = useState('') diff --git a/apps/remix-ide/src/app/plugins/storage.ts b/apps/remix-ide/src/app/plugins/storage.ts index cd7ec1e1622..8feeac49c81 100644 --- a/apps/remix-ide/src/app/plugins/storage.ts +++ b/apps/remix-ide/src/app/plugins/storage.ts @@ -23,7 +23,7 @@ export class StoragePlugin extends Plugin { } } const _paq = (window as any)._paq = (window as any)._paq || [] - _paq.push(['trackEvent', 'Storage', 'used', this.formatString(storage)]); + // _paq.push(['trackEvent', 'Storage', 'used', this.formatString(storage)]); return storage } diff --git a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx index d7ede8f142a..c98391a98a5 100644 --- a/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx +++ b/libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx @@ -483,7 +483,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => { compileIcon.current.classList.remove('remixui_spinningIcon') compileIcon.current.classList.remove('remixui_bouncingIcon') if (!state.autoCompile || (state.autoCompile && state.matomoAutocompileOnce)) { - _paq.push(['trackEvent', 'compiler', 'compiled', 'solCompilationFinishedTriggeredByUser']) + // _paq.push(['trackEvent', 'compiler', 'compiled', 'solCompilationFinishedTriggeredByUser']) _paq.push(['trackEvent', 'compiler', 'compiled', 'with_config_file_' + state.useFileConfiguration]) _paq.push(['trackEvent', 'compiler', 'compiled', 'with_version_' + _retrieveVersion()]) if (state.autoCompile && state.matomoAutocompileOnce) { From a030af013b1399b14a014e1d6366ea816fa280e0 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Tue, 9 Sep 2025 15:07:31 +0900 Subject: [PATCH 12/38] feat:apply portal-based submenus for Environment dropdown and reorder items --- .../src/lib/components/environment.tsx | 135 +++++++++++------- .../src/lib/components/subMenuPortal.tsx | 102 +++++++++++++ 2 files changed, 184 insertions(+), 53 deletions(-) create mode 100644 libs/remix-ui/run-tab/src/lib/components/subMenuPortal.tsx diff --git a/libs/remix-ui/run-tab/src/lib/components/environment.tsx b/libs/remix-ui/run-tab/src/lib/components/environment.tsx index 9f075320a50..c1e0c07aca1 100644 --- a/libs/remix-ui/run-tab/src/lib/components/environment.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/environment.tsx @@ -6,15 +6,24 @@ import { Dropdown } from 'react-bootstrap' import { CustomMenu, CustomToggle, CustomTooltip } from '@remix-ui/helper' import { DropdownLabel } from './dropdownLabel' import { setExecutionContext } from '../actions/account' +import SubmenuPortal from './subMenuPortal' const _paq = (window._paq = window._paq || []) export function EnvironmentUI(props: EnvironmentProps) { const vmStateName = useRef('') - Object.entries(props.providers.providerList.filter((provider) => { return provider.config.isVM })) - Object.entries(props.providers.providerList.filter((provider) => { return provider.config.isInjected })) - Object.entries(props.providers.providerList.filter((provider) => { return !(provider.config.isVM || provider.config.isInjected) })) + const providers = props.providers.providerList + + const remixVMs = providers.filter(p => p.config.isVM) + const injectedProviders = providers.filter(p => p.config.isInjected) + const isDevByLabel = (p: any) => { + const label = (p?.displayName || '').trim() + return /^dev\s*[-–—]\s*/i.test(label) + } + const devProviders = providers.filter(isDevByLabel) + const walletConnect = providers.find(p => p.name === 'walletconnect' || p.name === 'walletConnect') + const httpProvider = providers.find(p => p.name === 'basic-http-provider' || p.name === 'web3Provider' || p.name === 'basicHttpProvider') const handleChangeExEnv = (env: string) => { const provider = props.providers.providerList.find((exEnv) => exEnv.name === env) @@ -165,65 +174,85 @@ export function EnvironmentUI(props: EnvironmentProps) { {/* {isL2(currentProvider && currentProvider.displayName)} */} - - {props.providers.providerList.length === 0 && - - No provider pinned - - } - { (props.providers.providerList.filter((provider) => { return provider.config.isInjected })).map(({ name, displayName }) => ( - { - handleChangeExEnv(name) - }} - data-id={`dropdown-item-${name}`} - > - - {displayName} - - - ))} - { props.providers.providerList.filter((provider) => { return provider.config.isInjected }).length !== 0 && } - { (props.providers.providerList.filter((provider) => { return provider.config.isVM })).map(({ displayName, name }) => ( + + {providers.length === 0 && No provider pinned} + + {remixVMs.length > 0 && ( + + {remixVMs.map(({ name, displayName }) => ( + handleChangeExEnv(name)} + data-id={`dropdown-item-${name}`} + > + {displayName} + + ))} + + )} + + + + {injectedProviders.length > 0 && ( + + {injectedProviders.map(({ name, displayName }) => ( + handleChangeExEnv(name)} + data-id={`dropdown-item-${name}`} + > + {displayName} + + ))} + + )} + + {walletConnect && ( { - handleChangeExEnv(name) - }} - data-id={`dropdown-item-${name}`} + key={walletConnect.name} + onClick={() => handleChangeExEnv(walletConnect.name)} + data-id={`dropdown-item-${walletConnect.name}`} > - - {displayName} - + {walletConnect.displayName} - ))} - { props.providers.providerList.filter((provider) => { return provider.config.isVM }).length !== 0 && } - { (props.providers.providerList.filter((provider) => { return !(provider.config.isVM || provider.config.isInjected) })).map(({ displayName, name }) => ( + )} + + + + {httpProvider && ( { - handleChangeExEnv(name) - }} - data-id={`dropdown-item-${name}`} + key={httpProvider.name} + onClick={() => handleChangeExEnv(httpProvider.name)} + data-id={`dropdown-item-${httpProvider.name}`} > - - {isL2(displayName)} - {displayName} - + {httpProvider.displayName} - ))} - + )} + + + + {devProviders.length > 0 && ( + + {devProviders.map(({ name, displayName }) => ( + handleChangeExEnv(name)} + data-id={`dropdown-item-${name}`} + > + {displayName} + + ))} + + )} + + + { - props.setExecutionContext({ context: 'item-another-chain' }) - }} - data-id={`dropdown-item-another-chain`} + onClick={() => { props.setExecutionContext({ context: 'item-another-chain' }) }} + data-id="dropdown-item-another-chain" > - - Customize this list... - + Customize this list... diff --git a/libs/remix-ui/run-tab/src/lib/components/subMenuPortal.tsx b/libs/remix-ui/run-tab/src/lib/components/subMenuPortal.tsx new file mode 100644 index 00000000000..ff7c182e5dd --- /dev/null +++ b/libs/remix-ui/run-tab/src/lib/components/subMenuPortal.tsx @@ -0,0 +1,102 @@ +import React, { useRef, useState, useLayoutEffect } from 'react' +import { createPortal } from 'react-dom' + +type SubmenuPortalProps = { + label: string + children: React.ReactNode + minWidth?: number + offsetX?: number +} + +export default function SubmenuPortal({ + label, + children, + minWidth = 224, + offsetX = -2 +}: SubmenuPortalProps) { + const anchorRef = useRef(null) + const menuRef = useRef(null) + const [open, setOpen] = useState(false) + const [pos, setPos] = useState<{top:number;left:number}|null>(null) + + useLayoutEffect(() => { + if (!open || !anchorRef.current) return + const a = anchorRef.current.getBoundingClientRect() + const left = a.right + offsetX + const top = a.top + + setPos({ top, left }) + }, [open, offsetX]) + + useLayoutEffect(() => { + if (!open || !anchorRef.current || !menuRef.current) return + const a = anchorRef.current.getBoundingClientRect() + const m = menuRef.current.getBoundingClientRect() + const vw = window.innerWidth + + let left = a.right + offsetX + let top = a.top + + const spaceRight = vw - a.right + const needWidth = Math.max(minWidth, m.width) + if (spaceRight < needWidth + 8) { + left = Math.max(8, a.left - needWidth) + } + + const spaceBottom = window.innerHeight - top + const overflowY = top + m.height - window.innerHeight + if (overflowY > 0 && spaceBottom < m.height) { + top = Math.max(8, window.innerHeight - m.height - 8) + } + + setPos({ top, left }) + }, [open, minWidth, offsetX]) + + useLayoutEffect(() => { + if (!open) return + const reflow = () => setOpen((o) => (o ? o : o)) + window.addEventListener('scroll', reflow, true) + window.addEventListener('resize', reflow) + return () => { + window.removeEventListener('scroll', reflow, true) + window.removeEventListener('resize', reflow) + } + }, [open]) + + return ( + <> + setOpen(true)} + onMouseLeave={() => setOpen(false)} + role="button" + aria-haspopup="menu" + aria-expanded={open} + > + {label} + + + {open && pos && + createPortal( +
setOpen(true)} + onMouseLeave={() => setOpen(false)} + > + {children} +
, + document.body + ) + } + + ) +} From d01568e4951867d8bcf24377f78ab39480fcacfd Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 10 Sep 2025 18:52:32 +0900 Subject: [PATCH 13/38] feat: smart account dropdown ui --- apps/remix-dapp/src/locales/en/udapp.json | 12 +- .../src/app/tabs/locales/en/udapp.json | 12 +- .../run-tab/src/lib/components/account.tsx | 144 +++++++++++++----- libs/remix-ui/run-tab/src/lib/css/run-tab.css | 75 +++++++++ libs/remix-ui/run-tab/src/lib/types/index.ts | 12 +- 5 files changed, 195 insertions(+), 60 deletions(-) diff --git a/apps/remix-dapp/src/locales/en/udapp.json b/apps/remix-dapp/src/locales/en/udapp.json index 8e04009040d..c9ef7abd75b 100644 --- a/apps/remix-dapp/src/locales/en/udapp.json +++ b/apps/remix-dapp/src/locales/en/udapp.json @@ -61,13 +61,11 @@ "udapp.reset": "Reset", "udapp.delete": "Delete", "udapp.injectedTitle": "Unfortunately it's not possible to create an account using injected provider. Please create the account directly from your provider (i.e metamask or other of the same type).", - "udapp.createSmartAccount": "Create Gnosis Safe Smart Account", - "udapp.createSmartAccountAlpha": "Create Gnosis Safe Smart Account [ALPHA]", - "udapp.createSmartAccountDesc1":"ERC-4337 introduces smart contract accounts, also referred as Smart Accounts. These accounts are more advanced than traditional EOAs and offer features like multi-signature functionality, gasless transactions and custom transaction rules.", - "udapp.createSmartAccountDesc2":"A Safe Smart Account is a Smart Account with the multi-signature functionality of Gnosis Safe at its core.", - "udapp.createSmartAccountDesc3":"Here, a Smart Account will be created with address listed below as the OWNER. The Owner account signs each transaction (user-operation) made using Smart Account.", - "udapp.createSmartAccountDesc4":"This Smart Account will, by default, have a PAYMASTER attached to it to execute GASLESS transactions. Currently, in Remix, the ERC4337 updates only work for GNOSIS Mainnet & Ethereum SEPOLIA testnet. ", - "udapp.createSmartAccountDesc5":"Please note that the Smart Account creation will require signing an initial transaction on the next step.", + "udapp.createSmartAccount": "Create a Safe Smart Account", + "udapp.createSmartAccountDesc1":"ERC-4337 introduces smart contract wallets, which are more advanced than traditional EOAs. ", + "udapp.createSmartAccountDesc2":"These wallets offer features like multi-signature for extra security, gasless transactions where users can pay fees with other tokens, and custom transaction rules for added flexibility.", + "udapp.createSmartAccountDesc3":"Your Smart Account will be created with the below account as owner. ", + "udapp.createSmartAccountDesc4":"This account will be used to interact with your Smart Account for signing or sending transaction.", "udapp.continue": "Continue", "udapp.authorize": "Authorize", "udapp.createNewAccount": "Create a new account", diff --git a/apps/remix-ide/src/app/tabs/locales/en/udapp.json b/apps/remix-ide/src/app/tabs/locales/en/udapp.json index e1accfd9526..61d3c1ee11a 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/udapp.json +++ b/apps/remix-ide/src/app/tabs/locales/en/udapp.json @@ -61,13 +61,11 @@ "udapp.reset": "Reset", "udapp.delete": "Delete", "udapp.injectedTitle": "Unfortunately it's not possible to create an account using injected provider. Please create the account directly from your provider (i.e metamask or other of the same type).", - "udapp.createSmartAccount": "Create Gnosis Safe Smart Account", - "udapp.createSmartAccountAlpha": "Create Gnosis Safe Smart Account [ALPHA]", - "udapp.createSmartAccountDesc1":"ERC-4337 introduces smart contract accounts, also referred as Smart Accounts. These accounts are more advanced than traditional EOAs and offer features like multi-signature functionality, gasless transactions and custom transaction rules.", - "udapp.createSmartAccountDesc2":"A Safe Smart Account is a Smart Account with the multi-signature functionality of Gnosis Safe at its core.", - "udapp.createSmartAccountDesc3":"Here, a Smart Account will be created with address listed below as the OWNER. The Owner account signs each transaction (user-operation) made using Smart Account.", - "udapp.createSmartAccountDesc4":"This Smart Account will, by default, have a PAYMASTER attached to it to execute GASLESS transactions. Currently, in Remix, the ERC4337 updates only work for GNOSIS Mainnet & Ethereum SEPOLIA testnet. ", - "udapp.createSmartAccountDesc5":"Please note that the Smart Account creation will require signing an initial transaction on the next step.", + "udapp.createSmartAccount": "Create a Safe Smart Account", + "udapp.createSmartAccountDesc1":"ERC-4337 introduces smart contract wallets, which are more advanced than traditional EOAs. ", + "udapp.createSmartAccountDesc2":"These wallets offer features like multi-signature for extra security, gasless transactions where users can pay fees with other tokens, and custom transaction rules for added flexibility.", + "udapp.createSmartAccountDesc3":"Your Smart Account will be created with the below account as owner. ", + "udapp.createSmartAccountDesc4":"This account will be used to interact with your Smart Account for signing or sending transaction.", "udapp.continue": "Continue", "udapp.authorize": "Authorize", "udapp.createNewAccount": "Create new account", diff --git a/libs/remix-ui/run-tab/src/lib/components/account.tsx b/libs/remix-ui/run-tab/src/lib/components/account.tsx index 3aa6650d870..ac4f51be9c0 100644 --- a/libs/remix-ui/run-tab/src/lib/components/account.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/account.tsx @@ -30,6 +30,11 @@ export function AccountUI(props: AccountProps) { const aaSupportedChainIds = ["11155111", "100"] // AA01: Add chain id here to show 'Create Smart Account' button in Udapp const smartAccounts: string[] = aaSupportedChainIds.some(e => networkName.includes(e)) ? Object.keys(props.runTabPlugin.REACT_API.smartAccounts) : [] + const smartAccountsData = props.runTabPlugin?.REACT_API?.smartAccounts || {} + const ownerAddressSet = new Set( + Object.values(smartAccountsData).map((sa: any) => sa.ownerEOA.toLowerCase()) + ) + useEffect(() => { if (accounts.length > 0 && !accounts.includes(selectedAccount)) { props.setAccount(accounts[0]) @@ -163,25 +168,52 @@ export function AccountUI(props: AccountProps) { } }, [selectExEnv, personalMode, networkName]) + const displayAccount = loadedAccounts?.[selectedAccount] ?? shortenAddress(selectedAccount) + const createSmartAccount = () => { props.modal( - intl.formatMessage({ id: 'udapp.createSmartAccountAlpha' }), +
+ Alpha + {intl.formatMessage({ id: 'udapp.createSmartAccount' })} +
, (
-
-

- + +

+

+ +

+
_paq.push(['trackEvent', 'udapp', 'safeSmartAccount', 'learnMore'])}> - Learn more -

-

- - -
-

-

-

+ rel="noreferrer noopener" + onClick={() => _paq.push(['trackEvent', 'udapp', 'safeSmartAccount', 'learnMore'])} + className="mb-3 d-inline-block link-primary" + > + Learn more + +

+ + +

+ + + +
), intl.formatMessage({ id: 'udapp.continue' }), @@ -384,6 +416,14 @@ export function AccountUI(props: AccountProps) { return (
+ { smartAccountSelected && ownerEOA.current && ( +
+ Owner: {shortenAddress(ownerEOA.current)} + + +
+ ) + }
- - - {selectedAccount ? loadedAccounts[selectedAccount] : ''} - - - {accounts && accounts.length > 0 ? accounts.map((value, index) => ( - { - props.setAccount(value) - }} - data-id={`txOriginSelectAccountItem-${value}`} - > - - {loadedAccounts[value]} - - - )) : } - - +
+ + + + { selectedAccount && loadedAccounts[selectedAccount] ? ( +
+ {(smartAccounts.includes(selectedAccount) || ownerAddressSet.has(selectedAccount)) && ( + + {smartAccounts.includes(selectedAccount) ? 'Smart' : 'Owner'} + + )} + + {loadedAccounts[selectedAccount].replace(/\[SMART\]\s*/, '')} + +
+ ) : '' } +
+ + + {accounts && accounts.length > 0 ? accounts.map((value) => { + const isSmartAccount = smartAccounts.includes(value) + const isOwner = ownerAddressSet.has(value.toLowerCase()) + const displayName = loadedAccounts[value].replace(/\[SMART\]\s*/, '') + + const itemContent = ( +
+ {(isSmartAccount || isOwner) && ( + + {isSmartAccount ? 'Smart' : 'Owner'} + + )} + + {displayName} + +
+ ) + return ( + { props.setAccount(value) }} + data-id={`txOriginSelectAccountItem-${value}`} + > + {isSmartAccount ?
{itemContent}
: itemContent} +
+ ); + }) : } +
+
+
+ { contractHasDelegation ? Delegation: {shortenAddress(delegationAuthorizationAddressRef.current || "")} @@ -447,11 +516,6 @@ export function AccountUI(props: AccountProps) { : null } - { smartAccountSelected ? - Owner: {shortenAddress(ownerEOA.current || '')} - - : null - } { enableCSM ? (
From 1e8b2ca2b0f96caf9044a1a69be847fc3f803430 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 18 Sep 2025 11:13:32 +0900 Subject: [PATCH 18/38] remove #pr and update e2e --- .../src/commands/switchEnvironment.ts | 31 +++++++++++++------ .../src/tests/environment-account.test.ts | 4 +-- apps/remix-ide-e2e/src/tests/vm_state.test.ts | 29 ++++++++++++----- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/apps/remix-ide-e2e/src/commands/switchEnvironment.ts b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts index a6b00912680..ee4bc6ee316 100644 --- a/apps/remix-ide-e2e/src/commands/switchEnvironment.ts +++ b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts @@ -65,10 +65,12 @@ class switchEnvironment extends EventEmitter { ) => { const tryOne = (i: number) => { if (i >= labels.length) return onDone() + const submenuXPath = `//span[contains(@class,'dropdown-item') and normalize-space()='${labels[i]}']` + browser .useXpath() .isPresent({ - selector: `//span[contains(@class,'dropdown-item') and normalize-space()='${labels[i]}']`, + selector: submenuXPath, suppressNotFoundErrors: true, timeout: 0 }, (present) => { @@ -77,21 +79,30 @@ class switchEnvironment extends EventEmitter { return tryOne(i + 1) } browser - .moveToElement(`//span[contains(@class,'dropdown-item') and normalize-space()='${labels[i]}']`, 5, 5) - .pause(250) + .execute(function(xpath) { + const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue + if (element) { + const event = new MouseEvent('mouseover', { 'view': window, 'bubbles': true, 'cancelable': true }) + element.dispatchEvent(event) + } + }, + [submenuXPath] + ) .useCss() - .isPresent({ - selector: `body .dropdown-menu.show [data-id="dropdown-item-${providerName}"]`, - suppressNotFoundErrors: true, - timeout: 1000 - }, (inPortal) => { - if (inPortal.value) { + .waitForElementVisible( + `body .dropdown-menu.show [data-id="dropdown-item-${providerName}"]`, + 5000, + undefined, + false, + (result) => { + if (result.status === 0) { clickAndMaybeWait(browser, `body .dropdown-menu.show [data-id="dropdown-item-${providerName}"]`, providerName, shouldWait) onDone() } else { tryOne(i + 1) } - }) + } + ) }) } tryOne(0) diff --git a/apps/remix-ide-e2e/src/tests/environment-account.test.ts b/apps/remix-ide-e2e/src/tests/environment-account.test.ts index 1a56f242b55..86c3551f6d7 100644 --- a/apps/remix-ide-e2e/src/tests/environment-account.test.ts +++ b/apps/remix-ide-e2e/src/tests/environment-account.test.ts @@ -12,7 +12,7 @@ module.exports = { init(browser, done, null) }, - 'Should open submenu and close both menus on selection #group1 #pr': function (browser: NightwatchBrowser) { + 'Should open submenu and close both menus on selection #group1': function (browser: NightwatchBrowser) { browser .clickLaunchIcon('udapp') .waitForElementVisible('[data-id="settingsSelectEnvOptions"]') @@ -24,7 +24,7 @@ module.exports = { .assert.containsText('[data-id="selected-provider-vm-cancun"]', 'Remix VM (Cancun)') }, - 'Should display sample accounts and balances #group1 #pr': function (browser: NightwatchBrowser) { + 'Should display sample accounts and balances #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('[data-id="runTabSelectAccount"]') .click('[data-id="runTabSelectAccount"]') diff --git a/apps/remix-ide-e2e/src/tests/vm_state.test.ts b/apps/remix-ide-e2e/src/tests/vm_state.test.ts index cf2dc7e6145..9eda86e07fb 100644 --- a/apps/remix-ide-e2e/src/tests/vm_state.test.ts +++ b/apps/remix-ide-e2e/src/tests/vm_state.test.ts @@ -141,15 +141,22 @@ const tests = { .clickLaunchIcon('filePanel') }, 'Should show fork states provider in environment dropdown & make txs using forked state #group1': function (browser: NightwatchBrowser) { + const remixVMSpanXPath = "//span[contains(@class,'dropdown-item') and normalize-space()='Remix VM']" browser .clickLaunchIcon('udapp') .waitForElementVisible('[data-id="settingsSelectEnvOptions"]') .click('[data-id="settingsSelectEnvOptions"] button') - .useXpath() - .moveToElement("//span[contains(@class,'dropdown-item') and normalize-space()='Remix VM']", 5, 5) - .useCss() + .execute(function(xpath) { + const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue + if (element) { + const event = new MouseEvent('mouseover', { 'view': window, 'bubbles': true, 'cancelable': true }) + element.dispatchEvent(event) + } + }, + [remixVMSpanXPath] + ) .waitForElementVisible(`[data-id="dropdown-item-vm-fs-forkedState_1"]`) - .click('[data-id="settingsSelectEnvOptions"] button') + .click('[data-id="settingsSelectEnvOptions"] button') .switchEnvironment('vm-prague') .openFile('contracts/1_Storage.sol') .clickLaunchIcon('solidity') @@ -170,12 +177,18 @@ const tests = { .assert.elementPresent('*[data-id="selected-provider-vm-fs-forkedState_2"]') .click('[data-id="settingsSelectEnvOptions"] button') - .useXpath() - .moveToElement("//span[contains(@class,'dropdown-item') and normalize-space()='Remix VM']", 5, 5) - .useCss() + .execute(function(xpath) { + const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue + if (element) { + const event = new MouseEvent('mouseover', { 'view': window, 'bubbles': true, 'cancelable': true }) + element.dispatchEvent(event) + } + }, + [remixVMSpanXPath] + ) + .waitForElementVisible(`[data-id="dropdown-item-vm-fs-forkedState_2"]`) .click('[data-id="settingsSelectEnvOptions"] button') - .click('*[data-id="Deploy - transact (not payable)"]') .clickInstance(0) .clickFunction('store - transact (not payable)', { types: 'uint256 num', values: '"555"' }) From 37817d8d9a4c51e68d3aa8a8d14e90b5165ae3bb Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 18 Sep 2025 12:51:32 +0900 Subject: [PATCH 19/38] fix e2e test code --- .../src/commands/switchEnvironment.ts | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/apps/remix-ide-e2e/src/commands/switchEnvironment.ts b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts index ee4bc6ee316..b554df2074e 100644 --- a/apps/remix-ide-e2e/src/commands/switchEnvironment.ts +++ b/apps/remix-ide-e2e/src/commands/switchEnvironment.ts @@ -1,3 +1,4 @@ +'use strict' import { NightwatchBrowser } from 'nightwatch' import EventEmitter from 'events' @@ -80,29 +81,27 @@ class switchEnvironment extends EventEmitter { } browser .execute(function(xpath) { - const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue - if (element) { - const event = new MouseEvent('mouseover', { 'view': window, 'bubbles': true, 'cancelable': true }) - element.dispatchEvent(event) - } - }, - [submenuXPath] - ) + const element = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue + if (element) { + const event = new MouseEvent('mouseover', { 'view': window, 'bubbles': true, 'cancelable': true }) + element.dispatchEvent(event) + } + }, + [submenuXPath] + ) .useCss() - .waitForElementVisible( - `body .dropdown-menu.show [data-id="dropdown-item-${providerName}"]`, - 5000, - undefined, - false, - (result) => { - if (result.status === 0) { + .isPresent({ + selector: `body .dropdown-menu.show [data-id="dropdown-item-${providerName}"]`, + suppressNotFoundErrors: true, + timeout: 2000 + }, (inPortal) => { + if (inPortal.value) { clickAndMaybeWait(browser, `body .dropdown-menu.show [data-id="dropdown-item-${providerName}"]`, providerName, shouldWait) onDone() } else { tryOne(i + 1) } - } - ) + }) }) } tryOne(0) @@ -133,19 +132,21 @@ class switchEnvironment extends EventEmitter { .perform((done) => { this.api.isPresent({ selector: `[data-id="selected-provider-${provider}"]`, suppressNotFoundErrors: true, timeout: 1000 }, (result) => { if (result.value) return done() - + this.api.click('[data-id="settingsSelectEnvOptions"] button') - - attemptSelect(this.api, provider, returnWhenInitialized, () => { - waitForSelectedOrModal(this.api, provider, 10000, (ok) => { - if (ok) { - return done() - } else { - this.api.assert.fail(`Environment "${provider}" could not be selected or found in the dropdown.`) - done() - } + .waitForElementVisible('body .dropdown-menu.show', 3000) + .perform(() => { + attemptSelect(this.api, provider, returnWhenInitialized, () => { + waitForSelectedOrModal(this.api, provider, 10000, (ok) => { + if (ok) { + return done() + } else { + this.api.assert.fail(`Environment "${provider}" could not be selected or found in the dropdown.`) + done() + } + }) + }) }) - }) }) }) .perform(() => this.emit('complete')) @@ -154,4 +155,4 @@ class switchEnvironment extends EventEmitter { } } -module.exports = switchEnvironment +module.exports = switchEnvironment \ No newline at end of file From c4e1ad9a335e3f9aeba3dabf7ed9e7ab71480fba Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 18 Sep 2025 17:46:50 +0900 Subject: [PATCH 20/38] add verifiable chain list --- .../app/ContractVerificationPluginClient.ts | 37 ++++++----- .../run-tab/src/lib/actions/deploy.ts | 61 +++++++++++-------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index d6f56db456d..3d16900e017 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -2,7 +2,7 @@ import { PluginClient } from '@remixproject/plugin' import { createClient } from '@remixproject/plugin-webview' import EventManager from 'events' -import { VERIFIERS, type ChainSettings, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier, SubmittedContract } from './types' +import { VERIFIERS, type ChainSettings,Chain, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier, SubmittedContract } from './types' import { mergeChainSettingsWithDefaults, validConfiguration } from './utils' import { getVerifier } from './Verifiers' import { CompilerAbstract } from '@remix-project/remix-solidity' @@ -68,9 +68,9 @@ export class ContractVerificationPluginClient extends PluginClient { try { await this.call('terminal', 'log', { type: 'info', value: 'Verification process started...' }) - const { chainId, contractAddress, contractName, compilationResult, constructorArgs, etherscanApiKey } = data + const { chainId, currentChain, contractAddress, contractName, compilationResult, constructorArgs, etherscanApiKey } = data - if (!chainId) throw new Error("Chain ID was not provided.") + if (!currentChain) throw new Error("Chain data was not provided.") const submittedContract: SubmittedContract = { id: `${chainId}-${contractAddress}`, @@ -88,19 +88,28 @@ export class ContractVerificationPluginClient extends PluginClient { const userSettings = this.getUserSettingsFromLocalStorage() const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings) - await this._verifyWithProvider('Sourcify', submittedContract, compilerAbstract, chainId, chainSettings) - - if (etherscanApiKey) { - if (!chainSettings.verifiers.Etherscan) chainSettings.verifiers.Etherscan = {} - chainSettings.verifiers.Etherscan.apiKey = etherscanApiKey - await this._verifyWithProvider('Etherscan', submittedContract, compilerAbstract, chainId, chainSettings) - } else { - await this.call('terminal', 'log', { type: 'warn', value: 'Etherscan verification skipped: API key not found in global Settings.' }) + if (validConfiguration(chainSettings, 'Sourcify')) { + await this._verifyWithProvider('Sourcify', submittedContract, compilerAbstract, chainId, chainSettings) + } + + if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.includes('etherscan'))) { + if (etherscanApiKey) { + if (!chainSettings.verifiers.Etherscan) chainSettings.verifiers.Etherscan = {} + chainSettings.verifiers.Etherscan.apiKey = etherscanApiKey + await this._verifyWithProvider('Etherscan', submittedContract, compilerAbstract, chainId, chainSettings) + } else { + await this.call('terminal', 'log', { type: 'warn', value: 'Etherscan verification skipped: API key not found in global Settings.' }) + } } - await this._verifyWithProvider('Routescan', submittedContract, compilerAbstract, chainId, chainSettings) - await this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings) + if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('routescan'))) { + await this._verifyWithProvider('Routescan', submittedContract, compilerAbstract, chainId, chainSettings) + } + if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.includes('blockscout'))) { + await this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings) + } + } catch (error) { await this.call('terminal', 'log', { type: 'error', value: `An unexpected error occurred during verification: ${error.message}` }) } @@ -140,7 +149,7 @@ export class ContractVerificationPluginClient extends PluginClient { } private getUserSettingsFromLocalStorage(): ContractVerificationSettings { - const fallbackSettings = { chains: {} }; + const fallbackSettings = { chains: {} } try { const settings = window.localStorage.getItem("contract-verification:settings") return settings ? JSON.parse(settings) : fallbackSettings diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index 353390bd5e5..3d47aa1a4bc 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -174,16 +174,16 @@ export const createInstance = async ( const finalCb = async (error, contractObject, address) => { if (error) { const log = logBuilder(error) - return terminalLogger(plugin, log) } + addInstance(dispatch, { contractData: contractObject, address, name: contractObject.name }) const data = await plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) - plugin.compilersArtefacts.addResolvedContract(addressToString(address), data) - + if (isVerifyChecked) { _paq.push(['trackEvent', 'udapp', 'DeployAndVerify', plugin.REACT_API.networkName]) + try { await publishToStorage('ipfs', selectedContract) } catch (e) { @@ -196,38 +196,51 @@ export const createInstance = async ( const log = logBuilder(msg) terminalLogger(plugin, log) - const etherscanApiKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token') + try { + const status = plugin.blockchain.getCurrentNetworkStatus() + if (status.error || !status.network) { + throw new Error(`Could not get network status: ${status.error || 'Unknown error'}`) + } + const currentChainId = parseInt(status.network.id) + + const response = await fetch('https://chainid.network/chains.json') + if (!response.ok) throw new Error('Could not fetch chains list from chainid.network.') + const allChains = await response.json() + const currentChain = allChains.find(chain => chain.chainId === currentChainId) + + if (!currentChain) { + const errorMsg = `The current network (Chain ID: ${currentChainId}) is not supported for verification via this plugin. Please switch to a supported network like Sepolia or Mainnet.` + const errorLog = logBuilder(errorMsg) + terminalLogger(plugin, errorLog) + return + } + + const etherscanApiKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token') + + const verificationData = { + chainId: currentChainId.toString(), + currentChain: currentChain, + contractAddress: addressToString(address), + contractName: selectedContract.name, + compilationResult: await plugin.compilersArtefacts.getCompilerAbstract(selectedContract.contract.file), + constructorArgs: args, + etherscanApiKey: etherscanApiKey + } + + await plugin.call('contract-verification', 'verifyOnDeploy', verificationData) - const status = plugin.blockchain.getCurrentNetworkStatus(); - if (status.error || !status.network) { - const errorMsg = `Could not get network status for verification: ${status.error || 'Unknown error'}` + } catch (e) { + const errorMsg = `Verification setup failed: ${e.message}` const errorLog = logBuilder(errorMsg) terminalLogger(plugin, errorLog) - return } - const chainId = status.network.id - - const verificationData = { - chainId: chainId, - contractAddress: addressToString(address), - contractName: selectedContract.name, - compilationResult: await plugin.compilersArtefacts.getCompilerAbstract(selectedContract.contract.file), - constructorArgs: args, - etherscanApiKey: etherscanApiKey - } - - console.log({verificationData}) - - plugin.call('contract-verification', 'verifyOnDeploy', verificationData) - } else { _paq.push(['trackEvent', 'udapp', 'DeployOnly', plugin.REACT_API.networkName]) } if (isProxyDeployment) { const initABI = contractObject.abi.find(abi => abi.name === 'initialize') - plugin.call('openzeppelin-proxy', 'executeUUPSProxy', addressToString(address), args, initABI, contractObject) } else if (isContractUpgrade) { plugin.call('openzeppelin-proxy', 'executeUUPSContractUpgrade', args, addressToString(address), contractObject) From a97b17a8fbe50d884ed254d3307fa4586a1a2489 Mon Sep 17 00:00:00 2001 From: pipper Date: Mon, 8 Sep 2025 12:03:34 +0200 Subject: [PATCH 21/38] dynamic conversation starters --- .../src/components/chat.tsx | 47 ++++++-- .../src/lib/conversationStarters.ts | 114 ++++++++++++++++++ 2 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts diff --git a/libs/remix-ui/remix-ai-assistant/src/components/chat.tsx b/libs/remix-ui/remix-ai-assistant/src/components/chat.tsx index 18a49a6848a..db5bfc2f2c6 100644 --- a/libs/remix-ui/remix-ai-assistant/src/components/chat.tsx +++ b/libs/remix-ui/remix-ai-assistant/src/components/chat.tsx @@ -2,16 +2,23 @@ import ReactMarkdown from "react-markdown" import remarkGfm from "remark-gfm" import copy from "copy-to-clipboard" import { ChatMessage, assistantAvatar } from "../lib/types" -import React from 'react' +import React, { useState, useEffect } from 'react' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' import { CustomTooltip } from "@remix-ui/helper" +<<<<<<< HEAD const DEFAULT_SUGGESTIONS = [ 'What is a modifier?', 'What is a Uniswap hook?', 'What is a ZKP?' ] +======= +import { + sampleConversationStarters, + type ConversationStarter +} from "../lib/conversationStarters" +>>>>>>> f3c92312c9 (dynamic conversation starters) // ChatHistory component export interface ChatHistoryComponentProps { @@ -22,24 +29,48 @@ export interface ChatHistoryComponentProps { historyRef: React.RefObject } -const AiChatIntro = (props) => { +interface AiChatIntroProps { + sendPrompt: (prompt: string) => void +} + +const AiChatIntro: React.FC = ({ sendPrompt }) => { + const [conversationStarters, setConversationStarters] = useState([]) + + useEffect(() => { + // Sample new conversation starters when component mounts + const starters = sampleConversationStarters() + setConversationStarters(starters) + }, []) + + const refreshStarters = () => { + const newStarters = sampleConversationStarters() + setConversationStarters(newStarters) + } + return (
RemixAI logo
RemixAI

- RemixAI provides you personalized guidance as you build. It can break down concepts, - answer questions about blockchain technology and assist you with your smart contracts. + RemixAI provides you personalized guidance as you build. It can break down concepts, + answer questions about blockchain technology and assist you with your smart contracts.

+<<<<<<< HEAD
{DEFAULT_SUGGESTIONS.map((s, index) => ( +======= + + {/* Dynamic Conversation Starters */} +
+ {conversationStarters.map((starter, index) => ( +>>>>>>> f3c92312c9 (dynamic conversation starters) ))}
diff --git a/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts new file mode 100644 index 00000000000..412752e6f0f --- /dev/null +++ b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts @@ -0,0 +1,114 @@ +export interface ConversationStarter { + question: string; + level: 'beginner' | 'intermediate' | 'expert'; + category: string; +} + +export const CONVERSATION_STARTERS: ConversationStarter[] = [ + // Beginner Level (20 Questions) + { question: "Python or JavaScript first?", level: "beginner", category: "programming" }, + { question: "What's a smart contract?", level: "beginner", category: "blockchain" }, + { question: "Git workflow tips?", level: "beginner", category: "development" }, + { question: "Favorite code editor?", level: "beginner", category: "tools" }, + { question: "Best programming tutorial?", level: "beginner", category: "learning" }, + { question: "What's DeFi?", level: "beginner", category: "blockchain" }, + { question: "VS Code extensions you use?", level: "beginner", category: "tools" }, + { question: "How do NFTs work?", level: "beginner", category: "blockchain" }, + { question: "Debugging strategies?", level: "beginner", category: "development" }, + { question: "What's a blockchain?", level: "beginner", category: "blockchain" }, + { question: "Frontend or backend preference?", level: "beginner", category: "programming" }, + { question: "Ethereum vs Bitcoin difference?", level: "beginner", category: "blockchain" }, + { question: "API vs library difference?", level: "beginner", category: "programming" }, + { question: "What's a crypto wallet?", level: "beginner", category: "blockchain" }, + { question: "Testing your code approach?", level: "beginner", category: "development" }, + { question: "Web3 vs Web2 difference?", level: "beginner", category: "blockchain" }, + { question: "Object-oriented vs functional?", level: "beginner", category: "programming" }, + { question: "What's a dApp?", level: "beginner", category: "blockchain" }, + { question: "Code review process?", level: "beginner", category: "development" }, + { question: "MetaMask usage experience?", level: "beginner", category: "blockchain" }, + + // Intermediate Level (20 Questions) + { question: "Solidity vs Rust for smart contracts?", level: "intermediate", category: "blockchain" }, + { question: "React hooks vs class components?", level: "intermediate", category: "programming" }, + { question: "Hardhat vs Foundry preference?", level: "intermediate", category: "tools" }, + { question: "Gas optimization techniques?", level: "intermediate", category: "blockchain" }, + { question: "GraphQL vs REST APIs?", level: "intermediate", category: "programming" }, + { question: "Layer 2 scaling solutions thoughts?", level: "intermediate", category: "blockchain" }, + { question: "Docker in development workflow?", level: "intermediate", category: "development" }, + { question: "Smart contract security practices?", level: "intermediate", category: "blockchain" }, + { question: "State management in React?", level: "intermediate", category: "programming" }, + { question: "IPFS implementation experience?", level: "intermediate", category: "blockchain" }, + { question: "TypeScript adoption benefits?", level: "intermediate", category: "programming" }, + { question: "DeFi protocol risks?", level: "intermediate", category: "blockchain" }, + { question: "Microservices vs monoliths?", level: "intermediate", category: "development" }, + { question: "Cross-chain bridge security?", level: "intermediate", category: "blockchain" }, + { question: "CI/CD pipeline setup?", level: "intermediate", category: "development" }, + { question: "Upgradeable contracts pros/cons?", level: "intermediate", category: "blockchain" }, + { question: "Database choice for dApps?", level: "intermediate", category: "development" }, + { question: "Wallet integration challenges?", level: "intermediate", category: "blockchain" }, + { question: "Smart contract testing frameworks?", level: "intermediate", category: "development" }, + { question: "Web3 authentication methods?", level: "intermediate", category: "blockchain" }, + + // Expert Level (20 Questions) + { question: "Account abstraction impact on UX?", level: "expert", category: "blockchain" }, + { question: "MEV protection strategies?", level: "expert", category: "blockchain" }, + { question: "ZK-rollups vs optimistic rollups?", level: "expert", category: "blockchain" }, + { question: "Formal verification tools worth it?", level: "expert", category: "development" }, + { question: "Intent-based architecture thoughts?", level: "expert", category: "blockchain" }, + { question: "Modular blockchain stacks future?", level: "expert", category: "blockchain" }, + { question: "Cross-chain messaging protocols?", level: "expert", category: "blockchain" }, + { question: "EIP-4844 blob space economics?", level: "expert", category: "blockchain" }, + { question: "Restaking security assumptions?", level: "expert", category: "blockchain" }, + { question: "AI-assisted smart contract auditing?", level: "expert", category: "development" }, + { question: "Maximal extractable value mitigation?", level: "expert", category: "blockchain" }, + { question: "Threshold cryptography applications?", level: "expert", category: "blockchain" }, + { question: "Verifiable delay functions usage?", level: "expert", category: "blockchain" }, + { question: "Proto-danksharding readiness?", level: "expert", category: "blockchain" }, + { question: "Homomorphic encryption in web3?", level: "expert", category: "blockchain" }, + { question: "Consensus mechanism evolution?", level: "expert", category: "blockchain" }, + { question: "Interchain security models?", level: "expert", category: "blockchain" }, + { question: "Decentralized sequencer designs?", level: "expert", category: "blockchain" }, + { question: "Proof aggregation scalability?", level: "expert", category: "blockchain" }, + { question: "Quantum-resistant cryptography timeline?", level: "expert", category: "blockchain" } +]; + +/** + * Randomly samples one question from each difficulty level + * @returns An array of 3 conversation starters (beginner, intermediate, expert) + */ +export function sampleConversationStarters(): ConversationStarter[] { + const beginnerQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'beginner'); + const intermediateQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'intermediate'); + const expertQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'expert'); + + const randomBeginner = beginnerQuestions[Math.floor(Math.random() * beginnerQuestions.length)]; + const randomIntermediate = intermediateQuestions[Math.floor(Math.random() * intermediateQuestions.length)]; + const randomExpert = expertQuestions[Math.floor(Math.random() * expertQuestions.length)]; + + return [randomBeginner, randomIntermediate, randomExpert]; +} + +/** + * Gets conversation starters with seeded randomization for consistent results in same session + * @param seed Optional seed for reproducible randomization + * @returns An array of 3 conversation starters (beginner, intermediate, expert) + */ +export function sampleConversationStartersWithSeed(seed?: number): ConversationStarter[] { + const seededRandom = (seedValue: number) => { + const x = Math.sin(seedValue) * 10000; + return x - Math.floor(x); + }; + + const actualSeed = seed ?? Date.now(); + + const beginnerQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'beginner'); + const intermediateQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'intermediate'); + const expertQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'expert'); + + const randomBeginner = beginnerQuestions[Math.floor(seededRandom(actualSeed) * beginnerQuestions.length)]; + const randomIntermediate = intermediateQuestions[Math.floor(seededRandom(actualSeed + 1) * intermediateQuestions.length)]; + const randomExpert = expertQuestions[Math.floor(seededRandom(actualSeed + 2) * expertQuestions.length)]; + + return [randomBeginner, randomIntermediate, randomExpert]; +} + From c61dd05e0c21d8daf9758fdc9deb84505992f026 Mon Sep 17 00:00:00 2001 From: pipper Date: Tue, 9 Sep 2025 18:13:14 +0200 Subject: [PATCH 22/38] lint --- libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts index 412752e6f0f..a6eac8064f0 100644 --- a/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts +++ b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts @@ -100,7 +100,6 @@ export function sampleConversationStartersWithSeed(seed?: number): ConversationS }; const actualSeed = seed ?? Date.now(); - const beginnerQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'beginner'); const intermediateQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'intermediate'); const expertQuestions = CONVERSATION_STARTERS.filter(q => q.level === 'expert'); From 6a9f89e335252016b59815964dae4bb4a1bc4466 Mon Sep 17 00:00:00 2001 From: ryestew Date: Tue, 9 Sep 2025 11:57:05 -0400 Subject: [PATCH 23/38] update questions --- .../src/lib/conversationStarters.ts | 73 +++++++------------ 1 file changed, 25 insertions(+), 48 deletions(-) diff --git a/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts index a6eac8064f0..97ec9272450 100644 --- a/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts +++ b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts @@ -6,70 +6,47 @@ export interface ConversationStarter { export const CONVERSATION_STARTERS: ConversationStarter[] = [ // Beginner Level (20 Questions) - { question: "Python or JavaScript first?", level: "beginner", category: "programming" }, - { question: "What's a smart contract?", level: "beginner", category: "blockchain" }, - { question: "Git workflow tips?", level: "beginner", category: "development" }, - { question: "Favorite code editor?", level: "beginner", category: "tools" }, - { question: "Best programming tutorial?", level: "beginner", category: "learning" }, - { question: "What's DeFi?", level: "beginner", category: "blockchain" }, - { question: "VS Code extensions you use?", level: "beginner", category: "tools" }, + { question: "", level: "beginner", category: "programming" }, + { question: "How to use blob storage?", level: "beginner", category: "Solidity" }, + { question: "What is the difference between storage, memory, and calldata in Solidity?", level: "beginner", category: "Solidity" }, + { question: "How are dynamic arrays stored in contract storage?", level: "beginner", category: "Solidity" }, + { question: "How does delegatecall differ from call? ", level: "beginner", category: "Solidity" }, + { question: "How to avoid using dynamic array in Solidity?", level: "beginner", category: "Solidity" }, + { question: "List some gas saving techniques", level: "beginner", category: "Solidity" }, { question: "How do NFTs work?", level: "beginner", category: "blockchain" }, { question: "Debugging strategies?", level: "beginner", category: "development" }, - { question: "What's a blockchain?", level: "beginner", category: "blockchain" }, - { question: "Frontend or backend preference?", level: "beginner", category: "programming" }, - { question: "Ethereum vs Bitcoin difference?", level: "beginner", category: "blockchain" }, - { question: "API vs library difference?", level: "beginner", category: "programming" }, - { question: "What's a crypto wallet?", level: "beginner", category: "blockchain" }, - { question: "Testing your code approach?", level: "beginner", category: "development" }, - { question: "Web3 vs Web2 difference?", level: "beginner", category: "blockchain" }, - { question: "Object-oriented vs functional?", level: "beginner", category: "programming" }, - { question: "What's a dApp?", level: "beginner", category: "blockchain" }, - { question: "Code review process?", level: "beginner", category: "development" }, - { question: "MetaMask usage experience?", level: "beginner", category: "blockchain" }, // Intermediate Level (20 Questions) - { question: "Solidity vs Rust for smart contracts?", level: "intermediate", category: "blockchain" }, - { question: "React hooks vs class components?", level: "intermediate", category: "programming" }, - { question: "Hardhat vs Foundry preference?", level: "intermediate", category: "tools" }, - { question: "Gas optimization techniques?", level: "intermediate", category: "blockchain" }, - { question: "GraphQL vs REST APIs?", level: "intermediate", category: "programming" }, - { question: "Layer 2 scaling solutions thoughts?", level: "intermediate", category: "blockchain" }, - { question: "Docker in development workflow?", level: "intermediate", category: "development" }, - { question: "Smart contract security practices?", level: "intermediate", category: "blockchain" }, - { question: "State management in React?", level: "intermediate", category: "programming" }, - { question: "IPFS implementation experience?", level: "intermediate", category: "blockchain" }, - { question: "TypeScript adoption benefits?", level: "intermediate", category: "programming" }, - { question: "DeFi protocol risks?", level: "intermediate", category: "blockchain" }, - { question: "Microservices vs monoliths?", level: "intermediate", category: "development" }, - { question: "Cross-chain bridge security?", level: "intermediate", category: "blockchain" }, - { question: "CI/CD pipeline setup?", level: "intermediate", category: "development" }, - { question: "Upgradeable contracts pros/cons?", level: "intermediate", category: "blockchain" }, - { question: "Database choice for dApps?", level: "intermediate", category: "development" }, - { question: "Wallet integration challenges?", level: "intermediate", category: "blockchain" }, - { question: "Smart contract testing frameworks?", level: "intermediate", category: "development" }, - { question: "Web3 authentication methods?", level: "intermediate", category: "blockchain" }, + { question: "What’s a Uniswap hook?", level: "intermediate", category: "DeFi" }, + { question: "How to use 1inch?", level: "intermediate", category: "DeFi" }, + { question: "Show a contract that includes a flash loan", level: "intermediate", category: "DeFi" }, + { question: "Show a smart contract that records carbon credits", level: "intermediate", category: "blockchain" }, + { question: "Show a sybil-resistant voting contract", level: "intermediate", category: "programming" }, // Expert Level (20 Questions) { question: "Account abstraction impact on UX?", level: "expert", category: "blockchain" }, - { question: "MEV protection strategies?", level: "expert", category: "blockchain" }, + { question: "MEV protection strategies?", level: "expert", category: "DeFi" }, { question: "ZK-rollups vs optimistic rollups?", level: "expert", category: "blockchain" }, { question: "Formal verification tools worth it?", level: "expert", category: "development" }, - { question: "Intent-based architecture thoughts?", level: "expert", category: "blockchain" }, - { question: "Modular blockchain stacks future?", level: "expert", category: "blockchain" }, + { question: "What is the power of tau?", level: "expert", category: "ZK" }, + { question: "Groth16 vs Plonk?", level: "expert", category: "ZK" }, { question: "Cross-chain messaging protocols?", level: "expert", category: "blockchain" }, { question: "EIP-4844 blob space economics?", level: "expert", category: "blockchain" }, { question: "Restaking security assumptions?", level: "expert", category: "blockchain" }, { question: "AI-assisted smart contract auditing?", level: "expert", category: "development" }, { question: "Maximal extractable value mitigation?", level: "expert", category: "blockchain" }, - { question: "Threshold cryptography applications?", level: "expert", category: "blockchain" }, - { question: "Verifiable delay functions usage?", level: "expert", category: "blockchain" }, + { question: "Explain a witness in a ZK circuit", level: "expert", category: "ZK" }, + { question: "Explain a rate limiting nullifier", level: "expert", category: "blockchain" }, { question: "Proto-danksharding readiness?", level: "expert", category: "blockchain" }, { question: "Homomorphic encryption in web3?", level: "expert", category: "blockchain" }, - { question: "Consensus mechanism evolution?", level: "expert", category: "blockchain" }, - { question: "Interchain security models?", level: "expert", category: "blockchain" }, - { question: "Decentralized sequencer designs?", level: "expert", category: "blockchain" }, - { question: "Proof aggregation scalability?", level: "expert", category: "blockchain" }, - { question: "Quantum-resistant cryptography timeline?", level: "expert", category: "blockchain" } + { question: "Explain the UUPS upgradeable contract", level: "expert", category: "blockchain" }, + { question: "Explain the Diamond Pattern", level: "expert", category: "blockchain" }, + { question: "Explain an underflow in Solidity", level: "expert", category: "blockchain" }, + { question: "What are some tools that can help with security?", level: "expert", category: "blockchain" }, + { question: "Explain the Transparent upgradeable contract", level: "expert", category: "blockchain" }, + { question: "What the difference between an ERC and an EIP?", level: "expert", category: "blockchain" } + { question: "How to work with EIP 7702?", level: "expert", category: "blockchain" }, + { question: "How to work a EIP 4337 Smart Account", level: "expert", category: "blockchain" }, ]; /** From 07a32781601a70cd9ec3aa2464e5a2f840011557 Mon Sep 17 00:00:00 2001 From: pipper Date: Tue, 9 Sep 2025 18:33:05 +0200 Subject: [PATCH 24/38] minor --- .../remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts index 97ec9272450..d8afebf2791 100644 --- a/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts +++ b/libs/remix-ui/remix-ai-assistant/src/lib/conversationStarters.ts @@ -44,7 +44,7 @@ export const CONVERSATION_STARTERS: ConversationStarter[] = [ { question: "Explain an underflow in Solidity", level: "expert", category: "blockchain" }, { question: "What are some tools that can help with security?", level: "expert", category: "blockchain" }, { question: "Explain the Transparent upgradeable contract", level: "expert", category: "blockchain" }, - { question: "What the difference between an ERC and an EIP?", level: "expert", category: "blockchain" } + { question: "What the difference between an ERC and an EIP?", level: "expert", category: "blockchain" }, { question: "How to work with EIP 7702?", level: "expert", category: "blockchain" }, { question: "How to work a EIP 4337 Smart Account", level: "expert", category: "blockchain" }, ]; From 0a3dad19540128f3536ac6bb7d39da6936273142 Mon Sep 17 00:00:00 2001 From: pipper Date: Wed, 17 Sep 2025 10:44:38 +0200 Subject: [PATCH 25/38] fixed e2e --- apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts b/apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts index 802afbb1882..e8e0af546c8 100644 --- a/apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts +++ b/apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts @@ -11,7 +11,9 @@ module.exports = { 'Check if RemixAI plugin is pinned #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="movePluginToLeft"]') - .waitForElementVisible('*[data-id="remix-ai-assistant-starter-0"]') + .waitForElementVisible('*[data-id="remix-ai-assistant-starter-beginner-0"]') + .waitForElementVisible('*[data-id="remix-ai-assistant-starter-intermediate-1"]') + .waitForElementVisible('*[data-id="remix-ai-assistant-starter-expert-2"]') .click('*[data-id="movePluginToLeft"]') .waitForElementVisible('*[data-pinnedPlugin="movePluginToRight-remixaiassistant"]') }, From 73b0d35cbbd0a16d2e0285f5439a2ccd11c262b7 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 17 Sep 2025 15:44:51 +0200 Subject: [PATCH 26/38] move repo --- apps/learneth/README.md | 8 ++++---- apps/learneth/src/redux/models/workshop.ts | 6 +++--- .../remix-ui/home-tab/src/lib/components/homeTabLearn.tsx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/learneth/README.md b/apps/learneth/README.md index ac17152a772..5abffa5f9fd 100644 --- a/apps/learneth/README.md +++ b/apps/learneth/README.md @@ -34,7 +34,7 @@ cannot compile and test files in the workshops. You can create your own workshops that can be imported in the plugin. When importing a github repo the plugin will look for a directory structure describing the workshops. -For example: https://github.com/ethereum/remix-workshops +For example: https://github.com/remix-project-org/remix-workshops ### Root directories @@ -118,9 +118,9 @@ tags: (function () { try { // You don't need to add a separate addRepository before calling startTutorial, this is just an example - remix.call('LearnEth', 'addRepository', "ethereum/remix-workshops", "master") - remix.call('LearnEth', 'startTutorial', "ethereum/remix-workshops", "master", "basics") - remix.call('LearnEth', 'startTutorial', "ethereum/remix-workshops", "master", 2) + remix.call('LearnEth', 'addRepository', "remix-project-org/remix-workshops", "master") + remix.call('LearnEth', 'startTutorial', "remix-project-org/remix-workshops", "master", "basics") + remix.call('LearnEth', 'startTutorial', "remix-project-org/remix-workshops", "master", 2) } catch (e) { console.log(e.message) } diff --git a/apps/learneth/src/redux/models/workshop.ts b/apps/learneth/src/redux/models/workshop.ts index 85bb522edd6..2dfa746d7e0 100644 --- a/apps/learneth/src/redux/models/workshop.ts +++ b/apps/learneth/src/redux/models/workshop.ts @@ -10,15 +10,15 @@ const apiUrl = 'https://learneth.api.remix.live' export const repoMap = { en: { - name: 'ethereum/remix-workshops', + name: 'remix-project-org/remix-workshops', branch: 'master', }, zh: { - name: 'ethereum/remix-workshops', + name: 'remix-project-org/remix-workshops', branch: 'zh', }, es: { - name: 'ethereum/remix-workshops', + name: 'remix-project-org/remix-workshops', branch: 'es', }, } diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabLearn.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabLearn.tsx index 51c23c9ff29..32250dce623 100644 --- a/libs/remix-ui/home-tab/src/lib/components/homeTabLearn.tsx +++ b/libs/remix-ui/home-tab/src/lib/components/homeTabLearn.tsx @@ -31,7 +31,7 @@ function HomeTabLearn({ plugin }: HomeTabLearnProps) { const startLearnEthTutorial = async (tutorial: 'basics' | 'soliditybeginner' | 'deploylibraries') => { await plugin.appManager.activatePlugin(['solidity', 'LearnEth', 'solidityUnitTesting']) plugin.verticalIcons.select('LearnEth') - plugin.call('LearnEth', 'startTutorial', 'ethereum/remix-workshops', 'master', tutorial) + plugin.call('LearnEth', 'startTutorial', 'remix-project-org/remix-workshops', 'master', tutorial) _paq.push(['trackEvent', 'hometab', 'startLearnEthTutorial', tutorial]) } From 9694dae9926f4a37d28164f791f448eff023876f Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 18 Sep 2025 18:05:19 +0530 Subject: [PATCH 27/38] fix matomo settings --- apps/remix-ide/src/app/tabs/settings-tab.tsx | 1 + libs/remix-ui/settings/src/lib/remix-ui-settings.tsx | 4 ++++ libs/remix-ui/settings/src/lib/settingsReducer.ts | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/tabs/settings-tab.tsx b/apps/remix-ide/src/app/tabs/settings-tab.tsx index abf6995a9d8..993f74e4fd6 100644 --- a/apps/remix-ide/src/app/tabs/settings-tab.tsx +++ b/apps/remix-ide/src/app/tabs/settings-tab.tsx @@ -129,6 +129,7 @@ export default class SettingsTab extends ViewPlugin { // set timestamp to local storage to track when the user has given consent localStorage.setItem('matomo-analytics-consent', Date.now().toString()) this.useMatomoPerfAnalytics = isChecked + this.emit('matomoPerfAnalyticsChoiceUpdated', isChecked) if (!isChecked) { // revoke tracking consent for performance data _paq.push(['disableCookies']) diff --git a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx index 014985de496..8d0d5da831f 100644 --- a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx +++ b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx @@ -244,6 +244,10 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => { dispatch({ type: 'SET_VALUE', payload: { name: 'copilot/suggest/activate', value: isChecked } }) }) + props.plugin.on('settings', 'matomoPerfAnalyticsChoiceUpdated', (isChecked) => { + dispatch({ type: 'SET_VALUE', payload: { name: 'matomo-perf-analytics', value: isChecked } }) + }) + }, []) useEffect(() => { diff --git a/libs/remix-ui/settings/src/lib/settingsReducer.ts b/libs/remix-ui/settings/src/lib/settingsReducer.ts index c4b703f03e8..e17b706eab4 100644 --- a/libs/remix-ui/settings/src/lib/settingsReducer.ts +++ b/libs/remix-ui/settings/src/lib/settingsReducer.ts @@ -88,7 +88,7 @@ export const initialState: SettingsState = { isLoading: false }, 'matomo-analytics': { - value: config.get('settings/matomo-analytics') || false, + value: config.get('settings/matomo-analytics') || true, isLoading: false }, 'auto-completion': { From cb610dd2c52f737850734d51a7ca565952cad7f2 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 18 Sep 2025 18:20:42 +0530 Subject: [PATCH 28/38] remove lang selection --- apps/remix-ide/src/app/tabs/settings-tab.tsx | 3 --- libs/remix-ui/settings/src/lib/remix-ui-settings.tsx | 10 ---------- libs/remix-ui/settings/src/lib/select-dropdown.tsx | 10 ---------- libs/remix-ui/settings/src/lib/settingsReducer.ts | 5 ----- libs/remix-ui/settings/src/types/index.ts | 1 - 5 files changed, 29 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/settings-tab.tsx b/apps/remix-ide/src/app/tabs/settings-tab.tsx index 993f74e4fd6..69a653b65db 100644 --- a/apps/remix-ide/src/app/tabs/settings-tab.tsx +++ b/apps/remix-ide/src/app/tabs/settings-tab.tsx @@ -33,7 +33,6 @@ export default class SettingsTab extends ViewPlugin { editor: any private _deps: { themeModule: any - localeModule: any } element: HTMLDivElement public useMatomoAnalytics: any @@ -48,7 +47,6 @@ export default class SettingsTab extends ViewPlugin { this.editor = editor this._deps = { themeModule: Registry.getInstance().get('themeModule').api, - localeModule: Registry.getInstance().get('localeModule').api } this.element = document.createElement('div') this.element.setAttribute('id', 'settingsTab') @@ -82,7 +80,6 @@ export default class SettingsTab extends ViewPlugin { useMatomoPerfAnalytics={state.useMatomoPerfAnalytics} useCopilot={state.useCopilot} themeModule={state._deps.themeModule} - localeModule={state._deps.localeModule} /> ) } diff --git a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx index 8d0d5da831f..294664289a4 100644 --- a/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx +++ b/libs/remix-ui/settings/src/lib/remix-ui-settings.tsx @@ -6,7 +6,6 @@ import { EtherscanConfigDescription, GitHubCredentialsDescription, SindriCredent import { initialState, settingReducer } from './settingsReducer' import {Toaster} from '@remix-ui/toaster' // eslint-disable-line import { ThemeModule } from '@remix-ui/theme-module' -import { LocaleModule } from '@remix-ui/locale-module' import { ThemeContext, themes } from '@remix-ui/home-tab' import { FormattedMessage } from 'react-intl' import { Registry } from '@remix-project/remix-lib' @@ -23,7 +22,6 @@ export interface RemixUiSettingsProps { useMatomoPerfAnalytics: boolean useCopilot: boolean themeModule: ThemeModule - localeModule: LocaleModule } const _paq = (window._paq = window._paq || []) @@ -73,14 +71,6 @@ const settingsSections: SettingsSection[] = [ { title: 'Appearance', options: [{ - name: 'locale', - label: 'settings.locales', - type: 'select', - selectOptions: settingsConfig.locales.map((locale) => ({ - label: locale.name.toLocaleUpperCase() + '-' + locale.localeName, - value: locale.code - })) - }, { name: 'theme', label: 'settings.theme', type: 'select', diff --git a/libs/remix-ui/settings/src/lib/select-dropdown.tsx b/libs/remix-ui/settings/src/lib/select-dropdown.tsx index c7a12529068..da2979d9a90 100644 --- a/libs/remix-ui/settings/src/lib/select-dropdown.tsx +++ b/libs/remix-ui/settings/src/lib/select-dropdown.tsx @@ -26,16 +26,6 @@ const SelectDropdown = ({ value, options, name, dispatch }: SelectDropdownProps) } else { console.error('Theme not found: ', value) } - } else if (name === 'locale') { - const localeModule = Registry.getInstance().get('localeModule').api - const locale = localeModule.getLocales().find((locale) => locale.code === value) - - if (locale) { - localeModule.switchLocale(locale.code) - dispatch({ type: 'SET_VALUE', payload: { name: name, value: locale.localeName } }) - } else { - console.error('Locale not found: ', value) - } } else { dispatch({ type: 'SET_VALUE', payload: { name: name, value: value } }) } diff --git a/libs/remix-ui/settings/src/lib/settingsReducer.ts b/libs/remix-ui/settings/src/lib/settingsReducer.ts index e17b706eab4..35907d6d19a 100644 --- a/libs/remix-ui/settings/src/lib/settingsReducer.ts +++ b/libs/remix-ui/settings/src/lib/settingsReducer.ts @@ -4,7 +4,6 @@ import { SettingsActions, SettingsState } from '../types' const config = Registry.getInstance().get('config').api const settingsConfig = Registry.getInstance().get('settingsConfig').api const defaultTheme = config.get('settings/theme') ? settingsConfig.themes.find((theme) => theme.name.toLowerCase() === config.get('settings/theme').toLowerCase()) : settingsConfig.themes[0] -const defaultLocale = config.get('settings/locale') ? settingsConfig.locales.find((locale) => (locale.code === config.get('settings/locale')) || locale.localeName.toLowerCase() === config.get('settings/locale').toLowerCase()) : settingsConfig.locales.find((locale) => locale.code === 'en') const gistAccessToken = config.get('settings/gist-access-token') || '' const githubUserName = config.get('settings/github-user-name') || '' const githubEmail = config.get('settings/github-email') || '' @@ -115,10 +114,6 @@ export const initialState: SettingsState = { value: defaultTheme ? defaultTheme.name : "Dark", isLoading: false }, - 'locale': { - value: defaultLocale ? defaultLocale.localeName : "English", - isLoading: false - }, 'github-config': { value: githubConfig, isLoading: false diff --git a/libs/remix-ui/settings/src/types/index.ts b/libs/remix-ui/settings/src/types/index.ts index 9106fddd571..2b6896d1d10 100644 --- a/libs/remix-ui/settings/src/types/index.ts +++ b/libs/remix-ui/settings/src/types/index.ts @@ -94,7 +94,6 @@ export interface SettingsState { 'copilot/suggest/activate': ConfigState 'save-evm-state': ConfigState, 'theme': ConfigState, - 'locale': ConfigState, 'github-config': ConfigState, 'ipfs-config': ConfigState, 'swarm-config': ConfigState, From ea96642a53a5f1d98e96106e62beae16ff7ac366 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 18 Sep 2025 18:26:26 +0530 Subject: [PATCH 29/38] RemixAI text --- apps/remix-ide/src/app/tabs/locales/en/settings.json | 10 +++++----- libs/remix-ai-core/src/agents/contractAgent.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/remix-ide/src/app/tabs/locales/en/settings.json b/apps/remix-ide/src/app/tabs/locales/en/settings.json index f473da1e0a1..ac9edcda3a8 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/settings.json +++ b/apps/remix-ide/src/app/tabs/locales/en/settings.json @@ -55,15 +55,15 @@ "settings.services": "Connected Services", "settings.generalSettingsDescription": "Manage code editor settings, UI theme, language and analytics permissions.", "settings.analyticsDescription": "Control how Remix uses AI and analytics to improve your experience.", - "settings.aiDescription": "The Remix AI Assistant enhances your coding experience with smart suggestions and automated insights. Manage how AI interacts with your code and data.", + "settings.aiDescription": "The RemixAI Assistant enhances your coding experience with smart suggestions and automated insights. Manage how RemixAI interacts with your code and data.", "settings.servicesDescription": "Configure the settings for connected services, including Github, IPFS, Swarm, Sindri and Etherscan.", "settings.matomoAnalyticsNoCookies": "Matomo Analytics (necessary, no cookies)", "settings.matomoAnalyticsNoCookiesDescription": "Help improve Remix with anonymous usage data.", "settings.matomoAnalyticsWithCookies": "Matomo Performance Analytics (with cookies)", "settings.matomoAnalyticsWithCookiesDescription": "Enable tracking with cookies for more detailed insights.", - "settings.aiCopilot": "AI Copilot", - "settings.aiCopilotDescription": "AI Copilot assists with code suggestions and improvements.", - "settings.aiPrivacyPolicy": "AI Privacy & Data Usage", + "settings.aiCopilot": "RemixAI Copilot", + "settings.aiCopilotDescription": "RemixAI Copilot assists with code suggestions and improvements.", + "settings.aiPrivacyPolicy": "RemixAI Privacy & Data Usage", "settings.viewPrivacyPolicy": "View Privacy Policy", - "settings.aiPrivacyPolicyDescription": "Understand how AI processes your data." + "settings.aiPrivacyPolicyDescription": "Understand how RemixAI processes your data." } diff --git a/libs/remix-ai-core/src/agents/contractAgent.ts b/libs/remix-ai-core/src/agents/contractAgent.ts index eb7ba3298e3..0d3d7c871df 100644 --- a/libs/remix-ai-core/src/agents/contractAgent.ts +++ b/libs/remix-ai-core/src/agents/contractAgent.ts @@ -3,7 +3,7 @@ import { workspaceAgent } from "./workspaceAgent"; import { CompilationResult } from "../types/types"; import { compilecontracts, compilationParams } from "../helpers/compile"; import { OllamaInferencer } from "../inferencers/local/ollamaInferencer" -const COMPILATION_WARNING_MESSAGE = '⚠️**Warning**: The compilation failed. Please check the compilation errors in the Solidity compiler plugin. Enter `/continue` or `/c` if you want Remix AI to try again until a compilable solution is generated?' +const COMPILATION_WARNING_MESSAGE = '⚠️**Warning**: The compilation failed. Please check the compilation errors in the Solidity compiler plugin. Enter `/continue` or `/c` if you want RemixAI to try again until a compilable solution is generated?' export class ContractAgent { plugin: any; From 2c034cf847adf76df9ce5731d9400fd0e64adbc6 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 18 Sep 2025 18:56:54 +0530 Subject: [PATCH 30/38] remove locale e2e --- .../src/tests/generalSettings.test.ts | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts index cf2f06d337e..99c14475172 100644 --- a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts +++ b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts @@ -83,38 +83,6 @@ module.exports = { // .checkElementStyle(':root', '--danger', remixIdeThemes.light.danger) // }, - - - 'Should load zh locale ': function (browser) { - browser.waitForElementVisible('*[data-id="settings-sidebar-general"]') - .click('*[data-id="settings-sidebar-general"]') - .pause(100) - .scrollAndClick('*[data-id="settingsTabDropdownTogglelocale"]') - .waitForElementVisible('[data-id="custom-dropdown-items"]') - .waitForElementVisible('[data-id="settingsTabDropdownItemzh"]') - .click('[data-id="settingsTabDropdownItemzh"]') - .pause(2000) - .assert.containsText('[data-id="settings-sidebar-header"] h2', '设置') - .assert.containsText('*[data-id="listenNetworkCheckInput"]', '监听所有交易') - .assert.containsText('*[data-id="settingsTabgenerate-contract-metadataLabel"]', '生成合约元数据') - .assert.containsText('*[data-id="settingsTabauto-completionLabel"]', '在编辑器中启用代码自动补全') - .assert.containsText('*[data-id="settingsTabshow-gasLabel"]', '在编辑器中展示 gas 预算') - .assert.containsText('*[data-id="settingsTabdisplay-errorsLabel"]', '编辑代码时展示错误提示') - }, - - 'Should load en locale ': function (browser) { - browser.scrollAndClick('*[data-id="settingsTabDropdownTogglelocale"]') - .waitForElementVisible('[data-id="custom-dropdown-items"]') - .waitForElementVisible('[data-id="settingsTabDropdownItemen"]') - .click('[data-id="settingsTabDropdownItemen"]') - .pause(2000) - .assert.containsText('[data-id="settings-sidebar-header"] h2', 'Settings') - .assert.containsText('*[data-id="listenNetworkCheckInput"]', 'Listen on all transactions') - .assert.containsText('*[data-id="settingsTabgenerate-contract-metadataLabel"]', 'Generate contract metadata') - .assert.containsText('*[data-id="settingsTabauto-completionLabel"]', 'Enable code completion in editor') - .assert.containsText('*[data-id="settingsTabshow-gasLabel"]', 'Display gas estimates in editor') - .assert.containsText('*[data-id="settingsTabdisplay-errorsLabel"]', 'Display errors in editor while typing') - } } const remixIdeThemes = { From faaf3842bfd7f75f03d3692d7e1aed1504d90cfc Mon Sep 17 00:00:00 2001 From: ci-bot Date: Mon, 22 Sep 2025 15:51:22 +0900 Subject: [PATCH 31/38] e2e test --- .../app/ContractVerificationPluginClient.ts | 23 +++++++------ .../src/tests/deploy_vefiry.test.ts | 33 +++++++++++++++++++ .../run-tab/src/lib/actions/deploy.ts | 10 +++--- 3 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index 3d16900e017..ac268143c6a 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -92,6 +92,14 @@ export class ContractVerificationPluginClient extends PluginClient { await this._verifyWithProvider('Sourcify', submittedContract, compilerAbstract, chainId, chainSettings) } + if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('routescan'))) { + await this._verifyWithProvider('Routescan', submittedContract, compilerAbstract, chainId, chainSettings) + } + + if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('blockscout'))) { + await this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings) + } + if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.includes('etherscan'))) { if (etherscanApiKey) { if (!chainSettings.verifiers.Etherscan) chainSettings.verifiers.Etherscan = {} @@ -101,14 +109,6 @@ export class ContractVerificationPluginClient extends PluginClient { await this.call('terminal', 'log', { type: 'warn', value: 'Etherscan verification skipped: API key not found in global Settings.' }) } } - - if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('routescan'))) { - await this._verifyWithProvider('Routescan', submittedContract, compilerAbstract, chainId, chainSettings) - } - - if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.includes('blockscout'))) { - await this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings) - } } catch (error) { await this.call('terminal', 'log', { type: 'error', value: `An unexpected error occurred during verification: ${error.message}` }) @@ -124,7 +124,7 @@ export class ContractVerificationPluginClient extends PluginClient { ): Promise => { try { if (validConfiguration(chainSettings, providerName)) { - await this.call('terminal', 'log', { type: 'info', value: `Verifying with ${providerName}...` }) + await this.call('terminal', 'log', { type: 'log', value: `Verifying with ${providerName}...` }) const verifierSettings = chainSettings.verifiers[providerName] const verifier = getVerifier(providerName, verifierSettings) @@ -132,12 +132,11 @@ export class ContractVerificationPluginClient extends PluginClient { if (verifier && typeof verifier.verify === 'function') { const result = await verifier.verify(submittedContract, compilerAbstract) - let successMessage = `${providerName} verification successful! Status: ${result.status}` - if (result.receiptId) successMessage += `, Receipt ID: ${result.receiptId}` + let successMessage = `${providerName} verification successful.` await this.call('terminal', 'log', { type: 'info', value: successMessage }) if (result.lookupUrl) { - await this.call('terminal', 'log', { type: 'html', value: `Check status: ${providerName} Link` }) + await this.call('terminal', 'log', { type: 'info', value: result.lookupUrl }) } } else { await this.call('terminal', 'log', { type: 'warn', value: `${providerName} verifier is not properly configured or does not support direct verification.` }) diff --git a/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts b/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts new file mode 100644 index 00000000000..15cef7b83c1 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts @@ -0,0 +1,33 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +declare global { + interface Window { testplugin: { name: string, url: string }; } +} + +module.exports = { + '@disabled': true, + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, null) + }, + + 'Should show warning for unsupported network when deploying with "Verify" on Remix VM #group1 #pr': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="remixIdeSidePanel"]') + .clickLaunchIcon('filePanel') + .click('*[data-id="treeViewLitreeViewItemcontracts"]') + .openFile('contracts/1_Storage.sol') + .clickLaunchIcon('udapp') + .waitForElementVisible('*[data-id="Deploy - transact (not payable)"]') + .waitForElementVisible('#deployAndRunVerifyContract') + .click('#deployAndRunVerifyContract') + .click('*[data-id="Deploy - transact (not payable)"]') + .waitForElementVisible({ + selector: "//*[contains(text(),'is not supported for verification via this plugin')]", + locateStrategy: 'xpath', + timeout: 10000 + }) + .end() + } +} \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index 3d47aa1a4bc..f8bc44a38ce 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -192,10 +192,6 @@ export const createInstance = async ( terminalLogger(plugin, errorLog) } - const msg = `Contract deployed successfully at ${addressToString(address)}. Starting verification process...` - const log = logBuilder(msg) - terminalLogger(plugin, log) - try { const status = plugin.blockchain.getCurrentNetworkStatus() if (status.error || !status.network) { @@ -226,8 +222,10 @@ export const createInstance = async ( constructorArgs: args, etherscanApiKey: etherscanApiKey } - - await plugin.call('contract-verification', 'verifyOnDeploy', verificationData) + + setTimeout(async () => { + await plugin.call('contract-verification', 'verifyOnDeploy', verificationData) + }, 1000) } catch (e) { const errorMsg = `Verification setup failed: ${e.message}` From 833e44f074e40f1b8cc0644e28b01e7dece6caf3 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 24 Sep 2025 15:49:26 +0900 Subject: [PATCH 32/38] feat(deployment): add receipt to verification plugin during contract deployment & verification --- .../app/ContractVerificationPluginClient.ts | 62 +++++++++++++++---- apps/contract-verification/src/app/app.tsx | 11 +++- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index ac268143c6a..ecee2b4f80a 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -2,7 +2,7 @@ import { PluginClient } from '@remixproject/plugin' import { createClient } from '@remixproject/plugin-webview' import EventManager from 'events' -import { VERIFIERS, type ChainSettings,Chain, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier, SubmittedContract } from './types' +import { VERIFIERS, type ChainSettings,Chain, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier, SubmittedContract, SubmittedContracts, VerificationReceipt } from './types' import { mergeChainSettingsWithDefaults, validConfiguration } from './utils' import { getVerifier } from './Verifiers' import { CompilerAbstract } from '@remix-project/remix-solidity' @@ -66,17 +66,27 @@ export class ContractVerificationPluginClient extends PluginClient { verifyOnDeploy = async (data: any): Promise => { try { - await this.call('terminal', 'log', { type: 'info', value: 'Verification process started...' }) + await this.call('terminal', 'log', { type: 'log', value: 'Verification process started...' }) const { chainId, currentChain, contractAddress, contractName, compilationResult, constructorArgs, etherscanApiKey } = data - if (!currentChain) throw new Error("Chain data was not provided.") + if (!currentChain) { + await this.call('terminal', 'log', { type: 'error', value: 'Chain data was not provided for verification.' }) + return + } + + const submittedContracts: SubmittedContracts = JSON.parse(window.localStorage.getItem('contract-verification:submitted-contracts') || '{}') + + const filePath = Object.keys(compilationResult.data.contracts).find(path => + compilationResult.data.contracts[path][contractName] + ) + if (!filePath) throw new Error(`Could not find file path for contract ${contractName}`) const submittedContract: SubmittedContract = { id: `${chainId}-${contractAddress}`, address: contractAddress, chainId: chainId, - filePath: Object.keys(compilationResult.data.contracts).find(path => path in compilationResult.source.sources), + filePath: filePath, contractName: contractName, abiEncodedConstructorArgs: constructorArgs, date: new Date().toISOString(), @@ -84,7 +94,6 @@ export class ContractVerificationPluginClient extends PluginClient { } const compilerAbstract: CompilerAbstract = compilationResult - const userSettings = this.getUserSettingsFromLocalStorage() const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings) @@ -100,7 +109,7 @@ export class ContractVerificationPluginClient extends PluginClient { await this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings) } - if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.includes('etherscan'))) { + if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('etherscan'))) { if (etherscanApiKey) { if (!chainSettings.verifiers.Etherscan) chainSettings.verifiers.Etherscan = {} chainSettings.verifiers.Etherscan.apiKey = etherscanApiKey @@ -110,6 +119,10 @@ export class ContractVerificationPluginClient extends PluginClient { } } + submittedContracts[submittedContract.id] = submittedContract + + window.localStorage.setItem('contract-verification:submitted-contracts', JSON.stringify(submittedContracts)) + this.internalEvents.emit('submissionUpdated') } catch (error) { await this.call('terminal', 'log', { type: 'error', value: `An unexpected error occurred during verification: ${error.message}` }) } @@ -117,33 +130,58 @@ export class ContractVerificationPluginClient extends PluginClient { private _verifyWithProvider = async ( providerName: VerifierIdentifier, - submittedContract: SubmittedContract, + submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract, chainId: string, chainSettings: ChainSettings ): Promise => { + let receipt: VerificationReceipt + const verifierSettings = chainSettings.verifiers[providerName] + const verifier = getVerifier(providerName, verifierSettings) + try { if (validConfiguration(chainSettings, providerName)) { await this.call('terminal', 'log', { type: 'log', value: `Verifying with ${providerName}...` }) - const verifierSettings = chainSettings.verifiers[providerName] - const verifier = getVerifier(providerName, verifierSettings) - if (verifier && typeof verifier.verify === 'function') { const result = await verifier.verify(submittedContract, compilerAbstract) + receipt = { + receiptId: result.receiptId || undefined, + verifierInfo: { name: providerName, apiUrl: verifier.apiUrl }, + status: result.status, + message: result.message, + lookupUrl: result.lookupUrl, + contractId: submittedContract.id, + isProxyReceipt: false, + failedChecks: 0 + } + let successMessage = `${providerName} verification successful.` await this.call('terminal', 'log', { type: 'info', value: successMessage }) if (result.lookupUrl) { - await this.call('terminal', 'log', { type: 'info', value: result.lookupUrl }) + const textMessage = `${result.lookupUrl}` + await this.call('terminal', 'log', { type: 'info', value: textMessage }) } } else { - await this.call('terminal', 'log', { type: 'warn', value: `${providerName} verifier is not properly configured or does not support direct verification.` }) + throw new Error(`${providerName} verifier is not properly configured or does not support direct verification.`) } } } catch (e) { + receipt = { + verifierInfo: { name: providerName, apiUrl: verifier?.apiUrl || 'N/A' }, + status: 'failed', + message: e.message, + contractId: submittedContract.id, + isProxyReceipt: false, + failedChecks: 0 + } await this.call('terminal', 'log', { type: 'error', value: `${providerName} verification failed: ${e.message}` }) + } finally { + if (receipt) { + submittedContract.receipts.push(receipt) + } } } diff --git a/apps/contract-verification/src/app/app.tsx b/apps/contract-verification/src/app/app.tsx index 48e95697c70..5cdd55e4227 100644 --- a/apps/contract-verification/src/app/app.tsx +++ b/apps/contract-verification/src/app/app.tsx @@ -68,9 +68,18 @@ const App = () => { .then((data) => setChains(data)) .catch((error) => console.error('Failed to fetch chains.json:', error)) + const submissionUpdatedListener = () => { + const latestSubmissions = window.localStorage.getItem('contract-verification:submitted-contracts') + if (latestSubmissions) { + setSubmittedContracts(JSON.parse(latestSubmissions)) + } + } + plugin.internalEvents.on('submissionUpdated', submissionUpdatedListener) + // Clean up on unmount return () => { plugin.off('compilerArtefacts' as any, 'compilationSaved') + plugin.internalEvents.removeListener('submissionUpdated', submissionUpdatedListener) } }, []) @@ -167,4 +176,4 @@ const App = () => { ) } -export default App +export default App \ No newline at end of file From f70f839d18d128465e2c7b2718898eab1b619714 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 25 Sep 2025 10:05:44 +0900 Subject: [PATCH 33/38] removed #pr --- apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts b/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts index 15cef7b83c1..acb134768ba 100644 --- a/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts +++ b/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts @@ -12,7 +12,7 @@ module.exports = { init(browser, done, null) }, - 'Should show warning for unsupported network when deploying with "Verify" on Remix VM #group1 #pr': function (browser: NightwatchBrowser) { + 'Should show warning for unsupported network when deploying with "Verify" on Remix VM #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="remixIdeSidePanel"]') .clickLaunchIcon('filePanel') From 7c7e2b3262bc353fb736b27e491733952bd853d5 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 25 Sep 2025 10:40:24 +0900 Subject: [PATCH 34/38] lint --- .../app/ContractVerificationPluginClient.ts | 58 +++++++++---------- .../run-tab/src/lib/actions/deploy.ts | 6 +- .../src/lib/components/contractDropdownUI.tsx | 2 +- .../lib/components/verificationSettingsUI.tsx | 6 +- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index ecee2b4f80a..ea4985eebdb 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -69,7 +69,7 @@ export class ContractVerificationPluginClient extends PluginClient { await this.call('terminal', 'log', { type: 'log', value: 'Verification process started...' }) const { chainId, currentChain, contractAddress, contractName, compilationResult, constructorArgs, etherscanApiKey } = data - + if (!currentChain) { await this.call('terminal', 'log', { type: 'error', value: 'Chain data was not provided for verification.' }) return @@ -77,13 +77,13 @@ export class ContractVerificationPluginClient extends PluginClient { const submittedContracts: SubmittedContracts = JSON.parse(window.localStorage.getItem('contract-verification:submitted-contracts') || '{}') - const filePath = Object.keys(compilationResult.data.contracts).find(path => + const filePath = Object.keys(compilationResult.data.contracts).find(path => compilationResult.data.contracts[path][contractName] ) if (!filePath) throw new Error(`Could not find file path for contract ${contractName}`) const submittedContract: SubmittedContract = { - id: `${chainId}-${contractAddress}`, + id: `${chainId}-${contractAddress}`, address: contractAddress, chainId: chainId, filePath: filePath, @@ -118,7 +118,7 @@ export class ContractVerificationPluginClient extends PluginClient { await this.call('terminal', 'log', { type: 'warn', value: 'Etherscan verification skipped: API key not found in global Settings.' }) } } - + submittedContracts[submittedContract.id] = submittedContract window.localStorage.setItem('contract-verification:submitted-contracts', JSON.stringify(submittedContracts)) @@ -129,10 +129,10 @@ export class ContractVerificationPluginClient extends PluginClient { } private _verifyWithProvider = async ( - providerName: VerifierIdentifier, + providerName: VerifierIdentifier, submittedContract: SubmittedContract, - compilerAbstract: CompilerAbstract, - chainId: string, + compilerAbstract: CompilerAbstract, + chainId: string, chainSettings: ChainSettings ): Promise => { let receipt: VerificationReceipt @@ -142,30 +142,30 @@ export class ContractVerificationPluginClient extends PluginClient { try { if (validConfiguration(chainSettings, providerName)) { await this.call('terminal', 'log', { type: 'log', value: `Verifying with ${providerName}...` }) - + if (verifier && typeof verifier.verify === 'function') { - const result = await verifier.verify(submittedContract, compilerAbstract) - - receipt = { - receiptId: result.receiptId || undefined, - verifierInfo: { name: providerName, apiUrl: verifier.apiUrl }, - status: result.status, - message: result.message, - lookupUrl: result.lookupUrl, - contractId: submittedContract.id, - isProxyReceipt: false, - failedChecks: 0 - } - - let successMessage = `${providerName} verification successful.` - await this.call('terminal', 'log', { type: 'info', value: successMessage }) - - if (result.lookupUrl) { - const textMessage = `${result.lookupUrl}` - await this.call('terminal', 'log', { type: 'info', value: textMessage }) - } + const result = await verifier.verify(submittedContract, compilerAbstract) + + receipt = { + receiptId: result.receiptId || undefined, + verifierInfo: { name: providerName, apiUrl: verifier.apiUrl }, + status: result.status, + message: result.message, + lookupUrl: result.lookupUrl, + contractId: submittedContract.id, + isProxyReceipt: false, + failedChecks: 0 + } + + const successMessage = `${providerName} verification successful.` + await this.call('terminal', 'log', { type: 'info', value: successMessage }) + + if (result.lookupUrl) { + const textMessage = `${result.lookupUrl}` + await this.call('terminal', 'log', { type: 'info', value: textMessage }) + } } else { - throw new Error(`${providerName} verifier is not properly configured or does not support direct verification.`) + throw new Error(`${providerName} verifier is not properly configured or does not support direct verification.`) } } } catch (e) { diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index f8bc44a38ce..6f8d5eddc57 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -176,14 +176,14 @@ export const createInstance = async ( const log = logBuilder(error) return terminalLogger(plugin, log) } - + addInstance(dispatch, { contractData: contractObject, address, name: contractObject.name }) const data = await plugin.compilersArtefacts.getCompilerAbstract(contractObject.contract.file) plugin.compilersArtefacts.addResolvedContract(addressToString(address), data) if (isVerifyChecked) { _paq.push(['trackEvent', 'udapp', 'DeployAndVerify', plugin.REACT_API.networkName]) - + try { await publishToStorage('ipfs', selectedContract) } catch (e) { @@ -222,7 +222,7 @@ export const createInstance = async ( constructorArgs: args, etherscanApiKey: etherscanApiKey } - + setTimeout(async () => { await plugin.call('contract-verification', 'verifyOnDeploy', verificationData) }, 1000) diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx index d82fdd78d67..de1311d9d2d 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -42,7 +42,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) { const atAddressValue = useRef(null) const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions } = props.contracts const [isVerifyChecked, setVerifyChecked] = useState(false) - + useEffect(() => { enableContractNames(Object.keys(props.contracts.contractList).length > 0) }, [Object.keys(props.contracts.contractList).length]) diff --git a/libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx b/libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx index f977a1c6fb3..988ef4a53d5 100644 --- a/libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/verificationSettingsUI.tsx @@ -28,9 +28,9 @@ export function VerificationSettingsUI(props: VerificationSettingsProps) { tooltipId="remixVerifyContractTooltip" tooltipText={ - } From 6535bf886e1055a3a65d1e2b271eda186e028002 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 25 Sep 2025 11:42:23 +0900 Subject: [PATCH 35/38] set timeout for etherscan --- .../src/app/ContractVerificationPluginClient.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index ea4985eebdb..110dfd44be5 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -141,6 +141,11 @@ export class ContractVerificationPluginClient extends PluginClient { try { if (validConfiguration(chainSettings, providerName)) { + + if (providerName === 'Etherscan') { + await new Promise(resolve => setTimeout(resolve, 3000)) + } + await this.call('terminal', 'log', { type: 'log', value: `Verifying with ${providerName}...` }) if (verifier && typeof verifier.verify === 'function') { From 838dca0f0c41dac40cc73c08b330cdb06ba94814 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 25 Sep 2025 13:37:56 +0900 Subject: [PATCH 36/38] fix(settings): Sync global Etherscan API key to local plugin settings for polling --- .../app/ContractVerificationPluginClient.ts | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index 110dfd44be5..53865684c3d 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -75,6 +75,29 @@ export class ContractVerificationPluginClient extends PluginClient { return } + const userSettings = this.getUserSettingsFromLocalStorage() + + if (etherscanApiKey) { + if (!userSettings.chains[chainId]) { + userSettings.chains[chainId] = { verifiers: {} } + } + + if (!userSettings.chains[chainId].verifiers.Etherscan) { + userSettings.chains[chainId].verifiers.Etherscan = {} + } + userSettings.chains[chainId].verifiers.Etherscan.apiKey = etherscanApiKey + + if (!userSettings.chains[chainId].verifiers.Routescan) { + userSettings.chains[chainId].verifiers.Routescan = {} + } + if (!userSettings.chains[chainId].verifiers.Routescan.apiKey){ + userSettings.chains[chainId].verifiers.Routescan.apiKey = "placeholder" + } + + window.localStorage.setItem("contract-verification:settings", JSON.stringify(userSettings)) + + } + const submittedContracts: SubmittedContracts = JSON.parse(window.localStorage.getItem('contract-verification:submitted-contracts') || '{}') const filePath = Object.keys(compilationResult.data.contracts).find(path => @@ -94,7 +117,6 @@ export class ContractVerificationPluginClient extends PluginClient { } const compilerAbstract: CompilerAbstract = compilationResult - const userSettings = this.getUserSettingsFromLocalStorage() const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings) if (validConfiguration(chainSettings, 'Sourcify')) { @@ -142,12 +164,12 @@ export class ContractVerificationPluginClient extends PluginClient { try { if (validConfiguration(chainSettings, providerName)) { - if (providerName === 'Etherscan') { - await new Promise(resolve => setTimeout(resolve, 3000)) - } - await this.call('terminal', 'log', { type: 'log', value: `Verifying with ${providerName}...` }) + if (providerName === 'Etherscan' || providerName === 'Routescan' || providerName === 'Blockscout') { + await new Promise(resolve => setTimeout(resolve, 10000)) + } + if (verifier && typeof verifier.verify === 'function') { const result = await verifier.verify(submittedContract, compilerAbstract) From 74d313e244cc75c606e272244a5983e42d3851d3 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Mon, 29 Sep 2025 13:21:21 +0900 Subject: [PATCH 37/38] feat: set default value for verification checkbox and hide it on unsupported networks --- .../run-tab/src/lib/actions/deploy.ts | 8 ---- .../src/lib/components/contractDropdownUI.tsx | 47 ++++++++++++++++--- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index 6f8d5eddc57..8625255cd45 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -184,14 +184,6 @@ export const createInstance = async ( if (isVerifyChecked) { _paq.push(['trackEvent', 'udapp', 'DeployAndVerify', plugin.REACT_API.networkName]) - try { - await publishToStorage('ipfs', selectedContract) - } catch (e) { - const errorMsg = `Could not publish contract metadata to IPFS. Continuing with verification... (Error: ${e.message})` - const errorLog = logBuilder(errorMsg) - terminalLogger(plugin, errorLog) - } - try { const status = plugin.blockchain.getCurrentNetworkStatus() if (status.error || !status.network) { diff --git a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx index de1311d9d2d..adbf75fb2f0 100644 --- a/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx @@ -7,6 +7,7 @@ import * as ethJSUtil from '@ethereumjs/util' import { ContractGUI } from './contractGUI' import { CustomTooltip, deployWithProxyMsg, upgradeWithProxyMsg } from '@remix-ui/helper' import { VerificationSettingsUI } from './verificationSettingsUI' + const _paq = (window._paq = window._paq || []) export function ContractDropdownUI(props: ContractDropdownProps) { @@ -41,7 +42,22 @@ export function ContractDropdownUI(props: ContractDropdownProps) { const contractsRef = useRef(null) const atAddressValue = useRef(null) const { contractList, loadType, currentFile, compilationSource, currentContract, compilationCount, deployOptions } = props.contracts - const [isVerifyChecked, setVerifyChecked] = useState(false) + const [isVerifyChecked, setVerifyChecked] = useState(() => { + const saved = window.localStorage.getItem('deploy-verify-contract-checked') + return saved !== null ? JSON.parse(saved) : true + }) + + const [isNetworkSupported, setNetworkSupported] = useState(false) + + useEffect(() => { + const checkSupport = async () => { + if (props.plugin) { + const supportedChain = await getSupportedChain(props.plugin) + setNetworkSupported(!!supportedChain) + } + } + checkSupport() + }, [props.networkName]) useEffect(() => { enableContractNames(Object.keys(props.contracts.contractList).length > 0) @@ -281,7 +297,7 @@ export function ContractDropdownUI(props: ContractDropdownProps) { const handleVerifyCheckedChange = (isChecked: boolean) => { setVerifyChecked(isChecked) - window.localStorage.setItem('deploy-verify-contract-checked', isChecked.toString()) + window.localStorage.setItem('deploy-verify-contract-checked', JSON.stringify(isChecked)) } const updateCompilerName = () => { @@ -315,6 +331,23 @@ export function ContractDropdownUI(props: ContractDropdownProps) { return props.isValidProxyUpgrade(proxyAddress, loadedContractData.contractName || loadedContractData.name, loadedContractData.compiler.source, loadedContractData.compiler.data, solcVersion) } + const getSupportedChain = async (plugin: any): Promise => { + try { + const response = await fetch('https://chainid.network/chains.json') + if (!response.ok) return null + const allChains = await response.json() + + const status = plugin.blockchain.getCurrentNetworkStatus() + if (status.error || !status.network) return null + + const currentChainId = parseInt(status.network.id) + return allChains.find(chain => chain.chainId === currentChainId) || null + } catch (e) { + console.error(e) + return null + } + } + const checkSumWarning = () => { return ( @@ -489,10 +522,12 @@ export function ContractDropdownUI(props: ContractDropdownProps) { plugin={props.plugin} runTabState={props.runTabState} /> - + {isNetworkSupported && ( + + )}
)}
From 7a5b8ccc31b147a2864491cb20b56b7d43d79290 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Tue, 30 Sep 2025 13:48:28 +0900 Subject: [PATCH 38/38] update e2e test --- apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts b/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts index acb134768ba..96b0ac7fd29 100644 --- a/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts +++ b/apps/remix-ide-e2e/src/tests/deploy_vefiry.test.ts @@ -12,7 +12,7 @@ module.exports = { init(browser, done, null) }, - 'Should show warning for unsupported network when deploying with "Verify" on Remix VM #group1': function (browser: NightwatchBrowser) { + 'Should NOT display the "Verify Contract" checkbox on an unsupported network (Remix VM) #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="remixIdeSidePanel"]') .clickLaunchIcon('filePanel') @@ -20,13 +20,9 @@ module.exports = { .openFile('contracts/1_Storage.sol') .clickLaunchIcon('udapp') .waitForElementVisible('*[data-id="Deploy - transact (not payable)"]') - .waitForElementVisible('#deployAndRunVerifyContract') - .click('#deployAndRunVerifyContract') - .click('*[data-id="Deploy - transact (not payable)"]') - .waitForElementVisible({ - selector: "//*[contains(text(),'is not supported for verification via this plugin')]", - locateStrategy: 'xpath', - timeout: 10000 + .waitForElementNotPresent({ + selector: '#deployAndRunVerifyContract', + timeout: 5000 }) .end() }