Skip to content

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.

  1. Node.js and pnpm. Use the repo’s pinned versions (Node ≥ 21, pnpm from the root packageManager field). From the repo root:

    Terminal window
    pnpm install
  2. Hardhat vars — the operator secrets the CLI reads per network. Set them once with npx hardhat vars set <NAME>:

    Terminal window
    cd packages/contracts
    npx hardhat vars set ALCHEMY_API_KEY
    npx hardhat vars set SEPOLIA_PRIVATE_KEY # the deployer/operator key
    npx hardhat vars set BASESCAN_API_KEY # optional, for verification
  3. 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>
  4. An RPC endpoint and an IPFS gateway. The Alchemy key above resolves the chain RPC; any public IPFS gateway resolves ipfs:// CIDs for verification.

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.

Terminal window
npx hardhat deploy \
--contract-uri "ipfs://bafybeiCONTRACTmetadataPLACEHOLDERplaceholderplaceholder" \
--network base-sepolia

This 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.

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.

Terminal window
npx hardhat metadata:upload-template \
--template ./metadata.template.json \
--token-ids "1,2,3" \
--vars "SERIES=Founders,MATERIAL=silver" \
--network base-sepolia

The task prints the folder CID, e.g.:

bafybeiSERIESmetadataPLACEHOLDERplaceholderplaceholderplaceholder

Register the series on chain, pointing it at the metadata CID from the previous step. --fungible false gives one-of-one tokens.

Terminal window
npx hardhat series \
--deployment 1700000000 \
--cid "bafybeiSERIESmetadataPLACEHOLDERplaceholderplaceholderplaceholder" \
--fungible false \
--network base-sepolia

This emits a series id (a decimal such as 1) that the mint and register steps take as --series-id.

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.

Terminal window
npx hardhat mint \
--deployment 1700000000 \
--to 0xYourSeriesActivationAddressPLACEHOLDER00000 \
--series-id 1 \
--token-ids "1,2,3" \
--amounts "1,1,1" \
--network base-sepolia

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.

Terminal window
npx hardhat register \
--deployment 1700000000 \
--token-ids "1,2,3" \
--holders "0xHolder1PLACEHOLDER,0xHolder2PLACEHOLDER,0xHolder3PLACEHOLDER" \
--network base-sepolia

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.

The payoff: anyone can confirm each claim by its source, without trusting a website.

  1. ON-CHAIN — read uri(id) directly from your Series1155V1 proxy 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.

  2. METADATA — fetch that CID from any IPFS gateway and confirm the token’s claims match what the page displays.

  3. 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.