$npx -y skills add agiprolabs/claude-trading-skills --skill solana-rpcDirect Solana blockchain interaction via JSON-RPC — account lookups, token balances, transaction submission, and program queries
| 1 | # Solana RPC — Direct Blockchain Interaction |
| 2 | |
| 3 | The Solana JSON-RPC API provides direct read/write access to the blockchain. Use it for account state queries, token balance lookups, transaction building and submission, and program account enumeration. This is the low-level foundation when higher-level APIs (Birdeye, Helius, SolanaTracker) don't have the data you need. |
| 4 | |
| 5 | ## Quick Start |
| 6 | |
| 7 | ```python |
| 8 | import httpx |
| 9 | |
| 10 | RPC = os.getenv("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com") |
| 11 | |
| 12 | def rpc_call(method: str, params: list = None) -> dict: |
| 13 | resp = httpx.post(RPC, json={ |
| 14 | "jsonrpc": "2.0", "id": 1, |
| 15 | "method": method, "params": params or [], |
| 16 | }, timeout=30.0) |
| 17 | return resp.json() |
| 18 | |
| 19 | # Get SOL balance |
| 20 | result = rpc_call("getBalance", ["WALLET_PUBKEY"]) |
| 21 | sol_balance = result["result"]["value"] / 1e9 |
| 22 | |
| 23 | # Get latest blockhash |
| 24 | result = rpc_call("getLatestBlockhash") |
| 25 | blockhash = result["result"]["value"]["blockhash"] |
| 26 | ``` |
| 27 | |
| 28 | ## RPC Providers |
| 29 | |
| 30 | | Provider | Free Tier | Paid | Notes | |
| 31 | |----------|-----------|------|-------| |
| 32 | | Helius | 50K credits/day | $49+/mo | Enhanced RPCs, DAS API | |
| 33 | | QuickNode | Limited | $49+/mo | Multi-chain, WebSocket | |
| 34 | | Triton | No free tier | ~$300+/mo | Yellowstone gRPC bundled | |
| 35 | | Shyft | Limited | $49+/mo | Yellowstone gRPC bundled | |
| 36 | | Alchemy | 300M CU/mo | Scaling | Good free tier | |
| 37 | | Public (mainnet-beta) | Free | — | Rate limited, unreliable | |
| 38 | |
| 39 | **Recommendation**: Use Helius or QuickNode for development. Never use public RPC for production trading. |
| 40 | |
| 41 | ## Core Read Methods |
| 42 | |
| 43 | ### Account & Balance |
| 44 | |
| 45 | ```python |
| 46 | # SOL balance (in lamports, divide by 1e9 for SOL) |
| 47 | getBalance(pubkey, {commitment: "confirmed"}) |
| 48 | |
| 49 | # Full account info (data, owner, lamports, executable) |
| 50 | getAccountInfo(pubkey, {encoding: "jsonParsed"}) |
| 51 | |
| 52 | # Multiple accounts in one call |
| 53 | getMultipleAccounts([pubkey1, pubkey2], {encoding: "jsonParsed"}) |
| 54 | ``` |
| 55 | |
| 56 | ### Token Accounts |
| 57 | |
| 58 | ```python |
| 59 | # All SPL token accounts owned by a wallet |
| 60 | getTokenAccountsByOwner(wallet_pubkey, { |
| 61 | "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" |
| 62 | }, {"encoding": "jsonParsed"}) |
| 63 | |
| 64 | # Token balance for a specific token account |
| 65 | getTokenAccountBalance(token_account_pubkey) |
| 66 | |
| 67 | # Largest token accounts (top holders) |
| 68 | getTokenLargestAccounts(mint_pubkey) |
| 69 | |
| 70 | # Total supply of a token |
| 71 | getTokenSupply(mint_pubkey) |
| 72 | ``` |
| 73 | |
| 74 | ### Transaction Data |
| 75 | |
| 76 | ```python |
| 77 | # Get parsed transaction by signature |
| 78 | getTransaction(signature, { |
| 79 | "encoding": "jsonParsed", |
| 80 | "maxSupportedTransactionVersion": 0, |
| 81 | }) |
| 82 | |
| 83 | # Recent transaction signatures for an address |
| 84 | getSignaturesForAddress(pubkey, { |
| 85 | "limit": 20, |
| 86 | "before": "optional_signature", # pagination cursor |
| 87 | }) |
| 88 | |
| 89 | # Transaction status |
| 90 | getSignatureStatuses([sig1, sig2]) |
| 91 | ``` |
| 92 | |
| 93 | ### Block & Slot |
| 94 | |
| 95 | ```python |
| 96 | # Current slot |
| 97 | getSlot({commitment: "confirmed"}) |
| 98 | |
| 99 | # Block data |
| 100 | getBlock(slot, { |
| 101 | "encoding": "jsonParsed", |
| 102 | "transactionDetails": "full", |
| 103 | "maxSupportedTransactionVersion": 0, |
| 104 | }) |
| 105 | |
| 106 | # Latest blockhash (needed for tx building) |
| 107 | getLatestBlockhash({commitment: "confirmed"}) |
| 108 | |
| 109 | # Slot leader schedule |
| 110 | getLeaderSchedule() |
| 111 | ``` |
| 112 | |
| 113 | ### Program Accounts |
| 114 | |
| 115 | ```python |
| 116 | # All accounts owned by a program (with filters) |
| 117 | getProgramAccounts(program_pubkey, { |
| 118 | "encoding": "jsonParsed", |
| 119 | "filters": [ |
| 120 | {"dataSize": 165}, # Filter by account data size |
| 121 | {"memcmp": { # Filter by data content |
| 122 | "offset": 32, |
| 123 | "bytes": "base58_encoded_value", |
| 124 | }}, |
| 125 | ], |
| 126 | }) |
| 127 | ``` |
| 128 | |
| 129 | **Warning**: `getProgramAccounts` without filters can return millions of results and timeout. Always use `dataSize` and/or `memcmp` filters. |
| 130 | |
| 131 | ### Priority Fees |
| 132 | |
| 133 | ```python |
| 134 | # Recent priority fee estimates |
| 135 | getRecentPrioritizationFees([account_pubkey]) |
| 136 | # Returns array of { slot, prioritizationFee } for recent slots |
| 137 | |
| 138 | # Minimum rent for account |
| 139 | getMinimumBalanceForRentExemption(data_length) |
| 140 | ``` |
| 141 | |
| 142 | ## Write Methods |
| 143 | |
| 144 | ### Send Transaction |
| 145 | |
| 146 | ```python |
| 147 | # Send a signed, serialized transaction |
| 148 | sendTransaction(base64_tx, { |
| 149 | "encoding": "base64", |
| 150 | "skipPreflight": False, |
| 151 | "preflightCommitment": "confirmed", |
| 152 | "maxRetries": 3, |
| 153 | }) |
| 154 | |
| 155 | # Simulate before sending |
| 156 | simulateTransaction(base64_tx, { |
| 157 | "encoding": "base64", |
| 158 | "sigVerify": False, |
| 159 | "commitment": "confirmed", |
| 160 | }) |
| 161 | ``` |
| 162 | |
| 163 | ### Transaction Confirmation |
| 164 | |
| 165 | ```python |
| 166 | import time |
| 167 | |
| 168 | def confirm_transaction(rpc_url: str, signature: str, timeout: float = 30.0) -> bool: |
| 169 | """Poll for transaction confirmation.""" |
| 170 | start = time.time() |
| 171 | while time.time() - start < timeout: |
| 172 | result = rpc_call("getSignatureStatuses", [[signature]]) |
| 173 | statuses = result.get("result", {}).get("value", [None]) |
| 174 | if statuses[0] is not None: |
| 175 | status = statuses[0] |
| 176 | if status.get("err"): |
| 177 | return False |
| 178 | if status.get("confirmationStatus") in ("confirmed", "finalized"): |
| 179 | return True |