Skip to content

Commit

Permalink
Add functionality to send notifications from ender
Browse files Browse the repository at this point in the history
This reverts commit d34c181.
  • Loading branch information
adamfraser committed Sep 3, 2024
1 parent f1c5a31 commit aaa9309
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 1 deletion.
2 changes: 2 additions & 0 deletions indexer/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import {
sendOrderFilledNotification,
sendOrderTriggeredNotification,
} from '../../src/helpers/notifications/notifications-functions';
import {
dbHelpers,
OrderFromDatabase,
PerpetualMarketFromDatabase,
PerpetualMarketStatus,
PerpetualMarketType,
SubaccountTable,
testMocks,
} from '@dydxprotocol-indexer/postgres';

import {
createNotification,
sendFirebaseMessage,
NotificationType,
} from '@dydxprotocol-indexer/notifications';
import {
defaultSubaccountId,
defaultMarket,
defaultToken,
} from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants';

// Mock only the sendFirebaseMessage function
jest.mock('@dydxprotocol-indexer/notifications', () => {
const actualModule = jest.requireActual('@dydxprotocol-indexer/notifications');
return {
...actualModule, // keep all other exports intact
sendFirebaseMessage: jest.fn(),
createNotification: jest.fn(),
};
});

const mockMarket: PerpetualMarketFromDatabase = {
id: '1',
clobPairId: '1',
ticker: 'BTC-USD',
marketId: 1,
status: PerpetualMarketStatus.ACTIVE,
priceChange24H: '0',
volume24H: '0',
trades24H: 0,
nextFundingRate: '0',
openInterest: '0',
quantumConversionExponent: 1,
atomicResolution: 1,
subticksPerTick: 1,
stepBaseQuantums: 1,
liquidityTierId: 1,
marketType: PerpetualMarketType.ISOLATED,
baseOpenInterest: '0',
};

describe('notification functions', () => {
beforeEach(async () => {
await testMocks.seedData();
});

afterEach(async () => {
await dbHelpers.clearData();
});
describe('sendOrderFilledNotification', () => {
it('should create and send an order filled notification', async () => {
const mockOrder: OrderFromDatabase = {
id: '1',
subaccountId: defaultSubaccountId,
clientId: '1',
clobPairId: String(defaultMarket.id),
side: 'BUY',
size: '10',
totalFilled: '0',
price: '100.50',
type: 'LIMIT',
status: 'OPEN',
timeInForce: 'GTT',
reduceOnly: false,
orderFlags: '0',
goodTilBlock: '1000000',
createdAtHeight: '900000',
clientMetadata: '0',
triggerPrice: undefined,
updatedAt: new Date().toISOString(),
updatedAtHeight: '900001',
} as OrderFromDatabase;

await sendOrderFilledNotification(mockOrder, mockMarket);

// Assert that createNotification was called with correct arguments
expect(createNotification).toHaveBeenCalledWith(
NotificationType.ORDER_FILLED,
{
AMOUNT: '10',
MARKET: 'BTC-USD',
AVERAGE_PRICE: '100.50',
},
);

expect(sendFirebaseMessage).toHaveBeenCalledWith(
[
expect.objectContaining({
token: defaultToken.token,
language: defaultToken.language,
}),
],
undefined,
);
});

describe('sendOrderTriggeredNotification', () => {
it('should create and send an order triggered notification', async () => {
const subaccount = await SubaccountTable.findById(defaultSubaccountId);
const mockOrder: OrderFromDatabase = {
id: '1',
subaccountId: subaccount!.id,
clientId: '1',
clobPairId: '1',
side: 'BUY',
size: '10',
price: '100.50',
type: 'LIMIT',
status: 'OPEN',
timeInForce: 'GTT',
reduceOnly: false,
orderFlags: '0',
goodTilBlock: '1000000',
createdAtHeight: '900000',
clientMetadata: '0',
triggerPrice: '99.00',
updatedAt: new Date().toISOString(),
updatedAtHeight: '900001',
} as OrderFromDatabase;

await sendOrderTriggeredNotification(mockOrder, mockMarket, subaccount!);

expect(createNotification).toHaveBeenCalledWith(
NotificationType.ORDER_TRIGGERED,
{
MARKET: 'BTC-USD',
PRICE: '100.50',
AMOUNT: '10',
},
);

expect(sendFirebaseMessage).toHaveBeenCalledWith([expect.objectContaining(
{
token: defaultToken.token,
language: defaultToken.language,
},
)], undefined);
});
});
});
});
1 change: 1 addition & 0 deletions indexer/services/ender/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@dydxprotocol-indexer/redis": "workspace:^0.0.1",
"@dydxprotocol-indexer/v4-proto-parser": "workspace:^0.0.1",
"@dydxprotocol-indexer/v4-protos": "workspace:^0.0.1",
"@dydxprotocol-indexer/notifications": "workspace:^0.0.1",
"big.js": "^6.0.2",
"dd-trace": "^3.32.1",
"dotenv-flow": "^3.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ import {
} from '@dydxprotocol-indexer/postgres';
import { StateFilledQuantumsCache } from '@dydxprotocol-indexer/redis';
import { isStatefulOrder } from '@dydxprotocol-indexer/v4-proto-parser';
import { IndexerOrder, IndexerOrderId, IndexerSubaccountId } from '@dydxprotocol-indexer/v4-protos';
import {
IndexerOrder, IndexerOrderId, IndexerSubaccountId,
} from '@dydxprotocol-indexer/v4-protos';
import Long from 'long';
import * as pg from 'pg';

