mirror of
https://github.com/orionprotocol/sdk.git
synced 2026-04-07 05:28:05 +03:00
Semantics improvements
This commit is contained in:
16
src/services/Aggregator/ws/MessageType.ts
Normal file
16
src/services/Aggregator/ws/MessageType.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
const MessageType = {
|
||||
ERROR: 'e',
|
||||
PING_PONG: 'pp',
|
||||
SWAP_INFO: 'si',
|
||||
INITIALIZATION: 'i',
|
||||
AGGREGATED_ORDER_BOOK_UPDATE: 'aobu',
|
||||
ASSET_PAIRS_CONFIG_UPDATE: 'apcu',
|
||||
ASSET_PAIR_CONFIG_UPDATE: 'apiu',
|
||||
ADDRESS_UPDATE: 'au',
|
||||
CFD_ADDRESS_UPDATE: 'auf',
|
||||
FUTURES_TRADE_INFO_UPDATE: 'fti',
|
||||
BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE: 'btasabu',
|
||||
UNSUBSCRIPTION_DONE: 'ud',
|
||||
} as const;
|
||||
|
||||
export default MessageType;
|
||||
12
src/services/Aggregator/ws/SubscriptionType.ts
Normal file
12
src/services/Aggregator/ws/SubscriptionType.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const SubscriptionType = {
|
||||
ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE: 'apcus',
|
||||
ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE: 'apius',
|
||||
AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE: 'aobus',
|
||||
ADDRESS_UPDATES_SUBSCRIBE: 'aus',
|
||||
CFD_ADDRESS_UPDATES_SUBSCRIBE: 'ausf',
|
||||
BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE: 'btasabus',
|
||||
SWAP_SUBSCRIBE: 'ss',
|
||||
FUTURES_TRADE_INFO_SUBSCRIBE: 'fts',
|
||||
} as const;
|
||||
|
||||
export default SubscriptionType;
|
||||
5
src/services/Aggregator/ws/UnsubscriptionType.ts
Normal file
5
src/services/Aggregator/ws/UnsubscriptionType.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
const UnsubscriptionType = {
|
||||
ASSET_PAIRS_CONFIG_UPDATES_UNSUBSCRIBE: 'apcu',
|
||||
BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_UNSUBSCRIBE: 'btasabu',
|
||||
} as const;
|
||||
export default UnsubscriptionType;
|
||||
664
src/services/Aggregator/ws/index.ts
Normal file
664
src/services/Aggregator/ws/index.ts
Normal file
@@ -0,0 +1,664 @@
|
||||
import { z } from 'zod';
|
||||
import WebSocket from 'isomorphic-ws';
|
||||
import { validate as uuidValidate, v4 as uuidv4 } from 'uuid';
|
||||
import MessageType from './MessageType.js';
|
||||
import SubscriptionType from './SubscriptionType.js';
|
||||
import {
|
||||
pingPongMessageSchema, initMessageSchema,
|
||||
errorSchema, brokerMessageSchema, orderBookSchema,
|
||||
assetPairsConfigSchema, addressUpdateSchema, swapInfoSchema,
|
||||
} from './schemas/index.js';
|
||||
import UnsubscriptionType from './UnsubscriptionType.js';
|
||||
import type {
|
||||
SwapInfoBase, AssetPairUpdate, OrderbookItem,
|
||||
Balance, Exchange, CFDBalance, FuturesTradeInfo, SwapInfo, Json,
|
||||
} from '../../../types.js';
|
||||
import unsubscriptionDoneSchema from './schemas/unsubscriptionDoneSchema.js';
|
||||
import assetPairConfigSchema from './schemas/assetPairConfigSchema.js';
|
||||
import type { fullOrderSchema, orderUpdateSchema } from './schemas/addressUpdateSchema.js';
|
||||
import cfdAddressUpdateSchema from './schemas/cfdAddressUpdateSchema.js';
|
||||
import futuresTradeInfoSchema from './schemas/futuresTradeInfoSchema.js';
|
||||
import { objectKeys } from '../../../utils/objectKeys.js';
|
||||
// import errorSchema from './schemas/errorSchema';
|
||||
|
||||
const UNSUBSCRIBE = 'u';
|
||||
|
||||
type SwapSubscriptionRequest = {
|
||||
// d: string, // swap request UUID, set by client side
|
||||
i: string // asset in
|
||||
o: string // asset out
|
||||
a: number // amount IN/OUT
|
||||
es?: Exchange[] | 'cex' | 'pools' // exchange list of all cex or all pools (ORION_POOL, UNISWAP, PANCAKESWAP etc)
|
||||
e?: boolean // is amount IN? Value `false` means a = amount OUT, `true` if omitted
|
||||
is?: boolean // instant settlement
|
||||
}
|
||||
|
||||
type BrokerTradableAtomicSwapBalanceSubscription = {
|
||||
callback: (balances: Partial<Record<string, number>>) => void
|
||||
}
|
||||
|
||||
type PairsConfigSubscription = {
|
||||
callback: ({ kind, data }: {
|
||||
kind: 'initial' | 'update'
|
||||
data: Partial<Record<string, AssetPairUpdate>>
|
||||
}) => void
|
||||
}
|
||||
|
||||
type PairConfigSubscription = {
|
||||
payload: string
|
||||
callback: ({ kind, data }: {
|
||||
kind: 'initial' | 'update'
|
||||
data: AssetPairUpdate
|
||||
}) => void
|
||||
}
|
||||
|
||||
type AggregatedOrderbookSubscription = {
|
||||
payload: string
|
||||
callback: (
|
||||
asks: OrderbookItem[],
|
||||
bids: OrderbookItem[],
|
||||
pair: string
|
||||
) => void
|
||||
errorCb?: (message: string) => void
|
||||
}
|
||||
|
||||
type SwapInfoSubscription = {
|
||||
payload: SwapSubscriptionRequest
|
||||
callback: (swapInfo: SwapInfo) => void
|
||||
}
|
||||
|
||||
type FuturesTradeInfoSubscription = {
|
||||
payload: {
|
||||
s: string
|
||||
i: string
|
||||
a: number
|
||||
p?: number
|
||||
}
|
||||
callback: (futuresTradeInfo: FuturesTradeInfo) => void
|
||||
errorCb?: (message: string) => void
|
||||
}
|
||||
|
||||
type AddressUpdateUpdate = {
|
||||
kind: 'update'
|
||||
balances: Partial<
|
||||
Record<
|
||||
string,
|
||||
Balance
|
||||
>
|
||||
>
|
||||
order?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema> | undefined
|
||||
}
|
||||
|
||||
type AddressUpdateInitial = {
|
||||
kind: 'initial'
|
||||
balances: Partial<
|
||||
Record<
|
||||
string,
|
||||
Balance
|
||||
>
|
||||
>
|
||||
orders?: Array<z.infer<typeof fullOrderSchema>> | undefined // The field is not defined if the user has no orders
|
||||
}
|
||||
|
||||
type CfdAddressUpdateUpdate = {
|
||||
kind: 'update'
|
||||
balances?: CFDBalance[] | undefined
|
||||
order?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema> | undefined
|
||||
}
|
||||
|
||||
type CfdAddressUpdateInitial = {
|
||||
kind: 'initial'
|
||||
balances: CFDBalance[]
|
||||
orders?: Array<z.infer<typeof fullOrderSchema>> | undefined // The field is not defined if the user has no orders
|
||||
}
|
||||
|
||||
type AddressUpdateSubscription = {
|
||||
payload: string
|
||||
callback: (data: AddressUpdateUpdate | AddressUpdateInitial) => void
|
||||
errorCb?: (message: string) => void
|
||||
}
|
||||
|
||||
type CfdAddressUpdateSubscription = {
|
||||
payload: string
|
||||
callback: (data: CfdAddressUpdateUpdate | CfdAddressUpdateInitial) => void
|
||||
}
|
||||
|
||||
type Subscription = {
|
||||
[SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE]: AddressUpdateSubscription
|
||||
[SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE]: CfdAddressUpdateSubscription
|
||||
[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]: AggregatedOrderbookSubscription
|
||||
[SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]: PairsConfigSubscription
|
||||
[SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]: PairConfigSubscription
|
||||
[SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE]: BrokerTradableAtomicSwapBalanceSubscription
|
||||
[SubscriptionType.SWAP_SUBSCRIBE]: SwapInfoSubscription
|
||||
[SubscriptionType.FUTURES_TRADE_INFO_SUBSCRIBE]: FuturesTradeInfoSubscription
|
||||
}
|
||||
|
||||
const exclusiveSubscriptions = [
|
||||
SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE,
|
||||
SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE,
|
||||
] as const;
|
||||
|
||||
type BufferLike =
|
||||
| string
|
||||
| Buffer
|
||||
| DataView
|
||||
| number
|
||||
| ArrayBufferView
|
||||
| Uint8Array
|
||||
| ArrayBuffer
|
||||
| SharedArrayBuffer
|
||||
| readonly unknown[]
|
||||
| readonly number[]
|
||||
| { valueOf: () => ArrayBuffer }
|
||||
| { valueOf: () => SharedArrayBuffer }
|
||||
| { valueOf: () => Uint8Array }
|
||||
| { valueOf: () => readonly number[] }
|
||||
| { valueOf: () => string }
|
||||
| { [Symbol.toPrimitive]: (hint: string) => string };
|
||||
|
||||
const isSubType = (subType: string): subType is keyof Subscription => Object.values(SubscriptionType).some((t) => t === subType);
|
||||
|
||||
const unknownMessageTypeRegex = /An unknown message type: '(.*)', json: (.*)/;
|
||||
const nonExistentMessageRegex = /Could not cancel nonexistent subscription: (.*)/;
|
||||
class AggregatorWS {
|
||||
private ws?: WebSocket | undefined;
|
||||
|
||||
// is used to make sure we do not need to renew ws subscription
|
||||
// we can not be sure that onclose event will recieve our code when we do `ws.close(4000)`
|
||||
// since sometimes it can be replaced with system one.
|
||||
// https://stackoverflow.com/questions/19304157/getting-the-reason-why-websockets-closed-with-close-code-1006
|
||||
private isClosedIntentionally = false;
|
||||
|
||||
private subscriptions: Partial<{
|
||||
[K in keyof Subscription]: Partial<Record<string, Subscription[K]>>
|
||||
}> = {};
|
||||
|
||||
public onInit: (() => void) | undefined
|
||||
|
||||
public onError: ((err: string) => void) | undefined
|
||||
|
||||
public logger: ((message: string) => void) | undefined
|
||||
|
||||
private readonly wsUrl: string;
|
||||
|
||||
get api() {
|
||||
return this.wsUrl;
|
||||
}
|
||||
|
||||
constructor(
|
||||
wsUrl: string,
|
||||
logger?: (msg: string) => void,
|
||||
onInit?: () => void,
|
||||
onError?: (err: string) => void
|
||||
) {
|
||||
this.wsUrl = wsUrl;
|
||||
this.logger = logger;
|
||||
this.onInit = onInit;
|
||||
this.onError = onError;
|
||||
}
|
||||
|
||||
private sendRaw(data: BufferLike) {
|
||||
if (this.ws?.readyState === 1) {
|
||||
this.ws.send(data);
|
||||
} else if (this.ws?.readyState === 0) {
|
||||
setTimeout(() => {
|
||||
this.sendRaw(data);
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
private send(jsonObject: Json) {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
const jsonData = JSON.stringify(jsonObject);
|
||||
this.ws.send(jsonData);
|
||||
this.logger?.(`Sent: ${jsonData}`);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.send(jsonObject);
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
subscribe<T extends typeof SubscriptionType[keyof typeof SubscriptionType]>(
|
||||
type: T,
|
||||
subscription: Subscription[T],
|
||||
) {
|
||||
if (!this.ws) this.init();
|
||||
const isExclusive = exclusiveSubscriptions.some((t) => t === type);
|
||||
const subs = this.subscriptions[type];
|
||||
if (isExclusive && subs && Object.keys(subs).length > 0) {
|
||||
throw new Error(`Subscription '${type}' already exists. Please unsubscribe first.`);
|
||||
}
|
||||
|
||||
const id = type === 'aobus'
|
||||
? ((subscription as any).payload as string) // TODO: Refactor!!!
|
||||
: uuidv4();
|
||||
const subRequest: Json = {};
|
||||
subRequest['T'] = type;
|
||||
subRequest['id'] = id;
|
||||
|
||||
// TODO Refactor this
|
||||
if ('payload' in subscription) {
|
||||
if (typeof subscription.payload === 'string') {
|
||||
subRequest['S'] = subscription.payload;
|
||||
} else {
|
||||
subRequest['S'] = {
|
||||
d: id,
|
||||
...subscription.payload,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.send(subRequest);
|
||||
|
||||
const subKey = isExclusive ? 'default' : id;
|
||||
this.subscriptions[type] = {
|
||||
...this.subscriptions[type],
|
||||
[subKey]: subscription,
|
||||
};
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
unsubscribe(subscription: keyof typeof UnsubscriptionType | string, details?: string) {
|
||||
this.send({
|
||||
T: UNSUBSCRIBE,
|
||||
S: subscription,
|
||||
...(details !== undefined) && { d: details },
|
||||
});
|
||||
|
||||
if (subscription.includes('0x')) { // is wallet address (ADDRESS_UPDATE)
|
||||
const auSubscriptions = this.subscriptions[SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE];
|
||||
if (auSubscriptions) {
|
||||
const targetAuSub = Object.entries(auSubscriptions).find(([, value]) => value?.payload === subscription);
|
||||
if (targetAuSub) {
|
||||
const [key] = targetAuSub;
|
||||
delete this.subscriptions[SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE]?.[key];
|
||||
}
|
||||
}
|
||||
|
||||
const aufSubscriptions = this.subscriptions[SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE];
|
||||
if (aufSubscriptions) {
|
||||
const targetAufSub = Object.entries(aufSubscriptions).find(([, value]) => value?.payload === subscription);
|
||||
if (targetAufSub) {
|
||||
const [key] = targetAufSub;
|
||||
delete this.subscriptions[SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE]?.[key];
|
||||
}
|
||||
}
|
||||
} else if (uuidValidate(subscription)) {
|
||||
// is swap info subscription (contains hyphen)
|
||||
delete this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.[subscription];
|
||||
delete this.subscriptions[SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]?.[subscription];
|
||||
delete this.subscriptions[SubscriptionType.FUTURES_TRADE_INFO_SUBSCRIBE]?.[subscription];
|
||||
// !!! swap info subscription is uuid that contains hyphen
|
||||
} else if (subscription.includes('-') && subscription.split('-').length === 2) { // is pair name(AGGREGATED_ORDER_BOOK_UPDATE)
|
||||
const aobSubscriptions = this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE];
|
||||
if (aobSubscriptions) {
|
||||
const targetAobSub = Object.entries(aobSubscriptions).find(([, value]) => value?.payload === subscription);
|
||||
if (targetAobSub) {
|
||||
const [key] = targetAobSub;
|
||||
delete this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]?.[key];
|
||||
}
|
||||
}
|
||||
} else if (subscription === UnsubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_UNSUBSCRIBE) {
|
||||
delete this.subscriptions[SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]?.['default'];
|
||||
} else if (subscription === UnsubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_UNSUBSCRIBE) {
|
||||
delete this.subscriptions[SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE]?.['default'];
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.isClosedIntentionally = true;
|
||||
this.ws?.close();
|
||||
delete this.ws;
|
||||
}
|
||||
|
||||
private init(isReconnect = false) {
|
||||
this.isClosedIntentionally = false;
|
||||
this.ws = new WebSocket(this.wsUrl);
|
||||
this.ws.onerror = (err) => {
|
||||
this.logger?.(`AggregatorWS: ${err.message}`);
|
||||
};
|
||||
this.ws.onclose = () => {
|
||||
this.logger?.(`AggregatorWS: connection closed ${this.isClosedIntentionally ? 'intentionally' : ''}`);
|
||||
if (!this.isClosedIntentionally) this.init(true);
|
||||
};
|
||||
this.ws.onopen = () => {
|
||||
// Re-subscribe to all subscriptions
|
||||
if (isReconnect) {
|
||||
const subscriptionsToReconnect = this.subscriptions;
|
||||
this.subscriptions = {};
|
||||
Object.keys(subscriptionsToReconnect)
|
||||
.filter(isSubType)
|
||||
.forEach((subType) => {
|
||||
const subscriptions = subscriptionsToReconnect[subType];
|
||||
if (subscriptions) {
|
||||
Object.keys(subscriptions).forEach((subKey) => {
|
||||
const sub = subscriptions[subKey];
|
||||
if (sub) this.subscribe(subType, sub);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
this.logger?.(`AggregatorWS: connection opened${isReconnect ? ' (reconnect)' : ''}`);
|
||||
};
|
||||
this.ws.onmessage = (e) => {
|
||||
const { data } = e;
|
||||
if (typeof data !== 'string') throw new Error('AggregatorWS: received non-string message');
|
||||
this.logger?.(`AggregatorWS: received message: ${data}`);
|
||||
const rawJson: unknown = JSON.parse(data);
|
||||
|
||||
const messageSchema = z.union([
|
||||
initMessageSchema,
|
||||
pingPongMessageSchema,
|
||||
addressUpdateSchema,
|
||||
cfdAddressUpdateSchema,
|
||||
assetPairsConfigSchema,
|
||||
assetPairConfigSchema,
|
||||
brokerMessageSchema,
|
||||
orderBookSchema,
|
||||
swapInfoSchema,
|
||||
futuresTradeInfoSchema,
|
||||
errorSchema,
|
||||
unsubscriptionDoneSchema,
|
||||
]);
|
||||
|
||||
const json = messageSchema.parse(rawJson);
|
||||
|
||||
switch (json.T) {
|
||||
case MessageType.ERROR: {
|
||||
const err = errorSchema.parse(json);
|
||||
// Get subscription error callback
|
||||
// 2. Find subscription by id
|
||||
// 3. Call onError callback
|
||||
|
||||
const { id, m } = err;
|
||||
if (id !== undefined) {
|
||||
const nonExistentMessageMatch = m.match(nonExistentMessageRegex);
|
||||
const unknownMessageMatch = m.match(unknownMessageTypeRegex);
|
||||
if (nonExistentMessageMatch !== null) {
|
||||
const [, subscription] = nonExistentMessageMatch;
|
||||
if (subscription === undefined) throw new TypeError('Subscription is undefined. This should not happen.')
|
||||
console.warn(`You tried to unsubscribe from non-existent subscription '${subscription}'. This is probably a bug in the code. Please be sure that you are unsubscribing from the subscription that you are subscribed to.`)
|
||||
} else if (unknownMessageMatch !== null) {
|
||||
const [, subscription, jsonPayload] = unknownMessageMatch;
|
||||
if (subscription === undefined) throw new TypeError('Subscription is undefined. This should not happen.')
|
||||
if (jsonPayload === undefined) throw new TypeError('JSON payload is undefined. This should not happen.')
|
||||
console.warn(`You tried to subscribe to '${subscription}' with unknown payload '${jsonPayload}'. This is probably a bug in the code. Please be sure that you are subscribing to the existing subscription with the correct payload.`)
|
||||
} else {
|
||||
const subType = objectKeys(this.subscriptions).find((st) => this.subscriptions[st]?.[id]);
|
||||
if (subType === undefined) throw new Error(`AggregatorWS: cannot find subscription type by id ${id}. Current subscriptions: ${JSON.stringify(this.subscriptions)}`);
|
||||
const sub = this.subscriptions[subType]?.[id];
|
||||
if (sub === undefined) throw new Error(`AggregatorWS: cannot find subscription by id ${id}. Current subscriptions: ${JSON.stringify(this.subscriptions)}`);
|
||||
if ('errorCb' in sub) {
|
||||
sub.errorCb(err.m);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.onError?.(err.m);
|
||||
}
|
||||
break;
|
||||
case MessageType.PING_PONG:
|
||||
this.sendRaw(data.toString());
|
||||
break;
|
||||
case MessageType.UNSUBSCRIPTION_DONE:
|
||||
// To implement
|
||||
break;
|
||||
case MessageType.SWAP_INFO: {
|
||||
const baseSwapInfo: SwapInfoBase = {
|
||||
swapRequestId: json.S,
|
||||
assetIn: json.ai,
|
||||
assetOut: json.ao,
|
||||
amountIn: json.a,
|
||||
amountOut: json.o,
|
||||
price: json.p,
|
||||
marketPrice: json.mp,
|
||||
minAmountOut: json.mao,
|
||||
minAmountIn: json.ma,
|
||||
path: json.ps,
|
||||
exchanges: json.e,
|
||||
poolOptimal: json.po,
|
||||
...(json.oi) && {
|
||||
orderInfo: {
|
||||
pair: json.oi.p,
|
||||
side: json.oi.s,
|
||||
amount: json.oi.a,
|
||||
safePrice: json.oi.sp,
|
||||
},
|
||||
},
|
||||
alternatives: json.as.map((item) => ({
|
||||
exchanges: item.e,
|
||||
path: item.ps,
|
||||
marketAmountOut: item.mo,
|
||||
marketAmountIn: item.mi,
|
||||
marketPrice: item.mp,
|
||||
availableAmountIn: item.aa,
|
||||
availableAmountOut: item.aao,
|
||||
})),
|
||||
};
|
||||
|
||||
switch (json.k) { // kind
|
||||
case 'exactSpend':
|
||||
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.[json.S]?.callback({
|
||||
kind: json.k,
|
||||
marketAmountOut: json.mo,
|
||||
availableAmountIn: json.aa,
|
||||
...baseSwapInfo,
|
||||
});
|
||||
|
||||
break;
|
||||
case 'exactReceive':
|
||||
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.[json.S]?.callback({
|
||||
kind: json.k,
|
||||
...baseSwapInfo,
|
||||
marketAmountIn: json.mi,
|
||||
availableAmountOut: json.aao,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageType.FUTURES_TRADE_INFO_UPDATE:
|
||||
this.subscriptions[SubscriptionType.FUTURES_TRADE_INFO_SUBSCRIBE]?.[json.id]?.callback({
|
||||
futuresTradeRequestId: json.id,
|
||||
sender: json.S,
|
||||
instrument: json.i,
|
||||
buyPrice: json.bp,
|
||||
sellPrice: json.sp,
|
||||
buyPower: json.bpw,
|
||||
sellPower: json.spw,
|
||||
minAmount: json.ma,
|
||||
});
|
||||
break;
|
||||
case MessageType.INITIALIZATION:
|
||||
this.onInit?.();
|
||||
break;
|
||||
case MessageType.AGGREGATED_ORDER_BOOK_UPDATE: {
|
||||
const { ob, S } = json;
|
||||
const mapOrderbookItems = (rawItems: typeof ob.a | typeof ob.b) => rawItems.reduce<OrderbookItem[]>((acc, item) => {
|
||||
const [
|
||||
price,
|
||||
amount,
|
||||
exchanges,
|
||||
vob,
|
||||
] = item;
|
||||
|
||||
acc.push({
|
||||
price,
|
||||
amount,
|
||||
exchanges,
|
||||
vob: vob.map(([side, pairName]) => ({
|
||||
side,
|
||||
pairName,
|
||||
})),
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
this.subscriptions[
|
||||
SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE
|
||||
]?.[json.S]?.callback(
|
||||
mapOrderbookItems(ob.a),
|
||||
mapOrderbookItems(ob.b),
|
||||
S,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case MessageType.ASSET_PAIR_CONFIG_UPDATE: {
|
||||
const pair = json.u;
|
||||
const [, minQty, pricePrecision] = pair;
|
||||
|
||||
this.subscriptions[
|
||||
SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE
|
||||
]?.[json.id]?.callback({
|
||||
data: {
|
||||
minQty,
|
||||
pricePrecision,
|
||||
},
|
||||
kind: json.k === 'i' ? 'initial' : 'update',
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case MessageType.ASSET_PAIRS_CONFIG_UPDATE: {
|
||||
const pairs = json;
|
||||
const priceUpdates: Partial<Record<string, AssetPairUpdate>> = {};
|
||||
|
||||
pairs.u.forEach(([pairName, minQty, pricePrecision]) => {
|
||||
priceUpdates[pairName] = {
|
||||
minQty,
|
||||
pricePrecision,
|
||||
};
|
||||
});
|
||||
|
||||
this.subscriptions[
|
||||
SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE
|
||||
]?.['default']?.callback({
|
||||
kind: json.k === 'i' ? 'initial' : 'update',
|
||||
data: priceUpdates,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case MessageType.CFD_ADDRESS_UPDATE:
|
||||
switch (json.k) { // message kind
|
||||
case 'i': { // initial
|
||||
const fullOrders = (json.o)
|
||||
? json.o.reduce<Array<z.infer<typeof fullOrderSchema>>>((prev, o) => {
|
||||
prev.push(o);
|
||||
|
||||
return prev;
|
||||
}, [])
|
||||
: undefined;
|
||||
|
||||
this.subscriptions[
|
||||
SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE
|
||||
]?.[json.id]?.callback({
|
||||
kind: 'initial',
|
||||
orders: fullOrders,
|
||||
balances: json.b,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'u': { // update
|
||||
let orderUpdate: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema> | undefined;
|
||||
if (json.o) {
|
||||
const firstOrder = json.o[0];
|
||||
orderUpdate = firstOrder;
|
||||
}
|
||||
|
||||
this.subscriptions[
|
||||
SubscriptionType.CFD_ADDRESS_UPDATES_SUBSCRIBE
|
||||
]?.[json.id]?.callback({
|
||||
kind: 'update',
|
||||
order: orderUpdate,
|
||||
balances: json.b,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MessageType.ADDRESS_UPDATE: {
|
||||
const balances = (json.b)
|
||||
? Object.entries(json.b)
|
||||
.reduce<Partial<Record<string, Balance>>>((prev, [asset, assetBalances]) => {
|
||||
if (!assetBalances) return prev;
|
||||
const [tradable, reserved, contract, wallet, allowance] = assetBalances;
|
||||
|
||||
prev[asset] = {
|
||||
tradable, reserved, contract, wallet, allowance,
|
||||
};
|
||||
|
||||
return prev;
|
||||
}, {})
|
||||
: {};
|
||||
switch (json.k) { // message kind
|
||||
case 'i': { // initial
|
||||
const fullOrders = json.o
|
||||
? json.o.reduce<Array<z.infer<typeof fullOrderSchema>>>((prev, o) => {
|
||||
prev.push(o);
|
||||
|
||||
return prev;
|
||||
}, [])
|
||||
: undefined;
|
||||
|
||||
this.subscriptions[
|
||||
SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE
|
||||
]?.[json.id]?.callback({
|
||||
kind: 'initial',
|
||||
orders: fullOrders,
|
||||
balances,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'u': { // update
|
||||
let orderUpdate: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema> | undefined;
|
||||
if (json.o) {
|
||||
const firstOrder = json.o[0];
|
||||
orderUpdate = firstOrder;
|
||||
}
|
||||
|
||||
this.subscriptions[
|
||||
SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE
|
||||
]?.[json.id]?.callback({
|
||||
kind: 'update',
|
||||
order: orderUpdate,
|
||||
balances,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE: {
|
||||
const brokerBalances: Partial<Record<string, number>> = {};
|
||||
|
||||
json.bb.forEach(([asset, balance]) => {
|
||||
brokerBalances[asset] = balance;
|
||||
});
|
||||
|
||||
this.subscriptions[
|
||||
SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE
|
||||
]?.['default']?.callback(brokerBalances);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export * as schemas from './schemas/index.js';
|
||||
export {
|
||||
AggregatorWS,
|
||||
SubscriptionType,
|
||||
UnsubscriptionType,
|
||||
MessageType,
|
||||
};
|
||||
137
src/services/Aggregator/ws/schemas/addressUpdateSchema.ts
Normal file
137
src/services/Aggregator/ws/schemas/addressUpdateSchema.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { z } from 'zod';
|
||||
import { exchanges } from '../../../../constants/index.js';
|
||||
import orderStatuses from '../../../../constants/orderStatuses.js';
|
||||
import subOrderStatuses from '../../../../constants/subOrderStatuses.js';
|
||||
import MessageType from '../MessageType.js';
|
||||
import balancesSchema from './balancesSchema.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
import executionTypes from '../../../../constants/cfdExecutionTypes.js';
|
||||
|
||||
const baseAddressUpdate = baseMessageSchema.extend({
|
||||
id: z.string(),
|
||||
T: z.literal(MessageType.ADDRESS_UPDATE),
|
||||
S: z.string(), // subscription
|
||||
uc: z.array(z.enum(['b', 'o'])), // update content
|
||||
});
|
||||
|
||||
const subOrderSchema = z.object({
|
||||
i: z.number(), // id
|
||||
I: z.string(), // parent order id
|
||||
O: z.string(), // sender (owner)
|
||||
P: z.string().toUpperCase(), // asset pair
|
||||
s: z.enum(['BUY', 'SELL']), // side
|
||||
a: z.number(), // amount
|
||||
A: z.number(), // settled amount
|
||||
p: z.number(), // avg weighed settlement price
|
||||
e: z.enum(exchanges), // exchange
|
||||
b: z.string(), // broker address
|
||||
S: z.enum(subOrderStatuses), // status
|
||||
o: z.boolean(), // internal only
|
||||
});
|
||||
|
||||
export const orderUpdateSchema = z.object({
|
||||
I: z.string(), // id
|
||||
A: z.number(), // settled amount
|
||||
S: z.enum(orderStatuses), // status
|
||||
l: z.boolean().optional(), // is liquidation order
|
||||
t: z.number(), // update time
|
||||
E: z.enum(executionTypes).optional(), // execution type
|
||||
C: z.string().optional(), // trigger condition
|
||||
c: subOrderSchema.array(),
|
||||
})
|
||||
.transform((val) => ({
|
||||
...val,
|
||||
k: 'update' as const,
|
||||
})).transform((o) => ({
|
||||
kind: o.k,
|
||||
id: o.I,
|
||||
settledAmount: o.A,
|
||||
status: o.S,
|
||||
liquidated: o.l,
|
||||
executionType: o.E,
|
||||
triggerCondition: o.C,
|
||||
subOrders: o.c.map((so) => ({
|
||||
pair: so.P,
|
||||
exchange: so.e,
|
||||
id: so.i,
|
||||
amount: so.a,
|
||||
settledAmount: so.A,
|
||||
price: so.p,
|
||||
status: so.S,
|
||||
side: so.s,
|
||||
subOrdQty: so.A,
|
||||
})),
|
||||
}));
|
||||
|
||||
export const fullOrderSchema = z.object({
|
||||
I: z.string(), // id
|
||||
O: z.string(), // sender (owner)
|
||||
P: z.string().toUpperCase(), // asset pair
|
||||
s: z.enum(['BUY', 'SELL']), // side
|
||||
a: z.number(), // amount
|
||||
A: z.number(), // settled amount
|
||||
p: z.number(), // price
|
||||
F: z.string().toUpperCase(), // fee asset
|
||||
f: z.number(), // fee
|
||||
l: z.boolean().optional(), // is liquidation order
|
||||
L: z.number().optional(), // stop limit price,
|
||||
o: z.boolean(), // internal only
|
||||
S: z.enum(orderStatuses), // status
|
||||
T: z.number(), // creation time / unix timestamp
|
||||
t: z.number(), // update time
|
||||
c: subOrderSchema.array(),
|
||||
E: z.enum(executionTypes).optional(), // execution type
|
||||
C: z.string().optional(), // trigger condition
|
||||
}).transform((val) => ({
|
||||
...val,
|
||||
k: 'full' as const,
|
||||
})).transform((o) => ({
|
||||
kind: o.k,
|
||||
id: o.I,
|
||||
settledAmount: o.A,
|
||||
feeAsset: o.F,
|
||||
fee: o.f,
|
||||
liquidated: o.l,
|
||||
stopPrice: o.L,
|
||||
status: o.S,
|
||||
date: o.T,
|
||||
clientOrdId: o.O,
|
||||
type: o.s,
|
||||
pair: o.P,
|
||||
amount: o.a,
|
||||
price: o.p,
|
||||
executionType: o.E,
|
||||
triggerCondition: o.C,
|
||||
subOrders: o.c.map((so) => ({
|
||||
pair: so.P,
|
||||
exchange: so.e,
|
||||
id: so.i,
|
||||
amount: so.a,
|
||||
settledAmount: so.A,
|
||||
price: so.p,
|
||||
status: so.S,
|
||||
side: so.s,
|
||||
subOrdQty: so.A,
|
||||
})),
|
||||
}));
|
||||
|
||||
const updateMessageSchema = baseAddressUpdate.extend({
|
||||
k: z.literal('u'), // kind of message: "u" - updates
|
||||
uc: z.array(z.enum(['b', 'o'])), // update content: "o" - orders updates, "b" - balance updates
|
||||
b: balancesSchema.optional(),
|
||||
o: z.tuple([fullOrderSchema.or(orderUpdateSchema)]).optional(),
|
||||
});
|
||||
|
||||
const initialMessageSchema = baseAddressUpdate.extend({
|
||||
k: z.literal('i'), // kind of message: "i" - initial
|
||||
b: balancesSchema,
|
||||
o: z.array(fullOrderSchema)
|
||||
.optional(), // When no orders — no field
|
||||
});
|
||||
|
||||
const addressUpdateSchema = z.union([
|
||||
initialMessageSchema,
|
||||
updateMessageSchema,
|
||||
]);
|
||||
|
||||
export default addressUpdateSchema;
|
||||
16
src/services/Aggregator/ws/schemas/assetPairConfigSchema.ts
Normal file
16
src/services/Aggregator/ws/schemas/assetPairConfigSchema.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const assetPairConfigSchema = baseMessageSchema.extend({
|
||||
id: z.string(),
|
||||
T: z.literal(MessageType.ASSET_PAIR_CONFIG_UPDATE),
|
||||
k: z.enum(['i', 'u']),
|
||||
u: z.tuple([
|
||||
z.string().toUpperCase(), // pairName
|
||||
z.number(), // minQty
|
||||
z.number().int(), // pricePrecision
|
||||
]),
|
||||
});
|
||||
|
||||
export default assetPairConfigSchema;
|
||||
18
src/services/Aggregator/ws/schemas/assetPairsConfigSchema.ts
Normal file
18
src/services/Aggregator/ws/schemas/assetPairsConfigSchema.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const assetPairsConfigSchema = baseMessageSchema.extend({
|
||||
id: z.string(),
|
||||
T: z.literal(MessageType.ASSET_PAIRS_CONFIG_UPDATE),
|
||||
k: z.enum(['i', 'u']),
|
||||
u: z.array(
|
||||
z.tuple([
|
||||
z.string().toUpperCase(), // pairName
|
||||
z.number(), // minQty
|
||||
z.number().int(), // pricePrecision
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
export default assetPairsConfigSchema;
|
||||
14
src/services/Aggregator/ws/schemas/balancesSchema.ts
Normal file
14
src/services/Aggregator/ws/schemas/balancesSchema.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
import { makePartial } from '../../../../utils/index.js';
|
||||
|
||||
const balancesSchema = z.record( // changed balances in format
|
||||
z.string().toUpperCase(), // asset
|
||||
z.tuple([
|
||||
z.string(), // tradable balance
|
||||
z.string(), // reserved balance
|
||||
z.string(), // contract balance
|
||||
z.string(), // wallet balance
|
||||
z.string(), // allowance
|
||||
]),
|
||||
).transform(makePartial);
|
||||
export default balancesSchema;
|
||||
9
src/services/Aggregator/ws/schemas/baseMessageSchema.ts
Normal file
9
src/services/Aggregator/ws/schemas/baseMessageSchema.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
|
||||
const baseMessageSchema = z.object({
|
||||
T: z.nativeEnum(MessageType),
|
||||
_: z.number(),
|
||||
});
|
||||
|
||||
export default baseMessageSchema;
|
||||
15
src/services/Aggregator/ws/schemas/brokerMessageSchema.ts
Normal file
15
src/services/Aggregator/ws/schemas/brokerMessageSchema.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const brokerMessageSchema = baseMessageSchema.extend({
|
||||
T: z.literal(MessageType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE),
|
||||
bb: z.array(
|
||||
z.tuple([
|
||||
z.string().toUpperCase(), // Asset name
|
||||
z.number(), // limit
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
export default brokerMessageSchema;
|
||||
33
src/services/Aggregator/ws/schemas/cfdAddressUpdateSchema.ts
Normal file
33
src/services/Aggregator/ws/schemas/cfdAddressUpdateSchema.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { z } from 'zod';
|
||||
import { fullOrderSchema, orderUpdateSchema } from './addressUpdateSchema.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
import MessageType from '../MessageType.js';
|
||||
import cfdBalancesSchema from './cfdBalancesSchema.js';
|
||||
|
||||
const baseCfdAddressUpdate = baseMessageSchema.extend({
|
||||
id: z.string(),
|
||||
T: z.literal(MessageType.CFD_ADDRESS_UPDATE),
|
||||
S: z.string(), // subscription
|
||||
uc: z.array(z.enum(['b', 'o'])), // update content
|
||||
});
|
||||
|
||||
const updateMessageSchema = baseCfdAddressUpdate.extend({
|
||||
k: z.literal('u'), // kind of message: "u" - updates
|
||||
uc: z.array(z.enum(['b', 'o'])), // update content: "o" - orders updates, "b" - balance updates
|
||||
b: cfdBalancesSchema.optional(),
|
||||
o: z.tuple([fullOrderSchema.or(orderUpdateSchema)]).optional(),
|
||||
});
|
||||
|
||||
const initialMessageSchema = baseCfdAddressUpdate.extend({
|
||||
k: z.literal('i'), // kind of message: "i" - initial
|
||||
b: cfdBalancesSchema,
|
||||
o: z.array(fullOrderSchema)
|
||||
.optional(), // When no orders — no field
|
||||
});
|
||||
|
||||
const cfdAddressUpdateSchema = z.union([
|
||||
initialMessageSchema,
|
||||
updateMessageSchema,
|
||||
]);
|
||||
|
||||
export default cfdAddressUpdateSchema
|
||||
52
src/services/Aggregator/ws/schemas/cfdBalancesSchema.ts
Normal file
52
src/services/Aggregator/ws/schemas/cfdBalancesSchema.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { z } from 'zod';
|
||||
import positionStatuses from '../../../../constants/positionStatuses.js';
|
||||
|
||||
const cfdBalanceSchema = z
|
||||
.object({
|
||||
i: z.string(),
|
||||
b: z.string(),
|
||||
pnl: z.string(),
|
||||
fr: z.string(),
|
||||
e: z.string(),
|
||||
p: z.string(),
|
||||
cp: z.string(),
|
||||
pp: z.string(),
|
||||
r: z.string(),
|
||||
m: z.string(),
|
||||
mu: z.string(),
|
||||
fmu: z.string(),
|
||||
awb: z.string(),
|
||||
l: z.string(),
|
||||
s: z.enum(positionStatuses),
|
||||
lfrs: z.string(),
|
||||
lfrd: z.string(),
|
||||
sfrs: z.string(),
|
||||
sfrd: z.string(),
|
||||
sop: z.string().optional(),
|
||||
})
|
||||
.transform((obj) => ({
|
||||
instrument: obj.i,
|
||||
balance: obj.b,
|
||||
profitLoss: obj.pnl,
|
||||
fundingRate: obj.fr,
|
||||
equity: obj.e,
|
||||
position: obj.p,
|
||||
currentPrice: obj.cp,
|
||||
positionPrice: obj.pp,
|
||||
reserves: obj.r,
|
||||
margin: obj.m,
|
||||
marginUSD: obj.mu,
|
||||
freeMarginUSD: obj.fmu,
|
||||
availableWithdrawBalance: obj.awb,
|
||||
leverage: obj.l,
|
||||
status: obj.s,
|
||||
longFundingRatePerSecond: obj.lfrs,
|
||||
longFundingRatePerDay: obj.lfrd,
|
||||
shortFundingRatePerSecond: obj.sfrs,
|
||||
shortFundingRatePerDay: obj.sfrd,
|
||||
stopOutPrice: obj.sop,
|
||||
}));
|
||||
|
||||
const cfdBalancesSchema = z.array(cfdBalanceSchema);
|
||||
|
||||
export default cfdBalancesSchema;
|
||||
12
src/services/Aggregator/ws/schemas/errorSchema.ts
Normal file
12
src/services/Aggregator/ws/schemas/errorSchema.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const errorSchema = baseMessageSchema.extend({
|
||||
T: z.literal(MessageType.ERROR),
|
||||
c: z.number().int(), // code
|
||||
id: z.string().optional(), // subscription id
|
||||
m: z.string(), // error message,
|
||||
});
|
||||
|
||||
export default errorSchema;
|
||||
16
src/services/Aggregator/ws/schemas/futuresTradeInfoSchema.ts
Normal file
16
src/services/Aggregator/ws/schemas/futuresTradeInfoSchema.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
|
||||
const futuresTradeInfoSchema = z.object({
|
||||
T: z.literal(MessageType.FUTURES_TRADE_INFO_UPDATE),
|
||||
id: z.string(), // trade info request UUID, set by client side
|
||||
S: z.string(), // sender
|
||||
i: z.string(), // instrument
|
||||
bp: z.number().optional(), // buy price
|
||||
sp: z.number().optional(), // sell price
|
||||
bpw: z.number(), // buy power
|
||||
spw: z.number(), // sell power
|
||||
ma: z.number(), // min amount
|
||||
});
|
||||
|
||||
export default futuresTradeInfoSchema;
|
||||
12
src/services/Aggregator/ws/schemas/index.ts
Normal file
12
src/services/Aggregator/ws/schemas/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { default as addressUpdateSchema } from './addressUpdateSchema.js';
|
||||
export { default as assetPairsConfigSchema } from './assetPairsConfigSchema.js';
|
||||
export { default as baseMessageSchema } from './baseMessageSchema.js';
|
||||
export { default as brokerMessageSchema } from './brokerMessageSchema.js';
|
||||
export { default as errorSchema } from './errorSchema.js';
|
||||
export { default as initMessageSchema } from './initMessageSchema.js';
|
||||
export { default as pingPongMessageSchema } from './pingPongMessageSchema.js';
|
||||
export { default as swapInfoSchema } from './swapInfoSchema.js';
|
||||
export { default as balancesSchema } from './balancesSchema.js';
|
||||
export { default as cfdBalancesSchema } from './cfdBalancesSchema.js';
|
||||
|
||||
export * from './orderBookSchema.js';
|
||||
10
src/services/Aggregator/ws/schemas/initMessageSchema.ts
Normal file
10
src/services/Aggregator/ws/schemas/initMessageSchema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const initMessageSchema = baseMessageSchema.extend({
|
||||
T: z.literal(MessageType.INITIALIZATION),
|
||||
i: z.string(),
|
||||
});
|
||||
|
||||
export default initMessageSchema;
|
||||
26
src/services/Aggregator/ws/schemas/orderBookSchema.ts
Normal file
26
src/services/Aggregator/ws/schemas/orderBookSchema.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { z } from 'zod';
|
||||
import exchanges from '../../../../constants/exchanges.js';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
export const orderBookItemSchema = z.tuple([
|
||||
z.string(), // price
|
||||
z.string(), // size
|
||||
z.array(
|
||||
z.enum(exchanges),
|
||||
), // exchanges
|
||||
z.array(z.tuple([
|
||||
z.enum(['SELL', 'BUY']), // side
|
||||
z.string().toUpperCase(), // pairname
|
||||
])),
|
||||
]);
|
||||
|
||||
export const orderBookSchema = baseMessageSchema.extend({
|
||||
// id: z.string(),
|
||||
T: z.literal(MessageType.AGGREGATED_ORDER_BOOK_UPDATE),
|
||||
S: z.string(),
|
||||
ob: z.object({
|
||||
a: z.array(orderBookItemSchema),
|
||||
b: z.array(orderBookItemSchema),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const pingPongMessageSchema = baseMessageSchema.extend({
|
||||
T: z.literal(MessageType.PING_PONG),
|
||||
});
|
||||
|
||||
export default pingPongMessageSchema;
|
||||
59
src/services/Aggregator/ws/schemas/swapInfoSchema.ts
Normal file
59
src/services/Aggregator/ws/schemas/swapInfoSchema.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { z } from 'zod';
|
||||
import exchanges from '../../../../constants/exchanges.js';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const alternativeSchema = z.object({ // execution alternatives
|
||||
e: z.enum(exchanges).array(), // exchanges
|
||||
ps: z.string().array(), // path
|
||||
mo: z.number().optional(), // market amount out
|
||||
mi: z.number().optional(), // market amount in
|
||||
mp: z.number(), // market price
|
||||
aa: z.number().optional(), // available amount in
|
||||
aao: z.number().optional(), // available amount out
|
||||
});
|
||||
const swapInfoSchemaBase = baseMessageSchema.extend({
|
||||
T: z.literal(MessageType.SWAP_INFO),
|
||||
S: z.string(), // swap request id
|
||||
ai: z.string().toUpperCase(), // asset in,
|
||||
ao: z.string().toUpperCase(), // asset out
|
||||
a: z.number(), // amount in
|
||||
o: z.number(), // amount out
|
||||
ma: z.number(), // min amount in
|
||||
mao: z.number(), // min amount out
|
||||
ps: z.string().array(), // path
|
||||
po: z.boolean(), // is swap through pool optimal
|
||||
e: z.enum(exchanges).array().optional(), // Exchanges
|
||||
p: z.number().optional(), // price
|
||||
mp: z.number().optional(), // market price
|
||||
oi: z.object({ // info about order equivalent to this swap
|
||||
p: z.string().toUpperCase(), // asset pair
|
||||
s: z.enum(['SELL', 'BUY']), // side
|
||||
a: z.number(), // amount
|
||||
sp: z.number(), // safe price (with safe deviation but without slippage)
|
||||
}).optional(),
|
||||
as: alternativeSchema.array(),
|
||||
});
|
||||
|
||||
const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({
|
||||
mo: z.number().optional(), // market amount out
|
||||
aa: z.number(), // available amount in
|
||||
}).transform((content) => ({
|
||||
...content,
|
||||
k: 'exactSpend' as const,
|
||||
}));
|
||||
|
||||
const swapInfoSchemaByAmountOut = swapInfoSchemaBase.extend({
|
||||
mi: z.number().optional(), // market amount in
|
||||
aao: z.number(), // available amount out
|
||||
}).transform((content) => ({
|
||||
...content,
|
||||
k: 'exactReceive' as const,
|
||||
}));
|
||||
|
||||
const swapInfoSchema = z.union([
|
||||
swapInfoSchemaByAmountIn,
|
||||
swapInfoSchemaByAmountOut,
|
||||
]);
|
||||
|
||||
export default swapInfoSchema;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
|
||||
const unsubscriptionDoneSchema = baseMessageSchema.extend({
|
||||
id: z.string(),
|
||||
T: z.literal(MessageType.UNSUBSCRIPTION_DONE),
|
||||
});
|
||||
|
||||
export default unsubscriptionDoneSchema;
|
||||
Reference in New Issue
Block a user