diff --git a/package.json b/package.json index 1faad4c7e7..38f49acab7 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "@solana-program/compute-budget": "^0.8.0", "@solana/kit": "^5.4.0", "google-auth-library": "^8.5.1", - "axios": "^1.7.4", "binance-api-node": "0.12.7", "dotenv": "^16.3.1", "ethers": "^5.7.2", diff --git a/scripts/fetchInventoryConfig.ts b/scripts/fetchInventoryConfig.ts index f99cd65b96..0e425d26cf 100644 --- a/scripts/fetchInventoryConfig.ts +++ b/scripts/fetchInventoryConfig.ts @@ -1,6 +1,5 @@ import { writeFile } from "node:fs/promises"; import { config } from "dotenv"; -import axios from "axios"; import { GoogleAuth } from "google-auth-library"; import { Logger, waitForLogger, delay } from "../src/utils"; @@ -26,8 +25,14 @@ async function fetchWithRetry( ): Promise { for (let i = 0; i < retries; i++) { try { - const response = await axios.get(url, { headers, responseType: "text", timeout: 30000 }); - return response.data as string; + const response = await fetch(url, { + headers, + signal: AbortSignal.timeout(30000), + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + return await response.text(); } catch (error) { if (i === retries - 1) { throw error; @@ -162,17 +167,14 @@ async function run(): Promise { } function getErrorMessage(error: unknown): string { - if (axios.isAxiosError(error)) { - if (error.response?.status === 404) { + if (error instanceof Error) { + const msg = error.message; + if (msg.includes("HTTP 404")) { return "File not found in Configurama"; - } else if (error.response?.status === 401 || error.response?.status === 403) { + } else if (msg.includes("HTTP 401") || msg.includes("HTTP 403")) { return "Authentication failed. Ensure ADC is configured to call the Configurama API."; - } else { - return `Configurama API error: ${error.response?.status} - ${error.message}`; } - } - if (error instanceof Error) { - return error.message; + return msg; } return String(error); } diff --git a/scripts/simulateFill.ts b/scripts/simulateFill.ts index df36852748..c2e7d5b1a0 100644 --- a/scripts/simulateFill.ts +++ b/scripts/simulateFill.ts @@ -1,4 +1,3 @@ -import axios from "axios"; import minimist from "minimist"; import { config } from "dotenv"; import { LogDescription } from "@ethersproject/abi"; @@ -170,58 +169,52 @@ async function createTenderlySimulation( public: true, // Make simulation publicly accessible }; - try { - const response = await axios.post(tenderlyUrl, simulationPayload, { - headers: { - "X-Access-Key": tenderlyAccessKey, - "Content-Type": "application/json", - }, - }); + const tenderlyHeaders = { + "X-Access-Key": tenderlyAccessKey, + "Content-Type": "application/json", + }; - const simulationId = response.data.simulation.id; - - console.log(`\nDebug: Simulation created with ID: ${simulationId}`); - - // Enable sharing by calling the share endpoint - const shareUrl = `https://api.tenderly.co/api/v1/account/${tenderlyUser}/project/${tenderlyProject}/simulations/${simulationId}/share`; - - try { - await axios.post( - shareUrl, - {}, // Empty body - { - headers: { - "X-Access-Key": tenderlyAccessKey, - "Content-Type": "application/json", - }, - } - ); - - console.log("Debug: Share enabled for simulation"); - - // Once sharing is enabled, construct the public share URL - const publicShareUrl = `https://www.tdly.co/shared/simulation/${simulationId}`; - console.log(`Debug: Using public share URL: ${publicShareUrl}`); - - return publicShareUrl; - } catch (shareError) { - // If share endpoint fails, fall back to the regular dashboard URL - console.warn("Could not enable sharing, using dashboard URL instead"); - if (axios.isAxiosError(shareError)) { - console.warn("Share endpoint error:", shareError.response?.status, shareError.response?.data); - } - } + const response = await fetch(tenderlyUrl, { + method: "POST", + headers: tenderlyHeaders, + body: JSON.stringify(simulationPayload), + }); - // Fallback to dashboard URL - return `https://dashboard.tenderly.co/${tenderlyUser}/${tenderlyProject}/simulator/${simulationId}`; - } catch (error) { - if (axios.isAxiosError(error)) { - throw new Error( - `Tenderly simulation failed: ${error.response?.status} - ${JSON.stringify(error.response?.data)}` - ); - } - throw error instanceof Error ? error : new Error(String(error)); + if (!response.ok) { + const errorBody = await response.text(); + throw new Error(`Tenderly simulation failed: ${response.status} - ${errorBody}`); + } + + const responseData = (await response.json()) as { simulation: { id: string } }; + const simulationId = responseData.simulation.id; + + console.log(`\nDebug: Simulation created with ID: ${simulationId}`); + + // Enable sharing by calling the share endpoint + const shareUrl = `https://api.tenderly.co/api/v1/account/${tenderlyUser}/project/${tenderlyProject}/simulations/${simulationId}/share`; + + const shareResponse = await fetch(shareUrl, { + method: "POST", + headers: tenderlyHeaders, + body: JSON.stringify({}), + }); + + if (shareResponse.ok) { + console.log("Debug: Share enabled for simulation"); + + // Once sharing is enabled, construct the public share URL + const publicShareUrl = `https://www.tdly.co/shared/simulation/${simulationId}`; + console.log(`Debug: Using public share URL: ${publicShareUrl}`); + + return publicShareUrl; + } else { + // If share endpoint fails, fall back to the regular dashboard URL + console.warn("Could not enable sharing, using dashboard URL instead"); + console.warn("Share endpoint error:", shareResponse.status, await shareResponse.text()); } + + // Fallback to dashboard URL + return `https://dashboard.tenderly.co/${tenderlyUser}/${tenderlyProject}/simulator/${simulationId}`; } async function getBlockNumberForTimestamp(destinationChainId: number, targetTimestamp: number): Promise { diff --git a/src/clients/AcrossAPIClient.ts b/src/clients/AcrossAPIClient.ts index b68d1ac8a1..5dc2375d13 100644 --- a/src/clients/AcrossAPIClient.ts +++ b/src/clients/AcrossAPIClient.ts @@ -1,5 +1,4 @@ import _ from "lodash"; -import axios, { AxiosError } from "axios"; import { bnZero, winston, @@ -132,28 +131,39 @@ export class AcrossApiClient { } } - const params = { l1Tokens: l1Tokens.join(",") }; + const params = new URLSearchParams({ l1Tokens: l1Tokens.join(",") }); let liquidReserves: BigNumber[] = []; try { - const result = await axios(url, { timeout, params }); - if (!result?.data) { + const response = await fetch(`${url}?${params}`, { + signal: AbortSignal.timeout(timeout), + }); + if (!response.ok) { this.logger.error({ at: "AcrossAPIClient", message: `Invalid response from /${path}, expected maxDeposit field.`, url, - params, - result, + params: Object.fromEntries(params), + status: response.status, }); + return l1Tokens.map(() => bnZero); } - liquidReserves = l1Tokens.map((l1Token) => BigNumber.from(result.data[l1Token.toEvmAddress()] ?? bnZero)); + const data = (await response.json()) as Record; + liquidReserves = l1Tokens.map((l1Token) => BigNumber.from(data[l1Token.toEvmAddress()] ?? bnZero)); } catch (err) { - const msg = _.get(err, "response.data", _.get(err, "response.statusText", (err as AxiosError).message)); - this.logger.warn({ at: "AcrossAPIClient", message: `Failed to get ${path},`, url, params, msg }); + const msg = (err as Error).message; + this.logger.warn({ + at: "AcrossAPIClient", + message: `Failed to get ${path},`, + url, + params: Object.fromEntries(params), + msg, + }); return l1Tokens.map(() => bnZero); } - if (redis) { - // Cache limit for 5 minutes. + if (redis && liquidReserves.some((r) => r.gt(bnZero))) { + // Cache limit for 5 minutes. Only cache if at least one reserve is non-zero to avoid + // persisting bad data from transient API issues. const baseTtl = 300; // Apply a random margin to spread expiry over a larger time window. const ttl = baseTtl + Math.ceil(_.random(-0.5, 0.5, true) * baseTtl); diff --git a/src/clients/AcrossApiBaseClient.ts b/src/clients/AcrossApiBaseClient.ts index 50d607b824..f89f846e0e 100644 --- a/src/clients/AcrossApiBaseClient.ts +++ b/src/clients/AcrossApiBaseClient.ts @@ -1,4 +1,4 @@ -import axios, { AxiosError } from "axios"; +import { isDefined } from "../utils"; import winston from "winston"; /** @@ -34,18 +34,35 @@ export abstract class BaseAcrossApiClient { protected async _get(endpoint: string, params: Record): Promise { try { - const config: { timeout: number; params: Record; headers?: Record } = { - timeout: this.apiResponseTimeout, - params, - }; + const url = new URL(`${this.urlBase}/${endpoint}`); + for (const [key, value] of Object.entries(params)) { + if (isDefined(value)) { + url.searchParams.set(key, String(value)); + } + } + const headers: Record = {}; if (this.apiKey) { - config.headers = { Authorization: `Bearer ${this.apiKey}` }; + headers["Authorization"] = `Bearer ${this.apiKey}`; } - const response = await axios.get(`${this.urlBase}/${endpoint}`, config); + const response = await fetch(url.toString(), { + headers, + signal: AbortSignal.timeout(this.apiResponseTimeout), + }); + + if (!response.ok) { + this.logger.warn({ + at: this.logContext, + message: `Invalid response from ${this.urlBase}`, + endpoint, + params, + }); + return; + } - if (!response?.data) { + const data = (await response.json()) as T; + if (!data) { this.logger.warn({ at: this.logContext, message: `Invalid response from ${this.urlBase}`, @@ -54,14 +71,14 @@ export abstract class BaseAcrossApiClient { }); return; } - return response.data; + return data; } catch (err) { this.logger.warn({ at: this.logContext, message: `Failed to get from ${this.urlBase}`, endpoint, params, - error: (err as AxiosError).message, + error: (err as Error).message, }); return; } diff --git a/src/finalizer/utils/helios.ts b/src/finalizer/utils/helios.ts index 1e2f1ca451..3e25247a72 100644 --- a/src/finalizer/utils/helios.ts +++ b/src/finalizer/utils/helios.ts @@ -14,14 +14,12 @@ import { } from "../../utils"; import { spreadEventWithBlockNumber } from "../../utils/EventUtils"; import { FinalizerPromise, CrossChainMessage } from "../types"; -import axios from "axios"; import UNIVERSAL_SPOKE_ABI from "../../common/abi/Universal_SpokePool.json"; import { RelayedCallDataEvent, StoredCallDataEvent } from "../../interfaces/Universal"; import { ApiProofRequest, ProofOutputs, ProofStateResponse, SP1HeliosProofData } from "../../interfaces/ZkApi"; import { StorageSlotVerifiedEvent, HeadUpdateEvent } from "../../interfaces/Helios"; import { calculateProofId, decodeProofOutputs } from "../../utils/ZkApiUtils"; import { calculateHubPoolStoreStorageSlot, getHubPoolStoreContract } from "../../utils/UniversalUtils"; -import { stringifyThrownValue } from "../../utils/LogUtils"; import { getSp1HeliosContractEVM } from "../../utils/HeliosUtils"; import { getBlockFinder, getBlockForTimestamp } from "../../utils/BlockUtils"; @@ -344,34 +342,36 @@ async function enrichHeliosActions( let proofState: ProofStateResponse | null = null; - // @dev We need try - catch here because of how API responds to non-existing proofs: with NotFound status - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let getError: any = null; - try { - const response = await axios.get(getProofUrl); - proofState = response.data; - logger.debug({ ...logContext, message: "Proof state received", proofId, status: proofState.status }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - getError = error; - } - - // Axios error. Handle based on whether was a NOTFOUND or another error - if (getError) { - const isNotFoundError = axios.isAxiosError(getError) && getError.response?.status === 404; - if (isNotFoundError) { - // NOTFOUND error -> Request proof + // @dev The API responds to non-existing proofs with 404, so we handle that specially + const getResponse = await fetch(getProofUrl); + if (!getResponse.ok) { + if (getResponse.status === 404) { + // NOTFOUND -> Request proof logger.debug({ ...logContext, message: "Proof not found (404), requesting...", proofId }); - await axios.post(`${apiBaseUrl}/v1/api/proofs`, apiRequest); + const postResponse = await fetch(`${apiBaseUrl}/v1/api/proofs`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(apiRequest), + }); + if (!postResponse.ok) { + throw new Error( + `Failed to request proof for proofId ${proofId}: ${postResponse.status} ${postResponse.statusText}` + ); + } logger.debug({ ...logContext, message: "Proof requested successfully.", proofId }); continue; } else { // If other error is returned -- throw and alert PD; this shouldn't happen - throw new Error(`Failed to get proof state for proofId ${proofId}: ${stringifyThrownValue(getError)}`); + throw new Error( + `Failed to get proof state for proofId ${proofId}: ${getResponse.status} ${getResponse.statusText}` + ); } } - // No axios error, process `proofState` + proofState = (await getResponse.json()) as ProofStateResponse; + logger.debug({ ...logContext, message: "Proof state received", proofId, status: proofState.status }); + + // Process `proofState` switch (proofState.status) { case "pending": // If proof generation is pending -- there's nothing for us to do yet. Will check this proof next run @@ -391,7 +391,16 @@ async function enrichHeliosActions( errorMessage: proofState.error_message, }); - await axios.post(`${apiBaseUrl}/v1/api/proofs`, apiRequest); + const retryResponse = await fetch(`${apiBaseUrl}/v1/api/proofs`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(apiRequest), + }); + if (!retryResponse.ok) { + throw new Error( + `Failed to re-request errored proof for proofId ${proofId}: ${retryResponse.status} ${retryResponse.statusText}` + ); + } logger.debug({ ...logContext, message: "Errored proof requested again successfully.", proofId }); break; } diff --git a/src/finalizer/utils/scroll.ts b/src/finalizer/utils/scroll.ts index 316d2f9e43..d993b47d95 100644 --- a/src/finalizer/utils/scroll.ts +++ b/src/finalizer/utils/scroll.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { utils as sdkUtils } from "@across-protocol/sdk"; -import axios from "axios"; import { HubPoolClient, SpokePoolClient } from "../../clients"; import { CONTRACT_ADDRESSES } from "../../common"; import { @@ -95,24 +94,25 @@ async function findOutstandingClaims(targetAddress: string): Promise(apiUrl, { - params: { - address: targetAddress, - page_size: MAX_PAGE_SIZE, - page: currentPage, - }, - }) - ).data.data?.results ?? []; + const params = new URLSearchParams({ + address: targetAddress, + page_size: String(MAX_PAGE_SIZE), + page: String(currentPage), + }); + const response = await fetch(`${apiUrl}?${params}`); + if (!response.ok) { + throw new Error(`Scroll API request failed: ${response.status} ${response.statusText}`); + } + const body = (await response.json()) as { + data: { + results: { + claim_info: ScrollClaimInfo; + l1_token_address: string; + token_amounts: string[]; + }[]; + }; + }; + requestResponse = body.data?.results ?? []; claimList.push( ...requestResponse .filter(({ claim_info }) => claim_info?.claimable) diff --git a/src/refiller/Refiller.ts b/src/refiller/Refiller.ts index dcc9d5d18b..e4d46d5edc 100644 --- a/src/refiller/Refiller.ts +++ b/src/refiller/Refiller.ts @@ -1,5 +1,4 @@ import winston from "winston"; -import axios from "axios"; import { RefillerConfig, RefillBalanceData } from "./RefillerConfig"; import { Address, @@ -450,7 +449,11 @@ export class Refiller { if (isDefined(addressIdCache)) { addressId = addressIdCache; } else { - const { data: registeredAddresses } = await axios.get(`${nativeMarketsApiUrl}/addresses`, { headers }); + const addressesResponse = await fetch(`${nativeMarketsApiUrl}/addresses`, { headers }); + if (!addressesResponse.ok) { + throw new Error(`Native markets addresses request failed: ${addressesResponse.status}`); + } + const registeredAddresses = (await addressesResponse.json()) as { items: NativeMarketsAddress[] }; addressId = registeredAddresses.items.find( ({ chain, token, address_hex }) => chain === "hyper_evm" && token === "usdh" && address_hex === this.baseSignerAddress.toNative() @@ -469,18 +472,34 @@ export class Refiller { message: `Address ${this.baseSignerAddress.toNative()} is not registered in the native markets API. Creating new address ID.`, address: this.baseSignerAddress, }); - const { data: _addressId } = await axios.post(`${nativeMarketsApiUrl}/addresses`, newAddressIdData, { + const createAddrResponse = await fetch(`${nativeMarketsApiUrl}/addresses`, { + method: "POST", headers, + body: JSON.stringify(newAddressIdData), }); - addressId = _addressId.id; + if (!createAddrResponse.ok) { + throw new Error(`Native markets create address failed: ${createAddrResponse.status}`); + } + const createdAddress = (await createAddrResponse.json()) as NativeMarketsAddress; + addressId = createdAddress.id; } await this.redisCache.set(addressIdCacheKey, addressId, 7 * day); } // Next, get the transfer route deposit address on Arbitrum. - const { data: transferRoutes } = await axios.get(`${nativeMarketsApiUrl}/transfer_routes`, { headers }); + const transferRoutesResponse = await fetch(`${nativeMarketsApiUrl}/transfer_routes`, { headers }); + if (!transferRoutesResponse.ok) { + throw new Error(`Native markets transfer_routes request failed: ${transferRoutesResponse.status}`); + } + const transferRoutes = (await transferRoutesResponse.json()) as { items: NativeMarketsTransferRoute[] }; let availableTransferRoute = transferRoutes.items - .filter((route) => isDefined(route.source_address)) + .filter( + ( + route + ): route is NativeMarketsTransferRoute & { + source_address: NonNullable; + } => isDefined(route.source_address) + ) .find( ({ source_address, destination_address }) => source_address.chain === "arbitrum" && @@ -502,14 +521,17 @@ export class Refiller { address: this.baseSignerAddress, addressId, }); - const { data: _availableTransferRoute } = await axios.post( - `${nativeMarketsApiUrl}/transfer_routes`, - newTransferRouteData, - { - headers, - } - ); - availableTransferRoute = _availableTransferRoute; + const createRouteResponse = await fetch(`${nativeMarketsApiUrl}/transfer_routes`, { + method: "POST", + headers, + body: JSON.stringify(newTransferRouteData), + }); + if (!createRouteResponse.ok) { + throw new Error(`Native markets create transfer_route failed: ${createRouteResponse.status}`); + } + availableTransferRoute = (await createRouteResponse.json()) as NativeMarketsTransferRoute & { + source_address: NonNullable; + }; } // Create the transfer transaction. @@ -694,3 +716,10 @@ export class Refiller { } type BalanceRequest = { chainId: number; token: Address; account: Address }; + +// Native Markets API response types +type NativeMarketsAddress = { id: string; chain: string; token: string; address_hex: string }; +type NativeMarketsTransferRoute = { + source_address?: { chain: string; token: string; address_hex: string }; + destination_address: { address_hex: string }; +}; diff --git a/src/utils/BridgeUtils.ts b/src/utils/BridgeUtils.ts index 1dde72c9a4..0c82f59554 100644 --- a/src/utils/BridgeUtils.ts +++ b/src/utils/BridgeUtils.ts @@ -1,5 +1,4 @@ import { CHAIN_IDs, Address, delay, TOKEN_SYMBOLS_MAP, toBN, winston, BigNumber } from "./"; -import axios, { RawAxiosRequestHeaders } from "axios"; // We need to instruct this bridge what tokens we expect to receive on L2, since the bridge // API supports multiple destination tokens for a single L1 token. @@ -113,17 +112,20 @@ export class BridgeApiClient { return transferRequestData.source_deposit_instructions.to_address; } - defaultHeaders(): RawAxiosRequestHeaders { + defaultHeaders(): Record { return { "Api-Key": `${this.bridgeApiKey}`, "Content-Type": "application/json", }; } - async getWithRetry(endpoint: string, headers: RawAxiosRequestHeaders, nRetries = this.nRetries) { + async getWithRetry(endpoint: string, headers: Record, nRetries = this.nRetries) { try { - const response = await axios.get(`${this.bridgeApiBase}/${endpoint}`, { headers }); - return response.data; + const response = await fetch(`${this.bridgeApiBase}/${endpoint}`, { headers }); + if (!response.ok) { + throw new Error(`Bridge API GET failed: ${response.status} ${response.statusText}`); + } + return (await response.json()) as T; } catch (e) { this.logger.debug({ at: "BridgeApi#_get", @@ -142,12 +144,19 @@ export class BridgeApiClient { async postWithRetry( endpoint: string, data: Record, - headers: RawAxiosRequestHeaders, + headers: Record, nRetries = this.nRetries ) { try { - const response = await axios.post(`${this.bridgeApiBase}/${endpoint}`, data, { headers }); - return response.data; + const response = await fetch(`${this.bridgeApiBase}/${endpoint}`, { + method: "POST", + headers, + body: JSON.stringify(data), + }); + if (!response.ok) { + throw new Error(`Bridge API POST failed: ${response.status} ${response.statusText}`); + } + return (await response.json()) as T; } catch (e) { this.logger.debug({ at: "BridgeApi#_post", diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index ce61682053..cf802d0854 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -1,7 +1,6 @@ import { arch, utils } from "@across-protocol/sdk"; import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import axios from "axios"; import { Contract, ethers } from "ethers"; import { CONTRACT_ADDRESSES, CCTP_MAX_SEND_AMOUNT } from "../common"; import { BigNumber } from "./BNUtils"; @@ -915,19 +914,21 @@ async function _fetchCctpV1Attestation( messageHash: string, isMainnet: boolean ): Promise { - const httpResponse = await axios.get( - `https://iris-api${isMainnet ? "" : "-sandbox"}.circle.com/attestations/${messageHash}` - ); - const attestationResponse = httpResponse.data; - return attestationResponse; + const response = await fetch(`https://iris-api${isMainnet ? "" : "-sandbox"}.circle.com/attestations/${messageHash}`); + if (!response.ok) { + throw new Error(`CCTP attestation request failed: ${response.status} ${response.statusText}`); + } + return (await response.json()) as CCTPV1APIGetAttestationResponse; } async function _fetchCCTPSvmAttestationProof(transactionHash: string): Promise { - const httpResponse = await axios.get( + const response = await fetch( `https://iris-api.circle.com/messages/${getCctpDomainForChainId(CHAIN_IDs.SOLANA)}/${transactionHash}` ); - const attestationResponse = httpResponse.data; - return attestationResponse; + if (!response.ok) { + throw new Error(`CCTP SVM attestation request failed: ${response.status} ${response.statusText}`); + } + return (await response.json()) as CCTPV1APIGetMessagesResponse; } /** diff --git a/src/utils/OFTUtils.ts b/src/utils/OFTUtils.ts index 39fcc0b0c0..afe62291b9 100644 --- a/src/utils/OFTUtils.ts +++ b/src/utils/OFTUtils.ts @@ -14,7 +14,6 @@ import { getSrcOftPeriphery, } from "."; import { BytesLike } from "ethers"; -import axios from "axios"; import { EVM_OFT_MESSENGERS } from "../common"; import { SortableEvent } from "../interfaces"; @@ -143,11 +142,12 @@ export function buildSimpleSendParamEvm(to: EvmAddress, dstEid: number, roundedA * @returns Array of message data objects as outlined in these docs: https://docs.layerzero.network/v2/concepts/troubleshooting/debugging-messages#response-shape. */ export async function getLzTransactionDetails(txHash: string): Promise { - const httpResponse = await axios.get<{ data: LzTransactionDetails[] }>( - `https://scan.layerzero-api.com/v1/messages/tx/${txHash}` - ); - const txDetails = httpResponse.data.data; - return txDetails; + const response = await fetch(`https://scan.layerzero-api.com/v1/messages/tx/${txHash}`); + if (!response.ok) { + throw new Error(`LayerZero API request failed: ${response.status} ${response.statusText}`); + } + const body = (await response.json()) as { data: LzTransactionDetails[] }; + return body.data; } /** diff --git a/yarn.lock b/yarn.lock index c15bf3a5d5..500a7de0e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3947,15 +3947,6 @@ axios@^1.12.2: form-data "^4.0.4" proxy-from-env "^1.1.0" -axios@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" - integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"