Cointime

Download App
iOS & Android

Releasing Alloy

From paradigm by Georgios Konstantopoulos, James Prestwich, Matthias Seitz, DaniPopes, Yash Atreya, Zerosnacks, Enrique Ortiz

Contents

The story so far

One year ago, we announced Alloy, our full rewrite of the low-level building blocks for interacting with EVM blockchains, from the RPC types, down to the ABI encoders and primitive types.

Today, we’re releasing Alloy 0.1 (crates), the first release of the Alloy project including all the necessary utilities for interacting with an EVM-based blockchain. This release includes multiple API improvements and new features which we are excited to finally share with our users. As part of this release, we’re also officially stopping maintenance of ethers-rs, and we’re encouraging everyone to move to Alloy.

While we are only now releasing Alloy as the top-level package including all JSON-RPC functionality, the packages inside of Alloy Core with low-level type & ABI coders have been released for a few months now, and many of them have >1 million all time downloads already!

To help users get up to speed with Alloy, we’re publishing the Alloy Book (including an ethers to alloy migration guide), the Alloy and Alloy Core docs, as well as a lot of examples showing all sorts of ways you can consume Alloy.

Alloy is already integrated in many Rust codebases, as well as all the projects we’re working on: RevmFoundry and Reth. We’ve been using it in production ourselves for months, and we’re excited for it to be a performant and stable foundation for the Rust Ethereum ecosystem, as we envisioned in our original announcement.

With that out of the way, let’s dive in!

What are the highlights of the Alloy release?

Alloy is written from the bottom-up to be performant, safe to use, and intuitive. We took our learnings from 4 years of ethers-rs development and put them all into designing a great client-side library for interacting with EVM-based blockchains.

This took a lot of time, 1 year since we announced the Alloy project, but we’re proud of the result.

Today we are excited to reveal some exciting new features co-architected and co-developed by our core team alongside James Prestwich who’s been maintaining Alloy with the rest of us over the last year. Big shoutout & thank you to James for pushing us to go beyond what ethers-rs allowed us before, and for being a core input to the successful delivery of today’s release.

Reworked Provider Middleware architecture

The most important abstraction in a RPC client library is the Provider, which lets you interact with the network. Users commonly want to extend basic provider operations: different gas estimations, gas escalators, alternative signing pipelines, and more. You want to do this in a modular way which empowers the developer to extend the behavior of the core library, instead of having to modify it.

In ethers-rs, we had defined the Middleware as the one-stop shop for modifying your RPC client’s behavior. If you wanted nonce management, it’s a middleware. Gas escalation, it’s a middleware. Signing transactions? Middleware. Flashbots bundles? Middleware. This approach worked because it gave us a lot of flexibility to override things as we wanted, but it also was too error prone, and had poor developer experience (e.g. expecting devs to panic when a function that’s required by the Middleware trait does not make sense to implement).

Alloy redesigned that abstraction from the ground up. We split the Middleware in 3 overridable abstractions:

  • Transport Layers: The Transport is the “wire” that carries over your data to the node. This now implement’s Tower’s Service Layers which allows tapping into the Tower Ecosystem for common middleware such as request retries, rate limits and more.
  • Provider Layers: Modeled after Tower’s Layers this allows overriding various Provider functionalities, e.g. if you wanted to hijack the get_storage_at method, you’d implement a ProviderLayer.
  • Fillers: This is probably the most exciting abstraction, Fillers handle everything about the transaction’s lifecycle. A user generally submits a partially populated transaction, and the provider is responsible for figuring out what data is missing and filling it in. Fillers can be installed independent of order from each other, solving a major footgun from ethers-rs. We provide the .with_recommended_fillers() method which aggregates commonly used Fillers via multiple JoinFill ProviderLayers for convenience:Wallet Filler: Signs a transaction with a credential from a wide suite: a local private key parsed from a hex string, a 12/24-word mnemonic, a keystore file, a hardware wallet like Trezor or Ledger, or even from a productionized key management system such as YubiHSM, AWS KMS or GCP KMS.Nonce Filler: Automatically manages nonces across all accounts.Gas Filler: Handles calculating the gas price and the gas limit for each transaction.ChainId Filler: Embeds the correct chain ID into transactions depending on which chain is connected.
  • Wallet Filler: Signs a transaction with a credential from a wide suite: a local private key parsed from a hex string, a 12/24-word mnemonic, a keystore file, a hardware wallet like Trezor or Ledger, or even from a productionized key management system such as YubiHSM, AWS KMS or GCP KMS.
  • Nonce Filler: Automatically manages nonces across all accounts.
  • Gas Filler: Handles calculating the gas price and the gas limit for each transaction.
  • ChainId Filler: Embeds the correct chain ID into transactions depending on which chain is connected.

Putting it all together, the stack of features we have can be seen below:

