A production-ready, gas-optimized ERC-4337 (Account Abstraction) multi-signature wallet implementation with token payment support. Built with Solidity and Foundry.
- ERC-4337 Account Abstraction - Full implementation of the account abstraction standard
- Multi-signature Wallet - Configurable threshold-based multi-sig with gas-optimized signature validation
- Token Payment Paymaster - Pay gas fees in ERC-20 tokens (DAI, USDC) using Chainlink price feeds
- Factory Pattern - CREATE2 support for deterministic wallet addresses
- Batch Execution - Execute multiple operations in a single transaction
- Signature Validity Period - Time-bound signatures (1 hour) for enhanced security
- Comprehensive Testing - 147+ tests with 80%+ coverage including unit, integration, fuzz, and invariant tests
- Foundry (latest version)
- Git
# Clone the repository git clone https://github.com/yourusername/safesocial.git cd safesocial # Install dependencies (submodules) git submodule update --init --recursive # Build contracts forge build # Run tests forge test# Deploy factory and paymaster forge script script/DeploySafeSocial.s.sol:DeploySafeSocial --rpc-url <RPC_URL> --broadcast --verify// Deploy via factory address[] memory owners = [owner1, owner2, owner3]; uint256 threshold = 2; address wallet = factory.deployWallet(owners, threshold); // Or deploy deterministically with CREATE2 bytes32 salt = keccak256("my-wallet-salt"); address wallet = factory.deployWalletDeterministic(owners, threshold, salt);// Sign UserOperation with required threshold signatures PackedUserOperation memory userOp = /* ... */; // Execute via EntryPoint entryPoint.handleOps(userOps, beneficiary);SafeSocial.sol- Main multi-signature wallet contract implementing ERC-4337IAccountSafeSocialFactory.sol- Factory for deploying wallets with CREATE/CREATE2SocialPaymaster.sol- Paymaster allowing token-based gas paymentsOwnersAndThreshouldManager.sol- Owner and threshold management logicTokenGasCalculator.sol- Library for calculating token amounts using Chainlink oracles
All contracts follow Solidity's standard structure:
- Type Declarations (Structs, Enums)
- State Variables
- Mappings
- Constants
- Events
- Errors
- Modifiers
- Constructor
- External Functions
- Public Functions
- Internal Functions
- Private Functions
- View Functions
SafeSocial implements several gas optimizations targeting the signature validation hot path. See GAS_OPTIMIZATION.md for detailed analysis.
- Calldata Signature Parsing - Direct
calldataloadassembly (~300-500 gas saved per signature) - O(1) Owner Lookup - Single mapping with index offset pattern (~40k+ gas saved per transaction for 5 sigs with 10 owners)
- Bitmap Duplicate Detection - O(1) duplicate checks vs O(n) array searches (~1k-2k gas saved)
- Early Exit - Stop validation once threshold is met (~1.5k-1.8k gas saved when extra sigs provided)
- Immutable Variables - EntryPoint and Factory embedded in bytecode (~2k gas saved per read)
- Custom Errors - ~50% gas savings on reverts vs string messages (~150 gas saved per revert)
- Unchecked Increments - Safe overflow skips in bounded loops (~100-150 gas saved)
- Batch Execution - Single validation for multiple operations (~13.5k-17k gas saved per batch of 3 ops)
- Library Pattern - Separated oracle logic for reusability and testability
- Factory-Only Deployment - Ensures paymaster integration and wallet tracking
- CEI Pattern - All functions follow Checks-Effects-Interactions for security
- Wallet Pays Gas - Wallet balance pays for gas in tokens (multisig-friendly)
Total Potential Savings: ~50,000-60,000+ gas per transaction for typical multi-sig operations (5 signatures, 10 owners).
- CEI Pattern - All state-changing functions follow Checks-Effects-Interactions
- Signature Validity Period - 1-hour time-bound signatures prevent replay attacks
- Input Validation - Comprehensive checks with custom errors
- Reentrancy Protection - EntryPoint guards + CEI pattern
- Factory Validation - Paymaster validates wallets are factory-deployed
- SafeERC20 - Handles non-standard ERC20 tokens safely
- EntryPoint-Only Execution: All execution functions (
execute,executeBatch,validateUserOp) are protected byonlyEntryPointmodifier - Self-Call Pattern: Owner management functions (
addOwner,removeOwner,updateThreshould) can only be called viaexecute(), ensuring multi-sig validation - Factory Validation: Paymaster validates wallets are deployed via factory to prevent unauthorized wallet usage
- Time-Bound Signatures: Signatures are valid for 1 hour (
SIGNATURE_VALIDITY_PERIOD) to prevent indefinite replay attacks - Duplicate Prevention: Bitmap-based duplicate detection prevents the same owner from signing multiple times
- Threshold Enforcement: Requires exactly threshold number of unique owner signatures
- ECDSA Recovery: Uses OpenZeppelin's
ECDSA.recoverwithtoEthSignedMessageHashfor secure signature verification
- Sequential Nonces: EntryPoint handles nonce validation and sequential ordering before calling
validateUserOp - Replay Protection: Invalid or reused nonces are rejected by EntryPoint before execution
- Stale Price Protection: Maximum price age of 10 hours (considering mostly stable tokens will be added for gas payments) (
MAX_PRICE_AGE) prevents using outdated prices - Zero/Negative Price Checks: Validates oracle prices are positive before calculations
- Oracle Validation: Checks oracle addresses are non-zero before querying
- Rounding Protection: Token amount calculations round up to ensure sufficient tokens are charged
- Zero Address Checks: All critical addresses (owners, EntryPoint, Factory, tokens) are validated
- Threshold Bounds: Threshold must be > 0 and <= owners.length
- Duplicate Owner Prevention: Constructor and
addOwnerprevent duplicate owners - Owner Removal Protection: Cannot remove owner if it would violate threshold requirement
- CEI Pattern: All functions follow Checks-Effects-Interactions pattern
- EntryPoint Guards: EntryPoint's built-in reentrancy protection (
tx.origin == msg.senderandmsg.sender.code.length == 0) - State Changes Before External Calls: Events emitted and state updated before any external interactions
- Owner Order Changes:
removeOwneruses swap-with-last-element pattern, which changes owner array order (documented behavior) - Threshold Validation: Cannot update threshold to invalid values (0 or > owners.length)
- Multi-Sig Required: All owner/threshold changes require threshold signatures via UserOperation
- Wallet Restriction: Owner can restrict/unrestrict specific wallets from using paymaster
- Token Support Validation: Only supported tokens (with valid oracle feeds) can be used
- Balance/Allowance Checks: Validates wallet has sufficient balance and approval before charging
- Factory Validation: Only factory-deployed wallets can use paymaster
- Owner Order Changes: Removing an owner changes the order of remaining owners (by design for gas efficiency)
- Signature Validity Window: 1-hour validity period may be too short for some use cases (can be adjusted)
- Oracle Dependency: Paymaster relies on Chainlink oracles; stale prices or oracle failures could temporarily disable token payments
- Factory Dependency: Direct wallet deployments won't work with paymaster (by design for security)
- No Upgradeability: Contracts are not upgradeable; changes require redeployment
- Centralization Risk: Paymaster owner has significant control (can restrict wallets, add/remove tokens)
- Multi-Sig Threshold: Use appropriate threshold (e.g., 2-of-3, 3-of-5) based on security requirements
- Owner Key Management: Store owner private keys securely; consider using hardware wallets
- Oracle Monitoring: Monitor Chainlink oracle feeds for staleness or failures
- Paymaster Funding: Ensure paymaster has sufficient ETH deposit in EntryPoint
- Regular Reviews: Review and update security practices as the ecosystem evolves
The project includes comprehensive test coverage:
- 147+ tests covering all functionality (core contracts coverage is 90%+)
- Unit tests for core contracts
- Integration tests for paymaster flows
- Fuzz tests for edge cases
- Invariant tests for critical properties
# Run all tests forge test # Run with verbosity forge test -vvv # Run specific test file forge test --match-path test/unit/SafeSocialTest.t.sol # Run with gas reporting forge test --gas-report # Run invariant tests forge test --match-path test/fuzz/**/*.t.sol# Generate coverage report forge coverage- GAS_OPTIMIZATION.md - Detailed gas optimization analysis and design decisions
- Foundry Book - Foundry documentation
- ERC-4337 Specification - Account Abstraction standard
safesocial/ βββ src/ β βββ SafeSocial.sol # Main wallet contract β βββ SafeSocialFactory.sol # Wallet deployment factory β βββ SocialPaymaster.sol # Token payment paymaster β βββ OwnersAndThreshouldManager.sol # Owner & threshold management β βββ interfaces/ # Contract interfaces β βββ libraries/ β βββ TokenGasCalculator.sol # Gas calculation library βββ test/ β βββ unit/ # Unit tests β βββ fuzz/ # Fuzz & invariant tests β βββ helpers/ β β βββ TestHelper.sol # Shared test utilities β βββ mocks/ # Mock contracts βββ script/ β βββ DeploySafeSocial.s.sol # Deployment script β βββ HelperConfig.s.sol # Configuration helper βββ GAS_OPTIMIZATION.md # Detailed optimization docs βββ README.md # This file forge buildforge testforge fmtforge snapshotforge script script/DeploySafeSocial.s.sol:DeploySafeSocial \ --rpc-url <RPC_URL> \ --private-key <PRIVATE_KEY> \ --broadcast \ --verifyanvil# Get contract bytecode cast code <CONTRACT_ADDRESS> --rpc-url <RPC_URL> # Call a view function cast call <CONTRACT_ADDRESS> "functionName(uint256)" <ARG> --rpc-url <RPC_URL> # Send a transaction cast send <CONTRACT_ADDRESS> "functionName(uint256)" <ARG> \ --rpc-url <RPC_URL> \ --private-key <PRIVATE_KEY>For detailed gas optimization analysis, see GAS_OPTIMIZATION.md.
| Optimization | Gas Saved | Impact |
|---|---|---|
| Calldata signature parsing | ~300-500 per sig | Critical (hot path) |
| Mapping-based owner lookup | ~40k+ (5 sigs, 10 owners) | Critical (hot path) |
| Bitmap duplicate detection | ~1k-2k | High |
| Early exit | ~1.5k-1.8k (when extra sigs) | Medium |
| Immutable variables | ~2k per read | Medium |
| Custom errors | ~150 per revert | Low |
| Batch execution | ~13.5k-17k (3 ops) | High (UX) |
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- ERC-4337 - Account Abstraction standard
- OpenZeppelin - Security libraries
- Foundry - Testing framework
- Chainlink - Price feeds
Built with β€οΈ using Foundry