Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ jobs:
VITE_ATP_REGISTRY_ADDRESS: "0x0000000000000000000000000000000000000003"
VITE_ATP_REGISTRY_AUCTION_ADDRESS: "0x0000000000000000000000000000000000000004"
VITE_STAKING_REGISTRY_ADDRESS: "0x0000000000000000000000000000000000000005"
VITE_ROLLUP_ADDRESS: "0x0000000000000000000000000000000000000006"
VITE_ATP_NON_WITHDRAWABLE_STAKER_ADDRESS: "0x0000000000000000000000000000000000000007"
VITE_ATP_WITHDRAWABLE_STAKER_ADDRESS: "0x0000000000000000000000000000000000000007"
VITE_ATP_WITHDRAWABLE_AND_CLAIMABLE_STAKER_ADDRESS: "0x0000000000000000000000000000000000000007"
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/deploy-indexer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ jobs:
ATP_REGISTRY_ADDRESS: ${{ vars.ATP_REGISTRY_ADDRESS }}
ATP_REGISTRY_AUCTION_ADDRESS: ${{ vars.ATP_REGISTRY_AUCTION_ADDRESS }}
STAKING_REGISTRY_ADDRESS: ${{ vars.STAKING_REGISTRY_ADDRESS }}
ROLLUP_ADDRESS: ${{ vars.ROLLUP_ADDRESS }}
REGISTRY_ADDRESS: ${{ vars.REGISTRY_ADDRESS }}
START_BLOCK: ${{ vars.ATP_FACTORY_DEPLOYMENT_BLOCK }}
MATP_FACTORY_START_BLOCK: ${{ vars.ATP_FACTORY_MATP_DEPLOYMENT_BLOCK }}
LATP_FACTORY_START_BLOCK: ${{ vars.ATP_FACTORY_LATP_DEPLOYMENT_BLOCK }}
REGISTRY_START_BLOCK: ${{ vars.REGISTRY_DEPLOYMENT_BLOCK }}

steps:
- uses: actions/checkout@v4
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/deploy-staking-dashboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ jobs:
VITE_ATP_REGISTRY_ADDRESS: ${{ vars.ATP_REGISTRY_ADDRESS }}
VITE_ATP_REGISTRY_AUCTION_ADDRESS: ${{ vars.ATP_REGISTRY_AUCTION_ADDRESS }}
VITE_STAKING_REGISTRY_ADDRESS: ${{ vars.STAKING_REGISTRY_ADDRESS }}
VITE_ROLLUP_ADDRESS: ${{ vars.ROLLUP_ADDRESS }}
VITE_ATP_NON_WITHDRAWABLE_STAKER_ADDRESS: ${{ vars.ATP_WITHDRAWABLE_AND_CLAIMABLE_STAKER_ADDRESS }}
VITE_ATP_WITHDRAWABLE_STAKER_ADDRESS: ${{ vars.ATP_WITHDRAWABLE_AND_CLAIMABLE_STAKER_ADDRESS }}
VITE_ATP_WITHDRAWABLE_AND_CLAIMABLE_STAKER_ADDRESS: ${{ vars.ATP_WITHDRAWABLE_AND_CLAIMABLE_STAKER_ADDRESS }}
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ yarn-error.log*
# Contract addresses (should be provided via env vars)
contract_addresses.json

# Multi-rollup test environment outputs (regenerated by deploy/seed scripts)
scripts/multi-rollup-test/deploy-output.json
scripts/multi-rollup-test/test-data.json

