mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
add Serai JSON-RPC methods (#627)
* add serai rpc methods * fix machete & dex quote price api * fix validators api --------- Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -9421,7 +9421,9 @@ dependencies = [
|
|||||||
"jsonrpsee",
|
"jsonrpsee",
|
||||||
"libp2p 0.52.4",
|
"libp2p 0.52.4",
|
||||||
"log",
|
"log",
|
||||||
|
"monero-wallet",
|
||||||
"pallet-transaction-payment-rpc",
|
"pallet-transaction-payment-rpc",
|
||||||
|
"parity-scale-codec",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"sc-authority-discovery",
|
"sc-authority-discovery",
|
||||||
"sc-basic-authorship",
|
"sc-basic-authorship",
|
||||||
@@ -9893,7 +9895,6 @@ dependencies = [
|
|||||||
"serai-dex-pallet",
|
"serai-dex-pallet",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-validator-sets-primitives",
|
"serai-validator-sets-primitives",
|
||||||
"serde",
|
|
||||||
"sp-application-crypto",
|
"sp-application-crypto",
|
||||||
"sp-consensus-babe",
|
"sp-consensus-babe",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ hex = "0.4"
|
|||||||
|
|
||||||
blake2 = "0.10"
|
blake2 = "0.10"
|
||||||
|
|
||||||
ciphersuite = { path = "../../crypto/ciphersuite", features = ["ristretto"] }
|
ciphersuite = { path = "../../crypto/ciphersuite", features = ["ristretto", "secp256k1"] }
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["tests"] }
|
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["tests"] }
|
||||||
schnorrkel = { path = "../../crypto/schnorrkel", package = "frost-schnorrkel" }
|
schnorrkel = { path = "../../crypto/schnorrkel", package = "frost-schnorrkel" }
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ pub use abi::{primitives, Transaction};
|
|||||||
use abi::*;
|
use abi::*;
|
||||||
|
|
||||||
pub use primitives::{SeraiAddress, Signature, Amount};
|
pub use primitives::{SeraiAddress, Signature, Amount};
|
||||||
use primitives::{Header, ExternalNetworkId};
|
use primitives::{Header, NetworkId, ExternalNetworkId, QuotePriceParams};
|
||||||
|
use crate::in_instructions::primitives::Shorthand;
|
||||||
|
|
||||||
pub mod coins;
|
pub mod coins;
|
||||||
pub use coins::SeraiCoins;
|
pub use coins::SeraiCoins;
|
||||||
@@ -317,6 +318,24 @@ impl Serai {
|
|||||||
) -> Result<Vec<multiaddr::Multiaddr>, SeraiError> {
|
) -> Result<Vec<multiaddr::Multiaddr>, SeraiError> {
|
||||||
self.call("p2p_validators", network).await
|
self.call("p2p_validators", network).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move this to SeraiValidatorSets?
|
||||||
|
pub async fn external_network_address(
|
||||||
|
&self,
|
||||||
|
network: ExternalNetworkId,
|
||||||
|
) -> Result<String, SeraiError> {
|
||||||
|
self.call("external_network_address", network).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move this to SeraiInInstructions?
|
||||||
|
pub async fn encoded_shorthand(&self, shorthand: Shorthand) -> Result<Vec<u8>, SeraiError> {
|
||||||
|
self.call("encoded_shorthand", shorthand).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move this to SeraiDex?
|
||||||
|
pub async fn quote_price(&self, params: QuotePriceParams) -> Result<u64, SeraiError> {
|
||||||
|
self.call("quote_price", params).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TemporalSerai<'_> {
|
impl TemporalSerai<'_> {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ impl SeraiValidatorSets<'_> {
|
|||||||
&self,
|
&self,
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
) -> Result<Vec<Public>, SeraiError> {
|
) -> Result<Vec<Public>, SeraiError> {
|
||||||
self.0.runtime_api("SeraiRuntimeApi_validators", network).await
|
self.0.runtime_api("ValidatorSetsApi_validators", network).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Store these separately since we almost never need both at once?
|
// TODO: Store these separately since we almost never need both at once?
|
||||||
|
|||||||
158
substrate/client/tests/serai-rpc.rs
Normal file
158
substrate/client/tests/serai-rpc.rs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use scale::Decode;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
use ciphersuite::{
|
||||||
|
group::{ff::Field, GroupEncoding},
|
||||||
|
Ciphersuite, Ed25519, Secp256k1,
|
||||||
|
};
|
||||||
|
|
||||||
|
use sp_core::{
|
||||||
|
Pair as PairTrait,
|
||||||
|
sr25519::{Public, Pair},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serai_abi::{
|
||||||
|
in_instructions::primitives::Shorthand,
|
||||||
|
primitives::{
|
||||||
|
insecure_pair_from_name, ExternalBalance, ExternalCoin, ExternalNetworkId, QuotePriceParams,
|
||||||
|
Amount,
|
||||||
|
},
|
||||||
|
validator_sets::primitives::{ExternalValidatorSet, KeyPair, Session},
|
||||||
|
};
|
||||||
|
use serai_client::{Serai, SeraiAddress};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::{validator_sets::set_keys, in_instructions::mint_coin, dex::add_liquidity};
|
||||||
|
|
||||||
|
serai_test!(
|
||||||
|
external_address: (|serai: Serai| async move {
|
||||||
|
test_external_address(serai).await;
|
||||||
|
})
|
||||||
|
|
||||||
|
encoded_shorthand: (|serai: Serai| async move {
|
||||||
|
test_encoded_shorthand(serai).await;
|
||||||
|
})
|
||||||
|
|
||||||
|
dex_quote_price: (|serai: Serai| async move {
|
||||||
|
test_dex_quote_price(serai).await;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
async fn set_network_keys<C: Ciphersuite>(
|
||||||
|
serai: &Serai,
|
||||||
|
set: ExternalValidatorSet,
|
||||||
|
pairs: &[Pair],
|
||||||
|
) {
|
||||||
|
// Ristretto key
|
||||||
|
let mut ristretto_key = [0; 32];
|
||||||
|
OsRng.fill_bytes(&mut ristretto_key);
|
||||||
|
|
||||||
|
// network key
|
||||||
|
let network_priv_key = Zeroizing::new(C::F::random(&mut OsRng));
|
||||||
|
let network_key = (C::generator() * *network_priv_key).to_bytes().as_ref().to_vec();
|
||||||
|
|
||||||
|
let key_pair = KeyPair(Public(ristretto_key), network_key.try_into().unwrap());
|
||||||
|
let _ = set_keys(serai, set, key_pair, pairs).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_external_address(serai: Serai) {
|
||||||
|
let pair = insecure_pair_from_name("Alice");
|
||||||
|
|
||||||
|
// set btc keys
|
||||||
|
let network = ExternalNetworkId::Bitcoin;
|
||||||
|
set_network_keys::<Secp256k1>(
|
||||||
|
&serai,
|
||||||
|
ExternalValidatorSet { session: Session(0), network },
|
||||||
|
&[pair.clone()],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// get the address from the node
|
||||||
|
let btc_address: String = serai.external_network_address(network).await.unwrap();
|
||||||
|
|
||||||
|
// make sure it is a valid address
|
||||||
|
let _ = bitcoin::Address::from_str(&btc_address)
|
||||||
|
.unwrap()
|
||||||
|
.require_network(bitcoin::Network::Bitcoin)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// set monero keys
|
||||||
|
let network = ExternalNetworkId::Monero;
|
||||||
|
set_network_keys::<Ed25519>(
|
||||||
|
&serai,
|
||||||
|
ExternalValidatorSet { session: Session(0), network },
|
||||||
|
&[pair],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// get the address from the node
|
||||||
|
let xmr_address: String = serai.external_network_address(network).await.unwrap();
|
||||||
|
|
||||||
|
// make sure it is a valid address
|
||||||
|
let _ = monero_wallet::address::MoneroAddress::from_str(
|
||||||
|
monero_wallet::address::Network::Mainnet,
|
||||||
|
&xmr_address,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_encoded_shorthand(serai: Serai) {
|
||||||
|
let shorthand = Shorthand::transfer(None, SeraiAddress::new([0u8; 32]));
|
||||||
|
let encoded = serai.encoded_shorthand(shorthand.clone()).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(Shorthand::decode::<&[u8]>(&mut encoded.as_slice()).unwrap(), shorthand);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_dex_quote_price(serai: Serai) {
|
||||||
|
// make a liquid pool to get the quote on
|
||||||
|
let coin1 = ExternalCoin::Bitcoin;
|
||||||
|
let coin2 = ExternalCoin::Monero;
|
||||||
|
let amount1 = Amount(10u64.pow(coin1.decimals()));
|
||||||
|
let amount2 = Amount(10u64.pow(coin2.decimals()));
|
||||||
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
|
||||||
|
// mint sriBTC in the account so that we can add liq.
|
||||||
|
// Ferdie account is already pre-funded with SRI.
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
ExternalBalance { coin: coin1, amount: amount1 },
|
||||||
|
0,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// add liquidity
|
||||||
|
let coin_amount = Amount(amount1.0 / 2);
|
||||||
|
let sri_amount = Amount(amount1.0 / 2);
|
||||||
|
let _ = add_liquidity(&serai, coin1, coin_amount, sri_amount, 0, pair.clone()).await;
|
||||||
|
|
||||||
|
// same for xmr
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
ExternalBalance { coin: coin2, amount: amount2 },
|
||||||
|
0,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// add liquidity
|
||||||
|
let coin_amount = Amount(amount2.0 / 2);
|
||||||
|
let sri_amount = Amount(amount2.0 / 2);
|
||||||
|
let _ = add_liquidity(&serai, coin2, coin_amount, sri_amount, 1, pair.clone()).await;
|
||||||
|
|
||||||
|
// price for BTC -> SRI -> XMR path
|
||||||
|
let params = QuotePriceParams {
|
||||||
|
coin1: coin1.into(),
|
||||||
|
coin2: coin2.into(),
|
||||||
|
amount: coin_amount.0 / 2,
|
||||||
|
include_fee: true,
|
||||||
|
exact_in: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = serai.quote_price(params).await.unwrap();
|
||||||
|
assert!(res > 0);
|
||||||
|
}
|
||||||
@@ -999,6 +999,25 @@ pub mod pallet {
|
|||||||
Ok(amounts)
|
Ok(amounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_swap_path_from_coins(
|
||||||
|
coin1: Coin,
|
||||||
|
coin2: Coin,
|
||||||
|
) -> Option<BoundedVec<Coin, T::MaxSwapPathLength>> {
|
||||||
|
if coin1 == coin2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = if (coin1 == Coin::native() && coin2 != Coin::native()) ||
|
||||||
|
(coin2 == Coin::native() && coin1 != Coin::native())
|
||||||
|
{
|
||||||
|
vec![coin1, coin2]
|
||||||
|
} else {
|
||||||
|
vec![coin1, Coin::native(), coin2]
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(path.try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
/// Used by the RPC service to provide current prices.
|
/// Used by the RPC service to provide current prices.
|
||||||
pub fn quote_price_exact_tokens_for_tokens(
|
pub fn quote_price_exact_tokens_for_tokens(
|
||||||
coin1: Coin,
|
coin1: Coin,
|
||||||
@@ -1006,20 +1025,24 @@ pub mod pallet {
|
|||||||
amount: SubstrateAmount,
|
amount: SubstrateAmount,
|
||||||
include_fee: bool,
|
include_fee: bool,
|
||||||
) -> Option<SubstrateAmount> {
|
) -> Option<SubstrateAmount> {
|
||||||
let pool_id = Self::get_pool_id(coin1, coin2).ok()?;
|
let path = Self::get_swap_path_from_coins(coin1, coin2)?;
|
||||||
let pool_account = Self::get_pool_account(pool_id);
|
|
||||||
|
|
||||||
let balance1 = Self::get_balance(&pool_account, coin1);
|
let mut amounts: Vec<SubstrateAmount> = vec![amount];
|
||||||
let balance2 = Self::get_balance(&pool_account, coin2);
|
for coins_pair in path.windows(2) {
|
||||||
if balance1 != 0 {
|
if let [coin1, coin2] = coins_pair {
|
||||||
if include_fee {
|
let (reserve_in, reserve_out) = Self::get_reserves(coin1, coin2).ok()?;
|
||||||
Self::get_amount_out(amount, balance1, balance2).ok()
|
let prev_amount = amounts.last().expect("Always has at least one element");
|
||||||
} else {
|
let amount_out = if include_fee {
|
||||||
Self::quote(amount, balance1, balance2).ok()
|
Self::get_amount_out(*prev_amount, reserve_in, reserve_out).ok()?
|
||||||
|
} else {
|
||||||
|
Self::quote(*prev_amount, reserve_in, reserve_out).ok()?
|
||||||
|
};
|
||||||
|
|
||||||
|
amounts.push(amount_out);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(*amounts.last().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the RPC service to provide current prices.
|
/// Used by the RPC service to provide current prices.
|
||||||
@@ -1029,20 +1052,23 @@ pub mod pallet {
|
|||||||
amount: SubstrateAmount,
|
amount: SubstrateAmount,
|
||||||
include_fee: bool,
|
include_fee: bool,
|
||||||
) -> Option<SubstrateAmount> {
|
) -> Option<SubstrateAmount> {
|
||||||
let pool_id = Self::get_pool_id(coin1, coin2).ok()?;
|
let path = Self::get_swap_path_from_coins(coin1, coin2)?;
|
||||||
let pool_account = Self::get_pool_account(pool_id);
|
|
||||||
|
|
||||||
let balance1 = Self::get_balance(&pool_account, coin1);
|
let mut amounts: Vec<SubstrateAmount> = vec![amount];
|
||||||
let balance2 = Self::get_balance(&pool_account, coin2);
|
for coins_pair in path.windows(2).rev() {
|
||||||
if balance1 != 0 {
|
if let [coin1, coin2] = coins_pair {
|
||||||
if include_fee {
|
let (reserve_in, reserve_out) = Self::get_reserves(coin1, coin2).ok()?;
|
||||||
Self::get_amount_in(amount, balance1, balance2).ok()
|
let prev_amount = amounts.last().expect("Always has at least one element");
|
||||||
} else {
|
let amount_in = if include_fee {
|
||||||
Self::quote(amount, balance2, balance1).ok()
|
Self::get_amount_in(*prev_amount, reserve_in, reserve_out).ok()?
|
||||||
|
} else {
|
||||||
|
Self::quote(*prev_amount, reserve_out, reserve_in).ok()?
|
||||||
|
};
|
||||||
|
amounts.push(amount_in);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(*amounts.last().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the optimal amount from the reserves.
|
/// Calculates the optimal amount from the reserves.
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ futures-util = "0.3"
|
|||||||
tokio = { version = "1", features = ["sync", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["sync", "rt-multi-thread"] }
|
||||||
jsonrpsee = { version = "0.16", features = ["server"] }
|
jsonrpsee = { version = "0.16", features = ["server"] }
|
||||||
|
|
||||||
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
sc-offchain = { git = "https://github.com/serai-dex/substrate" }
|
sc-offchain = { git = "https://github.com/serai-dex/substrate" }
|
||||||
sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" }
|
sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" }
|
||||||
sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" }
|
sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" }
|
||||||
@@ -77,6 +79,11 @@ pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate
|
|||||||
|
|
||||||
serai-env = { path = "../../common/env" }
|
serai-env = { path = "../../common/env" }
|
||||||
|
|
||||||
|
bitcoin-serai = { path = "../../networks/bitcoin", default-features = false, features = ["std", "hazmat"] }
|
||||||
|
monero-wallet = { path = "../../networks/monero/wallet", default-features = false, features = ["std"] }
|
||||||
|
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["ed25519", "secp256k1"] }
|
||||||
|
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate" }
|
substrate-build-script-utils = { git = "https://github.com/serai-dex/substrate" }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{sync::Arc, collections::HashSet};
|
use std::{sync::Arc, ops::Deref, collections::HashSet};
|
||||||
|
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
|
|
||||||
@@ -7,13 +7,17 @@ use sp_block_builder::BlockBuilder;
|
|||||||
use sp_api::ProvideRuntimeApi;
|
use sp_api::ProvideRuntimeApi;
|
||||||
|
|
||||||
use serai_runtime::{
|
use serai_runtime::{
|
||||||
primitives::{NetworkId, SubstrateAmount, PublicKey},
|
in_instructions::primitives::Shorthand,
|
||||||
Nonce, Block, SeraiRuntimeApi,
|
primitives::{ExternalNetworkId, NetworkId, PublicKey, SubstrateAmount, QuotePriceParams},
|
||||||
|
validator_sets::ValidatorSetsApi,
|
||||||
|
dex::DexApi,
|
||||||
|
Block, Nonce,
|
||||||
};
|
};
|
||||||
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use jsonrpsee::RpcModule;
|
use jsonrpsee::{RpcModule, core::Error};
|
||||||
|
use scale::Encode;
|
||||||
|
|
||||||
pub use sc_rpc_api::DenyUnsafe;
|
pub use sc_rpc_api::DenyUnsafe;
|
||||||
use sc_transaction_pool_api::TransactionPool;
|
use sc_transaction_pool_api::TransactionPool;
|
||||||
@@ -40,11 +44,14 @@ pub fn create_full<
|
|||||||
where
|
where
|
||||||
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, PublicKey, Nonce>
|
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, PublicKey, Nonce>
|
||||||
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, SubstrateAmount>
|
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, SubstrateAmount>
|
||||||
+ SeraiRuntimeApi<Block>
|
+ ValidatorSetsApi<Block>
|
||||||
|
+ DexApi<Block>
|
||||||
+ BlockBuilder<Block>,
|
+ BlockBuilder<Block>,
|
||||||
{
|
{
|
||||||
use substrate_frame_rpc_system::{System, SystemApiServer};
|
use substrate_frame_rpc_system::{System, SystemApiServer};
|
||||||
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer};
|
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer};
|
||||||
|
use ciphersuite::{Ciphersuite, Ed25519, Secp256k1};
|
||||||
|
use bitcoin_serai::{bitcoin, crypto::x_only};
|
||||||
|
|
||||||
let mut module = RpcModule::new(());
|
let mut module = RpcModule::new(());
|
||||||
let FullDeps { id, client, pool, deny_unsafe, authority_discovery } = deps;
|
let FullDeps { id, client, pool, deny_unsafe, authority_discovery } = deps;
|
||||||
@@ -54,7 +61,7 @@ where
|
|||||||
|
|
||||||
if let Some(authority_discovery) = authority_discovery {
|
if let Some(authority_discovery) = authority_discovery {
|
||||||
let mut authority_discovery_module =
|
let mut authority_discovery_module =
|
||||||
RpcModule::new((id, client, RwLock::new(authority_discovery)));
|
RpcModule::new((id, client.clone(), RwLock::new(authority_discovery)));
|
||||||
authority_discovery_module.register_async_method(
|
authority_discovery_module.register_async_method(
|
||||||
"p2p_validators",
|
"p2p_validators",
|
||||||
|params, context| async move {
|
|params, context| async move {
|
||||||
@@ -63,7 +70,7 @@ where
|
|||||||
let latest_block = client.info().best_hash;
|
let latest_block = client.info().best_hash;
|
||||||
|
|
||||||
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
|
let validators = client.runtime_api().validators(latest_block, network).map_err(|_| {
|
||||||
jsonrpsee::core::Error::to_call_error(std::io::Error::other(format!(
|
Error::to_call_error(std::io::Error::other(format!(
|
||||||
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
|
"couldn't get validators from the latest block, which is likely a fatal bug. {}",
|
||||||
"please report this at https://github.com/serai-dex/serai/issues",
|
"please report this at https://github.com/serai-dex/serai/issues",
|
||||||
)))
|
)))
|
||||||
@@ -99,5 +106,95 @@ where
|
|||||||
module.merge(authority_discovery_module)?;
|
module.merge(authority_discovery_module)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut serai_json_module = RpcModule::new(client);
|
||||||
|
|
||||||
|
// add network address rpc
|
||||||
|
serai_json_module.register_async_method(
|
||||||
|
"external_network_address",
|
||||||
|
|params, context| async move {
|
||||||
|
let network: ExternalNetworkId = params.parse()?;
|
||||||
|
let client = &*context;
|
||||||
|
let latest_block = client.info().best_hash;
|
||||||
|
|
||||||
|
let external_key = client
|
||||||
|
.runtime_api()
|
||||||
|
.external_network_key(latest_block, network)
|
||||||
|
.map_err(|_| Error::Custom("api call error".to_string()))?
|
||||||
|
.ok_or(Error::Custom("no address for the network".to_string()))?;
|
||||||
|
|
||||||
|
match network {
|
||||||
|
ExternalNetworkId::Bitcoin => {
|
||||||
|
let key = <Secp256k1 as Ciphersuite>::read_G::<&[u8]>(&mut external_key.as_slice())
|
||||||
|
.map_err(|_| Error::Custom("invalid key stored in db".to_string()))?;
|
||||||
|
|
||||||
|
let addr = bitcoin::Address::p2tr_tweaked(
|
||||||
|
bitcoin::key::TweakedPublicKey::dangerous_assume_tweaked(x_only(&key)),
|
||||||
|
bitcoin::address::KnownHrp::Mainnet,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(addr.to_string())
|
||||||
|
}
|
||||||
|
// We don't know the eth address before the smart contract is deployed.
|
||||||
|
ExternalNetworkId::Ethereum => Ok(String::new()),
|
||||||
|
ExternalNetworkId::Monero => {
|
||||||
|
let view_private = zeroize::Zeroizing::new(
|
||||||
|
<Ed25519 as Ciphersuite>::hash_to_F(
|
||||||
|
b"Serai DEX Additional Key",
|
||||||
|
&["Monero".as_bytes(), &0u64.to_le_bytes()].concat(),
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let spend = <Ed25519 as Ciphersuite>::read_G::<&[u8]>(&mut external_key.as_slice())
|
||||||
|
.map_err(|_| Error::Custom("invalid key stored in db".to_string()))?;
|
||||||
|
|
||||||
|
let addr = monero_wallet::address::MoneroAddress::new(
|
||||||
|
monero_wallet::address::Network::Mainnet,
|
||||||
|
monero_wallet::address::AddressType::Featured {
|
||||||
|
subaddress: false,
|
||||||
|
payment_id: None,
|
||||||
|
guaranteed: true,
|
||||||
|
},
|
||||||
|
*spend,
|
||||||
|
view_private.deref() * curve25519_dalek::constants::ED25519_BASEPOINT_TABLE,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(addr.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// add shorthand encoding rpc
|
||||||
|
serai_json_module.register_async_method("encoded_shorthand", |params, _| async move {
|
||||||
|
// decode using serde and encode back using scale
|
||||||
|
let shorthand: Shorthand = params.parse()?;
|
||||||
|
Ok(shorthand.encode())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// add simulating a swap path rpc
|
||||||
|
serai_json_module.register_async_method("quote_price", |params, context| async move {
|
||||||
|
let client = &*context;
|
||||||
|
let latest_block = client.info().best_hash;
|
||||||
|
let QuotePriceParams { coin1, coin2, amount, include_fee, exact_in } = params.parse()?;
|
||||||
|
|
||||||
|
let amount = if exact_in {
|
||||||
|
client
|
||||||
|
.runtime_api()
|
||||||
|
.quote_price_exact_tokens_for_tokens(latest_block, coin1, coin2, amount, include_fee)
|
||||||
|
.map_err(|_| Error::Custom("api call error".to_string()))?
|
||||||
|
.ok_or(Error::Custom("invalid params or empty pool".to_string()))?
|
||||||
|
} else {
|
||||||
|
client
|
||||||
|
.runtime_api()
|
||||||
|
.quote_price_tokens_for_exact_tokens(latest_block, coin1, coin2, amount, include_fee)
|
||||||
|
.map_err(|_| Error::Custom("api call error".to_string()))?
|
||||||
|
.ok_or(Error::Custom("invalid params or empty pool".to_string()))?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(amount)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
module.merge(serai_json_module)?;
|
||||||
Ok(module)
|
Ok(module)
|
||||||
}
|
}
|
||||||
|
|||||||
19
substrate/primitives/src/dex.rs
Normal file
19
substrate/primitives/src/dex.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#[cfg(feature = "borsh")]
|
||||||
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use scale::{Encode, Decode, MaxEncodedLen};
|
||||||
|
|
||||||
|
use crate::{Coin, SubstrateAmount};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen)]
|
||||||
|
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct QuotePriceParams {
|
||||||
|
pub coin1: Coin,
|
||||||
|
pub coin2: Coin,
|
||||||
|
pub amount: SubstrateAmount,
|
||||||
|
pub include_fee: bool,
|
||||||
|
pub exact_in: bool,
|
||||||
|
}
|
||||||
@@ -40,6 +40,10 @@ pub use account::*;
|
|||||||
mod constants;
|
mod constants;
|
||||||
pub use constants::*;
|
pub use constants::*;
|
||||||
|
|
||||||
|
mod dex;
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub use dex::*;
|
||||||
|
|
||||||
pub type BlockNumber = u64;
|
pub type BlockNumber = u64;
|
||||||
pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
|
pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ use sp_runtime::{
|
|||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use primitives::{
|
use primitives::{
|
||||||
NetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, EXTERNAL_NETWORKS,
|
NetworkId, ExternalNetworkId, PublicKey, AccountLookup, SubstrateAmount, Coin, EXTERNAL_NETWORKS,
|
||||||
MEDIAN_PRICE_WINDOW_LENGTH, HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE,
|
MEDIAN_PRICE_WINDOW_LENGTH, HOURS, DAYS, MINUTES, TARGET_BLOCK_TIME, BLOCK_SIZE,
|
||||||
FAST_EPOCH_DURATION,
|
FAST_EPOCH_DURATION,
|
||||||
};
|
};
|
||||||
@@ -374,13 +374,6 @@ mod benches {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sp_api::decl_runtime_apis! {
|
|
||||||
#[api_version(1)]
|
|
||||||
pub trait SeraiRuntimeApi {
|
|
||||||
fn validators(network_id: NetworkId) -> Vec<PublicKey>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sp_api::impl_runtime_apis! {
|
sp_api::impl_runtime_apis! {
|
||||||
impl sp_api::Core<Block> for Runtime {
|
impl sp_api::Core<Block> for Runtime {
|
||||||
fn version() -> RuntimeVersion {
|
fn version() -> RuntimeVersion {
|
||||||
@@ -589,7 +582,7 @@ sp_api::impl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::SeraiRuntimeApi<Block> for Runtime {
|
impl validator_sets::ValidatorSetsApi<Block> for Runtime {
|
||||||
fn validators(network_id: NetworkId) -> Vec<PublicKey> {
|
fn validators(network_id: NetworkId) -> Vec<PublicKey> {
|
||||||
if network_id == NetworkId::Serai {
|
if network_id == NetworkId::Serai {
|
||||||
Babe::authorities()
|
Babe::authorities()
|
||||||
@@ -604,6 +597,10 @@ sp_api::impl_runtime_apis! {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn external_network_key(network: ExternalNetworkId) -> Option<Vec<u8>> {
|
||||||
|
ValidatorSets::external_network_key(network)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dex::DexApi<Block> for Runtime {
|
impl dex::DexApi<Block> for Runtime {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ serde = { version = "1", default-features = false, features = ["derive", "alloc"
|
|||||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-api = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
@@ -65,6 +66,7 @@ std = [
|
|||||||
"sp-core/std",
|
"sp-core/std",
|
||||||
"sp-io/std",
|
"sp-io/std",
|
||||||
"sp-std/std",
|
"sp-std/std",
|
||||||
|
"sp-api/std",
|
||||||
"sp-application-crypto/std",
|
"sp-application-crypto/std",
|
||||||
"sp-runtime/std",
|
"sp-runtime/std",
|
||||||
"sp-session/std",
|
"sp-session/std",
|
||||||
|
|||||||
@@ -994,6 +994,14 @@ pub mod pallet {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the external network key for a given external network
|
||||||
|
pub fn external_network_key(network: ExternalNetworkId) -> Option<Vec<u8>> {
|
||||||
|
let current_session = Self::session(NetworkId::from(network))?;
|
||||||
|
let keys = Keys::<T>::get(ExternalValidatorSet { network, session: current_session })?;
|
||||||
|
|
||||||
|
Some(keys.1.into_inner())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::call]
|
#[pallet::call]
|
||||||
@@ -1365,4 +1373,17 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sp_api::decl_runtime_apis! {
|
||||||
|
#[api_version(1)]
|
||||||
|
pub trait ValidatorSetsApi {
|
||||||
|
/// Returns the validator set for a given network.
|
||||||
|
fn validators(network_id: NetworkId) -> Vec<PublicKey>;
|
||||||
|
|
||||||
|
/// Returns the external network key for a given external network.
|
||||||
|
fn external_network_key(
|
||||||
|
network: ExternalNetworkId,
|
||||||
|
) -> Option<Vec<u8>>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user