Skip to content

Base·Scanned 04 Nov 2026, 13:37 UTC

Basecat

0xb2000000000000000000004c27f6523082f41d01

Verdict

Caution

60/100 safety

Unverified token, but the launchpad enforced no admin roles; sells work at 0% tax; holder data unavailable.

Basecat's token source is not verified, so its safety relies on checks by the launchpad that created it. That launchpad enforced no admin, mint or pause powers on the token. A test buy and sell worked with 0% token tax, and the main pool's liquidity is permanently locked. The main unknowns are who holds the supply, because the holder scan failed and launch allocations are unknown. The fee escrow and vesting contracts were also not reviewed.

Score breakdown

  • Contract
    58
  • Token
    60
  • Social / X
    65
  • Website
    n/a

Higher is safer · 0 = worst · 100 = best

Yes
0%
Unknown
0% / 0%
MC at check
$8.66M
MC now
no live price
Since check
—
Liquidity
$418.4K

Key findings

  • MediumSource code is not verified
  • GoodLaunchpad created the token with no admin, mint or pause roles

What would change the verdict

  • Raises safety:Token bytecode or source gets verified on the explorer and confirms no admin, mint or pause roles → contract score up
  • Lowers safety:A working holder scan shows insiders, vesting or genesis wallets hold a large share → down
  • Lowers safety:FeeEscrow or vesting vault source reveals admin withdrawal or redirect powers → down
  • Lowers safety:The unlocked secondary v3/v4 pools turn out to be deployer-owned and get pulled → down
View on explorer ↗

Findings

10 findings

Risks (1)

  • MediumSource code is not verifiedotherDeterministic

    The contract source is not published on the explorer, so nobody can read what it really does. The analysis relies on bytecode only.

    Evidence

    • on-chainexplorer: no verified source for 0xB2000000000000000000004c27f6523082f41D01

    Confirmed by: facts

