From e864b29b3c94c0554b2be4a3b0d9d46c1997cd12 Mon Sep 17 00:00:00 2001 From: Adam Fraser Date: Tue, 3 Sep 2024 15:32:25 -0400 Subject: [PATCH] Add functionality to send notifications from ender This reverts commit d34c1813908f75aeaec8f745724db15dc8ab73cd. --- indexer/pnpm-lock.yaml | 2 + .../helpers/notification-functions.test.ts | 155 ++++++++++++++++++ indexer/services/ender/package.json | 1 + .../src/handlers/order-fills/order-handler.ts | 10 +- .../conditional-order-triggered-handler.ts | 3 + .../notifications/notifications-functions.ts | 76 +++++++++ 6 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 indexer/services/ender/__tests__/helpers/notification-functions.test.ts create mode 100644 indexer/services/ender/src/helpers/notifications/notifications-functions.ts diff --git a/indexer/pnpm-lock.yaml b/indexer/pnpm-lock.yaml index abf6a1151df..8e6e089ad7f 100644 --- a/indexer/pnpm-lock.yaml +++ b/indexer/pnpm-lock.yaml @@ -551,6 +551,7 @@ importers: '@dydxprotocol-indexer/base': workspace:^0.0.1 '@dydxprotocol-indexer/dev': workspace:^0.0.1 '@dydxprotocol-indexer/kafka': workspace:^0.0.1 + '@dydxprotocol-indexer/notifications': workspace:^0.0.1 '@dydxprotocol-indexer/postgres': workspace:^0.0.1 '@dydxprotocol-indexer/redis': workspace:^0.0.1 '@dydxprotocol-indexer/v4-proto-parser': workspace:^0.0.1 @@ -577,6 +578,7 @@ importers: dependencies: '@dydxprotocol-indexer/base': link:../../packages/base '@dydxprotocol-indexer/kafka': link:../../packages/kafka + '@dydxprotocol-indexer/notifications': link:../../packages/notifications '@dydxprotocol-indexer/postgres': link:../../packages/postgres '@dydxprotocol-indexer/redis': link:../../packages/redis '@dydxprotocol-indexer/v4-proto-parser': link:../../packages/v4-proto-parser diff --git a/indexer/services/ender/__tests__/helpers/notification-functions.test.ts b/indexer/services/ender/__tests__/helpers/notification-functions.test.ts new file mode 100644 index 00000000000..0be17844bce --- /dev/null +++ b/indexer/services/ender/__tests__/helpers/notification-functions.test.ts @@ -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); + }); + }); + }); +}); diff --git a/indexer/services/ender/package.json b/indexer/services/ender/package.json index 32a81997151..b8c40a60f79 100644 --- a/indexer/services/ender/package.json +++ b/indexer/services/ender/package.json @@ -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", diff --git a/indexer/services/ender/src/handlers/order-fills/order-handler.ts b/indexer/services/ender/src/handlers/order-fills/order-handler.ts index f8b98e6c8dc..10f6418f35c 100644 --- a/indexer/services/ender/src/handlers/order-fills/order-handler.ts +++ b/indexer/services/ender/src/handlers/order-fills/order-handler.ts @@ -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'; @@ -113,6 +116,11 @@ export class OrderHandler extends AbstractOrderFillHandler