Contracts overview
The Droppie protocol is two small contracts, built with Foundry, Solidity ^0.8.24, and OpenZeppelin v5:
| Contract | Role |
|---|---|
DroppieDrop | One airdrop campaign — escrows the tokens, verifies Merkle proofs, pays claims |
DroppieERC20 | "Token Alchemist" — a no-code ERC-20 with optional mint/burn |
Direct-deploy architecture
There are no factories, no proxies, and no pre-deployed infrastructure. Every campaign is its own plain contract, deployed straight from the creator's wallet:
- The deployer is the creator. The wallet that sends the deployment transaction becomes
creator— the only address allowed tosweep. There is no creator parameter and no third party in the loop; you own the contract from block one. - Parameters are immutable.
token,merkleRoot,startTime, andendTimeare Solidityimmutables baked into the runtime code at deployment;metadataURI(a dynamic string) is set in the constructor and has no setter. There is noinitialize, no admin, no upgrade path — what you deploy is what runs forever. - Funding is a separate, plain transfer. After deployment the creator sends
token.transfer(drop, totalAmount)— noapprove, notransferFrom. Until the transfer lands the drop is simply underfunded (details). - Any EVM chain works. Because nothing needs to be pre-deployed, Droppie runs on every configured chain out of the box. The creation bytecode the app deploys ships inside
@droppie/sdkasdroppieDropBytecodeanddroppieErc20Bytecode.
Every campaign is fully isolated: one drop = one contract = one token balance. A bug or sweep in one campaign can never touch another's funds.
DroppieDrop constructor
constructor(
IERC20 token_,
bytes32 merkleRoot_,
uint64 startTime_, // 0 = immediate
uint64 endTime_, // 0 = no deadline
string memory metadataURI_ // distribution JSON (ipfs://, https://, data:)
)
| Parameter | Description |
|---|---|
token_ | The ERC-20 being distributed. Must be non-zero. |
merkleRoot_ | Root of the recipients tree (leaf scheme). Must be non-zero. |
startTime_ | Unix timestamp claims open. 0 means claims open immediately. |
endTime_ | Unix timestamp claims close. 0 means no deadline. If non-zero, must lie strictly after both startTime_ and the current block timestamp. |
metadataURI_ | Where the distribution JSON lives. |
Reverts with ZeroAddress, ZeroMerkleRoot, or InvalidTimeWindow if validation fails. See the full DroppieDrop reference for claim, sweep, views, events, and errors.
DroppieERC20 — the Token Alchemist
The Token Alchemist (/app/tokens in the app) deploys production-grade ERC-20s without writing Solidity. It's a single OpenZeppelin v5-based contract with 18 decimals and two immutable feature flags — pick mintable and/or burnable at deploy time. Like the drop, it's deployed directly by your wallet; it modernizes the legacy four-variant token deployer into one parameterized contract.
constructor(
string memory name,
string memory symbol,
uint256 premintWei, // RAW wei — 1 token = 10^18
address owner_, // mint authority; receives the premint
bool mintable_,
bool burnable_
)
| Parameter | Description |
|---|---|
name / symbol | Standard ERC-20 metadata. |
premintWei | Initial supply in raw wei, minted to owner_. May be zero. |
owner_ | Token owner (mint authority). The app passes your connected address. |
mintable_ | Whether the owner may mint after deployment. Immutable. |
burnable_ | Whether holders may burn. Immutable. |
premintWei is the raw amount, already scaled by 18 decimals — the frontend does the multiplication. This fixes the legacy deployer's unit asymmetry, where some variants took whole tokens and others took wei. To premint 1,000,000 tokens, pass 1000000 * 10^18.
Functions
| Function | Access | Behavior |
|---|---|---|
mint(address to, uint256 amount) | onlyOwner | Mints amount (raw wei) to to. Reverts NotMintable() if the token was deployed with mintable_ = false. |
burn(uint256 amount) | Anyone (own balance) | OZ ERC20Burnable semantics. Reverts NotBurnable() if burnable_ = false. |
burnFrom(address account, uint256 amount) | Requires allowance | OZ ERC20Burnable semantics. Reverts NotBurnable() if burnable_ = false. |
| Standard ERC-20 | — | transfer, approve, transferFrom, balanceOf, allowance, totalSupply, name, symbol, decimals (always 18) |
Errors
error NotMintable();
error NotBurnable();
The flags are fixed at deployment. A token deployed with mintable_ = false has a hard supply cap at its premint — no owner, including the deployer, can ever mint more.
Deploying outside the app
The app deploys via deployContract with the SDK's bytecode, but nothing about the contracts is app-specific — forge create works just as well:
cd packages/contracts
forge build
# deploy a drop: token, merkle root, startTime, endTime, metadataURI
forge create src/DroppieDrop.sol:DroppieDrop \
--rpc-url $RPC_URL \
--private-key $CREATOR_KEY \
--broadcast \
--constructor-args \
0xYourTokenAddress \
0xYourMerkleRoot \
0 0 \
"ipfs://.../distribution.json"
# fund it — one plain transfer, no approve
cast send 0xYourTokenAddress \
"transfer(address,uint256)" 0xTheNewDropAddress $TOTAL_AMOUNT \
--rpc-url $RPC_URL --private-key $CREATOR_KEY
The wallet behind $CREATOR_KEY becomes the drop's creator. Deploying a token works the same way with src/DroppieERC20.sol:DroppieERC20 and its six constructor args.
Security posture
- Contracts follow OpenZeppelin v5 primitives (
SafeERC20,MerkleProof,ERC20Burnable,Ownable). - The full Foundry test suite covers claim happy paths, double-claim and wrong-proof reverts, window enforcement, sweep authorization and timing, underfunded-drop behavior, campaign isolation, token flags, and fuzzing on amounts.
- The contracts have not undergone a third-party audit. See the FAQ before committing significant value.