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.

Multi-Stage Presale
Progressive pricing across stages with per-wallet limits and time windows.
Boost Vault
Lock tokens for bonus rewards with configurable tiers and multipliers.
Referral System
On-chain referral tracking with automatic BNB/token commission splits.
Auto-Liquidity
Automatic percentage allocation to a dedicated liquidity wallet on every buy.
Emergency Market
Optional P2P order book for holders to trade tokens before listing.
Admin Controls
Pause/unpause, whitelist tokens, withdraw unsold, transfer ownership.

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

DependencyVersionPurpose
FoundryLatestBuild, test, and deploy Solidity contracts
Solidity0.8.24Smart contract language
OpenZeppelinv5Ownable2Step, ReentrancyGuard, Pausable, ERC20, SafeERC20
Chainlinkv0.8AggregatorV3Interface for price feeds

Frontend

DependencyVersionPurpose
Node.js18+JavaScript runtime
npm9+Package manager (bundled with Node)
React19.xUI framework
Vite7.xBuild tool and dev server
Wagmi3.xReact hooks for Ethereum
Reown AppKit1.8+Wallet connection (MetaMask, Trust, WalletConnect, etc.)
Tailwind CSS4.xUtility-first CSS
Framer Motion12.xAnimations

External Accounts

  • Reown Cloud account — get a free Project ID at cloud.reown.com for 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.

Terminal
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup

# Verify installation
forge --version

2. Install Contract Dependencies

Terminal
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

Terminal
cd frontend
npm install

4. Start the Development Server

Terminal
npm run dev
# Opens at http://localhost:5173

Build for Production

Terminal
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:

frontend/.env
# 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

  1. Create a Reown Cloud account
    Visit cloud.reown.com and sign up for free.
  2. Create a new project
    Click "New Project", give it a name, and select "AppKit" as the SDK.
  3. Copy the Project ID
    Paste it into your .env as VITE_REOWN_PROJECT_ID.

Chain Configuration

The chain is configured in frontend/src/lib/appkit-config.ts. To switch to BSC Mainnet:

appkit-config.ts
// 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

ParameterTypeDescription
_saleTokenaddressERC20 token being sold
_bnbPriceFeedaddressChainlink BNB/USD oracle address
_ownerWalletaddressWallet that receives owner share of payments
_liquidityWalletaddressWallet that receives liquidity allocation
_liquidityBpsuint256Basis points to liquidity (1000 = 10%)
_referralRewardBpsuint256Basis points for referrals (300 = 3%)
_stagesStageConfig[]Array of presale stage configurations
_boostTiersBoostTier[]Array of boost lock tier configurations

StageConfig Struct

Solidity
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

Solidity
struct BoostTier {
    uint256 lockDuration;     // Seconds (30 days = 2592000)
    uint256 multiplierBps;    // Multiplier in BPS (12000 = 1.2x)
}

Key Functions

FunctionAccessDescription
buyWithBNB(referrer)payableBuy tokens by sending BNB
buyWithToken(token, amount, referrer)publicBuy tokens with whitelisted ERC20 (USDT/USDC)
lockTokens(amount, tierIndex)publicLock purchased tokens into boost vault
claimBoost(lockIndex)publicClaim tokens + bonus after lock expires
whitelistToken(token, priceFeed)onlyOwnerAdd ERC20 as payment option
removeWhitelistedToken(token)onlyOwnerRemove ERC20 payment option
pause() / unpause()onlyOwnerEmergency pause all operations
withdrawUnsoldTokens()onlyOwnerWithdraw remaining tokens after sale ends
transferOwnership(newOwner)onlyOwnerBegin 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

ParameterTypeDescription
_presaleVaultaddressReference to deployed PresaleVault (reads current price)
_bnbPriceFeedaddressChainlink BNB/USD oracle
_protocolFeeBpsuint256Protocol fee on fills (200 = 2%, max 1000 = 10%)
_maxDiscountBpsuint256Max discount from presale price (5000 = 50%, max 9000 = 90%)

Key Functions

FunctionAccessDescription
createOrder(tokenAmount, priceUsd)publicList tokens for sale (escrows tokens in contract)
cancelOrder(orderId)seller onlyCancel order and reclaim escrowed tokens
fillOrderWithBNB(orderId, tokenAmount)payableBuy listed tokens with BNB
fillOrderWithToken(orderId, tokenAmount, payToken)publicBuy listed tokens with USDT/USDC
setProtocolFeeBps(feeBps)onlyOwnerUpdate protocol fee (max 10%)
setMaxDiscountBps(discountBps)onlyOwnerUpdate max allowed discount (max 90%)
pause() / unpause()onlyOwnerEnable or disable the market
withdrawFees(token)onlyOwnerCollect 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.

