Skip to content

Commit

Permalink
Add Kado fiat provider
Browse files Browse the repository at this point in the history
  • Loading branch information
gemcoder21 committed May 22, 2024
1 parent b4b0e55 commit bd95cfd
Show file tree
Hide file tree
Showing 9 changed files with 351 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ url = ""
public = ""
secret = ""

[kado]
[kado.key]
secret = ""

[pricer]
timer = 60
outdated = 604800 # 7 days
Expand Down
6 changes: 5 additions & 1 deletion crates/fiat/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ pub mod model;
pub mod provider;
pub use provider::FiatProvider;
pub mod providers;
use crate::providers::{BanxaClient, MercuryoClient, MoonPayClient, RampClient, TransakClient};
use crate::providers::{
BanxaClient, KadoClient, MercuryoClient, MoonPayClient, RampClient, TransakClient,
};
use settings::Settings;

pub struct FiatProviderFactory {}
Expand All @@ -29,13 +31,15 @@ impl FiatProviderFactory {
settings.banxa.key.public,
settings.banxa.key.secret,
);
let kado = KadoClient::new(request_client.clone(), settings.kado.key.secret);

vec![
Box::new(moonpay),
Box::new(ramp),
Box::new(mercuryo),
Box::new(transak),
Box::new(banxa),
Box::new(kado),
]
}
}
166 changes: 166 additions & 0 deletions crates/fiat/src/providers/kado/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use crate::model::{filter_token_id, FiatMapping, FiatProviderAsset};
use primitives::{Chain, FiatBuyRequest, FiatProviderName, FiatQuote};
use reqwest::Client;
use url::Url;

use super::model::{Asset, Blockchain, Blockchains, Quote, QuoteData, QuoteQuery, Response};

const API_BASE_URL: &str = "https://api.kado.money";
const REDIRECT_URL: &str = "https://app.kado.money";

pub struct KadoClient {
pub client: Client,
pub api_key: String,
}

impl KadoClient {
pub const NAME: FiatProviderName = FiatProviderName::Kado;

pub fn new(client: Client, api_key: String) -> Self {
KadoClient { client, api_key }
}

pub async fn get_quote_buy(
&self,
fiat_currency: String,
symbol: String,
fiat_amount: f64,
network: String,
) -> Result<QuoteData, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/v2/ramp/quote", API_BASE_URL);
let query = QuoteQuery {
transaction_type: "buy".to_string(),
blockchain: network,
asset: symbol,
amount: fiat_amount,
currency: fiat_currency,
};
let quote = self
.client
.get(&url)
.query(&query)
.send()
.await?
.json::<Response<QuoteData>>()
.await?;
Ok(quote.data)
}

// pub async fn get_assets(&self) -> Result<Vec<Asset>, Box<dyn std::error::Error + Send + Sync>> {
// let url = format!("{}/v1/ramp/supported-assets", API_BASE_URL);
// let response = self
// .client
// .get(&url)
// .send()
// .await?
// .json::<Response<Assets>>()
// .await?;
// Ok(response.data.assets)
// }

pub async fn get_blockchains(
&self,
) -> Result<Vec<Blockchain>, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/v1/ramp/blockchains", API_BASE_URL);
let response = self
.client
.get(&url)
.send()
.await?
.json::<Response<Blockchains>>()
.await?;
Ok(response.data.blockchains)
}

pub fn map_blockchain(blockchain: Blockchain) -> Vec<FiatProviderAsset> {
blockchain
.clone()
.associated_assets
.into_iter()
.flat_map(|x| Self::map_asset(blockchain.clone(), x))
.collect()
}

pub fn map_asset(blockchain: Blockchain, asset: Asset) -> Option<FiatProviderAsset> {
let chain = Self::map_asset_chain(blockchain.network.clone());
let token_id = if asset.address.is_none() {
None
} else {
Some(asset.address.clone().unwrap())
};
let token_id = filter_token_id(token_id);
Some(FiatProviderAsset {
id: asset.clone().symbol + "_" + blockchain.network.as_str(),
chain,
token_id,
symbol: asset.clone().symbol,
network: Some(blockchain.network),
enabled: true,
})
}

pub fn map_asset_chain(chain: String) -> Option<Chain> {
match chain.as_str() {
"bitcoin" => Some(Chain::Bitcoin),
"litecoin" => Some(Chain::Litecoin),
"ethereum" => Some(Chain::Ethereum),
"optimism" => Some(Chain::Optimism),
"polygon" => Some(Chain::Polygon),
"base" => Some(Chain::Base),
"arbitrum" => Some(Chain::Arbitrum),
"avalanche" => Some(Chain::AvalancheC),
"solana" => Some(Chain::Solana),
"osmosis" => Some(Chain::Osmosis),
"cosmos hub" => Some(Chain::Cosmos),
"ripple" => Some(Chain::Xrp),
"celestia" => Some(Chain::Celestia),
"injective" => Some(Chain::Injective),
_ => None,
}
}

pub fn get_fiat_quote(
&self,
request: FiatBuyRequest,
request_map: FiatMapping,
quote: Quote,
) -> FiatQuote {
FiatQuote {
provider: Self::NAME.as_fiat_provider(),
fiat_amount: request.fiat_amount,
fiat_currency: request.clone().fiat_currency,
crypto_amount: quote.clone().receive_unit_count_after_fees.amount,
redirect_url: self.redirect_url(
request.fiat_currency,
request.fiat_amount,
request_map.clone(),
request.wallet_address,
),
}
}

