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..1e74c72d 100644
--- a/frontend/src/components/stream-creation/StreamCreationWizard.tsx
+++ b/frontend/src/components/stream-creation/StreamCreationWizard.tsx
@@ -19,7 +19,10 @@ import {
useStreamForm,
type StreamFormData,
} from "@/hooks/useStreamForm";
-import { validateAmount } from "@/lib/stream-validation";
+import {
+ SAME_SENDER_RECIPIENT_ERROR,
+ validateAmount,
+} from "@/lib/stream-validation";
// Re-export StreamFormData so existing imports keep working
export type { StreamFormData };
@@ -97,6 +100,15 @@ export const StreamCreationWizard: React.FC = ({
const amountValid = amountError === null;
const canSubmit = !isSubmitting && amountValid;
+ const isSelfRecipient =
+ Boolean(walletPublicKey?.trim()) &&
+ formData.recipient.trim().length > 0 &&
+ formData.recipient.trim().toLowerCase() ===
+ walletPublicKey!.trim().toLowerCase();
+ const recipientError =
+ errors.recipient ??
+ (isSelfRecipient ? SAME_SENDER_RECIPIENT_ERROR : undefined);
+
const handleApplyTemplate = (templateId: string) => {
const msg = applyTemplateHook(templateId);
if (msg) setTemplateSaveMessage(msg);
@@ -260,7 +272,8 @@ export const StreamCreationWizard: React.FC = ({
updateFormData({ recipient: value })}
- error={errors.recipient}
+ error={recipientError}
+ senderAddress={walletPublicKey}
/>
);
case 3:
@@ -469,7 +482,10 @@ export const StreamCreationWizard: React.FC = ({
Cancel
{currentStep < STEPS.length ? (
-