@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.
- 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 +48874 -18336
- package/dist/browser.mjs +674 -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-YWGJ77A2.mjs +33806 -0
- 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-DXh2IGkz.d.ts +24681 -0
- package/dist/index-DeE1ZzA4.d.mts +24681 -0
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +48676 -17318
- package/dist/index.mjs +583 -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 +276 -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 +201 -0
- 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 +402 -0
- package/src/chains/solana/providers/index.ts +85 -0
- package/src/chains/solana/providers/interface.ts +221 -0
- 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 +790 -0
- package/src/chains/solana/rpc-client.ts +1150 -0
- package/src/chains/solana/scan.ts +170 -73
- 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 +77 -7
- 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 +37 -0
- package/src/compliance/range-sas.ts +956 -0
- 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 +785 -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 +336 -0
- package/src/privacy-backends/interface.ts +906 -0
- 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-types.ts +278 -0
- package/src/privacy-backends/privacycash.ts +456 -0
- package/src/privacy-backends/private-swap.ts +570 -0
- package/src/privacy-backends/rate-limiter.ts +683 -0
- package/src/privacy-backends/registry.ts +690 -0
- package/src/privacy-backends/router.ts +626 -0
- package/src/privacy-backends/shadowwire.ts +449 -0
- package/src/privacy-backends/sip-native.ts +256 -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 +111 -30
- 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/surveillance/algorithms/address-reuse.ts +143 -0
- package/src/surveillance/algorithms/cluster.ts +247 -0
- package/src/surveillance/algorithms/exchange.ts +295 -0
- package/src/surveillance/algorithms/temporal.ts +337 -0
- package/src/surveillance/analyzer.ts +442 -0
- package/src/surveillance/index.ts +64 -0
- package/src/surveillance/scoring.ts +372 -0
- package/src/surveillance/types.ts +264 -0
- 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-3INS3PR5.mjs +0 -884
- package/dist/chunk-3OVABDRH.mjs +0 -17096
- package/dist/chunk-DLDWZFYC.mjs +0 -1495
- package/dist/chunk-E6SZWREQ.mjs +0 -57
- package/dist/chunk-G33LB27A.mjs +0 -16166
- package/dist/chunk-HGU6HZRC.mjs +0 -231
- package/dist/chunk-L2K34JCU.mjs +0 -1496
- package/dist/chunk-SN4ZDTVW.mjs +0 -16166
- package/dist/constants-VOI7BSLK.mjs +0 -27
- 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-xbWjohNq.d.mts +0 -11390
- package/dist/solana-5EMCTPTS.mjs +0 -46
- 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
|
+
}
|