Skip to content

Commit 6c29d66

Browse files
committed
wip
1 parent c613a45 commit 6c29d66

File tree

8 files changed

+348
-2
lines changed

8 files changed

+348
-2
lines changed

.claude/settings.local.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(rg:*)",
5+
"Bash(grep:*)"
6+
],
7+
"deny": []
8+
}
9+
}
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import React from 'react'
2+
import {
3+
Dialog,
4+
DialogTitle,
5+
DialogContent,
6+
DialogActions,
7+
Button,
8+
Typography,
9+
Box,
10+
// styled,
11+
// useTheme,
12+
} from '@mui/material'
13+
import { withTranslation, WithTranslation } from 'react-i18next'
14+
import { AlertIcon, SoursURL } from '@loopring-web/common-resources'
15+
import styled from '@emotion/styled'
16+
import { useTheme } from '@emotion/react'
17+
// styled
18+
19+
const StyledDialog = styled(Dialog)(({ theme }) => ({
20+
'& .MuiDialog-paper': {
21+
borderRadius: theme.unit * 2,
22+
maxWidth: '480px',
23+
width: '90vw',
24+
maxHeight: '90vh',
25+
overflow: 'hidden',
26+
},
27+
}))
28+
29+
const StyledDialogTitle = styled(DialogTitle)(({ theme }) => ({
30+
background: `linear-gradient(135deg, ${theme.colorBase.primary} 0%, ${theme.colorBase.warning} 100%)`,
31+
color: theme.colorBase.textPrimary,
32+
padding: theme.unit * 2,
33+
display: 'flex',
34+
alignItems: 'center',
35+
gap: theme.unit,
36+
fontSize: '1.25rem',
37+
fontWeight: 600,
38+
}))
39+
40+
const StyledDialogContent = styled(Box)(({ theme }) => ({
41+
42+
paddingTop: theme.unit * 5,
43+
paddingBottom: theme.unit * 2,
44+
paddingLeft: theme.unit * 4,
45+
paddingRight: theme.unit * 4,
46+
maxHeight: '85vh',
47+
overflow: 'auto',
48+
'&::-webkit-scrollbar': {
49+
width: '6px',
50+
},
51+
'&::-webkit-scrollbar-track': {
52+
backgroundColor: theme.colorBase.divide,
53+
borderRadius: '3px',
54+
},
55+
'&::-webkit-scrollbar-thumb': {
56+
backgroundColor: theme.colorBase.textSecondary,
57+
borderRadius: '3px',
58+
},
59+
}))
60+
61+
const StyledDialogActions = styled(DialogActions)(({ theme }) => ({
62+
padding: theme.unit * 2,
63+
borderTop: `1px solid ${theme.colorBase.divide}`,
64+
}))
65+
66+
const HighlightDate = styled('span')(({ theme }) => ({
67+
backgroundColor: theme.colorBase.warning,
68+
color: theme.colorBase.textPrimary,
69+
padding: '2px 8px',
70+
borderRadius: '4px',
71+
fontWeight: 600,
72+
fontSize: '0.9rem',
73+
}))
74+
75+
const SectionTitle = styled(Typography)(({ theme }) => ({
76+
fontSize: '1.5rem',
77+
fontWeight: 600,
78+
color: theme.colorBase.textPrimary,
79+
marginBottom: theme.unit,
80+
display: 'flex',
81+
alignItems: 'center',
82+
gap: theme.unit,
83+
}))
84+
85+
const BulletList = styled('ul')(({ theme }) => ({
86+
paddingLeft: theme.unit,
87+
margin: `${theme.unit}px 0`,
88+
listStyle: 'none',
89+
'& li': {
90+
marginBottom: theme.unit * 0.5,
91+
color: theme.colorBase.textSecondary,
92+
lineHeight: 1.5,
93+
position: 'relative',
94+
paddingLeft: theme.unit,
95+
fontSize: '1.3rem',
96+
},
97+
'& li::before': {
98+
content: '"•"',
99+
color: theme.colorBase.textSecondary,
100+
position: 'absolute',
101+
left: 0,
102+
},
103+
}))
104+
105+
const NetworkSection = styled(Box)(({ theme }) => ({
106+
backgroundColor: theme.colorBase.box,
107+
borderRadius: theme.unit,
108+
marginBottom: theme.unit * 2,
109+
}))
110+
111+
export const ClosureAnnouncementModal = withTranslation('common')(
112+
({
113+
t,
114+
open,
115+
handleClose,
116+
}: WithTranslation & {
117+
open: boolean
118+
handleClose: () => void
119+
}) => {
120+
const theme = useTheme()
121+
122+
return (
123+
<StyledDialog
124+
open={open}
125+
keepMounted
126+
onClose={() => {}} // Prevent closing on outside click
127+
disableEscapeKeyDown // Prevent closing with Escape key
128+
aria-describedby='closure-announcement-dialog'
129+
>
130+
<StyledDialogContent position={'relative'}>
131+
<Typography variant='h2' mb={4}>
132+
Loopring DeFi{' '}
133+
<Typography
134+
variant='h2'
135+
bgcolor={'var(--color-primary)'}
136+
component={'span'}
137+
borderRadius={'17.75px'}
138+
color={theme.colorBase.white}
139+
px={1}
140+
>
141+
Closure
142+
</Typography>{' '}
143+
Announcement
144+
</Typography>
145+
<Box width={'150px'} sx={{
146+
position: 'absolute',
147+
top: 5,
148+
right: 0
149+
}} component={'img'} src={SoursURL + 'svg/closure_logo.svg'}/>
150+
<Typography variant='body1' sx={{ mb: 2, color: theme.colorBase.textSecondary, fontSize: '1.4rem' }}>
151+
We have made the difficult decision to sunset the Loopring DeFi business, with the final
152+
closure date set for <HighlightDate>July 31st, 2025</HighlightDate> on top of the
153+
Loopring DeFi portal technology—many of which have been well received by our users.
154+
However, one of the core challenges we've faced is scalability— specifically, how to
155+
make these services fully trustless and decentralized.
156+
</Typography>
157+
158+
<Typography variant='body1' sx={{ mb: 2, color: theme.colorBase.textSecondary, fontSize: '1.4rem' }}>
159+
Currently, many Loopring DeFi products aim to bridge CeFi and DeFi, requiring
160+
participation from market makers operating in a centralized technology. While if
161+
Loopring alone serves as the primary market maker, these services cannot scale to
162+
compete with leading DeFi protocols. While the user experience and design of Loopring
163+
DeFi are compelling, the underlying structure makes it difficult to enable broader,
164+
permissionless participation in a fully decentralized, non-custodial manner.
165+
</Typography>
166+
167+
<Typography variant='body1' sx={{ mb: 2, color: theme.colorBase.textSecondary, fontSize: '1.4rem' }}>
168+
After a thorough review, we have decided to shut down all Loopring DeFi services,
169+
including:
170+
</Typography>
171+
172+
<BulletList>
173+
<Box sx={{'&&': {color: theme.colorBase.textPrimary}} } component={'li'}>Dual Investment</Box>
174+
<Box sx={{'&&': {color: theme.colorBase.textPrimary}} } component={'li'}>Portal</Box>
175+
<Box sx={{'&&': {color: theme.colorBase.textPrimary}} } component={'li'}>Block Trade</Box>
176+
<Box sx={{'&&': {color: theme.colorBase.textPrimary}} } component={'li'}>ETH Staking</Box>
177+
</BulletList>
178+
179+
<Typography variant='body1' sx={{ mb: 3, color: theme.colorBase.textSecondary, fontSize: '1.4rem' }}>
180+
This allows us to allocate all resources to focus on improving the Loopring Layer2
181+
network.
182+
</Typography>
183+
184+
<SectionTitle>📅 After the Sunset Date (July 31, 2025)</SectionTitle>
185+
186+
<Box sx={{ mb: 3 }}>
187+
<Typography variant='h6' sx={{ mb: 1, color: theme.colorBase.textPrimary, fontSize: '1.5rem' }}>
188+
🔷 For Existing Positions:
189+
</Typography>
190+
191+
<Typography variant='body2' sx={{ mb: 1,color: theme.colorBase.textPrimary, fontWeight: 600, fontSize: '1.3rem' }}>
192+
1. Dual Investment:
193+
</Typography>
194+
<BulletList>
195+
<li>No new subscriptions will be allowed after the sunset date.</li>
196+
<li>All existing positions will be settled before July 31st.</li>
197+
</BulletList>
198+
199+
<Typography variant='body2' sx={{ mb: 1,color: theme.colorBase.textPrimary, fontWeight: 600, fontSize: '1.3rem' }}>
200+
2. Portal:
201+
</Typography>
202+
<BulletList>
203+
<li>All active positions will be force-closed after the sunset date.</li>
204+
</BulletList>
205+
206+
<Typography variant='body2' sx={{ mb: 1,color: theme.colorBase.textPrimary, fontWeight: 600, fontSize: '1.3rem' }}>
207+
3. ETH Staking:
208+
</Typography>
209+
<BulletList>
210+
<li>No new subscriptions will be accepted.</li>
211+
<li>Users holding wsETH, rETH, or CiETH can still:</li>
212+
<Box pl={2.5}>
213+
<li >
214+
Redeem directly on Loopring Layer 2, or Withdraw to Ethereum Layer 1 and interact
215+
directly with Lido, Rocket Pool, or the CIAN protocol for redemption.
216+
</li>
217+
</Box>
218+
219+
</BulletList>
220+
</Box>
221+
222+
<NetworkSection>
223+
<SectionTitle>🌐 For Users on Taiko and Base Networks:</SectionTitle>
224+
<Typography variant='body1' sx={{ mb: 2, color: theme.colorBase.textPrimary, fontSize: '1.4rem' }}>
225+
After July 31st, Loopring will cease all services on both the Taiko and Base networks.
226+
</Typography>
227+
<BulletList>
228+
<li>
229+
Users who do not withdraw their assets from their Loopring DeFi (Layer 3) account
230+
before the sunset date will have their funds automatically force-withdrawn to the
231+
upper layer (Taiko or Base).
232+
</li>
233+
</BulletList>
234+
<Typography
235+
variant='body2'
236+
sx={{ mt: 1, fontStyle: 'italic', color: theme.colorBase.textTertiary, fontSize: '1.2rem' }}
237+
>
238+
Note: Accounts with dust balances (i.e., less than $0.10 USD) will not trigger a
239+
force-withdrawal operation.
240+
</Typography>
241+
</NetworkSection>
242+
243+
<NetworkSection>
244+
<SectionTitle>🌐 For Ethereum Users:</SectionTitle>
245+
<Typography variant='body1' sx={{ mb: 2, color: theme.colorBase.textSecondary, fontSize: '1.4rem' }}>
246+
Loopring Layer 2 will continue to operate as usual. All core features—including the
247+
fully decentralized exchange—will remain functional.
248+
</Typography>
249+
<BulletList>
250+
<li>All assets, including tokens and NFTs, remain safe and intact on Layer 2.</li>
251+
<li>
252+
However, all Loopring DeFi features will be permanently disabled after the sunset
253+
date.
254+
</li>
255+
</BulletList>
256+
</NetworkSection>
257+
258+
<Typography variant='body1' sx={{ mt: 3, mb: 2, color: theme.colorBase.textSecondary, fontSize: '1.4rem' }}>
259+
Thank you to all our users and community members who have supported Loopring DeFi. While
260+
this chapter is closing, we remain committed to building and supporting decentralized
261+
infrastructure that empowers users and developers alike.
262+
</Typography>
263+
264+
<Typography variant='body1' sx={{ color: theme.colorBase.textSecondary, fontSize: '1.4rem' }}>
265+
If you have any questions, please reach out to our support channels.
266+
</Typography>
267+
268+
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4, mb: 2 }}>
269+
<Button
270+
variant='contained'
271+
color='primary'
272+
onClick={handleClose}
273+
sx={{
274+
minWidth: '200px',
275+
height: '40px',
276+
borderRadius: '6px',
277+
textTransform: 'none',
278+
fontWeight: 600,
279+
fontSize: '1.1rem',
280+
}}
281+
>
282+
I Know
283+
</Button>
284+
</Box>
285+
</StyledDialogContent>
286+
</StyledDialog>
287+
)
288+
},
289+
)

