@@ -7,13 +7,7 @@ import { NativeTokenDetails } from './nativeTokenDetails'
77
88const Q192 = BigInt . fromI32 ( 2 ) . pow ( 192 as u8 )
99
10- /**
11- * Cap how many whitelist pools findNativePerToken will walk. Tokens with long
12- * whitelistPools lists otherwise cost O(N) store.gets (Pool + other Token) per
13- * call, twice per swap. Prefer the first K entries; pool init appends newest
14- * pools at the end so older/larger pools tend to sit earlier.
15- */
16- const MAX_WHITELIST_POOLS_TO_WALK = 8
10+ const PRICING_POOL_RESCAN_INTERVAL_SECONDS = BigInt . fromI32 ( 86400 )
1711
1812export function sqrtPriceX96ToTokenPrices (
1913 sqrtPriceX96 : BigInt ,
@@ -35,24 +29,65 @@ export function sqrtPriceX96ToTokenPrices(
3529 return [ price0 , price1 ]
3630}
3731
38- export function getNativePriceInUSD ( stablecoinWrappedNativePoolId : string , stablecoinIsToken0 : boolean ) : BigDecimal {
32+ export function getNativePriceInUSD (
33+ stablecoinWrappedNativePoolId : string ,
34+ stablecoinIsToken0 : boolean ,
35+ loadedPool : Pool | null = null ,
36+ ) : BigDecimal {
3937 // On chains where the native/reference token is itself a USD stablecoin (e.g. Arc, whose native
4038 // gas token is USDC and which has no wrapped-native / reference-stable pool), the native price is
4139 // 1 by definition. Such chains opt in by setting stablecoinWrappedNativePoolId to '' (empty).
4240 if ( stablecoinWrappedNativePoolId == '' ) {
4341 return ONE_BD
4442 }
45- const stablecoinWrappedNativePool = Pool . load ( stablecoinWrappedNativePoolId )
43+ const stablecoinWrappedNativePool =
44+ loadedPool !== null && loadedPool . id == stablecoinWrappedNativePoolId
45+ ? loadedPool
46+ : Pool . load ( stablecoinWrappedNativePoolId )
4647 if ( stablecoinWrappedNativePool !== null ) {
4748 return stablecoinIsToken0 ? stablecoinWrappedNativePool . token0Price : stablecoinWrappedNativePool . token1Price
4849 } else {
4950 return ZERO_BD
5051 }
5152}
5253
54+ function getPoolNativeLiquidityAndPrice ( token : Token , pool : Pool , loadedReferenceToken : Token | null ) : BigDecimal [ ] {
55+ if ( ! pool . liquidity . gt ( ZERO_BI ) ) {
56+ return [ ZERO_BD , ZERO_BD ]
57+ }
58+
59+ let referenceToken = loadedReferenceToken
60+ if ( pool . token0 == token . id ) {
61+ if ( referenceToken === null || referenceToken . id != pool . token1 ) {
62+ referenceToken = Token . load ( pool . token1 )
63+ }
64+ if ( referenceToken ) {
65+ return [
66+ pool . totalValueLockedToken1 . times ( referenceToken . derivedETH ) ,
67+ pool . token1Price . times ( referenceToken . derivedETH ) ,
68+ ]
69+ }
70+ } else if ( pool . token1 == token . id ) {
71+ if ( referenceToken === null || referenceToken . id != pool . token0 ) {
72+ referenceToken = Token . load ( pool . token0 )
73+ }
74+ if ( referenceToken ) {
75+ return [
76+ pool . totalValueLockedToken0 . times ( referenceToken . derivedETH ) ,
77+ pool . token0Price . times ( referenceToken . derivedETH ) ,
78+ ]
79+ }
80+ }
81+
82+ return [ ZERO_BD , ZERO_BD ]
83+ }
84+
5385/**
5486 * Search through graph to find derived Eth per token.
5587 * Callers pass the already-loaded Bundle to avoid a redundant store.get.
88+ * The best pool is cached on Token and compared with the active pool on each
89+ * call. A periodic full scan repairs stale cache entries and seeds grafted
90+ * tokens without assuming whitelistPools creation order reflects liquidity.
5691 * @todo update to be derived ETH (add stablecoin estimates)
5792 **/
5893export function findNativePerToken (
@@ -61,57 +96,77 @@ export function findNativePerToken(
6196 stablecoinAddresses : string [ ] ,
6297 minimumNativeLocked : BigDecimal ,
6398 bundle : Bundle ,
99+ timestamp : BigInt ,
100+ activePool : Pool | null ,
101+ activeReferenceToken : Token | null ,
64102) : BigDecimal {
65103 if ( token . id == wrappedNativeAddress || token . id == ADDRESS_ZERO ) {
66104 return ONE_BD
67105 }
106+ // hardcoded fix for incorrect rates
107+ // if whitelist includes token - get the safe price
108+ if ( stablecoinAddresses . includes ( token . id ) ) {
109+ return safeDiv ( ONE_BD , bundle . ethPriceUSD )
110+ }
111+
68112 const whiteList = token . whitelistPools
69- // for now just take USD from pool with greatest TVL
70- // need to update this to actually detect best rate based on liquidity distribution
113+ const cachedPoolId = token . pricingPool
114+ const lastScanTimestamp = token . pricingPoolLastScanTimestamp
115+ let shouldRescan =
116+ lastScanTimestamp === null || timestamp . minus ( lastScanTimestamp ) . ge ( PRICING_POOL_RESCAN_INTERVAL_SECONDS )
71117 let largestLiquidityETH = ZERO_BD
72118 let priceSoFar = ZERO_BD
119+ let bestPoolId : string | null = null
120+
121+ if ( ! shouldRescan && cachedPoolId !== null ) {
122+ const cachedPool = activePool !== null && activePool . id == cachedPoolId ? activePool : Pool . load ( cachedPoolId )
123+ if ( cachedPool !== null ) {
124+ const loadedReferenceToken = activePool !== null && activePool . id == cachedPoolId ? activeReferenceToken : null
125+ const cachedValues = getPoolNativeLiquidityAndPrice ( token , cachedPool , loadedReferenceToken )
126+ if ( cachedValues [ 0 ] . gt ( minimumNativeLocked ) ) {
127+ largestLiquidityETH = cachedValues [ 0 ]
128+ priceSoFar = cachedValues [ 1 ]
129+ bestPoolId = cachedPool . id
130+ } else {
131+ shouldRescan = true
132+ }
133+ } else {
134+ shouldRescan = true
135+ }
136+ }
73137
74- // hardcoded fix for incorrect rates
75- // if whitelist includes token - get the safe price
76- if ( stablecoinAddresses . includes ( token . id ) ) {
77- priceSoFar = safeDiv ( ONE_BD , bundle . ethPriceUSD )
78- } else {
79- const walkLimit = whiteList . length < MAX_WHITELIST_POOLS_TO_WALK ? whiteList . length : MAX_WHITELIST_POOLS_TO_WALK
80- for ( let i = 0 ; i < walkLimit ; ++ i ) {
138+ if ( shouldRescan ) {
139+ largestLiquidityETH = ZERO_BD
140+ priceSoFar = ZERO_BD
141+ bestPoolId = null
142+
143+ for ( let i = 0 ; i < whiteList . length ; ++ i ) {
81144 const poolAddress = whiteList [ i ]
82- const pool = Pool . load ( poolAddress )
83-
84- if ( pool ) {
85- if ( pool . liquidity . gt ( ZERO_BI ) ) {
86- if ( pool . token0 == token . id ) {
87- // whitelist token is token1
88- const token1 = Token . load ( pool . token1 )
89- // get the derived ETH in pool
90- if ( token1 ) {
91- const ethLocked = pool . totalValueLockedToken1 . times ( token1 . derivedETH )
92- if ( ethLocked . gt ( largestLiquidityETH ) && ethLocked . gt ( minimumNativeLocked ) ) {
93- largestLiquidityETH = ethLocked
94- // token1 per our token * Eth per token1
95- priceSoFar = pool . token1Price . times ( token1 . derivedETH as BigDecimal )
96- }
97- }
98- }
99- if ( pool . token1 == token . id ) {
100- const token0 = Token . load ( pool . token0 )
101- // get the derived ETH in pool
102- if ( token0 ) {
103- const ethLocked = pool . totalValueLockedToken0 . times ( token0 . derivedETH )
104- if ( ethLocked . gt ( largestLiquidityETH ) && ethLocked . gt ( minimumNativeLocked ) ) {
105- largestLiquidityETH = ethLocked
106- // token0 per our token * ETH per token0
107- priceSoFar = pool . token0Price . times ( token0 . derivedETH as BigDecimal )
108- }
109- }
110- }
145+ const pool = activePool !== null && activePool . id == poolAddress ? activePool : Pool . load ( poolAddress )
146+ if ( pool !== null ) {
147+ const loadedReferenceToken = activePool !== null && activePool . id == poolAddress ? activeReferenceToken : null
148+ const values = getPoolNativeLiquidityAndPrice ( token , pool , loadedReferenceToken )
149+ if ( values [ 0 ] . gt ( largestLiquidityETH ) && values [ 0 ] . gt ( minimumNativeLocked ) ) {
150+ largestLiquidityETH = values [ 0 ]
151+ priceSoFar = values [ 1 ]
152+ bestPoolId = pool . id
111153 }
112154 }
113155 }
156+ token . pricingPoolLastScanTimestamp = timestamp
157+ } else if (
158+ activePool !== null &&
159+ ( cachedPoolId === null || activePool . id != cachedPoolId ) &&
160+ whiteList . includes ( activePool . id )
161+ ) {
162+ const values = getPoolNativeLiquidityAndPrice ( token , activePool , activeReferenceToken )
163+ if ( values [ 0 ] . gt ( largestLiquidityETH ) && values [ 0 ] . gt ( minimumNativeLocked ) ) {
164+ priceSoFar = values [ 1 ]
165+ bestPoolId = activePool . id
166+ }
114167 }
168+
169+ token . pricingPool = bestPoolId
115170 return priceSoFar
116171}
117172
0 commit comments