diff --git a/api/src/services/db/transactionsService.ts b/api/src/services/db/transactionsService.ts index 54011f092..89608b06b 100644 --- a/api/src/services/db/transactionsService.ts +++ b/api/src/services/db/transactionsService.ts @@ -3,6 +3,7 @@ import { AkashBlock as Block, AkashMessage as Message } from "@shared/dbSchemas/ import { msgToJSON } from "@src/utils/protobuf"; import { QueryTypes } from "sequelize"; import { chainDb } from "@src/db/dbConnection"; +import { ApiTransactionResponse } from "@src/types/transactions"; export async function getTransactions(limit: number) { const _limit = Math.min(limit, 100); @@ -41,7 +42,7 @@ export async function getTransactions(limit: number) { })); } -export async function getTransaction(hash: string) { +export async function getTransaction(hash: string): Promise { const tx = await Transaction.findOne({ where: { hash: hash @@ -62,7 +63,9 @@ export async function getTransaction(hash: string) { ] }); - if (!tx) return null; + if (!tx) { + return null; + } const messages = await Message.findAll({ where: { diff --git a/api/src/services/external/apiNodeService.ts b/api/src/services/external/apiNodeService.ts index 583475920..867701827 100644 --- a/api/src/services/external/apiNodeService.ts +++ b/api/src/services/external/apiNodeService.ts @@ -1,6 +1,6 @@ import fetch from "node-fetch"; import { getDeploymentRelatedMessages } from "../db/deploymentService"; -import { averageBlockCountInAMonth } from "@src/utils/constants"; +import { apiNodeUrl, averageBlockCountInAMonth, betaTypeVersion, betaTypeVersionMarket } from "@src/utils/constants"; import { coinToAsset } from "@src/utils/coin"; import { getTransactionByAddress } from "@src/services/db/transactionsService"; import axios from "axios"; @@ -8,7 +8,6 @@ import { Validator } from "@shared/dbSchemas/base"; import { Op } from "sequelize"; import { Deployment, Lease, Provider, ProviderAttribute } from "@shared/dbSchemas/akash"; import { cacheKeys, cacheResponse } from "@src/caching/helpers"; -import { env } from "@src/utils/env"; import { CosmosGovProposalResponse, CosmosGovProposalsResponse, @@ -31,15 +30,7 @@ import { CosmosDistributionParamsResponse } from "@src/types/rest/cosmosDistribu import { CosmosDistributionValidatorsCommissionResponse } from "@src/types/rest/cosmosDistributionValidatorsCommissionResponse"; import { getProviderList } from "../db/providerStatusService"; -const defaultNodeUrlMapping: { [key: string]: string } = { - mainnet: "https://api.akashnet.net:443", - sandbox: "https://api.sandbox-01.aksh.pw", - testnet: "https://api.testnet-02.aksh.pw" -}; -const apiNodeUrl = env.RestApiNodeUrl ?? defaultNodeUrlMapping[env.Network] ?? defaultNodeUrlMapping.mainnet; -const betaTypeVersion = "v1beta3"; -const betaTypeVersionMarket = "v1beta4"; export async function getChainStats() { const result = await cacheResponse( diff --git a/api/src/types/rest/cosmosTransactionResponse.ts b/api/src/types/rest/cosmosTransactionResponse.ts new file mode 100644 index 000000000..ec0cca3d6 --- /dev/null +++ b/api/src/types/rest/cosmosTransactionResponse.ts @@ -0,0 +1,147 @@ +export type RestCosmostTransactionResponse = { + tx: { + body: { + messages: { + "@type": string; + client_id: string; + header: { + "@type": string; + signed_header: { + header: { + version: { + block: string; + app: string; + }; + chain_id: string; + height: string; + time: string; + last_block_id: { + hash: string; + part_set_header: { + total: number; + hash: string; + }; + }; + last_commit_hash: string; + data_hash: string; + validators_hash: string; + next_validators_hash: string; + consensus_hash: string; + app_hash: string; + last_results_hash: string; + evidence_hash: string; + proposer_address: string; + }; + commit: { + height: string; + round: number; + block_id: { + hash: string; + part_set_header: { + total: number; + hash: string; + }; + }; + signatures: { + block_id_flag: string; + validator_address: string; + timestamp: string; + signature: string; + }[]; + }; + }; + }; + }[]; + }; + auth_info: { + signer_infos: [ + { + public_key: { + "@type": string; + key: string; + }; + mode_info: { + single: { + mode: string; + }; + }; + sequence: string; + } + ]; + fee: { + amount: [ + { + denom: string; + amount: string; + } + ]; + gas_limit: string; + payer: string; + granter: string; + }; + }; + signatures: string[]; + }; + tx_response: { + height: string; + txhash: string; + codespace: string; + code: number; + data: string; + raw_log: string; + logs: { + msg_index: number; + log: string; + events: { + type: string; + attributes: { + key: string; + value: string; + }[]; + }[]; + }[]; + info: string; + gas_wanted: string; + gas_used: string; + tx: { + type: string; + value: { + msg: { + type: string; + value: { + from_address: string; + to_address: string; + amount: { + denom: string; + amount: string; + }[]; + }; + }[]; + fee: { + amount: { + denom: string; + amount: string; + }[]; + gas: string; + }; + signatures: { + pub_key: { + type: string; + value: string; + }; + signature: string; + }[]; + memo: string; + }; + }; + timestamp: string; + events: { + type: string; + attributes: { + key: string; + value: string; + index: boolean; + }[]; + }[]; + }; +}; diff --git a/api/src/types/transactions.ts b/api/src/types/transactions.ts new file mode 100644 index 000000000..53e7b640d --- /dev/null +++ b/api/src/types/transactions.ts @@ -0,0 +1,19 @@ +export type ApiTransactionResponse = { + height: number | string; + datetime: Date; + hash: string; + multisigThreshold?: number; + signers?: string[]; + isSuccess: boolean; + error: string | null; + gasUsed: number; + gasWanted: number; + fee: number; + memo: string; + messages: { + id: string; + type: string; + data: any; + relatedDeploymentId: string | null; + }[]; +}; diff --git a/api/src/utils/constants.ts b/api/src/utils/constants.ts index 2aeb898dc..7c2eada31 100644 --- a/api/src/utils/constants.ts +++ b/api/src/utils/constants.ts @@ -14,3 +14,13 @@ export const openApiExampleAddress = "akash13265twfqejnma6cc93rw5dxk4cldyz2zyy8c export const openApiExampleProviderAddress = "akash18ga02jzaq8cw52anyhzkwta5wygufgu6zsz6xc"; export const openApiExampleTransactionHash = "A19F1950D97E576F0D7B591D71A8D0366AA8BA0A7F3DA76F44769188644BE9EB"; export const openApiExampleValidatorAddress = "akashvaloper14mt78hz73d9tdwpdvkd59ne9509kxw8yj7qy8f"; + +export const defaultNodeUrlMapping: { [key: string]: string } = { + mainnet: "https://api.akashnet.net:443", + sandbox: "https://api.sandbox-01.aksh.pw", + testnet: "https://api.testnet-02.aksh.pw" +}; + +export const apiNodeUrl = env.RestApiNodeUrl ?? defaultNodeUrlMapping[env.Network] ?? defaultNodeUrlMapping.mainnet; +export const betaTypeVersion = "v1beta3"; +export const betaTypeVersionMarket = "v1beta4"; diff --git a/deploy-web/src/context/WalletProvider/WalletProvider.tsx b/deploy-web/src/context/WalletProvider/WalletProvider.tsx index 202d07fd6..1090d2b77 100644 --- a/deploy-web/src/context/WalletProvider/WalletProvider.tsx +++ b/deploy-web/src/context/WalletProvider/WalletProvider.tsx @@ -2,7 +2,7 @@ import React, { useRef } from "react"; import { useState, useEffect } from "react"; import { SigningStargateClient } from "@cosmjs/stargate"; -import { uAktDenom } from "@src/utils/constants"; +import { STATS_APP_URL, uAktDenom } from "@src/utils/constants"; import { EncodeObject } from "@cosmjs/proto-signing"; import { TransactionModal } from "@src/components/layout/TransactionModal"; import { event } from "nextjs-google-analytics"; @@ -325,8 +325,9 @@ export function useWallet() { const TransactionSnackbarContent = ({ snackMessage, transactionHash }) => { const selectedNetwork = useSelectedNetwork(); - const txUrl = transactionHash && `https://stats.akash.network/transactions/${transactionHash}?network=${selectedNetwork.id}`; + const txUrl = transactionHash && `${STATS_APP_URL}/transactions/${transactionHash}?network=${selectedNetwork.id}`; + console.log(txUrl); return ( <> {snackMessage} diff --git a/deploy-web/src/utils/constants.ts b/deploy-web/src/utils/constants.ts index 1ab7a9a9c..be8d08d37 100644 --- a/deploy-web/src/utils/constants.ts +++ b/deploy-web/src/utils/constants.ts @@ -24,6 +24,7 @@ export enum RouteStepKeys { const productionMainnetApiUrl = "https://api.cloudmos.io"; const productionTestnetApiUrl = "https://api-testnet.cloudmos.io"; const productionSandboxApiUrl = "https://api-sandbox.cloudmos.io"; +const productionStatsAppUrl = "https://stats.akash.network"; const productionHostnames = ["deploy.cloudmos.io", "console.akash.network", "staging-console.akash.network", "beta.cloudmos.io"]; export const isProd = process.env.NODE_ENV === "production"; @@ -31,6 +32,7 @@ export const isMaintenanceMode = process.env.MAINTENANCE_MODE === "true"; export const BASE_API_MAINNET_URL = getApiMainnetUrl(); export const BASE_API_TESTNET_URL = getApiTestnetUrl(); export const BASE_API_SANDBOX_URL = getApiSandboxUrl(); +export const STATS_APP_URL = getStatsAppUrl(); export const BASE_API_URL = getApiUrl(); @@ -100,6 +102,13 @@ function getApiUrl() { return "http://localhost:3080"; } +function getStatsAppUrl() { + if (process.env.STATS_APP_URL) return process.env.STATS_APP_URL; + if (typeof window === "undefined") return "http://localhost:3001"; + if (productionHostnames.includes(window.location?.hostname)) return productionStatsAppUrl; + return "http://localhost:3001"; +} + function getProviderProxyHttpUrl() { if (typeof window === "undefined") return "http://localhost:3040"; if (window.location?.hostname === "deploybeta.cloudmos.io") return "https://deployproxybeta.cloudmos.io"; diff --git a/stats-web/src/app/errors.tsx b/stats-web/src/app/error.tsx similarity index 62% rename from stats-web/src/app/errors.tsx rename to stats-web/src/app/error.tsx index 65ae27eb5..975376567 100644 --- a/stats-web/src/app/errors.tsx +++ b/stats-web/src/app/error.tsx @@ -1,5 +1,8 @@ "use client"; // Error components must be Client Components +import PageContainer from "@/components/PageContainer"; +import { Title } from "@/components/Title"; +import { Button } from "@/components/ui/button"; import { useEffect } from "react"; export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { @@ -9,16 +12,17 @@ export default function Error({ error, reset }: { error: Error & { digest?: stri }, [error]); return ( -
-

Something went wrong!

- -
+ + ); } diff --git a/stats-web/src/app/transactions/[hash]/errors.tsx b/stats-web/src/app/transactions/[hash]/error.tsx similarity index 62% rename from stats-web/src/app/transactions/[hash]/errors.tsx rename to stats-web/src/app/transactions/[hash]/error.tsx index 65ae27eb5..975376567 100644 --- a/stats-web/src/app/transactions/[hash]/errors.tsx +++ b/stats-web/src/app/transactions/[hash]/error.tsx @@ -1,5 +1,8 @@ "use client"; // Error components must be Client Components +import PageContainer from "@/components/PageContainer"; +import { Title } from "@/components/Title"; +import { Button } from "@/components/ui/button"; import { useEffect } from "react"; export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { @@ -9,16 +12,17 @@ export default function Error({ error, reset }: { error: Error & { digest?: stri }, [error]); return ( -
-

