An upgradeable ERC-20 rewards token for the BlessUP referral ecosystem.
| Component | Version |
|---|---|
| Solidity | 0.8.28 |
| Foundry | v1.5.1 |
| OpenZeppelin Upgradeable | 5.5.0 |
ACT.X is a fixed-supply ERC-20 token with:
- 100,000,000 ACTX total supply (minted once at deployment)
- Transaction tax (0-10%) sent to reservoir for ecosystem recycling
- Reward distribution from pre-funded pool with replay protection
- UUPS upgradeability with 48-hour timelock protection
- Role-based access control for granular permissions
| Token Proxy | 0x7F6A84e2971016E515bda7b2948A8583985aF624 | View |
| TimelockController | 0x2e01317084250bf05dFa01feEf349bEEEF2BA5b4 | View |
| Implementation | 0xB05278D719c03D48be45A2Fe16b800EE3C5efB03 | View |
Gas Used: 0.00436 ETH (~$10 at current prices)
┌─────────────────────────────────────────────────────────────────┐
│ ERC1967 Proxy │
│ (Deployed Contract) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ACTXToken.sol │
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ ERC20Upgradeable│ │ AccessControl │ │ UUPSUpgradeable│ │
│ └─────────────────┘ └─────────────────┘ └────────────────┘ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ERC20Pausable │ │ ACTXStorageV1 │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ TimelockController │
│ (48-hour upgrade delay) │
└─────────────────────────────────────────────────────────────────┘
| Parameter | Value |
|---|---|
| Name | ACT.X |
| Symbol | ACTX |
| Total Supply | 100,000,000 ACTX |
| Decimals | 18 |
| Max Tax Rate | 10% (1000 basis points) |
| Default Tax Rate | 2% (200 basis points) |
Treasury (100M ACTX at deployment)
│
├─── Transfer to Users ──────► 2% tax to Reservoir
│
├─── Fund Reward Pool ───────► Tokens held by contract
│
└─── Distribute Rewards ─────► Transfer from contract (replay-protected)
| Role | Permissions |
|---|---|
DEFAULT_ADMIN_ROLE |
Manage roles, fund reward pool |
REWARD_MANAGER_ROLE |
Distribute rewards (with rewardId) |
TAX_ADMIN_ROLE |
Set tax rate, reservoir address, exemptions |
PAUSER_ROLE |
Emergency pause/unpause transfers |
UPGRADER_ROLE |
Authorize upgrades (pre-timelock only) |
- Pre-timelock: Requires
UPGRADER_ROLE(treasury multi-sig) - Post-timelock: Only
TimelockControllercan upgrade - 48-hour delay: All upgrades require waiting period
- One-time lock:
setTimelockController()cannot be called twice
Every reward distribution requires a unique rewardId (bytes32):
function distributeReward(address recipient, uint256 amount, bytes32 rewardId)rewardIdis tracked on-chain inusedRewardIdsmapping- Duplicate
rewardIdreverts withRewardIdAlreadyUsed - Backend should generate:
keccak256(userId + activityId + timestamp)
- EIP-7201 namespaced storage pattern
__gap[50]reserved for future storage slots- No storage collisions across upgrades
- Foundry
- Git
git clone https://github.com/ayushns01/act.x-token.git
cd act.x-token
forge installforge build# Run all tests
forge test -vv
# Run with gas reporting
forge test --gas-report
# Run specific test file
forge test --match-path test/unit/ACTXToken.t.sol -vvforge coverage --report summaryCreate .env file:
PRIVATE_KEY=your_deployer_private_key
TREASURY_ADDRESS=0x... # Must be a multi-sig (e.g., Safe)
RESERVOIR_ADDRESS=0x...
INITIAL_TAX_RATE=200 # 2% in basis points
BASE_RPC_URL=https://mainnet.base.org
BASESCAN_API_KEY=your_api_keysource .env
forge script script/DeployWithTimelock.s.sol --rpc-url $BASE_RPC_URL --broadcast --verifyThis deploys:
- ACTXToken implementation
- ERC1967 Proxy
- TimelockController (48-hour delay)
- Sets timelock as upgrade controller
# 1. Schedule upgrade (starts 48-hour countdown)
forge script script/UpgradeACTX.s.sol --rpc-url $BASE_RPC_URL --broadcast
# 2. Wait 48 hours
# 3. Execute upgrade
forge script script/UpgradeACTX.s.sol:ExecuteUpgrade --rpc-url $BASE_RPC_URL --broadcastbless-up/
├── src/
│ ├── ACTXToken.sol # Main upgradeable token
│ ├── Vesting.sol # 4-year cliff vesting
│ ├── Airdrop.sol # Merkle + KYC airdrop
│ ├── interfaces/
│ │ ├── IACTXToken.sol
│ │ ├── IAirdrop.sol
│ │ └── IVesting.sol
│ ├── libraries/
│ │ └── Errors.sol # Custom errors
│ └── storage/
│ └── ACTXStorageV1.sol # Namespaced storage
├── test/
│ ├── unit/ # 60 unit tests
│ ├── fuzz/ # 5 fuzz tests
│ └── invariant/ # 4 invariant tests
├── script/
│ ├── DeployACTX.s.sol # Basic deployment
│ ├── DeployWithTimelock.s.sol# Production deployment
│ ├── DeployBonus.s.sol # Vesting + Airdrop
│ └── UpgradeACTX.s.sol # Upgrade scripts
├── docs/
│ ├── OFFCHAIN_INTEGRATION.md # Backend integration guide
│ └── SECURITY.md # Security architecture
└── audit/
└── gas-snapshot.txt # Gas benchmarks
Ran 69 tests: 69 passed, 0 failed
╭────────────────────────┬────────┬────────┬─────────╮
│ Test Suite │ Passed │ Failed │ Skipped │
╞════════════════════════╪════════╪════════╪═════════╡
│ ACTXTokenTest │ 38 │ 0 │ 0 │
│ ACTXTokenFuzzTest │ 5 │ 0 │ 0 │
│ ACTXTokenInvariantTest │ 4 │ 0 │ 0 │
│ AirdropTest │ 12 │ 0 │ 0 │
│ VestingTest │ 10 │ 0 │ 0 │
╰────────────────────────┴────────┴────────┴─────────╯
| File | Line | Branch | Function |
|---|---|---|---|
| ACTXToken.sol | 100.00% | 98.99% | 100.00% |
| ACTXStorageV1.sol | 100.00% | 100.00% | 100.00% |
| Airdrop.sol | 81.48% | 77.78% | 100.00% |
| Vesting.sol | 86.79% | 84.75% | 80.00% |
invariant_TotalSupplyExactly100M() // totalSupply() == 100M (never changes)
invariant_RewardPoolNeverNegative() // rewardPoolBalance >= 0
invariant_TaxRateNeverExceedsMax() // taxRate <= 1000 (10%)
invariant_ReservoirAddressNeverZero() // reservoir != address(0)[profile.default.fuzz]
runs = 1000
[profile.default.invariant]
runs = 256
depth = 15// Generate unique rewardId
const rewardId = ethers.keccak256(
ethers.solidityPacked(
['address', 'string', 'uint256'],
[userAddress, activityId, Date.now()]
)
);
// Check if already used (idempotency)
const isUsed = await token.isRewardIdUsed(rewardId);
if (isUsed) {
console.log('Reward already distributed');
return;
}
// Distribute reward
const tx = await token.distributeReward(userAddress, amount, rewardId);
await tx.wait();token.on('RewardDistributed', (recipient, amount, rewardId) => {
console.log(`Reward ${rewardId}: ${amount} to ${recipient}`);
markRewardComplete(rewardId);
});
token.on('TaxCollected', (from, to, taxAmount, netAmount) => {
console.log(`Tax: ${taxAmount} from ${from}`);
});See OFFCHAIN_INTEGRATION.md for complete backend guide.
| Operation | Gas |
|---|---|
| Transfer (with tax) | ~99,000 |
| Transfer (tax exempt) | ~59,000 |
| Distribute Reward | ~94,000 |
| Fund Reward Pool | ~59,000 |
| Set Tax Rate | ~26,000 |
| Contract | Description |
|---|---|
ACTXToken.sol |
Main upgradeable ERC-20 token |
ACTXStorageV1.sol |
Namespaced storage (EIP-7201) |
| Contract | Description |
|---|---|
Vesting.sol |
4-year linear vesting with 1-year cliff |
Airdrop.sol |
Merkle tree airdrop with KYC verification |
- Internal review complete
- External audit pending
- Multi-sig not enforced on-chain — Treasury address MUST be a Safe multi-sig
- Tax can be set to 0% — Intentional for flexibility
- Vesting/Airdrop use Ownable — Simpler governance for non-upgradeable contracts
See SECURITY.md for full security architecture.
| Package | Version |
|---|---|
| OpenZeppelin Contracts Upgradeable | 5.5.0 |
| Forge Std | Latest |
| Solidity | 0.8.28 |
MIT License — see LICENSE
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Run tests (
forge test) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
- Project: BlessUP
- Token: ACT.X (ACTX)
- Network: Base L2