UPC — Universal Private ComplianceASP Operators

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:

  1. Be published in the AttestationHub
  2. 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 CaseRecommended IntervalRationale
Active DeFi protocolEvery 1-4 hoursUsers shouldn't wait long for proofs
Enterprise/institutionalEvery 24 hoursLower transaction costs
Government sanctionsOn change + dailySanctions 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:

  1. Check if the root has changed (skip publish if tree hasn't been modified)
  2. Publish if the root is stale (approaching TTL)
  3. Emit rootPublished events 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')
}

On this page