Skip to content

Commit

Permalink
[OTE-760] implement comlink affiliate metadata endpoint (#2243)
Browse files Browse the repository at this point in the history
  • Loading branch information
jerryfan01234 authored Sep 13, 2024
1 parent 759498f commit 9c3d96d
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,140 @@
import {
dbHelpers,
testConstants,
testMocks,
SubaccountUsernamesTable,
WalletTable,
AffiliateReferredUsersTable,
} from '@dydxprotocol-indexer/postgres';
import { AffiliateSnapshotRequest, RequestMethod } from '../../../../src/types';
import request from 'supertest';
import { sendRequest } from '../../../helpers/helpers';
import { defaultWallet, defaultWallet2 } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants';

describe('affiliates-controller#V4', () => {
beforeAll(async () => {
await dbHelpers.migrate();
});

afterAll(async () => {
await dbHelpers.teardown();
});

describe('GET /metadata', () => {
it('should return referral code for a valid address string', async () => {
const address = 'some_address';
beforeEach(async () => {
await testMocks.seedData();
await SubaccountUsernamesTable.create(testConstants.defaultSubaccountUsername);
});

afterEach(async () => {
await dbHelpers.clearData();
});

it('should return referral code for address with username', async () => {
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/affiliates/metadata?address=${address}`,
path: `/v4/affiliates/metadata?address=${testConstants.defaultWallet.address}`,
expectedStatus: 200, // helper performs expect on status
});

expect(response.status).toBe(200);
expect(response.body).toEqual({
referralCode: 'TempCode123',
// username is the referral code
referralCode: testConstants.defaultSubaccountUsername.username,
isVolumeEligible: false,
isAffiliate: false,
});
});

it('should fail if address does not exist', async () => {
const nonExistentAddress = 'adgsakhasgt';
await sendRequest({
type: RequestMethod.GET,
path: `/v4/affiliates/metadata?address=${nonExistentAddress}`,
expectedStatus: 404, // helper performs expect on status
});
});

it('should classify not volume eligible', async () => {
await WalletTable.update(
{
address: testConstants.defaultWallet.address,
totalVolume: '0',
totalTradingRewards: '0',
},
);
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/affiliates/metadata?address=${testConstants.defaultWallet.address}`,
expectedStatus: 200, // helper performs expect on status
});
expect(response.body).toEqual({
referralCode: testConstants.defaultSubaccountUsername.username,
isVolumeEligible: false,
isAffiliate: false,
});
});

it('should classify volume eligible', async () => {
await WalletTable.update(
{
address: testConstants.defaultWallet.address,
totalVolume: '100000',
totalTradingRewards: '0',
},
);
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/affiliates/metadata?address=${testConstants.defaultWallet.address}`,
expectedStatus: 200, // helper performs expect on status
});
expect(response.body).toEqual({
referralCode: testConstants.defaultSubaccountUsername.username,
isVolumeEligible: true,
isAffiliate: false,
});
});

it('should classify is not affiliate', async () => {
// AffiliateReferredUsersTable is empty
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/affiliates/metadata?address=${testConstants.defaultWallet.address}`,
expectedStatus: 200, // helper performs expect on status
});
expect(response.body).toEqual({
referralCode: testConstants.defaultSubaccountUsername.username,
isVolumeEligible: false,
isAffiliate: false,
});
});

it('should classify is affiliate', async () => {
await AffiliateReferredUsersTable.create({
affiliateAddress: defaultWallet.address,
refereeAddress: defaultWallet2.address,
referredAtBlock: '1',
});
const response: request.Response = await sendRequest({
type: RequestMethod.GET,
path: `/v4/affiliates/metadata?address=${testConstants.defaultWallet.address}`,
expectedStatus: 200, // helper performs expect on status
});
expect(response.body).toEqual({
referralCode: testConstants.defaultSubaccountUsername.username,
isVolumeEligible: false,
isAffiliate: true,
});
});

it('should fail if subaccount username not found', async () => {
// create defaultWallet2 without subaccount username
await WalletTable.create(testConstants.defaultWallet2);
await sendRequest({
type: RequestMethod.GET,
path: `/v4/affiliates/metadata?address=${testConstants.defaultWallet2.address}`,
expectedStatus: 500, // helper performs expect on status
});
});
});

describe('GET /address', () => {
Expand Down
3 changes: 3 additions & 0 deletions indexer/services/comlink/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export const configSchema = {
// vaults table is added.
EXPERIMENT_VAULTS: parseString({ default: '' }),
EXPERIMENT_VAULT_MARKETS: parseString({ default: '' }),

// Affiliates config
VOLUME_ELIGIBILITY_THRESHOLD: parseInteger({ default: 10_000 }),
};

////////////////////////////////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { stats } from '@dydxprotocol-indexer/base';
import {
WalletTable,
AffiliateReferredUsersTable,
SubaccountTable,
SubaccountUsernamesTable,
} from '@dydxprotocol-indexer/postgres';
import express from 'express';
import { checkSchema, matchedData } from 'express-validator';
import {
Expand All @@ -7,6 +13,7 @@ import {

import { getReqRateLimiter } from '../../../caches/rate-limiters';
import config from '../../../config';
import { NotFoundError, UnexpectedServerError } from '../../../lib/errors';
import { handleControllerError } from '../../../lib/helpers';
import { rateLimiterMiddleware } from '../../../lib/rate-limit';
import { handleValidationErrors } from '../../../request-helpers/error-handler';
Expand All @@ -31,14 +38,56 @@ const controllerName: string = 'affiliates-controller';
class AffiliatesController extends Controller {
@Get('/metadata')
async getMetadata(
@Query() address: string, // eslint-disable-line @typescript-eslint/no-unused-vars
@Query() address: string,
): Promise<AffiliateMetadataResponse> {
// simulate a delay
await new Promise((resolve) => setTimeout(resolve, 100));
const [walletRow, referredUserRows, subaccountRows] = await Promise.all([
WalletTable.findById(address),
AffiliateReferredUsersTable.findByAffiliateAddress(address),
SubaccountTable.findAll(
{
address,
subaccountNumber: 0,
},
[],
),
]);

// Check that the address exists
if (!walletRow) {
throw new NotFoundError(`Wallet with address ${address} not found`);
}

// Check if the address is an affiliate (has referred users)
const isVolumeEligible = Number(walletRow.totalVolume) >= config.VOLUME_ELIGIBILITY_THRESHOLD;
const isAffiliate = referredUserRows !== undefined ? referredUserRows.length > 0 : false;

// No need to check subaccountRows.length > 1 as subaccountNumber is unique for an address
if (subaccountRows.length === 0) {
// error logging will be performed by handleInternalServerError
throw new UnexpectedServerError(`Subaccount 0 not found for address ${address}`);
}
const subaccountId = subaccountRows[0].id;

// Get subaccount0 username, which is the referral code
const usernameRows = await SubaccountUsernamesTable.findAll(
{
subaccountId: [subaccountId],
},
[],
);
// No need to check usernameRows.length > 1 as subAccountId is unique (foreign key constraint)
// This error can happen if a user calls this endpoint before subaccount-username-generator
// has generated the username
if (usernameRows.length === 0) {
stats.increment(`${config.SERVICE_NAME}.${controllerName}.get_metadata.subaccount_username_not_found`);
throw new UnexpectedServerError(`Username not found for subaccount ${subaccountId}`);
}
const referralCode = usernameRows[0].username;

return {
referralCode: 'TempCode123',
isVolumeEligible: true,
isAffiliate: false,
referralCode,
isVolumeEligible,
isAffiliate,
};
}

Expand Down

0 comments on commit 9c3d96d

Please sign in to comment.