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.
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.
| Layer | Representative contracts | Purpose |
|---|---|---|
| Core | TAGITCore, TAGITFactory, TAGITRegistry | Asset identity, minting, lookup |
| Token | TAGITToken, TAGITStaking, TAGITVesting | TAGIT utility token, staking rewards, vesting |
| UX | TAGITPrograms, TAGITRecovery, TAGITVerifier | Incentives, AIRP disputes, verification |
| Governance | TAGITGovernor, TAGITTreasury, TAGITTimelock | DAO voting, fee distribution, timelocks |
| Agent (ERC-8004) | TAGITAgentIdentity, TAGITAgentReputation, TAGITAgentValidation | AI 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 byTAGITCore)AccessLib— permission bitmask operations (used byTAGITAccess)CryptoLib— hash functions and post-quantum crypto hooks (used byTAGITCore,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.
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.