UNPKG

@shogun-sdk/money-legos

Version:

Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.

296 lines 11.7 kB
import { ethers } from 'ethers'; import { USDC_ABI } from '../abi/usdc-arbitrum.abi.js'; import { isSpotAsset } from '../utils/helpers.js'; import { HL_BRIDGE2_CA } from '../types/constants.js'; import { ARBITRUM_CHAIN_ID, getRpcUrls } from '../../config/chains.js'; import { STABLECOINS } from '../../config/stablecoins.js'; export class CustomOperations { constructor(exchange, infoApi, privateKey, symbolConversion, parent) { Object.defineProperty(this, "exchange", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "infoApi", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "wallet", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "symbolConversion", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "parent", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "DEFAULT_SLIPPAGE", { enumerable: true, configurable: true, writable: true, value: 0.05 }); Object.defineProperty(this, "MAX_DECIMALS_SPOT", { enumerable: true, configurable: true, writable: true, value: 8 }); Object.defineProperty(this, "MAX_DECIMALS_PERP", { enumerable: true, configurable: true, writable: true, value: 6 }); this.exchange = exchange; this.infoApi = infoApi; this.wallet = new ethers.Wallet(privateKey); this.symbolConversion = symbolConversion; this.parent = parent; } async cancelAllOrders(symbol) { try { await this.parent.ensureInitialized(); const address = this.wallet.address; const openOrders = await this.infoApi.getUserOpenOrders(address); let ordersToCancel; for (let order of openOrders) { order.coin = await this.symbolConversion.convertSymbol(order.coin); } if (symbol) { ordersToCancel = openOrders.filter((order) => order.coin === symbol); } else { ordersToCancel = openOrders; } if (ordersToCancel.length === 0) { throw new Error('No orders to cancel'); } const cancelRequests = ordersToCancel.map((order) => ({ coin: order.coin, o: order.oid, })); const response = await this.exchange.cancelOrder(cancelRequests); return response; } catch (error) { throw error; } } async getAllAssets() { await this.parent.ensureInitialized(); return await this.symbolConversion.getAllAssets(); } async getSlippagePrice(symbol, isBuy, slippage, px) { const convertedSymbol = await this.symbolConversion.convertSymbol(symbol); if (!px) { const allMids = await this.infoApi.getAllMids(); px = Number(allMids[convertedSymbol]); } px *= isBuy ? 1 + slippage : 1 - slippage; if (!(await this.isValidPrice(symbol, px))) { const result = await this.adjustPrice(symbol, px); if (result.success === false) { return px; } else { px = result.data; } } return px; } async marketOpen(symbol, isBuy, size, px, slippage = this.DEFAULT_SLIPPAGE, cloid) { await this.parent.ensureInitialized(); const convertedSymbol = await this.symbolConversion.convertSymbol(symbol); if (px && !this.isValidPrice(symbol, px)) { throw new Error(`Invalid price: ${px}`); } const slippagePrice = await this.getSlippagePrice(convertedSymbol, isBuy, slippage, px); const orderRequest = { coin: convertedSymbol, is_buy: isBuy, sz: size, limit_px: slippagePrice, order_type: { limit: { tif: 'Ioc' } }, reduce_only: false, }; if (cloid) { orderRequest.cloid = cloid; } console.log(orderRequest); return this.exchange.placeOrder(orderRequest); } async marketClose(symbol, size, px, slippage = this.DEFAULT_SLIPPAGE, cloid) { await this.parent.ensureInitialized(); const convertedSymbol = await this.symbolConversion.convertSymbol(symbol); const address = this.wallet.address; const positions = await this.infoApi.perpetuals.getClearinghouseState(address); for (const position of positions.assetPositions) { const item = position.position; if (convertedSymbol !== item.coin) { continue; } const szi = parseFloat(item.szi); const closeSize = size || Math.abs(szi); const isBuy = szi < 0; // Get aggressive Market Price const slippagePrice = await this.getSlippagePrice(convertedSymbol, isBuy, slippage, px); // Market Order is an aggressive Limit Order IoC const orderRequest = { coin: convertedSymbol, is_buy: isBuy, sz: closeSize, limit_px: slippagePrice, order_type: { limit: { tif: 'Ioc' } }, reduce_only: true, }; if (cloid) { orderRequest.cloid = cloid; } return this.exchange.placeOrder(orderRequest); } throw new Error(`No position found for ${convertedSymbol}`); } async closeAllPositions(slippage = this.DEFAULT_SLIPPAGE) { try { await this.parent.ensureInitialized(); const address = this.wallet.address; const positions = await this.infoApi.perpetuals.getClearinghouseState(address); const closeOrders = []; console.log(positions); for (const position of positions.assetPositions) { const item = position.position; if (parseFloat(item.szi) !== 0) { const symbol = await this.symbolConversion.convertSymbol(item.coin, 'forward'); closeOrders.push(this.marketClose(symbol, undefined, undefined, slippage)); } } return await Promise.all(closeOrders); } catch (error) { throw error; } } // see docs: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size async isValidPrice(symbol, price) { const isSpot = isSpotAsset(symbol); const maxDecimals = isSpot ? this.MAX_DECIMALS_SPOT : this.MAX_DECIMALS_PERP; const priceStr = price.toString(); const [_ = [], decimalPart = []] = priceStr.split('.'); const szDecimals = await this.symbolConversion.getAssetDecimals(symbol); if (typeof szDecimals !== 'number') { return false; } // Check significant figures const significantFigures = priceStr.replace('.', '').replace(/^0+/, '').length; // for example 12345.6 if (significantFigures > 5 && decimalPart) { return false; } // Check decimal places // for example 2.6789123123123123 if (decimalPart?.length > maxDecimals - szDecimals) { return false; } return true; } async adjustPrice(symbol, price) { const isSpot = isSpotAsset(symbol); const priceStr = price.toString(); const [integerPart = [], decimalPart = []] = priceStr.split('.'); const szDecimals = await this.symbolConversion.getAssetDecimals(symbol); if (typeof szDecimals !== 'number') { return { success: false }; } let adjustedPrice = price; const significantFiguresInIntegerPart = Number(integerPart) === 0 ? 0 : Math.min(5, integerPart.length); if (significantFiguresInIntegerPart) { adjustedPrice = Number(price.toFixed(5 - significantFiguresInIntegerPart)); } else { let leadingZeroesInDecimalPart = 0; for (let i = 0; i < decimalPart.length; i++) { if (decimalPart[i] === '0') { leadingZeroesInDecimalPart++; } else { break; } } if (leadingZeroesInDecimalPart === 0) { adjustedPrice = Number(price.toFixed(5)); } else { const maxDecimals = isSpot ? this.MAX_DECIMALS_SPOT - szDecimals : this.MAX_DECIMALS_PERP - szDecimals; const decimals = leadingZeroesInDecimalPart + 5 > maxDecimals ? maxDecimals : leadingZeroesInDecimalPart + 5; adjustedPrice = Number(price.toFixed(decimals)); } } return { success: true, data: adjustedPrice }; } // See docs: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/bridge2#:~:text=%7D-,Deposit%20with%20permit,-The%20bridge%20supports async signPermitToDeposit(usdcAmount) { const rpcUrl = getRpcUrls()[ARBITRUM_CHAIN_ID]?.rpc[0]; const provider = new ethers.JsonRpcProvider(rpcUrl); const ownerWallet = new ethers.Wallet(this.wallet.privateKey, provider); const usdcAddress = STABLECOINS[ARBITRUM_CHAIN_ID]?.USDC?.address; if (!usdcAddress) { throw new Error('USDC address not found'); } const usdcContract = new ethers.Contract(usdcAddress, USDC_ABI, ownerWallet); const domain = { name: 'USD Coin', version: '2', chainId: ARBITRUM_CHAIN_ID, verifyingContract: usdcAddress, }; const permitTypes = { Permit: [ { name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; if (!usdcContract) { throw new Error('USDC contract not found'); } const nonces = await usdcContract.nonces(ownerWallet.address); const payload = { owner: ownerWallet.address, // The address of the user with funds we want to deposit spender: HL_BRIDGE2_CA, value: BigInt(usdcAmount), nonce: BigInt(nonces ?? '0'), deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now }; const data = { domain, types: permitTypes, message: payload, }; const signature = await ownerWallet.signTypedData(data.domain, data.types, data.message); const deposits = [ { user: ownerWallet.address, usd: payload.value.toString(), deadline: payload.deadline.toString(), signature, }, ]; return deposits; } } //# sourceMappingURL=custom.js.map