Skip to content

Commit dac431c

Browse files
committed
feat: 🎸 get protocol fees
1 parent e66e21f commit dac431c

File tree

8 files changed

+165
-2
lines changed

8 files changed

+165
-2
lines changed

‎src/common/utils/functions.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,24 @@ export function getNextYearISO(): string {
202202

203203
return nextYear.toISOString();
204204
}
205+
206+
/**
207+
* Helper to convert a string to an array
208+
*
209+
* @param value - The value to convert to an array
210+
* @returns The value as an array
211+
*/
212+
export function toArray(value: unknown): string[] | undefined {
213+
if (Array.isArray(value)) {
214+
return value;
215+
}
216+
217+
if (typeof value === 'string') {
218+
return value
219+
.split(',')
220+
.map(item => item.trim())
221+
.filter(Boolean);
222+
}
223+
224+
return undefined;
225+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/* istanbul ignore file */
2+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
3+
import { TxTag, TxTags } from '@polymeshassociation/polymesh-sdk/types';
4+
import { Transform } from 'class-transformer';
5+
import { ArrayNotEmpty, IsArray, IsOptional, IsString } from 'class-validator';
6+
7+
import { IsTxTag } from '~/common/decorators/validation';
8+
import { getTxTags, toArray } from '~/common/utils';
9+
10+
export class ProtocolFeesQueryDto {
11+
@ApiProperty({
12+
description: 'Transaction tags whose protocol fees should be returned',
13+
isArray: true,
14+
enum: getTxTags(),
15+
example: [TxTags.asset.CreateAsset],
16+
})
17+
@Transform(({ value }) => toArray(value))
18+
@IsArray()
19+
@ArrayNotEmpty()
20+
@IsTxTag({ each: true })
21+
readonly tags: TxTag[];
22+
23+
@ApiPropertyOptional({
24+
description: 'Optional block hash to query historic protocol fees',
25+
type: 'string',
26+
example: '0xc549227a184d7a16ffd7cd9ca923577c84f6e26946de092b30fcc2d9509789f7',
27+
})
28+
@IsOptional()
29+
@IsString()
30+
readonly blockHash?: string;
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* istanbul ignore file */
2+
import { ApiProperty } from '@nestjs/swagger';
3+
import { BigNumber } from '@polymeshassociation/polymesh-sdk';
4+
import { TxTag } from '@polymeshassociation/polymesh-sdk/types';
5+
6+
import { FromBigNumber } from '~/common/decorators/transformation';
7+
import { getTxTags } from '~/common/utils';
8+
9+
type ProtocolFeeModelParams = {
10+
tag: TxTag;
11+
fee: BigNumber;
12+
};
13+
14+
export class ProtocolFeeModel {
15+
@ApiProperty({
16+
description: 'Transaction tag the fee applies to',
17+
enum: getTxTags(),
18+
example: 'asset.createAsset',
19+
})
20+
readonly tag: TxTag;
21+
22+
@ApiProperty({
23+
description: 'Protocol fee amount in POLYX',
24+
type: 'string',
25+
example: '1',
26+
})
27+
@FromBigNumber()
28+
readonly fee: BigNumber;
29+
30+
constructor(model: ProtocolFeeModelParams) {
31+
Object.assign(this, model);
32+
}
33+
}

‎src/network/network.controller.spec.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { DeepMocked } from '@golevelup/ts-jest';
22
import { Test, TestingModule } from '@nestjs/testing';
33
import { BigNumber } from '@polymeshassociation/polymesh-sdk';
4+
import { TxTags } from '@polymeshassociation/polymesh-sdk/types';
45

56
import { MockMiddlewareMetadata } from '~/network/mocks/middleware-metadata.mock';
67
import { MockNetworkProperties } from '~/network/mocks/network-properties.mock';
78
import { MiddlewareMetadataModel } from '~/network/models/middleware-metadata.model';
89
import { NetworkBlockModel } from '~/network/models/network-block.model';
10+
import { ProtocolFeeModel } from '~/network/models/protocol-fee.model';
911
import { NetworkController } from '~/network/network.controller';
1012
import { NetworkService } from '~/network/network.service';
1113
import { mockNetworkServiceProvider } from '~/test-utils/service-mocks';
@@ -40,6 +42,19 @@ describe('NetworkController', () => {
4042
});
4143
});
4244

