ShopinX Info

The global retail industry is undergoing a significant transformation as customer acquisition and retention costs reach historic highs. Brands continue allocating billions of dollars to loyalty initiatives in an effort to retain customers within their ecosystems. However, a substantial portion of consumers ultimately abandon these programs due to the limited flexibility and utility of closed-loop reward structures. At the core of this challenge lies the traditional database architecture underpinning most loyalty systems, which prevents true user ownership and restricts the free movement of rewards and value across independent networks.

ShopinX Logo

TrustNet Score

The TrustNet Score evaluates crypto projects based on audit results, security, KYC verification, and social media presence. This score offers a quick, transparent view of a project's credibility, helping users make informed decisions in the Web3 space.

2.41
Poor Excellent

Real-Time Threat Detection

Real-time threat detection, powered by Cyvers.io, is currently not activated for this project.

This advanced feature provides continuous monitoring and instant alerts to safeguard your assets from potential security threats. Real-time detection enhances your project's security by proactively identifying and mitigating risks. For more information, click here.

Security Assessments

"Static Analysis Dynamic Analysis Symbolic Execution SWC Check Manual Review"
Contract address
N/A
Network N/A
License N/A
Compiler N/A
Type N/A
Language Solidity
Onboard date 2026/04/23
Revision date 2026/04/23

Summary and Final Words

No crucial issues found

The contract does not contain issues of high or medium criticality. This means that no known vulnerabilities were found in the source code.

Contract owner cannot mint

It is not possible to mint new tokens.

Contract owner can blacklist addresses

It is possible to lock user funds by blacklisting addresses.

Contract owner cannot set high fees

The fees, if applicable, can be a maximum of 25% or lower. The contract can therefore not be locked. Please take a look in the comment section for more details.

Token transfer can be locked

Owner can lock user funds with owner functions.

Token can be burned

There is a function to burn tokens in the contract without any allowances.

Ownership is not renounced

The owner retains significant control, which could potentially be used to modify key contract parameters.

Contract is not upgradeable

The contract does not use proxy patterns or other mechanisms to allow future upgrades. Its behavior is locked in its current state.

Scope of Work

This audit encompasses the evaluation of the files listed below, each verified with a SHA-1 Hash. The team referenced above has provided the necessary files for assessment.

The auditing process consists of the following systematic steps:

  1. Specification Review: Analyze the provided specifications, source code, and instructions to fully understand the smart contract's size, scope, and functionality.
  2. Manual Code Examination: Conduct a thorough line-by-line review of the source code to identify potential vulnerabilities and areas for improvement.
  3. Specification Alignment: Ensure that the code accurately implements the provided specifications and intended functionalities.
  4. Test Coverage Assessment: Evaluate the extent and effectiveness of test cases in covering the codebase, identifying any gaps in testing.
  5. Symbolic Execution: Analyze the smart contract to determine how various inputs affect execution paths, identifying potential edge cases and vulnerabilities.
  6. Best Practices Evaluation: Assess the smart contracts against established industry and academic best practices to enhance efficiency, maintainability, and security.
  7. Actionable Recommendations: Provide detailed, specific, and actionable steps to secure and optimize the smart contracts.

A file with a different Hash has been intentionally or otherwise modified after the security review. A different Hash may indicate a changed condition or potential vulnerability that was not within the scope of this review.

Final Words

The following provides a concise summary of the audit report, accompanied by insightful comments from the auditor. This overview captures the key findings and observations, offering valuable context and clarity.


Smart Contract Analysis Statement

Contract Analysis

The ShopinX Token (SPX) contract implements an ERC20 token with a fixed supply of 300,000,000 SPX, EIP-2612 gasless approvals, holder self-burn, owner-controlled time-locks, and an owner-controlled linear step-vesting module. While the overall design follows common patterns on Ethereum, a few areas need attention:

  • The setLock primitive has no upper bound on the unlock timestamp and locks can only be extended, never reduced; the owner can therefore freeze any holder permanently and irreversibly, which is a de-facto blacklist regardless of how the project chooses to label it.
  • transferWithLock stores its lock as a per-address timestamp rather than against a specific amount, so sending even a minimal transfer to an address applies the lock to that address's entire current and future balance.
  • The availableToTransfer view diverges from the rules enforced inside _update whenever an address has both a time-lock and a vesting schedule, which can mislead user interfaces and integrations about how many tokens are actually movable.
  • All administrative powers sit behind a single-key, one-step Ownable owner, with no on-chain delay, no multisig requirement and no two-step transfer, and renounceOwnership is exposed and would permanently disable every administrative path if called by mistake.

