Skip to main content

cast/cmd/vaddr/
create.rs

1use crate::{
2    cmd::{
3        erc20::build_provider_with_signer,
4        send::{cast_send, cast_send_with_access_key},
5        tip20::mine,
6    },
7    tempo,
8    tx::{SendTxOpts, TxParams},
9};
10use alloy_primitives::{Address, B256};
11use alloy_signer::Signer;
12use eyre::Result;
13use foundry_cli::utils::{LoadConfig, get_chain};
14use foundry_common::{provider::ProviderBuilder, shell};
15use rand::{RngCore, SeedableRng, rngs::StdRng};
16use serde_json::json;
17use std::time::Instant;
18use tempo_alloy::{
19    TempoNetwork,
20    contracts::precompiles::{ADDRESS_REGISTRY_ADDRESS, IAddressRegistry},
21};
22use tempo_primitives::{TempoAddressExt, UserTag};
23
24const POW_BYTES: usize = 4;
25
26#[allow(clippy::too_many_arguments)]
27pub(super) async fn run(
28    owner: Address,
29    salt: Option<B256>,
30    tag: u64,
31    count: u32,
32    threads: Option<usize>,
33    seed: Option<B256>,
34    no_random: bool,
35    no_register: bool,
36    send_tx: SendTxOpts,
37    tx_opts: TxParams,
38) -> Result<()> {
39    if count == 0 {
40        // no virtual addresses to compute
41        return Ok(());
42    }
43
44    if !owner.is_valid_master() {
45        eyre::bail!(
46            "invalid owner address {owner}; see https://docs.tempo.xyz/protocol/tips/tip-1022"
47        );
48    }
49
50    let output = if let Some(salt) = salt {
51        let output = mine::derive(owner, salt);
52        if !mine::has_pow(&output.registration_hash, POW_BYTES) {
53            eyre::bail!(
54                "provided salt does not satisfy TIP-1022 proof of work: {}",
55                output.registration_hash
56            );
57        }
58        output
59    } else {
60        let mut n_threads = threads.unwrap_or(0);
61        if n_threads == 0 {
62            n_threads = std::thread::available_parallelism().map_or(1, |n| n.get());
63        }
64
65        let mut start_salt = B256::ZERO;
66        if !no_random {
67            let mut rng = match seed {
68                Some(seed) => StdRng::from_seed(seed.0),
69                None => StdRng::from_os_rng(),
70            };
71            rng.fill_bytes(&mut start_salt[..]);
72        }
73
74        if !shell::is_json() {
75            sh_println!("Mining TIP-1022 salt for {owner} with {n_threads} threads...")?;
76        }
77        let timer = Instant::now();
78        let output = mine::mine(owner, start_salt, n_threads, POW_BYTES)?;
79        if !shell::is_json() {
80            sh_println!("Found salt in {:?}", timer.elapsed())?;
81        }
82        output
83    };
84
85    const MAX_USER_TAG: u64 = 0x0000_FFFF_FFFF_FFFF;
86    let mut virtual_addresses = Vec::with_capacity(count as usize);
87    for i in 0..count {
88        let tag_value = tag
89            .checked_add(i as u64)
90            .filter(|&t| t <= MAX_USER_TAG)
91            .ok_or_else(|| eyre::eyre!("tag overflow: tag + count exceeds the 6-byte user tag range (max {MAX_USER_TAG:#x})"))?;
92        let raw = tag_value.to_be_bytes();
93        let user_tag = UserTag::new(raw[2..].try_into().expect("slice is 6 bytes"));
94        let vaddr = Address::new_virtual(output.master_id, user_tag);
95        virtual_addresses.push((user_tag, vaddr));
96    }
97
98    if shell::is_json() {
99        sh_println!(
100            "{}",
101            serde_json::to_string_pretty(&json!({
102                "salt": format!("{}", output.salt),
103                "registration_hash": format!("{}", output.registration_hash),
104                "master_id": format!("{}", output.master_id),
105                "virtual_addresses": virtual_addresses.iter().map(|(tag, addr)| json!({
106                    "tag": format!("{tag}"),
107                    "address": format!("{addr}"),
108                })).collect::<Vec<_>>(),
109            }))?
110        )?;
111    } else {
112        sh_println!(
113            "Salt:              {}
114Registration hash: {}
115Master ID:         {}",
116            output.salt,
117            output.registration_hash,
118            output.master_id,
119        )?;
120        sh_println!("\nVirtual addresses:")?;
121        for (tag, vaddr) in &virtual_addresses {
122            sh_println!("  tag={tag}  {vaddr}")?;
123        }
124    }
125
126    if no_register {
127        return Ok(());
128    }
129
130    register(owner, output.salt, send_tx, tx_opts).await
131}
132
133async fn register(
134    owner: Address,
135    salt: B256,
136    send_tx: SendTxOpts,
137    mut tx_opts: TxParams,
138) -> Result<()> {
139    let (signer, tempo_access_key) = send_tx.eth.wallet.maybe_signer().await?;
140    let signer = signer.ok_or_else(|| {
141        eyre::eyre!("cast vaddr create requires a signer (for example --private-key or --from)")
142    })?;
143
144    let sender =
145        tempo_access_key.as_ref().map(|ak| ak.wallet_address).unwrap_or_else(|| signer.address());
146
147    if sender != owner {
148        eyre::bail!(
149            "signer mismatch: salt is for {owner}, but the configured signer would register as {sender}"
150        );
151    }
152
153    let config = send_tx.eth.load_config()?;
154    let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout);
155    let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
156
157    let mut tx = IAddressRegistry::new(ADDRESS_REGISTRY_ADDRESS, &provider)
158        .registerVirtualMaster(salt)
159        .into_transaction_request();
160    let expires_at = tx_opts.tempo.resolve_expires();
161    tempo::print_expires(expires_at)?;
162    tx_opts.apply::<TempoNetwork>(&mut tx, get_chain(config.chain, &provider).await?.is_legacy());
163
164    sh_println!("Submitting registerVirtualMaster({salt})...")?;
165
166    if let Some(ref access_key) = tempo_access_key {
167        cast_send_with_access_key(
168            &provider,
169            tx,
170            &signer,
171            access_key,
172            send_tx.cast_async,
173            send_tx.confirmations,
174            timeout,
175        )
176        .await?;
177    } else {
178        let provider = build_provider_with_signer::<TempoNetwork>(&send_tx, signer)?;
179        cast_send(provider, tx, send_tx.cast_async, send_tx.sync, send_tx.confirmations, timeout)
180            .await?;
181    }
182
183    Ok(())
184}