Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Mastra ai adapter #394

Merged
merged 8 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions typescript/.changeset/green-pumpkins-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@goat-sdk/adapter-mastra": patch
---

Release package
3 changes: 3 additions & 0 deletions typescript/examples/by-framework/mastra/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OPENAI_API_KEY=
WALLET_PRIVATE_KEY=
RPC_PROVIDER_URL=
76 changes: 76 additions & 0 deletions typescript/examples/by-framework/mastra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@

<div align="center">
<img src="https://github.com/user-attachments/assets/5fc7f121-259c-492c-8bca-f15fe7eb830c" alt="GOAT" width="100px" height="auto" style="object-fit: contain;">
</div>

# Mastra
## 🚀 Quickstart

This example demonstrates how to use GOAT to allow a [Mastra](https://github.com/mastra-ai/mastra) agent to **send and receive USDC** on EVM networks. This example uses [Base Sepolia](https://base.org) but you can implement it with any other EVM network by changing the chain and RPC URL.

You can use this example with any other agent framework, chain, and wallet of your choice.

## Setup
1. Clone the repository:
```bash
git clone https://github.com/goat-sdk/goat.git && cd goat
```

2. Run the following commands from the `typescript` directory:
```bash
cd typescript
pnpm install
pnpm build
```

3. Go to the example directory:
```bash
cd examples/by-framework/mastra
```

4. Copy the `.env.template` and populate with your values:
```bash
cp .env.template .env
```
- `OPENAI_API_KEY`
- `WALLET_PRIVATE_KEY`
- `RPC_PROVIDER_URL`

5. Add some test funds to your wallet by going to any [Base Sepolia Faucet](https://www.alchemy.com/faucets/base-sepolia)

## Usage
1. Run the interactive CLI:
```bash
pnpm tsx src/index.ts
```

2. Chat with the agent:
- Check your balance for ERC-20 tokens
- Send ERC-20 tokens to another address
- Check your balance again to see the tokens you just sent

## Using in production
In production, developers require advanced wallet setups that utilize [smart wallets](https://docs.goat-sdk.com/concepts/smart-wallets), which allow them to:
1. **Increase security** by setting programmable permissions (e.g. limiting fund amounts, restricting contract interactions, and defining required signatures)
2. **Maintain regulatory compliance** by ensuring agent wallets are non-custodial. This means that:
- Launchpads, wallet providers, or agent platforms never have access to agents' wallets.
- Agent platforms do not require money transmitter licenses.

### Agent Wallets
[Crossmint](https://docs.crossmint.com/wallets/quickstarts/agent-wallets) offers one of the most advanced solutions for agent developers and launchpads: [Agent Wallets](https://docs.crossmint.com/wallets/quickstarts/agent-wallets).

To integrate Agent Wallets with GOAT, check out the following quickstarts:
1. Agent Wallets Quickstart [[EVM](https://github.com/goat-sdk/goat/tree/main/typescript/examples/by-wallet/crossmint-smart-wallets), [Solana](https://github.com/goat-sdk/goat/tree/main/typescript/examples/by-wallet/crossmint-smart-wallets)]
2. [Agent Launchpad Starter Kit](https://github.com/Crossmint/agent-launchpad-starter-kit/)




<footer>
<br/>
<br/>
<div>
<img src="https://github.com/user-attachments/assets/59fa5ddc-9d47-4d41-a51a-64f6798f94bd" alt="GOAT" width="100%" height="auto" style="object-fit: contain; max-width: 800px;">

<div>
</footer>
30 changes: 30 additions & 0 deletions typescript/examples/by-framework/mastra/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "goat-examples-mastra",
"version": "0.0.0",
"type": "module",
"description": "",
"private": true,
"scripts": {
"test": "vitest run --passWithNoTests"
},
"author": "",
"license": "MIT",
"dependencies": {
"@ai-sdk/openai": "~1.0.4",
"@goat-sdk/adapter-mastra": "0.1.0",
"@goat-sdk/core": "0.4.9",
"@goat-sdk/plugin-erc20": "0.2.14",
"@goat-sdk/wallet-evm": "0.2.11",
"@goat-sdk/wallet-viem": "0.2.12",
"@mastra/core": "0.6.3",
"dotenv": "^16.4.5",
"viem": "2.23.4",
"zod": "3.23.8"
},
"devDependencies": {
"@types/node": "22.13.13",
"mastra": "0.4.2",
"tsx": "4.19.2",
"typescript": "5.8.2"
}
}
46 changes: 46 additions & 0 deletions typescript/examples/by-framework/mastra/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import readline from "node:readline";

import { mastra } from "./mastra";

(async () => {
// 1. Import the agent
const agent = mastra.getAgent("moneyTransmitter");

// 2. Create a readline interface to interact with the agent
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

while (true) {
const prompt = await new Promise<string>((resolve) => {
rl.question('Enter your prompt (or "exit" to quit): ', resolve);
});

if (prompt === "exit") {
rl.close();
break;
}

console.log("\n-------------------\n");
console.log("TOOLS CALLED");
console.log("\n-------------------\n");
try {
// 3. Generate a response
const result = await agent.generate(prompt, {
onStepFinish: (step: string) => {
const parsedStep = JSON.parse(step);
console.log(parsedStep.toolCalls);
},
});

console.log("\n-------------------\n");
console.log("RESPONSE");
console.log("\n-------------------\n");
console.log(result.text);
} catch (error) {
console.error(error);
}
console.log("\n-------------------\n");
}
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import "dotenv/config";

import { openai } from "@ai-sdk/openai";
import { Agent } from "@mastra/core/agent";

import { http } from "viem";
import { createWalletClient } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

import { getOnChainTools } from "@goat-sdk/adapter-mastra";
import { USDC, erc20 } from "@goat-sdk/plugin-erc20";

import { sendETH } from "@goat-sdk/wallet-evm";
import { viem } from "@goat-sdk/wallet-viem";

const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`);

const walletClient = createWalletClient({
account: account,
transport: http(process.env.RPC_PROVIDER_URL),
chain: baseSepolia,
});

const tools = await getOnChainTools({
wallet: viem(walletClient),
plugins: [
sendETH(), // Enable ETH transfers
erc20({ tokens: [USDC] }), // Enable ERC20 token operations
],
});

export const moneyTransmitter = new Agent({
name: "Money Transmitter Agent",
instructions: `You are a money transmitter agent. You are responsible for sending money to the user's wallet.`,
model: openai("gpt-4o-mini"),
tools: tools,
});
7 changes: 7 additions & 0 deletions typescript/examples/by-framework/mastra/src/mastra/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Mastra } from "@mastra/core";

import { moneyTransmitter } from "./agents/moneyTransmitter";

export const mastra = new Mastra({
agents: { moneyTransmitter },
});
14 changes: 14 additions & 0 deletions typescript/examples/by-framework/mastra/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", ".mastra"]
}
47 changes: 47 additions & 0 deletions typescript/packages/adapters/mastra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<div align="center">
<a href="https://github.com/goat-sdk/goat">

<img src="https://github.com/user-attachments/assets/5fc7f121-259c-492c-8bca-f15fe7eb830c" alt="GOAT" width="100px" height="auto" style="object-fit: contain;">
</a>
</div>

# Mastra AI SDK Adapter for GOAT

Integrate the more than +200 onchain tools of GOAT with Mastra AI SDK.

## Installation
```
npm install @goat-sdk/adapter-mastra
yarn add @goat-sdk/adapter-mastra
pnpm add @goat-sdk/adapter-mastra
```

## Usage

See a full working example [here](https://github.com/goat-sdk/goat/tree/main/typescript/examples/by-framework/mastra).

```ts
import { getOnChainTools } from "@goat-sdk/adapter-mastra";

const tools = await getOnChainTools({
wallet: // your wallet
plugins: // your plugins
});

const result = await generateText({
model: openai("gpt-4o-mini"),
tools: tools,
prompt: "Your prompt here",
});
```

<footer>
<br/>
<br/>
<div>
<a href="https://github.com/goat-sdk/goat">
<img src="https://github.com/user-attachments/assets/59fa5ddc-9d47-4d41-a51a-64f6798f94bd" alt="GOAT" width="100%" height="auto" style="object-fit: contain; max-width: 800px;">

</a>
<div>
</footer>
36 changes: 36 additions & 0 deletions typescript/packages/adapters/mastra/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@goat-sdk/adapter-mastra",
"version": "0.1.0",
"sideEffects": false,
"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",
"dependencies": {
"@goat-sdk/core": "workspace:*",
"ai": "catalog:"
},
"peerDependencies": {
"@goat-sdk/core": "workspace:*",
"ai": "catalog:",
"zod": "^3.0.0"
},
"devDependencies": {
"zod": "catalog:"
},
"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"]
}
30 changes: 30 additions & 0 deletions typescript/packages/adapters/mastra/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type GetToolsParams, type ToolBase, type WalletClientBase, getTools } from "@goat-sdk/core";

import { type CoreTool, tool } from "ai";
import type { z } from "zod";

export type GetOnChainToolsParams<TWalletClient extends WalletClientBase> = GetToolsParams<TWalletClient>;

export async function getOnChainTools<TWalletClient extends WalletClientBase>({
wallet,
plugins,
}: GetOnChainToolsParams<TWalletClient>) {
const tools: ToolBase[] = await getTools<TWalletClient>({
wallet,
plugins,
});

const aiTools: { [key: string]: CoreTool } = {};

for (const t of tools) {
aiTools[t.name] = tool({
description: t.description,
parameters: t.parameters,
execute: async (arg: z.output<typeof t.parameters>) => {
return await t.execute(arg);
},
});
}

return aiTools;
}
9 changes: 9 additions & 0 deletions typescript/packages/adapters/mastra/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../tsconfig.base.json",
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"moduleResolution": "bundler"
}
}
6 changes: 6 additions & 0 deletions typescript/packages/adapters/mastra/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from "tsup";
import { treeShakableConfig } from "../../../tsup.config.base";

export default defineConfig({
...treeShakableConfig,
});
11 changes: 11 additions & 0 deletions typescript/packages/adapters/mastra/turbo.json
Original file line number Diff line number Diff line change
@@ -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/**"]
}
}
}
Loading