@@ -44,6 +44,17 @@ import {
4444} from '@/components/ui/select' ;
4545import { Checkbox } from '@/components/ui/checkbox' ;
4646import { Alert , AlertDescription , AlertTitle } from '@/components/ui/alert' ;
47+ import {
48+ AlertDialog ,
49+ AlertDialogAction ,
50+ AlertDialogCancel ,
51+ AlertDialogContent ,
52+ AlertDialogDescription ,
53+ AlertDialogFooter ,
54+ AlertDialogHeader ,
55+ AlertDialogTitle ,
56+ AlertDialogTrigger ,
57+ } from '@/components/ui/alert-dialog' ;
4758import type { ApiError } from '@/lib/api/api' ;
4859import { AssetIcon } from './AssetIcon' ;
4960import { cn } from '@/lib/utils' ;
@@ -88,8 +99,6 @@ export function FamilyWalletDrawer({
8899 const [ sendDestination , setSendDestination ] = useState ( '' ) ;
89100 const [ sendCurrency , setSendCurrency ] = useState ( '' ) ;
90101 const [ sendAmount , setSendAmount ] = useState ( '' ) ;
91- const [ sendMemo , setSendMemo ] = useState ( '' ) ;
92- const [ sendMemoRequired , setSendMemoRequired ] = useState ( false ) ;
93102 const [ validateLoading , setValidateLoading ] = useState ( false ) ;
94103 const [ validateResult , setValidateResult ] = useState <
95104 'idle' | 'valid' | 'invalid'
@@ -127,8 +136,6 @@ export function FamilyWalletDrawer({
127136 const resetSendForm = useCallback ( ( ) => {
128137 setSendDestination ( '' ) ;
129138 setSendAmount ( '' ) ;
130- setSendMemo ( '' ) ;
131- setSendMemoRequired ( false ) ;
132139 setValidateResult ( 'idle' ) ;
133140 setValidateError ( '' ) ;
134141 setValidateErrorDetails ( [ ] ) ;
@@ -297,24 +304,13 @@ export function FamilyWalletDrawer({
297304 ) ;
298305 return ;
299306 }
300- if ( sendMemoRequired && ! sendMemo . trim ( ) ) {
301- setSendError ( 'Memo is required by the recipient' ) ;
302- return ;
303- }
304- const memoBytes = new TextEncoder ( ) . encode ( sendMemo ) . length ;
305- if ( sendMemo && memoBytes > 28 ) {
306- setSendError ( 'Memo must be 28 bytes or less (UTF-8)' ) ;
307- return ;
308- }
309307 setSendLoading ( true ) ;
310308 setSendError ( '' ) ;
311309 try {
312310 await sendFunds ( {
313311 destinationPublicKey : dest ,
314312 amount,
315313 currency,
316- memo : sendMemo . trim ( ) || undefined ,
317- memoRequired : sendMemoRequired || undefined ,
318314 idempotencyKey : crypto . randomUUID ( ) ,
319315 } ) ;
320316 toast . success ( 'Send submitted successfully' ) ;
@@ -332,8 +328,6 @@ export function FamilyWalletDrawer({
332328 sendDestination ,
333329 sendCurrency ,
334330 sendAmount ,
335- sendMemo ,
336- sendMemoRequired ,
337331 validateResult ,
338332 balances ,
339333 refreshWallet ,
@@ -863,6 +857,30 @@ export function FamilyWalletDrawer({
863857 </ div >
864858
865859 < div className = 'space-y-4' >
860+ < Alert className = 'mx-1 border-orange-500/20 bg-orange-500/10 text-orange-600' >
861+ < AlertCircle className = 'h-4 w-4 text-orange-600' />
862+ < AlertTitle > Important Withdrawal Notice</ AlertTitle >
863+ < AlertDescription className = 'text-xs leading-relaxed' >
864+ < p className = 'mt-1 text-orange-700 dark:text-orange-400' >
865+ Do not withdraw directly to a Centralized Exchange
866+ (e.g., Binance, Coinbase) wallet. This wallet does
867+ not support memos, and your funds will be lost if
868+ you use one. You must use a self-custodial wallet
869+ that does not require a memo.
870+ </ p >
871+ < p className = 'mt-2' >
872+ < a
873+ href = 'https://docs.boundlessfi.xyz/how-to-guides/withdraw-funds'
874+ target = '_blank'
875+ rel = 'noopener noreferrer'
876+ className = 'font-semibold underline underline-offset-2 hover:text-orange-800 dark:hover:text-orange-300'
877+ >
878+ Read our withdrawal guide here.
879+ </ a >
880+ </ p >
881+ </ AlertDescription >
882+ </ Alert >
883+
866884 < div className = 'space-y-2' >
867885 < Label htmlFor = 'send-destination' >
868886 Destination (Stellar G...)
@@ -983,33 +1001,6 @@ export function FamilyWalletDrawer({
9831001 } ) ( ) }
9841002 </ div >
9851003
986- < div className = 'space-y-2' >
987- < Label htmlFor = 'send-memo' >
988- Memo (optional, max 28 bytes)
989- </ Label >
990- < Input
991- id = 'send-memo'
992- placeholder = 'Memo for exchange/deposit'
993- value = { sendMemo }
994- onChange = { e => setSendMemo ( e . target . value ) }
995- />
996- < div className = 'flex items-center gap-2' >
997- < Checkbox
998- id = 'send-memo-required'
999- checked = { sendMemoRequired }
1000- onCheckedChange = { c =>
1001- setSendMemoRequired ( c === true )
1002- }
1003- />
1004- < Label
1005- htmlFor = 'send-memo-required'
1006- className = 'text-muted-foreground cursor-pointer text-sm font-normal'
1007- >
1008- Memo required by recipient (e.g. exchange)
1009- </ Label >
1010- </ div >
1011- </ div >
1012-
10131004 { sendError && (
10141005 < Alert variant = 'destructive' className = 'mt-2' >
10151006 < AlertCircle className = 'h-4 w-4' />
@@ -1029,28 +1020,69 @@ export function FamilyWalletDrawer({
10291020 </ Alert >
10301021 ) }
10311022
1032- < Button
1033- className = 'w-full'
1034- onClick = { handleSendSubmit }
1035- disabled = {
1036- sendLoading ||
1037- validateResult !== 'valid' ||
1038- ! sendAmount ||
1039- parseFloat ( sendAmount ) <= 0
1040- }
1041- >
1042- { sendLoading ? (
1043- < >
1044- < Loader2 className = 'mr-2 h-4 w-4 animate-spin' />
1045- Sending…
1046- </ >
1047- ) : (
1048- < >
1049- < ArrowUpRight className = 'mr-2 h-4 w-4' />
1050- Send
1051- </ >
1052- ) }
1053- </ Button >
1023+ < AlertDialog >
1024+ < AlertDialogTrigger asChild >
1025+ < Button
1026+ className = 'w-full'
1027+ disabled = {
1028+ sendLoading ||
1029+ validateResult !== 'valid' ||
1030+ ! sendAmount ||
1031+ parseFloat ( sendAmount ) <= 0
1032+ }
1033+ >
1034+ { sendLoading ? (
1035+ < >
1036+ < Loader2 className = 'mr-2 h-4 w-4 animate-spin' />
1037+ Sending…
1038+ </ >
1039+ ) : (
1040+ < >
1041+ < ArrowUpRight className = 'mr-2 h-4 w-4' />
1042+ Send
1043+ </ >
1044+ ) }
1045+ </ Button >
1046+ </ AlertDialogTrigger >
1047+ < AlertDialogContent className = 'z-100 max-w-[90vw] sm:max-w-md' >
1048+ < AlertDialogHeader >
1049+ < AlertDialogTitle >
1050+ Confirm Withdrawal
1051+ </ AlertDialogTitle >
1052+ < AlertDialogDescription className = 'space-y-3 text-left' >
1053+ < span className = 'block text-sm' >
1054+ You are about to send{ ' ' }
1055+ < strong className = 'text-foreground' >
1056+ { sendAmount } { sendCurrency }
1057+ </ strong > { ' ' }
1058+ to{ ' ' }
1059+ < strong className = 'text-foreground font-mono break-all' >
1060+ { sendDestination }
1061+ </ strong >
1062+ .
1063+ </ span >
1064+ < span className = 'text-destructive mt-4 block text-sm font-semibold' >
1065+ ⚠️ WARNING: Do not withdraw to Centralized
1066+ Exchanges (e.g. Binance, Coinbase).
1067+ </ span >
1068+ < span className = 'block text-sm' >
1069+ Exchanges require a memo, which this wallet does
1070+ NOT support. Doing so will result in permanent
1071+ loss of your funds.
1072+ </ span >
1073+ </ AlertDialogDescription >
1074+ </ AlertDialogHeader >
1075+ < AlertDialogFooter className = 'mt-4 gap-2 sm:gap-0' >
1076+ < AlertDialogCancel > Cancel</ AlertDialogCancel >
1077+ < AlertDialogAction
1078+ onClick = { handleSendSubmit }
1079+ className = 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
1080+ >
1081+ Yes, I confirm this is a self-custodial wallet
1082+ </ AlertDialogAction >
1083+ </ AlertDialogFooter >
1084+ </ AlertDialogContent >
1085+ </ AlertDialog >
10541086 </ div >
10551087 </ motion . div >
10561088 ) }
0 commit comments