Use vouchers
How to create a voucher campaign as a token creator, and how to redeem one as a recipient.
Vouchers are an off-chain-distributed, on-chain-redeemed promo primitive. Pre-fund ETH, hand out tickets, let recipients self-redeem. See Vouchers (protocol) for the mechanism.
Create a campaign — creator side
Open /voucher/new. You’ll choose between Link mode (open-link / QR distribution) and Whitelist mode (Merkle-gated wallets).
Step 1 — Pick a mode
🔗 Open link — sign N vouchers; share each as a link. Anyone with the link can claim — first wallet wins. Good for:
- Public airdrop drops where you don’t know recipients in advance
- KOL marketing where you DM each influencer a unique URL
- Drops that feel like “scratch tickets”
📋 Whitelist — restrict to specific wallets, each redeems up to their declared allocation. Good for:
- Pre-announced allowlist (followers, partners)
- Anti-bot drops (no public link to scrape)
- Per-wallet quotas (give one wallet 1 voucher, another 10)
Step 2 — Pick a token
Use the token search. The list is filtered to INIT / BATTLE / GRADUATED tokens only — frozen states won’t accept vouchers.
Step 3 — Voucher amount (ETH per voucher)
The fixed ETH that each voucher will spend at redeem time. Minimum 0.001 ETH. The contract enforces this — going lower reverts with BadEthPerVoucher.
Common presets: 0.01, 0.05, 0.1, 0.5 ETH.
Step 4 — Distribution-specific config
Link mode:
- Set Total vouchers (1 to 1000). Each voucher is one redeemable slot.
msg.valueyou’ll pay =ethPerVoucher × totalVouchers. UI computes & shows.
Whitelist mode:
- Paste or upload addresses (one per line, comments start with
#). - Each wallet listed will get 1 voucher by default; if you want per-wallet allocations > 1, edit the textarea:
0xABC… 5(address, space, allocation). - The UI builds the Merkle tree client-side, computes the root, and submits it with the campaign.
msg.value=ethPerVoucher × sum(all allocations).
Step 5 — Sign & send
UI calls VoucherManager.createCampaign(...) (Link) or createCampaignWhitelist(...) (Whitelist) with the ETH attached. Confirm in your wallet.
After confirmation:
- Link mode: UI prompts you to sign N vouchers (one
eth_personal_signper slot). The signatures are persisted locally + optionally posted to the protocol’s signing-service for short-code lookup. UI then opens the QR / link sharing modal — one link per voucher slot. - Whitelist mode: UI persists the Merkle tree locally + posts to signing-service. Recipients fetch their proof from that store at redeem time.
Step 6 — Share
Link mode outputs:
https://battle.meme/voucher/redeem?c=42&v=7&s=0xabc…Or a short-code wrapped via the signing-service:
https://battle.meme/voucher/redeem?code=8h2k9pDrop in DMs, Telegram, X DMs, QR posters, etc. The first wallet to redeem each slot wins (one wallet can claim at most one slot per campaign).
Whitelist mode outputs:
https://battle.meme/voucher/redeem?c=42Send to your whitelisted wallets. The UI auto-resolves their allocation + Merkle proof from the persisted tree.
Cancel a campaign
Open /voucher/dashboard (or your portfolio).
- Cancel specific slots — pick voucher indexes that haven’t been redeemed yet; refund returns to you. Already-redeemed slots are silently skipped.
- Cancel all — fast-path. Marks the campaign cancelled (subsequent redeems short-circuit) and refunds every un-redeemed slot in one tx.
Redeem a voucher — recipient side
Open the link the creator sent you. The redeem page (/voucher/redeem?…) decodes the params from the URL.
Link mode — recipient flow
- Connect wallet.
- UI shows: token, ETH per voucher, expected token output (preview based on current pool price), creator address, slippage tolerance.
- Click Redeem voucher.
- UI calls
VoucherManager.redeem(campaignId, voucherIndex, signature, minTokensOut). - The contract:
- Verifies signature recovers to
campaign.signer - Marks this slot consumed forever
- Marks your wallet “claimed in this campaign” (you can’t claim another slot in the same Link campaign)
- Swaps
ethPerVoucherETH → TOKEN at current pool price - Forwards the TOKEN output to your wallet
- Verifies signature recovers to
Common errors:
| Error | Cause | Fix |
|---|---|---|
AlreadyRedeemed | Slot was claimed by someone else first | Sorry, first wallet wins |
AlreadyClaimedInCampaign | You already redeemed a different slot in this campaign | One per wallet per campaign |
BadSignature | Signature mismatch (corrupted link?) | Get a fresh link from the creator |
BadTokenState | Token entered a frozen state (QUEUE / WARMUP / ELIMINATED / LOST) | Wait for INIT/BATTLE/GRADUATED |
SlippageTooHigh | Pool moved; would give fewer tokens than minTokensOut | Increase slippage tolerance in UI and retry |
Whitelist mode — recipient flow
- Connect wallet — the wallet must be in the whitelist.
- UI fetches the tree (from signing-service or local cache) and computes your Merkle proof + allocation.
- UI shows: token, ETH per voucher, your allocation, how many you’ve already redeemed.
- Click Redeem voucher (you can redeem multiple times up to your allocation).
- UI calls
VoucherManager.redeemWhitelist(campaignId, allocation, proof, minTokensOut). - The contract:
- Verifies
keccak(keccak(abi.encode(msg.sender, allocation)))is in the tree (via proof) - Increments
whitelistRedeemedBy[campaignId][msg.sender] - Reverts
OverAllocationif count would exceedallocation - Swaps
ethPerVoucherETH → TOKEN - Forwards the TOKEN output to your wallet
- Verifies
Common errors:
| Error | Cause | Fix |
|---|---|---|
BadProof | Wallet isn’t in the whitelist, or proof is wrong | You’re not on the list — DM the creator |
OverAllocation | Already redeemed your full allocation | All used up |
BadTokenState | Token entered a frozen state | Wait |
SlippageTooHigh | Pool moved | Raise slippage |
Receive-side outcome
After a successful redeem, your wallet holds the token. Same as if you’d manually swapped ethPerVoucher ETH on Uniswap — except the creator paid the ETH.
Now you can:
- Hold the token through the battle (if it’s still pre-graduation)
- Sell on Uniswap any time (subject to state-machine gates)
- Use it to qualify for further drops, etc.
Gas + fee notes
- Recipient pays gas for the redeem tx.
- The 1.3% pool fee still applies on the underlying swap — split as normal (1.0% LP fee + 0.3% creator). Yes, the campaign creator is paying for a swap that ALSO earns them 0.3% of the swap — slightly comical but matches every other buy.
- No additional protocol fee on top of the swap fee.
See also: Vouchers (mechanism), VoucherManager contract.