diff --git a/docs/plugins.mdx b/docs/plugins.mdx index b63ab8a34..c862b2364 100644 --- a/docs/plugins.mdx +++ b/docs/plugins.mdx @@ -51,7 +51,34 @@ This allows plugins to: ---- ## Creating your own GOAT plugin -Building a custom GOAT plugin is straightforward. Below, we'll walk you through creating a plugin that signs messages with "BAAAA" 🐐 prefixed to them. + +### Using the Plugin Generator + +The easiest way to create a new plugin is using the `create-plugin` command. This command generates all the necessary files and configuration for a new plugin. + +```bash +# Create a plugin with default type (any) +pnpm create-plugin -n your-plugin-name + +# Create a plugin for a specific chain type +pnpm create-plugin -n your-plugin-name -t evm # For EVM chains +pnpm create-plugin -n your-plugin-name -t solana # For Solana +``` + +The command will generate: +- A package.json with standard metadata and dependencies +- TypeScript configuration files (tsconfig.json, tsup.config.ts) +- A basic plugin structure in the src directory: + - parameters.ts - Example parameters using Zod schema + - your-plugin-name.service.ts - Service class with an example tool + - your-plugin-name.plugin.ts - Plugin class extending PluginBase + - index.ts - Exports for your plugin + +After generating the plugin, you can customize the generated code according to your needs following the guide below. + +### Manual Creation (Example) + +For a detailed understanding of plugin structure, let's walk through creating a plugin that signs messages with "BAAAA" 🐐 prefixed to them. @@ -79,10 +106,53 @@ export const baaaSigner = () => new BAAAASigner(); There are two ways to add tools to the plugin: - 1. Using the `getTools` and `createTool` functions - 2. Using the `@Tool` decorator on our own class + 1. Using the `@Tool` decorator on our own class + 2. Using the `getTools` and `createTool` functions to create tools dynamically + + #### Option 1: Using the `@Tool` decorator + The `@Tool` decorator is a way to create tools in a more declarative way. + You can create a class and decorate its methods with the `@Tool` decorator to create tools. + The tool methods will receive the wallet client as the first argument and the parameters as the second argument. + + ```typescript + import { Tool } from "@goat-sdk/core"; - #### Option 1: Using the `getTools` and `createTool` functions + class MyTools { + @Tool({ + name: "sign_message_baaaa", + description: "Sign a message with 'BAAAA' prefix", + parameters: z.object({ + message: z.string(), + }), + }) + async signMessage(walletClient: WalletClientBase, parameters: z.infer) { + const originalMessage: string = parameters.message; + const prefixedMessage = `BAAAA${originalMessage}`; + const signed = await walletClient.signMessage(prefixedMessage); + return signed.signedMessage; + } + } + ``` + + Once we have our class we now need to import it in our plugin class. + + ```typescript + import { PluginBase, WalletClientBase } from "@goat-sdk/core"; + import { MyTools } from "./my-tools.service.ts"; + +export class BAAAASigner extends PluginBase { + constructor() { + // Import the tools we created in the previous step here + super("baaaSigner", [new MyTools()]); + } + + supportsChain = (chain: Chain) => true; +} + +export const baaaSigner = () => new BAAAASigner(); + ``` + + #### Option 2: Using the `getTools` and `createTool` functions We will start by implementing the `getTools` method in our plugin class. Inside the method, we will return an array of tools created using the `createTool` function. @@ -124,49 +194,6 @@ export class BAAAASigner extends PluginBase { } // We export a factory function to create a new instance of the plugin -export const baaaSigner = () => new BAAAASigner(); - ``` - - #### Option 2: Using the `@Tool` decorator - The `@Tool` decorator is a way to create tools in a more declarative way. - You can create a class and decorate its methods with the `@Tool` decorator to create tools. - The tool methods will receive the wallet client as the first argument and the parameters as the second argument. - - ```typescript - import { Tool } from "@goat-sdk/core"; - - class MyTools { - @Tool({ - name: "sign_message_baaaa", - description: "Sign a message with 'BAAAA' prefix", - parameters: z.object({ - message: z.string(), - }), - }) - async signMessage(walletClient: WalletClientBase, parameters: z.infer) { - const originalMessage: string = parameters.message; - const prefixedMessage = `BAAAA${originalMessage}`; - const signed = await walletClient.signMessage(prefixedMessage); - return signed.signedMessage; - } - } - ``` - - Once we have our class we now need to import it in our plugin class. - - ```typescript - import { PluginBase, WalletClientBase } from "@goat-sdk/core"; - import { MyTools } from "./my-tools.service.ts"; - -export class BAAAASigner extends PluginBase { - constructor() { - // Import the tools we created in the previous step here - super("baaaSigner", [new MyTools()]); - } - - supportsChain = (chain: Chain) => true; -} - export const baaaSigner = () => new BAAAASigner(); ``` diff --git a/typescript/package.json b/typescript/package.json index dcadac100..ee4184579 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -4,6 +4,7 @@ "version": "0.1.1", "scripts": { "prepare": "cd .. && pnpm install && cd typescript", + "create-plugin": "tsx scripts/createPlugin.ts", "build": "pnpm turbo build", "build:libs": "pnpm turbo --filter \"./packages/**\" build", "build:libs:prod": "cross-env NODE_ENV=production pnpm build:libs", @@ -27,6 +28,7 @@ "ts-node": "10.9.2", "tsc-alias": "1.8.10", "tsup": "8.3.5", + "tsx": "4.19.2", "turbo": "2.3.1", "typescript": "5.6.3", "vitest": "2.1.5" @@ -47,5 +49,8 @@ "url": "https://github.com/crossmint/goat/issues" }, "keywords": ["ai", "agents", "web3"], - "packageManager": "pnpm@9.14.2" + "packageManager": "pnpm@9.14.2", + "dependencies": { + "commander": "13.0.0" + } } diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml index e0fcd5c2a..1fa092827 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -64,6 +64,10 @@ catalogs: importers: .: + dependencies: + commander: + specifier: 13.0.0 + version: 13.0.0 devDependencies: '@biomejs/biome': specifier: 1.9.4 @@ -94,7 +98,10 @@ importers: version: 1.8.10 tsup: specifier: 8.3.5 - version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@1.21.7)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.7.0) + version: 8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@1.21.7)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0) + tsx: + specifier: 4.19.2 + version: 4.19.2 turbo: specifier: 2.3.1 version: 2.3.1 @@ -1463,6 +1470,18 @@ importers: specifier: 'catalog:' version: 3.23.8 + packages/plugins/test-plugin: + 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/uniswap: dependencies: '@goat-sdk/core': @@ -3225,6 +3244,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.24.2': resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} @@ -3243,6 +3268,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.24.2': resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} engines: {node: '>=18'} @@ -3261,6 +3292,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.24.2': resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} engines: {node: '>=18'} @@ -3279,6 +3316,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.24.2': resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} engines: {node: '>=18'} @@ -3297,6 +3340,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.24.2': resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} engines: {node: '>=18'} @@ -3315,6 +3364,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.24.2': resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} engines: {node: '>=18'} @@ -3333,6 +3388,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} engines: {node: '>=18'} @@ -3351,6 +3412,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} engines: {node: '>=18'} @@ -3369,6 +3436,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.24.2': resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} engines: {node: '>=18'} @@ -3387,6 +3460,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.24.2': resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} engines: {node: '>=18'} @@ -3405,6 +3484,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.24.2': resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} engines: {node: '>=18'} @@ -3423,6 +3508,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.24.2': resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} engines: {node: '>=18'} @@ -3441,6 +3532,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.24.2': resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} engines: {node: '>=18'} @@ -3459,6 +3556,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.24.2': resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} engines: {node: '>=18'} @@ -3477,6 +3580,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.24.2': resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} engines: {node: '>=18'} @@ -3495,6 +3604,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.24.2': resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} engines: {node: '>=18'} @@ -3513,6 +3628,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.24.2': resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} engines: {node: '>=18'} @@ -3537,12 +3658,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.24.2': resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} engines: {node: '>=18'} @@ -3561,6 +3694,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} engines: {node: '>=18'} @@ -3579,6 +3718,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.24.2': resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} engines: {node: '>=18'} @@ -3597,6 +3742,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.24.2': resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} engines: {node: '>=18'} @@ -3615,6 +3766,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.24.2': resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} engines: {node: '>=18'} @@ -3633,6 +3790,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.24.2': resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} engines: {node: '>=18'} @@ -6900,6 +7063,10 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@13.0.0: + resolution: {integrity: sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -7398,6 +7565,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.24.2: resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} @@ -7828,6 +8000,9 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + git-package-json@1.4.10: resolution: {integrity: sha512-DRAcvbzd2SxGK7w8OgYfvKqhFliT5keX0lmSmVdgScgf1kkl5tbbo7Pam6uYoCa1liOiipKxQZG8quCtGWl/fA==} @@ -10203,6 +10378,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -10966,6 +11144,11 @@ packages: typescript: optional: true + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -14442,6 +14625,9 @@ snapshots: '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.23.1': + optional: true + '@esbuild/aix-ppc64@0.24.2': optional: true @@ -14451,6 +14637,9 @@ snapshots: '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.23.1': + optional: true + '@esbuild/android-arm64@0.24.2': optional: true @@ -14460,6 +14649,9 @@ snapshots: '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.23.1': + optional: true + '@esbuild/android-arm@0.24.2': optional: true @@ -14469,6 +14661,9 @@ snapshots: '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.23.1': + optional: true + '@esbuild/android-x64@0.24.2': optional: true @@ -14478,6 +14673,9 @@ snapshots: '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.23.1': + optional: true + '@esbuild/darwin-arm64@0.24.2': optional: true @@ -14487,6 +14685,9 @@ snapshots: '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.23.1': + optional: true + '@esbuild/darwin-x64@0.24.2': optional: true @@ -14496,6 +14697,9 @@ snapshots: '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.23.1': + optional: true + '@esbuild/freebsd-arm64@0.24.2': optional: true @@ -14505,6 +14709,9 @@ snapshots: '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.23.1': + optional: true + '@esbuild/freebsd-x64@0.24.2': optional: true @@ -14514,6 +14721,9 @@ snapshots: '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.23.1': + optional: true + '@esbuild/linux-arm64@0.24.2': optional: true @@ -14523,6 +14733,9 @@ snapshots: '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.23.1': + optional: true + '@esbuild/linux-arm@0.24.2': optional: true @@ -14532,6 +14745,9 @@ snapshots: '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.23.1': + optional: true + '@esbuild/linux-ia32@0.24.2': optional: true @@ -14541,6 +14757,9 @@ snapshots: '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.23.1': + optional: true + '@esbuild/linux-loong64@0.24.2': optional: true @@ -14550,6 +14769,9 @@ snapshots: '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.23.1': + optional: true + '@esbuild/linux-mips64el@0.24.2': optional: true @@ -14559,6 +14781,9 @@ snapshots: '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.23.1': + optional: true + '@esbuild/linux-ppc64@0.24.2': optional: true @@ -14568,6 +14793,9 @@ snapshots: '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.23.1': + optional: true + '@esbuild/linux-riscv64@0.24.2': optional: true @@ -14577,6 +14805,9 @@ snapshots: '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.23.1': + optional: true + '@esbuild/linux-s390x@0.24.2': optional: true @@ -14586,6 +14817,9 @@ snapshots: '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.23.1': + optional: true + '@esbuild/linux-x64@0.24.2': optional: true @@ -14598,9 +14832,15 @@ snapshots: '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.23.1': + optional: true + '@esbuild/netbsd-x64@0.24.2': optional: true + '@esbuild/openbsd-arm64@0.23.1': + optional: true + '@esbuild/openbsd-arm64@0.24.2': optional: true @@ -14610,6 +14850,9 @@ snapshots: '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.23.1': + optional: true + '@esbuild/openbsd-x64@0.24.2': optional: true @@ -14619,6 +14862,9 @@ snapshots: '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.23.1': + optional: true + '@esbuild/sunos-x64@0.24.2': optional: true @@ -14628,6 +14874,9 @@ snapshots: '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.23.1': + optional: true + '@esbuild/win32-arm64@0.24.2': optional: true @@ -14637,6 +14886,9 @@ snapshots: '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.23.1': + optional: true + '@esbuild/win32-ia32@0.24.2': optional: true @@ -14646,6 +14898,9 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true + '@esbuild/win32-x64@0.23.1': + optional: true + '@esbuild/win32-x64@0.24.2': optional: true @@ -20449,6 +20704,8 @@ snapshots: commander@12.1.0: {} + commander@13.0.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -20990,6 +21247,33 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + esbuild@0.24.2: optionalDependencies: '@esbuild/aix-ppc64': 0.24.2 @@ -21594,6 +21878,10 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + git-package-json@1.4.10: dependencies: deffy: 2.2.4 @@ -23803,12 +24091,13 @@ snapshots: postcss: 8.4.49 ts-node: 10.9.2(@swc/core@1.10.1(@swc/helpers@0.5.15))(@types/node@22.7.4)(typescript@5.6.3) - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.4.49)(yaml@2.7.0): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.4.49)(tsx@4.19.2)(yaml@2.7.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 postcss: 8.4.49 + tsx: 4.19.2 yaml: 2.7.0 postcss-nested@6.2.0(postcss@8.4.49): @@ -24337,6 +24626,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -25256,7 +25547,7 @@ snapshots: - supports-color - ts-node - tsup@8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@1.21.7)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.7.0): + tsup@8.3.5(@swc/core@1.10.1(@swc/helpers@0.5.15))(jiti@1.21.7)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.7.0): dependencies: bundle-require: 5.1.0(esbuild@0.24.2) cac: 6.7.14 @@ -25266,7 +25557,7 @@ snapshots: esbuild: 0.24.2 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.4.49)(yaml@2.7.0) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.4.49)(tsx@4.19.2)(yaml@2.7.0) resolve-from: 5.0.0 rollup: 4.29.1 source-map: 0.8.0-beta.0 @@ -25284,6 +25575,13 @@ snapshots: - tsx - yaml + tsx@4.19.2: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 diff --git a/typescript/scripts/createPlugin.ts b/typescript/scripts/createPlugin.ts new file mode 100755 index 000000000..b63e94942 --- /dev/null +++ b/typescript/scripts/createPlugin.ts @@ -0,0 +1,296 @@ +#!/usr/bin/env node +import * as fs from "node:fs"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Command } from "commander"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +type PluginType = "evm" | "solana" | "any"; + +const program = new Command(); + +interface PluginOptions { + name: string; + type: "evm" | "solana" | "any"; +} + +// Validate plugin name format +function validatePluginName(name: string): boolean { + return /^[a-z0-9-]+$/.test(name); +} + +// Get wallet client import based on plugin type +function getWalletClientImport(type: PluginOptions["type"]): string { + switch (type) { + case "evm": + return 'import type { EVMWalletClient } from "@goat-sdk/wallet-evm";'; + case "solana": + return 'import type { SolanaWalletClient } from "@goat-sdk/wallet-solana";'; + case "any": + return 'import { WalletClientBase } from "@goat-sdk/core";'; + } +} + +// Get wallet client type based on plugin type +function getWalletClientType(type: PluginOptions["type"]): string { + switch (type) { + case "evm": + return "EVMWalletClient"; + case "solana": + return "SolanaWalletClient"; + case "any": + return "WalletClientBase"; + } +} + +// Get dependencies based on plugin type +function getDependencies(type: PluginOptions["type"]): Record { + const deps: Record = { + "@goat-sdk/core": "workspace:*", + zod: "catalog:", + }; + + switch (type) { + case "evm": + deps["@goat-sdk/wallet-evm"] = "workspace:*"; + break; + case "solana": + deps["@goat-sdk/wallet-solana"] = "workspace:*"; + break; + } + + return deps; +} + +// Create package.json content +function createPackageJson(options: PluginOptions): string { + const { name, type } = options; + const dependencies = getDependencies(type); + + const packageJson = { + name: `@goat-sdk/plugin-${name}`, + 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, + peerDependencies: { + "@goat-sdk/core": "workspace:*", + }, + }; + return JSON.stringify(packageJson, null, 4); +} + +// Create parameters.ts content +function createParametersContent(): string { + return `import { createToolParameters } from "@goat-sdk/core"; +import { z } from "zod"; + +export class ExampleParameters extends createToolParameters( + z.object({ + exampleField: z.string().describe("An example field"), + }), +) {} +`; +} + +// Convert kebab-case to PascalCase +function kebabToPascalCase(str: string): string { + return str + .split("-") + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(""); +} + +// Create service.ts content +function createServiceContent(options: PluginOptions): string { + const { name, type } = options; + const walletClientImport = getWalletClientImport(type); + const walletClientType = getWalletClientType(type); + const className = kebabToPascalCase(name); + const functionName = name.replace(/-/g, "_"); + + return `import { Tool } from "@goat-sdk/core"; +${walletClientImport} +import { ExampleParameters } from "./parameters"; + +export class ${className}Service { + @Tool({ + name: "${functionName}_example", + description: "An example method in ${className}Service", + }) + async doSomething(walletClient: ${walletClientType}, parameters: ExampleParameters) { + // Implementation goes here + return "Hello from ${className}Service!"; + } +} +`; +} + +// Create plugin.ts content +function createPluginContent(options: PluginOptions): string { + const { name } = options; + const className = kebabToPascalCase(name); + const functionName = name.replace(/-/g, ""); + + return `import { PluginBase } from "@goat-sdk/core"; +import { ${className}Service } from "./${name}.service"; + +export class ${className}Plugin extends PluginBase { + constructor() { + super("${name}", [new ${className}Service()]); + } + + supportsChain = () => true; +} + +export function ${functionName}() { + return new ${className}Plugin(); +} +`; +} + +// Create index.ts content +function createIndexContent(options: PluginOptions): string { + const { name } = options; + return `export * from "./${name}.plugin"; +export * from "./parameters"; +`; +} + +// Create all necessary files for the plugin +async function createPlugin(options: PluginOptions): Promise { + const { name } = options; + const rootDir = process.cwd(); + const pluginDir = path.join(rootDir, "packages", "plugins", name); + const srcDir = path.join(pluginDir, "src"); + + // Create directories + fs.mkdirSync(pluginDir, { recursive: true }); + fs.mkdirSync(srcDir, { recursive: true }); + + // Copy configuration files from superfluid plugin + const superfluidDir = path.join(rootDir, "packages", "plugins", "superfluid"); + const configFiles = ["tsconfig.json", "tsup.config.ts", "turbo.json"]; + + for (const file of configFiles) { + const sourcePath = path.join(superfluidDir, file); + const targetPath = path.join(pluginDir, file); + try { + if (fs.existsSync(sourcePath)) { + fs.copyFileSync(sourcePath, targetPath); + } else { + console.warn(`Warning: Could not find ${file} in superfluid plugin`); + } + } catch (error) { + console.error(`Error copying ${file}:`, error); + throw error; + } + } + + // Create all files first + console.log("Creating plugin files..."); + // Create package.json + const packageJsonPath = path.join(pluginDir, "package.json"); + fs.writeFileSync(packageJsonPath, createPackageJson(options)); + console.log("✓ Created package.json"); + + // Create source files + const sourceFiles = { + "parameters.ts": createParametersContent(), + [`${name}.service.ts`]: createServiceContent(options), + [`${name}.plugin.ts`]: createPluginContent(options), + "index.ts": createIndexContent(options), + }; + + for (const [file, content] of Object.entries(sourceFiles)) { + const filePath = path.join(srcDir, file); + fs.writeFileSync(filePath, content); + console.log(`✓ Created ${file}`); + } + + // Verify all required files exist + const requiredFiles = [ + packageJsonPath, + path.join(pluginDir, "tsconfig.json"), + path.join(pluginDir, "tsup.config.ts"), + path.join(pluginDir, "turbo.json"), + ...Object.keys(sourceFiles).map((file) => path.join(srcDir, file)), + ]; + + console.log("\nVerifying required files..."); + for (const file of requiredFiles) { + if (!fs.existsSync(file)) { + throw new Error(`Required file missing: ${file}`); + } + console.log(`✓ Verified ${path.basename(file)}`); + } + + // Install dependencies and build + try { + console.log("\nInstalling dependencies..."); + const { execSync } = await import("node:child_process"); + await execSync("pnpm install", { stdio: "inherit", cwd: rootDir }); + console.log("✓ Dependencies installed"); + console.log("\nBuilding plugin..."); + await execSync("pnpm build", { stdio: "inherit", cwd: pluginDir }); + console.log("✓ Plugin built successfully"); + } catch (error) { + console.error("Error during install/build:", error); + throw error; + } + + console.log(`\nPlugin ${name} created successfully!`); +} + +// Set up command line interface +program + .name("createPlugin") + .description("Create a new GOAT SDK plugin") + .requiredOption("-n, --name ", "plugin name (lowercase, hyphen-separated)") + .option("-t, --type ", "plugin type (evm, solana, or any)", "any") + .action(async (options: { name: string; type: string }) => { + const { name, type } = options; + + // Validate inputs + if (!validatePluginName(name)) { + console.error("Error: Plugin name must be lowercase and may only contain letters, numbers, and hyphens"); + process.exit(1); + } + + const validTypes = ["evm", "solana", "any"] as const; + const pluginType = type || "any"; + + if (!validTypes.includes(pluginType as (typeof validTypes)[number])) { + console.error("Error: Plugin type must be one of: evm, solana, any"); + process.exit(1); + } + + try { + await createPlugin({ name, type: pluginType as PluginOptions["type"] }); + } catch (error) { + console.error("Error creating plugin:", error); + process.exit(1); + } + }); + +program.parse(); diff --git a/typescript/scripts/package.json b/typescript/scripts/package.json new file mode 100644 index 000000000..28051b67f --- /dev/null +++ b/typescript/scripts/package.json @@ -0,0 +1,5 @@ +{ + "name": "@goat-sdk/scripts", + "private": true, + "type": "module" +} diff --git a/typescript/scripts/tsconfig.json b/typescript/scripts/tsconfig.json new file mode 100644 index 000000000..93939af46 --- /dev/null +++ b/typescript/scripts/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": ["node"] + }, + "ts-node": { + "esm": true, + "experimentalSpecifiers": true, + "moduleTypes": { + "scripts/*.ts": "esm" + } + }, + "include": ["./**/*"], + "exclude": ["node_modules", "dist"] +}