diff --git a/src/app/(sidebar)/account/fund/page.tsx b/src/app/(sidebar)/account/fund/page.tsx index 01b91566c..3c79c2dcf 100644 --- a/src/app/(sidebar)/account/fund/page.tsx +++ b/src/app/(sidebar)/account/fund/page.tsx @@ -389,102 +389,124 @@ export default function FundAccount() { Choose asset to fund - - } - size="sm" - > - What is a trustline? -
- {fundTokens.map((t) => { - const asset = `${t.assetCode}:${t.assetIssuer}`; - const hasTrustline = Boolean(accountBalances?.[asset]); - - return ( -
-
- {`${t.label} -
- -
- - {t.label} - - {`${formatNumber(parseFloat(t.amount))} ${t.currency}`} + {fundTokens + .filter((t) => { + // Hide XLM if the account is already funded + if (t.id === "xlm" && accountInfo?.isFunded) { + return false; + } + return true; + }) + .map((t) => { + const asset = `${t.assetCode}:${t.assetIssuer}`; + const hasTrustline = Boolean(accountBalances?.[asset]); + + return ( +
+
+ {`${t.label} +
+ +
+ + {t.label} + + {`${formatNumber(parseFloat(t.amount))} ${t.currency}`} +
+ + {t.id === "xlm" || hasTrustline ? ( + // Fund button + + ) : ( + // Add trustline button + + )}
- - {t.id === "xlm" || hasTrustline ? ( - // Fund button - - ) : ( - // Add trustline button - - )} -
- ); - })} + ); + })}
+ + + + A trustline lets your account hold and receive this asset. Your + address must have XLM to submit the transaction.{" "} + } + size="xs" + > + What is a trustline? + + + {isFriendBotFetchedAfterMount && friendBotError ? ( diff --git a/src/components/SignTransactionXdr/index.tsx b/src/components/SignTransactionXdr/index.tsx index 097cfd86f..0f6ab1595 100644 --- a/src/components/SignTransactionXdr/index.tsx +++ b/src/components/SignTransactionXdr/index.tsx @@ -230,7 +230,6 @@ export const SignTransactionXdr = ({ signatureSigs = sigSignature; } - const tx = TransactionBuilder.fromXDR(xdrToSign, network.passphrase); const allSigs = [ ...secretKeySigs, ...hardwareSigs, @@ -248,9 +247,29 @@ export const SignTransactionXdr = ({ setAllSigsCount(allSigsCount); if (allSigs.length > 0) { - tx.signatures.push(...allSigs); + // When only the extension wallet signed, use its signed XDR directly. + // This avoids issues where wallets may modify the transaction + // (e.g., fee adjustments), making the extracted signature invalid + // for the original unsigned transaction. + const isExtensionWalletOnly = + extensionSigs.length > 0 && + secretKeySigs.length === 0 && + hardwareSigs.length === 0 && + signatureSigs.length === 0; + + let signedTx: string; + + if (isExtensionWalletOnly && exSignedTxXdr) { + signedTx = exSignedTxXdr; + } else { + const tx = TransactionBuilder.fromXDR( + xdrToSign, + network.passphrase, + ); + tx.signatures.push(...allSigs); + signedTx = tx.toEnvelope().toXDR("base64"); + } - const signedTx = tx.toEnvelope().toXDR("base64"); onDoneAction({ signedXdr: signedTx, successMessage: getAllSigsMessage(allSigsCount), diff --git a/tests/e2e/fundAccountPage.test.ts b/tests/e2e/fundAccountPage.test.ts index 717926d3c..59e5b122f 100644 --- a/tests/e2e/fundAccountPage.test.ts +++ b/tests/e2e/fundAccountPage.test.ts @@ -141,6 +141,7 @@ test.describe("[futurenet/testnet] Fund Account Page", () => { const tokens = page.getByTestId("fund-account-token"); + // Before funding: XLM is at index 0, USDC at 1, EURC at 2 const fundXlmButton = tokens.nth(0).getByRole("button"); const fundUsdcButton = tokens.nth(1).getByRole("button"); const fundEurcButton = tokens.nth(2).getByRole("button"); @@ -169,14 +170,18 @@ test.describe("[futurenet/testnet] Fund Account Page", () => { ), ).toBeVisible(); - await expect(fundUsdcButton).toBeEnabled(); - await expect(fundEurcButton).toBeEnabled(); + // After funding XLM, the XLM token is hidden. USDC is now at index 0, EURC at 1. + const fundUsdcButtonAfterFund = tokens.nth(0).getByRole("button"); + const fundEurcButtonAfterFund = tokens.nth(1).getByRole("button"); + + await expect(fundUsdcButtonAfterFund).toBeEnabled(); + await expect(fundEurcButtonAfterFund).toBeEnabled(); // Add USDC trustline const signTxBox = page.getByTestId("sign-tx-xdr-fund-account-sign-tx"); await expect(signTxBox).toBeHidden(); - await fundUsdcButton.click(); + await fundUsdcButtonAfterFund.click(); await expect(signTxBox).toBeVisible(); const signTxButton = signTxBox @@ -204,8 +209,8 @@ test.describe("[futurenet/testnet] Fund Account Page", () => { `USDC trustline has been successfully added to ${TEST_ACCOUNT_PUBLICK_KEY_SHORT} on Testnet.`, ), ).toBeVisible(); - await expect(fundUsdcButton).toHaveText("Fund"); - await expect(fundEurcButton).toHaveText("Add trustline"); + await expect(fundUsdcButtonAfterFund).toHaveText("Fund"); + await expect(fundEurcButtonAfterFund).toHaveText("Add trustline"); }); test("Load funded account wiht USDC trustline", async ({ page }) => { @@ -225,6 +230,7 @@ test.describe("[futurenet/testnet] Fund Account Page", () => { const tokens = page.getByTestId("fund-account-token"); + // Before entering key: all 3 tokens visible (XLM at 0, USDC at 1, EURC at 2) const fundXlmButton = tokens.nth(0).getByRole("button"); const fundUsdcButton = tokens.nth(1).getByRole("button"); const fundEurcButton = tokens.nth(2).getByRole("button"); @@ -243,13 +249,16 @@ test.describe("[futurenet/testnet] Fund Account Page", () => { await publicKeyInput.fill(TEST_ACCOUNT_PUBLIC_KEY); await publicKeyInput.blur(); - await expect(fundXlmButton).toBeEnabled(); + // After loading a funded account with USDC trustline, XLM is hidden. + // USDC is now at index 0, EURC at index 1. + const fundUsdcButtonAfterLoad = tokens.nth(0).getByRole("button"); + const fundEurcButtonAfterLoad = tokens.nth(1).getByRole("button"); - await expect(fundUsdcButton).toBeEnabled(); - await expect(fundUsdcButton).toHaveText("Fund"); + await expect(fundUsdcButtonAfterLoad).toBeEnabled(); + await expect(fundUsdcButtonAfterLoad).toHaveText("Fund"); - await expect(fundEurcButton).toBeEnabled(); - await expect(fundEurcButton).toHaveText("Add trustline"); + await expect(fundEurcButtonAfterLoad).toBeEnabled(); + await expect(fundEurcButtonAfterLoad).toHaveText("Add trustline"); }); test("Fund muxed account with XLM", async ({ page }) => { @@ -313,15 +322,28 @@ test.describe("[futurenet/testnet] Fund Account Page", () => { }); test("Error when funding already funded account", async ({ page }) => { - // Mock accounts API call + // Mock accounts API call - return unfunded first so XLM button is visible, + // then funded after the fund attempt + let accountsCallCount = 0; + await page.route( `*/**/accounts/${TEST_ACCOUNT_PUBLIC_KEY}`, async (route) => { - await route.fulfill({ - status: 200, - contentType: "application/hal+json; charset=utf-8", - body: JSON.stringify(MOCK_ACCOUNT_FUNDED_RESPONSE), - }); + if (accountsCallCount === 0) { + await route.fulfill({ + status: 200, + contentType: "application/hal+json; charset=utf-8", + body: JSON.stringify(MOCK_ACCOUNT_UNFUNDED_RESPONSE), + }); + } else { + await route.fulfill({ + status: 200, + contentType: "application/hal+json; charset=utf-8", + body: JSON.stringify(MOCK_ACCOUNT_FUNDED_RESPONSE), + }); + } + + accountsCallCount++; }, );