Skip to content

Commit

Permalink
feat: Added the ability to output a signed transaction (serialized as…
Browse files Browse the repository at this point in the history
… base64) to a file (#313)

Resolves #308

---------

Co-authored-by: FroVolod <[email protected]>
  • Loading branch information
FroVolod and FroVolod authored Mar 18, 2024
1 parent 62d6f3a commit ae25ded
Show file tree
Hide file tree
Showing 7 changed files with 393 additions and 211 deletions.
57 changes: 57 additions & 0 deletions src/transaction_signature_options/display/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)]
#[interactive_clap(input_context = super::SubmitContext)]
#[interactive_clap(output_context = DisplayContext)]
pub struct Display;

#[derive(Debug, Clone)]
pub struct DisplayContext;

impl DisplayContext {
pub fn from_previous_context(
previous_context: super::SubmitContext,
_scope: &<Display as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
let mut storage_message = String::new();

match previous_context.signed_transaction_or_signed_delegate_action {
super::SignedTransactionOrSignedDelegateAction::SignedTransaction(
signed_transaction,
) => {
(previous_context.on_before_sending_transaction_callback)(
&signed_transaction,
&previous_context.network_config,
&mut storage_message,
)
.map_err(color_eyre::Report::msg)?;

eprintln!(
"\nSigned transaction (serialized as base64):\n{}\n",
crate::types::signed_transaction::SignedTransactionAsBase64::from(
signed_transaction
)
);
eprintln!(
"This base64-encoded signed transaction is ready to be sent to the network. You can call RPC server directly, or use a helper command on near CLI:\n$ {} transaction send-signed-transaction\n",
crate::common::get_near_exec_path()
);
eprintln!("{storage_message}");
}
super::SignedTransactionOrSignedDelegateAction::SignedDelegateAction(
signed_delegate_action,
) => {
eprintln!(
"\nSigned delegate action (serialized as base64):\n{}\n",
crate::types::signed_delegate_action::SignedDelegateActionAsBase64::from(
signed_delegate_action
)
);
eprintln!(
"This base64-encoded signed delegate action is ready to be sent to the meta-transaction relayer. There is a helper command on near CLI that can do that:\n$ {} transaction send-meta-transaction\n",
crate::common::get_near_exec_path()
);
eprintln!("{storage_message}");
}
}
Ok(Self)
}
}
206 changes: 15 additions & 191 deletions src/transaction_signature_options/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use serde::Deserialize;
use strum::{EnumDiscriminants, EnumIter, EnumMessage};

use crate::common::JsonRpcClientExt;

pub mod display;
pub mod save_to_file;
pub mod send;
pub mod sign_later;
pub mod sign_with_access_key_file;
pub mod sign_with_keychain;
Expand Down Expand Up @@ -54,206 +55,29 @@ pub enum SignWith {
message = "sign-later - Prepare an unsigned transaction to sign it later"
))]
/// Prepare unsigned transaction to sign it later
SignLater(self::sign_later::Display),
SignLater(self::sign_later::SignLater),
}

#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(context = SubmitContext)]
#[strum_discriminants(derive(EnumMessage, EnumIter))]
#[interactive_clap(skip_default_from_cli)]
/// How would you like to proceed?
pub enum Submit {
#[strum_discriminants(strum(message = "send - Send the transaction to the network"))]
#[strum_discriminants(strum(
message = "send - Send the transaction to the network"
))]
/// Send the transaction to the network
Send,
Send(self::send::Send),
#[strum_discriminants(strum(
message = "display - Print the signed transaction to terminal (if you want to send it later)"
message = "save-to-file - Save the signed transaction to file (if you want to send it later)"
))]
/// Save the signed transaction to file (if you want to send it later)
SaveToFile(self::save_to_file::SaveToFile),
#[strum_discriminants(strum(
message = "display - Print the signed transaction to terminal (if you want to send it later)"
))]
/// Print the signed transaction to terminal (if you want to send it later)
Display,
}

