Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

funds-manager: Add endpoint to fetch token balances for hot wallets #20

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions funds-manager/funds-manager-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ pub const GET_FEE_WALLETS_ROUTE: &str = "get-fee-wallets";
/// The route to withdraw a fee balance
pub const WITHDRAW_FEE_BALANCE_ROUTE: &str = "withdraw-fee-balance";

/// The route to create a new hot wallet
pub const CREATE_HOT_WALLET_ROUTE: &str = "create-hot-wallet";

// -------------
// | Api Types |
// -------------
Expand Down Expand Up @@ -102,3 +99,28 @@ pub struct CreateHotWalletResponse {
/// The address of the hot wallet
pub address: String,
}

/// The response containing hot wallet balances
#[derive(Debug, Serialize, Deserialize)]
pub struct HotWalletBalancesResponse {
/// The list of hot wallets with their balances
pub wallets: Vec<WalletWithBalances>,
}

/// A hot wallet with its balances
#[derive(Debug, Serialize, Deserialize)]
pub struct WalletWithBalances {
/// The address of the hot wallet
pub address: String,
/// The balances of various tokens
pub balances: Vec<TokenBalance>,
}

/// A balance for a specific token
#[derive(Debug, Serialize, Deserialize)]
pub struct TokenBalance {
/// The mint address of the token
pub mint: String,
/// The balance amount
pub amount: u128,
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
//! We store funds in hot wallets to prevent excessive in/out-flow from
//! Fireblocks

use std::sync::Arc;

use ethers::{
providers::{Http, Provider},
signers::{LocalWallet, Signer},
types::Address,
utils::hex::ToHexExt,
};
use funds_manager_api::{TokenBalance, WalletWithBalances};
use rand::thread_rng;
use tracing::info;

use super::CustodyClient;
use super::{CustodyClient, ERC20};
use crate::{error::FundsManagerError, helpers::create_secrets_manager_entry_with_description};

impl CustodyClient {
Expand Down Expand Up @@ -40,4 +45,50 @@ impl CustodyClient {
info!("Created hot wallet with address: {} for vault: {}", address, vault);
Ok(address)
}

/// Get balances for all hot wallets
pub async fn get_hot_wallet_balances(
&self,
mints: &[String],
) -> Result<Vec<WalletWithBalances>, FundsManagerError> {
let hot_wallets = self.get_all_hot_wallets().await?;
let provider = Arc::new(self.get_rpc_provider()?);

let mut hot_wallet_balances = Vec::new();
for wallet in hot_wallets.iter().map(|w| w.address.clone()) {
// Fetch token balances for the wallet
let mut balances = Vec::new();
for mint in mints.iter() {
let balance = self.get_token_balance(&wallet, mint, provider.clone()).await?;
balances.push(TokenBalance { mint: mint.clone(), amount: balance });
}

hot_wallet_balances.push(WalletWithBalances { address: wallet, balances });
}

Ok(hot_wallet_balances)
}

/// Fetch the token balance at the given address for a wallet
async fn get_token_balance(
&self,
wallet_address: &str,
token_address: &str,
provider: Arc<Provider<Http>>,
) -> Result<u128, FundsManagerError> {
let wallet_address: Address = wallet_address.parse().map_err(|_| {
FundsManagerError::parse(format!("Invalid wallet address: {wallet_address}"))
})?;
let token_address: Address = token_address.parse().map_err(|_| {
FundsManagerError::parse(format!("Invalid token address: {token_address}"))
})?;

let token = ERC20::new(token_address, provider);
token
.balance_of(wallet_address)
.call()
.await
.map(|balance| balance.as_u128())
.map_err(FundsManagerError::arbitrum)
}
}
10 changes: 8 additions & 2 deletions funds-manager/funds-manager-server/src/custody_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::error::FundsManagerError;
abigen!(
ERC20,
r#"[
function balanceOf(address owner) external view returns (uint256)
function symbol() external view returns (string memory)
]"#
);
Expand Down Expand Up @@ -91,15 +92,20 @@ impl CustodyClient {
.map_err(FundsManagerError::fireblocks)
}

/// Get a JSON RPC provider for the given RPC url
pub fn get_rpc_provider(&self) -> Result<Provider<Http>, FundsManagerError> {
Provider::<Http>::try_from(&self.arbitrum_rpc_url)
.map_err(err_str!(FundsManagerError::Arbitrum))
}

