# Table of Contents
- [Omni Account – Rhinestone Docs](#omni-account-rhinestone-docs)
- [Welcome – Rhinestone Docs](#welcome-rhinestone-docs)
- [ModuleSDK – Rhinestone Docs](#modulesdk-rhinestone-docs)
- [Build Modules – Rhinestone Docs](#build-modules-rhinestone-docs)
- [Getting started – Rhinestone Docs](#getting-started-rhinestone-docs)
- [Architecture – Rhinestone Docs](#architecture-rhinestone-docs)
- [Omni Account – Rhinestone Docs](#omni-account-rhinestone-docs)
- [Settlement Layer – Rhinestone Docs](#settlement-layer-rhinestone-docs)
- [Orchestrator – Rhinestone Docs](#orchestrator-rhinestone-docs)
- [Solvers – Rhinestone Docs](#solvers-rhinestone-docs)
- [Meta Intents – Rhinestone Docs](#meta-intents-rhinestone-docs)
- [Getting started – Rhinestone Docs](#getting-started-rhinestone-docs)
- [Use Cases Beyond Chain Abstraction – Rhinestone Docs](#use-cases-beyond-chain-abstraction-rhinestone-docs)
- [Account Abstraction 101 – Rhinestone Docs](#account-abstraction-101-rhinestone-docs)
- [Modules explained – Rhinestone Docs](#modules-explained-rhinestone-docs)
- [Tutorial 1: Create a new Omni Account – Rhinestone Docs](#tutorial-1-create-a-new-omni-account-rhinestone-docs)
- [Tutorial 2: Send your first intent – Rhinestone Docs](#tutorial-2-send-your-first-intent-rhinestone-docs)
- [ERC-7579 – Rhinestone Docs](#erc-7579-rhinestone-docs)
- [Using the executor flow – Rhinestone Docs](#using-the-executor-flow-rhinestone-docs)
- [Tutorial 3: Turn an existing account into an Omni Account – Rhinestone Docs](#tutorial-3-turn-an-existing-account-into-an-omni-account-rhinestone-docs)
- [ERC-7484: Registry Extension for ERC-7579 – Rhinestone Docs](#erc-7484-registry-extension-for-erc-7579-rhinestone-docs)
- [Address Book – Rhinestone Docs](#address-book-rhinestone-docs)
- [Specifying a token preference – Rhinestone Docs](#specifying-a-token-preference-rhinestone-docs)
- [ModuleKit – Rhinestone Docs](#modulekit-rhinestone-docs)
- [getAccountLockerTargetExecutor – Rhinestone Docs](#getaccountlockertargetexecutor-rhinestone-docs)
- [getUnlockFundsAction – Rhinestone Docs](#getunlockfundsaction-rhinestone-docs)
- [getAccountLockerHook – Rhinestone Docs](#getaccountlockerhook-rhinestone-docs)
- [Audits – Rhinestone Docs](#audits-rhinestone-docs)
- [getAccountLockerSourceExecutor – Rhinestone Docs](#getaccountlockersourceexecutor-rhinestone-docs)
- [getDepositToAcrossAction – Rhinestone Docs](#getdeposittoacrossaction-rhinestone-docs)
- [ACCOUNT_LOCKER_HOOK – Rhinestone Docs](#account-locker-hook-rhinestone-docs)
- [registerApprovalSpendAction – Rhinestone Docs](#registerapprovalspendaction-rhinestone-docs)
- [ACCOUNT_LOCKER_TARGET_EXECUTOR – Rhinestone Docs](#account-locker-target-executor-rhinestone-docs)
- [ACCOUNT_LOCKER_SOURCE_EXECUTOR – Rhinestone Docs](#account-locker-source-executor-rhinestone-docs)
- [getOrchestrator – Rhinestone Docs](#getorchestrator-rhinestone-docs)
- [createUserAccount – Rhinestone Docs](#createuseraccount-rhinestone-docs)
- [getUserId – Rhinestone Docs](#getuserid-rhinestone-docs)
- [getPortfolio – Rhinestone Docs](#getportfolio-rhinestone-docs)
- [getOrderPath – Rhinestone Docs](#getorderpath-rhinestone-docs)
- [getSolverClaimPayload – Rhinestone Docs](#getsolverclaimpayload-rhinestone-docs)
- [Types in the Orchestrator SDK – Rhinestone Docs](#types-in-the-orchestrator-sdk-rhinestone-docs)
- [getBundleStatus – Rhinestone Docs](#getbundlestatus-rhinestone-docs)
- [postSignedOrderBundle – Rhinestone Docs](#postsignedorderbundle-rhinestone-docs)
- [Getting started – Rhinestone Docs](#getting-started-rhinestone-docs)
- [Tutorial 1: Install and use your first module – Rhinestone Docs](#tutorial-1-install-and-use-your-first-module-rhinestone-docs)
- [How to use the Module SDK with the Safe – Rhinestone Docs](#how-to-use-the-module-sdk-with-the-safe-rhinestone-docs)
- [How to use the Module SDK with the ZeroDev SDK – Rhinestone Docs](#how-to-use-the-module-sdk-with-the-zerodev-sdk-rhinestone-docs)
- [How to use the Module SDK with the Kernel – Rhinestone Docs](#how-to-use-the-module-sdk-with-the-kernel-rhinestone-docs)
- [How to use the Module SDK with EIP-7702 – Rhinestone Docs](#how-to-use-the-module-sdk-with-eip-7702-rhinestone-docs)
- [How to use passkeys to control an account – Rhinestone Docs](#how-to-use-passkeys-to-control-an-account-rhinestone-docs)
- [How to secure and recovery an account using the Deadman Switch Module – Rhinestone Docs](#how-to-secure-and-recovery-an-account-using-the-deadman-switch-module-rhinestone-docs)
- [How to use the Module SDK with Permissionless.js – Rhinestone Docs](#how-to-use-the-module-sdk-with-permissionless-js-rhinestone-docs)
- [How to use session keys using Smart Sessions – Rhinestone Docs](#how-to-use-session-keys-using-smart-sessions-rhinestone-docs)
- [How to recover an account using the Social Recovery Module – Rhinestone Docs](#how-to-recover-an-account-using-the-social-recovery-module-rhinestone-docs)
- [Smart Sessions – Rhinestone Docs](#smart-sessions-rhinestone-docs)
- [getSudoPolicy – Rhinestone Docs](#getsudopolicy-rhinestone-docs)
- [getSpendingLimitsPolicy – Rhinestone Docs](#getspendinglimitspolicy-rhinestone-docs)
- [getUniversalActionPolicy – Rhinestone Docs](#getuniversalactionpolicy-rhinestone-docs)
- [getUsageLimitPolicy – Rhinestone Docs](#getusagelimitpolicy-rhinestone-docs)
- [getEnableSessionsAction – Rhinestone Docs](#getenablesessionsaction-rhinestone-docs)
- [getValueLimitPolicy – Rhinestone Docs](#getvaluelimitpolicy-rhinestone-docs)
- [getSmartSessionsValidator – Rhinestone Docs](#getsmartsessionsvalidator-rhinestone-docs)
- [getTimeFramePolicy – Rhinestone Docs](#gettimeframepolicy-rhinestone-docs)
- [getRemoveSessionAction – Rhinestone Docs](#getremovesessionaction-rhinestone-docs)
- [getEnableSessionDetails – Rhinestone Docs](#getenablesessiondetails-rhinestone-docs)
- [getPermissions – Rhinestone Docs](#getpermissions-rhinestone-docs)
- [getEnableUserOpPoliciesAction – Rhinestone Docs](#getenableuseroppoliciesaction-rhinestone-docs)
---
# Omni Account – Rhinestone Docs
Omni Account
Overview
Omni Account
============
Why Omni Account?[](#why-omni-account)
---------------------------------------
The strategy to scale Ethereum horizontally has fragmented liquidity and the application layer across many L2s. This introduces a new UX challenge: Users must know about the underlying networks and how to access and utilize their assets.
Omni Account is the answer to Ethereum’s fragmented liquidity and account layer. It provides developers with a complete Chain Abstraction stack, starting with balance abstraction and soon supporting cross-chain key management and module config sync.
What is it?[](#what-is-it)
---------------------------
Omni Account is a system that employs Smart Account Modules to chain abstract ERC-7579-compliant Smart Accounts.
At launch, Omni Account will offer balance abstraction through cross-chain intents built on Across as the settlement layer. This will transform the user’s assets into one unified balance that can be instantly spent on any chain.
Omni Account consists of two core components:
1. The **Resource Lock Hook** to enable irrevocable onchain guarantees to offchain entities through a single signature, and
2. The **Orchestrator**, an offchain entity that sequences transactions and ensures users cannot break their onchain guarantees.

Core Propositions of Omni Account[](#core-propositions-of-omni-account)
------------------------------------------------------------------------
* **Account-native resource locking with no asset separation.** Rhinestone uses the Resource Lock Hook to enable onchain guarantees without the user first transferring to a new contract. This ensures compatibility with existing token primitives for balance tracking, yield, rebasing, and airdrop payouts.
* **Trustless and completely self-custodial.** The user has complete control over their funds at all times, with minimized liveness or censorship threat due to our unique onchain design that is true to the philosophy behind Smart Accounts.
* **Composable and interoperable**: Omni Account’s approach to resource locking is minimal in terms of how it affects the functionality of Modular Smart Accounts. Add existing Smart Account Modules or build your own to level up your Omni Account to your needs.
Check out our [blog (opens in a new tab)](https://blog.rhinestone.wtf/resource-lock-hook-335590cec733)
for a deeper understanding of our Resource Lock Hook Module.
Play with the [demo (opens in a new tab)](https://wallet.rhinestone.wtf/)
to experience instant cross-chain swaps between Arbitrum and Base.
Components[](#components)
--------------------------
* **Smart Account:** Initially supporting Safe, Biconomy’s Nexus, and Magic’s Newton.
* [**Omni Account Modules:**](/omni-account/architecture/omni-account)
A set of ERC-7579 modules that enforce resource locks and set verifiable execution pathways for the Orchestrator to the integrated Settlement Layers.
* [**Orchestrator:**](/omni-account/architecture/orchestrator)
An offchain entity that tracks ongoing intents to ensure locked funds cannot be double spent.
* [**Solver Network:**](/omni-account/architecture/solvers)
Integrated solvers who manage inventory, fill executions and settle via the integrated Settlement Layers.
* [**Settlement Layer:**](/omni-account/architecture/settlement-layer)
An execution layer with sophisticated actors performing executions on the user’s behalf.

Key Features[](#key-features)
------------------------------
* **Instant and atomic cross-chain intents. No bridge. No gas.** All cross-chain transactions from an Omni Account are propagated to a solver network to complete these actions on behalf of the user.
* **Supports EIP-7702 for chain abstracting EOAs.** Data structures and interfaces natively support [The Compact (opens in a new tab)](https://github.com/Uniswap/the-compact)
, an open-source escrow contract for cross-chain intents, enabling EIP-7702 support.
* **Any arbitrary calldata can be appended to the cross-chain intent.** This enables destination chain userops or any other smart contract interaction to be bundled into one atomic cross-chain intent.
* **Intent atomicity and deterministic token transfers.** Token transfers to the destination chain have a deterministic output, allowing batched executions to be appended to the transfer with guaranteed and atomic completion.
* **Solver-based swaps.** A solver-based swap fills the intent if the destination chain output token differs from the origin chain input token. If the swap token is unsupported, destination chain swaps through an injected execution provide a fallback.
* **M-to-1 chains to destination chain and M-to-n input tokens to output tokens.** Use an arbitrary number of origin chains and tokens to fund a single intent on a single destination chain.
* **Supports same-chain intents, a faster and cheaper alternative to ERC-4337.** Use Omni Account's underlying intent system to perform same-chain intents with solvers sponsoring gas and performing swaps.
* **Lazy deployment to enable cross-chain intents even if the user has no account on the target chain.** Omni Account is “everything everywhere all at once” for the user. In the background, Rhinestone’s infrastructure abstracts away the complexity of executing transactions on new chains whilst ensuring assets moved to the new chain are instantly spendable on any other chain.
[Getting Started](/omni-account/getting-started "Getting Started")
---
# Welcome – Rhinestone Docs
Overview
Why Rhinestone
Welcome
=======
Rhinestone is a programmable and open platform for chain-abstracted Smart Accounts. [ERC-7579 (opens in a new tab)](https://erc7579.com/)
, the leading shared standard for Modular Smart Accounts, underpins our infrastructure and tooling, making it the only account vendor-agnostic smart wallet platform. ERC-7579 is now supported by all significant Smart Accounts, including Safe, Biconomy Nexus, ZeroDev Kernel, Magic’s Newton, Thirdweb, Trust Wallet, OKX, and many more. With [EIP-7702 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-7702)
around the corner (via [Pectra (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-7600)
) EOAs will soon be welcomed to the Rhinestone platform.
Build with Rhinestone to maximize distribution, ensure broader ecosystem interoperability, and significantly enhance your product UX with the [deepest library](/module-sdk)
of Smart Account features ([Modules](/overview/modules)
) and native intent-based chain abstraction.
Supported accounts[](#supported-accounts)
------------------------------------------
[Safe](https://safe.global/)
[ZeroDev Kernel](https://zerodev.app/)
[Biconomy Nexus](https://biconomy.io/)
[Magic Newton](https://magic.link/)
[Thirdweb](https://thirdweb.com/)
[OKX](https://www.okx.com/)
[Trust Wallet](https://trustwallet.com/)
Chain Abstraction[](#chain-abstraction)
----------------------------------------
Rhinestone’s chain abstraction system utilizes Smart Account Modules and an intent-based settlement layer built on Across to chain abstract any ERC-7579 smart account and Smart EOAs. This system prioritizes programmability, interoperability, and self-sovereignty.
Dive into a deeper overview of [Omni Account](/omni-account)
.
Modular Smart Accounts[](#modular-smart-accounts)
--------------------------------------------------
Smart Accounts are the future of Ethereum. They drastically enhance application UX with features like gas abstraction, batching, and passkeys. They also provide critical security features to enable any user to be truly self-sovereign, including recovery, multi-sig, and more.
The power of Smart Accounts lies in their programmability. Rhinestone is a leader in Modular Smart Accounts and has played an integral role in building and defining key technical primitives to unlock smart accounts as the next open platform for innovation. [Rhinestone’s infrastructure (opens in a new tab)](https://blog.rhinestone.wtf/rhinestone-protocol-1-0-hits-mainnet-695469dce425)
enables anyone to extend the feature set of any ERC-7579 account.
Our stack[](#our-stack)
------------------------

Omni Account
An intent-powered system that transforms any ERC-7579 accounts (and EOAs) into chain abstracted accounts
[Open](/omni-account)

ModuleSDK
A TypeScript library for using Smart Account Modules. The ModuleSDK supports any ERC-7579 account and is built to be used with existing AA SDKs.
[Open](/module-sdk)

ModuleKit
The fastest way to start building a smart account module is by using ModuleKit, a development kit for building and testing account-agnostic modules.
[Open](/modulekit)
[Getting started](/overview/getting-started "Getting started")
---
# ModuleSDK – Rhinestone Docs
SDK
Overview
ModuleSDK
=========
**A TypeScript library for using smart account modules in applications**
Use the ModuleSDK to integrate smart account modules into your application. It allows you to easily install and uninstall modules for any [ERC-7579 (opens in a new tab)](https://erc7579.com/)
account, interact with and use modules using dedicated helper utilites and can be used alongside existing account SDKs such as [permissionless.js (opens in a new tab)](https://www.npmjs.com/package/permissionless)
, [Biconomy (opens in a new tab)](https://www.npmjs.com/package/@biconomy/account)
, [ZeroDev (opens in a new tab)](https://docs.zerodev.app)
and many more.
The Module SDK is split into two components:
* **Accounts** - Account-related helpers, like installing and uninstalling modules
* **Modules** - Integrated core modules with helpers for their installation and usage

Current accounts supported by the Module SDK include:
[ERC-7579](https://erc7579.com/)
[Safe](https://safe.global/)
[Kernel (v3)](https://zerodev.app/)
[Biconomy (v3)](https://biconomy.io/)
Core Modules[](#core-modules)
------------------------------
| Module | Description |
| --- | --- |
| Ownable Validator | This module enables an EOA as a signer for a smart account. It is ideal for product use cases where users are expected to be crypto-native and possess an EOA wallet. Alternatively, it can be combined with MPC providers [embedded signers (opens in a new tab)](https://docs.pimlico.io/permissionless/how-to/signers)
. |
| Passkeys / Webauthn Validator | Enables a passkey as a signer on a smart account, allowing users to sign cryptographic messages with their biometrics via the secure enclave of their device or use a preferred password manager. |
| Social Recovery | This allows users to specify one or multiple guardians with an m or n threshold for account recovery. The user sets a guardian by expressing the public address of the guardian. |
| Multifactor Authentication (MFA) | The module is a multiplexer, which allows developers to compose any set of signer modules together. For example, passkeys can be set as the main signer, but passkeys and an ECDSA validation scheme are required when making high-value transfers. |
| Module Registry Adapter | The Module Registry enforces security guarantees and standards when installing a module on a smart account. The Module Registry stores onchain security attestations made by independent auditors. When installing a new module on the account, the Module Registry Adapter queries the Module Registry and checks that pre-set security thresholds have been met. |
| Scheduled Transfers | Allows for automated transfers to be triggered on a smart account. The user can create a schedule on which a relayer will execute the transfers based on some parameters, like frequency or number of repetitions. |
| Scheduled Orders | Allows for automated token swaps to be triggered on a smart account. The user can create a schedule on which a relayer will execute the swaps based on some parameters, like frequency or number of repetitions. |
| Auto Save | This is a more opinionated version of the Schedule Transfer module. It allows a user to automatically transfer a set percentage of any received token to a target ERC-4626 yield-bearing vault. |
| Deadman Switch | Recover an account after a specified inactive period. The user sets the target recovery address (this could be another smart account or a typical EOA wallet) along with the required period of inactivity. |
| Ownable Executor | Create a hierarchy ownership structure across smart accounts. This module allows one smart account to have execution rights on another smart account. The execution rights can trigger any transaction with the owner account paying for gas. This can enable automated relationships between DAOs and sub-DAOs or main accounts to sub-accounts. |
| Cold Storage Hook | Creates timelock and transfer restrictions. It restricts execution on the account in two ways: 1) a timelock period and 2) transfers are limited to just one address. |
| Flash Loan | A module base found in [ModuleKit](/modulekit)
allows developers to easily create executor modules that tap into flash loan capabilities, as described in ERC-3156. This could be used to create a peer-to-peer rental system without needing an escrow contract or over-collateralization. |
| Hook Multiplexer | An opinionated router for combining multiple hook modules. Hooks are modules that are triggered before or after execution and can be used to enforce certain smart account behavior. Some examples of hooks include spending limits, white/blacklists, and more. This is an important module for accounts with only one global hook slot. |
Useful starting resources[](#useful-starting-resources)
--------------------------------------------------------
* Blog: [Part 1: Modular Account Abstraction for Everyone Else (opens in a new tab)](https://blog.rhinestone.wtf/part-1-modular-account-abstraction-for-everyone-else-84567422bc46)
* Blog: [App-layer Innovation with Modular Smart Accounts (opens in a new tab)](https://blog.rhinestone.wtf/app-layer-innovation-with-modular-smart-accounts-8a9d7030c908)
* [List of modules (opens in a new tab)](https://erc7579.com/modules)
* Learn more about [modules](/overview/modules)
* To build your own modules, check out [ModuleKit](/modulekit)
[Getting Started](/module-sdk/getting-started "Getting Started")
---
# Build Modules – Rhinestone Docs
Build Modules
Overview
Build Modules
=============
**A development kit for building smart account modules**
ModuleKit aims to make it simple for any developer to build a module that works across all ERC-7579 compliant accounts, including Safe, ZeroDev’s Kernel V3, Biconomy’s Nexus, and many more.

ModuleKit has several tools to help during the development lifecycle of a module:
**Build**
* **Standardized interfaces and templates**: These ensure that your module is compatible with all the major account implementations and that it's extremely easy to get started.
* **Third-party integrations**: ModuleKit has a library of integrations and pre-built conditions for execution to make building powerful modules seamless.
**Test**
* **Testing frameworks**: These are out-of-the-box testing setups with in-built unit and integration tests. They allow developers to easily test modules against different account implementations and abstract away the complexities of the entire ERC-4337 flow.
* **Helper utilities**: These utilities improve the developer experience, such as calculating gas consumption (including on L2s) and validating that a module conforms to the ERC-4337 rules.
**Deploy**
* **Deployment script**: The ModuleKit comes with a helper contract that allows you to easily deploy a module and register it in the Module Registry.
### Supported Accounts[](#supported-accounts)
[ERC-7579](https://erc7579.com/)
[Safe](https://safe.global/)
[Kernel (v3)](https://zerodev.app/)
[Biconomy (v3)](https://biconomy.io/)
### Supported Module Types[](#supported-module-types)

Validators
Determine whether a transaction is valid and should be executed

Executors
Create executions on the account with custom logic

Hooks
Enforce conditions or execute logic pre- or post-execution

Fallbacks
Extend the account logic to add more functionality into the account
Useful starting resources[](#useful-starting-resources)
--------------------------------------------------------
* Blog: [Introducing ModuleKit (opens in a new tab)](https://blog.rhinestone.wtf/introducing-modulekit-b5a0737e228e)
* Blog: [ModuleKit deep dive (opens in a new tab)](https://blog.rhinestone.wtf/modulekit-deep-dive-ad84ee0797c6)
* [Module examples (opens in a new tab)](https://github.com/rhinestonewtf/core-modules)
* List of open-source modules and resources: [awesome-modular-accounts repo (opens in a new tab)](https://github.com/rhinestonewtf/awesome-modular-accounts)
* Learn more about [modules](/overview/modules)
* To use existing modules, check out the [Module SDK](/module-sdk)
[Getting Started](/build-modules/getting-started "Getting Started")
---
# Getting started – Rhinestone Docs
Omni Account
Getting Started
Getting started
===============
The easiest way to use the Rhinestone Orchestrator is to use the Orchestrator SDK. It is a modern typescript environment that makes it easy to interact with the Orchestrator to create a user account, get a meta intent path, submit a meta intent, get its' status and more.
If you want to interact with the Orchestrator directly, you can use the [Orchestrator API (opens in a new tab)](https://orchestrator.api.rhinestone.wtf/api-docs/)
.
Installation[](#installation)
------------------------------
Install the orchestator SDK using a package manager of your choice.
npmpnpmyarnbun
npm i @rhinestone/orchestrator-sdk
Quick Start[](#quick-start)
----------------------------
// create the orchestrator client to start interacting with the service
const orchestrator = getOrchestrator(orchestratorApiKey);
// create a new user account
const userId = await orchestrator.createUserAccount(
accountAddress,
[firstChain.id, secondChain.id]
);
For a complete tutorial to create a new Omni Account, see [tutorial 1](/omni-account/tutorial-1)
, to send your first intent, see [tutorial 2](/omni-account/tutorial-2)
and for a tutorial on using an existing Omni Account, see [tutorial 3](/omni-account/tutorial-3)
.
[Overview](/omni-account "Overview")
Omni Account Demo
---
# Architecture – Rhinestone Docs
Omni Account
Architecture
Overview
Architecture
============
High Level Components[](#high-level-components)
------------------------------------------------

The Omni Account system has integrated the Across Protocol as the first Settlement Layer. The Omni Account intent data structure mirrors Across, making it dead easy for Across Relayers to integrate. For more on why we choose Across, visit the [Settlement Layer section](/omni-account/architecture/settlement-layer)
.
Side-by-Side the “Chain Abstraction Stack”[](#side-by-side-the-chain-abstraction-stack)
----------------------------------------------------------------------------------------
Omni Account sits squarely in the permission layer of the Chain Abstraction Stack, allowing users to express intents, orchestrating resource locks, and permissioning solvers to execute these intents. The Across ecosystem currently covers the solver, clearing, and settlement layers. Across Relayers listen to our [Meta Intents](/omni-account/meta-intents)
, fill, settle, and rebalance via the [Across Settlement Layer](/omni-account/architecture/settlement-layer)
.

High-level Across Flow Diagram[](#high-level-across-flow-diagram)
------------------------------------------------------------------

1. The user interacts with an onchain app, which sends a [Meta Intent](/omni-account/meta-intents)
to the Orchestrator
2. The Orchestrator propagates the Meta Intent to a solver network (Across Relayers)
3. The winning Relayer (X) fills the Meta Intent via the Across (Destination) Spokepool
4. Destination Spokepool sends funds and (optionally) makes an execution via the Omni Account on the destination chain
At this point, the user experience ends, yet funds have not been withdrawn from the origin chain Omni Account. The **second half of the flow is the claim event:**
5. Relayer triggers the claim event on the origin chain Omni Account
6. Omni Account releases funds to the Across (Origin) Spokepool
7. Across settles with the Relayer after matching the origin chain deposit event with the correct destination chain prefill event.
Omni Account Demo[Omni Account](/omni-account/architecture/omni-account "Omni Account")
---
# Omni Account – Rhinestone Docs
Omni Account
Architecture
Omni Account
Omni Account
============
Omni Account consists of two onchain components: 1) an ERC-7579 compliant Smart Account, and 2) the Omni Account Modules, including the Resource Lock Hook and the Settlement Executor Modules.

Supported Accounts[](#supported-accounts)
------------------------------------------
* Safe, via the Safe7579 Adapter
* Biconomy’s Nexus Account
* Magic’s Newton Account
Resource Lock Hook[](#resource-lock-hook)
------------------------------------------
The Resource Lock Hook is a Smart Account Module that inspects all account execution and checks a simple invariant: will the execution, and any approvals made during execution, reduce the account balance below the resource locked amount? If false, the Resource Lock Hook allows the transaction to pass. If true, the transaction will only pass with a signature from the Orchestrator.
Core features of the Resource Lock Hook:
* Simple invariant check that the post-execution balance of the account will not be less than the locked balance
* **Tracks and manages token approvals** during transaction execution, ensuring that any increases or decreases in allowances are properly accounted for when verifying the locked balance invariant.
* **Pre-validation hooks** allow the Resource Lock to run checks before UserOp and signature validation, ensuring that an account has enough unlocked balance to pay paymasters or use permit/permit2 signatures.
* Data structures and interfaces natively support The Compact, an open-source escrow contract for cross-chain intents, **enabling EIP-7702 support**.
Settlement Executors[](#settlement-executors)
----------------------------------------------
The Settlement Executors consist of two Modules:
1. **Destination Executor:** This module interprets and executes arbitrary call data from the Across Spokepool when the Relayer fills an intent with an injected execution. This allows for any action to be appended to the bridge intent – e.g., swap, lend, borrow, liquidate, LP, buy NFT, etc.
2. **Origin Executor:** Provides verifiable “onchain rails” for claim requests made by the Relayer after filling a user's intent on the destination chain. The Origin Executor receives, verifies and processes these claim requests on behalf of the user. A claim request includes the user’s signatures, a token transfer, and the claim co-signature from the Orchestrator.
[Overview](/omni-account/architecture/overview "Overview")
[Orchestrator](/omni-account/architecture/orchestrator "Orchestrator")
---
# Settlement Layer – Rhinestone Docs
Omni Account
Architecture
Settlement Layer
Settlement Layer
================
Why Across?[](#why-across)
---------------------------
* **Existing Solver Network and Liquidity**: Across has a mature marketplace of solvers (or Relayers) that can be used to scale this new form of cross-chain intents.
* **Integrated Solver Netting**: Across supports automatic solver netting by allowing fillers to claim funds using liquidity provider (LP) funds on any chain. LP yields are regulated based on the net assets entering and leaving a chain, ensuring efficient use of available liquidity.
* **Trustless Settlement Process**: Across already has a mature infrastructure that utilizes optimistic proofs to secure the settlement process in a trustless way.
* **Support for Arbitrary Calls on Target Chain**: Across allows intents to encode message data with the fill transaction, which can be used to make arbitrary calls on the target chain. Our system uses this feature to call the appropriate functions on the user’s target chain Smart Account. It also allows the solvers to provide gas abstraction services and act as independent ERC-4337 bundlers (if the call is made to an entrypoint contract).
* **Exclusive Relayers**: Across allows intents to specify exclusive relayers while depositing. This allows our system to use any preferred solver auction while benefiting from exclusive relayers. We can select our solvers through our auction process while leveraging Across for settlement. This flexibility ensures that we maintain control over the selection of solvers while benefiting from Across’ robust settlement infrastructure.
For more details on Across, [visit their docs (opens in a new tab)](https://docs.across.to/introduction/what-is-across)
.
Future Settlement Layers[](#future-settlement-layers)
------------------------------------------------------
Omni Account can support multiple Settlement Layers per Resource-Locked balance. If you operate a Settlement Layer and are looking for a partner in the Permission Layer, [please contact us](mailto:kurt@rhinestone.wtf)
.
[Orchestrator](/omni-account/architecture/orchestrator "Orchestrator")
[Solvers](/omni-account/architecture/solvers "Solvers")
---
# Orchestrator – Rhinestone Docs
Omni Account
Architecture
Orchestrator
Orchestrator[](#orchestrator)
------------------------------
The Orchestrator acts as a trusted entity to enforce resource locks. It listens to user intents, propagates them to a solver network, creates resource lock allocations for counterparties (e.g., the Across Relayers), and facilitates the claim process via the chosen settlement layer (e.g., Across).
The Orchestrator is **trustless from the end user’s perspective**. It can only interact with the Smart Account via the [Settlement Executors](/omni-account/architecture/settlement-layer)
, providing onchain verifiable execution paths. The Settlement Executors utilize the Across Protocol as the sole settlement layer, inheriting Across’ battle-tested optimistic proof system.
The Orchestrator does not present a liveness and censorship risk to users. An onchain escape hatch can be activated through an onchain call without a dependency on Rhinestone or the Orchestrator.

Orchestrator Features[](#orchestrator-features)
------------------------------------------------
* **Creates and maintains (Omni) Account Clusters for the user**. There are two types of user accounts in the Orchestrator; 1) chain accounts, a tuple of chainID and address, that represents a user on one particular chain, and 2) a user account, which is a group of chain accounts assigned to a unique user ID.
* **Provides API endpoints for coordination between users, applications, and solvers**.
* **Performs resource lock accounting and intent queuing** to ensure users can not overspend or double-spend a locked balance.
* **Provides the optimal routes for cross-chain intents**, including optimal origin chain, input tokens, and swap routes. This can be upgraded to consume optimal paths from the client or solver.
* **Performs security checks on the Smart Account** to ensure the code is not malicious. These security checks include verifying the Smart Account is a compatible implementation, has the right modules installed, has not triggered the onchain escape hatch, and is not using an unknown proxy contract.
* **Injected execution** for any onchain action to be appended to a cross-chain intent. This enables destination chain userops or any other smart contract interaction (such as a swap via the Li.Fi API).
Orchestrator Trust[](#orchestrator-trust)
------------------------------------------
**Currently, solvers are the only party that must trust the Orchestrator**. They must trust that the accounting and security checks are performed correctly, preventing users from double spending the system.
**The solver does not need to trust the Orchestrator for liveness and settlement guarantees**. When the Meta Intent is passed to an Across Relayer, they also receive a co-signed claim request within the same payload. Any Relayer can trigger the claim process via the origin chain Omni Account, but only the Relayer with a matching fill event on the Across Protocol can be repaid and receive the fees.
Path to Decentralization[](#path-to-decentralization)
------------------------------------------------------
The Orchestrator only co-signs transactions involving locked funds (unlike a co-signer approach that requires signing every Smart Account interaction), making the system significantly more straightforward to operate and maintain. This reduced complexity creates a more efficient path toward solving the second challenge of verifiably correct sequencing and security checks. The Orchestrator has been built in Rust, and zkVM is being explored to provide a verifiable and trusted computing environment. Our mission is to make the Orchestration layer open and permissionless, and we’ll be publishing research and diving into this with early partners building on resource locks.
Please reach out if you’re interested in becoming an [Orchestration Layer design partner](mailto:kurt@rhinestone.wtf)
.
[Omni Account](/omni-account/architecture/omni-account "Omni Account")
[Settlement Layer](/omni-account/architecture/settlement-layer "Settlement Layer")
---
# Solvers – Rhinestone Docs
Omni Account
Architecture
Solvers
Solvers
=======
Solvers are entities that hold token inventory on each supported chain and fill Meta Intents on behalf of the user. In the Across ecosystem, Solvers are referred to as Relayers. Other chain abstraction systems refer to solvers as “Fillers” or onchain “Market Makers.”
The Omni Account system is built on Across with similar data structures and endpoints to reduce integration complexity for existing Across Relayers.
Features facilitated by Omni Account Solver include:
* **Same chain intents**: Across Relayers can act as paymasters, providing a cheaper and faster alternative to ERC-4337 infrastructure. Omni Account allows developers to transact on any chain with one integration.
* **Solver-based swaps**: If the input token does not equal the output token, the Omni Account system instructs Solvers to provide a swap quote and execution, or the destination chain swap is injected into the Meta Intent.
* **Deterministic and atomic cross-chain intents**: Omni Account Solvers provide deterministic prefills on the destination chain with zero slippage. Atomicity is guaranteed through peripheral contracts that ensure the Solver prefills meet the requirements of the Meta Intent.
To view a reference implementation of a solver, check out our [Rhinestone Relayer repository (opens in a new tab)](https://github.com/rhinestonewtf/rhinestone-relayer)
. The solver is a simple Across solver that has been adapted to be able to fill Omni Account intents. Note that this repo is still a work in progress and better documentation will follow soon.
[Settlement Layer](/omni-account/architecture/settlement-layer "Settlement Layer")
[Meta Intents](/omni-account/meta-intents "Meta Intents")
---
# Meta Intents – Rhinestone Docs
Omni Account
Meta Intents
Meta Intents
============
Omni Account employs an ERC-712 signature envelope to encode complex cross-chain intents into a single signature. This signature contains the cross-chain routes and arbitrary call data to be executed on the destination chain, allowing any intent to atomically execute with the cross-chain transfers once funds hit the target chain. **From the user's perspective, this is one interaction for any intent that happens instantaneously, as if they have transacted on the same chain where their assets reside**.
Meta Intent Detailed Flow Diagram[](#meta-intent-detailed-flow-diagram)
------------------------------------------------------------------------

1. The user interacts with an application / wallet to make a transaction. This Meta Intent is sent to the Orchestrator, which responds with an Order Bundle and any required injected executions.
2. The user signs the Order Bundle (and any injected executions), which is posted to the Orchestrator.
3. The Orchestrator propagates the signed Order Bundle and the Claim Payload (collectively the “Omni Payload” in the chart) to the solver network (Across Relayers).
4. The winning solver fills the Meta Intent via the Across Destination Spokepool and includes arbitrary call data to instruct the Omni Account on the Destination Chain to interact with an onchain application (swap, LP, buy NFT, etc).
5. The Destination Spokepool forwards the funds and the arbitrary calldata to the Destination Omni Account.
6. Omni Account receives funds and processes the calldata to perform destination chain executions.
7. The Across Relayer sends the Claim Payload to the Omni Account on the Origin Chain
8. Origin Chain Omni Account releases funds to the Origin Chain Spokepool.
9. Across Protocol matches the origin chain deposit event with the destination chain prefill event.
Examples[](#examples)
----------------------
Below, you can see examples of the two core objects that clients of OmniAccount use. The first is the `MetaIntent`, which is used to create the initial intent. The second is the `SignedOrderBundle` which is the finalized intent that gets sent to the Orchestrator to be filled by relayers.
const metaIntent: MetaIntent = {
targetChainId: 11155420,
tokenTransfers: [\
{\
tokenAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',\
amount: 2n,\
},\
],
targetAccount: '0xC104087121E4fb804dcAae62DC20A154E4BcB0c5',
targetExecutions: [],
userOp: {
accountGasLimits:
'0x00000000000000000000000000065576000000000000000000000000000a28cd',
callData:
'0xd9ed0e8f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000007579011ab74c46090561ea277ba79d510c6c00ff00000000000000000000000000000000000000000000000000000000000001400000000000000000000000007579ee8307284f293b1927136486880611f20002000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b78f19946b878eeccd0f5dbe1cd99e6f00753827000000000000000000000000000000000000000000000000000000000000038415cca6380000000000000000000000007579ee8307284f293b1927136486880611f2000200000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e1058634834e01038cadbae8208bfff81b1ede5100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a90f831363708b32a3f1502165253e0210cf680d0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a90f831363708b32a3f1502165253e0210cf680d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000803a5be8cb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000333034e9f539ce08819e12c1b8cb29084d0000000000000000000000008a310b9085faf5d9464d84c3d9a7be3b28c9453100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002483da3a338895199e5e538530213157e931bf0600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b78f19946b878eeccd0f5dbe1cd99e6f0075382700000000000000000000000000000000000000000000000000000000000003e4e9ae5c530100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000e1058634834e01038cadbae8208bfff81b1ede5100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018467277f95000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000679a196a0000000000000000000000005fd84259d66cd46123540766be93dfe6d43130d70000000000000000000000008a310b9085faf5d9464d84c3d9a7be3b28c9453100000000000000000000000000000000000000000000000000000000000000004e491bc8f3c10e5d54f7325011b39ce9ed48762d60e96b4def69dfcbd70878b70000000000000000000000000000000000000000000000000000000000aa37dc000000000000000000000000e1058634834e01038cadbae8208bfff81b1ede51000000000000000000000000000000000000000000000000000000000000004163865951bec03f8f22858deb66b921c01dde9d74a2862d3489ced8ec1078ab4430d043b131f755774a9b4f38d1a75846bfbe71662ad46c4715d9769bf97ef0f31b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fd84259d66cd46123540766be93dfe6d43130d7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
initCode:
'0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec671688f0b90000000000000000000000007579011ab74c46090561ea277ba79d510c6c00ff0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000844fff40e164ae0962cd7626584923f878d00a7679da2f043664b59f8c7fb4858a5d2bdf5200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
gasFees:
'0x0000000000000000000000000010c8e00000000000000000000000000010ca32',
nonce:
16516225664291178990882509495406907701463033268145267606555553463177186377728n,
paymasterAndData:
'0x0000000000000039cd5e8aE05257CE51C473ddd10000000000000000000000000000ea6000000000000000000000000000004e20000000679a1bc3000000000000e9e9e384b07af950046c7b162275de1bb2e8aee24f6d4a38e245545cfa2c504a00a6714fa669a9292c039bcb3ffe88591a622de6710bca2f79b594541796be721c',
preVerificationGas: 425387n,
sender: '0xC104087121E4fb804dcAae62DC20A154E4BcB0c5',
signature:
'0x01afb75d71005f82cfb4ef4bb2f2289adf0b24e5f2579e96f8127098d22e904a5c8aa5a07c89ec7a4f5cd76a0b04ebd6c2caebc63d6f2406a11d4f9172b803531c',
},
}
const signedOrderBundle: SignedOrderBundle = {
settlement: {
orchestrator: '0x8a310b9085faF5d9464D84C3d9a7BE3b28c94531',
recipient: '0xC104087121E4fb804dcAae62DC20A154E4BcB0c5',
settlementContract: '0x20038b572633E45F3aB5b1a46CB85D0D241b80D8',
targetChainId: 11155420,
fillDeadline: 1738152417,
lastDepositId:
'105405913354356643216620936582641520508443801435124451135568189572898599948141',
},
acrossTransfers: [\
{\
originModule: '0xE1058634834E01038CadbaE8208BFfF81B1Ede51',\
originAccount: '0xC104087121E4fb804dcAae62DC20A154E4BcB0c5',\
targetAccount: '0xC104087121E4fb804dcAae62DC20A154E4BcB0c5',\
originChainId: 84532,\
initiateDeadline: 1769688298,\
maxFee: '0',\
depositId:\
'105405913354356643216620936582641520508443801435124451135568189572898599948141',\
originTransfer: [\
{\
tokenAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\
amount: '2',\
},\
],\
targetTransfer: [\
{\
tokenAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',\
amount: '2',\
},\
],\
userSignature:\
'0x2483da3a338895199e5e538530213157e931bf06705444f371ded295663f88c5fdf4136e0c7fc2bc57640d58a808d911846f1d0c77fe0270830a69e4adf71c2b08b5924547bf9aca0d0856b772500d823cd518d51c',\
},\
],
targetChainExecutions: { executions: [] },
userOp: {
accountGasLimits:
'0x00000000000000000000000000065576000000000000000000000000000a28cd',
callData:
'0xd9ed0e8f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000041675c099f32341bf84bfc5382af534df5c7461a000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000007579011ab74c46090561ea277ba79d510c6c00ff00000000000000000000000000000000000000000000000000000000000001400000000000000000000000007579ee8307284f293b1927136486880611f20002000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b78f19946b878eeccd0f5dbe1cd99e6f00753827000000000000000000000000000000000000000000000000000000000000038415cca6380000000000000000000000007579ee8307284f293b1927136486880611f2000200000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000e1058634834e01038cadbae8208bfff81b1ede5100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a90f831363708b32a3f1502165253e0210cf680d0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a90f831363708b32a3f1502165253e0210cf680d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000803a5be8cb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000333034e9f539ce08819e12c1b8cb29084d0000000000000000000000008a310b9085faf5d9464d84c3d9a7be3b28c9453100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002483da3a338895199e5e538530213157e931bf0600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b78f19946b878eeccd0f5dbe1cd99e6f0075382700000000000000000000000000000000000000000000000000000000000003e4e9ae5c530100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000260000000000000000000000000e1058634834e01038cadbae8208bfff81b1ede5100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000018467277f95000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000679a196a0000000000000000000000005fd84259d66cd46123540766be93dfe6d43130d70000000000000000000000008a310b9085faf5d9464d84c3d9a7be3b28c9453100000000000000000000000000000000000000000000000000000000000000004e491bc8f3c10e5d54f7325011b39ce9ed48762d60e96b4def69dfcbd70878b70000000000000000000000000000000000000000000000000000000000aa37dc000000000000000000000000e1058634834e01038cadbae8208bfff81b1ede51000000000000000000000000000000000000000000000000000000000000004163865951bec03f8f22858deb66b921c01dde9d74a2862d3489ced8ec1078ab4430d043b131f755774a9b4f38d1a75846bfbe71662ad46c4715d9769bf97ef0f31b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fd84259d66cd46123540766be93dfe6d43130d7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
initCode:
'0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec671688f0b90000000000000000000000007579011ab74c46090561ea277ba79d510c6c00ff0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000844fff40e164ae0962cd7626584923f878d00a7679da2f043664b59f8c7fb4858a5d2bdf5200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
gasFees:
'0x0000000000000000000000000010c8e00000000000000000000000000010ca32',
nonce:
16516225664291178990882509495406907701463033268145267606555553463177186377728n,
paymasterAndData:
'0x0000000000000039cd5e8aE05257CE51C473ddd10000000000000000000000000000ea6000000000000000000000000000004e20000000679a1bc3000000000000e9e9e384b07af950046c7b162275de1bb2e8aee24f6d4a38e245545cfa2c504a00a6714fa669a9292c039bcb3ffe88591a622de6710bca2f79b594541796be721c',
preVerificationGas: 425387n,
sender: '0xC104087121E4fb804dcAae62DC20A154E4BcB0c5',
signature:
'0x01afb75d71005f82cfb4ef4bb2f2289adf0b24e5f2579e96f8127098d22e904a5c8aa5a07c89ec7a4f5cd76a0b04ebd6c2caebc63d6f2406a11d4f9172b803531c',
},
targetExecutionSignature: '0x',
}
Lazy Deployment Flow Diagram[](#lazy-deployment-flow-diagram)
--------------------------------------------------------------
If Ethereum’s horizontal scaling strategy plays out as expected, the first time a user interacts with an onchain app will likely be their first interaction with a new chain. Therefore, a scalable Chain Abstraction system must enable instant cross-chain intents even if the user does not have an account on the destination chain. Omni Account provides this functionality with a just-in-time deployment flow via the ERC-4337 entrypoint contract. With one signature, the user authorizes the intent and the deployment of the Omni Account. **The solver fronts the gas for deployment and fills the intent all in one!**

Omni Account enables instant and atomic cross-chain intents, even when the user does not have a Smart Account on the destination chain. When the Relayer fills the Meta Intent, a userOp payload is included, which is handled by a trampoline contract that deploys the account via the ERC-4337 EntryPoint contract.
When the user’s Smart Account is deployed on the new destination chain, the Omni Account Modules are initialized, and the injected execution is executed. This initialization ensures that any assets held on the new chain are also Chain Abstracted and ready to be used on any other network.
[Solvers](/omni-account/architecture/solvers "Solvers")
[Further Use Cases](/omni-account/use-cases "Further Use Cases")
---
# Getting started – Rhinestone Docs
Overview
Getting started
Getting started
===============
Rhinestone’s tools and services make building with modular smart accounts a breeze.
Use ModuleSDK to install your first module[](#use-modulesdk-to-install-your-first-module)
------------------------------------------------------------------------------------------
If you are new to smart account modules, we recommend checking out the [ModuleSDK](/module-sdk)
, a typeScript library for integrating modules into applications and wallets.
Get started with one of our [Core Modules](/module-sdk)
:

ModuleSDK is built to be used alongside existing account abstraction SDKs, such as permissionless.js, Biconomy SDK, ZeroDev SDK, and userop.js. Follow this [simple tutorial](/module-sdk/tutorial-1)
to install your first module.
Supported smart accounts[](#supported-smart-accounts)
------------------------------------------------------
[Safe](https://safe.global/)
[ZeroDev Kernel](https://zerodev.app/)
[Biconomy Nexus](https://biconomy.io/)
[Magic Newton](https://magic.link/)
[Thirdweb](https://thirdweb.com/)
[OKX](https://www.okx.com/)
[Trust Wallet](https://trustwallet.com/)
[Why Rhinestone](/overview "Why Rhinestone")
[Account Abstraction](/overview/account-abstraction "Account Abstraction")
---
# Use Cases Beyond Chain Abstraction – Rhinestone Docs
Omni Account
Further Use Cases
Use Cases Beyond Chain Abstraction
==================================
**The vision for Omni Account goes beyond solving Ethereum’s fragmented liquidity**. Omni Account allows users to express any intent to a specialized execution layer without the strict requirement of atomized settlement. Our vision is to enable application-specific Orchestrators to plug into Omni Account and process intents against a single resource-locked balance per user.
From debit card integrations to offchain order books, Omni Account’s architecture is designed to make resource locks fully open and composable to any developer. One single balance with infinite possibilities — **everything everywhere, all at once**.
The underlying substrate between these application-specific Orchestrators, of which Chain Abstraction is one, is a single source of truth for intents in flight and the sequencing. If you’re interested in learning more and becoming a design partner for the Orchestration Layer, please [reach out](mailto:kurt@rhinestone.wtf)
.
Debit Card Integration[](#debit-card-integration)
--------------------------------------------------
Like chain-abstracted balances, fiat debit systems linked to on-chain assets require credible commitments that funds will be available for settlement. This ensures that after supplying fiat via traditional payment rails, onchain assets are accessible for settlement.
Margin Accounts[](#margin-accounts)
------------------------------------
Create margin accounts with collateral management, borrowing, and risk reduction features. By integrating onchain credit systems, users can leverage their collateral without moving funds into third-party contracts, reducing risk exposure and improving capital efficiency.
Credit Systems[](#credit-systems)
----------------------------------
* Offchain Credit
* Without resource locking, collateral must be placed in an escrow contract, which must be actively managed to prevent liquidation or meet interest payments.
* With resource locking, collateral stays within the existing Smart Account, offering:
* Higher capital management efficiency.
* Removal of shared bad debt risk.
* Protection from protocol hacks.
* Greater flexibility in how collateral can be used (within defined boundaries).
* Onchain Credit
* It provides the same benefits as offchain credit systems, but all transactions and collateral management remain on-chain. This creates transparent, trustless credit systems.
Offchain Order Books[](#offchain-order-books)
----------------------------------------------
* Funds are **resource-locked** in the user's Smart Account while trades are placed in an offchain order book. Settlement happens on-chain, allowing trading at Web2 speeds while ensuring the assets are available for on-chain settlement when needed.
* **With Margin Features**: Combine offchain order book trading with onchain credit, enabling more complex financial products like margin trading without moving assets to third-party contracts.
Advanced access controls and permissions[](#advanced-access-controls-and-permissions)
--------------------------------------------------------------------------------------
* Lock assets in a Smart Account without segregating them into different wallets. This provides high security, allowing assets to be frozen or limited in terms of withdrawal while still keeping them in a single account.
* Implement spending limits with advanced conditions (e.g., spending under X requires a passkey; above X requires multisig authorization).
* Prevent certain assets, like NFT-backed tokens, from leaving the wallet without strict conditions.
* Create accounts with specific access rights to certain tokens and balances, allowing for unique governance setups (e.g., subDAOs, multisig, DApps, or third-party integrations).
Locking Funds for Protocol Use[](#locking-funds-for-protocol-use)
------------------------------------------------------------------
Lock funds directly in a Smart Account for use in specific decentralized protocols, such as zkP2P lending or other decentralized liquidity markets. This ensures that funds remain secure and available for protocol needs without moving them to external contracts.
DEXs / DeFi Protocols Accessing Cross-Chain Liquidity[](#dexs--defi-protocols-accessing-cross-chain-liquidity)
---------------------------------------------------------------------------------------------------------------
A DEX or DeFi protocol built on one chain can request a Smart Account to install resource lock modules. This enables the app to access liquidity from all the user’s accounts across multiple chains without moving funds or opening new accounts.
[Meta Intents](/omni-account/meta-intents "Meta Intents")
[1: Create a new Omni Account](/omni-account/tutorial-1 "1: Create a new Omni Account")
---
# Account Abstraction 101 – Rhinestone Docs
Overview
Account Abstraction
Account Abstraction 101
=======================
Account Abstraction is an umbrella term that refers to adding more flexibility to the validation phase of Ethereum transactions. Currently, externally owned accounts (EOAs) are “active” in that they can initiate transactions and pay gas for execution. Contract accounts are “passive” — they can be triggered by an EOA, which pays the gas for execution. However, the upside of contract accounts is that they are programmable and can execute arbitrary logic stored in the code of the account. Therefore, Account Abstraction refers to the transition of contract accounts from being passive to becoming active, allowing a smart contract to become the user account and initiate transactions without reliance on an EOA for triggering the transaction or paying the gas for execution.
Until recently, smart accounts relied on an EOA relay to initiate transactions – for example, the Gelato Network relay used by Safe’s smart account. ERC-4337 removes the reliance on EOA relays by introducing the concept of userOperations (smart account transactions), an EntryPoint contract for userOperations and Bundlers (entities that take userOperations and submit them to the entrypoint contract). ERC-4337 also introduces the concept of a Paymaster, an entity that funds gas for EVM execution and has the flexibility to define bespoke pay-back mechanisms for the user. For example, the Paymaster could allow the user to pay gas in any ERC-20 or create unique gas subsidy policies.
For more on ERC-4337 please refer to the [official proposal (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-4337)
or check out our partners:
* [Pimlico (opens in a new tab)](https://www.pimlico.io/)
* [Biconomy (opens in a new tab)](https://biconomy.io/)
* [ZeroDev (opens in a new tab)](https://zerodev.app/)
* [Safe (opens in a new tab)](https://safe.global/)
* [Stackup (opens in a new tab)](https://www.stackup.sh/)
* [Candide (opens in a new tab)](https://www.candidewallet.com/)
The layers of Account Abstraction[](#the-layers-of-account-abstraction)
------------------------------------------------------------------------
There are three layers to Account Abstraction:
* Infrastructure for smart account execution [(ERC-4337) (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-4337)
* Smart accounts and modules [(ERC-7579) (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-7579)
* User interface for controlling a smart account (wallets)
Motivation for Account Abstraction[](#motivation-for-account-abstraction)
--------------------------------------------------------------------------
We can’t say it better than Vitalik himself – [The Three Transitions (opens in a new tab)](https://vitalik.eth.limo/general/2023/06/09/three_transitions.html)
Account Abstraction terminology[](#account-abstraction-terminology)
--------------------------------------------------------------------
* Contract account – A smart contract used for the user’s account.
* Smart account – A contract account that is modular (existing implementations include Safe’s core account and ZeroDev’s Kernel).
* UserOperation – A structure that describes a transaction to be sent on behalf of a user. To avoid confusion, it is not named “transaction”.
* EntryPoint – A singleton contract to execute bundles of UserOperations. Bundlers / Clients whitelist the supported entryPoint.
* Bundler – A node (block builder) that can handle UserOperations, create a valid transaction calling EntryPoint.handleOps(), and add it to the block while it is still valid.
* Paymaster – A contract that can define specific gas policies for specific user operations.
Account Abstraction vs. MPC[](#account-abstraction-vs-mpc)
-----------------------------------------------------------
MPC ≠ Account Abstraction. See [this article (opens in a new tab)](https://spark.litprotocol.com/mass-adoption-of-digital-ownership-and-progressive-self-custody/)
by Lit Protocol, a distributed key management platform.
[Getting started](/overview/getting-started "Getting started")
[Modules explained](/overview/modules "Modules explained")
---
# Modules explained – Rhinestone Docs
Overview
Modules explained
Modules explained
=================
Modules are smart contracts, usually singletons, that extend the functionality of smart accounts. Modular smart accounts allow users and developers to easily change how a smart account works. In order to understand more deeply what is possible with modules (spoiler: a lot), let’s quickly recap the transaction flow of a smart account.
ERC-4337 UserOperation flow[](#erc-4337-useroperation-flow)
------------------------------------------------------------
While there are many different ways of implementing Account Abstraction on Ethereum, Rhinestone is built on top of ERC-4337, which is a standard for implementing Account Abstraction using an alternative mempool and without requiring protocol changes. If you want to read up more about this ERC, view the specs here and an excellent blog post here.
In order to achieve Account Abstraction, ERC-4337 specifies a new type of transaction for this alternative mempool, termed a UserOperation. On a high level, this UserOperation goes through two distinct phases: validation and execution.
### Validation Phase[](#validation-phase)
During the validation phase, the ERC-4337 Entrypoint calls the `validateUserOp` function on the smart account in order for it to determine whether a UserOperation is valid and should be executed. If this function returns 0, then the Entrypoint will consider it valid, if it returns 1 or reverts then the Entrypoint will halt execution there and move on to the next UserOperation.
How exactly signature validation occurs is entirely up to the developer to decide, which enables the possibility of modular validation logic that can be added and replaced at any point.
ERC-4337 places some storage and opcode restrictions on accounts during the validation phase, which impacts how validation modules can be built. Read more about these below.
### Execution Phase[](#execution-phase)
If the validation phase was successful, then the Entrypoint will call the smart account again, this time with the calldata provided in the UserOperation. While the Entrypoint always calls the `validateUserOp` function during the validation phase, there is no such restriction during execution, meaning that accounts can implement any number of execution functions and the wallet client can choose which exact one to call depending on the transaction intent.
Similar to the validation phase, ERC-4337 does not stipulate how execution occurs, enabling the possibility to build modules for the execution phase.
Module types[](#module-types)
------------------------------
ERC-4337 breaks down the flow of a UserOperation into two distinct phases, validation and execution. The account functionality that is required in either of these two phases differs to that of the other, so there are at least two different types of modules: validators and executors. However, there are at least two more distinct types of modules that perform different functions to the aforementioned: hooks and fallback modules.

Validators
Determine whether a transaction is valid and should be executed

Executors
Create executions on the account with custom logic

Hooks
Enforce conditions or execute logic pre- or post-execution

Fallbacks
Extend the account logic to add more functionality into the account
### Validators[](#validators)
Validators are modules that are called during the validation phase of a UserOperation. This means that their primary function is to verify the signature of a UserOperation and determine whether it is valid and should be executed. As a result, validators are the primary mechanism for enforcing access control on a smart account and are highly security critical.
### Executors[](#executors)
Executors are modules that are called during the execution phase of a UserOperation. They extend the execution logic of the account and thus allow for a more diverse set of actions that the account can natively perform. On top of this, Executors can also be used to automate certain actions that are triggered outside of the regular ERC-4337 execution flow on the account. For example, a user could set up an executor that automatically swaps their tokens when the price of a token reaches a certain threshold.
**Conditional Executors**
Conditional executors extend the capabilities of normal executors, by allowing transactions to be executed based on checks performed by the ComposableConditionManager. This allows for more complex logic to be implemented in executors, such as the ability to execute transactions based on the price of a token or the current gas price. Importantly, these conditions are verified on-chain meaning that a user can be sure that the transaction will be executed only if the condition is met and does not need to trust a third party.
### Hooks[](#hooks)
Hooks are modules that are triggered either before or after execution and can be used to enforce certain behavior. Some examples of hooks include spending limits, restricting tokens that can be transferred, ensuring that contracts that are interacted with are audited, and more.
### Fallbacks[](#fallbacks)
Fallbacks are modules that can add additional functionality into the account. They are called by the fallback function of the account, meaning that they are triggered when a transaction is sent to the account that does not match any of the existing functions on the account. Fallbacks can, for example, implement logic for receiving tokens, allow smart accounts to be used for flashloans or potentially even as a paymaster for sub-accounts.
[Account Abstraction](/overview/account-abstraction "Account Abstraction")
[ERC-7579](/overview/erc-7579 "ERC-7579")
---
# Tutorial 1: Create a new Omni Account – Rhinestone Docs
Omni Account
1: Create a new Omni Account
Tutorial 1: Create a new Omni Account
=====================================
In this tutorial, we will walk through creating a new Omni Account. What this means is that you will create a smart account, in this case a Safe with the Safe7579 module, and add the required modules to transform it into an Omni Account. This will enable the user to lock funds and instantly spend them on any other chain. To send the first intent, check out [tutorial 2](/omni-account/tutorial-2)
.
For this tutorial, we are using `@rhinestone/module-sdk` at the latest version to set up the required modules. We are also using `@rhinestone/orchestrator-sdk` to interact with the Omni Account modules and the backend service, the Orchestrator. Finally, we use `permissionless@^0.2.22` and `viem@^2.21.51` for their account abstraction features. See the full source code for this tutorial in our [module-sdk-tutorials repo (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-tutorials/blob/main/src/orchestrator-sdk/new-account.ts)
.
### Install the packages[](#install-the-packages)
First, install the required dependencies:
npmpnpmyarnbun
npm i @rhinestone/module-sdk @rhinestone/orchestrator-sdk permissionless viem
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import {
encodeModuleInstallationData,
getAccount,
getAccountLockerHook,
getAccountLockerSourceExecutor,
getAccountLockerTargetExecutor,
getOwnableValidator,
RHINESTONE_ATTESTER_ADDRESS,
} from "@rhinestone/module-sdk";
import { createSmartAccountClient } from "permissionless";
import {
toSafeSmartAccount,
ToSafeSmartAccountParameters,
} from "permissionless/accounts";
import {
Chain,
createPublicClient,
createWalletClient,
encodeFunctionData,
erc20Abi,
Hex,
http,
zeroAddress,
} from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { entryPoint07Address } from "viem/account-abstraction";
import { getOrchestrator, getTokenAddress } from "@rhinestone/orchestrator-sdk";
import { erc7579Actions } from "permissionless/actions/erc7579";
import { createPimlicoClient } from "permissionless/clients/pimlico";
### Create the owner for the smart account[](#create-the-owner-for-the-smart-account)
Before creating a new smart account, we will set up the owner. In this example, we just generate a new private key and use the Ownable Validator, but you could use an existing user account, for example using wagmi, or a different authentication method such as passkeys through the Webauthn Validator.
const owner = privateKeyToAccount(generatePrivateKey());
const ownableValidator = getOwnableValidator({
owners: [owner.address],
threshold: 1,
});
### Create the source chain clients[](#create-the-source-chain-clients)
Next, we will create the source chain clients. Since we are interacting with multiple chains, we need to create clients for each of them. First, we will create some for the source chain, ie the chains on which the funds will reside.
const sourcePublicClient = createPublicClient({
chain: sourceChain,
transport: http(),
});
const sourcePimlicoClient = createPimlicoClient({
transport: http(
`https://api.pimlico.io/v2/${sourceChain.id}/rpc?apikey=${pimlicoApiKey}`,
),
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
});
### Create the source chain smart account client[](#create-the-source-chain-smart-account-client)
Now we will create the smart account client for the source chain. This client will be used to interact with the smart account on the source chain and calculates the counterfactual address.
When creating the smart account, we pass a few initial modules. The first of these is the Ownable Validator that we set up before. The remaining ones are the executors and fallback required for Omni Account. In the future, this will be moved into the sdk, but for now we do it manually.
const sourceExecutor = getAccountLockerSourceExecutor();
const targetExecutor = getAccountLockerTargetExecutor();
const smartAccountConfig: ToSafeSmartAccountParameters<
"0.7",
"0x7579011aB74c46090561ea277Ba79D510c6C00ff"
> = {
client: sourcePublicClient,
owners: [owner],
version: "1.4.1",
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
safe4337ModuleAddress: "0x7579EE8307284F293B1927136486880611F20002",
erc7579LaunchpadAddress: "0x7579011aB74c46090561ea277Ba79D510c6C00ff",
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
"0x8a310b9085faF5d9464D84C3d9a7BE3b28c94531", // Mock attester for omni account\
],
attestersThreshold: 1,
validators: [\
{\
address: ownableValidator.address,\
context: ownableValidator.initData,\
},\
],
executors: [\
{\
address: sourceExecutor.address,\
context: sourceExecutor.initData,\
},\
{\
address: targetExecutor.address,\
context: targetExecutor.initData,\
},\
],
fallbacks: [\
{\
address: targetExecutor.address,\
context: encodeModuleInstallationData({\
account: getAccount({\
address: zeroAddress,\
type: "safe",\
}),\
module: {\
...targetExecutor,\
type: "fallback",\
functionSig: "0x3a5be8cb",\
},\
}),\
},\
],
};
const sourceSafeAccount = await toSafeSmartAccount(smartAccountConfig);
const sourceSmartAccountClient = createSmartAccountClient({
account: sourceSafeAccount,
chain: sourceChain,
bundlerTransport: http(
`https://api.pimlico.io/v2/${sourceChain.id}/rpc?apikey=${pimlicoApiKey}`,
),
paymaster: sourcePimlicoClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await sourcePimlicoClient.getUserOperationGasPrice()).fast;
},
},
}).extend(erc7579Actions());
### Create the orchestrator client and account cluster[](#create-the-orchestrator-client-and-account-cluster)
Next, we will create a client to interact with the Orchestrator service that will receive the intents and broadcast them to solvers. Then, we will create an "account cluster". This is the set of accounts that can use the same funds. In our case, this will just be the created smart account address and the two chains we interact with, source and target, but it can be more chains and even multiple smart accounts that pull from the same funds.
const orchestrator = getOrchestrator(orchestratorApiKey);
const userId = await orchestrator.createUserAccount(
sourceSafeAccount.address,
[sourceChain.id, targetChain.id],
);
### Fund the smart account on the source chain[](#fund-the-smart-account-on-the-source-chain)
Now we will fund the smart account on the source chain. In our case, we will use USDC. Later, we will use the funds from source chain to instantly spend on the target chain.
const fundingAccount = privateKeyToAccount(fundingPrivateKey);
const sourceWalletClient = createWalletClient({
chain: sourceChain,
transport: http(),
});
const fundingTxHash = await sourceWalletClient.sendTransaction({
account: fundingAccount,
to: getTokenAddress("USDC", sourceChain.id),
data: encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: [sourceSafeAccount.address, 2n],
}),
});
await sourcePublicClient.waitForTransactionReceipt({
hash: fundingTxHash,
});
### Install the resource lock hook[](#install-the-resource-lock-hook)
Finally, we need to install the resource lock hook on the source chain. This ensures that the funds in the users wallet are locked so that when a solver comes to reclaim them they will still be there. Note that like the other modules, this will be abstracted into the sdk in the future.
const opHash = await sourceSmartAccountClient.installModule({
address: resourceLockHook.address,
initData: encodeModuleInstallationData({
account: getAccount({
address: sourceSafeAccount.address,
type: "safe",
}),
module: resourceLockHook,
}),
type: "hook",
});
await sourcePimlicoClient.waitForUserOperationReceipt({
hash: opHash,
});
Now that the resource lock hook is installed, the account can use any funds on the source chain instantly on a different chain. In the [next tutorial](/omni-account/tutorial-2)
, we will send the first intent.
[Further Use Cases](/omni-account/use-cases "Further Use Cases")
[2: Send your first intent](/omni-account/tutorial-2 "2: Send your first intent")
---
# Tutorial 2: Send your first intent – Rhinestone Docs
Omni Account
2: Send your first intent
Tutorial 2: Send your first intent
==================================
In this tutorial, we will walk through sending your first intent. This assumes that you have already set up an Omni Account, such as by following [tutorial 1](/omni-account/tutorial-1)
or by following [tutorial 3](/omni-account/tutorial-3)
.
For this tutorial, we are using `@rhinestone/module-sdk` at the latest version to set up the required modules. We are also using `@rhinestone/orchestrator-sdk` to interact with the Omni Account modules and the backend service, the Orchestrator. Finally, we use `permissionless@^0.2.22` and `viem@^2.21.51` for their account abstraction features. See the full source code for this tutorial in our [module-sdk-tutorials repo (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-tutorials/blob/main/src/orchestrator-sdk/new-account.ts)
.
### Create the clients for the target chain[](#create-the-clients-for-the-target-chain)
First, we will create the clients required to interact with the target chain.
const targetPublicClient = createPublicClient({
chain: targetChain,
transport: http(),
});
const targetPimlicoClient = createPimlicoClient({
transport: http(
`https://api.pimlico.io/v2/${targetChain.id}/rpc?apikey=${pimlicoApiKey}`,
),
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
});
### Create the target chain smart account client[](#create-the-target-chain-smart-account-client)
Just like on the source chain side, we will create the smart account client on the target chain. This is required since permissionless uses the rpc url passed to the client, for example to determine if an account is already deployed. Make sure to pass the exact same arguments for the initial modules to this client as otherwise the address would be different, leading to this tutorial not working.
const targetSafeAccount = await toSafeSmartAccount({
...smartAccountConfig,
client: targetPublicClient,
});
const targetSmartAccountClient = createSmartAccountClient({
account: targetSafeAccount,
chain: targetChain,
bundlerTransport: http(
`https://api.pimlico.io/v2/${targetChain.id}/rpc?apikey=${pimlicoApiKey}`,
),
paymaster: targetPimlicoClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await targetPimlicoClient.getUserOperationGasPrice()).fast;
},
},
}).extend(erc7579Actions());
### Create the meta intent[](#create-the-meta-intent)
To send an intent that spends the funds from source chain on the target chain, we will first need to create a MetaIntent and the corresponding token transfers. The token transfers tells the relayer which tokens a user wants on the target chain. The remaining fields (except target chain id and account address) can be left empty for now as they will be filled in later.
const tokenTransfers = [\
{\
tokenAddress: getTokenAddress("USDC", targetChain.id),\
amount: 2n,\
},\
];
// create the meta intent
const metaIntent: MetaIntent = {
targetChainId: targetChain.id,
tokenTransfers: tokenTransfers,
targetAccount: targetSafeAccount.address,
targetExecutions: [],
userOp: {
sender: zeroAddress,
nonce: 0n,
initCode: "0x",
callData: "0x",
accountGasLimits: zeroHash,
preVerificationGas: 0n,
gasFees: zeroHash,
paymasterAndData: "0x",
signature: "0x",
},
};
### Get the order path[](#get-the-order-path)
Next, we will get the order path from the orchestrator. What this means is that we get the completed order bundle and a set of injected executions. These executions allow for the orchestrator to inject a set of executions, for example to guarantee the atomicity of the fill. In this step, the orchestrator also figures out where exactly to pull the required funds from (which is easy in this tutorial). Because of this, the user signature only happens after getting the order path so that the user is able to see and needs to confirm (by signing) that they want those tokens to be used.
const { orderBundle, injectedExecutions } = await orchestrator.getOrderPath(
metaIntent,
userId,
);
### Create the target chain UserOperation[](#create-the-target-chain-useroperation)
Now we will create the UserOperation for the target chain. This UserOperation defines what will be executed on the target chain. In this tutorial it is required to use a UserOperation on the target chain since the account is not deployed there yet. However, after an account is deployed, the more efficient executor flow can be used (see the next tutorial).
To create the UserOperation, we will first of all encode the right nonce and define the actions a user wants to execute. These can be arbitrarily complex, but in our case we just transfer the USDC out to an address. Finally, we will also need to provide a set of state overrides. This is so that when the UserOperation is simulated, we can pretend that the USDC is already there even though it will only be there just in time when the transaction is filled by the solver.
const nonce = await getAccountNonce(targetPublicClient, {
address: targetSafeAccount.address,
entryPointAddress: entryPoint07Address,
key: BigInt(
pad(ownableValidator.address, {
dir: "right",
size: 24,
}) || 0,
),
});
const userOpActions = [\
...injectedExecutions.slice(1).map((execution: any) => ({\
to: execution.target,\
value: BigInt(Number(execution.value)),\
data: execution.callData || "0x",\
})),\
{\
to: getTokenAddress("USDC", targetChain.id),\
data: encodeFunctionData({\
abi: erc20Abi,\
functionName: "transfer",\
args: ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 2n],\
}),\
},\
];
const balanceSlot = keccak256(
encodeAbiParameters(
[{ type: "address" }, { type: "uint256" }],
[targetSafeAccount.address, 9n],
),
);
const userOp = await targetSmartAccountClient.prepareUserOperation({
account: targetSafeAccount,
calls: userOpActions,
nonce: nonce,
signature: getOwnableValidatorMockSignature({ threshold: 1 }),
stateOverride: [\
{\
address: getTokenAddress("USDC", targetChain.id),\
stateDiff: [\
{\
slot: balanceSlot,\
value: pad("0xa"),\
},\
],\
},\
],
});
### Modify the UserOperation after simulation[](#modify-the-useroperation-after-simulation)
After the simulation is complete, we will need to modify it. The reason this needs to be done after simulation is that due to the way that the simulation works, it would revert with the first injected execution, which is a callback to a trampoline contract that ensures the correct filling of the user intent. Hence, we simulate the UserOperation first and calculate the gas before manually adding this injected execution back in.
const injectedExecutionsMapped = [\
// callback action is always the first action in injectedExecutions\
...(injectedExecutions?.[0] ? [injectedExecutions[0]] : []),\
].map((action) => ({
to: action.target,
value: BigInt(Number(action.value)),
data: action.callData || "0x",
}));
// add the callback
userOp.callData = await targetSafeAccount.encodeCalls([\
...injectedExecutionsMapped,\
...userOpActions,\
]);
// manually increase gas
userOp.callGasLimit += BigInt(100000);
### Sign the UserOperation[](#sign-the-useroperation)
Next, we will sign the UserOperation to be verified on the target chain.
const userOpHash = getUserOperationHash({
userOperation: userOp,
chainId: targetChain.id,
entryPointAddress: entryPoint07Address,
entryPointVersion: "0.7",
});
userOp.signature = await owner.signMessage({
message: { raw: userOpHash },
});
// add userOperation into metaIntent
orderBundle.userOp = toPackedUserOperation(userOp);
### Sign the MetaIntent[](#sign-the-metaintent)
Next, we will also need to sign the entire Meta Intent. This ensures that the solver cannot do anything that the user does not want, such as using a specific set of funds.
const orderBundleHash = await getOrderBundleHash(orderBundle);
const bundleSignature = await owner.signMessage({
message: { raw: orderBundleHash },
});
const packedSig = encodePacked(
["address", "bytes"],
[ownableValidator.address, bundleSignature],
);
const signedOrderBundle: SignedOrderBundle = {
...orderBundle,
acrossTransfers: orderBundle.acrossTransfers.map((transfer: any) => ({
...transfer,
userSignature: packedSig,
})),
targetExecutionSignature:
orderBundle.userOp.sender !== zeroAddress ? "0x" : packedSig,
};
### Send the MetaIntent[](#send-the-metaintent)
Finally, we will send the MetaIntent to the orchestrator. This will broadcast the intent to the solvers, who will then fill the transaction on the target chain.
const bundleId = await orchestrator.postSignedOrderBundle(
signedOrderBundle,
userId,
);
### Check the status of the MetaIntent[](#check-the-status-of-the-metaintent)
To ensure everything went well, we can check the status of the Meta Intent. Initially, this should be `RECEIVED` but then it will move to `FILLED` when the target chain fill has been completed and `COMPLETED` when the solver has recouped their funds on the source chain.
const bundleStatus = await orchestrator.getBundleStatus(userId, bundleId);
[1: Create a new Omni Account](/omni-account/tutorial-1 "1: Create a new Omni Account")
[3: Turn an existing account into an Omni Account](/omni-account/tutorial-3 "3: Turn an existing account into an Omni Account")
---
# ERC-7579 – Rhinestone Docs
Overview
ERC-7579
ERC-7579
========
ERC-7579 outlines the minimally required interfaces and behavior for modular smart accounts and modules to ensure interoperability across implementations. The standard is designed to allow account builders to continue to innovate and compete on the account level, while also enabling third-party developers to build modules that can be used seamlessly across different accounts.
Our approach to the standard is the following:
* Take learnings from existing smart accounts that have been used in production and from building interoperability layers between them
* Ensure that the interfaces are as minimal and open to alternative architectures as possible
The standard is co-authored by ZeroDev, Biconomy, Rhinestone and OKX and is currently being rolled out across the ecosystem.
To find out more about ERC-7579, check out the links below:
* [ERC-7579 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-7579)
* [Website (opens in a new tab)](https://erc7579.com/)
* [Github organization (opens in a new tab)](https://github.com/erc7579)
* [Blog: Introducing: ERC-7579 (opens in a new tab)](https://blog.rhinestone.wtf/introducing-erc-7579-417084d7a66f)
* [Blog: ERC-7579 changes (opens in a new tab)](https://blog.rhinestone.wtf/erc-7579-changes-c49259d07356)
[Modules explained](/overview/modules "Modules explained")
[ERC-7484](/overview/erc-7484 "ERC-7484")
---
# Using the executor flow – Rhinestone Docs
Omni Account
Executor Flow
Using the executor flow
=======================
Natively, Omni Account can be used with ERC-4337, meaning that the target chain execution will happen through a UserOperation. This has the upside of being easily compatible with existing smart accounts and that even undeployed accounts can be used for target executions. Because of this, when an account is undeployed on the target chain, the "UserOperation flow" has to be used, as shown in [tutorial 2](/omni-account/tutorial-1)
.
However, when an account is already deployed and has the target executor installed, there is a more efficient way of doing target chain executions, namely through the "executor flow". This flow cuts out the ERC-4337 middleware, namely the EntryPoint and thus reduces execution gas significantly. Instead, the execution path will now be through the target executor which can call into the ERC-7579 account.
This guide briefly shows the difference for a client of Omni Account when using the executor flow. As mentioned before, the upside of using this flow is that the execution gas is lower and that the user only needs to make a single signature, over the `OrderBundle`.
The executor flow[](#the-executor-flow)
----------------------------------------
Before being able to use the executor flow, the target executor has to be installed on the target chain. This is also needed in the UserOp flow so if you've sent an intent before then this should already be the case. Otherwise, you can use the helper function in the [ModuleSDK](/module-sdk)
to get the target executor:
const targetExecutor = getAccountLockerTargetExecutor();
Then, you can use the account abstraction sdk of your choice to install this module.
The main difference to the UserOp flow is that when constructing the `MetaIntent`, you should supply the target chain executions in the `targetExecutions` array and the `userOp` field remains unused:
const metaIntent: MetaIntent = {
targetChainId: targetChain.id,
tokenTransfers: tokenTransfers,
targetAccount: targetSafeAccount.address,
targetExecutions: [\
{\
target: getTokenAddress("USDC", targetChain.id),\
value: 0n,\
callData: encodeFunctionData({\
abi: erc20Abi,\
functionName: "transfer",\
args: ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 2n],\
}),\
},\
],
userOp: {
sender: zeroAddress,
nonce: 0n,
initCode: "0x",
callData: "0x",
accountGasLimits: zeroHash,
preVerificationGas: 0n,
gasFees: zeroHash,
paymasterAndData: "0x",
signature: "0x",
},
};
This is also true when adding the injected executions, who should be added like so:
metaIntent.targetExecutions = [\
...injectedExecutions,\
...metaIntent.targetExecutions,\
];
Apart from this, everything else remains the same. For the full code of this guide, check out the [tutorial (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-tutorials/blob/main/src/orchestrator-sdk/new-account.ts)
[3: Turn an existing account into an Omni Account](/omni-account/tutorial-3 "3: Turn an existing account into an Omni Account")
[Token Preference](/omni-account/token-preference "Token Preference")
---
# Tutorial 3: Turn an existing account into an Omni Account – Rhinestone Docs
Omni Account
3: Turn an existing account into an Omni Account
Tutorial 3: Turn an existing account into an Omni Account
=========================================================
In this tutorial, we will walk through turning an existing smart account into an Omni Account. This will require installing the relevant modules on all the source chains, in this case we will only use a single source chain.
For this tutorial, we are using `@rhinestone/module-sdk` at the latest version to set up the required modules. We are also using `@rhinestone/orchestrator-sdk` to interact with the Omni Account modules and the backend service, the Orchestrator. Finally, we use `permissionless@^0.2.22` and `viem@^2.21.51` for their account abstraction features. See the full source code for this tutorial in our [module-sdk-tutorials repo (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-tutorials/blob/main/src/orchestrator-sdk/existing-account.ts)
.
### Install the packages[](#install-the-packages)
First, install the required dependencies:
npmpnpmyarnbun
npm i @rhinestone/module-sdk @rhinestone/orchestrator-sdk permissionless viem
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { createSmartAccountClient } from "permissionless";
import {
toSafeSmartAccount,
} from "permissionless/accounts";
import {
Chain,
createPublicClient,
encodeFunctionData,
encodePacked,
erc20Abi,
Hex,
http,
zeroAddress,
zeroHash,
} from "viem";
import { entryPoint07Address } from "viem/account-abstraction";
import {
getOrderBundleHash,
getTokenAddress,
MetaIntent,
SignedOrderBundle,
} from "@rhinestone/orchestrator-sdk";
import { erc7579Actions } from "permissionless/actions/erc7579";
import { createPimlicoClient } from "permissionless/clients/pimlico";
### Create the clients for the target chain[](#create-the-clients-for-the-target-chain)
First, we will create the clients required to interact with the target chain.
const targetPublicClient = createPublicClient({
chain: targetChain,
transport: http(),
});
const targetPimlicoClient = createPimlicoClient({
transport: http(
`https://api.pimlico.io/v2/${targetChain.id}/rpc?apikey=${pimlicoApiKey}`,
),
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
});
### Create the target chain smart account client[](#create-the-target-chain-smart-account-client)
Next, we will create the target chain smart account client. Since the account already exists, we can just add the address and don't need the initial parameters.
const targetSafeAccount = await toSafeSmartAccount({
address: accountAddress,
client: targetPublicClient,
owners: [owner],
version: "1.4.1",
entryPoint: {
address: entryPoint07Address,
version: "0.7",
},
safe4337ModuleAddress: "0x7579EE8307284F293B1927136486880611F20002",
erc7579LaunchpadAddress: "0x7579011aB74c46090561ea277Ba79D510c6C00ff",
});
const targetSmartAccountClient = createSmartAccountClient({
account: targetSafeAccount,
chain: targetChain,
bundlerTransport: http(
`https://api.pimlico.io/v2/${targetChain.id}/rpc?apikey=${pimlicoApiKey}`,
),
paymaster: targetPimlicoClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await targetPimlicoClient.getUserOperationGasPrice()).fast;
},
},
}).extend(erc7579Actions());
### Install the target executor (optional)[](#install-the-target-executor-optional)
If the target executor is not already installed, you can proceed in two ways. The first is by directly installing it through a UserOperation. The second is by doing it inside an intent flow. For brevity, we will go with the former but if you want to do the latter, then you can follow [tutorial 1](/omni-account/tutorial-1)
to see how the UserOp flow works. In the UserOperation, you would add another batched call to install the target executor.
const opHash = await targetSmartAccountClient.installModule(getAccountLockerTargetExecutor());
await sourcePimlicoClient.waitForUserOperationReceipt({
hash: opHash,
});
### Create the meta intent[](#create-the-meta-intent)
To send an intent that spends the funds from source chain on the target chain, we will first need to create a MetaIntent and the corresponding token transfers. The token transfers tells the relayer which tokens a user wants on the target chain. We also add the actions that the user wants to do into the `targetExecutions` field, in our case the transfer of the USDC out to another address.
const tokenTransfers = [\
{\
tokenAddress: getTokenAddress("USDC", targetChain.id),\
amount: 2n,\
},\
];
// create the meta intent
const metaIntent: MetaIntent = {
targetChainId: targetChain.id,
tokenTransfers: tokenTransfers,
targetAccount: targetSafeAccount.address,
targetExecutions: [\
{\
target: getTokenAddress("USDC", targetChain.id),\
value: 0n,\
callData: encodeFunctionData({\
abi: erc20Abi,\
functionName: "transfer",\
args: ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 2n],\
}),\
},\
],
userOp: {
sender: zeroAddress,
nonce: 0n,
initCode: "0x",
callData: "0x",
accountGasLimits: zeroHash,
preVerificationGas: 0n,
gasFees: zeroHash,
paymasterAndData: "0x",
signature: "0x",
},
};
### Get the order path[](#get-the-order-path)
Next, we will get the order path from the orchestrator. What this means is that we get the completed order bundle and a set of injected executions. These executions allow for the orchestrator to inject a set of executions, for example to guarantee the atomicity of the fill. In this step, the orchestrator also figures out where exactly to pull the required funds from (which is easy in this tutorial). Because of this, the user signature only happens after getting the order path so that the user is able to see and needs to confirm (by signing) that they want those tokens to be used.
const { orderBundle, injectedExecutions } = await orchestrator.getOrderPath(
metaIntent,
userId,
);
metaIntent.targetExecutions = [\
...injectedExecutions,\
...metaIntent.targetExecutions,\
];
### Sign the MetaIntent[](#sign-the-metaintent)
Next, we will also need to sign the entire Meta Intent. This ensures that the solver cannot do anything that the user does not want, such as using a specific set of funds.
const orderBundleHash = await getOrderBundleHash(orderBundle);
const bundleSignature = await owner.signMessage({
message: { raw: orderBundleHash },
});
const packedSig = encodePacked(
["address", "bytes"],
[ownableValidator.address, bundleSignature],
);
const signedOrderBundle: SignedOrderBundle = {
...orderBundle,
acrossTransfers: orderBundle.acrossTransfers.map((transfer: any) => ({
...transfer,
userSignature: packedSig,
})),
targetExecutionSignature:
orderBundle.userOp.sender !== zeroAddress ? "0x" : packedSig,
};
### Send the MetaIntent[](#send-the-metaintent)
Finally, we will send the MetaIntent to the orchestrator. This will broadcast the intent to the solvers, who will then fill the transaction on the target chain.
const bundleId = await orchestrator.postSignedOrderBundle(
signedOrderBundle,
userId,
);
### Check the status of the MetaIntent[](#check-the-status-of-the-metaintent)
To ensure everything went well, we can check the status of the Meta Intent. Initially, this should be `RECEIVED` but then it will move to `FILLED` when the target chain fill has been completed and `COMPLETED` when the solver has recouped their funds on the source chain.
const bundleStatus = await orchestrator.getBundleStatus(userId, bundleId);
[2: Send your first intent](/omni-account/tutorial-2 "2: Send your first intent")
[Executor Flow](/omni-account/executor-flow "Executor Flow")
---
# ERC-7484: Registry Extension for ERC-7579 – Rhinestone Docs
Overview
ERC-7484
ERC-7484: Registry Extension for ERC-7579
=========================================
A standard for a registry extensions to support the [ERC-7579 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-7579)
standard. The registry extension allows a modular smart account to get security guarantees about the modules it is about to install or use. This standard outlines the interface for a registry extension that can be used to query the registry for attestations about modules. It also includes a standard way to implement a registry adapter on the account.
To find out more about ERC-7484 and the Module Registry, check out the links below:
* [ERC-7484 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-7484)
* [Module Registry (opens in a new tab)](https://github.com/rhinestonewtf/registry/)
* [Blog: Introducing: ERC-7484 (opens in a new tab)](https://blog.rhinestone.wtf/introducing-erc-7484-1d4d5c7e6dc1)
* [Blog (3 part): A foundational layer to modular account abstraction (opens in a new tab)](https://blog.rhinestone.wtf/a-foundational-layer-to-modular-account-abstraction-e7b21ae56034)
[ERC-7579](/overview/erc-7579 "ERC-7579")
[Address book](/overview/address-book "Address book")
---
# Address Book – Rhinestone Docs
Overview
Address book
Address Book
============
A collection of addresses for the various contracts we have deployed or are using. If you need any of our contracts on a different chain, just [send me a message (opens in a new tab)](https://t.me/konradkopp)
or you can deploy any of them yourself from our repos.
Supported Networks[](#supported-networks)
------------------------------------------
We support a wide range of networks. To view the specific networks that a contract is on, click the link and you will be taken to the contract's page on ContractScan, where you can see the networks that the contract is deployed to.
Generally, we support the following networks, although we likely also support others, especially with the core infrastructure:
**Mainnets**
• Ethereum
• Optimism
• Arbitrum
• Base
• Polygon
• Avalanche
• Gnosis
• Scroll
• Fuse
• BSC
**Testnets**
• Sepolia
• Optimism Sepolia
• Arbitrum Sepolia
• Base Sepolia
• Polygon Amoy
• Avalanche Fuji
• Scroll Sepolia
• Fuse Spark
• BSC Testnet
Infrastructure[](#infrastructure)
----------------------------------
| contract | address | mainnets | testnets |
| --- | --- | --- | --- |
| ERC-4337 EntryPoint (v0.7) | [0x0000000071727De22E5E9d8BAf0edAc6f37da032 (opens in a new tab)](https://contractscan.xyz/contract/0x0000000071727De22E5E9d8BAf0edAc6f37da032) | ✅ | ✅ |
| Module Registry | [0x000000000069E2a187AEFFb852bF3cCdC95151B2 (opens in a new tab)](https://contractscan.xyz/contract/0x000000000069E2a187AEFFb852bF3cCdC95151B2) | ✅ | ✅ |
| Rhinestone Attester | [0x000000333034E9f539ce08819E12c1b8Cb29084d (opens in a new tab)](https://contractscan.xyz/contract/0x000000333034E9f539ce08819E12c1b8Cb29084d) | ✅ | ✅ |
| Rhinestone Resolver | [0xF0f468571e764664c93308504642aF941d9f77F1 (opens in a new tab)](https://contractscan.xyz/contract/0xF0f468571e764664c93308504642aF941d9f77F1) | ✅ | ✅ |
| Rhinestone Schema Validator | [0x86430E19D7D204807bBb8CDa997bb57b7EE785dD (opens in a new tab)](https://contractscan.xyz/contract/0x86430E19D7D204807bBb8CDa997bb57b7EE785dD) | ✅ | ✅ |
| Safe7579 Adapter | [0x7579EE8307284F293B1927136486880611F20002 (opens in a new tab)](https://contractscan.xyz/contract/0x7579EE8307284F293B1927136486880611F20002) | ✅ | ✅ |
| Safe7579 Launchpad | [0x7579011aB74c46090561ea277Ba79D510c6C00ff (opens in a new tab)](https://contractscan.xyz/contract/0x7579011aB74c46090561ea277Ba79D510c6C00ff) | ✅ | ✅ |
| ERC-7579 Reference Factory | [0xDC15682AEDba36Cf3121507993b50Ef22b457053 (opens in a new tab)](https://contractscan.xyz/contract/0xDC15682AEDba36Cf3121507993b50Ef22b457053) | ❌ | ✅ |
| ERC-7579 Reference Singleton (Advanced) | [0xa951A1179bA8bd08b8140aB9dc7910AF08AE7181 (opens in a new tab)](https://contractscan.xyz/contract/0xa951A1179bA8bd08b8140aB9dc7910AF08AE7181) | ❌ | ✅ |
| ERC-7579 Reference Bootstrap | [0x1E919660050C68BFEf868945Cf5f9a26ad7E360b (opens in a new tab)](https://contractscan.xyz/contract/0x1E919660050C68BFEf868945Cf5f9a26ad7E360b) | ❌ | ✅ |
Additional variables:
* Rhinestone Resolver UID: 0xdbca873b13c783c0c9c6ddfc4280e505580bf6cc3dac83f8a0f7b44acaafca4f
* Rhinestone Schema UID: 0x93d46fcca4ef7d66a413c7bde08bb1ff14bacbd04c4069bb24cd7c21729d7bf1
Modules[](#modules)
--------------------
| contract | address | mainnets | testnets |
| --- | --- | --- | --- |
| Smart Sessions Validator | [0x00000000002B0eCfbD0496EE71e01257dA0E37DE (opens in a new tab)](https://contractscan.xyz/contract/0x00000000002B0eCfbD0496EE71e01257dA0E37DE) | ✅ | ✅ |
| Ownable Validator | [0x2483DA3A338895199E5e538530213157e931Bf06 (opens in a new tab)](https://contractscan.xyz/contract/0x2483DA3A338895199E5e538530213157e931Bf06) | ✅ | ✅ |
| Webauthn Validator | [0x2f167e55d42584f65e2e30a748f41ee75a311414 (opens in a new tab)](https://contractscan.xyz/contract/0x2f167e55d42584f65e2e30a748f41ee75a311414) | ✅ | ✅ |
| MultiFactor Validator | [0xf6bDf42c9BE18cEcA5C06c42A43DAf7FBbe7896b (opens in a new tab)](https://contractscan.xyz/contract/0xf6bDf42c9BE18cEcA5C06c42A43DAf7FBbe7896b) | ✅ | ✅ |
| AutoSavings | [0x6AE48bD83B6bdc8489584Ea0814086f963d1BD95 (opens in a new tab)](https://contractscan.xyz/contract/0x6AE48bD83B6bdc8489584Ea0814086f963d1BD95) | ✅ | ✅ |
| ScheduledOrders Executor | [0x40dc90D670C89F322fa8b9f685770296428DCb6b (opens in a new tab)](https://contractscan.xyz/contract/0x40dc90D670C89F322fa8b9f685770296428DCb6b) | ✅ | ✅ |
| ScheduledTransfers Executor | [0xA8E374779aeE60413c974b484d6509c7E4DDb6bA (opens in a new tab)](https://contractscan.xyz/contract/0xA8E374779aeE60413c974b484d6509c7E4DDb6bA) | ✅ | ✅ |
| Ownable Executor | [0x4Fd8d57b94966982B62e9588C27B4171B55E8354 (opens in a new tab)](https://contractscan.xyz/contract/0x4Fd8d57b94966982B62e9588C27B4171B55E8354) | ✅ | ✅ |
| ColdStorage Hook | [0x7E31543b269632ddc55a23553f902f84C9DD8454 (opens in a new tab)](https://contractscan.xyz/contract/0x7E31543b269632ddc55a23553f902f84C9DD8454) | ✅ | ✅ |
| SocialRecovery | [0xA04D053b3C8021e8D5bF641816c42dAA75D8b597 (opens in a new tab)](https://contractscan.xyz/contract/0xA04D053b3C8021e8D5bF641816c42dAA75D8b597) | ✅ | ✅ |
| RegistryHook | [0x0ac6160DBA30d665cCA6e6b6a2CDf147DC3dED22 (opens in a new tab)](https://contractscan.xyz/contract/0x0ac6160DBA30d665cCA6e6b6a2CDf147DC3dED22) | ✅ | ✅ |
| DeadmanSwitch | [0x8bAdE54bca47199B6732EB2F92318DD666bdE413 (opens in a new tab)](https://contractscan.xyz/contract/0x8bAdE54bca47199B6732EB2F92318DD666bdE413) | ✅ | ✅ |
| HookMultiPlexer | [0xF6782ed057F95f334D04F0Af1Af4D14fb84DE549 (opens in a new tab)](https://contractscan.xyz/contract/0xF6782ed057F95f334D04F0Af1Af4D14fb84DE549) | ✅ | ✅ |
Policies[](#policies)
----------------------
| contract | address | mainnets | testnets |
| --- | --- | --- | --- |
| ERC20 Spending Limit Policy | [0x00000088D48cF102A8Cdb0137A9b173f957c6343 (opens in a new tab)](https://contractscan.xyz/contract/0x00000088D48cF102A8Cdb0137A9b173f957c6343) | ✅ | ✅ |
| Universal Action Policy | [0x0000006DDA6c463511C4e9B05CFc34C1247fCF1F (opens in a new tab)](https://contractscan.xyz/contract/0x0000006DDA6c463511C4e9B05CFc34C1247fCF1F) | ✅ | ✅ |
| Sudo Policy | [0x0000003111cD8e92337C100F22B7A9dbf8DEE301 (opens in a new tab)](https://contractscan.xyz/contract/0x0000003111cD8e92337C100F22B7A9dbf8DEE301) | ✅ | ✅ |
Mocks[](#mocks)
----------------
| contract | address | mainnets | testnets |
| --- | --- | --- | --- |
| Mock Validator | [0x11D02847245Df7cF19f48C8907ace59289D8aCEe (opens in a new tab)](https://contractscan.xyz/contract/0x11D02847245Df7cF19f48C8907ace59289D8aCEe) | ❌ | ✅ |
| Mock Registry | [0x25A4b2F363678E13A0A5DB79b712dE00347a593E (opens in a new tab)](https://contractscan.xyz/contract/0x25A4b2F363678E13A0A5DB79b712dE00347a593E) | ❌ | ✅ |
| Mock Attester | [0xA4C777199658a41688E9488c4EcbD7a2925Cc23A (opens in a new tab)](https://contractscan.xyz/contract/0xA4C777199658a41688E9488c4EcbD7a2925Cc23A) | ❌ | ✅ |
[ERC-7484](/overview/erc-7484 "ERC-7484")
[Audits](/overview/audits "Audits")
---
# Specifying a token preference – Rhinestone Docs
Omni Account
Token Preference
Specifying a token preference
=============================
In the normal Meta Intent flow, the Orchestrator will find the best path when called to get the bundle path. However, there could be cases in which a user wants the Orchestrator to only use certain assets, maybe because they want to continue holding others.
The way that this can be done is by supplying an optional `accountAccessList` field in the `MetaIntent` object. This field looks as follows:
type MetaIntent = {
...
accountAccessList?: {
accountAddress: Address
chainId: number
tokenAddress: Address
}[]
}
As you can tell, this field is an array of token objectes, consisting of the chain id and token address as well as the account address. The latter is useful since an account cluster could have multiple accounts that a user can pull funds from.
[Executor Flow](/omni-account/executor-flow "Executor Flow")
[getAccountLockerHook](/omni-account/orchestrator-sdk/modules/getAccountLockerHook "getAccountLockerHook")
---
# ModuleKit – Rhinestone Docs
ModuleKit
Overview
ModuleKit
=========
**A development kit for building smart account modules**
ModuleKit aims to make it simple for any developer to build a module that works across all ERC-7579 compliant accounts, including Safe, ZeroDev’s Kernel V3, Biconomy’s Nexus, and many more.

ModuleKit has several tools to help during the development lifecycle of a module:
**Build**
* **Standardized interfaces and templates**: These ensure that your module is compatible with all the major account implementations and that it's extremely easy to get started.
* **Third-party integrations**: ModuleKit has a library of integrations and pre-built conditions for execution to make building powerful modules seamless.
**Test**
* **Testing frameworks**: These are out-of-the-box testing setups with in-built unit and integration tests. They allow developers to easily test modules against different account implementations and abstract away the complexities of the entire ERC-4337 flow.
* **Helper utilities**: These utilities improve the developer experience, such as calculating gas consumption (including on L2s) and validating that a module conforms to the ERC-4337 rules.
**Deploy**
* **Deployment script**: The ModuleKit comes with a helper contract that allows you to easily deploy a module and register it in the Module Registry.
### Supported Accounts[](#supported-accounts)
[ERC-7579](https://erc7579.com/)
[Safe](https://safe.global/)
[Kernel (v3)](https://zerodev.app/)
[Biconomy (v3)](https://biconomy.io/)
### Supported Module Types[](#supported-module-types)

Validators
Determine whether a transaction is valid and should be executed

Executors
Create executions on the account with custom logic

Hooks
Enforce conditions or execute logic pre- or post-execution

Fallbacks
Extend the account logic to add more functionality into the account
Useful starting resources[](#useful-starting-resources)
--------------------------------------------------------
* Blog: [Introducing ModuleKit (opens in a new tab)](https://blog.rhinestone.wtf/introducing-modulekit-b5a0737e228e)
* Blog: [ModuleKit deep dive (opens in a new tab)](https://blog.rhinestone.wtf/modulekit-deep-dive-ad84ee0797c6)
* [Module examples (opens in a new tab)](https://github.com/rhinestonewtf/core-modules)
* List of open-source modules and resources: [awesome-modular-accounts repo (opens in a new tab)](https://github.com/rhinestonewtf/awesome-modular-accounts)
* Learn more about [modules](/overview/modules)
* To use existing modules, check out the [Module SDK](/module-sdk)
[Getting Started](/modulekit/getting-started "Getting Started")
---
# getAccountLockerTargetExecutor – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
getAccountLockerTargetExecutor
getAccountLockerTargetExecutor
==============================
Get the module installation data for the account locker target executor. This executor is used to execute the user action on the target chain.
Usage[](#usage)
----------------
const module = getAccountLockerTargetExecutor({
moduleType: 'executor',
})
Parameters[](#parameters)
--------------------------
### moduleType[](#moduletype)
* Type: `executor | fallback`
The type to install the module as.
### hook (optional)[](#hook-optional)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the hook module to associate with. This is only required for the Kernel account.
Returns[](#returns)
--------------------
### module[](#module)
* Type: [`Module`](/module-sdk/glossary/types#module)
The account locker target executor module object.
[getAccountLockerSourceExecutor](/omni-account/orchestrator-sdk/modules/getAccountLockerSourceExecutor "getAccountLockerSourceExecutor")
[getUnlockFundsAction](/omni-account/orchestrator-sdk/modules/getUnlockFundsAction "getUnlockFundsAction")
---
# getUnlockFundsAction – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
getUnlockFundsAction
getUnlockFundsAction
====================
Get the action to unlock funds on the source chain. This is done if a user wants to move funds out of the resource lock.
Usage[](#usage)
----------------
const action = getUnlockFundsAction({
orchestratorSignature: '0x',
request: {
timestamp: Math.floor(Date.now() / 1000), // Current timestamp in seconds
tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
orchestrator: '0x122B68D0E84d33F1Af7F1C0D332851AcaD0d457E',
amount: BigInt(100),
nonce: BigInt(1),
},
})
Parameters[](#parameters)
--------------------------
### orchestratorSignature[](#orchestratorsignature)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The orchestrator signature for the unlock.
### request.timestamp[](#requesttimestamp)
* Type: `bigint`
The timestamp of the unlock request.
### request.tokenAddress[](#requesttokenaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the token to unlock. If the token is the native token, the address is `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`.
### request.orchestrator[](#requestorchestrator)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the orchestrator.
### request.amount[](#requestamount)
* Type: `bigint`
The amount to unlock.
### request.nonce[](#requestnonce)
* Type: `bigint`
The nonce of the unlock request.
Returns[](#returns)
--------------------
### action[](#action)
* Type: [`Action`](/module-sdk/glossary/types#action)
The action to execute on the account.
[getAccountLockerTargetExecutor](/omni-account/orchestrator-sdk/modules/getAccountLockerTargetExecutor "getAccountLockerTargetExecutor")
[getDepositToAcrossAction](/omni-account/orchestrator-sdk/modules/getDepositToAcrossAction "getDepositToAcrossAction")
---
# getAccountLockerHook – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
getAccountLockerHook
getAccountLockerHook
====================
Get the module installation data for the account locker hook. This hook enforces the resource locks on the account.
Usage[](#usage)
----------------
const module = getAccountLockerHook({
isOmniMode: true,
})
Parameters[](#parameters)
--------------------------
### isOmniMode[](#isomnimode)
* Type: `boolean`
Whether the account locker is in omni mode, meaning that the entire user balance is locked.
### hook (optional)[](#hook-optional)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the hook module to associate with. This is only required for the Kernel account.
Returns[](#returns)
--------------------
### module[](#module)
* Type: [`Module`](/module-sdk/glossary/types#module)
The account locker hook module object.
[Token Preference](/omni-account/token-preference "Token Preference")
[getAccountLockerSourceExecutor](/omni-account/orchestrator-sdk/modules/getAccountLockerSourceExecutor "getAccountLockerSourceExecutor")
---
# Audits – Rhinestone Docs
Overview
Audits
Audits
======
| contract name | contract repository | auditor | report | date |
| --- | --- | --- | --- | --- |
| Module Registry | [Repository (opens in a new tab)](https://github.com/rhinestonewtf/registry) | Ackee | [Report (opens in a new tab)](https://github.com/rhinestonewtf/registry/blob/main/audits/ackee-blockchain-rhinestone-registry-report.pdf) | 3.07.2024 |
| Safe7579 | [Repository (opens in a new tab)](https://github.com/rhinestonewtf/safe7579) | Ackee | [Report (opens in a new tab)](https://github.com/rhinestonewtf/safe7579/blob/main/audits/ackee-blockchain-rhinestone-safe7579-report.pdf) | 5.07.2024 |
| Core Modules | [Repository (opens in a new tab)](https://github.com/rhinestonewtf/core-modules) | Ackee | [Report (opens in a new tab)](https://github.com/rhinestonewtf/core-modules/blob/main/audits/ackee-blockchain-rhinestone-core-modules-report.pdf) | 3.10.2024 |
| SentinelList | [Repository (opens in a new tab)](https://github.com/rhinestonewtf/sentinellist) | Ackee | [Report (opens in a new tab)](https://github.com/rhinestonewtf/sentinellist/blob/main/audits/ackee-blockchain-rhinestone-core-modules-report.pdf) | 3.10.2024 |
| CheckNSignatures | [Repository (opens in a new tab)](https://github.com/rhinestonewtf/checknsignatures) | Ackee | [Report (opens in a new tab)](https://github.com/rhinestonewtf/checknsignatures/blob/main/audits/ackee-blockchain-rhinestone-core-modules-report.pdf) | 3.10.2024 |
| SmartSessions | [Repository (opens in a new tab)](https://github.com/erc7579/smartsessions) | Cantina | [Report (opens in a new tab)](https://github.com/erc7579/smartsessions/blob/main/audits/report-cantinacode-rhinestone-0826-core.pdf) | 8.10.2024 |
| SmartSessions | [Repository (opens in a new tab)](https://github.com/erc7579/smartsessions) | Cantina | [Report (opens in a new tab)](https://github.com/erc7579/smartsessions/blob/main/audits/report-cantinacode-rhinestone-0826-external-policies.pdf) | 8.10.2024 |
| SmartSessions | [Repository (opens in a new tab)](https://github.com/erc7579/smartsessions) | Renascence | [Report (opens in a new tab)](https://github.com/erc7579/smartsessions/blob/main/audits/rhinestone_smartsessions_v1.pdf) | 9.11.2024 |
| SmartSessions | [Repository (opens in a new tab)](https://github.com/erc7579/smartsessions) | Renascence | [Report (opens in a new tab)](https://github.com/erc7579/smartsessions/blob/main/audits/rhinestone_smartsessions_update.pdf) | 25.12.2024 |
[Address book](/overview/address-book "Address book")
---
# getAccountLockerSourceExecutor – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
getAccountLockerSourceExecutor
getAccountLockerSourceExecutor
==============================
Get the module installation data for the account locker source executor. This executor allows the solver to reclaim their fronted funds.
Usage[](#usage)
----------------
const module = getAccountLockerSourceExecutor()
Parameters[](#parameters)
--------------------------
### hook (optional)[](#hook-optional)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the hook module to associate with. This is only required for the Kernel account.
Returns[](#returns)
--------------------
### module[](#module)
* Type: [`Module`](/module-sdk/glossary/types#module)
The account locker source executor module object.
[getAccountLockerHook](/omni-account/orchestrator-sdk/modules/getAccountLockerHook "getAccountLockerHook")
[getAccountLockerTargetExecutor](/omni-account/orchestrator-sdk/modules/getAccountLockerTargetExecutor "getAccountLockerTargetExecutor")
---
# getDepositToAcrossAction – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
getDepositToAcrossAction
getDepositToAcrossAction
========================
Get the action to deposit funds to across on the source chain. This is done after a target execution has been successfully completed.
Usage[](#usage)
----------------
const action = getDepositToAcrossAction({
originModulePayload: {
order: {
settlement: {
orchestrator: '0x8a310b9085faF5d9464D84C3d9a7BE3b28c94531',
recipient: '0xFfF799094Ede20f26d06A6Ff9bFDca13AD260018',
settlementContract: '0x634341C2FCa77A82F3885e2cB28C5f068bbB4788',
lastDepositId:
BigInt(
37067938379779921880355465646938906079222528935573724150604866268218749224547,
),
targetChainId: 8453,
targetAccount: '0xFfF799094Ede20f26d06A6Ff9bFDca13AD260018',
fillDeadline: 1733491631,
},
acrossTransfer: {
originModule: '0x868E00ae42214a5a1BB2d01aE1587c8814cF45BB',
originAccount: '0xFfF799094Ede20f26d06A6Ff9bFDca13AD260018',
originChainId: 42161,
initiateDeadline: 1733493431,
maxFee: BigInt(0),
depositId:
BigInt(
37067938379779921880355465646938906079222528935573724150604866268218749224547,
),
originTransfer: {
tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
amount: BigInt(1000),
},
targetTransfer: {
tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
amount: BigInt(1000),
},
},
smartDigests: {
userOpDigest:
'0x5baad1e01c2149d054c7fd327eff823962c0efbd9d315bbadeeb45261ca6d5e1',
executionDigest:
'0x631feebedce2f51ff96ab6c059b1d13bb7aadb36ab8d1e8400d0c070c5227112',
acrossTransferDigests: {
digestIndex: BigInt(0),
chainDataDigests: [\
'0x5cdc36dea4f46f1f892a3d8496dece0be3e81af55aa6984167f33835042df767',\
],
},
},
userSig:
'0x2483DA3A338895199E5e538530213157e931Bf0672ede06cbfe5dc3cc6a04d5dbe68d43ae7eea66187c6e9b733be29362d7c972b5d9c4fed70c1114f0962b17358a6c2bb246c75e8d5120f8a2271f3b535aedcf01c',
},
auctionFee: BigInt(0),
orchestratorSig:
'0x82be0c8d789685dbd1cb4699614518b60ac53a6f8db864adcb90746fb15ea1160d2260f3861fda173fb94580e56deb559efe54c6d473571e46305dcf551962301b',
acrossMessagePayload:
'0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000007e287a503f0d19b7899c15e80eb18c0ee55ffd1200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000552483da3a338895199e5e538530213157e931bf0672ede06cbfe5dc3cc6a04d5dbe68d43ae7eea66187c6e9b733be29362d7c972b5d9c4fed70c1114f0962b17358a6c2bb246c75e8d5120f8a2271f3b535aedcf01c0000000000000000000000',
},
})
Parameters[](#parameters)
--------------------------
### originModulePayload.order.settlement.orchestrator[](#originmodulepayloadordersettlementorchestrator)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the orchestrator.
### originModulePayload.order.settlement.recipient[](#originmodulepayloadordersettlementrecipient)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the recipient of funds on the target chain, usually the user.
### originModulePayload.order.settlement.settlementContract[](#originmodulepayloadordersettlementsettlementcontract)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the settlement contract, in our case the Across SpokePool.
### originModulePayload.order.settlement.lastDepositId[](#originmodulepayloadordersettlementlastdepositid)
* Type: `bigint`
The last deposit Id.
### originModulePayload.order.settlement.targetChainId[](#originmodulepayloadordersettlementtargetchainid)
* Type: `bigint`
The chainId of the target chain.
### originModulePayload.order.settlement.targetAccount[](#originmodulepayloadordersettlementtargetaccount)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the user on the target chain.
### originModulePayload.order.settlement.fillDeadline[](#originmodulepayloadordersettlementfilldeadline)
* Type: `bigint`
The deadline for the fill to happen by.
### originModulePayload.order.acrossTransfer.originModule[](#originmodulepayloadorderacrosstransferoriginmodule)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the executor module on the origin chain, in our case the Source Executor.
### originModulePayload.order.acrossTransfer.originAccount[](#originmodulepayloadorderacrosstransferoriginaccount)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the user on the origin chain.
### originModulePayload.order.acrossTransfer.originChainId[](#originmodulepayloadorderacrosstransferoriginchainid)
* Type: `bigint`
The chainId of the origin chain.
### originModulePayload.order.acrossTransfer.initiateDeadline[](#originmodulepayloadorderacrosstransferinitiatedeadline)
* Type: `bigint`
The deadline for the deposit to happen by on the source chain.
### originModulePayload.order.acrossTransfer.maxFee[](#originmodulepayloadorderacrosstransfermaxfee)
* Type: `bigint`
The maximum fee to be paid by the user.
### originModulePayload.order.acrossTransfer.depositId[](#originmodulepayloadorderacrosstransferdepositid)
* Type: `bigint`
The deposit Id.
### originModulePayload.order.acrossTransfer.originTransfer.tokenAddress[](#originmodulepayloadorderacrosstransferorigintransfertokenaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the token to transfer on the origin chain.
### originModulePayload.order.acrossTransfer.originTransfer.amount[](#originmodulepayloadorderacrosstransferorigintransferamount)
* Type: `bigint`
The amount to transfer on the origin chain.
### originModulePayload.order.acrossTransfer.targetTransfer.tokenAddress[](#originmodulepayloadorderacrosstransfertargettransfertokenaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the token to transfer on the target chain.
### originModulePayload.order.acrossTransfer.targetTransfer.amount[](#originmodulepayloadorderacrosstransfertargettransferamount)
* Type: `bigint`
The amount to transfer on the target chain.
### originModulePayload.order.smartDigests.userOpDigest[](#originmodulepayloadordersmartdigestsuseropdigest)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The hash of the UserOperation.
### originModulePayload.order.smartDigests.executionDigest[](#originmodulepayloadordersmartdigestsexecutiondigest)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The hash of the Execution.
### originModulePayload.order.smartDigests.acrossTransferDigests.digestIndex[](#originmodulepayloadordersmartdigestsacrosstransferdigestsdigestindex)
* Type: `bigint`
The index of the current source chain in the chain data digests.
### originModulePayload.order.smartDigests.acrossTransferDigests.chainDataDigests[](#originmodulepayloadordersmartdigestsacrosstransferdigestschaindatadigests)
* Type: [`Array` (opens in a new tab)](https://viem.sh/docs/glossary/types#array)
The array of chain data digests in case multiple source chains are used.
### originModulePayload.order.userSig[](#originmodulepayloadorderusersig)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The user signature.
### originModulePayload.auctionFee[](#originmodulepayloadauctionfee)
* Type: `bigint`
The fee to be paid for the solver auction.
### originModulePayload.orchestratorSig[](#originmodulepayloadorchestratorsig)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The orchestrator signature for the order.
### originModulePayload.acrossMessagePayload[](#originmodulepayloadacrossmessagepayload)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The payload to be sent to the target chain.
Returns[](#returns)
--------------------
### action[](#action)
* Type: [`Action`](/module-sdk/glossary/types#action)
The action to execute on the account.
[getUnlockFundsAction](/omni-account/orchestrator-sdk/modules/getUnlockFundsAction "getUnlockFundsAction")
[getRegisterApprovalSpendAction](/omni-account/orchestrator-sdk/modules/getRegisterApprovalSpendAction "getRegisterApprovalSpendAction")
---
# ACCOUNT_LOCKER_HOOK – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
ACCOUNT\_LOCKER\_HOOK
ACCOUNT\_LOCKER\_HOOK
=====================
Returns the address of the module.
Usage[](#usage)
----------------
const moduleAddress = ACCOUNT_LOCKER_HOOK
Parameters[](#parameters)
--------------------------
None
Returns[](#returns)
--------------------
### moduleAddress[](#moduleaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the module.
[getRegisterApprovalSpendAction](/omni-account/orchestrator-sdk/modules/getRegisterApprovalSpendAction "getRegisterApprovalSpendAction")
[ACCOUNT\_LOCKER\_SOURCE\_EXECUTOR](/omni-account/orchestrator-sdk/modules/ACCOUNT_LOCKER_SOURCE_EXECUTOR "ACCOUNT_LOCKER_SOURCE_EXECUTOR")
---
# registerApprovalSpendAction – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
getRegisterApprovalSpendAction
registerApprovalSpendAction
===========================
Register an approval spend action on a resource locked account. This is useful for when the account is in omnilock mode and a user wants to make an approval.
Usage[](#usage)
----------------
const action = getRegisterApprovalSpendAction({
approvalSpend: {
account: '0xf003346C11bF3F1Fd3780E8F66164B6CFEc2fEf4',
token: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
amount: BigInt(100),
},
orchestratorSignature: '0x',
})
Parameters[](#parameters)
--------------------------
### approvalSpend.account[](#approvalspendaccount)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the account to approve the spend.
### approvalSpend.token[](#approvalspendtoken)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the token to spend. If the token is the native token, the address is `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`.
### approvalSpend.amount[](#approvalspendamount)
* Type: `bigint`
The amount to spend.
### orchestratorSignature[](#orchestratorsignature)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The orchestrator signature for the order.
Returns[](#returns)
--------------------
### action[](#action)
* Type: [`Action`](/module-sdk/glossary/types#action)
The action to execute on the account.
[getDepositToAcrossAction](/omni-account/orchestrator-sdk/modules/getDepositToAcrossAction "getDepositToAcrossAction")
[ACCOUNT\_LOCKER\_HOOK](/omni-account/orchestrator-sdk/modules/ACCOUNT_LOCKER_HOOK "ACCOUNT_LOCKER_HOOK")
---
# ACCOUNT_LOCKER_TARGET_EXECUTOR – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
ACCOUNT\_LOCKER\_TARGET\_EXECUTOR
ACCOUNT\_LOCKER\_TARGET\_EXECUTOR
=================================
Returns the address of the module.
Usage[](#usage)
----------------
const moduleAddress = ACCOUNT_LOCKER_TARGET_EXECUTOR
Parameters[](#parameters)
--------------------------
None
Returns[](#returns)
--------------------
### moduleAddress[](#moduleaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the module.
[ACCOUNT\_LOCKER\_SOURCE\_EXECUTOR](/omni-account/orchestrator-sdk/modules/ACCOUNT_LOCKER_SOURCE_EXECUTOR "ACCOUNT_LOCKER_SOURCE_EXECUTOR")
[getOrchestrator](/omni-account/orchestrator-sdk/getOrchestrator "getOrchestrator")
---
# ACCOUNT_LOCKER_SOURCE_EXECUTOR – Rhinestone Docs
Omni Account
Orchestrator SDK
Modules
ACCOUNT\_LOCKER\_SOURCE\_EXECUTOR
ACCOUNT\_LOCKER\_SOURCE\_EXECUTOR
=================================
Returns the address of the module.
Usage[](#usage)
----------------
const moduleAddress = ACCOUNT_LOCKER_SOURCE_EXECUTOR
Parameters[](#parameters)
--------------------------
None
Returns[](#returns)
--------------------
### moduleAddress[](#moduleaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the module.
[ACCOUNT\_LOCKER\_HOOK](/omni-account/orchestrator-sdk/modules/ACCOUNT_LOCKER_HOOK "ACCOUNT_LOCKER_HOOK")
[ACCOUNT\_LOCKER\_TARGET\_EXECUTOR](/omni-account/orchestrator-sdk/modules/ACCOUNT_LOCKER_TARGET_EXECUTOR "ACCOUNT_LOCKER_TARGET_EXECUTOR")
---
# getOrchestrator – Rhinestone Docs
Omni Account
Orchestrator SDK
getOrchestrator
getOrchestrator
===============
Get the client to interact with the Orchestrator.
Usage[](#usage)
----------------
const orchestrator = getOrchestrator('ORCHESTRATOR_API_KEY')
Parameters[](#parameters)
--------------------------
### apiKey[](#apikey)
* Type: `string`
The API key to use for the orchestrator.
Returns[](#returns)
--------------------
### orchestrator[](#orchestrator)
* Type: `Orchestrator`
The client class to interact with the Orchestrator.
[ACCOUNT\_LOCKER\_TARGET\_EXECUTOR](/omni-account/orchestrator-sdk/modules/ACCOUNT_LOCKER_TARGET_EXECUTOR "ACCOUNT_LOCKER_TARGET_EXECUTOR")
[createUserAccount](/omni-account/orchestrator-sdk/createUserAccount "createUserAccount")
---
# createUserAccount – Rhinestone Docs
Omni Account
Orchestrator SDK
createUserAccount
createUserAccount
=================
Creates a new user account for a specified address and list of chains.
Usage[](#usage)
----------------
const userId = await orchestrator.createUserAccount(
'0x1234567890123456789012345678901234567890',
[8453],
)
Parameters[](#parameters)
--------------------------
### accountAddress[](#accountaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the account to create.
### chains[](#chains)
* Type: `number[]`
The list of chain IDs to create the account on.
Returns[](#returns)
--------------------
### userId[](#userid)
* Type: `Promise`
The ID of the created user account.
[getOrchestrator](/omni-account/orchestrator-sdk/getOrchestrator "getOrchestrator")
[getUserId](/omni-account/orchestrator-sdk/getUserId "getUserId")
---
# getUserId – Rhinestone Docs
Omni Account
Orchestrator SDK
getUserId
getUserId
=========
Get the user id of the user account.
Usage[](#usage)
----------------
const userId = await orchestrator.getUserId(
'0x9EB7504B7546b1B66e177B364A3566eC10132A40',
)
Parameters[](#parameters)
--------------------------
### accountAddress[](#accountaddress)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the account to get the user id for.
### chainId (optional)[](#chainid-optional)
* Type: `number`
The chain ID to get the user id for. If not provided, returns the user id for all chains.
Returns[](#returns)
--------------------
### userId[](#userid)
* Type: `Promise<{ userId: string; chainId: number }[]>`
The user id for the account with the respective chain id.
[createUserAccount](/omni-account/orchestrator-sdk/createUserAccount "createUserAccount")
[getPortfolio](/omni-account/orchestrator-sdk/getPortfolio "getPortfolio")
---
# getPortfolio – Rhinestone Docs
Omni Account
Orchestrator SDK
getPortfolio
getPortfolio
============
Get the assets in a user's portfolio to display in the UI.
Usage[](#usage)
----------------
const portfolio = await orchestrator.getPortfolio(
'd6f64241-a62c-4542-bb23-e78d7e1e0cd6',
)
Parameters[](#parameters)
--------------------------
### userId[](#userid)
* Type: `string`
The ID of the user account to get the portfolio for.
Returns[](#returns)
--------------------
### portfolio[](#portfolio)
* Type: `Promise`
The portfolio of the user account.
[getUserId](/omni-account/orchestrator-sdk/getUserId "getUserId")
[getOrderPath](/omni-account/orchestrator-sdk/getOrderPath "getOrderPath")
---
# getOrderPath – Rhinestone Docs
Omni Account
Orchestrator SDK
getOrderPath
getOrderPath
============
Get the path of an order, ie the required intents to fulfill an execution on a target chain.
Usage[](#usage)
----------------
const { orderBundle, injectedExecutions } = await orchestrator.getOrderPath(
metaIntent: {
targetChainId: 8453, // Base
tokenTransfers: [\
{\
tokenAddress: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",\
amount: 2n,\
},\
],
targetAccount: '0x9EB7504B7546b1B66e177B364A3566eC10132A40',
targetExecutions: [{\
target: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",\
value: 0n,\
callData: encodeFunctionData({\
abi: erc20Abi,\
functionName: 'transfer',\
args: ['0xD1dcdD8e6Fe04c338aC3f76f7D7105bEcab74F77', 1n],\
}),\
}],
// empty example userOp
userOp: {
sender: zeroAddress,
nonce: 0n,
initCode: '0x',
callData: '0x',
accountGasLimits: zeroHash,
preVerificationGas: 0n,
gasFees: zeroHash,
paymasterAndData: '0x',
signature: '0x',
}
},
userId: 'd6f64241-a62c-4542-bb23-e78d7e1e0cd6',
)
Parameters[](#parameters)
--------------------------
### metaIntent.targetChainId[](#metaintenttargetchainid)
* Type: `number`
The chain id of the target chain.
### metaIntent.tokenTransfers[](#metaintenttokentransfers)
* Type: `TokenTransfer[]`
The token transfers to make in the order.
### metaIntent.targetAccount[](#metaintenttargetaccount)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the target account to receive the funds and execute the execution on.
### metaIntent.targetExecutions[](#metaintenttargetexecutions)
* Type: `Execution[]`
The executions to make on the target chain.
### metaIntent.userOp[](#metaintentuserop)
* Type: `PackedUserOperation`
The user operation to make on the target chain. Note that this is only required when the account is not yet deployed.
### metaIntent.accountAccessList (optional)[](#metaintentaccountaccesslist-optional)
* Type: `AccountAccessList`
The account access list to use for the order.
### userId[](#userid)
* Type: `string`
The ID of the user account to get the order path for.
Returns[](#returns)
--------------------
### orderBundle[](#orderbundle)
* Type: `Promise`
The signed intent to execute the order.
### injectedExecutions[](#injectedexecutions)
* Type: `Execution[]`
A list of executions to add into the order in order to make sure it is able to execute correctly.
[getPortfolio](/omni-account/orchestrator-sdk/getPortfolio "getPortfolio")
[postSignedOrderBundle](/omni-account/orchestrator-sdk/postSignedOrderBundle "postSignedOrderBundle")
---
# getSolverClaimPayload – Rhinestone Docs
Omni Account
Orchestrator SDK
getSolverClaimPayload
getSolverClaimPayload
=====================
Allows solvers to get the payload required to claim their funds back on the source chains.
Usage[](#usage)
----------------
const claimPayload = orchestrator.getSolverClaimPayload(bundleId)
Parameters[](#parameters)
--------------------------
### bundleId[](#bundleid)
* Type: `string`
The ID of the bundle to get the claim payload for.
Returns[](#returns)
--------------------
### claimPayload[](#claimpayload)
* Type: `Promise`
The payload to submit on the source chains.
[getBundleStatus](/omni-account/orchestrator-sdk/getBundleStatus "getBundleStatus")
[Types](/omni-account/glossary/types "Types")
---
# Types in the Orchestrator SDK – Rhinestone Docs
Omni Account
Glossary
Types
Types in the Orchestrator SDK
=============================
Glossary of types in the Orchestrator SDK.
[getSolverClaimPayload](/omni-account/orchestrator-sdk/getSolverClaimPayload "getSolverClaimPayload")
---
# getBundleStatus – Rhinestone Docs
Omni Account
Orchestrator SDK
getBundleStatus
getBundleStatus
===============
Get the status of a bundle, ie cross chain intents.
Usage[](#usage)
----------------
const bundleStatus = await orchestrator.getBundleStatus(
'd6f64241-a62c-4542-bb23-e78d7e1e0cd6',
bundleId,
)
Parameters[](#parameters)
--------------------------
### userId[](#userid)
* Type: `string`
The ID of the user account to get the bundle status for.
### bundleId[](#bundleid)
* Type: `string`
The ID of the bundle to get the status for.
Returns[](#returns)
--------------------
### bundleStatus[](#bundlestatus)
* Type: `Promise`
The status of the bundle.
[postSignedOrderBundle](/omni-account/orchestrator-sdk/postSignedOrderBundle "postSignedOrderBundle")
[getSolverClaimPayload](/omni-account/orchestrator-sdk/getSolverClaimPayload "getSolverClaimPayload")
---
# postSignedOrderBundle – Rhinestone Docs
Omni Account
Orchestrator SDK
postSignedOrderBundle
postSignedOrderBundle
=====================
Send off a signed order bundle to the orchestrator to execute.
Usage[](#usage)
----------------
const bundleId = await orchestrator.postSignedOrderBundle(
signedOrderBundle,
'd6f64241-a62c-4542-bb23-e78d7e1e0cd6',
)
Parameters[](#parameters)
--------------------------
### signedOrderBundle[](#signedorderbundle)
* Type: `SignedOrderBundle`
The signed order bundle to post to the orchestrator.
### userId[](#userid)
* Type: `string`
The ID of the user account to post the signed order bundle for.
Returns[](#returns)
--------------------
### bundleId[](#bundleid)
* Type: `Promise`
The id of the bundle, used to check its' status.
[getOrderPath](/omni-account/orchestrator-sdk/getOrderPath "getOrderPath")
[getBundleStatus](/omni-account/orchestrator-sdk/getBundleStatus "getBundleStatus")
---
# Getting started – Rhinestone Docs
SDK
Getting Started
Getting started
===============
The Module SDK is built on top of [viem (opens in a new tab)](https://viem.sh/)
, a typesafe, modern Ethereum library. This SDK is meant to be used alongside existing account SDKs, such as [permissionless.js (opens in a new tab)](https://www.npmjs.com/package/permissionless)
, [Biconomy (opens in a new tab)](https://www.npmjs.com/package/@biconomy/account)
, [Zerodev (opens in a new tab)](https://www.npmjs.com/package/@zerodevapp/sdk)
, and many more.
Installation[](#installation)
------------------------------
Install viem as a peer dependency and then install the Module SDK:
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk
Quick Start[](#quick-start)
----------------------------
// Import the required functions
import {
getClient,
getModule,
getAccount,
getMFAValidator,
installModule,
} from '@rhinestone/module-sdk'
// Create a client for the current network
const client = getClient(network)
// Create the module object if you are using a custom module
const module = getModule({
module: moduleAddress,
data: initData,
type: moduleType,
})
// Or use one of the existing modules
const mfaModule = getMFAValidator({
type: 'mfa-validator',
data: {
threshold: 2,
methods: ['webauthn', 'passkey'],
},
})
// Create the account object
const account = getAccount({
address: '0x123...',
type: 'safe',
})
// Get the executions required to install the module
const executions = await installModule({
client,
account,
module,
})
// Install the module on your account, using your existing account SDK
accountSDK.execute(executions)
Integrating the Module SDK with another account SDK[](#integrating-the-module-sdk-with-another-account-sdk)
------------------------------------------------------------------------------------------------------------
If you are not yet using an account SDK, you can use the permissionless.js SDK to create an account and execute the `UserOperations`. Permissionless.js currently supports the Safe, Kernel and Biconomy accounts. To learn how you can use the SDK, check out the [permissionless.js documentation (opens in a new tab)](https://docs.pimlico.io/permissionless)
. To install the SDK, run:
npmpnpmyarnbun
npm i permissionless
[Overview](/module-sdk "Overview")
[1: Install and use your first module](/module-sdk/tutorial-1 "1: Install and use your first module")
---
# Tutorial 1: Install and use your first module – Rhinestone Docs
SDK
1: Install and use your first module
Tutorial 1: Install and use your first module
=============================================
In this tutorial, you will learn how to install and use a module on your account, in this case a Safe7579 account.
We will first set up the smart account, install the Deadman Switch Module, and then take over the account with the guardian.
### Install the packages[](#install-the-packages)
First, install the required packages. We use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { createPublicClient, Hex, http } from 'viem'
import { getAccountNonce } from 'permissionless/actions'
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
createPaymasterClient,
entryPoint07Address,
getUserOperationHash,
} from 'viem/account-abstraction'
import {
RHINESTONE_ATTESTER_ADDRESS,
MOCK_ATTESTER_ADDRESS,
getDeadmanSwitch,
getAccount,
getClient,
getDeadmanSwitchValidatorMockSignature,
getTrustAttestersAction,
encodeModuleInstallationData,
encodeValidatorNonce,
} from '@rhinestone/module-sdk'
### Create the clients[](#create-the-clients)
Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey())
### Create the Safe account[](#create-the-safe-account)
Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.
const safeAccount = await toSafeSmartAccount({
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
MOCK_ATTESTER_ADDRESS, // Mock Attester - do not use in production\
],
attestersThreshold: 1,
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Install the Deadman Switch Module[](#install-the-deadman-switch-module)
Next, we will install the Deadman Switch Module on the Safe account. This requires creating a nominee. Then, we will need to install the module as both a validator and a hook. The second time this installation happens, we do not need to pass the initialization data again, but will instead pass empty data. However, we still need to encode this empty data so that it can be correctly interpreted by the account.
const nominee = privateKeyToAccount(
'0xc171c45f3d35fad832c53cade38e8d21b8d5cc93d1887e867fac626c1c0d6be7',
)
const account = getAccount({
address: safeAccount.address,
type: 'safe',
})
const client = getClient({
rpcUrl,
})
const deadmanSwitch = await getDeadmanSwitch({
account,
client,
nominee: nominee.address,
timeout: 1,
moduleType: 'validator',
})
const opHash1 = await smartAccountClient.installModule(deadmanSwitch)
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash1,
})
const opHash2 = await smartAccountClient.installModule({
type: 'hook',
address: deadmanSwitch.module,
context: encodeModuleInstallationData({
account,
module: {
...deadmanSwitch,
initData: '0x',
type: 'hook',
},
}),
})
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash2,
})
### Wait for the timeout to expire[](#wait-for-the-timeout-to-expire)
Since we set our timeout to 1 second, we can wait for the timeout to expire. In a production environment, setting a low timeout will mean that it will be easier for a hostile nominee to take over the account.
await new Promise((resolve) => setTimeout(resolve, 10000))
### Create the takeover UserOperation[](#create-the-takeover-useroperation)
Now, we will create a UserOperation from the nominee. The calldata, in this case to the Module Registry is entirely random and a nominee will be able to do any action.
const nonce = await getAccountNonce(publicClient, {
address: safeAccount.address,
entryPointAddress: entryPoint07Address,
key: encodeValidatorNonce({ account, validator: deadmanSwitch }),
})
const trustAttestersAction = getTrustAttestersAction({
threshold: 1,
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
],
})
const userOperation = await smartAccountClient.prepareUserOperation({
account: safeAccount,
calls: [trustAttestersAction],
nonce: nonce,
signature: getDeadmanSwitchValidatorMockSignature() as Hex,
})
### Sign the taekover UserOperation[](#sign-the-taekover-useroperation)
Next, the nominee will have to sign the recovery UserOperation.
const userOpHashToSign = getUserOperationHash({
chainId: chain.id,
entryPointAddress: entryPoint07Address,
entryPointVersion: '0.7',
userOperation,
})
userOperation.signature = await nominee.signMessage({
message: { raw: userOpHashToSign },
})
### Execute the takeover UserOperation[](#execute-the-takeover-useroperation)
Finally, we can execute the UserOperation to take over the account.
const userOpHash = await smartAccountClient.sendUserOperation(userOperation)
const receipt = await pimlicoClient.waitForUserOperationReceipt({
hash: userOpHash,
})
[Getting Started](/module-sdk/getting-started "Getting Started")
[Safe](/module-sdk/account-guides/safe "Safe")
---
# How to use the Module SDK with the Safe – Rhinestone Docs
SDK
Accounts
Safe
How to use the Module SDK with the Safe
=======================================
The [Safe (opens in a new tab)](https://github.com/safe-global/safe-smart-account)
is the most widely used smart account and together with [Safe7579 (opens in a new tab)](https://github.com/rhinestonewtf/safe7579)
it is able to use ERC-7579 modules. This guide will walk you through using the Safe with Module SDK with the help of permissionless.js.
### Install the packages[](#install-the-packages)
For this guide, we use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import { createPublicClient, http, encodePacked } from 'viem'
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
createPaymasterClient,
entryPoint07Address,
} from 'viem/account-abstraction'
import {
getSocialRecoveryValidator,
RHINESTONE_ATTESTER_ADDRESS,
MOCK_ATTESTER_ADDRESS,
} from '@rhinestone/module-sdk'
### Create the clients[](#create-the-clients)
Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey())
### Create the Safe account[](#create-the-safe-account)
Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.
const safeAccount = await toSafeSmartAccount({
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
MOCK_ATTESTER_ADDRESS, // Mock Attester - do not use in production\
],
attestersThreshold: 1,
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Create the module object[](#create-the-module-object)
Get the module object for the module that you want to install on the smart account. In this case, we will install the Social Recovery Module. We will pass to it a number of guardians that can recover the account as well as a threshold of guardians required to recover the account.
const guardian1 = privateKeyToAccount(
'0xc171c45f3d35fad832c53cade38e8d21b8d5cc93d1887e867fac626c1c0d6be7',
) // the key coresponding to the first guardian
const guardian2 = privateKeyToAccount(
'0x1a4c05be22dd9294615087ba1dba4266ae68cdc320d9164dbf3650ec0db60f67',
) // the key coresponding to the second guardian
const socialRecovery = getSocialRecoveryValidator({
threshold: 2,
guardians: [guardian1.address, guardian2.address],
})
### Install the module[](#install-the-module)
With this module object, we can now install it on the smart account.
const opHash = await smartAccountClient.installModule(socialRecovery)
### Wait for the UserOperation to be confirmed[](#wait-for-the-useroperation-to-be-confirmed)
Let's wait until the UserOperation is confirmed, after which the module will be installed.
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash,
})
[1: Install and use your first module](/module-sdk/tutorial-1 "1: Install and use your first module")
[Kernel](/module-sdk/account-guides/kernel "Kernel")
---
# How to use the Module SDK with the ZeroDev SDK – Rhinestone Docs
SDK
Account SDKs
ZeroDev SDK
How to use the Module SDK with the ZeroDev SDK
==============================================
[ZeroDev SDK (opens in a new tab)](https://docs.zerodev.app/sdk/intro)
is a typescript library that makes it easy to build a dapp using the Kernel. This guide will show you how to use the Module SDK with ZeroDev SDK.
### Install the packages[](#install-the-packages)
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless @zerodev/sdk @zerodev/ecdsa-validator
### Create a signer[](#create-a-signer)
Create a to control the account with, in this case, we will use an EOA.
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
const main = async () => {
const privateKey = generatePrivateKey()
const signer = privateKeyToAccount(privateKey)
}
### Create a validator[](#create-a-validator)
Each Kernel account handles validation through a smart contract known as a "validator." In this case, we will be using the ECDSA validator.
Add the following code to create the ECDSA validator:
import { signerToEcdsaValidator } from '@zerodev/ecdsa-validator'
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer,
entryPoint,
kernelVersion: KERNEL_V3_1,
})
### Create an account[](#create-an-account)
Create the Kernel account object using the signer.
import { createKernelAccount } from '@zerodev/sdk'
const account = await createKernelAccount(publicClient, {
plugins: {
sudo: ecdsaValidator,
},
entryPoint,
}).extend(erc7579Actions({ entryPoint }))
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account.
import { createKernelAccountClient } from '@zerodev/sdk'
const smartClient = createKernelAccountClient({
account,
chain,
entryPoint,
bundlerTransport: http(BUNDLER_RPC),
middleware: {
sponsorUserOperation: async ({ userOperation }) => {
const zerodevPaymaster = createZeroDevPaymasterClient({
chain,
entryPoint,
transport: http(PAYMASTER_RPC),
})
return zerodevPaymaster.sponsorUserOperation({
userOperation,
entryPoint,
})
},
},
})
### Create the module object[](#create-the-module-object)
Get the module object for the module that you want to install on the smart account. In this case, we will install the Social Recovery Module. We will pass to it a number of guardians that can recover the account as well as a threshold of guardians required to recover the account.
import { getSocialRecoveryValidator } from '@rhinestone/module-sdk'
const module = getSocialRecoveryValidator({
threshold: 2,
guardians: ['0x1234...', '0x5678...'],
})
### Install the module[](#install-the-module)
With this module object, we can now install it on the smart account.
const context = encodePacked(
['address', 'bytes'],
[\
module.hook ?? zeroAddress,\
encodeAbiParameters(\
[{ type: 'bytes' }, { type: 'bytes' }],\
[module.initData || '0x', '0x'],\
),\
],
)
const opHash = await smartClient.installModule({
type: module.type,
address: module.module,
context,
})
### Wait for the UserOperation to be confirmed[](#wait-for-the-useroperation-to-be-confirmed)
Let's wait until the UserOperation is confirmed, after which the module will be installed.
const bundlerClient = kernelClient.extend(bundlerActions(entryPoint))
await bundlerClient.waitForUserOperationReceipt({
hash: userOpHash,
})
[Permissionless.js](/module-sdk/account-sdks/permissionless "Permissionless.js")
[Smart Sessions](/module-sdk/using-modules/smart-sessions "Smart Sessions")
---
# How to use the Module SDK with the Kernel – Rhinestone Docs
SDK
Accounts
Kernel
How to use the Module SDK with the Kernel
=========================================
The [Kernel (opens in a new tab)](https://github.com/zerodevapp/kernel)
is one of the most widely used smart accounts. In this tutorial, you will learn how to use the Module SDK with the Kernel.
### Install the packages[](#install-the-packages)
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless @zerodev/sdk @zerodev/ecdsa-validator
### Create a signer[](#create-a-signer)
Create a to control the account with, in this case, we will use an EOA.
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
const main = async () => {
const privateKey = generatePrivateKey()
const signer = privateKeyToAccount(privateKey)
}
### Create a validator[](#create-a-validator)
Each Kernel account handles validation through a smart contract known as a "validator." In this case, we will be using the ECDSA validator.
Add the following code to create the ECDSA validator:
import { signerToEcdsaValidator } from '@zerodev/ecdsa-validator'
const ecdsaValidator = await signerToEcdsaValidator(publicClient, {
signer,
entryPoint,
kernelVersion: KERNEL_V3_1,
})
### Create an account[](#create-an-account)
Create the Kernel account object using the signer.
import { createKernelAccount } from '@zerodev/sdk'
const account = await createKernelAccount(publicClient, {
plugins: {
sudo: ecdsaValidator,
},
entryPoint,
}).extend(erc7579Actions({ entryPoint }))
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account.
import { createKernelAccountClient } from '@zerodev/sdk'
const smartClient = createKernelAccountClient({
account,
chain,
entryPoint,
bundlerTransport: http(BUNDLER_RPC),
middleware: {
sponsorUserOperation: async ({ userOperation }) => {
const zerodevPaymaster = createZeroDevPaymasterClient({
chain,
entryPoint,
transport: http(PAYMASTER_RPC),
})
return zerodevPaymaster.sponsorUserOperation({
userOperation,
entryPoint,
})
},
},
})
### Create the module object[](#create-the-module-object)
Get the module object for the module that you want to install on the smart account. In this case, we will install the Social Recovery Module. We will pass to it a number of guardians that can recover the account as well as a threshold of guardians required to recover the account.
import { getSocialRecoveryValidator } from '@rhinestone/module-sdk'
const module = getSocialRecoveryValidator({
threshold: 2,
guardians: ['0x1234...', '0x5678...'],
})
### Install the module[](#install-the-module)
With this module object, we can now install it on the smart account.
const context = encodePacked(
['address', 'bytes'],
[\
module.hook ?? zeroAddress,\
encodeAbiParameters(\
[{ type: 'bytes' }, { type: 'bytes' }],\
[module.initData || '0x', '0x'],\
),\
],
)
const opHash = await smartClient.installModule({
type: module.type,
address: module.module,
context,
})
### Wait for the UserOperation to be confirmed[](#wait-for-the-useroperation-to-be-confirmed)
Let's wait until the UserOperation is confirmed, after which the module will be installed.
const bundlerClient = kernelClient.extend(bundlerActions(entryPoint))
await bundlerClient.waitForUserOperationReceipt({
hash: userOpHash,
})
[Safe](/module-sdk/account-guides/safe "Safe")
[EIP-7702](/module-sdk/account-guides/eip-7702 "EIP-7702")
---
# How to use the Module SDK with EIP-7702 – Rhinestone Docs
SDK
Accounts
EIP-7702
How to use the Module SDK with EIP-7702
=======================================
[EIP-7702 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-7702)
is an improvement to the Ethereum protocol that allows an EOA to delegate to a smart contract and be used as a smart contract account. In thi guide, we will walk you through using EIP-7702 in combination with the Safe and the Module SDK.
You can view the full source code in the [module sdk demos repository (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-demos)
.
### Install the packages[](#install-the-packages)
For this guide, we use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import {
Account,
Address,
Chain,
createPublicClient,
encodeFunctionData,
Hex,
http,
parseAbi,
toBytes,
toHex,
Transport,
zeroAddress,
} from "viem";
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { signAuthorization } from "viem/experimental";
import { writeContract } from "viem/actions";
import {
createPaymasterClient,
entryPoint07Address,
} from 'viem/account-abstraction'
import {
getSocialRecoveryValidator,
RHINESTONE_ATTESTER_ADDRESS,
getOwnableValidator,
} from '@rhinestone/module-sdk'
### Create the clients[](#create-the-clients)
Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
Note that we currently still need a Safe owner that is different to the Safe (in this case the EOA) but this is likely to change in the future.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey())
### Create the EOA to delegate from[](#create-the-eoa-to-delegate-from)
Create the EOA that you want to transform into a smart account. Note, that this EOA can already exist.
const eoa = privateKeyToAccount(generatePrivateKey())
### Create the validator to use[](#create-the-validator-to-use)
Next, we need to create a validator to use on the account. This could be an EOA based one, Webauthn or any other validator.
const ownableValidator = getOwnableValidator({
owners: [owner.address],
threshold: 1,
});
### Delegate the EOA to the Safe[](#delegate-the-eoa-to-the-safe)
To delegate to the Safe we need to do the following things:
1. Create a sponsor account (optional): this allows for the sponsor to pay the gas and not the user
2. Sign the EIP-7702 authorization to actually delegate the EOA to the Safe
3. Setup the Safe with the EOA as the owner and the validator as the Ownable Validator
Note: currently, this setup could be frontrun so that someone else initializes the users account with different owners or modules. For this reason, you shouldn't use this in production but the Safe team is working on removing this issue before EIP-7702 reaches mainnets.
const sponsorAccount = privateKeyToAccount(sponsorPrivateKey);
const authorization = await signAuthorization(publicClient, {
account: account,
contractAddress: "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762",
delegate: sponsorAccount,
});
const txHash = await writeContract(publicClient, {
address: account.address,
abi: parseAbi([\
"function setup(address[] calldata _owners,uint256 _threshold,address to,bytes calldata data,address fallbackHandler,address paymentToken,uint256 payment, address paymentReceiver) external",\
]),
functionName: "setup",
args: [\
[safeOwner.address],\
BigInt(1),\
"0x7579011aB74c46090561ea277Ba79D510c6C00ff",\
encodeFunctionData({\
abi: parseAbi([\
"struct ModuleInit {address module;bytes initData;}",\
"function addSafe7579(address safe7579,ModuleInit[] calldata validators,ModuleInit[] calldata executors,ModuleInit[] calldata fallbacks, ModuleInit[] calldata hooks,address[] calldata attesters,uint8 threshold) external",\
]),\
functionName: "addSafe7579",\
args: [\
"0x7579EE8307284F293B1927136486880611F20002",\
[\
{\
module: ownableValidator.address,\
initData: ownableValidator.initData,\
},\
],\
[],\
[],\
[],\
[\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
],\
1,\
],\
}),\
"0x7579EE8307284F293B1927136486880611F20002",\
zeroAddress,\
BigInt(0),\
zeroAddress,\
],
account: sponsorAccount,
authorizationList: [authorization],
});
await publicClient.waitForTransactionReceipt({
hash: txHash,
});
### Create the Safe account client[](#create-the-safe-account-client)
Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.
const safeAccount = await toSafeSmartAccount({
address: eoa.address,
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Create the module object[](#create-the-module-object)
Get the module object for the module that you want to install on the smart account. In this case, we will install the Social Recovery Module. We will pass to it a number of guardians that can recover the account as well as a threshold of guardians required to recover the account.
const guardian1 = privateKeyToAccount(
'0xc171c45f3d35fad832c53cade38e8d21b8d5cc93d1887e867fac626c1c0d6be7',
) // the key coresponding to the first guardian
const guardian2 = privateKeyToAccount(
'0x1a4c05be22dd9294615087ba1dba4266ae68cdc320d9164dbf3650ec0db60f67',
) // the key coresponding to the second guardian
const socialRecovery = getSocialRecoveryValidator({
threshold: 2,
guardians: [guardian1.address, guardian2.address],
})
### Install the module[](#install-the-module)
With this module object, we can now install it on the smart account.
const opHash = await smartAccountClient.installModule(socialRecovery)
### Wait for the UserOperation to be confirmed[](#wait-for-the-useroperation-to-be-confirmed)
Let's wait until the UserOperation is confirmed, after which the module will be installed.
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash,
})
[Kernel](/module-sdk/account-guides/kernel "Kernel")
[Permissionless.js](/module-sdk/account-sdks/permissionless "Permissionless.js")
---
# How to use passkeys to control an account – Rhinestone Docs
SDK
Using Modules
WebAuthn
How to use passkeys to control an account
=========================================
This guide walks you through using passkeys to make transactions on an account, using the Webauthn Module. In this case, we will use the Safe account and install the module on it, but the module also works on other ERC-7579 accounts. You can also check out the [entire code (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-demos/blob/main/src/app/webauthn/page.tsx)
of the guide.
We will first set up the smart account, install the Webauthn Module, and use a passkey to make a transaction.
### Install the packages[](#install-the-packages)
First, install the required packages. We use the latest version of module sdk, permissionless ^0.2, viem ^2.21 and ox ^0.6.0.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless ox
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import {
toSafeSmartAccount,
ToSafeSmartAccountReturnType,
} from "permissionless/accounts";
import { http, Transport } from "viem";
import { createSmartAccountClient, SmartAccountClient } from "permissionless";
import {
createWebAuthnCredential,
entryPoint07Address,
getUserOperationHash,
P256Credential,
} from "viem/account-abstraction";
import {
RHINESTONE_ATTESTER_ADDRESS,
MOCK_ATTESTER_ADDRESS,
getOwnableValidator,
encodeValidatorNonce,
getAccount,
getWebauthnValidatorMockSignature,
getWebAuthnValidator,
WEBAUTHN_VALIDATOR_ADDRESS,
getWebauthnValidatorSignature,
} from "@rhinestone/module-sdk";
import { baseSepolia } from "viem/chains";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { getAccountNonce } from "permissionless/actions";
import { erc7579Actions } from "permissionless/actions/erc7579";
import { PublicKey } from "ox";
import { sign } from "ox/WebAuthnP256";
### Create the clients[](#create-the-clients)
Create the smart account client and the pimlico client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: baseSepolia,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey());
### Create the Safe account[](#create-the-safe-account)
Create the Safe account object using the signer.
const safeAccount = await toSafeSmartAccount({
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
],
attestersThreshold: 1,
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: baseSepolia,
bundlerTransport: http(bundlerUrl),
paymaster: pimlicoClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Create the Webauthn Credential[](#create-the-webauthn-credential)
Next, we will create a Webauthn Credential. This will be used to sign the UserOperation.
// You could also use the `createCredential` function from the `ox` package to create the credential.
const credential = await createWebAuthnCredential({
name: "Wallet Owner",
});
### Install the Webauthn Module[](#install-the-webauthn-module)
Next, we will install the Webauthn Module on the Safe account so that the user can use their passkey to sign a UserOperation.
const { x, y, prefix } = PublicKey.from(credential.publicKey);
const validator = getWebAuthnValidator({
pubKey: { x, y, prefix },
authenticatorId: credential.id,
});
const installOp = await smartAccountClient.installModule(validator);
const receipt = await smartAccountClient.waitForUserOperationReceipt({
hash: installOp,
});
### Create a UserOperation[](#create-a-useroperation)
Now, we will create a UserOperation to execute.
const nonce = await getAccountNonce(publicClient, {
address: smartAccountClient.account.address,
entryPointAddress: entryPoint07Address,
key: encodeValidatorNonce({
account: getAccount({
address: smartAccountClient.account.address,
type: "safe",
}),
validator: WEBAUTHN_VALIDATOR_ADDRESS,
}),
});
const userOperation = await smartAccountClient.prepareUserOperation({
account: smartAccountClient.account,
calls: [\
{\
to: "0x19575934a9542be941d3206f3ecff4a5ffb9af88",\
value: BigInt(0),\
data: "0xd09de08a",\
} // this call increments a counter on a counter contract - note that this contract might need to be deployed depending on which network this is used on\
],
nonce,
signature: getWebauthnValidatorMockSignature(),
});
const userOpHashToSign = getUserOperationHash({
chainId: baseSepolia.id,
entryPointAddress: entryPoint07Address,
entryPointVersion: "0.7",
userOperation,
});
### Sign the UserOperation with the passkey[](#sign-the-useroperation-with-the-passkey)
Next, the nominee will have to sign the recovery UserOperation.
const { metadata: webauthn, signature } = await sign({
credentialId: credential.id,
challenge: userOpHashToSign,
});
### Encode the signature[](#encode-the-signature)
Finally, we will encode the signature and add it to the UserOperation.
const encodedSignature = getWebauthnValidatorSignature({
webauthn,
signature,
usePrecompiled: false,
});
userOperation.signature = encodedSignature;
### Execute the UserOperation[](#execute-the-useroperation)
Finally, we can execute the UserOperation.
const userOpHash =
await smartAccountClient.sendUserOperation(userOperation);
const receipt = await smartAccountClient.waitForUserOperationReceipt({
hash: userOpHash,
});
[Smart Sessions](/module-sdk/using-modules/smart-sessions "Smart Sessions")
[Social Recovery](/module-sdk/using-modules/social-recovery "Social Recovery")
---
# How to secure and recovery an account using the Deadman Switch Module – Rhinestone Docs
SDK
Using Modules
Deadman Switch
How to secure and recovery an account using the Deadman Switch Module
=====================================================================
The Deadman Switch Module allows users to specify an inactivity period and a nominee. If the period of time expires without the account owner doing a transaction, the nominee will be able to take over the account. This guide will show you how to install and use the Deadman Switch module on a Safe smart account using the permissionless.js SDK. You can also check out the [entire code (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-tutorials/blob/main/src/deadman-switch/permissionless-safe.ts)
of the guide.
We will first set up the smart account, install the Deadman Switch Module, and then take over the account with the guardian.
### Install the packages[](#install-the-packages)
First, install the required packages. We use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { createPublicClient, Hex, http } from 'viem'
import { getAccountNonce } from 'permissionless/actions'
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
createPaymasterClient,
entryPoint07Address,
getUserOperationHash,
} from 'viem/account-abstraction'
import {
RHINESTONE_ATTESTER_ADDRESS,
MOCK_ATTESTER_ADDRESS,
getDeadmanSwitch,
getAccount,
getClient,
getDeadmanSwitchValidatorMockSignature,
getTrustAttestersAction,
encodeModuleInstallationData,
encodeValidatorNonce,
} from '@rhinestone/module-sdk'
### Create the clients[](#create-the-clients)
Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey())
### Create the Safe account[](#create-the-safe-account)
Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.
const safeAccount = await toSafeSmartAccount({
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
MOCK_ATTESTER_ADDRESS, // Mock Attester - do not use in production\
],
attestersThreshold: 1,
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Install the Deadman Switch Module[](#install-the-deadman-switch-module)
Next, we will install the Deadman Switch Module on the Safe account. This requires creating a nominee. Then, we will need to install the module as both a validator and a hook. The second time this installation happens, we do not need to pass the initialization data again, but will instead pass empty data. However, we still need to encode this empty data so that it can be correctly interpreted by the account.
const nominee = privateKeyToAccount(
'0xc171c45f3d35fad832c53cade38e8d21b8d5cc93d1887e867fac626c1c0d6be7',
)
const account = getAccount({
address: safeAccount.address,
type: 'safe',
})
const client = getClient({
rpcUrl,
})
const deadmanSwitch = await getDeadmanSwitch({
account,
client,
nominee: nominee.address,
timeout: 1,
moduleType: 'validator',
})
const opHash1 = await smartAccountClient.installModule(deadmanSwitch)
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash1,
})
const opHash2 = await smartAccountClient.installModule({
type: 'hook',
address: deadmanSwitch.module,
context: encodeModuleInstallationData({
account,
module: {
...deadmanSwitch,
initData: '0x',
type: 'hook',
},
}),
})
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash2,
})
### Wait for the timeout to expire[](#wait-for-the-timeout-to-expire)
Since we set our timeout to 1 second, we can wait for the timeout to expire. In a production environment, setting a low timeout will mean that it will be easier for a hostile nominee to take over the account.
await new Promise((resolve) => setTimeout(resolve, 10000))
### Create the takeover UserOperation[](#create-the-takeover-useroperation)
Now, we will create a UserOperation from the nominee. The calldata, in this case to the Module Registry is entirely random and a nominee will be able to do any action.
const nonce = await getAccountNonce(publicClient, {
address: safeAccount.address,
entryPointAddress: entryPoint07Address,
key: encodeValidatorNonce({ account, validator: deadmanSwitch }),
})
const trustAttestersAction = getTrustAttestersAction({
threshold: 1,
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
],
})
const userOperation = await smartAccountClient.prepareUserOperation({
account: safeAccount,
calls: [trustAttestersAction],
nonce: nonce,
signature: getDeadmanSwitchValidatorMockSignature() as Hex,
})
### Sign the taekover UserOperation[](#sign-the-taekover-useroperation)
Next, the nominee will have to sign the recovery UserOperation.
const userOpHashToSign = getUserOperationHash({
chainId: chain.id,
entryPointAddress: entryPoint07Address,
entryPointVersion: '0.7',
userOperation,
})
userOperation.signature = await nominee.signMessage({
message: { raw: userOpHashToSign },
})
### Execute the takeover UserOperation[](#execute-the-takeover-useroperation)
Finally, we can execute the UserOperation to take over the account.
const userOpHash = await smartAccountClient.sendUserOperation(userOperation)
const receipt = await pimlicoClient.waitForUserOperationReceipt({
hash: userOpHash,
})
[Social Recovery](/module-sdk/using-modules/social-recovery "Social Recovery")
[Smart Sessions](/module-sdk/modules/smart-sessions "Smart Sessions")
---
# How to use the Module SDK with Permissionless.js – Rhinestone Docs
SDK
Account SDKs
Permissionless.js
How to use the Module SDK with Permissionless.js
================================================
[Permissionless.js (opens in a new tab)](https://docs.pimlico.io/permissionless)
is one of the most widely used account SDKs. It is a typescript library that makes it easy to build with smart accounts. This guide will show you how to use the Module SDK with permissionless.js.
### Install the packages[](#install-the-packages)
For this guide, we use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import { createPublicClient, http, encodePacked } from 'viem'
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
createPaymasterClient,
entryPoint07Address,
} from 'viem/account-abstraction'
import {
getSocialRecoveryValidator,
RHINESTONE_ATTESTER_ADDRESS,
MOCK_ATTESTER_ADDRESS,
} from '@rhinestone/module-sdk'
### Create the clients[](#create-the-clients)
Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey())
### Create the Safe account[](#create-the-safe-account)
Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.
const safeAccount = await toSafeSmartAccount({
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
MOCK_ATTESTER_ADDRESS, // Mock Attester - do not use in production\
],
attestersThreshold: 1,
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Create the module object[](#create-the-module-object)
Get the module object for the module that you want to install on the smart account. In this case, we will install the Social Recovery Module. We will pass to it a number of guardians that can recover the account as well as a threshold of guardians required to recover the account.
const guardian1 = privateKeyToAccount(
'0xc171c45f3d35fad832c53cade38e8d21b8d5cc93d1887e867fac626c1c0d6be7',
) // the key coresponding to the first guardian
const guardian2 = privateKeyToAccount(
'0x1a4c05be22dd9294615087ba1dba4266ae68cdc320d9164dbf3650ec0db60f67',
) // the key coresponding to the second guardian
const socialRecovery = getSocialRecoveryValidator({
threshold: 2,
guardians: [guardian1.address, guardian2.address],
})
### Install the module[](#install-the-module)
With this module object, we can now install it on the smart account.
const opHash = await smartAccountClient.installModule(socialRecovery)
### Wait for the UserOperation to be confirmed[](#wait-for-the-useroperation-to-be-confirmed)
Let's wait until the UserOperation is confirmed, after which the module will be installed.
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash,
})
[EIP-7702](/module-sdk/account-guides/eip-7702 "EIP-7702")
[ZeroDev SDK](/module-sdk/account-sdks/zerodev "ZeroDev SDK")
---
# How to use session keys using Smart Sessions – Rhinestone Docs
SDK
Using Modules
Smart Sessions
How to use session keys using Smart Sessions
============================================
The Smart Sessions Module allows developers to create session keys with scoped permissions and access rights on a users account. This allows the user to determine exactly what an app is allowed to do on its' behalf and enforce this onchain. This guide will show you how to set up and use Smart Sessions with a Safe smart account using permissionless.js SDK. You can also view the [entire code (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-tutorials/blob/main/src/smart-sessions/permissionless-safe.ts)
of this guide.
We will first set up the smart account, install the Smart Sessions Module, create a session and then use this new session to execute a UserOperation.
For futher guides, check out the [module-sdk-demos (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-demos/)
, which contains an example of using smart sessions on the frontend. You can also try out the [hosted demo (opens in a new tab)](https://module-demos.rhinestone.wtf/smart-sessions)
.
### Install the packages[](#install-the-packages)
First, install the required packages. We use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { getAccountNonce } from 'permissionless/actions'
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
toHex,
Address,
Hex,
createPublicClient,
http,
Chain,
toBytes,
} from 'viem'
import {
entryPoint07Address,
getUserOperationHash,
createPaymasterClient,
} from 'viem/account-abstraction'
import {
getSmartSessionsValidator,
OWNABLE_VALIDATOR_ADDRESS,
getSudoPolicy,
Session,
getAccount,
encodeSmartSessionSignature,
getOwnableValidatorMockSignature,
RHINESTONE_ATTESTER_ADDRESS,
MOCK_ATTESTER_ADDRESS,
encodeValidatorNonce,
getOwnableValidator,
encodeValidationData,
getEnableSessionDetails,
} from '@rhinestone/module-sdk'
### Create the clients[](#create-the-clients)
Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey())
### Create the initial validator[](#create-the-initial-validator)
We will also create and add an initial validator. In this guide, we will use the ownable validator to create the signature to enable the session on the user's account.
const ownableValidator = getOwnableValidator({
owners: [owner.address],
threshold: 1,
})
### Create the Safe account[](#create-the-safe-account)
Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.
const safeAccount = await toSafeSmartAccount({
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
MOCK_ATTESTER_ADDRESS, // Mock Attester - do not use in production\
],
attestersThreshold: 1,
validators: [\
{\
address: ownableValidator.address,\
context: ownableValidator.initData,\
},\
],
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Install the Smart Sessions Module[](#install-the-smart-sessions-module)
Next, we will install the Smart Sessions Module on the account. We could pass it a new session to create when installing, but in this guide we will instead demonstrate the "enable" flow of smart sessions.
const smartSessions = getSmartSessionsValidator({})
const opHash = await smartAccountClient.installModule(smartSessions)
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash,
})
### Create the session to enable[](#create-the-session-to-enable)
Now we will create a new session to enable. This session will be scoped to allow a single action, to the specified target and with the (in this case empty) target selector. For this action, we install the `SudoPolicy` which will always allow this action.
const sessionOwner = privateKeyToAccount(generatePrivateKey())
const session: Session = {
sessionValidator: OWNABLE_VALIDATOR_ADDRESS,
sessionValidatorInitData: encodeValidationData({
threshold: 1,
owners: [sessionOwner.address],
}),
salt: toHex(toBytes('0', { size: 32 })),
userOpPolicies: [getSudoPolicy()],
erc7739Policies: {
allowedERC7739Content: [],
erc1271Policies: [],
},
actions: [\
{\
actionTarget: '0xa564cB165815937967a7d018B7F34B907B52fcFd' as Address, // an address as the target of the session execution\
actionTargetSelector: '0x00000000' as Hex, // function selector to be used in the execution, in this case no function selector is used\
actionPolicies: [getSudoPolicy()],\
},\
],
chainId: BigInt(chain.id),
permitERC4337Paymaster: true,
}
### Get the session details[](#get-the-session-details)
Now that we have the session, we will generate the details to enable the session. For this, we will need to pass an account and client object. These details include the correctly formulated data about the exact session to encode. However, all of this is abstracted away and you can just use the function below to easily get these details and then use them to enable a new session.
const account = getAccount({
address: safeAccount.address,
type: 'safe',
})
const sessionDetails = await getEnableSessionDetails({
sessions: [session],
account,
clients: [publicClient],
})
### Have the user sign the enable signature[](#have-the-user-sign-the-enable-signature)
Next, the user will need to sign the `permissionEnableHash`. How exactly this is done depends on how your application works and which validator the user currently has installed.
In this case, we will assume that an ownable validator is already installed on the account, so we can use the users' EOA to sign the hash.
sessionDetails.enableSessionData.enableSession.permissionEnableSig =
await owner.signMessage({
message: { raw: sessionDetails.permissionEnableHash },
})
### Create the UserOperation to execute[](#create-the-useroperation-to-execute)
Now that we have the session set up, we can create the UserOperation. Since we scope this session for a specific target and selector, we will use those to create the calldata for the UserOperation.
const nonce = await getAccountNonce(publicClient, {
address: safeAccount.address,
entryPointAddress: entryPoint07Address,
key: encodeValidatorNonce({
account,
validator: smartSessions,
}),
})
sessionDetails.signature = getOwnableValidatorMockSignature({
threshold: 1,
})
const userOperation = await smartAccountClient.prepareUserOperation({
account: safeAccount,
calls: [\
{\
to: session.actions[0].actionTarget,\
value: BigInt(0),\
data: session.actions[0].actionTargetSelector,\
},\
],
nonce,
signature: encodeSmartSessionSignature(sessionDetails),
})
### Create the session key signature[](#create-the-session-key-signature)
Next, we will use the session key to sign the UserOperation.
const userOpHashToSign = getUserOperationHash({
chainId: chain.id,
entryPointAddress: entryPoint07Address,
entryPointVersion: '0.7',
userOperation,
})
sessionDetails.signature = await sessionOwner.signMessage({
message: { raw: userOpHashToSign },
})
userOperation.signature = encodeSmartSessionSignature(sessionDetails)
### Execute the UserOperation[](#execute-the-useroperation)
Finally, we can execute the UserOperation to test the session.
const userOpHash = await smartAccountClient.sendUserOperation(userOperation)
const receipt = await pimlicoClient.waitForUserOperationReceipt({
hash: userOpHash,
})
[ZeroDev SDK](/module-sdk/account-sdks/zerodev "ZeroDev SDK")
[WebAuthn](/module-sdk/using-modules/webauthn "WebAuthn")
---
# How to recover an account using the Social Recovery Module – Rhinestone Docs
SDK
Using Modules
Social Recovery
How to recover an account using the Social Recovery Module
==========================================================
The Social Recovery Module allows users to add a set of guardians to their account so that they can recover it in case they lose access to it. This guide will show you how to install and use the Social Recovery Module on a Safe smart account using the permissionless.js SDK. You can also check out the [entire code (opens in a new tab)](https://github.com/rhinestonewtf/module-sdk-tutorials/blob/main/src/social-recovery/permissionless-safe.ts)
of the guide.
We will first set up the smart account, install the Social Recovery Module, and then recover the account using the guardians.
### Install the packages[](#install-the-packages)
First, install the required packages. We use the latest version of module sdk, permissionless ^0.2 and viem ^2.21.
npmpnpmyarnbun
npm i viem @rhinestone/module-sdk permissionless
### Import the required functions and constants[](#import-the-required-functions-and-constants)
import { getAccountNonce } from 'permissionless/actions'
import { createSmartAccountClient } from 'permissionless'
import { toSafeSmartAccount } from 'permissionless/accounts'
import { erc7579Actions } from 'permissionless/actions/erc7579'
import { createPublicClient, http, encodePacked, pad } from 'viem'
import { createPimlicoClient } from 'permissionless/clients/pimlico'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import {
createPaymasterClient,
entryPoint07Address,
getUserOperationHash,
} from 'viem/account-abstraction'
import {
getSetOwnableValidatorThresholdAction,
getSocialRecoveryValidator,
getOwnableValidator,
getSocialRecoveryMockSignature,
RHINESTONE_ATTESTER_ADDRESS,
MOCK_ATTESTER_ADDRESS,
encodeValidatorNonce,
getAccount,
} from '@rhinestone/module-sdk'
### Create the clients[](#create-the-clients)
Create the smart account client, the bundler client and the paymaster client. You will need to add your own urls here.
const publicClient = createPublicClient({
transport: http(rpcUrl),
chain: chain,
})
const pimlicoClient = createPimlicoClient({
transport: http(bundlerUrl),
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
})
const paymasterClient = createPaymasterClient({
transport: http(paymasterUrl),
})
### Create the signer[](#create-the-signer)
The Safe account will need to have a signer to sign user operations. In permissionless.js, the default Safe account validates ECDSA signatures.
For example, to create a signer based on a private key:
const owner = privateKeyToAccount(generatePrivateKey())
### Create the initial validator[](#create-the-initial-validator)
We will also create and add an initial validator. We are using the Ownable Validator which can be used to verify UserOperations using one or more EOA owners. In this guide, we are installing the validator to then recover it using social recovery.
const ownableValidator = getOwnableValidator({
owners: [\
'0x2DC2fb2f4F11DeE1d6a2054ffCBf102D09b62bE2',\
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',\
],
threshold: 2,
})
### Create the Safe account[](#create-the-safe-account)
Create the Safe account object using the signer. Note that you should only use the `MockAttester` on testnets.
const safeAccount = await toSafeSmartAccount({
client: publicClient,
owners: [owner],
version: '1.4.1',
entryPoint: {
address: entryPoint07Address,
version: '0.7',
},
safe4337ModuleAddress: '0x7579EE8307284F293B1927136486880611F20002',
erc7579LaunchpadAddress: '0x7579011aB74c46090561ea277Ba79D510c6C00ff',
attesters: [\
RHINESTONE_ATTESTER_ADDRESS, // Rhinestone Attester\
MOCK_ATTESTER_ADDRESS, // Mock Attester - do not use in production\
],
attestersThreshold: 1,
validators: [\
{\
address: ownableValidator.address,\
context: ownableValidator.initData,\
},\
],
})
### Create the smart account client[](#create-the-smart-account-client)
The smart account client is used to interact with the smart account. You will need to add your own bundler url and the chain that you are using.
const smartAccountClient = createSmartAccountClient({
account: safeAccount,
chain: chain,
bundlerTransport: http(bundlerUrl),
paymaster: paymasterClient,
userOperation: {
estimateFeesPerGas: async () => {
return (await pimlicoClient.getUserOperationGasPrice()).fast
},
},
}).extend(erc7579Actions())
### Install the Social Recovery Module[](#install-the-social-recovery-module)
Next, we will install the Social Recovery Module on the account. We will pass to it a number of guardians that can recover the account as well as a threshold of guardians required to recover the account.
const guardian1 = privateKeyToAccount(
'0xc171c45f3d35fad832c53cade38e8d21b8d5cc93d1887e867fac626c1c0d6be7',
) // the key coresponding to the first guardian
const guardian2 = privateKeyToAccount(
'0x1a4c05be22dd9294615087ba1dba4266ae68cdc320d9164dbf3650ec0db60f67',
) // the key coresponding to the second guardian
const socialRecovery = getSocialRecoveryValidator({
threshold: 2,
guardians: [guardian1.address, guardian2.address],
})
const opHash1 = await smartAccountClient.installModule(socialRecovery)
await pimlicoClient.waitForUserOperationReceipt({
hash: opHash1,
})
### Get the recovery calldata[](#get-the-recovery-calldata)
Finally, we will recover the account using the guardians. In this case, lets assume that the user has lost access to the first signer but still has access to the second one. In this case, the easiest way to recover is to reduce the threshold on the ownable validator to 1 and then the user can add another signer instead.
const recoveryAction = getSetOwnableValidatorThresholdAction({
threshold: 1,
})
### Create the recovery UserOperation[](#create-the-recovery-useroperation)
Now that we have the calldata, we can create the UserOperation to recover the account.
const nonce = await getAccountNonce(publicClient, {
address: safeAccount.address,
entryPointAddress: entryPoint07Address,
key: encodeValidatorNonce({
account: getAccount({
address: safeAccount.address,
type: 'safe',
}),
validator: socialRecovery,
}),
})
const userOperation = await smartAccountClient.prepareUserOperation({
account: safeAccount,
calls: [recoveryAction],
nonce: nonce,
signature: getSocialRecoveryMockSignature({
threshold: 2,
}),
})
### Sign the recovery UserOperation[](#sign-the-recovery-useroperation)
Next, the guardians will have to sign the recovery UserOperation.
const userOpHashToSign = getUserOperationHash({
chainId: chain.id,
entryPointAddress: entryPoint07Address,
entryPointVersion: '0.7',
userOperation,
})
const signature1 = await guardian1.signMessage({
message: { raw: userOpHashToSign },
})
const signature2 = await guardian2.signMessage({
message: { raw: userOpHashToSign },
})
userOperation.signature = encodePacked(
['bytes', 'bytes'],
[signature1, signature2],
)
### Execute the recovery UserOperation[](#execute-the-recovery-useroperation)
Finally, we can execute the UserOperation to recover the account.
const userOpHash = await smartAccountClient.sendUserOperation(userOperation)
const receipt = await pimlicoClient.waitForUserOperationReceipt({
hash: userOpHash,
})
[WebAuthn](/module-sdk/using-modules/webauthn "WebAuthn")
[Deadman Switch](/module-sdk/using-modules/deadman-switch "Deadman Switch")
---
# Smart Sessions – Rhinestone Docs
SDK
Modules
Smart Sessions
Smart Sessions
==============
Smart Sessions allows developers to use session keys with any ERC-7579 smart account. It further combines granular permissions with session keys in the form of policies, which restrict what the key is allowed to do.
How it works[](#how-it-works)
------------------------------
Smart Sessions consists of two modular components: stateless validators and policies. Stateless validators verify the siganture made by session keys and policies restrcit what actions a session key is allowed to do. Module SDK currently supports the following policies: spending limits, universal action and sudo.
Getting started[](#getting-started)
------------------------------------
To get started with Smart Sessions, you need to install the validators on the account. Either during installation or afterwards, you will need to create a new session and enable it. If this happens after installation, it is possible to enable a session using a signature and include this in the next UserOperation. This will both enable and use the session in one go.
[Deadman Switch](/module-sdk/using-modules/deadman-switch "Deadman Switch")
[getSudoPolicy](/module-sdk/modules/smart-sessions/policies/getSudoPolicy "getSudoPolicy")
---
# getSudoPolicy – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
Policies
getSudoPolicy
getSudoPolicy
=============
Get the sudo policy to be used when creating a new session. The sudo policy is an action policy that will allow any action for the specified target and selector.
Usage[](#usage)
----------------
const sudoPolicy = getSudoPolicy()
Returns[](#returns)
--------------------
### policy[](#policy)
* Type: [`Policy`](/module-sdk/glossary/types#policy-1)
The sudo policy.
[Smart Sessions](/module-sdk/modules/smart-sessions "Smart Sessions")
[getSpendingLimitsPolicy](/module-sdk/modules/smart-sessions/policies/getSpendingLimitsPolicy "getSpendingLimitsPolicy")
---
# getSpendingLimitsPolicy – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
Policies
getSpendingLimitsPolicy
getSpendingLimitsPolicy
=======================
Get the spending limits policy to use when creating a new session. The spending limits policy can be used to ensure that only a certain amount of ERC-20 tokens can be spent. For native value spends, use the value limit policy.
Usage[](#usage)
----------------
const spendingLimitsPolicy = getSpendingLimitsPolicy([\
{\
token: '0x1234...',\
limit: 100,\
},\
])
Parameters[](#parameters)
--------------------------
### TokenWithLimits[](#tokenwithlimits)
* Type: [`TokenWithLimit[]`](/module-sdk/glossary/types#tokenwithlimit)
Token with limits array. The limit is the maximum amount of the token that can be spent.
Returns[](#returns)
--------------------
### policy[](#policy)
* Type: [`Policy`](/module-sdk/glossary/types#policy-1)
The spending limits policy.
[getSudoPolicy](/module-sdk/modules/smart-sessions/policies/getSudoPolicy "getSudoPolicy")
[getUniversalActionPolicy](/module-sdk/modules/smart-sessions/policies/getUniversalActionPolicy "getUniversalActionPolicy")
---
# getUniversalActionPolicy – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
Policies
getUniversalActionPolicy
getUniversalActionPolicy
========================
Get the universal action policy to use when creating a new session. The universal action policy can be used to ensure that only actions where the calldata has certain parameters can be used. For example, it could restrict swaps on Uniswap to be only under X amount of input token.
Usage[](#usage)
----------------
const universalActionPolicy = getUniversalActionPolicy({
paramRules: {
length: 1,
rules: new Array(16).fill({
condition: ParamCondition.EQUAL,
isLimited: false,
offset: 0,
ref: toHex(toBytes('0x', { size: 32 })),
usage: { limit: BigInt(0), used: BigInt(0) },
}),
},
valueLimitPerUse: BigInt(100),
})
Parameters[](#parameters)
--------------------------
### paramRules[](#paramrules)
* Type: [`paramRules`](/module-sdk/glossary/types#paramrules-1)
Parameter rules.
valueLimitPerUse[](#valuelimitperuse)
--------------------------------------
* Type: `bigint`
Value limit per use.
Returns[](#returns)
--------------------
### policy[](#policy)
* Type: [`Policy`](/module-sdk/glossary/types#policy-1)
The policy object.
[getSpendingLimitsPolicy](/module-sdk/modules/smart-sessions/policies/getSpendingLimitsPolicy "getSpendingLimitsPolicy")
[getTimeFramePolicy](/module-sdk/modules/smart-sessions/policies/getTimeFramePolicy "getTimeFramePolicy")
---
# getUsageLimitPolicy – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
Policies
getUsageLimitPolicy
getUsageLimitPolicy
===================
Get the usage limits policy to use when creating a new session. The usage limit policy is used to restrict a sesion to only be able to be used a certain number of times.
Usage[](#usage)
----------------
const usageLimitPolicy = getUsageLimitPolicy({
limit: BigInt(100),
})
Parameters[](#parameters)
--------------------------
### limit[](#limit)
* Type: `bigint`
The maximum number of times the session can be used.
Returns[](#returns)
--------------------
### policy[](#policy)
* Type: [`Policy`](/module-sdk/glossary/types#policy-1)
The spending limits policy.
[getTimeFramePolicy](/module-sdk/modules/smart-sessions/policies/getTimeFramePolicy "getTimeFramePolicy")
[getValueLimitPolicy](/module-sdk/modules/smart-sessions/policies/getValueLimitPolicy "getValueLimitPolicy")
---
# getEnableSessionsAction – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
getEnableSessionsAction
getEnableSessionsAction
=======================
Get the action to enable new sessions on smart sessions. Note that this action cannot be used to change an existing session, but only to create new ones.
Usage[](#usage)
----------------
const action = await getEnableSessionsAction({
sessions: [\
{\
sessionValidator: '0x1234...',\
sessionValidatorInitData: '0xabcd...',\
salt: toHex(toBytes('1', { size: 32 })),\
userOpPolicies: [],\
erc7739Policies: {\
allowedERC7739Content: [],\
erc1271Policies: [],\
},\
actions: [\
{\
actionTarget: '0x1234...',\
actionTargetSelector: '0x00112233',\
actionPolicies: [\
{\
policy: '0x1234...',\
initData: '0xabcd...',\
},\
],\
},\
],\
permitERC4337Paymaster: false,\
},\
],
})
Parameters[](#parameters)
--------------------------
### sessions[](#sessions)
* Type: [`Session[]`](/module-sdk/glossary/types#session)
List of sessions to enable in the smart sessions validator.
Returns[](#returns)
--------------------
### action[](#action)
* Type: [`Promise`](/module-sdk/glossary/types#action)
The action to enable sessions in the smart sessions validator.
[getPermissions](/module-sdk/modules/smart-sessions/getPermissions "getPermissions")
[getRemoveSessionAction](/module-sdk/modules/smart-sessions/getRemoveSessionAction "getRemoveSessionAction")
---
# getValueLimitPolicy – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
Policies
getValueLimitPolicy
getValueLimitPolicy
===================
Get the value limit policy to use when creating a new session. The value limit policy can be used to enforce that only a certain amount of native value can be spent. For ERC-20 limits, use the spending limit policy.
Usage[](#usage)
----------------
const valueLimitPolicy = getValueLimitPolicy({
limit: BigInt(100),
})
Parameters[](#parameters)
--------------------------
### limit[](#limit)
* Type: `bigint`
The maximum amount of native value that can be spent.
Returns[](#returns)
--------------------
### policy[](#policy)
* Type: [`Policy`](/module-sdk/glossary/types#policy-1)
The value limit policy.
[getUsageLimitPolicy](/module-sdk/modules/smart-sessions/policies/getUsageLimitPolicy "getUsageLimitPolicy")
[getSmartSessionsValidator](/module-sdk/modules/smart-sessions/getSmartSessionsValidator "getSmartSessionsValidator")
---
# getSmartSessionsValidator – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
getSmartSessionsValidator
getSmartSessionsValidator
=========================
Get the smart sessions module object.
Usage[](#usage)
----------------
const module = getSmartSessionsValidator({
sessions: [\
{\
sessionValidator: '0x1234...',\
sessionValidatorInitData: '0xabcd...',\
salt: toHex(toBytes('1', { size: 32 })),\
userOpPolicies: [],\
erc7739Policies: {\
allowedERC7739Content: [],\
erc1271Policies: [],\
},\
actions: [\
{\
actionTarget: '0x1234...',\
actionTargetSelector: '0x00112233',\
actionPolicies: [\
{\
policy: '0x1234...',\
initData: '0xabcd...',\
},\
],\
},\
],\
permitERC4337Paymaster: false,\
},\
],
})
Parameters[](#parameters)
--------------------------
### sessions[](#sessions)
* Type: [`Session[]`](/module-sdk/glossary/types#session)
List of sessions to enable in the smart sessions validator.
### useRegistry (optional)[](#useregistry-optional)
* Type: `boolean`
Defaults to true. When installing the module during account creation, it could be the case that the account, such as Safe7579 or Nexus, installs the module before setting their trusted attesters on the Registry. In this case, the flag should be set to false.
### hook (optional)[](#hook-optional)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the hook to use. This is only required for the Kernel account.
Returns[](#returns)
--------------------
### module[](#module)
* Type: [`Module` (opens in a new tab)](https://viem.sh/docs/glossary/types#module)
The smart sessions validator module object.
[getValueLimitPolicy](/module-sdk/modules/smart-sessions/policies/getValueLimitPolicy "getValueLimitPolicy")
[getPermissions](/module-sdk/modules/smart-sessions/getPermissions "getPermissions")
---
# getTimeFramePolicy – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
Policies
getTimeFramePolicy
getTimeFramePolicy
==================
Get the timeframe policy to use when creating a new session. The timeframe policy can be used to restrict a session to only be able to be used within a certain timeframe.
Usage[](#usage)
----------------
const timeFramePolicy = getTimeFramePolicy({
validAfter: 0, // always valid start
validUntil: Date.now() + 60 * 60 * 24, // valid for 24 hours
})
Parameters[](#parameters)
--------------------------
### validAfter[](#validafter)
* Type: `number`
The timestamp after which the session is valid in seconds.
### validUntil[](#validuntil)
* Type: `number`
The timestamp until which the session is valid in seconds.
Returns[](#returns)
--------------------
### policy[](#policy)
* Type: [`Policy`](/module-sdk/glossary/types#policy-1)
The timeframe policy.
[getUniversalActionPolicy](/module-sdk/modules/smart-sessions/policies/getUniversalActionPolicy "getUniversalActionPolicy")
[getUsageLimitPolicy](/module-sdk/modules/smart-sessions/policies/getUsageLimitPolicy "getUsageLimitPolicy")
---
# getRemoveSessionAction – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
getRemoveSessionAction
getRemoveSessionAction
======================
Get the action to remove a session from the smart sessions validator.
Usage[](#usage)
----------------
const action = await getRemoveSessionAction({
permissionId: '0x1234...',
})
Parameters[](#parameters)
--------------------------
### permissionId[](#permissionid)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The permission ID of the session to remove.
Returns[](#returns)
--------------------
### action[](#action)
* Type: [`Promise`](/module-sdk/glossary/types#action)
The action to remove a session from the smart sessions validator.
[getEnableSessionsAction](/module-sdk/modules/smart-sessions/getEnableSessionsAction "getEnableSessionsAction")
[getEnableSessionDetails](/module-sdk/modules/smart-sessions/getEnableSessionDetails "getEnableSessionDetails")
---
# getEnableSessionDetails – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
getEnableSessionDetails
getEnableSessionDetails
=======================
This method is used to get the details for a new session to enable through the `enable` flow. It abstracts away the different steps required and makes to easier when wanting to enable a new session without custom logic.
Usage[](#usage)
----------------
const session: Session = {
..., // Session details
}
const sessionDetails = await getEnableSessionDetails({
sessions: [session],
account,
clients: [publicClient],
})
sessionDetails.enableSessionData.enableSession.permissionEnableSig = await signer.signMessage({
message: { raw: sessionDetails.permissionEnableHash },
})
sessionDetails.signature = await signer.signMessage({
message: { raw: userOpHash },
})
const userOpSignature = encodeSmartSessionSignature(sessionDetails)
Parameters[](#parameters)
--------------------------
### sessions[](#sessions)
* Type: [`Session[]`](/module-sdk/glossary/types#session)
An array of sessions to enable. Note that these can be on multiple chains. A user will sign over this array once and when enabling the session on each chain, the index pointing to the right session to enable out of this array will be provided.
### account[](#account)
* Type: [`Account`](/module-sdk/glossary/types#account)
The account to enable the session for.
### clients[](#clients)
* Type: `PublicClient[]`
The public clients to use to interact with the smart sessions validator. Note that one client needs to be provided for each chain on which a session is enabled.
### sessionIndex (optional)[](#sessionindex-optional)
* Type `number`
When enabling more than one session and the session to enable on this particular chain is not the first item in the array (index 0), then this is used to provide the index of the session to enable.
### enableMode (optional)[](#enablemode-optional)
* Type: [`SmartSessionMode`](/module-sdk/glossary/types#smartsessionmode)
The mode of the smart session, in this case either `ENABLE` OR `UNSAFE_ENABLE`. The default is `ENABLE`.
### enableValidatorAddress (optional)[](#enablevalidatoraddress-optional)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the validator to validate the `permissionEnableSig`. When not provided, the `session.sessionValidator` is used.
### permitGenericPolicy (optional)[](#permitgenericpolicy-optional)
* Type: `boolean`
When enabling a session with a generic policy, this flag should be set to `true`. The default is `false`. Setting this to true allows for the installation of a "fallback" action policy.
### permitAdminAccess (optional)[](#permitadminaccess-optional)
* Type: `boolean`
When enabling a session with admin access, this flag should be set to `true`. The default is `false`. Setting this to true allows a session to modify itself and create other sessions.
### ignoreSecurityAttestations (optional)[](#ignoresecurityattestations-optional)
* Type: `boolean`
When enabling a session without ensuring policies are attested, this flag should be set to `true`. The default is `false`.
Returns[](#returns)
--------------------
### enableSessionData.permissionEnableHash[](#enablesessiondatapermissionenablehash)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The hash to sign with a validator on the account in order to enable the session.
### enableSessionData.mode[](#enablesessiondatamode)
* Type: [`SmartSessionMode`](/module-sdk/glossary/types#smartsessionmode)
The mode of the smart session, in this case `Enable`.
### enableSessionData.permissionId[](#enablesessiondatapermissionid)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The permission ID of the session to enable.
### enableSessionData.signature[](#enablesessiondatasignature)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The signature to use the session, made with the session key over the `UserOpHash`. Note that this will be empty and needs to be set.
### enableSessionData.enableSessionData.enableSession.chainDigestIndex[](#enablesessiondataenablesessiondataenablesessionchaindigestindex)
* Type: `number`
The index of the chain digest to use for the session.
### enableSessionData.enableSessionData.enableSession.hashesAndChainIds[](#enablesessiondataenablesessiondataenablesessionhashesandchainids)
* Type: [`ChainDigest[]`](/module-sdk/glossary/types#chaindigest)
The chain digests to use for the session.
### enableSessionData.enableSessionData.enableSession.sessionToEnable[](#enablesessiondataenablesessiondataenablesessionsessiontoenable)
* Type: [`Session`](/module-sdk/glossary/types#session)
The session to enable in this specific UserOperation.
### enableSessionData.enableSessionData.enableSession.permissionEnableSig[](#enablesessiondataenablesessiondataenablesessionpermissionenablesig)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The signature to enable the session, validated by the validator provided. Note that this field is empty and needs to be set.
### enableSessionData.enableSessionData.validator[](#enablesessiondataenablesessiondatavalidator)
* Type: [`Address` (opens in a new tab)](https://viem.sh/docs/glossary/types#address)
The address of the validator to use for the session. This is used to verify the `permissionEnableSig`.
### enableSessionData.enableSessionData.accountType[](#enablesessiondataenablesessiondataaccounttype)
* Type: [`AccountType`](/module-sdk/glossary/types#accounttype)
The type of the account to enable the session for. This is required since accounts format the signature differently.
[getRemoveSessionAction](/module-sdk/modules/smart-sessions/getRemoveSessionAction "getRemoveSessionAction")
[getEnableUserOpPoliciesAction](/module-sdk/modules/smart-sessions/getEnableUserOpPoliciesAction "getEnableUserOpPoliciesAction")
---
# getPermissions – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
getPermissions
getPermissions
==============
This function leverages [ERC-7715 (opens in a new tab)](https://github.com/ethereum/ERCs/pull/436/)
and allows a client to transform 7715 permission objects into policies for [Smart Sessions](/module-sdk/modules/smart-sessions)
.
Usage[](#usage)
----------------
const permissions = getPermissions({
permissions: [\
{\
type: 'erc20-token-transfer',\
data: {\
address: '0x1234...', // token address\
allowance: '0x1', // allowance\
},\
},\
],
})
const session: Session = {
...permissions,
sessionValidator: OWNABLE_VALIDATOR_ADDRESS,
sessionValidatorInitData: encodeValidationData({
threshold: 1,
owners: [sessionOwner.address],
}),
salt: toHex(0),
permitERC4337Paymaster: true,
}
Parameters[](#parameters)
--------------------------
### permissions[](#permissions)
* Type: `{ type: string, data: any }[]`
An array of permission objects according to ERC-7715.
Returns[](#returns)
--------------------
* Type: `Policies`
An object consisting of `userOpPolicies`, `erc7739Policies` and `actions` that can be used to create a new session for smart sessions.
[getSmartSessionsValidator](/module-sdk/modules/smart-sessions/getSmartSessionsValidator "getSmartSessionsValidator")
[getEnableSessionsAction](/module-sdk/modules/smart-sessions/getEnableSessionsAction "getEnableSessionsAction")
---
# getEnableUserOpPoliciesAction – Rhinestone Docs
SDK
Modules
[Smart Sessions](/module-sdk/modules/smart-sessions)
getEnableUserOpPoliciesAction
getEnableUserOpPoliciesAction
=============================
Get the action to enable userOp policies for a specific session in the smart sessions validator.
Usage[](#usage)
----------------
const action = await getEnableUserOpPoliciesAction({
permissionId: '0x1234...',
userOpPolicies: [\
{\
policy: '0xabcd...',\
initData: '0x1234...',\
},\
],
})
Parameters[](#parameters)
--------------------------
### permissionId[](#permissionid)
* Type: [`Hex` (opens in a new tab)](https://viem.sh/docs/glossary/types#hex)
The permission ID of the session to enable userOp policies.
### userOpPolicies[](#useroppolicies)
* Type: [`PolicyData[]`](/module-sdk/glossary/types#policydata)
A list of userOp policies.
Returns[](#returns)
--------------------
### action[](#action)
* Type: [`Promise`](/module-sdk/glossary/types#action)
The action to enable userOp policies for a specific session in the smart sessions validator.
[getEnableSessionDetails](/module-sdk/modules/smart-sessions/getEnableSessionDetails "getEnableSessionDetails")
[getDisableUserOpPoliciesAction](/module-sdk/modules/smart-sessions/getDisableUserOpPoliciesAction "getDisableUserOpPoliciesAction")
---