Skip to content

Commit 18461a3

Browse files
tmarwen0xaguspunk
andauthored
feat(chain) Add Form Chain and Curves Protocol support (#274)
* feat(chain) Add Form Chain and Curves Protocol support This commit introduces native support for Form Chain and its Curves Protocol: - Add Form Mainnet and Testnet chain definitions - Implement Curves smart contract interface - Create FormWalletClient extending viem base wallet - Add Curves plugin with all core operations - Implement price formatting utilities - Add comprehensive error handling Resolves #273 Testing details: - Tested buy/sell operations on Form Testnet - Verified price queries and balance checks - Validated ERC20 metadata management - Confirmed proper error handling Tools tested: - buy_curves_token - sell_curves_token - get_curves_erc20 - get_curves_balance - set_erc20_metadata * Delete typescript/examples/vercel-ai/form-curves/CHANGELOG.md * Delete typescript/packages/plugins/curves/README.md * Create README.md * Delete typescript/packages/plugins/curves/CHANGELOG.md * Add changeset --------- Co-authored-by: Agus <[email protected]>
1 parent 2ab7377 commit 18461a3

File tree

20 files changed

+2182
-0
lines changed

20 files changed

+2182
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"goat-examples-vercel-ai-form-curves": patch
3+
"@goat-sdk/plugin-curves": patch
4+
---
5+
6+
Release plugin
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#################
2+
# LLM #
3+
#################
4+
OPENAI_API_KEY=
5+
6+
#################
7+
# WALLET #
8+
#################
9+
WALLET_PRIVATE_KEY=
10+
11+
#################
12+
# Form chain #
13+
#################
14+
# RPC_PROVIDER_URL=https://rpc.form.network/http # Mainnet RPC
15+
RPC_PROVIDER_URL=https://sepolia-rpc.form.network/http
16+
17+
#################
18+
# Curves #
19+
#################
20+
CURVES_CONTRACT_ADDRESS=0x3e4A86563f0a6688378a692e3D2a9651F4b704e9
21+
# CURVES_CONTRACT_ADDRESS=0xEad4138380B508949Ccd48B97AD930bd89aAb719 # Mainnet Curves
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Vercel AI with Form Curves Example
2+
3+
This example demonstrates how to use GOAT with Vercel AI SDK and viem for Curves contract operations (on Form chain).
4+
It provides a natural language interface through a CLI for Curves tokens trading (buy/sell) withdrawal and deposit (to and from associated ERC20 token) and price querying.
5+
6+
## Setup
7+
8+
1. Install dependencies:
9+
```bash
10+
pnpm install
11+
```
12+
13+
2. Copy the `.env.template` and populate with your values:
14+
```bash
15+
cp .env.template .env
16+
```
17+
18+
### Required Environment Variables:
19+
- `OPENAI_API_KEY`: Your OpenAI API key for the AI model
20+
- `WALLET_PRIVATE_KEY`: Your wallet's private key (with 0x prefix)
21+
22+
### Default Environment Variables:
23+
- `RPC_PROVIDER_URL`: Defaults to the Form Testnet chain but can be switched to any EVM compatible chain given there is a Curves contract deployed to interact with
24+
- `CURVES_CONTRACT_ADDRESS`: Defaults to the Form Testnet deployed Curves contract but can be switched to any Curves ABI compatible contract address
25+
26+
## Usage
27+
28+
1. Run the interactive CLI:
29+
```bash
30+
npx ts-node index.ts
31+
```
32+
33+
2. Example interactions:
34+
```
35+
# Buy Ooperations
36+
- How much does 0x123...789 cost?
37+
- Buy one 0x123...789
38+
39+
# Sell Operations
40+
- How much is 0x123...789 is sold for?
41+
- Sell one 0x123...789
42+
43+
# ERC20 Operations
44+
- Mint my ERC20 token
45+
- Set ERC20 token name to 'Great On-chain Agent Toolkil' and symbol to 'GOAT'
46+
- Withdraw 3 curves
47+
- Deposit 2 curves
48+
```
49+
50+
3. Understanding responses:
51+
- Transaction confirmations
52+
- Balance updates
53+
- Error messages
54+
- Operation status
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { defineChain } from "viem";
2+
import { mainnet } from "viem/chains";
3+
import { chainConfig } from "viem/op-stack";
4+
5+
export default defineChain({
6+
...chainConfig,
7+
id: 478,
8+
name: "Form",
9+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
10+
rpcUrls: {
11+
default: { http: ["https://rpc.form.network/http"] },
12+
},
13+
blockExplorers: {
14+
default: {
15+
name: "Form Explorer",
16+
url: "https://explorer.form.network/",
17+
},
18+
},
19+
contracts: {
20+
addressManager: {
21+
[mainnet.id]: {
22+
address: "0x15c249E46A2F924C2dB3A1560CF86729bAD1f07B",
23+
},
24+
},
25+
l1CrossDomainMessenger: {
26+
[mainnet.id]: {
27+
address: "0xF333158DCCad1dF6C3F0a3aEe8BC31fA94d9eD5c",
28+
},
29+
},
30+
l2OutputOracle: {
31+
[mainnet.id]: {
32+
address: "0x4ccAAF69F41c5810cA875183648B577CaCf1F67E",
33+
},
34+
},
35+
portal: {
36+
[mainnet.id]: {
37+
address: "0x4E259Ee5F4136408908160dD32295A5031Fa426F",
38+
},
39+
},
40+
l1StandardBridge: {
41+
[mainnet.id]: {
42+
address: "0xdc20aA63D3DE59574E065957190D8f24e0F7B8Ba",
43+
},
44+
},
45+
},
46+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { defineChain } from "viem";
2+
import { sepolia } from "viem/chains";
3+
import { chainConfig } from "viem/op-stack";
4+
5+
export default defineChain({
6+
...chainConfig,
7+
id: 132902,
8+
name: "Form Testnet",
9+
nativeCurrency: {
10+
decimals: 18,
11+
name: "Ether",
12+
symbol: "ETH",
13+
},
14+
rpcUrls: {
15+
default: { http: ["https://sepolia-rpc.form.network/http"] },
16+
},
17+
blockExplorers: {
18+
default: {
19+
name: "Form Testnet Explorer",
20+
url: "https://sepolia-explorer.form.network/",
21+
},
22+
},
23+
contracts: {
24+
addressManager: {
25+
[sepolia.id]: {
26+
address: "0xd5C38fa934f7fd7477D4800F4f38a1c5BFdF1373",
27+
},
28+
},
29+
l1CrossDomainMessenger: {
30+
[sepolia.id]: {
31+
address: "0x37A68565c4BE9700b3E3Ec60cC4416cAC3052FAa",
32+
},
33+
},
34+
l2OutputOracle: {
35+
[sepolia.id]: {
36+
address: "0x9eA2239E65a59EC9C7F1ED4C116dD58Da71Fc1e2",
37+
},
38+
},
39+
portal: {
40+
[sepolia.id]: {
41+
address: "0x60377e3cE15dF4CCA24c4beF076b60314240b032",
42+
},
43+
},
44+
l1StandardBridge: {
45+
[sepolia.id]: {
46+
address: "0xD4531f633942b2725896F47cD2aFd260b44Ab1F7",
47+
},
48+
},
49+
},
50+
});
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import readline from "node:readline";
2+
3+
import { openai } from "@ai-sdk/openai";
4+
import { CoreTool, generateText } from "ai";
5+
6+
import { http } from "viem";
7+
import { createWalletClient } from "viem";
8+
import { privateKeyToAccount } from "viem/accounts";
9+
10+
import { getOnChainTools } from "@goat-sdk/adapter-vercel-ai";
11+
import { Token, erc20 } from "@goat-sdk/plugin-erc20";
12+
13+
import { curves } from "@goat-sdk/plugin-curves";
14+
import { sendETH } from "@goat-sdk/wallet-evm";
15+
import { viem } from "@goat-sdk/wallet-viem";
16+
17+
import { default as formTestnet } from "./formt.testnet";
18+
19+
require("dotenv").config();
20+
21+
type FunctionTool = CoreTool & {
22+
type?: "function";
23+
description?: string;
24+
};
25+
26+
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`);
27+
28+
const walletClient = createWalletClient({
29+
account: account,
30+
transport: http(process.env.RPC_PROVIDER_URL),
31+
chain: formTestnet,
32+
});
33+
34+
(async () => {
35+
// Declare WETH token on testnet
36+
const WETH: Token = {
37+
name: "Wrapped Ether",
38+
symbol: "WETH",
39+
decimals: 18,
40+
chains: {
41+
[132902]: {
42+
contractAddress: "0xA65be6D7DE4A82Cc9638FB3Dbf8E68b7f2e757ab",
43+
},
44+
},
45+
};
46+
47+
// Declare USDC token on testnet
48+
const USDC: Token = {
49+
decimals: 6,
50+
symbol: "USDC",
51+
name: "USD Coin",
52+
chains: {
53+
[132902]: {
54+
contractAddress: "0xaC96dbABb398ee0c49660049590a6e5527Ae581F",
55+
},
56+
},
57+
};
58+
59+
// Load Form tools
60+
const tools = await getOnChainTools({
61+
wallet: viem(walletClient),
62+
plugins: [
63+
curves(), // Add Curves plugin with all tools
64+
sendETH(), // Add sendETH plugin for compatibilty showcase
65+
erc20({
66+
tokens: [WETH, USDC],
67+
}), // Add erc20 plugin for compatibilty showcase
68+
],
69+
});
70+
71+
const rl = readline.createInterface({
72+
input: process.stdin,
73+
output: process.stdout,
74+
});
75+
76+
const log = {
77+
header: (text: string) => console.log(`\n🌟 ${text.toUpperCase()} 🌟\n`),
78+
section: (text: string) => console.log(`\n📍 ${text}\n`),
79+
info: (text: string) => console.log(` ℹ️ ${text}`),
80+
success: (text: string) => console.log(` ✅ ${text}`),
81+
warning: (text: string) => console.log(` ⚠️ ${text}`),
82+
error: (text: string, e?: Error) => {
83+
console.error(` ❌ ${text}`);
84+
85+
// Parse and simplify the error message
86+
if (e?.message) {
87+
const errorMessage = e.message.toLowerCase();
88+
89+
// Common error cases
90+
if (errorMessage.includes("execution reverted")) {
91+
log.warning("Transaction was rejected by the network. Possible reasons:");
92+
log.list([
93+
"Insufficient balance",
94+
"You don't own the tokens you're trying to sell",
95+
"Contract restrictions prevent this operation",
96+
]);
97+
} else if (errorMessage.includes("insufficient funds")) {
98+
log.warning("You don't have enough funds for this transaction");
99+
} else {
100+
// For unknown errors, show a simplified message
101+
console.error(` Reason: ${e.message.split("Raw Call Arguments")[0].trim()}`);
102+
}
103+
}
104+
},
105+
list: (items: string[]) => {
106+
for (const item of items) {
107+
console.log(` • ${item}`);
108+
}
109+
},
110+
step: (num: number, text: string) => console.log(`\n→ Step ${num}: ${text}`),
111+
result: (text: string) => console.log(` ${text}`),
112+
spacer: () => console.log(""),
113+
};
114+
115+
// Usage in your code:
116+
log.header("Form Curves CLI");
117+
log.section("Available Tools");
118+
log.list(
119+
Object.values(tools)
120+
.filter((t) => (t.type === undefined || t.type === "function") && "description" in t)
121+
.map((t) => (t as FunctionTool).description || "No description available"),
122+
);
123+
while (true) {
124+
log.spacer();
125+
const prompt = await new Promise<string>((resolve) => {
126+
rl.question('💭 What would you like to do? (type "exit" to quit): ', resolve);
127+
});
128+
129+
if (prompt === "exit") {
130+
log.info("Thanks for using Form Curves CLI!");
131+
rl.close();
132+
break;
133+
}
134+
135+
log.section("Processing Your Request");
136+
137+
try {
138+
const result = await generateText({
139+
model: openai("gpt-4o-mini"),
140+
tools,
141+
maxSteps: 10,
142+
prompt,
143+
onStepFinish: (event) => {
144+
if (event.toolResults) {
145+
event.toolResults.forEach((result, index) => {
146+
log.info(`tool_call[${index + 1}]: ${JSON.stringify(result)}`);
147+
});
148+
}
149+
},
150+
});
151+
log.spacer();
152+
log.success(result.text.replace(/`/g, "")); // Remove backticks for cleaner output
153+
} catch (error) {
154+
log.error("Sorry, there was a problem with your request", error as Error);
155+
}
156+
}
157+
})();
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "goat-examples-vercel-ai-form-curves",
3+
"version": "0.2.13",
4+
"description": "",
5+
"private": true,
6+
"scripts": {
7+
"test": "vitest run --passWithNoTests"
8+
},
9+
"author": "",
10+
"license": "MIT",
11+
"dependencies": {
12+
"@ai-sdk/openai": "^1.0.4",
13+
"@goat-sdk/adapter-vercel-ai": "workspace:*",
14+
"@goat-sdk/core": "workspace:*",
15+
"@goat-sdk/plugin-erc20": "workspace:*",
16+
"@goat-sdk/plugin-uniswap": "workspace:*",
17+
"@goat-sdk/plugin-curves": "workspace:*",
18+
"@goat-sdk/wallet-evm": "workspace:*",
19+
"@goat-sdk/wallet-viem": "workspace:*",
20+
"ai": "catalog:",
21+
"dotenv": "^16.4.5",
22+
"viem": "2.21.49"
23+
}
24+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"outDir": "dist"
5+
},
6+
"include": ["index.ts"],
7+
"exclude": ["node_modules", "dist"]
8+
}

0 commit comments

Comments
 (0)