Circle Internet Financial
Circle Internet Financial Logo

Nov 06, 2024

November 6, 2024

Enabling AI Agents with Blockchain

what you’ll learn

We demonstrate how you can leverage AI agents to operate and make autonomous payments using USDC. Read our walkthrough to learn more.

Enabling AI Agents with Blockchain

Introduction

In this article, we will explore how the intersection of AI and blockchain technology is poised to transform our understanding of work and finance, particularly through the use of autonomous payments.

We'll introduce a concept that enables AI agents to operate and transact independently using digital dollars—USDC. By adding financial features, these agents can handle tasks fully and work independently. We'll give a simple example of how to combine advanced AI with secure, automated payments and blockchain technology to create this system, using the Circle Developer Platform for payment processing. You can sign up here.

We will use AutoGen's AI framework and the Circle platform to create a network of AI agents that work together on complex tasks and get paid directly with USDC, showing how automated payments can change how we do tasks and exchange value online. We have also built a sample application to demonstrate the autonomous system. We encourage you to use this template to build your own project and contribute back Circle’s sample applications.

The benefits of AI generated income

Obviously, the AI agents don’t "own" or "spend" this compensation in a traditional sense. But this serves as a proof of concept for several futuristic scenarios:

  • Autonomous AI Services: In the future, individuals or businesses may own custom AI agents that perform specific tasks or provide services over the internet. These agents can autonomously interact with other AI agents and manage business processes, generating passive income for their owners. Notably, when payment is initiated by another AI agent, traditional payment methods like credit cards won't suffice. Instead, transactions between AI agents will need to occur through digital assets, with USDC being the preferred option.
  • AI Marketplaces: A digital marketplace could allow people to rent or commission AI agents for various tasks, with earnings reinvested in AI capabilities and distributed through autonomous payments.
  • AI Research Funding: In academic or corporate settings, this system could be used to allocate research funds based on the contributions of different AI models or systems.
  • Human-AI Collaboration: Humans could work alongside AI agents in various fields, with compensation distributed based on the contributions of both human and AI team members, potentially utilizing autonomous payments.

In this setup, the "assets" earned by AI agents quantifies their performance and contributions. In a real-world application, this could translate to:

  • Resources for further AI development or training
  • Credits for human users to access AI services
  • Financial returns for individuals or organizations that develop and deploy these AI agents

Using Circle's Programmable Wallets and USDC on a testnet blockchain, we simulate these transactions to safely demonstrate the potential of combining AI with blockchain technology—creating new paradigms of value creation and distribution in the digital age through autonomous payments.

The image below is what we’d achieve at the end of the tutorial where various AI agents work together to complete a task and get compensated in USDC sent to their respective wallets for the work they have delivered.

Ready? Let’s dive in.

Understanding AI Agents and Their Roles

At the core of our system are AI agents powered by AutoGen, an open-source programming framework designed for building and orchestrating multiple AI agents for complex task solving. These agents collaborate to complete a research task and are subsequently compensated with USDC, a digital representation of US dollars on the blockchain. 

Here's a breakdown of each agent's role and how each contributes to the task:

  1. Admin (User Proxy): Representing the human user, this agent initiates the research task and approves the final plan. It serves as a bridge between human oversight and the automated workflow, showcasing AutoGen's ability to integrate human decision-making into AI-driven processes.
  2. Planner: Responsible for creating and revising the research plan, the Planner agent incorporates feedback from other agents to refine the approach. AutoGen's architecture enables this agent to engage in complex, multi-step reasoning processes, adapting the plan as new information emerges.
  3. Engineer: This agent writes code to address specific tasks within the research process. By leveraging AutoGen's code generation and execution capabilities, the Engineer seamlessly transitions between natural language understanding and programming tasks, enhancing the system's problem-solving versatility.
  4. Scientist: Tasked with categorizing research papers and analyzing scientific information, the Scientist agent represents AutoGen's capacity for domain-specific reasoning. It applies specialized knowledge to interpret and synthesize complex scientific data.
  5. Executor: Running the code produced by the Engineer as it takes input messages from the Engineer (e.g., those containing code blocks), performs execution in the command line, and outputs messages with the results.
  6. Critic: Reviewing the work of other agents, providing feedback to the human host, and ensuring quality, the Critic agent plays a crucial role in maintaining high standards. With AutoGen's architecture that supports this self-correction mechanism, we enable continuous improvement and quality assurance within the multi-agent system.

