---
title: Activate a SmartMetal
description: Activation is one transaction on Base, activate(timestamp, signature) on SeriesActivation, in which the key from the token's QR code signs the activator's address and a timestamp no more than an hour old, and the contract transfers the token from itself to the activator's wallet and emits TokenActivated.
url: https://veri.how/smartmetal/activate
area: smartmetal
kind: howto
updated: 2026-10-15T00:00:00Z
author: Patryk Dwórznik
license: CC BY 4.0
contracts:
  - id: eip155:8453:0x412e962bbae0bb97ce98e74f3424543651a21ce3
    name: SeriesActivation
    verified:
      date: 2026-10-13
sources:
  - title: SeriesActivation, verified source on BaseScan
    url: https://basescan.org/address/0x412e962bbae0bb97ce98e74f3424543651a21ce3#code
  - title: QRToken contract reference, SeriesActivation
    url: https://docs.qrtoken.xyz/reference/contracts/seriesactivation/
  - title: QRToken getting started, Activate
    url: https://docs.qrtoken.xyz/getting-started/#6-activate
  - title: Activation of token 2259 on 25 Aug 2026, BaseScan
    url: https://basescan.org/tx/0x09cebf3a9f8c278722d10849ede67deada5712753f39f1319c19ebd9c590a7ba
  - title: "EIP-191: Signed Data Standard"
    url: https://eips.ethereum.org/EIPS/eip-191
---

# Activate a SmartMetal

Activation is one transaction on Base, activate(timestamp, signature) on SeriesActivation, in which the key from the token's QR code signs the activator's address and a timestamp no more than an hour old, and the contract transfers the token from itself to the activator's wallet and emits TokenActivated.

## Facts