Ownership Privileges

The ownership of the contract has been assigned to a single externally-owned account passed at deployment time through OpenZeppelin's Ownable, with no two-step transfer, timelock or multisig enforced in-contract. The owner retains full privileges including:

  • Extending the time-lock on any address through setLock, up to and including type(uint256).max, with no on-chain cap.
  • Combining a transfer with a time-lock on the recipient through transferWithLock, which also freezes any unrelated balance the recipient already holds or later receives.
  • Creating a one-time vesting schedule on any address through transferWithVesting, with freely chosen cliff, step duration and step percent.
  • Transferring or renouncing ownership in a single transaction.
  • The owner cannot mint new tokens; the supply is fixed at 300,000,000 SPX minted once in the constructor and there is no mint entrypoint or minter role.
  • The owner cannot set or change any fee or tax; no fee logic exists in the contract.
  • The owner cannot upgrade the contract; the code is deployed directly, without a proxy, so the bytecode is immutable after deployment.
  • The owner cannot reduce or remove an existing time-lock, cancel a vesting schedule, or amend a vesting schedule once it has been created, because the lock rule is strictly monotonic and transferWithVesting rejects any target that already has a schedule.

Security Features

The contract implements several positive security features:

  • It inherits from well-audited OpenZeppelin v5 building blocks (ERC20, ERC20Burnable, ERC20Permit, Ownable) and compiles cleanly with Solidity 0.8.27, benefiting from built-in overflow and underflow checks on every arithmetic operation.
  • The supply is fixed at deployment and cannot be inflated; there is no mint function, no minter role and no fee or tax logic that could be tuned by the owner.
  • The transfer gate is implemented in a single override of _update, which means every token movement — transfers, transferFrom and burns — goes through the same lock and vesting checks, avoiding the common class of bugs where an alternate code path bypasses access control.
  • The contract has no external calls, no low-level calls, no assembly, no delegate-calls and no upgrade mechanism, which eliminates reentrancy, storage collisions and proxy-upgrade risk by construction.
  • EIP-2612 support is provided natively through ERC20Permit, enabling gasless approvals for end users while relying on the battle-tested OpenZeppelin implementation of the permit signature logic.

Note - This Audit report consists of a security analysis of the ShopinX Token (SPX) smart contract. This analysis did not include economic analysis of the contract's tokenomics. Moreover, we only audited the main contract for the ShopinX Token (SPX) team. Other contracts associated with the project were not audited by our team. We recommend investors do their own research before investing.

Files and details

Functions
public

/

State variables
public

/

Total lines
of code

/

Capabilities
Hover on items

/

Findings and Audit result

high Issues | 2 findings

Pending

#1 high Issue
Owner can irreversibly freeze any holder via setLock
ShopinXToken.sol
L43-48
Description

setLock accepts any unlockTime strictly greater than block.timestamp, with no upper bound. Combined with the invariant that a lock can only ever be extended, never reduced, this allows the owner to call setLock(victim, type(uint256).max) and freeze every token the victim currently holds as well as every token they ever receive afterwards, because _update uses lockUntil[from] as the authoritative gate on every outgoing transfer. The freeze cannot be reduced or removed by any party afterwards, not even the owner, because of the monotonic-only rule.

Pending

#2 high Issue
transferWithLock freezes the recipient's entire pre-existing and future balance
ShopinXToken.sol
L35-41
Description

transferWithLock stores the lock as a timestamp on the recipient's address (lockUntil[to] = unlockTime). Because _update checks that timestamp for every outgoing transfer, the lock extends to the recipient's whole balance at that moment and to any tokens they later receive from third parties, not just to the amount transferred in the call. A single wei of SPX sent to a market-maker, exchange hot wallet or retail holder with a far-future unlockTime freezes that address entirely for the duration of the lock.

