DocsReference
HTTP API
The web app exposes the data it renders as JSON. The routes are read-only, unauthenticated, and served from the same origin as the app (https://<host>/api/...). They exist so that bots, dashboards and other front ends can reuse plop's holder index and split engine instead of re-implementing them. Everything they return can also be derived from chain data alone; see Reading the data.
Conventions#
- Amounts are decimal strings of base units (
"235436800000000000000000"), never JSON numbers. Convert with the token'sdecimals, which every response includes. - Times are unix seconds from block headers (
czas), never wall-clock time. Countdowns are relative to theczasof the same response. - Counts, block numbers and seconds are numbers.
- Errors are
{ "blad": "<sentence>" }with a 4xx or 5xx status.400malformed input,404unknown dropper,409the instance is not configured for this (no registry on its chain, or a token mismatch),502the chain did not answer. - Responses are cached in the server's memory for 10 to 20 s and requests for the same thing are coalesced.
?swiezo=1bypasses the cache; use it sparingly, the chain's RPC rate-limits aggressively. - The registry is permissionless: symbols and names in these responses are whatever a creator wrote. Identify coins by address.
- An instance may hide coins it does not want to show (test coins, spam). Hidden droppers and tokens are absent from
/api/monetyand answer404withThis coin is not shown on this site.everywhere else. The chain is unaffected. - Field names are Polish and mirror the contracts' vocabulary (
kroplomierz= dropper,saldo= balance,doNastepnej= seconds to the next drop). Every table below gives the meaning, and the glossary has the full list.
The shapes below are the TypeScript types in web/components/dto.ts.
GET /api/monety#
The dashboard list: every registered dropper with its state and token metadata, read in one multicall pinned to one block.
| Query | Values | Default |
|---|---|---|
zakladka | kapie (dripping), pauza (paused), puste (empty), wszystkie (all) | kapie |
strona | page number, 30 per page | 1 |
szukaj | case-insensitive match on dropper address, token address (full or prefix), symbol or name | none |
swiezo | 1 to bypass the 10 s cache |
A dropper is empty when its balance is smaller than one drop (saldo < reguly.kwota; or zero when kwota is 0), paused when wstrzymane, dripping otherwise. A paused, empty dropper is in both tabs.
The route reads at most 500 registry entries; beyond that klopot says so.
{
"monety": [
{
"kroplomierz": "0x1234567890abcdef1234567890abcdef12345678",
"token": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"wlasciciel": "0x9999999999999999999999999999999999999999",
"logo": "ipfs://bafkreiexamplecidexamplecidexamplecidexamplecidexamplecid",
"utworzono": 1789482286,
"symbol": "DEMO",
"nazwa": "Demo coin",
"decimals": 18,
"podaz": "1000000000000000000000000000",
"saldo": "941747200000000000000003",
"doNastepnej": 74,
"epoka": 1,
"wstrzymane": false,
"domknieta": true,
"maksNaEpoke": "235436800000000000000000",
"minOdstep": 120,
"wykonawca": "0x5e25b93C6454C10E4110B70DAFa76C6934cDee03",
"reguly": {
"kwota": "235436800000000000000000",
"minSaldo": "0",
"sufitNaAdres": "0",
"minCzasTrzymania": 0,
"okresBezruchu": 3600,
"porowno": false,
"pomijajKontrakty": true,
"pomijajWlasne": true
}
}
],
"ile": 1,
"strona": 1,
"naStronie": 30,
"czas": 1789488585,
"blok": 63787090,
"fabryka": "0x1CE8f5785617eaf9B6BB3c77CE7d242D86Dd0502",
"klopot": null
}
| Field | Meaning |
|---|---|
kroplomierz | dropper address, the identity of a coin in plop and the key of /api/moneta |
wlasciciel | owner at registration; display only |
logo | https:// or ipfs:// URI or null; sanitised, render through your own proxy or an IPFS gateway |
saldo | live balanceOf(dropper) |
doNastepnej | seconds after czas at which a new epoch may open; 0 = now |
epoka | epochs so far, i.e. number of drops |
domknieta | the current epoch is closed |
wykonawca | executor; 0x000…000 means nobody can drop |
reguly | the on-chain rules; kwota: "0" means not set |
ile | total after the filter, for pagination |
klopot | what could not be read, or null |
GET /api/moneta/{kroplomierz}#
One dropper, everything the coin page needs. The path parameter is the dropper address.
Same fields as one element of monety, plus:
| Field | Meaning |
|---|---|
czas, blok | the block all reads were pinned to; compute qualification against this time |
blokKroplomierza | the dropper's deployment block, the start of every log scan; null when it could not be established (then klopot says why) |
ostatniRuch | unix time of the token's latest Transfer, from the block header; 0 = not established |
bezruchPelny | false when the backwards scan did not find a transfer in its window; read ostatniRuch only when true |
Statuses: 400 not an address, 404 not registered, 409 no registry on this chain, 502 the chain did not answer.
GET /api/migawka#
The holder snapshot of a token, built from Transfer logs and cached incrementally on the server's disk. This is the input of the split engine.
| Query | Meaning |
|---|---|
kroplomierz | dropper address; the token is read from its token() |
token | token address, for a coin without a dropper (the launch preview); mutually exclusive with kroplomierz |
prog | holding threshold in base units for odKiedy; default 0. Use the coin's reguly.minSaldo to get the same streaks the keeper uses |
swiezo | 1 to bypass the 20 s cache |
{
"token": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"decimals": 18,
"symbol": "DEMO",
"podaz": "1000000000000000000000000000",
"doBloku": 63787176,
"czas": 1789488594,
"pelna": true,
"prog": "0",
"holdery": [
{ "adres": "0xcccc…", "saldo": "995983353632655580442562568", "odKiedy": 1789480218, "pierwszyRaz": 1789480218, "kontrakt": true },
{ "adres": "0xdddd…", "saldo": "805189751761844465545842", "odKiedy": 1789480219, "pierwszyRaz": 1789480219, "kontrakt": false }
],
"msTrwania": 447
}
| Field | Meaning |
|---|---|
doBloku, czas | the snapshot block and its header time |
pelna | the sum of balances equals totalSupply at doBloku; when false the index has a gap and every derived number is a lower bound |
prog | the threshold the streaks were computed with |
holdery[].saldo | balance at doBloku |
holdery[].odKiedy | since when the balance has continuously been ≥ prog; 0 = no streak |
holdery[].pierwszyRaz | first time the address received the token; display only |
holdery[].kontrakt | the address had code |
msTrwania | how long the scan took |
Holders are sorted by balance descending. The first full scan of a busy token takes seconds; later calls extend the cache from where it stopped. A snapshot for token uses the same cache as one for any dropper of that token.
To reproduce the coin page's qualifies / reason column, feed this response and the coin's reguly to the split engine with now = czas.
GET /api/historia#
Every drop of a dropper, oldest first, with the dropper's balance after each one.
| Query | Meaning |
|---|---|
kroplomierz | dropper address (required on a public instance) |
{
"krople": [
{
"epoka": 1,
"odbiorcow": 5,
"suma": "235436799999999999999997",
"saldoPo": "941747200000000000000003",
"skrotListy": "0x…",
"skrotRegul": "0x…",
"domkniecie": true,
"blok": 63753710,
"czas": 1789485157,
"tx": "0x…"
}
],
"odBloku": 63726100,
"doBloku": 63787181,
"pelna": true,
"powod": null
}
One element per Kropla event, i.e. per batch; an epoch with several batches appears several times with the same epoka. saldoPo is reconstructed backwards from the live balance using Kropla, Zasilenie and Wyplata events, so it is exact for the latest drop and an approximation before any funding done with a plain transfer or through a fee-on-transfer token. pelna: false with powod means the scan could not cover the dropper's whole life.
GET /api/dostalem#
What one wallet actually received from one dropper, counted from the token's Transfer events (from = dropper, to = wallet), so fee-on-transfer tokens are reported as delivered, not as intended.
| Query | Meaning |
|---|---|
kroplomierz | dropper address |
adres | wallet address |
{ "suma": "47196412821206234847704", "wyplat": 5, "ostatnia": 1789485741, "pelna": true, "powod": null }
ostatnia is the header time of the latest payout, 0 when none. With pelna: false the sum is a lower bound.
GET /api/moje-tokeny#
Tokens a wallet currently holds, discovered from Transfer logs to that wallet and confirmed with live balanceOf. Powers the coin picker on the launch screen.
| Query | Meaning |
|---|---|
adres | wallet address |
swiezo | 1 to bypass the 15 s cache |
{
"tokeny": [
{
"adres": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"symbol": "DEMO",
"nazwa": "Demo coin",
"decimals": 18,
"saldo": "1177184033103841900617129",
"podaz": "1000000000000000000000000000",
"udzialWPodazy": "0.001177184033103841",
"maKroplomierz": true,
"kroplomierz": "0x1234567890abcdef1234567890abcdef12345678",
"logo": "ipfs://…"
}
],
"doBloku": 63787000,
"odBloku": 63500000,
"pelna": false,
"czas": 1789488500,
"blok": 63787000,
"klopot": 0
}
The scan looks at a window of recent blocks per request (a filter without an address is limited to about 40 000 blocks per query on this chain) and extends the window on each request; odBloku/pelna say how far it got. A token that answers neither symbol() nor decimals() is dropped and counted in klopot. Sorted by share of supply, descending.
POST /api/rpc#
A JSON-RPC proxy for the app's own browser code, so that the server's RPC endpoint never reaches the client. It accepts single requests and batches of up to 50, read-only methods only:
eth_chainId, eth_blockNumber, eth_call, eth_getCode, eth_getBalance, eth_getLogs, eth_getBlockByNumber, eth_getTransactionByHash, eth_getTransactionReceipt, eth_getTransactionCount, eth_estimateGas, eth_gasPrice, eth_feeHistory, eth_maxPriorityFeePerGas, net_version.
eth_sendRawTransaction is refused with -32601: wallets broadcast through their own provider. Limits: 120 requests per 10 s per IP, 256 KB body, 20 s upstream timeout, no CORS headers. Upstream error messages are rewritten so they never reveal the endpoint. This route is not meant as a public RPC; point your own software at the chain's RPC.
GET /api/obraz?cid=<CID[/path]>#
IPFS image proxy for token logos. Accepts a CID (base58 CIDv0 or base32 CIDv1) with an optional path, tries a short list of gateways with a cool-off on failure, caches on disk, returns image/* only, at most 4 MB, with a sandboxing Content-Security-Policy. It never fetches arbitrary URLs: the caller chooses the content hash, the server chooses where to fetch it from. https:// logos are loaded by the browser directly.
Internal routes#
GET /api/stan and POST /api/wdrozenie serve the legacy single-deployment setup screen (/start) used by the local development environment. They are not part of the public API and may change without notice.
Examples#
S=https://<host>
# all dripping coins, first page
curl -s "$S/api/monety" | jq '.monety[] | {symbol, kroplomierz, saldo, doNastepnej}'
# one coin, its rules and the block it was read at
curl -s "$S/api/moneta/0x1234567890abcdef1234567890abcdef12345678" | jq '{symbol, czas, blok, reguly}'
# holders with the coin's own minimum balance as the streak threshold
curl -s "$S/api/migawka?kroplomierz=0x1234567890abcdef1234567890abcdef12345678&prog=0" | jq '.holdery | length'
# what a wallet received
curl -s "$S/api/dostalem?kroplomierz=0x1234…5678&adres=0xabcd…ef01" | jq
// Recompute the coin page's qualification column in your own code
const coin = await (await fetch(`${S}/api/moneta/${dropper}`)).json()
const snapshot = await (await fetch(`${S}/api/migawka?kroplomierz=${dropper}&prog=${coin.reguly.minSaldo}`)).json()
// the engine's input shapes keep the API's field names
const holders = snapshot.holdery.map((h) => ({ adres: h.adres, saldo: BigInt(h.saldo), odKiedy: h.odKiedy, kontrakt: h.kontrakt }))
const rules = {
kwota: BigInt(coin.reguly.kwota), // amount per drop
podzial: coin.reguly.porowno ? 'porowno' : 'proporcjonalnie', // equal or proportional
minSaldo: BigInt(coin.reguly.minSaldo), // minimum balance
minCzasTrzymania: coin.reguly.minCzasTrzymania, // minimum holding time
sufitNaAdres: coin.reguly.sufitNaAdres === '0' ? null : BigInt(coin.reguly.sufitNaAdres), // per-wallet cap
pomijajKontrakty: coin.reguly.pomijajKontrakty, // skip contracts
wykluczone: coin.reguly.pomijajWlasne ? [coin.wlasciciel, coin.wykonawca] : [], // excluded wallets
}
const result = policz({ czas: snapshot.czas, holdery: holders }, rules, snapshot.czas, { kroplomierz: dropper })
// result.przydzialy = allocations, result.odrzuceni = rejected with reasons, result.reszta = remainder