Skip to content

Commit

Permalink
feat: uniswap plugin and example (#5)
Browse files Browse the repository at this point in the history
* create uni plugin

* update readme file

* lockfile

* define method

* add api key env var

* clean up api key and base url

* add swap too

* add supported chains

* uni lock

* fix readme

* define send swap tx

* do not req the wallet

* new lockfile

* update types

* lint it

* pubish guide

* also check for approval

* update some tool desc

* also add check approval as its capabilities

* remove script run from template

* allow smart wallets

* update lockfile

* response is json and update lockfile and lint

* send tx in core

* remove parse eth import

* uni versions to 0

* check in changeset

* trigger gh action

* rename and export

* apply lint fixes
  • Loading branch information
dannythedawger authored Dec 10, 2024
1 parent 184253f commit cad06c0
Show file tree
Hide file tree
Showing 20 changed files with 986 additions and 560 deletions.
7 changes: 7 additions & 0 deletions typescript/.changeset/good-parrots-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"goat-examples-vercel-ai-uniswap": minor
"@goat-sdk/plugin-uniswap": minor
"@goat-sdk/core": patch
---

uniswap package and example
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { useConversation } from "@11labs/react";
import { getOnChainTools } from "@goat-sdk/adapter-eleven-labs";
import { useCallback, useEffect } from "react";

import { isEthereumWallet } from "@dynamic-labs/ethereum";
import { DynamicWidget, getNetwork, useDynamicContext, useSwitchNetwork } from "@dynamic-labs/sdk-react-core";
import { coingecko } from "@goat-sdk/plugin-coingecko";
import { viem } from "@goat-sdk/wallet-viem";
import { DynamicWidget, getNetwork, useSwitchNetwork, useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { isEthereumWallet } from "@dynamic-labs/ethereum";
import { sepolia } from "viem/chains";
import { useAccount, useWalletClient } from "wagmi";
import { sendETH } from "../../../../../../packages/core/dist/plugins/send-eth";
import { sepolia } from "viem/chains";

export function Conversation() {
const { primaryWallet, sdkHasLoaded } = useDynamicContext();
Expand All @@ -34,7 +34,7 @@ export function Conversation() {

useEffect(() => {
if (primaryWallet) {
checkAndSwitchNetwork();
checkAndSwitchNetwork();
}
}, [primaryWallet]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
"use client";

import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";
import { http, createConfig, WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { http, WagmiProvider, createConfig } from "wagmi";

const config = createConfig({
const config = createConfig({
chains: [sepolia],
multiInjectedProviderDiscovery: false,
transports: {
[sepolia.id]: http(process.env.NEXT_PUBLIC_SEPOLIA_RPC_URL ?? ""),
},
});
});

const queryClient = new QueryClient();

export const Web3Provider = ({ children }: { children: React.ReactNode }) => {
return (
<DynamicContextProvider
settings={{
// Find your environment id at https://app.dynamic.xyz/dashboard/developer
environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID ?? "",
walletConnectors: [EthereumWalletConnectors],
}}
>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<DynamicWagmiConnector>
{children}
</DynamicWagmiConnector>
</QueryClientProvider>
</WagmiProvider>
</DynamicContextProvider>
settings={{
// Find your environment id at https://app.dynamic.xyz/dashboard/developer
environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID ?? "",
walletConnectors: [EthereumWalletConnectors],
}}
>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<DynamicWagmiConnector>{children}</DynamicWagmiConnector>
</QueryClientProvider>
</WagmiProvider>
</DynamicContextProvider>
);
};
2 changes: 2 additions & 0 deletions typescript/examples/vercel-ai/uniswap/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
UNISWAP_API_KEY=
UNISWAP_BASE_URL=
34 changes: 34 additions & 0 deletions typescript/examples/vercel-ai/uniswap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Vercel AI Uniswap Example

## Setup

Create an `.env` file by copying the `.env.template` and populating with your values.

```
cp .env.template .env
```

`UNISWAP_API_KEY` and `UNISWAP_BASE_URL` will be populated in the next step.

## Usage

1. Create an API key for Uniswap's Trading API by visiting [this page](https://hub.uniswap.org/) and following the instructions. Also obtain the url from the API key creation page.

2. Run the script with your prompt!

```
npx ts-node index.ts
```

Some examples:

* Prompt it to swap 1 ETH for USDC

## Actions it supports

- `Get Quote`: Get a quote for a swap
- `Get Swap Transaction`: Get the transaction details for a swap
- `Send Transaction`: Send a transaction
- `Check Approval`: Check if the wallet has enough approval for a token and returns the transaction to approve the token. The approval must takes place before the swap transaction.

Want support for more actions? [Open an issue](https://github.com/goat-sdk/goat-sdk/issues) and let us know!
42 changes: 42 additions & 0 deletions typescript/examples/vercel-ai/uniswap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";

import { http } from "viem";
import { createWalletClient } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { polygon } from "viem/chains";

import { getOnChainTools } from "@goat-sdk/adapter-vercel-ai";
import { uniswap } from "@goat-sdk/plugin-uniswap";
import { viem } from "@goat-sdk/wallet-viem";

require("dotenv").config();

const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`);

const walletClient = createWalletClient({
account: account,
transport: http(process.env.ALCHEMY_API_KEY),
chain: polygon,
});

(async () => {
const tools = await getOnChainTools({
wallet: viem(walletClient),
plugins: [
uniswap({
apiKey: process.env.UNISWAP_API_KEY as string,
baseUrl: process.env.UNISWAP_BASE_URL as string,
}),
],
});

const result = await generateText({
model: openai("gpt-4o-mini"),
tools: tools,
maxSteps: 5,
prompt: "Swap 1 ETH for USDC",
});

console.log(result.text);
})();
21 changes: 21 additions & 0 deletions typescript/examples/vercel-ai/uniswap/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "goat-examples-vercel-ai-uniswap",
"version": "0.0.0",
"description": "",
"private": true,
"scripts": {
"test": "vitest run --passWithNoTests"
},
"author": "",
"license": "MIT",
"dependencies": {
"@ai-sdk/openai": "^1.0.4",
"@goat-sdk/adapter-vercel-ai": "workspace:*",
"@goat-sdk/core": "workspace:*",
"@goat-sdk/plugin-uniswap": "workspace:*",
"@goat-sdk/wallet-viem": "workspace:*",
"ai": "^4.0.3",
"dotenv": "^16.4.5",
"viem": "2.21.49"
}
}
8 changes: 8 additions & 0 deletions typescript/examples/vercel-ai/uniswap/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["index.ts"],
"exclude": ["node_modules", "dist"]
}
2 changes: 2 additions & 0 deletions typescript/packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ethSendTransaction } from "./plugins/eth-send-transaction";
import type { Plugin } from "./plugins/plugins";
import { sendETH } from "./plugins/send-eth";
import { sendSOL } from "./plugins/send-sol";
Expand Down Expand Up @@ -34,6 +35,7 @@ export {
getDeferredTools,
sendETH,
sendSOL,
ethSendTransaction,
addParametersToDescription,
parametersToJsonExample,
type Tool,
Expand Down
41 changes: 41 additions & 0 deletions typescript/packages/core/src/plugins/eth-send-transaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { z } from "zod";
import type { EVMWalletClient } from "../wallets";
import type { Plugin } from "./plugins";

export function ethSendTransaction(): Plugin<EVMWalletClient> {
return {
name: "ethSendTransaction",
supportsSmartWallets: () => true,
supportsChain: (chain) => chain.type === "evm",
getTools: async () => {
return [
{
name: "eth_sendTransaction",
description: "This {{tool}} signs and sends an EVM transaction.",
parameters: EthSendTransactionSchema,
method: async (
walletClient: EVMWalletClient,
parameters: z.infer<typeof EthSendTransactionSchema>,
) => {
return ethSendTransactionMethod(parameters, walletClient);
},
},
];
},
};
}

export const EthSendTransactionSchema = z.object({
from: z.string(),
to: z.string(),
amount: z.string(),
token: z.string(),
});

export async function ethSendTransactionMethod(
transaction: z.infer<typeof EthSendTransactionSchema>,
walletClient: EVMWalletClient,
): Promise<string> {
const txHash = await walletClient.sendTransaction(transaction);
return txHash.hash;
}
34 changes: 34 additions & 0 deletions typescript/packages/plugins/uniswap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Goat Uniswap Plugin 🐐 - TypeScript

Uniswap plugin for [Goat 🐐](https://ohmygoat.dev). Allows you to create tools for interacting with Uniswap.

## Installation
```
npm install @goat-sdk/plugin-uniswap
```

## Usage

```typescript
import { uniswap } from "@goat-sdk/plugin-uniswap";


const plugin = uniswap({
baseUrl: process.env.UNISWAP_BASE_URL as string,
apiKey: process.env.UNISWAP_API_KEY as string,
});
```

## Working example

See the [Vercel AI example](https://github.com/goat-sdk/goat/tree/main/typescript/examples/vercel-ai/uniswap) for a working example of how to use the Uniswap plugin.

## Goat

<div align="center">
Go out and eat some grass.

[Docs](https://ohmygoat.dev) | [Examples](https://github.com/goat-sdk/goat/tree/main/typescript/examples) | [Discord](https://discord.gg/2F8zTVnnFz)</div>

## Goat 🐐
Goat 🐐 (Great Onchain Agent Toolkit) is an open-source library enabling AI agents to interact with blockchain protocols and smart contracts via their own wallets.
48 changes: 48 additions & 0 deletions typescript/packages/plugins/uniswap/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@goat-sdk/plugin-uniswap",
"version": "0.0.0",
"files": ["dist/**/*", "README.md", "package.json"],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "vitest"
},
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"devDependencies": {
"@types/node": "^22.7.4",
"globals": "^15.12.0",
"ts-node": "^10.9.2",
"tsup": "^8.3.5",
"typescript": "^5.6.3",
"vitest": "^2.1.5"
},
"dependencies": {
"@goat-sdk/core": "workspace:*",
"viem": "^2.21.49",
"zod": "^3.23.8"
},
"peerDependencies": {
"@goat-sdk/core": "workspace:*",
"viem": "^2.21.49"
},
"engines": {
"node": ">=20.12.2 <21",
"npm": "please-use-pnpm",
"pnpm": ">=9",
"yarn": "please-use-pnpm"
},
"homepage": "https://ohmygoat.dev",
"repository": {
"type": "git",
"url": "git+https://github.com/goat-sdk/goat.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/goat-sdk/goat/issues"
},
"keywords": ["ai", "agents", "web3"],
"packageManager": "[email protected]"
}
Loading

0 comments on commit cad06c0

Please sign in to comment.