Terminal
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.

Terminal
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.

Terminal
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)

Terminal
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

NetworkBNB/USD Feed
BSC Mainnet0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE
BSC Testnet0x2514895c72f50D8bd4B4F9b1110F0D6bD2c97526

Deployment via Remix IDE

If you prefer a browser-based approach without CLI tools, you can deploy directly from Remix IDE.

  1. Open Remix IDE

    Navigate to remix.ethereum.org in your browser.

  2. Create the contract files

    In the File Explorer, create a new file for each contract. Copy the contents of PresaleVault.sol and EmergencyMarket.sol from the contracts/src/ folder.

  3. 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
  4. Compile

    Go to the Solidity Compiler tab. Set compiler version to 0.8.24, enable optimization (200 runs), and click "Compile".

  5. 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).

  6. Deploy PresaleVault

    Select PresaleVault from 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:

Constructor Args
// _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.

  1. 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.

  2. 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.

  3. 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.

Stage 1
$0.0001 333M tokens
Stage 2
$0.00015 333M tokens
Stage 3
$0.0002 333M tokens
Sale End
Listing

Buying Flow

  1. User connects wallet

    MetaMask, Trust Wallet, WalletConnect, or any supported wallet via Reown AppKit.

  2. Select payment method

    Choose BNB (native), USDT, or USDC. For ERC20 tokens, an approve transaction is required first.

  3. 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.

  4. Payment is split automatically

    Referral commission (3%) + liquidity (10%) + owner (87%) are distributed in the same transaction.

  5. 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)

TierLock DurationMultiplierBPS Value
Tier 130 days1.2x12,000
Tier 260 days1.5x15,000
Tier 390 days2.0x20,000

Bonus Calculation

Formula:

combinedMultiplier = tierMultiplierBps × stageBaseMultiplier / 100
bonus = 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)

StageBase MultiplierEffect
Stage 12.0x (200)Highest bonus — early bird advantage
Stage 21.5x (150)Medium bonus
Stage 31.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

Buy Tokens
Approve
Lock in Vault
Wait Duration
Claim All

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:

  1. 3% of payment → sent to referrer immediately
  2. 10% of remaining 97% → sent to liquidity wallet
  3. 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:

Payment
100%
Referral
3%
+
Liquidity
10% of rest
+
Owner
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

  1. Approve tokens
    The UI handles this automatically. Seller approves the EmergencyMarket contract to spend their tokens.
  2. Set price and amount
    Choose a discount (1–50% below current presale price) and token amount to sell.
  3. Create listing
    Tokens are escrowed in the contract. The order appears in the Market tab for all buyers.
  4. Wait for fills or cancel
    Orders can be partially filled. Cancel anytime to reclaim remaining tokens.

Buyer Flow

  1. Browse orders
    See all active sell orders with discount percentages in the Market → Buy Orders section.
  2. 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:

frontend/.env
# Leave empty or remove this line to hide Market tab
VITE_EMERGENCY_MARKET_ADDRESS=

To show the Market tab, set the deployed address:

frontend/.env
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)

Terminal
# 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

FunctionWhat It DoesWhen 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

FunctionWhat It DoesWhen 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

ActionMethodDetails
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

ActionMethodDetails
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

CommandDescription
npm installInstall all dependencies
npm run devStart development server (http://localhost:5173)
npm run buildBuild for production (output: dist/)
npm run previewPreview the production build locally
npm run lintCheck 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 to npm 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

frontend/src/
  ├─ 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:

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:

index.css (key variables)
: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:

contracts/script/Deploy.s.sol
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:

contracts/script/Deploy.s.sol
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:

Constructor call in Deploy.s.sol
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:

Terminal (using cast)
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 TypeDecimalsExample
USD prices in contract6100 = $0.000100
Chainlink price feeds8BNB at $600 = 60,000,000,000
Token amounts181 token = 1,000,000,000,000,000,000
Basis points (BPS)01000 = 10%, 300 = 3%

Contract Constants

ConstantValueMeaning
STALENESS_THRESHOLD3600Price feed must be updated within 1 hour
USD_DECIMALS6USD values use 6 decimal places
BPS_DENOMINATOR10,000Base for all percentage calculations

Token Presale Platform — Documentation