diff --git a/.registryrc b/.registryrc index e2d1700ce1..82c0369f03 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -9e900994370ccc702b84c332eb4a0479bfeabe07 +80023dd9e9fadd4aa9fd868b5e7ad76dc3082494 diff --git a/rust/main/agents/relayer/src/msg/metadata/base.rs b/rust/main/agents/relayer/src/msg/metadata/base.rs index b2e4e5d185..4bc55457f9 100644 --- a/rust/main/agents/relayer/src/msg/metadata/base.rs +++ b/rust/main/agents/relayer/src/msg/metadata/base.rs @@ -367,6 +367,7 @@ impl BaseMetadataBuilder { pub async fn build_checkpoint_syncer( &self, + message: &HyperlaneMessage, validators: &[H256], app_context: Option, ) -> Result { @@ -375,9 +376,18 @@ impl BaseMetadataBuilder { .get_announced_storage_locations(validators) .await?; + debug!( + hyp_message=?message, + ?validators, + validators_len = ?validators.len(), + ?storage_locations, + storage_locations_len = ?storage_locations.len(), + "List of validators and their storage locations for message"); + // Only use the most recently announced location for now. let mut checkpoint_syncers: HashMap> = HashMap::new(); for (&validator, validator_storage_locations) in validators.iter().zip(storage_locations) { + debug!(hyp_message=?message, ?validator, ?validator_storage_locations, "Validator and its storage locations for message"); for storage_location in validator_storage_locations.iter().rev() { let Ok(config) = CheckpointSyncerConf::from_str(storage_location) else { debug!( diff --git a/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs b/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs index f7898c7212..233f7b7f77 100644 --- a/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs +++ b/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs @@ -115,9 +115,11 @@ impl MetadataBuilder for T { return Ok(None); } + info!(hyp_message=?message, ?validators, threshold, "List of validators and threshold for message"); + let checkpoint_syncer = self .as_ref() - .build_checkpoint_syncer(&validators, self.as_ref().app_context.clone()) + .build_checkpoint_syncer(message, &validators, self.as_ref().app_context.clone()) .await .context(CTX)?; diff --git a/rust/main/hyperlane-base/src/types/multisig.rs b/rust/main/hyperlane-base/src/types/multisig.rs index dc7878af39..35fe371509 100644 --- a/rust/main/hyperlane-base/src/types/multisig.rs +++ b/rust/main/hyperlane-base/src/types/multisig.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use derive_new::new; use eyre::Result; -use tracing::{debug, instrument}; +use tracing::{debug, instrument, warn}; use hyperlane_core::{ HyperlaneDomain, MultisigSignedCheckpoint, SignedCheckpointWithMessageId, H160, H256, @@ -40,6 +40,10 @@ impl MultisigCheckpointSyncer { for validator in validators { let address = H160::from(*validator); + debug!( + ?address, + "Getting latest checkpoint from validator via checkpoint syncer", + ); if let Some(checkpoint_syncer) = self.checkpoint_syncers.get(&address) { // Gracefully handle errors getting the latest_index match checkpoint_syncer.latest_index().await { @@ -56,6 +60,8 @@ impl MultisigCheckpointSyncer { latest_indices.insert(H160::from(*validator), None); } } + } else { + warn!(?address, "Checkpoint syncer is not provided for validator"); } } diff --git a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json index b743b5326d..6e0a0c3dba 100644 --- a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json +++ b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json @@ -942,6 +942,15 @@ "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb" ] }, + "trumpchain": { + "type": "messageIdMultisigIsm", + "threshold": 2, + "validators": [ + "0x3ada634c8dfa57a67f5f22ca757b35cde6cfab5e", + "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0xcF8151b8aEFfF4e22F6B48fe2Ffe2d60F00C890C" + ] + }, "unichain": { "type": "messageIdMultisigIsm", "threshold": 2, diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-base-solanamainnet/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-arbitrum-avalanche-base-flowmainnet-form-solanamainnet-worldchain/program-ids.json similarity index 87% rename from rust/sealevel/environments/mainnet3/warp-routes/TRUMP-base-solanamainnet/program-ids.json rename to rust/sealevel/environments/mainnet3/warp-routes/TRUMP-arbitrum-avalanche-base-flowmainnet-form-solanamainnet-worldchain/program-ids.json index 8bb6368e91..fb614bb48c 100644 --- a/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-base-solanamainnet/program-ids.json +++ b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-arbitrum-avalanche-base-flowmainnet-form-solanamainnet-worldchain/program-ids.json @@ -1,11 +1,7 @@ { - "form": { - "hex": "0x0000000000000000000000008528baa7d1d386e7967603e480fa2b558a23644c", - "base58": "1111111111112rbf8mSSJDBCxvjCyPAButjBen7u" - }, - "worldchain": { - "hex": "0x0000000000000000000000000fc7b3518c03bfa5e01995285b1ef3c4b55c8922", - "base58": "111111111111DkZ1qm7hv57Sa8ZhNG9yo9cp2qP" + "arbitrum": { + "hex": "0x0000000000000000000000005155eb1bcd30189915cf84717550acfa537068bf", + "base58": "11111111111128itaWLcGgPMeaS1ccv916mk2HT4" }, "avalanche": { "hex": "0x0000000000000000000000000e2a546a53678ee8f8605748193a8c114fa0317f", @@ -15,16 +11,24 @@ "hex": "0x000000000000000000000000d3378b419feae4e3a4bb4f3349dba43a1b511760", "base58": "1111111111113wfw1fbmfxSpYvFU6NfBtUNJ5q7u" }, - "base": { - "hex": "0x00000000000000000000000053c0499e7e4abd3e7994ca161523fd50a12bb8c8", - "base58": "1111111111112Ag6Vg3QoPPGirwYeTWBQWR9VmcB" + "optimism": { + "hex": "0x000000000000000000000000e36c02471e708a9f16da58168da744b059a1c6fe", + "base58": "1111111111114AmPuC4vLYgpwQK9Z4vycDaDMQWq" }, - "arbitrum": { - "hex": "0x0000000000000000000000005155eb1bcd30189915cf84717550acfa537068bf", - "base58": "11111111111128itaWLcGgPMeaS1ccv916mk2HT4" + "worldchain": { + "hex": "0x0000000000000000000000000fc7b3518c03bfa5e01995285b1ef3c4b55c8922", + "base58": "111111111111DkZ1qm7hv57Sa8ZhNG9yo9cp2qP" + }, + "form": { + "hex": "0x0000000000000000000000008528baa7d1d386e7967603e480fa2b558a23644c", + "base58": "1111111111112rbf8mSSJDBCxvjCyPAButjBen7u" }, "solanamainnet": { "hex": "0x0f1589e8a3b501fb3eedfddd2eed7a262ef34b2292ae9bb86ab08f372c752a29", "base58": "21tAY4poz2VXvghqdSQpn9j7gYravQmGpuQi8pHPx9DS" + }, + "base": { + "hex": "0x00000000000000000000000053c0499e7e4abd3e7994ca161523fd50a12bb8c8", + "base58": "1111111111112Ag6Vg3QoPPGirwYeTWBQWR9VmcB" } } \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-base-solanamainnet/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-arbitrum-avalanche-base-flowmainnet-form-solanamainnet-worldchain/token-config.json similarity index 87% rename from rust/sealevel/environments/mainnet3/warp-routes/TRUMP-base-solanamainnet/token-config.json rename to rust/sealevel/environments/mainnet3/warp-routes/TRUMP-arbitrum-avalanche-base-flowmainnet-form-solanamainnet-worldchain/token-config.json index a8e979c981..9e4ca5a170 100644 --- a/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-base-solanamainnet/token-config.json +++ b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-arbitrum-avalanche-base-flowmainnet-form-solanamainnet-worldchain/token-config.json @@ -48,5 +48,12 @@ "foreignDeployment": "0x0fC7b3518C03BfA5e01995285b1eF3c4B55c8922", "name": "OFFICIAL TRUMP", "symbol": "TRUMP" + }, + "optimism": { + "type": "synthetic", + "decimals": 18, + "foreignDeployment": "0xe36c02471e708a9f16da58168da744b059a1c6fe", + "name": "OFFICIAL TRUMP", + "symbol": "TRUMP" } -} \ No newline at end of file +} diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-solanamainnet-trumpchain/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-solanamainnet-trumpchain/program-ids.json new file mode 100644 index 0000000000..4064524c22 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-solanamainnet-trumpchain/program-ids.json @@ -0,0 +1,10 @@ +{ + "trumpchain": { + "hex": "0x000000000000000000000000d84981ecd4c411a86e1ccda77f944c8f3d9737ab", + "base58": "11111111111141mYwaip4dprGDBXbJZAr5Bs17qL" + }, + "solanamainnet": { + "hex": "0x9425c4caefa3e2a616aac1f4f53a05c34107dd2f28ea028702fe2b2c3877d520", + "base58": "AyJk3V2SjswRv1ppDazVofXnFgjCTeydNEDSyA2UyVNX" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-solanamainnet-trumpchain/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-solanamainnet-trumpchain/token-config.json new file mode 100644 index 0000000000..a6a16b5f58 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/TRUMP-solanamainnet-trumpchain/token-config.json @@ -0,0 +1,17 @@ +{ + "solanamainnet": { + "type": "collateral", + "decimals": 6, + "remoteDecimals": 18, + "token": "6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN", + "interchainGasPaymaster": "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF", + "splTokenProgram": "token" + }, + "trumpchain": { + "type": "native", + "decimals": 18, + "foreignDeployment": "0xD84981Ecd4c411A86E1Ccda77F944c8f3D9737ab", + "name": "OFFICIAL TRUMP", + "symbol": "TRUMP" + } +} diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index c740beb354..be7f84bd09 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -678,7 +678,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '95deca3-20250120-103609', + tag: '359ce5d-20250121-133827', }, resources: scraperResources, }, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts index 32313ecf76..46f0258c09 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts @@ -9,6 +9,42 @@ import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; import { DEPLOYER } from '../../owners.js'; import { SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT } from '../consts.js'; +export const getTrumpchainTRUMPWarpConfig = async ( + routerConfig: ChainMap, + _abacusWorksEnvOwnerConfig: ChainMap, +): Promise> => { + const name = 'OFFICIAL TRUMP'; + const symbol = 'TRUMP'; + const totalSupply = 0; + const tokenConfig: ChainMap = { + solanamainnet: { + ...routerConfig.solanamainnet, + type: TokenType.collateral, + name, + symbol, + totalSupply, + token: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', + owner: DEPLOYER, + gas: SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT, + foreignDeployment: 'AyJk3V2SjswRv1ppDazVofXnFgjCTeydNEDSyA2UyVNX', + }, + trumpchain: { + ...routerConfig.trumpchain, + type: TokenType.native, + name, + symbol, + decimals: 18, + totalSupply, + owner: DEPLOYER, + proxyAdmin: { + owner: DEPLOYER, + address: '0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB', + }, + }, + }; + return tokenConfig; +}; + export const getTRUMPWarpConfig = async ( routerConfig: ChainMap, abacusWorksEnvOwnerConfig: ChainMap, @@ -28,7 +64,6 @@ export const getTRUMPWarpConfig = async ( type: TokenType.collateral, name, symbol, - decimals: 6, totalSupply, token: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', owner: DEPLOYER, @@ -49,9 +84,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.arbitrum, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.arbitrum.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.arbitrum.owner, address: '0x2350389Ea8649Da5dD4Fdd09c414dD8463C2695c', }, }, @@ -59,9 +94,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.avalanche, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.avalanche.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.avalanche.owner, address: '0x86a2E32BB42584127a24079a4f9113EeFE80db90', }, }, @@ -69,9 +104,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.flowmainnet, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.flowmainnet.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.flowmainnet.owner, address: '0xB504EA900302C7Faf24Cc4F155006d6c0357Dc35', }, }, @@ -79,9 +114,9 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.form, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.form.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.form.owner, address: '0x5b3EeADcc0E2d4284eA6816e2E503c24d30a9E54', }, }, @@ -89,12 +124,22 @@ export const getTRUMPWarpConfig = async ( ...routerConfig.worldchain, ...syntheticToken, type: TokenType.synthetic, - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.worldchain.owner, proxyAdmin: { - owner: DEPLOYER, + owner: abacusWorksEnvOwnerConfig.worldchain.owner, address: '0x97e4682dBC4Bfd432F1563a7fa9aC218Bc48c861', }, }, + optimism: { + ...routerConfig.optimism, + ...syntheticToken, + type: TokenType.synthetic, + owner: abacusWorksEnvOwnerConfig.optimism.owner, + proxyAdmin: { + owner: abacusWorksEnvOwnerConfig.optimism.owner, + address: '0x6Fa52E2Fc86B200e7b80394e226929C1f9Ff2950', + }, + }, }; return tokenConfig; }; diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index 1754493255..3e8ea485e5 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -9,7 +9,8 @@ export enum WarpRouteIds { ArtelaBaseSolanaART = 'ART/artela-base-solanamainnet', BscEthereumLumiaPrismPNDR = 'PNDR/bsc-ethereum-lumiaprism', BaseSolanamainnetTONY = 'TONY/base-solanamainnet', - ArbitrumAvalancheBaseFlowmainnetFormSolanamainnetWorldchainTRUMP = 'TRUMP/arbitrum-avalanche-base-flowmainnet-form-solanamainnet-worldchain', + ArbitrumAvalancheBaseFlowmainnetFormOptimismSolanamainnetWorldchainTRUMP = 'TRUMP/arbitrum-avalanche-base-flowmainnet-form-optimism-solanamainnet-worldchain', + SolanamainnetTrumpchainTRUMP = 'TRUMP/solanamainnet-trumpchain', EclipseEthereumApxEth = 'APXETH/eclipsemainnet-ethereum', EclipseEthereumSolanaUSDC = 'USDC/eclipsemainnet-ethereum-solanamainnet', EclipseEthereumSolanaUSDT = 'USDT/eclipsemainnet-ethereum-solanamainnet', diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index d83fa93270..6374d05ae7 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -25,7 +25,10 @@ import { getArtelaBaseUSDCWarpConfig } from './environments/mainnet3/warp/config import { getArtelaBaseWETHWarpConfig } from './environments/mainnet3/warp/configGetters/getArtelaBaseWETHWarpConfig.js'; import { getBaseFormAIXBTWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseFormAIXBTWarpConfig.js'; import { getBaseFormGAMEWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseFormGAMEWarpConfig.js'; -import { getTRUMPWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.js'; +import { + getTRUMPWarpConfig, + getTrumpchainTRUMPWarpConfig, +} from './environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.js'; import { getBaseSolanamainnetTONYWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseSolanamainnetTONYWarpConfig.js'; import { getBaseZeroNetworkCBBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseZeroNetworkCBBTCWarpConfig.js'; import { getBobaBsquaredSwellUBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getBobaBsquaredSwellUBTCWarpConfig.js'; @@ -79,8 +82,9 @@ export const warpConfigGetterMap: Record = { getRenzoEZETHWarpConfig, [WarpRouteIds.InevmInjectiveINJ]: getInevmInjectiveINJWarpConfig, [WarpRouteIds.BaseSolanamainnetTONY]: getBaseSolanamainnetTONYWarpConfig, - [WarpRouteIds.ArbitrumAvalancheBaseFlowmainnetFormSolanamainnetWorldchainTRUMP]: + [WarpRouteIds.ArbitrumAvalancheBaseFlowmainnetFormOptimismSolanamainnetWorldchainTRUMP]: getTRUMPWarpConfig, + [WarpRouteIds.SolanamainnetTrumpchainTRUMP]: getTrumpchainTRUMPWarpConfig, [WarpRouteIds.BscEthereumLumiaPrismPNDR]: getBscEthereumLumiaPrismPNDRWarpConfig, [WarpRouteIds.EthereumFlowCbBTC]: getEthereumFlowCbBTCWarpConfig, diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 2b0a788b2b..260c51956b 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -286,15 +286,6 @@ export function withRpcUrls(args: Argv) { .alias('r', 'rpcUrls'); } -export function withTxHashes(args: Argv) { - return args - .describe('txHashes', 'transaction hash') - .string('txHashes') - .array('txHashes') - .demandOption('txHashes') - .alias('t', 'txHashes'); -} - // Interactively gets a single warp route ID export async function getWarpRouteIdInteractive() { const choices = Object.values(WarpRouteIds).map((id) => ({ diff --git a/typescript/infra/scripts/safes/get-pending-txs.ts b/typescript/infra/scripts/safes/get-pending-txs.ts index 13d8c15d88..ead1b69c74 100644 --- a/typescript/infra/scripts/safes/get-pending-txs.ts +++ b/typescript/infra/scripts/safes/get-pending-txs.ts @@ -1,118 +1,25 @@ import { confirm } from '@inquirer/prompts'; import chalk from 'chalk'; -import { formatUnits } from 'ethers/lib/utils.js'; import yargs from 'yargs'; -import { MultiProvider } from '@hyperlane-xyz/sdk'; -import { LogFormat, LogLevel, configureRootLogger } from '@hyperlane-xyz/utils'; +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; 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 { + SafeTxStatus, + executeTx, + getPendingTxsForChains, +} 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; - balance: string; -}; - -export async function getPendingTxsForChains( - chains: string[], - multiProvider: MultiProvider, -): Promise { - 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; - } - - const balance = await safeSdk.getBalance(); - const nativeToken = await multiProvider.getNativeToken(chain); - const formattedBalance = formatUnits(balance, nativeToken.decimals); - - 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, - balance: `${Number(formattedBalance).toFixed(5)} ${ - nativeToken.symbol - }`, - }); - }, - ); - }), - ); - return txs.sort( - (a, b) => a.chain.localeCompare(b.chain) || a.nonce - b.nonce, - ); -} - async function main() { const safeChains = Object.keys(safes); configureRootLogger(LogFormat.Pretty, LogLevel.Info); @@ -129,7 +36,7 @@ async function main() { const chainsToCheck = chains || safeChains; if (chainsToCheck.length === 0) { - console.error('No chains provided'); + rootLogger.error('No chains provided'); process.exit(1); } @@ -141,11 +48,16 @@ async function main() { chainsToCheck, ); - const pendingTxs = await getPendingTxsForChains(chainsToCheck, multiProvider); + const pendingTxs = await getPendingTxsForChains( + chainsToCheck, + multiProvider, + safes, + ); if (pendingTxs.length === 0) { - console.info(chalk.green('No pending transactions found!')); + rootLogger.info(chalk.green('No pending transactions found!')); process.exit(0); } + // eslint-disable-next-line no-console console.table(pendingTxs, [ 'chain', 'nonce', @@ -161,7 +73,7 @@ async function main() { (tx) => tx.status === SafeTxStatus.READY_TO_EXECUTE, ); if (executableTxs.length === 0) { - console.info(chalk.green('No transactions to execute!')); + rootLogger.info(chalk.green('No transactions to execute!')); process.exit(0); } @@ -171,7 +83,7 @@ async function main() { }); if (!shouldExecute) { - console.info( + rootLogger.info( chalk.blue( `${executableTxs.length} transactions available for execution`, ), @@ -179,7 +91,7 @@ async function main() { process.exit(0); } - console.info(chalk.blueBright('Executing transactions...')); + rootLogger.info(chalk.blueBright('Executing transactions...')); for (const tx of executableTxs) { const confirmExecuteTx = await confirm({ @@ -187,7 +99,7 @@ async function main() { default: false, }); if (confirmExecuteTx) { - console.log( + rootLogger.info( `Executing transaction ${tx.shortTxHash} on chain ${tx.chain}`, ); try { @@ -198,7 +110,7 @@ async function main() { tx.fullTxHash, ); } catch (error) { - console.error(chalk.red(`Error executing transaction: ${error}`)); + rootLogger.error(chalk.red(`Error executing transaction: ${error}`)); return; } } @@ -210,6 +122,6 @@ async function main() { main() .then() .catch((e) => { - console.error(e); + rootLogger.error(e); process.exit(1); }); diff --git a/typescript/infra/scripts/safes/parse-txs.ts b/typescript/infra/scripts/safes/parse-txs.ts index 21e8f87eed..6df936817d 100644 --- a/typescript/infra/scripts/safes/parse-txs.ts +++ b/typescript/infra/scripts/safes/parse-txs.ts @@ -1,23 +1,29 @@ +import chalk from 'chalk'; import { BigNumber } from 'ethers'; +import yargs from 'yargs'; import { AnnotatedEV5Transaction } from '@hyperlane-xyz/sdk'; import { LogFormat, LogLevel, configureRootLogger, + rootLogger, stringifyObject, } from '@hyperlane-xyz/utils'; +import { safes } from '../../config/environments/mainnet3/owners.js'; import { GovernTransactionReader } from '../../src/tx/govern-transaction-reader.js'; -import { getSafeTx } from '../../src/utils/safe.js'; -import { getArgs, withChainsRequired, withTxHashes } from '../agent-utils.js'; +import { getPendingTxsForChains, getSafeTx } from '../../src/utils/safe.js'; +import { writeYamlAtPath } from '../../src/utils/utils.js'; +import { withChains } from '../agent-utils.js'; import { getEnvironmentConfig, getHyperlaneCore } from '../core-utils.js'; -async function main() { - const { environment, chains, txHashes } = await withTxHashes( - withChainsRequired(getArgs()), - ).argv; +const environment = 'mainnet3'; +const safeChains = Object.keys(safes); +async function main() { + const { chains } = await withChains(yargs(process.argv.slice(2)), safeChains) + .argv; configureRootLogger(LogFormat.Pretty, LogLevel.Info); const config = getEnvironmentConfig(environment); @@ -35,11 +41,31 @@ async function main() { warpRoutes, ); + const pendingTxs = await getPendingTxsForChains( + !chains || chains.length === 0 ? safeChains : chains, + multiProvider, + safes, + ); + if (pendingTxs.length === 0) { + rootLogger.info(chalk.green('No pending transactions found!')); + process.exit(0); + } + // eslint-disable-next-line no-console + console.table(pendingTxs, [ + 'chain', + 'nonce', + 'submissionDate', + 'fullTxHash', + 'confs', + 'threshold', + 'status', + 'balance', + ]); + const chainResultEntries = await Promise.all( - chains.map(async (chain, chainIndex) => { - const txHash = txHashes[chainIndex]; - console.log(`Reading tx ${txHash} on ${chain}`); - const safeTx = await getSafeTx(chain, multiProvider, txHash); + pendingTxs.map(async ({ chain, fullTxHash }) => { + rootLogger.info(`Reading tx ${fullTxHash} on ${chain}`); + const safeTx = await getSafeTx(chain, multiProvider, fullTxHash); const tx: AnnotatedEV5Transaction = { to: safeTx.to, data: safeTx.data, @@ -48,27 +74,30 @@ async function main() { try { const results = await reader.read(chain, tx); - console.log(`Finished reading tx ${txHash} on ${chain}`); - return [chain, results]; + rootLogger.info(`Finished reading tx ${fullTxHash} on ${chain}`); + return [`${chain}-${fullTxHash}`, results]; } catch (err) { - console.error('Error reading transaction', err, chain, tx); + rootLogger.error('Error reading transaction', err, chain, tx); process.exit(1); } }), ); - const chainResults = Object.fromEntries(chainResultEntries); - console.log(stringifyObject(chainResults, 'yaml', 2)); - if (reader.errors.length) { - console.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); - console.log(stringifyObject(reader.errors, 'yaml', 2)); - console.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); + rootLogger.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); + rootLogger.info(stringifyObject(reader.errors, 'yaml', 2)); + rootLogger.error('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'); process.exit(1); + } else { + rootLogger.info('✅✅✅✅✅ No fatal errors ✅✅✅✅✅'); + const chainResults = Object.fromEntries(chainResultEntries); + const resultsPath = `safe-tx-results-${Date.now()}.yaml`; + writeYamlAtPath(resultsPath, chainResults); + rootLogger.info(`Results written to ${resultsPath}`); } } main().catch((err) => { - console.error('Error:', err); + rootLogger.error('Error:', err); process.exit(1); }); diff --git a/typescript/infra/src/tx/govern-transaction-reader.ts b/typescript/infra/src/tx/govern-transaction-reader.ts index ef82ab1e1a..7e21335a17 100644 --- a/typescript/infra/src/tx/govern-transaction-reader.ts +++ b/typescript/infra/src/tx/govern-transaction-reader.ts @@ -8,7 +8,12 @@ import assert from 'assert'; import chalk from 'chalk'; import { BigNumber, ethers } from 'ethers'; -import { ProxyAdmin__factory, TokenRouter__factory } from '@hyperlane-xyz/core'; +import { + Ownable__factory, + ProxyAdmin__factory, + TimelockController__factory, + TokenRouter__factory, +} from '@hyperlane-xyz/core'; import { AnnotatedEV5Transaction, ChainMap, @@ -35,6 +40,7 @@ import { icaOwnerChain, icas, safes, + timelocks, } from '../../config/environments/mainnet3/owners.js'; import { DeployEnvironment } from '../config/environment.js'; import { getSafeAndService } from '../utils/safe.js'; @@ -128,6 +134,11 @@ export class GovernTransactionReader { return this.readProxyAdminTransaction(chain, tx); } + // If it's to a TimelockController + if (this.isTimelockControllerTransaction(chain, tx)) { + return this.readTimelockControllerTransaction(chain, tx); + } + // If it's a Multisend if (await this.isMultisendTransaction(chain, tx)) { return this.readMultisendTransaction(chain, tx); @@ -138,6 +149,11 @@ export class GovernTransactionReader { return this.readWarpModuleTransaction(chain, tx); } + // If it's an Ownable transaction + if (await this.isOwnableTransaction(chain, tx)) { + return this.readOwnableTransaction(chain, tx); + } + const insight = '⚠️ Unknown transaction type'; // If we get here, it's an unknown transaction this.errors.push({ @@ -153,6 +169,73 @@ export class GovernTransactionReader { }; } + private isTimelockControllerTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): boolean { + return ( + tx.to !== undefined && + timelocks[chain] !== undefined && + eqAddress(tx.to!, timelocks[chain]!) + ); + } + + private async readTimelockControllerTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): Promise { + if (!tx.data) { + throw new Error('No data in TimelockController transaction'); + } + + const timelockControllerInterface = + TimelockController__factory.createInterface(); + const decoded = timelockControllerInterface.parseTransaction({ + data: tx.data, + value: tx.value, + }); + + let insight; + if ( + decoded.functionFragment.name === + timelockControllerInterface.functions[ + 'schedule(address,uint256,bytes,bytes32,bytes32,uint256)' + ].name + ) { + const [target, value, data, eta, executor, delay] = decoded.args; + insight = `Schedule ${target} to be executed at ${eta} with ${value} ${data}. Executor: ${executor}, Delay: ${delay}`; + } + + if ( + decoded.functionFragment.name === + timelockControllerInterface.functions[ + 'execute(address,uint256,bytes,bytes32,bytes32)' + ].name + ) { + const [target, value, data, executor] = decoded.args; + insight = `Execute ${target} with ${value} ${data}. Executor: ${executor}`; + } + + if ( + decoded.functionFragment.name === + timelockControllerInterface.functions['cancel(bytes32)'].name + ) { + const [id] = decoded.args; + insight = `Cancel scheduled transaction ${id}`; + } + + const args = formatFunctionFragmentArgs( + decoded.args, + decoded.functionFragment, + ); + + return { + chain, + to: `Timelock Controller (${chain} ${tx.to})`, + ...(insight ? { insight } : { args }), + }; + } + private isWarpModuleTransaction( chain: ChainName, tx: AnnotatedEV5Transaction, @@ -180,13 +263,7 @@ export class GovernTransactionReader { value: tx.value, }); - const args = formatFunctionFragmentArgs( - decoded.args, - decoded.functionFragment, - ); - - let insight = ''; - + let insight; if ( decoded.functionFragment.name === tokenRouterInterface.functions['setHook(address)'].name @@ -255,16 +332,22 @@ export class GovernTransactionReader { insight = `Unenroll remote routers for ${insights.join(', ')}`; } + let ownableTx = {}; + if (!insight) { + ownableTx = await this.readOwnableTransaction(chain, tx); + } + assert(tx.to, 'Warp Module transaction must have a to address'); - const token = this.warpRouteIndex[chain][tx.to.toLowerCase()]; + const tokenAddress = tx.to.toLowerCase(); + const token = this.warpRouteIndex[chain][tokenAddress]; return { + ...ownableTx, chain, - to: `${token.symbol} (${token.name}, ${token.standard})`, + to: `${token.symbol} (${token.name}, ${token.standard}, ${tokenAddress})`, insight, value: `${ethers.utils.formatEther(decoded.value)} ${symbol}`, signature: decoded.signature, - args, }; } @@ -401,26 +484,11 @@ export class GovernTransactionReader { value: tx.value, }); - const args = formatFunctionFragmentArgs( - decoded.args, - decoded.functionFragment, - ); - - let insight; - if ( - decoded.functionFragment.name === - proxyAdminInterface.functions['transferOwnership(address)'].name - ) { - const [newOwner] = decoded.args; - insight = `Transfer ownership to ${newOwner}`; - } - + const ownableTx = await this.readOwnableTransaction(chain, tx); return { - chain, + ...ownableTx, to: `Proxy Admin (${chain} ${this.chainAddresses[chain].proxyAdmin})`, - insight, signature: decoded.signature, - args, }; } @@ -629,6 +697,49 @@ export class GovernTransactionReader { }; } + private async readOwnableTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): Promise { + if (!tx.data) { + throw new Error('⚠️ No data in Ownable transaction'); + } + + const ownableInterface = Ownable__factory.createInterface(); + const decoded = ownableInterface.parseTransaction({ + data: tx.data, + value: tx.value, + }); + + let insight; + if ( + decoded.functionFragment.name === + ownableInterface.functions['renounceOwnership()'].name + ) { + insight = `Renounce ownership`; + } + + if ( + decoded.functionFragment.name === + ownableInterface.functions['transferOwnership(address)'].name + ) { + const [newOwner] = decoded.args; + insight = `Transfer ownership to ${newOwner}`; + } + + const args = formatFunctionFragmentArgs( + decoded.args, + decoded.functionFragment, + ); + + return { + chain, + to: `Ownable (${chain} ${tx.to})`, + ...(insight ? { insight } : { args }), + signature: decoded.signature, + }; + } + isIcaTransaction(chain: ChainName, tx: AnnotatedEV5Transaction): boolean { return ( tx.to !== undefined && @@ -670,6 +781,23 @@ export class GovernTransactionReader { return eqAddress(multiSendCallOnlyAddress, tx.to); } + async isOwnableTransaction( + chain: ChainName, + tx: AnnotatedEV5Transaction, + ): Promise { + if (!tx.to) return false; + try { + const account = Ownable__factory.connect( + tx.to, + this.multiProvider.getProvider(chain), + ); + await account.owner(); + return true; + } catch { + return false; + } + } + private multiSendCallOnlyAddressCache: ChainMap = {}; async getMultiSendCallOnlyAddress( diff --git a/typescript/infra/src/utils/safe.ts b/typescript/infra/src/utils/safe.ts index 95e27c50e3..c85a4f6043 100644 --- a/typescript/infra/src/utils/safe.ts +++ b/typescript/infra/src/utils/safe.ts @@ -7,6 +7,7 @@ import { } from '@safe-global/safe-core-sdk-types'; import chalk from 'chalk'; import { BigNumber, ethers } from 'ethers'; +import { formatUnits } from 'ethers/lib/utils.js'; import { ChainNameOrId, @@ -14,9 +15,16 @@ import { getSafe, getSafeService, } from '@hyperlane-xyz/sdk'; -import { Address, CallData, eqAddress, retryAsync } from '@hyperlane-xyz/utils'; +import { + Address, + CallData, + eqAddress, + retryAsync, + rootLogger, +} from '@hyperlane-xyz/utils'; import safeSigners from '../../config/environments/mainnet3/safe/safeSigners.json' assert { type: 'json' }; +// eslint-disable-next-line import/no-cycle import { AnnotatedCallData } from '../govern/HyperlaneAppGovernor.js'; export async function getSafeAndService( @@ -82,7 +90,7 @@ export async function executeTx( await safeSdk.executeTransaction(safeTransaction); - console.log( + rootLogger.info( chalk.green.bold(`Executed transaction ${safeTxHash} on ${chain}`), ); } @@ -122,7 +130,7 @@ export async function proposeSafeTransaction( senderSignature: senderSignature.data, }); - console.log( + rootLogger.info( chalk.green(`Proposed transaction on ${chain} with hash ${safeTxHash}`), ); } @@ -143,7 +151,7 @@ export async function deleteAllPendingSafeTxs( }); if (!pendingTxsResponse.ok) { - console.error( + rootLogger.error( chalk.red(`Failed to fetch pending transactions for ${safeAddress}`), ); return; @@ -156,7 +164,7 @@ export async function deleteAllPendingSafeTxs( await deleteSafeTx(chain, multiProvider, safeAddress, tx.safeTxHash); } - console.log( + rootLogger.info( `Deleted all pending transactions on ${chain} for ${safeAddress}\n`, ); } @@ -177,7 +185,7 @@ export async function getSafeTx( }); if (!txDetailsResponse.ok) { - console.error( + rootLogger.error( chalk.red(`Failed to fetch transaction details for ${safeTxHash}`), ); return; @@ -205,7 +213,7 @@ export async function deleteSafeTx( }); if (!txDetailsResponse.ok) { - console.error( + rootLogger.error( chalk.red(`Failed to fetch transaction details for ${safeTxHash}`), ); return; @@ -215,21 +223,23 @@ export async function deleteSafeTx( const proposer = txDetails.proposer; if (!proposer) { - console.error(chalk.red(`No proposer found for transaction ${safeTxHash}`)); + rootLogger.error( + chalk.red(`No proposer found for transaction ${safeTxHash}`), + ); return; } // Compare proposer to signer const signerAddress = await signer.getAddress(); - if (proposer !== signerAddress) { - console.log( + if (!eqAddress(proposer, signerAddress)) { + rootLogger.info( chalk.italic( `Skipping deletion of transaction ${safeTxHash} proposed by ${proposer}`, ), ); return; } - console.log(`Deleting transaction ${safeTxHash} proposed by ${proposer}`); + rootLogger.info(`Deleting transaction ${safeTxHash} proposed by ${proposer}`); try { // Generate the EIP-712 signature @@ -275,7 +285,7 @@ export async function deleteSafeTx( }); if (res.status === 204) { - console.log( + rootLogger.info( chalk.green( `Successfully deleted transaction ${safeTxHash} on ${chain}`, ), @@ -284,13 +294,13 @@ export async function deleteSafeTx( } const errorBody = await res.text(); - console.error( + rootLogger.error( chalk.red( `Failed to delete transaction ${safeTxHash} on ${chain}: Status ${res.status} ${res.statusText}. Response body: ${errorBody}`, ), ); } catch (error) { - console.error( + rootLogger.error( chalk.red(`Failed to delete transaction ${safeTxHash} on ${chain}:`), error, ); @@ -310,8 +320,8 @@ export async function updateSafeOwner( (newOwner) => !owners.some((owner) => eqAddress(newOwner, owner)), ); - console.log(chalk.magentaBright('Owners to remove:', ownersToRemove)); - console.log(chalk.magentaBright('Owners to add:', ownersToAdd)); + rootLogger.info(chalk.magentaBright('Owners to remove:', ownersToRemove)); + rootLogger.info(chalk.magentaBright('Owners to add:', ownersToAdd)); const transactions: AnnotatedCallData[] = []; @@ -343,3 +353,104 @@ export async function updateSafeOwner( return transactions; } + +type SafeStatus = { + chain: string; + nonce: number; + submissionDate: string; + shortTxHash: string; + fullTxHash: string; + confs: number; + threshold: number; + status: string; + balance: string; +}; + +export enum SafeTxStatus { + NO_CONFIRMATIONS = '🔴', + PENDING = '🟡', + ONE_AWAY = '🔵', + READY_TO_EXECUTE = '🟢', +} + +export async function getPendingTxsForChains( + chains: string[], + multiProvider: MultiProvider, + safes: Record, +): Promise { + const txs: SafeStatus[] = []; + await Promise.all( + chains.map(async (chain) => { + if (!safes[chain]) { + rootLogger.error(chalk.red.bold(`No safe found for ${chain}`)); + return; + } + + if (chain === 'endurance') { + rootLogger.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) { + rootLogger.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; + } + + const balance = await safeSdk.getBalance(); + const nativeToken = await multiProvider.getNativeToken(chain); + const formattedBalance = formatUnits(balance, nativeToken.decimals); + + 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, + balance: `${Number(formattedBalance).toFixed(5)} ${ + nativeToken.symbol + }`, + }); + }, + ); + }), + ); + return txs.sort( + (a, b) => a.chain.localeCompare(b.chain) || a.nonce - b.nonce, + ); +} diff --git a/typescript/infra/src/warp/helm.ts b/typescript/infra/src/warp/helm.ts index 74271fff3a..baff961634 100644 --- a/typescript/infra/src/warp/helm.ts +++ b/typescript/infra/src/warp/helm.ts @@ -28,7 +28,7 @@ export class WarpRouteMonitorHelmManager extends HelmManager { return { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '157b27b-20250120-194432', + tag: 'fe71e9b-20250121-110722', }, warpRouteId: this.warpRouteId, fullnameOverride: this.helmReleaseName, diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 9c0b130684..aef1b75a55 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -12,7 +12,7 @@ "@hyperlane-xyz/utils": "8.2.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", - "@safe-global/safe-deployments": "1.37.8", + "@safe-global/safe-deployments": "1.37.23", "@solana/spl-token": "^0.4.9", "@solana/web3.js": "^1.95.4", "bignumber.js": "^9.1.1", diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index 499aa10729..a3479e8af1 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -1971,12 +1971,17 @@ export const defaultMultisigConfigs: ChainMap = { }, trumpchain: { - threshold: 1, + threshold: 2, validators: [ { address: '0x3ada634c8dfa57a67f5f22ca757b35cde6cfab5e', alias: AW_VALIDATOR_ALIAS, }, + DEFAULT_MERKLY_VALIDATOR, + { + address: '0xcF8151b8aEFfF4e22F6B48fe2Ffe2d60F00C890C', + alias: 'Caldera', + }, ], }, diff --git a/typescript/sdk/src/utils/gnosisSafe.js b/typescript/sdk/src/utils/gnosisSafe.js index 51235e2121..90f360821a 100644 --- a/typescript/sdk/src/utils/gnosisSafe.js +++ b/typescript/sdk/src/utils/gnosisSafe.js @@ -43,6 +43,16 @@ const safeDeploymentsVersions = { }, }; +// Override for chains that haven't yet been published in the safe-deployments package. +// Temporary until PR to safe-deployments package is merged and SDK dependency is updated. +const chainOverrides = { + // zeronetwork + 543210: { + multiSend: '0x0dFcccB95225ffB03c6FBB2559B530C2B7C8A912', + multiSendCallOnly: '0xf220D3b4DFb23C4ade8C88E526C1353AbAcbC38F', + }, +}; + export async function getSafe(chain, multiProvider, safeAddress) { // Create Ethers Adapter const signer = multiProvider.getSigner(chain); @@ -61,7 +71,16 @@ export async function getSafe(chain, multiProvider, safeAddress) { // Get the multiSend and multiSendCallOnly deployments for the given chain let multiSend, multiSendCallOnly; - if (safeDeploymentsVersions[safeVersion]) { + if (chainOverrides[chainId]) { + multiSend = { + networkAddresses: { [chainId]: chainOverrides[chainId].multiSend }, + }; + multiSendCallOnly = { + networkAddresses: { + [chainId]: chainOverrides[chainId].multiSendCallOnly, + }, + }; + } else if (safeDeploymentsVersions[safeVersion]) { const { multiSendVersion, multiSendCallOnlyVersion } = safeDeploymentsVersions[safeVersion]; multiSend = getMultiSendDeployment({ diff --git a/yarn.lock b/yarn.lock index cf72bf2284..5b8d3be79a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7563,7 +7563,7 @@ __metadata: "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" "@safe-global/protocol-kit": "npm:1.3.0" - "@safe-global/safe-deployments": "npm:1.37.8" + "@safe-global/safe-deployments": "npm:1.37.23" "@solana/spl-token": "npm:^0.4.9" "@solana/web3.js": "npm:^1.95.4" "@types/mocha": "npm:^10.0.1" @@ -12754,12 +12754,12 @@ __metadata: languageName: node linkType: hard -"@safe-global/safe-deployments@npm:1.37.8": - version: 1.37.8 - resolution: "@safe-global/safe-deployments@npm:1.37.8" +"@safe-global/safe-deployments@npm:1.37.23": + version: 1.37.23 + resolution: "@safe-global/safe-deployments@npm:1.37.23" dependencies: semver: "npm:^7.6.2" - checksum: 10/bc8fce2c4d557e547a6cceebb611f9584d998dfb459cd50cf338409de986bed247ebca9425b0984a6e1a6accab42c7c4d1c68811e09cc981756183ba50a5e5a9 + checksum: 10/90fca085c94fdeed7d2112699dfe58e1b1178358ccaf98049fd1fdd52be78de261753d2abc7351d6a9f977e400706d960ca6ad3f66413847a7b268999be0eff0 languageName: node linkType: hard