# GET /verify/merkle/{identifier}

Récupérer la preuve d'appartenance d'un article au lot ancré sur Base, avec sa feuille, sa preuve de voisinage et la racine inscrite sur la chaîne. Point d'entrée public.

Source : https://docs.sealtrust.io/reference/get-verify-merkle/

---

Vous récupérez la preuve qu'un article faisait partie d'un lot dont l'empreinte
a été inscrite sur la chaîne Base. En quittant cette page, vous saurez demander
cette preuve à partir de n'importe quel identifiant d'article, la recalculer
vous-même sans nous faire confiance, et distinguer un article qui n'est pas
ancré d'un échec de vérification.

Adresse complète :

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

Le même point d'entrée répond aussi sans le préfixe `/v1`, à
`https://api.sealtrust.io/verify/merkle/{identifier}`. Les deux adresses
appellent le même code. Utilisez la forme `/v1` pour une nouvelle intégration.

> [!INFO] Un 404 est le cas courant
> La plupart des articles n'appartiennent à aucun lot ancré. Pour eux, ce point
> d'entrée répond 404. Ce 404 signifie que l'article ne fait partie d'aucun lot
> ancré. Il ne dit rien sur son authenticité. La vérification d'authenticité
> d'un article se fait sur un autre point d'entrée.

## Autorisation

Aucune, point d'entrée public. Vous n'envoyez ni clef d'API, ni session, ni
en-tête d'origine. La réponse est la même pour tout le monde.

## Plafond d'appels

30 appels par tranche de 60 secondes, comptés par adresse réseau appelante.

Ce compteur est commun aux chemins qui commencent par `/verify/`, comme
`/verify/batch` ou `/verify/scan-log`. Les appels que vous adressez à l'un
d'eux entament donc le budget des autres. Le point d'entrée `/verify_any`
possède son propre budget, distinct de celui-ci.

Le préfixe `/v1` ne crée pas un second budget : `/v1/verify/merkle/1042` et
`/verify/merkle/1042` remplissent le même compteur.

Chaque réponse acceptée porte trois en-têtes.

| En-tête | Contenu |
| --- | --- |
| `X-RateLimit-Limit` | le plafond appliqué sur la fenêtre, ici `30` |
| `X-RateLimit-Remaining` | ce qu'il vous reste dans la fenêtre en cours |
| `X-RateLimit-Reset` | l'horodatage de fin de la fenêtre, en secondes |

Un refus renvoie 429, avec ces trois en-têtes et `Retry-After`. Sur ce point
d'entrée, `Retry-After` vaut la durée de la fenêtre, soit 60 secondes.

## Paramètres de chemin et de requête

| Nom | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | oui | L'article dont vous voulez la preuve. Quatre formes sont acceptées, voir ci-dessous. |

Ce point d'entrée n'a aucun paramètre de requête.

`identifier` accepte quatre formes, essayées dans cet ordre.

| Forme | Aspect | Provenance |
| --- | --- | --- |
| Empreinte d'étiquette | `0x` suivi de 64 caractères hexadécimaux | l'empreinte de l'identifiant de la puce NFC |
| Identifiant de jeton | un nombre écrit en décimal | l'identifiant de l'article sur la chaîne |
| Numéro de série imprimé | 12 caractères | ce que porte le QR code sur le produit, dans l'adresse `/p/{serial}` |
| Numéro de certificat | tel que le certificat le porte | le certificat d'authenticité de l'article |

Vous écrivez le numéro de série dans la casse que vous voulez. Nous ramenons
les caractères qui se ressemblent à une forme unique avant de chercher, donc un
`I` ou un `L` saisi à la main retrouve le `1`, et un `O` retrouve le `0`.

### En-têtes

Aucun en-tête n'est requis.

## Corps de la requête

Aucun. Cette requête n'a pas de corps.

## Requête d'exemple

Preuve d'appartenance de l'article dont l'identifiant de jeton est `1042`.

> [!INFO] Le SDK TypeScript ne couvre pas ce point d'entrée
> Aucune méthode du paquet `@sealtrust-io/sdk` n'appelle cette adresse.
> L'exemple TypeScript ci-dessous utilise `fetch`, sans dépendance.

