-
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: deposit: Endpoint to fetch deposit addr
Adds an endpoint to fetch a deposit address for the quoters. This endpoint looks up the available account asset wallets for the quoter vault and finds the asset prefixed with the ERC20 symbol.
- Loading branch information
Showing
8 changed files
with
351 additions
and
42 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
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
81 changes: 81 additions & 0 deletions
81
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 |
---|---|---|
@@ -0,0 +1,81 @@ | ||
//! 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}; | ||
|
||
impl CustodyClient { | ||
/// Get the deposit address for the given mint | ||
pub(crate) async fn get_deposit_address( | ||
&self, | ||
mint: &str, | ||
source: DepositSource, | ||
) -> 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()), | ||
))?; | ||
|
||
// TODO: Create an account asset if one doesn't exist | ||
let asset = self.get_wallet_for_ticker(&deposit_vault, &symbol).ok_or( | ||
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 | ||
)))?; | ||
|
||
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 | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
funds-manager/funds-manager-server/src/custody_client/mod.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,87 @@ | ||
//! Manages the custody backend for the funds manager | ||
#![allow(missing_docs)] | ||
pub mod deposit; | ||
|
||
use ethers::prelude::abigen; | ||
use ethers::providers::{Http, Provider}; | ||
use ethers::types::Address; | ||
use fireblocks_sdk::{Client as FireblocksClient, ClientBuilder as FireblocksClientBuilder}; | ||
use renegade_util::err_str; | ||
use std::str::FromStr; | ||
use std::sync::Arc; | ||
|
||
use crate::error::FundsManagerError; | ||
|
||
abigen!( | ||
ERC20, | ||
r#"[ | ||
function symbol() external view returns (string memory) | ||
]"# | ||
); | ||
|
||
/// The source of a deposit | ||
pub(crate) enum DepositSource { | ||
/// A Renegade quoter | ||
Quoter, | ||
/// A fee withdrawal | ||
FeeWithdrawal, | ||
Check failure on line 27 in funds-manager/funds-manager-server/src/custody_client/mod.rs GitHub Actions / clippyvariant `FeeWithdrawal` is never constructed
|
||
} | ||
|
||
impl DepositSource { | ||
/// Get the Fireblocks vault name into which the given deposit source should | ||
/// deposit funds | ||
pub(crate) fn get_vault_name(&self) -> &str { | ||
match self { | ||
DepositSource::Quoter => "Quoters", | ||
DepositSource::FeeWithdrawal => unimplemented!("no vault for fee withdrawal yet"), | ||
} | ||
} | ||
} | ||
|
||
/// The client interacting with the custody backend | ||
#[derive(Clone)] | ||
pub struct CustodyClient { | ||
/// The API key for the Fireblocks API | ||
fireblocks_api_key: String, | ||
/// The API secret for the Fireblocks API | ||
fireblocks_api_secret: Vec<u8>, | ||
/// The arbitrum RPC url to use for the custody client | ||
arbitrum_rpc_url: String, | ||
} | ||
|
||
impl CustodyClient { | ||
/// Create a new CustodyClient | ||
#[allow(clippy::needless_pass_by_value)] | ||
pub fn new( | ||
fireblocks_api_key: String, | ||
fireblocks_api_secret: String, | ||
arbitrum_rpc_url: String, | ||
) -> Self { | ||
let fireblocks_api_secret = fireblocks_api_secret.as_bytes().to_vec(); | ||
Self { fireblocks_api_key, fireblocks_api_secret, arbitrum_rpc_url } | ||
} | ||
|
||
/// Get a fireblocks client | ||
pub fn get_fireblocks_client(&self) -> Result<FireblocksClient, FundsManagerError> { | ||
FireblocksClientBuilder::new(&self.fireblocks_api_key, &self.fireblocks_api_secret) | ||
// TODO: Remove the sandbox config | ||
.with_sandbox() | ||
.build() | ||
.map_err(FundsManagerError::fireblocks) | ||
} | ||
|
||
/// 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 client = Arc::new(provider); | ||
let erc20 = ERC20::new(addr, client); | ||
|
||
erc20.symbol().call().await.map_err(FundsManagerError::arbitrum) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
//! Route handlers for the funds manager | ||
|
||
use crate::custody_client::DepositSource; | ||
use crate::error::ApiError; | ||
use crate::Server; | ||
use funds_manager_api::DepositAddressResponse; | ||
use std::collections::HashMap; | ||
use std::sync::Arc; | ||
use warp::reply::Json; | ||
|
||
/// The "mint" query param | ||
pub const MINT_QUERY_PARAM: &str = "mint"; | ||
|
||
/// Handler for indexing fees | ||
pub(crate) async fn index_fees_handler(server: Arc<Server>) -> Result<Json, warp::Rejection> { | ||
let mut indexer = server | ||
.build_indexer() | ||
.await | ||
.map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; | ||
indexer | ||
.index_fees() | ||
.await | ||
.map_err(|e| warp::reject::custom(ApiError::IndexingError(e.to_string())))?; | ||
Ok(warp::reply::json(&"Fees indexed successfully")) | ||
} | ||
|
||
/// Handler for redeeming fees | ||
pub(crate) async fn redeem_fees_handler(server: Arc<Server>) -> Result<Json, warp::Rejection> { | ||
let mut indexer = server | ||
.build_indexer() | ||
.await | ||
.map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; | ||
indexer | ||
.redeem_fees() | ||
.await | ||
.map_err(|e| warp::reject::custom(ApiError::RedemptionError(e.to_string())))?; | ||
Ok(warp::reply::json(&"Fees redeemed successfully")) | ||
} | ||
|
||
/// Handler for withdrawing funds from custody | ||
pub(crate) async fn withdraw_funds_handler(_server: Arc<Server>) -> Result<Json, warp::Rejection> { | ||
// Implement the withdrawal logic here | ||
todo!("Implement withdrawal from custody") | ||
} | ||
|
||
/// Handler for retrieving the address to deposit custody funds to | ||
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 address = server | ||
.custody_client | ||
.get_deposit_address(mint, DepositSource::Quoter) | ||
.await | ||
.map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; | ||
let resp = DepositAddressResponse { address }; | ||
Ok(warp::reply::json(&resp)) | ||
} |
Oops, something went wrong.