After completing their respective tasks, each agent is compensated with USDC. This payment process is facilitated through Programmable Wallets, which allow for precise, programmable transactions on the blockchain. The compensation amount for each agent is determined based on their contribution to the overall task, as assessed by the Admin agent or predefined criteria.

For instance, the Planner might receive a base payment for developing the initial research plan, with additional compensation for each successful revision. The Engineer could be paid based on the complexity and effectiveness of the code produced, while the Scientist's compensation might be tied to the accuracy and depth of their analysis. The Executor's payment could be linked to the successful implementation of solutions, and the Critic might receive compensation based on the quality and impact of their feedback.

Code Walkthrough

In the following sections, we'll dive deeper into the practical implementation of this system, guiding you through each step of setting up the AI research process and the subsequent payment mechanism with developer-controlled Programmable Wallets, a part of the Circle platform

Here’s a comprehensive video walkthrough for those who prefer a visual guide.

YouTube video thumbnail

Python and NodeJS Environment Setup

Our project uses Python for AI research and Node.js for blockchain interactions, specifically leveraging Programmable Wallets. This dual-language approach allows us to create a system where AI agents can perform complex research tasks and receive compensation through USDC payments.

Python Powers AI Research:

We use Python for its robust AI and machine learning capabilities. The AutoGen library, which we use to create and manage our AI agents, is Python-based. Python's simplicity and extensive data processing libraries make it ideal for handling the complex, language-model-driven research tasks at the core of our project.

Node.js Enables Blockchain Integration:

Once the AI agents have completed their research tasks and their work has been verified, our Node.js application interacts with Programmable Wallets to process payments. This setup allows for a seamless flow from task completion to compensation, all managed programmatically without requiring the AI agents themselves to handle any aspect of the blockchain transactions.

Environment Setup:

a. Installing Python and Node.js:

  • Install Python 3.9 or later from python.org
  • Install Node.js v20.17.0 or later from nodejs.org
  • Get your API Keys from Circle’s Developer Console

b. Setting up virtual environments:

For Python:

python -m venv ai_agents_env

source ai_agents_env/bin/activate # On Windows, 
use `ai_agents_env\Scripts\activate`

For Node.js:

mkdir ai_agents_project
cd ai_agents_project
npm init -y

c. Installing required packages:

For Python:

pip install autogen python-dotenv

