DocsStart

Overview

plop is three components and one chain.

 creator's wallet                       holders' wallets
       │ approve + zasil (fund)                ▲
       ▼                                       │ token.transfer, one per recipient
 ┌─────────────────────┐   kapnij(list)   ┌────┴───────────┐
 │  Kroplomierz        │ ◄────────────────┤  keeper        │  reads the registry every
 │  (dropper)          │                  │  (executor key)│  15 s, snapshots holders,
 │  holds the supply   │  Kropla event    │                │  applies the rules, sends
 │  caps every epoch   ├────────────────► │                │  the batches, keeps a journal
 └─────────┬───────────┘                  └────────────────┘
           │ listed in, rules stored in            ▲
           ▼                                       │ eth_getLogs Transfer(token)
 ┌─────────────────────┐                  ┌────────┴───────┐
 │  Fabryka            │  lista(), wpis() │  web app       │  dashboard, coin page,
 │  (registry)         ├────────────────► │  Next.js + API │  launch flow, JSON API,
 │  never holds tokens │                  │                │  same rules engine as keeper
 └─────────────────────┘                  └────────────────┘
ComponentWhereRole
Kroplomierz ("dropper")kontrakty/src/Kroplomierz.sol, one instance per airdropHolds one token's supply. Pays out batches when its executor calls kapnij. Enforces a hard cap per epoch, a minimum interval between epochs, a pause switch and an owner-only withdrawal.
Fabryka ("registry")kontrakty/src/Fabryka.sol, one instance per chainDeploys droppers (utworz), lists them, stores each dropper's split rules and logo. Never holds tokens. Permissionless.
Keeperkeeper/fabryka.mjsThe executor. Reads the registry, and for every dropper whose executor is its key: snapshots holders, computes the split, sends the batches, journals every step.
Web appweb/ (Next.js 15, viem)Dashboard of all droppers, per-coin page with rules, holders and drop history, one-click launch, and a JSON API that other software can consume.

Lifecycle of an airdrop#

  1. Create. The creator calls Fabryka.utworz(token, executor, capPerEpoch, minInterval, logo, rules). The registry deploys a new dropper owned by the caller and lists it. In the web app this is the first of three signatures on the Add airdrop screen.
  2. Fund. The creator approves the dropper and calls Kroplomierz.zasil(amount). Anyone can fund any dropper; funding is a gift to the holders. The dropper's balance is always token.balanceOf(dropper); there is no internal ledger.
  3. Drip. Every minOdstep seconds the executor may open a new epoch by calling kapnij(epoch + 1, recipients, amounts, rulesHash, close). Large lists are split into several batches in the same epoch; all batches count towards the same cap. The dropper emits Kropla for every batch.
  4. Repeat until the balance is smaller than one drop. The web app then shows the coin as empty. The creator can top it up at any time (Add supply).
  5. Stop or take back. The owner can pause drips (ustawPauze), change the executor, cap and interval (ustaw), or withdraw the remainder (wyplac) at any time. The rules may declare a quiet period after which the creator intends to take the rest back; the contract does not enforce it.

Who qualifies for a drop#

Qualification is computed from a snapshot of the token's holders at a block, using the rules stored in the registry:

  • balance at the snapshot block must be at least minSaldo;
  • the balance must have stayed at or above that threshold continuously for at least minCzasTrzymania seconds (dipping below resets the clock);
  • contracts are skipped when pomijajKontrakty is set (pools, routers, the bonding curve);
  • the dropper's owner and executor are skipped when pomijajWlasne is set;
  • the dropper itself is always skipped;
  • the drop is split proportionally to balance, or equally when porowno is set, with an optional per-wallet cap sufitNaAdres whose overflow is redistributed.

The exact algorithm, including rounding and ordering, is specified in Split rules. The web app and the keeper run the same code (web/lib/regula.ts), so the list shown on the coin page is the list the keeper would send.

Epochs, batches and the cap#

The dropper does not know who should receive tokens; it only limits how much can leave and how often:

  • maksNaEpoke is a hard cap on the sum of all batches of one epoch. Splitting a drop into a hundred calls does not raise it.
  • minOdstep is the shortest allowed time between two epoch openings. Zero is rejected by the constructor and by ustaw.
  • kapnij with epokaId == epoka + 1 opens a new epoch (interval enforced); with epokaId == epoka it continues the current one (no interval check) unless it was closed; anything else reverts.

Worst case, with a hostile executor, at most maksNaEpoke × (floor(86400 / minOdstep) + 1) base units can leave the dropper per day. The creator chooses both numbers; the Add airdrop screen shows the resulting figure before anything is signed.

Time and numbers on this chain#

