--- name: sign-message description: Implement message signing with Phantom Connect SDK for Solana and EVM, including Sign-in with Solana (SIWS) authentication --- # Sign Message ## When to use - User asks to sign a message - User wants to implement SIWS (Sign-in with Solana) - User wants to verify wallet ownership or add wallet-based authentication ## Workflow ### Step 1: Sign a Message on Solana (React SDK) ```tsx import { useSolana } from "@phantom/react-sdk"; function SignMessage() { const solana = useSolana(); const handleSign = async () => { const message = new TextEncoder().encode("Hello from my dApp!"); try { const { signature } = await solana.signMessage(message); console.log("Signature:", signature); } catch (error) { console.error("Signing failed:", error); } }; return ; } ``` ### Step 2: Sign a Message on Solana (Browser SDK) ```ts async function signMessage(sdk: BrowserSDK) { const message = new TextEncoder().encode("Hello from my dApp!"); try { const { signature } = await sdk.solana.signMessage(message); console.log("Signature:", signature); } catch (error) { console.error("Signing failed:", error); } } ``` ### Step 3: Sign a Message on EVM For Ethereum/EVM chains, use `signPersonalMessage`: ```tsx import { useEthereum } from "@phantom/react-sdk"; function SignEVMMessage() { const ethereum = useEthereum(); const handleSign = async () => { try { const { signature } = await ethereum.signPersonalMessage("Hello from my dApp!"); console.log("Signature:", signature); } catch (error) { console.error("Signing failed:", error); } }; return ; } ``` ### Step 4: Sign-in with Solana (SIWS) SIWS provides a standardized authentication flow — proving wallet ownership to your backend: ```tsx import { useSolana, useAccounts } from "@phantom/react-sdk"; function SignInWithSolana() { const solana = useSolana(); const { accounts } = useAccounts(); const handleSIWS = async () => { const solanaAccount = accounts.find((a) => a.chain === "solana"); if (!solanaAccount) return; // Construct the SIWS message const domain = window.location.host; const uri = window.location.origin; const nonce = await fetch("/api/auth/nonce").then((r) => r.text()); // Must be server-issued const issuedAt = new Date().toISOString(); const message = [ `${domain} wants you to sign in with your Solana account:`, solanaAccount.address, "", "Sign in to access your account.", "", `URI: ${uri}`, `Version: 1`, `Nonce: ${nonce}`, `Issued At: ${issuedAt}`, ].join("\n"); try { const { signature } = await solana.signMessage( new TextEncoder().encode(message) ); // Send signature + message to your backend for verification const response = await fetch("/api/auth/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, signature, publicKey: solanaAccount.address, }), }); if (response.ok) { console.log("Authenticated successfully!"); } } catch (error) { console.error("SIWS failed:", error); } }; return ; } ``` ## API Reference | Chain | Method | Input | Output | | -------- | ------------------------------- | -------------------- | ------------- | | Solana | `solana.signMessage(bytes)` | `Uint8Array` | `{ signature }` | | Ethereum | `ethereum.signPersonalMessage(msg)` | `string` | `{ signature }` | ## Important Notes - Solana `signMessage` expects a `Uint8Array` — use `new TextEncoder().encode(...)` to convert strings. - EVM `signPersonalMessage` accepts a plain string. - For SIWS, the nonce **must** be issued by your backend, stored server-side, and validated as single-use on verification. A client-generated nonce provides no replay protection. - Always wrap signing calls in try-catch to handle user rejections.