-
Notifications
You must be signed in to change notification settings - Fork 296
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
180 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
use crate::App; | ||
use anyhow::{Context, Result}; | ||
use penumbra_fee::FeeTier; | ||
use penumbra_fee::GasPrices; | ||
use penumbra_keys::{keys::AddressIndex, FullViewingKey}; | ||
use penumbra_proto::view::v1::GasPricesRequest; | ||
use penumbra_view::ViewClient; | ||
use penumbra_wallet::plan::Planner; | ||
use rand_core::OsRng; | ||
use std::str::FromStr; | ||
|
||
#[derive(Debug, clap::Parser)] | ||
pub enum MigrateCmd { | ||
/// Migrate your entire balance to the wallet of the provided FullViewingKey | ||
#[clap(name = "balance")] | ||
Balance { | ||
/// The FullViewingKey associated with the destination wallet. | ||
#[clap(long)] | ||
to: String, | ||
}, | ||
} | ||
|
||
impl MigrateCmd { | ||
#[tracing::instrument(skip(self, app))] | ||
pub async fn exec(&self, app: &mut App) -> Result<()> { | ||
let gas_prices: GasPrices = app | ||
.view | ||
.as_mut() | ||
.context("view service must be initialized")? | ||
.gas_prices(GasPricesRequest {}) | ||
.await? | ||
.into_inner() | ||
.gas_prices | ||
.expect("gas prices must be available") | ||
.try_into()?; | ||
|
||
match self { | ||
MigrateCmd::Balance { to } => { | ||
let source_fvk = app.config.full_viewing_key.clone(); | ||
|
||
let dest_fvk = to.parse::<FullViewingKey>().map_err(|_| { | ||
anyhow::anyhow!("The provided string is not a valid FullViewingKey.") | ||
})?; | ||
|
||
let mut planner = Planner::new(OsRng); | ||
|
||
let memo = format!("Migrating balance from {} to {}", source_fvk, dest_fvk); | ||
|
||
let (source_address, _) = | ||
FullViewingKey::payment_address(&source_fvk, AddressIndex::new(0)); | ||
|
||
let (dest_address, _) = FullViewingKey::payment_address( | ||
&FullViewingKey::from_str(&to[..])?, | ||
AddressIndex::new(0), | ||
); | ||
|
||
planner | ||
.set_gas_prices(gas_prices) | ||
.set_fee_tier(FeeTier::default()); | ||
|
||
// Return all unspent notes from the view service | ||
let notes = app | ||
.view | ||
.as_mut() | ||
.context("view service must be initialized")? | ||
.unspent_notes_by_account_and_asset() | ||
.await?; | ||
|
||
// Get all remaining note values after filtering out notes already spent by the planner | ||
let note_values = notes.iter().flat_map(|(_, notes_by_asset)| { | ||
notes_by_asset.iter().map(|(asset, notes)| { | ||
let sum: u128 = notes | ||
.iter() | ||
.map(|record| u128::from(record.note.amount())) | ||
.sum(); | ||
|
||
asset.value(sum.into()) | ||
}) | ||
}); | ||
|
||
// Add all note values to the planner | ||
note_values.clone().for_each(|value| { | ||
planner.output(value, dest_address.clone()); | ||
}); | ||
|
||
let fee = | ||
planner.compute_fee_estimate(&gas_prices, &FeeTier::default(), &source_address); | ||
|
||
println!("Estimated fee for sending total balance: {:?}", fee); | ||
|
||
// Update relevant note values to be less the estimated fee | ||
|
||
let mut note_values_2 = Vec::new(); | ||
|
||
note_values.for_each(|value| { | ||
if value.asset_id == fee.0.asset_id { | ||
note_values_2.push(penumbra_asset::Value { | ||
asset_id: value.asset_id, | ||
amount: value.amount - fee.0.amount, | ||
}); | ||
} else { | ||
note_values_2.push(penumbra_asset::Value { | ||
asset_id: value.asset_id, | ||
amount: value.amount, | ||
}); | ||
} | ||
}); | ||
|
||
let mut planner_2 = Planner::new(OsRng); | ||
|
||
planner_2 | ||
.set_gas_prices(gas_prices) | ||
.set_fee_tier(FeeTier::default()); | ||
|
||
// Add all note values to the second planner | ||
note_values_2.clone().into_iter().for_each(|value| { | ||
planner_2.output(value, dest_address.clone()); | ||
}); | ||
|
||
// defaulting to index 0 (the default account) here means that this operation | ||
// will fail if the default account has insufficient funds for fees | ||
// a more general solution could be finding the first/lowest-indexed account with sufficient funds | ||
// and spending fees from that account | ||
let address_index = AddressIndex::new(0); | ||
|
||
let plan = planner_2 | ||
.memo(memo) | ||
.plan( | ||
app.view | ||
.as_mut() | ||
.context("view service must be initialized")?, | ||
address_index, | ||
) | ||
.await | ||
.context("can't build send transaction")?; | ||
|
||
app.build_and_submit_transaction(plan).await?; | ||
|
||
Result::Ok(()) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters