1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use crate::*;

use kv_log_macro::info;
use solana_sdk::signer::Signer;

impl SwitchboardWallet {
    pub fn from_seed(attestation_queue: Pubkey, authority: Pubkey, name: Vec<u8>) -> Pubkey {
        Self::derive_key(NativeMint::ID, attestation_queue, authority, name)
    }

    pub fn fetch(
        client: &solana_client::rpc_client::RpcClient,
        pubkey: Pubkey,
    ) -> std::result::Result<Self, switchboard_common::SbError> {
        crate::client::fetch_borsh_account(client, pubkey)
    }

    pub async fn fetch_async(
        client: &solana_client::nonblocking::rpc_client::RpcClient,
        pubkey: Pubkey,
    ) -> std::result::Result<Self, switchboard_common::SbError> {
        crate::client::fetch_borsh_account_async(client, pubkey).await
    }

    pub fn fetch_sync<T: solana_sdk::client::SyncClient>(
        client: &T,
        pubkey: Pubkey,
    ) -> std::result::Result<Self, switchboard_common::SbError> {
        crate::client::fetch_borsh_account_sync(client, pubkey)
    }

    pub async fn get_or_create_from_seed(
        rpc: &solana_client::nonblocking::rpc_client::RpcClient,
        payer: std::sync::Arc<Keypair>,
        attestation_queue: Pubkey,
        seed: Option<Vec<u8>>,
    ) -> Result<Pubkey, SbError> {
        let name_seed = seed.unwrap_or(b"default".to_vec());
        if name_seed.len() != 32 {
            return Err(SbError::Message("InvalidSeed"));
        }
        let wallet_pubkey = Self::from_seed(attestation_queue, payer.pubkey(), name_seed.clone());

        if let Err(SbError::AccountNotFound) =
            SwitchboardWallet::fetch_async(rpc, wallet_pubkey).await
        {
            info!(
                "[Wallet] creating new switchboard wallet account {} ...",
                wallet_pubkey
            );

            let wallet_init_ixn = WalletInit::build_ix(
                &SwitchboardWalletInitAccounts {
                    wallet: wallet_pubkey,
                    attestation_queue,
                    authority: payer.pubkey(),
                    payer: payer.pubkey(),
                },
                &WalletInitParams {
                    name: name_seed,
                    max_len: [0u8; 4],
                },
            )
            .unwrap();

            println!(
                "[Wallet] ({}) {:?}",
                wallet_init_ixn.data.len(),
                wallet_init_ixn.data
            );

            let recent_blockhash = retry!(3, 500, rpc.get_latest_blockhash().await)
                .await
                .map_err(|e| SbError::SolanaBlockhashFetchError(std::sync::Arc::new(e)))?;

            let tx = crate::ix_to_tx(&[wallet_init_ixn], &[&*payer], recent_blockhash).unwrap();

            let signature = rpc.send_and_confirm_transaction(&tx).await.unwrap();

            info!(
                "[Wallet] switchboard wallet {} initialized. Tx Signature: {}",
                wallet_pubkey, signature
            );
        };

        // TODO: fetch wallet state and balance and send a fund ixn if below a given threshold

        Ok(wallet_pubkey)
    }
}

pub struct SwitchboardWalletInitAccounts {
    pub wallet: Pubkey,
    pub payer: Pubkey,
    pub attestation_queue: Pubkey,
    pub authority: Pubkey,
}
impl ToAccountMetas for SwitchboardWalletInitAccounts {
    fn to_account_metas(&self, _: Option<bool>) -> Vec<AccountMeta> {
        vec![
            AccountMeta::new(self.wallet, false),
            AccountMeta::new_readonly(NativeMint::ID, false),
            AccountMeta::new_readonly(self.authority, false),
            AccountMeta::new_readonly(self.attestation_queue, false),
            AccountMeta::new(
                find_associated_token_address(&self.wallet, &NativeMint::id()),
                false,
            ),
            AccountMeta::new(self.payer, true),
            AccountMeta::new_readonly(anchor_spl::token::ID, false),
            AccountMeta::new_readonly(anchor_spl::associated_token::ID, false),
            AccountMeta::new_readonly(anchor_lang::system_program::ID, false),
        ]
    }
}