From 57d1de80c0d33e0125f6f4ec256f9215395ce7d5 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Wed, 1 Oct 2025 21:21:51 +0200 Subject: [PATCH 1/9] mcp: deploy and interact --- apps/remix-ide/src/blockchain/blockchain.tsx | 7 +- .../handlers/DeploymentHandler.ts | 184 +++++++++++------- .../src/remix-mcp-server/types/mcpTools.ts | 10 +- libs/remix-core-plugin/src/index.ts | 2 +- .../src/lib/compiler-artefacts.ts | 44 ++++- libs/remix-lib/src/execution/txFormat.ts | 6 +- libs/remix-lib/src/execution/txRunner.ts | 7 + libs/remix-lib/src/execution/txRunnerWeb3.ts | 5 +- libs/remix-lib/src/index.ts | 4 +- .../run-tab/src/lib/actions/deploy.ts | 47 +---- .../remix-ui/run-tab/src/lib/actions/index.ts | 6 +- .../run-tab/src/lib/types/blockchain.d.ts | 2 +- package.json | 1 + 13 files changed, 186 insertions(+), 139 deletions(-) diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index 53e89ba9305..1e851474eba 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 } @@ -546,7 +546,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) }) } @@ -668,7 +668,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 +701,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/handlers/DeploymentHandler.ts b/libs/remix-ai-core/src/remix-mcp-server/handlers/DeploymentHandler.ts index afd077b6d37..e17d304321d 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 @@ -14,7 +14,12 @@ import { AccountInfo, ContractInteractionResult } from '../types/mcpTools'; +import { bytesToHex } from '@ethereumjs/util' 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 web3 from 'web3' /** * Deploy Contract Tool Handler @@ -54,6 +59,10 @@ 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'] @@ -86,56 +95,54 @@ 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.'); + 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}'`); } - // 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`); - } - - // 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: () => {}, 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 : [], + null, + compilerContracts.getData().contracts, + callbacks, + confirmationCb + ) + }) + } catch (e) { + return this.createErrorResult(`Deployment error: ${e.message}`); } - - // 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, + + + console.log('txReturn', txReturn) + 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}`); @@ -161,6 +168,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', @@ -238,43 +250,65 @@ 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`); - } - - // 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'); + 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.address, + params, + isView, + (msg) => { + // logMsg + }, + (msg) => { + // logCallback + }, + (returnValue) => { + // outputCb + }, + (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + // confirmationCb + continueTxExecution(null) + }, + (error, continueTxExecution, cancelCb) => { + // continueCb + continueTxExecution() + }, + (okCb, cancelCb) => { + // promptCb + }, + (error, cancelCb) => { + // promptCb + }, + (error, {txResult, address, returnValue}) => { + if (error) return reject(error) + resolve({txResult, address, returnValue}) + }, + ) + }) + } catch (e) { + return this.createErrorResult(`Deployment error: ${e.message}`); } - // 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}`); 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..623a6df2a47 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; @@ -254,9 +256,9 @@ export interface DeploymentResult { success: boolean; contractAddress?: string; transactionHash: string; - gasUsed: number; + gasUsed: number | bigint; effectiveGasPrice: string; - blockNumber: number; + blockNumber: number | bigint; logs: any[]; } @@ -264,7 +266,7 @@ export interface ContractInteractionResult { success: boolean; result?: any; transactionHash?: string; - gasUsed?: number; + gasUsed?: number | bigint; logs?: any[]; error?: string; } diff --git a/libs/remix-core-plugin/src/index.ts b/libs/remix-core-plugin/src/index.ts index 6e3ef19562e..4a011430d81 100644 --- a/libs/remix-core-plugin/src/index.ts +++ b/libs/remix-core-plugin/src/index.ts @@ -2,7 +2,7 @@ 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' 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-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/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index f587450c9ed..10633f202e7 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -1,4 +1,4 @@ -import { ContractData, FuncABI, NetworkDeploymentFile, SolcBuildFile, OverSizeLimit } from "@remix-project/core-plugin" +import { ContractData, FuncABI, NetworkDeploymentFile, SolcBuildFile, OverSizeLimit, getContractData } from "@remix-project/core-plugin" import { RunTab } from "../types/run-tab" import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity' import * as remixLib from '@remix-project/remix-lib' @@ -40,49 +40,6 @@ const loadContractFromAddress = (plugin: RunTab, address, confirmCb, cb) => { } } -export const getSelectedContract = (contractName: string, compiler: CompilerAbstractType): 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 txHelper.getConstructorInterface(contract.object.abi) - }, - getConstructorInputs: () => { - const constructorInterface = txHelper.getConstructorInterface(contract.object.abi) - - return txHelper.inputParametersDeclarationToString(constructorInterface.inputs) - }, - isOverSizeLimit: async (args: string) => { - const encodedParams = await txFormat.encodeParams(args, 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 - } -} - const getCompilerContracts = (plugin: RunTab) => { return plugin.compilersArtefacts.__last.getData().contracts } @@ -269,7 +226,7 @@ export const loadAddress = (plugin: RunTab, dispatch: React.Dispatch, contr if (!contract) return plugin.call('notification', 'toast', 'No compiled contracts found.') const currentFile = plugin.REACT_API.contracts.currentFile const compiler = plugin.REACT_API.contracts.contractList[currentFile].find(item => item.alias === contract.name) - const contractData = getSelectedContract(contract.name, compiler.compiler) + const contractData = getContractData(contract.name, compiler.compiler) return addInstance(dispatch, { contractData, address, name: contract.name }) } } 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..4208b910c87 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/index.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -6,9 +6,9 @@ import { createNewBlockchainAccount, createSmartAccount, delegationAuthorization import { clearInstances, clearPopUp, removeInstance, pinInstance, unpinInstance, setAccount, setGasFee, setMatchPassphrasePrompt, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath } from './actions' -import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal, isValidContractAddress, isValidContractUpgrade } from './deploy' +import { createInstance, getContext, getFuncABIInputs, loadAddress, runTransactions, updateInstanceBalance, syncContractsInternal, isValidContractAddress, isValidContractUpgrade } from './deploy' import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity' -import { ContractData, FuncABI, OverSizeLimit } from "@remix-project/core-plugin" +import { ContractData, FuncABI, OverSizeLimit, getContractData } from "@remix-project/core-plugin" import { DeployMode, MainnetPrompt } from '../types' import { runCurrentScenario, storeScenario } from './recorder' import { SolcInput, SolcOutput } from '@openzeppelin/upgrades-core' @@ -44,7 +44,7 @@ export const internalDelegationAuthorization = (contractAddress: string) => dele export const setPassphraseModal = (passphrase: string) => setPassphrasePrompt(dispatch, passphrase) 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 fetchSelectedContract = (contractName: string, compiler: CompilerAbstractType) => getContractData(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 setSendValue = (value: string) => setSendTransactionValue(dispatch, value) export const setBaseFeePerGas = (baseFee: string) => updateBaseFeePerGas(dispatch, baseFee) diff --git a/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts b/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts index cb9d91e4e44..df6595958a3 100644 --- a/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts +++ b/libs/remix-ui/run-tab/src/lib/types/blockchain.d.ts @@ -49,7 +49,7 @@ export class Blockchain extends Plugin { signMessage(message: any, account: any, passphrase: any, cb: any): void; web3(): any; getTxListener(opts: any): any; - runOrCallContractMethod(contractName: any, contractAbi: any, funABI: any, contract: any, value: any, address: any, callType: any, lookupOnly: any, logMsg: any, logCallback: any, outputCb: any, confirmationCb: any, continueCb: any, promptCb: any): void; + runOrCallContractMethod(contractName: any, contractAbi: any, funABI: any, contract: any, value: any, address: any, callType: any, lookupOnly: any, logMsg: any, logCallback: any, outputCb: any, confirmationCb: any, continueCb: any, promptCb: any, finalCb?: any): void; context(): "memory" | "blockchain"; resetAndInit(config: any, transactionContextAPI: any): void; transactionContextAPI: any; diff --git a/package.json b/package.json index ecf828ee24e..02c36b7ea23 100644 --- a/package.json +++ b/package.json @@ -412,3 +412,4 @@ "@ethereumjs/binarytree": "^10.0.0" } } + From 02195a88c54574fafa60700025ac250bd355e17b Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 2 Oct 2025 13:58:32 +0200 Subject: [PATCH 2/9] implement other deployment tools --- apps/remix-ide/src/app/udapp/run-tab.tsx | 12 +- apps/remix-ide/src/blockchain/blockchain.tsx | 2 +- .../handlers/DebuggingHandler.ts | 27 +--- .../handlers/DeploymentHandler.ts | 142 +++++++----------- .../registry/RemixToolRegistry.ts | 19 ++- .../src/remix-mcp-server/types/mcpTools.ts | 7 - .../run-tab/src/lib/actions/events.ts | 6 +- 7 files changed, 93 insertions(+), 122 deletions(-) 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 1e851474eba..f1fb0c17ee3 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -607,7 +607,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 } 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..4939501846b 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[] { @@ -62,17 +55,10 @@ export class StartDebugSessionHandler extends BaseToolHandler { 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'; } @@ -83,16 +69,11 @@ export class StartDebugSessionHandler extends BaseToolHandler { async execute(args: DebugSessionArgs, plugin: Plugin): Promise { try { // TODO: Integrate with Remix debugger plugin - const sessionId = 'debug_' + Date.now(); - + 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() }; 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 e17d304321d..6d3b2f67ac2 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 @@ -14,12 +14,12 @@ import { AccountInfo, ContractInteractionResult } from '../types/mcpTools'; -import { bytesToHex } from '@ethereumjs/util' 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 web3 from 'web3' +import { BrowserProvider } from "ethers" +import web3, { Web3 } from 'web3' /** * Deploy Contract Tool Handler @@ -65,7 +65,7 @@ export class DeployContractHandler extends BaseToolHandler { description: 'The file containing the contract to deploy' } }, - required: ['contractName'] + required: ['contractName', 'file'] }; getPermissions(): string[] { @@ -116,7 +116,7 @@ export class DeployContractHandler extends BaseToolHandler { const compilerContracts = await plugin.call('compilerArtefacts', 'getLastCompilationResult') plugin.call('blockchain', 'deployContractAndLibraries', data, - args.constructorArgs ? args : [], + args.constructorArgs ? args.constructorArgs : [], null, compilerContracts.getData().contracts, callbacks, @@ -128,7 +128,6 @@ export class DeployContractHandler extends BaseToolHandler { } - console.log('txReturn', txReturn) const receipt = (txReturn.txResult.receipt as TransactionReceipt) const result: DeploymentResult = { transactionHash: web3.utils.bytesToHex(receipt.transactionHash), @@ -216,7 +215,7 @@ export class CallContractHandler extends BaseToolHandler { description: 'Account to call from' } }, - required: ['address', 'abi', 'methodName'] + required: ['address', 'abi', 'methodName', 'contractName'] }; getPermissions(): string[] { @@ -224,7 +223,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, { @@ -261,7 +260,7 @@ export class CallContractHandler extends BaseToolHandler { args.abi, funcABI, undefined, - args.args ? args : [], + args.args ? args.args : [], args.address, params, isView, @@ -314,15 +313,6 @@ export class CallContractHandler extends BaseToolHandler { return this.createErrorResult(`Contract call 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 []; - } - } } /** @@ -397,40 +387,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 []; - } - } } /** @@ -455,21 +449,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) { @@ -489,7 +473,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: { @@ -505,26 +489,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) { @@ -568,16 +549,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}`); } @@ -712,9 +691,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) { @@ -750,37 +730,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}`); } } 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 623a6df2a47..e4306d49967 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 @@ -134,10 +134,7 @@ export interface SendTransactionArgs { } export interface DebugSessionArgs { - contractAddress: string; transactionHash?: string; - sourceFile?: string; - network?: string; } export interface BreakpointArgs { @@ -273,11 +270,7 @@ export interface ContractInteractionResult { export interface DebugSessionResult { success: boolean; - sessionId: string; - contractAddress: string; - network: string; transactionHash?: string; - sourceFile?: string; status: string; createdAt: string; } diff --git a/libs/remix-ui/run-tab/src/lib/actions/events.ts b/libs/remix-ui/run-tab/src/lib/actions/events.ts index de198d5cb4f..9f6e33f2f47 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/events.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/events.ts @@ -1,7 +1,7 @@ import { envChangeNotification } from "@remix-ui/helper" import { RunTab } from "../types/run-tab" import { setExecutionContext, setFinalContext, updateAccountBalances, fillAccountsList } from "./account" -import { addExternalProvider, addInstance, addNewProxyDeployment, removeExternalProvider, setNetworkNameFromProvider, setPinnedChainId, setExecEnv } from "./actions" +import { setAccount, addExternalProvider, addInstance, addNewProxyDeployment, removeExternalProvider, setNetworkNameFromProvider, setPinnedChainId, setExecEnv } from "./actions" import { addDeployOption, clearAllInstances, clearRecorderCount, fetchContractListSuccess, resetProxyDeployments, resetUdapp, setCurrentContract, setCurrentFile, setLoadType, setRecorderCount, setRemixDActivated, setSendValue, fetchAccountsListSuccess, fetchAccountsListRequest } from "./payload" import { updateInstanceBalance } from './deploy' import { CompilerAbstract } from '@remix-project/remix-solidity' @@ -114,6 +114,10 @@ export const setupEvents = (plugin: RunTab) => { dispatch(clearAllInstances()) }) + plugin.on('udapp', 'setAccountReducer', (account: string) => { + setAccount(dispatch, account) + }) + plugin.on('udapp', 'addInstanceReducer', (address, abi, name, contractData?) => { addInstance(dispatch, { contractData, abi, address, name }) }) From 0729e760abd9130e52beb3afdd9c5fa593cd7c63 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 2 Oct 2025 22:04:52 +0200 Subject: [PATCH 3/9] fix deployment --- apps/remix-ide/src/blockchain/blockchain.tsx | 4 +++- .../handlers/DeploymentHandler.ts | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/remix-ide/src/blockchain/blockchain.tsx b/apps/remix-ide/src/blockchain/blockchain.tsx index f1fb0c17ee3..e9812f20c89 100644 --- a/apps/remix-ide/src/blockchain/blockchain.tsx +++ b/apps/remix-ide/src/blockchain/blockchain.tsx @@ -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...`) 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 6d3b2f67ac2..52b17cb2496 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 @@ -106,7 +106,9 @@ export class DeployContractHandler extends BaseToolHandler { txReturn = await new Promise(async (resolve, reject) => { const callbacks = { continueCb: (error, continueTxExecution, cancelCb) => { continueTxExecution() - }, promptCb: () => {}, statusCb: () => {}, finalCb: (error, contractObject, address: string, txResult: TxResult) => { + }, promptCb: () => {}, statusCb: (error) => { + console.log(error) + }, finalCb: (error, contractObject, address: string, txResult: TxResult) => { if (error) return reject(error) resolve({contractObject, address, txResult}) }} @@ -240,8 +242,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; @@ -266,9 +276,11 @@ export class CallContractHandler extends BaseToolHandler { isView, (msg) => { // logMsg + console.log(msg) }, (msg) => { // logCallback + console.log(msg) }, (returnValue) => { // outputCb @@ -278,6 +290,7 @@ export class CallContractHandler extends BaseToolHandler { continueTxExecution(null) }, (error, continueTxExecution, cancelCb) => { + if (error) reject(error) // continueCb continueTxExecution() }, @@ -286,6 +299,7 @@ export class CallContractHandler extends BaseToolHandler { }, (error, cancelCb) => { // promptCb + if (error) reject(error) }, (error, {txResult, address, returnValue}) => { if (error) return reject(error) From 52fc6d8bcbde9c3742153fa73ba1c1dc87981340 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Thu, 2 Oct 2025 22:05:15 +0200 Subject: [PATCH 4/9] add audio prompt --- .../src/components/prompt.tsx | 13 +++ .../remix-ui-remix-ai-assistant.tsx | 68 ++++++++++++ .../src/hooks/useAudioTranscription.ts | 105 ++++++++++++++++++ .../src/services/fireworksTranscription.ts | 92 +++++++++++++++ .../remix-ai-assistant/src/utils/README.md | 96 ++++++++++++++++ .../src/utils/audioRecorder.ts | 101 +++++++++++++++++ 6 files changed, 475 insertions(+) create mode 100644 libs/remix-ui/remix-ai-assistant/src/hooks/useAudioTranscription.ts create mode 100644 libs/remix-ui/remix-ai-assistant/src/services/fireworksTranscription.ts create mode 100644 libs/remix-ui/remix-ai-assistant/src/utils/README.md create mode 100644 libs/remix-ui/remix-ai-assistant/src/utils/audioRecorder.ts 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 = ({ )} +