diff --git a/goat.code-workspace b/goat.code-workspace index d722d643..94448db0 100644 --- a/goat.code-workspace +++ b/goat.code-workspace @@ -58,6 +58,10 @@ "name": "[Plugin] ๐Ÿ’ฐ coingecko", "path": "./typescript/packages/plugins/coingecko" }, + { + "name": "[Plugin] ๐Ÿงข coinmarketcap", + "path": "./typescript/packages/plugins/coinmarketcap" + }, { "name": "[Plugin] ๐Ÿ“ก farcaster", "path": "./typescript/packages/plugins/farcaster" diff --git a/typescript/.changeset/swift-mugs-drum.md b/typescript/.changeset/swift-mugs-drum.md new file mode 100644 index 00000000..6b56b23a --- /dev/null +++ b/typescript/.changeset/swift-mugs-drum.md @@ -0,0 +1,5 @@ +--- +"@goat-sdk/plugin-coinmarketcap": patch +--- + +Create plugin diff --git a/typescript/packages/plugins/coinmarketcap/package.json b/typescript/packages/plugins/coinmarketcap/package.json new file mode 100644 index 00000000..a7a7c693 --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/package.json @@ -0,0 +1,31 @@ +{ + "name": "@goat-sdk/plugin-coinmarketcap", + "version": "0.1.0", + "files": ["dist/**/*", "README.md", "package.json"], + "scripts": { + "build": "tsup", + "clean": "rm -rf dist", + "test": "vitest run --passWithNoTests" + }, + "sideEffects": false, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "dependencies": { + "@goat-sdk/core": "workspace:*", + "zod": "catalog:" + }, + "peerDependencies": { + "@goat-sdk/core": "workspace:*" + }, + "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"] +} diff --git a/typescript/packages/plugins/coinmarketcap/src/api.ts b/typescript/packages/plugins/coinmarketcap/src/api.ts new file mode 100644 index 00000000..65403151 --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/src/api.ts @@ -0,0 +1,47 @@ +export class CoinmarketcapApi { + private readonly BASE_URL = "https://pro-api.coinmarketcap.com/"; + + constructor(private readonly apiKey: string) {} + + async makeRequest(endpoint: string, params: T) { + const queryString = new URLSearchParams( + Object.entries(params || {}).reduce( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = String(value); + } + return acc; + }, + {} as Record, + ), + ).toString(); + + const url = `${this.BASE_URL}${endpoint}${queryString ? `?${queryString}` : ""}`; + + try { + const response = await fetch(url, { + method: "GET", + headers: { + "X-CMC_PRO_API_KEY": this.apiKey, + Accept: "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + `Coinmarketcap API Error: ${response.status} - ${ + errorData?.status?.error_message || response.statusText + }`, + ); + } + + return (await response.json()).data; + } catch (error) { + if (error instanceof Error) { + throw error; + } + throw new Error("An unknown error occurred while fetching data"); + } + } +} diff --git a/typescript/packages/plugins/coinmarketcap/src/coinmarketcap.plugin.ts b/typescript/packages/plugins/coinmarketcap/src/coinmarketcap.plugin.ts new file mode 100644 index 00000000..d0622540 --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/src/coinmarketcap.plugin.ts @@ -0,0 +1,21 @@ +import { type Chain, PluginBase } from "@goat-sdk/core"; +import { CoinmarketcapService } from "./coinmarketcap.service"; + +export interface CoinmarketcapOptions { + apiKey: string; +} + +export class CoinmarketcapPlugin extends PluginBase { + constructor(private readonly options: CoinmarketcapOptions) { + super("coinmarketcap", [new CoinmarketcapService(options.apiKey)]); + } + + supportsChain(_chain: Chain): boolean { + // This plugin doesn't require specific chain support as it's an API wrapper + return true; + } +} + +export function coinmarketcap(options: CoinmarketcapOptions) { + return new CoinmarketcapPlugin(options); +} diff --git a/typescript/packages/plugins/coinmarketcap/src/coinmarketcap.service.ts b/typescript/packages/plugins/coinmarketcap/src/coinmarketcap.service.ts new file mode 100644 index 00000000..4e761c5f --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/src/coinmarketcap.service.ts @@ -0,0 +1,209 @@ +import { Tool } from "@goat-sdk/core"; + +import { + ContentLatestParameters, + CryptocurrencyListingsParameters, + CryptocurrencyMapParameters, + CryptocurrencyOHLCVLatestParameters, + CryptocurrencyQuotesLatestParameters, + CryptocurrencyTrendingGainersLosersParameters, + CryptocurrencyTrendingLatestParameters, + CryptocurrencyTrendingMostVisitedParameters, + ExchangeListingsParameters, + ExchangeQuotesLatestParameters, +} from "./parameters"; + +import { CoinmarketcapApi } from "./api"; + +export class CoinmarketcapService { + private readonly api: CoinmarketcapApi; + + constructor(apiKey: string) { + this.api = new CoinmarketcapApi(apiKey); + } + + @Tool({ + description: + "Fetch the latest cryptocurrency listings with market data including price, market cap, volume, and other key metrics", + }) + async getCryptocurrencyListings(parameters: CryptocurrencyListingsParameters) { + try { + return await this.api.makeRequest("/v1/cryptocurrency/listings/latest", { + start: parameters.start, + limit: parameters.limit, + sort: parameters.sort, + sort_dir: parameters.sort_dir, + cryptocurrency_type: parameters.cryptocurrency_type, + tag: parameters.tag, + aux: parameters.aux?.join(","), + convert: parameters.convert, + }); + } catch (error) { + throw new Error(`Failed to fetch cryptocurrency listings: ${error}`); + } + } + + @Tool({ + description: + "Get the latest market quotes for one or more cryptocurrencies, including price, market cap, and volume in any supported currency", + }) + async getCryptocurrencyQuotes(parameters: CryptocurrencyQuotesLatestParameters) { + try { + return await this.api.makeRequest("/v2/cryptocurrency/quotes/latest", { + id: parameters.id?.join(","), + symbol: parameters.symbol?.join(","), + convert: parameters.convert, + aux: parameters.aux?.join(","), + }); + } catch (error) { + throw new Error(`Failed to fetch cryptocurrency quotes: ${error}`); + } + } + + @Tool({ + description: + "Fetch the latest cryptocurrency exchange listings with market data including trading volume, number of markets, and liquidity metrics", + }) + async getExchangeListings(parameters: ExchangeListingsParameters) { + try { + return await this.api.makeRequest("/v1/exchange/listings/latest", { + start: parameters.start, + limit: parameters.limit, + sort: parameters.sort, + sort_dir: parameters.sort_dir, + market_type: parameters.market_type, + aux: parameters.aux?.join(","), + }); + } catch (error) { + throw new Error(`Failed to fetch exchange listings: ${error}`); + } + } + + @Tool({ + description: + "Get the latest market data for one or more exchanges including trading volume, number of markets, and other exchange-specific metrics", + }) + async getExchangeQuotes(parameters: ExchangeQuotesLatestParameters) { + try { + return await this.api.makeRequest("/v1/exchange/quotes/latest", { + id: parameters.id?.join(","), + slug: parameters.slug?.join(","), + convert: parameters.convert, + aux: parameters.aux?.join(","), + }); + } catch (error) { + throw new Error(`Failed to fetch exchange quotes: ${error}`); + } + } + + @Tool({ + description: "Fetch the latest cryptocurrency news, articles, and market analysis content from trusted sources", + }) + async getContent(parameters: ContentLatestParameters) { + try { + return await this.api.makeRequest("/v1/content/latest", { + start: parameters.start, + limit: parameters.limit, + id: parameters.id?.join(","), + slug: parameters.slug?.join(","), + symbol: parameters.symbol?.join(","), + news_type: parameters.news_type, + content_type: parameters.content_type, + category: parameters.category, + language: parameters.language, + }); + } catch (error) { + throw new Error(`Failed to fetch content: ${error}`); + } + } + + @Tool({ + description: + "Get a mapping of all cryptocurrencies with unique CoinMarketCap IDs, including active and inactive assets", + }) + async getCryptocurrencyMap(parameters: CryptocurrencyMapParameters) { + try { + return await this.api.makeRequest("/v1/cryptocurrency/map", { + listing_status: parameters.listing_status, + start: parameters.start, + limit: parameters.limit, + sort: parameters.sort, + symbol: parameters.symbol?.join(","), + aux: parameters.aux?.join(","), + }); + } catch (error) { + throw new Error(`Failed to fetch cryptocurrency map: ${error}`); + } + } + + @Tool({ + description: "Get the latest OHLCV (Open, High, Low, Close, Volume) values for cryptocurrencies", + }) + async getCryptocurrencyOHLCV(parameters: CryptocurrencyOHLCVLatestParameters) { + try { + return await this.api.makeRequest("/v2/cryptocurrency/ohlcv/latest", { + id: parameters.id?.join(","), + symbol: parameters.symbol?.join(","), + convert: parameters.convert, + convert_id: parameters.convert_id, + skip_invalid: parameters.skip_invalid, + }); + } catch (error) { + throw new Error(`Failed to fetch cryptocurrency OHLCV data: ${error}`); + } + } + + @Tool({ + description: "Get the latest trending cryptocurrencies based on CoinMarketCap user activity", + }) + async getCryptocurrencyTrending(parameters: CryptocurrencyTrendingLatestParameters) { + try { + return await this.api.makeRequest("/cryptocurrency/trending/latest", { + start: parameters.start, + limit: parameters.limit, + time_period: parameters.time_period, + convert: parameters.convert, + convert_id: parameters.convert_id, + }); + } catch (error) { + throw new Error(`Failed to fetch trending cryptocurrencies: ${error}`); + } + } + + @Tool({ + description: "Get the most visited cryptocurrencies on CoinMarketCap over a specified time period", + }) + async getCryptocurrencyMostVisited(parameters: CryptocurrencyTrendingMostVisitedParameters) { + try { + return await this.api.makeRequest("/cryptocurrency/trending/most-visited", { + start: parameters.start, + limit: parameters.limit, + time_period: parameters.time_period, + convert: parameters.convert, + convert_id: parameters.convert_id, + }); + } catch (error) { + throw new Error(`Failed to fetch most visited cryptocurrencies: ${error}`); + } + } + + @Tool({ + description: + "Get the top gaining and losing cryptocurrencies based on price changes over different time periods", + }) + async getCryptocurrencyGainersLosers(parameters: CryptocurrencyTrendingGainersLosersParameters) { + try { + return await this.api.makeRequest("/cryptocurrency/trending/gainers-losers", { + start: parameters.start, + limit: parameters.limit, + time_period: parameters.time_period, + convert: parameters.convert, + convert_id: parameters.convert_id, + sort: parameters.sort, + sort_dir: parameters.sort_dir, + }); + } catch (error) { + throw new Error(`Failed to fetch cryptocurrency gainers and losers: ${error}`); + } + } +} diff --git a/typescript/packages/plugins/coinmarketcap/src/index.ts b/typescript/packages/plugins/coinmarketcap/src/index.ts new file mode 100644 index 00000000..3264e59f --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/src/index.ts @@ -0,0 +1 @@ +export * from "./coinmarketcap.plugin"; diff --git a/typescript/packages/plugins/coinmarketcap/src/parameters.ts b/typescript/packages/plugins/coinmarketcap/src/parameters.ts new file mode 100644 index 00000000..0b308ca7 --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/src/parameters.ts @@ -0,0 +1,239 @@ +import { createToolParameters } from "@goat-sdk/core"; +import { z } from "zod"; + +export class CryptocurrencyListingsParameters extends createToolParameters( + z.object({ + start: z.number().optional().describe("Starting position of results"), + limit: z.number().optional().describe("Number of results to return"), + sort: z + .enum([ + "market_cap", + "name", + "symbol", + "date_added", + "market_cap_strict", + "price", + "circulating_supply", + "total_supply", + "max_supply", + "num_market_pairs", + "volume_24h", + "percent_change_1h", + "percent_change_24h", + "percent_change_7d", + "market_cap_by_total_supply_strict", + "volume_7d", + "volume_30d", + ]) + .optional() + .default("market_cap") + .describe("What field to sort the list by"), + sort_dir: z.enum(["asc", "desc"]).optional().describe("Direction to sort the list"), + cryptocurrency_type: z + .enum(["all", "coins", "tokens"]) + .optional() + .describe("Type of cryptocurrency to include"), + tag: z.string().optional().describe("Tag to filter by"), + aux: z + .array( + z.enum([ + "num_market_pairs", + "cmc_rank", + "date_added", + "tags", + "platform", + "max_supply", + "circulating_supply", + "total_supply", + "market_cap_by_total_supply", + "volume_24h_reported", + "volume_7d", + "volume_7d_reported", + "volume_30d", + "volume_30d_reported", + "is_market_cap_included_in_calc", + ]), + ) + .optional() + .describe("Array of auxiliary fields to return"), + convert: z.string().optional().describe("Currency to convert prices to"), + }), +) {} + +export class CryptocurrencyQuotesLatestParameters extends createToolParameters( + z.object({ + id: z.array(z.string()).optional().describe("One or more cryptocurrency IDs"), + symbol: z.array(z.string()).optional().describe("One or more cryptocurrency symbols"), + convert: z.string().optional().describe("Currency to convert prices to"), + aux: z + .array( + z.enum([ + "num_market_pairs", + "cmc_rank", + "date_added", + "tags", + "platform", + "max_supply", + "circulating_supply", + "total_supply", + "market_cap_by_total_supply", + "volume_24h_reported", + "volume_7d", + "volume_7d_reported", + "volume_30d", + "volume_30d_reported", + "is_active", + "is_fiat", + ]), + ) + .optional() + .describe("Array of auxiliary fields to return"), + }), +) {} + +export class ExchangeListingsParameters extends createToolParameters( + z.object({ + start: z.number().optional().describe("Starting position of results"), + limit: z.number().optional().describe("Number of results to return"), + sort: z + .enum(["name", "volume_24h", "volume_24h_adjusted", "exchange_score"]) + .optional() + .describe("What field to sort the list by"), + sort_dir: z.enum(["asc", "desc"]).optional().describe("Direction to sort the list"), + market_type: z.enum(["all", "spot", "derivatives"]).optional().describe("Type of exchange market"), + aux: z + .array( + z.enum([ + "num_market_pairs", + "traffic_score", + "rank", + "exchange_score", + "effective_liquidity_24h", + "date_launched", + "fiats", + ]), + ) + .optional() + .describe("Array of auxiliary fields to return"), + convert: z.string().optional().describe("Currency to convert prices to"), + }), +) {} + +export class ExchangeQuotesLatestParameters extends createToolParameters( + z.object({ + id: z.array(z.string()).optional().describe("One or more exchange IDs"), + slug: z.array(z.string()).optional().describe("One or more exchange slugs"), + convert: z.string().optional().describe("Currency to convert prices to"), + aux: z + .array( + z.enum([ + "num_market_pairs", + "traffic_score", + "rank", + "exchange_score", + "liquidity_score", + "effective_liquidity_24h", + ]), + ) + .optional() + .describe("Array of auxiliary fields to return"), + }), +) {} + +export class ContentLatestParameters extends createToolParameters( + z.object({ + start: z.number().optional().describe("Starting position of results"), + limit: z.number().optional().describe("Number of results to return"), + id: z.array(z.string()).optional().describe("One or more cryptocurrency IDs"), + slug: z.array(z.string()).optional().describe("One or more cryptocurrency slugs, e.g bitcoin, ethereum, etc."), + symbol: z.array(z.string()).optional().describe("One or more cryptocurrency symbols e.g BTC, ETH, etc."), + news_type: z.enum(["news", "community", "alexandria"]).optional().describe("Type of news content to return"), + content_type: z + .enum(["all", "news", "video", "audio"]) + .optional() + .describe("Type of content category to return"), + category: z.string().optional().describe("Category of content to return Example: GameFi, DeFi, etc."), + language: z + .enum([ + "en", + "zh", + "zh-tw", + "de", + "id", + "ja", + "ko", + "es", + "th", + "tr", + "vi", + "ru", + "fr", + "nl", + "ar", + "pt-br", + "hi", + "pl", + "uk", + "fil-rph", + "it", + ]) + .optional() + .describe("Language of content to return"), + }), +) {} + +export class CryptocurrencyMapParameters extends createToolParameters( + z.object({ + listing_status: z.enum(["active", "inactive", "untracked"]).optional().describe("Status of listings to return"), + start: z.number().optional().describe("Starting position of results"), + limit: z.number().optional().describe("Number of results to return"), + sort: z.enum(["cmc_rank", "id", "name"]).optional().describe("What field to sort the list by"), + symbol: z.array(z.string()).optional().describe("Cryptocurrency symbol(s) to filter by"), + aux: z + .array(z.enum(["platform", "first_historical_data", "last_historical_data", "is_active", "status"])) + .optional() + .describe("Array of auxiliary fields to return"), + }), +) {} + +export class CryptocurrencyOHLCVLatestParameters extends createToolParameters( + z.object({ + id: z.array(z.string()).optional().describe("One or more cryptocurrency IDs"), + symbol: z.array(z.string()).optional().describe("One or more cryptocurrency symbols"), + convert: z.string().optional().describe("Currency to convert prices to"), + convert_id: z.string().optional().describe("Currency ID to convert prices to"), + skip_invalid: z.boolean().optional().describe("Skip invalid currency conversions"), + }), +) {} + +export class CryptocurrencyTrendingLatestParameters extends createToolParameters( + z.object({ + start: z.number().optional().describe("Starting position of results"), + limit: z.number().optional().describe("Number of results to return"), + time_period: z.enum(["24h", "30d", "7d"]).optional().describe("Time period for trending data"), + convert: z.string().optional().describe("Currency to convert prices to"), + convert_id: z.string().optional().describe("Currency ID to convert prices to"), + }), +) {} + +export class CryptocurrencyTrendingMostVisitedParameters extends createToolParameters( + z.object({ + start: z.number().optional().describe("Starting position of results"), + limit: z.number().optional().describe("Number of results to return"), + time_period: z.enum(["24h", "30d", "7d"]).optional().describe("Time period for trending data"), + convert: z.string().optional().describe("Currency to convert prices to"), + convert_id: z.string().optional().describe("Currency ID to convert prices to"), + }), +) {} + +export class CryptocurrencyTrendingGainersLosersParameters extends createToolParameters( + z.object({ + start: z.number().optional().describe("Starting position of results"), + limit: z.number().optional().describe("Number of results to return"), + time_period: z.enum(["1h", "24h", "7d", "30d"]).optional().describe("Time period for trending data"), + convert: z.string().optional().describe("Currency to convert prices to"), + convert_id: z.string().optional().describe("Currency ID to convert prices to"), + sort: z.enum(["percent_change_24h"]).optional().describe("What field to sort the list by"), + sort_dir: z.enum(["asc", "desc"]).optional().describe("Direction to sort the list"), + }), +) {} diff --git a/typescript/packages/plugins/coinmarketcap/tsconfig.json b/typescript/packages/plugins/coinmarketcap/tsconfig.json new file mode 100644 index 00000000..b4ae67c1 --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/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/coinmarketcap/tsup.config.ts b/typescript/packages/plugins/coinmarketcap/tsup.config.ts new file mode 100644 index 00000000..2d38789a --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/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/coinmarketcap/turbo.json b/typescript/packages/plugins/coinmarketcap/turbo.json new file mode 100644 index 00000000..45f95167 --- /dev/null +++ b/typescript/packages/plugins/coinmarketcap/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 d983fcab..4a3eb34e 100644 --- a/typescript/pnpm-lock.yaml +++ b/typescript/pnpm-lock.yaml @@ -907,6 +907,15 @@ importers: specifier: 'catalog:' version: 3.23.8 + packages/plugins/coinmarketcap: + dependencies: + '@goat-sdk/core': + specifier: workspace:* + version: link:../../core + zod: + specifier: 'catalog:' + version: 3.23.8 + packages/plugins/crossmint-headless-checkout: dependencies: '@crossmint/client-sdk-base':