8. The QR code and the public page
The QR is like a booking reference: alone it says nothing, but at the right counter it shows your file.
The address
Each bottle has a web address shaped like /b/<lot>/<n>, for example /b/25B001/12. The QR is just that address drawn as small squares. Any QR library makes it, one line per bottle, starting from the site's public address.
The verification function
A view function reads data and costs the caller nothing: no wallet, no signature.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract VerificaMinima {
mapping(bytes32 => uint256) private _idPerCodice; // keccak256(codice) => id, 0 = sconosciuto
mapping(uint256 => uint32) private _nBottiglie;
mapping(uint256 => bool) private _richiamato;
function verificaBottiglia(string calldata codice, uint32 n)
external
view
returns (bool esiste, bool richiamato, uint256 id)
{
id = _idPerCodice[keccak256(bytes(codice))];
if (id == 0) return (false, false, 0);
esiste = n >= 1 && n <= _nBottiglie[id];
richiamato = _richiamato[id];
}
}Line by line
- Three helper lists: from the code (turned into a number with
keccak256) to the lot number, then bottles and recall for each lot. In the real contractcreaLotto,imbottigliaandrichiamafill them. viewmeans "read-only"; it returns three named values.- If the code is unknown the id is 0 and we answer "does not exist".
esisteis true ifnis between 1 and the lot's bottle count: this is the third check of chapter 4.richiamatodrives the red badge.
Calling it from the page
import { Contract, JsonRpcProvider } from "ethers";
const abi = ["function verificaBottiglia(string codice, uint32 n) view returns (bool esiste, bool richiamato, uint256 id)"];
const contratto = new Contract(INDIRIZZO, abi, new JsonRpcProvider(RPC_URL));
// dall'indirizzo della pagina: /b/25B001/12
const [, , codice, n] = window.location.pathname.split("/");
const [esiste, richiamato] = await contratto.verificaBottiglia(codice, Number(n));Line by line
- We import ethers, a library that talks to the chain.
- The
abiis the list of functions we want to call, in readable form. Contractjoins the contract address, the abi and a connection to the network (INDIRIZZOandRPC_URLare your own values).- From the browser address we split the lot code and the bottle number.
- We call
verificaBottigliaand receive the answers to display.
In the demo this step is not even needed in the browser: the backend answers /api/pubblico/bottiglia/<lot>/<n> with the verification already done, and the page shows the badge.
Try it yourself
With the full contract in your Hardhat project, write a test that creates a 5-bottle lot and checks that verificaBottiglia("25B001", 5) gives esiste = true and verificaBottiglia("25B001", 6) gives false.
What you learned
- The QR contains only the address /b/lot/number.
- A view function reads the chain for free and without a wallet.
- The bottle number must lie between 1 and the number of bottles filled.