Skip to main content
Preventing immediate flipping of NFTs can help build stronger communities and reduce speculation. This policy enforces minimum hold times for NFTs, with different durations for original mints versus subsequent transfers.

How does it work?

By hooking the mint, transfer, and transferFrom functions of the NFT contract into the Rules Engine with a policy that tracks when each NFT was last transferred and enforces minimum hold periods before allowing new transfers.

Let’s break that down further.

When an NFT is minted, the Rules Engine records a timestamp indicating when it becomes eligible for transfer (20 days later). For subsequent transfers, a new hold period begins (10 days) to prevent rapid flipping while still allowing legitimate trading. The system uses a mapped tracker to store the “unlock timestamp” for each NFT, checking against the current block timestamp before allowing any transfer. If the NFT is still within its hold period, the transaction reverts. This is particularly useful for:
  • Community building: Encourage longer-term ownership and engagement
  • Anti-speculation: Prevent immediate flipping and price manipulation
  • Fair distribution: Give all holders equal opportunity to participate in the ecosystem

Implementation

The policy uses a Mapped Tracker to store unlock timestamps for each NFT. You’ll need to:
  1. Set the initial hold period for mints (20 days = 1,728,000 seconds)
  2. Set the hold period for transfers (10 days = 864,000 seconds)
  3. Ensure your NFT contract implements standard ERC-721 transfer functions
  4. Optionally, adjust hold periods based on your community needs

Policy JSON

{
  "Policy": "NFT Minimum Hold Time",
  "Description": "Set 20 day hold time for mints, 10 day hold time for subsequent transfers",
  "PolicyType": "open",
  "CallingFunctions": [
    {
      "Name": "Mint",
      "FunctionSignature": "mint(address to)",
      "EncodedValues": "address to"
    },
    {
      "Name": "transfer(address to, uint256 value)",
      "FunctionSignature": "transfer(address to, uint256 value)",
      "EncodedValues": "address to, uint256 value"
    },
    {
      "Name": "transferFrom(address from, address to, uint256 value)",
      "FunctionSignature": "transferFrom(address from, address to, uint256 value)",
      "EncodedValues": "address from, address to, uint256 value"
    }
  ],
  "ForeignCalls": [],
  "MappedTrackers": [
    {
      "Name": "HoldUntil",
      "KeyType": "address",
      "ValueType": "uint256",
      "InitialKeys": [],
      "InitialValues": []
    }
  ],
  "Trackers": [],
  "Rules": [
    {
      "Name": "Min Hold Time for Mint",
      "Description": "Hold time after mint is 20 days",
      "Condition": "1 == 1",
      "PositiveEffects": ["TRU:HoldUntil(to) = GV:BLOCK_TIMESTAMP + 1728000"],
      "NegativeEffects": [],
      "CallingFunction": "Mint"
    },
    {
      "Name": "Min Hold Time for Transfer",
      "Description": "Ensure NFT eligible for transfer, set new hold window up success",
      "Condition": "GV:BLOCK_TIMESTAMP > TRU:HoldUntil(to)",
      "PositiveEffects": ["TRU:HoldUntil(to) = GV:BLOCK_TIMESTAMP + 864000"],
      "NegativeEffects": ["revert(\"NFT is within hold window\")"],
      "CallingFunction": "transfer(address to, uint256 value)"
    },
    {
      "Name": "Min Hold Time for TransferFrom",
      "Description": "Ensure NFT eligible for transfer, set new hold window up success",
      "Condition": "GV:BLOCK_TIMESTAMP > TRU:HoldUntil(to)",
      "PositiveEffects": ["TRU:HoldUntil(to) = GV:BLOCK_TIMESTAMP + 864000"],
      "NegativeEffects": ["revert(\"NFT is within hold window\")"],
      "CallingFunction": "transferFrom(address from, address to, uint256 value)"
    }
  ]
}
It’s critical that you add the rule to both the transfer and transferFrom functions to ensure the hold time requirement cannot be bypassed through allowance-based transfers.
I