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
19pub(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(¤cy) && !force {
60 sh_warn!("{}", iso4217_warning_message(¤cy))?;
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
90async 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}