BrandAttestation is the on-chain receipt that proves a brand slug
on droplinked has been operator-reviewed and is who it says it is. This page
walks the full lifecycle — request → operator approval → EAS Schema A mint →
merchant polling → public verifier read — across the three audiences that
touch it: merchants requesting from the shop builder, AI agents requesting on
behalf of a merchant via MCP, and third-party verifiers reading the resulting
on-chain attestation.
Schema A is currently anchored on Base Sepolia testnet. Mainnet flip is
gated on the KMS-backed signer migration. Every endpoint below returns the
active chain in its response so consumers can verify on the right
easscan domain.
When to use this guide
- Merchants — you clicked Request brand attestation in the shop builder and want to know what happens next.
- AI agents — you’re orchestrating a merchant-onboarding flow via the droplinked MCP server and need to drive the same lifecycle programmatically.
- Third-party verifiers (lenders, partners, regulators) — you need to resolve a brand attestation UID to chain-anchored facts without trusting droplinked.
The state machine
A brand-attestation request walks five discriminator values. Four are real queue-row states; the fifth (NONE) is a synthetic state the status endpoint
returns when no row exists yet, so the widget can render the initial CTA
without 404-handling.
NONE — no request exists
NONE — no request exists
No row in the queue for this
shopSlug. The status endpoint returns
{ "status": "NONE", "request": null } and always 200. UIs render the
Request brand attestation CTA in this state.PENDING — awaiting operator review
PENDING — awaiting operator review
Merchant has clicked the CTA. A queue row is in
SUPER_ADMIN review. Re-clicking the CTA is idempotent — the API returns
the same
requestId instead of duplicating the row. UIs render
“Pending operator review — we’ll email you when it ships”.APPROVED — mint orchestrator engaged
APPROVED — mint orchestrator engaged
Operator has approved the row. The mint orchestrator will attempt a
Schema A on-chain mint. If the mint fails (RPC drop, gas spike,
wallet-nonce contention) the row stays APPROVED;
mintError +
mintAttempts persist on the operator-side row for retry. The row is
never auto-flipped back to PENDING or REJECTED.MINTED — on-chain attestation live
MINTED — on-chain attestation live
Schema A attestation is on-chain.
attestationUid + mintedAt are
populated on the public projection. The widget can quote the easscan
reference without a second round-trip. This is a terminal state — the
row is never re-minted.REJECTED — operator declined
REJECTED — operator declined
Operator declined the row with a reason (visible only on the
SUPER_ADMIN console — never on the public projection). Terminal state.
The merchant may submit a fresh
PENDING row at any time; the
partial-unique index on (shopSlug, status: PENDING) does NOT block
re-submits after a terminal state.Step 1 — Request the attestation
There are three equivalent paths to queue a request. They all hit the same backend endpoint and the same idempotency check.Shop builder UI (merchant path)
From your merchant dashboard, open the Trust Fabric card and click
Request brand attestation. Optionally add a free-text note (max
2048 chars — surfaces only to the operator). The card immediately
flips to “Pending operator review”.No further action is required from the merchant. The status card polls
GET /v2/attestations/brand/:shopSlug/request-status every 60s and
auto-advances through PENDING → APPROVED → MINTED (or → REJECTED).MCP `request_brand_attestation` tool (agent path)
An AI agent orchestrating onboarding calls the MCP tool wired against
the droplinked MCP server.See Brand attestation MCP tools
for the full envelope.
Direct POST (custom integration path)
If you’re driving the flow from your own backend or a third-party
portal, hit the endpoint directly. No JWT, no IP-allowlist — the
endpoint is Idempotent on
@Public().(shopSlug, status: PENDING). Re-submitting while a
PENDING row exists returns the same requestId — never a 4xx.Fail-open by design. The endpoint hard-casts the response
status to
PENDING regardless of whether the row is new or re-found by the
idempotency check. UIs should never have to disambiguate “first submit” vs
“re-submit” — the discriminator alone drives the success toast.Step 2 — Poll the status
Once a request is queued, walk the discriminator with the status endpoint. The widget polls this; an agent can long-poll it; a third-party portal can display it.PENDING row wins (there is at most one per slug by the partial
index); otherwise the newest terminal row (createdAt desc) is returned.
Response shapes per state
- NONE
- PENDING
- APPROVED
- MINTED
- REJECTED
Operator-private fields stay private.
decidedBy, decisionReason,
mintError, mintAttempts, merchantId, and the merchant’s free-text
notes are scrubbed from the public projection. They only surface on the
SUPER_ADMIN admin route. The shape above is the full public surface.Polling cadence
- Merchant widget: 60s. The state walks
PENDING → APPROVED → MINTEDover operator-review timelines (minutes to hours); a 60s poll cadence feels live to the merchant without hammering the endpoint. - AI agent: prefer event-driven UX. If you can park the conversation and ping the merchant when state advances, do that. Otherwise long-poll at 30s intervals for up to 5 minutes — beyond that, hand the lifecycle back to the merchant.
- Third-party verifier: don’t poll. Read once at decision time and
cache the
attestationUid— the EAS attestation is immutable once minted.
Step 3 — See the mint land on-chain
Oncestatus === "MINTED", the attestationUid resolves to a Schema A
attestation on the Ethereum Attestation Service. The easscan link scheme:
- The Schema A definition (the
BrandAttestationschema UID) - The issuer wallet — droplinked’s operator signer
- The recipient — the merchant’s on-chain address
- The decoded payload —
brandSlug,displayName,issuedAt,expiresAt, and the registry pointers - A timeline of any subsequent revocations (Schema A revocations are rare and operator-driven; the reconciler never auto-revokes)
Step 4 — For AI agents
Two MCP tools cover the full agent-side lifecycle, both published by the droplinked MCP server (mcp.droplinked.com):
request_brand_attestation
get_brand_attestation_status
MINTED or
REJECTED) before handing back to a human. The five-state discriminator
maps cleanly to a switch statement — same shape as the public HTTP
endpoint, no envelope translation.
A typical agent sequence:
- Call
request_brand_attestationonce at onboarding time. - Call
get_brand_attestation_statuson a backoff (30s → 60s → 120s) untilstatusisMINTEDorREJECTED. - On
MINTED, callverify_brand_attestation(Schema A read) to confirm the on-chain envelope decodes cleanly. Then hand the merchant the easscan link from Step 3.
Step 5 — For third-party verifiers
A third-party verifier (lender, partner, regulator) does not poll the request lifecycle. They consume the terminal artifact: the on-chain Schema A attestation referenced byattestationUid.
The two canonical reads:
easscan UI (human verifier)
Openhttps://base-sepolia.easscan.org/attestation/view/<UID> in a
browser. Decoded payload + issuer wallet + timeline are visible without
running any infrastructure.
Droplinked public read (programmatic verifier)
Public verifier reads return only chain-anchored claims + aggregate
identifiers — never the operator-side
notes, decisionReason,
decidedBy, or PII. Verifier-side policy decides whether the chain
artifact is sufficient for the verifier’s risk threshold; droplinked
never blocks reads on its own opinion of “good standing”.Common errors
| Symptom | Cause | Fix |
|---|---|---|
POST returns 404 | shopSlug not found via ShopService | Confirm the slug matches the kebab-case URL slug, not the human-display name. |
POST returns 400 “notes too long” | notes exceeded 2048 chars | Trim the note; only the operator sees it anyway. |
GET request-status returns NONE after a successful POST | You’re hitting a stale CDN cache (rare) or the wrong slug | Confirm the slug. The endpoints are not edge-cached at the application layer. |
Status stuck at APPROVED for >24h | Mint orchestrator hit a persistent RPC error | The row is recoverable — operator-side mintError + mintAttempts drive the retry. Email support@droplinked.com with the requestId if it persists. |
MINTED with a UID that doesn’t resolve on easscan | Wrong chain — you’re on the wrong easscan domain | Use base-sepolia.easscan.org (testnet) until mainnet flip. The active chain is returned in the chain-anchored read at /v2/attestations/brand/:slug. |
Repeated POST returns the same requestId | This is expected — the endpoint is idempotent on (shopSlug, status: PENDING) | Treat as success. |
Related
Trust Fabric (EAS Schema v2)
The 4-axis architecture context for Schema A and its sibling schemas.
Lender Trinity MCP Tools
Full catalog of MCP tools including
request_brand_attestation and get_brand_attestation_status.Brand attestation API reference
Endpoint-level reference for
POST /request + GET /request-status.Embed Trust-Fabric Widget
Drop-in widget for surfacing brand-attestation status on your own dashboard.