Skip to content

Commit c6fb254

Browse files
feat: support Magic account deletion alongside thirdweb (#630)
* feat: scaffold account settings shell under /account * feat: apply account settings brand colors (violet gradient, magenta nav) * feat: build account wallets section (balances, receive, deep-links) * feat: build account notifications settings section * feat: build account delete section with thirdweb flow * feat: build account credits settings section * fix: lazy-construct thirdweb client to avoid shell-chunk top-level throw * feat: rework account notifications as per-type accordions with master toggle * feat: redesign account sidebar (user header with address copy, dividers, icons) * feat: make account sidebar a full-height left rail * feat: add lazy BlockchainShell and in-page MANA send to account wallets * fix: match account notifications and delete account designs to figma * fix: mobile master-detail navigation for account settings * fix: notification accordions fill their column width * fix: resolve env via sites config so dev defaults to zone not production * fix: add per-group icons and hover shadow to notification accordions * fix: align account sidebar with content and use 59px navbar gap on desktop * fix: keep notification toggles instant and independent on change * feat: enroll in credits program in-place instead of marketplace redirect * fix: master email toggle flips only the global mute, not every switch * fix: use the design SVG icons for notification group accordions * fix: inject web3 reducers before Web3Inner mounts to avoid wallet crash * feat: in-page MANA swap depositing from ethereum to polygon * feat: track and list wallet send and swap transactions locally * feat: align account wallets ui with figma (transactions grid, action buttons, mana mark) * feat: in-page buy, withdraw and claim for account wallets with thirdweb-gated delete * fix: make the account buy-with-fiat modal directional per card * feat: support Magic account deletion alongside thirdweb The account Delete flow was thirdweb-only. Extend it to Magic logins: - Detect Magic accounts via the dcl_magic_user_email key (useIsMagicAccount), mirroring useIsThirdwebAccount. - Gate the Delete section/nav on thirdweb OR Magic (DeleteAccountPage, AccountSidebar). - Branch the confirm modal by provider: thirdweb keeps the client-side SDK unlink flow; Magic mints a DID token (lib/magic) and calls the auth-server DELETE /accounts (lib/accountDeletion) via a signed fetch carrying the token in the request metadata. Local cleanup is provider-aware. - Add AUTH_SERVER_URL per env; fix dev to use the test Magic app key so the minted token's audience matches auth-server-dev's MAGIC_CLIENT_ID (test app). stg/prd were already aligned (test/prod respectively). * refactor: detect Magic via layered signals to cover email-less logins useIsMagicAccount keyed off dcl_magic_user_email, which only exists for Magic logins that produced an email. SMS / passkey / OAuth-without-email sessions never set it, so those users were classed as non-Magic and never saw the Delete flow (the very users Magic's public-address deletion targets). Detect with layered signals, cheapest first: 1. dcl_magic_user_email (synchronous positive fast-path for email logins). 2. wagmi's persisted connector id (wagmi.recentConnectorId === 'magic') — the core-web3 connector signal, present once a wallet action has populated it; covers email-less logins without loading the Magic SDK. 3. magic.user.isLoggedIn() — authoritative fallback when neither is present (fresh session, no wallet action); the same call getMagicDidToken() makes. The check is skipped for known thirdweb logins so the Magic SDK/iframe is never loaded needlessly. The hook now returns boolean | undefined; the Delete page shows a loader while the async check resolves instead of flashing the "unavailable" message at a Magic user. wagmi.recentConnectorId is read straight from localStorage (new utils/recentConnector, defensively parsed) because core-web3 builds its wagmi config lazily — only on a BlockchainShell wallet action — so the connector is not reachable via wagmi hooks in the provider-free account section. * refactor: detect Magic purely via the SDK session check Drop the synchronous signal layering (dcl_magic_user_email + wagmi.recentConnectorId) from useIsMagicAccount and resolve solely through magic.user.isLoggedIn() — one authoritative, login-method-agnostic source instead of three, and no more coupling to wagmi's localStorage shape (the recentConnector util is removed). The thirdweb skip guard stays, so the Magic SDK is still not loaded for known thirdweb logins. Other non-Magic logins (e.g. MetaMask) now resolve through the SDK check — a brief loader / hidden Delete entry while it resolves — rather than a synchronous localStorage read. * refactor: extract useCanDeleteAccount, cache Magic instance, name the status hook Addresses PR review (P2): - Extract useCanDeleteAccount() returning { canDelete, isMagic, isResolvingProvider }, shared by the Delete page and the sidebar so the gating logic can't drift (it was duplicated 3 lines in both). - Cache the Magic SDK instance as a module-level lazy singleton so the SDK / its iframe bootstraps once even when the sidebar and page detect concurrently; a failed bootstrap is not cached, so a later call can retry. - Rename useIsMagicAccount -> useMagicAccountStatus returning an explicit { isMagic, isLoading } object, since the useIs* convention implies a boolean (it previously returned boolean | undefined). The fetchWithIdentity positional-args -> options-object cleanup (also P2) is left for a follow-up: it touches ~16 call sites across unrelated features. --------- Co-authored-by: Braian Mellor <braianj@gmail.com>
1 parent cb2beec commit c6fb254

21 files changed

Lines changed: 704 additions & 82 deletions

src/components/account/AccountSidebar/AccountSidebar.spec.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ jest.mock('../../../hooks/useWalletAddress', () => ({
5959
useWalletAddress: () => ({ disconnect: mockDisconnect })
6060
}))
6161

62-
let mockIsThirdweb = true
63-
jest.mock('../../../hooks/useIsThirdwebAccount', () => ({
64-
useIsThirdwebAccount: () => mockIsThirdweb
62+
let mockCanDelete = true
63+
jest.mock('../../../hooks/useCanDeleteAccount', () => ({
64+
useCanDeleteAccount: () => ({ canDelete: mockCanDelete, isMagic: false, isResolvingProvider: false })
6565
}))
6666

6767
let mockProfile: unknown = undefined
@@ -86,7 +86,7 @@ describe('AccountSidebar', () => {
8686
const writeText = jest.fn().mockResolvedValue(undefined)
8787

8888
beforeEach(() => {
89-
mockIsThirdweb = true
89+
mockCanDelete = true
9090
mockProfile = undefined
9191
Object.assign(navigator, { clipboard: { writeText } })
9292
})
@@ -95,7 +95,7 @@ describe('AccountSidebar', () => {
9595
jest.clearAllMocks()
9696
})
9797

98-
it('should render the three section links plus delete and logout for a thirdweb account', () => {
98+
it('should render the three section links plus delete and logout for an account that can be deleted', () => {
9999
renderSidebar()
100100

101101
expect(screen.getByText('account.nav.wallets')).toBeInTheDocument()
@@ -105,8 +105,8 @@ describe('AccountSidebar', () => {
105105
expect(screen.getByText('account.nav.logout')).toBeInTheDocument()
106106
})
107107

108-
it('should hide the delete entry for a non-thirdweb account (logout stays)', () => {
109-
mockIsThirdweb = false
108+
it('should hide the delete entry when the account cannot be deleted (logout stays)', () => {
109+
mockCanDelete = false
110110
renderSidebar()
111111

112112
expect(screen.queryByText('account.nav.delete')).not.toBeInTheDocument()

src/components/account/AccountSidebar/AccountSidebar.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import NotificationsNoneOutlinedIcon from '@mui/icons-material/NotificationsNone
1717
import { Address, Tooltip } from 'decentraland-ui2'
1818
import { useGetProfileQuery } from '../../../features/profile/profile.client'
1919
import { useFormatMessage } from '../../../hooks/adapters/useFormatMessage'
20-
import { useIsThirdwebAccount } from '../../../hooks/useIsThirdwebAccount'
20+
import { useCanDeleteAccount } from '../../../hooks/useCanDeleteAccount'
2121
import { useWalletAddress } from '../../../hooks/useWalletAddress'
2222
import { getAvatarBackgroundColor, getDisplayName } from '../../../utils/avatarColor'
2323
import {
@@ -62,7 +62,9 @@ const AccountSidebar = ({ address }: AccountSidebarProps) => {
6262
const t = useFormatMessage()
6363
const { pathname } = useLocation()
6464
const { disconnect } = useWalletAddress()
65-
const isThirdweb = useIsThirdwebAccount()
65+
// The Delete entry appears only once detection positively confirms a deletable account (thirdweb
66+
// or Magic); while the Magic check resolves, canDelete stays false so it is not shown prematurely.
67+
const { canDelete } = useCanDeleteAccount()
6668
const { data: profile } = useGetProfileQuery(address)
6769
const [copied, setCopied] = useState(false)
6870
const copiedTimeout = useRef<ReturnType<typeof setTimeout>>()
@@ -134,9 +136,9 @@ const AccountSidebar = ({ address }: AccountSidebarProps) => {
134136
</Nav>
135137

136138
<BottomGroup>
137-
{/* Delete only applies to thirdweb (email/social-OTP) wallets — the SDK unlink flow has no
138-
equivalent for self-custodial logins — so the entry is hidden for everyone else. */}
139-
{isThirdweb && (
139+
{/* Delete applies to web2 logins (thirdweb via the SDK, Magic via the auth-server); it is
140+
hidden for self-custodial logins, which have no account to delete. */}
141+
{canDelete && (
140142
<>
141143
<Divider />
142144
<DeleteNavItem

src/components/account/DeleteAccount/DeleteAccountConfirmModal/DeleteAccountConfirmModal.spec.tsx

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,19 @@ jest.mock('../../../../hooks/adapters/useFormatMessage', () => ({
7878
useFormatMessage: () => (id: string) => id
7979
}))
8080

81+
const mockGetMagicDidToken = jest.fn()
82+
const mockDeleteMagicAccount = jest.fn()
83+
const mockIdentity = { authChain: [], ephemeralIdentity: {} }
84+
jest.mock('../../../../hooks/useAuthIdentity', () => ({
85+
useAuthIdentity: () => ({ identity: mockIdentity, hasValidIdentity: true, address: undefined })
86+
}))
87+
jest.mock('../../../../lib/magic', () => ({
88+
getMagicDidToken: (...args: unknown[]) => mockGetMagicDidToken(...args)
89+
}))
90+
jest.mock('../../../../lib/accountDeletion', () => ({
91+
deleteMagicAccount: (...args: unknown[]) => mockDeleteMagicAccount(...args)
92+
}))
93+
8194
const ADDRESS = '0x1234567890123456789012345678901234567890'
8295

8396
describe('DeleteAccountConfirmModal', () => {
@@ -96,6 +109,8 @@ describe('DeleteAccountConfirmModal', () => {
96109
mockGetEnv.mockImplementation((key: string) => (key === 'AUTH_URL' ? 'https://decentraland.org/auth' : undefined))
97110
mockGetProfiles.mockResolvedValue([{ type: 'email' }, { type: 'google' }])
98111
mockUnlinkProfile.mockResolvedValue(undefined)
112+
mockGetMagicDidToken.mockResolvedValue('did-token-abc')
113+
mockDeleteMagicAccount.mockResolvedValue(undefined)
99114
jest.spyOn(console, 'error').mockImplementation(() => undefined)
100115
})
101116

@@ -107,6 +122,13 @@ describe('DeleteAccountConfirmModal', () => {
107122
const renderModal = (address: string | undefined = ADDRESS) =>
108123
render(<DeleteAccountConfirmModal open address={address} onClose={onClose} />)
109124

125+
const confirmAndDelete = async () => {
126+
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'DELETE' } })
127+
await act(async () => {
128+
fireEvent.click(screen.getByRole('button', { name: 'account.delete.modal.delete' }))
129+
})
130+
}
131+
110132
it('should not render its content when closed', () => {
111133
render(<DeleteAccountConfirmModal open={false} address={ADDRESS} onClose={onClose} />)
112134

@@ -129,10 +151,7 @@ describe('DeleteAccountConfirmModal', () => {
129151
it('should unlink every profile and redirect to login when confirmed', async () => {
130152
renderModal()
131153

132-
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'DELETE' } })
133-
await act(async () => {
134-
fireEvent.click(screen.getByRole('button', { name: 'account.delete.modal.delete' }))
135-
})
154+
await confirmAndDelete()
136155

137156
await waitFor(() => expect(replaceMock).toHaveBeenCalledTimes(1))
138157

@@ -149,10 +168,7 @@ describe('DeleteAccountConfirmModal', () => {
149168
mockUnlinkProfile.mockRejectedValueOnce(new Error('boom'))
150169
renderModal()
151170

152-
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'DELETE' } })
153-
await act(async () => {
154-
fireEvent.click(screen.getByRole('button', { name: 'account.delete.modal.delete' }))
155-
})
171+
await confirmAndDelete()
156172

157173
await waitFor(() => expect(screen.getByRole('button', { name: 'account.delete.modal.delete' })).toBeEnabled())
158174
expect(screen.getByText('account.delete.modal.generic_error')).toBeInTheDocument()
@@ -168,12 +184,30 @@ describe('DeleteAccountConfirmModal', () => {
168184
expect(mockGetProfiles).not.toHaveBeenCalled()
169185
})
170186

171-
const confirmAndDelete = async () => {
172-
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'DELETE' } })
173-
await act(async () => {
174-
fireEvent.click(screen.getByRole('button', { name: 'account.delete.modal.delete' }))
175-
})
176-
}
187+
it('should delete via the auth-server with a fresh DID token for a Magic account', async () => {
188+
render(<DeleteAccountConfirmModal open address={ADDRESS} isMagic onClose={onClose} />)
189+
190+
await confirmAndDelete()
191+
192+
await waitFor(() => expect(replaceMock).toHaveBeenCalledTimes(1))
193+
194+
expect(mockGetMagicDidToken).toHaveBeenCalledTimes(1)
195+
expect(mockDeleteMagicAccount).toHaveBeenCalledWith(mockIdentity, 'did-token-abc')
196+
expect(mockGetProfiles).not.toHaveBeenCalled()
197+
expect(mockClearIdentity).toHaveBeenCalledWith(ADDRESS)
198+
expect(mockDisconnect).toHaveBeenCalledTimes(1)
199+
})
200+
201+
it('should surface a generic error and not redirect when Magic deletion fails', async () => {
202+
mockDeleteMagicAccount.mockRejectedValueOnce(new Error('boom'))
203+
render(<DeleteAccountConfirmModal open address={ADDRESS} isMagic onClose={onClose} />)
204+
205+
await confirmAndDelete()
206+
207+
await waitFor(() => expect(screen.getByRole('button', { name: 'account.delete.modal.delete' })).toBeEnabled())
208+
expect(screen.getByText('account.delete.modal.generic_error')).toBeInTheDocument()
209+
expect(replaceMock).not.toHaveBeenCalled()
210+
})
177211

178212
describe('when the account has thirdweb data in web storage', () => {
179213
beforeEach(() => {

src/components/account/DeleteAccount/DeleteAccountConfirmModal/DeleteAccountConfirmModal.tsx

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { getProfiles, unlinkProfile } from 'thirdweb/wallets/in-app'
77
import { localStorageClearIdentity } from '@dcl/single-sign-on-client'
88
import { getEnv } from '../../../../config/env'
99
import { useFormatMessage } from '../../../../hooks/adapters/useFormatMessage'
10+
import { useAuthIdentity } from '../../../../hooks/useAuthIdentity'
1011
import { useWalletAddress } from '../../../../hooks/useWalletAddress'
12+
import { deleteMagicAccount } from '../../../../lib/accountDeletion'
13+
import { getMagicDidToken } from '../../../../lib/magic'
1114
import { getThirdwebClient } from '../../../../lib/thirdweb'
1215
import { DeleteAccountConfirmModalProps } from './DeleteAccountConfirmModal.types'
1316
import {
@@ -71,52 +74,57 @@ async function deleteThirdwebAccount() {
7174
* clean up local storage. Since this modal is not rendered inside a
7275
* ThirdwebProvider, we clear thirdweb's local data manually.
7376
*/
74-
async function clearLocalSession(address: string, disconnect: () => void) {
77+
async function clearLocalSession(address: string, disconnect: () => void, isMagic: boolean) {
7578
// Clear the Decentraland SSO identity for the connected address.
7679
localStorageClearIdentity(address)
7780

78-
// Clear thirdweb session data from localStorage (auth cookies, device shares, wallet user id).
79-
Object.keys(localStorage)
80-
.filter(key => key.startsWith('thirdweb'))
81-
.forEach(key => localStorage.removeItem(key))
82-
83-
// Clear thirdweb session data from sessionStorage.
84-
Object.keys(sessionStorage)
85-
.filter(key => key.startsWith('thirdweb'))
86-
.forEach(key => sessionStorage.removeItem(key))
87-
88-
// Clear thirdweb IndexedDB databases (device shares, wallet encryption keys).
89-
// indexedDB.deleteDatabase() returns an IDBOpenDBRequest, not a Promise, so we
90-
// wrap each call to await completion before redirecting.
91-
if (typeof indexedDB !== 'undefined' && typeof indexedDB.databases === 'function') {
92-
try {
93-
const databases = await indexedDB.databases()
94-
await Promise.all(
95-
databases
96-
.filter(db => db.name?.includes('thirdweb'))
97-
.map(
98-
db =>
99-
new Promise<void>((resolve, reject) => {
100-
if (!db.name) return resolve()
101-
const request = indexedDB.deleteDatabase(db.name)
102-
request.onsuccess = () => resolve()
103-
request.onerror = () => reject(request.error)
104-
})
105-
)
106-
)
107-
} catch {
108-
// Best-effort: not all browsers support indexedDB.databases().
81+
// Magic keeps no client-side wallet data beyond the SSO identity and the email pointer that
82+
// `disconnect()` clears below, so the thirdweb-specific storage sweep is skipped for it.
83+
if (!isMagic) {
84+
// Clear thirdweb session data from localStorage (auth cookies, device shares, wallet user id).
85+
Object.keys(localStorage)
86+
.filter(key => key.startsWith('thirdweb'))
87+
.forEach(key => localStorage.removeItem(key))
88+
89+
// Clear thirdweb session data from sessionStorage.
90+
Object.keys(sessionStorage)
91+
.filter(key => key.startsWith('thirdweb'))
92+
.forEach(key => sessionStorage.removeItem(key))
93+
94+
// Clear thirdweb IndexedDB databases (device shares, wallet encryption keys).
95+
// indexedDB.deleteDatabase() returns an IDBOpenDBRequest, not a Promise, so we
96+
// wrap each call to await completion before redirecting.
97+
if (typeof indexedDB !== 'undefined' && typeof indexedDB.databases === 'function') {
98+
try {
99+
const databases = await indexedDB.databases()
100+
await Promise.all(
101+
databases
102+
.filter(db => db.name?.includes('thirdweb'))
103+
.map(
104+
db =>
105+
new Promise<void>((resolve, reject) => {
106+
if (!db.name) return resolve()
107+
const request = indexedDB.deleteDatabase(db.name)
108+
request.onsuccess = () => resolve()
109+
request.onerror = () => reject(request.error)
110+
})
111+
)
112+
)
113+
} catch {
114+
// Best-effort: not all browsers support indexedDB.databases().
115+
}
109116
}
110117
}
111118

112119
// Drop the sites wallet pointer + SSO/connect keys (replaces decentraland-connect's
113-
// connection.disconnect() — sites has no decentraland-connect).
120+
// connection.disconnect() — sites has no decentraland-connect). Also clears the Magic email pointer.
114121
disconnect()
115122
}
116123

117-
const DeleteAccountConfirmModal = ({ open, address, onClose }: DeleteAccountConfirmModalProps) => {
124+
const DeleteAccountConfirmModal = ({ open, address, isMagic = false, onClose }: DeleteAccountConfirmModalProps) => {
118125
const t = useFormatMessage()
119126
const { disconnect } = useWalletAddress()
127+
const { identity } = useAuthIdentity()
120128
const [isLoading, setIsLoading] = useState(false)
121129
const [error, setError] = useState<string | null>(null)
122130
const [confirmationText, setConfirmationText] = useState('')
@@ -132,7 +140,15 @@ const DeleteAccountConfirmModal = ({ open, address, onClose }: DeleteAccountConf
132140
setError(null)
133141

134142
try {
135-
await deleteThirdwebAccount()
143+
if (isMagic) {
144+
// Magic accounts are deleted server-side: mint a fresh DID token and call the auth-server.
145+
if (!identity) throw new Error('Missing Decentraland identity for the connected account')
146+
const didToken = await getMagicDidToken()
147+
await deleteMagicAccount(identity, didToken)
148+
} else {
149+
// thirdweb in-app wallets are deleted client-side via the SDK.
150+
await deleteThirdwebAccount()
151+
}
136152
} catch (deletionError) {
137153
// Log the raw error; surface only a generic message to the user.
138154
console.error('Account deletion failed:', deletionError)
@@ -145,15 +161,15 @@ const DeleteAccountConfirmModal = ({ open, address, onClose }: DeleteAccountConf
145161
// Past the point of no return: always clear local session and redirect,
146162
// even if individual cleanup steps fail.
147163
try {
148-
await clearLocalSession(address, disconnect)
164+
await clearLocalSession(address, disconnect, isMagic)
149165
} catch (cleanupError) {
150166
console.error('Local session cleanup failed:', cleanupError)
151167
}
152168

153169
// Redirect to the login page; a full page reload destroys all in-memory state.
154170
const authUrl = getEnv('AUTH_URL') ?? ''
155171
window.location.replace(`${authUrl}/login?redirectTo=${encodeURIComponent(window.location.pathname)}`)
156-
}, [address, disconnect, isConfirmed, t])
172+
}, [address, disconnect, identity, isConfirmed, isMagic, t])
157173

158174
// Prevent dismissal via ESC or backdrop click while deletion is in flight.
159175
const canDismiss = !isLoading
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export interface DeleteAccountConfirmModalProps {
22
open: boolean
3-
/** The thirdweb in-app wallet address whose account will be deleted. */
3+
/** The wallet address whose account will be deleted. */
44
address?: string
5+
/** Whether the connected account is a Magic login (deleted via the auth-server) instead of thirdweb. */
6+
isMagic?: boolean
57
onClose: () => void
68
}

src/config/env/dev.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"ONBOARDING_URL": "/auth/login/?newUser&redirectTo=%2Fdownload",
33
"AUTH_URL": "/auth",
4-
"MAGIC_API_KEY": "pk_live_212568025B158355",
4+
"MAGIC_API_KEY": "pk_live_CE856A4938B36648",
5+
"AUTH_SERVER_URL": "https://auth-api.decentraland.zone",
56
"THIRDWEB_CLIENT_ID": "e1adce863fe287bb6cf0e3fd90bdb77f",
67
"PEER_URL": "https://peer.decentraland.zone",
78
"WALLET_CONNECT_PROJECT_ID": "61570c542c2d66c659492e5b24a41522",

src/config/env/prd.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"ONBOARDING_URL": "https://decentraland.org/auth/login/?newUser&redirectTo=https%3A%2F%2Fdecentraland.org%2Fdownload",
33
"AUTH_URL": "https://decentraland.org/auth",
44
"MAGIC_API_KEY": "pk_live_212568025B158355",
5+
"AUTH_SERVER_URL": "https://auth-api.decentraland.org",
56
"THIRDWEB_CLIENT_ID": "e1adce863fe287bb6cf0e3fd90bdb77f",
67
"PEER_URL": "https://peer.decentraland.org",
78
"WALLET_CONNECT_PROJECT_ID": "61570c542c2d66c659492e5b24a41522",

src/config/env/stg.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"ONBOARDING_URL": "https://decentraland.org/auth/login/?newUser&redirectTo=https%3A%2F%2Fdecentraland.org%2Fdownload",
33
"AUTH_URL": "https://decentraland.today/auth",
44
"MAGIC_API_KEY": "pk_live_CE856A4938B36648",
5+
"AUTH_SERVER_URL": "https://auth-api.decentraland.today",
56
"THIRDWEB_CLIENT_ID": "e1adce863fe287bb6cf0e3fd90bdb77f",
67
"PEER_URL": "https://peer.decentraland.zone",
78
"WALLET_CONNECT_PROJECT_ID": "61570c542c2d66c659492e5b24a41522",

0 commit comments

Comments
 (0)