| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- use std::convert::TryFrom;
- use std::str::FromStr;
- use async_native_tls::TlsConnector;
- use async_std::sync::{Arc, Mutex};
- use async_trait::async_trait;
- use futures::{SinkExt, StreamExt};
- use log::{debug, error, warn};
- use rand::rngs::OsRng;
- use serde::Serialize;
- use serde_json::{json, Value};
- use solana_client::{blockhash_query::BlockhashQuery, rpc_client::RpcClient};
- use solana_sdk::{
- native_token::lamports_to_sol, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair,
- system_instruction, transaction::Transaction,
- };
- use tungstenite::Message;
- use crate::rpc::{jsonrpc, jsonrpc::JsonResult, websockets};
- use crate::serial::{deserialize, serialize, Decodable, Encodable};
- use crate::{Error, Result};
- use super::bridge::{NetworkClient, TokenNotification, TokenSubscribtion};
- #[derive(Serialize)]
- struct SubscribeParams {
- encoding: Value,
- commitment: Value,
- }
- pub struct SolClient {
- keypair: Keypair,
- // Subscriptions vector of pubkey
- subscriptions: Arc<Mutex<Vec<Pubkey>>>,
- notify_channel: (
- async_channel::Sender<TokenNotification>,
- async_channel::Receiver<TokenNotification>,
- ),
- rpc_server: &'static str,
- wss_server: &'static str,
- }
- impl SolClient {
- pub async fn new(keypair: Vec<u8>, network: &str) -> Result<Arc<Self>> {
- let keypair: Keypair = deserialize(&keypair)?;
- let notify_channel = async_channel::unbounded();
- let (rpc_server, wss_server) = match network {
- "mainnet" => (
- "https://api.mainnet-beta.solana.com",
- "wss://api.devnet.solana.com",
- ),
- "devnet" => (
- "https://api.devnet.solana.com",
- "wss://api.devnet.solana.com",
- ),
- "testnet" => (
- "https://api.testnet.solana.com",
- "wss://api.testnet.solana.com",
- ),
- "localhost" => ("http://localhost:8899", "ws://localhost:8900"),
- _ => return Err(Error::NotSupportedNetwork),
- };
- Ok(Arc::new(Self {
- keypair,
- subscriptions: Arc::new(Mutex::new(Vec::new())),
- notify_channel,
- rpc_server,
- wss_server,
- }))
- }
- // TODO: Make this function more robust. Currently we just call it
- // and put it in the background. This means no errors are actually
- // handled, and it just fails silently.
- async fn handle_subscribe_request(
- self: Arc<Self>,
- keypair: Keypair,
- is_token: bool,
- ) -> Result<()> {
- debug!(target: "SOL BRIDGE", "handle_subscribe_request()");
- // Check if we're already subscribed
- if self.subscriptions.lock().await.contains(&keypair.pubkey()) {
- return Ok(());
- }
- let rpc = RpcClient::new(self.rpc_server.to_string());
- // Fetch the current balance.
- let prev_balance = if !is_token {
- rpc.get_balance(&keypair.pubkey())
- .map_err(|err| SolFailed::from(err))?
- } else {
- // TODO: SPL Token balance
- 0
- };
- let mut cur_balance = prev_balance;
- let mut decimals: Option<u64> = None;
- let mut mint: Option<&str> = None;
- // WebSocket connection
- let builder = native_tls::TlsConnector::builder();
- let tls = TlsConnector::from(builder);
- let (mut stream, _) = websockets::connect(self.wss_server, tls).await?;
- // Subscription request build
- let sub_params = SubscribeParams {
- encoding: json!("jsonParsed"),
- commitment: json!("finalized"),
- };
- let subscription = jsonrpc::request(
- json!("accountSubscribe"),
- json!([json!(keypair.pubkey().to_string()), json!(sub_params)]),
- );
- debug!(target: "SOLANA RPC", "--> {}", serde_json::to_string(&subscription)?);
- stream
- .send(Message::text(serde_json::to_string(&subscription)?))
- .await?;
- // Declare params here for longer variable lifetime.
- let params: Value;
- // Subscription ID used for unsubscribing later.
- let mut sub_id: i64 = 0;
- loop {
- let message = stream.next().await.ok_or_else(|| Error::TungsteniteError)?;
- let message = message.unwrap();
- debug!(target: "SOLANA SUBSCRIPTION", "<-- {}", message.clone().into_text()?);
- match serde_json::from_slice(&message.into_data())? {
- JsonResult::Resp(r) => {
- // ACK
- debug!(target: "SOLANA RPC", "<-- {}", serde_json::to_string(&r)?);
- self.subscriptions.lock().await.push(keypair.pubkey());
- sub_id = r.result.as_i64().unwrap();
- }
- JsonResult::Err(e) => {
- debug!(target: "SOLANA RPC", "<-- {}", serde_json::to_string(&e)?);
- // TODO: Try removing pubkey from subscriptions here?
- return Err(Error::JsonRpcError(e.error.message.to_string()));
- }
- JsonResult::Notif(n) => {
- // Account updated
- debug!(target: "SOLANA RPC", "Got WebSocket notification");
- params = n.params["result"]["value"].clone();
- if is_token {
- cur_balance = params["data"]["info"]["tokenAmount"]["amount"]
- .as_u64()
- .unwrap();
- decimals = Some(
- params["data"]["info"]["tokenAmount"]["decimals"]
- .as_u64()
- .unwrap(),
- );
- mint = Some(params["data"]["info"]["mint"].as_str().unwrap());
- } else {
- cur_balance = params["lamports"].as_u64().unwrap();
- decimals = None;
- mint = None;
- }
- break;
- }
- }
- }
- // I miss goto/defer.
- let index = self
- .subscriptions
- .lock()
- .await
- .iter()
- .position(|p| p == &keypair.pubkey());
- if let Some(ind) = index {
- debug!("Removing subscription from list");
- self.subscriptions.lock().await.remove(ind);
- }
- let unsubscription = jsonrpc::request(json!("accountUnsubscribe"), json!([sub_id]));
- stream
- .send(Message::text(serde_json::to_string(&unsubscription)?))
- .await?;
- if cur_balance - prev_balance <= 0 {
- error!("Current balance is not positive");
- return Err(Error::ServicesError("Current balance is not positive"));
- }
- if is_token {
- debug!(target: "SOL BRIDGE", "Received {} {:?} tokens",
- (cur_balance - prev_balance) * decimals.unwrap(), mint.unwrap());
- self.send_tok_to_main_wallet(mint.unwrap(), cur_balance, keypair)
- } else {
- debug!(target: "SOL BRIDGE", "Received {} SOL", lamports_to_sol(cur_balance - prev_balance));
- self.send_sol_to_main_wallet(cur_balance, &keypair)
- }
- }
- // TODO
- fn send_tok_to_main_wallet(
- self: Arc<Self>,
- mint: &str,
- amount: u64,
- keypair: Keypair,
- ) -> Result<()> {
- debug!(target: "SOL BRIDGE", "Sending tokens to main wallet");
- Ok(())
- }
- fn send_sol_to_main_wallet(self: Arc<Self>, amount: u64, keypair: &Keypair) -> Result<()> {
- debug!(target: "SOL BRIDGE", "Sending {} SOL to main wallet", lamports_to_sol(amount));
- let rpc = RpcClient::new(self.rpc_server.to_string());
- let fee = rpc
- .get_fees()
- .unwrap()
- .fee_calculator
- .lamports_per_signature;
- if fee >= amount {
- warn!(target: "SOL BRIDGE", "Insufficient funds on {:?} to send tx", &keypair.pubkey());
- return Ok(());
- }
- let amnt_to_transfer = amount - fee;
- let ix = system_instruction::transfer(
- &keypair.pubkey(),
- &self.keypair.pubkey(),
- amnt_to_transfer,
- );
- let mut tx = Transaction::new_with_payer(&[ix], Some(&keypair.pubkey()));
- let bhq = BlockhashQuery::default();
- match bhq.get_blockhash_and_fee_calculator(&rpc, rpc.commitment()) {
- Err(_) => panic!("Couldn't connect to RPC"),
- Ok(v) => tx.sign(&[keypair], v.0),
- }
- let signature = rpc.send_and_confirm_transaction(&tx);
- debug!(target: "SOL BRIDGE", "Sent to main wallet: {}", signature.unwrap());
- Ok(())
- }
- }
- #[async_trait]
- impl NetworkClient for SolClient {
- async fn subscribe(self: Arc<Self>) -> Result<TokenSubscribtion> {
- let keypair = Keypair::generate(&mut OsRng);
- let public_key = keypair.pubkey().to_string();
- let secret_key = serialize(&keypair);
- let self2 = self.clone();
- // TODO: true/false depending on is_token
- smol::spawn(self2.handle_subscribe_request(keypair, false)).detach();
- Ok(TokenSubscribtion {
- secret_key,
- public_key,
- })
- }
- // in solana case private key it's the same as keypair
- async fn subscribe_with_keypair(
- self: Arc<Self>,
- private_key: Vec<u8>,
- _public_key: Vec<u8>,
- ) -> Result<String> {
- let keypair: Keypair = deserialize(&private_key)?;
- let public_key = keypair.pubkey().to_string();
- let self2 = self.clone();
- // TODO: true/false depending on is_token
- smol::spawn(self2.handle_subscribe_request(keypair, false)).detach();
- Ok(public_key)
- }
- async fn get_notifier(self: Arc<Self>) -> Result<async_channel::Receiver<TokenNotification>> {
- Ok(self.notify_channel.1.clone())
- }
- async fn send(self: Arc<Self>, address: Vec<u8>, amount: u64) -> Result<()> {
- let rpc = RpcClient::new(self.rpc_server.to_string());
- let address: Pubkey = deserialize(&address)?;
- let instruction = system_instruction::transfer(&self.keypair.pubkey(), &address, amount);
- let mut tx = Transaction::new_with_payer(&[instruction], Some(&self.keypair.pubkey()));
- let bhq = BlockhashQuery::default();
- match bhq.get_blockhash_and_fee_calculator(&rpc, rpc.commitment()) {
- Err(_) => panic!("Couldn't connect to RPC"),
- Ok(v) => tx.sign(&[&self.keypair], v.0),
- }
- let _signature = rpc
- .send_and_confirm_transaction(&tx)
- .map_err(|err| SolFailed::from(err))?;
- Ok(())
- }
- }
- /// Derive an associated token address from given owner and mint
- pub fn get_associated_token_account(owner: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
- let associated_token =
- Pubkey::from_str("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL").unwrap();
- Pubkey::find_program_address(
- &[
- &owner.to_bytes(),
- &spl_token::id().to_bytes(),
- &mint.to_bytes(),
- ],
- &associated_token,
- )
- }
- /// Check if given account is a valid token mint
- pub fn account_is_initialized_mint(rpc_server: String, mint: &Pubkey) -> bool {
- let rpc = RpcClient::new(rpc_server);
- match rpc.get_token_supply(mint) {
- Ok(_) => return true,
- Err(_) => return false,
- }
- }
- impl Encodable for Keypair {
- fn encode<S: std::io::Write>(&self, s: S) -> Result<usize> {
- let key: Vec<u8> = self.to_bytes().to_vec();
- let len = key.encode(s)?;
- Ok(len)
- }
- }
- impl Decodable for Keypair {
- fn decode<D: std::io::Read>(mut d: D) -> Result<Self> {
- let key: Vec<u8> = Decodable::decode(&mut d)?;
- let key = Keypair::from_bytes(key.as_slice()).map_err(|_| {
- crate::Error::from(SolFailed::DecodeAndEncodeError(
- "load keypair from slice".into(),
- ))
- })?;
- Ok(key)
- }
- }
- impl Encodable for Pubkey {
- fn encode<S: std::io::Write>(&self, s: S) -> Result<usize> {
- let key = self.to_string();
- let len = key.encode(s)?;
- Ok(len)
- }
- }
- impl Decodable for Pubkey {
- fn decode<D: std::io::Read>(mut d: D) -> Result<Self> {
- let key: String = Decodable::decode(&mut d)?;
- let key = Pubkey::try_from(key.as_str()).map_err(|_| {
- crate::Error::from(SolFailed::DecodeAndEncodeError(
- "load public key from slice".into(),
- ))
- })?;
- Ok(key)
- }
- }
- #[derive(Debug)]
- pub enum SolFailed {
- NotEnoughValue(u64),
- BadSolAddress(String),
- DecodeAndEncodeError(String),
- WebSocketError(String),
- SolClientError(String),
- ParseError(String),
- SolError(String),
- }
- impl std::error::Error for SolFailed {}
- impl std::fmt::Display for SolFailed {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- match self {
- SolFailed::NotEnoughValue(i) => {
- write!(f, "There is no enough value {}", i)
- }
- SolFailed::BadSolAddress(ref err) => {
- write!(f, "Bad Sol Address: {}", err)
- }
- SolFailed::DecodeAndEncodeError(ref err) => {
- write!(f, "Decode and decode keys error: {}", err)
- }
- SolFailed::WebSocketError(i) => {
- write!(f, "WebSocket Error: {}", i)
- }
- SolFailed::ParseError(i) => {
- write!(f, "Parse Error: {}", i)
- }
- SolFailed::SolClientError(i) => {
- write!(f, "Solana Client Error: {}", i)
- }
- SolFailed::SolError(i) => {
- write!(f, "SolFailed: {}", i)
- }
- }
- }
- }
- impl From<solana_sdk::pubkey::ParsePubkeyError> for SolFailed {
- fn from(err: solana_sdk::pubkey::ParsePubkeyError) -> SolFailed {
- SolFailed::ParseError(err.to_string())
- }
- }
- impl From<tungstenite::Error> for SolFailed {
- fn from(err: tungstenite::Error) -> SolFailed {
- SolFailed::WebSocketError(err.to_string())
- }
- }
- impl From<solana_client::client_error::ClientError> for SolFailed {
- fn from(err: solana_client::client_error::ClientError) -> SolFailed {
- SolFailed::SolError(err.to_string())
- }
- }
- impl From<crate::error::Error> for SolFailed {
- fn from(err: crate::error::Error) -> SolFailed {
- SolFailed::SolError(err.to_string())
- }
- }
- pub type SolResult<T> = std::result::Result<T, SolFailed>;
|