Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ model Transaction {
lastRetryAt DateTime?
retryHistory RetryAttempt[]
amlAlerts AMLAlert[]

@@index([senderId, createdAt])
@@index([recipientId, createdAt])
}

model RetryAttempt {
Expand Down Expand Up @@ -142,6 +145,8 @@ model KYCRecord {
riskLevel String?
submittedAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([status])
}

model AMLAlert {
Expand All @@ -156,6 +161,9 @@ model AMLAlert {
riskScore Int
riskLevel String
createdAt DateTime @default(now())

@@index([userId])
@@index([severity, createdAt])
}

model FeeBumpStat {
Expand Down
201 changes: 41 additions & 160 deletions frontend/src/components/SecurityKeyWarning.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,77 +13,44 @@ export function SecurityKeyWarning({ onAcknowledge }) {
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
style={{
background: 'linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%)',
border: '2px solid #ef4444',
borderRadius: 8,
padding: 16,
marginBottom: 16,
}}
>
{/* Header */}
<div style={{ display: 'flex', alignItems: 'start', gap: 12, marginBottom: 12 }}>
<span style={{ fontSize: 24 }}>🔐</span>
<div className="security-warning__header">
<span className="security-warning__icon">🔐</span>
<div>
<h3 style={{ margin: '0 0 4px 0', color: '#991b1b', fontSize: 16 }}>
Secret Key Security Alert
</h3>
<p style={{ margin: 0, fontSize: 12, color: '#7f1d1d' }}>
Your secret key is displayed. Keep it secure and private.
</p>
<h3 className="security-warning__title">Secret Key Security Alert</h3>
<p className="security-warning__subtitle">Your secret key is displayed. Keep it secure and private.</p>
</div>
</div>

{/* Warning list */}
<div style={{ background: 'white', borderRadius: 6, padding: 12, marginBottom: 12 }}>
<ul style={{
listStyle: 'none',
padding: 0,
margin: 0,
fontSize: 13,
color: '#7f1d1d',
}}>
<li style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<span>⚠️</span>
<span><strong>Never share</strong> your secret key with anyone, including support staff</span>
</li>
<li style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<span>⚠️</span>
<span><strong>Never paste</strong> your secret key into websites or applications you don't trust</span>
</li>
<li style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<span>⚠️</span>
<span><strong>Store offline</strong> in a secure location (hardware wallet, encrypted file, etc.)</span>
</li>
<li style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
<span>⚠️</span>
<span><strong>Screenshot carefully</strong> and store in encrypted cloud storage only</span>
</li>
<li style={{ display: 'flex', gap: 8 }}>
<span>⚠️</span>
<span><strong>Anyone with this key</strong> can access and transfer all your funds</span>
</li>
</ul>
</div>
<ul className="security-warning__list">
<li className="security-warning__list-item">
<span className="security-warning__list-icon">⚠️</span>
<span><strong>Never share</strong> your secret key with anyone, including support staff</span>
</li>
<li className="security-warning__list-item">
<span className="security-warning__list-icon">⚠️</span>
<span><strong>Never paste</strong> your secret key into websites or applications you don't trust</span>
</li>
<li className="security-warning__list-item">
<span className="security-warning__list-icon">⚠️</span>
<span><strong>Store offline</strong> in a secure location (hardware wallet, encrypted file, etc.)</span>
</li>
<li className="security-warning__list-item">
<span className="security-warning__list-icon">⚠️</span>
<span><strong>Screenshot carefully</strong> and store in encrypted cloud storage only</span>
</li>
<li className="security-warning__list-item">
<span className="security-warning__list-icon">⚠️</span>
<span><strong>Anyone with this key</strong> can access and transfer all your funds</span>
</li>
</ul>

{/* Action buttons */}
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<div className="security-warning__actions">
<motion.button
onClick={() => onAcknowledge?.()}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
style={{
background: '#ef4444',
color: 'white',
border: 'none',
borderRadius: 4,
padding: '8px 12px',
fontSize: 13,
fontWeight: 600,
cursor: 'pointer',
flex: 1,
minWidth: 120,
}}
className="security-warning__button"
>
I Understand the Risks
</motion.button>
Expand Down Expand Up @@ -112,9 +79,9 @@ export function SecretKeyDisplay({ secretKey, publicKey }) {

return (
<motion.div
className="secret-key-display"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{ marginTop: 16 }}
>
{!acknowledged && (
<SecurityKeyWarning onAcknowledge={() => setAcknowledged(true)} />
Expand All @@ -128,99 +95,32 @@ export function SecretKeyDisplay({ secretKey, publicKey }) {
exit={{ opacity: 0, y: -8 }}
style={{ marginBottom: 16 }}
>
<div style={{ marginBottom: 12 }}>
<label style={{
display: 'block',
fontSize: 12,
fontWeight: 600,
marginBottom: 6,
color: '#555',
}}>
Public Key (safe to share)
</label>
<div style={{
display: 'flex',
gap: 8,
alignItems: 'center',
background: '#f0f9ff',
border: '1px solid #bfdbfe',
borderRadius: 4,
padding: 10,
}}>
<code style={{
flex: 1,
fontSize: 12,
overflow: 'auto',
fontFamily: 'monospace',
wordBreak: 'break-all',
}}>
{publicKey}
</code>
<div className="secret-key-display__field">
<label className="secret-key-display__label">Public Key (safe to share)</label>
<div className="secret-key-display__input-group secret-key-display__input-group--public">
<code className="secret-key-display__code">{publicKey}</code>
<motion.button
onClick={() => handleCopy(publicKey, 'public')}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
style={{
background: 'white',
border: '1px solid #bfdbfe',
borderRadius: 4,
padding: '4px 8px',
fontSize: 12,
cursor: 'pointer',
width: 'auto',
minHeight: 'unset',
}}
className="secret-key-display__button"
>
{copied === 'public' ? '✓ Copied' : 'Copy'}
</motion.button>
</div>
</div>

<div>
<label style={{
display: 'block',
fontSize: 12,
fontWeight: 600,
marginBottom: 6,
color: '#555',
}}>
Secret Key (Keep Private & Secure)
</label>
<div style={{
display: 'flex',
gap: 8,
alignItems: 'center',
background: '#fef2f2',
border: '2px solid #ef4444',
borderRadius: 4,
padding: 10,
}}>
<code style={{
flex: 1,
fontSize: 12,
overflow: 'auto',
fontFamily: 'monospace',
wordBreak: 'break-all',
color: revealed ? '#991b1b' : '#999',
}}>
<div className="secret-key-display__field">
<label className="secret-key-display__label">Secret Key (Keep Private & Secure)</label>
<div className="secret-key-display__input-group secret-key-display__input-group--secret">
<code className={`secret-key-display__code ${revealed ? 'secret-key-display__code--secret' : 'secret-key-display__code--masked'}`}>
{revealed ? secretKey : masked}
</code>
<motion.button
onClick={() => setRevealed(!revealed)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
style={{
background: revealed ? '#fecaca' : 'white',
color: revealed ? '#991b1b' : '#666',
border: '1px solid #ef4444',
borderRadius: 4,
padding: '4px 8px',
fontSize: 12,
cursor: 'pointer',
width: 'auto',
minHeight: 'unset',
fontWeight: 600,
}}
className="secret-key-display__button secret-key-display__button--reveal"
>
{revealed ? '👁 Hide' : '👁 Show'}
</motion.button>
Expand All @@ -229,37 +129,18 @@ export function SecretKeyDisplay({ secretKey, publicKey }) {
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
disabled={!revealed}
style={{
background: revealed ? '#ef4444' : '#e5e7eb',
color: revealed ? 'white' : '#999',
border: 'none',
borderRadius: 4,
padding: '4px 8px',
fontSize: 12,
cursor: revealed ? 'pointer' : 'not-allowed',
width: 'auto',
minHeight: 'unset',
fontWeight: 600,
}}
className="secret-key-display__button secret-key-display__button--copy"
>
{copied === 'secret' ? '✓ Copied' : 'Copy'}
</motion.button>
</div>
</div>

<motion.div
className="secret-key-display__tip"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3 }}
style={{
background: '#fef3c7',
border: '1px solid #fbbf24',
borderRadius: 4,
padding: 10,
marginTop: 10,
fontSize: 12,
color: '#78350f',
}}
>
💡 <strong>Tip:</strong> Save both keys somewhere secure before leaving this page.
They will not be displayed again.
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/components/StatusMessage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ function Message({ msg, onRemove, onRetry }) {
initial="hidden" animate="visible" exit="exit"
layout
role="alert"
aria-live={msg.type === 'error' ? 'assertive' : 'polite'}
aria-atomic="true"
>
<span className="sm-icon" aria-hidden="true">{msg.icon}</span>
Expand All @@ -34,7 +33,7 @@ export function StatusMessage({ messages, onRemove, showHistory = false, history
const [historyOpen, setHistoryOpen] = useState(false);

return (
<div className="sm-wrap" aria-label="Notifications">
<div className="sm-wrap" aria-label="Notifications" aria-live="polite" aria-atomic="false">
<AnimatePresence initial={false}>
{messages.map((msg) => (
<Message key={msg.id} msg={msg} onRemove={onRemove} onRetry={onRemove} />
Expand Down
Loading
Loading