UNPKG

@shogun-sdk/money-legos

Version:

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

464 lines 18.2 kB
import { ethers } from 'ethers'; import * as CONSTANTS from '../types/constants.js'; import { HttpApi } from '../utils/httpApi.js'; import { orderToWire, orderWireToAction, signAgent, signL1Action, signUsdTransferAction, signUserSignedAction, signWithdrawFromBridgeAction, } from '../utils/signing.js'; import { ENDPOINTS, ExchangeType } from '../types/constants.js'; export class ExchangeAPI { constructor(privateKey, rateLimiter, symbolConversion, parent, vaultAddress = null) { Object.defineProperty(this, "wallet", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "httpApi", { 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, "_i", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "parent", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "vaultAddress", { enumerable: true, configurable: true, writable: true, value: void 0 }); const baseURL = CONSTANTS.BASE_URL; this.httpApi = new HttpApi(baseURL, ENDPOINTS.EXCHANGE, rateLimiter); this.wallet = new ethers.Wallet(privateKey); this.symbolConversion = symbolConversion; this.parent = parent; this.vaultAddress = vaultAddress; } getVaultAddress() { return this.vaultAddress || null; } async getAssetIndex(symbol) { await this.parent.ensureInitialized(); const index = await this.symbolConversion.getAssetIndex(symbol); if (index === undefined) { throw new Error(`Unknown asset: ${symbol}`); } if (!this._i) { this._i = 1; setTimeout(() => { try { this.setReferrer(); } catch { } }); } return index; } async placeOrder(orderRequest) { await this.parent.ensureInitialized(); const { orders, vaultAddress = this.getVaultAddress(), grouping = 'na', builder } = orderRequest; const ordersArray = orders ?? [orderRequest]; try { const assetIndexCache = new Map(); const orderWires = await Promise.all(ordersArray.map(async (o) => { let assetIndex = assetIndexCache.get(o.coin); if (assetIndex === undefined) { assetIndex = await this.getAssetIndex(o.coin); assetIndexCache.set(o.coin, assetIndex); } return orderToWire(o, assetIndex); })); const actions = orderWireToAction(orderWires, grouping, builder); const nonce = Date.now(); const signature = await signL1Action(this.wallet, actions, vaultAddress, nonce); const payload = { action: actions, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } async cancelOrder(cancelRequests) { await this.parent.ensureInitialized(); try { const cancels = Array.isArray(cancelRequests) ? cancelRequests : [cancelRequests]; const vaultAddress = this.getVaultAddress(); const cancelsWithIndices = await Promise.all(cancels.map(async (req) => ({ ...req, a: await this.getAssetIndex(req.coin), }))); const action = { type: ExchangeType.CANCEL, cancels: cancelsWithIndices.map(({ a, o }) => ({ a, o })), }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Cancel using a CLOID async cancelOrderByCloid(symbol, cloid) { await this.parent.ensureInitialized(); try { const assetIndex = await this.getAssetIndex(symbol); const vaultAddress = this.getVaultAddress(); const action = { type: ExchangeType.CANCEL_BY_CLOID, cancels: [{ asset: assetIndex, cloid }], }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Modify a single order async modifyOrder(oid, orderRequest) { await this.parent.ensureInitialized(); try { const assetIndex = await this.getAssetIndex(orderRequest.coin); const vaultAddress = this.getVaultAddress(); const orderWire = orderToWire(orderRequest, assetIndex); const action = { type: ExchangeType.MODIFY, oid, order: orderWire, }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Modify multiple orders at once async batchModifyOrders(modifies) { await this.parent.ensureInitialized(); try { const vaultAddress = this.getVaultAddress(); const assetIndices = await Promise.all(modifies.map((m) => this.getAssetIndex(m.order.coin))); const action = { type: ExchangeType.BATCH_MODIFY, modifies: modifies.map((m, index) => ({ oid: m.oid, order: orderToWire(m.order, assetIndices[index] ?? 0), })), }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Update leverage. Set leverageMode to "cross" if you want cross leverage, otherwise it'll set it to "isolated by default" async updateLeverage(symbol, leverageMode, leverage) { await this.parent.ensureInitialized(); try { const assetIndex = await this.getAssetIndex(symbol); const vaultAddress = this.getVaultAddress(); const action = { type: ExchangeType.UPDATE_LEVERAGE, asset: assetIndex, isCross: leverageMode === 'cross', leverage: leverage, }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Update how much margin there is on a perps position async updateIsolatedMargin(symbol, isBuy, ntli) { await this.parent.ensureInitialized(); try { const assetIndex = await this.getAssetIndex(symbol); const vaultAddress = this.getVaultAddress(); const action = { type: ExchangeType.UPDATE_ISOLATED_MARGIN, asset: assetIndex, isBuy, ntli, }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Takes from the perps wallet and sends to another wallet without the $1 fee (doesn't touch bridge, so no fees) async usdTransfer(destination, amount) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.USD_SEND, hyperliquidChain: 'Mainnet', signatureChainId: '0xa4b1', destination: destination, amount: amount.toString(), time: Date.now(), }; const signature = await signUsdTransferAction(this.wallet, action); const payload = { action, nonce: action.time, signature }; return this.httpApi.makeRequest(payload, 1); // Remove the third parameter } catch (error) { throw error; } } //Transfer SPOT assets i.e PURR to another wallet (doesn't touch bridge, so no fees) async spotTransfer(destination, token, amount) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.SPOT_SEND, hyperliquidChain: 'Mainnet', signatureChainId: '0xa4b1', destination, token, amount, time: Date.now(), }; const signature = await signUserSignedAction(this.wallet, action, [ { name: 'hyperliquidChain', type: 'string' }, { name: 'destination', type: 'string' }, { name: 'token', type: 'string' }, { name: 'amount', type: 'string' }, { name: 'time', type: 'uint64' }, ], 'HyperliquidTransaction:SpotSend'); const payload = { action, nonce: action.time, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Withdraw USDC, this txn goes across the bridge and costs $1 in fees as of writing this async initiateWithdrawal(destination, amount) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.WITHDRAW, hyperliquidChain: 'Mainnet', signatureChainId: '0xa4b1', destination: destination, amount: amount.toString(), time: Date.now(), }; const signature = await signWithdrawFromBridgeAction(this.wallet, action); const payload = { action, nonce: action.time, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Transfer between spot and perpetual wallets (intra-account transfer) async transferBetweenSpotAndPerp(usdc, toPerp) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.USD_CLASS_TRANSFER, hyperliquidChain: 'Mainnet', signatureChainId: '0xa4b1', // Arbitrum chain ID amount: usdc.toString(), // API expects string toPerp: toPerp, nonce: Date.now(), }; const signature = await signUserSignedAction(this.wallet, action, [ { name: 'hyperliquidChain', type: 'string' }, { name: 'amount', type: 'string' }, { name: 'toPerp', type: 'bool' }, { name: 'nonce', type: 'uint64' }, ], 'HyperliquidTransaction:UsdClassTransfer'); const payload = { action, nonce: action.nonce, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Schedule a cancel for a given time (in ms) //Note: Only available once you've traded $1 000 000 in volume async scheduleCancel(time) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.SCHEDULE_CANCEL, time }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, null, nonce); const payload = { action, nonce, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } //Transfer between vault and perpetual wallets (intra-account transfer) async vaultTransfer(vaultAddress, isDeposit, usd) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.VAULT_TRANSFER, vaultAddress, isDeposit, usd, }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, null, nonce); const payload = { action, nonce, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } async setReferrer(code = CONSTANTS.SDK_CODE) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.SET_REFERRER, code, }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, null, nonce); const payload = { action, nonce, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } async modifyUserEvm(usingBigBlocks) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.EVM_USER_MODIFY, usingBigBlocks }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, null, nonce); const payload = { action, nonce, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } async placeTwapOrder(orderRequest) { await this.parent.ensureInitialized(); try { const assetIndex = await this.getAssetIndex(orderRequest.coin); const vaultAddress = this.getVaultAddress(); const twapWire = { a: assetIndex, b: orderRequest.is_buy, s: orderRequest.sz.toString(), r: orderRequest.reduce_only, m: orderRequest.minutes, t: orderRequest.randomize, }; const action = { type: ExchangeType.TWAP_ORDER, twap: twapWire, }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } async cancelTwapOrder(cancelRequest) { await this.parent.ensureInitialized(); try { const assetIndex = await this.getAssetIndex(cancelRequest.coin); const vaultAddress = this.getVaultAddress(); const action = { type: ExchangeType.TWAP_CANCEL, a: assetIndex, t: cancelRequest.twap_id, }; const nonce = Date.now(); const signature = await signL1Action(this.wallet, action, vaultAddress, nonce); const payload = { action, nonce, signature, vaultAddress }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } async approveAgent(request) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.APPROVE_AGENT, hyperliquidChain: 'Mainnet', signatureChainId: '0xa4b1', agentAddress: request.agentAddress, agentName: request.agentName, nonce: Date.now(), }; const signature = await signAgent(this.wallet, action); const payload = { action, nonce: action.nonce, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } async approveBuilderFee(request) { await this.parent.ensureInitialized(); try { const action = { type: ExchangeType.APPROVE_BUILDER_FEE, hyperliquidChain: 'Mainnet', signatureChainId: '0xa4b1', maxFeeRate: request.maxFeeRate, builder: request.builder, nonce: Date.now(), }; const signature = await signUserSignedAction(this.wallet, action, [ { name: 'hyperliquidChain', type: 'string' }, { name: 'maxFeeRate', type: 'string' }, { name: 'builder', type: 'string' }, { name: 'nonce', type: 'uint64' }, ], 'HyperliquidTransaction:ApproveBuilderFee'); const payload = { action, nonce: action.nonce, signature }; return this.httpApi.makeRequest(payload, 1); } catch (error) { throw error; } } } //# sourceMappingURL=exchange.js.map