You've already forked AstralRinth
* wip: gotenberg * Generate and provide supporting docs for Mural payouts * Correct docs * shear * update cargo lock because r-a complains otherwise * Remove local Gotenberg queue and use Redis instead * Store platform_id in database correctly * Address PR comments * Fix up CI * fix rebase * Add timeout to default env vars
331 lines
9.6 KiB
Rust
331 lines
9.6 KiB
Rust
use std::{env, fmt::Debug, io};
|
|
|
|
use eyre::{Result, WrapErr, eyre};
|
|
use muralpay::{
|
|
AccountId, CounterpartyId, CreatePayout, CreatePayoutDetails, Dob,
|
|
FiatAccountType, FiatAndRailCode, FiatAndRailDetails, FiatFeeRequest,
|
|
FiatPayoutFee, MuralPay, PayoutMethodId, PayoutRecipientInfo,
|
|
PayoutRequestId, PhysicalAddress, TokenAmount, TokenFeeRequest,
|
|
TokenPayoutFee, UsdSymbol,
|
|
};
|
|
use rust_decimal::{Decimal, dec};
|
|
use serde::Serialize;
|
|
|
|
#[derive(Debug, clap::Parser)]
|
|
struct Args {
|
|
#[arg(short, long)]
|
|
output: Option<OutputFormat>,
|
|
#[clap(subcommand)]
|
|
command: Command,
|
|
}
|
|
|
|
#[derive(Debug, clap::Subcommand)]
|
|
enum Command {
|
|
/// Account listing and management
|
|
Account {
|
|
#[command(subcommand)]
|
|
command: AccountCommand,
|
|
},
|
|
/// Payouts and payout requests
|
|
Payout {
|
|
#[command(subcommand)]
|
|
command: PayoutCommand,
|
|
},
|
|
/// Counterparty management
|
|
Counterparty {
|
|
#[command(subcommand)]
|
|
command: CounterpartyCommand,
|
|
},
|
|
/// Payout method management
|
|
PayoutMethod {
|
|
#[command(subcommand)]
|
|
command: PayoutMethodCommand,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, clap::Subcommand)]
|
|
enum AccountCommand {
|
|
/// List all accounts
|
|
#[clap(alias = "ls")]
|
|
List,
|
|
}
|
|
|
|
#[derive(Debug, clap::Subcommand)]
|
|
enum PayoutCommand {
|
|
/// List all payout requests
|
|
#[clap(alias = "ls")]
|
|
List,
|
|
/// Get details for a single payout request
|
|
Get {
|
|
/// ID of the payout request
|
|
payout_request_id: PayoutRequestId,
|
|
},
|
|
/// Create a payout request
|
|
Create {
|
|
/// ID of the Mural account to send from
|
|
source_account_id: AccountId,
|
|
/// Description for this payout request
|
|
memo: Option<String>,
|
|
},
|
|
/// Get fees for a transaction
|
|
Fees {
|
|
#[command(subcommand)]
|
|
command: PayoutFeesCommand,
|
|
},
|
|
/// Get bank details for a fiat and rail code
|
|
BankDetails {
|
|
/// Fiat and rail code to fetch bank details for
|
|
fiat_and_rail_code: FiatAndRailCode,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, clap::Subcommand)]
|
|
enum PayoutFeesCommand {
|
|
/// Get fees for a token-to-fiat transaction
|
|
Token {
|
|
amount: Decimal,
|
|
fiat_and_rail_code: FiatAndRailCode,
|
|
},
|
|
/// Get fees for a fiat-to-token transaction
|
|
Fiat {
|
|
amount: Decimal,
|
|
fiat_and_rail_code: FiatAndRailCode,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, clap::Subcommand)]
|
|
enum CounterpartyCommand {
|
|
/// List all counterparties
|
|
#[clap(alias = "ls")]
|
|
List,
|
|
}
|
|
|
|
#[derive(Debug, clap::Subcommand)]
|
|
enum PayoutMethodCommand {
|
|
/// List payout methods for a counterparty
|
|
#[clap(alias = "ls")]
|
|
List {
|
|
/// ID of the counterparty
|
|
counterparty_id: CounterpartyId,
|
|
},
|
|
/// Delete a payout method
|
|
Delete {
|
|
/// ID of the counterparty
|
|
counterparty_id: CounterpartyId,
|
|
/// ID of the payout method to delete
|
|
payout_method_id: PayoutMethodId,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
|
enum OutputFormat {
|
|
Json,
|
|
JsonMin,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
_ = dotenvy::dotenv();
|
|
color_eyre::install().expect("failed to install `color-eyre`");
|
|
tracing_subscriber::fmt().init();
|
|
|
|
let args = <Args as clap::Parser>::parse();
|
|
let of = args.output;
|
|
|
|
let api_url = env::var("MURALPAY_API_URL")
|
|
.unwrap_or_else(|_| muralpay::SANDBOX_API_URL.to_string());
|
|
let api_key = env::var("MURALPAY_API_KEY").wrap_err("no API key")?;
|
|
let transfer_api_key = env::var("MURALPAY_TRANSFER_API_KEY").ok();
|
|
|
|
let muralpay = MuralPay::new(api_url, api_key, transfer_api_key);
|
|
|
|
match args.command {
|
|
Command::Account {
|
|
command: AccountCommand::List,
|
|
} => run(of, muralpay.get_all_accounts().await?),
|
|
Command::Payout {
|
|
command: PayoutCommand::List,
|
|
} => run(of, muralpay.search_payout_requests(None, None).await?),
|
|
Command::Payout {
|
|
command: PayoutCommand::Get { payout_request_id },
|
|
} => run(of, muralpay.get_payout_request(payout_request_id).await?),
|
|
Command::Payout {
|
|
command:
|
|
PayoutCommand::Create {
|
|
source_account_id,
|
|
memo,
|
|
},
|
|
} => run(
|
|
of,
|
|
create_payout_request(
|
|
&muralpay,
|
|
source_account_id,
|
|
memo.as_deref(),
|
|
)
|
|
.await?,
|
|
),
|
|
Command::Payout {
|
|
command:
|
|
PayoutCommand::Fees {
|
|
command:
|
|
PayoutFeesCommand::Token {
|
|
amount,
|
|
fiat_and_rail_code,
|
|
},
|
|
},
|
|
} => run(
|
|
of,
|
|
get_fees_for_token_amount(&muralpay, amount, fiat_and_rail_code)
|
|
.await?,
|
|
),
|
|
Command::Payout {
|
|
command:
|
|
PayoutCommand::Fees {
|
|
command:
|
|
PayoutFeesCommand::Fiat {
|
|
amount,
|
|
fiat_and_rail_code,
|
|
},
|
|
},
|
|
} => run(
|
|
of,
|
|
get_fees_for_fiat_amount(&muralpay, amount, fiat_and_rail_code)
|
|
.await?,
|
|
),
|
|
Command::Payout {
|
|
command: PayoutCommand::BankDetails { fiat_and_rail_code },
|
|
} => run(of, muralpay.get_bank_details(&[fiat_and_rail_code]).await?),
|
|
Command::Counterparty {
|
|
command: CounterpartyCommand::List,
|
|
} => run(of, list_counterparties(&muralpay).await?),
|
|
Command::PayoutMethod {
|
|
command: PayoutMethodCommand::List { counterparty_id },
|
|
} => run(
|
|
of,
|
|
muralpay
|
|
.search_payout_methods(counterparty_id, None)
|
|
.await?,
|
|
),
|
|
Command::PayoutMethod {
|
|
command:
|
|
PayoutMethodCommand::Delete {
|
|
counterparty_id,
|
|
payout_method_id,
|
|
},
|
|
} => run(
|
|
of,
|
|
muralpay
|
|
.delete_payout_method(counterparty_id, payout_method_id)
|
|
.await?,
|
|
),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn create_payout_request(
|
|
muralpay: &MuralPay,
|
|
source_account_id: AccountId,
|
|
memo: Option<&str>,
|
|
) -> Result<()> {
|
|
muralpay
|
|
.create_payout_request(
|
|
source_account_id,
|
|
memo,
|
|
&[CreatePayout {
|
|
amount: TokenAmount {
|
|
token_amount: dec!(2.00),
|
|
token_symbol: muralpay::USDC.into(),
|
|
},
|
|
payout_details: CreatePayoutDetails::Fiat {
|
|
bank_name: "Foo Bank".into(),
|
|
bank_account_owner: "John Smith".into(),
|
|
developer_fee: None,
|
|
fiat_and_rail_details: FiatAndRailDetails::Usd {
|
|
symbol: UsdSymbol::Usd,
|
|
account_type: FiatAccountType::Checking,
|
|
bank_account_number: "123456789".into(),
|
|
// idk what the format is, https://wise.com/us/routing-number/bank/us-bank
|
|
bank_routing_number: "071004200".into(),
|
|
},
|
|
},
|
|
recipient_info: PayoutRecipientInfo::Individual {
|
|
first_name: "John".into(),
|
|
last_name: "Smith".into(),
|
|
email: "john.smith@example.com".into(),
|
|
date_of_birth: Dob::new(1970, 1, 1).unwrap(),
|
|
physical_address: PhysicalAddress {
|
|
address1: "1234 Elm Street".into(),
|
|
address2: Some("Apt 56B".into()),
|
|
country: rust_iso3166::US,
|
|
state: "CA".into(),
|
|
city: "Springfield".into(),
|
|
zip: "90001".into(),
|
|
},
|
|
},
|
|
supporting_details: None,
|
|
}],
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_fees_for_token_amount(
|
|
muralpay: &MuralPay,
|
|
amount: Decimal,
|
|
fiat_and_rail_code: FiatAndRailCode,
|
|
) -> Result<TokenPayoutFee> {
|
|
let fees = muralpay
|
|
.get_fees_for_token_amount(&[TokenFeeRequest {
|
|
amount: TokenAmount {
|
|
token_amount: amount,
|
|
token_symbol: muralpay::USDC.into(),
|
|
},
|
|
fiat_and_rail_code,
|
|
}])
|
|
.await?;
|
|
let fee = fees
|
|
.into_iter()
|
|
.next()
|
|
.ok_or_else(|| eyre!("no fee results returned"))?;
|
|
Ok(fee)
|
|
}
|
|
|
|
async fn get_fees_for_fiat_amount(
|
|
muralpay: &MuralPay,
|
|
amount: Decimal,
|
|
fiat_and_rail_code: FiatAndRailCode,
|
|
) -> Result<FiatPayoutFee> {
|
|
let fees = muralpay
|
|
.get_fees_for_fiat_amount(&[FiatFeeRequest {
|
|
fiat_amount: amount,
|
|
token_symbol: muralpay::USDC.into(),
|
|
fiat_and_rail_code,
|
|
}])
|
|
.await?;
|
|
let fee = fees
|
|
.into_iter()
|
|
.next()
|
|
.ok_or_else(|| eyre!("no fee results returned"))?;
|
|
Ok(fee)
|
|
}
|
|
|
|
async fn list_counterparties(muralpay: &MuralPay) -> Result<()> {
|
|
let _counterparties = muralpay.search_counterparties(None).await?;
|
|
Ok(())
|
|
}
|
|
|
|
fn run<T: Debug + Serialize>(output_format: Option<OutputFormat>, value: T) {
|
|
match output_format {
|
|
None => {
|
|
println!("{value:#?}");
|
|
}
|
|
Some(OutputFormat::Json) => {
|
|
_ = serde_json::to_writer_pretty(io::stdout(), &value)
|
|
}
|
|
Some(OutputFormat::JsonMin) => {
|
|
_ = serde_json::to_writer(io::stdout(), &value);
|
|
}
|
|
}
|
|
}
|