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: 1 addition & 0 deletions docs/networks/arbitrum-one.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ All network contracts can be found in
| `INDEXER_AGENT_INDEXER_ADDRESS` | `--indexer-address` | Ethereum address of mainnet indexer |
| `INDEXER_AGENT_INDEXER_GEO_COORDINATES` | `--indexer-geo-coordinates` | Geo coordinates of mainnet indexer infrastructure |
| `INDEXER_AGENT_MNEMONIC` | `--mnemonic` | Ethereum mnemonic for mainnet operator |
| `INDEXER_AGENT_LEGACY_MNEMONICS` | `--legacy-mnemonics` | Legacy operator mnemonics for collecting TAPv1 RAVs after operator rotation (pipe-separated) |
| `INDEXER_AGENT_GATEWAY_ENDPOINT` | `--gateway-endpoint` | `https://gateway-arbitrum.network.thegraph.com/` |
| `INDEXER_AGENT_GAS_PRICE_MAX` | `--gas-price-max` | `50` |
| `INDEXER_AGENT_NETWORK_SUBGRAPH_DEPLOYMENT` | `--network-subgraph-deployment` | `QmU5tKrP7YNpm69iUdC3YuQWfJmdye1AHJLLc5pRC4dQBv` |
Expand Down
1 change: 1 addition & 0 deletions docs/networks/arbitrum-sepolia.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ testnet (for now) are Mainnet subgraphs. This means:
| `INDEXER_AGENT_INDEXER_ADDRESS` | `--indexer-address` | Ethereum address of testnet indexer |
| `INDEXER_AGENT_INDEXER_GEO_COORDINATES` | `--indexer-geo-coordinates` | Geo coordinates of testnet indexer infrastructure |
| `INDEXER_AGENT_MNEMONIC` | `--mnemonic` | Ethereum mnemonic for testnet operator |
| `INDEXER_AGENT_LEGACY_MNEMONICS` | `--legacy-mnemonics` | Legacy operator mnemonics for collecting TAPv1 RAVs after operator rotation (pipe-separated) |
| `INDEXER_AGENT_GATEWAY_ENDPOINT` | `--gateway-endpoint` | `https://gateway.testnet.thegraph.com/` |
| `INDEXER_AGENT_NETWORK_SUBGRAPH_DEPLOYMENT` | `--network-subgraph-deployment` | `QmU1cnG7azpYU3BMBZaHR36kVo5YB3fMjJwxKtwa6L5GFK` |
| `INDEXER_AGENT_NETWORK_SUBGRAPH_ENDPOINT` | `--network-subgraph-endpoint` | `https://gateway.network.thegraph.com/api/[api-key]/subgraphs/id/3xQHhMudr1oh69ut36G2mbzpYmYxwqCeU6wwqyCDCnqV` |
Expand Down
22 changes: 22 additions & 0 deletions packages/indexer-agent/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,27 @@ export const start = {
required: true,
group: 'Ethereum',
})
.option('legacy-mnemonics', {
description:
'Legacy operator mnemonics for collecting TAPv1 RAVs from previous operators. ' +
'Via CLI: use multiple --legacy-mnemonics flags. ' +
'Via env var: separate mnemonics with | (pipe character).',
type: 'string',
array: true,
default: [],
group: 'Ethereum',
coerce: (value: string | string[]): string[] => {
if (typeof value === 'string') {
// Environment variable: split by pipe delimiter
return value
.split('|')
.map(m => m.trim())
.filter(m => m.length > 0)
}
// CLI: already an array
return value.filter(m => m.length > 0)
},
})
.option('indexer-address', {
description: 'Ethereum address of the indexer',
type: 'string',
Expand Down Expand Up @@ -391,6 +412,7 @@ export async function createNetworkSpecification(
register: argv.register,
maxProvisionInitialSize: argv.maxProvisionInitialSize,
finalityTime: argv.chainFinalizeTime,
legacyMnemonics: argv.legacyMnemonics,
}

const transactionMonitoring = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ const setup = () => {

networkSubgraph,
tapSubgraph,
legacyMnemonics: [],
})
}
}
Expand Down
52 changes: 50 additions & 2 deletions packages/indexer-common/src/allocations/tap-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import pReduce from 'p-reduce'
import { SubgraphClient, QueryResult } from '../subgraph-client'
import gql from 'graphql-tag'
import { getEscrowAccounts } from './escrow-accounts'
import { HDNodeWallet, Wallet } from 'ethers'

