repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | # Solana-Specific Patterns |
| #2 | |
| #3 | Production patterns for Solana development with Claude Code. |
| #4 | |
| #5 | --- |
| #6 | |
| #7 | ## Retry Wrapper |
| #8 | |
| #9 | Every RPC call should go through a retry wrapper: |
| #10 | |
| #11 | ```typescript |
| #12 | interface RetryOptions { |
| #13 | maxRetries: number; |
| #14 | backoffMs: number; |
| #15 | maxBackoffMs?: number; |
| #16 | retryOn?: (error: unknown) => boolean; |
| #17 | } |
| #18 | |
| #19 | async function withRetry<T>( |
| #20 | fn: () => Promise<T>, |
| #21 | options: RetryOptions |
| #22 | ): Promise<T> { |
| #23 | const { maxRetries, backoffMs, maxBackoffMs = 10_000 } = options; |
| #24 | |
| #25 | for (let attempt = 0; attempt <= maxRetries; attempt++) { |
| #26 | try { |
| #27 | return await fn(); |
| #28 | } catch (error) { |
| #29 | if (attempt === maxRetries) throw error; |
| #30 | if (options.retryOn && !options.retryOn(error)) throw error; |
| #31 | |
| #32 | const delay = Math.min( |
| #33 | backoffMs * Math.pow(2, attempt), |
| #34 | maxBackoffMs |
| #35 | ); |
| #36 | await new Promise(r => setTimeout(r, delay)); |
| #37 | } |
| #38 | } |
| #39 | |
| #40 | throw new Error('Unreachable'); |
| #41 | } |
| #42 | ``` |
| #43 | |
| #44 | --- |
| #45 | |
| #46 | ## Transaction Builder Pattern |
| #47 | |
| #48 | Simulate, set CU, set priority fee, then send: |
| #49 | |
| #50 | ```typescript |
| #51 | async function buildAndSend( |
| #52 | connection: Connection, |
| #53 | instructions: TransactionInstruction[], |
| #54 | signer: Keypair, |
| #55 | options: { useJito?: boolean; urgency?: 'low' | 'medium' | 'high' } |
| #56 | ): Promise<string> { |
| #57 | // 1. Build transaction |
| #58 | const { blockhash } = await withRetry( |
| #59 | () => connection.getLatestBlockhash('confirmed'), |
| #60 | { maxRetries: 3, backoffMs: 200 } |
| #61 | ); |
| #62 | |
| #63 | const tx = new VersionedTransaction( |
| #64 | new TransactionMessage({ |
| #65 | payerKey: signer.publicKey, |
| #66 | recentBlockhash: blockhash, |
| #67 | instructions, |
| #68 | }).compileToV0Message() |
| #69 | ); |
| #70 | |
| #71 | // 2. Simulate |
| #72 | const sim = await connection.simulateTransaction(tx); |
| #73 | if (sim.value.err) { |
| #74 | throw new SimulationError(sim.value.err, sim.value.logs); |
| #75 | } |
| #76 | |
| #77 | // 3. Set compute units based on simulation |
| #78 | const cuUsed = sim.value.unitsConsumed || 200_000; |
| #79 | const cuLimit = Math.ceil(cuUsed * 1.2); |
| #80 | |
| #81 | // 4. Get priority fee |
| #82 | const priorityFee = await getPriorityFee(options.urgency || 'medium'); |
| #83 | |
| #84 | // 5. Rebuild with CU + priority fee instructions prepended |
| #85 | const finalInstructions = [ |
| #86 | ComputeBudgetProgram.setComputeUnitLimit({ units: cuLimit }), |
| #87 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }), |
| #88 | ...instructions, |
| #89 | ]; |
| #90 | |
| #91 | // 6. Send via Jito or standard |
| #92 | if (options.useJito) { |
| #93 | return await sendViaJito(finalInstructions, signer, blockhash); |
| #94 | } |
| #95 | |
| #96 | return await sendWithConfirmation(finalInstructions, signer, blockhash); |
| #97 | } |
| #98 | ``` |
| #99 | |
| #100 | --- |
| #101 | |
| #102 | ## Confirmation Polling |
| #103 | |
| #104 | Never block indefinitely. Poll with timeout: |
| #105 | |
| #106 | ```typescript |
| #107 | async function confirmTransaction( |
| #108 | connection: Connection, |
| #109 | signature: string, |
| #110 | options: { timeoutMs?: number; commitment?: Commitment } |
| #111 | ): Promise<void> { |
| #112 | const { timeoutMs = 30_000, commitment = 'confirmed' } = options; |
| #113 | const start = Date.now(); |
| #114 | |
| #115 | while (Date.now() - start < timeoutMs) { |
| #116 | const status = await withRetry( |
| #117 | () => connection.getSignatureStatus(signature), |
| #118 | { maxRetries: 2, backoffMs: 100 } |
| #119 | ); |
| #120 | |
| #121 | if (status.value?.confirmationStatus === commitment) return; |
| #122 | if (status.value?.err) { |
| #123 | throw new TransactionError(signature, status.value.err); |
| #124 | } |
| #125 | |
| #126 | await new Promise(r => setTimeout(r, 500)); |
| #127 | } |
| #128 | |
| #129 | throw new TimeoutError(signature, timeoutMs); |
| #130 | } |
| #131 | ``` |
| #132 | |
| #133 | --- |
| #134 | |
| #135 | ## Graceful Shutdown |
| #136 | |
| #137 | Handle process termination cleanly: |
| #138 | |
| #139 | ```typescript |
| #140 | class GracefulShutdown { |
| #141 | private shutdownCallbacks: (() => Promise<void>)[] = []; |
| #142 | private isShuttingDown = false; |
| #143 | |
| #144 | constructor() { |
| #145 | process.on('SIGTERM', () => this.shutdown('SIGTERM')); |
| #146 | process.on('SIGINT', () => this.shutdown('SIGINT')); |
| #147 | } |
| #148 | |
| #149 | onShutdown(callback: () => Promise<void>) { |
| #150 | this.shutdownCallbacks.push(callback); |
| #151 | } |
| #152 | |
| #153 | private async shutdown(signal: string) { |
| #154 | if (this.isShuttingDown) return; |
| #155 | this.isShuttingDown = true; |
| #156 | |
| #157 | console.log(`[shutdown] Received ${signal}, cleaning up...`); |
| #158 | |
| #159 | for (const callback of this.shutdownCallbacks) { |
| #160 | try { |
| #161 | await callback(); |
| #162 | } catch (error) { |
| #163 | console.error('[shutdown] Cleanup error:', error); |
| #164 | } |
| #165 | } |
| #166 | |
| #167 | console.log('[shutdown] Complete.'); |
| #168 | process.exit(0); |
| #169 | } |
| #170 | } |
| #171 | ``` |
| #172 | |
| #173 | --- |
| #174 | |
| #175 | ## Structured Error Types |
| #176 | |
| #177 | No generic throws. Every error has context: |
| #178 | |
| #179 | ```typescript |
| #180 | class SolanaError extends Error { |
| #181 | constructor( |
| #182 | message: string, |
| #183 | public readonly code: string, |
| #184 | public readonly context: Record<string, unknown> |
| #185 | ) { |
| #186 | super(message); |
| #187 | this.name = 'SolanaError'; |
| #188 | } |
| #189 | } |
| #190 | |
| #191 | class SimulationError extends SolanaError { |
| #192 | constructor(err: unknown, logs: string[] | null) { |
| #193 | super('Transaction simulation failed', 'SIMULATION_FAILED', { err, logs }); |
| #194 | } |
| #195 | } |
| #196 | |
| #197 | class TransactionError extends SolanaError { |
| #198 | constructor(signature: string, err: unknown) { |
| #199 | super('Transaction failed', 'TX_FAILED', { signature, err }); |
| #200 | } |
| #201 | } |
| #202 | |
| #203 | class TimeoutError extends SolanaError { |
| #204 | constructor(signature: string, timeoutMs: number) { |
| #205 | super('Transaction confirmation timed out', 'TX_TIMEOUT', { signature, timeoutMs }); |
| #206 | } |
| #207 | } |
| #208 | ``` |
| #209 | |
| #210 | --- |
| #211 | |
| #212 | ## Websocket Reconnection |
| #213 | |
| #214 | Resilient websocket connections for real-time monitoring: |
| #215 | |
| #216 | ```typescript |
| #217 | function createResilientSubscription( |
| #218 | endpoint: string, |
| #219 | onMessage: (data: unknown) => void, |
| #220 | options: { maxReconnects?: number; reconnectDelayMs?: number } = {} |
| #221 | ) { |
| #222 | const { maxReconnects = 10, reconnectDelayMs = 1000 } = options; |
| #223 | let reconnects = 0; |
| #224 | let ws: WebSocket; |
| #225 | |
| #226 | function connect() { |
| #227 | ws = new WebSocket(endpoint); |
| #228 | |
| #229 | ws.onopen = () => { |
| #230 | reconnects = 0; // Reset on successful connection |
| #231 | }; |
| #232 | |
| #233 | ws.onmessage = (event) => { |
| #234 | onMessage(JSON.parse(event.data)); |
| #235 | }; |
| #236 | |
| #237 | ws.onclose = () => { |
| #238 | if (reconnects < maxReconnects) { |
| #239 | reconnects++; |
| #240 | const delay = reconnectDelayMs * Math.pow(2, reconnects - 1); |
| #241 | setTimeout(connect, delay); |
| #242 | } |
| #243 | }; |
| #244 | |
| #245 | ws.onerror = () => ws.close(); |
| #246 | } |
| #247 | |
| #248 | connect(); |
| #249 | |
| #250 | return { |
| #251 | close: () => ws?.close(), |
| #252 | }; |
| #253 | } |
| #254 | ``` |
| #255 | |
| #256 | --- |
| #257 | |
| #258 | *These patterns form the foundation of production Solana development. The skill enforces them automatically when active.* |
| #259 |