impl interactive_clap::FromCli for Submit {
type FromCliContext = SubmitContext;
type FromCliError = color_eyre::eyre::Error;

fn from_cli(
mut optional_clap_variant: Option<<Self as interactive_clap::ToCli>::CliVariant>,
context: Self::FromCliContext,
) -> interactive_clap::ResultFromCli<
<Self as interactive_clap::ToCli>::CliVariant,
Self::FromCliError,
>
where
Self: Sized + interactive_clap::ToCli,
{
let mut storage_message = String::new();

if optional_clap_variant.is_none() {
match Self::choose_variant(context.clone()) {
interactive_clap::ResultFromCli::Ok(cli_submit) => {
optional_clap_variant = Some(cli_submit)
}
result => return result,
}
}

match optional_clap_variant {
Some(CliSubmit::Send) => match context.signed_transaction_or_signed_delegate_action {
SignedTransactionOrSignedDelegateAction::SignedTransaction(signed_transaction) => {
if let Err(report) = (context.on_before_sending_transaction_callback)(
&signed_transaction,
&context.network_config,
&mut storage_message,
) {
return interactive_clap::ResultFromCli::Err(
optional_clap_variant,
color_eyre::Report::msg(report),
);
};

eprintln!("Transaction sent ...");
let transaction_info = loop {
let transaction_info_result = context.network_config.json_rpc_client()
.blocking_call(
near_jsonrpc_client::methods::broadcast_tx_commit::RpcBroadcastTxCommitRequest{
signed_transaction: signed_transaction.clone()
}
);
match transaction_info_result {
Ok(response) => {
break response;
}
Err(err) => match crate::common::rpc_transaction_error(err) {
Ok(_) => std::thread::sleep(std::time::Duration::from_millis(100)),
Err(report) => {
return interactive_clap::ResultFromCli::Err(
optional_clap_variant,
color_eyre::Report::msg(report),
)
}
},
};
};
if let Err(report) = crate::common::print_transaction_status(
&transaction_info,
&context.network_config,
) {
return interactive_clap::ResultFromCli::Err(
optional_clap_variant,
color_eyre::Report::msg(report),
);
};
if let Err(report) = (context.on_after_sending_transaction_callback)(
&transaction_info,
&context.network_config,
) {
return interactive_clap::ResultFromCli::Err(
optional_clap_variant,
color_eyre::Report::msg(report),
);
};
eprintln!("{storage_message}");
interactive_clap::ResultFromCli::Ok(CliSubmit::Send)
}
SignedTransactionOrSignedDelegateAction::SignedDelegateAction(
signed_delegate_action,
) => {
let client = reqwest::blocking::Client::new();
let json_payload = serde_json::json!({
"signed_delegate_action": crate::types::signed_delegate_action::SignedDelegateActionAsBase64::from(
signed_delegate_action
).to_string()
});
match client
.post(
context
.network_config
.meta_transaction_relayer_url
.expect("Internal error: Meta-transaction relayer URL must be Some() at this point"),
)
.json(&json_payload)
.send()
{
Ok(relayer_response) => {
if relayer_response.status().is_success() {
let response_text = match relayer_response.text() {
Ok(text) => text,
Err(report) => {
return interactive_clap::ResultFromCli::Err(
optional_clap_variant,
color_eyre::Report::msg(report),
)
}
};
println!("Relayer Response text: {}", response_text);
} else {
println!(
"Request failed with status code: {}",
relayer_response.status()
);
}
}
Err(report) => {
return interactive_clap::ResultFromCli::Err(
optional_clap_variant,
color_eyre::Report::msg(report),
)
}
}
eprintln!("{storage_message}");
interactive_clap::ResultFromCli::Ok(CliSubmit::Send)
}
},
Some(CliSubmit::Display) => {
match context.signed_transaction_or_signed_delegate_action {
SignedTransactionOrSignedDelegateAction::SignedTransaction(
signed_transaction,
) => {
if let Err(report) = (context.on_before_sending_transaction_callback)(
&signed_transaction,
&context.network_config,
&mut storage_message,
) {
return interactive_clap::ResultFromCli::Err(
optional_clap_variant,
color_eyre::Report::msg(report),
);
};
eprintln!(
"\nSigned transaction (serialized as base64):\n{}\n",
crate::types::signed_transaction::SignedTransactionAsBase64::from(
signed_transaction
)
);
eprintln!(
"This base64-encoded signed transaction is ready to be sent to the network. You can call RPC server directly, or use a helper command on near CLI:\n$ {} transaction send-signed-transaction\n",
crate::common::get_near_exec_path()
);
eprintln!("{storage_message}");
interactive_clap::ResultFromCli::Ok(CliSubmit::Display)
}
SignedTransactionOrSignedDelegateAction::SignedDelegateAction(
signed_delegate_action,
) => {
eprintln!(
"\nSigned delegate action (serialized as base64):\n{}\n",
crate::types::signed_delegate_action::SignedDelegateActionAsBase64::from(
signed_delegate_action
)
);
eprintln!(
"This base64-encoded signed delegate action is ready to be sent to the meta-transaction relayer. There is a helper command on near CLI that can do that:\n$ {} transaction send-meta-transaction\n",
crate::common::get_near_exec_path()
);
eprintln!("{storage_message}");
interactive_clap::ResultFromCli::Ok(CliSubmit::Display)
}
}
}
None => unreachable!("Unexpected error"),
}
}
Display(self::display::Display),
}

