Inscriptions

0x98fefc530d4242b25d2209ee57348de8dfb064f0

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
12,576 bytes
ABI entries
49
20 read · 6 write
License
none

Contract information

Address
0x98fefc530d4242b25d2209ee57348de8dfb064f0
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xabe49d9f60…12f5d918b1

Token

Name
Symbol
Decimals
Holders
31

Read contract (20)

DEPLOY_FEE_WEI()uint256
MAX_SUPPLY()uint128
MINT_FEE_WEI()uint256
MINT_PER_CALL()uint128
RBNH_TICK()bytes4
ROYALTY_BPS()uint256
TOKEN_ID()uint256
balanceOf(address, uint256)uint256
balanceOfBatch(address[], uint256[])uint256[]
feeRecipient()address
getRemainingSupply(uint256)uint128
getTotalMinted(uint256)uint128
idToTick(uint256)bytes4
isApprovedForAll(address, address)bool
nextTokenId()uint256
royaltyInfo(uint256, uint256)address, uint256
supportsInterface(bytes4)bool
tickInfo(uint256)uint128, uint128, uint128, address, uint64
tickToId(bytes4)uint256
uri(uint256)string

Events (8)

ApprovalForAllDeployedInscriptionInscriptionTransferredMintedTransferBatchTransferSingleURI

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_feeRecipient",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AccountBalanceOverflow",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ArrayLengthsMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadParams",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BadRecipient",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientBalance",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InsufficientFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LowBalance",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotOwnerNorApproved",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Reentrancy",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SoldOut",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TickExists",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferToNonERC1155ReceiverImplementer",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferToZeroAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownTick",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "isApproved",
        "type": "bool"
      }
    ],
    "name": "ApprovalForAll",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes4",
        "name": "tick",
        "type": "bytes4"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "max",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "lim",
        "type": "uint128"
      }
    ],
    "name": "Deployed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "enum Op",
        "name": "op",
        "type": "uint8"
      },
      {
        "indexed": false,
        "internalType": "string",
        "name": "data",
        "type": "string"
      }
    ],
    "name": "Inscription",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes4",
        "name": "tick",
        "type": "bytes4"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "amount",
        "type": "uint128"
      }
    ],
    "name": "InscriptionTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes4",
        "name": "tick",
        "type": "bytes4"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "amount",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "totalMinted",
        "type": "uint128"
      }
    ],
    "name": "Minted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256[]",
        "name": "ids",
        "type": "uint256[]"
      },
      {
        "indexed": false,
        "internalType": "uint256[]",
        "name": "amounts",
        "type": "uint256[]"
      }
    ],
    "name": "TransferBatch",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "TransferSingle",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "string",
        "name": "value",
        "type": "string"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      }
    ],
    "name": "URI",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "DEPLOY_FEE_WEI",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_SUPPLY",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MINT_FEE_WEI",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MINT_PER_CALL",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "RBNH_TICK",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "ROYALTY_BPS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "TOKEN_ID",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "result",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "owners",
        "type": "address[]"
      },
      {
        "internalType": "uint256[]",
        "name": "ids",
        "type": "uint256[]"
      }
    ],
    "name": "balanceOfBatch",
    "outputs": [
      {
        "internalType": "uint256[]",
        "name": "balances",
        "type": "uint256[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "tick",
        "type": "bytes4"
      },
      {
        "internalType": "uint128",
        "name": "max",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "lim",
        "type": "uint128"
      }
    ],
    "name": "deploy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      }
    ],
    "name": "getRemainingSupply",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      }
    ],
    "name": "getTotalMinted",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "",
        "type": "uint128"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "idToTick",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      }
    ],
    "name": "isApprovedForAll",
    "outputs": [
      {
        "internalType": "bool",
        "name": "result",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "tick",
        "type": "bytes4"
      }
    ],
    "name": "mint",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "toMint",
        "type": "uint128"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextTokenId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "salePrice",
        "type": "uint256"
      }
    ],
    "name": "royaltyInfo",
    "outputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "royaltyAmount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256[]",
        "name": "ids",
        "type": "uint256[]"
      },
      {
        "internalType": "uint256[]",
        "name": "amounts",
        "type": "uint256[]"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "safeBatchTransferFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "safeTransferFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "isApproved",
        "type": "bool"
      }
    ],
    "name": "setApprovalForAll",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "tickInfo",
    "outputs": [
      {
        "internalType": "uint128",
        "name": "max",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "lim",
        "type": "uint128"
      },
      {
        "internalType": "uint128",
        "name": "minted",
        "type": "uint128"
      },
      {
        "internalType": "address",
        "name": "deployer",
        "type": "address"
      },
      {
        "internalType": "uint64",
        "name": "deployedAt",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "name": "tickToId",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "tick",
        "type": "bytes4"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint128",
        "name": "amt",
        "type": "uint128"
      }
    ],
    "name": "transfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      }
    ],
    "name": "uri",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import {ERC1155} from "solady/tokens/ERC1155.sol";