# Test coverage
coverage/

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ Example `contract_addresses.json`:
"atpRegistry": "0x...",
"atpRegistryAuction": "0x...",
"stakingRegistry": "0x...",
"rollupAddress": "0x...",
"registryAddress": "0x...",
"registryDeploymentBlock": "12345678",
"atpWithdrawableAndClaimableStaker": "0x...",
"genesisSequencerSale": "0x...",
"governanceAddress": "0x...",
Expand All @@ -103,6 +104,8 @@ Example `contract_addresses.json`:
}
```

The canonical rollup is no longer a separate configuration value, the indexer and frontend both resolve it dynamically from `Registry.getCanonicalRollup()`. Rollup upgrades (new `addRollup()` calls on the Registry) are picked up automatically: the indexer continues indexing every historical rollup via Ponder's factory pattern on the `CanonicalRollupUpdated` event, and the frontend re-resolves on every page load.

For production contract addresses, see the [Aztec documentation](https://docs.aztec.network/) or contact the Aztec team.

## Project Structure
Expand Down
6 changes: 5 additions & 1 deletion atp-indexer/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ ATP_FACTORY_AUCTION_ADDRESS=0x...
ATP_FACTORY_MATP_ADDRESS=0x...
ATP_FACTORY_LATP_ADDRESS=0x...
STAKING_REGISTRY_ADDRESS=0x...
ROLLUP_ADDRESS=0x...
# Aztec Registry, used as factory source so the current AND all historical
# rollups are indexed automatically. On mainnet: 0x35b22e09Ee0390539439E24f06Da43D83f90e298
REGISTRY_ADDRESS=0x...

# Indexer Settings
START_BLOCK=0
# Per-factory start blocks (optional, for efficiency - set to deployment block of each factory)
MATP_FACTORY_START_BLOCK=0
LATP_FACTORY_START_BLOCK=0
# Registry deployment block, factory backfill starts here (mainnet: 23786827)
REGISTRY_START_BLOCK=0

# Application
NODE_ENV=development
Expand Down
21 changes: 14 additions & 7 deletions atp-indexer/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ get_contract_addresses() {
ATP_REGISTRY_AUCTION_ADDRESS="${ATP_REGISTRY_AUCTION_ADDRESS:-}"
ATP_FACTORY_AUCTION_ADDRESS="${ATP_FACTORY_AUCTION_ADDRESS:-}"
STAKING_REGISTRY_ADDRESS="${STAKING_REGISTRY_ADDRESS:-}"
ROLLUP_ADDRESS="${ROLLUP_ADDRESS:-}"
REGISTRY_ADDRESS="${REGISTRY_ADDRESS:-}"
START_BLOCK="${START_BLOCK:-0}"
MATP_FACTORY_START_BLOCK="${MATP_FACTORY_START_BLOCK:-0}"
LATP_FACTORY_START_BLOCK="${LATP_FACTORY_START_BLOCK:-0}"
REGISTRY_START_BLOCK="${REGISTRY_START_BLOCK:-0}"
return 0
fi

Expand Down Expand Up @@ -82,14 +83,16 @@ get_contract_addresses() {

# other
STAKING_REGISTRY_ADDRESS=$(cat $contract_addresses_file | jq -r '.stakingRegistry')
ROLLUP_ADDRESS=$(cat $contract_addresses_file | jq -r '.rollupAddress')
REGISTRY_ADDRESS=$(cat $contract_addresses_file | jq -r '.registryAddress')

# For dev environment, use 0 to catch all events
# For other environments, use atpFactoryDeploymentBlock as the starting point
if [ "$environment" = "dev" ]; then
START_BLOCK=0
REGISTRY_START_BLOCK=0
else
START_BLOCK=$(cat $contract_addresses_file | jq -r '.atpFactoryDeploymentBlock // 0')
REGISTRY_START_BLOCK=$(cat $contract_addresses_file | jq -r '.registryDeploymentBlock // 0')
fi
return 0
fi
Expand Down Expand Up @@ -159,10 +162,11 @@ ATP_FACTORY_AUCTION_ADDRESS=${ATP_FACTORY_AUCTION_ADDRESS}
ATP_FACTORY_MATP_ADDRESS=${ATP_FACTORY_MATP_ADDRESS}
ATP_FACTORY_LATP_ADDRESS=${ATP_FACTORY_LATP_ADDRESS}
STAKING_REGISTRY_ADDRESS=${STAKING_REGISTRY_ADDRESS}
ROLLUP_ADDRESS=${ROLLUP_ADDRESS}
REGISTRY_ADDRESS=${REGISTRY_ADDRESS}

# Ponder settings
START_BLOCK=${START_BLOCK}
REGISTRY_START_BLOCK=${REGISTRY_START_BLOCK}

# API
PORT=${PORT}
Expand Down Expand Up @@ -198,7 +202,8 @@ ATP_FACTORY_AUCTION_ADDRESS=${ATP_FACTORY_AUCTION_ADDRESS}
ATP_FACTORY_MATP_ADDRESS=${ATP_FACTORY_MATP_ADDRESS}
ATP_FACTORY_LATP_ADDRESS=${ATP_FACTORY_LATP_ADDRESS}
STAKING_REGISTRY_ADDRESS=${STAKING_REGISTRY_ADDRESS}
ROLLUP_ADDRESS=${ROLLUP_ADDRESS}
REGISTRY_ADDRESS=${REGISTRY_ADDRESS}
REGISTRY_START_BLOCK=${REGISTRY_START_BLOCK}

# Indexer settings
START_BLOCK=${DEFAULT_START_BLOCK}
Expand Down Expand Up @@ -355,6 +360,7 @@ function deploy() {
START_BLOCK="${START_BLOCK:-0}"
MATP_FACTORY_START_BLOCK="${MATP_FACTORY_START_BLOCK:-0}"
LATP_FACTORY_START_BLOCK="${LATP_FACTORY_START_BLOCK:-0}"
REGISTRY_START_BLOCK="${REGISTRY_START_BLOCK:-0}"

# Initialize Terraform with the S3 backend
(cd terraform && terraform init \
Expand All @@ -376,7 +382,8 @@ function deploy() {
-var=atp_factory_matp_address=$ATP_FACTORY_MATP_ADDRESS \
-var=atp_factory_latp_address=$ATP_FACTORY_LATP_ADDRESS \
-var=staking_registry_address=$STAKING_REGISTRY_ADDRESS \
-var=rollup_address=$ROLLUP_ADDRESS \
-var=registry_address=$REGISTRY_ADDRESS \
-var=registry_start_block=$REGISTRY_START_BLOCK \
-var=start_block=$START_BLOCK \
-var=matp_factory_start_block=$MATP_FACTORY_START_BLOCK \
-var=latp_factory_start_block=$LATP_FACTORY_START_BLOCK \
Expand Down Expand Up @@ -487,8 +494,8 @@ case $ACTION in
echo " ATP_FACTORY_ADDRESS, ATP_FACTORY_AUCTION_ADDRESS"
echo " ATP_FACTORY_MATP_ADDRESS, ATP_FACTORY_LATP_ADDRESS"
echo " ATP_REGISTRY_ADDRESS, ATP_REGISTRY_AUCTION_ADDRESS"
echo " STAKING_REGISTRY_ADDRESS, ROLLUP_ADDRESS"
echo " START_BLOCK (optional, defaults to 0)"
echo " STAKING_REGISTRY_ADDRESS, REGISTRY_ADDRESS"
echo " START_BLOCK, REGISTRY_START_BLOCK (optional, defaults to 0)"
echo ""
echo " For production contract addresses, contact the Aztec team."
;;
Expand Down
41 changes: 37 additions & 4 deletions atp-indexer/ponder.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@ import {
STAKING_REGISTRY_ABI,
ROLLUP_ABI,
STAKER_ABI,
REGISTRY_ABI,
} from "./src/abis";

// ATPCreated event signature for factory pattern
const ATPCreatedEvent = parseAbiItem(
"event ATPCreated(address indexed beneficiary, address indexed atp, uint256 allocation)"
);

// Aztec Registry emits this on every rollup upgrade. Used as a factory source
// so every canonical rollup, historical and future, is indexed without a
// config change.
const CanonicalRollupUpdatedEvent = parseAbiItem(
"event CanonicalRollupUpdated(address indexed instance, uint256 indexed version)"
);

// Per-factory start blocks for efficient indexing
const FACTORY_START_BLOCKS = {
genesis: config.START_BLOCK || 0,
Expand Down Expand Up @@ -111,14 +119,39 @@ export default createConfig({
},

/**
* Rollup Contract
* Handles validator deposits and tracks validator queue
* Aztec Registry
* Tracked as a regular contract so we can index CanonicalRollupUpdated
* events into a rollup_version table, served from /api/rollups so the
* frontend doesn't need its own Registry RPC calls. The same event is
* also used as a factory source for the Rollup contract below.
*/
Registry: {
chain: config.networkName,
abi: REGISTRY_ABI,
address: config.REGISTRY_ADDRESS as `0x${string}`,
startBlock: config.REGISTRY_START_BLOCK,
},

/**
* Rollup Contract (dynamic, sourced from the Aztec Registry)
* Handles validator deposits and tracks validator queue.
*
* Uses Ponder's factory pattern on the Registry's `CanonicalRollupUpdated`
* event so every rollup address the Registry has ever announced (historical
* versions and any future upgrade) is indexed automatically. Handlers in
* src/events/rollup/ fire for events from any of these addresses, so
* rewards/withdrawals/slashing on older rollups keep being tracked even
* after an upgrade.
*/
Rollup: {
chain: config.networkName,
abi: ROLLUP_ABI,
address: config.ROLLUP_ADDRESS as `0x${string}`,
startBlock: config.START_BLOCK,
address: factory({
address: config.REGISTRY_ADDRESS as `0x${string}`,
event: CanonicalRollupUpdatedEvent,
parameter: "instance",
}),
startBlock: config.REGISTRY_START_BLOCK,
},

/**
Expand Down
20 changes: 20 additions & 0 deletions atp-indexer/ponder.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,23 @@ export const tokensWithdrawnToBeneficiary = onchainTable("tokens_withdrawn_to_be
atpAddressIdx: index().on(table.atpAddress),
stakerAddressIdx: index().on(table.stakerAddress),
}));

/**
* RollupVersion
* Every rollup the Aztec Registry has made canonical via addRollup().
* Populated from CanonicalRollupUpdated events; the latest row (by blockNumber)
* is the current canonical rollup. Used by /api/rollups so the frontend doesn't
* have to make its own Registry RPC calls at boot, and so historical rollup
* addresses are available for future cross-rollup claim flows.
*/
export const rollupVersion = onchainTable("rollup_version", (t) => ({
version: t.bigint().primaryKey(),
address: t.hex().notNull(),
blockNumber: t.bigint().notNull(),
txHash: t.hex().notNull(),
logIndex: t.integer().notNull(),
timestamp: t.bigint().notNull(),
}), (table) => ({
addressIdx: index().on(table.address),
blockNumberIdx: index().on(table.blockNumber),
}));
7 changes: 7 additions & 0 deletions atp-indexer/src/abis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,10 @@ export {
TokensWithdrawnToBeneficiaryEventAbi,
STAKER_ABI,
} from './staker.abi';

// Registry ABIs (Aztec governance Registry: source of canonical rollup upgrades)
export {
CanonicalRollupUpdatedEventAbi,
REGISTRY_FUNCTIONS,
REGISTRY_ABI,
} from './registry.abi';
40 changes: 40 additions & 0 deletions atp-indexer/src/abis/registry.abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Aztec Registry Contract ABI
*
* The Registry tracks the canonical rollup instance over time. When a new
* rollup is deployed (e.g. on upgrade), `addRollup()` is called which emits
* `CanonicalRollupUpdated`. The indexer uses this event as a factory source
* so every rollup (past, present, and future) is indexed automatically.
*/

export const CanonicalRollupUpdatedEventAbi = {
type: 'event',
name: 'CanonicalRollupUpdated',
inputs: [
{ name: 'instance', type: 'address', indexed: true },
{ name: 'version', type: 'uint256', indexed: true },
],
anonymous: false,
} as const;

export const REGISTRY_FUNCTIONS = [
{
type: 'function',
name: 'getCanonicalRollup',
inputs: [],
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
},
{
type: 'function',
name: 'numberOfVersions',
inputs: [],
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
},
] as const;

export const REGISTRY_ABI = [
CanonicalRollupUpdatedEventAbi,
...REGISTRY_FUNCTIONS,
] as const;
5 changes: 3 additions & 2 deletions atp-indexer/src/api/handlers/atp/details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { db } from 'ponder:api';
import { eq, desc, sql, or } from 'drizzle-orm';
import { normalizeAddress, checksumAddress } from '../../../utils/address';
import { getActivationThreshold } from '../../../utils/rollup';
import { config } from '../../../config';
import { getCanonicalRollupAddress } from '../../utils/canonical-rollup';
import { getPublicClient } from '../../../utils/viem-client';
import type { ATPDetailsResponse } from '../../types/atp.types';
import { fetchFailedDeposits, markStakesWithFailedDeposits } from '../../../utils/failed-deposits';
Expand Down Expand Up @@ -148,7 +148,8 @@ export async function handleATPDetails(c: Context): Promise<Response> {
const validStakingOpsCount = markedDelegations.filter(isActiveStake).length;

// Calculate total staked
const activationThreshold = await getActivationThreshold(config.ROLLUP_ADDRESS, client);
const rollupAddress = await getCanonicalRollupAddress(client);
const activationThreshold = await getActivationThreshold(rollupAddress, client);
const totalStaked = BigInt(activationThreshold) * (BigInt(validDirectStakesCount) + BigInt(validStakingOpsCount));

// Query slashed table to get total slashed per attester address
Expand Down
5 changes: 3 additions & 2 deletions atp-indexer/src/api/handlers/provider/details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getProviderMetadata } from '../../../utils/provider-metadata';
import type { ProviderDetailsResponse } from '../../types/provider.types';
import { fetchFailedDeposits, filterValidStakes } from '../../../utils/failed-deposits';
import { getActivationThreshold } from '../../../utils/rollup';
import { config } from '../../../config';
import { getCanonicalRollupAddress } from '../../utils/canonical-rollup';
import { getPublicClient } from '../../../utils/viem-client';
import {
provider,
Expand All @@ -26,8 +26,9 @@ export async function handleProviderDetails(c: Context): Promise<Response> {
const id = c.req.param('id');
const client = getPublicClient();

const rollupAddress = await getCanonicalRollupAddress(client);
const [activationThreshold, providerData, allAtpDelegationsCount, allErc20DelegationsCount, allDirectStakesCount, allFailedDepositCount] = await Promise.all([
getActivationThreshold(config.ROLLUP_ADDRESS, client),
getActivationThreshold(rollupAddress, client),
db.select().from(provider).where(eq(provider.providerIdentifier, id)).limit(1),
db.select({ count: count() }).from(stakedWithProvider),
db.select({ count: count() }).from(erc20StakedWithProvider),
Expand Down
5 changes: 3 additions & 2 deletions atp-indexer/src/api/handlers/provider/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getAllProviderMetadata } from '../../../utils/provider-metadata';
import type { ProviderListResponse } from '../../types/provider.types';
import { fetchFailedDeposits, markStakesWithFailedDeposits } from '../../../utils/failed-deposits';
import { getActivationThreshold } from '../../../utils/rollup';
import { config } from '../../../config';
import { getCanonicalRollupAddress } from '../../utils/canonical-rollup';
import { getPublicClient } from '../../../utils/viem-client';
import {
provider,
Expand All @@ -27,8 +27,9 @@ export async function handleProviderList(c: Context): Promise<Response> {
// Get provider list of IDs from JSON
const providerIds = Array.from(metadata.keys());

const rollupAddress = await getCanonicalRollupAddress(client);
const [activationThreshold, dbProviders, atpDelegations, erc20Delegations, allDirectStakes] = await Promise.all([
getActivationThreshold(config.ROLLUP_ADDRESS, client),
getActivationThreshold(rollupAddress, client),
db.select().from(provider).where(inArray(provider.providerIdentifier, providerIds)),
db.select({
providerIdentifier: stakedWithProvider.providerIdentifier,
Expand Down
Loading
Loading