Skip to main content

cast/cmd/tip20/
mod.rs

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::utils::get_chain;
11use std::str::FromStr;
12use tempo_alloy::TempoNetwork;
13
14mod create;
15pub(crate) use create::iso4217_warning_message;
16pub(crate) mod logo;
17pub(crate) mod mine;
18
19const MINE_REGISTER_MESSAGES: mine::RegisterMessages = mine::RegisterMessages {
20    no_signer: "--register requires a signer or Tempo keychain identity (for example --private-key or --from)",
21    mismatch: "registration sender mismatch: mined salt is for",
22    submitting: " on Tempo...",
23};
24
25/// TIP-20 token operations (Tempo).
26#[derive(Debug, Parser, Clone)]
27pub enum Tip20Subcommand {
28    /// Create a new TIP-20 token via the TIP20Factory.
29    #[command(visible_alias = "c")]
30    Create {
31        /// The token name (e.g. "US Dollar Coin").
32        name: String,
33
34        /// The token symbol (e.g. "USDC").
35        symbol: String,
36
37        /// The ISO 4217 currency code (e.g. "USD", "EUR", "GBP").
38        /// This field is IMMUTABLE after creation and affects fee payment
39        /// eligibility, DEX routing, and quote token pairing.
40        currency: String,
41
42        /// The TIP-20 quote token address used for exchange pricing.
43        #[arg(value_parser = NameOrAddress::from_str)]
44        quote_token: NameOrAddress,
45
46        /// The admin address to receive DEFAULT_ADMIN_ROLE on the new token.
47        #[arg(value_parser = NameOrAddress::from_str)]
48        admin: NameOrAddress,
49
50        /// A unique salt for deterministic address derivation (hex-encoded bytes32).
51        salt: B256,
52
53        /// Optional T5 logo URI for the token.
54        #[arg(long, value_name = "URI")]
55        logo_uri: Option<String>,
56
57        /// Skip the ISO 4217 currency code validation warning.
58        #[arg(long)]
59        force: bool,
60
61        #[command(flatten)]
62        send_tx: SendTxOpts,
63
64        #[command(flatten)]
65        tx: TxParams,
66    },
67
68    /// Validate a TIP-20 logo URI offline against Tempo T5 constraints.
69    LogoCheck {
70        /// The logo URI to validate. Empty string is valid.
71        #[arg(value_name = "URI")]
72        logo_uri: String,
73    },
74
75    /// Update a TIP-20 token logo URI.
76    LogoSet {
77        /// The TIP-20 token contract address.
78        #[arg(value_parser = NameOrAddress::from_str)]
79        token: NameOrAddress,
80
81        /// The new logo URI. Empty string clears the on-chain value.
82        #[arg(value_name = "URI")]
83        logo_uri: String,
84
85        #[command(flatten)]
86        send_tx: SendTxOpts,
87
88        #[command(flatten)]
89        tx: TxParams,
90    },
91
92    /// Mine a TIP-1022 salt for virtual address' master registration on Tempo.
93    #[command(visible_alias = "m")]
94    Mine {
95        /// Address that will call `registerVirtualMaster(bytes32)`.
96        #[arg(value_name = "ADDRESS")]
97        master: Address,
98
99        /// Salt to validate directly instead of mining one.
100        #[arg(long, conflicts_with_all = ["seed", "no_random"], value_name = "HEX")]
101        salt: Option<B256>,
102
103        /// Number of threads to use. Specifying 0 defaults to the number of logical cores.
104        #[arg(global = true, long, short = 'j', visible_alias = "jobs")]
105        threads: Option<usize>,
106
107        /// The random number generator's seed, used to initialize the salt search.
108        #[arg(long, value_name = "HEX")]
109        seed: Option<B256>,
110
111        /// Don't initialize the salt with a random value, and instead use the default value of 0.
112        #[arg(long, conflicts_with = "seed")]
113        no_random: bool,
114
115        /// Submit `registerVirtualMaster(bytes32)` on Tempo after finding or validating the salt.
116        #[arg(long, conflicts_with_all = ["seed", "no_random"])]
117        register: bool,
118
119        #[command(flatten)]
120        send_tx: SendTxOpts,
121
122        #[command(flatten)]
123        tx: TxParams,
124    },
125}
126
127impl Tip20Subcommand {
128    pub async fn run(self) -> Result<()> {
129        match self {
130            Self::Create {
131                name,
132                symbol,
133                currency,
134                quote_token,
135                admin,
136                salt,
137                logo_uri,
138                force,
139                send_tx,
140                tx,
141            } => {
142                create::run(
143                    name,
144                    symbol,
145                    currency,
146                    quote_token,
147                    admin,
148                    salt,
149                    logo_uri,
150                    force,
151                    send_tx,
152                    tx,
153                )
154                .await
155            }
156            Self::LogoCheck { logo_uri } => logo::check(&logo_uri),
157            Self::LogoSet { token, logo_uri, send_tx, tx } => {
158                logo::set(token, logo_uri, send_tx, tx).await
159            }
160            Self::Mine { master, salt, threads, seed, no_random, register, send_tx, tx } => {
161                let output = mine::run(master, salt, threads, seed, no_random)?;
162                if register {
163                    let msgs = &MINE_REGISTER_MESSAGES;
164                    mine::register_virtual_master(master, output.salt, send_tx, tx, false, msgs)
165                        .await?;
166                }
167                Ok(())
168            }
169        }
170    }
171}
172
173/// Sends pre-encoded `data` to the Tempo contract at `to` through the `cast send` flow, signing
174/// with the selected Tempo session, access key, or wallet.
175pub(crate) async fn send_tip20_transaction(
176    to: Address,
177    data: Vec<u8>,
178    send_tx: SendTxOpts,
179    tx: TxParams,
180) -> Result<()> {
181    tempo::ensure_session_not_browser(&tx.tempo, send_tx.browser.browser)?;
182    let (config, provider) = tempo::tempo_provider(&send_tx.eth.rpc)?;
183    let chain = get_chain(config.chain, &provider).await?;
184    let (signer, access_key) =
185        tempo::resolve_session_or_wallet_signer(&tx.tempo, &send_tx.eth.wallet, chain.id()).await?;
186    SendTxArgs::contract_call(NameOrAddress::Address(to), data, send_tx, tx)
187        .run_generic::<TempoNetwork>(signer, access_key)
188        .await
189}