medium Issues | 4 findings

Pending

#1 medium Issue
availableToTransfer view is inconsistent with the transfer rules enforced in _update
ShopinXToken.sol
L102-113
L119-127
Description

When an address carries both a time-lock (lockUntil in the future) and a vesting schedule, availableToTransfer evaluates only the vesting branch and can return a non-zero value, while _update still reverts the actual transfer because it checks block.timestamp >= lockUntil[from] first. User interfaces, routers, bots and accounting tools relying on this view will mislead users about what they can actually move. This also means transferWithVesting never inspects lockUntil[to] when writing a new schedule, which is a silent way to land an address in the inconsistent state above.

Pending

#2 medium Issue
Single-step Ownable with broad, irreversible operational powers
ShopinXToken.sol
L7
L9
Description

The contract uses a single-key, one-step Ownable for all administrative functions (lock creation, lock extension, vesting creation). A mistyped transferOwnership sends ownership to an unusable address in a single transaction, and there is no on-chain delay or co-signer requirement between a privileged call being initiated and it taking effect. Given the impact of setLock, transferWithLock and transferWithVesting, this access-control model is thinner than best practice for a production token.

Pending

#3 medium Issue
Vesting lien is enforced as a fungible reserve against the entire balance rather than a segregated escrow
ShopinXToken.sol
L54-76
L115-131
Description

The _update hook enforces balanceOf(from) >= value + (v.totalAmount - vestedAmount(from)). The result is a fungible reserve: any tokens in the account can satisfy the locked amount, and granted tokens are not distinguished from pre-existing or later-received tokens. This does not freeze unrelated balance, but it does couple the accounting of the grant to the beneficiary's wider balance, which can confuse off-chain tooling that expects vesting grants to be held in a separate escrow. It also means burning, slashing or clawing back granted tokens in isolation is impossible.

Pending

#4 medium Issue
Locked and undervested holders cannot burn their tokens
ShopinXToken.sol
L115-131
Description

_update applies the lock and vesting checks to every non-mint transition, including _burn (which routes through _update with to == address(0)). A holder under an active lock or with an undervested grant therefore cannot exercise the burn / burnFrom interface inherited from ERC20Burnable. This contradicts the common user expectation that holders can always destroy tokens they own, and makes the advertised 'burnable' behaviour conditional in a way that is not documented.

low Issues | 3 findings

Pending

#1 low Issue
Floating Solidity pragma and default PUSH0 target
ShopinXToken.sol
L2
Description

The contract uses a floating pragma ^0.8.27, allowing compilation with any compatible 0.8.27+ compiler. Combined with the default Shanghai target of modern solc, the emitted bytecode uses PUSH0, which is not universally supported across EVM-compatible chains. Two rebuilds of the same source can therefore produce different artifacts, and deployment can silently fail on chains that do not implement PUSH0.

Pending

#2 low Issue
Missing input validation on privileged functions
ShopinXToken.sol
L35
L43
L54
Description

The privileged entrypoints do not validate inputs that have operational impact. setLock accepts address(0) because it never calls _transfer. transferWithLock and transferWithVesting do not require amount > 0, so a zero-amount call silently consumes a vesting slot forever (combined with the write-once rule on vesting). transferWithVesting also lacks upper bounds on cliffDays and stepDays, allowing pathological schedule parameters.

Pending

#3 low Issue
renounceOwnership is exposed and would be destructive
ShopinXToken.sol
L7
L9
Description

OpenZeppelin's Ownable exposes renounceOwnership, which sets the owner to the zero address. Given the design of this contract, a renounced ownership makes every existing lock permanent and every existing vesting schedule frozen in place, with no path to correct a mistake. There is no operational reason to keep this function active in a production deployment of this token.

optimization Issues | 4 findings

Pending

#1 optimization Issue
Mark public administrative and view functions as external
ShopinXToken.sol
L35
L43
L50
L54
L102
Description

Several administrative and view functions are declared public but are never called from within the contract. public adds a small gas overhead because the arguments are copied from calldata into memory. external is the appropriate visibility whenever internal consumption is not needed, and it also communicates more clearly that these functions are part of the contract's external surface only.

