From 6b10f2237d3b476f3ccb16981b185b24b5e5ec8c Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Mon, 29 Dec 2025 21:22:51 +0700 Subject: [PATCH 01/10] add multisig-modules --- SUMMARY.md | 6 +- sdk/multisig-modules/README.md | 84 +++ .../protocol-multisig-safe/README.md | 69 ++ .../protocol-multisig-safe/api-reference.md | 588 ++++++++++++++++++ .../protocol-multisig-safe/configuration.md | 298 +++++++++ .../protocol-multisig-safe/usage.md | 506 +++++++++++++++ 6 files changed, 1550 insertions(+), 1 deletion(-) create mode 100644 sdk/multisig-modules/README.md create mode 100644 sdk/multisig-modules/protocol-multisig-safe/README.md create mode 100644 sdk/multisig-modules/protocol-multisig-safe/api-reference.md create mode 100644 sdk/multisig-modules/protocol-multisig-safe/configuration.md create mode 100644 sdk/multisig-modules/protocol-multisig-safe/usage.md diff --git a/SUMMARY.md b/SUMMARY.md index fe4c561..a4e38df 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -80,7 +80,11 @@ * [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) + * [protocol-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) ## UI Kits * [React Native UI Kit](ui-kits/react-native-ui-kit/README.md) diff --git a/sdk/multisig-modules/README.md b/sdk/multisig-modules/README.md new file mode 100644 index 0000000..270abe7 --- /dev/null +++ b/sdk/multisig-modules/README.md @@ -0,0 +1,84 @@ +# Multisig Modules + +Overview of 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 messages with multisig approval + +### Account Abstraction Features + +Modules with ERC-4337 support include: + +* **Gasless Transactions**: Pay fees in ERC-20 tokens +* **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? + +**Discord Community** + +Connect with developers, ask questions, share your projects + +[Join Community](https://discord.gg/arYXDhHB2w) + +**GitHub Issues** + +Report bugs, request features, and get technical help + +[Open an Issue](https://github.com/tetherto/wdk-core) + +**Email Contact** + +For sensitive or private matters, contact our team directly + +[Send an email](mailto:support@tether.to) \ 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..7b94e5d --- /dev/null +++ b/sdk/multisig-modules/protocol-multisig-safe/README.md @@ -0,0 +1,69 @@ +# 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? + +**Discord Community** + +Connect with developers, ask questions, share your projects + +[Join Community](https://discord.gg/arYXDhHB2w) + +**GitHub Issues** + +Report bugs, request features, and get technical help + +[Open an Issue](https://github.com/tetherto/wdk-core) + +**Email Contact** + +For sensitive or private matters, contact our team directly + +[Send an email](mailto:support@tether.to) \ 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..e88bbf7 --- /dev/null +++ b/sdk/multisig-modules/protocol-multisig-safe/api-reference.md @@ -0,0 +1,588 @@ +# 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` (MultisigSafeConfig): 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 + * `safeAccountConfig` (SafeAccountConfig, optional): 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` + +#### discoverExistingSafes + +Discovers existing Safe wallets where the signer's EOA is an owner. + +```javascript +const safes = await wallet.discoverExistingSafes() +``` + +**Returns:** `Promise` - Array of Safe addresses + +#### 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` (MultisigSafeConfig): Configuration object + +### Methods + +#### getAddress + +Returns the Safe contract address. + +```javascript +const address = await account.getAddress() +``` + +**Returns:** `Promise` - Safe address (checksummed) + +#### 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 + +#### 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<{ hash: string }>` - Deployment transaction hash + +#### propose + +Proposes a new transaction for multisig approval. + +```javascript +const proposal = await account.propose(transaction, overrides?) +``` + +**Parameters:** + +* `transaction` (TransactionRequest): Transaction to propose + * `to` (string): Recipient address + * `value` (string | bigint): Value in wei + * `data` (string, optional): Transaction data +* `overrides` (PaymasterOverrides, optional): Override paymaster settings + +**Returns:** `Promise` + +* `safeOperationHash` (string): Unique operation identifier +* `confirmations` (string[]): Array of signer addresses who confirmed +* `threshold` (number): Required confirmations to execute + +#### approve + +Approves a pending transaction. + +```javascript +await account.approve(safeOperationHash) +``` + +**Parameters:** + +* `safeOperationHash` (string): Operation hash from propose() + +**Returns:** `Promise` + +#### execute + +Executes a transaction that has met the threshold. + +```javascript +const result = await account.execute(safeOperationHash, overrides?) +``` + +**Parameters:** + +* `safeOperationHash` (string): Operation hash from propose() +* `overrides` (PaymasterOverrides, optional): Override paymaster settings + +**Returns:** `Promise<{ hash: string, fee: bigint }>` + +#### sendTransaction + +Proposes, collects approvals, and executes in one call. Use when you control enough signers. + +```javascript +const result = await account.sendTransaction(transaction, overrides?) +``` + +**Parameters:** + +* `transaction` (TransactionRequest): Transaction to send +* `overrides` (PaymasterOverrides, optional): Override paymaster settings + +**Returns:** `Promise<{ hash: string, fee: bigint }>` + +#### quoteSendTransaction + +Estimates the fee for a transaction without executing. + +```javascript +const quote = await account.quoteSendTransaction(transaction, overrides?) +``` + +**Parameters:** + +* `transaction` (TransactionRequest): Transaction to quote +* `overrides` (PaymasterOverrides, optional): Override paymaster settings + +**Returns:** `Promise<{ fee: bigint }>` + +#### transfer + +Transfers ERC-20 tokens using the multisig workflow. + +```javascript +const result = await account.transfer(transferRequest, overrides?) +``` + +**Parameters:** + +* `transferRequest` (TransferRequest): Transfer details + * `token` (string): ERC-20 token address + * `recipient` (string): Recipient address + * `amount` (bigint): Amount in smallest units +* `overrides` (PaymasterOverrides, optional): Override paymaster settings + +**Returns:** `Promise<{ hash: string, fee: bigint }>` + +#### quoteTransfer + +Estimates the fee for a token transfer. + +```javascript +const quote = await account.quoteTransfer(transferRequest, overrides?) +``` + +**Returns:** `Promise<{ fee: bigint }>` + +#### getPendingTransactions + +Gets all pending transactions awaiting approval or execution. + +```javascript +const pending = await account.getPendingTransactions() +``` + +**Returns:** `Promise` + +#### getTransactionHistory + +Gets the transaction history for the Safe. + +```javascript +const history = await account.getTransactionHistory() +``` + +**Returns:** `Promise` + +#### proposeAddOwner + +Proposes adding a new owner to the Safe. + +```javascript +const proposal = await account.proposeAddOwner({ owner, threshold? }) +``` + +**Parameters:** + +* `owner` (string): New owner address +* `threshold` (number, optional): New threshold after adding + +**Returns:** `Promise` + +#### proposeRemoveOwner + +Proposes removing an owner from the Safe. + +```javascript +const proposal = await account.proposeRemoveOwner({ owner, threshold }) +``` + +**Parameters:** + +* `owner` (string): Owner address to remove +* `threshold` (number): New threshold after removal (required) + +**Returns:** `Promise` + +#### proposeSwapOwner + +Proposes swapping one owner for another. + +```javascript +const proposal = await account.proposeSwapOwner({ oldOwner, newOwner }) +``` + +**Parameters:** + +* `oldOwner` (string): Owner address to remove +* `newOwner` (string): New owner address to add + +**Returns:** `Promise` + +#### proposeChangeThreshold + +Proposes changing the approval threshold. + +```javascript +const proposal = await account.proposeChangeThreshold({ threshold }) +``` + +**Parameters:** + +* `threshold` (number): New threshold value + +**Returns:** `Promise` + +#### signMessage + +Signs a message with the multisig Safe. + +```javascript +const signature = await account.signMessage(message) +``` + +**Parameters:** + +* `message` (string): Message to sign + +**Returns:** `Promise` - EIP-191 compliant signature + +#### verifyMessage + +Verifies a message signature. + +```javascript +const isValid = await account.verifyMessage(message, signature) +``` + +**Parameters:** + +* `message` (string): Original message +* `signature` (string): Signature to verify + +**Returns:** `Promise` + +#### 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. + +### Constructor + +```javascript +new WalletAccountReadOnlyEvmMultisigSafe(address, config) +``` + +**Parameters:** + +* `address` (string): Safe contract address +* `config` (MultisigSafeConfig): Configuration object (without safeAccountConfig) + +### Methods + +#### getAddress + +Returns the Safe contract address. + +```javascript +const address = await readOnlyAccount.getAddress() +``` + +**Returns:** `Promise` + +#### getBalance + +Returns the native token balance in wei. + +```javascript +const balance = await readOnlyAccount.getBalance() +``` + +**Returns:** `Promise` + +#### getTokenBalance + +Returns the ERC-20 token balance. + +```javascript +const balance = await readOnlyAccount.getTokenBalance(tokenAddress) +``` + +**Returns:** `Promise` + +#### getPaymasterTokenBalance + +Returns the paymaster token balance. + +```javascript +const balance = await readOnlyAccount.getPaymasterTokenBalance() +``` + +**Returns:** `Promise` + +#### isDeployed + +Checks if the Safe contract is deployed. + +```javascript +const deployed = await readOnlyAccount.isDeployed() +``` + +**Returns:** `Promise` + +#### getPendingTransactions + +Gets all pending transactions. + +```javascript +const pending = await readOnlyAccount.getPendingTransactions() +``` + +**Returns:** `Promise` + +#### getTransactionHistory + +Gets the transaction history. + +```javascript +const history = await readOnlyAccount.getTransactionHistory() +``` + +**Returns:** `Promise` + +## Types + +### MultisigSafeConfig + +```typescript +interface MultisigSafeConfig { + chainId: bigint + provider: string | Provider + bundlerUrl: string + paymasterOptions: PaymasterOptions + safeAccountConfig?: SafeAccountConfig + transferMaxFee?: number +} +``` + +### PaymasterOptions + +```typescript +interface PaymasterOptions { + paymasterUrl: string + paymasterTokenAddress?: string // For ERC-20 paymaster mode + sponsoredPaymaster?: boolean // For sponsored mode +} +``` + +### SafeAccountConfig + +```typescript +interface SafeAccountConfig { + owners?: string[] // For creating new Safe + threshold?: number // For creating new Safe + safeAddress?: string // For importing existing Safe +} +``` + +### PaymasterOverrides + +```typescript +interface PaymasterOverrides { + paymasterTokenAddress?: string // Override paymaster token + sponsoredPaymaster?: boolean // Override to sponsored mode + transferMaxFee?: number // Override max fee +} +``` + +### SafeOperationProposal + +```typescript +interface SafeOperationProposal { + safeOperationHash: string + confirmations: string[] + threshold: number + to: string + value: string + data: 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? + +**Discord Community** + +Connect with developers, ask questions, share your projects + +[Join Community](https://discord.gg/arYXDhHB2w) + +**GitHub Issues** + +Report bugs, request features, and get technical help + +[Open an Issue](https://github.com/tetherto/wdk-core) + +**Email Contact** + +For sensitive or private matters, contact our team directly + +[Send an email](mailto:support@tether.to) \ 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..fd78946 --- /dev/null +++ b/sdk/multisig-modules/protocol-multisig-safe/configuration.md @@ -0,0 +1,298 @@ +# 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: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC + }, + + // Safe configuration + safeAccountConfig: { + owners: ['0xAliceEOA...', '0xBobEOA...'], + threshold: 2 + }, + + // Optional parameters + transferMaxFee: 100000000000000 +} + +const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, config) +``` + +## 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 (transferMaxFee not needed) +const readOnlyAccount = new WalletAccountReadOnlyEvmMultisigSafe( + '0xSafeAddress...', // Safe contract address + { + 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: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' + } + // Note: safeAccountConfig not needed for read-only, address is provided directly + } +) +``` + +## 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: + +```javascript +paymasterOptions: { + paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', + paymasterTokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC +} +``` + +#### Sponsored Paymaster Mode + +Use a sponsored paymaster for gasless transactions: + +```javascript +paymasterOptions: { + paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', + sponsoredPaymaster: true +} +``` + +### Safe Account Configuration + +The `safeAccountConfig` object specifies how to create or import a Safe. + +#### Creating a New Safe + +```javascript +safeAccountConfig: { + owners: ['0xAliceEOA...', '0xBobEOA...', '0xCharlieEOA...'], + threshold: 2 // 2-of-3 multisig +} +``` + +#### Importing an Existing Safe + +```javascript +safeAccountConfig: { + safeAddress: '0xExistingSafeAddress...' +} +``` + +#### Discovering Existing Safes + +Omit `safeAccountConfig` to discover Safes: + +```javascript +const config = { + 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...' + } + // No safeAccountConfig - use wallet.discoverExistingSafes() +} +``` + +### Transfer Max Fee + +The `transferMaxFee` option specifies the maximum fee amount for transfer operations in paymaster token units. + +```javascript +// Maximum fee of 1 USDC (6 decimals) +transferMaxFee: 1000000 + +// Maximum fee of 0.1 USDC +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: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC + }, + safeAccountConfig: { + 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: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359' // USDC + }, + safeAccountConfig: { + 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: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' // USDC + }, + safeAccountConfig: { + 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: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC testnet + }, + safeAccountConfig: { + owners: ['0xOwner1...', '0xOwner2...'], + threshold: 2 + }, + transferMaxFee: 1000000 // 1 USDC max fee +} +``` + +--- + +### Need Help? + +**Discord Community** + +Connect with developers, ask questions, share your projects + +[Join Community](https://discord.gg/arYXDhHB2w) + +**GitHub Issues** + +Report bugs, request features, and get technical help + +[Open an Issue](https://github.com/tetherto/wdk-core) + +**Email Contact** + +For sensitive or private matters, contact our team directly + +[Send an email](mailto:support@tether.to) \ 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..f0232dc --- /dev/null +++ b/sdk/multisig-modules/protocol-multisig-safe/usage.md @@ -0,0 +1,506 @@ +# 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 +``` + +## 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: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC on Sepolia + }, + safeAccountConfig: { + 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 WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe' + +// Create wallet manager without safeAccountConfig to discover existing Safes +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...' + } +}) + +// Discover Safes where your EOA is an owner +const existingSafes = await wallet.discoverExistingSafes() +console.log('Found Safes:', existingSafes) +// Returns: ['0xSafe1...', '0xSafe2...', ...] +``` + +### Importing an Existing Safe + +```javascript +import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe' + +// Import an existing Safe by address +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...' + }, + safeAccountConfig: { + 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 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('Deploy transaction hash:', deployResult.hash) +} +``` + +### 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 = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC contract address +const tokenBalance = await account.getTokenBalance(tokenContract) +console.log('USDC 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('0xSafeAddress...', { + 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...' + } +}) + +// 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 proposal = await account.propose({ + to: '0xRecipientAddress...', + value: '1000000000000000000', // 1 ETH in wei + data: '0x' // Optional transaction data +}) + +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) + +await bobAccount.approve(proposal.safeOperationHash) +console.log('Transaction approved by Bob') +``` + +### Execute a Transaction + +```javascript +// Execute when threshold is met +const result = await account.execute(proposal.safeOperationHash) + +console.log('Transaction hash:', result.hash) +console.log('Fee paid:', result.fee, 'paymaster token units') +``` + +### Get Pending Transactions + +```javascript +// Get all pending transactions for the Safe +const pendingTxs = await account.getPendingTransactions() + +for (const tx of pendingTxs) { + console.log('Hash:', tx.safeOperationHash) + console.log('Confirmations:', tx.confirmations.length) + console.log('Threshold:', tx.threshold) +} +``` + +## Paymaster Modes + +### ERC-20 Paymaster Mode (Default) + +Pay transaction fees using ERC-20 tokens (e.g., USDC): + +```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: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC + }, + safeAccountConfig: { + owners: ['0xAlice...', '0xBob...'], + threshold: 2 + } +}) +``` + +### 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', + sponsoredPaymaster: true // Enable sponsored mode + }, + safeAccountConfig: { + owners: ['0xAlice...', '0xBob...'], + threshold: 2 + } +}) +``` + +### 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' +}, { + sponsoredPaymaster: true // Override to sponsored mode +}) + +// Override to different ERC-20 token +const result2 = await account.sendTransaction({ + to: '0x...', + value: '1000000000000000000', + data: '0x' +}, { + paymasterTokenAddress: '0xDifferentToken...' // Use different token +}) +``` + +## Owner Management + +### Add an Owner + +```javascript +// Propose adding a new owner +const proposal = await account.proposeAddOwner({ + owner: '0xNewOwnerAddress...', + threshold: 2 // Optional: update threshold +}) + +// 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.proposeRemoveOwner({ + owner: '0xOwnerToRemove...', + threshold: 1 // Required: new threshold after removal +}) + +// 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.proposeSwapOwner({ + oldOwner: '0xOldOwnerAddress...', + newOwner: '0xNewOwnerAddress...' +}) + +// 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.proposeChangeThreshold({ + threshold: 3 // New threshold value +}) + +// Other owners approve and execute +await bobAccount.approve(proposal.safeOperationHash) +await account.execute(proposal.safeOperationHash) +``` + +## Message Signing + +### Sign a Message + +```javascript +// Sign a message with the multisig Safe +const message = 'Hello, Safe!' +const signature = await account.signMessage(message) + +console.log('Message signature:', signature) +``` + +### Verify a Message + +```javascript +// Verify a message signature +const isValid = await account.verifyMessage(message, signature) +console.log('Signature valid:', isValid) +``` + +## Transaction Tracking + +### Get Transaction History + +```javascript +// Get transaction history for the Safe +const history = await account.getTransactionHistory() + +for (const tx of history) { + console.log('Hash:', tx.hash) + console.log('Status:', tx.status) + console.log('Timestamp:', tx.timestamp) +} +``` + +## 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: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC + }, + safeAccountConfig: { + 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, 'USDC units') + + return { wallet, account, address, paymasterBalance } +} +``` + +### Complete Multisig Transaction Flow + +```javascript +async function multisigTransactionFlow() { + // Alice proposes a transaction + const proposal = await aliceAccount.propose({ + to: '0xRecipient...', + value: '1000000000000000000', // 1 ETH + data: '0x' + }) + + console.log('Proposal created:', proposal.safeOperationHash) + console.log('Current confirmations:', proposal.confirmations.length) + console.log('Required threshold:', proposal.threshold) + + // Bob approves + await bobAccount.approve(proposal.safeOperationHash) + console.log('Bob approved') + + // Check if ready to execute + const pendingTxs = await aliceAccount.getPendingTransactions() + const tx = pendingTxs.find(t => t.safeOperationHash === proposal.safeOperationHash) + + if (tx.confirmations.length >= tx.threshold) { + // Execute the transaction + const result = await aliceAccount.execute(proposal.safeOperationHash) + console.log('Transaction executed:', result.hash) + console.log('Fee paid:', result.fee) + } +} +``` + +--- + +### Need Help? + +**Discord Community** + +Connect with developers, ask questions, share your projects + +[Join Community](https://discord.gg/arYXDhHB2w) + +**GitHub Issues** + +Report bugs, request features, and get technical help + +[Open an Issue](https://github.com/tetherto/wdk-core) + +**Email Contact** + +For sensitive or private matters, contact our team directly + +[Send an email](mailto:support@tether.to) \ No newline at end of file From c07cb3c7b1573273c3b0b911f9231f747ef1d578 Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Tue, 6 Jan 2026 14:08:34 +0700 Subject: [PATCH 02/10] replace USDC by USDT and update readme acording change from multsig-safe project --- sdk/multisig-modules/README.md | 4 +- .../protocol-multisig-safe/api-reference.md | 479 ++++++++++++------ .../protocol-multisig-safe/configuration.md | 104 ++-- .../protocol-multisig-safe/usage.md | 303 ++++++++--- 4 files changed, 626 insertions(+), 264 deletions(-) diff --git a/sdk/multisig-modules/README.md b/sdk/multisig-modules/README.md index 270abe7..c3bb71a 100644 --- a/sdk/multisig-modules/README.md +++ b/sdk/multisig-modules/README.md @@ -29,13 +29,13 @@ All multisig modules share these core features: * **Threshold Configuration**: Set required approval count * **Propose/Approve/Execute**: Standard multisig workflow * **Transaction Tracking**: View pending and historical transactions -* **Message Signing**: Sign messages with multisig approval +* **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 +* **Gasless Transactions**: Pay fees in ERC-20 tokens (e.g., USDT) * **Sponsored Mode**: Completely gas-free transactions * **Bundled Operations**: Multiple approvals in single transaction diff --git a/sdk/multisig-modules/protocol-multisig-safe/api-reference.md b/sdk/multisig-modules/protocol-multisig-safe/api-reference.md index e88bbf7..f2325e8 100644 --- a/sdk/multisig-modules/protocol-multisig-safe/api-reference.md +++ b/sdk/multisig-modules/protocol-multisig-safe/api-reference.md @@ -23,12 +23,12 @@ new WalletManagerEvmMultisigSafe(seedPhrase, config) **Parameters:** * `seedPhrase` (string): BIP-39 mnemonic seed phrase -* `config` (MultisigSafeConfig): Configuration object +* `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 - * `safeAccountConfig` (SafeAccountConfig, optional): Safe creation or import config + * `options` (ExistingSafeOptions | PredictedSafeOptions): Safe creation or import config * `transferMaxFee` (number, optional): Maximum fee in paymaster token units ### Methods @@ -61,16 +61,6 @@ const account = await wallet.getAccountByPath(path) **Returns:** `Promise` -#### discoverExistingSafes - -Discovers existing Safe wallets where the signer's EOA is an owner. - -```javascript -const safes = await wallet.discoverExistingSafes() -``` - -**Returns:** `Promise` - Array of Safe addresses - #### dispose Disposes the wallet manager and clears sensitive data from memory. @@ -93,7 +83,7 @@ new WalletAccountEvmMultisigSafe(seedPhrase, path, config) * `seedPhrase` (string): BIP-39 mnemonic seed phrase * `path` (string): BIP-44 derivation path -* `config` (MultisigSafeConfig): Configuration object +* `config` (EvmMultisigSafeConfig): Configuration object ### Methods @@ -107,6 +97,16 @@ 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. @@ -141,6 +141,26 @@ 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. @@ -159,28 +179,28 @@ Deploys the Safe contract on-chain. Requires ETH in signer's EOA. const result = await account.deploy() ``` -**Returns:** `Promise<{ hash: string }>` - Deployment transaction hash +**Returns:** `Promise<{ deployed: boolean, txHash: string | null }>` - Deployment result #### propose Proposes a new transaction for multisig approval. ```javascript -const proposal = await account.propose(transaction, overrides?) +const proposal = await account.propose(transaction, options?) ``` **Parameters:** -* `transaction` (TransactionRequest): Transaction to propose +* `transaction` (EvmTransaction): Transaction to propose * `to` (string): Recipient address * `value` (string | bigint): Value in wei * `data` (string, optional): Transaction data -* `overrides` (PaymasterOverrides, optional): Override paymaster settings +* `options` (ProposeOptions, optional): Propose options including paymaster overrides -**Returns:** `Promise` +**Returns:** `Promise` * `safeOperationHash` (string): Unique operation identifier -* `confirmations` (string[]): Array of signer addresses who confirmed +* `confirmations` (number): Number of confirmations * `threshold` (number): Required confirmations to execute #### approve @@ -188,88 +208,140 @@ const proposal = await account.propose(transaction, overrides?) Approves a pending transaction. ```javascript -await account.approve(safeOperationHash) +const result = await account.approve(safeOperationHash) ``` **Parameters:** * `safeOperationHash` (string): Operation hash from propose() -**Returns:** `Promise` +**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, overrides?) +const result = await account.execute(safeOperationHash) ``` **Parameters:** * `safeOperationHash` (string): Operation hash from propose() -* `overrides` (PaymasterOverrides, optional): Override paymaster settings -**Returns:** `Promise<{ hash: string, fee: bigint }>` +**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. Use when you control enough signers. +Proposes, collects approvals, and executes in one call. Auto-executes if threshold is met. ```javascript -const result = await account.sendTransaction(transaction, overrides?) +const result = await account.sendTransaction(transaction, options?) ``` **Parameters:** -* `transaction` (TransactionRequest): Transaction to send -* `overrides` (PaymasterOverrides, optional): Override paymaster settings +* `transaction` (EvmTransaction): Transaction to send +* `options` (ProposeOptions, optional): Options including paymaster overrides + +**Returns:** `Promise` -**Returns:** `Promise<{ hash: string, fee: bigint }>` +* `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, overrides?) +const quote = await account.quoteSendTransaction(transaction, options?) ``` **Parameters:** -* `transaction` (TransactionRequest): Transaction to quote -* `overrides` (PaymasterOverrides, optional): Override paymaster settings +* `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. +Transfers ERC-20 tokens using the multisig workflow. Auto-executes if threshold is met. ```javascript -const result = await account.transfer(transferRequest, overrides?) +const result = await account.transfer(transferOptions, proposeOptions?) ``` **Parameters:** -* `transferRequest` (TransferRequest): Transfer details +* `transferOptions` (TransferOptions): Transfer details * `token` (string): ERC-20 token address * `recipient` (string): Recipient address - * `amount` (bigint): Amount in smallest units -* `overrides` (PaymasterOverrides, optional): Override paymaster settings + * `amount` (string | bigint): Amount in smallest units +* `proposeOptions` (ProposeOptions, optional): Options including paymaster overrides -**Returns:** `Promise<{ hash: string, fee: bigint }>` +**Returns:** `Promise` #### quoteTransfer Estimates the fee for a token transfer. ```javascript -const quote = await account.quoteTransfer(transferRequest, overrides?) +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. @@ -278,266 +350,385 @@ Gets all pending transactions awaiting approval or execution. const pending = await account.getPendingTransactions() ``` -**Returns:** `Promise` +**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() +const history = await account.getTransactionHistory(options?) ``` -**Returns:** `Promise` +**Returns:** `Promise` -#### proposeAddOwner +#### addOwner Proposes adding a new owner to the Safe. ```javascript -const proposal = await account.proposeAddOwner({ owner, threshold? }) +const proposal = await account.addOwner(ownerAddress, newThreshold?, options?) ``` **Parameters:** -* `owner` (string): New owner address -* `threshold` (number, optional): New threshold after adding +* `ownerAddress` (string): New owner address +* `newThreshold` (number, optional): New threshold after adding +* `options` (ProposeOptions, optional): Propose options -**Returns:** `Promise` +**Returns:** `Promise` -#### proposeRemoveOwner +#### removeOwner Proposes removing an owner from the Safe. ```javascript -const proposal = await account.proposeRemoveOwner({ owner, threshold }) +const proposal = await account.removeOwner(ownerAddress, newThreshold?, options?) ``` **Parameters:** -* `owner` (string): Owner address to remove -* `threshold` (number): New threshold after removal (required) +* `ownerAddress` (string): Owner address to remove +* `newThreshold` (number, optional): New threshold after removal +* `options` (ProposeOptions, optional): Propose options -**Returns:** `Promise` +**Returns:** `Promise` -#### proposeSwapOwner +#### swapOwner Proposes swapping one owner for another. ```javascript -const proposal = await account.proposeSwapOwner({ oldOwner, newOwner }) +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` +**Returns:** `Promise` -#### proposeChangeThreshold +#### changeThreshold Proposes changing the approval threshold. ```javascript -const proposal = await account.proposeChangeThreshold({ threshold }) +const proposal = await account.changeThreshold(newThreshold, options?) ``` **Parameters:** -* `threshold` (number): New threshold value +* `newThreshold` (number): New threshold value +* `options` (ProposeOptions, optional): Propose options -**Returns:** `Promise` +**Returns:** `Promise` -#### signMessage +#### updateOwners -Signs a message with the multisig Safe. +Proposes batch updating owners and threshold. ```javascript -const signature = await account.signMessage(message) +const proposal = await account.updateOwners(newOwners, newThreshold, options?) ``` **Parameters:** -* `message` (string): Message to sign +* `newOwners` (string[]): New list of owner addresses +* `newThreshold` (number): New threshold value +* `options` (ProposeOptions, optional): Propose options -**Returns:** `Promise` - EIP-191 compliant signature +**Returns:** `Promise` -#### verifyMessage +#### sign -Verifies a message signature. +Signs a message with the multisig Safe. Proposes a new message or approves an existing one. ```javascript -const isValid = await account.verifyMessage(message, signature) +const result = await account.sign(message, options?) ``` **Parameters:** -* `message` (string): Original message -* `signature` (string): Signature to verify +* `message` (string): Message to sign +* `options` (SignOptions, optional): Sign options + * `isApproval` (boolean): If true, approve existing message; otherwise propose new -**Returns:** `Promise` +**Returns:** `Promise` -#### toReadOnlyAccount +* `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 -Converts to a read-only account. +#### verify + +Verifies a message signature using EIP-1271. ```javascript -const readOnly = await account.toReadOnlyAccount() +const isValid = await account.verify(message, signature) ``` -**Returns:** `Promise` - -#### dispose - -Disposes the account and clears sensitive data from memory. +**Parameters:** -```javascript -account.dispose() -``` +* `message` (string): Original message +* `signature` (string): Combined signature (preparedSignature from SafeMessage) -## WalletAccountReadOnlyEvmMultisigSafe +**Returns:** `Promise` - True if signature is valid -Read-only account for balance checks and queries without signing capabilities. +#### getMessage -### Constructor +Gets the status of a signed message. ```javascript -new WalletAccountReadOnlyEvmMultisigSafe(address, config) +const message = await account.getMessage(messageHash) ``` **Parameters:** -* `address` (string): Safe contract address -* `config` (MultisigSafeConfig): Configuration object (without safeAccountConfig) +* `messageHash` (string): Message hash -### Methods +**Returns:** `Promise` -#### getAddress +#### getPendingMessages -Returns the Safe contract address. +Gets all pending messages awaiting signatures. ```javascript -const address = await readOnlyAccount.getAddress() +const messages = await account.getPendingMessages() ``` -**Returns:** `Promise` +**Returns:** `Promise<{ results: SafeMessage[] }>` -#### getBalance +#### toReadOnlyAccount -Returns the native token balance in wei. +Converts to a read-only account. ```javascript -const balance = await readOnlyAccount.getBalance() +const readOnly = await account.toReadOnlyAccount() ``` -**Returns:** `Promise` +**Returns:** `Promise` -#### getTokenBalance +#### dispose -Returns the ERC-20 token balance. +Disposes the account and clears sensitive data from memory. ```javascript -const balance = await readOnlyAccount.getTokenBalance(tokenAddress) +account.dispose() ``` -**Returns:** `Promise` +## WalletAccountReadOnlyEvmMultisigSafe -#### getPaymasterTokenBalance +Read-only account for balance checks and queries without signing capabilities. + +### Static Methods + +#### getSafesByOwner -Returns the paymaster token balance. +Gets all Safe addresses owned by an EOA. ```javascript -const balance = await readOnlyAccount.getPaymasterTokenBalance() +const safes = await WalletAccountReadOnlyEvmMultisigSafe.getSafesByOwner(ownerAddress, config) ``` -**Returns:** `Promise` +**Parameters:** -#### isDeployed +* `ownerAddress` (string): EOA address to search +* `config` (SafesByOwnerConfig): Configuration with chainId + +**Returns:** `Promise` - Array of Safe addresses -Checks if the Safe contract is deployed. +#### getSafeInfo + +Gets Safe info without creating an instance. ```javascript -const deployed = await readOnlyAccount.isDeployed() +const info = await WalletAccountReadOnlyEvmMultisigSafe.getSafeInfo(safeAddress, config) ``` -**Returns:** `Promise` +**Parameters:** -#### getPendingTransactions +* `safeAddress` (string): Safe contract address +* `config` (SafesByOwnerConfig): Configuration with chainId + +**Returns:** `Promise` - Safe info including owners, threshold, version -Gets all pending transactions. +#### generateDeterministicSaltNonce + +Generates a deterministic salt nonce from owners and threshold. ```javascript -const pending = await readOnlyAccount.getPendingTransactions() +const saltNonce = WalletAccountReadOnlyEvmMultisigSafe.generateDeterministicSaltNonce(owners, threshold) ``` -**Returns:** `Promise` - -#### getTransactionHistory +**Returns:** `string` - Deterministic salt nonce -Gets the transaction history. +### Constructor ```javascript -const history = await readOnlyAccount.getTransactionHistory() +new WalletAccountReadOnlyEvmMultisigSafe(address, config) ``` -**Returns:** `Promise` +**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 -### MultisigSafeConfig +### EvmMultisigSafeConfig ```typescript -interface MultisigSafeConfig { +interface EvmMultisigSafeConfig { + // Required chainId: bigint - provider: string | Provider + provider: string | Eip1193Provider bundlerUrl: string - paymasterOptions: PaymasterOptions - safeAccountConfig?: SafeAccountConfig - transferMaxFee?: number + + // 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 -interface PaymasterOptions { +// ERC-20 Paymaster Mode +interface ERC20PaymasterOptions { paymasterUrl: string - paymasterTokenAddress?: string // For ERC-20 paymaster mode - sponsoredPaymaster?: boolean // For sponsored mode + 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 } ``` -### SafeAccountConfig +### SignOptions ```typescript -interface SafeAccountConfig { - owners?: string[] // For creating new Safe - threshold?: number // For creating new Safe - safeAddress?: string // For importing existing Safe +interface SignOptions { + isApproval?: boolean // If true, approve existing; otherwise propose new } ``` -### PaymasterOverrides +### SignResult ```typescript -interface PaymasterOverrides { - paymasterTokenAddress?: string // Override paymaster token - sponsoredPaymaster?: boolean // Override to sponsored mode - transferMaxFee?: number // Override max fee +interface SignResult { + signature: string // This owner's signature + safeMessage: SafeMessage // Full SafeMessage from API } ``` -### SafeOperationProposal +### SafeMessage + +Imported from `@safe-global/api-kit`: ```typescript -interface SafeOperationProposal { - safeOperationHash: string - confirmations: string[] - threshold: number - to: string - value: string - data: string +interface SafeMessage { + messageHash: string + message: string | EIP712TypedData + proposedBy: string + confirmations: Array<{ + owner: string + signature: string + }> + preparedSignature: string | null + created: string + modified: string + safe: string } ``` diff --git a/sdk/multisig-modules/protocol-multisig-safe/configuration.md b/sdk/multisig-modules/protocol-multisig-safe/configuration.md index fd78946..bd113d7 100644 --- a/sdk/multisig-modules/protocol-multisig-safe/configuration.md +++ b/sdk/multisig-modules/protocol-multisig-safe/configuration.md @@ -18,11 +18,11 @@ const config = { // Paymaster configuration paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC + paymasterTokenAddress: '0x...' // USDT or other supported token }, - // Safe configuration - safeAccountConfig: { + // Safe configuration - for creating new Safe + options: { owners: ['0xAliceEOA...', '0xBobEOA...'], threshold: 2 }, @@ -34,6 +34,11 @@ const config = { 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: @@ -51,20 +56,19 @@ const account = new WalletAccountEvmMultisigSafe( config // Same config as wallet manager ) -// Read-only account (transferMaxFee not needed) -const readOnlyAccount = new WalletAccountReadOnlyEvmMultisigSafe( - '0xSafeAddress...', // Safe contract address - { - 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: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' - } - // Note: safeAccountConfig not needed for read-only, address is provided directly +// 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 @@ -130,12 +134,13 @@ The `paymasterOptions` object configures how transaction fees are paid. #### ERC-20 Paymaster Mode -Pay fees using ERC-20 tokens: +Pay fees using ERC-20 tokens (e.g., USDT): ```javascript paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC + paymasterAddress: '0x...', // Optional: paymaster contract address + paymasterTokenAddress: '0x...' // USDT or other supported token } ``` @@ -146,45 +151,52 @@ Use a sponsored paymaster for gasless transactions: ```javascript paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - sponsoredPaymaster: true + paymasterAddress: '0x...', // Optional: needed for ERC-20 override + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy' // Optional: sponsorship policy ID } ``` -### Safe Account Configuration +### Safe Options -The `safeAccountConfig` object specifies how to create or import a Safe. +The `options` object specifies how to create or import a Safe. -#### Creating a New Safe +#### Creating a New Safe (PredictedSafeOptions) ```javascript -safeAccountConfig: { +options: { owners: ['0xAliceEOA...', '0xBobEOA...', '0xCharlieEOA...'], - threshold: 2 // 2-of-3 multisig + 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 +#### Importing an Existing Safe (ExistingSafeOptions) ```javascript -safeAccountConfig: { +options: { safeAddress: '0xExistingSafeAddress...' } ``` #### Discovering Existing Safes -Omit `safeAccountConfig` to discover 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 = { - 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...' + // ... other config + options: { + safeAddress: safes[0] } - // No safeAccountConfig - use wallet.discoverExistingSafes() } ``` @@ -193,10 +205,10 @@ const config = { The `transferMaxFee` option specifies the maximum fee amount for transfer operations in paymaster token units. ```javascript -// Maximum fee of 1 USDC (6 decimals) +// Maximum fee of 1 USDT (6 decimals) transferMaxFee: 1000000 -// Maximum fee of 0.1 USDC +// Maximum fee of 0.1 USDT transferMaxFee: 100000 ``` @@ -211,9 +223,9 @@ const ethereumMainnetConfig = { bundlerUrl: 'https://api.pimlico.io/v2/ethereum/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/ethereum/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC + paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens }, - safeAccountConfig: { + options: { owners: ['0xOwner1...', '0xOwner2...'], threshold: 2 } @@ -229,9 +241,9 @@ const polygonMainnetConfig = { bundlerUrl: 'https://api.pimlico.io/v2/polygon/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/polygon/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359' // USDC + paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens }, - safeAccountConfig: { + options: { owners: ['0xOwner1...', '0xOwner2...'], threshold: 2 } @@ -247,9 +259,9 @@ const arbitrumOneConfig = { bundlerUrl: 'https://api.pimlico.io/v2/arbitrum/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/arbitrum/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' // USDC + paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens }, - safeAccountConfig: { + options: { owners: ['0xOwner1...', '0xOwner2...'], threshold: 2 } @@ -265,16 +277,18 @@ const sepoliaTestnetConfig = { bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC testnet + paymasterTokenAddress: '0x...' // See Pimlico docs for supported tokens }, - safeAccountConfig: { + options: { owners: ['0xOwner1...', '0xOwner2...'], threshold: 2 }, - transferMaxFee: 1000000 // 1 USDC max fee + 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? diff --git a/sdk/multisig-modules/protocol-multisig-safe/usage.md b/sdk/multisig-modules/protocol-multisig-safe/usage.md index f0232dc..e07e35d 100644 --- a/sdk/multisig-modules/protocol-multisig-safe/usage.md +++ b/sdk/multisig-modules/protocol-multisig-safe/usage.md @@ -10,6 +10,11 @@ To install the `@tetherto/wdk-protocol-multisig-safe` package, follow these inst 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` @@ -36,9 +41,9 @@ const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, { bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC on Sepolia + paymasterTokenAddress: '0x...' // USDT or other supported token }, - safeAccountConfig: { + options: { owners: ['0xAliceEOA...', '0xBobEOA...'], threshold: 2 }, @@ -54,23 +59,27 @@ console.log('Safe address:', safeAddress) ### Discovering Existing Safes ```javascript -import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe' - -// Create wallet manager without safeAccountConfig to discover existing Safes -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...' - } -}) +import { WalletAccountReadOnlyEvmMultisigSafe } from '@tetherto/wdk-protocol-multisig-safe' // Discover Safes where your EOA is an owner -const existingSafes = await wallet.discoverExistingSafes() +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 @@ -78,7 +87,7 @@ console.log('Found Safes:', existingSafes) ```javascript import WalletManagerEvmMultisigSafe from '@tetherto/wdk-protocol-multisig-safe' -// Import an existing Safe by address +// Import an existing Safe by address using ExistingSafeOptions const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, { chainId: 11155111n, provider: 'https://sepolia.infura.io/v3/YOUR_KEY', @@ -87,7 +96,7 @@ const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', paymasterTokenAddress: '0x...' }, - safeAccountConfig: { + options: { safeAddress: '0xExistingSafeAddress...' // Import by address } }) @@ -103,6 +112,10 @@ console.log('Imported Safe:', address) // 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) @@ -110,8 +123,12 @@ console.log('Is deployed:', isDeployed) // Deploy the Safe (if not already deployed) if (!isDeployed) { const deployResult = await account.deploy() - console.log('Deploy transaction hash:', deployResult.hash) + 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 @@ -150,9 +167,9 @@ const balance = await account.getBalance() console.log('Native balance:', balance, 'wei') // 1 ETH = 1000000000000000000 wei // Get ERC20 token balance -const tokenContract = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC contract address +const tokenContract = '0x...' // Token contract address const tokenBalance = await account.getTokenBalance(tokenContract) -console.log('USDC balance:', tokenBalance) +console.log('Token balance:', tokenBalance) // Get paymaster token balance (for paying fees) const paymasterBalance = await account.getPaymasterTokenBalance() @@ -167,13 +184,16 @@ For addresses where you don't have the seed phrase: import { WalletAccountReadOnlyEvmMultisigSafe } from '@tetherto/wdk-protocol-multisig-safe' // Create a read-only account -const readOnlyAccount = new WalletAccountReadOnlyEvmMultisigSafe('0xSafeAddress...', { +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...' } }) @@ -192,10 +212,20 @@ console.log('Token balance:', tokenBalance) ```javascript // Propose a transaction (first signer) -const proposal = await account.propose({ +const quote = await account.quoteSendTransaction({ to: '0xRecipientAddress...', value: '1000000000000000000', // 1 ETH in wei - data: '0x' // Optional transaction data + 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) @@ -209,30 +239,39 @@ console.log('Threshold:', proposal.threshold) // Second signer approves the transaction const bobAccount = new WalletAccountEvmMultisigSafe(bobSeedPhrase, "0'/0/0", config) -await bobAccount.approve(proposal.safeOperationHash) +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 -const result = await account.execute(proposal.safeOperationHash) +if (isReady) { + const result = await account.execute(proposal.safeOperationHash) + console.log('UserOp hash:', result.hash) -console.log('Transaction hash:', result.hash) -console.log('Fee paid:', result.fee, 'paymaster token units') + // 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 pendingTxs = await account.getPendingTransactions() +const pending = await account.getPendingTransactions() +const threshold = await account.getThreshold() -for (const tx of pendingTxs) { - console.log('Hash:', tx.safeOperationHash) - console.log('Confirmations:', tx.confirmations.length) - console.log('Threshold:', tx.threshold) +for (const op of pending.results) { + console.log('Hash:', op.safeOperationHash) + console.log('Confirmations:', op.confirmations?.length || 0, '/', threshold) } ``` @@ -240,7 +279,7 @@ for (const tx of pendingTxs) { ### ERC-20 Paymaster Mode (Default) -Pay transaction fees using ERC-20 tokens (e.g., USDC): +Pay transaction fees using ERC-20 tokens (e.g., USDT): ```javascript const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, { @@ -249,13 +288,19 @@ const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, { bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC + paymasterTokenAddress: '0x...' // USDT or other supported token }, - safeAccountConfig: { + 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 @@ -269,13 +314,18 @@ const wallet = new WalletManagerEvmMultisigSafe(seedPhrase, { bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - sponsoredPaymaster: true // Enable sponsored mode + paymasterAddress: '0x...', // Needed for ERC-20 override + isSponsored: true, + sponsorshipPolicyId: 'sp_my_policy' // Optional }, - safeAccountConfig: { + options: { owners: ['0xAlice...', '0xBob...'], threshold: 2 } }) + +// No amountToApprove needed - sponsor pays gas! +const proposal = await account.propose(tx) ``` ### Per-Transaction Override @@ -292,7 +342,7 @@ const result = await account.sendTransaction({ value: '1000000000000000000', data: '0x' }, { - sponsoredPaymaster: true // Override to sponsored mode + isSponsored: true // Override to sponsored mode }) // Override to different ERC-20 token @@ -301,6 +351,7 @@ const result2 = await account.sendTransaction({ value: '1000000000000000000', data: '0x' }, { + isSponsored: false, paymasterTokenAddress: '0xDifferentToken...' // Use different token }) ``` @@ -311,9 +362,8 @@ const result2 = await account.sendTransaction({ ```javascript // Propose adding a new owner -const proposal = await account.proposeAddOwner({ - owner: '0xNewOwnerAddress...', - threshold: 2 // Optional: update threshold +const proposal = await account.addOwner('0xNewOwnerAddress...', null, { + amountToApprove: fee * 200n / 100n }) // Other owners approve @@ -327,9 +377,8 @@ await account.execute(proposal.safeOperationHash) ```javascript // Propose removing an owner -const proposal = await account.proposeRemoveOwner({ - owner: '0xOwnerToRemove...', - threshold: 1 // Required: new threshold after removal +const proposal = await account.removeOwner('0xOwnerToRemove...', newThreshold, { + amountToApprove: fee * 200n / 100n }) // Other owners approve and execute @@ -341,9 +390,8 @@ await account.execute(proposal.safeOperationHash) ```javascript // Propose swapping an owner -const proposal = await account.proposeSwapOwner({ - oldOwner: '0xOldOwnerAddress...', - newOwner: '0xNewOwnerAddress...' +const proposal = await account.swapOwner('0xOldOwner...', '0xNewOwner...', { + amountToApprove: fee * 200n / 100n }) // Other owners approve and execute @@ -355,8 +403,8 @@ await account.execute(proposal.safeOperationHash) ```javascript // Propose changing the threshold -const proposal = await account.proposeChangeThreshold({ - threshold: 3 // New threshold value +const proposal = await account.changeThreshold(newThreshold, { + amountToApprove: fee * 200n / 100n }) // Other owners approve and execute @@ -364,24 +412,85 @@ 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 +### Sign a Message (Propose) ```javascript -// Sign a message with the multisig Safe -const message = 'Hello, Safe!' -const signature = await account.signMessage(message) +// Alice signs (proposes) a message +const result = await alice.sign('Hello from Safe!') -console.log('Message signature:', signature) +console.log('Alice signature:', result.signature) +console.log('Message hash:', result.safeMessage.messageHash) +console.log('Confirmations:', result.safeMessage.confirmations.length) ``` -### Verify a Message +### Sign a Message (Approve) ```javascript -// Verify a message signature -const isValid = await account.verifyMessage(message, signature) -console.log('Signature valid:', isValid) +// 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 @@ -392,13 +501,24 @@ console.log('Signature valid:', isValid) // Get transaction history for the Safe const history = await account.getTransactionHistory() -for (const tx of history) { - console.log('Hash:', tx.hash) - console.log('Status:', tx.status) - console.log('Timestamp:', tx.timestamp) +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 @@ -426,9 +546,9 @@ async function setupMultisigWallet() { bundlerUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', paymasterOptions: { paymasterUrl: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY', - paymasterTokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // USDC + paymasterTokenAddress: '0x...' // USDT or other supported token }, - safeAccountConfig: { + options: { owners: ['0xAliceEOA...', '0xBobEOA...'], threshold: 2 }, @@ -445,7 +565,7 @@ async function setupMultisigWallet() { console.log('Native balance:', nativeBalance, 'wei') const paymasterBalance = await account.getPaymasterTokenBalance() - console.log('Paymaster token balance:', paymasterBalance, 'USDC units') + console.log('Paymaster token balance:', paymasterBalance, 'units') return { wallet, account, address, paymasterBalance } } @@ -455,30 +575,67 @@ async function setupMultisigWallet() { ```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', // 1 ETH + value: '1000000000000000000', data: '0x' + }, { + amountToApprove: quote.fee * 150n / 100n }) console.log('Proposal created:', proposal.safeOperationHash) - console.log('Current confirmations:', proposal.confirmations.length) - console.log('Required threshold:', proposal.threshold) + console.log('Confirmations:', proposal.confirmations, '/', proposal.threshold) // Bob approves - await bobAccount.approve(proposal.safeOperationHash) + const approval = await bobAccount.approve(proposal.safeOperationHash) console.log('Bob approved') + console.log('Confirmations:', approval.confirmations, '/', approval.threshold) // Check if ready to execute - const pendingTxs = await aliceAccount.getPendingTransactions() - const tx = pendingTxs.find(t => t.safeOperationHash === proposal.safeOperationHash) + const isReady = await aliceAccount.isReadyToExecute(proposal.safeOperationHash) - if (tx.confirmations.length >= tx.threshold) { + if (isReady) { // Execute the transaction const result = await aliceAccount.execute(proposal.safeOperationHash) console.log('Transaction executed:', result.hash) - console.log('Fee paid:', result.fee) + + // 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) } } ``` From 054f01c74616c8de2c21d33b804376e2e9f59b2c Mon Sep 17 00:00:00 2001 From: Quoc Le Date: Tue, 6 Jan 2026 14:42:08 +0700 Subject: [PATCH 03/10] fixed multiplesig module in SUMMARY --- SUMMARY.md | 18 +++++++----------- sdk/get-started.md | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index a4e38df..e35dd40 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -71,20 +71,17 @@ * [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) - * [protocol-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) +* [Multisig Modules](sdk/multisig-modules/README.md) + * [protocol-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) + ## UI Kits * [React Native UI Kit](ui-kits/react-native-ui-kit/README.md) @@ -111,5 +108,4 @@ ## Resources * [Concepts](resources/concepts.md) -* [Showcase](resources/showcase.md) - +* [Showcase](resources/showcase.md) \ No newline at end of file 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 From aee68eabb33b922d62d9e4849f87feaeeae4fa35 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:43:13 +0700 Subject: [PATCH 04/10] Update sdk/multisig-modules/protocol-multisig-safe/api-reference.md Co-authored-by: Matteo Giardino --- .../protocol-multisig-safe/api-reference.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/sdk/multisig-modules/protocol-multisig-safe/api-reference.md b/sdk/multisig-modules/protocol-multisig-safe/api-reference.md index f2325e8..c0e06a9 100644 --- a/sdk/multisig-modules/protocol-multisig-safe/api-reference.md +++ b/sdk/multisig-modules/protocol-multisig-safe/api-reference.md @@ -760,20 +760,4 @@ try { ### Need Help? -**Discord Community** - -Connect with developers, ask questions, share your projects - -[Join Community](https://discord.gg/arYXDhHB2w) - -**GitHub Issues** - -Report bugs, request features, and get technical help - -[Open an Issue](https://github.com/tetherto/wdk-core) - -**Email Contact** - -For sensitive or private matters, contact our team directly - -[Send an email](mailto:support@tether.to) \ No newline at end of file +{% include "../../../.gitbook/includes/support-cards.md" %} \ No newline at end of file From a1fc30ded1068042565452a93e3d56ac95092ba2 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:43:20 +0700 Subject: [PATCH 05/10] Update sdk/multisig-modules/protocol-multisig-safe/README.md Co-authored-by: Matteo Giardino --- .../protocol-multisig-safe/README.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/sdk/multisig-modules/protocol-multisig-safe/README.md b/sdk/multisig-modules/protocol-multisig-safe/README.md index 7b94e5d..c1aa5cf 100644 --- a/sdk/multisig-modules/protocol-multisig-safe/README.md +++ b/sdk/multisig-modules/protocol-multisig-safe/README.md @@ -50,20 +50,4 @@ Get started with WDK's Multisig Safe usage ### Need Help? -**Discord Community** - -Connect with developers, ask questions, share your projects - -[Join Community](https://discord.gg/arYXDhHB2w) - -**GitHub Issues** - -Report bugs, request features, and get technical help - -[Open an Issue](https://github.com/tetherto/wdk-core) - -**Email Contact** - -For sensitive or private matters, contact our team directly - -[Send an email](mailto:support@tether.to) \ No newline at end of file +{% include "../../../.gitbook/includes/support-cards.md" %} \ No newline at end of file From 732ef9d1ae6e9b8cb663489d0eec01051b0b320c Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:43:27 +0700 Subject: [PATCH 06/10] Update sdk/multisig-modules/protocol-multisig-safe/configuration.md Co-authored-by: Matteo Giardino --- .../protocol-multisig-safe/configuration.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/sdk/multisig-modules/protocol-multisig-safe/configuration.md b/sdk/multisig-modules/protocol-multisig-safe/configuration.md index bd113d7..4084e77 100644 --- a/sdk/multisig-modules/protocol-multisig-safe/configuration.md +++ b/sdk/multisig-modules/protocol-multisig-safe/configuration.md @@ -293,20 +293,4 @@ For supported tokens on each network, see: [Pimlico ERC-20 Paymaster Supported T ### Need Help? -**Discord Community** - -Connect with developers, ask questions, share your projects - -[Join Community](https://discord.gg/arYXDhHB2w) - -**GitHub Issues** - -Report bugs, request features, and get technical help - -[Open an Issue](https://github.com/tetherto/wdk-core) - -**Email Contact** - -For sensitive or private matters, contact our team directly - -[Send an email](mailto:support@tether.to) \ No newline at end of file +{% include "../../../.gitbook/includes/support-cards.md" %} \ No newline at end of file From 6ff23e589755103108742888fb6a3bc46ffbc71a Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:43:35 +0700 Subject: [PATCH 07/10] Update sdk/multisig-modules/README.md Co-authored-by: Matteo Giardino --- sdk/multisig-modules/README.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/sdk/multisig-modules/README.md b/sdk/multisig-modules/README.md index c3bb71a..4f36f7c 100644 --- a/sdk/multisig-modules/README.md +++ b/sdk/multisig-modules/README.md @@ -65,20 +65,5 @@ Learn about key concepts like Account Abstraction ### Need Help? -**Discord Community** -Connect with developers, ask questions, share your projects - -[Join Community](https://discord.gg/arYXDhHB2w) - -**GitHub Issues** - -Report bugs, request features, and get technical help - -[Open an Issue](https://github.com/tetherto/wdk-core) - -**Email Contact** - -For sensitive or private matters, contact our team directly - -[Send an email](mailto:support@tether.to) \ No newline at end of file +{% include "../../.gitbook/includes/support-cards.md" %} \ No newline at end of file From d129bb3a0daeb92b79be21b2379bc9c158d526a3 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Tue, 6 Jan 2026 14:43:43 +0700 Subject: [PATCH 08/10] Update sdk/multisig-modules/protocol-multisig-safe/usage.md Co-authored-by: Matteo Giardino --- .../protocol-multisig-safe/usage.md | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/sdk/multisig-modules/protocol-multisig-safe/usage.md b/sdk/multisig-modules/protocol-multisig-safe/usage.md index e07e35d..5d5504d 100644 --- a/sdk/multisig-modules/protocol-multisig-safe/usage.md +++ b/sdk/multisig-modules/protocol-multisig-safe/usage.md @@ -644,20 +644,4 @@ async function messageSigningFlow() { ### Need Help? -**Discord Community** - -Connect with developers, ask questions, share your projects - -[Join Community](https://discord.gg/arYXDhHB2w) - -**GitHub Issues** - -Report bugs, request features, and get technical help - -[Open an Issue](https://github.com/tetherto/wdk-core) - -**Email Contact** - -For sensitive or private matters, contact our team directly - -[Send an email](mailto:support@tether.to) \ No newline at end of file +{% include "../../../.gitbook/includes/support-cards.md" %} \ No newline at end of file From 953da26fe6dc5b1260b0596a1532e8e3c6c5c934 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:46:56 +0700 Subject: [PATCH 09/10] Update sdk/multisig-modules/README.md Co-authored-by: Lucas Tortora <85233773+lucas-tortora@users.noreply.github.com> --- sdk/multisig-modules/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/multisig-modules/README.md b/sdk/multisig-modules/README.md index 4f36f7c..7405ba9 100644 --- a/sdk/multisig-modules/README.md +++ b/sdk/multisig-modules/README.md @@ -1,6 +1,6 @@ # Multisig Modules -Overview of WDK 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. From 6ab82b9bc863d78667ad49d7b8c6b33abea6ed20 Mon Sep 17 00:00:00 2001 From: Sunday <38057360+quocle108@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:47:17 +0700 Subject: [PATCH 10/10] Update sdk/multisig-modules/README.md Co-authored-by: Lucas Tortora <85233773+lucas-tortora@users.noreply.github.com> --- sdk/multisig-modules/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/multisig-modules/README.md b/sdk/multisig-modules/README.md index 7405ba9..1296afe 100644 --- a/sdk/multisig-modules/README.md +++ b/sdk/multisig-modules/README.md @@ -43,9 +43,9 @@ Modules with ERC-4337 support include: 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 +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