@lukso/up-connector 0.4.0-dev.a8c9315

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +280 -0
  3. package/dist/account-modal.cjs +9 -0
  4. package/dist/account-modal.cjs.map +1 -0
  5. package/dist/account-modal.d.cts +16 -0
  6. package/dist/account-modal.d.ts +16 -0
  7. package/dist/account-modal.js +9 -0
  8. package/dist/account-modal.js.map +1 -0
  9. package/dist/auto-setup.cjs +17 -0
  10. package/dist/auto-setup.cjs.map +1 -0
  11. package/dist/auto-setup.d.cts +123 -0
  12. package/dist/auto-setup.d.ts +123 -0
  13. package/dist/auto-setup.js +17 -0
  14. package/dist/auto-setup.js.map +1 -0
  15. package/dist/avatar-CmUCtW_w.d.cts +205 -0
  16. package/dist/avatar-CmUCtW_w.d.ts +205 -0
  17. package/dist/avatar.cjs +12 -0
  18. package/dist/avatar.cjs.map +1 -0
  19. package/dist/avatar.d.cts +1 -0
  20. package/dist/avatar.d.ts +1 -0
  21. package/dist/avatar.js +12 -0
  22. package/dist/avatar.js.map +1 -0
  23. package/dist/backup-modal.cjs +9 -0
  24. package/dist/backup-modal.cjs.map +1 -0
  25. package/dist/backup-modal.d.cts +41 -0
  26. package/dist/backup-modal.d.ts +41 -0
  27. package/dist/backup-modal.js +9 -0
  28. package/dist/backup-modal.js.map +1 -0
  29. package/dist/chunk-3SGSPHOZ.js +595 -0
  30. package/dist/chunk-3SGSPHOZ.js.map +1 -0
  31. package/dist/chunk-6AYZOIFY.js +181 -0
  32. package/dist/chunk-6AYZOIFY.js.map +1 -0
  33. package/dist/chunk-6N35TCFT.js +852 -0
  34. package/dist/chunk-6N35TCFT.js.map +1 -0
  35. package/dist/chunk-7ETKG6KR.cjs +387 -0
  36. package/dist/chunk-7ETKG6KR.cjs.map +1 -0
  37. package/dist/chunk-EUXUH3YW.js +15 -0
  38. package/dist/chunk-EUXUH3YW.js.map +1 -0
  39. package/dist/chunk-GFVUWAG4.cjs +158 -0
  40. package/dist/chunk-GFVUWAG4.cjs.map +1 -0
  41. package/dist/chunk-IAKQFHFD.cjs +595 -0
  42. package/dist/chunk-IAKQFHFD.cjs.map +1 -0
  43. package/dist/chunk-MH7MP7XK.cjs +181 -0
  44. package/dist/chunk-MH7MP7XK.cjs.map +1 -0
  45. package/dist/chunk-NWCNJSG3.js +387 -0
  46. package/dist/chunk-NWCNJSG3.js.map +1 -0
  47. package/dist/chunk-NXU2DQAV.js +1128 -0
  48. package/dist/chunk-NXU2DQAV.js.map +1 -0
  49. package/dist/chunk-ORJK2YGG.cjs +852 -0
  50. package/dist/chunk-ORJK2YGG.cjs.map +1 -0
  51. package/dist/chunk-RFA6SEIS.cjs +1128 -0
  52. package/dist/chunk-RFA6SEIS.cjs.map +1 -0
  53. package/dist/chunk-XGIT7YUY.js +31 -0
  54. package/dist/chunk-XGIT7YUY.js.map +1 -0
  55. package/dist/chunk-XOKG3KIL.cjs +31 -0
  56. package/dist/chunk-XOKG3KIL.cjs.map +1 -0
  57. package/dist/chunk-YIWSPI4I.js +158 -0
  58. package/dist/chunk-YIWSPI4I.js.map +1 -0
  59. package/dist/chunk-ZBDE64SD.cjs +15 -0
  60. package/dist/chunk-ZBDE64SD.cjs.map +1 -0
  61. package/dist/connect-modal/index.cjs +20 -0
  62. package/dist/connect-modal/index.cjs.map +1 -0
  63. package/dist/connect-modal/index.d.cts +9 -0
  64. package/dist/connect-modal/index.d.ts +9 -0
  65. package/dist/connect-modal/index.js +20 -0
  66. package/dist/connect-modal/index.js.map +1 -0
  67. package/dist/index-D2orHGFi.d.cts +8 -0
  68. package/dist/index-D2orHGFi.d.ts +8 -0
  69. package/dist/index.cjs +793 -0
  70. package/dist/index.cjs.map +1 -0
  71. package/dist/index.d.cts +189 -0
  72. package/dist/index.d.ts +189 -0
  73. package/dist/index.js +793 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/restore-modal.cjs +9 -0
  76. package/dist/restore-modal.cjs.map +1 -0
  77. package/dist/restore-modal.d.cts +68 -0
  78. package/dist/restore-modal.d.ts +68 -0
  79. package/dist/restore-modal.js +9 -0
  80. package/dist/restore-modal.js.map +1 -0
  81. package/dist/wagmi-CVuDs_0h.d.cts +386 -0
  82. package/dist/wagmi-CVuDs_0h.d.ts +386 -0
  83. package/package.json +158 -0
  84. package/src/account-modal.ts +142 -0
  85. package/src/auto-setup.ts +362 -0
  86. package/src/avatar.ts +1135 -0
  87. package/src/backup-modal.ts +439 -0
  88. package/src/connect-modal/components/connection-view.ts +398 -0
  89. package/src/connect-modal/components/eoa-connection-view.ts +408 -0
  90. package/src/connect-modal/components/qr-code-view.ts +71 -0
  91. package/src/connect-modal/connect-modal.base.ts +18 -0
  92. package/src/connect-modal/connect-modal.config.ts +27 -0
  93. package/src/connect-modal/connect-modal.templates.ts +21 -0
  94. package/src/connect-modal/connect-modal.ts +270 -0
  95. package/src/connect-modal/connect-modal.types.ts +104 -0
  96. package/src/connect-modal/images/up-cube-glass.png +0 -0
  97. package/src/connect-modal/index.ts +23 -0
  98. package/src/connect-modal/services/wagmi.ts +266 -0
  99. package/src/connect-modal/styles/styles.css +1 -0
  100. package/src/connect-modal/utils/walletConnectDeepLinkUrl.ts +43 -0
  101. package/src/connector.ts +544 -0
  102. package/src/index.ts +62 -0
  103. package/src/popup-instance.ts +537 -0
  104. package/src/restore-modal.ts +702 -0
  105. package/src/styles/index.ts +28 -0
  106. package/src/styles/styles.css +1 -0
  107. package/src/types/css-raw.d.ts +4 -0
  108. package/src/types/images.d.ts +4 -0
  109. package/src/types.ts +168 -0