For Node.js (Circle's Developer-Controlled Wallets SDK):

d. File structure:

Create the following file structure in your project directory:

ai_agents_project/
├── ai_research.py
├── circle_payment.js
├── main.py
├── .env
└── OAI_CONFIG_LIST

e. Environment variables:

Create a .env file in your project root with the following contents:

OPENAI_API_KEY=your_openai_api_key
CIRCLE_API_KEY=your_circle_api_key
CIRCLE_ENTITY_SECRET=your_circle_entity_secret

Replace the placeholder values with your actual API keys. The CIRCLE_API_KEY and CIRCLE_ENTITY_SECRET are crucial for interacting with Programmable Wallets.

f. OpenAI Configuration:

In AutoGen, agents use LLMs as key components to understand and react. To configure an agent’s access to LLMs, we create an OAI_CONFIG_LIST file with an OpenAI API configuration:

[
    {
        "model": "gpt-4o",
        "api_key": "your_openai_api_key"
    }
]

g. Circle Console Account Setup:

  • Create a free Circle Console account.
  • Generate an API Key to use in your requests to Circle APIs.
  • Create an entity secret for use with the Developer-Controlled Programmable Wallets SDK.

h. Version Control (optional but recommended):

Initialize a git repository and create a .gitignore file:

git init
echo ".env" >> .gitignore
echo "ai_agents_env/" >> .gitignore
echo "node_modules/" >> .gitignore

Agent Setup

Now let's set up our AI agents, which form the core of our research system. The ai_research.py file contains the configuration and initialization of these agents using the AutoGen framework. 

Let's break down each component in detail:

1. Importing Dependencies:

import autogen
import json

AutoGen allows us to create a team of specialized AI agents that can collaborate on complex tasks. The json module will be useful for handling configuration files and serializing results.

2. Loading Configuration:

config_list = autogen.config_list_from_json("OAI_CONFIG_LIST")

gpt4_config = {
    "cache_seed": 42,
    "temperature": 0,
    "config_list": config_list,
    "timeout": 120,
}

Here, we're loading our OpenAI API configurations from a JSON file and setting up the parameters for our GPT-4 model. The cache_seed ensures reproducibility, while the temperature of 0 maximizes determinism in the model's outputs. The timeout parameter sets a limit on how long each API call can take.

3. Creating AI Agents:

Now, we'll create our team of specialized AI agents. Each agent has a specific role in the research process:

user_proxy = autogen.UserProxyAgent(
    name="Admin",
    system_message="A human admin. Interact with the planner to discuss the plan. Plan execution needs to be approved by this admin.",
    code_execution_config=False,
)

The Admin agent acts as a proxy for human oversight in our system. It's responsible for approving plans and providing high-level direction to the other agents.

engineer = autogen.AssistantAgent(
    name="Engineer",
    llm_config=gpt4_config,
    system_message="""Engineer. You follow an approved plan. You write python/shell code to solve tasks. Wrap the code in a code block that specifies the script type. The user can't modify your code. So do not suggest incomplete code which requires others to modify. Don't use a code block if it's not intended to be executed by the executor.
Don't include multiple code blocks in one response. Do not ask others to copy and paste the result. Check the execution result returned by the executor.
If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.""",
)

The Engineer agent is responsible for writing code to solve specific tasks within the research process. It uses the GPT-4 configuration we defined earlier, allowing it to generate complex code solutions.

scientist = autogen.AssistantAgent(
   name="Scientist", llm_config=gpt4_config,
   system_message="""Scientist. You follow an approved plan. You are able
to categorize papers after seeing their abstracts printed. You don't
write code.""",
)

The Scientist agent is our domain expert. It's tasked with analyzing research papers and providing scientific insights. This agent doesn't write code but instead focuses on interpreting and categorizing scientific information.

scientist = autogen.AssistantAgent(
    name="Scientist",
    llm_config=gpt4_config,
    system_message="""Scientist. You follow an approved plan. You are able to categorize papers after seeing their abstracts printed. You don't write code.""",
)

The Planner agent is crucial for organizing the research process. It creates and refines plans based on feedback from other agents, ensuring a structured approach to our research tasks.

planner = autogen.AssistantAgent(
    name="Planner",
    system_message="""Planner. Suggest a plan. Revise the plan based on feedback from admin and critic, until admin approval.
The plan may involve an engineer who can write code and a scientist who doesn't write code.
Explain the plan first. Be clear which step is performed by an engineer, and which step is performed by a scientist.
""",

    llm_config=gpt4_config,
)

The Executor agent is responsible for running the code produced by the Engineer. It's set up as a UserProxyAgent with code execution capabilities, allowing it to interact with the system environment. The human_input_mode is set to "NEVER" to ensure fully automated operation.

executor = autogen.UserProxyAgent(
    name="Executor",
    system_message="Executor. Execute the code written by the engineer and report the result.",
    human_input_mode="NEVER",
    code_execution_config={
        "last_n_messages": 3,
        "work_dir": "paper",
        "use_docker": False,
    },
)

The Critic agent plays a vital role in quality assurance. It reviews the work of other agents, providing feedback and ensuring the research output meets high standards.

4. Setting Up Group Chat:

groupchat = autogen.GroupChat(
    agents=[user_proxy, engineer, scientist, planner, executor, critic],
    messages=[],
    max_round=50
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=gpt4_config)

Here, we're creating a virtual meeting room (GroupChat) where our agents can interact. The GroupChatManager oversees this interaction, ensuring smooth communication and collaboration between agents.

5. Defining the Research Function:

def run_research(topic):
    initial_message = f"Research the following topic, check the arxiv website for interesting papers to support your result and create a markdown table of different domains: {topic}"
    user_proxy.initiate_chat(manager, message=initial_message)
    
    # For simplicity, we'll assume all agents contributed equally
    return {
        "Engineer": 1,
        "Scientist": 1,
        "Planner": 1,
        "Executor": 1,
        "Critic": 1
    }

This function kickstarts our research process. It formulates an initial message based on the given topic and uses the Admin agent to initiate the group chat. The agents then collaborate to research the topic, with each playing its specialized role.

6. Main Execution Block:

if __name__ == "__main__":
    topic = "Artificial intelligence and employment trends"
    contributions = run_research(topic)
    print(json.dumps(contributions))

This block runs when the script is executed directly. It:

  • Sets the research topic to "Artificial intelligence and employment trends".
  • Calls the run_research function with this topic, initiating the multi-agent research process.
  • Prints the contributions of each agent as a JSON-formatted string.

This allows us to test our research system by running the script standalone, simulating the complete research task on AI and employment trends.

Programmable Wallets Setup for Paying AI Agents

Now that we have the agents set up with assignments  the next step is to set up the system that pays them for verifiable work. We are going to use Programmable Wallets to pay each AI agent the amount of 1 USDC for completed research tasks. Each AI agent will have their own developer controlled programmable wallets on the blockchain - Solana Devnet. The Developer-controlled wallet is ideal for applications where developers handle more of the blockchain interactions by not requiring a user to authenticate all of the transactions, especially those unfamiliar with blockchain intricacies. With this we can retain control over the AI agents' wallets. To learn more about developer-controlled wallets, check out our docs.

The following explains the steps of the circle_payment.js file that contains code, and logging functionality to make payment to each AI agent.

1. Importing Dependencies and Initializing the Client:

const { initiateDeveloperControlledWalletsClient } = require("@circle-fin/developer-controlled-wallets");
require("dotenv").config();

const client = initiateDeveloperControlledWalletsClient({
  apiKey: process.env.CIRCLE_API_KEY,
  entitySecret: process.env.CIRCLE_ENTITY_SECRET,
});

This section initializes the client using the Developer-Controlled Wallets SDK. The client is configured with an API key and entity secret that are important for secure interactions with Programmable Wallets.

2. Creating a Wallet Set, and Wallets

After importing the dependencies the next step is to create walletset and wallets for each AI agent. The function below creates a wallet set, which is a grouping of wallets tied together by a single cryptographic key.

/**
 * Creates a new WalletSet.
 * @param name - The name of the WalletSet.
 * @returns The created WalletSet.
 * @throws Will throw an error if WalletSet creation fails.
 */
const createWalletSet = async (name) => {
  console.log("Creating WalletSet...");
  const response = await client.createWalletSet({ name });

  if (!response.data?.walletSet) {
    throw new Error("Failed to create WalletSet.");
  }

  console.log(
    `WalletSet "${name}" created with ID: ${response.data.walletSet.id}`
  );
  return response.data.walletSet;
};

Creating Wallets

This function creates individual wallets within a WalletSet. It specifies the blockchain(s) on which the wallets should be created and the number of wallets to create. These developer-controlled wallets allow our application to perform blockchain interactions on behalf of the AI agents.

/**
 * Creates wallets within the specified WalletSet.
 * @param walletSetId - The ID of the WalletSet.
 * @param blockchains - Array of blockchain identifiers.
 * @param count - Number of wallets to create.
 * @returns Array of created wallets.
 * @throws Will throw an error if wallet creation fails.
 */
const createWallets = async (walletSetId, blockchains, count) => {
  console.log("Creating wallets...");
  const response = await client.createWallets({
    blockchains,
    count,
    walletSetId,
  });

  if (!response.data?.wallets || response.data.wallets.length === 0) {
    throw new Error("Failed to create wallets.");
  }

  const addresses = response.data.wallets
    .map((wallet) => wallet.address)
    .join(", ");
  console.log(`Successfully created wallets: ${addresses}`);
  return response.data.wallets;
};

3. Requesting Testnet Tokens

We are going to use testnet tokens - USDC - to fund the admin wallet from which each agent will earn. Using the requestTestnetTokens method, the function requests testnet tokens (USDC).

const requestTestnetTokens = async (address, blockchain, usdc) => {
  console.log(`Requesting testnet tokens for address: ${address}...`);
  await client.requestTestnetTokens({
    address,
    blockchain,
    usdc,
  });
  console.log("Testnet tokens requested successfully.");
};

4. Checking Wallet Balance

In our payment system, we need to check wallet balances as it is important for verifying funds before transactions, confirming successful payments, and detecting any discrepancies. The checkBalance function uses the getWalletTokenBalance method to retrieve the current balance of a specified wallet. See how to implement it below.

/**
 * Checks the balance of a wallet.
 * @param walletId - The ID of the wallet to check.
 * @returns The wallet's token balance.
 * @throws Will throw an error if balance check fails.
 */
const checkBalance = async (walletId) => {
  console.log(`Checking balance for wallet ${walletId}...`);
  try {
    const response = await client.getWalletTokenBalance({ id: walletId });
    const walletTokenBalance = response?.data;
    console.log("Wallet balance:", JSON.stringify(walletTokenBalance, null, 2));
    return walletTokenBalance;
  } catch (error) {
    console.error("Error checking wallet balance:", error);
    throw error;
  }
};

5. Ensuring Sufficient Balance

The ensureSufficientBalance function is used right before we start processing individual payments to ensure our main wallet has enough funds to cover all transactions. 

Here's how it works:

  • We calculate the total required balance by summing up all the contributions.
  • We call ensureSufficientBalance with the main wallet's ID and the total required balance calculated in the previous step.
  • The function repeatedly checks the wallet's balance until it's sufficient or we reach a maximum number of attempts.
  • If successful, it returns the USDC token ID needed for transactions.

We use ensureSufficientBalance to:

  • Prevent payment failures due to insufficient funds.
  • Allow time for testnet tokens to be credited to the wallet because when testnet tokens are requested, there may be a delay before they appear in the wallet due to network latency or processing time on the blockchain. This function provides a way to wait and re-check the balance, ensuring that the funds are available before attempting any payments.
  • Ensure we have the correct token ID for all transactions.
/**
 * Ensures that the wallet has a sufficient USDC balance.
 * @param walletId - The ID of the wallet to check.
 * @param requiredBalance - The minimum required balance.
 * @param maxAttempts - Maximum number of balance check attempts.
 * @param interval - Interval between balance checks in milliseconds.
 * @returns The USDC token ID if sufficient balance is found.
 * @throws Will throw an error if sufficient balance is not found within the attempts.
 */
const ensureSufficientBalance = async (walletId, requiredBalance, maxAttempts = 20, interval = 15000) => {
  console.log(`Ensuring sufficient balance for wallet ${walletId}...`);
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const balance = await checkBalance(walletId);
    const usdcBalance = balance.tokenBalances.find(token => token.token.symbol === 'USDC');
    
    if (usdcBalance && parseFloat(usdcBalance.amount) >= requiredBalance) {
      console.log(`Sufficient USDC balance found: ${usdcBalance.amount}`);
      return usdcBalance.token.id;
    }
    
    console.log(`Attempt ${attempt}: Insufficient balance. Waiting before next check...`);
    await new Promise(resolve => setTimeout(resolve, interval));
  }
  
  throw new Error(`Failed to find sufficient USDC balance after ${maxAttempts} attempts.`);
};

