Trust Center
One page for a buyer or an auditor in a hurry. Every statement below is either recomputable from a public endpoint or bounded by a constant in our code. Where we cannot prove something, we say that too.
This is the most widely misunderstood distinction in the industry, and the one we are most often asked to clarify in audits. A unit belongs to a batch whose Merkle root is published on Base. That unit's Merkle leaf is built from three values, and the passport's fingerprint is not one of them.
The Merkle leaf, exactly as our code and the contract compute it
keccak256(abi.encode(tokenId, keccak256(uid), keccak256(metadata_uri)))A unit whose batch was never anchored falls back to its own mint transaction, which only proves the token exists. Our pages then read "on-chain registration", and reserve the word "anchored" for batches that really were: the scope of each reference is decided server-side, so no client has to infer it from a field name, which is how a client ends up overstating it.
Four independent links. Each one is verifiable on its own, and a failure in one does not hide the others.
The passport content is serialised to canonical JSON (sorted keys) then hashed with SHA-256. The fingerprint is stored alongside the passport and republished as-is: anyone can recompute it from the published data and compare.
On publication only the passport's public projection gets pinned to IPFS. The full record never is. The IPFS link is only surfaced publicly once it has been fetched back and checked against that projection's fingerprint. Not every passport is pinned: when the copy is missing, the API omits it rather than inventing one.
Units are grouped into batches, the batch's Merkle root is published on-chain on Base (chain id 8453), and the contract stores the root, the leaf count and a batch identity fingerprint. Each unit's inclusion proof is served publicly.
The passport is also issued as an SD-JWT-VC verifiable credential, signed with ES256 by a key belonging to the brand, generated inside AWS KMS and never exported. The matching public key is published in a did:web DID document, so the signature verifies without us.
These endpoints are public and unauthenticated. An auditor, a customer or a marketplace can call them directly.
| Endpoint | What it returns |
|---|---|
| GET /passport/{id} | The published digital product passport, filtered by ESPR access tier (public by default). |
| GET /passport/{id}/proof | The proof block: SHA-256 fingerprint, IPFS copy when pinned, on-chain reference with its scope, credential status. |
| GET /passport/{id}/verify | The integrity check: stored fingerprint against recomputed fingerprint against the IPFS copy. |
| GET /passport/{id}/vc/verify | Verification of the SD-JWT-VC signature against the brand's published key. |
| GET /verify/merkle/{id} | The Merkle inclusion proof: leaf, index, sibling nodes, anchored root and a BaseScan link. |
| GET /certificate/{id} | The public certificate of authenticity, also available as a PDF. |
| GET /resolve/{id} | The consolidated view: product, certificate, passport, events and anchor. |
| GET /01/{gtin}/21/{serial} | The GS1 Digital Link resolver: from a GTIN and a serial to the passport. |
| GET /brand/{id}/did.json | The brand's DID document (did:web): the public signing keys as JsonWebKey2020. |
Public API base: api.sealtrust.io. The same routes also exist under the /v1 prefix.
Rather than a diagram, here is a unit genuinely minted and genuinely anchored on Base mainnet. Follow every link: nothing here asks you to take our word for it.
Read this before drawing any conclusion
The token, the batch and the anchoring transaction are real. The passport content is a demonstration: the brand is a fictional workshop and the production site does not exist. This unit illustrates the proof mechanics. It is not on sale.
Chain
Base mainnet · 8453
Leaves in batch
3
Merkle root
0x64ee41e6800f397208e0f319af87bf1db25eae99a8be594b7886aa980f4374d0
Anchoring transaction
0x0e74e132bc7c281a196fe5f9c24dd6673ee00a1068a01e0dafea748978b7ab6b
Contract
0x9d55ad8b11bc883c454bdf16e9fe39b79620ce62
UID fingerprint (= token id)
0x9b724603fac23a6990fc86edda52af205c2785e85a62235632cc7e218fcb0f36
The check we invite you to redo
Call the proof endpoint: it returns this unit's leaf, its index and two sibling nodes. Hash them pairwise with keccak-256, sorted pairs, the OpenZeppelin way. You must land exactly on the root above, the one carried by the on-chain transaction. For this unit, the token id is also identical to the chip's UID fingerprint.
These are not intentions. They are the constants read by our scheduled jobs, which run continuously inside the application.
| Data | After | Treatment |
|---|---|---|
| IP addresses in scan and audit logs | 30 days | Anonymised: the last IPv4 octet is zeroed, or the last 80 bits in IPv6. The job runs twice a day. |
| User agents in logs | 30 days | Erased. |
| Scan geolocation coordinates | 90 days | Coarsened to one decimal, roughly an 11 km grid: the city survives, the precise point does not. |
| NFC authentication logs | 365 days | Deleted. |
| Unlabeled scan data (model training) | 365 days | Deleted. Only labeled rows are kept for the model, and their geolocation is coarsened. |
| Partner API key usage logs | 365 days | Deleted. |
| Administrator audit logs | 730 days | Deleted after 24 months. |
Data export (Article 15) and erasure (Article 17) are two authenticated endpoints. Erasure runs immediately, with no grace period, and propagates anonymisation across a dozen tables: account, transfers, products, wallet, scan logs, newsletter, returns, buybacks, theft reports. Live session tokens are revoked in the same pass.
Accounting records are kept for ten years under the legal obligation carved out by Article 17(3)(b). On-chain history cannot be erased by nature. The custodial wallet key is retained so the assets it holds are not orphaned. Only the link to your identity is severed. A product's lifecycle events stay attached to the product, no longer to you.
The points an audit asks about first, stated with their real parameters.
Parameters in force: time cost 3, 64 MiB of memory, parallelism 4. Legacy bcrypt digests are transparently rehashed to Argon2id on the first successful login. An unknown address triggers a dummy verification so response time does not give it away.
Every refresh mints a new token and invalidates the one just presented. If an already-consumed token comes back, that is the signature of a theft: the session is not merely refused, every token on the account is revoked at once. Access tokens last 60 minutes, refresh tokens 7 days.
The platform's blockchain signing key and each brand's credential-signing key are generated inside AWS KMS and never leave it: signing happens on the KMS side. In production the code refuses to sign when the KMS key id is missing. NFC master keys are held KMS-encrypted, never in plaintext at rest.
Five attempts per address/IP pair over a 15-minute window, plus a ceiling of 25 attempts per address across all IPs to cut off distributed attacks. TOTP two-factor is available, with single-use backup codes.
One-year HSTS with includeSubDomains and preload, a content security policy whose scripts are nonce-restricted with no 'unsafe-inline'. Inline styles are still allowed. Anti-framing protection, nosniff. Rate limiting applies per IP address to sign-in, verification and password reset. The thresholds are not published: stating them would tell an attacker the pace to stay under.
Every change passes a secret scan over the full git history. Container images are scanned for vulnerabilities on every released version, and Python and Node dependencies are audited whenever a manifest changes and nightly. Third-party actions are pinned to an exact commit.
Does a claim on this page look thin to you, or were you expecting one we did not make? That is exactly the conversation we want to have.