# $205K in Revenue, 19% Real Money — Payment-Graph Forensics on Arc

> Published: 2026-10-23 | Author: AgentBadge Team | Canonical: https://agentbadge.xyz/blog/arc-c32-payment-forensics

Assay — a credit bureau for x402 payments — published its Celo findings: $205K of gross agent revenue, and only 19% of it was real, outside money. The rest was loops: sellers paying themselves through chains of fresh wallets, and payers whose first funding traced straight back to the seller they were paying. Nine loops, the largest spanning 378 wallets; 96K payers funded by the very sellers they paid ([Assay, MIT-licensed](https://github.com/Oshioke-Salaki/assay)). Those are their numbers on Celo, not ours on Arc — but the wash-trading pattern does not care which chain it runs on. So we built the same kind of payment-graph index for Arc and shipped a public verdict endpoint: GET /api/v1/risk/:address returns pass, caution, block, or no_data for any address, with the organic share, the reasons, and the index freshness — no signup, 60 requests a minute. Here is how the index works and what we found on Arc mainnet so far.

**TL;DR for agents:** payment-graph forensics treats every EIP-3009 settle as an edge payer → seller, then separates organic revenue from circular flow using strongly connected components (Tarjan SCC), first-funder tracing, and shared-funder analysis. To check any Arc address: curl https://agentbadge.xyz/api/v1/risk/0x<address> → {verdict, organicShare, coveredPct, indexedToBlock, lagBlocks, reasons[]}.

![A directed payment graph on dark slate; one tight circular loop of nodes glows amber while organic star-bursts around it stay cyan](/images/blog/arc-c32-payment-forensics-hero.webp)

## What is payment-graph forensics?

Payment-graph forensics reconstructs who actually paid whom on a chain, then asks whether the money is new. Every x402 settle on Arc is a USDC transferWithAuthorization (EIP-3009): the payer signs, someone else submits. On Arc the submitter is almost never the payer — the facilitator pattern is the default — so tx.from is useless for attribution. What counts is the signed authorization: our indexer extracts payer and seller from the settle event itself and stores the directed edge. A wallet balance can lie about nothing here; the graph is the evidence.

Two kinds of fake revenue fall out of that graph immediately. A **loop** is a cycle — A pays B, B pays C, C pays A — which inflates everyone's gross volume with the same USDC going in circles. A **self-funded payer** is a fresh wallet whose first inbound funding came from the seller it then "pays" — manufactured customers. Organic revenue is what remains: money that entered the graph from outside it.

## How do you find circular revenue in a payment graph?

You run three passes over the edge set and let arithmetic do the accusing. **First, Tarjan's algorithm** finds strongly connected components — groups of addresses where money can travel from any member back to itself. Every SCC bigger than one node is a loop by definition. **Second, first-funder tracing:** for each payer, we record the earliest funding event that seeded the wallet; if the seller funded its own "customer," that edge is circular, not organic. **Third, shared-funder analysis:** when one address seeds a cluster of payers that all pay the same seller, the whole cluster is a strike — one operator renting wallets by the dozen.

A subtlety that matters on Arc: custody providers are legitimate funders. A payer whose wallet was created and funded through Crossmint's embedded-wallet rails is not self-funded — it is a real customer arriving through a custody front door. Our funding pass keeps a public-source registry (FORENSICS_PUBLIC_SOURCES) for known custodians and faucets; funding from a registered public source counts as organic, not as a strike. Without that classification every Crossmint-onboarded agent would look like a fake payer — Crossmint is an Arc Day One partner shipping embedded wallets, treasury, and agentic payment APIs, so this is not an edge case.

![A knotted amber cycle cluster highlighted inside a larger sparse cyan graph, with a magnifier lens ring around it](/images/blog/arc-c32-payment-forensics-2.webp)

## What does the risk API actually return?

A verdict — not a rating, not a score you can shop against. GET /api/v1/risk/:address runs the graph analysis over the indexed edges and returns one of four outcomes with the evidence attached. This is a real response from Arc mainnet (block 27,434,627, fetched 2026-10-23):

```
GET /api/v1/risk/0x998d7c178a1f9607b47ea3a8e22426451b3ce707

{"verdict":"pass","organicShare":1,"grossVolume":"10274939098",
 "organicVolume":"10274939098","effectivePayers":2.7948377777149314,
 "coveredPct":0,"reasons":["funding coverage 0% — verdict uses on-chain data only"],
 "indexedToBlock":"27434627","lagBlocks":"2"}
```

That seller has 15 settles from 14 distinct payers, $10,274.94 in gross USDC volume, and not a single circular edge in the index — pass, organic share 1.0. effectivePayers (≈2.8) discounts the payer count by how concentrated the volume is: 14 names, but the money really came from about three. coveredPct: 0 is the honest part — our funding pass had not traced these payers' first-funder events yet, so the verdict says so in reasons instead of pretending coverage it does not have.

The four verdicts: **pass** — organic share ≥ threshold with no loop or self-funding hits; **caution** — organic share degraded or shared-funder strikes; **block** — loops detected or dominant self-funding; **no_data** — the file is thin (fewer than 5 settles, fewer than 2 payers, or gross under $0.05) or the address is unknown. And no_data is the common case on a young network. A seller with $10,274.94 gross from exactly one payer? no_data — one wallet is not a customer base, whatever it paid. A seller with 197 settles totaling $1.28? no_data — dust is not revenue. Both are verbatim responses from the live endpoint today.

## What did we find on Arc mainnet?

An honest baseline, mostly. As of block ~27,434,686 on 2026-10-23 the index holds **301 settles between real mainnet addresses — 29 sellers, 37 payers — plus 12 traced funding events**, with the live tail 2–5 blocks behind tip. We ran Tarjan over the whole edge set: **zero loops**, zero self-funded payers on traced funding, and organic share ≈100% of covered volume. Arc's agent economy is young enough that nobody has bothered to wash-trade it yet — or they have not settled through the rails we index. Either way, the baseline is now measured, public, and updating every ~15 seconds. When a loop appears, this is where it surfaces first.

The no_data responses tell their own story about the network: most sellers still see one or two payers, often the operator testing their own endpoint. That is exactly what a healthy early graph looks like — sparse, honest, and not yet worth faking.

![A verdict card showing four stamps — pass green, caution amber, block red, no_data grey — with small freshness fields below](/images/blog/arc-c32-payment-forensics-3.webp)

![Diagram: the forensics pipeline — settle events → indexer → edge/funding stores → SCC + first-funder → organic share → verdict → risk API consumers](/images/blog/arc-c32-payment-forensics-d1.webp)

*The pipeline in one picture: signed settles become edges; edges become a graph; the graph becomes a verdict with its freshness disclosed.*

## Why does no_data matter more than a confident answer?

Because a risk check that always has an opinion is worse than none. The thin-file rules are mechanical: under five settles, under two payers, or under $0.05 gross — no_data, full stop. A brand-new seller is not suspicious; it is unmeasured. The distinction matters for policy: a buyer agent can treat no_data as "proceed at your own cap" while treating block as "do not sign" — conflating them either locks out every new merchant or waves through wash loops.

Freshness is disclosed the same way: indexedToBlock tells you the last indexed block, lagBlocks tells you how far behind the tip that is. A verdict computed over stale data says so. coveredPct does the same for funding coverage — 0% coverage means "we have not traced first-funders here" and the response prints that in reasons. Every field exists so a machine can decide how much weight to give the answer.

![A thin-file gauge on slate-900; the needle sits parked in a grey NO_DATA zone; a tiny dataset card feeds it](/images/blog/arc-c32-payment-forensics-4.webp)

## Where does the verdict plug in?

Three seams already consume it. **The buyer side:** @agentbadge/circle-payments ships createRiskGuard — a beforePayment hook on createPayingClient that checks every payTo offered in the 402 challenge *before* reservation or signing, and the CLI gets it with one flag (--risk-check). A block verdict becomes a PaymentRefusedError; no_data policy is caller-configurable. **The reputation side:** our internal reputation work feeds organic share in as a weighting input — manufactured volume stops counting toward reputation. **The policy side:** smart-wallet platforms that keep approved-destination lists — the exact control Crossmint ships for its agentic wallets — can drive that list straight off this endpoint: pass/no_data destinations allowed, block rejected at the wallet layer, not after the fact.

And one advantage we have over a third-party indexer: our own facilitator seam sees its settles directly at verify time, so our own payment history does not depend on log indexing catching up.

## How do you check an address yourself?

One curl, no auth:

```
curl https://agentbadge.xyz/api/v1/risk/0x998d7c178a1f9607b47ea3a8e22426451b3ce707
```

The endpoint is rate-limited to 60 requests/minute per IP, documented in the [OpenAPI spec](https://agentbadge.xyz/api/openapi.yaml), and returns 400 on malformed addresses. If you are building a buyer agent, you do not need the curl — pass createRiskGuard() into createPayingClient and the check happens before every signature. If you are building a marketplace or a venue, the same verdict feeds your listing policy: a seller's revenue graph is public evidence, not our private opinion.

*This is C32 in the Arc Campaign series. Previously: [Never Charged for a No — a machine-checkable refusal contract for paid agent APIs](https://agentbadge.xyz/blog/arc-c17-honest-refusal). Next: the buyer-side guard — createRiskGuard and --risk-check in @agentbadge/circle-payments.*

**Links:** [Risk API — OpenAPI](https://agentbadge.xyz/api/openapi.yaml) · [Service catalog](https://agentbadge.xyz/api/v1/services) · [Crossmint × Arc partner spotlight](https://community.arc.io/home/blogs/how-crossmint-makes-stablecoin-apps-and-the-agentic-economy-easier-to-build-on-arc-2026-10-20) · [Assay — x402 credit bureau](https://github.com/Oshioke-Salaki/assay) · [AgentBadge](https://agentbadge.xyz)

*Don't certify. Measure.*

---

## For AI Agents

- Companion guide: https://agentbadge.xyz/agent-guide/articles/arc-c32-payment-forensics
- Knowledge Index: https://agentbadge.xyz/agent-guide/
- LLM entry point: https://agentbadge.xyz/llms.txt
- Engineering services: https://agentbadge.xyz/agent-guide/team/services
