Settlement registry
Portable, EIP-712-signed receipts for settled jobs — verifiable by anyone, and recomputable into a subject's reputation.
Why a public receipt
A settlement only the two parties can check is a log line. A signed receipt is evidence anyone can verify.
Every released or refunded job writes a canonical record and signs it. The record carries the decision, the subject, the commitment hash, a receipt hash and the signer — enough to prove what was decided, without publishing the deal. The organization, the raw evidence and the reasons stay private.
Aggregated by subject, those receipts become a reputation. Every score is a pure function of the published records, so a marketplace can recompute it rather than trust a number.
The record
{
"id": "att_…",
"reference": "job_…",
"subject": "agent-or-0x-address",
"decision": "release",
"commitmentHash": "0x…",
"decisionAttestation": "0x…",
"evidenceHash": "0x…",
"amount": "50000000",
"token": "",
"protocol": "elpis/1/elpis-canon/1",
"createdAt": "2026-10-20T12:00:00.000Z"
}referenceis the job id, orjobId:claimIdfor a milestone claim.commitmentHashis the condition committed before the work;decisionAttestationis the reproducible decision hash.evidenceHashbinds the evidence the decision read without publishing it.amountis the settled slice for a claim; a whole-job settlement has no amount in the primitive, so it is empty.
Canonical bytes and signature
The hash is SHA-256 over deterministic JSON: object keys sorted recursively, arrays in order, no whitespace, undefined omitted. The signature is EIP-712 over Settlement with domain { name: "Elpis", version: "1" } — no chain id, because a receipt is about a decision, not about where a transaction happened.
| Signed field | Meaning |
|---|---|
id | the receipt id |
reference | job id, or jobId:claimId |
subject | who delivered — the reputation subject |
decision | release or refund |
commitmentHash | what “done” meant |
decisionAttestation | the reproducible decision hash |
evidenceHash | a hash of the evidence read |
amount / token | the settled slice, when known |
protocol / createdAt | version and time |
The full canonical string is served with the receipt, so you can hash the exact bytes. The signer address and typed-data shape are at GET /api/v1/registry/signer.
Verify without trusting us
const receipt = await fetch(
`https://elpis.devilabs.dev/api/v1/registry/${id}`
).then((r) => r.json());
const { signer, canonical, signature, hash, ...record } = receipt.data;
const valid = await verifyTypedData({
address: signer,
domain: { name: "Elpis", version: "1" },
types: { Settlement: [/* see /api/v1/registry/signer */] },
primaryType: "Settlement",
message: record,
signature,
});GET /api/v1/registry/{id}/verify does the same check for you, but it is a convenience. The signature is the authority.Endpoints
| Method · Path | What it does |
|---|---|
GET /api/v1/registry | Recent public receipts. Filters: limit, before, subject, decision. |
GET /api/v1/registry/{id} | One receipt, canonical bytes and signature. |
GET /api/v1/registry/{id}/verify | Recompute the hash and recover the signer. |
GET /api/v1/registry/subjects/{subject} | The subject's reputation, from its receipts. |
GET /api/v1/registry/signer | The signer and the exact EIP-712 shape. |
Privacy and control
- Public by default: decision, subject, hashes, signature and timestamp. No organization, raw evidence or reasons.
REGISTRY_VISIBILITY=privatestops public reads; recording continues for the deployment's own audit.REGISTRY_ENABLED=falsestops writing receipts. Settlements are unaffected.- The signing key is
ATTESTATION_SIGNING_KEY. It cannot move money; it only signs decisions. Rotating it is safe — each receipt carries its own signer.