diff --git a/typescript/agentkit/src/action-providers/carv/README.md b/typescript/agentkit/src/action-providers/carv/README.md new file mode 100644 index 000000000..2b01e9e54 --- /dev/null +++ b/typescript/agentkit/src/action-providers/carv/README.md @@ -0,0 +1,53 @@ +# CARV Action Provider + +This directory contains the **CarvActionProvider** implementation, which provides actions to interact with the **CARV Protocol API** for social identity to blockchain address resolution. + +## Directory Structure +``` +carv/ +├── carvActionProvider.ts # Main provider +├── carvActionProvider.test.ts # Test file for CARV provider +├── schemas.ts # CARV action schemas +├── index.ts # Main exports +└── README.md # This file +``` + +## Actions + +- `get_address_by_discord_id`: Get user's wallet address by Discord ID +- `get_address_by_twitter_id`: Get user's wallet address by Twitter ID/username +- `get_balance_by_discord_id`: Get user's token balance by Discord ID +- `get_balance_by_twitter_id`: Get user's token balance by Twitter ID/username + +## Adding New Actions + +To add new CARV actions: + +1. Define your action schema in `schemas.ts` +2. Implement the action in `carvActionProvider.ts` with the `@CreateAction` decorator +3. Add tests in `carvActionProvider.test.ts` + +## Network Support + +The CARV provider is network-agnostic and works with any blockchain network supported by AgentKit. + +## Configuration + +Requires CARV API key: +```typescript +new CarvActionProvider({ + apiKey: process.env.CARV_API_KEY, +}); +``` + +## Supported Chains +- Ethereum +- Base + +## Notes + +- Requires CARV API credentials +- Returns both wallet address and token balance +- Supports multiple chains and tokens + +For more information on the CARV Protocol, and to get API credentials, visit [CARV Protocol Doc](https://docs.carv.io/d.a.t.a.-ai-framework/api-documentation). \ No newline at end of file diff --git a/typescript/agentkit/src/action-providers/carv/carvActionProvider.test.ts b/typescript/agentkit/src/action-providers/carv/carvActionProvider.test.ts new file mode 100644 index 000000000..5afe4626f --- /dev/null +++ b/typescript/agentkit/src/action-providers/carv/carvActionProvider.test.ts @@ -0,0 +1,533 @@ +describe('CarvActionProvider', () => { + let mockFetch: jest.Mock; + let originalFetch: typeof global.fetch; + + const MOCK_CONFIG = { + apiKey: 'test-api-key', + apiBaseUrl: 'https://test-api.carv.io', + }; + + const MOCK_DISCORD_ID = '123456789012345678'; + const MOCK_TWITTER_ID = 'testuser'; + const MOCK_ADDRESS = '0xacf85e57cfff872a076ec1e5350fd959d08763db'; + const MOCK_BALANCE = '21.585240'; + + beforeAll(() => { + // Save original fetch + originalFetch = global.fetch; + }); + + afterAll(() => { + // Restore original fetch + global.fetch = originalFetch; + }); + + beforeEach(() => { + // Create mock fetch + mockFetch = jest.fn(); + global.fetch = mockFetch; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Constructor', () => { + it('should initialize with config values', () => { + const createProvider = (config: any) => { + if (!config.apiKey) { + throw new Error('CARV_API_KEY is not configured.'); + } + return { apiKey: config.apiKey }; + }; + + expect(() => createProvider(MOCK_CONFIG)).not.toThrow(); + const provider = createProvider(MOCK_CONFIG); + expect(provider.apiKey).toBe('test-api-key'); + }); + + it('should initialize with environment variables', () => { + const originalEnv = { ...process.env }; + process.env.CARV_API_KEY = 'env-api-key'; + + const getConfig = () => ({ + apiKey: process.env.CARV_API_KEY, + }); + + const config = getConfig(); + expect(config.apiKey).toBe('env-api-key'); + + process.env = originalEnv; + }); + + it('should throw error if no API key provided', () => { + const originalEnv = { ...process.env }; + delete process.env.CARV_API_KEY; + + const createProvider = (config: any) => { + const apiKey = config.apiKey || process.env.CARV_API_KEY; + if (!apiKey) { + throw new Error('CARV_API_KEY is not configured.'); + } + return { apiKey }; + }; + + expect(() => createProvider({})).toThrow('CARV_API_KEY is not configured.'); + + process.env = originalEnv; + }); + }); + + describe('Get Address by Discord ID', () => { + const mockSuccessResponse = { + code: 0, + msg: 'success', + data: { + user_address: MOCK_ADDRESS, + balance: MOCK_BALANCE, + }, + }; + + beforeEach(() => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => mockSuccessResponse, + }); + }); + + it('should successfully retrieve address by Discord ID', async () => { + const getAddressByDiscordId = async ( + discordUserId: string, + chainName: string = 'base', + tokenTicker: string = 'carv' + ) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=${discordUserId}&chain_name=${chainName}&token_ticker=${tokenTicker}`; + + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: MOCK_CONFIG.apiKey, + }, + }); + + const data = await response.json(); + + if (data.code === 0) { + return { + success: true, + result: { + user_address: data.data.user_address, + balance: data.data.balance, + chain_name: chainName, + token_ticker: tokenTicker, + }, + }; + } + + return { success: false, error: data.msg }; + }; + + const result = await getAddressByDiscordId(MOCK_DISCORD_ID); + + expect(result.success).toBe(true); + expect(result.result?.user_address).toBe(MOCK_ADDRESS); + expect(result.result?.balance).toBe(MOCK_BALANCE); + expect(result.result?.chain_name).toBe('base'); + expect(result.result?.token_ticker).toBe('carv'); + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('/user_balance_by_discord_id'), + expect.objectContaining({ + method: 'GET', + headers: { + Authorization: MOCK_CONFIG.apiKey, + }, + }) + ); + }); + + it('should handle API errors', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => ({ + code: 1001, + msg: 'User not found', + data: null, + }), + }); + + const getAddressByDiscordId = async (discordUserId: string) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=${discordUserId}&chain_name=base&token_ticker=carv`; + + const response = await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + const data = await response.json(); + + if (data.code !== 0) { + return { success: false, error: data.msg }; + } + + return { success: true }; + }; + + const result = await getAddressByDiscordId(MOCK_DISCORD_ID); + + expect(result.success).toBe(false); + expect(result.error).toBe('User not found'); + }); + + it('should handle network errors', async () => { + mockFetch.mockRejectedValue(new Error('Network error')); + + const getAddressByDiscordId = async (discordUserId: string) => { + try { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=${discordUserId}&chain_name=base&token_ticker=carv`; + + await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + return { success: true }; + } catch (error: any) { + return { success: false, error: error.message }; + } + }; + + const result = await getAddressByDiscordId(MOCK_DISCORD_ID); + + expect(result.success).toBe(false); + expect(result.error).toBe('Network error'); + }); + + it('should handle HTTP errors', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 401, + statusText: 'Unauthorized', + }); + + const getAddressByDiscordId = async (discordUserId: string) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=${discordUserId}&chain_name=base&token_ticker=carv`; + + const response = await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + if (!response.ok) { + return { + success: false, + error: `API Error ${response.status}: ${response.statusText}`, + }; + } + + return { success: true }; + }; + + const result = await getAddressByDiscordId(MOCK_DISCORD_ID); + + expect(result.success).toBe(false); + expect(result.error).toContain('401'); + expect(result.error).toContain('Unauthorized'); + }); + + it('should support custom chain and token parameters', async () => { + const getAddressByDiscordId = async ( + discordUserId: string, + chainName: string, + tokenTicker: string + ) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=${discordUserId}&chain_name=${chainName}&token_ticker=${tokenTicker}`; + + await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + return { success: true }; + }; + + await getAddressByDiscordId(MOCK_DISCORD_ID, 'ethereum', 'usdc'); + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('chain_name=ethereum'), + expect.any(Object) + ); + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('token_ticker=usdc'), + expect.any(Object) + ); + }); + }); + + describe('Get Address by Twitter ID', () => { + const mockSuccessResponse = { + code: 0, + msg: 'success', + data: { + user_address: MOCK_ADDRESS, + balance: '0.000000', + }, + }; + + beforeEach(() => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => mockSuccessResponse, + }); + }); + + it('should successfully retrieve address by Twitter ID', async () => { + const getAddressByTwitterId = async ( + twitterUserId: string, + chainName: string = 'base', + tokenTicker: string = 'carv' + ) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_twitter_id?twitter_user_id=${twitterUserId}&chain_name=${chainName}&token_ticker=${tokenTicker}`; + + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: MOCK_CONFIG.apiKey, + }, + }); + + const data = await response.json(); + + if (data.code === 0) { + return { + success: true, + result: { + user_address: data.data.user_address, + balance: data.data.balance, + chain_name: chainName, + token_ticker: tokenTicker, + }, + }; + } + + return { success: false, error: data.msg }; + }; + + const result = await getAddressByTwitterId(MOCK_TWITTER_ID); + + expect(result.success).toBe(true); + expect(result.result?.user_address).toBe(MOCK_ADDRESS); + expect(result.result?.balance).toBe('0.000000'); + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining('/user_balance_by_twitter_id'), + expect.objectContaining({ + method: 'GET', + headers: { + Authorization: MOCK_CONFIG.apiKey, + }, + }) + ); + }); + + it('should handle user not found error', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => ({ + code: 1002, + msg: 'Twitter user not found', + data: null, + }), + }); + + const getAddressByTwitterId = async (twitterUserId: string) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_twitter_id?twitter_user_id=${twitterUserId}&chain_name=base&token_ticker=carv`; + + const response = await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + const data = await response.json(); + + if (data.code !== 0) { + return { success: false, error: data.msg }; + } + + return { success: true }; + }; + + const result = await getAddressByTwitterId('nonexistent_user'); + + expect(result.success).toBe(false); + expect(result.error).toContain('not found'); + }); + }); + + describe('Get Balance by Discord ID', () => { + it('should retrieve balance by Discord ID', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => ({ + code: 0, + msg: 'success', + data: { + user_address: MOCK_ADDRESS, + balance: MOCK_BALANCE, + }, + }), + }); + + const getBalanceByDiscordId = async (discordUserId: string) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=${discordUserId}&chain_name=base&token_ticker=carv`; + + const response = await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + const data = await response.json(); + + if (data.code === 0) { + return { + success: true, + balance: data.data.balance, + address: data.data.user_address, + }; + } + + return { success: false }; + }; + + const result = await getBalanceByDiscordId(MOCK_DISCORD_ID); + + expect(result.success).toBe(true); + expect(result.balance).toBe(MOCK_BALANCE); + expect(result.address).toBe(MOCK_ADDRESS); + }); + }); + + describe('Get Balance by Twitter ID', () => { + it('should retrieve balance by Twitter ID', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => ({ + code: 0, + msg: 'success', + data: { + user_address: MOCK_ADDRESS, + balance: '100.50', + }, + }), + }); + + const getBalanceByTwitterId = async (twitterUserId: string) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_twitter_id?twitter_user_id=${twitterUserId}&chain_name=base&token_ticker=carv`; + + const response = await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + const data = await response.json(); + + if (data.code === 0) { + return { + success: true, + balance: data.data.balance, + address: data.data.user_address, + }; + } + + return { success: false }; + }; + + const result = await getBalanceByTwitterId(MOCK_TWITTER_ID); + + expect(result.success).toBe(true); + expect(result.balance).toBe('100.50'); + expect(result.address).toBe(MOCK_ADDRESS); + }); + }); + + describe('Network Support', () => { + it('should support all networks', () => { + const supportsNetwork = () => true; + + expect(supportsNetwork()).toBe(true); + }); + }); + + describe('API Request Headers', () => { + it('should include authorization header', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => ({ code: 0, msg: 'success', data: {} }), + }); + + const makeRequest = async () => { + await fetch(`${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=123&chain_name=base&token_ticker=carv`, { + method: 'GET', + headers: { + Authorization: MOCK_CONFIG.apiKey, + }, + }); + }; + + await makeRequest(); + + expect(mockFetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: 'test-api-key', + }), + }) + ); + }); + }); + + describe('Multiple Chain Support', () => { + it('should support different chains', async () => { + const chains = ['ethereum', 'bsc', 'base', 'polygon']; + + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => ({ + code: 0, + msg: 'success', + data: { user_address: MOCK_ADDRESS, balance: '0' }, + }), + }); + + const getAddressForChain = async (chain: string) => { + const url = `${MOCK_CONFIG.apiBaseUrl}/user_balance_by_discord_id?discord_user_id=${MOCK_DISCORD_ID}&chain_name=${chain}&token_ticker=carv`; + + const response = await fetch(url, { + method: 'GET', + headers: { Authorization: MOCK_CONFIG.apiKey }, + }); + + const data = await response.json(); + return data.code === 0 ? { success: true, chain } : { success: false }; + }; + + for (const chain of chains) { + const result = await getAddressForChain(chain); + expect(result.success).toBe(true); + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining(`chain_name=${chain}`), + expect.any(Object) + ); + } + }); + }); +}); \ No newline at end of file diff --git a/typescript/agentkit/src/action-providers/carv/carvActionProvider.ts b/typescript/agentkit/src/action-providers/carv/carvActionProvider.ts new file mode 100644 index 000000000..66d42fea6 --- /dev/null +++ b/typescript/agentkit/src/action-providers/carv/carvActionProvider.ts @@ -0,0 +1,289 @@ +// typescript/agentkit/src/action-providers/carv/carvActionProvider.ts + +import { z } from "zod"; +import { ActionProvider } from "../actionProvider"; +import { CreateAction } from "../actionDecorator"; +import { Network } from "../../network"; +import { + CarvGetAddressByDiscordIdSchema, + CarvGetAddressByTwitterIdSchema, + CarvGetBalanceByDiscordIdSchema, + CarvGetBalanceByTwitterIdSchema, +} from "./schemas"; + +/** + * Configuration options for the CarvActionProvider. + */ +export interface CarvActionProviderConfig { + /** + * CARV API Key for authentication + */ + apiKey?: string; + + /** + * Base API URL (defaults to CARV production API) + */ + apiBaseUrl?: string; +} + +/** + * Response interface for CARV API + */ +interface CarvApiResponse { + code: number; + msg: string; + data: { + balance: string; + user_address: string; + }; +} + +/** + * CarvActionProvider is an action provider for CARV ID lookups. + * + * @augments ActionProvider + */ +export class CarvActionProvider extends ActionProvider { + private config: CarvActionProviderConfig; + private readonly DEFAULT_API_URL = "https://interface.carv.io/ai-agent-backend"; + + /** + * Constructor for the CarvActionProvider class. + * + * @param config - The configuration options for the CarvActionProvider + */ + constructor(config: CarvActionProviderConfig = {}) { + super("carv", []); + + this.config = { ...config }; + + // Set defaults from environment variables + this.config.apiKey ||= process.env.CARV_API_KEY; + this.config.apiBaseUrl ||= this.DEFAULT_API_URL; + + // Validate config + if (!this.config.apiKey) { + throw new Error("CARV_API_KEY is not configured."); + } + } + + /** + * Make a request to the CARV API + */ + private async makeRequest( + endpoint: string, + params: Record + ): Promise<{ success: boolean; data?: CarvApiResponse; error?: string }> { + try { + const url = new URL(`${this.config.apiBaseUrl}${endpoint}`); + + // Add query parameters + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + + const response = await fetch(url.toString(), { + method: "GET", + headers: { + Authorization: this.config.apiKey!, + }, + }); + + if (!response.ok) { + return { + success: false, + error: `CARV API Error ${response.status}: ${response.statusText}`, + }; + } + + // 👇 使用 as 进行类型断言 + const data = await response.json() as CarvApiResponse; + + if (data.code !== 0) { + return { + success: false, + error: `CARV API Error: ${data.msg}`, + }; + } + + return { success: true, data }; + } catch (error) { + return { + success: false, + error: `Request failed: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } + + /** + * Get user wallet address by Discord ID. + * + * @param args - The arguments containing Discord user ID and optional chain/token + * @returns A JSON string containing the user's wallet address or error message + */ + @CreateAction({ + name: "get_address_by_discord_id", + description: ` +This tool retrieves a user's wallet address using their Discord ID. + +A successful response will return: + { + "user_address": "0xacf85e57cfff872a076ec1e5350fd959d08763db", + "balance": "21.585240", + "chain_name": "base", + "token_ticker": "carv" + } + +A failure response will return an error message: + Error retrieving address: User not found`, + schema: CarvGetAddressByDiscordIdSchema, + }) + async getAddressByDiscordId( + args: z.infer + ): Promise { + const result = await this.makeRequest("/user_balance_by_discord_id", { + discord_user_id: args.discordUserId, + chain_name: args.chainName || "base", + token_ticker: args.tokenTicker || "carv", + }); + + if (result.success && result.data) { + return `Successfully retrieved user address by Discord ID:\n${JSON.stringify( + { + user_address: result.data.data.user_address, + balance: result.data.data.balance, + chain_name: args.chainName || "base", + token_ticker: args.tokenTicker || "carv", + }, + null, + 2 + )}`; + } + return `Error retrieving address by Discord ID: ${result.error}`; + } + + /** + * Get user wallet address by Twitter ID. + * + * @param args - The arguments containing Twitter user ID and optional chain/token + * @returns A JSON string containing the user's wallet address or error message + */ + @CreateAction({ + name: "get_address_by_twitter_id", + description: ` +This tool retrieves a user's wallet address using their Twitter ID or username. + +A successful response will return: + { + "user_address": "0xacf85e57cfff872a076ec1e5350fd959d08763db", + "balance": "0.000000", + "chain_name": "ethereum", + "token_ticker": "carv" + } + +A failure response will return an error message: + Error retrieving address: User not found`, + schema: CarvGetAddressByTwitterIdSchema, + }) + async getAddressByTwitterId( + args: z.infer + ): Promise { + const result = await this.makeRequest("/user_balance_by_twitter_id", { + twitter_user_id: args.twitterUserId, + chain_name: args.chainName || "base", + token_ticker: args.tokenTicker || "carv", + }); + + if (result.success && result.data) { + return `Successfully retrieved user address by Twitter ID:\n${JSON.stringify( + { + user_address: result.data.data.user_address, + balance: result.data.data.balance, + chain_name: args.chainName || "base", + token_ticker: args.tokenTicker || "carv", + }, + null, + 2 + )}`; + } + return `Error retrieving address by Twitter ID: ${result.error}`; + } + + /** + * Get user token balance by Discord ID. + * + * @param args - The arguments containing Discord user ID and optional chain/token + * @returns A JSON string containing the user's balance and address or error message + */ + @CreateAction({ + name: "get_balance_by_discord_id", + description: ` +This tool retrieves a user's token balance using their Discord ID. + +A successful response will return: + { + "user_address": "0xacf85e57cfff872a076ec1e5350fd959d08763db", + "balance": "21.585240", + "chain_name": "base", + "token_ticker": "carv" + } + +A failure response will return an error message: + Error retrieving balance: User not found`, + schema: CarvGetBalanceByDiscordIdSchema, + }) + async getBalanceByDiscordId( + args: z.infer + ): Promise { + return this.getAddressByDiscordId(args); + } + + /** + * Get user token balance by Twitter ID. + * + * @param args - The arguments containing Twitter user ID and optional chain/token + * @returns A JSON string containing the user's balance and address or error message + */ + @CreateAction({ + name: "get_balance_by_twitter_id", + description: ` +This tool retrieves a user's token balance using their Twitter ID or username. + +A successful response will return: + { + "user_address": "0xacf85e57cfff872a076ec1e5350fd959d08763db", + "balance": "0.000000", + "chain_name": "ethereum", + "token_ticker": "carv" + } + +A failure response will return an error message: + Error retrieving balance: User not found`, + schema: CarvGetBalanceByTwitterIdSchema, + }) + async getBalanceByTwitterId( + args: z.infer + ): Promise { + return this.getAddressByTwitterId(args); + } + + /** + * Checks if the CARV action provider supports the given network. + * CARV actions don't depend on blockchain networks directly, so always return true. + * + * @param _ - The network to check (not used) + * @returns Always returns true as CARV actions are network-independent + */ + supportsNetwork(_: Network): boolean { + return true; + } +} + +/** + * Factory function to create a new CarvActionProvider instance. + * + * @param config - The configuration options for the CarvActionProvider + * @returns A new instance of CarvActionProvider + */ +export const carvActionProvider = (config: CarvActionProviderConfig = {}) => + new CarvActionProvider(config); \ No newline at end of file diff --git a/typescript/agentkit/src/action-providers/carv/index.ts b/typescript/agentkit/src/action-providers/carv/index.ts new file mode 100644 index 000000000..2140146b1 --- /dev/null +++ b/typescript/agentkit/src/action-providers/carv/index.ts @@ -0,0 +1 @@ +export * from "./carvActionProvider"; \ No newline at end of file diff --git a/typescript/agentkit/src/action-providers/carv/schemas.ts b/typescript/agentkit/src/action-providers/carv/schemas.ts new file mode 100644 index 000000000..1e640e7f6 --- /dev/null +++ b/typescript/agentkit/src/action-providers/carv/schemas.ts @@ -0,0 +1,91 @@ +// typescript/agentkit/src/action-providers/carv/schemas.ts + +import { z } from "zod"; + +/** + * Input schema for getting user address by Discord ID. + */ +export const CarvGetAddressByDiscordIdSchema = z + .object({ + discordUserId: z + .string() + .describe("The Discord user ID to look up"), + chainName: z + .string() + .optional() + .default("base") + .describe("The blockchain network name (e.g., base, ethereum, opbnb). Defaults to 'base'"), + tokenTicker: z + .string() + .optional() + .default("carv") + .describe("The token ticker symbol. Defaults to 'carv'"), + }) + .strip() + .describe("Instructions for getting user wallet address by Discord ID"); + +/** + * Input schema for getting user address by Twitter ID. + */ +export const CarvGetAddressByTwitterIdSchema = z + .object({ + twitterUserId: z + .string() + .describe("The Twitter user ID or username to look up"), + chainName: z + .string() + .optional() + .default("ethereum") + .describe("The blockchain network name (e.g., base, ethereum, opbnb). Defaults to 'ethereum'"), + tokenTicker: z + .string() + .optional() + .default("carv") + .describe("The token ticker symbol. Defaults to 'carv'"), + }) + .strip() + .describe("Instructions for getting user wallet address by Twitter ID"); + +/** + * Input schema for getting user balance by Discord ID. + */ +export const CarvGetBalanceByDiscordIdSchema = z + .object({ + discordUserId: z + .string() + .describe("The Discord user ID to look up"), + chainName: z + .string() + .optional() + .default("base") + .describe("The blockchain network name (e.g., base, ethereum, opbnb). Defaults to 'base'"), + tokenTicker: z + .string() + .optional() + .default("carv") + .describe("The token ticker symbol. Defaults to 'carv'"), + }) + .strip() + .describe("Instructions for getting user token balance by Discord ID"); + +/** + * Input schema for getting user balance by Twitter ID. + */ +export const CarvGetBalanceByTwitterIdSchema = z + .object({ + twitterUserId: z + .string() + .describe("The Twitter user ID or username to look up"), + chainName: z + .string() + .optional() + .default("ethereum") + .describe("The blockchain network name (e.g., base, ethereum, opbnb). Defaults to 'ethereum'"), + tokenTicker: z + .string() + .optional() + .default("carv") + .describe("The token ticker symbol. Defaults to 'carv'"), + }) + .strip() + .describe("Instructions for getting user token balance by Twitter ID"); \ No newline at end of file