@dhedge/v2-sdk 2.1.4 → 2.1.6

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 (41) hide show
  1. package/README.md +180 -45
  2. package/dist/config.d.ts +10 -0
  3. package/dist/entities/pool.d.ts +110 -1
  4. package/dist/services/hyperliquid/constants.d.ts +16 -0
  5. package/dist/services/hyperliquid/index.d.ts +6 -0
  6. package/dist/services/hyperliquid/marketData.d.ts +12 -0
  7. package/dist/services/hyperliquid/positionData.d.ts +1 -0
  8. package/dist/services/odos/index.d.ts +15 -1
  9. package/dist/services/toros/limitOrder.d.ts +8 -0
  10. package/dist/test/constants.d.ts +7 -0
  11. package/dist/test/wallet.d.ts +1 -0
  12. package/dist/types.d.ts +12 -2
  13. package/dist/v2-sdk.cjs.development.js +4256 -1335
  14. package/dist/v2-sdk.cjs.development.js.map +1 -1
  15. package/dist/v2-sdk.cjs.production.min.js +1 -1
  16. package/dist/v2-sdk.cjs.production.min.js.map +1 -1
  17. package/dist/v2-sdk.esm.js +5007 -2087
  18. package/dist/v2-sdk.esm.js.map +1 -1
  19. package/package.json +3 -2
  20. package/src/abi/hyperliquid/ICoreDepositWallet.json +130 -0
  21. package/src/abi/hyperliquid/ICoreWriter.json +1 -0
  22. package/src/abi/odos/OdosRouterV3.json +1351 -0
  23. package/src/abi/toros/IPoolLimitOrderManager.json +78 -0
  24. package/src/config.ts +44 -13
  25. package/src/entities/pool.ts +348 -4
  26. package/src/services/hyperliquid/constants.ts +23 -0
  27. package/src/services/hyperliquid/index.ts +176 -0
  28. package/src/services/hyperliquid/marketData.ts +157 -0
  29. package/src/services/hyperliquid/positionData.ts +33 -0
  30. package/src/services/odos/index.ts +97 -13
  31. package/src/services/toros/completeWithdrawal.ts +1 -1
  32. package/src/services/toros/initWithdrawal.ts +1 -1
  33. package/src/services/toros/limitOrder.ts +86 -0
  34. package/src/services/toros/swapData.ts +83 -12
  35. package/src/test/constants.ts +10 -3
  36. package/src/test/hyperliquid.test.ts +107 -0
  37. package/src/test/odos.test.ts +43 -12
  38. package/src/test/pool.test.ts +37 -45
  39. package/src/test/torosLimitOrder.test.ts +130 -0
  40. package/src/test/wallet.ts +2 -1
  41. package/src/types.ts +13 -2
