n8n-nodes-dex
Version:
n8n node module for dYdX v4 trading and account access
179 lines • 8.17 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TradingService = void 0;
const v4_client_js_1 = require("@dydxprotocol/v4-client-js");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const crypto_1 = __importDefault(require("crypto"));
class TradingService {
async init(network, address) {
this.composite = await v4_client_js_1.CompositeClient.connect(network);
this.indexer = new v4_client_js_1.IndexerClient(network.indexerConfig);
this.address = address;
return this;
}
async placeLimitOrder(mnemonic, params) {
const wallet = await v4_client_js_1.LocalWallet.fromMnemonic(mnemonic, v4_client_js_1.BECH32_PREFIX);
const sub = new v4_client_js_1.CompositeClient.SubaccountInfo(wallet, 0);
const side = params.side === v4_client_js_1.OrderSide.BUY ? v4_client_js_1.OrderSide.BUY : v4_client_js_1.OrderSide.SELL;
const tx = await this.composite.placeOrder(sub, params.market, // market
side, // side - cast to any to avoid TypeScript errors
params.price.toString(), // price
params.size.toString(), // size
v4_client_js_1.OrderType.LIMIT, // orderType
v4_client_js_1.OrderTimeInForce.GTT, // timeInForce
v4_client_js_1.OrderExecution.DEFAULT, // execution
crypto_1.default.randomUUID(), // clientId
v4_client_js_1.OrderFlags.SHORT_TERM);
return {
hash: tx.hash.toString(),
clientId: tx.clientId?.toString() || '',
};
}
async placeMarketOrder(mnemonic, params) {
const wallet = await v4_client_js_1.LocalWallet.fromMnemonic(mnemonic, v4_client_js_1.BECH32_PREFIX);
const sub = new v4_client_js_1.CompositeClient.SubaccountInfo(wallet, 0);
const side = params.side === v4_client_js_1.OrderSide.BUY ? v4_client_js_1.OrderSide.BUY : v4_client_js_1.OrderSide.SELL;
const tx = await this.composite.placeOrder(sub, params.market, // market
side, // side - cast to any to avoid TypeScript errors
'0', // price
params.size.toString(), // size
v4_client_js_1.OrderType.MARKET, // orderType
v4_client_js_1.OrderTimeInForce.IOC, // timeInForce
v4_client_js_1.OrderExecution.DEFAULT, // execution
crypto_1.default.randomUUID(), // clientId
v4_client_js_1.OrderFlags.SHORT_TERM);
return {
hash: tx.hash.toString(),
};
}
async cancelOrder(params, mnemonic, accountId = 0) {
const wallet = await v4_client_js_1.LocalWallet.fromMnemonic(mnemonic, v4_client_js_1.BECH32_PREFIX);
const sub = new v4_client_js_1.CompositeClient.SubaccountInfo(wallet, accountId);
const tx = await this.composite.cancelOrder(sub, params.clientId, v4_client_js_1.OrderFlags.SHORT_TERM, // orderFlags
params.market);
return {
hash: tx.hash.toString(),
};
}
async transferBetweenSubaccounts(mnemonic, params) {
const wallet = await v4_client_js_1.LocalWallet.fromMnemonic(mnemonic, v4_client_js_1.BECH32_PREFIX);
const tx = await this.composite.transfer(wallet, params.fromId, params.toId, params.amount.toString());
return {
hash: tx.hash.toString(),
};
}
async getPositions() {
const subaccounts = await this.indexer.getSubaccounts(this.address);
return subaccounts.map((s) => ({
subaccountId: s.subaccountNumber,
positions: Object.entries(s.openPerpetualPositions || {}).map(([m, p]) => ({
market: m,
size: p.size,
price: p.entryPrice,
status: p.status,
isLong: parseFloat(p.size) > 0,
leverage: new bignumber_js_1.default(p.size).multipliedBy(p.entryPrice).dividedBy(s.equity).toNumber(),
})),
}));
}
async smartPosition(params, mnemonic, accountId = 0) {
// Get current positions
const subaccounts = await this.indexer.getSubaccounts(this.address);
const subaccount = subaccounts.find((s) => s.subaccountNumber === accountId);
if (!subaccount) {
throw new Error(`Subaccount ${accountId} not found`);
}
// Find if we already have a position for this market
const positions = subaccount.openPerpetualPositions || {};
const currentPosition = positions[params.market];
const currentSize = currentPosition ? parseFloat(currentPosition.size) : 0;
// If target size is same as current, do nothing
if (currentSize === params.size) {
return currentSize;
}
// Calculate the difference to achieve target position
const sizeDiff = params.size - currentSize;
const side = sizeDiff > 0 ? v4_client_js_1.OrderSide.BUY : v4_client_js_1.OrderSide.SELL;
const size = Math.abs(sizeDiff);
// Get market price and calculate 1% slippage
const marketInfo = await this.getPerpetualMarket(params.market);
const midPrice = (parseFloat(marketInfo.oraclePrice) * (side === v4_client_js_1.OrderSide.BUY ? 1.01 : 0.99));
// Place market order
await this.placeMarketOrder(mnemonic, {
market: params.market,
side: side,
price: midPrice,
size: size,
type: v4_client_js_1.OrderType.MARKET,
});
return params.size;
}
async getAllPerpetualMarkets() {
const markets = await this.indexer.getPerpetualMarkets();
return Object.values(markets);
}
async getPerpetualMarket(market) {
const markets = await this.indexer.getPerpetualMarkets();
return markets[market];
}
async getMarketTrades(market, limit = 50) {
const trades = await this.indexer.getTrades({ ticker: market, limit });
return trades.trades;
}
async getMarketCandles(market, resolution, limit = 50) {
const candles = await this.indexer.getCandles({ ticker: market, resolution, limit });
return candles.candles;
}
async marketSpread(market) {
const orderbook = await this.indexer.getOrderbook(market);
const bestBid = orderbook.bids && orderbook.bids.length > 0 ?
parseFloat(orderbook.bids[0].price) : 0;
const bestAsk = orderbook.asks && orderbook.asks.length > 0 ?
parseFloat(orderbook.asks[0].price) : 0;
// Not returning spread info in the response currently, but might be useful in the future
return {
bestBid: {
price: bestBid,
size: orderbook.bids && orderbook.bids.length > 0 ? parseFloat(orderbook.bids[0].size) : 0,
},
bestAsk: {
price: bestAsk,
size: orderbook.asks && orderbook.asks.length > 0 ? parseFloat(orderbook.asks[0].size) : 0,
}
};
}
async getHistoricalFundingRates(market) {
const rates = await this.indexer.getHistoricalFundingRates({ ticker: market });
return rates.fundingRates;
}
async getSparkline(market) {
// Get 24-hour candles
const endTime = new Date();
const startTime = new Date(endTime.getTime() - 24 * 60 * 60 * 1000);
const candles = await this.indexer.getCandles({
ticker: market,
resolution: '15MINS',
fromISO: startTime.toISOString(),
toISO: endTime.toISOString()
});
if (!candles.candles || candles.candles.length === 0) {
return {
ticker: market,
prices: [],
times: []
};
}
const prices = candles.candles.map((c) => c.close);
const times = candles.candles.map((c) => c.startedAt);
return {
ticker: market,
prices,
times
};
}
}
exports.TradingService = TradingService;
//# sourceMappingURL=trading.service.js.map