use crate::cli::authority::Authority;
use crate::cli::output::{emit, progress, OutputMode};
use solana_client::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::read_keypair_file;
use solana_sdk::signer::Signer;
use spherenet_program_whitelist_client::instructions::{
ApproveWhitelistEntryBuilder, RejectWhitelistEntryBuilder, RemoveWhitelistEntryBuilder,
RequestWhitelistEntryBuilder,
};
use spherenet_program_whitelist_interface::{
account_solana, program_solana,
state::{load, whitelist_entry::ProgramWhitelistEntry, EntryState},
};
use std::sync::LazyLock;
pub static SYSTEM_PROGRAM: LazyLock<Pubkey> = LazyLock::new(Pubkey::default);
pub fn derive_whitelist_entry(deployer_authority: &Pubkey) -> (Pubkey, u8) {
let whitelist_pubkey = Pubkey::from(account_solana::id().to_bytes());
let program_id = Pubkey::from(program_solana::id().to_bytes());
Pubkey::find_program_address(
&[whitelist_pubkey.as_ref(), deployer_authority.as_ref()],
&program_id,
)
}
pub fn request(
rpc_url: &str,
deploy_authority_keypair: String,
authority: Authority,
mode: OutputMode,
) -> eyre::Result<()> {
let rpc_client = RpcClient::new(rpc_url);
let deploy_authority = read_keypair_file(&deploy_authority_keypair).map_err(|e| {
eyre::eyre!(
"Failed to load deploy authority keypair from {}: {}",
deploy_authority_keypair,
e
)
})?;
let deploy_authority_pubkey = deploy_authority.pubkey();
let (whitelist_entry_pda, _bump) = derive_whitelist_entry(&deploy_authority_pubkey);
let whitelist_pubkey = Pubkey::from(account_solana::id().to_bytes());
let payer = authority.instruction_authority_pubkey()?;
progress("Requesting program whitelist entry:");
progress(format!("Deploy Authority: {}", deploy_authority_pubkey));
progress(format!("Whitelist Entry PDA: {}", whitelist_entry_pda));
progress(format!("Payer: {}", payer));
let instruction = RequestWhitelistEntryBuilder::new()
.payer(payer)
.deploy_authority(deploy_authority_pubkey)
.whitelist_account(whitelist_pubkey)
.whitelist_entry_account(whitelist_entry_pda)
.system_program(*SYSTEM_PROGRAM)
.instruction();
let description = format!(
"Request program whitelist entry for {}",
deploy_authority_pubkey
);
let result = authority.execute_instruction_with_cosigners(
&rpc_client,
instruction,
&[&deploy_authority],
&description,
)?;
emit(&result, mode)
}
pub fn approve(
rpc_url: &str,
deploy_authority: String,
authority: Authority,
mode: OutputMode,
) -> eyre::Result<()> {
let rpc_client = RpcClient::new(rpc_url);
let deploy_authority_pubkey = deploy_authority
.parse::<Pubkey>()
.map_err(|e| eyre::eyre!("Invalid deploy authority: {}", e))?;
let (whitelist_entry_pda, _bump) = derive_whitelist_entry(&deploy_authority_pubkey);
let whitelist_pubkey = Pubkey::from(account_solana::id().to_bytes());
let instruction_authority = authority.instruction_authority_pubkey()?;
progress("Approving deployer whitelist entry:");
progress(format!("Deploy Authority: {}", deploy_authority_pubkey));
progress(format!("Whitelist Entry PDA: {}", whitelist_entry_pda));
progress(format!("Whitelist Authority: {}", instruction_authority));
let instruction = ApproveWhitelistEntryBuilder::new()
.payer(instruction_authority)
.whitelist_authority(instruction_authority)
.whitelist_account(whitelist_pubkey)
.whitelist_entry_account(whitelist_entry_pda)
.deploy_authority(deploy_authority_pubkey)
.instruction();
let description = format!(
"Approve deployer whitelist entry for {}",
deploy_authority_pubkey
);
let result = authority.execute_instruction(&rpc_client, instruction, &description)?;
emit(&result, mode)
}
pub fn reject(
rpc_url: &str,
deploy_authority: String,
authority: Authority,
mode: OutputMode,
) -> eyre::Result<()> {
let rpc_client = RpcClient::new(rpc_url);
let deploy_authority_pubkey = deploy_authority
.parse::<Pubkey>()
.map_err(|e| eyre::eyre!("Invalid deploy authority: {}", e))?;
let (whitelist_entry_pda, _bump) = derive_whitelist_entry(&deploy_authority_pubkey);
let whitelist_pubkey = Pubkey::from(account_solana::id().to_bytes());
let instruction_authority = authority.instruction_authority_pubkey()?;
progress("Rejecting deployer whitelist entry:");
progress(format!("Deploy Authority: {}", deploy_authority_pubkey));
progress(format!("Whitelist Entry PDA: {}", whitelist_entry_pda));
progress(format!("Whitelist Authority: {}", instruction_authority));
let instruction = RejectWhitelistEntryBuilder::new()
.payer(instruction_authority)
.whitelist_authority(instruction_authority)
.whitelist_account(whitelist_pubkey)
.whitelist_entry_account(whitelist_entry_pda)
.deploy_authority(deploy_authority_pubkey)
.instruction();
let description = format!(
"Reject deployer whitelist entry for {}",
deploy_authority_pubkey
);
let result = authority.execute_instruction(&rpc_client, instruction, &description)?;
emit(&result, mode)
}
pub fn remove(
rpc_url: &str,
deploy_authority: String,
authority: Authority,
mode: OutputMode,
) -> eyre::Result<()> {
let rpc_client = RpcClient::new(rpc_url);
let deploy_authority_pubkey = deploy_authority
.parse::<Pubkey>()
.map_err(|e| eyre::eyre!("Invalid deploy authority: {}", e))?;
let (whitelist_entry_pda, _bump) = derive_whitelist_entry(&deploy_authority_pubkey);
let whitelist_pubkey = Pubkey::from(account_solana::id().to_bytes());
let instruction_authority = authority.instruction_authority_pubkey()?;
progress("Removing deployer authority from whitelist:");
progress(format!("Deploy Authority: {}", deploy_authority_pubkey));
progress(format!("Whitelist Entry PDA: {}", whitelist_entry_pda));
progress(format!("Whitelist Authority: {}", instruction_authority));
let instruction = RemoveWhitelistEntryBuilder::new()
.payer(instruction_authority)
.whitelist_authority(instruction_authority)
.whitelist_account(whitelist_pubkey)
.whitelist_entry_account(whitelist_entry_pda)
.deploy_authority(deploy_authority_pubkey)
.instruction();
let description = format!(
"Remove deployer authority {} from whitelist",
deploy_authority_pubkey
);
let result = authority.execute_instruction(&rpc_client, instruction, &description)?;
emit(&result, mode)
}
pub fn require_whitelist_entry(
rpc_client: &RpcClient,
upgrade_authority: Pubkey,
) -> eyre::Result<Pubkey> {
progress("🔐 Verifying whitelist...");
let (whitelist_entry_pda, _bump) = derive_whitelist_entry(&upgrade_authority);
progress(format!("Whitelist entry PDA: {}", whitelist_entry_pda));
match rpc_client.get_account(&whitelist_entry_pda) {
Ok(account) => {
let entry = load::<ProgramWhitelistEntry>(&account.data).map_err(|e| {
eyre::eyre!(
"Failed to read whitelist entry {}: {:?}",
whitelist_entry_pda,
e
)
})?;
if entry.state == EntryState::Approved as u8 {
progress("✅ Upgrade authority is whitelisted (approved)");
Ok(whitelist_entry_pda)
} else {
Err(eyre::eyre!(
"❌ Upgrade authority {} has a PENDING (not yet approved) whitelist entry.\n An authority must approve it first:\n spherenet-admin pw approve {} --auth <AUTHORITY>",
upgrade_authority,
upgrade_authority
))
}
}
Err(_) => Err(eyre::eyre!(
"❌ Upgrade authority {} is not whitelisted!\n The deployer requests, then an authority approves:\n spherenet-admin pw request <DEPLOY_AUTHORITY_KEYPAIR> --auth <PAYER>\n spherenet-admin pw approve {} --auth <AUTHORITY>",
upgrade_authority,
upgrade_authority
)),
}
}