Facts measured on Robinhood Chain that shape the code and the docs:

  • Blocks are about 100 ms apart, produced by a single sequencer, first come first served, no public mempool and therefore no priority-fee auction. The keeper sends plain legacy transactions with gasPrice from the node.
  • block.number returns the Ethereum L1 block number, as on every Arbitrum Orbit chain. The contracts use block.timestamp exclusively.
  • blockTimestamp in eth_getLogs results can be 0x0. Every timestamp in the API is fetched from the block header instead.
  • eth_getLogs is limited: roughly 50 000 to 100 000 blocks per query, at most 10 000 logs per response, and addresses × blocks must stay at or below 200 000. A filter without an address counts as five addresses. Over-use answers 429 without a Retry-After header; the node recovers in about 0.7 s.
  • The public RPC is not an archive node. Historical eth_getCode and state reads need an archive endpoint; the web app keeps such an endpoint server-side and never exposes it.

Repository layout#

kontrakty/          Foundry project: Kroplomierz.sol, Fabryka.sol, tests, deploy scripts
web/                Next.js app (App Router), viem, TypeScript
  lib/regula.ts       split engine (pure, shared with the keeper)
  lib/holdery.ts      holder index built from Transfer logs
  lib/fabryka.ts      registry reads and rule conversions
  app/api/**          JSON routes, see HTTP API
  components/dto.ts   JSON shapes of every route
keeper/             executor: fabryka.mjs (all droppers), harmonogram.mjs (one dropper)
docs/               these pages

Glossary#

The contracts, the API and the code use Polish identifiers. This table is the bridge.

Contracts and roles#

IdentifierMeaning
Fabrykathe registry contract ("factory")
Kroplomierza dropper contract ("drip meter"); one per airdrop, one token each
kropla / Kroplaa drop; the event emitted for every batch
kapnijfire a drop (executor only)
epokaepoch: one drop, possibly several batches, capped as a whole
wsada batch: one kapnij transaction
wykonawcaexecutor: the only address allowed to call kapnij
wlasciciel / owner()owner of a dropper (two-step Ownable2Step)
zasil / Zasileniefund the dropper / the funding event
wyplac / Wyplataowner withdrawal / its event
ustaw / Ustawieniaowner sets executor, cap and interval / its event
ustawPauze / wstrzymane / Wstrzymaniepause switch / paused flag / its event
domknieta / domkniecieepoch closed flag / "this batch closes the epoch"
maksNaEpokehard cap per epoch, base units
minOdstepminimum interval between epoch openings, seconds
ostatniaKroplatimestamp at which the current epoch was opened
wydaneWEpocesum of intended amounts sent in the current epoch
stan()(saldo, doNastepnej, epokaTeraz): balance, seconds until a new epoch may open, epoch counter
utworz / Utworzonocreate and list a dropper / the listing event
zglosregister a dropper deployed outside the registry
ustawReguly / ZmianaRegulset split rules / the rules-changed event
ustawLogo / ZmianaLogoset logo URI / the logo-changed event
lista, ile, wpis, czyWpisany, poTokenielist entries, count, one entry, is-registered, droppers of a token
Wpisa registry entry
przezFabryke"created through the registry" flag on Utworzono
utworzonoregistration timestamp

Rules#

IdentifierMeaning
Reguly / regulythe split rules struct
kwotaamount per drop, base units; 0 means "not set yet"
minSaldominimum balance to qualify, base units
sufitNaAdresper-wallet cap per drop, base units; 0 means no cap
minCzasTrzymaniaminimum continuous holding time, seconds
okresBezruchudeclared quiet period, seconds; 0 means not declared
porownoequal split (true) instead of proportional (false)
pomijajKontraktyskip contract addresses
pomijajWlasneskip the dropper's owner and executor
wykluczonemanual exclusion list (off chain, keeper-side)
podzialsplit mode in the engine: proporcjonalnie or porowno
skrotRegulkeccak256 of the canonical rules text
skrotListykeccak256 of abi.encode(address[] recipients, uint256[] amounts)
przydzialy / odrzuceni / resztaallocations / rejected holders with reasons / remainder left in the dropper

Data#

IdentifierMeaning
migawkaholder snapshot
holdery / saldo / odKiedy / pierwszyRaz / kontraktholders / balance / held-since timestamp / first-acquired timestamp / is a contract
podaztotalSupply
pelnathe scan is complete; false means a gap
powod / klopot / bladreason / trouble note / error message
czas / blok / doBloku / odBlokublock-header time / block number / scan upper bound / scan lower bound
doNastepnejseconds until a new epoch may open
zakladkadashboard tab: kapie dripping, pauza paused, puste empty, wszystkie all
szukaj / strona / swiezosearch / page / bypass the server cache
dostalem"what I received"
moje-tokenytokens held by a wallet
obrazimage (IPFS proxy)