Skip to main content

cast/cmd/tip20/
create.rs

1use crate::{
2    cmd::confirm_continue,
3    tempo::tempo_provider,
4    tx::{SendTxOpts, TxParams},
5};
6use alloy_ens::NameOrAddress;
7use alloy_network::{Network, TransactionBuilder};
8use alloy_primitives::{B256, Bytes};
9use alloy_provider::Provider;
10use alloy_rpc_types::TransactionInputKind;
11use alloy_sol_types::{SolCall, SolError};
12use eyre::Result;
13use tempo_alloy::TempoNetwork;
14use tempo_contracts::precompiles::{
15    TIP20_FACTORY_ADDRESS, UnknownFunctionSelector, createTokenCall, createTokenWithLogoCall,
16    is_iso4217_currency,
17};
18
19/// Returns a warning message for non-ISO 4217 currency codes used in TIP-20 token creation.
20pub(crate) fn iso4217_warning_message(currency: &str) -> String {
21    let hyperlink = |url: &str| format!("\x1b]8;;{url}\x1b\\{url}\x1b]8;;\x1b\\");
22    let tip20_docs = hyperlink("https://docs.tempo.xyz/protocol/tip20/overview");
23    let iso_docs = hyperlink("https://www.iso.org/iso-4217-currency-codes.html");
24
25    format!(
26        "\"{currency}\" is not a recognized ISO 4217 currency code.\n\
27         \n\
28         If the token you are trying to deploy is a fiat-backed stablecoin, Tempo strongly\n\
29         recommends that the currency code field be the ISO-4217 currency code of the fiat\n\
30         currency your token tracks (e.g. \"USD\", \"EUR\", \"GBP\").\n\
31         \n\
32         The currency field is IMMUTABLE after token creation and affects fee payment\n\
33         eligibility, DEX routing, and quote token pairing. Only \"USD\"-denominated tokens\n\
34         can be used to pay transaction fees on Tempo.\n\
35         \n\
36         Learn more:\n  \
37         - Tempo TIP-20 docs: {tip20_docs}\n  \
38         - ISO 4217 standard: {iso_docs}"
39    )
40}
41
42#[allow(clippy::too_many_arguments)]
43pub(super) async fn run(
44    name: String,
45    symbol: String,
46    currency: String,
47    quote_token: NameOrAddress,
48    admin: NameOrAddress,
49    salt: B256,
50    logo_uri: Option<String>,
51    force: bool,
52    send_tx: SendTxOpts,
53    tx_opts: TxParams,
54) -> Result<()> {
55    if let Some(logo_uri) = logo_uri.as_deref() {
56        super::logo::validate_logo_uri(logo_uri)?;
57    }
58
59    if !is_iso4217_currency(&currency) && !force {
60        sh_warn!("{}", iso4217_warning_message(&currency))?;
61        if !confirm_continue()? {
62            return Ok(());
63        }
64    }
65
66    let (_, provider) = tempo_provider(&send_tx.eth.rpc)?;
67    let quote_token = quote_token.resolve(&provider).await?;
68    let admin = admin.resolve(&provider).await?;
69
70    let data = match logo_uri {
71        Some(logo_uri) => {
72            let call = createTokenWithLogoCall {
73                name,
74                symbol,
75                currency,
76                quoteToken: quote_token,
77                admin,
78                salt,
79                logoURI: logo_uri,
80            };
81            ensure_t5_create_logo_supported(&provider, &call).await?;
82            call.abi_encode()
83        }
84        None => createTokenCall { name, symbol, currency, quoteToken: quote_token, admin, salt }
85            .abi_encode(),
86    };
87    super::send_tip20_transaction(TIP20_FACTORY_ADDRESS, data, send_tx, tx_opts).await
88}
89
90/// Fails early when the factory rejects the 7-arg `createToken` selector, which only T5+
91/// factories implement.
92async fn ensure_t5_create_logo_supported<P: Provider<TempoNetwork>>(
93    provider: &P,
94    call: &createTokenWithLogoCall,
95) -> Result<()> {
96    let mut tx = <TempoNetwork as Network>::TransactionRequest::default();
97    tx.set_kind(TIP20_FACTORY_ADDRESS.into());
98    tx.set_input_kind(call.abi_encode(), TransactionInputKind::Both);
99
100    let unknown_selector =
101        UnknownFunctionSelector { selector: createTokenWithLogoCall::SELECTOR.into() }.abi_encode();
102    if let Err(err) = provider.call(tx).await
103        && let Some(data) = err.as_error_resp().and_then(|resp| resp.data.as_ref())
104        && serde_json::from_str::<Bytes>(data.get()).is_ok_and(|data| data == unknown_selector)
105    {
106        eyre::bail!(
107            "--logo-uri requires a T5-compatible TIP20Factory; the configured RPC rejected the 7-arg createToken selector 0x5323d222"
108        );
109    }
110    Ok(())
111}