How is a SmartMetal activated?

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.

Transactions#
All transactions
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.

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 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 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), 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; the order they are checked in is in the verified source:

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’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:

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.

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 (transaction). 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 (transaction, token 427).

Check it yourself#

# 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 installed, its cast command runs the same checks and decodes the answers:

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

Sources#

  1. SeriesActivation, verified source on BaseScan
  2. QRToken contract reference, SeriesActivation
  3. QRToken getting started, Activate
  4. Activation of token 2259 on 25 Aug 2026, BaseScan
  5. EIP-191: Signed Data Standard