Sending and verifying token transactions with Agent
Introduction
This example demonstrates the integration of uAgents with Web3.py to interact with Ethereum smart contracts and manage agent systems. It highlights the process of initiating a USDC transfer, processing the transaction, and verifying the transaction status using the agent system. The example also shows how uAgents can facilitate token transfers to smart contracts and enable seamless fund transfers.
Please check out the example code in our examples repo (opens in a new tab) to run this locally.
Supporting Documents
- Almanac contract overview .
- How to create an agent .
- Registering in the Almanac Contract .
- Creating an interval task
- Communicating with other agents
Pre-requisites
- Python: Download and install from Python official website (opens in a new tab).
- Poetry: Install by following the instructions on Poetry's official website (opens in a new tab).
- A running Ethereum node (or a provider like Infura) on Infura official website (opens in a new tab).
- Contract ABI: USDC smart contract ABI (in abi.json file) on sepolia.etherscan.io official website (opens in a new tab).
Project Structure
.sending-and-verifying-token-transactions-with-agent ├── poetry.lock ├── pyproject.toml ├── README.md └── web3 ├── abi.json └── transaction.py
Sending and Verifying Token Transactions with Agents
This example demonstrates how to build a simple agent-based system for transferring USDC (USD Coin) using the uAgents library. The system consists of three agents: transaction_initiator
, transaction_processor
, and verification_agent
. These agents work together to initiate, process, and verify a USDC transaction on the Ethereum blockchain.
Get the Infura API Key and Endpoints
- Sign Up : Create an account on Infura.
- Create a New API Key : After logging in, navigate to the dashboard and click on
Create a New API Key
- Access the URL : Once the API key is created, click on the API key to get endpoint URLs for different Ethereum networks.
- Select Network : Choose the Sepolia testnet to obtain the specific URL, which includes your API key (e.g.,
https://sepolia.infura.io/v3/{your-api-key}
).
Get the ABI JSON, Contract Address and Private Key.
- Visit the contract's Etherscan page (in this case, we are using USDC on the Sepolia testnet) Link (opens in a new tab).
- ABI JSON : Download the ABI JSON File.
- Get the Contract Address: You can also find the Contract Address at the top of the page
- Private key : Access your crypto wallet and retrieve the private key associated with your account. Ensure you keep this private key secure and confidential.
Agents Overview
1. transaction_initiator
- Role : Initiates the transaction process by sending a request to the
transaction_processor
. - Event : On startup, it triggers a transaction request.
- Handles : Responses from
transaction_processor
and sends verification requests toverification_agent
.
2. transaction_processor
- Role : Processes the transaction request by building, signing, and sending the USDC transfer.
- Handles : Requests from
transaction_initiator
and responds with the transaction receipt.
3. verification_agent
- Role : Verifies the transaction status using the transaction hash.
- Handles : Verification requests from
transaction_initiator
and stores the verification receipt.
Workflow
1. Transaction Initiation:
transaction_initiator
starts up and sends aTransactionRequest
to thetransaction_processor
to initiate a USDC transfer.
2. Transaction Processing:
transaction_processor
receives the request, builds and signs the transaction using the sender's private key, and sends the transaction to the blockchain. After receiving the transaction receipt, it sends aTransactionResponse
back to thetransaction_initiator
.
3. Transaction Verification:
- Upon receiving the transaction receipt,
transaction_initiator
sends aVerificationRequest
to theverification_agent
. Theverification_agent
checks the transaction status on the blockchain and logs the result.
transaction.pyfrom uagents import Agent, Bureau, Context, Model from web3 import Web3 from dotenv import load_dotenv from typing import Optional, Dict import os import json load_dotenv() provider_url = os.getenv("WEB3_PROVIDER_URL") sender_address = os.getenv("SENDER_ADDRESS") receiver_address = os.getenv("RECEIVER_ADDRESS") sender_private_key = os.getenv("SENDER_PRIVATE_KEY") usdc_contract_address = os.getenv("USDC_CONTRACT_ADDRESS") amount = os.getenv("AMOUNT") class TransactionRequest(Model): message: str action: str transaction_hash: Optional[str] = None class TransactionResponse(Model): message: str balance: Optional[int] = 0 transaction_receipt: Optional[Dict] = None class VerificationRequest(Model): transaction_hash: str class VerificationResponse(Model): status: str receipt: Optional[Dict] = None with open("abi.json") as abi_file: proxy_abi = json.load(abi_file) w3 = Web3(Web3.HTTPProvider(provider_url)) proxy_contract = w3.eth.contract(address=usdc_contract_address, abi=proxy_abi) transaction_initiator = Agent( name="transaction_initiator", seed="transaction_initiator recovery phrase" ) transaction_processor = Agent( name="transaction_processor", seed="transaction_processor recovery phrase" ) verification_agent = Agent( name="verification_agent", seed="verification_agent recovery phrase" ) @transaction_initiator.on_event("startup") async def initiate_transaction(ctx: Context): """ Triggered on startup. Sends a transaction request to the transaction processor to initiate a USDC transfer. """ ctx.logger.info(f"*** Transaction Initiator startup event triggered. ***") await ctx.send( transaction_processor.address, TransactionRequest(message="Requesting transaction", action="send_usdc"), ) @transaction_initiator.on_message( model=TransactionResponse, replies=VerificationRequest ) async def transaction_initiator_response_handler( ctx: Context, sender: str, msg: TransactionResponse ): """ Handles the transaction response. Logs the transaction receipt and sends a verification request if successful. """ ctx.logger.info(f"*** Received response from {sender}: {msg.message} ***") if msg.transaction_receipt is not None: ctx.logger.info( f"*** USDC Transaction successful: {msg.transaction_receipt} ***" ) transaction_hash = msg.transaction_receipt["transactionHash"] print(transaction_hash) await ctx.send( verification_agent.address, VerificationRequest(transaction_hash=transaction_hash), ) @transaction_processor.on_message(model=TransactionRequest, replies=TransactionResponse) async def transaction_processor_request_handler( ctx: Context, sender: str, msg: TransactionRequest ): """ Processes the transaction request. Builds, signs, and sends a USDC transfer transaction, then sends the receipt back. """ ctx.logger.info( f"*** Transaction Processor received request from {sender}: {msg.message} with action {msg.action} ***" ) if msg.action == "send_usdc": ctx.logger.info( f"*** Preparing to send USDC from {sender_address} to {receiver_address} ***" ) sender_account = w3.eth.account.from_key(sender_private_key) gas_price = w3.eth.gas_price usdc_amount = w3.to_wei(amount, "mwei") transaction = proxy_contract.functions.transfer( receiver_address, usdc_amount ).build_transaction( { "chainId": w3.eth.chain_id, "nonce": w3.eth.get_transaction_count(sender_address), "gas": 600000, "gasPrice": gas_price, } ) signed_txn = sender_account.sign_transaction(transaction) raw_transaction = signed_txn.raw_transaction tx_hash = w3.eth.send_raw_transaction(raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash) receipt_dict = { "transactionHash": receipt.transactionHash.hex(), "transactionIndex": receipt.transactionIndex, "blockNumber": receipt.blockNumber, "blockHash": receipt.blockHash.hex(), "cumulativeGasUsed": receipt.cumulativeGasUsed, "gasUsed": receipt.gasUsed, "status": receipt.status, } ctx.logger.info( f"*** USDC Transaction complete. Receipt details: {receipt_dict} ***" ) await ctx.send( sender, TransactionResponse( message="USDC Transaction complete", transaction_receipt=receipt_dict ), ) @verification_agent.on_message(model=VerificationRequest, replies=VerificationResponse) async def verification_request_handler( ctx: Context, sender: str, msg: VerificationRequest ): """ Verifies the transaction using the provided hash. Logs the status and stores the receipt in the agent's context. """ ctx.logger.info( f"*** Verification Agent received request: {msg.transaction_hash} ***" ) try: receipt = w3.eth.get_transaction_receipt(msg.transaction_hash) if receipt: status = "Success" if receipt.status == 1 else "Failure" receipt_dict = { "transactionHash": receipt.transactionHash.hex(), "transactionIndex": receipt.transactionIndex, "blockNumber": receipt.blockNumber, "blockHash": receipt.blockHash.hex(), "cumulativeGasUsed": receipt.cumulativeGasUsed, "gasUsed": receipt.gasUsed, "status": status, } ctx.logger.info(f"*** Transaction Verification: {status} ***") ctx.storage.set("receipt", receipt_dict) else: ctx.logger.info(f"*** Transaction not found: {msg.transaction_hash} ***") except Exception as e: ctx.logger.error(f"*** Error during verification: {e} ***") bureau = Bureau() bureau.add(transaction_initiator) bureau.add(transaction_processor) bureau.add(verification_agent) if __name__ == "__main__": bureau.run()
Poetry Dependencies
pyproject.toml[tool.poetry.dependencies] python = "^3.10" web3 = "^7.0.0" python-dotenv = "^1.0.1" uagents = { version = "^0.13.0", python = ">=3.10,<3.13" }
How to Run This Example
Update the Required environment variables
.env.exampleWEB3_PROVIDER_URL= SENDER_ADDRESS= RECEIVER_ADDRESS= SENDER_PRIVATE_KEY= USDC_CONTRACT_ADDRESS= AMOUNT=
Instructions to execute the example.
- Navigate to the root folder of the example.
- Update the
.env
file. - Install dependencies by running
poetry install
. - Execute the script with python transaction.py.
Expected Output
INFO: [transaction_initiator]: *** Transaction Initiator startup event triggered. *** INFO: [transaction_processor]: *** Transaction Processor received request from agent1qg4qyv4xtnh9v9ct8uk6yhgllr4sj47uryc5hzk76wqr5nt6vgjtvyfhfjt: Requesting transaction with action send_usdc *** INFO: [transaction_processor]: *** Preparing to send USDC from 0x6E45CaC31883cff53d5Dbc0Fdc2b73b5aa7e1157 to 0xd0385eac66580C189cd721cD09FB671B0840Cac2 *** INFO: [transaction_processor]: *** USDC Transaction complete. Receipt details: {'transactionHash': '0596f4c91aa7fd88b8eab59f705fba4c8ba1530b3e5767f6bde969eee0d75b40', 'transactionIndex': 6, 'blockNumber': 6598943, 'blockHash': '3d18bac50491aa6b65711304a7cd6a21de2e90f10a132ac422917fa5691a2363', 'cumulativeGasUsed': 524654, 'gasUsed': 45059, 'status': 1} *** INFO: [transaction_initiator]: *** Received response from agent1qg874jqdg7kfh0d3g7l049a7q5jecv3wkmy4rkqky3fxl60824dq2g4rdga: USDC Transaction complete *** INFO: [transaction_initiator]: *** USDC Transaction successful: {'transactionHash': '0596f4c91aa7fd88b8eab59f705fba4c8ba1530b3e5767f6bde969eee0d75b40', 'transactionIndex': 6, 'blockNumber': 6598943, 'blockHash': '3d18bac50491aa6b65711304a7cd6a21de2e90f10a132ac422917fa5691a2363', 'cumulativeGasUsed': 524654, 'gasUsed': 45059, 'status': 1} *** 0596f4c91aa7fd88b8eab59f705fba4c8ba1530b3e5767f6bde969eee0d75b40 INFO: [verification_agent]: *** Verification Agent received request: 0596f4c91aa7fd88b8eab59f705fba4c8ba1530b3e5767f6bde969eee0d75b40 *** INFO: [verification_agent]: *** Transaction Verification: Success *** INFO: [bureau]: Starting server on http://0.0.0.0:8000 (Press CTRL+C to quit)