How to use uAgents to send tokens ππΈ
Introduction
In the world of 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 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.
Walk-through
-
First of all, create a Python script for this task, and name it:
touch sending_tokens.py
-
Then, import the necessary modules from
uagents
,uagents.network
, anduagents.setup
. Let's then define two data models:PaymentRequest
andTransactionInfo
. Then, set up the values for theAMOUNT
andDENOM
variables, which define the default amount and denomination for the payment requests:from 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 define our uAgents,
alice
andbob
. Ensure they have enough funds in their wallets to carry out transactions.alice = Agent(name="alice", seed="alice secret phrase") bob = Agent(name="bob", seed="bob secret phrase") fund_agent_if_low(alice.wallet.address()) fund_agent_if_low(bob.wallet.address())
-
We can now define our uAgents functions.
@alice.on_interval(period=10.0) async def request_funds(ctx: Context): await ctx.send( bob.address, PaymentRequest( wallet_address=str(ctx.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 of10.0
seconds in this case. The event handler function is namedrequest_funds()
and takes actx
parameter of typeContext
. Within the function,alice
sends a payment request message tobob
by using thectx.send()
method. Thectx.send()
method is called with the recipient address,bob.address
, which specifies that the message should be sent tobob
. The message is an instance of thePaymentRequest()
model. It containsalice
's wallet address (ctx.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
:@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) coin_received = tx_resp.events["coin_received"] if ( coin_received["receiver"] == str(ctx.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
, andmsg
. Within the function,alice
logs an informational message using thectx.logger.info()
method, indicating the receipt of transaction information from the sender agent,bob
, and displaying themsg
object. Thewait_for_tx_to_complete()
function is used to await the completion of the transaction specified by thetx_hash
received in the message.Once the transaction is completed, the code accesses the
coin_received
event from the transaction response usingtx_resp.events[\"coin_received\"]
. It checks if the receiver address matchesalice
's wallet address (ctx.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
:@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}") transaction = ctx.ledger.send_tokens( msg.wallet_address, msg.amount, msg.denom, ctx.wallet ) 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
, andmsg
. Within the function,bob
logs an informational message using thectx.logger.info()
method, indicating the receipt of a payment request from the sender agent,bob
, and displaying themsg
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
), andctx.wallet()
as parameters. This method is responsible for sending the requested payment.Once the transaction is completed,
bob
sends a message back toalice
to inform her about the transaction, usingctx.send()
. The message is created using theTransactionInfo
model with thetx_hash
obtained from the transaction response. Thectx.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.bureau = Bureau() bureau.add(alice) bureau.add(bob) if __name__ == "__main__": bureau.run()
The overall code for this example should look as follows:
from 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(alice.wallet.address())
fund_agent_if_low(bob.wallet.address())
@alice.on_interval(period=10.0)
async def request_funds(ctx: Context):
await ctx.send(
bob.address,
PaymentRequest(
wallet_address=str(ctx.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)
coin_received = tx_resp.events["coin_received"]
if (
coin_received["receiver"] == str(ctx.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}")
transaction = ctx.ledger.send_tokens(
msg.wallet_address, msg.amount, msg.denom, ctx.wallet
)
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:
[ bob]: Received payment request from agent1qdp9j2ev86k3h5acaayjm8tpx36zv4mjxn05pa2kwesspstzj697xy5vk2a: wallet_address='fetch1967p3vkp0yngdfturv4ypq2p4g760ml705wcxy' amount=100 denom='atestfet'
[alice]: Received transaction info from agent1q2kxet3vh0scsf0sm7y2erzz33cve6tv5uk63x64upw5g68kr0chkv7hw50: tx_hash='DB662CCF415C7B0C9A02928967BE1817506D02A041AA05CA48DCE5CF87D5A638'
[alice]: Transaction was successful: {'receiver': 'fetch1967p3vkp0yngdfturv4ypq2p4g760ml705wcxy', 'amount': '100atestfet'}