Skip to Content
⚔ BattleMeme docs · early preview · expect rough edges
ProtocolVouchers

Vouchers

A voucher is a pre-funded right to buy a BattleMeme token at the current pool price, redeemable by a specific person (or a whitelisted wallet). The campaign creator locks ETH up-front; recipients redeem on-chain; the contract auto-swaps the ETH into tokens and forwards them.

Vouchers are independent of the battle lifecycle. They don’t queue tokens, don’t affect scoring, don’t consume any battle ETH. They’re a marketing / airdrop primitive built on top of the pool — that’s it.

Why vouchers exist

Most launchpads handle airdrops with off-chain spreadsheets or pre-mints reserved for the team. Both are bad:

ApproachProblem
Off-chain CSV airdropSingle-tx mass-transfer drains gas / favors bots; no replay protection
Pre-mint reserved for teamBreaks fair launch; rug risk
”Just buy and gift it”Creator pays gas + slippage twice; no anti-bot guard

Vouchers solve all three:

  • Pre-funded ETH is locked in VoucherManager, not on the creator’s hot wallet.
  • One redeem per recipient is enforced on-chain (hasClaimedInCampaign map for Link mode, whitelistRedeemedBy for Whitelist mode).
  • Distribution is off-chain (DM / QR / email / whitelist file) → no public mempool race, no MEV bots.
  • Token state is checked at redeem — vouchers can’t be redeemed when the token is QUEUE/WARMUP/RESTING/ELIMINATED/LOST. INIT, BATTLE, GRADUATED only.

Two modes

The creator picks a signer address (themselves, or a dedicated keypair on a server) and pre-signs one voucher per slot off-chain. Each signed message is a unique redeem ticket. Recipient pastes the link / scans the QR; the contract verifies the signature.

One-per-wallet enforcement. A wallet can only redeem one voucher per Link campaign. The signature also enforces single-use per slot. So mass-spamming the same link can’t drain a campaign.

Signature replay protection. Each digest binds (VoucherManager address, chainId, campaignId, voucherIndex), so the same signature can’t be replayed across networks or campaigns.

📋 Whitelist mode — Merkle proof

The creator publishes a Merkle root over (wallet, allocation) leaves. Whitelisted wallets self-redeem with a proof; the contract verifies. No signatures needed.

Leaf hash:

leaf=keccak256(keccak256(abi.encode(wallet,allocation)))\text{leaf} = \text{keccak256}(\text{keccak256}(\text{abi.encode}(\text{wallet}, \text{allocation})))

Total ETH locked: msg.value=ethPerVoucher×iallocationi\text{msg.value} = \text{ethPerVoucher} \times \sum_i \text{allocation}_i.

Per-wallet allocation. Unlike Link mode, a wallet may redeem multiple times in Whitelist mode — up to their declared allocation. So you can give a KOL 10 vouchers worth in a single tree entry.

When to use which mode

ScenarioMode
Generic airdrop link in your Telegram / Discord — first-comers winLink
KOL marketing — each KOL gets 1 specific QR you DM themLink
Pre-announced allowlist for token sale → followers self-redeemWhitelist
Internal team / partner allocation with specific quotasWhitelist
Anti-bot: don’t want the airdrop to be public-claimable on-chainWhitelist
Want recipients to redeem before knowing if they’re pickedLink (sealed envelope vibe)

Constraints

LimitValueWhy
MIN_ETH_PER_VOUCHER0.001 ETHSweep economics; tiny vouchers aren’t worth gas
MAX_VOUCHERS_PER_CAMPAIGN1000Bounds gas for cancellation paths
Eligible token statesINIT, BATTLE, GRADUATEDFrozen states (QUEUE, WARMUP, ELIMINATED, LOST) reject redeem
Per-wallet limit (Link)1 voucher per campaignAnti-spray
Per-wallet limit (Whitelist)Declared allocationSet by creator at tree-build time

Cancellation

The campaign creator can cancel un-redeemed vouchers any time:

  • cancelCampaign(campaignId, indexes[]) — refund specific slots (idempotent; already-redeemed indexes silently skipped)
  • cancelAll(campaignId) — fast-path; sets campaign cancelled = true and refunds everything un-redeemed in one tx

Refund always goes to campaign.creator (the original deployer). Already-redeemed vouchers can’t be clawed back.

Token state guard

A campaign cannot be created or redeemed against a token in a non-tradable state. The contract checks hook.getState(poolId) at both create-time and redeem-time:

StateCreate?Redeem?
NONE
INIT
QUEUE
WARMUP
BATTLE
RESTING
ELIMINATED
GRADUATED
LOST

This means if you set up a Whitelist campaign during INIT and the token enters QUEUE before everyone has redeemed, redemption is paused — they’ll have to wait for BATTLE or finalize.

Security model

  • Signatures are EIP-191 personal-sign wrapping keccak256(VoucherManager, chainId, campaignId, voucherIndex). Any wallet that can eth_sign / eth_personal_sign can be the campaign signer.
  • Merkle leaves use the OZ StandardMerkleTree double-hash convention: keccak256(bytes.concat(keccak256(abi.encode(wallet, allocation)))). The contract exposes whitelistLeaf(wallet, allocation) as a helper so off-chain builders match exactly.
  • No signature replay across networks. ChainId is baked into the digest.
  • Slippage floor. Redeem requires minTokensOut. Recipients (or their UI) should set this to avoid bad fills if the pool moves between sign-and-share and redeem.

How vouchers interact with battles

They don’t, directly. But:

  • Voucher redeems credit totalETHRaised like any normal buy — so a busy voucher campaign can push a token toward the 5 ETH queue threshold or push score during a battle round.
  • Voucher redeems are subject to the same 1.3% pool fee — the 0.3% creator share + 1.0% LP fee share apply normally.
  • Voucher redeems pause when the token is frozen (QUEUE / WARMUP / RESTING / ELIMINATED / LOST) — they can’t bypass the trading freeze.

So you can totally use vouchers as a “rally minute” tool during WARMUP-prep — pre-mint a Whitelist allocation for your community, then drop the proof when WARMUP ends and BATTLE opens, to pump R1 score.

See also: Use vouchers (step-by-step UI guide), VoucherManager contract (full API).