Something went wrong!

- -
+ + ); } diff --git a/stats-web/src/app/transactions/[hash]/page.tsx b/stats-web/src/app/transactions/[hash]/page.tsx index f2fc56b3f..b35fa2a97 100644 --- a/stats-web/src/app/transactions/[hash]/page.tsx +++ b/stats-web/src/app/transactions/[hash]/page.tsx @@ -8,6 +8,7 @@ import { Title } from "@/components/Title"; import { Card, CardContent } from "@/components/ui/card"; import { TransactionInfo } from "./TransactionInfo"; import { TxMessageRow } from "@/components/transactions/TxMessageRow"; +import { Alert } from "@/components/ui/alert"; interface IProps { params: { hash: string }; @@ -21,13 +22,15 @@ export async function generateMetadata({ params: { hash } }: IProps, parent: Res }; } -async function fetchTransactionData(hash: string, network: string): Promise { +async function fetchTransactionData(hash: string, network: string): Promise { const apiUrl = getNetworkBaseApiUrl(network); const response = await fetch(`${apiUrl}/v1/transactions/${hash}`); - if (!response.ok) { + if (!response.ok && response.status !== 404) { // This will activate the closest `error.js` Error Boundary - throw new Error("Error fetching block data"); + throw new Error("Error fetching transction data"); + } else if (response.status === 404) { + return null; } return response.json(); @@ -40,21 +43,27 @@ export default async function TransactionDetailPage({ params: { hash }, searchPa Transaction Details - - -
- - Messages - - - {transaction.messages.map(msg => ( - - - - - - ))} -
+ {transaction ? ( + <> + + +
+ + Messages + + + {transaction.messages.map(msg => ( + + + + + + ))} +
+ + ) : ( + Transaction not found or indexed yet. Please wait a few seconds before refreshing. + )}
); }