@defisaver/ethena-sdk 0.0.1

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/.env.example ADDED
@@ -0,0 +1 @@
1
+ RPC=
package/.eslintrc.js ADDED
@@ -0,0 +1,34 @@
1
+
2
+ module.exports = {
3
+ extends: ['@defisaver/eslint-config/base-config'],
4
+ parserOptions: {
5
+ ecmaVersion: 2018,
6
+ sourceType: 'module',
7
+ },
8
+ env: {
9
+ es6: true,
10
+ browser: true,
11
+ mocha: true,
12
+ },
13
+ ignorePatterns: ['esm/', 'cjs/'],
14
+ overrides: [{
15
+ files: ['*.ts', '*.tsx'],
16
+ extends: ['@defisaver/eslint-config/base-config-typescript'],
17
+ parser: '@typescript-eslint/parser',
18
+ parserOptions: { project: ['./tsconfig.json'] },
19
+ }],
20
+ rules: {
21
+ 'max-len': 0,
22
+ },
23
+ settings: {
24
+ // 'import/resolver': {
25
+ // node: {
26
+ // extensions: ['.js', '.ts'],
27
+ // },
28
+ // typescript: {
29
+ // alwaysTryTypes: true,
30
+ // project: ['./tsconfig.json'],
31
+ // },
32
+ // },
33
+ },
34
+ };
package/.mocharc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "require": "ts-node/register",
3
+ "extension": ["ts"]
4
+ }
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v20.17.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeFi Saver
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@defisaver/ethena-sdk",
3
+ "version": "0.0.1",
4
+ "description": "SDK for ethena lev create",
5
+ "main": "./cjs/index.js",
6
+ "module": "./esm/index.js",
7
+ "types": "./esm/index.d.ts",
8
+ "scripts": {
9
+ "build:esm": "rm -rf esm && tsc -p tsconfig.esm.json",
10
+ "build:cjs": "rm -rf cjs && tsc -p tsconfig.cjs.json",
11
+ "build": "npm run lint && npm run build:cjs && npm run build:esm",
12
+ "dev": "tsc -p tsconfig.json --watch",
13
+ "lint": "eslint src/ --fix",
14
+ "lint-check": "eslint src/",
15
+ "test": "mocha tests/*"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/defisaver/ethena-sdk.git"
20
+ },
21
+ "author": "DeFi Saver",
22
+ "license": "MIT",
23
+ "bugs": {
24
+ "url": "https://github.com/defisaver/ethena-sdk/issues"
25
+ },
26
+ "homepage": "https://github.com/defisaver/ethena-sdk#readme",
27
+ "dependencies": {
28
+ "@defisaver/positions-sdk": "^2.1.57",
29
+ "@defisaver/tokens": "^1.7.22",
30
+ "@types/lodash": "^4.17.15",
31
+ "@types/memoizee": "^0.4.12",
32
+ "decimal.js": "^10.6.0",
33
+ "lodash": "^4.17.21",
34
+ "memoizee": "^0.4.17",
35
+ "viem": "^2.37.9"
36
+ },
37
+ "devDependencies": {
38
+ "@defisaver/eslint-config": "^1.0.1",
39
+ "@metamask/eth-json-rpc-middleware": "^15.0.1",
40
+ "@metamask/eth-json-rpc-provider": "^4.1.6",
41
+ "@types/chai": "^5.0.0",
42
+ "@types/mocha": "^10.0.9",
43
+ "chai": "^4.3.8",
44
+ "dotenv": "^16.3.1",
45
+ "eslint": "^8.49.0",
46
+ "eslint-plugin-import": "^2.31.0",
47
+ "mocha": "^10.2.0",
48
+ "nock": "^14.0.0",
49
+ "ts-node": "^10.9.2",
50
+ "typescript": "^5.2.2"
51
+ }
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ import './setup';
2
+
3
+ import * as positionData from './positionData';
4
+ import * as marketData from './marketData';
5
+
6
+ export * from './types';
7
+
8
+ export {
9
+ positionData,
10
+ marketData,
11
+ };
@@ -0,0 +1,25 @@
1
+ import { NetworkNumber } from '@defisaver/positions-sdk';
2
+ import { MarketData, PositionData, SupportedMarkets } from '../types';
3
+ import {
4
+ getMorphoMarketData, getMorphoNetApy,
5
+ } from './morpho';
6
+
7
+ export const getMarketData = async (market: SupportedMarkets, rpcUrl: string, network: NetworkNumber): Promise<MarketData> => {
8
+ switch (market) {
9
+ case SupportedMarkets.MorphoBlueSUSDeUSDtb_915: {
10
+ return getMorphoMarketData(market, rpcUrl, network);
11
+ }
12
+ default:
13
+ throw new Error(`Unsupported market: ${market}`);
14
+ }
15
+ };
16
+
17
+ export const getNetApy = (marketData: MarketData, leverage: number) => {
18
+ switch (marketData.market) {
19
+ case SupportedMarkets.MorphoBlueSUSDeUSDtb_915: {
20
+ return getMorphoNetApy(marketData, leverage);
21
+ }
22
+ default:
23
+ throw new Error(`Unsupported market: ${marketData.market}`);
24
+ }
25
+ };
@@ -0,0 +1,75 @@
1
+ import {
2
+ helpers, markets, morphoBlue, MorphoBlueVersions, NetworkNumber,
3
+ } from '@defisaver/positions-sdk';
4
+ import Dec from 'decimal.js';
5
+ import { assetAmountInEth } from '@defisaver/tokens';
6
+ import {
7
+ AssetData, MarketData, SupportedMarkets,
8
+ } from '../types';
9
+ import { getViemProvider } from '../services/viem';
10
+
11
+ const getMaxLeverage = (collateralFactor: string) => new Dec(1)
12
+ .div(new Dec(1).sub(collateralFactor))
13
+ .toDP(2).toString();
14
+
15
+ const scaleMaxLeverage = (leverage: number) => leverage * 0.9;
16
+
17
+ const getLevApy = (leverage: number, supplyAsset: AssetData, borrowAsset: AssetData) => {
18
+ const supplyRate = new Dec(supplyAsset.supplyRate).add(supplyAsset.supplyIncentives.reduce((acc, curr) => acc.add(curr.apy), new Dec(0)));
19
+ const borrowRate = new Dec(borrowAsset.borrowRate).add(borrowAsset.borrowIncentives.reduce((acc, curr) => acc.add(curr.apy), new Dec(0)));
20
+
21
+ return new Dec(leverage).mul(supplyRate)
22
+ .sub(new Dec(leverage).sub(1).mul(borrowRate))
23
+ .toDP(4)
24
+ .toString();
25
+ };
26
+
27
+ const formatAssetsData = (assetsData: Record<string, any>): Record<string, AssetData> => {
28
+ const formattedData: Record<string, any> = {};
29
+ Object.entries(assetsData).forEach(([assetSymbol, assetData]) => {
30
+ formattedData[assetSymbol] = {
31
+ symbol: assetData.symbol,
32
+ address: assetData.address,
33
+ supplyRate: assetData.supplyRate,
34
+ borrowRate: assetData.borrowRate,
35
+ supplyIncentives: assetData.supplyIncentives,
36
+ borrowIncentives: assetData.borrowIncentives,
37
+ isDebtAsset: assetData.canBeBorrowed,
38
+ };
39
+ });
40
+ return formattedData;
41
+ };
42
+
43
+ export const getMorphoMarketData = async (market: SupportedMarkets, rpcUrl: string, network: NetworkNumber): Promise<MarketData> => {
44
+ const provider = getViemProvider(rpcUrl, network);
45
+
46
+ const morphoMarket = markets.MorphoBlueMarkets(network)[MorphoBlueVersions.MorphoBlueSUSDeUSDtb_915];
47
+ const [marketData, { reallocatableLiquidity: availableLiquidityWei }] = await Promise.all([
48
+ morphoBlue._getMorphoBlueMarketData(provider, network, morphoMarket),
49
+ helpers.morphoBlueHelpers.getReallocatableLiquidity(morphoMarket.marketId, network),
50
+ ]);
51
+ const maxLeverageFactor = getMaxLeverage(marketData.lltv);
52
+ const scaledMaxLeverage = scaleMaxLeverage(parseFloat(maxLeverageFactor));
53
+ const assetsData = formatAssetsData(marketData.assetsData);
54
+
55
+ const availableLiquidity = assetAmountInEth(availableLiquidityWei, 'USDtb');
56
+ const leftToBorrowGlobal = new Dec(marketData.assetsData.USDtb.totalSupply || '0').sub(marketData.assetsData.USDtb.totalBorrow || '0').toString();
57
+ const leftToBorrowGlobalAdjusted = new Dec(availableLiquidity).add(leftToBorrowGlobal).toString();
58
+
59
+ return {
60
+ market,
61
+ maxLeverage: scaledMaxLeverage,
62
+ assetsData,
63
+ maxApy: getLevApy(scaledMaxLeverage, assetsData.sUSDe, assetsData.USDtb),
64
+ leftToBorrowGlobal: leftToBorrowGlobalAdjusted,
65
+ lltv: marketData.lltv,
66
+ rate: marketData.oracle,
67
+ };
68
+ };
69
+
70
+ export const getMorphoNetApy = (marketData: MarketData, leverage: number) => {
71
+ const supplyAsset = Object.values(marketData.assetsData).find((asset) => !asset.isDebtAsset);
72
+ const borrowAsset = Object.values(marketData.assetsData).find((asset) => asset.isDebtAsset);
73
+
74
+ return getLevApy(leverage, supplyAsset!, borrowAsset!);
75
+ };
@@ -0,0 +1,24 @@
1
+ import {
2
+ MarketData, NetworkNumber, PositionData, SupportedMarkets,
3
+ } from '../types';
4
+ import { getMorphoMaxLeverageForSupplyAmount, getMorphoResultingPosition } from './morpho';
5
+
6
+ export const getMaxLeverageForSupplyAmount = (marketData: MarketData, supplyAmount: string) => {
7
+ switch (marketData.market) {
8
+ case SupportedMarkets.MorphoBlueSUSDeUSDtb_915: {
9
+ return getMorphoMaxLeverageForSupplyAmount(marketData, supplyAmount);
10
+ }
11
+ default:
12
+ throw new Error(`Unsupported market: ${marketData.market}`);
13
+ }
14
+ };
15
+
16
+ export const getResultingPosition = async (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber): Promise<PositionData> => {
17
+ switch (marketData.market) {
18
+ case SupportedMarkets.MorphoBlueSUSDeUSDtb_915: {
19
+ return getMorphoResultingPosition(marketData, supplyAmount, leverage, rpcUrl, network);
20
+ }
21
+ default:
22
+ throw new Error(`Unsupported market: ${marketData.market}`);
23
+ }
24
+ };
@@ -0,0 +1,90 @@
1
+ import Dec from 'decimal.js';
2
+ import { getAssetInfo } from '@defisaver/tokens';
3
+ import {
4
+ helpers, markets, MMUsedAssets, morphoBlue, MorphoBlueVersions, NetworkNumber,
5
+ } from '@defisaver/positions-sdk';
6
+ import { AssetData, MarketData, PositionData } from '../types';
7
+ import { getViemProvider } from '../services/viem';
8
+
9
+ const getMaxBoostUsd = (lltv: string, borrowLimit: string, debt: string, targetRatio = 1.01, bufferPercent = 1) => new Dec(targetRatio).mul(debt).sub(borrowLimit)
10
+ .div(new Dec(lltv).sub(targetRatio).toString())
11
+ .mul((100 - bufferPercent) / 100)
12
+ .toString();
13
+
14
+ const getMorphoMaxLeverageBorrow = (marketData: MarketData, supplyAmount: string) => {
15
+ const {
16
+ lltv, rate: oracle, assetsData, leftToBorrowGlobal,
17
+ } = marketData;
18
+
19
+ const borrowAsset = Object.values(assetsData).find((asset) => asset.isDebtAsset);
20
+
21
+ const maxBorrow = new Dec(supplyAmount).mul(oracle).mul(lltv).toString();
22
+
23
+ const maxBoost = getMaxBoostUsd(lltv, maxBorrow, '0');
24
+
25
+ return Dec.min(Dec.max(0, maxBoost), leftToBorrowGlobal).toDP(getAssetInfo(borrowAsset?.symbol).decimals).toString();
26
+ };
27
+
28
+ export const getMorphoMaxLeverageForSupplyAmount = (marketData: MarketData, supplyAmount: string) => {
29
+ // TODO: implement FL logic
30
+ const feeMultiplier = 1;
31
+
32
+ const maxDebt = new Dec(getMorphoMaxLeverageBorrow(marketData, supplyAmount)).div(feeMultiplier).toString();
33
+
34
+ const rate = new Dec(1).div(marketData.rate).toString();
35
+
36
+ const maxLeverage = new Dec(supplyAmount).plus(new Dec(maxDebt).times(rate)).div(supplyAmount).toNumber();
37
+
38
+ return maxLeverage;
39
+ };
40
+
41
+ export const getMorphoResultingPosition = async (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber): Promise<PositionData> => {
42
+ const provider = getViemProvider(rpcUrl, network);
43
+
44
+ const morphoMarket = markets.MorphoBlueMarkets(network)[MorphoBlueVersions.MorphoBlueSUSDeUSDtb_915];
45
+ const {
46
+ rate: oracle, assetsData,
47
+ } = marketData;
48
+ const debtAmount = new Dec(leverage)
49
+ .times(supplyAmount).minus(supplyAmount).times(oracle)
50
+ .toString();
51
+
52
+ // TODO: add price fetching logic
53
+ const priceForAmount = new Dec(1).div(oracle).toString();
54
+ const leveragedAmount = new Dec(debtAmount).times(priceForAmount);
55
+ const collIncrease = new Dec(supplyAmount).plus(leveragedAmount).toString();
56
+
57
+ const supplyAsset: AssetData = Object.values(assetsData).find((asset) => !asset.isDebtAsset)!;
58
+ const borrowAsset: AssetData = Object.values(assetsData).find((asset) => asset.isDebtAsset)!;
59
+
60
+ const morphoMarketData = await morphoBlue._getMorphoBlueMarketData(provider, network, morphoMarket);
61
+ const usedAssets: MMUsedAssets = {};
62
+
63
+ usedAssets[borrowAsset.symbol] = {
64
+ symbol: borrowAsset.symbol,
65
+ supplied: '0',
66
+ borrowed: debtAmount,
67
+ isSupplied: false,
68
+ isBorrowed: true,
69
+ collateral: false,
70
+ suppliedUsd: '0',
71
+ borrowedUsd: new Dec(debtAmount).mul(morphoMarketData.assetsData[borrowAsset.symbol].price).toString(),
72
+ };
73
+
74
+ usedAssets[supplyAsset.symbol] = {
75
+ symbol: supplyAsset.symbol,
76
+ supplied: collIncrease,
77
+ borrowed: '0',
78
+ isSupplied: true,
79
+ isBorrowed: false,
80
+ collateral: true,
81
+ suppliedUsd: new Dec(collIncrease).mul(morphoMarketData.assetsData[supplyAsset.symbol].price).toString(),
82
+ borrowedUsd: '0',
83
+ };
84
+
85
+ const aggregatedPosition = helpers.morphoBlueHelpers.getMorphoBlueAggregatedPositionData({ usedAssets, assetsData: morphoMarketData.assetsData, marketInfo: morphoMarketData });
86
+ return {
87
+ usedAssets,
88
+ ...aggregatedPosition,
89
+ };
90
+ };
@@ -0,0 +1,33 @@
1
+ import { NetworkNumber } from '@defisaver/positions-sdk';
2
+ import {
3
+ createPublicClient,
4
+ http,
5
+ } from 'viem';
6
+ import {
7
+ arbitrum, base, mainnet, optimism, linea, plasma,
8
+ } from 'viem/chains';
9
+
10
+ export const getViemChain = (network: NetworkNumber) => {
11
+ switch (network) {
12
+ case NetworkNumber.Eth:
13
+ return mainnet;
14
+ case NetworkNumber.Opt:
15
+ return optimism;
16
+ case NetworkNumber.Arb:
17
+ return arbitrum;
18
+ case NetworkNumber.Base:
19
+ return base;
20
+ case NetworkNumber.Linea:
21
+ return linea;
22
+ case NetworkNumber.Plasma:
23
+ return plasma;
24
+ default:
25
+ throw new Error(`Unsupported network: ${network}`);
26
+ }
27
+ };
28
+
29
+ export const getViemProvider = (rpcUrl: string, network: NetworkNumber, options?: any) => createPublicClient({
30
+ transport: http(rpcUrl),
31
+ chain: getViemChain(network),
32
+ ...options,
33
+ });
package/src/setup.ts ADDED
@@ -0,0 +1,8 @@
1
+ import Decimal from 'decimal.js';
2
+
3
+ Decimal.set({
4
+ rounding: Decimal.ROUND_DOWN,
5
+ toExpPos: 9e15,
6
+ toExpNeg: -9e15,
7
+ precision: 50,
8
+ });
@@ -0,0 +1,34 @@
1
+ import {
2
+ IncentiveData, MMUsedAssets, MorphoBlueAggregatedPositionData, NetworkNumber,
3
+ } from '@defisaver/positions-sdk';
4
+ import { SupportedMarkets } from './markets';
5
+
6
+ export interface AssetData {
7
+ symbol: string;
8
+ address: string;
9
+ supplyRate: string;
10
+ borrowRate: string;
11
+ supplyIncentives: IncentiveData[];
12
+ borrowIncentives: IncentiveData[];
13
+ isDebtAsset: boolean;
14
+ }
15
+
16
+ export interface MarketData {
17
+ market: SupportedMarkets;
18
+ maxLeverage: number;
19
+ assetsData: Record<string, AssetData>;
20
+ maxApy: string;
21
+ leftToBorrowGlobal: string;
22
+ lltv: string;
23
+ rate: string;
24
+ }
25
+
26
+ export interface PositionData extends MorphoBlueAggregatedPositionData {
27
+ usedAssets: MMUsedAssets;
28
+ }
29
+
30
+ export {
31
+ NetworkNumber,
32
+ MMUsedAssets,
33
+ IncentiveData,
34
+ };
@@ -0,0 +1,2 @@
1
+ export * from './common';
2
+ export * from './markets';
@@ -0,0 +1,5 @@
1
+ import { MorphoBlueVersions } from '@defisaver/positions-sdk';
2
+
3
+ export enum SupportedMarkets {
4
+ MorphoBlueSUSDeUSDtb_915 = MorphoBlueVersions.MorphoBlueSUSDeUSDtb_915,
5
+ }
@@ -0,0 +1,51 @@
1
+ import 'dotenv/config';
2
+
3
+ import {
4
+ marketData, NetworkNumber, positionData, SupportedMarkets,
5
+ } from '../src';
6
+
7
+ const { assert } = require('chai');
8
+
9
+
10
+ describe('Tests', () => {
11
+ let rpcUrl: string;
12
+
13
+ before(async () => {
14
+ rpcUrl = process.env.RPC || '';
15
+ });
16
+
17
+ it('can fetch morpho sUSDe/USDtb market data', async function () {
18
+ this.timeout(10000);
19
+ const network = NetworkNumber.Eth;
20
+ const selectedMarket = SupportedMarkets.MorphoBlueSUSDeUSDtb_915;
21
+
22
+ const market = await marketData.getMarketData(selectedMarket, rpcUrl, network);
23
+ });
24
+
25
+ it('can fetch morpho sUSDe/USDtb apy for 4x exposure', async function () {
26
+ this.timeout(10000);
27
+ const network = NetworkNumber.Eth;
28
+ const selectedMarket = SupportedMarkets.MorphoBlueSUSDeUSDtb_915;
29
+
30
+ const market = await marketData.getMarketData(selectedMarket, rpcUrl, network);
31
+ const apy = marketData.getNetApy(market, 4);
32
+ });
33
+
34
+ it('can fetch morpho sUSDe/USDtb max exposure for 100000 supply', async function () {
35
+ this.timeout(10000);
36
+ const network = NetworkNumber.Eth;
37
+ const selectedMarket = SupportedMarkets.MorphoBlueSUSDeUSDtb_915;
38
+
39
+ const market = await marketData.getMarketData(selectedMarket, rpcUrl, network);
40
+ const maxLeverage = positionData.getMaxLeverageForSupplyAmount(market, '100000');
41
+ });
42
+
43
+ it('can fetch morpho sUSDe/USDtb resulting position for 100000 supply and 4x exposure', async function () {
44
+ this.timeout(10000);
45
+ const network = NetworkNumber.Eth;
46
+ const selectedMarket = SupportedMarkets.MorphoBlueSUSDeUSDtb_915;
47
+
48
+ const market = await marketData.getMarketData(selectedMarket, rpcUrl, network);
49
+ const position = await positionData.getResultingPosition(market, '100000', 4, rpcUrl, network);
50
+ });
51
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": [
4
+ "tests/**/*"
5
+ ]
6
+ }
7
+
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "outDir": "./esm/"
7
+ },
8
+ "exclude": [
9
+ "tests/**/*"
10
+ ]
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "node16",
4
+ "moduleResolution": "node16",
5
+ "outDir": "./cjs/",
6
+ "target": "ES2015",
7
+ "allowJs": true,
8
+ "declaration": true,
9
+ "strict": true,
10
+ "resolveJsonModule": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true
14
+ },
15
+ "include": [
16
+ "./src/*",
17
+ "./tests/**/*.ts"
18
+ ],
19
+ "typedocOptions": {
20
+ "entryPoints": [
21
+ "./src/index.ts"
22
+ ],
23
+ "out": "docs"
24
+ }
25
+ }