Skip to content

Commit

Permalink
funds-manager: Lookup Fireblocks asset IDs via ERC20 address
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Jul 31, 2024
1 parent 4ed2ffb commit 6874465
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 43 deletions.
15 changes: 7 additions & 8 deletions funds-manager/funds-manager-server/src/custody_client/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,21 @@ impl CustodyClient {
mint: &str,
vault_name: &str,
) -> Result<String, FundsManagerError> {
// Find a vault account for the asset
let symbol = self.get_erc20_token_symbol(mint).await?;
// Find a vault account and asset
let deposit_vault = self.get_vault_account(vault_name).await?.ok_or_else(|| {
FundsManagerError::fireblocks(format!("no vault for deposit source: {vault_name}"))
})?;

// TODO: Create an account asset if one doesn't exist
let asset = self.get_wallet_for_ticker(&deposit_vault, &symbol).ok_or_else(|| {
FundsManagerError::fireblocks(format!("no wallet for deposit source: {vault_name}"))
})?;
let asset_id = self
.get_asset_id_for_address(mint)
.await?
.ok_or_else(|| FundsManagerError::fireblocks(format!("no asset for mint: {mint}")))?;

// Fetch the wallet addresses for the asset
let client = self.get_fireblocks_client()?;
let (addresses, _rid) = client.addresses(deposit_vault.id, &asset.id).await?;
let (addresses, _rid) = client.addresses(deposit_vault.id, &asset_id).await?;
let addr = addresses.first().ok_or_else(|| {
FundsManagerError::fireblocks(format!("no addresses for asset: {}", asset.id))
FundsManagerError::fireblocks(format!("no addresses for asset: {}", asset_id))
})?;

Ok(addr.address.clone())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ impl CustodyClient {
// Fetch the wallet info, then withdraw
let wallet = self.get_hot_wallet_by_vault(vault).await?;
let source = DepositWithdrawSource::from_vault_name(vault)?;
let symbol = self.get_erc20_token_symbol(mint).await?;
self.withdraw_from_fireblocks(source, &wallet.address, &symbol, amount).await
self.withdraw_from_fireblocks(source, &wallet.address, mint, amount).await
}

// ------------
Expand Down
44 changes: 19 additions & 25 deletions funds-manager/funds-manager-server/src/custody_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use ethers::types::{Address, TransactionReceipt, TransactionRequest, U256};
use ethers::utils::format_units;
use fireblocks_sdk::types::Transaction;
use fireblocks_sdk::{
types::{Account as FireblocksAccount, AccountAsset},
Client as FireblocksClient, ClientBuilder as FireblocksClientBuilder,
types::Account as FireblocksAccount, Client as FireblocksClient,
ClientBuilder as FireblocksClientBuilder,
};
use renegade_util::err_str;
use std::str::FromStr;
Expand Down Expand Up @@ -124,13 +124,30 @@ impl CustodyClient {
.map_err(FundsManagerError::fireblocks)
}

/// Get the fireblocks asset ID for a given ERC20 address
pub(crate) async fn get_asset_id_for_address(
&self,
address: &str,
) -> Result<Option<String>, FundsManagerError> {
let client = self.get_fireblocks_client()?;
let (supported_assets, _rid) = client.supported_assets().await?;
for asset in supported_assets {
if asset.contract_address == address {
return Ok(Some(asset.id.to_string()));
}
}

Ok(None)
}

/// Get the vault account for a given asset and source
pub(crate) async fn get_vault_account(
&self,
name: &str,
) -> Result<Option<FireblocksAccount>, FundsManagerError> {
let client = self.get_fireblocks_client()?;
let req = fireblocks_sdk::PagingVaultRequestBuilder::new()
.name_prefix(name)
.limit(100)
.build()
.map_err(err_str!(FundsManagerError::Fireblocks))?;
Expand All @@ -145,15 +162,6 @@ impl CustodyClient {
Ok(None)
}

/// Find the wallet in a vault account for a given symbol
pub(crate) fn get_wallet_for_ticker(
&self,
vault: &FireblocksAccount,
symbol: &str,
) -> Option<AccountAsset> {
vault.assets.iter().find(|acct| acct.id.starts_with(symbol)).cloned()
}

/// Poll a fireblocks transaction for completion
pub(crate) async fn poll_fireblocks_transaction(
&self,
Expand Down Expand Up @@ -216,20 +224,6 @@ impl CustodyClient {
.ok_or_else(|| FundsManagerError::arbitrum("Transaction failed".to_string()))
}

/// 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 = self.get_rpc_provider()?;
let client = Arc::new(provider);
let erc20 = ERC20::new(addr, client);

erc20.symbol().call().await.map_err(FundsManagerError::arbitrum)
}

/// Get the erc20 balance of an address
pub(crate) async fn get_erc20_balance(
&self,
Expand Down
22 changes: 14 additions & 8 deletions funds-manager/funds-manager-server/src/custody_client/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl CustodyClient {
&self,
source: DepositWithdrawSource,
destination_address: &str,
symbol: &str,
mint: &str,
amount: f64,
) -> Result<(), FundsManagerError> {
let client = self.get_fireblocks_client()?;
Expand All @@ -55,30 +55,36 @@ impl CustodyClient {
.get_vault_account(source.vault_name())
.await?
.ok_or_else(|| FundsManagerError::Custom("Vault not found".to_string()))?;

let asset = self.get_wallet_for_ticker(&vault, symbol).ok_or_else(|| {
FundsManagerError::Custom(format!("Asset not found for symbol: {}", symbol))
let asset_id = self.get_asset_id_for_address(mint).await?.ok_or_else(|| {
FundsManagerError::Custom(format!("Asset not found for mint: {mint}"))
})?;

// Check if the available balance is sufficient
let available = vault
.assets
.iter()
.find(|a| a.id == asset_id)
.map(|acct| acct.available.clone())
.unwrap_or_default();
let withdraw_amount = BigDecimal::from_f64(amount)
.ok_or_else(|| FundsManagerError::Custom("Invalid amount".to_string()))?;
if asset.available < withdraw_amount {
if available < withdraw_amount {
return Err(FundsManagerError::Custom(format!(
"Insufficient balance. Available: {}, Requested: {}",
asset.available, withdraw_amount
available, withdraw_amount
)));
}

// Transfer
let vault_name = source.vault_name();
let note = format!("Withdraw {amount} {symbol} from {vault_name} to {destination_address}");
let note =
format!("Withdraw {amount} {asset_id} from {vault_name} to {destination_address}");

let (resp, _rid) = client
.create_transaction_external(
vault.id,
destination_address,
asset.id,
asset_id,
withdraw_amount,
Some(&note),
)
Expand Down

0 comments on commit 6874465

Please sign in to comment.