As of 3 Nov 2026, TokenFi has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 6 Feb 2024. Those reports list no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 3 | 2 | 0 |
| Medium | 3 | 0 | 11 |
| Low | 15 | 0 | 24 |
In the contract `FLOKI` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. - The function `setTreasuryHandler` to set the `treasuryHandler`. - The function `setTaxHandler` to set the `taxHandler`. Note that the token transfer functions may revert if these addresses are incorrectly set.  In the contract `Tokenfi` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. - The function `withdraw` to send any tokens or ETH stuck in the token contract to the treasury handler. - The function `setTreasuryHandler` to set `treasuryHandler`. - The function `setTaxHandler` to set `taxHandler`. Note that the token transfer functions may revert if these addresses are incorrectly set.  In the contract `ExponentialTaxHandler` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. The functions addExemption/removeExemption can be used to add/remove addresses from the `_exempted` address set.  In the contract `StaticTaxHandler` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. - The function `setTaxBasisPoints` can be used to set `taxBasisPoints`. - The functions `addExemption/removeExemption` can be used to add/remove addresses from the `_exempted` address set.  In the contract `ExchangePoolProcessor` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. - The functions `addExchangePool/removeExchangePool` can be used to add/remove pool addresses from the `_exchangePools` address set. - The function `setPrimaryPool` can be used to set `primaryPool`.  In the contract `TreasuryHandlerAlpha` the role `_owner` has authority over the functions shown in the diagram below. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority. - The function `withdraw` can be used to withdraw any tokens or ETH in the treasury handler to the treasury EOA address. - The function `setPriceImpactBasisPoints` can be used to set `priceImpactBasisPoints`. - The function `setTreasury` can be used to set `treasury`. - The function `setLiquidityBasisPoints` can be used to set `liquidityBasisPoints`. 
Open the reportIn the contracts `FLOCKI` and `Tokenfi`, taxes collected by the `treasuryHandler` are partially used to add liquidity, resulting in the LP tokens being transferred to the `treasury` address. Over time, this design will lead to an increasing centralization of LP tokens within the `treasury` address. Any compromise to the private key of the `treasury` address may allow a hacker to take advantage of the EOA address authority and remove the liquidity from the pool. This could have devastating consequences for the entire project.
Open the reportAll of the FLOKI tokens and Tokenfi tokens are sent to the contract deployers. This is a centralization risk because the deployers can distribute tokens without obtaining the consensus of the community. Any compromise to these addresses may allow a hacker to steal and sell tokens on the market, resulting in severe damage to the project.
Open the reportThe `TokenMintPaymentModule` and `TokenMintPricingModule` contracts are designed to use charge fee for creating new TokenFiERC721. However, the attacker has chance to use their own malicious `TokenMintPaymentModule` and `TokenMintPricingModule` contracts to be free for creating TokenFiERC721. Within `createErc721()` function and TokenFiERC721 constructor, there is no validation for `_input.tokenMintPricingModule` and `_input.tokenMintPaymentModule` before deploying TokenFiERC721 contract, at L53. ```solidity=29 function createErc721(CreateErc721Input memory input) external payable override { // First let's calculate the price ITokenLauncherPricingModule.AddOns memory addOns = ITokenLauncherPricingModule.AddOns({ erc20TransferFee: false, erc20BurnFee: false, erc20Reflection: false, erc20Buyback: false }); ITokenLauncherPricingModule.PriceResult memory price = ITokenLauncherPricingModule(pricingModule).getPrice( msg.sender, addOns, ITokenLauncherPricingModule.TokenType.ERC721 ); // Now let's process the payment ITokenLauncherPaymentModule.ProcessPaymentInput memory paymentInput = ITokenLauncherPaymentModule.ProcessPaymentInput( msg.sender, input.referrer, price.usdPrice, ITokenLauncherPaymentModule.PaymentMethod(uint256(input.paymentMethod)) ); ITokenLauncherPaymentModule.ProcessPaymentOutput memory paymentOutput = ITokenLauncherPaymentModule(paymentModule).processPayment{ value: msg.value }( paymentInput ); // Create the new ERC20 Token TokenFiERC721 token = new TokenFiERC721(input); bytes32 paymentAdminRole = ITokenMintPaymentModule(token.paymentModule()).PAYMENT_ADMIN_ROLE(); ITokenMintPaymentModule(input.tokenMintPaymentModule).grantRole(paymentAdminRole, address(token)); ``` ```solidity=36 constructor(ITokenLauncherERC721.CreateErc721Input memory _input) ERC721(_input.name, _input.symbol) { _baseTokenURI = _input.tokenURI; maxSupply = _input.maxSupply; collectionLogo = _input.collectionLogo; isPublicMintEnabled = _input.isPublicMintEnabled; isAdminMintEnabled = _input.isAdminMintEnabled; pricingModule = _input.tokenMintPricingModule; paymentModule = _input.tokenMintPaymentModule; ``` So, any malicious contracts can be assigned to the two state variables, `pricingModule` and `paymentModule`, and may charge zero fee for launching TokenFiERC721 contract. A similar issue also exists in the process of launching TokenFiERC1155. --- According to the code logic of `createV2LiquidityPool()` function, the router address used to create liquidity pool is passed in by users. However, the attacker user has chance to pass in a malicious router contract address because the absence of validation for `input.liquidityPoolDetails.routerAddress`. **TokenLauncherLiquidityPoolFactoryV1.sol** ```solidity=28 function createV2LiquidityPool(CreateV2Input memory input) external payable override onlyRole(DEFAULT_ADMIN_ROLE) returns (CreateV2Output memory) { IUniswapV2Router02 router = IUniswapV2Router02(input.liquidityPoolDetails.routerAddress); IERC20 sourceToken = IERC20(input.liquidityPoolDetails.sourceToken); uint256 sourceInitialBalance = sourceToken.balanceOf(address(this)); sourceToken.safeTransferFrom(input.owner, address(this), input.liquidityPoolDetails.amountSourceToken); uint256 sourceEarnedAmount = sourceToken.balanceOf(address(this)) - sourceInitialBalance; sourceToken.safeApprove(input.liquidityPoolDetails.routerAddress, sourceEarnedAmount); uint256 liquidity = _addLiquidity(input, sourceEarnedAmount); // Lock Liquidity if applicable address pairAddress = IUniswapV2Factory(router.factory()).getPair(input.liquidityPoolDetails.sourceToken, input.liquidityPoolDetails.pairedToken); if (input.lockLPDetails.lockLPTokenPercentage > 0) { _lockLiquidity(input, pairAddress); } return CreateV2Output({ liquidityPoolToken: pairAddress, liquidity: liquidity }); } ```
Open the reportAny user has the ability to utilize the `createV2LiquidityPool` function within the `TokenLauncherERC20` contract for creating a liquidity pool. However, it is crucial to note that this process mandates the TokenFi ERC20 allowance to be equal to or greater than the specified `input.liquidityPoolDetails.amountSourceToken`. The flaw in this code logic lies in its susceptibility to exploitation, as it allows potential attackers to generate profits by leveraging the assets of the token owner. **TokenLauncherERC20.sol** ```solidity=91 function createV2LiquidityPool(ITokenLauncherLiquidityPoolFactory.CreateV2Input memory input) external payable { // Prepare TokenFiERC20 token = TokenFiERC20(input.liquidityPoolDetails.sourceToken); uint256 balance = token.allowance(input.owner, liquidityPoolFactory); require(balance >= input.liquidityPoolDetails.amountSourceToken, "TokenLauncherERC20: Not enough allowance"); // Create LP ITokenLauncherLiquidityPoolFactory lpFactory = ITokenLauncherLiquidityPoolFactory(liquidityPoolFactory); ITokenLauncherLiquidityPoolFactory.CreateV2Output memory lpOutput = lpFactory.createV2LiquidityPool{ value: msg.value }(input); .... ``` **TokenLauncherLiquidityPoolFactoryV1.sol** ```solidity=30 function createV2LiquidityPool(CreateV2Input memory input) external payable override onlyRole(DEFAULT_ADMIN_ROLE) returns (CreateV2Output memory) { IUniswapV2Router02 router = IUniswapV2Router02(input.liquidityPoolDetails.routerAddress); IERC20 sourceToken = IERC20(input.liquidityPoolDetails.sourceToken); uint256 sourceInitialBalance = sourceToken.balanceOf(address(this)); sourceToken.safeTransferFrom(input.owner, address(this), input.liquidityPoolDetails.amountSourceToken); uint256 sourceEarnedAmount = sourceToken.balanceOf(address(this)) - sourceInitialBalance; sourceToken.safeApprove(input.liquidityPoolDetails.routerAddress, sourceEarnedAmount); uint256 liquidity = _addLiquidity(input, sourceEarnedAmount); // Lock Liquidity if applicable address pairAddress = IUniswapV2Factory(router.factory()).getPair(input.liquidityPoolDetails.sourceToken, input.liquidityPoolDetails.pairedToken); if (input.lockLPDetails.lockLPTokenPercentage > 0) { _lockLiquidity(input, pairAddress); } return CreateV2Output({ liquidityPoolToken: pairAddress, liquidity: liquidity }); } ``` ```solidity=47 function _addLiquidity(CreateV2Input memory input, uint256 sourceEarnedAmount) private returns (uint256 liquidity) { address to = address(this); if (input.lockLPDetails.lockLPTokenPercentage == 0) { to = input.treasury; } IUniswapV2Router02 router = IUniswapV2Router02(input.liquidityPoolDetails.routerAddress); address weth = router.WETH(); IERC20 pairedToken = IERC20(input.liquidityPoolDetails.pairedToken); if (input.liquidityPoolDetails.pairedToken == weth) { (, , liquidity) = router.addLiquidityETH{ value: msg.value }(input.liquidityPoolDetails.sourceToken, sourceEarnedAmount, 0, 0, to, block.timestamp); } else { .... ```
Open the reportThe `setTaxBasisPoints()` function allows the `_owner` to modify the `taxBasisPoints`, which determines the fee percentage. While there is a cap set at 4,000 basis points (equivalent to 40%), such a high fee rate could be unreasonably high for users. ```solidity function setTaxBasisPoints(uint256 newBasisPoints) external onlyOwner { require( newBasisPoints <= 4000, "StaticTaxHandler:setTaxBasisPoints:HIGHER_VALUE: Basis points cannot exceed 4,000." ); uint256 oldBasisPoints = taxBasisPoints; taxBasisPoints = newBasisPoints; emit TaxBasisPointsUpdated(oldBasisPoints, newBasisPoints); } ```
Open the report**TokenLauncherERC20.sol** ```solidity=49 // Now let's process the payment ITokenLauncherPaymentModule.ProcessPaymentInput memory paymentInput = ITokenLauncherPaymentModule.ProcessPaymentInput( msg.sender, input.referrer, price.usdPrice, ITokenLauncherPaymentModule.PaymentMethod(uint256(input.paymentMethod)) ); ``` There is no validation for the `input.referrer` when calling `createErc20()` in `TokenLauncherERC20.sol`. Anyone can launch TokenFiERC20 without paying referrer fee by setting the `input.referrer` to be zero address, even though that user is invited by others. **TokenLauncherPaymentModuleV1.sol** ```solidity=81 uint256 referrerUsdShare = 0; if (input.referrer != address(0)) { referrerUsdShare = (input.usdPrice * referrerBasisPoints) / 10000; usdToken.safeTransfer(input.referrer, referrerUsdShare); } ``` A similar issue also exists in `TokenLauncherERC1155` and `TokenLauncherERC721` contract.
Open the reportThe absence of validation for `msg.sender` within `constructor()` function in `TokenFiERC20` indicates that anyone can deploy a new `TokenFiERC20` contract with any inputs. However, the new `TokenFiERC20` contract isn't listed on launcher store.
Open the reportThe `balanceOf()` function call at line 130 retrieves the quantity of the pair token, which is not ETH. Therefore, the presence of the `Wei` unit, which is specifically associated with ETH measurements, in the local variable `remainingWeiBalance` indicating a non-ETH pair token amount is inappropriate. ```solidity=130 uint256 remainingWeiBalance = _pairToken.balanceOf(address(this)); if (remainingWeiBalance > 0) { _pairToken.safeTransfer(treasury, remainingWeiBalance); } ```
Open the reportCoin sniping is a trading strategy focused on purchasing a newly launched cryptocurrency token. Traders sell it for a profit when the hype spreads and the price pumps. Within the `TokenFiERC20` contract, there is no code logic for protecting sniper. Reference: https://medium.com/@ProdigySniperBot/snipe-to-success-the-best-sniper-bot-to-trade-shiba-inu-shib-and-shibarium-tokens-4668822bd5b1
Open the reportThe cited address input is missing a check that it is not `address(0)`.
Open the reportThe `updateTokenOwner()` function is designed to update tokenfi ERC20 owner. However, the tokenfi ERC20 owner can be set to the zero address because of the absence of zero address validation of the argument `newOwner`. **TokenLauncherStore.sol** ```solidity=54 address owner = tokenOwner[tokenAddress]; require(msg.sender == owner, "TokenLauncherStore: Not owner"); require(owner != newOwner, "TokenLauncherStore: Same owner"); ``` ```solidity=65 _tokensByOwner[tokenType][newOwner].push(tokenAddress); tokenOwner[tokenAddress] = newOwner; ```
Open the reportThe state variables, `isPublicMintEnabled` and `isAdminMintEnabled`, are intended to regulate NFT minting authority. However, a flaw in the current code allows both state variables to be set to `true` simultaneously, and their values are never updated. This situation leads to two issues: 1. Any account can mint NFTs, even if the NFT owner specifically desires only the Admin to have minting authority. This results in a lack of control over the minting process. 2. The functions `adminMint()` and `adminMintBatch()` become meaningless when the NFT owner prefers any account to have minting privileges. ```solidity=36 isPublicMintEnabled = _input.isPublicMintEnabled; isAdminMintEnabled = _input.isAdminMintEnabled; ``` ```solidity=65 function adminMint(address _to) external adminMintAvailable whenNotPaused supplyAvailable(1) onlyRole(DEFAULT_ADMIN_ROLE) { _mintBatch(_to, 1); } function adminMintBatch(address _to, uint256 _amount) external adminMintAvailable whenNotPaused supplyAvailable(_amount) onlyRole(DEFAULT_ADMIN_ROLE) { _mintBatch(_to, _amount); } ``` ```solidity=79 function mint(address _to) external payable publicMintAvailable whenNotPaused supplyAvailable(1) { _processPayment(1); _mintBatch(_to, 1); } function mintBatch(address _to, uint256 _amount) external payable publicMintAvailable whenNotPaused supplyAvailable(_amount) { _processPayment(_amount); _mintBatch(_to, _amount); } ``` A similar issue also exists in `TokenFiERC1155.sol`.
Open the reportIn the context of the reflection token's code logic, the condition `_tOwned[holder] > 0` indicates that the holder is exempted from reflection. The existing flaw in the code lies in the fact that, despite the value of `_tOwned[_input.treasury]` being assigned as `_input.initialSupply` and being greater than zero, the `treasury` account is not properly excluded from reflection. ```solidity=75 if (_input.fees.reflection.percentage > 0) { require(_input.initialSupply > 0, "TokenFiERC20.constructor: initialSupply must be greater than 0"); totalReflection.r = (MAX - (MAX % _input.initialSupply)); _rOwned[_input.treasury] = totalReflection.r; totalReflection.t = _input.initialSupply; _tOwned[_input.treasury] = _input.initialSupply; emit Transfer(address(0), _input.treasury, _input.initialSupply); } else { _mint(_input.treasury, _input.initialSupply); } ```
Open the reportIn general, the `_setURI()` function, designed to map the token metadata URI to its corresponding token ID, should be invoked once upon the creation of a new type token. Furthermore, token assets associated with the same token ID should utilize the identical metadata URI. ```solidity=42 for (uint256 i = 0; i < _input.initialTokens.length; i++) { require(_input.initialTokens[i].maxSupply > 0, "TokenFiERC1155: maxSupply must be greater than 0"); maxSupply[_input.initialTokens[i].tokenId] = _input.initialTokens[i].maxSupply; decimals[_input.initialTokens[i].tokenId] = _input.initialTokens[i].decimals; _setURI(_input.initialTokens[i].tokenId, _input.initialTokens[i].uri); existingTokenIds.push(_input.initialTokens[i].tokenId); } ``` ```solidity=70 function createToken(ITokenLauncherERC1155.CreateErc1155TokenInput memory input) external onlyRole(DEFAULT_ADMIN_ROLE) { require(!exists(input.tokenId), "TokenFiERC1155: token already exists"); require(input.maxSupply > 0, "TokenFiERC1155: maxSupply must be greater than 0"); maxSupply[input.tokenId] = input.maxSupply; decimals[input.tokenId] = input.decimals; _setURI(input.tokenId, input.uri); existingTokenIds.push(input.tokenId); } ``` The `DEFAULT_ADMIN_ROLE` in the `TokenFiERC1155` contract enables the updating of the metadata URI for a specific token during each minting process through the `adminMint()` or `mintWithSignature()` function. ```solidity=59 function adminMint( address _to, uint256 _tokenId, uint256 _amount, string memory _uri ) external whenNotPaused tokenExists(_tokenId) supplyAvailable(_tokenId) onlyRole(DEFAULT_ADMIN_ROLE) { require(isAdminMintEnabled, "TokenFiERC1155: admin mint disabled"); _setURI(_tokenId, _uri); _mint(_to, _tokenId, _amount, ""); } ``` ```solidity=98 function mintWithSignature( address _to, uint256 _tokenId, uint256 _amount, string memory _uri, string memory signature, uint256 nonce ) external whenNotPaused tokenExists(_tokenId) supplyAvailable(_tokenId) checkSignature(_uri, nonce, signature) { if (bytes(_uri).length > 0) { _setURI(_tokenId, _uri); } _mint(_to, _tokenId, _amount, ""); } ```
Open the reportThe linked functions are equivalent to the compiler-generated getter functions for the respective variables.
Open the reportThe linked variables assigned in the constructor can be declared as `immutable`. Immutable state variables can be assigned during contract creation but will remain constant throughout the lifetime of a deployed contract. A big advantage of immutable variables is that reading them is significantly cheaper than reading from regular state variables since they will not be stored in storage.
Open the reportThe function `transferFrom()` in an ERC20 contract is used in the above-mentioned contracts to receive the user payments to the contract. When the contract needs to transfer assets out, it is better to use the function `transfer()/safeTransfer()` because it is possible that the target `tokenAddress` does not support transfer from the user itself. ```solidity=343 function withdraw(address tokenAddress, uint256 amount) external onlyOwner { if (tokenAddress == address(0)) { payable(address(treasuryHandler)).sendValue(amount); } else { IERC20(tokenAddress).transferFrom(address(this), address(treasuryHandler), amount); } } ``` If the contract uses `transferFrom()` to transfer the ERC20 token from itself, and it has no approvals, it may revert transactions.
Open the reportIn a front-running attack, an attacker identifies a pending transaction on the public blockchain and jumps ahead of it (typically by paying higher gas fees). A sandwich attack is a specialized form of front-running. In this scenario, attackers identify users making token swaps or adding liquidity without appropriate slippage limits. The attacker then profits by buying the asset before the victim's transaction and selling it immediately afterwards. The following functions are called without setting restrictions on slippage or minimum output amount, so transactions triggering these functions are vulnerable to sandwich attacks, especially when the input amount is large: ```solidity=234 router.swapExactTokensForETHSupportingFeeOnTransferTokens(tokenAmount, 0, path, address(this), block.timestamp); ``` ```solidity=247 router.addLiquidityETH{ value: weiAmount }( address(token), tokenAmount, 0, 0, address(treasury), block.timestamp ); ```
Open the reportThe contract is serving as the underlying entity to interact with one or more third party protocols. The scope of the audit treats third party entities as black boxes and assumes their functional correctness. However, in the real world, third parties can be compromised and this may lead to lost or stolen assets. In addition, upgrades of third parties can possibly create severe impacts, such as increasing fees of third parties, migrating to new LP pools, etc. ```solidity=35 IUniswapV2Router02 public router; ``` - The contract `TreasuryHandlerAlpha` interacts with third party contract with `IUniswapV2Router02` interface via `router`.
Open the reportThe `withdraw()` function is designed to allow the `_owner` to withdraw locked Ether or ERC20 tokens. However, the contract lacks any `payable` or `receive()` function, making it incapable of holding or receiving Ether. ```solidity=339 function withdraw(address tokenAddress, uint256 amount) external onlyOwner { if (tokenAddress == address(0)) { payable(address(treasuryHandler)).sendValue(amount); } else { IERC20(tokenAddress).transferFrom(address(this), address(treasuryHandler), amount); } } ```
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="tokenfi"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us