Skip to content

Commit

Permalink
feat: API v2 get submarine swap transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Jan 22, 2024
1 parent bacf555 commit 30ad5f0
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 15 deletions.
7 changes: 4 additions & 3 deletions lib/api/v2/routers/ChainRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class ChainRouter extends RouterBase {
* parameters:
* - in: path
* name: currency
* required: true
* schema:
* type: string
* description: Currency of the chain to broadcast on
Expand All @@ -134,16 +135,16 @@ class ChainRouter extends RouterBase {
* type: string
* description: The transaction to broadcast as raw HEX
* responses:
* '200':
* description: Id of the broadcast transaction
* '201':
* description: ID of the broadcast transaction
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* description: Id of the broadcast transaction
* description: ID of the broadcast transaction
* '400':
* description: Error that caused the broadcast of the transaction to fail
* content:
Expand Down
68 changes: 68 additions & 0 deletions lib/api/v2/routers/SwapRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,59 @@ class SwapRouter extends RouterBase {
*/
router.post('/submarine', this.handleError(this.createSubmarine));

/**
* @openapi
* components:
* schemas:
* SubmarineTransaction:
* 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
* timeoutEta:
* type: number
* description: UNIX timestamp at which the time-lock expires; set if it has not expired already
*/

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

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

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

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

private refundSubmarine = async (req: Request, res: Response) => {
const { id, pubNonce, index, transaction } = validateRequest(req.body, [
{ name: 'id', type: 'string' },
Expand Down
16 changes: 9 additions & 7 deletions lib/service/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ type Contracts = {
rsk?: NetworkContracts;
};

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

class Service {
public allowReverseSwaps = true;

Expand Down Expand Up @@ -487,12 +494,7 @@ class Service {
* Gets the hex encoded lockup transaction of a Submarine Swap, the block height
* at which it will time out and the expected ETA for that block
*/
public getSwapTransaction = async (
id: string,
): Promise<{
transactionHex: string;
timeoutBlockHeight: number;
}> => {
public getSwapTransaction = async (id: string): Promise<SwapTransaction> => {
const swap = await SwapRepository.getSwap({
id,
});
Expand All @@ -510,7 +512,7 @@ class Service {

const currency = this.getCurrency(chainCurrency);

const response: any = {
const response: SwapTransaction = {
transactionId: swap.lockupTransactionId,
timeoutBlockHeight: swap.timeoutBlockHeight,
};
Expand Down
69 changes: 66 additions & 3 deletions swagger-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
{
"in": "path",
"name": "currency",
"required": true,
"schema": {
"type": "string"
},
Expand All @@ -155,16 +156,16 @@
}
},
"responses": {
"200": {
"description": "Id of the broadcast transaction",
"201": {
"description": "ID of the broadcast transaction",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Id of the broadcast transaction"
"description": "ID of the broadcast transaction"
}
}
}
Expand Down Expand Up @@ -373,6 +374,47 @@
}
}
},
"/swap/submarine/{id}/transaction": {
"get": {
"tags": [
"Submarine"
],
"description": "Get the lockup transaction of a Submarine Swap",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of the Submarine Swap"
}
],
"responses": {
"200": {
"description": "The lockup transaction of the Submarine Swap and accompanying information",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubmarineTransaction"
}
}
}
},
"400": {
"description": "Error that caused the request to fail",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/swap/submarine/refund": {
"post": {
"description": "Requests a partial signature for a cooperative Submarine Swap refund transaction",
Expand Down Expand Up @@ -698,6 +740,27 @@
}
}
},
"SubmarineTransaction": {
"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"
},
"timeoutEta": {
"type": "number",
"description": "UNIX timestamp at which the time-lock expires; set if it has not expired already"
}
}
},
"SubmarineRefundRequest": {
"type": "object",
"properties": {
Expand Down
51 changes: 49 additions & 2 deletions test/unit/api/v2/routers/SwapRouter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ describe('SwapRouter', () => {
.mockReturnValue({ pairId: 'L-BTC/BTC', orderSide: OrderSide.BUY }),
createSwapWithInvoice: jest.fn().mockResolvedValue({ id: 'randomId' }),
createReverseSwap: jest.fn().mockResolvedValue({ id: 'reverseId' }),
getSwapTransaction: jest.fn().mockResolvedValue({
transactionId: 'txId',
transactionHex: 'txHex',
timeoutBlockHeight: 21,
timeoutEta: 210987,
}),
} as unknown as Service;

const swapRouter = new SwapRouter(Logger.disabledLogger, service);
Expand All @@ -74,11 +80,15 @@ describe('SwapRouter', () => {

expect(Router).toHaveBeenCalledTimes(1);

expect(mockedRouter.get).toHaveBeenCalledTimes(2);
expect(mockedRouter.get).toHaveBeenCalledTimes(3);
expect(mockedRouter.get).toHaveBeenCalledWith(
'/submarine',
expect.anything(),
);
expect(mockedRouter.get).toHaveBeenCalledWith(
'/submarine/:id/transaction',
expect.anything(),
);
expect(mockedRouter.get).toHaveBeenCalledWith(
'/reverse',
expect.anything(),
Expand Down Expand Up @@ -197,8 +207,8 @@ describe('SwapRouter', () => {
referralId: 'partner',
refundPublicKey: '0021',
};
const res = mockResponse();

const res = mockResponse();
await swapRouter['createSubmarine'](mockRequest(reqBody), res);

expect(service.createSwapWithInvoice).toHaveBeenCalledTimes(1);
Expand All @@ -214,6 +224,43 @@ describe('SwapRouter', () => {
);
});

test.each`
error | params
${'undefined parameter: id'} | ${{}}
${'invalid parameter: id'} | ${{ id: 1 }}
`(
'should not get lockup transaction of submarine swaps with invalid parameters ($error)',
async ({ error, params }) => {
await expect(
swapRouter['getSubmarineTransaction'](
mockRequest(undefined, undefined, params),
mockResponse(),
),
).rejects.toEqual(error);
},
);

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

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

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

expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
id: 'txId',
hex: 'txHex',
timeoutBlockHeight: 21,
timeoutEta: 210987,
});
});

test.each`
error | body
${'undefined parameter: id'} | ${{}}
Expand Down

0 comments on commit 30ad5f0

Please sign in to comment.