@parifi/synthetix-sdk-ts
Version:
A Typescript SDK for interactions with the Synthetix protocol
1,256 lines (1,242 loc) • 90.1 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/perps/index.ts
var perps_exports = {};
__export(perps_exports, {
Perps: () => Perps
});
module.exports = __toCommonJS(perps_exports);
var import_viem7 = require("viem");
// src/utils/common.ts
var import_viem3 = require("viem");
var import_chains3 = require("viem/chains");
var import_crypto = require("crypto");
// src/constants/protocol.ts
var DEFAULT_DECIMALS = 18;
// src/constants/common.ts
var import_viem = require("viem");
var DISABLED_MARKETS = {
84532: [3, 6300],
8453: [6300]
};
var CUSTOM_DECIMALS = {
[421614 /* ARBITUM_SEPOLIA */]: {
2: 6,
USDC: 6
},
[42161 /* ARBITRUM */]: {
2: 6,
USDC: 6
},
[84532 /* BASE_SEPOLIA */]: {
1: 6,
USDC: 6
},
[8453 /* BASE */]: {
1: 6,
USDC: 6
}
};
// src/contracts/addreses/zap.ts
var import_viem2 = require("viem");
var ZAP_BY_CHAIN = {
[8453 /* BASE */]: "0x459Bbb4231f0a606DC514BacD5712A5922CBe6c8",
[84532 /* BASE_SEPOLIA */]: import_viem2.zeroAddress,
[42161 /* ARBITRUM */]: "0x9ec181B2E69fB36C50031F0c87Bc0749b766A9f4",
[421614 /* ARBITUM_SEPOLIA */]: "0xb569ed692206a1d73996088ae646333b1d59d9c5"
};
var SYNTHETIX_ZAP = {
[84532 /* BASE_SEPOLIA */]: "0xA6Fab50eB36F3eB2118eE2f4C12C968F8608bbc9",
[8453 /* BASE */]: "0xA6Fab50eB36F3eB2118eE2f4C12C968F8608bbc9"
};
// src/utils/common.ts
function convertWeiToEther(amountInWei) {
if (amountInWei == void 0) {
throw new Error("Invalid amount received during conversion: undefined");
}
if (typeof amountInWei == "bigint") {
return Number((0, import_viem3.formatEther)(amountInWei));
} else if (typeof amountInWei == "string") {
return Number((0, import_viem3.formatEther)(BigInt(amountInWei)));
} else {
throw new Error("Expected string or bigint for conversion");
}
}
function convertEtherToWei(amount) {
if (amount == void 0) {
throw new Error("Invalid amount received during conversion: undefined");
}
if (typeof amount == "number") {
return (0, import_viem3.parseEther)(amount.toString());
} else if (typeof amount == "string") {
return (0, import_viem3.parseEther)(amount);
} else {
throw new Error("Expected string or bigint for conversion");
}
}
function sleep(seconds) {
return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
}
function generateRandomAccountId() {
const maxUint128Half = import_viem3.maxUint128 / BigInt(2);
const buffer = (0, import_crypto.randomBytes)(8);
const randomAccountId = BigInt("0x" + buffer.toString("hex"));
if (randomAccountId > maxUint128Half) {
throw new Error("Account ID greater than Maxuint128");
}
return randomAccountId;
}
var batchArray = (arr, batchSize) => {
return arr.reduce((acc, _, i) => i % batchSize ? acc : [...acc, arr.slice(i, i + batchSize)], []);
};
var fetcher = (url, init = {
headers: {
"Content-Type": "application/json"
}
}) => fetch(url, {
...init,
body: JSON.stringify(init.body),
headers: init.headers,
method: init.body && !init.method ? "POST" : init.method
});
var odoFetcher = async (url, options) => {
return fetcher(`https://api.odos.xyz/sor${url}`, options);
};
var assemblePath = async (user, pathId) => {
const response = await odoFetcher(`/assemble`, {
body: {
userAddr: user,
pathId
},
headers: {
"Content-Type": "application/json"
}
});
if (!response.ok) return "";
const data = await response.json();
return data.transaction.data;
};
var getOdosPath = async (quoteParams) => {
if (!quoteParams.fromToken || !quoteParams.toToken || !quoteParams.fromAmount) {
throw new Error("Missing required parameters for Odos path generation");
}
const data = {
chainId: quoteParams.fromChain,
inputTokens: [{ tokenAddress: quoteParams.fromToken, amount: quoteParams.fromAmount }],
outputTokens: [{ tokenAddress: quoteParams.toToken, proportion: 1 }],
userAddr: ZAP_BY_CHAIN[quoteParams.fromChain]
};
const response = await odoFetcher("/quote/v2", {
body: data,
headers: {
"Content-Type": "application/json"
}
});
if (!response.ok) {
console.error(`Failed to get Odos quote: ${response.status} ${response.statusText}`);
return { path: "" };
}
const quote = await response.json();
if (!quote?.pathId) {
console.error("Invalid response from Odos API: missing pathId");
return { path: "" };
}
const quoteId = quote.pathId;
const path = await assemblePath(data.userAddr, quoteId);
if (!path) {
console.error("Failed to assemble path from pathId");
}
return { path };
};
// src/utils/index.ts
var import_viem4 = require("viem");
// src/contracts/addreses/oracleProxy.ts
var ORACLE_PROXY_BY_CHAIN = {
[421614 /* ARBITUM_SEPOLIA */]: "0x59d6ec32e05900949d7fff679a4adc7f94f0208c",
[42161 /* ARBITRUM */]: "0xe87ceB87b63267ef925E2897B629052eb815bB7d",
[8453 /* BASE */]: "0xa03A0Be2f3B85B76765A442683d62E992972d816",
[84532 /* BASE_SEPOLIA */]: "0x4Bc47c016EE6Ead253152CCfe6427D0bC06F544c"
};
// src/utils/market.ts
var import_viem5 = require("viem");
var Market = class {
constructor(synthetixSdk) {
this.disabledMarkets = [];
this.sdk = synthetixSdk;
this.marketsById = /* @__PURE__ */ new Map();
this.marketsByName = /* @__PURE__ */ new Map();
this.marketsBySymbol = /* @__PURE__ */ new Map();
if (synthetixSdk.rpcConfig.chainId in DISABLED_MARKETS) {
this.disabledMarkets = DISABLED_MARKETS[synthetixSdk.rpcConfig.chainId];
}
}
/**
* Look up the market_id and market_name for a market. If only one is provided,
* the other is resolved. If both are provided, they are checked for consistency.
* @param marketIdOrName Id or name of the market to resolve
*/
async resolveMarket(marketIdOrName) {
if (!this.sdk.resolveMarketNames && typeof marketIdOrName === "number") {
console.log("Resolve markets set to false. Returning market id without validating...");
return { resolvedMarketName: "Unresolved Market", resolvedMarketId: Number(marketIdOrName) };
}
const market = await this.getMarket(marketIdOrName);
if (!market?.marketName || market?.marketId === void 0)
throw new Error(`Market not found for ${marketIdOrName}`);
return { resolvedMarketName: market.marketName, resolvedMarketId: market.marketId };
}
/**
* Format the size of a synth for an order. This is used for synths whose base asset
* does not use 18 decimals. For example, USDC uses 6 decimals, so we need to handle size
* differently from other assets.
* @param size The size as an ether value (e.g. 100).
* @param marketId The id of the market.
* @returns The formatted size in wei. (e.g. 100 = 100000000000000000000)
*/
async formatSize(size, marketId) {
return (0, import_viem5.parseUnits)(size.toString(), CUSTOM_DECIMALS[this.sdk.rpcConfig.chainId][marketId] || 18);
}
async prepareOracleCallsWithPriceId(priceFeedIds) {
if (!priceFeedIds.length) {
return [];
}
const stalenessTolerance = 30n;
const updateData = await this.sdk.pyth.getPriceFeedsUpdateData(priceFeedIds);
const signedRequiredData = (0, import_viem5.encodeAbiParameters)(
[
{ type: "uint8", name: "updateType" },
{ type: "uint64", name: "stalenessTolerance" },
{ type: "bytes32[]", name: "priceIds" },
{ type: "bytes[]", name: "updateData" }
],
[1, stalenessTolerance, priceFeedIds, updateData]
);
const pythWrapper = await this.sdk.contracts.getPythErc7412WrapperInstance();
const dataVerificationTx = this.sdk.utils.generateDataVerificationTx(pythWrapper.address, signedRequiredData);
return [{ ...dataVerificationTx, value: 0n, requireSuccess: false }];
}
/**
* Prepare a call to the external node with oracle updates for the specified market names.
* The result can be passed as the first argument to a multicall function to improve performance
* of ERC-7412 calls. If no market names are provided, all markets are fetched. This is useful for
* read functions since the user does not pay gas for those oracle calls, and reduces RPC calls and
* runtime.
* @param {number[]} marketIds An array of market ids to fetch prices for. If not provided, all markets are fetched
* @returns {Promise<Call3Value[]>} objects representing the target contract, call data, value, requireSuccess flag and other necessary details for executing the function in the blockchain.
*/
async prepareOracleCall(marketIds = []) {
let priceFeedIds = [];
if (marketIds.length != 0) {
const marketSymbols = [];
marketIds.forEach((marketId) => {
const marketSymbol = this.marketsById.get(marketId)?.symbol;
if (!marketSymbol) return;
marketSymbols.push(marketSymbol);
});
marketSymbols.forEach((marketSymbol) => {
const feedId = this.sdk.pyth.priceFeedIds.get(marketSymbol);
if (!feedId) return;
priceFeedIds.push(feedId);
});
} else {
priceFeedIds = Array.from(this.sdk.pyth.priceFeedIds.values());
}
return this.prepareOracleCallsWithPriceId(priceFeedIds);
}
async getMarket(marketIdOrName) {
throw new Error("Method not implemented. " + marketIdOrName);
}
};
// src/utils/override.ts
var import_viem6 = require("viem");
// src/constants/slots.ts
var tokensSlots = [
{ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", balanceSlot: 9, approveSlot: 10 },
{ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", balanceSlot: 9, approveSlot: 10 },
{ address: "0x4EA71A20e655794051D1eE8b6e4A3269B13ccaCc", balanceSlot: 5, approveSlot: 6 },
{ address: "0x4EA71A20e655794051D1eE8b6e4A3269B13ccaCc", balanceSlot: 5, approveSlot: 6 },
{ address: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", balanceSlot: 9, approveSlot: 10 },
{ address: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", balanceSlot: 9, approveSlot: 10 },
{ address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", balanceSlot: 51, approveSlot: 52 },
{ address: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", balanceSlot: 51, approveSlot: 52 },
{ address: "0x4200000000000000000000000000000000000006", balanceSlot: 3, approveSlot: 4 },
{ address: "0x4200000000000000000000000000000000000006", balanceSlot: 3, approveSlot: 4 },
{ address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", balanceSlot: 1, approveSlot: 2 },
{ address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", balanceSlot: 1, approveSlot: 2 },
{
address: "0xc43708f8987Df3f3681801e5e640667D86Ce3C30",
balanceSlot: 0,
approveSlot: 1
},
{
address: "0x8608d511E224180051A36d34121725D978064e6E",
balanceSlot: 0,
approveSlot: 1
},
{
address: "0x4200000000000000000000000000000000000006",
balanceSlot: 3,
approveSlot: 4
},
{
address: "0xB3f05d39504dA95876EA0174D25Ae51Ac2422a70",
balanceSlot: 0,
approveSlot: 1
},
{
address: "0x00ab6b818652bB3bFE334983171edFD38184DbeD",
balanceSlot: 0,
approveSlot: 1
},
{
address: "0x7Bf65af7EFBd0E933fb87dD2C9cE7A17d959b822",
balanceSlot: 0,
approveSlot: 1
}
];
// src/utils/override.ts
function erc20BalanceOverride({
token,
owner,
slot,
balance = BigInt("0x100000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
}) {
const smartAccountErc20BalanceSlot = (0, import_viem6.keccak256)(
(0, import_viem6.encodeAbiParameters)(
[
{
type: "address"
},
{
type: "uint256"
}
],
[owner, slot]
)
);
return [
{
address: token,
stateDiff: [
{
slot: smartAccountErc20BalanceSlot,
value: (0, import_viem6.toHex)(balance)
}
]
}
];
}
function erc20AllowanceOverride({
token,
owner,
spender,
slot,
amount = BigInt("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
}) {
const smartAccountErc20AllowanceSlot = (0, import_viem6.keccak256)(
(0, import_viem6.encodeAbiParameters)(
[
{
type: "address"
},
{
type: "bytes32"
}
],
[
spender,
(0, import_viem6.keccak256)(
(0, import_viem6.encodeAbiParameters)(
[
{
type: "address"
},
{
type: "uint256"
}
],
[owner, BigInt(slot)]
)
)
]
)
);
return [
{
address: token,
stateDiff: [
{
slot: smartAccountErc20AllowanceSlot,
value: (0, import_viem6.toHex)(amount)
}
]
}
];
}
var erc20StateOverrideBalanceAndAllowance = ({
token,
owner,
balance = BigInt("0x100000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"),
spender
}) => {
const balanceSlot = tokensSlots.find((slotData) => slotData.address.toLowerCase() === token.toLowerCase());
if (!balanceSlot) throw new Error("No balance slot");
const balanceOverride = erc20BalanceOverride({ balance, slot: BigInt(balanceSlot.balanceSlot), token, owner });
const approveOverride = erc20AllowanceOverride({
spender,
amount: balance,
token,
slot: BigInt(balanceSlot.approveSlot),
owner
});
return [...balanceOverride, ...approveOverride];
};
// src/contracts/abis/zap.ts
var SYNTHETIX_ZAP_ABI = [
{
type: "constructor",
inputs: [
{ name: "perps", type: "address", internalType: "address" },
{ name: "spot", type: "address", internalType: "address" },
{ name: "account", type: "address", internalType: "address" }
],
stateMutability: "nonpayable"
},
{
type: "function",
name: "createAccountAndDeposit",
inputs: [
{ name: "accountId", type: "uint128", internalType: "uint128" },
{ name: "token", type: "address", internalType: "contract IERC20" },
{ name: "sToken", type: "address", internalType: "contract IERC20" },
{ name: "amount", type: "uint256", internalType: "uint256" },
{ name: "collateralId", type: "uint128", internalType: "uint128" }
],
outputs: [{ name: "", type: "uint128", internalType: "uint128" }],
stateMutability: "nonpayable"
},
{
inputs: [
{
internalType: "uint128",
name: "accountId",
type: "uint128"
},
{
internalType: "contract IERC20[]",
name: "token",
type: "address[]"
},
{
internalType: "contract IERC20[]",
name: "sToken",
type: "address[]"
},
{
internalType: "uint256[]",
name: "amount",
type: "uint256[]"
},
{
internalType: "uint128[]",
name: "collateralId",
type: "uint128[]"
}
],
stateMutability: "nonpayable",
type: "function",
name: "createAccountAndDeposits",
outputs: [{ internalType: "uint128", name: "", type: "uint128" }]
},
{
type: "function",
name: "deposit",
inputs: [
{ name: "accountId", type: "uint128", internalType: "uint128" },
{ name: "token", type: "address", internalType: "contract IERC20" },
{ name: "sToken", type: "address", internalType: "contract IERC20" },
{ name: "amount", type: "uint256", internalType: "uint256" },
{ name: "collateralId", type: "uint128", internalType: "uint128" }
],
stateMutability: "nonpayable"
},
{
type: "function",
name: "withdraw",
inputs: [
{ name: "accountId", type: "uint128", internalType: "uint128" },
{
name: "token",
type: "address",
internalType: "contract IERC20"
},
{ name: "amount", type: "int256", internalType: "int256" },
{ name: "collateralId", type: "uint128", internalType: "uint128" }
],
outputs: [],
stateMutability: "nonpayable"
},
{
type: "function",
name: "createAccountDepositAndCommit",
inputs: [
{ name: "accountId", type: "uint128", internalType: "uint128" },
{
name: "token",
type: "address",
internalType: "contract IERC20"
},
{
name: "sToken",
type: "address",
internalType: "contract IERC20"
},
{ name: "amount", type: "uint256", internalType: "uint256" },
{
name: "collateralId",
type: "uint128",
internalType: "uint128"
},
{
name: "commit",
type: "tuple",
internalType: "struct SynthetixZap.Commit",
components: [
{
name: "marketId",
type: "uint128",
internalType: "uint128"
},
{ name: "sizeDelta", type: "int128", internalType: "int128" },
{
name: "settlementStrategyId",
type: "uint128",
internalType: "uint128"
},
{
name: "acceptablePrice",
type: "uint256",
internalType: "uint256"
},
{
name: "trackingCode",
type: "bytes32",
internalType: "bytes32"
},
{ name: "referrer", type: "address", internalType: "address" }
]
}
],
outputs: [{ name: "", type: "uint128", internalType: "uint128" }],
stateMutability: "nonpayable"
}
];
// src/perps/index.ts
var Perps = class extends Market {
constructor(synthetixSdk) {
super(synthetixSdk);
// Markets data
this.isErc7412Enabled = true;
// Set multicollateral to false by default
this.isMulticollateralEnabled = false;
this.disabledMarkets = [];
this.isInitialized = false;
this.sdk = synthetixSdk;
this.accountIds = [];
this.marketMetadata = /* @__PURE__ */ new Map();
}
// === READ CALLS ===
async initPerps() {
if (this.isInitialized) return;
await this.getAccountIds();
const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const debtFunctionData = (0, import_viem7.getAbiItem)({
abi: marketProxy.abi,
name: "debt"
});
const payDebtFunctionData = (0, import_viem7.getAbiItem)({
abi: marketProxy.abi,
name: "payDebt"
});
if (debtFunctionData != void 0 && payDebtFunctionData != void 0) {
this.isMulticollateralEnabled = true;
this.sdk.logger.info("Multicollateral perps is enabled");
}
this.isInitialized = true;
}
/**
* Fetch a list of perps ``account_id`` owned by an address. Perps accounts
* are minted as an NFT to the owner's address. The ``account_id`` is the
* token id of the NFTs held by the address.
* @param {string} address The address to get accounts for. Uses connected address if not provided.
* @param {bigint} defaultAccountId The default account ID to set after fetching.
* @returns A list of account IDs owned by the address
*/
async getAccountIds(accountAddress = this.sdk.accountAddress, defaultAccountId) {
if (!accountAddress) throw new Error("Invalid address");
const accountProxy = await this.sdk.contracts.getPerpsAccountProxyInstance();
const balance = await accountProxy.read.balanceOf([accountAddress]);
this.sdk.logger.info("balance", balance);
const argsList = [];
for (let index = 0; index < Number(balance); index++) {
argsList.push([accountAddress, index]);
}
const accountIds = await this.sdk.utils.multicallErc7412({
contractAddress: accountProxy.address,
abi: accountProxy.abi,
functionName: "tokenOfOwnerByIndex",
args: argsList
});
if (accountIds == void 0) return [];
this.accountIds = accountIds;
this.sdk.logger.info("accountIds", accountIds);
if (defaultAccountId) {
this.defaultAccountId = defaultAccountId;
} else if (this.accountIds.length > 0) {
this.defaultAccountId = this.accountIds[0];
this.sdk.logger.info("Using default account id as ", this.defaultAccountId);
}
return accountIds;
}
async getMarkets() {
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const marketIdResponse = await perpsMarketProxy.read.getMarkets();
const marketMetadataResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "metadata",
args: marketIdResponse
});
const marketIds = marketIdResponse.map((id) => Number(id));
const settlementStrategies = await this.getSettlementStrategies(marketIds);
const { marketMetadatas, pythPriceIds } = marketMetadataResponse.reduce(
(acc, market, index) => {
const [name, symbol] = market;
const strategy = settlementStrategies.find((strategy2) => strategy2.marketId == marketIds[index]);
acc.pythPriceIds.push({
symbol,
feedId: strategy?.feedId ?? "0x"
});
acc.marketMetadatas.push({
marketName: name,
symbol,
feedId: strategy?.feedId ?? "0x"
});
return acc;
},
{
pythPriceIds: [],
marketMetadatas: []
}
);
this.sdk.pyth.updatePriceFeedIds(pythPriceIds);
const [marketSummaries, fundingParameters, orderFees, maxMarketValues] = await Promise.all([
this.getMarketSummaries(marketIds),
this.getFundingParameters(marketIds),
this.getOrderFees(marketIds),
this.getMaxMarketValues(marketIds)
]);
const datas = marketMetadatas.map((marketMetadata, index) => {
const marketId = Number(marketIds.at(index) || 0);
const marketSummary = marketSummaries.find((summary) => summary.marketId == marketId);
const fundingParam = fundingParameters.find((fundingParam2) => fundingParam2.marketId == marketId);
const orderFee = orderFees.find((orderFee2) => orderFee2.marketId == marketId);
const maxMarketValue = maxMarketValues.find((maxMarketValue2) => maxMarketValue2.marketId == marketId);
const result = {
marketId: marketIds[index],
marketName: marketMetadata.marketName,
symbol: marketMetadata.symbol,
feedId: marketMetadata.feedId,
skew: marketSummary?.skew,
size: marketSummary?.size,
maxOpenInterest: marketSummary?.maxOpenInterest,
interestRate: marketSummary?.interestRate,
currentFundingRate: marketSummary?.currentFundingRate,
currentFundingVelocity: marketSummary?.currentFundingVelocity,
indexPrice: marketSummary?.indexPrice,
skewScale: fundingParam?.skewScale,
maxFundingVelocity: fundingParam?.maxFundingVelocity,
makerFee: orderFee?.makerFeeRatio,
takerFee: orderFee?.takerFeeRatio,
maxMarketValue: maxMarketValue?.maxMarketValue
};
this.marketMetadata.set(marketId, marketMetadata);
this.marketsById.set(marketId, result);
this.marketsByName.set(marketMetadata.marketName, result);
this.marketsBySymbol.set(marketMetadata.symbol, result);
return result;
});
return datas;
}
async getMarket(marketIdOrName) {
const market = (this.marketsById.get(Number(marketIdOrName)) ?? this.marketsByName.get(marketIdOrName)) || {};
if (market.marketId) {
return market;
}
let marketId, marketName, marketSymbol;
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
if (typeof marketIdOrName === "number" || typeof marketIdOrName === "bigint") {
const marketMetadataResponse = await this.sdk.utils.callErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "metadata",
args: [marketIdOrName]
});
marketId = Number(marketIdOrName);
marketName = marketMetadataResponse[0];
marketSymbol = marketMetadataResponse[1];
} else {
const marketIdResponse = await perpsMarketProxy.read.getMarkets();
const marketMetadataResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "metadata",
args: marketIdResponse
});
marketMetadataResponse.forEach((metadata, idx) => {
if (metadata[0] == marketIdOrName) {
marketId = marketIdResponse[idx];
marketName = metadata[0];
marketSymbol = metadata[1];
}
});
}
const functionNames = [];
const argsList = [];
functionNames.push("getSettlementStrategy");
argsList.push([marketId, 0]);
functionNames.push("getMarketSummary");
argsList.push([marketId]);
functionNames.push("interestRate");
argsList.push([]);
functionNames.push("getFundingParameters");
argsList.push([marketId]);
functionNames.push("getOrderFees");
argsList.push([marketId]);
functionNames.push("getMaxMarketValue");
argsList.push([marketId]);
const multicallResponse = await this.sdk.utils.multicallMultifunctionErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionNames,
args: argsList
});
const settlementStrategy = multicallResponse.at(0);
const marketSummary = multicallResponse.at(1);
const interestRate = multicallResponse.at(2);
const fundingParams = multicallResponse.at(3);
const orderFees = multicallResponse.at(4);
const maxMarketValue = multicallResponse.at(5);
market.marketId = marketId || 0;
market.marketName = marketName || "INVALID MARKET";
market.symbol = marketSymbol || "INVALID";
market.feedId = settlementStrategy.feedId;
market.skew = Number((0, import_viem7.formatEther)(marketSummary.skew));
market.size = Number((0, import_viem7.formatEther)(marketSummary.size));
market.maxOpenInterest = Number((0, import_viem7.formatEther)(marketSummary.maxOpenInterest));
market.interestRate = Number((0, import_viem7.formatEther)(interestRate));
market.currentFundingRate = Number((0, import_viem7.formatEther)(marketSummary.currentFundingRate));
market.currentFundingVelocity = Number((0, import_viem7.formatEther)(marketSummary.currentFundingVelocity));
market.indexPrice = Number((0, import_viem7.formatEther)(marketSummary.indexPrice));
market.skewScale = Number((0, import_viem7.formatEther)(fundingParams.at(0) || 0n));
market.maxFundingVelocity = Number((0, import_viem7.formatEther)(fundingParams.at(1) || 0n));
market.makerFee = Number((0, import_viem7.formatEther)(orderFees.at(0) || 0n));
market.takerFee = Number((0, import_viem7.formatEther)(orderFees.at(1) || 0n));
market.maxMarketValue = Number((0, import_viem7.formatEther)(maxMarketValue));
this.marketMetadata.set(market.marketId || 0, {
marketName: market.marketName,
symbol: market.symbol,
feedId: market.feedId
});
this.marketsById.set(market.marketId, market);
this.marketsByName.set(market.marketName, market);
this.marketsBySymbol.set(market.symbol, market);
return market;
}
/**
* Fetch the market summaries for an array of marketIds
* @param marketIds Array of market ids to fetch
* @returns Summary of market ids data fetched from the contract
*/
async getMarketSummaries(marketIds) {
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const interestRate = await this.sdk.utils.callErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "interestRate",
args: []
});
const marketSummariesInput = marketIds.map((marketId) => [marketId]);
const marketSummariesResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getMarketSummary",
args: marketSummariesInput
});
if (marketIds.length !== marketSummariesResponse.length) {
this.sdk.logger.info("Inconsistent data");
}
const marketSummaries = [];
const batchedResponse = batchArray(
marketSummariesResponse.map((market, index) => {
return {
...market,
marketId: marketIds[index]
};
}),
10
);
for (const batch of batchedResponse) {
const promises = batch.map(async (market) => {
const marketId = market.marketId;
marketSummaries.push({
marketId,
marketName: (await this.getMarket(marketId)).marketName,
feedId: (await this.getMarket(marketId)).feedId,
indexPrice: Number((0, import_viem7.formatEther)(market.indexPrice)),
skew: Number((0, import_viem7.formatEther)(market.skew)),
size: Number((0, import_viem7.formatEther)(market.size)),
maxOpenInterest: Number((0, import_viem7.formatEther)(market.maxOpenInterest)),
interestRate: Number((0, import_viem7.formatEther)(interestRate)),
currentFundingRate: Number((0, import_viem7.formatEther)(market.currentFundingRate)),
currentFundingVelocity: Number((0, import_viem7.formatEther)(market.currentFundingVelocity))
});
});
await Promise.allSettled(promises);
}
return marketSummaries;
}
/**
* @name getMarketSummary
* @description Fetches the summary of a given market. This includes information like skew, size, max open interest, current funding rate, and more.
* @param {string|number} marketIdOrName - The identifier or name of the market to fetch the summary for.
* @returns {MarketSummary} - An object containing the summary data for the specified market.
*/
async getMarketSummary(marketIdOrName) {
const { resolvedMarketId, resolvedMarketName } = await this.resolveMarket(marketIdOrName);
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const interestRate = await this.sdk.utils.callErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "interestRate",
args: []
});
const marketSummaryResponse = await this.sdk.utils.callErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getMarketSummary",
args: [resolvedMarketId]
});
this.sdk.logger.info("marketSummaryResponse", marketSummaryResponse);
return {
marketId: resolvedMarketId,
marketName: resolvedMarketName,
feedId: "",
indexPrice: Number((0, import_viem7.formatEther)(marketSummaryResponse.indexPrice)),
skew: Number((0, import_viem7.formatEther)(marketSummaryResponse.skew)),
size: Number((0, import_viem7.formatEther)(marketSummaryResponse.size)),
maxOpenInterest: Number((0, import_viem7.formatEther)(marketSummaryResponse.maxOpenInterest)),
interestRate: Number((0, import_viem7.formatEther)(interestRate)),
currentFundingRate: Number((0, import_viem7.formatEther)(marketSummaryResponse.currentFundingRate)),
currentFundingVelocity: Number((0, import_viem7.formatEther)(marketSummaryResponse.currentFundingVelocity))
};
}
/**
* @name getSettlementStrategy
* @description This function retrieves the settlement strategy for a given market by its ID and either Market ID or Name.
* @param {number} settlementStrategyId - The ID of the settlement strategy to retrieve.
* @param {MarketIdOrName} marketIdOrName - The unique identifier (ID or Name) of the market this settlement strategy belongs to.
* @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.
*/
async getSettlementStrategy(settlementStrategyId, marketIdOrName) {
const { resolvedMarketId } = await this.resolveMarket(marketIdOrName);
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const settlementStrategy = await this.sdk.utils.callErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getSettlementStrategy",
args: [resolvedMarketId, settlementStrategyId]
});
return {
marketId: resolvedMarketId,
strategyType: settlementStrategy.strategyType,
settlementDelay: Number(settlementStrategy.settlementDelay),
settlementWindowDuration: Number(settlementStrategy.settlementWindowDuration),
priceVerificationContract: settlementStrategy.priceVerificationContract,
feedId: settlementStrategy.feedId,
settlementReward: Number((0, import_viem7.formatEther)(settlementStrategy.settlementReward ?? 0n)),
disabled: settlementStrategy.disabled,
commitmentPriceDelay: Number(settlementStrategy.commitmentPriceDelay)
};
}
/**
* Fetch the settlement strategies for an array of market ids. Settlement strategies describe the
* conditions under which an order can be settled.
* @param marketIds Array of marketIds to fetch settlement strategy
* @returns Settlement strategy array for markets
*/
async getSettlementStrategies(marketIds) {
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const settlementStrategies = [];
const argsList = marketIds.map((marketId) => [marketId, 0]);
const settlementStrategiesResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getSettlementStrategy",
args: argsList
});
settlementStrategiesResponse.forEach((strategy, index) => {
settlementStrategies.push({
marketId: marketIds[index],
strategyType: strategy.strategyType,
settlementDelay: Number(strategy.settlementDelay),
settlementWindowDuration: Number(strategy.settlementWindowDuration),
priceVerificationContract: strategy.priceVerificationContract,
feedId: strategy.feedId,
settlementReward: Number((0, import_viem7.formatEther)(strategy.settlementReward ?? 0n)),
disabled: strategy.disabled,
commitmentPriceDelay: Number(strategy.commitmentPriceDelay)
});
});
return settlementStrategies;
}
/**
* Fetch funding parameters for an array of market ids.
* @param marketIds Array of marketIds to fetch settlement strategy
* @returns Funding Parameters array for markets
*/
async getFundingParameters(marketIds) {
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const fundingParams = [];
const fundingParamsResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getFundingParameters",
args: marketIds
});
fundingParamsResponse.forEach((param, index) => {
fundingParams.push({
marketId: marketIds[index],
skewScale: Number((0, import_viem7.formatEther)(param[0])),
maxFundingVelocity: Number((0, import_viem7.formatEther)(param[1]))
});
});
return fundingParams;
}
/**
* Gets the order fees of a market.
* @param marketIds Array of market ids.
* @return Order fees array for markets
*/
async getOrderFees(marketIds) {
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const orderFees = [];
const orderFeesResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getOrderFees",
args: marketIds
});
orderFeesResponse.forEach((param, index) => {
orderFees.push({
marketId: marketIds[index],
makerFeeRatio: Number((0, import_viem7.formatEther)(param[0])),
takerFeeRatio: Number((0, import_viem7.formatEther)(param[1]))
});
});
return orderFees;
}
/**
* Gets the max size (in value) of an array of marketIds.
* @param marketIds Array of market ids.
* @return Max market size in market USD value for each market
*/
async getMaxMarketValues(marketIds) {
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const maxMarketValues = [];
const maxMarketValuesResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getMaxMarketValue",
args: marketIds
});
maxMarketValuesResponse.forEach((maxMarketValue, index) => {
maxMarketValues.push({
marketId: marketIds[index],
maxMarketValue: Number((0, import_viem7.formatEther)(maxMarketValue))
});
});
return maxMarketValues;
}
/**
* Fetches the open order for an account. Optionally fetches the settlement strategy,
* which can be useful for order settlement and debugging.
* @param {bigint} accountId The id of the account. If not provided, the default account is used
* @param {boolean} fetchSettlementStrategy Flag to indicate whether to fetch the settlement strategy
* @returns {OrderData} The order data for the account
*/
async getOrder(accountId = this.defaultAccountId, fetchSettlementStrategy = true) {
if (!accountId) throw new Error("Invalid account id");
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const orderResponse = await this.sdk.utils.callErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "getOrder",
args: [accountId]
});
const orderReq = orderResponse.request;
const orderData = {
marketId: Number(orderReq.marketId),
commitmentTime: Number(orderResponse.commitmentTime),
accountId: orderReq.accountId,
sizeDelta: Number((0, import_viem7.formatEther)(orderReq.sizeDelta)),
settlementStrategyId: Number(orderReq.settlementStrategyId),
acceptablePrice: Number(orderReq.acceptablePrice),
trackingCode: orderReq.trackingCode,
referrer: orderReq.referrer
};
if (fetchSettlementStrategy) {
const strategy = await this.getSettlementStrategy(
Number(orderReq.settlementStrategyId),
Number(orderReq.marketId)
);
orderData.settlementStrategy = strategy;
}
return orderData;
}
/**
* Fetch information about an account's margin requirements and balances.
* Accounts must maintain an ``available_margin`` above the ``maintenance_margin_requirement``
* to avoid liquidation. Accounts with ``available_margin`` below the ``initial_margin_requirement``
* can not interact with their position unless they deposit more collateral.
* @param {bigint} accountId The id of the account to fetch the margin info for. If not provided, the default account is used
* @returns {CollateralData} The margin information for the account
*/
async getMarginInfo(accountId = this.defaultAccountId) {
if (!accountId) throw new Error("Invalid account ID");
const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const functionNames = [];
const argsList = [];
functionNames.push("totalCollateralValue");
argsList.push([accountId]);
functionNames.push("getAvailableMargin");
argsList.push([accountId]);
functionNames.push("getWithdrawableMargin");
argsList.push([accountId]);
functionNames.push("getRequiredMargins");
argsList.push([accountId]);
const multicallResponse = await this.sdk.utils.multicallMultifunctionErc7412({
contractAddress: marketProxy.address,
abi: marketProxy.abi,
functionNames,
args: argsList
});
const totalCollateralValue = multicallResponse.at(0);
const availableMargin = multicallResponse.at(1);
const withdrawableMargin = multicallResponse.at(2);
const requiredMarginsResponse = multicallResponse.at(3);
const requiredInitialMargin = requiredMarginsResponse.at(0);
const requiredMaintenanceMargin = requiredMarginsResponse.at(1);
const maxLiquidationReward = requiredMarginsResponse.at(2);
const collateralAmountsRecord = [];
let debt = 0;
if (this.isMulticollateralEnabled) {
const fNames = [];
const aList = [];
fNames.push("getAccountCollateralIds");
aList.push([accountId]);
fNames.push("debt");
aList.push([accountId]);
const response = await this.sdk.utils.multicallMultifunctionErc7412({
contractAddress: marketProxy.address,
abi: marketProxy.abi,
functionNames: fNames,
args: aList
});
const collateralIds = response.at(0);
debt = convertWeiToEther(response.at(1));
if (collateralIds.length != 0) {
const inputs = collateralIds.map((id) => {
return [accountId, id];
});
this.sdk.logger.info("inputs", inputs);
const collateralAmounts = await this.sdk.utils.multicallErc7412({
contractAddress: marketProxy.address,
abi: marketProxy.abi,
functionName: "getCollateralAmount",
args: inputs
});
collateralIds.forEach((collateralId, index) => {
collateralAmountsRecord[Number(collateralId)] = convertWeiToEther(collateralAmounts.at(index));
});
}
} else {
collateralAmountsRecord[0] = convertWeiToEther(totalCollateralValue);
}
const marginInfo = {
totalCollateralValue: convertWeiToEther(totalCollateralValue),
collateralBalances: collateralAmountsRecord,
debt,
availableMargin: convertWeiToEther(availableMargin),
withdrawableMargin: convertWeiToEther(withdrawableMargin),
initialMarginRequirement: convertWeiToEther(requiredInitialMargin),
maintenanceMarginRequirement: convertWeiToEther(requiredMaintenanceMargin),
maxLiquidationReward: convertWeiToEther(maxLiquidationReward)
};
this.sdk.logger.info(`${marginInfo}marginInfo`);
return marginInfo;
}
/**
* @name buildModifyCollateral
* @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.
* @param {string | number} data.amount - The amount of the underlying asset to modify collateral for.
* @param {string | number} data.marketIdOrName - The ID or name of the perp market where the collateral will be modified.
* @param {string} data.accountId - The ID of the account whose collateral is being modified.
* @param {string} data.collateralId - The ID of the collateral being modified.
* @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.
*/
async buildModifyCollateral({ amount, collateralMarketIdOrName, accountId }) {
const { resolvedMarketId: collateralMarketId, resolvedMarketName: collateralMarketName } = await this.sdk.spot.resolveMarket(collateralMarketIdOrName);
this.sdk.logger.info(`Building ${amount} ${collateralMarketName} for account ${accountId}`);
const synthCollateral = await this.sdk.spot.getMarket(collateralMarketId);
const collateral = await this.sdk.contracts.getCollateralInstance(
(synthCollateral.marketName ?? "Unresolved Market").replace("s", "")
);
const zapContract = SYNTHETIX_ZAP[this.sdk.rpcConfig.chainId];
const formattedAmount = await this.formatSize(amount, amount > 0 ? collateralMarketId : 0);
if (amount > 0)
return {
target: zapContract,
callData: (0, import_viem7.encodeFunctionData)({
abi: SYNTHETIX_ZAP_ABI,
functionName: "deposit",
// all wrap versions have 18 decimals so setting collateralId default to 0
args: [accountId, collateral.address, synthCollateral.contractAddress, formattedAmount, collateralMarketId]
}),
value: 0n,
requireSuccess: true
};
return {
target: zapContract,
callData: (0, import_viem7.encodeFunctionData)({
abi: SYNTHETIX_ZAP_ABI,
functionName: "withdraw",
args: [accountId, collateral.address, formattedAmount, collateralMarketId]
}),
value: 0n,
requireSuccess: true
};
}
/**
* Fetch the balance of each collateral type for an account.
* @param {bigint} accountId The id of the account to fetch the collateral balances for. If not provided, the default account is used.
* @returns {Promise<number>} The balance of the account's collateral.
*/
async getCollateralBalances(accountId) {
throw new Error("Not implemented " + accountId);
}
/**
* Check if an `accountId` is eligible for liquidation.
* @param accountId The id of the account to check. If not provided, the default account is used.
* @returns {Promise<boolean>} A boolean indicating whether the account can be liquidated.
*/
async getCanLiquidate(accountId = this.defaultAccountId) {
if (!accountId) throw new Error("Invalid account ID");
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const canBeLiquidated = await this.sdk.utils.callErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "canLiquidate",
args: [accountId]
});
this.sdk.logger.info("canBeLiquidated", canBeLiquidated);
return canBeLiquidated;
}
/**
* Check if a batch of `accountId`'s are eligible for liquidation.
* @param {string[]} accountIds An array of account ids
* @returns {Promise<{ accountId: bigint; canLiquidate: boolean }[]>} An array of objects containing the account id and whether the account can be liquidated.
*/
async getCanLiquidates(accountIds = void 0) {
const perpsMarketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
if (accountIds == void 0) {
if (this.defaultAccountId != void 0) {
accountIds = this.accountIds;
} else {
throw new Error("Invalid account ID");
}
}
const input = accountIds.map((accountId) => [accountId]);
const canLiquidatesResponse = await this.sdk.utils.multicallErc7412({
contractAddress: perpsMarketProxy.address,
abi: perpsMarketProxy.abi,
functionName: "canLiquidate",
args: input
});
const canLiquidates = canLiquidatesResponse.map((response, index) => {
return {
accountId: accountIds.at(index) ?? 0n,
canLiquidate: response
};
});
this.sdk.logger.info("canLiquidates", canLiquidates);
return canLiquidates;
}
/**
* Fetch the position for a specified account and market. The result includes the unrealized
* pnl since the last interaction with this position, any accrued funding, and the position size.
* Provide either a ``marketId`` or a ``marketName``::
* @param {string | number} marketIdOrName - The identifier or name of the market for which the collateral is being modified
* @param {bigint} accountId The id of the account to fetch the position for. If not provided, the default account is used.
* @returns {OpenPositionData} An object containing the open position data for the specified market.
*/
async getOpenPosition(marketIdOrName, accountId = this.defaultAccountId) {
if (!accountId) throw new Error("Account ID is required");
const marketProxy = await this.sdk.contracts.getPerpsMarketProxyInstance();
const { resolvedMarketId, resolvedMarketName } = await this.resolveMarket(marketIdOrName);
const response = await this.sdk.utils.callErc7412({
contractAddress: marketProxy.address,
abi: marketProxy.abi,
functionName: "getOpenPosition",
args: [accountId, resolvedMarketId]
});
const openPositionData = {
accountId,
marketId: resolvedMarketId,
marketName: resolvedMarketName,
totalPnl: convertWeiToEther(response.at(0)),
accruedFunding: convertWeiToEther(response.at(1)),
positionSize: convertWeiToEther(response.at(2)),
owedInterest: convertWeiToEther(response.at(3))
};
this.sdk.logger.info("openPositionData", openPositionData);
return openPositionData;
}
/**
* Fetch positions for an array of specified markets. The result includes the unrealized
* pnl since the last interaction with this position, any accrued funding, and the position size.
* Provide either an array of ``marketIds`` or a ``marketNames``::
* @param {MarketIdOrName} marketIdsOrNames - The ID or name of the Perpetual market to get a quote for.
* @param {bigint} accountId The id of the account to fetch the position for. If not provided, the default account is used.
* @returns {OpenPositionData[]} An array of objects containing the open position data for the specified markets.
*/
// NOTE: maybe is better use subgraph?
async getOpenPositions(marketIdsOrNames, accountId = this.defaultAccountId) {
if (!accountId) throw new Error("Account ID is required");
const marketProxy = await this.sdk.contracts.getPerps