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: Added the ability to output a signed transaction (serialized as base64) to a file #313

Merged
merged 2 commits into from
Mar 18, 2024
Merged
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
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
Loading