Finding Smart Contract Addresses Efficiently with Rust
Partner logo
All posts

Finding Smart Contract Addresses Efficiently with Rust

Use Rust and CREATE2 opcode to generate deterministic smart contract addresses with vanity suffixes — enabling memorable, branded on-chain addresses while maintaining security and predictability.

Luca Cevasco
Blockchain Developer
April 23, 2025·4 min read

TL;DR

To generate deterministic smart contract addresses ending in a custom suffix (like ...ba5e), use Solidity's CREATE2 opcode and brute-force salt values until the address matches. Rust with the alloy_primitives crate runs this search far faster than JavaScript — Rather Labs measured a 9,000% performance gain — and AWS Fargate, Lambda, and DynamoDB scale it into a reliable pipeline.

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.

Frequently asked questions

How does CREATE2 produce a predictable contract address?

CREATE2 derives the final contract address from three deterministic inputs: the deployer address, a user-provided salt (usually a 32-byte value), and the keccak256 hash of the contract's bytecode. Because the result is fully deterministic, you can compute the address off-chain before deployment, and you can repeatedly try different salts until the address ends with a desired suffix.

Why use Rust instead of JavaScript to search for vanity contract addresses?

The original JavaScript implementation hit performance bottlenecks when generating hundreds of thousands of addresses. Rust offers low-level control and zero-cost abstractions so hashing and arithmetic run close to C/C++ speed, plus memory safety and concurrency without sacrificing performance. Migrating from JavaScript to Rust yielded a 9,000% performance improvement, cutting both the time and cost of finding matching addresses.

How can the salt search be scaled or parallelized?

On AWS, the Rust program runs in AWS Fargate containers that scale on demand, found nonces are stored in DynamoDB, and AWS Lambda triggers new Fargate tasks when nonces drop below a threshold. For more speed, Rust multi-threading can split the salt search across CPU threads using per-thread offsets, and porting keccak256 to CUDA or OpenCL lets GPUs compute massive numbers of parallel hashes.

Share this article