Protocol

Smart Contracts

The TAG IT protocol is a suite of 30 contracts, 21 interfaces and 6 libraries built with Foundry and Solidity 0.8.28+, exposed through 17 public proxy addresses. The suite is deployed and verified on Base Sepolia (chainId 84532); mainnet deployment is audit-gated, targeted H1 2027. Contracts consumers interact with are UUPS upgradeable proxies owned by a TimelockController, so upgrades are subject to a governance delay.

Looking for deployed addresses? The canonical registry lives at Contract Addresses — every entry links to verified source on BaseScan.

Suite at a glance

Contracts are organized into five layers. The table lists the flagship contracts of each layer; supporting contracts (badge tokens, account abstraction, bridge adapters, interfaces) round out the suite of 25.

LayerRepresentative contractsPurpose
CoreTAGITCore, TAGITFactory, TAGITRegistryAsset identity, minting, lookup
TokenTAGITToken, TAGITStaking, TAGITVestingTAGIT utility token, staking rewards, vesting
UXTAGITPrograms, TAGITRecovery, TAGITVerifierIncentives, AIRP disputes, verification
GovernanceTAGITGovernor, TAGITTreasury, TAGITTimelockDAO voting, fee distribution, timelocks
Agent (ERC-8004)TAGITAgentIdentity, TAGITAgentReputation, TAGITAgentValidationAI agent registry, reputation, validation

Core modules

TAGITCore

The primary contract: an ERC-721 of digital twins with a seven-state lifecycle machine (NONE → MINTED → BOUND → ACTIVATED → CLAIMED → FLAGGED → RECYCLED). Each token stores a tagHash binding it to exactly one NFC tag — a tag hash can never be reused. Key functions: mint(), bindTag(), activate(), claim(), flag(), resolve(), transferAsset(), recycle(), plus the read surface verify(), getAssetState(), and tagHashToToken(). See Asset Lifecycle for full state semantics.

TAGITAccess

The unified facade for BIDGES — soulbound ERC-5192 identity badges (IdentityBadge) plus ERC-1155 capability badges (CapabilityBadge). Every privileged call in the protocol routes through a capability check here. Key functions: hasCapability(), requireCapability(), grantCapability(), revokeCapability(). Badge and capability catalogs are documented in Access Control (BIDGES).

TAGITRecovery

Implements AIRP, the Asset Identity Recovery Protocol for lost, stolen, or disputed assets. Owners open a case with reportLost(); the asset enters a 48-hour quarantine() window, and a capability-gated resolve() settles the case (return to owner, transfer to claimant, or recycle). A TransferGate — canTransfer() — blocks transfers while a case or quarantine is active.

TAGITGovernor

Multi-house DAO governance for protocol upgrades and parameters. Proposals move through propose(), vote(), queue(), and execute(), with per-house voting weights configured via setHouseWeight(). Execution flows through the timelock that owns the protocol proxies.

TAGITTreasury

Collects and distributes protocol fees under timelock control. Key functions: collectFee(), distribute(), setFeeRate(), withdraw(), allocate().

TAGITPrograms

Loyalty, rewards, and compliance programs layered on top of verified assets — including customs holds and manufacturer recalls. Key functions: createProgram(), enroll(), claimReward(), customsHold(), initiateRecall().

Libraries

Four shared libraries keep the modules small and consistent:

  • LifecycleLib — state-machine transition validation (used by TAGITCore)
  • AccessLib — permission bitmask operations (used by TAGITAccess)
  • CryptoLib — hash functions and post-quantum crypto hooks (used by TAGITCore, TAGITRecovery)
  • EventLib — standardized event definitions (used across the suite)

Lifecycle functions

The eight state-changing functions on TAGITCore, in lifecycle order:

// NONE → MINTED — create the digital twin
function mint(address to, bytes32 metadataHash, string calldata metadataURI)
    external returns (uint256 tokenId);

// MINTED → BOUND — cryptographically bind one NFC tag, permanently
function bindTag(uint256 tokenId, bytes32 tagHash, bytes calldata signature) external;

// BOUND → ACTIVATED — QA gate: product is ready for market
function activate(uint256 tokenId) external;

// ACTIVATED → CLAIMED — first consumer takes ownership
function claim(uint256 tokenId, address owner) external;

// BOUND | ACTIVATED | CLAIMED → FLAGGED — lost / stolen / recall / dispute
function flag(uint256 tokenId, bytes32 reason) external;

// FLAGGED → exact pre-flag state — the state before flag() is restored
function resolve(uint256 tokenId) external;

// CLAIMED → CLAIMED (new owner) — secondary-market resale
function transferAsset(uint256 tokenId, address to) external;

// BOUND | ACTIVATED | CLAIMED | FLAGGED → RECYCLED — terminal, end of life
function recycle(uint256 tokenId) external;

Three semantics worth internalizing: flag() is reachable from BOUND, ACTIVATED, or CLAIMED; resolve() always returns the asset to its exact pre-flag state; and RECYCLED is terminal — no function leaves it.

Security practices

  • Checks-Effects-Interactions on every state-changing function, verified in static analysis.
  • ReentrancyGuard on all external state-changing entry points.
  • Custom errors throughout — no string reverts — for cheaper, machine-readable failures.
  • Fuzz and invariant testing with Foundry; CI runs the fuzz suite on every pull request to main.
  • Timelocked UUPS upgrades — proxies are owned by a TimelockController, so no instant admin upgrades.

Audit status

Honest status, no badges: Internal Slither static analysis has run three times (December 2025, January 2026, February 2026); the January run flagged four high-severity findings that the team triaged as false positives, and that baseline is now months behind the current tree. No third-party audit report has been published. A paid external audit by Hacken was engaged in July 2026; no report has been published yet. There is no bug bounty program today. Mainnet deployment is gated on a completed external audit. Report vulnerabilities to info@tagit.network.

Testnet software. These contracts run on Base Sepolia and have not yet completed an external audit. Interfaces can change before mainnet.

Reading contract state

Everything on-chain is public. Reading TAGITCore with viem takes a few lines and no API key:

import { createPublicClient, http, parseAbi } from "viem";
import { baseSepolia } from "viem/chains";

const client = createPublicClient({ chain: baseSepolia, transport: http() });

const TAGIT_CORE = "0x3aDc7EFDb58Ae85483eFf5D4966D916185f31d1D";

const abi = parseAbi([
  "function ownerOf(uint256 tokenId) view returns (address)",
  "function getAssetState(uint256 tokenId) view returns (uint8)",
]);

const [owner, state] = await Promise.all([
  client.readContract({ address: TAGIT_CORE, abi, functionName: "ownerOf", args: [1n] }),
  client.readContract({ address: TAGIT_CORE, abi, functionName: "getAssetState", args: [1n] }),
]);

// state: 0 NONE · 1 MINTED · 2 BOUND · 3 ACTIVATED · 4 CLAIMED · 5 FLAGGED · 6 RECYCLED
console.log({ owner, state });

For the full deployment table, continue to Contract Addresses; for typed bindings, see the TypeScript SDK.