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
2 changes: 2 additions & 0 deletions .github/workflows/deploy-indexer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ jobs:
# Contract Addresses (from GitHub environment variables)
ATP_FACTORY_ADDRESS: ${{ vars.ATP_FACTORY_ADDRESS }}
ATP_FACTORY_AUCTION_ADDRESS: ${{ vars.ATP_FACTORY_AUCTION_ADDRESS }}
ATP_FACTORY_MATP_ADDRESS: ${{ vars.ATP_FACTORY_MATP_ADDRESS }}
ATP_FACTORY_LATP_ADDRESS: ${{ vars.ATP_FACTORY_LATP_ADDRESS }}
ATP_REGISTRY_ADDRESS: ${{ vars.ATP_REGISTRY_ADDRESS }}
ATP_REGISTRY_AUCTION_ADDRESS: ${{ vars.ATP_REGISTRY_AUCTION_ADDRESS }}
STAKING_REGISTRY_ADDRESS: ${{ vars.STAKING_REGISTRY_ADDRESS }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ build/
.env.local
.env.*.local
.env.docker
.env.bak

# IDE
.idea/
Expand Down
9 changes: 7 additions & 2 deletions atp-indexer/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Database URL
DATABASE_URL=postgresql://user:password@localhost:5432/ponder
# Database (NOTE: Use POSTGRES_CONNECTION_STRING, not DATABASE_URL)
POSTGRES_CONNECTION_STRING=postgresql://user:password@localhost:5432/ponder

# RPC URL
RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
Expand All @@ -10,11 +10,16 @@ CHAIN_ID=1
# Contract Addresses
ATP_FACTORY_ADDRESS=0x...
ATP_FACTORY_AUCTION_ADDRESS=0x...
ATP_FACTORY_MATP_ADDRESS=0x...
ATP_FACTORY_LATP_ADDRESS=0x...
STAKING_REGISTRY_ADDRESS=0x...
ROLLUP_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

# Application
NODE_ENV=development
Expand Down
11 changes: 11 additions & 0 deletions atp-indexer/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ get_contract_addresses() {
ATP_REGISTRY_AUCTION_ADDRESS=$(cat $contract_addresses_file | jq -r '.atpRegistryAuction')
ATP_FACTORY_AUCTION_ADDRESS=$(cat $contract_addresses_file | jq -r '.atpFactoryAuction')

# new factories
ATP_FACTORY_MATP_ADDRESS=$(cat $contract_addresses_file | jq -r '.atpFactoryMatp')
ATP_FACTORY_LATP_ADDRESS=$(cat $contract_addresses_file | jq -r '.atpFactoryLatp')

# other
STAKING_REGISTRY_ADDRESS=$(cat $contract_addresses_file | jq -r '.stakingRegistry')
ROLLUP_ADDRESS=$(cat $contract_addresses_file | jq -r '.rollupAddress')
Expand Down Expand Up @@ -150,6 +154,8 @@ CHAIN_ID=${CHAIN_ID}
# Contract addresses
ATP_FACTORY_ADDRESS=${ATP_FACTORY_ADDRESS}
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}

Expand Down Expand Up @@ -187,6 +193,8 @@ CHAIN_ID=${CHAIN_ID}
# Contract addresses
ATP_FACTORY_ADDRESS=${ATP_FACTORY_ADDRESS}
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}

Expand Down Expand Up @@ -361,6 +369,8 @@ function deploy() {
local args="-var=rpc_url=$RPC_URL \
-var=atp_factory_address=$ATP_FACTORY_ADDRESS \
-var=atp_factory_auction_address=$ATP_FACTORY_AUCTION_ADDRESS \
-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=start_block=$START_BLOCK \
Expand Down Expand Up @@ -469,6 +479,7 @@ case $ACTION in
echo ""
echo " Required contract address variables:"
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)"
Expand Down
43 changes: 38 additions & 5 deletions atp-indexer/ponder.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ const ATPCreatedEvent = parseAbiItem(
"event ATPCreated(address indexed beneficiary, address indexed atp, uint256 allocation)"
);

