diff --git a/apps/remix-ide/src/app/udapp/run-tab.tsx b/apps/remix-ide/src/app/udapp/run-tab.tsx index 0294c005072..8a94b857717 100644 --- a/apps/remix-ide/src/app/udapp/run-tab.tsx +++ b/apps/remix-ide/src/app/udapp/run-tab.tsx @@ -56,7 +56,9 @@ const profile = { 'resolveContractAndAddInstance', 'showPluginDetails', 'getRunTabAPI', - 'getDeployedContracts' + 'getDeployedContracts', + 'getAllDeployedInstances', + 'setAccount' ] } @@ -151,6 +153,14 @@ export class RunTab extends ViewPlugin { } } + setAccount(address: string) { + this.emit('setAccountReducer', address) + } + + getAllDeployedInstances() { + return this.REACT_API.instances?.instanceList + } + clearAllInstances() { this.emit('clearAllInstancesReducer') this.transactionHistory.clear() diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index 53e89ba9305..e9812f20c89 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -25,7 +25,7 @@ const profile = { name: 'blockchain', displayName: 'Blockchain', description: 'Blockchain - Logic', - methods: ['dumpState', 'getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'isSmartAccount', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentProvider', 'getCurrentNetworkStatus', 'getCurrentNetworkCurrency', 'getAllProviders', 'getPinnedProviders', 'changeExecutionContext', 'getProviderObject', 'runTx', 'getBalanceInEther', 'getCurrentProvider'], + methods: ['dumpState', 'getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'isSmartAccount', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentProvider', 'getCurrentNetworkStatus', 'getCurrentNetworkCurrency', 'getAllProviders', 'getPinnedProviders', 'changeExecutionContext', 'getProviderObject', 'runTx', 'getBalanceInEther', 'getCurrentProvider', 'deployContractAndLibraries', 'runOrCallContractMethod'], version: packageJson.version } @@ -303,7 +303,9 @@ export class Blockchain extends Plugin { args, (error, data) => { if (error) { - return statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error.error ? error.error : error}`) + statusCb(`creation of ${selectedContract.name} errored: ${error.message ? error.message : error.error ? error.error : error}`) + finalCb(error) + return } statusCb(`creation of ${selectedContract.name} pending...`) @@ -546,7 +548,7 @@ export class Blockchain extends Plugin { if (txResult.receipt.status === false || txResult.receipt.status === '0x0' || txResult.receipt.status === 0) { return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`) } - finalCb(null, selectedContract, address) + finalCb(null, selectedContract, address, txResult) }) } @@ -607,7 +609,7 @@ export class Blockchain extends Plugin { } changeExecutionContext(context, confirmCb, infoCb, cb) { - if (this.currentRequest && this.currentRequest.from && !this.currentRequest.from.startsWith('injected')) { + if (this.currentRequest && this.currentRequest.from && !this.currentRequest.from.startsWith('injected') && this.currentRequest.from !== 'remixAI') { // only injected provider can update the provider. return } @@ -668,7 +670,7 @@ export class Blockchain extends Plugin { return txlistener } - runOrCallContractMethod(contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) { + runOrCallContractMethod(contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb, finalCb) { // contractsDetails is used to resolve libraries txFormat.buildData( contractName, @@ -701,6 +703,7 @@ export class Blockchain extends Plugin { if (lookupOnly) { outputCb(returnValue) } + if (finalCb) finalCb(error, {txResult, address: _address, returnValue}) }) }, (msg) => { diff --git a/libs/remix-ai-core/src/remix-mcp-server/RemixMCPServer.ts b/libs/remix-ai-core/src/remix-mcp-server/RemixMCPServer.ts index b34c6ebc904..1fae2a5bca0 100644 --- a/libs/remix-ai-core/src/remix-mcp-server/RemixMCPServer.ts +++ b/libs/remix-ai-core/src/remix-mcp-server/RemixMCPServer.ts @@ -29,6 +29,7 @@ import { createCompilationTools } from './handlers/CompilationHandler'; import { createFileManagementTools } from './handlers/FileManagementHandler'; import { createDeploymentTools } from './handlers/DeploymentHandler'; import { createDebuggingTools } from './handlers/DebuggingHandler'; +import { createCodeAnalysisTools } from './handlers/CodeAnalysisHandler'; // Import resource providers import { ProjectResourceProvider } from './providers/ProjectResourceProvider'; @@ -464,9 +465,14 @@ export class RemixMCPServer extends EventEmitter implements IRemixMCPServer { console.log(`Registered ${deploymentTools.length} deployment tools`, 'info'); // Register debugging tools - // const debuggingTools = createDebuggingTools(); - // this._tools.registerBatch(debuggingTools); - // console.log(`Registered ${debuggingTools.length} debugging tools`, 'info'); + const debuggingTools = createDebuggingTools(); + this._tools.registerBatch(debuggingTools); + console.log(`Registered ${debuggingTools.length} debugging tools`, 'info'); + + // Register debugging tools + const codeAnalysisTools = createCodeAnalysisTools(); + this._tools.registerBatch(codeAnalysisTools); + console.log(`Registered ${codeAnalysisTools.length} code analysis tools`, 'info'); const totalTools = this._tools.list().length; console.log(`Total tools registered: ${totalTools}`, 'info'); diff --git a/libs/remix-ai-core/src/remix-mcp-server/handlers/CodeAnalysisHandler.ts b/libs/remix-ai-core/src/remix-mcp-server/handlers/CodeAnalysisHandler.ts new file mode 100644 index 00000000000..43297fe267b --- /dev/null +++ b/libs/remix-ai-core/src/remix-mcp-server/handlers/CodeAnalysisHandler.ts @@ -0,0 +1,126 @@ +/** + * Code Analysis Tool Handlers for Remix MCP Server + */ + +import { IMCPToolResult } from '../../types/mcp'; +import { BaseToolHandler } from '../registry/RemixToolRegistry'; +import { + ToolCategory, + RemixToolDefinition +} from '../types/mcpTools'; +import { Plugin } from '@remixproject/engine'; +import { performSolidityScan } from '@remix-project/core-plugin'; + +/** + * Solidity Scan Tool Handler + * Analyzes Solidity code for security vulnerabilities and code quality issues + */ +export class SolidityScanHandler extends BaseToolHandler { + name = 'solidity_scan'; + description = 'Scan Solidity smart contracts for security vulnerabilities and code quality issues using SolidityScan API'; + inputSchema = { + type: 'object', + properties: { + filePath: { + type: 'string', + description: 'Path to the Solidity file to scan (relative to workspace root)' + } + }, + required: ['filePath'] + }; + + getPermissions(): string[] { + return ['analysis:scan', 'file:read']; + } + + validate(args: { filePath: string }): boolean | string { + const required = this.validateRequired(args, ['filePath']); + if (required !== true) return required; + + const types = this.validateTypes(args, { + filePath: 'string' + }); + if (types !== true) return types; + + if (!args.filePath.endsWith('.sol')) { + return 'File must be a Solidity file (.sol)'; + } + + return true; + } + + async execute(args: { filePath: string }, plugin: Plugin): Promise { + try { + // Check if file exists + const workspace = await plugin.call('filePanel', 'getCurrentWorkspace'); + const fileName = `${workspace.name}/${args.filePath}`; + const filePath = `.workspaces/${fileName}`; + + const exists = await plugin.call('fileManager', 'exists', filePath); + if (!exists) { + return this.createErrorResult(`File not found: ${args.filePath}`); + } + + // Use the core scanning function from remix-core-plugin + const scanReport = await performSolidityScan(plugin, args.filePath); + + // Process scan results into structured format + const findings = []; + + for (const template of scanReport.multi_file_scan_details || []) { + if (template.metric_wise_aggregated_findings?.length) { + for (const details of template.metric_wise_aggregated_findings) { + for (const finding of details.findings) { + findings.push({ + metric: details.metric_name, + severity: details.severity || 'unknown', + title: finding.title || details.metric_name, + description: finding.description || details.description, + lineStart: finding.line_nos_start?.[0], + lineEnd: finding.line_nos_end?.[0], + file: template.file_name, + recommendation: finding.recommendation + }); + } + } + } + } + + const result = { + success: true, + fileName, + scanCompletedAt: new Date().toISOString(), + totalFindings: findings.length, + findings, + summary: { + critical: findings.filter(f => f.severity === 'critical').length, + high: findings.filter(f => f.severity === 'high').length, + medium: findings.filter(f => f.severity === 'medium').length, + low: findings.filter(f => f.severity === 'low').length, + informational: findings.filter(f => f.severity === 'informational').length + } + }; + + return this.createSuccessResult(result); + + } catch (error) { + return this.createErrorResult(`Scan failed: ${error.message}`); + } + } +} + +/** + * Create code analysis tool definitions + */ +export function createCodeAnalysisTools(): RemixToolDefinition[] { + return [ + { + name: 'solidity_scan', + description: 'Scan Solidity smart contracts for security vulnerabilities and code quality issues using SolidityScan API', + inputSchema: new SolidityScanHandler().inputSchema, + category: ToolCategory.ANALYSIS, + permissions: ['analysis:scan', 'file:read'], + handler: new SolidityScanHandler() + } + ]; +} diff --git a/libs/remix-ai-core/src/remix-mcp-server/handlers/DebuggingHandler.ts b/libs/remix-ai-core/src/remix-mcp-server/handlers/DebuggingHandler.ts index 486385bc25a..47c419b516a 100644 --- a/libs/remix-ai-core/src/remix-mcp-server/handlers/DebuggingHandler.ts +++ b/libs/remix-ai-core/src/remix-mcp-server/handlers/DebuggingHandler.ts @@ -30,27 +30,20 @@ export class StartDebugSessionHandler extends BaseToolHandler { inputSchema = { type: 'object', properties: { - contractAddress: { - type: 'string', - description: 'Contract address to debug', - pattern: '^0x[a-fA-F0-9]{40}$' - }, transactionHash: { type: 'string', description: 'Transaction hash to debug (optional)', pattern: '^0x[a-fA-F0-9]{64}$' }, - sourceFile: { - type: 'string', - description: 'Source file path to debug' - }, + /* network: { type: 'string', description: 'Network to debug on', default: 'local' } + */ }, - required: ['contractAddress'] + required: ['transactionHash'] }; getPermissions(): string[] { @@ -58,21 +51,14 @@ export class StartDebugSessionHandler extends BaseToolHandler { } validate(args: DebugSessionArgs): boolean | string { - const required = this.validateRequired(args, ['contractAddress']); + const required = this.validateRequired(args, ['transactionHash']); if (required !== true) return required; const types = this.validateTypes(args, { - contractAddress: 'string', transactionHash: 'string', - sourceFile: 'string', - network: 'string' }); if (types !== true) return types; - if (!args.contractAddress.match(/^0x[a-fA-F0-9]{40}$/)) { - return 'Invalid contract address format'; - } - if (args.transactionHash && !args.transactionHash.match(/^0x[a-fA-F0-9]{64}$/)) { return 'Invalid transaction hash format'; } @@ -82,21 +68,15 @@ export class StartDebugSessionHandler extends BaseToolHandler { async execute(args: DebugSessionArgs, plugin: Plugin): Promise { try { - // TODO: Integrate with Remix debugger plugin - const sessionId = 'debug_' + Date.now(); - + await plugin.call('debugger', 'debug', args.transactionHash) // Mock debug session creation const result: DebugSessionResult = { success: true, - sessionId, - contractAddress: args.contractAddress, - network: args.network || 'local', transactionHash: args.transactionHash, - sourceFile: args.sourceFile, status: 'started', createdAt: new Date().toISOString() }; - + plugin.call('menuicons', 'select', 'debugger') return this.createSuccessResult(result); } catch (error) { diff --git a/libs/remix-ai-core/src/remix-mcp-server/handlers/DeploymentHandler.ts b/libs/remix-ai-core/src/remix-mcp-server/handlers/DeploymentHandler.ts index afd077b6d37..25de0520252 100644 --- a/libs/remix-ai-core/src/remix-mcp-server/handlers/DeploymentHandler.ts +++ b/libs/remix-ai-core/src/remix-mcp-server/handlers/DeploymentHandler.ts @@ -12,9 +12,16 @@ import { SendTransactionArgs, DeploymentResult, AccountInfo, - ContractInteractionResult + ContractInteractionResult, + RunScriptArgs, + RunScriptResult } from '../types/mcpTools'; import { Plugin } from '@remixproject/engine'; +import { getContractData } from '@remix-project/core-plugin' +import type { TxResult } from '@remix-project/remix-lib'; +import type { TransactionReceipt } from 'web3' +import { BrowserProvider } from "ethers" +import web3, { Web3 } from 'web3' /** * Deploy Contract Tool Handler @@ -54,9 +61,13 @@ export class DeployContractHandler extends BaseToolHandler { account: { type: 'string', description: 'Account to deploy from (address or index)' + }, + file: { + type: 'string', + description: 'The file containing the contract to deploy' } }, - required: ['contractName'] + required: ['contractName', 'file'] }; getPermissions(): string[] { @@ -86,70 +97,60 @@ export class DeployContractHandler extends BaseToolHandler { async execute(args: DeployContractArgs, plugin: Plugin): Promise { try { // Get compilation result to find contract - // TODO: Get actual compilation result - const contracts = {}; // await plugin.solidity.getCompilationResult(); - - if (!contracts || Object.keys(contracts).length === 0) { - return this.createErrorResult('No compiled contracts found. Please compile first.'); - } - - // Find the contract to deploy - const contractKey = Object.keys(contracts).find(key => - key.includes(args.contractName) - ); - - if (!contractKey) { - return this.createErrorResult(`Contract '${args.contractName}' not found in compilation result`); + const compilerAbstract = await plugin.call('compilerArtefacts', 'getCompilerAbstract', args.file) as any; + const data = getContractData(args.contractName, compilerAbstract) + if (!data) { + return this.createErrorResult(`Could not retrieve contract data for '${args.contractName}'`); } - // Get current account - const accounts = await this.getAccounts(plugin); - const deployAccount = args.account || accounts[0]; - - if (!deployAccount) { - return this.createErrorResult('No account available for deployment'); + let txReturn + try { + txReturn = await new Promise(async (resolve, reject) => { + const callbacks = { continueCb: (error, continueTxExecution, cancelCb) => { + continueTxExecution() + }, promptCb: () => {}, statusCb: (error) => { + console.log(error) + }, finalCb: (error, contractObject, address: string, txResult: TxResult) => { + if (error) return reject(error) + resolve({contractObject, address, txResult}) + }} + const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + continueTxExecution(null) + } + const compilerContracts = await plugin.call('compilerArtefacts', 'getLastCompilationResult') + plugin.call('blockchain', 'deployContractAndLibraries', + data, + args.constructorArgs ? args.constructorArgs : [], + null, + compilerContracts.getData().contracts, + callbacks, + confirmationCb + ) + }) + } catch (e) { + return this.createErrorResult(`Deployment error: ${e.message || e}`); } - - // Prepare deployment transaction - const deploymentData = { - contractName: args.contractName, - account: deployAccount, - constructorArgs: args.constructorArgs || [], - gasLimit: args.gasLimit, - gasPrice: args.gasPrice, - value: args.value || '0' - }; - - // TODO: Execute actual deployment via Remix Run Tab API - const mockResult: DeploymentResult = { - success: false, - contractAddress: undefined, - transactionHash: '0x' + Math.random().toString(16).substr(2, 64), - gasUsed: args.gasLimit || 1000000, + + + const receipt = (txReturn.txResult.receipt as TransactionReceipt) + const result: DeploymentResult = { + transactionHash: web3.utils.bytesToHex(receipt.transactionHash), + gasUsed: web3.utils.toNumber(receipt.gasUsed), effectiveGasPrice: args.gasPrice || '20000000000', - blockNumber: Math.floor(Math.random() * 1000000), - logs: [] + blockNumber: web3.utils.toNumber(receipt.blockNumber), + logs: receipt.logs, + contractAddress: receipt.contractAddress, + success: receipt.status === BigInt(1) ? true : false }; - // Mock implementation - in real implementation, use Remix deployment API - mockResult.success = true; - mockResult.contractAddress = '0x' + Math.random().toString(16).substr(2, 40); + plugin.call('udapp', 'addInstance', result.contractAddress, data.abi, args.contractName, data) - return this.createSuccessResult(mockResult); + return this.createSuccessResult(result); } catch (error) { return this.createErrorResult(`Deployment failed: ${error.message}`); } } - - private async getAccounts(plugin: Plugin): Promise { - try { - // TODO: Get accounts from Remix API - return ['0x' + Math.random().toString(16).substr(2, 40)]; // Mock account - } catch (error) { - return []; - } - } } /** @@ -161,6 +162,11 @@ export class CallContractHandler extends BaseToolHandler { inputSchema = { type: 'object', properties: { + contractName: { + type: 'string', + description: 'Contract name', + pattern: '^0x[a-fA-F0-9]{40}$' + }, address: { type: 'string', description: 'Contract address', @@ -204,7 +210,7 @@ export class CallContractHandler extends BaseToolHandler { description: 'Account to call from' } }, - required: ['address', 'abi', 'methodName'] + required: ['address', 'abi', 'methodName', 'contractName'] }; getPermissions(): string[] { @@ -212,7 +218,7 @@ export class CallContractHandler extends BaseToolHandler { } validate(args: CallContractArgs): boolean | string { - const required = this.validateRequired(args, ['address', 'abi', 'methodName']); + const required = this.validateRequired(args, ['address', 'abi', 'methodName', 'contractName']); if (required !== true) return required; const types = this.validateTypes(args, { @@ -229,8 +235,16 @@ export class CallContractHandler extends BaseToolHandler { return 'Invalid contract address format'; } + if (!Array.isArray(args.abi)) { - return 'ABI must be an array'; + try { + args.abi = JSON.parse(args.abi as any) + if (!Array.isArray(args.abi)) { + return 'ABI must be an array' + } + } catch (e) { + return 'ABI must be an array' + } } return true; @@ -238,55 +252,113 @@ export class CallContractHandler extends BaseToolHandler { async execute(args: CallContractArgs, plugin: Plugin): Promise { try { - // Find the method in ABI - const method = args.abi.find((item: any) => - item.name === args.methodName && item.type === 'function' - ); - - if (!method) { - return this.createErrorResult(`Method '${args.methodName}' not found in ABI`); + const funcABI = args.abi.find((item: any) => item.name === args.methodName && item.type === 'function') + const isView = funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure'; + let txReturn + try { + txReturn = await new Promise(async (resolve, reject) => { + const params = funcABI.type !== 'fallback' ? args.args.join(',') : '' + plugin.call('blockchain', 'runOrCallContractMethod', + args.contractName, + args.abi, + funcABI, + undefined, + args.args ? args.args : [], + args.address, + params, + isView, + (msg) => { + // logMsg + console.log(msg) + }, + (msg) => { + // logCallback + console.log(msg) + }, + (returnValue) => { + // outputCb + }, + (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + // confirmationCb + continueTxExecution(null) + }, + (error, continueTxExecution, cancelCb) => { + if (error) reject(error) + // continueCb + continueTxExecution() + }, + (okCb, cancelCb) => { + // promptCb + }, + (error, {txResult, address, returnValue}) => { + if (error) return reject(error) + resolve({txResult, address, returnValue}) + }, + ) + }) + } catch (e) { + return this.createErrorResult(`Deployment error: ${e.message}`); } - // Get accounts - const accounts = await this.getAccounts(plugin); - const callAccount = args.account || accounts[0]; - - if (!callAccount) { - return this.createErrorResult('No account available for contract call'); - } - - // Determine if this is a view function or transaction - const isView = method.stateMutability === 'view' || method.stateMutability === 'pure'; - // TODO: Execute contract call via Remix Run Tab API - const mockResult: ContractInteractionResult = { - success: true, - result: isView ? 'mock_view_result' : undefined, - transactionHash: isView ? undefined : '0x' + Math.random().toString(16).substr(2, 64), - gasUsed: isView ? 0 : (args.gasLimit || 100000), - logs: [] + const receipt = (txReturn.txResult.receipt as TransactionReceipt) + const result: ContractInteractionResult = { + result: txReturn.returnValue, + transactionHash: isView ? undefined : web3.utils.bytesToHex(receipt.transactionHash), + gasUsed: web3.utils.toNumber(receipt.gasUsed), + logs: receipt.logs, + success: receipt.status === BigInt(1) ? true : false }; - if (isView) { - mockResult.result = `View function result for ${args.methodName}`; - } else { - mockResult.transactionHash = '0x' + Math.random().toString(16).substr(2, 64); - mockResult.gasUsed = args.gasLimit || 100000; - } - - return this.createSuccessResult(mockResult); + return this.createSuccessResult(result); } catch (error) { return this.createErrorResult(`Contract call failed: ${error.message}`); } } +} + +/** + * Run Script + */ +export class RunScriptHandler extends BaseToolHandler { + name = 'send_transaction'; + description = 'Run a script in the current environment'; + inputSchema = { + type: 'object', + properties: { + file: { + type: 'string', + description: 'path to the file', + pattern: '^0x[a-fA-F0-9]{40}$' + } + }, + required: ['file'] + }; + + getPermissions(): string[] { + return ['transaction:send']; + } - private async getAccounts(plugin: Plugin): Promise { + validate(args: RunScriptArgs): boolean | string { + const required = this.validateRequired(args, ['file']); + if (required !== true) return required; + + return true; + } + + async execute(args: RunScriptArgs, plugin: Plugin): Promise { try { - // TODO: Get accounts from Remix API - return ['0x' + Math.random().toString(16).substr(2, 40)]; // Mock account + const content = await plugin.call('fileManager', 'readFile', args.file) + await plugin.call('scriptRunnerBridge', 'execute', content, args.file) + + const result: RunScriptResult = {} + + return this.createSuccessResult(result); + } catch (error) { - return []; + console.log(error) + return this.createErrorResult(`Run script failed: ${error.message}`); } } } @@ -363,40 +435,44 @@ export class SendTransactionHandler extends BaseToolHandler { async execute(args: SendTransactionArgs, plugin: Plugin): Promise { try { - // Get accounts - const accounts = await this.getAccounts(plugin); - const sendAccount = args.account || accounts[0]; + // Get accounts + const sendAccount = args.account if (!sendAccount) { return this.createErrorResult('No account available for sending transaction'); } + const web3: Web3 = await plugin.call('blockchain', 'web3') + const ethersProvider = new BrowserProvider(web3.currentProvider) + const signer = await ethersProvider.getSigner(); + const tx = await signer.sendTransaction({ + from: args.account, + to: args.to, + value: args.value || '0', + data: args.data, + gasLimit: args.gasLimit, + gasPrice: args.gasPrice + }); + // Wait for the transaction to be mined + const receipt = await tx.wait() // TODO: Send a real transaction via Remix Run Tab API const mockResult = { success: true, - transactionHash: '0x' + Math.random().toString(16).substr(2, 64), - from: sendAccount, + transactionHash: receipt.hash, + from: args.account, to: args.to, value: args.value || '0', - gasUsed: args.gasLimit || 21000, - blockNumber: Math.floor(Math.random() * 1000000) + gasUsed: web3.utils.toNumber(receipt.gasUsed), + blockNumber: receipt.blockNumber }; return this.createSuccessResult(mockResult); } catch (error) { + console.log(error) return this.createErrorResult(`Transaction failed: ${error.message}`); } } - - private async getAccounts(plugin: Plugin): Promise { - try { - // TODO: Get accounts from Remix API - return ['0x' + Math.random().toString(16).substr(2, 40)]; // Mock account - } catch (error) { - return []; - } - } } /** @@ -421,21 +497,11 @@ export class GetDeployedContractsHandler extends BaseToolHandler { async execute(args: { network?: string }, plugin: Plugin): Promise { try { - // TODO: Get deployed contracts from Remix storage/state - const mockDeployedContracts = [ - { - name: '', - address: '0x' , - network: '', - deployedAt: '', - transactionHash: '0x' - } - ]; - + const deployedContracts = await plugin.call('udapp', 'getAllDeployedInstances') return this.createSuccessResult({ success: true, - contracts: mockDeployedContracts, - count: mockDeployedContracts.length + contracts: deployedContracts, + count: deployedContracts.length }); } catch (error) { @@ -455,7 +521,7 @@ export class SetExecutionEnvironmentHandler extends BaseToolHandler { properties: { environment: { type: 'string', - enum: ['vm-london', 'vm-berlin', 'injected', 'web3'], + enum: ['vm-prague', 'vm-cancun', 'vm-shanghai', 'vm-paris', 'vm-london', 'vm-berlin', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-custom-fork', 'walletconnect', 'basic-http-provider', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'injected-Rabby Wallet', 'injected-MetaMask', 'injected-metamask-optimism', 'injected-metamask-arbitrum', 'injected-metamask-sepolia', 'injected-metamask-ephemery', 'injected-metamask-gnosis', 'injected-metamask-chiado', 'injected-metamask-linea'], description: 'Execution environment' }, networkUrl: { @@ -471,26 +537,23 @@ export class SetExecutionEnvironmentHandler extends BaseToolHandler { } validate(args: { environment: string; networkUrl?: string }): boolean | string { - const required = this.validateRequired(args, ['environment']); - if (required !== true) return required; - - const validEnvironments = ['vm-london', 'vm-berlin', 'injected', 'web3']; - if (!validEnvironments.includes(args.environment)) { - return `Invalid environment. Must be one of: ${validEnvironments.join(', ')}`; - } - + // we validate in the execute method to have access to the list of available providers. return true; } - async execute(args: { environment: string; networkUrl?: string }, plugin: Plugin): Promise { + async execute(args: { environment: string }, plugin: Plugin): Promise { try { - // TODO: Set execution environment via Remix Run Tab API - + const providers = await plugin.call('blockchain', 'getAllProviders') + console.log('available providers', Object.keys(providers)) + const provider = Object.keys(providers).find((p) => p === args.environment) + if (!provider) { + return this.createErrorResult(`Could not find provider for environment '${args.environment}'`); + } + await plugin.call('blockchain', 'changeExecutionContext', { context: args.environment }) return this.createSuccessResult({ success: true, message: `Execution environment set to: ${args.environment}`, environment: args.environment, - networkUrl: args.networkUrl }); } catch (error) { @@ -534,16 +597,14 @@ export class GetAccountBalanceHandler extends BaseToolHandler { async execute(args: { account: string }, plugin: Plugin): Promise { try { - // TODO: Get account balance from current provider - const mockBalance = (Math.random() * 10).toFixed(4); - + const web3 = await plugin.call('blockchain', 'web3') + const balance = await web3.eth.getBalance(args.account) return this.createSuccessResult({ success: true, account: args.account, - balance: mockBalance, + balance: web3.utils.fromWei(balance, 'ether'), unit: 'ETH' - }); - + }) } catch (error) { return this.createErrorResult(`Failed to get account balance: ${error.message}`); } @@ -678,9 +739,10 @@ export class SetSelectedAccountHandler extends BaseToolHandler { try { // Set the selected account through the udapp plugin await plugin.call('udapp' as any, 'setAccount', args.address); + await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait a moment for the change to propagate // Verify the account was set - const runTabApi = await plugin.call('udapp' as any, 'getAccounts'); + const runTabApi = await plugin.call('udapp' as any, 'getRunTabAPI'); const currentSelected = runTabApi?.accounts?.selectedAccount; if (currentSelected !== args.address) { @@ -716,37 +778,25 @@ export class GetCurrentEnvironmentHandler extends BaseToolHandler { async execute(_args: any, plugin: Plugin): Promise { try { // Get environment information - const provider = await plugin.call('blockchain' as any, 'getCurrentProvider'); - const networkName = await plugin.call('blockchain' as any, 'getNetworkName').catch(() => 'unknown'); - const chainId = await plugin.call('blockchain' as any, 'getChainId').catch(() => 'unknown'); + const provider = await plugin.call('blockchain' as any, 'getProvider'); + const network = await plugin.call('network', 'detectNetwork') - // Get accounts info - const runTabApi = await plugin.call('udapp' as any, 'getAccounts'); - const accountsCount = runTabApi?.accounts?.loadedAccounts - ? Object.keys(runTabApi.accounts.loadedAccounts).length - : 0; + // Verify the account was set + const runTabApi = await plugin.call('udapp' as any, 'getRunTabAPI'); + const accounts = runTabApi?.accounts; const result = { success: true, environment: { - provider: { - name: provider?.name || 'unknown', - displayName: provider?.displayName || provider?.name || 'unknown', - kind: provider?.kind || 'unknown' - }, - network: { - name: networkName, - chainId: chainId - }, - accounts: { - total: accountsCount, - selected: runTabApi?.accounts?.selectedAccount || null - } + provider, + network, + accounts } }; return this.createSuccessResult(result); } catch (error) { + console.error(error) return this.createErrorResult(`Failed to get environment information: ${error.message}`); } } @@ -828,6 +878,14 @@ export function createDeploymentTools(): RemixToolDefinition[] { category: ToolCategory.DEPLOYMENT, permissions: ['environment:read'], handler: new GetCurrentEnvironmentHandler() + }, + { + name: 'run_script', + description: 'Run a script in the current environment', + inputSchema: new RunScriptHandler().inputSchema, + category: ToolCategory.DEPLOYMENT, + permissions: ['transaction:send'], + handler: new RunScriptHandler() } ]; } \ No newline at end of file diff --git a/libs/remix-ai-core/src/remix-mcp-server/index.ts b/libs/remix-ai-core/src/remix-mcp-server/index.ts index e27a3645652..abc6a3cb16b 100644 --- a/libs/remix-ai-core/src/remix-mcp-server/index.ts +++ b/libs/remix-ai-core/src/remix-mcp-server/index.ts @@ -16,6 +16,7 @@ export { createFileManagementTools } from './handlers/FileManagementHandler'; export { createCompilationTools } from './handlers/CompilationHandler'; export { createDeploymentTools } from './handlers/DeploymentHandler'; export { createDebuggingTools } from './handlers/DebuggingHandler'; +export { createCodeAnalysisTools } from './handlers/CodeAnalysisHandler'; // Resource Providers export { ProjectResourceProvider } from './providers/ProjectResourceProvider'; diff --git a/libs/remix-ai-core/src/remix-mcp-server/registry/RemixToolRegistry.ts b/libs/remix-ai-core/src/remix-mcp-server/registry/RemixToolRegistry.ts index 6d5f98c3e58..3fd168cc41f 100644 --- a/libs/remix-ai-core/src/remix-mcp-server/registry/RemixToolRegistry.ts +++ b/libs/remix-ai-core/src/remix-mcp-server/registry/RemixToolRegistry.ts @@ -1,7 +1,7 @@ /** * Remix Tool Registry Implementation */ - +import { isBigInt } from 'web3-validator'; import EventEmitter from 'events'; import { IMCPToolCall, IMCPToolResult } from '../../types/mcp'; import { @@ -219,6 +219,19 @@ export class RemixToolRegistry extends EventEmitter implements ToolRegistry { } } +const replacer = (key: string, value: any) => { + if (isBigInt(value)) return value.toString(); // Convert BigInt to string + if (typeof value === 'function') return undefined; // Remove functions + if (value instanceof Error) { + return { + message: value.message, + name: value.name, + stack: value.stack, + }; // Properly serialize Error objects + } + return value; +}; + /** * Base class for implementing tool handlers */ @@ -237,11 +250,13 @@ export abstract class BaseToolHandler implements RemixToolHandler { return true; } + + protected createSuccessResult(content: any): IMCPToolResult { return { content: [{ type: 'text', - text: typeof content === 'string' ? content : JSON.stringify(content, null, 2) + text: typeof content === 'string' ? content : JSON.stringify(content, replacer, 2) }], isError: false }; diff --git a/libs/remix-ai-core/src/remix-mcp-server/types/mcpTools.ts b/libs/remix-ai-core/src/remix-mcp-server/types/mcpTools.ts index ab27d2337bb..90c562e7568 100644 --- a/libs/remix-ai-core/src/remix-mcp-server/types/mcpTools.ts +++ b/libs/remix-ai-core/src/remix-mcp-server/types/mcpTools.ts @@ -104,14 +104,16 @@ export interface CompilerConfigArgs { export interface DeployContractArgs { contractName: string; - constructorArgs?: any[]; + constructorArgs: any[]; gasLimit?: number; gasPrice?: string; value?: string; account?: string; + file: string; } export interface CallContractArgs { + contractName: string; address: string; abi: any[]; methodName: string; @@ -131,11 +133,12 @@ export interface SendTransactionArgs { account?: string; } +export interface RunScriptArgs { + file: string +} + export interface DebugSessionArgs { - contractAddress: string; transactionHash?: string; - sourceFile?: string; - network?: string; } export interface BreakpointArgs { @@ -254,28 +257,26 @@ export interface DeploymentResult { success: boolean; contractAddress?: string; transactionHash: string; - gasUsed: number; + gasUsed: number | bigint; effectiveGasPrice: string; - blockNumber: number; + blockNumber: number | bigint; logs: any[]; } +export interface RunScriptResult {} + export interface ContractInteractionResult { success: boolean; result?: any; transactionHash?: string; - gasUsed?: number; + gasUsed?: number | bigint; logs?: any[]; error?: string; } export interface DebugSessionResult { success: boolean; - sessionId: string; - contractAddress: string; - network: string; transactionHash?: string; - sourceFile?: string; status: string; createdAt: string; } diff --git a/libs/remix-core-plugin/src/index.ts b/libs/remix-core-plugin/src/index.ts index 6e3ef19562e..279a52783a3 100644 --- a/libs/remix-core-plugin/src/index.ts +++ b/libs/remix-core-plugin/src/index.ts @@ -2,10 +2,11 @@ export { OffsetToLineColumnConverter } from './lib/offset-line-to-column-convert export { CompilerMetadata } from './lib/compiler-metadata' export { FetchAndCompile } from './lib/compiler-fetch-and-compile' export { CompilerImports } from './lib/compiler-content-imports' -export { CompilerArtefacts } from './lib/compiler-artefacts' +export { CompilerArtefacts, getContractData } from './lib/compiler-artefacts' export { GistHandler } from './lib/gist-handler' export * from './types/contract' export { LinkLibraries, DeployLibraries } from './lib/link-libraries' export { OpenZeppelinProxy } from './lib/openzeppelin-proxy' export { fetchContractFromEtherscan } from './lib/helpers/fetch-etherscan' export { fetchContractFromBlockscout } from './lib/helpers/fetch-blockscout' +export { performSolidityScan, handleSolidityScan, type ScanReportRenderer } from './lib/solidity-scan' diff --git a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts index cca781d0608..62b96576bc2 100644 --- a/libs/remix-core-plugin/src/lib/compiler-artefacts.ts +++ b/libs/remix-core-plugin/src/lib/compiler-artefacts.ts @@ -1,8 +1,9 @@ 'use strict' import { Plugin } from '@remixproject/engine' -import { util } from '@remix-project/remix-lib' +import { util, execution } from '@remix-project/remix-lib' import { CompilerAbstract } from '@remix-project/remix-solidity' import { toChecksumAddress } from '@ethereumjs/util' +import { ContractData } from '../types/contract' const profile = { name: 'compilerArtefacts', @@ -227,3 +228,44 @@ export class CompilerArtefacts extends Plugin { return found } } + +export const getContractData = (contractName: string, compiler: CompilerAbstract): ContractData => { + if (!contractName) return null + // const compiler = plugin.compilersArtefacts[compilerAttributeName] + + if (!compiler) return null + + const contract = compiler.getContract(contractName) + + return { + name: contractName, + contract: contract, + compiler: compiler, + abi: contract.object.abi, + bytecodeObject: contract.object.evm.bytecode.object, + bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences, + object: contract.object, + deployedBytecode: contract.object.evm.deployedBytecode, + getConstructorInterface: () => { + return execution.txHelper.getConstructorInterface(contract.object.abi) + }, + getConstructorInputs: () => { + const constructorInterface = execution.txHelper.getConstructorInterface(contract.object.abi) + return execution.txHelper.inputParametersDeclarationToString(constructorInterface.inputs) + }, + isOverSizeLimit: async (args: string) => { + const encodedParams = await execution.txFormat.encodeParams(args, execution.txHelper.getConstructorInterface(contract.object.abi)) + const bytecode = contract.object.evm.bytecode.object + (encodedParams as any).dataHex + // https://eips.ethereum.org/EIPS/eip-3860 + const initCodeOversize = bytecode && (bytecode.length / 2 > 2 * 24576) + const deployedBytecode = contract.object.evm.deployedBytecode + // https://eips.ethereum.org/EIPS/eip-170 + const deployedBytecodeOversize = deployedBytecode && (deployedBytecode.object.length / 2 > 24576) + return { + overSizeEip3860: initCodeOversize, + overSizeEip170: deployedBytecodeOversize + } + }, + metadata: contract.object.metadata + } +} \ No newline at end of file diff --git a/libs/remix-core-plugin/src/lib/solidity-scan.ts b/libs/remix-core-plugin/src/lib/solidity-scan.ts new file mode 100644 index 00000000000..743d3625c8c --- /dev/null +++ b/libs/remix-core-plugin/src/lib/solidity-scan.ts @@ -0,0 +1,136 @@ +import axios from 'axios' +import { endpointUrls } from '@remix-endpoints-helper' +import { ScanReport } from '@remix-ui/helper' + +const _paq = (window._paq = window._paq || []) + +/** + * Core function to perform Solidity scan and return the scan report + * @param api - Remix API instance + * @param compiledFileName - Name of the file to scan + * @returns Promise with the scan report or throws error + */ +export const performSolidityScan = async (api: any, compiledFileName: string): Promise => { + const workspace = await api.call('filePanel', 'getCurrentWorkspace') + const fileName = `${workspace.name}/${compiledFileName}` + const filePath = `.workspaces/${fileName}` + const file = await api.call('fileManager', 'readFile', filePath) + + const urlResponse = await axios.post(`${endpointUrls.solidityScan}/uploadFile`, { file, fileName }) + + if (urlResponse.data.status !== 'success') { + throw new Error(urlResponse.data.error || 'Failed to upload file to SolidityScan') + } + + return new Promise((resolve, reject) => { + const ws = new WebSocket(`${endpointUrls.solidityScanWebSocket}/solidityscan`) + + const timeout = setTimeout(() => { + ws.close() + reject(new Error('Scan timeout')) + }, 300000) // 5 minute timeout + + ws.addEventListener('error', (error) => { + clearTimeout(timeout) + reject(new Error('WebSocket connection failed')) + }) + + ws.addEventListener('message', async (event) => { + try { + const data = JSON.parse(event.data) + + if (data.type === "auth_token_register" && data.payload.message === "Auth token registered.") { + ws.send(JSON.stringify({ + action: "message", + payload: { + type: "private_project_scan_initiate", + body: { + file_urls: [urlResponse.data.result.url], + project_name: "RemixProject", + project_type: "new" + } + } + })) + } else if (data.type === "scan_status" && data.payload.scan_status === "download_failed") { + clearTimeout(timeout) + ws.close() + reject(new Error(data.payload.scan_status_err_message || 'Scan failed')) + } else if (data.type === "scan_status" && data.payload.scan_status === "scan_done") { + clearTimeout(timeout) + const { data: scanData } = await axios.post(`${endpointUrls.solidityScan}/downloadResult`, { url: data.payload.scan_details.link }) + const scanReport: ScanReport = scanData.scan_report + + if (scanReport?.multi_file_scan_details?.length) { + // Process positions for each template + for (const template of scanReport.multi_file_scan_details) { + if (template.metric_wise_aggregated_findings?.length) { + const positions = [] + for (const details of template.metric_wise_aggregated_findings) { + for (const f of details.findings) + positions.push(`${f.line_nos_start[0]}:${f.line_nos_end[0]}`) + } + template.positions = JSON.stringify(positions) + } + } + ws.close() + resolve(scanReport) + } else { + ws.close() + reject(new Error('No scan results found')) + } + } + } catch (error) { + clearTimeout(timeout) + ws.close() + reject(error) + } + }) + }) +} + +/** + * Callback type for rendering scan results + * @param scanReport - The scan report to render + * @param fileName - The name of the scanned file + * @returns JSX element or any renderable content for the terminal + */ +export type ScanReportRenderer = (scanReport: ScanReport, fileName: string) => any + +/** + * Handler for Solidity scan with notifications and terminal output + * @param api - Remix API instance + * @param compiledFileName - Name of the file to scan + * @param modalMessage - Error modal title message + * @param renderResults - Callback function to render the scan results (e.g., as JSX) + */ +export const handleSolidityScan = async ( + api: any, + compiledFileName: string, + modalMessage: string, + renderResults: ScanReportRenderer +) => { + await api.call('notification', 'toast', 'Processing data to scan...') + _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'initiateScan']) + + try { + const workspace = await api.call('filePanel', 'getCurrentWorkspace') + const fileName = `${workspace.name}/${compiledFileName}` + + await api.call('notification', 'toast', 'Loading scan result in Remix terminal...') + + const scanReport = await performSolidityScan(api, compiledFileName) + + _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanSuccess']) + const renderedResults = renderResults(scanReport, fileName) + await api.call('terminal', 'logHtml', renderedResults) + } catch (error) { + _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanFailed']) + await api.call('notification', 'modal', { + id: 'SolidityScanError', + title: modalMessage, + message: error.message || 'Some error occurred! Please try again', + okLabel: 'Close' + }) + console.error(error) + } +} \ No newline at end of file diff --git a/libs/remix-lib/src/execution/txFormat.ts b/libs/remix-lib/src/execution/txFormat.ts index 6775140759a..1449ced9997 100644 --- a/libs/remix-lib/src/execution/txFormat.ts +++ b/libs/remix-lib/src/execution/txFormat.ts @@ -205,13 +205,15 @@ export function buildData (contractName, contract, contracts, isConstructor, fun let data: Buffer | string = '' let dataHex = '' - if (params.indexOf('raw:0x') === 0) { + if (!Array.isArray(params) && params.indexOf('raw:0x') === 0) { // in that case we consider that the input is already encoded and *does not* contain the method signature dataHex = params.replace('raw:0x', '') data = Buffer.from(dataHex, 'hex') } else { try { - if (params.length > 0) { + if (Array.isArray(params)) { + funArgs = params + } else if (params.length > 0) { funArgs = parseFunctionParams(params) } } catch (e) { diff --git a/libs/remix-lib/src/execution/txRunner.ts b/libs/remix-lib/src/execution/txRunner.ts index 0cb0ac6a5dc..cb8b303515f 100644 --- a/libs/remix-lib/src/execution/txRunner.ts +++ b/libs/remix-lib/src/execution/txRunner.ts @@ -1,6 +1,7 @@ 'use strict' import { EventManager } from '../eventManager' import { EOACode7702AuthorizationList } from '@ethereumjs/util' +import type { TransactionReceipt } from 'web3' /* * A type that represents a `0x`-prefixed hex string. */ @@ -21,6 +22,12 @@ export type Transaction = { type?: '0x1' | '0x2' | '0x4' } +export type TxResult = { + receipt: TransactionReceipt, + transactionHash: string, + tx: any +} + export class TxRunner { event pendingTxs diff --git a/libs/remix-lib/src/execution/txRunnerWeb3.ts b/libs/remix-lib/src/execution/txRunnerWeb3.ts index 0e44cab042c..b5ad760bea5 100644 --- a/libs/remix-lib/src/execution/txRunnerWeb3.ts +++ b/libs/remix-lib/src/execution/txRunnerWeb3.ts @@ -1,6 +1,6 @@ 'use strict' import { EventManager } from '../eventManager' -import type { Transaction as InternalTransaction } from './txRunner' +import type { Transaction as InternalTransaction, TxResult} from './txRunner' import { Web3 } from 'web3' import { BrowserProvider } from 'ethers' import { normalizeHexAddress } from '../helpers/uiHelper' @@ -88,7 +88,7 @@ export class TxRunnerWeb3 { }) }) } - listenOnResponse().then((txData) => { + listenOnResponse().then((txData: TxResult) => { callback(null, txData) }).catch((error) => { callback(error) }) } @@ -347,3 +347,4 @@ async function tryTillTxAvailable (txhash: string, web3: Web3) { } async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) } + diff --git a/libs/remix-lib/src/index.ts b/libs/remix-lib/src/index.ts index 1caf61e29bd..6d9d8706c26 100644 --- a/libs/remix-lib/src/index.ts +++ b/libs/remix-lib/src/index.ts @@ -9,7 +9,7 @@ import * as txExecution from './execution/txExecution' import * as txHelper from './execution/txHelper' import * as txFormat from './execution/txFormat' import { TxListener } from './execution/txListener' -import { TxRunner } from './execution/txRunner' +import { TxRunner, TxResult } from './execution/txRunner' import { LogsManager } from './execution/logsManager' import { forkAt } from './execution/forkAt' import * as typeConversion from './execution/typeConversion' @@ -43,4 +43,4 @@ const execution = { LogsManager, forkAt } -export { EventManager, helpers, Storage, util, execution, hash, eip7702Constants } +export { EventManager, helpers, Storage, util, execution, hash, eip7702Constants, TxResult } diff --git a/libs/remix-ui/helper/src/index.ts b/libs/remix-ui/helper/src/index.ts index 179f9946264..ff7dc200ebf 100644 --- a/libs/remix-ui/helper/src/index.ts +++ b/libs/remix-ui/helper/src/index.ts @@ -6,6 +6,5 @@ export * from './lib/components/custom-dropdown' export * from './lib/components/custom-tooltip' export * from './lib/components/feedback' export * from './lib/components/solScanTable' -export * from './lib/solidity-scan' export type { CompilerReport } from './types/compilerTypes' export * from './types/solidity-scan' \ No newline at end of file diff --git a/libs/remix-ui/helper/src/lib/solidity-scan.tsx b/libs/remix-ui/helper/src/lib/solidity-scan.tsx deleted file mode 100644 index 00afaaff441..00000000000 --- a/libs/remix-ui/helper/src/lib/solidity-scan.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react' -import axios from 'axios' -import { FormattedMessage } from 'react-intl' -import { endpointUrls } from '@remix-endpoints-helper' -import { ScanReport, SolScanTable } from '@remix-ui/helper' - -const _paq = (window._paq = window._paq || []) - -export const handleSolidityScan = async (api: any, compiledFileName: string) => { - await api.call('notification', 'toast', 'Processing data to scan...') - _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'initiateScan']) - - const workspace = await api.call('filePanel', 'getCurrentWorkspace') - const fileName = `${workspace.name}/${compiledFileName}` - const filePath = `.workspaces/${fileName}` - const file = await api.call('fileManager', 'readFile', filePath) - - try { - const urlResponse = await axios.post(`${endpointUrls.solidityScan}/uploadFile`, { file, fileName }) - - if (urlResponse.data.status === 'success') { - const ws = new WebSocket(`${endpointUrls.solidityScanWebSocket}/solidityscan`) - ws.addEventListener('error', console.error) - - ws.addEventListener('open', async () => { - await api.call('notification', 'toast', 'Loading scan result in Remix terminal...') - }) - - ws.addEventListener('message', async (event) => { - const data = JSON.parse(event.data) - if (data.type === "auth_token_register" && data.payload.message === "Auth token registered.") { - ws.send(JSON.stringify({ - action: "message", - payload: { - type: "private_project_scan_initiate", - body: { - file_urls: [urlResponse.data.result.url], - project_name: "RemixProject", - project_type: "new" - } - } - })) - } else if (data.type === "scan_status" && data.payload.scan_status === "download_failed") { - _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanFailed']) - await api.call('notification', 'modal', { - id: 'SolidityScanError', - title: , - message: data.payload.scan_status_err_message, - okLabel: 'Close' - }) - ws.close() - } else if (data.type === "scan_status" && data.payload.scan_status === "scan_done") { - _paq.push(['trackEvent', 'solidityCompiler', 'solidityScan', 'scanSuccess']) - const { data: scanData } = await axios.post(`${endpointUrls.solidityScan}/downloadResult`, { url: data.payload.scan_details.link }) - const scanReport: ScanReport = scanData.scan_report - - if (scanReport?.multi_file_scan_details?.length) { - for (const template of scanReport.multi_file_scan_details) { - if (template.metric_wise_aggregated_findings?.length) { - const positions = [] - for (const details of template.metric_wise_aggregated_findings) { - for (const f of details.findings) - positions.push(`${f.line_nos_start[0]}:${f.line_nos_end[0]}`) - } - template.positions = JSON.stringify(positions) - } - } - await api.call('terminal', 'logHtml', ) - } else { - await api.call('notification', 'modal', { - id: 'SolidityScanError', - title: , - message: "Some error occurred! Please try again", - okLabel: 'Close' - }) - } - ws.close() - } - }) - } else { - await api.call('notification', 'toast', 'Error in processing data to scan') - console.error(urlResponse.data && urlResponse.data.error ? urlResponse.data.error : urlResponse) - } - } catch (error) { - await api.call('notification', 'toast', 'Error in processing data to scan. Please check the console for details.') - console.error(error) - } -} \ No newline at end of file diff --git a/libs/remix-ui/helper/src/types/solidity-scan.ts b/libs/remix-ui/helper/src/types/solidity-scan.ts index 26864fb94ae..1debbbb0c12 100644 --- a/libs/remix-ui/helper/src/types/solidity-scan.ts +++ b/libs/remix-ui/helper/src/types/solidity-scan.ts @@ -16,6 +16,7 @@ export interface ScanDetails { metric_wise_aggregated_findings?: Record[] positions?: string template_details: ScanTemplate + file_name: string } export interface ScanReport { diff --git a/libs/remix-ui/remix-ai-assistant/src/components/prompt.tsx b/libs/remix-ui/remix-ai-assistant/src/components/prompt.tsx index 75c4499b0b6..b0f322e5f7a 100644 --- a/libs/remix-ui/remix-ai-assistant/src/components/prompt.tsx +++ b/libs/remix-ui/remix-ai-assistant/src/components/prompt.tsx @@ -30,6 +30,8 @@ export interface PromptAreaProps { handleSetModel: () => void handleModelSelection: (modelName: string) => void handleGenerateWorkspace: () => void + handleRecord: () => void + isRecording: boolean dispatchActivity: (type: ActivityType, payload?: any) => void contextBtnRef: React.RefObject modelBtnRef: React.RefObject @@ -70,6 +72,8 @@ export const PromptArea: React.FC = ({ handleSetModel, handleModelSelection, handleGenerateWorkspace, + handleRecord, + isRecording, dispatchActivity, contextBtnRef, modelBtnRef, @@ -205,6 +209,15 @@ export const PromptArea: React.FC = ({ )} +