The FlapTaxTokenV3 contract implements an upgradeable ERC20 token with dynamic tax mechanisms and pool state management. The contract leverages OpenZeppelin's upgradeable standards, contributing to a robust architectural foundation. However, a critical reentrancy vulnerability exists in the tax liquidation process, potentially allowing users to bypass taxes during transfers. Additionally, significant centralization risk is present due to owner-controlled state transitions, and a denial-of-service vector could block all token transfers if the external tax processor reverts.

On-chain security analysis — is it a scam or legit?
Is b-money Safe?
Not verified
This record has not gone through deep verification and is not being monitored. The score is a dated snapshot — the token’s risk can change at any time.Own this token? Put it under verification →The FlapTaxTokenV3 contract implements an upgradeable ERC20 token with dynamic tax mechanisms and pool state management.
Read the full analysis- Proxy contract
- Non-standard proxy storage
- Liquidity not locked, but no owner/deployer address holds LP — market-depth risk, not rug risk
- LP top1 unlocked holder = 61.4%
- LP top3 unlocked holders = 100.0%
- Token age < 30 days
- 1 Critical finding(s) from audit
- 2 Medium finding(s) from audit
- 2 Low finding(s) from audit
Exact factors
- Proxy contract (upgradeable — admin can replace logic)
- Non-standard proxy storage (Etherscan-confirmed)
- Liquidity not locked, but no owner/deployer address holds LP — market-depth risk, not rug risk
- LP top1 unlocked holder = 61.4% (independent LP — depth risk, pool = 99% of DEX liquidity)
- LP top3 unlocked holders = 100.0% (independent LP — depth risk, pool = 99% of DEX liquidity)
- Token age < 30 days (still settling)
- 1 Critical finding(s) from audit
- 2 Medium finding(s) from audit
- 2 Low finding(s) from audit
Each factor is an on-chain fact recorded at the time of this analysis. The score is computed from them by a deterministic function, so the same contract returns the same score for anyone who runs the audit. How scores are computed
Security Checklist
as of audit · Sep 4Holders
as of audit · Sep 4, 2026- Contractstreasury, vesting, bridge, staking20.1%
- Walletsanonymous holders, free to sell4.9%
- Everyone elsethe rest of the supply75.1%
How holder types affect the score
Share held by contracts — treasury, vesting, bridge or staking — is discounted against share held by wallets when the score is computed: a contract cannot decide to sell the way an anonymous holder can, though it can still be drained or voted to sell. Effective concentration is the figure the risk score is actually calculated from.
Holder balances as recorded by the audit of Sep 4, 2026
Liquidity
as of audit · Sep 4The risk score reads depth across every pair.
The LP tokens of the analysed pool are not locked or burned, so whoever holds them can withdraw that liquidity.
0x1584…cb020x14e5…be4aNo privileged address appears among these holders: the unlocked liquidity sits with independent providers, not with the deployer.
Security Findings
CriticalReentrancy Vulnerability Leading to Tax Bypass
The _liquidateTax function, called by _transfer, sets poolState.notLiquidating to false *before* making an external call to ITaxProcessor.processTax. If the taxProcessor contract is malicious or compromised, it can re-enter the _transfer function. During this re-entrant call, the _liquidateTax function will be called again. However, because poolState.notLiquidating is still false from the initial call, the tax processing logic within _liquidateTax will be skipped. More critically, the _getTaxWithPoolState function, which determines the tax amount for transfers, also checks currentPoolState.notLiquidating. If a re-entrant transfer occurs while notLiquidating is `f…C-01
Issue
The _liquidateTax function, called by _transfer, sets poolState.notLiquidating to false *before* making an external call to ITaxProcessor.processTax. If the taxProcessor contract is malicious or compromised, it can re-enter the _transfer function. During this re-entrant call, the _liquidateTax function will be called again. However, because poolState.notLiquidating is still false from the initial call, the tax processing logic within _liquidateTax will be skipped. More critically, the _getTaxWithPoolState function, which determines the tax amount for transfers, also checks currentPoolState.notLiquidating. If a re-entrant transfer occurs while notLiquidating is `f…
Fix
Implement the Checks-Effects-Interactions pattern strictly. Ensure all state changes related to notLiquidating are finalized *before* any external calls. A reentrancy guard on _liquidateTax or _transfer would also mitigate this. For example, set notLiquidating back to true immediately after the _processTax call, or use OpenZeppelin's ReentrancyGuard.
Status
Unresolved
MediumCentralization Risk with Owner-Controlled State Transitions
Functions startMigration and finalizeMigration are protected by the onlyOwner modifier. These functions control critical state transitions of the token's economic model, including enabling and disabling tax enforcement and anti-farmer mechanisms. This grants a single address (the owner) significant power over the token's operational state, representing a single point of failure and a high degree of trust in the owner's benevolence and security practices.M-01
Issue
Functions startMigration and finalizeMigration are protected by the onlyOwner modifier. These functions control critical state transitions of the token's economic model, including enabling and disabling tax enforcement and anti-farmer mechanisms. This grants a single address (the owner) significant power over the token's operational state, representing a single point of failure and a high degree of trust in the owner's benevolence and security practices.
Fix
Consider implementing a multi-signature wallet for ownership of the contract to distribute control and reduce the risk associated with a single private key compromise. For highly sensitive state transitions, consider adding a time-lock mechanism to allow the community or other stakeholders to react to proposed changes.
Status
Unresolved
MediumPotential for Denial of Service in Transfers due to External Call Reverts
The _transfer function calls _liquidateTax at its beginning. Inside _liquidateTax, an external call to ITaxProcessor.processTax(taxAmount) is made. If this external call reverts for any reason (e.g., the taxProcessor contract is paused, experiences an error, runs out of gas, or is malicious), the _liquidateTax function will revert, which in turn will cause the _transfer function to revert. This could effectively block all token transfers, leading to a denial of service for users.M-02
Issue
The _transfer function calls _liquidateTax at its beginning. Inside _liquidateTax, an external call to ITaxProcessor.processTax(taxAmount) is made. If this external call reverts for any reason (e.g., the taxProcessor contract is paused, experiences an error, runs out of gas, or is malicious), the _liquidateTax function will revert, which in turn will cause the _transfer function to revert. This could effectively block all token transfers, leading to a denial of service for users.
Fix
Implement robust error handling for the external call to ITaxProcessor.processTax. Consider using a try-catch block to gracefully handle reverts from the external contract, allowing transfers to proceed even if tax processing fails. Alternatively, make the _processTax call an onlyOwner function or a function callable by anyone but with a mechanism to disable automatic liquidation if it consistently fails, preventing it from blocking all transfers.
Status
Unresolved
LowInefficient Loop in initialize for Pool Addresses
The initialize function iterates through the params.pools array to set pools[address] = true for each address. While functional, if the params.pools array contains a very large number of addresses, this loop could lead to high gas costs during contract deployment or exceed block gas limits on certain networks, making initialization prohibitively expensive or impossible.L-01
initialize for Pool AddressesIssue
The initialize function iterates through the params.pools array to set pools[address] = true for each address. While functional, if the params.pools array contains a very large number of addresses, this loop could lead to high gas costs during contract deployment or exceed block gas limits on certain networks, making initialization prohibitively expensive or impossible.
Fix
For scenarios involving a large number of pool addresses, consider implementing a separate addPools function that can be called multiple times by the owner after initialization, or a more gas-efficient batching mechanism that processes addresses in smaller chunks.
Status
Unresolved
LowHardcoded maxSupply as a Constant
The maxSupply variable is declared as a constant with a value of 1e9 ether. While this defines a clear maximum supply, its immutability means that the total supply can never be adjusted in the future without a full contract upgrade. This design choice removes flexibility for potential future economic adjustments, such as burning tokens to reduce supply or minting for specific purposes beyond the initial supply.L-02
maxSupply as a ConstantIssue
The maxSupply variable is declared as a constant with a value of 1e9 ether. While this defines a clear maximum supply, its immutability means that the total supply can never be adjusted in the future without a full contract upgrade. This design choice removes flexibility for potential future economic adjustments, such as burning tokens to reduce supply or minting for specific purposes beyond the initial supply.
Fix
If future flexibility regarding total supply is desired, consider making maxSupply a public state variable that can be set by the owner during initialization or through a governance mechanism. Alternatively, if the token is intended to be fully elastic, remove the maxSupply constraint entirely.
Status
Unresolved
InfoReliance on block.timestamp for Time-Sensitive Logic
The contract uses block.timestamp to calculate taxExpirationTime and antiFarmerExpirationTime. While commonly used, block.timestamp can be manipulated by miners within a certain range (e.g., up to 900 seconds on Ethereum, potentially more on other EVM chains) to slightly influence the timing of these events. For expiration times, the risk is generally low, but it's a known characteristic of block.timestamp.I-01
block.timestamp for Time-Sensitive LogicIssue
The contract uses block.timestamp to calculate taxExpirationTime and antiFarmerExpirationTime. While commonly used, block.timestamp can be manipulated by miners within a certain range (e.g., up to 900 seconds on Ethereum, potentially more on other EVM chains) to slightly influence the timing of these events. For expiration times, the risk is generally low, but it's a known characteristic of block.timestamp.
Fix
Be aware of the potential for block.timestamp manipulation. For critical, short-duration events where precise timing is paramount, consider using block.number in conjunction with average block times, or external oracle solutions like Chainlink Keepers for more robust time-based execution. For expiration times, the current implementation is generally acceptable.
Status
Unresolved
Analysis
AI Copilotas of audit · Sep 4The contract utilizes OpenZeppelin's upgradeable ERC20 and Ownable patterns, providing a solid base for code security (7.2 Code Security) and architecture (7.1 Architecture). The use of SafeERC20 is commendable. However, a critical reentrancy vulnerability in the _liquidateTax function (7.2 Code Security) allows for tax bypass. Furthermore, the reliance on an external ITaxProcessor without robust error handling introduces a denial-of-service risk for all token transfers (7.6 External, 7.8 Operations).
The contract's economic model (7.4 Economic) is centered around dynamic tax rates and state transitions (e.g., BondingCurve, TaxEnforcedAntiFarmer). The onlyOwner modifier protects critical functions like startMigration and finalizeMigration (7.5 Governance, 7.3 Access Control), but this introduces a high degree of centralization risk. The hardcoded maxSupply limits future flexibility, and the antiFarmerDuration and taxExpirationTime rely on block.timestamp, which has minor manipulation risks.
The contract is designed as an upgradeable proxy using OpenZeppelin's Initializable pattern (7.7 Upgrades). The initialize function is correctly protected by the initializer modifier, and _disableInitializers() is called in the constructor to prevent re-initialization of the implementation contract. This setup adheres to standard upgradeability best practices.
Prioritize resolving the critical reentrancy vulnerability in _liquidateTax by strictly adhering to the Checks-Effects-Interactions pattern or implementing a reentrancy guard.
Implement robust error handling for external calls to ITaxProcessor.processTax to prevent denial-of-service for token transfers. Consider decentralizing control over critical state transitions by moving towards a multi-signature wallet or a time-locked governance mechanism for the onlyOwner functions.
Contract & Record
Free for any token on the record. It links to this page and always shows the current score and the date it was checked.
- Sep 4, 202655Medium
- Aug 31, 202636Medium
Score changes are recorded as dated snapshots — history is append-only and cannot be edited.
Would You Like a More Detailed Audit of b-money?
Our AI-powered scanner gives you a deeper, real-time smart contract analysis — free, with every scoring factor shown.
Get Detailed Audit Is this your token? Publish your own audit on this page