@@ -0,0 +1,176 @@
1
+ import { ethers } from "ethers";
2
+ import ICoreDepositWalletAbi from "../../abi/hyperliquid/ICoreDepositWallet.json";
3
+ import ICoreWriterAbi from "../../abi/hyperliquid/ICoreWriter.json";
4
+ import {
5
+ HYPERLIQUID_VERSION,
6
+ LIMIT_ORDER_ACTION,
7
+ LIMIT_ORDER_TIF_IOC,
8
+ SEND_ASSET_ACTION,
9
+ SPOT_DEX_ID,
10
+ SPOT_SEND_ACTION,
11
+ USDC_CORE_ADDRESS,
12
+ USDC_TOKEN_ID
13
+ } from "./constants";
14
+
15
+ import {
16
+ calculatePrice,
17
+ calculateSize,
18
+ getAssetInfo,
19
+ getMidPrice,
20
+ isSpotAsset,
21
+ scaleSize
22
+ } from "./marketData";
23
+ import { getPositionSize } from "./positionData";
24
+
25
+ const depositWallet = new ethers.utils.Interface(ICoreDepositWalletAbi);
26
+ const coreWriter = new ethers.utils.Interface(ICoreWriterAbi);
27
+
28
+ export const getDepositHyperliquidTxData = (
29
+ dexId: number,
30
+ amount: ethers.BigNumber | string
31
+ ): string => {
32
+ return depositWallet.encodeFunctionData("deposit", [amount, dexId]);
33
+ };
34
+
35
+ export const getWithdrawSpotHyperliquidTxData = (
36
+ amount: ethers.BigNumber | string
37
+ ): string => {
38
+ const coreAmount = ethers.BigNumber.from(amount).mul(100); //USDC on Core has two more decimals
39
+ //Hardcoded to USDC address and id on Hyperliquid Core
40
+ //From Spot to EVM
41
+ const innerEncoded = ethers.utils.defaultAbiCoder.encode(
42
+ //to, token, amount
43
+ ["address", "uint64", "uint64"],
44
+ [USDC_CORE_ADDRESS, USDC_TOKEN_ID, coreAmount]
45
+ );
46
+
47
+ const rawTXData = ethers.utils.solidityPack(
48
+ ["uint8", "uint24", "bytes"],
49
+ [HYPERLIQUID_VERSION, SPOT_SEND_ACTION, innerEncoded]
50
+ );
51
+ return coreWriter.encodeFunctionData("sendRawAction", [rawTXData]);
52
+ };
53
+ export const getPerpToSpotHyperliquidTxData = (
54
+ dexId: number,
55
+ receiver: string,
56
+ amount: ethers.BigNumber | string
57
+ ): string => {
58
+ const coreAmount = ethers.BigNumber.from(amount).mul(100); //USDC on Core has two more decimals
59
+ //From Perp to Spot
60
+ const innerEncoded = ethers.utils.defaultAbiCoder.encode(
61
+ //destination, subAccount, sourceDex, destinationDex, token, amount
62
+ ["address", "address", "uint32", "uint32", "uint64", "uint64"],
63
+ [
64
+ receiver,
65
+ ethers.constants.AddressZero,
66
+ dexId,
67
+ SPOT_DEX_ID,
68
+ USDC_TOKEN_ID,
69
+ coreAmount
70
+ ]
71
+ );
72
+
73
+ const rawTXData = ethers.utils.solidityPack(
74
+ ["uint8", "uint24", "bytes"],
75
+ [HYPERLIQUID_VERSION, SEND_ASSET_ACTION, innerEncoded]
76
+ );
77
+
78
+ return coreWriter.encodeFunctionData("sendRawAction", [rawTXData]);
79
+ };
80
+
81
+ export const getLimitOrderHyperliquidTxData = async (
82
+ assetId: number,
83
+ isLong: boolean,
84
+ changeAmount: number,
85
+ slippage: number
86
+ ): Promise<string> => {
87
+ let isBuy = isLong;
88
+ let reduceOnly = false;
89
+ if (changeAmount < 0) {
90
+ changeAmount = changeAmount * -1;
91
+ isBuy = !isLong;
92
+ reduceOnly = !isSpotAsset(assetId);
93
+ }
94
+
95
+ //Calculate price with slippage
96
+ const { assetName, szDecimals } = await getAssetInfo(assetId);
97
+ const midPrice = await getMidPrice(assetId, assetName);
98
+ const price = calculatePrice(
99
+ isSpotAsset(assetId),
100
+ szDecimals,
101
+ midPrice,
102
+ isBuy,
103
+ slippage
104
+ );
105
+ const size = calculateSize(szDecimals, changeAmount, midPrice);
106
+
107
+ const innerEncoded = ethers.utils.defaultAbiCoder.encode(
108
+ //assetIndex, isBuy, price, size, reduceOnly, tif, clientOrderId
109
+ ["uint32", "bool", "uint64", "uint64", "bool", "uint8", "uint128"],
110
+ [
111
+ assetId,
112
+ isBuy,
113
+ price,
114
+ size,
115
+ reduceOnly,
116
+ LIMIT_ORDER_TIF_IOC, // immediate or cancel
117
+ ethers.BigNumber.from(0) //client order id
118
+ ]
119
+ );
120
+
121
+ const rawTXData = ethers.utils.solidityPack(
122
+ ["uint8", "uint24", "bytes"],
123
+ [HYPERLIQUID_VERSION, LIMIT_ORDER_ACTION, innerEncoded]
124
+ );
125
+
126
+ return coreWriter.encodeFunctionData("sendRawAction", [rawTXData]);
127
+ };
128
+
129
+ export const getClosePositionHyperliquidTxData = async (
130
+ assetId: number,
131
+ percentageToClose: number,
132
+ slippage: number,
133
+ poolAddress: string
134
+ ): Promise<string> => {
135
+ const isSpot = isSpotAsset(assetId);
136
+ const { assetName, szDecimals, baseTokenName } = await getAssetInfo(assetId);
137
+ const positionSize = await getPositionSize(
138
+ assetId,
139
+ isSpot,
140
+ baseTokenName ?? assetName,
141
+ poolAddress
142
+ );
143
+ const isBuy = positionSize < 0; // if position size is negative, we need to buy to close, otherwise sell
144
+ const sizeRaw = scaleSize(szDecimals, positionSize, percentageToClose);
145
+
146
+ //Calculate price with slippage
147
+ const midPrice = await getMidPrice(assetId, assetName);
148
+ const price = calculatePrice(
149
+ isSpotAsset(assetId),
150
+ szDecimals,
151
+ midPrice,
152
+ isBuy,
153
+ slippage
154
+ );
155
+
156
+ const innerEncoded = ethers.utils.defaultAbiCoder.encode(
157
+ //assetIndex, isBuy, price, size, reduceOnly, tif, clientOrderId
158
+ ["uint32", "bool", "uint64", "uint64", "bool", "uint8", "uint128"],
159
+ [
160
+ assetId,
161
+ positionSize < 0, // if position size is negative, we need to buy to close, otherwise sell
162
+ price,
163
+ sizeRaw,
164
+ !isSpot,
165
+ LIMIT_ORDER_TIF_IOC, // immediate or cancel
166
+ ethers.BigNumber.from(0) //client order id
167
+ ]
168
+ );
169
+
170
+ const rawTXData = ethers.utils.solidityPack(
171
+ ["uint8", "uint24", "bytes"],
172
+ [HYPERLIQUID_VERSION, LIMIT_ORDER_ACTION, innerEncoded]
173
+ );
174
+
175
+ return coreWriter.encodeFunctionData("sendRawAction", [rawTXData]);
176
+ };
@@ -0,0 +1,157 @@
1
+ import axios from "axios";
2
+ import { API_URL, dexIdNameMap } from "./constants";
3
+ import { BigNumber } from "bignumber.js";
4
+ import { ApiError } from "../..";
5
+
6
+ export const perpDexIndex = (assetId: number): number => {
7
+ return Math.max(Math.floor((assetId - 100000) / 10000), 0);
8
+ };
9
+
10
+ const assetIndex = (assetId: number): number => {
11
+ if (assetId > 100000) {
12
+ //builder-deployed perps
13
+ return (assetId - 100000) % 10000;
14
+ } else return assetId;
15
+ };
16
+
17
+ export const spotAssetIndex = (assetId: number): number => {
18
+ return assetId - 10000;
19
+ };
20
+
21
+ export const isSpotAsset = (assetId: number): boolean => {
22
+ return assetId > 10000 && assetId < 100000;
23
+ };
24
+
25
+ export const getMidPrice = async (
26
+ assetId: number,
27
+ assetName: string
28
+ ): Promise<number> => {
29
+ const response = await axios.post(API_URL, {
30
+ type: "allMids",
31
+ dex: dexIdNameMap[perpDexIndex(assetId)]
32
+ });
33
+ const raw = response.data[assetName];
34
+ if (raw === undefined || raw === null) {
35
+ throw new ApiError(
36
+ `Hyperliquid allMids response missing price for asset "${assetName}"`
37
+ );
38
+ }
39
+ const price = +raw;
40
+ if (isNaN(price)) {
41
+ throw new ApiError(
42
+ `Hyperliquid allMids returned non-numeric price for asset "${assetName}": ${raw}`
43
+ );
44
+ }
45
+ return price;
46
+ };
47
+
48
+ export const getAssetInfo = async (
49
+ assetId: number
50
+ ): Promise<{
51
+ assetName: string;
52
+ szDecimals: number;
53
+ baseTokenName?: string;
54
+ }> => {
55
+ if (isSpotAsset(assetId)) {
56
+ const response = await axios.post(API_URL, {
57
+ type: "spotMeta"
58
+ });
59
+ const asset = response.data.universe.find(
60
+ (e: { index: number }) => e.index === spotAssetIndex(assetId)
61
+ );
62
+ if (!asset) {
63
+ throw new ApiError(
64
+ `Hyperliquid spotMeta response contains no asset for assetId ${assetId} (index ${spotAssetIndex(
65
+ assetId
66
+ )})`
67
+ );
68
+ }
69
+ const baseToken = response.data.tokens[asset.tokens[0]];
70
+ return {
71
+ assetName: asset.name,
72
+ szDecimals: baseToken.szDecimals,
73
+ baseTokenName: baseToken.name
74
+ };
75
+ } else {
76
+ const dex = dexIdNameMap[perpDexIndex(assetId)];
77
+ const response = await axios.post(API_URL, {
78
+ type: "metaAndAssetCtxs",
79
+ dex
80
+ });
81
+ if (!Array.isArray(response.data) || response.data.length === 0) {
82
+ throw new ApiError(
83
+ `Hyperliquid metaAndAssetCtxs response has invalid data for assetId ${assetId} (dex ${String(
84
+ dex
85
+ )})`
86
+ );
87
+ }
88
+ const meta = response.data[0];
89
+ if (!meta || !Array.isArray(meta.universe)) {
90
+ throw new ApiError(
91
+ `Hyperliquid metaAndAssetCtxs response contains no universe for assetId ${assetId} (dex ${String(
92
+ dex
93
+ )})`
94
+ );
95
+ }
96
+ const assets = meta.universe;
97
+ const index = assetIndex(assetId);
98
+ if (index < 0 || index >= assets.length) {
99
+ throw new ApiError(
100
+ `Hyperliquid metaAndAssetCtxs response contains no asset for assetId ${assetId} (dex ${String(
101
+ dex
102
+ )}, index ${index}, universe length ${assets.length})`
103
+ );
104
+ }
105
+ const asset = assets[index];
106
+ return {
107
+ assetName: asset.name,
108
+ szDecimals: asset.szDecimals
109
+ };
110
+ }
111
+ };
112
+
113
+ export const calculatePrice = (
114
+ isSpotAsset: boolean,
115
+ szDecimals: number,
116
+ midPrice: number,
117
+ isBuy: boolean,
118
+ slippage: number
119
+ ): string => {
120
+ // 1. Apply slippage
121
+ const price = midPrice * (isBuy ? 1 + slippage / 100 : 1 - slippage / 100);
122
+
123
+ // 2. Round to 5 significant figures
124
+ const roundedSignificant = parseFloat(price.toPrecision(5));
125
+
126
+ // 3. For perp base decimals = 6
127
+ const baseDecimals = isSpotAsset ? 8 : 6;
128
+ const finalDecimals = baseDecimals - szDecimals;
129
+ const factor = Math.pow(10, finalDecimals);
130
+ const roundedDecimals = Math.round(roundedSignificant * factor) / factor;
131
+ return new BigNumber(roundedDecimals).times(1e8).toFixed(0);
132
+ };
133
+
134
+ export const calculateSize = (
135
+ szDecimals: number,
136
+ value: number,
137
+ price: number
138
+ ): string => {
139
+ const factor = Math.pow(10, szDecimals);
140
+ return new BigNumber(Math.round((value / price) * factor) / factor)
141
+ .times(1e8)
142
+ .toFixed(0);
143
+ };
144
+
145
+ export const scaleSize = (
146
+ szDecimals: number,
147
+ positionSize: number,
148
+ percentageToClose: number
149
+ ): string => {
150
+ const factor = Math.pow(10, szDecimals);
151
+ return new BigNumber(
152
+ Math.round(((positionSize * percentageToClose) / 100) * factor) / factor
153
+ )
154
+ .times(1e8)
155
+ .abs()
156
+ .toFixed(0);
157
+ };
@@ -0,0 +1,33 @@
1
+ import axios from "axios";
2
+ import { API_URL, dexIdNameMap } from "./constants";
3
+ import { perpDexIndex } from "./marketData";
4
+
5
+ export const getPositionSize = async (
6
+ assetId: number,
7
+ isSpot: boolean,
8
+ assetName: string,
9
+ user: string
10
+ ): Promise<number> => {
11
+ if (isSpot) {
12
+ const response = await axios.post(API_URL, {
13
+ type: "spotClearinghouseState",
14
+ user
15
+ });
16
+ const balance = response.data.balances.find(
17
+ (e: { coin: string }) => e.coin === assetName
18
+ );
19
+ if (!balance) throw new Error(`No balance found for asset ${assetName}`);
20
+ return +balance.total;
21
+ } else {
22
+ const response = await axios.post(API_URL, {
23
+ type: "clearinghouseState",
24
+ user,
25
+ dex: dexIdNameMap[perpDexIndex(assetId)]
26
+ });
27
+ const position = response.data.assetPositions.find(
28
+ (e: { position: { coin: string } }) => e.position.coin === assetName
29
+ );
30
+ if (!position) throw new Error(`No position found for asset ${assetName}`);
31
+ return +position.position.szi;
32
+ }
33
+ };
@@ -1,10 +1,29 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import axios from "axios";
3
3
  import { ApiError, ethers } from "../..";
