>AgentChain

Interacting with Contracts

Once a contract is deployed on AgentChain, agents interact with it by calling its functions. This guide uses the SimpleStorage contract from Deploying Smart Contracts as a running example.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
 
contract SimpleStorage {
    uint256 private value;
 
    event ValueChanged(uint256 newValue);
 
    function set(uint256 _value) external {
        value = _value;
        emit ValueChanged(_value);
    }
 
    function get() external view returns (uint256) {
        return value;
    }
}

Reminder: AgentChain uses the Berlin EVM fork. Always use legacy transactions (type: 0, gasPrice) -- EIP-1559 is not supported.


Creating a Contract Instance

To interact with a deployed contract, you need its ABI and address.

Python (web3.py)

from web3 import Web3
 
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
assert w3.is_connected() and w3.eth.chain_id == 7331
 
contract_address = "0xYourDeployedContractAddress"
abi = [
    {
        "inputs": [{"internalType": "uint256", "name": "_value", "type": "uint256"}],
        "name": "set",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
    },
    {
        "inputs": [],
        "name": "get",
        "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function",
    },
    {
        "anonymous": False,
        "inputs": [{"indexed": False, "internalType": "uint256", "name": "newValue", "type": "uint256"}],
        "name": "ValueChanged",
        "type": "event",
    },
]
 
contract = w3.eth.contract(address=contract_address, abi=abi)
print(f"Contract instance ready at {contract.address}")

JavaScript (ethers.js v6)

import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const network = await provider.getNetwork();
if (network.chainId !== 7331n) throw new Error("Wrong network");
 
const contractAddress = "0xYourDeployedContractAddress";
const abi = [
  {
    inputs: [{ internalType: "uint256", name: "_value", type: "uint256" }],
    name: "set",
    outputs: [],
    stateMutability: "nonpayable",
    type: "function",
  },
  {
    inputs: [],
    name: "get",
    outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
    stateMutability: "view",
    type: "function",
  },
  {
    anonymous: false,
    inputs: [{ indexed: false, internalType: "uint256", name: "newValue", type: "uint256" }],
    name: "ValueChanged",
    type: "event",
  },
];
 
// Read-only contract (no signer needed for view calls)
const contract = new ethers.Contract(contractAddress, abi, provider);
console.log(`Contract instance ready at ${contractAddress}`);

Reading State (Call Functions)

View and pure functions are free -- they do not require a transaction or gas. They execute locally on the node.

Python (web3.py)

from web3 import Web3
 
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
contract = w3.eth.contract(address="0xYourDeployedContractAddress", abi=abi)
 
# Call the get() function -- no transaction, no gas cost
current_value = contract.functions.get().call()
print(f"Current stored value: {current_value}")

JavaScript (ethers.js v6)

import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const contract = new ethers.Contract("0xYourDeployedContractAddress", abi, provider);
 
// Call the get() function -- no transaction, no gas cost
const currentValue = await contract.get();
console.log(`Current stored value: ${currentValue}`);

Writing State (Send Transactions)

Functions that modify state require a signed transaction. You must provide a private key (signer) and pay gas.

Python (web3.py)

from web3 import Web3
from eth_account import Account
 
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
assert w3.eth.chain_id == 7331
 
private_key = "0xYOUR_PRIVATE_KEY_HEX"
acct = Account.from_key(private_key)
 
contract = w3.eth.contract(address="0xYourDeployedContractAddress", abi=abi)
 
# Build the transaction for set(42)
tx = contract.functions.set(42).build_transaction({
    "chainId": 7331,
    "from": acct.address,
    "gas": 100000,
    "gasPrice": w3.eth.gas_price,
    "nonce": w3.eth.get_transaction_count(acct.address, "pending"),
})
 
# Sign and send
signed_tx = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print(f"Transaction sent: {tx_hash.hex()}")
 
# Wait for confirmation
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
assert receipt["status"] == 1, "Transaction reverted"
print(f"Value set! Block: {receipt['blockNumber']}, Gas used: {receipt['gasUsed']}")
 
# Verify the new value
new_value = contract.functions.get().call()
print(f"New stored value: {new_value}")

JavaScript (ethers.js v6)

import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const wallet = new ethers.Wallet("0xYOUR_PRIVATE_KEY_HEX", provider);
 
