Skip to content

Token-gated identity

A token gate ties an agent’s authorisation to ownership of an on-chain asset (NFT, soulbound token, semi-fungible). The gate evaluates at decision time: if the agent’s wallet currently holds the required token, the policy may proceed; otherwise the request denies fail-closed with auth.token_gate_failed.

Why token gates

Three concrete patterns:

  • Membership-only agents — only DAO members (balanceOf >= 1 on the membership NFT) can run a research agent owned by the DAO.
  • Time-windowed credentials — an agent runs only while its operator holds an event-attendance NFT, with the gate auto-expiring N hours after the event.
  • Tier-stratified policies — Free / Pro / Enterprise tiers determined by which 1155 token type the operator holds.

Without token gates, the same outcomes require off-chain user databases. With them, the credential lives where it should: on chain, public, transferable subject to the issuer’s rules.

Gate shape

{
"type": "token_gate",
"any_of": [
{ "erc721": { "address": "0xMembership...", "min_balance": 1 } },
{ "erc1155": { "address": "0xPro...", "token_id": "1", "min_balance": 1 } }
]
}

any_of succeeds if either branch holds; all_of requires both. They nest:

{
"all_of": [
{ "erc721": { "address": "0xMembership...", "min_balance": 1 } },
{ "any_of": [
{ "erc1155": { "address": "0xPro...", "token_id": "1" } },
{ "erc1155": { "address": "0xEnterprise...", "token_id": "1" } }
]
}
]
}

Reads as: “must hold membership NFT AND (Pro OR Enterprise tier)”.

Time-window extension

Add a valid_after / valid_before pair:

{
"type": "token_gate",
"valid_after": "2026-06-01T00:00:00Z",
"valid_before": "2026-06-08T23:59:59Z",
"erc721": { "address": "0xConfNFT...", "min_balance": 1 }
}

Useful for conference-attendance agents, event-bot policies, or any credential with an expiry. The gate checks both: token currently held AND now within window.

Risk-class presets

For common patterns, the gate can reference a named preset:

PresetExpansion
risk:lowaccepts soulbound + non-transferable membership tokens; rejects transferable NFTs
risk:mediumaccepts any 721 / 1155 from a vetted issuer registry
risk:highaccepts any 721 / 1155 from any issuer (maximum permissiveness)

Presets resolve at policy-load time; the resolved gate is what gets signed into the receipt. Updating a preset issuer registry doesn’t retroactively change in-flight policies.

Evaluation cost

The daemon caches token-balance lookups per (address, agent_wallet) pair for --token-gate-cache-ttl seconds (default 30 s). Within the TTL, no RPC call fires; afterwards, a single eth_call to balanceOf resolves it.

For high-frequency agents with stable credentials, set TTL to 5 minutes — most gates are cheap. For credentials that revoke fast (sale of NFT mid-stream), set TTL to 5 seconds.

Terminal window
# CLI evaluation (dry-run a gate without running the daemon)
sbo3l token-gate test \
--gate '{ "type": "token_gate", "erc721": { "address": "0xABC...", "min_balance": 1 } }' \
--wallet 0xDEF...
# gate-passes: true
# basis: balanceOf(0xDEF) = 3, min_balance = 1

Source pointers

  • Implementation: crates/sbo3l-policy/src/token_gate.rs (#237)
  • Issuer registry: crates/sbo3l-policy/src/registries/issuer.rs
  • Test corpus: tests/fixtures/token-gates/*.json (good + adversarial cases)
  • Sepolia test contract: deployed at 0xConfNFT... (see demo-fixtures/sepolia-token-gates.json)

See also

  • Policy decision — token gates are one predicate type among many.
  • Signing model — operator wallet signature on the gate config (rotation = re-sign).