@sip-protocol/sdk 0.7.3 → 0.8.0

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 (263) hide show
  1. package/README.md +267 -0
  2. package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
  3. package/dist/browser.d.mts +10 -4
  4. package/dist/browser.d.ts +10 -4
  5. package/dist/browser.js +47556 -19603
  6. package/dist/browser.mjs +628 -48
  7. package/dist/chunk-4GRJ5MAW.mjs +152 -0
  8. package/dist/chunk-5D7A3L3W.mjs +717 -0
  9. package/dist/chunk-64AYA5F5.mjs +7834 -0
  10. package/dist/chunk-GMDGB22A.mjs +379 -0
  11. package/dist/chunk-I534WKN7.mjs +328 -0
  12. package/dist/chunk-IBZVA5Y7.mjs +1003 -0
  13. package/dist/chunk-PRRZAWJE.mjs +223 -0
  14. package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
  15. package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
  16. package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
  17. package/dist/constants-LHAAUC2T.mjs +51 -0
  18. package/dist/dist-2OGQ7FED.mjs +3957 -0
  19. package/dist/dist-IFHPYLDX.mjs +254 -0
  20. package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
  21. package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
  22. package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
  23. package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
  24. package/dist/index.d.mts +9 -3
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.js +48396 -19623
  27. package/dist/index.mjs +537 -19
  28. package/dist/interface-Bf7w1PLW.d.mts +679 -0
  29. package/dist/interface-Bf7w1PLW.d.ts +679 -0
  30. package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
  31. package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
  32. package/dist/proofs/halo2.d.mts +151 -0
  33. package/dist/proofs/halo2.d.ts +151 -0
  34. package/dist/proofs/halo2.js +350 -0
  35. package/dist/proofs/halo2.mjs +11 -0
  36. package/dist/proofs/kimchi.d.mts +160 -0
  37. package/dist/proofs/kimchi.d.ts +160 -0
  38. package/dist/proofs/kimchi.js +431 -0
  39. package/dist/proofs/kimchi.mjs +13 -0
  40. package/dist/proofs/noir.d.mts +1 -1
  41. package/dist/proofs/noir.d.ts +1 -1
  42. package/dist/proofs/noir.js +74 -18
  43. package/dist/proofs/noir.mjs +84 -24
  44. package/dist/solana-U3MEGU7W.mjs +280 -0
  45. package/dist/validity_proof-3POXLPNY.mjs +21 -0
  46. package/package.json +44 -11
  47. package/src/adapters/index.ts +41 -0
  48. package/src/adapters/jupiter.ts +571 -0
  49. package/src/adapters/near-intents.ts +135 -0
  50. package/src/advisor/advisor.ts +653 -0
  51. package/src/advisor/index.ts +54 -0
  52. package/src/advisor/tools.ts +303 -0
  53. package/src/advisor/types.ts +164 -0
  54. package/src/chains/ethereum/announcement.ts +536 -0
  55. package/src/chains/ethereum/bnb-optimizations.ts +474 -0
  56. package/src/chains/ethereum/commitment.ts +522 -0
  57. package/src/chains/ethereum/constants.ts +462 -0
  58. package/src/chains/ethereum/deployment.ts +596 -0
  59. package/src/chains/ethereum/gas-estimation.ts +538 -0
  60. package/src/chains/ethereum/index.ts +268 -0
  61. package/src/chains/ethereum/optimizations.ts +614 -0
  62. package/src/chains/ethereum/privacy-adapter.ts +855 -0
  63. package/src/chains/ethereum/registry.ts +584 -0
  64. package/src/chains/ethereum/rpc.ts +905 -0
  65. package/src/chains/ethereum/stealth.ts +491 -0
  66. package/src/chains/ethereum/token.ts +790 -0
  67. package/src/chains/ethereum/transfer.ts +637 -0
  68. package/src/chains/ethereum/types.ts +456 -0
  69. package/src/chains/ethereum/viewing-key.ts +455 -0
  70. package/src/chains/near/commitment.ts +608 -0
  71. package/src/chains/near/constants.ts +284 -0
  72. package/src/chains/near/function-call.ts +871 -0
  73. package/src/chains/near/history.ts +654 -0
  74. package/src/chains/near/implicit-account.ts +840 -0
  75. package/src/chains/near/index.ts +393 -0
  76. package/src/chains/near/native-transfer.ts +658 -0
  77. package/src/chains/near/nep141.ts +775 -0
  78. package/src/chains/near/privacy-adapter.ts +889 -0
  79. package/src/chains/near/resolver.ts +971 -0
  80. package/src/chains/near/rpc.ts +1016 -0
  81. package/src/chains/near/stealth.ts +419 -0
  82. package/src/chains/near/types.ts +317 -0
  83. package/src/chains/near/viewing-key.ts +876 -0
  84. package/src/chains/solana/anchor-transfer.ts +386 -0
  85. package/src/chains/solana/commitment.ts +577 -0
  86. package/src/chains/solana/constants.ts +126 -12
  87. package/src/chains/solana/ephemeral-keys.ts +543 -0
  88. package/src/chains/solana/index.ts +252 -1
  89. package/src/chains/solana/key-derivation.ts +418 -0
  90. package/src/chains/solana/kit-compat.ts +334 -0
  91. package/src/chains/solana/optimizations.ts +560 -0
  92. package/src/chains/solana/privacy-adapter.ts +605 -0
  93. package/src/chains/solana/providers/generic.ts +47 -6
  94. package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
  95. package/src/chains/solana/providers/helius-enhanced.ts +623 -0
  96. package/src/chains/solana/providers/helius.ts +186 -33
  97. package/src/chains/solana/providers/index.ts +31 -0
  98. package/src/chains/solana/providers/interface.ts +61 -18
  99. package/src/chains/solana/providers/quicknode.ts +409 -0
  100. package/src/chains/solana/providers/triton.ts +426 -0
  101. package/src/chains/solana/providers/webhook.ts +338 -67
  102. package/src/chains/solana/rpc-client.ts +1150 -0
  103. package/src/chains/solana/scan.ts +83 -66
  104. package/src/chains/solana/sol-transfer.ts +732 -0
  105. package/src/chains/solana/spl-transfer.ts +886 -0
  106. package/src/chains/solana/stealth-scanner.ts +703 -0
  107. package/src/chains/solana/sunspot-verifier.ts +453 -0
  108. package/src/chains/solana/transaction-builder.ts +755 -0
  109. package/src/chains/solana/transfer.ts +74 -5
  110. package/src/chains/solana/types.ts +57 -6
  111. package/src/chains/solana/utils.ts +110 -0
  112. package/src/chains/solana/viewing-key.ts +807 -0
  113. package/src/compliance/fireblocks.ts +921 -0
  114. package/src/compliance/index.ts +23 -0
  115. package/src/compliance/range-sas.ts +398 -33
  116. package/src/config/endpoints.ts +100 -0
  117. package/src/crypto.ts +11 -8
  118. package/src/errors.ts +82 -0
  119. package/src/evm/erc4337-relayer.ts +830 -0
  120. package/src/evm/index.ts +47 -0
  121. package/src/fees/calculator.ts +396 -0
  122. package/src/fees/index.ts +87 -0
  123. package/src/fees/near-contract.ts +429 -0
  124. package/src/fees/types.ts +268 -0
  125. package/src/index.ts +686 -1
  126. package/src/intent.ts +6 -3
  127. package/src/logger.ts +324 -0
  128. package/src/network/index.ts +80 -0
  129. package/src/network/proxy.ts +691 -0
  130. package/src/optimizations/index.ts +541 -0
  131. package/src/oracle/types.ts +1 -0
  132. package/src/privacy-backends/arcium-types.ts +727 -0
  133. package/src/privacy-backends/arcium.ts +719 -0
  134. package/src/privacy-backends/combined-privacy.ts +866 -0
  135. package/src/privacy-backends/cspl-token.ts +595 -0
  136. package/src/privacy-backends/cspl-types.ts +512 -0
  137. package/src/privacy-backends/cspl.ts +907 -0
  138. package/src/privacy-backends/health.ts +488 -0
  139. package/src/privacy-backends/inco-types.ts +323 -0
  140. package/src/privacy-backends/inco.ts +616 -0
  141. package/src/privacy-backends/index.ts +254 -4
  142. package/src/privacy-backends/interface.ts +649 -6
  143. package/src/privacy-backends/lru-cache.ts +343 -0
  144. package/src/privacy-backends/magicblock.ts +458 -0
  145. package/src/privacy-backends/mock.ts +258 -0
  146. package/src/privacy-backends/privacycash.ts +13 -17
  147. package/src/privacy-backends/private-swap.ts +570 -0
  148. package/src/privacy-backends/rate-limiter.ts +683 -0
  149. package/src/privacy-backends/registry.ts +414 -2
  150. package/src/privacy-backends/router.ts +283 -3
  151. package/src/privacy-backends/shadowwire.ts +449 -0
  152. package/src/privacy-backends/sip-native.ts +3 -0
  153. package/src/privacy-logger.ts +191 -0
  154. package/src/production-safety.ts +373 -0
  155. package/src/proofs/aggregator.ts +1029 -0
  156. package/src/proofs/browser-composer.ts +1150 -0
  157. package/src/proofs/browser.ts +113 -25
  158. package/src/proofs/cache/index.ts +127 -0
  159. package/src/proofs/cache/interface.ts +545 -0
  160. package/src/proofs/cache/key-generator.ts +188 -0
  161. package/src/proofs/cache/lru-cache.ts +481 -0
  162. package/src/proofs/cache/multi-tier-cache.ts +575 -0
  163. package/src/proofs/cache/persistent-cache.ts +788 -0
  164. package/src/proofs/compliance-proof.ts +872 -0
  165. package/src/proofs/composer/base.ts +923 -0
  166. package/src/proofs/composer/index.ts +25 -0
  167. package/src/proofs/composer/interface.ts +518 -0
  168. package/src/proofs/composer/types.ts +383 -0
  169. package/src/proofs/converters/halo2.ts +452 -0
  170. package/src/proofs/converters/index.ts +208 -0
  171. package/src/proofs/converters/interface.ts +363 -0
  172. package/src/proofs/converters/kimchi.ts +462 -0
  173. package/src/proofs/converters/noir.ts +451 -0
  174. package/src/proofs/fallback.ts +888 -0
  175. package/src/proofs/halo2.ts +42 -0
  176. package/src/proofs/index.ts +471 -0
  177. package/src/proofs/interface.ts +13 -0
  178. package/src/proofs/kimchi.ts +42 -0
  179. package/src/proofs/lazy.ts +1004 -0
  180. package/src/proofs/mock.ts +25 -1
  181. package/src/proofs/noir.ts +110 -29
  182. package/src/proofs/orchestrator.ts +960 -0
  183. package/src/proofs/parallel/concurrency.ts +297 -0
  184. package/src/proofs/parallel/dependency-graph.ts +602 -0
  185. package/src/proofs/parallel/executor.ts +420 -0
  186. package/src/proofs/parallel/index.ts +131 -0
  187. package/src/proofs/parallel/interface.ts +685 -0
  188. package/src/proofs/parallel/worker-pool.ts +644 -0
  189. package/src/proofs/providers/halo2.ts +560 -0
  190. package/src/proofs/providers/index.ts +34 -0
  191. package/src/proofs/providers/kimchi.ts +641 -0
  192. package/src/proofs/validator.ts +881 -0
  193. package/src/proofs/verifier.ts +867 -0
  194. package/src/quantum/index.ts +112 -0
  195. package/src/quantum/winternitz-vault.ts +639 -0
  196. package/src/quantum/wots.ts +611 -0
  197. package/src/settlement/backends/direct-chain.ts +1 -0
  198. package/src/settlement/index.ts +9 -0
  199. package/src/settlement/router.ts +732 -46
  200. package/src/solana/index.ts +72 -0
  201. package/src/solana/jito-relayer.ts +687 -0
  202. package/src/solana/noir-verifier-types.ts +430 -0
  203. package/src/solana/noir-verifier.ts +816 -0
  204. package/src/stealth/address-derivation.ts +193 -0
  205. package/src/stealth/ed25519.ts +431 -0
  206. package/src/stealth/index.ts +233 -0
  207. package/src/stealth/meta-address.ts +221 -0
  208. package/src/stealth/secp256k1.ts +368 -0
  209. package/src/stealth/utils.ts +194 -0
  210. package/src/stealth.ts +50 -1504
  211. package/src/sync/index.ts +106 -0
  212. package/src/sync/manager.ts +504 -0
  213. package/src/sync/mock-provider.ts +318 -0
  214. package/src/sync/oblivious.ts +625 -0
  215. package/src/tokens/index.ts +15 -0
  216. package/src/tokens/registry.ts +301 -0
  217. package/src/utils/deprecation.ts +94 -0
  218. package/src/utils/index.ts +9 -0
  219. package/src/wallet/ethereum/index.ts +68 -0
  220. package/src/wallet/ethereum/metamask-privacy.ts +420 -0
  221. package/src/wallet/ethereum/multi-wallet.ts +646 -0
  222. package/src/wallet/ethereum/privacy-adapter.ts +700 -0
  223. package/src/wallet/ethereum/types.ts +3 -1
  224. package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
  225. package/src/wallet/hardware/index.ts +10 -0
  226. package/src/wallet/hardware/ledger-privacy.ts +414 -0
  227. package/src/wallet/index.ts +71 -0
  228. package/src/wallet/near/adapter.ts +626 -0
  229. package/src/wallet/near/index.ts +86 -0
  230. package/src/wallet/near/meteor-wallet.ts +1153 -0
  231. package/src/wallet/near/my-near-wallet.ts +790 -0
  232. package/src/wallet/near/wallet-selector.ts +702 -0
  233. package/src/wallet/solana/adapter.ts +6 -4
  234. package/src/wallet/solana/index.ts +13 -0
  235. package/src/wallet/solana/privacy-adapter.ts +567 -0
  236. package/src/wallet/sui/types.ts +6 -4
  237. package/src/zcash/rpc-client.ts +13 -6
  238. package/dist/chunk-2XIVXWHA.mjs +0 -1930
  239. package/dist/chunk-3INS3PR5.mjs +0 -884
  240. package/dist/chunk-3OVABDRH.mjs +0 -17096
  241. package/dist/chunk-7RFRWDCW.mjs +0 -1504
  242. package/dist/chunk-DLDWZFYC.mjs +0 -1495
  243. package/dist/chunk-E6SZWREQ.mjs +0 -57
  244. package/dist/chunk-F6F73W35.mjs +0 -16166
  245. package/dist/chunk-G33LB27A.mjs +0 -16166
  246. package/dist/chunk-HGU6HZRC.mjs +0 -231
  247. package/dist/chunk-L2K34JCU.mjs +0 -1496
  248. package/dist/chunk-OFDBEIEK.mjs +0 -16166
  249. package/dist/chunk-SF7YSLF5.mjs +0 -1515
  250. package/dist/chunk-SN4ZDTVW.mjs +0 -16166
  251. package/dist/chunk-WWUSGOXE.mjs +0 -17129
  252. package/dist/constants-VOI7BSLK.mjs +0 -27
  253. package/dist/index-B71aXVzk.d.ts +0 -13264
  254. package/dist/index-BYZbDjal.d.ts +0 -11390
  255. package/dist/index-CHB3KuOB.d.mts +0 -11859
  256. package/dist/index-CzWPI6Le.d.ts +0 -11859
  257. package/dist/index-pOIIuwfV.d.mts +0 -13264
  258. package/dist/index-xbWjohNq.d.mts +0 -11390
  259. package/dist/solana-4O4K45VU.mjs +0 -46
  260. package/dist/solana-5EMCTPTS.mjs +0 -46
  261. package/dist/solana-NDABAZ6P.mjs +0 -56
  262. package/dist/solana-Q4NAVBTS.mjs +0 -46
  263. package/dist/solana-ZYO63LY5.mjs +0 -46
