Touchstone

Products · AssetGate

The contract that acts only when the evidence holds

AssetGate answers one question on chain: check(key) returns (allowed, reason). Every condition it applies — which statuses count, how old an observation may be, which publisher, which control set — is fixed in the constructor and cannot be widened afterwards. A consumer contract calls it before it acts; when the answer is no, the action reverts with the gate's own reason string.

LIVE · MAINNET 196 LIVE · TESTNET 1952

Current block-time decision

What the mainnet gate answers now

0x8641CF6d40524AC55aBd0a02601AfBd374EFB059 · chain 196 · block 68559234

check(0xa40f966944356ce543320f59f22ba003e3f0be28532bae029d4f9f9d05efc589)

true, “allowed”

The disclosure-freshness:1 verdict is CONFIRMED, the observation is inside the window the gate was constructed with, the publisher matches the lineage it requires, and the control-set root is the one it pins. The additional v2 policy and approval pins also matched, so the gate returned allowed at that block.

Verified on 2026-08-21 through both configured RPC providers. Registry V2 sequence 3 is valid through 2026-08-23T23:59:59Z. This remains a block-time result, not a promise about a future block, so a consumer should read the registry at an explicit block before acting.

AssetGate v1 · live

The five conditions, in the order they are applied

The gate reads the latest report for the key and returns on the first condition that fails, with the reason string exactly as written below. It never interprets a state charitably and it never falls back.

  1. The key is known

    A key with no report has sequence zero. The gate answers “unknown asset” — not a default, not an empty result a caller might mistake for a permission.

  2. The status is one it accepts

    The allowed statuses are a bit mask fixed at construction; the deployed gates accept CONFIRMED only. Anything else — STALE, INCONSISTENT, UNVERIFIABLE — returns “status not allowed”. The constructor rejects an empty mask and any mask with bits outside the four statuses.

  3. The observation is current

    Three tests share one reason string. The report must not be observed in the future, the current time must not be past the report's own validUntil, and the elapsed time since observation must not exceed the maxObservationAge the gate was constructed with. Any of the three returns “observation too old”.

  4. The publisher is the right one

    With a required publisher pinned, the report's publisher must share that publisher's registry identity — which is what makes a key rotation survivable and a substituted signer not. With no publisher pinned, the report's publisher must at least be authorized on the registry. Otherwise “wrong publisher”.

  5. The control set is the agreed one

    The report commits to the exact set of approved controls it was evaluated under. The gate pins that root, so a quietly weakened control set cannot pass as the one that was agreed to: “control-set mismatch”.

All five pass and the gate returns (true, “allowed”). The same logic is available as a state-changing call, demand(key), which reverts with the reason instead of returning it — for consumers that would rather fail the transaction than branch.

The live pins, read from chain: the mainnet freshness gate holds maxObservationAge 93600s and control-set root 0x35dbfa3dfbae5a7dc6a2852264fca08b2674629bc51e6018be06b5c083158d73; the testnet freshness gate holds 93600s and the same root; the first-generation testnet gate holds 172800s and a zero control-set root — the opt-out its own successors now refuse at the constructor, kept on chain as the reason the rule exists.

v1 and v2

Two generations of the same contract

AssetGate v1 is deployed on both chains, reading a first-generation registry with five conditions and five pins. AssetGateV2 reads a v2 registry, adds four conditions and three more pins, and refuses at construction to be deployed with any of them left blank. AssetGateV2 is live on mainnet at 0x8641CF6d40524AC55aBd0a02601AfBd374EFB059.

AssetGate v1 and AssetGateV2 compared, pin by pin
Pin AssetGate v1 AssetGateV2
registry Immutable. Must be a contract, not an address with no code. Same, against a v2 registry.
allowedStatuses CONFIRMED only on the live gates. Empty or out-of-range masks are rejected. Identical.
maxObservationAge Immutable ceiling on evidence age, applied alongside the report's own expiry. Identical.
requiredPublisher Publisher lineage, or any authorized publisher when left unset. Identical.
requiredControlSetRoot Enforced when nonzero; zero opts out of the binding. Must be nonzero. The constructor reverts on zero, so a gate cannot claim policy protection it does not require.
expectedPolicyId Must be nonzero. The report's policy identity must match exactly, so a verdict for one policy cannot answer for another.
expectedPolicyRoot Must be nonzero. Pins the exact policy manifest digest the protection was agreed against.
expectedApprovalDigest Must be nonzero. Pins the human approval ledger behind the control set, so a publisher cannot swap the human decision while every other pin stays identical.
report digest A stored report with a zero digest is refused before any other check.
Every reason string a gate can return
Reason Meaning Generation
unknown assetNo report has ever been published under this key.v1 · v2
invalid report digestThe stored report carries no report digest to verify against.v2
policy-id mismatchThe verdict answers a different policy than this gate protects.v2
policy-root mismatchThe policy text behind the verdict is not the manifest this gate pinned.v2
status not allowedThe state is not in the gate's accepted mask.v1 · v2
observation too oldObserved in the future, past its own expiry, or older than the pinned ceiling.v1 · v2
wrong publisherThe report's publisher is outside the required lineage.v1 · v2
control-set mismatchThe report was evaluated under a different set of approved controls.v1 · v2
approval mismatchThe control set carries a different human approval ledger than the pinned one.v2
allowedEvery condition held. The consumer may act.v1 · v2

