Skip to content

Commit

Permalink
feat: API v2 swap status
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Jan 22, 2024
1 parent 30ad5f0 commit 85289bd
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 14 deletions.
2 changes: 1 addition & 1 deletion lib/api/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Api {
},
);

new ApiV2(this.logger, service).registerRoutes(this.app);
new ApiV2(this.logger, service, this.controller).registerRoutes(this.app);
this.registerRoutes(this.controller);
}

Expand Down
8 changes: 4 additions & 4 deletions lib/api/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ import {
} from './Utils';

class Controller {
// A map between the ids and HTTP streams of all pending swaps
private pendingSwapStreams = new Map<string, Response>();

// TODO: refactor
// A map between the ids and statuses of the swaps
private pendingSwapInfos = new Map<string, SwapUpdate>();
public pendingSwapInfos = new Map<string, SwapUpdate>();

// A map between the ids and HTTP streams of all pending swaps
private pendingSwapStreams = new Map<string, Response>();

constructor(
private logger: Logger,
Expand Down
4 changes: 3 additions & 1 deletion lib/api/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ export const writeErrorResponse = (
if (!errorsNotToLog.includes(error?.error || error)) {
logger.warn(
`Request ${req.method} ${urlPrefix + req.url} ${
Object.keys(req.body).length > 0 ? `${JSON.stringify(req.body)} ` : ''
req.body && Object.keys(req.body).length > 0
? `${JSON.stringify(req.body)} `
: ''
}failed: ${JSON.stringify(error)}`,
);
}
Expand Down
12 changes: 7 additions & 5 deletions lib/api/v2/ApiV2.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Application } from 'express';
import Logger from '../../Logger';
import Service from '../../service/Service';
import Controller from '../Controller';
import { apiPrefix } from './Consts';
import ChainRouter from './routers/ChainRouter';
import InfoRouter from './routers/InfoRouter';
Expand All @@ -13,13 +14,14 @@ class ApiV2 {

constructor(
private readonly logger: Logger,
private readonly service: Service,
service: Service,
controller: Controller,
) {
this.routers = [
new InfoRouter(this.logger, this.service),
new SwapRouter(this.logger, this.service),
new ChainRouter(this.logger, this.service),
new NodesRouter(this.logger, this.service),
new InfoRouter(this.logger, service),
new SwapRouter(this.logger, service, controller),
new ChainRouter(this.logger, service),
new NodesRouter(this.logger, service),
];
}

Expand Down
84 changes: 84 additions & 0 deletions lib/api/v2/routers/SwapRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { getHexString, stringify } from '../../../Utils';
import { SwapVersion } from '../../../consts/Enums';
import RateProviderTaproot from '../../../rates/providers/RateProviderTaproot';
import Service from '../../../service/Service';
import Controller from '../../Controller';
import {
checkPreimageHashLength,
createdResponse,
errorResponse,
successResponse,
validateRequest,
} from '../../Utils';
Expand All @@ -16,6 +18,7 @@ class SwapRouter extends RouterBase {
constructor(
logger: Logger,
private readonly service: Service,
private readonly controller: Controller,
) {
super(logger, 'swap');
}
Expand Down Expand Up @@ -51,6 +54,67 @@ class SwapRouter extends RouterBase {

const router = Router();

/**
* @openapi
* tags:
* name: Swap
* description: Generic Swap related endpoints
*/

/**
* @openapi
* components:
* schemas:
* SwapStatus:
* type: object
* properties:
* status:
* type: string
* description: Status of the Swap
* zeroConfRejected:
* type: boolean
* description: Whether 0-conf was accepted for the lockup transaction of the Submarine Swap
* transaction:
* type: object
* description: Details of the lockup transaction of a Reverse Swap
* properties:
* id:
* type: string
* description: ID of the transaction
* hex:
* type: string
* description: Raw hex of the transaction
*/

/**
* @openapi
* /swap/{id}:
* get:
* tags: [Swap]
* description: Get the status of a Swap
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: ID of the Swap
* responses:
* '200':
* description: The latest status of the Swap
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/SwapStatus'
* '404':
* description: When no Swap with the ID could be found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
*/
router.get('/:id', this.handleError(this.getSwapStatus));

/**
* @openapi
* tags:
Expand Down Expand Up @@ -548,6 +612,26 @@ class SwapRouter extends RouterBase {
return router;
};

private getSwapStatus = (req: Request, res: Response) => {
const { id } = validateRequest(req.params, [
{ name: 'id', type: 'string' },
]);

const response = this.controller.pendingSwapInfos.get(id);

if (response) {
successResponse(res, response);
} else {
errorResponse(
this.logger,
req,
res,
`could not find swap with id: ${id}`,
404,
);
}
};

private getSubmarine = (_req: Request, res: Response) =>
successResponse(
res,
Expand Down
72 changes: 72 additions & 0 deletions swagger-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,47 @@
}
}
},
"/swap/{id}": {
"get": {
"tags": [
"Swap"
],
"description": "Get the status of a Swap",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of the Swap"
}
],
"responses": {
"200": {
"description": "The latest status of the Swap",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SwapStatus"
}
}
}
},
"404": {
"description": "When no Swap with the ID could be found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/swap/submarine": {
"get": {
"description": "Possible pairs for Submarine Swaps",
Expand Down Expand Up @@ -626,6 +667,33 @@
}
}
},
"SwapStatus": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Status of the Swap"
},
"zeroConfRejected": {
"type": "boolean",
"description": "Whether 0-conf was accepted for the lockup transaction of the Submarine Swap"
},
"transaction": {
"type": "object",
"description": "Details of the lockup transaction of a Reverse Swap",
"properties": {
"id": {
"type": "string",
"description": "ID of the transaction"
},
"hex": {
"type": "string",
"description": "Raw hex of the transaction"
}
}
}
}
},
"SubmarinePair": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -967,6 +1035,10 @@
"name": "Nodes",
"description": "Lightning nodes"
},
{
"name": "Swap",
"description": "Generic Swap related endpoints"
},
{
"name": "Submarine",
"description": "Submarine Swap related endpoints"
Expand Down
2 changes: 1 addition & 1 deletion test/unit/api/v2/ApiV2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('ApiV2', () => {
use: jest.fn(),
} as any;

new ApiV2(Logger.disabledLogger, {} as any).registerRoutes(app);
new ApiV2(Logger.disabledLogger, {} as any, {} as any).registerRoutes(app);

expect(mockGetInfoRouter).toHaveBeenCalledTimes(1);
expect(mockSwapGetRouter).toHaveBeenCalledTimes(1);
Expand Down
48 changes: 46 additions & 2 deletions test/unit/api/v2/routers/SwapRouter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { randomBytes } from 'crypto';
import { Router } from 'express';
import Logger from '../../../../../lib/Logger';
import { getHexBuffer, getHexString } from '../../../../../lib/Utils';
import Controller from '../../../../../lib/api/Controller';
import SwapRouter from '../../../../../lib/api/v2/routers/SwapRouter';
import { OrderSide, SwapVersion } from '../../../../../lib/consts/Enums';
import RateProviderTaproot from '../../../../../lib/rates/providers/RateProviderTaproot';
Expand Down Expand Up @@ -64,7 +65,11 @@ describe('SwapRouter', () => {
}),
} as unknown as Service;

const swapRouter = new SwapRouter(Logger.disabledLogger, service);
const controller = {
pendingSwapInfos: new Map([['swapId', { some: 'statusData' }]]),
} as unknown as Controller;

const swapRouter = new SwapRouter(Logger.disabledLogger, service, controller);

beforeEach(() => {
jest.clearAllMocks();
Expand All @@ -80,7 +85,8 @@ describe('SwapRouter', () => {

expect(Router).toHaveBeenCalledTimes(1);

expect(mockedRouter.get).toHaveBeenCalledTimes(3);
expect(mockedRouter.get).toHaveBeenCalledTimes(4);
expect(mockedRouter.get).toHaveBeenCalledWith('/:id', expect.anything());
expect(mockedRouter.get).toHaveBeenCalledWith(
'/submarine',
expect.anything(),
Expand Down Expand Up @@ -113,6 +119,44 @@ describe('SwapRouter', () => {
);
});

test.each`
error | params
${'undefined parameter: id'} | ${{}}
${'invalid parameter: id'} | ${{ id: 1 }}
`(
'should not get status of swaps with invalid parameters ($error)',
({ params, error }) => {
expect(() =>
swapRouter['getSwapStatus'](
mockRequest(undefined, undefined, params),
mockResponse(),
),
).toThrow(error);
},
);

test('should get status of swaps', () => {
const id = 'swapId';

const res = mockResponse();
swapRouter['getSwapStatus'](mockRequest(undefined, undefined, { id }), res);

expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith(controller.pendingSwapInfos.get(id));
});

test('should return 404 as status when swap id cannot be found', () => {
const id = 'notFound';

const res = mockResponse();
swapRouter['getSwapStatus'](mockRequest(undefined, undefined, { id }), res);

expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
error: `could not find swap with id: ${id}`,
});
});

test('should get submarine pairs', () => {
const res = mockResponse();
swapRouter['getSubmarine'](mockRequest(), res);
Expand Down

0 comments on commit 85289bd

Please sign in to comment.