From dde10a3aa5505e4821587c62368de01db7405394 Mon Sep 17 00:00:00 2001 From: Jonathan <59397732+jonathanchw@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:15:40 -0300 Subject: [PATCH] GET /positions/best-cloneable, server-side best parent selection (#25) * feat(positions): add GET /positions/reference endpoint for adjustPriceWithReference * add GET /positions/best-cloneable endpoint --- positions/positions.controller.ts | 12 +++++++++++- positions/positions.service.ts | 25 +++++++++++++++++++++++++ positions/positions.types.ts | 4 ++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/positions/positions.controller.ts b/positions/positions.controller.ts index 10b2126..f92ddc0 100644 --- a/positions/positions.controller.ts +++ b/positions/positions.controller.ts @@ -1,6 +1,7 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, Query } from '@nestjs/common'; import { PositionsService } from './positions.service'; import { + ApiBestCloneable, ApiMintingUpdateListing, ApiMintingUpdateMapping, ApiPositionDefault, @@ -9,6 +10,7 @@ import { ApiPositionsOwners, ApiReferencePositions, } from './positions.types'; +import { Address } from 'viem'; import { ApiResponse, ApiTags } from '@nestjs/swagger'; @ApiTags('Positions Controller') @@ -87,4 +89,12 @@ export class PositionsController { getReferencePositions(): ApiReferencePositions { return this.positionsService.getReferencePositions(); } + + @Get('best-cloneable') + @ApiResponse({ + description: 'Returns the best cloneable parent position for the given collateral (highest price, active, not challenged)', + }) + getBestCloneable(@Query('collateral') collateral: string): ApiBestCloneable { + return this.positionsService.getBestCloneableParent((collateral ?? '').toLowerCase() as Address); + } } diff --git a/positions/positions.service.ts b/positions/positions.service.ts index a88543c..bf3ed83 100644 --- a/positions/positions.service.ts +++ b/positions/positions.service.ts @@ -6,6 +6,7 @@ import { Address, erc20Abi, getAddress } from 'viem'; import { PONDER_CLIENT } from '../api.apollo.config'; import { CONFIG, VIEM_CONFIG } from '../api.config'; import { + ApiBestCloneable, ApiMintingUpdateListing, ApiMintingUpdateMapping, ApiPositionDefault, @@ -306,6 +307,30 @@ export class PositionsService { return { num: collaterals.length, collaterals, map }; } + getBestCloneableParent(collateral: Address): ApiBestCloneable { + const now = Math.floor(Date.now() / 1000); + const collateralLower = collateral.toLowerCase() as Address; + const candidates = Object.values(this.fetchedPositions) as PositionQuery[]; + + const cloneable = candidates + .filter( + (p) => + !p.closed && + !p.denied && + p.expiration > now && + p.cooldown < now && + BigInt(p.collateralBalance) >= BigInt(p.minimumCollateral) && + p.collateral.toLowerCase() === collateralLower && + !p.isChallenged, + ) + .sort((a, b) => { + const diff = BigInt(b.price) - BigInt(a.price); + return diff > 0n ? 1 : diff < 0n ? -1 : 0; + }); + + return { position: cloneable[0] ?? null }; + } + 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 fe51b2d..8560b29 100644 --- a/positions/positions.types.ts +++ b/positions/positions.types.ts @@ -129,6 +129,10 @@ export type ApiReferencePositions = { map: ReferencePositionsMapping; }; +export type ApiBestCloneable = { + position: PositionQuery | null; +}; + export type ApiPositionDefault = { position: Address; collateral: Address;