Skip to content

Commit

Permalink
Merge branch 'dev' into feat/add-epoch-page
Browse files Browse the repository at this point in the history
  • Loading branch information
begonaalvarezd authored Mar 5, 2024
2 parents 46e0c51 + 9a62d97 commit ba93bca
Show file tree
Hide file tree
Showing 19 changed files with 548 additions and 14 deletions.
9 changes: 9 additions & 0 deletions api/src/models/api/nova/IDelegationDetailsResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IDelegationWithDetails } from "./IDelegationWithDetails";
import { IResponse } from "./IResponse";

export interface IDelegationDetailsResponse extends IResponse {
/**
* The outputs data.
*/
outputs?: IDelegationWithDetails[];
}
16 changes: 16 additions & 0 deletions api/src/models/api/nova/IDelegationWithDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable import/no-unresolved */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { OutputWithMetadataResponse } from "@iota/sdk-nova";
import { IRewardsResponse } from "./IRewardsResponse";

export interface IDelegationWithDetails {
/**
* The output.
*/
output: OutputWithMetadataResponse;

/**
* The rewards for the output.
*/
rewards: IRewardsResponse;
}
6 changes: 6 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ export const routes: IRoute[] = [
folder: "nova/address/outputs/nft",
func: "get",
},
{
path: "/nova/address/outputs/delegation/:network/:address",
method: "get",
folder: "nova/address/outputs/delegation",
func: "get",
},
{
path: "/nova/output/associated/:network/:address",
method: "post",
Expand Down
30 changes: 30 additions & 0 deletions api/src/routes/nova/address/outputs/delegation/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ServiceFactory } from "../../../../../factories/serviceFactory";
import { IAddressDetailsRequest } from "../../../../../models/api/nova/IAddressDetailsRequest";
import { IDelegationDetailsResponse } from "../../../../../models/api/nova/IDelegationDetailsResponse";
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 delegation output details by address.
* @param config The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(config: IConfiguration, request: IAddressDetailsRequest): Promise<IDelegationDetailsResponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
ValidationHelper.oneOf(request.network, networks, "network");
ValidationHelper.string(request.address, "address");

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

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

const novaApiService = ServiceFactory.get<NovaApiService>(`api-service-${networkConfig.network}`);
return novaApiService.delegationOutputDetailsByAddress(request.address);
}
55 changes: 55 additions & 0 deletions api/src/services/nova/novaApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { IAnchorDetailsResponse } from "../../models/api/nova/IAnchorDetailsResp
import { IBlockDetailsResponse } from "../../models/api/nova/IBlockDetailsResponse";
import { IBlockResponse } from "../../models/api/nova/IBlockResponse";
import { ICongestionResponse } from "../../models/api/nova/ICongestionResponse";
import { IDelegationDetailsResponse } from "../../models/api/nova/IDelegationDetailsResponse";
import { IDelegationWithDetails } from "../../models/api/nova/IDelegationWithDetails";
import { INftDetailsResponse } from "../../models/api/nova/INftDetailsResponse";
import { IOutputDetailsResponse } from "../../models/api/nova/IOutputDetailsResponse";
import { IRewardsResponse } from "../../models/api/nova/IRewardsResponse";
Expand Down Expand Up @@ -270,6 +272,25 @@ export class NovaApiService {
}
}

/**
* Get the outputs mana rewards.
* @param outputIds The output ids to get the mana rewards for.
* @returns The rewards details.
*/
public async outputsRewardsDetails(outputIds: string[]): Promise<IRewardsResponse[]> {
const promises: Promise<IRewardsResponse>[] = [];

for (const outputId of outputIds) {
const promise = this.getRewards(outputId);
promises.push(promise);
}
try {
return await Promise.all(promises);
} catch (e) {
logger.error(`Fetching outputs rewards failed. Cause: ${e}`);
}
}

/**
* Get the relevant basic output details for an address.
* @param addressBech32 The address in bech32 format.
Expand Down Expand Up @@ -323,6 +344,40 @@ export class NovaApiService {
};
}

/**
* Get the relevant basic output details for an address.
* @param addressBech32 The address in bech32 format.
* @returns The basic output details.
*/
public async delegationOutputDetailsByAddress(addressBech32: string): Promise<IDelegationDetailsResponse> {
let cursor: string | undefined;
let outputIds: string[] = [];
const delegationResponse: IDelegationWithDetails[] = [];

do {
try {
const outputIdsResponse = await this.client.delegationOutputIds({ address: addressBech32, cursor: cursor ?? "" });

outputIds = outputIds.concat(outputIdsResponse.items);
cursor = outputIdsResponse.cursor;
} catch (e) {
logger.error(`Fetching delegation output ids failed. Cause: ${e}`);
}
} while (cursor);

const outputRewards = await this.outputsRewardsDetails(outputIds);
const outputResponses = await this.outputsDetails(outputIds);

for (const outputResponse of outputResponses) {
const matchingReward = outputRewards?.find((outputReward) => outputReward.outputId === outputResponse.metadata.outputId);
delegationResponse.push({ rewards: matchingReward, output: outputResponse });
}

return {
outputs: delegationResponse,
};
}

