
Helius Api
- 196 installs
- 257 repo stars
- Updated June 24, 2026
- agiprolabs/claude-trading-skills
helius-api is a Claude Code skill that integrates the Helius enhanced Solana RPC (DAS API, parsed transactions, webhooks, priority fees) for wallet and token analysis.
About
helius-api integrates the Helius enhanced Solana RPC, covering the Digital Asset Standard (DAS) API, parsed transaction history, webhooks, and priority-fee estimation. A developer uses it to query token metadata, analyze wallets, and monitor Solana transactions from an agent or script. It documents the two Helius base URLs and full DAS method set.
- Wraps the Helius enhanced Solana RPC: DAS API, parsed transactions, webhooks, priority fees
- Includes token_lookup.py and wallet_analysis.py plus DAS/webhook/error references
- Free tier: 1M credits/mo, no card required
Helius Api by the numbers
- 196 all-time installs (skills.sh)
- Ranked #2,066 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
helius-api capabilities & compatibility
Free tier of 1M credits/mo, no card required
- Capabilities
- wallet analysis · token lookup · transaction monitoring
- Works with
- github
- Use cases
- api development · data analysis · research
- Pricing
- Freemium
What helius-api says it does
Helius extends standard Solana RPC with parsed transaction history, a unified Digital Asset Standard (DAS) API, webhooks, and priority fee estimation.
Sign up at [dashboard.helius.dev](https://dashboard.helius.dev) — free tier available (1M credits/mo, no card required).
npx skills add https://github.com/agiprolabs/claude-trading-skills --skill helius-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 196 |
|---|---|
| repo stars | ★ 257 |
| Last updated | June 24, 2026 |
| Repository | agiprolabs/claude-trading-skills ↗ |
What it does
Query Solana assets, wallets, and parsed transactions through the Helius enhanced RPC.
Who is it for?
Fetching Solana asset metadata, wallet holdings, and human-readable parsed transactions via Helius.
Skip if: Chains other than Solana or raw RPC without an API key.
When should I use this skill?
You need enhanced Solana RPC data (DAS, parsed txs, webhooks) for wallet or token analysis.
By the numbers
- 12 DAS methods tabulated
- 2 base URLs documented
- 1M free credits/mo
Files
Helius API — Enhanced Solana RPC
Helius extends standard Solana RPC with parsed transaction history, a unified Digital Asset Standard (DAS) API, webhooks, and priority fee estimation. Essential for wallet analysis, token metadata, and transaction monitoring.
Quick Start
1. Get an API Key
Sign up at dashboard.helius.dev — free tier available (1M credits/mo, no card required).
export HELIUS_API_KEY="your-api-key"2. Install Dependencies
uv pip install httpx python-dotenv3. Two Base URLs
Helius uses different base URLs depending on the API:
| API | Base URL | Protocol |
|---|---|---|
| RPC / DAS / Priority Fees | https://mainnet.helius-rpc.com/?api-key=KEY | JSON-RPC 2.0 |
| Enhanced Transactions / Webhooks | https://api-mainnet.helius-rpc.com/v0/...?api-key=KEY | REST |
DAS API (Digital Asset Standard)
Unified interface for querying all Solana digital assets — fungible tokens, NFTs, compressed NFTs, Token-2022.
Get Asset Metadata
import httpx, os
API_KEY = os.environ["HELIUS_API_KEY"]
RPC_URL = f"https://mainnet.helius-rpc.com/?api-key={API_KEY}"
resp = httpx.post(RPC_URL, json={
"jsonrpc": "2.0", "id": 1,
"method": "getAsset",
"params": {
"id": "So11111111111111111111111111111111111111112", # wSOL
"options": {"showFungible": True}
}
})
asset = resp.json()["result"]
# asset.content.metadata.name, asset.token_info.decimals, etc.Get All Assets for a Wallet
resp = httpx.post(RPC_URL, json={
"jsonrpc": "2.0", "id": 1,
"method": "getAssetsByOwner",
"params": {
"ownerAddress": "WALLET_ADDRESS",
"page": 1,
"limit": 100,
"displayOptions": {
"showFungible": True,
"showNativeBalance": True,
"showZeroBalance": False,
}
}
})
assets = resp.json()["result"]["items"]Search Assets with Filters
resp = httpx.post(RPC_URL, json={
"jsonrpc": "2.0", "id": 1,
"method": "searchAssets",
"params": {
"ownerAddress": "WALLET_ADDRESS",
"tokenType": "fungible", # fungible | nonFungible | all
"page": 1,
"limit": 50,
}
})All DAS Methods
| Method | Purpose | Credits |
|---|---|---|
getAsset | Single asset metadata | 10 |
getAssetBatch | Up to 1,000 assets | 10 |
getAssetsByOwner | All assets for a wallet | 10 |
getAssetsByGroup | Assets by collection | 10 |
getAssetsByCreator | Assets by creator | 10 |
getAssetsByAuthority | Assets by update authority | 10 |
searchAssets | Multi-criteria filtered search | 10 |
getAssetProof | Merkle proof (compressed NFTs) | 10 |
getAssetProofBatch | Batch proofs | 10 |
getSignaturesForAsset | Tx history for an asset | 10 |
getNftEditions | All editions of a master | 10 |
getTokenAccounts | Token accounts by mint/owner | 10 |
See references/das_api.md for complete field documentation.
Enhanced Transactions API
Transforms raw Solana transactions into human-readable structured data with categorized types and sources.
Parse a Transaction
API_URL = f"https://api-mainnet.helius-rpc.com/v0/transactions?api-key={API_KEY}"
resp = httpx.post(API_URL, json={
"transactions": ["SIGNATURE_HERE"],
})
parsed = resp.json()[0]
# parsed["type"] → "SWAP"
# parsed["source"] → "JUPITER"
# parsed["description"] → "User swapped 1 SOL for 150 USDC on Jupiter"
# parsed["tokenTransfers"] → [{mint, amount, from, to}, ...]
# parsed["nativeTransfers"] → [{from, to, amount}, ...]Get Parsed Transaction History for a Wallet
url = f"https://api-mainnet.helius-rpc.com/v0/addresses/{wallet}/transactions"
resp = httpx.get(url, params={
"api-key": API_KEY,
"limit": 50,
"type": "SWAP", # optional filter
})
history = resp.json()Transaction Types (151+)
Key types for trading analysis:
| Type | Meaning |
|---|---|
SWAP | DEX swap |
TRANSFER | Token/SOL transfer |
ADD_LIQUIDITY / REMOVE_LIQUIDITY | LP operations |
NFT_SALE / NFT_MINT / NFT_LISTING | NFT marketplace |
STAKE_SOL / UNSTAKE_SOL | Staking |
CREATE_ORDER / FILL_ORDER | Limit orders |
Transaction Sources (50+)
JUPITER, RAYDIUM, ORCA, MAGIC_EDEN, TENSOR, MARINADE, METEORA, PHANTOM, etc.
See references/enhanced_transactions.md for full type/source enums.
Webhooks
Real-time notifications for on-chain events — no polling required.
Create a Webhook
url = f"https://api-mainnet.helius-rpc.com/v0/webhooks?api-key={API_KEY}"
resp = httpx.post(url, json={
"webhookURL": "https://your-server.com/helius-hook",
"transactionTypes": ["SWAP", "TRANSFER"],
"accountAddresses": ["WalletAddress1", "WalletAddress2"],
"webhookType": "enhanced",
"authHeader": "your-secret-token",
})
webhook_id = resp.json()["webhookID"]Webhook Types
| Type | Data Format | Filtering |
|---|---|---|
enhanced | Parsed (like Enhanced Transactions API) | By transaction type + account |
raw | Unprocessed transaction data | By account only (lower latency) |
discord | Formatted messages to Discord channel | By transaction type + account |
Manage Webhooks
# List all
webhooks = httpx.get(f"{url}?api-key={API_KEY}").json()
# Update
httpx.put(f"{url}/{webhook_id}?api-key={API_KEY}", json={
"webhookURL": "https://new-url.com/hook",
"transactionTypes": ["SWAP"],
"accountAddresses": ["NewWallet..."],
"webhookType": "enhanced",
})
# Delete
httpx.delete(f"{url}/{webhook_id}?api-key={API_KEY}")Up to 100,000 addresses per webhook (via API). 1 credit per event delivered.
Priority Fee API
Estimate optimal priority fees for transaction landing.
resp = httpx.post(RPC_URL, json={
"jsonrpc": "2.0", "id": 1,
"method": "getPriorityFeeEstimate",
"params": [{
"accountKeys": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
"options": {
"includeAllPriorityFeeLevels": True,
"recommended": True,
}
}]
})
fees = resp.json()["result"]
# fees["priorityFeeEstimate"] → recommended fee (microlamports/CU)
# fees["priorityFeeLevels"] → {min, low, medium, high, veryHigh, unsafeMax}| Level | Percentile | Use Case |
|---|---|---|
min | 0-20th | Non-urgent |
low | 20-40th | Standard transfers |
medium | 40-60th | DEX swaps (recommended default) |
high | 60-80th | Time-sensitive |
veryHigh | 80-95th | Critical timing |
unsafeMax | 100th | Emergency only |
Fee in microlamports/CU. Total priority fee = microlamports/CU × compute units consumed.
Pricing & Rate Limits
| Plan | Price/mo | Credits | RPC req/s | DAS req/s |
|---|---|---|---|---|
| Free | $0 | 1M | 10 | 2 |
| Developer | $49 | 10M | 50 | 10 |
| Business | $499 | 100M | 200 | 50 |
| Professional | $999 | 200M | 500 | 100 |
Credit costs: Standard RPC = 1, DAS = 10, Enhanced Txns = 100, Webhooks = 1/event. Additional credits: $5/million.
Files
References
references/das_api.md— Complete DAS API field reference and response schemasreferences/enhanced_transactions.md— Transaction types, sources, and response structurereferences/webhooks.md— Webhook setup, management, and event handlingreferences/error_handling.md— Rate limits, error codes, and retry strategies
Scripts
scripts/wallet_analysis.py— Fetch wallet assets and parsed transaction historyscripts/token_lookup.py— Look up token metadata and holder information via DAS
Helius DAS API — Field Reference
Base URL
POST https://mainnet.helius-rpc.com/?api-key=YOUR_KEY
Content-Type: application/jsonAll DAS methods use JSON-RPC 2.0. Cost: 10 credits per call.
Asset Object (Canonical Response)
Every DAS method returns assets in this structure:
{
"interface": "FungibleToken",
"id": "So11111111111111111111111111111111111111112",
"content": {
"json_uri": "https://...",
"files": [{"uri": "...", "cdn_uri": "...", "mime": "image/png"}],
"metadata": {
"name": "Wrapped SOL",
"symbol": "SOL",
"description": "...",
"token_standard": "Fungible",
"attributes": [{"trait_type": "...", "value": "..."}]
},
"links": {"image": "...", "external_url": "..."}
},
"authorities": [{"address": "...", "scopes": ["full"]}],
"compression": {
"compressed": false,
"eligible": false,
"data_hash": "", "creator_hash": "", "asset_hash": "",
"tree": "", "seq": 0, "leaf_id": 0
},
"grouping": [{"group_key": "collection", "group_value": "..."}],
"royalty": {
"royalty_model": "creators",
"percent": 0.0,
"basis_points": 0,
"primary_sale_happened": true,
"locked": false
},
"creators": [{"address": "...", "share": 100, "verified": true}],
"ownership": {
"owner": "...",
"frozen": false,
"delegated": false,
"delegate": null,
"ownership_model": "single"
},
"supply": {
"print_max_supply": 0,
"print_current_supply": 0,
"edition_nonce": null
},
"token_info": {
"supply": 1000000000,
"decimals": 9,
"token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"mint_authority": "...",
"freeze_authority": "..."
},
"mutable": true,
"burnt": false
}Interface Types
| Value | Description |
|---|---|
FungibleToken | SPL tokens |
FungibleAsset | SPL tokens with metadata |
V1_NFT | Metaplex v1 NFT |
V2_NFT | Metaplex v2 NFT |
ProgrammableNFT | pNFT (royalty-enforced) |
LEGACY_NFT | Older format |
V1_PRINT | Edition print |
Method Details
getAsset
{
"method": "getAsset",
"params": {
"id": "MINT_ADDRESS",
"options": {
"showFungible": true,
"showCollectionMetadata": false,
"showInscription": false,
"showUnverifiedCollections": false
}
}
}getAssetsByOwner
{
"method": "getAssetsByOwner",
"params": {
"ownerAddress": "WALLET_ADDRESS",
"page": 1,
"limit": 100,
"sortBy": {"sortBy": "recent_action", "sortDirection": "desc"},
"displayOptions": {
"showFungible": true,
"showNativeBalance": true,
"showZeroBalance": false
}
}
}Response: { "total": 42, "limit": 100, "page": 1, "items": [Asset, ...] }
getAssetBatch
{
"method": "getAssetBatch",
"params": {
"ids": ["MINT1", "MINT2", "MINT3"]
}
}Up to 1,000 assets per call. Same cost as single getAsset (10 credits).
searchAssets
{
"method": "searchAssets",
"params": {
"ownerAddress": "WALLET",
"tokenType": "fungible",
"compressed": false,
"page": 1,
"limit": 50
}
}Filter options: tokenType (fungible/nonFungible/all), compressed, burnt, frozen, interface, grouping.
getTokenAccounts
{
"method": "getTokenAccounts",
"params": {
"owner": "WALLET_ADDRESS",
"options": {"showZeroBalance": false}
}
}Or by mint: {"mint": "TOKEN_MINT_ADDRESS"} to find all holders.
Pagination
Two modes:
Offset-based (simpler, max 1000 items):
{"page": 1, "limit": 100}Cursor-based (for large datasets):
{"before": "cursor_string", "after": "cursor_string", "limit": 100}Sort options: created, recent_action, updated, none.
Important Notes
- Price data in
getAssetis cached (600s TTL), only for top 10k tokens by volume getAssetsByOwnerwithshowFungible: trueis the fastest way to get a wallet's token portfoliogetTokenAccountswithmintparameter is how you query all holders of a token- Compressed NFT operations require
getAssetProoffor transfer/burn operations getSignaturesForAssetprovides transaction history for compressed NFTs (not available via standard RPC)
Helius Enhanced Transactions API — Reference
Base URL
https://api-mainnet.helius-rpc.com/v0/Note: This is a REST API (not JSON-RPC). Cost: 100 credits per call.
Parse Transactions (Batch)
POST /v0/transactions?api-key=YOUR_KEY{
"transactions": ["sig1", "sig2"],
"commitment": "finalized"
}Max 100 signatures per batch.
Get Parsed History by Address
GET /v0/addresses/{address}/transactions?api-key=YOUR_KEY| Parameter | Type | Description |
|---|---|---|
limit | int | 1-100 results |
before-signature | string | Paginate backward |
after-signature | string | Paginate forward |
type | string | Filter by TransactionType |
source | string | Filter by TransactionSource |
commitment | string | confirmed or finalized |
sort-order | string | asc or desc |
EnhancedTransaction Response
{
"signature": "5K2b...",
"description": "User swapped 1 SOL for 150 USDC on Jupiter",
"type": "SWAP",
"source": "JUPITER",
"fee": 5000,
"feePayer": "WalletAddress...",
"slot": 250000000,
"timestamp": 1700000000,
"nativeTransfers": [
{"fromUserAccount": "A", "toUserAccount": "B", "amount": 1000000000}
],
"tokenTransfers": [
{
"fromUserAccount": "A",
"toUserAccount": "B",
"fromTokenAccount": "ATA1",
"toTokenAccount": "ATA2",
"tokenAmount": 150.0,
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"tokenStandard": "Fungible"
}
],
"accountData": [
{"account": "addr", "nativeBalanceChange": -1005000, "tokenBalanceChanges": [...]}
],
"transactionError": null,
"instructions": [...],
"events": {
"swap": {
"nativeInput": {"account": "...", "amount": "1000000000"},
"nativeOutput": null,
"tokenInputs": [],
"tokenOutputs": [{"userAccount": "...", "tokenAccount": "...", "mint": "...", "rawTokenAmount": {"tokenAmount": "150000000", "decimals": 6}}],
"tokenFees": [],
"nativeFees": [],
"innerSwaps": [...]
}
}
}Transaction Types (Key Categories)
DeFi / Trading
| Type | Description |
|---|---|
SWAP | DEX swap (any aggregator or AMM) |
ADD_LIQUIDITY | Add to LP pool |
REMOVE_LIQUIDITY | Remove from LP pool |
DEPOSIT | Deposit to protocol |
WITHDRAW | Withdraw from protocol |
BORROW_FOX | Borrow operations |
REPAY_LOAN | Loan repayment |
LIQUIDATE | Liquidation |
CREATE_ORDER | Limit order creation |
CANCEL_ORDER | Limit order cancellation |
FILL_ORDER | Limit order filled |
Token Operations
| Type | Description |
|---|---|
TRANSFER | Token or SOL transfer |
MINT_TO | Token minting |
BURN | Token burning |
APPROVE | Token delegation |
REVOKE | Revoke delegation |
NFT
| Type | Description |
|---|---|
NFT_SALE | NFT sold on marketplace |
NFT_MINT | NFT minted |
NFT_LISTING | NFT listed for sale |
NFT_BID | Bid placed on NFT |
NFT_CANCEL_LISTING | Listing cancelled |
COMPRESSED_NFT_MINT | Compressed NFT minted |
COMPRESSED_NFT_TRANSFER | Compressed NFT transferred |
Staking
| Type | Description |
|---|---|
STAKE_SOL | Stake SOL |
UNSTAKE_SOL | Unstake SOL |
CLAIM_REWARDS | Claim staking rewards |
System
| Type | Description |
|---|---|
UNKNOWN | Unrecognized transaction type |
UPGRADE_PROGRAM_INSTRUCTION | Program upgrade |
Transaction Sources
DEXes & DeFi
JUPITER, RAYDIUM, ORCA, METEORA, SABER, MERCURIAL, MARINADE, ALDRIN, CREMA, LIFINITY, CYKURA, ORCA_WHIRLPOOLS, PHOENIX
NFT Marketplaces
MAGIC_EDEN, TENSOR, HYPERSPACE, SOLANART, EXCHANGE_ART, DIGITAL_EYES, FORM_FUNCTION, HADESWAP, CORAL_CUBE
Protocols & Programs
METAPLEX, CANDY_MACHINE_V1, CANDY_MACHINE_V2, CANDY_MACHINE_V3, BUBBLEGUM, ANCHOR, SYSTEM_PROGRAM, STAKE_PROGRAM
Ecosystem
PHANTOM, COINBASE, OPENSEA, STEPN, SHARKY_FI, SQUADS
UNKNOWN for unrecognized sources.
Using Enhanced Transactions for Wallet Profiling
Common patterns for analyzing a wallet:
# 1. Get all swaps for a wallet
swaps = get_transactions(wallet, type="SWAP")
# Analyze: DEX preference, token diversity, trade sizes, frequency
# 2. Get all transfers
transfers = get_transactions(wallet, type="TRANSFER")
# Analyze: fund flows, counterparties, deposit/withdrawal patterns
# 3. Compute PnL per token from tokenTransfers
# Group tokenTransfers by mint, sum inflows and outflows
# 4. Identify trading style from timing
# Frequency, hold times (time between buy and sell of same token)Events Object
The events field provides structured data for specific transaction types:
Swap Event
{
"swap": {
"nativeInput": {"account": "...", "amount": "1000000000"},
"nativeOutput": null,
"tokenInputs": [],
"tokenOutputs": [{"mint": "...", "rawTokenAmount": {"tokenAmount": "150", "decimals": 6}}],
"innerSwaps": [{"tokenInputs": [...], "tokenOutputs": [...], "programInfo": {...}}]
}
}NFT Event
{
"nft": {
"description": "...",
"type": "NFT_SALE",
"source": "MAGIC_EDEN",
"amount": 5000000000,
"fee": 250000000,
"buyer": "...",
"seller": "...",
"nfts": [{"mint": "...", "tokenStandard": "NonFungible"}]
}
}Helius API — Error Handling & Rate Limits
Rate Limits by Plan
| Plan | RPC req/s | DAS req/s | Enhanced req/s | Webhook events |
|---|---|---|---|---|
| Free | 10 | 2 | 2 | 1 credit/event |
| Developer ($49) | 50 | 10 | 10 | 1 credit/event |
| Business ($499) | 200 | 50 | 50 | 1 credit/event |
| Professional ($999) | 500 | 100 | 100 | 1 credit/event |
Credit Costs
| API | Credits per Call |
|---|---|
| Standard RPC | 1 |
| getProgramAccounts | 10 |
| DAS methods | 10 |
| Enhanced Transactions | 100 |
| Webhook management | 100 |
| Webhook event delivery | 1 |
| Priority Fee API | 1 |
| ZK Compression | 10 (getValidityProofs: 100) |
| LaserStream/Enhanced WS | 3 per 0.1 MB |
Additional credits: $5 per 1M across all plans.
HTTP Error Codes
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 400 | Bad request | Check request format/params |
| 401 | Unauthorized | Check API key |
| 404 | Not found | Check endpoint URL |
| 429 | Rate limited | Back off and retry |
| 500 | Server error | Retry with backoff |
| 502/503 | Service unavailable | Retry with backoff |
JSON-RPC Errors
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Invalid request"
},
"id": 1
}| Code | Meaning |
|---|---|
| -32600 | Invalid request (malformed JSON-RPC) |
| -32601 | Method not found |
| -32602 | Invalid params |
| -32603 | Internal error |
| -32000 | Server error (Solana RPC specific) |
Retry Strategy
import httpx
import time
import random
def helius_request(
url: str,
payload: dict,
max_retries: int = 3,
base_delay: float = 1.0,
) -> dict:
"""Make a Helius API request with exponential backoff.
Args:
url: Full endpoint URL with API key.
payload: Request body.
max_retries: Max retry attempts.
base_delay: Initial delay in seconds.
Returns:
Parsed JSON response.
Raises:
httpx.HTTPStatusError: After all retries exhausted.
"""
for attempt in range(max_retries + 1):
try:
resp = httpx.post(url, json=payload, timeout=30.0)
if resp.status_code == 429:
delay = base_delay * (2 ** attempt) + random.uniform(0, 0.5)
print(f"Rate limited. Retrying in {delay:.1f}s...")
time.sleep(delay)
continue
resp.raise_for_status()
return resp.json()
except httpx.TimeoutException:
if attempt < max_retries:
time.sleep(base_delay * (2 ** attempt))
continue
raise
except httpx.HTTPStatusError as e:
if e.response.status_code >= 500 and attempt < max_retries:
time.sleep(base_delay * (2 ** attempt))
continue
raise
raise RuntimeError("Max retries exceeded")Pagination Best Practices
DAS API Pagination
def paginate_das(rpc_url: str, method: str, params: dict) -> list:
"""Paginate through all results from a DAS method."""
all_items = []
page = 1
while True:
params["page"] = page
params["limit"] = 1000 # max per page
resp = helius_request(rpc_url, {
"jsonrpc": "2.0", "id": 1,
"method": method,
"params": params,
})
items = resp.get("result", {}).get("items", [])
all_items.extend(items)
if len(items) < 1000:
break # last page
page += 1
time.sleep(0.5) # respect rate limits
return all_itemsEnhanced Transactions Pagination
def paginate_history(api_key: str, address: str, limit: int = 1000) -> list:
"""Paginate through wallet transaction history."""
all_txns = []
before_sig = None
while len(all_txns) < limit:
params = {"api-key": api_key, "limit": 100}
if before_sig:
params["before-signature"] = before_sig
url = f"https://api-mainnet.helius-rpc.com/v0/addresses/{address}/transactions"
resp = httpx.get(url, params=params, timeout=30.0)
resp.raise_for_status()
txns = resp.json()
if not txns:
break
all_txns.extend(txns)
before_sig = txns[-1]["signature"]
time.sleep(1.0) # enhanced API has lower rate limits
return all_txns[:limit]Common Gotchas
1. Two different base URLs — RPC/DAS uses mainnet.helius-rpc.com, Enhanced/Webhooks uses api-mainnet.helius-rpc.com 2. DAS price data is cached — 600s TTL, only top 10k tokens by volume 3. Enhanced Transactions cost 100 credits — budget carefully on free tier (1M credits = 10k calls) 4. Webhook duplicates — Helius retries failed deliveries; always deduplicate by signature 5. getAssetsByOwner — Set showFungible: true or you'll only get NFTs 6. Pagination max — DAS offset-based pagination maxes at 1000; use cursor for larger sets
Helius Webhooks — Reference
Overview
Webhooks deliver real-time on-chain events to your server via HTTP POST — no polling.
Base URL: https://api-mainnet.helius-rpc.com/v0/webhooks?api-key=YOUR_KEY
Cost: 1 credit per event delivered, 100 credits per management API call.
Webhook Types
| Type | Data | Latency | Filtering |
|---|---|---|---|
enhanced | Parsed (same as Enhanced Transactions API) | Higher | By type + account |
raw | Unprocessed transaction data | Lower | By account only |
discord | Formatted for Discord channel | Higher | By type + account |
Devnet variants: enhancedDevnet, rawDevnet, discordDevnet.
CRUD Operations
Create
import httpx, os
API_KEY = os.environ["HELIUS_API_KEY"]
url = f"https://api-mainnet.helius-rpc.com/v0/webhooks?api-key={API_KEY}"
resp = httpx.post(url, json={
"webhookURL": "https://your-server.com/helius-hook",
"transactionTypes": ["SWAP", "TRANSFER"],
"accountAddresses": ["WalletAddr1", "WalletAddr2"],
"webhookType": "enhanced",
"authHeader": "Bearer your-secret", # optional, sent with each delivery
})
webhook = resp.json()
webhook_id = webhook["webhookID"]List All
resp = httpx.get(f"https://api-mainnet.helius-rpc.com/v0/webhooks?api-key={API_KEY}")
webhooks = resp.json()Get One
resp = httpx.get(
f"https://api-mainnet.helius-rpc.com/v0/webhooks/{webhook_id}?api-key={API_KEY}"
)Update
resp = httpx.put(
f"https://api-mainnet.helius-rpc.com/v0/webhooks/{webhook_id}?api-key={API_KEY}",
json={
"webhookURL": "https://your-server.com/helius-hook",
"transactionTypes": ["SWAP"],
"accountAddresses": ["NewWallet1", "NewWallet2"],
"webhookType": "enhanced",
}
)Delete
resp = httpx.delete(
f"https://api-mainnet.helius-rpc.com/v0/webhooks/{webhook_id}?api-key={API_KEY}"
)Request Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
webhookURL | string | Yes | Your endpoint URL |
transactionTypes | string[] | Yes | Filter types (use ["ANY"] for all) |
accountAddresses | string[] | Yes | Addresses to monitor (max 100,000) |
webhookType | string | Yes | enhanced, raw, or discord |
authHeader | string | No | Auth header sent with each delivery |
encoding | string | No | Response encoding |
txnStatus | string | No | Filter by tx status |
Webhook Delivery Payload
Enhanced Webhook
Same structure as Enhanced Transactions API:
[
{
"signature": "5K2b...",
"type": "SWAP",
"source": "JUPITER",
"description": "User swapped 1 SOL for 150 USDC on Jupiter",
"fee": 5000,
"feePayer": "...",
"timestamp": 1700000000,
"nativeTransfers": [...],
"tokenTransfers": [...],
"accountData": [...],
"events": {...}
}
]Note: Payload is an array — multiple events may be batched.
Raw Webhook
[
{
"blockTime": 1700000000,
"indexWithinBlock": 42,
"meta": {
"err": null,
"fee": 5000,
"preBalances": [...],
"postBalances": [...],
"preTokenBalances": [...],
"postTokenBalances": [...],
"logMessages": [...]
},
"slot": 250000000,
"transaction": {
"message": {...},
"signatures": [...]
}
}
]Handling Webhook Events
Verification
Always verify the authHeader you configured:
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]
@app.post("/helius-hook")
async def handle_webhook(request: Request):
auth = request.headers.get("Authorization", "")
if auth != WEBHOOK_SECRET:
raise HTTPException(status_code=401)
events = await request.json()
for event in events:
if event["type"] == "SWAP":
handle_swap(event)
elif event["type"] == "TRANSFER":
handle_transfer(event)
return {"status": "ok"}Idempotency
Helius retries failed deliveries, which can cause duplicates. Deduplicate by transaction signature:
seen_sigs = set()
def handle_event(event):
sig = event["signature"]
if sig in seen_sigs:
return # duplicate
seen_sigs.add(sig)
process(event)Limits
- Max addresses per webhook: 100,000 (via API; dashboard limited to 25)
- Max webhooks per API key: Varies by plan
- Delivery timeout: Events may be delayed during high load
- Retry policy: Helius retries on 4xx/5xx responses
Use Cases for Trading
| Use Case | Configuration |
|---|---|
| Monitor a wallet for swaps | accountAddresses: [wallet], transactionTypes: ["SWAP"] |
| Track token transfers | accountAddresses: [token_mint], transactionTypes: ["TRANSFER"] |
| NFT marketplace activity | accountAddresses: [collection], transactionTypes: ["NFT_SALE"] |
| Any activity for a set of wallets | accountAddresses: [w1, w2, ...], transactionTypes: ["ANY"] |
#!/usr/bin/env python3
"""Look up token metadata and holder information via Helius DAS API.
Queries the DAS API for comprehensive token information including metadata,
supply, authorities, and top holders. Useful for pre-trade due diligence
on new tokens.
Usage:
python scripts/token_lookup.py
TOKEN_MINT="So11111111111111111111111111111111111111112" python scripts/token_lookup.py
Dependencies:
uv pip install httpx python-dotenv
Environment Variables:
HELIUS_API_KEY: Your Helius API key (free tier works)
TOKEN_MINT: Token mint address to look up
"""
import os
import sys
import time
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
API_KEY = os.getenv("HELIUS_API_KEY", "")
if not API_KEY:
print("Set HELIUS_API_KEY environment variable")
print(" Get a free key at https://dashboard.helius.dev")
sys.exit(1)
TOKEN_MINT = os.getenv(
"TOKEN_MINT", "So11111111111111111111111111111111111111112"
)
RPC_URL = f"https://mainnet.helius-rpc.com/?api-key={API_KEY}"
RATE_LIMIT_DELAY = 0.5
# ── API Helper ──────────────────────────────────────────────────────
def das_request(method: str, params: dict, retries: int = 2) -> dict:
"""Make a DAS API request with retry logic.
Args:
method: DAS method name.
params: Method parameters.
retries: Number of retry attempts.
Returns:
The 'result' field from the JSON-RPC response.
Raises:
RuntimeError: On DAS error or exhausted retries.
"""
for attempt in range(retries + 1):
try:
resp = httpx.post(
RPC_URL,
json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params},
timeout=30.0,
)
if resp.status_code == 429:
time.sleep(2.0 * (attempt + 1))
continue
resp.raise_for_status()
data = resp.json()
if "error" in data:
raise RuntimeError(f"DAS error: {data['error']}")
return data["result"]
except httpx.TimeoutException:
if attempt < retries:
time.sleep(1.0)
continue
raise
raise RuntimeError("Max retries exceeded")
# ── Token Analysis ──────────────────────────────────────────────────
def get_token_metadata(mint: str) -> dict:
"""Fetch comprehensive token metadata via getAsset.
Args:
mint: Token mint address.
Returns:
Parsed metadata dict.
"""
result = das_request("getAsset", {
"id": mint,
"options": {
"showFungible": True,
"showCollectionMetadata": True,
}
})
metadata = result.get("content", {}).get("metadata", {})
token_info = result.get("token_info", {})
authorities = result.get("authorities", [])
creators = result.get("creators", [])
return {
"mint": mint,
"name": metadata.get("name", "Unknown"),
"symbol": metadata.get("symbol", "?"),
"description": metadata.get("description", ""),
"interface": result.get("interface", "Unknown"),
"decimals": token_info.get("decimals", 0),
"supply": token_info.get("supply", 0),
"token_program": token_info.get("token_program", ""),
"mint_authority": token_info.get("mint_authority"),
"freeze_authority": token_info.get("freeze_authority"),
"mutable": result.get("mutable", False),
"burnt": result.get("burnt", False),
"authorities": [
{"address": a["address"], "scopes": a.get("scopes", [])}
for a in authorities
],
"creators": [
{"address": c["address"], "share": c.get("share", 0), "verified": c.get("verified", False)}
for c in creators
],
"image": result.get("content", {}).get("links", {}).get("image", ""),
}
def get_top_holders(mint: str, limit: int = 20) -> list[dict]:
"""Fetch top token holders via getTokenAccounts.
Args:
mint: Token mint address.
limit: Max holders to return.
Returns:
List of holder dicts sorted by balance (descending).
"""
result = das_request("getTokenAccounts", {
"mint": mint,
"limit": limit,
"options": {"showZeroBalance": False},
})
holders = []
for account in result.get("token_accounts", []):
holders.append({
"owner": account.get("owner", ""),
"amount": float(account.get("amount", 0)),
"address": account.get("address", ""),
})
# Sort by amount descending
holders.sort(key=lambda x: x["amount"], reverse=True)
return holders[:limit]
def compute_concentration(holders: list[dict], total_supply: int, decimals: int) -> dict:
"""Compute holder concentration metrics.
Args:
holders: List of holder dicts with 'amount' field (raw amounts).
total_supply: Total token supply (raw, not decimalized).
decimals: Token decimals.
Returns:
Concentration metrics dict.
"""
if not holders or total_supply == 0:
return {"top10_pct": 0, "top20_pct": 0, "largest_holder_pct": 0}
supply_float = total_supply / (10 ** decimals)
amounts = [h["amount"] / (10 ** decimals) for h in holders]
top10_sum = sum(amounts[:10])
top20_sum = sum(amounts[:20])
return {
"top10_pct": round(top10_sum / supply_float * 100, 2) if supply_float > 0 else 0,
"top20_pct": round(top20_sum / supply_float * 100, 2) if supply_float > 0 else 0,
"largest_holder_pct": round(amounts[0] / supply_float * 100, 2) if supply_float > 0 and amounts else 0,
"holder_count_sampled": len(holders),
}
# ── Display ─────────────────────────────────────────────────────────
def format_supply(raw_supply: int, decimals: int) -> str:
"""Format raw supply into human-readable string."""
value = raw_supply / (10 ** decimals)
if value >= 1_000_000_000:
return f"{value / 1_000_000_000:.2f}B"
elif value >= 1_000_000:
return f"{value / 1_000_000:.2f}M"
elif value >= 1_000:
return f"{value / 1_000:.2f}K"
return f"{value:.4f}"
def print_token_report(
metadata: dict,
holders: list[dict],
concentration: dict,
) -> None:
"""Print a formatted token analysis report.
Args:
metadata: Token metadata dict.
holders: Top holders list.
concentration: Concentration metrics.
"""
print(f"{'=' * 60}")
print(f"TOKEN REPORT: {metadata['symbol']} ({metadata['name']})")
print(f"{'=' * 60}")
print()
# Metadata
print("Metadata:")
print(f" Mint: {metadata['mint']}")
print(f" Interface: {metadata['interface']}")
print(f" Decimals: {metadata['decimals']}")
print(f" Supply: {format_supply(metadata['supply'], metadata['decimals'])}")
print(f" Mutable: {metadata['mutable']}")
print(f" Token Program: {metadata['token_program'][:20]}...")
# Authorities
print()
print("Authorities:")
mint_auth = metadata.get("mint_authority")
freeze_auth = metadata.get("freeze_authority")
print(f" Mint authority: {mint_auth if mint_auth else 'None (supply locked)'}")
print(f" Freeze authority: {freeze_auth if freeze_auth else 'None (cannot freeze)'}")
if metadata["creators"]:
print()
print("Creators:")
for c in metadata["creators"]:
verified = "verified" if c["verified"] else "unverified"
print(f" {c['address'][:20]}... ({c['share']}%, {verified})")
# Safety flags
print()
print("Safety Indicators:")
if mint_auth:
print(" [!] Mint authority exists — supply can be increased")
else:
print(" [ok] No mint authority — supply is fixed")
if freeze_auth:
print(" [!] Freeze authority exists — tokens can be frozen")
else:
print(" [ok] No freeze authority")
if metadata["mutable"]:
print(" [!] Metadata is mutable — can be changed")
else:
print(" [ok] Metadata is immutable")
# Holders
if holders:
print()
print(f"Top Holders (sampled {concentration['holder_count_sampled']}):")
print(f" Top 10 own: {concentration['top10_pct']}%")
print(f" Top 20 own: {concentration['top20_pct']}%")
print(f" Largest holder: {concentration['largest_holder_pct']}%")
print()
print(f" {'#':>3} {'Owner':>20} {'Amount':>20}")
print(f" {'—'*3} {'—'*20} {'—'*20}")
for i, h in enumerate(holders[:10], 1):
owner = h["owner"][:20] if h["owner"] else "unknown"
amt = format_supply(int(h["amount"]), metadata["decimals"])
print(f" {i:>3} {owner:>20} {amt:>20}")
if concentration["top10_pct"] > 80:
print()
print(" [!] HIGH CONCENTRATION — top 10 holders own >80% of supply")
elif concentration["top10_pct"] > 50:
print()
print(" [!] MODERATE CONCENTRATION — top 10 holders own >50% of supply")
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Look up token and print analysis report."""
print(f"Looking up token: {TOKEN_MINT}\n")
# 1. Get metadata
print("Fetching metadata...")
metadata = get_token_metadata(TOKEN_MINT)
time.sleep(RATE_LIMIT_DELAY)
# 2. Get holders
print("Fetching top holders...")
holders = get_top_holders(TOKEN_MINT, limit=20)
time.sleep(RATE_LIMIT_DELAY)
# 3. Compute concentration
concentration = compute_concentration(
holders, metadata["supply"], metadata["decimals"]
)
# 4. Print report
print()
print_token_report(metadata, holders, concentration)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Analyze a Solana wallet using Helius DAS and Enhanced Transactions APIs.
Fetches a wallet's token portfolio via DAS API, retrieves parsed transaction
history, and generates a profile including top holdings, recent swap activity,
preferred DEXes, and trading frequency.
Usage:
python scripts/wallet_analysis.py
WALLET_ADDRESS="YourWallet..." python scripts/wallet_analysis.py
Dependencies:
uv pip install httpx python-dotenv
Environment Variables:
HELIUS_API_KEY: Your Helius API key (free tier works)
WALLET_ADDRESS: Solana wallet to analyze (optional, uses example if not set)
"""
import os
import sys
import time
from collections import Counter
from datetime import datetime, timezone
from typing import Optional
import httpx
# ── Configuration ───────────────────────────────────────────────────
API_KEY = os.getenv("HELIUS_API_KEY", "")
if not API_KEY:
print("Set HELIUS_API_KEY environment variable")
print(" Get a free key at https://dashboard.helius.dev")
sys.exit(1)
WALLET = os.getenv("WALLET_ADDRESS", "")
if not WALLET:
print("Set WALLET_ADDRESS environment variable")
print(" export WALLET_ADDRESS='YourSolanaWalletAddress'")
sys.exit(1)
RPC_URL = f"https://mainnet.helius-rpc.com/?api-key={API_KEY}"
API_URL = f"https://api-mainnet.helius-rpc.com/v0"
# Rate limit delay between API calls (seconds)
RATE_LIMIT_DELAY = 0.5
# ── API Helpers ─────────────────────────────────────────────────────
def das_request(method: str, params: dict) -> dict:
"""Make a DAS API request via JSON-RPC.
Args:
method: DAS method name (e.g., 'getAssetsByOwner').
params: Method parameters.
Returns:
The 'result' field from the JSON-RPC response.
Raises:
httpx.HTTPStatusError: On non-2xx response.
KeyError: If response has no 'result'.
"""
resp = httpx.post(
RPC_URL,
json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params},
timeout=30.0,
)
resp.raise_for_status()
data = resp.json()
if "error" in data:
raise RuntimeError(f"DAS error: {data['error']}")
return data["result"]
def get_enhanced_transactions(
address: str,
limit: int = 50,
tx_type: Optional[str] = None,
) -> list[dict]:
"""Fetch parsed transaction history for an address.
Args:
address: Solana address to query.
limit: Max transactions to return (max 100 per request).
tx_type: Optional filter (e.g., 'SWAP', 'TRANSFER').
Returns:
List of EnhancedTransaction dicts.
"""
params: dict = {"api-key": API_KEY, "limit": min(limit, 100)}
if tx_type:
params["type"] = tx_type
resp = httpx.get(
f"{API_URL}/addresses/{address}/transactions",
params=params,
timeout=30.0,
)
resp.raise_for_status()
return resp.json()
# ── Analysis Functions ──────────────────────────────────────────────
def fetch_portfolio(wallet: str) -> list[dict]:
"""Fetch all fungible token holdings for a wallet.
Args:
wallet: Solana wallet address.
Returns:
List of asset dicts with token info.
"""
result = das_request("getAssetsByOwner", {
"ownerAddress": wallet,
"page": 1,
"limit": 1000,
"displayOptions": {
"showFungible": True,
"showNativeBalance": True,
"showZeroBalance": False,
},
})
return result.get("items", [])
def analyze_portfolio(assets: list[dict]) -> dict:
"""Analyze a wallet's token portfolio.
Args:
assets: List of asset dicts from DAS API.
Returns:
Analysis dict with holdings summary.
"""
fungible = []
nfts = []
for asset in assets:
interface = asset.get("interface", "")
if interface in ("FungibleToken", "FungibleAsset"):
name = asset.get("content", {}).get("metadata", {}).get("name", "Unknown")
symbol = asset.get("content", {}).get("metadata", {}).get("symbol", "?")
token_info = asset.get("token_info", {})
decimals = token_info.get("decimals", 0)
fungible.append({
"name": name,
"symbol": symbol,
"mint": asset.get("id", ""),
"decimals": decimals,
})
else:
nfts.append(asset)
return {
"fungible_count": len(fungible),
"nft_count": len(nfts),
"top_holdings": fungible[:20],
}
def analyze_swap_history(swaps: list[dict]) -> dict:
"""Analyze swap transaction history.
Args:
swaps: List of enhanced swap transactions.
Returns:
Analysis dict with swap patterns.
"""
if not swaps:
return {"total_swaps": 0}
sources = Counter()
tokens_traded = Counter()
hourly_activity = Counter()
total_fee = 0
for tx in swaps:
sources[tx.get("source", "UNKNOWN")] += 1
total_fee += tx.get("fee", 0)
# Count tokens involved
for tt in tx.get("tokenTransfers", []):
mint = tt.get("mint", "unknown")
tokens_traded[mint] += 1
# Hourly distribution
ts = tx.get("timestamp")
if ts:
hour = datetime.fromtimestamp(ts, tz=timezone.utc).hour
hourly_activity[hour] += 1
# Time span
timestamps = [tx.get("timestamp", 0) for tx in swaps if tx.get("timestamp")]
time_span_hours = 0
if len(timestamps) >= 2:
time_span_hours = (max(timestamps) - min(timestamps)) / 3600
swaps_per_hour = len(swaps) / time_span_hours if time_span_hours > 0 else 0
return {
"total_swaps": len(swaps),
"time_span_hours": round(time_span_hours, 1),
"swaps_per_hour": round(swaps_per_hour, 2),
"preferred_dexes": sources.most_common(5),
"unique_tokens_traded": len(tokens_traded),
"most_traded_mints": tokens_traded.most_common(5),
"peak_hours_utc": hourly_activity.most_common(3),
"total_fees_sol": round(total_fee / 1e9, 6),
}
def analyze_transfers(transfers: list[dict], wallet: str) -> dict:
"""Analyze transfer patterns.
Args:
transfers: List of enhanced transfer transactions.
wallet: The wallet address for determining direction.
Returns:
Analysis dict with transfer patterns.
"""
if not transfers:
return {"total_transfers": 0}
inbound = 0
outbound = 0
for tx in transfers:
for nt in tx.get("nativeTransfers", []):
if nt.get("toUserAccount") == wallet:
inbound += nt.get("amount", 0)
elif nt.get("fromUserAccount") == wallet:
outbound += nt.get("amount", 0)
return {
"total_transfers": len(transfers),
"sol_received": round(inbound / 1e9, 4),
"sol_sent": round(outbound / 1e9, 4),
"net_sol_flow": round((inbound - outbound) / 1e9, 4),
}
# ── Main ────────────────────────────────────────────────────────────
def main() -> None:
"""Run wallet analysis and print results."""
print(f"Analyzing wallet: {WALLET}")
print(f"{'=' * 60}\n")
# 1. Fetch portfolio
print("Fetching portfolio...")
assets = fetch_portfolio(WALLET)
portfolio = analyze_portfolio(assets)
time.sleep(RATE_LIMIT_DELAY)
print(f"\nPortfolio: {portfolio['fungible_count']} tokens, {portfolio['nft_count']} NFTs")
if portfolio["top_holdings"]:
print("\nTop Holdings:")
for h in portfolio["top_holdings"][:10]:
print(f" {h['symbol']:>10} | {h['name']}")
# 2. Fetch recent swaps
print("\nFetching swap history...")
swaps = get_enhanced_transactions(WALLET, limit=100, tx_type="SWAP")
swap_analysis = analyze_swap_history(swaps)
time.sleep(RATE_LIMIT_DELAY)
print(f"\nSwap Activity ({swap_analysis['total_swaps']} swaps):")
if swap_analysis["total_swaps"] > 0:
print(f" Time span: {swap_analysis['time_span_hours']}h")
print(f" Rate: {swap_analysis['swaps_per_hour']} swaps/hr")
print(f" Unique tokens: {swap_analysis['unique_tokens_traded']}")
print(f" Total fees: {swap_analysis['total_fees_sol']} SOL")
print(f" Preferred DEXes:")
for dex, count in swap_analysis["preferred_dexes"]:
print(f" {dex}: {count}")
if swap_analysis["peak_hours_utc"]:
print(f" Peak hours (UTC):")
for hour, count in swap_analysis["peak_hours_utc"]:
print(f" {hour:02d}:00 — {count} swaps")
# 3. Fetch recent transfers
print("\nFetching transfer history...")
transfers = get_enhanced_transactions(WALLET, limit=100, tx_type="TRANSFER")
transfer_analysis = analyze_transfers(transfers, WALLET)
time.sleep(RATE_LIMIT_DELAY)
print(f"\nTransfer Activity ({transfer_analysis['total_transfers']} transfers):")
if transfer_analysis["total_transfers"] > 0:
print(f" SOL received: {transfer_analysis['sol_received']}")
print(f" SOL sent: {transfer_analysis['sol_sent']}")
print(f" Net flow: {transfer_analysis['net_sol_flow']} SOL")
# 4. Summary
print(f"\n{'=' * 60}")
print("WALLET PROFILE SUMMARY")
print(f"{'=' * 60}")
print(f" Address: {WALLET[:20]}...")
print(f" Token holdings: {portfolio['fungible_count']}")
print(f" NFT holdings: {portfolio['nft_count']}")
print(f" Recent swaps: {swap_analysis['total_swaps']}")
if swap_analysis.get("preferred_dexes"):
print(f" Primary DEX: {swap_analysis['preferred_dexes'][0][0]}")
print(f" Trade frequency: {swap_analysis.get('swaps_per_hour', 0)} swaps/hr")
if __name__ == "__main__":
main()