// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
// Take ownership of a wallet if a claim for it exists in a merkle root.
interface IMerkleWalletClaimer {
// Returns the address of the wallet factory.
function factory() external view returns (address);
// Returns the merkle root of the merkle tree containing wallets available to claim.
function merkleRoot() external view returns (bytes32);
// Returns true if the index has been marked claimed.
function isClaimed(uint256 index) external view returns (bool);
// Claim the wallet. Reverts if the inputs are invalid. Deploys the wallet if necessary using the initial signing key.
function claim(uint256 index, address wallet, address initialSigningKey, bytes calldata claimantSignature, bytes32[] calldata merkleProof) external;
// Claim the wallet on behalf of a specified owner.
function claimFor(address owner, uint256 index, address wallet, address initialSigningKey, bytes calldata claimantSignature, bytes32[] calldata merkleProof) external;
// This event is triggered whenever a call to #claim succeeds.
event Claimed(uint256 index, address wallet, address owner);
}
/**
* @dev Partial interface of the wallet factory.
*/
interface WalletFactory {
/**
* @dev Deploys a new wallet.
*/
function newSmartWallet(
address userSigningKey
) external returns (address wallet);
}
/**
* @dev Partial interface of the wallet.
*/
interface Wallet {
/**
* @dev Sets ownership on the wallet.
*/
function claimOwnership(address owner) external;
}
/**
* @dev These functions deal with verification of Merkle trees (hash trees),
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = _efficientHash(computedHash, proofElement);
} else {
// Hash(current element of the proof + current computed hash)
computedHash = _efficientHash(proofElement, computedHash);
}
}
// Check if the computed hash (root) is equal to the provided root
return computedHash == root;
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
library ECDSA {
function recover(
bytes32 hash, bytes memory signature
) internal pure returns (address) {
if (signature.length != 65) {
return (address(0));
}
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return address(0);
}
if (v != 27 && v != 28) {
return address(0);
}
return ecrecover(hash, v, r, s);
}
function toEthSignedMessageHash(address subject) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n20", subject));
}
}
/// @author 0age
contract MerkleWalletClaimer is IMerkleWalletClaimer {
using ECDSA for address;
using ECDSA for bytes32;
address public immutable override factory;
bytes32 public immutable override merkleRoot;
mapping (address => address[]) public claimedWalletsByOwner;
// This is a packed array of booleans.
mapping(uint256 => uint256) private claimedBitMap;
constructor(address factory_, bytes32 merkleRoot_) {
factory = factory_;
merkleRoot = merkleRoot_;
}
function claim(
uint256 index,
address wallet,
address initialSigningKey,
bytes calldata claimantSignature,
bytes32[] calldata merkleProof
) external override {
_claim(msg.sender, index, wallet, initialSigningKey, claimantSignature, merkleProof);
}
function claimFor(
address owner,
uint256 index,
address wallet,
address initialSigningKey,
bytes calldata claimantSignature,
bytes32[] calldata merkleProof
) external override {
_claim(owner, index, wallet, initialSigningKey, claimantSignature, merkleProof);
}
function isClaimed(uint256 index) public view override returns (bool) {
uint256 claimedWordIndex = index >> 8;
uint256 claimedBitIndex = index & 0xff;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask != 0;
}
function _setClaimed(uint256 index) private {
uint256 claimedWordIndex = index >> 8;
uint256 claimedBitIndex = index & 0xff;
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
}
function _deployIfNecessary(address wallet, address initialSigningKey) private {
// Deploy the wallet if necessary.
uint256 walletCode;
assembly { walletCode := extcodesize(wallet) }
if (walletCode == 0) {
WalletFactory(factory).newSmartWallet(initialSigningKey);
assembly { walletCode := extcodesize(wallet) }
require(
walletCode != 0,
'MerkleWalletClaimer: Invalid initial signing key supplied.'
);
}
}
function _claim(
address owner,
uint256 index,
address wallet,
address initialSigningKey,
bytes calldata claimantSignature,
bytes32[] calldata merkleProof
) private {
require(!isClaimed(index), 'MerkleWalletClaimer: Wallet already claimed.');
// Claimant signs an EIP-191 v0x45 message consisting of the designated owner.
bytes32 messageHash = owner.toEthSignedMessageHash();
// Recover the claimant from the signature and cast the type.
uint256 claimantKey = uint256(uint160(messageHash.recover(claimantSignature)));
// Verify the merkle proof.
bytes32 node = keccak256(abi.encodePacked(index, wallet, claimantKey));
require(
MerkleProof.verify(merkleProof, merkleRoot, node),
'MerkleWalletClaimer: Invalid proof.'
);
// Mark the wallet as claimed.
_setClaimed(index);
// Deploy the wallet if necessary.
_deployIfNecessary(wallet, initialSigningKey);
// Set the caller as the owner on the wallet.
Wallet(wallet).claimOwnership(owner);
// Track wallet as having been claimed by the owner and emit an event.
claimedWalletsByOwner[owner].push(wallet);
emit Claimed(index, wallet, owner);
}
}
{
"compilationTarget": {
"MerkleWalletClaimer.sol": "MerkleWalletClaimer"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"factory_","type":"address"},{"internalType":"bytes32","name":"merkleRoot_","type":"bytes32"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"Claimed","type":"event"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"initialSigningKey","type":"address"},{"internalType":"bytes","name":"claimantSignature","type":"bytes"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"initialSigningKey","type":"address"},{"internalType":"bytes","name":"claimantSignature","type":"bytes"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"claimFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"claimedWalletsByOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"isClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"merkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}]