6. Creating Transactions

The createTransaction function is used within the payment processing loop to initiate individual transfers to each agent. With createTransaction, we're able to execute the core action of our payment system: transferring the earned contributions to each AI agent's wallet.

Here's how it works:

  • For each agent, we call createTransaction with:some text
    • The earned contribution amount.
    • The main wallet's ID (source).
    • The agent's wallet address (destination).
    • The USDC token ID.
  • The function uses the Programmable Wallets API to create a new transaction with the details above.
  • It also sets a high fee level to prioritize the transaction.
  • If successful, it returns the transaction data, including the transaction ID.
const createTransaction = async (
  amount,
  walletId,
  destinationAddress,
  tokenId
) => {
  console.log("Creating transaction...");
  const transactionResponse = await client.createTransaction({
    amounts: [amount],
    walletId,
    destinationAddress,
    tokenId,
    fee: {
      type: "level",
      config: {
        feeLevel: "HIGH",
      },
    },
  });
  if (!transactionResponse.data) {
    throw new Error("Failed to create transaction.");
  }
  console.log(`Transaction initiated with ID: ${transactionResponse.data.id}`);
  return transactionResponse.data;
};

7. Waiting for Transaction Confirmation

The waitForTransactionConfirmation function is called immediately after creating a transaction for each agent to confirm that the transaction has been processed successfully.

