Skip to content
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
522 changes: 261 additions & 261 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,23 @@ serde_with = "3.12.0"
serial_test = "3.2.0"
solana-account = "2.2.1"
solana-account-info = "2.2.1"
solana-clap-v3-utils = "2.2.1"
solana-cli-config = "2.2.1"
solana-cli-output = "2.2.1"
solana-client = "2.2.1"
solana-commitment-config = "2.2.1"
solana-cpi = "2.2.1"
solana-decode-error = "2.2.1"
solana-logger = "2.2.1"
solana-hash = "2.2.1"
solana-instruction = "2.2.1"
solana-keypair = "2.2.1"
solana-msg = "2.2.1"
solana-presigner = "2.2.1"
solana-program-entrypoint = "2.2.1"
solana-program-error = "2.2.1"
solana-program-option = "2.2.1"
solana-program-pack = "2.2.1"
solana-pubkey = "2.2.1"
solana-remote-wallet = "2.2.1"
solana-rent = "2.2.1"
solana-sdk-ids = "2.2.1"
solana-signature = "2.2.1"
Expand All @@ -82,3 +82,8 @@ tokio = { version = "1.44.1", features = ["full"] }

# Should depend on the next crate version after 7.0.0 when https://github.com/solana-program/token-2022/pull/253 is deployed
spl-token-2022 = { git = "https://github.com/solana-program/token-2022", rev = "00e0f4723c2606c0facbb4921e1b2e2e030d1fa6", features = ["no-entrypoint"] }

