--- eip: 8458 title: Agent Income Statements description: Public statements of what an agent wallet received in one token, one leaf per transfer, chained and rebuildable from the token logs author: Priors contributors (@priors-agents) discussions-to: https://ethereum-magicians.org/t/erc-8458-agent-income-statements/30047 status: Draft type: Standards Track category: ERC created: 2026-10-20 requires: 20, 165, 721, 8004 --- ## Abstract This proposal defines public income statements for agents of [ERC-8004](./eip-8004.md), carried on its Reputation Registry. A statement commits, with a Merkle root, to the incoming transfers of one [ERC-20](./eip-20.md) token to the agent's ERC-8004 `agentWallet` over a window of log positions, with each transfer as its own leaf. One writer's statements about one agent are chained by hash over back-to-back windows, and the agent's owner can name, in the agent's ERC-8004 metadata, the writer readers read first. Anyone can rebuild every leaf and total from the token's own logs and the identity registry's, and an optional verifier contract checks one transfer against a statement's root on chain. Statements are ordinary `giveFeedback` entries, with `tag1` set to `revenues`, the tag ERC-8004 gives as its example of cumulative revenues, and `tag2` set to `x402.statement`. An entry's value is the cumulative total through the end of its window, and its file, carried in the entry itself, is the evidence. x402 payments, which settle as a token transfer to the address an agent names as its `payTo`, are the motivating case. A token log cannot show how a transfer was settled, so a statement counts every incoming transfer except mints, transfers of nothing and the agent's own, and calls the total income, not x402 revenue. ## Motivation An agent paid over x402, an HTTP payment protocol, receives each payment as a token transfer on chain. Its income is therefore public in principle. In practice a lender, a marketplace or an explorer that wants an agent's income has two options: trust a figure someone else computed, or scan the token's logs itself. ERC-8004 already suggests the first: a `revenues` entry carries one cumulative number. Nothing ties that number to the transfers, says over which period they arrived, or lets a second reader check it without redoing the scan. ERC-8004's feedback file can also carry a `proofOfPayment`, but that names one payment per entry. Income is also what lenders size credit on. An agent that earns steadily can carry a larger line, but only if the lender can check the earnings cheaply, without trusting the agent or whoever reports them. Aggregate figures computed by an issuer under an algorithm it pins answer part of that question. They do not let a reader see the transfers, recount them, or prove one of them to a contract. Goals: 1. **Every transfer, in public.** Each incoming transfer is its own leaf, named by its log, so anyone can list, recount and weigh them. 2. **Checkable from the chain alone.** Every leaf, total and root can be rebuilt from the token's logs and the identity registry's. A reader that rebuilds needs no trust in the writer. 3. **Nobody else's income.** Only transfers to the address the agent declared count, and declaring it takes that address's own signature, so a writer cannot credit an agent with another address's income. 4. **Periods that add up.** Windows are back to back and chained, so a reader that checked an agent's statements once only checks the new windows afterwards. 5. **Provable on chain, one transfer at a time.** A contract can check that a transfer is in a statement with a Merkle proof. 6. **No new core contract.** It works on every chain where the ERC-8004 registries are deployed. This profile shares its encoding, its chained windows and the shape of its reader rules with a credit history profile of the same registry, which records loan outcomes. It repeats here what it needs, so that it stands alone. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. ### Terms - **Agent:** an identity of an ERC-8004 Identity Registry, which is an [ERC-721](./eip-721.md) contract, named by its `agentId`. - **Owner:** the identity registry's `ownerOf(agentId)`. - **`payTo`:** the agent's ERC-8004 `agentWallet`, the address where it is paid. - **Writer:** the address that posts statements (the entry's `clientAddress`). - **Asset:** the ERC-20 token a statement counts. - **Position:** a log's block number and its index in the block. Positions are ordered by block, then by log index. - **Change log:** an ERC-721 `Transfer` log of the agent, or a `MetadataSet` log of the agent for the key `agentWallet`, both emitted by the identity registry. - **Payment:** a transfer of the asset into `payTo` that a statement counts, as defined below. The name follows the motivating case; a log does not show whether the transfer settled an x402 payment. - **Income:** the sum of the payments of a window, or of a run of windows. It is gross receipts at one address in one asset. It is not profit, and it is not revenue in an accounting sense. - **Leaf:** one payment. - **Statement:** the leaves of one agent in one asset over a window, posted by one writer. - **(writer, agent) chain:** one writer's statements about one agent, linked by `seq` and `prev`. - **Named writer:** the writer the agent's owner names under the metadata key `incomeWriter` (see Named writer). ### Entries A writer posts each statement with `giveFeedback(agentId, value, valueDecimals, tag1, tag2, endpoint, feedbackURI, feedbackHash)`, and readers find it through ERC-8004's `NewFeedback` event: | tag1 | tag2 | value | when | | --- | --- | --- | --- | | `revenues` | `x402.statement` | the agent's income from `since` through the window's last position, in the asset's base units | periodically, per agent | - `valueDecimals` MUST equal the asset's decimals, which MUST be at most 18, the bound ERC-8004 sets on `valueDecimals`. - `value` MUST NOT exceed 2^127 - 1, as it is an `int128`. - `endpoint` SHOULD be empty. - `feedbackURI` MUST be exactly `data:application/json;base64,` followed by the base64 of the file's bytes (RFC 4648 standard alphabet, padded, no other character), so the entry carries its own evidence and outlives any server. `feedbackHash` MUST be `keccak256` of those bytes. - The file's bytes MUST be its canonical encoding (see Encoding). - A writer MUST NOT revoke a statement. - A writer SHOULD be an address used only for these entries. The registry already refuses entries from the agent's owner and its approved operators. - A statement is identified by its entry, `(agentId, clientAddress, feedbackIndex)` in its Reputation Registry, and its bytes by its `feedbackHash`. Other documents MAY reference a statement by both. ### Windows A window runs from the position (`fromBlock`, `fromLog`) to the position (`toBlock`, `toLog`), both included. `toLog` is `null` when the window runs to the end of `toBlock`. Windows are cut at the agent's change logs, so that every transfer is counted under the owner and wallet in force at its own log: - A window MUST start either at log 0 of `fromBlock`, when that log is not one of the agent's change logs, or at the agent's last change log in `fromBlock`. - A window MUST end at the end of `toBlock`, or at the position just before one of the agent's change logs. - A window MUST NOT contain one of the agent's change logs after its first position. - The statement's `owner` and `payTo` MUST be the registry's `ownerOf(agentId)` and `getAgentWallet(agentId)` at the end of `fromBlock` when the window starts at a change log, and at the end of the block before `fromBlock` otherwise. `payTo` is `null` when that wallet is the zero address. `owner` MUST NOT be the zero address: the agent exists. Since no change log lies inside a window, its owner and wallet hold at every position of it. A transfer in the block of a change, but at a log before it, belongs to the window before the change. When a block holds several of the agent's change logs, the window before them ends just before the first and the next starts at the last, so transfers between them are in no window. A transfer of the agent under ERC-8004 is such a case: the registry clears the wallet and emits the ERC-721 `Transfer`. ERC-8004 emits `MetadataSet` when metadata is set, and names it for `agentWallet` at registration. Its text does not name the event for `setAgentWallet`, `unsetAgentWallet` or the clearing of the wallet on a transfer; its reference registry emits `MetadataSet(agentId, "agentWallet", ...)` in all three cases. This profile applies only to identity registries that emit it for every change of the wallet. A writer MUST NOT write statements about an agent of a registry that does not. Agent identity documents that rebuild an address's intervals from the same logs depend on the same event, and an ERC-8004 text that required it for every wallet change would serve both. ### Payments The agent declares where it is paid by setting its ERC-8004 `agentWallet`, which takes that wallet's own signature; the registry clears it when the agent is transferred. An agent that wants its income counted SHOULD use its `agentWallet` as the `payTo` of its x402 payment requirements. Other transfers into that address count as well (see Security Considerations). - The asset MUST be an ERC-20 token on the identity registry's chain. - A **payment** is a log of the asset, at a position inside the window, with the signature `Transfer(address indexed from, address indexed to, uint256 value)` and three topics, whose `to` is `payTo`, whose `value` is not zero, and whose `from` is none of the zero address, `payTo` and `owner`. - A window whose `payTo` is `null` has no payment. These rules leave out four transfers that are never income from someone else: a mint, a transfer of nothing, a transfer from `payTo` to itself and a transfer from the owner. Every other transfer in counts, whoever sent it: deciding which payers are customers is the reader's job (see Readers). ### Encoding A file is UTF-8 JSON in the canonical form of RFC 8785 (JCS): object keys sorted by UTF-16 code units, no whitespace, strings escaped as JCS does. One file has one byte string, so every reader gives it the same meaning. - Every JSON number in a file MUST be an integer from 0 to 2^53 - 1. The agent id and amounts (`value` and every `amount`) are decimal strings without leading zeros, because they are `uint256` (`value` is `int128`) and can pass 2^53. ERC-8004's example file writes `agentId` and `value` as JSON numbers; this profile writes them as strings. - Addresses are [ERC-55](./eip-55.md) checksummed. `agentRegistry` and `clientAddress` are CAIP-10 account ids (`eip155::
`) on the identity registry's chain. Hashes are lowercase `0x`-prefixed hex. `createdAt` is UTC to the second (`2026-10-20T09:00:00Z`). - A file has exactly the fields this document lists. None is optional. - A file MUST NOT exceed 8,192 bytes, and MUST NOT hold more than 16 anchors. A reader MUST reject a file whose URI is not exactly the prefix and canonical base64, whose bytes are not UTF-8, or whose bytes are not the canonical encoding of the JSON they parse to. The last check rejects duplicate keys (parsers disagree on which one wins), whitespace, and other spellings of a number. ### Leaves A leaf is one payment: | field | meaning | | --- | --- | | `payer` | the log's `from` | | `amount` | the log's `value`, in base units, a decimal string | | `paidAt` | the timestamp of the block holding the log | | `tx`, `log` | the log's transaction hash, and its index in its block | | `block` | the log's block (a lookup aid, not committed) | The chain id, the asset, the agent, the owner and `payTo` come from the statement. With the owner in it, a leaf alone shows whether it is one of the four transfers Payments leaves out. The leaf hash is the one OpenZeppelin's `MerkleProof` expects: ``` leafHash = keccak256(bytes.concat(keccak256(abi.encode( uint256 chainId, address asset, uint256 agentId, address owner, address payer, address payTo, uint256 amount, uint64 paidAt, bytes32 txHash, uint32 logIndex)))) ``` Leaves are ordered by position. The root is a binary Merkle tree over the leaf hashes in that order: each pair is hashed sorted, as `keccak256(abi.encodePacked(min, max))`, and an odd last node is carried up unchanged. The root of no leaves is `bytes32(0)`. ### Statement file The file holds ERC-8004's off-chain feedback fields and one object, `income`. It is shown here in reading order; its bytes are canonical (keys sorted, no whitespace): ```json { "agentRegistry": "eip155:1:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432", "agentId": "6197", "clientAddress": "eip155:1:0x...writer", "createdAt": "2026-10-20T09:00:00Z", "value": "...", "valueDecimals": 6, "tag1": "revenues", "tag2": "x402.statement", "income": { "v": "erc8004-income/v1", "asset": "eip155:1/erc20:0x...", "decimals": 6, "since": 26000000, "seq": 2, "prev": "0x...feedbackHash of seq 1", "window": { "fromBlock": 26050000, "fromLog": 0, "toBlock": 26099999, "toLog": null, "toTime": 1791199988 }, "owner": "0x...", "payTo": "0x...", "received": { "count": 412, "amount": "..." }, "payers": 37, "root": "0x...", "anchors": [ { "payer": "0x...", "amount": "...", "paidAt": 0, "block": 0, "tx": "0x...", "log": 0 } ] } } ``` - `asset` is the token counted, as a CAIP-19 id, `eip155:/erc20:
`. `decimals` is the token's `decimals()` and equals `valueDecimals`. - `asset`, `decimals` and `since` MUST NOT change along a (writer, agent) chain. A writer that reports several assets for one agent uses one writer address per asset. - `seq` starts at 1 for each (writer, agent) pair and increases by 1. `prev` is the previous statement's `feedbackHash`, or `null` for seq 1. - `since` is seq 1's `window.fromBlock`. - `window` is as defined in Windows; `toTime` is the timestamp of `toBlock`. `toBlock` SHOULD be final on its chain. - `owner` and `payTo` are the window's, as defined in Windows. - A statement MUST have one leaf for every payment of its window, and no other leaf. - `received.count` is the number of leaves and `received.amount` the sum of their amounts. `payers` is the number of distinct payers among the leaves. `root` is `bytes32(0)` exactly when `received.count` is 0. - `value` is the previous statement's `value` plus `received.amount`, or `received.amount` for seq 1. - `anchors` are the latest leaves in leaf order, written in full (RECOMMENDED: 5). Statement files SHOULD stay under 4 KB, and a writer drops the oldest anchors first to fit. ### Writers A writer: - MUST post statements so that each agent's windows are back to back. Seq 1's window starts in block `since`, at log 0 or at the agent's last change log there, as Windows requires. Every later window starts at the position right after the previous window's last position, or, when that position is one of the agent's change logs, at the agent's last change log in its block; - SHOULD post a statement at least every 30 days; - MUST NOT revoke a statement, nor post two statements with the same `seq` for one agent; - SHOULD check each file against this profile before posting it: a posted file cannot be replaced. ### Named writer An agent's owner MAY name the writer whose statements readers read first, with ERC-8004's `setMetadata` under the key `incomeWriter`. The value is `abi.encode(address)` of the writer: 32 bytes, the address in the last 20. - A reader reads the key with `getMetadata(agentId, "incomeWriter")` at the latest block it reads. - A reader treats the key as unset when its value is not 32 bytes, when its first 12 bytes are not zero, or when it names the zero address or the agent's owner at that block. The Reputation Registry refuses entries from the owner, so the owner has no chain to name. - When the key is unset, a reader chooses the writers it reads (see Readers). - When the owner changes the key, the newly named writer is the one readers read from then on. Each chain stands alone: the earlier writer's statements stay valid, as statements of another writer. A newly named writer SHOULD start its seq 1 at the earlier writer's `since`, so that the agent's figures keep their history. - A transfer of the agent leaves the key as it is: ERC-8004 names only `agentWallet` as cleared on a transfer. A new owner that wants another writer sets the key again. - A writer's chain counts one asset (see Statement file), so the key names a writer for one asset. A reader that counts other assets for the agent MAY read other writers for them. - A `MetadataSet` log for `incomeWriter` is not a change log: it cuts no window. - The key adds no trust. A reader checks the named writer's statements as any other's, and a full rebuild needs no trusted writer. The key tells readers which chain to read, and lets them skip entries planted by anyone else. ### Readers An entry, for a reader, is the fields of the registry's `NewFeedback` log for it (from a log query, or from its transaction receipt), the block that log is in, and whether `readFeedback` reports it revoked. A reader: - MUST check each entry's bytes: the URI (see Entries), that `feedbackHash` equals `keccak256` of the bytes it carries, the encoding and limits (see Encoding), and that the file's ERC-8004 fields match the entry and the registry it reads (`agentRegistry`, `agentId`, `clientAddress`, `value`, `valueDecimals`, `tag1`, `tag2`); - MUST check that the asset's `decimals()` returns `decimals`: a smaller figure would make the same income look larger to every reader of `value`; - MUST read every entry the writer has given the agent, statements or not, and check that their `feedbackIndex` values are exactly 1 to the Reputation Registry's `getLastIndex(agentId, writer)`, each once: a set that left out an entry could hide a statement. An entry is a statement when its `tag1` is `revenues` and its `tag2` is `x402.statement`, and a malformed statement anywhere in the set fails the writer; - MUST walk the statements of each (writer, agent) chain in `feedbackIndex` order and treat any of the following as a failure of that writer for that agent: a revoked statement, a seq that is not the previous one plus 1 (so a repeated `seq` fails), a `prev` that is not the previous `feedbackHash`, windows that are not back to back as Writers requires, a `value` that is not the previous `value` plus `received.amount`, or an `asset`, `decimals` or `since` that changes; - MUST check that each statement's window ends before the block its entry was posted in, and that `toTime` is the time of `toBlock` (a statement cannot pre-claim blocks still to come); - MUST check each window against the agent's change logs and the registry's state: where it starts and ends, that no change log lies inside it, and its `owner` and `payTo` (see Windows). It MUST also check that `ownerOf(agentId)` and `getAgentWallet(agentId)` still return them at the end of the last block that ends inside the window: `toBlock` when `toLog` is `null`, otherwise the block before `toBlock` when that block is in the window. A change the registry made without its log then fails the window, unless it was undone within it; - MUST check that a window whose `payTo` is `null` has no leaf, and that `root` is `bytes32(0)` exactly when `received.count` is 0; - MUST check each anchor against the asset's logs: the log at (`tx`, `log`) in `block` is a payment of the window from `payer` of `amount`, `paidAt` is its block's time, and the anchors are in leaf order and no more than the leaves; - MUST report where each chain ends (its last window's `toBlock`, `toLog` and `toTime`), and MUST NOT present a chain's figures as the agent's income past that point; - MUST NOT present the totals of a statement it has not rebuilt as checked. Without a rebuild they are the writer's claims; - SHOULD, when the agent names a writer (see Named writer), read that writer's statements first, and by default read no statement about the agent from any other writer; - MUST take an agent's income in an asset from one (writer, agent) chain: its latest statement's `value`. A per-tag summary of `revenues` entries is not income: ERC-8004's `getSummary`, or any aggregator's mean per tag, averages cumulative values. A reader MUST NOT add up the income of two writers' chains about one agent in one asset, since they count the same transfers; - MUST NOT add income across assets, and MUST decide which assets it counts (see Security Considerations); - MUST report an agent with no statement from the writers it reads as having no data, not as having no income; - MUST treat a malformed entry or a failed check as a failure of that writer only, and go on with the others; - SHOULD weigh payers before relying on an agent's income (see Security Considerations); - SHOULD bound what it reads: entries per writer, log queries per rebuild, and the requests one read makes across every writer it reads. Anyone can post entries about an agent, and checking one costs requests. There are two levels of check: - **Light** is a sample: the checks above, with a handful of calls per statement. It shows that nothing it checked is wrong. It does not show that the root covers every payment of a window, nor that the totals are right: a statement whose anchors are real and whose totals are invented passes every light check. - **Full** also rebuilds each statement from the asset's logs, and MUST then find exactly the statement's leaves, `received`, `payers`, `root` and anchors (the latest leaves). One log query per window, on the asset's address with the `Transfer` topic and `payTo` as the `to` topic over the window's blocks (more queries where an endpoint limits block ranges), returns every candidate. The payment rules select the leaves, and one block time per block holding a payment gives their `paidAt`. Everything in a statement is then checked against the token and the identity registry, so a full rebuild needs no trust in the writer. ### Verifier An on-chain verifier is OPTIONAL. A verifier MUST implement `IIncomeStatementVerifier` and [ERC-165](./eip-165.md), returning `true` for the interface identifier of `IIncomeStatementVerifier` and for `0x01ffc9a7`. ```solidity interface IIncomeStatementVerifier { struct Payment { uint256 chainId; address asset; uint256 agentId; address owner; address payer; address payTo; uint256 amount; uint64 paidAt; bytes32 txHash; uint32 logIndex; } event PaymentProven(uint256 indexed agentId, bytes32 indexed root, bytes32 txHash, uint32 logIndex); function leafHash(Payment calldata payment) external pure returns (bytes32); function verifyPayment(bytes32 root, bytes32[] calldata proof, Payment calldata payment) external returns (bool); } ``` - `leafHash(payment)` MUST return the leaf hash defined in Leaves, over the payment's fields. - `verifyPayment(root, proof, payment)` MUST return `true` exactly when `payment.amount` is not zero, `payment.payer` is none of the zero address, `payment.payTo` and `payment.owner`, and hashing `leafHash(payment)` with each element of `proof` in turn, each pair sorted, gives `root`. It MUST NOT revert because one of these checks fails; it returns `false`. - When `verifyPayment` returns `true`, it MUST emit `PaymentProven` with the payment's `agentId`, `txHash` and `logIndex` and the `root`. When it returns `false`, it MUST NOT emit it. - `verifyPayment` MUST be callable by any account. A verifier SHOULD hold no state. A verifier checks membership only. A statement's root is in its file, not in registry storage, so a contract that calls a verifier decides which roots it trusts: for example roots it was given by a keeper that rebuilt them, or roots posted by a writer it trusts. ## Rationale - **Leaves per transfer, not aggregates.** Aggregate figures (inflow, volume, counterparty counts) are useful and can be recomputed by anyone who accepts their algorithm, but they cannot be proven one transfer at a time, nor show which payers stand behind them. One leaf per transfer, named by its log, gives readers the payers and gives contracts something to prove. - **A profile of `revenues`, not a new tag.** ERC-8004 already names `revenues` as cumulative revenues. With it as `tag1`, every reader of that tag sees these entries; `tag2` marks the ones whose value can be rebuilt, and names the motivating case. The value stays cumulative, as ERC-8004 describes it, and each window's own income is in its file. - **The token's logs are the record.** On an EVM chain, x402 settles a payment with an [ERC-3009](./eip-3009.md) `transferWithAuthorization`, or through a contract the payer approved, which moves the tokens from the payer to the `payTo`. Both end in one `Transfer` from the payer to the `payTo`, so reading that log catches both and needs nothing but the token. Other transfers end in the same log, which is why the totals are called income, not x402 revenue. - **`agentWallet` declares the address.** ERC-8004 describes it as the address where the agent receives payments. Setting it takes the wallet's own signature, and a transfer of the agent clears it. If the writer chose the address, it could credit any agent with the income of any busy address, and every check would pass because the transfers are real. - **Windows cut at log positions.** An owner or wallet change takes effect at its own log, not at the end of its block. A window that ended at a block boundary would count a transfer made to the new wallet earlier in that block, before it was the agent's. Cutting at the change log puts every transfer under the owner and wallet in force when it happened. Starting the next window at the block's last change log keeps the state of every window readable at the end of one block, with `eth_call`, without decoding the registry's events. The cost is the transfers between two change logs of one block, which no window counts. - **Every transfer in, less four.** A mint, a transfer of nothing, a transfer from `payTo` to itself and a transfer from the owner are not money from anyone else, and a reader can recognise them from the log and the window alone. Which other payers are customers is a judgement, so the leaves name the payers and leave it to readers. - **The owner in the leaf.** The owner is fixed for a window, and the statement already names it. In the leaf, it lets a verifier refuse the owner's own transfers from the payment alone, as it refuses the other three. Top-ups from the owner are probably the most common transfer into an agent's wallet that is not income; without the owner in the leaf, a root that counted them would still prove them as payments on chain. The leaf also tells a contract under which owner each payment arrived. - **No class per leaf.** Gross receipts mix sales with loan draws, swap proceeds, bridge fills and the owner's own money, and a reader that wants income has to tell them apart. A class per leaf (customer, loan, swap, bridge, owner, self) written by the writer would not do it. A file carries its root and a few anchors, not its leaves, so a class for every leaf would have to be committed in the leaf hash, and a root could then no longer be rebuilt from the logs: a full rebuild would need the writer's classes as input, and `verifyPayment` would prove a class no log supports. Outside the hash, a class would reach the anchors only. Either way it is the claim of a writer the owner may have named, and the owner is the one that gains when its own money is called a customer's. Each reader derives the kinds instead, from the chain and the addresses it knows (see Security Considerations), and can extend its lists without any statement being written again. - **A statement saves the scan, not the trust.** Anyone can recount an agent's transfers from the token's logs, so a statement adds no authority. It fixes a window's figures once, lets a reader check only new windows afterwards, and gives contracts a root to prove single transfers against. - **One chain's latest value.** Two writers reporting on one agent count the same transfers, so, unlike the credit statements of two lenders, their figures cannot be added. A per-tag mean of cumulative values, which is what generic summaries of feedback compute, has no meaning either. - **A writer the owner names, not a default set.** A facilitator sees only the payments it settled, and a statement counts every transfer into `payTo`, so a facilitator has no data a reader lacks, and a full rebuild needs no trusted writer. What a reader lacks is which chain to read. The registry refuses the owner as a client, so an agent cannot write its own chain, but its owner can name one. Readers then skip entries planted by anyone else by default, and check the named writer as any other. - **A Merkle root rather than a list.** It keeps the file small, it can be rebuilt from logs, and it allows one-transfer proofs on chain. The leaf encoding and the sorted pair hashing are OpenZeppelin's, so existing proof libraries verify them. - **A verifier that returns `false`.** A contract that tries a payment against several roots, or decides what a failed proof means, does so without catching reverts. The event leaves a public record that a payment was proven against a root. - **`data:` URIs and canonical JSON.** The evidence is in the entry's own calldata, so it is immutable and needs no server or pinning. The hash fixes the bytes, and one canonical form leaves one reading of them. A reader checks it by encoding the fields it decoded and comparing the bytes. - **Shared with the credit history profile.** The encoding, the `data:` URIs, the chained windows and the light and full checks are those of a credit history profile of the same registry, so one reader implementation can read both. - **Next to agent identity documents.** Agent identity documents under discussion can carry facets about an agent address over a window, including its observed on-chain finances as aggregate figures recomputed under a pinned algorithm, and recommend that the address be the agent's `agentWallet`. A statement is then about the same address. - A facet type in the identity document's open namespace (`aid:behavior//v1` in the proposal that names it) can point at an agent's statements through the ERC-8004 Reputation Registry. Such a resolver is defined by `tag1`, `revenues`; `tag2`, `x402.statement`, is an extra member it may carry, and a generic resolver need not filter on it. - Listing such a facet attests that a statement is in its interval and current or historical for that address, not the statement's figures: those stand on the statement's Merkle root and its chain. - Statements are cumulative within a chain, so a document reads them as the latest statement per writer through that facet. A facet that averages a tag's values, as a score-like reputation summary does, is not the place for them: a mean of cumulative figures is not income (see Readers). - An observed-finance algorithm can also take statements as its input. - Those documents attribute evidence by a facet's observation time inside an authority interval, not by the period it is about. A facet issuer that points at a statement therefore sets the facet's period to the statement's window and its observation time to the window's `toTime`. - Each window lies within one owner and one wallet, and so within at most one authority interval: those documents also cut intervals at their own binding, unbinding and retirement, which statements do not observe. Only the cuts at ERC-8004's `Transfer` and `MetadataSet` coincide. - **Public income, by choice.** An agent that wants to prove an income threshold without publishing its payers can use a zero-knowledge predicate over the same logs, which is what know-your-agent and agent identity proposals under discussion recommend for financial behaviour. That keeps the payers private, but a reader then trusts the prover's circuit and the issuer's inputs. This profile is the public alternative: anyone can recount every payment without trusting anyone, and the cost is that the agent's payers are easy to read (see Security Considerations). An agent can use both, for different readers. - **Payments named by their log.** A leaf names its transfer by its log (chain, transaction hash and log index), with its payer, `payTo`, asset and amount. ERC-8004's `proofOfPayment` (`fromAddress`, `toAddress`, `chainId`, `txHash`) points at the same transaction and parties, and so does work in the ERC-8004 and x402 communities on payment receipts that ground one feedback entry in one payment. A reader can therefore match a leaf to such a receipt, and a feedback entry grounded in one payment to the statement that counts it. - **Not a company report.** [ERC-7776](./eip-7776.md) publishes figures a company reports about itself. Every figure here is rebuilt from transfers on chain, and it covers gross receipts only. ## Backwards Compatibility No backward compatibility issues found. The profile adds a `tag2` value, a metadata key and a file format and changes nothing in ERC-8004. Readers that already read `revenues` see a cumulative value in the asset's base units with the asset's decimals; a reader that averages `revenues` values across entries gets no meaningful figure (see Readers). Readers that do not know the tags ignore them. ## Test Cases The reference implementation's tests check every value and case below. Except for the payment on chain at the end, the vectors use chain id 1; the asset `0x00000000000000000000000000000000000a55E7`, with 6 decimals; agent `9007199254740993` (2^53 + 1, so that an id read as a floating-point number fails); the owner `0x000000000000000000000000000000000000041E`; and `payTo` `0x0000000000000000000000000000000000000402`. The payers are P1 `0x00000000000000000000000000000000000000B1`, P2 `0x00000000000000000000000000000000000000b2`, P3 `0x00000000000000000000000000000000000000b3`, P4 `0x00000000000000000000000000000000000000B4` and P5 `0x00000000000000000000000000000000000000b5`. A transaction hash written `0xa1` is `bytes32(uint256(0xa1))`. Block `b` has the time `1790000000 + 12 * (b - 26000000)`. The agent was registered in block 25990000 (its mint at log 1, its `agentWallet` set to the owner at log 2), and its `agentWallet` set to `payTo` at log 0 of block 25995000. **Rebuilding a window.** The asset's `Transfer` logs to `payTo` in blocks 26000000 to 26049999 are: | block | log | from | value | tx | in the statement | | --- | --- | --- | --- | --- | --- | | 26000010 | 3 | P1 | 10000 | `0xa1` | leaf 0 | | 26000010 | 7 | P2 | 2500 | `0xa2` | leaf 1 | | 26000500 | 2 | the owner | 500000 | `0xa6` | no: from the owner | | 26003001 | 0 | P1 | 1000000 | `0xa3` | leaf 2 | | 26010000 | 4 | P5 | 0 | `0xa7` | no: a transfer of nothing | | 26020000 | 12 | P3 | 50000 | `0xa4` | leaf 3 | | 26030000 | 0 | the zero address | 7000 | `0xa8` | no: a mint | | 26040000 | 5 | `payTo` | 3000 | `0xa9` | no: to itself | | 26049999 | 1 | P4 | 10000 | `0xa5` | leaf 4 | The five payments have these leaf hashes: | leaf | `paidAt` | leaf hash | | --- | --- | --- | | 0 | 1790000120 | `0x973838fd10839c33761186ef4e63069cb758e3ec534221eaa42705f06c46c4ec` | | 1 | 1790000120 | `0x9e191334aadc974cad9b88b02911142857fbecfb0d4db28e6d561c880dc54e60` | | 2 | 1790036012 | `0x04e8b3fcfeca684cb61d0f4a3d48965c91250c785218d23d20e25367df5b00e4` | | 3 | 1790240000 | `0xe94b8baca86013e08d72e5cbf5e8c6daafba4961cdd79c441e3e444e9735c132` | | 4 | 1790599988 | `0x46894c3239fd5fda0dbf685ec1e94b3b81dbc215f4b07d4e747e36530d0effbd` | Their root is `0x520ba6ddc52623d5852649d9c9237f6ad5b35794d9413bc3903741f428abf238`, over 1,072,500 base units from four distinct payers. The inner nodes are `0x04dbfb095919cbe6bf6dffbe366a9899d44bfd271495dc7b820f6e24d56e2830` (leaves 0 and 1), `0x9705053cd0b8cdc5dd701db3fa3b5bc7b150cf53d42c09e1e05d1e130ae84047` (leaves 2 and 3) and `0x94f6220ecbb45711f1cb5c30a80950a103742d8831136ae8db6abc2efa379fda` (leaves 0 to 3). The proofs are: - leaf 0: leaf 1's hash, the node of leaves 2 and 3, leaf 4's hash; - leaf 1: leaf 0's hash, the node of leaves 2 and 3, leaf 4's hash; - leaf 2: leaf 3's hash, the node of leaves 0 and 1, leaf 4's hash; - leaf 3: leaf 2's hash, the node of leaves 0 and 1, leaf 4's hash; - leaf 4: the node of leaves 0 to 3. **Statement files.** The writer is `0x00000000000000000000000000000000000000C1`, and the identity registry `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432`. Seq 1 covers the window above, from (26000000, 0) to the end of block 26049999, with the five leaves as its anchors. Its canonical bytes (1,736 bytes) are: ```text {"agentId":"9007199254740993","agentRegistry":"eip155:1:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432","clientAddress":"eip155:1:0x00000000000000000000000000000000000000C1","createdAt":"2026-09-28T13:53:08Z","income":{"anchors":[{"amount":"10000","block":26000010,"log":3,"paidAt":1790000120,"payer":"0x00000000000000000000000000000000000000B1","tx":"0x00000000000000000000000000000000000000000000000000000000000000a1"},{"amount":"2500","block":26000010,"log":7,"paidAt":1790000120,"payer":"0x00000000000000000000000000000000000000b2","tx":"0x00000000000000000000000000000000000000000000000000000000000000a2"},{"amount":"1000000","block":26003001,"log":0,"paidAt":1790036012,"payer":"0x00000000000000000000000000000000000000B1","tx":"0x00000000000000000000000000000000000000000000000000000000000000a3"},{"amount":"50000","block":26020000,"log":12,"paidAt":1790240000,"payer":"0x00000000000000000000000000000000000000b3","tx":"0x00000000000000000000000000000000000000000000000000000000000000a4"},{"amount":"10000","block":26049999,"log":1,"paidAt":1790599988,"payer":"0x00000000000000000000000000000000000000B4","tx":"0x00000000000000000000000000000000000000000000000000000000000000a5"}],"asset":"eip155:1/erc20:0x00000000000000000000000000000000000a55E7","decimals":6,"owner":"0x000000000000000000000000000000000000041E","payTo":"0x0000000000000000000000000000000000000402","payers":4,"prev":null,"received":{"amount":"1072500","count":5},"root":"0x520ba6ddc52623d5852649d9c9237f6ad5b35794d9413bc3903741f428abf238","seq":1,"since":26000000,"v":"erc8004-income/v1","window":{"fromBlock":26000000,"fromLog":0,"toBlock":26049999,"toLog":null,"toTime":1790599988}},"tag1":"revenues","tag2":"x402.statement","value":"1072500","valueDecimals":6} ``` and their `keccak256`, the entry's `feedbackHash`, is `0xbf23a8780b9fbdf0e12b68a92d3f66556c4c6d0ce6e15c5e0df1810af6e368ee`. Seq 2 covers blocks 26050000 to 26099999, in which `payTo` received nothing. It chains to seq 1 and keeps its cumulative value. Its canonical bytes (838 bytes) are: ```text {"agentId":"9007199254740993","agentRegistry":"eip155:1:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432","clientAddress":"eip155:1:0x00000000000000000000000000000000000000C1","createdAt":"2026-10-05T12:33:08Z","income":{"anchors":[],"asset":"eip155:1/erc20:0x00000000000000000000000000000000000a55E7","decimals":6,"owner":"0x000000000000000000000000000000000000041E","payTo":"0x0000000000000000000000000000000000000402","payers":0,"prev":"0xbf23a8780b9fbdf0e12b68a92d3f66556c4c6d0ce6e15c5e0df1810af6e368ee","received":{"amount":"0","count":0},"root":"0x0000000000000000000000000000000000000000000000000000000000000000","seq":2,"since":26000000,"v":"erc8004-income/v1","window":{"fromBlock":26050000,"fromLog":0,"toBlock":26099999,"toLog":null,"toTime":1791199988}},"tag1":"revenues","tag2":"x402.statement","value":"1072500","valueDecimals":6} ``` and their `keccak256` is `0xeea2227a6c70110c0d147bbd5b8bb7a80ea6ea21a3e0dab7c1545db0f8488520`. The reference writer produces both files byte for byte, and their URIs equal the prefix followed by an independent base64 encoder's output. **Windows cut at change logs.** After seq 2, the agent's `agentWallet` moves to `0x0000000000000000000000000000000000004030` (W2) at (26100005, 3). The agent is transferred to `0x00000000000000000000000000000000000000e1` at (26150000, 8), the registry clearing the wallet at (26150000, 6). The new owner sets `0x0000000000000000000000000000000000004040` (W3) at (26160000, 0). The transfers in those blocks are: (26100005, 1) P3 to `payTo`, 9000; (26100005, 2) P1 to W2, 7000; (26100005, 4) P2 to W2, 8000; (26100005, 5) P4 to `payTo`, 6000; (26150000, 5) P1 to W2, 1111; (26150000, 7) P3 to W2, 3333; (26150000, 9) P2 to W2, 2222; (26160000, 1) P5 to W3, 4444; (26160000, 2) the new owner to W3, 5555. With every window allowed to run to block 26200000, the writer posts: - seq 3, from (26100000, 0) to (26100005, 2), `payTo` the old wallet: one payment, 9000, value 1081500; - seq 4, from (26100005, 3) to (26150000, 5), `payTo` W2: 8000 and 1111, value 1090611. The 7000 that reached W2 at log 2, before log 3 made it the `agentWallet`, is in no statement; - seq 5, from (26150000, 8) to the end of block 26159999, owner the new owner, `payTo` `null`: no payment. The 3333 at log 7, between the block's two change logs, is in no statement; - seq 6, from (26160000, 0) to the end of block 26200000, `payTo` W3: 4444 (the owner's 5555 is not a payment), value 1095055. A seq 1 started in block 26100005 instead runs from (26100005, 0) to (26100005, 2) and takes its wallet from the end of block 26100004: it counts P3's 9000 to the old wallet, not P1's 7000 to W2. **The reference writer** refuses to write what a reader rejects: a next statement whose chain id, registry, agent, writer, asset or decimals differ from the previous one's; decimals of 19; 17 anchors; decimals of 18 for the 6-decimal asset, or an asset without `decimals()`; a `since` of 2^53; a value past 2^127 - 1; and a window over which the registry unset the wallet without a log. Asked for 16 anchors over a window with fifteen more payments of about 10^37, it drops the oldest anchors until the file fits in 4 KB, keeps the latest leaf, and the checker accepts the result. **The reference checker** accepts both chains, light and full, and reports seq 6's value, 1095055, through the end of block 26200000: not the sum or the mean of the six values. It rejects, each with its own error: - a URI with another prefix, without padding, or with a character after the base64; a `feedbackHash` of other bytes; a file with a leading space, a file whose asset names chain 8453, and decoded fields that differ from the bytes; a file over 8,192 bytes, 17 anchors, a number of 2^53, decimals of 19, a value of 2^127; - an entry whose agent, client, value, decimals, `tag1` or `tag2` differ from the file; a revoked entry; a reader whose registry or chain differ; an asset whose `decimals()` is 18 or missing; - a writer's entries against a `getLastIndex` of 3 when it has two, with indexes 1 and 3, with index 1 twice, or with another writer's entry among them; a second seq 1 posted at index 2 (read, it repeats seq 1; left out, it leaves a gap); a statement at index 2 whose URI is not its file. A rating at index 2, between seq 1 and seq 2, is accepted; - seq 1 with seq 2, with a `prev`, with `since` before its window, with `fromLog` 3, or with a value other than its amount; seq 2 with seq 3, with seq 1 again, with another `prev`, with another value, asset, decimals, `since`, writer or agent, or starting one block late or one block early; - seq 5 starting at log 6, the block's first change log instead of its last, as a window and as a link; seq 3 starting at log 2 of a block, or ending at log 1 instead of just before the change log at log 3; a window ending before it starts; seq 3 running to the end of block 26100005, across the wallet change; seq 4 starting at log 0 of block 26100005, which would count the 7000 under W2; - a window posted in its own last block; a `toTime` off by one second; a window before the agent was registered; another owner or `payTo`; seq 4 claiming the old wallet, or seq 3 claiming W2; seq 5 with a payment; seq 1 with a zero root, and seq 2 with a root; - seq 1 when the registry unset the wallet at (26030000, 5) without a log: the wallet at the end of block 26049999 is not `payTo`. (A wallet unset and set back inside that window without logs is not seen: the registry would be outside this profile.); - an anchor with an inflated amount, another payer, another transaction or another time, two anchors out of order, five anchors for four payments, an anchor outside the window, the owner's own transfer as an anchor, and an anchor at a log that does not exist; - in a full check only: seq 1 rewritten without P2's 2500 (four leaves, a root over them, every light check passing); seq 1 rewritten with the owner's 500000 as a sixth leaf (a root over the six, every light check passing), against whose root the verifier refuses the owner's transfer and proves the five payments; seq 1 with five payers, an amount 1 higher, another root, or a count of 6; and seq 1 whose anchors are the first four leaves instead of the latest. Given chain results, it reports one chain's value, never the sum across writers (two writers reporting 1072500 give 1072500), a rebuilt chain over a sampled one, the furthest of two rebuilt chains, a sampled chain as not checked, no data for no chain or a failed one, and nothing for an asset the reader does not count. Of two writers, one with a revoked entry, it fails that one and rebuilds the other. It reads no named writer while `incomeWriter` is unset, empty, 20 bytes long, past 160 bits, the zero address or the owner, and none once the address it names becomes the owner. It reads the named writer from the block the key is set in, and the new one after the owner changes it. With a named writer, it reports that writer's chain, even sampled, over a chain from another writer that is rebuilt and reaches further, and no data when the named writer has no chain. **The verifier.** A conforming verifier passes these cases: - `leafHash` returns each leaf hash above for its payment, and equals the double `keccak256` of the ten fields `abi.encode`d in order. - The five payments, in the order above, give the root above; the root of no payment is `bytes32(0)`. - `verifyPayment` returns `true` for each leaf with its proof and emits `PaymentProven` with the agent, the root, the transaction hash and the log index. - A forged leaf fails: leaf 2 claimed with an amount of 10000000 returns `false` with leaf 2's proof, and so does leaf 2 with any one of its ten fields changed. - Leaf 0 for agent `9007199254740994` or `9007199254740992` returns `false`. - A wrong proof fails: leaf 0 with one sibling changed, with leaf 2's proof, with its last sibling dropped, or with no proof, returns `false`. - A wrong root fails: leaf 0 against the root with its last bit flipped, against `bytes32(0)`, or against the statement's `feedbackHash`, returns `false`. - A failing call does not revert and emits no event. - The result does not depend on the caller: leaves 0 to 2 verify when three unrelated accounts call. - The transfer of zero, the mint, the transfer from `payTo` to itself and the owner's transfer, as payments, return `false` against a root whose tree holds their leaf hash next to leaf 0, and emit nothing; the same tree proves leaf 0. - A tree of one payment has that payment's leaf hash as its root, and an empty proof verifies. - In random trees of 1 to 40 payments, every leaf verifies with its proof, and fails once its amount changes. - `supportsInterface` returns `true` for `0xcc3df75f`, the interface identifier of `IIncomeStatementVerifier`, and for `0x01ffc9a7`, and `false` for `0xffffffff`. **A payment on chain.** On Robinhood Chain (chain id 4663), agent 8811 of the identity registry `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` was minted at log 1 of block 89385625 to `0xF5858D7Ea59b4fB176925Df77D332a43e786Bb65`, and its `agentWallet` set to that same address at log 4. The agent has no other change log before block 94630540, whose time is 1792621780. In that block, transaction `0x1921a861077692f32b64812f811c7afc3c400822d11fb7ae4883a1ade48ea601` settles an x402 payment with ERC-3009's `transferWithAuthorization` on USDG, `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` (6 decimals): the token's `AuthorizationUsed` is log 1, and log 2 is a `Transfer` of 1000000 from `0x33d6112f2F3BCD2a476bD9C13208E4C632bE2d0a` to the wallet. The owner and `payTo` are one address and the payer is neither, so the transfer is a payment. Its leaf hash is `0xf87837f67578cc0593869d5409d2310e50e12de8c3da89b92a5e6cd45805b12f`. It is the only payment of the window from (94630540, 0) to the end of block 94630540, so its leaf hash is that window's root, and `verifyPayment` proves it with an empty proof. Given the agent's two change logs and the token's transfers to the wallet, the reference writer writes that window with that root. With the owner as its payer, the same transfer fails. No window starts before block 89385625: the 5000000 that reached the wallet at log 2 of block 89378384, before the mint, is in no statement. ## Reference Implementation These contracts accompany this proposal with their tests. None has a dependency. - `IncomeStatementVerifier`: the optional verifier. It holds no state. - `IncomeStatementCodec`: the file's canonical encoding for its fixed schema, the `data:` URI, the leaf hash and the root. - `IncomeStatementWriter`: a reference writer. It builds an agent's next statement from the chain, cutting its window at the agent's next change log, and returns the file, its URI and its `feedbackHash`, ready for `giveFeedback`. - `IncomeStatementChecker`: a reference reader. Each reader rule is a check that fails with its own error. It reads all of one writer's entries for an agent, picks the statements among them, and checks them light or full; it reads several writers so that one failure fails only that writer, reads the writer the agent names, and picks an agent's income in an asset from the results, from the named writer's chain when there is one. - `IChainHistory`: the chain reads the writer and the checker make: block times, the registry's `ownerOf`, `getAgentWallet` and `getMetadata` at the end of a block, the agent's change logs, the asset's `Transfer` logs, and the Reputation Registry's `getLastIndex`. A contract cannot read past blocks, so a reader implements it over an RPC endpoint and runs the checker with `eth_call`; the tests implement it with a recorded chain. ## Security Considerations - **Self-payment.** An agent can pay itself: its owner funds other addresses and pays the agent's `payTo` from them, over x402 or directly. Every check in this profile passes for such payments, because they happened. Readers SHOULD therefore weigh payers before relying on an agent's income. Signals that help: - how many distinct payers there are, and the share of the largest; - the payers' own age and history, and whether their funds came from the agent's owner or from the agent; - whether the money comes back to the payers afterwards. - **Settlement contracts as payers.** `payer` is the `Transfer` log's `from`, so when settlement goes through an escrow, a batching contract, a fee splitter, a bridge or a solver in front of `payTo`, every leaf names that contract or the solver's address: an honest agent then looks like it has one customer, and a self-payment hides in the contract's flow. A bridge or solver fill can be a lone `Transfer` from the solver, with nothing in its transaction to name who paid on the other chain. Readers SHOULD resolve known settlement contracts, bridges and solvers to the original payer, or treat that payer as unknown in the signals above. - **The owner's other addresses.** The payment rules leave out transfers from the owner's own address only. Money the owner sends from another of its addresses, or bridges from another chain through a solver, meets them, and by the log alone it cannot be told from a payer's. Readers that want income SHOULD treat addresses linked to the owner as the owner's: addresses whose funds came from the owner or from `payTo`, and a bridge or solver fill that relays a transfer the owner, or such an address, made on another chain. - **Transfers that are not sales.** Loan draws, swap proceeds, refunds and the owner's money all meet the payment rules, and no leaf says which it is (see Rationale). A reader that wants income derives the kind of each payment from the chain and from the addresses it knows, and a lender that sizes credit on income SHOULD leave out payers it knows to be lenders, itself included: - a loan draw is a `Transfer` from a lending pool, usually with the pool's own event in the same transaction; - swap proceeds are a `Transfer` from a DEX pool or a pool manager, with its swap event in the same transaction; - a bridge or solver fill is a `Transfer` from the bridge's contract or the solver's address, resolved as above; - the owner's money, and a test buyer the owner funded, come from addresses linked to the owner, as above; - a `Transfer` in a transaction that `payTo` or the owner sent was pulled by the agent: a swap, a loan draw or a claim from an escrow, which the transaction's other logs tell apart; - an ERC-3009 `AuthorizationUsed` log of the asset, by the payer, in the same transaction shows that the payer signed the transfer, not that the payer is a customer: an owner's test buyer settles the same way. - **Another address's income.** Readers check `payTo` against the registry and the window against the agent's change logs, so a writer cannot count transfers to an address that was not the agent's `agentWallet` when they arrived. The cuts rely on the registry emitting `MetadataSet` for every change of the wallet, which this profile requires. Against a registry that does not, the check of the owner and wallet at the end of each window still fails a window across which a change lasts. What it cannot see is a change made and undone inside one window without logs, or a change without a log inside the last, partial block of a window that ends just before a change log. - **The asset is the writer's choice.** Any contract can emit `Transfer` logs, so statements about a worthless token rebuild exactly. Readers count only assets they know, and price them. - **Income follows the identity.** A buyer of an agent gets the record its earlier owners earned. Each window and each leaf names its owner, and readers SHOULD weigh income received under the current owner. - **Gross receipts.** A statement shows what came in, not what the agent spent to earn it. - **Light checks are samples.** A statement with real anchors and invented totals passes them. A light result is the writer's claim (see Readers). - **A named writer is the owner's choice.** The key tells readers which chain to read, not that the chain is right. Until a reader rebuilds them, a named writer's totals are claims made for the agent by a writer its owner chose. - **The verifier proves membership only.** It cannot tell a statement's root from any other hash. A contract that relies on it holds roots it trusts. - **Reorgs.** A statement over unfinalized blocks can name logs that later disappear. Windows SHOULD end at final blocks. - **Privacy.** Every payer and amount in a statement is already public in the token's logs, and anyone can write statements about any agent. A statement does not make an agent's customers public; it makes them easier to read. Agent identity proposals under discussion default to zero-knowledge disclosure of an agent's financial behaviour; this profile publishes by choice, and an agent that wants its payers private uses a predicate proof instead (see Rationale). Files have no free-text field, so they carry no personal data. - **Endpoints.** Checks read logs and the registry's state at past blocks (`ownerOf` and `getAgentWallet`), so a reader may need an archive endpoint. - **Planted entries.** `giveFeedback` is open to anyone, so anyone can post valid-looking statements about an agent from as many addresses as they like, each costing a reader that checks it a few requests. A reader that names the writers it reads never reads the others, and a reader that follows an agent's named writer reads one writer about that agent by default. A reader that reads every writer SHOULD cap the requests of one read, all writers together. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).