-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
funds-manager: custody-client: Add withdrawal handler
- Loading branch information
Showing
8 changed files
with
171 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 14 additions & 55 deletions
69
funds-manager/funds-manager-server/src/custody_client/deposit.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +1,40 @@ | ||
//! Deposit funds into the custody backend | ||
|
||
use fireblocks_sdk::{ | ||
types::{Account as FireblocksAccount, AccountAsset}, | ||
PagingVaultRequestBuilder, | ||
}; | ||
use renegade_util::err_str; | ||
|
||
use crate::error::FundsManagerError; | ||
|
||
use super::{CustodyClient, DepositSource}; | ||
use super::{CustodyClient, DepositWithdrawSource}; | ||
|
||
impl CustodyClient { | ||
/// Get the deposit address for the given mint | ||
pub(crate) async fn get_deposit_address( | ||
&self, | ||
mint: &str, | ||
source: DepositSource, | ||
source: DepositWithdrawSource, | ||
) -> Result<String, FundsManagerError> { | ||
// Find a vault account for the asset | ||
let symbol = self.get_erc20_token_symbol(mint).await?; | ||
let deposit_vault = | ||
self.get_vault_account(&source).await?.ok_or(FundsManagerError::fireblocks( | ||
format!("no vault for deposit source: {}", source.get_vault_name()), | ||
))?; | ||
let deposit_vault = self.get_vault_account(&source).await?.ok_or_else(|| { | ||
FundsManagerError::fireblocks(format!( | ||
"no vault for deposit source: {}", | ||
source.get_vault_name() | ||
)) | ||
})?; | ||
|
||
// TODO: Create an account asset if one doesn't exist | ||
let asset = self.get_wallet_for_ticker(&deposit_vault, &symbol).ok_or( | ||
let asset = self.get_wallet_for_ticker(&deposit_vault, &symbol).ok_or_else(|| { | ||
FundsManagerError::fireblocks(format!( | ||
"no wallet for deposit source: {}", | ||
source.get_vault_name() | ||
)), | ||
)?; | ||
)) | ||
})?; | ||
|
||
// 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 addr = addresses.first().ok_or(FundsManagerError::fireblocks(format!( | ||
"no addresses for asset: {}", | ||
asset.id | ||
)))?; | ||
let addr = addresses.first().ok_or_else(|| { | ||
FundsManagerError::fireblocks(format!("no addresses for asset: {}", asset.id)) | ||
})?; | ||
|
||
Ok(addr.address.clone()) | ||
} | ||
|
||
/// Get the vault account for a given asset and source | ||
async fn get_vault_account( | ||
&self, | ||
source: &DepositSource, | ||
) -> Result<Option<FireblocksAccount>, FundsManagerError> { | ||
let client = self.get_fireblocks_client()?; | ||
let req = PagingVaultRequestBuilder::new() | ||
.limit(100) | ||
.build() | ||
.map_err(err_str!(FundsManagerError::Fireblocks))?; | ||
|
||
let (vaults, _rid) = client.vaults(req).await?; | ||
for vault in vaults.accounts.into_iter() { | ||
if vault.name == source.get_vault_name() { | ||
return Ok(Some(vault)); | ||
} | ||
} | ||
|
||
Ok(None) | ||
} | ||
|
||
/// Find the wallet in a vault account for a given symbol | ||
fn get_wallet_for_ticker( | ||
&self, | ||
vault: &FireblocksAccount, | ||
symbol: &str, | ||
) -> Option<AccountAsset> { | ||
for acct in vault.assets.iter() { | ||
if acct.id.starts_with(symbol) { | ||
return Some(acct.clone()); | ||
} | ||
} | ||
|
||
None | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
funds-manager/funds-manager-server/src/custody_client/withdraw.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use crate::error::FundsManagerError; | ||
use bigdecimal::{BigDecimal, FromPrimitive}; | ||
use fireblocks_sdk::types::TransactionStatus; | ||
|
||
use super::{CustodyClient, DepositWithdrawSource}; | ||
|
||
impl CustodyClient { | ||
/// Withdraw funds from custody | ||
pub(crate) async fn withdraw( | ||
&self, | ||
source: DepositWithdrawSource, | ||
token_address: &str, | ||
amount: u128, | ||
destination_address: String, | ||
) -> Result<(), FundsManagerError> { | ||
let client = self.get_fireblocks_client()?; | ||
let symbol = self.get_erc20_token_symbol(token_address).await?; | ||
|
||
// Get the vault account and asset to transfer from | ||
let vault = self | ||
.get_vault_account(&source) | ||
.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)) | ||
})?; | ||
|
||
// Check if the available balance is sufficient | ||
let withdraw_amount = BigDecimal::from_u128(amount).expect("amount too large"); | ||
if asset.available < withdraw_amount { | ||
return Err(FundsManagerError::Custom(format!( | ||
"Insufficient balance. Available: {}, Requested: {}", | ||
asset.available, withdraw_amount | ||
))); | ||
} | ||
|
||
// Transfer | ||
let note = format!( | ||
"Withdraw {} {} from {} to {}", | ||
amount, | ||
symbol, | ||
source.get_vault_name(), | ||
destination_address | ||
); | ||
|
||
let (resp, _rid) = client | ||
.create_transaction_external( | ||
vault.id, | ||
destination_address, | ||
asset.id, | ||
withdraw_amount, | ||
Some(¬e), | ||
) | ||
.await?; | ||
|
||
let tx = self.poll_fireblocks_transaction(&resp.id).await?; | ||
if tx.status != TransactionStatus::COMPLETED && tx.status != TransactionStatus::CONFIRMING { | ||
let err_msg = format!("Transaction failed: {:?}", tx.status); | ||
return Err(FundsManagerError::Custom(err_msg)); | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters