Skip to content

Commit 8390cbc

Browse files
authored
feat: allow providing old mnemonics to collect pending TAPv1 RAVs (#1159)
Signed-off-by: Tomás Migone <tomas@edgeandnode.com>
1 parent 8e9bdb9 commit 8390cbc

File tree

7 files changed

+77
-2
lines changed

7 files changed

+77
-2
lines changed

docs/networks/arbitrum-one.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ All network contracts can be found in
4444
| `INDEXER_AGENT_INDEXER_ADDRESS` | `--indexer-address` | Ethereum address of mainnet indexer |
4545
| `INDEXER_AGENT_INDEXER_GEO_COORDINATES` | `--indexer-geo-coordinates` | Geo coordinates of mainnet indexer infrastructure |
4646
| `INDEXER_AGENT_MNEMONIC` | `--mnemonic` | Ethereum mnemonic for mainnet operator |
47+
| `INDEXER_AGENT_LEGACY_MNEMONICS` | `--legacy-mnemonics` | Legacy operator mnemonics for collecting TAPv1 RAVs after operator rotation (pipe-separated) |
4748
| `INDEXER_AGENT_GATEWAY_ENDPOINT` | `--gateway-endpoint` | `https://gateway-arbitrum.network.thegraph.com/` |
4849
| `INDEXER_AGENT_GAS_PRICE_MAX` | `--gas-price-max` | `50` |
4950
| `INDEXER_AGENT_NETWORK_SUBGRAPH_DEPLOYMENT` | `--network-subgraph-deployment` | `QmU5tKrP7YNpm69iUdC3YuQWfJmdye1AHJLLc5pRC4dQBv` |

docs/networks/arbitrum-sepolia.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ testnet (for now) are Mainnet subgraphs. This means:
5050
| `INDEXER_AGENT_INDEXER_ADDRESS` | `--indexer-address` | Ethereum address of testnet indexer |
5151
| `INDEXER_AGENT_INDEXER_GEO_COORDINATES` | `--indexer-geo-coordinates` | Geo coordinates of testnet indexer infrastructure |
5252
| `INDEXER_AGENT_MNEMONIC` | `--mnemonic` | Ethereum mnemonic for testnet operator |
53+
| `INDEXER_AGENT_LEGACY_MNEMONICS` | `--legacy-mnemonics` | Legacy operator mnemonics for collecting TAPv1 RAVs after operator rotation (pipe-separated) |
5354
| `INDEXER_AGENT_GATEWAY_ENDPOINT` | `--gateway-endpoint` | `https://gateway.testnet.thegraph.com/` |
5455
| `INDEXER_AGENT_NETWORK_SUBGRAPH_DEPLOYMENT` | `--network-subgraph-deployment` | `QmU1cnG7azpYU3BMBZaHR36kVo5YB3fMjJwxKtwa6L5GFK` |
5556
| `INDEXER_AGENT_NETWORK_SUBGRAPH_ENDPOINT` | `--network-subgraph-endpoint` | `https://gateway.network.thegraph.com/api/[api-key]/subgraphs/id/3xQHhMudr1oh69ut36G2mbzpYmYxwqCeU6wwqyCDCnqV` |

packages/indexer-agent/src/commands/start.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,27 @@ export const start = {
112112
required: true,
113113
group: 'Ethereum',
114114
})
115+
.option('legacy-mnemonics', {
116+
description:
117+
'Legacy operator mnemonics for collecting TAPv1 RAVs from previous operators. ' +
118+
'Via CLI: use multiple --legacy-mnemonics flags. ' +
119+
'Via env var: separate mnemonics with | (pipe character).',
120+
type: 'string',
121+
array: true,
122+
default: [],
123+
group: 'Ethereum',
124+
coerce: (value: string | string[]): string[] => {
125+
if (typeof value === 'string') {
126+
// Environment variable: split by pipe delimiter
127+
return value
128+
.split('|')
129+
.map(m => m.trim())
130+
.filter(m => m.length > 0)
131+
}
132+
// CLI: already an array
133+
return value.filter(m => m.length > 0)
134+
},
135+
})
115136
.option('indexer-address', {
116137
description: 'Ethereum address of the indexer',
117138
type: 'string',
@@ -391,6 +412,7 @@ export async function createNetworkSpecification(
391412
register: argv.register,
392413
maxProvisionInitialSize: argv.maxProvisionInitialSize,
393414
finalityTime: argv.chainFinalizeTime,
415+
legacyMnemonics: argv.legacyMnemonics,
394416
}
395417

396418
const transactionMonitoring = {

packages/indexer-common/src/allocations/__tests__/tap-pagination.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ const setup = () => {
170170

171171
networkSubgraph,
172172
tapSubgraph,
173+
legacyMnemonics: [],
173174
})
174175
}
175176
}

packages/indexer-common/src/allocations/tap-collector.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import pReduce from 'p-reduce'
2727
import { SubgraphClient, QueryResult } from '../subgraph-client'
2828
import gql from 'graphql-tag'
2929
import { getEscrowAccounts } from './escrow-accounts'
30+
import { HDNodeWallet, Wallet } from 'ethers'
3031