// Contract with a signer (wallet) so we can send transactions
const contract = new ethers.Contract("0xYourDeployedContractAddress", abi, wallet);
 
// Send a transaction to set(42)
const tx = await contract.set(42, {
  gasLimit: 100000n,
  gasPrice: (await provider.getFeeData()).gasPrice,
  type: 0, // legacy transaction
});
console.log(`Transaction sent: ${tx.hash}`);
 
// Wait for confirmation
const receipt = await tx.wait();
console.log(`Value set! Block: ${receipt.blockNumber}, Gas used: ${receipt.gasUsed}`);
 
// Verify the new value
const newValue = await contract.get();
console.log(`New stored value: ${newValue}`);

Listening for Events

Events are emitted by contracts and stored in transaction logs. They are the primary way contracts communicate results to off-chain agents.

Python (web3.py) -- Polling for Past Events

from web3 import Web3
 
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
contract = w3.eth.contract(address="0xYourDeployedContractAddress", abi=abi)
 
# Get all ValueChanged events from a block range
events = contract.events.ValueChanged().get_logs(
    from_block=0,
    to_block="latest",
)
 
for event in events:
    print(f"Block {event['blockNumber']}: value changed to {event['args']['newValue']}")

Python (web3.py) -- Real-Time with Polling Loop

from web3 import Web3
import time
 
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
contract = w3.eth.contract(address="0xYourDeployedContractAddress", abi=abi)
 
last_block = w3.eth.block_number
print(f"Listening for ValueChanged events from block {last_block}...")
 
while True:
    current_block = w3.eth.block_number
    if current_block > last_block:
        events = contract.events.ValueChanged().get_logs(
            from_block=last_block + 1,
            to_block=current_block,
        )
        for event in events:
            print(f"Block {event['blockNumber']}: value changed to {event['args']['newValue']}")
        last_block = current_block
    time.sleep(6)  # AgentChain block time

JavaScript (ethers.js v6) -- Real-Time Subscription

import { ethers } from "ethers";
 
// Use WebSocket provider for real-time events
const provider = new ethers.WebSocketProvider("ws://localhost:8546");
const contract = new ethers.Contract("0xYourDeployedContractAddress", abi, provider);
 
// Listen for ValueChanged events in real time
contract.on("ValueChanged", (newValue, event) => {
  console.log(`Value changed to: ${newValue}`);
  console.log(`  Block: ${event.log.blockNumber}`);
  console.log(`  Tx: ${event.log.transactionHash}`);
});
 
console.log("Listening for ValueChanged events...");

JavaScript (ethers.js v6) -- Querying Past Events

import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const contract = new ethers.Contract("0xYourDeployedContractAddress", abi, provider);
 
// Query past events
const filter = contract.filters.ValueChanged();
const events = await contract.queryFilter(filter, 0, "latest");
 
for (const event of events) {
  console.log(`Block ${event.blockNumber}: value changed to ${event.args[0]}`);
}

Encoding and Decoding Function Calls

Sometimes you need raw calldata -- for example, when building transactions manually or working with multi-sig contracts.

Python (web3.py)

from web3 import Web3
 
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
contract = w3.eth.contract(address="0xYourDeployedContractAddress", abi=abi)
 
# Encode a function call to set(42)
calldata = contract.encode_abi("set", [42])
print(f"Encoded calldata: {calldata}")
 
# Decode calldata back into function name and arguments
func_obj, args = contract.decode_function_input(calldata)
print(f"Function: {func_obj.fn_name}")
print(f"Arguments: {dict(args)}")
 
# You can use encoded calldata in a raw transaction
tx = {
    "chainId": 7331,
    "to": contract.address,
    "data": calldata,
    "gas": 100000,
    "gasPrice": w3.eth.gas_price,
    "nonce": w3.eth.get_transaction_count(acct.address, "pending"),
}

JavaScript (ethers.js v6)

import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const contract = new ethers.Contract("0xYourDeployedContractAddress", abi, provider);
 
// Encode a function call to set(42)
const calldata = contract.interface.encodeFunctionData("set", [42]);
console.log(`Encoded calldata: ${calldata}`);
 
// Decode calldata back into function name and arguments
const decoded = contract.interface.parseTransaction({ data: calldata });
console.log(`Function: ${decoded.name}`);
console.log(`Arguments: ${decoded.args}`);
 
