Skip to content

Commit 7a5447b

Browse files
authored
chore: separate token operations from commands (#208)
1 parent 7abf84a commit 7a5447b

File tree

11 files changed

+675
-414
lines changed

11 files changed

+675
-414
lines changed
Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,53 @@
11
use icp::context::Context;
2+
use icp::{agent, context::GetAgentError, identity, network};
23

34
use crate::commands::token;
5+
use crate::operations::token::balance::{GetBalanceError, get_balance};
46

57
#[derive(Debug, thiserror::Error)]
68
pub(crate) enum CommandError {
79
#[error(transparent)]
8-
Balance(#[from] token::balance::CommandError),
10+
Project(#[from] icp::LoadError),
11+
12+
#[error(transparent)]
13+
Identity(#[from] identity::LoadError),
14+
15+
#[error(transparent)]
16+
Access(#[from] network::AccessError),
17+
18+
#[error(transparent)]
19+
Agent(#[from] agent::CreateAgentError),
20+
21+
#[error(transparent)]
22+
GetAgent(#[from] GetAgentError),
23+
24+
#[error(transparent)]
25+
GetBalance(#[from] GetBalanceError),
926
}
1027

1128
pub(crate) async fn exec(
1229
ctx: &Context,
1330
args: &token::balance::BalanceArgs,
1431
) -> Result<(), CommandError> {
15-
token::balance::exec(ctx, "cycles", args)
16-
.await
17-
.map_err(Into::into)
32+
let selections = args.token_command_args.selections();
33+
34+
// Agent
35+
let agent = ctx
36+
.get_agent(
37+
&selections.identity,
38+
&selections.network,
39+
&selections.environment,
40+
)
41+
.await?;
42+
43+
// Get the balance from the ledger
44+
let balance_info = get_balance(&agent, "cycles").await?;
45+
46+
// Output information
47+
let _ = ctx.term.write_line(&format!(
48+
"Balance: {} {}",
49+
balance_info.amount, balance_info.symbol
50+
));
51+
52+
Ok(())
1853
}

crates/icp-cli/src/commands/cycles/mint.rs

Lines changed: 17 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,11 @@
1-
use bigdecimal::{BigDecimal, ToPrimitive};
2-
use candid::{Decode, Encode};
1+
use bigdecimal::BigDecimal;
32
use clap::Args;
4-
use ic_agent::AgentError;
5-
use ic_ledger_types::{
6-
AccountIdentifier, Memo, Subaccount, Tokens, TransferArgs, TransferError, TransferResult,
7-
};
83
use icp::{agent, context::GetAgentError, identity, network};
9-
use icp_canister_interfaces::{
10-
cycles_ledger::CYCLES_LEDGER_BLOCK_FEE,
11-
cycles_minting_canister::{
12-
CYCLES_MINTING_CANISTER_PRINCIPAL, ConversionRateResponse, MEMO_MINT_CYCLES,
13-
NotifyMintArgs, NotifyMintErr, NotifyMintResponse,
14-
},
15-
icp_ledger::{ICP_LEDGER_BLOCK_FEE_E8S, ICP_LEDGER_PRINCIPAL},
16-
};
174

185
use icp::context::Context;
196

207
use crate::commands::args::TokenCommandArgs;
8+
use crate::operations::token::mint::{MintCyclesError, mint_cycles};
219

2210
#[derive(Debug, Args)]
2311
pub(crate) struct MintArgs {
@@ -47,38 +35,22 @@ pub(crate) enum CommandError {
4735
#[error(transparent)]
4836
Agent(#[from] agent::CreateAgentError),
4937

50-
#[error("Failed to get identity principal: {message}")]
51-
Principal { message: String },
52-
53-
#[error("Failed to talk to {canister} canister: {source}")]
54-
CanisterError {
55-
canister: String,
56-
source: AgentError,
57-
},
58-
59-
#[error("ICP amount overflow. Specify less tokens.")]
60-
IcpAmountOverflow,
61-
62-
#[error("Failed ICP ledger transfer: {src:?}")]
63-
TransferError { src: TransferError },
38+
#[error(transparent)]
39+
GetAgent(#[from] GetAgentError),
6440

65-
#[error("Insufficient funds: {required} ICP required, {available} ICP available.")]
66-
InsufficientFunds {
67-
required: BigDecimal,
68-
available: BigDecimal,
69-
},
41+
#[error(transparent)]
42+
MintCycles(#[from] MintCyclesError),
7043

7144
#[error("No amount specified. Use --icp or --cycles.")]
7245
NoAmountSpecified,
73-
74-
#[error("Failed to notify mint cycles: {src:?}")]
75-
NotifyMintError { src: NotifyMintErr },
76-
77-
#[error(transparent)]
78-
GetAgent(#[from] GetAgentError),
7946
}
8047

8148
pub(crate) async fn exec(ctx: &Context, args: &MintArgs) -> Result<(), CommandError> {
49+
// Validate args
50+
if args.icp.is_none() && args.cycles.is_none() {
51+
return Err(CommandError::NoAmountSpecified);
52+
}
53+
8254
let selections = args.token_command_args.selections();
8355

8456
// Agent
@@ -90,116 +62,13 @@ pub(crate) async fn exec(ctx: &Context, args: &MintArgs) -> Result<(), CommandEr
9062
)
9163
.await?;
9264

93-
// Prepare deposit
94-
let user_principal = agent
95-
.get_principal()
96-
.map_err(|e| CommandError::Principal { message: e })?;
97-
98-
let icp_e8s_to_deposit = if let Some(icp_amount) = &args.icp {
99-
(icp_amount * 100_000_000_u64)
100-
.to_u64()
101-
.ok_or(CommandError::IcpAmountOverflow)?
102-
} else if let Some(cycles_amount) = args.cycles {
103-
let cmc_response = agent
104-
.query(
105-
&CYCLES_MINTING_CANISTER_PRINCIPAL,
106-
"get_icp_xdr_conversion_rate",
107-
)
108-
.with_arg(Encode!(&()).expect("Failed to encode get ICP XDR conversion rate args"))
109-
.call()
110-
.await
111-
.map_err(|e| CommandError::CanisterError {
112-
canister: "cmc".to_string(),
113-
source: e,
114-
})?;
115-
116-
let cmc_response =
117-
Decode!(&cmc_response, ConversionRateResponse).expect("CMC response type changed");
118-
let cycles_per_e8s = cmc_response.data.xdr_permyriad_per_icp as u128;
119-
let cycles_plus_fees = cycles_amount + CYCLES_LEDGER_BLOCK_FEE;
120-
let e8s_to_deposit = cycles_plus_fees.div_ceil(cycles_per_e8s);
121-
122-
e8s_to_deposit
123-
.to_u64()
124-
.ok_or(CommandError::IcpAmountOverflow)?
125-
} else {
126-
return Err(CommandError::NoAmountSpecified);
127-
};
128-
129-
let account_id = AccountIdentifier::new(
130-
&CYCLES_MINTING_CANISTER_PRINCIPAL,
131-
&Subaccount::from(user_principal),
132-
);
133-
let memo = Memo(MEMO_MINT_CYCLES);
134-
let transfer_args = TransferArgs {
135-
memo,
136-
amount: Tokens::from_e8s(icp_e8s_to_deposit),
137-
fee: Tokens::from_e8s(ICP_LEDGER_BLOCK_FEE_E8S),
138-
from_subaccount: None,
139-
to: account_id,
140-
created_at_time: None,
141-
};
142-
143-
let transfer_result = agent
144-
.update(&ICP_LEDGER_PRINCIPAL, "transfer")
145-
.with_arg(Encode!(&transfer_args).expect("Failed to encode transfer args"))
146-
.call_and_wait()
147-
.await
148-
.map_err(|e| CommandError::CanisterError {
149-
canister: "ICP ledger".to_string(),
150-
source: e,
151-
})?;
152-
let transfer_response =
153-
Decode!(&transfer_result, TransferResult).expect("ICP ledger transfer result type changed");
154-
let block_index = match transfer_response {
155-
Ok(block_index) => block_index,
156-
Err(err) => match err {
157-
TransferError::TxDuplicate { duplicate_of } => duplicate_of,
158-
TransferError::InsufficientFunds { balance } => {
159-
let required =
160-
BigDecimal::new((icp_e8s_to_deposit + ICP_LEDGER_BLOCK_FEE_E8S).into(), 8);
161-
let available = BigDecimal::new(balance.e8s().into(), 8);
162-
return Err(CommandError::InsufficientFunds {
163-
required,
164-
available,
165-
});
166-
}
167-
err => {
168-
return Err(CommandError::TransferError { src: err });
169-
}
170-
},
171-
};
172-
173-
let notify_response = agent
174-
.update(&CYCLES_MINTING_CANISTER_PRINCIPAL, "notify_mint_cycles")
175-
.with_arg(
176-
Encode!(&NotifyMintArgs {
177-
block_index,
178-
deposit_memo: None,
179-
to_subaccount: None,
180-
})
181-
.expect("Failed to encode notify mint cycles args"),
182-
)
183-
.call_and_wait()
184-
.await
185-
.map_err(|e| CommandError::CanisterError {
186-
canister: "cmc".to_string(),
187-
source: e,
188-
})?;
189-
let notify_response = Decode!(&notify_response, NotifyMintResponse)
190-
.expect("Notify mint cycles response type changed");
191-
let minted = match notify_response {
192-
NotifyMintResponse::Ok(ok) => ok,
193-
NotifyMintResponse::Err(err) => {
194-
return Err(CommandError::NotifyMintError { src: err });
195-
}
196-
};
197-
198-
// display
199-
let deposited = BigDecimal::new((minted.minted - CYCLES_LEDGER_BLOCK_FEE).into(), 12);
200-
let new_balance = BigDecimal::new(minted.balance.into(), 12);
65+
// Execute mint operation
66+
let mint_info = mint_cycles(&agent, args.icp.as_ref(), args.cycles).await?;
67+
68+
// Display results
20169
let _ = ctx.term.write_line(&format!(
202-
"Minted {deposited} TCYCLES to your account, new balance: {new_balance} TCYCLES."
70+
"Minted {} TCYCLES to your account, new balance: {} TCYCLES.",
71+
mint_info.deposited, mint_info.new_balance
20372
));
20473

20574
Ok(())
Lines changed: 10 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
use bigdecimal::BigDecimal;
2-
use candid::{Decode, Encode, Nat, Principal};
31
use clap::Args;
4-
use ic_agent::AgentError;
52
use icp::{agent, context::GetAgentError, identity, network};
6-
use icrc_ledger_types::icrc1::account::Account;
73

84
use icp::context::Context;
95

10-
use crate::commands::{args::TokenCommandArgs, token::TOKEN_LEDGER_CIDS};
6+
use crate::commands::args::TokenCommandArgs;
7+
use crate::operations::token::balance::{GetBalanceError, get_balance};
118

129
#[derive(Args, Clone, Debug)]
1310
pub(crate) struct BalanceArgs {
@@ -29,17 +26,11 @@ pub(crate) enum CommandError {
2926
#[error(transparent)]
3027
Agent(#[from] agent::CreateAgentError),
3128

32-
#[error("failed to get identity principal: {err}")]
33-
Principal { err: String },
34-
35-
#[error(transparent)]
36-
Query(#[from] AgentError),
37-
3829
#[error(transparent)]
39-
Candid(#[from] candid::Error),
30+
GetAgent(#[from] GetAgentError),
4031

4132
#[error(transparent)]
42-
GetAgent(#[from] GetAgentError),
33+
GetBalance(#[from] GetBalanceError),
4334
}
4435

4536
/// Check the token balance of a given identity
@@ -63,81 +54,14 @@ pub(crate) async fn exec(
6354
)
6455
.await?;
6556

66-
// Obtain ledger address
67-
let cid = match TOKEN_LEDGER_CIDS.get(token) {
68-
// Given token matched known token names
69-
Some(cid) => cid.to_string(),
70-
71-
// Given token is not known, indicating it's either already a canister id
72-
// or is simply a name of a token we do not know of
73-
None => token.to_string(),
74-
};
75-
76-
// Parse the canister id
77-
let cid = Principal::from_text(cid).map_err(|err| CommandError::Principal {
78-
err: err.to_string(),
79-
})?;
80-
81-
// Perform the required ledger calls
82-
let (balance, decimals, symbol) = tokio::join!(
83-
//
84-
// Obtain token balance
85-
async {
86-
// Convert identity to sender principal
87-
let owner = agent
88-
.get_principal()
89-
.map_err(|err| CommandError::Principal { err })?;
90-
91-
// Specify sub-account
92-
let subaccount = None;
93-
94-
// Perform query
95-
let resp = agent
96-
.query(&cid, "icrc1_balance_of")
97-
.with_arg(Encode!(&Account { owner, subaccount }).expect("failed to encode arg"))
98-
.await?;
99-
100-
// Decode response
101-
Ok::<_, CommandError>(Decode!(&resp, Nat)?)
102-
},
103-
//
104-
// Obtain the number of decimals the token uses
105-
async {
106-
// Perform query
107-
let resp = agent
108-
.query(&cid, "icrc1_decimals")
109-
.with_arg(Encode!(&()).expect("failed to encode arg"))
110-
.await?;
111-
112-
// Decode response
113-
Ok::<_, CommandError>(Decode!(&resp, u8)?)
114-
},
115-
//
116-
// Obtain the symbol of the token
117-
async {
118-
// Perform query
119-
let resp = agent
120-
.query(&cid, "icrc1_symbol")
121-
.with_arg(Encode!(&()).expect("failed to encode arg"))
122-
.await?;
123-
124-
// Decode response
125-
Ok::<_, CommandError>(Decode!(&resp, String)?)
126-
},
127-
);
128-
129-
// Check for errors
130-
let (Nat(balance), decimals, symbol) = (
131-
balance?, //
132-
decimals? as i64, //
133-
symbol?, //
134-
);
135-
136-
// Calculate amount
137-
let amount = BigDecimal::from_biguint(balance, decimals);
57+
// Get the balance from the ledger
58+
let balance_info = get_balance(&agent, token).await?;
13859

13960
// Output information
140-
let _ = ctx.term.write_line(&format!("Balance: {amount} {symbol}"));
61+
let _ = ctx.term.write_line(&format!(
62+
"Balance: {} {}",
63+
balance_info.amount, balance_info.symbol
64+
));
14165

14266
Ok(())
14367
}

0 commit comments

Comments
 (0)