UNPKG

@parifi/synthetix-sdk-ts

Version:

A Typescript SDK for interactions with the Synthetix protocol

1 lines 190 kB
{"version":3,"sources":["../../src/perps/index.ts","../../src/utils/common.ts","../../src/constants/protocol.ts","../../src/constants/common.ts","../../src/contracts/addreses/zap.ts","../../src/utils/index.ts","../../src/contracts/addreses/oracleProxy.ts","../../src/utils/market.ts","../../src/utils/override.ts","../../src/constants/slots.ts","../../src/contracts/abis/zap.ts"],"sourcesContent":["import { encodeFunctionData, erc20Abi, formatEther, formatUnits, getAbiItem, Hex } from 'viem';\nimport { SynthetixSdk } from '..';\nimport {\n CollateralData,\n FundingParameters,\n MarketData,\n MarketMetadata,\n MarketSummary,\n MarketSummaryResponse,\n MaxMarketValue,\n OpenPositionData,\n OrderData,\n OrderFees,\n OrderQuote,\n PayDebtAndWithdraw,\n SettlementStrategy,\n SettlementStrategyResponse,\n SpotMarketData,\n} from './interface';\nimport {\n batchArray,\n convertEtherToWei,\n convertWeiToEther,\n generateRandomAccountId,\n getOdosPath,\n sleep,\n} from '../utils';\nimport { Call3Value } from '../interface/contractTypes';\nimport { MarketIdOrName, OverrideParamsRead, OverrideParamsWrite, WriteReturnType } from '../interface/commonTypes';\nimport {\n AccountPermissions,\n CommitOrder,\n CreateAccountAndDeposit,\n CreateAccountAndDepositMany,\n CreateAccountDepositAndCreateOrder,\n CreateIsolateOrder,\n GetPermissions,\n GetPerpsQuote,\n GrantPermission,\n ModifyCollateral,\n PayDebt,\n} from '../interface/Perps';\nimport { PerpsRepository } from '../interface/Perps/repositories';\nimport { Market } from '../utils/market';\nimport { PERPS_PERMISSIONS } from '../constants/perpsPermissions';\nimport { MetadataResponse, PythPriceId } from '../interface/Markets';\nimport { erc20StateOverrideBalanceAndAllowance } from '../utils/override';\nimport { SYNTHETIX_ZAP_ABI } from '../contracts/abis/zap';\nimport { SYNTHETIX_ZAP } from '../contracts/addreses/zap';\nimport { DEFAULT_DECIMALS } from '../constants';\n\n/**\n * Class for interacting with Synthetix Perps V3 contracts\n * Provides methods for creating and managing accounts, depositing and withdrawing\n * collateral, committing and settling orders, and liquidating accounts.\n *\n * Use ``get`` methods to fetch information about accounts, markets, and orders::\n * const markets = await sdk.perps.getMarkets()\n * const openPositions = await sdk.perps.getOpenPositions()\n * Other methods prepare transactions, and submit them to your RPC::\n * const createTxHash = await sdk.perps.createAccount(submit=True)\n * const collateralTxHash = await sdk.perps.modifyCollateral(amount=1000, market_name='sUSD', submit=True)\n * const orderTxHash = await sdk.perps.commitOrder(size=10, market_name='ETH', desired_fill_price=2000, submit=True)\n * An instance of this module is available as ``sdk.perps``. If you are using a network without\n * perps deployed, the contracts will be unavailable and the methods will raise an error.\n * The following contracts are required:\n * - PerpsMarketProxy\n * - PerpsAccountProxy\n * - PythERC7412Wrapper\n * @param synthetixSdk An instance of the Synthetix class\n */\nexport class Perps extends Market<MarketData> implements PerpsRepository {\n sdk: SynthetixSdk;\n defaultAccountId?: bigint;\n accountIds: bigint[];\n marketMetadata: Map<number, MarketMetadata>;\n\n // Markets data\n\n isErc7412Enabled: boolean = true;\n // Set multicollateral to false by default\n isMulticollateralEnabled: boolean = false;\n disabledMarkets: number[] = [];\n isInitialized: boolean = false;\n\n constructor(synthetixSdk: SynthetixSdk) {\n super(synthetixSdk);\n this.sdk = synthetixSdk;\n this.accountIds = [];\n\n this.marketMetadata = new Map<number, MarketMetadata>();\n }\n\n // === READ CALLS ===\n\n async initPerps() {\n if (this.isInitialized) return;\n await this.getAccountIds();\n\n // Check if the Multicollateral is enabled\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n const debtFunctionData = getAbiItem({\n abi: marketProxy.abi,\n name: 'debt',\n });\n\n const payDebtFunctionData = getAbiItem({\n abi: marketProxy.abi,\n name: 'payDebt',\n });\n\n if (debtFunctionData != undefined && payDebtFunctionData != undefined) {\n this.isMulticollateralEnabled = true;\n this.sdk.logger.info('Multicollateral perps is enabled');\n }\n this.isInitialized = true;\n }\n\n /**\n * Fetch a list of perps ``account_id`` owned by an address. Perps accounts\n * are minted as an NFT to the owner's address. The ``account_id`` is the\n * token id of the NFTs held by the address.\n * @param {string} address The address to get accounts for. Uses connected address if not provided.\n * @param {bigint} defaultAccountId The default account ID to set after fetching.\n * @returns A list of account IDs owned by the address\n */\n\n public async getAccountIds(accountAddress = this.sdk.accountAddress, defaultAccountId?: bigint): Promise<bigint[]> {\n if (!accountAddress) throw new Error('Invalid address');\n\n const accountProxy = await this.sdk.contracts.getPerpsAccountProxyInstance();\n const balance = await accountProxy.read.balanceOf([accountAddress]);\n this.sdk.logger.info('balance', balance);\n const argsList = [];\n\n for (let index = 0; index < Number(balance); index++) {\n argsList.push([accountAddress, index]);\n }\n const accountIds = (await this.sdk.utils.multicallErc7412({\n contractAddress: accountProxy.address,\n abi: accountProxy.abi,\n functionName: 'tokenOfOwnerByIndex',\n args: argsList,\n })) as unknown[];\n\n if (accountIds == undefined) return [];\n // Set Perps account ids\n this.accountIds = accountIds as bigint[];\n this.sdk.logger.info('accountIds', accountIds);\n if (defaultAccountId) {\n this.defaultAccountId = defaultAccountId;\n } else if (this.accountIds.length > 0) {\n this.defaultAccountId = this.accountIds[0] as bigint;\n this.sdk.logger.info('Using default account id as ', this.defaultAccountId);\n }\n return accountIds as bigint[];\n }\n\n public async getMarkets(): Promise<MarketData[]> {\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n const marketIdResponse: bigint[] = (await perpsMarketProxy.read.getMarkets()) as bigint[];\n\n const marketMetadataResponse = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'metadata',\n args: marketIdResponse as unknown[],\n })) as MetadataResponse[];\n\n const marketIds = marketIdResponse.map((id) => Number(id));\n\n const settlementStrategies = await this.getSettlementStrategies(marketIds);\n const { marketMetadatas, pythPriceIds } = marketMetadataResponse.reduce(\n (acc, market, index) => {\n const [name, symbol] = market;\n const strategy = settlementStrategies.find((strategy) => strategy.marketId == marketIds[index]);\n\n acc.pythPriceIds.push({\n symbol,\n feedId: strategy?.feedId ?? '0x',\n });\n\n acc.marketMetadatas.push({\n marketName: name,\n symbol,\n feedId: strategy?.feedId ?? '0x',\n });\n\n return acc;\n },\n {\n pythPriceIds: [],\n marketMetadatas: [],\n } as { pythPriceIds: PythPriceId[]; marketMetadatas: MarketMetadata[] },\n );\n\n this.sdk.pyth.updatePriceFeedIds(pythPriceIds);\n\n const [marketSummaries, fundingParameters, orderFees, maxMarketValues] = await Promise.all([\n this.getMarketSummaries(marketIds),\n this.getFundingParameters(marketIds),\n this.getOrderFees(marketIds),\n this.getMaxMarketValues(marketIds),\n ]);\n\n const datas = marketMetadatas.map((marketMetadata, index) => {\n const marketId = Number(marketIds.at(index) || 0);\n const marketSummary = marketSummaries.find((summary) => summary.marketId == marketId);\n const fundingParam = fundingParameters.find((fundingParam) => fundingParam.marketId == marketId);\n const orderFee = orderFees.find((orderFee) => orderFee.marketId == marketId);\n const maxMarketValue = maxMarketValues.find((maxMarketValue) => maxMarketValue.marketId == marketId);\n const result = {\n marketId: marketIds[index],\n marketName: marketMetadata.marketName,\n symbol: marketMetadata.symbol,\n feedId: marketMetadata.feedId,\n skew: marketSummary?.skew,\n size: marketSummary?.size,\n maxOpenInterest: marketSummary?.maxOpenInterest,\n interestRate: marketSummary?.interestRate,\n currentFundingRate: marketSummary?.currentFundingRate,\n currentFundingVelocity: marketSummary?.currentFundingVelocity,\n indexPrice: marketSummary?.indexPrice,\n skewScale: fundingParam?.skewScale,\n maxFundingVelocity: fundingParam?.maxFundingVelocity,\n makerFee: orderFee?.makerFeeRatio,\n takerFee: orderFee?.takerFeeRatio,\n maxMarketValue: maxMarketValue?.maxMarketValue,\n };\n\n this.marketMetadata.set(marketId, marketMetadata);\n this.marketsById.set(marketId, result);\n this.marketsByName.set(marketMetadata.marketName, result);\n this.marketsBySymbol.set(marketMetadata.symbol, result);\n\n return result;\n });\n\n return datas;\n }\n\n public async getMarket(marketIdOrName: MarketIdOrName): Promise<MarketData> {\n // Smart contract calls to populate MarketData\n // 0. Fetch all marketIds - Only if marketName is provided, else use the market id\n // 1. metadata - To get market name and symbol\n // 2. getSettlementStrategy - To get feedId\n // 3. getMarketSummary - To get skew, size, maxOi, currentFundingRate, currentFundingVelocity, indexPrice\n // 4. interestRate - To get interestRate\n // 5. getFundingParameters - To get skewScale and maxFundingVelocity\n // 6. getOrderFees - To get maker and taker fee\n // 7. getMaxMarketValue - To get maxMarketValue\n\n const market =\n (this.marketsById.get(Number(marketIdOrName)) ?? this.marketsByName.get(marketIdOrName as string)) || {};\n if (market.marketId) {\n return market;\n }\n\n let marketId, marketName, marketSymbol;\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n // If the market id is known, avoid fetching fetching all the markets and their metadata\n if (typeof marketIdOrName === 'number' || typeof marketIdOrName === 'bigint') {\n const marketMetadataResponse = (await this.sdk.utils.callErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n\n functionName: 'metadata',\n args: [marketIdOrName],\n })) as MetadataResponse;\n\n marketId = Number(marketIdOrName);\n marketName = marketMetadataResponse[0];\n marketSymbol = marketMetadataResponse[1];\n } else {\n const marketIdResponse: bigint[] = (await perpsMarketProxy.read.getMarkets()) as bigint[];\n const marketMetadataResponse = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'metadata',\n args: marketIdResponse as unknown[],\n })) as MetadataResponse[];\n\n marketMetadataResponse.forEach((metadata, idx) => {\n if (metadata[0] == marketIdOrName) {\n marketId = marketIdResponse[idx];\n marketName = metadata[0];\n marketSymbol = metadata[1];\n }\n });\n }\n\n const functionNames: string[] = [];\n const argsList: unknown[] = [];\n\n // 0. Get settlement strategy\n functionNames.push('getSettlementStrategy');\n argsList.push([marketId, 0]);\n\n // 1. Get market Summary\n functionNames.push('getMarketSummary');\n argsList.push([marketId]);\n\n // 2. Get interest rate\n functionNames.push('interestRate');\n argsList.push([]);\n\n // 3. Get funding values\n functionNames.push('getFundingParameters');\n argsList.push([marketId]);\n\n // 4. Get maker and taker fees\n functionNames.push('getOrderFees');\n argsList.push([marketId]);\n\n // 5. Get max market value\n functionNames.push('getMaxMarketValue');\n argsList.push([marketId]);\n\n const multicallResponse: unknown[] = await this.sdk.utils.multicallMultifunctionErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionNames,\n args: argsList,\n });\n\n const settlementStrategy = multicallResponse.at(0) as SettlementStrategyResponse;\n const marketSummary = multicallResponse.at(1) as MarketSummaryResponse;\n const interestRate = multicallResponse.at(2) as bigint;\n const fundingParams = multicallResponse.at(3) as bigint[];\n const orderFees = multicallResponse.at(4) as bigint[];\n const maxMarketValue = multicallResponse.at(5) as bigint;\n\n market.marketId = marketId || 0;\n market.marketName = marketName || 'INVALID MARKET';\n market.symbol = marketSymbol || 'INVALID';\n\n // From settlementStrategy data\n market.feedId = settlementStrategy.feedId;\n\n // From marketSummary data\n market.skew = Number(formatEther(marketSummary.skew));\n market.size = Number(formatEther(marketSummary.size));\n market.maxOpenInterest = Number(formatEther(marketSummary.maxOpenInterest));\n market.interestRate = Number(formatEther(interestRate));\n market.currentFundingRate = Number(formatEther(marketSummary.currentFundingRate));\n market.currentFundingVelocity = Number(formatEther(marketSummary.currentFundingVelocity));\n market.indexPrice = Number(formatEther(marketSummary.indexPrice));\n // From fundingParams data\n market.skewScale = Number(formatEther(fundingParams.at(0) || 0n));\n market.maxFundingVelocity = Number(formatEther(fundingParams.at(1) || 0n));\n\n // From orderFees data\n market.makerFee = Number(formatEther(orderFees.at(0) || 0n));\n market.takerFee = Number(formatEther(orderFees.at(1) || 0n));\n\n market.maxMarketValue = Number(formatEther(maxMarketValue));\n\n this.marketMetadata.set(market.marketId || 0, {\n marketName: market.marketName,\n symbol: market.symbol,\n feedId: market.feedId,\n });\n\n this.marketsById.set(market.marketId, market);\n this.marketsByName.set(market.marketName, market);\n this.marketsBySymbol.set(market.symbol, market);\n return market;\n }\n\n /**\n * Fetch the market summaries for an array of marketIds\n * @param marketIds Array of market ids to fetch\n * @returns Summary of market ids data fetched from the contract\n */\n public async getMarketSummaries(marketIds: number[]): Promise<MarketSummary[]> {\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const interestRate = await this.sdk.utils.callErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'interestRate',\n args: [],\n });\n\n const marketSummariesInput = marketIds.map((marketId) => [marketId]);\n const marketSummariesResponse: MarketSummaryResponse[] = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'getMarketSummary',\n args: marketSummariesInput,\n })) as MarketSummaryResponse[];\n\n if (marketIds.length !== marketSummariesResponse.length) {\n this.sdk.logger.info('Inconsistent data');\n }\n\n const marketSummaries: MarketSummary[] = [];\n const batchedResponse = batchArray(\n marketSummariesResponse.map((market, index) => {\n return {\n ...market,\n marketId: marketIds[index],\n };\n }),\n 10,\n );\n\n for (const batch of batchedResponse) {\n const promises = batch.map(async (market) => {\n const marketId = market.marketId;\n\n marketSummaries.push({\n marketId: marketId,\n marketName: (await this.getMarket(marketId)).marketName,\n feedId: (await this.getMarket(marketId)).feedId,\n indexPrice: Number(formatEther(market.indexPrice)),\n skew: Number(formatEther(market.skew)),\n size: Number(formatEther(market.size)),\n maxOpenInterest: Number(formatEther(market.maxOpenInterest)),\n interestRate: Number(formatEther(interestRate as bigint)),\n currentFundingRate: Number(formatEther(market.currentFundingRate)),\n currentFundingVelocity: Number(formatEther(market.currentFundingVelocity)),\n });\n });\n await Promise.allSettled(promises);\n }\n return marketSummaries;\n }\n\n /**\n * @name getMarketSummary\n * @description Fetches the summary of a given market. This includes information like skew, size, max open interest, current funding rate, and more.\n * @param {string|number} marketIdOrName - The identifier or name of the market to fetch the summary for.\n * @returns {MarketSummary} - An object containing the summary data for the specified market.\n */\n\n public async getMarketSummary(marketIdOrName: MarketIdOrName): Promise<MarketSummary> {\n const { resolvedMarketId, resolvedMarketName } = await this.resolveMarket(marketIdOrName);\n\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const interestRate = await this.sdk.utils.callErc7412({\n contractAddress: perpsMarketProxy.address,\n\n abi: perpsMarketProxy.abi,\n functionName: 'interestRate',\n args: [],\n });\n\n const marketSummaryResponse: MarketSummaryResponse = (await this.sdk.utils.callErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'getMarketSummary',\n args: [resolvedMarketId],\n })) as MarketSummaryResponse;\n this.sdk.logger.info('marketSummaryResponse', marketSummaryResponse);\n return {\n marketId: resolvedMarketId,\n marketName: resolvedMarketName,\n feedId: '',\n indexPrice: Number(formatEther(marketSummaryResponse.indexPrice)),\n skew: Number(formatEther(marketSummaryResponse.skew)),\n size: Number(formatEther(marketSummaryResponse.size)),\n maxOpenInterest: Number(formatEther(marketSummaryResponse.maxOpenInterest)),\n interestRate: Number(formatEther(interestRate as bigint)),\n currentFundingRate: Number(formatEther(marketSummaryResponse.currentFundingRate)),\n currentFundingVelocity: Number(formatEther(marketSummaryResponse.currentFundingVelocity)),\n } as MarketSummary;\n }\n\n /**\n * @name getSettlementStrategy\n * @description This function retrieves the settlement strategy for a given market by its ID and either Market ID or Name.\n * @param {number} settlementStrategyId - The ID of the settlement strategy to retrieve.\n * @param {MarketIdOrName} marketIdOrName - The unique identifier (ID or Name) of the market this settlement strategy belongs to.\n * @returns {SettlementStrategy} - An object containing the details of the retrieved settlement strategy, including its strategy type, delay times, associated contract addresses, feed ID, settlement reward, and whether it is disabled or not.\n */\n public async getSettlementStrategy(\n settlementStrategyId: number,\n marketIdOrName: MarketIdOrName,\n ): Promise<SettlementStrategy> {\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const settlementStrategy: SettlementStrategyResponse = (await this.sdk.utils.callErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n\n functionName: 'getSettlementStrategy',\n args: [resolvedMarketId, settlementStrategyId],\n })) as SettlementStrategyResponse;\n\n return {\n marketId: resolvedMarketId,\n strategyType: settlementStrategy.strategyType,\n settlementDelay: Number(settlementStrategy.settlementDelay),\n settlementWindowDuration: Number(settlementStrategy.settlementWindowDuration),\n priceVerificationContract: settlementStrategy.priceVerificationContract,\n feedId: settlementStrategy.feedId,\n settlementReward: Number(formatEther(settlementStrategy.settlementReward ?? 0n)),\n disabled: settlementStrategy.disabled,\n commitmentPriceDelay: Number(settlementStrategy.commitmentPriceDelay),\n } as SettlementStrategy;\n }\n\n /**\n * Fetch the settlement strategies for an array of market ids. Settlement strategies describe the\n * conditions under which an order can be settled.\n * @param marketIds Array of marketIds to fetch settlement strategy\n * @returns Settlement strategy array for markets\n */\n public async getSettlementStrategies(marketIds: number[]): Promise<SettlementStrategy[]> {\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n const settlementStrategies: SettlementStrategy[] = [];\n\n const argsList: [number, number][] = marketIds.map((marketId) => [marketId, 0]);\n\n const settlementStrategiesResponse: SettlementStrategyResponse[] = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'getSettlementStrategy',\n args: argsList,\n })) as SettlementStrategyResponse[];\n\n settlementStrategiesResponse.forEach((strategy, index) => {\n settlementStrategies.push({\n marketId: marketIds[index],\n strategyType: strategy.strategyType,\n settlementDelay: Number(strategy.settlementDelay),\n settlementWindowDuration: Number(strategy.settlementWindowDuration),\n priceVerificationContract: strategy.priceVerificationContract,\n feedId: strategy.feedId,\n settlementReward: Number(formatEther(strategy.settlementReward ?? 0n)),\n disabled: strategy.disabled,\n commitmentPriceDelay: Number(strategy.commitmentPriceDelay),\n });\n });\n return settlementStrategies;\n }\n\n /**\n * Fetch funding parameters for an array of market ids.\n * @param marketIds Array of marketIds to fetch settlement strategy\n * @returns Funding Parameters array for markets\n */\n public async getFundingParameters(marketIds: number[]): Promise<FundingParameters[]> {\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n type FundingParamsResponse = [bigint, bigint];\n const fundingParams: FundingParameters[] = [];\n\n const fundingParamsResponse = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'getFundingParameters',\n args: marketIds,\n })) as FundingParamsResponse[];\n\n fundingParamsResponse.forEach((param, index) => {\n fundingParams.push({\n marketId: marketIds[index],\n skewScale: Number(formatEther(param[0])),\n maxFundingVelocity: Number(formatEther(param[1])),\n });\n });\n return fundingParams;\n }\n\n /**\n * Gets the order fees of a market.\n * @param marketIds Array of market ids.\n * @return Order fees array for markets\n */\n public async getOrderFees(marketIds: number[]): Promise<OrderFees[]> {\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n type OrderFeesResponse = [bigint, bigint];\n const orderFees: OrderFees[] = [];\n\n const orderFeesResponse = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'getOrderFees',\n args: marketIds,\n })) as OrderFeesResponse[];\n\n orderFeesResponse.forEach((param, index) => {\n orderFees.push({\n marketId: marketIds[index],\n makerFeeRatio: Number(formatEther(param[0])),\n takerFeeRatio: Number(formatEther(param[1])),\n });\n });\n return orderFees;\n }\n\n /**\n * Gets the max size (in value) of an array of marketIds.\n * @param marketIds Array of market ids.\n * @return Max market size in market USD value for each market\n */\n public async getMaxMarketValues(marketIds: number[]): Promise<MaxMarketValue[]> {\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const maxMarketValues: MaxMarketValue[] = [];\n\n const maxMarketValuesResponse = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'getMaxMarketValue',\n args: marketIds,\n })) as bigint[];\n\n maxMarketValuesResponse.forEach((maxMarketValue, index) => {\n maxMarketValues.push({\n marketId: marketIds[index],\n maxMarketValue: Number(formatEther(maxMarketValue)),\n });\n });\n return maxMarketValues;\n }\n\n /**\n * Fetches the open order for an account. Optionally fetches the settlement strategy,\n * which can be useful for order settlement and debugging.\n * @param {bigint} accountId The id of the account. If not provided, the default account is used\n * @param {boolean} fetchSettlementStrategy Flag to indicate whether to fetch the settlement strategy\n * @returns {OrderData} The order data for the account\n */\n public async getOrder(\n accountId = this.defaultAccountId,\n fetchSettlementStrategy: boolean = true,\n ): Promise<OrderData> {\n if (!accountId) throw new Error('Invalid account id');\n\n interface OrderCommitmentRequestRes {\n marketId: bigint;\n accountId: bigint;\n sizeDelta: bigint;\n settlementStrategyId: bigint;\n acceptablePrice: bigint;\n trackingCode: string;\n referrer: string;\n }\n\n interface AsyncOrderDataRes {\n commitmentTime: bigint;\n request: OrderCommitmentRequestRes;\n }\n\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n const orderResponse = (await this.sdk.utils.callErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'getOrder',\n args: [accountId],\n })) as AsyncOrderDataRes;\n const orderReq = orderResponse.request;\n\n const orderData: OrderData = {\n marketId: Number(orderReq.marketId),\n commitmentTime: Number(orderResponse.commitmentTime),\n accountId: orderReq.accountId,\n sizeDelta: Number(formatEther(orderReq.sizeDelta)),\n settlementStrategyId: Number(orderReq.settlementStrategyId),\n acceptablePrice: Number(orderReq.acceptablePrice),\n trackingCode: orderReq.trackingCode,\n referrer: orderReq.referrer,\n };\n\n if (fetchSettlementStrategy) {\n const strategy = await this.getSettlementStrategy(\n Number(orderReq.settlementStrategyId),\n Number(orderReq.marketId),\n );\n orderData.settlementStrategy = strategy;\n }\n return orderData;\n }\n\n /**\n * Fetch information about an account's margin requirements and balances.\n * Accounts must maintain an ``available_margin`` above the ``maintenance_margin_requirement``\n * to avoid liquidation. Accounts with ``available_margin`` below the ``initial_margin_requirement``\n * can not interact with their position unless they deposit more collateral.\n * @param {bigint} accountId The id of the account to fetch the margin info for. If not provided, the default account is used\n * @returns {CollateralData} The margin information for the account\n */\n public async getMarginInfo(accountId = this.defaultAccountId): Promise<CollateralData> {\n if (!accountId) throw new Error('Invalid account ID');\n\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const functionNames: string[] = [];\n const argsList: unknown[] = [];\n\n // 0. Get Total collateral value\n functionNames.push('totalCollateralValue');\n argsList.push([accountId]);\n\n // 1. Get available margin\n functionNames.push('getAvailableMargin');\n argsList.push([accountId]);\n\n // 2. Get withdrawable margin\n functionNames.push('getWithdrawableMargin');\n argsList.push([accountId]);\n\n // 3. Get required margins\n functionNames.push('getRequiredMargins');\n argsList.push([accountId]);\n\n const multicallResponse: unknown[] = await this.sdk.utils.multicallMultifunctionErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionNames,\n args: argsList,\n });\n\n const totalCollateralValue = multicallResponse.at(0) as bigint;\n const availableMargin = multicallResponse.at(1) as bigint;\n const withdrawableMargin = multicallResponse.at(2) as bigint;\n\n // returns (uint256 requiredInitialMargin,uint256 requiredMaintenanceMargin,uint256 maxLiquidationReward)\n const requiredMarginsResponse = multicallResponse.at(3) as bigint[];\n const requiredInitialMargin = requiredMarginsResponse.at(0) as bigint;\n const requiredMaintenanceMargin = requiredMarginsResponse.at(1) as bigint;\n const maxLiquidationReward = requiredMarginsResponse.at(2) as bigint;\n\n const collateralAmountsRecord: Record<number, number> = [];\n let debt = 0;\n if (this.isMulticollateralEnabled) {\n const fNames: string[] = []; // Function names\n const aList: unknown[] = []; // Argument list\n\n // 0. Get account collateral ids\n fNames.push('getAccountCollateralIds');\n aList.push([accountId]);\n\n // 1. Get account debt\n fNames.push('debt');\n aList.push([accountId]);\n\n const response: unknown[] = await this.sdk.utils.multicallMultifunctionErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionNames: fNames,\n args: aList,\n });\n\n // returns and array of collateral ids(uint256[] memory)\n const collateralIds = response.at(0) as bigint[];\n debt = convertWeiToEther(response.at(1) as bigint);\n\n // 'debt' function is only available for markets with Multicollateral enabled\n if (collateralIds.length != 0) {\n const inputs = collateralIds.map((id) => {\n return [accountId, id];\n });\n this.sdk.logger.info('inputs', inputs);\n const collateralAmounts = (await this.sdk.utils.multicallErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'getCollateralAmount',\n args: inputs,\n })) as bigint[];\n\n collateralIds.forEach((collateralId, index) => {\n collateralAmountsRecord[Number(collateralId)] = convertWeiToEther(collateralAmounts.at(index));\n });\n }\n } else {\n collateralAmountsRecord[0] = convertWeiToEther(totalCollateralValue);\n }\n\n const marginInfo: CollateralData = {\n totalCollateralValue: convertWeiToEther(totalCollateralValue),\n collateralBalances: collateralAmountsRecord,\n debt,\n availableMargin: convertWeiToEther(availableMargin),\n withdrawableMargin: convertWeiToEther(withdrawableMargin),\n initialMarginRequirement: convertWeiToEther(requiredInitialMargin),\n maintenanceMarginRequirement: convertWeiToEther(requiredMaintenanceMargin),\n maxLiquidationReward: convertWeiToEther(maxLiquidationReward),\n };\n this.sdk.logger.info(`${marginInfo}marginInfo`);\n return marginInfo;\n }\n\n /**\n * @name buildModifyCollateral\n * @description This function builds the call to modify collateral in a perp market. It takes an amount, market ID or name, account ID, and collateral ID as parameters and returns an array of Call3Value objects.\n * @param {string | number} data.amount - The amount of the underlying asset to modify collateral for.\n * @param {string | number} data.marketIdOrName - The ID or name of the perp market where the collateral will be modified.\n * @param {string} data.accountId - The ID of the account whose collateral is being modified.\n * @param {string} data.collateralId - The ID of the collateral being modified.\n * @returns {Call3Value[]} - An array of Call3Value objects containing the target contract address, call data, value, requireSuccess flag, and other relevant information for executing the 'modifyCollateral' function on the market proxy contract.\n */\n async buildModifyCollateral({ amount, collateralMarketIdOrName, accountId }: ModifyCollateral): Promise<Call3Value> {\n const { resolvedMarketId: collateralMarketId, resolvedMarketName: collateralMarketName } =\n await this.sdk.spot.resolveMarket(collateralMarketIdOrName);\n\n this.sdk.logger.info(`Building ${amount} ${collateralMarketName} for account ${accountId}`);\n\n const synthCollateral = await this.sdk.spot.getMarket(collateralMarketId);\n\n // Remove Synthetix asset's `s` from market name.\n // For example, remove `s` from `sUSDe` to get `USDe`\n const collateral = await this.sdk.contracts.getCollateralInstance(\n (synthCollateral.marketName ?? 'Unresolved Market').replace('s', ''),\n );\n const zapContract = SYNTHETIX_ZAP[this.sdk.rpcConfig.chainId];\n const formattedAmount = await this.formatSize(amount, amount > 0 ? collateralMarketId : 0);\n\n if (amount > 0)\n return {\n target: zapContract,\n callData: encodeFunctionData({\n abi: SYNTHETIX_ZAP_ABI,\n functionName: 'deposit',\n // all wrap versions have 18 decimals so setting collateralId default to 0\n args: [accountId, collateral.address, synthCollateral.contractAddress, formattedAmount, collateralMarketId],\n }),\n value: 0n,\n requireSuccess: true,\n };\n\n return {\n target: zapContract,\n callData: encodeFunctionData({\n abi: SYNTHETIX_ZAP_ABI,\n functionName: 'withdraw',\n args: [accountId, collateral.address, formattedAmount, collateralMarketId],\n }),\n value: 0n,\n requireSuccess: true,\n };\n }\n\n /**\n * Fetch the balance of each collateral type for an account.\n * @param {bigint} accountId The id of the account to fetch the collateral balances for. If not provided, the default account is used.\n * @returns {Promise<number>} The balance of the account's collateral.\n */\n\n public async getCollateralBalances(accountId?: bigint): Promise<number> {\n // const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n // if (accountId == undefined) {\n // accountId = this.defaultAccountId;\n // }\n throw new Error('Not implemented ' + accountId);\n }\n\n /**\n * Check if an `accountId` is eligible for liquidation.\n * @param accountId The id of the account to check. If not provided, the default account is used.\n * @returns {Promise<boolean>} A boolean indicating whether the account can be liquidated.\n */\n public async getCanLiquidate(accountId = this.defaultAccountId): Promise<boolean> {\n if (!accountId) throw new Error('Invalid account ID');\n\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const canBeLiquidated = (await this.sdk.utils.callErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'canLiquidate',\n args: [accountId],\n })) as boolean;\n this.sdk.logger.info('canBeLiquidated', canBeLiquidated);\n return canBeLiquidated;\n }\n\n /**\n * Check if a batch of `accountId`'s are eligible for liquidation.\n * @param {string[]} accountIds An array of account ids\n * @returns {Promise<{ accountId: bigint; canLiquidate: boolean }[]>} An array of objects containing the account id and whether the account can be liquidated.\n */\n public async getCanLiquidates(\n accountIds: bigint[] | undefined = undefined,\n ): Promise<{ accountId: bigint; canLiquidate: boolean }[]> {\n const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n if (accountIds == undefined) {\n if (this.defaultAccountId != undefined) {\n accountIds = this.accountIds;\n } else {\n throw new Error('Invalid account ID');\n }\n }\n\n // Format the args to the required array format\n const input = accountIds.map((accountId) => [accountId]);\n\n const canLiquidatesResponse = (await this.sdk.utils.multicallErc7412({\n contractAddress: perpsMarketProxy.address,\n abi: perpsMarketProxy.abi,\n functionName: 'canLiquidate',\n args: input,\n })) as boolean[];\n\n const canLiquidates = canLiquidatesResponse.map((response, index) => {\n return {\n accountId: accountIds.at(index) ?? 0n,\n canLiquidate: response,\n };\n });\n this.sdk.logger.info('canLiquidates', canLiquidates);\n return canLiquidates;\n }\n\n /**\n * Fetch the position for a specified account and market. The result includes the unrealized\n * pnl since the last interaction with this position, any accrued funding, and the position size.\n * Provide either a ``marketId`` or a ``marketName``::\n * @param {string | number} marketIdOrName - The identifier or name of the market for which the collateral is being modified\n * @param {bigint} accountId The id of the account to fetch the position for. If not provided, the default account is used.\n * @returns {OpenPositionData} An object containing the open position data for the specified market.\n */\n public async getOpenPosition(\n marketIdOrName: MarketIdOrName,\n accountId = this.defaultAccountId,\n ): Promise<OpenPositionData> {\n if (!accountId) throw new Error('Account ID is required');\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n const { resolvedMarketId, resolvedMarketName } = await this.resolveMarket(marketIdOrName);\n\n // Smart contract response:\n // returns (int256 totalPnl, int256 accruedFunding, int128 positionSize, uint256 owedInterest);\n const response = (await this.sdk.utils.callErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'getOpenPosition',\n args: [accountId, resolvedMarketId],\n })) as bigint[];\n\n const openPositionData: OpenPositionData = {\n accountId: accountId,\n marketId: resolvedMarketId,\n marketName: resolvedMarketName,\n totalPnl: convertWeiToEther(response.at(0)),\n accruedFunding: convertWeiToEther(response.at(1)),\n positionSize: convertWeiToEther(response.at(2)),\n owedInterest: convertWeiToEther(response.at(3)),\n };\n this.sdk.logger.info('openPositionData', openPositionData);\n return openPositionData;\n }\n\n /**\n * Fetch positions for an array of specified markets. The result includes the unrealized\n * pnl since the last interaction with this position, any accrued funding, and the position size.\n * Provide either an array of ``marketIds`` or a ``marketNames``::\n * @param {MarketIdOrName} marketIdsOrNames - The ID or name of the Perpetual market to get a quote for.\n * @param {bigint} accountId The id of the account to fetch the position for. If not provided, the default account is used.\n * @returns {OpenPositionData[]} An array of objects containing the open position data for the specified markets.\n */\n // NOTE: maybe is better use subgraph?\n public async getOpenPositions(\n marketIdsOrNames?: MarketIdOrName[],\n accountId = this.defaultAccountId,\n ): Promise<OpenPositionData[]> {\n if (!accountId) throw new Error('Account ID is required');\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const marketIds: number[] = !marketIdsOrNames\n ? Array.from(this.marketsById.keys())\n : await Promise.all(marketIdsOrNames.map(async (market) => (await this.resolveMarket(market)).resolvedMarketId));\n\n const inputs = marketIds?.map((marketId) => {\n return [accountId, marketId];\n }) as unknown[];\n\n // Smart contract response:\n // returns (int256 totalPnl, int256 accruedFunding, int128 positionSize, uint256 owedInterest);\n const response = (await this.sdk.utils.multicallErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'getOpenPosition',\n args: inputs,\n })) as bigint[][];\n\n const openPositionsData: OpenPositionData[] = [];\n const batchedResponse = batchArray(\n response.map((data, index) => {\n return {\n data,\n marketId: marketIds?.at(index) ?? 0,\n };\n }),\n 10,\n );\n\n for (const batch of batchedResponse) {\n const promises = batch.map(async ({ data: positionData, marketId }) => {\n const positionSize = convertWeiToEther(positionData.at(2));\n if (Math.abs(positionSize) > 0) {\n openPositionsData.push({\n accountId: accountId,\n marketId: marketId,\n marketName: (await this.getMarket(marketId)).marketName ?? 'Unresolved market',\n totalPnl: convertWeiToEther(positionData.at(0)),\n accruedFunding: convertWeiToEther(positionData.at(1)),\n positionSize: positionSize,\n owedInterest: convertWeiToEther(positionData.at(3)),\n });\n }\n });\n await Promise.allSettled(promises);\n }\n\n return openPositionsData;\n }\n\n /**\n * Retrieves open positions for multiple accounts and markets using a multicall.\n * @param positions - An array of objects representing the positions,\n * where each object contains:\n * - accountId: {bigint} The id of the account to fetch the position for .\n * - marketIdOrName: {MarketIdOrName} The market ID or name.\n * @returns An array of OpenPositionData objects. */\n public async getOpenPositionsMulticall(\n positions: { accountId: bigint; marketIdOrName: MarketIdOrName }[],\n ): Promise<OpenPositionData[]> {\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const marketIds: number[] = await Promise.all(\n positions.map(async (position) => (await this.resolveMarket(position.marketIdOrName)).resolvedMarketId),\n );\n\n const inputs = positions.map((position, idx) => {\n return [position.accountId, marketIds.at(idx)];\n }) as unknown[];\n\n // Smart contract response:\n // returns (int256 totalPnl, int256 accruedFunding, int128 positionSize, uint256 owedInterest);\n const response = (await this.sdk.utils.multicallErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'getOpenPosition',\n args: inputs,\n })) as bigint[][];\n\n const openPositionsData: OpenPositionData[] = [];\n const batchedResponse = batchArray(\n response.map((market, index) => {\n return { data: market, marketId: marketIds.at(index)! };\n }),\n 10,\n );\n for (const batch of batchedResponse) {\n const promises = batch.map(async ({ data: positionData, marketId }, idx) => {\n const positionSize = convertWeiToEther(positionData.at(2));\n if (Math.abs(positionSize) > 0) {\n openPositionsData.push({\n accountId: positions.at(idx)?.accountId ?? 0n,\n marketId: marketId,\n marketName: (await this.getMarket(marketId)).marketName ?? 'Unresolved market',\n totalPnl: convertWeiToEther(positionData.at(0)),\n accruedFunding: convertWeiToEther(positionData.at(1)),\n positionSize: positionSize,\n owedInterest: convertWeiToEther(positionData.at(3)),\n });\n }\n });\n await Promise.allSettled(promises);\n }\n return openPositionsData;\n }\n\n /**\n * @name getQuote\n * @description Fetches a quote for a Perpetual market, calculating the fees, fill price and required margin.\n * @param {number} data.size - The size of the order in base asset units\n * @param {number} data.price - The price of the order in quote asset units. If not provided, it will be fetched from Pyth Oracle.\n * @param {MarketIdOrName} data.marketIdOrName - The ID or name of the Perpetual market to get a quote for.\n * @param {number} [data.accountId=this.defaultAccountId] - The account ID of the user requesting the quote. Defaults to the default account ID.\n * @param {number} [data.settlementStrategyId=0] - The settlement strategy ID of the market. Defaults to 0 (no leverage).\n * @param {boolean} [data.includeRequiredMargin=true] - Whether to include the required margin in the quote. Defaults to true.\n * @returns {OrderQuote} An object containing the order size, index price, order fees, settlement reward cost, fill price and required margin (if provided).\n */\n public async getQuote({\n size,\n price,\n marketIdOrName,\n accountId = this.defaultAccountId,\n settlementStrategyId = 0,\n includeRequiredMargin = true,\n }: GetPerpsQuote): Promise<OrderQuote> {\n if (!accountId) throw new Error('No account Id!');\n\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n\n const feedId = (await this.getMarket(resolvedMarketId)).feedId;\n if (!feedId) throw new Error('Invalid feed id received from market data');\n\n if (!price) {\n price = await this.sdk.pyth.getFormattedPrice(feedId as Hex);\n this.sdk.logger.info('Formatted price:', price);\n }\n const [orderFeesWithPriceResponse, settlementRewardCost, requiredMargin] = await Promise.all([\n this.sdk.utils.callErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'computeOrderFeesWithPrice',\n args: [resolvedMarketId, convertEtherToWei(size), convertEtherToWei(price)],\n }) as Promise<[bigint, bigint]>,\n this.sdk.utils.callErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'getSettlementRewardCost',\n args: [resolvedMarketId, settlementStrategyId],\n }) as Promise<bigint>,\n includeRequiredMargin && accountId\n ? (this.sdk.utils.callErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'requiredMarginForOrderWithPrice',\n args: [accountId, resolvedMarketId, convertEtherToWei(size), convertEtherToWei(price)],\n }) as Promise<bigint>)\n : 0n,\n ]);\n const orderQuote: OrderQuote = {\n orderSize: size,\n indexPrice: price,\n orderFees: convertWeiToEther(orderFeesWithPriceResponse[0]),\n settlementRewardCost: convertWeiToEther(settlementRewardCost),\n fillPrice: convertWeiToEther(orderFeesWithPriceResponse[1]),\n };\n\n if (includeRequiredMargin && accountId) {\n orderQuote.requiredMargin = convertWeiToEther(requiredMargin);\n }\n return orderQuote;\n }\n\n /**\n * Returns the debt of the account id\n * @param accountId The id of the account to get the debt for. If not provided, the default account is used.\n * @returns debt Account debt in ether\n */\n public async getDebt(accountId = this.defaultAccountId): Promise<number> {\n if (!accountId) throw new Error('No account id selected');\n if (!this.isMulticollateralEnabled)\n throw new Error(`Multicollateral is not enabled for chainId ${this.sdk.rpcConfig.chainId}`);\n\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n\n const debt = (await this.sdk.utils.callErc7412({\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionName: 'debt',\n args: [accountId],\n })) as bigint;\n this.sdk.logger.info('Account Debt: ', debt);\n return convertWeiToEther(debt);\n }\n\n /**\n * Fetch Position and related margin details from contracts\n * @param accountId The id of the account to get the debt for\n * @param marketIdOrName Market\n * @param override override params for the call\n * @returns Formatted values in decimal numbers for position and margin data\n */\n protected async _getPositionData(accountId: bigint, marketIdOrName: MarketIdOrName, override?: OverrideParamsRead) {\n if (!accountId) throw new Error('Account ID is required');\n const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n\n const functionNames: string[] = [];\n const argsList: unknown[] = [];\n\n // 0. Get available margin\n functionNames.push('getAvailableMargin');\n argsList.push([accountId]);\n\n // 1. Get required margins\n functionNames.push('getRequiredMargins');\n argsList.push([accountId]);\n\n // 2. Get position size\n functionNames.push('getOpenPositionSize');\n argsList.push([accountId, resolvedMarketId]);\n\n // 3. Get market index price\n functionNames.push('indexPrice');\n argsList.push([resolvedMarketId]);\n\n const multicallResponse: unknown[] = await this.sdk.utils.multicallMultifunctionErc7412(\n {\n contractAddress: marketProxy.address,\n abi: marketProxy.abi,\n functionNames,\n args: argsList,\n },\n override,\n );\n\n const availableMargin = multicallResponse.at(0) as bigint;\n const requiredMaintenanceMargin = (multicallResponse.at(1) as bigint[]).at(1) as b