Skip to content

Commit

Permalink
feat: implement coinmarketcap plugin with @tool decorator (#175)
Browse files Browse the repository at this point in the history
* refactor: migrate coinmarketcap plugin to use @tool decorator

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* refactor: remove tools.ts after migration to @tool decorator

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* feat: implement coinmarketcap plugin with @tool decorator and integration tests

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: address lint formatting issues

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: add explicit type annotations to parameter classes

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: use createToolParameters correctly for parameter classes

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: format parameter class declarations

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: export parameter schemas directly without createToolParameters

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: add explicit type declarations to parameter classes

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: simplify parameter classes to match ERC20 plugin implementation

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: add static schema properties and separate schema definitions

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: cast zod objects to ZodSchema type

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* fix: update package.json and fix parameter formatting

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* chore: remove tests as requested

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* style: fix type assertions, import sorting and tool descriptions

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* style: apply biome formatting to coinmarketcap plugin

Co-Authored-By: Alfonso Gomez Jordana Manas <[email protected]>

* Fix PR

* Fix pnpm lock file

* Fix pnpm lock file

* Add changeset

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Alfonso Gomez Jordana Manas <[email protected]>
Co-authored-by: Agustin Armellini Fischer <[email protected]>
  • Loading branch information
3 people authored Jan 4, 2025
1 parent 50180d4 commit b31d3ce
Show file tree
Hide file tree
Showing 12 changed files with 589 additions and 0 deletions.
4 changes: 4 additions & 0 deletions goat.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions typescript/.changeset/swift-mugs-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@goat-sdk/plugin-coinmarketcap": patch
---

Create plugin
31 changes: 31 additions & 0 deletions typescript/packages/plugins/coinmarketcap/package.json
Original file line number Diff line number Diff line change
@@ -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"]
}
47 changes: 47 additions & 0 deletions typescript/packages/plugins/coinmarketcap/src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export class CoinmarketcapApi {
private readonly BASE_URL = "https://pro-api.coinmarketcap.com/";

constructor(private readonly apiKey: string) {}

async makeRequest<T>(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<string, string>,
),
).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");
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
209 changes: 209 additions & 0 deletions typescript/packages/plugins/coinmarketcap/src/coinmarketcap.service.ts
Original file line number Diff line number Diff line change
@@ -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}`);
}
}
}
1 change: 1 addition & 0 deletions typescript/packages/plugins/coinmarketcap/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./coinmarketcap.plugin";
Loading

0 comments on commit b31d3ce

Please sign in to comment.