diff --git a/Cargo.lock b/Cargo.lock index af31b47..b2b5f48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -956,7 +956,7 @@ dependencies = [ [[package]] name = "galoy-cli" -version = "0.1.3-dev" +version = "0.1.3" dependencies = [ "actix-web", "anyhow", diff --git a/src/app/operations/onchain.rs b/src/app/operations/onchain.rs index 2d0ecde..c61724b 100644 --- a/src/app/operations/onchain.rs +++ b/src/app/operations/onchain.rs @@ -1,4 +1,5 @@ -use anyhow::Context; +use anyhow::{Context, Result}; +use rust_decimal::Decimal; use crate::{ app::App, @@ -6,6 +7,45 @@ use crate::{ }; impl App { + pub async fn send_onchain( + &self, + onchain_address: String, + wallet: Wallet, + cents: Option, + sats: Option, + memo: Option, + ) -> Result<()> { + match (wallet, sats, cents) { + (Wallet::Btc, Some(sats), _) => { + let btc_wallet_id = self.get_user_btc_wallet_id().await?; + self.client + .onchain_payment_send(btc_wallet_id, onchain_address.clone(), sats, memo) + .await + .context("Error occurred while executing BTC onchain payment")?; + + println!( + "Successfully sent {} sats to address: {}", + sats, onchain_address + ); + } + (Wallet::Usd, _, Some(cents)) => { + let usd_wallet_id = self.get_user_usd_wallet_id().await?; + self.client + .onchain_payment_send(usd_wallet_id, onchain_address.clone(), cents, memo) + .await + .context("Error occurred while executing USD onchain payment")?; + + println!( + "Successfully sent {} cents to address: {}", + cents, onchain_address + ); + } + _ => {} + } + + Ok(()) + } + pub async fn receive(&self, wallet: Wallet, via: ReceiveVia) -> anyhow::Result<()> { let receiving_wallet_id = match wallet { Wallet::Btc => self.get_user_btc_wallet_id().await?, diff --git a/src/cli/commands.rs b/src/cli/commands.rs index 4cec041..833be34 100644 --- a/src/cli/commands.rs +++ b/src/cli/commands.rs @@ -61,7 +61,9 @@ pub enum Command { /// Execute a Payment Pay { #[clap(short, long)] - username: String, + username: Option, + #[clap(short, long, conflicts_with("username"))] + onchain_address: Option, #[clap(short, long, value_parser)] wallet: Wallet, #[clap(short, long, required_if_eq("wallet", "usd"))] diff --git a/src/cli/runner.rs b/src/cli/runner.rs index 5c240a4..c31c232 100644 --- a/src/cli/runner.rs +++ b/src/cli/runner.rs @@ -38,14 +38,22 @@ pub async fn run() -> anyhow::Result<()> { } Command::Pay { username, + onchain_address, wallet, cents, sats, memo, - } => { - app.intraledger_payment(username, wallet, cents, sats, memo) - .await?; - } + } => match (username, onchain_address) { + (Some(username), None) => { + app.intraledger_payment(username, wallet, cents, sats, memo) + .await?; + } + (None, Some(onchain_address)) => { + app.send_onchain(onchain_address, wallet, cents, sats, memo) + .await?; + } + _ => {} + }, Command::Receive { wallet, via } => { app.receive(wallet, via).await?; } diff --git a/src/client/gql/mutations/onchain_payment_send.gql b/src/client/gql/mutations/onchain_payment_send.gql new file mode 100644 index 0000000..78ba781 --- /dev/null +++ b/src/client/gql/mutations/onchain_payment_send.gql @@ -0,0 +1,10 @@ +mutation OnChainPaymentSend($input: OnChainPaymentSendInput!) { + onChainPaymentSend(input: $input) { + errors { + message + __typename + } + status + __typename + } +} diff --git a/src/client/queries.rs b/src/client/queries.rs index 70a090c..df5787b 100644 --- a/src/client/queries.rs +++ b/src/client/queries.rs @@ -157,3 +157,14 @@ use super::errors::captcha_error::CaptchaError; pub(super) struct OnChainAddressCurrent; pub use self::on_chain_address_current::OnChainAddressCurrentInput; pub use self::on_chain_address_current::OnChainAddressCurrentOnChainAddressCurrent; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/client/gql/schema.gql", + query_path = "src/client/gql/mutations/onchain_payment_send.gql", + response_derives = "Debug, Serialize" +)] +pub(super) struct OnChainPaymentSend; +pub use self::on_chain_payment_send::OnChainPaymentSendInput; +pub use self::on_chain_payment_send::OnChainPaymentSendOnChainPaymentSend; +pub use self::on_chain_payment_send::PayoutSpeed; diff --git a/src/client/requests/onchain.rs b/src/client/requests/onchain.rs index 2efefad..7c22593 100644 --- a/src/client/requests/onchain.rs +++ b/src/client/requests/onchain.rs @@ -1,9 +1,13 @@ use graphql_client::reqwest::post_graphql; use on_chain_address_current::OnChainAddressCurrentOnChainAddressCurrent; +use rust_decimal::Decimal; use crate::client::{ errors::{api_error::ApiError, ClientError}, - queries::{on_chain_address_current, OnChainAddressCurrent}, + queries::{ + on_chain_address_current, on_chain_payment_send, OnChainAddressCurrent, OnChainPaymentSend, + OnChainPaymentSendInput, PayoutSpeed, + }, GaloyClient, }; @@ -28,4 +32,45 @@ impl GaloyClient { Ok(result) } + + pub async fn onchain_payment_send( + &self, + sender_wallet_id: String, + onchain_address: String, + amount: Decimal, + memo: Option, + ) -> Result<(), ClientError> { + let input = OnChainPaymentSendInput { + wallet_id: sender_wallet_id, + address: onchain_address, + speed: Some(PayoutSpeed::FAST), + amount, + memo, + }; + + let variables = on_chain_payment_send::Variables { input }; + + let response_body = + post_graphql::(&self.graphql_client, &self.api, variables) + .await + .map_err(|err| ApiError::IssueGettingResponse(anyhow::Error::new(err)))?; + + let response_data = response_body.data.ok_or(ApiError::IssueParsingResponse)?; + + if !response_data.on_chain_payment_send.errors.is_empty() { + let error_string: String = response_data + .on_chain_payment_send + .errors + .iter() + .map(|error| format!("{:?}", error)) + .collect::>() + .join(", "); + + return Err(ClientError::ApiError(ApiError::RequestFailedWithError( + error_string, + ))); + } else { + Ok(()) + } + } }