Etherscan finds it. Attestcoin proves it. Creditcoin never takes anyone’s word.
The pieces
What Attestcoin establishes, and what it does not
The precompile authenticates that a transaction was included in an attested Ethereum block and returns its receipt. That is all. It does not check that the transaction succeeded, and it does not interpret the receipt. Both are the application’s job, and both are done inAttestationRegistry.
For Demo Payroll the authenticated receipt contains an ERC-20 transfer, so
Attestcoin proves the payment itself. For Semuni, an off-chain payer, the receipt
contains the payer’s anchored commitment, so Attestcoin proves that the approved
payer attested to the payment. It does not prove that a bank transfer settled.
The statement records which payer the evidence came from, so a verifier can
apply its own policy to each.
On-chain: the consumer contract
contracts/src/attestcoin/USCBase.sol is the adapter around the verifier
interface. Its execute function is permissionless, because the proof is the
authorization:
- computes a query id from
(chainKey, blockHeight, txIndex)and refuses one already processed, so a source transaction is consumed once; - calls the native query verifier with the encoded transaction, Merkle root, siblings, lower endpoint digest and continuity roots;
- only if that returns true, hands the receipt to
_processAndEmitEvent.
contracts/src/creditcoin/AttestationRegistry.sol overrides
_processAndEmitEvent and adds the five checks the precompile deliberately
leaves out:
Receipts are bounded to 256 matching logs. Every surviving log records the
earliest authenticated Ethereum height in
provenAtHeight[commitment][payer],
marks acceptedByPayer[commitment][payer], and emits
CommitmentAccepted(commitment, payer, queryId, blockHeight). Acceptance is
idempotent so a repeated commitment cannot strand the others in the same
receipt. A receipt with no accepted log reverts NoTrustedLogs. Each of these
rules has a negative test in contracts/test.
Composed with the zero-knowledge proof
Attestcoin proves a commitment was anchored. A Noir proof, built in the user’s browser, shows that the amounts behind three commitments fall in one income band. The two are joined inCredentialRegistry.issue:
bytes32 is checked on both sides. That binding is the security:
requiring two unrelated facts would not be composition. The newest
provenAtHeight across the three becomes the statement’s evidenceEndHeight,
and DemoCreditPool refuses to disburse below its minimumEvidenceHeight
(EvidenceTooOld). A credit decision cannot happen without an attestation.
Off-chain: the worker
worker/src/attest.ts carries anchored payments from Ethereum to Creditcoin
with the official SDK. Discovery uses Etherscan and is trusted for nothing; a
found payment is only a candidate until the precompile accepts it.
- Scan Sepolia for
PaymentAnchoredevents with a checkpoint journal that walks ancestors on a reorg and never advances past a mismatch. - Keep only anchors whose payer is approved on the registry (an allowlist first, then a cached on-chain lookup, bounded per pass).
- Wait until Attestcoin covers
anchor block + 10(WORKER_ATTEST_LOOKAHEAD), read throughPrecompileChainInfoProviderand the proof builder’s/api/v1/attested-height/1. - Fetch the proof bundle from
proofProvider.service.ProofBuilderand submitexecutefrom the relayer key. - Read back
acceptedByPayerfor every(commitment, payer)pair before recording the anchor as done; a submitted transaction is not the same as an accepted commitment.
.github/workflows/attest-cron.yml,
oldest first, fifteen anchors per pass, because verification cost rises with
event age.
In the product
The demo faucet page reads Attestcoin’s own state so a tester can watch it work:get_latest_attestation_height_and_hash(1) from the chain-info
precompile and the proof builder’s attested height, whichever is lower, against
the block the claim needs. The four stages it shows are real: sent to Ethereum,
Ethereum confirmed, Attestcoin covered, Creditcoin accepted. Review and
statement screens read acceptedByPayer for the exact pairs, never a cache of
what was submitted.
Limits, stated plainly
- Attestcoin reads Ethereum only. Payments on other chains reach Orru only by a payer anchoring a commitment on Ethereum.
- It trails Ethereum by roughly seven minutes, and the relay adds up to five, so a fresh anchor takes ten to fifteen minutes to become provable.
- Verification cost grows with event age, about tenfold after a day. The worker processes fresh events first.
- Inclusion is not success, and an anchor is the payer’s word. The contract handles the first; the statement discloses the second by naming the payer.
- The write layer (Creditcoin to Ethereum) is not live in this window. The receiver design, an adapter between the Inbox and the application with the emitter validated against a trusted set, is written down and not depended on.
See it on-chain
One complete run, made on 12 September 2026 by a fresh wallet through the demo faucet:
The earlier Semuni batch on this page’s sibling, Two payer models,
went the same way: anchor
0xfd96eaf3…df1cbf,
attestation
0x49743077…83e5fd0,
statement
0xb5f78f3d…902d4ab.
Check an acceptance yourself, with one of that batch’s public commitments: