diff --git a/extension/e2e-tests/addAsset.test.ts b/extension/e2e-tests/addAsset.test.ts index fdca9ddb3..7e8a37d95 100644 --- a/extension/e2e-tests/addAsset.test.ts +++ b/extension/e2e-tests/addAsset.test.ts @@ -1,5 +1,6 @@ import { test, expect, expectPageToHaveScreenshot } from "./test-fixtures"; import { loginToTestAccount, PASSWORD } from "./helpers/login"; +import { TEST_TOKEN_ADDRESS } from "./helpers/test-token"; test("Adding unverified Soroban token", async ({ page, extensionId }) => { test.slow(); @@ -15,13 +16,11 @@ test("Adding unverified Soroban token", async ({ page, extensionId }) => { await expect(page.getByText("Your assets")).toBeVisible(); await page.getByText("Add an asset").click({ force: true }); await page.getByText("Add manually").click({ force: true }); - await page - .getByTestId("search-token-input") - .fill("CAHX2LUNQ4YKNJTDEFW2LSFOXDAL4QI4736RV52ZUGCIRJK5U7MWQWW6"); + await page.getByTestId("search-token-input").fill(TEST_TOKEN_ADDRESS); await expect(page.getByTestId("asset-notification")).toHaveText( "Not on your listsFreighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", ); - await expect(page.getByTestId("ManageAssetCode")).toHaveText("E2E Token"); + await expect(page.getByTestId("ManageAssetCode")).toHaveText("E2E"); await expect(page.getByTestId("ManageAssetRowButton")).toHaveText("Add"); await page.getByTestId("ManageAssetRowButton").click({ force: true }); diff --git a/extension/e2e-tests/helpers/login.ts b/extension/e2e-tests/helpers/login.ts index c843196ee..ea528b888 100644 --- a/extension/e2e-tests/helpers/login.ts +++ b/extension/e2e-tests/helpers/login.ts @@ -22,7 +22,7 @@ export const loginAndFund = async ({ page, extensionId }) => { }); await page.goto(`chrome-extension://${extensionId}/index.html#/account`); - await page.getByTestId("BlockaidAnnouncement__accept").click(); + // await page.getByTestId("BlockaidAnnouncement__accept").click(); await expect(page.getByTestId("network-selector-open")).toBeVisible({ timeout: 10000, }); @@ -76,7 +76,7 @@ export const loginToTestAccount = async ({ page, extensionId }) => { }); await page.goto(`chrome-extension://${extensionId}/index.html#/account`); - await page.getByTestId("BlockaidAnnouncement__accept").click(); + // await page.getByTestId("BlockaidAnnouncement__accept").click(); await expect(page.getByTestId("network-selector-open")).toBeVisible({ timeout: 50000, }); diff --git a/extension/e2e-tests/helpers/test-token.ts b/extension/e2e-tests/helpers/test-token.ts new file mode 100644 index 000000000..ce274d84f --- /dev/null +++ b/extension/e2e-tests/helpers/test-token.ts @@ -0,0 +1,2 @@ +export const TEST_TOKEN_ADDRESS = + "CA5F3Q3KQWGG5J4U6OBCJEFVG4B5JVMHFRGQGMLNZXTEMO7YEO6UYMMD"; diff --git a/extension/e2e-tests/sendPayment.test.ts b/extension/e2e-tests/sendPayment.test.ts index 058e5f5d8..bc59800ed 100644 --- a/extension/e2e-tests/sendPayment.test.ts +++ b/extension/e2e-tests/sendPayment.test.ts @@ -1,5 +1,6 @@ import { test, expect, expectPageToHaveScreenshot } from "./test-fixtures"; import { loginAndFund, loginToTestAccount, PASSWORD } from "./helpers/login"; +import { TEST_TOKEN_ADDRESS } from "./helpers/test-token"; test("Send XLM payment to G address", async ({ page, extensionId }) => { test.slow(); @@ -78,9 +79,7 @@ test("Send XLM payment to C address", async ({ page, extensionId }) => { // send XLM to C address await page.getByTitle("Send Payment").click({ force: true }); await expect(page.getByText("Send To")).toBeVisible(); - await page - .getByTestId("send-to-input") - .fill("CAHX2LUNQ4YKNJTDEFW2LSFOXDAL4QI4736RV52ZUGCIRJK5U7MWQWW6"); + await page.getByTestId("send-to-input").fill(TEST_TOKEN_ADDRESS); await page.getByText("Continue").click({ force: true }); await expect(page.getByText("Send XLM")).toBeVisible(); @@ -172,9 +171,7 @@ test("Send SAC to C address", async ({ page, extensionId }) => { // send SAC to C address await page.getByTitle("Send Payment").click({ force: true }); - await page - .getByTestId("send-to-input") - .fill("CAHX2LUNQ4YKNJTDEFW2LSFOXDAL4QI4736RV52ZUGCIRJK5U7MWQWW6"); + await page.getByTestId("send-to-input").fill(TEST_TOKEN_ADDRESS); await page.getByText("Continue").click({ force: true }); await page.getByTestId("send-amount-asset-select").click({ force: true }); @@ -227,17 +224,13 @@ test("Send token payment to C address", async ({ page, extensionId }) => { await expect(page.getByText("Your assets")).toBeVisible(); await page.getByText("Add an asset").click({ force: true }); await page.getByText("Add manually").click({ force: true }); - await page - .getByTestId("search-token-input") - .fill("CAHX2LUNQ4YKNJTDEFW2LSFOXDAL4QI4736RV52ZUGCIRJK5U7MWQWW6"); + await page.getByTestId("search-token-input").fill(TEST_TOKEN_ADDRESS); await page.getByTestId("ManageAssetRowButton").click({ force: true }); await page.getByTestId("add-asset").dispatchEvent("click"); // send E2E token to C address await page.getByTitle("Send Payment").click({ force: true }); - await page - .getByTestId("send-to-input") - .fill("CAHX2LUNQ4YKNJTDEFW2LSFOXDAL4QI4736RV52ZUGCIRJK5U7MWQWW6"); + await page.getByTestId("send-to-input").fill(TEST_TOKEN_ADDRESS); await page.getByText("Continue").click({ force: true }); await page.getByTestId("send-amount-asset-select").click({ force: true }); diff --git a/extension/src/popup/components/__tests__/HistoryItem.test.tsx b/extension/src/popup/components/__tests__/HistoryItem.test.tsx index 9075f5d03..a905c4b5d 100644 --- a/extension/src/popup/components/__tests__/HistoryItem.test.tsx +++ b/extension/src/popup/components/__tests__/HistoryItem.test.tsx @@ -4,6 +4,7 @@ import { render, waitFor, screen } from "@testing-library/react"; import { HistoryItem } from "popup/components/accountHistory/HistoryItem"; import { TESTNET_NETWORK_DETAILS } from "@shared/constants/stellar"; import * as sorobanHelpers from "popup/helpers/soroban"; +import * as internalApi from "@shared/api/internal"; describe("HistoryItem", () => { afterAll(() => { @@ -20,6 +21,13 @@ describe("HistoryItem", () => { amount: "100000000", }; }); + jest.spyOn(internalApi, "getTokenDetails").mockImplementation(() => { + return Promise.resolve({ + decimals: 7, + name: "native", + symbol: "XLM", + }); + }); it("renders SAC transfers as payments", async () => { const props = { accountBalances: { diff --git a/extension/src/popup/components/accountHistory/HistoryItem/index.tsx b/extension/src/popup/components/accountHistory/HistoryItem/index.tsx index 7d618e7c7..2e4f9d792 100644 --- a/extension/src/popup/components/accountHistory/HistoryItem/index.tsx +++ b/extension/src/popup/components/accountHistory/HistoryItem/index.tsx @@ -397,17 +397,26 @@ export const HistoryItem = ({ setIconComponent( , ); + setIsLoading(true); - if (!tokenKey) { - // TODO: attempt to fetch token details, not stored - setRowText(operationString); - setTxDetails((_state) => ({ - ..._state, - headerTitle: translations("Transaction"), - operationText: operationString, - })); - } else { - const { token, decimals } = balances[tokenKey] as TokenBalance; + try { + const tokenDetailsResponse = await getTokenDetails({ + contractId: attrs.contractId, + publicKey, + networkDetails, + }); + + if (!tokenDetailsResponse) { + setRowText(operationString); + setTxDetails((_state) => ({ + ..._state, + headerTitle: translations("Transaction"), + operationText: operationString, + })); + } + + const { symbol, decimals } = tokenDetailsResponse!; + const code = symbol === "native" ? "XLM" : symbol; const formattedTokenAmount = formatTokenAmount( new BigNumber(attrs.amount), decimals, @@ -418,7 +427,7 @@ export const HistoryItem = ({ setBodyComponent( <> {paymentDifference} - {formattedTokenAmount} {token.code} + {formattedTokenAmount} {code} , ); setIconComponent( @@ -428,7 +437,7 @@ export const HistoryItem = ({ ), ); - setRowText(token.code); + setRowText(code); setDateText( (_dateText) => `${ @@ -440,9 +449,19 @@ export const HistoryItem = ({ isRecipient: _isRecipient, headerTitle: `${ _isRecipient ? translations("Received") : translations("Sent") - } ${token.code}`, - operationText: `${paymentDifference}${formattedTokenAmount} ${token.code}`, + } ${code}`, + operationText: `${paymentDifference}${formattedTokenAmount} ${code}`, + })); + } catch (error) { + // falls back to only showing contract ID + setRowText(operationString); + setTxDetails((_state) => ({ + ..._state, + headerTitle: translations("Transaction"), + operationText: operationString, })); + } finally { + setIsLoading(false); } } else { setRowText(operationString); diff --git a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx index 9d08d96e2..910d4aa06 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx @@ -258,7 +258,13 @@ const getBuiltTx = async ( .setTimeout(transactionTimeout); }; -export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { +export const TransactionDetails = ({ + goBack, + shouldScanTx, +}: { + goBack: () => void; + shouldScanTx: boolean; +}) => { const dispatch: AppDispatch = useDispatch(); const submission = useSelector(transactionSubmissionSelector); const { @@ -289,7 +295,7 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { const isPathPayment = useSelector(isPathPaymentSelector); const { isMemoValidationEnabled } = useSelector(settingsSelector); const isSwap = useIsSwap(); - const { scanTx, data: scanResult, isLoading } = useScanTx(); + const { scanTx, data: scanResult, isLoading, setLoading } = useScanTx(); const { t } = useTranslation(); @@ -349,6 +355,7 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { const url = "internal"; // blockaid prefers a URL for this endpoint, but this does not originate from a URL const scanSorobanTx = async () => { if ( + shouldScanTx && submission.submitStatus === ActionStatus.IDLE && transactionSimulation.preparedTransaction ) { @@ -358,28 +365,32 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { networkDetails, ); } + setLoading(false); }; const scanClassicTx = async () => { - const transaction = await getBuiltTx( - publicKey, - { - sourceAsset, - destAsset, - amount, - destinationAmount, - destination, - allowedSlippage, - path, - isPathPayment, - isSwap, - isFunded: destinationBalances.isFunded!, - }, - transactionFee, - transactionTimeout, - networkDetails, - ); + if (shouldScanTx) { + const transaction = await getBuiltTx( + publicKey, + { + sourceAsset, + destAsset, + amount, + destinationAmount, + destination, + allowedSlippage, + path, + isPathPayment, + isSwap, + isFunded: destinationBalances.isFunded!, + }, + transactionFee, + transactionTimeout, + networkDetails, + ); - await scanTx(transaction.build().toXDR(), url, networkDetails); + await scanTx(transaction.build().toXDR(), url, networkDetails); + } + setLoading(false); }; if (isToken || isSoroswap) { scanSorobanTx(); diff --git a/extension/src/popup/components/sendPayment/SendConfirm/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/index.tsx index 02ec3a732..fcfc5c988 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/index.tsx @@ -24,6 +24,7 @@ export const SendConfirm = ({ previous }: { previous: ROUTES }) => { if (isSendComplete) { return ( { dispatch(resetSubmission()); navigateTo(ROUTES.accountHistory); @@ -33,15 +34,30 @@ export const SendConfirm = ({ previous }: { previous: ROUTES }) => { } switch (submission.submitStatus) { case ActionStatus.IDLE: - return navigateTo(previous)} />; + return ( + navigateTo(previous)} + /> + ); case ActionStatus.PENDING: - return navigateTo(previous)} />; + return ( + navigateTo(previous)} + /> + ); case ActionStatus.SUCCESS: return setIsSendComplete(true)} />; case ActionStatus.ERROR: return ; default: - return navigateTo(previous)} />; + return ( + navigateTo(previous)} + /> + ); } }; diff --git a/extension/src/popup/helpers/blockaid.ts b/extension/src/popup/helpers/blockaid.ts index 0ce50bec5..aa4aa5f67 100644 --- a/extension/src/popup/helpers/blockaid.ts +++ b/extension/src/popup/helpers/blockaid.ts @@ -106,6 +106,7 @@ export const useScanTx = () => { data, error, isLoading, + setLoading, scanTx, }; }; diff --git a/extension/src/popup/helpers/soroban.ts b/extension/src/popup/helpers/soroban.ts index 70a98b150..3d6aaf415 100644 --- a/extension/src/popup/helpers/soroban.ts +++ b/extension/src/popup/helpers/soroban.ts @@ -111,6 +111,13 @@ export const parseTokenAmount = (value: string, decimals: number) => { return wholeValue.shiftedBy(decimals).plus(fractionValue); }; +export const addressToString = (address: xdr.ScAddress) => { + if (address.switch().name === "scAddressTypeAccount") { + return StrKey.encodeEd25519PublicKey(address.accountId().ed25519()); + } + return StrKey.encodeContract(address.contractId()); +}; + export const getArgsForTokenInvocation = ( fnName: string, args: xdr.ScVal[], @@ -121,18 +128,12 @@ export const getArgsForTokenInvocation = ( switch (fnName) { case SorobanTokenInterface.transfer: - from = StrKey.encodeEd25519PublicKey( - args[0].address().accountId().ed25519(), - ); - to = StrKey.encodeEd25519PublicKey( - args[1].address().accountId().ed25519(), - ); + from = addressToString(args[0].address()); + to = addressToString(args[1].address()); amount = scValToNative(args[2]); break; case SorobanTokenInterface.mint: - to = StrKey.encodeEd25519PublicKey( - args[0].address().accountId().ed25519(), - ); + to = addressToString(args[0].address()); amount = scValToNative(args[1]); break; default: diff --git a/extension/src/popup/views/__tests__/SendPayment.test.tsx b/extension/src/popup/views/__tests__/SendPayment.test.tsx index fd8693f0f..4f1fe1e5f 100644 --- a/extension/src/popup/views/__tests__/SendPayment.test.tsx +++ b/extension/src/popup/views/__tests__/SendPayment.test.tsx @@ -40,6 +40,7 @@ jest.spyOn(BlockaidHelpers, "useScanTx").mockImplementation(() => { return { scanTx: () => Promise.resolve(null), isLoading: false, + setLoading: () => {}, data: null, error: null, }; diff --git a/extension/src/popup/views/__tests__/Swap.test.tsx b/extension/src/popup/views/__tests__/Swap.test.tsx index 094a1f1e7..387522612 100644 --- a/extension/src/popup/views/__tests__/Swap.test.tsx +++ b/extension/src/popup/views/__tests__/Swap.test.tsx @@ -111,6 +111,7 @@ jest.spyOn(BlockaidHelpers, "useScanTx").mockImplementation(() => { return { scanTx: () => Promise.resolve(null), isLoading: false, + setLoading: () => {}, data: null, error: null, }; diff --git a/package.json b/package.json index cd3b9a609..bea41a107 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "start:unpacked": "yarn workspace extension start:unpacked-extension", "install-if-package-changed": "git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD | grep --quiet yarn.lock && yarn setup || exit 0", "test:ci": "jest --ci", - "test:e2e": "yarn workspace extension test:e2e", + "test:e2e": "yarn workspace extension test:e2e --workers=1", "test": "jest -o --watch", "prepare": "husky install" },