Here's how it works:

It grabs the transaction ID from the createTransaction call and starts checking its status using the PW API. The function keeps checking until it sees the transaction is in a 'CONFIRMED' state or the process times out.

Why this function matters:

  • Ensures Completion: Blockchain transactions take time to process. This function ensures that all aspects of the transaction are successfully completed before any subsequent operations are executed. Additionally, it helps prevent race conditions, which can occur when multiple processes compete to access and modify the same data concurrently.
  • Enables Accurate Reporting: By waiting for confirmation, we can confidently say the payment was successful.

Improves Reliability: In case of network issues or temporary blockchain congestion, this function provides time for the transaction to go through.

/**
 * Waits until the specified transaction reaches the 'CONFIRMED' state.
 * @param transactionId - The ID of the transaction to monitor.
 * @returns The confirmed transaction object.
 * @throws Will throw an error if the transaction is not confirmed within the timeout.
 */
const waitForTransactionConfirmation = async (
  transactionId,
  timeout = 240000,
  interval = 200
) => {
  console.log(`Waiting for transaction ${transactionId} to be confirmed...`);
  const startTime = Date.now();

  while (Date.now() - startTime < timeout) {
    const transactionResponse = await client.getTransaction({
      id: transactionId,
    });

    const state = transactionResponse.data?.transaction?.state;
    if (state) {
      console.log(`Current transaction state: ${state}`);

      if (state === "CONFIRMED") {
        console.log("Transaction confirmed successfully.");
        return transactionResponse.data.transaction;
      }
    }

    await new Promise((resolve) => setTimeout(resolve, interval));
  }

  throw new Error(
    "Timeout: Transaction was not confirmed in the expected time."
  );
};

