diff --git a/positions/positions.controller.ts b/positions/positions.controller.ts index fb7feac..10b2126 100644 --- a/positions/positions.controller.ts +++ b/positions/positions.controller.ts @@ -7,6 +7,7 @@ import { ApiPositionsListing, ApiPositionsMapping, ApiPositionsOwners, + ApiReferencePositions, } from './positions.types'; import { ApiResponse, ApiTags } from '@nestjs/swagger'; @@ -78,4 +79,12 @@ export class PositionsController { geMintingtMapping(): ApiMintingUpdateMapping { return this.positionsService.getMintingUpdatesMapping(); } + + @Get('reference') + @ApiResponse({ + description: 'Returns the active position with the highest price per collateral for cooldown-free price increases', + }) + getReferencePositions(): ApiReferencePositions { + return this.positionsService.getReferencePositions(); + } } diff --git a/positions/positions.service.ts b/positions/positions.service.ts index f07d230..a88543c 100644 --- a/positions/positions.service.ts +++ b/positions/positions.service.ts @@ -12,11 +12,13 @@ import { ApiPositionsListing, ApiPositionsMapping, ApiPositionsOwners, + ApiReferencePositions, MintingUpdateQuery, MintingUpdateQueryObjectArray, OwnersPositionsObjectArray, PositionQuery, PositionsQueryObjectArray, + ReferencePositionsMapping, } from './positions.types'; // Genesis position address from NPM package @@ -117,6 +119,7 @@ export class PositionsService { denied closed original + isChallenged minimumCollateral riskPremiumPPM @@ -237,6 +240,7 @@ export class PositionsService { denied: p.denied, closed: p.closed, original: getAddress(p.original), + isChallenged: p.isChallenged, minimumCollateral: p.minimumCollateral, annualInterestPPM: leadrate + p.riskPremiumPPM, @@ -279,6 +283,29 @@ export class PositionsService { return list; } + getReferencePositions(): ApiReferencePositions { + const now = Math.floor(Date.now() / 1000); + const candidates = Object.values(this.fetchedPositions) as PositionQuery[]; + const map: ReferencePositionsMapping = {}; + + for (const p of candidates) { + if (p.closed || p.denied) continue; + if (p.isChallenged) continue; + if (BigInt(p.principal) === 0n) continue; + if (Number(p.cooldown) >= now) continue; + if (Number(p.expiration) <= now) continue; + + const collateral = p.collateral.toLowerCase() as Address; + const current = map[collateral]; + if (!current || BigInt(p.price) > BigInt(current.price)) { + map[collateral] = p; + } + } + + const collaterals = Object.keys(map) as Address[]; + return { num: collaterals.length, collaterals, map }; + } + getMintingUpdatesList(): ApiMintingUpdateListing { const m = Object.values(this.fetchedMintingUpdates).flat(1) as MintingUpdateQuery[]; return { diff --git a/positions/positions.types.ts b/positions/positions.types.ts index 42cb7a1..fe51b2d 100644 --- a/positions/positions.types.ts +++ b/positions/positions.types.ts @@ -16,6 +16,7 @@ export type PositionQuery = { denied: boolean; closed: boolean; original: Address; + isChallenged: boolean; minimumCollateral: string; annualInterestPPM: number; // @dev: in V2, sum of leadrate and riskPremium @@ -118,6 +119,16 @@ export type ApiMintingUpdateMapping = { map: MintingUpdateQueryObjectArray; }; +export type ReferencePositionsMapping = { + [collateral: Address]: PositionQuery; +}; + +export type ApiReferencePositions = { + num: number; + collaterals: Address[]; + map: ReferencePositionsMapping; +}; + export type ApiPositionDefault = { position: Address; collateral: Address;