Method GET/verify/merkle/{identifier}

Retrieve the proof that an item belongs to the batch anchored on Base, with its leaf, its neighbor proof and the root written to the chain. Public endpoint.

On this page

You retrieve the proof that an item was part of a batch whose hash was written to the Base chain. When you leave this page, you will know how to ask for that proof from any item identifier, how to recompute it yourself without trusting us, and how to tell an item that is not anchored apart from a verification failure.

Full address:

HTTP
GET https://api.sealtrust.io/v1/verify/merkle/{identifier}

The same endpoint also answers without the /v1 prefix, at https://api.sealtrust.io/verify/merkle/{identifier}. Both addresses call the same code. Use the /v1 form for a new integration.

#Authorization

None, public endpoint. You send no API key, no session and no origin header. The response is the same for everyone.

#Rate limit

30 calls per 60-second window, counted per calling network address.

This counter is shared by the paths that start with /verify/, such as /verify/batch or /verify/scan-log. The calls you address to one of them therefore eat into the budget of the others. The /verify_any endpoint has its own budget, distinct from this one.

The /v1 prefix does not create a second budget: /v1/verify/merkle/1042 and /verify/merkle/1042 fill the same counter.

Every accepted response carries three headers.

HeaderContent
X-RateLimit-Limitthe limit applied over the window, here 30
X-RateLimit-Remainingwhat is left to you in the current window
X-RateLimit-Resetthe timestamp for the end of the window, in seconds

A refusal returns 429, with these three headers and Retry-After. On this endpoint, Retry-After equals the duration of the window, that is 60 seconds.

#Path and query parameters

NameTypeRequiredDescription
identifierstringyesThe item you want the proof for. Four forms are accepted, see below.

This endpoint has no query parameter.

identifier accepts four forms, tried in this order.

FormAppearanceWhere it comes from
Label hash0x followed by 64 hexadecimal charactersthe hash of the identifier of the NFC chip
Token ida number written in decimalthe identifier of the item on the chain
Printed serial number12 characterswhat the QR code on the product carries, in the /p/{serial} address
Certificate numberas the certificate carries itthe certificate of authenticity of the item

You write the serial number in whatever case you want. We bring the characters that look alike back to a single form before searching, so an I or an L typed by hand finds the 1, and an O finds the 0.

#Headers

No header is required.

#Request body

None. This request has no body.

#Example request

Membership proof of the item whose token id is 1042.

curl -i https://api.sealtrust.io/v1/verify/merkle/1042

#Example response

HTTP code 200.

JSON
{
  "anchor_batch_id": 118,
  "merkle_root": "0x1111111111111111111111111111111111111111111111111111111111111111",
  "leaf": "0x2222222222222222222222222222222222222222222222222222222222222222",
  "leaf_index": 3,
  "proof": [
    "0x3333333333333333333333333333333333333333333333333333333333333333",
    "0x4444444444444444444444444444444444444444444444444444444444444444"
  ],
  "anchor_tx_hash": "0x5555555555555555555555555555555555555555555555555555555555555555",
  "basescan_url": "https://basescan.org/tx/0x5555555555555555555555555555555555555555555555555555555555555555",
  "contract_address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "network": "Base",
  "token_id": "1042",
  "verified": true,
  "lifecycle_status": "mined",
  "withdrawn": false,
  "leaf_set": "frozen"
}

The response has fourteen fields and nothing else.

