@defisaver/positions-sdk 2.1.127-midnight-dev → 2.1.127-midnight-2-dev

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.
Files changed (79) hide show
  1. package/cjs/aaveV3/index.js +7 -1
  2. package/cjs/aaveV4/lend.js +3 -3
  3. package/cjs/claiming/index.d.ts +2 -1
  4. package/cjs/claiming/index.js +3 -1
  5. package/cjs/claiming/uniswap.d.ts +5 -0
  6. package/cjs/claiming/uniswap.js +75 -0
  7. package/cjs/config/contracts.d.ts +22 -0
  8. package/cjs/config/contracts.js +9 -1
  9. package/cjs/contracts.d.ts +35 -0
  10. package/cjs/contracts.js +2 -1
  11. package/cjs/curveUsd/index.js +1 -1
  12. package/cjs/helpers/morphoBlueHelpers/index.js +1 -0
  13. package/cjs/helpers/morphoMidnightHelpers/index.d.ts +62 -0
  14. package/cjs/helpers/morphoMidnightHelpers/index.js +156 -1
  15. package/cjs/llamaLend/index.js +1 -1
  16. package/cjs/markets/aaveV4/index.d.ts +2 -0
  17. package/cjs/markets/aaveV4/index.js +14 -1
  18. package/cjs/morphoBlue/index.d.ts +8 -6
  19. package/cjs/morphoBlue/index.js +69 -40
  20. package/cjs/morphoMidnight/index.js +25 -1
  21. package/cjs/portfolio/index.js +31 -2
  22. package/cjs/savings/summerVaults/options.js +10 -10
  23. package/cjs/staking/staking.js +3 -0
  24. package/cjs/types/aaveV4.d.ts +2 -1
  25. package/cjs/types/aaveV4.js +1 -0
  26. package/cjs/types/claiming.d.ts +10 -0
  27. package/cjs/types/claiming.js +2 -0
  28. package/cjs/types/morphoBlue.d.ts +9 -0
  29. package/cjs/types/morphoMidnight.d.ts +3 -0
  30. package/esm/aaveV3/index.js +7 -1
  31. package/esm/aaveV4/lend.js +3 -3
  32. package/esm/claiming/index.d.ts +2 -1
  33. package/esm/claiming/index.js +2 -1
  34. package/esm/claiming/uniswap.d.ts +5 -0
  35. package/esm/claiming/uniswap.js +67 -0
  36. package/esm/config/contracts.d.ts +22 -0
  37. package/esm/config/contracts.js +8 -0
  38. package/esm/contracts.d.ts +35 -0
  39. package/esm/contracts.js +1 -0
  40. package/esm/curveUsd/index.js +1 -1
  41. package/esm/helpers/morphoBlueHelpers/index.js +1 -0
  42. package/esm/helpers/morphoMidnightHelpers/index.d.ts +62 -0
  43. package/esm/helpers/morphoMidnightHelpers/index.js +149 -0
  44. package/esm/llamaLend/index.js +1 -1
  45. package/esm/markets/aaveV4/index.d.ts +2 -0
  46. package/esm/markets/aaveV4/index.js +12 -0
  47. package/esm/morphoBlue/index.d.ts +8 -6
  48. package/esm/morphoBlue/index.js +62 -39
  49. package/esm/morphoMidnight/index.js +26 -2
  50. package/esm/portfolio/index.js +32 -3
  51. package/esm/savings/summerVaults/options.js +10 -10
  52. package/esm/staking/staking.js +3 -0
  53. package/esm/types/aaveV4.d.ts +2 -1
  54. package/esm/types/aaveV4.js +1 -0
  55. package/esm/types/claiming.d.ts +10 -0
  56. package/esm/types/claiming.js +2 -0
  57. package/esm/types/morphoBlue.d.ts +9 -0
  58. package/esm/types/morphoMidnight.d.ts +3 -0
  59. package/package.json +2 -2
  60. package/src/aaveV3/index.ts +13 -1
  61. package/src/aaveV4/lend.ts +3 -3
  62. package/src/claiming/index.ts +2 -0
  63. package/src/claiming/uniswap.ts +70 -0
  64. package/src/config/contracts.ts +9 -0
  65. package/src/contracts.ts +2 -0
  66. package/src/curveUsd/index.ts +1 -1
  67. package/src/helpers/morphoBlueHelpers/index.ts +1 -0
  68. package/src/helpers/morphoMidnightHelpers/index.ts +206 -0
  69. package/src/llamaLend/index.ts +1 -1
  70. package/src/markets/aaveV4/index.ts +13 -0
  71. package/src/morphoBlue/index.ts +101 -44
  72. package/src/morphoMidnight/index.ts +29 -2
  73. package/src/portfolio/index.ts +32 -3
  74. package/src/savings/summerVaults/options.ts +10 -10
  75. package/src/staking/staking.ts +2 -0
  76. package/src/types/aaveV4.ts +1 -0
  77. package/src/types/claiming.ts +11 -0
  78. package/src/types/morphoBlue.ts +11 -0
  79. package/src/types/morphoMidnight.ts +6 -0
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import Dec from 'decimal.js';
11
11
  import { NetworkNumber } from '../types/common';
12
12
  import { AaveMarkets, AaveV4Spokes, CompoundMarkets, CrvUsdMarkets, EulerV2Markets, LiquityV2Markets, LlamaLendMarkets, MorphoBlueMarkets, MorphoMidnightMarkets, SparkMarkets, } from '../markets';
