Skip to content

Commit

Permalink
feat: referral API v2
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Jan 27, 2024
1 parent 71d49b6 commit 5191b6d
Show file tree
Hide file tree
Showing 18 changed files with 795 additions and 96 deletions.
2 changes: 1 addition & 1 deletion lib/api/Bouncer.ts
Expand Up @@ -22,7 +22,7 @@ class Bouncer {
referral,
ts,
req.method,
req.path,
req.originalUrl,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
req.rawBody,
Expand Down
2 changes: 1 addition & 1 deletion lib/api/Controller.ts
Expand Up @@ -534,7 +534,7 @@ class Controller {
): Promise<void> => {
try {
const referral = await Bouncer.validateRequestAuthentication(req);
const stats = await ReferralStats.generate(referral.id);
const stats = await ReferralStats.getReferralFees(referral.id);

successResponse(res, stats);
} catch (error) {
Expand Down
34 changes: 9 additions & 25 deletions lib/api/Utils.ts
Expand Up @@ -58,41 +58,26 @@ export const errorResponse = (
res: Response,
error: unknown,
statusCode = 400,
urlPrefix: string = '',
): void => {
if (typeof error === 'string') {
writeErrorResponse(logger, req, res, statusCode, { error }, urlPrefix);
writeErrorResponse(logger, req, res, statusCode, { error });
} else {
const errorObject = error as any;

// Bitcoin Core related errors
if (errorObject.details) {
writeErrorResponse(
logger,
req,
res,
statusCode,
{
error: errorObject.details,
},
urlPrefix,
);
writeErrorResponse(logger, req, res, statusCode, {
error: errorObject.details,
});
// Custom error when broadcasting a refund transaction fails because
// the locktime requirement has not been met yet
} else if (errorObject.timeoutBlockHeight) {
writeErrorResponse(logger, req, res, statusCode, error, urlPrefix);
writeErrorResponse(logger, req, res, statusCode, error);
// Everything else
} else {
writeErrorResponse(
logger,
req,
res,
statusCode,
{
error: errorObject.message,
},
urlPrefix,
);
writeErrorResponse(logger, req, res, statusCode, {
error: errorObject.message,
});
}
}
};
Expand Down Expand Up @@ -120,11 +105,10 @@ export const writeErrorResponse = (
res: Response,
statusCode: number,
error: any,
urlPrefix: string = '',
) => {
if (!errorsNotToLog.includes(error?.error || error)) {
logger.warn(
`Request ${req.method} ${urlPrefix + req.url} ${
`Request ${req.method} ${req.originalUrl} ${
req.body && Object.keys(req.body).length > 0
? `${JSON.stringify(req.body)} `
: ''
Expand Down
2 changes: 2 additions & 0 deletions lib/api/v2/ApiV2.ts
Expand Up @@ -6,6 +6,7 @@ import { apiPrefix } from './Consts';
import ChainRouter from './routers/ChainRouter';
import InfoRouter from './routers/InfoRouter';
import NodesRouter from './routers/NodesRouter';
import ReferralRouter from './routers/ReferralRouter';
import RouterBase from './routers/RouterBase';
import SwapRouter from './routers/SwapRouter';

Expand All @@ -22,6 +23,7 @@ class ApiV2 {
new SwapRouter(this.logger, service, controller),
new ChainRouter(this.logger, service),
new NodesRouter(this.logger, service),
new ReferralRouter(this.logger),
];
}

Expand Down
249 changes: 249 additions & 0 deletions lib/api/v2/routers/ReferralRouter.ts
@@ -0,0 +1,249 @@
import { Request, Response, Router } from 'express';
import Logger from '../../../Logger';
import ReferralStats from '../../../data/ReferralStats';
import Stats from '../../../data/Stats';
import Referral from '../../../db/models/Referral';
import Bouncer from '../../Bouncer';
import { errorResponse, successResponse } from '../../Utils';
import RouterBase from './RouterBase';

class ReferralRouter extends RouterBase {
constructor(logger: Logger) {
super(logger, 'referral');
}

public getRouter = () => {
const router = Router();

/**
* @openapi
* tags:
* name: Referral
* description: Referral related endpoints
*/

/**
* @openapi
* /referral:
* get:
* description: Referral ID for the used API keys
* tags: [Referral]
* parameters:
* - in: header
* name: TS
* required: true
* schema:
* type: string
* description: Current UNIX timestamp when the request is sent
* - in: header
* name: API-KEY
* required: true
* schema:
* type: string
* description: Your API key
* - in: header
* name: API-HMAC
* required: true
* schema:
* type: string
* description: HMAC-SHA256 with your API-Secret as key of the TS + HTTP method (all uppercase) + the HTTP path
* responses:
* '200':
* description: The referral ID for your API-KEY to be used when creating Swaps
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* description: The referral ID for your API-KEY
* '401':
* description: Unauthorized in case of an unknown API-KEY or bad HMAC
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
*/
router.get('/', this.handleError(this.getName));

/**
* @openapi
* /referral/fees:
* get:
* description: Referral fees collected for an ID
* tags: [Referral]
* parameters:
* - in: header
* name: TS
* required: true
* schema:
* type: string
* description: Current UNIX timestamp when the request is sent
* - in: header
* name: API-KEY
* required: true
* schema:
* type: string
* description: Your API key
* - in: header
* name: API-HMAC
* required: true
* schema:
* type: string
* description: HMAC-SHA256 with your API-Secret as key of the TS + HTTP method (all uppercase) + the HTTP path
* responses:
* '200':
* description: The referral ID for your API-KEY to be used when creating Swaps
* content:
* application/json:
* schema:
* type: object
* description: Year
* additionalProperties:
* type: object
* description: Month
* additionalProperties:
* type: object
* description: Fees collected in that month
* additionalProperties:
* type: string
* description: Fees collected in that currency in satoshis
* examples:
* json:
* value: '{"2024":{"1":{"BTC":307}}}'
* '401':
* description: Unauthorized in case of an unknown API-KEY or bad HMAC
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
*/
router.get('/fees', this.handleError(this.getFees));

/**
* @openapi
* /referral/stats:
* get:
* description: Statistics for Swaps created with an referral ID
* tags: [Referral]
* parameters:
* - in: header
* name: TS
* required: true
* schema:
* type: string
* description: Current UNIX timestamp when the request is sent
* - in: header
* name: API-KEY
* required: true
* schema:
* type: string
* description: Your API key
* - in: header
* name: API-HMAC
* required: true
* schema:
* type: string
* description: HMAC-SHA256 with your API-Secret as key of the TS + HTTP method (all uppercase) + the HTTP path
* responses:
* '200':
* description: Swap statistics
* content:
* application/json:
* schema:
* type: object
* description: Year
* additionalProperties:
* type: object
* description: Month
* additionalProperties:
* type: object
* description: Swap statistics for that month
* properties:
* volume:
* description: Swap volume
* properties:
* total:
* type: string
* description: Volume across all pairs in BTC
* additionalProperties:
* type: string
* description: Volume in that pair in BTC
* trades:
* type: object
* description: Swap counts
* properties:
* total:
* type: integer
* description: Swap count across all pairs
* additionalProperties:
* type: integer
* description: Swap count for that pair
* failureRates:
* type: object
* description: Swap failure rates for each type
* properties:
* swaps:
* type: number
* description: Submarine Swap failure rate
* reverseSwaps:
* type: number
* description: Reverse Swap failure rate
* examples:
* json:
* value: '{"2024":{"1":{"volume":{"total":"0.00321844","L-BTC/BTC":"0.00321844"},"trades":{"total":3,"L-BTC/BTC":3},"failureRates":{"swaps": 0.12, "reverseSwaps":0}}}}'
* '401':
* description: Unauthorized in case of an unknown API-KEY or bad HMAC
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
*/
router.get('/stats', this.handleError(this.getStats));

return router;
};

private getName = async (req: Request, res: Response) => {
const referral = await this.checkAuthentication(req, res);
if (referral === undefined) {
return;
}

successResponse(res, { id: referral.id });
};

private getFees = async (req: Request, res: Response) => {
const referral = await this.checkAuthentication(req, res);
if (referral === undefined) {
return;
}

successResponse(res, await ReferralStats.getReferralFees(referral.id));
};

private getStats = async (req: Request, res: Response) => {
const referral = await this.checkAuthentication(req, res);
if (referral === undefined) {
return;
}

successResponse(res, await Stats.generate(0, 0, referral.id));
};

private checkAuthentication = async (
req: Request,
res: Response,
): Promise<Referral | undefined> => {
try {
return await Bouncer.validateRequestAuthentication(req);
} catch (e) {
errorResponse(this.logger, req, res, e, 401);
}

return;
};
}

export default ReferralRouter;
10 changes: 1 addition & 9 deletions lib/api/v2/routers/RouterBase.ts
@@ -1,7 +1,6 @@
import { Request, Response, Router } from 'express';
import Logger from '../../../Logger';
import { errorResponse } from '../../Utils';
import { apiPrefix } from '../Consts';

abstract class RouterBase {
public readonly path: string;
Expand Down Expand Up @@ -32,14 +31,7 @@ abstract class RouterBase {
try {
await handler(req, res);
} catch (e) {
errorResponse(
this.logger,
req,
res,
e,
400,
`${apiPrefix}/${this.path}`,
);
errorResponse(this.logger, req, res, e, 400);
}
};
};
Expand Down

0 comments on commit 5191b6d

Please sign in to comment.