Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added IMG_20250418_005731.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions skills/skills/solana_token_rank/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import TypedDict
from abstracts.skill import SkillStoreABC
from skills.base import SkillConfig, SkillState
from skills.solana_token_rank.base import SolanaTokenRankBaseTool
from skills.solana_token_rank.solana_token_rank import SolanaTokenRank

_cache: dict[str, SolanaTokenRankBaseTool] = {}

class SkillStates(TypedDict):
solana_token_rank: SkillState

class Config(SkillConfig):
states: SkillStates

async def get_skills(config: "Config", is_private: bool, store: SkillStoreABC, **_) -> list[SolanaTokenRankBaseTool]:
available_skills = []
for skill_name, state in config["states"].items():
if state == "disabled":
continue
elif state == "public" or (state == "private" and is_private):
available_skills.append(skill_name)

return [get_solana_skill(name, store) for name in available_skills]

def get_solana_skill(name: str, store: SkillStoreABC) -> SolanaTokenRankBaseTool:
if name == "solana_token_rank":
if name not in _cache:
_cache[name] = SolanaTokenRank(skill_store=store)
return _cache[name]
else:
raise ValueError(f"Unknown solana_token_rank skill: {name}")

17 changes: 17 additions & 0 deletions skills/skills/solana_token_rank/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Type
from pydantic import BaseModel, Field
from abstracts.skill import SkillStoreABC
from skills.base import IntentKitSkill

class SolanaTokenRankBaseTool(IntentKitSkill):
"""Base class for Solana Token Rank tools."""

name: str = Field(description="The name of the tool")
description: str = Field(description="A description of what the tool does")
args_schema: Type[BaseModel]
skill_store: SkillStoreABC = Field(description="The skill store for persisting data")

@property
def category(self) -> str:
return "solana_token_rank"

26 changes: 26 additions & 0 deletions skills/skills/solana_token_rank/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "Solana Token Rank Skill",
"description": "Configuration schema for Solana Token Rank skill",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add an image in the solana_token_rank folder, and add the image path here as it is being done in the other skills, thanks!

"properties": {
"states": {
"type": "object",
"properties": {
"solana_token_rank": {
"type": "string",
"title": "Solana Token Rank",
"enum": [
"disabled",
"public",
"private"
],
"description": "State of the solana_token_rank skill"
}
},
"description": "States for each Solana token rank skill (disabled, public, or private)"
}
},
"required": ["states"],
"additionalProperties": true
}
Binary file added skills/skills/solana_token_rank/solana.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions skills/skills/solana_token_rank/solana_token_rank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import httpx
from pydantic import BaseModel, Field
from typing import Type
from skills.solana_token_rank.base import SolanaTokenRankBaseTool

class SolanaTokenRankInput(BaseModel):
wallet_address: str = Field(description="Solana wallet address to look up")
token_mint: str = Field(description="Solana token mint address to rank against")

class SolanaTokenRank(SolanaTokenRankBaseTool):
name: str = "solana_token_rank"
description: str = "Get the rank, amount held, and top holder of a given Solana token."
args_schema: Type[BaseModel] = SolanaTokenRankInput

async def _arun(self, wallet_address: str, token_mint: str, **kwargs) -> str:
try:
base_url = "https://api.helius.xyz/v0/tokens/holding"
api_key = "YOUR_HELIUS_API_KEY" # Replace with env or system config in production
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not the right way to add the api key, please check how other skills get the api key from config, you're need to add it in the .env file, thanks!

url = f"{base_url}?api-key={api_key}&wallets[]={wallet_address}&mints[]={token_mint}"

async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
data = response.json()

if not data or not data[0].get("tokens"):
return "No token data found for this wallet and mint."

token_data = data[0]["tokens"][0]
amount = token_data.get("amount")

# Simulate a rank and top holder — in real code you'd use on-chain index or API
fake_rank = 1234
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this simulation needs to be removed, as it should work using the api only!

fake_total = 100000
fake_top_holder = "TopHolderAddress"

return (
f"Wallet: {wallet_address}\n"
f"Holds: {amount} tokens of {token_mint}\n"
f"Rank: #{fake_rank} of {fake_total} holders\n"
f"Top Holder: {fake_top_holder}"
)

except Exception as e:
return f"Error getting token rank: {str(e)}"