11import { getChainNameFromChainId } from './chains' ;
2- import { MarketCompactData , ChainPositions , PendleClient , MarketPosition } from './client' ;
2+ import { MarketCompactData , ChainPositions , PendleClient , MarketPosition , PendleAsset } from './client' ;
3+ import { to$$$ } from './format' ;
4+ import { formatUnits } from 'viem' ;
35
46/**
57 * Represents a single token position (PT, YT, or LP) with its context
@@ -11,6 +13,8 @@ export type FlattenedTokenPosition = {
1113 marketId : string ;
1214 /** Token type: PT, YT, or LP */
1315 tokenType : 'PT' | 'YT' | 'LP' ;
16+ /** Token address */
17+ tokenAddress ?: `0x${string } `;
1418 /** Position type: open or closed */
1519 positionStatus : 'open' | 'closed' ;
1620 /** Valuation in USD */
@@ -31,8 +35,10 @@ export type FlattenedTokenPosition = {
3135 marketLpNonBoostedApy ?: number ;
3236 /** Market implied APY, that is, the APY at which the market trades (different from underlying APY) */
3337 marketImpliedApy ?: number ;
34- /** Full market details (optional) */
38+ /** Full market details */
3539 market ?: MarketCompactData ;
40+ /** Full asset details */
41+ asset ?: PendleAsset ;
3642} ;
3743
3844/**
@@ -67,7 +73,7 @@ export async function flattenAndSortPositions(
6773
6874 // Helper function to process a single PT, YT, or
6975 // LP market position
70- const processMarketPosition = (
76+ const processMarketPosition = async (
7177 market : MarketPosition ,
7278 chain : ChainPositions ,
7379 chainName : string ,
@@ -81,31 +87,46 @@ export async function flattenAndSortPositions(
8187 throw new Error ( `Market data not found for market ${ marketAddress } ` ) ;
8288 }
8389
90+ // Get all assets
91+ const assets = await pendleClient . getAllAssets ( chain . chainId ) ;
92+
8493 // Process each token type (PT, YT, LP)
8594 const tokenTypes : Array < 'pt' | 'yt' | 'lp' > = [ 'pt' , 'yt' , 'lp' ] ;
8695
8796 for ( const tokenKey of tokenTypes ) {
88- const token = market [ tokenKey ] ;
89- if ( token && ( includeZeroPositions || token . balance !== '0' ) ) {
97+ const pos = market [ tokenKey ] ;
98+ if ( pos && ( includeZeroPositions || pos . balance !== '0' ) ) {
99+ // Extract token address of position
100+ let tokenAddress : `0x${string } `;
101+ if ( tokenKey === 'lp' ) {
102+ tokenAddress = marketData . address ;
103+ } else {
104+ tokenAddress = marketData [ tokenKey ] . split ( '-' ) [ 1 ] as `0x${string } `;
105+ }
106+ // Get asset details
107+ const asset = assets . find ( ( a ) => a . address === tokenAddress ) ;
108+ // Build flattened position row
90109 flattenedPositions . push ( {
91110 chainId : chain . chainId ,
92111 chainName,
93112 marketId : market . marketId ,
94113 tokenType : tokenKey . toUpperCase ( ) as 'PT' | 'YT' | 'LP' ,
114+ tokenAddress,
95115 positionStatus,
96- valuation : token . valuation || 0 ,
97- balance : token . balance ,
98- activeBalance : token . activeBalance ,
99- claimTokenAmounts : token . claimTokenAmounts ,
116+ valuation : pos . valuation || 0 ,
117+ balance : pos . balance ,
118+ activeBalance : pos . activeBalance ,
119+ claimTokenAmounts : pos . claimTokenAmounts ,
100120 marketName : marketData . name ,
101121 marketExpiry : marketData . expiry ,
102122 marketLpNonBoostedApy : marketData . details . aggregatedApy ,
103123 marketImpliedApy : marketData . details . impliedApy ,
104124 market : marketData ,
125+ asset,
105126 } ) ;
106127
107- if ( token . valuation > 0 ) {
108- totalValuation += token . valuation ;
128+ if ( pos . valuation ) {
129+ totalValuation += pos . valuation ;
109130 }
110131 }
111132 }
@@ -123,14 +144,14 @@ export async function flattenAndSortPositions(
123144 // Process open positions
124145 if ( chain . openPositions ) {
125146 for ( const market of chain . openPositions ) {
126- processMarketPosition ( market , chain , chainName , 'open' , marketDetailsMap ) ;
147+ await processMarketPosition ( market , chain , chainName , 'open' , marketDetailsMap ) ;
127148 }
128149 }
129150
130151 // Process closed positions if requested
131152 if ( includeClosedPositions && chain . closedPositions ) {
132153 for ( const market of chain . closedPositions ) {
133- processMarketPosition ( market , chain , chainName , 'closed' , marketDetailsMap ) ;
154+ await processMarketPosition ( market , chain , chainName , 'closed' , marketDetailsMap ) ;
134155 }
135156 }
136157 }
@@ -169,39 +190,52 @@ export function formatFlattenedPositions(flattenedPositions: FlattenedTokenPosit
169190 switch ( position . tokenType ) {
170191 case 'LP' :
171192 if ( position . marketLpNonBoostedApy ) {
172- apyString = `( unboosted APY: ${ ( 100 * position . marketLpNonBoostedApy ) . toFixed ( 2 ) } %) ` ;
193+ apyString = `unboosted APY: ${ ( 100 * position . marketLpNonBoostedApy ) . toFixed ( 2 ) } %` ;
173194 }
174195 break ;
175196 case 'YT' :
176197 if ( position . marketImpliedApy ) {
177- apyString = `( implied APY: ${ ( 100 * position . marketImpliedApy ) . toFixed ( 2 ) } %) ` ;
198+ apyString = `implied APY: ${ ( 100 * position . marketImpliedApy ) . toFixed ( 2 ) } %` ;
178199 }
179200 break ;
180201 case 'PT' :
181202 break ;
182203 }
183204
184- let parts : string [ ] = [
185- `$${ position . valuation . toFixed ( 2 ) } ` ,
186- `${ position . tokenType } ` ,
187- `${ position . positionStatus === 'closed' ? '(closed)' : '' } ` ,
188- `position` ,
189- `on ${ position . marketName } market` ,
190- `on ${ position . chainName } chain` ,
191- `${ apyString } ` ,
192- ] . filter ( Boolean ) ; // Remove empty strings
193-
194- // Add expiry if available
205+ let expiryString = '' ;
195206 if ( position . marketExpiry ) {
196- parts . push ( `expires ${ position . marketExpiry } ` ) ;
207+ expiryString = `expires on ${ position . marketExpiry } ` ;
197208 }
198209
199- // Add claimable yield indicator
210+ let claimableYieldString = '' ;
200211 if ( position . claimTokenAmounts && position . claimTokenAmounts . length > 0 ) {
201- parts . push ( `(has claimable yield)` ) ;
212+ claimableYieldString = `has claimable yield` ;
213+ }
214+
215+ let balanceString = '' ;
216+ if ( position . asset ?. decimals ) {
217+ balanceString = `${ formatUnits ( BigInt ( position . balance ) , position . asset . decimals ) } ${ position . tokenType } tokens` ;
218+ }
219+
220+ let parts : string [ ] = [
221+ `${ to$$$ ( position . valuation ) } ` ,
222+ `${ position . tokenType } ` ,
223+ `${ position . positionStatus === 'closed' ? 'closed' : '' } ` ,
224+ `position` ,
225+ `on ${ position . marketName } market` ,
226+ `on ${ position . chainName } chain:` ,
227+ `${ balanceString } ,` ,
228+ `${ apyString ? `${ apyString } ,` : '' } ` ,
229+ `${ expiryString ? `${ expiryString } ,` : '' } ` ,
230+ `${ claimableYieldString ? `${ claimableYieldString } ,` : '' } ` ,
231+ ] ;
232+
233+ // Remove last comma if it exists
234+ if ( parts [ parts . length - 1 ] === ',' ) {
235+ parts . pop ( ) ;
202236 }
203237
204- formattedParts . push ( parts . join ( ' ' ) ) ;
238+ formattedParts . push ( parts . filter ( Boolean ) . join ( ' ' ) ) ;
205239 }
206240 return formattedParts . join ( '\n' ) . replace ( / ^ / gm, linePrefix ) ;
207241}
0 commit comments