// Per-factory start blocks for efficient indexing
const FACTORY_START_BLOCKS = {
genesis: config.START_BLOCK || 0,
auction: config.START_BLOCK || 0,
matp: config.MATP_FACTORY_START_BLOCK || config.START_BLOCK || 0,
latp: config.LATP_FACTORY_START_BLOCK || config.START_BLOCK || 0,
};


let databaseConfig: DatabaseConfig | undefined;

Expand Down Expand Up @@ -48,14 +56,14 @@ export default createConfig({
},
contracts: {
/**
* ATP Factory - Main contract
* ATP Factory - Genesis Sale contract
* Emits ATPCreated events when new ATP positions are created
*/
ATPFactory: {
chain: config.networkName,
abi: ATP_ABI,
address: config.ATP_FACTORY_ADDRESS as `0x${string}`,
startBlock: config.START_BLOCK,
startBlock: FACTORY_START_BLOCKS.genesis,
},

/**
Expand All @@ -66,7 +74,29 @@ export default createConfig({
chain: config.networkName,
abi: ATP_ABI,
address: config.ATP_FACTORY_AUCTION_ADDRESS as `0x${string}`,
startBlock: config.START_BLOCK,
startBlock: FACTORY_START_BLOCKS.auction,
},

/**
* ATP Factory - MATP contract
* Issues milestone-based ATPs (MATPs)
*/
ATPFactoryMATP: {
chain: config.networkName,
abi: ATP_ABI,
address: config.ATP_FACTORY_MATP_ADDRESS as `0x${string}`,
startBlock: FACTORY_START_BLOCKS.matp,
},

/**
* ATP Factory - LATP contract
* Issues linear vesting ATPs (LATPs) and MATPs
*/
ATPFactoryLATP: {
chain: config.networkName,
abi: ATP_ABI,
address: config.ATP_FACTORY_LATP_ADDRESS as `0x${string}`,
startBlock: FACTORY_START_BLOCKS.latp,
},

/**
Expand Down Expand Up @@ -94,7 +124,7 @@ export default createConfig({
/**
* Dynamic ATP Contracts
* Created by factory events, tracks operator updates
* Uses factory pattern to only index ATPs created by our factories
* Uses factory pattern to only index ATPs created by all 4 factories
*/
ATP: {
chain: config.networkName,
Expand All @@ -103,11 +133,14 @@ export default createConfig({
address: [
config.ATP_FACTORY_ADDRESS as `0x${string}`,
config.ATP_FACTORY_AUCTION_ADDRESS as `0x${string}`,
config.ATP_FACTORY_MATP_ADDRESS as `0x${string}`,
config.ATP_FACTORY_LATP_ADDRESS as `0x${string}`,
],
event: ATPCreatedEvent,
parameter: "atp",
}),
startBlock: config.START_BLOCK,
// Use earliest factory start block to capture all ATP contracts
startBlock: Math.min(...Object.values(FACTORY_START_BLOCKS)),
},

/**
Expand Down
2 changes: 2 additions & 0 deletions atp-indexer/ponder.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const atpPosition = onchainTable("atp_position", (t) => ({
type: atpType("type").notNull(),
stakerAddress: t.hex().notNull(),
operatorAddress: t.hex(),
factoryAddress: t.hex().notNull(), // Factory that created this ATP
blockNumber: t.bigint().notNull(),
txHash: t.hex().notNull(),
logIndex: t.integer().notNull(),
Expand All @@ -23,6 +24,7 @@ export const atpPosition = onchainTable("atp_position", (t) => ({
addressIdx: index().on(table.address),
beneficiaryIdx: index().on(table.beneficiary),
stakerAddressIdx: index().on(table.stakerAddress),
factoryAddressIdx: index().on(table.factoryAddress),
}));

export const atpPositionRelations = relations(atpPosition, ({ many }) => ({
Expand Down
1 change: 1 addition & 0 deletions atp-indexer/src/api/handlers/atp/beneficiary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export async function handleATPByBeneficiary(c: Context): Promise<Response> {
allocation: pos.allocation.toString(),
type: pos.type,
stakerAddress: checksumAddress(pos.stakerAddress),
factoryAddress: checksumAddress(pos.factoryAddress),
sequentialNumber: index + 1,
timestamp: Number(pos.timestamp),
totalWithdrawn: (withdrawalMap.get(normalizedAddress) ?? 0n).toString(),
Expand Down
1 change: 1 addition & 0 deletions atp-indexer/src/api/types/atp.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface ATPPosition {
allocation: string;
type: string;
stakerAddress: string;
factoryAddress: string; // Factory that created this ATP
sequentialNumber: number;
timestamp: number;
totalWithdrawn?: string;
Expand Down
4 changes: 4 additions & 0 deletions atp-indexer/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ const configSchema = z.object({
// Contract addresses
ATP_FACTORY_ADDRESS: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address format'),
ATP_FACTORY_AUCTION_ADDRESS: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address format'),
ATP_FACTORY_MATP_ADDRESS: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address format'),
ATP_FACTORY_LATP_ADDRESS: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address format'),
STAKING_REGISTRY_ADDRESS: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address format'),
ROLLUP_ADDRESS: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address format'),

// Indexer settings
START_BLOCK: z.string().transform(Number).refine(n => n >= 0, 'START_BLOCK must be non-negative').default('0'),
MATP_FACTORY_START_BLOCK: z.string().transform(Number).refine(n => n >= 0, 'MATP_FACTORY_START_BLOCK must be non-negative').optional(),
LATP_FACTORY_START_BLOCK: z.string().transform(Number).refine(n => n >= 0, 'LATP_FACTORY_START_BLOCK must be non-negative').optional(),

// Application
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
Expand Down
14 changes: 12 additions & 2 deletions atp-indexer/src/events/atp-factory/atp-created.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ async function handleATPCreated({ event, context }: IndexingFunctionArgs<'ATPFac

const atpType = await determineATPType(atp, client);
const stakerAddress = await getStakerAddress(atp, client);
const factoryAddress = event.log.address; // Factory contract that emitted the event

await db.insert(atpPosition).values({
id: normalizeAddress(atp),
Expand All @@ -71,19 +72,28 @@ async function handleATPCreated({ event, context }: IndexingFunctionArgs<'ATPFac
type: atpType,
stakerAddress: normalizeAddress(stakerAddress) as `0x${string}`,
operatorAddress: null,
factoryAddress: normalizeAddress(factoryAddress) as `0x${string}`,
blockNumber: event.block.number,
txHash: event.transaction.hash,
logIndex: event.log.logIndex,
timestamp: event.block.timestamp,
})

console.log(`${atpType} created (${source}): ${atp}`);
console.log(`${atpType} created (${source}): ${atp} from factory ${factoryAddress}`);
}

ponder.on("ATPFactory:ATPCreated", async (params) => {
await handleATPCreated(params, "factory");
await handleATPCreated(params, "genesis");
});

ponder.on("ATPFactoryAuction:ATPCreated", async (params) => {
await handleATPCreated(params, "auction");
});

ponder.on("ATPFactoryMATP:ATPCreated", async (params) => {
await handleATPCreated(params, "matp");
});

ponder.on("ATPFactoryLATP:ATPCreated", async (params) => {
await handleATPCreated(params, "latp");
});
4 changes: 4 additions & 0 deletions atp-indexer/terraform/app.tf
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ locals {
{ name = "ATP_FACTORY_ADDRESS", value = var.atp_factory_address },
{ name = "STAKING_REGISTRY_ADDRESS", value = var.staking_registry_address },
{ name = "ATP_FACTORY_AUCTION_ADDRESS", value = var.atp_factory_auction_address },
{ name = "ATP_FACTORY_MATP_ADDRESS", value = var.atp_factory_matp_address },
{ name = "ATP_FACTORY_LATP_ADDRESS", value = var.atp_factory_latp_address },
{ name = "ROLLUP_ADDRESS", value = var.rollup_address },
{ name = "POLLING_INTERVAL", value = var.polling_interval },
{ name = "MAX_RETRIES", value = var.max_retries },
Expand All @@ -282,6 +284,8 @@ locals {
{ name = "ATP_FACTORY_ADDRESS", value = var.atp_factory_address },
{ name = "STAKING_REGISTRY_ADDRESS", value = var.staking_registry_address },
{ name = "ATP_FACTORY_AUCTION_ADDRESS", value = var.atp_factory_auction_address },
{ name = "ATP_FACTORY_MATP_ADDRESS", value = var.atp_factory_matp_address },
{ name = "ATP_FACTORY_LATP_ADDRESS", value = var.atp_factory_latp_address },
{ name = "ROLLUP_ADDRESS", value = var.rollup_address },
{ name = "POLLING_INTERVAL", value = var.polling_interval },
{ name = "MAX_RETRIES", value = var.max_retries },
Expand Down
12 changes: 12 additions & 0 deletions atp-indexer/terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ variable "atp_factory_auction_address" {
default = ""
}

variable "atp_factory_matp_address" {
description = "ATP Factory MATP contract address"
type = string
default = ""
}

variable "atp_factory_latp_address" {
description = "ATP Factory LATP contract address"
type = string
default = ""
}

variable "staking_registry_address" {
description = "Staking Registry contract address"
type = string
Expand Down
2 changes: 1 addition & 1 deletion db-schemas.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"atp-indexer": {
"testnet": "atp-indexer-testnet-v03",
"prod": "atp-indexer-prod-v15"
"prod": "atp-indexer-prod-v16"
}
}
2 changes: 1 addition & 1 deletion staking-dashboard/src/components/ATPCard/ATPCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export default function ATPCard({
<div className={styles.milestoneBadgeRow}>
{atp.milestoneId !== undefined && (
<span className={styles.milestoneTag}>
Milestone {atp.milestoneId.toString()}
Milestone {Number(atp.milestoneId) + 1}
</span>
)}
<span
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ interface ATPDetailsDelegationItemProps {
providerRewardsRecipient: string
}) => void
onWithdrawSuccess?: () => void
// ATP context for milestone validation
atpType?: string
registryAddress?: Address
milestoneId?: bigint
}

/**
Expand All @@ -45,7 +49,10 @@ export const ATPDetailsDelegationItem = ({
stakerAddress,
rollupVersion,
onClaimClick,
onWithdrawSuccess
onWithdrawSuccess,
atpType,
registryAddress,
milestoneId
}: ATPDetailsDelegationItemProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const { symbol, decimals } = useStakingAssetTokenDetails()
Expand Down Expand Up @@ -488,6 +495,9 @@ export const ATPDetailsDelegationItem = ({
refetchStatus()
onWithdrawSuccess?.()
}}
atpType={atpType}
registryAddress={registryAddress}
milestoneId={milestoneId}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ interface ATPDetailsDirectStakeItemProps {
atp: ATPData
onClaimSuccess?: () => void
onWithdrawSuccess?: () => void
// ATP context for milestone validation
atpType?: string
registryAddress?: Address
milestoneId?: bigint
}

/**
* Individual self stake item component
* Displays sequencer address, transaction info, and links to explorers
*/
export const ATPDetailsDirectStakeItem = ({ stake, stakerAddress, rollupVersion, atp, onClaimSuccess, onWithdrawSuccess }: ATPDetailsDirectStakeItemProps) => {
export const ATPDetailsDirectStakeItem = ({ stake, stakerAddress, rollupVersion, atp, onClaimSuccess, onWithdrawSuccess, atpType, registryAddress, milestoneId }: ATPDetailsDirectStakeItemProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const [isClaimModalOpen, setIsClaimModalOpen] = useState(false)
const { symbol, decimals } = useStakingAssetTokenDetails()
Expand Down Expand Up @@ -393,6 +397,9 @@ export const ATPDetailsDirectStakeItem = ({ stake, stakerAddress, rollupVersion,
refetchStatus()
onWithdrawSuccess?.()
}}
atpType={atpType}
registryAddress={registryAddress}
milestoneId={milestoneId}
/>
)}
</>
Expand Down
Loading
Loading