@@ -7,70 +7,202 @@ use std::io::Write;
7
7
use std:: str:: FromStr ;
8
8
9
9
fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
10
- // Configure these based on your needs
11
- let rpc_url = std:: env:: var ( "RPC_URL" ) . unwrap_or_else ( |_| "http://localhost:8899" . to_string ( ) ) ;
10
+ let args: Vec < String > = std:: env:: args ( ) . collect ( ) ;
12
11
13
- // Replace these with actual addresses you want to fetch
14
- // You can pass them as command line arguments or environment variables
15
- let addresses: Vec < & str > = vec ! [
16
- // Add your addresses here, e.g.:
17
- // "11111111111111111111111111111111",
18
- // "22222222222222222222222222222222",
19
- ] ;
20
-
21
- if addresses. is_empty ( ) {
22
- println ! ( "Please add addresses to fetch in the source code or pass them as arguments" ) ;
23
- println ! ( "Usage: cargo run --bin fetch_rpc <address1> <address2> ..." ) ;
24
-
25
- // Check for command line arguments
26
- let args: Vec < String > = std:: env:: args ( ) . collect ( ) ;
27
- if args. len ( ) > 1 {
28
- fetch_accounts_from_args ( & rpc_url, & args[ 1 ..] ) ?;
29
- }
12
+ if args. len ( ) < 2 {
13
+ print_usage ( ) ;
30
14
return Ok ( ( ) ) ;
31
15
}
32
16
33
- let client = RpcClient :: new ( rpc_url. clone ( ) ) ;
34
- println ! ( "Connected to RPC: {}" , rpc_url) ;
17
+ // Get RPC URL - can be set via environment variable or use predefined networks
18
+ let rpc_url = get_rpc_url ( ) ;
19
+ println ! ( "Using RPC: {}" , rpc_url) ;
20
+
21
+ let client = RpcClient :: new ( rpc_url) ;
22
+
23
+ // Check if we should process as lookup tables
24
+ let is_lut = std:: env:: var ( "IS_LUT" ) . unwrap_or_default ( ) == "true" ;
35
25
36
- for address_str in addresses {
37
- fetch_and_save_account ( & client, address_str) ?;
26
+ // Fetch all addresses provided as command line arguments
27
+ for address_str in & args[ 1 ..] {
28
+ if is_lut {
29
+ fetch_and_process_lut ( & client, address_str) ?;
30
+ } else {
31
+ fetch_and_save_account ( & client, address_str) ?;
32
+ }
38
33
}
39
34
35
+ println ! ( "✅ Finished processing {} accounts" , args. len( ) - 1 ) ;
40
36
Ok ( ( ) )
41
37
}
42
38
43
- fn fetch_accounts_from_args ( rpc_url : & str , addresses : & [ String ] ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
44
- let client = RpcClient :: new ( rpc_url. to_string ( ) ) ;
45
- println ! ( "Connected to RPC: {}" , rpc_url) ;
39
+ fn get_rpc_url ( ) -> String {
40
+ // Check for custom RPC_URL environment variable first
41
+ if let Ok ( custom_url) = std:: env:: var ( "RPC_URL" ) {
42
+ return custom_url;
43
+ }
44
+
45
+ // Check for NETWORK environment variable for predefined networks
46
+ match std:: env:: var ( "NETWORK" ) . as_deref ( ) {
47
+ Ok ( "mainnet" ) => "https://api.mainnet-beta.solana.com" . to_string ( ) ,
48
+ Ok ( "devnet" ) => "https://api.devnet.solana.com" . to_string ( ) ,
49
+ Ok ( "testnet" ) => "https://api.testnet.solana.com" . to_string ( ) ,
50
+ Ok ( "localnet" ) | Ok ( "local" ) => "http://localhost:8899" . to_string ( ) ,
51
+ _ => "http://localhost:8899" . to_string ( ) , // default to localnet
52
+ }
53
+ }
54
+
55
+ fn print_usage ( ) {
56
+ println ! ( "Account Fetcher - Fetch Solana accounts and save as JSON" ) ;
57
+ println ! ( ) ;
58
+ println ! ( "USAGE:" ) ;
59
+ println ! ( " cargo run --bin fetch_rpc <pubkey1> <pubkey2> ..." ) ;
60
+ println ! ( ) ;
61
+ println ! ( "NETWORKS:" ) ;
62
+ println ! ( " Set NETWORK environment variable:" ) ;
63
+ println ! ( " NETWORK=mainnet - Solana Mainnet" ) ;
64
+ println ! ( " NETWORK=devnet - Solana Devnet" ) ;
65
+ println ! ( " NETWORK=testnet - Solana Testnet" ) ;
66
+ println ! ( " NETWORK=local - Local validator (default)" ) ;
67
+ println ! ( ) ;
68
+ println ! ( " Or set custom RPC_URL:" ) ;
69
+ println ! ( " RPC_URL=https://your-custom-rpc.com" ) ;
70
+ println ! ( ) ;
71
+ println ! ( "LOOKUP TABLE MODE:" ) ;
72
+ println ! ( " Set IS_LUT=true to decode/modify lookup tables:" ) ;
73
+ println ! ( " IS_LUT=true NETWORK=mainnet cargo run --bin fetch_rpc <lut_pubkey>" ) ;
74
+ println ! ( ) ;
75
+ println ! ( "EXAMPLES:" ) ;
76
+ println ! ( " # Fetch from mainnet" ) ;
77
+ println ! ( " NETWORK=mainnet cargo run --bin fetch_rpc 11111111111111111111111111111111" ) ;
78
+ println ! ( ) ;
79
+ println ! ( " # Fetch multiple accounts from devnet" ) ;
80
+ println ! ( " NETWORK=devnet cargo run --bin fetch_rpc \\ " ) ;
81
+ println ! ( " 11111111111111111111111111111111 \\ " ) ;
82
+ println ! ( " TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ) ;
83
+ println ! ( ) ;
84
+ println ! ( " # Process lookup table" ) ;
85
+ println ! ( " IS_LUT=true NETWORK=mainnet cargo run --bin fetch_rpc <lut_pubkey>" ) ;
86
+ }
87
+
88
+ fn fetch_and_process_lut ( client : & RpcClient , address_str : & str ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
89
+ let pubkey = Pubkey :: from_str ( address_str) ?;
90
+ println ! ( "📥 Fetching lookup table: {}" , pubkey) ;
46
91
47
- for address_str in addresses {
48
- fetch_and_save_account ( & client, address_str) ?;
92
+ match client. get_account ( & pubkey) {
93
+ Ok ( account) => {
94
+ println ! ( "✅ Fetched LUT: {} bytes" , account. data. len( ) ) ;
95
+
96
+ // Decode the lookup table
97
+ let modified_data = decode_and_modify_lut ( & account. data ) ?;
98
+
99
+ let filename = format ! ( "modified_lut_{}.json" , pubkey) ;
100
+
101
+ // Create JSON with modified data
102
+ let data_base64 = encode ( & modified_data) ;
103
+ let json_obj = json ! ( {
104
+ "pubkey" : pubkey. to_string( ) ,
105
+ "account" : {
106
+ "lamports" : account. lamports,
107
+ "data" : [ data_base64, "base64" ] ,
108
+ "owner" : account. owner. to_string( ) ,
109
+ "executable" : account. executable,
110
+ "rentEpoch" : account. rent_epoch,
111
+ "space" : modified_data. len( ) ,
112
+ }
113
+ } ) ;
114
+
115
+ let mut file = File :: create ( & filename) ?;
116
+ file. write_all ( json_obj. to_string ( ) . as_bytes ( ) ) ?;
117
+
118
+ println ! ( "✅ Modified LUT saved: {} (last_extended_slot set to 0)" , filename) ;
119
+ }
120
+ Err ( e) => {
121
+ println ! ( "❌ Error fetching LUT {}: {}" , pubkey, e) ;
122
+ }
49
123
}
50
124
51
125
Ok ( ( ) )
52
126
}
53
127
128
+ fn decode_and_modify_lut ( data : & [ u8 ] ) -> Result < Vec < u8 > , Box < dyn std:: error:: Error > > {
129
+ if data. len ( ) < 56 {
130
+ return Err ( "LUT data too small" . into ( ) ) ;
131
+ }
132
+
133
+ let mut modified_data = data. to_vec ( ) ;
134
+
135
+ // Based on Solana's AddressLookupTable structure:
136
+ // - discriminator: u32 (4 bytes) - should be 1 for LookupTable
137
+ // - deactivation_slot: u64 (8 bytes) - at offset 4
138
+ // - last_extended_slot: u64 (8 bytes) - at offset 12 *** THIS IS WHAT WE MODIFY ***
139
+ // - last_extended_slot_start_index: u8 (1 byte) - at offset 20
140
+ // - authority: Option<Pubkey> (33 bytes max) - at offset 21
141
+ // - _padding: u16 (2 bytes)
142
+ // - addresses follow...
143
+
144
+ // Verify this is a lookup table (discriminator should be 1)
145
+ let discriminator = u32:: from_le_bytes ( [ data[ 0 ] , data[ 1 ] , data[ 2 ] , data[ 3 ] ] ) ;
146
+ if discriminator != 1 {
147
+ return Err ( format ! ( "Not a lookup table account (discriminator: {})" , discriminator) . into ( ) ) ;
148
+ }
149
+
150
+ println ! ( "📊 LUT Analysis:" ) ;
151
+
152
+ // Read current values for logging
153
+ let deactivation_slot = u64:: from_le_bytes ( [
154
+ data[ 4 ] , data[ 5 ] , data[ 6 ] , data[ 7 ] , data[ 8 ] , data[ 9 ] , data[ 10 ] , data[ 11 ]
155
+ ] ) ;
156
+ let current_last_extended_slot = u64:: from_le_bytes ( [
157
+ data[ 12 ] , data[ 13 ] , data[ 14 ] , data[ 15 ] , data[ 16 ] , data[ 17 ] , data[ 18 ] , data[ 19 ]
158
+ ] ) ;
159
+ let last_extended_slot_start_index = data[ 20 ] ;
160
+
161
+ println ! ( " Discriminator: {}" , discriminator) ;
162
+ println ! ( " Deactivation slot: {}" , deactivation_slot) ;
163
+ println ! ( " Current last_extended_slot: {}" , current_last_extended_slot) ;
164
+ println ! ( " Last extended slot start index: {}" , last_extended_slot_start_index) ;
165
+
166
+ // Check authority (1 byte for Some/None + potentially 32 bytes for pubkey)
167
+ let has_authority = data[ 21 ] == 1 ;
168
+ if has_authority && data. len ( ) >= 54 {
169
+ let authority_bytes = & data[ 22 ..54 ] ;
170
+ let authority = Pubkey :: try_from ( authority_bytes) ?;
171
+ println ! ( " Authority: {}" , authority) ;
172
+ } else {
173
+ println ! ( " Authority: None" ) ;
174
+ }
175
+
176
+ // Modify last_extended_slot to 0 (at offset 12, 8 bytes)
177
+ let zero_bytes = 0u64 . to_le_bytes ( ) ;
178
+ modified_data[ 12 ..20 ] . copy_from_slice ( & zero_bytes) ;
179
+
180
+ println ! ( "🔧 Modified last_extended_slot: {} -> 0" , current_last_extended_slot) ;
181
+
182
+ // Calculate number of addresses
183
+ let addresses_start = 56 ; // LOOKUP_TABLE_META_SIZE
184
+ if data. len ( ) > addresses_start {
185
+ let addresses_data_len = data. len ( ) - addresses_start;
186
+ let num_addresses = addresses_data_len / 32 ; // Each address is 32 bytes
187
+ println ! ( " Number of addresses: {}" , num_addresses) ;
188
+ }
189
+
190
+ Ok ( modified_data)
191
+ }
192
+
54
193
fn fetch_and_save_account ( client : & RpcClient , address_str : & str ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
55
194
let pubkey = Pubkey :: from_str ( address_str) ?;
56
- println ! ( "Fetching account: {}" , pubkey) ;
195
+ println ! ( "📥 Fetching account: {}" , pubkey) ;
57
196
58
197
match client. get_account ( & pubkey) {
59
198
Ok ( account) => {
60
- println ! ( "Fetched account: {} bytes" , account. data. len( ) ) ;
61
-
62
199
let filename = format ! ( "account_{}.json" , pubkey) ;
63
200
write_account_json ( & account, & pubkey, & filename) ?;
64
201
65
- println ! ( "Saved to: {}" , filename) ;
66
- println ! ( " Lamports: {}" , account. lamports) ;
67
- println ! ( " Owner: {}" , account. owner) ;
68
- println ! ( " Executable: {}" , account. executable) ;
69
- println ! ( " Data length: {}" , account. data. len( ) ) ;
70
- println ! ( ) ;
202
+ println ! ( "✅ Saved: {} ({} bytes, {} lamports)" , filename, account. data. len( ) , account. lamports) ;
71
203
}
72
204
Err ( e) => {
73
- println ! ( "Error fetching account {}: {:? }" , pubkey, e) ;
205
+ println ! ( "❌ Error fetching {}: {}" , pubkey, e) ;
74
206
}
75
207
}
76
208
0 commit comments