This stack is bundled and exposed to the user via the ProviderBuilder which has a very ergonomic API:

// Create a signer from a random private key. let signer = PrivateKeySigner::random(); let provider = ProviderBuilder::new() // configures all the fillers .with_recommended_fillers() // sets the signer, allows configuring more than 1 signer which will be picked based on your transaction's `from` field. .wallet(EthereumWallet::from(signer)) // connects to the chain // can also use `.on_http`, `.on_ws`, or `.on_ipc` for no dyn dispatch // can also use `.on_anvil` for local testing // can also use `.on_client` for configuring auth options e.g. bearer auth. .on_builtin("ws://localhost:8545") .await?; let tx = TransactionRequest::new().with_to(...).with_value(...); let receipt = provider.send_transaction(tx).await?.get_receipt().await?; // do something with the receipt

Oh, we also made sure the Provider and Signers are object-safe, to make it easier to avoid generics in your code by Boxing them. For more on how to consume providers, see the book.

RPC Types Abstraction

The world is going multichain, and that means more differences in RPC types! That was one of the most painful things in ethers-rs where we supported e.g. Celo-related fields with a feature flag. This meant that if you wanted to have a type-safe connection to both Celo and Ethereum you had to choose between importing the library twice, or expecting that the Celo fields would be None in all cases in Ethereum. This is a problem that we set out to fix.

In Alloy, we define the Network trait which defines the “shape” of every network for all its RPC requests and responses where each type must implement certain traits, e.g. Eip2718EnvelopeTxReceiptTransactionBuilder, summarized below:

