Skip to content

Commit

Permalink
add code to connect 1inch api, add rpcs to use classic swap api
Browse files Browse the repository at this point in the history
  • Loading branch information
dimxy committed Sep 16, 2024
1 parent aad3739 commit 5df753b
Show file tree
Hide file tree
Showing 20 changed files with 1,377 additions and 9 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ members = [
"mm2src/proxy_signature",
"mm2src/rpc_task",
"mm2src/trezor",
"mm2src/trading_api",
]

exclude = [
Expand Down
4 changes: 2 additions & 2 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4527,7 +4527,7 @@ impl EthCoin {
self.call(request, Some(BlockId::Number(BlockNumber::Latest))).await
}

fn allowance(&self, spender: Address) -> Web3RpcFut<U256> {
pub(crate) fn allowance(&self, spender: Address) -> Web3RpcFut<U256> {
let coin = self.clone();
let fut = async move {
match coin.coin_type {
Expand Down Expand Up @@ -4592,7 +4592,7 @@ impl EthCoin {
Box::new(fut.boxed().compat())
}

fn approve(&self, spender: Address, amount: U256) -> EthTxFut {
pub(crate) fn approve(&self, spender: Address, amount: U256) -> EthTxFut {
let coin = self.clone();
let fut = async move {
let token_addr = match coin.coin_type {
Expand Down
90 changes: 86 additions & 4 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#[macro_use] extern crate serde_json;
#[macro_use] extern crate ser_error_derive;

use crate::eth::Web3RpcError;
use async_trait::async_trait;
use base58::FromBase58Error;
use bip32::ExtendedPrivateKey;
Expand Down Expand Up @@ -241,10 +242,9 @@ pub mod coins_tests;

pub mod eth;
use eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err};
use eth::GetValidEthWithdrawAddError;
use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails,
GetEthAddressError, SignedEthTx};
use ethereum_types::U256;
use eth::{eth_coin_from_conf_and_request, get_eth_address, u256_to_big_decimal, wei_from_big_decimal, EthCoin,
EthGasDetailsErr, EthTxFeeDetails, GetEthAddressError, GetValidEthWithdrawAddError, SignedEthTx};
use ethereum_types::{Address as EthAddress, U256};

pub mod hd_wallet;
use hd_wallet::{AccountUpdatingError, AddressDerivingError, HDAccountOps, HDAddressId, HDAddressOps, HDCoinAddress,
Expand Down Expand Up @@ -713,6 +713,10 @@ impl TransactionErr {
}
}

impl std::fmt::Display for TransactionErr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.get_plain_text_format()) }
}

#[derive(Debug, PartialEq)]
pub enum FoundSwapTxSpend {
Spent(TransactionEnum),
Expand Down Expand Up @@ -5751,3 +5755,81 @@ pub mod for_tests {
}
}
}

#[derive(Debug, Deserialize)]
pub struct Erc20ApproveRequest {
coin: String,
spender: EthAddress,
amount: BigDecimal,
}

#[derive(Debug, Deserialize)]
pub struct Erc20AllowanceRequest {
coin: String,
spender: EthAddress,
}

#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum Erc20CallError {
#[display(fmt = "No such coin {}", coin)]
NoSuchCoin { coin: String },
#[display(fmt = "Coin not supported {}", coin)]
CoinNotSupported { coin: String },
#[from_stringify("NumConversError")]
#[display(fmt = "Invalid param: {}", _0)]
InvalidParam(String),
#[from_stringify("TransactionErr")]
#[display(fmt = "Transaction error {}", _0)]
TransactionError(String),
#[from_stringify("Web3RpcError")]
#[display(fmt = "Web3 RPC error {}", _0)]
Web3RpcError(String),
}

impl HttpStatusCode for Erc20CallError {
fn status_code(&self) -> StatusCode {
match self {
Erc20CallError::NoSuchCoin { .. }
| Erc20CallError::CoinNotSupported { .. }
| Erc20CallError::InvalidParam(_)
| Erc20CallError::TransactionError(_)
| Erc20CallError::Web3RpcError(_) => StatusCode::BAD_REQUEST,
}
}
}

type Erc20AllowanceResult = MmResult<BigDecimal, Erc20CallError>;
type Erc20ApproveResult = MmResult<BytesJson, Erc20CallError>;

/// Call allowance for ERC20 tokens
/// Returns BigDecimal value
pub async fn allowance_rpc(ctx: MmArc, req: Erc20AllowanceRequest) -> Erc20AllowanceResult {
let coin = lp_coinfind_or_err(&ctx, &req.coin)
.await
.mm_err(|_| Erc20CallError::NoSuchCoin { coin: req.coin.clone() })?;
match coin {
MmCoinEnum::EthCoin(eth_coin) => {
let wei = eth_coin.allowance(req.spender).compat().await?;
let amount = u256_to_big_decimal(wei, eth_coin.decimals())?;
Ok(amount)
},
_ => Err(MmError::new(Erc20CallError::CoinNotSupported { coin: req.coin })),
}
}

/// Call approve for ERC20 coins
/// Returns signed transaction to send to the chain
pub async fn approve_rpc(ctx: MmArc, req: Erc20ApproveRequest) -> Erc20ApproveResult {
let coin = lp_coinfind_or_err(&ctx, &req.coin)
.await
.mm_err(|_| Erc20CallError::NoSuchCoin { coin: req.coin.clone() })?;
match coin {
MmCoinEnum::EthCoin(eth_coin) => {
let amount = wei_from_big_decimal(&req.amount, eth_coin.decimals())?;
let tx = eth_coin.approve(req.spender, amount).compat().await?;
Ok(tx.tx_hash_as_bytes())
},
_ => Err(MmError::new(Erc20CallError::CoinNotSupported { coin: req.coin })),
}
}
7 changes: 7 additions & 0 deletions mm2src/common/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,13 @@ pub fn http_uri_to_ws_address(uri: http::Uri) -> String {
format!("{}{}{}{}", address_prefix, host_address, port, path)
}

#[macro_export]
macro_rules! str_strip_0x {
($s: expr) => {
$s.strip_prefix("0x").unwrap_or($s)
};
}

#[test]
fn test_http_uri_to_ws_address() {
let uri = "https://cosmos-rpc.polkachu.com".parse::<http::Uri>().unwrap();
Expand Down
2 changes: 2 additions & 0 deletions mm2src/mm2_main/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ mm2_net = { path = "../mm2_net", features = ["event-stream", "p2p"] }
mm2_number = { path = "../mm2_number" }
mm2_rpc = { path = "../mm2_rpc", features = ["rpc_facilities"]}
mm2_state_machine = { path = "../mm2_state_machine" }
trading_api = { path = "../trading_api" }
num-traits = "0.2"
parity-util-mem = "0.11"
parking_lot = { version = "0.12.0", features = ["nightly"] }
primitives = { path = "../mm2_bitcoin/primitives" }
primitive-types = "0.11.1"
prost = "0.11"
rand = { version = "0.7", features = ["std", "small_rng"] }
rand6 = { version = "0.6", package = "rand" }
Expand Down
4 changes: 4 additions & 0 deletions mm2src/mm2_main/src/ext_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// RPCs to integrate with external third party trading API

/// RPCs to access 1inch eth-like swap API
pub mod one_inch;
4 changes: 4 additions & 0 deletions mm2src/mm2_main/src/ext_api/one_inch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod errors;
pub mod rpcs;
/// RPC imlementation to integrate 1inch api
pub mod types;
83 changes: 83 additions & 0 deletions mm2src/mm2_main/src/ext_api/one_inch/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use coins::{eth::u256_to_big_decimal, NumConversError};
use common::{HttpStatusCode, StatusCode};
use enum_derives::EnumFromStringify;
use mm2_number::BigDecimal;
use ser_error_derive::SerializeErrorType;
use serde::Serialize;
use trading_api::one_inch_api::errors::ApiClientError;

#[derive(Debug, Display, Serialize, SerializeErrorType, EnumFromStringify)]
#[serde(tag = "error_type", content = "error_data")]
pub enum ApiIntegrationRpcError {
#[from_stringify("coins::CoinFindError")]
CoinFindError(String),
#[display(fmt = "EVM token needed")]
CoinTypeError,
#[display(fmt = "NFT not supported")]
NftNotSupported,
#[display(fmt = "Chain not supported")]
ChainNotSupported,
#[from_stringify("coins::UnexpectedDerivationMethod")]
MyAddressError(String),
InvalidParam(String),
#[display(fmt = "allowance not enough for 1inch contract, available: {allowance}, needed: {amount}")]
OneInchAllowanceNotEnough {
allowance: BigDecimal,
amount: BigDecimal,
},
#[display(fmt = "1inch API error: {}", _0)]
OneInchError(ApiClientError),
ApiDataError(String),
}

impl HttpStatusCode for ApiIntegrationRpcError {
fn status_code(&self) -> StatusCode {
match self {
ApiIntegrationRpcError::CoinFindError(_)
| ApiIntegrationRpcError::CoinTypeError
| ApiIntegrationRpcError::NftNotSupported
| ApiIntegrationRpcError::ChainNotSupported
| ApiIntegrationRpcError::MyAddressError(_)
| ApiIntegrationRpcError::InvalidParam(_)
| ApiIntegrationRpcError::OneInchAllowanceNotEnough { .. } => StatusCode::BAD_REQUEST,
ApiIntegrationRpcError::OneInchError(_) | ApiIntegrationRpcError::ApiDataError(_) => {
StatusCode::BAD_GATEWAY
},
}
}
}

impl ApiIntegrationRpcError {
pub(crate) fn from_api_error(error: ApiClientError, decimals: u8) -> Self {
match error {
ApiClientError::InvalidParam(error) => ApiIntegrationRpcError::InvalidParam(error),
ApiClientError::HttpClientError(_)
| ApiClientError::ParseBodyError(_)
| ApiClientError::GeneralApiError(_) => ApiIntegrationRpcError::OneInchError(error),
ApiClientError::AllowanceNotEnough(nested_err) => ApiIntegrationRpcError::OneInchAllowanceNotEnough {
allowance: u256_to_big_decimal(nested_err.allowance, decimals).unwrap_or_default(),
amount: u256_to_big_decimal(nested_err.amount, decimals).unwrap_or_default(),
},
}
}
}

/// Error aggregator for errors of conversion of api returned values
#[derive(Debug, Display, Serialize)]
pub(crate) struct FromApiValueError(String);

impl From<NumConversError> for FromApiValueError {
fn from(err: NumConversError) -> Self { Self(err.to_string()) }
}

impl From<primitive_types::Error> for FromApiValueError {
fn from(err: primitive_types::Error) -> Self { Self(format!("{:?}", err)) }
}

impl From<hex::FromHexError> for FromApiValueError {
fn from(err: hex::FromHexError) -> Self { Self(err.to_string()) }
}

impl From<ethereum_types::FromDecStrErr> for FromApiValueError {
fn from(err: ethereum_types::FromDecStrErr) -> Self { Self(err.to_string()) }
}
Loading

0 comments on commit 5df753b

Please sign in to comment.