Skip to content

Commit

Permalink
feat(cli): new wallet cmd to create a unsigned transaction to be used…
Browse files Browse the repository at this point in the history
… for offline signing
  • Loading branch information
bochaco committed Jan 3, 2024
1 parent 6614f7e commit 5639420
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 18 deletions.
3 changes: 2 additions & 1 deletion sn_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ async fn main() -> Result<()> {
if let WalletCmds::Address
| WalletCmds::Balance { .. }
| WalletCmds::Deposit { .. }
| WalletCmds::Create { .. } = cmds
| WalletCmds::Create { .. }
| WalletCmds::Transaction { .. } = cmds
{
wallet_cmds_without_client(cmds, &client_data_dir_path).await?;
return Ok(());
Expand Down
81 changes: 69 additions & 12 deletions sn_cli/src/subcommands/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ pub enum WalletCmds {
#[clap(long)]
cash_note: Option<String>,
},
/// Create a local wallet from the given (hex-encoded) Secret Key.
/// Create a hot or watch-only wallet from the given (hex-encoded) key.
Create {
/// Hex-encoded main secret key
#[clap(name = "sk")]
sk: String,
/// Hex-encoded main secret or public key. If the key is a secret key a hot-wallet will be created
/// which can be used to sign and broadcast transfers. Otherwise, if the passed key is a public key,
/// then a watch-only wallet is created.
#[clap(name = "key")]
key: String,
},
/// Get tokens from a faucet.
GetFaucet {
Expand All @@ -85,6 +87,15 @@ pub enum WalletCmds {
#[clap(name = "to")]
to: String,
},
/// Builds an unsigned transaction to be signed offline.
Transaction {
/// The number of SafeNetworkTokens to transfer.
#[clap(name = "amount")]
amount: String,
/// Hex-encoded public address of the recipient.
#[clap(name = "to")]
to: String,
},
/// Receive a transfer created by the 'send' command.
Receive {
/// Read the encrypted transfer from a file.
Expand Down Expand Up @@ -156,18 +167,36 @@ pub(crate) async fn wallet_cmds_without_client(cmds: &WalletCmds, root_dir: &Pat
Ok(())
}
WalletCmds::Deposit { stdin, cash_note } => deposit(root_dir, *stdin, cash_note.as_deref()),
WalletCmds::Create { sk } => {
let main_sk = match SecretKey::from_hex(sk) {
Ok(sk) => MainSecretKey::new(sk),
Err(err) => return Err(eyre!("Failed to parse hex-encoded SK: {err:?}")),
WalletCmds::Create { key } => {
match SecretKey::from_hex(key) {
Ok(sk) => {
let main_sk = MainSecretKey::new(sk);
// TODO: encrypt wallet file

Check notice

Code scanning / devskim

A "TODO" or similar was left in source code, possibly indicating incomplete functionality Note

Suspicious comment
let local_wallet = LocalWallet::load_from_main_key(root_dir, main_sk)?;
let balance = local_wallet.balance();
println!("Hot-Wallet created (balance {balance}) for main secret key.");
}
Err(_err) => {
let main_pk = match PublicKey::from_hex(key) {
Ok(pk) => MainPubkey::new(pk),
Err(err) => return Err(eyre!("Failed to parse hex-encoded PK: {err:?}")),
};
let pk_hex = main_pk.to_hex();
let folder_name =
format!("pk_{}_{}", &pk_hex[..6], &pk_hex[pk_hex.len() - 6..]);
let wallet_dir = root_dir.join(folder_name);
let main_pubkey = main_pk.public_key();
let watch_only_wallet = WatchOnlyWallet::load_from(&wallet_dir, main_pk)?;
let balance = watch_only_wallet.balance();
println!("Watch-only wallet created (balance {balance}) for main public key: {main_pubkey:?}.");
}
};
let main_pubkey = main_sk.main_pubkey();
let local_wallet = LocalWallet::load_from_main_key(root_dir, main_sk)?;
let balance = local_wallet.balance();
println!("Wallet created (balance {balance}) for main public key: {main_pubkey:?}.");

Ok(())
}
WalletCmds::Transaction { amount, to } => {
build_unsigned_transaction(amount, to, root_dir).await
}
cmd => Err(eyre!("{cmd:?} requires us to be connected to the Network")),
}
}
Expand Down Expand Up @@ -381,6 +410,34 @@ async fn send(
Ok(())
}

async fn build_unsigned_transaction(amount: &str, to: &str, root_dir: &Path) -> Result<()> {
let mut wallet = LocalWallet::load_from(root_dir)?;
let amount = match NanoTokens::from_str(amount) {
Ok(amount) => amount,
Err(err) => {
println!("The amount cannot be parsed. Nothing sent.");
return Err(err.into());
}
};
let to = match MainPubkey::from_hex(to) {
Ok(to) => to,
Err(err) => {
println!("Error while parsing the recipient's 'to' key: {err:?}");
return Err(err.into());
}
};

let unsigned_tx = wallet.build_unsigned_transaction(vec![(amount, to)], None)?;

println!(
"The unsigned transaction has been successfully created:\n\n{}\n",
hex::encode(unsigned_tx.to_bytes())
);
println!("Please copy the above text, sign it offline with 'wallet sign' cmd, and then use the signed transaction to broadcast it with 'wallet broadcast' cmd.");

Ok(())
}

async fn receive(transfer: String, is_file: bool, client: &Client, root_dir: &Path) -> Result<()> {
let transfer = if is_file {
std::fs::read_to_string(transfer)?.trim().to_string()
Expand Down
2 changes: 1 addition & 1 deletion sn_transfers/src/transfers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@
mod offline_transfer;
mod transfer;

pub use offline_transfer::{create_offline_transfer, create_unsigned_transfer, OfflineTransfer};
pub use offline_transfer::{build_unsigned_transaction, create_offline_transfer, OfflineTransfer};
pub use transfer::{CashNoteRedemption, Transfer};
2 changes: 1 addition & 1 deletion sn_transfers/src/transfers/offline_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub fn create_offline_transfer(
}

/// A function for creating an unsigned transfer of tokens.
pub fn create_unsigned_transfer(
pub fn build_unsigned_transaction(
available_cash_notes: Vec<(CashNote, Option<DerivedSecretKey>)>,
recipients: Vec<(NanoTokens, MainPubkey, DerivationIndex)>,
change_to: MainPubkey,
Expand Down
6 changes: 3 additions & 3 deletions sn_transfers/src/wallet/local_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use super::{

use crate::{
calculate_royalties_fee,
transfers::{create_offline_transfer, create_unsigned_transfer, OfflineTransfer},
transfers::{build_unsigned_transaction, create_offline_transfer, OfflineTransfer},
CashNote, CashNoteRedemption, DerivationIndex, DerivedSecretKey, Hash, MainPubkey,
MainSecretKey, NanoTokens, SignedSpend, Transaction, Transfer, UniquePubkey, WalletError,
NETWORK_ROYALTIES_PK,
Expand Down Expand Up @@ -221,7 +221,7 @@ impl LocalWallet {
self.watchonly_wallet.get_payment_transaction(name)
}

pub fn create_unsigned_transfer(
pub fn build_unsigned_transaction(
&mut self,
to: Vec<(NanoTokens, MainPubkey)>,
reason_hash: Option<Hash>,
Expand Down Expand Up @@ -256,7 +256,7 @@ impl LocalWallet {

let reason_hash = reason_hash.unwrap_or_default();

let unsigned_transfer = create_unsigned_transfer(
let unsigned_transfer = build_unsigned_transaction(
available_cash_notes,
to_unique_keys,
self.address(),
Expand Down

0 comments on commit 5639420

Please sign in to comment.