use icepick_module::Module; use serde::{Deserialize, Serialize}; use spacemesh::bech32::{self, Hrp}; use std::str::FromStr; #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] #[serde(rename_all = "kebab-case")] pub enum Cluster { Testnet, #[default] Mainnet, } impl Cluster { fn hrp(&self) -> bech32::Hrp { match self { Cluster::Testnet => Hrp::parse("stest").unwrap(), Cluster::Mainnet => Hrp::parse("sm").unwrap(), } } } impl std::str::FromStr for Cluster { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "testnet" => Ok(Self::Testnet), "mainnet" => Ok(Self::Mainnet), _ => Err("Invalid value"), } } } impl std::fmt::Display for Cluster { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Cluster::Testnet => f.write_str("testnet"), Cluster::Mainnet => f.write_str("mainnet"), } } } #[derive(thiserror::Error, Debug)] pub enum Error {} #[derive(Serialize, Deserialize, Debug)] pub struct GenerateWallet { account: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct GetWalletAddress { pubkey: [u8; 32], cluster: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct GetAccountData { account: String, cluster: Option, } #[derive(Serialize, Deserialize, Debug)] pub struct AwaitFunds { address: String, amount: String, cluster: Option, } #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "operation", content = "values", rename_all = "kebab-case")] pub enum Operation { GenerateWallet(GenerateWallet), GetWalletAddress(GetWalletAddress), AwaitFunds(AwaitFunds), } #[derive(Serialize, Deserialize, Debug)] pub struct Request { derived_keys: Option>, #[serde(flatten)] operation: Operation, } pub fn run_async(f: F) -> F::Output { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(f) } pub struct Spacemesh; impl Module for Spacemesh { type Error = Error; type Request = Request; fn describe_operations() -> Vec { use icepick_module::help::*; let account = Argument::builder() .name("account") .description("The derivation index for the account.") .r#type(ArgumentType::Optional) .build(); let cluster = Argument::builder() .name("cluster") .description("Spacemesh cluster to interact with (mainnet, testnet).") .r#type(ArgumentType::Required) .build(); let generate_wallet = Operation::builder() .name("generate-wallet") .description("Generate a wallet for the given account.") .build() .argument(&account); let get_wallet_address = Operation::builder() .name("get-wallet-address") .description("Get the address for a given wallet.") .build() .argument(&cluster) .argument( &Argument::builder() .name("wallet_pubkey") .description("Public key of the wallet.") .r#type(ArgumentType::Required) .build(), ); vec![generate_wallet, get_wallet_address] } fn handle_request(request: Self::Request) -> Result { let Request { operation, derived_keys: _, } = request; match operation { Operation::GenerateWallet(GenerateWallet { account }) => { let account = u32::from_str(account.as_deref().unwrap_or("0")).unwrap(); Ok(serde_json::json!({ "blob": {}, "derivation_accounts": [(account | 1 << 31)], })) } Operation::GetWalletAddress(GetWalletAddress { pubkey, cluster }) => { use spacemesh::wallet::AsAddress; let account = pubkey.as_address(); let hrp = cluster.unwrap_or_default().hrp(); let address = bech32::encode(hrp, &account).unwrap(); Ok(serde_json::json!({ "blob": { "address": address, }, "derivation_accounts": [], })) } Operation::AwaitFunds(AwaitFunds { address, amount, cluster, }) => todo!(), } } }