Skip to content

Commit b508197

Browse files
committed
feat: support chainport bridge fee upgrade
1 parent 5b983b1 commit b508197

File tree

7 files changed

+130
-20
lines changed

7 files changed

+130
-20
lines changed

main/api/chainport/handleSendChainportBridgeTransaction.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { z } from "zod";
44
import {
55
buildTransactionRequestParams,
66
buildTransactionRequestParamsInputs,
7+
isBridgeFeeV1,
8+
isBridgeFeeV2,
79
} from "./utils/buildTransactionRequestParams";
10+
import { getConfig } from "./vendor/config";
811
import { manager } from "../manager";
912

1013
export const handleSendChainportBridgeTransactionInput =
@@ -19,6 +22,22 @@ export async function handleSendChainportBridgeTransaction({
1922
}: z.infer<typeof handleSendChainportBridgeTransactionInput>) {
2023
const ironfish = await manager.getIronfish();
2124
const rpcClient = await ironfish.rpcClient();
25+
const currentNetwork = (await rpcClient.chain.getNetworkInfo()).content
26+
.networkId;
27+
const config = getConfig(currentNetwork);
28+
const isBridgeFeeUpgradeActivated =
29+
new Date(config.bridgeFeeUpgrade) < new Date();
30+
const bridgeFeeV1 = isBridgeFeeV1(txDetails);
31+
const bridgeFeeV2 = isBridgeFeeV2(txDetails);
32+
if (isBridgeFeeUpgradeActivated) {
33+
if (!bridgeFeeV2) {
34+
throw new Error("Unsupported bridge fee version");
35+
}
36+
} else {
37+
if (!bridgeFeeV1) {
38+
throw new Error("Unsupported bridge fee version");
39+
}
40+
}
2241

2342
const params = buildTransactionRequestParams({
2443
fromAccount,

main/api/chainport/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,21 +86,27 @@ ${err}
8686
assetId: z.string(),
8787
to: z.string(),
8888
selectedNetwork: z.number(),
89+
fromAccount: z.string(),
8990
}),
9091
)
9192
.query(async (opts) => {
9293
const ironfish = await manager.getIronfish();
9394
const rpcClient = await ironfish.rpcClient();
9495
const network = await rpcClient.chain.getNetworkInfo();
9596

96-
const { amount, assetId, to, selectedNetwork } = opts.input;
97+
const { amount, assetId, to, selectedNetwork, fromAccount } = opts.input;
98+
const publicAddressResponse = await rpcClient.wallet.getAccountPublicKey({
99+
account: fromAccount,
100+
});
101+
const sourceAddress = publicAddressResponse.content.publicKey;
97102
try {
98103
return await fetchChainportBridgeTransaction(
99104
network.content.networkId,
100105
BigInt(amount),
101106
assetId,
102107
selectedNetwork,
103108
to,
109+
sourceAddress,
104110
);
105111
} catch (err) {
106112
log.error(`Failed to fetch Chainport bridge transaction details.

main/api/chainport/utils/buildTransactionRequestParams.ts

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ export const buildTransactionRequestParamsInputs = z.object({
1515
amount: z.string(),
1616
memo: z.string(),
1717
}),
18+
bridge_fee: z.union([
19+
z.object({
20+
source_token_fee_amount: z.string(),
21+
portx_fee_amount: z.string(),
22+
is_portx_fee_payment: z.boolean(),
23+
}),
24+
z.object({
25+
publicAddress: z.string(),
26+
source_token_fee_amount: z.string(),
27+
memo: z.string(),
28+
assetId: z.string(),
29+
}),
30+
]),
1831
}),
1932
fee: z.number().optional(),
2033
feeRate: z.string().optional(),
@@ -24,27 +37,78 @@ export type BuildTransactionRequestParamsInputs = z.infer<
2437
typeof buildTransactionRequestParamsInputs
2538
>;
2639

40+
export function isBridgeFeeV1(
41+
txDetails: BuildTransactionRequestParamsInputs["txDetails"],
42+
): txDetails is BuildTransactionRequestParamsInputs["txDetails"] & {
43+
bridge_fee: {
44+
source_token_fee_amount: string;
45+
portx_fee_amount: string;
46+
is_portx_fee_payment: boolean;
47+
};
48+
} {
49+
return (
50+
"is_portx_fee_payment" in txDetails.bridge_fee &&
51+
"source_token_fee_amount" in txDetails.bridge_fee &&
52+
"portx_fee_amount" in txDetails.bridge_fee
53+
);
54+
}
55+
56+
export function isBridgeFeeV2(
57+
txDetails: BuildTransactionRequestParamsInputs["txDetails"],
58+
): txDetails is BuildTransactionRequestParamsInputs["txDetails"] & {
59+
bridge_fee: {
60+
publicAddress: string;
61+
assetId: string;
62+
memo: string;
63+
source_token_fee_amount: string;
64+
};
65+
} {
66+
return (
67+
"publicAddress" in txDetails.bridge_fee &&
68+
"memo" in txDetails.bridge_fee &&
69+
"assetId" in txDetails.bridge_fee &&
70+
"source_token_fee_amount" in txDetails.bridge_fee
71+
);
72+
}
73+
2774
export function buildTransactionRequestParams({
2875
fromAccount,
2976
txDetails,
3077
fee,
3178
feeRate,
3279
}: BuildTransactionRequestParamsInputs) {
80+
const userOutput = {
81+
publicAddress: txDetails.bridge_output.publicAddress,
82+
amount: txDetails.bridge_output.amount,
83+
memoHex: txDetails.bridge_output.memoHex,
84+
assetId: txDetails.bridge_output.assetId,
85+
};
86+
const gasFeeOutput = {
87+
publicAddress: txDetails.gas_fee_output.publicAddress,
88+
amount: txDetails.gas_fee_output.amount,
89+
memo: txDetails.gas_fee_output.memo,
90+
};
91+
92+
const outputs = [userOutput, gasFeeOutput];
93+
94+
const bridgeFee = txDetails.bridge_fee.source_token_fee_amount;
95+
if (isBridgeFeeV2(txDetails) && BigInt(bridgeFee) > 0n) {
96+
userOutput.amount = (
97+
BigInt(userOutput.amount) - BigInt(bridgeFee)
98+
).toString();
99+
100+
const bridgeFeeOutput = {
101+
publicAddress: txDetails.bridge_fee.publicAddress,
102+
amount: bridgeFee,
103+
memo: txDetails.bridge_fee.memo,
104+
assetId: txDetails.bridge_fee.assetId,
105+
};
106+
outputs.push(bridgeFeeOutput);
107+
}
108+
33109
const params: CreateTransactionRequest = {
34110
account: fromAccount,
35-
outputs: [
36-
{
37-
publicAddress: txDetails.bridge_output.publicAddress,
38-
amount: txDetails.bridge_output.amount,
39-
memoHex: txDetails.bridge_output.memoHex,
40-
assetId: txDetails.bridge_output.assetId,
41-
},
42-
{
43-
publicAddress: txDetails.gas_fee_output.publicAddress,
44-
amount: txDetails.gas_fee_output.amount,
45-
memo: txDetails.gas_fee_output.memo,
46-
},
47-
],
111+
outputs,
48112
fee: fee ? CurrencyUtils.encode(BigInt(fee)) : null,
49113
feeRate: feeRate ?? null,
50114
expiration: undefined,

main/api/chainport/vendor/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const config = {
1515
incomingAddresses: new Set([
1616
"06102d319ab7e77b914a1bd135577f3e266fd82a3e537a02db281421ed8b3d13",
1717
]),
18+
bridgeFeeUpgrade: new Date("2025-07-01T00:00:00Z"),
1819
},
1920
[MAINNET.id]: {
2021
endpoint: "https://api.ironfish.network",
@@ -25,6 +26,7 @@ const config = {
2526
incomingAddresses: new Set([
2627
"1216302193e8f1ad020f458b54a163039403d803e98673c6a85e59b5f4a1a900",
2728
]),
29+
bridgeFeeUpgrade: new Date("2025-07-15T00:00:00Z"),
2830
},
2931
};
3032

main/api/chainport/vendor/requests.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,19 @@ export const fetchChainportBridgeTransaction = async (
6464
assetId: string,
6565
targetNetworkId: number,
6666
targetAddress: string,
67+
sourceAddress: string,
6768
): Promise<ChainportBridgeTransaction> => {
6869
const config = getConfig(networkId);
70+
const isBridgeFeeUpgradeActivated =
71+
new Date(config.bridgeFeeUpgrade) < new Date();
6972
const url = new URL(`/bridges/transactions/create`, config.endpoint);
7073
url.searchParams.append("amount", amount.toString());
7174
url.searchParams.append("asset_id", assetId);
7275
url.searchParams.append("target_network_id", targetNetworkId.toString());
7376
url.searchParams.append("target_address", targetAddress.toString());
77+
if (isBridgeFeeUpgradeActivated) {
78+
url.searchParams.append("source_address", sourceAddress);
79+
}
7480

7581
return await makeChainportRequest<ChainportBridgeTransaction>(url.toString());
7682
};

main/api/chainport/vendor/types.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44

55
// This file contains response types for chainport requests
66

7+
export type ChainportBridgeFeeV1 = {
8+
source_token_fee_amount: string;
9+
portx_fee_amount: string;
10+
is_portx_fee_payment: boolean;
11+
};
12+
13+
export type ChainportBridgeFeeV2 = {
14+
publicAddress: string;
15+
source_token_fee_amount: string;
16+
memo: string;
17+
assetId: string;
18+
};
19+
720
export type ChainportBridgeTransaction = {
821
bridge_output: {
922
publicAddress: string;
@@ -16,11 +29,7 @@ export type ChainportBridgeTransaction = {
1629
amount: string;
1730
memo: string;
1831
};
19-
bridge_fee: {
20-
source_token_fee_amount: string;
21-
portx_fee_amount: string;
22-
is_portx_fee_payment: boolean;
23-
};
32+
bridge_fee: ChainportBridgeFeeV1 | ChainportBridgeFeeV2;
2433
};
2534

2635
export type ChainportNetwork = {

renderer/components/BridgeAssetsForm/BridgeConfirmationModal/BridgeConfirmationModal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export function BridgeConfirmationModal({
115115
assetId: chainportToken.web3_address,
116116
to: formData.destinationAddress,
117117
selectedNetwork: destinationNetwork.network.chainport_network_id,
118+
fromAccount: formData.fromAccount,
118119
},
119120
{
120121
retry: false,
@@ -210,7 +211,10 @@ export function BridgeConfirmationModal({
210211
return <Skeleton>123 FOO</Skeleton>;
211212
}
212213

213-
if (txDetails.bridge_fee.is_portx_fee_payment) {
214+
if (
215+
"is_portx_fee_payment" in txDetails.bridge_fee &&
216+
txDetails.bridge_fee.is_portx_fee_payment
217+
) {
214218
const fee = CurrencyUtils.formatCurrency(
215219
txDetails.bridge_fee.portx_fee_amount,
216220
18,

0 commit comments

Comments
 (0)