Stick is a Solana launchpad for Pump.fun tokens. It runs a single, opinionated raise model: timed SOL commitments, public creator buy-in, weighted allocation, oversubscription refunds, and one aggregate Pump.fun/PumpSwap launch route.
Current mainnet deployment:
- Smart contract:
3cp7EpueLdu5RM5sPGLdnE8smPdWAkco3aMwAihju7VL - Keeper / sponsor:
ExSmZtVRQQjyFQzA1JteTm3nyVqQgADoSru9aHWs2DHx
The project is structured as a production-oriented monorepo:
programs/fair_launchpad- Anchor program that owns presale state, SOL vaults, settlement roots, token vaults, refund claims, vesting, and guarded route calls.apps/web- Next.js app for launch creation, public presale pages, claims, launched-token views, wallet login, and sponsored transaction flow.services/keeper- Node keeper/indexer that closes expired raises, builds settlement manifests, submits Pump/PumpSwap finalize routes, and writes the live feed to Postgres.packages/shared- TypeScript domain types, BN math, weighted allocation, Merkle settlement, and Pump FDV helpers.packages/launchpad-client- Solana instruction builders and PDA helpers for the launchpad program.packages/pump-integration- Official Pump.fun and PumpSwap SDK integration, route planning, Jito bundle assembly, and metadata/upload helpers.
Stick intentionally avoids many launch modes. The MVP uses one transparent model that is easy for creators and funders to understand:
- A creator configures a token page, raise target, raise window, creator buy-in, vesting, and optional max-wallet supply cap.
- The creator starts the presale with one wallet action: create the presale, commit creator buy-in, and open contributions.
- Anyone can commit SOL until the timer ends. The raise does not close early when the target is reached.
- If total committed is below target, the raise becomes refund-only.
- If total committed is at or above target, exactly the target amount is accepted into the launch route and the rest is refundable.
- Accepted SOL is spent once as an aggregate buy:
- below Pump.fun graduation:
create_v2 + buy_v2; - above graduation: Pump.fun buy to completion, migration, then PumpSwap remainder buy through an ordered Jito bundle.
- below Pump.fun graduation:
- Contributors claim purchased tokens plus unused SOL from one claim path.
There are no per-user Pump.fun buys. A 100-person raise becomes one aggregate route, then token allocation is distributed by settlement math.
Creator fees are chosen once during launch creation. Self is the default:
Pump fee-sharing sends 100% of creator fees to the creator wallet. Automated
modes route 100% of fee-sharing to a platform-created subwallet:
Buyback + Burn- claim creator fees, buy the launched token, then burn the bought token delta.Coinflip- claim creator fees, flip through Slotana, and use winning payout for buyback + burn. Losses are logged publicly.Flywheel- claim creator fees and distribute spendable SOL to 10 random current holders.
Non-self modes are off-chain worker actions backed by public signatures. The
presale page shows the selected mode, fee recipient/subwallet public key, and
action history. Subwallet secrets are stored encrypted in Postgres with
WALLET_ENCRYPTION_KEY.
Every non-self subwallet keeps a fixed gas reserve:
action_budget = max(subwallet_balance - 0.05 SOL, 0)
The worker never uses the reserve as action budget. If spendable balance is below the mode threshold, the worker records/skips the cycle.
Internal flow:
Creator wallet
+ create page + buy-in
+-------------------------+
|
v
+-------------------------+ Contributors
+ Stick Anchor program +<----- commit SOL until timer ends
+ presale + SOL vault +
+-------------------------+
|
| close + settle
v
+-------------------------+
+ Keeper / indexer +
+ manifest + Merkle root +
+-------------------------+
|
| guarded finalize
v
+-------------------------+ +-------------------------+
+ Pump.fun create_v2/buy +----->+ PumpSwap remainder buy +
+-------------------------+ +-------------------------+
|
v
+-------------------------+
+ User claim +
+ tokens + unused SOL +
+-------------------------+
Stick accepts oversubscription. If the target is 1 SOL and the pool commits
10 SOL, the route still buys with 1 SOL; the remaining 9 SOL is refunded
according to the final settlement.
Allocation is weight-based, not first-come-only:
remaining_seconds = presale_end_ts - contribution_ts
time_fraction = remaining_seconds / duration_seconds
fill_before = committed_before / target
fill_multiplier = 1 + boost_strength * (1 - fill_before)^2
weight_added = amount * (2 + time_fraction * fill_multiplier)
The base 2x weight keeps late contributors meaningful, while time and fill
boost reward early discovery. The keeper settles with a weighted cap and
redistribute pass:
- nobody can be charged more SOL than they committed;
- if a wallet hits its cap, unaccepted SOL stays refundable;
- any accepted SOL that cannot be assigned to capped wallets is redistributed across remaining eligible weights;
- if the max-wallet supply cap leaves no eligible wallet, the route can spend less than target and the rest remains refundable.
The settlement manifest is public JSON and committed on-chain by Merkle root. Claims verify the Merkle proof before transferring refund SOL and token allocation.
The Anchor program is the trust boundary for funds. It enforces:
- SOL-only raise flow for the current MVP;
- one active launch type:
EarlyBoostBatch; - 1 minute to 1 day raise windows;
- creator buy-in before opening;
- creator buy-in counted inside total commitments;
- oversubscription until the timer ends;
- refund-only mode when target is missed;
- Merkle root based claims;
- double-claim prevention;
- Token-2022 token vault support for Pump.fun
create_v2; - guarded CPI route calls to Pump.fun, PumpSwap, and Pump fee-sharing programs;
- route state so finalize steps cannot be skipped or double-spent;
- optional creator buy-in vesting.
The keeper can close, settle, and finalize, but it does not freely withdraw vault funds. Route instructions must move SOL through approved Pump/PumpSwap paths and token claims are constrained by the purchased-token vault.
The keeper is an operational service, not a custodian. It:
- polls launchpad accounts and Postgres rows;
- closes expired open raises;
- detects target-missed raises and marks them refund-only;
- indexes contributor accounts;
- builds deterministic settlement manifests and Merkle roots;
- stores manifests in Postgres for API proof lookup;
- calls
set_settlement; - builds official Pump.fun SDK
create_v2andbuy_v2instructions; - builds official PumpSwap SDK remainder buys;
- builds ordered Jito bundles for split Pump/PumpSwap routes;
- updates launched-token and activity tables for the web app;
- runs creator-fee subwallet cycles for completed non-self launches while preserving the fixed gas reserve.
For split routes, the intended production behavior is bundle retry. A normal sequential fallback would leave an avoidable gap between Pump graduation and the PumpSwap remainder buy.
The Next.js app contains:
- Futard-style launch board and project cards adapted for Stick;
- launch creation flow with file uploads for avatar/banner;
- 3:1 banner handling and backend-served asset URLs;
- public presale pages with timer, target, committed amount, status, route info, max-wallet cap, creator buy-in, and claim actions;
- claim/refund APIs that load settlement data from Postgres and on-chain state;
- sponsored transaction API with a restricted fee-payer wallet;
- SOL/USDT backend cache used for estimated FDV and UI display.
Postgres is used for indexing and fast UI reads. The schema lives in
infra/postgres/001_init.sql.
Core tables:
launches- project metadata, status, target, committed totals, timing, FDV.contributors- indexed commitments and claim state.settlement_manifests- Merkle root, public manifest JSON, accepted/refund data.route_steps- Pump/PumpSwap finalize progress.launched_tokens- post-launch token cards.activity_events- ticker and audit feed events.creator_fee_wallets- encrypted non-self creator-fee subwallets and one-time0.05 SOLreserve funding state.creator_fee_cycles- public action logs for fee claims, buyback burns, coinflips, flywheel distributions, signatures, recipients, and errors.
Apply schema:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/stick pnpm db:schema- Node.js 20+
- pnpm 8+
- Rust stable
- Solana CLI
- Anchor CLI 0.30.1 for the current local build path
- PostgreSQL 14+
- A funded Solana keeper wallet for mainnet finalization and sponsored fees
The current SBF build is verified with:
anchor build --no-idlIf you need full IDL generation, use an Anchor/Rust version pair compatible with
Anchor's idl-build path.
Copy the example file and fill real values:
cp .env.example .envImportant variables:
| Variable | Used by | Description |
|---|---|---|
NEXT_PUBLIC_SOLANA_CLUSTER |
web | Usually mainnet-beta. |
NEXT_PUBLIC_SOLANA_RPC_URL |
web | Public Solana RPC endpoint. |
NEXT_PUBLIC_LAUNCHPAD_PROGRAM_ID |
web | Deployed launchpad program id. |
NEXT_PUBLIC_PRIVY_APP_ID |
web | Privy app id for wallet login. |
DATABASE_URL |
web, keeper | Postgres connection string. |
WALLET_ENCRYPTION_KEY |
web, keeper | Required for encrypted creator-fee subwallet storage and worker decrypt. |
METADATA_UPLOAD_URL |
web, keeper | Pump.fun metadata upload endpoint. |
SPONSORED_TX_ENABLED |
web API | Enables server-side fee payer signing. |
SPONSOR_KEYPAIR_JSON |
web API | Low-balance sponsor wallet secret JSON. Never commit. |
RPC_URL |
keeper | Private/high-throughput RPC, usually Helius. |
LAUNCHPAD_PROGRAM_ID |
keeper | Same deployed program id. |
KEEPER_PRIVATE_KEY / KEEPER_KEYPAIR_JSON |
keeper | Keeper wallet. Never commit. |
JITO_ENDPOINT |
keeper | Comma-separated Jito block engine endpoints. |
CREATOR_FEE_WORKER_MS |
keeper | Creator-fee worker cadence. Defaults to 30000. |
DISABLE_CREATOR_FEE_WORKER |
keeper | Set true to disable fee-action cycles. |
JITO_TIP_LAMPORTS |
keeper | Tip used for bundles. |
DISABLE_AUTO_FINALIZE |
keeper | Set true to index/settle without route finalization. |
Never commit .env, keypairs, target/deploy/*.json, local validator ledgers,
or production wallet secrets. The .gitignore is intentionally strict around
these files.
pnpm installpnpm typecheck
pnpm test
pnpm anchor:build
pnpm --filter @fair/web build
pnpm --filter @fair/keeper buildRun the local smoke test:
anchor build --no-idl
solana-test-validator \
--ledger /tmp/stick-test-ledger \
--reset \
--quiet \
--bpf-program <LOCAL_PROGRAM_ID> target/deploy/fair_launchpad.so
pnpm local:smokeThe local smoke flow covers:
- missed target -> refund-only;
- oversubscribed settlement;
- fake-finalize rejection;
- claim/refund accounting basics.
Start Postgres and apply schema:
createdb stick
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/stick pnpm db:schemaStart web:
pnpm devStart keeper:
pnpm keeper:devThe web app runs on http://localhost:3000.
-
Generate a fresh program id or choose the existing upgradeable program id.
-
Build the Anchor program:
anchor build --no-idl
-
Deploy with an upgrade authority you control:
solana program deploy target/deploy/fair_launchpad.so \ --program-id target/deploy/fair_launchpad-keypair.json \ --upgrade-authority /path/to/upgrade-authority.json \ --keypair /path/to/payer.json \ --url mainnet-beta
-
Verify authority:
solana program show <PROGRAM_ID> --url mainnet-beta
-
Set both program id variables:
NEXT_PUBLIC_LAUNCHPAD_PROGRAM_ID=<PROGRAM_ID> LAUNCHPAD_PROGRAM_ID=<PROGRAM_ID>
-
Run the Postgres schema migration.
-
Build and start the web app.
-
Start keeper with a funded operational wallet.
-
Test in this order:
- below-target refund;
- target hit without oversubscription;
- oversubscription with two or more wallets;
- successful claim;
- successful Pump route;
- fee-sharing config for creator fees;
- split route with Jito bundle when using a large enough target.
- Keep the sponsor wallet low-balance and scoped to launchpad transactions.
- Keep the program upgrade authority separate from hot operational services when moving beyond MVP testing.
- Do not run auto-finalization until Jito endpoints, tip account, RPC, and keeper balance are confirmed.
- If the route transaction creates temporary address lookup tables, close them after use to recover rent.
- Use database backups before truncating test data.
- Treat settlement manifests as public audit artifacts.
Before pushing:
git status --short
git check-ignore -v .env apps/web/.env.local services/keeper/.env target/deploy/fair_launchpad-keypair.jsonExpected: all secret/runtime files should be ignored.
Then initialize and push:
git init
git add .
git commit -m "Initial Stick launchpad monorepo"
git branch -M main
git remote add origin git@github.com:<owner>/<repo>.git
git push -u origin mainPrivate MVP codebase unless a license is added explicitly.