diff --git a/__tests__/components/screens/WalletKit/DappMessageDisplay.test.tsx b/__tests__/components/screens/WalletKit/DappMessageDisplay.test.tsx
new file mode 100644
index 000000000..3948ed817
--- /dev/null
+++ b/__tests__/components/screens/WalletKit/DappMessageDisplay.test.tsx
@@ -0,0 +1,65 @@
+import { DappMessageDisplay } from "components/screens/WalletKit/DappMessageDisplay";
+import { renderWithProviders } from "helpers/testUtils";
+import React from "react";
+import { Dimensions, StyleSheet } from "react-native";
+
+jest.mock("hooks/useAppTranslation", () => ({
+ __esModule: true,
+ default: () => ({ t: (key: string) => key }),
+}));
+
+describe("DappMessageDisplay", () => {
+ it("renders the message content", () => {
+ const { getByTestId } = renderWithProviders(
+ ,
+ );
+
+ expect(getByTestId("message-display-content")).toHaveTextContent(
+ "hello world",
+ );
+ });
+
+ it("pretty-prints JSON messages", () => {
+ const { getByTestId } = renderWithProviders(
+ ,
+ );
+
+ expect(getByTestId("message-display-content").props.children).toBe(
+ JSON.stringify({ a: 1 }, null, 2),
+ );
+ });
+
+ it("bounds the message scroll area so long messages cannot push the action buttons off-screen", () => {
+ const { getByTestId } = renderWithProviders(
+ ,
+ );
+
+ const scrollView = getByTestId("message-display-content-scroll");
+ const style = StyleSheet.flatten(scrollView.props.style);
+
+ expect(style.maxHeight).toBeDefined();
+ expect(style.maxHeight).toBeLessThanOrEqual(
+ Dimensions.get("window").height * 0.5,
+ );
+ });
+
+ it("can shrink below the cap (with a usable floor) when the sheet needs the room", () => {
+ // With a security banner + stacked warning buttons the sheet's fixed
+ // content grows; the message box must yield height so the action buttons
+ // stay on screen, while keeping a scrollable sliver of the message.
+ const { getByTestId } = renderWithProviders(
+ ,
+ );
+
+ const boxStyle = StyleSheet.flatten(
+ getByTestId("message-display").props.style,
+ );
+ const scrollStyle = StyleSheet.flatten(
+ getByTestId("message-display-content-scroll").props.style,
+ );
+
+ expect(boxStyle.flexShrink).toBe(1);
+ expect(scrollStyle.flexShrink).toBe(1);
+ expect(scrollStyle.minHeight).toBeGreaterThan(0);
+ });
+});
diff --git a/__tests__/helpers/walletKitValidation.test.ts b/__tests__/helpers/walletKitValidation.test.ts
index 8ecb0ca6b..6d663bda3 100644
--- a/__tests__/helpers/walletKitValidation.test.ts
+++ b/__tests__/helpers/walletKitValidation.test.ts
@@ -148,13 +148,22 @@ describe("validateSignMessageLength", () => {
}
});
- it("returns valid for a message exactly at the 1 KB limit", () => {
+ it("allows a multi-KB JSON payload (limit is 10 KB, not 1 KB)", () => {
+ // Regression for stellar/freighter-mobile#957: dApps sign JSON payloads
+ // that routinely exceed 1 KB. SEP-53 imposes no size limit.
+ expect(SIGN_MESSAGE_MAX_BYTES).toBe(10240);
+ const message = JSON.stringify({ data: "a".repeat(4000) });
+ const result = validateSignMessageLength(message);
+ expect(result.valid).toBe(true);
+ });
+
+ it("returns valid for a message exactly at the limit", () => {
const message = "a".repeat(SIGN_MESSAGE_MAX_BYTES);
const result = validateSignMessageLength(message);
expect(result.valid).toBe(true);
});
- it("returns error for a message exceeding 1 KB (ASCII)", () => {
+ it("returns error for a message exceeding the limit (ASCII)", () => {
const message = "a".repeat(SIGN_MESSAGE_MAX_BYTES + 1);
const result = validateSignMessageLength(message);
expect(result.valid).toBe(false);
@@ -163,9 +172,9 @@ describe("validateSignMessageLength", () => {
}
});
- it("returns error for a message exceeding 1 KB due to multi-byte UTF-8 chars", () => {
- // Each emoji is 4 bytes in UTF-8 β 257 emojis = 1028 bytes > 1024
- const message = "π".repeat(257);
+ it("returns error for a message exceeding the limit due to multi-byte UTF-8 chars", () => {
+ // Each emoji is 4 bytes in UTF-8, so this is 4 bytes over the limit
+ const message = "π".repeat(SIGN_MESSAGE_MAX_BYTES / 4 + 1);
const result = validateSignMessageLength(message);
expect(result.valid).toBe(false);
if (!result.valid) {
@@ -173,9 +182,9 @@ describe("validateSignMessageLength", () => {
}
});
- it("returns valid for multi-byte chars that stay within 1 KB", () => {
- // 256 emojis = 1024 bytes β exactly at the limit
- const message = "π".repeat(256);
+ it("returns valid for multi-byte chars that stay within the limit", () => {
+ // Each emoji is 4 bytes in UTF-8 β exactly at the limit
+ const message = "π".repeat(SIGN_MESSAGE_MAX_BYTES / 4);
const result = validateSignMessageLength(message);
expect(result.valid).toBe(true);
});
diff --git a/src/components/screens/WalletKit/DappMessageDisplay.tsx b/src/components/screens/WalletKit/DappMessageDisplay.tsx
index 8f794c45f..700923b24 100644
--- a/src/components/screens/WalletKit/DappMessageDisplay.tsx
+++ b/src/components/screens/WalletKit/DappMessageDisplay.tsx
@@ -3,7 +3,7 @@ import { Text } from "components/sds/Typography";
import useColors from "hooks/useColors";
import React from "react";
import { useTranslation } from "react-i18next";
-import { ScrollView, View } from "react-native";
+import { Dimensions, ScrollView, View } from "react-native";
/**
* Props for the DappMessageDisplay component
@@ -28,7 +28,7 @@ const isJsonString = (str: string): boolean => {
/**
* DappMessageDisplay component for showing SEP-53 messages
* Displays the message with the SEP-53 prefix and handles JSON formatting
- * Dynamically increases height based on message length
+ * Grows with the message up to a capped height, then scrolls
*
* @component
* @param {DappMessageDisplayProps} props - The component props
@@ -49,7 +49,13 @@ export const DappMessageDisplay: React.FC = ({
return (
@@ -63,7 +69,19 @@ export const DappMessageDisplay: React.FC = ({
{t("common.message")}
-
+
{
const { themeColors } = useColors();
const { t } = useAppTranslation();
+ const { height: windowHeight } = useWindowDimensions();
+ const insets = useSafeAreaInsets();
+ const maxContentHeight =
+ windowHeight - insets.top - insets.bottom - SHEET_CHROME_HEIGHT;
const accountList = useMemo(
() => [
@@ -89,6 +102,7 @@ export const DappSignMessageBottomSheetContent: React.FC<
return (
@@ -112,7 +126,9 @@ export const DappSignMessageBottomSheetContent: React.FC<
securityWarningAction={securityWarningAction}
/>
-
+ {/* flexShrink lets the message box inside absorb the squeeze when the
+ sheet is taller than the screen; the account list keeps its size. */}
+
diff --git a/src/helpers/walletKitValidation.ts b/src/helpers/walletKitValidation.ts
index b5458b7c9..afd981918 100644
--- a/src/helpers/walletKitValidation.ts
+++ b/src/helpers/walletKitValidation.ts
@@ -16,8 +16,13 @@ export const ValidationErrorKeys = {
AUTH_ENTRY_ADDRESS_MISMATCH: "walletKit.errorAuthEntryAddressMismatch",
} as const;
-/** Max UTF-8 byte length for sign_message content per SEP-53. */
-export const SIGN_MESSAGE_MAX_BYTES = 1024;
+/**
+ * Max UTF-8 byte length for sign_message content. SEP-53 imposes no size
+ * limit (and the browser extension enforces none) β this is a sanity cap
+ * against absurd WalletConnect payloads, sized to comfortably fit the JSON
+ * payloads dApps actually sign (see stellar/freighter-mobile#957).
+ */
+export const SIGN_MESSAGE_MAX_BYTES = 10240;
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Types
@@ -46,7 +51,7 @@ export function validateSignMessageContent(
}
/**
- * Validates sign_message length: max 1KB UTF-8 bytes per SEP-53.
+ * Validates sign_message length against SIGN_MESSAGE_MAX_BYTES (UTF-8 bytes).
*/
export function validateSignMessageLength(
message: string,
diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json
index af0e19741..89df5d691 100644
--- a/src/i18n/locales/en/translations.json
+++ b/src/i18n/locales/en/translations.json
@@ -895,7 +895,7 @@
"errorAuthEntryNetworkMismatch": "Authorization entry is for a different network",
"errorAuthEntryAddressMismatch": "Authorization entry is bound to a different account",
"errorEmptyMessage": "Cannot sign empty message",
- "errorMessageTooLong": "Message too long (max 1KB)",
+ "errorMessageTooLong": "Message too long (max 10KB)",
"errorSubmitting": "Failed to submit transaction",
"errorRespondingRequest": "Failed to respond transaction request",
"errorWrongNetwork": "Wrong Network",
diff --git a/src/i18n/locales/pt/translations.json b/src/i18n/locales/pt/translations.json
index 3f15659c3..5291d6478 100644
--- a/src/i18n/locales/pt/translations.json
+++ b/src/i18n/locales/pt/translations.json
@@ -858,7 +858,7 @@
"errorAuthEntryNetworkMismatch": "A entrada de autorizaΓ§Γ£o Γ© para uma rede diferente",
"errorAuthEntryAddressMismatch": "A entrada de autorizaΓ§Γ£o estΓ‘ vinculada a uma conta diferente",
"errorEmptyMessage": "NΓ£o Γ© possΓvel assinar mensagem vazia",
- "errorMessageTooLong": "Mensagem muito longa (mΓ‘ximo 1KB)",
+ "errorMessageTooLong": "Mensagem muito longa (mΓ‘ximo 10KB)",
"errorSubmitting": "Erro ao enviar a transaΓ§Γ£o",
"errorRespondingRequest": "Erro ao responder Γ solicitaΓ§Γ£o de transaΓ§Γ£o",
"errorWrongNetwork": "Rede incorreta",
diff --git a/src/providers/WalletKitProvider.tsx b/src/providers/WalletKitProvider.tsx
index c216380de..20666ff4f 100644
--- a/src/providers/WalletKitProvider.tsx
+++ b/src/providers/WalletKitProvider.tsx
@@ -724,7 +724,7 @@ export const WalletKitProvider: React.FC = ({
return false;
}
- // Step 2: Validate message length (1KB limit per SEP-53)
+ // Step 2: Validate message length (sanity cap; SEP-53 imposes no limit)
const lengthResult = validateSignMessageLength(contentResult.value);
if (!lengthResult.valid) {
showToast({