3132
// every 15 minutes
3233
const RAV_CHECK_INTERVAL_MS = 900_000
@@ -52,6 +53,7 @@ interface TapCollectorOptions {
5253
networkSpecification: spec.NetworkSpecification
5354
tapSubgraph: SubgraphClient
5455
networkSubgraph: SubgraphClient
56+
legacyMnemonics: string[]
5557
}
5658

5759
interface ValidRavs {
@@ -109,6 +111,7 @@ export class TapCollector {
109111
declare networkSubgraph: SubgraphClient
110112
declare finalityTime: number
111113
declare indexerAddress: Address
114+
declare legacyMnemonics: string[]
112115

113116
// eslint-disable-next-line @typescript-eslint/no-empty-function -- Private constructor to prevent direct instantiation
114117
private constructor() {}
@@ -123,6 +126,7 @@ export class TapCollector {
123126
networkSpecification,
124127
tapSubgraph,
125128
networkSubgraph,
129+
legacyMnemonics,
126130
}: TapCollectorOptions): TapCollector {
127131
const collector = new TapCollector()
128132
collector.logger = logger.child({ component: 'TapCollector' })
@@ -137,14 +141,21 @@ export class TapCollector {
137141
collector.protocolNetwork = networkSpecification.networkIdentifier
138142
collector.tapSubgraph = tapSubgraph
139143
collector.networkSubgraph = networkSubgraph
144+
collector.legacyMnemonics = legacyMnemonics
140145

141146
const { voucherRedemptionThreshold, finalityTime, address } =
142147
networkSpecification.indexerOptions
143148
collector.ravRedemptionThreshold = voucherRedemptionThreshold
144149
collector.finalityTime = finalityTime
145150
collector.indexerAddress = address
146151

147-
collector.logger.info(`[TAPv1] RAV processing is initiated`)
152+
if (legacyMnemonics.length > 0) {
153+
collector.logger.info(
154+
`[TAPv1] RAV processing is initiated with ${legacyMnemonics.length} legacy mnemonic(s) for old allocation support`,
155+
)
156+
} else {
157+
collector.logger.info(`[TAPv1] RAV processing is initiated`)
158+
}
148159
collector.startRAVProcessing()
149160
return collector
150161
}
@@ -712,6 +723,37 @@ export class TapCollector {
712723
)
713724
}
714725

726+
private getAllocationSigner(allocation: Allocation): {
727+
signer: ReturnType<typeof allocationSigner>
728+
isLegacy: boolean
729+
} {
730+
// Try current wallet first
731+
try {
732+
return {
733+
signer: allocationSigner(this.transactionManager.wallet, allocation),
734+
isLegacy: false,
735+
}
736+
} catch {
737+
// Current wallet doesn't match, try legacy mnemonics
738+
}
739+
740+
// Try legacy mnemonics
741+
for (const mnemonic of this.legacyMnemonics) {
742+
try {
743+
const legacyWallet = Wallet.fromPhrase(mnemonic) as HDNodeWallet
744+
return { signer: allocationSigner(legacyWallet, allocation), isLegacy: true }
745+
} catch {
746+
// This mnemonic doesn't match either, try next
747+
continue
748+
}
749+
}
750+
751+
throw new Error(
752+
`[TAPv1] No mnemonic found that can sign for allocation ${allocation.id}. ` +
753+
`Tried current operator wallet and ${this.legacyMnemonics.length} legacy mnemonic(s).`,
754+
)
755+
}
756+
715757
public async redeemRav(
716758
logger: Logger,
717759
allocation: Allocation,
@@ -722,8 +764,13 @@ export class TapCollector {
722764

723765
const escrow = this.tapContracts
724766

767+
const { signer, isLegacy } = this.getAllocationSigner(allocation)
768+
if (isLegacy) {
769+
logger.info(`[TAPv1] Using legacy mnemonic to sign for allocation ${allocation.id}`)
770+
}
771+
725772
const proof = await tapAllocationIdProof(
726-
allocationSigner(this.transactionManager.wallet, allocation),
773+
signer,
727774
parseInt(this.protocolNetwork.split(':')[1]),
728775
sender,
729776
toAddress(rav.allocationId.toString()),
@@ -732,6 +779,7 @@ export class TapCollector {
732779
this.logger.debug(`[TAPv1] Computed allocationIdProof`, {
733780
allocationId: rav.allocationId,
734781
proof,
782+
isLegacySigner: isLegacy,
735783
})
736784
// Submit the signed RAV on chain
737785
const txReceipt = await this.transactionManager.executeTransaction(

packages/indexer-common/src/network-specification.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const IndexerOptions = z
6969
})
7070
.default(0),
7171
finalityTime: positiveNumber().default(3600),
72+
legacyMnemonics: z.array(z.string()).default([]),
7273
})
7374
.strict()
7475
export type IndexerOptions = z.infer<typeof IndexerOptions>

packages/indexer-common/src/network.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ export class Network {
316316
networkSpecification: specification,
317317
tapSubgraph,
318318
networkSubgraph,
319+
legacyMnemonics: specification.indexerOptions.legacyMnemonics,
319320
})
320321
} else {
321322
logger.info(`RAV process not initiated.

0 commit comments

Comments
 (0)