Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions prisma/sql/getFilecoinPaymentsForDealsHistory.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- @param {Boolean} $1:testnet?

WITH daily_payments AS (
SELECT
timezone(
'UTC',
to_timestamp(
p."createdAtBlock" * 30 +
CASE
WHEN $1 IS TRUE THEN 1667326380
ELSE 1598306400
END
)
)::date AS day,
r.token,
SUM(p."netPayeeAmount") AS daily_amount
FROM filecoin_pay_payment p
INNER JOIN filecoin_pay_rail r
ON r."railId" = p."railId"
WHERE EXISTS (
SELECT 1
FROM po_rep_deal d
WHERE d."railId" = p."railId"
)
GROUP BY 1, 2
)

SELECT
day,
token,
daily_amount,
SUM(daily_amount) OVER (
PARTITION BY token
ORDER BY day
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_amount
FROM daily_payments
ORDER BY day;
4 changes: 4 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ import { PoRepIndexerModule } from './po-rep-indexer';
import { PoRepController } from './controller/po-rep/po-rep.controller';
import { FilecoinPayController } from './controller/filecoin-pay/filecoin-pay.controller';
import { ERC20TokenInfoService } from './service/erc20-token-info/erc20-token-info.service';
import { PoRepPriceOracleService } from './service/po-rep-price-oracle/po-rep-price-oracle.service';
import { PoRepService } from './service/po-rep/po-rep.service';

const AGGREGATION_RUNNERS = [
ClientDatacapAllocationRunner,
Expand Down Expand Up @@ -179,6 +181,8 @@ const AGGREGATION_RUNNERS_RUN_ONLY = [];
OldDatacapService,
StorageProviderUrlFinderSnapshotMetricService,
ERC20TokenInfoService,
PoRepPriceOracleService,
PoRepService,
{ provide: APP_FILTER, useClass: ErrorHandlerMiddleware },
{ provide: APP_INTERCEPTOR, useClass: CacheInterceptor },
{
Expand Down
160 changes: 157 additions & 3 deletions src/controller/po-rep/po-rep.controller.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { Cache, CACHE_MANAGER, CacheTTL } from '@nestjs/cache-manager';
import { Controller, Get, Inject, Query } from '@nestjs/common';
import { ApiOkResponse, ApiOperation } from '@nestjs/swagger';
import { DateTime } from 'luxon';
import {
PoRepDealState,
Prisma,
StorageProviderUrlFinderMetricType,
} from 'prisma/generated/client';
import { PrismaService } from 'src/db/prisma.service';
import { PoRepService } from 'src/service/po-rep/po-rep.service';
import { bigIntDiv } from 'src/utils/utils';
import { ControllerBase } from '../base/controller-base';
import {
DashboardStatistic,
DashboardStatisticValue,
PaginationInfoRequest,
} from '../base/types.controller-base';
import {
GetPoRepProvidersResponse,
GetPoRepStatisticsRequest,
PoRepDashboardStatistic,
PoRepDashboardStatisticType,
PoRepDealsPaymentsHistoryEntry,
PoRepProviderSLIInfo,
PoRepSLIMeasurment,
PoRepSLIType,
poRepSLITypes,
} from './types.po-rep';
import { PaginationInfoRequest } from '../base/types.controller-base';
import { ControllerBase } from '../base/controller-base';

const sliTypesMap: Record<
PoRepSLIType,
Expand All @@ -27,15 +38,91 @@ const sliTypesMap: Record<
indexingPct: null,
};

const dashboardStatisticsTitleDict: Record<
PoRepDashboardStatisticType,
PoRepDashboardStatistic['title']
> = {
TOTAL_DEALS_DONE: 'Total Deals Done',
TOTAL_USD_PAID: 'Total USD Paid',
};

const dashboardStatisticsDescriptionDict: Record<
PoRepDashboardStatisticType,
PoRepDashboardStatistic['description']
> = {
TOTAL_DEALS_DONE: 'Total count of deals done up to date',
TOTAL_USD_PAID:
'Total amount in USD of funds transferred to providers for fulfilling deals',
};

@Controller('po-rep')
export class PoRepController extends ControllerBase {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache,
@Inject(CACHE_MANAGER) private _cacheManager: Cache,
private readonly prismaService: PrismaService,
private readonly poRepService: PoRepService,
) {
super();
}

@Get('/statistics')
@ApiOperation({
summary: 'Get list of statistics regarding PoRep market',
})
@ApiOkResponse({
description: 'List of statistics regarding PoRep market',
type: [PoRepDashboardStatistic],
})
@CacheTTL(1000 * 60 * 5) // 5 minutes
public async getStatistics(
@Query() query: GetPoRepStatisticsRequest,
): Promise<PoRepDashboardStatistic[]> {
Comment thread
tmaciej-neti marked this conversation as resolved.
const { interval = 'day' } = query;
const cutoffDate = DateTime.now()
.toUTC()
.minus({ [interval]: 1 });

const [currentDealsCount, previousDealsCount, paymentsHistory] =
await Promise.all([
this.poRepService.getDealsDoneCountUpToDate(),
this.poRepService.getDealsDoneCountUpToDate(cutoffDate.toJSDate()),
this.poRepService.getDealsPaymentsSummaryHistory(),
]);

const currentPaymentsEntry = paymentsHistory.at(-1);
const cuttofDateISODateString = cutoffDate.toISODate();
const comparedPaymentsEntry = paymentsHistory.find((entry) => {
return entry.day.toISODate() === cuttofDateISODateString;
});

return [
this.calculateDashboardStatistic({
type: 'TOTAL_DEALS_DONE',
interval: interval,
currentValue: {
value: currentDealsCount,
type: 'numeric',
},
previousValue: {
value: previousDealsCount,
type: 'numeric',
},
}),
this.calculateDashboardStatistic({
type: 'TOTAL_USD_PAID',
interval: interval,
currentValue: {
value: currentPaymentsEntry?.cumulativeAmountUSD ?? 0,
type: 'numeric',
},
previousValue: {
value: comparedPaymentsEntry?.cumulativeAmountUSD ?? 0,
type: 'numeric',
},
}),
];
}

@Get('/providers')
@ApiOperation({
summary: 'Get list of storage providers participating in PoRep market',
Expand Down Expand Up @@ -151,4 +238,71 @@ export class PoRepController extends ControllerBase {
totalCount,
);
}

@Get('/payments-history')
@ApiOperation({
summary:
'Get the history of USD amounts paid to providers for deal settlements',
})
@ApiOkResponse({
type: [PoRepDealsPaymentsHistoryEntry],
})
@CacheTTL(1000 * 60 * 30) // 30 minutes
public async getPaymentsHistory(): Promise<PoRepDealsPaymentsHistoryEntry[]> {
const results = await this.poRepService.getDealsPaymentsSummaryHistory();
return results.map((result) => {
return {
day: result.day.toFormat('yyyy-MM-dd'),
dailyAmountUSD: result.dailyAmountUSD,
cumulativeAmountUSD: result.cumulativeAmountUSD,
};
});
}

private calculateDashboardStatistic(options: {
type: PoRepDashboardStatistic['type'];
currentValue: DashboardStatisticValue;
previousValue: DashboardStatisticValue;
interval: DashboardStatistic['percentageChange']['interval'];
}): PoRepDashboardStatistic {
const { type, currentValue, previousValue, interval } = options;

if (currentValue.type !== previousValue.type) {
throw new TypeError(
'Cannot compare different dashboard statistics types',
);
}

const percentageChange: PoRepDashboardStatistic['percentageChange'] =
(() => {
const dividerIsZero =
previousValue.type === 'bigint'
? BigInt(previousValue.value) === 0n
: previousValue.value === 0;
if (dividerIsZero) return null;

const ratio =
currentValue.type === 'bigint' || previousValue.type === 'bigint'
? bigIntDiv(
BigInt(currentValue.value),
BigInt(previousValue.value),
2,
)
: currentValue.value / previousValue.value;

return {
value: ratio - 1,
interval: interval,
increaseNegative: false,
};
})();

return {
type: type,
title: dashboardStatisticsTitleDict[type],
description: dashboardStatisticsDescriptionDict[type],
value: currentValue,
percentageChange: percentageChange,
};
}
}
46 changes: 44 additions & 2 deletions src/controller/po-rep/types.po-rep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
import { PaginationInfoResponse } from '../base/types.controller-base';
import { ApiProperty, PartialType, PickType } from '@nestjs/swagger';
import {
DashboardStatistic,
DashboardStatisticChange,
PaginationInfoResponse,
} from '../base/types.controller-base';

export type PoRepSLIType = (typeof poRepSLITypes)[number];

Expand All @@ -10,6 +14,11 @@ export const poRepSLITypes = [
'indexingPct',
] as const;

export const poRepDashboardStatisticTypes = [
'TOTAL_DEALS_DONE',
'TOTAL_USD_PAID',
] as const;

export class PoRepSLIMeasurment {
@ApiProperty({
description: 'Date of measurement',
Expand Down Expand Up @@ -104,6 +113,39 @@ export class PoRepProviderInfo {
registeredAtBlock: string;
}

export class PoRepDealsPaymentsHistoryEntry {
@ApiProperty({
description: 'Entry date',
})
day: string;

@ApiProperty({
description: 'Daily volume of payments in USD',
})
dailyAmountUSD: number;

@ApiProperty({
description: 'Cumulative amount of payments in USD up to the entry date',
})
cumulativeAmountUSD: number;
}

export type PoRepDashboardStatisticType =
(typeof poRepDashboardStatisticTypes)[number];

export class PoRepDashboardStatistic extends DashboardStatistic {
@ApiProperty({
description: 'Type of PoRep dashboard statistic',
enumName: 'PoRepDashboardStatisticType',
enum: poRepDashboardStatisticTypes,
})
type: PoRepDashboardStatisticType;
}

export class GetPoRepStatisticsRequest extends PartialType(
PickType(DashboardStatisticChange, ['interval'] as const),
) {}

export class GetPoRepProvidersResponse extends PaginationInfoResponse {
@ApiProperty({
description: 'List of Providers participating in Po-Rep market',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Injectable } from '@nestjs/common';

@Injectable()
export class PoRepPriceOracleService {
public async getTokenExchangeRateUSD(_tokenAddress: string): Promise<number> {
// Currently we assume only stable coins will be used for PoRep deals
return 1;
}
}
Loading
Loading