# Should depend on next version of these packages after https://github.com/anza-xyz/agave/pull/5534 is deployed
solana-clap-v3-utils = { git = "https://github.com/anza-xyz/agave", package = "solana-clap-v3-utils", rev = "8cf1f8dbf36b2d7b083d5e9883f51282b2cb7c86" }
# Need to specify same commit as above for type compatiblity
solana-remote-wallet = { git = "https://github.com/anza-xyz/agave", package = "solana-remote-wallet", rev = "8cf1f8dbf36b2d7b083d5e9883f51282b2cb7c86" }
2 changes: 2 additions & 0 deletions clients/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ solana-cli-config = { workspace = true }
solana-cli-output = { workspace = true }
solana-client = { workspace = true }
solana-commitment-config = { workspace = true }
solana-hash = { workspace = true }
solana-instruction = { workspace = true }
solana-logger = { workspace = true }
solana-presigner = { workspace = true }
solana-program-pack = { workspace = true }
solana-pubkey = { workspace = true }
solana-remote-wallet = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions clients/cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub struct Cli {
pub output_format: Option<OutputFormat>,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Subcommand)]
pub enum Command {
/// Create a wrapped mint for a given SPL Token
Expand Down
13 changes: 13 additions & 0 deletions clients/cli/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ use {
crate::{config::Config, output::println_display, Error},
clap::ArgMatches,
solana_clap_v3_utils::keypair::pubkey_from_path,
solana_presigner::Presigner,
solana_pubkey::Pubkey,
solana_signature::Signature,
solana_transaction::Transaction,
std::str::FromStr,
};

pub fn parse_pubkey(value: &str) -> Result<Pubkey, String> {
Expand All @@ -26,6 +28,17 @@ pub fn parse_token_program(value: &str) -> Result<Pubkey, String> {
}
}

pub fn parse_presigner(value: &str) -> Result<Presigner, String> {
let (pubkey_string, sig_string) = value
.split_once('=')
.ok_or("failed to split `pubkey=signature` pair")?;
let pubkey = Pubkey::from_str(pubkey_string)
.map_err(|_| "Failed to parse pubkey from string".to_string())?;
let sig = Signature::from_str(sig_string)
.map_err(|_| "Failed to parse signature from string".to_string())?;
Ok(Presigner::new(&pubkey, &sig))
}

pub async fn process_transaction(
config: &Config,
transaction: Transaction,
Expand Down
18 changes: 13 additions & 5 deletions clients/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use {
crate::{cli::Cli, Error},
anyhow::anyhow,
clap::ArgMatches,
solana_clap_v3_utils::keypair::{signer_from_path, signer_from_source},
solana_clap_v3_utils::keypair::{
signer_from_path, signer_from_source_with_config, SignerFromPathConfig,
},
solana_cli_output::OutputFormat,
solana_client::nonblocking::rpc_client::RpcClient,
solana_commitment_config::CommitmentConfig,
Expand Down Expand Up @@ -39,13 +41,19 @@ impl Config {
));

let fee_payer = match &cli.fee_payer {
Some(fee_payer_source) => {
signer_from_source(&matches, fee_payer_source, "fee_payer", wallet_manager)
}
Some(fee_payer_source) => signer_from_source_with_config(
&matches,
fee_payer_source,
"fee_payer",
wallet_manager,
&SignerFromPathConfig {
allow_null_signer: true,
},
),
None => signer_from_path(
&matches,
&cli_config.keypair_path,
"default",
"fee_payer",
wallet_manager,
),
}
Expand Down
173 changes: 140 additions & 33 deletions clients/cli/src/wrap.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
use {
crate::{
common::{parse_pubkey, parse_token_program, process_transaction},
common::{parse_presigner, parse_pubkey, parse_token_program, process_transaction},
config::Config,
output::{format_output, println_display},
CommandResult, Error,
},
clap::Args,
clap::{value_parser, Args},
serde_derive::{Deserialize, Serialize},
serde_with::{serde_as, DisplayFromStr},
solana_cli_output::{display::writeln_name_value, QuietDisplay, VerboseDisplay},
solana_clap_v3_utils::{
input_parsers::signer::{SignerSource, SignerSourceParserBuilder},
keypair::{signer_from_source_with_config, SignerFromPathConfig},
},
solana_cli_output::{
display::writeln_name_value, return_signers_data, CliSignOnlyData, QuietDisplay,
ReturnSignersConfig, VerboseDisplay,
},
solana_client::nonblocking::rpc_client::RpcClient,
solana_hash::Hash,
solana_presigner::Presigner,
solana_pubkey::Pubkey,
solana_remote_wallet::remote_wallet::RemoteWalletManager,
solana_signature::Signature,
Expand Down Expand Up @@ -43,10 +52,12 @@ pub struct WrapArgs {
#[clap(value_parser)]
pub amount: u64,

/// Path to the signer for the transfer authority if different from
/// fee payer
#[clap(long, value_name = "PATH")]
pub transfer_authority: Option<String>,
/// Signer source of transfer authority if different from fee payer
#[clap(
long,
value_parser = SignerSourceParserBuilder::default().allow_all().build()
)]
pub transfer_authority: Option<SignerSource>,

/// The address of the mint to wrap, queried if not provided
#[clap(long, value_parser = parse_pubkey)]
Expand All @@ -61,6 +72,33 @@ pub struct WrapArgs {
/// Queries account for `unwrapped_token_account` if not provided.
#[clap(long, value_parser = parse_token_program)]
pub unwrapped_token_program: Option<Pubkey>,

/// Member signer of a multisig account.
/// Use this argument multiple times for each signer.
#[clap(
long,
multiple = true,
value_parser = SignerSourceParserBuilder::default().allow_all().build(),
requires = "blockhash"
)]
pub multisig_signer: Option<Vec<SignerSource>>,

#[clap(long, value_parser = value_parser!(Hash))]
pub blockhash: Option<Hash>,

/// Signatures to add to transaction.
/// Often the `PUBKEY=SIGNATURE` output from a multisig --sign-only signer.
#[clap(
long,
multiple = true,
value_parser = parse_presigner,
requires = "blockhash"
)]
pub signer: Option<Vec<Presigner>>,

/// Do not broadcast signed transaction, just sign
#[clap(long)]
pub sign_only: bool,
}

