11use anyhow:: anyhow;
22use solana_cli_config:: Config ;
3- use solana_client:: rpc_client:: RpcClient ;
3+ use solana_client:: {
4+ rpc_client:: RpcClient ,
5+ rpc_config:: RpcProgramAccountsConfig ,
6+ rpc_filter:: { Memcmp , RpcFilterType } ,
7+ } ;
48use std:: {
59 io:: { self , Read , Write } ,
610 str:: FromStr ,
@@ -12,11 +16,38 @@ use solana_sdk::{
1216 system_program, transaction:: Transaction ,
1317} ;
1418
19+ use solana_account_decoder:: UiAccountEncoding ;
20+ use solana_sdk:: commitment_config:: { CommitmentConfig , CommitmentLevel } ;
21+
1522use crate :: api:: get_last_deployed_slot;
1623
1724const OTTER_VERIFY_PROGRAMID : & str = "verifycLy8mB96wd9wqq3WDXQwM4oU6r42Th37Db9fC" ;
1825const OTTER_SIGNER : & str = "9VWiUUhgNoRwTH5NVehYJEDwcotwYX3VgW4MChiHPAqU" ;
1926
27+ #[ derive( BorshDeserialize , BorshSerialize , Debug ) ]
28+ pub struct OtterBuildParams {
29+ pub address : Pubkey ,
30+ pub signer : Pubkey ,
31+ pub version : String ,
32+ pub git_url : String ,
33+ pub commit : String ,
34+ pub args : Vec < String > ,
35+ pub deployed_slot : u64 ,
36+ bump : u8 ,
37+ }
38+ impl std:: fmt:: Display for OtterBuildParams {
39+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
40+ writeln ! ( f, "Program Id: {}" , self . address) ?;
41+ writeln ! ( f, "Signer: {}" , self . signer) ?;
42+ writeln ! ( f, "Git Url: {}" , self . git_url) ?;
43+ writeln ! ( f, "Commit: {}" , self . commit) ?;
44+ writeln ! ( f, "Deployed Slot: {}" , self . deployed_slot) ?;
45+ writeln ! ( f, "Args: {:?}" , self . args) ?;
46+ writeln ! ( f, "Version: {}" , self . version) ?;
47+ Ok ( ( ) )
48+ }
49+ }
50+
2051pub fn prompt_user_input ( message : & str ) -> bool {
2152 let mut buffer = [ 0 ; 1 ] ;
2253 print ! ( "{}" , message) ;
@@ -60,19 +91,18 @@ fn create_ix_data(params: &InputParams, ix: &OtterVerifyInstructions) -> Vec<u8>
6091 data
6192}
6293
94+ fn get_keypair_from_path ( path : & str ) -> anyhow:: Result < Keypair > {
95+ solana_clap_utils:: keypair:: keypair_from_path ( & Default :: default ( ) , & path, "keypair" , false )
96+ . map_err ( |err| anyhow ! ( "Unable to get signer from path: {}" , err) )
97+ }
98+
6399fn get_user_config ( ) -> anyhow:: Result < ( Keypair , RpcClient ) > {
64100 let config_file = solana_cli_config:: CONFIG_FILE
65101 . as_ref ( )
66102 . ok_or_else ( || anyhow ! ( "Unable to get config file path" ) ) ?;
67103 let cli_config: Config = Config :: load ( config_file) ?;
68104
69- let signer = solana_clap_utils:: keypair:: keypair_from_path (
70- & Default :: default ( ) ,
71- & cli_config. keypair_path ,
72- "keypair" ,
73- false ,
74- )
75- . map_err ( |err| anyhow ! ( "Unable to get signer from path: {}" , err) ) ?;
105+ let signer = get_keypair_from_path ( & cli_config. keypair_path ) ?;
76106
77107 let rpc_client = RpcClient :: new ( cli_config. json_rpc_url . clone ( ) ) ;
78108 Ok ( ( signer, rpc_client) )
@@ -84,9 +114,14 @@ fn process_otter_verify_ixs(
84114 program_address : Pubkey ,
85115 instruction : OtterVerifyInstructions ,
86116 rpc_client : RpcClient ,
117+ path_to_keypair : Option < String > ,
87118) -> anyhow:: Result < ( ) > {
88119 let user_config = get_user_config ( ) ?;
89- let signer = user_config. 0 ;
120+ let signer = if let Some ( path_to_keypair) = path_to_keypair {
121+ get_keypair_from_path ( & path_to_keypair) ?
122+ } else {
123+ user_config. 0
124+ } ;
90125 let signer_pubkey = signer. pubkey ( ) ;
91126 let connection = rpc_client;
92127
@@ -135,15 +170,23 @@ pub async fn upload_program(
135170 program_address : Pubkey ,
136171 connection_url : Option < String > ,
137172 skip_prompt : bool ,
173+ path_to_keypair : Option < String > ,
138174) -> anyhow:: Result < ( ) > {
139- if skip_prompt || prompt_user_input (
140- "Do you want to upload the program verification to the Solana Blockchain? (y/n) " ,
141- ) {
175+ if skip_prompt
176+ || prompt_user_input (
177+ "Do you want to upload the program verification to the Solana Blockchain? (y/n) " ,
178+ )
179+ {
142180 println ! ( "Uploading the program verification params to the Solana blockchain..." ) ;
143181
144182 let cli_config = get_user_config ( ) ?;
145183
146- let signer_pubkey = cli_config. 0 . pubkey ( ) ;
184+ let signer_pubkey: Pubkey = if let Some ( ref path_to_keypair) = path_to_keypair {
185+ get_keypair_from_path ( & path_to_keypair) ?. pubkey ( )
186+ } else {
187+ cli_config. 0 . pubkey ( )
188+ } ;
189+
147190 let connection = match connection_url. as_deref ( ) {
148191 Some ( "m" ) => RpcClient :: new ( "https://api.mainnet-beta.solana.com" ) ,
149192 Some ( "d" ) => RpcClient :: new ( "https://api.devnet.solana.com" ) ,
@@ -194,9 +237,10 @@ pub async fn upload_program(
194237 program_address,
195238 OtterVerifyInstructions :: Update ,
196239 connection,
240+ path_to_keypair,
197241 ) ?;
198242 } else if connection. get_account ( & pda_account_2) . is_ok ( ) {
199- let wanna_create_new_pda = prompt_user_input (
243+ let wanna_create_new_pda = skip_prompt || prompt_user_input (
200244 "Program already uploaded by another signer. Do you want to upload a new program? (Y/n)"
201245 ) ;
202246 if wanna_create_new_pda {
@@ -206,6 +250,7 @@ pub async fn upload_program(
206250 program_address,
207251 OtterVerifyInstructions :: Initialize ,
208252 connection,
253+ path_to_keypair,
209254 ) ?;
210255 }
211256 return Ok ( ( ) ) ;
@@ -217,6 +262,7 @@ pub async fn upload_program(
217262 program_address,
218263 OtterVerifyInstructions :: Initialize ,
219264 connection,
265+ path_to_keypair,
220266 ) ?;
221267 }
222268 } else {
@@ -260,12 +306,54 @@ pub async fn process_close(program_address: Pubkey) -> anyhow::Result<()> {
260306 program_address,
261307 OtterVerifyInstructions :: Close ,
262308 connection,
309+ None ,
263310 ) ?;
264311 } else {
265312 return Err ( anyhow ! (
266- "Program account does not exist. Please provide the program address not PDA address."
313+ "No PDA found for signer {:?} and program address {:?}. Make sure you are providing the program address, not the PDA address. Check that a signer exists for the program by running `solana-verify list-program-pdas --program-id {:?}`" ,
314+ signer_pubkey,
315+ program_address,
316+ program_address
267317 ) ) ;
268318 }
269319
270320 Ok ( ( ) )
271321}
322+
323+ pub async fn get_all_pdas_available (
324+ client : & RpcClient ,
325+ program_id_pubkey : & Pubkey ,
326+ ) -> anyhow:: Result < Vec < ( Pubkey , OtterBuildParams ) > > {
327+ let filter = vec ! [ RpcFilterType :: Memcmp ( Memcmp :: new_base58_encoded(
328+ 8 ,
329+ & program_id_pubkey. to_bytes( ) ,
330+ ) ) ] ;
331+
332+ let config = RpcProgramAccountsConfig {
333+ filters : Some ( filter) ,
334+ account_config : solana_client:: rpc_config:: RpcAccountInfoConfig {
335+ encoding : Some ( UiAccountEncoding :: Base64 ) ,
336+ data_slice : None ,
337+ commitment : Some ( CommitmentConfig {
338+ commitment : CommitmentLevel :: Confirmed ,
339+ } ) ,
340+ min_context_slot : None ,
341+ } ,
342+ with_context : None ,
343+ } ;
344+
345+ let accounts = client. get_program_accounts_with_config (
346+ & Pubkey :: from_str ( OTTER_VERIFY_PROGRAMID ) . unwrap ( ) ,
347+ config,
348+ ) ?;
349+
350+ let mut pdas = vec ! [ ] ;
351+ for account in accounts {
352+ let otter_build_params = OtterBuildParams :: try_from_slice ( & account. 1 . data [ 8 ..] ) ;
353+ if let Ok ( otter_build_params) = otter_build_params {
354+ pdas. push ( ( account. 0 , otter_build_params) ) ;
355+ }
356+ }
357+
358+ Ok ( pdas)
359+ }
0 commit comments