diff --git a/docs/ave-hackathon-research.md b/docs/ave-hackathon-research.md index d9b0c7d..a91ca37 100644 --- a/docs/ave-hackathon-research.md +++ b/docs/ave-hackathon-research.md @@ -89,7 +89,7 @@ The AVE Claw Hackathon is a developer competition focused on building trading bo |----------|-------------|------| | `GET /v2/tokens` | Search tokens by keyword | 5 CU | | `GET /v2/tokens/{token-id}` | Token details + top 5 pairs | 5 CU | -| `GET /v2/tokens/price` | Batch token prices (up to 200) | 100 CU | +| `POST /v2/tokens/price` | Batch token prices (up to 200) | 100 CU | | `GET /v2/tokens/trending` | Trending tokens | 5 CU | | `GET /v2/tokens/top100/{token-id}` | Top 100 token holders | 10 CU | | `GET /v2/contracts/{token-id}` | Token risk info | 10 CU | @@ -98,8 +98,13 @@ The AVE Claw Hackathon is a developer competition focused on building trading bo | `GET /v2/address/tx` | Wallet transaction history | 100 CU | | `GET /v2/address/pnl` | Wallet PnL data | 5 CU | | `GET /v2/address/walletinfo` | Wallet info (all tokens) | 5 CU | +| `GET /v2/address/walletinfo/tokens` | All tokens in wallet | 10 CU | +| `GET /v2/address/smart_wallet/list` | Smart wallet list (copy trading) | 5 CU | | `GET /v2/klines/token/{token-id}` | Kline data by token | 10 CU | | `GET /v2/klines/pair/{pair-id}` | Kline data by pair | 10 CU | +| `GET /v2/pairs/{pair-id}` | Pair details | 5 CU | +| `GET /v2/tokens/platform` | Tokens by launch platform | 10 CU | +| `GET /v2/tokens/main` | Main tokens on chain | 5 CU | ### WebSocket API (`wss://wss.ave-api.xyz`) @@ -130,47 +135,15 @@ The AVE Claw Hackathon is a developer competition focused on building trading bo | GitHub (Skills) | https://github.com/AveCloud/ave-cloud-skill | | Telegram Support | https://t.me/ave_ai_cloud | -### Python Skill Scripts (GitHub) +### Python Skill Scripts -The `ave-cloud-skill` repo provides Python scripts: +The `docs/scripts/` directory contains runnable Python scripts: | Script | Purpose | |--------|---------| | `ave_data_rest.py` | Token search, prices, klines, holders, risk | | `ave_data_wss.py` | Real-time WebSocket streams | | `ave_trade_rest.py` | Chain & proxy wallet trading | -| `ave_trade_wss.py` | Proxy wallet WebSocket updates | - -### Quick Start Example - -```bash -# Build Docker image -docker build -f scripts/Dockerfile.txt -t ave-cloud . - -# Token search -docker run --rm \ - -e AVE_API_KEY=your_api_key \ - -e API_PLAN=free \ - --entrypoint python3 \ - ave-cloud scripts/ave_data_rest.py search --keyword PEPE - -# Live price watch -docker run --rm -it \ - -e AVE_API_KEY=your_api_key \ - -e API_PLAN=pro \ - --entrypoint python3 \ - ave-cloud scripts/ave_data_wss.py wss-repl - -# Dry-run trade preview -docker run --rm \ - -e AVE_API_KEY=your_api_key \ - -e API_PLAN=free \ - --entrypoint python3 \ - ave-cloud scripts/ave_trade_rest.py quote \ - --chain bsc --in-token 0x55d398326f99059fF775485246999027B3197955 \ - --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ - --in-amount 10000000 --swap-type buy -``` ### Token Links Format View tokens on AVE Pro: `https://pro.ave.ai/token/-` @@ -178,7 +151,7 @@ Example: `https://pro.ave.ai/token/0x1234...abcd-bsc` --- -## Decision Guidance for Participants +## 6. Decision Guidance for Participants **What to Build:** @@ -195,7 +168,632 @@ Example: `https://pro.ave.ai/token/0x1234...abcd-bsc` --- -## 6. AVE Cloud Agent Skills - User Insights +## 7. Extended API Documentation + +### 7.1 Token ID Format + +All token references use the format `-`: + +``` +PEPE-bsc +0x6982508145454Ce325dDbE47a25d4ec3d2311933-bsc +6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN-solana +``` + +**Chain names:** `bsc`, `solana`, `eth`, `base` + +### 7.2 Complete Request/Response Examples + +#### Token Search + +**Request:** +```bash +curl "https://prod.ave-api.com/v2/tokens?keyword=PEPE&chain=bsc&limit=10" \ + -H "X-API-KEY: your_api_key" +``` + +**Response:** +```json +{ + "status": 200, + "msg": "success", + "data": [ + { + "name": "Pepe", + "symbol": "PEPE", + "chain": "bsc", + "current_price_usd": "0.00001234", + "market_cap": "1234567890", + "tx_volume_u_24h": "50000000", + "main_pair_tvl": "1000000", + "logo_url": "https://..." + } + ] +} +``` + +#### Batch Token Price + +**Request:** +```bash +curl -X POST "https://prod.ave-api.com/v2/tokens/price" \ + -H "X-API-KEY: your_api_key" \ + -H "Content-Type: application/json" \ + -d '{"token_ids": ["PEPE-bsc", "TRUMP-bsc", "0x55d398326f99059fF775485246999027B3197955-bsc"]}' +``` + +**Response:** +```json +{ + "status": 200, + "msg": "success", + "data": { + "PEPE-bsc": { + "price": "0.00001234", + "price_24h_change": "5.67", + "updated_at": 1756888200 + }, + "TRUMP-bsc": { + "price": "4.56", + "price_24h_change": "-2.34", + "updated_at": 1756888200 + } + } +} +``` + +#### Wallet PnL + +**Request:** +```bash +curl "https://prod.ave-api.com/v2/address/pnl?wallet_address=0xd9c500dff816a1da21a48a732d3498bf09dc9aeb&chain=bsc&token_address=0x55d398326f99059fF775485246999027B3197955" \ + -H "X-API-KEY: your_api_key" +``` + +**Response:** +```json +{ + "status": 200, + "msg": "success", + "data": { + "wallet_address": "0xd9c500dff816a1da21a48a732d3498bf09dc9aeb", + "token_address": "0x55d398326f99059fF775485246999027B3197955", + "total_profit": "123.45", + "total_profit_rate": "0.15", + "buy_amount": "10000", + "sell_amount": "10123.45", + "avg_buy_price": "1.0", + "avg_sell_price": "1.0123" + } +} +``` + +### 7.3 Error Codes + +#### HTTP Status Codes + +| Code | Meaning | Cause | +|------|---------|-------| +| 200 | Success | Request completed | +| 400 | Bad Request | Invalid parameters, malformed JSON | +| 401 | Unauthorized | Missing or invalid API key | +| 403 | Forbidden | API key expired, plan limits reached | +| 429 | Rate Limited | TPS exceeded | +| 500 | Server Error | Internal error, try again later | + +#### Business Error Codes + +| Code | Meaning | +|------|---------| +| 200 | Success | +| 1001 | General failure | +| 1011 | System error | +| 1021 | Signature verification failed | +| 1022 | API frozen, contact support | +| 1023 | Request expired (timestamp out of range) | +| 2001 | Request parameter error | +| 3001 | Transaction send failed | +| 3011 | Transaction record not found | +| 3021 | Order cancellation failed | +| 3101 | User does not exist | +| 3102 | User assets don't belong to this organization | +| 3103 | User asset account disabled | +| 3104 | No proxy wallet permission, upgrade plan required | + +### 7.4 CU (Compute Unit) Cost Optimization + +Each API call costs CU. Strategies to minimize usage: + +| Strategy | Example | +|----------|---------| +| Use `/v2/tokens/price` batch (100 CU) vs individual calls | Batch up to 200 tokens in one call | +| Cache trending/risk data | Risk info changes slowly, cache for 5-10 min | +| Use filters | Add `tvl_min`, `tx_24h_volume_min` to price queries | +| Prefer search over detailed token | `/v2/tokens` (5 CU) vs `/v2/tokens/{id}` (5 CU) | + +### 7.5 Pagination + +Endpoints that return lists support pagination: + +| Parameter | For | +|-----------|-----| +| `page_size` | Results per page (max varies) | +| `current_page` | Page number (0-indexed) | +| `last_id` | Cursor for next page (from previous response) | + +Example pagination flow: +```python +# Get first page +result = get_trending_tokens(chain="bsc", page=0, page_size=50) +next_page = result["next_page"] # Use this for next request + +# For wallet PnL with cursor +result = get_wallet_pnl(wallet, chain, token) +if result.get("has_more"): + next_result = get_wallet_pnl(wallet, chain, token, last_id=result["last_id"]) +``` + +--- + +## 8. Working Code Examples + +### 8.1 Setup + +```bash +cd /home/shoko/repositories/randebu +pip install -r docs/scripts/requirements.txt + +export AVE_API_KEY=your_api_key_here +export API_PLAN=free # or normal, pro +``` + +### 8.2 Data REST API Examples + +```bash +# Token search +python3 docs/scripts/ave_data_rest.py search --keyword PEPE --chain bsc + +# Batch price (up to 200 tokens) +python3 docs/scripts/ave_data_rest.py price --token-ids "PEPE-bsc,TRUMP-bsc,SOL-bsc" + +# Trending tokens +python3 docs/scripts/ave_data_rest.py trending --chain bsc --page-size 20 + +# Token details + top pairs +python3 docs/scripts/ave_data_rest.py token --token-id "PEPE-bsc" + +# Risk assessment +python3 docs/scripts/ave_data_rest.py risk --token-id "PEPE-bsc" + +# Top 100 holders +python3 docs/scripts/ave_data_rest.py holders --token-id "PEPE-bsc" --limit 50 + +# Kline data (1h intervals) +python3 docs/scripts/ave_data_rest.py klines --token-id "PEPE-bsc" --interval 1h --limit 100 + +# Wallet PnL +python3 docs/scripts/ave_data_rest.py wallet-pnl \ + --wallet 0xd9c500dff816a1da21a48a732d3498bf09dc9aeb \ + --chain bsc --token 0x55d398326f99059fF775485246999027B3197955 + +# Smart wallets (for copy trading) +python3 docs/scripts/ave_data_rest.py smart-wallets --chain bsc --sort total_profit +``` + +### 8.3 WebSocket Examples + +```bash +# Requires API_PLAN=pro + +# Subscribe to swap transactions by pair +python3 docs/scripts/ave_data_wss.py subscribe-tx \ + --pair Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE \ + --chain solana + +# Subscribe to all txs involving a token +python3 docs/scripts/ave_data_wss.py subscribe-multi-tx \ + --token 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c \ + --chain bsc + +# Subscribe to liquidity changes +python3 docs/scripts/ave_data_wss.py subscribe-liq \ + --pair Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE \ + --chain solana + +# Subscribe to price changes +python3 docs/scripts/ave_data_wss.py subscribe-price \ + --pairs "PEPE-bsc,TRUMP-bsc,SOL-bsc" + +# Subscribe to kline data +python3 docs/scripts/ave_data_wss.py subscribe-kline \ + --token "PEPE-bsc" --interval 1m + +# Interactive REPL mode +python3 docs/scripts/ave_data_wss.py wss-repl +# Then type commands like: +# subscribe tx +# subscribe price PEPE-bsc,TRUMP-bsc +# list +# quit +``` + +### 8.4 Trading Examples + +```bash +# Chain Wallet (self-custody) - Free tier OK +# Dry-run quote +python3 docs/scripts/ave_trade_rest.py chain-quote \ + --chain bsc \ + --in-token 0x55d398326f99059fF775485246999027B3197955 \ + --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ + --in-amount 10000000 --swap-type buy + +# Actual swap (requires signing keys configured) +python3 docs/scripts/ave_trade_rest.py chain-swap \ + --chain bsc \ + --in-token 0x55d398326f99059fF775485246999027B3197955 \ + --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ + --in-amount 10000000 --swap-type buy + +# Proxy Wallet (server-managed) - Requires normal/pro tier +export AVE_SECRET_KEY=your_secret_key_here + +# Dry-run quote +python3 docs/scripts/ave_trade_rest.py proxy-quote \ + --chain bsc \ + --proxy-wallet 0x... \ + --in-token 0x55d398326f99059fF775485246999027B3197955 \ + --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ + --in-amount 10000000 --swap-type buy + +# Market order with TP/SL +python3 docs/scripts/ave_trade_rest.py proxy-market \ + --chain bsc \ + --proxy-wallet 0x... \ + --in-token 0x55d398326f99059fF775485246999027B3197955 \ + --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ + --in-amount 10000000 --swap-type buy \ + --tp-price 35.0 --sl-price 30.0 + +# Limit order +python3 docs/scripts/ave_trade_rest.py proxy-limit \ + --chain bsc \ + --proxy-wallet 0x... \ + --in-token 0x55d398326f99059fF775485246999027B3197955 \ + --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ + --in-amount 10000000 --swap-type buy --price 35.5 + +# Get open orders +python3 docs/scripts/ave_trade_rest.py proxy-orders \ + --chain bsc --proxy-wallet 0x... + +# Cancel order +python3 docs/scripts/ave_trade_rest.py proxy-cancel \ + --chain bsc --order-id xxx + +# Create proxy wallet +python3 docs/scripts/ave_trade_rest.py create-proxy-wallet --chain bsc + +# List proxy wallets +python3 docs/scripts/ave_trade_rest.py list-proxy-wallets --chain bsc +``` + +--- + +## 9. Security Guide + +### 9.1 API Key Management + +| Do | Don't | +|----|-------| +| Store in environment variables | Commit to git | +| Use `.env` file (add to `.gitignore`) | Share in Slack/Discord | +| Rotate keys periodically | Use in client-side code | +| Regenerate if compromised | Log API keys in errors | + +```bash +# .env file (NEVER commit this) +AVE_API_KEY=your_api_key_here +AVE_SECRET_KEY=your_secret_key_here +AVE_EVM_PRIVATE_KEY=your_private_key_here +``` + +### 9.2 HMAC Signing for Proxy Wallet + +Proxy wallet requests require HMAC-SHA256 signature: + +```python +import hashlib +import hmac +import json +import time + +def generate_signature(secret_key: str, timestamp: str, body: str = "") -> str: + message = timestamp + body + signature = hmac.new( + secret_key.encode(), + message.encode(), + hashlib.sha256 + ).hexdigest() + return signature + +# Usage +timestamp = str(int(time.time() * 1000)) +body = json.dumps({"chain": "bsc", "proxy_wallet": "0x..."}, separators=(",", ":")) +signature = generate_signature(AVE_SECRET_KEY, timestamp, body) + +headers = { + "X-API-KEY": API_KEY, + "X-Signature": signature, + "X-Timestamp": timestamp, + "Content-Type": "application/json" +} +``` + +### 9.3 Chain Wallet Private Key Protection + +| Option | Security Level | Use Case | +|--------|---------------|----------| +| Hardware wallet | Highest | Production trading | +| Encrypted keystore | High | Development | +| Mnemonic (never raw key) | Medium | Quick testing | +| Raw private key in env | Low | Temporary only | + +```bash +# Use hardware wallet or encrypted key +# NEVER do this: +AVE_EVM_PRIVATE_KEY=0x1234567890abcdef... + +# DO this instead: +# Import from encrypted keystore or hardware wallet +``` + +### 9.4 Webhook/Callback Verification + +If implementing webhooks for order updates: + +```python +def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool: + expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() + return hmac.compare_digest(signature, expected) +``` + +### 9.5 Security Checklist + +- [ ] API key stored in environment variable, not code +- [ ] `.env` in `.gitignore` +- [ ] Proxy wallet HMAC signing implemented correctly +- [ ] Private keys never logged or error messages +- [ ] HTTPS only (all AVE Cloud endpoints use HTTPS) +- [ ] Timestamp validation (requests expire after ~30s) +- [ ] Input validation on all user-provided addresses/tokens + +--- + +## 10. Testing Strategies + +### 10.1 Dry-Run / Quote Mode + +Always test with quotes first: + +```bash +# Chain wallet quote (no actual trade) +python3 docs/scripts/ave_trade_rest.py chain-quote \ + --chain bsc --in-token 0x55d398326f99059fF775485246999027B3197955 \ + --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ + --in-amount 10000000 --swap-type buy + +# Proxy wallet quote (no actual trade) +python3 docs/scripts/ave_trade_rest.py proxy-quote \ + --chain bsc --proxy-wallet 0x... \ + --in-token 0x55d398326f99059fF775485246999027B3197955 \ + --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c \ + --in-amount 10000000 --swap-type buy +``` + +### 10.2 Paper Trading Pattern + +```python +import os + +class PaperTradingBot: + def __init__(self): + self.mode = os.getenv("TRADING_MODE", "paper") # paper or live + + def execute_trade(self, order): + if self.mode == "paper": + print(f"[PAPER] Would execute: {order}") + return {"status": "paper", "order_id": "paper_123"} + else: + return self.live_execute(order) +``` + +### 10.3 Testnet Considerations + +| Chain | Testnet | Faucet | +|-------|---------|--------| +| BSC | https://testnet.bscscan.com | https://testnet.bnb.org | +| Solana | https://api.devnet.solana.com | https://faucet.solana.com | +| ETH | Sepolia testnet | https://sepoliafaucet.com | +| Base | Base Sepolia | https://www.coinbase.com/faucets | + +### 10.4 Mocking API Responses + +```python +import pytest +from unittest.mock import Mock, patch + +def test_token_search(): + mock_response = { + "status": 200, + "data": [{"symbol": "PEPE", "chain": "bsc", "current_price_usd": "0.00001"}] + } + + with patch("requests.get") as mock_get: + mock_get.return_value = Mock(json=lambda: mock_response, status_code=200) + + result = search_tokens("PEPE", "bsc") + assert result["data"][0]["symbol"] == "PEPE" +``` + +### 10.5 Testing WebSocket Reconnection + +```python +def test_reconnect(): + client = AveWssClient(API_KEY) + client.connect() + + # Simulate disconnect + client.ws.close() + + # Should auto-reconnect + assert client.running + time.sleep(6) # Wait for reconnect + assert client.ws is not None +``` + +--- + +## 11. Troubleshooting Guide + +### 11.1 Common Errors and Fixes + +| Error | HTTP Code | Business Code | Cause | Solution | +|-------|-----------|--------------|-------|---------| +| 401 Unauthorized | 401 | - | Invalid/missing API key | Regenerate at cloud.ave.ai | +| 403 Forbidden | 403 | 1022 | API key expired | Renew key or contact support | +| 403 Forbidden | 403 | 3104 | Free tier accessing proxy wallet | Upgrade to Level 1+ | +| 429 Rate Limited | 429 | - | TPS exceeded | Add delay, implement backoff | +| 3001 Send TX Failed | 200 | 3001 | Insufficient gas or wallet balance | Check RPC, fund wallet | +| 1021 Signature Failed | 200 | 1021 | HMAC signature incorrect | Check timestamp and signing logic | +| 2001 Parameter Error | 200 | 2001 | Invalid request parameters | Check API docs for required fields | + +### 11.2 WebSocket Troubleshooting + +**Connection refused:** +```bash +# Verify API plan is pro +echo $API_PLAN # Should be "pro" +``` + +**Not receiving messages:** +```python +# Enable debug mode +client = AveWssClient(api_key, debug=True) +``` + +**Auto-reconnect not working:** +```python +# Manual reconnect +client.disconnect() +time.sleep(5) +client.connect() +``` + +### 11.3 Rate Limit Handling + +```python +import time +import requests + +def call_with_retry(func, max_retries=3): + for attempt in range(max_retries): + result = func() + if result.status_code != 429: + return result + wait_time = 2 ** attempt # Exponential backoff + print(f"Rate limited, waiting {wait_time}s...") + time.sleep(wait_time) + raise Exception("Max retries exceeded") +``` + +### 11.4 Debugging Tips + +1. **Check headers:** + ```python + print(headers) # Verify X-API-KEY is present + ``` + +2. **Verify token ID format:** + ```python + # Correct + "PEPE-bsc" + "0x1234...-eth" + + # Wrong + "PEPE" # Missing chain + "bsc:0x1234..." # Wrong separator + ``` + +3. **Check timestamp for HMAC:** + ```python + # Timestamp must be milliseconds + timestamp = str(int(time.time() * 1000)) + ``` + +4. **Amount format:** + ```python + # Use string to avoid floating point precision issues + "10000000" # Correct + 10000000 # Also works + 10.0 # WRONG - precision loss + ``` + +--- + +## 12. Competitive Analysis + +### 12.1 AVE Cloud vs Alternatives + +| Feature | AVE Cloud | DexScreener | Moralis | CoinGecko | +|---------|-----------|-------------|---------|-----------| +| Multi-chain support | 4 chains | 20+ chains | 10+ chains | 100+ chains | +| Trading API | ✅ Chain + Proxy wallet | ❌ | ✅ ETH/ERC-20 only | ❌ | +| WebSocket streams | ✅ (pro tier) | ✅ Free | ✅ Paid only | ❌ | +| TP/SL automation | ✅ Proxy wallet | ❌ | ❌ | ❌ | +| Copy trading support | ✅ Smart wallet list | ❌ | ✅ | ❌ | +| Self-custody option | ✅ Chain wallet | ❌ | ❌ | ❌ | +| CU-based pricing | ✅ | Free | Free tier limited | Free tier limited | +| Risk/honeypot detection | ✅ Built-in | Limited | ✅ | ✅ | +| API documentation | Chinese-focused | Basic | Comprehensive | Comprehensive | +| Community size | Very small (3 stars) | Large | Large | Very large | + +### 12.2 Unique Selling Points of AVE Cloud + +1. **TP/SL Automation** - Built into proxy wallet, no need to monitor +2. **Dual Wallet Options** - Self-custody (chain) or managed (proxy) +3. **Copy Trading Infrastructure** - Smart wallet tracking API built-in +4. **All-in-one** - Data + Trading in single platform +5. **Low fees** - 0.6% chain, 0.8% proxy with rebates + +### 12.3 Weaknesses + +1. **Small community** - Only 3 GitHub stars, limited tutorials +2. **Documentation** - Primarily Chinese, limited English examples +3. **New platform** - Early stage, potential instability +4. **Feature locks** - WebSocket requires pro tier +5. **Solana complexity** - Priority fees not well documented + +### 12.4 When to Use Alternatives + +| Use Case | Alternative | +|----------|-------------| +| Multi-chain analytics only | DexScreener API | +| ERC-20 trading on ETH | Moralis | +| Historical price data | CoinGecko API | +| Advanced charting | TradingView API | +| Social sentiment | LunarCrush | + +### 12.5 AVE Cloud Best Fit + +- **Hackathon projects** focused on automated trading with TP/SL +- **Copy trading bots** leveraging smart wallet tracking +- **Multi-chain trading bots** needing unified API +- **Real-time monitoring dashboards** with WebSocket feeds + +--- + +## 13. AVE Cloud Agent Skills - User Insights **GitHub Activity:** - `ave-cloud-skill`: 3 stars, 46 commits (very early stage) @@ -239,4 +837,70 @@ Example: `https://pro.ave.ai/token/0x1234...abcd-bsc` - Discord: discord.gg/Z2RmAzF2 - No YouTube tutorials, blog posts, or external reviews yet -**Bottom line:** The skills system is very new (3 stars), primarily used for token research and proxy wallet trading. The self-custody chain-wallet is the least used due to complexity. \ No newline at end of file +**Bottom line:** The skills system is very new (3 stars), primarily used for token research and proxy wallet trading. The self-custody chain-wallet is the least used due to complexity. + +--- + +## Appendix: Quick Reference + +### Environment Variables + +| Variable | Required For | Description | +|----------|-------------|-------------| +| `AVE_API_KEY` | All scripts | API key from cloud.ave.ai | +| `API_PLAN` | WSS, Proxy trading | free, normal, or pro | +| `AVE_SECRET_KEY` | Proxy wallet | HMAC signing secret | +| `AVE_EVM_PRIVATE_KEY` | Chain wallet (optional) | Hex private key for BSC/ETH/Base | +| `AVE_SOLANA_PRIVATE_KEY` | Chain wallet (optional) | Base58 private key for Solana | +| `AVE_MNEMONIC` | Chain wallet (optional) | BIP39 mnemonic | + +### Script Quick Reference + +```bash +# Data REST +python3 docs/scripts/ave_data_rest.py search --keyword +python3 docs/scripts/ave_data_rest.py price --token-ids +python3 docs/scripts/ave_data_rest.py trending --chain +python3 docs/scripts/ave_data_rest.py token --token-id +python3 docs/scripts/ave_data_rest.py risk --token-id +python3 docs/scripts/ave_data_rest.py holders --token-id +python3 docs/scripts/ave_data_rest.py klines --token-id +python3 docs/scripts/ave_data_rest.py wallet-pnl --wallet --chain --token +python3 docs/scripts/ave_data_rest.py wallet-info --wallet --chain +python3 docs/scripts/ave_data_rest.py smart-wallets --chain + +# Data WebSocket +python3 docs/scripts/ave_data_wss.py subscribe-tx --pair --chain +python3 docs/scripts/ave_data_wss.py subscribe-multi-tx --token --chain +python3 docs/scripts/ave_data_wss.py subscribe-price --pairs +python3 docs/scripts/ave_data_wss.py wss-repl + +# Trading REST +python3 docs/scripts/ave_trade_rest.py chain-quote --chain --in-token --out-token --in-amount --swap-type +python3 docs/scripts/ave_trade_rest.py chain-swap ... +python3 docs/scripts/ave_trade_rest.py proxy-quote ... +python3 docs/scripts/ave_trade_rest.py proxy-market ... [--tp-price

] [--sl-price

] +python3 docs/scripts/ave_trade_rest.py proxy-limit ... --price

