Creating an AI Agent to Help Solve the Crypto UX Problem

Introduction

In this guide we are going to illustrate how to create an AI Agent workflow using the Open Agents SDK by OpenAI; however, we are going to power the inference capabilities of our agents using decentralized, open source tech!

Our goal will be to create the foundations for a simple, yet powerful agent who can be used to onboard a non-crypto native person to crypto from creating their first wallet to helping them interact on-chain.

Here’s the tech we’ll be using:

The full code base is available on GitHub if you ever need to reference something

Motivation

Crypto has had a long standing user experience issue with wallet creation, private key management and transaction execution just to name a few. With the rise of AI and the adoption of chat agents by the majority of the globe, we have an opportunity to address Crypto’s long standing problem of making the on-chain experience more approachable to a non-technical user.

Creating the Agent

Before getting into the nitty, gritty of this guide, let’s talk a little about the SDK we’re going to use from OpenAI and how it works. The Open Agents SDK is one of the newer frameworks introduced by OpenAI to allow people to more easily create AI Agents and multi-agent flows. We will be using this framework to take away a lot of the boilerplate work we’d have to do to get up and running with our agent. If you’re not familiar with what an AI Agent is, it’s a program helping a user achieve a particular goal using a set of tools (like api’s, web search, file search, etc…) and Large Language Model (LLM) as the brain to help orchestrate.

Setting up the app

First, let's create our project structure:

mkdir crypto-assistant-agent
cd crypto-assistant-agent
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

Create a requirements.txt file with our dependencies:

openai>=1.12.0
openai-agents==0.0.6
python-dotenv>=1.0.0 
eth-account
web3
python-dotenv
requests
rich>=13.7.0

Install the dependencies:

pip install -r requirements.txt

Create a .env file in your project root so that we can set up a custom OpenAI configuration:

# Custom OpenAI Url and API Key
OPENAI_API_URL=your_api_url_here 
OPENAI_API_KEY=your_api_key_here
MODEL=your_model_here

Don’t worry about the values for now as we’ll fill this in in the next section

Setting up our AI inference

With this section I’m going to provide two options for you to be able to power your agent with open source AI models: using Ollama to run your model inference locally or Lilypad’s Anura API for programatic way

Running AI Inference locally (Option 1)

If you have a capable computer, Ollama is a great option for running powerful open source models locally on your computer. If you’re not familiar, Ollama is an awesome project providing you access to the biggest and most popular open source AI models such as Llama3, Deepseek, Gemma3 and more. All you need to do is install their software, download your first model and you’re on your way!

So here’s how to get up and running with Ollama:

  1. Download the Ollama by visiting their website and clicking the download button

  2. Double click on the resulting file to install Ollama

  3. Once it’s installed you should see it simply running as a background app with no UI

  4. Open up a terminal and confirm its installation with the following command:

     which ollama
     # Should output /usr/local/bin/ollama
    
  5. Now let’s download our first model, for this guide we are going to download llama3.2 as its a pretty small model (3B version) that’s likely to work on most computers (if you have more ram and a capable graphics card you can try and use the bigger models too). To download the model, execute the following command in your terminal:

ollama pull llama3.2:latest

This command will take a bit to complete as it will be pulling the model locally to your machine

  1. Once the download as completed, let’s test it out by running the model and passing it a query:
ollama run llama3.2:latest
# type hello or anything else to see a response from the model
# type ctrl + d or /bye to exit the session

Congrats, you just ran your first open source model directly on your machine all for free without any of your queries or data leaving your compute! Not only that but you now have access to you’re own OpenAI compliant chant completions endpoint via http://localhost:11434/v1/chat/completions. Not bad for open source eh?

Running AI inference using Lilypad’s Anura API (Option 2)

Ollama is fantastic and allows you to harness the power of open source AI with just a few steps; however, the crummy thing is that you are limited by what models you can actually run by the hardware on your machine. If you want to run some of the beefier models beyond 8B parameters, you need to have quite a bit of ram and/or a top notch GPU to be able to run it. While accessible to some AI and tech enthusiasts, a lot of folks don’t necessary have $5k+ lying around to spend on a new computer. This is where Lilypad comes into play offering a decentralized compute network capable of running any open source model powered by some of the top GPU’s available on the market today. What’s cool about Lilypad is that they offer a CLI tool and API for you to be able to utilize their powerful network to your AI workflow.

Today we will be focusing on the Anura API which offers an OpenAI compliant chat completions endpoint capable of being plug and played into most AI frameworks like the one we’ll be using to build our crypto agent. So here how to get started:

  1. Head over to the Anura website and sign up (its free)

  2. Once you’ve signed up, create your Anura API Key which will let you programatically access the API

  3. You can also check out the docs if you want to learn more about the API and see which models are supported (at the moment most of the popular Ollama models are supported)

