diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/affiliates-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/affiliates-controller.test.ts new file mode 100644 index 00000000000..eedcca2ab09 --- /dev/null +++ b/indexer/services/comlink/__tests__/controllers/api/v4/affiliates-controller.test.ts @@ -0,0 +1,23 @@ +import { RequestMethod } from '../../../../src/types'; +import request from 'supertest'; +import { sendRequest } from '../../../helpers/helpers'; + +describe('test-controller#V4', () => { + + describe('GET /referral_code', () => { + + it('should return referral code for a valid address string', async () => { + const address = 'some_address'; + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/affiliates/referral_code?address=${address}`, + }); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + referralCode: `${address}-referral_code`, + }); + + }); + }); +}); diff --git a/indexer/services/comlink/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index b0152c11295..5cb87ac3926 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -431,6 +431,82 @@ fetch(`${baseURL}/addresses/{address}/parentSubaccountNumber/{parentSubaccountNu This operation does not require authentication +## GetReferralCode + + + +> Code samples + +```python +import requests +headers = { + 'Accept': 'application/json' +} + +# For the deployment by DYDX token holders, use +# baseURL = 'https://indexer.dydx.trade/v4' +baseURL = 'https://dydx-testnet.imperator.co/v4' + +r = requests.get(f'{baseURL}/affiliate/referral_code', params={ + 'address': 'string' +}, headers = headers) + +print(r.json()) + +``` + +```javascript + +const headers = { + 'Accept':'application/json' +}; + +// For the deployment by DYDX token holders, use +// const baseURL = 'https://indexer.dydx.trade/v4'; +const baseURL = 'https://dydx-testnet.imperator.co/v4'; + +fetch(`${baseURL}/affiliate/referral_code?address=string`, +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +`GET /affiliate/referral_code` + +### Parameters + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|address|query|string|true|none| + +> Example responses + +> 200 Response + +```json +{ + "referralCode": "string" +} +``` + +### Responses + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[AffiliateReferralCodeResponse](#schemaaffiliatereferralcoderesponse)| + + + ## GetAssetPositions @@ -3742,6 +3818,26 @@ This operation does not require authentication |freeCollateral|string|true|none|none| |childSubaccounts|[[SubaccountResponseObject](#schemasubaccountresponseobject)]|true|none|none| +## AffiliateReferralCodeResponse + + + + + + +```json +{ + "referralCode": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|referralCode|string¦null|true|none|none| + ## AssetPositionResponse diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index 089550239a9..baf60cfb99d 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -240,6 +240,19 @@ "type": "object", "additionalProperties": false }, + "AffiliateReferralCodeResponse": { + "properties": { + "referralCode": { + "type": "string", + "nullable": true + } + }, + "required": [ + "referralCode" + ], + "type": "object", + "additionalProperties": false + }, "AssetPositionResponse": { "properties": { "positions": { @@ -1520,6 +1533,34 @@ ] } }, + "/affiliate/referral_code": { + "get": { + "operationId": "GetReferralCode", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AffiliateReferralCodeResponse" + } + } + } + } + }, + "security": [], + "parameters": [ + { + "in": "query", + "name": "address", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, "/assetPositions": { "get": { "operationId": "GetAssetPositions", diff --git a/indexer/services/comlink/src/controllers/api/index-v4.ts b/indexer/services/comlink/src/controllers/api/index-v4.ts index d3e9f4f4120..af31fc6311d 100644 --- a/indexer/services/comlink/src/controllers/api/index-v4.ts +++ b/indexer/services/comlink/src/controllers/api/index-v4.ts @@ -1,6 +1,7 @@ import express from 'express'; import AddressesController from './v4/addresses-controller'; +import AffiliatesController from './v4/affiliates-controller'; import AssetPositionsController from './v4/asset-positions-controller'; import CandlesController from './v4/candles-controller'; import ComplianceController from './v4/compliance-controller'; @@ -26,6 +27,7 @@ import VaultController from './v4/vault-controller'; const router: express.Router = express.Router(); router.use('/addresses', AddressesController); +router.use('/affiliates', AffiliatesController); router.use('/assetPositions', AssetPositionsController); router.use('/candles', CandlesController); router.use('/fills', FillsController); diff --git a/indexer/services/comlink/src/controllers/api/v4/affiliates-controller.ts b/indexer/services/comlink/src/controllers/api/v4/affiliates-controller.ts new file mode 100644 index 00000000000..e02aa0f3d51 --- /dev/null +++ b/indexer/services/comlink/src/controllers/api/v4/affiliates-controller.ts @@ -0,0 +1,72 @@ +import { stats } from '@dydxprotocol-indexer/base'; +import express from 'express'; +import { checkSchema, matchedData } from 'express-validator'; +import { + Controller, Get, Query, Route, +} from 'tsoa'; + +import { getReqRateLimiter } from '../../../caches/rate-limiters'; +import config from '../../../config'; +import { handleControllerError } from '../../../lib/helpers'; +import { rateLimiterMiddleware } from '../../../lib/rate-limit'; +import ExportResponseCodeStats from '../../../request-helpers/export-response-code-stats'; +import { AffiliateReferralCodeRequest, AffiliateReferralCodeResponse } from '../../../types'; + +const router: express.Router = express.Router(); +const controllerName: string = 'affiliates-controller'; + +@Route('affiliates') +class AffiliatesController extends Controller { + @Get('/referral_code') + async getReferralCode( + @Query() address: string, + ): Promise { + + // simulate a delay + await new Promise((resolve) => setTimeout(resolve, 100)); + + return { + referralCode: `${address}-referral_code`, + }; + } +} + +router.get( + '/referral_code', + rateLimiterMiddleware(getReqRateLimiter), + ...checkSchema({ + address: { + in: ['query'], + isString: true, + errorMessage: 'address must be a valid string', + }, + }), + ExportResponseCodeStats({ controllerName }), + async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); + const { + address, + }: AffiliateReferralCodeRequest = matchedData(req) as AffiliateReferralCodeRequest; + + try { + const controller: AffiliatesController = new AffiliatesController(); + const response: AffiliateReferralCodeResponse = await controller.getReferralCode(address); + return res.send(response); + } catch (error) { + return handleControllerError( + 'AffiliatesController GET /referral_code', + 'Affiliates refferal code error', + error, + req, + res, + ); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.get_referral_code.timing`, + Date.now() - start, + ); + } + }, +); + +export default router; diff --git a/indexer/services/comlink/src/types.ts b/indexer/services/comlink/src/types.ts index bf9db5c2f63..1715cddf8c6 100644 --- a/indexer/services/comlink/src/types.ts +++ b/indexer/services/comlink/src/types.ts @@ -666,3 +666,32 @@ export interface VaultPosition { export interface MegavaultPositionResponse { positions: VaultPosition[]; } + +/* ------- Affiliates Types ------- */ +export interface AffiliateReferralCodeRequest{ + address: string +} + +export interface AffiliateReferralCodeResponse { + referralCode: string | null, +} + +export interface AffiliateAddressResponse { + address: string | null, +} + +export interface AffiliateSnapshotResponse { + affiliateList: AffiliateSnapshotResponseObject[], + total: number, + currentOffset: number +} + +export interface AffiliateSnapshotResponseObject { + affiliateAddress: string, + affiliateEarnings: number, + affiliateReferralCode: string, + affiliateReferredTrades: number, + affiliateTotalReferredFees: number, + affiliateReferredUsers: number, + affiliateReferredNetProtocolEarnings: number +}