Pilot pack · Attestations

Supplier accreditations anyone can check, and you can withdraw.

Your company states that a supplier passed a check, an audit or a certification, until a date. The statement is an attestation on a public network: your partners and auditors verify it without asking you, and if the supplier stops meeting the bar, you revoke it and everyone sees that too.

Demo

The console, in three minutes.

Recorded from the real console on a sandbox over a fork of a public test network where nothing has value: an accreditation attested, verified, and revoked after approval.

How it works

One key, one schema, a reviewer for every withdrawal.

The system that records a passed check sends the attestation to CryptoRail. The attestation policy reads the call itself: the company's attester key may attest only under the supplier accreditation schema, so it cannot be used to state anything else. Withdrawing an accreditation waits for a reviewer. Every attestation and revocation comes back to your systems as a signed notification carrying its identifier.

See it in code ↓

flowchart TD
    BO[Your back office records a passed check] --> P{Attestation policy}
    P -->|another schema| X[Refused with the reason, nothing sent]
    P -->|the accreditation schema| S[Signed with the attester's own key]
    S --> E[Attestation on chain: identifier and evidence hash]
    E --> H[Signed webhook with its UID]
    E --> V[Anyone verifies: valid, revoked, expired]
    W[Your back office withdraws it] --> A[A reviewer approves]
    A --> R[Revocation on chain, signed webhook]
Before you attest

What is attested is public for good.

An attestation can be revoked, but it can never be erased: anyone can read it, and its history, for as long as the network exists. So the pack attests only an internal identifier of the supplier, the name of the check, and the hash of the evidence's reference, never personal data or documents. The audit report stays in your systems; whoever holds its reference can show that it matches the hash on chain.

The pack's encoder refuses anything that is not an identifier, such as a person's name or an email address.

flowchart LR
    Y[Your records: supplier file, audit report] --> K[Kept in your systems]
    Y -->|internal id, check name, hash of the report's reference| C[On chain, public for good]
    C --> Z[Anyone holding the reference can show it matches]
What the pack brings

Four parts, ready for a pilot.

The pack connects the Ethereum Attestation Service that already runs on the network; there is no contract of ours to trust.

Contracts

The real EAS

EAS and its schema registry where the network places them, checked against the chain before use. See it in code ↓

Schema

Supplier accreditation

A supplier identifier, the check passed and the evidence's hash, with an expiry and a revocation. See it in code ↓

Policy

Your key, one schema

The attester key attests nothing else, and a revocation waits for a reviewer. See it in code ↓

Verify

Valid or not

Valid, revoked, expired or unknown, read from the chain, by you or by anyone. See it in code ↓

The controls

What compliance can rely on.

A retry never attests twice, a refusal always says why, and every withdrawal is recorded with who approved what and when.

  • Declarative policies decide before anything happens: limits per operation, payer and period, held even when several workers decide at once.
  • Operations above a threshold wait for a quorum of approvers, who sign an EIP-712 digest that names neither nonce nor fee.
  • Every approval, signature and decision is recorded with who, when and what, and every change made through the API lands in an audit log.
  • Submitting the same intent twice with the same idempotency key returns the same transaction: a retry never becomes a second payment.
  • Every failure comes with a report: the stage, a stable cause, whether a retry can work and on what condition, and the suggested action.
  • Signed webhooks, retried for about three days, then held in a dead-letter queue an operator can redeliver from.
  • Read-only calls to any contract (a price, a balance, a position), decoded, from the library.
  • A ready-made attestations pilot pack: the Ethereum Attestation Service and its schema registry verified against the chain, a supplier accreditation schema that holds an identifier and the hash of the evidence and nothing personal, a policy that ties the attester's key to that schema with revocations under approval, and a one-command setup that registers the schema once.
Evidence

Proven twice: on a copy of Base Sepolia, and on Base Sepolia itself.

On every change, an end-to-end scenario runs the pack against the real EAS and schema registry on a fork of Base Sepolia at a pinned block: the schema registered once, an accreditation attested, delivered and verified, another schema refused, a revocation approved, an expiry reached. The scenario and the requirements behind it are listed on the evidence page. On 2026-10-12 the same pack ran on Base Sepolia itself; anyone can check its attestations on EAS's own explorer, without asking us.

On Base SepoliaOutcomeTransactionAttestation
The schema registered, once for goodConfirmed; a second setup found it and sent nothing0x090b3bc4…49a0e2none
A quality audit attested for a listed supplierConfirmed; delivered as a signed webhook, read as valid0x53cb71b4…7405b00xb76ba006…1cba1b
An attestation under another schemaRefused by the policy; nothing sentnonenone
That audit revoked, with a reasonHeld until the reviewer approved; then confirmed, read as revoked0xe3679115…a718de0xb76ba006…1cba1b
An ISO 9001 certificate attested, valid until 2027-10-12Confirmed; left valid for anyone to check0x421b5749…c37d360x8dcc9a14…8dbf6d

The attester's address is 0xd9d057ab…488f01; the schema is 0xd1f2429d…daf8ac. The suppliers are demo addresses nobody holds a key for.

For engineers

How it is done, in code.

The pack is a small library over CryptoRail's API: a manifest of EAS and its schema registry verified against the chain, the schema, the attestation policy for the service's rules engine, and a setup that registers everything by name. Your systems then use the typed SDK, or plain HTTP. The calls below are the ones the scenario makes on every change.

Step 1

The schema

Three fields, encoded with the same ABI rules as any contract call. The schema's UID is a hash of its text, so the policy can name it before it is registered anywhere.

import {
  SUPPLIER_ACCREDITATION_SCHEMA,
  SUPPLIER_ACCREDITATION_UID,
  encodeAccreditation,
  evidenceHash,
} from '@cryptorail/pack-attestations'

SUPPLIER_ACCREDITATION_SCHEMA
// 'string supplierId,string accreditation,bytes32 evidenceHash'
// revocable, no resolver; the expiry is the attestation's own

SUPPLIER_ACCREDITATION_UID
// keccak256(schema, resolver 0x0, revocable): the same on every chain
// '0xd1f2429d9ded9746094cc4a4b6819260aeea2b7ccb52fdc61eaffeac25daf8ac'

const data = encodeAccreditation({
  supplierId: 'SUP-00042',                 // your internal id, never a name
  accreditation: 'quality-audit-2026',
  evidenceHash: evidenceHash('AUD-2026-0042'),  // the report stays with you
})
// encodeAccreditation({ supplierId: 'Jane Doe', … }) throws:
// what is attested is public for good
Step 2

Set the pack up

Verify that the network's EAS is the one the pack knows, print the policy, and register everything on the service. The schema is read first and registered through the API only when the chain does not have it: never twice.

# Check EAS and the schema registry against the chain, at the pinned block:
# code hashes of proxy and implementation, version, every selector
node packages/pack-attestations/dist/bin.js verify --network base-sepolia

# Print the attestation policy for the service's configuration
node packages/pack-attestations/dist/bin.js policy --network base-sepolia \
  --approvers <approver id>

# Register EAS and the registry by name, the schema if the chain lacks it,
# the back office's webhook and the Attested and Revoked subscriptions
CRYPTORAIL_API_KEY=<admin key> \
node packages/pack-attestations/dist/bin.js setup --network base-sepolia \
  --url <service URL> --registrar <attester identity id> --webhook <back office URL>
# First run: schema "registered", with its transaction. Any later run: "found";
# nothing is registered twice
Step 3

The policy ties the key to the schema

The rules read inside the call: the schema is a field of the struct EAS takes, and the policy reads it by its path, request.schema. A call the rules cannot read is refused, never let through.

import { BASE_SEPOLIA, attestationPolicy } from '@cryptorail/pack-attestations'

const policy = attestationPolicy({
  version: 'accreditations-2026-10',   // recorded with every decision
  network: BASE_SEPOLIA,
  approverIds: [reviewerId],           // who lets a revocation through
})

// What it says, as the rules engine reads it:
// deny   attest, revoke   when request.schema is not ours
// deny   register         unless it is our schema, no resolver, revocable
// deny   raw calls or ether to EAS and the registry
// approve revoke          1 of the reviewers
//
// adapters: { policy: { kind: 'rules', options: policy } }
Step 4

Attest

One call, by function name, with the attester's own identity. The UID of the attestation arrives in the signed webhook of its Attested event.

import { createClient } from '@cryptorail/sdk'
import { attestRequest } from '@cryptorail/pack-attestations'

const cryptorail = createClient({ baseUrl, apiKey })

const { data } = await cryptorail.POST('/v1/transactions', {
  params: { header: { 'idempotency-key': `accreditation-${checkId}` } },
  body: {
    chainId: 84532,                         // Base Sepolia
    actor: { kind: 'identity', identityId: attesterId },  // a key of its own
    target: {
      kind: 'contract',
      contractId: easId,                    // 'pack-eas', registered by the setup
      function: 'attest',
      args: [attestRequest({
        recipient: supplierAddress,         // from your list of suppliers
        expirationTime: validUntil,         // seconds; 0n for never
        accreditation: { supplierId: 'SUP-00042',
          accreditation: 'quality-audit-2026',
          evidenceHash: evidenceHash('AUD-2026-0042') },
      })],
    },
  },
})
// The same key, another schema: refused before anything is signed
{
  "state": "REJECTED_BY_POLICY",
  "failure": {
    "stage": "policy",
    "cause": "policy.operation-denied",
    …
  }
}
// In the back office's webhook endpoint, after verifyWebhook(...) passes
for (const event of JSON.parse(rawBody).events) {
  // event.type: 'contract.event'
  // event.payload: { contractLabel: 'pack-eas',
  //   eventName: 'Attested' | 'Revoked',
  //   params: { recipient, attester, uid, schemaUID }, … }
  // params.uid is the attestation's identifier: store it with the check
}
Step 5

Verify

A read through CryptoRail, judged at a time. EAS's own validity check only says that an identifier exists, so the pack reads the record and its revocation and expiry.

import { attestationValidity } from '@cryptorail/pack-attestations'

// getAttestation(uid) on EAS through CryptoRail: a read, nothing signed
const { status, accreditation } = await attestationValidity(
  cryptorail, { contractId: easId }, uid, BigInt(Math.floor(Date.now() / 1000)),
)
// status: 'valid' | 'revoked' | 'expired' | 'unknown'
// accreditation: { supplierId, accreditation, evidenceHash }

// Anyone else checks the same record without us, on any node or on
// https://base-sepolia.easscan.org/attestation/view/<uid>
Step 6

Revoke

Withdrawing a public statement waits for a reviewer, who signs the typed data the service serves, exactly as an approver of a payment does.

Possible next steps, not part of the pack: attestations signed off chain (EIP-712), delegated attestation, and a resolver contract that checks each attestation on chain.

import { revokeRequest } from '@cryptorail/pack-attestations'

const { data } = await cryptorail.POST('/v1/transactions', {
  params: { header: { 'idempotency-key': `withdraw-${checkId}` } },
  body: {
    chainId: 84532,
    actor: { kind: 'identity', identityId: attesterId },
    target: { kind: 'contract', contractId: easId,
      function: 'revoke', args: [revokeRequest(uid)] },
  },
})
// state: PENDING_APPROVAL until a reviewer signs what the decoded call says
// (POST /v1/approvals/{transactionId}/signatures); then CONFIRMED,
// a signed Revoked webhook with the same uid, and the status reads 'revoked'
Demo

See it run, then try it on yours.

We show the pack on our sandbox, on a public test network where nothing has value. A pilot gives you your own:A pilot environment stood up in minutes with nothing installed on your side.