add base support for spacemesh
This commit is contained in:
parent
4f5779c983
commit
097bacbdea
File diff suppressed because it is too large
Load Diff
|
@ -6,9 +6,14 @@ members = [
|
|||
"crates/icepick-workflow",
|
||||
"crates/icepick-module",
|
||||
"crates/builtins/icepick-internal",
|
||||
"crates/builtins/icepick-ed25519",
|
||||
"crates/by-chain/icepick-solana",
|
||||
"crates/by-chain/icepick-cosmos",
|
||||
"crates/miniquorum",
|
||||
"crates/spacemesh/api-client",
|
||||
"crates/spacemesh/codec",
|
||||
"crates/spacemesh/spacemesh",
|
||||
"crates/by-chain/icepick-spacemesh",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "icepick-ed25519"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = ["distrust"]
|
||||
|
||||
[dependencies]
|
||||
ed25519-dalek = "2.1.1"
|
||||
icepick-module = { version = "0.1.0", path = "../../icepick-module" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smex = { version = "0.1.0", registry = "distrust" }
|
||||
thiserror = "2.0.9"
|
|
@ -0,0 +1,91 @@
|
|||
use ed25519_dalek::Signer;
|
||||
use icepick_module::Module;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "operation", content = "values", rename_all = "kebab-case")]
|
||||
pub enum Operation {
|
||||
GetPubkey {},
|
||||
|
||||
Sign { message: Vec<u8> },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Request {
|
||||
derived_keys: Option<Vec<[u8; 32]>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
operation: Operation,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {}
|
||||
|
||||
pub struct Ed25519;
|
||||
|
||||
impl Module for Ed25519 {
|
||||
type Error = Error;
|
||||
|
||||
type Request = Request;
|
||||
|
||||
fn describe_operations() -> Vec<icepick_module::help::Operation> {
|
||||
use icepick_module::help::*;
|
||||
|
||||
let message = Argument::builder()
|
||||
.name("message")
|
||||
.description("The message to sign, as an array of bytes.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build();
|
||||
|
||||
let get_pubkey = Operation::builder()
|
||||
.name("get-pubkey")
|
||||
.description("Get an Ed25519 public key from the provided private key.")
|
||||
.build();
|
||||
|
||||
let sign = Operation::builder()
|
||||
.name("sign")
|
||||
.description("Sign a message using an Ed25519 private key.")
|
||||
.build()
|
||||
.argument(&message);
|
||||
|
||||
vec![get_pubkey, sign]
|
||||
}
|
||||
|
||||
fn handle_request(request: Self::Request) -> Result<serde_json::Value, Self::Error> {
|
||||
let Request {
|
||||
derived_keys,
|
||||
operation,
|
||||
} = request;
|
||||
|
||||
match operation {
|
||||
Operation::GetPubkey {} => {
|
||||
let key = derived_keys
|
||||
.iter()
|
||||
.flatten()
|
||||
.next()
|
||||
.map(ed25519_dalek::SigningKey::from_bytes)
|
||||
.unwrap();
|
||||
let key = key.verifying_key().to_bytes();
|
||||
Ok(serde_json::json!({
|
||||
"blob": {
|
||||
"pubkey": key,
|
||||
}
|
||||
}))
|
||||
}
|
||||
Operation::Sign { message } => {
|
||||
let key = derived_keys
|
||||
.iter()
|
||||
.flatten()
|
||||
.next()
|
||||
.map(ed25519_dalek::SigningKey::from_bytes)
|
||||
.unwrap();
|
||||
let signature = key.sign(&message);
|
||||
Ok(serde_json::json!({
|
||||
"blob": {
|
||||
"signature": signature.to_vec(),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
use icepick_module::Module;
|
||||
use icepick_ed25519::Ed25519;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ed25519::run_responder()
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "icepick-spacemesh"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = ["distrust"]
|
||||
|
||||
[dependencies]
|
||||
icepick-module = { version = "0.1.0", path = "../../icepick-module" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
spacemesh = { version = "0.1.0", path = "../../spacemesh/spacemesh" }
|
||||
thiserror = "2.0.11"
|
||||
tokio = { version = "1.43.0", features = ["rt", "net"] }
|
|
@ -0,0 +1,172 @@
|
|||
use icepick_module::Module;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use spacemesh::bech32::{self, Hrp};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, 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<Self, Self::Err> {
|
||||
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<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetWalletAddress {
|
||||
pubkey: [u8; 32],
|
||||
cluster: Option<Cluster>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountData {
|
||||
account: String,
|
||||
cluster: Option<Cluster>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AwaitFunds {
|
||||
address: String,
|
||||
amount: String,
|
||||
cluster: Option<Cluster>,
|
||||
}
|
||||
|
||||
#[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<Vec<[u8; 32]>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
operation: Operation,
|
||||
}
|
||||
|
||||
pub fn run_async<F: std::future::Future>(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<icepick_module::help::Operation> {
|
||||
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<serde_json::Value, Self::Error> {
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
use icepick_module::Module;
|
||||
use icepick_spacemesh::Spacemesh;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Spacemesh::run_responder()
|
||||
}
|
|
@ -69,7 +69,8 @@ pub struct OperationResult {
|
|||
derivation_accounts: Vec<DerivationIndex>,
|
||||
}
|
||||
|
||||
type DeriveKeys<'a> = &'a dyn Fn(&DerivationAlgorithm, &DerivationPath, &[DerivationIndex]) -> Vec<Vec<u8>>;
|
||||
type DeriveKeys<'a> =
|
||||
&'a dyn Fn(&DerivationAlgorithm, &DerivationPath, &[DerivationIndex]) -> Vec<Vec<u8>>;
|
||||
|
||||
impl Workflow {
|
||||
pub fn simulate_workflow<T: InvocableOperation + Sized>(
|
||||
|
@ -128,15 +129,6 @@ impl Workflow {
|
|||
return Err(WorkflowError::InvocableOperationNotFound(step_type));
|
||||
};
|
||||
|
||||
// Add requested derivation keys and clear derivation account requests.
|
||||
if !derivation_accounts.is_empty() {
|
||||
let Some((algo, path_prefix)) = operation.derivation_configuration() else {
|
||||
return Err(WorkflowError::DerivationConfigurationNotFound(step_type));
|
||||
};
|
||||
derived_keys.extend(derive_keys(algo, path_prefix, &derivation_accounts));
|
||||
}
|
||||
derivation_accounts.clear();
|
||||
|
||||
// Prepare all inputs for the operation invocation
|
||||
let inputs: StringMap<Value> = data
|
||||
.iter()
|
||||
|
@ -167,13 +159,20 @@ impl Workflow {
|
|||
let (_given, stored) = step.outputs.iter().find(|(k1, _)| k == **k1)?;
|
||||
Some((stored.clone(), v))
|
||||
}));
|
||||
|
||||
// Add requested derivation keys and clear derivation account requests.
|
||||
if !derivation_accounts.is_empty() {
|
||||
let Some((algo, path_prefix)) = operation.derivation_configuration() else {
|
||||
return Err(WorkflowError::DerivationConfigurationNotFound(step_type));
|
||||
};
|
||||
derived_keys.extend(derive_keys(algo, path_prefix, &derivation_accounts));
|
||||
}
|
||||
derivation_accounts.clear();
|
||||
}
|
||||
|
||||
if let Some(last_step) = &self.steps.last() {
|
||||
let values = last_step.outputs.values().collect::<HashSet<_>>();
|
||||
data.retain(|stored_name, _| {
|
||||
values.contains(stored_name)
|
||||
});
|
||||
data.retain(|stored_name, _| values.contains(stored_name));
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
|
|
|
@ -139,6 +139,14 @@ pub fn do_cli_thing() {
|
|||
derivation_prefix: Default::default(),
|
||||
workflows: Default::default(),
|
||||
});
|
||||
config.modules.push(ModuleConfig {
|
||||
name: "ed25519".to_string(),
|
||||
command_name: Default::default(),
|
||||
algorithm: Some(DerivationAlgorithm::Ed25519),
|
||||
// TODO: impl Last
|
||||
derivation_prefix: Default::default(),
|
||||
workflows: Default::default(),
|
||||
});
|
||||
|
||||
let workflows = default_workflows();
|
||||
for module in &mut config.modules {
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
name: generate-address
|
||||
optional_inputs:
|
||||
- account
|
||||
- cluster
|
||||
step:
|
||||
- type: spacemesh-generate-wallet
|
||||
inputs:
|
||||
account: account
|
||||
- type: ed25519-get-pubkey
|
||||
outputs:
|
||||
pubkey: pubkey
|
||||
- type: spacemesh-get-wallet-address
|
||||
inputs:
|
||||
pubkey: pubkey
|
||||
cluster: cluster
|
||||
outputs:
|
||||
address: address
|
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "spacemesh-api-client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = ["distrust"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
progenitor-client = { git = "https://github.com/geoffreygarrett/progenitor", rev = "8726ea91eb19f92e1357f1ceeeab507477dcfeb6" }
|
||||
reqwest = { version = "0.11", features = ["json", "stream"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
base64 = "0.22.1"
|
||||
smex = { version = "0.1.0", registry = "distrust" }
|
||||
|
||||
[build-dependencies]
|
||||
prettyplease = "0.2.22"
|
||||
progenitor = { git = "https://github.com/geoffreygarrett/progenitor", rev = "8726ea91eb19f92e1357f1ceeeab507477dcfeb6" }
|
||||
serde_json = "1.0"
|
||||
syn = "2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
base64 = "0.22.1"
|
||||
smex = { version = "0.1.0", registry = "distrust" }
|
||||
tokio = { version = "1.43.0", features = ["macros", "net", "rt", "test-util"] }
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
curl -X 'GET' \
|
||||
'https://converter.swagger.io/api/convert?url=https%3A%2F%2Fmainnet-api-docs.spacemesh.network%2Fv1.7.12%2Fapi.swagger.json' \
|
||||
-H 'accept: application/json'
|
||||
*/
|
||||
|
||||
fn main() {
|
||||
let src = "openapi.json";
|
||||
println!("cargo:rerun-if-changed={}", src);
|
||||
let file = std::fs::File::open(src).unwrap();
|
||||
let spec = serde_json::from_reader(file).unwrap();
|
||||
let mut generator = progenitor::Generator::default();
|
||||
|
||||
let tokens = generator.generate_tokens(&spec).unwrap();
|
||||
let ast = syn::parse2(tokens).unwrap();
|
||||
let content = prettyplease::unparse(&ast);
|
||||
|
||||
let mut out_file = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).to_path_buf();
|
||||
out_file.push("codegen.rs");
|
||||
|
||||
std::fs::write(out_file, content).unwrap();
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,42 @@
|
|||
#![allow(warnings, unused)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||
|
||||
// NOTE: The RPC API requires base64-encoded transaction IDs rather than hex-encoded.
|
||||
// That was confusing, after all their branding is `0x` based.
|
||||
|
||||
pub fn encode_transaction_id(txid: impl AsRef<str>) -> Result<String, smex::DecodeError> {
|
||||
use base64::prelude::*;
|
||||
let tx = smex::decode(txid)?;
|
||||
Ok(BASE64_STANDARD.encode(tx))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use base64::prelude::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_works() {
|
||||
let client = Client::new("https://mainnet-api.spacemesh.network");
|
||||
let txid = "638442a2033f20b5a7280b9a4f2bfc73022f6e7ec64b1497b85335444381d99d";
|
||||
let txid = smex::decode(txid).unwrap();
|
||||
let txid = BASE64_STANDARD.encode(txid);
|
||||
let result = client
|
||||
.transaction_service_list(&types::Spacemeshv2alpha1TransactionRequest {
|
||||
txid: vec![txid],
|
||||
limit: Some(100.to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.into_inner();
|
||||
|
||||
let result = match result {
|
||||
types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::GooglerpcStatus(googlerpc_status) => panic!("{:?}", googlerpc_status.message),
|
||||
types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::Spacemeshv2alpha1TransactionList(transaction_list) => {
|
||||
transaction_list
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "spacemesh-codec"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = ["distrust"]
|
||||
|
||||
[dependencies]
|
||||
parity-scale-codec = { version = "3.6.12", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
base64 = "0.22.1"
|
||||
bech32 = "0.11.0"
|
|
@ -0,0 +1,530 @@
|
|||
//! Spacemesh transaction encoding and decoding.
|
||||
//! Based loosely on: <https://github.com/spacemeshos/sm-codec/>.
|
||||
//!
|
||||
//! # Encoding Transactions
|
||||
//!
|
||||
//! ```rust
|
||||
//! let principal = [0u8; 24];
|
||||
//! let destination = [1u8; 24];
|
||||
//!
|
||||
//! let single_sig_spend = Spend {
|
||||
//! header: TxHeader {
|
||||
//! principal,
|
||||
//! },
|
||||
//! payload: SpendPayload {
|
||||
//! nonce: Compact(2),
|
||||
//! gas_price: Compact(1),
|
||||
//! arguments: SpendArguments {
|
||||
//! destination,
|
||||
//! amount: Compact(100000),
|
||||
//! },
|
||||
//! },
|
||||
//! // unsigned transaction
|
||||
//! signature: [0; 64],
|
||||
//! };
|
||||
//! ```
|
||||
//!
|
||||
//! # Decoding Transactions
|
||||
//!
|
||||
//! Transactions can be decoded to bytes using the [`base64`][base64] crate. Using the Spacemesh
|
||||
//! client, the transaction should also include `template` and `method` values. With those values,
|
||||
//! [`tx_types::decode_by_address_and_method()`] can be used to attempt to parse the transaction.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use base64::prelude::*;
|
||||
//!
|
||||
//! let encoded_tx = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAIBAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAYIaBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
//! let raw_tx = BASE64_STANDARD.decode(encoded_tx).unwrap();
|
||||
//! let spend = tx_types::single_signature::Spend::decode(&mut &raw_tx[..]).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! [base64]: https://docs.rs/base64/latest/base64/
|
||||
|
||||
pub use parity_scale_codec::{Compact, Decode, Encode};
|
||||
|
||||
pub mod constants {
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/constants.ts
|
||||
|
||||
/// The length of an address.
|
||||
pub const ADDRESS_BYTES_LENGTH: usize = 24;
|
||||
}
|
||||
|
||||
pub mod core {
|
||||
use super::*;
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/core.ts
|
||||
|
||||
// NOTE: Encoding an array doesn't encode length, matching the same functionality
|
||||
// as Bytes in scale-ts.
|
||||
pub type Address = [u8; constants::ADDRESS_BYTES_LENGTH];
|
||||
pub type PublicKey = [u8; 32];
|
||||
|
||||
pub type Nonce = Compact<u64>;
|
||||
pub type GasPrice = Compact<u64>;
|
||||
}
|
||||
|
||||
pub mod signatures {
|
||||
use super::*;
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/signatures.ts
|
||||
|
||||
pub type SingleSig = [u8; 64];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct MultiSigPart {
|
||||
pub r#ref: Compact<u8>,
|
||||
pub sig: SingleSig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct MultiSig {
|
||||
pub parts: Vec<MultiSigPart>,
|
||||
}
|
||||
|
||||
impl Encode for MultiSig {
|
||||
fn size_hint(&self) -> usize {
|
||||
self.parts.len() * std::mem::size_of::<SingleSig>()
|
||||
}
|
||||
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
// NOTE: No inline length is included.
|
||||
let mut r = Vec::with_capacity(self.size_hint());
|
||||
for sig in &self.parts {
|
||||
sig.encode_to(&mut r);
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for MultiSig {
|
||||
fn decode<I: parity_scale_codec::Input>(
|
||||
input: &mut I,
|
||||
) -> Result<Self, parity_scale_codec::Error> {
|
||||
let mut parts = vec![];
|
||||
// NOTE: We can't rely on the length of the input. It may not be available.
|
||||
// Unfortunately, we also don't have enough context to know if the reason it can't
|
||||
// decode is because we ran out of input, or because there was a format error.
|
||||
while let Ok(part) = MultiSigPart::decode(input) {
|
||||
parts.push(part);
|
||||
}
|
||||
Ok(Self { parts })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod tx {
|
||||
use super::*;
|
||||
|
||||
pub trait TransactionMethod {
|
||||
fn method_selector() -> u8;
|
||||
}
|
||||
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/codecs/tx.ts
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TxHeader<const M: u8> {
|
||||
// should always be 0
|
||||
// pub transaction_type: Compact<u8>,
|
||||
pub principal: core::Address,
|
||||
// covered by const M
|
||||
// pub method_selector: Compact<u8>,
|
||||
}
|
||||
|
||||
impl<const M: u8> Encode for TxHeader<M> {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
let mut r = Vec::with_capacity(self.size_hint());
|
||||
let transaction_type = Compact(0u8);
|
||||
transaction_type.encode_to(&mut r);
|
||||
self.principal.encode_to(&mut r);
|
||||
let method_selector = Compact(M);
|
||||
method_selector.encode_to(&mut r);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl<const M: u8> Decode for TxHeader<M> {
|
||||
fn decode<I: parity_scale_codec::Input>(
|
||||
input: &mut I,
|
||||
) -> Result<Self, parity_scale_codec::Error> {
|
||||
let transaction_type = Compact::<u8>::decode(input)?;
|
||||
if transaction_type.0 != 0 {
|
||||
return Err("transaction_type != 0".into());
|
||||
}
|
||||
let principal = core::Address::decode(input)?;
|
||||
let method_selector = Compact::<u8>::decode(input)?;
|
||||
if method_selector.0 != M {
|
||||
return Err("method_selector != M".into());
|
||||
}
|
||||
Ok(Self {
|
||||
principal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This is used in place of `withTemplateAddress()`.
|
||||
// The original source implementation placed `template_address` as the last field,
|
||||
// but I don't think that's correct based on the implementation of `withTemplateAddress()`.
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SpawnTxHeader<const M: u8> {
|
||||
pub template_address: core::Address,
|
||||
// should always be 0
|
||||
// pub transaction_type: Compact<u8>,
|
||||
pub principal: core::Address,
|
||||
// covered by const M
|
||||
// pub method_selector: Compact<u8>,
|
||||
}
|
||||
|
||||
impl<const M: u8> Encode for SpawnTxHeader<M> {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
let mut r = Vec::with_capacity(self.size_hint());
|
||||
self.template_address.encode_to(&mut r);
|
||||
let transaction_type = Compact(0u8);
|
||||
transaction_type.encode_to(&mut r);
|
||||
self.principal.encode_to(&mut r);
|
||||
let method_selector = Compact(M);
|
||||
method_selector.encode_to(&mut r);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl<const M: u8> Decode for SpawnTxHeader<M> {
|
||||
fn decode<I: parity_scale_codec::Input>(
|
||||
input: &mut I,
|
||||
) -> Result<Self, parity_scale_codec::Error> {
|
||||
let template_address = core::Address::decode(input)?;
|
||||
let transaction_type = Compact::<u8>::decode(input)?;
|
||||
if transaction_type.0 != 0 {
|
||||
return Err("transaction_type != 0".into());
|
||||
}
|
||||
let principal = core::Address::decode(input)?;
|
||||
let method_selector = Compact::<u8>::decode(input)?;
|
||||
if method_selector.0 != M {
|
||||
return Err("method_selector != M".into());
|
||||
}
|
||||
Ok(Self {
|
||||
template_address,
|
||||
principal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/transaction.ts
|
||||
|
||||
mod sealed {
|
||||
use super::signatures;
|
||||
|
||||
pub trait Signature {}
|
||||
impl Signature for signatures::SingleSig {}
|
||||
impl Signature for signatures::MultiSig {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpawnTransaction<Payload, Signature: sealed::Signature, const M: u8> {
|
||||
pub header: SpawnTxHeader<M>,
|
||||
pub payload: Payload,
|
||||
pub signature: Signature,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct Transaction<Payload, Signature: sealed::Signature, const M: u8> {
|
||||
pub header: TxHeader<M>,
|
||||
pub payload: Payload,
|
||||
pub signature: Signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod tx_types {
|
||||
use super::*;
|
||||
|
||||
pub type DecodeResult<T> = Option<Result<T, parity_scale_codec::Error>>;
|
||||
|
||||
pub mod common {
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/common.ts
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct TxPayload<Arguments> {
|
||||
pub nonce: core::Nonce,
|
||||
pub gas_price: core::GasPrice,
|
||||
pub arguments: Arguments,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod vault {
|
||||
use super::*;
|
||||
use common::TxPayload;
|
||||
use signatures::SingleSig;
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/vault.ts
|
||||
|
||||
pub const VAULT_TEMPLATE_ADDRESS: core::Address = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpawnArguments {
|
||||
pub owner: core::Address,
|
||||
pub total_amount: Compact<u64>,
|
||||
pub initial_unlock_amount: Compact<u64>,
|
||||
pub vesting_start: Compact<u32>,
|
||||
pub vesting_end: Compact<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpendArguments {
|
||||
pub destination: core::Address,
|
||||
pub amount: Compact<u64>,
|
||||
}
|
||||
|
||||
pub type SpawnPayload = TxPayload<SpawnArguments>;
|
||||
pub type SpendPayload = TxPayload<SpendArguments>;
|
||||
|
||||
pub type Spawn = tx::SpawnTransaction<SpawnPayload, SingleSig, 0>;
|
||||
pub type Spend = tx::Transaction<SpendPayload, SingleSig, 16>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Method {
|
||||
Spawn(Spawn),
|
||||
Spend(Spend),
|
||||
}
|
||||
|
||||
pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult<Method> {
|
||||
match method {
|
||||
0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)),
|
||||
16 => Some(Spend::decode(&mut &*input).map(Method::Spend)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod vesting {
|
||||
use super::*;
|
||||
use common::TxPayload;
|
||||
use signatures::MultiSig;
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/vesting.ts
|
||||
|
||||
pub const VESTING_TEMPLATE_ADDRESS: core::Address = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpawnArguments {
|
||||
pub required: Compact<u8>,
|
||||
pub public_keys: Vec<core::PublicKey>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpendArguments {
|
||||
pub destination: core::Address,
|
||||
pub amount: Compact<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct DrainVaultArguments {
|
||||
pub vault: core::Address,
|
||||
pub destination: core::Address,
|
||||
pub amount: Compact<u64>,
|
||||
}
|
||||
|
||||
pub type SpawnPayload = TxPayload<SpawnArguments>;
|
||||
pub type SpendPayload = TxPayload<SpendArguments>;
|
||||
pub type DrainVaultPayload = TxPayload<DrainVaultArguments>;
|
||||
|
||||
pub type Spawn = tx::SpawnTransaction<SpawnPayload, MultiSig, 0>;
|
||||
pub type Spend = tx::Transaction<SpendPayload, MultiSig, 16>;
|
||||
pub type DrainVault = tx::Transaction<DrainVaultPayload, MultiSig, 17>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Method {
|
||||
Spawn(Spawn),
|
||||
Spend(Spend),
|
||||
DrainVault(DrainVault),
|
||||
}
|
||||
|
||||
pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult<Method> {
|
||||
match method {
|
||||
0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)),
|
||||
16 => Some(Spend::decode(&mut &*input).map(Method::Spend)),
|
||||
17 => Some(DrainVault::decode(&mut &*input).map(Method::DrainVault)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod single_signature {
|
||||
use super::*;
|
||||
use common::TxPayload;
|
||||
use signatures::SingleSig;
|
||||
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/singlesig.ts
|
||||
|
||||
pub const SINGLE_SIG_TEMPLATE_ADDRESS: core::Address = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpawnArguments {
|
||||
pub public_key: core::PublicKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpendArguments {
|
||||
pub destination: core::Address,
|
||||
pub amount: Compact<u64>,
|
||||
}
|
||||
|
||||
pub type SpawnPayload = TxPayload<SpawnArguments>;
|
||||
pub type SpendPayload = TxPayload<SpendArguments>;
|
||||
|
||||
pub type Spawn = tx::SpawnTransaction<SpawnPayload, SingleSig, 0>;
|
||||
pub type Spend = tx::Transaction<SpendPayload, SingleSig, 16>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Method {
|
||||
Spawn(Spawn),
|
||||
Spend(Spend),
|
||||
}
|
||||
|
||||
pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult<Method> {
|
||||
match method {
|
||||
0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)),
|
||||
16 => Some(Spend::decode(&mut &*input).map(Method::Spend)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod multi_signature {
|
||||
use super::*;
|
||||
use common::TxPayload;
|
||||
use signatures::MultiSig;
|
||||
|
||||
// ref: https://github.com/spacemeshos/sm-codec/blob/master/src/std/singlesig.ts
|
||||
|
||||
pub const MULTI_SIG_TEMPLATE_ADDRESS: core::Address = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpawnArguments {
|
||||
pub required: Compact<u8>,
|
||||
pub public_key: Vec<core::PublicKey>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
pub struct SpendArguments {
|
||||
pub destination: core::Address,
|
||||
pub amount: Compact<u64>,
|
||||
}
|
||||
|
||||
pub type SpawnPayload = TxPayload<SpawnArguments>;
|
||||
pub type SpendPayload = TxPayload<SpendArguments>;
|
||||
|
||||
pub type Spawn = tx::SpawnTransaction<SpawnPayload, MultiSig, 0>;
|
||||
pub type Spend = tx::Transaction<SpendPayload, MultiSig, 16>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Method {
|
||||
Spawn(Spawn),
|
||||
Spend(Spend),
|
||||
}
|
||||
|
||||
pub fn decode_by_method(method: u8, input: &[u8]) -> DecodeResult<Method> {
|
||||
match method {
|
||||
0 => Some(Spawn::decode(&mut &*input).map(Method::Spawn)),
|
||||
16 => Some(Spend::decode(&mut &*input).map(Method::Spend)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ModuleMethod {
|
||||
Vault(vault::Method),
|
||||
Vesting(vesting::Method),
|
||||
SingleSig(single_signature::Method),
|
||||
MultiSig(multi_signature::Method),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn decode_by_address_and_method(
|
||||
address: core::Address,
|
||||
method: u8,
|
||||
input: &[u8],
|
||||
) -> DecodeResult<ModuleMethod> {
|
||||
match address {
|
||||
vault::VAULT_TEMPLATE_ADDRESS =>
|
||||
vault::decode_by_method(method, input)
|
||||
.map(|method| method.map(ModuleMethod::Vault)),
|
||||
vesting::VESTING_TEMPLATE_ADDRESS =>
|
||||
vesting::decode_by_method(method, input)
|
||||
.map(|method| method.map(ModuleMethod::Vesting)),
|
||||
single_signature::SINGLE_SIG_TEMPLATE_ADDRESS => {
|
||||
single_signature::decode_by_method(method, input)
|
||||
.map(|method| method.map(ModuleMethod::SingleSig))
|
||||
}
|
||||
multi_signature::MULTI_SIG_TEMPLATE_ADDRESS => {
|
||||
multi_signature::decode_by_method(method, input)
|
||||
.map(|method| method.map(ModuleMethod::MultiSig))
|
||||
}
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
use base64::prelude::*;
|
||||
use bech32::Bech32;
|
||||
let (hrp, data) =
|
||||
bech32::decode("sm1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg56ypy7").unwrap();
|
||||
assert_eq!(hrp.as_str(), "sm");
|
||||
assert_eq!(
|
||||
&data,
|
||||
&tx_types::single_signature::SINGLE_SIG_TEMPLATE_ADDRESS
|
||||
);
|
||||
|
||||
let encoded_tx = "AAAAAAAvqmgSN6hBGS16FVNfNDURojTRU0AQBAAAAABJThXbKEnjnty59ht5e/5EkjDK8AeANolPDOAiIHlzj7CIG60FzFRpuR/fLVRQsmzRbApYBryfg4RKcnZgmmWPywafADHyuVjkLNGup0gpvhnXAHICeSXveAs=";
|
||||
let raw_tx = BASE64_STANDARD.decode(encoded_tx).unwrap();
|
||||
let spend = tx_types::single_signature::Spend::decode(&mut &raw_tx[..]).unwrap();
|
||||
let equivalence = spend.encode();
|
||||
assert_eq!(raw_tx, equivalence);
|
||||
|
||||
let recipient_address =
|
||||
bech32::encode::<Bech32>(hrp, &spend.payload.arguments.destination).unwrap();
|
||||
assert_eq!(
|
||||
recipient_address,
|
||||
"sm1qqqqqqzffc2ak2zfuw0dew0krduhhljyjgcv4uqdt6nrd"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recode() {
|
||||
use tx::*;
|
||||
use tx_types::single_signature::*;
|
||||
|
||||
let principal = [0u8; 24];
|
||||
|
||||
let single_sig_spend = Spend {
|
||||
header: TxHeader {
|
||||
principal,
|
||||
},
|
||||
payload: SpendPayload {
|
||||
nonce: Compact(2),
|
||||
gas_price: Compact(1),
|
||||
arguments: SpendArguments {
|
||||
destination: [1; 24],
|
||||
amount: Compact(100000),
|
||||
},
|
||||
},
|
||||
signature: [0; 64],
|
||||
};
|
||||
|
||||
let encoded = single_sig_spend.encode();
|
||||
let recoded = Spend::decode(&mut &*encoded).unwrap();
|
||||
assert_eq!(single_sig_spend, recoded);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "spacemesh"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = ["distrust"]
|
||||
|
||||
[dependencies]
|
||||
bech32 = "0.11.0"
|
||||
spacemesh-api-client = { version = "0.1.0", path = "../api-client" }
|
||||
spacemesh-codec = { version = "0.1.0", path = "../codec" }
|
||||
|
||||
[dev-dependencies]
|
||||
base64 = "0.22.1"
|
||||
bech32 = "0.11.0"
|
||||
smex = { version = "0.1.0", registry = "distrust" }
|
||||
tokio = { version = "1.43.0", features = ["net", "rt", "macros"] }
|
|
@ -0,0 +1,58 @@
|
|||
pub use spacemesh_api_client as client;
|
||||
pub use spacemesh_api_client::Client;
|
||||
pub use spacemesh_codec as codec;
|
||||
pub use spacemesh_codec::tx_types as transaction;
|
||||
|
||||
|
||||
pub mod wallet;
|
||||
|
||||
pub mod bech32 {
|
||||
pub use bech32::*;
|
||||
|
||||
pub fn encode(hrp: Hrp, input: &[u8]) -> Result<String, EncodeError> {
|
||||
bech32::encode::<Bech32>(hrp, input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use base64::prelude::*;
|
||||
use spacemesh_api_client::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn decodes_live_transaction() {
|
||||
let client = Client::new("https://mainnet-api.spacemesh.network");
|
||||
let txid = "638442a2033f20b5a7280b9a4f2bfc73022f6e7ec64b1497b85335444381d99d";
|
||||
let txid = smex::decode(txid).unwrap();
|
||||
let txid = BASE64_STANDARD.encode(txid);
|
||||
let result = client
|
||||
.transaction_service_list(&types::Spacemeshv2alpha1TransactionRequest {
|
||||
txid: vec![txid],
|
||||
limit: Some(100.to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.into_inner();
|
||||
|
||||
let mut result = match result {
|
||||
types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::GooglerpcStatus(googlerpc_status) => panic!("{:?}", googlerpc_status.message),
|
||||
types::GooglerpcStatusOrSpacemeshv2alpha1TransactionList::Spacemeshv2alpha1TransactionList(transaction_list) => {
|
||||
transaction_list
|
||||
},
|
||||
};
|
||||
|
||||
let tx = result.transactions.pop().unwrap().tx.unwrap();
|
||||
let (_hrp, address) = bech32::decode(&tx.template.unwrap()).unwrap();
|
||||
let tx_raw = BASE64_STANDARD.decode(tx.raw.unwrap()).unwrap();
|
||||
let decoded = transaction::decode_by_address_and_method(
|
||||
address.try_into().unwrap(),
|
||||
tx.method.unwrap() as u8,
|
||||
&tx_raw,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
drop(decoded);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
//! Spacemesh wallet management.
|
||||
|
||||
pub use crate::codec::core::Address;
|
||||
use crate::codec::tx::*;
|
||||
use crate::codec::Compact;
|
||||
use crate::transaction::single_signature;
|
||||
|
||||
const ADDRESS_RESERVED: usize = 4;
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
pub trait AsAddress: sealed::Sealed {
|
||||
fn as_address(&self) -> Address;
|
||||
}
|
||||
|
||||
impl sealed::Sealed for Address {}
|
||||
impl AsAddress for Address {
|
||||
#[inline(always)]
|
||||
fn as_address(&self) -> Address {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Sealed for [u8; 32] {}
|
||||
impl AsAddress for [u8; 32] {
|
||||
#[inline(always)]
|
||||
fn as_address(&self) -> Address {
|
||||
let mut output = [0u8; std::mem::size_of::<Address>()];
|
||||
const START: usize = 32 - std::mem::size_of::<Address>() + ADDRESS_RESERVED;
|
||||
output[ADDRESS_RESERVED..].copy_from_slice(
|
||||
&self[START..],
|
||||
);
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(principal: [u8; 32], nonce: u64, gas_price: u64) -> single_signature::Spawn {
|
||||
single_signature::Spawn {
|
||||
header: SpawnTxHeader {
|
||||
principal: principal.as_address(),
|
||||
template_address: single_signature::SINGLE_SIG_TEMPLATE_ADDRESS,
|
||||
},
|
||||
payload: single_signature::SpawnPayload {
|
||||
nonce: Compact(nonce),
|
||||
gas_price: Compact(gas_price),
|
||||
arguments: single_signature::SpawnArguments {
|
||||
public_key: principal,
|
||||
},
|
||||
},
|
||||
signature: [0u8; 64],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transfer(
|
||||
principal: impl AsAddress,
|
||||
recipient: impl AsAddress,
|
||||
amount: u64,
|
||||
nonce: u64,
|
||||
gas_price: u64,
|
||||
) -> single_signature::Spend {
|
||||
single_signature::Spend {
|
||||
header: TxHeader {
|
||||
principal: principal.as_address(),
|
||||
},
|
||||
payload: single_signature::SpendPayload {
|
||||
nonce: Compact(nonce),
|
||||
gas_price: Compact(gas_price),
|
||||
arguments: single_signature::SpendArguments {
|
||||
destination: recipient.as_address(),
|
||||
amount: Compact(amount),
|
||||
},
|
||||
},
|
||||
signature: [0u8; 64],
|
||||
}
|
||||
}
|
|
@ -221,3 +221,8 @@ outputs = { transaction = "transaction" }
|
|||
type = "cosmos-broadcast"
|
||||
inputs = { blockchain_config = "blockchain_config", transaction = "transaction" }
|
||||
outputs = { status = "status", url = "url", error = "error", error_code = "error_code" }
|
||||
|
||||
[[module]]
|
||||
name = "spacemesh"
|
||||
derivation_prefix = "m/44'/540'/0'/0'"
|
||||
algorithm = "Ed25519"
|
||||
|
|
Loading…
Reference in New Issue