Now make note of your API key which we’ll be using in the next step

Setting up our .env file

So the option you chose in the above section will determine what you end up putting into your .env file but I’ll show you both so you can proceed with which ever option you went with (why not try both?):

Option 1: Local Ollama

# OpenAI API Custom Configuration

# Ollama local chat completions endpoint supporting the OpenAI Request and Response model
OPENAI_API_URL=http://localhost:11434/v1
# Ollama doesn't need an api key to run locally but we provide one here anyway
OPENAI_API_KEY=ollama
# The Model we'll use, note: this is the 3b parameter model
MODEL=llama3.2:latest

Option 2: Lilypad Anura API

# OpenAI API Custom Configuration

# Anura chat completions endpoint supporting the OpenAI Request and Response model
OPENAI_API_URL=https://anura-testnet.lilypad.tech/api/v1
# The API Key you created after you set up your Anura account
OPENAI_API_KEY=<your-anura-api-key>
# The Model we'll use, note: this is the 8b parameter model
MODEL=llama3.1:8b

Alright, with that in place, let’s start building our agent!

Setting up our app

So first things first, let’s create a main.py file in the src directory of our project, and then bring in our imports:

import os
import asyncio
from eth_account import Account
from web3 import AsyncWeb3
from dotenv import load_dotenv
from agents import Agent, Runner, set_default_openai_api, set_default_openai_client, set_tracing_disabled, function_tool
from openai import AsyncOpenAI
import requests
from rich.console import Console
from rich.markdown import Markdown
from rich.prompt import Prompt

Now let’s add a main function and first version of our agent

import os
import asyncio
from eth_account import Account
from web3 import Web3
from dotenv import load_dotenv
from agents import Agent, Runner, set_default_openai_api, set_default_openai_client, set_tracing_disabled, function_tool
from openai import AsyncOpenAI
import requests
from rich.console import Console
from rich.markdown import Markdown
from rich.prompt import Prompt

# Load environment variables
load_dotenv(override=True)
# This is for debugging and we don't need it for this tutorial
set_tracing_disabled(True)

async def chat_loop():
    # Make Agent run with chat completions
    set_default_openai_api("chat_completions")

    # Initialize the custom OpenAI client
    custom_client = AsyncOpenAI(
        base_url=os.getenv("OPENAI_API_URL"),
        api_key=os.getenv("OPENAI_API_KEY")
    )
    set_default_openai_client(custom_client)

    crypto_agent = Agent(
        name="Crypto Assistant",
        instructions="""You are a helpful assistant that helps users onboard into crypto""",
        model=os.getenv("MODEL"),
    )

    result = await Runner.run(crypto_agent, "Tell me about blockchains")
    console.print(result.final_output)

async def main():
    await chat_loop()

if __name__ == "__main__":
    asyncio.run(main())

Let’s explain what’s going on here:

What we’ve done is create the base of our AI Agent using the openai-agent-sdk but with some modifications:

  • We’ve configured our agent to utilize the standard chat/completions endpoint when using our specified api instead of the agent specific api only present through the OpenAI API

  • We’ve created a custom client passing in our OPEN_API_URL and OPEN_API_KEY

  • We set the SDK to use our custom client

  • We create our crypto_agent , passing in it’s name, its instruction prompt and the model we are using (i.e. llama3.2)

  • We tell the agent to run, passing in our own input and then print out the result to the console

At this point you can go ahead and run this app and observe what it outputs in your terminal. Run the app via

python src/main,py

Create our tools

Well this is cool, we have a one shot inference agent that can tell us about blockchains; however, this isn’t really super useful on its own. What would make this a bit more intriguing to use if we can plug in some useful tools for the agent to be able to do things like create wallets, query an api, etc…

Indeed, an LLM is the foundation to any AI Agent; however, its the tooling capabilities that we provide them that take them beyond just a standard chat bot good for regurgitating facts. Let’s equip our agent with some useful tooling!

Adding Wallet Creation Support

Let’s start off with equipping our agent with the capability to create Ethereum and Bitcoin wallets for our users. Lets first add a couple of objects to hold the wallet address and private key/mnemonic.

Add these two lines just above our chat_loop function:

ethWalletInfo = {}
btcWalletInfo = {}

Now let’s create our first tool, namely our Ethereum Wallet function:

