Skip to content

Ethereum·Scanned 04 Nov 2026, 22:43 UTC

GrailToken $GRAIL

0x0d8e9046a6a6e24f5c0ff86303940b270208d26a

Verdict

High risk

46/100 safety

Launchpad keeps a flagged uncapped mint via initialize() and the token is a proxy clone; sells work at 0% tax.

This is rated high risk because the rule-based scan flags that the launchpad contract can mint new GRAIL without a cap, and the token is an upgradeable-style proxy clone. The code review says minting can only happen once and that the clone cannot be changed, but this has not been independently confirmed. Buying and selling both work with 0% tax, the token has no owner, blacklist or pause, the creator holds very little, and the pool position sits in a launchpad locker. Liquidity is thin, at about $17.6k, and the project's X posts contain some claims that do not match its own docs.

Score breakdown

  • Contract
    35
  • Token
    60
  • Social / X
    45
  • Website
    50

Higher is safer · 0 = worst · 100 = best

Yes
0%
Unknown
0% / 0%
MC at check
$35.0K
MC now
no live price
Since check
—
Liquidity
$25.6K

Key findings

  • CriticalLaunchpad can mint unlimited tokens
  • MediumContract is upgradeable
  • LowLaunchpad owner can redirect the protocol's 30% fee share
  • LowThin pool liquidity of about $17.6k
  • GoodNo owner, fees, blacklist or pause; sells work at 0% tax

What would change the verdict

  • Raises safety:Independent confirmation that initialize() reverts once supply exists, so no further mint is possible → contract score up
  • Raises safety:Confirmation that the minimal-proxy clone has no admin or upgrade path → contract score up
Post on XView on explorer ↗

Findings

14 findings