| Fact | Value |
| --- | --- |
| Address | [0x412E962bbAE0bB97CE98e74f3424543651a21ce3](https://basescan.org/address/0x412e962bbae0bb97ce98e74f3424543651a21ce3) |
| Chain | [Base (eip155:8453)](https://basescan.org) |
| Deployed | [Block 30,569,436 · 22 May 2025](https://basescan.org/tx/0x9c8f01b4f78f24f948e0f77ada064817637e548bbe9b8ae9ef010cbd8f1dcafa) |
| First activation | [Block 30,576,415 · 22 May 2025](https://basescan.org/tx/0x90aebc8390cc4b4ba96b0c7c4199353b577c4b4bc0b5f4e45e6d0575f406d754) |
| Owner | [0x73196fa74d6061e02648078246a1fE4D93a7AC2f](https://basescan.org/address/0x73196fa74d6061e02648078246a1fe4d93a7ac2f) |
| Source | [Verified on BaseScan](https://basescan.org/address/0x412e962bbae0bb97ce98e74f3424543651a21ce3#code) |
| Transactions | [All transactions](https://basescan.org/address/0x412e962bbae0bb97ce98e74f3424543651a21ce3) |
| ABI | [smartmetal-activation.json](https://veri.how/abi/smartmetal-activation.json) |
| Last verified | 13 Oct 2026 |

Every SmartMetal token is minted to `SeriesActivation`, 0x412E962bbAE0bB97CE98e74f3424543651a21ce3, and stays there until it is activated. Activation is how the token reaches the wallet of whoever holds the physical piece. This article covers what happens on chain, which is the same whichever app sends the transaction. It does not cover the QR code itself.

## What you need

- **The key from the piece's QR code.** The QR code carries a private key, the holder key. Its address was registered with `SeriesActivation` before the token was minted (`tokenHolders(id)` returns it), and it can sign for that one token only.
- **A wallet on Base** with a little ETH for gas. This wallet, the activator, sends the transaction and receives the token. In the one example below, activation used 94,541 gas.
- **A token that is still waiting.** `tokenStatus(id)` must return `0`; see [Look up a SmartMetal token](https://veri.how/smartmetal/lookup.md#3-check-whether-it-is-activated).

## 1. Sign the message with the holder key

The holder key signs the current Unix timestamp and the activator's address, packed together, as an [EIP-191](https://eips.ethereum.org/EIPS/eip-191) personal message:

```
message = keccak256(abi.encodePacked(timestamp, activator))   // uint256, then address
signature = sign("\x19Ethereum Signed Message:\n32" ‖ message) // 65 bytes: r, s, v
```

This is the format the [QRToken documentation](https://docs.qrtoken.xyz/getting-started/#6-activate) gives; the signature must recover to the registered holder, so any other message fails. Because the activator's address is inside the signed message, a signature only works for the wallet it was made for, and because the timestamp is inside it, it only works for an hour.

## 2. Send activate from the activator wallet

Call [`activate(uint256 timestamp, bytes signature)`](https://docs.qrtoken.xyz/reference/contracts/seriesactivation/#activate), selector `0x5314e9fa`, on 0x412E962bbAE0bB97CE98e74f3424543651a21ce3 from the activator wallet. No token id is passed: the contract recovers the signer from the signature and looks the token up with `holderTokenIds(signer)`, the reverse of `tokenHolders`. Anyone may call the function; what it checks is the signature, not the caller.

## 3. What the contract checks

The errors `activate` declares for these checks, from the ABI and the [QRToken reference](https://docs.qrtoken.xyz/reference/contracts/seriesactivation/); the order they are checked in is in the [verified source](https://basescan.org/address/0x412e962bbae0bb97ce98e74f3424543651a21ce3#code):

| Error                       | When                                                                             |
| --------------------------- | -------------------------------------------------------------------------------- |
| `FutureTimestampNotAllowed` | declared for a timestamp later than the block's time; see the note below         |
| `TimestampTooOld`           | the timestamp is more than `maxTimestampAge` seconds old, 3,600 since deployment |
| `InvalidSignature`          | the signature is malformed or does not recover to a registered holder            |
| `HolderNotRegistered`       | the recovered signer is not registered for any token                             |
| `TokenAlreadyActivated`     | that holder's token was activated before                                         |

A timestamp even one second ahead of the block does not reach `FutureTimestampNotAllowed` in the deployed contract: a dry run on 15 Oct 2026 reverted with an arithmetic panic (`Panic(0x11)`) instead. Take the timestamp from the chain, not from your computer's clock, as the example below does.

The checks need no backend, oracle or allow-list: the only inputs are the timestamp and the signature, and the only state is what was registered at mint.

## 4. What happens

When the checks pass, in one transaction:

1. `SeriesActivation` marks the token activated (`activatedTokens(id)` becomes `true`, `tokenStatus(id)` becomes `2`) and clears `tokenHolders(id)` to the zero address, so the holder key cannot be used again.
2. It transfers the token to the activator: the token contract emits `TransferSingle(operator, from, to, id, 1)` with `from` and `operator` both `SeriesActivation` and `to` the activator.
3. It emits `TokenActivated(tokenId, holder, activator)`.

From then on the token is an ordinary ERC-1155 token in the activator's wallet, transferable like any other.

## Activate with cast

[Foundry](https://www.getfoundry.sh/introduction/installation)'s `cast` can sign and send the activation from a terminal. `HOLDER_KEY` is the key from the QR code and `ACTIVATOR_KEY` the key of the wallet that receives the token:

```sh
export ETH_RPC_URL=https://mainnet.base.org
ACTIVATOR=$(cast wallet address $ACTIVATOR_KEY)

# The token this holder key activates, and its status: 0 means it is waiting
ID=$(cast call 0x412E962bbAE0bB97CE98e74f3424543651a21ce3 'holderTokenIds(address)(uint256)' $(cast wallet address $HOLDER_KEY))
cast call 0x412E962bbAE0bB97CE98e74f3424543651a21ce3 'tokenStatus(uint256)(uint8)' $ID

# 1. Sign keccak256(abi.encodePacked(timestamp, activator)) with the holder key
TS=$(cast block latest -f timestamp)
MESSAGE=$(cast keccak $(cast abi-encode --packed 'f(uint256,address)' $TS $ACTIVATOR))
SIGNATURE=$(cast wallet sign --private-key $HOLDER_KEY $MESSAGE)

# 2. Dry run: a revert here means the send would fail too
cast call 0x412E962bbAE0bB97CE98e74f3424543651a21ce3 'activate(uint256,bytes)' $TS $SIGNATURE --from $ACTIVATOR

# 3. Send it
cast send 0x412E962bbAE0bB97CE98e74f3424543651a21ce3 'activate(uint256,bytes)' $TS $SIGNATURE --private-key $ACTIVATOR_KEY
```

`cast wallet sign` treats a message that starts with `0x` as bytes and adds the EIP-191 prefix itself, which is the format `activate` expects. Use `--interactive` in place of `--private-key` to type a key at a prompt and keep it out of your shell history. Send within the hour, or `TimestampTooOld` rejects the signature and you sign again.

The same commands check the activation below, reading everything from the transaction: its message, built from its timestamp and sender, recovers to the holder in its `TokenActivated` log.

```sh
TX=0x09cebf3a9f8c278722d10849ede67deada5712753f39f1319c19ebd9c590a7ba
ARGS=$(cast decode-calldata 'activate(uint256,bytes)' $(cast tx $TX input))
TS=$(echo "$ARGS" | head -1 | cut -d' ' -f1)
SIGNATURE=$(echo "$ARGS" | tail -1)
HOLDER=$(cast parse-bytes32-address $(cast receipt $TX --json | jq -r '.logs[1].topics[2]'))
MESSAGE=$(cast keccak $(cast abi-encode --packed 'f(uint256,address)' $TS $(cast tx $TX from)))

# Validation succeeded. Address 0x42eF…a428 signed this message.
cast wallet verify --address $HOLDER $MESSAGE $SIGNATURE
```

## One activation on record

Token 2259 was activated on 25 Aug 2026 at 19:13:35 UTC in block [50,447,934](https://basescan.org/block/50447934) ([transaction](https://basescan.org/tx/0x09cebf3a9f8c278722d10849ede67deada5712753f39f1319c19ebd9c590a7ba)). The sender, 0x5685…4708, called `activate` with a timestamp of 1,787,685,205 (19:13:25 UTC, ten seconds before the block) and a 65-byte signature. The receipt has two logs:

- `TransferSingle` on the token contract: token 2259, amount 1, from 0x412E962bbAE0bB97CE98e74f3424543651a21ce3 to 0x5685…4708.
- `TokenActivated(2259, 0x42eF…a428, 0x5685…4708)` on `SeriesActivation`: the registered holder, then the activator.

`tokenStatus(2259)` has returned `2` since, and `tokenHolders(2259)` all zeros. The first activation of any SmartMetal came the day the contracts were deployed, 22 May 2025, in block [30,576,415](https://basescan.org/block/30576415) ([transaction](https://basescan.org/tx/0x90aebc8390cc4b4ba96b0c7c4199353b577c4b4bc0b5f4e45e6d0575f406d754), token 427).

## Check it yourself

```sh
# tokenStatus(2259): 0x…02, activated
curl -s https://mainnet.base.org -X POST -H 'content-type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x412e962bbae0bb97ce98e74f3424543651a21ce3","data":"0xb01ce31f00000000000000000000000000000000000000000000000000000000000008d3"},"latest"]}'

# maxTimestampAge(): 0x…e10, which is 3600 seconds
curl -s https://mainnet.base.org -X POST -H 'content-type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x412e962bbae0bb97ce98e74f3424543651a21ce3","data":"0x0dee2789"},"latest"]}'

# The receipt of the activation above: two logs, "status": "0x1", "gasUsed": "0x1714d" (94,541)
curl -s https://mainnet.base.org -X POST -H 'content-type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["0x09cebf3a9f8c278722d10849ede67deada5712753f39f1319c19ebd9c590a7ba"]}'
```

With [Foundry](https://www.getfoundry.sh/introduction/installation) installed, its `cast` command runs the same checks and decodes the answers:

```sh
export ETH_RPC_URL=https://mainnet.base.org

# 2
cast call 0x412e962bbae0bb97ce98e74f3424543651a21ce3 'tokenStatus(uint256)(uint8)' 2259

# 3600
cast call 0x412e962bbae0bb97ce98e74f3424543651a21ce3 'maxTimestampAge()(uint256)'

# The receipt: status 1 (success), gasUsed 94541, two logs
cast receipt 0x09cebf3a9f8c278722d10849ede67deada5712753f39f1319c19ebd9c590a7ba
```

`https://base-rpc.publicnode.com` answers the same calls if the first endpoint is busy. The [ABI](https://veri.how/abi/smartmetal-activation.json) decodes `activate`, the errors and both events. The indexed state of any token, including its activation time and transaction, is at `https://veri.how/smartmetal/tokens/<id>.json` ([data files](https://veri.how/data.md)).

## Sources

1. [SeriesActivation, verified source on BaseScan](https://basescan.org/address/0x412e962bbae0bb97ce98e74f3424543651a21ce3#code)
2. [QRToken contract reference, SeriesActivation](https://docs.qrtoken.xyz/reference/contracts/seriesactivation/)
3. [QRToken getting started, Activate](https://docs.qrtoken.xyz/getting-started/#6-activate)
4. [Activation of token 2259 on 25 Aug 2026, BaseScan](https://basescan.org/tx/0x09cebf3a9f8c278722d10849ede67deada5712753f39f1319c19ebd9c590a7ba)
5. [EIP-191: Signed Data Standard](https://eips.ethereum.org/EIPS/eip-191)

## Related

- [Veri SmartMetal contracts on Base](https://veri.how/smartmetal/contracts.md): Veri SmartMetal is an ERC-1155 token at 0x4516a5D613C30A36D157d3b579813734cbb929a4 on Base, an ERC-1967 proxy in front of the Series1155V1 implementation, with a separate SeriesActivation contract that holds every token until its holder activates it; all three were deployed on 22 May 2025 and are built on QRToken.
- [SmartMetal series: 2024 and the Limited Edition Azure Dragon](https://veri.how/smartmetal/series.md): The Veri SmartMetal contract has two series, the 2024 series of 6,566 tokens (ids 1 to 6566, created on chain on 22 May 2025) and Veri SmartMetal Limited Edition Azure Dragon, 16 tokens created on 28 May 2026; each series is one metadata folder on IPFS, and its id is the keccak256 hash of that folder's CID.
- [Look up a SmartMetal token](https://veri.how/smartmetal/lookup.md): Given a SmartMetal token id, read its series with tokenSeries(id) and its metadata URI with uri(id) on the token contract, fetch the metadata JSON from any IPFS gateway, and ask SeriesActivation whether it is activated with tokenStatus(id); veri.how also serves the indexed answer at /smartmetal/tokens/<id>.json.
- [veri.vip](https://veri.how/smartmetal/veri-vip.md): veri.vip is the website that shows one page per Veri SmartMetal token, at veri.vip/<id>, built with QRToken; every value on a page is tagged as read from the Base blockchain or from the token's metadata file on IPFS, so each can be checked without the site.
