@sip-protocol/sdk 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  4. package/dist/browser.d.mts +10 -4
  5. package/dist/browser.d.ts +10 -4
  6. package/dist/browser.js +48874 -18336
  7. package/dist/browser.mjs +674 -48
  8. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  9. package/dist/chunk-5D7A3L3W.mjs +717 -0
  10. package/dist/chunk-64AYA5F5.mjs +7834 -0
  11. package/dist/chunk-GMDGB22A.mjs +379 -0
  12. package/dist/chunk-I534WKN7.mjs +328 -0
  13. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  14. package/dist/chunk-PRRZAWJE.mjs +223 -0
  15. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  16. package/dist/chunk-YWGJ77A2.mjs +33806 -0
  17. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  18. package/dist/constants-LHAAUC2T.mjs +51 -0
  19. package/dist/dist-2OGQ7FED.mjs +3957 -0
  20. package/dist/dist-IFHPYLDX.mjs +254 -0
  21. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  22. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  23. package/dist/index-DXh2IGkz.d.ts +24681 -0
  24. package/dist/index-DeE1ZzA4.d.mts +24681 -0
  25. package/dist/index.d.mts +9 -3
  26. package/dist/index.d.ts +9 -3
  27. package/dist/index.js +48676 -17318
  28. package/dist/index.mjs +583 -19
  29. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  30. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  31. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  32. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  33. package/dist/proofs/halo2.d.mts +151 -0
  34. package/dist/proofs/halo2.d.ts +151 -0
  35. package/dist/proofs/halo2.js +350 -0
  36. package/dist/proofs/halo2.mjs +11 -0
  37. package/dist/proofs/kimchi.d.mts +160 -0
  38. package/dist/proofs/kimchi.d.ts +160 -0
  39. package/dist/proofs/kimchi.js +431 -0
  40. package/dist/proofs/kimchi.mjs +13 -0
  41. package/dist/proofs/noir.d.mts +1 -1
  42. package/dist/proofs/noir.d.ts +1 -1
  43. package/dist/proofs/noir.js +74 -18
  44. package/dist/proofs/noir.mjs +84 -24
  45. package/dist/solana-U3MEGU7W.mjs +280 -0
  46. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  47. package/package.json +54 -21
  48. package/src/adapters/index.ts +41 -0
  49. package/src/adapters/jupiter.ts +571 -0
  50. package/src/adapters/near-intents.ts +135 -0
  51. package/src/advisor/advisor.ts +653 -0
  52. package/src/advisor/index.ts +54 -0
  53. package/src/advisor/tools.ts +303 -0
  54. package/src/advisor/types.ts +164 -0
  55. package/src/chains/ethereum/announcement.ts +536 -0
  56. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  57. package/src/chains/ethereum/commitment.ts +522 -0
  58. package/src/chains/ethereum/constants.ts +462 -0
  59. package/src/chains/ethereum/deployment.ts +596 -0
  60. package/src/chains/ethereum/gas-estimation.ts +538 -0
  61. package/src/chains/ethereum/index.ts +268 -0
  62. package/src/chains/ethereum/optimizations.ts +614 -0
  63. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  64. package/src/chains/ethereum/registry.ts +584 -0
  65. package/src/chains/ethereum/rpc.ts +905 -0
  66. package/src/chains/ethereum/stealth.ts +491 -0
  67. package/src/chains/ethereum/token.ts +790 -0
  68. package/src/chains/ethereum/transfer.ts +637 -0
  69. package/src/chains/ethereum/types.ts +456 -0
  70. package/src/chains/ethereum/viewing-key.ts +455 -0
  71. package/src/chains/near/commitment.ts +608 -0
  72. package/src/chains/near/constants.ts +284 -0
  73. package/src/chains/near/function-call.ts +871 -0
  74. package/src/chains/near/history.ts +654 -0
  75. package/src/chains/near/implicit-account.ts +840 -0
  76. package/src/chains/near/index.ts +393 -0
  77. package/src/chains/near/native-transfer.ts +658 -0
  78. package/src/chains/near/nep141.ts +775 -0
  79. package/src/chains/near/privacy-adapter.ts +889 -0
  80. package/src/chains/near/resolver.ts +971 -0
  81. package/src/chains/near/rpc.ts +1016 -0
  82. package/src/chains/near/stealth.ts +419 -0
  83. package/src/chains/near/types.ts +317 -0
  84. package/src/chains/near/viewing-key.ts +876 -0
  85. package/src/chains/solana/anchor-transfer.ts +386 -0
  86. package/src/chains/solana/commitment.ts +577 -0
  87. package/src/chains/solana/constants.ts +126 -12
  88. package/src/chains/solana/ephemeral-keys.ts +543 -0
  89. package/src/chains/solana/index.ts +276 -1
  90. package/src/chains/solana/key-derivation.ts +418 -0
  91. package/src/chains/solana/kit-compat.ts +334 -0
  92. package/src/chains/solana/optimizations.ts +560 -0
  93. package/src/chains/solana/privacy-adapter.ts +605 -0
  94. package/src/chains/solana/providers/generic.ts +201 -0
  95. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  96. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  97. package/src/chains/solana/providers/helius.ts +402 -0
  98. package/src/chains/solana/providers/index.ts +85 -0
  99. package/src/chains/solana/providers/interface.ts +221 -0
  100. package/src/chains/solana/providers/quicknode.ts +409 -0
  101. package/src/chains/solana/providers/triton.ts +426 -0
  102. package/src/chains/solana/providers/webhook.ts +790 -0
  103. package/src/chains/solana/rpc-client.ts +1150 -0
  104. package/src/chains/solana/scan.ts +170 -73
  105. package/src/chains/solana/sol-transfer.ts +732 -0
  106. package/src/chains/solana/spl-transfer.ts +886 -0
  107. package/src/chains/solana/stealth-scanner.ts +703 -0
  108. package/src/chains/solana/sunspot-verifier.ts +453 -0
  109. package/src/chains/solana/transaction-builder.ts +755 -0
  110. package/src/chains/solana/transfer.ts +74 -5
  111. package/src/chains/solana/types.ts +77 -7
  112. package/src/chains/solana/utils.ts +110 -0
  113. package/src/chains/solana/viewing-key.ts +807 -0
  114. package/src/compliance/fireblocks.ts +921 -0
  115. package/src/compliance/index.ts +37 -0
  116. package/src/compliance/range-sas.ts +956 -0
  117. package/src/config/endpoints.ts +100 -0
  118. package/src/crypto.ts +11 -8
  119. package/src/errors.ts +82 -0
  120. package/src/evm/erc4337-relayer.ts +830 -0
  121. package/src/evm/index.ts +47 -0
  122. package/src/fees/calculator.ts +396 -0
  123. package/src/fees/index.ts +87 -0
  124. package/src/fees/near-contract.ts +429 -0
  125. package/src/fees/types.ts +268 -0
  126. package/src/index.ts +785 -1
  127. package/src/intent.ts +6 -3
  128. package/src/logger.ts +324 -0
  129. package/src/network/index.ts +80 -0
  130. package/src/network/proxy.ts +691 -0
  131. package/src/optimizations/index.ts +541 -0
  132. package/src/oracle/types.ts +1 -0
  133. package/src/privacy-backends/arcium-types.ts +727 -0
  134. package/src/privacy-backends/arcium.ts +719 -0
  135. package/src/privacy-backends/combined-privacy.ts +866 -0
  136. package/src/privacy-backends/cspl-token.ts +595 -0
  137. package/src/privacy-backends/cspl-types.ts +512 -0
  138. package/src/privacy-backends/cspl.ts +907 -0
  139. package/src/privacy-backends/health.ts +488 -0
  140. package/src/privacy-backends/inco-types.ts +323 -0
  141. package/src/privacy-backends/inco.ts +616 -0
  142. package/src/privacy-backends/index.ts +336 -0
  143. package/src/privacy-backends/interface.ts +906 -0
  144. package/src/privacy-backends/lru-cache.ts +343 -0
  145. package/src/privacy-backends/magicblock.ts +458 -0
  146. package/src/privacy-backends/mock.ts +258 -0
  147. package/src/privacy-backends/privacycash-types.ts +278 -0
  148. package/src/privacy-backends/privacycash.ts +456 -0
  149. package/src/privacy-backends/private-swap.ts +570 -0
  150. package/src/privacy-backends/rate-limiter.ts +683 -0
  151. package/src/privacy-backends/registry.ts +690 -0
  152. package/src/privacy-backends/router.ts +626 -0
  153. package/src/privacy-backends/shadowwire.ts +449 -0
  154. package/src/privacy-backends/sip-native.ts +256 -0
  155. package/src/privacy-logger.ts +191 -0
  156. package/src/production-safety.ts +373 -0
  157. package/src/proofs/aggregator.ts +1029 -0
  158. package/src/proofs/browser-composer.ts +1150 -0
  159. package/src/proofs/browser.ts +113 -25
  160. package/src/proofs/cache/index.ts +127 -0
  161. package/src/proofs/cache/interface.ts +545 -0
  162. package/src/proofs/cache/key-generator.ts +188 -0
  163. package/src/proofs/cache/lru-cache.ts +481 -0
  164. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  165. package/src/proofs/cache/persistent-cache.ts +788 -0
  166. package/src/proofs/compliance-proof.ts +872 -0
  167. package/src/proofs/composer/base.ts +923 -0
  168. package/src/proofs/composer/index.ts +25 -0
  169. package/src/proofs/composer/interface.ts +518 -0
  170. package/src/proofs/composer/types.ts +383 -0
  171. package/src/proofs/converters/halo2.ts +452 -0
  172. package/src/proofs/converters/index.ts +208 -0
  173. package/src/proofs/converters/interface.ts +363 -0
  174. package/src/proofs/converters/kimchi.ts +462 -0
  175. package/src/proofs/converters/noir.ts +451 -0
  176. package/src/proofs/fallback.ts +888 -0
  177. package/src/proofs/halo2.ts +42 -0
  178. package/src/proofs/index.ts +471 -0
  179. package/src/proofs/interface.ts +13 -0
  180. package/src/proofs/kimchi.ts +42 -0
  181. package/src/proofs/lazy.ts +1004 -0
  182. package/src/proofs/mock.ts +25 -1
  183. package/src/proofs/noir.ts +111 -30
  184. package/src/proofs/orchestrator.ts +960 -0
  185. package/src/proofs/parallel/concurrency.ts +297 -0
  186. package/src/proofs/parallel/dependency-graph.ts +602 -0
  187. package/src/proofs/parallel/executor.ts +420 -0
  188. package/src/proofs/parallel/index.ts +131 -0
  189. package/src/proofs/parallel/interface.ts +685 -0
  190. package/src/proofs/parallel/worker-pool.ts +644 -0
  191. package/src/proofs/providers/halo2.ts +560 -0
  192. package/src/proofs/providers/index.ts +34 -0
  193. package/src/proofs/providers/kimchi.ts +641 -0
  194. package/src/proofs/validator.ts +881 -0
  195. package/src/proofs/verifier.ts +867 -0
  196. package/src/quantum/index.ts +112 -0
  197. package/src/quantum/winternitz-vault.ts +639 -0
  198. package/src/quantum/wots.ts +611 -0
  199. package/src/settlement/backends/direct-chain.ts +1 -0
  200. package/src/settlement/index.ts +9 -0
  201. package/src/settlement/router.ts +732 -46
  202. package/src/solana/index.ts +72 -0
  203. package/src/solana/jito-relayer.ts +687 -0
  204. package/src/solana/noir-verifier-types.ts +430 -0
  205. package/src/solana/noir-verifier.ts +816 -0
  206. package/src/stealth/address-derivation.ts +193 -0
  207. package/src/stealth/ed25519.ts +431 -0
  208. package/src/stealth/index.ts +233 -0
  209. package/src/stealth/meta-address.ts +221 -0
  210. package/src/stealth/secp256k1.ts +368 -0
  211. package/src/stealth/utils.ts +194 -0
  212. package/src/stealth.ts +50 -1504
  213. package/src/surveillance/algorithms/address-reuse.ts +143 -0
  214. package/src/surveillance/algorithms/cluster.ts +247 -0
  215. package/src/surveillance/algorithms/exchange.ts +295 -0
  216. package/src/surveillance/algorithms/temporal.ts +337 -0
  217. package/src/surveillance/analyzer.ts +442 -0
  218. package/src/surveillance/index.ts +64 -0
  219. package/src/surveillance/scoring.ts +372 -0
  220. package/src/surveillance/types.ts +264 -0
  221. package/src/sync/index.ts +106 -0
  222. package/src/sync/manager.ts +504 -0
  223. package/src/sync/mock-provider.ts +318 -0
  224. package/src/sync/oblivious.ts +625 -0
  225. package/src/tokens/index.ts +15 -0
  226. package/src/tokens/registry.ts +301 -0
  227. package/src/utils/deprecation.ts +94 -0
  228. package/src/utils/index.ts +9 -0
  229. package/src/wallet/ethereum/index.ts +68 -0
  230. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  231. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  232. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  233. package/src/wallet/ethereum/types.ts +3 -1
  234. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  235. package/src/wallet/hardware/index.ts +10 -0
  236. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  237. package/src/wallet/index.ts +71 -0
  238. package/src/wallet/near/adapter.ts +626 -0
  239. package/src/wallet/near/index.ts +86 -0
  240. package/src/wallet/near/meteor-wallet.ts +1153 -0
  241. package/src/wallet/near/my-near-wallet.ts +790 -0
  242. package/src/wallet/near/wallet-selector.ts +702 -0
  243. package/src/wallet/solana/adapter.ts +6 -4
  244. package/src/wallet/solana/index.ts +13 -0
  245. package/src/wallet/solana/privacy-adapter.ts +567 -0
  246. package/src/wallet/sui/types.ts +6 -4
  247. package/src/zcash/rpc-client.ts +13 -6
  248. package/dist/chunk-3INS3PR5.mjs +0 -884
  249. package/dist/chunk-3OVABDRH.mjs +0 -17096
  250. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  251. package/dist/chunk-E6SZWREQ.mjs +0 -57
  252. package/dist/chunk-G33LB27A.mjs +0 -16166
  253. package/dist/chunk-HGU6HZRC.mjs +0 -231
  254. package/dist/chunk-L2K34JCU.mjs +0 -1496
  255. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  256. package/dist/constants-VOI7BSLK.mjs +0 -27
  257. package/dist/index-BYZbDjal.d.ts +0 -11390
  258. package/dist/index-CHB3KuOB.d.mts +0 -11859
  259. package/dist/index-CzWPI6Le.d.ts +0 -11859
  260. package/dist/index-xbWjohNq.d.mts +0 -11390
  261. package/dist/solana-5EMCTPTS.mjs +0 -46
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
@@ -0,0 +1,1150 @@
1
+ /**
2
+ * Solana RPC Client for Privacy Transactions
3
+ *
4
+ * Provides a robust RPC client with:
5
+ * - Retry logic with exponential backoff
6
+ * - Multiple endpoint failover
7
+ * - Priority fee estimation and application
8
+ * - Compute unit optimization
9
+ * - Transaction confirmation tracking
10
+ * - Error classification and handling
11
+ *
12
+ * Migrated to @solana/kit while maintaining backward-compatible public API.
13
+ *
14
+ * @module chains/solana/rpc-client
15
+ */
16
+
17
+ import {
18
+ createSolanaRpc,
19
+ createSolanaRpcSubscriptions,
20
+ type Address,
21
+ type Rpc,
22
+ type RpcSubscriptions,
23
+ type SolanaRpcApi,
24
+ type SolanaRpcSubscriptionsApi,
25
+ type Commitment as KitCommitment,
26
+ type Base64EncodedWireTransaction,
27
+ type Signature,
28
+ } from '@solana/kit'
29
+ import {
30
+ Connection,
31
+ PublicKey,
32
+ Transaction,
33
+ VersionedTransaction,
34
+ ComputeBudgetProgram,
35
+ type Commitment,
36
+ type SendOptions,
37
+ type TransactionSignature,
38
+ type SignatureResult,
39
+ } from '@solana/web3.js'
40
+ import {
41
+ toAddress,
42
+ toPublicKey,
43
+ } from './kit-compat'
44
+ import type { NetworkPrivacyConfig } from '../../network/proxy'
45
+ import { createNetworkPrivacyClient, rotateCircuit as rotateProxyCircuit } from '../../network/proxy'
46
+ import type { Agent } from 'http'
47
+
48
+ // ─── Types ────────────────────────────────────────────────────────────────────
49
+
50
+ /**
51
+ * RPC client configuration
52
+ */
53
+ export interface RPCClientConfig {
54
+ /**
55
+ * Primary RPC endpoint URL
56
+ */
57
+ endpoint: string
58
+
59
+ /**
60
+ * Fallback RPC endpoints (used when primary fails)
61
+ */
62
+ fallbackEndpoints?: string[]
63
+
64
+ /**
65
+ * Commitment level for confirmations
66
+ * @default 'confirmed'
67
+ */
68
+ commitment?: Commitment
69
+
70
+ /**
71
+ * Maximum retry attempts for failed requests
72
+ * @default 3
73
+ */
74
+ maxRetries?: number
75
+
76
+ /**
77
+ * Base delay for exponential backoff (ms)
78
+ * @default 500
79
+ */
80
+ retryBaseDelay?: number
81
+
82
+ /**
83
+ * Maximum delay between retries (ms)
84
+ * @default 5000
85
+ */
86
+ retryMaxDelay?: number
87
+
88
+ /**
89
+ * Request timeout (ms)
90
+ * @default 30000
91
+ */
92
+ timeout?: number
93
+
94
+ /**
95
+ * Enable priority fees for faster confirmation
96
+ * @default false
97
+ */
98
+ usePriorityFees?: boolean
99
+
100
+ /**
101
+ * Target percentile for priority fee estimation
102
+ * @default 75
103
+ */
104
+ priorityFeePercentile?: number
105
+
106
+ /**
107
+ * Enable debug logging
108
+ * @default false
109
+ */
110
+ debug?: boolean
111
+
112
+ /**
113
+ * Network privacy configuration (Tor/SOCKS5 proxy)
114
+ *
115
+ * Routes all RPC traffic through the specified proxy for IP privacy.
116
+ *
117
+ * @example Using Tor
118
+ * ```typescript
119
+ * const client = createRPCClient({
120
+ * endpoint: 'https://api.mainnet-beta.solana.com',
121
+ * networkPrivacy: {
122
+ * proxy: 'tor',
123
+ * rotateCircuit: true,
124
+ * },
125
+ * })
126
+ * ```
127
+ *
128
+ * @example Using custom SOCKS5
129
+ * ```typescript
130
+ * const client = createRPCClient({
131
+ * endpoint: 'https://api.mainnet-beta.solana.com',
132
+ * networkPrivacy: {
133
+ * proxy: 'socks5://127.0.0.1:1080',
134
+ * },
135
+ * })
136
+ * ```
137
+ */
138
+ networkPrivacy?: NetworkPrivacyConfig
139
+ }
140
+
141
+ /**
142
+ * Transaction send options with priority fee support
143
+ */
144
+ export interface SendTransactionOptions extends SendOptions {
145
+ /**
146
+ * Skip adding priority fee instructions
147
+ */
148
+ skipPriorityFee?: boolean
149
+
150
+ /**
151
+ * Custom priority fee in microlamports per compute unit
152
+ */
153
+ priorityFeeMicroLamports?: number
154
+
155
+ /**
156
+ * Custom compute unit limit
157
+ */
158
+ computeUnitLimit?: number
159
+
160
+ /**
161
+ * Commitment level for this transaction
162
+ */
163
+ commitment?: Commitment
164
+ }
165
+
166
+ /**
167
+ * Transaction confirmation result
168
+ */
169
+ export interface TransactionConfirmationResult {
170
+ /**
171
+ * Transaction signature
172
+ */
173
+ signature: TransactionSignature
174
+
175
+ /**
176
+ * Confirmation status
177
+ */
178
+ confirmed: boolean
179
+
180
+ /**
181
+ * Slot when confirmed
182
+ */
183
+ slot?: number
184
+
185
+ /**
186
+ * Error if transaction failed
187
+ */
188
+ error?: Error
189
+
190
+ /**
191
+ * Time to confirmation (ms)
192
+ */
193
+ confirmationTime?: number
194
+ }
195
+
196
+ /**
197
+ * Priority fee estimate
198
+ */
199
+ export interface PriorityFeeEstimate {
200
+ /**
201
+ * Estimated priority fee in microlamports per compute unit
202
+ */
203
+ microLamportsPerComputeUnit: number
204
+
205
+ /**
206
+ * Total fee estimate in lamports
207
+ */
208
+ totalLamports: bigint
209
+
210
+ /**
211
+ * Percentile used for estimation
212
+ */
213
+ percentile: number
214
+ }
215
+
216
+ /**
217
+ * RPC error classification
218
+ */
219
+ export enum RPCErrorType {
220
+ /** Network/connection error */
221
+ NETWORK = 'NETWORK',
222
+ /** Rate limit exceeded */
223
+ RATE_LIMIT = 'RATE_LIMIT',
224
+ /** Transaction simulation failed */
225
+ SIMULATION_FAILED = 'SIMULATION_FAILED',
226
+ /** Transaction expired (blockhash invalid) */
227
+ BLOCKHASH_EXPIRED = 'BLOCKHASH_EXPIRED',
228
+ /** Insufficient funds */
229
+ INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS',
230
+ /** Invalid transaction */
231
+ INVALID_TRANSACTION = 'INVALID_TRANSACTION',
232
+ /** Node is behind */
233
+ NODE_BEHIND = 'NODE_BEHIND',
234
+ /** Unknown error */
235
+ UNKNOWN = 'UNKNOWN',
236
+ }
237
+
238
+ /**
239
+ * Classified RPC error
240
+ */
241
+ export interface ClassifiedRPCError {
242
+ /** Error type classification */
243
+ type: RPCErrorType
244
+ /** Original error message */
245
+ message: string
246
+ /** Whether this error is retryable */
247
+ retryable: boolean
248
+ /** Suggested retry delay (ms) */
249
+ suggestedDelay?: number
250
+ /** Original error */
251
+ originalError: Error
252
+ }
253
+
254
+ // ─── Internal Types ──────────────────────────────────────────────────────────
255
+
256
+ interface RpcEndpoint {
257
+ rpc: Rpc<SolanaRpcApi>
258
+ rpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi>
259
+ legacyConnection: Connection
260
+ endpoint: string
261
+ }
262
+
263
+ // ─── SolanaRPCClient Class ────────────────────────────────────────────────────
264
+
265
+ /**
266
+ * Robust Solana RPC client for privacy transactions
267
+ *
268
+ * Migrated to @solana/kit while maintaining full backward compatibility.
269
+ *
270
+ * @example Basic usage
271
+ * ```typescript
272
+ * const client = new SolanaRPCClient({
273
+ * endpoint: 'https://api.mainnet-beta.solana.com',
274
+ * fallbackEndpoints: [
275
+ * 'https://solana-api.projectserum.com',
276
+ * ],
277
+ * usePriorityFees: true,
278
+ * })
279
+ *
280
+ * // Send transaction with retry logic
281
+ * const result = await client.sendAndConfirmTransaction(
282
+ * signedTransaction,
283
+ * { skipPreflight: false }
284
+ * )
285
+ * ```
286
+ *
287
+ * @example With priority fees
288
+ * ```typescript
289
+ * // Estimate priority fee
290
+ * const feeEstimate = await client.estimatePriorityFee(transaction)
291
+ *
292
+ * // Send with custom priority fee
293
+ * const result = await client.sendAndConfirmTransaction(
294
+ * transaction,
295
+ * { priorityFeeMicroLamports: feeEstimate.microLamportsPerComputeUnit }
296
+ * )
297
+ * ```
298
+ */
299
+ export class SolanaRPCClient {
300
+ private endpoints: RpcEndpoint[] = []
301
+ private currentEndpointIndex: number = 0
302
+ private commitment: Commitment
303
+ private maxRetries: number
304
+ private retryBaseDelay: number
305
+ private retryMaxDelay: number
306
+ private usePriorityFees: boolean
307
+ private priorityFeePercentile: number
308
+ private debug: boolean
309
+ private proxyAgent?: Agent
310
+ private networkPrivacyConfig?: NetworkPrivacyConfig
311
+ private torControlPort?: number
312
+ private torControlPassword?: string
313
+
314
+ constructor(config: RPCClientConfig) {
315
+ this.commitment = config.commitment ?? 'confirmed'
316
+ this.maxRetries = config.maxRetries ?? 3
317
+ this.retryBaseDelay = config.retryBaseDelay ?? 500
318
+ this.retryMaxDelay = config.retryMaxDelay ?? 5000
319
+ this.usePriorityFees = config.usePriorityFees ?? false
320
+ this.priorityFeePercentile = config.priorityFeePercentile ?? 75
321
+ this.debug = config.debug ?? false
322
+ this.networkPrivacyConfig = config.networkPrivacy
323
+ this.torControlPort = config.networkPrivacy?.torControlPort
324
+ this.torControlPassword = config.networkPrivacy?.torControlPassword
325
+
326
+ // Create primary endpoint
327
+ this.endpoints.push(this.createEndpoint(config.endpoint, config.commitment ?? 'confirmed', config.timeout ?? 30000))
328
+
329
+ // Create fallback endpoints
330
+ if (config.fallbackEndpoints) {
331
+ for (const endpoint of config.fallbackEndpoints) {
332
+ this.endpoints.push(this.createEndpoint(endpoint, config.commitment ?? 'confirmed', config.timeout ?? 30000))
333
+ }
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Create an RPC client with network privacy (async initialization)
339
+ *
340
+ * Use this factory method when you need Tor or SOCKS5 proxy support.
341
+ * The proxy agent is initialized before creating the client.
342
+ *
343
+ * @param config - Client configuration with network privacy settings
344
+ * @returns Configured RPC client with proxy support
345
+ *
346
+ * @example Using Tor
347
+ * ```typescript
348
+ * const client = await SolanaRPCClient.withNetworkPrivacy({
349
+ * endpoint: 'https://api.mainnet-beta.solana.com',
350
+ * networkPrivacy: {
351
+ * proxy: 'tor',
352
+ * rotateCircuit: true,
353
+ * torControlPassword: 'my-password',
354
+ * },
355
+ * })
356
+ *
357
+ * // All RPC calls now go through Tor
358
+ * const balance = await client.getBalance(publicKey)
359
+ * ```
360
+ */
361
+ static async withNetworkPrivacy(config: RPCClientConfig): Promise<SolanaRPCClient> {
362
+ const client = new SolanaRPCClient(config)
363
+
364
+ if (config.networkPrivacy?.proxy) {
365
+ const networkClient = await createNetworkPrivacyClient({
366
+ proxy: config.networkPrivacy.proxy,
367
+ timeout: config.timeout ?? 30000,
368
+ fallbackToDirect: config.networkPrivacy.fallbackToDirect,
369
+ })
370
+
371
+ client.proxyAgent = networkClient.agent
372
+
373
+ // Recreate endpoints with proxy agent
374
+ client.endpoints = []
375
+ client.endpoints.push(
376
+ client.createEndpoint(config.endpoint, config.commitment ?? 'confirmed', config.timeout ?? 30000)
377
+ )
378
+ if (config.fallbackEndpoints) {
379
+ for (const endpoint of config.fallbackEndpoints) {
380
+ client.endpoints.push(
381
+ client.createEndpoint(endpoint, config.commitment ?? 'confirmed', config.timeout ?? 30000)
382
+ )
383
+ }
384
+ }
385
+
386
+ if (client.debug) {
387
+ console.log(`[RPC] Network privacy enabled: ${networkClient.status.type}`)
388
+ }
389
+ }
390
+
391
+ return client
392
+ }
393
+
394
+ /**
395
+ * Create an endpoint with both kit and legacy clients
396
+ */
397
+ private createEndpoint(endpoint: string, commitment: Commitment, timeout: number): RpcEndpoint {
398
+ const wsEndpoint = this.deriveWsEndpoint(endpoint)
399
+
400
+ // Connection options with optional proxy agent
401
+ const connectionOptions: {
402
+ commitment: Commitment
403
+ confirmTransactionInitialTimeout: number
404
+ wsEndpoint: string
405
+ httpAgent?: Agent
406
+ httpsAgent?: Agent
407
+ } = {
408
+ commitment,
409
+ confirmTransactionInitialTimeout: timeout,
410
+ wsEndpoint,
411
+ }
412
+
413
+ // Add proxy agent if configured (for Node.js)
414
+ if (this.proxyAgent) {
415
+ connectionOptions.httpAgent = this.proxyAgent
416
+ connectionOptions.httpsAgent = this.proxyAgent
417
+ }
418
+
419
+ return {
420
+ rpc: createSolanaRpc(endpoint),
421
+ rpcSubscriptions: createSolanaRpcSubscriptions(wsEndpoint),
422
+ legacyConnection: new Connection(endpoint, connectionOptions),
423
+ endpoint,
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Derive WebSocket endpoint from HTTP endpoint
429
+ */
430
+ private deriveWsEndpoint(httpEndpoint: string): string {
431
+ try {
432
+ const url = new URL(httpEndpoint)
433
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
434
+ return url.toString()
435
+ } catch {
436
+ // Fallback for malformed URLs
437
+ return httpEndpoint.replace(/^http/, 'ws')
438
+ }
439
+ }
440
+
441
+ // ─── Connection Management ──────────────────────────────────────────────────
442
+
443
+ /**
444
+ * Get the current active kit RPC client
445
+ */
446
+ getRpc(): Rpc<SolanaRpcApi> {
447
+ return this.endpoints[this.currentEndpointIndex].rpc
448
+ }
449
+
450
+ /**
451
+ * Get the current active kit RPC subscriptions client
452
+ */
453
+ getRpcSubscriptions(): RpcSubscriptions<SolanaRpcSubscriptionsApi> {
454
+ return this.endpoints[this.currentEndpointIndex].rpcSubscriptions
455
+ }
456
+
457
+ /**
458
+ * Get the current active legacy Connection (for backward compatibility)
459
+ */
460
+ getConnection(): Connection {
461
+ return this.endpoints[this.currentEndpointIndex].legacyConnection
462
+ }
463
+
464
+ /**
465
+ * Get all configured legacy connections
466
+ * @deprecated Use getRpc() for new code
467
+ */
468
+ getConnections(): Connection[] {
469
+ return this.endpoints.map(e => e.legacyConnection)
470
+ }
471
+
472
+ /**
473
+ * Switch to the next available endpoint (failover)
474
+ */
475
+ private switchEndpoint(): boolean {
476
+ if (this.endpoints.length <= 1) {
477
+ return false
478
+ }
479
+
480
+ const previousIndex = this.currentEndpointIndex
481
+ this.currentEndpointIndex = (this.currentEndpointIndex + 1) % this.endpoints.length
482
+
483
+ if (this.debug) {
484
+ console.log(`[RPC] Switching from endpoint ${previousIndex} to ${this.currentEndpointIndex}`)
485
+ }
486
+
487
+ return true
488
+ }
489
+
490
+ // ─── Transaction Submission ─────────────────────────────────────────────────
491
+
492
+ /**
493
+ * Send a transaction with retry logic
494
+ *
495
+ * @param transaction - Signed transaction
496
+ * @param options - Send options
497
+ * @returns Transaction signature
498
+ */
499
+ async sendTransaction(
500
+ transaction: Transaction | VersionedTransaction,
501
+ options: SendTransactionOptions = {}
502
+ ): Promise<TransactionSignature> {
503
+ const {
504
+ skipPriorityFee = false,
505
+ priorityFeeMicroLamports,
506
+ computeUnitLimit,
507
+ ...sendOptions
508
+ } = options
509
+
510
+ // Add priority fee if enabled and not skipped
511
+ let txToSend = transaction
512
+ if (this.usePriorityFees && !skipPriorityFee && transaction instanceof Transaction) {
513
+ txToSend = await this.addPriorityFee(
514
+ transaction,
515
+ priorityFeeMicroLamports,
516
+ computeUnitLimit
517
+ )
518
+ }
519
+
520
+ return this.withRetry(async () => {
521
+ const rpc = this.getRpc()
522
+ const serialized = txToSend.serialize()
523
+ // Cast to branded type (safe: we're encoding valid serialized transaction)
524
+ const base64Encoded = Buffer.from(serialized).toString('base64') as unknown as Base64EncodedWireTransaction
525
+
526
+ // Use kit RPC for sending
527
+ const signature = await rpc.sendTransaction(base64Encoded, {
528
+ encoding: 'base64',
529
+ skipPreflight: sendOptions.skipPreflight ?? false,
530
+ preflightCommitment: (sendOptions.preflightCommitment ?? this.commitment) as KitCommitment,
531
+ maxRetries: BigInt(sendOptions.maxRetries ?? 0), // We handle retries ourselves
532
+ }).send()
533
+
534
+ // Cast signature back to legacy type
535
+ return signature as unknown as TransactionSignature
536
+ })
537
+ }
538
+
539
+ /**
540
+ * Send and confirm a transaction with retry logic
541
+ *
542
+ * @param transaction - Signed transaction
543
+ * @param options - Send options
544
+ * @returns Confirmation result
545
+ */
546
+ async sendAndConfirmTransaction(
547
+ transaction: Transaction | VersionedTransaction,
548
+ options: SendTransactionOptions = {}
549
+ ): Promise<TransactionConfirmationResult> {
550
+ const startTime = Date.now()
551
+
552
+ try {
553
+ const signature = await this.sendTransaction(transaction, options)
554
+ const confirmation = await this.confirmTransaction(
555
+ signature,
556
+ options.commitment ?? this.commitment
557
+ )
558
+
559
+ return {
560
+ signature,
561
+ confirmed: true,
562
+ slot: confirmation.context?.slot,
563
+ confirmationTime: Date.now() - startTime,
564
+ }
565
+ } catch (error) {
566
+ const classified = this.classifyError(error as Error)
567
+
568
+ return {
569
+ signature: '' as TransactionSignature,
570
+ confirmed: false,
571
+ error: classified.originalError,
572
+ confirmationTime: Date.now() - startTime,
573
+ }
574
+ }
575
+ }
576
+
577
+ /**
578
+ * Confirm a transaction signature
579
+ *
580
+ * @param signature - Transaction signature
581
+ * @param commitment - Commitment level
582
+ * @returns Signature result
583
+ */
584
+ async confirmTransaction(
585
+ signature: TransactionSignature,
586
+ commitment: Commitment = this.commitment
587
+ ): Promise<{ context: { slot: number }; value: SignatureResult }> {
588
+ return this.withRetry(async () => {
589
+ const rpc = this.getRpc()
590
+
591
+ // Get latest blockhash for confirmation
592
+ const { value: latestBlockhash } = await rpc.getLatestBlockhash({
593
+ commitment: commitment as KitCommitment,
594
+ }).send()
595
+
596
+ // Poll for signature status using kit RPC
597
+ // Cast to branded Signature type (safe: signature comes from sendTransaction)
598
+ const { value: statuses, context } = await rpc.getSignatureStatuses([signature as unknown as Signature]).send()
599
+ const status = statuses[0]
600
+
601
+ if (status === null) {
602
+ // Transaction not found yet, need to poll
603
+ // For now, use legacy connection for confirmation as kit doesn't have built-in polling
604
+ const connection = this.getConnection()
605
+ const result = await connection.confirmTransaction(
606
+ {
607
+ signature,
608
+ blockhash: latestBlockhash.blockhash,
609
+ lastValidBlockHeight: Number(latestBlockhash.lastValidBlockHeight),
610
+ },
611
+ commitment
612
+ )
613
+
614
+ if (result.value.err) {
615
+ throw new Error(`Transaction failed: ${JSON.stringify(result.value.err)}`)
616
+ }
617
+
618
+ return result
619
+ }
620
+
621
+ if (status.err) {
622
+ throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`)
623
+ }
624
+
625
+ return {
626
+ context: { slot: Number(context.slot) },
627
+ value: { err: null },
628
+ }
629
+ })
630
+ }
631
+
632
+ // ─── Priority Fees ──────────────────────────────────────────────────────────
633
+
634
+ /**
635
+ * Estimate priority fee for a transaction
636
+ *
637
+ * @param transaction - Transaction to estimate fees for
638
+ * @returns Priority fee estimate
639
+ */
640
+ async estimatePriorityFee(
641
+ transaction: Transaction
642
+ ): Promise<PriorityFeeEstimate> {
643
+ try {
644
+ const rpc = this.getRpc()
645
+
646
+ // Get accounts involved in transaction
647
+ const accountKeys = transaction.compileMessage().accountKeys.map(k => toAddress(k))
648
+
649
+ // Get recent priority fees using kit RPC
650
+ // Kit API takes array directly, not wrapped in object
651
+ const recentFees = await rpc.getRecentPrioritizationFees(accountKeys.slice(0, 5)).send()
652
+
653
+ if (recentFees.length === 0) {
654
+ return {
655
+ microLamportsPerComputeUnit: 1000, // Default: 1000 microlamports
656
+ totalLamports: 200_000n, // Assuming 200k CU
657
+ percentile: this.priorityFeePercentile,
658
+ }
659
+ }
660
+
661
+ // Sort by priority fee and get percentile
662
+ const sortedFees = recentFees
663
+ .map(f => Number(f.prioritizationFee))
664
+ .sort((a, b) => a - b)
665
+
666
+ const percentileIndex = Math.floor(sortedFees.length * (this.priorityFeePercentile / 100))
667
+ const estimatedFee = sortedFees[percentileIndex] || sortedFees[sortedFees.length - 1]
668
+
669
+ // Estimate compute units (default 200k for privacy transactions)
670
+ const estimatedComputeUnits = 200_000
671
+
672
+ return {
673
+ microLamportsPerComputeUnit: estimatedFee,
674
+ totalLamports: BigInt(Math.ceil(estimatedFee * estimatedComputeUnits / 1_000_000)),
675
+ percentile: this.priorityFeePercentile,
676
+ }
677
+ } catch {
678
+ // Return conservative default on error
679
+ return {
680
+ microLamportsPerComputeUnit: 1000,
681
+ totalLamports: 200_000n,
682
+ percentile: this.priorityFeePercentile,
683
+ }
684
+ }
685
+ }
686
+
687
+ /**
688
+ * Add priority fee instructions to a transaction
689
+ *
690
+ * @param transaction - Transaction to modify
691
+ * @param priorityFee - Priority fee in microlamports (optional, will estimate)
692
+ * @param computeUnitLimit - Compute unit limit (optional)
693
+ * @returns Modified transaction
694
+ */
695
+ async addPriorityFee(
696
+ transaction: Transaction,
697
+ priorityFee?: number,
698
+ computeUnitLimit?: number
699
+ ): Promise<Transaction> {
700
+ // Estimate priority fee if not provided
701
+ let feeToUse = priorityFee
702
+ if (feeToUse === undefined) {
703
+ const estimate = await this.estimatePriorityFee(transaction)
704
+ feeToUse = estimate.microLamportsPerComputeUnit
705
+ }
706
+
707
+ // Create new transaction with priority fee instructions at the start
708
+ const modifiedTx = new Transaction()
709
+
710
+ // Set compute unit limit
711
+ modifiedTx.add(
712
+ ComputeBudgetProgram.setComputeUnitLimit({
713
+ units: computeUnitLimit ?? 200_000,
714
+ })
715
+ )
716
+
717
+ // Set compute unit price (priority fee)
718
+ modifiedTx.add(
719
+ ComputeBudgetProgram.setComputeUnitPrice({
720
+ microLamports: feeToUse,
721
+ })
722
+ )
723
+
724
+ // Add original instructions
725
+ modifiedTx.add(...transaction.instructions)
726
+
727
+ // Copy over transaction metadata
728
+ modifiedTx.recentBlockhash = transaction.recentBlockhash
729
+ modifiedTx.feePayer = transaction.feePayer
730
+ modifiedTx.lastValidBlockHeight = transaction.lastValidBlockHeight
731
+
732
+ return modifiedTx
733
+ }
734
+
735
+ // ─── Error Handling ─────────────────────────────────────────────────────────
736
+
737
+ /**
738
+ * Classify an RPC error for appropriate handling
739
+ *
740
+ * @param error - Error to classify
741
+ * @returns Classified error with retry information
742
+ */
743
+ classifyError(error: Error): ClassifiedRPCError {
744
+ const message = error.message.toLowerCase()
745
+
746
+ // Network errors - retryable
747
+ if (message.includes('network') || message.includes('econnrefused') ||
748
+ message.includes('timeout') || message.includes('socket')) {
749
+ return {
750
+ type: RPCErrorType.NETWORK,
751
+ message: error.message,
752
+ retryable: true,
753
+ suggestedDelay: 1000,
754
+ originalError: error,
755
+ }
756
+ }
757
+
758
+ // Rate limit - retryable with longer delay
759
+ if (message.includes('429') || message.includes('rate limit') ||
760
+ message.includes('too many requests')) {
761
+ return {
762
+ type: RPCErrorType.RATE_LIMIT,
763
+ message: error.message,
764
+ retryable: true,
765
+ suggestedDelay: 5000,
766
+ originalError: error,
767
+ }
768
+ }
769
+
770
+ // Blockhash expired - retryable (need to refresh blockhash)
771
+ if (message.includes('blockhash') || message.includes('expired')) {
772
+ return {
773
+ type: RPCErrorType.BLOCKHASH_EXPIRED,
774
+ message: error.message,
775
+ retryable: true,
776
+ suggestedDelay: 500,
777
+ originalError: error,
778
+ }
779
+ }
780
+
781
+ // Simulation failed - not retryable
782
+ if (message.includes('simulation') || message.includes('preflight')) {
783
+ return {
784
+ type: RPCErrorType.SIMULATION_FAILED,
785
+ message: error.message,
786
+ retryable: false,
787
+ originalError: error,
788
+ }
789
+ }
790
+
791
+ // Insufficient funds - not retryable
792
+ if (message.includes('insufficient') || message.includes('balance')) {
793
+ return {
794
+ type: RPCErrorType.INSUFFICIENT_FUNDS,
795
+ message: error.message,
796
+ retryable: false,
797
+ originalError: error,
798
+ }
799
+ }
800
+
801
+ // Node behind - retryable with failover
802
+ if (message.includes('behind') || message.includes('slot')) {
803
+ return {
804
+ type: RPCErrorType.NODE_BEHIND,
805
+ message: error.message,
806
+ retryable: true,
807
+ suggestedDelay: 2000,
808
+ originalError: error,
809
+ }
810
+ }
811
+
812
+ // Unknown error - conservative retry
813
+ return {
814
+ type: RPCErrorType.UNKNOWN,
815
+ message: error.message,
816
+ retryable: true,
817
+ suggestedDelay: 1000,
818
+ originalError: error,
819
+ }
820
+ }
821
+
822
+ // ─── Retry Logic ────────────────────────────────────────────────────────────
823
+
824
+ /**
825
+ * Execute an operation with retry logic and failover
826
+ */
827
+ private async withRetry<T>(
828
+ operation: () => Promise<T>
829
+ ): Promise<T> {
830
+ let lastError: Error | null = null
831
+ let attempt = 0
832
+
833
+ while (attempt < this.maxRetries) {
834
+ try {
835
+ return await operation()
836
+ } catch (error) {
837
+ lastError = error as Error
838
+ const classified = this.classifyError(lastError)
839
+
840
+ if (this.debug) {
841
+ console.log(`[RPC] Attempt ${attempt + 1} failed:`, classified.type, classified.message)
842
+ }
843
+
844
+ // If not retryable, throw immediately
845
+ if (!classified.retryable) {
846
+ throw lastError
847
+ }
848
+
849
+ // Try failover for network-related errors
850
+ if (classified.type === RPCErrorType.NETWORK ||
851
+ classified.type === RPCErrorType.RATE_LIMIT ||
852
+ classified.type === RPCErrorType.NODE_BEHIND) {
853
+ this.switchEndpoint()
854
+ }
855
+
856
+ // Calculate delay with exponential backoff
857
+ const baseDelay = classified.suggestedDelay ?? this.retryBaseDelay
858
+ const delay = Math.min(
859
+ baseDelay * Math.pow(2, attempt),
860
+ this.retryMaxDelay
861
+ )
862
+
863
+ if (this.debug) {
864
+ console.log(`[RPC] Retrying in ${delay}ms...`)
865
+ }
866
+
867
+ await this.sleep(delay)
868
+ attempt++
869
+ }
870
+ }
871
+
872
+ throw lastError ?? new Error('Max retries exceeded')
873
+ }
874
+
875
+ /**
876
+ * Sleep for a specified duration
877
+ */
878
+ private sleep(ms: number): Promise<void> {
879
+ return new Promise(resolve => setTimeout(resolve, ms))
880
+ }
881
+
882
+ // ─── Utilities ──────────────────────────────────────────────────────────────
883
+
884
+ /**
885
+ * Get latest blockhash with retry logic
886
+ */
887
+ async getLatestBlockhash(): Promise<{
888
+ blockhash: string
889
+ lastValidBlockHeight: number
890
+ }> {
891
+ return this.withRetry(async () => {
892
+ const rpc = this.getRpc()
893
+ const { value } = await rpc.getLatestBlockhash({
894
+ commitment: this.commitment as KitCommitment,
895
+ }).send()
896
+
897
+ return {
898
+ blockhash: value.blockhash,
899
+ lastValidBlockHeight: Number(value.lastValidBlockHeight),
900
+ }
901
+ })
902
+ }
903
+
904
+ /**
905
+ * Get balance with retry logic
906
+ *
907
+ * @param publicKey - PublicKey or Address to check balance
908
+ */
909
+ async getBalance(publicKey: PublicKey | Address): Promise<bigint> {
910
+ return this.withRetry(async () => {
911
+ const rpc = this.getRpc()
912
+ const addr = typeof publicKey === 'string' ? publicKey : toAddress(publicKey)
913
+ const { value } = await rpc.getBalance(addr, {
914
+ commitment: this.commitment as KitCommitment,
915
+ }).send()
916
+ return value
917
+ })
918
+ }
919
+
920
+ /**
921
+ * Get account info with retry logic
922
+ *
923
+ * @param publicKey - PublicKey or Address to get info for
924
+ */
925
+ async getAccountInfo(publicKey: PublicKey | Address) {
926
+ return this.withRetry(async () => {
927
+ const rpc = this.getRpc()
928
+ const addr = typeof publicKey === 'string' ? publicKey : toAddress(publicKey)
929
+ const { value } = await rpc.getAccountInfo(addr, {
930
+ commitment: this.commitment as KitCommitment,
931
+ encoding: 'base64',
932
+ }).send()
933
+
934
+ if (value === null) {
935
+ return null
936
+ }
937
+
938
+ // Convert to legacy format for backward compatibility
939
+ // Note: rentEpoch is deprecated and not returned by kit
940
+ return {
941
+ executable: value.executable,
942
+ owner: toPublicKey(value.owner),
943
+ lamports: Number(value.lamports),
944
+ data: Buffer.from(value.data[0], 'base64'),
945
+ }
946
+ })
947
+ }
948
+
949
+ /**
950
+ * Check if the client is healthy (can connect to RPC)
951
+ */
952
+ async isHealthy(): Promise<boolean> {
953
+ try {
954
+ const rpc = this.getRpc()
955
+ const version = await rpc.getVersion().send()
956
+ return version !== null
957
+ } catch {
958
+ return false
959
+ }
960
+ }
961
+
962
+ /**
963
+ * Get current RPC endpoint
964
+ */
965
+ getCurrentEndpoint(): string {
966
+ return this.endpoints[this.currentEndpointIndex].endpoint
967
+ }
968
+
969
+ // ─── Network Privacy Methods ─────────────────────────────────────────────────
970
+
971
+ /**
972
+ * Check if network privacy is enabled
973
+ *
974
+ * @returns True if RPC calls go through a proxy
975
+ */
976
+ isNetworkPrivacyEnabled(): boolean {
977
+ return this.proxyAgent !== undefined
978
+ }
979
+
980
+ /**
981
+ * Get network privacy configuration
982
+ *
983
+ * @returns Current network privacy config or undefined
984
+ */
985
+ getNetworkPrivacyConfig(): NetworkPrivacyConfig | undefined {
986
+ return this.networkPrivacyConfig
987
+ }
988
+
989
+ /**
990
+ * Rotate Tor circuit for new identity
991
+ *
992
+ * Requests a new circuit from the Tor control port (NEWNYM signal).
993
+ * This provides unlinkability between subsequent requests.
994
+ *
995
+ * Requires:
996
+ * - Tor control port enabled (default: 9051)
997
+ * - Control port password if configured
998
+ *
999
+ * @returns True if circuit rotation succeeded
1000
+ *
1001
+ * @example
1002
+ * ```typescript
1003
+ * const client = await SolanaRPCClient.withNetworkPrivacy({
1004
+ * endpoint: 'https://api.mainnet-beta.solana.com',
1005
+ * networkPrivacy: {
1006
+ * proxy: 'tor',
1007
+ * torControlPassword: 'my-password',
1008
+ * },
1009
+ * })
1010
+ *
1011
+ * // Send first transaction
1012
+ * await client.sendTransaction(tx1)
1013
+ *
1014
+ * // Rotate circuit for unlinkability
1015
+ * await client.rotateTorCircuit()
1016
+ *
1017
+ * // Send second transaction through new circuit
1018
+ * await client.sendTransaction(tx2)
1019
+ * ```
1020
+ */
1021
+ async rotateTorCircuit(): Promise<boolean> {
1022
+ if (!this.networkPrivacyConfig?.proxy) {
1023
+ if (this.debug) {
1024
+ console.log('[RPC] Circuit rotation skipped: no proxy configured')
1025
+ }
1026
+ return false
1027
+ }
1028
+
1029
+ const success = await rotateProxyCircuit(
1030
+ this.torControlPort ?? 9051,
1031
+ this.torControlPassword
1032
+ )
1033
+
1034
+ if (this.debug) {
1035
+ console.log(`[RPC] Circuit rotation: ${success ? 'success' : 'failed'}`)
1036
+ }
1037
+
1038
+ return success
1039
+ }
1040
+ }
1041
+
1042
+ // ─── Factory Function ─────────────────────────────────────────────────────────
1043
+
1044
+ /**
1045
+ * Create a new Solana RPC client
1046
+ *
1047
+ * @param config - Client configuration
1048
+ * @returns Configured RPC client
1049
+ *
1050
+ * @example
1051
+ * ```typescript
1052
+ * // Simple client with single endpoint
1053
+ * const client = createRPCClient({
1054
+ * endpoint: 'https://api.mainnet-beta.solana.com',
1055
+ * })
1056
+ *
1057
+ * // Production client with failover and priority fees
1058
+ * const prodClient = createRPCClient({
1059
+ * endpoint: 'https://mainnet.helius-rpc.com/?api-key=xxx',
1060
+ * fallbackEndpoints: [
1061
+ * 'https://api.mainnet-beta.solana.com',
1062
+ * 'https://solana-api.projectserum.com',
1063
+ * ],
1064
+ * usePriorityFees: true,
1065
+ * maxRetries: 5,
1066
+ * })
1067
+ * ```
1068
+ */
1069
+ export function createRPCClient(config: RPCClientConfig): SolanaRPCClient {
1070
+ return new SolanaRPCClient(config)
1071
+ }
1072
+
1073
+ /**
1074
+ * Create an RPC client with network privacy support (async)
1075
+ *
1076
+ * Use this factory when you need Tor or SOCKS5 proxy support.
1077
+ * The proxy agent is initialized before creating the client.
1078
+ *
1079
+ * @param config - Client configuration with network privacy settings
1080
+ * @returns Configured RPC client with proxy support
1081
+ *
1082
+ * @example Using Tor
1083
+ * ```typescript
1084
+ * const client = await createPrivateRPCClient({
1085
+ * endpoint: 'https://api.mainnet-beta.solana.com',
1086
+ * networkPrivacy: {
1087
+ * proxy: 'tor',
1088
+ * rotateCircuit: true,
1089
+ * },
1090
+ * })
1091
+ *
1092
+ * // All RPC calls go through Tor
1093
+ * const balance = await client.getBalance(publicKey)
1094
+ *
1095
+ * // Rotate circuit between sensitive operations
1096
+ * await client.rotateTorCircuit()
1097
+ * ```
1098
+ *
1099
+ * @example Using custom SOCKS5
1100
+ * ```typescript
1101
+ * const client = await createPrivateRPCClient({
1102
+ * endpoint: 'https://api.mainnet-beta.solana.com',
1103
+ * networkPrivacy: {
1104
+ * proxy: 'socks5://127.0.0.1:1080',
1105
+ * },
1106
+ * })
1107
+ * ```
1108
+ */
1109
+ export async function createPrivateRPCClient(config: RPCClientConfig): Promise<SolanaRPCClient> {
1110
+ return SolanaRPCClient.withNetworkPrivacy(config)
1111
+ }
1112
+
1113
+ // ─── Utility Functions ────────────────────────────────────────────────────────
1114
+
1115
+ /**
1116
+ * Pre-configured RPC endpoints for common clusters
1117
+ */
1118
+ export const RPC_ENDPOINTS = {
1119
+ 'mainnet-beta': [
1120
+ 'https://api.mainnet-beta.solana.com',
1121
+ 'https://solana-api.projectserum.com',
1122
+ 'https://rpc.ankr.com/solana',
1123
+ ],
1124
+ 'devnet': [
1125
+ 'https://api.devnet.solana.com',
1126
+ ],
1127
+ 'testnet': [
1128
+ 'https://api.testnet.solana.com',
1129
+ ],
1130
+ } as const
1131
+
1132
+ /**
1133
+ * Create an RPC client for a specific cluster with default endpoints
1134
+ *
1135
+ * @param cluster - Solana cluster
1136
+ * @param options - Additional client options
1137
+ * @returns Configured RPC client
1138
+ */
1139
+ export function createClusterClient(
1140
+ cluster: 'mainnet-beta' | 'devnet' | 'testnet',
1141
+ options: Partial<RPCClientConfig> = {}
1142
+ ): SolanaRPCClient {
1143
+ const endpoints = RPC_ENDPOINTS[cluster]
1144
+
1145
+ return createRPCClient({
1146
+ endpoint: endpoints[0],
1147
+ fallbackEndpoints: endpoints.slice(1),
1148
+ ...options,
1149
+ })
1150
+ }