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}
-
-
{`${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}
+
+ {`${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++;
},
);