@@ -0,0 +1,905 @@
1
+ /**
2
+ * Ethereum RPC Client for Privacy Transactions
3
+ *
4
+ * Provides JSON-RPC integration for submitting and monitoring
5
+ * privacy transactions with nonce management and confirmation tracking.
6
+ *
7
+ * @module chains/ethereum/rpc
8
+ */
9
+
10
+ import type { HexString } from '@sip-protocol/types'
11
+ import {
12
+ type EthereumNetwork,
13
+ ETHEREUM_RPC_ENDPOINTS,
14
+ getChainId,
15
+ } from './constants'
16
+ import {
17
+ updateGasPriceCache,
18
+ parseFeeHistoryResponse,
19
+ parseGasPriceResponse,
20
+ } from './gas-estimation'
21
+
22
+ // ─── Types ────────────────────────────────────────────────────────────────────
23
+
24
+ /**
25
+ * Transaction status
26
+ */
27
+ export type TransactionStatus =
28
+ | 'pending'
29
+ | 'submitted'
30
+ | 'confirmed'
31
+ | 'failed'
32
+ | 'replaced'
33
+
34
+ /**
35
+ * Submitted transaction info
36
+ */
37
+ export interface SubmittedTransaction {
38
+ /**
39
+ * Transaction hash
40
+ */
41
+ txHash: HexString
42
+
43
+ /**
44
+ * Transaction status
45
+ */
46
+ status: TransactionStatus
47
+
48
+ /**
49
+ * Nonce used
50
+ */
51
+ nonce: number
52
+
53
+ /**
54
+ * Block number (if confirmed)
55
+ */
56
+ blockNumber?: number
57
+
58
+ /**
59
+ * Block hash (if confirmed)
60
+ */
61
+ blockHash?: HexString
62
+
63
+ /**
64
+ * Gas used (if confirmed)
65
+ */
66
+ gasUsed?: bigint
67
+
68
+ /**
69
+ * Effective gas price (if confirmed)
70
+ */
71
+ effectiveGasPrice?: bigint
72
+
73
+ /**
74
+ * Confirmation count
75
+ */
76
+ confirmations: number
77
+
78
+ /**
79
+ * Error message (if failed)
80
+ */
81
+ error?: string
82
+
83
+ /**
84
+ * Timestamp of submission
85
+ */
86
+ submittedAt: number
87
+ }
88
+
89
+ /**
90
+ * Transaction receipt from RPC
91
+ */
92
+ export interface TransactionReceipt {
93
+ /**
94
+ * Transaction hash
95
+ */
96
+ transactionHash: HexString
97
+
98
+ /**
99
+ * Block number
100
+ */
101
+ blockNumber: number
102
+
103
+ /**
104
+ * Block hash
105
+ */
106
+ blockHash: HexString
107
+
108
+ /**
109
+ * Contract address (if deployment)
110
+ */
111
+ contractAddress?: HexString
112
+
113
+ /**
114
+ * Gas used
115
+ */
116
+ gasUsed: bigint
117
+
118
+ /**
119
+ * Effective gas price
120
+ */
121
+ effectiveGasPrice: bigint
122
+
123
+ /**
124
+ * Status (1 = success, 0 = failure)
125
+ */
126
+ status: 0 | 1
127
+
128
+ /**
129
+ * Transaction logs
130
+ */
131
+ logs: Array<{
132
+ address: HexString
133
+ topics: HexString[]
134
+ data: HexString
135
+ logIndex: number
136
+ }>
137
+ }
138
+
139
+ /**
140
+ * RPC request options
141
+ */
142
+ export interface RpcRequestOptions {
143
+ /**
144
+ * Request timeout in ms (default: 30000)
145
+ */
146
+ timeout?: number
147
+
148
+ /**
149
+ * Number of retries (default: 3)
150
+ */
151
+ retries?: number
152
+
153
+ /**
154
+ * Retry delay in ms (default: 1000)
155
+ */
156
+ retryDelay?: number
157
+ }
158
+
159
+ /**
160
+ * Nonce manager state
161
+ */
162
+ interface NonceState {
163
+ current: number
164
+ pending: number
165
+ lastUpdated: number
166
+ }
167
+
168
+ // ─── Constants ────────────────────────────────────────────────────────────────
169
+
170
+ /**
171
+ * Default request timeout (30 seconds)
172
+ */
173
+ const DEFAULT_TIMEOUT = 30000
174
+
175
+ /**
176
+ * Default retry count
177
+ */
178
+ const DEFAULT_RETRIES = 3
179
+
180
+ /**
181
+ * Default retry delay
182
+ */
183
+ const DEFAULT_RETRY_DELAY = 1000
184
+
185
+ /**
186
+ * Nonce cache duration (10 seconds)
187
+ */
188
+ const NONCE_CACHE_DURATION = 10000
189
+
190
+ // ─── RPC Client Class ────────────────────────────────────────────────────────
191
+
192
+ /**
193
+ * Ethereum RPC Client
194
+ *
195
+ * Provides JSON-RPC methods for privacy transaction submission,
196
+ * monitoring, and gas price fetching.
197
+ *
198
+ * @example Basic usage
199
+ * ```typescript
200
+ * const rpc = new EthereumRpcClient('mainnet')
201
+ *
202
+ * // Fetch gas prices
203
+ * const gasPrice = await rpc.getGasPrice()
204
+ *
205
+ * // Get account nonce
206
+ * const nonce = await rpc.getTransactionCount(address)
207
+ *
208
+ * // Submit transaction
209
+ * const txHash = await rpc.sendRawTransaction(signedTx)
210
+ *
211
+ * // Wait for confirmation
212
+ * const receipt = await rpc.waitForTransaction(txHash)
213
+ * ```
214
+ */
215
+ export class EthereumRpcClient {
216
+ private rpcUrl: string
217
+ private network: EthereumNetwork
218
+ private chainId: number
219
+ private nonceCache: Map<string, NonceState> = new Map()
220
+ private pendingTxs: Map<string, SubmittedTransaction> = new Map()
221
+ private requestId: number = 0
222
+
223
+ constructor(
224
+ network: EthereumNetwork = 'mainnet',
225
+ options?: {
226
+ rpcUrl?: string
227
+ }
228
+ ) {
229
+ this.network = network
230
+ this.rpcUrl = options?.rpcUrl ?? ETHEREUM_RPC_ENDPOINTS[network]
231
+ this.chainId = getChainId(network)
232
+ }
233
+
234
+ // ─── Gas Price Methods ───────────────────────────────────────────────────────
235
+
236
+ /**
237
+ * Get current gas price (legacy)
238
+ *
239
+ * @param options - Request options
240
+ * @returns Gas price in wei
241
+ */
242
+ async getGasPrice(options?: RpcRequestOptions): Promise<bigint> {
243
+ const result = await this.rpcCall<HexString>('eth_gasPrice', [], options)
244
+ const gasPrice = parseGasPriceResponse(result)
245
+
246
+ // Update cache
247
+ updateGasPriceCache(this.network, gasPrice, 2n * 10n ** 9n) // Default 2 gwei priority
248
+
249
+ return gasPrice
250
+ }
251
+
252
+ /**
253
+ * Get EIP-1559 fee data
254
+ *
255
+ * @param blockCount - Number of blocks to analyze (default: 4)
256
+ * @param options - Request options
257
+ * @returns Base fee and priority fee suggestions
258
+ */
259
+ async getFeeData(
260
+ blockCount: number = 4,
261
+ options?: RpcRequestOptions
262
+ ): Promise<{
263
+ baseFeePerGas: bigint
264
+ maxPriorityFeePerGas: bigint
265
+ maxFeePerGas: bigint
266
+ }> {
267
+ try {
268
+ const result = await this.rpcCall<{
269
+ baseFeePerGas: HexString[]
270
+ reward: HexString[][]
271
+ }>(
272
+ 'eth_feeHistory',
273
+ [
274
+ `0x${blockCount.toString(16)}`,
275
+ 'latest',
276
+ [25, 50, 75], // Percentiles
277
+ ],
278
+ options
279
+ )
280
+
281
+ const { baseFee, priorityFee } = parseFeeHistoryResponse(
282
+ result.baseFeePerGas,
283
+ result.reward
284
+ )
285
+
286
+ // Update cache
287
+ updateGasPriceCache(this.network, baseFee, priorityFee)
288
+
289
+ const maxFeePerGas = baseFee * 2n + priorityFee
290
+
291
+ return {
292
+ baseFeePerGas: baseFee,
293
+ maxPriorityFeePerGas: priorityFee,
294
+ maxFeePerGas,
295
+ }
296
+ } catch {
297
+ // Fallback to legacy gas price
298
+ const gasPrice = await this.getGasPrice(options)
299
+ return {
300
+ baseFeePerGas: gasPrice,
301
+ maxPriorityFeePerGas: 2n * 10n ** 9n,
302
+ maxFeePerGas: gasPrice * 2n,
303
+ }
304
+ }
305
+ }
306
+
307
+ // ─── Account Methods ─────────────────────────────────────────────────────────
308
+
309
+ /**
310
+ * Get transaction count (nonce) for an address
311
+ *
312
+ * @param address - Ethereum address
313
+ * @param blockTag - Block tag (default: 'pending')
314
+ * @param options - Request options
315
+ * @returns Transaction count
316
+ */
317
+ async getTransactionCount(
318
+ address: HexString,
319
+ blockTag: 'latest' | 'pending' = 'pending',
320
+ options?: RpcRequestOptions
321
+ ): Promise<number> {
322
+ // Check cache
323
+ const cached = this.getCachedNonce(address)
324
+ if (cached !== undefined && blockTag === 'pending') {
325
+ return cached
326
+ }
327
+
328
+ const result = await this.rpcCall<HexString>(
329
+ 'eth_getTransactionCount',
330
+ [address, blockTag],
331
+ options
332
+ )
333
+
334
+ const count = parseInt(result, 16)
335
+
336
+ // Update cache
337
+ this.updateNonceCache(address, count)
338
+
339
+ return count
340
+ }
341
+
342
+ /**
343
+ * Get next available nonce (with local tracking)
344
+ *
345
+ * @param address - Ethereum address
346
+ * @param options - Request options
347
+ * @returns Next nonce to use
348
+ */
349
+ async getNextNonce(
350
+ address: HexString,
351
+ options?: RpcRequestOptions
352
+ ): Promise<number> {
353
+ const key = address.toLowerCase()
354
+ const cached = this.nonceCache.get(key)
355
+
356
+ // If we have pending transactions, return the next pending nonce
357
+ if (cached && Date.now() - cached.lastUpdated < NONCE_CACHE_DURATION) {
358
+ return cached.pending
359
+ }
360
+
361
+ // Fetch from network
362
+ const networkNonce = await this.getTransactionCount(address, 'pending', options)
363
+ return networkNonce
364
+ }
365
+
366
+ /**
367
+ * Reserve a nonce for a transaction
368
+ *
369
+ * @param address - Ethereum address
370
+ * @returns Reserved nonce
371
+ */
372
+ reserveNonce(address: HexString): number {
373
+ const key = address.toLowerCase()
374
+ const cached = this.nonceCache.get(key)
375
+
376
+ if (!cached) {
377
+ // Initialize with 0, will be corrected on first actual tx
378
+ this.nonceCache.set(key, {
379
+ current: 0,
380
+ pending: 1,
381
+ lastUpdated: Date.now(),
382
+ })
383
+ return 0
384
+ }
385
+
386
+ // Increment pending nonce
387
+ const nonce = cached.pending
388
+ cached.pending++
389
+ cached.lastUpdated = Date.now()
390
+
391
+ return nonce
392
+ }
393
+
394
+ /**
395
+ * Release a reserved nonce (if transaction failed before submission)
396
+ *
397
+ * @param address - Ethereum address
398
+ * @param nonce - Nonce to release
399
+ */
400
+ releaseNonce(address: HexString, nonce: number): void {
401
+ const key = address.toLowerCase()
402
+ const cached = this.nonceCache.get(key)
403
+
404
+ if (cached && cached.pending > nonce) {
405
+ cached.pending = nonce
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Get account balance
411
+ *
412
+ * @param address - Ethereum address
413
+ * @param blockTag - Block tag
414
+ * @param options - Request options
415
+ * @returns Balance in wei
416
+ */
417
+ async getBalance(
418
+ address: HexString,
419
+ blockTag: 'latest' | 'pending' = 'latest',
420
+ options?: RpcRequestOptions
421
+ ): Promise<bigint> {
422
+ const result = await this.rpcCall<HexString>(
423
+ 'eth_getBalance',
424
+ [address, blockTag],
425
+ options
426
+ )
427
+
428
+ return BigInt(result)
429
+ }
430
+
431
+ // ─── Transaction Methods ─────────────────────────────────────────────────────
432
+
433
+ /**
434
+ * Send a signed raw transaction
435
+ *
436
+ * @param signedTx - Signed transaction hex
437
+ * @param options - Request options
438
+ * @returns Transaction hash
439
+ */
440
+ async sendRawTransaction(
441
+ signedTx: HexString,
442
+ options?: RpcRequestOptions
443
+ ): Promise<HexString> {
444
+ const txHash = await this.rpcCall<HexString>(
445
+ 'eth_sendRawTransaction',
446
+ [signedTx],
447
+ options
448
+ )
449
+
450
+ // Track pending transaction
451
+ this.pendingTxs.set(txHash.toLowerCase(), {
452
+ txHash,
453
+ status: 'submitted',
454
+ nonce: -1, // Will be updated from receipt
455
+ confirmations: 0,
456
+ submittedAt: Date.now(),
457
+ })
458
+
459
+ return txHash
460
+ }
461
+
462
+ /**
463
+ * Get transaction receipt
464
+ *
465
+ * @param txHash - Transaction hash
466
+ * @param options - Request options
467
+ * @returns Receipt or null if pending
468
+ */
469
+ async getTransactionReceipt(
470
+ txHash: HexString,
471
+ options?: RpcRequestOptions
472
+ ): Promise<TransactionReceipt | null> {
473
+ const result = await this.rpcCall<{
474
+ transactionHash: HexString
475
+ blockNumber: HexString
476
+ blockHash: HexString
477
+ contractAddress: HexString | null
478
+ gasUsed: HexString
479
+ effectiveGasPrice: HexString
480
+ status: HexString
481
+ logs: Array<{
482
+ address: HexString
483
+ topics: HexString[]
484
+ data: HexString
485
+ logIndex: HexString
486
+ }>
487
+ } | null>('eth_getTransactionReceipt', [txHash], options)
488
+
489
+ if (!result) {
490
+ return null
491
+ }
492
+
493
+ return {
494
+ transactionHash: result.transactionHash,
495
+ blockNumber: parseInt(result.blockNumber, 16),
496
+ blockHash: result.blockHash,
497
+ contractAddress: result.contractAddress ?? undefined,
498
+ gasUsed: BigInt(result.gasUsed),
499
+ effectiveGasPrice: BigInt(result.effectiveGasPrice),
500
+ status: parseInt(result.status, 16) as 0 | 1,
501
+ logs: result.logs.map((log) => ({
502
+ address: log.address,
503
+ topics: log.topics,
504
+ data: log.data,
505
+ logIndex: parseInt(log.logIndex, 16),
506
+ })),
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Wait for transaction confirmation
512
+ *
513
+ * @param txHash - Transaction hash
514
+ * @param confirmations - Number of confirmations to wait for (default: 1)
515
+ * @param timeout - Timeout in ms (default: 60000)
516
+ * @returns Transaction receipt
517
+ */
518
+ async waitForTransaction(
519
+ txHash: HexString,
520
+ confirmations: number = 1,
521
+ timeout: number = 60000
522
+ ): Promise<TransactionReceipt> {
523
+ const startTime = Date.now()
524
+ const pollInterval = 2000 // 2 seconds
525
+
526
+ while (Date.now() - startTime < timeout) {
527
+ const receipt = await this.getTransactionReceipt(txHash)
528
+
529
+ if (receipt) {
530
+ // Get current block for confirmation count
531
+ const currentBlock = await this.getBlockNumber()
532
+ const txConfirmations = currentBlock - receipt.blockNumber + 1
533
+
534
+ if (txConfirmations >= confirmations) {
535
+ // Update tracked transaction
536
+ const tracked = this.pendingTxs.get(txHash.toLowerCase())
537
+ if (tracked) {
538
+ tracked.status = receipt.status === 1 ? 'confirmed' : 'failed'
539
+ tracked.blockNumber = receipt.blockNumber
540
+ tracked.blockHash = receipt.blockHash
541
+ tracked.gasUsed = receipt.gasUsed
542
+ tracked.effectiveGasPrice = receipt.effectiveGasPrice
543
+ tracked.confirmations = txConfirmations
544
+ }
545
+
546
+ return receipt
547
+ }
548
+ }
549
+
550
+ // Wait before next poll
551
+ await this.sleep(pollInterval)
552
+ }
553
+
554
+ throw new Error(`Transaction ${txHash} not confirmed within ${timeout}ms`)
555
+ }
556
+
557
+ /**
558
+ * Get current block number
559
+ *
560
+ * @param options - Request options
561
+ * @returns Block number
562
+ */
563
+ async getBlockNumber(options?: RpcRequestOptions): Promise<number> {
564
+ const result = await this.rpcCall<HexString>('eth_blockNumber', [], options)
565
+ return parseInt(result, 16)
566
+ }
567
+
568
+ // ─── Call Methods ────────────────────────────────────────────────────────────
569
+
570
+ /**
571
+ * Execute a read-only call
572
+ *
573
+ * @param call - Call parameters
574
+ * @param blockTag - Block tag
575
+ * @param options - Request options
576
+ * @returns Call result
577
+ */
578
+ async call(
579
+ call: {
580
+ to: HexString
581
+ data: HexString
582
+ from?: HexString
583
+ value?: bigint
584
+ },
585
+ blockTag: 'latest' | 'pending' = 'latest',
586
+ options?: RpcRequestOptions
587
+ ): Promise<HexString> {
588
+ const params: Record<string, string> = {
589
+ to: call.to,
590
+ data: call.data,
591
+ }
592
+
593
+ if (call.from) {
594
+ params.from = call.from
595
+ }
596
+
597
+ if (call.value !== undefined) {
598
+ params.value = `0x${call.value.toString(16)}`
599
+ }
600
+
601
+ return await this.rpcCall<HexString>('eth_call', [params, blockTag], options)
602
+ }
603
+
604
+ /**
605
+ * Estimate gas for a transaction
606
+ *
607
+ * @param tx - Transaction parameters
608
+ * @param options - Request options
609
+ * @returns Estimated gas
610
+ */
611
+ async estimateGas(
612
+ tx: {
613
+ to: HexString
614
+ data?: HexString
615
+ from?: HexString
616
+ value?: bigint
617
+ },
618
+ options?: RpcRequestOptions
619
+ ): Promise<bigint> {
620
+ const params: Record<string, string> = {
621
+ to: tx.to,
622
+ }
623
+
624
+ if (tx.data) {
625
+ params.data = tx.data
626
+ }
627
+
628
+ if (tx.from) {
629
+ params.from = tx.from
630
+ }
631
+
632
+ if (tx.value !== undefined) {
633
+ params.value = `0x${tx.value.toString(16)}`
634
+ }
635
+
636
+ const result = await this.rpcCall<HexString>('eth_estimateGas', [params], options)
637
+ return BigInt(result)
638
+ }
639
+
640
+ // ─── Log Methods ─────────────────────────────────────────────────────────────
641
+
642
+ /**
643
+ * Get logs matching a filter
644
+ *
645
+ * @param filter - Log filter
646
+ * @param options - Request options
647
+ * @returns Matching logs
648
+ */
649
+ async getLogs(
650
+ filter: {
651
+ address?: HexString | HexString[]
652
+ topics?: (HexString | HexString[] | null)[]
653
+ fromBlock?: number | 'latest'
654
+ toBlock?: number | 'latest'
655
+ },
656
+ options?: RpcRequestOptions
657
+ ): Promise<
658
+ Array<{
659
+ address: HexString
660
+ topics: HexString[]
661
+ data: HexString
662
+ blockNumber: number
663
+ transactionHash: HexString
664
+ logIndex: number
665
+ }>
666
+ > {
667
+ const params: Record<string, unknown> = {}
668
+
669
+ if (filter.address) {
670
+ params.address = filter.address
671
+ }
672
+
673
+ if (filter.topics) {
674
+ params.topics = filter.topics
675
+ }
676
+
677
+ params.fromBlock =
678
+ typeof filter.fromBlock === 'number'
679
+ ? `0x${filter.fromBlock.toString(16)}`
680
+ : (filter.fromBlock ?? 'latest')
681
+
682
+ params.toBlock =
683
+ typeof filter.toBlock === 'number'
684
+ ? `0x${filter.toBlock.toString(16)}`
685
+ : (filter.toBlock ?? 'latest')
686
+
687
+ const result = await this.rpcCall<
688
+ Array<{
689
+ address: HexString
690
+ topics: HexString[]
691
+ data: HexString
692
+ blockNumber: HexString
693
+ transactionHash: HexString
694
+ logIndex: HexString
695
+ }>
696
+ >('eth_getLogs', [params], options)
697
+
698
+ return result.map((log) => ({
699
+ address: log.address,
700
+ topics: log.topics,
701
+ data: log.data,
702
+ blockNumber: parseInt(log.blockNumber, 16),
703
+ transactionHash: log.transactionHash,
704
+ logIndex: parseInt(log.logIndex, 16),
705
+ }))
706
+ }
707
+
708
+ // ─── Utility Methods ─────────────────────────────────────────────────────────
709
+
710
+ /**
711
+ * Get RPC URL
712
+ */
713
+ getRpcUrl(): string {
714
+ return this.rpcUrl
715
+ }
716
+
717
+ /**
718
+ * Get network
719
+ */
720
+ getNetwork(): EthereumNetwork {
721
+ return this.network
722
+ }
723
+
724
+ /**
725
+ * Get chain ID
726
+ */
727
+ getChainId(): number {
728
+ return this.chainId
729
+ }
730
+
731
+ /**
732
+ * Get tracked pending transactions
733
+ */
734
+ getPendingTransactions(): SubmittedTransaction[] {
735
+ return Array.from(this.pendingTxs.values()).filter(
736
+ (tx) => tx.status === 'submitted' || tx.status === 'pending'
737
+ )
738
+ }
739
+
740
+ /**
741
+ * Get transaction status
742
+ *
743
+ * @param txHash - Transaction hash
744
+ * @returns Transaction info or undefined
745
+ */
746
+ getTrackedTransaction(txHash: HexString): SubmittedTransaction | undefined {
747
+ return this.pendingTxs.get(txHash.toLowerCase())
748
+ }
749
+
750
+ /**
751
+ * Clear tracked transactions
752
+ */
753
+ clearTrackedTransactions(): void {
754
+ this.pendingTxs.clear()
755
+ }
756
+
757
+ /**
758
+ * Clear nonce cache
759
+ */
760
+ clearNonceCache(): void {
761
+ this.nonceCache.clear()
762
+ }
763
+
764
+ // ─── Private Methods ─────────────────────────────────────────────────────────
765
+
766
+ /**
767
+ * Make an RPC call
768
+ */
769
+ private async rpcCall<T>(
770
+ method: string,
771
+ params: unknown[],
772
+ options?: RpcRequestOptions
773
+ ): Promise<T> {
774
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT
775
+ const retries = options?.retries ?? DEFAULT_RETRIES
776
+ const retryDelay = options?.retryDelay ?? DEFAULT_RETRY_DELAY
777
+
778
+ let lastError: Error | undefined
779
+
780
+ for (let attempt = 0; attempt <= retries; attempt++) {
781
+ try {
782
+ const controller = new AbortController()
783
+ const timeoutId = setTimeout(() => controller.abort(), timeout)
784
+
785
+ const response = await fetch(this.rpcUrl, {
786
+ method: 'POST',
787
+ headers: {
788
+ 'Content-Type': 'application/json',
789
+ },
790
+ body: JSON.stringify({
791
+ jsonrpc: '2.0',
792
+ id: ++this.requestId,
793
+ method,
794
+ params,
795
+ }),
796
+ signal: controller.signal,
797
+ })
798
+
799
+ clearTimeout(timeoutId)
800
+
801
+ if (!response.ok) {
802
+ throw new Error(`HTTP error: ${response.status}`)
803
+ }
804
+
805
+ const json = (await response.json()) as {
806
+ result?: T
807
+ error?: { code: number; message: string }
808
+ }
809
+
810
+ if (json.error) {
811
+ throw new Error(`RPC error: ${json.error.message} (${json.error.code})`)
812
+ }
813
+
814
+ return json.result as T
815
+ } catch (error) {
816
+ lastError = error as Error
817
+
818
+ // Don't retry on abort
819
+ if (lastError.name === 'AbortError') {
820
+ throw new Error(`Request timeout after ${timeout}ms`)
821
+ }
822
+
823
+ // Wait before retry
824
+ if (attempt < retries) {
825
+ await this.sleep(retryDelay * (attempt + 1))
826
+ }
827
+ }
828
+ }
829
+
830
+ throw lastError ?? new Error('RPC call failed')
831
+ }
832
+
833
+ /**
834
+ * Get cached nonce
835
+ */
836
+ private getCachedNonce(address: HexString): number | undefined {
837
+ const key = address.toLowerCase()
838
+ const cached = this.nonceCache.get(key)
839
+
840
+ if (!cached || Date.now() - cached.lastUpdated > NONCE_CACHE_DURATION) {
841
+ return undefined
842
+ }
843
+
844
+ return cached.pending
845
+ }
846
+
847
+ /**
848
+ * Update nonce cache
849
+ */
850
+ private updateNonceCache(address: HexString, nonce: number): void {
851
+ const key = address.toLowerCase()
852
+ const cached = this.nonceCache.get(key)
853
+
854
+ if (cached) {
855
+ cached.current = nonce
856
+ if (cached.pending < nonce) {
857
+ cached.pending = nonce
858
+ }
859
+ cached.lastUpdated = Date.now()
860
+ } else {
861
+ this.nonceCache.set(key, {
862
+ current: nonce,
863
+ pending: nonce,
864
+ lastUpdated: Date.now(),
865
+ })
866
+ }
867
+ }
868
+
869
+ /**
870
+ * Sleep for a duration
871
+ */
872
+ private sleep(ms: number): Promise<void> {
873
+ return new Promise((resolve) => setTimeout(resolve, ms))
874
+ }
875
+ }
876
+
877
+ // ─── Factory Functions ────────────────────────────────────────────────────────
878
+
879
+ /**
880
+ * Create an RPC client for a network
881
+ *
882
+ * @param network - Target network
883
+ * @param rpcUrl - Optional custom RPC URL
884
+ * @returns RPC client
885
+ */
886
+ export function createRpcClient(
887
+ network: EthereumNetwork = 'mainnet',
888
+ rpcUrl?: string
889
+ ): EthereumRpcClient {
890
+ return new EthereumRpcClient(network, { rpcUrl })
891
+ }
892
+
893
+ /**
894
+ * Create a mainnet RPC client
895
+ */
896
+ export function createMainnetRpcClient(rpcUrl?: string): EthereumRpcClient {
897
+ return new EthereumRpcClient('mainnet', { rpcUrl })
898
+ }
899
+
900
+ /**
901
+ * Create a Sepolia testnet RPC client
902
+ */
903
+ export function createSepoliaRpcClient(rpcUrl?: string): EthereumRpcClient {
904
+ return new EthereumRpcClient('sepolia', { rpcUrl })
905
+ }