13
- import { _getMorphoBlueAccountData, _getMorphoBlueMarketData, getMorphoEarn } from '../morphoBlue';
13
+ import { _getMorphoBlueAccountData, _getMorphoBluePortfolioMarketData, getMorphoEarn } from '../morphoBlue';
14
14
  import { _getMorphoMidnightAccountData, _getMorphoMidnightMarketData, getMorphoMidnightEarn } from '../morphoMidnight';
15
15
  import { AaveVersions, CompoundVersions, } from '../types';
16
16
  import { _getCompoundV3AccountData, _getCompoundV3MarketsData } from '../compoundV3';
@@ -35,6 +35,7 @@ import { fetchSparkAirdropRewards, fetchSparkRewards } from '../claiming/spark';
35
35
  import { getKingRewards } from '../claiming/king';
36
36
  import { fetchEthenaAirdropRewards } from '../claiming/ethena';
37
37
  import { _getAaveV4AccountData, _getAaveV4SpokeData } from '../aaveV4';
38
+ import { getUniswapRewards } from '../claiming/uniswap';
38
39
  export function getPortfolioData(provider_1, network_1, defaultProvider_1, addresses_1) {
39
40
  return __awaiter(this, arguments, void 0, function* (provider, network, defaultProvider, addresses, isSim = false) {
40
41
  const isMainnet = network === NetworkNumber.Eth;
@@ -52,7 +53,7 @@ export function getPortfolioData(provider_1, network_1, defaultProvider_1, addre
52
53
  const liquityV2Markets = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)) : [];
53
54
  const liquityV2MarketsStaking = [NetworkNumber.Eth].includes(network) ? Object.values(LiquityV2Markets(network)).filter(market => !market.isLegacy) : [];
54
55
  const aaveV4Spokes = Object.values(AaveV4Spokes(network)).filter((market) => market.chainIds.includes(network));
55
- const args = [network, { batch: { multicall: { batchSize: isSim ? 500000 : 2500000 } } }];
56
+ const args = [network, { batch: { multicall: { batchSize: isSim ? 2000 : 2500000 } } }];
56
57
  const client = getViemProvider(provider, ...args);
57
58
  const defaultClient = getViemProvider(defaultProvider, ...args);
58
59
  const morphoMarketsData = {};
@@ -137,7 +138,7 @@ export function getPortfolioData(provider_1, network_1, defaultProvider_1, addre
137
138
  yield Promise.allSettled([
138
139
  // === MARKET DATA (needs to be fetched first) ===
139
140
  ...morphoMarkets.map((market) => __awaiter(this, void 0, void 0, function* () {
140
- const marketData = yield _getMorphoBlueMarketData(client, network, market);
141
+ const marketData = yield _getMorphoBluePortfolioMarketData(client, network, market);
141
142
  morphoMarketsData[market.value] = marketData;
142
143
  })),
143
144
  ...morphoMidnightMarkets.map((market) => __awaiter(this, void 0, void 0, function* () {
@@ -299,6 +300,34 @@ export function getPortfolioData(provider_1, network_1, defaultProvider_1, addre
299
300
  }
300
301
  }
301
302
  }))(),
303
+ // Batch UNI rewards
304
+ (() => __awaiter(this, void 0, void 0, function* () {
305
+ try {
306
+ if (!isMainnet) {
307
+ for (const address of addresses) {
308
+ rewardsData[address.toLowerCase()].uniswap = { error: '', data: [] };
309
+ }
310
+ return;
311
+ }
312
+ const uniswapRewards = yield getUniswapRewards(client, network, addresses);
313
+ for (const address of addresses) {
314
+ const lowerAddress = address.toLowerCase();
315
+ rewardsData[lowerAddress].uniswap = {
316
+ error: '',
317
+ data: uniswapRewards[lowerAddress] || [],
318
+ };
319
+ }
320
+ }
321
+ catch (error) {
322
+ console.error('Error fetching Uniswap rewards data in batch:', error);
323
+ for (const address of addresses) {
324
+ rewardsData[address.toLowerCase()].uniswap = {
325
+ error: 'Error fetching Uniswap rewards data in batch',
326
+ data: null,
327
+ };
328
+ }
329
+ }
330
+ }))(),
302
331
  ...sparkMarkets.map((market) => addresses.map((address) => __awaiter(this, void 0, void 0, function* () {
303
332
  try {
304
333
  if (!isMainnet) {
@@ -7,7 +7,7 @@ export const SUMMER_VAULT_USDC_MAINNET_LR = {
7
7
  asset: 'USDC',
8
8
  network: NetworkNumber.Eth,
9
9
  deploymentBlock: 21795049,
10
- isLegacy: false,
10
+ isLegacy: true,
11
11
  };
12
12
  export const SUMMER_VAULT_USDC_MAINNET_HR = {
13
13
  type: SummerVaultType.SummerVaultUSDCMainnetHR,
@@ -16,7 +16,7 @@ export const SUMMER_VAULT_USDC_MAINNET_HR = {
16
16
  asset: 'USDC',
17
17
  network: NetworkNumber.Eth,
18
18
  deploymentBlock: 22488041,
19
- isLegacy: false,
19
+ isLegacy: true,
20
20
  };
21
21
  export const SUMMER_VAULT_USDT_MAINNET_LR = {
22
22
  type: SummerVaultType.SummerVaultUSDTMainnetLR,
@@ -25,7 +25,7 @@ export const SUMMER_VAULT_USDT_MAINNET_LR = {
25
25
  asset: 'USDT',
26
26
  network: NetworkNumber.Eth,
27
27
  deploymentBlock: 21795276,
28
- isLegacy: false,
28
+ isLegacy: true,
29
29
  };
30
30
  export const SUMMER_VAULT_ETH_MAINNET_LR = {
31
31
  type: SummerVaultType.SummerVaultETHMainnetLR,
@@ -34,7 +34,7 @@ export const SUMMER_VAULT_ETH_MAINNET_LR = {
34
34
  asset: 'WETH',
35
35
  network: NetworkNumber.Eth,
36
36
  deploymentBlock: 21795390,
37
- isLegacy: false,
37
+ isLegacy: true,
38
38
  };
39
39
  export const SUMMER_VAULT_ETH_MAINNET_HR = {
40
40
  type: SummerVaultType.SummerVaultETHMainnetHR,
@@ -43,7 +43,7 @@ export const SUMMER_VAULT_ETH_MAINNET_HR = {
43
43
  asset: 'WETH',
44
44
  network: NetworkNumber.Eth,
45
45
  deploymentBlock: 22291252,
46
- isLegacy: false,
46
+ isLegacy: true,
47
47
  };
48
48
  export const SUMMER_VAULT_USDC_ARBITRUM_LR = {
49
49
  type: SummerVaultType.SummerVaultUSDCArbitrumLR,
@@ -52,7 +52,7 @@ export const SUMMER_VAULT_USDC_ARBITRUM_LR = {
52
52
  asset: 'USDC',
53
53
  network: NetworkNumber.Arb,
54
54
  deploymentBlock: 404339289,
55
- isLegacy: false,
55
+ isLegacy: true,
56
56
  };
57
57
  export const SUMMER_VAULT_USDT_ARBITRUM_LR = {
58
58
  type: SummerVaultType.SummerVaultUSDTArbitrumLR,
@@ -61,7 +61,7 @@ export const SUMMER_VAULT_USDT_ARBITRUM_LR = {
61
61
  asset: 'USDT',
62
62
  network: NetworkNumber.Arb,
63
63
  deploymentBlock: 303594065,
64
- isLegacy: false,
64
+ isLegacy: true,
65
65
  };
66
66
  export const SUMMER_VAULT_USDC_BASE_LR = {
67
67
  type: SummerVaultType.SummerVaultUSDCBaseLR,
@@ -70,7 +70,7 @@ export const SUMMER_VAULT_USDC_BASE_LR = {
70
70
  asset: 'USDC',
71
71
  network: NetworkNumber.Base,
72
72
  deploymentBlock: 303594065,
73
- isLegacy: false,
73
+ isLegacy: true,
74
74
  };
75
75
  export const SUMMER_VAULT_EURC_BASE_LR = {
76
76
  type: SummerVaultType.SummerVaultEURCBaseLR,
@@ -79,7 +79,7 @@ export const SUMMER_VAULT_EURC_BASE_LR = {
79
79
  asset: 'EURC',
80
80
  network: NetworkNumber.Base,
81
81
  deploymentBlock: 27544064,
82
- isLegacy: false,
82
+ isLegacy: true,
83
83
  };
84
84
  export const SUMMER_VAULT_ETH_BASE_LR = {
85
85
  type: SummerVaultType.SummerVaultETHBaseLR,
@@ -88,7 +88,7 @@ export const SUMMER_VAULT_ETH_BASE_LR = {
88
88
  asset: 'WETH',
89
89
  network: NetworkNumber.Base,
90
90
  deploymentBlock: 29066887,
91
- isLegacy: false,
91
+ isLegacy: true,
92
92
  };
93
93
  export const SUMMER_VAULTS = {
94
94
  [SummerVaultType.SummerVaultUSDCMainnetLR]: SUMMER_VAULT_USDC_MAINNET_LR,
@@ -68,6 +68,7 @@ export const STAKING_ASSETS = [
68
68
  'PT sUSDe Nov', 'PT USDe Nov', 'PT USDe Jan', 'PT sUSDe Jan', 'wrsETH', 'wstETH', 'syrupUSDT', 'syrupUSDC', 'wstUSR',
69
69
  'PT sUSDe Feb', 'PT USDe Feb', 'PT sUSDe Apr', 'PT USDe Apr', 'PT sUSDe May', 'PT USDe May', 'PT srUSDe Apr', 'GHO',
70
70
  'PT sUSDe June', 'PT USDe June', 'PT srUSDe Jun', 'PT srUSDe Oct', 'PT USDG May', 'PT sUSDS Nov', 'PT sUSDe Oct', 'PT USDG Sep',
71
+ 'syrupUSDG',
71
72
  ];
72
73
  export const getStakingApy = memoize((asset_1, ...args_1) => __awaiter(void 0, [asset_1, ...args_1], void 0, function* (asset, network = NetworkNumber.Eth) {
73
74
  try {
@@ -125,6 +126,8 @@ export const getStakingApy = memoize((asset_1, ...args_1) => __awaiter(void 0, [
125
126
  return yield getApyFromDfsApi('syrupUSDT');
126
127
  if (asset === 'syrupUSDC')
127
128
  return yield getApyFromDfsApi('syrupUSDC');
129
+ if (asset === 'syrupUSDG')
130
+ return yield getApyFromDfsApi('syrupUSDG');
128
131
  if (asset === 'wstUSR')
129
132
  return yield getApyFromDfsApi('wstUSR');
130
133
  if (asset === 'PT sUSDe Feb')
@@ -16,7 +16,8 @@ export declare enum AaveV4SpokesType {
16
16
  AaveV4LidoSpoke = "aave_v4_lido_spoke",
17
17
  AaveV4LombardBtcSpoke = "aave_v4_lombard_btc_spoke",
18
18
  AaveV4MainSpoke = "aave_v4_main_spoke",
19
- AaveV4USDGPendleSpoke = "aave_v4_usdg_pendle_spoke"
19
+ AaveV4USDGPendleSpoke = "aave_v4_usdg_pendle_spoke",
20
+ AaveV4USDGMapleSpoke = "aave_v4_usdg_maple_spoke"
20
21
  }
21
22
  export interface AaveV4HubInfo {
22
23
  chainIds: NetworkNumber[];
@@ -18,4 +18,5 @@ export var AaveV4SpokesType;
18
18
  AaveV4SpokesType["AaveV4LombardBtcSpoke"] = "aave_v4_lombard_btc_spoke";
19
19
  AaveV4SpokesType["AaveV4MainSpoke"] = "aave_v4_main_spoke";
20
20
  AaveV4SpokesType["AaveV4USDGPendleSpoke"] = "aave_v4_usdg_pendle_spoke";
21
+ AaveV4SpokesType["AaveV4USDGMapleSpoke"] = "aave_v4_usdg_maple_spoke";
21
22
  })(AaveV4SpokesType || (AaveV4SpokesType = {}));
@@ -14,6 +14,8 @@ export declare enum ClaimType {
14
14
  SPARK_AIRDROP = "SPARK_AIRDROP",
15
15
  /** Spark Airdrop */
16
16
  SPARK_WST_ETH_REWARDS = "SPARK_WST_ETH_REWARDS",// TODO: This will be removed once we fully refactor spark rewards
17
+ /** UNI Airdrop */
18
+ UNI_REWARDS = "UNI_REWARDS",
17
19
  ETHENA_AIRDROP = "ETHENA_AIRDROP"
18
20
  }
19
21
  type _ClaimableTokenPartial = {
@@ -81,5 +83,13 @@ export type SparkWstEthRewardsClaimableToken = _ClaimableTokenPartial & {
81
83
  export type EthenaAirdropClaimableToken = _ClaimableTokenPartial & {
82
84
  claimType: ClaimType.ETHENA_AIRDROP;
83
85
  };
86
+ export type UniswapAirdropClaimableToken = _ClaimableTokenPartial & {
87
+ claimType: ClaimType.UNI_REWARDS;
88
+ additionalClaimFields: {
89
+ index: number;
90
+ isClaimed: boolean;
91
+ proof: string[];
92
+ };
93
+ };
84
94
  export type ClaimableToken = AaveRewardsClaimableToken | AaveMeritRewardsClaimableToken | CompoundV3CompClaimableToken | SparkRewardsClaimableToken | KingRewardsClaimableToken | SparkAirdropClaimableToken | SparkWstEthRewardsClaimableToken | EthenaAirdropClaimableToken;
85
95
  export {};
@@ -14,6 +14,8 @@ export var ClaimType;
14
14
  ClaimType["SPARK_AIRDROP"] = "SPARK_AIRDROP";
15
15
  /** Spark Airdrop */
16
16
  ClaimType["SPARK_WST_ETH_REWARDS"] = "SPARK_WST_ETH_REWARDS";
17
+ /** UNI Airdrop */
18
+ ClaimType["UNI_REWARDS"] = "UNI_REWARDS";
17
19
  ClaimType["ETHENA_AIRDROP"] = "ETHENA_AIRDROP";
18
20
  })(ClaimType || (ClaimType = {}));
19
21
  export var SparkAirdropType;
@@ -212,6 +212,15 @@ export interface MorphoBluePositionData {
212
212
  borrowShares: string;
213
213
  exposure: string;
214
214
  }
215
+ export interface MorphoBlueEarnData {
216
+ apy: string;
217
+ amount: string;
218
+ amountUsd: string;
219
+ }
220
+ export interface MorphoBlueMarketRewards {
221
+ supplyApy: string;
222
+ borrowApy: string;
223
+ }
215
224
  export interface MorphoBlueVault {
216
225
  address: string;
217
226
  }
@@ -86,6 +86,9 @@ export interface MorphoMidnightPositionData extends MorphoMidnightAggregatedPosi
86
86
  usedAssets: MMUsedAssets;
87
87
  credit: string;
88
88
  debt: string;
89
+ borrowRate: string;
90
+ debtBase: string;
91
+ debtInterest: string;
89
92
  maturity: number;
90
93
  isMatured: boolean;
91
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/positions-sdk",
3
- "version": "2.1.127-midnight-dev",
3
+ "version": "2.1.127-midnight-2-dev",
4
4
  "description": "",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -21,7 +21,7 @@
21
21
  "author": "",
22
22
  "license": "ISC",
23
23
  "dependencies": {
24
- "@defisaver/tokens": "^1.7.41",
24
+ "@defisaver/tokens": "^1.7.44",
25
25
  "@types/lodash": "^4.17.15",
26
26
  "@types/memoizee": "^0.4.12",
27
27
  "decimal.js": "^10.6.0",
@@ -84,9 +84,21 @@ export async function _getAaveV3MarketData(provider: Client, network: NetworkNum
84
84
  const aaveIncentivesContract = AaveIncentiveDataProviderV3ContractViem(provider, network);
85
85
  const marketAddress = market.providerAddress;
86
86
  const networksWithIncentives = [NetworkNumber.Eth, NetworkNumber.Arb, NetworkNumber.Opt, NetworkNumber.Linea, NetworkNumber.Plasma];
87
+ // Limit each view call to 10 tokens, run chunks concurrently, then combine their results into loanInfo.
88
+ const addressesPerRequest = 10;
89
+ const addressChunks: EthAddress[][] = _addresses.length > addressesPerRequest
90
+ ? Array.from(
91
+ { length: Math.ceil(_addresses.length / addressesPerRequest) },
92
+ (_, index) => _addresses.slice(index * addressesPerRequest, (index + 1) * addressesPerRequest) as EthAddress[],
93
+ )
94
+ : [_addresses as EthAddress[]];
95
+ const loanInfoPromise = Promise.all(addressChunks.map((addresses) => loanInfoContract.read.getFullTokensInfo(
96
+ [marketAddress, addresses],
97
+ setViemBlockNumber(blockNumber),
98
+ ))).then((loanInfoChunks) => loanInfoChunks.flat());
87
99
  // eslint-disable-next-line prefer-const
88
100
  let [loanInfo, eModesInfo, rewardInfo, merkleRewardsMap, meritRewardsMap] = await Promise.all([
89
- loanInfoContract.read.getFullTokensInfo([marketAddress, _addresses as EthAddress[]], setViemBlockNumber(blockNumber)),
101
+ loanInfoPromise,
90
102
  loanInfoContract.read.getAllEmodes([marketAddress], setViemBlockNumber(blockNumber)),
91
103
  networksWithIncentives.includes(network) ? aaveIncentivesContract.read.getReservesIncentivesData([marketAddress], setViemBlockNumber(blockNumber)) : null,
92
104
  getMerkleCampaigns(network),
@@ -49,9 +49,9 @@ export const AAVE_V4_TOKENIZED_SPOKES: Record<string, EthAddress> = {
49
49
  USDC_PRIME: '0x486415fb1F8b062c89ED548f871cf64304AACb31',
50
50
  USDT_PRIME: '0x46c588DD8453aC259c1f6a54b4C9A93C2aC3762D',
51
51
 
52
- USDC_PAXOS: '0x4131E0B2E7AFeCEAf3d3b4225aA61a3B2B7535b8',
53
- USDT_PAXOS: '0x8Dabe53E8cB991c57f0307F6f419E6D469b0deAA',
54
- PT_USDG_Sep_PAXOS: '0x27eF1140364948A0E30E248297FfDFE5a4091ec4',
52
+ USDC_PAXOS: '0xaed7c529bD2878170B61C758DfAa215AC7a4FD07',
53
+ USDT_PAXOS: '0xa0e97e45C2f89003730E467Bd484fA3eEcE5B4Cf',
54
+ PT_USDG_Sep_PAXOS: '0x7Df10B4A01350D2A1d95cFbE7c9207d7210A2663',
55
55
  };
56
56
 
57
57
  export const AAVE_V4_TOKENIZED_SPOKE_ADDRESSES: Partial<Record<NetworkNumber, EthAddress[]>> = {
@@ -2,10 +2,12 @@ import * as aaveV3Claim from './aaveV3';
2
2
  import * as compV3Claim from './compV3';
3
3
  import * as kingV3Claim from './king';
4
4
  import * as sparkClaim from './spark';
5
+ import * as uniswapClaim from './uniswap';
5
6
 
6
7
  export {
7
8
  aaveV3Claim,
8
9
  compV3Claim,
9
10
  kingV3Claim,
10
11
  sparkClaim,
12
+ uniswapClaim,
11
13
  };
@@ -0,0 +1,70 @@
1
+ import Dec from 'decimal.js';
2
+ import { Client } from 'viem';
3
+ import { assetAmountInEth } from '@defisaver/tokens';
4
+ import { EthAddress, NetworkNumber } from '../types';
5
+ import { UniswapTokenDistributorViem } from '../contracts';
6
+ import { ClaimType, UniswapAirdropClaimableToken } from '../types/claiming';
7
+
8
+ const EMPTY_DATA = (walletAddress: EthAddress) => ({
9
+ address: walletAddress, index: 0, amount: '0x0', proof: [],
10
+ });
11
+
12
+ export const fetchUniswapRewardsData = async (walletAddress: EthAddress) => {
13
+ try {
14
+ const res = await fetch(`https://fe.defisaver.com/api/rewards/uniswap?user=${walletAddress}`,
15
+ { signal: AbortSignal.timeout(5000) });
16
+
17
+ if (!res.ok) throw new Error(await res.text());
18
+
19
+ const data = await res.json();
20
+ if (data.data.error) return EMPTY_DATA(walletAddress);
21
+
22
+ return data.data;
23
+ } catch (err) {
24
+ return EMPTY_DATA(walletAddress);
25
+ }
26
+ };
27
+
28
+ export const getUniswapRewards = async (provider: Client, network: NetworkNumber, walletAddresses: EthAddress[]): Promise<Record<string, UniswapAirdropClaimableToken[]>> => {
29
+ // Fetch all API data in parallel (these are external API calls, can't be batched with multicall)
30
+ const apiDataPromises = walletAddresses.map(address => fetchUniswapRewardsData(address));
31
+ const apiDataArray = await Promise.all(apiDataPromises);
32
+
33
+ // Batch all contract calls using multicall
34
+ const contract = UniswapTokenDistributorViem(provider, network);
35
+ const cumulativePromises = apiDataArray.map(data => (data.index ? contract.read.isClaimed([data.index]) : Promise.resolve(false)),
36
+ );
37
+ const cumulativeResults = await Promise.all(cumulativePromises);
38
+
39
+ // Process results
40
+ const results: Record<string, UniswapAirdropClaimableToken[]> = {};
41
+
42
+ for (let i = 0; i < walletAddresses.length; i++) {
43
+ const walletAddress = walletAddresses[i];
44
+ const data = apiDataArray[i];
45
+ const cumulative = cumulativeResults[i];
46
+
47
+ const amountToClaim = new Dec(data.amount);
48
+
49
+ if (amountToClaim.lessThanOrEqualTo('0') || cumulative) {
50
+ results[walletAddress.toLowerCase() as EthAddress] = [];
51
+ } else {
52
+ results[walletAddress.toLowerCase() as EthAddress] = [{
53
+ symbol: 'UNI',
54
+ underlyingSymbol: 'UNI',
55
+ label: 'Uniswap Airdrop',
56
+ tokenAddress: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
57
+ amount: assetAmountInEth(amountToClaim.toString(), 'UNI'),
58
+ walletAddress,
59
+ claimType: ClaimType.UNI_REWARDS,
60
+ additionalClaimFields: {
61
+ index: data.index,
62
+ isClaimed: cumulative,
63
+ proof: data.proof,
64
+ },
65
+ }];
66
+ }
67
+ }
68
+
69
+ return results;
70
+ };
@@ -1360,3 +1360,12 @@ export const AaveV4View = {
1360
1360
  }
1361
1361
  }
1362
1362
  } as const;
1363
+
1364
+ export const UniswapTokenDistributor = {
1365
+ "abi": [{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"isClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],
1366
+ "networks": {
1367
+ "1": {
1368
+ "address": "0x090D4613473dEE047c3f2706764f49E0821D256e",
1369
+ }
1370
+ }
1371
+ } as const;
package/src/contracts.ts CHANGED
@@ -182,3 +182,5 @@ export const MakerDsrContractViem = createViemContractFromConfigFunc('MakerDsr')
182
182
  export const SkySavingsContractView = createViemContractFromConfigFunc('SkySavings');
183
183
 
184
184
  export const AaveV4ViewContractViem = createViemContractFromConfigFunc('AaveV4View');
185
+
186
+ export const UniswapTokenDistributorViem = createViemContractFromConfigFunc('UniswapTokenDistributor');
@@ -24,7 +24,7 @@ const getAndFormatBands = async (provider: Client, network: NetworkNumber, selec
24
24
  // getBandsData uses a lot of gas to get all of the bands at once, so we use pagination and fetch 200 bands at a time
25
25
  let i = minBand;
26
26
  while (i < maxBand) {
27
- i += 200;
27
+ i += 20;
28
28
  if (i > maxBand) {
29
29
  pivots.push(maxBand);
30
30
  } else {
@@ -389,6 +389,7 @@ export const getRewardsForMarket = async (marketId: string, network: NetworkNumb
389
389
  query: REWARDS_QUERY,
390
390
  variables: { marketId, chainId: network },
391
391
  }),
392
+ signal: AbortSignal.timeout(LONGER_TIMEOUT),
392
393
  });
393
394
 
394
395
  const data = await response.json();
@@ -1,4 +1,5 @@
1
1
  import Dec from 'decimal.js';
2
+ import { assetAmountInEth } from '@defisaver/tokens';
2
3
  import {
3
4
  calcLeverageLiqPrice, getAssetsTotal, getExposure, isLeveragedPos,
4
5
  } from '../../moneymarket';
@@ -7,6 +8,8 @@ import {
7
8
  LeverageType, MMAssetsData, MMUsedAsset, MMUsedAssets,
8
9
  } from '../../types/common';
9
10
  import { MorphoMidnightAggregatedPositionData, MorphoMidnightAssetsData, MorphoMidnightMarketInfo } from '../../types';
11
+ import { SECONDS_PER_DAY, WAD } from '../../constants';
12
+ import { LONGER_TIMEOUT } from '../../services/utils';
10
13
 
11
14
  /**
12
15
  * Aggregate a Morpho Midnight position. Midnight markets are multi-collateral, so the borrow limit is
@@ -84,3 +87,206 @@ export const getMorphoMidnightAggregatedPositionData = ({
84
87
 
85
88
  return payload;
86
89
  };
90
+
91
+ // ── Off-chain order-book rate helpers ──────────────────────────────────────────────────────────────
92
+ // notion: https://app.notion.com/p/defisaver/Estimate-borrow-rate-and-slippage-before-execution-3a70be682adc80c783c8c11fdb761dd2
93
+ // the borrow rate is not exposed on-chain (MidnightView only stores total debt at maturity in `units`).
94
+ // derive the rate + interest from Morpho's public keyless Midnight API
95
+ // Quote prices are WAD-scaled
96
+ // loan-per-unit ratios (< 1 for a discounted fixed-term borrow); annualizing them yields the borrow APY.
97
+
98
+ const MIDNIGHT_API_BASE = 'https://api.morpho.org/v0/midnight';
99
+ const nowInSeconds = () => Math.floor(Date.now() / 1000);
100
+
101
+ // The quote endpoint's `slippage` query param is validated as a string: 0.1–100, at most one decimal
102
+ // place (`0.50` is rejected even though `0.5` passes). See `midnightSlippageParam`.
103
+ const MIDNIGHT_SLIPPAGE_MIN = 0.1;
104
+ const MIDNIGHT_SLIPPAGE_MAX = 100;
105
+
106
+ interface MidnightTransaction {
107
+ event_type: string,
108
+ market_id: string,
109
+ created_at: number,
110
+ data: { seller_assets?: string, units?: string },
111
+ }
112
+
113
+ interface MidnightApiError {
114
+ code?: string,
115
+ message?: string,
116
+ details?: ({ field?: string, issue?: string })[] | null,
117
+ }
118
+
119
+ interface MidnightQuoteResponse {
120
+ average_best_price?: string,
121
+ average_worst_price?: string,
122
+ available_assets?: string,
123
+ available_units?: string,
124
+ takeable_offers?: unknown[],
125
+ }
126
+
127
+ export interface MorphoMidnightBorrowInfo {
128
+ borrowRate: string, // weighted-average borrow APY as a percent
129
+ debtBase: string, // base borrowed (Σ seller_assets), loan-token units
130
+ debtInterest: string, // debtTotal − debtBase (interest owed at maturity), loan-token units
131
+ debtTotal: string, // Σ units = on-chain debt at maturity, loan-token units
132
+ }
133
+
134
+ export interface MorphoMidnightBorrowQuote {
135
+ bestPrice: string, // average_best_price, loan-per-unit
136
+ worstPrice: string, // average_worst_price, slippage-adjusted
137
+ estBorrowRate: string, // estimated borrow APY as a percent
138
+ maxRate: string, // borrow APY the on-chain cap permits, i.e. `maxUnits` annualized (display only)
139
+ newUnits: string, // debt added at best price, raw loan-token base units
140
+ maxUnits: string, // capped debt (on-chain cap), raw loan-token base units
141
+ availableAssets: string,
142
+ availableUnits: string,
143
+ takeableOffers: any[], // opaque orderbook offers, forwarded verbatim to on-chain execution
144
+ }
145
+
146
+ // Days remaining until maturity, optionally measured at a past timestamp (for historical fills).
147
+ export const midnightTimeToMaturityDays = (maturity: number, atSeconds: number = nowInSeconds()): number => new Dec(maturity).sub(atSeconds).div(SECONDS_PER_DAY).toNumber();
148
+
149
+ // Annualize a fixed-term discount price into an APY percent: (1 / price)^(365 / ttmDays) − 1.
150
+ // `price` is loan-per-unit (assets received / units owed), so 1/price ≥ 1.
151
+ export const midnightApyFromPrice = (price: Dec.Value, ttmDays: Dec.Value): string => {
152
+ const p = new Dec(price);
153
+ const ttm = new Dec(ttmDays);
154
+ if (p.lte(0) || ttm.lte(0)) return '0';
155
+ return new Dec(1).div(p).pow(new Dec(365).div(ttm)).sub(1)
156
+ .mul(100)
157
+ .toString();
158
+ };
159
+
160
+ /**
161
+ * Inverse of `midnightApyFromPrice`: the loan-per-unit price a borrow APY implies,
162
+ * price = (1 + rate)^(−ttmDays / 365).
163
+ *
164
+ * This is what turns an absolute rate ceiling into an on-chain `maxUnits` cap (units = assets / price),
165
+ * and equally the principal a unit of borrow power is worth — Midnight debt is recorded at its maturity
166
+ * face value, so borrowing the full limit as principal would overshoot it by the interest.
167
+ */
168
+ export const midnightPriceFromApy = (ratePercent: Dec.Value, ttmDays: Dec.Value): string => {
169
+ const rate = new Dec(ratePercent);
170
+ const ttm = new Dec(ttmDays);
171
+ if (rate.lte(0) || ttm.lte(0)) return '1';
172
+ return new Dec(1).div(new Dec(1).add(rate.div(100)).pow(ttm.div(365))).toString();
173
+ };
174
+
175
+ /**
176
+ * Coerce a slippage into what the quote endpoint accepts: 0.1–100 with at most one decimal place. The
177
+ * validation is lexical, so a computed value (`4.15066671050631467`) is rejected outright — without this
178
+ * the request 400s and the quote looks unavailable.
179
+ *
180
+ * Rounded **down**, since a wider slippage is a looser cap than the caller asked for.
181
+ */
182
+ export const midnightSlippageParam = (slippagePercent: Dec.Value): string => Dec.min(
183
+ Dec.max(new Dec(slippagePercent), MIDNIGHT_SLIPPAGE_MIN),
184
+ MIDNIGHT_SLIPPAGE_MAX,
185
+ ).toDP(1, Dec.ROUND_DOWN).toString();
186
+
187
+ /**
188
+ * Current borrower rate + debt breakdown from the Midnight transactions API. On-chain we can only read the
189
+ * total debt at maturity (`units`); the base-vs-interest split and the effective borrow rate require the
190
+ * fill history. Per fill the rate is (units / seller_assets)^(365 / ttmAtFill) − 1, weighted by base amount.
191
+ * The caller swallows errors — a missing rate must never block position rendering.
192
+ */
193
+ export const getMorphoMidnightUserBorrowInfo = async (
194
+ account: string,
195
+ marketId: string,
196
+ maturity: number,
197
+ loanTokenSymbol: string,
198
+ ): Promise<MorphoMidnightBorrowInfo> => {
199
+ const res = await fetch(`${MIDNIGHT_API_BASE}/users/${account}/transactions`, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
200
+ const json: { data?: MidnightTransaction[] } = await res.json();
201
+ const borrows = (json?.data || []).filter((t) => t.event_type === 'borrow' && t.market_id?.toLowerCase() === marketId.toLowerCase());
202
+
203
+ let sumSeller = new Dec(0); // Σ seller_assets (base), raw
204
+ let sumUnits = new Dec(0); // Σ units (debt at maturity), raw
205
+ let weightedApy = new Dec(0); // Σ seller_assets × APYᵢ
206
+
207
+ borrows.forEach((t) => {
208
+ const sellerAssets = new Dec(t.data?.seller_assets || 0);
209
+ const units = new Dec(t.data?.units || 0);
210
+ if (sellerAssets.lte(0) || units.lte(0)) return;
211
+ const ttmDays = midnightTimeToMaturityDays(maturity, t.created_at);
212
+ const apy = midnightApyFromPrice(sellerAssets.div(units), ttmDays); // price = seller_assets / units
213
+ sumSeller = sumSeller.add(sellerAssets);
214
+ sumUnits = sumUnits.add(units);
215
+ weightedApy = weightedApy.add(sellerAssets.mul(apy));
216
+ });
217
+
218
+ const borrowRate = sumSeller.lte(0) ? '0' : weightedApy.div(sumSeller).toString();
219
+ const debtBase = assetAmountInEth(sumSeller.toFixed(0), loanTokenSymbol);
220
+ const debtTotal = assetAmountInEth(sumUnits.toFixed(0), loanTokenSymbol);
221
+ const debtInterest = Dec.max(new Dec(debtTotal).sub(debtBase), 0).toString();
222
+
223
+ return {
224
+ borrowRate, debtBase, debtInterest, debtTotal,
225
+ };
226
+ };
227
+
228
+ // The API says why a quote failed — NOT_FOUND (market matured or not open yet), INSUFFICIENT_LIQUIDITY
229
+ // (book can't fill the size), VALIDATION_ERROR (bad param, with the offending field in `details`).
230
+ // Callers surface this to the user, so keep the reason rather than collapsing everything into one string.
231
+ const midnightQuoteError = (error?: MidnightApiError): string => {
232
+ const detail = (error?.details || []).map(({ issue }) => issue).filter(Boolean).join('; ');
233
+ const reason = detail || error?.message || error?.code;
234
+ return reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable';
235
+ };
236
+
237
+ /**
238
+ * Quote a prospective borrow against the Midnight order book: the estimated rate, the debt units it adds,
239
+ * and the `maxUnits` cap sent on-chain to protect the user if better offers get filled first. `assetsRaw`
240
+ * (and the returned `newUnits`/`maxUnits`) are raw loan-token base units — callers convert to/from human
241
+ * amounts. Throws if the book can't fill the amount (caller handles).
242
+ *
243
+ * Two ways to set the cap:
244
+ * - `maxBorrowRate` — an absolute APY ceiling, honoured **exactly**: the cap price is derived locally via
245
+ * `midnightPriceFromApy`. Prefer this when a user pins a max rate.
246
+ * - otherwise `slippagePercent`, the API's own knob. Note it is a **price**-level slippage, not APY points:
247
+ * near maturity the annualisation factor (365 / ttmDays) multiplies it heavily, so on a 22-day market a
248
+ * slippage of 0.5 permitted an APY ~9pp above the estimate, not 0.5pp. It also saturates at the book's
249
+ * cheapest bid. `maxRate` therefore reports what the cap actually permits, derived from the cap price.
250
+ *
251
+ * A `maxBorrowRate` below `estBorrowRate` yields `maxUnits < newUnits` — the borrow would revert on-chain.
252
+ * Compare the two before submitting and tell the user their ceiling is under the market rate.
253
+ */
254
+ export const getMorphoMidnightBorrowQuote = async (
255
+ marketId: string,
256
+ assetsRaw: string,
257
+ slippagePercent: Dec.Value,
258
+ maturity: number,
259
+ maxBorrowRate?: Dec.Value,
260
+ ): Promise<MorphoMidnightBorrowQuote> => {
261
+ const url = `${MIDNIGHT_API_BASE}/books/${marketId}/bids/quote?assets=${assetsRaw}&slippage=${midnightSlippageParam(slippagePercent)}`;
262
+ const res = await fetch(url, { signal: AbortSignal.timeout(LONGER_TIMEOUT) });
263
+ const json: { data?: MidnightQuoteResponse, error?: MidnightApiError } = await res.json();
264
+ const d = json?.data;
265
+ if (!d?.average_best_price) throw new Error(midnightQuoteError(json?.error));
266
+
267
+ const bestPrice = new Dec(d.average_best_price).div(WAD).toString();
268
+ const worstPrice = new Dec(d.average_worst_price || 0).div(WAD).toString();
269
+ const ttmDays = midnightTimeToMaturityDays(maturity);
270
+ const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
271
+
272
+ // Price the cap sits at, and the rate that price represents — one derivation, so `maxRate` and
273
+ // `maxUnits` can never disagree about what the user is protected at.
274
+ const capPrice = maxBorrowRate !== undefined && new Dec(maxBorrowRate).gt(0)
275
+ ? midnightPriceFromApy(maxBorrowRate, ttmDays)
276
+ : worstPrice;
277
+ const maxRate = midnightApyFromPrice(capPrice, ttmDays);
278
+ const newUnits = new Dec(bestPrice).lte(0) ? '0' : new Dec(assetsRaw).div(bestPrice).toFixed(0);
279
+ const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
280
+
281
+ return {
282
+ bestPrice,
283
+ worstPrice,
284
+ estBorrowRate,
285
+ maxRate,
286
+ newUnits,
287
+ maxUnits,
288
+ availableAssets: d.available_assets || '0',
289
+ availableUnits: d.available_units || '0',
290
+ takeableOffers: d.takeable_offers || [],
291
+ };
292
+ };