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 timeJavaScript (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 = resultJavaScript (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_valueJavaScript (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
- Connecting to AgentChain -- provider setup and error handling
- Sending Transactions -- nonce management and gas estimation
- Checking Balances -- monitoring CRD balances