45+
describe('getProtocolFees', () => {
46+
it('should return protocol fees as ProtocolFeeModel[]', async () => {
47+
const tag = TxTags.asset.CreateAsset;
48+
const mockServiceResult = [{ tag, fees: new BigNumber(1) }];
49+
50+
mockNetworkService.getProtocolFees.mockResolvedValue(mockServiceResult);
51+
52+
const result = await controller.getProtocolFees({ tags: [tag] });
53+
54+
expect(result).toEqual([new ProtocolFeeModel({ tag, fee: new BigNumber(1) })]);
55+
});
56+
});
57+
4358
describe('getLatestBlock', () => {
4459
it('should return latest block number as NetworkBlockModel', async () => {
4560
const mockLatestBlock = new BigNumber(1);

‎src/network/network.controller.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { Controller, Get, NotFoundException } from '@nestjs/common';
1+
import { Controller, Get, NotFoundException, Query } from '@nestjs/common';
22
import { ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
33

4+
import { ProtocolFeesQueryDto } from '~/network/dto/protocol-fees-query.dto';
45
import { MiddlewareMetadataModel } from '~/network/models/middleware-metadata.model';
56
import { NetworkBlockModel } from '~/network/models/network-block.model';
67
import { NetworkPropertiesModel } from '~/network/models/network-properties.model';
8+
import { ProtocolFeeModel } from '~/network/models/protocol-fee.model';
79
import { NetworkService } from '~/network/network.service';
810

911
@ApiTags('network')
@@ -26,6 +28,25 @@ export class NetworkController {
2628
return new NetworkPropertiesModel(networkProperties);
2729
}
2830

31+
@ApiOperation({
32+
summary: 'Get protocol fees for transactions',
33+
description: 'Returns the protocol fees charged for the specified transaction tags',
34+
})
35+
@ApiOkResponse({
36+
description: 'Protocol fees per transaction tag',
37+
type: ProtocolFeeModel,
38+
isArray: true,
39+
})
40+
@Get('protocol-fees')
41+
public async getProtocolFees(@Query() query: ProtocolFeesQueryDto): Promise<ProtocolFeeModel[]> {
42+
const { tags, blockHash } = query;
43+
const fees = await this.networkService.getProtocolFees(tags, blockHash);
44+
45+
return fees.map(
46+
protocolFee => new ProtocolFeeModel({ tag: protocolFee.tag, fee: protocolFee.fees })
47+
);
48+
}
49+
2950
@ApiOperation({
3051
summary: 'Get the latest block',
3152
description: 'This endpoint will provide the latest block number',

‎src/network/network.service.spec.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const mockHexStripPrefix = jest.fn().mockImplementation(params => params);
33

44
import { Test, TestingModule } from '@nestjs/testing';
55
import { BigNumber } from '@polymeshassociation/polymesh-sdk';
6+
import { TxTags } from '@polymeshassociation/polymesh-sdk/types';
67

78
import { MockMiddlewareMetadata } from '~/network/mocks/middleware-metadata.mock';
89
import { MockNetworkProperties } from '~/network/mocks/network-properties.mock';
@@ -96,6 +97,39 @@ describe('NetworkService', () => {
9697
});
9798
});
9899

100+
describe('getProtocolFees', () => {
101+
it('should return protocol fees for the provided tags', async () => {
102+
const tag = TxTags.asset.CreateAsset;
103+
const protocolFees = [{ tag, fees: new BigNumber(1) }];
104+
105+
mockPolymeshApi.network.getProtocolFees.mockResolvedValue(protocolFees);
106+
107+
const result = await networkService.getProtocolFees([tag]);
108+
109+
expect(result).toBe(protocolFees);
110+
expect(mockPolymeshApi.network.getProtocolFees).toHaveBeenCalledWith({
111+
tags: [tag],
112+
blockHash: undefined,
113+
});
114+
});
115+
116+
it('should forward block hash when provided', async () => {
117+
const tag = TxTags.asset.CreateAsset;
118+
const blockHash = '0x123';
119+
const protocolFees = [{ tag, fees: new BigNumber(2) }];
120+
121+
mockPolymeshApi.network.getProtocolFees.mockResolvedValue(protocolFees);
122+
123+
const result = await networkService.getProtocolFees([tag], blockHash);
124+
125+
expect(result).toBe(protocolFees);
126+
expect(mockPolymeshApi.network.getProtocolFees).toHaveBeenCalledWith({
127+
tags: [tag],
128+
blockHash,
129+
});
130+
});
131+
});
132+
99133
describe('getTransactionByHash', () => {
100134
it('should return the extrinsic details', async () => {
101135
mockPolymeshApi.network.getTransactionByHash.mockReturnValue(extrinsicWithFees);

‎src/network/network.service.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Injectable } from '@nestjs/common';
22
import { hexStripPrefix } from '@polkadot/util';
33
import { BigNumber } from '@polymeshassociation/polymesh-sdk';
4-
import { Account, ExtrinsicDataWithFees } from '@polymeshassociation/polymesh-sdk/types';
4+
import { ProtocolFees } from '@polymeshassociation/polymesh-sdk/api/client/types';
5+
import { Account, ExtrinsicDataWithFees, TxTag } from '@polymeshassociation/polymesh-sdk/types';
56

67
import { MiddlewareMetadataModel } from '~/network/models/middleware-metadata.model';
78
import { NetworkPropertiesModel } from '~/network/models/network-properties.model';
@@ -29,6 +30,12 @@ export class NetworkService {
2930
return this.polymeshService.polymeshApi.network.getTreasuryBalance();
3031
}
3132

33+
public async getProtocolFees(tags: TxTag[], blockHash?: string): Promise<ProtocolFees[]> {
34+
const args = blockHash ? { tags, blockHash } : { tags };
35+
36+
return this.polymeshService.polymeshApi.network.getProtocolFees(args);
37+
}
38+
3239
public getTransactionByHash(hash: string): Promise<ExtrinsicDataWithFees | null> {
3340
return this.polymeshService.polymeshApi.network.getTransactionByHash({
3441
txHash: hexStripPrefix(hash),

‎src/test-utils/mocks.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export class MockPolymesh {
8181
getNetworkProperties: jest.fn(),
8282
getTreasuryAccount: jest.fn(),
8383
getTreasuryBalance: jest.fn(),
84+
getProtocolFees: jest.fn(),
8485
getTransactionByHash: jest.fn(),
8586
submitTransaction: jest.fn(),
8687
getMiddlewareMetadata: jest.fn(),

0 commit comments

Comments
 (0)