Skip to content

Commit

Permalink
feat: Add Python implementation of OpenSea plugin (#216)
Browse files Browse the repository at this point in the history
* feat: Add Python implementation of OpenSea plugin

- Created Python version of OpenSea plugin following coingecko structure
- Implemented getNftCollectionStatistics and getNftSales endpoints
- Added parameter schemas using Pydantic
- Added async service implementation using aiohttp
- Added plugin initialization with API key configuration
- Includes proper error handling and response validation
- Matches TypeScript functionality exactly

Co-Authored-By: Agus Armellini Fischer <[email protected]>

* Delete python/src/plugins/opensea/poetry.lock

* Update README.md

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Agus Armellini Fischer <[email protected]>
Co-authored-by: Agus <[email protected]>
  • Loading branch information
3 people authored Jan 13, 2025
1 parent 7d63e9e commit c34bf16
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 0 deletions.
26 changes: 26 additions & 0 deletions python/src/plugins/opensea/README.md
Original file line number Diff line number Diff line change
@@ -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"})
```
20 changes: 20 additions & 0 deletions python/src/plugins/opensea/goat_plugins/opensea/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
85 changes: 85 additions & 0 deletions python/src/plugins/opensea/goat_plugins/opensea/parameters.py
Original file line number Diff line number Diff line change
@@ -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
58 changes: 58 additions & 0 deletions python/src/plugins/opensea/goat_plugins/opensea/service.py
Original file line number Diff line number Diff line change
@@ -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]
2 changes: 2 additions & 0 deletions python/src/plugins/opensea/poetry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[virtualenvs]
in-project = true
43 changes: 43 additions & 0 deletions python/src/plugins/opensea/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[tool.poetry]
name = "goat-sdk-plugin-opensea"
version = "0.1.0"
description = "Goat plugin for OpenSea"
authors = ["Devin <[email protected]>"]
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"

0 comments on commit c34bf16

Please sign in to comment.