diff --git a/SUMMARY.md b/SUMMARY.md index 19489db..5ce34c6 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -73,15 +73,16 @@ * [Usage](sdk/bridge-modules/bridge-usdt0-evm/usage.md) * [Configuration](sdk/bridge-modules/bridge-usdt0-evm/configuration.md) * [API Reference](sdk/bridge-modules/bridge-usdt0-evm/api-reference.md) - * [Lending Modules](sdk/lending-modules/README.md) * [lending-aave-evm](sdk/lending-modules/lending-aave-evm/README.md) * [Usage](sdk/lending-modules/lending-aave-evm/usage.md) * [Configuration](sdk/lending-modules/lending-aave-evm/configuration.md) * [API Reference](sdk/lending-modules/lending-aave-evm/api-reference.md) +* [Multisig Modules](sdk/multisig-modules/README.md) + * [multisig-safe](sdk/multisig-modules/protocol-multisig-safe/README.md) + * [Usage](sdk/multisig-modules/protocol-multisig-safe/usage.md) + * [Configuration](sdk/multisig-modules/protocol-multisig-safe/configuration.md) + * [API Reference](sdk/multisig-modules/protocol-multisig-safe/api-reference.md) * [Fiat Modules](sdk/fiat-modules/README.md) * [fiat-moonpay](sdk/fiat-modules/fiat-moonpay/README.md) * [Usage](sdk/fiat-modules/fiat-moonpay/usage.md) diff --git a/sdk/get-started.md b/sdk/get-started.md index 11d2bac..7899b21 100644 --- a/sdk/get-started.md +++ b/sdk/get-started.md @@ -28,6 +28,7 @@ It is built on some core principles: **self-custodial and stateless** (private k * **Multi-Chain Support**: Bitcoin, Ethereum, TON, TRON, Solana, Spark, and more * **Account Abstraction**: Gasless transactions on supported chains * **DeFi Integration**: Plug-in support for swaps, bridges, and lending protocols +* **Multisig Wallets**: Safe Protocol integration for multi-party wallet control * **Extensible Design**: Add custom modules for new blockchains or protocols *** @@ -46,7 +47,7 @@ New functionality is added through modules rather than modifying core code. Also #### Module Types -WDK modules are organized into five main categories, each serving a specific purpose in the blockchain application stack: +WDK modules are organized into six main categories, each serving a specific purpose in the blockchain application stack: @@ -75,6 +76,15 @@ WDK modules are organized into five main categories, each serving a specific pur Wallet Modules + + + + +
+ Multisig + Multi-party wallet management + Multisig Modules +
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