diff --git a/funds-manager/funds-manager-api/src/lib.rs b/funds-manager/funds-manager-api/src/lib.rs index f901861..295f8b2 100644 --- a/funds-manager/funds-manager-api/src/lib.rs +++ b/funds-manager/funds-manager-api/src/lib.rs @@ -79,7 +79,6 @@ pub struct WithdrawFundsRequest { // --- Gas --- // -// Update request body name and documentation /// The request body for withdrawing gas from custody #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WithdrawGasRequest { @@ -89,6 +88,13 @@ pub struct WithdrawGasRequest { pub destination_address: String, } +/// The response containing the gas wallet's address +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateGasWalletResponse { + /// The address of the gas wallet + pub address: String, +} + // --- Hot Wallets --- // /// The request body for creating a hot wallet diff --git a/funds-manager/funds-manager-server/src/custody_client/gas_wallets.rs b/funds-manager/funds-manager-server/src/custody_client/gas_wallets.rs new file mode 100644 index 0000000..e5b13a9 --- /dev/null +++ b/funds-manager/funds-manager-server/src/custody_client/gas_wallets.rs @@ -0,0 +1,45 @@ +//! Handlers for gas wallet operations + +use ethers::{ + signers::{LocalWallet, Signer}, + utils::hex::ToHexExt, +}; +use rand::thread_rng; +use tracing::info; + +use crate::{error::FundsManagerError, helpers::create_secrets_manager_entry_with_description}; + +use super::CustodyClient; + +impl CustodyClient { + /// Create a new gas wallet + pub(crate) async fn create_gas_wallet(&self) -> Result { + // Sample a new ethereum keypair + let keypair = LocalWallet::new(&mut thread_rng()); + let address = keypair.address().encode_hex(); + + // Add the gas wallet to the database + self.add_gas_wallet(&address).await?; + + // Store the private key in secrets manager + let secret_name = Self::gas_wallet_secret_name(&address); + let private_key = keypair.signer().to_bytes(); + let secret_value = hex::encode(private_key); + let description = "Gas wallet private key for use by Renegade relayers"; + create_secrets_manager_entry_with_description( + &secret_name, + &secret_value, + &self.aws_config, + description, + ) + .await?; + info!("Created gas wallet with address: {}", address); + + Ok(address) + } + + /// Get the secret name for a gas wallet's private key + fn gas_wallet_secret_name(address: &str) -> String { + format!("gas-wallet-{}", address) + } +} diff --git a/funds-manager/funds-manager-server/src/custody_client/mod.rs b/funds-manager/funds-manager-server/src/custody_client/mod.rs index 6ff940a..be1c33d 100644 --- a/funds-manager/funds-manager-server/src/custody_client/mod.rs +++ b/funds-manager/funds-manager-server/src/custody_client/mod.rs @@ -1,6 +1,7 @@ //! Manages the custody backend for the funds manager #![allow(missing_docs)] pub mod deposit; +pub mod gas_wallets; mod hot_wallets; mod queries; pub mod withdraw; diff --git a/funds-manager/funds-manager-server/src/custody_client/queries.rs b/funds-manager/funds-manager-server/src/custody_client/queries.rs index 5983194..907037c 100644 --- a/funds-manager/funds-manager-server/src/custody_client/queries.rs +++ b/funds-manager/funds-manager-server/src/custody_client/queries.rs @@ -5,12 +5,30 @@ use diesel_async::RunQueryDsl; use renegade_util::err_str; use uuid::Uuid; -use crate::db::models::HotWallet; +use crate::db::models::{GasWallet, HotWallet}; +use crate::db::schema::gas_wallets; use crate::db::schema::hot_wallets; use crate::error::FundsManagerError; use crate::CustodyClient; impl CustodyClient { + // --- Gas Wallets --- // + + /// Add a new gas wallet + pub async fn add_gas_wallet(&self, address: &str) -> Result<(), FundsManagerError> { + let mut conn = self.get_db_conn().await?; + let entry = GasWallet::new(address.to_string()); + diesel::insert_into(gas_wallets::table) + .values(entry) + .execute(&mut conn) + .await + .map_err(err_str!(FundsManagerError::Db))?; + + Ok(()) + } + + // --- Hot Wallets --- // + /// Get all hot wallets pub async fn get_all_hot_wallets(&self) -> Result, FundsManagerError> { let mut conn = self.get_db_conn().await?; diff --git a/funds-manager/funds-manager-server/src/db/models.rs b/funds-manager/funds-manager-server/src/db/models.rs index 1c02267..7f13ae9 100644 --- a/funds-manager/funds-manager-server/src/db/models.rs +++ b/funds-manager/funds-manager-server/src/db/models.rs @@ -164,3 +164,12 @@ pub struct GasWallet { pub status: String, pub created_at: SystemTime, } + +impl GasWallet { + /// Construct a new gas wallet + pub fn new(address: String) -> Self { + let id = Uuid::new_v4(); + let status = GasWalletStatus::Inactive.to_string(); + GasWallet { id, address, peer_id: None, status, created_at: SystemTime::now() } + } +} diff --git a/funds-manager/funds-manager-server/src/handlers.rs b/funds-manager/funds-manager-server/src/handlers.rs index 88155d9..6607f09 100644 --- a/funds-manager/funds-manager-server/src/handlers.rs +++ b/funds-manager/funds-manager-server/src/handlers.rs @@ -5,9 +5,10 @@ use crate::error::ApiError; use crate::Server; use bytes::Bytes; use funds_manager_api::{ - CreateHotWalletRequest, CreateHotWalletResponse, DepositAddressResponse, FeeWalletsResponse, - HotWalletBalancesResponse, TransferToVaultRequest, WithdrawFeeBalanceRequest, - WithdrawFundsRequest, WithdrawGasRequest, WithdrawToHotWalletRequest, + CreateGasWalletResponse, CreateHotWalletRequest, CreateHotWalletResponse, + DepositAddressResponse, FeeWalletsResponse, HotWalletBalancesResponse, TransferToVaultRequest, + WithdrawFeeBalanceRequest, WithdrawFundsRequest, WithdrawGasRequest, + WithdrawToHotWalletRequest, }; use itertools::Itertools; use std::collections::HashMap; @@ -21,6 +22,8 @@ pub const GAS_ASSET_NAME: &str = "ETH"; /// The maximum amount of gas that can be withdrawn at a given time pub const MAX_GAS_WITHDRAWAL_AMOUNT: f64 = 0.1; // 0.1 ETH +// --- Fee Indexing --- // + /// Handler for indexing fees pub(crate) async fn index_fees_handler(server: Arc) -> Result { let mut indexer = server @@ -45,6 +48,32 @@ pub(crate) async fn redeem_fees_handler(server: Arc) -> Result, +) -> Result { + let mut indexer = server.build_indexer()?; + let wallets = indexer.fetch_fee_wallets().await?; + Ok(warp::reply::json(&FeeWalletsResponse { wallets })) +} + +/// Handler for withdrawing a fee balance +pub(crate) async fn withdraw_fee_balance_handler( + req: WithdrawFeeBalanceRequest, + server: Arc, +) -> Result { + let mut indexer = server.build_indexer()?; + indexer + .withdraw_fee_balance(req.wallet_id, req.mint) + .await + .map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; + + Ok(warp::reply::json(&"Fee withdrawal initiated...")) +} + +// --- Quoters --- // + /// Handler for withdrawing funds from custody pub(crate) async fn quoter_withdraw_handler( withdraw_request: WithdrawFundsRequest, @@ -77,6 +106,8 @@ pub(crate) async fn get_deposit_address_handler( Ok(warp::reply::json(&resp)) } +// --- Gas --- // + /// Handler for withdrawing gas from custody pub(crate) async fn withdraw_gas_handler( withdraw_request: WithdrawGasRequest, @@ -98,29 +129,17 @@ pub(crate) async fn withdraw_gas_handler( Ok(warp::reply::json(&format!("Gas withdrawal of {} ETH complete", withdraw_request.amount))) } -/// Handler for getting fee wallets -pub(crate) async fn get_fee_wallets_handler( +/// Handler for creating a new gas wallet +pub(crate) async fn create_gas_wallet_handler( _body: Bytes, // no body server: Arc, ) -> Result { - let mut indexer = server.build_indexer()?; - let wallets = indexer.fetch_fee_wallets().await?; - Ok(warp::reply::json(&FeeWalletsResponse { wallets })) + let address = server.custody_client.create_gas_wallet().await?; + let resp = CreateGasWalletResponse { address }; + Ok(warp::reply::json(&resp)) } -/// Handler for withdrawing a fee balance -pub(crate) async fn withdraw_fee_balance_handler( - req: WithdrawFeeBalanceRequest, - server: Arc, -) -> Result { - let mut indexer = server.build_indexer()?; - indexer - .withdraw_fee_balance(req.wallet_id, req.mint) - .await - .map_err(|e| warp::reject::custom(ApiError::InternalError(e.to_string())))?; - - Ok(warp::reply::json(&"Fee withdrawal initiated...")) -} +// --- Hot Wallets --- // /// Handler for creating a hot wallet pub(crate) async fn create_hot_wallet_handler( diff --git a/funds-manager/funds-manager-server/src/main.rs b/funds-manager/funds-manager-server/src/main.rs index d691721..184ed90 100644 --- a/funds-manager/funds-manager-server/src/main.rs +++ b/funds-manager/funds-manager-server/src/main.rs @@ -27,10 +27,10 @@ use funds_manager_api::{ WITHDRAW_FEE_BALANCE_ROUTE, WITHDRAW_GAS_ROUTE, WITHDRAW_TO_HOT_WALLET_ROUTE, }; use handlers::{ - create_hot_wallet_handler, get_deposit_address_handler, get_fee_wallets_handler, - get_hot_wallet_balances_handler, index_fees_handler, quoter_withdraw_handler, - redeem_fees_handler, transfer_to_vault_handler, withdraw_fee_balance_handler, - withdraw_from_vault_handler, withdraw_gas_handler, + create_gas_wallet_handler, create_hot_wallet_handler, get_deposit_address_handler, + get_fee_wallets_handler, get_hot_wallet_balances_handler, index_fees_handler, + quoter_withdraw_handler, redeem_fees_handler, transfer_to_vault_handler, + withdraw_fee_balance_handler, withdraw_from_vault_handler, withdraw_gas_handler, }; use middleware::{identity, with_hmac_auth, with_json_body}; use relayer_client::RelayerClient; @@ -340,6 +340,13 @@ async fn main() -> Result<(), Box> { .and(with_server(server.clone())) .and_then(withdraw_gas_handler); + let add_gas_wallet = warp::post() + .and(warp::path("custody")) + .and(warp::path("gas-wallets")) + .and(with_hmac_auth(server.clone())) + .and(with_server(server.clone())) + .and_then(create_gas_wallet_handler); + // --- Hot Wallets --- // let create_hot_wallet = warp::post() @@ -385,6 +392,7 @@ async fn main() -> Result<(), Box> { .or(withdraw_custody) .or(get_deposit_address) .or(withdraw_gas) + .or(add_gas_wallet) .or(get_balances) .or(withdraw_fee_balance) .or(transfer_to_vault)