/// Get the symbol for an ERC20 token at the given address
pub(self) async fn get_erc20_token_symbol(
&self,
token_address: &str,
) -> Result<String, FundsManagerError> {
let addr =
Address::from_str(token_address).map_err(err_str!(FundsManagerError::Arbitrum))?;
let provider = Provider::<Http>::try_from(&self.arbitrum_rpc_url)
.map_err(err_str!(FundsManagerError::Arbitrum))?;
let provider = self.get_rpc_provider()?;
let client = Arc::new(provider);
let erc20 = ERC20::new(addr, client);

Expand Down
11 changes: 11 additions & 0 deletions funds-manager/funds-manager-server/src/custody_client/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ use crate::error::FundsManagerError;
use crate::CustodyClient;

impl CustodyClient {
/// Get all hot wallets
pub async fn get_all_hot_wallets(&self) -> Result<Vec<HotWallet>, FundsManagerError> {
let mut conn = self.get_db_conn().await?;
let wallets = hot_wallets::table
.load::<HotWallet>(&mut conn)
.await
.map_err(err_str!(FundsManagerError::Db))?;

Ok(wallets)
}

/// Insert a new hot wallet into the database
pub async fn insert_hot_wallet(
&self,
Expand Down
32 changes: 27 additions & 5 deletions funds-manager/funds-manager-server/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ use crate::Server;
use bytes::Bytes;
use funds_manager_api::{
CreateHotWalletRequest, CreateHotWalletResponse, DepositAddressResponse, FeeWalletsResponse,
WithdrawFeeBalanceRequest, WithdrawFundsRequest, WithdrawGasRequest,
HotWalletBalancesResponse, WithdrawFeeBalanceRequest, WithdrawFundsRequest, WithdrawGasRequest,
};
use itertools::Itertools;
use std::collections::HashMap;
use std::sync::Arc;
use warp::reply::Json;

/// The "mint" query param
pub const MINT_QUERY_PARAM: &str = "mint";
/// The "mints" query param
pub const MINTS_QUERY_PARAM: &str = "mints";
/// The asset used for gas (ETH)
pub const GAS_ASSET_NAME: &str = "ETH";
/// The maximum amount of gas that can be withdrawn at a given time
Expand Down Expand Up @@ -67,8 +68,8 @@ pub(crate) async fn get_deposit_address_handler(
query_params: HashMap<String, String>,
server: Arc<Server>,
) -> Result<Json, warp::Rejection> {
let mint = query_params.get(MINT_QUERY_PARAM).ok_or_else(|| {
warp::reject::custom(ApiError::BadRequest("Missing 'mint' query parameter".to_string()))
let mint = query_params.get(MINTS_QUERY_PARAM).ok_or_else(|| {
warp::reject::custom(ApiError::BadRequest("Missing 'mints' query parameter".to_string()))
joeykraut marked this conversation as resolved.
Show resolved Hide resolved
})?;

let address = server
Expand Down Expand Up @@ -144,3 +145,24 @@ pub(crate) async fn create_hot_wallet_handler(
let resp = CreateHotWalletResponse { address };
Ok(warp::reply::json(&resp))
}

/// Handler for getting hot wallet balances
pub(crate) async fn get_hot_wallet_balances_handler(
_body: Bytes, // unused
query_params: HashMap<String, String>,
server: Arc<Server>,
) -> Result<Json, warp::Rejection> {
let mints = query_params
.get(MINTS_QUERY_PARAM)
.map(|s| s.split(',').map(String::from).collect_vec())
.unwrap_or_default();

let wallets = server
.custody_client
.get_hot_wallet_balances(&mints)
.await
.map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?;

let resp = HotWalletBalancesResponse { wallets };
Ok(warp::reply::json(&resp))
}
15 changes: 11 additions & 4 deletions funds-manager/funds-manager-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ use error::FundsManagerError;
use ethers::signers::LocalWallet;
use fee_indexer::Indexer;
use funds_manager_api::{
CreateHotWalletRequest, WithdrawFeeBalanceRequest, WithdrawGasRequest, CREATE_HOT_WALLET_ROUTE,
CreateHotWalletRequest, WithdrawFeeBalanceRequest, WithdrawGasRequest,
GET_DEPOSIT_ADDRESS_ROUTE, GET_FEE_WALLETS_ROUTE, INDEX_FEES_ROUTE, PING_ROUTE,
REDEEM_FEES_ROUTE, WITHDRAW_CUSTODY_ROUTE, WITHDRAW_FEE_BALANCE_ROUTE, WITHDRAW_GAS_ROUTE,
};
use handlers::{
create_hot_wallet_handler, get_deposit_address_handler, get_fee_wallets_handler,
index_fees_handler, quoter_withdraw_handler, redeem_fees_handler, withdraw_fee_balance_handler,
withdraw_gas_handler,
get_hot_wallet_balances_handler, index_fees_handler, quoter_withdraw_handler,
redeem_fees_handler, withdraw_fee_balance_handler, withdraw_gas_handler,
};
use middleware::{identity, with_hmac_auth, with_json_body};
use relayer_client::RelayerClient;
Expand Down Expand Up @@ -342,13 +342,19 @@ async fn main() -> Result<(), Box<dyn Error>> {

let create_hot_wallet = warp::post()
.and(warp::path("hot-wallets"))
.and(warp::path(CREATE_HOT_WALLET_ROUTE))
.and(with_hmac_auth(server.clone()))
.map(with_json_body::<CreateHotWalletRequest>)
.and_then(identity)
.and(with_server(server.clone()))
.and_then(create_hot_wallet_handler);

let get_hot_wallet_balances = warp::get()
.and(warp::path("hot-wallets"))
.and(with_hmac_auth(server.clone()))
joeykraut marked this conversation as resolved.
Show resolved Hide resolved
.and(warp::query::<HashMap<String, String>>())
.and(with_server(server.clone()))
.and_then(get_hot_wallet_balances_handler);

let routes = ping
.or(index_fees)
.or(redeem_fees)
Expand All @@ -358,6 +364,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.or(get_balances)
.or(withdraw_fee_balance)
.or(create_hot_wallet)
.or(get_hot_wallet_balances)
.recover(handle_rejection);
warp::serve(routes).run(([0, 0, 0, 0], cli.port)).await;

Expand Down
Loading