@function_tool
def create_eth_wallet() -> dict:
    acct = Account.create()
    ethWalletInfo["address"] = acct.address
    ethWalletInfo["private_key"] = acct.key.hex()
    return ethWalletInfo

What we’re using here is the eth-account library to cook us up an account and then setting the wallet address and private key in our ethWalletInfo object. Now to get our agent to actually be able to use this tool, we simply have to pass it in as an argument in the line we create the agent

crypto_agent = Agent(
        name="Crypto Assistant",
        instructions="""You are a helpful crypto assistant that helps users create wallets, get balances, check prices, and explain concepts in a simple way.

        For technical operations:
        1. Ethereum Operations:
           - Use create_eth_wallet to create a new Ethereum wallet
        """,
        model=os.getenv("MODEL"),
        tools=[create_eth_wallet]
    )

That’s it! So what will happen is when a user asks to create an Ethereum wallet, the SDK will call our inference api with the initial query with the tool definition telling the LLM it needs to use the tool to answer the users question. Then the SDK will call our create_eth_wallet function creating the account for the user, then pass in the resulting information and message history back again to the inference api to summarize the results. If you want to learn more about the pattern the OpenAI Agent SDK uses, have a read of their docs.

Now let’s round out our wallet capabilities by adding a some utility methods to retrieve the users address and private key

@function_tool
def get_eth_wallet_address() -> str:
    return ethWalletInfo.get("address", "No Ethereum wallet created yet")

@function_tool
def get_eth_wallet_private_key() -> str:
    return ethWalletInfo.get("private_key", "No Ethereum wallet created yet")

So now your main.py file should look something like this:

import os
import asyncio
from eth_account import Account
from web3 import Web3
from dotenv import load_dotenv
from agents import Agent, Runner, set_default_openai_api, set_default_openai_client, set_tracing_disabled, function_tool
from openai import AsyncOpenAI
import requests
from rich.console import Console
from rich.markdown import Markdown
from rich.prompt import Prompt

# Load environment variables
load_dotenv(override=True)
# This is for debugging and we don't need it for this tutorial
set_tracing_disabled(True)

@function_tool
def create_eth_wallet() -> dict:
    acct = Account.create()
    ethWalletInfo["address"] = acct.address
    ethWalletInfo["private_key"] = acct.key.hex()
    return ethWalletInfo

@function_tool
def get_eth_wallet_address() -> str:
    return ethWalletInfo.get("address", "No Ethereum wallet created yet")

@function_tool
def get_eth_wallet_private_key() -> str:
    return ethWalletInfo.get("private_key", "No Ethereum wallet created yet")

async def chat_loop():
    # Make Agent run with chat completions
    set_default_openai_api("chat_completions")

    # Initialize the custom OpenAI client
    custom_client = AsyncOpenAI(
        base_url=os.getenv("OPENAI_API_URL"),
        api_key=os.getenv("OPENAI_API_KEY")
    )
    set_default_openai_client(custom_client)

    crypto_agent = Agent(
        name="Crypto Assistant",
        instructions="""You are a helpful crypto assistant that helps users create wallets, get balances, check prices, and explain concepts in a simple way.

        For technical operations:
        1. Ethereum Operations:
           - Use create_eth_wallet to create a new Ethereum wallet
           - Use get_eth_wallet_address to get the Ethereum address
           - Use get_eth_wallet_private_key to get the Ethereum private key""",
        model=os.getenv("MODEL"),
        tools=[create_eth_wallet, get_eth_wallet_address, get_eth_wallet_private_key]
    )

    result = await Runner.run(crypto_agent, "I'm interested in Ethereum, can you please create a wallet for me?")
    console.print(result.final_output)

async def main():
    await chat_loop()

if __name__ == "__main__":
    asyncio.run(main())

Note how we continue to expand the instructions for the agent as we build out more tooling, this is so that the agent knows exactly what function to call depending on the circumstance.

Now go ahead and run this again via python src/main.py and you should see the agent respond to you with a brand new wallet address and corresponding private key!

Adding the ability to read data from the blockchain

Now let’s expand out agents capabilities and enable it to read data from the blockchain through the use of the web3.py library. What we'll be adding is another tool function to read the balance of an account through the agent. What we’ll first need to do is find a RPC url for us to use to connect to an Ethereum node, i’m going to use one that I found from Chainlist and add it to our env file:

ETH_RPC_URL=https://gateway.tenderly.co/public/mainnet

Next let’s create the function that will read the balance of a given Eth address:

@function_tool
async def get_eth_wallet_balance(address: str) -> str:
    w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(os.getenv("ETH_RPC_URL")))
    balance = await w3.eth.get_balance(address)
    return f"{balance / 10**18:.4f} ETH"