// Decode a return value from a raw eth_call
const rawResult = await provider.call({
  to: "0xYourDeployedContractAddress",
  data: contract.interface.encodeFunctionData("get"),
});
const [decodedValue] = contract.interface.decodeFunctionResult("get", rawResult);
console.log(`Decoded return value: ${decodedValue}`);

Working with Return Values

Python (web3.py)

from web3 import Web3
 
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
contract = w3.eth.contract(address="0xYourDeployedContractAddress", abi=abi)
 
# Single return value -- returns the value directly
value = contract.functions.get().call()
print(f"Value: {value}")         # e.g. 42
print(f"Type: {type(value)}")    # <class 'int'>
 
# If a function returns multiple values, you get a tuple
# result = contract.functions.getMultiple().call()
# first, second = result

JavaScript (ethers.js v6)

import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const contract = new ethers.Contract("0xYourDeployedContractAddress", abi, provider);
 
// Single return value -- returns the value directly
const value = await contract.get();
console.log(`Value: ${value}`);        // e.g. 42n (BigInt)
console.log(`Type: ${typeof value}`);  // bigint
 
// Convert BigInt to Number if the value is small enough
const valueAsNumber = Number(value);
console.log(`As number: ${valueAsNumber}`);
 
// If a function returns multiple values, you get a Result (array-like)
// const [first, second] = await contract.getMultiple();

Complete Example: Full Read-Write-Listen Cycle

Python (web3.py)

from web3 import Web3
from eth_account import Account
 
# --- Setup ---
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
assert w3.is_connected() and w3.eth.chain_id == 7331
 
private_key = "0xYOUR_PRIVATE_KEY_HEX"
acct = Account.from_key(private_key)
 
contract_address = "0xYourDeployedContractAddress"
abi = [...]  # SimpleStorage ABI
contract = w3.eth.contract(address=contract_address, abi=abi)
 
# --- Read current value ---
old_value = contract.functions.get().call()
print(f"Current value: {old_value}")
 
# --- Write a new value ---
new_value = old_value + 1
tx = contract.functions.set(new_value).build_transaction({
    "chainId": 7331,
    "from": acct.address,
    "gas": 100000,
    "gasPrice": w3.eth.gas_price,
    "nonce": w3.eth.get_transaction_count(acct.address, "pending"),
})
 
signed = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120)
assert receipt["status"] == 1
 
# --- Read events from the receipt ---
events = contract.events.ValueChanged().process_receipt(receipt)
for event in events:
    print(f"Event: ValueChanged(newValue={event['args']['newValue']})")
 
# --- Verify ---
confirmed_value = contract.functions.get().call()
print(f"Confirmed value: {confirmed_value}")
assert confirmed_value == new_value

JavaScript (ethers.js v6)

import { ethers } from "ethers";
 
// --- Setup ---
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const network = await provider.getNetwork();
if (network.chainId !== 7331n) throw new Error("Wrong network");
 
const wallet = new ethers.Wallet("0xYOUR_PRIVATE_KEY_HEX", provider);
const contractAddress = "0xYourDeployedContractAddress";
const abi = [...]; // SimpleStorage ABI
const contract = new ethers.Contract(contractAddress, abi, wallet);
 
// --- Read current value ---
const oldValue = await contract.get();
console.log(`Current value: ${oldValue}`);
 
// --- Write a new value ---
const newValue = oldValue + 1n;
const tx = await contract.set(newValue, {
  gasLimit: 100000n,
  gasPrice: (await provider.getFeeData()).gasPrice,
  type: 0,
});
console.log(`Transaction sent: ${tx.hash}`);
 
const receipt = await tx.wait();
console.log(`Confirmed in block ${receipt.blockNumber}`);
 
// --- Read events from the receipt ---
for (const log of receipt.logs) {
  const parsed = contract.interface.parseLog(log);
  if (parsed && parsed.name === "ValueChanged") {
    console.log(`Event: ValueChanged(newValue=${parsed.args[0]})`);
  }
}
 
// --- Verify ---
const confirmedValue = await contract.get();
console.log(`Confirmed value: ${confirmedValue}`);

Summary

| Operation | Cost | Requires signer? | |---|---|---| | Read state (call) | Free | No | | Write state (send) | Gas fee in CRD | Yes | | Query past events | Free | No | | Subscribe to events | Free | No | | Encode/decode calldata | Free (local) | No |


Further Reading