@parifi/synthetix-sdk-ts
Version:
A Typescript SDK for interactions with the Synthetix protocol
1 lines • 80 kB
Source Map (JSON)
{"version":3,"sources":["../../src/spot/index.ts","../../src/constants/common.ts","../../src/utils/common.ts","../../src/contracts/addreses/zap.ts","../../src/utils/index.ts","../../src/contracts/addreses/oracleProxy.ts","../../src/utils/market.ts"],"sourcesContent":["import { Address, ContractFunctionParameters, encodeFunctionData, erc20Abi, getContract, Hex, maxUint256 } from 'viem';\nimport { SynthetixSdk } from '..';\nimport { SpotMarketData } from '../perps/interface';\nimport { ZERO_ADDRESS } from '../constants';\nimport { convertEtherToWei, convertWeiToEther, sleep } from '../utils';\nimport { SpotSettlementStrategy, SpotOrder, Side } from './interface';\nimport {\n Approve,\n AtomicOrder,\n CommitOrderSpot,\n GetOrder,\n GetSettlementStrategies,\n GetSettlementStrategy,\n SettlementStrategyResponse,\n SettleOrder,\n Wrap,\n} from '../interface/Spot';\nimport { Market } from '../utils/market';\nimport { MarketIdOrName, OverrideParamsWrite, WriteReturnType } from '../interface/commonTypes';\nimport { Call3Value } from '../interface/contractTypes';\n\n/**\n * Class for interacting with Synthetix V3 spot market contracts.\n * Provider methods for wrapping and unwrapping assets, approvals, atomic orders, and async orders.\n * Use ``get`` methods to fetch information about balances, allowances, and markets\n * const { marketsById, marketsByName } = await sdk.perps.getMarkets();\n *\n * Other methods prepare transactions, and submit them to your RPC.\n * An instance of this module is available as ``sdk.spot``. If you are using a network without\n * spot contracts deployed, the contracts will be unavailable and the methods will raise an error.\n * The following contracts are required:\n * - SpotMarketProxy\n */\nexport class Spot extends Market<SpotMarketData> {\n sdk: SynthetixSdk;\n defaultAccountId?: bigint;\n accountIds: bigint[];\n\n asyncOrderEnabled: boolean = false;\n disabledMarkets: number[] = [];\n\n constructor(synthetixSdk: SynthetixSdk) {\n super(synthetixSdk);\n this.sdk = synthetixSdk;\n this.accountIds = [];\n\n if (synthetixSdk.rpcConfig.chainId == 42161 || synthetixSdk.rpcConfig.chainId == 421614) {\n this.asyncOrderEnabled = true;\n }\n }\n\n async initSpot() {\n await this.getMarkets();\n }\n\n public async getMarket(marketIdOrName: MarketIdOrName): Promise<SpotMarketData> {\n const market = this.marketsById.get(Number(marketIdOrName)) ?? this.marketsByName.get(marketIdOrName as string);\n if (!market) {\n this.sdk.logger.warn(`Spot market ${marketIdOrName} not available. Available markets: ${this.marketsById}`);\n throw new Error(`Invalid market id or name: ${marketIdOrName}`);\n }\n\n return market;\n }\n\n /**\n * Fetches contracts and metadata about all spot markets on the network. This includes\n * the market id, synth name, contract address, and the underlying synth contract. Each\n * synth is an ERC20 token, so these contracts can be used for transfers and allowances.\n * The metadata is also used to simplify interactions in the SDK by mapping market ids\n * and names to their metadata\n * For example:\n * sdk.spot.wrap(100, market_name='sUSDC', submit=True)\n * This will look up the market id for the sUSDC market and use that to wrap 100 USDC into sUSDC.\n * The market metadata is returned from the method as a mapping object of two dictionaries/records.\n * The first is keyed by ``marketsById`` and the second is keyed by ``marketsByName``\n * For example: sdk.spot.marketsByName\n * Response: {\n 'sUSD' => {\n marketId: 0,\n marketName: 'sUSD',\n contractAddress: '0x682f0d17feDC62b2a0B91f8992243Bf44cAfeaaE'\n },\n\n * Example: sdk.spot.marketsById\n * Response: {\n 0 => {\n marketId: 0,\n marketName: 'sUSD',\n contractAddress: '0x682f0d17feDC62b2a0B91f8992243Bf44cAfeaaE'\n },...\n */\n public async getMarkets(): Promise<{\n marketsById: Map<number, SpotMarketData>;\n marketsByName: Map<string, SpotMarketData>;\n }> {\n // Initialize markets with defaults\n const usdProxy = await this.sdk.contracts.getUSDProxyInstance();\n this.marketsById.set(0, {\n marketId: 0,\n marketName: 'sUSD',\n symbol: 'sUSD',\n contractAddress: usdProxy.address,\n });\n\n this.marketsByName.set('sUSD', {\n marketId: 0,\n marketName: 'sUSD',\n symbol: 'sUSD',\n contractAddress: usdProxy.address,\n });\n\n const spotProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n const finalSynths: SpotMarketData[] = [];\n\n // Iterate through all the market IDs until ADDRESS_ZERO is returned\n const MAX_MARKETS = 100;\n const ITEMS_PER_ITER = 5;\n\n for (let index = 0; index < MAX_MARKETS; index += ITEMS_PER_ITER) {\n const argsList = Array.from({ length: ITEMS_PER_ITER }, (_, i) => index + i);\n const synthAddresses = (await this.sdk.utils.multicallErc7412({\n contractAddress: spotProxy.address,\n abi: spotProxy.abi,\n functionName: 'getSynth',\n args: argsList,\n })) as Address[];\n\n synthAddresses.forEach((synthAddress, idx) => {\n // Filter disabled and invalid markets\n const isMarketDisabled = this.disabledMarkets.includes(argsList[idx]);\n if (synthAddress != ZERO_ADDRESS && !isMarketDisabled) {\n finalSynths.push({\n marketId: argsList[idx],\n contractAddress: synthAddress,\n });\n }\n });\n\n // Do not query additional markets if the last fetched market was zero address\n if (synthAddresses.at(-1) == ZERO_ADDRESS) {\n break;\n }\n }\n\n let settlementStrategies: SpotSettlementStrategy[];\n if (this.asyncOrderEnabled) {\n // const marketIds = Array.from(finalSynths.keys());\n // Get settlement strategies\n // settlementStrategies = await this.getSettlementStrategies(0, marketIds);\n } else {\n this.sdk.logger.info('Async orders not enabled on network ', this.sdk.rpcConfig.chainId);\n }\n\n // Query ERC20 contract for market details for each synth\n const multicallInputs: ContractFunctionParameters[] = [];\n finalSynths.forEach((synth) => {\n multicallInputs.push({\n address: synth.contractAddress as Address,\n abi: erc20Abi,\n functionName: 'symbol',\n });\n });\n\n const synthSymbols = await this.sdk.publicClient.multicall({\n contracts: multicallInputs,\n });\n\n finalSynths.forEach((synth, idx) => {\n if (synthSymbols.at(idx)?.status == 'success') {\n const name = synthSymbols.at(idx)?.result as string;\n synth.marketName = name;\n synth.symbol = name.slice(1); // Example: Remove initial character 's' from sETH.\n }\n });\n\n // Populate the final market objects\n finalSynths.forEach((synth) => {\n if (settlementStrategies != undefined && settlementStrategies.length > 0) {\n const strategy = settlementStrategies.find((strategy) => strategy.marketId == synth.marketId);\n synth.settlementStrategy = strategy;\n }\n this.marketsById.set(synth.marketId, synth);\n this.marketsByName.set(synth.marketName ?? 'INVALID', synth);\n });\n\n return { marketsById: this.marketsById, marketsByName: this.marketsByName };\n }\n\n /**\n * @name getSynthContract\n * @description This function retrieves the Synth contract for a given market based on its ID or name. It returns the Synth contract instance.\n * @param {MarketIdOrName} marketIdOrName - The unique identifier or name of the market.\n * @returns {Contract<Synth>} - An instance of the Synth contract associated with the provided market.\n */\n public async getSynthContract(marketIdOrName: MarketIdOrName) {\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n\n const contractAddress = this.marketsById.get(resolvedMarketId)?.contractAddress;\n if (contractAddress == undefined) {\n throw new Error('Invalid market - contractAddress');\n }\n\n const synthContract = getContract({\n address: contractAddress as Hex,\n abi: erc20Abi,\n client: this.sdk.publicClient,\n });\n return synthContract;\n }\n\n /**\n * Get the balance of a spot synth. Provide either a ``marketId`` or ``marketName``\n * to choose the synth.\n * @param {string} address The address to check the balance of. If not provided, the\n * current account will be used.\n * @param {MarketIdOrName} marketIdOrName - The unique identifier or name of the market.\n * @returns {number} The balance of the synth in ether.\n */\n public async getBalance(address: string = this.sdk.accountAddress, marketIdOrName: MarketIdOrName): Promise<number> {\n const synthContract = await this.getSynthContract(marketIdOrName);\n const balance = await synthContract.read.balanceOf([address as Hex]);\n return convertWeiToEther(balance);\n }\n\n /**\n * Get the allowance for a ``target_address`` to transfer from ``address``. Provide either\n * a ``marketId`` or ``marketName`` to choose the synth.\n * @param {string} targetAddress The address for which to check allowance.\n * @param {string} address The owner address to check allowance for.\n * @param {MarketIdOrName} marketIdOrName - The unique identifier or name of the market.\n * @returns The allowance in ether.\n */\n public async getAllowance(\n targetAddress: string,\n address: string = this.sdk.accountAddress,\n marketIdOrName: MarketIdOrName,\n ): Promise<number> {\n const synthContract = await this.getSynthContract(marketIdOrName);\n const allowance = await synthContract.read.allowance([address as Hex, targetAddress as Hex]);\n return convertWeiToEther(allowance);\n }\n\n /**\n * Get details about an async order by its ID.\n * Retrieves order details like owner, amount escrowed, settlement strategy, etc.\n * Can also fetch the full settlement strategy parameters if ``fetchSettlementStrategy``\n * is ``true``.\n * Requires either a ``market_id`` or ``market_name`` to be provided to resolve the market.\n * @param {string} data.asyncOrderId The ID of the async order to retrieve.\n * @param {MarketIdOrName} data.marketIdOrName - The unique identifier or name of the market.\n * @param {boolean} data.fetchSettlementStrategy Whether to fetch the full settlement strategy parameters. Default is true.\n * @returns {SpotOrder} The order details.\n */\n public async getOrder({\n asyncOrderId,\n marketIdOrName,\n fetchSettlementStrategy = true,\n }: GetOrder): Promise<SpotOrder> {\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n\n const spotProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n\n const order = (await spotProxy.read.getAsyncOrderClaim([resolvedMarketId, asyncOrderId])) as unknown as SpotOrder;\n this.sdk.logger.info('order', order);\n\n if (fetchSettlementStrategy) {\n const settlementStrategy = await this.getSettlementStrategy({\n settlementStrategyId: Number(order.settlementStrategyId) || 0,\n marketIdOrName: resolvedMarketId,\n });\n order.settlementStrategy = settlementStrategy;\n }\n\n return order;\n }\n\n /**\n * Fetch the settlement strategy for a spot market.\n * @param {GetSettlementStrategy} data The data for fetching the settlement strategy.\n * @param {number} data.settlementStrategyId The id of the settlement strategy to retrieve.\n * @param {MarketIdOrName} data.marketIdOrName - The unique identifier or name of the market.\n * @returns {SpotSettlementStrategy} The settlement strategy for the market.\n */\n public async getSettlementStrategy({\n settlementStrategyId,\n marketIdOrName,\n }: GetSettlementStrategy): Promise<SpotSettlementStrategy> {\n const { resolvedMarketId, resolvedMarketName } = await this.resolveMarket(marketIdOrName);\n const spotProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n\n const settlementStrategy: SettlementStrategyResponse = (await this.sdk.utils.callErc7412({\n contractAddress: spotProxy.address,\n abi: spotProxy.abi,\n functionName: 'getSettlementStrategy',\n args: [resolvedMarketId, settlementStrategyId],\n })) as SettlementStrategyResponse;\n\n return {\n marketId: resolvedMarketId,\n marketName: resolvedMarketName,\n strategyType: settlementStrategy.strategyType,\n settlementDelay: Number(settlementStrategy.settlementDelay),\n settlementWindowDuration: Number(settlementStrategy.settlementWindowDuration),\n priceVerificationContract: settlementStrategy.priceVerificationContract,\n feedId: settlementStrategy.feedId,\n url: settlementStrategy.url,\n settlementReward: convertWeiToEther(settlementStrategy.settlementReward),\n priceDeviationTolerance: convertWeiToEther(settlementStrategy.priceDeviationTolerance),\n minimumUsdExchangeAmount: convertWeiToEther(settlementStrategy.minimumUsdExchangeAmount),\n maxRoundingLoss: convertWeiToEther(settlementStrategy.maxRoundingLoss),\n disabled: settlementStrategy.disabled,\n } as SpotSettlementStrategy;\n }\n\n /**\n * Fetch the settlement strategies for all spot markets.\n * @param {GetSettlementStrategies} data The data for fetching the settlement strategies.\n * @param {number} data.stragegyId The id of the settlement strategy to retrieve.\n * @param {number[]} data.marketIds Array of marketIds to fetch settlement strategy\n * @returns {SpotSettlementStrategy[]} The settlement strategies for the markets.\n */\n public async getSettlementStrategies({\n settlementStrategyId: stragegyId,\n marketIds,\n }: GetSettlementStrategies): Promise<SpotSettlementStrategy[]> {\n const spotProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n\n interface SettlementStrategyResponse {\n strategyType?: number;\n settlementDelay?: bigint;\n settlementWindowDuration?: bigint;\n priceVerificationContract?: string;\n feedId?: string;\n url?: string;\n settlementReward?: bigint;\n priceDeviationTolerance?: bigint;\n minimumUsdExchangeAmount?: bigint;\n maxRoundingLoss?: bigint;\n disabled?: boolean;\n }\n\n const settlementStrategies: SpotSettlementStrategy[] = [];\n let settlementStrategiesResponse: SettlementStrategyResponse[];\n\n const argsList: [number, number][] = marketIds.map((marketId) => [marketId, stragegyId]);\n\n const response = await this.sdk.utils.multicallErc7412({\n contractAddress: spotProxy.address,\n abi: spotProxy.abi,\n functionName: 'getSettlementStrategy',\n args: argsList,\n });\n\n if (response == undefined) {\n settlementStrategiesResponse = [];\n } else {\n settlementStrategiesResponse = response as unknown[] as SettlementStrategyResponse[];\n }\n\n this.sdk.logger.info('settlementStrategiesResponse', settlementStrategiesResponse);\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 url: strategy.url,\n settlementReward: convertWeiToEther(strategy.settlementReward),\n priceDeviationTolerance: convertWeiToEther(strategy.priceDeviationTolerance),\n minimumUsdExchangeAmount: convertWeiToEther(strategy.minimumUsdExchangeAmount),\n maxRoundingLoss: convertWeiToEther(strategy.maxRoundingLoss),\n disabled: strategy.disabled,\n });\n });\n return settlementStrategies;\n }\n\n // === WRITE CALLS ===\n async buildAtomicOrder({\n side,\n size,\n slippageTolerance = 0,\n minAmountReceived,\n marketIdOrName,\n }: AtomicOrder): Promise<Call3Value> {\n const { resolvedMarketId, resolvedMarketName } = await this.resolveMarket(marketIdOrName);\n const spotMarketProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n\n // If network is Base where USDC and sUSDC are 1:1, set minAmount to actual amount\n if (\n this.sdk.rpcConfig.chainId in [8453, 84532] &&\n resolvedMarketName in ['sUSDC'] &&\n minAmountReceived == undefined\n ) {\n minAmountReceived = size;\n } else if (minAmountReceived == undefined) {\n // Get asset price\n const tokenSymbol = this.marketsById.get(resolvedMarketId)?.symbol;\n if (tokenSymbol == undefined) {\n throw new Error(`Invalid token symbol for market id: ${resolvedMarketId}`);\n }\n\n const feedId =\n this.sdk.pyth.priceFeedIds.get(tokenSymbol) ??\n '0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a';\n\n const price = await this.sdk.pyth.getFormattedPrice(feedId as Hex);\n this.sdk.logger.info('Formatted price:', price);\n\n let tradeSize;\n if (side == Side.BUY) {\n tradeSize = size / price;\n } else {\n tradeSize = size * price;\n }\n minAmountReceived = tradeSize * (1 - slippageTolerance);\n }\n\n const minAmountReceivedInWei = convertEtherToWei(minAmountReceived);\n const sizeInWei = convertEtherToWei(size);\n\n const functionName = side == Side.BUY ? 'buy' : 'sell';\n const args = [resolvedMarketId, sizeInWei, minAmountReceivedInWei, this.sdk.referrer];\n\n return {\n target: spotMarketProxy.address,\n callData: encodeFunctionData({\n abi: spotMarketProxy.abi,\n functionName,\n args,\n }),\n value: 0n,\n requireSuccess: true,\n };\n }\n\n /**\n * Execute an atomic order on the spot market.\n * Atomically executes a buy or sell order for the given size.\n * Amounts are transferred directly, no need to settle later. This function\n * is useful for swapping sUSDC with sUSD on Base Andromeda contracts. The default\n * slippage is set to zero, since sUSDC and sUSD can be swapped 1:1\n * For example:\n * const tx = await atomicOrder(Side.BUY, 100, 0, undefined, undefined, \"sUSDC\");\n * Requires either a ``market_id`` or ``market_name`` to be provided to resolve the market.\n * @param {AtomicOrder} data The data for the atomic order.\n * @param {Side} data.side The side of the order (buy/sell).\n * @param {number} data.size The order size in ether.\n * @param {number} data.slippageTolerance The slippage tolerance for the order as a percentage (0.01 = 1%). Default is 0.\n * @param {number} data.minAmountReceived The minimum amount to receive in ether units. This will override the slippage_tolerance.\n * @param {MarketIdOrName} data.marketIdOrName - The unique identifier or name of the market.\n * @param {OverrideParamsWrite} override - Override the default parameters for the transaction.\n * @returns {WriteReturnType} The transaction hash if ``submit`` is ``true``.\n */\n public async atomicOrder(\n { side, size, slippageTolerance = 0, minAmountReceived, marketIdOrName }: AtomicOrder,\n override: OverrideParamsWrite = {},\n ): Promise<WriteReturnType> {\n const atomicOrderTx = await this.buildAtomicOrder({\n side,\n size,\n slippageTolerance,\n minAmountReceived,\n marketIdOrName,\n });\n\n const txs = [atomicOrderTx];\n\n return this.sdk.utils.processTransactions(txs, { ...override });\n }\n public async buildWrap({ size, marketIdOrName }: Wrap): Promise<Call3Value> {\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n const spotMarketProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n const tokenToWrap = resolvedMarketId;\n\n const sizeInWei = await this.formatSize(Math.abs(size), tokenToWrap);\n const functionName = size > 0 ? 'wrap' : 'unwrap';\n const offset = (sizeInWei * 100n) / 1000n;\n\n const wrapTx: Call3Value = {\n target: spotMarketProxy.address,\n callData: encodeFunctionData({\n abi: spotMarketProxy.abi,\n functionName,\n args: [resolvedMarketId, sizeInWei, sizeInWei - offset],\n }),\n value: 0n,\n requireSuccess: true,\n };\n\n return wrapTx;\n }\n /**\n * Wrap an underlying asset into a synth or unwrap back to the asset.\n * Wraps an asset into a synth if size > 0, unwraps if size < 0.\n * The default slippage is set to zero, since the synth and asset can be swapped 1:1.\n * For example:\n * const tx = wrap(100, undefined, \"sUSDC\") # wrap 100 USDC into sUSDC\n * const tx = wrap(-100, undefined, \"sUSDC\") # unwrap 100 sUSDC into USDC\n * Requires either a ``market_id`` or ``market_name`` to be provided to resolve the market.\n * @param {Wrap} data The data for the wrap/unwrap transaction.\n * @param data.size The amount of the asset to wrap/unwrap.\n * @param {MarketIdOrName} data.marketIdOrName - The unique identifier or name of the market.\n * @param {OverrideParamsWrite} override - Override the default parameters for the transaction.\n * @returns {WriteReturnType} The transaction hash if ``submit`` is ``true``.\n */\n public async wrap({ size, marketIdOrName }: Wrap, override: OverrideParamsWrite = {}): Promise<WriteReturnType> {\n const wrapTx = await this.buildWrap({ size, marketIdOrName });\n\n const txs = [wrapTx];\n return this.sdk.utils.processTransactions(txs, { ...override });\n }\n\n async buildCommitOrder({\n side,\n size,\n slippageTolerance,\n minAmountReceived,\n settlementStrategyId = 0,\n marketIdOrName,\n }: CommitOrderSpot): Promise<Call3Value> {\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n const spotMarketProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n\n if (!minAmountReceived) {\n const settlementReward = this.marketsById.get(resolvedMarketId)?.settlementStrategy?.settlementReward ?? 0;\n\n // Get asset price\n const feedId =\n this.marketsById.get(resolvedMarketId)?.settlementStrategy?.feedId ??\n '0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a';\n\n const price = await this.sdk.pyth.getFormattedPrice(feedId as Hex);\n this.sdk.logger.info('Formatted price:', price);\n\n let tradeSize = size * price;\n if (side == Side.BUY) {\n tradeSize = size / price;\n }\n\n minAmountReceived = tradeSize * (1 - slippageTolerance) - settlementReward;\n }\n\n const minAmountReceivedInWei = convertEtherToWei(minAmountReceived);\n const sizeInWei = convertEtherToWei(size);\n const orderType = side == Side.BUY ? 3 : 4;\n\n const args = [\n resolvedMarketId,\n orderType,\n sizeInWei,\n settlementStrategyId,\n minAmountReceivedInWei,\n this.sdk.referrer,\n ];\n\n return {\n target: spotMarketProxy.address,\n callData: encodeFunctionData({\n abi: spotMarketProxy.abi,\n functionName: 'commitOrder',\n args,\n }),\n value: 0n,\n requireSuccess: true,\n };\n }\n\n /**\n * Commit an async order to the spot market.\n * Commits a buy or sell order of the given size. The order will be settled\n * according to the settlement strategy.\n * Requires either a ``marketId`` or ``marketName`` to be provided to resolve the market.\n * @param {CommitOrderSpot} data The data for the async order.\n * @param {Side} data.side The side of the order (buy/sell).\n * @param {number} data.size The order size in ether. If ``side`` is \"buy\", this is the amount\n * of the synth to buy. If ``side`` is \"sell\", this is the amount of the synth to sell.\n * @param {number} data.slippageTolerance The slippage tolerance for the order as a percentage (0.01 = 1%). Default is 0.\n * @param {number} data.minAmountReceived The minimum amount to receive in ether units. This will override the slippage_tolerance.\n * @param {number} data.settlementStrategyId The settlement strategy ID. Default 2.\n * @param {MarketIdOrName} data.marketIdOrName - The unique identifier or name of the market.\n * @param {OverrideParamsWrite} override - Override the default parameters for the transaction.\n */\n public async commitOrder(\n { side, size, slippageTolerance, minAmountReceived, settlementStrategyId = 0, marketIdOrName }: CommitOrderSpot,\n override: OverrideParamsWrite = {},\n ): Promise<WriteReturnType> {\n const commitTx = await this.buildCommitOrder({\n side,\n size,\n slippageTolerance,\n minAmountReceived,\n settlementStrategyId,\n marketIdOrName,\n });\n const txs = [commitTx];\n return this.sdk.utils.processTransactions(txs, { ...override });\n }\n\n /**\n * Settle an async Pyth order after price data is available.\n * Fetches the price for the order from Pyth and settles the order.\n * Retries up to ``maxTxTries`` times on failure with a delay of ``txDelay`` seconds.\n * Requires either a ``marketId`` or ``marketName`` to be provided to resolve the market.\n * @param {SettleOrder} data The data for the async order.\n * @param {string} asyncOrderId The ID of the async order to settle.\n * @param {MarketIdOrName} marketIdOrName The unique identifier or name of the market.\n * @param {OverrideParamsWrite} override - Override the default parameters for the transaction.\n * @returns {WriteReturnType} The transaction hash if ``submit`` is ``true``.\n */\n public async settleOrder(\n { asyncOrderId, marketIdOrName }: SettleOrder,\n override: OverrideParamsWrite = {},\n ): Promise<WriteReturnType> {\n const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);\n const spotMarketProxy = await this.sdk.contracts.getSpotMarketProxyInstance();\n\n const order = await this.getOrder({ asyncOrderId, marketIdOrName: resolvedMarketId });\n if (order.settledAt == undefined || order.commitmentTime == undefined) {\n throw new Error('Invalid fields for order: undefined');\n }\n const settlementStrategy = order.settlementStrategy;\n const settlementTime = order.commitmentTime + (settlementStrategy?.settlementDelay ?? 0);\n const expirationTime = order.commitmentTime + (settlementStrategy?.settlementWindowDuration ?? 0);\n\n const currentTimestamp = Math.floor(Date.now() / 1000);\n\n if (order.settledAt > 0)\n throw new Error(`Order ${asyncOrderId} on market ${resolvedMarketId} is already settled for account`);\n\n if (expirationTime < currentTimestamp)\n throw new Error(`Order ${asyncOrderId} on market ${resolvedMarketId} has expired`);\n\n if (settlementTime > currentTimestamp) {\n const duration = settlementTime - currentTimestamp;\n this.sdk.logger.info(`Waiting ${duration} seconds to settle order`);\n await sleep(duration);\n }\n\n this.sdk.logger.info(`Order ${asyncOrderId} on market ${resolvedMarketId} is ready to be settled`);\n const settleTx: Call3Value = {\n target: spotMarketProxy.address,\n callData: encodeFunctionData({\n abi: spotMarketProxy.abi,\n functionName: 'settleOrder',\n args: [resolvedMarketId, asyncOrderId],\n }),\n value: 0n,\n requireSuccess: true,\n };\n const txs = [settleTx];\n return this.sdk.utils.processTransactions(txs, { ...override });\n }\n\n public async buildApprove({\n spender,\n amount,\n token,\n }: {\n spender: Address;\n amount: number;\n token: Address;\n }): Promise<Call3Value> {\n const amountInWei = convertEtherToWei(amount);\n const tx = {\n target: token,\n callData: encodeFunctionData({\n abi: erc20Abi,\n functionName: 'approve',\n args: [spender as Hex, amountInWei],\n }),\n value: 0n,\n requireSuccess: true,\n };\n\n return tx;\n }\n\n /**\n * Approve an address to transfer a specified synth from the connected address.\n * Approves the ``targetAddress`` to transfer up to the ``amount`` from your account.\n * If ``amount`` is ``undefined``, approves the maximum possible amount.\n * Requires either a ``marketId`` or ``marketName`` to be provided to resolve the market.\n * @param {string} data.targetAddress The address to approve.\n * @param {number} data.amount The amount in ether to approve. Default is max uint256.\n * @param {MarketIdOrName} dart.marketIdOrName - The unique identifier or name of the market.\n * @param {OverrideParamsWrite} override - Override the default parameters for the transaction.\n * @returns {WriteReturnType} The transaction hash if ``submit`` is ``true``.\n */\n public async approve(\n { targetAddress, amount = 0, marketIdOrName }: Approve,\n override: Omit<OverrideParamsWrite, 'useOracleCalls'> = {},\n ): Promise<WriteReturnType> {\n let amountInWei: bigint = maxUint256;\n if (amount) {\n amountInWei = convertEtherToWei(amount);\n }\n\n const synthContract = await this.getSynthContract(marketIdOrName);\n\n const txs = [\n {\n target: synthContract.address,\n callData: encodeFunctionData({\n abi: synthContract.abi,\n functionName: 'approve',\n args: [targetAddress as Hex, amountInWei],\n }),\n value: 0n,\n requireSuccess: true,\n },\n ];\n\n return this.sdk.utils.processTransactions(txs, { ...override, useOracleCalls: false });\n }\n}\n","import { Hex, zeroAddress } from 'viem';\nimport { MarketIdOrName } from '../interface/commonTypes';\nimport { SUPPORTED_CHAINS } from './chains';\n\nexport const ZERO_ADDRESS: Hex = zeroAddress;\n\nexport const SIG_ORACLE_DATA_REQUIRED = '0xcf2cabdf';\nexport const SIG_FEE_REQUIRED = '0x0e7186fb';\nexport const SIG_ERRORS = '0x0b42fd17';\n\n// for markets metadata it is requesting too much oracle updates\nexport const MAX_ERC7412_RETRIES = 80; // Limit the max failures to prevent infinite loops\n\n// List of market ids disabled by chainId\nexport const DISABLED_MARKETS: { [key: number]: number[] } = {\n 84532: [3, 6300],\n 8453: [6300],\n};\n\nexport const publicRpcEndpoints: { [key: number]: string } = {\n 8453: 'https://base.llamarpc.com',\n 84532: 'https://sepolia.base.org',\n 42161: 'https://arbitrum.llamarpc.com',\n 421614: 'https://sepolia-rollup.arbitrum.io/rpc',\n};\n\n// Logger constants\nexport type LOGGER_MESSAGE_TYPE = 'json' | 'pretty' | 'hidden';\n\n// 0: silly, 1: trace, 2: debug, 3: info, 4: warn, 5: error, 6: fatal\nexport const DEFAULT_LOGGER_LEVEL = 4; // https://tslog.js.org/#/?id=highlights to see more levels\nexport const CUSTOM_DECIMALS: Record<number, Record<MarketIdOrName, number>> = {\n [SUPPORTED_CHAINS.ARBITUM_SEPOLIA]: {\n 2: 6,\n USDC: 6,\n },\n [SUPPORTED_CHAINS.ARBITRUM]: {\n 2: 6,\n USDC: 6,\n },\n [SUPPORTED_CHAINS.BASE_SEPOLIA]: {\n 1: 6,\n USDC: 6,\n },\n [SUPPORTED_CHAINS.BASE]: {\n 1: 6,\n USDC: 6,\n },\n};\n","import { Address, formatEther, maxUint128, parseEther } from 'viem';\n\nimport { mainnet, base, optimism, arbitrum, baseSepolia, arbitrumSepolia, Chain } from 'viem/chains';\nimport { randomBytes } from 'crypto';\nimport { publicRpcEndpoints } from '../constants';\nimport { ZAP_BY_CHAIN } from '../contracts/addreses/zap';\n\nexport function getPublicRpcEndpoint(chainId: number) {\n return publicRpcEndpoints[chainId];\n}\n\n/**\n * Gets the chain object for the given chain id.\n * @param chainId - Chain id of the target EVM chain.\n * @returns Viem's chain object.\n */\nexport function getChain(chainId: number): Chain {\n const chains = [mainnet, base, optimism, arbitrum, baseSepolia, arbitrumSepolia];\n for (const chain of Object.values(chains)) {\n if (chain.id === chainId) {\n return chain;\n }\n }\n throw new Error(`Chain with id ${chainId} not found`);\n}\n\nexport function convertWeiToEther(amountInWei: string | bigint | undefined): number {\n if (amountInWei == undefined) {\n throw new Error('Invalid amount received during conversion: undefined');\n }\n if (typeof amountInWei == 'bigint') {\n return Number(formatEther(amountInWei));\n } else if (typeof amountInWei == 'string') {\n return Number(formatEther(BigInt(amountInWei)));\n } else {\n throw new Error('Expected string or bigint for conversion');\n }\n}\n\nexport function convertEtherToWei(amount: string | number | undefined): bigint {\n if (amount == undefined) {\n throw new Error('Invalid amount received during conversion: undefined');\n }\n if (typeof amount == 'number') {\n return parseEther(amount.toString());\n } else if (typeof amount == 'string') {\n return parseEther(amount);\n } else {\n throw new Error('Expected string or bigint for conversion');\n }\n}\n\nexport function sleep(seconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n}\n\nexport function generateRandomAccountId(): bigint {\n const maxUint128Half = maxUint128 / BigInt(2);\n\n const buffer = randomBytes(8); // 8 bytes * 8 bits/byte = 64 bits\n const randomAccountId = BigInt('0x' + buffer.toString('hex'));\n if (randomAccountId > maxUint128Half) {\n throw new Error('Account ID greater than Maxuint128');\n }\n return randomAccountId;\n}\n\n/**\n * @name batchArray\n * @description This function batches an array into smaller subarrays based on a given size. It uses the reduce method to iterate over the array and push each batch into an accumulator array.\n * @param {any[]} arr - The array that needs to be batched.\n * @param {number} batchSize - The number of elements to include in each subarray.\n * @returns {any[][]} - An array of arrays, where each inner array contains the batched subarrays.\n */\nexport const batchArray = <T>(arr: T[], batchSize: number): T[][] => {\n return arr.reduce((acc, _, i) => (i % batchSize ? acc : [...acc, arr.slice(i, i + batchSize)]), [] as T[][]);\n};\n\nexport type QuoteParams = {\n fromChain: number;\n fromToken: Address;\n toToken: Address;\n fromAmount: string;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type FetcherArgs = Omit<RequestInit, 'body'> & { body?: any };\n\nexport const fetcher = (\n url: string,\n init: FetcherArgs = {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n) =>\n fetch(url, {\n ...init,\n body: JSON.stringify(init.body),\n headers: init.headers,\n method: init.body && !init.method ? 'POST' : init.method,\n });\n\nconst odoFetcher = async (url: string, options: FetcherArgs) => {\n return fetcher(`https://api.odos.xyz/sor${url}`, options);\n};\n\nconst assemblePath = async (user: string, pathId: string) => {\n const response = await odoFetcher(`/assemble`, {\n body: {\n userAddr: user,\n pathId,\n },\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) return '';\n const data = (await response.json()) as { transaction: { data: string } };\n\n return data.transaction.data;\n};\n\nexport const getOdosPath = async (quoteParams: QuoteParams) => {\n if (!quoteParams.fromToken || !quoteParams.toToken || !quoteParams.fromAmount) {\n throw new Error('Missing required parameters for Odos path generation');\n }\n\n const data = {\n chainId: quoteParams.fromChain,\n inputTokens: [{ tokenAddress: quoteParams.fromToken, amount: quoteParams.fromAmount }],\n outputTokens: [{ tokenAddress: quoteParams.toToken, proportion: 1 }],\n userAddr: ZAP_BY_CHAIN[quoteParams.fromChain],\n };\n\n const response = await odoFetcher('/quote/v2', {\n body: data,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n console.error(`Failed to get Odos quote: ${response.status} ${response.statusText}`);\n return { path: '' };\n }\n\n const quote = (await response.json()) as { pathId: string };\n if (!quote?.pathId) {\n console.error('Invalid response from Odos API: missing pathId');\n return { path: '' };\n }\n const quoteId = quote.pathId;\n\n const path = await assemblePath(data.userAddr, quoteId);\n if (!path) {\n console.error('Failed to assemble path from pathId');\n }\n\n return { path };\n};\n","import { Address, zeroAddress } from 'viem';\nimport { SUPPORTED_CHAINS } from '../../constants/chains';\n\nexport const ZAP_BY_CHAIN: Record<number, Address> = {\n [SUPPORTED_CHAINS.BASE]: '0x459Bbb4231f0a606DC514BacD5712A5922CBe6c8',\n [SUPPORTED_CHAINS.BASE_SEPOLIA]: zeroAddress,\n [SUPPORTED_CHAINS.ARBITRUM]: '0x9ec181B2E69fB36C50031F0c87Bc0749b766A9f4',\n [SUPPORTED_CHAINS.ARBITUM_SEPOLIA]: '0xb569ed692206a1d73996088ae646333b1d59d9c5',\n};\n\nexport const SYNTHETIX_ZAP: Record<number, Address> = {\n [SUPPORTED_CHAINS.BASE_SEPOLIA]: '0xA6Fab50eB36F3eB2118eE2f4C12C968F8608bbc9',\n [SUPPORTED_CHAINS.BASE]: '0xA6Fab50eB36F3eB2118eE2f4C12C968F8608bbc9',\n};\n","export * from './common';\n\nimport {\n Abi,\n Address,\n CallExecutionError,\n CallParameters,\n decodeAbiParameters,\n decodeErrorResult,\n decodeFunctionResult,\n encodeAbiParameters,\n encodeFunctionData,\n Hash,\n Hex,\n parseAbiParameters,\n StateMapping,\n StateOverride,\n} from 'viem';\nimport { SynthetixSdk } from '..';\nimport { IERC7412Abi } from '../contracts/abis/IERC7412';\nimport { Call3Value, Result } from '../interface/contractTypes';\nimport { parseError } from './parseError';\nimport { MAX_ERC7412_RETRIES, SIG_ERRORS, SIG_FEE_REQUIRED, SIG_ORACLE_DATA_REQUIRED } from '../constants';\nimport {\n OverrideParamsRead,\n OverrideParamsWrite,\n TransactionData,\n WriteContractParams,\n WriteErc7412,\n WriteReturnType,\n} from '../interface/commonTypes';\nimport { batchArray } from './common';\nimport { ORACLE_PROXY_BY_CHAIN } from '../contracts/addreses/oracleProxy';\n/**\n * Utility class\n *\n */\nexport class Utils {\n sdk: SynthetixSdk;\n constructor(synthetixSdk: SynthetixSdk) {\n this.sdk = synthetixSdk;\n }\n\n public async isOracleCall(data: TransactionData | Address) {\n return Object.values(ORACLE_PROXY_BY_CHAIN)\n .map((a) => a.toLowerCase())\n .includes(typeof data === 'string' ? data.toLowerCase() : data.to.toLowerCase());\n }\n\n /**\n * Returns the Pyth price ids array\n * @param oracleQueryData\n * @returns Pyth Price IDs array\n */\n public getPythPriceIdsFromOracleQuery(oracleQueryData: Hex): Hex[] {\n const values = decodeAbiParameters(parseAbiParameters('uint8, uint64, bytes32[]'), oracleQueryData);\n return values[2] as Hex[];\n }\n\n /**\n * Decodes the response from a Smart contract function call\n * @param abi Contract ABI\n * @param functionName Function name to decode\n * @param result Response from function call that needs to be decoded\n * @returns Decoded result\n */\n public decodeResponse(abi: unknown, functionName: string, result: Hex) {\n const decodedResult = decodeFunctionResult({\n abi: abi as Abi,\n functionName: functionName,\n data: result,\n });\n\n return decodedResult;\n }\n\n /**\n * Determines the type of Error emitted by ERC7412 contract and prepares signed update data\n * @param data Error data emitted from ERC7412 contract\n * @returns Encoded data for oracle price update transaction\n */\n public async fetchOracleUpdateData(data: Hex): Promise<Hex> {\n const [updateType] = decodeAbiParameters([{ name: 'updateType', type: 'uint8' }], data);\n\n if (updateType === 1) {\n const [updateType, stalenessOrTime, priceIds] = decodeAbiParameters(\n [\n { name: 'updateType', type: 'uint8' },\n { name: 'stalenessTolerance', type: 'uint64' },\n { name: 'priceIds', type: 'bytes32[]' },\n ],\n data,\n );\n\n const stalenessTolerance = stalenessOrTime;\n const updateData = (await this.sdk.pyth.pythConnection.getPriceFeedsUpdateData(\n priceIds as string[],\n )) as unknown as Address[];\n\n return encodeAbiParameters(\n [\n { type: 'uint8', name: 'updateType' },\n { type: 'uint64', name: 'stalenessTolerance' },\n { type: 'bytes32[]', name: 'priceIds' },\n { type: 'bytes[]', name: 'updateData' },\n ],\n [updateType, stalenessTolerance, priceIds, updateData],\n );\n } else if (updateType === 2) {\n const [updateType, requestedTime, priceId] = decodeAbiParameters(\n [\n { name: 'updateType', type: 'uint8' },\n { name: 'requestedTime', type: 'uint64' },\n { name: 'priceIds', type: 'bytes32' },\n ],\n data,\n );\n this.sdk.logger.info('Update type: ', updateType);\n this.sdk.logger.info('priceIds: ', priceId);\n\n const [priceFeedUpdateVaa] = await this.sdk.pyth.pythConnection.getVaa(\n priceId as string,\n Number((requestedTime as unknown as bigint).toString()),\n );\n const priceFeedUpdate = '0x' + Buffer.from(priceFeedUpdateVaa, 'base64').toString('hex');\n\n return encodeAbiParameters(\n [\n { type: 'uint8', name: 'updateType' },\n { type: 'uint64', name: 'timestamp' },\n { type: 'bytes32[]', name: 'priceIds' },\n { type: 'bytes[]', name: 'updateData' },\n ],\n [updateType, requestedTime, [priceId], [priceFeedUpdate as Address]],\n );\n } else {\n throw new Error(`Error encoding/decoding data`);\n }\n }\n\n async handleOracleDataRequiredError(parsedError: Hash): Promise<Call3Value> {\n const err = decodeErrorResult({\n abi: IERC7412Abi,\n data: parsedError,\n });\n\n const oracleAddress = err.args![0] as Address;\n const oracleQuery = err.args![1] as Hex;\n const signedRequiredData = await this.fetchOracleUpdateData(oracleQuery);\n const dataVerificationTx = this.generateDataVerificationTx(oracleAddress, signedRequiredData);\n\n return dataVerificationTx;\n }\n\n /**\n * Handles ERC7412 error by creating a price update tx which is prepended to the existing calls\n * @param error Error thrown by the call function\n * @param calls Multicall call data\n * @returns call array with ERC7412 fulfillOracleQuery transaction\n */\n public async handleErc7412Error(parsedError: Hash): Promise<Call3Value[]> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let err: any;\n\n try {\n err = decodeErrorResult({\n abi: IERC7412Abi,\n data: parsedError,\n });\n } catch (decodeErr) {\n this.sdk.logger.error('Decode Error: ', decodeErr);\n throw new Error('Handle ERC7412 error');\n }\n if (!['OracleDataRequired', 'FeeRequired', 'Errors'].includes(err?.errorName))\n throw new Error('Handle ERC7412 error');\n\n if (err?.errorName === 'Errors') {\n const oracleErrors = err.args[0] as Hex[];\n let oracleCalls: Call3Value[] = [];\n const batchedOracleErrors = batchArray<Hex>(oracleErrors, 10);\n\n for (const oracleErrors of batchedOracleErrors) {\n const promiseOracleCalls = oracleErrors.map((oracleError) => {\n return this.handleErc7412Error(oracleError);\n });\n const resolvedCalls = await Promise.all(promiseOracleCalls);\n oracleCalls = [...oracleCalls, ...resolvedCalls.flat()];\n }\n\n return oracleCalls.flat();\n }\n\n if (err?.errorName === 'OracleDataRequired') {\n return [await this.handleOracleDataRequiredError(parsedError)];\n }\n\n // this.sdk.logger.info('Fee Required oracle error. Adding fee to tx.value', err);\n // if (!calls.length) throw new Error('Handle ERC7412 error: Calls.length == 0');\n //\n // calls[0].value = err.args[0] as bigint;\n\n return [];\n }\n\n /**\n * Generates Call3Value tx for Price update data of Oracle\n * @param oracleContract Oracle Contract address\n * @param signedRequiredData Encoded Price Update data\n * @returns Transaction Request for Oracle price update\n */\n public generateDataVerificationTx(_oracleContract: Hex, signedRequiredData: string): Call3Value {\n const priceUpdateCall: Call3Value = {\n target: ORACLE_PROXY_BY_CHAIN[this.sdk.rpcConfig.chainId],\n callData: encodeFunctionData({\n abi: IERC7412Abi as unknown as Abi,\n functionName: 'fulfillOracleQuery',\n args: [signedRequiredData],\n }),\n value: 0n,\n requireSuccess: false,\n };\n return priceUpdateCall;\n }\n\n /**\n * Calls the `functionName` on `contractAddress` target using the Multicall contract. If the call requires\n * a price update, ERC7412 price update tx is prepended to the tx.\n * @param contractAddress Target contract address for the call\n * @param abi Contract ABI\n * @param functionName Function to be called on the contract\n * @param args Arguments list for the function call\n * @param calls Array of Call3Value calls for Multicall contract\n * @returns Response from the contract function call\n */\n public async callErc7412({ contractAddress, abi, args, functionName, calls = [] }: WriteContractParams) {\n const multicallInstance = await this.sdk.contracts.getMulticallInstance();\n\n const currentCall: Call3Value = {\n target: contractAddress,\n callData: encodeFunctionData({\n abi: abi as Abi,\n functionName: functionName,\n args: args,\n }),\n value: 0n,\n requireSuccess: true,\n };\n\n calls.push(currentCall);\n\n const publicClient = this.sdk.getPublicClient();\n\n const oracleCalls = await this.getMissingOracleCalls(calls);\n const multicallData = encodeFunctionData({\n abi: multicallInstance.abi,\n functionName: 'aggregate3Value',\n args: [[...oracleCalls, ...calls]],\n });\n\n const totalValue = [...oracleCalls, ...calls].reduce((acc, tx) => {\n return acc + (tx.value || 0n);\n }, 0n);\n\n const finalTx = {\n account: this.sdk.accountAddress,\n to: multicallInstance.address,\n data: multicallData,\n value: totalValue,\n };\n\n const response = await publicClient.call(finalTx);\n this.sdk.logger.info('=== response', response);\n if (!response.data) throw new Error('Error decoding call data');\n const multicallResult: Result[] = this.decodeResponse(\n multicallInstance.abi,\n 'aggregate3Value',\n response.data as Hex,\n ) as unknown as Result[];\n\n const returnData = multicallResult.at(-1);\n\n if (!returnData?.success) throw new Error('Error decoding call data');\n\n const decodedResult = this.decodeResponse(abi, functionName, returnData.returnData);\n return decodedResult;\n }\n\n /**\n * Calls the `functionName` on `contractAddress` target using the Multicall contract. If the call requires\n * a price update, ERC7412 price update tx is prepended to the tx.\n * @param contractAddress Target contract address for the call\n * @param abi Contract ABI\n * @param functionName Function to be called on the contract\n * @param argsList Array of arguments list for the function call\n * @param calls Array of Call3Value calls for Multicall contract\n * @returns Array of responses from the contract function call for the multicalls\n */\n public async multicallErc7412({\n contractAddress,\n abi,\n functionName,\n args: argsList,\n calls = [],\n }: WriteContractParams) {\n const multicallInstance = await this.sdk.contracts.getMulticallInstance();\n // Format the args to the required array format\n argsList = argsList.map((args) => (Array.isArray(args) ? args : [args]));\n\n argsList.forEach((args) => {\n const currentCall: Call3Value = {\n target: contractAddress,\n callData: encodeFunctionData({\n abi: abi as Abi,\n functionName: functionName,\n args: args as unknown[],\n }),\n value: 0n,\n requireSuccess: true,\n };\n calls.push(currentCall);\n });\n calls = calls.map((call) => {\n return {\n ...call,\n requireSuccess: call.value === 0n ? true : false,\n };\n });\n const oracleCalls = await this.getMissingOracleCalls(calls);\n\n const numCalls = calls.length - oracleCalls.length;\n\n const publicClient = this.sdk.getPublicClient();\n\n const totalValue = [...oracleCalls, ...calls].reduce((acc, tx) => {\n return acc + (tx.value || 0n);\n }, 0n);\n\n const multicallData = encodeFunctionData({\n abi: multicallInstance.abi,\n functionName: 'aggregate3Value',\n args: [[...oracleCalls, ...calls]],\n });\n\n const finalTx = {\n account: this.sdk.accountAddress,\n to: multicallInstance.address,\n data: multicallData,\n value: totalValue,\n };\n\n const response = await publicClient.call(finalTx);\n const multicallResult: Result[] = this.decodeResponse(\n multicallInstance.abi,\n 'aggregate3Value',\n response.data as Hex,\n ) as unknown as Result[];\n\n const callsToDecode = multicallResult.slice(-numCalls);\n\n const decodedResult = callsToDecode.map((result) => this.decodeResponse(abi, functionName, result.returnData));\n return decodedResult;\n }\n protected isWriteContractParams(data: WriteErc7412): data is WriteContractParams {\n return (data as WriteContractParams).contractAddress !== undefined;\n }\n\n /**\n * Simulates the `functionName` on `contractAddress` target using the Multicall contract and returns the\n * final transaction call with ERC7412 price update data if required\n