diff --git a/src/components/popup/Route.tsx b/src/components/popup/Route.tsx index 6aa301a3..2902ea07 100644 --- a/src/components/popup/Route.tsx +++ b/src/components/popup/Route.tsx @@ -8,13 +8,15 @@ import styled from "styled-components"; */ const Route: typeof BaseRoute = ({ path, component, children }) => { const [matches, params] = useRoute(path); + if (!matches) return null; + const routeContent = component ? createElement(component, { params }) : typeof children === "function" ? children(params) : children; - return matches ? {routeContent} : null; + return {routeContent}; }; const PageWrapper = styled(motion.main)` diff --git a/src/components/popup/home/Transactions.tsx b/src/components/popup/home/Transactions.tsx index 8e24e4d7..7d41067e 100644 --- a/src/components/popup/home/Transactions.tsx +++ b/src/components/popup/home/Transactions.tsx @@ -16,7 +16,7 @@ import { import { useHistory } from "~utils/hash_router"; import { getArPrice } from "~lib/coingecko"; import useSetting from "~settings/hook"; -import { suggestedGateways } from "~gateways/gateway"; +import { printTxWorkingGateways, txHistoryGateways } from "~gateways/gateway"; import { Spacer } from "@arconnect/components"; import { Heading, ViewAll, TokenCount } from "../Title"; import { @@ -29,6 +29,7 @@ import { type ExtendedTransaction } from "~lib/transactions"; import BigNumber from "bignumber.js"; +import { retryWithDelay } from "~utils/retry"; export default function Transactions() { const [transactions, fetchTransactions] = useState([]); @@ -65,8 +66,24 @@ export default function Transactions() { rawAoReceived, rawPrintArchive ] = await Promise.allSettled( - queries.map((query) => - gql(query, { address: activeAddress }, suggestedGateways[1]) + queries.map((query, index) => + retryWithDelay(async (attempt) => { + const data = await gql( + query, + { address: activeAddress }, + index !== 4 + ? txHistoryGateways[attempt % txHistoryGateways.length] + : printTxWorkingGateways[ + attempt % printTxWorkingGateways.length + ] + ); + if (data?.data === null && (data as any)?.errors?.length > 0) { + throw new Error( + (data as any)?.errors?.[0]?.message || "GraphQL Error" + ); + } + return data; + }, 2) ) ); @@ -122,6 +139,16 @@ export default function Transactions() { } }); + combinedTransactions = combinedTransactions.reduce( + (acc, transaction) => { + if (!acc.some((t) => t.node.id === transaction.node.id)) { + acc.push(transaction); + } + return acc; + }, + [] as ExtendedTransaction[] + ); + fetchTransactions(combinedTransactions); } } catch (error) { diff --git a/src/gateways/gateway.ts b/src/gateways/gateway.ts index 183b6f75..e4227d33 100644 --- a/src/gateways/gateway.ts +++ b/src/gateways/gateway.ts @@ -24,7 +24,7 @@ export const suggestedGateways: Gateway[] = [ protocol: "https" }, { - host: "arweave.live", + host: "g8way.io", port: 443, protocol: "https" } @@ -49,4 +49,33 @@ export const fallbackGateway = { protocol: "https" }; +export const printTxWorkingGateways: Gateway[] = [ + { + host: "arweave-search.goldsky.com", + port: 443, + protocol: "https" + }, + { + host: "permagate.io", + port: 443, + protocol: "https" + }, + { + host: "ar-io.dev", + port: 443, + protocol: "https" + }, + { + host: "arweave.dev", + port: 443, + protocol: "https" + } +]; + +export const txHistoryGateways = [ + suggestedGateways[1], + suggestedGateways[0], + suggestedGateways[3] +]; + export const defaultGateway = suggestedGateways[0]; diff --git a/src/lib/transactions.ts b/src/lib/transactions.ts index 7e747f05..295e17e4 100644 --- a/src/lib/transactions.ts +++ b/src/lib/transactions.ts @@ -1,7 +1,7 @@ import type GQLResultInterface from "ar-gql/dist/faces"; import type { GQLEdgeInterface } from "ar-gql/dist/faces"; import type { RawTransaction } from "~notifications/api"; -import type { TokenInfo } from "~tokens/aoTokens/ao"; +import { timeoutPromise, type TokenInfo } from "~tokens/aoTokens/ao"; import { formatAddress } from "~utils/format"; import { ExtensionStorage } from "~utils/storage"; import { getTokenInfo } from "~tokens/aoTokens/router"; @@ -91,7 +91,10 @@ const processAoTransaction = async ( transaction: GQLEdgeInterface, type: string ) => { - const tokenData = await fetchTokenByProcessId(transaction.node.recipient); + const tokenData = await timeoutPromise( + fetchTokenByProcessId(transaction.node.recipient), + 10000 + ).catch(() => null); const quantityTag = transaction.node.tags.find( (tag) => tag.name === "Quantity" ); diff --git a/src/notifications/utils.ts b/src/notifications/utils.ts index f306a99d..581e4c86 100644 --- a/src/notifications/utils.ts +++ b/src/notifications/utils.ts @@ -287,6 +287,7 @@ query ($address: String!, $after: String) { recipient owner { address } quantity { ar } + fee { ar } block { timestamp, height } tags { name diff --git a/src/popup.tsx b/src/popup.tsx index f9b8c6c0..fefead63 100644 --- a/src/popup.tsx +++ b/src/popup.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import { useHashLocation } from "~utils/hash_router"; import { syncLabels, useSetUp } from "~wallets"; import { useEffect, useState } from "react"; -import { Router } from "wouter"; +import { Router, Switch } from "wouter"; import HistoryProvider from "~components/popup/HistoryProvider"; @@ -122,17 +122,21 @@ export default function Popup() { {(params: { url: string }) => } - - {(params: { id: string }) => } - - + + + + {(params: { id: string }) => } + + - - {(params: { address: string }) => ( - - )} - - + + + + {(params: { address: string }) => ( + + )} + + { return hasNextPages[idx] - ? gql( - query, - { address: activeAddress, after: cursors[idx] }, - suggestedGateways[1] - ) + ? retryWithDelay(async (attempt) => { + const data = await gql( + query, + { address: activeAddress, after: cursors[idx] }, + idx !== 4 + ? txHistoryGateways[attempt % txHistoryGateways.length] + : printTxWorkingGateways[ + attempt % printTxWorkingGateways.length + ] + ); + if ( + data?.data === null && + (data as any)?.errors?.length > 0 + ) { + throw new Error( + (data as any)?.errors?.[0]?.message || "GraphQL Error" + ); + } + return data; + }, 2) : ({ data: { transactions: { @@ -228,18 +244,16 @@ export default function Transactions() { : "Pending"} - {transaction.transactionType !== "printArchive" && ( -
-
{getFormattedAmount(transaction)}
- - {getFormattedFiatAmount( - transaction, - arPrice, - currency - )} - -
- )} +
+
{getFormattedAmount(transaction)}
+ + {getFormattedFiatAmount( + transaction, + arPrice, + currency + )} + +
))} diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 60193365..13f4b16d 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -6,7 +6,7 @@ * @return A Promise that resolves with the result of the function or rejects after all attempts fail. */ export async function retryWithDelay( - fn: () => Promise, + fn: (attemp: number) => Promise, maxAttempts: number = 3, delay: number = 1000 ): Promise { @@ -14,7 +14,7 @@ export async function retryWithDelay( const attempt = async (): Promise => { try { - return await fn(); + return await fn(attempts); } catch (error) { attempts += 1; if (attempts < maxAttempts) {