packages/component-lib/src/components/modal/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './WalletPanels'
66
export * from './RedPacketPanels'
77
export * from './setting'
88
export * from './Vault'
9+
export * from './ClosureAnnouncementModal'
910
export type ModalBasicProps = {
1011
open: boolean
1112
onClose: {

packages/component-lib/src/stores/reducer/modals/hook.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
setShowTransferToTaikoAccount,
4545
setShowVaultCloseConfirm,
4646
setShowBridge,
47+
setShowClosureAnnouncement,
4748
} from './reducer'
4849

4950
import React from 'react'
@@ -372,5 +373,9 @@ export const useOpenModals = () => {
372373
(state: ModalStatePlayLoad) => dispatch(setShowBridge(state)),
373374
[dispatch],
374375
),
376+
setShowClosureAnnouncement: React.useCallback(
377+
(state: ModalStatePlayLoad) => dispatch(setShowClosureAnnouncement(state)),
378+
[dispatch],
379+
),
375380
}
376381
}

packages/component-lib/src/stores/reducer/modals/interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,5 @@ export interface ModalState {
113113
isShowConfirmedVault: ModalStatePlayLoad
114114
isShowTransferToTaikoAccount: ModalStatePlayLoad & { from?: string }
115115
isShowBridge: ModalStatePlayLoad & Partial<Transaction>
116+
isShowClosureAnnouncement: ModalStatePlayLoad
116117
}

