Smart Wallet

What is a Smart Wallet?

A Smart Wallet is a wallet contract deployed on the blockchain, designed to provide enhanced functionality and security beyond traditional wallets. Unlike regular wallets, which are typically controlled by a single private key, smart wallets operate through a smart contract. This makes them programmable, allowing for features like multi-signature support, spending limits, automated approvals, and upgradeable logic.

Benefits of Smart Wallets

  1. Enhanced Security: Smart wallets can require multiple signers (multi-sig) to authorize transactions, making it harder for hackers to steal funds.

  2. Programmability: You can set custom rules (like daily spending limits or requiring additional signatures for large transactions).

  3. Recovery Options: Smart wallets can include social recovery mechanisms, allowing users to regain access if they lose their keys.

  4. Interoperability with DeFi: Smart wallets can interact directly with DeFi platforms and other smart contracts, streamlining complex interactions like lending, staking, and swapping assets.

How is a Smart Wallet Different from a Regular Wallet?

Feature
Regular Wallets
Smart Wallets

Control

Single private key

Controlled by smart contract logic

Security

Based solely on private key

Can include multi-sig, time locks, etc.

Recovery Options

Limited (seed phrase)

Can have social recovery mechanisms

Programmability

Minimal

Highly programmable

Transaction Fees

User pays directly

Smart wallets can “batch” or sponsor fees

Building a Simple Smart Wallet

In this tutorial, we'll build a basic Smart Wallet contract that:

  1. Has an owner address.

  2. Can execute transactions (like transferring funds or interacting with other contracts).

  3. Allows upgrades using the UUPS Proxy Pattern.

Step 1: Import Libraries

Start by importing OpenZeppelin’s libraries to manage upgrades and cryptographic functions:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";

These imports will help us handle cryptographic verification, upgrades, and initialization.

Step 2: Define the Smart Wallet Contract

Here, we define a contract called SmartWallet that:

  1. Sets an owner address.

  2. Implements functions to execute transactions on behalf of the wallet.

  3. Includes a modifier that allows only the owner to execute sensitive functions.

contract SmartWallet is Initializable, UUPSUpgradeable {
    using ECDSA for bytes32;

    address public owner;

    event WalletInitialized(address indexed owner);
    event ExecutedTransaction(address indexed target, uint256 value, bytes data);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not the wallet owner");
        _;
    }

    function initialize(address _owner) public initializer {
        owner = _owner;
        emit WalletInitialized(_owner);
    }

    // Function to authorize upgrades (for UUPS pattern)
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    // Accept Ether deposits
    receive() external payable {}

    // Execute a transaction to a specified address
    function executeTransaction(
        address payable target,
        uint256 value,
        bytes calldata data
    ) external onlyOwner {
        (bool success, ) = target.call{value: value}(data);
        require(success, "Transaction failed");
        emit ExecutedTransaction(target, value, data);
    }
}

Step 3: Explanation of Key Components

  1. Owner Initialization:

    • We use the initialize function to set the wallet’s owner. This function is only called once when deploying the contract.

  2. Upgradability:

    • _authorizeUpgrade: Required by the UUPS Proxy Pattern, allowing only the owner to upgrade the logic contract.

  3. Execute Transaction:

    • The executeTransaction function lets the owner send Ether or interact with other contracts by specifying the target address, the amount of Ether, and the call data. The transaction only goes through if it’s signed by the owner.

  4. Ether Handling:

    • The receive function enables the contract to receive Ether directly, allowing users to fund their smart wallet by sending Ether to its address.

Step 4: Deploy the Contract

To deploy the contract on Remix (or a local environment like Hardhat):

  1. Paste the code above into Remix.

  2. Compile it.

  3. Deploy the contract, passing in the wallet owner's address to the initialize function.

  4. Verify that the wallet’s owner was set correctly.

Step 5: Interact with the Wallet

Once the contract is deployed, you can:

  1. Deposit Ether: Send Ether directly to the contract address.

  2. Execute a Transaction:

    • Call executeTransaction, providing:

      • The target contract or wallet address you wish to interact with.

      • The value of Ether (in wei) to send with the transaction.

      • Any encoded function data to pass.

    If the transaction succeeds, you’ll see the ExecutedTransaction event.

Step 6: Upgrade the Wallet (UUPS Pattern)

To upgrade the contract using UUPS:

  1. Deploy a new version of the SmartWallet contract with any modifications.

  2. Call upgradeTo from the proxy contract, specifying the new implementation contract’s address.

This will point the proxy to the new implementation, allowing you to update functionality without affecting stored data.

This smart wallet setup provides the basics, but real-world smart wallets usually include:

  1. Multi-Sig Support: Requiring multiple keys to approve transactions.

  2. Spending Limits: To control daily or per-transaction spending.

  3. Recovery Mechanisms: Social recovery or key rotation for security.

Smart wallets are powerful tools for managing assets and interacting with the blockchain more securely and flexibly. With this foundational setup, you can experiment with additional features and refine your contract for real-world use cases.

Last updated