Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example/wallet agent #12

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/wallet-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OPENAI_API_KEY=
INFURA_PROJECT_ID=
PRIVATE_KEY=
41 changes: 41 additions & 0 deletions examples/wallet-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
28 changes: 28 additions & 0 deletions examples/wallet-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Wallet Agent App

## Overview

The Wallet Agent App is a web application that enables users to interact with their cryptocurrency wallets through a chat interface powered by AI. It supports functionalities such as connecting wallets, sending transactions, and checking account balances.

## Key Functionalities

- **Connect Wallet:** Users can connect their cryptocurrency wallet to the app, allowing them to manage their assets directly from the interface. This functionality is primarily handled in the `Navbar` component (`examples/quickstart/components/navbar.tsx`).

- **AI-Powered Chat:** The app features a chat interface where users can interact with an AI assistant to perform wallet-related actions. This is implemented in the `Chat` component (`examples/wallet-agent/components/Chat.tsx`).

- **Send Transactions:** Users can initiate cryptocurrency transactions through the chat interface. The app provides a button to send transactions, which is facilitated by the `sendTransactionTool` in `examples/wallet-agent/ai/tools.ts`.

- **Check Balance:** Users can request their wallet balance via the chat interface. The balance is fetched using the `balanceTool` defined in `examples/wallet-agent/ai/tools.ts`.

- **API Integration:** The app uses an API route to process chat messages and invoke AI tools. This is managed in `examples/wallet-agent/app/api/chat/route.ts`.

## Getting Started

To run the app locally, follow these steps:

1. Clone the repository.
2. Install dependencies using `npm install`.
3. Set up environment variables for blockchain connections.
4. Start the development server using `npm run dev`.

For more detailed instructions, refer to the [installation guide](#).
40 changes: 40 additions & 0 deletions examples/wallet-agent/ai/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { tool as createTool } from "ai";
import { z } from "zod";
import { publicClient } from "@/wagmi.config";
import { formatEther } from "viem";

export const balanceTool = createTool({
description: "Request the account balance of the user",
parameters: z.object({
address: z.string().describe("The address of the user"),
}),

execute: async ({ address }) => {
try {
const balance = await publicClient.getBalance({
address: address as `0x${string}`,
});
return { balance: formatEther(balance) };
} catch (error) {
console.error(error);
return { balance: "0" };
}
},
});

export const sendTransactionTool = createTool({
description:
"You're going to provide a button that will initiate a transaction to the wallet address the user provided, you are not going to send the transaction",
parameters: z.object({
to: z.string().describe("The wallet address of the user"),
amount: z.string().describe("The amount of eth the transaction"),
}),
execute: async ({ to, amount }) => {
return { to, amount };
},
});

export const tools = {
displayBalance: balanceTool,
sendTransaction: sendTransactionTool,
};
23 changes: 23 additions & 0 deletions examples/wallet-agent/app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
import { tools } from "@/ai/tools";

export async function POST(request: Request) {
const { messages } = await request.json();

try {
const result = streamText({
model: openai("gpt-4o"),
system:
"You are a wallet assistant. You can perform actions on the user's wallet like sending transactions and checking the balance",
messages,
maxSteps: 5,
tools,
});

return result.toDataStreamResponse();
} catch (error) {
console.error(error);
return new Response("Internal server error", { status: 500 });
}
}
Binary file added examples/wallet-agent/app/favicon.ico
Binary file not shown.
72 changes: 72 additions & 0 deletions examples/wallet-agent/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
font-family: Arial, Helvetica, sans-serif;
}

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
36 changes: 36 additions & 0 deletions examples/wallet-agent/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { Provider } from "@/lib/Provider";
import { headers } from "next/headers";
import { getConfig } from "@/wagmi.config";
import { cookieToInitialState } from "wagmi";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const initialState = cookieToInitialState(
getConfig(),
(await headers()).get("cookie") ?? ""
);
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Provider initialState={initialState}>{children}</Provider>
</body>
</html>
);
}
90 changes: 90 additions & 0 deletions examples/wallet-agent/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use client";

import { Chat } from "@/components/Chat";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import { useAccount, useConnect, useDisconnect } from "wagmi";

const ConnectButton = () => {
const { connectors, connect } = useConnect();
const { disconnect } = useDisconnect();
const { isConnected } = useAccount();

const connector = connectors[0];

const handleConnect = () => {
if (isConnected) {
disconnect();
} else {
connect({ connector });
}
};

return (
<Button className="max-w-fit" onClick={handleConnect}>
{isConnected ? "Disconnect" : "Connect Wallet"}
</Button>
);
};

export default function Home() {
const { isConnected } = useAccount();

return (
<div className="h-screen w-full overflow-y-auto grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="gap-8 row-start-2 sm:items-start h-full w-full">
<div className="flex flex-col gap-3 items-center justify-center h-full">
<ConnectButton />
{isConnected && <Chat />}
</div>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org →
</a>
</footer>
</div>
);
}
21 changes: 21 additions & 0 deletions examples/wallet-agent/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
Loading