-
Notifications
You must be signed in to change notification settings - Fork 98
feat: Add quickstart command #2455
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
Merged
Merged
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
906068e
Refactor ledger functions out into ops module
adamspofford-dfinity d0e9d8e
Print useful information
adamspofford-dfinity c0d2bd1
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity e76f984
Dedupe code from merge
adamspofford-dfinity 4d3553a
fmt
adamspofford-dfinity f6a1eb0
Add wallet create/import flow
adamspofford-dfinity 577f21c
tweaks
adamspofford-dfinity f580716
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity b0b2910
those were supposed to be part of the merge
adamspofford-dfinity 87856f0
mismerged
adamspofford-dfinity f9da4f4
fmt
adamspofford-dfinity 5c15e3a
post merge
adamspofford-dfinity 8422f26
Implement wallet creation
adamspofford-dfinity 488b43b
Discord, not Twitter
adamspofford-dfinity c81663b
spin
adamspofford-dfinity 23396b0
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity 55bd25c
lint
adamspofford-dfinity 15f7928
Only grab the identity once
adamspofford-dfinity 54d4ad7
convert query call to update call
adamspofford-dfinity 62688e9
Squashed commit of the following:
adamspofford-dfinity a95db90
break into functions
adamspofford-dfinity 6ecd4e9
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity 8d64880
misleadingly named
adamspofford-dfinity 4694790
"too many arguments"
adamspofford-dfinity File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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,224 @@ | ||
| use anyhow::{bail, Context}; | ||
| use candid::Principal; | ||
| use dialoguer::{Confirm, Input}; | ||
| use ic_agent::Agent; | ||
| use ic_utils::interfaces::{ | ||
| management_canister::builders::InstallMode, ManagementCanister, WalletCanister, | ||
| }; | ||
| use indicatif::ProgressBar; | ||
| use num_traits::Inv; | ||
| use rust_decimal::Decimal; | ||
| use tokio::runtime::Runtime; | ||
|
|
||
| use crate::{ | ||
| commands::ledger::{create_canister::MEMO_CREATE_CANISTER, notify_create, transfer_cmc}, | ||
| lib::{ | ||
| environment::Environment, | ||
| error::DfxResult, | ||
| identity::Identity, | ||
| ledger_types::{Memo, NotifyError}, | ||
| nns_types::{ | ||
| account_identifier::AccountIdentifier, | ||
| icpts::{ICPTs, TRANSACTION_FEE}, | ||
| }, | ||
| operations::{ | ||
| canister::install_wallet, | ||
| ledger::{balance, xdr_permyriad_per_icp}, | ||
| }, | ||
| provider::create_agent_environment, | ||
| waiter::waiter_with_timeout, | ||
| }, | ||
| util::{assets::wallet_wasm, expiry_duration}, | ||
| }; | ||
|
|
||
| pub fn exec(env: &dyn Environment) -> DfxResult { | ||
| let env = create_agent_environment(env, Some("ic".to_string()))?; | ||
| let agent = env.get_agent().expect("Unable to create agent"); | ||
| let ident = env.get_selected_identity().unwrap(); | ||
| let principal = env.get_selected_identity_principal().unwrap(); | ||
| eprintln!("Your DFX user principal: {principal}"); | ||
| let acct = AccountIdentifier::new(principal, None); | ||
| eprintln!("Your ledger account address: {acct}"); | ||
| let runtime = Runtime::new().expect("Unable to create a runtime"); | ||
| runtime.block_on(async { | ||
| let balance = balance(agent, &acct, None).await?; | ||
|
adamspofford-dfinity marked this conversation as resolved.
|
||
| eprintln!("Your ICP balance: {balance}"); | ||
| let xdr_conversion_rate = xdr_permyriad_per_icp(agent).await?; | ||
| let xdr_per_icp = Decimal::from_i128_with_scale(xdr_conversion_rate as i128, 4); | ||
| let icp_per_tc = xdr_per_icp.inv(); | ||
| eprintln!("Conversion rate: 1 ICP <> {xdr_per_icp} XDR"); | ||
| let wallet = Identity::wallet_canister_id(env.get_network_descriptor(), ident)?; | ||
| if let Some(wallet) = wallet { | ||
| step_print_wallet(agent, wallet).await?; | ||
| } else if Confirm::new() | ||
| .with_prompt("Import an existing wallet?") | ||
| .interact()? | ||
| { | ||
| step_import_wallet(&env, agent, ident).await?; | ||
| } else { | ||
| step_deploy_wallet( | ||
| &env, | ||
| agent, | ||
| acct, | ||
| ident, | ||
| principal, | ||
| balance.to_decimal(), | ||
| xdr_per_icp, | ||
| icp_per_tc, | ||
| ) | ||
| .await?; | ||
| } | ||
| Ok(()) | ||
| }) | ||
| } | ||
|
|
||
| async fn step_print_wallet(agent: &Agent, wallet: Principal) -> DfxResult { | ||
| eprintln!("Mainnet wallet canister: {wallet}"); | ||
| if let Ok(wallet_canister) = WalletCanister::create(agent, wallet).await { | ||
| if let Ok(balance) = wallet_canister.wallet_balance().await { | ||
| eprintln!( | ||
| "Mainnet wallet balance: {:.2} TC", | ||
| Decimal::from(balance.amount) / Decimal::from(1_000_000_000_000_u64) | ||
| ); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| async fn step_import_wallet(env: &dyn Environment, agent: &Agent, ident: &str) -> DfxResult { | ||
| let id = Input::<Principal>::new() | ||
| .with_prompt("Paste the principal ID of the existing wallet") | ||
| .interact_text()?; | ||
| let wallet = if let Ok(wallet) = WalletCanister::create(agent, id).await { | ||
| wallet | ||
| } else { | ||
| let mgmt = ManagementCanister::create(agent); | ||
| let wasm = wallet_wasm(env.get_logger())?; | ||
| mgmt.install_code(&id, &wasm) | ||
| .with_mode(InstallMode::Install) | ||
| .call_and_wait(waiter_with_timeout(expiry_duration())) | ||
| .await?; | ||
| WalletCanister::create(agent, id).await? | ||
| }; | ||
| Identity::set_wallet_id(env.get_network_descriptor(), ident, id)?; | ||
| eprintln!("Successfully imported wallet {id}."); | ||
| if let Ok(balance) = wallet.wallet_balance().await { | ||
| eprintln!( | ||
| "Mainnet wallet balance: {:.2} TC", | ||
| Decimal::from(balance.amount) / Decimal::from(1_000_000_000_000_u64) | ||
| ); | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| async fn step_deploy_wallet( | ||
| env: &dyn Environment, | ||
| agent: &Agent, | ||
| acct: AccountIdentifier, | ||
| ident: &str, | ||
| ident_principal: Principal, | ||
| balance: Decimal, | ||
| xdr_per_icp: Decimal, | ||
| icp_per_tc: Decimal, | ||
| ) -> DfxResult { | ||
| let possible_tc = xdr_per_icp * balance; | ||
| let needed_tc = Decimal::new(10, 0) - possible_tc; | ||
| if needed_tc.is_sign_positive() { | ||
| let needed_icp = needed_tc * icp_per_tc; | ||
| step_explain_deploy(acct, needed_icp.round_dp(8)); | ||
| return Ok(()); | ||
| } | ||
| let to_spend = Decimal::new(10, 0) * icp_per_tc; | ||
| let rounded = to_spend.round_dp(8); | ||
| if !Confirm::new() | ||
| .with_prompt(format!( | ||
| "Spend {rounded:.8} ICP to create a new wallet with 10 TC?" | ||
| )) | ||
| .interact()? | ||
| { | ||
| eprintln!("Run this command again at any time to continue from here."); | ||
| return Ok(()); | ||
| } | ||
| let wallet = step_interact_ledger(agent, ident_principal, rounded).await?; | ||
| step_finish_wallet(env, agent, wallet, ident).await?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| async fn step_interact_ledger( | ||
| agent: &Agent, | ||
| ident_principal: Principal, | ||
| to_spend: Decimal, | ||
| ) -> DfxResult<Principal> { | ||
| let send_spinner = ProgressBar::new_spinner(); | ||
| send_spinner.set_message(format!( | ||
| "Sending {to_spend:.8} ICP to the cycles minting canister..." | ||
| )); | ||
| send_spinner.enable_steady_tick(100); | ||
| let icpts = ICPTs::from_decimal(to_spend)?; | ||
| let height = transfer_cmc( | ||
| agent, | ||
| Memo(MEMO_CREATE_CANISTER /* 👽 */), | ||
| icpts, | ||
| TRANSACTION_FEE, | ||
| None, | ||
| ident_principal, | ||
| ) | ||
| .await | ||
| .context("Failed to transfer to the cycles minting canister")?; | ||
| send_spinner.finish_with_message(format!( | ||
| "Sent {icpts} to the cycles minting canister at height {height}" | ||
| )); | ||
| let notify_spinner = ProgressBar::new_spinner(); | ||
| notify_spinner.set_message("Notifying the the cycles minting canister..."); | ||
| notify_spinner.enable_steady_tick(100); | ||
| let res = notify_create(agent, ident_principal, height).await | ||
| .with_context(|| format!("Failed to notify the CMC of the transfer. Write down that height ({height}), and once the error is fixed, use `dfx ledger notify create-canister`."))?; | ||
| let wallet = match res { | ||
| Ok(principal) => principal, | ||
| Err(NotifyError::Refunded { | ||
| reason, | ||
| block_index, | ||
| }) => { | ||
| match block_index { | ||
| Some(height) => { | ||
| bail!("Refunded at block height {height} with message: {reason}") | ||
| } | ||
| None => bail!("Refunded with message: {reason}"), | ||
| }; | ||
| } | ||
| Err(err) => bail!("{err:?}"), | ||
| }; | ||
| notify_spinner.finish_with_message(format!( | ||
| "Created wallet canister with principal ID {wallet}" | ||
| )); | ||
| Ok(wallet) | ||
| } | ||
|
|
||
| async fn step_finish_wallet( | ||
| env: &dyn Environment, | ||
| agent: &Agent, | ||
| wallet: Principal, | ||
| ident: &str, | ||
| ) -> DfxResult { | ||
| let install_spinner = ProgressBar::new_spinner(); | ||
| install_spinner.set_message("Installing the wallet code to the canister..."); | ||
| install_spinner.enable_steady_tick(100); | ||
| install_wallet(env, agent, wallet, InstallMode::Install) | ||
| .await | ||
| .context("Failed to install the wallet code to the canister")?; | ||
| Identity::set_wallet_id(env.get_network_descriptor(), ident, wallet) | ||
| .context("Failed to record the wallet's principal as your associated wallet")?; | ||
| install_spinner.finish_with_message("Installed the wallet code to the canister"); | ||
| eprintln!("Success! Run this command again at any time to print all this information again."); | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn step_explain_deploy(acct: AccountIdentifier, needed_icp: Decimal) { | ||
| eprintln!("\nYou need {needed_icp:.8} more ICP to deploy a 10 TC wallet canister on mainnet."); | ||
| eprintln!("Deposit at least {needed_icp:.8} ICP into the address {acct}, and then run this command again, to deploy a mainnet wallet."); | ||
| eprintln!("\nAlternatively:"); | ||
| eprintln!("- If you have ICP in an NNS account, you can create a new canister through the NNS interface"); | ||
| eprintln!("- If you have a Discord account, you can request free cycles at https://faucet.dfinity.org"); | ||
| eprintln!("Either of these options will ask for your DFX user principal, listed above."); | ||
| eprintln!("And either of these options will hand you back a wallet canister principal; when you run the command again, select the 'import an existing wallet' option."); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.