:::onglets
```bash title="curl"
curl -i https://api.sealtrust.io/v1/verify/merkle/1042
```
```typescript
const reponse = await fetch("https://api.sealtrust.io/v1/verify/merkle/1042");

if (reponse.status === 404) {
  console.log("Cet article ne fait pas partie d'un lot ancré.");
} else if (reponse.ok) {
  const preuve = await reponse.json();
  console.log(preuve.merkle_root);
  console.log(preuve.leaf, preuve.leaf_index);
  console.log(preuve.proof);
  console.log(preuve.basescan_url);
} else {
  console.log(reponse.status, await reponse.json());
}
```
```python
import requests

response = requests.get(
    "https://api.sealtrust.io/v1/verify/merkle/1042",
    timeout=30,
)

if response.status_code == 404:
    print("Cet article ne fait pas partie d'un lot ancré.")
elif response.ok:
    preuve = response.json()
    print(preuve["merkle_root"])
    print(preuve["leaf"], preuve["leaf_index"])
    print(preuve["proof"])
    print(preuve["basescan_url"])
else:
    print(response.status_code, response.json())
```
:::

## Réponse d'exemple

Code HTTP `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"
}
```

La réponse compte quatorze champs et rien d'autre.

| Champ | Type | Description |
| --- | --- | --- |
| `anchor_batch_id` | `integer` | Le numéro sous lequel la racine du lot est inscrite dans le contrat. C'est le premier argument à passer au contrat si vous refaites la vérification sur la chaîne. |
| `merkle_root` | `string` | La racine inscrite sur la chaîne, `0x` suivi de 64 caractères hexadécimaux. C'est la valeur de référence. |
| `leaf` | `string` | L'empreinte de cet article dans l'arbre, `0x` suivi de 64 caractères hexadécimaux. |
| `leaf_index` | `integer` | La position de cette empreinte dans la liste des empreintes du lot. Le comptage démarre à 0. |
| `proof` | `string[]` | Les empreintes voisines à combiner avec `leaf`, de bas en haut, pour retrouver `merkle_root`. La liste est vide quand le lot ne contient qu'un article. |
| `anchor_tx_hash` | `string` | La transaction Base qui porte l'inscription de la racine. |
| `basescan_url` | `string` | Le lien direct vers cette transaction sur l'explorateur public de Base. |
| `contract_address` | `string` ou `null` | L'adresse du contrat qui détient la racine ancrée. C'est l'adresse à interroger si vous refaites la vérification sur la chaîne. Le modèle de réponse autorise `null`, prévoyez ce cas dans votre code. |
| `network` | `string` | Toujours `Base`. Le réseau de niveau 2 dont l'identifiant de chaîne est 8453. |
| `token_id` | `string` ou `null` | L'identifiant de l'article sur la chaîne, écrit en décimal dans une chaîne de caractères. C'est le deuxième argument à passer au contrat. |
| `verified` | `boolean` ou `null` | Le résultat de la vérification que nous avons faite pour vous sur la chaîne. Trois valeurs possibles, `true`, `false` et `null`, voir ci-dessous. |
| `lifecycle_status` | `string` ou `null` | L'état de l'article aujourd'hui. Sur ce point d'entrée, vous lisez `draft`, `minting`, `mined`, `written`, `burn_submitted`, `stolen` ou `revoked`. Les états `burned`, `superseded` et `archived` n'apparaissent jamais ici, voir l'encadré plus bas. |
| `withdrawn` | `boolean` | Sur ce point d'entrée, toujours `false`, voir l'encadré plus bas. Le champ passe à `true` quand `lifecycle_status` vaut `superseded` ou `archived`, les deux états qui sortent un article du catalogue de la marque. |
| `leaf_set` | `string` | `frozen` ou `recomputed`. D'où vient l'arbre qui a servi à produire la preuve, voir ci-dessous. |

La réponse porte aussi l'en-tête `Cache-Control: no-store, max-age=0`. Une
racine inscrite ne change plus, donc la preuve d'un article donné reste la même
d'un appel à l'autre. Vous pouvez donc la conserver de votre côté. Rien dans la
réponse n'autorise un cache partagé à la garder pour vous.

### Recalculer la racine vous-même

C'est la raison d'être de ce point d'entrée. Vous n'avez pas à nous croire sur
parole.

Vous partez de `leaf`. Pour chaque élément de `proof`, dans l'ordre, vous
mettez les deux valeurs de 32 octets côte à côte, la plus petite des deux
d'abord, puis vous appliquez keccak256 à la concaténation. Le résultat devient
la nouvelle valeur de travail. Après le dernier élément de `proof`, vous devez
obtenir exactement `merkle_root`.

C'est la convention de vérification d'OpenZeppelin. Les voisins sont triés à
chaque étage, donc la preuve n'a pas besoin d'indiquer un sens. Un lot d'un
seul article rend une liste `proof` vide : la feuille est alors la racine.

Il vous reste à vérifier que `merkle_root` est bien la valeur inscrite sur la
chaîne. Ouvrez `basescan_url` pour lire la transaction d'inscription, ou
appelez la fonction de lecture `verifyAnchored(batchId, tokenId, proof)` du
contrat à l'adresse `contract_address`, avec `anchor_batch_id`, `token_id` et
`proof`.

