Finding Smart Contract Addresses efficiently with Rust

April 23, 2025
Key Points

Accelerating Deterministic Contract Creation with Rust and AWS

In many blockchain-based products, there’s a need to deploy contracts deterministically so that each newly deployed contract address ends in a specific suffix (e.g., ...ba5e). This is more than a vanity feature—having a recognizable suffix can be part of branding, user experience, or security. To achieve this, we leverage Solidity’s CREATE2 opcode, which allows us to deploy contracts at predictable addresses based on a salt value and the contract’s initialization code hash.

Why Deterministic Deployments?

  • Branding/UX: A consistent suffix like ...ba5e makes addresses memorable and instantly identifiable.
  • Security: It’s easier to confirm on-chain that a particular address is indeed from our product.
  • Predictable State: Developers can compute the final address off-chain before the contract is even deployed, simplifying integration and testing.

How CREATE2 Works

In Solidity, CREATE2 is a variant of the traditional CREATE opcode. Instead of basing the contract’s final address solely on the deployer’s nonce, it also incorporates:

  1. The deployer address (EOA or contract calling create2).
  2. A user-provided salt (usually a 32-byte value).
  3. The bytecode’s keccak256 hash.

Because CREATE2 uses this combination deterministically, we can repeatedly try different salts until the resulting address ends with our desired suffix. This process, however, can be computationally expensive.

Rust Implementation for Speed

Our first implementation was in JavaScript, but it quickly hit performance bottlenecks when generating hundreds of thousands of addresses. We then switched to Rust, taking advantage of:

  • Low-level control and “zero-cost abstractions,” so hashing and arithmetic run close to the speed of C/C++.
  • Memory safety and concurrency features without sacrificing performance.
  • The alloy_primitives crate, which provides efficient Rust-based Ethereum primitives for addresses, hashing (keccak256), and other low-level operations—making it simple to implement CREATE2 logic directly in Rust.

Below is a simplified snippet from our Rust code that demonstrates how we loop over a range of salts, compute the resulting contract address with create2, and check for the desired suffix:


fn find_salt_for_suffix(
    deployer_address: &str,
    contract_bytecode: &str,
    target_suffix: &str,
    existing_nonces: &[u64],
    mut salt: u64,
) -> (String, u64) {
    // Calculate the keccak256 hash of the init code using alloy_primitives
    let init_code_hash = keccak256(
        &hex::decode(contract_bytecode.trim_start_matches("0x"))
            .expect("Invalid bytecode")
    );
    
    loop {
        // Skip any salts we’ve already used
        if existing_nonces.contains(&salt) {
            salt += 1;
            continue;
        }

        // Prepare salt bytes for CREATE2
        let mut salt_bytes = [0u8; 32];
        salt_bytes[24..].copy_from_slice(&salt.to_be_bytes());

        // Compute the address deterministically
        let address = Address::parse_checksummed(deployer_address, None)
            .unwrap()
            .create2(FixedBytes::from(salt_bytes), init_code_hash.clone());

        let address_str = address.to_string();
        // Check if it ends with the target suffix (e.g., ...ba5e)
        if address_str.ends_with(target_suffix) {
            return (address_str, salt);
        }
        salt += 1;
    }
}

By migrating from JavaScript to Rust, we achieved a 9,000% performance improvement, drastically reducing both the time and cost of generating contract addresses that match a specific suffix.

AWS Integration and DynamoDB

To make this part of a scalable pipeline:

  1. AWS Fargate: We run the Rust program in a containerized environment that can scale automatically based on demand.
  2. DynamoDB: We store the list of found nonces (the salt values) so our system can quickly retrieve and serve them.
  3. AWS Lambda: Acts as a gatekeeper. When nonces go below a threshold, it triggers new Fargate tasks to generate more, ensuring a steady supply.

Going Further: Parallelization and GPU

  1. Multi-Threading in Rust:
    • We can split the search space across CPU threads, each with a unique random offset to avoid collisions in the same salt range.
    • A simple approach is to have each thread try salt + thread_id * OFFSET. Rust’s powerful concurrency primitives (e.g., channels, locks, or atomic counters) can coordinate results without sacrificing speed.
  2. Using CUDA/OpenCL for GPU Acceleration:
    • For massive needs—like generating millions of deterministic addresses—GPUs can handle huge numbers of parallel hash computations.
    • By porting the core hashing logic (like keccak256) to CUDA or OpenCL, each GPU kernel can work on different salt ranges simultaneously, providing another big jump in performance over CPU-bound threads.

Conclusion

Deterministically deploying contracts ending in a memorable suffix, such as ...ba5e, can offer both branding and security benefits. Using CREATE2 in Solidity allows for predictable addresses, but the raw computation to find them can become a bottleneck. By embracing Rust (with alloy_primitives) and deploying on AWS (with DynamoDB, Fargate, and Lambda), we built a reliable, cost-effective, and lightning-fast system that meets our growing demands—while still leaving room to scale further with multi-threaded or GPU-based solutions for truly large workloads.

Macarena López Morillo
Head of People @ Rather Labs
Get the Full Picture
For an in-depth understanding of this topic, don't miss out. Learn more here and elevate your knowledge.
right arrow

Featured Resources

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.