Skip to content

Commit 5b08d6c

Browse files
authored
feat: add 0x support (#255)
* Add 0x plugin * Fix example
1 parent 8d53145 commit 5b08d6c

File tree

11 files changed

+206
-2
lines changed

11 files changed

+206
-2
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-viem": patch
3+
"@goat-sdk/plugin-0x": patch
4+
---
5+
6+
Release 0x plugin
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@goat-sdk/plugin-0x",
3+
"version": "0.1.0",
4+
"files": ["dist/**/*", "README.md", "package.json"],
5+
"scripts": {
6+
"build": "tsup",
7+
"clean": "rm -rf dist",
8+
"test": "vitest run --passWithNoTests"
9+
},
10+
"main": "./dist/index.js",
11+
"module": "./dist/index.mjs",
12+
"types": "./dist/index.d.ts",
13+
"sideEffects": false,
14+
"homepage": "https://ohmygoat.dev",
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/goat-sdk/goat.git"
18+
},
19+
"license": "MIT",
20+
"bugs": {
21+
"url": "https://github.com/goat-sdk/goat/issues"
22+
},
23+
"keywords": ["ai", "agents", "web3"],
24+
"dependencies": {
25+
"@goat-sdk/core": "workspace:*",
26+
"zod": "catalog:",
27+
"@goat-sdk/wallet-evm": "workspace:*"
28+
},
29+
"peerDependencies": {
30+
"@goat-sdk/core": "workspace:*"
31+
}
32+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { PluginBase } from "@goat-sdk/core";
2+
import { Chain } from "@goat-sdk/core";
3+
import { EVMWalletClient } from "@goat-sdk/wallet-evm";
4+
import { ZeroExService } from "./0x.service";
5+
6+
export type ZeroExCtorParams = {
7+
apiKey: string;
8+
};
9+
10+
const supportedChains = [
11+
"1",
12+
"42161", // arbitrum
13+
"43114", // avalanche
14+
"8453", // base
15+
"81457", // blast
16+
"56", // bsc
17+
"59144", // linea
18+
"5000", // mantle
19+
"34443", // mode
20+
"10", // optimism
21+
"137", // polygon
22+
"534352", // scroll
23+
"480", // worldcoin
24+
];
25+
26+
export class ZeroExPlugin extends PluginBase<EVMWalletClient> {
27+
constructor(params: ZeroExCtorParams) {
28+
super("0x", [new ZeroExService(params.apiKey)]);
29+
}
30+
31+
supportsChain = (chain: Chain) => chain.type === "evm" && supportedChains.includes(chain.id.toString());
32+
}
33+
34+
export function zeroEx(params: ZeroExCtorParams) {
35+
return new ZeroExPlugin(params);
36+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Tool } from "@goat-sdk/core";
2+
import { EVMWalletClient } from "@goat-sdk/wallet-evm";
3+
import { GetQuoteParameters } from "./parameters";
4+
5+
export class ZeroExService {
6+
constructor(private readonly apiKey: string) {}
7+
8+
private async makeRequest(queryParams: Record<string, string | undefined>) {
9+
const filteredParams = Object.fromEntries(
10+
Object.entries(queryParams).filter(([_, v]) => v !== undefined),
11+
) as Record<string, string>;
12+
13+
const url = new URL(
14+
`https://api.0x.org/swap/allowance-holder/quote?${new URLSearchParams(filteredParams).toString()}`,
15+
);
16+
17+
const response = await fetch(url.toString(), {
18+
method: "GET",
19+
headers: {
20+
"0x-api-key": this.apiKey,
21+
"0x-version": "v2",
22+
},
23+
});
24+
25+
if (!response.ok) {
26+
throw new Error(`Failed to fetch ${url}: ${JSON.stringify(await response.text(), null, 2)}`);
27+
}
28+
29+
return response.json();
30+
}
31+
32+
@Tool({
33+
name: "0x_getQuote",
34+
description: "Get a quote for a swap from 0x",
35+
})
36+
async getQuote(walletClient: EVMWalletClient, parameters: GetQuoteParameters) {
37+
const queryParams = {
38+
chainId: walletClient.getChain().id.toString(),
39+
sellToken: parameters.sellToken,
40+
buyToken: parameters.buyToken,
41+
sellAmount: parameters.sellAmount,
42+
taker: parameters.taker,
43+
txOrigin: parameters.txOrigin,
44+
slippageBps: parameters.slippageBps?.toString(),
45+
};
46+
47+
return await this.makeRequest(queryParams);
48+
}
49+
50+
@Tool({
51+
name: "0x_swap",
52+
description: "Swap tokens using 0x",
53+
})
54+
async swap(walletClient: EVMWalletClient, parameters: GetQuoteParameters) {
55+
const quote = await this.getQuote(walletClient, parameters);
56+
57+
const transaction = quote.transaction;
58+
59+
const tx = await walletClient.sendTransaction({
60+
to: transaction.to,
61+
value: transaction.value,
62+
data: transaction.data,
63+
});
64+
65+
return tx.hash;
66+
}
67+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./0x.plugin";
2+
export * from "./parameters";
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createToolParameters } from "@goat-sdk/core";
2+
import { z } from "zod";
3+
4+
export class GetQuoteParameters extends createToolParameters(
5+
z.object({
6+
chainId: z.number().describe("The chain ID to swap on"),
7+
sellToken: z.string().describe("The token to sell"),
8+
buyToken: z.string().describe("The token to buy"),
9+
sellAmount: z.string().describe("The amount of tokens to sell in base units"),
10+
taker: z
11+
.string()
12+
.describe("The address which holds the sellToken balance and has the allowance set for the swap"),
13+
txOrigin: z
14+
.string()
15+
.optional()
16+
.describe(
17+
"The contract address of the external account that started the transaction. This is only needed if taker is a smart contract.",
18+
),
19+
slippageBps: z
20+
.number()
21+
.optional()
22+
.describe(
23+
"The maximum acceptable slippage of the buyToken in Bps. If this parameter is set to 0, no slippage will be tolerated. If not provided, the default slippage tolerance is 100Bps",
24+
),
25+
}),
26+
) {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"$schema": "https://json.schemastore.org/tsconfig",
3+
"extends": "../../../tsconfig.base.json",
4+
"include": ["src/**/*"],
5+
"exclude": ["node_modules", "dist"]
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineConfig } from "tsup";
2+
import { treeShakableConfig } from "../../../tsup.config.base";
3+
4+
export default defineConfig({
5+
...treeShakableConfig,
6+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://turbo.build/schema.json",
3+
"extends": ["//"],
4+
"tasks": {
5+
"build": {
6+
"inputs": ["src/**", "tsup.config.ts", "!./**/*.test.{ts,tsx}", "tsconfig.json"],
7+
"dependsOn": ["^build"],
8+
"outputs": ["dist/**"]
9+
}
10+
}
11+
}

typescript/pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typescript/scripts/createPlugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ function validatePluginName(name: string): boolean {
2525
function getWalletClientImport(type: PluginOptions["type"]): string {
2626
switch (type) {
2727
case "evm":
28-
return 'import type { EVMWalletClient } from "@goat-sdk/wallet-evm";';
28+
return 'import { EVMWalletClient } from "@goat-sdk/wallet-evm";';
2929
case "solana":
30-
return 'import type { SolanaWalletClient } from "@goat-sdk/wallet-solana";';
30+
return 'import { SolanaWalletClient } from "@goat-sdk/wallet-solana";';
3131
case "any":
3232
return 'import { WalletClientBase } from "@goat-sdk/core";';
3333
}

0 commit comments

Comments
 (0)