UNPKG

@d8x/perpetuals-sdk

Version:

Node TypeScript SDK for D8X Perpetual Futures

182 lines (172 loc) 6.82 kB
import { Contract, ContractTransactionResponse, JsonRpcProvider, Overrides, parseEther, Provider, Signer, Wallet, } from "ethers"; import { MAX_UINT_256, MULTICALL_ADDRESS } from "./constants"; import { ERC20__factory, IPerpetualManager__factory, MockTokenSwap__factory, Multicall3__factory } from "./contracts"; import { PayableOverrides } from "./contracts/common"; import { floatToDecN } from "./d8XMath"; import MarketData from "./marketData"; import { type NodeSDKConfig } from "./nodeSDKTypes"; import PerpetualDataHandler from "./perpetualDataHandler"; /** * This is a parent class for the classes that require * write access to the contracts. * This class requires a private key and executes smart-contract interaction that * require gas-payments. * @extends PerpetualDataHandler */ export default class WriteAccessHandler extends PerpetualDataHandler { protected privateKey: string | undefined; protected traderAddr: string = ""; protected signer: Signer | null = null; protected gasLimit: number = 15_000_000; /** * Constructor * @param {string | Signer} signer Private key or ethers Signer of the account */ public constructor(config: NodeSDKConfig, signer: string | Signer) { super(config); if (typeof signer == "string") { this.privateKey = signer; } else { this.signer = signer; } if (config.gasLimit != undefined) { this.gasLimit = config.gasLimit; } } public async createProxyInstance(provider?: Provider, overrides?: Overrides): Promise<void>; public async createProxyInstance(marketData: MarketData): Promise<void>; /** * Initialize the writeAccessHandler-Class with this function * to create instance of D8X perpetual contract and gather information * about perpetual currencies * @param provider optional provider */ public async createProxyInstance(providerOrMarketData?: Provider | MarketData, overrides?: Overrides) { await this.priceFeedGetter.init(); if (providerOrMarketData == undefined || !("createProxyInstance" in providerOrMarketData)) { this.provider = providerOrMarketData ?? new JsonRpcProvider(this.nodeURL, this.network, { staticNetwork: true }); if (!this.signer) { const wallet = new Wallet(this.privateKey!); this.signer = wallet.connect(this.provider); } await this.initContractsAndData(this.signer, overrides); } else { const mktData = providerOrMarketData; this.nodeURL = mktData.config.nodeURL; this.provider = new JsonRpcProvider(mktData.config.nodeURL, mktData.network, { staticNetwork: true }); this.proxyContract = IPerpetualManager__factory.connect(mktData.getProxyAddress(), this.provider); this.multicall = Multicall3__factory.connect(this.config.multicall ?? MULTICALL_ADDRESS, this.provider); ({ nestedPerpetualIDs: this.nestedPerpetualIDs, poolStaticInfos: this.poolStaticInfos, symbolToTokenAddrMap: this.symbolToTokenAddrMap, symbolToPerpStaticInfo: this.symbolToPerpStaticInfo, perpetualIdToSymbol: this.perpetualIdToSymbol, } = mktData.getAllMappings()); this.priceFeedGetter.setTriangulations(mktData.getTriangulations()); this.signerOrProvider = this.provider; } if (!this.signer) { const wallet = new Wallet(this.privateKey!); this.signer = wallet.connect(this.provider); } this.traderAddr = await this.signer.getAddress(); } /** * Set allowance for ar margin token (e.g., MATIC, ETH, USDC) * @param symbol token in 'long-form' such as MATIC, symbol also fine (ETH-USD-MATIC) * @param amount optional, amount to approve if not 'infinity' * @returns Contract Transaction */ public async setAllowance( symbol: string, amount?: number, overrides?: Overrides ): Promise<ContractTransactionResponse> { // extract margin-currency name let symbolarr = symbol.split("-"); symbol = symbol.length == 3 ? symbolarr[2] : symbolarr[0]; // let settleTokenAddr = this.getSettlementTokenFromSymbol(symbol); if (settleTokenAddr == undefined || this.signer == null) { throw Error("No margin token or signer defined, call createProxyInstance"); } let amountDec18: bigint; if (amount == undefined) { amountDec18 = MAX_UINT_256; } else { amountDec18 = floatToDecN(amount, this.getSettlementTokenDecimalsFromSymbol(symbol)!); } return WriteAccessHandler._setAllowance(settleTokenAddr, this.proxyAddr, this.signer, amountDec18, overrides); } protected static async _setAllowance( tokenAddr: string, proxyAddr: string, signer: Signer, amount: bigint, overrides?: Overrides ): Promise<ContractTransactionResponse> { const marginToken = ERC20__factory.connect(tokenAddr, signer); return await marginToken.approve(proxyAddr, amount, overrides || {}); } /** * Address corresponding to the private key used to instantiate this class. * @returns {string} Address of this wallet. */ public getAddress(): string { return this.traderAddr; } protected _getFeeData(overrides?: PayableOverrides) { // handle legacy and eip-1552 (ensure at most one is specified) if (overrides?.maxFeePerGas) { return { maxFeePerGas: overrides.maxFeePerGas, maxPriorityFeePerGas: overrides.maxPriorityFeePerGas ?? overrides.maxFeePerGas, }; } else { return { gasPrice: overrides?.gasPrice }; } } /** * Converts a given amount of chain native currency (test MATIC) * into a mock token used for trading on testnet, with a rate of 1:100_000 * @param symbol Pool margin token e.g. MATIC * @param amountToPay Amount in chain currency, e.g. "0.1" for 0.1 MATIC * @returns Transaction object */ public async swapForMockToken( symbol: string, amountToPay: string, overrides?: PayableOverrides ): Promise<ContractTransactionResponse> { if (this.signer == null) { throw Error("no wallet initialized. Use createProxyInstance()."); } let tokenAddress = this.getMarginTokenFromSymbol(symbol); if (tokenAddress == undefined) { throw Error("symbols not found"); } let tokenToSwap = new Map<string, string>(Object.entries(require("./config/mockSwap.json"))); let swapAddress = tokenToSwap.get(tokenAddress); if (swapAddress == undefined) { throw Error("No swap contract found for symbol."); } let contract = MockTokenSwap__factory.connect(swapAddress, this.signer); if (overrides && overrides.value !== undefined) { throw Error("Pass value to send in function call, not overrides."); } return await contract.swapToMockToken({ value: parseEther(amountToPay), ...overrides, } as PayableOverrides); } }