Skip to content
lalilulel0xPublic

About

A Standard for On-Chain Royalty Enforcement Through Deferred Payment Reconciliation

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

ERC721G: On-Chain Royalty Enforcement Through Deferred Payment

Experimental NFT standard for permissionless royalty enforcement

ERC721G solves the creator royalty problem by allowing transfers to execute optimistically while introducing economic consequences for unpaid royalties. Instead of blocking trades or whitelisting marketplaces, it uses a locked state that makes unpaid transfers economically undesirable.

════════════════════════════════════════════════════════════════════════════════
ABSTRACT
════════════════════════════════════════════════════════════════════════════════

ERC721G extends ERC-721 with guaranteed on-chain royalty enforcement. Unlike
ERC-2981 which is merely informational, or ERC721C which requires permissioned
marketplace whitelisting, ERC721G makes royalty payment a prerequisite for
token transferability—while remaining fully permissionless.

The standard introduces:
  • Deferred payment support (compatible with marketplace payment flows)
  • Dynamic floor pricing via GLUE Protocol collateral oracle
  • Per-token locking mechanism for unpaid royalties
  • transferFrom2: A new transfer primitive with native royalty enforcement
  • approve2: ERC-20-style count-based allowances for transferFrom2

════════════════════════════════════════════════════════════════════════════════

The Problem

  • ERC-2981 provides royalty information but no enforcement → marketplaces ignore it
  • Whitelist approaches (ERC-721C) enforce royalties but centralize control and fragment liquidity
  • Current reality: Creator royalties collapsed from universal to near-zero compliance

The Solution

ERC721G uses deferred payment reconciliation:

  1. Transfers execute freely - No blocking, no whitelisting
  2. Unpaid items lock - Can't create new listings until royalty paid
  3. Payment unlocks - ETH/WETH sent to contract unlocks items automatically
  4. Floor price royalties - Based on GLUE Protocol's verifiable backing, not market speculation
  5. Compounding backing - Royalties flow to GLUE → floor rises → future royalties increase

How It Works: The Two-Hook Architecture

ERC721G operates under a fundamental constraint: the NFT contract only observes two events - transferFrom() calls (when tokens move) and receive() calls (when ETH arrives). It cannot see marketplace transaction structure, payment timing, or coordination between calls.

This constraint forces a specific solution pattern:

