Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add Nova Transaction page #1162

Merged
merged 9 commits into from
Feb 26, 2024
5 changes: 5 additions & 0 deletions api/src/models/api/nova/ISearchResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ export interface ISearchResponse extends IResponse {
* Nft id if it was found.
*/
nftId?: string;

/**
* Transaction included block.
*/
transactionBlock?: Block;
}
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ITransactionDetailsRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ITransactionDetailsRequest {
/**
* The network to search on.
*/
network: string;

/**
* The transaction id to get the details for.
*/
transactionId: string;
}
11 changes: 11 additions & 0 deletions api/src/models/api/nova/ITransactionDetailsResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */
import { Block } from "@iota/sdk-nova";
import { IResponse } from "./IResponse";

export interface ITransactionDetailsResponse extends IResponse {
/**
* Transaction included block.
*/
block?: Block;
}
6 changes: 6 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ export const routes: IRoute[] = [
folder: "nova/account/foundries",
func: "get",
},
{
path: "/nova/transaction/:network/:transactionId",
method: "get",
folder: "nova/transaction",
func: "get",
},
{
path: "/nova/account/congestion/:network/:accountId",
method: "get",
Expand Down
30 changes: 30 additions & 0 deletions api/src/routes/nova/transaction/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../factories/serviceFactory";
import { ITransactionDetailsRequest } from "../../../models/api/nova/ITransactionDetailsRequest";
import { ITransactionDetailsResponse } from "../../../models/api/nova/ITransactionDetailsResponse";
import { IConfiguration } from "../../../models/configuration/IConfiguration";
import { NOVA } from "../../../models/db/protocolVersion";
import { NetworkService } from "../../../services/networkService";
import { NovaApiService } from "../../../services/nova/novaApiService";
import { ValidationHelper } from "../../../utils/validationHelper";

/**
* Find the object from the network.
* @param config The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(config: IConfiguration, request: ITransactionDetailsRequest): Promise<ITransactionDetailsResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
ValidationHelper.string(request.transactionId, "transactionId");

const networkConfig = networkService.get(request.network);

if (networkConfig.protocolVersion !== NOVA) {
return {};
}

const novaApiService = ServiceFactory.get<NovaApiService>(`api-service-${networkConfig.network}`);
return novaApiService.transactionIncludedBlock(request.transactionId);
}
25 changes: 25 additions & 0 deletions api/src/services/nova/novaApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { INftDetailsResponse } from "../../models/api/nova/INftDetailsResponse";
import { IOutputDetailsResponse } from "../../models/api/nova/IOutputDetailsResponse";
import { IRewardsResponse } from "../../models/api/nova/IRewardsResponse";
import { ISearchResponse } from "../../models/api/nova/ISearchResponse";
import { ITransactionDetailsResponse } from "../../models/api/nova/ITransactionDetailsResponse";
import { INetwork } from "../../models/db/INetwork";
import { HexHelper } from "../../utils/hexHelper";
import { SearchExecutor } from "../../utils/nova/searchExecutor";
Expand Down Expand Up @@ -86,6 +87,30 @@ export class NovaApiService {
}
}

/**
* Get the transaction included block.
* @param transactionId The transaction id to get the details.
* @returns The item details.
*/
public async transactionIncludedBlock(transactionId: string): Promise<ITransactionDetailsResponse> {
transactionId = HexHelper.addPrefix(transactionId);
try {
const block = await this.client.getIncludedBlock(transactionId);

if (!block) {
return { error: `Couldn't find block from transaction id ${transactionId}` };
}
if (block && Object.keys(block).length > 0) {
return {
block,
};
}
} catch (e) {
logger.error(`Failed fetching block with transaction id ${transactionId}. Cause: ${e}`);
return { error: "Block fetch failed." };
}
}

/**
* Get the output details.
* @param outputId The output id to get the details.
Expand Down
15 changes: 15 additions & 0 deletions api/src/utils/nova/searchExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ export class SearchExecutor {
);
}

if (searchQuery.transactionId) {
promises.push(
this.executeQuery(
this.apiService.transactionIncludedBlock(searchQuery.transactionId),
(response) => {
promisesResult = {
transactionBlock: response.block,
error: response.error || response.message,
};
},
"Transaction included block fetch failed",
),
);
}

await Promise.any(promises).catch((_) => {});

if (promisesResult !== null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import classNames from "classnames";
import { TRANSACTION_FAILURE_REASON_STRINGS, Transaction, TransactionMetadata } from "@iota/sdk-wasm-nova/web";
import { TRANSACTION_FAILURE_REASON_STRINGS, Transaction, TransactionMetadata, Utils } from "@iota/sdk-wasm-nova/web";
import React from "react";
import "./TransactionMetadataSection.scss";
import Spinner from "../../../Spinner";
Expand All @@ -14,7 +14,7 @@ interface TransactionMetadataSectionProps {
}

const TransactionMetadataSection: React.FC<TransactionMetadataSectionProps> = ({ transaction, transactionMetadata, metadataError }) => {
const { name: network } = useNetworkInfoNova((s) => s.networkInfo);
const { name: network, bech32Hrp } = useNetworkInfoNova((s) => s.networkInfo);

return (
<div className="section metadata-section">
Expand Down Expand Up @@ -73,7 +73,7 @@ const TransactionMetadataSection: React.FC<TransactionMetadataSectionProps> = ({
<div className="value code highlight margin-b-t" key={idx}>
<TruncatedId
id={allotment.accountId}
link={`/${network}/account/${allotment.accountId}`}
link={`/${network}/addr/${Utils.accountIdToBech32(allotment.accountId, bech32Hrp)}`}
showCopyButton
/>
</div>
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import NftRedirectRoute from "./routes/stardust/NftRedirectRoute";
import StardustOutputList from "./routes/stardust/OutputList";
import StardustOutputPage from "./routes/stardust/OutputPage";
import NovaBlockPage from "./routes/nova/Block";
import NovaTransactionPage from "./routes/nova/TransactionPage";
import NovaOutputPage from "./routes/nova/OutputPage";
import NovaSearch from "./routes/nova/Search";
import StardustSearch from "./routes/stardust/Search";
Expand Down Expand Up @@ -178,6 +179,7 @@ const buildAppRoutes = (protocolVersion: string, withNetworkContext: (wrappedCom
<Route path="/:network/block/:blockId" key={keys.next().value} component={NovaBlockPage} />,
<Route path="/:network/output/:outputId" key={keys.next().value} component={NovaOutputPage} />,
<Route path="/:network/search/:query?" key={keys.next().value} component={NovaSearch} />,
<Route path="/:network/transaction/:transactionId" key={keys.next().value} component={NovaTransactionPage} />,
];

return (
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/routes/nova/OutputPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const OutputPage: React.FC<RouteComponentProps<OutputPageProps>> = ({
<div className="section--data">
<div className="label">Transaction ID</div>
<div className="value code highlight row middle">
<TruncatedId id={transactionId} showCopyButton />
<TruncatedId id={transactionId} link={`/${network}/transaction/${transactionId}`} showCopyButton />
</div>
</div>
)}
Expand Down
3 changes: 1 addition & 2 deletions client/src/app/routes/nova/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,8 @@ const Search: React.FC<RouteComponentProps<SearchRouteProps>> = (props) => {
} else if (response.output) {
route = "output";
routeParam = response.output.metadata.outputId;
} else if (response.transactionId) {
} else if (response.transactionBlock) {
route = "transaction";
routeParam = response.transactionId;
} else if (response.foundryId) {
route = "foundry";
routeParam = response.foundryId;
Expand Down
71 changes: 71 additions & 0 deletions client/src/app/routes/nova/TransactionPage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
@import "../../../scss/fonts";
@import "../../../scss/mixins";
@import "../../../scss/media-queries";
@import "../../../scss/variables";

.transaction-page {
display: flex;
flex-direction: column;

.wrapper {
display: flex;
justify-content: center;

.inner {
display: flex;
flex: 1;
flex-direction: column;
max-width: $desktop-width;
margin: 40px 25px;

@include desktop-down {
flex: unset;
width: 100%;
max-width: 100%;
margin: 40px 24px;
padding-right: 24px;
padding-left: 24px;

> .row {
flex-direction: column;
}
}

@include tablet-down {
margin: 28px 0;
}

.transation-page--header {
display: flex;
align-items: center;
justify-content: space-between;
}

.section {
padding-top: 44px;

.section--header {
margin-top: 44px;
}
}

.link {
@include font-size(14px);

max-width: 100%;
color: var(--link-color);
font-family: $ibm-plex-mono;
font-weight: normal;
letter-spacing: 0.02em;
line-height: 20px;
}
}

.section--data {
.amount-transacted {
@include font-size(15px);
font-weight: 700;
}
}
}
}
Loading
Loading