Skip to content

Commit ab892ca

Browse files
feat: support chainport bridge fee upgrade (#307)
* feat: support chainport bridge fee upgrade * feat: update chainport memo decode * Bump version 3.0.17 * fix: update memoHex decoding v1 v2 * Update package.json we don't need to bump the version until after releases * Update package-lock.json * feat: update bridge memoHex decode mechanism * feat: remove date config for chainport upgrade --------- Co-authored-by: Daniel Cogan <[email protected]>
1 parent 5b983b1 commit ab892ca

File tree

6 files changed

+100
-49
lines changed

6 files changed

+100
-49
lines changed

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: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export const buildTransactionRequestParamsInputs = z.object({
1515
amount: z.string(),
1616
memo: z.string(),
1717
}),
18+
bridge_fee: z.object({
19+
publicAddress: z.string(),
20+
source_token_fee_amount: z.string(),
21+
memo: z.string(),
22+
assetId: z.string(),
23+
}),
1824
}),
1925
fee: z.number().optional(),
2026
feeRate: z.string().optional(),
@@ -30,21 +36,38 @@ export function buildTransactionRequestParams({
3036
fee,
3137
feeRate,
3238
}: BuildTransactionRequestParamsInputs) {
39+
const userOutput = {
40+
publicAddress: txDetails.bridge_output.publicAddress,
41+
amount: txDetails.bridge_output.amount,
42+
memoHex: txDetails.bridge_output.memoHex,
43+
assetId: txDetails.bridge_output.assetId,
44+
};
45+
const gasFeeOutput = {
46+
publicAddress: txDetails.gas_fee_output.publicAddress,
47+
amount: txDetails.gas_fee_output.amount,
48+
memo: txDetails.gas_fee_output.memo,
49+
};
50+
51+
const outputs = [userOutput, gasFeeOutput];
52+
53+
const bridgeFee = txDetails.bridge_fee.source_token_fee_amount;
54+
if (BigInt(bridgeFee) > 0n) {
55+
userOutput.amount = (
56+
BigInt(userOutput.amount) - BigInt(bridgeFee)
57+
).toString();
58+
59+
const bridgeFeeOutput = {
60+
publicAddress: txDetails.bridge_fee.publicAddress,
61+
amount: bridgeFee,
62+
memo: txDetails.bridge_fee.memo,
63+
assetId: txDetails.bridge_fee.assetId,
64+
};
65+
outputs.push(bridgeFeeOutput);
66+
}
67+
3368
const params: CreateTransactionRequest = {
3469
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-
],
70+
outputs,
4871
fee: fee ? CurrencyUtils.encode(BigInt(fee)) : null,
4972
feeRate: feeRate ?? null,
5073
expiration: undefined,

main/api/chainport/vendor/metadata.ts

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,21 @@ export class ChainportMemoMetadata {
4444
return String.fromCharCode(num - 10 + "a".charCodeAt(0));
4545
}
4646

47-
public static encode(
48-
networkId: number,
49-
address: string,
50-
toIronfish: boolean,
51-
) {
52-
if (address.startsWith("0x")) {
53-
address = address.slice(2);
47+
public static convertHexToBinary(encodedHex: string): string {
48+
const buffer = Buffer.from(encodedHex, "hex");
49+
let binaryString = "";
50+
for (let i = 0; i < buffer.length; i++) {
51+
binaryString += buffer[i].toString(2).padStart(8, "0");
5452
}
55-
56-
const encodedNetworkId = this.encodeNumberTo10Bits(networkId);
57-
const encodedAddress = address
58-
.toLowerCase()
59-
.split("")
60-
.map((character: string) => {
61-
return this.encodeCharacterTo6Bits(character);
62-
})
63-
.join("");
64-
65-
const combined =
66-
(toIronfish ? "1" : "0") + (encodedNetworkId + encodedAddress).slice(1);
67-
const hexString = BigInt("0b" + combined).toString(16);
68-
return hexString.padStart(64, "0");
53+
return binaryString;
6954
}
7055

71-
public static decode(encodedHex: string): [number, string, boolean] {
56+
/**
57+
* Decode the encoded hex string into a network id, address, and toIronfish flag
58+
* @param encodedHex - The encoded hex string
59+
* @returns A tuple containing the network id, address, and toIronfish flag
60+
*/
61+
public static decodeV1(encodedHex: string): [number, string, boolean] {
7262
const hexInteger = BigInt("0x" + encodedHex);
7363
const encodedString = hexInteger.toString(2);
7464
const padded = encodedString.padStart(250, "0");
@@ -87,4 +77,32 @@ export class ChainportMemoMetadata {
8777

8878
return [networkId, address.toLowerCase(), toIronfish];
8979
}
80+
81+
public static decodeV2(encodedHex: string): [number, string, boolean] {
82+
const bits = this.convertHexToBinary(encodedHex);
83+
const toIronfish = bits[6] === "1";
84+
const memoHexVersion = bits.slice(8, 10);
85+
if (memoHexVersion !== "01") {
86+
throw new Error(`Unexpected memoHex version: ${memoHexVersion}`);
87+
}
88+
89+
const networkIdBits = bits.slice(10, 16);
90+
const networkId = parseInt(networkIdBits, 2);
91+
const addressBits = bits.slice(16, 176);
92+
let address = "0x";
93+
for (let i = 0; i < addressBits.length; i += 4) {
94+
address += parseInt(addressBits.slice(i, i + 4), 2).toString(16);
95+
}
96+
97+
return [networkId, address.toLowerCase(), toIronfish];
98+
}
99+
100+
public static decode(encodedHex: string): [number, string, boolean] {
101+
const bits = this.convertHexToBinary(encodedHex);
102+
const memoHexVersion = bits.slice(8, 10);
103+
if (memoHexVersion === "01") {
104+
return this.decodeV2(encodedHex);
105+
}
106+
return this.decodeV1(encodedHex);
107+
}
90108
}

main/api/chainport/vendor/requests.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,15 @@ export const fetchChainportBridgeTransaction = async (
6464
assetId: string,
6565
targetNetworkId: number,
6666
targetAddress: string,
67+
sourceAddress: string,
6768
): Promise<ChainportBridgeTransaction> => {
6869
const config = getConfig(networkId);
6970
const url = new URL(`/bridges/transactions/create`, config.endpoint);
7071
url.searchParams.append("amount", amount.toString());
7172
url.searchParams.append("asset_id", assetId);
7273
url.searchParams.append("target_network_id", targetNetworkId.toString());
7374
url.searchParams.append("target_address", targetAddress.toString());
75+
url.searchParams.append("source_address", sourceAddress);
7476

7577
return await makeChainportRequest<ChainportBridgeTransaction>(url.toString());
7678
};

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: ChainportBridgeFeeV2;
2433
};
2534

2635
export type ChainportNetwork = {

renderer/components/BridgeAssetsForm/BridgeConfirmationModal/BridgeConfirmationModal.tsx

Lines changed: 1 addition & 8 deletions
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,14 +211,6 @@ export function BridgeConfirmationModal({
210211
return <Skeleton>123 FOO</Skeleton>;
211212
}
212213

213-
if (txDetails.bridge_fee.is_portx_fee_payment) {
214-
const fee = CurrencyUtils.formatCurrency(
215-
txDetails.bridge_fee.portx_fee_amount,
216-
18,
217-
);
218-
return `${fee} PORTX`;
219-
}
220-
221214
return `${CurrencyUtils.formatCurrency(
222215
txDetails.bridge_fee.source_token_fee_amount,
223216
chainportToken.decimals,

0 commit comments

Comments
 (0)