Skip to content

Commit 59570fe

Browse files
authored
feat: support chainport bridge fee v2 (#1805)
* feat: integrate chainport upgrade * feat: upgrade actions/cache to v4
1 parent 7a2d351 commit 59570fe

File tree

9 files changed

+127
-15
lines changed

9 files changed

+127
-15
lines changed

.env.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ BLOCK_LOADER_TRANSACTION_TIMEOUT=5000
33
CHAINPORT_ACTIVE=false
44
CHAINPORT_API_URL=https://preprod-api.chainport.io/
55
CHAINPORT_API_VERSION=2
6+
CHAINPORT_BRIDGE_FEE_VERSION=1
67
CHAINPORT_MAINTENANCE=false
78
CHAINPORT_NETWORK_ID=22
89
CORS_ENABLED=true

.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ BLOCK_LOADER_TRANSACTION_TIMEOUT=5000
33
CHAINPORT_ACTIVE=false
44
CHAINPORT_API_URL=https://preprod-api.chainport.io/
55
CHAINPORT_API_VERSION=2
6+
CHAINPORT_BRIDGE_FEE_VERSION=1
67
CHAINPORT_MAINTENANCE=false
78
CHAINPORT_NETWORK_ID=22
89
CORS_ENABLED=true

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838

3939
- name: Restore Yarn cache
4040
id: yarn-cache
41-
uses: actions/cache@v2
41+
uses: actions/cache@v4
4242
with:
4343
path: |
4444
node_modules
@@ -69,7 +69,7 @@ jobs:
6969

7070
- name: Restore Yarn cache
7171
id: yarn-cache
72-
uses: actions/cache@v2
72+
uses: actions/cache@v4
7373
with:
7474
path: |
7575
node_modules

src/app.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const REST_MODULES = [
5656
CHAINPORT_ACTIVE: joi.boolean().required(),
5757
CHAINPORT_API_URL: joi.string().required(),
5858
CHAINPORT_API_VERSION: joi.number().required(),
59+
CHAINPORT_BRIDGE_FEE_VERSION: joi.number().required(),
5960
CHAINPORT_MAINTENANCE: joi.boolean().required(),
6061
CHAINPORT_NETWORK_ID: joi.string().required(),
6162
CORS_ENABLED: joi.boolean().default(true),

src/bridges/bridges.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export class BridgesController {
107107
query.asset_id,
108108
query.target_network_id,
109109
query.target_address,
110+
query.source_address,
110111
);
111112

112113
return transactionOutputs;

src/bridges/chainport.service.spec.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import { INestApplication } from '@nestjs/common';
55
import { ApiConfigService } from '../api-config/api-config.service';
66
import { NATIVE_ASSET_ID } from '../common/constants';
77
import { bootstrapTestApp } from '../test/test-app';
8-
import { ChainportService } from './chainport.service';
8+
import {
9+
ChainportBridgeFeeV1,
10+
ChainportBridgeFeeV2,
11+
ChainportService,
12+
} from './chainport.service';
913

1014
// eslint-disable-next-line jest/no-disabled-tests
1115
describe.skip('ChainportService', () => {
@@ -160,6 +164,54 @@ describe.skip('ChainportService', () => {
160164

161165
expect(results).not.toBeNull();
162166
}, 20000);
167+
168+
it('works with bridge fee v1', async () => {
169+
const originalGet = config.get.bind(config);
170+
jest.spyOn(config, 'get').mockImplementation((val) => {
171+
if (val === 'CHAINPORT_BRIDGE_FEE_VERSION') {
172+
return 1;
173+
}
174+
return originalGet(val);
175+
});
176+
const results = await chainport.getIronFishMetadata(
177+
100n,
178+
NATIVE_ASSET_ID,
179+
15,
180+
'0xF1d90Af0D4638cD947971d858053696DC72bd241',
181+
);
182+
183+
expect(results).not.toBeNull();
184+
expect(results.bridge_fee).toMatchObject({
185+
source_token_fee_amount: expect.any(String),
186+
portx_fee_amount: expect.any(String),
187+
is_portx_fee_payment: expect.any(Boolean),
188+
} as ChainportBridgeFeeV1);
189+
}, 20000);
190+
191+
it('works with bridge fee v2', async () => {
192+
const originalGet = config.get.bind(config);
193+
jest.spyOn(config, 'get').mockImplementation((val) => {
194+
if (val === 'CHAINPORT_BRIDGE_FEE_VERSION') {
195+
return 2;
196+
}
197+
return originalGet(val);
198+
});
199+
const results = await chainport.getIronFishMetadata(
200+
100n,
201+
NATIVE_ASSET_ID,
202+
15,
203+
'0xF1d90Af0D4638cD947971d858053696DC72bd241',
204+
'2cabe0bddf475a478f4b3903f8fc2d2c70b52526f991bacea8267793da63f44b',
205+
);
206+
207+
expect(results).not.toBeNull();
208+
expect(results.bridge_fee).toMatchObject({
209+
publicAddress: expect.any(String),
210+
source_token_fee_amount: expect.any(String),
211+
memo: expect.any(String),
212+
assetId: expect.any(String),
213+
} as ChainportBridgeFeeV2);
214+
}, 20000);
163215
});
164216

165217
describe('getPort', () => {

src/bridges/chainport.service.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { HttpService } from '@nestjs/axios';
55
import {
66
BadGatewayException,
7+
BadRequestException,
78
Injectable,
89
NotFoundException,
910
UnprocessableEntityException,
@@ -111,6 +112,32 @@ type ChainportTokenListResponse = {
111112
verified_tokens: ChainportToken[];
112113
};
113114

115+
export type ChainportBridgeFeeV1 = {
116+
source_token_fee_amount: string;
117+
portx_fee_amount: string;
118+
is_portx_fee_payment: boolean;
119+
};
120+
121+
const chainportBridgeFeeV1Schema = Joi.object<ChainportBridgeFeeV1>({
122+
source_token_fee_amount: Joi.string().required(),
123+
portx_fee_amount: Joi.string().required(),
124+
is_portx_fee_payment: Joi.boolean().required(),
125+
});
126+
127+
export type ChainportBridgeFeeV2 = {
128+
publicAddress: string;
129+
source_token_fee_amount: string;
130+
memo: string;
131+
assetId: string;
132+
};
133+
134+
const chainportBridgeFeeV2Schema = Joi.object<ChainportBridgeFeeV2>({
135+
publicAddress: Joi.string().required(),
136+
source_token_fee_amount: Joi.string().required(),
137+
memo: Joi.string().required(),
138+
assetId: Joi.string().required(),
139+
});
140+
114141
export type ChainportIronFishMetadata = {
115142
bridge_output: {
116143
publicAddress: string;
@@ -123,11 +150,7 @@ export type ChainportIronFishMetadata = {
123150
amount: string;
124151
memo: string;
125152
};
126-
bridge_fee: {
127-
source_token_fee_amount: string;
128-
portx_fee_amount: string;
129-
is_portx_fee_payment: boolean;
130-
};
153+
bridge_fee: ChainportBridgeFeeV1 | ChainportBridgeFeeV2;
131154
};
132155

133156
const chainportIronFishMetadataSchema = Joi.object<ChainportIronFishMetadata>({
@@ -142,11 +165,9 @@ const chainportIronFishMetadataSchema = Joi.object<ChainportIronFishMetadata>({
142165
amount: Joi.string().required(),
143166
memo: Joi.string().required(),
144167
}).required(),
145-
bridge_fee: Joi.object({
146-
source_token_fee_amount: Joi.string().required(),
147-
portx_fee_amount: Joi.string().required(),
148-
is_portx_fee_payment: Joi.boolean().required(),
149-
}).required(),
168+
bridge_fee: Joi.alternatives()
169+
.try(chainportBridgeFeeV1Schema, chainportBridgeFeeV2Schema)
170+
.required(),
150171
});
151172

152173
export type ChainportPort =
@@ -432,8 +453,12 @@ export class ChainportService {
432453
assetId: string,
433454
targetNetworkId: number,
434455
targetWeb3Address: string,
456+
sourceAddress?: string,
435457
): Promise<ChainportIronFishMetadata> {
436458
const apiurl = this.config.get<string>('CHAINPORT_API_URL');
459+
const bridgeFeeVersion = this.config.get<number>(
460+
'CHAINPORT_BRIDGE_FEE_VERSION',
461+
);
437462

438463
const ironfishMetadataUrl = new URL(`/ironfish/metadata`, apiurl);
439464
ironfishMetadataUrl.searchParams.append('raw_amount', amount.toString());
@@ -447,6 +472,22 @@ export class ChainportService {
447472
targetWeb3Address,
448473
);
449474

475+
if (bridgeFeeVersion === 1 && sourceAddress) {
476+
throw new BadRequestException(
477+
'Source address is not supported for bridge fee version 1',
478+
);
479+
} else if (bridgeFeeVersion === 2 && !sourceAddress) {
480+
throw new BadRequestException(
481+
'Source address is required for bridge fee version 2',
482+
);
483+
}
484+
if (sourceAddress) {
485+
ironfishMetadataUrl.searchParams.append(
486+
'ironfish_user_address',
487+
sourceAddress,
488+
);
489+
}
490+
450491
const ironfishMetadataResult =
451492
await this.makeChainportRequest<ChainportIronFishMetadata>(
452493
ironfishMetadataUrl.toString(),

src/bridges/dto/transactions-create.dto.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
/* This Source Code Form is subject to the terms of the Mozilla Public
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4-
import { ApiProperty } from '@nestjs/swagger';
4+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
55
import { Transform, TransformFnParams, Type } from 'class-transformer';
6-
import { IsDefined, IsInt, IsPositive, IsString } from 'class-validator';
6+
import {
7+
IsDefined,
8+
IsInt,
9+
IsOptional,
10+
IsPositive,
11+
IsString,
12+
} from 'class-validator';
713
import { stringToPositiveBigint } from '../../common/utils/bigint';
814

915
export class TransactionsCreateDto {
@@ -45,4 +51,12 @@ export class TransactionsCreateDto {
4551
})
4652
@IsString()
4753
readonly target_address!: string;
54+
55+
@ApiPropertyOptional({
56+
description:
57+
'Address on the source network that will send the bridged asset',
58+
})
59+
@IsOptional()
60+
@IsString()
61+
readonly source_address: string | undefined;
4862
}

src/test/test-app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export async function bootstrapTestApp(): Promise<INestApplication> {
2323
CHAINPORT_ACTIVE: joi.boolean().required(),
2424
CHAINPORT_API_URL: joi.string().required(),
2525
CHAINPORT_API_VERSION: joi.number().required(),
26+
CHAINPORT_BRIDGE_FEE_VERSION: joi.number().required(),
2627
CHAINPORT_MAINTENANCE: joi.boolean().required(),
2728
CHAINPORT_NETWORK_ID: joi.string().required(),
2829
CORS_ENABLED: joi.boolean().default(true),

0 commit comments

Comments
 (0)