1use alloy_primitives::{Address, hex};
2use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address};
3use alloy_signer_local::PrivateKeySigner;
4use clap::Parser;
5use eyre::Result;
6use foundry_cli::json::print_json_success;
7use foundry_common::{sh_println, shell};
8use itertools::Either;
9use rayon::iter::{self, ParallelIterator};
10use regex::Regex;
11use serde::{Deserialize, Serialize};
12use serde_json::json;
13use std::{
14 fs,
15 path::{Path, PathBuf},
16 time::Instant,
17};
18
19pub type GeneratedWallet = (SigningKey, Address);
21
22#[derive(Clone, Debug, Parser)]
24pub struct VanityArgs {
25 #[arg(long, value_name = "PATTERN", required_unless_present = "ends_with")]
27 pub starts_with: Option<String>,
28
29 #[arg(long, value_name = "PATTERN")]
31 pub ends_with: Option<String>,
32
33 #[arg(long)]
37 pub nonce: Option<u64>,
38
39 #[arg(
44 long,
45 value_hint = clap::ValueHint::FilePath,
46 value_name = "PATH",
47 )]
48 pub save_path: Option<PathBuf>,
49}
50
51#[derive(Serialize, Deserialize)]
53struct WalletData {
54 address: String,
55 private_key: String,
56}
57
58#[derive(Default, Serialize, Deserialize)]
60struct Wallets {
61 wallets: Vec<WalletData>,
62}
63
64impl WalletData {
65 pub fn new(wallet: &PrivateKeySigner) -> Self {
66 Self {
67 address: wallet.address().to_checksum(None),
68 private_key: format!("0x{}", hex::encode(wallet.credential().to_bytes())),
69 }
70 }
71}
72
73impl VanityArgs {
74 pub fn run(self) -> Result<PrivateKeySigner> {
75 let Self { starts_with, ends_with, nonce, save_path } = self;
76
77 let mut left_exact_hex = None;
78 let mut left_regex = None;
79 if let Some(prefix) = starts_with {
80 match parse_pattern(&prefix, true)? {
81 Either::Left(left) => left_exact_hex = Some(left),
82 Either::Right(re) => left_regex = Some(re),
83 }
84 }
85
86 let mut right_exact_hex = None;
87 let mut right_regex = None;
88 if let Some(suffix) = ends_with {
89 match parse_pattern(&suffix, false)? {
90 Either::Left(right) => right_exact_hex = Some(right),
91 Either::Right(re) => right_regex = Some(re),
92 }
93 }
94
95 macro_rules! find_vanity {
96 ($m:ident, $nonce:ident) => {
97 if let Some(nonce) = $nonce {
98 find_vanity_address_with_nonce($m, nonce)
99 } else {
100 find_vanity_address($m)
101 }
102 };
103 }
104
105 sh_status!("Starting to generate vanity address...")?;
106 let timer = Instant::now();
107
108 let wallet = match (left_exact_hex, left_regex, right_exact_hex, right_regex) {
109 (Some(left), _, Some(right), _) => {
110 let matcher = HexMatcher { left, right };
111 find_vanity!(matcher, nonce)
112 }
113 (Some(left), _, _, Some(right)) => {
114 let matcher = LeftExactRightRegexMatcher { left, right };
115 find_vanity!(matcher, nonce)
116 }
117 (_, Some(left), _, Some(right)) => {
118 let matcher = RegexMatcher { left, right };
119 find_vanity!(matcher, nonce)
120 }
121 (_, Some(left), Some(right), _) => {
122 let matcher = LeftRegexRightExactMatcher { left, right };
123 find_vanity!(matcher, nonce)
124 }
125 (Some(left), None, None, None) => {
126 let matcher = LeftHexMatcher { left };
127 find_vanity!(matcher, nonce)
128 }
129 (None, None, Some(right), None) => {
130 let matcher = RightHexMatcher { right };
131 find_vanity!(matcher, nonce)
132 }
133 (None, Some(re), None, None) => {
134 let matcher = SingleRegexMatcher { re };
135 find_vanity!(matcher, nonce)
136 }
137 (None, None, None, Some(re)) => {
138 let matcher = SingleRegexMatcher { re };
139 find_vanity!(matcher, nonce)
140 }
141 _ => unreachable!(),
142 }
143 .expect("failed to generate vanity wallet");
144
145 if let Some(save_path) = save_path {
147 save_wallet_to_file(&wallet, &save_path)?;
148 }
149
150 let contract_address = nonce.map(|nonce| wallet.address().create(nonce).to_checksum(None));
151 let address = wallet.address().to_checksum(None);
152 let private_key = format!("0x{}", hex::encode(wallet.credential().to_bytes()));
153
154 if shell::is_json() {
155 print_json_success(json!({
156 "address": address,
157 "private_key": private_key,
158 "contract_address": contract_address,
159 }))?;
160 } else {
161 sh_println!(
162 "Successfully found vanity address in {:.3} seconds.{}{}\nAddress: {}\nPrivate Key: {}",
163 timer.elapsed().as_secs_f64(),
164 if contract_address.is_some() { "\nContract address: " } else { "" },
165 contract_address.unwrap_or_default(),
166 address,
167 private_key,
168 )?;
169 }
170
171 Ok(wallet)
172 }
173}
174
175fn save_wallet_to_file(wallet: &PrivateKeySigner, path: &Path) -> Result<()> {
179 let mut wallets = if path.exists() {
180 let data = fs::read_to_string(path)?;
181 serde_json::from_str::<Wallets>(&data).unwrap_or_default()
182 } else {
183 Wallets::default()
184 };
185
186 wallets.wallets.push(WalletData::new(wallet));
187
188 fs::write(path, serde_json::to_string_pretty(&wallets)?)?;
189 Ok(())
190}
191
192pub fn find_vanity_address<T: VanityMatcher>(matcher: T) -> Option<PrivateKeySigner> {
194 wallet_generator().find_any(create_matcher(matcher)).map(|(key, _)| key.into())
195}
196
197pub fn find_vanity_address_with_nonce<T: VanityMatcher>(
200 matcher: T,
201 nonce: u64,
202) -> Option<PrivateKeySigner> {
203 wallet_generator().find_any(create_nonce_matcher(matcher, nonce)).map(|(key, _)| key.into())
204}
205
206#[inline]
209pub fn create_matcher<T: VanityMatcher>(matcher: T) -> impl Fn(&GeneratedWallet) -> bool {
210 move |(_, addr)| matcher.is_match(addr)
211}
212
213#[inline]
217pub fn create_nonce_matcher<T: VanityMatcher>(
218 matcher: T,
219 nonce: u64,
220) -> impl Fn(&GeneratedWallet) -> bool {
221 move |(_, addr)| {
222 let contract_addr = addr.create(nonce);
223 matcher.is_match(&contract_addr)
224 }
225}
226
227#[inline]
229pub fn wallet_generator() -> iter::Map<iter::Repeat<()>, impl Fn(()) -> GeneratedWallet> {
230 iter::repeat(()).map(|()| generate_wallet())
231}
232
233pub fn generate_wallet() -> GeneratedWallet {
235 let key = SigningKey::random(&mut rand_08::thread_rng());
236 let address = secret_key_to_address(&key);
237 (key, address)
238}
239
240pub trait VanityMatcher: Send + Sync {
242 fn is_match(&self, addr: &Address) -> bool;
243}
244
245pub struct HexMatcher {
247 pub left: Vec<u8>,
248 pub right: Vec<u8>,
249}
250
251impl VanityMatcher for HexMatcher {
252 #[inline]
253 fn is_match(&self, addr: &Address) -> bool {
254 let bytes = addr.as_slice();
255 bytes.starts_with(&self.left) && bytes.ends_with(&self.right)
256 }
257}
258
259pub struct LeftHexMatcher {
261 pub left: Vec<u8>,
262}
263
264impl VanityMatcher for LeftHexMatcher {
265 #[inline]
266 fn is_match(&self, addr: &Address) -> bool {
267 let bytes = addr.as_slice();
268 bytes.starts_with(&self.left)
269 }
270}
271
272pub struct RightHexMatcher {
274 pub right: Vec<u8>,
275}
276
277impl VanityMatcher for RightHexMatcher {
278 #[inline]
279 fn is_match(&self, addr: &Address) -> bool {
280 let bytes = addr.as_slice();
281 bytes.ends_with(&self.right)
282 }
283}
284
285pub struct LeftExactRightRegexMatcher {
287 pub left: Vec<u8>,
288 pub right: Regex,
289}
290
291impl VanityMatcher for LeftExactRightRegexMatcher {
292 #[inline]
293 fn is_match(&self, addr: &Address) -> bool {
294 let bytes = addr.as_slice();
295 bytes.starts_with(&self.left) && self.right.is_match(&hex::encode(bytes))
296 }
297}
298
299pub struct LeftRegexRightExactMatcher {
301 pub left: Regex,
302 pub right: Vec<u8>,
303}
304
305impl VanityMatcher for LeftRegexRightExactMatcher {
306 #[inline]
307 fn is_match(&self, addr: &Address) -> bool {
308 let bytes = addr.as_slice();
309 bytes.ends_with(&self.right) && self.left.is_match(&hex::encode(bytes))
310 }
311}
312
313pub struct SingleRegexMatcher {
315 pub re: Regex,
316}
317
318impl VanityMatcher for SingleRegexMatcher {
319 #[inline]
320 fn is_match(&self, addr: &Address) -> bool {
321 let addr = hex::encode(addr);
322 self.re.is_match(&addr)
323 }
324}
325
326pub struct RegexMatcher {
328 pub left: Regex,
329 pub right: Regex,
330}
331
332impl VanityMatcher for RegexMatcher {
333 #[inline]
334 fn is_match(&self, addr: &Address) -> bool {
335 let addr = hex::encode(addr);
336 self.left.is_match(&addr) && self.right.is_match(&addr)
337 }
338}
339
340fn parse_pattern(pattern: &str, is_start: bool) -> Result<Either<Vec<u8>, Regex>> {
341 if let Ok(decoded) = hex::decode(pattern) {
342 if decoded.len() > 20 {
343 return Err(eyre::eyre!("Hex pattern must be less than 20 bytes"));
344 }
345 Ok(Either::Left(decoded))
346 } else {
347 let (prefix, suffix) = if is_start { ("^", "") } else { ("", "$") };
348 Ok(Either::Right(Regex::new(&format!("{prefix}{pattern}{suffix}"))?))
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn find_simple_vanity_start() {
358 let args: VanityArgs = VanityArgs::parse_from(["foundry-cli", "--starts-with", "00"]);
359 let wallet = args.run().unwrap();
360 let addr = wallet.address();
361 let addr = format!("{addr:x}");
362 assert!(addr.starts_with("00"));
363 }
364
365 #[test]
366 fn find_simple_vanity_start2() {
367 let args: VanityArgs = VanityArgs::parse_from(["foundry-cli", "--starts-with", "9"]);
368 let wallet = args.run().unwrap();
369 let addr = wallet.address();
370 let addr = format!("{addr:x}");
371 assert!(addr.starts_with('9'));
372 }
373
374 #[test]
375 fn find_simple_vanity_end() {
376 let args: VanityArgs = VanityArgs::parse_from(["foundry-cli", "--ends-with", "00"]);
377 let wallet = args.run().unwrap();
378 let addr = wallet.address();
379 let addr = format!("{addr:x}");
380 assert!(addr.ends_with("00"));
381 }
382
383 #[test]
384 fn save_path() {
385 let tmp = tempfile::NamedTempFile::new().unwrap();
386 let args: VanityArgs = VanityArgs::parse_from([
387 "foundry-cli",
388 "--starts-with",
389 "00",
390 "--save-path",
391 tmp.path().to_str().unwrap(),
392 ]);
393 args.run().unwrap();
394 assert!(tmp.path().exists());
395 let s = fs::read_to_string(tmp.path()).unwrap();
396 let wallets: Wallets = serde_json::from_str(&s).unwrap();
397 assert!(!wallets.wallets.is_empty());
398 }
399}