/**
* Get Congestion for Account
* @param accountId The account address to get the congestion for.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import foundriesMessage from "~assets/modals/stardust/alias/foundries.json";
import stateMessage from "~assets/modals/stardust/alias/state.json";
import bicMessage from "~assets/modals/nova/account/bic.json";
import validatorMessage from "~assets/modals/nova/account/validator.json";
import delegationMessage from "~assets/modals/nova/delegation.json";
import nftMetadataMessage from "~assets/modals/stardust/nft/metadata.json";
import addressNftsMessage from "~assets/modals/stardust/address/nfts-in-wallet.json";
import TabbedSection from "../../../hoc/TabbedSection";
Expand All @@ -26,12 +27,14 @@ import NftSection from "~/app/components/nova/address/section/nft/NftSection";
import NftMetadataSection from "~/app/components/nova/address/section/nft/NftMetadataSection";
import { TransactionsHelper } from "~/helpers/nova/transactionsHelper";
import AccountValidatorSection from "./account/AccountValidatorSection";
import DelegationSection from "./delegation/DelegationSection";

enum DEFAULT_TABS {
Transactions = "Transactions",
AssocOutputs = "Outputs",
NativeTokens = "Native Tokens",
Nfts = "NFTs",
Delegation = "Delegation",
}

enum ACCOUNT_TABS {
Expand All @@ -52,8 +55,10 @@ const buildDefaultTabsOptions = (
tokensCount: number,
nftsCount: number,
associatedOutputCount: number,
delegationCount: number,
isNativeTokensLoading: boolean,
isNftOutputsLoading: boolean,
isDelegationOutputsLoading: boolean,
isAddressHistoryLoading: boolean,
isAddressHistoryDisabled: boolean,
) => ({
Expand Down Expand Up @@ -83,6 +88,13 @@ const buildDefaultTabsOptions = (
isLoading: isNftOutputsLoading,
infoContent: addressNftsMessage,
},
[DEFAULT_TABS.Delegation]: {
disabled: delegationCount === 0,
hidden: delegationCount === 0,
counter: delegationCount,
isLoading: isDelegationOutputsLoading,
infoContent: delegationMessage,
},
});

const buildAccountAddressTabsOptions = (
Expand Down Expand Up @@ -156,7 +168,15 @@ export const AddressPageTabbedSections: React.FC<IAddressPageTabbedSectionsProps
if (!addressState.addressDetails) {
return null;
}
const { addressDetails, addressBasicOutputs, isAddressHistoryLoading, isAddressHistoryDisabled } = addressState;
const {
addressDetails,
addressBasicOutputs,
isBasicOutputsLoading,
isNftOutputsLoading,
isDelegationOutputsLoading,
isAddressHistoryLoading,
isAddressHistoryDisabled,
} = addressState;
const { bech32: addressBech32 } = addressDetails;
const { name: network } = networkInfo;

Expand All @@ -176,6 +196,7 @@ export const AddressPageTabbedSections: React.FC<IAddressPageTabbedSectionsProps
/>,
<AssetsTable key={`assets-table-${addressBech32}`} outputs={addressBasicOutputs} setTokensCount={setTokensCount} />,
<NftSection key={`nft-section-${addressBech32}`} outputs={addressState.addressNftOutputs} />,
<DelegationSection key={`delegation-${addressBech32}`} delegationDetails={addressState.addressDelegationOutputs} />,
];

const accountAddressSections =
Expand Down Expand Up @@ -209,12 +230,15 @@ export const AddressPageTabbedSections: React.FC<IAddressPageTabbedSectionsProps

let tabEnums = DEFAULT_TABS;
const nftsCount = addressState.addressNftOutputs?.length ?? 0;
const delegationCount = addressState.addressDelegationOutputs?.length ?? 0;
const defaultTabsOptions = buildDefaultTabsOptions(
tokensCount,
nftsCount,
outputCount,
addressState.isBasicOutputsLoading,
addressState.isNftOutputsLoading,
delegationCount,
isBasicOutputsLoading,
isNftOutputsLoading,
isDelegationOutputsLoading,
isAddressHistoryLoading,
isAddressHistoryDisabled,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
@import "../../../../../../scss/fonts";
@import "../../../../../../scss/media-queries";
@import "../../../../../../scss/mixins";
@import "../../../../../../scss/variables";
@import "../../../../../../scss/themes";

.table--delegation {
width: 100%;
border-spacing: 12px 28px;
border-collapse: separate;

@include tablet-down {
display: none;
}

tr {
@include font-size(14px);

color: $gray-7;
font-family: $inter;
letter-spacing: 0.5px;

th {
@include font-size(12px);

color: $gray-6;
font-weight: 600;
text-align: left;
text-transform: uppercase;
}

td {
color: var(--body-color);

&.highlight {
color: var(--link-color);
@include font-size(14px);

font-family: $ibm-plex-mono;
font-weight: normal;
letter-spacing: 0.02em;
line-height: 20px;

a {
max-width: 200px;
}
}
&.truncate {
max-width: 150px;
}
}
}
}

.cards--delegation {
display: none;

@include tablet-down {
display: block;
}
.card--delegation {
margin-bottom: 16px;
padding: 8px;

.field {
margin-bottom: 8px;

.label {
color: $gray-6;
font-family: $inter;
letter-spacing: 0.5px;

@include font-size(14px, 21px);
}

.value {
@include font-size(14px, 21px);

color: var(--body-color);
font-family: $metropolis;
font-weight: 700;

.highlight {
color: var(--link-color);
@include font-size(14px);

font-family: $ibm-plex-mono;
font-weight: normal;
letter-spacing: 0.02em;
line-height: 20px;
max-width: 200px;
}
}
}

.field:last-child {
margin-bottom: 0;
}
}
}
Loading

0 comments on commit ba93bca

Please sign in to comment.