Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat client #11

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
944 changes: 897 additions & 47 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ members = [
'runtime',
'pallets/*',
'support',
'primitives'
'primitives',
'tfchain-client'
]

[profile.release]
Expand Down
36 changes: 36 additions & 0 deletions tfchain-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "tfchain-client"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
path = "src/lib.rs"
name = "tfchain_client"

[dependencies]
codec = {package = "parity-scale-codec", workspace = true, features = ["derive"]}
subxt = "0.27.1"
subxt-codegen = "0.27.1"
tokio = { version = "1.8", features = ["rt-multi-thread", "macros", "time"] }
serde = { workspace = true, features = ["derive"] }

# Substrate stuff
sp-core = { version = "16.0.0", default-features = false }
frame-system = { version = "16.0.0", default-features = false }
sp-std.workspace = true
sp-runtime.workspace = true
sp-io.workspace = true
pallet-balances.workspace = true

[features]
default = ['std']
std = [
'sp-std/std',
'sp-runtime/std',
'sp-io/std',
'sp-core/std',
'frame-system/std',
'pallet-balances/std'
]
Binary file added tfchain-client/artifacts/mainnet.scale
Binary file not shown.
171 changes: 171 additions & 0 deletions tfchain-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
pub mod runtimes;
use crate::runtimes::mainnet;
use crate::runtimes::types::*;
use serde::{Deserialize, Serialize};
use sp_core::{crypto::SecretStringError, ed25519, sr25519, Pair};
use std::str::FromStr;
use subxt::config::extrinsic_params::BaseExtrinsicParams;
use subxt::config::polkadot::PlainTip;
use subxt::config::WithExtrinsicParams;
use subxt::SubstrateConfig;
use subxt::{
tx::{PairSigner, Signer},
Error, OnlineClient, PolkadotConfig,
};

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Runtime {
// Local,
// Devnet,
// Testnet,
Mainnet,
}

impl FromStr for Runtime {
type Err = &'static str;

fn from_str(v: &str) -> Result<Self, Self::Err> {
match v {
// "local" => Ok(Self::Local),
// "devnet" => Ok(Self::Devnet),
"mainnet" => Ok(Self::Mainnet),
// "testnet" => Ok(Self::Testnet),
_ => Err("unknown runtime"),
}
}
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum KeyType {
Sr25519,
Ed25519,
}

impl FromStr for KeyType {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"sr25519" => Ok(Self::Sr25519),
"ed25519" => Ok(Self::Ed25519),
_ => Err("unknown key type"),
}
}
}

#[derive(Clone)]
pub enum KeyPair {
Sr25519(sr25519::Pair),
Ed25519(ed25519::Pair),
}

impl KeyPair {
// create a key pair from a seed prefixed with `0x`. or a BIP-39 phrase
pub fn from_phrase<S: AsRef<str>>(
k: KeyType,
phrase: S,
password: Option<&str>,
) -> Result<Self, SecretStringError> {
let phrase = phrase.as_ref();

let pair = match k {
KeyType::Sr25519 => {
let pair: sr25519::Pair = Pair::from_string(phrase, password)?;
Self::Sr25519(pair)
},
KeyType::Ed25519 => {
let pair: ed25519::Pair = Pair::from_string(phrase, password)?;
Self::Ed25519(pair)
},
};

Ok(pair)
}

pub fn signer(&self) -> KeypairSigner {
match self {
Self::Ed25519(pair) => KeypairSigner(Box::new(PairSigner::new(pair.clone()))),
Self::Sr25519(pair) => KeypairSigner(Box::new(PairSigner::new(pair.clone()))),
}
}
}

pub struct KeypairSigner(Box<dyn Signer<PolkadotConfig> + Send + Sync>);

impl
subxt::tx::Signer<
WithExtrinsicParams<SubstrateConfig, BaseExtrinsicParams<SubstrateConfig, PlainTip>>,
> for KeypairSigner
{
fn account_id(&self) -> &<WithExtrinsicParams<SubstrateConfig, BaseExtrinsicParams<SubstrateConfig, PlainTip>> as subxt::Config>::AccountId{
self.0.account_id()
}

fn address(&self) -> <WithExtrinsicParams<SubstrateConfig, BaseExtrinsicParams<SubstrateConfig, PlainTip>> as subxt::Config>::Address{
self.0.address()
}

fn sign(&self, signer_payload: &[u8]) -> <WithExtrinsicParams<SubstrateConfig, BaseExtrinsicParams<SubstrateConfig, PlainTip>> as subxt::Config>::Signature{
self.0.sign(signer_payload)
}
}

impl From<sr25519::Pair> for KeyPair {
fn from(value: sr25519::Pair) -> Self {
Self::Sr25519(value)
}
}

impl From<ed25519::Pair> for KeyPair {
fn from(value: ed25519::Pair) -> Self {
Self::Ed25519(value)
}
}

