Introducing Barz. TrustWallet's Smart Wallet Solution
By David Kim
Special thanks to Luis Ocegueda and Artem Goryunov for their feedback and contributions.
This is the first of an article series on Barz.
For Builders By Builders: Introducing the Barz SDK
Multi-tier Module system. A secure foundation for open innovation
In February 2024, we successfully launched Swift Wallet, an Account Abstraction Wallet to offer our users a more secure, smarter wallet. Swift Wallet introduced innovative features including Passkeys, gas payment with 200+ tokens, 1 step swap & bridging.
Today, we are open sourcing our robust Smart Wallet solution “Barz”.
Let’s dive into the details of how we got here!
Why we built Barz
Trust Wallet, empowers more than 122 million Web3 users worldwide, understands the common issues and inconveniences users face when using wallets.
Many users struggle with:
Improper management of their Mnemonic Phrases
Granting excessive privileges to a dApp for a single transaction.
Ability to create multiple automated tasks, e.g. schedule payments.
While the issues and challenges with mnemonic seed phrases are well known, there are larger security and UX issues as more users are onboarded to Web3.
To provide a solution to these challenges and limitations and ultimately drive more adoption of Web3, we decided to develop a Smart Wallet through Account Abstraction, which offers a fundamental solution overcoming these limitations - Barz.
Barz
Barz is an ERC 4337 compatible Smart Contract Wallet focused on a secure and smarter experience for users through modular, upgradeable, and secure designs.
We aggregated the benefits from each wallet and pioneered new approaches to provide best-in-class service to users. Barz is also one of the first Passkeys based 4337 account that launched in production.
Barz, at its core is a proxy contract that utilizes the Diamond Proxy Pattern(EIP 2535) for a scalable and secure addition of use cases with high security threshold.
Barz system currently has 12 fully built Facet implementations that can provide features of:
Account Recovery
Lock
Signature Migration
Guardian
Restrictions (Custom Rules for Transactions)
Diverse Validation Mechanisms
Secp256k1 - Default EVM Scheme (e.g., Mnemonic phrase)
Secp256r1 - Passkeys, Okta
Multi-sig
Let’s dive into Diamond Proxy pattern and how it works with ERC 4337. We’ll dive into the optimization points we made to Diamond for Barz in our next article.
Diamond
Diamond is a modular smart contract system enabled by a multi-faceted proxy stated in EIP-2535.
A multi-faceted proxy is different from the conventional proxy pattern like UUPS(Universal Upgradeable Proxy Standard) and TransparentUpgradeable where they have a single implementation to route the call to.
For example, a UUPS based proxy smart contract stores the single implementation contract address in the EIP-1967 based storage slot and performs upgrade by modifying the storage slot.
The proxy would then delegate all calls to the implementation contract through a fallback function that makes a delegatecall
for all calls that comes with msg.data
.
Unlike the UUPS pattern we saw above, Diamond Proxy has multiple “implementation” smart contracts, which are called Facets.
But Diamond not only includes how Proxy and Facets interact but proposes a comprehensive approach to manage the following components of a proxy contract:
upgrade
view
storage
Let’s dive into the details of how Diamond works under the hood.
Considering Diamond has multiple implementation contracts called Facets, Diamond requires a routing logic to route the function call to the correct corresponding facet.
The core routing logic is implemented through a mapping of bytes4
type which holds the function selector as the key and an address
which holds the Facet contract address as value.
When a function call is made to the contract and gets routed to the fallback
function, the fallback
function will fetch the function selector from the calldata
through msg.sig
and make a delegatecall
to the facet if the corresponding facet exists and reverts otherwise.
This multi-faceted proxy pattern provides a benefit of modular implementation designs by enabling each facet to be grouped into a specific domain of functionality.
For example, Account Facet could hold account related logic like execute()
, executeBatch()
while Token Receiver Facet could hold logic like onERC721Received()
, tokensReceived()
.
For a better separation of Facet logic and storage, the Diamond standard also provides an approach of DiamondStorage
mainly for a specific Facet’s storage and AppStorage
which is more suitable for shared storage between Facets.
DiamondStorage
relies on a Solidity struct that contains set variables for the Facet and stores it in the designated namespace storage slot. It is particularly good for isolating or compartmenting state variables to specific facets or functionality.
As the storage slot will be intentionally different to prevent storage collision, this provides a scalable approach of utilizing storage with Multiple Facets compared to the approach of using the default EVM Storage Slot.
This concept is also utilized in the Barz implementation to detach the storage between facets.
In contrast, App Storage
is another type of storage pattern that is more suitable for storage variables that are shared among facets.
App Storage also uses the Struct to define the storage, however, uses the storage slot 0
unlike the Diamond Storage which used a custom storage slot.
For example, Barz stores the address of the EntryPoint
contract and signerMigration
flag which is shared across multiple facets.
Let’s have a look on how Diamonds perform upgrades.
To register a new facet or remove/replace them from Diamond, the contract should comply with the standard interface of diamondCut()
.
diamondCut()
normally performs 4 main steps
Ownership Check
This is crucial considering that a malicious Diamond can overwrite storage or perform malicious activities
Facet Check
Check if the Facet is indeed a contract, check if the selector is okay to be added. Diamond does not allow multiple facets from registering an identical function selector.
Add/Remove/Replace Facet’s function selector and its corresponding actions
Perform Diamond Init
Diamond Init is an approach for one-time initialization for Diamonds similar to constructor.
Once the diamondCut()
is called to the Diamond, the Diamond should store the mapping of functionSelectors
and the Facet address
to route the call from the fallback
function.
Within the diamondCut()
execution, Diamond also performs a process called Diamond Init
for initializing/uninitializing state variables. This is useful during the installation and uninstallation of Facets to clean up storage.
This is enabled by making a delegatecall
to the _init
address with the provided calldata
from diamondCut()
.
For example, below is an implementation of Diamond Init contract to add ERC-165 based supportedInterfaces()
check.
Now as we’ve checked how storage and upgrade work for Diamonds, let’s check the approaches to view the status of the Diamond - DiamondLoupe
.
DiamondLoupe
is a standardized interface to look into Diamond. This allows external components and entities to check which Facet and Function Selectors are registered to the Diamond
.
The method facets()
returns the whole information about its Facet mappings.
facetFunctionSelectors()
returns the list of function selectors corresponding to the given facet.
facetAddresses()
returns the list of all facets registered to the Diamond.
facetAddress()
returns the facet address of the corresponding function selector.
By complying with this standard interface, Diamond can transparently show its state to external entities while being compatible with tools like louper.dev.
For example, this is an overview of Barz seen from louper.dev tool:
Overview of Barz from Louper - The Ethereum Diamond Inspector
ERC 4337 Account with Diamond
From an ERC 4337 account contract perspective, there are 2 main aspects to handle:
Validation
Execution
For Validation, the account should implement the IAccount
interface specified in the ERC 4337 standard.
For execution, it can implement its own execution methods like execute()
and executeBatch()
.
An example of Barz handling the 4337 logic in a modular approach through Diamond can be seen from the separation of Account Facet and Verification Facet during initialization and execution.
In the BarzFactory
code below, users can create an account with any verification facet with their preferred signature scheme by providing the address in the _verificationFacet
and the corresponding _owner
for initialization.
Signer will be initialized using the verificationFacet
provided in the createAccount()
function.
This allows BarzFactory
and AccountFacet
, together with all the remaining Facets to be agnostic to the signature validation mechanism and focus on its core logic.
This can also be applied during the validation of UserOperation e.g., validateUserOp()
.
With the separation of Account and Verification logic through modular designs, it enables the account to dynamically switch the signer and signature scheme depending on user’s needs and wants.
It also helps the codebase to be clean and focus on its domain logic which helps reduce potential code bugs and unwanted complexity coming from interdependencies between logic.
Account Facet
delegates the validation of UserOperation signature to the Verification Facet
and only propagates the result from the Verification Facet
.
Conclusion
The Barz Smart Wallet Architecture utilizing Diamond allows modular and flexible development of a wide array of use cases. It allows each Facets to focus on its specific business logic while providing the flexibility to easily switch each component and maintain interoperability.
In our next article, we’ll share the optimizations we did to make Diamonds much better for building smart wallets.
Stay tuned for our next article series and releases of powerful use cases of smart wallet built on Barz.
If you have questions or want to use Trust Wallet’s AA SDK in your service or any ideas for collaboration, reach out in smartwallet channel in our Trust Wallet Discord
Last updated