diff --git a/indexer/packages/postgres/__tests__/helpers/constants.ts b/indexer/packages/postgres/__tests__/helpers/constants.ts index 7a8a9a224b..75adcdea16 100644 --- a/indexer/packages/postgres/__tests__/helpers/constants.ts +++ b/indexer/packages/postgres/__tests__/helpers/constants.ts @@ -158,21 +158,29 @@ export const vaultSubaccountId: string = SubaccountTable.uuid( export const defaultWallet: WalletCreateObject = { address: defaultAddress, totalTradingRewards: denomToHumanReadableConversion(0), + totalVolume: '0', + isWhitelistAffiliate: false, }; export const defaultWallet2: WalletCreateObject = { address: defaultWalletAddress, totalTradingRewards: denomToHumanReadableConversion(1), + totalVolume: '0', + isWhitelistAffiliate: false, }; export const vaultWallet: WalletCreateObject = { address: vaultAddress, totalTradingRewards: denomToHumanReadableConversion(0), + totalVolume: '0', + isWhitelistAffiliate: false, }; export const defaultWallet3: WalletCreateObject = { address: defaultAddress2, totalTradingRewards: denomToHumanReadableConversion(0), + totalVolume: '0', + isWhitelistAffiliate: true, }; // ============== Assets ============== diff --git a/indexer/packages/postgres/__tests__/stores/transfer-table.test.ts b/indexer/packages/postgres/__tests__/stores/transfer-table.test.ts index 758fad9d67..a6f011bfe4 100644 --- a/indexer/packages/postgres/__tests__/stores/transfer-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/transfer-table.test.ts @@ -322,6 +322,8 @@ describe('Transfer store', () => { await WalletTable.create({ address: defaultWalletAddress, totalTradingRewards: '0', + totalVolume: '0', + isWhitelistAffiliate: false, }); const invalidDeposit: TransferCreateObject = { ...defaultDeposit, @@ -353,6 +355,8 @@ describe('Transfer store', () => { await WalletTable.create({ address: defaultWalletAddress, totalTradingRewards: '0', + totalVolume: '0', + isWhitelistAffiliate: false, }); await Promise.all([ TransferTable.create(defaultTransfer), diff --git a/indexer/packages/postgres/__tests__/stores/wallet-table.test.ts b/indexer/packages/postgres/__tests__/stores/wallet-table.test.ts index 84cbabf639..d5c01982ce 100644 --- a/indexer/packages/postgres/__tests__/stores/wallet-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/wallet-table.test.ts @@ -1,6 +1,6 @@ import { WalletFromDatabase } from '../../src/types'; import { clearData, migrate, teardown } from '../../src/helpers/db-helpers'; -import { defaultWallet2 } from '../helpers/constants'; +import { defaultWallet2, defaultWallet3 } from '../helpers/constants'; import * as WalletTable from '../../src/stores/wallet-table'; describe('Wallet store', () => { @@ -27,10 +27,18 @@ describe('Wallet store', () => { ); expect(wallet).toEqual(expect.objectContaining(defaultWallet2)); - await WalletTable.upsert(defaultWallet2); + await WalletTable.upsert({ + ...defaultWallet2, + isWhitelistAffiliate: true, + totalVolume: '100.1', + }); wallet = await WalletTable.findById(defaultWallet2.address); - expect(wallet).toEqual(expect.objectContaining(defaultWallet2)); + expect(wallet).toEqual(expect.objectContaining({ + ...defaultWallet2, + isWhitelistAffiliate: true, + totalVolume: '100.1', + })); }); it('Successfully finds all Wallets', async () => { @@ -39,6 +47,8 @@ describe('Wallet store', () => { WalletTable.create({ address: 'fake_address', totalTradingRewards: '0', + totalVolume: '0', + isWhitelistAffiliate: false, }), ]); @@ -64,4 +74,22 @@ describe('Wallet store', () => { expect(wallet).toEqual(expect.objectContaining(defaultWallet2)); }); + + it('Successfully finds wallets by whitelist flag', async () => { + await Promise.all([ + WalletTable.create(defaultWallet3), + WalletTable.create(defaultWallet2), + ]); + + const wallets: WalletFromDatabase[] = await WalletTable.findAll( + { + isWhitelistAffiliate: true, + }, + [], + { readReplica: true }, + ); + + expect(wallets.length).toEqual(1); + expect(wallets[0]).toEqual(expect.objectContaining(defaultWallet3)); + }); }); diff --git a/indexer/packages/postgres/src/db/migrations/migration_files/20240827162119_add_wallets_total_volume_and_affiliates_whitelist.ts b/indexer/packages/postgres/src/db/migrations/migration_files/20240827162119_add_wallets_total_volume_and_affiliates_whitelist.ts new file mode 100644 index 0000000000..59c026b2a0 --- /dev/null +++ b/indexer/packages/postgres/src/db/migrations/migration_files/20240827162119_add_wallets_total_volume_and_affiliates_whitelist.ts @@ -0,0 +1,19 @@ +import * as Knex from 'knex'; + +export async function up(knex: Knex): Promise { + return knex + .schema + .alterTable('wallets', (table) => { + table.decimal('totalVolume', null).defaultTo(0).notNullable(); + table.boolean('isWhitelistAffiliate').defaultTo(false).notNullable(); + }); +} + +export async function down(knex: Knex): Promise { + return knex + .schema + .alterTable('wallets', (table) => { + table.dropColumn('totalVolume'); + table.dropColumn('isWhitelistAffiliate'); + }); +} diff --git a/indexer/packages/postgres/src/models/wallet-model.ts b/indexer/packages/postgres/src/models/wallet-model.ts index a588a2d275..dfa771ce94 100644 --- a/indexer/packages/postgres/src/models/wallet-model.ts +++ b/indexer/packages/postgres/src/models/wallet-model.ts @@ -40,10 +40,14 @@ export default class WalletModel extends BaseModel { required: [ 'address', 'totalTradingRewards', + 'totalVolume', + 'isWhitelistAffiliate', ], properties: { address: { type: 'string' }, totalTradingRewards: { type: 'string', pattern: NonNegativeNumericPattern }, + totalVolume: { type: 'string', pattern: NonNegativeNumericPattern }, + isWhitelistAffiliate: { type: 'boolean' }, }, }; } @@ -57,6 +61,9 @@ export default class WalletModel extends BaseModel { static get sqlToJsonConversions() { return { address: 'string', + totalTradingRewards: 'string', + totalVolume: 'string', + isWhitelistAffiliate: 'boolean', }; } @@ -65,4 +72,8 @@ export default class WalletModel extends BaseModel { address!: string; totalTradingRewards!: string; + + totalVolume!: string; + + isWhitelistAffiliate!: boolean; } diff --git a/indexer/packages/postgres/src/stores/wallet-table.ts b/indexer/packages/postgres/src/stores/wallet-table.ts index fd04bb560d..8869aa5d36 100644 --- a/indexer/packages/postgres/src/stores/wallet-table.ts +++ b/indexer/packages/postgres/src/stores/wallet-table.ts @@ -19,6 +19,7 @@ import { export async function findAll( { address, + isWhitelistAffiliate, limit, }: WalletQueryConfig, requiredFields: QueryableField[], @@ -27,6 +28,7 @@ export async function findAll( verifyAllRequiredFields( { address, + isWhitelistAffiliate, limit, } as QueryConfig, requiredFields, @@ -41,6 +43,10 @@ export async function findAll( baseQuery = baseQuery.where(WalletColumns.address, address); } + if (isWhitelistAffiliate !== undefined) { + baseQuery = baseQuery.where(WalletColumns.isWhitelistAffiliate, isWhitelistAffiliate); + } + if (options.orderBy !== undefined) { for (const [column, order] of options.orderBy) { baseQuery = baseQuery.orderBy( diff --git a/indexer/packages/postgres/src/types/db-model-types.ts b/indexer/packages/postgres/src/types/db-model-types.ts index 9a3413fd75..ce8d09a115 100644 --- a/indexer/packages/postgres/src/types/db-model-types.ts +++ b/indexer/packages/postgres/src/types/db-model-types.ts @@ -28,6 +28,8 @@ export interface SubaccountFromDatabase extends IdBasedModelFromDatabase { export interface WalletFromDatabase { address: string, totalTradingRewards: string, + totalVolume: string, + isWhitelistAffiliate: boolean, } export interface PerpetualPositionFromDatabase extends IdBasedModelFromDatabase { diff --git a/indexer/packages/postgres/src/types/query-types.ts b/indexer/packages/postgres/src/types/query-types.ts index b70b17100c..5e2d174e14 100644 --- a/indexer/packages/postgres/src/types/query-types.ts +++ b/indexer/packages/postgres/src/types/query-types.ts @@ -88,6 +88,7 @@ export enum QueryableField { USERNAME = 'username', TIMESPAN = 'timeSpan', RANK = 'rank', + IS_WHITELIST_AFFILIATE = 'isWhitelistAffiliate', } export interface QueryConfig { @@ -110,6 +111,7 @@ export interface SubaccountUsernamesQueryConfig extends QueryConfig { export interface WalletQueryConfig extends QueryConfig { [QueryableField.ADDRESS]?: string, + [QueryableField.IS_WHITELIST_AFFILIATE]?: boolean, } export interface PerpetualPositionQueryConfig extends QueryConfig { diff --git a/indexer/packages/postgres/src/types/wallet-types.ts b/indexer/packages/postgres/src/types/wallet-types.ts index 48ad8307af..2466c91d28 100644 --- a/indexer/packages/postgres/src/types/wallet-types.ts +++ b/indexer/packages/postgres/src/types/wallet-types.ts @@ -3,14 +3,20 @@ export interface WalletCreateObject { address: string, totalTradingRewards: string, + totalVolume: string, + isWhitelistAffiliate: boolean, } export interface WalletUpdateObject { address: string, totalTradingRewards: string, + totalVolume: string, + isWhitelistAffiliate: boolean, } export enum WalletColumns { address = 'address', totalTradingRewards = 'totalTradingRewards', + totalVolume = 'totalVolume', + isWhitelistAffiliate = 'isWhitelistAffiliate', } diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts index 1550753c81..89646c3d1e 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts @@ -27,6 +27,12 @@ import { } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants'; import Big from 'big.js'; +const defaultWallet = { + ...testConstants.defaultWallet, + address: defaultWalletAddress, // defaultWalletAddress != testConstants.defaultWallet.address + +}; + describe('transfers-controller#V4', () => { beforeAll(async () => { await dbHelpers.migrate(); @@ -53,10 +59,8 @@ describe('transfers-controller#V4', () => { createdAt: testConstants.createdDateTime.toISO(), createdAtHeight: testConstants.createdHeight, }; - await WalletTable.create({ - address: testConstants.defaultWalletAddress, - totalTradingRewards: '0', - }); + // use wallet2 to not create duplicate + await WalletTable.create(testConstants.defaultWallet2); await Promise.all([ TransferTable.create(testConstants.defaultTransfer), TransferTable.create(transfer2), @@ -177,10 +181,7 @@ describe('transfers-controller#V4', () => { createdAt: testConstants.createdDateTime.toISO(), createdAtHeight: testConstants.createdHeight, }; - await WalletTable.create({ - address: testConstants.defaultWalletAddress, - totalTradingRewards: '0', - }); + await WalletTable.create(defaultWallet); await Promise.all([ TransferTable.create(testConstants.defaultTransfer), TransferTable.create(transfer2), @@ -458,10 +459,7 @@ describe('transfers-controller#V4', () => { createdAt: testConstants.createdDateTime.toISO(), createdAtHeight: testConstants.createdHeight, }; - await WalletTable.create({ - address: testConstants.defaultWalletAddress, - totalTradingRewards: '0', - }); + await WalletTable.create(defaultWallet); await Promise.all([ TransferTable.create(testConstants.defaultTransfer), TransferTable.create(transfer2), @@ -582,10 +580,7 @@ describe('transfers-controller#V4', () => { createdAt: testConstants.createdDateTime.toISO(), createdAtHeight: testConstants.createdHeight, }; - await WalletTable.create({ - address: testConstants.defaultWalletAddress, - totalTradingRewards: '0', - }); + await WalletTable.create(defaultWallet); await Promise.all([ TransferTable.create(testConstants.defaultTransfer), TransferTable.create(transfer2), @@ -735,10 +730,7 @@ describe('transfers-controller#V4', () => { createdAt: testConstants.createdDateTime.toISO(), createdAtHeight: testConstants.createdHeight, }; - await WalletTable.create({ - address: testConstants.defaultWalletAddress, - totalTradingRewards: '0', - }); + await WalletTable.create(defaultWallet); await Promise.all([ TransferTable.create(testConstants.defaultTransfer), TransferTable.create(transfer2), @@ -821,10 +813,7 @@ describe('transfers-controller#V4', () => { createdAt: createdDateTime.toISO(), createdAtHeight: createdHeight, }; - await WalletTable.create({ - address: testConstants.defaultWalletAddress, - totalTradingRewards: '0', - }); + await WalletTable.create(defaultWallet); await Promise.all([ TransferTable.create(transferFromNonParent), TransferTable.create(transferToNonParent), diff --git a/indexer/services/ender/__tests__/handlers/trading-rewards-handler.test.ts b/indexer/services/ender/__tests__/handlers/trading-rewards-handler.test.ts index a71039261e..337f493777 100644 --- a/indexer/services/ender/__tests__/handlers/trading-rewards-handler.test.ts +++ b/indexer/services/ender/__tests__/handlers/trading-rewards-handler.test.ts @@ -171,8 +171,9 @@ describe('tradingRewardHandler', () => { const wallet: WalletFromDatabase | undefined = await WalletTable.findById( testConstants.defaultWallet.address, ); + expect(wallet).toEqual({ - address: testConstants.defaultWallet.address, + ...testConstants.defaultWallet, totalTradingRewards: testConversionHelpers.denomToHumanReadableConversion(1_000_000_000), }); }); @@ -195,7 +196,7 @@ describe('tradingRewardHandler', () => { }); await WalletTable.update({ - address: testConstants.defaultWallet.address, + ...testConstants.defaultWallet, totalTradingRewards: testConversionHelpers.denomToHumanReadableConversion(1_000_000_000), }); @@ -204,7 +205,7 @@ describe('tradingRewardHandler', () => { testConstants.defaultWallet.address, ); expect(wallet).toEqual({ - address: testConstants.defaultWallet.address, + ...testConstants.defaultWallet, totalTradingRewards: testConversionHelpers.denomToHumanReadableConversion(2_000_000_000), }); }); diff --git a/indexer/services/ender/__tests__/handlers/transfer-handler.test.ts b/indexer/services/ender/__tests__/handlers/transfer-handler.test.ts index 898f5d2697..4f7f959070 100644 --- a/indexer/services/ender/__tests__/handlers/transfer-handler.test.ts +++ b/indexer/services/ender/__tests__/handlers/transfer-handler.test.ts @@ -25,7 +25,7 @@ import { assetRefresher, WalletTable, WalletFromDatabase, - testConversionHelpers, + testConstants, } from '@dydxprotocol-indexer/postgres'; import { KafkaMessage } from 'kafkajs'; import { createKafkaMessage, producer } from '@dydxprotocol-indexer/kafka'; @@ -52,6 +52,11 @@ import { import { updateBlockCache } from '../../src/caches/block-cache'; import { createPostgresFunctions } from '../../src/helpers/postgres/postgres-functions'; +const defaultWallet = { + ...testConstants.defaultWallet, + address: defaultWalletAddress, +}; + describe('transferHandler', () => { beforeAll(async () => { await dbHelpers.migrate(); @@ -303,10 +308,7 @@ describe('transferHandler', () => { const wallet: WalletFromDatabase | undefined = await WalletTable.findById( defaultWalletAddress, ); - expect(wallet).toEqual({ - address: defaultWalletAddress, - totalTradingRewards: testConversionHelpers.denomToHumanReadableConversion(0), - }); + expect(wallet).toEqual(defaultWallet); }); it('creates new deposit for previously non-existent subaccount', async () => { @@ -355,10 +357,7 @@ describe('transferHandler', () => { defaultRecipientSubaccountId, ); expect(newRecipientSubaccount).toBeDefined(); - expect(wallet).toEqual({ - address: defaultWalletAddress, - totalTradingRewards: testConversionHelpers.denomToHumanReadableConversion(0), - }); + expect(wallet).toEqual(defaultWallet); }); it('creates new withdrawal for existing subaccount', async () => { @@ -408,10 +407,7 @@ describe('transferHandler', () => { const wallet: WalletFromDatabase | undefined = await WalletTable.findById( defaultWalletAddress, ); - expect(wallet).toEqual({ - address: defaultWalletAddress, - totalTradingRewards: testConversionHelpers.denomToHumanReadableConversion(0), - }); + expect(wallet).toEqual(defaultWallet); }); it('creates new transfer and the recipient subaccount', async () => { diff --git a/indexer/services/ender/src/scripts/handlers/dydx_transfer_handler.sql b/indexer/services/ender/src/scripts/handlers/dydx_transfer_handler.sql index ced6bb5cd3..4685be0de8 100644 --- a/indexer/services/ender/src/scripts/handlers/dydx_transfer_handler.sql +++ b/indexer/services/ender/src/scripts/handlers/dydx_transfer_handler.sql @@ -57,6 +57,8 @@ BEGIN recipient_wallet_record."address" = transfer_record."recipientWalletAddress"; recipient_wallet_record."totalTradingRewards" = '0'; + recipient_wallet_record."totalVolume" = '0'; + recipient_wallet_record."isWhitelistAffiliate" = FALSE; INSERT INTO wallets VALUES (recipient_wallet_record.*) ON CONFLICT DO NOTHING; END IF; @@ -65,6 +67,8 @@ BEGIN sender_wallet_record."address" = transfer_record."senderWalletAddress"; sender_wallet_record."totalTradingRewards" = '0'; + sender_wallet_record."totalVolume" = '0'; + sender_wallet_record."isWhitelistAffiliate" = FALSE; INSERT INTO wallets VALUES (sender_wallet_record.*) ON CONFLICT DO NOTHING; END IF;