8. Validating Payments

The validatePayment function is the final check in our payment process for each agent. 

Here's how it works:

  1. It takes the agent's wallet ID and the expected payment amount.
  2. It calls checkBalance to get the current balance of the agent's wallet.
  3. It compares the USDC balance with the expected amount.
  4. It returns true if the balance is equal to or greater than the expected amount, false otherwise.

/**
 * Validates that the payment was received by the agent's wallet.
 * @param walletId - The ID of the agent's wallet.
 * @param expectedAmount - The expected payment amount.
 * @returns Boolean indicating if the payment was received.
 */
const validatePayment = async (walletId, expectedAmount) => {
  const balance = await checkBalance(walletId);
  const usdcBalance = balance.tokenBalances.find(token => token.token.symbol === 'USDC');
  
  if (usdcBalance && parseFloat(usdcBalance.amount) >= parseFloat(expectedAmount)) {
    console.log(`Payment validated for wallet ${walletId}. Expected: ${expectedAmount}, Actual: ${usdcBalance.amount}`);
    return true;
  } else {
    console.log(`Payment validation failed for wallet ${walletId}. Expected: ${expectedAmount}, Actual: ${usdcBalance?.amount || '0'}`);
    return false;
  }
};

9. Processing Payments

The processPayments function runs our entire payment workflow:

  1. Creates a WalletSet and individual wallets for each agent.
  2. Ensures the main wallet has sufficient balance.
  3. For each agent:some text
    • Creates a transaction.
    • Waits for transaction confirmation.
    • Validates the payment receipt.
  4. Handles errors and provides detailed logging throughout the process.
  5. Generates a comprehensive payment summary in the terminal.