#[derive(Clone)]
pub struct Client {
pub runtime: Runtime,
pub api: OnlineClient<PolkadotConfig>,
}

macro_rules! call {
($self:ident, $name:ident, $($arg:expr),+) => (
match $self.runtime {
// Runtime::Local => local::$name($self, $($arg),+).await,
// Runtime::Devnet => devnet::$name($self, $($arg),+).await,
// Runtime::Testnet => testnet::$name($self, $($arg),+).await,
Runtime::Mainnet => mainnet::$name($self, $($arg),+).await,
}
)
}

impl Client {
pub async fn new<U: AsRef<str>>(url: U, runtime: Runtime) -> Result<Client, Error> {
let api = OnlineClient::<PolkadotConfig>::from_url(url).await?;

Ok(Client { api, runtime })
}

pub async fn get_balance(
&self,
account: &AccountId32,
at_block: Option<Hash>,
) -> Result<Option<SystemAccountInfo>, Error> {
call!(self, get_balance, account, at_block)
}

pub async fn get_block_hash(
&self,
block_number: Option<BlockNumber>,
) -> Result<Option<Hash>, Error> {
call!(self, get_block_hash, block_number)
}

pub async fn transfer_native(
&self,
keypair: &KeyPair,
to: AccountId32,
amount: u128,
) -> Result<Hash, Error> {
call!(self, transfer_native, keypair, to, amount)
}
}
73 changes: 73 additions & 0 deletions tfchain-client/src/runtimes/mainnet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use super::types::*;
use crate::{Client, KeyPair};
use subxt::{subxt, Error};

#[subxt(runtime_metadata_path = "artifacts/mainnet.scale", derive_for_all_types = "Eq, PartialEq")]
pub mod mainnet {}
pub use mainnet::runtime_types::frame_system::AccountInfo;

pub type AccountData = mainnet::runtime_types::pallet_balances::AccountData<u128>;
pub type SystemAccountInfo = AccountInfo<u32, AccountData>;

use super::types::SystemAccountInfo as GenericAccountInfo;

pub async fn get_block_hash(
cl: &Client,
block_number: Option<BlockNumber>,
) -> Result<Option<Hash>, Error> {
cl.api.rpc().block_hash(block_number).await
}

pub async fn get_balance(
cl: &Client,
account: &AccountId32,
at_block: Option<Hash>,
) -> Result<Option<GenericAccountInfo>, Error> {
Ok(cl
.api
.storage()
.at(at_block)
.await?
.fetch(&mainnet::storage().system().account(account))
.await?
.map(|t| GenericAccountInfo::from(t)))
}

// Wrapper around the transfer function of the balances pallet
// This function will wait for the transaction to be finalized and return the block hash
// If the transaction fails, it will return an error
// If the transaction is successful, it will return the block hash
pub async fn transfer_native(
cl: &Client,
kp: &KeyPair,
dest: AccountId32,
amount: u128,
) -> Result<Hash, Error> {
let transfer_tx = mainnet::tx().balances().transfer(dest.clone().into(), amount);

let s = &kp.signer();

let events = cl
.api
.tx()
.sign_and_submit_then_watch_default(&transfer_tx, s)
.await?
.wait_for_finalized_success()
.await?;

let expected_event = mainnet::balances::events::Transfer {
from: s.0.account_id().clone(),
to: dest.into(),
amount,
};

let exists = events
.find::<mainnet::balances::events::Transfer>()
.any(|e| e.map(|x| assert_eq!(x, expected_event)).is_ok());

if exists {
Ok(events.block_hash())
} else {
Err(Error::Other(String::from("failed to transfer")))
}
}
4 changes: 4 additions & 0 deletions tfchain-client/src/runtimes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#![allow(clippy::all)]

pub mod mainnet;
pub mod types;
28 changes: 28 additions & 0 deletions tfchain-client/src/runtimes/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use subxt::{Config, PolkadotConfig};

pub use frame_system::AccountInfo;
pub use pallet_balances::AccountData;
pub type Hash = <PolkadotConfig as Config>::Hash;
pub type BlockNumber = subxt::rpc::types::BlockNumber;
pub use subxt::utils::AccountId32;

pub type SystemAccountInfo = AccountInfo<u32, AccountData<u128>>;

use super::mainnet::SystemAccountInfo as MainnetSystemAccountInfo;

impl From<MainnetSystemAccountInfo> for SystemAccountInfo {
fn from(info: MainnetSystemAccountInfo) -> Self {
SystemAccountInfo {
nonce: info.nonce,
consumers: info.consumers,
providers: info.providers,
sufficients: info.sufficients,
data: pallet_balances::AccountData {
free: info.data.free,
fee_frozen: info.data.fee_frozen,
misc_frozen: info.data.misc_frozen,
reserved: info.data.reserved,
},
}
}
}