import { STATEFUL_ORDER_ORDER_FILL_EVENT_TYPE, SUBACCOUNT_ORDER_FILL_EVENT_TYPE } from '../../constants';
import { annotateWithPnl, convertPerpetualPosition } from '../../helpers/kafka-helper';
import { sendOrderFilledNotification } from '../../helpers/notifications/notifications-functions';
import { redisClient } from '../../helpers/redis/redis-controller';
import { orderFillWithLiquidityToOrderFillEventWithOrder } from '../../helpers/translation-helper';
import { OrderFillWithLiquidity } from '../../lib/translated-types';
Expand Down Expand Up @@ -113,6 +116,11 @@ export class OrderHandler extends AbstractOrderFillHandler<OrderFillWithLiquidit
redisClient,
);

// If order is filled, send a notification to firebase
if (order.status === OrderStatus.FILLED) {
await sendOrderFilledNotification(order, perpetualMarket);
}

// If the order is stateful and fully-filled, send an order removal to vulcan. We only do this
// for stateful orders as we are guaranteed a stateful order cannot be replaced until the next
// block.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@dydxprotocol-indexer/v4-protos';
import * as pg from 'pg';

import { sendOrderTriggeredNotification } from '../../helpers/notifications/notifications-functions';
import { ConsolidatedKafkaEvent } from '../../lib/types';
import { AbstractStatefulOrderHandler } from '../abstract-stateful-order-handler';

Expand All @@ -39,6 +40,8 @@ export class ConditionalOrderTriggeredHandler extends

const indexerOrder: IndexerOrder = orderTranslations.convertToIndexerOrderWithSubaccount(
order, perpetualMarket, subaccount);

await sendOrderTriggeredNotification(order, perpetualMarket, subaccount);
return this.createKafkaEvents(indexerOrder);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { logger } from '@dydxprotocol-indexer/base';
import {
createNotification,
NotificationDynamicFieldKey,
NotificationType,
sendFirebaseMessage,
} from '@dydxprotocol-indexer/notifications';
import {
OrderFromDatabase,
PerpetualMarketFromDatabase,
SubaccountFromDatabase,
SubaccountTable,
TokenTable,
} from '@dydxprotocol-indexer/postgres';

export async function sendOrderFilledNotification(
order: OrderFromDatabase,
market: PerpetualMarketFromDatabase,
) {
try {
const subaccount = await SubaccountTable.findById(order.subaccountId);
if (!subaccount) {
throw new Error(`Subaccount not found for id ${order.subaccountId}`);
}

const tokens = (await TokenTable.findAll({ address: subaccount.address }, []));
if (tokens.length === 0) {
throw new Error(`No token found for address ${subaccount.address}`);
}

const notification = createNotification(
NotificationType.ORDER_FILLED,
{
[NotificationDynamicFieldKey.AMOUNT]: order.size.toString(),
[NotificationDynamicFieldKey.MARKET]: market.ticker,
[NotificationDynamicFieldKey.AVERAGE_PRICE]: order.price,
},
);

await sendFirebaseMessage(tokens, notification);
} catch (error) {
logger.error({
at: 'ender#notification-functions',
message: 'Error sending order filled notification',
error,
});
}
}

export async function sendOrderTriggeredNotification(
order: OrderFromDatabase,
market: PerpetualMarketFromDatabase,
subaccount: SubaccountFromDatabase,
) {
try {
const tokens = (await TokenTable.findAll({ address: subaccount.address }, []));
if (tokens.length === 0) {
throw new Error(`No tokens found for address ${subaccount.address}`);
}
const notification = createNotification(
NotificationType.ORDER_TRIGGERED,
{
[NotificationDynamicFieldKey.MARKET]: market.ticker,
[NotificationDynamicFieldKey.PRICE]: order.price,
[NotificationDynamicFieldKey.AMOUNT]: order.size.toString(),
},
);
await sendFirebaseMessage(tokens, notification);
} catch (error) {
logger.error({
at: 'ender#notification-functions',
message: 'Error sending order triggered notification',
error,
});
}
}

0 comments on commit aaa9309

Please sign in to comment.