diff --git a/typescript/examples/virtuals-game/viem/.env.template b/typescript/examples/virtuals-game/viem/.env.template new file mode 100644 index 000000000..fb1580721 --- /dev/null +++ b/typescript/examples/virtuals-game/viem/.env.template @@ -0,0 +1,4 @@ +OPENAI_API_KEY= +WALLET_PRIVATE_KEY= +RPC_PROVIDER_URL= +VIRTUALS_GAME_API_KEY= diff --git a/typescript/examples/virtuals-game/viem/README.md b/typescript/examples/virtuals-game/viem/README.md new file mode 100644 index 000000000..9f2da5069 --- /dev/null +++ b/typescript/examples/virtuals-game/viem/README.md @@ -0,0 +1,15 @@ +# Vercel AI with viem Example + +## Setup + +Copy the `.env.template` and populate with your values. + +``` +cp .env.template .env +``` + +## Usage + +``` +npx ts-node index.ts +``` diff --git a/typescript/examples/virtuals-game/viem/index.ts b/typescript/examples/virtuals-game/viem/index.ts new file mode 100644 index 000000000..b3a6c2816 --- /dev/null +++ b/typescript/examples/virtuals-game/viem/index.ts @@ -0,0 +1,90 @@ +import { + ExecutableGameFunctionResponse, + ExecutableGameFunctionStatus, + GameAgent, + GameFunction, + GameWorker, +} from "@virtuals-protocol/game"; + +import { http } from "viem"; +import { createWalletClient } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { mode } from "viem/chains"; + +import { ToolBase, getTools } from "@goat-sdk/core"; +import { PEPE, USDC, erc20 } from "@goat-sdk/plugin-erc20"; +import { sendETH } from "@goat-sdk/wallet-evm"; +import { viem } from "@goat-sdk/wallet-viem"; +import type { JSONSchemaType } from "ajv"; +import { zodToJsonSchema } from "zod-to-json-schema"; + +require("dotenv").config(); + +const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`); + +const walletClient = createWalletClient({ + account: account, + transport: http(process.env.RPC_PROVIDER_URL), + chain: mode, +}); + +(async () => { + const tools: ToolBase[] = await getTools({ + wallet: viem(walletClient), + plugins: [sendETH(), erc20({ tokens: [USDC, PEPE] })], + }); + + const workerFunctions = tools.map((tool) => { + // biome-ignore lint/suspicious/noExplicitAny: Fix types later + const schema = zodToJsonSchema(tool.parameters as any, { + target: "jsonSchema7", + }) as JSONSchemaType; + + const properties = Object.keys(schema.properties); + + const args = properties.map((property) => ({ + name: property, + description: schema.properties[property].description ?? "", + })); + + return new GameFunction({ + name: tool.name, + description: tool.description, + args: args, + executable: async (args) => { + try { + const result = await tool.execute(args); + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Done, + JSON.stringify(result), + ); + } catch (e) { + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + `Failed to execute tool: ${e}`, + ); + } + }, + }); + }); + + const onChainWorker = new GameWorker({ + id: "onchain_worker", + name: "Onchain worker", + description: "Worker that executes onchain actions", + functions: [...workerFunctions], + }); + + const agent = new GameAgent(process.env.VIRTUALS_GAME_API_KEY as string, { + name: "Onchain agent", + goal: "Swap 0.01 USDC to MODE", + description: "An agent that executes onchain actions", + workers: [onChainWorker], + }); + + await agent.init(); + + await agent.run(10, { + verbose: true, + }); +})(); diff --git a/typescript/examples/virtuals-game/viem/package.json b/typescript/examples/virtuals-game/viem/package.json new file mode 100644 index 000000000..7d3ada6cb --- /dev/null +++ b/typescript/examples/virtuals-game/viem/package.json @@ -0,0 +1,27 @@ +{ + "name": "goat-examples-virtuals-game-viem", + "version": "0.2.7", + "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-erc20": "workspace:*", + "@goat-sdk/wallet-evm": "workspace:*", + "@goat-sdk/plugin-kim": "workspace:*", + "@goat-sdk/wallet-viem": "workspace:*", + "@virtuals-protocol/game": "0.1.4", + "ai": "4.0.27", + "ajv": "8.17.1", + "dotenv": "^16.4.5", + "viem": "2.21.49", + "zod": "catalog:", + "zod-to-json-schema": "3.24.1" + } +} diff --git a/typescript/examples/virtuals-game/viem/toolCalling.ts b/typescript/examples/virtuals-game/viem/toolCalling.ts new file mode 100644 index 000000000..49a80dfaa --- /dev/null +++ b/typescript/examples/virtuals-game/viem/toolCalling.ts @@ -0,0 +1,95 @@ +import { + ExecutableGameFunctionResponse, + ExecutableGameFunctionStatus, + GameAgent, + GameFunction, + GameWorker, +} from "@virtuals-protocol/game"; + +import { http } from "viem"; +import { createWalletClient } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { mode } from "viem/chains"; + +import { openai } from "@ai-sdk/openai"; +import { getOnChainTools } from "@goat-sdk/adapter-vercel-ai"; +import { MODE, PEPE, USDC, erc20 } from "@goat-sdk/plugin-erc20"; +import { kim } from "@goat-sdk/plugin-kim"; +import { sendETH } from "@goat-sdk/wallet-evm"; +import { viem } from "@goat-sdk/wallet-viem"; +import { generateText } from "ai"; + +require("dotenv").config(); + +const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`); + +const walletClient = createWalletClient({ + account: account, + transport: http(process.env.RPC_PROVIDER_URL), + chain: mode, +}); + +(async () => { + const tools = await getOnChainTools({ + wallet: viem(walletClient), + plugins: [sendETH(), erc20({ tokens: [USDC, MODE] }), kim()], + }); + + const swap = new GameFunction({ + name: "Swap tokens", + description: "Allows you to swap tokens", + args: [ + { + name: "from", + description: "The token to swap from", + }, + { + name: "to", + description: "The token to swap to", + }, + { + name: "amount", + description: "The amount of tokens to swap", + }, + ], + executable: async (args) => { + try { + const result = await generateText({ + model: openai("gpt-4o-mini"), + tools: tools, + maxSteps: 10, + prompt: `Swap tokens using Kim protocol in a single hop: ${JSON.stringify(args)}`, + onStepFinish: (event) => { + console.log(event.toolResults); + }, + }); + return new ExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Done, result.text); + } catch (e) { + return new ExecutableGameFunctionResponse( + ExecutableGameFunctionStatus.Failed, + `Failed to execute tool: ${e}`, + ); + } + }, + }); + + const onChainWorker = new GameWorker({ + id: "onchain_worker", + name: "Onchain worker", + description: "Worker that executes onchain actions", + functions: [swap], + }); + + const agent = new GameAgent(process.env.VIRTUALS_GAME_API_KEY as string, { + name: "Onchain agent", + goal: "Swap 0.01 USDC to MODE", + description: "An agent that executes onchain actions", + workers: [onChainWorker], + }); + + await agent.init(); + + await agent.run(10, { + verbose: true, + }); +})(); diff --git a/typescript/examples/virtuals-game/viem/tsconfig.json b/typescript/examples/virtuals-game/viem/tsconfig.json new file mode 100644 index 000000000..00ef74912 --- /dev/null +++ b/typescript/examples/virtuals-game/viem/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["index.ts", "toolCalling.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/typescript/pnpm-lock.yaml b/typescript/pnpm-lock.yaml index eea388f29..c39f6011f 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -827,6 +827,51 @@ importers: specifier: 2.21.49 version: 2.21.49(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.24.1) + examples/virtuals-game/viem: + dependencies: + '@ai-sdk/openai': + specifier: ^1.0.4 + version: 1.0.13(zod@3.23.8) + '@goat-sdk/adapter-vercel-ai': + specifier: workspace:* + version: link:../../../packages/adapters/vercel-ai + '@goat-sdk/core': + specifier: workspace:* + version: link:../../../packages/core + '@goat-sdk/plugin-erc20': + specifier: workspace:* + version: link:../../../packages/plugins/erc20 + '@goat-sdk/plugin-kim': + specifier: workspace:* + version: link:../../../packages/plugins/kim + '@goat-sdk/wallet-evm': + specifier: workspace:* + version: link:../../../packages/wallets/evm + '@goat-sdk/wallet-viem': + specifier: workspace:* + version: link:../../../packages/wallets/viem + '@virtuals-protocol/game': + specifier: 0.1.4 + version: 0.1.4 + ai: + specifier: 4.0.27 + version: 4.0.27(react@18.3.1)(zod@3.23.8) + ajv: + specifier: 8.17.1 + version: 8.17.1 + dotenv: + specifier: ^16.4.5 + version: 16.4.7 + viem: + specifier: 2.21.49 + version: 2.21.49(bufferutil@4.0.9)(typescript@5.6.3)(utf-8-validate@5.0.10)(zod@3.23.8) + zod: + specifier: 'catalog:' + version: 3.23.8 + zod-to-json-schema: + specifier: 3.24.1 + version: 3.24.1(zod@3.23.8) + packages/adapters/eleven-labs: dependencies: '@goat-sdk/core': @@ -5738,6 +5783,9 @@ packages: '@upstash/vector@1.1.7': resolution: {integrity: sha512-yxgXdH/Z2uX8XUVrLeo6ZNci2Z8EzXGwjIZ8EZwctF5+oiBIKwz8tsh/yUesyGrbuQ+ViBPGtZWV5vLFbp649w==} + '@virtuals-protocol/game@0.1.4': + resolution: {integrity: sha512-H8Tr3CYPwVxCeJGsGqvKAWnJHPiAeeLk2c32p/O1ZC1GMEMoJTPgKVtnDE7xY7AOQn69jOVK0BQRNZH11gvdlg==} + '@vitest/expect@0.34.6': resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} @@ -6170,6 +6218,18 @@ packages: zod: optional: true + ai@4.0.27: + resolution: {integrity: sha512-3wMFXNAR6a269R7k4ZDQ9FfF3fb8gQlnlIE4kWaQfZDUTEM5aWgQdbzFhsr9Q5DULCyZg7l/bFpGd48cRfvLaA==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + ai@4.0.3: resolution: {integrity: sha512-nx5cNMldOQ72hwxL60NLtRnsmQd5Bo887Wznvxt8F5xnmjdeXRpz4ixp+0xGA88X7wiCn6c+xrhGEb9fesi/Tw==} engines: {node: '>=18'} @@ -11874,6 +11934,16 @@ snapshots: react: 18.3.1 zod: 3.24.1 + '@ai-sdk/react@1.0.7(react@18.3.1)(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 2.0.5(zod@3.23.8) + '@ai-sdk/ui-utils': 1.0.6(zod@3.23.8) + swr: 2.3.0(react@18.3.1) + throttleit: 2.1.0 + optionalDependencies: + react: 18.3.1 + zod: 3.23.8 + '@ai-sdk/react@1.0.7(react@18.3.1)(zod@3.24.1)': dependencies: '@ai-sdk/provider-utils': 2.0.5(zod@3.24.1) @@ -11927,6 +11997,14 @@ snapshots: optionalDependencies: zod: 3.24.1 + '@ai-sdk/ui-utils@1.0.6(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 1.0.3 + '@ai-sdk/provider-utils': 2.0.5(zod@3.23.8) + zod-to-json-schema: 3.24.1(zod@3.23.8) + optionalDependencies: + zod: 3.23.8 + '@ai-sdk/ui-utils@1.0.6(zod@3.24.1)': dependencies: '@ai-sdk/provider': 1.0.3 @@ -18307,6 +18385,12 @@ snapshots: '@upstash/vector@1.1.7': {} + '@virtuals-protocol/game@0.1.4': + dependencies: + axios: 1.7.9 + transitivePeerDependencies: + - debug + '@vitest/expect@0.34.6': dependencies: '@vitest/spy': 0.34.6 @@ -19595,6 +19679,19 @@ snapshots: react: 18.3.1 zod: 3.24.1 + ai@4.0.27(react@18.3.1)(zod@3.23.8): + dependencies: + '@ai-sdk/provider': 1.0.3 + '@ai-sdk/provider-utils': 2.0.5(zod@3.23.8) + '@ai-sdk/react': 1.0.7(react@18.3.1)(zod@3.23.8) + '@ai-sdk/ui-utils': 1.0.6(zod@3.23.8) + '@opentelemetry/api': 1.9.0 + jsondiffpatch: 0.6.0 + zod-to-json-schema: 3.24.1(zod@3.23.8) + optionalDependencies: + react: 18.3.1 + zod: 3.23.8 + ai@4.0.3(react@18.3.1)(zod@3.23.8): dependencies: '@ai-sdk/provider': 1.0.1