josip
josip

Reputation: 308

Decode program data (UIAccountData) from Solana websocket

I connect to Solana websocket and subscribe to program data of Raydium decentralized exchange account (https://raydium.io/swap/) to listen to swap transactions.

Current State

I successfully connected, but I cannot find resourced on how to transform (decode & deserialize) an UiAccountData binary to something readable. What I believe am missing is the deserialization struct of a Raydium "swap".

const RAYDIUM_WALLET_ADDRESS: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
const SOLANA_WS_MAINNET: &str = "wss://api.mainnet-beta.solana.com";
const HELIUS_WS_MAINNET: &str = "wss://mainnet.helius-rpc.com";
const HELIUS_API_KEY: &str = "HELIUS_API_KEY";

#[derive(Debug, Serialize, Deserialize)]
pub struct RaydiumSwap {
    pub instruction: u8,
    pub amount_in: u64,
    pub min_amount_out: u64,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv::dotenv().ok();
    let raydium_pubkey = Pubkey::from_str_const(RAYDIUM_WALLET_ADDRESS);
    let helius_api_key = std::env::var(HELIUS_API_KEY).expect("HELIUS_API_KEY must be set");
    tokio::spawn(async move {
        println!("Connecting to Helius Solana Mainnet");
        let solana = PubsubClient::new(&format!("{}?api-key={}", HELIUS_WS_MAINNET, helius_api_key))
            .await
            .expect("Cannot connect to Helius Solana Mainnet");
        print!("Connected to Helius Solana Mainnet");
        let rpc_account_info_config = RpcAccountInfoConfig {
            commitment: Some(CommitmentConfig::confirmed()),
            encoding: Some(UiAccountEncoding::Base64),
            data_slice: None,
            min_context_slot: None,
        };

        let rpc_program_accounts_config = RpcProgramAccountsConfig {
            filters: None,
            account_config: rpc_account_info_config,
            with_context: None,
            sort_results: None,
        };

        let subscription = solana
            .program_subscribe(&raydium_pubkey, Some(rpc_program_accounts_config))
            .await
            .expect("Cannot subscribe to account");
        println!("Subscribed to account");
        let (mut account_stream, _unsubscribe) = subscription;

        while let Some(response) = account_stream.next().await {
            deserialize_raydium_swap(response);
        }
    });

    loop {}

    Ok(())
}

fn deserialize_raydium_swap(response: Response<RpcKeyedAccount>) {
    if let UiAccountData::Binary(binary_str, _) = response.value.account.data {
        // Step 1: Decode the base64 string into bytes
        let decoded_bytes = BASE64_STANDARD.decode(binary_str).expect("Failed to decode base64");

        let raydium_swap: RaydiumSwap = bincode::deserialize(&decoded_bytes).expect("Failed to deserialize data");

        println!("{:?}", raydium_swap);
    } else {
        println!("The provided data is not of Binary type");
    }
}

Response

If I print out the response, it looks like this:

Response {
    context: RpcResponseContext {
        slot: 322346382,
        api_version: None,
    },
    value: RpcKeyedAccount {
        pubkey: "6T5HPoskH55fa61GNXP4SSNSMQcmyeAo2h41TMjJ1Jct",
        account: UiAccount {
            lamports: 5324400,
            data: Binary(
                "9+3j9dfD3kazIT+6i/nIf6keR4GWKMOD4AvqfpjHoD4DuhBpz8P286cm3/If6FlkWTnsnxfd7RQupsSdPfJu9q5y6dmi83wZt4D+mYqLzfRAnwhRaEX6FLFVTiffWIsc/Rl8DBS2uCMBZSyXTmO8C3qn9v67NXreXSsZlvfMU/ygJ4R+enAqiaGpU9alUOF7CYe8A7MbBqBR5xw305nbKGfZK67PDo3xBpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAHA+v6YxhTMcFzuNvBHHMaY2JcB8bpPm0fSdv/8nEQMSQbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKk+iWorY4q5NIXH6uKU+mthvYnZavGm2oz7Q13SFAmrDv0ACQkJCI1SqZKsAAAaq/0BAAAAAD1hvckyBwAAnZGtAQAAAAAuM64n4AYAAGUFuWcAAAAA6gIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
                Base64,
            ),
            owner: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C",
            executable: false,
            rent_epoch: 18446744073709551615,
            space: Some(
                637,
            ),
        },
    },
}

Conclusion

So it seems I first have to base64 decode the response.value.account.data and then deserialize it into a struct. However, I am struggling to find out how to do that. When data is deserialized, I assume I will be able to filter through it only to process Swap transactions.

Please advise, thank you.

P.S. I am using Helius WebSockets server (which needs API key) just because Solana Mainnet's looks unstable to me and is slow.

Upvotes: 0

Views: 46

Answers (0)

Related Questions