1use std::str::FromStr;
2
3use crate::{format_uint_exp, tx::signing_provider};
4use alloy_eips::BlockId;
5use alloy_ens::NameOrAddress;
6use alloy_primitives::U256;
7use alloy_sol_types::sol;
8use clap::Parser;
9use foundry_cli::{
10 opts::RpcOpts,
11 utils::{LoadConfig, get_provider},
12};
13use foundry_wallets::WalletOpts;
14
15#[doc(hidden)]
16pub use foundry_config::utils::*;
17
18sol! {
19 #[sol(rpc)]
20 interface IERC20 {
21 #[derive(Debug)]
22 function name() external view returns (string);
23 function symbol() external view returns (string);
24 function decimals() external view returns (uint8);
25 function totalSupply() external view returns (uint256);
26 function balanceOf(address owner) external view returns (uint256);
27 function transfer(address to, uint256 amount) external returns (bool);
28 function approve(address spender, uint256 amount) external returns (bool);
29 function allowance(address owner, address spender) external view returns (uint256);
30 function mint(address to, uint256 amount) external;
31 function burn(uint256 amount) external;
32 }
33}
34
35#[derive(Debug, Parser, Clone)]
37pub enum Erc20Subcommand {
38 #[command(visible_alias = "b")]
40 Balance {
41 #[arg(value_parser = NameOrAddress::from_str)]
43 token: NameOrAddress,
44
45 #[arg(value_parser = NameOrAddress::from_str)]
47 owner: NameOrAddress,
48
49 #[arg(long, short = 'B')]
51 block: Option<BlockId>,
52
53 #[command(flatten)]
54 rpc: RpcOpts,
55 },
56
57 #[command(visible_alias = "t")]
59 Transfer {
60 #[arg(value_parser = NameOrAddress::from_str)]
62 token: NameOrAddress,
63
64 #[arg(value_parser = NameOrAddress::from_str)]
66 to: NameOrAddress,
67
68 amount: String,
70
71 #[command(flatten)]
72 rpc: RpcOpts,
73
74 #[command(flatten)]
75 wallet: WalletOpts,
76 },
77
78 #[command(visible_alias = "a")]
80 Approve {
81 #[arg(value_parser = NameOrAddress::from_str)]
83 token: NameOrAddress,
84
85 #[arg(value_parser = NameOrAddress::from_str)]
87 spender: NameOrAddress,
88
89 amount: String,
91
92 #[command(flatten)]
93 rpc: RpcOpts,
94
95 #[command(flatten)]
96 wallet: WalletOpts,
97 },
98
99 #[command(visible_alias = "al")]
101 Allowance {
102 #[arg(value_parser = NameOrAddress::from_str)]
104 token: NameOrAddress,
105
106 #[arg(value_parser = NameOrAddress::from_str)]
108 owner: NameOrAddress,
109
110 #[arg(value_parser = NameOrAddress::from_str)]
112 spender: NameOrAddress,
113
114 #[arg(long, short = 'B')]
116 block: Option<BlockId>,
117
118 #[command(flatten)]
119 rpc: RpcOpts,
120 },
121
122 #[command(visible_alias = "n")]
124 Name {
125 #[arg(value_parser = NameOrAddress::from_str)]
127 token: NameOrAddress,
128
129 #[arg(long, short = 'B')]
131 block: Option<BlockId>,
132
133 #[command(flatten)]
134 rpc: RpcOpts,
135 },
136
137 #[command(visible_alias = "s")]
139 Symbol {
140 #[arg(value_parser = NameOrAddress::from_str)]
142 token: NameOrAddress,
143
144 #[arg(long, short = 'B')]
146 block: Option<BlockId>,
147
148 #[command(flatten)]
149 rpc: RpcOpts,
150 },
151
152 #[command(visible_alias = "d")]
154 Decimals {
155 #[arg(value_parser = NameOrAddress::from_str)]
157 token: NameOrAddress,
158
159 #[arg(long, short = 'B')]
161 block: Option<BlockId>,
162
163 #[command(flatten)]
164 rpc: RpcOpts,
165 },
166
167 #[command(visible_alias = "ts")]
169 TotalSupply {
170 #[arg(value_parser = NameOrAddress::from_str)]
172 token: NameOrAddress,
173
174 #[arg(long, short = 'B')]
176 block: Option<BlockId>,
177
178 #[command(flatten)]
179 rpc: RpcOpts,
180 },
181
182 #[command(visible_alias = "m")]
184 Mint {
185 #[arg(value_parser = NameOrAddress::from_str)]
187 token: NameOrAddress,
188
189 #[arg(value_parser = NameOrAddress::from_str)]
191 to: NameOrAddress,
192
193 amount: String,
195
196 #[command(flatten)]
197 rpc: RpcOpts,
198
199 #[command(flatten)]
200 wallet: WalletOpts,
201 },
202
203 #[command(visible_alias = "bu")]
205 Burn {
206 #[arg(value_parser = NameOrAddress::from_str)]
208 token: NameOrAddress,
209
210 amount: String,
212
213 #[command(flatten)]
214 rpc: RpcOpts,
215
216 #[command(flatten)]
217 wallet: WalletOpts,
218 },
219}
220
221impl Erc20Subcommand {
222 fn rpc(&self) -> &RpcOpts {
223 match self {
224 Self::Allowance { rpc, .. } => rpc,
225 Self::Approve { rpc, .. } => rpc,
226 Self::Balance { rpc, .. } => rpc,
227 Self::Transfer { rpc, .. } => rpc,
228 Self::Name { rpc, .. } => rpc,
229 Self::Symbol { rpc, .. } => rpc,
230 Self::Decimals { rpc, .. } => rpc,
231 Self::TotalSupply { rpc, .. } => rpc,
232 Self::Mint { rpc, .. } => rpc,
233 Self::Burn { rpc, .. } => rpc,
234 }
235 }
236
237 pub async fn run(self) -> eyre::Result<()> {
238 let config = self.rpc().load_config()?;
239 let provider = get_provider(&config)?;
240
241 match self {
242 Self::Allowance { token, owner, spender, block, .. } => {
244 let token = token.resolve(&provider).await?;
245 let owner = owner.resolve(&provider).await?;
246 let spender = spender.resolve(&provider).await?;
247
248 let allowance = IERC20::new(token, &provider)
249 .allowance(owner, spender)
250 .block(block.unwrap_or_default())
251 .call()
252 .await?;
253
254 sh_println!("{}", format_uint_exp(allowance))?
255 }
256 Self::Balance { token, owner, block, .. } => {
257 let token = token.resolve(&provider).await?;
258 let owner = owner.resolve(&provider).await?;
259
260 let balance = IERC20::new(token, &provider)
261 .balanceOf(owner)
262 .block(block.unwrap_or_default())
263 .call()
264 .await?;
265 sh_println!("{}", format_uint_exp(balance))?
266 }
267 Self::Name { token, block, .. } => {
268 let token = token.resolve(&provider).await?;
269
270 let name = IERC20::new(token, &provider)
271 .name()
272 .block(block.unwrap_or_default())
273 .call()
274 .await?;
275 sh_println!("{}", name)?
276 }
277 Self::Symbol { token, block, .. } => {
278 let token = token.resolve(&provider).await?;
279
280 let symbol = IERC20::new(token, &provider)
281 .symbol()
282 .block(block.unwrap_or_default())
283 .call()
284 .await?;
285 sh_println!("{}", symbol)?
286 }
287 Self::Decimals { token, block, .. } => {
288 let token = token.resolve(&provider).await?;
289
290 let decimals = IERC20::new(token, &provider)
291 .decimals()
292 .block(block.unwrap_or_default())
293 .call()
294 .await?;
295 sh_println!("{}", decimals)?
296 }
297 Self::TotalSupply { token, block, .. } => {
298 let token = token.resolve(&provider).await?;
299
300 let total_supply = IERC20::new(token, &provider)
301 .totalSupply()
302 .block(block.unwrap_or_default())
303 .call()
304 .await?;
305 sh_println!("{}", format_uint_exp(total_supply))?
306 }
307 Self::Transfer { token, to, amount, wallet, .. } => {
309 let token = token.resolve(&provider).await?;
310 let to = to.resolve(&provider).await?;
311 let amount = U256::from_str(&amount)?;
312
313 let provider = signing_provider(wallet, &provider).await?;
314 let tx = IERC20::new(token, &provider).transfer(to, amount).send().await?;
315 sh_println!("{}", tx.tx_hash())?
316 }
317 Self::Approve { token, spender, amount, wallet, .. } => {
318 let token = token.resolve(&provider).await?;
319 let spender = spender.resolve(&provider).await?;
320 let amount = U256::from_str(&amount)?;
321
322 let provider = signing_provider(wallet, &provider).await?;
323 let tx = IERC20::new(token, &provider).approve(spender, amount).send().await?;
324 sh_println!("{}", tx.tx_hash())?
325 }
326 Self::Mint { token, to, amount, wallet, .. } => {
327 let token = token.resolve(&provider).await?;
328 let to = to.resolve(&provider).await?;
329 let amount = U256::from_str(&amount)?;
330
331 let provider = signing_provider(wallet, &provider).await?;
332 let tx = IERC20::new(token, &provider).mint(to, amount).send().await?;
333 sh_println!("{}", tx.tx_hash())?
334 }
335 Self::Burn { token, amount, wallet, .. } => {
336 let token = token.resolve(&provider).await?;
337 let amount = U256::from_str(&amount)?;
338
339 let provider = signing_provider(wallet, &provider).await?;
340 let tx = IERC20::new(token, &provider).burn(amount).send().await?;
341 sh_println!("{}", tx.tx_hash())?
342 }
343 };
344 Ok(())
345 }
346}