diff --git a/api/src/models/api/nova/ISlotRequest.ts b/api/src/models/api/nova/ISlotRequest.ts new file mode 100644 index 000000000..557fb3337 --- /dev/null +++ b/api/src/models/api/nova/ISlotRequest.ts @@ -0,0 +1,11 @@ +export interface ISlotRequest { + /** + * The network to search on. + */ + network: string; + + /** + * The slot index to get the details for. + */ + slotIndex: string; +} diff --git a/api/src/models/api/nova/ISlotResponse.ts b/api/src/models/api/nova/ISlotResponse.ts new file mode 100644 index 000000000..342e6c65f --- /dev/null +++ b/api/src/models/api/nova/ISlotResponse.ts @@ -0,0 +1,10 @@ +// eslint-disable-next-line import/no-unresolved +import { SlotCommitment } from "@iota/sdk-nova"; +import { IResponse } from "./IResponse"; + +export interface ISlotResponse extends IResponse { + /** + * The deserialized slot. + */ + slot?: SlotCommitment; +} diff --git a/api/src/routes.ts b/api/src/routes.ts index 60a25ca9d..dad8537aa 100644 --- a/api/src/routes.ts +++ b/api/src/routes.ts @@ -256,4 +256,5 @@ export const routes: IRoute[] = [ }, { path: "/nova/block/:network/:blockId", method: "get", folder: "nova/block", func: "get" }, { path: "/nova/block/metadata/:network/:blockId", method: "get", folder: "nova/block/metadata", func: "get" }, + { path: "/nova/slot/:network/:slotIndex", method: "get", folder: "nova/slot", func: "get" }, ]; diff --git a/api/src/routes/nova/slot/get.ts b/api/src/routes/nova/slot/get.ts new file mode 100644 index 000000000..40339920f --- /dev/null +++ b/api/src/routes/nova/slot/get.ts @@ -0,0 +1,30 @@ +import { ServiceFactory } from "../../../factories/serviceFactory"; +import { ISlotRequest } from "../../../models/api/nova/ISlotRequest"; +import { ISlotResponse } from "../../../models/api/nova/ISlotResponse"; +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"; + +/** + * Fetch the block from the network. + * @param _ The configuration. + * @param request The request. + * @returns The response. + */ +export async function get(_: IConfiguration, request: ISlotRequest): Promise { + const networkService = ServiceFactory.get("network"); + const networks = networkService.networkNames(); + ValidationHelper.oneOf(request.network, networks, "network"); + ValidationHelper.numberFromString(request.slotIndex, "slotIndex"); + + const networkConfig = networkService.get(request.network); + + if (networkConfig.protocolVersion !== NOVA) { + return {}; + } + + const novaApiService = ServiceFactory.get(`api-service-${networkConfig.network}`); + return novaApiService.getSlotCommitment(Number(request.slotIndex)); +} diff --git a/api/src/services/nova/novaApiService.ts b/api/src/services/nova/novaApiService.ts index a85727dd6..441c1b520 100644 --- a/api/src/services/nova/novaApiService.ts +++ b/api/src/services/nova/novaApiService.ts @@ -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 { ISlotResponse } from "../../models/api/nova/ISlotResponse"; import { ITransactionDetailsResponse } from "../../models/api/nova/ITransactionDetailsResponse"; import { INetwork } from "../../models/db/INetwork"; import { HexHelper } from "../../utils/hexHelper"; @@ -325,6 +326,21 @@ export class NovaApiService { return manaRewardsResponse ? { outputId, manaRewards: manaRewardsResponse } : { outputId, message: "Rewards data not found" }; } + /** + * Get the slot commitment. + * @param slotIndex The slot index to get the commitment for. + * @returns The slot commitment. + */ + public async getSlotCommitment(slotIndex: number): Promise { + try { + const slot = await this.client.getCommitmentByIndex(slotIndex); + + return { slot }; + } catch (e) { + logger.error(`Failed fetching slot with slot index ${slotIndex}. Cause: ${e}`); + } + } + /** * Find item on the stardust network. * @param query The query to use for finding items. diff --git a/client/src/app/components/nova/PageDataRow.tsx b/client/src/app/components/nova/PageDataRow.tsx new file mode 100644 index 000000000..57658ed01 --- /dev/null +++ b/client/src/app/components/nova/PageDataRow.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import classNames from "classnames"; +import TruncatedId from "../stardust/TruncatedId"; + +export interface IPageDataRow { + label: string; + value?: string | number; + highlight?: boolean; + truncatedId?: { + id: string; + link?: string; + showCopyButton?: boolean; + }; +} +const PageDataRow = ({ label, value, truncatedId, highlight }: IPageDataRow): React.JSX.Element => { + return ( +
+
{label}
+
+ {truncatedId ? ( + + ) : ( + value + )} +
+
+ ); +}; + +export default PageDataRow; diff --git a/client/src/app/components/nova/StatusPill.scss b/client/src/app/components/nova/StatusPill.scss new file mode 100644 index 000000000..6467cdc5b --- /dev/null +++ b/client/src/app/components/nova/StatusPill.scss @@ -0,0 +1,41 @@ +@import "./../../../scss/fonts"; +@import "./../../../scss/mixins"; +@import "./../../../scss/media-queries"; +@import "./../../../scss/variables"; + +.status-pill { + @include font-size(12px); + + display: flex; + align-items: center; + margin-right: 8px; + padding: 6px 12px; + border: 0; + border-radius: 6px; + outline: none; + color: $gray-midnight; + font-family: $inter; + font-weight: 500; + letter-spacing: 0.5px; + white-space: nowrap; + + @include phone-down { + height: 32px; + } + + &.status__ { + &success { + background-color: var(--message-confirmed-bg); + color: $mint-green-7; + } + + &error { + background-color: var(--message-conflicting-bg); + } + + &pending { + background-color: var(--light-bg); + color: #8493ad; + } + } +} diff --git a/client/src/app/components/nova/StatusPill.tsx b/client/src/app/components/nova/StatusPill.tsx new file mode 100644 index 000000000..289ee81f2 --- /dev/null +++ b/client/src/app/components/nova/StatusPill.tsx @@ -0,0 +1,42 @@ +import classNames from "classnames"; +import React from "react"; +import { PillStatus } from "~/app/lib/ui/enums"; +import Tooltip from "../Tooltip"; +import "./StatusPill.scss"; + +interface IStatusPill { + /** + * Label for the status. + */ + label: string; + /** + * The status of the pill. + */ + status: PillStatus; + /** + * Tooltip explaining further for the label. + */ + tooltip?: string; +} + +const StatusPill: React.FC = ({ label, status, tooltip }): React.JSX.Element => ( + <> +
+ {tooltip ? ( + + {status} + + ) : ( + {label} + )} +
+ +); + +export default StatusPill; diff --git a/client/src/app/components/nova/block/BlockTangleState.scss b/client/src/app/components/nova/block/BlockTangleState.scss index c69f8c0ab..66652ad31 100644 --- a/client/src/app/components/nova/block/BlockTangleState.scss +++ b/client/src/app/components/nova/block/BlockTangleState.scss @@ -40,41 +40,4 @@ } } } - - .block-tangle-state { - @include font-size(12px); - - display: flex; - align-items: center; - height: 24px; - margin-right: 8px; - padding: 0 8px; - border: 0; - border-radius: 6px; - outline: none; - background-color: $gray-light; - color: $gray-midnight; - font-family: $inter; - font-weight: 500; - letter-spacing: 0.5px; - white-space: nowrap; - - @include phone-down { - height: 32px; - } - - &.block-tangle-state__confirmed { - background-color: var(--message-confirmed-bg); - color: $mint-green-7; - } - - &.block-tangle-state__conflicting { - background-color: var(--message-conflicting-bg); - } - - &.block-tangle-state__pending { - background-color: var(--light-bg); - color: #8493ad; - } - } } diff --git a/client/src/app/components/nova/block/BlockTangleState.tsx b/client/src/app/components/nova/block/BlockTangleState.tsx index e8a4619e6..1d8630c6c 100644 --- a/client/src/app/components/nova/block/BlockTangleState.tsx +++ b/client/src/app/components/nova/block/BlockTangleState.tsx @@ -1,9 +1,9 @@ -import classNames from "classnames"; import React from "react"; -import Tooltip from "../../Tooltip"; -import { BlockState, u64 } from "@iota/sdk-wasm-nova/web"; -import { BlockFailureReason, BLOCK_FAILURE_REASON_STRINGS } from "@iota/sdk-wasm-nova/web/lib/types/models/block-failure-reason"; import moment from "moment"; +import { BlockState, u64 } from "@iota/sdk-wasm-nova/web"; +import { BLOCK_FAILURE_REASON_STRINGS, BlockFailureReason } from "@iota/sdk-wasm-nova/web/lib/types/models/block-failure-reason"; +import StatusPill from "~/app/components/nova/StatusPill"; +import { PillStatus } from "~/app/lib/ui/enums"; import "./BlockTangleState.scss"; export interface BlockTangleStateProps { @@ -23,38 +23,29 @@ export interface BlockTangleStateProps { failureReason?: BlockFailureReason; } +const BLOCK_STATE_TO_PILL_STATUS: Record = { + pending: PillStatus.Pending, + accepted: PillStatus.Success, + confirmed: PillStatus.Success, + finalized: PillStatus.Success, + failed: PillStatus.Error, + rejected: PillStatus.Error, +}; + const BlockTangleState: React.FC = ({ status, issuingTime, failureReason }) => { const blockIssueMoment = moment(Number(issuingTime) / 1000000); const timeReference = blockIssueMoment.fromNow(); const longTimestamp = blockIssueMoment.format("LLLL"); + const pillStatus: PillStatus = BLOCK_STATE_TO_PILL_STATUS[status]; + const failureReasonString: string | undefined = failureReason ? BLOCK_FAILURE_REASON_STRINGS[failureReason] : undefined; + return ( <>
{status && ( -
- {failureReason ? ( - - - {status} - - - ) : ( - {status} - )} -
+
{timeReference} diff --git a/client/src/app/components/nova/block/section/TransactionMetadataSection.scss b/client/src/app/components/nova/block/section/TransactionMetadataSection.scss deleted file mode 100644 index c87daebc3..000000000 --- a/client/src/app/components/nova/block/section/TransactionMetadataSection.scss +++ /dev/null @@ -1,42 +0,0 @@ -@import "../../../../../scss/fonts"; -@import "../../../../../scss/mixins"; -@import "../../../../../scss/media-queries"; -@import "../../../../../scss/variables"; -@import "../../../../../scss/themes"; - -.transaction-tangle-state { - @include font-size(12px); - - display: flex; - align-items: center; - height: 24px; - margin-right: 8px; - padding: 0 8px; - border: 0; - border-radius: 6px; - outline: none; - background-color: $gray-light; - color: $gray-midnight; - font-family: $inter; - font-weight: 500; - letter-spacing: 0.5px; - white-space: nowrap; - - @include phone-down { - height: 32px; - } - - &.transaction-tangle-state__confirmed { - background-color: var(--message-confirmed-bg); - color: $mint-green-7; - } - - &.transaction-tangle-state__conflicting { - background-color: var(--message-conflicting-bg); - } - - &.transaction-tangle-state__pending { - background-color: var(--light-bg); - color: #8493ad; - } -} diff --git a/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx b/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx index eea4c3a02..79ea81e1c 100644 --- a/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx +++ b/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx @@ -1,11 +1,11 @@ -import classNames from "classnames"; -import { TRANSACTION_FAILURE_REASON_STRINGS, Transaction, TransactionMetadata, Utils } from "@iota/sdk-wasm-nova/web"; +import { TRANSACTION_FAILURE_REASON_STRINGS, Transaction, TransactionMetadata, TransactionState, Utils } from "@iota/sdk-wasm-nova/web"; import React from "react"; -import "./TransactionMetadataSection.scss"; import Spinner from "../../../Spinner"; import TruncatedId from "~/app/components/stardust/TruncatedId"; import ContextInputView from "../../ContextInputView"; import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; +import { PillStatus } from "~/app/lib/ui/enums"; +import StatusPill from "~/app/components/nova/StatusPill"; interface TransactionMetadataSectionProps { readonly transaction?: Transaction; @@ -13,8 +13,17 @@ interface TransactionMetadataSectionProps { readonly metadataError?: string; } +const TRANSACTION_STATE_TO_PILL_STATUS: Record = { + pending: PillStatus.Pending, + accepted: PillStatus.Success, + confirmed: PillStatus.Success, + finalized: PillStatus.Success, + failed: PillStatus.Error, +}; + const TransactionMetadataSection: React.FC = ({ transaction, transactionMetadata, metadataError }) => { const { name: network, bech32Hrp } = useNetworkInfoNova((s) => s.networkInfo); + const pillStatus: PillStatus | undefined = TRANSACTION_STATE_TO_PILL_STATUS[transactionMetadata?.transactionState ?? "pending"]; return (
@@ -28,23 +37,8 @@ const TransactionMetadataSection: React.FC = ({ <>
Transaction Status
-
-
- {transactionMetadata.transactionState} -
+
+
{transactionMetadata.transactionFailureReason && ( diff --git a/client/src/app/lib/enums/index.ts b/client/src/app/lib/enums/index.ts new file mode 100644 index 000000000..877581b6e --- /dev/null +++ b/client/src/app/lib/enums/index.ts @@ -0,0 +1 @@ +export * from "./slot-state.enums"; diff --git a/client/src/app/lib/enums/slot-state.enums.ts b/client/src/app/lib/enums/slot-state.enums.ts new file mode 100644 index 000000000..3a190a546 --- /dev/null +++ b/client/src/app/lib/enums/slot-state.enums.ts @@ -0,0 +1,16 @@ +export enum SlotState { + /** + * The slot is pending. + */ + Pending = "pending", + + /** + * The slot is committed. + */ + Committed = "committed", + + /** + * The slot is finalized. + */ + Finalized = "finalized", +} diff --git a/client/src/app/lib/ui/enums/index.ts b/client/src/app/lib/ui/enums/index.ts new file mode 100644 index 000000000..eeda1a054 --- /dev/null +++ b/client/src/app/lib/ui/enums/index.ts @@ -0,0 +1 @@ +export * from "./pill-status.enum"; diff --git a/client/src/app/lib/ui/enums/pill-status.enum.ts b/client/src/app/lib/ui/enums/pill-status.enum.ts new file mode 100644 index 000000000..5a3e05e32 --- /dev/null +++ b/client/src/app/lib/ui/enums/pill-status.enum.ts @@ -0,0 +1,5 @@ +export enum PillStatus { + Pending = "pending", + Success = "success", + Error = "error", +} diff --git a/client/src/app/routes.tsx b/client/src/app/routes.tsx index f45fbe482..62ac887e8 100644 --- a/client/src/app/routes.tsx +++ b/client/src/app/routes.tsx @@ -38,6 +38,7 @@ 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 NovaSlotPage from "./routes/nova/SlotPage"; import StardustSearch from "./routes/stardust/Search"; import StardustStatisticsPage from "./routes/stardust/statistics/StatisticsPage"; import StardustTransactionPage from "./routes/stardust/TransactionPage"; @@ -179,6 +180,7 @@ const buildAppRoutes = (protocolVersion: string, withNetworkContext: (wrappedCom , , , + , , ]; diff --git a/client/src/app/routes/nova/SlotPage.scss b/client/src/app/routes/nova/SlotPage.scss new file mode 100644 index 000000000..2c802392c --- /dev/null +++ b/client/src/app/routes/nova/SlotPage.scss @@ -0,0 +1,65 @@ +@import "../../../scss/fonts"; +@import "../../../scss/mixins"; +@import "../../../scss/media-queries"; +@import "../../../scss/variables"; + +.slot-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; + } + + .slot-page--header { + display: flex; + flex-direction: column; + align-items: flex-start; + + .header--title { + margin-bottom: 8px; + } + + .header--status { + display: flex; + } + } + + .section { + padding-top: 44px; + + .section--header { + margin-top: 44px; + } + + .card--content__output { + margin-top: 20px; + } + } + } + } +} diff --git a/client/src/app/routes/nova/SlotPage.tsx b/client/src/app/routes/nova/SlotPage.tsx new file mode 100644 index 000000000..a8d6aed2e --- /dev/null +++ b/client/src/app/routes/nova/SlotPage.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import useuseSlotDetails from "~/helpers/nova/hooks/useSlotDetails"; +import StatusPill from "~/app/components/nova/StatusPill"; +import PageDataRow, { IPageDataRow } from "~/app/components/nova/PageDataRow"; +import Modal from "~/app/components/Modal"; +import mainHeaderMessage from "~assets/modals/nova/slot/main-header.json"; +import NotFound from "~/app/components/NotFound"; +import { SlotState } from "~/app/lib/enums"; +import { RouteComponentProps } from "react-router-dom"; +import { PillStatus } from "~/app/lib/ui/enums"; +import "./SlotPage.scss"; + +const SLOT_STATE_TO_PILL_STATUS: Record = { + [SlotState.Pending]: PillStatus.Pending, + [SlotState.Committed]: PillStatus.Success, + [SlotState.Finalized]: PillStatus.Success, +}; + +export default function SlotPage({ + match: { + params: { network, slotIndex }, + }, +}: RouteComponentProps<{ + network: string; + slotIndex: string; +}>): React.JSX.Element { + const { slotCommitment } = useuseSlotDetails(network, slotIndex); + + const parsedSlotIndex = parseSlotIndex(slotIndex); + const slotState = slotCommitment ? SlotState.Finalized : SlotState.Pending; + const pillStatus: PillStatus = SLOT_STATE_TO_PILL_STATUS[slotState]; + + const dataRows: IPageDataRow[] = [ + { + label: "Slot Index", + value: slotCommitment?.slot || parsedSlotIndex, + highlight: true, + }, + { + label: "RMC", + value: slotCommitment?.referenceManaCost.toString(), + }, + ]; + + function parseSlotIndex(slotIndex: string): number | undefined { + const slotIndexNum = parseInt(slotIndex, 10); + if (isNaN(slotIndexNum)) { + return; + } + return slotIndexNum; + } + + return ( +
+
+
+
+
+

Slot

+ +
+ {parsedSlotIndex && ( +
+ +
+ )} +
+ {parsedSlotIndex ? ( +
+
+
+

General

+
+
+ {dataRows.map((dataRow, index) => { + if (dataRow.value || dataRow.truncatedId) { + return ; + } + })} +
+ ) : ( + + )} +
+
+
+ ); +} diff --git a/client/src/assets/modals/nova/slot/main-header.json b/client/src/assets/modals/nova/slot/main-header.json new file mode 100644 index 000000000..c9c5f0a80 --- /dev/null +++ b/client/src/assets/modals/nova/slot/main-header.json @@ -0,0 +1,11 @@ +{ + "title": "Slot", + "description": "

Each block in IOTA 2.0 contains a commitment to the content of a certain slot in the past. A slot commitment is a hash value that encapsulates all the crucial information about a slot (such as accepted blocks and transactions, the index of the slot, etc.).

", + "links": [ + { + "label": "Read more", + "href": "https://wiki.iota.org/learn/protocols/iota2.0/core-concepts/consensus/preliminaries/#slot-commitment-chain", + "isExternal": true + } + ] +} diff --git a/client/src/helpers/nova/hooks/useSlotDetails.ts b/client/src/helpers/nova/hooks/useSlotDetails.ts new file mode 100644 index 000000000..45da0620f --- /dev/null +++ b/client/src/helpers/nova/hooks/useSlotDetails.ts @@ -0,0 +1,54 @@ +import { SlotCommitment } from "@iota/sdk-wasm-nova/web"; +import { plainToInstance } from "class-transformer"; +import { useEffect, useState } from "react"; +import { ServiceFactory } from "~/factories/serviceFactory"; +import { useIsMounted } from "~/helpers/hooks/useIsMounted"; +import { NOVA } from "~/models/config/protocolVersion"; +import { NovaApiClient } from "~/services/nova/novaApiClient"; + +interface IUseSlotDetails { + slotCommitment: SlotCommitment | null; + error: string | undefined; + isLoading: boolean; +} + +export default function useSlotDetails(network: string, slotIndex: string): IUseSlotDetails { + const isMounted = useIsMounted(); + const [apiClient] = useState(ServiceFactory.get(`api-client-${NOVA}`)); + const [slotCommitment, setSlotCommitment] = useState(null); + const [error, setError] = useState(); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + setIsLoading(true); + setSlotCommitment(null); + if (!slotCommitment) { + // eslint-disable-next-line no-void + void (async () => { + apiClient + .getSlotCommitment({ + network, + slotIndex, + }) + .then((response) => { + if (isMounted) { + const slot = plainToInstance(SlotCommitment, response.slot) as unknown as SlotCommitment; + setSlotCommitment(slot); + setError(response.error); + } + }) + .finally(() => { + setIsLoading(false); + }); + })(); + } else { + setIsLoading(false); + } + }, [network, slotIndex]); + + return { + slotCommitment, + error, + isLoading, + }; +} diff --git a/client/src/models/api/nova/ISlotRequest.ts b/client/src/models/api/nova/ISlotRequest.ts new file mode 100644 index 000000000..b00482593 --- /dev/null +++ b/client/src/models/api/nova/ISlotRequest.ts @@ -0,0 +1,11 @@ +export interface ISlotRequest { + /** + * The network to search on. + */ + network: string; + + /** + * The slot index to get the commitment for. + */ + slotIndex: string; +} diff --git a/client/src/models/api/nova/ISlotResponse.ts b/client/src/models/api/nova/ISlotResponse.ts new file mode 100644 index 000000000..dc7e83cb4 --- /dev/null +++ b/client/src/models/api/nova/ISlotResponse.ts @@ -0,0 +1,6 @@ +import { SlotCommitment } from "@iota/sdk-wasm-nova/web"; +import { IResponse } from "../IResponse"; + +export interface ISlotResponse extends IResponse { + slot: SlotCommitment; +} diff --git a/client/src/services/nova/novaApiClient.ts b/client/src/services/nova/novaApiClient.ts index 5c03dbf0b..bff44cf77 100644 --- a/client/src/services/nova/novaApiClient.ts +++ b/client/src/services/nova/novaApiClient.ts @@ -29,6 +29,8 @@ import { IAddressDetailsRequest } from "~/models/api/nova/address/IAddressDetail import { IAddressDetailsResponse } from "~/models/api/nova/address/IAddressDetailsResponse"; import { IFoundriesResponse } from "~/models/api/nova/foundry/IFoundriesResponse"; import { IFoundriesRequest } from "~/models/api/nova/foundry/IFoundriesRequest"; +import { ISlotRequest } from "~/models/api/nova/ISlotRequest"; +import { ISlotResponse } from "~/models/api/nova/ISlotResponse"; import { ITransactionDetailsRequest } from "~/models/api/nova/ITransactionDetailsRequest"; import { ITransactionDetailsResponse } from "~/models/api/nova/ITransactionDetailsResponse"; import { ICongestionRequest } from "~/models/api/nova/ICongestionRequest"; @@ -177,6 +179,15 @@ export class NovaApiClient extends ApiClient { return this.callApi(`nova/output/rewards/${request.network}/${request.outputId}`, "get"); } + /** + * Get the slot commitment. + * @param request The request to send. + * @returns The response from the request. + */ + public async getSlotCommitment(request: ISlotRequest): Promise { + return this.callApi(`nova/slot/${request.network}/${request.slotIndex}`, "get"); + } + /** * Get the stats. * @param request The request to send.