@strkfarm/sdk 2.0.0-staging.69 → 2.0.0-staging.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/modules/pricer.ts
2
- import axios2 from "axios";
2
+ import axios3 from "axios";
3
3
 
4
4
  // src/global.ts
5
5
  import axios from "axios";
@@ -466,8 +466,9 @@ var defaultTokens = [{
466
466
  decimals: 18,
467
467
  coingeckId: void 0,
468
468
  displayDecimals: 6,
469
- priceCheckAmount: 1e-4
469
+ priceCheckAmount: 1e-4,
470
470
  // 112000 * 0.0001 = $11.2
471
+ dontPrice: true
471
472
  }, {
472
473
  name: "mRe7YIELD",
473
474
  symbol: "mRe7YIELD",
@@ -476,7 +477,8 @@ var defaultTokens = [{
476
477
  decimals: 18,
477
478
  coingeckId: void 0,
478
479
  displayDecimals: 2,
479
- priceCheckAmount: 100
480
+ priceCheckAmount: 100,
481
+ dontPrice: true
480
482
  }, {
481
483
  name: "fyeWBTC",
482
484
  symbol: "fyeWBTC",
@@ -485,8 +487,9 @@ var defaultTokens = [{
485
487
  decimals: 8,
486
488
  coingeckId: void 0,
487
489
  displayDecimals: 6,
488
- priceCheckAmount: 1e-3
490
+ priceCheckAmount: 1e-3,
489
491
  // 112000 * 0.0001 = $110.2
492
+ dontPrice: true
490
493
  }, {
491
494
  name: "fyETH",
492
495
  symbol: "fyETH",
@@ -495,7 +498,8 @@ var defaultTokens = [{
495
498
  decimals: 18,
496
499
  coingeckId: void 0,
497
500
  displayDecimals: 4,
498
- priceCheckAmount: 0.1
501
+ priceCheckAmount: 0.1,
502
+ dontPrice: true
499
503
  }, {
500
504
  name: "fyeUSDC",
501
505
  symbol: "fyeUSDC",
@@ -504,7 +508,8 @@ var defaultTokens = [{
504
508
  decimals: 6,
505
509
  coingeckId: void 0,
506
510
  displayDecimals: 2,
507
- priceCheckAmount: 100
511
+ priceCheckAmount: 100,
512
+ dontPrice: true
508
513
  }, {
509
514
  name: "strkBTC",
510
515
  symbol: "strkBTC",
@@ -795,7 +800,96 @@ var AvnuWrapper = class _AvnuWrapper {
795
800
  }
796
801
  };
797
802
 
803
+ // src/modules/pricer-avnu-api.ts
804
+ import axios2 from "axios";
805
+ var AVNU_TOKENS_API = "https://starknet.impulse.avnu.fi/v3/tokens";
806
+ var PricerAvnuApi = class extends PricerBase {
807
+ constructor(config, tokens2) {
808
+ super(config, tokens2);
809
+ this.prices = {};
810
+ this.refreshInterval = 15e3;
811
+ this.staleTime = 5 * 60 * 1e3;
812
+ this.pollTimer = null;
813
+ this.loading = false;
814
+ }
815
+ start() {
816
+ this._loadPrices();
817
+ this.pollTimer = setInterval(() => {
818
+ this._loadPrices();
819
+ }, this.refreshInterval);
820
+ }
821
+ stop() {
822
+ if (this.pollTimer) {
823
+ clearInterval(this.pollTimer);
824
+ this.pollTimer = null;
825
+ }
826
+ }
827
+ isStale(timestamp) {
828
+ return Date.now() - timestamp.getTime() > this.staleTime;
829
+ }
830
+ hasPrice(tokenSymbol) {
831
+ const info = this.prices[tokenSymbol];
832
+ return !!info && !this.isStale(info.timestamp);
833
+ }
834
+ async getPrice(tokenSymbol) {
835
+ const info = this.prices[tokenSymbol];
836
+ if (!info) {
837
+ throw new Error(`AvnuApi: price of ${tokenSymbol} not found`);
838
+ }
839
+ if (this.isStale(info.timestamp)) {
840
+ throw new Error(`AvnuApi: price of ${tokenSymbol} is stale`);
841
+ }
842
+ return info;
843
+ }
844
+ async _loadPrices() {
845
+ if (this.loading) {
846
+ return;
847
+ }
848
+ this.loading = true;
849
+ const timestamp = /* @__PURE__ */ new Date();
850
+ try {
851
+ const result = await axios2.get(AVNU_TOKENS_API);
852
+ const priceByAddress = /* @__PURE__ */ new Map();
853
+ for (const entry of result.data) {
854
+ const usd = entry.starknet?.usd;
855
+ if (usd != null && usd > 0) {
856
+ priceByAddress.set(ContractAddr.standardise(entry.address), usd);
857
+ }
858
+ }
859
+ for (const token of this.tokens) {
860
+ if (token.symbol === "USDT" || token.symbol === "USDC") {
861
+ this.prices[token.symbol] = { price: 1, timestamp };
862
+ continue;
863
+ }
864
+ const targetToken = token.priceProxySymbol ? this.tokens.find((t) => t.symbol === token.priceProxySymbol) : token;
865
+ if (!targetToken) {
866
+ continue;
867
+ }
868
+ const addr = targetToken.address.address;
869
+ const price = priceByAddress.get(addr);
870
+ if (price != null) {
871
+ this.prices[token.symbol] = { price, timestamp };
872
+ logger.verbose(
873
+ `AvnuApi: ${token.symbol} -> $${price}`
874
+ );
875
+ }
876
+ }
877
+ } catch (error) {
878
+ logger.warn(`AvnuApi: failed to fetch tokens: ${error?.message ?? error}`);
879
+ } finally {
880
+ this.loading = false;
881
+ }
882
+ }
883
+ };
884
+
798
885
  // src/modules/pricer.ts
886
+ var PRICE_METHOD_PRIORITY = [
887
+ "AvnuApi",
888
+ "Coinbase",
889
+ "Coinmarketcap",
890
+ "Ekubo",
891
+ "Avnu"
892
+ ];
799
893
  var Pricer = class extends PricerBase {
800
894
  // e.g. ETH/USDC
801
895
  constructor(config, tokens2, refreshInterval = 3e4, staleTime = 6e4) {
@@ -814,6 +908,7 @@ var Pricer = class extends PricerBase {
814
908
  this.EKUBO_API = "https://prod-api-quoter.ekubo.org/23448594291968334/{{AMOUNT}}/{{TOKEN_ADDRESS}}/0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb";
815
909
  this.refreshInterval = refreshInterval;
816
910
  this.staleTime = staleTime;
911
+ this.avnuApiPricer = new PricerAvnuApi(config, tokens2);
817
912
  }
818
913
  isReady() {
819
914
  const allPricesExist = Object.keys(this.prices).length === this.tokens.length;
@@ -843,6 +938,7 @@ var Pricer = class extends PricerBase {
843
938
  });
844
939
  }
845
940
  start() {
941
+ this.avnuApiPricer.start();
846
942
  this._loadPrices();
847
943
  setInterval(() => {
848
944
  this._loadPrices();
@@ -867,6 +963,9 @@ var Pricer = class extends PricerBase {
867
963
  let retry = 0;
868
964
  while (retry < MAX_RETRIES) {
869
965
  try {
966
+ if (token.dontPrice) {
967
+ return;
968
+ }
870
969
  if (token.symbol === "USDT" || token.symbol === "USDC") {
871
970
  this.prices[token.symbol] = {
872
971
  price: 1,
@@ -909,60 +1008,61 @@ var Pricer = class extends PricerBase {
909
1008
  });
910
1009
  if (this.isReady() && this.config.heartbeatUrl) {
911
1010
  console.log(`sending beat`);
912
- axios2.get(this.config.heartbeatUrl).catch((err) => {
1011
+ axios3.get(this.config.heartbeatUrl).catch((err) => {
913
1012
  console.error("Pricer: Heartbeat err", err);
914
1013
  });
915
1014
  }
916
1015
  }
917
- async _getPrice(token, defaultMethod = "all") {
918
- const methodToUse = this.methodToUse[token.symbol] || defaultMethod;
919
- logger.verbose(`Fetching price of ${token.symbol} using ${methodToUse}`);
920
- switch (methodToUse) {
1016
+ async _getPrice(token) {
1017
+ const pinned = this.methodToUse[token.symbol];
1018
+ if (pinned) {
1019
+ logger.verbose(`Fetching price of ${token.symbol} using pinned ${pinned}`);
1020
+ try {
1021
+ return await this._tryPriceMethod(token, pinned);
1022
+ } catch (error) {
1023
+ console.warn(`${pinned}: pinned price failed [${token.symbol}]: `, error.message);
1024
+ delete this.methodToUse[token.symbol];
1025
+ }
1026
+ }
1027
+ for (const method of PRICE_METHOD_PRIORITY) {
1028
+ logger.verbose(`Fetching price of ${token.symbol} using ${method}`);
1029
+ try {
1030
+ const result = await this._tryPriceMethod(token, method);
1031
+ this.methodToUse[token.symbol] = method;
1032
+ return result;
1033
+ } catch (error) {
1034
+ console.warn(`${method}: price err [${token.symbol}]: `, error.message);
1035
+ }
1036
+ }
1037
+ throw new FatalError(`Price not found for ${token.symbol}`);
1038
+ }
1039
+ async _tryPriceMethod(token, method) {
1040
+ switch (method) {
1041
+ case "AvnuApi":
1042
+ return await this._getPriceAvnuApi(token);
921
1043
  case "Coinbase":
922
- // try {
923
- // const result = await this._getPriceCoinbase(token);
924
- // this.methodToUse[token.symbol] = 'Coinbase';
925
- // return result;
926
- // } catch (error: any) {
927
- // console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
928
- // // do nothing, try next
929
- // }
1044
+ return await this._getPriceCoinbase(token);
930
1045
  case "Coinmarketcap":
931
- try {
932
- const result = await this._getPriceCoinMarketCap(token);
933
- this.methodToUse[token.symbol] = "Coinmarketcap";
934
- return result;
935
- } catch (error) {
936
- console.warn(`CoinMarketCap: price err [${token.symbol}]: `, Object.keys(error));
937
- console.warn(`CoinMarketCap: price err [${token.symbol}]: `, error.message);
938
- }
1046
+ return await this._getPriceCoinMarketCap(token);
939
1047
  case "Ekubo":
940
- try {
941
- const result = await this._getPriceEkubo(token, new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals));
942
- this.methodToUse[token.symbol] = "Ekubo";
943
- return result;
944
- } catch (error) {
945
- console.warn(`Ekubo: price err [${token.symbol}]: `, error.message);
946
- console.warn(`Ekubo: price err [${token.symbol}]: `, Object.keys(error));
947
- }
1048
+ return await this._getPriceEkubo(
1049
+ token,
1050
+ new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals)
1051
+ );
948
1052
  case "Avnu":
949
- try {
950
- const result = await this._getAvnuPrice(token, new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals));
951
- this.methodToUse[token.symbol] = "Avnu";
952
- return result;
953
- } catch (error) {
954
- console.warn(`Avnu: price err [${token.symbol}]: `, error.message);
955
- console.warn(`Avnu: price err [${token.symbol}]: `, Object.keys(error));
956
- }
957
- }
958
- if (defaultMethod == "all") {
959
- return await this._getPrice(token, "Coinbase");
1053
+ return await this._getAvnuPrice(
1054
+ token,
1055
+ new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals)
1056
+ );
960
1057
  }
961
- throw new FatalError(`Price not found for ${token.symbol}`);
1058
+ }
1059
+ async _getPriceAvnuApi(token) {
1060
+ const priceInfo = await this.avnuApiPricer.getPrice(token.symbol);
1061
+ return priceInfo.price;
962
1062
  }
963
1063
  async _getPriceCoinbase(token) {
964
1064
  const url = this.PRICE_API.replace("{{PRICER_KEY}}", `${token.symbol}-USD`);
965
- const result = await axios2.get(url);
1065
+ const result = await axios3.get(url);
966
1066
  const data = result.data;
967
1067
  return Number(data.data.amount);
968
1068
  }
@@ -988,7 +1088,7 @@ var Pricer = class extends PricerBase {
988
1088
  async _getPriceEkubo(token, amountIn = new Web3Number(1, token.decimals), retry = 0) {
989
1089
  logger.verbose(`Getting price of ${token.symbol} using Ekubo, amountIn: ${amountIn.toWei()}`);
990
1090
  const url = this.EKUBO_API.replace("{{TOKEN_ADDRESS}}", token.address.toString()).replace("{{AMOUNT}}", amountIn.toWei());
991
- const result = await axios2.get(url);
1091
+ const result = await axios3.get(url);
992
1092
  const data = result.data;
993
1093
  const multiplier = 1 / amountIn.toNumber();
994
1094
  const outputUSDC = Number(Web3Number.fromWei(data.total_calculated, 6).toFixed(6)) * multiplier;
@@ -1134,7 +1234,7 @@ var Pragma = class extends PricerBase {
1134
1234
  };
1135
1235
 
1136
1236
  // src/modules/zkLend.ts
1137
- import axios3 from "axios";
1237
+ import axios4 from "axios";
1138
1238
 
1139
1239
  // src/dataTypes/bignumber.browser.ts
1140
1240
  import { uint256 as uint2564 } from "starknet";
@@ -1190,7 +1290,7 @@ var _ZkLend = class _ZkLend extends ILending {
1190
1290
  async init() {
1191
1291
  try {
1192
1292
  logger.verbose(`Initialising ${this.metadata.name}`);
1193
- const result = await axios3.get(_ZkLend.POOLS_URL);
1293
+ const result = await axios4.get(_ZkLend.POOLS_URL);
1194
1294
  const data = result.data;
1195
1295
  const savedTokens = await Global.getTokens();
1196
1296
  data.forEach((pool) => {
@@ -1282,7 +1382,7 @@ var _ZkLend = class _ZkLend extends ILending {
1282
1382
  */
1283
1383
  async getPositions(user) {
1284
1384
  const url = this.POSITION_URL.replace("{{USER_ADDR}}", user.address);
1285
- const result = await axios3.get(url);
1385
+ const result = await axios4.get(url);
1286
1386
  const data = result.data;
1287
1387
  const lendingPosition = [];
1288
1388
  logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(data)}`);
@@ -1315,7 +1415,7 @@ _ZkLend.POOLS_URL = "https://app.zklend.com/api/pools";
1315
1415
  var ZkLend = _ZkLend;
1316
1416
 
1317
1417
  // src/modules/pricer-from-api.ts
1318
- import axios4 from "axios";
1418
+ import axios5 from "axios";
1319
1419
 
1320
1420
  // src/modules/apollo-client-config.ts
1321
1421
  import { ApolloClient, InMemoryCache } from "@apollo/client";
@@ -1697,7 +1797,7 @@ var PricerFromApi = class extends PricerBase {
1697
1797
  const MAX_RETRIES = 5;
1698
1798
  for (retry = 1; retry < MAX_RETRIES + 1; retry++) {
1699
1799
  try {
1700
- const priceInfo = await axios4.get(
1800
+ const priceInfo = await axios5.get(
1701
1801
  `https://api.coinbase.com/v2/prices/${tokenSymbol}-USDT/spot`
1702
1802
  );
1703
1803
  if (!priceInfo) {
@@ -2981,7 +3081,7 @@ var ERC20 = class {
2981
3081
  };
2982
3082
 
2983
3083
  // src/modules/ekubo-quoter.ts
2984
- import axios5 from "axios";
3084
+ import axios6 from "axios";
2985
3085
  var EkuboQuoter = class {
2986
3086
  // e.g. ETH/USDC'
2987
3087
  constructor(config) {
@@ -2999,7 +3099,7 @@ var EkuboQuoter = class {
2999
3099
  let _fromToken = amount.gt(0) ? fromToken : toToken;
3000
3100
  let _toToken = amount.gt(0) ? toToken : fromToken;
3001
3101
  try {
3002
- const quote = await axios5.get(this.ENDPOINT.replace("{{AMOUNT}}", amount.toWei()).replace("{{TOKEN_FROM_ADDRESS}}", _fromToken).replace("{{TOKEN_TO_ADDRESS}}", _toToken));
3102
+ const quote = await axios6.get(this.ENDPOINT.replace("{{AMOUNT}}", amount.toWei()).replace("{{TOKEN_FROM_ADDRESS}}", _fromToken).replace("{{TOKEN_TO_ADDRESS}}", _toToken));
3003
3103
  console.log(`Ekubo quote from ${_fromToken} to ${_toToken} for ${amount.toString()}: ${JSON.stringify(quote.data)}`);
3004
3104
  return quote.data;
3005
3105
  } catch (error) {
@@ -4901,9 +5001,9 @@ var BaseStrategy = class extends CacheClass {
4901
5001
  };
4902
5002
 
4903
5003
  // src/node/headless.browser.ts
4904
- import axios6 from "axios";
5004
+ import axios7 from "axios";
4905
5005
  async function getAPIUsingHeadlessBrowser(url) {
4906
- const res = await axios6.get(url);
5006
+ const res = await axios7.get(url);
4907
5007
  return res.data;
4908
5008
  }
4909
5009
 
@@ -16832,7 +16932,7 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
16832
16932
  const token0Usd = Number(amount0.toFixed(13)) * P0.price;
16833
16933
  const token1Usd = Number(amount1.toFixed(13)) * P1.price;
16834
16934
  const totalUsdValue = token0Usd + token1Usd;
16835
- if (totalUsdValue === 0 || token0Usd === 0 || token1Usd === 0 || amount0.eq(0) || amount1.eq(0)) {
16935
+ if ((totalUsdValue === 0 || token0Usd === 0 || token1Usd === 0 || amount0.eq(0) || amount1.eq(0)) && this.metadata.settings?.liveStatus === "Active" /* ACTIVE */) {
16836
16936
  logger.warn(
16837
16937
  `${this.metadata.name}:getTVL - Zero value detected: usdValue=${totalUsdValue}, amount0=${amount0.toString()}, amount1=${amount1.toString()}, token0Price=${P0.price}, token1Price=${P1.price}, token0Usd=${token0Usd}, token1Usd=${token1Usd}`
16838
16938
  );
@@ -29057,7 +29157,7 @@ var YoLoVault = class extends BaseStrategy {
29057
29157
  sDec
29058
29158
  ).multipliedBy(secondaryTokenPrice.price);
29059
29159
  const totalUsdValue = primaryTokenUsd.plus(secondaryTokenUsd).toNumber();
29060
- if (totalUsdValue === 0 || primaryTokenAmount.eq(0) || secondaryTokenAmount.eq(0)) {
29160
+ if ((totalUsdValue === 0 || primaryTokenAmount.eq(0) || secondaryTokenAmount.eq(0)) && this.metadata.settings?.liveStatus === "Active" /* ACTIVE */) {
29061
29161
  logger.warn(
29062
29162
  `${this.metadata.name}:getTVL - Zero value detected: usdValue=${totalUsdValue}, primaryTokenAmount=${primaryTokenAmount.toString()}, secondaryTokenAmount=${secondaryTokenAmount.toString()}, primaryTokenPrice=${primaryTokenPrice.price}, secondaryTokenPrice=${secondaryTokenPrice.price}, primaryTokenUsd=${primaryTokenUsd.toNumber()}, secondaryTokenUsd=${secondaryTokenUsd.toNumber()}`
29063
29163
  );
@@ -32416,7 +32516,7 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
32416
32516
  this.metadata.depositTokens[0].symbol
32417
32517
  );
32418
32518
  const usdValue = Number(amount.toFixed(6)) * price.price;
32419
- if (usdValue === 0 || amount.eq(0)) {
32519
+ if ((usdValue === 0 || amount.eq(0)) && this.metadata.settings?.liveStatus === "Active" /* ACTIVE */) {
32420
32520
  logger.warn(
32421
32521
  `${this.metadata.name}:getTVL - Zero value detected: usdValue=${usdValue}, amount=${amount.toString()}, price=${price.price}`
32422
32522
  );
@@ -35155,7 +35255,7 @@ function createStrategy(type, config, pricer, metadata) {
35155
35255
  }
35156
35256
 
35157
35257
  // src/modules/pricer-lst.ts
35158
- import axios7 from "axios";
35258
+ import axios8 from "axios";
35159
35259
  var PricerLST2 = class extends Pricer {
35160
35260
  // e.g. xSTRK/STRK
35161
35261
  constructor(config, tokenMaps) {
@@ -35191,7 +35291,7 @@ var PricerLST2 = class extends Pricer {
35191
35291
  async _getPriceEkubo(token, amountIn = new Web3Number(1, token.decimals), retry = 0) {
35192
35292
  const underlying = this.getUnderlying(token);
35193
35293
  const url = this.EKUBO_API.replace("{{TOKEN_ADDRESS}}", token.address.toString()).replace("{{AMOUNT}}", amountIn.toWei()).replace("{{UNDERLYING_ADDRESS}}", underlying.address.toString());
35194
- const result = await axios7.get(url);
35294
+ const result = await axios8.get(url);
35195
35295
  const data = result.data;
35196
35296
  const multiplier = 1 / amountIn.toNumber();
35197
35297
  const outputUnderlying = Number(Web3Number.fromWei(data.total_calculated, underlying.decimals).toFixed(6)) * multiplier;
@@ -35343,6 +35443,7 @@ var PricerRedis = class extends Pricer {
35343
35443
  async startWithRedis(redisUrl) {
35344
35444
  await this.initRedis(redisUrl);
35345
35445
  logger.info(`Starting Pricer with Redis`);
35446
+ this.avnuApiPricer.start();
35346
35447
  this._loadPrices(this._setRedisPrices.bind(this));
35347
35448
  setInterval(() => {
35348
35449
  this._loadPrices(this._setRedisPrices.bind(this));
@@ -35776,6 +35877,7 @@ export {
35776
35877
  PasswordJsonCryptoUtil,
35777
35878
  Pragma,
35778
35879
  Pricer,
35880
+ PricerAvnuApi,
35779
35881
  PricerFromApi,
35780
35882
  PricerLST2 as PricerLST,
35781
35883
  PricerRedis,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strkfarm/sdk",
3
- "version": "2.0.0-staging.69",
3
+ "version": "2.0.0-staging.70",
4
4
  "description": "STRKFarm TS SDK (Meant for our internal use, but feel free to use it)",
5
5
  "typings": "dist/index.d.ts",
6
6
  "types": "dist/index.d.ts",
package/src/global.ts CHANGED
@@ -156,6 +156,7 @@ const defaultTokens: TokenInfo[] = [{
156
156
  coingeckId: undefined,
157
157
  displayDecimals: 6,
158
158
  priceCheckAmount: 0.0001, // 112000 * 0.0001 = $11.2
159
+ dontPrice: true,
159
160
  }, {
160
161
  name: 'mRe7YIELD',
161
162
  symbol: 'mRe7YIELD',
@@ -165,6 +166,7 @@ const defaultTokens: TokenInfo[] = [{
165
166
  coingeckId: undefined,
166
167
  displayDecimals: 2,
167
168
  priceCheckAmount: 100,
169
+ dontPrice: true,
168
170
  }, {
169
171
  name: "fyeWBTC",
170
172
  symbol: "fyeWBTC",
@@ -174,6 +176,7 @@ const defaultTokens: TokenInfo[] = [{
174
176
  coingeckId: undefined,
175
177
  displayDecimals: 6,
176
178
  priceCheckAmount: 0.001, // 112000 * 0.0001 = $110.2
179
+ dontPrice: true,
177
180
  }, {
178
181
  name: "fyETH",
179
182
  symbol: "fyETH",
@@ -183,6 +186,7 @@ const defaultTokens: TokenInfo[] = [{
183
186
  coingeckId: undefined,
184
187
  displayDecimals: 4,
185
188
  priceCheckAmount: 0.1,
189
+ dontPrice: true,
186
190
  }, {
187
191
  name: "fyeUSDC",
188
192
  symbol: "fyeUSDC",
@@ -192,6 +196,7 @@ const defaultTokens: TokenInfo[] = [{
192
196
  coingeckId: undefined,
193
197
  displayDecimals: 2,
194
198
  priceCheckAmount: 100,
199
+ dontPrice: true,
195
200
  }, {
196
201
  name: 'strkBTC',
197
202
  symbol: 'strkBTC',
@@ -35,6 +35,7 @@ export interface TokenInfo {
35
35
  displayDecimals: number;
36
36
  priceProxySymbol?: string; // for tokens like illiquid tokens, we use a proxy symbol to get the price
37
37
  priceCheckAmount?: number; // for tokens like BTC, doing 1BTC price check may not be ideal, esp on illiquid netwrks like sn
38
+ dontPrice?: boolean; // a flag that skips pricer to check these tokens.
38
39
  }
39
40
 
40
41
  export enum Network {
@@ -1,4 +1,5 @@
1
1
  export * from './pricer';
2
+ export * from './pricer-avnu-api';
2
3
  export * from './pragma';
3
4
  export * from './zkLend';
4
5
  export * from './pricer-from-api';
@@ -0,0 +1,114 @@
1
+ import axios from "axios";
2
+ import { TokenInfo } from "@/interfaces/common";
3
+ import { IConfig } from "@/interfaces/common";
4
+ import { PricerBase } from "./pricerBase";
5
+ import { PriceInfo } from "./pricer";
6
+ import { logger } from "@/utils/logger";
7
+ import { ContractAddr } from "@/dataTypes";
8
+
9
+ const AVNU_TOKENS_API = "https://starknet.impulse.avnu.fi/v3/tokens";
10
+
11
+ interface AvnuTokenApiEntry {
12
+ address: string;
13
+ symbol: string;
14
+ starknet?: {
15
+ usd?: number;
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Polls Avnu impulse tokens API and keeps USD prices in memory for configured tokens.
21
+ * Price timestamp is set when each poll request completes.
22
+ */
23
+ export class PricerAvnuApi extends PricerBase {
24
+ protected prices: { [key: string]: PriceInfo } = {};
25
+
26
+ readonly refreshInterval = 15_000;
27
+ readonly staleTime = 5 * 60 * 1000;
28
+
29
+ private pollTimer: ReturnType<typeof setInterval> | null = null;
30
+ private loading = false;
31
+
32
+ constructor(config: IConfig, tokens: TokenInfo[]) {
33
+ super(config, tokens);
34
+ }
35
+
36
+ start() {
37
+ this._loadPrices();
38
+ this.pollTimer = setInterval(() => {
39
+ this._loadPrices();
40
+ }, this.refreshInterval);
41
+ }
42
+
43
+ stop() {
44
+ if (this.pollTimer) {
45
+ clearInterval(this.pollTimer);
46
+ this.pollTimer = null;
47
+ }
48
+ }
49
+
50
+ isStale(timestamp: Date) {
51
+ return Date.now() - timestamp.getTime() > this.staleTime;
52
+ }
53
+
54
+ hasPrice(tokenSymbol: string) {
55
+ const info = this.prices[tokenSymbol];
56
+ return !!info && !this.isStale(info.timestamp);
57
+ }
58
+
59
+ async getPrice(tokenSymbol: string): Promise<PriceInfo> {
60
+ const info = this.prices[tokenSymbol];
61
+ if (!info) {
62
+ throw new Error(`AvnuApi: price of ${tokenSymbol} not found`);
63
+ }
64
+ if (this.isStale(info.timestamp)) {
65
+ throw new Error(`AvnuApi: price of ${tokenSymbol} is stale`);
66
+ }
67
+ return info;
68
+ }
69
+
70
+ protected async _loadPrices() {
71
+ if (this.loading) {
72
+ return;
73
+ }
74
+ this.loading = true;
75
+ const timestamp = new Date();
76
+ try {
77
+ const result = await axios.get<AvnuTokenApiEntry[]>(AVNU_TOKENS_API);
78
+ const priceByAddress = new Map<string, number>();
79
+ for (const entry of result.data) {
80
+ const usd = entry.starknet?.usd;
81
+ if (usd != null && usd > 0) {
82
+ priceByAddress.set(ContractAddr.standardise(entry.address), usd);
83
+ }
84
+ }
85
+
86
+ for (const token of this.tokens) {
87
+ if (token.symbol === "USDT" || token.symbol === "USDC") {
88
+ this.prices[token.symbol] = { price: 1, timestamp };
89
+ continue;
90
+ }
91
+
92
+ const targetToken = token.priceProxySymbol
93
+ ? this.tokens.find((t) => t.symbol === token.priceProxySymbol)
94
+ : token;
95
+ if (!targetToken) {
96
+ continue;
97
+ }
98
+
99
+ const addr = targetToken.address.address;
100
+ const price = priceByAddress.get(addr);
101
+ if (price != null) {
102
+ this.prices[token.symbol] = { price, timestamp };
103
+ logger.verbose(
104
+ `AvnuApi: ${token.symbol} -> $${price}`,
105
+ );
106
+ }
107
+ }
108
+ } catch (error: any) {
109
+ logger.warn(`AvnuApi: failed to fetch tokens: ${error?.message ?? error}`);
110
+ } finally {
111
+ this.loading = false;
112
+ }
113
+ }
114
+ }