# Table of Contents - [Starter Kit – Safe Docs](#starter-kit-safe-docs) - [Welcome – Safe Docs](#welcome-safe-docs) - [Safenet Overview – Safe Docs](#safenet-overview-safe-docs) - [Safe{Core} Infrastructure – Safe Docs](#safe-core-infrastructure-safe-docs) - [How do Safe Smart Accounts work? – Safe Docs](#how-do-safe-smart-accounts-work-safe-docs) - [Safe{Core} SDK – Safe Docs](#safe-core-sdk-safe-docs) - [Safe Signatures – Safe Docs](#safe-signatures-safe-docs) - [Starter Kit Reference – Safe Docs](#starter-kit-reference-safe-docs) - [Protocol Kit – Safe Docs](#protocol-kit-safe-docs) - [Send User Operations – Safe Docs](#send-user-operations-safe-docs) - [Migrate to v3 – Safe Docs](#migrate-to-v3-safe-docs) - [Protocol Kit Reference – Safe Docs](#protocol-kit-reference-safe-docs) - [Send Transactions – Safe Docs](#send-transactions-safe-docs) - [Safe Deployment – Safe Docs](#safe-deployment-safe-docs) - [Migrate to v4 – Safe Docs](#migrate-to-v4-safe-docs) - [Multichain Safe Deployment – Safe Docs](#multichain-safe-deployment-safe-docs) - [Migrate to v5 – Safe Docs](#migrate-to-v5-safe-docs) - [Message signatures – Safe Docs](#message-signatures-safe-docs) - [Resource Hub – Safe Docs](#resource-hub-safe-docs) - [Execute transactions – Safe Docs](#execute-transactions-safe-docs) - [Migrate to v2 – Safe Docs](#migrate-to-v2-safe-docs) - [API Kit – Safe Docs](#api-kit-safe-docs) - [Migrate to v1 – Safe Docs](#migrate-to-v1-safe-docs) - [Migrate to v6 – Safe Docs](#migrate-to-v6-safe-docs) - [Transaction signatures – Safe Docs](#transaction-signatures-safe-docs) - [Migrate to v1 – Safe Docs](#migrate-to-v1-safe-docs) - [API Kit Reference – Safe Docs](#api-kit-reference-safe-docs) - [Propose and confirm transactions – Safe Docs](#propose-and-confirm-transactions-safe-docs) - [Migrate to v2 – Safe Docs](#migrate-to-v2-safe-docs) - [Migrate to v3 – Safe Docs](#migrate-to-v3-safe-docs) - [Relay Kit – Safe Docs](#relay-kit-safe-docs) - [Safe accounts with the Safe4337Module – Safe Docs](#safe-accounts-with-the-safe4337module-safe-docs) - [Integration with Gelato – Safe Docs](#integration-with-gelato-safe-docs) - [Migrate to v2 – Safe Docs](#migrate-to-v2-safe-docs) - [Migrate to v3 – Safe Docs](#migrate-to-v3-safe-docs) - [Safe React Hooks – Safe Docs](#safe-react-hooks-safe-docs) - [Migrate to v4 – Safe Docs](#migrate-to-v4-safe-docs) - [Safe4337Pack – Safe Docs](#safe4337pack-safe-docs) - [Send Transactions – Safe Docs](#send-transactions-safe-docs) - [Safe React Hooks Reference – Safe Docs](#safe-react-hooks-reference-safe-docs) - [Signers – Safe Docs](#signers-safe-docs) - [Dynamic Signer – Safe Docs](#dynamic-signer-safe-docs) - [Magic Signer – Safe Docs](#magic-signer-safe-docs) - [Resource Hub – Safe Docs](#resource-hub-safe-docs) - [Passkeys Signer – Safe Docs](#passkeys-signer-safe-docs) - [Support – Safe Docs](#support-safe-docs) - [Privy Signer – Safe Docs](#privy-signer-safe-docs) --- # Starter Kit – Safe Docs SDK Starter Kit Starter Kit =========== The Starter Kit is the starting point for interacting with the Safe smart account using a TypeScript interface. The Starter Kit is built on top of several kits from the Safe{Core} SDK, leveraging and abstracting the complex logic. At the same time, it's modular and customizable, offering the most simplified way to deploy new accounts and handle the Safe transaction flow in all its different forms: * User operations * Multi-signature transactions * Off-chain and on-chain messages [#### @safe-global/sdk-starter-kit](https://www.npmjs.com/package/@safe-global/sdk-starter-kit) The following guides show how to use the Starter Kit and integrate it into your project: * [Send transactions](/sdk/starter-kit/guides/send-transactions) * [Send user operations](/sdk/starter-kit/guides/send-user-operations) Resources[](#resources) ------------------------ * [Starter Kit on GitHub (opens in a new tab)](https://github.com/safe-global/safe-core-sdk/tree/main/packages/sdk-starter-kit) [Overview](/sdk/overview "Overview") [Send Transactions](/sdk/starter-kit/guides/send-transactions "Send Transactions") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Welcome – Safe Docs Home What is Safe? Welcome ======= At [Safe (opens in a new tab)](https://safe.global) , we pursue a future where everyone has complete control and flexibility over their digital assets. Our vision is to move from merely reading and writing in the digital realm to fully owning our digital identities, financial assets, digital art, and more. Smart accounts[](#smart-accounts) ---------------------------------- While [externally-owned accounts](/home/glossary#externally-owned-account) (EOAs) have been the cornerstone of digital assets management thus far, they have a lot of limitations and fall short in onboarding mainstream users. Not only are seed phrases cumbersome to secure, but the lack of flexibility and the limited security of EOAs hinder our progress toward actual digital ownership. Safe is at the forefront of modular [smart account](/home/glossary#smart-account) infrastructure, paving the way for developers to create various applications and wallets. Safe brings digital ownership of accounts to everyone by building universal and open contract standards for the custody of digital assets, data, and identity. Our stack[](#our-stack) ------------------------ Our goal is to establish smart accounts as the default, and our approach to making this a reality has developed across two primary areas of focus: [#### Safe{Core}\ \ The most secure and robust tooling and infrastructure to integrate Safe Smart Account and leverage account abstraction into your product.](/home/safe-core) [#### Safe{Wallet}\ \ Official interface designed for individuals and industries spanning various sectors, ensuring a seamless and secure digital asset management experience.](https://safe.global/wallet) This documentation site is solely focused on [Safe{Core}](/home/safe-core) . Visit our [Help Center (opens in a new tab)](https://help.safe.global) to learn more about Safe{Wallet}. [Safe{Core}](/home/safe-core "Safe{Core}") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safenet Overview – Safe Docs Safenet Overview ![Safenet-introduction](/_next/static/media/safenet-introduction.cf3046fd.png) Safenet Overview ================ Safe announced Safenet on December 3rd, 2024. These docs describe the current state of technical vision. The current state of implementation can be found in the roadmap. The docs consist of the following sections: **[Introduction](/safenet/introduction) **: Provides a brief overview of Safenet, its high-level architecture, and the flow of a Safenet transaction. Reading this part will explain why Safe builds Safenet. It is recommended to start reading these docs with the Introduction in the given order. This page is part of the Introduction. Details[](#details) -------------------- **[Core Components](/safenet/core-components/processor) **: Provides details of the different components of Safenet. Reading this part will provide an understanding of what Safenet is composed of. **[Concepts](/safenet/concepts/settlement) **: Describes the different concepts and procedures of Safenet. Reading this part will provide an understanding of how Safenet works. **[Protocol](/safenet/protocol/overview) **: Describes the smart contracts and their role in the Safenet protocol. Reading this part will provide an understanding of Safenet's technical details. Next steps[](#next-steps) -------------------------- It is recommended to use the arrow buttons at the bottom of this page to read the introduction section in the given order. [Why Safenet](/safenet/introduction "Why Safenet") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safe{Core} Infrastructure – Safe Docs API Overview Safe{Core} Infrastructure ========================= The Safe{Core} Infrastructure consists of the following services: [#### Safe Transaction Service\ \ The Safe Transaction Service tracks transactions related to Safe contracts using tracing on Mainnet, Sepolia, and Gnosis Chain. It uses event indexing for the other chains. For each supported network there is one instance of the Transaction Service.](/core-api/transaction-service-overview) [#### Safe Events Service\ \ The Events Service handles Safe indexing events and delivers them as HTTP webhooks, connection to the events queue processed by the Transaction Service. The service's database stores the configuration of webhook destinations.](https://github.com/safe-global/safe-events-service) Architecture[](#architecture) ------------------------------ Safe{Wallet} uses these services to offer functionality to end customers via the web and mobile applications. The [Safe Client Gateway (opens in a new tab)](https://github.com/safe-global/safe-client-gateway-nest) acts as a facade between the end customer and the Safe{Core} services and the [Safe Config Service (opens in a new tab)](https://github.com/safe-global/safe-config-service) stores all supported networks and chain-specific variables. Safe's production setup consists of several instances of the Transaction Service orchestrated by the Config Service, which are later consumed by the Safe Client Gateway. The Events Service notifies the Safe Client Gateway when new events are indexed, helping to improve the user experience. ![Overview of the backend services and their components.](/_next/static/media/diagram-services.bf9f3417.png) Integration Flow for Safe{Wallet} and Safe{Core}[](#integration-flow-for-safewallet-and-safecore) -------------------------------------------------------------------------------------------------- * The Client Gateway leverages the Config Service to find the Transaction Service instance required for a specific request. * The Client Gateway forwards the request to the specified Transaction Service instance for the supported networks (determined by the Config Service). * The Client Gateway transforms, aggregates, and caches information from the Config and Transaction Services, optimizing data for Safe's web and mobile clients. * The Event Service provides information to the Client Gateway when the Transaction Service indexes an event using webhooks. The Client Gateway is then responsible for providing this information to the end clients. Even though the Config Service and Transaction Service instances are reachable by clients that aren't the Client Gateway, this may change in the future. The Client Gateway is the outermost component of the Safe infrastructure and should be the single point of communication with any front-end client. Rate limits[](#rate-limits) ---------------------------- All Safe{Core} Infrastructure services have a rate limit of 5 requests per second. Running locally[](#running-locally) ------------------------------------ [Safe Infrastructure (opens in a new tab)](https://github.com/safe-global/safe-infrastructure) repository and the [running services locally (opens in a new tab)](https://github.com/safe-global/safe-infrastructure/blob/main/docs/running_locally.md) guide show how to run Safe's infrastructure ([Safe{Wallet} (opens in a new tab)](https://app.safe.global) and Safe{Core}). Note that these documents are examples of how these services run, and the configuration should adapt to the needs of a specific use case. ← Go Home[Running the Safe Transaction Service](/core-api/api-safe-transaction-service "Running the Safe Transaction Service") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # How do Safe Smart Accounts work? – Safe Docs Advanced Overview How do Safe Smart Accounts work? ================================ EOAs[](#eoas) -------------- [EOAs](/home/glossary#externally-owned-account) , for short, are Ethereum accounts that use traditional key pairs. That is, they consist of a single private key that can be used to make transactions and sign messages. If you gain access to that private key, you gain complete control of the account. This means that your private key is a single point of failure between you and your funds being lost. Smart Accounts[](#smart-accounts) ---------------------------------- The other type of Ethereum account is a [Smart Account](/home/glossary#smart-account) , also sometimes called Smart Contract Account. Like EOAs, smart contract accounts each have a unique public Ethereum address, and it is impossible to tell them apart from EOAs by looking at an Ethereum address. Smart contract accounts, too, can receive funds and make transactions like EOAs but cannot initiate them. Generally, the key difference is that no single private key is used to verify transactions. Instead, the smart contract code defines the logic behind how the account completes transactions. Smart contracts are programs that run on the Ethereum blockchain and execute when specific conditions are met. Their functionality within contract accounts means that such accounts, in contrast to EOAs, can, for example, implement access rights that specify by whom, how, and under which conditions transactions can be executed, as well as more complex logic. Difference between EOAs and Contract Accounts ============================================= ![Ethereum Accounts](/_next/static/media/safe-smart-accounts.0a67e916.png) Safe Smart Account[](#safe-smart-account) ------------------------------------------ Safe Smart Account is a Smart Account with multi-signature functionality at its core. It is secure and flexible, and it can be used to manage funds and execute transactions on the Ethereum blockchain. The vision for Safe Smart Accounts is to become the standard core used in all smart contract-based wallets. It also aims to make the benefits of Account Abstraction accessible to users and developers. The architectural design of Safe Smart Account keeps the following principles in mind. * **Secure default** Uses a multi-signature logic where a threshold of owners must confirm a transaction before execution to provide a secure default without trusting any additional contract—for example, a module, guard, or fallback handler (explained below). * **Maximum flexibility** Supports modules that execute transactions using alternative access patterns (instead of multi-signature). It also supports the `delegatecall` function, which introduces complex execution logic by loading instructions from other contracts and executing via a Safe Smart Account. ### Features[](#features) #### High Security[](#high-security) Safe's **multi-signature** functionality allows you to define a list of owner accounts and a threshold number of accounts required to confirm a transaction. Once the threshold of owner accounts have confirmed a transaction, the Safe transaction can be executed. Owners can be EOAs, other smart contract accounts, or even a passkey. #### Advanced execution logic[](#advanced-execution-logic) It is possible to use different **Safe Library contracts** to perform complex transactions. A prevalent example is **batched transactions**, where multiple simple Ethereum transactions are combined and executed at once. That means instead of having to sign several transactions sequentially, a user just needs to sign one batched transaction. #### Advanced access management[](#advanced-access-management) You can add **Safe Modules** to your Safe, which facilitates more fine-grained access management. For instance, defining a module that can only be used to **recover access** to a Safe under specific circumstances is possible. Another example is **allowance modules** that allow owners of a Safe to grant limited execution permission, such as a daily limit to external accounts. #### Token callback support[](#token-callback-support) Many new tokens require wallet contracts to implement callbacks. Token standards like **ERC-721** and **ERC-1155** allow contracts to immediately react to receiving tokens through these and make it even possible to reject the transfer altogether. #### Sponsored transactions[](#sponsored-transactions) Another core functionality of the Safe is **token payment**. Generally, Ethereum transactions require ETH to pay transaction fees ("gas"). With the Safe, users can pay transaction fees in several supported ERC20 tokens. This is realized via a transaction relay service that accepts those tokens and submits the transactions to the blockchain, paying the gas fee in ETH. With the same functionality, Ether-less transactions can be implemented, where a 3rd party pays transaction fees on behalf of a Safe via the same relay service. ### Architecture[](#architecture) ![Safe Smart Accounts Architecture](/_next/static/media/diagram-safe-smart-accounts-architecture.b82e6643.png) #### Safe Singleton Factory[](#safe-singleton-factory) The Safe Singleton Factory is a contract that deploys all the Safe Smart Account related contracts. This contract helps to deploy Safe Smart Account contracts at the same address across different networks and eventually also helps to deploy Safe proxies at the same address across different networks. For more information, refer to the [Safe Singleton Factory (opens in a new tab)](https://github.com/safe-global/safe-singleton-factory) repository. #### Safe Proxy Factory[](#safe-proxy-factory) The Safe proxy factory contract provides a simple way to create a new proxy contract pointing to a singleton and executing a setup function in the newly deployed proxy all in one transaction. #### Safe[](#safe) This is a singleton contract deployed only once and used by Safe Proxy to delegate calls. It is the main contract that holds the logic for signature verification, executing transactions, managing owners, modules, and the fallback handler. As a singleton contract, it cannot be used directly as a Safe account but only through a Safe Proxy contract. The two types of Safe Smart Accounts are: * Safe * SafeL2: The version emits additional events and is to be used for L2 chains that don't support tracing. _Hint: For legacy reasons they are referred as Safe._ A Safe contract itself is composed of different contracts. The diagram below shows the main components of a Safe contract. ![Safe Smart Account Components](/_next/static/media/diagram-safe-smart-account-safe-components.c903463c.png) ##### Owner Management[](#owner-management) One core feature of a Safe account is that it can be operated by multiple accounts, known as owners. `OwnerManager.sol` allows you to add, remove, and replace owners. Furthermore, a threshold number of owners required to confirm a transaction for it to be executed can be specified and modified. You can retrieve the list of owners. Events are emitted every time an owner is added or removed and whenever the threshold changes. ##### Module Management[](#module-management) Modules add additional functionalities to the Safe accounts. They are smart contracts that implement Safe's functionality while separating module logic from Safe's core contract. Depending on the use case, modules could, for instance, allow the execution of transactions without requiring all confirmations. A basic Safe does not require any modules. Adding and removing a module requires confirmation from the required threshold of owners. Modules are very security-critical, so they need to be as secure as all other Safe contracts. Some of the available modules are: * [Allowance Module (opens in a new tab)](https://github.com/safe-global/safe-modules/tree/main/modules/4337) * [Recovery Module (opens in a new tab)](https://github.com/safe-global/safe-modules/tree/main/modules/recovery) * [4337 Module (opens in a new tab)](https://github.com/safe-global/safe-modules/tree/main/modules/4337) * [Passkey Module (opens in a new tab)](https://github.com/safe-global/safe-modules/tree/main/modules/passkey) ##### Executor[](#executor) This contract contains the logic to execute `call` or `delegatecall` to external address. ##### Fallback Manager[](#fallback-manager) Ethereum fallback functions are executed when a called function signature does not match any defined function. Specific use cases require those fallback functions to contain some logic. EVM limits the size of a Smart contract to 24KB. The Fallback Manager contract allows you to extend the functionality of the Safe contract by adding additional logic to the fallback function and overcoming the contract size limit. ##### Guard Management[](#guard-management) Guards are used to check if a transaction should be executed or rejected based on the logic defined in the guard. A Guard Manager contract allows you to add, remove, and replace guards. Guards are security critical as a malicious guard could prevent transactions from being executed and block access to funds stored in the Safe. Events are emitted whenever a guard is updated. #### SafeProxy[](#safeproxy) A Safe Proxy is a contract that delegates all calls to the Safe Singleton. Deploying a Proxy reduces the cost of creating a Safe account, as the proxy contract's byte code size is less than that of the actual Safe contract code. ![Safe Proxy Creation](/_next/static/media/diagram-safe-smart-account-proxy-creation.0b57cc3f.png) ← Go Home[Concepts](/advanced/smart-account-concepts "Concepts") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safe{Core} SDK – Safe Docs SDK Overview Safe{Core} SDK ============== The Safe{Core} SDK aims to bring Account Abstraction to life by integrating Safe with different third parties. This SDK helps developers to abstract the complexity of setting up a smart contract account. The Safe{Core} SDK groups its functionality into four different kits: Starter Kit[](#starter-kit) ---------------------------- The [Starter Kit](/sdk/starter-kit) is the starting point for interacting with the Safe smart account. It leverages and abstracts the complex logic from other kits while being modular and customizable, offering the most simplified way to deploy new accounts and handle the Safe transaction flow in all its different forms: * User operations * Multi-signature transactions * Off-chain and on-chain messages [#### Starter Kit\ \ Learn about the Starter Kit and the Safe Smart Account integration.](/sdk/starter-kit) Protocol Kit[](#protocol-kit) ------------------------------ The [Protocol Kit](/sdk/protocol-kit) helps interact with Safe Smart Accounts. It enables the creation of new Safe accounts, updating their configuration, and signing and executing transactions, among other features. * Modular and customizable smart contract accounts * Battle-tested security * Transaction batching [#### Protocol Kit\ \ Learn about the Protocol Kit and the Safe Smart Account contracts integration.](/sdk/protocol-kit) API Kit[](#api-kit) -------------------- The [API Kit](/sdk/api-kit) helps interact with the Safe Transaction Service API. It helps share transactions among the signers and get information from a Safe account. For example, the configuration or transaction history. [#### API Kit\ \ Learn about the API Kit and the integration with the Safe Transaction Service.](/sdk/api-kit) Relay Kit[](#relay-kit) ------------------------ The [Relay Kit](/sdk/relay-kit) enables transaction relaying with Safe and allows users to pay for the transaction fees from their Safe account using the blockchain native token, ERC-20 tokens, or get their transactions sponsored. * Use ERC-4337 with Safe * Gas-less experiences using Gelato * Sponsored transaction * Pay fees in ERC-20 tokens [#### Relay Kit\ \ Learn about the Relay Kit and the packs integrating ERC-4337 and Gelato.](/sdk/relay-kit) Resources[](#resources) ------------------------ * [Safe{Core} Account Abstraction SDK on GitHub (opens in a new tab)](https://github.com/safe-global/safe-core-sdk) * [Safe{Core} Account Abstraction SDK demo application (opens in a new tab)](https://github.com/5afe/account-abstraction-demo-ui) ← Go Home[Starter Kit](/sdk/starter-kit "Starter Kit") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safe Signatures – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Signatures Safe Signatures =============== This guide shows how Safe signatures work and how to generate them using the Protocol Kit. Setup[](#setup) ---------------- Safe accounts can be configured with different threshold values and types of owners. Safe owners can be any Ethereum address, such as: * Externally-owned accounts (EOAs). They generate an ECDSA signature to approve Safe transactions. * Smart accounts that implement the [EIP-1271 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-1271) for signature validation, like Safe does. They use a different signature algorithm depending on the implementation of the smart account. In the following guides, there are different accounts involved that will be used as an example: | Who | Description | Address for this example | | --- | --- | --- | | `SAFE_3_4_ADDRESS` | 3/4 Safe (3 signatures required out of 4 owners) | 0xb3b3862D8e38a1E965eb350B09f2167B2371D652 | | `OWNER_1_ADDRESS` | EOA and owner of `SAFE_3_4_ADDRESS` | 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 | | `OWNER_2_ADDRESS` | EOA and owner of `SAFE_3_4_ADDRESS` | 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0 | | `SAFE_1_1_ADDRESS` | 1/1 Safe and owner of `SAFE_3_4_ADDRESS` | 0x215033cdE0619D60B7352348F4598316Cc39bC6E | | `OWNER_3_ADDRESS` | EOA and owner of `SAFE_1_1_ADDRESS` | 0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b | | `SAFE_2_3_ADDRESS` | 2/3 Safe and owner of `SAFE_3_4_ADDRESS` | 0xf75D61D6C27a7CC5788E633c1FC130f0F4a62D33 | | `OWNER_4_ADDRESS` | EOA and owner of `SAFE_2_3_ADDRESS` | 0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d | | `OWNER_5_ADDRESS` | EOA and owner of `SAFE_2_3_ADDRESS` | 0xd03ea8624C8C5987235048901fB614fDcA89b117 | We need to instantiate all the signers based on the Safe owner accounts. ` _10 // https://chainlist.org/?search=sepolia&testnets=true _10 const RPC_URL = 'https://eth-sepolia.public.blastapi.io' _10 _10 // Initialize signers _10 const OWNER_1_PRIVATE_KEY = // ... _10 const OWNER_2_PRIVATE_KEY = // ... _10 const OWNER_3_PRIVATE_KEY = // ... _10 const OWNER_4_PRIVATE_KEY = // ... _10 const OWNER_5_PRIVATE_KEY = // ... ` Guides[](#guides) ------------------ ### Transaction signatures[](#transaction-signatures) * The [Transaction signatures](/sdk/protocol-kit/guides/signatures/transactions) guide explains how transactions are signed by Safe owners using the Protocol Kit. ### Message signatures[](#message-signatures) * Using the Protocol Kit, the [Message signatures](/sdk/protocol-kit/guides/signatures/messages) guide explains how to generate and sign messages, including plain string messages and EIP-712 JSON messages. [Execute transactions](/sdk/protocol-kit/guides/execute-transactions "Execute transactions") [Transactions](/sdk/protocol-kit/guides/signatures/transactions "Transactions") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Starter Kit Reference – Safe Docs Starter Kit Reference Overview Starter Kit Reference ===================== The [Starter Kit](/sdk/starter-kit) is the starting point for integrating the Safe account in the front-end, as it provides the most simplified way to handle the Safe transaction flow in its different forms: * User operations * Multi-signature transactions * Off-chain and on-chain messages Install dependencies[](#install-dependencies) ---------------------------------------------- To add the Starter Kit to your project, run: pnpm npm yarn ` _10 pnpm add @safe-global/sdk-starter-kit ` ← Go Back[constructor](/reference-sdk-starter-kit/safe-client/constructor "constructor") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Protocol Kit – Safe Docs SDK Protocol Kit Protocol Kit ============ The Protocol Kit enables developers to interact with [Safe Smart Accounts (opens in a new tab)](https://github.com/safe-global/safe-smart-account) using a TypeScript interface. This kit can be used to create new Safe smart accounts, update the configuration of existing Safes, propose and execute transactions, among other features. [#### @safe-global/protocol-kit](https://www.npmjs.com/package/@safe-global/protocol-kit) The following guides show how to use the Protocol Kit and integrate it into your project: * [Safe deployment](/sdk/protocol-kit/guides/safe-deployment) * [Multichain Safe deployment](/sdk/protocol-kit/guides/multichain-safe-deployment) * [Execute transactions](/sdk/protocol-kit/guides/execute-transactions) * [Transactions signatures](/sdk/protocol-kit/guides/signatures/transactions) * [Message signatures](/sdk/protocol-kit/guides/signatures/messages) Resources[](#resources) ------------------------ * [Protocol Kit on GitHub (opens in a new tab)](https://github.com/safe-global/safe-core-sdk/tree/main/packages/protocol-kit) [Reference](/sdk/protocol-kit# "Reference") [Safe deployment](/sdk/protocol-kit/guides/safe-deployment "Safe deployment") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Send User Operations – Safe Docs SDK [Starter Kit](/sdk/starter-kit) Guides Send User Operations Send User Operations ==================== In this guide, you will learn how to create Safe [user operations](/home/glossary#useroperation) , sign them, collect the signatures from the different owners, and execute them. For more detailed information, see the [Starter Kit Reference](/reference-sdk-starter-kit/overview) . [Pimlico (opens in a new tab)](https://pimlico.io) is used in this guide as the service provider, but any other provider compatible with the ERC-4337 can be used. Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) * A [Pimlico account (opens in a new tab)](https://dashboard.pimlico.io) and an API key. Install dependencies[](#install-dependencies) ---------------------------------------------- First, you need to install some dependencies. ` _10 pnpm add @safe-global/sdk-starter-kit ` Steps[](#steps) ---------------- ### Imports[](#imports) Here are all the necessary imports for this guide. ` _10 import { _10 createSafeClient, _10 safeOperations, _10 BundlerOptions _10 } from '@safe-global/sdk-starter-kit' ` ### Create a signer[](#create-a-signer) Firstly, you need to get a signer, which will be the owner of a Safe account after it's deployed. This example uses a private key, but any way to get an EIP-1193 compatible signer can be used. ` _10 const SIGNER_ADDRESS = // ... _10 const SIGNER_PRIVATE_KEY = // ... _10 const RPC_URL = 'https://rpc.ankr.com/eth_sepolia' ` ### Initialize the `SafeClient`[](#initialize-the-safeclient) New Safe accountExisting Safe account When deploying a new Safe account, you need to pass the configuration of the Safe in the `safeOptions` property. The Safe account is configured with your signer as the only owner in this case. ` _10 const safeClient = await createSafeClient({ _10 provider: RPC_URL, _10 signer: SIGNER_PRIVATE_KEY, _10 safeOptions: { _10 owners: [SIGNER_ADDRESS], _10 threshold: 1 _10 } _10 }) ` As this guide is related with ERC-4337 user operations, you need to add the `safeOperations` extension to the `safeClient` instance to support this functionality. `` _12 const bundlerOptions: BundlerOptions = { _12 bundlerUrl: `https://api.pimlico.io/v1/sepolia/rpc?add_balance_override&apikey=${PIMLICO_API_KEY}` _12 } _12 _12 const paymasterOptions: PaymasterOptions = { _12 isSponsored: true, _12 paymasterUrl: `https://api.pimlico.io/v2/sepolia/rpc?add_balance_override&apikey=${PIMLICO_API_KEY}` _12 } _12 _12 const safeClientWithSafeOperation = await safeClient.extend( _12 safeOperations(bundlerOptions, paymasterOptions) _12 ) `` The `safeClientWithSafeOperation` instance has now support for managing user operations. ### Create a Safe transaction[](#create-a-safe-transaction) Create an array of Safe transactions to execute. ` _10 const transactions = [{ _10 to: '0x...', _10 data: '0x', _10 value: '0' _10 }] ` ### Send the Safe operation[](#send-the-safe-operation) If you configured your Safe with `threshold` equal to `1`, calling the [`sendSafeOperation`](/reference-sdk-starter-kit/safe-operations/sendsafeoperation) method will execute the user operation. However, if the `threshold` is greater than `1` the other owners of the Safe will need to confirm the user operation until the required number of signatures are collected. ` _10 const safeOperationResult = await safeClientWithSafeOperation.sendSafeOperation({ _10 transactions _10 }) _10 _10 const safeOperationHash = safeOperationResult.safeOperations?.safeOperationHash ` ### Confirm the Safe operations[](#confirm-the-safe-operations) If the user operation needs to be confirmed by other Safe owners, call the [`confirmSafeOperation`](/reference-sdk-starter-kit/safe-operations/confirmsafeoperation) method from a new `SafeClient` instance initialized with each of the signers that need to confirm it. `` _14 const newSafeClient = await createSafeClient({ _14 provider: RPC_URL, _14 signer, _14 safeAddress: '0x...' _14 }) _14 _14 const newSafeClientWithSafeOperation = await newSafeClient.extend( _14 safeOperations({ _14 bundlerUrl: `https://api.pimlico.io/v1/sepolia/rpc?add_balance_override&apikey=${PIMLICO_API_KEY}` _14 }, { _14 isSponsored: true, _14 paymasterUrl: `https://api.pimlico.io/v2/sepolia/rpc?add_balance_override&apikey=${PIMLICO_API_KEY}` _14 }) _14 ) `` Finally, retrieve all the pending user operations from the Safe Transaction Service and confirm the one you just created with each missing owner. ` _11 const pendingSafeOperations = _11 await newSafeClientWithSafeOperation.getPendingSafeOperations() _11 _11 for (const safeOperation of pendingSafeOperations.results) { _11 if (safeOperation.safeOperationHash !== safeOperationHash) { _11 return _11 } _11 _11 const safeOperationResult = _11 await newSafeClientWithSafeOperation.confirmSafeOperation({ safeOperationHash }) _11 } ` Recap and further reading[](#recap-and-further-reading) -------------------------------------------------------- After following this guide, you are able to deploy new Safe accounts and create, sign, and submit user operations with the Starter Kit. [Send Transactions](/sdk/starter-kit/guides/send-transactions "Send Transactions") [Reference](/sdk/starter-kit/guides/send-user-operations# "Reference") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v3 – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Migrate to v3 Migrate to v3 ============= This guide references the major changes between v2 and v3 to help those migrating an existing app. **Note:** When upgrading to `protocol-kit` v3, it's necessary to upgrade to `safe-core-sdk-types` v4. The signTransactionHash() was renamed signHash()[](#the-signtransactionhash-was-renamed-signhash) -------------------------------------------------------------------------------------------------- The `signTransactionHash()` method was renamed to `signHash()` to align with the method's purpose. The method is not strictly for transactions, as the parameter is a hash, so the new name is more accurate. ` _10 // old: _10 protocolKit.signTransactionHash(safeTxHash) _10 _10 // new: _10 protocolKit.signHash(safeTxHash) ` Type changes[](#type-changes) ------------------------------ The `SafeTransactionEIP712Args` was renamed `SafeEIP712Args` as the EIP-712 is not exclusive for transactions. [Migrate to v2](/sdk/protocol-kit/guides/migrate-to-v2 "Migrate to v2") [Migrate to v4](/sdk/protocol-kit/guides/migrate-to-v4 "Migrate to v4") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Protocol Kit Reference – Safe Docs Protocol Kit Reference Overview Protocol Kit Reference ====================== The [Protocol Kit (opens in a new tab)](https://github.com/safe-global/safe-core-sdk/tree/main/packages/protocol-kit) facilitates the interaction with the Safe smart account contracts. Install dependencies[](#install-dependencies) ---------------------------------------------- To add the Protocol Kit to your project, run: ` _10 yarn add @safe-global/protocol-kit ` ← Go Back[connect](/reference-sdk-protocol-kit/initialization/connect "connect") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Send Transactions – Safe Docs SDK [Starter Kit](/sdk/starter-kit) Guides Send Transactions Send Transactions ================= In this guide, you will learn how to create Safe transactions, sign them, collect the signatures from the different owners, and execute them. For more detailed information, see the [Starter Kit Reference](/reference-sdk-starter-kit/overview) . Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) Install dependencies[](#install-dependencies) ---------------------------------------------- First, you need to install some dependencies. ` _10 pnpm add @safe-global/sdk-starter-kit ` Steps[](#steps) ---------------- ### Imports[](#imports) Here are all the necessary imports for this guide. ` _10 import { createSafeClient } from '@safe-global/sdk-starter-kit' ` ### Create a signer[](#create-a-signer) Firstly, you need to get a signer, which will be the owner of a Safe account after it's deployed. This example uses a private key, but any way to get an EIP-1193 compatible signer can be used. ` _10 const SIGNER_ADDRESS = // ... _10 const SIGNER_PRIVATE_KEY = // ... _10 const RPC_URL = 'https://rpc.ankr.com/eth_sepolia' ` ### Initialize the `SafeClient`[](#initialize-the-safeclient) New Safe accountExisting Safe account When deploying a new Safe account, you need to pass the configuration of the Safe in the `safeOptions` property. The Safe account is configured with your signer as the only owner in this case. ` _10 const safeClient = await createSafeClient({ _10 provider: RPC_URL, _10 signer: SIGNER_PRIVATE_KEY, _10 safeOptions: { _10 owners: [SIGNER_ADDRESS], _10 threshold: 1 _10 } _10 }) ` ### Create a Safe transaction[](#create-a-safe-transaction) Create an array of Safe transactions to execute. ` _10 const transactions = [{ _10 to: '0x...', _10 data: '0x', _10 value: '0' _10 }] ` ### Send the Safe transaction[](#send-the-safe-transaction) If you configured your Safe with `threshold` equal to `1`, calling the [`send`](/reference-sdk-starter-kit/safe-client/send) method will execute the Safe transaction. However, if the `threshold` is greater than `1` the other owners of the Safe will need to confirm the transaction until the required number of signatures are collected. ` _10 const txResult = await safeClient.send({ transactions }) _10 _10 const safeTxHash = txResult.transactions?.safeTxHash ` ### Confirm the Safe transaction[](#confirm-the-safe-transaction) If the Safe transaction needs to be confirmed by other Safe owners, call the [`confirm`](/reference-sdk-starter-kit/safe-client/confirm) method from a new `SafeClient` instance initialized with each of the signers that need to confirm it. ` _10 const newSafeClient = await createSafeClient({ _10 provider: RPC_URL, _10 signer, _10 safeAddress: '0x...' _10 }) ` Finally, retrieve all the pending Safe transactions from the Safe Transaction Service and confirm the one you just created with each missing owner. ` _10 const pendingTransactions = await newSafeClient.getPendingTransactions() _10 _10 for (const transaction of pendingTransactions.results) { _10 if (transaction.safeTxHash !== safeTxHash) { _10 return _10 } _10 _10 const txResult = await safeClient.confirm({ safeTxHash }) _10 } ` Recap and further reading[](#recap-and-further-reading) -------------------------------------------------------- After following this guide, you are able to deploy new Safe accounts and create, sign, and execute Safe transactions with the Starter Kit. [Starter Kit](/sdk/starter-kit "Starter Kit") [Send User Operations](/sdk/starter-kit/guides/send-user-operations "Send User Operations") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safe Deployment – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Safe deployment Safe Deployment =============== This guide will teach you how to deploy a new Safe using the Protocol Kit. This process includes initializing the Protocol Kit, setting up your Safe configuration, and executing the deployment. For more detailed information, see the [Protocol Kit Reference](/reference-sdk-protocol-kit/overview) . Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) Install dependencies[](#install-dependencies) ---------------------------------------------- First, you need to install some dependencies. ` _10 pnpm add @safe-global/protocol-kit viem ` Steps[](#steps) ---------------- ### Imports[](#imports) Here are all the necessary imports for this guide. ` _10 import Safe, { _10 PredictedSafeProps, _10 SafeAccountConfig, _10 SafeDeploymentConfig _10 } from '@safe-global/protocol-kit' _10 import { sepolia } from 'viem/chains' ` ### Create a signer[](#create-a-signer) You need a signer to instantiate the Protocol Kit. This example uses a private key to obtain a signer, but [EIP-1193 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-1193) compatible signers are also supported. For detailed information about signers, please refer to the [Protocol Kit reference](/reference-sdk-protocol-kit/overview) . ` _10 const SIGNER_PRIVATE_KEY = // ... ` ### Initialize the Protocol Kit[](#initialize-the-protocol-kit) Initialize an instance of the Protocol Kit for each network where you want to deploy a new Safe smart account by calling the [`init`](/reference-sdk-protocol-kit/initialization/init) method. Pass the `provider` with its corresponding value depending on the network, the `signer` executing the deployment, and the [`predictedSafe`](/reference-sdk-protocol-kit/initialization/init#predictedsafe-optional) with the Safe account configuration. Optionally, you can [track your Safe deployments and transactions on-chain](/sdk/onchain-tracking) by using the `onchainAnalytics` property. ` _17 const safeAccountConfig: SafeAccountConfig = { _17 owners: ['0x...', '0x...', '0x...'], _17 threshold: 2 _17 // More optional properties _17 } _17 _17 const predictedSafe: PredictedSafeProps = { _17 safeAccountConfig _17 // More optional properties _17 } _17 _17 const protocolKit = await Safe.init({ _17 provider: sepolia.rpcUrls.default.http[0], _17 signer: SIGNER_PRIVATE_KEY, _17 predictedSafe, _17 onchainAnalytics // Optional _17 }) ` ### Predict the Safe address[](#predict-the-safe-address) You can predict the Safe address using the [`getAddress`](/reference-sdk-protocol-kit/safe-info/getaddress) method in the Protocol Kit. ` _10 const safeAddress = await protocolKit.getAddress() ` ### Create the deployment transaction[](#create-the-deployment-transaction) Create the deployment transaction to deploy a new Safe smart account by calling the [`createSafeDeploymentTransaction`](/reference-sdk-protocol-kit/deployment/createsafedeploymenttransaction) method. ` _10 const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() ` ### Execute the deployment transaction[](#execute-the-deployment-transaction) Once the deployment transaction object is ready, execute it using the provided signer or your preferred external Ethereum client. `` _12 const client = await protocolKit.getSafeProvider().getExternalSigner() _12 _12 const transactionHash = await client.sendTransaction({ _12 to: deploymentTransaction.to, _12 value: BigInt(deploymentTransaction.value), _12 data: deploymentTransaction.data as `0x${string}`, _12 chain: sepolia _12 }) _12 _12 const transactionReceipt = await client.waitForTransactionReceipt({ _12 hash: transactionHash _12 }) `` ### Reinitialize the Protocol Kit[](#reinitialize-the-protocol-kit) Once the deployment transaction is executed, connect the new Safe address to the Protocol Kit instance by calling the [`connect`](/reference-sdk-protocol-kit/initialization/connect) method. ` _10 const newProtocolKit = await protocolKit.connect({ _10 safeAddress _10 }) _10 _10 const isSafeDeployed = await newProtocolKit.isSafeDeployed() // True _10 const safeAddress = await newProtocolKit.getAddress() _10 const safeOwners = await newProtocolKit.getOwners() _10 const safeThreshold = await newProtocolKit.getThreshold() ` Recap and further reading[](#recap-and-further-reading) -------------------------------------------------------- After following this guide, you are able to deploy new Safe smart accounts with the Protocol Kit. [Protocol Kit](/sdk/protocol-kit "Protocol Kit") [Multichain Safe deployment](/sdk/protocol-kit/guides/multichain-safe-deployment "Multichain Safe deployment") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v4 – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Migrate to v4 Migrate to v4 ============= This guide references the major changes between v3 and v4 to help those migrating an existing app. **Note:** When upgrading to `protocol-kit` v4, it's necessary to upgrade to `types-kit` v1. The create() method was renamed init() in the SafeFactory and Safe classes[](#the-create-method-was-renamed-init-in-the-safefactory-and-safe-classes) ------------------------------------------------------------------------------------------------------------------------------------------------------ We renamed the `create()` method to `init()` to better reflect the method's purpose. The term `create()` was misleading, suggesting a new Safe account would be created and deployed. However, this method only initializes the `Safe` class, so `init()` is a more accurate and descriptive name. ` _10 // old _10 const protocolKit = await Safe.create({ ... }) _10 const safeFactory = await SafeFactory.create({ ... }) _10 _10 // new _10 const protocolKit = await Safe.init({ ... }) _10 const safeFactory = await SafeFactory.init({ ... }) ` Remove the adapters[](#remove-the-adapters) -------------------------------------------- We have removed the concept of adapters from the `protocol-kit` to simplify the library. Instead of using specific library adapters, we use now an internal `SafeProvider` object to interact with the Safe. This `SafeProvider` will be created using: * An Ethereum provider, an [EIP-1193 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-1193) compatible provider, or an RPC URL. * An optional address of the signer that is connected to the provider or a private key. If not provided, the first account of the provider (`eth_accounts`) will be selected as the signer. The `EthAdapter` interface, the `EthersAdapter` class, and the `Web3Adapter` class are no longer available. Similarly, `EthersAdapterConfig` and `Web3AdapterConfig` were removed as well. ` _24 // old _24 const ethAdapter = new EthersAdapter({ ethers, signerOrProvider }) _24 // const ethAdapter = new Web3Adapter({ web3, signerAddress }) _24 await Safe.create({ _24 ethAdapter, _24 safeAddress: '0xSafeAddress' _24 ... _24 }) _24 _24 // new _24 await Safe.init({ _24 provider: window.ethereum, // Or any compatible EIP-1193 provider _24 signer: '0xSignerAddressOrPrivateKey', // Signer address or private key _24 safeAddress: '0xSafeAddress' _24 ... _24 }) _24 _24 // ...or... _24 await Safe.init({ _24 provider: 'http://rpc.url', // Or websocket _24 signer: '0xPrivateKey' // Signer private key _24 safeAddress: '0xSafeAddress' _24 ... _24 }) ` `EthersTransactionOptions` and `Web3TransactionOptions` types are now `TransactionOptions`[](#etherstransactionoptions-and-web3transactionoptions-types-are-now-transactionoptions) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Together with the adapters, we also removed the specific transaction options objects for each library, leaving just a single `TransactionOptions` type. We removed the `gas` property from the `TransactionOptions` object as it was a specific property for the web3.js library. Now, you should use the `gasLimit` property instead. `EthersTransactionResult` and `Web3TransactionResult` types are now `TransactionResult`[](#etherstransactionresult-and-web3transactionresult-types-are-now-transactionresult) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Together with the adapters, we also removed the specific transaction result objects for each library, leaving just a single `TransactionResult` type. Contract classes suffixed with Ethers and Web3[](#contract-classes-suffixed-with-ethers-and-web3) -------------------------------------------------------------------------------------------------- All the contract classes that were suffixed with `Ethers` or `Web3` were renamed to remove the suffix. ` _10 SafeBaseContractEthers, SafeBaseContractWeb3 -> SafeBaseContract _10 MultiSendBaseContractEthers, MultiSendBaseContractWeb3 -> MultiSendBaseContract _10 MultiSendCallOnlyBaseContractEthers, MultiSendCallOnlyBaseContractWeb3 -> MultiSendCallOnlyBaseContract _10 SafeProxyFactoryBaseContractEthers, SafeProxyFactoryBaseContractWeb3 -> SafeProxyFactoryBaseContract _10 SignMessageLibBaseContractEthers, SignMessageLibBaseContractWeb3 -> SignMessageLibBaseContract _10 CreateCallBaseContractEthers, CreateCallBaseContractWeb3 -> CreateCallBaseContract ` [Migrate to v3](/sdk/protocol-kit/guides/migrate-to-v3 "Migrate to v3") [Migrate to v5](/sdk/protocol-kit/guides/migrate-to-v5 "Migrate to v5") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Multichain Safe Deployment – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Multichain Safe deployment Multichain Safe Deployment ========================== This guide will teach you how to replicate a Safe address across different chains using the Protocol Kit. This process includes initializing the Protocol Kit, configuring the Safes to deploy, predicting its address on different chains, and executing the deployment transactions. For more detailed information, see the [Protocol Kit Reference](/reference-sdk-protocol-kit/overview) . Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) Install dependencies[](#install-dependencies) ---------------------------------------------- First, you need to install the Protocol Kit. ` _10 pnpm add @safe-global/protocol-kit viem ` Steps[](#steps) ---------------- ### Imports[](#imports) Here are all the necessary imports for this guide. ` _10 import Safe, { _10 PredictedSafeProps, _10 SafeAccountConfig, _10 SafeDeploymentConfig _10 } from '@safe-global/protocol-kit' _10 import { waitForTransactionReceipt } from 'viem/actions' _10 import { gnosisChiado, sepolia } from 'viem/chains' ` ### Create a signer[](#create-a-signer) You need a signer to instantiate the Protocol Kit. This example uses a private key to obtain a signer, but other [EIP-1193 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-1193) compatible signers are also supported. For detailed information about signers, please refer to the [Protocol Kit reference](/reference-sdk-protocol-kit/overview) . ` _10 const SIGNER_PRIVATE_KEY = // ... ` ### Configure the Safe deployment[](#configure-the-safe-deployment) Define the [`predictedSafe`](/reference-sdk-protocol-kit/initialization/init#predictedsafe-optional) object with the configuration for all the Safe accounts you will deploy. Check the reference to learn about all the different configuration options. ` _10 const safeAccountConfig: SafeAccountConfig = { _10 owners: ['0x...', '0x...', '0x...'], _10 threshold: 2 _10 // ... _10 } _10 _10 const predictedSafe: PredictedSafeProps = { _10 safeAccountConfig _10 // ... _10 } ` ### Initialize the Protocol Kit[](#initialize-the-protocol-kit) Initialize an instance of the Protocol Kit for each network where you want to deploy a new Safe smart account by calling the [`init`](/reference-sdk-protocol-kit/initialization/init) method. Pass the `provider` with its corresponding value depending on the network, the `signer` executing the deployment, and the [`predictedSafe`](/reference-sdk-protocol-kit/initialization/init#predictedsafe-optional) with the Safe account configuration. ` _15 const protocolKitSepolia = await Safe.init({ _15 provider: sepolia.rpcUrls.default.http[0], _15 signer: SIGNER_PRIVATE_KEY, _15 predictedSafe, _15 onchainAnalytics // Optional _15 // ... _15 }) _15 _15 const protocolKitChiado = await Safe.init({ _15 provider: gnosisChiado.rpcUrls.default.http[0], _15 signer: PRIVATE_KEY, _15 predictedSafe, _15 onchainAnalytics // Optional _15 // ... _15 }) ` Optionally, you can [track your Safe deployments and transactions on-chain](/sdk/onchain-tracking) by using the `onchainAnalytics` property. ### Predict the Safe addresses[](#predict-the-safe-addresses) You can predict the Safe addresses by calling the [`getAddress`](/reference-sdk-protocol-kit/safe-info/getaddress) method from each Protocol Kit instance and ensure that the result addresses are the same. ` _10 const sepoliaPredictedSafeAddress = await protocolKitSepolia.getAddress() _10 const chiadoPredictedSafeAddress = await protocolKitChiado.getAddress() ` ### Deployment on Sepolia[](#deployment-on-sepolia) Create the deployment transaction to deploy a new Safe account in Sepolia by calling the [`createSafeDeploymentTransaction`](/reference-sdk-protocol-kit/deployment/createsafedeploymenttransaction) method. ` _10 const sepoliaDeploymentTransaction = _10 await protocolKitSepolia.createSafeDeploymentTransaction() ` Call the `sendTransaction` method from your Sepolia client instance and wait for the transaction to be executed. `` _14 const sepoliaClient = _14 await protocolKitSepolia.getSafeProvider().getExternalSigner() _14 _14 const transactionHashSepolia = await sepoliaClient!.sendTransaction({ _14 to: sepoliaDeploymentTransaction.to, _14 value: BigInt(sepoliaDeploymentTransaction.value), _14 data: sepoliaDeploymentTransaction.data as `0x${string}`, _14 chain: sepolia _14 }) _14 _14 await waitForTransactionReceipt( _14 sepoliaClient!, _14 { hash: transactionHashSepolia } _14 ) `` Once the deployment transaction is executed, connect the new Safe address to the Protocol Kit instance by calling the [`connect`](/reference-sdk-protocol-kit/initialization/connect) method. ` _10 const newProtocolKitSepolia = await protocolKitSepolia.connect({ _10 safeAddress: sepoliaPredictedSafeAddress _10 }) _10 _10 const isSepoliaSafeDeployed = await newProtocolKitSepolia.isSafeDeployed() // True _10 const sepoliaDeployedSafeAddress = await newProtocolKitSepolia.getAddress() ` If everything went well, `isSepoliaSafeDeployed` should be `true`, and the `sepoliaDeployedSafeAddress` should equal the `sepoliaPredictedSafeAddress`. ### Deployment on Chiado[](#deployment-on-chiado) Repeat the same steps to deploy a Safe with the same configuration on Chiado testnet. `` _24 const chiadoDeploymentTransaction = _24 await protocolKitChiado.createSafeDeploymentTransaction() _24 _24 const chiadoClient = _24 await protocolKitChiado.getSafeProvider().getExternalSigner() _24 _24 const transactionHashChiado = await chiadoClient!.sendTransaction({ _24 to: chiadoDeploymentTransaction.to, _24 value: BigInt(chiadoDeploymentTransaction.value), _24 data: chiadoDeploymentTransaction.data as `0x${string}`, _24 chain: gnosisChiado _24 }) _24 _24 await waitForTransactionReceipt( _24 chiadoClient!, _24 { hash: transactionHashChiado } _24 ) _24 _24 const newProtocolKitChiado = await protocolKitChiado.connect({ _24 safeAddress: chiadoPredictedSafeAddress _24 }) _24 _24 const isChiadoSafeDeployed = await newProtocolKitChiado.isSafeDeployed() // True _24 const chiadoDeployedSafeAddress = await newProtocolKitChiado.getAddress() `` If everything went well, `isChiadoSafeDeployed` should be `true`, and the `chiadoDeployedSafeAddress` should equal the `chiadoPredictedSafeAddress`. In addition, `chiadoDeployedSafeAddress` and `sepoliaDeployedSafeAddress` should have the same value. Recap and further reading[](#recap-and-further-reading) -------------------------------------------------------- After following this guide, you are able to deploy multiple Safe accounts with the same address on different chains using the Protocol Kit. [Safe deployment](/sdk/protocol-kit/guides/safe-deployment "Safe deployment") [Execute transactions](/sdk/protocol-kit/guides/execute-transactions "Execute transactions") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v5 – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Migrate to v5 Migrate to v5 ============= This guide references the major changes between v4 and v5 to help those migrating an existing app. Removing `SafeFactory` class[](#removing-safefactory-class) ------------------------------------------------------------ The `SafeFactory` class, previously used for deploying Safes, has been removed. The functionality to deploy Safes is now directly available in the `Safe` class through the new `createSafeDeploymentTransaction` method. ### Old Method Using `SafeFactory`[](#old-method-using-safefactory) ` _24 // old v4 code _24 import { SafeFactory, SafeAccountConfig } from '@safe-global/protocol-kit' _24 _24 const safeFactory = await SafeFactory.init({ _24 provider, _24 signer, _24 safeVersion // Optional _24 }) _24 _24 const safeAccountConfig: SafeAccountConfig = { _24 owners: ['0x...', '0x...', '0x...'], _24 threshold: 2 _24 } _24 _24 const protocolKit = await safeFactory.deploySafe({ _24 safeAccountConfig, _24 saltNonce // Optional _24 }) _24 _24 // Confirm the Safe is deployed and fetch properties _24 console.log('Is Safe deployed:', await protocolKit.isSafeDeployed()) _24 console.log('Safe Address:', await protocolKit.getAddress()) _24 console.log('Safe Owners:', await protocolKit.getOwners()) _24 console.log('Safe Threshold:', await protocolKit.getThreshold()) ` ### New Method Using `Safe` class[](#new-method-using-safe-class) `` _45 // new v5 code _45 import Safe, { PredictedSafeProps } from '@safe-global/protocol-kit' _45 _45 const predictedSafe: PredictedSafeProps = { _45 safeAccountConfig: { _45 owners: ['0x...', '0x...', '0x...'], _45 threshold: 2 _45 }, _45 safeDeploymentConfig: { _45 saltNonce, // Optional _45 safeVersion // Optional _45 } _45 } _45 _45 let protocolKit = await Safe.init({ _45 provider, _45 signer, _45 predictedSafe _45 }) _45 _45 // you can predict the address of your Safe if the Safe version is `v1.3.0` or above _45 const safeAddress = await protocolKit.getAddress() _45 _45 const deploymentTransaction = await protocolKit.createSafeDeploymentTransaction() _45 _45 // Execute this transaction using the integrated signer or your preferred external Ethereum client _45 const client = await protocolKit.getSafeProvider().getExternalSigner() _45 _45 const txHash = await client.sendTransaction({ _45 to: deploymentTransaction.to, _45 value: BigInt(deploymentTransaction.value), _45 data: deploymentTransaction.data as `0x${string}`, _45 chain: sepolia _45 }) _45 _45 const txReceipt = await client.waitForTransactionReceipt({ hash: txHash }) _45 _45 // Reconnect to the newly deployed Safe using the protocol-kit _45 protocolKit = await protocolKit.connect({ safeAddress }) _45 _45 // Confirm the Safe is deployed and fetch properties _45 console.log('Is Safe deployed:', await protocolKit.isSafeDeployed()) _45 console.log('Safe Address:', await protocolKit.getAddress()) _45 console.log('Safe Owners:', await protocolKit.getOwners()) _45 console.log('Safe Threshold:', await protocolKit.getThreshold()) `` ### Predict the Safe Address[](#predict-the-safe-address) You can predict the address of a Safe account before its deployment, as long as you are using Safe `v1.3.0` or greater, by replacing the `SafeFactory.predictSafeAddress` method with the `Safe.getAddress` method: ` _10 // old v4 code _10 const predictedSafeAddress = await safeFactory.predictSafeAddress( _10 safeAccountConfig, _10 saltNonce // optional _10 ) _10 _10 // new v5 code _10 const predictedSafeAddress = await protocolKit.getAddress() ` ### Migration Steps[](#migration-steps) * Remove any import or reference of the `SafeFactory` class from your code. * Replace the `SafeFactory.deploySafe` method with the `Safe.createSafeDeploymentTransaction` method where necessary. You can use your Ethereum client to execute this deployment transaction. * To predict the Address of your Safe Account, replace the `SafeFactory.predictSafeAddress` method with the `Safe.getAddress` method. * After the deployment transaction has been executed, it is necessary to reconnect the Protocol Kit instance to the newly created Safe address by using the `connect` method. The removal of `SafeFactory` means there’s one less class to initialize and manage within your project. You can now directly use the `Safe` class to handle all operations related to Safes, including their deployment. [Migrate to v4](/sdk/protocol-kit/guides/migrate-to-v4 "Migrate to v4") [Migrate to v6](/sdk/protocol-kit/guides/migrate-to-v6 "Migrate to v6") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Message signatures – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides [Signatures](/sdk/protocol-kit/guides/signatures) Messages Message signatures ================== Using the Protocol Kit, this guide explains how to generate and sign messages from a Safe account, including plain string messages and EIP-712 JSON messages. ℹ️ Before starting, check this guide's [setup](/sdk/protocol-kit/guides/signatures) . Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) Steps[](#steps) ---------------- ### Install dependencies[](#install-dependencies) ` _10 yarn install @safe-global/protocol-kit ` ### Create a message[](#create-a-message) Messages can be plain strings or valid EIP-712 typed data structures. ` _10 // An example of a string message _10 const STRING_MESSAGE = "I'm the owner of this Safe account" ` ` _47 // An example of a typed data message _47 const TYPED_MESSAGE = { _47 types: { _47 EIP712Domain: [ _47 { name: 'name', type: 'string' }, _47 { name: 'version', type: 'string' }, _47 { name: 'chainId', type: 'uint256' }, _47 { name: 'verifyingContract', type: 'address' } _47 ], _47 Person: [ _47 { name: 'name', type: 'string' }, _47 { name: 'wallets', type: 'address[]' } _47 ], _47 Mail: [ _47 { name: 'from', type: 'Person' }, _47 { name: 'to', type: 'Person[]' }, _47 { name: 'contents', type: 'string' } _47 ] _47 }, _47 domain: { _47 name: 'Ether Mail', _47 version: '1', _47 chainId: Number(chainId), _47 verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' _47 }, _47 primaryType: 'Mail', _47 message: { _47 from: { _47 name: 'Cow', _47 wallets: [ _47 '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', _47 '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF' _47 ] _47 }, _47 to: [ _47 { _47 name: 'Bob', _47 wallets: [ _47 '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', _47 '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', _47 '0xB0B0b0b0b0b0B000000000000000000000000000' _47 ] _47 } _47 ], _47 contents: 'Hello, Bob!' _47 } _47 } ` The `createMessage` method in the Protocol Kit allows for creating new messages and returns an instance of the `EthSafeMessage` class. Here, we are passing `TYPED_MESSAGE`, but `STRING_MESSAGE` could also be passed. ` _10 let safeMessage = protocolKit.createMessage(TYPED_MESSAGE) ` The returned `safeMessage` object contains the message data (`safeMessage.data`) and a map of owner-signature pairs (`safeMessage.signatures`). The structure is similar to the `EthSafeTransaction` class but applied for messages instead of transactions. We use `let` to initialize the `safeMessage` variable because we will add the signatures later. ` _10 class EthSafeMessage implements SafeMessage { _10 data: EIP712TypedData | string _10 signatures: Map = new Map() _10 ... _10 // Other props and methods _10 } ` ### Sign the message[](#sign-the-message) Once the `safeMessage` object is created, we need to collect the signatures from the signers who will sign it. Following our [setup](/sdk/protocol-kit/guides/signatures) , we will sign a message with `SAFE_3_4_ADDRESS`, the main Safe account in this guide. To do that, we first need to sign the same message with its owners: `OWNER_1_ADDRESS`, `OWNER_2_ADDRESS`, `SAFE_1_1_ADDRESS`, and `SAFE_2_3_ADDRESS`. #### ECDSA signatures[](#ecdsa-signatures) This applies to `OWNER_1_ADDRESS` and `OWNER_2_ADDRESS` accounts, as both are EOAs. The `signMessage` method takes the `safeMessage` together with a `SigningMethod` and adds the new signature to the `signMessage.signatures` map. Depending on the type of message, the `SigningMethod` can take these values: * `SigningMethod.ETH_SIGN` * `SigningMethod.ETH_SIGN_TYPED_DATA_V4` ` _25 // Connect OWNER_1_ADDRESS _25 protocolKit = await protocolKit.connect({ _25 provider: RPC_URL _25 signer: OWNER_1_PRIVATE_KEY _25 }) _25 _25 // Sign the safeMessage with OWNER_1_ADDRESS _25 // After this, the safeMessage contains the signature from OWNER_1_ADDRESS _25 safeMessage = await protocolKit.signMessage( _25 safeMessage, _25 SigningMethod.ETH_SIGN_TYPED_DATA_V4 _25 ) _25 _25 // Connect OWNER_2_ADDRESS _25 protocolKit = await protocolKit.connect({ _25 provider: RPC_URL _25 signer: OWNER_2_PRIVATE_KEY _25 }) _25 _25 // Sign the safeMessage with OWNER_2_ADDRESS _25 // After this, the safeMessage contains the signature from OWNER_1_ADDRESS and OWNER_2_ADDRESS _25 safeMessage = await protocolKit.signMessage( _25 safeMessage, _25 SigningMethod.ETH_SIGN_TYPED_DATA_V4 _25 ) ` #### Smart contract signatures[](#smart-contract-signatures) When signing with a Safe account, the `SigningMethod` will take the value `SigningMethod.SAFE_SIGNATURE`. ##### 1/1 Safe account[](#11-safe-account) This applies to the `SAFE_1_1_ADDRESS` account, another owner of `SAFE_3_4_ADDRESS`. We need to connect the Protocol Kit to `SAFE_1_1_ADDRESS` and the `OWNER_3_ADDRESS` account (the only owner of `SAFE_1_1_ADDRESS`) and sign the message. ` _27 // Create a new message object _27 let messageSafe1_1 = await createMessage(TYPED_MESSAGE) _27 _27 // Connect OWNER_3_ADDRESS and SAFE_1_1_ADDRESS _27 protocolKit = await protocolKit.connect({ _27 provider: RPC_URL _27 signer: OWNER_3_PRIVATE_KEY, _27 safeAddress: SAFE_1_1_ADDRESS _27 }) _27 _27 // Sign the messageSafe1_1 with OWNER_3_ADDRESS _27 // After this, the messageSafe1_1 contains the signature from OWNER_3_ADDRESS _27 messageSafe1_1 = await signMessage( _27 messageSafe1_1, _27 SigningMethod.SAFE_SIGNATURE, _27 SAFE_3_4_ADDRESS // Parent Safe address _27 ) _27 _27 // Build the contract signature of SAFE_1_1_ADDRESS _27 const signatureSafe1_1 = await buildContractSignature( _27 Array.from(messageSafe1_1.signatures.values()), _27 SAFE_1_1_ADDRESS _27 ) _27 _27 // Add the signatureSafe1_1 to safeMessage _27 // After this, the safeMessage contains the signature from OWNER_1_ADDRESS, OWNER_2_ADDRESS and SAFE_1_1_ADDRESS _27 safeMessage.addSignature(signatureSafe1_1) ` When signing with a child Safe account, we need to specify the parent Safe address to generate the signature based on the version of the contract. ##### 2/3 Safe account[](#23-safe-account) This applies to the `SAFE_2_3_ADDRESS` account, another owner of `SAFE_3_4_ADDRESS`. We need to connect the Protocol Kit to `SAFE_2_3_ADDRESS` and the `OWNER_4_ADDRESS` and `OWNER_5_ADDRESS` accounts (owners of `SAFE_2_3_ADDRESS`) and sign the message. ` _41 // Create a new message object _41 let messageSafe2_3 = await createMessage(TYPED_MESSAGE) _41 _41 // Connect OWNER_4_ADDRESS and SAFE_2_3_ADDRESS _41 protocolKit = await protocolKit.connect({ _41 provider: RPC_URL, _41 signer: OWNER_4_PRIVATE_KEY, _41 safeAddress: SAFE_2_3_ADDRESS _41 }) _41 _41 // Sign the messageSafe2_3 with OWNER_4_ADDRESS _41 // After this, the messageSafe2_3 contains the signature from OWNER_4_ADDRESS _41 messageSafe2_3 = await protocolKit.signMessage( _41 messageSafe2_3, _41 SigningMethod.SAFE_SIGNATURE, _41 SAFE_3_4_ADDRESS // Parent Safe address _41 ) _41 _41 // Connect OWNER_5_ADDRESS _41 protocolKit = await protocolKit.connect({ _41 provider: RPC_URL, _41 signer: OWNER_5_PRIVATE_KEY _41 }) _41 _41 // Sign the messageSafe2_3 with OWNER_5_ADDRESS _41 // After this, the messageSafe2_3 contains the signature from OWNER_5_ADDRESS _41 messageSafe2_3 = await protocolKit.signMessage( _41 messageSafe2_3, _41 SigningMethod.SAFE_SIGNATURE, _41 SAFE_3_4_ADDRESS // Parent Safe address _41 ) _41 _41 // Build the contract signature of SAFE_2_3_ADDRESS _41 const signatureSafe2_3 = await buildContractSignature( _41 Array.from(messageSafe2_3.signatures.values()), _41 SAFE_2_3_ADDRESS _41 ) _41 _41 // Add the signatureSafe2_3 to safeMessage _41 // After this, the safeMessage contains the signature from OWNER_1_ADDRESS, OWNER_2_ADDRESS, SAFE_1_1_ADDRESS and SAFE_2_3_ADDRESS _41 safeMessage.addSignature(signatureSafe2_3) ` After following all the steps above, the `safeMessage` now contains all the signatures from the owners of the Safe. ### Publish the signed message[](#publish-the-signed-message) As messages aren't stored in the blockchain, we must make them public and available to others by storing them elsewhere. Safe messages can be stored on-chain and off-chain: * **Off-chain**: Messages are stored in the Safe Transaction Service. This is the default option and doesn't require any on-chain interaction. * **On-chain**: Messages are [stored (opens in a new tab)](https://github.com/safe-global/safe-smart-account/blob/f03dfae65fd1d085224b00a10755c509a4eaacfe/contracts/Safe.sol#L68-L69) in the Safe contract. Safe supports signing [EIP-191 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-191) messages and [EIP-712 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-712) typed data messages all together with off-chain [EIP-1271 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-1271) validation for signatures. #### Off-chain messages[](#off-chain-messages) To use off-chain messages, we need to use the functionality from this guide and call the Safe Transaction Service API to store the messages and signatures. We mentioned the utility of storing messages in the contract. Off-chain messages have the same purpose, but they're stored in the Safe Transaction Service. It stores the messages and signatures in a database. It's a centralized service, but it's open-source and can be deployed by anyone. The Safe Transaction Service is used by [Safe{Wallet}](https:/app.safe.global) to store messages and signatures by default. ##### Propose the message[](#propose-the-message) To store a new message, we need to call the `addMessage` from the API Kit, passing the Safe address, an object with the message, and a signature from one owner. ` _12 // Get the signature from OWNER_1_ADDRESS _12 const signatureOwner1 = safeMessage.getSignature(OWNER_1_PRIVATE_KEY) as EthSafeSignature _12 _12 // Instantiate the API Kit _12 // Use the chainId where you have the Safe account deployed _12 const apiKit = new SafeApiKit({ chainId }) _12 _12 // Propose the message _12 apiKit.addMessage(SAFE_3_4_ADDRESS, { _12 message: TYPED_MESSAGE, // or STRING_MESSAGE _12 signature: buildSignatureBytes([signatureOwner1]) _12 }) ` The message is now publicly available in the Safe Transaction Service with the signature of the owner who submitted it. ##### Confirm the message[](#confirm-the-message) To add the signatures from the remaining owners, we need to call the `addMessageSignature`, passing the `safeMessageHash` and a signature from the owner. ` _25 // Get the safeMessageHash _25 const safeMessageHash = await protocolKit.getSafeMessageHash( _25 hashSafeMessage(TYPED_MESSAGE) // or STRING_MESSAGE _25 ) _25 _25 // Get the signature from OWNER_2_ADDRESS _25 const signatureOwner2 = safeMessage.getSignature(OWNER_2_ADDRESS) as EthSafeSignature _25 _25 // Add signature from OWNER_2_ADDRESS _25 await apiKit.addMessageSignature( _25 safeMessageHash, _25 buildSignatureBytes([signatureOwner2]) _25 ) _25 _25 // Add signature from the owner SAFE_1_1_ADDRESS _25 await apiKit.addMessageSignature( _25 safeMessageHash, _25 buildSignatureBytes([signatureSafe1_1]) _25 ) _25 _25 // Add signature from the owner SAFE_2_3_ADDRESS _25 await apiKit.addMessageSignature( _25 safeMessageHash, _25 buildSignatureBytes([signatureSafe2_3]) _25 ) ` At this point, the message stored in the Safe Transaction Service contains all the required signatures from the owners of the Safe. The `getMessage` method returns the status of a message. ` _10 const confirmedMessage = await apiKit.getMessage(safeMessageHash) ` [Safe{Wallet} (opens in a new tab)](https://app.safe.global) exposes to its users the list of off-chain messages signed by a Safe account. ` _10 https://app.safe.global/transactions/messages?safe=: ` #### On-chain messages[](#on-chain-messages) Storing messages on-chain is less efficient than doing it off-chain because it requires executing a transaction to store the message hash in the contract, resulting in additional gas costs. To do this on-chain, we use the `SignMessageLib` contract. ` _10 // Get the contract with the correct version _10 const signMessageLibContract = await getSignMessageLibContract({ _10 safeVersion: '1.4.1' _10 }) ` We need to calculate the `messageHash`, encode the call to the `signMessage` function in the `SignMessageLib` contract and create the transaction that will store the message hash in that contract. ` _13 const messageHash = hashSafeMessage(MESSAGE) _13 const txData = signMessageLibContract.encode('signMessage', [messageHash]) _13 _13 const safeTransactionData: SafeTransactionDataPartial = { _13 to: signMessageLibContract.address, _13 value: '0', _13 data: txData, _13 operation: OperationType.DelegateCall _13 } _13 _13 const signMessageTx = await protocolKit.createTransaction({ _13 transactions: [safeTransactionData] _13 }) ` Once the transaction object is instantiated, the owners must sign and execute it. ` _10 // Collect the signatures using the signTransaction method _10 _10 // Execute the transaction to store the messageHash _10 await protocolKit.executeTransaction(signMessageTx) ` Once the transaction is executed, the message hash will be stored in the contract. ### Validate the signature[](#validate-the-signature) #### On-chain[](#on-chain) When a message is stored on-chain, the `isValidSignature` method in the Protocol Kit needs to be called with the parameters `messageHash` and `0x`. The method will check the stored hashes in the Safe contract to validate the signature. ` _10 import { hashSafeMessage } from '@safe-global/protocol-kit' _10 _10 const messageHash = hashSafeMessage(MESSAGE) _10 _10 const isValid = await protocolKit.isValidSignature(messageHash, '0x') ` #### Off-chain[](#off-chain) When a message is stored off-chain, the `isValidSignature` method in the Protocol Kit must be called with the `messageHash` and the `encodedSignatures` parameters. The method will check the `isValidSignature` function defined in the `CompatibilityFallbackHandler` [contract (opens in a new tab)](https://github.com/safe-global/safe-smart-account/blob/f03dfae65fd1d085224b00a10755c509a4eaacfe/contracts/handler/CompatibilityFallbackHandler.sol#L51-L68) to validate the signature. ` _10 const encodedSignatures = safeMessage.encodedSignatures() _10 _10 const isValid = await protocolKit.isValidSignature( _10 messageHash, _10 encodedSignatures _10 ) ` [Transactions](/sdk/protocol-kit/guides/signatures/transactions "Transactions") [Migrate to v1](/sdk/protocol-kit/guides/migrate-to-v1 "Migrate to v1") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Resource Hub – Safe Docs Resource Hub ============ ​ ​ Example: 4337, Introduction, 7579 25 results Filter [Suggest new resource](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=resource-hub&projects=&template=resource-hub-submission.yml&title=%5BResource+Hub%5D+) Resource type * Blog Post * Podcast * Video Source * Community * Safe Team Topics * 4337 * Introduction * 7579 * 7702 * Account Abstraction * AI * Deep Dive * Passkeys * Permissionless * Perspectives * React * React Native * Safe Core SDK * Safe Smart Account * Safe Transaction Service * Security * Signer * Tutorial * Vue * WalletConnect [Create an AI agent that can interact with your Safe\ \ February, 2025\ \ In this tutorial, we will learn how to set up and deploy an AI agent that has capabilities to access a Safe and prepare transactions for it 🤖.\ \ Blog Post\ \ Tutorial\ \ Safe Core SDK\ \ AI](https://docs.safe.global/home/ai-agent-setup) [Workshop: Build reliable AI agents with Safe capabilities\ \ February, 2025\ \ Video\ \ Tutorial\ \ Safe Core SDK\ \ AI](https://www.youtube.com/watch?v=y90oH7w8h8U) [![resource-img](https://docs.safe.global/og_image.png)\ \ How to build a React Native app with Safe and passkeys\ \ January, 2025\ \ An increasing number of applications rely on passkeys to authenticate users securely and with little friction. Security and user-friendliness are crucial to...\ \ Blog Post\ \ Tutorial\ \ Safe Smart Account\ \ Passkeys\ \ React Native\ \ 4337](https://docs.safe.global/advanced/passkeys/tutorials/react-native) [![resource-img](https://mintlify.com/docs/api/og?division=Documentation&title=How+to+Use+Dynamic+in+a+Safe+App&logoLight=https%3A%2F%2Fmintlify.s3-us-west-1.amazonaws.com%2Fdynamic-docs%2Flogo%2Flight.svg&logoDark=https%3A%2F%2Fmintlify.s3-us-west-1.amazonaws.com%2Fdynamic-docs%2Flogo%2Fdark.svg&primaryColor=%234779FF&lightColor=%23A0BFFF&darkColor=%2369A5FF)\ \ How to Use Dynamic in a Safe App\ \ October, 2024\ \ In this guide, you'll learn how to integrate Dynamic with the Safe App environment.\ \ Blog Post\ \ Tutorial\ \ Signer](https://docs.dynamic.xyz/guides/integrations/safe-app) [Convert an EOA to a Smart Account with EIP-7702\ \ October, 2024\ \ Video\ \ Tutorial\ \ Safe Smart Account\ \ 7702](https://www.youtube.com/watch?v=dx4mk6tKHCo) [![resource-img](https://docs.safe.global/og_image.png)\ \ How to build a React app with Safe and passkeys – Safe Docs\ \ August, 2024\ \ This tutorial will teach you to create a React app for using passkeys in your Safe. You will learn how to use passkeys (create, store, and use them securely) and how they can interact with a Safe.\ \ Blog Post\ \ Tutorial\ \ Safe Core SDK\ \ Passkeys\ \ React](https://docs.safe.global/home/passkeys-tutorials/safe-passkeys-tutorial) [![resource-img](https://docs.safe.global/og_image.png)\ \ How to build a Vue app with Safe and passkeys – Safe Docs\ \ August, 2024\ \ This tutorial will teach you to create a Vue app for using passkeys in your Safe. You will learn how to use passkeys (create, store, and use them securely) and how they can interact with a Safe.\ \ Blog Post\ \ Tutorial\ \ Safe Core SDK\ \ Passkeys\ \ Vue](https://docs.safe.global/home/passkeys-tutorials/safe-passkeys-nuxt) [![resource-img](https://docs.safe.global/og_image.png)\ \ How to build an app with Safe and ERC-7579 – Safe Docs\ \ August, 2024\ \ This tutorial will teach you how to deploy an ERC-7579-compatible Safe Smart Account and use an ERC-7579-compatible module, the Scheduled Transfer from Rhinestone.\ \ Blog Post\ \ Tutorial\ \ Permissionless\ \ 7579](https://docs.safe.global/advanced/erc-7579/tutorials/7579-tutorial) [Using Passkeys with Safe\ \ August, 2024\ \ Video\ \ Tutorial\ \ Passkeys\ \ Permissionless\ \ 4337\ \ React\ \ Signer](https://www.youtube.com/watch?v=zDe3zl7yApk) [Building with the Safe 4337 Module\ \ August, 2024\ \ Video\ \ Safe Smart Account\ \ 4337\ \ Safe Core SDK](https://www.youtube.com/watch?v=VInxAVoYYkE) [Universal Wallets with Safe\ \ August, 2024\ \ Video\ \ Safe Smart Account\ \ WalletConnect](https://www.youtube.com/watch?v=h40Gy3fsRJg) [![resource-img](https://img.youtube.com/vi/yWnFCjBr7_E/0.jpg)\ \ Tutorial to build a validator and enable on Safe\ \ June, 2024\ \ In this tutorial, we will create a basic 7579 validator module. This module can be installed into a Safe Account using the Safe 7579 adapter, enhancing the validation flow of the Safe Account.\ \ Blog Post\ \ Tutorial\ \ 7579](https://docs.zenguard.xyz/getting-started/building-7579-validator/) [Show more](/resource-hub?page=2) Listings are not endorsements and are only for educational purposes. --- # Execute transactions – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Execute transactions Execute transactions ==================== In this guide, you will learn how to create Safe transactions, sign them, collect the signatures from the different owners, and execute them. See the [Protocol Kit reference](/reference-sdk-protocol-kit/overview) to find more details and configuration options. Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) . * An existing Safe with several signers. Install dependencies[](#install-dependencies) ---------------------------------------------- First, you need to install some dependencies. ` _10 pnpm add @safe-global/api-kit \ _10 @safe-global/protocol-kit \ _10 @safe-global/types-kit ` Steps[](#steps) ---------------- ### Imports[](#imports) Here are all the necessary imports for this guide. ` _10 import SafeApiKit from '@safe-global/api-kit' _10 import Safe from '@safe-global/protocol-kit' _10 import { _10 MetaTransactionData, _10 OperationType _10 } from '@safe-global/types-kit' ` ### Setup[](#setup) You need a Safe account setup with two or more signers and threshold two, so at least multiple signatures have to be collected when executing a transaction. This example uses private keys, but any EIP-1193 compatible signers can be used. ` _10 const SAFE_ADDRESS = // ... _10 _10 const OWNER_1_ADDRESS = // ... _10 const OWNER_1_PRIVATE_KEY = // ... _10 _10 const OWNER_2_PRIVATE_KEY = // ... _10 _10 const RPC_URL = 'https://eth-sepolia.public.blastapi.io' ` This guide uses Sepolia, but you can use any chain from the Safe Transaction Service [supported networks](/advanced/smart-account-supported-networks?service=Transaction+Service&service=Safe%7BCore%7D+SDK) . ### Initialize the Protocol Kit[](#initialize-the-protocol-kit) To handle transactions and signatures, you need to create an instance of the Protocol Kit with the `provider`, `signer` and `safeAddress`. Optionally, you can [track your Safe transactions on-chain](/sdk/onchain-tracking) by using the `onchainAnalytics` property. ` _10 const protocolKitOwner1 = await Safe.init({ _10 provider: RPC_URL, _10 signer: OWNER_1_PRIVATE_KEY, _10 safeAddress: SAFE_ADDRESS, _10 onchainAnalytics // Optional _10 }) ` ### Create a transaction[](#create-a-transaction) Create a `safeTransactionData` object with the properties of the transaction, add it to an array of transactions you want to execute, and pass it to the `createTransaction` method. ` _10 const safeTransactionData: MetaTransactionData = { _10 to: '0x', _10 value: '1', // 1 wei _10 data: '0x', _10 operation: OperationType.Call _10 } _10 _10 const safeTransaction = await protocolKitOwner1.createTransaction({ _10 transactions: [safeTransactionData] _10 }) ` For more details on what to include in a transaction, see the [`createTransaction`](/sdk/protocol-kit/reference/safe#createtransaction) method in the reference. ### Propose the transaction[](#propose-the-transaction) Before a transaction can be executed, the signer who creates it needs to send it to the Safe Transaction Service so that it is accessible by the other owners, who can then give their approval and sign the transaction. Firstly, you need to create an instance of the API Kit. In chains where the [Safe Transaction Service](/core-api/transaction-service-overview) is supported, it's enough to specify the `chainId` property. ` _10 const apiKit = new SafeApiKit({ _10 chainId: 11155111n _10 }) ` You need to calculate the Safe transaction hash, sign the transaction hash, and call the `proposeTransaction` method from the API Kit instance to propose a transaction. For a full list and description of the properties see [`proposeTransaction`](/reference-sdk-api-kit/proposetransaction) in the API Kit reference. ` _13 // Deterministic hash based on transaction parameters _13 const safeTxHash = await protocolKitOwner1.getTransactionHash(safeTransaction) _13 _13 // Sign transaction to verify that the transaction is coming from owner 1 _13 const senderSignature = await protocolKitOwner1.signHash(safeTxHash) _13 _13 await apiKit.proposeTransaction({ _13 safeAddress, _13 safeTransactionData: safeTransaction.data, _13 safeTxHash, _13 senderAddress: OWNER_1_ADDRESS, _13 senderSignature: senderSignature.data _13 }) ` ### Retrieve the pending transactions[](#retrieve-the-pending-transactions) The other signers need to retrieve the pending transactions from the Safe Transaction Service. Depending on the situation, different methods in the API Kit are available. Call the [`getPendingTransactions`](/reference-sdk-api-kit/getpendingtransactions) method to retrieve all the pending transactions of a Safe account. ` _10 const pendingTransactions = (await apiKit.getPendingTransactions(safeAddress)).results ` ### Confirm the transaction[](#confirm-the-transaction) Once a signer has the pending transaction, they need to sign it with the Protocol Kit and submit the signature to the service using the [`confirmTransaction`](/reference-sdk-api-kit/confirmtransaction) method. ` _14 const protocolKitOwner2 = await Safe.init({ _14 provider: RPC_URL, _14 signer: OWNER_2_PRIVATE_KEY, _14 safeAddress: SAFE_ADDRESS _14 }) _14 _14 const safeTxHash = transaction.transactionHash _14 const signature = await protocolKitOwner2.signHash(safeTxHash) _14 _14 // Confirm the Safe transaction _14 const signatureResponse = await apiKit.confirmTransaction( _14 safeTxHash, _14 signature.data _14 ) ` ### Execute the transaction[](#execute-the-transaction) The Safe transaction is now ready to be executed. This can be done using the [Safe{Wallet} (opens in a new tab)](https://app.safe.global) web interface, the [Protocol Kit](/reference-sdk-protocol-kit/transactions/executetransaction) , the [Safe CLI](/advanced/cli-reference/tx-service-commands#execute-pending-transaction) or any other tool that's available. In this guide, the first signer will get the transaction from the service by calling the [`getTransaction`](/reference-sdk-api-kit/gettransaction) method and execute it by passing the transaction with all the signatures to the [`executeTransaction`](/reference-sdk-protocol-kit/transactions/executetransaction) method. ` _10 const safeTransaction = await apiKit.getTransaction(safeTxHash) _10 const executeTxResponse = await protocolKitOwner1.executeTransaction(safeTransaction) ` Recap and further reading[](#recap-and-further-reading) -------------------------------------------------------- After following this guide, you are able to create, sign, and execute Safe transactions with the Protocol Kit and share the signatures with the different signers using the API Kit. [Multichain Safe deployment](/sdk/protocol-kit/guides/multichain-safe-deployment "Multichain Safe deployment") [Signatures](/sdk/protocol-kit/guides/signatures "Signatures") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v2 – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Migrate to v2 Migrate to v2 ============= This guide references the major changes between v1 and v2 to help those migrating an existing app. **Note:** When upgrading to `protocol-kit` v2, it's necessary to upgrade to `safe-core-sdk-types` v3. MasterCopy to Singleton[](#mastercopy-to-singleton) ---------------------------------------------------- To avoid confusion between terms used as synonyms, we aligned all our code to use the word `singleton`. * Rename `isL1SafeMasterCopy` to `isL1SafeSingleton` ` _10 // old: _10 SafeFactory.create({ ethAdapter, isL1SafeMasterCopy: true }) _10 _10 // new: _10 SafeFactory.create({ ethAdapter, isL1SafeSingleton: true }) ` Ethers v6[](#ethers-v6) ------------------------ From `protocolKit v2`, `EthersAdapter` will only be compatible with ethers.js v6. If you still need to use v5, we recommend you keep `protocolKit v1`, but we encourage you to migrate to the latest version when you can. Protocol Kit createTransaction() accepts only transaction array[](#protocol-kit-createtransaction-accepts-only-transaction-array) ---------------------------------------------------------------------------------------------------------------------------------- In `protocolKit v1`, the `createTransaction()` method accepted either an object or an array as a parameter. To avoid confusion, we changed it to accept only an array. Here is a migration example: ` _24 // old: _24 const safeTransactionData = { _24 to: '', _24 data: '', _24 value: '', _24 nonce: '', _24 safeTxGas: '' _24 } _24 const safeTransaction = protocolKit.createTransaction({ safeTransactionData }) _24 _24 // new: _24 const safeTransactionData = { _24 to: '', _24 data: '', _24 value: '' _24 } _24 const options = { _24 nonce: '', _24 safeTxGas: '' _24 } _24 const safeTransaction = protocolKit.createTransaction({ _24 transactions: [safeTransactionData], _24 options _24 }) ` [Migrate to v1](/sdk/protocol-kit/guides/migrate-to-v1 "Migrate to v1") [Migrate to v3](/sdk/protocol-kit/guides/migrate-to-v3 "Migrate to v3") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # API Kit – Safe Docs SDK API Kit API Kit ======= The API Kit facilitates the interaction with the [Safe Transaction Service API](/core-api/transaction-service-overview) , allowing to propose and share transactions with the other signers of a Safe, sending the signatures to the service to collect them, getting information about a Safe (like reading the transaction history, pending transactions, enabled Modules and Guards, etc.), among other features. [#### @safe-global/api-kit](https://www.npmjs.com/package/@safe-global/api-kit) The following guides show how to use the API Kit and integrate it into your project: * [Propose and confirm transactions](/sdk/api-kit/guides/propose-and-confirm-transactions) Resources[](#resources) ------------------------ * [API Kit on GitHub (opens in a new tab)](https://github.com/safe-global/safe-core-sdk/tree/main/packages/api-kit) [Reference](/sdk/api-kit# "Reference") [Propose and Confirm Transactions](/sdk/api-kit/guides/propose-and-confirm-transactions "Propose and Confirm Transactions") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v1 – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Migrate to v1 Migrate to v1 ============= This guide references the major changes between `safe-core-sdk` and `protocol-kit` v1 to help those migrating an existing application. **Note:** Follow this guide before migrating to `protocol-kit` v2. You can remove `@safe-global/safe-core-sdk` from your `package.json` after completing this guide. Adding the new dependency[](#adding-the-new-dependency) -------------------------------------------------------- To add the Protocol Kit to your project, run the following: ` _10 yarn add @safe-global/protocol-kit@1.3.0 ` If you use the types library, you will need to update to v2.3.0: ` _10 yarn add @safe-global/safe-core-sdk-types@2.3.0 ` `EthAdapter`[](#ethadapter) ---------------------------- ### `EthersAdapter` (safe-ethers-lib)[](#ethersadapter-safe-ethers-lib) `EthersAdapter` isn't in a separate package anymore. Now, it's provided inside the `protocol-kit` package. **`protocol-kit v1` only supports `ethers v5`** ` _10 // old _10 import EthersAdapter from '@safe-global/safe-ethers-lib' _10 _10 // new _10 import { EthersAdapter } from '@safe-global/protocol-kit' ` After this change, you can remove `@safe-global/safe-ethers-lib` from your `package.json`. ### `Web3Adapter` (safe-web3-lib)[](#web3adapter-safe-web3-lib) `Web3Adapter` isn't in a separate package anymore. Now, it's part of the `protocol-kit` package. **Note:** `protocol-kit` v1 only supports Web3.js v1. ` _10 // old _10 import Web3Adapter from '@safe-global/safe-web3-lib' _10 _10 // new _10 import { Web3Adapter } from '@safe-global/protocol-kit' ` After this change, you can remove `@safe-global/safe-web3-lib` from your `package.json`. ### Type changes[](#type-changes) Type changes are affecting the web3 and ethers adapter libraries. `getSafeContract`, `getMultisendContract`, `getMultisendCallOnlyContract`, `getCompatibilityFallbackHandlerContract`, `getSafeProxyFactoryContract`, `getSignMessageLibContract` and `getCreateCallContract` don't need the `chainId` parameter anymore, they will use the chain set on the provider. Also, they return a `Promise` now. `estimateGas` now returns a `string` instead of a `number`. `safeFactory.deploySafe()`[](#safefactorydeploysafe) ----------------------------------------------------- `SafeDeploymentConfig` was simplified. If you were using a `saltNonce` you should set it like this: ` _16 // old _16 const safeAccountConfig: SafeAccountConfig = { _16 ... _16 } _16 const safeDeploymentConfig: SafeDeploymentConfig = { saltNonce } _16 _16 const safeSdk = await safeFactory.deploySafe({ safeAccountConfig, safeDeploymentConfig }) _16 _16 // new _16 const safeAccountConfig: SafeAccountConfig = { _16 ... _16 } _16 _16 const saltNonce = '' _16 _16 const protocolKit = await safeFactory.deploySafe({ safeAccountConfig, saltNonce }) ` `getAddress()`[](#getaddress) ------------------------------ The `getAddress()` method now returns a `Promise`. ` _10 // old _10 const safeAddress = safeSdk.getAddress() _10 _10 // new _10 const safeAddress = await protocolKit.getAddress() ` General type changes[](#general-type-changes) ---------------------------------------------- If you set `safeTxGas`, `baseGas`, or `gasPrice`, you must use the type `string` instead of `number`. [Messages](/sdk/protocol-kit/guides/signatures/messages "Messages") [Migrate to v2](/sdk/protocol-kit/guides/migrate-to-v2 "Migrate to v2") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v6 – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides Migrate to v6 Migrate to v6 ============= This guide references the major changes between v5 and v6 to help those migrating an existing app. Removing `SigningMethod` and `SigningMethodType`[](#removing-signingmethod-and-signingmethodtype) -------------------------------------------------------------------------------------------------- We moved these types to the `api-kit`. ` _10 // old v5 code _10 import { SigningMethod, SigningMethodType } from '@safe-global/protocol-kit' _10 _10 // new v6 code _10 import { SigningMethod, SigningMethodType } from '@safe-global/types-kit' ` [Migrate to v5](/sdk/protocol-kit/guides/migrate-to-v5 "Migrate to v5") [Reference](/sdk/protocol-kit/guides/migrate-to-v6# "Reference") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Transaction signatures – Safe Docs SDK [Protocol Kit](/sdk/protocol-kit) Guides [Signatures](/sdk/protocol-kit/guides/signatures) Transactions Transaction signatures ====================== This guide explains how transactions are signed by the Safe owners using the Protocol Kit. ℹ️ Before starting, check this guide's [setup](/sdk/protocol-kit/guides/signatures) . Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) Steps[](#steps) ---------------- ### Install dependencies[](#install-dependencies) ` _10 yarn install @safe-global/protocol-kit ` ### Create a transaction[](#create-a-transaction) The `createTransaction` method in the Protocol Kit allows the creation of new Safe transactions and returns an instance of the `EthSafeTransaction` class. ` _10 // Create a transaction to send 0.01 ETH _10 const safeTransactionData: SafeTransactionDataPartial = { _10 to: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1', _10 value: '100000000000000000', // 0.01 ETH _10 data: '0x' _10 } _10 _10 let safeTransaction = await protocolKit.createTransaction({ _10 transactions: [safeTransactionData] _10 }) ` The returned `safeTransaction` object contains the transaction data (`safeTransaction.data`) and a map of the owner-signature pairs (`safeTransaction.signatures`). The structure is similar to the `EthSafeMessage` class but applied for transactions instead of messages. We use `let` to initialize the `safeTransaction` variable because we will add the signatures later. ` _10 class EthSafeTransaction implements SafeTransaction { _10 data: SafeTransactionData _10 signatures: Map = new Map() _10 ... _10 // Other properties and methods _10 } ` ### Sign the transaction[](#sign-the-transaction) Once the `safeTransaction` object is created, we need to collect the signatures from the signers who will sign it. Following our [setup](/sdk/protocol-kit/guides/signatures) , we will sign a Safe transaction from `SAFE_3_4_ADDRESS`, the main Safe account in this guide. To do that, we first need to sign the same transaction with its owners: `OWNER_1_ADDRESS`, `OWNER_2_ADDRESS`, `SAFE_1_1_ADDRESS`, and `SAFE_2_3_ADDRESS`. #### ECDSA signature[](#ecdsa-signature) This applies to `OWNER_1_ADDRESS` and `OWNER_2_ADDRESS` accounts, as both are EOAs. The `signTransaction` method takes the `safeTransaction` together with a `SigningMethod` and adds the new signature to the `safeTransaction.signatures` map. Depending on the type of message, the `SigningMethod` can take these values: * `SigningMethod.ETH_SIGN` * `SigningMethod.ETH_SIGN_TYPED_DATA_V4` ` _25 // Connect OWNER_1_ADDRESS _25 protocolKit = await protocolKit.connect({ _25 provider: RPC_URL, _25 signer: OWNER_1_PRIVATE_KEY _25 }) _25 _25 // Sign the safeTransaction with OWNER_1_ADDRESS _25 // After this, the safeTransaction contains the signature from OWNER_1_ADDRESS _25 safeTransaction = await protocolKit.signTransaction( _25 safeTransaction, _25 SigningMethod.ETH_SIGN _25 ) _25 _25 // Connect OWNER_2_ADDRESS _25 protocolKit = await protocolKit.connect({ _25 provider: RPC_URL, _25 signer: OWNER_2_PRIVATE_KEY _25 }) _25 _25 // Sign the safeTransaction with OWNER_2_ADDRESS _25 // After this, the safeTransaction contains the signature from OWNER_1_ADDRESS and OWNER_2_ADDRESS _25 safeTransaction = await protocolKit.signTransaction( _25 safeTransaction, _25 SigningMethod.ETH_SIGN_TYPED_DATA_V4 _25 ) ` At this point, the `safeTransaction` object should look like this: ` _15 EthSafeTransaction { _15 signatures: Map(2) { _15 '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1' => EthSafeSignature { _15 signer: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1', _15 data: '0x969308e2abeda61a0c9c41b3c615012f50dd7456ca76ea39a18e3b975abeb67f275b07810dd59fc928f3f9103e52557c1578c7c5c171ffc983afa5306466b1261f', _15 isContractSignature: false _15 }, _15 '0xffcf8fdee72ac11b5c542428b35eef5769c409f0' => EthSafeSignature { _15 signer: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', _15 data: '0x4d63c79cf9d743782bc31ad58c1a316020b39839ab164caee7ecac9829f685cc44ec0d066a5dfe646b2ffeeb37575df131daf9c96ced41b8c7c4aea8dc5461801c', _15 isContractSignature: false _15 } _15 }, _15 data: { ... } _15 } ` The `signatures.data` represents a specific signature. The `isContractSignature` flag set to `false` indicates that the signature isn't a smart contract signature but an ECDSA signature instead. An ECDSA signature comprises two 32-byte integers (`r`, `s`) and an extra byte for recovery (`v`), totaling 65 bytes. In hexadecimal string format, each byte is represented by two characters. Hence, a 65-byte Ethereum signature will be 130 characters long. Including the `0x` prefix commonly used with signatures, the total character count for such a signature would be 132. Two more characters are required to represent a byte (8 bits) in hexadecimal. Each hexadecimal character represents four bits. Therefore, two hexadecimal characters (2 x 4 bits) can represent a byte (8 bits). The final part of the signature, either `1f` or `1c`, indicates the signature type. Safe supports the following `v` values: * `0`: Contract signature. * `1`: Approved hash. * `{27, 28} + 4`: Ethereum adjusted ECDSA recovery byte for EIP-191 signed message. > Regarding the EIP-191 signed message, the `v` value is adjusted to the ECDSA `v + 4`. If the generated value is `28` and adjusted to `0x1f`, the signature verification will fail as it should be `0x20` ('28 + 4 = 32`) instead. If` v > 30`, then the default` v `(`27`,` 28`) was adjusted because of the` eth\_sign\` implementation. This calculation is automatically done by the Safe{Core} SDK. * Other: Ethereum adjusted ECDSA recovery byte for raw signed hash. The hexadecimal value `1f` equals the decimal number `31`. If the decimal value is greater than `30`, it [indicates (opens in a new tab)](https://github.com/safe-global/safe-smart-account/blob/f03dfae65fd1d085224b00a10755c509a4eaacfe/contracts/Safe.sol#L344-L347) that the signature is an `eth_sign` signature. The hexadecimal value `1c` equals the decimal number `28`, indicating that the signature is a typed data signature. The initial signature should look like this: `0x969308e2abeda61a0c9c41b3c615012f50dd7456ca76ea39a18e3b975abeb67f275b07810dd59fc928f3f9103e52557c1578c7c5c171ffc983afa5306466b1261f`: | Type | Description | Bytes | Value | | --- | --- | --- | --- | | Hex | Hex string characters | 1 | 0x | | Signature | Signature bytes | 64 | 969308e2abeda61a0c9c41b3c615012f50dd7456ca76ea39a18e3b975abeb67f275b07810dd59fc928f3f9103e52557c1578c7c5c171ffc983afa5306466b126 | | Signature Type | 1f hex is 31 in decimal | 1 | 1f | #### Smart contract signatures[](#smart-contract-signatures) When signing with a Safe account, the `SigningMethod` will take the value `SigningMethod.SAFE_SIGNATURE`. ##### 1/1 Safe account[](#11-safe-account) This applies to the `SAFE_1_1_ADDRESS` account, another owner of `SAFE_3_4_ADDRESS`. We need to connect the Protocol Kit to `SAFE_1_1_ADDRESS` and the `OWNER_3_ADDRESS` account (the only owner of `SAFE_1_1_ADDRESS`) and sign the transaction. ` _19 // Create a new transaction object _19 let transactionSafe1_1 = await protocolKit.createTransaction({ _19 transactions: [safeTransactionData] _19 }) _19 _19 // Connect OWNER_3_ADDRESS and SAFE_1_1_ADDRESS _19 protocolKit = await protocolKit.connect({ _19 provider: RPC_URL, _19 signer: OWNER_3_PRIVATE_KEY, _19 safeAddress: SAFE_1_1_ADDRESS _19 }) _19 _19 // Sign the transactionSafe1_1 with OWNER_3_ADDRESS _19 // After this, transactionSafe1_1 contains the signature from OWNER_3_ADDRESS _19 transactionSafe1_1 = await protocolKit.signTransaction( _19 transactionSafe1_1, _19 SigningMethod.SAFE_SIGNATURE, _19 SAFE_3_4_ADDRESS // Parent Safe address _19 ) ` When signing with a child Safe account, we need to specify the parent Safe address to generate the signature based on the version of the contract. At this point, the `transactionSafe1_1` object should look like this: ` _10 EthSafeTransaction { _10 signatures: Map(1) { _10 '0x22d491bde2303f2f43325b2108d26f1eaba1e32b' => EthSafeSignature { _10 signer: '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', _10 data: '0x5edb6ffe67dd935d93d07c634970944ba0b096f767b92018ad635e8b28effeea5a1e512f1ad6f886690e0e30a3fae2c8c61d3f83d24d43276acdb3254b92ea5b1f', _10 isContractSignature: false _10 } _10 }, _10 data: { ...} _10 } ` The `signatures.data` represents a specific signature. The `isContractSignature` flag set to `false` indicates that the signature isn't a smart contract signature but an ECDSA signature instead. To generate a Safe compatible signature, we use the `buildContractSignature` method, which takes an array of signatures and returns another signature that can be used with Safe accounts. After that, we add the signature from `SAFE_1_1_ADDRESS` to our initial transaction. ` _10 // Build the contract signature of SAFE_1_1_ADDRESS _10 const signatureSafe1_1 = await buildContractSignature( _10 Array.from(transactionSafe1_1.signatures.values()), _10 SAFE_1_1_ADDRESS _10 ) _10 _10 // Add the signatureSafe1_1 to safeTransaction _10 // After this, the safeTransaction contains the signature from OWNER_1_ADDRESS, OWNER_2_ADDRESS and SAFE_1_1_ADDRESS _10 safeTransaction.addSignature(signatureSafe1_1) ` The `signatureSafe1_1` object should look like this: ` _10 EthSafeSignature { _10 signer: '0x215033cdE0619D60B7352348F4598316Cc39bC6E', _10 data: '0x5edb6ffe67dd935d93d07c634970944ba0b096f767b92018ad635e8b28effeea5a1e512f1ad6f886690e0e30a3fae2c8c61d3f83d24d43276acdb3254b92ea5b1f', _10 isContractSignature: true _10 } ` The `isContractSignature` flag is now `true` because `signatureSafe1_1` is an EIP-1271 smart contract signature from the `SAFE_1_1_ADDRESS` account. The `signatureSafe1_1.data` signature should look like this: ` _10 0x000000000000000000000000215033cdE0619D60B7352348F4598316Cc39bC6E00000000000000000000000000000000000000000000000000000000000000410000000000000000000000000000000000000000000000000000000000000000415edb6ffe67dd935d93d07c634970944ba0b096f767b92018ad635e8b28effeea5a1e512f1ad6f886690e0e30a3fae2c8c61d3f83d24d43276acdb3254b92ea5b1f ` | Type | Description | Bytes | Value | | --- | --- | --- | --- | | Hex | Hex string characters | 1 | 0x | | Verifier | Padded address of the contract that implements the EIP-1271 interface to verify the signature. The Safe signer address | 32 | 000000000000000000000000215033cdE0619D60B7352348F4598316Cc39bC6E | | Data position | Start position of the signature data (offset relative to the beginning of the signature data). 41 hex is 65 in decimal | 32 | 0000000000000000000000000000000000000000000000000000000000000041 | | Signature Type | [00 for Safe accounts (opens in a new tab)](https://github.com/safe-global/safe-smart-account/blob/f03dfae65fd1d085224b00a10755c509a4eaacfe/contracts/Safe.sol#L322-L336) | 1 | 00 | | Signature Length | The length of the signature. 41 hex is 65 in decimal | 32 | 0000000000000000000000000000000000000000000000000000000000000041 | | Signature | Signature bytes that are verified by the signature verifier | 65 | 5edb6ffe67dd935d93d07c634970944ba0b096f767b92018ad635e8b28effeea5a1e512f1ad6f886690e0e30a3fae2c8c61d3f83d24d43276acdb3254b92ea5b1f | ##### 2/3 Safe account[](#23-safe-account) This applies to the `SAFE_2_3_ADDRESS` account, another owner of `SAFE_3_4_ADDRESS`. We need to connect the Protocol Kit to `SAFE_2_3_ADDRESS` and the `OWNER_4_ADDRESS` and `OWNER_5_ADDRESS` accounts (owners of `SAFE_2_3_ADDRESS`) and sign the transaction. ` _33 // Create a new transaction object _33 let transactionSafe2_3 = await protocolKit.createTransaction({ _33 transactions: [safeTransactionData] _33 }) _33 _33 // Connect OWNER_4_ADDRESS and the address of SAFE_2_3_ADDRESS _33 protocolKit = await protocolKit.connect({ _33 provider: RPC_URL, _33 signer: OWNER_4_ADDRESS, _33 safeAddress: SAFE_2_3_ADDRESS _33 }) _33 _33 // Sign the transactionSafe2_3 with OWNER_4_ADDRESS _33 // After this, the transactionSafe2_3 contains the signature from OWNER_4_ADDRESS _33 transactionSafe2_3 = await protocolKit.signTransaction( _33 transactionSafe2_3, _33 SigningMethod.SAFE_SIGNATURE, _33 SAFE_3_4_ADDRESS // Parent Safe address _33 ) _33 _33 // Connect OWNER_5_ADDRESS _33 protocolKit = await protocolKit.connect({ _33 provider: RPC_URL, _33 signer: OWNER_5_ADDRESS _33 }) _33 _33 // Sign the transactionSafe2_3 with OWNER_5_ADDRESS _33 // After this, the transactionSafe2_3 contains the signature from OWNER_5_ADDRESS _33 transactionSafe2_3 = await protocolKit.signTransaction( _33 transactionSafe2_3, _33 SigningMethod.SAFE_SIGNATURE, _33 SAFE_3_4_ADDRESS // Parent Safe address _33 ) ` At this point, the `transactionSafe2_3` object should look like this: ` _15 EthSafeTransaction { _15 signatures: Map(2) { _15 '0xe11ba2b4d45eaed5996cd0823791e0c93114882d' => EthSafeSignature { _15 signer: '0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d', _15 data: '0xd3e6565e5590641db447277243cf24711dce533cfcaaf3a64415dcb9fa309fbf2de1ae4709c6450752acc0d45e01b67b55379bdf4e3dc32b2d89ad0a60c231d61f', _15 isContractSignature: false _15 }, _15 '0xd03ea8624c8c5987235048901fb614fdca89b117' => EthSafeSignature { _15 signer: '0xd03ea8624C8C5987235048901fB614fDcA89b117', _15 data: '0x023d1746ed548e90f387a6b8ddba26e6b80a78d5bfbc36e5bfcbfd63e136f8071db6e91c037fa36bde72159138bbb74fc359b35eb515e276a7c0547d5eaa042520', _15 isContractSignature: false _15 } _15 }, _15 data: { ... } _15 } ` We now have two signatures from the owners, `OWNER_4_ADDRESS` and `OWNER_5_ADDRESS`. Following the same process, we can create the contract signature and examine the result. The `signatures.data` represents a specific signature. The `isContractSignature` flag set to `false` indicates that the signature isn't a smart contract signature but an ECDSA signature instead. To generate a Safe compatible signature, we use the `buildContractSignature` method, which takes an array of signatures and returns another signature that can be used with Safe accounts. After that, we add the signature from `safe1_1` to our initial transaction. ` _10 // Build the contract signature of SAFE_2_3_ADDRESS _10 const signatureSafe2_3 = await buildContractSignature( _10 Array.from(transactionSafe2_3.signatures.values()), _10 SAFE_2_3_ADDRESS _10 ) _10 _10 // Add the signatureSafe2_3 to safeTransaction _10 // After this, the safeTransaction contains the signature from OWNER_1_ADDRESS, OWNER_2_ADDRESS, SAFE_1_1_ADDRESS and SAFE_2_3_ADDRESS _10 safeTransaction.addSignature(signatureSafe2_3) ` The `signatureSafe2_3` object should look like this: ` _10 0x000000000000000000000000f75D61D6C27a7CC5788E633c1FC130f0F4a62D330000000000000000000000000000000000000000000000000000000000000041000000000000000000000000000000000000000000000000000000000000000082023d1746ed548e90f387a6b8ddba26e6b80a78d5bfbc36e5bfcbfd63e136f8071db6e91c037fa36bde72159138bbb74fc359b35eb515e276a7c0547d5eaa042520d3e6565e5590641db447277243cf24711dce533cfcaaf3a64415dcb9fa309fbf2de1ae4709c6450752acc0d45e01b67b55379bdf4e3dc32b2d89ad0a60c231d61f ` | Type | Description | Bytes | Value | | --- | --- | --- | --- | | Hex | Hex string characters | 1 | 0x | | Verifier | Padded address of the contract that implements the EIP-1271 interface to verify the signature. The Safe signer address | 32 | 000000000000000000000000f75D61D6C27a7CC5788E633c1FC130f0F4a62D33 | | Data position | Start position of the signature data (offset relative to the beginning of the signature data). 41 hex is 65 in decimal | 32 | 0000000000000000000000000000000000000000000000000000000000000041 | | Signature Type | [00 for Safe accounts (opens in a new tab)](https://github.com/safe-global/safe-smart-account/blob/f03dfae65fd1d085224b00a10755c509a4eaacfe/contracts/Safe.sol#L322-L336) | 1 | 00 | | Signature Length | The length of the signature. 82 hex is 130 in decimal | 32 | 0000000000000000000000000000000000000000000000000000000000000082 | | Signature | Signature bytes that are verified by the signature verifier (130 bytes are represented by 260 characters in an hex string) | 130 | 023d1746ed548e90f387a6b8ddba26e6b80a78d5bfbc36e5bfcbfd63e136f8071db6e91c037fa36bde72159138bbb74fc359b35eb515e276a7c0547d5eaa042520d3e6565e5590641db447277243cf24711dce533cfcaaf3a64415dcb9fa309fbf2de1ae4709c6450752acc0d45e01b67b55379bdf4e3dc32b2d89ad0a60c231d61f | The table looks very similar to the previous one, but there are two main differences: * The **Signature Length** value has doubled because `safe2_3` needs two signatures. * The **Signature** value is a concatenation of the two regular signatures. After following all the steps above, the `safeTransaction` now contains all the signatures from the owners of the Safe. The `safeTransaction` object should look like this: ` _36 EthSafeTransaction { _36 signatures: Map(4) { _36 '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1' => EthSafeSignature { _36 signer: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1', _36 data: '0x969308e2abeda61a0c9c41b3c615012f50dd7456ca76ea39a18e3b975abeb67f275b07810dd59fc928f3f9103e52557c1578c7c5c171ffc983afa5306466b1261f', _36 isContractSignature: false _36 }, _36 '0xffcf8fdee72ac11b5c542428b35eef5769c409f0' => EthSafeSignature { _36 signer: '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', _36 data: '0x4d63c79cf9d743782bc31ad58c1a316020b39839ab164caee7ecac9829f685cc44ec0d066a5dfe646b2ffeeb37575df131daf9c96ced41b8c7c4aea8dc5461801c', _36 isContractSignature: false _36 }, _36 '0x215033cde0619d60b7352348f4598316cc39bc6e' => EthSafeSignature { _36 signer: '0x215033cdE0619D60B7352348F4598316Cc39bC6E', _36 data: '0x5edb6ffe67dd935d93d07c634970944ba0b096f767b92018ad635e8b28effeea5a1e512f1ad6f886690e0e30a3fae2c8c61d3f83d24d43276acdb3254b92ea5b1f', _36 isContractSignature: true _36 }, _36 '0xf75d61d6c27a7cc5788e633c1fc130f0f4a62d33' => EthSafeSignature { _36 signer: '0xf75D61D6C27a7CC5788E633c1FC130f0F4a62D33', _36 data: '0x023d1746ed548e90f387a6b8ddba26e6b80a78d5bfbc36e5bfcbfd63e136f8071db6e91c037fa36bde72159138bbb74fc359b35eb515e276a7c0547d5eaa042520d3e6565e5590641db447277243cf24711dce533cfcaaf3a64415dcb9fa309fbf2de1ae4709c6450752acc0d45e01b67b55379bdf4e3dc32b2d89ad0a60c231d61f', _36 isContractSignature: true _36 } _36 }, _36 data: { _36 to: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1', _36 value: '100000000000000000', _36 data: '0x', _36 operation: 0, _36 baseGas: '0', _36 gasPrice: '0', _36 gasToken: '0x0000000000000000000000000000000000000000', _36 refundReceiver: '0x0000000000000000000000000000000000000000', _36 nonce: 0, _36 safeTxGas: '0' _36 } _36 } ` ### Propose the transaction[](#propose-the-transaction) To store the transactions and signatures off-chain, we need to call the Safe Transaction Service API - a centralized and open-source service that anyone can deploy and run. The Safe Transaction Service is used by [Safe{Wallet} (opens in a new tab)](https://app.safe.global) to store transactions and signatures by default. To store a new transaction, we need to call the `proposeTransaction` from the API Kit, passing the Safe address, an object with the transaction, and a signature from one owner. ` _18 // Get the signature from OWNER_1_ADDRESS _18 const signatureOwner1 = safeTransaction.getSignature(OWNER_1_ADDRESS) as EthSafeSignature _18 _18 // Get the transaction hash of the safeTransaction _18 const safeTransactionHash = await protocolKit.getTransactionHash(safeTransaction) _18 _18 // Instantiate the API Kit _18 // Use the chainId where you have the Safe account deployed _18 const apiKit = new SafeApiKit({ chainId }) _18 _18 // Propose the transaction _18 await apiKit.proposeTransaction({ _18 safeAddress: SAFE_3_4_ADDRESS, _18 safeTransactionData: safeTransaction.data, _18 safeTxHash: safeTransactionHash, _18 senderAddress: signerAddress, _18 senderSignature: buildSignatureBytes([signatureOwner1]) _18 }) ` The transaction is now publicly available in the Safe Transaction Service with the signature of the owner who submitted it. ### Confirm the transaction[](#confirm-the-transaction) To add the signatures from the remaining owners, we need to call the `confirmTransaction`, passing the `safeMessageHash` and a signature from the owner. Once a transaction is proposed, it becomes available on [Safe{Wallet} (opens in a new tab)](https://app.safe.global) . However, to execute the transaction, all the confirmations from the owners are needed. ` _19 const signatureOwner2 = safeTransaction.getSignature(OWNER_2_ADDRESS) as EthSafeSignature _19 _19 // Confirm the transaction from OWNER_2_ADDRESS _19 await apiKit.confirmTransaction( _19 safeTransactionHash, _19 buildSignatureBytes([signatureOwner2]) _19 ) _19 _19 // Confirm the transaction with the owner SAFE_1_1_ADDRESS _19 await apiKit.confirmTransaction( _19 safeTransactionHash, _19 buildSignatureBytes([signatureSafe1_1]) _19 ) _19 _19 // Add signature from the owner SAFE_2_3_ADDRESS _19 await apiKit.confirmTransaction( _19 safeTransactionHash, _19 buildSignatureBytes([signerSafeSig2_3]) _19 ) ` At this point, the transaction stored in the Safe Transaction Service contains all the required signatures from the owners of the Safe. The `getTransaction` method returns the transaction with the `confirmations` property to check all the added signatures. ` _10 // Get the transactions _10 const signedTransaction = await apiKit.getTransaction(safeTransactionHash) _10 _10 // Get the confirmations _10 const confirmations = signedTransaction.confirmations ` [Safe{Wallet} (opens in a new tab)](https://app.safe.global) exposes to its users the list of pending transactions. ` _10 https://app.safe.global/transactions/queue?safe=: ` ### Execute the transaction[](#execute-the-transaction) Connect the Safe and an a signer to the Protocol Kit. Ensure enough funds are available in the owner's account to execute the transaction and cover the gas costs. Once the Protocol Kit is initialized, the `executeTransaction` method receives and executes the transaction with the required signatures. ` _10 protocolKit = await protocolKit.connect({ _10 provider: RPC_URL, _10 signer: OWNER_1_PRIVATE_KEY, _10 safeAddress: SAFE_3_4_ADDRESS _10 }) _10 _10 // Execute the Safe transaction _10 const transactionResponse = await protocolKit.executeTransaction(safeTransaction) ` At this point, the Safe transaction should be executed on-chain and listed on [Safe{Wallet} (opens in a new tab)](https://app.safe.global) . ` _10 https://app.safe.global/transactions/history?safe=: ` The `safeTransaction.encodedSignature` method returns the signatures concatenated and sorted by the address of the signers. It should look like this: ` _10 0x000000000000000000000000215033cdE0619D60B7352348F4598316Cc39bC6E000000000000000000000000000000000000000000000000000000000000010400969308e2abeda61a0c9c41b3c615012f50dd7456ca76ea39a18e3b975abeb67f275b07810dd59fc928f3f9103e52557c1578c7c5c171ffc983afa5306466b1261f000000000000000000000000f75D61D6C27a7CC5788E633c1FC130f0F4a62D330000000000000000000000000000000000000000000000000000000000000165004d63c79cf9d743782bc31ad58c1a316020b39839ab164caee7ecac9829f685cc44ec0d066a5dfe646b2ffeeb37575df131daf9c96ced41b8c7c4aea8dc5461801c00000000000000000000000000000000000000000000000000000000000000415edb6ffe67dd935d93d07c634970944ba0b096f767b92018ad635e8b28effeea5a1e512f1ad6f886690e0e30a3fae2c8c61d3f83d24d43276acdb3254b92ea5b1f0000000000000000000000000000000000000000000000000000000000000082023d1746ed548e90f387a6b8ddba26e6b80a78d5bfbc36e5bfcbfd63e136f8071db6e91c037fa36bde72159138bbb74fc359b35eb515e276a7c0547d5eaa042520d3e6565e5590641db447277243cf24711dce533cfcaaf3a64415dcb9fa309fbf2de1ae4709c6450752acc0d45e01b67b55379bdf4e3dc32b2d89ad0a60c231d61f ` | Type | Description | Bytes | Acc byte | Value | | --- | --- | --- | --- | --- | | Hex | Hex string characters | 1 | \- | 0x | | 1/1 Safe signer | Safe Address | 32 | 32 | 000000000000000000000000215033cdE0619D60B7352348F4598316Cc39bC6E | | Data position for 1/1 Safe | 104 hex = Signature data for 1/1 Safe start at byte 260 | 32 | 64 | 0000000000000000000000000000000000000000000000000000000000000104 | | Signature Type | Smart contract signature | 1 | 65 | 00 | | Owner signature | `OWNER_1_ADDRESS` signature | 65 | 130 | 969308e2abeda61a0c9c41b3c615012f50dd7456ca76ea39a18e3b975abeb67f275b07810dd59fc928f3f9103e52557c1578c7c5c171ffc983afa5306466b1261f | | 2/3 Safe signer | Safe Address | 32 | 162 | 000000000000000000000000f75D61D6C27a7CC5788E633c1FC130f0F4a62D33 | | Data position for 2/3 Verifier | 165 hex = Signature data for 2/3 Safe start at byte 357 | 32 | 194 | 0000000000000000000000000000000000000000000000000000000000000165 | | Signature | Type Smart contract signature | 1 | 195 | 00 | | Owner signature | `OWNER_2_ADDRESS` signature | 65 | 260 | 4d63c79cf9d743782bc31ad58c1a316020b39839ab164caee7ecac9829f685cc44ec0d066a5dfe646b2ffeeb37575df131daf9c96ced41b8c7c4aea8dc5461801c | | 1/1 Safe Signature Length | Start of the 1/1 Safe Signature. 41 hex = 65 bytes | 32 | 292 | 0000000000000000000000000000000000000000000000000000000000000041 | | Signature | `OWNER_3_ADDRESS` signature | 65 | 357 | 5edb6ffe67dd935d93d07c634970944ba0b096f767b92018ad635e8b28effeea5a1e512f1ad6f886690e0e30a3fae2c8c61d3f83d24d43276acdb3254b92ea5b1f | | 2/3 Safe Signature length | Start of the 2/3 Safe Signature. 82 hex = 130 bytes | 32 | 389 | 0000000000000000000000000000000000000000000000000000000000000082 | | Signature | `OWNER_4_ADDRESS` and `OWNER_5_ADDRESS` concatenated signatures | 130 | 519 | 023d1746ed548e90f387a6b8ddba26e6b80a78d5bfbc36e5bfcbfd63e136f8071db6e91c037fa36bde72159138bbb74fc359b35eb515e276a7c0547d5eaa042520d3e6565e5590641db447277243cf24711dce533cfcaaf3a64415dcb9fa309fbf2de1ae4709c6450752acc0d45e01b67b55379bdf4e3dc32b2d89ad0a60c231d61f | [Signatures](/sdk/protocol-kit/guides/signatures "Signatures") [Messages](/sdk/protocol-kit/guides/signatures/messages "Messages") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v1 – Safe Docs SDK [API Kit](/sdk/api-kit) Guides Migrate to v1 Migrate to v1 ============= This guide references the major changes between `safe-service-client` and `api-kit` v1 to help those migrating an existing application. **Note:** Follow this guide before migrating to `api-kit` v2. After completing this guide, you can remove `@safe-global/safe-service-client` from your `package.json`. Adding the new dependency[](#adding-the-new-dependency) -------------------------------------------------------- To add the API Kit to your project, run the following: ` _10 yarn add @safe-global/api-kit@1.3.1 ` Change your initialization like this: ` _15 // old _15 import SafeServiceClient from '@safe-global/safe-service-client' _15 _15 const safeService = new SafeServiceClient({ _15 txServiceUrl: 'https://your-transaction-service-url', _15 ethAdapter _15 }) _15 _15 // new _15 import SafeApiKit from '@safe-global/api-kit' _15 _15 const apiKit = new SafeApiKit({ _15 txServiceUrl: 'https://your-transaction-service-url', _15 ethAdapter _15 }) ` `getSafeDelegates()`[](#getsafedelegates) ------------------------------------------ The `getSafeDelegates` was updated to accept more filtering parameters. Now, it accepts an object with multiple properties instead of only the `safeAddress` parameter. ` _10 const delegateConfig: GetSafeDelegateProps = { _10 safeAddress, // Optional _10 delegateAddress, // Optional _10 delegatorAddress, // Optional _10 label, // Optional _10 limit, // Optional _10 offset // Optional _10 } _10 const delegates: SafeDelegateListResponse = await apiKit.getSafeDelegates(delegateConfig) ` `addSafeDelegate()`[](#addsafedelegate) ---------------------------------------- Parameter object properties were updated as follows: ` _18 // old _18 const delegateConfig: SafeDelegateConfig = { _18 safe, _18 delegate, _18 label, _18 signer _18 } _18 await safeService.addSafeDelegate(delegateConfig) _18 _18 // new _18 const delegateConfig: AddSafeDelegateProps = { _18 safeAddress, // Optional _18 delegateAddress, _18 delegatorAddress, _18 label, _18 signer _18 } _18 await apiKit.addSafeDelegate(delegateConfig) ` `removeAllSafeDelegates()`[](#removeallsafedelegates) ------------------------------------------------------ The method was deprecated and removed. `removeSafeDelegate()`[](#removesafedelegate) ---------------------------------------------- Parameter object properties were updated as follows: ` _15 // old _15 const delegateConfig: SafeDelegateDeleteConfig = { _15 safe, _15 delegate, _15 signer _15 } _15 await safeService.removeSafeDelegate(delegateConfig) _15 _15 // new _15 const delegateConfig: DeleteSafeDelegateProps = { _15 delegateAddress, _15 delegatorAddress, _15 signer _15 } _15 await apiKit.removeSafeDelegate(delegateConfig) ` `getBalances()`[](#getbalances) -------------------------------- The method was deprecated and removed. `getUSDBalances()`[](#getusdbalances) -------------------------------------- The method was deprecated and removed. `getCollectibles()`[](#getcollectibles) ---------------------------------------- The method was deprecated and removed. [Propose and Confirm Transactions](/sdk/api-kit/guides/propose-and-confirm-transactions "Propose and Confirm Transactions") [Migrate to v2](/sdk/api-kit/guides/migrate-to-v2 "Migrate to v2") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # API Kit Reference – Safe Docs API Kit Reference Overview API Kit Reference ================= The [API Kit](/sdk/api-kit) facilitates the interaction with the [Safe Transaction Service API](/core-api/transaction-service-overview) . Install dependencies[](#install-dependencies) ---------------------------------------------- To add the API Kit to your project, run: pnpm npm yarn ` _10 pnpm add @safe-global/api-kit ` ← Go Back[constructor](/reference-sdk-api-kit/constructor "constructor") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Propose and confirm transactions – Safe Docs SDK [API Kit](/sdk/api-kit) Guides Propose and Confirm Transactions Propose and confirm transactions ================================ In this guide you will learn how to propose transactions to the service and collect the signatures from the owners so they become executable. For more detailed information, see the [API Kit Reference](/reference-sdk-api-kit/overview) . Prerequisites[](#prerequisites) -------------------------------- 1. [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) 2. A Safe with several signers Steps[](#steps) ---------------- ### Install dependencies[](#install-dependencies) First, you need to install some dependencies. ` _10 yarn add @safe-global/api-kit \ _10 @safe-global/protocol-kit \ _10 @safe-global/types-kit ` ### Imports[](#imports) Here are all the necessary imports for this guide. ` _10 import SafeApiKit from '@safe-global/api-kit' _10 import Safe from '@safe-global/protocol-kit' _10 import { _10 MetaTransactionData, _10 OperationType _10 } from '@safe-global/types-kit' ` ### Setup[](#setup) We will use a Safe account setup with two or more signers, and threshold two, so at least multiple signatures will need to be collected when executing a transaction. ` _10 // https://chainlist.org/?search=sepolia&testnets=true _10 const RPC_URL = 'https://eth-sepolia.public.blastapi.io' _10 _10 const SAFE_ADDRESS = // ... _10 _10 const OWNER_1_ADDRESS = // ... _10 const OWNER_1_PRIVATE_KEY = // ... _10 _10 const OWNER_2_PRIVATE_KEY = // ... ` ### Initialize the API Kit[](#initialize-the-api-kit) Firstly, you need to create an instance of the API Kit. In chains where the [Safe Transaction Service](/core-api/transaction-service-overview) is supported, it's enough to specify the `chainId` property. ` _10 const apiKit = new SafeApiKit({ _10 chainId: 1n _10 }) ` Alternatively, you can use a custom service using the optional `txServiceUrl` property. ` _10 const apiKit = new SafeApiKit({ _10 chainId: 1n, // set the correct chainId _10 txServiceUrl: 'https://url-to-your-custom-service' _10 }) ` ### Initialize the Protocol Kit[](#initialize-the-protocol-kit) To handle transactions and signatures, you need to create an instance of the Protocol Kit with the `provider`, `signer` and `safeAddress`. ` _10 const protocolKitOwner1 = await Safe.init({ _10 provider: RPC_URL, _10 signer: OWNER_1_PRIVATE_KEY, _10 safeAddress: SAFE_ADDRESS _10 }) ` ### Propose a transaction to the service[](#propose-a-transaction-to-the-service) Before a transaction can be executed, any of the Safe signers needs to initiate the process by creating a proposal of a transaction. This transaction is sent to the service to make it accessible by the other owners so they can give their approval and sign the transaction as well. For a full list and description of the properties see [`proposeTransaction`](/reference-sdk-api-kit/proposetransaction) in the API Kit reference. ` _23 // Create transaction _23 const safeTransactionData: MetaTransactionData = { _23 to: '0x', _23 value: '1', // 1 wei _23 data: '0x', _23 operation: OperationType.Call _23 } _23 _23 const safeTransaction = await protocolKitOwner1.createTransaction({ _23 transactions: [safeTransactionData] _23 }) _23 _23 const safeTxHash = await protocolKitOwner1.getTransactionHash(safeTransaction) _23 const signature = await protocolKitOwner1.signHash(safeTxHash) _23 _23 // Propose transaction to the service _23 await apiKit.proposeTransaction({ _23 safeAddress: SAFE_ADDRESS, _23 safeTransactionData: safeTransaction.data, _23 safeTxHash, _23 senderAddress: OWNER_1_ADDRESS, _23 senderSignature: signature.data _23 }) ` ### Retrieve the pending transactions[](#retrieve-the-pending-transactions) Different methods in the API Kit are available to retrieve pending transactions depending on the situation. To retrieve a transaction given the Safe transaction hash use the method that's not commented. ` _10 const transaction = await service.getTransaction(safeTxHash) _10 // const transactions = await service.getPendingTransactions() _10 // const transactions = await service.getIncomingTransactions() _10 // const transactions = await service.getMultisigTransactions() _10 // const transactions = await service.getModuleTransactions() _10 // const transactions = await service.getAllTransactions() ` ### Confirm the transaction[](#confirm-the-transaction) In this step you need to sign the transaction with the Protocol Kit and submit the signature to the Safe Transaction Service using the [`confirmTransaction`](/reference-sdk-api-kit/confirmtransaction) method. ` _14 const protocolKitOwner2 = await Safe.init({ _14 provider: RPC_URL, _14 signer: OWNER_2_PRIVATE_KEY, _14 safeAddress: SAFE_ADDRESS _14 }) _14 _14 const safeTxHash = transaction.transactionHash _14 const signature = await protocolKitOwner2.signHash(safeTxHash) _14 _14 // Confirm the Safe transaction _14 const signatureResponse = await apiKit.confirmTransaction( _14 safeTxHash, _14 signature.data _14 ) ` The Safe transaction is now ready to be executed. This can be done using the [Safe{Wallet} web (opens in a new tab)](https://app.safe.global) interface, the [Protocol Kit](/reference-sdk-protocol-kit/transactions/executetransaction) , the [Safe CLI](/advanced/cli-reference/tx-service-commands#execute-pending-transaction) or any other tool that's available. [API Kit](/sdk/api-kit "API Kit") [Migrate to v1](/sdk/api-kit/guides/migrate-to-v1 "Migrate to v1") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v2 – Safe Docs SDK [API Kit](/sdk/api-kit) Guides Migrate to v2 Migrate to v2 ============= This guide references the major changes between v1 and v2 to help those migrating an existing app. API Kit constructor[](#api-kit-constructor) -------------------------------------------- It won't be necessary to specify a `txServiceUrl` in environments where Safe has a Transaction Service running. Providing the chain ID will be enough. If you want to use your custom service or the kit in a chain not supported by a Safe Transaction Service, you can add the `txServiceUrl` parameter. ` _21 // old: _21 import SafeApiKit from '@safe-global/api-kit' _21 _21 const apiKit = new SafeApiKit({ _21 txServiceUrl: 'https://your-transaction-service-url', _21 ethAdapter _21 }) _21 _21 // new: _21 import SafeApiKit from '@safe-global/api-kit' _21 _21 const chainId: bigint = 1n _21 const apiKit = new SafeApiKit({ _21 chainId _21 }) _21 _21 // or set a custom Transaction Service _21 const apiKit = new SafeApiKit({ _21 chainId, _21 txServiceUrl: 'https://your-transaction-service-url' _21 }) ` Use the route you prefer[](#use-the-route-you-prefer) ------------------------------------------------------ API Kit v1 forced any custom service to be hosted under the `/api` route of the URL specified in `txServiceUrl`. This isn't the case anymore; you can specify any preferred route or subdomain. Note that if you use a custom service running under `/api`, you will now need to migrate as follows: ` _13 // old: _13 const txServiceUrl = 'https://your-transaction-service-domain/' _13 const apiKit = new SafeApiKit({ _13 txServiceUrl, _13 ethAdapter _13 }) _13 // new: _13 const chainId: bigint = 1n _13 const txServiceUrl = 'https://your-transaction-service-domain/api' _13 const apiKit = new SafeApiKit({ _13 chainId, _13 txServiceUrl _13 }) ` MasterCopy to Singleton[](#mastercopy-to-singleton) ---------------------------------------------------- To avoid confusion between terms used as synonyms, we aligned all our code to use the word `singleton`. * Rename type `MasterCopyResponse` to `SafeSingletonResponse` * Rename method `getServiceMasterCopiesInfo()` to `getServiceSingletonsInfo()` [Migrate to v1](/sdk/api-kit/guides/migrate-to-v1 "Migrate to v1") [Migrate to v3](/sdk/api-kit/guides/migrate-to-v3 "Migrate to v3") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v3 – Safe Docs SDK [API Kit](/sdk/api-kit) Guides Migrate to v3 Migrate to v3 ============= This guide references the major changes between v2 and v3 to help those migrating an existing app. Changed method signature[](#changed-method-signature) ------------------------------------------------------ We extracted `safeAddress` and renamed `xxxProps` types to `xxxOptions` types in the following method * `getSafeOperationsByAddress(props: GetSafeOperationListProps)` is now `getSafeOperationsBySafeAddress(safeAddress, options: GetSafeOperationListOptions)` Renamed types[](#renamed-types) -------------------------------- We renamed the `xxxProps` types to `xxxOptions` in the following methods: * `addMessage(safeAddress: string, addMessageProps: AddMessageProps)` is now `addMessage(safeAddress: string, addMessageOptions: AddMessageOptions)` * `getMessages(safeAddress: string, props: GetSafeMessageListProps)` is now `getMessages(safeAddress: string, options: GetSafeMessageListOptions)` [Migrate to v2](/sdk/api-kit/guides/migrate-to-v2 "Migrate to v2") [Reference](/sdk/api-kit/guides/migrate-to-v3# "Reference") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Relay Kit – Safe Docs SDK Relay Kit Relay Kit ========= The Relay Kit enables transaction relaying with Safe, and allows users to pay for the transaction fees from their Safe account using the blockchain native token, ERC-20 tokens, or to get their transactions sponsored. [#### @safe-global/relay-kit](https://www.npmjs.com/package/@safe-global/relay-kit) The following guides show how to use the Relay Kit and integrate it into your project by using one of the packs: * [Integrate ERC-4337 Safe accounts](/sdk/relay-kit/guides/4337-safe-sdk) * [Integrate Gelato Relay](/sdk/relay-kit/guides/gelato-relay) Resources[](#resources) ------------------------ * [Relay Kit on GitHub (opens in a new tab)](https://github.com/safe-global/safe-core-sdk/tree/main/packages/relay-kit) [Reference](/sdk/relay-kit# "Reference") [ERC-4337 Safe SDK](/sdk/relay-kit/guides/4337-safe-sdk "ERC-4337 Safe SDK") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safe accounts with the Safe4337Module – Safe Docs SDK [Relay Kit](/sdk/relay-kit) Guides ERC-4337 Safe SDK Safe accounts with the Safe4337Module ===================================== In this guide, you will learn how to create and execute multiple Safe transactions grouped in a batch from a Safe account that is not yet deployed and where the executor may or may not have funds to pay for the transaction fees. This can be achieved by supporting the [ERC-4337](/home/glossary#erc-4337) execution flow, which is supported by the [Safe4337Module](/advanced/erc-4337/guides/safe-sdk) and exposed via the Relay Kit from the Safe{Core} SDK. Read the [Safe4337Module documentation](/advanced/erc-4337/guides/safe-sdk) to understand its benefits and flows better. [Pimlico (opens in a new tab)](https://pimlico.io) is used in this guide as the service provider, but any other provider compatible with the ERC-4337 can be used. ℹ️ We have added support for then Entrypoint v0.7 contract but we are not making it the default yet. If you are using Entrypoint v0.7, you need to set the `safeModuleVersion` to `0.3.0` when calling the `Safe4337Pack.init` method. This version of the Safe 4337 Module is the one compatible with the Entrypoint v0.7. Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) . * A [Pimlico account (opens in a new tab)](https://dashboard.pimlico.io) and an API key. Install dependencies[](#install-dependencies) ---------------------------------------------- ` _10 yarn add @safe-global/relay-kit ` Steps[](#steps) ---------------- ### Imports[](#imports) Here are all the necessary imports for the script we implement in this guide. ` _10 import { Safe4337Pack } from '@safe-global/relay-kit' ` ### Create a signer[](#create-a-signer) Firstly, we need to get a signer, which will be the owner of a Safe account after it's deployed. In this example, we use a private key, but any way to get an EIP-1193 compatible signer can be used. ` _10 const SIGNER_ADDRESS = // ... _10 const SIGNER_PRIVATE_KEY = // ... _10 const RPC_URL = 'https://rpc.ankr.com/eth_sepolia' ` ### Initialize the `Safe4337Pack`[](#initialize-the-safe4337pack) The `Safe4337Pack` class is exported from the Relay Kit and implements the ERC-4337 to create, sign, and submit Safe user operations. To instantiate this class, the static `init()` method allows connecting existing Safe accounts (as long as they have the `Safe4337Module` enabled) or setting a custom configuration to deploy a new Safe account at the time where the first Safe transaction is submitted. New Safe accountExisting Safe account When deploying a new Safe account, we need to pass the configuration of the Safe in the `options` property. In this case, we are configuring a Safe account that will have our signer as the only owner. Optionally, you can [track your ERC-4337 Safe transactions on-chain](/sdk/onchain-tracking) by using the `onchainAnalytics` property. By default `Safe4337Pack` is using version `0.2.0` of the Safe 4337 Module that is only compatible with Entrypoint v0.6. If you need to use v0.7 then add the `safeModulesVersion` property to the options object with the '0.3.0' value. `` _12 const safe4337Pack = await Safe4337Pack.init({ _12 provider: RPC_URL, _12 signer: SIGNER_PRIVATE_KEY, _12 bundlerUrl: `https://api.pimlico.io/v2/11155111/rpc?add_balance_override&apikey=${PIMLICO_API_KEY}`, _12 // safeModulesVersion: '0.3.0', // Defaults to 0.2.0. If you are using the v0.7 of the EntryPoint set the value to '0.3.0' _12 options: { _12 owners: [SIGNER_ADDRESS], _12 threshold: 1 _12 }, _12 onchainAnalytics // Optional _12 // ... _12 }) `` By default, the transaction fees will be paid in the native token and extracted from the Safe account, so there must be enough funds in the Safe address. You can also use a paymaster to handle the fees. If you choose to use a paymaster, there are two other ways to initialize the `Safe4337Pack`. Using an ERC-20 PaymasterUsing a verifying Paymaster (Sponsored) A paymaster will execute the transactions and get reimbursed from the Safe account, which must have enough funds in the Safe address in advance. Payment of transaction fees is made using an ERC-20 token specified with the `paymasterTokenAddress` property. If an ERC-20 token is used, the Safe must approve that token to the paymaster. If no balance is approved, it can be specified using the `amountToApprove` property. `` _10 const safe4337Pack = await Safe4337Pack.init({ _10 // ... _10 paymasterOptions: { _10 paymasterUrl: `https://api.pimlico.io/v2/11155111/rpc?apikey=${PIMLICO_API_KEY}`, _10 paymasterAddress: '0x...', _10 paymasterTokenAddress: '0x...', _10 amountToApprove // Optional _10 } _10 }) `` ### Create a user operation[](#create-a-user-operation) To create a Safe user operation, use the `createTransaction()` method, which takes the array of transactions to execute and returns a `SafeOperation` object. ` _10 // Define the transactions to execute _10 const transaction1 = { to, data, value } _10 const transaction2 = { to, data, value } _10 _10 // Build the transaction array _10 const transactions = [transaction1, transaction2] _10 _10 // Create the SafeOperation with all the transactions _10 const safeOperation = await safe4337Pack.createTransaction({ transactions }) ` The `safeOperation` object has the `data` and `signatures` properties, which contain all the information about the transaction batch and the signatures of the Safe owners, respectively. ### Sign the user operation[](#sign-the-user-operation) Before sending the user operation to the bundler, it's required to sign the `safeOperation` object with the connected signer. The `signSafeOperation()` method, which receives a `SafeOperation` object, generates a signature that will be checked when the `Safe4337Module` validates the user operation. ` _10 const signedSafeOperation = await safe4337Pack.signSafeOperation(identifiedSafeOperation) ` ### Submit the user operation[](#submit-the-user-operation) Once the `safeOperation` object is signed, we can call the `executeTransaction()` method to submit the user operation to the bundler. ` _10 const userOperationHash = await safe4337Pack.executeTransaction({ _10 executable: signedSafeOperation _10 }) ` This method returns the hash of the user operation. With it, we can monitor the transaction status using a block explorer or the bundler's API. ### Check the transaction status[](#check-the-transaction-status) To check the transaction status, we can use the `getTransactionReceipt()` method, which returns the transaction receipt after it's executed. ` _10 let userOperationReceipt = null _10 _10 while (!userOperationReceipt) { _10 // Wait 2 seconds before checking the status again _10 await new Promise((resolve) => setTimeout(resolve, 2000)) _10 userOperationReceipt = await safe4337Pack.getUserOperationReceipt( _10 userOperationHash _10 ) _10 } ` In addition, we can use the `getUserOperationByHash()` method with the returned hash to retrieve the user operation object we sent to the bundler. ` _10 const userOperationPayload = await safe4337Pack.getUserOperationByHash( _10 userOperationHash _10 ) ` Recap and further reading[](#recap-and-further-reading) -------------------------------------------------------- After following this guide, we are able to deploy new Safe accounts and create, sign, and execute Safe transactions in a batch without the executor needing to have funds to pay for the transaction fees. Learn more about the ERC-4337 standard and the `Safe4337Module` contract following these links: * [ERC-4337 website (opens in a new tab)](https://www.erc4337.io) * [EIP-4337 on Ethereum EIPs (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-4337) * [Safe4337Module on GitHub (opens in a new tab)](https://github.com/safe-global/safe-modules/tree/main/modules/4337) * \[Safe On-chain Identifiers on GitHub\]([https://github.com/5afe/safe-onchain-identifiers (opens in a new tab)](https://github.com/5afe/safe-onchain-identifiers) showcases where and how to add the identifier at the end of your Safe transactions data if you are not using the Relay Kit. Check also the [specific code (opens in a new tab)](https://github.com/5afe/safe-onchain-identifiers/blob/main/test/OnchainIdentifier.ts#L197-L217) where the identifier is concatenated to the `callData`. [Relay Kit](/sdk/relay-kit "Relay Kit") [Gelato Relay](/sdk/relay-kit/guides/gelato-relay "Gelato Relay") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Integration with Gelato – Safe Docs SDK [Relay Kit](/sdk/relay-kit) Guides Gelato Relay Integration with Gelato ======================= The [Gelato relay (opens in a new tab)](https://docs.gelato.network/developer-services/relay) allows developers to execute gasless transactions. Prerequisites[](#prerequisites) -------------------------------- 1. [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm#using-a-node-version-manager-to-install-nodejs-and-npm) . 2. Have a Safe account configured with threshold equal to 1, where only one signature is needed to execute transactions. 3. To use Gelato 1Balance an [API key (opens in a new tab)](https://docs.gelato.network/developer-services/relay/payment-and-fees/1balance) is required. Install dependencies[](#install-dependencies) ---------------------------------------------- ` _10 yarn add ethers @safe-global/relay-kit @safe-global/protocol-kit @safe-global/types-kit ` Relay Kit options[](#relay-kit-options) ---------------------------------------- Currently, the Relay Kit is only compatible with the [Gelato relay (opens in a new tab)](https://docs.gelato.network/developer-services/relay) . The Gelato relay can be used in two ways: 1. [Gelato 1Balance (opens in a new tab)](https://docs.gelato.network/developer-services/relay/payment-and-fees/1balance) 2. [Gelato SyncFee (opens in a new tab)](https://docs.gelato.network/developer-services/relay/quick-start/callwithsyncfee) Gelato 1Balance[](#gelato-1balance) ------------------------------------ [Gelato 1Balance (opens in a new tab)](https://docs.gelato.network/developer-services/relay/payment-and-fees/1balance) allows you to execute transactions using a prepaid deposit. This can be used to sponsor transactions to other Safes or even to use a deposit on Polygon to pay the fees for a wallet on another chain. For the 1Balance quickstart tutorial, you will use the Gelato relayer to pay for the gas fees on BNB Chain using the Polygon USDC you have deposited into your Gelato 1Balance account. ### Setup[](#setup) 1. Start with a [1/1 Safe on BNB Chain (opens in a new tab)](https://app.safe.global/transactions/history?safe=bnb:0x6651FD6Abe0843f7B6CB9047b89655cc7Aa78221) . 2. [Deposit Polygon USDC into Gelato 1Balance (opens in a new tab)](https://docs.gelato.network/developer-services/relay/payment-and-fees/.1balance#how-can-i-use-1balance) ([transaction 0xa5f38 (opens in a new tab)](https://polygonscan.com/tx/0xa5f388c2d6e0d1bb32e940fccddf8eab182ad191644936665a54bf4bb1bac555) ). 3. The Safe owner [0x6Dbd26Bca846BDa60A90890cfeF8fB47E7d0f22c (opens in a new tab)](https://bscscan.com/address/0x6Dbd26Bca846BDa60A90890cfeF8fB47E7d0f22c) signs a transaction to send 0.0005 BNB and submits it to Gelato relay. 4. [Track the relay request (opens in a new tab)](https://docs.gelato.network/developer-services/relay/quick-start/tracking-your-relay-request) of [Gelato Task ID 0x1bf7 (opens in a new tab)](https://relay.gelato.digital/tasks/status/0x1bf7664a1e176472f604bb3840d3d2a5bf56f98b60307961c3f8cee099f1eeb8) . 5. [Transaction 0x814d3 (opens in a new tab)](https://bscscan.com/tx/0x814d385c0ec036be65663b5fbfb0d8d4e0d35af395d4d96b13f2cafaf43138f9) is executed on the blockchain. ### Use a Safe as the Relay[](#use-a-safe-as-the-relay) While using Gelato, you can specify that you only want the relay to allow transactions from specific smart contracts. If one of those smart contracts is a Safe smart contract, you will need to either verify the contract on a block explorer or get the ABI of the contract implementation (not the ABI of the smart contract address). This is because the Safe smart contracts use the [Proxy Pattern (opens in a new tab)](https://medium.com/coinmonks/proxy-pattern-and-upgradeable-smart-contracts-45d68d6f15da) , so the implementation logic for your smart contract exists on a different address. ### Imports[](#imports) ` _10 import { ethers } from 'ethers' _10 import { GelatoRelayPack } from '@safe-global/relay-kit' _10 import Safe from '@safe-global/protocol-kit' _10 import { _10 MetaTransactionData, _10 MetaTransactionOptions _10 } from '@safe-global/types-kit' ` ### Initialize the transaction settings[](#initialize-the-transaction-settings) Modify the variables to customize to match your desired transaction settings. ` _10 // https://chainlist.org _10 const RPC_URL = 'https://endpoints.omniatech.io/v1/bsc/mainnet/public' _10 const OWNER_PRIVATE_KEY = process.env.OWNER_PRIVATE_KEY _10 const safeAddress = '0x...' // Safe from which the transaction will be sent _10 _10 // Any address can be used for destination. In this example, we use vitalik.eth _10 const destinationAddress = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' _10 const withdrawAmount = ethers.parseUnits('0.005', 'ether').toString() ` ### Create a transaction[](#create-a-transaction) ` _10 // Create a transactions array with one transaction object _10 const transactions: MetaTransactionData[] = [{ _10 to: destinationAddress, _10 data: '0x', _10 value: withdrawAmount _10 }] _10 _10 const options: MetaTransactionOptions = { _10 isSponsored: true _10 } ` ### Instantiate the Protocol Kit and Relay Kit[](#instantiate-the-protocol-kit-and-relay-kit) ` _10 const protocolKit = await Safe.init({ _10 provider: RPC_URL, _10 signer: OWNER_PRIVATE_KEY, _10 safeAddress _10 }) _10 _10 const relayKit = new GelatoRelayPack({ _10 apiKey: process.env.GELATO_RELAY_API_KEY!, _10 protocolKit _10 }) ` ### Prepare the transaction[](#prepare-the-transaction) ` _10 const safeTransaction = await relayKit.createTransaction({ _10 transactions, _10 options _10 }) _10 _10 const signedSafeTransaction = await protocolKit.signTransaction(safeTransaction) ` ### Send the transaction to the relay[](#send-the-transaction-to-the-relay) `` _10 const response = await relayKit.executeTransaction({ _10 executable: signedSafeTransaction, _10 options _10 }) _10 _10 console.log(`Relay Transaction Task ID: https://relay.gelato.digital/tasks/status/${response.taskId}`) `` Gelato SyncFee[](#gelato-syncfee) ---------------------------------- [Gelato SyncFee (opens in a new tab)](https://docs.gelato.network/developer-services/relay/quick-start/callwithsyncfee) allows you to execute a transaction and pay the gas fees directly with funds in your Safe, even if you don't have ETH or the native blockchain token. For the SyncFee quickstart tutorial, you will use the Gelato relayer to pay for the gas fees on the BNB Chain using the BNB you hold in your Safe. No need to have funds on your signer. ### Imports[](#imports-1) ` _10 import { ethers } from 'ethers' _10 import { GelatoRelayPack } from '@safe-global/relay-kit' _10 import Safe from '@safe-global/protocol-kit' _10 import { MetaTransactionData } from '@safe-global/types-kit' ` ### Initialize the transaction settings[](#initialize-the-transaction-settings-1) Modify the variables to customize to match your desired transaction settings. ` _10 // https://chainlist.org _10 const RPC_URL = 'https://endpoints.omniatech.io/v1/bsc/mainnet/public' _10 const OWNER_PRIVATE_KEY = process.env.OWNER_PRIVATE_KEY _10 const safeAddress = '0x...' // Safe from which the transaction will be sent _10 _10 // Any address can be used for destination. In this example, we use vitalik.eth _10 const destinationAddress = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' _10 const withdrawAmount = ethers.parseUnits('0.005', 'ether').toString() ` ### Create a transaction[](#create-a-transaction-1) ` _10 // Create a transactions array with one transaction object _10 const transactions: MetaTransactionData[] = [{ _10 to: destinationAddress, _10 data: '0x', _10 value: withdrawAmount _10 }] ` ### Instantiate the Protocol Kit and Relay Kit[](#instantiate-the-protocol-kit-and-relay-kit-1) ` _10 const protocolKit = await Safe.init({ _10 provider: RPC_URL, _10 signer: OWNER_PRIVATE_KEY, _10 safeAddress _10 }) _10 _10 const relayKit = new GelatoRelayPack({ protocolKit }) ` ### Prepare the transaction[](#prepare-the-transaction-1) ` _10 const safeTransaction = await relayKit.createTransaction({ transactions }) _10 _10 const signedSafeTransaction = await protocolKit.signTransaction(safeTransaction) ` ### Send the transaction to the relay[](#send-the-transaction-to-the-relay-1) `` _10 const response = await relayKit.executeTransaction({ _10 executable: signedSafeTransaction _10 }) _10 _10 console.log(`Relay Transaction Task ID: https://relay.gelato.digital/tasks/status/${response.taskId}`) `` [ERC-4337 Safe SDK](/sdk/relay-kit/guides/4337-safe-sdk "ERC-4337 Safe SDK") [Migrate to v2](/sdk/relay-kit/guides/migrate-to-v2 "Migrate to v2") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v2 – Safe Docs SDK [Relay Kit](/sdk/relay-kit) Guides Migrate to v2 Migrate to v2 ============= This guide references the major changes between v1 and v2 to help those migrating an existing app. GelatoRelayPack[](#gelatorelaypack) ------------------------------------ * The `GelatoRelayPack` constructor now includes a mandatory `protocolKit` parameter. It's required for any new pack extending the `RelayKitBasePack`. ` _10 constructor({ apiKey, protocolKit }: GelatoOptions) ` * We removed the `protocolKit` parameter from the `createTransactionWithHandlePayment()`, `createTransactionWithTransfer()`, and `executeRelayTransaction()` methods in the `GelatoRelayPack` as now it's included in the constructor. * Removed the `export interface RelayPack` type as we now use an abstract class. [Gelato Relay](/sdk/relay-kit/guides/gelato-relay "Gelato Relay") [Migrate to v3](/sdk/relay-kit/guides/migrate-to-v3 "Migrate to v3") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v3 – Safe Docs SDK [Relay Kit](/sdk/relay-kit) Guides Migrate to v3 Migrate to v3 ============= This guide references the major changes between v2 and v3 to help those migrating an existing app. Remove the adapters[](#remove-the-adapters) -------------------------------------------- We have removed the concept of adapters from the `protocol-kit` to simplify the library. Instead of using specific library adapters, we use now an internal `SafeProvider` object to interact with the Safe. This `SafeProvider` will be created using: * An Ethereum provider, an [EIP-1193 (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-1193) compatible provider, or an RPC URL. * An optional address of the signer that is connected to the provider or a private key. If not provided, the first account of the provider (`eth_accounts`) will be selected as the signer. These changes affect the creation of the `Safe4337Pack` instance, as it was previously using an `ethAdapter` compatible object. ` _10 // old _10 const safe4337Pack = await Safe4337Pack.init({ _10 ethAdapter: new EthersAdapter({ ethers, signerOrProvider }), _10 // ... _10 }) ` ` _12 // new _12 const safe4337Pack = await Safe4337Pack.init({ _12 provider: window.ethereum, // Or any compatible EIP-1193 provider, _12 signer: 'signerAddressOrPrivateKey', // Signer address or signer private key _12 // ... _12 }) _12 _12 const safe4337Pack = await Safe4337Pack.init({ _12 provider: 'http://rpc.url', // Or websocket _12 signer: 'privateKey', // Signer private key _12 // ... _12 }) ` [Migrate to v2](/sdk/relay-kit/guides/migrate-to-v2 "Migrate to v2") [Migrate to V4](/sdk/relay-kit/guides/migrate-to-v4 "Migrate to V4") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safe React Hooks – Safe Docs SDK Safe React Hooks Safe React Hooks ================ The Safe React Hooks are the starting point for interacting with the Safe smart account using a React application. These hooks are built on top of the [Starter Kit](/sdk/starter-kit) , which leverages and abstracts the complex logic from several kits from the Safe{Core} SDK, allowing you to set a React context that gives access to the exposed Safe functionality everywhere in your application. [#### @safe-global/safe-react-hooks](https://www.npmjs.com/package/@safe-global/safe-react-hooks) The following guides show how to use the Safe React Hooks and integrate it into your project: * [Send transactions](/sdk/react-hooks/guides/send-transactions) Resources[](#resources) ------------------------ * [Safe React Hooks on GitHub (opens in a new tab)](https://github.com/safe-global/safe-react-hooks) [Safe4337Pack](/sdk/relay-kit/reference/safe-4337-pack "Safe4337Pack") [Send Transactions](/sdk/react-hooks/guides/send-transactions "Send Transactions") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Migrate to v4 – Safe Docs SDK [Relay Kit](/sdk/relay-kit) Guides Migrate to V4 Migrate to v4 ============= This guide references the major changes between v3 and v4 to help those migrating an existing app. FeeEstimator interface updated[](#feeestimator-interface-updated) ------------------------------------------------------------------ The `IFeeEstimator` interface originally included three methods for users to hook into the process to estimate the fees of an User operation. In the `relay-kit` new version we simplified the interface to feature only two methods that configure or update the User operation fields before and after calling the standard RPC estimation method (`eth_estimateUserOperationGas`). Additionally, we renamed the methods to be more descriptive. The old interface had this 3 methods: * `setupEstimation`: This method enabled updating the gas related properties as needed before calling the `eth_estimateUserOperationGas` method. * `adjustEstimation`: This method enabled to adjust the gas related properties as needed after calling the `eth_estimateUserOperationGas` method. * `getPaymasterEstimation`: This method was called to retrieve and adjust the paymaster related fields if we want to use the `paymaster` feature. It was only called when the User operation were an sponsored one. The new interface has only two methods: * `preEstimateUserOperationGas`: This method enable developers to update the gas or paymaster fields before calling `eth_estimateUserOperationGas`. * `postEstimateUserOperationGas`: This method enable to adjust the gas or paymaster fields as needed after calling the `eth_estimateUserOperationGas`. With this new API, the user can have more control over the gas estimation process and enhance the flexibility of the User operation fields that can be updated after this call. `PimlicoCustomRpcSchema` only with Pimlico custom methods[](#pimlicocustomrpcschema-only-with-pimlico-custom-methods) ---------------------------------------------------------------------------------------------------------------------- We now have a separated `PimlicoCustomRpcSchema` that is used to define the custom RPC methods that are not part of the standard JSON-RPC methods. This schema is used to define the custom methods that are used in the `Pimlico` module. If you want to create your own estimator and call non-standard methods you should create your own one. Previously the `PimlicoCustomRpcSchema` included both the standard and custom methods. The former `PimlicoCustomRpcSchema` is now `Safe4337RpcSchema` and includes only the standard JSON-RPC methods. `paymasterUrl` is now mandatory in `PaymasterOptions`[](#paymasterurl-is-now-mandatory-in-paymasteroptions) ------------------------------------------------------------------------------------------------------------ Previously the `paymasterUrl` was only mandatory to work with sponsored User operations. Now it is mandatory to be set in the `PaymasterOptions` object as we are calling paymaster specific RPC methods (`pm_getPaymasterStubData` and `pm_getPaymasterData`) to fill the paymaster related fields. Rename `addModulesLibAddress`[](#rename-addmoduleslibaddress) -------------------------------------------------------------- We renamed the `addModulesLibAddress` method to `safeModulesSetupAddress` to reflect the change of the Safe contract name. ` _10 const safe4337Pack = await Safe4337Pack.init({ _10 provider: window.ethereum, // Or any compatible EIP-1193 provider, _10 signer: 'signerAddressOrPrivateKey', _10 bundlerUrl: 'https://...', _10 customContracts: { _10 safeModulesSetupAddress: '0x1234567890123456789012345678901234567890' // Previously addModulesLibAddress _10 } _10 }) ` Renamed `EthSafeOperation`[](#renamed-ethsafeoperation) -------------------------------------------------------- We renamed the `EthSafeOperation` to `BaseSafeOperation` so all the methods using it as a type in the `Safe4337Pack` will reflect this change [Migrate to v3](/sdk/relay-kit/guides/migrate-to-v3 "Migrate to v3") [Safe4337Pack](/sdk/relay-kit/reference/safe-4337-pack "Safe4337Pack") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Safe4337Pack – Safe Docs SDK [Relay Kit](/sdk/relay-kit) Reference Safe4337Pack Safe4337Pack ============ The `Safe4337Pack` enables Safe accounts to interact with user operations through the implementation of the `RelayKitBasePack`. You can find more about ERC-4337 at [this link (opens in a new tab)](https://eips.ethereum.org/EIPS/eip-4337) . Install dependencies[](#install-dependencies) ---------------------------------------------- To use `Safe4337Pack` in your project, start by installing the `relay-kit` package with this command: ` _10 yarn add @safe-global/relay-kit ` Reference[](#reference) ------------------------ The `Safe4337Pack` class make easy to use the [Safe 4337 Module (opens in a new tab)](https://github.com/safe-global/safe-modules/tree/main/modules/4337/contracts/Safe4337Module.sol) with your Safe. It enables creating, signing, and executing transactions grouped in user operations using a selected provider. You can select your preferred [bundler (opens in a new tab)](https://www.erc4337.io/docs/bundlers/introduction) and [paymaster (opens in a new tab)](https://www.erc4337.io/docs/paymasters/introduction) . ` _10 const safe4337Pack = await Safe4337Pack.init({ _10 provider, _10 signer, _10 bundlerUrl, _10 safeModulesVersion, _10 customContracts, _10 options, _10 paymasterOptions _10 }) ` ### `init(safe4337InitOptions)`[](#initsafe4337initoptions) The static method `init()` generates an instance of `Safe4337Pack`. Use this method to create the initial instance instead of the regular constructor. ℹ️ We have added support for then Entrypoint v0.7 contract but we are not making it the default yet. If you are using Entrypoint v0.7, you need to set the `safeModuleVersion` to `0.3.0` when calling the `Safe4337Pack.init` method. This version of the Safe 4337 Module is the one compatible with the Entrypoint v0.7. **Parameters** The `Safe4337InitOptions` used in the `init()` method are: ` _47 Safe4337InitOptions = { _47 provider: Eip1193Provider | HttpTransport | SocketTransport _47 signer?: HexAddress | PrivateKey | PasskeyArgType _47 bundlerUrl: string _47 safeModulesVersion?: string _47 customContracts?: { _47 entryPointAddress?: string _47 safe4337ModuleAddress?: string _47 addModulesLibAddress?: string _47 } _47 options: ExistingSafeOptions | PredictedSafeOptions _47 paymasterOptions?: PaymasterOptions _47 } _47 _47 HexAddress = string _47 PrivateKey = string _47 HttpTransport = string _47 SocketTransport = string _47 _47 Eip1193Provider = { _47 request: (args: RequestArguments) => Promise _47 } _47 _47 RequestArguments = { _47 method: string _47 params?: readonly unknown[] | object _47 } _47 _47 ExistingSafeOptions = { _47 safeAddress: string _47 } _47 _47 PredictedSafeOptions = { _47 owners: string[] _47 threshold: number _47 safeVersion?: SafeVersion _47 saltNonce?: string _47 } _47 _47 PaymasterOptions = { _47 paymasterUrl?: string _47 isSponsored?: boolean _47 sponsorshipPolicyId?: string _47 paymasterAddress: string _47 paymasterTokenAddress?: string _47 amountToApprove?: bigint _47 } ` * **`provider`** : The EIP-1193 compatible provider or RPC URL of the selected chain. * **`signer`** : A passkey or the signer private key if the `provider` doesn't resolve to a signer account. If the `provider` resolves to multiple signer addresses, the `signer` property can be used to specify which account to connect, otherwise the first address returned will be used. * **`rpcUrl`** : The RPC URL of the selected chain. * **`bundlerUrl`** : The bundler's URL. * **`safeModulesVersion`** : The version of the [Safe Modules contract (opens in a new tab)](https://github.com/safe-global/safe-modules-deployments/tree/main/src/assets/safe-4337-module) . * **`customContracts`** : An object with custom contract addresses. This is optional, if no custom contracts are provided, default ones will be used. * **`entryPointAddress`** : The address of the entry point. Defaults to the address returned by the `eth_supportedEntryPoints` method from the provider API. * **`safe4337ModuleAddress`** : The address of the `Safe4337Module`. Defaults to `safe-modules-deployments` using the current version. * **`addModulesLibAddress`** : The address of the `AddModulesLib` library. Defaults to `safe-modules-deployments` using the current version. * **`options`** : The Safe account options. * **`safeAddress`** : The Safe address. You can only use this prop to specify an existing Safe account. * **`owners`** : The array with Safe owners. * **`threshold`** : The Safe threshold. This is the number of owners required to sign and execute a transaction. * **`safeVersion`** : The version of the [Safe contract (opens in a new tab)](https://github.com/safe-global/safe-deployments/tree/main/src/assets) . Defaults to the current version. * **`saltNonce`** : The Safe salt nonce. Changing this value enables the creation of different safe (predicted) addresses using the same configuration (`owners`, `threshold`, and `safeVersion`). * **`paymasterOptions`** : The paymaster options. * **`paymasterUrl`** : The paymaster URL. You can obtain the URL from the management dashboard of the selected services provider. This URL will be used for gas estimations. * **`isSponsored`** : A boolean flag to indicate if we want to use a paymaster to sponsor transactions. * **`sponsorshipPolicyId`** : The sponsorship policy ID can be obtained from the management dashboard of the selected payment services provider. * **`paymasterAddress`** : The address of the paymaster contract to use. * **`paymasterTokenAddress`** : The paymaster token address for transaction fee payments. * **`amountToApprove`** : The `paymasterTokenAddress` amount to approve. **Returns** A promise that resolves to an instance of the `Safe4337Pack`. **Caveats** * Use this method to create the initial instance instead of the standard constructor. * You should search for some API services URLs and contract addresses in the management dashboards of your selected provider. These include `bundlerUrl`, `paymasterUrl`, `paymasterAddress`, `paymasterTokenAddress`, `sponsorshipPolicyId`, and `rpcUrl` (In this case any valid RPC should be fine). * The SDK uses default versions when `safeModulesVersion` or `safeVersion` are not specified. You can find more details about the current versions [here (opens in a new tab)](https://github.com/safe-global/safe-core-sdk/blob/924ae56ff707509e561c99296fb5e1fbc2050d28/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts#L34-L35) . * The `saltNonce` derives different Safe addresses by using the `protocol-kit` method `predictSafeAddress`. You can find more details about this process [here (opens in a new tab)](https://github.com/safe-global/safe-core-sdk/blob/924ae56ff707509e561c99296fb5e1fbc2050d28/packages/protocol-kit/src/contracts/utils.ts#L245-L315) . * We typically initialize the pack in two ways. One way is by using an existing account with the `safeAddress` prop. The other way is by using the `owners`, `threshold`, `saltNonce`, and `safeVersion` props to create a new Safe account. You can also apply the second method to existing addresses, as the output address will be the same if the inputs are identical. * The SDK queries `eth_supportedEntryPoints` for a default `entryPointAddress` if not given. It fetches `safe4337ModuleAddress` and `addModulesLibAddress` from the `safe-modules-deployments` repository if not provided. You can find them at: [safe-modules-deployments (opens in a new tab)](https://github.com/safe-global/safe-modules-deployments/tree/main/src/assets/safe-4337-module) . * To use a paymaster without sponsorship, you need to hold a certain amount of `paymasterTokenAddress` in the Safe account for fees. Make sure to provide the `paymasterAddress` as well. * You can choose to use a paymaster to sponsor transactions by setting the `isSponsored` prop. When sponsoring transactions, you need to provide the `paymasterUrl`, `paymasterAddress`, and optionally the `sponsorshipPolicyId`. * An approval for the concrete ERC-20 token is required to use the paymaster so remember to add the `paymasterTokenAddress` of the ERC-20 token that will pay the fees. The SDK will encode this approval internally and send it to the bundler with the rest of the user operation. * Specify the amount to approve for the `paymasterTokenAddress` using the `amountToApprove` prop. This is necessary when the Safe account is not deployed, and you need to approve the paymaster token for fee payments and Safe account setup. ### `new Safe4337Pack({protocolKit, bundlerClient, publicClient, bundlerUrl, paymasterOptions, entryPointAddress, safe4337ModuleAddress})`[](#new-safe4337packprotocolkit-bundlerclient-publicclient-bundlerurl-paymasteroptions-entrypointaddress-safe4337moduleaddress) The `Safe4337Pack` constructor method is used within the `init()` method and should not be directly accessed. The parameters are calculated or provided by the `init()` method. ### `createTransaction(safe4337CreateTransactionProps)`[](#createtransactionsafe4337createtransactionprops) Create a `SafeOperation` from a transaction batch. You can send multiple transactions to this method. The SDK internally bundles these transactions into a batch sent to the bundler as a `UserOperation`. If the transaction is only one then no batch is created a it's not necessary. **Parameters** The `Safe4337CreateTransactionProps` ` _10 Safe4337CreateTransactionProps = { _10 transactions: MetaTransactionData[] _10 options?: { _10 amountToApprove?: bigint _10 validUntil?: number _10 validAfter?: number _10 feeEstimator?: IFeeEstimator _10 customNonce?: bigint _10 } _10 } ` * **`transactions`** : Array of `MetaTransactionData` to batch in a `SafeOperation` (using the MultiSend contract if more than one transaction is included). * **`options`** : Optional parameters. * **`amountToApprove`** : The amount to approve to the `paymasterTokenAddress`. * **`validUntil`** : The UserOperation will remain valid until this block's timestamp. * **`validAfter`** : The UserOperation will be valid after this block's timestamp. * **`feeEstimator`** : The fee estimator calculates gas requirements by implementing the `IFeeEstimator` interface. * **`customNonce`** : The custom nonce for the SafeOperation. If not provided, the nonce will be calculated internally using the `getNonce` method from the Entrypoint contract. **Returns** A promise that resolves to the `SafeOperation`. **Caveats** * The `SafeOperation` is similar to the standard user operation but includes Safe-specific fields. Before sending it to the bundler, we convert the `SafeOperation` to a regular user operation. We need to sign the operation for the bundler to execute it using the `Safe4337Module`. * You can set the `amountToApprove` in this method to approve the `paymasterTokenAddress` for transaction payments, similar to how `amountToApprove` works in the `init()` method. * We use a similar API to `protocol-kit` for developers transitioning to `Safe4337Pack`. This API helps with creating and executing transactions, bundling user operations and sending them to the bundler. * Use `validUntil` and `validAfter` to set the block timestamp range for the user operation's validity. The operation will be rejected if the block timestamp falls outside this range. * The `feeEstimator` calculates gas needs for the UserOperation. We default to Pimlico's `feeEstimator`, but you can use a different one by providing your own. The IFeeEstimator interface requires an object with specific methods. * User operations support the usage of [custom nonce (opens in a new tab)](https://docs.stackup.sh/docs/useroperation-nonce) . You can use a custom nonce by leveraging the `createTransaction` method and passing the `customNonce` property as part of the `options` parameter. We exported an utility method `encodeNonce` in the `relay-kit` to make it easier to compose the nonce. ` _17 IFeeEstimator { _17 preEstimateUserOperationGas?: EstimateFeeFunction _17 postEstimateUserOperationGas?: EstimateFeeFunction _17 } _17 _17 EstimateFeeFunctionProps = { _17 userOperation: UserOperation _17 bundlerUrl: string _17 entryPoint: string _17 } _17 _17 EstimateSponsoredFeeFunctionProps = { _17 userOperation: UserOperation _17 paymasterUrl: string _17 entryPoint: string _17 sponsorshipPolicyId?: string _17 } ` All methods are optional and will be called in the specified order if you provide any of them: 1. `preEstimateUserOperationGas` : This method is used before calling the standard RPC `eth_estimateUserOperationGas` method, allowing you to setup the User operation before the bundler gas estimation. 2. `postEstimateUserOperationGas` : This method is used after calling `eth_estimateUserOperationGas` method to adjust the bundler estimation. ### `signSafeOperation(safeOperation, signingMethod)`[](#signsafeoperationsafeoperation-signingmethod) Signs a `SafeOperation`. **Parameters** * **`safeOperation`** : The `EthSafeOperation | SafeOperationResponse` to sign. Can either be created by the `Safe4337Pack` or fetched via `api-kit`. * **`signingMethod`** : The method to use for signing the transaction. The default is `SigningMethod.ETH_SIGN_TYPED_DATA_V4`. **Returns** A promise that resolves to the signed `SafeOperation`. **Caveats** * Use this method after the `SafeOperation` is generated with the `createTransaction` method. * This method adds the signer's signature to the signatures map of the `SafeOperation` object. Additional signatures can be included from multiple owners. * It works similar to `signTransaction` and `signMessage` methods in the `protocol-kit` but using `SafeOperation` instead of `SafeTransaction` or `SafeMessage`. For more information, refer to the Safe [docs (opens in a new tab)](https://docs.safe.global/sdk/protocol-kit/guides/signatures) . ### `executeTransaction(safe4337ExecutableProps)`[](#executetransactionsafe4337executableprops) This method sends the user operation to the bundler. ℹ️ If you are not using a paymaster and need to deploy a new Safe (counterfactual deployment), you must hold in the predicted Safe address the amount of native token required to cover the fees. **Parameters** The `Safe4337ExecutableProps` ` _10 Safe4337ExecutableProps = { _10 executable: EthSafeOperation | SafeOperationResponse _10 } ` * **`executable`** : The `SafeOperation` to execute. Can either be created by the `Safe4337Pack` or fetched via `api-kit`. **Returns** A promise, resolves to the user operation hash. **Caveats** * The process converts the `SafeOperation` to a standard user operation, then forwards it to the bundler. The `SafeOperation` must be created and signed by the Safe owner. * You can use the user operation hash to browse the status (e.g `https://jiffyscan.xyz/userOpHash/{userOpHash}`) ### `getUserOperationByHash(userOpHash)`[](#getuseroperationbyhashuserophash) Retrieve the user operation using its hash. **Parameters** * **`userOpHash`** : The user operation hash is returned by the `executeTransaction` method. The user operation can be executed or pending, and the method will return the payload data for the user operation. **Returns** A Promise that resolves to `UserOperationWithPayload`. ` _10 UserOperationWithPayload = { _10 userOperation: UserOperation _10 entryPoint: string _10 transactionHash: string _10 blockHash: string _10 blockNumber: string _10 } ` **Caveats** * Use this method to request information about the user operation sent to the bundler, but do not use it for the execution status. ### `getUserOperationReceipt(userOpHash)`[](#getuseroperationreceiptuserophash) Get `UserOperation` receipt by a hash. **Parameters** * **`userOpHash`** : Unique identifier for the `UserOperation` **Returns** A Promise that resolves to `UserOperationReceipt` after the user operation is executed. ` _10 UserOperationReceipt = { _10 userOpHash: string _10 sender: string _10 nonce: string _10 actualGasUsed: string _10 actualGasCost: string _10 success: boolean _10 logs: Log[] _10 receipt: Receipt _10 } ` **Caveats** * Use this method to obtain the full execution trace and status. * You can use this method to check if the `UserOperation` was successful by calling it repeatedly until the receipt is available. ### `getSupportedEntryPoints()`[](#getsupportedentrypoints) Retrieve all supported entry points. **Returns** A promise that resolves to an array of entry point addresses (strings) supported by the bundler. **Caveats** We use this method to obtain the default entry point if not provided in the `init()` method. ### `getChainId()`[](#getchainid) Retrieve the EIP-155 Chain ID. **Returns** A promise that resolves to the EIP-155 Chain ID string. [Migrate to V4](/sdk/relay-kit/guides/migrate-to-v4 "Migrate to V4") [Safe React Hooks](/sdk/react-hooks "Safe React Hooks") Was this page helpful? [Report issue](https://github.com/safe-global/safe-docs/issues/new?assignees=&labels=nextra-feedback&projects=&template=nextra-feedback.yml&title=%5BFeedback%5D+) --- # Send Transactions – Safe Docs SDK [Safe React Hooks](/sdk/react-hooks) Guides Send Transactions Send Transactions ================= This guide will teach you how to deploy new Safe accounts and create, sign, and execute Safe transactions using the Safe React Hooks. For more detailed information, see the [Safe React Hooks Reference](/reference-sdk-react-hooks/overview) . Prerequisites[](#prerequisites) -------------------------------- * [Node.js and npm (opens in a new tab)](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) Install dependencies[](#install-dependencies) ---------------------------------------------- First, you need to install some dependencies. ` _10 pnpm add @safe-global/safe-react-hooks ` Steps[](#steps) ---------------- ### Imports[](#imports) Here are all the necessary imports for this guide. ` _10 import { _10 SafeProvider, _10 createConfig, _10 useSafe, _10 useSendTransaction, _10 SendTransactionVariables, _10 useConfirmTransaction, _10 ConfirmTransactionVariables _10 } from '@safe-global/safe-react-hooks' _10 import { sepolia } from 'viem/chains' ` ### Create a signer and provider[](#create-a-signer-and-provider) Firstly, you need to get a signer, which will be the owner of a Safe account after it's deployed. This example uses a private key, but any way to get an EIP-1193 compatible signer can be used. ` _10 const SIGNER_ADDRESS = // ... _10 const SIGNER_PRIVATE_KEY = // ... _10 _10 const RPC_URL = 'https://rpc.ankr.com/eth_sepolia' ` ### Initialize the Safe React Hooks[](#initialize-the-safe-react-hooks) You need to wrap your app with the [`SafeProvider`](/reference-sdk-react-hooks/safeprovider) to have access to the different Safe React Hooks like `useSendTransaction()`, `useConfirmTransaction()`, and `usePendingTransactions()` that will provide the functionality you need in this guide. `SafeProvider` receives a `config` object with different properties to create the global configuration that you can get from the [`createConfig`](/reference-sdk-react-hooks/createconfig) function. New Safe accountExisting Safe account When deploying a new Safe account for the connected signer, you need to pass the configuration of the Safe in the `safeOptions` property. In this case, the Safe account is configured with your signer as the only owner. ` _10 const config = createConfig({ _10 chain: sepolia, _10 provider: RPC_URL, _10 signer: SIGNER_PRIVATE_KEY, _10 safeOptions: { _10 owners: [SIGNER_ADDRESS], _10 threshold: 1 _10 } _10 }) ` To apply the global configuration to your app, pass the created `config` to the `SafeProvider`. ` _10 _10 _10 ` ### Create a Safe transaction[](#create-a-safe-transaction) Create an array of Safe transactions to execute. ` _10 const transactions = [{ _10 to: '0x...', _10 data: '0x', _10 value: '0' _10 }] ` ### Send the Safe transaction[](#send-the-safe-transaction) Create a `SendTransaction` component in your application to create and send a transaction. If you configured your Safe with `threshold` equal to `1`, calling the `sendTransaction` function from the [`useSendTransaction`](/reference-sdk-react-hooks/usesendtransaction) hook will execute the Safe transaction. However, if the `threshold` is greater than `1` the other owners of the Safe will need to confirm the transaction until the required number of signatures are collected. SendTransaction.tsx ` _15 function SendTransaction() { _15 const { sendTransaction } = useSendTransaction() _15 _15 const sendTransactionParams: SendTransactionVariables = { _15 transactions _15 } _15 _15 return ( _15 _15 ) _15 } _15 _15 export default SendTransaction ` ### Confirm the Safe transaction[](#confirm-the-safe-transaction) Create a `ConfirmPendingTransactions` component in your application to check the transactions pending for confirmation in case the Safe transaction needs to be confirmed by other Safe owners. Retrieve all the pending Safe transactions from the Safe Transaction Service by calling the [`getPendingTransaction`](/reference-sdk-react-hooks/usesafe/getpendingtransactions) function from the [`useSafe`](/reference-sdk-react-hooks/usesafe) hook, and call the `confirmTransaction` function from the [`useConfirmTransaction`](/reference-sdk-react-hooks/useconfirmtransaction) hook to confirm them. Notice that the `SafeProvider` configuration needs to be initialized with a different Safe owner as the `signer` when confirming a transaction. ConfirmPendingTransactions.tsx ` _20 function ConfirmPendingTransactions() { _20 const { getPendingTransactions } = useSafe() _20 const { data = [] } = getPendingTransactions() _20 const { confirmTransaction } = useConfirmTransaction() _20 _20 return ( _20 <> _20 {data.length > 0 && data.map(tx => ( _20 <> _20 {tx.safeTxHash} _20