Skip to content

Commit

Permalink
feat: get reverse swap lockup transaction endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Feb 6, 2024
1 parent d2e3489 commit c6a1f4c
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 2 deletions.
64 changes: 64 additions & 0 deletions lib/api/v2/routers/SwapRouter.ts
Expand Up @@ -688,6 +688,56 @@ class SwapRouter extends RouterBase {
*/
router.post('/reverse', this.handleError(this.createReverse));

/**
* @openapi
* components:
* schemas:
* ReverseTransaction:
* type: object
* properties:
* id:
* type: string
* description: ID the lockup transaction
* hex:
* type: string
* description: Lockup transaction as raw HEX
* timeoutBlockHeight:
* type: number
* description: Block height at which the time-lock expires
*/

/**
* @openapi
* /swap/reverse/{id}/transaction:
* get:
* tags: [Reverse]
* description: Get the lockup transaction of a Reverse Swap
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: ID of the Reverse Swap
* responses:
* '200':
* description: The lockup transaction of the Reverse Swap and accompanying information
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ReverseTransaction'
* '400':
* description: Error that caused the request to fail
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
*/
router.get(
'/reverse/:id/transaction',
this.handleError(this.getReverseTransaction),
);

/**
* @openapi
* components:
Expand Down Expand Up @@ -1034,6 +1084,20 @@ class SwapRouter extends RouterBase {
createdResponse(res, response);
};

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

const { transactionHex, transactionId, timeoutBlockHeight } =
await this.service.getReverseSwapTransaction(id);
successResponse(res, {
id: transactionId,
hex: transactionHex,
timeoutBlockHeight,
});
};

private claimReverse = async (req: Request, res: Response) => {
const { id, preimage, pubNonce, index, transaction } = validateRequest(
req.body,
Expand Down
39 changes: 38 additions & 1 deletion lib/service/Service.ts
Expand Up @@ -93,10 +93,13 @@ type Contracts = {
rsk?: NetworkContracts;
};

type SwapTransaction = {
type ReverseTransaction = {
transactionId: string;
timeoutBlockHeight: number;
transactionHex?: string;
};

type SwapTransaction = ReverseTransaction & {
timeoutEta?: number;
};

Expand Down Expand Up @@ -541,6 +544,40 @@ class Service {
return response;
};

public getReverseSwapTransaction = async (
id: string,
): Promise<ReverseTransaction> => {
const reverseSwap = await ReverseSwapRepository.getReverseSwap({
id,
});

if (!reverseSwap) {
throw Errors.SWAP_NOT_FOUND(id);
}

if (!reverseSwap.transactionId) {
throw Errors.SWAP_NO_LOCKUP();
}

const { base, quote } = splitPairId(reverseSwap.pair);
const currency = this.getCurrency(
getChainCurrency(base, quote, reverseSwap.orderSide, true),
);

const response: ReverseTransaction = {
transactionId: reverseSwap.transactionId,
timeoutBlockHeight: reverseSwap.timeoutBlockHeight,
};

if (currency.chainClient) {
response.transactionHex = await currency.chainClient.getRawTransaction(
reverseSwap.transactionId,
);
}

return response;
};

public deriveKeys = (symbol: string, index: number): DeriveKeysResponse => {
const wallet = this.walletManager.wallets.get(symbol.toUpperCase());

Expand Down
58 changes: 58 additions & 0 deletions swagger-spec.json
Expand Up @@ -1178,6 +1178,47 @@
}
}
},
"/swap/reverse/{id}/transaction": {
"get": {
"tags": [
"Reverse"
],
"description": "Get the lockup transaction of a Reverse Swap",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of the Reverse Swap"
}
],
"responses": {
"200": {
"description": "The lockup transaction of the Reverse Swap and accompanying information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReverseTransaction"
}
}
}
},
"400": {
"description": "Error that caused the request to fail",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/swap/reverse/claim": {
"post": {
"description": "Requests a partial signature for a cooperative Reverse Swap claim transaction",
Expand Down Expand Up @@ -1687,6 +1728,23 @@
}
}
},
"ReverseTransaction": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "ID the lockup transaction"
},
"hex": {
"type": "string",
"description": "Lockup transaction as raw HEX"
},
"timeoutBlockHeight": {
"type": "number",
"description": "Block height at which the time-lock expires"
}
}
},
"ReverseClaimRequest": {
"type": "object",
"properties": {
Expand Down
31 changes: 30 additions & 1 deletion test/unit/api/v2/routers/SwapRouter.spec.ts
Expand Up @@ -96,6 +96,11 @@ describe('SwapRouter', () => {
timeoutBlockHeight: 21,
timeoutEta: 210987,
}),
getReverseSwapTransaction: jest.fn().mockResolvedValue({
transactionId: 'txIdReverse',
transactionHex: 'txHexReverse',
timeoutBlockHeight: 42,
}),
} as unknown as Service;

const controller = {
Expand Down Expand Up @@ -128,7 +133,7 @@ describe('SwapRouter', () => {

expect(Router).toHaveBeenCalledTimes(1);

expect(mockedRouter.get).toHaveBeenCalledTimes(6);
expect(mockedRouter.get).toHaveBeenCalledTimes(7);
expect(mockedRouter.get).toHaveBeenCalledWith('/:id', expect.anything());
expect(mockedRouter.get).toHaveBeenCalledWith(
'/submarine',
Expand All @@ -150,6 +155,10 @@ describe('SwapRouter', () => {
'/reverse',
expect.anything(),
);
expect(mockedRouter.get).toHaveBeenCalledWith(
'/reverse/:id/transaction',
expect.anything(),
);

expect(mockedRouter.post).toHaveBeenCalledTimes(6);
expect(mockedRouter.post).toHaveBeenCalledWith(
Expand Down Expand Up @@ -819,6 +828,26 @@ describe('SwapRouter', () => {
});
});

test('should get lockup transactions of reverse swaps', async () => {
const id = 'asdf';

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

expect(service.getReverseSwapTransaction).toHaveBeenCalledTimes(1);
expect(service.getReverseSwapTransaction).toHaveBeenCalledWith(id);

expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
id: 'txIdReverse',
hex: 'txHexReverse',
timeoutBlockHeight: 42,
});
});

test.each`
error | body
${'undefined parameter: id'} | ${{}}
Expand Down
28 changes: 28 additions & 0 deletions test/unit/service/Service.spec.ts
Expand Up @@ -1016,6 +1016,34 @@ describe('Service', () => {
);
});

test('should get lockup transactions of reverse swaps', async () => {
const blockDelta = 10;

mockGetReverseSwapResult = {
id: '123asd',
pair: 'LTC/BTC',
orderSide: OrderSide.SELL,
timeoutBlockHeight: blockchainInfo.blocks + blockDelta,
transactionId:
'eb63a8b1511f83c8d649fdaca26c4bc0dee4313689f62fd0f4ff8f71b963900d',
};

await expect(
service.getReverseSwapTransaction(mockGetReverseSwapResult.id),
).resolves.toEqual({
transactionHex: rawTransaction,
transactionId: mockGetReverseSwapResult.transactionId,
timeoutBlockHeight: mockGetReverseSwapResult.timeoutBlockHeight,
});

expect(ReverseSwapRepository.getReverseSwap).toHaveBeenCalledTimes(1);
expect(ReverseSwapRepository.getReverseSwap).toHaveBeenCalledWith({
id: mockGetReverseSwapResult.id,
});

mockGetReverseSwapResult = null;
});

test('should get lockup transactions of Ethereum swaps', async () => {
const blockDelta = 10;

Expand Down

0 comments on commit c6a1f4c

Please sign in to comment.