packages/component-lib/src/stores/reducer/modals/reducer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const initialState: ModalState = {
6969
isShowConfirmedVault: { isShow: false },
7070
isShowTransferToTaikoAccount: { isShow: false, from: undefined },
7171
isShowBridge: { isShow: false, symbol: undefined, info: undefined },
72+
isShowClosureAnnouncement: { isShow: false },
7273
}
7374

7475
export const modalsSlice: Slice<ModalState> = createSlice({
@@ -476,6 +477,9 @@ export const modalsSlice: Slice<ModalState> = createSlice({
476477
setShowBridge(state, action: PayloadAction<ModalStatePlayLoad & Partial<Transaction & { contactName?: string }>>) {
477478
state.isShowBridge = action.payload
478479
},
480+
setShowClosureAnnouncement(state, action: PayloadAction<ModalStatePlayLoad>) {
481+
state.isShowClosureAnnouncement = action.payload
482+
},
479483
},
480484
})
481485
export const {
@@ -521,5 +525,6 @@ export const {
521525
setShowNoVaultAccount,
522526
setShowConfirmedVault,
523527
setShowBridge,
524-
setShowTransferToTaikoAccount
528+
setShowTransferToTaikoAccount,
529+
setShowClosureAnnouncement
525530
} = modalsSlice.actions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const CLOSURE_ANNOUNCEMENT_KEY = 'loopring_closure_announcement_dismissed'
2+
3+
export const isClosureAnnouncementDismissed = (): boolean => {
4+
try {
5+
return localStorage.getItem(CLOSURE_ANNOUNCEMENT_KEY) === 'true'
6+
} catch (error) {
7+
return false
8+
}
9+
}
10+
11+
export const setClosureAnnouncementDismissed = (): void => {
12+
try {
13+
localStorage.setItem(CLOSURE_ANNOUNCEMENT_KEY, 'true')
14+
} catch (error) {
15+
console.warn('Failed to save closure announcement state:', error)
16+
}
17+
}

0 commit comments

Comments
 (0)