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.
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.
-
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. -
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. -
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 themaxObservationAgethe gate was constructed with. Any of the three returns“observation too old”. -
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”. -
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.
| 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. |
| Reason | Meaning | Generation |
|---|---|---|
| unknown asset | No report has ever been published under this key. | v1 · v2 |
| invalid report digest | The stored report carries no report digest to verify against. | v2 |
| policy-id mismatch | The verdict answers a different policy than this gate protects. | v2 |
| policy-root mismatch | The policy text behind the verdict is not the manifest this gate pinned. | v2 |
| status not allowed | The state is not in the gate's accepted mask. | v1 · v2 |
| observation too old | Observed in the future, past its own expiry, or older than the pinned ceiling. | v1 · v2 |
| wrong publisher | The report's publisher is outside the required lineage. | v1 · v2 |
| control-set mismatch | The report was evaluated under a different set of approved controls. | v1 · v2 |
| approval mismatch | The control set carries a different human approval ledger than the pinned one. | v2 |
| allowed | Every 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
| 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
| 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.