Skip to content

Commit

Permalink
Merge branch 'dev' into fix/alias-ref-nova
Browse files Browse the repository at this point in the history
  • Loading branch information
begonaalvarezd authored Apr 23, 2024
2 parents 3037502 + cc410f1 commit ce551e2
Show file tree
Hide file tree
Showing 19 changed files with 537 additions and 218 deletions.
6 changes: 3 additions & 3 deletions api/src/initServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,6 @@ function initNovaServices(networkConfig: INetwork): void {
void NovaClient.create(novaClientParams).then((novaClient) => {
ServiceFactory.register(`client-${networkConfig.network}`, () => novaClient);

const novaApiService = new NovaApiService(networkConfig);
ServiceFactory.register(`api-service-${networkConfig.network}`, () => novaApiService);

// eslint-disable-next-line no-void
void NodeInfoServiceNova.build(networkConfig).then((nodeInfoService) => {
ServiceFactory.register(`node-info-${networkConfig.network}`, () => nodeInfoService);
Expand All @@ -250,6 +247,9 @@ function initNovaServices(networkConfig: INetwork): void {
.then((novaTimeService) => {
ServiceFactory.register(`nova-time-${networkConfig.network}`, () => novaTimeService);

const novaApiService = new NovaApiService(networkConfig);
ServiceFactory.register(`api-service-${networkConfig.network}`, () => novaApiService);

const influxDBService = new InfluxServiceNova(networkConfig);
influxDBService
.buildClient()
Expand Down
2 changes: 2 additions & 0 deletions api/src/models/api/nova/IAssociationsResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IResponse } from "../IResponse";

export enum AssociationType {
BASIC_ADDRESS,
BASIC_ADDRESS_EXPIRED,
BASIC_STORAGE_RETURN,
BASIC_EXPIRATION_RETURN,
BASIC_SENDER,
Expand All @@ -18,6 +19,7 @@ export enum AssociationType {
DELEGATION_VALIDATOR,
FOUNDRY_ACCOUNT,
NFT_ADDRESS,
NFT_ADDRESS_EXPIRED,
NFT_STORAGE_RETURN,
NFT_EXPIRATION_RETURN,
NFT_ISSUER,
Expand Down
10 changes: 2 additions & 8 deletions api/src/routes/nova/output/associated/post.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ServiceFactory } from "../../../../factories/serviceFactory";
import { IAssociationsRequest } from "../../../../models/api/nova/IAssociationsRequest";
import { IAssociationsRequestBody } from "../../../../models/api/nova/IAssociationsRequestBody";
import { IAssociation, IAssociationsResponse } from "../../../../models/api/nova/IAssociationsResponse";
import { IAssociationsResponse } from "../../../../models/api/nova/IAssociationsResponse";
import { IConfiguration } from "../../../../models/configuration/IConfiguration";
import { NOVA } from "../../../../models/db/protocolVersion";
import { NetworkService } from "../../../../services/networkService";
Expand Down Expand Up @@ -33,13 +33,7 @@ export async function post(

const helper = new AssociatedOutputsHelper(networkConfig, body.addressDetails);
await helper.fetch();
const result = helper.associationToOutputIds;

const associations: IAssociation[] = [];
for (const [type, outputIds] of result.entries()) {
associations.push({ type, outputIds: outputIds.reverse() });
}

const associations = helper.getAssociations();
return {
associations,
};
Expand Down
130 changes: 128 additions & 2 deletions api/src/services/nova/novaApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
BasicOutputQueryParameters,
NftOutputQueryParameters,
} from "@iota/sdk-nova";
import moment from "moment";
import { NovaTimeService } from "./novaTimeService";
import { ServiceFactory } from "../../factories/serviceFactory";
import logger from "../../logger";
import { IFoundriesResponse } from "../../models/api/nova/foundry/IFoundriesResponse";
Expand Down Expand Up @@ -49,9 +51,15 @@ export class NovaApiService {
*/
private readonly client: Client;

/**
* Nova time service for conversions.
*/
private readonly _novatimeService: NovaTimeService;

constructor(network: INetwork) {
this.network = network;
this.client = ServiceFactory.get<Client>(`client-${network.network}`);
this._novatimeService = ServiceFactory.get<NovaTimeService>(`nova-time-${network.network}`);
}

/**
Expand Down Expand Up @@ -356,18 +364,21 @@ export class NovaApiService {
public async basicOutputDetailsByAddress(addressBech32: string): Promise<IAddressDetailsResponse> {
let cursor: string | undefined;
let outputIds: string[] = [];
const expiredIds = await this.basicExpiredOutputIdsByAddress(addressBech32);
const notClaimedIds = await this.basicNotClaimedOutputIdsByAddress(addressBech32);

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

outputIds = outputIds.concat(outputIdsResponse.items);
outputIds = outputIds.concat(outputIdsResponse.items.filter((id) => !expiredIds.includes(id)));
cursor = outputIdsResponse.cursor;
} catch (e) {
logger.error(`Fetching basic output ids failed. Cause: ${e}`);
}
} while (cursor);

outputIds = outputIds.concat(notClaimedIds);
const outputResponses = await this.outputsDetails(outputIds);

return {
Expand All @@ -383,18 +394,21 @@ export class NovaApiService {
public async nftOutputDetailsByAddress(addressBech32: string): Promise<IAddressDetailsResponse> {
let cursor: string | undefined;
let outputIds: string[] = [];
const expiredIds = await this.nftExpiredOutputIdsByAddress(addressBech32);
const notClaimedIds = await this.nftNotClaimedOutputIdsByAddress(addressBech32);

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

outputIds = outputIds.concat(outputIdsResponse.items);
outputIds = outputIds.concat(outputIdsResponse.items.filter((id) => !expiredIds.includes(id)));
cursor = outputIdsResponse.cursor;
} catch (e) {
logger.error(`Fetching nft output ids failed. Cause: ${e}`);
}
} while (cursor);

outputIds = outputIds.concat(notClaimedIds);
const outputResponses = await this.outputsDetails(outputIds);
return {
outputs: outputResponses,
Expand Down Expand Up @@ -630,4 +644,116 @@ export class NovaApiService {
const searchQuery = new SearchQueryBuilder(query, this.network.bechHrp).build();
return new SearchExecutor(this.network, searchQuery).run();
}

/**
* Get the expired basic output ids for an address (outputs no longer owned by the address but by the expirationReturnAddress).
* @param addressBech32 The address in bech32 format.
* @returns The basic output ids.
*/
private async basicExpiredOutputIdsByAddress(addressBech32: string): Promise<string[]> {
let cursor: string | undefined;
let outputIds: string[] = [];
const currentSlotIndex = this._novatimeService.getUnixTimestampToSlotIndex(moment().unix());

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

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

return outputIds;
}

/**
* Get the expired ntf output ids for an address (outputs no longer owned by the address but by the expirationReturnAddress).
* @param addressBech32 The address in bech32 format.
* @returns The nft output ids.
*/
private async nftExpiredOutputIdsByAddress(addressBech32: string): Promise<string[]> {
let cursor: string | undefined;
let outputIds: string[] = [];
const currentSlotIndex = this._novatimeService.getUnixTimestampToSlotIndex(moment().unix());

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

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

return outputIds;
}

/**
* Get the not claimed basic output ids for an address (outputs owned by the expirationReturnAddress).
* @param expirationReturnAddress The address in bech32 format.
* @returns The nft output ids.
*/
private async basicNotClaimedOutputIdsByAddress(expirationReturnAddress: string): Promise<string[]> {
let cursor: string | undefined;
let outputIds: string[] = [];
const currentSlotIndex = this._novatimeService.getUnixTimestampToSlotIndex(moment().unix());

do {
try {
const outputIdsResponse = await this.client.basicOutputIds({
expirationReturnAddress,
expiresBefore: currentSlotIndex,
cursor: cursor ?? "",
});

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

return outputIds;
}

/**
* Get the not claimed ntf output ids for an address (outputs owned by the expirationReturnAddress).
* @param expirationReturnAddress The address in bech32 format.
* @returns The nft output ids.
*/
private async nftNotClaimedOutputIdsByAddress(expirationReturnAddress: string): Promise<string[]> {
let cursor: string | undefined;
let outputIds: string[] = [];
const currentSlotIndex = this._novatimeService.getUnixTimestampToSlotIndex(moment().unix());

do {
try {
const outputIdsResponse = await this.client.nftOutputIds({
expirationReturnAddress,
expiresBefore: currentSlotIndex,
cursor: cursor ?? "",
});

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

return outputIds;
}
}
60 changes: 59 additions & 1 deletion api/src/utils/nova/associatedOutputsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
FoundryOutputQueryParameters,
NftOutputQueryParameters,
} from "@iota/sdk-nova";
import moment from "moment";
import { ServiceFactory } from "../../factories/serviceFactory";
import { IAddressDetails } from "../../models/api/nova/IAddressDetails";
import { AssociationType } from "../../models/api/nova/IAssociationsResponse";
import { AssociationType, IAssociation } from "../../models/api/nova/IAssociationsResponse";
import { INetwork } from "../../models/db/INetwork";
import { NovaTimeService } from "../../services/nova/novaTimeService";

/**
* Helper class to fetch associated outputs of an address on stardust.
Expand All @@ -34,6 +36,8 @@ export class AssociatedOutputsHelper {
const address = this.addressDetails.bech32;

const client = ServiceFactory.get<Client>(`client-${network}`);
const novatimeService = ServiceFactory.get<NovaTimeService>(`nova-time-${network}`);
const currentSlotIndex = novatimeService.getUnixTimestampToSlotIndex(moment().unix());
const promises: Promise<void>[] = [];

// BASIC OUTPUTS
Expand All @@ -47,6 +51,15 @@ export class AssociatedOutputsHelper {
),
);

promises.push(
// Basic output -> owner address expired outputs
this.fetchAssociatedOutputIds<BasicOutputQueryParameters>(
async (query) => client.basicOutputIds(query),
{ address, expiresBefore: currentSlotIndex },
AssociationType.BASIC_ADDRESS_EXPIRED,
),
);

promises.push(
// Basic output -> storage return address
this.fetchAssociatedOutputIds<BasicOutputQueryParameters>(
Expand Down Expand Up @@ -210,6 +223,15 @@ export class AssociatedOutputsHelper {
),
);

promises.push(
// Nft output -> owner address expired outputs
this.fetchAssociatedOutputIds<NftOutputQueryParameters>(
async (query) => client.nftOutputIds(query),
{ address, expiresBefore: currentSlotIndex },
AssociationType.NFT_ADDRESS_EXPIRED,
),
);

promises.push(
// Nft output -> storage return address
this.fetchAssociatedOutputIds<NftOutputQueryParameters>(
Expand Down Expand Up @@ -249,6 +271,42 @@ export class AssociatedOutputsHelper {
await Promise.all(promises);
}

/**
* Retrieves the associations between output types and output IDs.
* @returns An array of associations.
*/
public getAssociations(): IAssociation[] {
const associations: IAssociation[] = [];
for (const [type, outputIds] of this.associationToOutputIds.entries()) {
if (type !== AssociationType.BASIC_ADDRESS_EXPIRED && type !== AssociationType.NFT_ADDRESS_EXPIRED) {
if (
type === AssociationType.BASIC_ADDRESS &&
this.associationToOutputIds.get(AssociationType.BASIC_ADDRESS_EXPIRED)?.length > 0
) {
// remove expired basic outputs from basic address associations if they exist
const expiredIds = this.associationToOutputIds.get(AssociationType.BASIC_ADDRESS_EXPIRED);
const filteredOutputIds = outputIds.filter((id) => !expiredIds?.includes(id));
if (filteredOutputIds.length > 0) {
associations.push({ type, outputIds: filteredOutputIds.reverse() });
}
} else if (
type === AssociationType.NFT_ADDRESS &&
this.associationToOutputIds.get(AssociationType.NFT_ADDRESS_EXPIRED)?.length > 0
) {
// remove expired nft outputs from nft address associations if they exist
const expiredIds = this.associationToOutputIds.get(AssociationType.NFT_ADDRESS_EXPIRED);
const filteredOutputIds = outputIds.filter((id) => !expiredIds?.includes(id));
if (filteredOutputIds.length > 0) {
associations.push({ type, outputIds: filteredOutputIds.reverse() });
}
} else {
associations.push({ type, outputIds: outputIds.reverse() });
}
}
}
return associations;
}

/**
* Generic helper function for fetching associated outputs.
* @param fetch The function for the API call
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/components/CardInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface CardInfoDetail {
copyValue?: string;
}

interface CardInfoProps {
export interface CardInfoProps {
/**
* The title of the card.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
@import "../../../../../../scss/mixins.scss";
@import "../../../../../../scss/mixins";
@import "../../../../../../scss/media-queries";

.block-issuance--card {
border: none !important;
margin-bottom: 48px;
.section--block-issuance {
.card-info-wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;

.field {
margin-bottom: 8px;
@include tablet-down {
grid-template-columns: repeat(2, 1fr);
}

.card--label {
@include font-size(14px, 21px);
@include phone-down {
grid-template-columns: repeat(1, 1fr);
}
}
}
Loading

0 comments on commit ce551e2

Please sign in to comment.