Positive signals (4)

  • PositiveLaunchpad created the token with no admin, mint or pause rolesownershipAI

    Basecat was created by the B20LaunchpadFactory through Base's B20 precompile. The factory passes initialAdmin = address(0), so the token has no admin from birth. After creation, the factory checks the new token and reverts the whole launch unless: supply equals the cap; no transfer-sender, receiver, executor or mint-receiver policy is set; and none of the creator, factory, hook, vesting vault or platform treasury holds an admin, mint, burn, burn-blocked, pause, unpause or operator role. In plain terms, no one should be able to mint more tokens, freeze holders or pause transfers. Caveat: the token's own bytecode is unverified (it is a precompile-created B20 token). This assurance therefore rests on the factory's checks, not on reading the token source.

    Evidence

    • codeB20LaunchpadFactory.sol:L51-L59
      Show source
      return StdPrecompiles.B20_FACTORY
                  .createB20(
                      IB20Factory.B20Variant.ASSET,
                      salt,
                      // initialAdmin = address(0): the token is admin-less from birth. initCalls run in the factory
                      // bootstrap window, which bypasses role gates, so the grants/mints below need no live admin.
                      B20FactoryLib.encodeAssetCreateParams(p.name, p.symbol, address(0), TOKEN_DECIMALS),
                      _buildInitCalls(p, supply, poolSupply, creator, vestedTotal)
                  );
    • codeB20LaunchpadFactory.sol:L123-L144
      Show source
      function _assertLaunchToken(address token, address creator, uint8 roleMode) internal view override {
              IB20 t = IB20(token);
              if (t.totalSupply() != launchSupply || t.supplyCap() != launchSupply) revert NotImmutable();
              if (t.policyId(B20Constants.TRANSFER_SENDER_POLICY) != 0) revert NotImmutable();
              if (t.policyId(B20Constants.TRANSFER_RECEIVER_POLICY) != 0) revert NotImmutable();
              if (t.policyId(B20Constants.TRANSFER_EXECUTOR_POLICY) != 0) revert NotImmutable();
              if (t.policyId(B20Constants.MINT_RECEIVER_POLICY) != 0) revert NotImmutable();
              bool hasMetadata = t.hasRole(B20Constants.METADATA_ROLE, creator);
              if ((roleMode == 1 && !hasMetadata) || (roleMode == 0 && hasMetadata)) revert NotImmutable();
              if (
                  _hasDangerousRole(t, creator) || _hasDangerousRole(t, address(this)) || _hasDangerousRole(t, address(hook))
                      || _hasDangerousRole(t, vestingVault) || _hasDangerousRole(t, feeDefaults.platformTreasury)
              ) revert NotImmutable();
          }
    • on-chainCreated by factory contract 0xa52ad458cE0282a971ecC71C051A32f28946bb9F; tx sender 0x48C7aB8F293d0c55FD4A95764fFAbCfDdc240faF

    Confirmed by: claude-opus-5.5

  • PositiveMain pool liquidity is permanently locked in the launch hookliquidityAI

    The LaunchHook seeds the token as one-sided liquidity that the hook itself owns. Every attempt by anyone else to add or remove liquidity reverts with LiquidityLocked. The hook's only liquidity code adds a positive liquidityDelta; there is no path that removes it. Neither the creator nor the platform can pull the main pool's liquidity (about $423k per facts).

    Evidence

    • codeLaunchHook.sol:L159-L198
      Show source
      /// @notice Seed the launch token as single-sided liquidity owned by this hook (permanent: there is no
          ///         path that ever removes it). Each band's quote-side delta is asserted zero, the on-chain
          ///         guarantee that the seed is token-only.
          function seedLiquidity(PoolKey calldata key, SeedPosition[] calldata positions, bool tokenIsCurrency0) external {
              if (msg.sender != factory) revert NotFactory();
              poolManager.unlock(abi.encode(key, positions, tokenIsCurrency0));
          }
      
          function unlockCallback(bytes calldata data) external returns (bytes memory) {
              // Only ever reached as the callback to this hook's own `seedLiquidity` unlock (the PoolManager calls
              // back the unlock caller), so this guard fully gates it.
              if (msg.sender != address(poolManager)) revert NotPoolManager();
              (PoolKey memory key, SeedPosition[] memory positions, bool tokenIsCurrency0) =
                  abi.decode(data, (PoolKey, SeedPosition[], bool));
    • codeLaunchHook.sol:L208-L225
      Show source
      function _beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata)
              internal
              pure
              override
              returns (bytes4)
          {
              // The hook's own genesis seed is a self-call and skips this callback; everyone else is blocked.
              revert LiquidityLocked();
          }
      
          function _beforeRemoveLiquidity(
              address,
              PoolKey calldata,
              IPoolManager.ModifyLiquidityParams calldata,
              bytes calldata
    • on-chainuniswap-v4 pool 0x215b2146c3164aba3f5b18e8619b737c7f7dd11e5ba693646dcd9a336c5a30ba liquidityUsd 422977, hooks=0x985C14BAa2a18316ffdA0aeFB3a632fAdfcA2AcC

    Confirmed by: claude-opus-5.5

  • PositiveSimulated buy and sell both work with 0% token taxhoneypotAI

    A real buy and sell on the uniswap-v4 pool succeeded. Token-level buy, sell and transfer taxes were 0%. A second wallet that received tokens by transfer could also sell, and a sell one hour later still worked. No honeypot behaviour was observed.

    Evidence

    • forksimulation: canBuy=true, canSell=true, buyTaxPct=0, sellTaxPct=0, transferTaxPct=0
      Show source
      second wallet (received via transfer) could sell
    • forksell after +1h
      Show source
      sell after +1h: ok

    Confirmed by: claude-opus-5.5, grok-4.7

  • PositiveVerified X account predates launch and posts the correct CAsocialAI

    The community account @BasecatOnBase is verified, was created on 2026-05-06 (before the 2026-08-15 deploy), and posts the same contract address as the target. Follower count (~9.4k) lines up with recent post engagement in the tens to low hundreds, which does not look like a bought empty shell.

    Evidence

    • Show source
      BASECAT is a community-run cat memecoin on Base. Make a PFP, play Basecat Run, and verify the token contract.

    Confirmed by: grok-4.7

Notes (5)

  • LowLaunch hook charges a frozen swap fee (up to 10% base)feesAI

    On every swap in the launch pool, the hook takes a fee in the quote currency (for example ETH). The fee goes to the FeeEscrow and is split between the creator, the platform treasury and an optional referrer. The base fee is capped at 10%. During an anti-snipe window right after launch, an extra surcharge applies; it starts as high as 99% total and decays linearly to the base fee. Exact-output swaps are blocked during that window. These values are set once by the factory when the pool is registered and cannot be changed afterwards. The material does not show this pool's actual base fee or window length. The simulation sold successfully and received almost exactly the expected amount, which suggests the anti-snipe surcharge has ended.

    Evidence

    • codeLaunchHook.sol:L40-L44
      Show source
      uint256 internal constant BPS = 10_000;
          /// @notice Hard ceiling on the admin-settable base fee (10%).
          uint16 public constant MAX_BASE_FEE_BPS = 1_000;
          /// @notice Hard ceiling on the total per-swap fee (base + anti-snipe), strictly below 100%.
          uint16 public constant MAX_TOTAL_FEE_BPS = 9_900;
    • codeLaunchHook.sol:L383-L390
      Show source
      function _totalFeeBps(PoolConfig memory cfg) internal view returns (uint256) {
              uint256 elapsed = block.timestamp - cfg.launchTime;
              if (elapsed >= cfg.antiSnipeWindowSeconds) return cfg.baseFeeBps;
              uint256 maxSurcharge = uint256(cfg.antiSnipeStartTotalBps) - cfg.baseFeeBps;
              uint256 surcharge = maxSurcharge * (cfg.antiSnipeWindowSeconds - elapsed) / cfg.antiSnipeWindowSeconds;
              uint256 total = uint256(cfg.baseFeeBps) + surcharge;
              return total > MAX_TOTAL_FEE_BPS ? MAX_TOTAL_FEE_BPS : total;
          }
    • codeLaunchHook.sol:L425-L433
      Show source
      function _validateFeeConfig(PoolConfig calldata cfg) internal pure {
              if (cfg.baseFeeBps > MAX_BASE_FEE_BPS) revert InvalidFeeConfig();
              if (uint256(cfg.creatorBps) + cfg.platformBps + cfg.referrerBps != BPS) revert InvalidFeeConfig();
              if (cfg.antiSnipeStartTotalBps < cfg.baseFeeBps || cfg.antiSnipeStartTotalBps > MAX_TOTAL_FEE_BPS) {
                  revert InvalidFeeConfig();
              }
              if (cfg.antiSnipeWindowSeconds == 0) revert InvalidFeeConfig();
              if (cfg.creator == address(0) || cfg.platformTreasury == address(0)) revert InvalidFeeConfig();
          }
    • codeLaunchHook.sol:L148-L157
      Show source
      function registerPool(PoolKey calldata key, PoolConfig calldata cfg) external {
              if (msg.sender != factory) revert NotFactory();
              _validateFeeConfig(cfg);
              PoolId id = key.toId();
              if (poolConfig[id].initialized) revert AlreadyRegistered();
              PoolConfig memory c = cfg;
              c.initialized = true;
              poolConfig[id] = c;
              emit PoolRegistered(id, c.creator, c.platformTreasury, c.baseFeeBps);
          }

    Confirmed by: claude-opus-5.5

  • LowPartial-fill and some exact-output swaps revert in launch poolotherAI

    The hook reverts a swap whenever the quote-currency side is specified and not fully filled (PartialFillUnsupported). It also reverts exact-output swaps while the anti-snipe surcharge is active. This is by design and affects all users equally; it is not a seller-only trap. Some routers or limit-price swaps may simply fail and need to be retried as exact-input.

    Evidence

    • codeLaunchHook.sol:L368-L379
      Show source
      function _requireFullSpecifiedFill(
              IPoolManager.SwapParams calldata params,
              BalanceDelta delta,
              uint256 specifiedFee
          ) internal pure {
              bool exactIn = params.amountSpecified < 0;
              bool specifiedIsCurrency0 = exactIn ? params.zeroForOne : !params.zeroForOne;
              int128 actualSpecified = specifiedIsCurrency0 ? delta.amount0() : delta.amount1();
              // forge-lint: disable-next-line(unsafe-typecast) beforeSwap already requires this fee to fit int128
              int256 expectedSpecified = params.amountSpecified + int256(specifiedFee);
              if (int256(actualSpecified) != expectedSpecified) revert PartialFillUnsupported();
          }
    • codeLaunchHook.sol:L238-L240
      Show source
      if (params.amountSpecified > 0 && totalFeeBps > cfg.baseFeeBps) {
                  revert ExactOutputDisabledDuringAntiSnipe();
              }

    Confirmed by: claude-opus-5.5

  • LowFactory owner can change settings only for future launchesownershipAI

    The B20LaunchpadFactory owner (0x7bfb…BC41) can change fee defaults, creation fees, quote tokens, launch supply, tick spacing, the vesting vault and the announcement registry. No code shown lets the owner change an existing pool's config. The hook's factory and fee-escrow addresses are wired once and cannot be re-wired. The token is admin-less. So the owner cannot alter Basecat's fees or liquidity after launch. It is unknown whether this owner is a multisig or an EOA.

    Evidence

    • on-chainB20LaunchpadFactory owner = 0x7bfb67CD4278838E20e29FC9Fe05DAD63D0BBC41
    • codeLaunchHook.sol:L130-L144
      Show source
      function initialize(address _factory, address _feeEscrow) external initializer {
              if (msg.sender != deployer) revert NotDeployer();
              if (_factory == address(0) || _feeEscrow == address(0)) revert ZeroAddress();
              // The hook is the single shared, un-re-wireable instance, so a mis-wire bricks every launch with no
              // recovery. Bind both sides: the factory and escrow must already point back at this exact hook (and
              // the escrow at the same PoolManager) or wiring fails closed instead of silently.
              if (IWiredFactory(_factory).hook() != address(this)) revert BadWiring();
              if (IWiredFactory(_factory).poolManager() != address(poolManager)) revert BadWiring();
              if (FeeEscrow(_feeEscrow).hook() != address(this)) revert BadWiring();
              if (address(FeeEscrow(_feeEscrow).poolManager()) != address(poolManager)) revert BadWiring();
              factory = _factory;
              feeEscrow = _feeEscrow;
              wired = true;
              emit Wired(_factory, _feeEscrow);
          }
    • codeLaunchHook.sol:L46
      Show source
      /// @notice Frozen per-pool economics, set by the factory at launch and never changed afterward.

    Confirmed by: claude-opus-5.5

  • InfoHolder distribution and launch allocations could not be checkedholdersAI

    The factory can mint genesis allocations to arbitrary recipients and to a vesting vault at launch. The holder scan failed (eth_getLogs errors) and returned no top holders, so it is unknown whether Basecat had creator, insider or vested allocations and who holds them now. The deployer/creator wallet holds 0% now. Additional pools (a second v4 pool and two v3 pools) exist, and their LP ownership is not known.

    Evidence

    • codeB20LaunchpadFactory.sol:L103-L120
      Show source
      function _allocationMint(LaunchParams calldata p, uint256 vestedTotal)
              internal
              view
              returns (address[] memory recips, uint256[] memory amts)
          {
              uint256 nImm = p.allocationRecipients.length;
              uint256 extra = vestedTotal > 0 ? 1 : 0;
              recips = new address[](nImm + extra);
              amts = new uint256[](nImm + extra);
              for (uint256 i = 0; i < nImm; i++) {
                  recips[i] = p.allocationRecipients[i];
                  amts[i] = p.allocationAmounts[i];
              }
              if (extra == 1) {
                  recips[nImm] = vestingVault;
    • on-chaintoken.notes
      Show source
      Deployer 0x48c7ab8f293d0c55fd4a95764ffabcfddc240faf holds 0% now
    • on-chainwarnings
      Show source
      transfer scan: eth_getLogs: invalid params

    Confirmed by: claude-opus-5.5, grok-4.7

  • InfoSingle unsupported scam accusation in an X replysocialAI

    A single low-engagement reply says $basecat was a scam and will never go up. It gives no wallet, transaction, or contract evidence, and no other recent posts in this search repeat a rug or honeypot allegation. Do not treat that reply as proof.

    Evidence

    Confirmed by: grok-4.7, 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%
    58
    Capped · max 60

    source code not verified → max 60

    Source is unverified, which caps the score, but the launchpad factory enforced an admin-less token and the sell test passed at 0% tax.

    Why this score

    Raised the score

    • Factory asserts no admin, mint, pause or burn roles
    • Buy and sell work with 0% token tax
    • Launch-pool fees are frozen and cannot be changed
    • Factory owner can only change future launches

    Lowered the score

    • Token source not verified
    • Fee escrow and vesting vault sources not reviewed
    • Launch hook takes a per-swap fee of up to 10%
  • Token

    weight 30%
    60

    Main-pool liquidity is large and permanently locked, but holder distribution and launch allocations could not be checked.

    Why this score

    Raised the score

    • About $423k of launch liquidity is permanently locked in the hook
    • Deployer holds 0% now
    • Several additional pools add depth

    Lowered the score

    • Holder scan failed, so the top-holder share is unknown
    • Genesis or vested allocations are unknown
    • LP ownership of the secondary pools is unknown
  • Social / X

    weight 15%
    65

    A verified community X account that predates the token posts the correct contract address, and engagement looks organic.

    Why this score

    Raised the score

    • Verified X account created before the deploy
    • Account posts the matching contract address
    • Engagement looks consistent with follower count

    Lowered the score

    • A promoter made unverified holder and exchange claims
    • An isolated scam accusation was posted without evidence
  • Website

    weight 10%
    —
    Not assessed

    Not enough data — excluded from the overall score.