pub trait Network { /// The network transaction type enum. type TxType /// The network transaction envelope type. type TxEnvelope: Eip2718Envelope + Debug; /// An enum over the various transaction types. type UnsignedTx: From<Self::TxEnvelope>; /// The network receipt envelope type. type ReceiptEnvelope: Eip2718Envelope + TxReceipt; /// The network header type. type Header; /// The JSON body of a transaction request. type TransactionRequest: RpcObject + TransactionBuilder<Self> + Debug + From<Self::TxEnvelope> + From<Self::UnsignedTx>; /// The JSON body of a transaction response. type TransactionResponse: RpcObject + TransactionResponse; /// The JSON body of a transaction receipt. type ReceiptResponse: RpcObject + ReceiptResponse; /// The JSON body of a header response. type HeaderResponse: RpcObject; }

This allows us to import the library once without any feature flags, and depending on what network we specify we get different type abstractions. This is great! Type-safety, without redundant overhead! This is also the approach we’re taking in Reth for allowing any developer to build a chain with custom RPC types on the server side, such as a network with native account abstraction.

We provide two network implementations Ethereum and AnyNetwork. Ethereum contains all the types you know and love. AnyNetwork however, wraps every RPC type with the WithOtherFields<T> type which acts as a catch-all for any RPC response fields that do not match the Ethereum structure.

A developer can choose their Network implementation using the .network::() method on the ProviderBuilder. This allows us to support more networks than just Ethereum in a principled way, without burdening the core maintenance process. All you need to do is implement the network trait and all its associated types, import it in your code, and you’re done!

Follow our work on defining the OpStackNetwork in the op-alloy crate, and reach out if you want to implement your own network!

The sol! Macro

We first talked about the sol macro in our initial post. It is a rework of our previous abigen macro, which was used to generate type-safe bindings to a contract’s ABI. The sol macro is not a compiler, but it is a complete representation of the Solidity type system in Rust, which means you can just paste Solidity in it, and it’ll codegen bindings for it, even allowing support for custom types!

The sol macro also codegens JSON RPC bindings via the #[sol(rpc)] attribute. A deployer method is also generated if you pass it the #[sol(bytecode = "...")] attribute. For example, the code below would generate a Counter::deploy function as well as a Counter::increment(&self) method which you can use to increment the counter.

sol! { // solc v0.8.26; solc a.sol --via-ir --optimize --bin #[sol(rpc, bytecode="608080...")] contract Counter { uint256 public number; function increment() public { number++; } } }

To learn more about the sol macro, check the page on the book and its detailed documentation. Interacting with a smart contract is similar to ethers-rs (note, no more Arcs!), with minor underlying changes in the API for fetching a transaction’s receipt.

let provider = ProviderBuilder::new().on_builtin("...").await?; // Deploy the contract. let contract = Counter::deploy(&provider).await?; println!("Deployed contract at address: {}", contract.address()); let receipt = contract.setNumber(U256::from(42)).send().await?.get_receipt().await?; println!("Receipt: {receipt}"); // Increment the number to 43 (without waiting for the receipt) let tx_hash = contract.increment().send().await?.watch().await?; println!("Incremented number: {tx_hash}"); // Retrieve the number, which should be 43. let number = contract.number().await?.number.to_string(); println!("Retrieved number: {number}");

The sol macro’s functionality is also integrated with Foundry in forge bind for generating type-safe bindings for all your client-side integrations. Check out the updated Foundry Rust template if that’s of interest to you!

Extensive documentation and tests

We want our users to be equipped with high-level tutorials & examples for consuming the project, as a library. To achieve that, we provide a large surface area of documentation:

  • Alloy Docs: Rustdoc documentation for each function on the Alloy repository.
  • Alloy Core Docs: Rustdoc documentation for each function on the Alloy Core repository.
  • Alloy Examples: Wide range of code examples on how to consume Alloy in your day to day.
  • Alloy Book: Tutorials and long form writeups about all things Alloy.

Making the docs excellent is a top priority for us, please open issues on the book with more tutorials you’d like to see.

What is next for Alloy?

Today’s 0.1 release marks Alloy at feature parity with ethers-rs and beyond, while also being performant, well-tested and well-documented. We think Alloy is the tool of choice for power users, yet it is simple and intuitive enough for anyone to work with.

Our next priority is the 1.0 release, which means we’ll be polishing our APIs and working towards proper stability. While we don’t offer any formal stability guarantees, most of the APIs are baked, and we do not expect large changes.

To help achieve Alloy’s long term success, we’re looking to add 1 full-time staff-level engineer to the Alloy team who will help drive the day to day of the project, as well as help us grow the contributor base. If the above sounds interesting, please reach out to [email protected].

We’re excited for every ethers-rs user to port their code to use Alloy, as well as new services to be built with it! Go read the docs of alloy-core and alloy, the examples, and the book!

Until then, see you on Github!

Comments

All Comments

Recommended for you

  • BTC falls below $88,000

     market shows BTC fell below $88,000, currently at $87,997.85, 24-hour decline reaches 0.88%, market volatility is significant, please manage your risk accordingly.

  • The U.S. spot Ethereum ETF saw net inflows of $84.59 million yesterday.

     according to Trader T monitoring, the US spot Ethereum ETF had a net inflow of 84.59 million USD yesterday.

  • ETH breaks $3,000

     the market shows ETH breaking through $3000, currently at $3000.08, with a 24-hour decline of 0.38%. The market is highly volatile, please manage your risk accordingly.

  • Binance Wallet launches "secure auto-signature" service

     according to the official announcement, Binance Wallet has launched the "Secure Auto Sign" (SAS) service: it now supports mnemonic/private key wallets to trade on Binance Wallet (web version).

  • Circle minted 500 million USDC on the Solana network.

    according to Onchain Lens monitoring, Circle has minted 500 million USDC on the Solana network. Since October 11, Circle has issued a total of 18 billion USDC on the Solana network.

  • Sources familiar with the matter: JPMorgan Chase is considering offering cryptocurrency trading services to institutional clients.

    according to Bloomberg, as major global banks deepen their involvement in the cryptocurrency asset class, JPMorgan Chase is considering offering cryptocurrency trading services to its institutional clients. A knowledgeable source revealed that JPMorgan is evaluating what products and services its market division can offer to expand its business in the cryptocurrency field. The source stated that these products and services may include spot and derivatives trading.

  • Federal Reserve Governor Milan: We believe that the policy rate will eventually be lowered.

    Federal Reserve Board member Mylan stated that due to the US government shutdown, there were some anomalies in last week's inflation data; he believes that the US will not experience an economic recession in the near term, but if policies are not adjusted, the US will face an increasing risk of economic recession. We believe that policy interest rates will eventually be lowered.

  • BlackRock deposited 819.39 BTC, worth approximately $73.72 million, into Coinbase.

     according to Onchain Lens monitoring, BlackRock deposited 819.39 BTC into Coinbase, worth approximately 73.72 million USD.

  • Ghana passes law legalizing the use of cryptocurrency

    according to Bloomberg, the Ghanaian Parliament has approved a cryptocurrency legalization bill aimed at addressing the expanding use of cryptocurrencies in the country but the lack of regulation. According to Johnson Asiamah, Governor of the Bank of Ghana, the newly passed Virtual Asset Service Providers Act will facilitate the licensing of crypto platforms and the regulation of related activities.

  • CryptoQuant: Bitcoin network activity cools, market shows clear bearish signs.

    CryptoQuant published an analysis stating that the Bitcoin market continues to be in a bear market state, with multiple network indicators showing a significant cooling of activity. Data shows that the 30-day moving average of Bitcoin is below the 365-day moving average (-0.52%), and the bull-bear cycle indicator confirms the current bear market pattern. The number of network transactions has dropped from about 460,000 to about 438,000, fees have decreased from $233,000 to $230,000, and highly active addresses have reduced from 43.3K to 41.5K, all indicating reduced speculative activity and that the market is in a defensive phase.