@sip-protocol/sdk 0.7.3 → 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.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/{TransportWebUSB-TQ7WZ4LE.mjs → TransportWebUSB-YQMAGJAJ.mjs} +12 -9
- package/dist/browser.d.mts +10 -4
- package/dist/browser.d.ts +10 -4
- package/dist/browser.js +47556 -19603
- package/dist/browser.mjs +628 -48
- package/dist/chunk-4GRJ5MAW.mjs +152 -0
- package/dist/chunk-5D7A3L3W.mjs +717 -0
- package/dist/chunk-64AYA5F5.mjs +7834 -0
- package/dist/chunk-GMDGB22A.mjs +379 -0
- package/dist/chunk-I534WKN7.mjs +328 -0
- package/dist/chunk-IBZVA5Y7.mjs +1003 -0
- package/dist/chunk-PRRZAWJE.mjs +223 -0
- package/dist/{chunk-UJCSKKID.mjs → chunk-XGB3TDIC.mjs} +13 -1
- package/dist/{chunk-3M3HNQCW.mjs → chunk-YWGJ77A2.mjs} +28656 -13103
- package/dist/{chunk-6WGN57S2.mjs → chunk-Z3K7W5S3.mjs} +48 -0
- package/dist/constants-LHAAUC2T.mjs +51 -0
- package/dist/dist-2OGQ7FED.mjs +3957 -0
- package/dist/dist-IFHPYLDX.mjs +254 -0
- package/dist/fulfillment_proof-ANHVPKTB.mjs +21 -0
- package/dist/funding_proof-ICFZ5LHY.mjs +21 -0
- package/dist/{index-DIBZHOOQ.d.ts → index-DXh2IGkz.d.ts} +21239 -10304
- package/dist/{index-8MQz13eJ.d.mts → index-DeE1ZzA4.d.mts} +21239 -10304
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48396 -19623
- package/dist/index.mjs +537 -19
- package/dist/interface-Bf7w1PLW.d.mts +679 -0
- package/dist/interface-Bf7w1PLW.d.ts +679 -0
- package/dist/{noir-DKfEzWy9.d.mts → noir-kzbLVTei.d.mts} +31 -21
- package/dist/{noir-DKfEzWy9.d.ts → noir-kzbLVTei.d.ts} +31 -21
- package/dist/proofs/halo2.d.mts +151 -0
- package/dist/proofs/halo2.d.ts +151 -0
- package/dist/proofs/halo2.js +350 -0
- package/dist/proofs/halo2.mjs +11 -0
- package/dist/proofs/kimchi.d.mts +160 -0
- package/dist/proofs/kimchi.d.ts +160 -0
- package/dist/proofs/kimchi.js +431 -0
- package/dist/proofs/kimchi.mjs +13 -0
- package/dist/proofs/noir.d.mts +1 -1
- package/dist/proofs/noir.d.ts +1 -1
- package/dist/proofs/noir.js +74 -18
- package/dist/proofs/noir.mjs +84 -24
- package/dist/solana-U3MEGU7W.mjs +280 -0
- package/dist/validity_proof-3POXLPNY.mjs +21 -0
- package/package.json +54 -21
- package/src/adapters/index.ts +41 -0
- package/src/adapters/jupiter.ts +571 -0
- package/src/adapters/near-intents.ts +135 -0
- package/src/advisor/advisor.ts +653 -0
- package/src/advisor/index.ts +54 -0
- package/src/advisor/tools.ts +303 -0
- package/src/advisor/types.ts +164 -0
- package/src/chains/ethereum/announcement.ts +536 -0
- package/src/chains/ethereum/bnb-optimizations.ts +474 -0
- package/src/chains/ethereum/commitment.ts +522 -0
- package/src/chains/ethereum/constants.ts +462 -0
- package/src/chains/ethereum/deployment.ts +596 -0
- package/src/chains/ethereum/gas-estimation.ts +538 -0
- package/src/chains/ethereum/index.ts +268 -0
- package/src/chains/ethereum/optimizations.ts +614 -0
- package/src/chains/ethereum/privacy-adapter.ts +855 -0
- package/src/chains/ethereum/registry.ts +584 -0
- package/src/chains/ethereum/rpc.ts +905 -0
- package/src/chains/ethereum/stealth.ts +491 -0
- package/src/chains/ethereum/token.ts +790 -0
- package/src/chains/ethereum/transfer.ts +637 -0
- package/src/chains/ethereum/types.ts +456 -0
- package/src/chains/ethereum/viewing-key.ts +455 -0
- package/src/chains/near/commitment.ts +608 -0
- package/src/chains/near/constants.ts +284 -0
- package/src/chains/near/function-call.ts +871 -0
- package/src/chains/near/history.ts +654 -0
- package/src/chains/near/implicit-account.ts +840 -0
- package/src/chains/near/index.ts +393 -0
- package/src/chains/near/native-transfer.ts +658 -0
- package/src/chains/near/nep141.ts +775 -0
- package/src/chains/near/privacy-adapter.ts +889 -0
- package/src/chains/near/resolver.ts +971 -0
- package/src/chains/near/rpc.ts +1016 -0
- package/src/chains/near/stealth.ts +419 -0
- package/src/chains/near/types.ts +317 -0
- package/src/chains/near/viewing-key.ts +876 -0
- package/src/chains/solana/anchor-transfer.ts +386 -0
- package/src/chains/solana/commitment.ts +577 -0
- package/src/chains/solana/constants.ts +126 -12
- package/src/chains/solana/ephemeral-keys.ts +543 -0
- package/src/chains/solana/index.ts +252 -1
- package/src/chains/solana/key-derivation.ts +418 -0
- package/src/chains/solana/kit-compat.ts +334 -0
- package/src/chains/solana/optimizations.ts +560 -0
- package/src/chains/solana/privacy-adapter.ts +605 -0
- package/src/chains/solana/providers/generic.ts +47 -6
- package/src/chains/solana/providers/helius-enhanced-types.ts +336 -0
- package/src/chains/solana/providers/helius-enhanced.ts +623 -0
- package/src/chains/solana/providers/helius.ts +186 -33
- package/src/chains/solana/providers/index.ts +31 -0
- package/src/chains/solana/providers/interface.ts +61 -18
- package/src/chains/solana/providers/quicknode.ts +409 -0
- package/src/chains/solana/providers/triton.ts +426 -0
- package/src/chains/solana/providers/webhook.ts +338 -67
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +83 -66
- package/src/chains/solana/sol-transfer.ts +732 -0
- package/src/chains/solana/spl-transfer.ts +886 -0
- package/src/chains/solana/stealth-scanner.ts +703 -0
- package/src/chains/solana/sunspot-verifier.ts +453 -0
- package/src/chains/solana/transaction-builder.ts +755 -0
- package/src/chains/solana/transfer.ts +74 -5
- package/src/chains/solana/types.ts +57 -6
- package/src/chains/solana/utils.ts +110 -0
- package/src/chains/solana/viewing-key.ts +807 -0
- package/src/compliance/fireblocks.ts +921 -0
- package/src/compliance/index.ts +23 -0
- package/src/compliance/range-sas.ts +398 -33
- package/src/config/endpoints.ts +100 -0
- package/src/crypto.ts +11 -8
- package/src/errors.ts +82 -0
- package/src/evm/erc4337-relayer.ts +830 -0
- package/src/evm/index.ts +47 -0
- package/src/fees/calculator.ts +396 -0
- package/src/fees/index.ts +87 -0
- package/src/fees/near-contract.ts +429 -0
- package/src/fees/types.ts +268 -0
- package/src/index.ts +686 -1
- package/src/intent.ts +6 -3
- package/src/logger.ts +324 -0
- package/src/network/index.ts +80 -0
- package/src/network/proxy.ts +691 -0
- package/src/optimizations/index.ts +541 -0
- package/src/oracle/types.ts +1 -0
- package/src/privacy-backends/arcium-types.ts +727 -0
- package/src/privacy-backends/arcium.ts +719 -0
- package/src/privacy-backends/combined-privacy.ts +866 -0
- package/src/privacy-backends/cspl-token.ts +595 -0
- package/src/privacy-backends/cspl-types.ts +512 -0
- package/src/privacy-backends/cspl.ts +907 -0
- package/src/privacy-backends/health.ts +488 -0
- package/src/privacy-backends/inco-types.ts +323 -0
- package/src/privacy-backends/inco.ts +616 -0
- package/src/privacy-backends/index.ts +254 -4
- package/src/privacy-backends/interface.ts +649 -6
- package/src/privacy-backends/lru-cache.ts +343 -0
- package/src/privacy-backends/magicblock.ts +458 -0
- package/src/privacy-backends/mock.ts +258 -0
- package/src/privacy-backends/privacycash.ts +13 -17
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +414 -2
- package/src/privacy-backends/router.ts +283 -3
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +3 -0
- package/src/privacy-logger.ts +191 -0
- package/src/production-safety.ts +373 -0
- package/src/proofs/aggregator.ts +1029 -0
- package/src/proofs/browser-composer.ts +1150 -0
- package/src/proofs/browser.ts +113 -25
- package/src/proofs/cache/index.ts +127 -0
- package/src/proofs/cache/interface.ts +545 -0
- package/src/proofs/cache/key-generator.ts +188 -0
- package/src/proofs/cache/lru-cache.ts +481 -0
- package/src/proofs/cache/multi-tier-cache.ts +575 -0
- package/src/proofs/cache/persistent-cache.ts +788 -0
- package/src/proofs/compliance-proof.ts +872 -0
- package/src/proofs/composer/base.ts +923 -0
- package/src/proofs/composer/index.ts +25 -0
- package/src/proofs/composer/interface.ts +518 -0
- package/src/proofs/composer/types.ts +383 -0
- package/src/proofs/converters/halo2.ts +452 -0
- package/src/proofs/converters/index.ts +208 -0
- package/src/proofs/converters/interface.ts +363 -0
- package/src/proofs/converters/kimchi.ts +462 -0
- package/src/proofs/converters/noir.ts +451 -0
- package/src/proofs/fallback.ts +888 -0
- package/src/proofs/halo2.ts +42 -0
- package/src/proofs/index.ts +471 -0
- package/src/proofs/interface.ts +13 -0
- package/src/proofs/kimchi.ts +42 -0
- package/src/proofs/lazy.ts +1004 -0
- package/src/proofs/mock.ts +25 -1
- package/src/proofs/noir.ts +110 -29
- package/src/proofs/orchestrator.ts +960 -0
- package/src/proofs/parallel/concurrency.ts +297 -0
- package/src/proofs/parallel/dependency-graph.ts +602 -0
- package/src/proofs/parallel/executor.ts +420 -0
- package/src/proofs/parallel/index.ts +131 -0
- package/src/proofs/parallel/interface.ts +685 -0
- package/src/proofs/parallel/worker-pool.ts +644 -0
- package/src/proofs/providers/halo2.ts +560 -0
- package/src/proofs/providers/index.ts +34 -0
- package/src/proofs/providers/kimchi.ts +641 -0
- package/src/proofs/validator.ts +881 -0
- package/src/proofs/verifier.ts +867 -0
- package/src/quantum/index.ts +112 -0
- package/src/quantum/winternitz-vault.ts +639 -0
- package/src/quantum/wots.ts +611 -0
- package/src/settlement/backends/direct-chain.ts +1 -0
- package/src/settlement/index.ts +9 -0
- package/src/settlement/router.ts +732 -46
- package/src/solana/index.ts +72 -0
- package/src/solana/jito-relayer.ts +687 -0
- package/src/solana/noir-verifier-types.ts +430 -0
- package/src/solana/noir-verifier.ts +816 -0
- package/src/stealth/address-derivation.ts +193 -0
- package/src/stealth/ed25519.ts +431 -0
- package/src/stealth/index.ts +233 -0
- package/src/stealth/meta-address.ts +221 -0
- package/src/stealth/secp256k1.ts +368 -0
- package/src/stealth/utils.ts +194 -0
- package/src/stealth.ts +50 -1504
- package/src/sync/index.ts +106 -0
- package/src/sync/manager.ts +504 -0
- package/src/sync/mock-provider.ts +318 -0
- package/src/sync/oblivious.ts +625 -0
- package/src/tokens/index.ts +15 -0
- package/src/tokens/registry.ts +301 -0
- package/src/utils/deprecation.ts +94 -0
- package/src/utils/index.ts +9 -0
- package/src/wallet/ethereum/index.ts +68 -0
- package/src/wallet/ethereum/metamask-privacy.ts +420 -0
- package/src/wallet/ethereum/multi-wallet.ts +646 -0
- package/src/wallet/ethereum/privacy-adapter.ts +700 -0
- package/src/wallet/ethereum/types.ts +3 -1
- package/src/wallet/ethereum/walletconnect-adapter.ts +675 -0
- package/src/wallet/hardware/index.ts +10 -0
- package/src/wallet/hardware/ledger-privacy.ts +414 -0
- package/src/wallet/index.ts +71 -0
- package/src/wallet/near/adapter.ts +626 -0
- package/src/wallet/near/index.ts +86 -0
- package/src/wallet/near/meteor-wallet.ts +1153 -0
- package/src/wallet/near/my-near-wallet.ts +790 -0
- package/src/wallet/near/wallet-selector.ts +702 -0
- package/src/wallet/solana/adapter.ts +6 -4
- package/src/wallet/solana/index.ts +13 -0
- package/src/wallet/solana/privacy-adapter.ts +567 -0
- package/src/wallet/sui/types.ts +6 -4
- package/src/zcash/rpc-client.ts +13 -6
- package/dist/chunk-2XIVXWHA.mjs +0 -1930
- package/dist/chunk-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-7RFRWDCW.mjs +0 -1504
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-F6F73W35.mjs +0 -16166
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-OFDBEIEK.mjs +0 -16166
- package/dist/chunk-SF7YSLF5.mjs +0 -1515
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/chunk-WWUSGOXE.mjs +0 -17129
- package/dist/constants-VOI7BSLK.mjs +0 -27
- package/dist/index-B71aXVzk.d.ts +0 -13264
- package/dist/index-BYZbDjal.d.ts +0 -11390
- package/dist/index-CHB3KuOB.d.mts +0 -11859
- package/dist/index-CzWPI6Le.d.ts +0 -11859
- package/dist/index-pOIIuwfV.d.mts +0 -13264
- package/dist/index-xbWjohNq.d.mts +0 -11390
- package/dist/solana-4O4K45VU.mjs +0 -46
- package/dist/solana-5EMCTPTS.mjs +0 -46
- package/dist/solana-NDABAZ6P.mjs +0 -56
- package/dist/solana-Q4NAVBTS.mjs +0 -46
- 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
|
+
}
|