Skip to content
Merged
Show file tree
Hide file tree
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 Apr 28, 2022
d0e9d8e
Print useful information
adamspofford-dfinity Apr 29, 2022
c0d2bd1
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity Apr 29, 2022
e76f984
Dedupe code from merge
adamspofford-dfinity Apr 29, 2022
4d3553a
fmt
adamspofford-dfinity Apr 29, 2022
f6a1eb0
Add wallet create/import flow
adamspofford-dfinity May 3, 2022
577f21c
tweaks
adamspofford-dfinity May 3, 2022
f580716
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity Aug 17, 2022
b0b2910
those were supposed to be part of the merge
adamspofford-dfinity Aug 17, 2022
87856f0
mismerged
adamspofford-dfinity Aug 17, 2022
f9da4f4
fmt
adamspofford-dfinity Aug 17, 2022
5c15e3a
post merge
adamspofford-dfinity Aug 17, 2022
8422f26
Implement wallet creation
adamspofford-dfinity Aug 17, 2022
488b43b
Discord, not Twitter
adamspofford-dfinity Aug 18, 2022
c81663b
spin
adamspofford-dfinity Aug 18, 2022
23396b0
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity Aug 18, 2022
55bd25c
lint
adamspofford-dfinity Aug 18, 2022
15f7928
Only grab the identity once
adamspofford-dfinity Aug 19, 2022
54d4ad7
convert query call to update call
adamspofford-dfinity Aug 22, 2022
62688e9
Squashed commit of the following:
adamspofford-dfinity Aug 22, 2022
a95db90
break into functions
adamspofford-dfinity Aug 22, 2022
6ecd4e9
Merge branch 'master' into spofford/quickstart
adamspofford-dfinity Aug 22, 2022
8d64880
misleadingly named
adamspofford-dfinity Aug 22, 2022
4694790
"too many arguments"
adamspofford-dfinity Aug 22, 2022
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
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/dfx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "dfx"
version = "0.11.1"
authors = ["DFINITY Team"]
edition = "2018"
edition = "2021"
build = "assets/build.rs"

[[bin]]
Expand Down
2 changes: 1 addition & 1 deletion src/dfx/src/commands/canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ enum SubCommand {
}

macro_rules! with_env {
($opts:expr, |$env:pat, $v:pat, $call_sender:pat| $e:expr) => {{
($opts:expr, |$env:pat, $v:pat, $call_sender:pat_param| $e:expr) => {{
let CanisterOpts {
network_opts: NetworkOpts { base_opts, network },
wallet,
Expand Down
33 changes: 4 additions & 29 deletions src/dfx/src/commands/ledger/balance.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::ledger_types::{AccountBalanceArgs, MAINNET_LEDGER_CANISTER_ID};
use crate::lib::nns_types::account_identifier::{AccountIdentifier, Subaccount};
use crate::lib::nns_types::icpts::ICPTs;
use crate::lib::operations::ledger;

use anyhow::{anyhow, Context};
use anyhow::anyhow;
use candid::Principal;
use candid::{Decode, Encode};
use clap::Parser;
use std::str::FromStr;

const ACCOUNT_BALANCE_METHOD: &str = "account_balance_dfx";

/// Prints the account balance of the user
#[derive(Parser)]
pub struct BalanceOpts {
Expand Down Expand Up @@ -43,30 +39,9 @@ pub async fn exec(env: &dyn Environment, opts: BalanceOpts) -> DfxResult {
.get_agent()
.ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?;

let canister_id = opts
.ledger_canister_id
.unwrap_or(MAINNET_LEDGER_CANISTER_ID);

let result = agent
.query(&canister_id, ACCOUNT_BALANCE_METHOD)
.with_arg(
Encode!(&AccountBalanceArgs {
account: acc_id.to_string()
})
.context("Failed to encode arguments.")?,
)
.call()
.await
.with_context(|| {
format!(
"Failed query call to {} for method {}.",
canister_id, ACCOUNT_BALANCE_METHOD
)
})?;

let balance = Decode!(&result, ICPTs).context("Failed to decode response.")?;
let balance = ledger::balance(agent, &acc_id, opts.ledger_canister_id).await?;

println!("{}", balance);
println!("{balance}");

Ok(())
}
2 changes: 1 addition & 1 deletion src/dfx/src/commands/ledger/create_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use candid::Principal;
use clap::Parser;
use std::str::FromStr;

const MEMO_CREATE_CANISTER: u64 = 1095062083_u64;
pub const MEMO_CREATE_CANISTER: u64 = 1095062083_u64;

/// Create a canister from ICP
#[derive(Parser)]
Expand Down
8 changes: 4 additions & 4 deletions src/dfx/src/commands/ledger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const NOTIFY_CREATE_METHOD: &str = "notify_create_canister";

mod account_id;
mod balance;
mod create_canister;
pub mod create_canister;
mod fabricate_cycles;
mod notify;
mod top_up;
Expand Down Expand Up @@ -183,7 +183,7 @@ pub async fn transfer(
Ok(block_height)
}

async fn transfer_cmc(
pub async fn transfer_cmc(
agent: &Agent,
memo: Memo,
amount: ICPTs,
Expand All @@ -206,7 +206,7 @@ async fn transfer_cmc(
.await
}

async fn notify_create(
pub async fn notify_create(
agent: &Agent,
controller: Principal,
block_height: BlockHeight,
Expand All @@ -228,7 +228,7 @@ async fn notify_create(
Ok(result)
}

async fn notify_top_up(
pub async fn notify_top_up(
agent: &Agent,
canister: Principal,
block_height: BlockHeight,
Expand Down
6 changes: 6 additions & 0 deletions src/dfx/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod ledger;
mod new;
mod nns;
mod ping;
mod quickstart;
mod remote;
mod replica;
mod schema;
Expand Down Expand Up @@ -47,6 +48,7 @@ pub enum Command {
#[clap(hide(true))]
Nns(nns::NnsCommand),
Ping(BaseOpts<ping::PingOpts>),
Quickstart(BaseOpts<Empty>),
Remote(remote::RemoteCommand),
Replica(BaseOpts<replica::ReplicaOpts>),
Schema(BaseOpts<schema::SchemaOpts>),
Expand All @@ -59,6 +61,9 @@ pub enum Command {
Wallet(wallet::WalletCommand),
}

#[derive(Args)]
pub struct Empty;

#[derive(Args)]
pub struct NetworkOpts<T: Args> {
#[clap(flatten)]
Expand Down Expand Up @@ -96,6 +101,7 @@ pub fn dispatch(cmd: Command) -> DfxResult {
}
Command::New(v) => new::exec(&init_env(v.env_opts)?, v.command_opts),
Command::Ping(v) => ping::exec(&init_env(v.env_opts)?, v.command_opts),
Command::Quickstart(v) => quickstart::exec(&init_env(v.env_opts)?),
Command::Replica(v) => replica::exec(&init_env(v.env_opts)?, v.command_opts),
Command::Schema(v) => schema::exec(&init_env(v.env_opts)?, v.command_opts),
Command::Start(v) => start::exec(&init_env(v.env_opts)?, v.command_opts),
Expand Down
224 changes: 224 additions & 0 deletions src/dfx/src/commands/quickstart.rs
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?;
Comment thread
adamspofford-dfinity marked this conversation as resolved.
Comment thread
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.");
}
Loading