/**
 * Main function to orchestrate wallet creation, token request, transaction processing for multiple agents.
 */
const processPayments = async (contributions) => {
  const results = { success: true, payments: {} };
  const paymentSummary = [];

  try {
    const walletSet = await createWalletSet("AI Agents Wallet Set");
    const mainWallet = (await createWallets(walletSet.id, ["SOL-DEVNET"], 1))[0];
    const agentWallets = await createWallets(walletSet.id, ["SOL-DEVNET"], Object.keys(contributions).length);

    await requestTestnetTokens(mainWallet.address, "SOL-DEVNET", true);

    const requiredBalance = Object.values(contributions).reduce((sum, value) => sum + value, 0);
    const tokenId = await ensureSufficientBalance(mainWallet.id, requiredBalance);

    for (const [agent, contribution] of Object.entries(contributions)) {
      const agentWallet = agentWallets[Object.keys(contributions).indexOf(agent)];
      const transaction = await createTransaction(
        contribution.toString(),
        mainWallet.id,
        agentWallet.address,
        tokenId
      );

      await waitForTransactionConfirmation(transaction.id);

      const balance = await checkBalance(agentWallet.id);
      const usdcBalance = balance.tokenBalances.find(token => token.token.symbol === 'USDC');

      paymentSummary.push({
        agent,
        balance: usdcBalance ? usdcBalance.amount : '0',
        address: agentWallet.address,
        blockchain: 'SOL-DEVNET'
      });
    }

    console.log("\n=== Payment Summary ===");
    console.table(paymentSummary);

  } catch (error) {
    results.success = false;
    results.error = error.message;
  }

  return results;
};

10. Reading Contributions and Processing Payments:

The last part of our script handles input and initiates the payment process:

  1. It sets up a data stream to read JSON input from stdin.
  2. Once all data is received, it parses the JSON into a contributions object.
  3. It then calls processPayments with these contributions.
  4. Finally, it logs the results of the payment process.
// Read contributions from stdin and process payments
let data = "";
process.stdin.on("data", (chunk) => {
  data += chunk;
});
process.stdin.on("end", async () => {
  const contributions = JSON.parse(data);
  const paymentResults = await processPayments(contributions);
  console.log(JSON.stringify(paymentResults));
});

Main File

The main.py script runs the entire AI research and payment process. It combines the AI research component with the payment processing system. This script integrates Python-based AI research with Node.js-based payment processing. It uses interprocess communication, passing JSON-serialized data via stdin to the Node.js script. By leveraging subprocess management and data serialization, it creates a cohesive workflow that combines the strengths of both languages and their respective libraries for AI and blockchain interactions.

How it works:

  • Imports necessary modules and functions:some text
    • json for data serialization.
    • subprocess for running the Node.js payment script.
    • run_research from the AI research module.
  • Defines a main() function that:some text
    • Initiates AI research on a specified topic.
    • Receives contributions data from the research process.
    • Converts contributions to JSON.
    • Runs the Circle payment script using subprocess.
    • Prints the results and any errors.
  • Executes the main() function when the script is run directly
import subprocess
import json
import re
from ai_research import run_research

def run_circle_payment(contributions):
    process = subprocess.Popen(['node', 'circle_payment.js'], 
                               stdin=subprocess.PIPE, 
                               stdout=subprocess.PIPE, 
                               stderr=subprocess.PIPE)
    stdout, stderr = process.communicate(input=json.dumps(contributions).encode())
    
    print("Raw stdout:", stdout.decode())
    print("Raw stderr:", stderr.decode())
    
    if process.returncode != 0:
        print(f"Error from payment service: {stderr.decode()}")
        return None
    
    try:
        # Extract JSON output using regex
        json_output = re.search(r'=== JSON_OUTPUT_START ===\n(.*)\n=== JSON_OUTPUT_END ===', stdout.decode(), re.DOTALL)
        if json_output:
            results = json.loads(json_output.group(1))
            if results['success']:
                print("All payments processed successfully.")
                for agent, payment in results['payments'].items():
                    print(f"Agent: {agent}")
                    print(f"  Transaction ID: {payment['transactionId']}")
                    print(f"  Amount: {payment['amount']}")
                    print(f"  Validated: {'Yes' if payment['validated'] else 'No'}")
            else:
                print(f"Payment processing failed: {results.get('error', 'Unknown error')}")
            return results
        else:
            print("Could not find JSON output in the response")
            return None
    except json.JSONDecodeError:
        print("Failed to parse JSON output from payment service")
        return None

