Token Presale Documentation
Complete guide to deploying, configuring, and managing your token presale platform with multi-stage pricing, boost vault, referral system, auto-liquidity, and P2P emergency market.
Chain: Built for BSC (BNB Smart Chain). Works on both testnet (Chain ID 97) and mainnet (Chain ID 56). Uses Chainlink price feeds for real-time BNB/USD conversion.
Requirements
Smart Contracts
| Dependency | Version | Purpose |
|---|---|---|
| Foundry | Latest | Build, test, and deploy Solidity contracts |
| Solidity | 0.8.24 | Smart contract language |
| OpenZeppelin | v5 | Ownable2Step, ReentrancyGuard, Pausable, ERC20, SafeERC20 |
| Chainlink | v0.8 | AggregatorV3Interface for price feeds |
Frontend
| Dependency | Version | Purpose |
|---|---|---|
| Node.js | 18+ | JavaScript runtime |
| npm | 9+ | Package manager (bundled with Node) |
| React | 19.x | UI framework |
| Vite | 7.x | Build tool and dev server |
| Wagmi | 3.x | React hooks for Ethereum |
| Reown AppKit | 1.8+ | Wallet connection (MetaMask, Trust, WalletConnect, etc.) |
| Tailwind CSS | 4.x | Utility-first CSS |
| Framer Motion | 12.x | Animations |
External Accounts
- Reown Cloud account — get a free Project ID at
cloud.reown.comfor wallet connections - BNB wallet — deployer wallet with BNB for gas fees
- BscScan API key (optional) — for contract verification
Installation
1. Install Foundry (for Smart Contracts)
Foundry is the Solidity development toolkit used for compiling, testing, and deploying the contracts.
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Verify installation
forge --version
2. Install Contract Dependencies
cd contracts
# Install OpenZeppelin, Chainlink, and forge-std
forge install OpenZeppelin/openzeppelin-contracts --no-commit
forge install smartcontractkit/chainlink-brownie-contracts --no-commit
forge install foundry-rs/forge-std --no-commit
3. Install Frontend Dependencies
cd frontend
npm install
4. Start the Development Server
npm run dev
# Opens at http://localhost:5173
Build for Production
npm run build
# Output: frontend/dist/
# Upload the dist/ folder to any static hosting (Vercel, Netlify, etc.)
Quick start: If you just want to run the frontend with the existing testnet contracts, only steps 3 and 4 are needed. Contract deployment is only required for mainnet or custom configurations.
Configuration
All frontend configuration lives in frontend/.env. After deploying your own contracts, update these values:
# Reown (WalletConnect) project ID from cloud.reown.com
VITE_REOWN_PROJECT_ID=your_project_id_here
# PresaleVault contract address
VITE_CONTRACT_ADDRESS=0xYourPresaleVaultAddress
# EmergencyMarket contract address (leave empty to disable the Market tab)
VITE_EMERGENCY_MARKET_ADDRESS=0xYourEmergencyMarketAddress
# Whitelisted stablecoin addresses
VITE_USDT_ADDRESS=0xYourUSDTAddress
VITE_USDC_ADDRESS=0xYourUSDCAddress
# Display settings
VITE_PROJECT_NAME=Token Presale
VITE_TOKEN_SYMBOL=TOKEN
VITE_TOKEN_DECIMALS=18
# Chain ID: 97 = BSC Testnet, 56 = BSC Mainnet
VITE_CHAIN_ID=97
Important: After changing .env, restart the dev server (npm run dev) for changes to take effect. For production builds, rebuild with npm run build.
Reown Project ID
- Create a Reown Cloud account
Visitcloud.reown.comand sign up for free. - Create a new project
Click "New Project", give it a name, and select "AppKit" as the SDK. - Copy the Project ID
Paste it into your.envasVITE_REOWN_PROJECT_ID.
Chain Configuration
The chain is configured in frontend/src/lib/appkit-config.ts. To switch to BSC Mainnet:
// Change from:
import { bscTestnet } from "@reown/appkit/networks"
export const networks = [bscTestnet]
// Change to:
import { bsc } from "@reown/appkit/networks"
export const networks = [bsc]
And update VITE_CHAIN_ID=56 in your .env.
PresaleVault Contract
The core contract that handles token sales, payment splitting, boost locking, and referral tracking.
Constructor Parameters
| Parameter | Type | Description |
|---|---|---|
_saleToken | address | ERC20 token being sold |
_bnbPriceFeed | address | Chainlink BNB/USD oracle address |
_ownerWallet | address | Wallet that receives owner share of payments |
_liquidityWallet | address | Wallet that receives liquidity allocation |
_liquidityBps | uint256 | Basis points to liquidity (1000 = 10%) |
_referralRewardBps | uint256 | Basis points for referrals (300 = 3%) |
_stages | StageConfig[] | Array of presale stage configurations |
_boostTiers | BoostTier[] | Array of boost lock tier configurations |
StageConfig Struct
struct StageConfig {
uint256 priceUsd; // Token price, 6 decimals (100 = $0.000100)
uint256 tokenAllocation; // Total tokens for this stage (18 decimals)
uint48 startTime; // Unix timestamp
uint48 endTime; // Unix timestamp
uint256 minBuyUsd; // Minimum purchase in USD (6 decimals)
uint256 maxBuyPerWalletUsd; // Per-wallet max in USD (6 decimals)
uint256 boostBaseMultiplier; // Base multiplier in BPS (200 = 2.0x)
}
BoostTier Struct
struct BoostTier {
uint256 lockDuration; // Seconds (30 days = 2592000)
uint256 multiplierBps; // Multiplier in BPS (12000 = 1.2x)
}
Key Functions
| Function | Access | Description |
|---|---|---|
buyWithBNB(referrer) | payable | Buy tokens by sending BNB |
buyWithToken(token, amount, referrer) | public | Buy tokens with whitelisted ERC20 (USDT/USDC) |
lockTokens(amount, tierIndex) | public | Lock purchased tokens into boost vault |
claimBoost(lockIndex) | public | Claim tokens + bonus after lock expires |
whitelistToken(token, priceFeed) | onlyOwner | Add ERC20 as payment option |
removeWhitelistedToken(token) | onlyOwner | Remove ERC20 payment option |
pause() / unpause() | onlyOwner | Emergency pause all operations |
withdrawUnsoldTokens() | onlyOwner | Withdraw remaining tokens after sale ends |
transferOwnership(newOwner) | onlyOwner | Begin 2-step ownership transfer |
Security Features
- Ownable2Step — Two-step ownership transfer prevents accidental loss
- ReentrancyGuard — Protects all payment functions from reentrancy
- Pausable — Emergency pause mechanism for all operations
- Chainlink Price Feed — Real-time pricing with 1-hour staleness check
- SafeERC20 — Safe token transfer handling
EmergencyMarket Contract
An optional P2P trading contract that lets presale holders sell tokens at a discount before exchange listing. Fully pausable and independent from the main presale.
Constructor Parameters
| Parameter | Type | Description |
|---|---|---|
_presaleVault | address | Reference to deployed PresaleVault (reads current price) |
_bnbPriceFeed | address | Chainlink BNB/USD oracle |
_protocolFeeBps | uint256 | Protocol fee on fills (200 = 2%, max 1000 = 10%) |
_maxDiscountBps | uint256 | Max discount from presale price (5000 = 50%, max 9000 = 90%) |
Key Functions
| Function | Access | Description |
|---|---|---|
createOrder(tokenAmount, priceUsd) | public | List tokens for sale (escrows tokens in contract) |
cancelOrder(orderId) | seller only | Cancel order and reclaim escrowed tokens |
fillOrderWithBNB(orderId, tokenAmount) | payable | Buy listed tokens with BNB |
fillOrderWithToken(orderId, tokenAmount, payToken) | public | Buy listed tokens with USDT/USDC |
setProtocolFeeBps(feeBps) | onlyOwner | Update protocol fee (max 10%) |
setMaxDiscountBps(discountBps) | onlyOwner | Update max allowed discount (max 90%) |
pause() / unpause() | onlyOwner | Enable or disable the market |
withdrawFees(token) | onlyOwner | Collect accumulated protocol fees |
Price Validation
When a seller creates an order, the contract enforces:
presalePrice × (1 − maxDiscountBps / 10000) ≤ sellerPrice ≤ presalePrice
Example: If presale price is $0.0001 and maxDiscount is 50%, sellers can set prices between $0.00005 and $0.0001.
Deployment via CLI (Foundry)
The project includes ready-to-use Foundry deploy scripts. This is the recommended deployment method.
Deploy to BSC Testnet (Full Stack)
This script deploys everything: mock sale token, mock USDT/USDC, mock price feeds, and the PresaleVault. Perfect for testing.
cd contracts
PRIVATE_KEY=your_deployer_private_key \
forge script script/DeployTestnet.s.sol:DeployTestnet \
--rpc-url https://data-seed-prebsc-1-s1.binance.org:8545 \
--broadcast
This will deploy and log all addresses. Copy them into frontend/.env.
Deploy to BSC Mainnet
For mainnet, you need your own ERC20 sale token already deployed.
cd contracts
PRIVATE_KEY=your_deployer_private_key \
SALE_TOKEN=0xYourTokenAddress \
OWNER_WALLET=0xYourOwnerWallet \
LIQUIDITY_WALLET=0xYourLiquidityWallet \
forge script script/Deploy.s.sol:Deploy \
--rpc-url https://bsc-dataseed1.binance.org \
--broadcast
Remember: After deploying the PresaleVault, you must transfer your sale tokens to the vault address. The vault distributes tokens from its own balance. Without funding, purchases will revert.
Deploy Emergency Market
Deploy separately after the PresaleVault is live. Update the hardcoded addresses in the script first.
cd contracts
# Edit script/DeployEmergencyMarket.s.sol first:
# - Set presaleVault to your deployed PresaleVault address
# - Set bnbFeed to the correct Chainlink feed
# - Set USDT/USDC addresses
# - Set stableFeed address
PRIVATE_KEY=your_deployer_private_key \
forge script script/DeployEmergencyMarket.s.sol:DeployEmergencyMarket \
--rpc-url https://data-seed-prebsc-1-s1.binance.org:8545 \
--broadcast
Verify on BscScan (Optional)
forge verify-contract 0xYourContractAddress src/PresaleVault.sol:PresaleVault \
--chain-id 97 \
--etherscan-api-key your_bscscan_api_key \
--constructor-args $(cast abi-encode "constructor(address,address,address,address,uint256,uint256,(uint256,uint256,uint48,uint48,uint256,uint256,uint256)[],(uint256,uint256)[])" ...)
Chainlink Price Feed Addresses
| Network | BNB/USD Feed |
|---|---|
| BSC Mainnet | 0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE |
| BSC Testnet | 0x2514895c72f50D8bd4B4F9b1110F0D6bD2c97526 |
Deployment via Remix IDE
If you prefer a browser-based approach without CLI tools, you can deploy directly from Remix IDE.
-
Open Remix IDE
Navigate to
remix.ethereum.orgin your browser. -
Create the contract files
In the File Explorer, create a new file for each contract. Copy the contents of
PresaleVault.solandEmergencyMarket.solfrom thecontracts/src/folder. -
Install dependencies via imports
Remix handles OpenZeppelin and Chainlink imports automatically via npm-style paths. Change the import paths to use npm format:
Remix Import Format// Change Foundry-style paths: import "@openzeppelin/contracts/access/Ownable2Step.sol"; // These already work in Remix as-is! // Remix auto-resolves @openzeppelin and @chainlink from npm -
Compile
Go to the Solidity Compiler tab. Set compiler version to 0.8.24, enable optimization (200 runs), and click "Compile".
-
Connect your wallet
In the "Deploy & Run" tab, set the environment to "Injected Provider - MetaMask". Make sure MetaMask is connected to BSC (testnet or mainnet).
-
Deploy PresaleVault
Select
PresaleVaultfrom the contract dropdown. Fill in the constructor parameters:
Example constructor arguments for Remix
In Remix, complex struct arrays need to be passed as tuples. Here's the format:
// _saleToken:
0xYourSaleTokenAddress
// _bnbPriceFeed (BSC Mainnet):
0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE
// _ownerWallet:
0xYourOwnerWallet
// _liquidityWallet:
0xYourLiquidityWallet
// _liquidityBps:
1000 // 10%
// _referralRewardBps:
300 // 3%
// _stages (3 stages, 7 days each):
[[100,"333333333000000000000000000",START_TIMESTAMP,END_STAGE1,1000000,10000000000,200],[150,"333333333000000000000000000",END_STAGE1,END_STAGE2,1000000,10000000000,150],[200,"333333334000000000000000000",END_STAGE2,END_STAGE3,1000000,10000000000,100]]
// _boostTiers (3 tiers):
[[2592000,12000],[5184000,15000],[7776000,20000]]
Replace START_TIMESTAMP etc. with actual Unix timestamps. Use an online converter or run date +%s in your terminal. Token allocations must be in wei (18 decimals) — 333,333,333 tokens = 333333333000000000000000000.
-
Fund the Vault
After deployment, call
transfer()on your sale token to send tokens to the PresaleVault address. The vault needs tokens in its balance to distribute during purchases. -
Whitelist payment tokens
Call
whitelistToken()on the PresaleVault for each stablecoin you want to accept (USDT, USDC). You'll need the stablecoin address and its Chainlink price feed address. -
Deploy EmergencyMarket (optional)
Select
EmergencyMarket, fill constructor with PresaleVault address, BNB price feed, fee (200), and max discount (5000). Then whitelist stablecoins on the market contract too.
How the Presale Works
Stage Progression
The presale operates in sequential stages. Each stage has its own price, time window, and token allocation. Price increases across stages to incentivize early participation.
$0.0001 333M tokens
$0.00015 333M tokens
$0.0002 333M tokens
Listing
Buying Flow
-
User connects wallet
MetaMask, Trust Wallet, WalletConnect, or any supported wallet via Reown AppKit.
-
Select payment method
Choose BNB (native), USDT, or USDC. For ERC20 tokens, an approve transaction is required first.
-
Enter amount and confirm
The UI shows a live preview of tokens to receive based on the current stage price and Chainlink BNB/USD rate.
-
Payment is split automatically
Referral commission (3%) + liquidity (10%) + owner (87%) are distributed in the same transaction.
-
Tokens received instantly
Sale tokens are transferred to the buyer's wallet in the same transaction.
Token Price Calculation
Formula:
tokensOut = (paymentAmount × bnbPriceUSD) × 1018 / (stagePriceUSD × 100)
Example:
- Buy with 0.1 BNB at BNB price = $600
- USD value = 0.1 × 600 = $60
- Stage 1 price = $0.0001 (100 in 6-decimal)
- Tokens = 60 / 0.0001 = 600,000 tokens
Per-Wallet Limits
Each wallet has a maximum USD spend tracked per stage. The default configuration allows $10,000 per wallet per stage. Exceeding this limit reverts the transaction. The minimum buy is $1 per transaction.
Boost Vault
The Boost Vault lets users lock their purchased tokens for a chosen duration and receive bonus tokens as a reward. Longer locks and earlier stages give higher multipliers.
Available Tiers (Default)
| Tier | Lock Duration | Multiplier | BPS Value |
|---|---|---|---|
| Tier 1 | 30 days | 1.2x | 12,000 |
| Tier 2 | 60 days | 1.5x | 15,000 |
| Tier 3 | 90 days | 2.0x | 20,000 |
Bonus Calculation
Formula:
combinedMultiplier = tierMultiplierBps × stageBaseMultiplier / 100bonus = amount × combinedMultiplier / 10000 − amount
Example (Stage 1, Tier 1):
- Lock 1,000 tokens, Tier 1 (1.2x = 12,000 BPS), Stage 1 base (2.0x = 200)
- Combined = 12,000 × 200 / 100 = 24,000 BPS (2.4x)
- Bonus = 1,000 × 24,000 / 10,000 − 1,000 = 1,400 bonus tokens
- After 30 days: claim 2,400 tokens total
Stage Base Multipliers (Default)
| Stage | Base Multiplier | Effect |
|---|---|---|
| Stage 1 | 2.0x (200) | Highest bonus — early bird advantage |
| Stage 2 | 1.5x (150) | Medium bonus |
| Stage 3 | 1.0x (100) | Tier multiplier only, no extra boost |
The stage base multiplier makes locking in earlier stages significantly more profitable. A 90-day lock in Stage 1 yields a 4.0x combined multiplier (3,000 bonus tokens per 1,000 locked), vs. 2.0x in Stage 3.
Locking Flow
Referral System
Built-in on-chain referral tracking. Referrers receive a percentage of every referred purchase, paid instantly in the same transaction.
How It Works
- Referral link format:
https://yoursite.com/?ref=0xReferrerAddress - Default commission: 3% of every referred purchase (300 BPS)
- Payment: BNB for BNB purchases, or original ERC20 for token purchases
- Validation: Referrer must have purchased tokens in any stage. Self-referral is blocked.
- Tracking: Referral earnings are tracked on-chain via
referralEarnings[address]
Referral Payout Flow
When a user buys with a valid referral link:
- 3% of payment → sent to referrer immediately
- 10% of remaining 97% → sent to liquidity wallet
- Remainder → sent to owner wallet
The referral link is captured automatically from the URL query parameter (?ref= or ?r=) and passed to the contract. Users can also copy their own referral link from the "More" tab after purchasing.
Auto-Liquidity
Every purchase automatically allocates a configurable percentage to a dedicated liquidity wallet. This builds up a transparent, verifiable liquidity fund for post-presale DEX listing.
Payment Split (Default 10%)
For every purchase, the payment is split in one atomic transaction:
100%
3%
10% of rest
Remainder
The liquidity wallet address and percentage are immutable — set at deployment and cannot be changed. This guarantees transparency: anyone can verify the allocation on-chain.
The frontend "More" tab displays the liquidity wallet address with a link to BscScan, allowing buyers to verify accumulated funds at any time.
Emergency Market Feature
The Emergency Market provides pre-listing secondary liquidity. Presale holders can list tokens for sale at a discount, and other users can buy them below presale price.
Seller Flow
- Approve tokens
The UI handles this automatically. Seller approves the EmergencyMarket contract to spend their tokens. - Set price and amount
Choose a discount (1–50% below current presale price) and token amount to sell. - Create listing
Tokens are escrowed in the contract. The order appears in the Market tab for all buyers. - Wait for fills or cancel
Orders can be partially filled. Cancel anytime to reclaim remaining tokens.
Buyer Flow
- Browse orders
See all active sell orders with discount percentages in the Market → Buy Orders section. - Select and fill
Choose an order, enter amount, pay with BNB or USDT/USDC. Tokens transferred instantly.
Protocol Fee
A protocol fee (default 2%) is deducted from the buyer's payment. The remainder goes to the seller. Fees accumulate in the contract and can be withdrawn by the owner.
Enabling / Disabling the Market
How to enable/disable the Emergency Market
On-Chain (Contract Level)
The market contract uses OpenZeppelin's Pausable:
- Disable: Call
pause()from the owner wallet. All new orders and fills are blocked. Sellers can still cancel existing orders. - Enable: Call
unpause()from the owner wallet. Market resumes full operation.
In the Frontend
To completely hide the Market tab from the UI, remove or leave empty the VITE_EMERGENCY_MARKET_ADDRESS in your .env:
# Leave empty or remove this line to hide Market tab
VITE_EMERGENCY_MARKET_ADDRESS=
To show the Market tab, set the deployed address:
VITE_EMERGENCY_MARKET_ADDRESS=0xYourEmergencyMarketAddress
Using Remix / Etherscan
If the contract is verified on BscScan, go to Contract → Write Contract, connect your owner wallet, and call pause() or unpause().
Using Cast (CLI)
# Pause the market
cast send 0xMarketAddress "pause()" \
--rpc-url https://bsc-dataseed1.binance.org \
--private-key your_owner_private_key
# Unpause the market
cast send 0xMarketAddress "unpause()" \
--rpc-url https://bsc-dataseed1.binance.org \
--private-key your_owner_private_key
Admin Functions Reference
PresaleVault — Owner Functions
| Function | What It Does | When to Use |
|---|---|---|
whitelistToken(token, priceFeed) |
Add a new ERC20 as accepted payment. Requires a Chainlink price feed address. | After deployment to add USDT, USDC, or other stablecoins |
removeWhitelistedToken(token) |
Disable an ERC20 payment option. Does not affect past purchases. | If a stablecoin depegs or you want to reduce payment options |
pause() |
Freeze all buying and locking. Claiming already-expired locks still works. | Emergency: exploit detected, price feed issues, or planned maintenance |
unpause() |
Resume all operations after a pause. | After resolving the emergency |
withdrawUnsoldTokens() |
Transfer all remaining sale tokens to the owner wallet. Only works after the last stage ends. | After presale concludes to recover unsold allocation |
transferOwnership(newOwner) |
Begin 2-step ownership transfer. New owner must call acceptOwnership(). |
Transferring admin control to a multisig or new team |
EmergencyMarket — Owner Functions
| Function | What It Does | When to Use |
|---|---|---|
whitelistToken(token, feed) |
Add ERC20 as payment option on the market | After deployment for USDT/USDC |
setProtocolFeeBps(feeBps) |
Update the protocol fee. Maximum: 1000 (10%). | Adjusting market economics |
setMaxDiscountBps(discountBps) |
Update max allowed seller discount. Maximum: 9000 (90%). | Tightening or loosening price controls |
pause() / unpause() |
Freeze or resume all market activity | Enable or disable the market feature |
withdrawFees(token) |
Withdraw accumulated fees. Pass address(0) for BNB or token address for ERC20. Safe: never withdraws escrowed tokens. |
Periodically collecting protocol revenue |
Ownership security: PresaleVault uses Ownable2Step, meaning the new owner must explicitly accept. EmergencyMarket uses standard Ownable. Consider using a multisig wallet (e.g., Gnosis Safe) as owner for mainnet deployments.
Enable / Disable Features
Emergency Market
| Action | Method | Details |
|---|---|---|
| Hide from UI | Remove VITE_EMERGENCY_MARKET_ADDRESS from .env |
Market tab disappears. Contract remains on-chain but inaccessible from your frontend. |
| Pause on-chain | Call pause() on EmergencyMarket |
Orders and fills blocked. Existing sellers can still cancel. Market tab may show as "paused". |
| Resume on-chain | Call unpause() on EmergencyMarket |
All market operations resume. |
| Never deploy it | Simply skip the EmergencyMarket deployment | Leave VITE_EMERGENCY_MARKET_ADDRESS empty. The presale works independently. |
Presale Operations
| Action | Method | Details |
|---|---|---|
| Pause buying | Call pause() on PresaleVault |
All buyWithBNB, buyWithToken, and lockTokens calls revert. ClaimBoost still works for expired locks. |
| Resume buying | Call unpause() on PresaleVault |
Operations resume within active stage time windows. |
| Remove a payment token | Call removeWhitelistedToken(tokenAddress) |
That token can no longer be used for purchases. BNB always works. |
| Add a payment token | Call whitelistToken(tokenAddress, priceFeedAddress) |
Any ERC20 with a Chainlink price feed can be added. |
Frontend Setup
Commands
| Command | Description |
|---|---|
npm install | Install all dependencies |
npm run dev | Start development server (http://localhost:5173) |
npm run build | Build for production (output: dist/) |
npm run preview | Preview the production build locally |
npm run lint | Check code quality with ESLint |
Wallet Connection
The frontend uses Reown AppKit (formerly WalletConnect Web3Modal) which supports 20+ wallets out of the box:
- MetaMask
- Trust Wallet
- WalletConnect (QR code for mobile wallets)
- Coinbase Wallet
- Ledger Live
- And many more
Hosting
After npm run build, the dist/ folder is a static site. Deploy to:
- Vercel — Drop the
frontend/folder, set build command tonpm run build - Netlify — Same setup, auto-deploys from folder
- Any static host — Upload
dist/contents. No server required. - IPFS — For fully decentralized hosting
Frontend Project Structure
├─ App.tsx — Root component with providers
├─ main.tsx — React entry point
├─ index.css — Global styles, CSS variables, themes
├─ lib/
│ ├─ appkit-config.ts — Reown AppKit + Wagmi setup
│ ├─ config.ts — Contract addresses from .env
│ ├─ abi.ts — PresaleVault ABI
│ └─ emergencyMarketAbi.ts — EmergencyMarket ABI
├─ hooks/
│ ├─ usePresaleData.ts — Read hooks (stages, tiers, balances)
│ ├─ usePresaleActions.ts — Write hooks (buy, lock, claim)
│ └─ useEmergencyMarket.ts — Market hooks (orders, fill)
├─ components/
│ ├─ PresaleWidget.tsx — Main widget with tab navigation
│ ├─ tabs/
│ │ ├─ BuyTab.tsx — Purchase tokens (BNB/USDT/USDC)
│ │ ├─ BoostTab.tsx — Lock tokens for bonus
│ │ ├─ MarketTab.tsx — P2P trading orders
│ │ └─ MoreTab.tsx — Stats, referral link, info
│ ├─ shared/ — PercentButtons, helpers
│ └─ ui/ — shadcn/ui components
└─ types/ — TypeScript declarations
Customization Guide
Change Token Name & Symbol
Update these values in frontend/.env:
VITE_PROJECT_NAME=My Token Sale
VITE_TOKEN_SYMBOL=MTK
Change Colors / Theme
All colors are defined as CSS variables in frontend/src/index.css. Edit the :root block for dark theme and [data-theme="light"] for light theme:
:root {
--primary: #2dd4bf; /* Main accent color */
--background: #0b0f19; /* Page background */
--card: #141b2d; /* Card background */
--accent: #2dd4bf; /* Accent color */
--destructive: #f43f5e; /* Error/warning color */
}
Modify Stage Configuration
Stages are set at contract deployment time and are immutable. To change prices, allocations, or timing, you must deploy a new contract. Edit the deploy script before running:
stages[0] = PresaleVault.StageConfig({
priceUsd: 100, // $0.0001
tokenAllocation: 333_333_333 ether, // 333M tokens
startTime: uint48(block.timestamp),
endTime: uint48(block.timestamp + 7 days),
minBuyUsd: 1e6, // $1 minimum
maxBuyPerWalletUsd: 10_000e6, // $10k max
boostBaseMultiplier: 200 // 2.0x boost base
});
Modify Boost Tiers
Also set at deployment. Adjust in the deploy script:
tiers[0] = PresaleVault.BoostTier({
lockDuration: 30 days, // Lock period
multiplierBps: 12000 // 1.2x multiplier
});
Change Liquidity / Referral Percentages
These are immutable constructor parameters. Set in the deploy script:
new PresaleVault(
saleToken,
bnbPriceFeed,
ownerWallet,
liquidityWallet,
1000, // 10% to liquidity (change this)
300, // 3% referral (change this)
stages,
tiers
);
Immutable parameters: liquidityBps, referralRewardBps, ownerWallet, and liquidityWallet cannot be changed after deployment. Double-check these values before deploying to mainnet.
Add More Payment Tokens
After deployment, the owner can add any ERC20 that has a Chainlink price feed:
cast send 0xPresaleVaultAddress \
"whitelistToken(address,address)" \
0xNewTokenAddress \
0xChainlinkPriceFeedForToken \
--rpc-url https://bsc-dataseed1.binance.org \
--private-key your_owner_private_key
Then update the frontend .env to add the token address so the UI can display it as a payment option.
Quick Reference
Decimal Conventions
| Value Type | Decimals | Example |
|---|---|---|
| USD prices in contract | 6 | 100 = $0.000100 |
| Chainlink price feeds | 8 | BNB at $600 = 60,000,000,000 |
| Token amounts | 18 | 1 token = 1,000,000,000,000,000,000 |
| Basis points (BPS) | 0 | 1000 = 10%, 300 = 3% |
Contract Constants
| Constant | Value | Meaning |
|---|---|---|
STALENESS_THRESHOLD | 3600 | Price feed must be updated within 1 hour |
USD_DECIMALS | 6 | USD values use 6 decimal places |
BPS_DENOMINATOR | 10,000 | Base for all percentage calculations |
Token Presale Platform — Documentation