use rayon::prelude::*;
use swap_kit_types::{MineRequest, MineResult};
use tiny_keccak::{Hasher, Keccak};
pub fn mine(req: MineRequest) -> MineResult {
let requested_iters = req.max_iterations.unwrap_or(1_000_000);
let max_iterations = std::cmp::min(requested_iters, 10_000_000);
let deployer_hex = req.deployer.strip_prefix("0x").unwrap_or(&req.deployer);
let deployer_bytes = match hex_decode(deployer_hex) {
Some(b) if b.len() == 20 => b,
_ => return MineResult { salt: String::new(), address: String::new(), attempts: 0, found: false },
};
let hash_hex = req
.init_code_hash
.strip_prefix("0x")
.unwrap_or(&req.init_code_hash);
let init_code_hash = match hex_decode(hash_hex) {
Some(b) if b.len() == 32 => b,
_ => return MineResult { salt: String::new(), address: String::new(), attempts: 0, found: false },
};
let prefix = req.prefix.strip_prefix("0x").unwrap_or(&req.prefix);
let prefix_bytes = match hex_decode(prefix) {
Some(b) => b,
_ => return MineResult { salt: String::new(), address: String::new(), attempts: 0, found: false },
};
let chunk_size = 10_000u64;
let num_chunks = (max_iterations.saturating_add(chunk_size.saturating_sub(1))) / chunk_size;
let result = (0..num_chunks)
.into_par_iter()
.find_map_any(|chunk_idx| {
let start = chunk_idx * chunk_size;
let end = std::cmp::min(start + chunk_size, max_iterations);
for i in start..end {
let mut salt = [0u8; 32];
let i_bytes = i.to_be_bytes();
salt[24..32].copy_from_slice(&i_bytes);
let address = compute_create2_address(&deployer_bytes, &salt, &init_code_hash);
if address.starts_with(&prefix_bytes) {
return Some(MineResult {
salt: format!("0x{}", hex_encode(&salt)),
address: format!("0x{}", hex_encode(&address)),
attempts: i + 1,
found: true,
});
}
}
None
});
result.unwrap_or(MineResult {
salt: String::new(),
address: String::new(),
attempts: max_iterations,
found: false,
})
}
fn compute_create2_address(deployer: &[u8], salt: &[u8; 32], init_code_hash: &[u8]) -> Vec<u8> {
let mut hasher = Keccak::v256();
hasher.update(&[0xff]);
hasher.update(deployer);
hasher.update(salt);
hasher.update(init_code_hash);
let mut hash = [0u8; 32];
hasher.finalize(&mut hash);
hash[12..32].to_vec()
}
fn hex_decode(hex: &str) -> Option<Vec<u8>> {
let hex = if hex.len() % 2 != 0 {
format!("0{}", hex) } else {
hex.to_string()
};
(0..hex.len())
.step_by(2)
.map(|i| {
let end = std::cmp::min(i + 2, hex.len());
u8::from_str_radix(&hex[i..end], 16).ok()
})
.collect()
}
fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_create2_address() {
let deployer = hex_decode("0000000000000000000000000000000000000000").unwrap();
let mut salt = [0u8; 32];
let init_code_hash =
hex_decode("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
let address = compute_create2_address(&deployer, &salt, &init_code_hash);
assert_eq!(address.len(), 20);
}
#[test]
fn test_mine_finds_prefix() {
let req = MineRequest {
deployer: "0x0000000000000000000000000000000000000001".to_string(),
init_code_hash:
"0x0000000000000000000000000000000000000000000000000000000000000001"
.to_string(),
prefix: "00".to_string(), max_iterations: Some(100_000),
};
let result = mine(req);
assert!(result.found, "Should find a matching address within 100k iterations");
assert!(result.address.starts_with("0x00"));
}
#[test]
fn test_hex_roundtrip() {
let original = vec![0xde, 0xad, 0xbe, 0xef];
let encoded = hex_encode(&original);
assert_eq!(encoded, "deadbeef");
let decoded = hex_decode(&encoded).unwrap();
assert_eq!(decoded, original);
}
}