feat: script to print and execute pending safe txs (#4704)
### Description feat: script to print and optionally execute pending safe txs ### Drive-by changes add optional chain selection array to `withChains` and `withChainsRequired` ### Related issues na ### Backward compatibility yes ### Testing Example without execution: ![image](https://github.com/user-attachments/assets/2bac68c6-e8fe-4aec-a005-f255259658da) Example with execution: ![image](https://github.com/user-attachments/assets/6a1729d5-a17d-4e33-b7c9-b1b02bc07073) ![image](https://github.com/user-attachments/assets/80ab11bb-fbf9-4409-97f8-34899d057e16) --------- Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com>pull/4864/merge
parent
3ce70725ee
commit
979bceb660
@ -0,0 +1,202 @@ |
||||
import { confirm } from '@inquirer/prompts'; |
||||
import chalk from 'chalk'; |
||||
import yargs from 'yargs'; |
||||
|
||||
import { MultiProvider } from '@hyperlane-xyz/sdk'; |
||||
|
||||
import { Contexts } from '../../config/contexts.js'; |
||||
import { safes } from '../../config/environments/mainnet3/owners.js'; |
||||
import { Role } from '../../src/roles.js'; |
||||
import { executeTx, getSafeAndService } from '../../src/utils/safe.js'; |
||||
import { withChains } from '../agent-utils.js'; |
||||
import { getEnvironmentConfig } from '../core-utils.js'; |
||||
|
||||
export enum SafeTxStatus { |
||||
NO_CONFIRMATIONS = '🔴', |
||||
PENDING = '🟡', |
||||
ONE_AWAY = '🔵', |
||||
READY_TO_EXECUTE = '🟢', |
||||
} |
||||
|
||||
type SafeStatus = { |
||||
chain: string; |
||||
nonce: number; |
||||
submissionDate: string; |
||||
shortTxHash: string; |
||||
fullTxHash: string; |
||||
confs: number; |
||||
threshold: number; |
||||
status: string; |
||||
}; |
||||
|
||||
export async function getPendingTxsForChains( |
||||
chains: string[], |
||||
multiProvider: MultiProvider, |
||||
): Promise<SafeStatus[]> { |
||||
const txs: SafeStatus[] = []; |
||||
await Promise.all( |
||||
chains.map(async (chain) => { |
||||
if (!safes[chain]) { |
||||
console.error(chalk.red.bold(`No safe found for ${chain}`)); |
||||
return; |
||||
} |
||||
|
||||
if (chain === 'endurance') { |
||||
console.info( |
||||
chalk.gray.italic( |
||||
`Skipping chain ${chain} as it does not have a functional safe API`, |
||||
), |
||||
); |
||||
return; |
||||
} |
||||
|
||||
let safeSdk, safeService; |
||||
try { |
||||
({ safeSdk, safeService } = await getSafeAndService( |
||||
chain, |
||||
multiProvider, |
||||
safes[chain], |
||||
)); |
||||
} catch (error) { |
||||
console.warn( |
||||
chalk.yellow( |
||||
`Skipping chain ${chain} as there was an error getting the safe service: ${error}`, |
||||
), |
||||
); |
||||
return; |
||||
} |
||||
|
||||
const threshold = await safeSdk.getThreshold(); |
||||
const pendingTxs = await safeService.getPendingTransactions(safes[chain]); |
||||
if (pendingTxs.results.length === 0) { |
||||
return; |
||||
} |
||||
|
||||
pendingTxs.results.forEach( |
||||
({ nonce, submissionDate, safeTxHash, confirmations }) => { |
||||
const confs = confirmations?.length ?? 0; |
||||
const status = |
||||
confs >= threshold |
||||
? SafeTxStatus.READY_TO_EXECUTE |
||||
: confs === 0 |
||||
? SafeTxStatus.NO_CONFIRMATIONS |
||||
: threshold - confs |
||||
? SafeTxStatus.ONE_AWAY |
||||
: SafeTxStatus.PENDING; |
||||
|
||||
txs.push({ |
||||
chain, |
||||
nonce, |
||||
submissionDate: new Date(submissionDate).toDateString(), |
||||
shortTxHash: `${safeTxHash.slice(0, 6)}...${safeTxHash.slice(-4)}`, |
||||
fullTxHash: safeTxHash, |
||||
confs, |
||||
threshold, |
||||
status, |
||||
}); |
||||
}, |
||||
); |
||||
}), |
||||
); |
||||
return txs.sort( |
||||
(a, b) => a.chain.localeCompare(b.chain) || a.nonce - b.nonce, |
||||
); |
||||
} |
||||
|
||||
async function main() { |
||||
const safeChains = Object.keys(safes); |
||||
const { chains, fullTxHash, execute } = await withChains( |
||||
yargs(process.argv.slice(2)), |
||||
safeChains, |
||||
) |
||||
.describe( |
||||
'fullTxHash', |
||||
'If enabled, include the full tx hash in the output', |
||||
) |
||||
.boolean('fullTxHash') |
||||
.default('fullTxHash', false) |
||||
.describe( |
||||
'execute', |
||||
'If enabled, execute transactions that have enough confirmations', |
||||
) |
||||
.boolean('execute') |
||||
.default('execute', false).argv; |
||||
|
||||
const chainsToCheck = chains || safeChains; |
||||
if (chainsToCheck.length === 0) { |
||||
console.error('No chains provided'); |
||||
process.exit(1); |
||||
} |
||||
|
||||
const envConfig = getEnvironmentConfig('mainnet3'); |
||||
const multiProvider = await envConfig.getMultiProvider( |
||||
Contexts.Hyperlane, |
||||
Role.Deployer, |
||||
true, |
||||
chainsToCheck, |
||||
); |
||||
|
||||
const pendingTxs = await getPendingTxsForChains(chainsToCheck, multiProvider); |
||||
if (pendingTxs.length === 0) { |
||||
console.info(chalk.green('No pending transactions found!')); |
||||
process.exit(0); |
||||
} |
||||
console.table(pendingTxs, [ |
||||
'chain', |
||||
'nonce', |
||||
'submissionDate', |
||||
fullTxHash ? 'fullTxHash' : 'shortTxHash', |
||||
'confs', |
||||
'threshold', |
||||
'status', |
||||
]); |
||||
|
||||
const executableTxs = pendingTxs.filter( |
||||
(tx) => tx.status === SafeTxStatus.READY_TO_EXECUTE, |
||||
); |
||||
if ( |
||||
executableTxs.length === 0 || |
||||
!execute || |
||||
!(await confirm({ |
||||
message: 'Execute transactions?', |
||||
default: execute, |
||||
})) |
||||
) { |
||||
console.info(chalk.green('No transactions to execute!')); |
||||
process.exit(0); |
||||
} else { |
||||
console.info(chalk.blueBright('Executing transactions...')); |
||||
} |
||||
|
||||
for (const tx of executableTxs) { |
||||
const confirmExecuteTx = await confirm({ |
||||
message: `Execute transaction ${tx.shortTxHash} on chain ${tx.chain}?`, |
||||
default: execute, |
||||
}); |
||||
if (confirmExecuteTx) { |
||||
console.log( |
||||
`Executing transaction ${tx.shortTxHash} on chain ${tx.chain}`, |
||||
); |
||||
try { |
||||
await executeTx( |
||||
tx.chain, |
||||
multiProvider, |
||||
safes[tx.chain], |
||||
tx.fullTxHash, |
||||
); |
||||
} catch (error) { |
||||
console.error(chalk.red(`Error executing transaction: ${error}`)); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
process.exit(0); |
||||
} |
||||
|
||||
main() |
||||
.then() |
||||
.catch((e) => { |
||||
console.error(e); |
||||
process.exit(1); |
||||
}); |
Loading…
Reference in new issue