Enforcement on chain

One gate, one permitted action, one refused action

Each chain carries a matched pair of guarded consumers sharing one gate. At the recorded execution block, the first was bound to a policy key whose verdict was CONFIRMED, so its action executed and its counter moved. The second is bound to a key derived from an identifier no report has ever been published under, so the gate answers (false, “unknown asset”) and the action reverts. The refusal was sent as a real transaction with a fixed gas limit rather than simulated, because a refusal that only ever happens in an eth_call is a refusal nobody can point at. Both outcomes were read back from receipts.

X Layer mainnet · chain 196

Gate and guarded consumers on X Layer mainnet
Contract Address Outcome
AssetGatepinned to disclosure-freshness:1 0xAac48DC261B04737FDCB101D5049395121034a83 first answered (true, “allowed”) on 2026-08-19 · block 68389983
GuardedActionpermitted key 0xBaE680e671e0451b95c9b09eD15F70C3E1EA7720 executed
GuardedActionnever-published key 0x8FbcFf50bf1F88cADEc9103a57c4C86e8A44BAcB reverted, counter unchanged

X Layer testnet · chain 1952

Gates and guarded consumers on X Layer testnet
Contract Address Outcome
AssetGatepinned to disclosure-freshness:1 0x0bc5c0cc879CE1b5AD23aEdA8fC42dB414eB8eE1 guards the pair below
AssetGatepinned to the USTB asset key 0xAac48DC261B04737FDCB101D5049395121034a83 the first gate deployed against the registry
GuardedActionpermitted key 0xf6D53a9cD76C6777835a6b4070e88337199127Dc executed
GuardedActionnever-published key 0x8641CF6d40524AC55aBd0a02601AfBd374EFB059 reverted, counter unchanged

9 enforcement transactions across the 2 chains. Both addresses in each pair also exist on the other network as different contracts — read every row with its chain id attached.

The receipts themselves: mainnet permitted execution · refused execution (status 0); testnet permitted · refused (status 0). The refusals were sent with a fixed gas limit precisely so they would exist as transactions a reader can cite, not only as reverted simulations.

The ninth enforcement transaction is the Terminal's permitted mainnet admission execution: Builder Code f0axgs7smtk2nfa7, AssetUsed number 2, block 68574822. Inspect the attributed receipt.

Integration

Two functions, and your action is policy-gated

Consumers depend on the interface, not on a generation. Both gates answer the same selector, so a contract written against ITouchstoneGate keeps working when its gate is upgraded from a v1 registry to a v2 one — no consumer redeployment, which is the point of having an interface at all.

ITouchstoneGate.sol

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.24;

interface ITouchstoneGate {
    function check(
        bytes32 assetKey
    ) external view returns (bool allowed, string memory reason);
}

THE CONSUMER PATTERN

// The gate and the key are immutable: a consumer cannot be re-pointed
// at a friendlier gate after it has been trusted.
ITouchstoneGate public immutable gate;
bytes32 public immutable assetKey;

function execute() external {
    (bool allowed, string memory reason) = gate.check(assetKey);
    if (!allowed) revert ActionRefused(assetKey, reason);

    uint256 nextAction = actionCount + 1;
    actionCount = nextAction;
    emit ActionExecuted(assetKey, msg.sender, nextAction);
}

The refusal carries the gate's own reason string up to the caller, so an integrator sees why the action was refused rather than a bare revert — and the same string is what the Policy Terminal shows before you ever send a transaction.

What a gate does not do

The limits, stated plainly

A gate reads a registry; it does not audit an issuer. It can prove that a verdict was published by the required publisher, under the pinned control set, inside the freshness window — and nothing about whether the issuer's underlying figures are true. That distinction is the product, not a caveat around it.

Every pin is immutable by construction. Widening a gate means deploying a new one and pointing consumers at it, in public, rather than quietly relaxing a threshold — which also means a gate deployed against a control set that is still moving would be wrong the day it shipped.