Shared funders
Several wallets tracing back to one funder looks like one operator. It is
evidence only when the funder is narrow. An exchange hot wallet or a payout
service funds unrelated wallets all day, so ancestry through it links nothing.
find_funding_source and find_funding_sources stop at a funder that pays
many distinct addresses, and find_funding_sources reports each shared funder
with its fan-out and flow shape.
Breadth
Section titled “Breadth”get_address_fanout counts the distinct addresses an address pays
(recipient_count) and is paid by (sender_count) over its recent activity,
and classifies the total, counterparty_count:
classification |
Reading |
|---|---|
hub |
An exchange hot wallet, bridge or faucet-scale distributor. Two addresses sharing it as a funder are not linked by it. |
distributor |
Pays widely. Shared funding through it is weak on its own; compare the rate against a control group from sample_control_addresses. |
narrow |
Several targets funded from here is meaningful and worth following. |
Both directions count. An exchange cold wallet receives from thousands of addresses and sends to few, and a count of recipients alone would read it as narrow.
A scan that stopped at max_transactions before the end of the address’s
history reports truncated: true. Its count is a lower bound, so a narrow
or distributor reading from it carries classification_provisional: true.
A hub reading is never provisional: what was seen already proves it.
Every sampled transaction’s balance changes are read to the end, including
payments with more recipients than fit on one page. If a continuation cannot
be read, get_address_fanout returns an incomplete-measurement error rather
than counts, a classification, flow shape or sponsor shape. No partial
measurement is cached. Retry the call once the read succeeds; increasing
max_transactions does not resolve missing rows within a transaction.
When fan-out is optional context beside a completed funding walk, a failed
measurement does not discard the walk. find_funding_source reports
origin_fanout_unread; find_funding_sources reports fanout_unread on the
affected shared funder. Both include the reason and omit the unread measurement.
Breadth alone does not separate an exchange from a sybil funder, since the two
can have near-identical counterparty counts. out_in_ratio is recipients over
senders, and flow_shape reads it:
flow_shape |
Meaning |
|---|---|
disperser |
Pays many addresses and is paid by few, like a funder or a payout wallet. |
collector |
Is paid by many addresses and pays few, like a sweep or deposit target. |
balanced |
Deposits in and withdrawals out, the way an exchange runs. |
unknown |
No senders were seen, so there is no ratio. |
A disperser paid by few can still be one operator’s payout wallet, so a
distributor with that shape needs the control group before any conclusion.
get_address_fanout also reports whether an address pays other people’s gas;
see Gas sponsors.
Related
Section titled “Related”- WAL claim farm example: a shared funder tested against a control group.
- Forensics skill: the base-rate check that keeps shared ancestry from reading as collusion.
Building shared-control edges
Section titled “Building shared-control edges”build_wallet_edges asks whether a fresh address reached by a trace is a new
party or another wallet of the same operator. It builds signals live, without
an analytics warehouse, from these sources:
- Multisig co-signature: a committee key can spend the wallet whose address the committee hashes to. This is the non-behavioral signal.
- A shared first funder.
- One address first-funding another.
- Value moving both ways between two non-service addresses.
- A shared gas sponsor.
- Co-appearance in one transaction.
Every intermediary is measured before use. An exchange or sponsorship relayer
is discarded rather than linking unrelated users. The role_split exception
applies when most other wallets served by a sponsor were first funded by the
seeds’ own narrow, unlabelled funder: one operator may fund and sponsor from
different addresses.
edges are facts with transaction digests to check; co_signer instead cites
the committee’s address hash. clusters are inferences with their own
evidence_tier, never proof of ownership.
Supply all suspected wallets as seeds, up to 25; links among them are exactly
verified. expand defaults to true, admitting unknown siblings only after
checking their own first funder. The expansion budget defaults to 25 candidates,
and unverified candidates are reported rather than hidden.
popularity_limit defaults to 50 distinct counterparties. Funders’ recipients
count only if paid at least 0.01 SUI or $0.10. Raise this limit only with cause:
otherwise a service can link thousands of unrelated wallets. Reciprocal
counterparties are also checked for popularity before that signal is trusted,
up to reciprocal_budget (15 by default).
min_signal_types: 2 is the stricter batch-pipeline rule, improving precision
but missing ordinary personal alt-wallets that share one mechanism; the
default is 1. max_cluster_size rejects merges above 100 by default.
query_budget caps GraphQL requests at 150 by default; inspect truncated.
Mermaid output has one box per cluster and signal-labelled edges, with
unclustered pairs dashed. graph_json returns nodes and edges; CSV gives one
row per edge. JSON is the default.
Following a funding chain
Section titled “Following a funding chain”find_funding_source starts at a wallet’s first funding transaction and its
sender, then follows the funder’s own funding, up to five hops by default
(maximum 12). It can establish a first funding source such as a Binance
withdrawal; that is narrower than attributing every later payment.
The walk stops at a labelled exchange, bridge or known wallet (managed through
manage_labels), a repeated wallet, a dead end, or a funder that paid more
than 50 distinct recipients at least 0.01 SUI or $0.10 each. Dust-only recipients
do not count. This is the same service threshold used by build_wallet_edges;
ancestry beyond a service does not attribute its users.
It also stops when a funder’s payment occurred after that funder’s own earliest 12 transactions. An established wallet can pay from a long-held balance, as when a victim transfers to a thief; that wallet’s original funding does not explain this payment.
Every hop reports funder popularity. dust_skipped lists ignored inflows;
sponsored_by lists gas payers for the hop’s own transactions even if no
funding was found. Address-balance gas can support a wallet with no SUI inflow,
and a poisoning lookalike’s operator may appear only in sponsorship.
measure_fanout defaults to true and uses get_address_fanout’s default window,
so the counts and truncation status agree.
Comparing many funding chains
Section titled “Comparing many funding chains”find_funding_sources shares reads when chains converge, so it is cheaper
than a separate single-address call for each subject. It accepts 1–100
addresses. Full depth is the default, up to max_hops (five by default);
depth: "first_hop" stops after one hop. That first hop is often the useful
one, since deeper chains tend to end at early distribution wallets.
Like the single walk, it stops at funders that paid more than 50 distinct addresses. A shared-funder count stops at the first funder that is itself a subject. Each common funder has fan-out and flow shape to distinguish a narrow origin from an exchange many unrelated users withdrew from.
Co-funding is measured against each transaction’s total recipient count.
Two subjects among two recipients suggests a bespoke payment; two among
twenty can occur in an unrelated service batch. The tool reports subjects
directly funding other subjects, and later payments one subject signed to
another under subject_paid_subject, checked pairwise for up to 20 subjects.
Fundings within a minute form timing clusters, a lead for scripted setup
rather than proof. Run the same read on a sample_control_addresses control
group before interpreting rates.
sample_control_addresses draws that group from the addresses that used the
same protocol in the same window. Its module and event_type filters follow
the event rules in
Querying events across transactions,
including the network’s relocate_event_module cutover.
The summary retains each result’s origin, first funder and first hop and
counts dust_skipped. Results and later subject payments share a display
budget, prioritizing every result tied to a shared funder, subject link,
co-funding, burst or payment. detail: "full" returns every hop under
results[].chain, every dust row and every list row.