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
- Creator
- 0xe805CCdF6F…6cF459c681
- 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