@@ -0,0 +1,702 @@
1
+ /**
2
+ * Restore Modal - Lit Component
3
+ *
4
+ * Framework-agnostic restore modal for wallet data import.
5
+ * Uses lukso-modal from @lukso/web-components for consistent UI.
6
+ */
7
+
8
+ import type {
9
+ BackupFile,
10
+ PasskeyAuthProvider,
11
+ WalletData,
12
+ } from '@lukso/passkey-auth'
13
+ import { readBackupFile } from '@lukso/passkey-auth'
14
+ import { html, nothing } from 'lit'
15
+ import { customElement, property, state } from 'lit/decorators.js'
16
+ import { CoreLitElement } from './styles'
17
+
18
+ // Import lukso web components
19
+ import '@lukso/web-components/dist/components/lukso-modal'
20
+ import '@lukso/web-components/dist/components/lukso-button'
21
+ import '@lukso/web-components/dist/components/lukso-input'
22
+
23
+ enum Step {
24
+ UPLOAD = 1,
25
+ PASSWORD = 2,
26
+ PREVIEW = 3,
27
+ SUCCESS = 4,
28
+ }
29
+
30
+ @customElement('restore-modal')
31
+ export class RestoreModal extends CoreLitElement {
32
+ // Public properties
33
+ @property({ type: Boolean, reflect: true }) isOpen = false
34
+ @property({ type: String }) theme: 'light' | 'dark' | 'auto' = 'auto'
35
+ @property({ type: Object }) keyGenerator: PasskeyAuthProvider | null = null
36
+
37
+ // Private state
38
+ @state() private currentStep: Step = Step.UPLOAD
39
+ @state() private selectedFile: File | null = null
40
+ @state() private backupData: BackupFile | null = null
41
+ @state() private fileError: string | null = null
42
+ @state() private isDragging = false
43
+ @state() private password = ''
44
+ @state() private passwordErrors: string[] = []
45
+ @state() private isRestoring = false
46
+ @state() private walletData: WalletData | null = null
47
+ @state() private validationWarnings: string[] = []
48
+ @state() private validationInfo: string[] = []
49
+ @state() private controllers: Array<{
50
+ address: string
51
+ status: 'new' | 'existing'
52
+ }> = []
53
+ @state() private restoredAddresses: string[] = []
54
+
55
+ /**
56
+ * Public method for parent to set preview result
57
+ * Called after parent handles the 'preview' event
58
+ */
59
+ public setPreviewResult(options: {
60
+ controllers: Array<{ address: string; status: 'new' | 'existing' }>
61
+ warnings?: string[]
62
+ }) {
63
+ this.controllers = options.controllers
64
+ this.validationWarnings = options.warnings || []
65
+ }
66
+
67
+ /**
68
+ * Public method for parent to set validation result
69
+ * Called after parent handles the 'validate' event
70
+ */
71
+ public setValidationResult(
72
+ walletData: WalletData | null,
73
+ options?: {
74
+ error?: string
75
+ warnings?: string[]
76
+ info?: string[]
77
+ }
78
+ ) {
79
+ this.isRestoring = false
80
+
81
+ if (options?.error) {
82
+ this.passwordErrors = [options.error]
83
+ this.validationWarnings = []
84
+ this.validationInfo = []
85
+ } else if (walletData) {
86
+ this.walletData = walletData
87
+ this.validationWarnings = options?.warnings || []
88
+ this.validationInfo = options?.info || []
89
+ this.currentStep = Step.SUCCESS
90
+ }
91
+ }
92
+
93
+ render() {
94
+ return html`
95
+ <lukso-modal
96
+ ?is-open=${this.isOpen}
97
+ size="medium"
98
+ @on-backdrop-click=${this.close}
99
+ >
100
+ <div class="p-6">
101
+ <!-- Header -->
102
+ <h2 class="m-0 mb-6 text-neutral-20 dark:text-white heading-inter-21-semi-bold">
103
+ ${this.getStepTitle()}
104
+ </h2>
105
+
106
+ <!-- Step content -->
107
+ ${this.renderStep()}
108
+ </div>
109
+ </lukso-modal>
110
+ `
111
+ }
112
+
113
+ private renderStep() {
114
+ switch (this.currentStep) {
115
+ case Step.UPLOAD:
116
+ return this.renderUploadStep()
117
+ case Step.PASSWORD:
118
+ return this.renderPasswordStep()
119
+ case Step.PREVIEW:
120
+ return this.renderPreviewStep()
121
+ case Step.SUCCESS:
122
+ return this.renderSuccessStep()
123
+ default:
124
+ return nothing
125
+ }
126
+ }
127
+
128
+ private renderUploadStep() {
129
+ return html`
130
+ <p class="mb-6 text-neutral-40 dark:text-neutral-50 paragraph-inter-16-regular">
131
+ Select your backup file to restore your wallet data. Your backup file stays on your device and is never uploaded.
132
+ </p>
133
+
134
+ <div
135
+ class="border-2 border-dashed rounded-xl p-10 text-center bg-neutral-95 dark:bg-neutral-80 transition-all cursor-pointer my-6 ${
136
+ this.isDragging
137
+ ? 'border-blue-60 bg-blue-95 dark:bg-blue-95/10'
138
+ : this.fileError
139
+ ? 'border-red-55 bg-red-95'
140
+ : 'border-neutral-90 dark:border-neutral-70'
141
+ }"
142
+ @drop=${this.handleDrop}
143
+ @dragover=${this.handleDragOver}
144
+ @dragleave=${this.handleDragLeave}
145
+ @click=${this.triggerFileInput}
146
+ >
147
+ <div class="flex flex-col items-center gap-3">
148
+ <p class="m-0 text-neutral-10 dark:text-white heading-inter-16-semi-bold">
149
+ Drag and drop your backup file here
150
+ </p>
151
+ <p class="m-0 text-neutral-40 paragraph-inter-14-regular">or</p>
152
+ <lukso-button
153
+ variant="primary"
154
+ @click=${(e: Event) => {
155
+ e.stopPropagation()
156
+ this.triggerFileInput()
157
+ }}
158
+ >
159
+ Choose File
160
+ </lukso-button>
161
+ ${
162
+ this.selectedFile
163
+ ? html`
164
+ <p class="mt-2 mb-0 text-green-54 paragraph-inter-14-semi-bold">
165
+ ${this.selectedFile.name}
166
+ </p>
167
+ `
168
+ : nothing
169
+ }
170
+ ${
171
+ this.fileError
172
+ ? html`
173
+ <p class="mt-2 mb-0 text-red-55 paragraph-inter-14-semi-bold">
174
+ ${this.fileError}
175
+ </p>
176
+ `
177
+ : nothing
178
+ }
179
+ </div>
180
+ </div>
181
+
182
+ <input
183
+ type="file"
184
+ class="hidden"
185
+ accept=".json"
186
+ @change=${this.handleFileSelect}
187
+ />
188
+
189
+ <div class="flex justify-end mt-6">
190
+ <lukso-button variant="text" @click=${this.close}>
191
+ Cancel
192
+ </lukso-button>
193
+ </div>
194
+ `
195
+ }
196
+
197
+ private renderPasswordStep() {
198
+ const canProceed = this.password && this.password.length > 0
199
+
200
+ return html`
201
+ <p class="mb-5 text-neutral-40 dark:text-neutral-50 paragraph-inter-14-regular">
202
+ This backup is encrypted. Enter the password to decrypt it.
203
+ </p>
204
+
205
+ <div>
206
+ <lukso-input
207
+ type="password"
208
+ label="Password"
209
+ placeholder="Enter decryption password"
210
+ .value=${this.password}
211
+ @on-input=${(e: CustomEvent) => (this.password = e.detail.value)}
212
+ @keyup=${(e: KeyboardEvent) => e.key === 'Enter' && canProceed && this.previewRestore()}
213
+ is-full-width
214
+ autofocus
215
+ ></lukso-input>
216
+ ${this.passwordErrors.map(
217
+ (error) => html`
218
+ <div class="mt-1 text-red-55 paragraph-inter-13-regular">${error}</div>
219
+ `
220
+ )}
221
+ </div>
222
+
223
+ <div class="flex justify-between gap-3 mt-6">
224
+ <div class="flex gap-3">
225
+ <lukso-button variant="text" @click=${this.close}>
226
+ Cancel
227
+ </lukso-button>
228
+ <lukso-button variant="secondary" @click=${this.goBack}>
229
+ Back
230
+ </lukso-button>
231
+ </div>
232
+ <lukso-button
233
+ variant="primary"
234
+ ?disabled=${!canProceed || this.isRestoring}
235
+ ?is-loading=${this.isRestoring}
236
+ @click=${this.previewRestore}
237
+ >
238
+ Continue
239
+ </lukso-button>
240
+ </div>
241
+ `
242
+ }
243
+
244
+ private renderPreviewStep() {
245
+ return html`
246
+ <p class="mb-6 text-neutral-40 dark:text-neutral-50 paragraph-inter-16-regular">
247
+ ${
248
+ this.controllers.length > 0
249
+ ? `Found ${this.controllers.length} controller${this.controllers.length === 1 ? '' : 's'} in backup.`
250
+ : 'No controllers found in backup.'
251
+ }
252
+ </p>
253
+
254
+ ${
255
+ this.controllers.length > 0
256
+ ? html`
257
+ <div class="mb-6">
258
+ <div class="mb-3 text-neutral-20 dark:text-neutral-90 paragraph-inter-14-semi-bold">
259
+ Controllers:
260
+ </div>
261
+ ${this.controllers.map(
262
+ (controller) => html`
263
+ <div class="bg-neutral-95 dark:bg-neutral-80 border border-neutral-90 dark:border-neutral-70 rounded-lg px-3 py-2.5 mb-2">
264
+ <div class="flex items-center justify-between">
265
+ <div class="text-neutral-40 paragraph-inter-13-regular font-mono">
266
+ ${this.truncateAddress(controller.address)}
267
+ </div>
268
+ <div
269
+ class="px-2 py-0.5 rounded text-xs font-semibold ${
270
+ controller.status === 'new'
271
+ ? 'bg-green-95 dark:bg-green-95/10 text-green-55 border border-green-55/30'
272
+ : 'bg-neutral-90 dark:bg-neutral-70 text-neutral-50 border border-neutral-80 dark:border-neutral-60'
273
+ }"
274
+ >
275
+ ${controller.status === 'new' ? 'NEW' : 'EXISTS'}
276
+ </div>
277
+ </div>
278
+ <div class="text-neutral-60 paragraph-inter-12-regular mt-1">
279
+ ${controller.address}
280
+ </div>
281
+ </div>
282
+ `
283
+ )}
284
+ </div>
285
+ `
286
+ : nothing
287
+ }
288
+
289
+ ${
290
+ this.validationWarnings.length > 0
291
+ ? html`
292
+ <div class="mb-4">
293
+ ${this.validationWarnings.map(
294
+ (warning) => html`
295
+ <div class="bg-yellow-95 dark:bg-yellow-95/10 border border-yellow-55 dark:border-yellow-55/50 rounded-lg px-3 py-2.5 mb-2 flex items-start gap-2">
296
+ <span class="text-yellow-55 dark:text-yellow-55 text-lg leading-none">⚠️</span>
297
+ <span class="text-yellow-55 dark:text-yellow-55 paragraph-inter-13-regular flex-1">${warning}</span>
298
+ </div>
299
+ `
300
+ )}
301
+ </div>
302
+ `
303
+ : nothing
304
+ }
305
+
306
+ <div class="flex justify-between gap-3 mt-6">
307
+ <div class="flex gap-3">
308
+ <lukso-button variant="text" @click=${this.close}>
309
+ Cancel
310
+ </lukso-button>
311
+ <lukso-button variant="secondary" @click=${this.goBackFromPreview}>
312
+ Back
313
+ </lukso-button>
314
+ </div>
315
+ <lukso-button
316
+ variant="primary"
317
+ @click=${this.confirmRestore}
318
+ >
319
+ Confirm Restore
320
+ </lukso-button>
321
+ </div>
322
+ `
323
+ }
324
+
325
+ private renderSuccessStep() {
326
+ return html`
327
+ <!-- Success header with icon -->
328
+ <div class="text-center mb-6">
329
+ <div class="w-16 h-16 mx-auto mb-4 bg-green-95 dark:bg-green-95/20 rounded-full flex items-center justify-center">
330
+ <span class="text-2xl text-green-55">✅</span>
331
+ </div>
332
+ <h3 class="text-neutral-20 dark:text-white heading-inter-18-semi-bold mb-2">
333
+ Wallet Restored Successfully!
334
+ </h3>
335
+ <p class="text-neutral-40 dark:text-neutral-50 paragraph-inter-14-regular">
336
+ ${
337
+ this.restoredAddresses.length > 0
338
+ ? `Imported ${this.restoredAddresses.length} address${this.restoredAddresses.length > 1 ? 'es' : ''} to your wallet.`
339
+ : 'Your wallet has been restored successfully.'
340
+ }
341
+ </p>
342
+ </div>
343
+
344
+ <!-- Restored addresses -->
345
+ ${
346
+ this.restoredAddresses.length > 0
347
+ ? html`
348
+ <div class="mb-6">
349
+ <div class="mb-3 text-neutral-20 dark:text-neutral-90 paragraph-inter-14-semi-bold">
350
+ Restored Addresses:
351
+ </div>
352
+ ${this.restoredAddresses.map(
353
+ (address: string) => html`
354
+ <div class="bg-green-95 dark:bg-green-95/10 border border-green-60 dark:border-green-60/50 rounded-lg px-3 py-2.5 mb-2 flex items-center gap-2">
355
+ <span class="text-green-60 dark:text-green-45 text-sm">✓</span>
356
+ <span class="text-neutral-20 dark:text-white paragraph-inter-13-regular font-mono flex-1">${this.truncateAddress(address)}</span>
357
+ </div>
358
+ `
359
+ )}
360
+ </div>
361
+ `
362
+ : ''
363
+ }
364
+
365
+ <!-- Action buttons for success -->
366
+ <div class="flex justify-center mt-6">
367
+ <lukso-button variant="primary" @click=${this.close}>
368
+ OK
369
+ </lukso-button>
370
+ </div>
371
+ `
372
+ }
373
+
374
+ private getStepTitle(): string {
375
+ switch (this.currentStep) {
376
+ case Step.UPLOAD:
377
+ return 'Restore Wallet'
378
+ case Step.PASSWORD:
379
+ return 'Enter Password'
380
+ case Step.PREVIEW:
381
+ return 'Review Changes'
382
+ case Step.SUCCESS:
383
+ return 'Restore Complete'
384
+ default:
385
+ return 'Restore Wallet'
386
+ }
387
+ }
388
+
389
+ private triggerFileInput() {
390
+ const input = this.shadowRoot?.querySelector(
391
+ 'input[type="file"]'
392
+ ) as HTMLInputElement
393
+ if (input) {
394
+ input.click()
395
+ }
396
+ }
397
+
398
+ private async handleFileSelect(e: Event) {
399
+ const input = e.target as HTMLInputElement
400
+ const file = input.files?.[0]
401
+ if (file) {
402
+ await this.handleFile(file)
403
+ }
404
+ }
405
+
406
+ private handleDragOver(e: DragEvent) {
407
+ e.preventDefault()
408
+ this.isDragging = true
409
+ }
410
+
411
+ private handleDragLeave() {
412
+ this.isDragging = false
413
+ }
414
+
415
+ private async handleDrop(e: DragEvent) {
416
+ e.preventDefault()
417
+ this.isDragging = false
418
+
419
+ const file = e.dataTransfer?.files?.[0]
420
+ if (file) {
421
+ await this.handleFile(file)
422
+ }
423
+ }
424
+
425
+ private async handleFile(file: File) {
426
+ this.fileError = null
427
+
428
+ if (!file.name.endsWith('.json')) {
429
+ this.fileError = 'Please select a valid JSON backup file'
430
+ return
431
+ }
432
+
433
+ try {
434
+ // Use passkey-auth readBackupFile function
435
+ const backup = await readBackupFile(file)
436
+
437
+ this.selectedFile = file
438
+ this.backupData = backup
439
+
440
+ // Check if encrypted (handle both current and legacy formats)
441
+ const isEncrypted = backup.encryptedSecrets // Legacy: always encrypted if encryptedSecrets exists
442
+ ? true
443
+ : backup.secrets?.encrypted // Current: check encrypted flag
444
+
445
+ if (isEncrypted) {
446
+ this.currentStep = Step.PASSWORD
447
+ } else {
448
+ // Unencrypted backup - preview directly
449
+ await this.previewRestore()
450
+ }
451
+ } catch (error) {
452
+ this.fileError =
453
+ error instanceof Error ? error.message : 'Failed to read backup file'
454
+ console.error('Failed to read backup file:', error)
455
+ }
456
+ }
457
+
458
+ private async previewRestore() {
459
+ if (!this.backupData) return
460
+
461
+ this.isRestoring = true
462
+ this.passwordErrors = []
463
+
464
+ try {
465
+ // First, we need to decrypt the backup if it's encrypted
466
+ let decryptedBackup = this.backupData
467
+
468
+ if (
469
+ this.backupData.encryptedSecrets ||
470
+ this.backupData.secrets?.encrypted
471
+ ) {
472
+ // PREVIEW ONLY: Use verify to check password and get preview data (don't actually import yet)
473
+ if (!this.keyGenerator) {
474
+ throw new Error('KeyGenerator not available')
475
+ }
476
+
477
+ // Use verifyBackup for preview - this validates password and returns public data only
478
+ const verifiedBackup = await this.keyGenerator.verifyBackup(
479
+ this.backupData,
480
+ this.password
481
+ )
482
+
483
+ // Use the verified backup data (which has filtered public data)
484
+ decryptedBackup = verifiedBackup
485
+ } else {
486
+ // Unencrypted - use data directly for preview (no import needed)
487
+ decryptedBackup = this.backupData
488
+ }
489
+
490
+ // Extract all controller addresses from the backup
491
+ const backupControllers = new Set<string>()
492
+
493
+ // Get addresses from the secrets (the actual private keys we're restoring)
494
+ const secretsData = (decryptedBackup.secrets?.data as any[]) || []
495
+ for (const secret of secretsData) {
496
+ if (secret.address) {
497
+ backupControllers.add(secret.address.toLowerCase())
498
+ }
499
+ }
500
+
501
+ // Also get controllers from account metadata (V2 format with networks)
502
+ for (const account of decryptedBackup.accounts || []) {
503
+ // V2 format: accounts have networks array with controllers
504
+ if (account.networks) {
505
+ for (const network of account.networks) {
506
+ for (const controller of network.controllers || []) {
507
+ if (controller.address) {
508
+ backupControllers.add(controller.address.toLowerCase())
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ // Also get controllers from LSP23CrossChainDeployment if present
516
+ const lsp23Deployments =
517
+ (decryptedBackup as any).LSP23CrossChainDeployment || []
518
+ for (const deployment of lsp23Deployments) {
519
+ for (const controller of deployment.initialControllers || []) {
520
+ if (controller.address) {
521
+ backupControllers.add(controller.address.toLowerCase())
522
+ }
523
+ }
524
+ }
525
+
526
+ console.log(
527
+ '🔍 [RestoreModal] Found controller addresses:',
528
+ Array.from(backupControllers)
529
+ )
530
+
531
+ // Extract addresses from the decrypted backup for preview
532
+ for (const secret of secretsData) {
533
+ if (secret.address) {
534
+ backupControllers.add(secret.address.toLowerCase())
535
+ }
536
+ }
537
+
538
+ // For now, mark all addresses as 'new' - could enhance this later
539
+ this.controllers = Array.from(backupControllers).map((address) => ({
540
+ address,
541
+ status: 'new' as const,
542
+ }))
543
+
544
+ this.currentStep = Step.PREVIEW
545
+ } catch (error) {
546
+ console.error('Failed to preview restore:', error)
547
+ const errorMessage =
548
+ error instanceof Error ? error.message : 'Failed to preview restore'
549
+
550
+ // Check for password-specific errors
551
+ if (errorMessage.toLowerCase().includes('password')) {
552
+ this.passwordErrors = ['Incorrect password. Please try again.']
553
+ } else {
554
+ // For non-password errors, set as file error to show in upload step
555
+ this.fileError = errorMessage
556
+ this.currentStep = Step.UPLOAD
557
+ // Clear the file selection so user can try again
558
+ this.selectedFile = null
559
+ this.backupData = null
560
+ // Clear the file input
561
+ const input = this.shadowRoot?.querySelector(
562
+ 'input[type="file"]'
563
+ ) as HTMLInputElement
564
+ if (input) {
565
+ input.value = ''
566
+ }
567
+ }
568
+ } finally {
569
+ this.isRestoring = false
570
+ }
571
+ }
572
+
573
+ private async confirmRestore() {
574
+ if (!this.backupData || !this.keyGenerator) {
575
+ console.error(
576
+ '❌ [RestoreModal] Missing backup data or keyGenerator for restore'
577
+ )
578
+ this.dispatchEvent(
579
+ new CustomEvent('error', {
580
+ detail: { error: 'Missing backup data or keyGenerator' },
581
+ bubbles: true,
582
+ composed: true,
583
+ })
584
+ )
585
+ return
586
+ }
587
+
588
+ try {
589
+ this.isRestoring = true
590
+ console.log('🔄 [RestoreModal] Starting secure restoration process...')
591
+
592
+ // ✅ SECURE: Use keyGenerator.restoreFromBackup internally
593
+ const result = await this.keyGenerator.restoreFromBackup(
594
+ this.backupData,
595
+ this.password
596
+ )
597
+
598
+ if (result.success) {
599
+ console.log('✅ [RestoreModal] Restore successful:', result.addresses)
600
+
601
+ // Store restored addresses for display on success screen
602
+ this.restoredAddresses = result.addresses
603
+
604
+ // Emit success event (only public addresses, no sensitive data)
605
+ this.dispatchEvent(
606
+ new CustomEvent('success', {
607
+ detail: { addresses: result.addresses },
608
+ bubbles: true,
609
+ composed: true,
610
+ })
611
+ )
612
+
613
+ // Move to success step
614
+ this.currentStep = Step.SUCCESS
615
+ } else {
616
+ console.error('❌ [RestoreModal] Restore failed')
617
+ this.dispatchEvent(
618
+ new CustomEvent('error', {
619
+ detail: {
620
+ error:
621
+ 'Restore failed. Please check the backup file and password.',
622
+ },
623
+ bubbles: true,
624
+ composed: true,
625
+ })
626
+ )
627
+ }
628
+ } catch (error) {
629
+ console.error('❌ [RestoreModal] Restore error:', error)
630
+ const errorMessage =
631
+ error instanceof Error ? error.message : 'Unknown error'
632
+
633
+ this.dispatchEvent(
634
+ new CustomEvent('error', {
635
+ detail: { error: errorMessage },
636
+ bubbles: true,
637
+ composed: true,
638
+ })
639
+ )
640
+ } finally {
641
+ this.isRestoring = false
642
+ }
643
+ }
644
+
645
+ private goBack() {
646
+ if (this.currentStep === Step.PASSWORD) {
647
+ this.currentStep = Step.UPLOAD
648
+ this.password = ''
649
+ this.passwordErrors = []
650
+ }
651
+ }
652
+
653
+ private goBackFromPreview() {
654
+ // Go back to password step (or upload if unencrypted)
655
+ const isEncrypted = this.backupData?.encryptedSecrets
656
+ ? true
657
+ : this.backupData?.secrets?.encrypted
658
+
659
+ if (isEncrypted) {
660
+ this.currentStep = Step.PASSWORD
661
+ } else {
662
+ this.currentStep = Step.UPLOAD
663
+ }
664
+ // Clear preview data
665
+ this.controllers = []
666
+ this.validationWarnings = []
667
+ }
668
+
669
+ private close() {
670
+ this.isOpen = false
671
+ this.dispatchEvent(
672
+ new CustomEvent('close', { bubbles: true, composed: true })
673
+ )
674
+
675
+ // Reset state after animation
676
+ setTimeout(() => {
677
+ this.currentStep = Step.UPLOAD
678
+ this.selectedFile = null
679
+ this.backupData = null
680
+ this.fileError = null
681
+ this.isDragging = false
682
+ this.password = ''
683
+ this.passwordErrors = []
684
+ this.isRestoring = false
685
+ this.walletData = null
686
+ this.restoredAddresses = []
687
+ this.controllers = []
688
+ this.validationWarnings = []
689
+ }, 300)
690
+ }
691
+
692
+ private truncateAddress(address: string): string {
693
+ if (!address || address.length < 10) return address
694
+ return `${address.slice(0, 6)}...${address.slice(-4)}`
695
+ }
696
+ }
697
+
698
+ declare global {
699
+ interface HTMLElementTagNameMap {
700
+ 'restore-modal': RestoreModal
701
+ }
702
+ }