SocialFeeVault

0xb8abef28253972d411510cece814903dd130c4a9

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
4,247 bytes
ABI entries
50
11 read · 8 write
License
none

Contract information

Address
0xb8abef28253972d411510cece814903dd130c4a9
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x2e61758480…8b55420464

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (11)

ATTESTER_DELAY()uint256
CLAIM_TYPEHASH()bytes32
MAX_ATTESTATION_TTL()uint256
accrued(bytes32)uint256
attesterActivatableAt(address)uint256
eip712Domain()bytes1, string, string, uint256, address, bytes32, uint256[]
isAttester(address)bool
nonces(bytes32)uint256
owner()address
pendingOwner()address
renounceOwnership()

Events (9)

AttesterActivatedAttesterQueuedAttesterRemovedClaimedDepositedEIP712DomainChangedOwnershipTransferStartedOwnershipTransferredWalletClaimed

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "initialAttester",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyAttester",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadNonce",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeadlineExpired",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DeadlineTooFar",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ECDSAInvalidSignature",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "length",
        "type": "uint256"
      }
    ],
    "name": "ECDSAInvalidSignatureLength",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "s",
        "type": "bytes32"
      }
    ],
    "name": "ECDSAInvalidSignatureS",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidPlatform",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidShortString",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotAttester",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotQueued",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NothingAccrued",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "OwnershipRenounced",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "str",
        "type": "string"
      }
    ],
    "name": "StringTooLong",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TooEarly",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAmount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "attester",
        "type": "address"
      }
    ],
    "name": "AttesterActivated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "attester",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "activatableAt",
        "type": "uint256"
      }
    ],
    "name": "AttesterQueued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "attester",
        "type": "address"
      }
    ],
    "name": "AttesterRemoved",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "identityKey",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "claimer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "attester",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      }
    ],
    "name": "Claimed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "identityKey",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newAccrued",
        "type": "uint256"
      }
    ],
    "name": "Deposited",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "EIP712DomainChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "identityKey",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "WalletClaimed",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "ATTESTER_DELAY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CLAIM_TYPEHASH",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_ATTESTATION_TTL",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "identityKey",
        "type": "bytes32"
      }
    ],
    "name": "accrued",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "attester",
        "type": "address"
      }
    ],
    "name": "activateAttester",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "attesterActivatableAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "platform",
        "type": "uint8"
      },
      {
        "internalType": "bytes32",
        "name": "handleHash",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "claimer",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "nonce",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "deadline",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "signature",
        "type": "bytes"
      }
    ],
    "name": "claim",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address payable",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "claimWallet",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "identityKey",
        "type": "bytes32"
      }
    ],
    "name": "deposit",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eip712Domain",
    "outputs": [
      {
        "internalType": "bytes1",
        "name": "fields",
        "type": "bytes1"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "version",
        "type": "string"
      },
      {
        "internalType": "uint256",
        "name": "chainId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "verifyingContract",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "salt",
        "type": "bytes32"
      },
      {
        "internalType": "uint256[]",
        "name": "extensions",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "isAttester",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "identityKey",
        "type": "bytes32"
      }
    ],
    "name": "nonces",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "attester",
        "type": "address"
      }
    ],
    "name": "queueAttester",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "attester",
        "type": "address"
      }
    ],
    "name": "removeAttester",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

import {HandleLib} from "./libraries/HandleLib.sol";