// every 15 minutes
const RAV_CHECK_INTERVAL_MS = 900_000
Expand All @@ -52,6 +53,7 @@ interface TapCollectorOptions {
networkSpecification: spec.NetworkSpecification
tapSubgraph: SubgraphClient
networkSubgraph: SubgraphClient
legacyMnemonics: string[]
}

interface ValidRavs {
Expand Down Expand Up @@ -109,6 +111,7 @@ export class TapCollector {
declare networkSubgraph: SubgraphClient
declare finalityTime: number
declare indexerAddress: Address
declare legacyMnemonics: string[]

// eslint-disable-next-line @typescript-eslint/no-empty-function -- Private constructor to prevent direct instantiation
private constructor() {}
Expand All @@ -123,6 +126,7 @@ export class TapCollector {
networkSpecification,
tapSubgraph,
networkSubgraph,
legacyMnemonics,
}: TapCollectorOptions): TapCollector {
const collector = new TapCollector()
collector.logger = logger.child({ component: 'TapCollector' })
Expand All @@ -137,14 +141,21 @@ export class TapCollector {
collector.protocolNetwork = networkSpecification.networkIdentifier
collector.tapSubgraph = tapSubgraph
collector.networkSubgraph = networkSubgraph
collector.legacyMnemonics = legacyMnemonics

const { voucherRedemptionThreshold, finalityTime, address } =
networkSpecification.indexerOptions
collector.ravRedemptionThreshold = voucherRedemptionThreshold
collector.finalityTime = finalityTime
collector.indexerAddress = address

collector.logger.info(`[TAPv1] RAV processing is initiated`)
if (legacyMnemonics.length > 0) {
collector.logger.info(
`[TAPv1] RAV processing is initiated with ${legacyMnemonics.length} legacy mnemonic(s) for old allocation support`,
)
} else {
collector.logger.info(`[TAPv1] RAV processing is initiated`)
}
collector.startRAVProcessing()
return collector
}
Expand Down Expand Up @@ -712,6 +723,37 @@ export class TapCollector {
)
}

private getAllocationSigner(allocation: Allocation): {
signer: ReturnType<typeof allocationSigner>
isLegacy: boolean
} {
// Try current wallet first
try {
return {
signer: allocationSigner(this.transactionManager.wallet, allocation),
isLegacy: false,
}
} catch {
// Current wallet doesn't match, try legacy mnemonics
}

// Try legacy mnemonics
for (const mnemonic of this.legacyMnemonics) {
try {
const legacyWallet = Wallet.fromPhrase(mnemonic) as HDNodeWallet
return { signer: allocationSigner(legacyWallet, allocation), isLegacy: true }
} catch {
// This mnemonic doesn't match either, try next
continue
}
}

throw new Error(
`[TAPv1] No mnemonic found that can sign for allocation ${allocation.id}. ` +
`Tried current operator wallet and ${this.legacyMnemonics.length} legacy mnemonic(s).`,
)
}

public async redeemRav(
logger: Logger,
allocation: Allocation,
Expand All @@ -722,8 +764,13 @@ export class TapCollector {

const escrow = this.tapContracts

const { signer, isLegacy } = this.getAllocationSigner(allocation)
if (isLegacy) {
logger.info(`[TAPv1] Using legacy mnemonic to sign for allocation ${allocation.id}`)
}

const proof = await tapAllocationIdProof(
allocationSigner(this.transactionManager.wallet, allocation),
signer,
parseInt(this.protocolNetwork.split(':')[1]),
sender,
toAddress(rav.allocationId.toString()),
Expand All @@ -732,6 +779,7 @@ export class TapCollector {
this.logger.debug(`[TAPv1] Computed allocationIdProof`, {
allocationId: rav.allocationId,
proof,
isLegacySigner: isLegacy,
})
// Submit the signed RAV on chain
const txReceipt = await this.transactionManager.executeTransaction(
Expand Down
1 change: 1 addition & 0 deletions packages/indexer-common/src/network-specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const IndexerOptions = z
})
.default(0),
finalityTime: positiveNumber().default(3600),
legacyMnemonics: z.array(z.string()).default([]),
})
.strict()
export type IndexerOptions = z.infer<typeof IndexerOptions>
Expand Down
1 change: 1 addition & 0 deletions packages/indexer-common/src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ export class Network {
networkSpecification: specification,
tapSubgraph,
networkSubgraph,
legacyMnemonics: specification.indexerOptions.legacyMnemonics,
})
} else {
logger.info(`RAV process not initiated.
Expand Down
Loading