From bf8f54cab9631eaeac576d988bc5fc71908411ea Mon Sep 17 00:00:00 2001 From: Abdullah Azmi Date: Mon, 19 Oct 2026 04:19:23 +0000 Subject: [PATCH 1/3] feat(frontend): prevent self-streaming when recipient matches sender wallet in StreamCreationWizard --- frontend/src/__tests__/components.test.tsx | 13 +++++++++ .../stream-creation/RecipientStep.tsx | 29 ++++++++++++++++--- .../stream-creation/StreamCreationWizard.tsx | 6 +++- .../lib/__tests__/stream-validation.test.ts | 22 ++++++++++++++ frontend/src/lib/stream-validation.ts | 17 +++++++++-- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/frontend/src/__tests__/components.test.tsx b/frontend/src/__tests__/components.test.tsx index d8a4a57c..870ee22d 100644 --- a/frontend/src/__tests__/components.test.tsx +++ b/frontend/src/__tests__/components.test.tsx @@ -146,6 +146,19 @@ describe('RecipientStep', () => { fireEvent.blur(screen.getByRole('textbox')); expect(onChange).toHaveBeenCalledWith(VALID_KEY); }); + + it('shows immediate inline error when recipient equals connected sender wallet address', () => { + render( + , + ); + expect(screen.getByRole('alert')).toHaveTextContent( + 'Recipient address cannot be the same as your connected sender wallet', + ); + }); }); // ─── AmountStep ────────────────────────────────────────────────────────────── diff --git a/frontend/src/components/stream-creation/RecipientStep.tsx b/frontend/src/components/stream-creation/RecipientStep.tsx index 8e91074d..ebe71d77 100644 --- a/frontend/src/components/stream-creation/RecipientStep.tsx +++ b/frontend/src/components/stream-creation/RecipientStep.tsx @@ -6,17 +6,32 @@ interface RecipientStepProps { value: string; onChange: (value: string) => void; error?: string; + senderAddress?: string | null; } const STELLAR_PUBLIC_KEY_LENGTH = 56; +function getPersistedWalletPublicKey(): string | undefined { + if (typeof window === "undefined") return undefined; + try { + const raw = window.localStorage.getItem("flowfi.wallet.session.v1"); + if (!raw) return undefined; + const parsed = JSON.parse(raw) as { publicKey?: unknown }; + return typeof parsed?.publicKey === "string" ? parsed.publicKey : undefined; + } catch { + return undefined; + } +} + export const RecipientStep: React.FC = ({ value, onChange, error, + senderAddress, }) => { const inputRef = useRef(null); const [touched, setTouched] = useState(false); + const effectiveSenderAddress = senderAddress ?? getPersistedWalletPublicKey(); // Auto-focus on mount useEffect(() => { @@ -24,13 +39,19 @@ export const RecipientStep: React.FC = ({ }, []); const trimmed = value.trim(); + const normalizedSender = effectiveSenderAddress?.trim(); + const isSameAsSender = + Boolean(normalizedSender) && + trimmed.length > 0 && + trimmed.toLowerCase() === normalizedSender!.toLowerCase(); // Real-time feedback: validate once the user has committed the field - // (blur / paste) or entered a full-length key, so partial typing does not - // flash errors while still catching bad checksums immediately. + // (blur / paste), entered a full-length key, or matched the connected sender, + // so partial typing does not flash errors while still catching bad checksums + // and self-streams immediately. const liveError = - touched || trimmed.length >= STELLAR_PUBLIC_KEY_LENGTH - ? validateRecipient(value) + touched || trimmed.length >= STELLAR_PUBLIC_KEY_LENGTH || isSameAsSender + ? validateRecipient(value, effectiveSenderAddress) : null; // The wizard-provided error (set on step validation) takes precedence. diff --git a/frontend/src/components/stream-creation/StreamCreationWizard.tsx b/frontend/src/components/stream-creation/StreamCreationWizard.tsx index f6b259cf..514a5180 100644 --- a/frontend/src/components/stream-creation/StreamCreationWizard.tsx +++ b/frontend/src/components/stream-creation/StreamCreationWizard.tsx @@ -261,6 +261,7 @@ export const StreamCreationWizard: React.FC = ({ value={formData.recipient} onChange={(value) => updateFormData({ recipient: value })} error={errors.recipient} + senderAddress={walletPublicKey} /> ); case 3: @@ -469,7 +470,10 @@ export const StreamCreationWizard: React.FC = ({ Cancel {currentStep < STEPS.length ? ( -