Risks (6)

  • CriticalLaunchpad can mint unlimited tokensmintDeterministic

    initialize(string,string,string,address,uint256,uint256) (launchpad) lets the privileged account create new tokens without a supply cap. Minting and dumping new supply drains the pool and dilutes every holder.

    Evidence

    • codeGrailToken.sol:L40
      Show source
      function initialize(
    • staticprivilege initialize(string,string,string,address,uint256,uint256) restricted to launchpad
      Show source
      Restricted state-changing function (custom — review); mints supply

    Confirmed by: facts

  • MediumContract is upgradeableproxy-upgradeDeterministic

    The contract logic can be replaced by its proxy admin (admin type unknown).

    Evidence

    • on-chainminimal proxy; implementation 0xe5fFB158b3AC190FB0C48bd1eF971BD3368F679F; admin unknown

    Confirmed by: facts

  • LowLaunchpad owner can redirect the protocol's 30% fee sharefeesAI

    Trading fees earned by the locked LP position (1% pool fee tier) go 70% to the creator's fee recipient and 30% to the launchpad's protocolFeeRecipient. Launch fees also go to protocolFeeRecipient. The launchpad owner can change protocolFeeRecipient at any time, with no timelock. This only decides who receives the protocol share. It cannot raise fees, touch holders' tokens, or touch the liquidity. The creator's fee recipient is fixed at launch and cannot be changed.

    Evidence

    • codeGrailLaunchpad.sol:L251-L257
      Show source
      function setProtocolFeeRecipient(address recipient) external onlyOwner {
              if (_isReserved(recipient, address(weth), address(uniswapFactory), address(positionManager), address(locker))) {
                  revert InvalidRecipient();
              }
              protocolFeeRecipient = recipient;
              emit ProtocolFeeRecipientUpdated(recipient);
          }
    • codeGrailLocker.sol:L63-L76
      Show source
      (,, address token0, address token1,,,,,,,,) = positionManager.positions(positionId);
              address creatorRecipient = ILaunchpadRecipients(launchpad).feeRecipientOf(token);
              address protocolRecipient = ILaunchpadRecipients(launchpad).protocolFeeRecipient();
              uint256 creatorAmount0 = amount0 * CREATOR_SHARE_BPS / BPS;
              uint256 creatorAmount1 = amount1 * CREATOR_SHARE_BPS / BPS;
      
              emit FeesCollected(
                  positionId, token, creatorRecipient, protocolRecipient, amount0, amount1, creatorAmount0, creatorAmount1
              );
      
              _pay(token0, creatorRecipient, creatorAmount0);
              _pay(token0, protocolRecipient, amount0 - creatorAmount0);
              _pay(token1, creatorRecipient, creatorAmount1);
              _pay(token1, protocolRecipient, amount1 - creatorAmount1);
    • on-chainGrailLaunchpad owner = 0xD6A5A06Eed254cfdeC061E047360E563EC9573B7; protocolFeeRecipient = 0xD6A5A06Eed254cfdeC061E047360E563EC9573B7

    Confirmed by: claude-opus-5.5

  • LowThin pool liquidity of about $17.6kliquidityAI

    The main Uniswap v3 pool has about $17,586 of liquidity and holds 47.875% of supply. Large sells will move the price a lot. A few top holders, each with 2-4% of supply, could push the price down sharply if they sold. A second Uniswap v4 pool holds only about $2 and is not part of the launchpad.

    Evidence

    • on-chainliquidity uniswap-v3 pool 0x5309a9F44498051d27D1A584bbD7CB32c62030f9 liquidityUsd 17586
      Show source
      uniswap-v3 pool 0x5309a9F44498051d27D1A584bbD7CB32c62030f9 holds 47.875% of supply
    • on-chainliquidity uniswap-v4 pool 0xb1bb3e5a092a0b98638943a6260f1b925d76bc7110a5941fa45e8ec7b3577037 liquidityUsd 2

    Confirmed by: claude-opus-5.5

  • LowLaunch delay blamed on Railgun checks that the docs denyclaims-mismatchAI

    The project account said the launch was postponed because Railgun was doing extra checks. The project's own docs say Railgun contributors do not run, review, or endorse Grail and did not write the documentation. That makes the delay explanation unverifiable and overstates a relationship the site itself denies. It is not proof the token cannot be sold.

    Evidence

    Confirmed by: grok-4.7

  • LowTicker $GRAIL collides with Camelot's existing tokensocialAI

    This new Ethereum token uses the ticker $GRAIL, the same ticker long used for Camelot DEX on Arbitrum. Search results still show people discussing Camelot $GRAIL as a separate project. Buyers can confuse the two and buy the wrong asset. No posts tying this contract to a prior rug were found.

    Evidence

    Confirmed by: grok-4.7

Positive signals (3)

  • PositiveNo owner, fees, blacklist or pause; sells work at 0% taxownershipAI

    GrailToken is an OpenZeppelin ERC20 with one custom rule: a short launch guard. It has no owner variable, no fee logic, no blacklist or whitelist, no pause, and no function to change balances. No one can change how transfers work after launch. The fork simulation agrees: buy, sell, transfer to a second wallet and a sell one hour later all worked with 0% token tax.

    Evidence

    • codeGrailToken.sol:L92-L106
      Show source
      function _update(address from, address to, uint256 value) internal override {
              address grailPool = pool();
              if (from == grailPool) {
                  if (to != launchpad && block.number < guardEndBlock) {
                      uint256 used = (_guardBlock == block.number ? _guardUsed : 0) + value;
                      if (used > guardMaxPerBlock) revert LaunchGuard();
                      _guardBlock = uint64(block.number);
                      _guardUsed = uint128(used);
                  }
              } else if (to == grailPool && _guardBlock == block.number) {
                  uint256 used = _guardUsed;
                  _guardUsed = uint128(used > value ? used - value : 0);
              }
              super._update(from, to, value);
          }
    • forkcanBuy=true, canSell=true, buyTaxPct=0, sellTaxPct=0, transferTaxPct=0
      Show source
      second wallet (received via transfer) could sell
    • on-chainabiFunctions: no owner/fee/blacklist/pause setters
      Show source
      transfer(address,uint256) nonpayable

    Confirmed by: claude-opus-5.5

  • PositivePool position held by a launchpad locker with no withdraw functionliquidityAI

    At launch the whole minted supply is put into a single-sided Uniswap v3 position. The position NFT is minted directly to GrailLocker. GrailLocker has only two functions: lock() (launchpad only) and collectFees(). It cannot decrease liquidity, withdraw, or transfer the NFT. Neither the creator nor the launchpad owner can pull the liquidity. The facts confirm the locker holds 100% of the tracked v3 position. The 'lpLockedPct 0' field does not reflect this because the locker is a launchpad contract, not a known locker brand.

    Evidence

    • codeGrailLaunchpad.sol:L361-L378
      Show source
      function _mintParams(address token, bool tokenFirst, int24 lower, int24 upper, uint256 amount)
              private
              view
              returns (INonfungiblePositionManager.MintParams memory)
          {
              return INonfungiblePositionManager.MintParams({
                  token0: tokenFirst ? token : address(weth),
                  token1: tokenFirst ? address(weth) : token,
                  fee: POOL_FEE,
                  tickLower: lower,
                  tickUpper: upper,
                  amount0Desired: tokenFirst ? amount : 0,
                  amount1Desired: tokenFirst ? 0 : amount,
                  amount0Min: 0,
                  amount1Min: 0,
    • codeGrailLocker.sol:L42-L82
      Show source
      function lock(uint256 positionId, address token) external {
              if (msg.sender != launchpad) revert OnlyLaunchpad();
              if (positions[positionId] != address(0)) revert AlreadyLocked();
              if (positionManager.ownerOf(positionId) != address(this)) revert NotOwner();
              positions[positionId] = token;
              emit PositionLocked(positionId, token);
          }
      
          function collectFees(uint256 positionId) external returns (uint256 amount0, uint256 amount1) {
              address token = positions[positionId];
              if (token == address(0)) revert UnknownPosition();
      
              (amount0, amount1) = positionManager.collect(
                  INonfungiblePositionManager.CollectParams({
                      tokenId: positionId,
    • on-chainv3 position liquidity other holders: 0x6bd169ca8a29da8ab6947425c03435eedd75aeef 100%
      Show source
      [contract — possibly a locker/launchpad] 100%

    Confirmed by: claude-opus-5.5

  • PositiveDocs disclose that Railgun does not endorse the projectwebsiteAI

    The docs state plainly that Grail is an independent integrator and that Railgun does not review or endorse it. That is a transparency note, not a safety guarantee. Team identities are still not published, so "doxxed" cannot be checked.

    Evidence

    • Show source
      Grail is an independent project built on Railgun's public contracts and SDK. Railgun contributors do not run, review or endorse Grail

    Confirmed by: grok-4.7

Notes (5)

  • InfoCode review: initialize() appears callable only once (not confirmed)mintAI

    The static scanner rated initialize() critical because it mints supply. In the code, only the launchpad can call it. It also reverts on the implementation contract itself and whenever totalSupply() is already non-zero. The launchpad calls it once per new clone with the fixed TOTAL_SUPPLY of 1,000,000,000 tokens. The token has no burn path that could bring supply back to zero, so the full supply already exists and no one can mint more.

    Evidence

    • codeGrailToken.sol:L40-L56
      Show source
      function initialize(
              string calldata name_,
              string calldata symbol_,
              string calldata metadataURI_,
              address creator_,
              uint256 supply,
              uint256 guardBlocks
          ) external {
              if (msg.sender != launchpad) revert OnlyLaunchpad();
              if (address(this) == _implementation || totalSupply() != 0) revert AlreadyInitialized();
              _tokenName = name_;
              _tokenSymbol = symbol_;
              _metadataURI = metadataURI_;
              creator = creator_;
              guardEndBlock = uint64(block.number + guardBlocks);
    • codeGrailLaunchpad.sol:L291-L301
      Show source
      function _deployToken(
              string calldata name,
              string calldata symbol,
              string calldata metadataURI,
              address feeRecipient
          ) private returns (address token) {
              _validateMetadata(name, symbol, metadataURI);
              bytes32 salt = _tokenSalt(msg.sender, name, symbol, metadataURI, feeRecipient);
              if (Clones.predictDeterministicAddress(tokenImplementation, salt).code.length > 0) revert DuplicateToken();
              token = Clones.cloneDeterministic(tokenImplementation, salt);
              GrailToken(token).initialize(name, symbol, metadataURI, msg.sender, TOTAL_SUPPLY, guardBlocks);
    • staticprivileges: initialize(...) restrictedTo launchpad — mints supply
      Show source
      Restricted state-changing function (custom — review); mints supply

    Confirmed by: claude-opus-5.5

  • InfoCode review: clone appears to have no upgrade admin (not confirmed)proxy-upgradeAI

    The facts label the token a 'minimal' proxy. The launchpad creates it with OpenZeppelin Clones.cloneDeterministic, which points to a fixed implementation address. Neither contract has an upgrade function or admin slot, so no one can swap the token's code.

    Evidence

    • codeGrailLaunchpad.sol:L298-L300
      Show source
      bytes32 salt = _tokenSalt(msg.sender, name, symbol, metadataURI, feeRecipient);
              if (Clones.predictDeterministicAddress(tokenImplementation, salt).code.length > 0) revert DuplicateToken();
              token = Clones.cloneDeterministic(tokenImplementation, salt);
    • on-chainproxyType = minimal, implementation = 0xe5fFB158b3AC190FB0C48bd1eF971BD3368F679F
      Show source
      tokenImplementation (settable by immutable (set at deploy))

    Confirmed by: claude-opus-5.5

  • InfoFive-block anti-sniper buy cap has already expiredotherAI

    For the first 5 blocks after launch, buys from the official pool were capped at 10,000,000 tokens (1% of supply) per block, to slow snipers. The cap ended at block 26343618. Sells were never blocked; they only lower the per-block counter. No one can change or extend the cap: it is set once at initialize and has no setter.

    Evidence

    • codeGrailToken.sol:L92-L104
      Show source
      function _update(address from, address to, uint256 value) internal override {
              address grailPool = pool();
              if (from == grailPool) {
                  if (to != launchpad && block.number < guardEndBlock) {
                      uint256 used = (_guardBlock == block.number ? _guardUsed : 0) + value;
                      if (used > guardMaxPerBlock) revert LaunchGuard();
                      _guardBlock = uint64(block.number);
                      _guardUsed = uint128(used);
                  }
              } else if (to == grailPool && _guardBlock == block.number) {
                  uint256 used = _guardUsed;
                  _guardUsed = uint128(used > value ? used - value : 0);
              }
    • on-chainguardEndBlock = 26343618; guardMaxPerBlock = 10000000000000000000000000; guardAllowance = max uint256
      Show source
      guardBlocks = 5

    Confirmed by: claude-opus-5.5

  • InfoLaunchpad pause blocks only new launches, not GRAIL tradingpauseAI

    The launchpad owner can pause and unpause. The whenNotPaused check guards only createToken. GRAIL transfers and Uniswap swaps never check the launchpad's pause state, so pausing cannot freeze existing holders.

    Evidence

    • codeGrailLaunchpad.sol:L196-L202
      Show source
      function createToken(
              string calldata name,
              string calldata symbol,
              string calldata metadataURI,
              address feeRecipient,
              uint256 minTokensOut
          ) external payable nonReentrant whenNotPaused returns (address token, uint256 tokensOut) {
    • codeGrailLaunchpad.sol:L259-L266
      Show source
      function pause() external onlyOwner {
              _pause();
          }
      
          function unpause() external onlyOwner {
              _unpause();
          }

    Confirmed by: claude-opus-5.5

  • InfoHoldings are spread out; creator holds about 0.15%holdersAI

    The creator/deployer holds 0.1468% and has sold none. The largest non-pool holder has 4.03%, and the top 10 together hold 25.82%, across 80 holders. No bundles were detected. Fresh wallets hold 2.06%. Several top holders are contracts, two labelled Farcaster, which are likely smart wallets. Sniper checks were paused in this data run, so early-buyer behaviour was not assessed.

    Evidence

    • on-chainDeployer 0xe6544c95c73ceec86fac32d421d8f4480277c278 holds 0.1468% now
      Show source
      deployerSoldPct: 0
    • on-chaintop10Pct = 25.8211; holdersCount = 80; bundles = []; freshWalletsPct = 2.0567

    Confirmed by: claude-opus-5.5

Score breakdown

Higher is safer. The overall is the weighted average of the assessed parts, then capped by hard facts.

  • Contract

    weight 45%
    35
    Capped · max 35

    uncapped mint by live launchpad: initialize(string,string,string,address,uint256,uint256) → max 35

    The rule-based scan flags an uncapped launchpad mint and an upgradeable proxy, which caps this score, although the code review suggests a plain, ownerless token.

    Why this score

    Raised the score

    • Verified source, no owner, fees, blacklist or pause
    • Buy, sell, wallet-to-wallet transfer and later sell all worked at 0% tax
    • Anti-sniper buy cap already expired and cannot be changed
    • Launchpad pause only affects new launches

    Lowered the score

    • Launchpad flagged as able to mint without a cap
    • Token is a minimal proxy clone, flagged as upgradeable
    • Launchpad owner can redirect the protocol's fee share with no timelock
  • Token

    weight 30%
    60

    Holdings are spread out and the creator holds little, but liquidity is thin and sniper activity at launch was not checked.

    Why this score

    Raised the score

    • Creator holds about 0.15% and has sold nothing
    • Top 10 non-pool holders hold about 26%, no bundles detected
    • Pool position held by a launchpad locker with no withdraw function (per code review)

    Lowered the score

    • Thin liquidity of about $17.6k
    • Sniper checks were not run in this scan
    • Only 80 holders, with several holding 2-4% each
  • Social / X

    weight 15%
    45

    The X account is young and small, and some of its posts conflict with the project's own docs.

    Why this score

    Raised the score

    • Account posts the matching contract address
    • No third-party rug reports found

    Lowered the score

    • Account created about two months before launch, around 513 followers
    • Blamed a launch delay on Railgun checks that the docs say never happen
    • Ticker $GRAIL is the same as Camelot's long-standing token
  • Website

    weight 10%
    50

    The site was not fetched by our scanners; the docs quoted in posts include an honest disclaimer, but claims of locked liquidity and a Safe owner were not verified.

    Why this score

    Raised the score

    • Docs state that Railgun does not endorse the project

    Lowered the score

    • Team identities are not published
    • Claims of a Safe owner were not verified