XPERSONA (XPersona) security report
Radar by SecureChain.ai AI-scanned the verified XPERSONA contract on BSC. GoPlus reports no minting, blacklist, pause, selfdestruct, or honeypot signal. Slither was not run.
The contract is not shown to have mint, blacklist, pause, selfdestruct, or proxy-upgrade controls, and GoPlus reports it as non-mintable and not a honeypot.
Trust score: 48/100. Source verified: Yes. Chain: bsc. Contract: 0x2060dbe99915064a6d474f6a95cc620db8067777.
This report was last modified on 2026-09-30 and contains 4 material findings.
high finding: External dividend callback can block ordinary token transfers
When dividendContract is nonzero, every transfer involving a non-excluded sender or recipient calls dividendContract.setShare(). Any revert from that external call is propagated as DividendShareUpdateFailed, causing the token transfer itself to revert. A broken, incompatible, or intentionally reverting dividend contract can therefore prevent users from transferring tokens.
Evidence: In _afterTokenTransfer(), calls to IDividend(dividendContract).setShare(from, balanceOf(from)) and setShare(to, balanceOf(to)) are wrapped in catch blocks that execute revert DividendShareUpdateFailed(...). dividendContract is configured during initialize() and there is no setter or fallback path for failed share updates.
Recommendation: Do not make core ERC20 transfers depend on a reverting external callback. Use a non-blocking try/catch with deferred share synchronization, or add a controlled emergency bypass. Validate the dividend contract interface and test transfer behavior when it reverts or runs out of gas.
high finding: Tax rates are not bounded to 10000 basis points
The initializer accepts uint16 buyTax and sellTax values without requiring either value to be at most 10000. If a value above 10000 is configured, the calculated tax can exceed the transferred amount and amount - tax reverts. Once tax enforcement is active, buys or sells involving configured pools can become impossible, creating a honeypot-like or complete trading-lock condition.
Evidence: initialize() stores params.buyTax and params.sellTax directly in poolState. _getTaxWithPoolState() computes amount * rate / 10000, while _taxedTransfer() executes uint256 remainingAmount = amount - tax with checked arithmetic. The supplied ABI exposes initialize() but does not attest to the actual initialization parameters.
Recommendation: Enforce buyTax <= 10000 and sellTax <= 10000 during initialization. Ideally impose a lower protocol maximum, publish the configured rates, and test buys and sells across all pool states before enabling trading.
medium finding: Owner can delay migration and keep pool transfers restricted
While poolState is BondingCurve, _transfer() rejects any transfer to or from an address marked in pools. Only the owner can call startMigration() and finalizeMigration(), and there is no permissionless timeout or automatic transition. An owner that does not complete migration can keep configured pool trading restricted indefinitely.
Evidence: _transfer() requires !pools[from] && !pools[to] in PoolState.BondingCurve. startMigration() and finalizeMigration() are both external onlyOwner functions. GoPlus reports hidden_owner as 0 and provides an owner address, but that does not remove the owner's ability to control these state transitions.
Recommendation: Use a timelocked or multisignature migration authority, publish transition conditions, and add an automatic or permissionless recovery path where appropriate.
medium finding: Tax processing relies on a privileged external processor and transfers failed-tax balances to it
The token grants taxProcessor an effectively unlimited allowance and invokes it during liquidation. If processing reverts, the fallback transfers the remaining token balance held by the token contract directly to taxProcessor. A compromised or incorrectly configured processor can therefore control collected tax tokens and can also disrupt liquidation behavior.
Evidence: _processTax() approves taxProcessor for type(uint256).max and calls ITaxProcessor(taxProcessor).processTaxTokens(taxAmount). In the catch branch, it calls _plainTransfer(address(this), taxProcessor, remainingBalance). taxProcessor is set during initialize() and has no setter in the supplied contract.
Recommendation: Use a narrowly scoped allowance, verify the processor implementation and configuration before initialization, add explicit accounting for failed liquidations, and use a timelocked or multisignature-controlled processor replacement or recovery mechanism in the surrounding system.