1use std::path::PathBuf;
2
3use clap::Args;
4use dirs::home_dir;
5use serde::Serialize;
6use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::EncodableKey};
7use tuktuk_program::{CompiledTransactionV0, TransactionSourceV0};
8
9use crate::{client::CliClient, result::Result, serde::serde_pubkey};
10
11pub mod cron;
12pub mod cron_transaction;
13pub mod task;
14pub mod task_queue;
15pub mod tuktuk_config;
16
17#[derive(Debug, Args, Clone)]
19pub struct Opts {
20 #[arg(short = 'w', long)]
22 wallet: Option<PathBuf>,
23
24 #[arg(long, short)]
26 url: String,
27}
28
29impl Opts {
30 pub fn default_wallet_path() -> PathBuf {
31 let mut path = home_dir().unwrap_or_else(|| PathBuf::from("/"));
32 path.push(".config/solana/id.json");
33 path
34 }
35
36 pub fn load_solana_keypair(&self) -> Result<Keypair> {
37 let path = self
38 .wallet
39 .as_ref()
40 .cloned()
41 .unwrap_or_else(Opts::default_wallet_path);
42 Keypair::read_from_file(path).map_err(|_| anyhow::anyhow!("Failed to read keypair"))
43 }
44
45 pub fn ws_url(&self) -> String {
46 self.url
47 .replace("https", "wss")
48 .replace("http", "ws")
49 .replace("127.0.0.1:8899", "127.0.0.1:8900")
50 }
51
52 pub fn rpc_url(&self) -> String {
53 self.url.clone()
54 }
55
56 pub async fn client(&self) -> Result<CliClient> {
57 CliClient::new(self).await
58 }
59}
60
61#[derive(Serialize)]
62pub enum TransactionSource {
63 RemoteV0 {
64 url: String,
65 #[serde(with = "serde_pubkey")]
66 signer: Pubkey,
67 },
68 CompiledV0 {
69 transaction: CompiledTransaction,
70 },
71}
72
73#[derive(Serialize)]
74pub struct CompiledTransaction {
75 pub instructions: Vec<Instruction>,
76}
77
78#[derive(Serialize)]
79pub struct Instruction {
80 pub data: Vec<u8>,
81 #[serde(with = "serde_pubkey")]
82 pub program_id: Pubkey,
83 pub accounts: Vec<Account>,
84}
85
86#[derive(Serialize)]
87pub struct Account {
88 #[serde(with = "serde_pubkey")]
89 pub pubkey: Pubkey,
90 pub is_signer: bool,
91 pub is_writable: bool,
92}
93
94impl From<TransactionSourceV0> for TransactionSource {
95 fn from(source: TransactionSourceV0) -> Self {
96 match source {
97 TransactionSourceV0::RemoteV0 { url, signer } => Self::RemoteV0 { url, signer },
98 TransactionSourceV0::CompiledV0(transaction) => Self::CompiledV0 {
99 transaction: transaction.into(),
100 },
101 }
102 }
103}
104
105impl From<CompiledTransactionV0> for CompiledTransaction {
106 fn from(transaction: CompiledTransactionV0) -> Self {
107 Self {
108 instructions: transaction
109 .instructions
110 .into_iter()
111 .map(|i| Instruction {
112 data: i.data,
113 program_id: transaction.accounts[i.program_id_index as usize],
114 accounts: i
115 .accounts
116 .into_iter()
117 .map(|index| Account {
118 pubkey: transaction.accounts[index as usize],
119 is_signer: index
120 < transaction.num_ro_signers + transaction.num_rw_signers,
121 is_writable: index < transaction.num_rw_signers
122 || (index
123 >= (transaction.num_rw_signers + transaction.num_ro_signers)
124 && index
125 < (transaction.num_rw_signers
126 + transaction.num_ro_signers
127 + transaction.num_rw)),
128 })
129 .collect(),
130 })
131 .collect(),
132 }
133 }
134}