From 44a0239df2205ab6299e9c0e6d188d6d72e42eb2 Mon Sep 17 00:00:00 2001 From: Tomas R Date: Wed, 3 Jun 2026 19:24:10 -0300 Subject: [PATCH] fix: emit EIP-4361-compliant SIWX message for EVM in Reown Cloud Auth Phantom (and other strict EIP-4361 wallets) rejected the Reown Cloud Auth sign-in message on EVM with "invalid formatting" and never showed the prompt. The message used the CAIP-2 chain id (`Chain ID: eip155:1`) instead of the decimal EIP-155 id (`Chain ID: 1`), and emitted a single blank line after the address instead of the two EIP-4361 requires for the empty statement block. ReownAuthenticationMessenger now emits the decimal chain id and the correct blank-line structure for the EVM (eip155) namespace only; non-EVM messages (Solana, Bitcoin, ...) are byte-for-byte unchanged. Co-Authored-By: Claude Opus 4.8 --- .changeset/siwx-eip4361-message-format.md | 7 +++++++ .../ReownAuthenticationMessenger.ts | 21 ++++++++++++++++--- .../features/ReownAuthentication.test.ts | 5 +++-- 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 .changeset/siwx-eip4361-message-format.md diff --git a/.changeset/siwx-eip4361-message-format.md b/.changeset/siwx-eip4361-message-format.md new file mode 100644 index 0000000000..5cbf124b27 --- /dev/null +++ b/.changeset/siwx-eip4361-message-format.md @@ -0,0 +1,7 @@ +--- +'@reown/appkit-controllers': patch +--- + +Fix SIWX sign-in failing on EVM with Phantom under Reown Cloud Auth ("The app's signature request cannot be shown due to invalid formatting"). The `ReownAuthentication` SIWE message was not EIP-4361-compliant for EVM: with no statement it emitted a single blank line after the address instead of the two the spec requires (the empty statement block must stay delimited), and it used the CAIP-2 chain id (`Chain ID: eip155:1`) instead of the decimal EIP-155 id (`Chain ID: 1`). Strict wallets like Phantom rejected the message before showing the prompt; lenient wallets (MetaMask) signed it anyway. + +`ReownAuthenticationMessenger` now emits the compliant blank-line structure and decimal `Chain ID` for EVM. The change is scoped to the EVM (`eip155`) namespace only — non-EVM messages (Solana, Bitcoin, ...) are byte-for-byte unchanged. The Reown Cloud Auth backend must accept the compliant message (it validates the message server-side). diff --git a/packages/controllers/src/features/siwx/reown-authentication/ReownAuthenticationMessenger.ts b/packages/controllers/src/features/siwx/reown-authentication/ReownAuthenticationMessenger.ts index 6916f0c117..24a80108b6 100644 --- a/packages/controllers/src/features/siwx/reown-authentication/ReownAuthenticationMessenger.ts +++ b/packages/controllers/src/features/siwx/reown-authentication/ReownAuthenticationMessenger.ts @@ -1,4 +1,4 @@ -import { type CaipNetworkId, NetworkUtil } from '@reown/appkit-common' +import { type CaipNetworkId, ConstantsUtil, NetworkUtil } from '@reown/appkit-common' import { ChainController } from '../../../controllers/ChainController.js' import type { SIWXMessage } from '../../../utils/SIWXUtil.js' @@ -35,15 +35,30 @@ export class ReownAuthenticationMessenger { } private stringify(params: SIWXMessage.Data): string { + const [namespace, reference] = params.chainId.split(':') + const isEvm = namespace === ConstantsUtil.CHAIN.EVM const networkName = this.getNetworkName(params.chainId) + /* + * EIP-4361 requires the decimal EIP-155 chain id for EVM (e.g. "1"), not the CAIP-2 id + * ("eip155:1"). Strict wallet parsers (e.g. Phantom) reject the CAIP form as "invalid + * formatting". EVM-only — non-EVM namespaces keep their CAIP-2 id. + */ + const chainId = isEvm ? reference : params.chainId return [ `${params.domain} wants you to sign in with your ${networkName} account:`, params.accountAddress, - params.statement ? `\n${params.statement}\n` : '', + /* + * EIP-4361 (EVM) places the optional statement between two blank lines, so a + * statement-less message must still emit the empty statement's blank line — otherwise + * there is a single blank line and strict parsers (e.g. Phantom) reject the message. + * This mirrors the reference `siwe` serializer. EVM-only, so non-EVM message formats + * (Solana, Bitcoin, ...) stay byte-for-byte unchanged. + */ + params.statement ? `\n${params.statement}\n` : isEvm ? '\n' : '', `URI: ${params.uri}`, `Version: ${params.version}`, - `Chain ID: ${params.chainId}`, + `Chain ID: ${chainId}`, `Nonce: ${params.nonce}`, params.issuedAt && `Issued At: ${params.issuedAt}`, params.expirationTime && `Expiration Time: ${params.expirationTime}`, diff --git a/packages/controllers/tests/features/ReownAuthentication.test.ts b/packages/controllers/tests/features/ReownAuthentication.test.ts index 5206f62ae4..283ed77c91 100644 --- a/packages/controllers/tests/features/ReownAuthentication.test.ts +++ b/packages/controllers/tests/features/ReownAuthentication.test.ts @@ -154,13 +154,14 @@ describe.each([ `${namespace}:${id}` ) + // EVM (EIP-4361) uses the decimal chain id + two blank lines; non-EVM is unchanged expect(message.toString()) .toBe(`mocked.com wants you to sign in with your ${networkName} account: ${address} - +${namespace === 'eip155' ? '\n' : ''} URI: http://mocked.com/ Version: 1 -Chain ID: ${namespace}:${id} +Chain ID: ${namespace === 'eip155' ? id : `${namespace}:${id}`} Nonce: mock_nonce Issued At: 2024-12-05T16:02:32.905Z`)