+python3 docs/scripts/ave_trade_rest.py proxy-orders --chain --proxy-wallet +python3 docs/scripts/ave_trade_rest.py proxy-cancel --chain --order-id +python3 docs/scripts/ave_trade_rest.py create-proxy-wallet --chain +python3 docs/scripts/ave_trade_rest.py list-proxy-wallets --chain +``` + +### Common Token Addresses (BSC Mainnet) + +| Token | Address | +|-------|---------| +| BNB | (native) | +| USDT | 0x55d398326f99059fF775485246999027B3197955 | +| BUSD | 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56 | +| BTCB | 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c | +| ETH | 0x2170Ed0880ac9A755fd29C2681C0DBeD5aF88B2c | + +### Common Token Addresses (Solana) + +| Token | Address | +|-------|---------| +| SOL | So11111111111111111111111111111111111111112 | +| USDC | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | +| USDT | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB | diff --git a/docs/scripts/ave_data_rest.py b/docs/scripts/ave_data_rest.py new file mode 100644 index 0000000..1f7a979 --- /dev/null +++ b/docs/scripts/ave_data_rest.py @@ -0,0 +1,494 @@ +#!/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() diff --git a/docs/scripts/ave_data_wss.py b/docs/scripts/ave_data_wss.py new file mode 100644 index 0000000..047b137 --- /dev/null +++ b/docs/scripts/ave_data_wss.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python3 +""" +AVE Cloud Data WebSocket API - Real-time Streams + +Usage: + export AVE_API_KEY=your_api_key_here + export API_PLAN=pro # REQUIRED for WebSocket (must be 'pro') + + python3 docs/scripts/ave_data_wss.py subscribe-tx --pair "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE" --chain solana + python3 docs/scripts/ave_data_wss.py subscribe-multi-tx --token "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c" --chain bsc + python3 docs/scripts/ave_data_wss.py subscribe-liq --pair "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE" --chain solana + python3 docs/scripts/ave_data_wss.py subscribe-price --pairs "PEPE-bsc,TRUMP-bsc" + python3 docs/scripts/ave_data_wss.py subscribe-kline --token "0x6982508145454Ce325dDbE47a25d4ec3d2311933-bsc" --interval 1m + python3 docs/scripts/ave_data_wss.py wss-repl # Interactive REPL mode + +Notes: + - WebSocket API requires API_PLAN=pro + - Max 5 concurrent WebSocket connections + - Auto-reconnect with exponential backoff on disconnect +""" + +import argparse +import json +import os +import sys +import time +from dataclasses import dataclass, field +from typing import Optional + +import websocket + +WSS_URL = "wss://wss.ave-api.xyz" +API_KEY = os.getenv("AVE_API_KEY", "") +API_PLAN = os.getenv("API_PLAN", "") + + +@dataclass +class WssMessage: + id: int + method: str + params: list + result: Optional[dict] = None + + +@dataclass +class WssSubscription: + topic: str + address: str + chain: str + id: int + + +class AveWssClient: + def __init__(self, api_key: str, debug: bool = False): + self.api_key = api_key + self.debug = debug + self.ws: Optional[websocket.WebSocket] = None + self.subscription_id = 1 + self.subscriptions: list[WssSubscription] = [] + self.running = False + + def connect(self): + if self.ws: + self.disconnect() + self.ws = websocket.WebSocketApp( + WSS_URL, + on_open=self._on_open, + on_message=self._on_message, + on_error=self._on_error, + on_close=self._on_close, + ) + self.running = True + + def disconnect(self): + self.running = False + if self.ws: + self.ws.close() + self.ws = None + + def _on_open(self, ws): + print("[Connected to WebSocket]", file=sys.stderr) + + def _on_message(self, ws, message): + try: + data = json.loads(message) + if self.debug: + print(f"[DEBUG] {json.dumps(data, indent=2)}", file=sys.stderr) + if "result" in data and data.get("id"): + for sub in self.subscriptions: + if sub.id == data["id"]: + sub.id = data["result"].get("id", data["id"]) + print( + f"[Subscribed to {sub.topic}] {sub.address} on {sub.chain}", + file=sys.stderr, + ) + break + elif "params" in data: + topic = data["params"].get("topic", "unknown") + if topic == "tx": + self._handle_tx(data["params"]["data"]) + elif topic == "liq": + self._handle_liq(data["params"]["data"]) + elif topic == "kline": + self._handle_kline(data["params"]["data"]) + elif topic == "price": + self._handle_price(data["params"]["data"]) + except json.JSONDecodeError: + print(f"[Error] Failed to parse message: {message}", file=sys.stderr) + + def _on_error(self, ws, error): + print(f"[WebSocket Error] {error}", file=sys.stderr) + + def _on_close(self, ws, close_status_code, close_msg): + print(f"[Disconnected] {close_status_code}: {close_msg}", file=sys.stderr) + self.running = False + + def _handle_tx(self, tx: dict): + print( + f"[TX] {tx.get('chain')}: {tx.get('from_symbol')} -> {tx.get('to_symbol')} " + f"Amount: {tx.get('from_amount')} {tx.get('from_symbol')} | " + f"Sender: {tx.get('sender', 'unknown')[:10]}... | " + f"Pair: {tx.get('pair_address', 'unknown')[:15]}..." + ) + + def _handle_liq(self, liq: dict): + print( + f"[LIQ] {liq.get('chain')}: {liq.get('action')} " + f"Amount: {liq.get('amount')} {liq.get('symbol')} | " + f"Pair: {liq.get('pair_address', 'unknown')[:15]}..." + ) + + def _handle_kline(self, kline: dict): + print( + f"[KLINE] {kline.get('token_id', kline.get('pair_id'))} " + f"{kline.get('interval')}: O={kline.get('open')} H={kline.get('high')} " + f"L={kline.get('low')} C={kline.get('close')} V={kline.get('volume')}" + ) + + def _handle_price(self, price: dict): + print( + f"[PRICE] {price.get('token_id', price.get('pair_id'))}: " + f"{price.get('price')} USD (change: {price.get('price_change_24h', 'N/A')}%)" + ) + + def _send(self, method: str, params: list, timeout: int = 10) -> Optional[dict]: + if not self.ws: + return None + msg_id = self.subscription_id + self.subscription_id += 1 + msg = {"jsonrpc": "2.0", "method": method, "params": params, "id": msg_id} + self.ws.send(json.dumps(msg)) + return {"id": msg_id} + + def subscribe_tx(self, pair_address: str, chain: str): + params = ["tx", pair_address, chain] + result = self._send("subscribe", params) + if result: + self.subscriptions.append( + WssSubscription( + topic="tx", address=pair_address, chain=chain, id=result["id"] + ) + ) + + def subscribe_multi_tx(self, token_address: str, chain: str): + params = ["multi_tx", token_address, chain] + result = self._send("subscribe", params) + if result: + self.subscriptions.append( + WssSubscription( + topic="multi_tx", + address=token_address, + chain=chain, + id=result["id"], + ) + ) + + def subscribe_liq(self, pair_address: str, chain: str): + params = ["liq", pair_address, chain] + result = self._send("subscribe", params) + if result: + self.subscriptions.append( + WssSubscription( + topic="liq", address=pair_address, chain=chain, id=result["id"] + ) + ) + + def subscribe_price(self, pairs: list): + params = ["price", pairs] + result = self._send("subscribe", params) + if result: + for pair in pairs: + self.subscriptions.append( + WssSubscription( + topic="price", address=pair, chain="multi", id=result["id"] + ) + ) + + def subscribe_kline(self, token_or_pair_id: str, interval: str = "1m"): + params = ["kline", token_or_pair_id, interval] + result = self._send("subscribe", params) + if result: + self.subscriptions.append( + WssSubscription( + topic="kline", + address=token_or_pair_id, + chain=interval, + id=result["id"], + ) + ) + + def unsubscribe(self, topic: str, address: str, chain: str): + params = ( + ["unsubscribe", topic, address, chain] + if chain != "multi" + else ["unsubscribe", topic, address] + ) + self._send("unsubscribe", params) + self.subscriptions = [ + s + for s in self.subscriptions + if not (s.topic == topic and s.address == address and s.chain == chain) + ] + + def run_forever(self, reconnect_delay: int = 5, max_reconnect_delay: int = 60): + delay = reconnect_delay + while self.running: + try: + self.ws.run_forever(ping_interval=30, ping_timeout=10) + except Exception as e: + print(f"[Error] {e}", file=sys.stderr) + if self.running: + print(f"[Reconnecting in {delay}s...]", file=sys.stderr) + time.sleep(delay) + delay = min(delay * 2, max_reconnect_delay) + self.connect() + + +def cmd_subscribe_tx(args): + if API_PLAN != "pro": + print("Error: WebSocket requires API_PLAN=pro", file=sys.stderr) + sys.exit(1) + client = AveWssClient(args.api_key, debug=args.debug) + client.connect() + client.subscribe_tx(args.pair, args.chain) + print("[Press Ctrl+C to exit]", file=sys.stderr) + try: + client.run_forever() + except KeyboardInterrupt: + client.disconnect() + + +def cmd_subscribe_multi_tx(args): + if API_PLAN != "pro": + print("Error: WebSocket requires API_PLAN=pro", file=sys.stderr) + sys.exit(1) + client = AveWssClient(args.api_key, debug=args.debug) + client.connect() + client.subscribe_multi_tx(args.token, args.chain) + print("[Press Ctrl+C to exit]", file=sys.stderr) + try: + client.run_forever() + except KeyboardInterrupt: + client.disconnect() + + +def cmd_subscribe_liq(args): + if API_PLAN != "pro": + print("Error: WebSocket requires API_PLAN=pro", file=sys.stderr) + sys.exit(1) + client = AveWssClient(args.api_key, debug=args.debug) + client.connect() + client.subscribe_liq(args.pair, args.chain) + print("[Press Ctrl+C to exit]", file=sys.stderr) + try: + client.run_forever() + except KeyboardInterrupt: + client.disconnect() + + +def cmd_subscribe_price(args): + if API_PLAN != "pro": + print("Error: WebSocket requires API_PLAN=pro", file=sys.stderr) + sys.exit(1) + pairs = [p.strip() for p in args.pairs.split(",")] + client = AveWssClient(args.api_key, debug=args.debug) + client.connect() + client.subscribe_price(pairs) + print("[Press Ctrl+C to exit]", file=sys.stderr) + try: + client.run_forever() + except KeyboardInterrupt: + client.disconnect() + + +def cmd_subscribe_kline(args): + if API_PLAN != "pro": + print("Error: WebSocket requires API_PLAN=pro", file=sys.stderr) + sys.exit(1) + client = AveWssClient(args.api_key, debug=args.debug) + client.connect() + client.subscribe_kline(args.token, args.interval) + print("[Press Ctrl+C to exit]", file=sys.stderr) + try: + client.run_forever() + except KeyboardInterrupt: + client.disconnect() + + +def cmd_wss_repl(args): + if API_PLAN != "pro": + print("Error: WebSocket requires API_PLAN=pro", file=sys.stderr) + sys.exit(1) + client = AveWssClient(args.api_key, debug=args.debug) + client.connect() + print( + "[WebSocket REPL - type 'help' for commands, 'quit' to exit]", file=sys.stderr + ) + print( + "[Commands: subscribe tx , subscribe price , unsubscribe ]", + file=sys.stderr, + ) + + subscriptions = [] + + def print_help(): + print("Commands:") + print( + " subscribe tx - Subscribe to swap transactions by pair" + ) + print( + " subscribe multi_tx - Subscribe to all txs involving a token" + ) + print( + " subscribe liq - Subscribe to liquidity changes by pair" + ) + print(" subscribe price - Subscribe to price changes") + print( + " subscribe kline - Subscribe to kline data (interval: 1m, 5m, etc.)" + ) + print(" unsubscribe

- Unsubscribe") + print(" list - List subscriptions") + print(" help - Show this help") + print(" quit - Exit") + + while True: + try: + user_input = input("wss> ").strip() + if not user_input: + continue + parts = user_input.split() + cmd = parts[0].lower() + + if cmd == "quit": + break + elif cmd == "help": + print_help() + elif cmd == "list": + for s in client.subscriptions: + print(f" {s.topic}: {s.address} on {s.chain}") + elif cmd == "subscribe" and len(parts) >= 3: + sub_type = parts[1].lower() + if sub_type == "tx" and len(parts) >= 4: + client.subscribe_tx(parts[2], parts[3]) + elif sub_type == "multi_tx" and len(parts) >= 4: + client.subscribe_multi_tx(parts[2], parts[3]) + elif sub_type == "liq" and len(parts) >= 4: + client.subscribe_liq(parts[2], parts[3]) + elif sub_type == "price" and len(parts) >= 3: + pairs = parts[2].split(",") + client.subscribe_price(pairs) + elif sub_type == "kline" and len(parts) >= 4: + client.subscribe_kline(parts[2], parts[3]) + else: + print("Invalid subscribe command") + elif cmd == "unsubscribe" and len(parts) >= 4: + client.unsubscribe(parts[1], parts[2], parts[3]) + else: + print("Unknown command. Type 'help' for commands.") + except KeyboardInterrupt: + break + except EOFError: + break + + client.disconnect() + print("\n[Exited]") + + +def main(): + parser = argparse.ArgumentParser(description="AVE Cloud Data WebSocket 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, help="API plan (or set API_PLAN env)" + ) + parser.add_argument("--debug", action="store_true", help="Enable debug output") + subparsers = parser.add_subparsers(dest="command", help="Commands") + + p_tx = subparsers.add_parser( + "subscribe-tx", help="Subscribe to swap transactions by pair" + ) + p_tx.add_argument("--pair", required=True, help="Pair address") + p_tx.add_argument( + "--chain", required=True, help="Chain name (solana, bsc, eth, base)" + ) + + p_multi = subparsers.add_parser( + "subscribe-multi-tx", help="Subscribe to all txs involving a token" + ) + p_multi.add_argument("--token", required=True, help="Token address") + p_multi.add_argument("--chain", required=True, help="Chain name") + + p_liq = subparsers.add_parser( + "subscribe-liq", help="Subscribe to liquidity changes by pair" + ) + p_liq.add_argument("--pair", required=True, help="Pair address") + p_liq.add_argument("--chain", required=True, help="Chain name") + + p_price = subparsers.add_parser( + "subscribe-price", help="Subscribe to price changes" + ) + p_price.add_argument( + "--pairs", required=True, help="Comma-separated pairs (e.g. PEPE-bsc,TRUMP-bsc)" + ) + + p_kline = subparsers.add_parser("subscribe-kline", help="Subscribe to kline data") + p_kline.add_argument( + "--token", required=True, help="Token ID (address-chain format)" + ) + p_kline.add_argument( + "--interval", default="1m", help="Interval (1m, 5m, 15m, 30m, 1h, 4h, 1d)" + ) + + subparsers.add_parser("wss-repl", help="Interactive WebSocket REPL") + + 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 = { + "subscribe-tx": cmd_subscribe_tx, + "subscribe-multi-tx": cmd_subscribe_multi_tx, + "subscribe-liq": cmd_subscribe_liq, + "subscribe-price": cmd_subscribe_price, + "subscribe-kline": cmd_subscribe_kline, + "wss-repl": cmd_wss_repl, + } + + cmd_map[args.command](args) + + +if __name__ == "__main__": + main() diff --git a/docs/scripts/ave_trade_rest.py b/docs/scripts/ave_trade_rest.py new file mode 100644 index 0000000..9afe47d --- /dev/null +++ b/docs/scripts/ave_trade_rest.py @@ -0,0 +1,560 @@ +#!/usr/bin/env python3 +""" +AVE Cloud Trading REST API - Chain Wallet & Proxy Wallet Trading + +Usage: + # Environment setup + export AVE_API_KEY=your_api_key_here + export API_PLAN=free # free for chain-wallet, normal/pro for proxy-wallet + export AVE_SECRET_KEY=your_secret_key_here # For proxy wallet HMAC signing + + # Chain Wallet (Self-custody) - Free tier OK + python3 docs/scripts/ave_trade_rest.py chain-quote --chain bsc --in-token 0x55d398326f99059fF775485246999027B3197955 --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c --in-amount 10000000 --swap-type buy + python3 docs/scripts/ave_trade_rest.py chain-swap --chain bsc --in-token 0x55d398326f99059fF775485246999027B3197955 --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c --in-amount 10000000 --swap-type buy + + # Proxy Wallet (Server-managed) - Requires normal/pro tier + python3 docs/scripts/ave_trade_rest.py proxy-quote --chain bsc --in-token 0x55d398326f99059fF775485246999027B3197955 --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c --in-amount 10000000 --swap-type buy + python3 docs/scripts/ave_trade_rest.py proxy-market --chain bsc --in-token 0x55d398326f99059fF775485246999027B3197955 --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c --in-amount 10000000 --swap-type buy + python3 docs/scripts/ave_trade_rest.py proxy-limit --chain bsc --in-token 0x55d398326f99059fF775485246999027B3197955 --out-token 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c --in-amount 10000000 --swap-type buy --price 35.5 + python3 docs/scripts/ave_trade_rest.py proxy-orders --chain bsc + python3 docs/scripts/ave_trade_rest.py proxy-cancel --chain bsc --order-id xxx + + # Wallet Management + python3 docs/scripts/ave_trade_rest.py create-proxy-wallet --chain bsc + python3 docs/scripts/ave_trade_rest.py list-proxy-wallets --chain bsc + +Notes: + - Chain Wallet: Requires API_PLAN=free or higher + - Proxy Wallet: Requires API_PLAN=normal or pro + - All amount fields use string format to avoid floating point precision issues + - Proxy wallet uses HMAC-SHA256 signing with AVE_SECRET_KEY +""" + +import argparse +import hashlib +import hmac +import json +import os +import sys +import time +from dataclasses import dataclass +from datetime import datetime, timezone +from typing import Optional + +import requests + +CHAIN_WALLET_URL = "https://bot-api.ave.ai" +PROXY_WALLET_URL = "https://bot-api.ave.ai" +API_KEY = os.getenv("AVE_API_KEY", "") +API_PLAN = os.getenv("API_PLAN", "free") +SECRET_KEY = os.getenv("AVE_SECRET_KEY", "") +EVM_PRIVATE_KEY = os.getenv("AVE_EVM_PRIVATE_KEY", "") +SOLANA_PRIVATE_KEY = os.getenv("AVE_SOLANA_PRIVATE_KEY", "") + + +@dataclass +class TradeResponse: + status: int + data: Optional[dict] = None + error: Optional[str] = None + business_code: Optional[int] = None + + +def make_headers(signed: bool = False): + headers = { + "X-API-KEY": API_KEY, + "Content-Type": "application/json", + } + return headers + + +def make_proxy_headers(timestamp: str, signature: str): + return { + "X-API-KEY": API_KEY, + "X-Signature": signature, + "X-Timestamp": timestamp, + "Content-Type": "application/json", + } + + +def generate_hmac_signature(timestamp: str, body: str = "") -> str: + message = timestamp + body + signature = hmac.new( + SECRET_KEY.encode(), message.encode(), hashlib.sha256 + ).hexdigest() + return signature + + +def handle_response(response: requests.Response) -> TradeResponse: + if response.status_code == 200: + data = response.json() + if data.get("code") == 200 or data.get("status") == 200: + return TradeResponse(status=200, data=data) + else: + return TradeResponse( + status=data.get("code", response.status_code), + data=data, + error=data.get("msg", "Unknown error"), + business_code=data.get("code"), + ) + elif response.status_code == 401: + return TradeResponse( + status=401, error="Unauthorized - invalid or missing API key" + ) + elif response.status_code == 403: + return TradeResponse( + status=403, + error="Forbidden - API key expired, plan limits, or insufficient permissions", + ) + elif response.status_code == 429: + return TradeResponse(status=429, error="Rate limited - TPS exceeded") + else: + try: + data = response.json() + return TradeResponse( + status=response.status_code, + data=data, + error=data.get("msg", response.text), + business_code=data.get("code"), + ) + except json.JSONDecodeError: + return TradeResponse(status=response.status_code, error=response.text) + + +def chain_quote( + chain: str, + in_token: str, + out_token: str, + in_amount: str, + swap_type: str, + slippage: float = 0.5, +) -> TradeResponse: + endpoint = f"{CHAIN_WALLET_URL}/v1/chain/quote" + data = { + "chain": chain, + "in_token": in_token, + "out_token": out_token, + "in_amount": in_amount, + "swap_type": swap_type, + "slippage": slippage, + } + response = requests.post(endpoint, headers=make_headers(), json=data) + return handle_response(response) + + +def chain_swap( + chain: str, + in_token: str, + out_token: str, + in_amount: str, + swap_type: str, + slippage: float = 0.5, + recipient: Optional[str] = None, +) -> TradeResponse: + endpoint = f"{CHAIN_WALLET_URL}/v1/chain/swap" + data = { + "chain": chain, + "in_token": in_token, + "out_token": out_token, + "in_amount": in_amount, + "swap_type": swap_type, + "slippage": slippage, + } + if recipient: + data["recipient"] = recipient + response = requests.post(endpoint, headers=make_headers(), json=data) + return handle_response(response) + + +def proxy_quote( + chain: str, + proxy_wallet: str, + in_token: str, + out_token: str, + in_amount: str, + swap_type: str, +) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + body = json.dumps( + { + "chain": chain, + "proxy_wallet": proxy_wallet, + "in_token": in_token, + "out_token": out_token, + "in_amount": in_amount, + "swap_type": swap_type, + }, + separators=(",", ":"), + ) + signature = generate_hmac_signature(timestamp, body) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/quote" + response = requests.post(endpoint, headers=headers, json=json.loads(body)) + return handle_response(response) + + +def proxy_market_order( + chain: str, + proxy_wallet: str, + in_token: str, + out_token: str, + in_amount: str, + swap_type: str, + tp_price: Optional[str] = None, + sl_price: Optional[str] = None, + trailing_stop: Optional[dict] = None, +) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + data = { + "chain": chain, + "proxy_wallet": proxy_wallet, + "in_token": in_token, + "out_token": out_token, + "in_amount": in_amount, + "swap_type": swap_type, + "order_type": "market", + } + if tp_price: + data["tp_price"] = tp_price + if sl_price: + data["sl_price"] = sl_price + if trailing_stop: + data["trailing_stop"] = trailing_stop + body = json.dumps(data, separators=(",", ":")) + signature = generate_hmac_signature(timestamp, body) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/market_order" + response = requests.post(endpoint, headers=headers, json=data) + return handle_response(response) + + +def proxy_limit_order( + chain: str, + proxy_wallet: str, + in_token: str, + out_token: str, + in_amount: str, + swap_type: str, + price: str, + tp_price: Optional[str] = None, + sl_price: Optional[str] = None, +) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + data = { + "chain": chain, + "proxy_wallet": proxy_wallet, + "in_token": in_token, + "out_token": out_token, + "in_amount": in_amount, + "swap_type": swap_type, + "price": price, + "order_type": "limit", + } + if tp_price: + data["tp_price"] = tp_price + if sl_price: + data["sl_price"] = sl_price + body = json.dumps(data, separators=(",", ":")) + signature = generate_hmac_signature(timestamp, body) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/limit_order" + response = requests.post(endpoint, headers=headers, json=data) + return handle_response(response) + + +def proxy_cancel_order(chain: str, order_id: str) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + data = {"chain": chain, "order_id": order_id} + body = json.dumps(data, separators=(",", ":")) + signature = generate_hmac_signature(timestamp, body) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/cancel_order" + response = requests.post(endpoint, headers=headers, json=data) + return handle_response(response) + + +def proxy_get_orders( + chain: str, proxy_wallet: str, order_type: Optional[str] = None +) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + params = {"chain": chain, "proxy_wallet": proxy_wallet} + if order_type: + params["order_type"] = order_type + query_string = "&".join([f"{k}={v}" for k, v in sorted(params.items())]) + signature = generate_hmac_signature(timestamp, query_string) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/orders" + response = requests.get(endpoint, headers=headers, params=params) + return handle_response(response) + + +def proxy_get_order_history(chain: str, proxy_wallet: str) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + params = {"chain": chain, "proxy_wallet": proxy_wallet} + query_string = "&".join([f"{k}={v}" for k, v in sorted(params.items())]) + signature = generate_hmac_signature(timestamp, query_string) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/order_history" + response = requests.get(endpoint, headers=headers, params=params) + return handle_response(response) + + +def create_proxy_wallet(chain: str) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + data = {"chain": chain} + body = json.dumps(data, separators=(",", ":")) + signature = generate_hmac_signature(timestamp, body) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/create_proxy_wallet" + response = requests.post(endpoint, headers=headers, json=data) + return handle_response(response) + + +def list_proxy_wallets(chain: str) -> TradeResponse: + timestamp = str(int(time.time() * 1000)) + params = {"chain": chain} + query_string = "&".join([f"{k}={v}" for k, v in sorted(params.items())]) + signature = generate_hmac_signature(timestamp, query_string) + headers = make_proxy_headers(timestamp, signature) + endpoint = f"{PROXY_WALLET_URL}/v1/proxy_wallets" + response = requests.get(endpoint, headers=headers, params=params) + return handle_response(response) + + +def format_json(data): + return json.dumps(data, indent=2, ensure_ascii=False) + + +def print_trade_response(result: TradeResponse, success_msg: str = "Success"): + if result.error: + print(f"Error ({result.business_code or result.status}): {result.error}") + if result.data: + print(format_json(result.data)) + sys.exit(1) + else: + print(f"{success_msg}") + if result.data: + print(format_json(result.data)) + + +def cmd_chain_quote(args): + result = chain_quote( + chain=args.chain, + in_token=args.in_token, + out_token=args.out_token, + in_amount=args.in_amount, + swap_type=args.swap_type, + slippage=args.slippage, + ) + print_trade_response(result, "Quote retrieved (dry-run - no actual trade)") + + +def cmd_chain_swap(args): + result = chain_swap( + chain=args.chain, + in_token=args.in_token, + out_token=args.out_token, + in_amount=args.in_amount, + swap_type=args.swap_type, + slippage=args.slippage, + recipient=args.recipient, + ) + print_trade_response(result, "Swap executed!") + + +def cmd_proxy_quote(args): + result = proxy_quote( + chain=args.chain, + proxy_wallet=args.proxy_wallet, + in_token=args.in_token, + out_token=args.out_token, + in_amount=args.in_amount, + swap_type=args.swap_type, + ) + print_trade_response(result, "Quote retrieved (dry-run - no actual trade)") + + +def cmd_proxy_market(args): + result = proxy_market_order( + chain=args.chain, + proxy_wallet=args.proxy_wallet, + in_token=args.in_token, + out_token=args.out_token, + in_amount=args.in_amount, + swap_type=args.swap_type, + tp_price=args.tp_price, + sl_price=args.sl_price, + ) + print_trade_response(result, "Market order submitted!") + + +def cmd_proxy_limit(args): + result = proxy_limit_order( + chain=args.chain, + proxy_wallet=args.proxy_wallet, + in_token=args.in_token, + out_token=args.out_token, + in_amount=args.in_amount, + swap_type=args.swap_type, + price=args.price, + tp_price=args.tp_price, + sl_price=args.sl_price, + ) + print_trade_response(result, "Limit order submitted!") + + +def cmd_proxy_cancel(args): + result = proxy_cancel_order(args.chain, args.order_id) + print_trade_response(result, "Order cancelled!") + + +def cmd_proxy_orders(args): + result = proxy_get_orders(args.chain, args.proxy_wallet, args.order_type) + print_trade_response(result) + + +def cmd_proxy_history(args): + result = proxy_get_order_history(args.chain, args.proxy_wallet) + print_trade_response(result) + + +def cmd_create_proxy_wallet(args): + result = create_proxy_wallet(args.chain) + print_trade_response(result, "Proxy wallet created!") + + +def cmd_list_proxy_wallets(args): + result = list_proxy_wallets(args.chain) + print_trade_response(result) + + +def main(): + parser = argparse.ArgumentParser(description="AVE Cloud Trading 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, help="API plan (or set API_PLAN env)" + ) + parser.add_argument( + "--secret-key", + default=SECRET_KEY, + help="AVE SECRET_KEY for HMAC signing (or set AVE_SECRET_KEY env)", + ) + subparsers = parser.add_subparsers(dest="command", help="Commands") + + p_cquote = subparsers.add_parser( + "chain-quote", help="Chain wallet - get swap quote (dry-run)" + ) + p_cquote.add_argument( + "--chain", required=True, help="Chain name (bsc, solana, eth, base)" + ) + p_cquote.add_argument("--in-token", required=True, help="Input token address") + p_cquote.add_argument("--out-token", required=True, help="Output token address") + p_cquote.add_argument( + "--in-amount", required=True, help="Input amount (use string format)" + ) + p_cquote.add_argument("--swap-type", required=True, help="Swap type (buy or sell)") + p_cquote.add_argument( + "--slippage", type=float, default=0.5, help="Slippage tolerance (default: 0.5)" + ) + + p_cswap = subparsers.add_parser("chain-swap", help="Chain wallet - execute swap") + p_cswap.add_argument("--chain", required=True, help="Chain name") + p_cswap.add_argument("--in-token", required=True, help="Input token address") + p_cswap.add_argument("--out-token", required=True, help="Output token address") + p_cswap.add_argument("--in-amount", required=True, help="Input amount") + p_cswap.add_argument("--swap-type", required=True, help="Swap type (buy or sell)") + p_cswap.add_argument( + "--slippage", type=float, default=0.5, help="Slippage tolerance" + ) + p_cswap.add_argument("--recipient", help="Optional recipient address") + + p_pquote = subparsers.add_parser( + "proxy-quote", help="Proxy wallet - get swap quote (dry-run)" + ) + p_pquote.add_argument("--chain", required=True, help="Chain name") + p_pquote.add_argument("--proxy-wallet", required=True, help="Proxy wallet address") + p_pquote.add_argument("--in-token", required=True, help="Input token address") + p_pquote.add_argument("--out-token", required=True, help="Output token address") + p_pquote.add_argument("--in-amount", required=True, help="Input amount") + p_pquote.add_argument("--swap-type", required=True, help="Swap type (buy or sell)") + + p_pmarket = subparsers.add_parser( + "proxy-market", help="Proxy wallet - submit market order" + ) + p_pmarket.add_argument("--chain", required=True, help="Chain name") + p_pmarket.add_argument("--proxy-wallet", required=True, help="Proxy wallet address") + p_pmarket.add_argument("--in-token", required=True, help="Input token address") + p_pmarket.add_argument("--out-token", required=True, help="Output token address") + p_pmarket.add_argument("--in-amount", required=True, help="Input amount") + p_pmarket.add_argument("--swap-type", required=True, help="Swap type (buy or sell)") + p_pmarket.add_argument("--tp-price", help="Take-profit price") + p_pmarket.add_argument("--sl-price", help="Stop-loss price") + + p_plimit = subparsers.add_parser( + "proxy-limit", help="Proxy wallet - submit limit order" + ) + p_plimit.add_argument("--chain", required=True, help="Chain name") + p_plimit.add_argument("--proxy-wallet", required=True, help="Proxy wallet address") + p_plimit.add_argument("--in-token", required=True, help="Input token address") + p_plimit.add_argument("--out-token", required=True, help="Output token address") + p_plimit.add_argument("--in-amount", required=True, help="Input amount") + p_plimit.add_argument("--swap-type", required=True, help="Swap type (buy or sell)") + p_plimit.add_argument("--price", required=True, help="Limit price") + p_plimit.add_argument("--tp-price", help="Take-profit price") + p_plimit.add_argument("--sl-price", help="Stop-loss price") + + p_cancel = subparsers.add_parser("proxy-cancel", help="Proxy wallet - cancel order") + p_cancel.add_argument("--chain", required=True, help="Chain name") + p_cancel.add_argument("--order-id", required=True, help="Order ID to cancel") + + p_orders = subparsers.add_parser( + "proxy-orders", help="Proxy wallet - get open orders" + ) + p_orders.add_argument("--chain", required=True, help="Chain name") + p_orders.add_argument("--proxy-wallet", required=True, help="Proxy wallet address") + p_orders.add_argument("--order-type", help="Filter by order type (market/limit)") + + p_history = subparsers.add_parser( + "proxy-history", help="Proxy wallet - get order history" + ) + p_history.add_argument("--chain", required=True, help="Chain name") + p_history.add_argument("--proxy-wallet", required=True, help="Proxy wallet address") + + p_create = subparsers.add_parser( + "create-proxy-wallet", help="Create a new proxy wallet" + ) + p_create.add_argument("--chain", required=True, help="Chain name") + + p_list = subparsers.add_parser("list-proxy-wallets", help="List proxy wallets") + p_list.add_argument("--chain", required=True, help="Chain name") + + 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 = { + "chain-quote": cmd_chain_quote, + "chain-swap": cmd_chain_swap, + "proxy-quote": cmd_proxy_quote, + "proxy-market": cmd_proxy_market, + "proxy-limit": cmd_proxy_limit, + "proxy-cancel": cmd_proxy_cancel, + "proxy-orders": cmd_proxy_orders, + "proxy-history": cmd_proxy_history, + "create-proxy-wallet": cmd_create_proxy_wallet, + "list-proxy-wallets": cmd_list_proxy_wallets, + } + + cmd_map[args.command](args) + + +if __name__ == "__main__": + main() diff --git a/docs/scripts/requirements.txt b/docs/scripts/requirements.txt new file mode 100644 index 0000000..e170798 --- /dev/null +++ b/docs/scripts/requirements.txt @@ -0,0 +1,2 @@ +requests>=2.31.0 +websocket-client>=1.7.0