@shogun-sdk/money-legos
Version:
Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.
464 lines • 18.2 kB
JavaScript
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