diff --git a/python/src/plugins/opensea/README.md b/python/src/plugins/opensea/README.md new file mode 100644 index 000000000..6d078de5b --- /dev/null +++ b/python/src/plugins/opensea/README.md @@ -0,0 +1,26 @@ +# OpenSea Plugin for GOAT SDK + +This plugin provides integration with the OpenSea API for the GOAT SDK, allowing you to: +- Get NFT collection statistics +- Get recent NFT sales + +## Installation + +```bash +pip install goat-sdk-plugin-opensea +``` + +## Usage + +```python +from goat_plugins.opensea import opensea, OpenSeaPluginOptions + +# Initialize the plugin with your API key +plugin = opensea(OpenSeaPluginOptions(api_key="your-api-key")) + +# Get NFT collection statistics +stats = await plugin.get_nft_collection_statistics({"collectionSlug": "collection-slug"}) + +# Get recent NFT sales +sales = await plugin.get_nft_sales({"collectionSlug": "collection-slug"}) +``` diff --git a/python/src/plugins/opensea/goat_plugins/opensea/__init__.py b/python/src/plugins/opensea/goat_plugins/opensea/__init__.py new file mode 100644 index 000000000..417a2860e --- /dev/null +++ b/python/src/plugins/opensea/goat_plugins/opensea/__init__.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from goat.classes.plugin_base import PluginBase +from .service import OpenSeaService + + +@dataclass +class OpenSeaPluginOptions: + api_key: str + + +class OpenSeaPlugin(PluginBase): + def __init__(self, options: OpenSeaPluginOptions): + super().__init__("opensea", [OpenSeaService(options.api_key)]) + + def supports_chain(self, chain) -> bool: + return True + + +def opensea(options: OpenSeaPluginOptions) -> OpenSeaPlugin: + return OpenSeaPlugin(options) diff --git a/python/src/plugins/opensea/goat_plugins/opensea/parameters.py b/python/src/plugins/opensea/goat_plugins/opensea/parameters.py new file mode 100644 index 000000000..08e3c109d --- /dev/null +++ b/python/src/plugins/opensea/goat_plugins/opensea/parameters.py @@ -0,0 +1,85 @@ +from pydantic import BaseModel, Field +from typing import List, Optional + + +class GetNftCollectionStatisticsParameters(BaseModel): + collectionSlug: str = Field( + ..., + description="The slug of the NFT collection on OpenSea" + ) + + +class GetNftSalesParameters(BaseModel): + collectionSlug: str = Field( + ..., + description="The slug of the NFT collection on OpenSea" + ) + + +class NftCollectionStatisticsTotal(BaseModel): + volume: float + sales: int + average_price: float + num_owners: int + market_cap: float + floor_price: float + floor_price_symbol: str + + +class NftCollectionStatisticsInterval(BaseModel): + interval: str + volume: float + volume_diff: float + volume_change: float + sales: int + sales_diff: int + average_price: float + + +class NftCollectionStatisticsResponse(BaseModel): + total: NftCollectionStatisticsTotal + intervals: List[NftCollectionStatisticsInterval] + + +class NftDetails(BaseModel): + identifier: str + collection: str + contract: str + token_standard: str + name: str + description: str + image_url: str + display_image_url: str + display_animation_url: Optional[str] + metadata_url: str + opensea_url: str + updated_at: str + is_disabled: bool + is_nsfw: bool + + +class PaymentDetails(BaseModel): + quantity: str + token_address: str + decimals: int + symbol: str + + +class NftSaleEvent(BaseModel): + event_type: str + order_hash: str + chain: str + protocol_address: str + closing_date: int + nft: NftDetails + quantity: int + seller: str + buyer: str + payment: PaymentDetails + transaction: str + event_timestamp: int + + +class NftSalesResponse(BaseModel): + asset_events: List[NftSaleEvent] + next: str diff --git a/python/src/plugins/opensea/goat_plugins/opensea/service.py b/python/src/plugins/opensea/goat_plugins/opensea/service.py new file mode 100644 index 000000000..94a4cdd4e --- /dev/null +++ b/python/src/plugins/opensea/goat_plugins/opensea/service.py @@ -0,0 +1,58 @@ +import aiohttp +from goat.decorators.tool import Tool +from .parameters import ( + GetNftCollectionStatisticsParameters, + GetNftSalesParameters, + NftCollectionStatisticsResponse, + NftSalesResponse, +) + + +class OpenSeaService: + def __init__(self, api_key: str): + self.api_key = api_key + self.base_url = "https://api.opensea.io/api/v2" + + @Tool({ + "description": "Get NFT collection statistics", + "parameters_schema": GetNftCollectionStatisticsParameters + }) + async def get_nft_collection_statistics(self, parameters: dict) -> NftCollectionStatisticsResponse: + """Get statistics for an NFT collection from OpenSea""" + async with aiohttp.ClientSession() as session: + url = f"{self.base_url}/collections/{parameters['collectionSlug']}/stats" + headers = { + "accept": "application/json", + "x-api-key": self.api_key + } + async with session.get(url, headers=headers) as response: + if not response.ok: + raise Exception(f"Failed to get NFT collection statistics: HTTP {response.status} - {await response.text()}") + data = await response.json() + return NftCollectionStatisticsResponse.model_validate(data) + + @Tool({ + "description": "Get recent NFT sales", + "parameters_schema": GetNftSalesParameters + }) + async def get_nft_sales(self, parameters: dict) -> list: + """Get recent NFT sales for a collection from OpenSea""" + async with aiohttp.ClientSession() as session: + url = f"{self.base_url}/events/collection/{parameters['collectionSlug']}?event_type=sale&limit=5" + headers = { + "accept": "application/json", + "x-api-key": self.api_key + } + async with session.get(url, headers=headers) as response: + if not response.ok: + raise Exception(f"Failed to get NFT sales: HTTP {response.status} - {await response.text()}") + data = await response.json() + sales_response = NftSalesResponse.model_validate(data) + + # Transform the response to match TypeScript implementation + return [{ + "name": event.nft.name, + "seller": event.seller, + "buyer": event.buyer, + "price": float(event.payment.quantity) / 10 ** event.payment.decimals + } for event in sales_response.asset_events] diff --git a/python/src/plugins/opensea/poetry.toml b/python/src/plugins/opensea/poetry.toml new file mode 100644 index 000000000..ab1033bd3 --- /dev/null +++ b/python/src/plugins/opensea/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/python/src/plugins/opensea/pyproject.toml b/python/src/plugins/opensea/pyproject.toml new file mode 100644 index 000000000..4d916c640 --- /dev/null +++ b/python/src/plugins/opensea/pyproject.toml @@ -0,0 +1,43 @@ +[tool.poetry] +name = "goat-sdk-plugin-opensea" +version = "0.1.0" +description = "Goat plugin for OpenSea" +authors = ["Devin "] +readme = "README.md" +keywords = ["goat", "sdk", "web3", "agents", "ai", "nft", "opensea"] +homepage = "https://ohmygoat.dev/" +repository = "https://github.com/goat-sdk/goat" +packages = [ + { include = "goat_plugins/opensea" }, +] + +[tool.poetry.dependencies] +python = "^3.10" +aiohttp = "^3.8.6" +goat-sdk = "^0.1.1" + +[tool.poetry.group.test.dependencies] +pytest = "^8.3.4" +pytest-asyncio = "^0.25.0" + +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/goat-sdk/goat/issues" + +[tool.pytest.ini_options] +addopts = [ + "--import-mode=importlib", +] +pythonpath = "src" +asyncio_default_fixture_loop_scope = "function" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.group.dev.dependencies] +ruff = "^0.8.6" +goat-sdk = { path = "../../goat-sdk", develop = true } + +[tool.ruff] +line-length = 120 +target-version = "py312"