#[derive(Debug, Deserialize)]
Expand Down
99 changes: 99 additions & 0 deletions src/transaction_signature_options/save_to_file/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::io::Write;

use color_eyre::eyre::Context;
use inquire::CustomType;

#[derive(Debug, Clone, interactive_clap_derive::InteractiveClap)]
#[interactive_clap(input_context = super::SubmitContext)]
#[interactive_clap(output_context = SaveToFileContext)]
pub struct SaveToFile {
#[interactive_clap(skip_default_input_arg)]
/// What is the location of the file to save the transaction information (path/to/signed-transaction-info.json)?
file_path: crate::types::path_buf::PathBuf,
}

#[derive(Debug, Clone)]
pub struct SaveToFileContext;

impl SaveToFileContext {
pub fn from_previous_context(
previous_context: super::SubmitContext,
scope: &<SaveToFile as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
let mut storage_message = String::new();
let file_path: std::path::PathBuf = scope.file_path.clone().into();

match previous_context.signed_transaction_or_signed_delegate_action {
super::SignedTransactionOrSignedDelegateAction::SignedTransaction(
signed_transaction,
) => {
(previous_context.on_before_sending_transaction_callback)(
&signed_transaction,
&previous_context.network_config,
&mut storage_message,
)
.map_err(color_eyre::Report::msg)?;

let signed_transaction_as_base64 =
crate::types::signed_transaction::SignedTransactionAsBase64::from(
signed_transaction,
)
.to_string();

let data_signed_transaction = serde_json::json!(
{"Signed transaction (serialized as base64)": signed_transaction_as_base64});

std::fs::File::create(&file_path)
.wrap_err_with(|| format!("Failed to create file: {:?}", &file_path))?
.write(&serde_json::to_vec(&data_signed_transaction)?)
.wrap_err_with(|| format!("Failed to write to file: {:?}", &file_path))?;
eprintln!("\nThe file {:?} was created successfully. It has a signed transaction (serialized as base64).", &file_path);

eprintln!(
"This base64-encoded signed transaction is ready to be sent to the network. You can call RPC server directly, or use a helper command on near CLI:\n$ {} transaction send-signed-transaction\n",
crate::common::get_near_exec_path()
);
eprintln!("{storage_message}");
}
super::SignedTransactionOrSignedDelegateAction::SignedDelegateAction(
signed_delegate_action,
) => {
let signed_delegate_action_as_base64 =
crate::types::signed_delegate_action::SignedDelegateActionAsBase64::from(
signed_delegate_action,
)
.to_string();

let data_signed_delegate_action = serde_json::json!(
{"Signed delegate action (serialized as base64)": signed_delegate_action_as_base64});

std::fs::File::create(&file_path)
.wrap_err_with(|| format!("Failed to create file: {:?}", &file_path))?
.write(&serde_json::to_vec(&data_signed_delegate_action)?)
.wrap_err_with(|| format!("Failed to write to file: {:?}", &file_path))?;
eprintln!("\nThe file {:?} was created successfully. It has a signed delegate action (serialized as base64).", &file_path);

eprintln!(
"This base64-encoded signed delegate action is ready to be sent to the meta-transaction relayer. There is a helper command on near CLI that can do that:\n$ {} transaction send-meta-transaction\n",
crate::common::get_near_exec_path()
);
eprintln!("{storage_message}");
}
}
Ok(Self)
}
}

impl SaveToFile {
fn input_file_path(
_context: &super::SubmitContext,
) -> color_eyre::eyre::Result<Option<crate::types::path_buf::PathBuf>> {
Ok(Some(
CustomType::new(
"What is the location of the file to save the transaction information?",
)
.with_starting_input("signed-transaction-info.json")
.prompt()?,
))
}
}
Loading

0 comments on commit ae25ded

Please sign in to comment.