Multi-round token presale with integrated vesting on Base.
The SELF public presale will not proceed. The deployed contract was never initialized,
contribute()was never callable, and no user ever contributed. No USDC was ever raised and no SELF was ever allocated to any buyer.All SELF funded into the contract was recovered successfully through the contract's 7-day emergency withdrawal timelock. The presale contract now holds 0 SELF.
This repository remains published for transparency and to keep the audited source of the deployed bytecode available. The contracts below should be treated as retired. Nothing here is an offer to sell tokens.
contracts/
├── SELFToken.sol (~20 lines - Standard ERC20)
└── SELFPresale.sol (~700 lines - Multi-round presale)
- Standard OpenZeppelin ERC20
- Fixed supply: 500,000,000 tokens
- OpenZeppelin v4.9.6
- 5 sequential rounds with role-managed round progression
- Progressive pricing: $0.06 → $0.10 per token
- Presale allocation: exactly 37,934,515 SELF; raise target up to approximately $2.5M
- Contribution limits: $100 - $10,000 per wallet (cumulative)
- Vesting: 40% TGE unlock + linear 12-month vesting
- No round bonuses
- Payment: USDC (native Circle) 6 decimals
- OpenZeppelin v4.9.6: AccessControl, ReentrancyGuard, Pausable, SafeERC20
The presale was deployed on Base mainnet and never initialized. initializeRounds was never executed, so contribute() reverted with RoundsNotInitialized for the contract's entire life.
- Presale: 0x9D762B5E519d6194aa829F31cF85317FE37Fe35d — cancelled, recovery completed successfully
- SELF token: 0xCBFc34863982f7563774F73004fd231982Ff0303
- Base USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
- Funded allocation: exactly 37,934,515 SELF — recovered successfully
- Total raised: $0
- Total participants: 0
- Verification: exact-match source verified on BaseScan
Anyone can confirm the above independently on BaseScan by reading roundsInitialized() (false), totalAllocatedSELF() (0), and getPresaleStats() on the presale address.
- Role-based access control (5 roles)
- Timelock delays (2-7 days on critical operations)
- Circuit breaker ($500k daily withdrawal limit)
- Flash loan protection (2-block cooldown)
- Whale protection (10% max per tx)
- Rate limiting ($100k/hour per wallet)
- Custom errors (gas optimized)
Base USDC uses 6 decimals (native Circle USDC).
Contract: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
uint256 constant MIN_CONTRIBUTION = 100 * 1e6; // $100
uint256 constant MAX_CONTRIBUTION = 10_000 * 1e6; // $10,000
uint256 constant HARD_CAP = 2_500_000 * 1e6; // $2.5MTotal Supply: 500,000,000 SELF (fixed, non-mintable)
Exactly 37,934,515 SELF was allocated to the presale contract and recovered after cancellation. Full token allocation information is published at https://docs.self.app/tokenomics.
All critical operations enforce mandatory timelock delays for community transparency:
| Operation | Timelock | Role Required | Guardrails |
|---|---|---|---|
| Enable TGE | 2 days | TGE_ENABLER_ROLE | One-time enablement; TGE time immutable once set |
| Withdraw USDC | 2 days | TREASURY_ROLE | Circuit breaker: $500k daily limit |
| Emergency SELF Withdrawal | 7 days | DEFAULT_ADMIN_ROLE | Blocked if any user allocations exist |
| Update Rate Limit | None | DEFAULT_ADMIN_ROLE | Bounded: $100 - $1M range |
| Pause/Unpause | None | PAUSER_ROLE | Emergency circuit breaker only |
Monitoring Events:
TimelockRequested(action, executionTime)- Initiates countdownTimelockExecuted(action)- Confirms execution after delayTimelockCancelled(action)- Operation cancelled before execution
All events are publicly visible on BaseScan for community monitoring.
Published source: https://docs.self.app/tokenomics
The presale contract enforces critical security invariants in code, not operational policy:
- USDC Withdrawals: Treasury withdrawals require privileged approval, a 2-day timelock, and a $500k/day circuit breaker.
- SELF Token Claims: Contributions require sufficient SELF balance on-chain. Contract verifies
balance >= outstandingClaims + newAllocationbefore accepting contributions. - Emergency Safeguards: Emergency SELF withdrawals blocked once any user allocations exist.
- Token Generation Event can only be enabled once
- Multiple layers prevent TGE time from being changed after activation:
- Pending request check (prevents request overwrites)
- Execution guard (prevents multiple executions)
- Explicit cancellation required to replace pending requests
- Precision Math: All token calculations round up in favor of users
- Dust Handling: Allows exact completion of rounds when remaining capacity < minimum contribution
Public view functions for external verification:
getExcessSELFBalance()- Shows withdrawable excess vs. outstanding claimsgetClaimableAmount(user)- Shows user's vested + unlocked tokensgetUserContribution(user)- Complete user allocation breakdowngetPresaleStats()- Aggregate presale state
All privileged operations emit events for on-chain monitoring.
Administrative Controls:
- Privileged roles are constrained by on-chain invariants; selected TGE and treasury actions also enforce timelocks and a circuit breaker
Unsold Token Recovery:
The contract protects against SELF tokens being locked while safeguarding buyer claims:
withdrawExcessSELF()allows the treasury to reclaim unsold SELF after TGE- Protected: cannot withdraw tokens needed for outstanding user claims
- Formula:
excess = balance - (totalAllocated - totalClaimed) executeEmergencyWithdrawSELF()recovers SELF only before any user allocations exist (7-day timelock), so buyer claims can never be stranded
The cancellation uses the second mechanism. It is available only because totalAllocatedSELF == 0 — the same guard that would have made recovery impossible had even one buyer contributed. The design intent is that a cancelled-before-launch sale can be unwound, while a launched sale can never be rug-pulled.
Test Coverage: 49 passing tests, zero compiler warnings
Deployed Contracts (Base Mainnet):
- SELF Token Contract: 0xCBFc34863982f7563774F73004fd231982Ff0303
- SELFPresale: 0x9D762B5E519d6194aa829F31cF85317FE37Fe35d — verified, never initialized, cancelled 16 August 2026; recovery completed successfully 23 August 2026
npm install
npx hardhat testTests cover token functionality, presale logic, vesting, and edge cases.
contracts/test/MockUSDC.sol is a test utility only (6-decimal USDC simulator).
contracts/
├── SELFToken.sol # Audit scope
├── SELFPresale.sol # Audit scope
└── test/MockUSDC.sol # Test utility
test/
├── SELFToken.test.cjs
└── SELFPresale.test.cjs
scripts/
├── deploy-token.js
├── deploy-presale.js
├── initialize-rounds.js
└── verify-contracts.js
docs/
└── architecture.md
Network: Base Mainnet
USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Compiler: Solidity 0.8.20, optimizer enabled with 200 runs
Status: Deployed and exact-match verified. Never initialized. Cancelled — no contributions were ever taken, and recovery completed successfully with 0 SELF remaining in the presale contract.
Audit Ready: December 14, 2025
Skyharbor Updated V1.1: December 25, 2025
Skyharbor Updated V1.2: December 30, 2025
SEA-16 Fix V1.3: December 31, 2025
Per-Round Accounting Fix V1.4: January 1, 2026
Base Migration V1.5: March 13, 2026
Tokenomics Update V1.6: April 8, 2026 — Unified TGE unlock to 40%, removed bonuses, extended vesting to 12 months
Treasury Model Update V1.7: Streamlined treasury withdrawals with a 2-day timelock and $500k/day circuit breaker
Current Deployment V1.8: Base mainnet exact-match deployment at 0x9D762B5E519d6194aa829F31cF85317FE37Fe35d; no refund mechanism or minimum aggregate raise condition
Presale Cancelled V1.9: August 16, 2026 — sale will not proceed. Contract was never initialized and took zero contributions; emergency recovery requested via the 7-day timelock
Recovery Completed V1.10: August 23, 2026 — recovery completed successfully, leaving 0 SELF in the presale contract