import {Base64} from "solady/utils/Base64.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";

// RBNH-20 inscriptions on Robinhood Chain — BRC-20-style fungible inscriptions
// (DEPLOY / MINT / TRANSFER over an ERC-1155 tick registry, on-chain SVG metadata).
// Each op emits a `data:`-URI event so an indexer can rebuild state from logs,
// while ERC-1155 balances stay live on-chain for the P2P Market.

enum Op {
    DEPLOY,
    MINT,
    TRANSFER
}

struct TickInfo {
    uint128 max;        // DEPLOY: max supply
    uint128 lim;        // DEPLOY: amount minted per mint() call
    uint128 minted;     // running total
    address deployer;
    uint64  deployedAt;
}

contract Inscriptions is ERC1155, ReentrancyGuard {
    using SafeTransferLib for address;

    // ── Constants ──────────────────────────────────────────────────────────

    bytes4  public constant RBNH_TICK     = bytes4("RBNH");
    uint128 public constant MAX_SUPPLY    = 21_000_000;
    uint128 public constant MINT_PER_CALL = 1_000;
    uint256 public constant TOKEN_ID      = 1;

    uint256 public constant MINT_FEE_WEI   = 0.000167 ether;
    uint256 public constant DEPLOY_FEE_WEI = 0.001 ether;
    uint256 public constant ROYALTY_BPS    = 250; // 2.5%

    bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a;

    // ── Immutables ─────────────────────────────────────────────────────────

    address public immutable feeRecipient;

    // ── Tick registry ──────────────────────────────────────────────────────

    mapping(bytes4  => uint256)  public tickToId;
    mapping(uint256 => bytes4)   public idToTick;
    mapping(uint256 => TickInfo) public tickInfo;
    uint256 public nextTokenId;

    // ── Events ─────────────────────────────────────────────────────────────

    event Deployed(bytes4 indexed tick, uint256 indexed tokenId, address indexed deployer, uint128 max, uint128 lim);
    event Minted(bytes4 indexed tick, uint256 indexed tokenId, address indexed to, uint128 amount, uint128 totalMinted);
    event InscriptionTransferred(bytes4 indexed tick, address indexed from, address indexed to, uint128 amount);

    // Raw inscription payload — lets an indexer rebuild state from logs alone.
    event Inscription(address indexed from, Op indexed op, string data);

    // ── Errors ─────────────────────────────────────────────────────────────

    error UnknownTick();
    error TickExists();
    error BadParams();
    error SoldOut();
    error InsufficientFee();
    error LowBalance();
    error BadRecipient();

    // ── Constructor ────────────────────────────────────────────────────────

    constructor(address _feeRecipient) {
        if (_feeRecipient == address(0)) revert BadParams();
        feeRecipient = _feeRecipient;

        // Pre-register RBNH as token ID 1 — the canonical tick for this launch.
        tickToId[RBNH_TICK] = TOKEN_ID;
        idToTick[TOKEN_ID]  = RBNH_TICK;
        tickInfo[TOKEN_ID] = TickInfo({
            max:        MAX_SUPPLY,
            lim:        MINT_PER_CALL,
            minted:     0,
            deployer:   msg.sender,
            deployedAt: uint64(block.timestamp)
        });
        nextTokenId = 2;

        emit Deployed(RBNH_TICK, TOKEN_ID, msg.sender, MAX_SUPPLY, MINT_PER_CALL);
    }

    // ── DEPLOY ─────────────────────────────────────────────────────────────

    /// @notice Register a new tick. Pays DEPLOY_FEE_WEI; excess is refunded.
    function deploy(bytes4 tick, uint128 max, uint128 lim)
        external
        payable
        nonReentrant
        returns (uint256 id)
    {
        if (msg.value < DEPLOY_FEE_WEI)         revert InsufficientFee();
        if (max == 0 || lim == 0 || lim > max) revert BadParams();
        if (tickToId[tick] != 0)               revert TickExists();

        id = nextTokenId++;
        tickToId[tick] = id;
        idToTick[id]   = tick;
        tickInfo[id] = TickInfo({
            max:        max,
            lim:        lim,
            minted:     0,
            deployer:   msg.sender,
            deployedAt: uint64(block.timestamp)
        });

        _payFee(DEPLOY_FEE_WEI);
        emit Deployed(tick, id, msg.sender, max, lim);
        emit Inscription(
            msg.sender,
            Op.DEPLOY,
            string(abi.encodePacked(
                'data:,{"p":"rbnh-20","op":"deploy","tick":"', _tickStr(tick),
                '","max":"', _u128(max), '","lim":"', _u128(lim), '"}'
            ))
        );
    }

    // ── MINT ───────────────────────────────────────────────────────────────

    /// @notice Mint `lim` of a tick to msg.sender. Pays MINT_FEE_WEI; excess refunded.
    function mint(bytes4 tick) external payable nonReentrant returns (uint128 toMint) {
        if (msg.value < MINT_FEE_WEI) revert InsufficientFee();

        uint256 id = tickToId[tick];
        if (id == 0) revert UnknownTick();

        TickInfo storage info = tickInfo[id];
        if (info.minted >= info.max) revert SoldOut();

        toMint = info.lim;
        if (info.minted + toMint > info.max) {
            toMint = info.max - info.minted;
        }

        info.minted += toMint;
        _mint(msg.sender, id, toMint, "");

        _payFee(MINT_FEE_WEI);
        emit Minted(tick, id, msg.sender, toMint, info.minted);
        emit Inscription(
            msg.sender,
            Op.MINT,
            string(abi.encodePacked(
                'data:,{"p":"rbnh-20","op":"mint","tick":"', _tickStr(tick),
                '","amt":"', _u128(toMint), '"}'
            ))
        );
    }

    // ── TRANSFER ───────────────────────────────────────────────────────────

    /// @notice Inscription-style transfer (no fee). Plain ERC-1155 safeTransferFrom
    ///         also works; this variant additionally emits the raw inscription log.
    function transfer(bytes4 tick, address to, uint128 amt) external nonReentrant {
        uint256 id = tickToId[tick];
        if (id == 0)                         revert UnknownTick();
        if (to == address(0) || to == msg.sender || amt == 0) revert BadRecipient();
        if (balanceOf(msg.sender, id) < amt) revert LowBalance();

        _safeTransfer(msg.sender, to, id, uint256(amt), "");

        emit InscriptionTransferred(tick, msg.sender, to, amt);
        emit Inscription(
            msg.sender,
            Op.TRANSFER,
            string(abi.encodePacked(
                'data:,{"p":"rbnh-20","op":"transfer","tick":"', _tickStr(tick),
                '","amt":"', _u128(amt), '","to":"', _addr(to), '"}'
            ))
        );
    }

    // ── Views ──────────────────────────────────────────────────────────────

    function getTotalMinted(uint256 id) external view returns (uint128) {
        return tickInfo[id].minted;
    }

    function getRemainingSupply(uint256 id) external view returns (uint128) {
        TickInfo memory info = tickInfo[id];
        return info.max - info.minted;
    }

    // ── ERC-1155 metadata (on-chain SVG) ────────────────────────────────────

    function uri(uint256 id) public view override returns (string memory) {
        TickInfo memory info = tickInfo[id];
        bytes4 tick = idToTick[id];

        string memory tickStr   = _tickStr(tick);
        string memory mintedStr = _u128(info.minted);
        string memory maxStr    = _u128(info.max);
        string memory limStr    = _u128(info.lim);

        string memory svg = string(abi.encodePacked(
            '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400">',
            '<rect width="400" height="400" fill="#000"/>',
            '<text x="200" y="160" text-anchor="middle" font-family="monospace" ',
            'font-size="64" font-weight="bold" fill="#fff">', tickStr, '</text>',
            '<text x="200" y="230" text-anchor="middle" font-family="monospace" ',
            'font-size="18" fill="#888">inscription</text>',
            '<text x="200" y="280" text-anchor="middle" font-family="monospace" ',
            'font-size="16" fill="#fff">', mintedStr, ' / ', maxStr, '</text>',
            '<text x="200" y="320" text-anchor="middle" font-family="monospace" ',
            'font-size="13" fill="#00C805">Robinhood Chain inscription</text>',
            '</svg>'
        ));

        string memory json = string(abi.encodePacked(
            '{"name":"RBNH Inscription #', mintedStr,
            '","description":"RBNH-20 BRC-20-style fungible inscription on Robinhood Chain (Arbitrum Orbit L2).",',
            '"image":"data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '",',
            '"attributes":[',
            '{"trait_type":"Standard","value":"rbnh-20"},',
            '{"trait_type":"Tick","value":"', tickStr, '"},',
            '{"trait_type":"Minted","value":', mintedStr, '},',
            '{"trait_type":"Max Supply","value":', maxStr, '},',
            '{"trait_type":"Mint Per Call","value":"', limStr, '"}',
            ']}'
        ));

        return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json))));
    }

    // ── ERC-2981 ───────────────────────────────────────────────────────────

    function royaltyInfo(uint256, uint256 salePrice)
        external view returns (address receiver, uint256 royaltyAmount)
    {
        receiver      = feeRecipient;
        royaltyAmount = salePrice * ROYALTY_BPS / 10_000;
    }

    function supportsInterface(bytes4 interfaceId) public view override returns (bool) {
        return super.supportsInterface(interfaceId) || interfaceId == _INTERFACE_ID_ERC2981;
    }

    // ── Internal ───────────────────────────────────────────────────────────

    /// @dev Forward the protocol fee and refund any overpayment to the caller.
    function _payFee(uint256 fee) private {
        feeRecipient.forceSafeTransferETH(fee);
        uint256 refund = msg.value - fee;
        if (refund > 0) msg.sender.forceSafeTransferETH(refund);
    }

    function _tickStr(bytes4 b) private pure returns (string memory) {
        uint256 len = 4;
        while (len > 0 && b[len - 1] == 0) len--;
        bytes memory trimmed = new bytes(len);
        for (uint256 i = 0; i < len; i++) trimmed[i] = b[i];
        return string(trimmed);
    }

    function _u128(uint128 v) private pure returns (string memory) {
        if (v == 0) return "0";
        uint128 temp = v;
        uint256 digits;
        while (temp != 0) { digits++; temp /= 10; }
        bytes memory buf = new bytes(digits);
        while (v != 0) {
            digits--;
            buf[digits] = bytes1(uint8(48 + uint256(v % 10)));
            v /= 10;
        }
        return string(buf);
    }

    function _addr(address a) private pure returns (string memory) {
        bytes16 hexd = "0123456789abcdef";
        bytes memory s = new bytes(42);
        s[0] = "0"; s[1] = "x";
        for (uint256 i = 0; i < 20; i++) {
            uint8 b = uint8(uint160(a) >> (8 * (19 - i)));
            s[2 + i * 2]     = hexd[b >> 4];
            s[2 + i * 2 + 1] = hexd[b & 0x0f];
        }
        return string(s);
    }
}
Chain explorer2326msChain node92ms