SponsorRegistry

0x0000000000000000000000000000000000002619Verified
Native balance0USDX
Account typeSmart contract
NetworkTuven 99359

Overview

Contract name
SponsorRegistry
Verification
Verified source
Balance updated at block
12000

Verified contract source

Verified
Contract name
SponsorRegistry
Compiler
v0.8.29+commit.ab55807c
EVM version
prague
License
none
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// src/userland/SponsorRegistry.sol

/// @title SponsorRegistry
/// @notice Gas-policy registry: decides, per sender, whether gas is paid in a
///         sponsored ERC-20 instead of USDX, and which token / fee / recipient
///         applies.
///
///         The Arc EL handler reads this contract's storage slots DIRECTLY (not
///         via a CALL to `resolveGas`). This means the storage layout is FIXED:
///         adding or reordering state variables will silently break gas accounting.
///         `resolveGas` exists only as a convenience view for off-chain callers.
///
///         Model:
///           - A "gas plan" = {token, feePerTx, feeBeneficiary}. Multiple plans
///             coexist, so different SBTs can charge different tokens.
///           - Each authorized source (an SBT) is bound to one plan via
///             `sourcePlan`. Issuing the SBT sets the holder's `userPlan`; revoke
///             clears it. One plan per user (a later issue overwrites).
///           - Gas tokens must be ERC-20 whose `balanceOf` mapping is at storage
///             slot 0 (MintableERC20 from TokenFactory) so the handler can debit
///             any of them uniformly.
///           - A sponsored sender whose plan is unconfigured (token = 0) or who
///             lacks the fee ⇒ the handler REJECTS the tx (no USDX fallback).
///
/// @dev STORAGE LAYOUT IS FROZEN. The handler hardcodes slot offsets for
///      `multisig` (slot 0), `plans` (slot 1), `sourcePlan` (slot 2), and
///      `userPlan` (slot 3). Any upgrade via setCode MUST preserve this layout
///      exactly — inserting or reordering variables will corrupt gas accounting
///      without any on-chain error. `multisig` MUST stay at slot 0 additionally
///      because the genesis predeploy bootstraps it via `alloc.storage`.
contract SponsorRegistry {
    // --- slot 0 (genesis-bootstrapped) ---
    address public multisig;

    /// @notice A gas plan: charge `feePerTx` of `token` per tx, credited to `feeBeneficiary`.
    struct GasPlan {
        address token;
        uint256 feePerTx;
        address feeBeneficiary;
    }

    /// @notice planId → plan. planId 0 is reserved as "none".
    mapping(uint256 => GasPlan) public plans;
    /// @notice source (e.g. an SBT) → the planId it grants. 0 = not authorized.
    mapping(address => uint256) public sourcePlan;
    /// @notice holder → planId. 0 = not sponsored (pays USDX).
    mapping(address => uint256) public userPlan;

    event MultisigChanged(address indexed oldMs, address indexed newMs);
    event PlanSet(uint256 indexed planId, address token, uint256 feePerTx, address feeBeneficiary);
    event SourcePlanSet(address indexed source, uint256 indexed planId);
    event SponsoredSet(address indexed who, uint256 indexed planId);

    error NotMultisig();
    error NotAuthorizedSource();
    error ZeroAddress();
    error ZeroPlan();

    modifier onlyMultisig() {
        if (msg.sender != multisig) revert NotMultisig();
        _;
    }

    /// @dev When predeployed at genesis (no constructor runs), the `multisig` slot
    ///      must be set via the genesis `alloc.storage` for the registry address.
    constructor(address _multisig) {
        if (_multisig == address(0)) revert ZeroAddress();
        multisig = _multisig;
    }

    // ====================================================================
    // Source-facing (the only mutation a source/SBT performs)
    // ====================================================================

    /// @notice Place/clear a holder on the calling source's plan. Callable only by
    ///         an authorized source (one bound to a non-zero plan via setSourcePlan).
    function setSponsored(address who, bool on) external {
        uint256 plan = sourcePlan[msg.sender];
        if (plan == 0) revert NotAuthorizedSource();
        uint256 newPlan = on ? plan : 0;
        userPlan[who] = newPlan;
        emit SponsoredSet(who, newPlan);
    }

    // ====================================================================
    // Multisig admin
    // ====================================================================

    function setMultisig(address newMs) external onlyMultisig {
        if (newMs == address(0)) revert ZeroAddress();
        emit MultisigChanged(multisig, newMs);
        multisig = newMs;
    }

    /// @notice Configure a gas plan. `planId` must be non-zero; `token` and
    ///         `feeBeneficiary` non-zero (`feePerTx` may be 0 = free for that plan).
    function setPlan(uint256 planId, address token, uint256 feePerTx, address feeBeneficiary)
        external
        onlyMultisig
    {
        if (planId == 0) revert ZeroPlan();
        if (token == address(0) || feeBeneficiary == address(0)) revert ZeroAddress();
        plans[planId] = GasPlan(token, feePerTx, feeBeneficiary);
        emit PlanSet(planId, token, feePerTx, feeBeneficiary);
    }

    /// @notice Bind a source (e.g. an SBT) to a plan, authorizing it to sponsor
    ///         holders onto that plan. `planId = 0` de-authorizes the source. The
    ///         plan need not be configured yet — until it is, sponsored holders on
    ///         it are rejected by the handler.
    function setSourcePlan(address source, uint256 planId) external onlyMultisig {
        if (source == address(0)) revert ZeroAddress();
        sourcePlan[source] = planId;
        emit SourcePlanSet(source, planId);
    }

    // ====================================================================
    // Views
    // ====================================================================

    /// @notice True if the holder pays gas in a sponsored token (not USDX).
    function isSponsored(address who) external view returns (bool) {
        return userPlan[who] != 0;
    }

    /// @notice Gas-policy hook the Arc EL handler calls per transaction.
    /// @return sponsored    true → pay gas in `feeToken`; false → pay USDX normally.
    /// @return feeToken     ERC-20 to charge (0 if plan unconfigured → handler rejects).
    /// @return feeAmount    flat fee per tx, in feeToken's smallest units.
    /// @return feeRecipient where the fee is credited.
    function resolveGas(address sender)
        external
        view
        returns (bool sponsored, address feeToken, uint256 feeAmount, address feeRecipient)
    {
        uint256 planId = userPlan[sender];
        if (planId == 0) return (false, address(0), 0, address(0));
        GasPlan storage p = plans[planId];
        return (true, p.token, p.feePerTx, p.feeBeneficiary);
    }
}

