546 NFTs, Provably Fair: Verify the Bored Apes XRP Club Fairdrop
On 2026-08-31(UTC), 546 Bored Apes XRP Club NFTs were distributed to the collection's holders, strictly proportional to how many each held. The amounts contain no randomness at all. The only random step — which specific apes go to which winner — was seeded by the hash of a ledger that did not exist when the rules were anchored on-chain. This page carries every value needed to re-derive the entire drop from public data, plus the one command that does it for you.
The drop in numbers
At ledger 106676567, 1,767 wallets held 9,451 Bored Apes XRP Club NFTs (excluding the 546 sitting in the distributor). Each holder's exact quota is 546 × holdings ÷ 9,451 — about one NFT per 17.3 held. Every holder is guaranteed the floor of that quota (277 NFTs assigned this way); the remaining 269 seats went to the largest fractional remainders, with exact ties broken by holdings (descending) then address (ascending). The result: 294 winners, 546 transfers, zero discretion. All arithmetic is exact BigInt rational math — no floats, so no rounding disputes at any collection size.
Who got which ape
All 546 assignments, exactly as plan.json records them, grouped by winner (largest allocations first). Paste your address to find yours.
The ceremony record
These eleven values pin the whole ceremony. Everything else on this page — and every one of the 546 transfers — is a pure function of them.
| Collection | issuer rEzbi191M5AjrucxXKZWbR5QeyfpbedBcV, taxon 1 |
| Distributor | rESvnQrpWVho8kEiHEVKXMBoiUzdkYVtDL |
| Snapshot ledger | 106676567 |
| snapshotHash | c6fa03cca4718f2207b595492795327b39e8dc0fb5fad923649baf4fd2086783 |
| codeSha256 | 7a7ed0330fa9fa9e5592859a8fc7c562c6eda55af31ab4d8c4b0ec7cd18836db |
| Commitment | b01c3a9f9b6bd9efa43acf118c7855cfc147dd5cdfad4f74502dd903a6167e93 |
| Commitment tx | 52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1 — validated in ledger 106676857, 72 ledgers before the beacon |
| Beacon ledger | 106676929 |
| Beacon ledger hash | 7ADFFDE4B1DBB3A4003EC6CC69374349CF2506327A3F6FA96F700C55452D5BBA |
| Seed | ed0700e7faa8051b68fa6ff15570bb07a4f6f4e6e68f35f186f2da7042068efd |
| transfersHash | fb0e92ce51d4ff0b5d607760ec8f50ed15405d4bd766d46e9a4643e9c62229f4 |
The artifacts live in the open: snapshot.json (who held what), plan.json (all 546 transfers), and fairdrop.js (the algorithm — one dependency-free file whose SHA-256 is the codeSha256 above).
How the commitment locks the rules in advance
Before the beacon ledger existed, the distributor anchored a commitment on-chain: an AccountSet memo (type fairdrop/v1) whose data is the SHA-256 of this exact string — sorted keys, no whitespace:
{"beaconLedger":106676929,"codeSha256":"7a7ed0330fa9fa9e5592859a8fc7c562c6eda55af31ab4d8c4b0ec7cd18836db","snapshotHash":"c6fa03cca4718f2207b595492795327b39e8dc0fb5fad923649baf4fd2086783","version":"fairdrop-v2"}Hash it and you get b01c3a9f9b6b…167e93— the memo's exact contents, validated in ledger 106676857 (52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1). That one memo binds three things at once: the exact holder snapshot, the exact code, and the exact entropy source — 72 ledgers before ledger 106676929 closed. Its hash was produced minutes later by the validator network, not by anyone running the drop. The operator could not know it, and after the commitment could no longer change the rules to suit it.
What the randomness decides — and what it cannot
The seed is SHA-256of the beacon ledger's hash (the 64-character uppercase hex string, hashed as ASCII). It feeds a counter stream: block i is SHA-256(seed ‖ i) with ia big-endian 64-bit counter, each block read as four 8-byte words, each word's top 53 bits divided by 2^53. That stream drives one Fisher–Yates shuffle of the 546 NFT ids (canonically sorted first, so database order cannot leak in), and the shuffled deck is dealt to winners in address order.
That is all it does. The randomness picks which specific apes land with which winner — so the operator cannot aim rare pieces at friends — but it cannot change who wins or how many anyone gets. The counts were fixed by the snapshot arithmetic before the seed existed. Anyone who re-runs the algorithm gets the identical 546 transfers: their canonical JSON hashes to the transfersHash above, byte for byte.
Verify it in one command
The verifier is dependency-free (Node 18+). It takes a few minutes, most of it re-sweeping the snapshot ledger — use --node to point it at any Clio server you trust:
git clone https://github.com/xrplto/bay-nft-airdrop && cd bay-nft-airdrop
sha256sum fairdrop.js # must print 7a7ed0330fa9fa9e5592859a8fc7c562c6eda55af31ab4d8c4b0ec7cd18836db
node fairdrop.js verify --snapshot snapshot.json --plan plan.json \
--node https://s2.ripple.com:51234/ \
--commit-tx 52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1It independently re-sweeps chain state at ledger 106676567 and reproduces snapshotHash; refetches the beacon hash and re-derives the seed; re-runs the algorithm and compares transfersHash; re-checks the allocation in exact rational arithmetic (floors, seat counts, sums, set equality, no self-transfers); and fetches the commitment memo to confirm it matches and validated before the beacon. Thirty-one checks; any failure is loud.
Check the two key hashes with nothing but Node
Don't want to run our code at all? The two load-bearing hashes take one line each.
1. The commitment
node -e 'const c=require("crypto");console.log(c.createHash("sha256").update(
JSON.stringify({beaconLedger:106676929,codeSha256:"7a7ed0330fa9fa9e5592859a8fc7c562c6eda55af31ab4d8c4b0ec7cd18836db",snapshotHash:"c6fa03cca4718f2207b595492795327b39e8dc0fb5fad923649baf4fd2086783",version:"fairdrop-v2"})
).digest("hex"))'
# → b01c3a9f9b6bd9efa43acf118c7855cfc147dd5cdfad4f74502dd903a6167e93Compare that to the MemoData of 52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1 — fetch the transaction from any XRPL node and decode the memo hex.
2. The seed
curl -s https://s2.ripple.com:51234/ -d '{"method":"ledger","params":[{"ledger_index":106676929}]}'
# → ledger_hash 7ADFFDE4B1DBB3A4003EC6CC69374349CF2506327A3F6FA96F700C55452D5BBA
node -e 'const c=require("crypto");console.log(c.createHash("sha256").update(
"7ADFFDE4B1DBB3A4003EC6CC69374349CF2506327A3F6FA96F700C55452D5BBA").digest("hex"))'
# → ed0700e7faa8051b68fa6ff15570bb07a4f6f4e6e68f35f186f2da7042068efdAny full-history node returns the same ledger_hash — it is consensus state, not our data.
Two commitments are on-chain — here is why
The distributor's memo history shows an earlier fairdrop/v1 commitment (66519E745E17F9881288C3EF797F26223ED9E08960DC5DBEB05AED1007011CC0, beacon 106677699, anchored in ledger 106676707). It was superseded by the final commitment to move the beacon from about an hour out to about five minutes out. The switch was decided, anchored and published before either beacon ledger validated — neither hash existed yet — so no draw was ever observed and discarded. Cherry-picking requires seeing at least one outcome; here there was never an outcome to see. Both memos are permanent public record, and the published plan binds only the final one. Same snapshot, same code, in both.
The operator log, as it happened
The ceremony ran live on 2026-08-31, and these are verbatim, timestamped excerpts from its session log — every on-chain fact (transaction hashes, ledgers, sequences, engine results) untouched and checkable against the chain. What is trimmed is internal tooling: infrastructure hostnames, file paths and operator plumbing are redacted or generalized, because a fairness proof should expose the ceremony, not the stack it ran on. Nothing removed changes a single verifiable claim.
Anchoring the commitment — twice
The first commitment set the beacon about an hour out. It was superseded minutes later by a commitment to a nearer beacon — before either beacon ledger validated, so no draw was ever observed. Both anchors, verbatim:
2026-08-31T23:05:43.984Z [execute] signing for rESvnQrpWVho8kEiHEVKXMBoiUzdkYVtDL via on-chain RegularKey rEXYhWuhUatbscRtxVv7PNSrGNUeym3dPm 2026-08-31T23:05:44.729Z [execute] commitment 8fd5185e08e997acc7ab84e31f44be04965f7572f7a2ae7907ff2b87411957df (beacon ledger 106677699) 2026-08-31T23:05:51.018Z [execute] commitment ANCHORED in ledger 106676707 — 992 ledgers before the beacon. Save this tx hash for verify --commit-tx. --- superseded by --- 2026-08-31T23:15:21.557Z [execute] beacon margin: 74 ledgers (~5 min) — the memo must validate before that 2026-08-31T23:15:21.557Z [execute] commitment b01c3a9f9b6bd9efa43acf118c7855cfc147dd5cdfad4f74502dd903a6167e93 (beacon ledger 106676929) 2026-08-31T23:15:22.105Z [execute] submit seq=75482022 AccountSet: tesSUCCESS (52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1) 2026-08-31T23:15:29.727Z [execute] validated 52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1 in ledger 106676857: tesSUCCESS 2026-08-31T23:15:29.727Z [execute] commitment ANCHORED in ledger 106676857 — 72 ledgers before the beacon. Save this tx hash for verify --commit-tx.
Waiting for randomness to exist
A supervised one-shot process polled the validated ledger until the beacon closed:
2026-08-31T23:15:42.086Z [ceremony] ceremony start: beacon=106676929 commitTx=52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1 2026-08-31T23:17:42.124Z [ceremony] waiting for beacon: validated 106676891, 38 ledgers to go (~3 min) 2026-08-31T23:18:42.129Z [ceremony] waiting for beacon: validated 106676907, 22 ledgers to go (~1 min) 2026-08-31T23:20:42.140Z [ceremony] BEACON REACHED: validated 106676938
Plan — one second of math
The beacon hash became the seed; the full allocation followed deterministically:
2026-08-31T23:20:42.140Z [ceremony] RUN node fairdrop.js plan --snapshot snapshot.json --beacon-ledger 106676929 [fairdrop] beacon ledger 106676929 hash 7ADFFDE4B1DBB3A4003EC6CC69374349CF2506327A3F6FA96F700C55452D5BBA [fairdrop] seed ed0700e7faa8051b68fa6ff15570bb07a4f6f4e6e68f35f186f2da7042068efd [fairdrop] 546 nfts -> 294/1767 holders (whole shares=277, largest-remainder seats=269) [fairdrop] transfersHash fb0e92ce51d4ff0b5d607760ec8f50ed15405d4bd766d46e9a4643e9c62229f4 2026-08-31T23:20:42.956Z [ceremony] PLAN DONE
The verify gate
Distribution was gated on the full independent re-verification — 31 checks including the chain resweep and the on-chain commitment:
2026-08-31T23:20:42.956Z [ceremony] RUN node fairdrop.js verify --snapshot snapshot.json --plan plan.json --commit-tx 52BECC0601879F136357F258FA3A14E3753A135D1C3FDB6DDEBA39A533F56FB1 PASS snapshot reproducible from chain at pinned ledger [rebuilt=c6fa03cca4718f22...] PASS transfers reproduce exactly from the seed [rebuilt=fb0e92ce51d4ff0b...] PASS commitment memo matches sha256(snapshot,code,beaconLedger) PASS commitment validated BEFORE beacon ledger [commit@106676857 beacon@106676929] ALL PASS — plan is fully reproducible from public data 2026-08-31T23:21:22.297Z [ceremony] VERIFY ALL PASS — distributing
Distribution
Each batch of eight offers was submitted with consecutive sequences and polled to final validation before the next. Two node failures hit the run, and the design absorbed both. First the public node throttled the pre-scan (HTTP 503s); the executor was restarted against a full-history endpoint, found the eight offers already in flight validated on-chain, and skipped them. Late in the run that endpoint also began returning sustained 503s on validation lookups — after six retries per call the executor failed LOUDLY rather than assume, and exited. Every submitted offer was already validated on-chain: the second restart found 488 live offers, created the remaining 58, and the final audit matched all 546. terPRE_SEQ is XRPL’s retry class (an upstream that had not yet seen the preceding sequence); every such hold resolved to tesSUCCESS. Two node failures, zero lost transactions, zero wrong destinations.
2026-08-31T23:25:05.789Z [ceremony] resume: distributing via our own full-history endpoint (8 offers already on-chain, executor skips them) 2026-08-31T23:25:25.849Z [execute] plan=546 already-offered=8 already-claimed=0 to-create=538 2026-08-31T23:25:25.964Z [execute] submit seq=75482031 NFTokenCreateOffer nft…00000A9C -> r3WfR3u4yahorGBiSnfTE3SABxLYQtNyoo: tesSUCCESS (B0FD5CB2ECEF81086B2B29FC496235AF6028101DBEB66B8CAE533CD3803FBB07) 2026-08-31T23:26:07.478Z [execute] submit seq=75482040 NFTokenCreateOffer nft…00001A61 -> r3idziPApkZBJmnGq2LtvP5Skrti9uDaCx: terPRE_SEQ (0B4850C3EC85380BBE504EAB5750EEF27A87F49EE3F5ABBDD874F79D116AA94C) 2026-08-31T23:26:15.682Z [execute] validated 0B4850C3EC85380BBE504EAB5750EEF27A87F49EE3F5ABBDD874F79D116AA94C in ledger 106677024: tesSUCCESS 2026-08-31T23:44:02.957Z [execute] progress: 296/538 processed (296 ok) 2026-08-31T23:50:24.171Z [execute] progress: 464/538 processed (464 ok) 2026-08-31T23:52:24.828Z [execute] rpc tx attempt 5/6 failed (http 503) — retrying 2026-08-31T23:54:37.248Z [ceremony] CEREMONY FAILED during distribution — executor is idempotent, restart this process to resume 2026-08-31T23:55:53.648Z [ceremony] resume 2: finishing distribution via the local node (validation polling on the previous endpoint hit sustained 503s; all submitted offers are on-chain) 2026-08-31T23:55:53.834Z [execute] plan=546 already-offered=488 already-claimed=0 to-create=58 created=58 skipped(offer)=488 skipped(claimed)=0 failed=0 2026-08-31T23:56:46.760Z [ceremony] DISTRIBUTION DONE offers matching plan: 546/546 already claimed : 0 missing offers : 0 wrong-destination : 0 2026-08-31T23:57:17.745Z [ceremony] CEREMONY COMPLETE
How the NFTs actually move
XLS-20 cannot force-push an NFT into a wallet, so each transfer is a zero-cost sell offer locked to its winner: NFTokenCreateOffer with Amount "0", the tfSellNFToken flag, and Destinationset to the winner's address. Only that address can accept it — a wrong recipient cannot claim someone else's ape even in principle. Winners accept the offer in any XRPL wallet; node fairdrop.js audit-offers --plan plan.json checks that every planned transfer has its matching offer or has already been claimed on-chain.
What this does not prove
Honest limits, so the claim stays exactly as strong as it is. Beacon influence: a participant who lands transactions in the beacon ledger can influence (not choose) its hash; for deciding which ape goes to which winner the attack buys nothing worth its cost, but a high-stakes draw should use a drand round instead — the commit/verify flow is identical. The snapshot is a moment: wallets that bought after ledger 106676567 are not in this draw; the ledger index was published precisely so that is checkable. Claims are voluntary: an offer sits until its winner accepts it, and offers left unclaimed long-term may be cancelled — the plan, not the claim rate, is what the ceremony fixes.