if __name__ == "__main__":
    topic = "Artificial intelligence and employment trends"
    contributions = run_research(topic)
    
    print("Research completed. Processing payments...")
    payment_result = run_circle_payment(contributions)
    
    if payment_result and payment_result['success']:
        print("Payment process completed successfully.")
    else:
        print("Payment process failed.")

To run the entire project, use the following command on your terminal

python main.py

Results

For simplicity we will skip the other steps and show the Critic agent in action before payments with Programmable Wallets. After the Critic agent has reviewed the AI agents’ work, and the admin is satisfied with the results we can then proceed to initiate payments for each agent.

Payments. 

The image below shows the payment process where the wallets for each agent were created, and the transaction into the wallet began to take place.

After a few seconds in the table below we can confirm that each agent now has the amount of 1 USDC in their wallet with the associated address on the Solana devnet blockchain.

Conclusion and Next Steps

In this tutorial, we explored the dynamic intersection of AI agents and blockchain technology, specifically how to empower AI agents to conduct research tasks and facilitate autonomous payments using USDC. 

Here's a recap of the key takeaways and additional pathways for further exploration.

Key Takeaways

  1. Understanding AI Agents: We learned to create a network of specialized AI agents that collaborate effectively on complex tasks, each with distinct roles. This multi-agent structure demonstrates how AI can operate autonomously while still being guided by human oversight.
  1. Blockchain Transactions: We discussed the implementation of Programmable Wallets and their importance for secure and automated transactions. We emphasized how to manage wallet balances and ensure sufficient funds are available before executing payments.
  1. AI-Driven Autonomy: The integration of AI with blockchain presents new opportunities for income generation and task completion, highlighting how autonomous systems can redefine value exchange in the digital landscape.

Suggested Extensions

  1. Handling Unsatisfactory Results: Implement a feedback mechanism where the Critic agent can flag agents' work that doesn't meet quality standards. This could involve revising the tasks or suggesting improvements, creating an iterative process of refinement.
  1. Integrating Real-World Data Sources: Improve the capabilities of the Scientist agent by enabling it to fetch and analyze data from external APIs or databases. This will deepen the depth and accuracy of research findings.

  2. Allowing AI Agents to Spend USDC: Extend the functionality of AI agents to enable them to spend USDC from their wallets. This capability facilitates financial transactions between AI agents, allowing them to pay for services or data from one another, thus creating a dynamic marketplace of automated interactions. Integrating this function allows AI agents to actively participate in transactions, enhancing their autonomy and expanding their role within a broader digital economy.

Get Started Today

We encourage you to experiment with the code and concepts presented in this tutorial. Get started for free with a Circle Developer Account, play around with the configurations, and try implementing the suggested extensions. 

We invite you to fork the code using our sample app, and stay tuned for more AI and blockchain payment intersections to come. Share your findings, improvements, or questions with us on Discord!

Related posts

Circle Announces 2nd Cohort of USDC Grant Program Recipients

Circle Announces 2nd Cohort of USDC Grant Program Recipients

October 30, 2024
Unveiling the Confidential ERC-20 Framework: Compliant Privacy on Public Blockchains using FHE

Unveiling the Confidential ERC-20 Framework: Compliant Privacy on Public Blockchains using FHE

October 28, 2024
Temporary ERC-20 approvals: A cheaper & safer way to do DeFi

Temporary ERC-20 approvals: A cheaper & safer way to do DeFi

October 17, 2024