Skip to content
⌘K

Contracts

Device Pass contract

The device_pass Move package: one soulbound pass per attested device, claims and rebinds signed by Rare's attestor, the registry, events and error codes.

The device_pass package gives each attested device one soulbound pass. Rare's attestor checks a device's TPM off chain (the EK certificate chain, credential activation, and a PCR quote against firmware and boot allowlists) and signs a claim for the holder. The registry is keyed by the device's Rare-salted EK hash, so each device has exactly one current pass. The package is a single module and depends only on the Sui framework.

Published on mainnet with the attestor key set. No device has claimed a pass yet.

Published on Sui mainnetView on Suiscan
Package id0xe81e2d12a766b56a9dae5cb33afc4216bd4ef1fea05f2dd1983de9cd6efc4a65
Registry0xd4724780ef48b31ec8f3a60c8b3a451df8265475b209af261c6776be5af35ca9

On this page, PACKAGE_ID is the package id, REGISTRY_ID is the shared Registry and 0x6 is the Clock.

Modules

ModuleWhat it does
device_passThe Registry, the DevicePass object, claims, rebinds, burns, views and events.

Shared objects and caps

ObjectOwnerWhat it is
Registryshared, oneVersion, pause switch, the attestor's ed25519 public key (issuer_key, empty means claims are closed), devices: Table<vector<u8>, Record> keyed by the 32-byte device hash, and count, the number of devices ever claimed.
Recordin the registry, one per devicenumber, model, first_seen_ms, holder (@0x0 after the current pass is burned), pass (the current pass id) and seq (0 for the first claim, +1 for each rebind).
DevicePassthe holderregistry, number, model, first_seen_ms, claimed_ms, device. Soulbound: it has key only, so nobody can transfer, wrap or sell it.
AdminCapplatformSets the attestor key, pauses and migrates. It cannot mint, move or burn passes.

Pass numbers run from 1 to count, in the order devices are first claimed.

How a pass follows the device

When a device changes hands, the new owner re-attests it and the attestor signs a rebind with seq set to the record's seq + 1. The device then points at the new holder's new pass, which keeps the device's number, model and first-seen date. The old pass stays in the old wallet but is no longer current (is_current returns false). A pass can't be farmed or sold apart from its device.

Claim

Move
public fun claim(
    reg: &mut Registry,
    device: vector<u8>,
    model: String,
    first_seen_ms: u64,
    seq: u64,
    expires_ms: u64,
    signature: vector<u8>,
    clock: &Clock,
    ctx: &mut TxContext,
): ID

Mints a pass to the sender and returns its id. The sender must be the holder the attestor signed for. Anyone can pay the gas, so sponsored transactions work. Checks, in order:

  1. The registry is current (1), not paused (2), and has an attestor key (4).
  2. clock < expires_ms (5).
  3. The ed25519 signature is valid for the sender (6).
  4. device is 32 bytes (10) and model is at most 64 bytes (11).
  5. First claim: the device has no record and seq is 0 (8). A second first claim for the same device aborts with 7.
  6. Rebind: seq is the record's seq + 1 (8), and the sender differs from the current holder (9). The new pass takes the number, model and first-seen date from the record.

The registry record then points at the new pass, PassClaimed is emitted and the pass goes to the sender.

Claim message

The attestor signs these raw bytes with ed25519 (no hashing, 64-byte signature):

Text
"RARE_DEVICE_V1" || registry id (32) || holder (32) || device (32) || seq (u64 LE) || first_seen_ms (u64 LE) || expires_ms (u64 LE) || model (UTF-8, the rest)

claim_message(registry, holder, device, model, first_seen_ms, seq, expires_ms) returns the same bytes on chain. The registry id ties each signature to one deployment, so a testnet claim never works on mainnet.

Burn

Move
public fun burn(reg: &mut Registry, pass: DevicePass, ctx: &TxContext)

The holder destroys their pass. If it was the device's current pass, the record keeps its number, first-seen date and seq with holder set to @0x0, and only a rebind can give the device a new pass. Burning an old, non-current pass leaves the record alone.

Admin calls

These take the AdminCap.

Move
public fun set_issuer_key(_: &AdminCap, reg: &mut Registry, public_key: vector<u8>)
public fun set_paused(_: &AdminCap, reg: &mut Registry, paused: bool)
public fun migrate(_: &AdminCap, reg: &mut Registry)
  • set_issuer_key sets or rotates the attestor key (32 bytes, or empty to close claims; 3 otherwise). The old key stops working in the same transaction.
  • set_paused stops every claim and rebind while true. Existing passes are untouched.
  • migrate moves the registry to a new package version after an upgrade (12 if it is not newer).

Views

  • Registry: is_claimed(reg, device), record(reg, device), count, issuer_key, paused, is_current(reg, pass)
  • Record: record_number, record_holder, record_pass, record_seq, record_first_seen_ms
  • Pass: number, model, first_seen_ms, claimed_ms, device, pass_registry

Holders can be listed from the devices table or from PassClaimed events.

Display

name (Rare Device Pass #{number}), description, image_url and thumbnail_url (https://rare.market/device-pass.svg), project_url (https://rare.market), creator (Rare), model, number, first_seen_ms.

Fees

The package charges no fees. Claims cost gas only, and anyone can pay it.

Events

EventFieldsWhen
PassClaimedregistry, pass, number, holder, device, model, first_seen_ms, seq, previous_holder: Option<address>, previous_pass: Option<ID>Every claim. The previous_* fields are set on a rebind
PassBurnedregistry, pass, number, holder, currentburn (current is true if it was the device's current pass)
IssuerKeySetregistry, public_keyset_issuer_key
RegistryPausedregistry, pausedset_paused

Error codes

CodeNameMeaning
1EOutdatedThe registry is not on the current package version
2EPausedClaims are paused
3EBadKeyThe attestor key is not 32 bytes or empty
4ENoIssuerNo attestor key is set
5EExpiredThe claim has expired
6EBadSignatureThe signature does not match
7EDeviceClaimedFirst claim for a device that already has a pass
8EBadSeqseq is not 0 for a new device or the record's seq + 1 for a rebind
9ESameHolderRebind to the current holder
10EBadDeviceThe device hash is not 32 bytes
11ETooLongThe model is longer than 64 bytes
12ENotNewermigrate on a registry that is already current
--
TPS:--