Getting Started
This is the golden path: one narrative you can follow end to end to stand up your own verifiable token-of-truth series — from an empty terminal to a holder who can independently verify every claim by its source.
The lifecycle mirrors the trust model: a series is a
set of tokens sharing one metadata folder on IPFS; holders are registered
against the activation contract; activation transfers possession by
signature; and provenance means every value is tagged ON-CHAIN or
METADATA so it can be checked without trusting any website.
Prerequisites
Section titled “Prerequisites”-
Node.js and pnpm. Use the repo’s pinned versions (Node ≥ 21, pnpm from the root
packageManagerfield). From the repo root:Terminal window pnpm install -
Hardhat vars — the operator secrets the CLI reads per network. Set them once with
npx hardhat vars set <NAME>:Terminal window cd packages/contractsnpx hardhat vars set ALCHEMY_API_KEYnpx hardhat vars set SEPOLIA_PRIVATE_KEY # the deployer/operator keynpx hardhat vars set BASESCAN_API_KEY # optional, for verification -
Filebase credentials — the S3-compatible gateway the metadata pipeline uploads to (it pins your folder to IPFS and returns a CID). Provide them in the environment the CLI runs in:
Terminal window export FILEBASE_KEY=<your-filebase-key>export FILEBASE_SECRET=<your-filebase-secret> -
An RPC endpoint and an IPFS gateway. The Alchemy key above resolves the chain RPC; any public IPFS gateway resolves
ipfs://CIDs for verification.
1. Deploy your own contracts
Section titled “1. Deploy your own contracts”Deploy the upgradeable Series1155V1 (behind a UUPS proxy) together with the
SeriesActivation holding contract. The deploy task takes your contract-level
metadata URI and writes a deployment record you reference by number in every
later step.
npx hardhat deploy \ --contract-uri "ipfs://bafybeiCONTRACTmetadataPLACEHOLDERplaceholderplaceholder" \ --network base-sepoliaThis prints and persists a deployment number (a timestamp id such as
1700000000) under deployments/<chainId>/<id>/info.json, holding your proxy
and activation addresses. Use that number as --deployment from here on.
2. Prepare and upload metadata to IPFS
Section titled “2. Prepare and upload metadata to IPFS”Each token’s claims live in a metadata JSON file; the whole series shares one folder, and the folder’s CID is the anchor everything else points at. Upload the folder to IPFS (via Filebase) and record the returned CID — the on-chain series will point at exactly this value.
npx hardhat metadata:upload-template \ --template ./metadata.template.json \ --token-ids "1,2,3" \ --vars "SERIES=Founders,MATERIAL=silver" \ --network base-sepoliaThe task prints the folder CID, e.g.:
bafybeiSERIESmetadataPLACEHOLDERplaceholderplaceholderplaceholder3. Create the series
Section titled “3. Create the series”Register the series on chain, pointing it at the metadata CID from the previous
step. --fungible false gives one-of-one tokens.
npx hardhat series \ --deployment 1700000000 \ --cid "bafybeiSERIESmetadataPLACEHOLDERplaceholderplaceholderplaceholder" \ --fungible false \ --network base-sepoliaThis emits a series id (a decimal such as 1) that the mint and register
steps take as --series-id.
4. Mint tokens
Section titled “4. Mint tokens”Mint the token ids of the series into the SeriesActivation contract, which
holds them until each is claimed. For a contiguous range use mass-mint; for an
explicit list use mint.
npx hardhat mint \ --deployment 1700000000 \ --to 0xYourSeriesActivationAddressPLACEHOLDER00000 \ --series-id 1 \ --token-ids "1,2,3" \ --amounts "1,1,1" \ --network base-sepolia5. Register holders
Section titled “5. Register holders”Bind each token id to the ephemeral holder address encoded in its QR token, so
SeriesActivation knows who may claim it. register takes explicit lists;
mass-register reads a folder of holder JSON files ({ index, secret, privateKey, address, url }) and mints in one atomic pass.
npx hardhat register \ --deployment 1700000000 \ --token-ids "1,2,3" \ --holders "0xHolder1PLACEHOLDER,0xHolder2PLACEHOLDER,0xHolder3PLACEHOLDER" \ --network base-sepolia6. Activate
Section titled “6. Activate”Activation is the moment a recipient takes possession. The holder key
embedded in the QR token signs the digest
keccak256(abi.encodePacked(timestamp, activator)) as an EIP-191 message, where
activator is the wallet claiming the token. That signature and the timestamp
are submitted to SeriesActivation.activate(timestamp, signature), which
recovers the signer, checks it against the registered holder, and transfers the
token from the holding contract to the activator.
In practice this happens end to end in the activation dApp (apps/activation):
the recipient scans the QR token, the app reconstructs and signs the message,
and their wallet sends the activate transaction. activate reverts on a stale
timestamp, a future timestamp, an already-activated token, or a signature that
doesn’t recover to the registered holder — the guarantees that make possession
trustworthy.
7. Verify
Section titled “7. Verify”The payoff: anyone can confirm each claim by its source, without trusting a website.
-
ON-CHAIN— readuri(id)directly from yourSeries1155V1proxy and confirm it resolves to the metadata CID you recorded in step 2. The contract, read at a pinned block, is the source of truth. -
METADATA— fetch that CID from any IPFS gateway and confirm the token’s claims match what the page displays. -
Cross-check. Because the on-chain
uri(id)and the IPFS content are independent, a holder can verify one against the other — provenance holds even if the website lies.
That’s the whole path: deploy → metadata/IPFS → series → mint → register → activate → verify, all against a deployment you control.