Ethereum·Scanned 04 Nov 2026, 22:43 UTC
GrailToken $GRAIL
Verdict
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
- Contract35
- Token60
- Social / X45
- Website50
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
Findings
14 findingsRisks (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
Show source
The launch has been postponed until tomorrow due to additional checks by Railgun.Show source
Railgun contributors do not run, review or endorse Grail, and they did not write this documentation.
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
Show source
GRAIL is the native token of Camelot, a DEX on the Arbitrum network.Show source
$GRAIL is now live Ca : 0x0d8e9046a6a6e24f5c0ff86303940b270208d26a
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%35Capped · max 35uncapped 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%60Holdings 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%45The 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%50The 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