1use crate::{
2 cmd::send::SendTxArgs,
3 tempo,
4 tx::{SendTxOpts, TxParams},
5};
6use alloy_ens::NameOrAddress;
7use alloy_primitives::{Address, B256};
8use clap::Parser;
9use eyre::Result;
10use foundry_cli::{opts::RpcOpts, utils::get_chain};
11use foundry_wallets::{TempoAccountsWallet, WalletSigner};
12use std::str::FromStr;
13use tempo_alloy::TempoNetwork;
14
15mod create;
16pub(crate) use create::iso4217_warning_message;
17pub(crate) mod logo;
18pub(crate) mod mine;
19mod role;
20pub use role::Tip20Role;
21
22const MINE_REGISTER_MESSAGES: mine::RegisterMessages = mine::RegisterMessages {
23 no_signer: "--register requires a signer or Tempo keychain identity (for example --private-key or --from)",
24 mismatch: "registration sender mismatch: mined salt is for",
25 submitting: " on Tempo...",
26};
27
28#[derive(Debug, Parser, Clone)]
30pub enum Tip20Subcommand {
31 #[command(visible_alias = "c")]
33 Create {
34 name: String,
36
37 symbol: String,
39
40 currency: String,
44
45 #[arg(value_parser = NameOrAddress::from_str)]
47 quote_token: NameOrAddress,
48
49 #[arg(value_parser = NameOrAddress::from_str)]
51 admin: NameOrAddress,
52
53 salt: B256,
55
56 #[arg(long, value_name = "URI")]
58 logo_uri: Option<String>,
59
60 #[arg(long)]
62 force: bool,
63
64 #[command(flatten)]
65 send_tx: SendTxOpts,
66
67 #[command(flatten)]
68 tx: TxParams,
69 },
70
71 LogoCheck {
73 #[arg(value_name = "URI")]
75 logo_uri: String,
76 },
77
78 LogoSet {
80 #[arg(value_parser = NameOrAddress::from_str)]
82 token: NameOrAddress,
83
84 #[arg(value_name = "URI")]
86 logo_uri: String,
87
88 #[command(flatten)]
89 send_tx: SendTxOpts,
90
91 #[command(flatten)]
92 tx: TxParams,
93 },
94
95 #[command(visible_alias = "m")]
97 Mine {
98 #[arg(value_name = "ADDRESS")]
100 master: Address,
101
102 #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")]
104 salt: Option<B256>,
105
106 #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
108 threads: Option<usize>,
109
110 #[arg(long, value_name = "HEX")]
112 seed: Option<B256>,
113
114 #[arg(long, conflicts_with = "seed")]
116 no_random: bool,
117
118 #[arg(long, conflicts_with_all = ["seed", "no_random"])]
120 register: bool,
121
122 #[command(flatten)]
123 send_tx: SendTxOpts,
124
125 #[command(flatten)]
126 tx: TxParams,
127 },
128
129 GrantRole {
135 #[arg(value_parser = NameOrAddress::from_str)]
137 token: NameOrAddress,
138
139 role: Tip20Role,
142
143 #[arg(value_parser = NameOrAddress::from_str)]
145 account: NameOrAddress,
146
147 #[command(flatten)]
148 send_tx: SendTxOpts,
149
150 #[command(flatten)]
151 tx: TxParams,
152 },
153
154 RevokeRole {
159 #[arg(value_parser = NameOrAddress::from_str)]
161 token: NameOrAddress,
162
163 role: Tip20Role,
166
167 #[arg(value_parser = NameOrAddress::from_str)]
169 account: NameOrAddress,
170
171 #[command(flatten)]
172 send_tx: SendTxOpts,
173
174 #[command(flatten)]
175 tx: TxParams,
176 },
177
178 HasRole {
180 #[arg(value_parser = NameOrAddress::from_str)]
182 token: NameOrAddress,
183
184 role: Tip20Role,
187
188 #[arg(value_parser = NameOrAddress::from_str)]
190 account: NameOrAddress,
191
192 #[command(flatten)]
193 rpc: RpcOpts,
194 },
195}
196
197impl Tip20Subcommand {
198 pub async fn run(self) -> Result<()> {
199 match self {
200 Self::Create {
201 name,
202 symbol,
203 currency,
204 quote_token,
205 admin,
206 salt,
207 logo_uri,
208 force,
209 send_tx,
210 tx,
211 } => {
212 create::run(
213 name,
214 symbol,
215 currency,
216 quote_token,
217 admin,
218 salt,
219 logo_uri,
220 force,
221 send_tx,
222 tx,
223 )
224 .await
225 }
226 Self::LogoCheck { logo_uri } => logo::check(&logo_uri),
227 Self::LogoSet { token, logo_uri, send_tx, tx } => {
228 logo::set(token, logo_uri, send_tx, tx).await
229 }
230 Self::Mine { master, salt, threads, seed, no_random, register, send_tx, tx } => {
231 let output = mine::run(master, salt, threads, seed, no_random)?;
232 if register {
233 let msgs = &MINE_REGISTER_MESSAGES;
234 mine::register_virtual_master(master, output.salt, send_tx, tx, false, msgs)
235 .await?;
236 }
237 Ok(())
238 }
239 Self::GrantRole { token, role, account, send_tx, tx } => {
240 role::update(role::RoleUpdate::Grant, token, role, account, send_tx, tx).await
241 }
242 Self::RevokeRole { token, role, account, send_tx, tx } => {
243 role::update(role::RoleUpdate::Revoke, token, role, account, send_tx, tx).await
244 }
245 Self::HasRole { token, role, account, rpc } => {
246 role::has_role(token, role, account, rpc).await
247 }
248 }
249 }
250}
251
252pub(crate) async fn send_tip20_transaction(
255 to: Address,
256 data: Vec<u8>,
257 send_tx: SendTxOpts,
258 tx: TxParams,
259) -> Result<()> {
260 let (signer, access_key) = resolve_tip20_signer(&send_tx, &tx).await?;
261 SendTxArgs::contract_call(NameOrAddress::Address(to), data, send_tx, tx)
262 .run_generic::<TempoNetwork>(signer, access_key)
263 .await
264}
265
266pub(crate) async fn resolve_tip20_signer(
268 send_tx: &SendTxOpts,
269 tx: &TxParams,
270) -> Result<(Option<WalletSigner>, Option<TempoAccountsWallet>)> {
271 tempo::ensure_session_not_browser(&tx.tempo, send_tx.browser.browser)?;
272 let (config, provider) = tempo::tempo_provider(&send_tx.eth.rpc)?;
273 let chain = get_chain(config.chain, &provider).await?;
274 tempo::resolve_session_or_wallet_signer(&tx.tempo, &send_tx.eth.wallet, chain.id()).await
275}