On Transfer (transferFrom)

  • Transfer executes immediately (standard ERC-721 ownership change)
  • Token ID added to locked list
  • No payment verification attempted (contract can't know if/when payment arrives)
  • Marketplace listing approvals seized for locked tokens
  • Result: Buyer owns the NFT, but can't create new listings until unlocked

On Payment (receive)

  • Native ETH arrives at contract
  • Contract checks both ETH and WETH balances (WETH tracked via balance checks)
  • LIFO unlocking: most recently locked tokens unlocked first
  • Handles same-transaction payments optimally
  • Works regardless of marketplace transaction structure
  • Result: Tokens unlock as payment is processed (ETH or WETH)

This execute-then-reconcile pattern works with any marketplace because it makes zero assumptions about transaction structure. Payment can arrive before transfer, after transfer in same transaction, in separate transaction, or never—the protocol handles all cases uniformly.

Why LIFO (Last In, First Out)?

When payment and transfer happen in the same transaction (most common case), the just-transferred token will be at the end of the locked list. LIFO unlocking processes it first, so the buyer receives an unlocked token even though the contract saw transfer and payment as separate events.

The Lock State: Precise Enforcement

A locked token is fully owned but has degraded liquidity:

What locked tokens CAN do:

  • ✅ Be owned and displayed in wallet
  • ✅ Be transferred by owner directly (gifts, wallet migrations)
  • ✅ Be burned to claim GLUE backing
  • ✅ Execute existing marketplace listings (if unlocked before lock occurred)
  • ✅ Participate in all non-marketplace functions

What locked tokens CANNOT do:

  • ❌ Create new marketplace listings (getApproved() returns address(0))
  • ❌ Be transferred via marketplace transferFrom (authorization fails on locked items)

This distinction is crucial: existing listings of unlocked items still work. The lock only prevents creating new listings, not executing existing ones. Composability is preserved.

Approval Seizure Mechanism

ERC721G uses a two-tier approval system:

External queries (getApproved(tokenId)):

  • Returns address(0) for locked tokens
  • Marketplaces see "not approved" and block listing creation
  • Returns actual approval for unlocked tokens

Internal checks (_getApprovedInternal(tokenId)):

  • Used during transferFrom execution
  • Returns actual stored approval regardless of lock state
  • Allows owner direct transfers to succeed

This split allows the protocol to block new listings while preserving owner rights and existing unlocked listings.

Economic Model

┌───────────────────────────────────────────────────────────────────────────────┐
│                          ROYALTY CALCULATION                                  │
├───────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  Floor Price Derivation:                                                      │
│  ──────────────────────────────────────────────────────────────────────────   │
│  floorPrice = GLUE.getCollateralByAmount(1, [ETH, WETH])                      │
│             = (ETH collateral per NFT) + (WETH collateral per NFT)            │
│             = max(calculated, 0.0000001 ETH)  // Minimum threshold            │
│                                                                               │
│  Minimum Royalty:                                                             │
│  ──────────────────────────────────────────────────────────────────────────   │
│  minRoyalty = floorPrice × totalRoyaltyBps / 10000                            │
│                                                                               │
│  Example: floorPrice = 0.1 ETH, totalRoyalty = 500 BPS (5%)                   │
│           minRoyalty = 0.1 ETH × 500 / 10000 = 0.005 ETH                      │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────┐
│                           FEE STRUCTURE                                       │
├───────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  Distribution Rewards:                                                        │
│  ──────────────────────────────────────────────────────────────────────────   │
│  • processRoyalties() caller:  0.05% (5 BPS) of distributed amount            │
│  • receive() auto-distribution: 0.05% (5 BPS) as unlock budget                │
│                                                                               │
│  Marketplace Incentive (transferFrom2):                                       │
│  ──────────────────────────────────────────────────────────────────────────   │
│  • 1.0% (100 BPS) of royalty payment → msg.sender (marketplace)               │
│  • 99.0% → royalty receivers                                                  │
│                                                                               │
│  BPS Reference:                                                               │
│  ──────────────────────────────────────────────────────────────────────────   │
│  10000 BPS = 100%  │  1000 BPS = 10%  │  100 BPS = 1%  │  10 BPS = 0.1%       │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────┐
│                        ROYALTY CONFIGURATION                                  │
├───────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  Receivers:                                                                   │
│  ──────────────────────────────────────────────────────────────────────────   │
│  • Index 0: GLUE (permanent, minimum 1 BPS = 0.01%)                           │
│  • Index 1-10: Custom receivers (optional)                                    │
│  • Maximum 11 total receivers                                                 │
│                                                                               │
│  Limits:                                                                      │
│  ──────────────────────────────────────────────────────────────────────────   │
│  • Maximum total royalty: 1000 BPS (10%)                                      │
│  • Minimum GLUE share: 1 BPS (0.01%)                                          │
│  • GLUE cannot be removed (permanent at index 0)                              │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘

Key Features

🔓 Deferred Payment Reconciliation

  • Transfers happen immediately, locking occurs after
  • Works with any marketplace transaction structure
  • Automatic unlock when payment arrives via receive()
  • LIFO optimization for same-transaction payments

🎯 GLUE Protocol Integration

  • Royalties calculated from verifiable floor price (GLUE backing ÷ supply)
  • Portion of royalties automatically sent to GLUE backing
  • Creates compounding floor price growth
  • Aligns creator revenue with holder value

⚡ transferFrom2() Evolution

  • Atomic royalty-paid transfers (no locking)
  • 1% marketplace reward for using this function
  • Allowance-based approval (approve2/batchTransferFrom2)
  • Better UX + economic incentive for adoption

🛡️ Security First

  • Flash loan protection against backing manipulation
  • Re-entrancy guards using transient storage (EIP-1153)
  • High-precision math (512-bit intermediate calculations)
  • LIFO unlocking for same-transaction payments

🎛️ Flexible Configuration

  • Up to 11 royalty receivers (GLUE + 10 custom)
  • Configurable royalty percentages
  • Exemption system for specific addresses
  • Emergency disable functionality

Installation

npm install @glue-finance/expansions-pack

Quick Start

Basic Implementation

import "@glue-finance/expansions-pack/contracts/blueprints/ERC721G.sol";

contract MyNFT is ERC721G {
    constructor(
        string memory name,
        string memory symbol,
        string memory contractURI,
        address weth,
        uint256 glueRoyaltyBps,
        address[] memory receivers,
        uint256[] memory sharesBps,
        bool royaltyActive
    ) ERC721G(
        name,
        symbol,
        contractURI,
        weth,
        glueRoyaltyBps,
        receivers,
        sharesBps,
        royaltyActive
    ) {
        // Your initialization
    }
    
    function mint(address to, uint256 tokenId) external {
        _mint(to, tokenId);
    }
}

Constructor Parameters

/**
 * @param name_ Collection name
 * @param symbol_ Collection symbol
 * @param contractURI_ ERC-7572 contract metadata URI
 * @param weth_ WETH contract address for this network
 * @param glueRoyaltyBps_ GLUE's royalty share in BPS (minimum 1 = 0.01%)
 * @param receivers_ Additional royalty receivers (excluding GLUE)
 * @param sharesBps_ Shares for additional receivers in BPS
 * @param royaltyActive_ Whether to enable royalty enforcement initially
 */

Configuration Example

// 5% total royalty: 1% to GLUE, 4% to artist
uint256 glueRoyaltyBps = 100;  // 1% to GLUE (required minimum)

address[] memory receivers = new address[](1);
receivers[0] = artistAddress;

uint256[] memory sharesBps = new uint256[](1);
sharesBps[0] = 400;  // 4% to artist

MyNFT nft = new MyNFT(
    "My Collection",
    "MYNFT",
    "ipfs://QmMetadata...",
    0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9, // WETH on Sepolia
    glueRoyaltyBps,
    receivers,
    sharesBps,
    true  // Enable royalty enforcement
);

Specification

┌───────────────────────────────────────────────────────────────────────────────┐
│                              INTERFACE                                        │
├───────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  CORE FUNCTIONS                                                               │
│  ───────────────────────────────────────────────────────────────────────────  │
│  transferFrom2(from, to, tokenId)      Royalty-enforced single transfer       │
│  batchTransferFrom2(from, to, ids[])   Royalty-enforced batch transfer        │
│  approve2(operator, amount)            ERC20-style count allowance            │
│  allowance2(owner, spender) → uint256  Query approve2 allowance               │
│                                                                               │
│  ROYALTY FUNCTIONS                                                            │
│  ───────────────────────────────────────────────────────────────────────────  │
│  processRoyalties()                    Distribute accumulated royalties       │
│  unlockItems()                         Pay to unlock caller's locked items    │
│  unlockItems(itemIds[])                Pay royalty to unlock specific items   │
│                                                                               │
│  VIEW FUNCTIONS                                                               │
│  ───────────────────────────────────────────────────────────────────────────  │
│  getMinimumRoyalty() → uint256         Minimum royalty per transfer           │
│  getImpliedFloorPrice() → uint256      Floor price from GLUE collateral       │
│  isItemLocked(id) → bool               Check if item is locked                │
│  isRoyaltyActive() → bool              Check if enforcement is enabled        │
│  getRoyaltyOwed(itemIds[])             Check which items locked + cost        │
│                                                                               │
│  EVENTS                                                                       │
│  ───────────────────────────────────────────────────────────────────────────  │
│  ItemLocked(itemId)                    Emitted when item becomes locked       │
│  ItemUnlocked(itemId)                  Emitted when item is unlocked          │
│  RoyaltyProcessed(...)                 Emitted on royalty distribution        │
│  Approval2(owner, operator, amount)    Emitted on approve2                    │
│                                                                               │
└───────────────────────────────────────────────────────────────────────────────┘

Transfer Flows

Standard Transfer (transferFrom)

transferFrom(from, to, tokenId)
         │
         ▼
┌────────────────────────────────────────────────┐
│ EXEMPTION CHECK                                │
│ Skip enforcement if:                           │
│   • Minting (from = 0)                         │
│   • Burning (to = 0/DEAD/GLUE)                 │
│   • Gift (msg.sender = owner)                  │
│   • Approved address                           │
│   • Enforcement disabled                       │
│   • transferFrom2 in progress                  │
└────────────────────────────────────────────────┘
         │ Not exempt
         ▼
┌────────────────────────────────────────────────┐
│ LOCK CHECK                                     │
│ If _locked[tokenId] == true → REVERT           │
│ Token locked from previous TX, must pay first  │
└────────────────────────────────────────────────┘
         │ Not locked
         ▼
┌────────────────────────────────────────────────┐
│ EXECUTE TRANSFER FIRST                         │
│ super._update() - token ownership changes      │
│ (Transfer happens BEFORE locking)              │
└────────────────────────────────────────────────┘
         │
         ▼
┌────────────────────────────────────────────────┐
│ ADD TO LOCKED LIST                             │
│ _locked[tokenId] = true                        │
│ _lockedIds.push(tokenId)  // LIFO priority     │
│ Counter assigned to new owner                  │
└────────────────────────────────────────────────┘
         │
         ▼
┌────────────────────────────────────────────────┐
│ PROCESS ROYALTY SPLIT                          │
│ Check for accumulated payments (ETH/WETH)      │
│ If sufficient: unlock this token (LIFO)        │
│ If TX ends without payment: LOCKED             │
└────────────────────────────────────────────────┘

Royalty-Paid Transfer (transferFrom2)

transferFrom2{value: royalty}(from, to, tokenId)
         │
         ▼
┌────────────────────────────────────────────────┐
│ CHECK msg.value >= getMinimumRoyalty()         │
│ CHECK approve2 allowance sufficient            │
│ CONSUME allowance (decrement by 1)             │
└────────────────────────────────────────────────┘
         │
         ▼
┌────────────────────────────────────────────────┐
│ SET _royaltyPaidTransferActive = true          │
│ Execute _transfer(from, to, tokenId)           │
│ SET _royaltyPaidTransferActive = false         │
│ Note: If already locked, STAYS locked          │
└────────────────────────────────────────────────┘
         │
         ▼
┌────────────────────────────────────────────────┐
│ DISTRIBUTE ROYALTY                             │
│ Marketplace reward: 1% → msg.sender            │
│ Remaining 99% → royalty receivers              │
│ Update GLUE collateral snapshot                │
└────────────────────────────────────────────────┘

Benefits: Token never locked, marketplace earns 1%, atomic

Receive Flow (ETH Payment)

receive() external payable
         │
         ▼
┌────────────────────────────────────────────────┐
│ 1. UNWRAP WETH                                 │
│    If WETH balance >= THRESHOLD, convert       │
└────────────────────────────────────────────────┘
         │
         ▼
┌────────────────────────────────────────────────┐
│ 2. DISTRIBUTE TO RECEIVERS                     │
│    executorReward = balance × 0.05%            │
│    distributable = balance - executorReward    │
│    For each receiver: proportional to BPS      │
└────────────────────────────────────────────────┘
         │
         ▼
┌────────────────────────────────────────────────┐
│ 3. _getGlueSnapshot() - ALL-IN-ONE             │
│    Query: currentCollateral (ETH+WETH per NFT) │
│    Query: currentSupply                        │
│    FLASH LOAN PROTECTION:                      │
│      If collateral↓ AND supply unchanged:      │
│        → Use old values (freeze snapshot)      │
│      Else: Update snapshot with new values     │
└────────────────────────────────────────────────┘
         │
         ▼
┌────────────────────────────────────────────────┐
│ 4. UNLOCK ITEMS (LIFO)                         │
│    available = distributed + excess            │
│    numToUnlock = available / minRoyalty        │
│    Pop from _lockedIds[] (recent first)        │
│    excess = leftover (accumulates)             │
└────────────────────────────────────────────────┘

Approval Seizure

Locked items have their approvals "seized"—they appear as unapproved to marketplaces, preventing listing and sale attempts:

getApproved(tokenId):
├── Token locked? → return address(0)     // Appears unapproved
└── Token unlocked? → return actual approved address

isApprovedForAll(owner, operator):
├── Owner has ANY locked item? → return false   // Can't list anything
└── No locked items? → return actual approval status

_isAuthorized(owner, spender, tokenId):
├── Token locked + spender is GLUE/whitelisted? → Allow
├── Token locked + spender is owner? → Allow (gift)
├── Token locked + other spender? → Block
└── Token unlocked? → Normal ERC721 rules

Important Notes:

  • GLUE is automatically added as first receiver (index 0, cannot be removed)
  • glueRoyaltyBps must be ≥ 1 (0.01%)
  • Total royalties (GLUE + receivers) cannot exceed 1000 BPS (10%)
  • Up to 10 additional receivers beyond GLUE
  • GLUE is automatically marked as royalty-exempt
  • contractURI_ follows ERC-7572 for contract-level metadata

Responding to Different Scenarios

Scenario 1: Marketplace Sends Payment After Transfer (Same Transaction)

What happens:

  1. Marketplace calls transferFrom(from, to, tokenId)
  2. Transfer executes, ownership changes
  3. Token added to locked list
  4. Marketplace sends ETH to contract (same tx)
  5. receive() fires, processes payment via LIFO
  6. Just-transferred token unlocked first
  7. Buyer receives unlocked token

Outcome: Seamless. Buyer never sees locked state because LIFO unlocking processes most recent lock first.

Scenario 2: Marketplace Sends Payment Before Transfer (Same Transaction)

What happens:

  1. Marketplace sends ETH to contract first
  2. receive() fires, but no locked tokens exist yet
  3. ETH accumulates in contract balance
  4. Marketplace calls transferFrom(from, to, tokenId)
  5. Transfer executes, token added to locked list
  6. Transaction ends with locked token and excess balance
  7. Next payment (any trade) unlocks this token

Outcome: Token temporarily locked. Unlocks on next payment received. Edge case handling for unusual transaction ordering.

Scenario 3: No Payment Sent

What happens:

  1. User calls transferFrom directly or via non-paying marketplace
  2. Transfer executes, ownership changes
  3. Token added to locked list
  4. No payment arrives
  5. Token remains locked

Outcome: Owner has the NFT but cannot create new marketplace listings. Can still transfer directly, burn, or send payment manually to unlock.

Scenario 4: Using transferFrom2 (Paid Transfer)

What happens:

  1. Seller approves marketplace via approve2(marketplace, quantity)
  2. Buyer initiates purchase
  3. Marketplace calls transferFrom2{value: royalty}(from, to, tokenId)
  4. Contract verifies payment ≥ minimum royalty
  5. Transfer executes WITHOUT locking
  6. Royalties distributed to receivers + GLUE
  7. Marketplace receives 1% reward

Outcome: Perfect UX. No locking, immediate unlock, marketplace earns reward for better integration.

Scenario 5: Locked Token Receives Manual Payment

What happens:

  1. Token is locked from previous unpaid transfer
  2. Anyone sends ETH or WETH to contract
  3. receive() fires (for ETH) or balance increases (for WETH)
  4. Contract checks both ETH and WETH balances
  5. LIFO unlocking processes locked list
  6. Most recent locks cleared first
  7. Token unlocks when its position reached

Outcome: Manual unlock succeeds. Payment tracked via balance checks for both ETH and WETH.

Scenario 6: Owner Direct Transfer (Not Marketplace)

What happens:

  1. Owner calls transferFrom to transfer to friend
  2. Check detects owner is sender
  3. Transfer executes WITHOUT locking
  4. No royalty expected for owner transfers

Outcome: Direct transfers bypass enforcement entirely. Owner retains full control.

Scenario 7: Transfer to GLUE or Burn

What happens:

  1. User transfers to GLUE address or dead address
  2. Check detects exempt destination
  3. Transfer executes WITHOUT locking
  4. No royalty expected for burns

Outcome: Burning/GLUE redemption works normally. No interference with utility.

Royalty Calculation

ERC721G royalties are based on GLUE floor price, not market price:

Floor Price = Total GLUE Backing ÷ NFT Supply
Minimum Royalty = Floor Price × Royalty Percentage

Example:

  • Collection: 10,000 NFTs
  • GLUE backing: 100 ETH
  • Floor price: 0.01 ETH per NFT
  • Royalty: 5%
  • Minimum royalty per trade: 0.0005 ETH

As trades occur:

  • Royalties flow partially to GLUE (1%+)
  • GLUE backing increases (100 → 101 ETH)
  • Floor price rises (0.01 → 0.0101 ETH)
  • Future royalties increase (0.0005 → 0.000505 ETH)
  • Compounding continues...

State Management: Understanding Lock Mechanics

The Locked List Data Structure

ERC721G maintains a dynamic array of locked token IDs with O(1) operations:

On Lock:

  • Token ID pushed to end of _lockedTokenIds array
  • Position recorded in _lockedTokenIndex mapping
  • O(1) operation

On Unlock (LIFO):

  • Last token ID popped from array
  • Swap-and-pop for non-terminal positions
  • Index mapping updated
  • O(1) operation regardless of position

This structure enables efficient batch unlocking: when payment arrives, the contract can unlock multiple tokens in a single transaction without iteration limits.

Lock State Transition Diagram

Minted → Unlocked ←→ Locked
    ↓         ↓          ↓
  Burned   Burned    Burned

Transitions to Locked:
- transferFrom() without payment (marketplace trade)
- transferFrom() with insufficient payment

Transitions to Unlocked:
- receive() processes payment (checks ETH + WETH balances, LIFO unlock)
- transferFrom2() with payment (never locks) - accepts ETH or WETH
- Owner direct transfer (bypass)
- Transfer to exempt address (bypass)

Always Unlocked (exempt):
- Transfers from owner to anyone (direct)
- Transfers to GLUE or dead address (burn)
- Transfers involving exempt addresses
- When royalty enforcement disabled

Lock State Effects on Functions

Token Ownership Functions (No Change):

  • ownerOf(tokenId) - Returns actual owner regardless of lock
  • balanceOf(owner) - Counts all tokens regardless of lock
  • tokenOfOwnerByIndex(owner, index) - Enumerates all tokens
  • tokenByIndex(index) - Global enumeration works normally

Approval Functions (Lock-Aware):

  • getApproved(tokenId) - Returns address(0) if locked (blocks listings)
  • isApprovedForAll(owner, operator) - Works normally
  • approve(to, tokenId) - Sets approval but ineffective while locked
  • setApprovalForAll(operator, approved) - Works normally

Transfer Functions (Lock-Aware):

  • transferFrom(from, to, tokenId) - Executes but adds to locked list if unpaid
  • safeTransferFrom(...) - Same as transferFrom with callback
  • transferFrom2(from, to, tokenId) - Requires payment, never locks
  • Direct owner transfers bypass locking entirely

What "Locked" Actually Means

Locked is NOT a custody state. The owner fully controls the token:

Owner can:

  • View token in wallet (all standard queries work)
  • Transfer to any address directly (bypass enforcement)
  • Burn token to claim GLUE backing
  • Use in non-marketplace DeFi protocols
  • Wait for automatic unlock via payment

Owner cannot:

  • Create new marketplace listings (getApproved returns null)
  • Have marketplace execute transferFrom (internal auth checks fail on locked)

Existing listings are unaffected:

  • If token was unlocked when listed, that listing still works
  • Only NEW listings of currently-locked tokens are blocked

This precision is crucial: the lock doesn't break composability or trap users. It creates economic pressure for payment while preserving all ownership rights.

Advanced Features

Batch Operations

// Batch paid transfers
uint256[] memory tokenIds = new uint256[](3);
tokenIds[0] = 1;
tokenIds[1] = 2;
tokenIds[2] = 3;

nft.batchTransferFrom2{value: totalRoyalties}(
    from,
    to,
    tokenIds
);

Reading State

// Check if token is locked
bool locked = nft.isLocked(tokenId);

// Get all locked token IDs
uint256[] memory lockedIds = nft.getLockedIds();

// Get minimum royalty for a token
uint256 minRoyalty = nft.getMinimumRoyalty(tokenId);

// Get floor price from GLUE
uint256 floorPrice = nft.getFloorPrice();

Integration Guide

For NFT Creators

  1. Deploy with GLUE integration:

    • Get your token's GLUE address from GLUE Protocol
    • Configure royalty split (min 1% to GLUE)
    • Deploy ERC721G with your parameters
  2. Set royalty receivers:

    • Add up to 10 custom receivers
    • Configure BPS allocation (max 10,000 total)
    • Test with small trades first
  3. Monitor backing growth:

    • Watch GLUE backing accumulate
    • Track floor price increases
    • Optionally reinvest earnings to GLUE for compounding

For Marketplaces

Option 1: Standard Integration (No Changes)

  • Continue using transferFrom() as normal
  • Items lock → users send payment → items unlock
  • Works but suboptimal UX

Option 2: transferFrom2 Integration (Recommended)

  • Implement approve2 approval flow
  • Call transferFrom2{value: royalty}() with payment
  • Earn 1% marketplace reward
  • Better UX (no locking)

Integration steps:

// 1. User approves marketplace
nft.approve2(marketplaceAddress, 1);

// 2. Calculate minimum royalty
uint256 minRoyalty = nft.getMinimumRoyalty(tokenId);

// 3. Execute paid transfer
nft.transferFrom2{value: minRoyalty}(from, to, tokenId);

// 4. Marketplace automatically receives 1% reward

For Users/Holders

Buying:

  • Marketplace handles payment automatically
  • If using transferFrom, item locks briefly then unlocks
  • If using transferFrom2, no lock occurs

Selling:

  • List on any marketplace (no restrictions)
  • Royalty based on floor price, not your listing price
  • Payment handled automatically

If locked:

  • Wait for payment processing (usually same transaction)
  • Or send ETH directly to contract to unlock
  • Locked items can still be owned/burned normally

Gas Optimization

ERC721G is optimized for efficiency:

  • O(1) lock/unlock operations (swap-and-pop pattern)
  • Transient storage for re-entrancy (EIP-1153, saves ~15k gas)
  • Batch processing for multiple unlocks
  • LIFO unlocking for same-transaction payments
  • Efficient approval checks using counter mappings

Comparison

Feature ERC-721 ERC-2981 ERC-721C ERC721G
Royalty enforcement ❌ ❌ ✅ ✅
Permissionless ✅ ✅ ❌ ✅
Backward compatible N/A ✅ ❌ ✅
Floor price based ❌ ❌ ❌ ✅
Marketplace incentives ❌ ❌ ❌ ✅
Compounding backing ❌ ❌ ❌ ✅

Contributing

This is an experimental standard. We need:

  • 🔍 Security researchers - Find vulnerabilities
  • 🏗️ Marketplace builders - Integration feedback
  • 🎨 Creators - Real-world usage insights
  • 💻 Developers - Code improvements
  • 📊 Economists - Mechanism design feedback

How to contribute:

  1. Open issues for bugs/suggestions
  2. Submit PRs with improvements
  3. Share integration experiences
  4. Propose mechanism alternatives

Resources

Disclaimer

This is experimental software. ERC721G is a first attempt at solving on-chain royalty enforcement. It has not been battle-tested at scale. Use at your own risk. The standard may have edge cases we haven't discovered. We welcome feedback and collaboration to improve it.

The code is provided as-is. While we've taken care to implement security best practices, no guarantee of security or fitness for any particular purpose is made. Independent audit is ongoing but not yet complete.


Built with ❤️ by La-Li-Lu-Le-Lo (@lalilulel0x) and the Glue Finance Team

🚀 Start building: npm install @glue-finance/expansions-pack

About

A Standard for On-Chain Royalty Enforcement Through Deferred Payment Reconciliation

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages