This commit is contained in:
Hardhat Chad
2025-05-24 16:37:30 -07:00
parent 5d275f7d6d
commit 47e6672aac
10 changed files with 394 additions and 54 deletions

View File

@@ -1,9 +1,20 @@
use solana_client::nonblocking::rpc_client::RpcClient;
use ore_api::{prelude::*, sdk::*};
use solana_account_decoder::UiAccountEncoding;
use solana_client::{
client_error::{reqwest::StatusCode, ClientErrorKind},
nonblocking::rpc_client::RpcClient,
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
rpc_filter::{Memcmp, RpcFilterType},
};
use solana_sdk::{
compute_budget::ComputeBudgetInstruction,
keccak::hashv,
native_token::{lamports_to_sol, sol_to_lamports},
pubkey::Pubkey,
signature::{read_keypair_file, Signer},
transaction::Transaction,
};
use steel::{AccountDeserialize, Clock, Discriminator, Instruction};
#[tokio::main]
async fn main() {
@@ -13,27 +24,349 @@ async fn main() {
// Build transaction
let rpc = RpcClient::new(std::env::var("RPC").expect("Missing RPC env var"));
let cu_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(1_400_000);
let ix = match std::env::var("COMMAND")
match std::env::var("COMMAND")
.expect("Missing COMMAND env var")
.as_str()
{
"initialize" => ore_api::sdk::initialize(payer.pubkey()),
// "payout" => ore_api::sdk::payout(payer.pubkey()),
// "reset" => ore_api::sdk::reset(payer.pubkey()),
"initialize" => {
let ix = initialize(payer.pubkey());
submit_transaction(&rpc, &payer, &[ix]).await.unwrap();
}
"payout" => {
let ix = payout(payer.pubkey(), Pubkey::new_unique(), Pubkey::new_unique());
submit_transaction(&rpc, &payer, &[ix]).await.unwrap();
}
"reset" => {
let ix = reset(payer.pubkey(), ore_boost_api::state::config_pda().0);
submit_transaction(&rpc, &payer, &[ix]).await.unwrap();
}
"block" => {
let block = get_block(&rpc).await.unwrap();
println!("Block: {:?}", block);
}
"crank" => {
crank(&rpc, &payer).await.unwrap();
}
"bet" => {
bet(&rpc, &payer, sol_to_lamports(1.0)).await.unwrap();
}
"close" => {
close_all_wagers(&rpc, &payer).await.unwrap();
}
"wagers" => {
let wagers = get_block_wagers(&rpc).await.unwrap();
println!("Wagers: {:?}", wagers);
}
_ => panic!("Invalid command"),
};
let blockhash = rpc.get_latest_blockhash().await.unwrap();
}
async fn crank(
rpc: &RpcClient,
payer: &solana_sdk::signer::keypair::Keypair,
) -> Result<(), solana_client::client_error::ClientError> {
loop {
let block = get_block(rpc).await?;
let clock = get_clock(rpc).await?;
if clock.slot >= block.ends_at {
// Time to payout and reset
let payout_ix = build_payout_ix(rpc, payer).await?;
let reset_ix = reset(payer.pubkey(), ore_boost_api::state::config_pda().0);
submit_transaction(rpc, &payer, &[payout_ix, reset_ix]).await?;
println!("Submitted payout and reset transaction");
} else {
// Calculate and print time remaining
let slots_remaining = block.ends_at.saturating_sub(clock.slot);
let seconds_remaining = (slots_remaining as f64) * 0.4;
println!(
"Time until payout: {:.1} seconds ({} slots) {} wagers {} SOL",
seconds_remaining,
slots_remaining,
block.bet_count,
lamports_to_sol(block.total_bets)
);
}
// Wait 3 seconds before next check
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
}
}
async fn bet(
rpc: &RpcClient,
payer: &solana_sdk::signer::keypair::Keypair,
amount: u64,
) -> Result<(), solana_client::client_error::ClientError> {
// Get current block to get round number
let block = get_block(rpc).await?;
// Create WSOL ATA if it doesn't exist
let wsol_ata = spl_associated_token_account::get_associated_token_address(
&payer.pubkey(),
&spl_token::native_mint::ID,
);
let create_ata_ix = spl_associated_token_account::instruction::create_associated_token_account(
&payer.pubkey(),
&payer.pubkey(),
&spl_token::native_mint::ID,
&spl_token::ID,
);
let mut ixs = match rpc.get_account(&wsol_ata).await {
Ok(_) => vec![],
Err(_) => vec![create_ata_ix],
};
// Wrap SOL
let wrap_ix = solana_sdk::system_instruction::transfer(
&payer.pubkey(),
&spl_associated_token_account::get_associated_token_address(
&payer.pubkey(),
&spl_token::native_mint::ID,
),
amount,
);
let sync_native_ix = spl_token::instruction::sync_native(
&spl_token::ID,
&spl_associated_token_account::get_associated_token_address(
&payer.pubkey(),
&spl_token::native_mint::ID,
),
)
.unwrap();
// Build bet instruction
let seed = generate_seed(&payer, &block);
println!("Seed: {:?}", seed);
let ix = ore_api::sdk::bet(
payer.pubkey(),
spl_token::native_mint::ID,
amount,
block.current_round,
seed,
);
ixs.push(wrap_ix);
ixs.push(sync_native_ix);
ixs.push(ix);
// Submit transaction
submit_transaction(rpc, payer, &ixs).await?;
println!("Placed bet of {} lamports", amount);
Ok(())
}
async fn build_payout_ix(
rpc: &RpcClient,
payer: &solana_sdk::signer::keypair::Keypair,
) -> Result<Instruction, solana_client::client_error::ClientError> {
let block = get_block(rpc).await?;
let wagers = get_block_wagers(rpc).await?;
// Return early if no wagers
if block.total_bets == 0 {
return Ok(payout(
payer.pubkey(),
Pubkey::new_unique(),
Pubkey::new_unique(),
));
}
// Get blockhash
let solana_block = rpc.get_block(block.ends_at).await?;
let blockhash = solana_block.blockhash;
let noise = hashv(&[&block.noise, blockhash.as_ref()]).to_bytes();
// Calculate the random number.
let x = u64::from_le_bytes(noise[0..8].try_into().unwrap());
let y = u64::from_le_bytes(noise[8..16].try_into().unwrap());
let z = u64::from_le_bytes(noise[16..24].try_into().unwrap());
let w = u64::from_le_bytes(noise[24..32].try_into().unwrap());
let roll = (x ^ y ^ z ^ w) % block.total_bets;
// Find the winning wager
let mut winner = None;
for (pubkey, wager) in wagers {
if roll >= wager.cumulative_bets && roll < wager.cumulative_bets + wager.amount {
println!("Roll: {}, Winner: {:?}", roll, pubkey);
winner = Some((pubkey, wager));
break;
}
}
// Build payout instruction
let ix = if let Some((pubkey, wager)) = winner {
payout(
payer.pubkey(),
pubkey,
spl_associated_token_account::get_associated_token_address(
&wager.authority,
&spl_token::native_mint::ID,
),
)
} else {
payout(payer.pubkey(), Pubkey::new_unique(), Pubkey::new_unique())
};
Ok(ix)
}
async fn close_all_wagers(
rpc: &RpcClient,
payer: &solana_sdk::signer::keypair::Keypair,
) -> Result<(), solana_client::client_error::ClientError> {
let block = get_block(rpc).await?;
let wagers = get_my_wagers(rpc, payer).await?;
let mut ixs = vec![];
for (pubkey, wager) in wagers {
if wager.round != block.current_round {
let ix = ore_api::sdk::close(payer.pubkey(), pubkey);
ixs.push(ix);
}
}
submit_transaction(rpc, payer, &ixs).await?;
Ok(())
}
async fn get_block(rpc: &RpcClient) -> Result<Block, solana_client::client_error::ClientError> {
let block_pda = ore_api::state::block_pda();
let account = rpc.get_account(&block_pda.0).await?;
let block = Block::try_from_bytes(&account.data).unwrap();
Ok(*block)
}
async fn get_clock(rpc: &RpcClient) -> Result<Clock, solana_client::client_error::ClientError> {
let data = rpc
.get_account_data(&solana_sdk::sysvar::clock::ID)
.await
.unwrap();
let clock = bincode::deserialize::<Clock>(&data).unwrap();
Ok(clock)
}
async fn get_block_wagers(
rpc: &RpcClient,
) -> Result<Vec<(Pubkey, Wager)>, solana_client::client_error::ClientError> {
let block = get_block(rpc).await?;
let filter = RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
56,
&block.current_round.to_le_bytes(),
));
let wagers = get_program_accounts::<Wager>(rpc, ore_api::ID, vec![filter])
.await
.unwrap();
Ok(wagers)
}
async fn get_my_wagers(
rpc: &RpcClient,
payer: &solana_sdk::signer::keypair::Keypair,
) -> Result<Vec<(Pubkey, Wager)>, solana_client::client_error::ClientError> {
let filter = RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
16,
&payer.pubkey().to_bytes().as_ref(),
));
let wagers = get_program_accounts::<Wager>(rpc, ore_api::ID, vec![filter])
.await
.unwrap();
Ok(wagers)
}
fn generate_seed(payer: &solana_sdk::signer::keypair::Keypair, block: &Block) -> [u8; 32] {
solana_sdk::hash::hash(
&[
payer.pubkey().to_bytes().as_ref(),
block.current_round.to_le_bytes().as_ref(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
.to_le_bytes()
.as_ref(),
]
.concat(),
)
.to_bytes()
}
async fn submit_transaction(
rpc: &RpcClient,
payer: &solana_sdk::signer::keypair::Keypair,
instructions: &[solana_sdk::instruction::Instruction],
) -> Result<solana_sdk::signature::Signature, solana_client::client_error::ClientError> {
let blockhash = rpc.get_latest_blockhash().await?;
let mut all_instructions = vec![ComputeBudgetInstruction::set_compute_unit_limit(1_400_000)];
all_instructions.extend_from_slice(instructions);
let transaction = Transaction::new_signed_with_payer(
&[cu_budget_ix, ix],
&all_instructions,
Some(&payer.pubkey()),
&[&payer],
&[payer],
blockhash,
);
// Send transaction
match rpc.send_and_confirm_transaction(&transaction).await {
Ok(signature) => println!("Transaction succeeded! Signature: {}", signature),
Err(err) => println!("Transaction failed: {}", err),
Ok(signature) => {
println!("Transaction submitted: {:?}", signature);
Ok(signature)
}
Err(e) => {
println!("Error submitting transaction: {:?}", e);
Err(e)
}
}
}
pub async fn get_program_accounts<T>(
client: &RpcClient,
program_id: Pubkey,
filters: Vec<RpcFilterType>,
) -> Result<Vec<(Pubkey, T)>, anyhow::Error>
where
T: AccountDeserialize + Discriminator + Clone,
{
let mut all_filters = vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
0,
&T::discriminator().to_le_bytes(),
))];
all_filters.extend(filters);
let result = client
.get_program_accounts_with_config(
&program_id,
RpcProgramAccountsConfig {
filters: Some(all_filters),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
..Default::default()
},
..Default::default()
},
)
.await;
match result {
Ok(accounts) => {
let accounts = accounts
.into_iter()
.map(|(pubkey, account)| {
let account = T::try_from_bytes(&account.data).unwrap().clone();
(pubkey, account)
})
.collect();
Ok(accounts)
}
Err(err) => match err.kind {
ClientErrorKind::Reqwest(err) => {
if let Some(status_code) = err.status() {
if status_code == StatusCode::GONE {
panic!(
"\n{} Your RPC provider does not support the getProgramAccounts endpoint, needed to execute this command. Please use a different RPC provider.\n",
"ERROR"
);
}
}
return Err(anyhow::anyhow!("Failed to get program accounts: {}", err));
}
_ => return Err(anyhow::anyhow!("Failed to get program accounts: {}", err)),
},
}
}