### Ce que veut dire `verified`

`verified` est le résultat de cette même lecture sur la chaîne, faite par nos
soins au moment de la réponse. Il prend trois valeurs, traitez-les
différemment.

`true` veut dire que le contrat a confirmé la preuve.

`false` veut dire que le contrat a répondu et a refusé la preuve. N'affichez
pas l'article comme vérifié dans ce cas. Signalez-le à la marque.

`null` veut dire que la lecture sur la chaîne n'a pas abouti, soit parce que le
nœud interrogé n'a pas répondu, soit parce que le contrat a rejeté l'appel. Un
`null` ne remet pas la preuve en cause : `leaf`, `proof` et `merkle_root`
restent vérifiables par vos propres moyens.

### Ce que veut dire `leaf_set`

`frozen` veut dire que la preuve a été produite à partir de la liste
d'empreintes enregistrée au moment même de l'inscription sur la chaîne. C'est
la valeur qui fait autorité.

`recomputed` veut dire que le lot a été ancré avant que nous n'enregistrions
cette liste, et que l'arbre a donc été reconstruit à partir des articles
actuels du lot. Une modification du lot survenue depuis l'ancrage peut le faire
diverger de la racine inscrite.

> [!ATTENTION] Un article retiré du catalogue ne se résout plus ici
> L'inscription sur la chaîne est une affirmation sur un moment passé. Elle
> reste vraie quoi qu'il arrive ensuite à l'article, et la preuve continue donc
> de se vérifier.
>
> Sur ce point d'entrée, `withdrawn` vaut toujours `false`, et
> `lifecycle_status` ne vaut jamais `superseded`, `archived` ni `burned`. La
> recherche de l'article écarte d'avance les articles détruits sur la chaîne et
> les articles sortis du catalogue de la marque : ils répondent 404. Les deux
> champs existent pour les surfaces où un article retiré reste lisible.
>
> Lisez `lifecycle_status` pour distinguer un article encore en cours de
> fabrication, déclaré volé ou révoqué.

## Erreurs

Le corps d'une réponse d'erreur porte un champ `detail`.

| Code | Condition | Que faire |
| --- | --- | --- |
| 404 | Aucun article ne correspond à cet identifiant, sous aucune des quatre formes acceptées. `detail` vaut `Product not found`. | Vérifiez l'identifiant. Un article détruit sur la chaîne, remplacé par une version ultérieure ou archivé ne se résout plus et donne cette même réponse. |
| 404 | L'article est connu, mais il n'a pas encore d'identifiant sur la chaîne, ou il n'appartient à aucun lot. `detail` vaut `No Merkle anchor for this product`. | Ne traitez pas cette réponse comme un échec. Cet article n'est pas ancré. |
| 404 | L'article appartient à un lot, mais ce lot n'a jamais été inscrit sur la chaîne. `detail` vaut `No Merkle anchor for this product`. | Ne traitez pas cette réponse comme un échec. L'ancrage d'un lot est une opération que SealTrust déclenche à la main, et la plupart des lots n'y passent pas. |
| 404 | Le lot est bien ancré, mais cet article n'a pas d'empreinte dans l'arbre ancré. `detail` vaut `Product is not part of the anchored Merkle tree`. | Aucune preuve ne peut être produite pour cet article. Contactez la marque si vous l'attendiez dans le lot. |
| 409 | Le lot a changé depuis son inscription sur la chaîne. La racine recalculée ne correspond plus à la racine inscrite. `detail` vaut `Merkle anchor is stale for this batch — re-anchoring required`. | Nous refusons de servir une preuve qui échouerait sur la chaîne. Signalez-le à la marque : le lot doit être ancré de nouveau. |
| 429 | Le plafond de 30 appels par 60 secondes est atteint pour votre adresse réseau. `detail` vaut `Rate limit exceeded: 30 requests per 60s`. | Attendez le nombre de secondes indiqué par `Retry-After`, puis réessayez. Mettez la réponse en cache de votre côté, la preuve d'un article ne change pas. |
| 500 | Une erreur inattendue s'est produite pendant le traitement de votre appel. `detail` vaut `Internal Server Error`. La réponse porte un en-tête `X-Request-Id`. | Réessayez. Si l'erreur persiste, contactez le support en indiquant la valeur de `X-Request-Id`. |

## Voir aussi

- [`GET /passport/{identifier}/proof`](/reference/get-passport-proof/),
  rassembler les preuves publiques du passeport d'un article.
- [`GET /p/{serial}`](/reference/get-p-serial/),
  traduire le numéro de série imprimé en adresse de page consommateur.
- [Confiance et preuves](/confiance-et-preuves/),
  ce que chaque preuve établit et comment un tiers refait la vérification.