/// @title SocialFeeVault
/// @notice Per-social-identity ETH escrow. A share of every mint.fast trade fee accrues here,
///         keyed by identityKey = keccak256(platform ‖ keccak256(handle)). The owner of the
///         X/GitHub account claims by presenting an EIP-712 attestation signed by a trusted
///         attester (issued off-chain after OAuth proof of account ownership).
///
/// Trust model / blast radius:
///  - The owner can manage the attester set (adds are 24h-timelocked) but can NEVER touch
///    escrowed ETH: no sweep, no withdraw, no pause on claims. A fully compromised
///    owner+attester can only redirect UNCLAIMED fee revenue, never curve reserves,
///    never user tokens, never already-claimed funds.
///  - Claim attestations must expire within MAX_ATTESTATION_TTL of the claim tx so a
///    stolen far-future signature cannot sit unredeemed indefinitely.
///  - Claims are single-use via a per-identity sequential nonce (latest attestation wins;
///    re-binding after a handle changes hands is by design).
///  - Operational note: keep the attester key in a multisig/HSM and monitor Claimed events.
contract SocialFeeVault is EIP712, Ownable2Step, ReentrancyGuard {
    // keccak256("SocialClaim(uint8 platform,bytes32 handleHash,address claimer,uint256 nonce,uint256 deadline)")
    bytes32 public constant CLAIM_TYPEHASH =
        keccak256("SocialClaim(uint8 platform,bytes32 handleHash,address claimer,uint256 nonce,uint256 deadline)");

    uint256 public constant ATTESTER_DELAY = 24 hours;
    /// @dev Hard ceiling on how far in the future a claim `deadline` may be. Caps the
    ///      lifetime of a stolen or mis-issued attestation even if the attester signs
    ///      a far-future expiry. The attester service already uses a shorter TTL.
    uint256 public constant MAX_ATTESTATION_TTL = 1 hours;

    mapping(bytes32 identityKey => uint256) public accrued;
    mapping(bytes32 identityKey => uint256) public nonces;
    mapping(address => bool) public isAttester;
    mapping(address => uint256) public attesterActivatableAt;

    event Deposited(bytes32 indexed identityKey, address indexed from, uint256 amount, uint256 newAccrued);
    event Claimed(
        bytes32 indexed identityKey, address indexed claimer, uint256 amount, address attester, uint256 nonce
    );
    event WalletClaimed(address indexed to, bytes32 indexed identityKey, uint256 amount);
    event AttesterQueued(address indexed attester, uint256 activatableAt);
    event AttesterActivated(address indexed attester);
    event AttesterRemoved(address indexed attester);

    error ZeroAmount();
    error ZeroAddress();
    error DeadlineExpired();
    error BadNonce();
    error NotAttester();
    error NothingAccrued();
    error TransferFailed();
    error NotQueued();
    error TooEarly();
    error AlreadyAttester();
    error DeadlineTooFar();
    error OwnershipRenounced();

    /// @param owner_ contract owner (attester management only)
    /// @param initialAttester first attester, active immediately (deploy-time bootstrap;
    ///        every later addition goes through the 24h timelock)
    constructor(address owner_, address initialAttester)
        EIP712("MintFastSocialFeeVault", "1")
        Ownable(owner_)
    {
        if (initialAttester == address(0)) revert ZeroAddress();
        isAttester[initialAttester] = true;
        emit AttesterActivated(initialAttester);
    }

    // ─── Escrow ────────────────────────────────────────────────────────────────

    /// @notice Accrue ETH to a social identity. Permissionless; the launchpad calls this
    ///         on every trade; anyone may gift an identity directly.
    function deposit(bytes32 identityKey) external payable {
        if (msg.value == 0) revert ZeroAmount();
        uint256 newAccrued = accrued[identityKey] + msg.value;
        accrued[identityKey] = newAccrued;
        emit Deposited(identityKey, msg.sender, msg.value, newAccrued);
    }

    /// @notice Sweep everything accrued to (platform, handleHash) to `claimer`, authorized
    ///         by an attester's EIP-712 signature. Callable by anyone (relayer-friendly);
    ///         the payout destination is inside the signed message.
    function claim(
        uint8 platform,
        bytes32 handleHash,
        address claimer,
        uint256 nonce,
        uint256 deadline,
        bytes calldata signature
    ) external nonReentrant {
        if (platform != HandleLib.PLATFORM_X && platform != HandleLib.PLATFORM_GITHUB) {
            revert HandleLib.InvalidPlatform();
        }
        if (claimer == address(0)) revert ZeroAddress();
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (deadline > block.timestamp + MAX_ATTESTATION_TTL) revert DeadlineTooFar();

        bytes32 identityKey = HandleLib.deriveIdentityKey(platform, handleHash);
        if (nonce != nonces[identityKey]) revert BadNonce();

        bytes32 digest = _hashTypedDataV4(
            keccak256(abi.encode(CLAIM_TYPEHASH, platform, handleHash, claimer, nonce, deadline))
        );
        // ECDSA.recover reverts on invalid/malleable signatures.
        address signer = ECDSA.recover(digest, signature);
        if (!isAttester[signer]) revert NotAttester();

        uint256 amount = accrued[identityKey];
        if (amount == 0) revert NothingAccrued();

        // Effects before interaction.
        nonces[identityKey] = nonce + 1;
        accrued[identityKey] = 0;

        (bool ok,) = claimer.call{value: amount}("");
        if (!ok) revert TransferFailed();

        emit Claimed(identityKey, claimer, amount, signer, nonce);
    }

    /// @notice Sweep everything accrued to a WALLET beneficiary to that wallet. No signature
    ///         needed. The payout destination is the key preimage, so it can only ever pay
    ///         `to`. Permissionless (relayer-friendly); a wallet claiming its own dev share
    ///         or a named wallet beneficiary both use this path.
    function claimWallet(address payable to) external nonReentrant {
        if (to == address(0)) revert ZeroAddress();
        bytes32 identityKey = HandleLib.deriveWalletKey(to);
        uint256 amount = accrued[identityKey];
        if (amount == 0) revert NothingAccrued();

        accrued[identityKey] = 0;

        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert TransferFailed();

        emit WalletClaimed(to, identityKey, amount);
    }

    // ─── Attester management (owner; adds timelocked) ─────────────────────────

    function queueAttester(address attester) external onlyOwner {
        if (attester == address(0)) revert ZeroAddress();
        if (isAttester[attester]) revert AlreadyAttester();
        uint256 activatableAt = block.timestamp + ATTESTER_DELAY;
        attesterActivatableAt[attester] = activatableAt;
        emit AttesterQueued(attester, activatableAt);
    }

    /// @notice Anyone may activate once the delay has passed (owner cannot stall silently).
    function activateAttester(address attester) external {
        uint256 activatableAt = attesterActivatableAt[attester];
        if (activatableAt == 0) revert NotQueued();
        if (block.timestamp < activatableAt) revert TooEarly();
        delete attesterActivatableAt[attester];
        isAttester[attester] = true;
        emit AttesterActivated(attester);
    }

    /// @notice Immediate removal. Removing an attester only shrinks attack surface.
    ///         Also cancels a queued (not yet activated) attester.
    function removeAttester(address attester) external onlyOwner {
        delete attesterActivatableAt[attester];
        if (isAttester[attester]) {
            isAttester[attester] = false;
        }
        emit AttesterRemoved(attester);
    }

    /// @dev Accidental renounce would permanently freeze attester management while
    ///      claims keep working, so disallow it so support can always rotate keys.
    function renounceOwnership() public pure override {
        revert OwnershipRenounced();
    }
}
Chain explorer3441msChain node97ms