-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b4b0e55
commit bd95cfd
Showing
9 changed files
with
351 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod client; | ||
pub mod model; | ||
pub mod provider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.