|
Swap
@@ -355,4 +365,4 @@ Ready to start building? Choose your development environment:
### Need Help?
-{% include "../.gitbook/includes/support-cards.md" %}
+{% include "../.gitbook/includes/support-cards.md" %}
\ No newline at end of file
diff --git a/sdk/multisig-modules/README.md b/sdk/multisig-modules/README.md
new file mode 100644
index 0000000..1296afe
--- /dev/null
+++ b/sdk/multisig-modules/README.md
@@ -0,0 +1,69 @@
+# Multisig Modules
+
+This document provides an overview of the WDK Multisig Modules.
+
+The Wallet Development Kit (WDK) provides multisig modules that enable secure multi-party wallet management. These modules allow multiple signers to control shared wallets with customizable approval thresholds.
+
+## Why Multisig?
+
+Multisig (multi-signature) wallets require multiple parties to approve transactions before execution. This provides:
+
+* **Enhanced Security**: No single point of failure - multiple keys required
+* **Team Control**: Perfect for companies, DAOs, and shared treasuries
+* **Approval Workflows**: Configurable thresholds
+* **Accountability**: Full audit trail of who approved what
+
+## Available Modules
+
+| Module | Blockchain | Status | Documentation |
+| --- | --- | --- | --- |
+| @tetherto/wdk-protocol-multisig-safe | EVM | ✅ Ready | [Documentation](protocol-multisig-safe/README.md) |
+
+## Module Features
+
+### Standard Features
+
+All multisig modules share these core features:
+
+* **Multi-Owner Management**: Add, remove, and swap owners
+* **Threshold Configuration**: Set required approval count
+* **Propose/Approve/Execute**: Standard multisig workflow
+* **Transaction Tracking**: View pending and historical transactions
+* **Message Signing**: Sign and verify messages with multisig approval (EIP-1271)
+
+### Account Abstraction Features
+
+Modules with ERC-4337 support include:
+
+* **Gasless Transactions**: Pay fees in ERC-20 tokens (e.g., USDT)
+* **Sponsored Mode**: Completely gas-free transactions
+* **Bundled Operations**: Multiple approvals in single transaction
+
+## Getting Started
+
+To get started with WDK multisig modules, follow these steps:
+
+1. Get up and running quickly with our [Quick Start Guide](/start-building/nodejs-bare-quickstart).
+2. Choose the module that best fits your needs from the table above.
+3. Check specific documentation for the module you wish to use.
+
+## Next Steps
+
+**Protocol Multisig Safe**
+
+Safe Protocol multisig with ERC-4337 account abstraction
+
+**Node.js Quickstart**
+
+Get started with WDK in a Node.js environment
+
+**Concepts**
+
+Learn about key concepts like Account Abstraction
+
+---
+
+### Need Help?
+
+
+{% include "../../.gitbook/includes/support-cards.md" %}
\ No newline at end of file
diff --git a/sdk/multisig-modules/protocol-multisig-safe/README.md b/sdk/multisig-modules/protocol-multisig-safe/README.md
new file mode 100644
index 0000000..c1aa5cf
--- /dev/null
+++ b/sdk/multisig-modules/protocol-multisig-safe/README.md
@@ -0,0 +1,53 @@
+# protocol-multisig-safe
+
+Overview of the @tetherto/wdk-protocol-multisig-safe module
+
+A simple and secure package to manage Safe Protocol multisig wallets with ERC-4337 account abstraction for EVM-compatible blockchains. This package provides a clean API for creating, managing, and interacting with multisig wallets using BIP-39 seed phrases and the Safe smart contract infrastructure.
+
+## Features
+
+* **Safe Protocol Integration**: Full support for Safe (formerly Gnosis Safe) multisig wallets
+* **ERC-4337 Account Abstraction**: Gasless transactions via paymasters and bundlers
+* **Paymaster Modes**: Support for both ERC-20 paymaster and sponsored (gasless) modes
+* **Per-Transaction Override**: Switch between ERC-20 and sponsored mode per transaction
+* **Multi-Owner Management**: Add, remove, swap owners and change threshold
+* **Propose/Approve/Execute**: Standard multisig transaction workflow
+* **Message Signing**: EIP-191 compliant multisig message signing
+* **Deterministic Addresses**: Predictable Safe addresses from owner configuration
+* **Auto-Execute**: Automatically execute transactions when threshold is met
+* **TypeScript Support**: Full TypeScript definitions included
+* **Memory Safety**: Secure private key management with memory-safe implementation
+
+## Supported Networks
+
+This package works with any EVM-compatible network that supports:
+
+* **Ethereum**: Mainnet, Sepolia
+* **Polygon**: Mainnet, Mumbai
+* **Arbitrum**: One, Nova
+* **Optimism**: Mainnet, Goerli
+* **And more**: Any chain with Safe Protocol and ERC-4337 infrastructure
+
+## Next Steps
+
+**Node.js Quickstart**
+
+Get started with WDK in a Node.js environment
+
+**WDK Multisig Safe Configuration**
+
+Get started with WDK's Multisig Safe configuration
+
+**WDK Multisig Safe API**
+
+Get started with WDK's Multisig Safe API
+
+**WDK Multisig Safe Usage**
+
+Get started with WDK's Multisig Safe usage
+
+---
+
+### Need Help?
+
+{% include "../../../.gitbook/includes/support-cards.md" %}
\ No newline at end of file
diff --git a/sdk/multisig-modules/protocol-multisig-safe/api-reference.md b/sdk/multisig-modules/protocol-multisig-safe/api-reference.md
new file mode 100644
index 0000000..c0e06a9
--- /dev/null
+++ b/sdk/multisig-modules/protocol-multisig-safe/api-reference.md
@@ -0,0 +1,763 @@
+# API Reference
+
+Complete API documentation for @tetherto/wdk-protocol-multisig-safe
+
+## Classes
+
+| Class | Description |
+| --- | --- |
+| WalletManagerEvmMultisigSafe | Main class for managing multisig Safe wallets. Extends WalletManager. |
+| WalletAccountEvmMultisigSafe | Individual multisig Safe account with signing capabilities. |
+| WalletAccountReadOnlyEvmMultisigSafe | Read-only multisig Safe account for balance checks and queries. |
+
+## WalletManagerEvmMultisigSafe
+
+The main class for managing Safe Protocol multisig wallets with ERC-4337 account abstraction.
+
+### Constructor
+
+```javascript
+new WalletManagerEvmMultisigSafe(seedPhrase, config)
+```
+
+**Parameters:**
+
+* `seedPhrase` (string): BIP-39 mnemonic seed phrase
+* `config` (EvmMultisigSafeConfig): Configuration object
+ * `chainId` (bigint): Blockchain network ID
+ * `provider` (string | Provider): RPC provider URL or EIP-1193 provider
+ * `bundlerUrl` (string): ERC-4337 bundler service URL
+ * `paymasterOptions` (PaymasterOptions): Paymaster configuration
+ * `options` (ExistingSafeOptions | PredictedSafeOptions): Safe creation or import config
+ * `transferMaxFee` (number, optional): Maximum fee in paymaster token units
+
+### Methods
+
+#### getAccount
+
+Returns a multisig Safe account at the specified index.
+
+```javascript
+const account = await wallet.getAccount(index)
+```
+
+**Parameters:**
+
+* `index` (number): Account index for BIP-44 derivation
+
+**Returns:** `Promise`
+
+#### getAccountByPath
+
+Returns a multisig Safe account at the specified BIP-44 derivation path.
+
+```javascript
+const account = await wallet.getAccountByPath(path)
+```
+
+**Parameters:**
+
+* `path` (string): BIP-44 derivation path (e.g., "0'/0/5")
+
+**Returns:** `Promise`
+
+#### dispose
+
+Disposes the wallet manager and clears sensitive data from memory.
+
+```javascript
+wallet.dispose()
+```
+
+## WalletAccountEvmMultisigSafe
+
+Individual multisig Safe account with full signing capabilities.
+
+### Constructor
+
+```javascript
+new WalletAccountEvmMultisigSafe(seedPhrase, path, config)
+```
+
+**Parameters:**
+
+* `seedPhrase` (string): BIP-39 mnemonic seed phrase
+* `path` (string): BIP-44 derivation path
+* `config` (EvmMultisigSafeConfig): Configuration object
+
+### Methods
+
+#### getAddress
+
+Returns the Safe contract address.
+
+```javascript
+const address = await account.getAddress()
+```
+
+**Returns:** `Promise` - Safe address (checksummed)
+
+#### getSignerAddress
+
+Returns the signer's EOA address.
+
+```javascript
+const signerAddress = await account.getSignerAddress()
+```
+
+**Returns:** `Promise` - EOA address
+
+#### getBalance
+
+Returns the native token balance in wei.
+
+```javascript
+const balance = await account.getBalance()
+```
+
+**Returns:** `Promise` - Balance in wei
+
+#### getTokenBalance
+
+Returns the ERC-20 token balance.
+
+```javascript
+const balance = await account.getTokenBalance(tokenAddress)
+```
+
+**Parameters:**
+
+* `tokenAddress` (string): ERC-20 token contract address
+
+**Returns:** `Promise` - Token balance in smallest units
+
+#### getPaymasterTokenBalance
+
+Returns the paymaster token balance for fee payment.
+
+```javascript
+const balance = await account.getPaymasterTokenBalance()
+```
+
+**Returns:** `Promise` - Paymaster token balance
+
+#### getOwners
+
+Returns the list of Safe owners.
+
+```javascript
+const owners = await account.getOwners()
+```
+
+**Returns:** `Promise` - Array of owner addresses
+
+#### getThreshold
+
+Returns the required number of confirmations.
+
+```javascript
+const threshold = await account.getThreshold()
+```
+
+**Returns:** `Promise` - Threshold value
+
+#### isDeployed
+
+Checks if the Safe contract is deployed on-chain.
+
+```javascript
+const deployed = await account.isDeployed()
+```
+
+**Returns:** `Promise`
+
+#### deploy
+
+Deploys the Safe contract on-chain. Requires ETH in signer's EOA.
+
+```javascript
+const result = await account.deploy()
+```
+
+**Returns:** `Promise<{ deployed: boolean, txHash: string | null }>` - Deployment result
+
+#### propose
+
+Proposes a new transaction for multisig approval.
+
+```javascript
+const proposal = await account.propose(transaction, options?)
+```
+
+**Parameters:**
+
+* `transaction` (EvmTransaction): Transaction to propose
+ * `to` (string): Recipient address
+ * `value` (string | bigint): Value in wei
+ * `data` (string, optional): Transaction data
+* `options` (ProposeOptions, optional): Propose options including paymaster overrides
+
+**Returns:** `Promise`
+
+* `safeOperationHash` (string): Unique operation identifier
+* `confirmations` (number): Number of confirmations
+* `threshold` (number): Required confirmations to execute
+
+#### approve
+
+Approves a pending transaction.
+
+```javascript
+const result = await account.approve(safeOperationHash)
+```
+
+**Parameters:**
+
+* `safeOperationHash` (string): Operation hash from propose()
+
+**Returns:** `Promise`
+
+* `confirmations` (number): Number of confirmations
+* `threshold` (number): Required threshold
+
+#### reject
+
+Rejects a pending transaction by proposing a rejection transaction.
+
+```javascript
+const result = await account.reject(safeOperationHash)
+```
+
+**Parameters:**
+
+* `safeOperationHash` (string): Operation hash to reject
+
+**Returns:** `Promise`
+
+#### execute
+
+Executes a transaction that has met the threshold.
+
+```javascript
+const result = await account.execute(safeOperationHash)
+```
+
+**Parameters:**
+
+* `safeOperationHash` (string): Operation hash from propose()
+
+**Returns:** `Promise`
+
+* `hash` (string): UserOperation hash
+
+#### isReadyToExecute
+
+Checks if a transaction has met the threshold and is ready to execute.
+
+```javascript
+const ready = await account.isReadyToExecute(safeOperationHash)
+```
+
+**Parameters:**
+
+* `safeOperationHash` (string): Operation hash to check
+
+**Returns:** `Promise`
+
+#### sendTransaction
+
+Proposes, collects approvals, and executes in one call. Auto-executes if threshold is met.
+
+```javascript
+const result = await account.sendTransaction(transaction, options?)
+```
+
+**Parameters:**
+
+* `transaction` (EvmTransaction): Transaction to send
+* `options` (ProposeOptions, optional): Options including paymaster overrides
+
+**Returns:** `Promise`
+
+* `hash` (string): Safe operation hash or UserOp hash
+* `fee` (bigint): Estimated fee
+* `confirmations` (number): Number of confirmations
+* `threshold` (number): Required threshold
+* `executed` (boolean): Whether transaction was executed
+
+#### quoteSendTransaction
+
+Estimates the fee for a transaction without executing.
+
+```javascript
+const quote = await account.quoteSendTransaction(transaction, options?)
+```
+
+**Parameters:**
+
+* `transaction` (EvmTransaction): Transaction to quote
+* `options` (ProposeOptions, optional): Options including paymaster overrides
+
+**Returns:** `Promise<{ fee: bigint }>`
+
+#### transfer
+
+Transfers ERC-20 tokens using the multisig workflow. Auto-executes if threshold is met.
+
+```javascript
+const result = await account.transfer(transferOptions, proposeOptions?)
+```
+
+**Parameters:**
+
+* `transferOptions` (TransferOptions): Transfer details
+ * `token` (string): ERC-20 token address
+ * `recipient` (string): Recipient address
+ * `amount` (string | bigint): Amount in smallest units
+* `proposeOptions` (ProposeOptions, optional): Options including paymaster overrides
+
+**Returns:** `Promise`
+
+#### quoteTransfer
+
+Estimates the fee for a token transfer.
+
+```javascript
+const quote = await account.quoteTransfer(transferOptions, proposeOptions?)
+```
+
+**Returns:** `Promise<{ fee: bigint }>`
+
+#### getTransactionHashByUserOpHash
+
+Gets the on-chain transaction hash from a UserOperation hash.
+
+```javascript
+const txHash = await account.getTransactionHashByUserOpHash(userOpHash)
+```
+
+**Parameters:**
+
+* `userOpHash` (string): UserOperation hash from execute()
+
+**Returns:** `Promise` - Transaction hash or null if pending
+
+#### getPendingTransactions
+
+Gets all pending transactions awaiting approval or execution.
+
+```javascript
+const pending = await account.getPendingTransactions()
+```
+
+**Returns:** `Promise<{ results: SafeOperationResponse[] }>`
+
+#### getSafeOperation
+
+Gets details of a specific Safe operation.
+
+```javascript
+const operation = await account.getSafeOperation(safeOperationHash)
+```
+
+**Returns:** `Promise`
+
+#### getTransactionHistory
+
+Gets the transaction history for the Safe.
+
+```javascript
+const history = await account.getTransactionHistory(options?)
+```
+
+**Returns:** `Promise`
+
+#### addOwner
+
+Proposes adding a new owner to the Safe.
+
+```javascript
+const proposal = await account.addOwner(ownerAddress, newThreshold?, options?)
+```
+
+**Parameters:**
+
+* `ownerAddress` (string): New owner address
+* `newThreshold` (number, optional): New threshold after adding
+* `options` (ProposeOptions, optional): Propose options
+
+**Returns:** `Promise`
+
+#### removeOwner
+
+Proposes removing an owner from the Safe.
+
+```javascript
+const proposal = await account.removeOwner(ownerAddress, newThreshold?, options?)
+```
+
+**Parameters:**
+
+* `ownerAddress` (string): Owner address to remove
+* `newThreshold` (number, optional): New threshold after removal
+* `options` (ProposeOptions, optional): Propose options
+
+**Returns:** `Promise`
+
+#### swapOwner
+
+Proposes swapping one owner for another.
+
+```javascript
+const proposal = await account.swapOwner(oldOwner, newOwner, options?)
+```
+
+**Parameters:**
+
+* `oldOwner` (string): Owner address to remove
+* `newOwner` (string): New owner address to add
+* `options` (ProposeOptions, optional): Propose options
+
+**Returns:** `Promise`
+
+#### changeThreshold
+
+Proposes changing the approval threshold.
+
+```javascript
+const proposal = await account.changeThreshold(newThreshold, options?)
+```
+
+**Parameters:**
+
+* `newThreshold` (number): New threshold value
+* `options` (ProposeOptions, optional): Propose options
+
+**Returns:** `Promise`
+
+#### updateOwners
+
+Proposes batch updating owners and threshold.
+
+```javascript
+const proposal = await account.updateOwners(newOwners, newThreshold, options?)
+```
+
+**Parameters:**
+
+* `newOwners` (string[]): New list of owner addresses
+* `newThreshold` (number): New threshold value
+* `options` (ProposeOptions, optional): Propose options
+
+**Returns:** `Promise`
+
+#### sign
+
+Signs a message with the multisig Safe. Proposes a new message or approves an existing one.
+
+```javascript
+const result = await account.sign(message, options?)
+```
+
+**Parameters:**
+
+* `message` (string): Message to sign
+* `options` (SignOptions, optional): Sign options
+ * `isApproval` (boolean): If true, approve existing message; otherwise propose new
+
+**Returns:** `Promise`
+
+* `signature` (string): This owner's signature
+* `safeMessage` (SafeMessage): Full SafeMessage object from Safe Transaction Service
+ * `messageHash` (string): Message hash
+ * `message` (string): Original message
+ * `confirmations` (array): Array of confirmations with owner and signature
+ * `preparedSignature` (string | null): Combined signature when fully signed
+ * `proposedBy` (string): Address that proposed the message
+ * `created` (string): Creation timestamp
+ * `modified` (string): Last modified timestamp
+
+#### verify
+
+Verifies a message signature using EIP-1271.
+
+```javascript
+const isValid = await account.verify(message, signature)
+```
+
+**Parameters:**
+
+* `message` (string): Original message
+* `signature` (string): Combined signature (preparedSignature from SafeMessage)
+
+**Returns:** `Promise` - True if signature is valid
+
+#### getMessage
+
+Gets the status of a signed message.
+
+```javascript
+const message = await account.getMessage(messageHash)
+```
+
+**Parameters:**
+
+* `messageHash` (string): Message hash
+
+**Returns:** `Promise`
+
+#### getPendingMessages
+
+Gets all pending messages awaiting signatures.
+
+```javascript
+const messages = await account.getPendingMessages()
+```
+
+**Returns:** `Promise<{ results: SafeMessage[] }>`
+
+#### toReadOnlyAccount
+
+Converts to a read-only account.
+
+```javascript
+const readOnly = await account.toReadOnlyAccount()
+```
+
+**Returns:** `Promise`
+
+#### dispose
+
+Disposes the account and clears sensitive data from memory.
+
+```javascript
+account.dispose()
+```
+
+## WalletAccountReadOnlyEvmMultisigSafe
+
+Read-only account for balance checks and queries without signing capabilities.
+
+### Static Methods
+
+#### getSafesByOwner
+
+Gets all Safe addresses owned by an EOA.
+
+```javascript
+const safes = await WalletAccountReadOnlyEvmMultisigSafe.getSafesByOwner(ownerAddress, config)
+```
+
+**Parameters:**
+
+* `ownerAddress` (string): EOA address to search
+* `config` (SafesByOwnerConfig): Configuration with chainId
+
+**Returns:** `Promise` - Array of Safe addresses
+
+#### getSafeInfo
+
+Gets Safe info without creating an instance.
+
+```javascript
+const info = await WalletAccountReadOnlyEvmMultisigSafe.getSafeInfo(safeAddress, config)
+```
+
+**Parameters:**
+
+* `safeAddress` (string): Safe contract address
+* `config` (SafesByOwnerConfig): Configuration with chainId
+
+**Returns:** `Promise` - Safe info including owners, threshold, version
+
+#### generateDeterministicSaltNonce
+
+Generates a deterministic salt nonce from owners and threshold.
+
+```javascript
+const saltNonce = WalletAccountReadOnlyEvmMultisigSafe.generateDeterministicSaltNonce(owners, threshold)
+```
+
+**Returns:** `string` - Deterministic salt nonce
+
+### Constructor
+
+```javascript
+new WalletAccountReadOnlyEvmMultisigSafe(address, config)
+```
+
+**Parameters:**
+
+* `address` (string | null): Safe contract address (null for predicted Safe)
+* `config` (EvmMultisigSafeReadOnlyConfig): Configuration object
+
+### Methods
+
+All query methods from WalletAccountEvmMultisigSafe are available:
+
+* `getAddress()`
+* `getBalance()`
+* `getTokenBalance(tokenAddress)`
+* `getPaymasterTokenBalance()`
+* `getOwners()`
+* `getThreshold()`
+* `getNonce()`
+* `isDeployed()`
+* `getVersion()`
+* `getPendingTransactions()`
+* `getSafeOperation(hash)`
+* `getTransactionHistory(options?)`
+* `getIncomingTransactions(options?)`
+* `isReadyToExecute(hash)`
+* `getTransactionHashByUserOpHash(hash)`
+* `getMessage(messageHash)`
+* `getPendingMessages()`
+* `quoteSendTransaction(tx, options?)`
+* `quoteTransfer(options, proposeOptions?)`
+
+## Types
+
+### EvmMultisigSafeConfig
+
+```typescript
+interface EvmMultisigSafeConfig {
+ // Required
+ chainId: bigint
+ provider: string | Eip1193Provider
+ bundlerUrl: string
+
+ // Required - Safe options (one of the following)
+ options: ExistingSafeOptions | PredictedSafeOptions
+
+ // Optional - Paymaster
+ paymasterOptions?: PaymasterOptions
+
+ // Optional - Safe Transaction Service
+ txServiceUrl?: string
+ safeApiKey?: string
+
+ // Optional - Fee limits
+ transferMaxFee?: number | bigint
+}
+```
+
+### ExistingSafeOptions
+
+```typescript
+interface ExistingSafeOptions {
+ safeAddress: string // Existing Safe address to import
+}
+```
+
+### PredictedSafeOptions
+
+```typescript
+interface PredictedSafeOptions {
+ owners: string[] // Owner addresses
+ threshold: number // Required signatures
+ saltNonce?: string // Optional: for deterministic address
+ safeVersion?: SafeVersion // Optional: Safe contract version
+ deploymentType?: DeploymentType
+}
+```
+
+### PaymasterOptions
+
+```typescript
+// ERC-20 Paymaster Mode
+interface ERC20PaymasterOptions {
+ paymasterUrl: string
+ paymasterAddress?: string
+ paymasterTokenAddress: string // ERC-20 token for gas payment
+ isSponsored?: false
+}
+
+// Sponsored Paymaster Mode
+interface SponsoredPaymasterOptions {
+ paymasterUrl: string
+ paymasterAddress?: string // Needed for ERC-20 override
+ isSponsored: true
+ sponsorshipPolicyId?: string
+}
+
+type PaymasterOptions = ERC20PaymasterOptions | SponsoredPaymasterOptions
+```
+
+### ProposeOptions
+
+```typescript
+interface ProposeOptions {
+ // Paymaster overrides
+ isSponsored?: boolean
+ sponsorshipPolicyId?: string
+ paymasterTokenAddress?: string
+ amountToApprove?: bigint
+}
+```
+
+### SignOptions
+
+```typescript
+interface SignOptions {
+ isApproval?: boolean // If true, approve existing; otherwise propose new
+}
+```
+
+### SignResult
+
+```typescript
+interface SignResult {
+ signature: string // This owner's signature
+ safeMessage: SafeMessage // Full SafeMessage from API
+}
+```
+
+### SafeMessage
+
+Imported from `@safe-global/api-kit`:
+
+```typescript
+interface SafeMessage {
+ messageHash: string
+ message: string | EIP712TypedData
+ proposedBy: string
+ confirmations: Array<{
+ owner: string
+ signature: string
+ }>
+ preparedSignature: string | null
+ created: string
+ modified: string
+ safe: string
+}
+```
+
+## Error Handling
+
+```javascript
+try {
+ const result = await account.execute(safeOperationHash)
+} catch (error) {
+ if (error.message.includes('threshold not met')) {
+ console.error('Not enough confirmations to execute')
+ }
+ if (error.message.includes('not enough funds')) {
+ console.error('Insufficient paymaster token balance')
+ }
+ if (error.message.includes('Exceeded maximum fee')) {
+ console.error('Transaction fee exceeds limit')
+ }
+ if (error.message.includes('not an owner')) {
+ console.error('Signer is not an owner of this Safe')
+ }
+ if (error.message.includes('already confirmed')) {
+ console.error('This signer has already confirmed')
+ }
+}
+```
+
+---
+
+### Need Help?
+
+{% include "../../../.gitbook/includes/support-cards.md" %}
\ No newline at end of file
diff --git a/sdk/multisig-modules/protocol-multisig-safe/configuration.md b/sdk/multisig-modules/protocol-multisig-safe/configuration.md
new file mode 100644
index 0000000..4084e77
--- /dev/null
+++ b/sdk/multisig-modules/protocol-multisig-safe/configuration.md
@@ -0,0 +1,296 @@
+# Configuration
+
+Configuration options and settings for @tetherto/wdk-protocol-multisig-safe
+
+## Wallet Configuration
+
+The WalletManagerEvmMultisigSafe requires a complete configuration object with required parameters:
+
+```javascript
+import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe'
+
+const config = {
+ // Required parameters
+ chainId: 11155111n,
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+
+ // Paymaster configuration
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // USDT or other supported token
+ },
+
+ // Safe configuration - for creating new Safe
+ options: {
+ owners: ['0xAliceEOA...', '0xBobEOA...'],
+ threshold: 2
+ },
+
+ // Optional parameters
+ transferMaxFee: 100000000000000
+}
+
+const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, config)
+```
+
+## Supported Paymaster Tokens
+
+For a complete list of supported ERC-20 tokens that can be used to pay gas fees on each network, see:
+[Pimlico ERC-20 Paymaster Supported Tokens](https://docs.pimlico.io/references/paymaster/erc20-paymaster/supported-tokens)
+
+## Account Configuration
+
+Both WalletAccountEvmMultisigSafe and WalletAccountReadOnlyEvmMultisigSafe use the same configuration structure:
+
+```javascript
+import {
+ WalletAccountEvmMultisigSafe,
+ WalletAccountReadOnlyEvmMultisigSafe
+} from '@tetherto/wdk-protocol-multisig-safe'
+
+// Full access account
+const account = new WalletAccountEvmMultisigSafe(
+ seedPhrase,
+ "0'/0/0", // BIP-44 derivation path
+ config // Same config as wallet manager
+)
+
+// Read-only account
+const readOnlyAccount = new WalletAccountReadOnlyEvmMultisigSafe(null, {
+ chainId: 11155111n,
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...'
+ },
+ options: {
+ safeAddress: '0xSafeAddress...'
+ }
+})
+```
+
+## Configuration Options
+
+### Chain ID
+
+The `chainId` option specifies the blockchain network ID. Must be a BigInt.
+
+```javascript
+// Ethereum Mainnet
+chainId: 1n
+
+// Sepolia Testnet
+chainId: 11155111n
+
+// Polygon Mainnet
+chainId: 137n
+
+// Arbitrum One
+chainId: 42161n
+```
+
+### Provider
+
+The `provider` option specifies the RPC endpoint for blockchain communication.
+
+```javascript
+// Using RPC URL
+const config = {
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY'
+}
+
+// Using browser provider (MetaMask)
+const config = {
+ provider: window.ethereum
+}
+
+// Using custom ethers provider
+import { JsonRpcProvider } from 'ethers'
+const config = {
+ provider: new JsonRpcProvider('https://sepolia.infura.io/v3/YOUR_KEY')
+}
+```
+
+### Bundler URL
+
+The `bundlerUrl` option specifies the URL of the ERC-4337 bundler service that handles UserOperation bundling and submission to the mempool. Required for transaction processing.
+
+```javascript
+// Pimlico bundler
+bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY'
+
+// Alchemy bundler
+bundlerUrl: 'https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY'
+
+// Stackup bundler
+bundlerUrl: 'https://api.stackup.sh/v1/node/YOUR_KEY'
+```
+
+### Paymaster Options
+
+The `paymasterOptions` object configures how transaction fees are paid.
+
+#### ERC-20 Paymaster Mode
+
+Pay fees using ERC-20 tokens (e.g., USDT):
+
+```javascript
+paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterAddress: '0x...', // Optional: paymaster contract address
+ paymasterTokenAddress: '0x...' // USDT or other supported token
+}
+```
+
+#### Sponsored Paymaster Mode
+
+Use a sponsored paymaster for gasless transactions:
+
+```javascript
+paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterAddress: '0x...', // Optional: needed for ERC-20 override
+ isSponsored: true,
+ sponsorshipPolicyId: 'sp_my_policy' // Optional: sponsorship policy ID
+}
+```
+
+### Safe Options
+
+The `options` object specifies how to create or import a Safe.
+
+#### Creating a New Safe (PredictedSafeOptions)
+
+```javascript
+options: {
+ owners: ['0xAliceEOA...', '0xBobEOA...', '0xCharlieEOA...'],
+ threshold: 2, // 2-of-3 multisig
+ saltNonce: '0x...', // Optional: for deterministic address
+ safeVersion: '1.4.1', // Optional: Safe contract version
+ deploymentType: 'canonical' // Optional: deployment type
+}
+```
+
+#### Importing an Existing Safe (ExistingSafeOptions)
+
+```javascript
+options: {
+ safeAddress: '0xExistingSafeAddress...'
+}
+```
+
+#### Discovering Existing Safes
+
+Use the static method to discover Safes before creating an account:
+
+```javascript
+const safes = await WalletAccountReadOnlyEvmMultisigSafe.getSafesByOwner(
+ '0xOwnerEOA...',
+ { chainId: 11155111n }
+)
+
+// Then import the Safe you want
+const config = {
+ // ... other config
+ options: {
+ safeAddress: safes[0]
+ }
+}
+```
+
+### Transfer Max Fee
+
+The `transferMaxFee` option specifies the maximum fee amount for transfer operations in paymaster token units.
+
+```javascript
+// Maximum fee of 1 USDT (6 decimals)
+transferMaxFee: 1000000
+
+// Maximum fee of 0.1 USDT
+transferMaxFee: 100000
+```
+
+## Network-Specific Configurations
+
+### Ethereum Mainnet
+
+```javascript
+const ethereumMainnetConfig = {
+ chainId: 1n,
+ provider: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/ethereum/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/ethereum/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens
+ },
+ options: {
+ owners: ['0xOwner1...', '0xOwner2...'],
+ threshold: 2
+ }
+}
+```
+
+### Polygon Mainnet
+
+```javascript
+const polygonMainnetConfig = {
+ chainId: 137n,
+ provider: 'https://polygon-mainnet.g.alchemy.com/v2/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/polygon/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/polygon/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens
+ },
+ options: {
+ owners: ['0xOwner1...', '0xOwner2...'],
+ threshold: 2
+ }
+}
+```
+
+### Arbitrum One
+
+```javascript
+const arbitrumOneConfig = {
+ chainId: 42161n,
+ provider: 'https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/arbitrum/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/arbitrum/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens
+ },
+ options: {
+ owners: ['0xOwner1...', '0xOwner2...'],
+ threshold: 2
+ }
+}
+```
+
+### Sepolia Testnet
+
+```javascript
+const sepoliaTestnetConfig = {
+ chainId: 11155111n,
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens
+ },
+ options: {
+ owners: ['0xOwner1...', '0xOwner2...'],
+ threshold: 2
+ },
+ transferMaxFee: 1000000 // 1 USDT max fee (6 decimals)
+}
+```
+
+For supported tokens on each network, see: [Pimlico ERC-20 Paymaster Supported Tokens](https://docs.pimlico.io/references/paymaster/erc20-paymaster/supported-tokens)
+
+---
+
+### Need Help?
+
+{% include "../../../.gitbook/includes/support-cards.md" %}
\ No newline at end of file
diff --git a/sdk/multisig-modules/protocol-multisig-safe/usage.md b/sdk/multisig-modules/protocol-multisig-safe/usage.md
new file mode 100644
index 0000000..5d5504d
--- /dev/null
+++ b/sdk/multisig-modules/protocol-multisig-safe/usage.md
@@ -0,0 +1,647 @@
+# Usage
+
+Installation, quick start, and usage examples for @tetherto/wdk-protocol-multisig-safe
+
+## Installation
+
+To install the `@tetherto/wdk-protocol-multisig-safe` package, follow these instructions:
+
+```bash
+npm install @tetherto/wdk-protocol-multisig-safe
+```
+
+## Supported Paymaster Tokens
+
+For a complete list of supported ERC-20 tokens that can be used to pay gas fees on each network, see:
+[Pimlico ERC-20 Paymaster Supported Tokens](https://docs.pimlico.io/references/paymaster/erc20-paymaster/supported-tokens)
+
+## Quick Start
+
+### Importing from `@tetherto/wdk-protocol-multisig-safe`
+
+1. WalletManagerEvmMultisigSafe: Main class for managing multisig Safe wallets
+2. WalletAccountEvmMultisigSafe: Use this for full access accounts
+3. WalletAccountReadOnlyEvmMultisigSafe: Use this for read-only accounts
+
+### Creating a New Multisig Wallet
+
+```javascript
+import WalletManagerEvmMultisigSafe, {
+ WalletAccountEvmMultisigSafe,
+ WalletAccountReadOnlyEvmMultisigSafe
+} from '@tetherto/wdk-protocol-multisig-safe'
+
+// Use a BIP-39 seed phrase (replace with your own secure phrase)
+const seedPhrase = 'your twelve word seed phrase here' // Replace with actual seed generation
+
+// Create wallet manager with multisig Safe configuration
+const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, {
+ chainId: 11155111n, // Sepolia testnet
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // USDT or other supported token
+ },
+ options: {
+ owners: ['0xAliceEOA...', '0xBobEOA...'],
+ threshold: 2
+ },
+ transferMaxFee: 100000000000000 // Optional: Maximum fee in paymaster token units
+})
+
+// Get the first account
+const account = await wallet.getAccount(0)
+const safeAddress = await account.getAddress()
+console.log('Safe address:', safeAddress)
+```
+
+### Discovering Existing Safes
+
+```javascript
+import { WalletAccountReadOnlyEvmMultisigSafe } from '@tetherto/wdk-protocol-multisig-safe'
+
+// Discover Safes where your EOA is an owner
+const ownerAddress = '0xYourEOA...'
+const existingSafes = await WalletAccountReadOnlyEvmMultisigSafe.getSafesByOwner(
+ ownerAddress,
+ { chainId: 11155111n }
+)
+
+console.log('Found Safes:', existingSafes)
+// Returns: ['0xSafe1...', '0xSafe2...', ...]
+
+// Get info about a specific Safe
+const safeInfo = await WalletAccountReadOnlyEvmMultisigSafe.getSafeInfo(
+ existingSafes[0],
+ { chainId: 11155111n }
+)
+
+console.log('Owners:', safeInfo.owners)
+console.log('Threshold:', safeInfo.threshold)
+console.log('Version:', safeInfo.version)
+```
+
+### Importing an Existing Safe
+
+```javascript
+import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe'
+
+// Import an existing Safe by address using ExistingSafeOptions
+const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, {
+ chainId: 11155111n,
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...'
+ },
+ options: {
+ safeAddress: '0xExistingSafeAddress...' // Import by address
+ }
+})
+
+const account = await wallet.getAccount(0)
+const address = await account.getAddress()
+console.log('Imported Safe:', address)
+```
+
+### Deploying a Safe
+
+```javascript
+// Note: Deployment requires ETH in the signer's EOA for gas
+const account = await wallet.getAccount(0)
+
+// Check signer's EOA address
+const signerEoa = await account.getSignerAddress()
+console.log('Fund this address with ETH for deployment:', signerEoa)
+
+// Check if Safe is deployed
+const isDeployed = await account.isDeployed()
+console.log('Is deployed:', isDeployed)
+
+// Deploy the Safe (if not already deployed)
+if (!isDeployed) {
+ const deployResult = await account.deploy()
+ console.log('Deployed:', deployResult.deployed)
+ console.log('Transaction hash:', deployResult.txHash)
+}
+
+// After deployment, all transactions can use paymaster or sponsor
+// No more ETH needed in the Safe or signer's EOA!
+```
+
+### Managing Multiple Accounts
+
+```javascript
+import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe'
+
+// Get the first account (index 0)
+const account = await wallet.getAccount(0)
+const address = await account.getAddress()
+console.log('Account 0 address:', address)
+
+// Get the second account (index 1)
+const account1 = await wallet.getAccount(1)
+const address1 = await account1.getAddress()
+console.log('Account 1 address:', address1)
+
+// Get account by custom derivation path
+const customAccount = await wallet.getAccountByPath("0'/0/5")
+const customAddress = await customAccount.getAddress()
+console.log('Custom account address:', customAddress)
+```
+
+### Checking Balances
+
+#### Owned Account
+
+For accounts where you have the seed phrase and full access:
+
+```javascript
+import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe'
+
+// Assume wallet and account are already created
+// Get native token balance (in wei)
+const balance = await account.getBalance()
+console.log('Native balance:', balance, 'wei') // 1 ETH = 1000000000000000000 wei
+
+// Get ERC20 token balance
+const tokenContract = '0x...' // Token contract address
+const tokenBalance = await account.getTokenBalance(tokenContract)
+console.log('Token balance:', tokenBalance)
+
+// Get paymaster token balance (for paying fees)
+const paymasterBalance = await account.getPaymasterTokenBalance()
+console.log('Paymaster token balance:', paymasterBalance, 'units')
+```
+
+#### Read-Only Account
+
+For addresses where you don't have the seed phrase:
+
+```javascript
+import { WalletAccountReadOnlyEvmMultisigSafe } from '@tetherto/wdk-protocol-multisig-safe'
+
+// Create a read-only account
+const readOnlyAccount = new WalletAccountReadOnlyEvmMultisigSafe(null, {
+ chainId: 11155111n,
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...'
+ },
+ options: {
+ safeAddress: '0xSafeAddress...'
+ }
+})
+
+// Check native token balance
+const balance = await readOnlyAccount.getBalance()
+console.log('Native balance:', balance, 'wei')
+
+// Check ERC20 token balance
+const tokenBalance = await readOnlyAccount.getTokenBalance('0x...')
+console.log('Token balance:', tokenBalance)
+```
+
+## Multisig Transaction Workflow
+
+### Propose a Transaction
+
+```javascript
+// Propose a transaction (first signer)
+const quote = await account.quoteSendTransaction({
+ to: '0xRecipientAddress...',
+ value: '1000000000000000000', // 1 ETH in wei
+ data: '0x'
+})
+
+console.log('Estimated fee:', quote.fee)
+
+const proposal = await account.propose({
+ to: '0xRecipientAddress...',
+ value: '1000000000000000000',
+ data: '0x'
+}, {
+ amountToApprove: quote.fee * 150n / 100n // 50% buffer
+})
+
+console.log('Safe operation hash:', proposal.safeOperationHash)
+console.log('Confirmations:', proposal.confirmations)
+console.log('Threshold:', proposal.threshold)
+```
+
+### Approve a Transaction
+
+```javascript
+// Second signer approves the transaction
+const bobAccount = new WalletAccountEvmMultisigSafe(bobSeedPhrase, "0'/0/0", config)
+
+const approval = await bobAccount.approve(proposal.safeOperationHash)
+console.log('Transaction approved by Bob')
+console.log('Confirmations:', approval.confirmations, '/', approval.threshold)
+```
+
+### Execute a Transaction
+
+```javascript
+// Check if ready to execute
+const isReady = await account.isReadyToExecute(proposal.safeOperationHash)
+console.log('Ready to execute:', isReady)
+
+// Execute when threshold is met
+if (isReady) {
+ const result = await account.execute(proposal.safeOperationHash)
+ console.log('UserOp hash:', result.hash)
+
+ // Get on-chain transaction hash
+ const txHash = await account.getTransactionHashByUserOpHash(result.hash)
+ console.log('Transaction hash:', txHash)
+}
+```
+
+### Get Pending Transactions
+
+```javascript
+// Get all pending transactions for the Safe
+const pending = await account.getPendingTransactions()
+const threshold = await account.getThreshold()
+
+for (const op of pending.results) {
+ console.log('Hash:', op.safeOperationHash)
+ console.log('Confirmations:', op.confirmations?.length || 0, '/', threshold)
+}
+```
+
+## Paymaster Modes
+
+### ERC-20 Paymaster Mode (Default)
+
+Pay transaction fees using ERC-20 tokens (e.g., USDT):
+
+```javascript
+const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, {
+ chainId: 11155111n,
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // USDT or other supported token
+ },
+ options: {
+ owners: ['0xAlice...', '0xBob...'],
+ threshold: 2
+ }
+})
+
+// Propose with token approval for gas
+const quote = await account.quoteSendTransaction(tx)
+const proposal = await account.propose(tx, {
+ amountToApprove: quote.fee * 150n / 100n
+})
+```
+
+### Sponsored (Gasless) Mode
+
+Use a sponsored paymaster for completely gasless transactions:
+
+```javascript
+const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, {
+ chainId: 11155111n,
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterAddress: '0x...', // Needed for ERC-20 override
+ isSponsored: true,
+ sponsorshipPolicyId: 'sp_my_policy' // Optional
+ },
+ options: {
+ owners: ['0xAlice...', '0xBob...'],
+ threshold: 2
+ }
+})
+
+// No amountToApprove needed - sponsor pays gas!
+const proposal = await account.propose(tx)
+```
+
+### Per-Transaction Override
+
+Override paymaster mode for individual transactions:
+
+```javascript
+// Default config uses ERC-20 paymaster
+const account = await wallet.getAccount(0)
+
+// Override to sponsored mode for this transaction
+const result = await account.sendTransaction({
+ to: '0x...',
+ value: '1000000000000000000',
+ data: '0x'
+}, {
+ isSponsored: true // Override to sponsored mode
+})
+
+// Override to different ERC-20 token
+const result2 = await account.sendTransaction({
+ to: '0x...',
+ value: '1000000000000000000',
+ data: '0x'
+}, {
+ isSponsored: false,
+ paymasterTokenAddress: '0xDifferentToken...' // Use different token
+})
+```
+
+## Owner Management
+
+### Add an Owner
+
+```javascript
+// Propose adding a new owner
+const proposal = await account.addOwner('0xNewOwnerAddress...', null, {
+ amountToApprove: fee * 200n / 100n
+})
+
+// Other owners approve
+await bobAccount.approve(proposal.safeOperationHash)
+
+// Execute when threshold is met
+await account.execute(proposal.safeOperationHash)
+```
+
+### Remove an Owner
+
+```javascript
+// Propose removing an owner
+const proposal = await account.removeOwner('0xOwnerToRemove...', newThreshold, {
+ amountToApprove: fee * 200n / 100n
+})
+
+// Other owners approve and execute
+await bobAccount.approve(proposal.safeOperationHash)
+await account.execute(proposal.safeOperationHash)
+```
+
+### Swap an Owner
+
+```javascript
+// Propose swapping an owner
+const proposal = await account.swapOwner('0xOldOwner...', '0xNewOwner...', {
+ amountToApprove: fee * 200n / 100n
+})
+
+// Other owners approve and execute
+await bobAccount.approve(proposal.safeOperationHash)
+await account.execute(proposal.safeOperationHash)
+```
+
+### Change Threshold
+
+```javascript
+// Propose changing the threshold
+const proposal = await account.changeThreshold(newThreshold, {
+ amountToApprove: fee * 200n / 100n
+})
+
+// Other owners approve and execute
+await bobAccount.approve(proposal.safeOperationHash)
+await account.execute(proposal.safeOperationHash)
+```
+
+### Batch Update Owners
+
+```javascript
+// Propose batch updating owners and threshold
+const proposal = await account.updateOwners(
+ ['0xOwner1...', '0xOwner2...', '0xOwner3...'],
+ 2, // new threshold
+ { amountToApprove: fee * 300n / 100n }
+)
+
+// Other owners approve and execute
+await bobAccount.approve(proposal.safeOperationHash)
+await account.execute(proposal.safeOperationHash)
+```
+
+## Message Signing
+
+### Sign a Message (Propose)
+
+```javascript
+// Alice signs (proposes) a message
+const result = await alice.sign('Hello from Safe!')
+
+console.log('Alice signature:', result.signature)
+console.log('Message hash:', result.safeMessage.messageHash)
+console.log('Confirmations:', result.safeMessage.confirmations.length)
+```
+
+### Sign a Message (Approve)
+
+```javascript
+// Bob signs (approves) the same message
+const approval = await bob.sign('Hello from Safe!', { isApproval: true })
+
+console.log('Bob signature:', approval.signature)
+console.log('Confirmations:', approval.safeMessage.confirmations.length)
+
+// Check if fully signed
+if (approval.safeMessage.preparedSignature) {
+ console.log('Combined signature:', approval.safeMessage.preparedSignature)
+}
+```
+
+### Verify a Signature (EIP-1271)
+
+```javascript
+// Verify the combined signature on-chain
+if (approval.safeMessage.preparedSignature) {
+ const isValid = await alice.verify(
+ 'Hello from Safe!',
+ approval.safeMessage.preparedSignature
+ )
+ console.log('Signature valid:', isValid)
+}
+```
+
+### Get Message Status
+
+```javascript
+// Get message by hash
+const message = await account.getMessage(messageHash)
+
+console.log('Message:', message.message)
+console.log('Proposed by:', message.proposedBy)
+console.log('Confirmations:', message.confirmations.length)
+console.log('Combined signature:', message.preparedSignature)
+```
+
+### Get Pending Messages
+
+```javascript
+// Get all pending messages
+const pending = await account.getPendingMessages()
+
+for (const msg of pending.results) {
+ console.log('Hash:', msg.messageHash)
+ console.log('Message:', msg.message)
+ console.log('Confirmations:', msg.confirmations.length)
+}
+```
+
+## Transaction Tracking
+
+### Get Transaction History
+
+```javascript
+// Get transaction history for the Safe
+const history = await account.getTransactionHistory()
+
+for (const tx of history.results) {
+ console.log('Hash:', tx.transactionHash)
+ console.log('Status:', tx.isExecuted ? 'Executed' : 'Pending')
+}
+```
+
+### Get On-Chain Transaction Hash
+
+```javascript
+// After executing, get the on-chain transaction hash
+const result = await account.execute(safeOperationHash)
+console.log('UserOp hash:', result.hash)
+
+// Wait for confirmation and get tx hash
+const txHash = await account.getTransactionHashByUserOpHash(result.hash)
+console.log('Transaction hash:', txHash)
+```
+
+## Memory Management
+
+```javascript
+// Dispose wallet accounts to clear private keys from memory
+account.dispose()
+
+// Dispose entire wallet manager
+wallet.dispose()
+```
+
+## Complete Examples
+
+### Complete Wallet Setup
+
+```javascript
+import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe'
+
+async function setupMultisigWallet() {
+ const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
+
+ // Create multisig wallet manager
+ const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, {
+ chainId: 11155111n, // Sepolia testnet
+ provider: 'https://sepolia.infura.io/v3/YOUR_KEY',
+ bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterOptions: {
+ paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
+ paymasterTokenAddress: '0x...' // USDT or other supported token
+ },
+ options: {
+ owners: ['0xAliceEOA...', '0xBobEOA...'],
+ threshold: 2
+ },
+ transferMaxFee: 100000 // Optional: Maximum fee in paymaster token units
+ })
+
+ // Get first account
+ const account = await wallet.getAccount(0)
+ const address = await account.getAddress()
+ console.log('Safe address:', address)
+
+ // Check balances
+ const nativeBalance = await account.getBalance()
+ console.log('Native balance:', nativeBalance, 'wei')
+
+ const paymasterBalance = await account.getPaymasterTokenBalance()
+ console.log('Paymaster token balance:', paymasterBalance, 'units')
+
+ return { wallet, account, address, paymasterBalance }
+}
+```
+
+### Complete Multisig Transaction Flow
+
+```javascript
+async function multisigTransactionFlow() {
+ // Get fee quote
+ const quote = await aliceAccount.quoteSendTransaction({
+ to: '0xRecipient...',
+ value: '1000000000000000000', // 1 ETH
+ data: '0x'
+ })
+
+ // Alice proposes a transaction
+ const proposal = await aliceAccount.propose({
+ to: '0xRecipient...',
+ value: '1000000000000000000',
+ data: '0x'
+ }, {
+ amountToApprove: quote.fee * 150n / 100n
+ })
+
+ console.log('Proposal created:', proposal.safeOperationHash)
+ console.log('Confirmations:', proposal.confirmations, '/', proposal.threshold)
+
+ // Bob approves
+ const approval = await bobAccount.approve(proposal.safeOperationHash)
+ console.log('Bob approved')
+ console.log('Confirmations:', approval.confirmations, '/', approval.threshold)
+
+ // Check if ready to execute
+ const isReady = await aliceAccount.isReadyToExecute(proposal.safeOperationHash)
+
+ if (isReady) {
+ // Execute the transaction
+ const result = await aliceAccount.execute(proposal.safeOperationHash)
+ console.log('Transaction executed:', result.hash)
+
+ // Get on-chain transaction hash
+ const txHash = await aliceAccount.getTransactionHashByUserOpHash(result.hash)
+ console.log('TX Hash:', txHash)
+ }
+}
+```
+
+### Complete Message Signing Flow
+
+```javascript
+async function messageSigningFlow() {
+ const message = 'Hello from Safe!'
+
+ // Alice signs (proposes)
+ const result = await aliceAccount.sign(message)
+ console.log('Alice signed:', result.signature.slice(0, 20) + '...')
+ console.log('Message hash:', result.safeMessage.messageHash)
+
+ // Bob signs (approves)
+ const approval = await bobAccount.sign(message, { isApproval: true })
+ console.log('Bob signed:', approval.signature.slice(0, 20) + '...')
+
+ // Verify combined signature
+ if (approval.safeMessage.preparedSignature) {
+ const isValid = await aliceAccount.verify(
+ message,
+ approval.safeMessage.preparedSignature
+ )
+ console.log('Signature valid:', isValid)
+ }
+}
+```
+
+---
+
+### Need Help?
+
+{% include "../../../.gitbook/includes/support-cards.md" %}
\ No newline at end of file
|