#[serde_as]
Expand All @@ -69,17 +107,24 @@ pub struct WrapArgs {
pub struct WrapOutput {
#[serde_as(as = "DisplayFromStr")]
pub unwrapped_mint_address: Pubkey,

#[serde_as(as = "DisplayFromStr")]
pub wrapped_mint_address: Pubkey,

#[serde_as(as = "DisplayFromStr")]
pub unwrapped_token_account: Pubkey,

#[serde_as(as = "DisplayFromStr")]
pub recipient_token_account: Pubkey,

#[serde_as(as = "DisplayFromStr")]
pub escrow_account: Pubkey,

pub amount: u64,
#[serde_as(as = "Option<DisplayFromStr>")]
pub signature: Option<Signature>,

pub signatures: Vec<Signature>,

pub sign_only_data: Option<CliSignOnlyData>,
}

impl Display for WrapOutput {
Expand All @@ -106,9 +151,16 @@ impl Display for WrapOutput {
)?;
writeln_name_value(f, "Escrow account:", &self.escrow_account.to_string())?;
writeln_name_value(f, "Amount:", &self.amount.to_string())?;
if let Some(signature) = self.signature {
writeln_name_value(f, "Signature:", &signature.to_string())?;

if let Some(data) = &self.sign_only_data {
writeln!(f, "{}", data)?;
} else {
writeln!(f, "Signers:")?;
for signature in &self.signatures {
writeln!(f, " {signature}")?;
}
}

Ok(())
}
}
Expand Down Expand Up @@ -143,13 +195,15 @@ pub async fn command_wrap(
get_unwrapped_mint(&config.rpc_client, &args.unwrapped_token_account).await?
};

println_display(
config,
format!(
"Wrapping {} tokens from mint {}",
args.amount, unwrapped_mint
),
);
if !args.sign_only {
println_display(
config,
format!(
"Wrapping {} tokens from mint {}",
args.amount, unwrapped_mint
),
);
}

// Derive wrapped mint address and mint authority
let wrapped_mint_address =
Expand All @@ -165,21 +219,47 @@ pub async fn command_wrap(
)
});

// NullSigner used for multisig scenarios
let parse_config = SignerFromPathConfig {
allow_null_signer: true,
};

// If transfer_authority is provided, use it as a signer,
// else default to fee payer
let transfer_authority_signer = if let Some(authority_keypair_path) = &args.transfer_authority {
let signer = solana_clap_v3_utils::keypair::signer_from_path(
let signer = signer_from_source_with_config(
matches,
authority_keypair_path,
"transfer-authority",
"transfer_authority",
wallet_manager,
&parse_config,
)
.map_err(|e| e.to_string())?;
Arc::from(signer)
} else {
payer.clone()
};

let mut multisig_signers: Vec<Arc<dyn Signer>> = vec![];
if let Some(sources) = &args.multisig_signer {
for source in sources {
let signer = signer_from_source_with_config(
matches,
source,
"multisig_signer",
wallet_manager,
&parse_config,
)
.map_err(|e| e.to_string())?;
multisig_signers.push(Arc::from(signer));
}
}

let multisig_pubkeys = multisig_signers
.iter()
.map(|s| s.pubkey())
.collect::<Vec<Pubkey>>();

let unwrapped_token_program = if let Some(pubkey) = args.unwrapped_token_program {
pubkey
} else {
Expand All @@ -201,25 +281,51 @@ pub async fn command_wrap(
&unwrapped_mint,
&args.escrow_account,
&transfer_authority_signer.pubkey(),
&[], // TODO: Add multisig support
&multisig_pubkeys.iter().collect::<Vec<&Pubkey>>(),
args.amount,
);

let latest_blockhash = config.rpc_client.get_latest_blockhash().await?;
let mut signers = vec![payer.as_ref()];
let blockhash = if let Some(hash) = args.blockhash {
hash
} else {
config.rpc_client.get_latest_blockhash().await?
};

if payer.pubkey() != transfer_authority_signer.pubkey() {
signers.push(transfer_authority_signer.as_ref());
// Payer will always be a signer
let mut signers = vec![payer.clone()];

// In the case that a transfer_authority is passed (otherwise defaults to
// payer), it needs to be added to signers if it isn't a multisig.
if payer.pubkey() != transfer_authority_signer.pubkey() && multisig_signers.is_empty() {
signers.push(transfer_authority_signer);
}

let transaction = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&signers,
latest_blockhash,
);
for signer in &multisig_signers {
signers.push(signer.clone());
}

// Pre-signed transactions can be passed as --signer `PUBKEY=SIGNATURE`
if let Some(pre_signers) = &args.signer {
for signer in pre_signers {
signers.push(Arc::from(signer));
}
}

let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
transaction.partial_sign(&signers, blockhash);

let signature = process_transaction(config, transaction).await?;
if !args.sign_only {
process_transaction(config, transaction.clone()).await?;
}

let sign_only_data = args.sign_only.then(|| {
return_signers_data(
&transaction,
&ReturnSignersConfig {
dump_transaction_message: true,
},
)
});

let output = WrapOutput {
unwrapped_mint_address: unwrapped_mint,
Expand All @@ -228,7 +334,8 @@ pub async fn command_wrap(
recipient_token_account,
escrow_account: args.escrow_account,
amount: args.amount,
signature,
signatures: transaction.signatures,
sign_only_data,
};

Ok(format_output(config, output))
Expand Down
Loading