FieldTypeDescription
anchor_batch_idintegerThe number under which the root of the batch is written in the contract. It is the first argument to pass to the contract if you redo the verification on the chain.
merkle_rootstringThe root written to the chain, 0x followed by 64 hexadecimal characters. It is the reference value.
leafstringThe hash of this item in the tree, 0x followed by 64 hexadecimal characters.
leaf_indexintegerThe position of this hash in the list of the hashes of the batch. Counting starts at 0.
proofstring[]The neighboring hashes to combine with leaf, from the bottom up, to obtain merkle_root. The list is empty when the batch contains only one item.
anchor_tx_hashstringThe Base transaction that carries the write of the root.
basescan_urlstringThe direct link to that transaction on the public explorer of Base.
contract_addressstring or nullThe address of the contract that holds the anchored root. It is the address to query if you redo the verification on the chain. The response model allows null, plan for that case in your code.
networkstringAlways Base. The layer 2 network whose chain identifier is 8453.
token_idstring or nullThe identifier of the item on the chain, written in decimal inside a character string. It is the second argument to pass to the contract.
verifiedboolean or nullThe result of the verification we made for you on the chain. Three possible values, true, false and null, see below.
lifecycle_statusstring or nullThe status of the item today. On this endpoint, you read draft, minting, mined, written, burn_submitted, stolen or revoked. The burned, superseded and archived statuses never appear here, see the callout below.
withdrawnbooleanOn this endpoint, always false, see the callout below. The field moves to true when lifecycle_status is superseded or archived, the two statuses that take an item out of the brand's catalog.
leaf_setstringfrozen or recomputed. Where the tree that produced the proof comes from, see below.

The response also carries the Cache-Control: no-store, max-age=0 header. A root that has been written no longer changes, so the proof of a given item stays the same from one call to the next. You can therefore keep it on your side. Nothing in the response authorizes a shared cache to keep it for you.

#Recomputing the root yourself

This is the reason this endpoint exists. You do not have to take our word for it.

You start from leaf. For each element of proof, in order, you place the two 32-byte values side by side, the smaller of the two first, then you apply keccak256 to the concatenation. The result becomes the new working value. After the last element of proof, you must obtain exactly merkle_root.

This is the OpenZeppelin verification convention. The neighbors are sorted at each level, so the proof does not need to indicate a direction. A batch with a single item returns an empty proof list: the leaf is then the root.

It remains for you to check that merkle_root really is the value written to the chain. Open basescan_url to read the write transaction, or call the read function verifyAnchored(batchId, tokenId, proof) of the contract at the address contract_address, with anchor_batch_id, token_id and proof.

#What verified means

verified is the result of that same read on the chain, made by us at the moment of the response. It takes three values, treat them differently.

true means that the contract confirmed the proof.

false means that the contract answered and refused the proof. Do not display the item as verified in that case. Report it to the brand.

null means that the read on the chain did not succeed, either because the node queried did not answer, or because the contract rejected the call. A null does not call the proof into question: leaf, proof and merkle_root stay verifiable by your own means.

#What leaf_set means

frozen means that the proof was produced from the list of hashes recorded at the very moment of the write to the chain. It is the value that is authoritative.

recomputed means that the batch was anchored before we recorded that list, and that the tree was therefore rebuilt from the current items of the batch. A change to the batch since the anchoring can make it diverge from the written root.

#Errors

The body of an error response carries a detail field.

CodeConditionWhat to do
404No item matches this identifier, under any of the four accepted forms. detail is Product not found.Check the identifier. An item destroyed on the chain, replaced by a later version or archived no longer resolves and gives this same response.
404The item is known, but it does not have an identifier on the chain yet, or it belongs to no batch. detail is No Merkle anchor for this product.Do not treat this response as a failure. This item is not anchored.
404The item belongs to a batch, but that batch has never been written to the chain. detail is No Merkle anchor for this product.Do not treat this response as a failure. Anchoring a batch is an operation that SealTrust triggers by hand, and most batches do not go through it.
404The batch is indeed anchored, but this item has no hash in the anchored tree. detail is Product is not part of the anchored Merkle tree.No proof can be produced for this item. Contact the brand if you expected it in the batch.
409The batch has changed since it was written to the chain. The recomputed root no longer matches the written root. detail is Merkle anchor is stale for this batch — re-anchoring required.We refuse to serve a proof that would fail on the chain. Report it to the brand: the batch must be anchored again.
429The limit of 30 calls per 60 seconds is reached for your network address. detail is Rate limit exceeded: 30 requests per 60s.Wait the number of seconds indicated by Retry-After, then try again. Cache the response on your side, the proof of an item does not change.
500An unexpected error occurred while your call was being processed. detail is Internal Server Error. The response carries an X-Request-Id header.Try again. If the error persists, contact support giving the value of X-Request-Id.

#See also

Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.

Suggest a correctionReport a problem