Publishing Roots
Root TTL, publish cadence, on-chain costs, and strategies for managing root freshness.
Publishing Roots
Why Root Publishing Matters
When a user generates a membership proof, they prove inclusion against a specific Merkle root. That root must:
- Be published in the
AttestationHub - Still be within its TTL (time-to-live) when the proof is verified
If your root expires or you haven't published a fresh root, users will fail proof generation.
Root TTL
Each published root has a configurable TTL. When the root expires, proofs against it are rejected on-chain:
// Default TTL: 24 hours
await asp.publishRoot({
walletClient,
ttl: 24 * 60 * 60 * 1000, // 24 hours in ms
})The AttestationHub stores the last N roots per ASP (default: 3). Users can prove against any of these.
Publish Cadence
Recommended cadence based on use case:
| Use Case | Recommended Interval | Rationale |
|---|---|---|
| Active DeFi protocol | Every 1-4 hours | Users shouldn't wait long for proofs |
| Enterprise/institutional | Every 24 hours | Lower transaction costs |
| Government sanctions | On change + daily | Sanctions lists change infrequently |
Publish Before TTL Expires
If you miss a publish window and all roots expire, users cannot generate valid proofs until you publish again. Set your publish interval to at most 80% of your TTL to have buffer.
On-Chain Costs
Root publishing is a single transaction to the AttestationHub:
publishRoot(aspId, root)
Gas: ~50,000–70,000
Data: 32 bytes (Merkle root)At current Ethereum gas prices (~10 gwei), publishing costs ~$2–5 per root. This is the primary operational cost of running an ASP.
Automatic Publishing
The upc-asp-whitelist service handles automatic publishing:
const asp = createWhitelistASP({
publishInterval: 4 * 60 * 60 * 1000, // publish every 4 hours
// ...
})The service will:
- Check if the root has changed (skip publish if tree hasn't been modified)
- Publish if the root is stale (approaching TTL)
- Emit
rootPublishedevents for monitoring
Manual Publishing
// Force publish now (regardless of interval)
await asp.publishRoot({ walletClient })
// Publish only if root has changed since last publish
await asp.publishRootIfChanged({ walletClient })Monitoring
Monitor root freshness with these checks:
// Check time since last publish
const lastPublish = await asp.getLastPublishTime()
const hoursSince = (Date.now() - lastPublish) / 3600000
if (hoursSince > 20) {
alert('Root approaching TTL — publish soon!')
}
// Check on-chain root matches local root
const onChainRoot = await asp.getLatestOnChainRoot(aspId)
const localRoot = asp.getRoot()
if (onChainRoot !== localRoot) {
alert('On-chain root is stale — publish pending changes')
}