From 5b08d6ceff208a463d85afe5f16ad3ba9f807916 Mon Sep 17 00:00:00 2001 From: Agus Date: Tue, 21 Jan 2025 10:42:48 +0100 Subject: [PATCH] feat: add 0x support (#255) * Add 0x plugin * Fix example --- typescript/.changeset/eight-islands-march.md | 6 ++ typescript/packages/plugins/0x/package.json | 32 +++++++++ .../packages/plugins/0x/src/0x.plugin.ts | 36 ++++++++++ .../packages/plugins/0x/src/0x.service.ts | 67 +++++++++++++++++++ typescript/packages/plugins/0x/src/index.ts | 2 + .../packages/plugins/0x/src/parameters.ts | 26 +++++++ typescript/packages/plugins/0x/tsconfig.json | 6 ++ typescript/packages/plugins/0x/tsup.config.ts | 6 ++ typescript/packages/plugins/0x/turbo.json | 11 +++ typescript/pnpm-lock.yaml | 12 ++++ typescript/scripts/createPlugin.ts | 4 +- 11 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 typescript/.changeset/eight-islands-march.md create mode 100644 typescript/packages/plugins/0x/package.json create mode 100644 typescript/packages/plugins/0x/src/0x.plugin.ts create mode 100644 typescript/packages/plugins/0x/src/0x.service.ts create mode 100644 typescript/packages/plugins/0x/src/index.ts create mode 100644 typescript/packages/plugins/0x/src/parameters.ts create mode 100644 typescript/packages/plugins/0x/tsconfig.json create mode 100644 typescript/packages/plugins/0x/tsup.config.ts create mode 100644 typescript/packages/plugins/0x/turbo.json diff --git a/typescript/.changeset/eight-islands-march.md b/typescript/.changeset/eight-islands-march.md new file mode 100644 index 000000000..f8e45f617 --- /dev/null +++ b/typescript/.changeset/eight-islands-march.md @@ -0,0 +1,6 @@ +--- +"goat-examples-vercel-ai-viem": patch +"@goat-sdk/plugin-0x": patch +--- + +Release 0x plugin diff --git a/typescript/packages/plugins/0x/package.json b/typescript/packages/plugins/0x/package.json new file mode 100644 index 000000000..d2aef5dcc --- /dev/null +++ b/typescript/packages/plugins/0x/package.json @@ -0,0 +1,32 @@ +{ + "name": "@goat-sdk/plugin-0x", + "version": "0.1.0", + "files": ["dist/**/*", "README.md", "package.json"], + "scripts": { + "build": "tsup", + "clean": "rm -rf dist", + "test": "vitest run --passWithNoTests" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "sideEffects": false, + "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"], + "dependencies": { + "@goat-sdk/core": "workspace:*", + "zod": "catalog:", + "@goat-sdk/wallet-evm": "workspace:*" + }, + "peerDependencies": { + "@goat-sdk/core": "workspace:*" + } +} diff --git a/typescript/packages/plugins/0x/src/0x.plugin.ts b/typescript/packages/plugins/0x/src/0x.plugin.ts new file mode 100644 index 000000000..a26f2c481 --- /dev/null +++ b/typescript/packages/plugins/0x/src/0x.plugin.ts @@ -0,0 +1,36 @@ +import { PluginBase } from "@goat-sdk/core"; +import { Chain } from "@goat-sdk/core"; +import { EVMWalletClient } from "@goat-sdk/wallet-evm"; +import { ZeroExService } from "./0x.service"; + +export type ZeroExCtorParams = { + apiKey: string; +}; + +const supportedChains = [ + "1", + "42161", // arbitrum + "43114", // avalanche + "8453", // base + "81457", // blast + "56", // bsc + "59144", // linea + "5000", // mantle + "34443", // mode + "10", // optimism + "137", // polygon + "534352", // scroll + "480", // worldcoin +]; + +export class ZeroExPlugin extends PluginBase { + constructor(params: ZeroExCtorParams) { + super("0x", [new ZeroExService(params.apiKey)]); + } + + supportsChain = (chain: Chain) => chain.type === "evm" && supportedChains.includes(chain.id.toString()); +} + +export function zeroEx(params: ZeroExCtorParams) { + return new ZeroExPlugin(params); +} diff --git a/typescript/packages/plugins/0x/src/0x.service.ts b/typescript/packages/plugins/0x/src/0x.service.ts new file mode 100644 index 000000000..1e6e9c761 --- /dev/null +++ b/typescript/packages/plugins/0x/src/0x.service.ts @@ -0,0 +1,67 @@ +import { Tool } from "@goat-sdk/core"; +import { EVMWalletClient } from "@goat-sdk/wallet-evm"; +import { GetQuoteParameters } from "./parameters"; + +export class ZeroExService { + constructor(private readonly apiKey: string) {} + + private async makeRequest(queryParams: Record) { + const filteredParams = Object.fromEntries( + Object.entries(queryParams).filter(([_, v]) => v !== undefined), + ) as Record; + + const url = new URL( + `https://api.0x.org/swap/allowance-holder/quote?${new URLSearchParams(filteredParams).toString()}`, + ); + + const response = await fetch(url.toString(), { + method: "GET", + headers: { + "0x-api-key": this.apiKey, + "0x-version": "v2", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${JSON.stringify(await response.text(), null, 2)}`); + } + + return response.json(); + } + + @Tool({ + name: "0x_getQuote", + description: "Get a quote for a swap from 0x", + }) + async getQuote(walletClient: EVMWalletClient, parameters: GetQuoteParameters) { + const queryParams = { + chainId: walletClient.getChain().id.toString(), + sellToken: parameters.sellToken, + buyToken: parameters.buyToken, + sellAmount: parameters.sellAmount, + taker: parameters.taker, + txOrigin: parameters.txOrigin, + slippageBps: parameters.slippageBps?.toString(), + }; + + return await this.makeRequest(queryParams); + } + + @Tool({ + name: "0x_swap", + description: "Swap tokens using 0x", + }) + async swap(walletClient: EVMWalletClient, parameters: GetQuoteParameters) { + const quote = await this.getQuote(walletClient, parameters); + + const transaction = quote.transaction; + + const tx = await walletClient.sendTransaction({ + to: transaction.to, + value: transaction.value, + data: transaction.data, + }); + + return tx.hash; + } +} diff --git a/typescript/packages/plugins/0x/src/index.ts b/typescript/packages/plugins/0x/src/index.ts new file mode 100644 index 000000000..05921fd55 --- /dev/null +++ b/typescript/packages/plugins/0x/src/index.ts @@ -0,0 +1,2 @@ +export * from "./0x.plugin"; +export * from "./parameters"; diff --git a/typescript/packages/plugins/0x/src/parameters.ts b/typescript/packages/plugins/0x/src/parameters.ts new file mode 100644 index 000000000..165d3440b --- /dev/null +++ b/typescript/packages/plugins/0x/src/parameters.ts @@ -0,0 +1,26 @@ +import { createToolParameters } from "@goat-sdk/core"; +import { z } from "zod"; + +export class GetQuoteParameters extends createToolParameters( + z.object({ + chainId: z.number().describe("The chain ID to swap on"), + sellToken: z.string().describe("The token to sell"), + buyToken: z.string().describe("The token to buy"), + sellAmount: z.string().describe("The amount of tokens to sell in base units"), + taker: z + .string() + .describe("The address which holds the sellToken balance and has the allowance set for the swap"), + txOrigin: z + .string() + .optional() + .describe( + "The contract address of the external account that started the transaction. This is only needed if taker is a smart contract.", + ), + slippageBps: z + .number() + .optional() + .describe( + "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", + ), + }), +) {} diff --git a/typescript/packages/plugins/0x/tsconfig.json b/typescript/packages/plugins/0x/tsconfig.json new file mode 100644 index 000000000..b4ae67c1f --- /dev/null +++ b/typescript/packages/plugins/0x/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "../../../tsconfig.base.json", + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/typescript/packages/plugins/0x/tsup.config.ts b/typescript/packages/plugins/0x/tsup.config.ts new file mode 100644 index 000000000..2d38789ad --- /dev/null +++ b/typescript/packages/plugins/0x/tsup.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "tsup"; +import { treeShakableConfig } from "../../../tsup.config.base"; + +export default defineConfig({ + ...treeShakableConfig, +}); diff --git a/typescript/packages/plugins/0x/turbo.json b/typescript/packages/plugins/0x/turbo.json new file mode 100644 index 000000000..45f951676 --- /dev/null +++ b/typescript/packages/plugins/0x/turbo.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "inputs": ["src/**", "tsup.config.ts", "!./**/*.test.{ts,tsx}", "tsconfig.json"], + "dependsOn": ["^build"], + "outputs": ["dist/**"] + } + } +} diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml index 3d77a9d02..c79cbedd3 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -950,6 +950,18 @@ importers: specifier: 'catalog:' version: 3.23.8 + packages/plugins/0x: + dependencies: + '@goat-sdk/core': + specifier: workspace:* + version: link:../../core + '@goat-sdk/wallet-evm': + specifier: workspace:* + version: link:../../wallets/evm + zod: + specifier: 'catalog:' + version: 3.23.8 + packages/plugins/1inch: dependencies: '@goat-sdk/core': diff --git a/typescript/scripts/createPlugin.ts b/typescript/scripts/createPlugin.ts index b63e94942..93abf9eba 100755 --- a/typescript/scripts/createPlugin.ts +++ b/typescript/scripts/createPlugin.ts @@ -25,9 +25,9 @@ function validatePluginName(name: string): boolean { function getWalletClientImport(type: PluginOptions["type"]): string { switch (type) { case "evm": - return 'import type { EVMWalletClient } from "@goat-sdk/wallet-evm";'; + return 'import { EVMWalletClient } from "@goat-sdk/wallet-evm";'; case "solana": - return 'import type { SolanaWalletClient } from "@goat-sdk/wallet-solana";'; + return 'import { SolanaWalletClient } from "@goat-sdk/wallet-solana";'; case "any": return 'import { WalletClientBase } from "@goat-sdk/core";'; }