read_contract
Extract and decode blockchain contract states by specifying contract address, method, and expected output types. Ensures accurate data retrieval for nested structures and tuples.
Instructions
Read contract state from a blockchain. important:
In case of a tuple, don't use type tuple, but specify the inner types (found in the source) in order. For nested structs, include the substructs types.
Example:
struct DataTypeA {
DataTypeB b;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
}
struct DataTypeB {
address token;
}
results in outputs for function with return type DataTypeA (tuple in abi): outputs: [{"type": "address"}, {"type": "uint128"}]
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| contract | Yes | The contract address | |
| inputs | Yes | Input parameters for the method call | |
| method | Yes | The contract method to call | |
| network | Yes | The blockchain network (e.g., "ethereum", "base") | |
| outputs | Yes | Expected output types for the method call. In case of a tuple, don't use type tuple, but specify the inner types (found in the source) in order. For nested structs, include the substructs types. Example: struct DataTypeA { DataTypeB b; //the liquidity index. Expressed in ray uint128 liquidityIndex; } struct DataTypeB { address token; } results in outputs for function with return type DataTypeA (tuple in abi): outputs: [{"type": "address"}, {"type": "uint128"}] |
Implementation Reference
- src/index.ts:63-81 (registration)Registration of the 'read_contract' tool in the MCP server's list of tools, specifying name, description, and input schema.name: "read_contract", description: `Read contract state from a blockchain. important: In case of a tuple, don't use type tuple, but specify the inner types (found in the source) in order. For nested structs, include the substructs types. Example: struct DataTypeA { DataTypeB b; //the liquidity index. Expressed in ray uint128 liquidityIndex; } struct DataTypeB { address token; } results in outputs for function with return type DataTypeA (tuple in abi): outputs: [{"type": "address"}, {"type": "uint128"}]`, inputSchema: zodToJsonSchema(contracts.ReadContractSchema), },
- src/operations/contracts.ts:45-66 (schema)Zod schema defining the input structure for the read_contract tool, including network, contract, method, inputs, and outputs.export const ReadContractSchema = z.object({ network: z.string().describe('The blockchain network (e.g., "ethereum", "base")'), contract: z.string().describe('The contract address'), method: z.string().describe('The contract method to call'), inputs: z.array(InputSchema).describe('Input parameters for the method call'), outputs: z.array(OutputSchema).describe(`Expected output types for the method call. In case of a tuple, don't use type tuple, but specify the inner types (found in the source) in order. For nested structs, include the substructs types. Example: struct DataTypeA { DataTypeB b; //the liquidity index. Expressed in ray uint128 liquidityIndex; } struct DataTypeB { address token; } results in outputs for function with return type DataTypeA (tuple in abi): outputs: [{"type": "address"}, {"type": "uint128"}] `) });
- src/index.ts:147-159 (handler)MCP CallToolRequest handler case for 'read_contract', parses arguments, calls the implementation function, and formats response.case "read_contract": { const args = contracts.ReadContractSchema.parse(request.params.arguments); const result = await contracts.readContractState( args.network, args.contract, args.method, args.inputs, args.outputs ); return { content: [{type: "text", text: JSON.stringify(result, null, 2)}], }; }
- src/operations/contracts.ts:155-212 (handler)Core implementation of the read_contract tool: processes outputs, authenticates with API token, makes POST request to Bankless API to read contract state, handles various errors.export async function readContractState( network: string, contract: string, method: string, inputs: z.infer<typeof InputSchema>[], outputs: z.infer<typeof OutputSchema>[] ): Promise<ContractCallResult[]> { const token = process.env.BANKLESS_API_TOKEN; if (!token) { throw new BanklessAuthenticationError('BANKLESS_API_TOKEN environment variable is not set'); } const endpoint = `${BASE_URL}/chains/${network}/contract/read`; const cleanedOutputs = processOutputs(outputs); try { const response = await axios.post( endpoint, { contract, method, inputs, outputs: cleanedOutputs }, { headers: { 'Content-Type': 'application/json', 'X-BANKLESS-TOKEN': `${token}` } } ); return response.data; } catch (error) { if (axios.isAxiosError(error)) { const statusCode = error.response?.status || 'unknown'; const errorMessage = error.response?.data?.message || error.message; if (statusCode === 401 || statusCode === 403) { throw new BanklessAuthenticationError(`Authentication Failed: ${errorMessage}`); } else if (statusCode === 404) { throw new BanklessResourceNotFoundError(`Not Found: ${errorMessage}`); } else if (statusCode === 422) { throw new BanklessValidationError(`Validation Error: ${errorMessage}`, error.response?.data); } else if (statusCode === 429) { // Extract reset timestamp or default to 60 seconds from now const resetAt = new Date(); resetAt.setSeconds(resetAt.getSeconds() + 60); throw new BanklessRateLimitError(`Rate Limit Exceeded: ${errorMessage}`, resetAt); } throw new Error(`Bankless API Error (${statusCode}): ${errorMessage}`); } throw new Error(`Failed to read contract state: ${error instanceof Error ? error.message : String(error)}`); } }
- src/operations/contracts.ts:137-150 (helper)Helper function to process output schemas, flattening tuples recursively for the API request.export function processOutputs(outputs: OutputSchemaType[]) { if (!outputs || !Array.isArray(outputs)) { return []; } const result: OutputSchemaType[] = []; for (const output of outputs) { const processed = processOutput(output); result.push(...processed); } return result; }