Proof-of-Funds attestation specification
A Proof-of-Funds attestation is a signed, time-stamped statement that a specific address held a specific balance on a specific finalized block or ledger, which anyone can re-verify on-chain. It is not a bank letter, not a guarantee of future funds, and not a statement about who controls the address beyond what the client signs.
What it is not
Balances borrowed inside a single transaction, including flash loans, can never qualify: the attestation requires the balance to be held across the whole observation window. The desk does not produce screenshots, "pending" transactions, or any figure that is not readable on a public ledger at a finalized block. An attestation that cannot be re-verified with the procedure in this document did not come from this desk.
What an attestation asserts
- Subject. Chain (
base,ethereum, orxrpl), the address, and the asset: native, a canonical token contract, or an XRPL currency and issuer. - Two finalized observations. The balance at a finalized block (EVM
finalizedtag) or validated ledger (XRPL), and the balance at the start of the window,window_blocksearlier. Both block hashes are recorded. - Held-for gate. The attestation is issued only if the smaller of the two balances is at least the claimed amount. A balance that exists in one block only cannot satisfy two observations.
- Control. In version 2.0.0 every envelope states
control: not_proven. It makes no claim about who controls the address. A counterparty who needs control proof should ask the holder for a signed message from the address directly. - Signature and expiry. Signed with Ed25519 by a key that exists only as a server secret; the public key is published and pinned in the repository. Every attestation expires 24 hours after issue.
Default windows: 300 blocks on Base (about 10 minutes), 50 blocks on Ethereum, 200 ledgers on XRPL. A client may request a longer window; a shorter one is refused below 10.
Envelope
{
"v": 1,
"id": "HdSo-N8u0pHClFtAIrOZpg",
"issued_at": "2026-09-04T16:00:18Z",
"expires_at": "2026-09-05T16:00:18Z",
"subject": { "chain": "base", "address": "0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB",
"asset": "USDC", "asset_contract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "decimals": 6 },
"claimed": "1000",
"observation": {
"window_blocks": 300,
"end": { "block": 50873642, "hash": "0xb8f30e7a...caef", "balance": "17961167.29152" },
"start": { "block": 50873342, "hash": "0x4d809ed6...5fcd", "balance": "18019954.283739" }
},
"held_for": { "satisfied": true, "min_balance": "17961167.29152" },
"control": "not_proven",
"issuer": "UnyKorn LLC",
"statement": "A Proof-of-Funds attestation is a signed, time-stamped statement ...",
"kid": "2026-09",
"public_key": "base64...",
"signature": "base64..."
}
This is the shape of a real attestation issued on 2026-09-04 against Base mainnet from the operator machine; the subject is the Aave aUSDC contract, chosen because its balance is public and large. Hashes are abbreviated here.
Field reference
| Field | Meaning |
|---|---|
id | 128-bit unguessable identifier, base64url. The attestation is public by id; share the id, not a key. |
issued_at, expires_at | UTC timestamps. Expiry is issue plus 24 hours. |
subject.decimals | Decimal places of the asset; all balances are decimal strings in whole asset units. |
observation.end | Balance at the finalized block or validated ledger at issue time, with its hash. |
observation.start | Balance window_blocks earlier, with its hash. |
held_for.min_balance | The smaller of the two balances; always at least claimed in an issued attestation. |
control | not_proven in version 2.0.0. |
kid | Key identifier; look it up in the published key list. |
signature | Ed25519 over the canonical serialization of every other field. |
Canonicalization and signing
Remove the signature field, serialize the remaining object with keys sorted recursively, no whitespace, UTF-8. The Ed25519 signature is over those bytes. Public keys are published at GET /v1/pof/key and pinned in the repository at api/worker/KEYS.md; old keys stay listed so old attestations remain verifiable.
Verifying without the desk
- Fetch the public key for the envelope's
kidfrom the published list or the repository pin. - Canonicalize and check the signature as above.
- Ask any node for the balance at
observation.start.hashandobservation.end.hash. Both must be at leastclaimed. - If either block hash is not finalized on the chain in question, the attestation is not one of ours.
The online verifier at https://flashrouter.io/verify/ performs steps 1 and 2 and re-reads the balance at the current finalized block, reporting signature_valid, expired, and still_held.
Issuance errors
| Status | Error | Meaning |
|---|---|---|
| 422 | held_for_not_satisfied | Balance at one of the two observations is below the claim. Not issued. The response carries both observations. |
| 400 | bad_request | Malformed chain, address, asset, claim, or window. |
| 501 | control_proof_not_implemented | A control_signature was supplied; control proof is reserved in version 2.0.0. |
| 502 | upstream_unavailable | The chain node did not answer. No cached value is returned. |
Boundaries
An attestation is a technical statement about a ledger. It is not legal, tax, or investment advice, it does not establish source of funds, and it does not satisfy any regulator's definition of anything. The desk does not attest at latest, attest a balance that exists inside one transaction, or re-issue against a shorter window to make a failing balance pass.