pub fn redirect_url(
&self,
fiat_currency: String,
fiat_amount: f64,
fiat_mapping: FiatMapping,
address: String,
) -> String {
let mut components = Url::parse(REDIRECT_URL).unwrap();
components
.query_pairs_mut()
.append_pair("apiKey", self.api_key.as_str())
.append_pair("product", "BUY")
.append_pair("onPayAmount", &fiat_amount.to_string())
.append_pair("onPayCurrency", &fiat_currency)
.append_pair("onRevCurrency", &fiat_mapping.symbol)
.append_pair("onToAddress", &address)
.append_pair(
"network",
&fiat_mapping.network.unwrap_or_default().to_uppercase(),
)
.append_pair("mode", "minimal");

return components.as_str().to_string();
}
}
3 changes: 3 additions & 0 deletions crates/fiat/src/providers/kado/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod client;
pub mod model;
pub mod provider;
84 changes: 84 additions & 0 deletions crates/fiat/src/providers/kado/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct Response<T> {
pub data: T,
}

// #[derive(Debug, Deserialize, Clone)]
// #[serde(rename_all = "camelCase")]
// pub struct Assets {
// pub assets: Vec<Asset>,
// }

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Blockchains {
pub blockchains: Vec<Blockchain>,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Blockchain {
pub network: String,
pub associated_assets: Vec<Asset>,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Asset {
pub official_chain_id: String,
pub symbol: String,
pub address: Option<String>,
pub ramp_products: Vec<String>,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Webhook {
#[serde(rename = "type")]
pub webhook_type: String,
pub data: WebhookData,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct WebhookData {
pub id: String,
pub tx_hash: String,
pub currency_type: String,
pub buy_amount: Amount,
pub receive_amount: Amount,
pub processing_fee: Amount,
pub gas_fee: Amount,
pub wallet_address: String,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct QuoteData {
pub quote: Quote,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Quote {
pub receive_unit_count_after_fees: Amount,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
pub amount: f64,
pub unit: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct QuoteQuery {
pub transaction_type: String,
pub blockchain: String,
pub asset: String,
pub amount: f64,
pub currency: String,
}
77 changes: 77 additions & 0 deletions crates/fiat/src/providers/kado/provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use async_trait::async_trait;
use primitives::{
FiatBuyRequest, FiatProviderName, FiatQuote, FiatTransaction, FiatTransactionStatus,
};

use crate::{
model::{FiatMapping, FiatProviderAsset},
FiatProvider,
};

use super::{client::KadoClient, model::Webhook};

#[async_trait]
impl FiatProvider for KadoClient {
fn name(&self) -> FiatProviderName {
Self::NAME
}

async fn get_quote(
&self,
request: FiatBuyRequest,
request_map: FiatMapping,
) -> Result<FiatQuote, Box<dyn std::error::Error + Send + Sync>> {
let quote = self
.get_quote_buy(
request.fiat_currency.clone(),
request_map.symbol.clone(),
request.fiat_amount,
request_map.network.clone().unwrap_or_default(),
)
.await?;

Ok(self.get_fiat_quote(request, request_map.clone(), quote.quote))
}

async fn get_assets(
&self,
) -> Result<Vec<FiatProviderAsset>, Box<dyn std::error::Error + Send + Sync>> {
let assets = self
.get_blockchains()
.await?
.into_iter()
.flat_map(Self::map_blockchain)
.collect::<Vec<FiatProviderAsset>>();
Ok(assets)
}

// full transaction: https://github.com/mercuryoio/api-migration-docs/blob/master/Widget_API_Mercuryo_v1.6.md#22-callbacks-response-body
async fn webhook(
&self,
data: serde_json::Value,
) -> Result<FiatTransaction, Box<dyn std::error::Error + Send + Sync>> {
let data = serde_json::from_value::<Webhook>(data)?;
let status = match data.webhook_type.as_str() {
"order_pending" | "order_processing" => FiatTransactionStatus::Pending,
"order_completed" => FiatTransactionStatus::Complete,
_ => FiatTransactionStatus::Unknown,
};

let transaction = FiatTransaction {
asset_id: None,
symbol: data.data.receive_amount.unit,
provider_id: Self::NAME.id(),
provider_transaction_id: data.data.id,
status,
fiat_amount: data.data.buy_amount.amount,
fiat_currency: data.data.currency_type,
transaction_hash: Some(data.data.tx_hash),
address: Some(data.data.wallet_address),
fee_provider: data.data.processing_fee.amount,
fee_network: data.data.gas_fee.amount,
fee_partner: 0.0,
};

Ok(transaction)
}
}
3 changes: 3 additions & 0 deletions crates/fiat/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ pub use self::transak::client::TransakClient;

pub mod banxa;
pub use self::banxa::client::BanxaClient;

pub mod kado;
pub use self::kado::client::KadoClient;
2 changes: 2 additions & 0 deletions crates/primitives/src/fiat_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum FiatProviderName {
MoonPay,
Ramp,
Banxa,
Kado,
}

impl FiatProviderName {
Expand All @@ -27,6 +28,7 @@ impl FiatProviderName {
FiatProviderName::MoonPay => "MoonPay",
FiatProviderName::Ramp => "Ramp",
FiatProviderName::Banxa => "Banxa",
FiatProviderName::Kado => "Kado",
}
}

Expand Down
Loading

0 comments on commit bd95cfd

Please sign in to comment.