Pending

#2 optimization Issue
Replace repeated numeric literals with named constants
ShopinXToken.sol
L32
L61
L63
L89
L94
L96
Description

The literal 100 is repeated in percent validation and in the vesting maths, and the initial supply 300_000_000 * 10 ** decimals() is written as an inline expression in the constructor. Promoting these to named constants improves readability and reduces bytecode duplication, while also making the cap part of the contract's public API.

Pending

#3 optimization Issue
Prefer multiplication before division to reduce precision loss
ShopinXToken.sol
L91-99
Description

vestedAmount performs a division (afterCliff / stepSeconds) before multiplying by stepPercent, and computes currentStepAmount by dividing totalAmount by 100 before multiplying by currentStepElapsed. Reordering to divide last keeps precision maximal and prevents rounding errors from accumulating across steps.

Pending

#4 optimization Issue
Pack VestingInfo fields to reduce storage writes
ShopinXToken.sol
L13-21
Description

VestingInfo declares five independent uint256 fields. The values stored there (token amounts bounded by the 300M supply, day counts and a percentage) all fit in smaller integer types, so the struct can be packed into fewer storage slots without loss of precision.

informational Issues | 8 findings

Pending

#1 informational Issue
Vesting schedules are write-once and cannot be cancelled or amended
ShopinXToken.sol
L64
Description

The check require(vesting[to].totalAmount == 0) prevents any subsequent update to a vesting schedule once it has been created, and the contract exposes no function to cancel or amend one. Whether this is acceptable depends on the product requirements around vesting and is therefore flagged as a product and operations decision rather than a bug.

Pending

#2 informational Issue
Vesting releases tokens continuously within each step
ShopinXToken.sol
L91-99
Description

After the cliff ends, vestedAmount releases tokens continuously each second inside the current step. Classic cliff-plus-step schedules typically unlock tokens discretely at step boundaries. Whether the continuous behaviour is correct depends on what the project has advertised; it is not itself a security bug, but it is a meaningful product decision worth explicitly documenting.

Pending

#3 informational Issue
permit allowances can still be signed while an account is locked
ShopinXToken.sol
L115-131
Description

A locked holder can still produce valid EIP-2612 permit signatures because permit only updates allowance. A subsequent transferFrom will revert in _update because of the lock. This matches the standard semantics of permit, which never promises that a downstream transfer will succeed, and is therefore documented here as informational rather than as a vulnerability.

Pending

#4 informational Issue
Reliance on block.timestamp for time comparisons
ShopinXToken.sol
L36
L44
L51
L82
L105
L121
Description

All lock checks, vesting progress checks and availability checks use block.timestamp. Validators have a limited ability to influence this value, which is irrelevant at the day-level resolution used by the vesting and lock logic. Listed for completeness because it is an industry-standard disclosure.

Pending

#5 informational Issue
Modulo used for interpolation flagged as weak randomness
ShopinXToken.sol
L95
Description

Static analysis reports afterCliff % stepSeconds as a weak pseudo-random number generator. The modulo here is purely a deterministic interpolation inside a vesting step and not a random value. Listed for documentation only.

Pending

#6 informational Issue
Strict equality v.totalAmount == 0 flagged as dangerous
ShopinXToken.sol
L64
L80
L104
Description

Static analysis flags v.totalAmount == 0 as a dangerous strict equality. Here zero is the default value for an uninitialised VestingInfo, so the comparison is the standard way to check whether a schedule has been set. Listed for documentation only.

Pending

#7 informational Issue
Verify EIP-712 domain separator after deployment
ShopinXToken.sol
L28
L30
Description

ERC20Permit is initialised with the name 'ShopinX Token', which matches the ERC20 name. A routine post-deployment check should confirm that the domain separator computed off-chain matches the one returned by the deployed contract, to rule out metadata or constructor-argument drift.

Pending

#8 informational Issue
Recipient and initial owner addresses should be publicly documented
ShopinXToken.sol
L27-33
Description

The constructor receives the initial 300M recipient and the administrative owner as two arguments, with no on-chain labelling of what each address represents. Clear documentation of these addresses is essential for downstream trust evaluation given the broad administrative powers described elsewhere in this report.