Migrate delivery status checking to front-end

Harden parts of debugger and status checker code
Improve block explorer query rate limiting
pull/32/head
J M Rossy 2 years ago
parent ce092f7879
commit 4ea5c77258
  1. 109
      src/features/debugger/debugMessage.ts
  2. 1
      src/features/debugger/strings.ts
  3. 1
      src/features/debugger/types.ts
  4. 69
      src/features/deliveryStatus/fetchDeliveryStatus.ts
  5. 24
      src/features/deliveryStatus/useMessageDeliveryStatus.ts
  6. 45
      src/features/messages/MessageDetails.tsx
  7. 36
      src/features/messages/ica.ts
  8. 4
      src/pages/api/delivery-status.ts
  9. 1
      src/pages/api/latest-nonce.ts
  10. 18
      src/utils/explorers.ts

@ -1,4 +1,4 @@
// Based on debug script in monorepo
// Forked from debug script in monorepo
// https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/infra/scripts/debug-message.ts
import { BigNumber, providers } from 'ethers';
@ -9,12 +9,12 @@ import {
DispatchedMessage,
HyperlaneCore,
MultiProvider,
TestChains,
} from '@hyperlane-xyz/sdk';
// TODO get exported from SDK properly
import { TestChains } from '@hyperlane-xyz/sdk/dist/consts/chains';
import { utils } from '@hyperlane-xyz/utils';
import { Environment } from '../../consts/environments';
import { getMultiProvider } from '../../multiProvider';
import { trimLeading0x } from '../../utils/addresses';
import { errorToString } from '../../utils/errors';
import { logger } from '../../utils/logger';
@ -36,8 +36,7 @@ export async function debugMessagesForHash(
environment: Environment,
attemptGetProcessTx = true,
): Promise<MessageDebugResult> {
// TODO use RPC with api keys
const multiProvider = new MultiProvider();
const multiProvider = getMultiProvider();
const txDetails = await findTransactionDetails(txHash, multiProvider);
if (!txDetails?.transactionReceipt) {
@ -64,14 +63,17 @@ export async function debugMessagesForTransaction(
environment: Environment,
nonce?: number,
attemptGetProcessTx = true,
multiProvider = new MultiProvider(),
multiProvider = getMultiProvider(),
): Promise<MessageDebugResult> {
const explorerLink = multiProvider.getExplorerTxUrl(chainName, {
hash: txReceipt.transactionHash,
});
// TODO PI support here
const core = HyperlaneCore.fromEnvironment(environment, multiProvider);
const dispatchedMessages = core.getDispatchedMessages(txReceipt);
const explorerLink =
multiProvider.tryGetExplorerTxUrl(chainName, {
hash: txReceipt.transactionHash,
}) || undefined;
if (!dispatchedMessages?.length) {
return {
status: TxDebugStatus.NoMessages,
@ -91,7 +93,7 @@ export async function debugMessagesForTransaction(
continue;
}
logger.debug(`Checking message ${i + 1} of ${dispatchedMessages.length}`);
messageDetails.push(await checkMessage(core, multiProvider, msg, attemptGetProcessTx));
messageDetails.push(await debugMessage(core, multiProvider, msg, attemptGetProcessTx));
logger.debug(`Done checking message ${i + 1}`);
}
return {
@ -138,13 +140,12 @@ async function fetchTransactionDetails(
}
}
async function checkMessage(
async function debugMessage(
core: HyperlaneCore,
multiProvider: MultiProvider,
message: DispatchedMessage,
attemptGetProcessTx = true,
): Promise<MessageDebugDetails> {
logger.debug(JSON.stringify(message));
const {
sender: senderBytes,
recipient: recipientBytes,
@ -154,13 +155,13 @@ async function checkMessage(
nonce,
} = message.parsed;
const messageId = utils.messageId(message.message);
const senderAddress = utils.bytes32ToAddress(senderBytes.toString());
const recipientAddress = utils.bytes32ToAddress(recipientBytes.toString());
const senderAddr = utils.bytes32ToAddress(senderBytes.toString());
const recipientAddr = utils.bytes32ToAddress(recipientBytes.toString());
const properties = new Map<string, string | LinkProperty>();
properties.set('ID', messageId);
properties.set('Sender', senderAddress);
properties.set('Recipient', recipientAddress);
properties.set('Sender', senderAddr);
properties.set('Recipient', recipientAddr);
properties.set('Origin Domain', origin.toString());
properties.set('Origin Chain', multiProvider.tryGetChainName(origin) || 'Unknown');
properties.set('Destination Domain', destination.toString());
@ -196,8 +197,8 @@ async function checkMessage(
if (attemptGetProcessTx) {
const processTxHash = await tryGetProcessTxHash(destinationMailbox, messageId);
if (processTxHash) {
const url = multiProvider.getExplorerTxUrl(destinationChain, { hash: processTxHash });
properties.set('Process TX', { url, text: processTxHash });
const url = multiProvider.tryGetExplorerTxUrl(destinationChain, { hash: processTxHash });
properties.set('Process TX', { url: url || 'UNKNOWN', text: processTxHash });
}
}
return {
@ -209,38 +210,29 @@ async function checkMessage(
logger.debug('Message not yet processed');
}
const recipientIsContract = await isContract(multiProvider, destinationChain, recipientAddress);
const recipientIsContract = await isContract(multiProvider, destinationChain, recipientAddr);
if (!recipientIsContract) {
logger.info(`Recipient address ${recipientAddress} is not a contract`);
logger.info(`Recipient address ${recipientAddr} is not a contract`);
return {
status: MessageDebugStatus.RecipientNotContract,
properties,
details: `Recipient address is ${recipientAddress}. Ensure that the bytes32 value is not malformed.`,
details: `Recipient address is ${recipientAddr}. Ensure that the bytes32 value is not malformed.`,
};
}
const destinationProvider = multiProvider.getProvider(destinationChain);
const recipientContract = IMessageRecipient__factory.connect(
recipientAddress,
destinationProvider,
);
const destProvider = multiProvider.getProvider(destinationChain);
const recipientContract = IMessageRecipient__factory.connect(recipientAddr, destProvider);
try {
await recipientContract.estimateGas.handle(origin, senderBytes, body, {
from: destinationMailbox.address,
});
logger.debug('Calling recipient `handle` function from the inbox does not revert');
return {
status: MessageDebugStatus.NoErrorsFound,
properties,
details: 'Message may just need more time to be processed',
};
} catch (err: any) {
logger.info('Estimate gas call failed');
const errorReason = extractReasonString(err);
logger.debug(errorReason);
const bytecodeHasHandle = await tryCheckBytecodeHandle(destinationProvider, recipientAddress);
const bytecodeHasHandle = await tryCheckBytecodeHandle(destProvider, recipientAddr);
if (!bytecodeHasHandle) {
logger.info('Bytecode does not have function matching handle sig');
return {
@ -250,18 +242,12 @@ async function checkMessage(
};
}
const icaCallError = await checkIcaMessageError(
senderAddress,
recipientAddress,
body,
origin,
destinationProvider,
);
if (icaCallError) {
const icaCallErr = await tryDebugIcaMsg(senderAddr, recipientAddr, body, origin, destProvider);
if (icaCallErr) {
return {
status: MessageDebugStatus.IcaCallFailure,
properties,
details: icaCallError,
details: icaCallErr,
};
}
@ -271,6 +257,21 @@ async function checkMessage(
details: errorReason,
};
}
const hasSufficientGas = tryCheckInterchainGas();
if (!hasSufficientGas) {
return {
status: MessageDebugStatus.GasUnderfunded,
properties,
details: '',
};
}
return {
status: MessageDebugStatus.NoErrorsFound,
properties,
details: 'Message may just need more time to be processed',
};
}
async function isContract(multiProvider: MultiProvider, chain: ChainName, address: string) {
@ -280,8 +281,6 @@ async function isContract(multiProvider: MultiProvider, chain: ChainName, addres
return code && code !== '0x';
}
// TODO use explorer for this instead of RPC to avoid block age limitations
// In doing so, de-dupe with features/search/useMessageProcessTx.ts
async function tryGetProcessTxHash(destinationMailbox: Mailbox, messageId: string) {
try {
const filter = destinationMailbox.filters.ProcessId(messageId);
@ -313,9 +312,9 @@ async function tryCheckBytecodeHandle(
}
}
async function checkIcaMessageError(
sender: string,
recipient: string,
async function tryDebugIcaMsg(
sender: Address,
recipient: Address,
body: string,
originDomainId: number,
destinationProvider: providers.Provider,
@ -328,13 +327,13 @@ async function checkIcaMessageError(
const { sender: originalSender, calls } = decodedBody;
const icaAddress = await tryFetchIcaAddress(originDomainId, originalSender);
const icaAddress = await tryFetchIcaAddress(originDomainId, originalSender, destinationProvider);
if (!icaAddress) return null;
for (let i = 0; i < calls.length; i++) {
const call = calls[i];
logger.debug(`Checking ica call ${i + 1} of ${calls.length}`);
const errorReason = await tryCheckIcaCallError(
const errorReason = await tryCheckIcaCall(
icaAddress,
call.destinationAddress,
call.callBytes,
@ -348,7 +347,7 @@ async function checkIcaMessageError(
return null;
}
async function tryCheckIcaCallError(
async function tryCheckIcaCall(
icaAddress: string,
destinationAddress: string,
callBytes: string,
@ -369,6 +368,16 @@ async function tryCheckIcaCallError(
}
}
async function tryCheckInterchainGas() {
try {
//TODO
return true;
} catch (error) {
logger.debug(`Error estimating delivery gas cost for message `, error);
return true;
}
}
function extractReasonString(rawError: any) {
const errorString = errorToString(rawError, 1000);
const ethersReasonRegex = /reason="(.*?)"/gm;

@ -10,4 +10,5 @@ export const debugStatusToDesc: Record<MessageDebugStatus, string> = {
'Recipient bytecode is missing handle function selector',
[MessageDebugStatus.IcaCallFailure]: 'A call from the ICA account failed',
[MessageDebugStatus.HandleCallFailure]: 'Error calling handle on the recipient contract',
[MessageDebugStatus.GasUnderfunded]: 'Insufficient interchain gas has been paid for delivery',
};

@ -13,6 +13,7 @@ export enum MessageDebugStatus {
RecipientNotHandler = 'recipientNotHandler',
IcaCallFailure = 'icaCallFailure',
HandleCallFailure = 'handleCallFailure',
GasUnderfunded = 'gasUnderfunded',
}
export interface DebugNotFoundResult {

@ -1,9 +1,9 @@
import { constants } from 'ethers';
import { MultiProvider, chainIdToMetadata, hyperlaneCoreAddresses } from '@hyperlane-xyz/sdk';
import { MultiProvider, hyperlaneCoreAddresses } from '@hyperlane-xyz/sdk';
import { getMultiProvider } from '../../multiProvider';
import { Message, MessageStatus } from '../../types';
import { ensureLeading0x, validateAddress } from '../../utils/addresses';
import {
queryExplorerForLogs,
queryExplorerForTx,
@ -26,15 +26,20 @@ import {
// https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/1.0.0-beta0/solidity/contracts/Mailbox.sol#L84
// https://emn178.github.io/online-tools/keccak_256.html
// Alternatively could get this by creating the Mailbox contract object via SDK
const TOPIC_0 = '0x1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a9';
const PROCESS_TOPIC_0 = '0x1cae38cdd3d3919489272725a5ae62a4f48b2989b0dae843d3c279fee18073a9';
export async function fetchDeliveryStatus(
message: Message,
): Promise<MessageDeliveryStatusResponse> {
validateMessage(message);
const multiProvider = getMultiProvider();
const multiProvider = new MultiProvider();
const logs = await fetchExplorerLogsForMessage(multiProvider, message);
const originName = multiProvider.getChainName(message.originChainId);
const destName = multiProvider.getChainName(message.destinationChainId);
// TODO PI support here
const destMailboxAddr = hyperlaneCoreAddresses[destName]?.mailbox;
if (!destMailboxAddr) throw new Error(`No mailbox address found for dest ${destName}`);
const logs = await fetchExplorerLogsForMessage(multiProvider, message, destMailboxAddr);
if (logs?.length) {
logger.debug(`Found delivery log for tx ${message.origin.hash}`);
@ -68,15 +73,12 @@ export async function fetchDeliveryStatus(
return result;
} else {
const { originChainId, origin, nonce } = message;
const originName = chainIdToMetadata[originChainId].name;
const environment = getChainEnvironment(originName);
const originTxReceipt = await queryExplorerForTxReceipt(
multiProvider,
originChainId,
origin.hash,
);
// TODO currently throwing this over the fence to the debugger script
// which isn't very robust and uses public RPCs. Could be improved
const debugResult = await debugMessagesForTransaction(
originName,
originTxReceipt,
@ -109,19 +111,14 @@ export async function fetchDeliveryStatus(
}
}
async function fetchExplorerLogsForMessage(multiProvider: MultiProvider, message: Message) {
const { msgId, originChainId, origin, destinationChainId } = message;
function fetchExplorerLogsForMessage(
multiProvider: MultiProvider,
message: Message,
mailboxAddr: Address,
) {
const { msgId, origin, destinationChainId } = message;
logger.debug(`Searching for delivery logs for tx ${origin.hash}`);
const originName = chainIdToMetadata[originChainId].name;
const destName = chainIdToMetadata[destinationChainId].name;
const destMailboxAddr = hyperlaneCoreAddresses[destName]?.mailbox;
if (!destMailboxAddr)
throw new Error(`No mailbox address found for dest ${destName} origin ${originName}`);
const topic1 = ensureLeading0x(msgId);
const logsQueryPath = `module=logs&action=getLogs&fromBlock=0&toBlock=999999999&topic0=${TOPIC_0}&topic0_1_opr=and&topic1=${topic1}&address=${destMailboxAddr}`;
const logsQueryPath = `module=logs&action=getLogs&fromBlock=1&toBlock=latest&topic0=${PROCESS_TOPIC_0}&topic0_1_opr=and&topic1=${msgId}&address=${mailboxAddr}`;
return queryExplorerForLogs(multiProvider, destinationChainId, logsQueryPath);
}
@ -134,36 +131,8 @@ async function tryFetchTransactionDetails(
const tx = await queryExplorerForTx(multiProvider, chainId, txHash);
return tx;
} catch (error) {
// Since we only need this for the from address, it's not critical.
// Swallowing error if there's an issue.
// Swallowing error if there's an issue so we can still surface delivery confirmation
logger.error('Failed to fetch tx details', txHash, chainId);
return null;
}
}
// TODO use zod here
function validateMessage(message: Message) {
const {
originDomainId,
destinationDomainId,
originChainId,
destinationChainId,
nonce,
origin,
recipient,
sender,
} = message;
if (!originDomainId) throw new Error(`Invalid origin domain ${originDomainId}`);
if (!destinationDomainId) throw new Error(`Invalid dest domain ${destinationDomainId}`);
if (!originChainId) throw new Error(`Invalid origin chain ${originChainId}`);
if (!destinationChainId) throw new Error(`Invalid dest chain ${destinationChainId}`);
if (!chainIdToMetadata[originChainId]?.name)
throw new Error(`No name found for chain ${originChainId}`);
if (!chainIdToMetadata[destinationChainId]?.name)
throw new Error(`No name found for chain ${destinationChainId}`);
if (!nonce) throw new Error(`Invalid nonce ${nonce}`);
if (!origin?.hash) throw new Error(`Invalid or missing origin tx`);
validateAddress(recipient, 'validateMessage recipient');
validateAddress(sender, 'validateMessage sender');
}

@ -5,31 +5,21 @@ import { toast } from 'react-toastify';
import { Message, MessageStatus } from '../../types';
import { logger } from '../../utils/logger';
import type { MessageDeliveryStatusResponse } from './types';
import { fetchDeliveryStatus } from './fetchDeliveryStatus';
// TODO: Consider deprecating this to simplify message details page
export function useMessageDeliveryStatus(message: Message, isReady: boolean) {
const serializedMessage = JSON.stringify(message);
const { data, error } = useQuery(
['messageProcessTx', serializedMessage, isReady],
['messageDeliveryStatus', serializedMessage, isReady],
async () => {
// TODO enable PI support here
if (!isReady || !message || message.status === MessageStatus.Delivered || message.isPiMsg)
return null;
const response = await fetch('/api/delivery-status', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: serializedMessage,
});
if (!response.ok) {
const errorMsg = await response.text();
throw new Error(errorMsg);
}
const result = (await response.json()) as MessageDeliveryStatusResponse;
logger.debug('Message delivery status result', result);
return result;
logger.debug('Fetching message delivery status for:', message.id);
const deliverStatus = await fetchDeliveryStatus(message);
logger.debug('Message delivery status result', deliverStatus);
return deliverStatus;
},
{ retry: false },
);

@ -70,24 +70,7 @@ export function MessageDetails({ messageId, message: propMessage }: Props) {
useInterval(reExecutor, AUTO_REFRESH_DELAY);
// Banner color setter
const setBanner = useStore((s) => s.setBanner);
useEffect(() => {
if (isFetching) return;
if (error) {
logger.error('Error fetching message details', error);
toast.error(`Error fetching message: ${error.message?.substring(0, 30)}`);
setBanner('bg-red-500');
} else if (status === MessageStatus.Failing) {
setBanner('bg-red-500');
} else if (!isMessageFound) {
setBanner('bg-gray-500');
} else {
setBanner('');
}
}, [error, isFetching, status, isMessageFound, setBanner]);
useEffect(() => {
return () => setBanner('');
}, [setBanner]);
useDynamicBannerColor(isFetching, status, isMessageFound, error);
return (
<>
@ -173,6 +156,32 @@ function StatusHeader({
);
}
function useDynamicBannerColor(
isFetching: boolean,
status: MessageStatus,
isMessageFound: boolean,
error?: Error,
) {
const setBanner = useStore((s) => s.setBanner);
useEffect(() => {
if (isFetching) return;
if (error) {
logger.error('Error fetching message details', error);
toast.error(`Error fetching message: ${error.message?.substring(0, 30)}`);
setBanner('bg-red-500');
} else if (status === MessageStatus.Failing) {
setBanner('bg-red-500');
} else if (!isMessageFound) {
setBanner('bg-gray-500');
} else {
setBanner('');
}
}, [error, isFetching, status, isMessageFound, setBanner]);
useEffect(() => {
return () => setBanner('');
}, [setBanner]);
}
const helpText = {
origin: 'Info about the transaction that initiated the message placement into the outbox.',
destination:

@ -1,9 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import { BigNumber, utils } from 'ethers';
import { BigNumber, providers, utils } from 'ethers';
import { InterchainAccountRouter__factory } from '@hyperlane-xyz/core';
import { MultiProvider, hyperlaneCoreAddresses } from '@hyperlane-xyz/sdk';
import { hyperlaneCoreAddresses } from '@hyperlane-xyz/sdk';
import { getMultiProvider } from '../../multiProvider';
import { areAddressesEqual, isValidAddress } from '../../utils/addresses';
import { logger } from '../../utils/logger';
@ -14,8 +15,8 @@ export function isIcaMessage({
sender,
recipient,
}: {
sender: string;
recipient: string;
sender: Address;
recipient: Address;
hash?: string;
}) {
const isSenderIca = isAddressIcaRouter(sender);
@ -30,7 +31,8 @@ export function isIcaMessage({
return false;
}
function isAddressIcaRouter(addr: string) {
function isAddressIcaRouter(addr: Address) {
// TODO PI support
return ICA_ADDRESS && areAddressesEqual(addr, ICA_ADDRESS);
}
@ -68,18 +70,19 @@ export function tryDecodeIcaBody(body: string) {
}
}
// TODO do this on backend and use private RPC
export async function tryFetchIcaAddress(originDomainId: number, senderAddress: string) {
export async function tryFetchIcaAddress(
originDomainId: number,
sender: Address,
provider: providers.Provider,
) {
try {
if (!ICA_ADDRESS) return null;
logger.debug('Fetching Ica address', originDomainId, senderAddress);
// TODO improved PI support
const multiProvider = new MultiProvider();
const provider = multiProvider.getProvider(originDomainId);
logger.debug('Fetching Ica address', originDomainId, sender);
const icaContract = InterchainAccountRouter__factory.connect(ICA_ADDRESS, provider);
const icaAddress = await icaContract['getInterchainAccount(uint32,address)'](
originDomainId,
senderAddress,
sender,
);
if (!isValidAddress(icaAddress)) throw new Error(`Invalid Ica addr ${icaAddress}`);
logger.debug('Ica address found', icaAddress);
@ -90,12 +93,13 @@ export async function tryFetchIcaAddress(originDomainId: number, senderAddress:
}
}
export function useIcaAddress(originDomainId: number, senderAddress?: string | null) {
export function useIcaAddress(originDomainId: number, sender?: Address | null) {
return useQuery(
['messageIcaAddress', originDomainId, senderAddress],
['messageIcaAddress', originDomainId, sender],
() => {
if (!originDomainId || !senderAddress || BigNumber.from(senderAddress).isZero()) return null;
return tryFetchIcaAddress(originDomainId, senderAddress);
if (!originDomainId || !sender || BigNumber.from(sender).isZero()) return null;
const provider = getMultiProvider().getProvider(originDomainId);
return tryFetchIcaAddress(originDomainId, sender, provider);
},
{ retry: false },
);

@ -6,7 +6,9 @@ import type { MessageDeliveryStatusResponse } from '../../features/deliveryStatu
import { Message } from '../../types';
import { logger } from '../../utils/logger';
// TODO: Deprecate this to simplify message details page
/**
* @deprecated use directly in client instead of via API
*/
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<MessageDeliveryStatusResponse | string>,

@ -21,6 +21,7 @@ export default async function handler(
try {
const body = req.body as { chainId: number };
if (!body.chainId) throw new Error('No chainId in body');
if (!chainIdToMetadata[body.chainId]) throw new Error('ChainId is unsupported');
const nonce = await fetchLatestNonce(body.chainId);
res.status(200).json({ nonce });
} catch (error) {

@ -10,7 +10,8 @@ import { hexToDecimal, tryHexToDecimal } from './number';
import { fetchWithTimeout, sleep } from './timeout';
const BLOCK_EXPLORER_RATE_LIMIT = 5100; // once every 5.1 seconds
let lastExplorerQuery = 0;
// Used for crude rate-limiting of explorer queries without API keys
const hostToLastQueried: Record<string, number> = {};
export interface ExplorerQueryResponse<R> {
status: string;
@ -22,7 +23,7 @@ async function queryExplorer<P>(
multiProvider: MultiProvider,
chainId: number,
params: URLSearchParams,
useKey = true,
useKey = false,
) {
const baseUrl = multiProvider.tryGetExplorerApiUrl(chainId);
if (!baseUrl) throw new Error(`No valid URL found for explorer for chain ${chainId}`);
@ -40,7 +41,6 @@ async function queryExplorer<P>(
logger.debug('Querying explorer url:', url.toString());
const result = await executeQuery<P>(url);
lastExplorerQuery = Date.now();
return result;
}
@ -48,6 +48,7 @@ async function executeQuery<P>(url: URL) {
try {
if (!url.searchParams.has('apikey')) {
// Without an API key, rate limits are strict so enforce a wait if necessary
const lastExplorerQuery = hostToLastQueried[url.hostname] || 0;
const waitTime = BLOCK_EXPLORER_RATE_LIMIT - (Date.now() - lastExplorerQuery);
if (waitTime > 0) await sleep(waitTime);
}
@ -65,7 +66,7 @@ async function executeQuery<P>(url: URL) {
return json.result;
} finally {
lastExplorerQuery = Date.now();
hostToLastQueried[url.hostname] = Date.now();
}
}
@ -86,7 +87,7 @@ export async function queryExplorerForLogs(
multiProvider: MultiProvider,
chainId: number,
params: string,
useKey = true,
useKey = false,
): Promise<ExplorerLogEntry[]> {
const logs = await queryExplorer<ExplorerLogEntry[]>(
multiProvider,
@ -103,6 +104,7 @@ export async function queryExplorerForLogs(
return logs;
}
// TODO use Zod
function validateExplorerLog(log: ExplorerLogEntry) {
if (!log) throw new Error('Log is nullish');
if (!log.transactionHash) throw new Error('Log has no tx hash');
@ -127,7 +129,7 @@ export async function queryExplorerForTx(
multiProvider: MultiProvider,
chainId: number,
txHash: string,
useKey = true,
useKey = false,
) {
const params = new URLSearchParams({
module: 'proxy',
@ -152,7 +154,7 @@ export async function queryExplorerForTxReceipt(
multiProvider: MultiProvider,
chainId: number,
txHash: string,
useKey = true,
useKey = false,
) {
const params = new URLSearchParams({
module: 'proxy',
@ -177,7 +179,7 @@ export async function queryExplorerForBlock(
multiProvider: MultiProvider,
chainId: number,
blockNumber?: number | string,
useKey = true,
useKey = false,
) {
const params = new URLSearchParams({
module: 'proxy',

Loading…
Cancel
Save