UNPKG

@d8x/perpetuals-sdk

Version:

Node TypeScript SDK for D8X Perpetual Futures

500 lines 27.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const d8XMath_1 = require("./d8XMath"); const perpetualDataHandler_1 = __importDefault(require("./perpetualDataHandler")); const writeAccessHandler_1 = __importDefault(require("./writeAccessHandler")); const buffer_1 = require("buffer"); const accountTrade_1 = __importDefault(require("./accountTrade")); const ethers_1 = require("ethers"); const constants_1 = require("./constants"); /** * Functions for white-label partners to determine fees, deposit lots, and sign-up traders. * This class requires a private key and executes smart-contract interactions that * require gas-payments. * @extends WriteAccessHandler */ class BrokerTool extends writeAccessHandler_1.default { /** * Constructor * @param {NodeSDKConfig} config Configuration object, see PerpetualDataHandler. * readSDKConfig. * @param {string} privateKey Private key of a white-label partner. * @param {Signer} signer Signer (ignored if a private key is provided) * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // load configuration for Polygon zkEVM (testnet) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * // BrokerTool (authentication required, PK is an environment variable with a private key) * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * // Create a proxy instance to access the blockchain * await brokTool.createProxyInstance(); * } * main(); * */ constructor(config, signer) { super(config, signer); } // Fee getters /** * Determine the exchange fee based on lots, traded volume, and D8X balance of this white-label partner. * This is the final exchange fee that this white-label partner can offer to traders that trade through him. * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get white-label partner induced fee * let brokFee = await brokTool.getBrokerInducedFee("MATIC"); * console.log(brokFee); * } * main(); * * @returns {number} Exchange fee for this white-label partner, in decimals (i.e. 0.1% is 0.001) */ async getBrokerInducedFee(poolSymbolName, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos); let feeTbps = await this.proxyContract.getBrokerInducedFee(poolId, this.traderAddr, overrides || {}); let fee = Number(feeTbps) / 100000; if (fee == 0.65535) { return undefined; } return fee; } /** * Determine the exchange fee based on lots purchased by this white-label partner. * The final exchange fee that this white-label partner can offer to traders that trade through him is equal to * maximum(brokerTool.getFeeForBrokerDesignation(poolSymbolName), brokerTool.getFeeForBrokerVolume(poolSymbolName), brokerTool.getFeeForBrokerStake()) * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @param {number=} lots Optional, designation to use if different from this white-label partner's. * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get white-label partner fee induced by lots * let brokFeeLots = await brokTool.getFeeForBrokerDesignation("MATIC"); * console.log(brokFeeLots); * } * main(); * * @returns {number} Fee based solely on this white-label partner's designation, in decimals (i.e. 0.1% is 0.001). */ async getFeeForBrokerDesignation(poolSymbolName, lots, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } // check if designation should be taken from the caller or as a parameter let brokerDesignation; if (typeof lots == "undefined") { brokerDesignation = await this.getBrokerDesignation(poolSymbolName); brokerDesignation = brokerDesignation > 0 ? brokerDesignation : 0; } else { brokerDesignation = lots; } let feeTbps = await this.proxyContract.getFeeForBrokerDesignation(brokerDesignation, overrides || {}); return Number(feeTbps) / 100000; } /** * Determine the exchange fee based on volume traded under this white-label partner. * The final exchange fee that this white-label partner can offer to traders that trade through him is equal to * maximum(brokerTool.getFeeForBrokerDesignation(poolSymbolName), brokerTool.getFeeForBrokerVolume(poolSymbolName), brokerTool.getFeeForBrokerStake()) * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get white-label partner fee induced by volume * let brokFeeVol = await brokTool.getFeeForBrokerVolume("MATIC"); * console.log(brokFeeVol); * } * main(); * * @returns {number} Fee based solely on a white-label partner's traded volume in the corresponding pool, in decimals (i.e. 0.1% is 0.001). */ async getFeeForBrokerVolume(poolSymbolName, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos); let feeTbps = await this.proxyContract.getFeeForBrokerVolume(poolId, this.traderAddr, overrides || {}); return Number(feeTbps) / 100000; } /** * Determine the exchange fee based on the current D8X balance in a white-label partner's wallet. * The final exchange fee that this white-label partner can offer to traders that trade through him is equal to * maximum(brokerTool.getFeeForBrokerDesignation(symbol, lots), brokerTool.getFeeForBrokerVolume(symbol), brokerTool.getFeeForBrokerStake) * @param {string=} brokerAddr Address of the white-label partner in question, if different from the one calling this function. * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get white-label partner fee induced by staked d8x * let brokFeeStake = await brokTool.getFeeForBrokerStake("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"); * console.log(brokFeeStake); * } * main(); * * @returns {number} Fee based solely on a white-label partner's D8X balance, in decimals (i.e. 0.1% is 0.001). */ async getFeeForBrokerStake(brokerAddr, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } if (typeof brokerAddr == "undefined") { brokerAddr = this.traderAddr; } let feeTbps = await this.proxyContract.getFeeForBrokerStake(brokerAddr, overrides || {}); return Number(feeTbps) / 100000; } /** * Determine exchange fee based on an order and a trader. * This is the fee charged by the exchange only, excluding the white-label partner fee, * For regular perpetuals, the result takes into account whether the order given here has been * signed by a white-label partner or not. * Use this, for instance, to verify that the fee to be charged for a given order is as expected, * before and after signing it with brokerTool.signOrder. * This fee is equal or lower than the white-label partner induced fee, provided the order is properly signed. * * For prediction markets, the correct fee is to be applied as tradeamt * fee/s3. * * @param {Order} order Order structure. As a minimum the structure needs to * specify symbol, side, type and quantity. * @param {string} traderAddr Address of the trader for whom to determine the fee. * @example * import { BrokerTool, PerpetualDataHandler, Order } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get exchange fee based on an order and trader * let order: Order = { * symbol: "MATIC-USD-MATIC", * side: "BUY", * type: "MARKET", * quantity: 100, * executionTimestamp: Date.now()/1000 * }; * let exchFee = await brokTool.determineExchangeFee(order, * "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"); * console.log(exchFee); * } * main(); * * @returns {number} Fee in decimals (i.e. 0.1% is 0.001). */ async determineExchangeFee(order, traderAddr, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } if (this.isPredictionMarket(order.symbol)) { const id = BrokerTool.symbolToPerpetualId(order.symbol, this.symbolToPerpStaticInfo); const fAmount = order.side == constants_1.BUY_SIDE ? (0, d8XMath_1.floatToABK64x64)(order.quantity) : (0, d8XMath_1.floatToABK64x64)(-order.quantity); const lvgTdr = order.leverage == undefined ? 0 : Math.round(100 * order.leverage); const feeTbps = await this.proxyContract.getExchangeFeePrdMkts(id, fAmount, lvgTdr, traderAddr); return Number(feeTbps) / 100000; } // regular markets let scOrder = accountTrade_1.default.toSmartContractOrder(order, traderAddr, this.symbolToPerpStaticInfo); let feeTbps = await this.proxyContract.determineExchangeFee(scOrder, overrides || {}); return Number(feeTbps) / 100000; } // Volume /** * Exponentially weighted EMA of the total trading volume of all trades performed under this white-label partner. * The weights are chosen so that in average this coincides with the 30 day volume. * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get 30 day volume for white-label partner * let brokVolume = await brokTool.getCurrentBrokerVolume("MATIC"); * console.log(brokVolume); * } * main(); * * @returns {number} Current trading volume for this white-label partner, in USD. */ async getCurrentBrokerVolume(poolSymbolName, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos); let volume = await this.proxyContract.getCurrentBrokerVolume(poolId, this.traderAddr, overrides || {}); return (0, d8XMath_1.ABK64x64ToFloat)(volume); } // Lots /** * Total amount of collateral currency a white-label partner has to deposit into the default fund to purchase one lot. * This is equivalent to the price of a lot expressed in a given pool's currency (e.g. MATIC, USDC, etc). * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get lot price * let brokLotSize = await brokTool.getLotSize("MATIC"); * console.log(brokLotSize); * } * main(); * * @returns {number} White-label partner lot size in a given pool's currency, e.g. in MATIC for poolSymbolName MATIC. */ async getLotSize(poolSymbolName, overrides) { if (this.proxyContract == null) { throw Error("no proxy contract initialized. Use createProxyInstance()."); } let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos); let pool = await this.proxyContract.getLiquidityPool(poolId, overrides || {}); let lot = (0, d8XMath_1.ABK64x64ToFloat)(pool.fBrokerCollateralLotSize); return lot; } /** * Provides information on how many lots a white-label partner purchased for a given pool. * This is relevant to determine the white-label partner's fee tier. * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // get white-label partner designation * let brokDesignation = await brokTool.getBrokerDesignation("MATIC"); * console.log(brokDesignation); * } * main(); * * @returns {number} Number of lots purchased by this white-label partner. */ async getBrokerDesignation(poolSymbolName, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos); let designation = await this.proxyContract.getBrokerDesignation(poolId, this.traderAddr, overrides || {}); return Number(designation); } /** * Deposit lots to the default fund of a given pool. * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @param {number} lots Number of lots to deposit into this pool. * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // deposit to perpetuals * await brokTool.setAllowance("MATIC"); * let respDeposit = await brokTool.depositBrokerLots("MATIC",1); * console.log(respDeposit); * } * main(); * * @returns {ContractTransaction} ContractTransaction object. */ async depositBrokerLots(poolSymbolName, lots, overrides) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos); let tx = await this.proxyContract.depositBrokerLots(poolId, lots, overrides || {}); return tx; } // Signatures /** * Adds this white-label partner's signature to a user-friendly order. An order signed by a white-label partner is considered * to be routed through this white-label partner and benefits from the white-label partner's fee conditions. * @param {Order} order Order to sign. It must contain valid white-label partner fee, white-label partner address, and order deadline. * @param {string} traderAddr Address of trader submitting the order. * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // sign order * let order = {symbol: "ETH-USD-MATIC", * side: "BUY", * type: "MARKET", * quantity: 1, * executionTimestamp: Date.now()/1000 * }; * let signedOrder = await brokTool.signOrder(order, "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", * 0.0001, 1669723339); * console.log(signedOrder); * // execute order * let orderTransaction = await accTrade.order(signedOrder); * console.log(orderTransaction.hash); * } * main(); * * @returns {Order} An order signed by this white-label partner, which can be submitted directly with AccountTrade.order. */ async signOrder(order, traderAddr) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } order.brokerAddr = this.traderAddr; if (order.deadline == undefined) { throw Error("brokerTool::signOrder: deadline not defined"); } order.brokerSignature = await BrokerTool._signOrder(order.symbol, order.brokerFeeTbps, traderAddr, order.deadline, this.signer, this.chainId, this.proxyAddr, this.symbolToPerpStaticInfo); return order; } /** * Generates a white-label partner's signature of a smart-contract ready order. An order signed by a white-label partner is considered * to be routed through this white-label partner and benefits from the white-label partner's fee conditions. * @param {SmartContractOrder} scOrder Order to sign. It must contain valid white-label partner fee, white-label partner address, and order deadline. * @param {string} traderAddr Address of trader submitting the order. * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * const brokTool = new BrokerTool(config, pk); * const traderAPI = new TraderInterface(config); * await brokTool.createProxyInstance(); * await traderAPI.createProxyInstance(); * // sign order * const order = {symbol: "ETH-USD-MATIC", * side: "BUY", * type: "MARKET", * quantity: 1, * executionTimestamp: Date.now()/1000 * }; * const scOrder = await traderAPI.createSmartContractOrder(order, "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") * const signature = await brokTool.signSCOrder(order, "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", * 0.0001, 1669723339); * console.log(signature); * } * main(); * * @returns {string} Signature of order. */ async signSCOrder(scOrder) { return await BrokerTool._signOrderFromRawData(scOrder.iPerpetualId, scOrder.brokerFeeTbps, scOrder.traderAddr, scOrder.iDeadline, this.signer, this.chainId, this.proxyAddr); } /** * Creates a signature that a trader can use to place orders with this white-label partner. * This signature can be used to pass on to a trader who wishes to trade via this SDK or directly on the blockchain. * @param {string} traderAddr Address of the trader signing up with this white-label partner. * @param {string} symbol Perpetual that this trader will be trading, of the form ETH-USD-MATIC. * @param {number} brokerFee White-label partner fee for this trader, in decimals (i.e. 0.1% is 0.001). * @param {number} deadline Deadline for the order to be executed. * @returns {string} White-label partner signature approving this trader's fee, symbol, and deadline. * @ignore */ async createSignatureForTrader(traderAddr, symbol, brokerFee, deadline) { if (this.proxyContract == null || this.signer == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } let brokerFeeTbps = 100000 * brokerFee; return await BrokerTool._signOrder(symbol, brokerFeeTbps, traderAddr, deadline, this.signer, this.chainId, this.proxyAddr, this.symbolToPerpStaticInfo); } static async _signOrderFromRawData(iPerpetualId, brokerFeeTbps, traderAddr, iDeadline, signer, chainId, proxyAddress) { const NAME = "Perpetual Trade Manager"; const DOMAIN_TYPEHASH = (0, ethers_1.keccak256)(buffer_1.Buffer.from("EIP712Domain(string name,uint256 chainId,address verifyingContract)")); let abiCoder = new ethers_1.AbiCoder(); let domainSeparator = (0, ethers_1.keccak256)(abiCoder.encode(["bytes32", "bytes32", "uint256", "address"], [DOMAIN_TYPEHASH, (0, ethers_1.keccak256)(buffer_1.Buffer.from(NAME)), chainId, proxyAddress])); // const TRADE_BROKER_TYPEHASH = (0, ethers_1.keccak256)(buffer_1.Buffer.from("Order(uint24 iPerpetualId,uint16 brokerFeeTbps,address traderAddr,uint32 iDeadline)")); let structHash = (0, ethers_1.keccak256)(abiCoder.encode(["bytes32", "uint24", "uint16", "address", "uint32"], [TRADE_BROKER_TYPEHASH, iPerpetualId, brokerFeeTbps, traderAddr, iDeadline])); let digest = (0, ethers_1.keccak256)(abiCoder.encode(["bytes32", "bytes32"], [domainSeparator, structHash])); let digestBuffer = buffer_1.Buffer.from(digest.substring(2, digest.length), "hex"); return await signer.signMessage(digestBuffer); } static async _signOrder(symbol, brokerFeeTbps, traderAddr, iDeadline, signer, chainId, proxyAddress, symbolToPerpStaticInfo) { let iPerpetualId = perpetualDataHandler_1.default.symbolToPerpetualId(symbol, symbolToPerpStaticInfo); return await BrokerTool._signOrderFromRawData(iPerpetualId, brokerFeeTbps, traderAddr, iDeadline, signer, chainId, proxyAddress); } // Transfer ownership /** * Transfer ownership of a white-label partner's status to a new wallet. This function transfers the values related to * (i) trading volume and (ii) deposited lots to newAddress. The white-label partner needs in addition to manually transfer * his D8X holdings to newAddress. Until this transfer is completed, the white-label partner will not have his current designation reflected at newAddress. * @param {string} poolSymbolName Pool symbol name (e.g. MATIC, USDC, etc). * @param {string} newAddress The address this white-label partner wants to use from now on. * @example * import { BrokerTool, PerpetualDataHandler } from '@d8x/perpetuals-sdk'; * async function main() { * console.log(BrokerTool); * // setup (authentication required, PK is an environment variable with a private key) * const config = PerpetualDataHandler.readSDKConfig("cardona"); * const pk: string = <string>process.env.PK; * let brokTool = new BrokerTool(config, pk); * await brokTool.createProxyInstance(); * // transfer ownership * let respTransferOwnership = await brokTool.transferOwnership("MATIC", "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"); * console.log(respTransferOwnership); * } * main(); * * @returns {ContractTransaction} ethers transaction object */ async transferOwnership(poolSymbolName, newAddress, overrides) { if (this.proxyContract == null) { throw Error("no proxy contract or wallet initialized. Use createProxyInstance()."); } let poolId = perpetualDataHandler_1.default._getPoolIdFromSymbol(poolSymbolName, this.poolStaticInfos); let tx = await this.proxyContract.transferBrokerOwnership(poolId, newAddress, overrides || {}); return tx; } } exports.default = BrokerTool; //# sourceMappingURL=brokerTool.js.map