- Added 3 runnable Python scripts for Data REST, Data WebSocket, and Trading APIs - Expanded research with 7 new sections: - Extended API documentation with full request/response examples - Working code examples with CLI commands - Security guide (API keys, HMAC signing, private key protection) - Testing strategies (dry-run, paper trading, testnets) - Troubleshooting guide with error codes - Competitive analysis vs DexScreener, Moralis, CoinGecko - Quick reference appendix Research doc grew from 242 to 906 lines
495 lines
15 KiB
Python
495 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AVE Cloud Data REST API - Token Search, Prices, Klines, Holders, Risk
|
|
|
|
Usage:
|
|
export AVE_API_KEY=your_api_key_here
|
|
export API_PLAN=free # free, normal, pro
|
|
|
|
python3 docs/scripts/ave_data_rest.py search --keyword PEPE
|
|
python3 docs/scripts/ave_data_rest.py price --token-ids "PEPE-bsc,TRUMP-bsc"
|
|
python3 docs/scripts/ave_data_rest.py trending --chain bsc
|
|
python3 docs/scripts/ave_data_rest.py token --token-id "0x6982508145454Ce325dDbE47a25d4ec3d2311933-bsc"
|
|
python3 docs/scripts/ave_data_rest.py risk --token-id "0x6982508145454Ce325dDbE47a25d4ec3d2311933-bsc"
|
|
python3 docs/scripts/ave_data_rest.py holders --token-id "0x6982508145454Ce325dDbE47a25d4ec3d2311933-bsc"
|
|
python3 docs/scripts/ave_data_rest.py klines --token-id "0x6982508145454Ce325dDbE47a25d4ec3d2311933-bsc"
|
|
python3 docs/scripts/ave_data_rest.py pairs --pair-id "0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae-bsc"
|
|
python3 docs/scripts/ave_data_rest.py wallet-pnl --wallet "0xd9c500dff816a1da21a48a732d3498bf09dc9aeb" --chain bsc --token "0x55d398326f99059fF775485246999027B3197955"
|
|
python3 docs/scripts/ave_data_rest.py wallet-info --wallet "0xd9c500dff816a1da21a48a732d3498bf09dc9aeb" --chain bsc
|
|
python3 docs/scripts/ave_data_rest.py smart-wallets --chain bsc
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
|
|
import requests
|
|
|
|
BASE_URL = "https://prod.ave-api.com"
|
|
API_KEY = os.getenv("AVE_API_KEY", "")
|
|
API_PLAN = os.getenv("API_PLAN", "free")
|
|
|
|
CU_COSTS = {
|
|
"search": 5,
|
|
"price": 100,
|
|
"trending": 5,
|
|
"token": 5,
|
|
"risk": 10,
|
|
"holders": 10,
|
|
"klines_token": 10,
|
|
"klines_pair": 10,
|
|
"pairs": 5,
|
|
"wallet_pnl": 5,
|
|
"wallet_info": 5,
|
|
"wallet_tokens": 10,
|
|
"smart_wallet": 5,
|
|
}
|
|
|
|
TIER_LIMITS = {
|
|
"free": {"tps": 1, "data_wss": False, "proxy_wallet": False},
|
|
"normal": {"tps": 5, "data_wss": False, "proxy_wallet": True},
|
|
"pro": {"tps": 20, "data_wss": True, "proxy_wallet": True},
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class ApiResponse:
|
|
status: int
|
|
data: Optional[dict] = None
|
|
error: Optional[str] = None
|
|
|
|
|
|
def make_headers():
|
|
return {
|
|
"X-API-KEY": API_KEY,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
|
|
def handle_response(response: requests.Response) -> ApiResponse:
|
|
if response.status_code == 200:
|
|
return ApiResponse(status=200, data=response.json())
|
|
elif response.status_code == 401:
|
|
return ApiResponse(
|
|
status=401, error="Unauthorized - invalid or missing API key"
|
|
)
|
|
elif response.status_code == 403:
|
|
return ApiResponse(
|
|
status=403, error="Forbidden - API key expired or plan limits reached"
|
|
)
|
|
elif response.status_code == 429:
|
|
return ApiResponse(status=429, error="Rate limited - TPS exceeded")
|
|
else:
|
|
return ApiResponse(status=response.status_code, error=response.text)
|
|
|
|
|
|
def search_tokens(
|
|
keyword: str, chain: Optional[str] = None, limit: int = 100
|
|
) -> ApiResponse:
|
|
params = {"keyword": keyword, "limit": limit}
|
|
if chain:
|
|
params["chain"] = chain
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/tokens", headers=make_headers(), params=params
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_token_price(token_ids: list) -> ApiResponse:
|
|
data = {"token_ids": token_ids}
|
|
response = requests.post(
|
|
f"{BASE_URL}/v2/tokens/price", headers=make_headers(), json=data
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_trending_tokens(chain: str, page: int = 0, page_size: int = 50) -> ApiResponse:
|
|
params = {"chain": chain, "current_page": page, "page_size": page_size}
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/tokens/trending", headers=make_headers(), params=params
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_token_detail(token_id: str) -> ApiResponse:
|
|
response = requests.get(f"{BASE_URL}/v2/tokens/{token_id}", headers=make_headers())
|
|
return handle_response(response)
|
|
|
|
|
|
def get_token_risk(token_id: str) -> ApiResponse:
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/contracts/{token_id}", headers=make_headers()
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_top_holders(token_id: str, limit: int = 100) -> ApiResponse:
|
|
params = {"limit": limit} if limit != 100 else {}
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/tokens/top100/{token_id}", headers=make_headers(), params=params
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_klines_by_token(
|
|
token_id: str, interval: str = "1h", limit: int = 100
|
|
) -> ApiResponse:
|
|
params = {"interval": interval, "limit": limit}
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/klines/token/{token_id}", headers=make_headers(), params=params
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_klines_by_pair(
|
|
pair_id: str, interval: str = "1h", limit: int = 100
|
|
) -> ApiResponse:
|
|
params = {"interval": interval, "limit": limit}
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/klines/pair/{pair_id}", headers=make_headers(), params=params
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_pair_detail(pair_id: str) -> ApiResponse:
|
|
response = requests.get(f"{BASE_URL}/v2/pairs/{pair_id}", headers=make_headers())
|
|
return handle_response(response)
|
|
|
|
|
|
def get_wallet_pnl(
|
|
wallet_address: str,
|
|
chain: str,
|
|
token_address: str,
|
|
from_time: Optional[int] = None,
|
|
to_time: Optional[int] = None,
|
|
) -> ApiResponse:
|
|
params = {
|
|
"wallet_address": wallet_address,
|
|
"chain": chain,
|
|
"token_address": token_address,
|
|
}
|
|
if from_time:
|
|
params["from_time"] = from_time
|
|
if to_time:
|
|
params["to_time"] = to_time
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/address/pnl", headers=make_headers(), params=params
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_wallet_info(wallet_address: str, chain: str) -> ApiResponse:
|
|
params = {"wallet_address": wallet_address, "chain": chain}
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/address/walletinfo", headers=make_headers(), params=params
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_wallet_tokens(
|
|
wallet_address: str,
|
|
chain: str,
|
|
sort: str = "last_txn_time",
|
|
sort_dir: str = "desc",
|
|
hide_sold: int = 0,
|
|
hide_small: int = 0,
|
|
) -> ApiResponse:
|
|
params = {
|
|
"wallet_address": wallet_address,
|
|
"chain": chain,
|
|
"sort": sort,
|
|
"sort_dir": sort_dir,
|
|
"hide_sold": hide_sold,
|
|
"hide_small": hide_small,
|
|
}
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/address/walletinfo/tokens",
|
|
headers=make_headers(),
|
|
params=params,
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def get_smart_wallets(
|
|
chain: str, sort: str = "total_profit", sort_dir: str = "desc"
|
|
) -> ApiResponse:
|
|
params = {"chain": chain, "sort": sort, "sort_dir": sort_dir}
|
|
response = requests.get(
|
|
f"{BASE_URL}/v2/address/smart_wallet/list",
|
|
headers=make_headers(),
|
|
params=params,
|
|
)
|
|
return handle_response(response)
|
|
|
|
|
|
def format_json(data):
|
|
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
|
|
|
|
def print_cu_warning(operation: str):
|
|
cu_cost = CU_COSTS.get(operation, "unknown")
|
|
print(f"[CU Cost: {cu_cost}]", file=sys.stderr)
|
|
|
|
|
|
def cmd_search(args):
|
|
result = search_tokens(args.keyword, args.chain, args.limit)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("search")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_price(args):
|
|
token_ids = [t.strip() for t in args.token_ids.split(",")]
|
|
result = get_token_price(token_ids)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("price")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_trending(args):
|
|
result = get_trending_tokens(args.chain, args.page, args.page_size)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("trending")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_token(args):
|
|
result = get_token_detail(args.token_id)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("token")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_risk(args):
|
|
result = get_token_risk(args.token_id)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("risk")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_holders(args):
|
|
result = get_top_holders(args.token_id, args.limit)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("holders")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_klines(args):
|
|
if args.pair_id:
|
|
result = get_klines_by_pair(args.pair_id, args.interval, args.limit)
|
|
print_cu_warning("klines_pair")
|
|
else:
|
|
result = get_klines_by_token(args.token_id, args.interval, args.limit)
|
|
print_cu_warning("klines_token")
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_pairs(args):
|
|
result = get_pair_detail(args.pair_id)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("pairs")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_wallet_pnl(args):
|
|
result = get_wallet_pnl(
|
|
args.wallet, args.chain, args.token, args.from_time, args.to_time
|
|
)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("wallet_pnl")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_wallet_info(args):
|
|
result = get_wallet_info(args.wallet, args.chain)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("wallet_info")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_wallet_tokens(args):
|
|
result = get_wallet_tokens(
|
|
args.wallet,
|
|
args.chain,
|
|
args.sort,
|
|
args.sort_dir,
|
|
args.hide_sold,
|
|
args.hide_small,
|
|
)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("wallet_tokens")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def cmd_smart_wallets(args):
|
|
result = get_smart_wallets(args.chain, args.sort, args.sort_dir)
|
|
if result.error:
|
|
print(f"Error: {result.error}")
|
|
sys.exit(1)
|
|
print_cu_warning("smart_wallet")
|
|
print(format_json(result.data))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="AVE Cloud Data REST API")
|
|
parser.add_argument(
|
|
"--api-key", default=API_KEY, help="AVE API key (or set AVE_API_KEY env)"
|
|
)
|
|
parser.add_argument(
|
|
"--api-plan",
|
|
default=API_PLAN,
|
|
choices=["free", "normal", "pro"],
|
|
help="API plan",
|
|
)
|
|
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
|
|
p_search = subparsers.add_parser("search", help="Search tokens by keyword")
|
|
p_search.add_argument("--keyword", required=True, help="Token symbol or address")
|
|
p_search.add_argument("--chain", help="Chain name (bsc, solana, eth, base)")
|
|
p_search.add_argument(
|
|
"--limit", type=int, default=100, help="Number of results (max 300)"
|
|
)
|
|
|
|
p_price = subparsers.add_parser("price", help="Get batch token prices")
|
|
p_price.add_argument(
|
|
"--token-ids",
|
|
required=True,
|
|
help="Comma-separated token IDs (e.g. PEPE-bsc,TRUMP-bsc)",
|
|
)
|
|
|
|
p_trending = subparsers.add_parser("trending", help="Get trending tokens")
|
|
p_trending.add_argument("--chain", required=True, help="Chain name")
|
|
p_trending.add_argument("--page", type=int, default=0, help="Page number")
|
|
p_trending.add_argument(
|
|
"--page-size", type=int, default=50, help="Results per page (max 100)"
|
|
)
|
|
|
|
p_token = subparsers.add_parser("token", help="Get token details and top pairs")
|
|
p_token.add_argument(
|
|
"--token-id", required=True, help="Token ID (address-chain format)"
|
|
)
|
|
|
|
p_risk = subparsers.add_parser("risk", help="Get token risk information")
|
|
p_risk.add_argument(
|
|
"--token-id", required=True, help="Token ID (address-chain format)"
|
|
)
|
|
|
|
p_holders = subparsers.add_parser("holders", help="Get top 100 token holders")
|
|
p_holders.add_argument(
|
|
"--token-id", required=True, help="Token ID (address-chain format)"
|
|
)
|
|
p_holders.add_argument(
|
|
"--limit", type=int, default=100, help="Number of holders (max 100)"
|
|
)
|
|
|
|
p_klines = subparsers.add_parser("klines", help="Get kline/candlestick data")
|
|
p_klines.add_argument("--token-id", help="Token ID (use OR --pair-id)")
|
|
p_klines.add_argument("--pair-id", help="Pair ID (use OR --token-id)")
|
|
p_klines.add_argument(
|
|
"--interval", default="1h", help="Interval (1m, 5m, 15m, 30m, 1h, 4h, 1d)"
|
|
)
|
|
p_klines.add_argument("--limit", type=int, default=100, help="Number of klines")
|
|
|
|
p_pairs = subparsers.add_parser("pairs", help="Get pair details")
|
|
p_pairs.add_argument(
|
|
"--pair-id", required=True, help="Pair ID (address-chain format)"
|
|
)
|
|
|
|
p_wallet_pnl = subparsers.add_parser(
|
|
"wallet-pnl", help="Get wallet PnL for a token"
|
|
)
|
|
p_wallet_pnl.add_argument("--wallet", required=True, help="Wallet address")
|
|
p_wallet_pnl.add_argument("--chain", required=True, help="Chain name")
|
|
p_wallet_pnl.add_argument("--token", required=True, help="Token address")
|
|
p_wallet_pnl.add_argument(
|
|
"--from-time", type=int, help="Unix epoch seconds (earliest 15 days ago)"
|
|
)
|
|
p_wallet_pnl.add_argument("--to-time", type=int, help="Unix epoch seconds")
|
|
|
|
p_wallet_info = subparsers.add_parser(
|
|
"wallet-info", help="Get wallet info (all tokens on chain)"
|
|
)
|
|
p_wallet_info.add_argument("--wallet", required=True, help="Wallet address")
|
|
p_wallet_info.add_argument("--chain", required=True, help="Chain name")
|
|
|
|
p_wallet_tokens = subparsers.add_parser(
|
|
"wallet-tokens", help="Get all tokens holding in wallet"
|
|
)
|
|
p_wallet_tokens.add_argument("--wallet", required=True, help="Wallet address")
|
|
p_wallet_tokens.add_argument("--chain", required=True, help="Chain name")
|
|
p_wallet_tokens.add_argument("--sort", default="last_txn_time", help="Sort field")
|
|
p_wallet_tokens.add_argument(
|
|
"--sort-dir", default="desc", help="Sort direction (desc/asc)"
|
|
)
|
|
p_wallet_tokens.add_argument(
|
|
"--hide-sold", type=int, default=0, help="Hide sold tokens (0/1)"
|
|
)
|
|
p_wallet_tokens.add_argument(
|
|
"--hide-small", type=int, default=0, help="Hide small balances (0/1)"
|
|
)
|
|
|
|
p_smart = subparsers.add_parser(
|
|
"smart-wallets", help="Get smart wallet list (for copy trading)"
|
|
)
|
|
p_smart.add_argument("--chain", required=True, help="Chain name")
|
|
p_smart.add_argument("--sort", default="total_profit", help="Sort field")
|
|
p_smart.add_argument("--sort-dir", default="desc", help="Sort direction (desc/asc)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not args.api_key:
|
|
print("Error: API key required. Set AVE_API_KEY env or use --api-key")
|
|
sys.exit(1)
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
cmd_map = {
|
|
"search": cmd_search,
|
|
"price": cmd_price,
|
|
"trending": cmd_trending,
|
|
"token": cmd_token,
|
|
"risk": cmd_risk,
|
|
"holders": cmd_holders,
|
|
"klines": cmd_klines,
|
|
"pairs": cmd_pairs,
|
|
"wallet-pnl": cmd_wallet_pnl,
|
|
"wallet-info": cmd_wallet_info,
|
|
"wallet-tokens": cmd_wallet_tokens,
|
|
"smart-wallets": cmd_smart_wallets,
|
|
}
|
|
|
|
cmd_map[args.command](args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|