mirror of
https://github.com/serai-dex/serai.git
synced 2025-04-22 22:18:15 +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:
parent
e4cc23b72d
commit
11d48d0685
13 changed files with 393 additions and 42 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -9421,7 +9421,9 @@ dependencies = [
|
|||
"jsonrpsee",
|
||||
"libp2p 0.52.4",
|
||||
"log",
|
||||
"monero-wallet",
|
||||
"pallet-transaction-payment-rpc",
|
||||
"parity-scale-codec",
|
||||
"rand_core",
|
||||
"sc-authority-discovery",
|
||||
"sc-basic-authorship",
|
||||
|
@ -9893,7 +9895,6 @@ dependencies = [
|
|||
"serai-dex-pallet",
|
||||
"serai-primitives",
|
||||
"serai-validator-sets-primitives",
|
||||
"serde",
|
||||
"sp-application-crypto",
|
||||
"sp-consensus-babe",
|
||||
"sp-core",
|
||||
|
|
|
@ -50,7 +50,7 @@ hex = "0.4"
|
|||
|
||||
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"] }
|
||||
schnorrkel = { path = "../../crypto/schnorrkel", package = "frost-schnorrkel" }
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ pub use abi::{primitives, Transaction};
|
|||
use abi::*;
|
||||
|
||||
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 use coins::SeraiCoins;
|
||||
|
@ -317,6 +318,24 @@ impl Serai {
|
|||
) -> Result<Vec<multiaddr::Multiaddr>, SeraiError> {
|
||||
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<'_> {
|
||||
|
|
|
@ -179,7 +179,7 @@ impl SeraiValidatorSets<'_> {
|
|||
&self,
|
||||
network: NetworkId,
|
||||
) -> 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?
|
||||
|
|
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)
|
||||
}
|
||||
|
||||
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.
|
||||
pub fn quote_price_exact_tokens_for_tokens(
|
||||
coin1: Coin,
|
||||
|
@ -1006,20 +1025,24 @@ pub mod pallet {
|
|||
amount: SubstrateAmount,
|
||||
include_fee: bool,
|
||||
) -> Option<SubstrateAmount> {
|
||||
let pool_id = Self::get_pool_id(coin1, coin2).ok()?;
|
||||
let pool_account = Self::get_pool_account(pool_id);
|
||||
let path = Self::get_swap_path_from_coins(coin1, coin2)?;
|
||||
|
||||
let balance1 = Self::get_balance(&pool_account, coin1);
|
||||
let balance2 = Self::get_balance(&pool_account, coin2);
|
||||
if balance1 != 0 {
|
||||
if include_fee {
|
||||
Self::get_amount_out(amount, balance1, balance2).ok()
|
||||
} else {
|
||||
Self::quote(amount, balance1, balance2).ok()
|
||||
let mut amounts: Vec<SubstrateAmount> = vec![amount];
|
||||
for coins_pair in path.windows(2) {
|
||||
if let [coin1, coin2] = coins_pair {
|
||||
let (reserve_in, reserve_out) = Self::get_reserves(coin1, coin2).ok()?;
|
||||
let prev_amount = amounts.last().expect("Always has at least one element");
|
||||
let amount_out = if include_fee {
|
||||
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.
|
||||
|
@ -1029,20 +1052,23 @@ pub mod pallet {
|
|||
amount: SubstrateAmount,
|
||||
include_fee: bool,
|
||||
) -> Option<SubstrateAmount> {
|
||||
let pool_id = Self::get_pool_id(coin1, coin2).ok()?;
|
||||
let pool_account = Self::get_pool_account(pool_id);
|
||||
let path = Self::get_swap_path_from_coins(coin1, coin2)?;
|
||||
|
||||
let balance1 = Self::get_balance(&pool_account, coin1);
|
||||
let balance2 = Self::get_balance(&pool_account, coin2);
|
||||
if balance1 != 0 {
|
||||
if include_fee {
|
||||
Self::get_amount_in(amount, balance1, balance2).ok()
|
||||
} else {
|
||||
Self::quote(amount, balance2, balance1).ok()
|
||||
let mut amounts: Vec<SubstrateAmount> = vec![amount];
|
||||
for coins_pair in path.windows(2).rev() {
|
||||
if let [coin1, coin2] = coins_pair {
|
||||
let (reserve_in, reserve_out) = Self::get_reserves(coin1, coin2).ok()?;
|
||||
let prev_amount = amounts.last().expect("Always has at least one element");
|
||||
let amount_in = if include_fee {
|
||||
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.
|
||||
|
|
|
@ -52,6 +52,8 @@ futures-util = "0.3"
|
|||
tokio = { version = "1", features = ["sync", "rt-multi-thread"] }
|
||||
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-transaction-pool = { 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" }
|
||||
|
||||
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]
|
||||
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};
|
||||
|
||||
|
@ -7,13 +7,17 @@ use sp_block_builder::BlockBuilder;
|
|||
use sp_api::ProvideRuntimeApi;
|
||||
|
||||
use serai_runtime::{
|
||||
primitives::{NetworkId, SubstrateAmount, PublicKey},
|
||||
Nonce, Block, SeraiRuntimeApi,
|
||||
in_instructions::primitives::Shorthand,
|
||||
primitives::{ExternalNetworkId, NetworkId, PublicKey, SubstrateAmount, QuotePriceParams},
|
||||
validator_sets::ValidatorSetsApi,
|
||||
dex::DexApi,
|
||||
Block, Nonce,
|
||||
};
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use jsonrpsee::RpcModule;
|
||||
use jsonrpsee::{RpcModule, core::Error};
|
||||
use scale::Encode;
|
||||
|
||||
pub use sc_rpc_api::DenyUnsafe;
|
||||
use sc_transaction_pool_api::TransactionPool;
|
||||
|
@ -40,11 +44,14 @@ pub fn create_full<
|
|||
where
|
||||
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, PublicKey, Nonce>
|
||||
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, SubstrateAmount>
|
||||
+ SeraiRuntimeApi<Block>
|
||||
+ ValidatorSetsApi<Block>
|
||||
+ DexApi<Block>
|
||||
+ BlockBuilder<Block>,
|
||||
{
|
||||
use substrate_frame_rpc_system::{System, SystemApiServer};
|
||||
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 FullDeps { id, client, pool, deny_unsafe, authority_discovery } = deps;
|
||||
|
@ -54,7 +61,7 @@ where
|
|||
|
||||
if let Some(authority_discovery) = authority_discovery {
|
||||
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(
|
||||
"p2p_validators",
|
||||
|params, context| async move {
|
||||
|
@ -63,7 +70,7 @@ where
|
|||
let latest_block = client.info().best_hash;
|
||||
|
||||
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. {}",
|
||||
"please report this at https://github.com/serai-dex/serai/issues",
|
||||
)))
|
||||
|
@ -99,5 +106,95 @@ where
|
|||
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)
|
||||
}
|
||||
|
|
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;
|
||||
pub use constants::*;
|
||||
|
||||
mod dex;
|
||||
#[allow(unused_imports)]
|
||||
pub use dex::*;
|
||||
|
||||
pub type BlockNumber = u64;
|
||||
pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ use sp_runtime::{
|
|||
|
||||
#[allow(unused_imports)]
|
||||
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,
|
||||
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! {
|
||||
impl sp_api::Core<Block> for Runtime {
|
||||
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> {
|
||||
if network_id == NetworkId::Serai {
|
||||
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 {
|
||||
|
|
|
@ -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-io = { 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-runtime = { 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-io/std",
|
||||
"sp-std/std",
|
||||
"sp-api/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-runtime/std",
|
||||
"sp-session/std",
|
||||
|
|
|
@ -994,6 +994,14 @@ pub mod pallet {
|
|||
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]
|
||||
|
@ -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::*;
|
||||
|
|
Loading…
Reference in a new issue