1use crate::{
2 cmd::{
3 erc20::build_provider_with_signer,
4 send::{cast_send, cast_send_with_access_key},
5 },
6 tempo,
7 tx::{SendTxOpts, TxParams},
8};
9use alloy_ens::NameOrAddress;
10use alloy_primitives::B256;
11use alloy_sol_types::sol;
12use foundry_cli::utils::{LoadConfig, get_chain};
13use foundry_common::provider::ProviderBuilder;
14use tempo_alloy::TempoNetwork;
15use tempo_contracts::precompiles::{TIP20_FACTORY_ADDRESS, is_iso4217_currency};
16
17sol! {
18 #[sol(rpc)]
19 interface ITIP20Factory {
20 function createToken(
21 string memory name,
22 string memory symbol,
23 string memory currency,
24 address quoteToken,
25 address admin,
26 bytes32 salt
27 ) external returns (address token);
28 }
29}
30
31pub(crate) fn iso4217_warning_message(currency: &str) -> String {
33 let hyperlink = |url: &str| format!("\x1b]8;;{url}\x1b\\{url}\x1b]8;;\x1b\\");
34 let tip20_docs = hyperlink("https://docs.tempo.xyz/protocol/tip20/overview");
35 let iso_docs = hyperlink("https://www.iso.org/iso-4217-currency-codes.html");
36
37 format!(
38 "\"{currency}\" is not a recognized ISO 4217 currency code.\n\
39 \n\
40 If the token you are trying to deploy is a fiat-backed stablecoin, Tempo strongly\n\
41 recommends that the currency code field be the ISO-4217 currency code of the fiat\n\
42 currency your token tracks (e.g. \"USD\", \"EUR\", \"GBP\").\n\
43 \n\
44 The currency field is IMMUTABLE after token creation and affects fee payment\n\
45 eligibility, DEX routing, and quote token pairing. Only \"USD\"-denominated tokens\n\
46 can be used to pay transaction fees on Tempo.\n\
47 \n\
48 Learn more:\n \
49 - Tempo TIP-20 docs: {tip20_docs}\n \
50 - ISO 4217 standard: {iso_docs}"
51 )
52}
53
54#[allow(clippy::too_many_arguments)]
55pub(super) async fn run(
56 name: String,
57 symbol: String,
58 currency: String,
59 quote_token: NameOrAddress,
60 admin: NameOrAddress,
61 salt: B256,
62 force: bool,
63 send_tx: SendTxOpts,
64 mut tx_opts: TxParams,
65) -> eyre::Result<()> {
66 let (signer, tempo_access_key) = if send_tx.eth.wallet.from.is_some() {
67 send_tx.eth.wallet.maybe_signer().await?
68 } else {
69 (None, None)
70 };
71
72 let config = send_tx.eth.rpc.load_config()?;
73
74 if !is_iso4217_currency(¤cy) && !force {
75 sh_warn!("{}", super::iso4217_warning_message(¤cy))?;
76 let response: String = foundry_common::prompt!("\nContinue anyway? [y/N] ")?;
77 if !matches!(response.trim(), "y" | "Y") {
78 sh_println!("Aborted.")?;
79 return Ok(());
80 }
81 }
82
83 let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout);
84 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
85 let quote_token_addr = quote_token.resolve(&provider).await?;
86 let admin_addr = admin.resolve(&provider).await?;
87
88 let mut tx = ITIP20Factory::new(TIP20_FACTORY_ADDRESS, &provider)
89 .createToken(name, symbol, currency, quote_token_addr, admin_addr, salt)
90 .into_transaction_request();
91
92 let expires_at = tx_opts.tempo.resolve_expires();
93 tempo::print_expires(expires_at)?;
94 tx_opts.apply::<TempoNetwork>(&mut tx, get_chain(config.chain, &provider).await?.is_legacy());
95
96 if let Some(ref access_key) = tempo_access_key {
97 let signer = signer.as_ref().ok_or_else(|| eyre::eyre!("access key requires a signer"))?;
98 cast_send_with_access_key(
99 &provider,
100 tx,
101 signer,
102 access_key,
103 send_tx.cast_async,
104 send_tx.confirmations,
105 timeout,
106 )
107 .await?;
108 } else {
109 let signer = signer.unwrap_or(send_tx.eth.wallet.signer().await?);
110 let provider = build_provider_with_signer::<TempoNetwork>(&send_tx, signer)?;
111 cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout)
112 .await?;
113 }
114
115 Ok(())
116}