Contract ABI
JSON
[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_multisig",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "NotAuthorizedSource",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotMultisig",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroPlan",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldMs",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newMs",
        "type": "address"
      }
    ],
    "name": "MultisigChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "planId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "feePerTx",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "feeBeneficiary",
        "type": "address"
      }
    ],
    "name": "PlanSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "source",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "planId",
        "type": "uint256"
      }
    ],
    "name": "SourcePlanSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "who",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "planId",
        "type": "uint256"
      }
    ],
    "name": "SponsoredSet",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "who",
        "type": "address"
      }
    ],
    "name": "isSponsored",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "multisig",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "plans",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "feePerTx",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "feeBeneficiary",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "resolveGas",
    "outputs": [
      {
        "internalType": "bool",
        "name": "sponsored",
        "type": "bool"
      },
      {
        "internalType": "address",
        "name": "feeToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "feeAmount",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "feeRecipient",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newMs",
        "type": "address"
      }
    ],
    "name": "setMultisig",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "planId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "feePerTx",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "feeBeneficiary",
        "type": "address"
      }
    ],
    "name": "setPlan",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "source",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "planId",
        "type": "uint256"
      }
    ],
    "name": "setSourcePlan",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "who",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "on",
        "type": "bool"
      }
    ],
    "name": "setSponsored",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "sourcePlan",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "userPlan",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

YOUR GATEWAY TO TUVEN

Connect to possibility.

Choose a wallet. Your assets stay in your control.

No wallet detected. On mobile, open this page in your wallet’s built-in browser.
Get MetaMask

Tuven · Chain 99359 · USDX