And finally let’s pass it to our tools array and update our instructions to notify the agent of its new capabilities:

# Code from before
# ...

crypto_agent = Agent(
        name="Crypto Assistant",
        instructions="""You are a helpful crypto assistant that helps users create wallets, get balances, check prices, and explain concepts in a simple way.

        For technical operations:
        1. Ethereum Operations:
           - Use create_eth_wallet to create a new Ethereum wallet
           - Use get_eth_wallet_address to get the Ethereum address
           - Use get_eth_wallet_private_key to get the Ethereum private key
           - Use get_eth_wallet_balance to get the Ethereum balance""",
        model=os.getenv("MODEL"),
        tools=[create_eth_wallet, get_eth_wallet_address, get_eth_wallet_private_key, get_eth_wallet_balance]
    )

At this point you can again start up your app and ask how much Eth a given wallet address has. You can update the query to something like “Find me how much eth balance is in the following wallet: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045” to test it out (that’s Vitalik’s wallet for reference).

Adding an API call to fetch the latest ETH price

So up to this point we’ve cooked up ways to natively create a wallet and private key pair, as well as query the Ethereum blockchain directly. Now let’s round out our agent by providing it the ability to fetch the latest ETH Price using the CoinGecko API. Alright, so just like we did before, we’ll define a new tool function and then update our agent definition accordingly. Go ahead and plop this function in your codebase:

@function_tool
def get_eth_price() -> str:
    url = "https://api.coingecko.com/api/v3/simple/price"
    params = {
        "ids": "ethereum",
        "vs_currencies": "usd"
    }
    response = requests.get(url, params=params)
    data = response.json()
    return data.get("ethereum", {}).get("usd", "Price not found")

And finally update your agent definition as follows:

# Code from before
# ...

crypto_agent = Agent(
        name="Crypto Assistant",
        instructions="""You are a helpful crypto assistant that helps users create wallets, get balances, check prices, and explain concepts in a simple way.

        For technical operations:
        1. Ethereum Operations:
           - Use create_eth_wallet to create a new Ethereum wallet
           - Use get_eth_wallet_address to get the Ethereum address
           - Use get_eth_wallet_private_key to get the Ethereum private key
           - Use get_eth_wallet_balance to get the Ethereum balance

        2. Price Checking:
           - Use get_eth_price to get ETH price
        """,
        model=os.getenv("MODEL"),
        tools=[create_eth_wallet, get_eth_wallet_address, get_eth_wallet_private_key, get_eth_wallet_balance, get_eth_price]
    )

Again, go ahead and update your query to test out the function, try something like “What’s the latest price of ETH?”

Recapping and adding the finishing touches

Ok so we did quite a bit, so congratulations if you made it this far! Just to review you just created an AI Agent that can:

  • Create Ethereum wallet and private key pairs natively on your machine

  • Query the Ethereum Blockchain for a given wallets ETH balance

  • Query an API to find the latest ETH Prices

  • Used open source AI to power the entire thing!

Pretty neat eh?

The only annoying thing is that we have to consistently change the user input manually in our source code to make one shot queries. Let’s fix that!

So now let’s round out our agent by adding some logic for it to run in a loop and give us some what of a user interface through the terminal. We’ll be using the Rich Console library to help us create this simple interface. This is mostly boiler plate at this point so go ahead and replace your main.py file with this final version that include:

import os
import asyncio
from eth_account import Account
from web3 import AsyncWeb3
from dotenv import load_dotenv
from agents import Agent, Runner, set_default_openai_api, set_default_openai_client, set_tracing_disabled, function_tool
from openai import AsyncOpenAI
import requests
from rich.console import Console
from rich.markdown import Markdown
from rich.prompt import Prompt

# Load environment variables
load_dotenv(override=True)
# This is for debugging and we don't need it for this tutorial
set_tracing_disabled(True)

# Initialize Rich console for better formatting
console = Console()

ethWalletInfo = {}

@function_tool
def create_eth_wallet() -> dict:
    acct = Account.create()
    ethWalletInfo["address"] = acct.address
    ethWalletInfo["private_key"] = acct.key.hex()
    return ethWalletInfo

@function_tool
def get_eth_wallet_address() -> str:
    return ethWalletInfo.get("address", "No Ethereum wallet created yet")

@function_tool
def get_eth_wallet_private_key() -> str:
    return ethWalletInfo.get("private_key", "No Ethereum wallet created yet")

@function_tool
async def get_eth_wallet_balance(address: str) -> str:
    w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(os.getenv("ETH_RPC_URL")))
    balance = await w3.eth.get_balance(address)
    return f"{balance / 10**18:.4f} ETH"

