From 6b0fcb7fef8184b38d7fbfa3bd669ef79d99ba24 Mon Sep 17 00:00:00 2001 From: Trisfald Date: Tue, 30 Apr 2024 17:47:09 +0200 Subject: [PATCH] add token transfer action --- src/account.rs | 11 +++-- src/config.rs | 5 ++- src/error.rs | 2 +- src/transaction/engine.rs | 8 +++- src/transaction/mod.rs | 1 + src/transaction/token_transfer.rs | 67 +++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 src/transaction/token_transfer.rs diff --git a/src/account.rs b/src/account.rs index b99def6..f473743 100644 --- a/src/account.rs +++ b/src/account.rs @@ -5,9 +5,10 @@ use derive_more::{Constructor, Display}; use crate::AppError; #[derive(Debug, Constructor, Default, Clone, Display)] -#[display("{}:{}", signer_id, network)] +#[display("{}:{}:{}", signer_id, buddy_id, network)] pub struct Account { pub signer_id: String, + pub buddy_id: String, pub network: String, } @@ -16,10 +17,14 @@ impl FromStr for Account { fn from_str(s: &str) -> Result { let split: Vec<_> = s.split(':').collect(); - if split.len() != 2 { + if split.len() != 3 { Err(AppError::AccountParseError(s.to_string())) } else { - Ok(Account::new(split[0].to_string(), split[1].to_string())) + Ok(Account::new( + split[0].to_string(), + split[1].to_string(), + split[2].to_string(), + )) } } } diff --git a/src/config.rs b/src/config.rs index 8f7be93..23392f1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,8 +29,9 @@ pub enum Command { /// Args needed to execute transactions. #[derive(Debug, Args, Default, Clone)] pub struct ExecArgs { - /// A list of :. - #[arg(env)] + /// A list of ::. + /// Main account is used to sign transactions. Buddy account is the receiver of token transfers (where applicable) + #[arg(env, verbatim_doc_comment)] pub accounts: Vec, #[clap(env, short, long, default_value = foo())] /// Path to the location storing the account keys. Defaults to the user's directory. diff --git a/src/error.rs b/src/error.rs index e3bfb42..589a83a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,7 +14,7 @@ pub enum AppError { NoMatchedTransaction(Regex), #[error("transaction error({0})")] TransactionError(String), - #[error("cannot parse account and network from '{0}'")] + #[error("cannot parse signer account, buddy account and network from '{0}'")] AccountParseError(String), #[error("unknown error")] Unknown, diff --git a/src/transaction/engine.rs b/src/transaction/engine.rs index 049ea0b..64ee5a2 100644 --- a/src/transaction/engine.rs +++ b/src/transaction/engine.rs @@ -5,6 +5,7 @@ use tracing::{info, warn}; use crate::{ config::RunArgs, metrics::{Labels, Metrics}, + transaction::token_transfer::TokenTransfer, Account, AppError, Transaction, }; @@ -31,6 +32,7 @@ impl Engine { } add_transaction!(SelfTokenTransfer); + add_transaction!(TokenTransfer); Engine { transactions } } @@ -200,7 +202,11 @@ mod tests { fn create_test_run_args() -> RunArgs { RunArgs { exec_args: ExecArgs { - accounts: vec![Account::new(String::new(), NETWORK.to_string())], + accounts: vec![Account::new( + String::new(), + String::new(), + NETWORK.to_string(), + )], key_path: String::new(), }, period: Duration::from_millis(1), diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 2428e29..9d3e62b 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -10,6 +10,7 @@ use crate::{Account, AppError}; pub mod engine; mod self_token_transfer; +mod token_transfer; #[derive(Debug, PartialEq, Eq, Hash, Display, From, Deref, Constructor, Clone)] pub struct TransactionKind(String); diff --git a/src/transaction/token_transfer.rs b/src/transaction/token_transfer.rs new file mode 100644 index 0000000..21ae74a --- /dev/null +++ b/src/transaction/token_transfer.rs @@ -0,0 +1,67 @@ +use crate::{Account, AppError, Transaction, TransactionOutcome}; +use async_trait::async_trait; +use tokio::{process::Command, time::Instant}; +use tracing::{debug, warn}; + +use super::TransactionKind; + +pub struct TokenTransfer {} + +#[async_trait] +impl Transaction for TokenTransfer { + fn kind(&self) -> TransactionKind { + TransactionKind("token_transfer".to_string()) + } + + async fn execute( + &self, + account: &Account, + key_path: &str, + ) -> Result { + let now = Instant::now(); + let output_result = Command::new("near") + .args([ + "tokens", + &account.signer_id, + "send-near", + &account.buddy_id, + "1 yoctoNEAR", + "network-config", + &account.network, + "sign-with-access-key-file", + &format!( + "{}/.near-credentials/{}/{}.json", + key_path, account.network, account.signer_id + ), + "send", + ]) + .output() + .await; + let elapsed = now.elapsed(); + + match output_result { + Ok(output) => { + if output.status.success() { + debug!( + "successful call to near token send:\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + Ok(TransactionOutcome::new(elapsed)) + } else { + warn!( + "failure during call to near token send:\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + Err(AppError::TransactionError( + "near token send-near failed".to_string(), + )) + } + } + Err(err) => Err(AppError::TransactionError(format!( + "near CLI invocation failure ({err})" + ))), + } + } +}