Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions extension/e2e-tests/helpers/sendPayment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ export const sendXlmPayment = async ({ page }) => {
await expect(page.getByTestId("SendSettingsTransactionFee")).toHaveText(
/[0-9]/,
);
// 100 XLM is the default, so likely a sign the fee was not set properly from Horizon
await expect(
page.getByTestId("SendSettingsTransactionFee"),
).not.toContainText("100 XLM");
await expect(page.getByTestId("SendSettingsTransactionFee")).toContainText(
"100 XLM",
);
await expectPageToHaveScreenshot(
{
page,
Expand Down
14 changes: 6 additions & 8 deletions extension/e2e-tests/sendPayment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,9 @@ test("Send XLM payments to recent federated addresses", async ({
await expect(page.getByTestId("SendSettingsTransactionFee")).toHaveText(
/[0-9]/,
);
// 100 XLM is the default, so likely a sign the fee was not set properly from Horizon
await expect(
page.getByTestId("SendSettingsTransactionFee"),
).not.toContainText("100 XLM");
await expect(page.getByTestId("SendSettingsTransactionFee")).toContainText(
"100 XLM",
);
await expect(page.getByText("Review Send")).toBeEnabled();
await page.getByText("Review Send").click();

Expand Down Expand Up @@ -281,10 +280,9 @@ test("Send XLM payments to recent federated addresses", async ({
await expect(page.getByTestId("SendSettingsTransactionFee")).toHaveText(
/[0-9]/,
);
// 100 XLM is the default, so likely a sign the fee was not set properly from Horizon
await expect(
page.getByTestId("SendSettingsTransactionFee"),
).not.toContainText("100 XLM");
await expect(page.getByTestId("SendSettingsTransactionFee")).toContainText(
"100 XLM",
);
await page.getByText("Review Send").click();
await expect(page.getByText("Confirm Send")).toBeVisible();
await page.getByTestId("transaction-details-btn-send").click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ interface ManageAssetRowButtonProps {
setNewAssetFlags: (flags: any) => void;
setShowUnverifiedWarning: (rowButtonShowing: boolean) => void;
setHandleAddToken: (func: any) => void;
recommendedFee: string;
balances: AccountBalances;
}

Expand All @@ -78,7 +77,6 @@ export const ManageAssetRowButton = ({
setNewAssetFlags,
setShowUnverifiedWarning,
setHandleAddToken,
recommendedFee,
balances,
}: ManageAssetRowButtonProps) => {
const dispatch = useDispatch<AppDispatch>();
Expand All @@ -101,7 +99,6 @@ export const ManageAssetRowButton = ({
const { changeTrustline } = useChangeTrustline({
assetCode: code,
assetIssuer: issuer,
recommendedFee,
setAssetSubmitting,
setIsSigningWithHardwareWallet,
setIsTrustlineErrorShowing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
truncateString,
} from "helpers/stellar";
import { isContractId, isSacContract } from "popup/helpers/soroban";
import { useNetworkFees } from "popup/helpers/useNetworkFees";
import { defaultBlockaidScanAssetResult } from "@shared/helpers/stellar";

import { LoadingBackground } from "popup/basics/LoadingBackground";
Expand Down Expand Up @@ -100,7 +99,6 @@ export const ManageAssetRows = ({
const [assetSubmitting, setAssetSubmitting] = useState("");
const dispatch = useDispatch<AppDispatch>();
const walletType = useSelector(hardwareWalletTypeSelector);
const { recommendedFee } = useNetworkFees();
const navigate = useNavigate();

const [showBlockedDomainWarning, setShowBlockedDomainWarning] =
Expand Down Expand Up @@ -238,7 +236,6 @@ export const ManageAssetRows = ({
setAssetSubmitting={setAssetSubmitting}
setShowNewAssetWarning={setShowNewAssetWarning}
setShowUnverifiedWarning={setShowUnverifiedWarning}
recommendedFee={recommendedFee}
/>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,12 @@ const getBuiltTx = async (

return transaction;
} catch (error) {
captureException({
error,
arguments: {
const err =
error instanceof Error
? error
: new Error(typeof error === "string" ? error : JSON.stringify(error));
captureException(err, {
extra: {
sourceAsset,
destAsset,
amount,
Expand All @@ -200,8 +203,7 @@ const getBuiltTx = async (
publicKey,
},
});
const err = typeof error === "string" ? error : JSON.stringify(error);
throw new Error(`Failed to build operation: ${err}`);
throw new Error(`Failed to build operation: ${err.message}`);
}
};

Expand Down
104 changes: 104 additions & 0 deletions extension/src/popup/helpers/__tests__/useNetworkFees.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useRef, useEffect } from "react";
import { render, act } from "@testing-library/react";
import { useNetworkFees, NetworkCongestion } from "../useNetworkFees";
import { BASE_FEE } from "stellar-sdk";

import { useSelector } from "react-redux";
import { stellarSdkServer } from "@shared/api/helpers/stellarSdkServer";

jest.mock("react-redux", () => ({
useSelector: jest.fn(),
}));

jest.mock("@shared/api/helpers/stellarSdkServer", () => ({
stellarSdkServer: jest.fn(),
}));

function TestComponent({ callback }) {
const data = useNetworkFees();

useEffect(() => {
callback(data);
}, [data]);

return null;
}

describe("useNetworkFees (React 18 compatible)", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("fetches and updates fee + congestion manually", async () => {
useSelector.mockReturnValue({
networkUrl: "https://testnet.stellar.org",
networkPassphrase: "Test SDF Network ; September 2015",
});

const feeStatsMock = jest.fn().mockResolvedValue({
max_fee: { mode: "300" },
ledger_capacity_usage: "0.6",
});

stellarSdkServer.mockReturnValue({ feeStats: feeStatsMock });

let hookResult;
await act(async () => {
render(
<TestComponent
callback={(data) => {
hookResult = data;
}}
/>,
);
});

await act(async () => {
await hookResult.fetchData();
});

expect(hookResult.recommendedFee).toBe("0.00003");
expect(hookResult.networkCongestion).toBe(NetworkCongestion.MEDIUM);

feeStatsMock.mockResolvedValueOnce({
max_fee: { mode: "1000" },
ledger_capacity_usage: "0.9",
});

await act(async () => {
await hookResult.fetchData();
});

expect(hookResult.recommendedFee).toBe("0.0001");
expect(hookResult.networkCongestion).toBe(NetworkCongestion.HIGH);
});

it("falls back to BASE_FEE on error", async () => {
useSelector.mockReturnValue({
networkUrl: "https://testnet.stellar.org",
networkPassphrase: "Test SDF Network ; September 2015",
});

stellarSdkServer.mockReturnValue({
feeStats: jest.fn().mockRejectedValue(new Error("Network failure")),
});

let hookResult;
await act(async () => {
render(
<TestComponent
callback={(data) => {
hookResult = data;
}}
/>,
);
});

await act(async () => {
await hookResult.fetchData();
});

expect(hookResult.recommendedFee).toBe(BASE_FEE);
expect(hookResult.networkCongestion).toBe("");
});
});
9 changes: 3 additions & 6 deletions extension/src/popup/helpers/useChangeTrustline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ import { useNetworkFees } from "./useNetworkFees";
export const useChangeTrustline = ({
assetCode,
assetIssuer,
recommendedFee: inputFee,
setAssetSubmitting,
setIsSigningWithHardwareWallet,
setIsTrustlineErrorShowing,
setRowButtonShowing,
}: {
assetCode: string;
assetIssuer: string;
recommendedFee?: string;
setAssetSubmitting?: (rowButtonShowing: string) => void;
setIsSigningWithHardwareWallet?: (value: boolean) => void;
setIsTrustlineErrorShowing?: (value: boolean) => void;
Expand All @@ -56,8 +54,7 @@ export const useChangeTrustline = ({
networkDetails.networkPassphrase,
);

const networkFees = useNetworkFees();
const recommendedFee = inputFee || networkFees.recommendedFee;
const { fetchData: fetchFees } = useNetworkFees();

const canonicalAsset = getCanonicalFromAsset(assetCode, assetIssuer);

Expand Down Expand Up @@ -104,14 +101,14 @@ export const useChangeTrustline = ({
successfulCallback?: () => Promise<void>,
) => {
setAssetSubmitting?.(canonicalAsset);

const fees = await fetchFees();
const transactionXDR: string = await getManageAssetXDR({
publicKey,
assetCode,
assetIssuer,
addTrustline,
server,
recommendedFee,
recommendedFee: fees.recommendedFee,
networkDetails,
});

Expand Down
48 changes: 27 additions & 21 deletions extension/src/popup/helpers/useNetworkFees.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { BASE_FEE } from "stellar-sdk";

import { stellarSdkServer } from "@shared/api/helpers/stellarSdkServer";
import { stroopToXlm } from "helpers/stellar";
Expand All @@ -15,34 +16,39 @@ export const useNetworkFees = () => {
const { networkUrl, networkPassphrase } = useSelector(
settingsNetworkDetailsSelector,
);
const [recommendedFee, setRecommendedFee] = useState("");
const [recommendedFee, setRecommendedFee] = useState(BASE_FEE);
const [networkCongestion, setNetworkCongestion] = useState(
"" as NetworkCongestion,
);

useEffect(() => {
(async () => {
try {
const server = stellarSdkServer(networkUrl, networkPassphrase);
const { max_fee: maxFee, ledger_capacity_usage: ledgerCapacityUsage } =
await server.feeStats();
const ledgerCapacityUsageNum = Number(ledgerCapacityUsage);
const fetchData = async () => {
try {
const server = stellarSdkServer(networkUrl, networkPassphrase);
const { max_fee: maxFee, ledger_capacity_usage: ledgerCapacityUsage } =
await server.feeStats();
const ledgerCapacityUsageNum = Number(ledgerCapacityUsage);

setRecommendedFee(stroopToXlm(maxFee.mode).toFixed());
if (ledgerCapacityUsageNum > 0.5 && ledgerCapacityUsageNum <= 0.75) {
setNetworkCongestion(NetworkCongestion.MEDIUM);
} else if (ledgerCapacityUsageNum > 0.75) {
setNetworkCongestion(NetworkCongestion.HIGH);
} else {
setNetworkCongestion(NetworkCongestion.LOW);
}
} catch (e) {
// use default values
setRecommendedFee("100");
console.error(e);
setRecommendedFee(stroopToXlm(maxFee.mode).toFixed());
if (ledgerCapacityUsageNum > 0.5 && ledgerCapacityUsageNum <= 0.75) {
setNetworkCongestion(NetworkCongestion.MEDIUM);
} else if (ledgerCapacityUsageNum > 0.75) {
setNetworkCongestion(NetworkCongestion.HIGH);
} else {
setNetworkCongestion(NetworkCongestion.LOW);
}
return { recommendedFee, networkCongestion };
} catch (e) {
setRecommendedFee(BASE_FEE);
return { recommendedFee };
}
};

useEffect(() => {
(async () => {
await fetchData();
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [networkUrl, networkPassphrase]);

return { recommendedFee, networkCongestion };
return { recommendedFee, networkCongestion, fetchData };
};
1 change: 1 addition & 0 deletions extension/src/popup/views/__tests__/ManageAssets.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ jest.spyOn(ApiInternal, "signFreighterTransaction").mockImplementation(() =>
jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => ({
recommendedFee: "0.00001",
networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM,
fetchData: () => Promise.resolve({ recommendedFee: "00.1" }),
}));

jest.spyOn(SearchAsset, "searchAsset").mockImplementation(({ asset }) => {
Expand Down
1 change: 1 addition & 0 deletions extension/src/popup/views/__tests__/SendPayment.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => {
return {
recommendedFee: ".00001",
networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM,
fetchData: () => Promise.resolve({ recommendedFee: "00.1" }),
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,7 @@ jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => {
return {
recommendedFee: ".00001",
networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM,
};
});

jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => {
return {
recommendedFee: ".00001",
networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM,
fetchData: () => Promise.resolve({ recommendedFee: "00.1" }),
};
});

Expand Down
1 change: 1 addition & 0 deletions extension/src/popup/views/__tests__/Swap.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ jest.spyOn(ApiInternal, "signFreighterTransaction").mockImplementation(() =>
jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => ({
recommendedFee: "0.00001",
networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM,
fetchData: () => Promise.resolve({ recommendedFee: "00.1" }),
}));

jest.spyOn(BlockaidHelpers, "useScanTx").mockImplementation(() => {
Expand Down
1 change: 1 addition & 0 deletions extension/src/popup/views/__tests__/SwapUnfunded.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jest.spyOn(ApiInternal, "signFreighterTransaction").mockImplementation(() =>
jest.spyOn(UseNetworkFees, "useNetworkFees").mockImplementation(() => ({
recommendedFee: "0.00001",
networkCongestion: UseNetworkFees.NetworkCongestion.MEDIUM,
fetchData: () => Promise.resolve({ recommendedFee: "00.1" }),
}));

jest.mock("popup/helpers/horizonGetBestPath", () => ({
Expand Down
Loading