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: custody-client: Add endpoint for creating gas wallets #27

Merged
merged 1 commit into from
Aug 2, 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
8 changes: 7 additions & 1 deletion funds-manager/funds-manager-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, FundsManagerError> {
// 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";
joeykraut marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
20 changes: 19 additions & 1 deletion funds-manager/funds-manager-server/src/custody_client/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<HotWallet>, FundsManagerError> {
let mut conn = self.get_db_conn().await?;
Expand Down
9 changes: 9 additions & 0 deletions funds-manager/funds-manager-server/src/db/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
}
}
61 changes: 40 additions & 21 deletions funds-manager/funds-manager-server/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Server>) -> Result<Json, warp::Rejection> {
let mut indexer = server
Expand All @@ -45,6 +48,32 @@ pub(crate) async fn redeem_fees_handler(server: Arc<Server>) -> Result<Json, war
Ok(warp::reply::json(&"Fees redeemed successfully"))
}

/// Handler for getting fee wallets
pub(crate) async fn get_fee_wallets_handler(
_body: Bytes, // no body
server: Arc<Server>,
) -> Result<Json, warp::Rejection> {
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<Server>,
) -> Result<Json, warp::Rejection> {
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,
Expand Down Expand Up @@ -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,
Expand All @@ -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<Server>,
) -> Result<Json, warp::Rejection> {
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<Server>,
) -> Result<Json, warp::Rejection> {
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(
Expand Down
16 changes: 12 additions & 4 deletions funds-manager/funds-manager-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -340,6 +340,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
.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()
Expand Down Expand Up @@ -385,6 +392,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.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)
Expand Down
Loading