From f81ebd47468b83ab1ec365d5ff6ea0a0d093a605 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Mon, 14 Sep 2026 11:49:48 +0300 Subject: [PATCH] feat: who the wormhole exits pay, and how much of it is the protocol itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The premise was that pools are the best surface for observing wormhole transfers, because payouts must leave a pattern in the batches. The pattern is there. Most of it is not payouts. Two things break the obvious reading. Batch *size* is aggregation: the pallet documents a public batch as one segment per inner private batch and a segment as one client, so a 120-output batch is an aggregator bundling unrelated people. And the most recurrent recipient is the aggregator itself — one address is credited in 91.5% of every public batch anyone submitted and in none of the private ones, which is the rebate minted to the proof's `aggregator_address`. QUANPOOL's mining address appears in 501 batches for 7.54 QTC total, 0.015 each: the fee share paid to whoever included the batch. Classified, three addresses take 76% of everything the exits pay. A cohort table that did not say so would be describing aggregator plumbing and calling it an economy, so the share is the section's eyebrow and its first sentence. What survives the subtraction is the finding worth shipping: among genuine recipients, 35 addresses paid twenty or more times take two thirds of the remaining value at twice everyone else's average credit. That is a recurring-payment shape and the page says it is no more than that — the chain records no sender, so a regular payee is as plausibly an exchange deposit or one person on a schedule as a pool meeting its obligations. Batch size is not rendered anywhere, on purpose. Closes #20 Co-Authored-By: Claude Opus 5 --- ...d8b5590c86b3cb8f9809fb3eec479150534be.json | 41 +++++ CLAUDE.md | 32 ++++ crates/blackbeard-api/src/routes.rs | 16 +- crates/blackbeard-data/src/store.rs | 147 ++++++++++++++++++ crates/blackbeard-entities/src/chain.rs | 43 +++++ crates/blackbeard-entities/src/lib.rs | 4 +- web/src/api/generated/ExitCohort.ts | 48 ++++++ web/src/api/generated/WormholeSummary.ts | 5 + web/src/components/WormholePanel.tsx | 138 ++++++++++++++++ 9 files changed, 471 insertions(+), 3 deletions(-) create mode 100644 .sqlx/query-5dfe48cf0681d8946c77c2d6904d8b5590c86b3cb8f9809fb3eec479150534be.json create mode 100644 web/src/api/generated/ExitCohort.ts diff --git a/.sqlx/query-5dfe48cf0681d8946c77c2d6904d8b5590c86b3cb8f9809fb3eec479150534be.json b/.sqlx/query-5dfe48cf0681d8946c77c2d6904d8b5590c86b3cb8f9809fb3eec479150534be.json new file mode 100644 index 0000000..9041e7e --- /dev/null +++ b/.sqlx/query-5dfe48cf0681d8946c77c2d6904d8b5590c86b3cb8f9809fb3eec479150534be.json @@ -0,0 +1,41 @@ +{ + "db_name": "PostgreSQL", + "query": "\n with ev as (\n select e.height, e.extrinsic_index,\n e.fields->>'to' as recipient,\n (e.fields->>'amount')::numeric as amount\n from chain_event e\n join chain_extrinsic x\n on x.chain = e.chain\n and x.height = e.height\n and x.extrinsic_index = e.extrinsic_index\n where e.chain = $1\n and e.pallet = 'Wormhole'\n and e.variant = 'NativeTransferred'\n and x.pallet = 'Wormhole'\n and x.call = 'verify_public_batch'\n ),\n batches as (\n select count(distinct (height, extrinsic_index))::numeric as n from ev\n ),\n per as (\n select recipient,\n count(distinct (height, extrinsic_index)) as credits,\n sum(amount) as amount\n from ev group by 1\n ),\n miners as (\n select distinct fields->>'miner' as address\n from chain_event\n where chain = $1\n and pallet = 'MiningRewards'\n and variant = 'MinerRewarded'\n ),\n classed as (\n select p.credits, p.amount,\n case\n when (select n from batches) > 0\n and p.credits::float8 / (select n from batches)::float8 > $2\n then 'infrastructure'\n when p.recipient in (select address from miners)\n then 'miner_fee'\n when p.credits = 1 then 'paid_once'\n when p.credits between 2 and 4 then 'paid_a_few'\n when p.credits between 5 and 19 then 'paid_often'\n else 'paid_regularly'\n end as kind\n from per p\n )\n select kind as \"kind!\",\n count(*) as \"addresses!\",\n sum(credits)::bigint as \"credits!\",\n coalesce(sum(amount), 0) as \"amount!\"\n from classed\n group by 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "kind!", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "addresses!", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "credits!", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "amount!", + "type_info": "Numeric" + } + ], + "parameters": { + "Left": [ + "Text", + "Float8" + ] + }, + "nullable": [ + null, + null, + null, + null + ] + }, + "hash": "5dfe48cf0681d8946c77c2d6904d8b5590c86b3cb8f9809fb3eec479150534be" +} diff --git a/CLAUDE.md b/CLAUDE.md index 7c43ecd..7ae8f3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -652,6 +652,38 @@ paying a miner, and the "sender" is the runtime. Inside `verify_public_batch` or is not recorded. Collapsing the two would make mining rewards look like anonymous payments. +**Exit batches look exactly like payroll and mostly are not.** Grouping +`NativeTransferred` by producing extrinsic gives one sender, many recipients, +repeating — and the obvious reading of it is wrong twice over. + +*Batch size is aggregation, not generosity.* The pallet documents a public batch +as one segment per inner private batch, and a segment as one client. A 120-output +batch is an aggregator bundling unrelated people, so size measures aggregator +throughput. It is deliberately not reported anywhere on the site. + +*The most recurrent recipient is the aggregator.* The address credited in 774 +batches is in **91.5% of every public batch anyone submitted and 0% of the +private ones** — that is the rebate `settle_exit_bundle` mints to the proof's +`aggregator_address`. QUANPOOL's mining address appears in 501 batches for 7.54 +QTC total, 0.015 each: the miner fee share credited to whoever included the +batch. Classified, **three addresses take 76% of everything the exits pay**: + + infrastructure 3 addresses 1,647 credits 4,018.9 QTC 2.44 each + recipient 515 addresses 3,167 credits 1,233.9 QTC 0.39 each + miner fee share 21 addresses 243 credits 5.2 QTC 0.02 each + +`exit_cohorts` does that split, and `INFRASTRUCTURE_SHARE` is the threshold — +credited in more than a fifth of all batches. The line is nowhere near anything: +the real values are 91.5% on one side and single-digit percentages on the other. + +What survives is genuine and is the reason the table ships: among real +recipients, **35 addresses paid twenty or more times take two thirds of the +remaining value**, at twice everyone else's average credit. That is a +recurring-payment shape and *no more than that* — the chain records no sender, so +nothing names a payer, and a regular payee is as plausibly an exchange deposit or +one person on a schedule as a pool meeting its obligations. Say that on the page +every time the table is shown. + This page exists because the distribution table shipped an outflow column that read zero for every miner and looked like evidence. The site now has somewhere that says how much of the chain is unobservable and by which route, so that zero diff --git a/crates/blackbeard-api/src/routes.rs b/crates/blackbeard-api/src/routes.rs index aec4f99..2d63372 100644 --- a/crates/blackbeard-api/src/routes.rs +++ b/crates/blackbeard-api/src/routes.rs @@ -18,7 +18,7 @@ use axum::{Json, Router}; use blackbeard_entities::{ AccountDetail, AccountEvent, AccountFlows, AccountRoleEntry, AccountRow, ActivitySource, ApiError, BigUintDec, BlockDetail, CallIndex, CallSummary, ChainInfo, ChainRoles, ChainSeries, - ChainState, ChainSummary, DailyActivity, DailyWormhole, Distribution, EventSummary, + ChainState, ChainSummary, DailyActivity, DailyWormhole, Distribution, EventSummary, ExitCohort, GenesisDetail, LeaderboardRow, LockCounts, MinerDetail, MinerFlow, MinerId, MinerSeriesPoint, NamedAccount, NetworkSummary, NodeActivity, NodeIndex, NodeInfo, NodeRow, PalletPot, PendingTransfer, RecentBlock, ReversibleState, RewardSummary, RoleSource, RuntimeConstant, @@ -821,6 +821,19 @@ async fn wormhole( amount: BigUintDec(r.amount), }) .collect(); + let cohorts = state + .store + .exit_cohorts(&id) + .await + .map_err(database_unavailable)? + .into_iter() + .map(|c| ExitCohort { + kind: c.kind, + addresses: c.addresses, + credits: c.credits, + amount: BigUintDec(c.amount), + }) + .collect(); let days = state .store .wormhole_by_day(&id, WORMHOLE_DAYS) @@ -886,6 +899,7 @@ async fn wormhole( segments_denied: fired("SegmentsDenied"), exit_mint_failed: fired("ExitMintFailed"), routes, + cohorts, days, indexed_from: scan.map(|s| s.0), indexed_to: scan.map(|s| s.1), diff --git a/crates/blackbeard-data/src/store.rs b/crates/blackbeard-data/src/store.rs index 6d52ace..6084abc 100644 --- a/crates/blackbeard-data/src/store.rs +++ b/crates/blackbeard-data/src/store.rs @@ -85,6 +85,35 @@ pub struct TransferRoute { pub amount: String, } +/// The share of all batches above which a credited address is the protocol's +/// own plumbing rather than anybody's payee. +/// +/// An aggregator's rebate is minted on every bundle it submits, so it appears +/// in a proportion of batches no recipient ever could: measured on mainnet, one +/// address is credited in **91.5%** of every public batch submitted by anyone, +/// and in none of the private ones. A fifth is far above any plausible payee and +/// far below that, so the classification does not turn on where exactly the line +/// falls. +const INFRASTRUCTURE_SHARE: f64 = 0.20; + +/// Credits out of the wormhole, grouped by what the address appears to be. +/// +/// One row per class, in the order they should be read: the two machinery +/// classes first, because they carry three quarters of the value and a table +/// that leads with cohorts is describing aggregator plumbing and calling it an +/// economy. +#[derive(Debug, Clone, PartialEq)] +pub struct ExitCohort { + /// Which class. Stable identifiers, not display strings. + pub kind: String, + /// How many distinct addresses fall in it. + pub addresses: u64, + /// How many times they were credited. + pub credits: u64, + /// Summed amount, smallest unit. + pub amount: String, +} + /// One day of wormhole activity. #[derive(Debug, Clone, PartialEq)] pub struct DailyWormhole { @@ -2112,6 +2141,124 @@ impl Store { .collect()) } + /// Who the wormhole exits pay, once the protocol's own plumbing is set aside. + /// + /// The premise this answers is that a pool paying out must leave a pattern + /// in the batches. A pattern is there and **most of it is not payouts**, so + /// the classification is the whole point of the query rather than a + /// refinement of it: + /// + /// - `infrastructure` — credited in more than [`INFRASTRUCTURE_SHARE`] of + /// every batch anyone submitted. That is the aggregator rebate + /// `settle_exit_bundle` mints to the proof's `aggregator_address`. Three + /// such addresses take 76% of everything the exits pay. + /// - `miner_fee` — an address the chain has paid a mining reward to, + /// receiving the fee share credited to whichever block author included + /// the batch. Frequent and tiny: 0.015 QTC a time on mainnet. + /// - the four `paid_*` bands — everyone else, by how often they were paid. + /// + /// Only `verify_public_batch` is counted. A private batch is one client's + /// own exit (measured: 1.0 outputs on average, never more than two), so it + /// carries no cohort at all, and mixing the two would halve every average + /// for no gain. + /// + /// Batch *size* is deliberately not reported as a cohort. The pallet + /// documents a public batch as one segment per inner private batch and a + /// segment as one client, so a 120-output batch is an aggregator bundling + /// unrelated people — it measures aggregator throughput, and reading it as + /// one payer paying 120 payees is the mistake this query exists to avoid. + pub async fn exit_cohorts(&self, chain: &ChainId) -> Result, DataError> { + let rows = sqlx::query!( + r#" + with ev as ( + select e.height, e.extrinsic_index, + e.fields->>'to' as recipient, + (e.fields->>'amount')::numeric as amount + from chain_event e + join chain_extrinsic x + on x.chain = e.chain + and x.height = e.height + and x.extrinsic_index = e.extrinsic_index + where e.chain = $1 + and e.pallet = 'Wormhole' + and e.variant = 'NativeTransferred' + and x.pallet = 'Wormhole' + and x.call = 'verify_public_batch' + ), + batches as ( + select count(distinct (height, extrinsic_index))::numeric as n from ev + ), + per as ( + select recipient, + count(distinct (height, extrinsic_index)) as credits, + sum(amount) as amount + from ev group by 1 + ), + miners as ( + select distinct fields->>'miner' as address + from chain_event + where chain = $1 + and pallet = 'MiningRewards' + and variant = 'MinerRewarded' + ), + classed as ( + select p.credits, p.amount, + case + when (select n from batches) > 0 + and p.credits::float8 / (select n from batches)::float8 > $2 + then 'infrastructure' + when p.recipient in (select address from miners) + then 'miner_fee' + when p.credits = 1 then 'paid_once' + when p.credits between 2 and 4 then 'paid_a_few' + when p.credits between 5 and 19 then 'paid_often' + else 'paid_regularly' + end as kind + from per p + ) + select kind as "kind!", + count(*) as "addresses!", + sum(credits)::bigint as "credits!", + coalesce(sum(amount), 0) as "amount!" + from classed + group by 1 + "#, + chain.as_str(), + INFRASTRUCTURE_SHARE, + ) + .fetch_all(&self.pool) + .await?; + + // Ordered here rather than in SQL: the reading order is editorial — the + // machinery first, because it carries three quarters of the value and a + // table that opens with cohorts invites the wrong conclusion — and a + // `case` in an `order by` would hide that decision in the query. + const ORDER: [&str; 6] = [ + "infrastructure", + "miner_fee", + "paid_once", + "paid_a_few", + "paid_often", + "paid_regularly", + ]; + let mut cohorts: Vec = rows + .into_iter() + .map(|r| ExitCohort { + kind: r.kind, + addresses: r.addresses as u64, + credits: r.credits as u64, + amount: r.amount.to_string(), + }) + .collect(); + cohorts.sort_by_key(|c| { + ORDER + .iter() + .position(|k| *k == c.kind) + .unwrap_or(usize::MAX) + }); + Ok(cohorts) + } + /// Notes created per day, from the `leaf_index` the transfers carry. /// /// Derived from transfers rather than from `ZkTree::LeafInserted`, which is diff --git a/crates/blackbeard-entities/src/chain.rs b/crates/blackbeard-entities/src/chain.rs index f799f0e..f4df494 100644 --- a/crates/blackbeard-entities/src/chain.rs +++ b/crates/blackbeard-entities/src/chain.rs @@ -526,6 +526,8 @@ pub struct WormholeSummary { pub exit_mint_failed: u64, /// Every transfer route, busiest first. pub routes: Vec, + /// Who the exits pay, grouped by what each address appears to be. + pub cohorts: Vec, /// Notes created per day, oldest first. pub days: Vec, /// Lowest block whose events this observer has read. @@ -536,6 +538,47 @@ pub struct WormholeSummary { pub indexed_to: Option, } +/// Credits out of the wormhole, grouped by what the address appears to be. +/// +/// This exists because the obvious reading of exit batches is wrong. Grouping +/// them by producing extrinsic gives a shape that looks exactly like payroll — +/// one sender, many recipients, repeating — and almost all of it is the +/// protocol's own plumbing: +/// +/// - an **aggregator's** rebate is minted on every bundle it submits, so one +/// address is credited in 91.5% of every public batch anyone sent, and three +/// such addresses take **76% of everything the exits pay**; +/// - a **miner's** fee share goes to whichever block author included the batch, +/// which is frequent and tiny — 0.015 QTC a time. +/// +/// Batch size is not reported at all, for the same reason: the pallet documents +/// a public batch as one segment per inner private batch and a segment as one +/// client, so a 120-output batch is an aggregator bundling unrelated people. +/// It measures aggregator throughput, not a payer's generosity. +/// +/// What survives the subtraction is real: among genuine recipients, 35 +/// addresses paid twenty or more times take two thirds of the remaining value. +/// That is a recurring-payment shape. It is **consistent with** pool payouts and +/// is not proof of them — the chain records no sender, so nobody can be named, +/// and a regular payee could equally be an exchange deposit or one person moving +/// their own funds on a schedule. +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[ts(export, export_to = "ExitCohort.ts")] +pub struct ExitCohort { + /// Stable identifier for the class: `infrastructure`, `miner_fee`, or one + /// of `paid_once`, `paid_a_few`, `paid_often`, `paid_regularly`. The label + /// is the frontend's to choose; this is not a display string. + pub kind: String, + /// Distinct addresses in the class. + #[ts(type = "number")] + pub addresses: u64, + /// How many times they were credited. + #[ts(type = "number")] + pub credits: u64, + /// Summed amount, smallest unit. + pub amount: BigUintDec, +} + /// Transfers sharing a production route and a sender visibility. #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export, export_to = "TransferRoute.ts")] diff --git a/crates/blackbeard-entities/src/lib.rs b/crates/blackbeard-entities/src/lib.rs index 6248d0f..b13af62 100644 --- a/crates/blackbeard-entities/src/lib.rs +++ b/crates/blackbeard-entities/src/lib.rs @@ -33,8 +33,8 @@ pub use block::{BlockDetail, BlockEvent, BlockExtrinsic, BlockObservation, Recen pub use call::{CallIndex, CallSummary, EventSummary}; pub use chain::{ AccountFlows, ChainId, ChainInfo, ChainStatus, ChainSummary, ClientVersion, DailyActivity, - DailyWormhole, Distribution, LockCounts, MinerFlow, NetworkSummary, PalletPot, Tracking, - TransferRoute, WormholeSummary, + DailyWormhole, Distribution, ExitCohort, LockCounts, MinerFlow, NetworkSummary, PalletPot, + Tracking, TransferRoute, WormholeSummary, }; pub use error::{ApiError, EntityError}; pub use miner::{ diff --git a/web/src/api/generated/ExitCohort.ts b/web/src/api/generated/ExitCohort.ts new file mode 100644 index 0000000..9d63cee --- /dev/null +++ b/web/src/api/generated/ExitCohort.ts @@ -0,0 +1,48 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { BigUintDec } from "./BigUintDec"; + +/** + * Credits out of the wormhole, grouped by what the address appears to be. + * + * This exists because the obvious reading of exit batches is wrong. Grouping + * them by producing extrinsic gives a shape that looks exactly like payroll — + * one sender, many recipients, repeating — and almost all of it is the + * protocol's own plumbing: + * + * - an **aggregator's** rebate is minted on every bundle it submits, so one + * address is credited in 91.5% of every public batch anyone sent, and three + * such addresses take **76% of everything the exits pay**; + * - a **miner's** fee share goes to whichever block author included the batch, + * which is frequent and tiny — 0.015 QTC a time. + * + * Batch size is not reported at all, for the same reason: the pallet documents + * a public batch as one segment per inner private batch and a segment as one + * client, so a 120-output batch is an aggregator bundling unrelated people. + * It measures aggregator throughput, not a payer's generosity. + * + * What survives the subtraction is real: among genuine recipients, 35 + * addresses paid twenty or more times take two thirds of the remaining value. + * That is a recurring-payment shape. It is **consistent with** pool payouts and + * is not proof of them — the chain records no sender, so nobody can be named, + * and a regular payee could equally be an exchange deposit or one person moving + * their own funds on a schedule. + */ +export type ExitCohort = { +/** + * Stable identifier for the class: `infrastructure`, `miner_fee`, or one + * of `paid_once`, `paid_a_few`, `paid_often`, `paid_regularly`. The label + * is the frontend's to choose; this is not a display string. + */ +kind: string, +/** + * Distinct addresses in the class. + */ +addresses: number, +/** + * How many times they were credited. + */ +credits: number, +/** + * Summed amount, smallest unit. + */ +amount: BigUintDec, }; diff --git a/web/src/api/generated/WormholeSummary.ts b/web/src/api/generated/WormholeSummary.ts index 0d73b44..58c3604 100644 --- a/web/src/api/generated/WormholeSummary.ts +++ b/web/src/api/generated/WormholeSummary.ts @@ -1,6 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ChainId } from "./ChainId"; import type { DailyWormhole } from "./DailyWormhole"; +import type { ExitCohort } from "./ExitCohort"; import type { TransferRoute } from "./TransferRoute"; /** @@ -57,6 +58,10 @@ exit_mint_failed: number, * Every transfer route, busiest first. */ routes: Array, +/** + * Who the exits pay, grouped by what each address appears to be. + */ +cohorts: Array, /** * Notes created per day, oldest first. */ diff --git a/web/src/components/WormholePanel.tsx b/web/src/components/WormholePanel.tsx index a9d1721..d908c10 100644 --- a/web/src/components/WormholePanel.tsx +++ b/web/src/components/WormholePanel.tsx @@ -52,6 +52,36 @@ function routeMeaning(produced_by: string, hidden: boolean): string { return hidden ? 'no sender recorded' : 'an ordinary transfer, sender recorded' } +/** + * What to call each cohort class, and what it means. + * + * The identifiers come from the API and are stable; the wording is this + * page's. Kept as one table so the two machinery classes cannot drift apart + * from the explanation that stops them being read as payouts. + */ +const COHORTS: Record = { + infrastructure: { + label: 'Aggregator rebates', + meaning: 'credited in more than a fifth of every batch anyone submitted', + }, + miner_fee: { + label: 'Miner fee shares', + meaning: 'paid to whichever block author included the batch', + }, + paid_once: { label: 'Paid once', meaning: 'a single credit, ever' }, + paid_a_few: { label: 'Paid 2–4 times', meaning: '' }, + paid_often: { label: 'Paid 5–19 times', meaning: '' }, + paid_regularly: { + label: 'Paid 20+ times', + meaning: 'a recurring payee — the closest thing to a payroll the chain shows', + }, +} + +/** Whether a class is the protocol's own plumbing rather than somebody's payee. */ +function isMachinery(kind: string): boolean { + return kind === 'infrastructure' || kind === 'miner_fee' +} + export function WormholePanel({ chain, decimals, @@ -92,6 +122,41 @@ export function WormholePanel({ .filter((r) => !r.sender_hidden) .reduce((sum, r) => sum + r.transfers, 0) + // What share of everything the exits pay is the protocol paying itself. The + // headline of the cohort table, because without it the bands below read as a + // pool ledger. BigInt throughout: these are token amounts, and a Number would + // lose precision well before the total does. + const machineryShare = (() => { + let total = 0n + let machinery = 0n + for (const c of data.cohorts) { + let amount: bigint + try { + amount = BigInt(c.amount) + } catch { + return null + } + total += amount + if (isMachinery(c.kind)) machinery += amount + } + if (total === 0n) return null + return Number((machinery * 1000n) / total) / 10 + })() + + // Mean credit, computed from the summed amount rather than sent per-row: the + // difference between the machinery's average and a payee's is most of what + // separates them, and rounding it away would flatten the table. + const each = (amount: string, credits: number): string => { + if (credits === 0) return '—' + let total: bigint + try { + total = BigInt(amount) + } catch { + return '—' + } + return tokens((total / BigInt(credits)).toString(), decimals, 3) + } + return ( <>
@@ -212,6 +277,79 @@ export function WormholePanel({ behaving correctly rather than the address behaving suspiciously.

+ +
+
+

Who the exits pay

+ + public batches only · amounts in {symbol} + {machineryShare !== null && ` · ${machineryShare}% is plumbing`} + +
+
+ + + + + + + + + + + + + {data.cohorts.map((c) => { + const meta = COHORTS[c.kind] + return ( + + + + + + + + ) + })} + +
+ Credits out of the wormhole, grouped by what each address appears to be +
+ Who + AddressesCreditsAmountEach
+
+ {meta?.label ?? c.kind} + {meta?.meaning ? {meta.meaning} : null} +
+
{fmtHeight(c.addresses)}{fmtHeight(c.credits)}{tokens(c.amount, decimals, 1)}{each(c.amount, c.credits)}
+
+

+ + Most of what leaves the wormhole is the protocol paying itself + {machineryShare !== null && `, ${machineryShare}% of it`}. + {' '} + An aggregator's rebate is minted on every bundle it submits, so a handful of + addresses appear in almost every batch anyone sent — one of them in 91.5% of them, and in + none of the private ones. A miner's fee share goes to whoever included the batch, + which is why a mining address can be credited hundreds of times for a few hundredths of a{' '} + {symbol} each. Neither is anybody's payout. +

+

+ Batch size is deliberately not on this page. A public batch carries one segment + per inner private batch and a segment is one client, so a 120-output batch is an + aggregator bundling unrelated people — it measures aggregator throughput, and reading it + as one payer paying a hundred payees is the mistake this table exists to prevent. +

+

+ What survives the subtraction is the bottom rows, and they are the real finding: a small + number of addresses paid over and over, taking most of the remaining value at a larger + amount each time.{' '} + That is a recurring-payment shape, and no more than that. The chain + records no sender, so nothing here names a payer — and a regular payee could as easily be + an exchange deposit, or one person moving their own funds on a schedule, as a pool meeting + its obligations. +

+
) }