@function_tool
def get_eth_price() -> str:
    url = "https://api.coingecko.com/api/v3/simple/price"
    params = {
        "ids": "ethereum",
        "vs_currencies": "usd"
    }
    response = requests.get(url, params=params)
    data = response.json()
    return data.get("ethereum", {}).get("usd", "Price not found")

def print_welcome_message():
    console.print("""
[bold blue]Welcome to the Crypto Assistant![/bold blue]
I can help you with:
• Creating Ethereum wallets
• Checking wallet balances
• Getting crypto prices
• Explaining crypto concepts

Type 'exit' or 'quit' to end the conversation.
Type 'clear' to clear all wallet info.
    """)

async def chat_loop():
    # Set up the configuration
    set_default_openai_api("chat_completions")

    # Initialize the OpenAI client
    custom_client = AsyncOpenAI(
        base_url=os.getenv("OPENAI_API_URL"),
        api_key=os.getenv("OPENAI_API_KEY")
    )
    set_default_openai_client(custom_client)

    # Create the agent
    crypto_agent = Agent(
        name="Crypto Assistant",
        instructions="""You are a helpful crypto assistant that helps users create wallets, get balances, check prices, and explain concepts in a simple way.

        For technical operations:
        1. Ethereum Operations:
           - Use create_eth_wallet to create a new Ethereum wallet
           - Use get_eth_wallet_address to get the Ethereum address
           - Use get_eth_wallet_private_key to get the Ethereum private key
           - Use get_eth_wallet_balance to get the Ethereum balance

        2. Price Checking:
           - Use get_eth_price to get ETH price

        Important notes:
        - Always warn users to keep their private keys secure
        - Do not tell the user what tools and functions you are using
        - When explaining concepts, be educational but approachable

        Style guidelines:
        - Maintain a friendly, conversational tone
        - Use markdown formatting for better readability
        - Do not tell the user what functions you are using
        - Provide the answer in a simple and easy to understand way
        - Break up long explanations into sections
        - Include relevant examples when helpful
        - Be encouraging and supportive of learning""",
        model=os.getenv("MODEL"),
        tools=[
            create_eth_wallet, get_eth_wallet_address, get_eth_wallet_private_key, get_eth_wallet_balance,
            get_eth_price
        ]
    )

    print_welcome_message()

    while True:
        try:
            # Get user input
            user_input = Prompt.ask("\n[bold green]You[/bold green]")

            # Check for exit commands
            if user_input.lower() in ['exit', 'quit']:
                console.print("\n[bold blue]Goodbye! Have a great day![/bold blue]")
                break

            # Check for clear command
            if user_input.lower() == 'clear':
                ethWalletInfo.clear()
                btcWalletInfo.clear()
                wallet_delete_if_exists('btc_wallet')
                console.print("[bold yellow]All wallet information cleared![/bold yellow]")
                continue

            # Show typing indicator
            with console.status("[bold blue]Thinking...[/bold blue]"):
                # Get response from agent
                result = await Runner.run(crypto_agent, user_input)

            # Print the response with markdown formatting
            console.print("\n[bold blue]Assistant[/bold blue]")
            console.print(Markdown(result.final_output))

        except KeyboardInterrupt:
            console.print("\n[bold blue]Goodbye! Have a great day![/bold blue]")
            break
        except Exception as e:
            console.print(f"\n[bold red]An error occurred: {str(e)}[/bold red]")

async def main():
    await chat_loop()

if __name__ == "__main__":
    asyncio.run(main())

Well done! Go ahead and run the agent in your terminal wth python src/main.py and enjoy living on the bleeding edge of AI x Web3

Where to go from here?

We accomplished a decent bit here with not a lot of code helping build the base of a pretty powerful AI Agent to help people more easily onboard to Crypto; however, there’s still a lot we can add here. For example, we can

  • Add support for Bitcoin (wallet creation, reading on-chain, etc…)

  • Create a nice UI to connect our agent to so that we don’t have to interact with it through a terminal

  • Add more on-chain capabilities such as being able to interact with a smart contract, sign messages, execute transaction, etc..

  • Dockerizing the agent

Indeed, there is a lot more we can add to this agent but you should now have all the tools you need to go and create your next AI Agent to help the UX problem that has plagued web3 and crypto up to now. If you enjoyed this article consider giving it a thumbs up or subscribing to never miss any content I push out. Cheers until the next one!

10
Subscribe to my newsletter

Read articles from Narbeh Shahnazarian directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Narbeh Shahnazarian
Narbeh Shahnazarian