How to use agents to send tokens
Introduction
Within agent-based decentralized systems, efficient communication and secure data exchange are essential. In this guide, we will walk you through the process of setting up two AI Agents utilizing the uagents
library to establish a dynamic workflow where one agent periodically sends payment requests to another, which in turn processes these requests, executes transactions, and provides transaction information back to sending agent.
Let's get started!
Prerequisites
Make sure you have read the following resources before going on with this guide:
- Quick Start Guide for uAgents Framework
- Creating your first agent
- Agents address
- Using agents storage function
- Communicating with other agents
Imports needed
Walk-through
-
First of all, create a Python script for this task, and name it:
windowsecho. > sending_tokens.py
-
Then, import the necessary modules from
uagents
,uagents.network
, anduagents.setup
. Let's then define two data models:PaymentRequest
andTransactionInfo
. We then need to set up the values for theAMOUNT
andDENOM
variables, which define the default amount and denomination for the payment requests:
sending_tokens.pyfrom uagents import Agent, Bureau, Context, Model from uagents.network import wait_for_tx_to_complete from uagents.setup import fund_agent_if_low class PaymentRequest(Model): wallet_address: str amount: int denom: str class TransactionInfo(Model): tx_hash: str AMOUNT = 100 DENOM = "atestfet"
-
The
PaymentRequest
model represents a payment request which contains thewallet_address
,amount
, anddenom
. -
The
TransactionInfo
model represents information about a transaction and contains a single attribute,tx_hash
.
- Let's now define our agents,
alice
andbob
. Ensure they have enough funds in their wallets to carry out transactions:
sending_tokens.pyalice = Agent(name="alice", seed="alice secret phrase") bob = Agent(name="bob", seed="bob secret phrase") fund_agent_if_low(bob.wallet.address(), min_balance=AMOUNT)
- We can now define our agents behaviour and functions:
sending_tokens.py@alice.on_interval(period=10.0) async def request_funds(ctx: Context): await ctx.send( bob.address, PaymentRequest( wallet_address=str(alice.wallet.address()), amount=AMOUNT, denom=DENOM ), )
This defines an event handler for alice
using the .on_interval()
decorator. This event handler is triggered at regular intervals of 10.0
seconds in this case. The event handler function is named request_funds()
and takes a ctx
parameter of type Context
. Within the function, alice
sends a payment request message to bob
by using the ctx.send()
method. The ctx.send()
method is called with the recipient address, bob.address
, which specifies that the message should be sent to bob
. The message is an instance of the PaymentRequest()
model. It contains alice
's wallet address (alice.wallet.address()
), the amount (AMOUNT
), and the denomination (DENOM
).
- We can now define a
confirm_transaction()
message handler foralice
to handle incoming messages frombob
of typeTransactionInfo
:
sending_tokens.py@alice.on_message(model=TransactionInfo) async def confirm_transaction(ctx: Context, sender: str, msg: TransactionInfo): ctx.logger.info(f"Received transaction info from {sender}: {msg}") tx_resp = await wait_for_tx_to_complete(msg.tx_hash, ctx.ledger) coin_received = tx_resp.events["coin_received"] if ( coin_received["receiver"] == str(alice.wallet.address()) and coin_received["amount"] == f"{AMOUNT}{DENOM}" ): ctx.logger.info(f"Transaction was successful: {coin_received}")
The event handler function is named confirm_transaction()
and takes three parameters: ctx
, sender
, and msg
. Within the function, alice
logs an informational message using the ctx.logger.info()
method, indicating the receipt of transaction information from the sender agent, bob
, and displaying the msg
object. The wait_for_tx_to_complete()
function is used to await the completion of the transaction specified by the tx_hash
received in the message.
Once the transaction is completed, the code accesses the coin_received
event from the transaction response using tx_resp.events[\"coin_received\"]
. It checks if the receiver address matches alice
's wallet address (alice.wallet.address()
) and if the amount received matches the expected amount (AMOUNT + DENOM
).
If the conditions are met, alice
logs another informational message indicating the success of the transaction and displaying the details of the received coins.
- Let's now define an event handler for
bob
. This event handler is triggered whenbob
receives a message of typePaymentRequest
fromalice
:
sending_tokens.py@bob.on_message(model=PaymentRequest, replies=TransactionInfo) async def send_payment(ctx: Context, sender: str, msg: PaymentRequest): ctx.logger.info(f"Received payment request from {sender}: {msg}") # send the payment transaction = ctx.ledger.send_tokens( msg.wallet_address, msg.amount, msg.denom, bob.wallet ) # send the tx hash so alice can confirm await ctx.send(alice.address, TransactionInfo(tx_hash=transaction.tx_hash))
The event handler function is named send_payment()
and takes three parameters: ctx
, sender
, and msg
. Within the function, bob
logs an informational message using the ctx.logger.info()
method, indicating the receipt of a payment request from the sender agent, bob
, and displaying the msg
object.
Next, the code performs a payment transaction using the ctx.ledger.send_tokens()
method. It takes the wallet address (msg.wallet_address
), amount (msg.amount
), denomination (msg.denom
), and bob.wallet()
as parameters. This method is responsible for sending the requested payment.
Once the transaction is completed, bob
sends a message back to alice
to inform her about the transaction, using ctx.send()
. The message is created using the TransactionInfo
model with the tx_hash
obtained from the transaction response. The ctx.send()
method is used to send this message to alice using her address (alice.address
).
- We are now ready to use the Bureau class to create a
bureau
object and add both uAgents to it so for them to be run together:
sending_tokens.pybureau = Bureau() bureau.add(alice) bureau.add(bob) if __name__ == "__main__": bureau.run()
The overall script for this example should look as follows:
sending_tokens.pyfrom uagents import Agent, Bureau, Context, Model from uagents.network import wait_for_tx_to_complete from uagents.setup import fund_agent_if_low class PaymentRequest(Model): wallet_address: str amount: int denom: str class TransactionInfo(Model): tx_hash: str AMOUNT = 100 DENOM = "atestfet" alice = Agent(name="alice", seed="alice secret phrase") bob = Agent(name="bob", seed="bob secret phrase") fund_agent_if_low(bob.wallet.address(), min_balance=AMOUNT) @alice.on_interval(period=10.0) async def request_funds(ctx: Context): await ctx.send( bob.address, PaymentRequest( wallet_address=str(alice.wallet.address()), amount=AMOUNT, denom=DENOM ), ) @alice.on_message(model=TransactionInfo) async def confirm_transaction(ctx: Context, sender: str, msg: TransactionInfo): ctx.logger.info(f"Received transaction info from {sender}: {msg}") tx_resp = await wait_for_tx_to_complete(msg.tx_hash, ctx.ledger) coin_received = tx_resp.events["coin_received"] if ( coin_received["receiver"] == str(alice.wallet.address()) and coin_received["amount"] == f"{AMOUNT}{DENOM}" ): ctx.logger.info(f"Transaction was successful: {coin_received}") @bob.on_message(model=PaymentRequest, replies=TransactionInfo) async def send_payment(ctx: Context, sender: str, msg: PaymentRequest): ctx.logger.info(f"Received payment request from {sender}: {msg}") # send the payment transaction = ctx.ledger.send_tokens( msg.wallet_address, msg.amount, msg.denom, bob.wallet ) # send the tx hash so alice can confirm await ctx.send(alice.address, TransactionInfo(tx_hash=transaction.tx_hash)) bureau = Bureau() bureau.add(alice) bureau.add(bob) if __name__ == "__main__": bureau.run()
Run the script
On your terminal, make sure to have activated the virtual environment.
Run the script: python sending_tokens.py
The output should be as follows:
INFO: [bureau]: Starting server on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: [ bob]: Received payment request from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: wallet_address='fetch1967p3vkp0yngdfturv4ypq2p4g760ml705wcxy' amount=100 denom='atestfet' INFO: [alice]: Received transaction info from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: tx_hash='DB662CCF415C7B0C9A02928967BE1817506D02A041AA05CA48DCE5CF87D5A638' INFO: [alice]: Transaction was successful: {'receiver': 'fetch1967p3vkp0yngdfturv4ypq2p4g760ml705wcxy', 'amount': '100atestfet'}