4
- import { networkChainIdMap } from "../../config";
4
+ import { networkChainIdMap, OdosSwapFeeRecipient } from "../../config";
5
5
  import { Pool } from "../../entities";
6
+ import OdosRouterV3Abi from "../../abi/odos/OdosRouterV3.json";
7
+ import BigNumber from "bignumber.js";
6
8
 
7
- export const odosBaseUrl = "https://api.odos.xyz/sor";
9
+ export const odosBaseUrl = "https://enterprise-api.odos.xyz/sor";
10
+
11
+ // Types for Odos Router V3 swap function parameters
12
+ export interface SwapTokenInfo {
13
+ inputToken: string;
14
+ inputAmount: ethers.BigNumber;
15
+ inputReceiver: string;
16
+ outputToken: string;
17
+ outputQuote: ethers.BigNumber;
18
+ outputMin: ethers.BigNumber;
19
+ outputReceiver: string;
20
+ }
21
+
22
+ export interface SwapReferralInfo {
23
+ code: ethers.BigNumber;
24
+ fee: ethers.BigNumber;
25
+ feeRecipient: string;
26
+ }
8
27
 
9
28
  export async function getOdosSwapTxData(
10
29
  pool: Pool,
@@ -13,13 +32,13 @@ export async function getOdosSwapTxData(
13
32
  amountIn: ethers.BigNumber | string,
14
33
  slippage: number
15
34
  ): Promise<{ swapTxData: string; minAmountOut: string }> {
16
- let referralCode = 0; // Defaults to 0 for unregistered activity.
17
- if (
18
- process.env.ODOS_REFERAL_CODE &&
19
- Number(process.env.ODOS_REFERAL_CODE) > 0
20
- ) {
21
- referralCode = Number(process.env.ODOS_REFERAL_CODE);
35
+ if (!process.env.ODOS_API_KEY) {
36
+ throw new Error("ODOS_API_KEY is not set");
22
37
  }
38
+ const ODOS_API_KEY = process.env.ODOS_API_KEY;
39
+
40
+ const referralFeeBips = 2; // 2 basis points = 0.02%
41
+
23
42
  const quoteParams = {
24
43
  chainId: networkChainIdMap[pool.network],
25
44
  inputTokens: [
@@ -36,12 +55,20 @@ export async function getOdosSwapTxData(
36
55
  ],
37
56
  slippageLimitPercent: slippage,
38
57
  userAddr: pool.address,
39
- referralCode
58
+ compact: false,
59
+ referralFeeRecipient: OdosSwapFeeRecipient[pool.network],
60
+ referralFee: referralFeeBips // 0.02% fee
40
61
  };
41
62
  try {
42
63
  const quoteResult = await axios.post(
43
- `${odosBaseUrl}/quote/v2`,
44
- quoteParams
64
+ `${odosBaseUrl}/quote/v3`,
65
+ quoteParams,
66
+ {
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ "x-api-key": ODOS_API_KEY
70
+ }
71
+ }
45
72
  );
46
73
 
47
74
  const assembleParams = {
@@ -51,10 +78,67 @@ export async function getOdosSwapTxData(
51
78
 
52
79
  const assembleResult = await axios.post(
53
80
  `${odosBaseUrl}/assemble`,
54
- assembleParams
81
+ assembleParams,
82
+ {
83
+ headers: {
84
+ "Content-Type": "application/json",
85
+ "x-api-key": ODOS_API_KEY
86
+ }
87
+ }
55
88
  );
89
+
90
+ const txData = assembleResult.data.transaction.data;
91
+
92
+ // Decode the transaction data
93
+ const iface = new ethers.utils.Interface(OdosRouterV3Abi.abi);
94
+ const decodedData = iface.parseTransaction({ data: txData });
95
+
96
+ const tokenInfo = decodedData.args[0] as SwapTokenInfo;
97
+ const pathDefinition = decodedData.args[1] as string;
98
+ const executor = decodedData.args[2] as string;
99
+ const referralInfo = decodedData.args[3] as SwapReferralInfo;
100
+
101
+ if (
102
+ referralInfo.fee.lte(
103
+ ethers.BigNumber.from((referralFeeBips * 1e18) / 10000)
104
+ )
105
+ ) {
106
+ // Referral fee is already correct, return original txData
107
+ return {
108
+ swapTxData: assembleResult.data.transaction.data,
109
+ minAmountOut: assembleResult.data.outputTokens[0].amount
110
+ };
111
+ }
112
+
113
+ const FEE_DENOM = new BigNumber(1e18);
114
+ const correctedFee = new BigNumber((referralFeeBips * 1e18) / 10000);
115
+ const factor = 1.1;
116
+ const correctedOutputQuote = new BigNumber(tokenInfo.outputQuote.toString())
117
+ .times(
118
+ FEE_DENOM.minus(correctedFee).div(
119
+ FEE_DENOM.minus(referralInfo.fee.toString())
120
+ )
121
+ )
122
+ .times(factor);
123
+
124
+ // example referralInfo.fee could be 0.0005 * 1e18 = 500000000000000, which is 0.05%
125
+ // Create corrected referral info
126
+ const correctedTxData = iface.encodeFunctionData(decodedData.name, [
127
+ {
128
+ ...tokenInfo,
129
+ outputQuote: correctedOutputQuote.toFixed(0)
130
+ },
131
+ pathDefinition,
132
+ executor,
133
+ {
134
+ code: referralInfo.code,
135
+ fee: correctedFee.toFixed(0), // align with referralFeeBips
136
+ feeRecipient: referralInfo.feeRecipient
137
+ }
138
+ ]);
139
+
56
140
  return {
57
- swapTxData: assembleResult.data.transaction.data,
141
+ swapTxData: correctedTxData,
58
142
  minAmountOut: assembleResult.data.outputTokens[0].amount
59
143
  };
60
144
  } catch (e) {
@@ -25,7 +25,7 @@ const getSwapWithdrawData = async (
25
25
  swapDestMinDestAmount: BigNumber
26
26
  ) => {
27
27
  const srcData = [];
28
- const routerKey = ethers.utils.formatBytes32String("ODOS_V2");
28
+ const routerKey = ethers.utils.formatBytes32String("ODOS_V3");
29
29
  // const destData
30
30
  for (const { token, balance } of trackedAssets) {
31
31
  if (token.toLowerCase() === receiveToken.toLowerCase()) {
@@ -46,7 +46,7 @@ const getAaveAssetWithdrawData = async (
46
46
  const { srcData, dstData } = swapDataParams;
47
47
 
48
48
  const srcDataToEncode: unknown[] = [];
49
- const routerKey = ethers.utils.formatBytes32String("ODOS_V2");
49
+ const routerKey = ethers.utils.formatBytes32String("ODOS_V3");
50
50
  for (const { asset, amount } of srcData) {
51
51
  const swapData = await retry({
52
52
  fn: () => {
@@ -0,0 +1,86 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { ethers } from "ethers";
3
+ import { Pool } from "../..";
4
+ import { limitOrderAddress } from "../../config";
5
+ import { LimitOrderInfo } from "../../types";
6
+ import IPoolLimitOrderManager from "../../abi/toros/IPoolLimitOrderManager.json";
7
+
8
+ const iface = new ethers.utils.Interface(IPoolLimitOrderManager);
9
+
10
+ export function getLimitOrderId(
11
+ userAddress: string,
12
+ vaultAddress: string
13
+ ): string {
14
+ return ethers.utils.solidityKeccak256(
15
+ ["address", "address"],
16
+ [userAddress, vaultAddress]
17
+ );
18
+ }
19
+
20
+ export function getCreateLimitOrderTxData(info: LimitOrderInfo): string {
21
+ return iface.encodeFunctionData("createLimitOrder", [
22
+ [
23
+ info.amount,
24
+ info.stopLossPriceD18,
25
+ info.takeProfitPriceD18,
26
+ info.user,
27
+ info.pool,
28
+ info.pricingAsset
29
+ ]
30
+ ]);
31
+ }
32
+
33
+ export function getModifyLimitOrderTxData(info: LimitOrderInfo): string {
34
+ return iface.encodeFunctionData("modifyLimitOrder", [
35
+ [
36
+ info.amount,
37
+ info.stopLossPriceD18,
38
+ info.takeProfitPriceD18,
39
+ info.user,
40
+ info.pool,
41
+ info.pricingAsset
42
+ ]
43
+ ]);
44
+ }
45
+
46
+ export function getDeleteLimitOrderTxData(vaultAddress: string): string {
47
+ return iface.encodeFunctionData("deleteLimitOrder", [vaultAddress]);
48
+ }
49
+
50
+ export async function getTorosLimitOrder(
51
+ pool: Pool,
52
+ userAddress: string,
53
+ vaultAddress: string
54
+ ): Promise<LimitOrderInfo | null> {
55
+ const managerAddress = limitOrderAddress[pool.network];
56
+ if (!managerAddress) return null;
57
+
58
+ const orderId = getLimitOrderId(userAddress, vaultAddress);
59
+ const contract = new ethers.Contract(
60
+ managerAddress,
61
+ IPoolLimitOrderManager,
62
+ pool.signer
63
+ );
64
+
65
+ const result = await contract.limitOrders(orderId);
66
+ // If amount is zero, the order doesn't exist
67
+ if (result.amount.isZero()) return null;
68
+
69
+ return {
70
+ amount: result.amount,
71
+ stopLossPriceD18: result.stopLossPriceD18,
72
+ takeProfitPriceD18: result.takeProfitPriceD18,
73
+ user: result.user,
74
+ pool: result.pool,
75
+ pricingAsset: result.pricingAsset
76
+ };
77
+ }
78
+
79
+ export async function hasActiveTorosLimitOrder(
80
+ pool: Pool,
81
+ userAddress: string,
82
+ vaultAddress: string
83
+ ): Promise<boolean> {
84
+ const order = await getTorosLimitOrder(pool, userAddress, vaultAddress);
85
+ return order !== null;
86
+ }