@lukso/transaction-view-core 0.3.1-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 (62) hide show
  1. package/.turbo/turbo-build.log +33 -0
  2. package/CHANGELOG.md +104 -0
  3. package/LICENSE +201 -0
  4. package/README.md +264 -0
  5. package/dist/index.cjs +4573 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +493 -0
  8. package/dist/index.d.ts +493 -0
  9. package/dist/index.js +4534 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/shared/index.cjs +2480 -0
  12. package/dist/shared/index.cjs.map +1 -0
  13. package/dist/shared/index.d.cts +887 -0
  14. package/dist/shared/index.d.ts +887 -0
  15. package/dist/shared/index.js +2437 -0
  16. package/dist/shared/index.js.map +1 -0
  17. package/dist/styles/index.cjs +58 -0
  18. package/dist/styles/index.cjs.map +1 -0
  19. package/dist/styles/index.d.cts +9 -0
  20. package/dist/styles/index.d.ts +9 -0
  21. package/dist/styles/index.js +31 -0
  22. package/dist/styles/index.js.map +1 -0
  23. package/package.json +57 -0
  24. package/publish.log +0 -0
  25. package/src/components/asset-card.ts +426 -0
  26. package/src/components/contract-card.ts +238 -0
  27. package/src/components/description-view.ts +605 -0
  28. package/src/components/index.ts +20 -0
  29. package/src/components/info-panel.ts +437 -0
  30. package/src/components/permissions-block.ts +138 -0
  31. package/src/components/transaction-playback.ts +429 -0
  32. package/src/components/universal-token-view.ts +281 -0
  33. package/src/declaration.d.ts +4 -0
  34. package/src/examples/INTEGRATION_GUIDE.md +240 -0
  35. package/src/examples/TransactionContentWithReplacement.vue +269 -0
  36. package/src/index.ts +38 -0
  37. package/src/registry/index.ts +31 -0
  38. package/src/registry/lit-view-loader.ts +191 -0
  39. package/src/shared/address-display.ts +370 -0
  40. package/src/shared/address-view.ts +385 -0
  41. package/src/shared/custom-select.ts +164 -0
  42. package/src/shared/function-name-view.ts +89 -0
  43. package/src/shared/gas-provider-view.ts +218 -0
  44. package/src/shared/icon-view.ts +288 -0
  45. package/src/shared/image-list.ts +261 -0
  46. package/src/shared/image-loader-controller.ts +160 -0
  47. package/src/shared/index.ts +34 -0
  48. package/src/shared/load-web-components.ts +56 -0
  49. package/src/shared/notification-view.ts +132 -0
  50. package/src/shared/progress-view.ts +86 -0
  51. package/src/shared/safe-custom-element.ts +35 -0
  52. package/src/shared/scroll-to-bottom-button.ts +55 -0
  53. package/src/shared/signal-controller.ts +124 -0
  54. package/src/shared/transaction-view-controller.ts +345 -0
  55. package/src/shared/url-view.ts +129 -0
  56. package/src/shared/value-view.ts +510 -0
  57. package/src/styles/index.ts +28 -0
  58. package/src/styles/styles.css +1 -0
  59. package/src/vue-entry.ts +15 -0
  60. package/tailwind.config.cjs +9 -0
  61. package/tsconfig.json +22 -0
  62. package/tsup.config.ts +90 -0
@@ -0,0 +1,426 @@
1
+ import { html } from 'lit'
2
+ import { property, state } from 'lit/decorators.js'
3
+ import { repeat } from 'lit/directives/repeat.js'
4
+ import { when } from 'lit/directives/when.js'
5
+
6
+ import '../shared/icon-view.js'
7
+ import '../shared/address-view.js'
8
+ import '../shared/value-view.js'
9
+ import '../shared/image-list.js'
10
+ // Import LUKSO web components
11
+ import '@lukso/web-components'
12
+ import { Task } from '@lit/task'
13
+ import {
14
+ ResolvedAddress,
15
+ useAddressResolver,
16
+ } from '@lukso/transaction-view-headless'
17
+ import { safeCustomElement } from '../shared/safe-custom-element.js'
18
+ import { CoreLitElement } from '../styles'
19
+
20
+ // Value formatting utilities
21
+ function formatUnits(value: bigint, decimals = 18): string {
22
+ const divisor = 10n ** BigInt(decimals)
23
+ const quotient = value / divisor
24
+ const remainder = value % divisor
25
+
26
+ if (remainder === 0n) {
27
+ return quotient.toString()
28
+ }
29
+
30
+ const remainderStr = remainder.toString().padStart(decimals, '0')
31
+ const trimmed = remainderStr.replace(/0+$/, '')
32
+ return `${quotient}.${trimmed}`
33
+ }
34
+
35
+ /**
36
+ * Asset card component for displaying token/NFT information
37
+ * Replaces the DigitalAssetInfo helper component
38
+ */
39
+ @safeCustomElement('asset-card')
40
+ export class AssetCard extends CoreLitElement {
41
+ @property() address: string | undefined
42
+
43
+ @property({ type: String, attribute: 'token-id' })
44
+ tokenId: string | undefined
45
+
46
+ @property({ type: String })
47
+ amount: string | undefined
48
+
49
+ /** Function name associated with the asset (e.g., "LSP4DigitalAsset") */
50
+ @property({ type: String, attribute: 'function-name' })
51
+ functionName: string | undefined
52
+
53
+ /** Standard associated with the asset (e.g., "LSP4") */
54
+ @property({ type: String, attribute: 'standard' })
55
+ standard: string | undefined
56
+
57
+ /** Function icon name */
58
+ @property({ type: String, attribute: 'function-icon' })
59
+ functionIcon: string = 'code-outline'
60
+
61
+ @property({ type: String })
62
+ size: 'small' | 'medium' | 'large' = 'medium'
63
+
64
+ @property({ type: Boolean, attribute: 'show-info-button' })
65
+ showInfoButton = true
66
+
67
+ @property({ type: Boolean, attribute: 'show-creator' })
68
+ showCreator = true
69
+
70
+ @state()
71
+ private displayDetails = false
72
+
73
+ @state()
74
+ private _resolvedData?: ResolvedAddress
75
+
76
+ @state()
77
+ private _imageError = false
78
+
79
+ private _handleImageError() {
80
+ this._imageError = true
81
+ }
82
+
83
+ private _handleInfoClick() {
84
+ this.displayDetails = !this.displayDetails
85
+ }
86
+
87
+ // Composables
88
+ private addressResolver = useAddressResolver()
89
+
90
+ private _resolutionTask = new Task(this, {
91
+ task: async ([address]) => {
92
+ if (!address) return null
93
+
94
+ const { resolveAddress } = this.addressResolver
95
+ const data = await resolveAddress(address as string)
96
+ this._resolvedData = data
97
+ return data
98
+ },
99
+ args: () => [this.address] as const,
100
+ })
101
+
102
+ private _extractBaseAddress(addressOrComposite: string): string {
103
+ return addressOrComposite.split('-')[0]
104
+ }
105
+
106
+ private _extractTokenId(addressOrComposite: string): string | undefined {
107
+ const parts = addressOrComposite.split('-')
108
+ return parts.length > 1 ? parts[1] : undefined
109
+ }
110
+
111
+ private _copyToClipboard(text: string) {
112
+ if (navigator.clipboard) {
113
+ navigator.clipboard.writeText(text)
114
+ // Could dispatch a 'copied' event here for toast notifications
115
+ this.dispatchEvent(new CustomEvent('copied', { detail: { text } }))
116
+ }
117
+ }
118
+
119
+ private _renderAddressSection() {
120
+ const data = this._resolutionTask.value
121
+ if (!data) return ''
122
+
123
+ return html`
124
+ <div class="flex items-center justify-between cursor-pointer">
125
+ ${when(
126
+ data.lsp4TokenSymbol,
127
+ () => html`
128
+ <address-view
129
+ address=${this._extractBaseAddress(data.address)}
130
+ size="medium"
131
+ max-width=${100}
132
+ .showIdenticon=${false}
133
+ ?is-card-view=${false}
134
+ ?show-copy-button=${false}
135
+ address-color="neutral-60"
136
+ ></address-view>
137
+ `
138
+ )}
139
+ ${when(
140
+ this.showInfoButton,
141
+ () => html`
142
+ <lukso-icon name="information" size="small" @click=${this._handleInfoClick}></lukso-icon>
143
+ `
144
+ )}
145
+ </div>
146
+ `
147
+ }
148
+
149
+ private _renderCreatorSection() {
150
+ const owner = (this._resolvedData as any)?.owner_id
151
+ const creators = (this._resolvedData as any)?.lsp4Creators || []
152
+ const resolvedCreator = owner || creators[0]?.profile_id
153
+ if (!resolvedCreator) return ''
154
+
155
+ return html`
156
+ <div class="gap-2">
157
+ <div class="text-neutral-60 text-[12px] font-normal">Created by</div>
158
+ <div class="flex justify-center flex-col gap-1 text-[12px] font-bold font-mono">
159
+ <icon-view
160
+ address=${owner}
161
+ size="small"
162
+ name-position="beside"
163
+ ></icon-view>
164
+ </div>
165
+ </div>
166
+ `
167
+ }
168
+
169
+ private _renderContractAddress() {
170
+ const data = this._resolutionTask.value
171
+
172
+ return html`
173
+ <div class="section">
174
+ ${when(
175
+ data.lsp4TokenSymbol,
176
+ () => html`
177
+ <address-view
178
+ address=${data.address}
179
+ size="medium"
180
+ label=${data.lsp4TokenName}
181
+ max-width=${100}
182
+ .showIdenticon=${false}
183
+ ?is-card-view=${true}
184
+ ?show-copy-button=${true}
185
+ address-color="neutral-60"
186
+ ></address-view>
187
+ `
188
+ )}
189
+ </div>
190
+ `
191
+ }
192
+
193
+ private _renderAssetLongName() {
194
+ const data = this._resolutionTask.value
195
+
196
+ return html`
197
+ ${when(
198
+ data?.lsp4TokenName,
199
+ () => html`
200
+ <value-view
201
+ ?is-card-view=${true}
202
+ ?show-copy-button=${true}
203
+ value=${data.lsp4TokenName}
204
+ label="Token Name"
205
+ ></value-view>
206
+ `
207
+ )}
208
+ `
209
+ }
210
+
211
+ private _renderAssetSymbol() {
212
+ const data = this._resolutionTask.value
213
+
214
+ return html`
215
+ ${when(
216
+ data?.lsp4TokenSymbol,
217
+ () => html`
218
+ <value-view
219
+ ?is-card-view=${true}
220
+ ?show-copy-button=${true}
221
+ value=${data.lsp4TokenSymbol}
222
+ label="Symbol"
223
+ ></value-view>
224
+ `
225
+ )}
226
+ `
227
+ }
228
+
229
+ private get displayedTokenId() {
230
+ const formattedTokenId = (this._resolvedData as any)?.formattedTokenId
231
+ const displayTokenId = formattedTokenId ? `${formattedTokenId}` : ''
232
+ if (displayTokenId) return displayTokenId
233
+ // If its a collection, do not show tokenId
234
+ return this.tokenId && !this._resolvedData?.isCollection
235
+ ? `${parseInt(this.tokenId, 16)}`
236
+ : undefined
237
+ }
238
+
239
+ private _renderTokenId() {
240
+ const displayTokenId = this.displayedTokenId
241
+ if (!displayTokenId) return ''
242
+
243
+ return html`
244
+ <value-view
245
+ ?is-card-view=${true}
246
+ ?show-copy-button=${true}
247
+ value=${displayTokenId}
248
+ label="Token ID"
249
+ ></value-view>
250
+ `
251
+ }
252
+
253
+ private _renderDescription() {
254
+ const description = (this._resolvedData as any)?.description
255
+ if (!description) return ''
256
+
257
+ return html`
258
+ <value-view
259
+ ?is-card-view=${true}
260
+ ?show-copy-button=${true}
261
+ value=${description}
262
+ label="Description"
263
+ >
264
+ </value-view>
265
+ `
266
+ }
267
+
268
+ private _renderLinks() {
269
+ const links = (this._resolvedData as any)?.links || []
270
+ if (!links.length) return ''
271
+
272
+ return html`
273
+ <div class="section">
274
+ <div class="section-title">🔗 Links</div>
275
+ <ul class="links-list">
276
+ ${repeat(
277
+ links,
278
+ (link: any) => link.url,
279
+ (link: any) => html`
280
+ <li class="link-item">
281
+ <a
282
+ class="external-link"
283
+ href="${link.url}"
284
+ target="_blank"
285
+ rel="noopener noreferrer"
286
+ >
287
+ ${link.title || link.url}
288
+ <span>↗</span>
289
+ </a>
290
+ </li>
291
+ `
292
+ )}
293
+ </ul>
294
+ </div>
295
+ `
296
+ }
297
+
298
+ private _renderImages() {
299
+ const images = (this._resolvedData as any)?.images || []
300
+ if (images.length === 0) return ''
301
+
302
+ return html`
303
+ <image-list
304
+ address=${this.address}
305
+ image-hint="images"
306
+ size="medium"
307
+ ?is-card-view=${true}
308
+ ></image-list>
309
+ `
310
+ }
311
+
312
+ private _renderAssets() {
313
+ const assets = (this._resolvedData as any)?.assets || []
314
+ if (assets.length === 0) return ''
315
+
316
+ return html`
317
+ <div class="section">
318
+ <div class="section-title">Assets</div>
319
+ <image-list
320
+ address=${this.address}
321
+ image-hint="assets"
322
+ size="medium"
323
+ ></image-list>
324
+ </div>
325
+ `
326
+ }
327
+
328
+ private _renderStandards() {
329
+ return html`
330
+ <div class="flex justify-between items-center my-4">
331
+ <function-name-view
332
+ label=${this.functionName}
333
+ size="small"
334
+ icon=${this.functionIcon ? this.functionIcon : 'code-outline'}
335
+ ></function-name-view>
336
+ <function-name-view label=${this._resolvedData?.standard} size="small"></function-name-view>
337
+ </div>
338
+ `
339
+ }
340
+
341
+ private _renderTokenDetails() {
342
+ if (!this.displayDetails) return ''
343
+ return html`
344
+ <div class="p-4 mt-6 flex flex-col border border-neutral-90 rounded-xl gap-4">
345
+ ${this._renderContractAddress()}
346
+ ${this._renderAssetLongName()}
347
+ ${this._renderAssetSymbol()}
348
+ ${this._renderTokenId()}
349
+ ${this._renderDescription()}
350
+ ${this._renderLinks()}
351
+ ${this._renderImages()}
352
+ ${this._renderAssets()}
353
+ </div>
354
+ `
355
+ }
356
+
357
+ private renderFormattedTokenIdBadge() {
358
+ if (!this.displayedTokenId) return ''
359
+ return html`
360
+ <span class="absolute top-3 left-3 z-10 text-[12px] text-neutral-20 font-mono font-bold bg-neutral-98 px-2 rounded-full">#${this.displayedTokenId}</span>
361
+ `
362
+ }
363
+
364
+ render() {
365
+ return html`
366
+ <div >
367
+ ${this._renderStandards()}
368
+ <div class="mt-6 flex-col">
369
+ <div class="flex items-center justify-center gap-4">
370
+ <div class="flex justify-end w-[48%]">
371
+ <div class="border relative border-neutral-90 rounded-xl">
372
+ ${this.renderFormattedTokenIdBadge()}
373
+ <div class="p-2 relative">
374
+ <icon-view
375
+ address="${this.address}"
376
+ size="2x-large"
377
+ .showName=${false}
378
+ ></icon-view>
379
+ </div>
380
+ <div style="height: 1px;" class="bg-neutral-90 w-full"></div>
381
+ <div class="px-2">
382
+ ${this._renderAddressSection()}
383
+ </div>
384
+ </div>
385
+ </div>
386
+
387
+ <div class="flex flex-col gap-1 w-[51%]">
388
+ <div class="text-[14px] font-bold text-neutral-20">
389
+ ${this._resolvedData?.lsp4TokenName || 'UNKNOWN'}
390
+ </div>
391
+ <div class="text-[17px] font-semibold text-neutral-20">
392
+ ${when(
393
+ this.amount,
394
+ () => html`
395
+ <span class="">${formatUnits(BigInt(this.amount!), this._resolvedData?.decimals)}</span>
396
+ <span class="ml-1 text-neutral-60 max-w-[60px] whitespace-nowrap overflow-hidden text-ellipsis text-[14px] font-semibold">${
397
+ this._resolvedData?.lsp4TokenSymbol || ''
398
+ }</span>
399
+ `,
400
+ () => html`${this._resolvedData?.lsp4TokenSymbol || ''}`
401
+ )}
402
+ </div>
403
+
404
+ ${when(
405
+ this.displayedTokenId,
406
+ () =>
407
+ html`<div class="text-[14px] font-medium text-neutral-60"><span class="font-bold">#${this.displayedTokenId}</span></div>`
408
+ )}
409
+ <div class="mt-4">
410
+ ${when(this.showCreator, () => this._renderCreatorSection())}
411
+ </div>
412
+ </div>
413
+ </div>
414
+ <div class="border-neutral-90 pt-2">
415
+ ${this._renderTokenDetails()}
416
+ </div>
417
+ </div>
418
+ `
419
+ }
420
+ }
421
+
422
+ declare global {
423
+ interface HTMLElementTagNameMap {
424
+ 'asset-card': AssetCard
425
+ }
426
+ }
@@ -0,0 +1,238 @@
1
+ import { Task } from '@lit/task'
2
+ import {
3
+ type ResolvedAddress,
4
+ useAddressResolver,
5
+ useImageLoader,
6
+ } from '@lukso/transaction-view-headless'
7
+ import { css, html, LitElement, nothing } from 'lit'
8
+ import { property, state } from 'lit/decorators.js'
9
+ import { safeCustomElement } from '../shared/safe-custom-element'
10
+ import '../shared/load-web-components'
11
+
12
+ /**
13
+ * ContractCard Component
14
+ *
15
+ * A wrapper around lukso-card that auto-resolves contract/token metadata from an address or address-tokenId.
16
+ * Provides the same variants and styling as lukso-card with automatic data population.
17
+ *
18
+ * @example
19
+ * ```html
20
+ * <!-- Basic contract card -->
21
+ * <contract-card address="0x1234..."></contract-card>
22
+ *
23
+ * <!-- Profile variant with token ID -->
24
+ * <contract-card
25
+ * address="0x1234..."
26
+ * variant="profile"
27
+ * ></contract-card>
28
+ *
29
+ * <!-- With custom slots -->
30
+ * <contract-card address="0x1234..." variant="with-header">
31
+ * <div slot="header">Custom Header</div>
32
+ * <div slot="content">Custom Content</div>
33
+ * </contract-card>
34
+ * ```
35
+ *
36
+ * @slot header - Custom header content (for with-header variant)
37
+ * @slot content - Custom content area
38
+ * @slot bottom - Bottom slot content
39
+ *
40
+ * @fires address-click - Fired when the card is clicked (if clickable=true)
41
+ */
42
+ @safeCustomElement('contract-card')
43
+ export class ContractCard extends LitElement {
44
+ /**
45
+ * Contract address or address-tokenId format
46
+ */
47
+ @property() address!: string
48
+
49
+ /**
50
+ * Pre-resolved address data (optional, for performance optimization)
51
+ */
52
+ @property({ type: Object }) resolved?: ResolvedAddress
53
+
54
+ /**
55
+ * Card variant (maps to lukso-card variants)
56
+ * - basic: Simple card with content slot
57
+ * - with-header: Card with header and content sections
58
+ * - profile: Profile card layout
59
+ * - profile-2: Alternative profile layout
60
+ * - hero: Large hero-style card
61
+ * - dapp: DApp-specific card layout
62
+ */
63
+ @property() variant:
64
+ | 'basic'
65
+ | 'with-header'
66
+ | 'profile'
67
+ | 'profile-2'
68
+ | 'hero'
69
+ | 'dapp' = 'basic'
70
+
71
+ /**
72
+ * Border radius size
73
+ */
74
+ @property({ attribute: 'border-radius' }) borderRadius:
75
+ | 'small'
76
+ | 'medium'
77
+ | 'none' = 'medium'
78
+
79
+ /**
80
+ * Shadow size
81
+ */
82
+ @property() shadow: 'small' | 'medium' | 'large' = 'large'
83
+
84
+ /**
85
+ * Card width in pixels
86
+ */
87
+ @property({ type: Number }) width?: number
88
+
89
+ /**
90
+ * Card height in pixels
91
+ */
92
+ @property({ type: Number }) height?: number
93
+
94
+ /**
95
+ * Whether the card is hoverable
96
+ */
97
+ @property({ type: Boolean, attribute: 'is-hoverable' }) isHoverable = false
98
+
99
+ /**
100
+ * Whether the card has an overlay
101
+ */
102
+ @property({ type: Boolean, attribute: 'has-overlay' }) hasOverlay = false
103
+
104
+ /**
105
+ * Custom CSS class for the card
106
+ */
107
+ @property({ attribute: 'custom-class' }) customClass = ''
108
+
109
+ /**
110
+ * Custom CSS class for the header
111
+ */
112
+ @property({ attribute: 'header-class' }) headerClass = ''
113
+
114
+ @state() private _resolvedData?: ResolvedAddress
115
+ @state() private _profileImageUrl?: string
116
+ @state() private _backgroundImageUrl?: string
117
+
118
+ private _resolutionTask = new Task(this, {
119
+ task: async ([address, resolved]) => {
120
+ if (resolved) {
121
+ this._resolvedData = resolved
122
+ return resolved
123
+ }
124
+
125
+ const { resolveAddress } = useAddressResolver()
126
+ const baseAddress = this._extractBaseAddress(address as string)
127
+ const data = await resolveAddress(baseAddress)
128
+ this._resolvedData = data
129
+ return data
130
+ },
131
+ args: () => [this.address, this.resolved] as const,
132
+ })
133
+
134
+ private _profileImageTask = new Task(this, {
135
+ task: async ([resolved]) => {
136
+ const { loadProfileImage } = useImageLoader()
137
+ const data = resolved || this._resolvedData
138
+
139
+ if (!data) return null
140
+
141
+ const image = await loadProfileImage(data, {
142
+ width: 256,
143
+ height: 256,
144
+ dpr: window.devicePixelRatio || 1,
145
+ })
146
+
147
+ if (image?.src) {
148
+ this._profileImageUrl = image.src
149
+ }
150
+
151
+ return image
152
+ },
153
+ args: () => [this._resolvedData] as const,
154
+ })
155
+
156
+ private _backgroundImageTask = new Task(this, {
157
+ task: async ([resolved]) => {
158
+ const { loadBackgroundImage } = useImageLoader()
159
+ const data = resolved || this._resolvedData
160
+
161
+ if (!data) return null
162
+
163
+ const image = await loadBackgroundImage(data, {
164
+ width: 800,
165
+ height: 200,
166
+ dpr: window.devicePixelRatio || 1,
167
+ })
168
+
169
+ if (image?.src) {
170
+ this._backgroundImageUrl = image.src
171
+ }
172
+
173
+ return image
174
+ },
175
+ args: () => [this._resolvedData] as const,
176
+ })
177
+
178
+ private _extractBaseAddress(addressOrComposite: string): string {
179
+ return addressOrComposite.split('-')[0]
180
+ }
181
+
182
+ private _handleClick() {
183
+ this.dispatchEvent(
184
+ new CustomEvent('address-click', {
185
+ detail: {
186
+ address: this.address,
187
+ resolved: this._resolvedData,
188
+ },
189
+ bubbles: true,
190
+ composed: true,
191
+ })
192
+ )
193
+ }
194
+
195
+ static styles = css`
196
+ :host {
197
+ display: block;
198
+ }
199
+ `
200
+
201
+ render() {
202
+ const baseAddress = this._extractBaseAddress(this.address)
203
+ // is-eoa should ONLY be true when there's no __gqltype at all (actual EOA with no on-chain data)
204
+ // For Profiles, Tokens, NFTs - they all have __gqltype, so is-eoa should be false
205
+ // This ensures lukso-card shows the resolved profile-url image instead of blockie identicon
206
+ const isEoa = !this._resolvedData?.__gqltype
207
+
208
+ return html`
209
+ <lukso-card
210
+ variant="${this.variant}"
211
+ border-radius="${this.borderRadius}"
212
+ shadow="${this.shadow}"
213
+ ?is-hoverable="${this.isHoverable}"
214
+ ?has-overlay="${this.hasOverlay}"
215
+ ?is-eoa="${isEoa}"
216
+ custom-class="${this.customClass}"
217
+ header-class="${this.headerClass}"
218
+ profile-url="${this._profileImageUrl || ''}"
219
+ background-url="${this._backgroundImageUrl || ''}"
220
+ profile-address="${baseAddress}"
221
+ .width="${this.width}"
222
+ .height="${this.height}"
223
+ @click="${this._handleClick}"
224
+ >
225
+ <!-- Pass through all slots -->
226
+ <slot name="header" slot="header"></slot>
227
+ <slot name="content" slot="content"></slot>
228
+ <slot name="bottom" slot="bottom"></slot>
229
+ </lukso-card>
230
+ `
231
+ }
232
+ }
233
+
234
+ declare global {
235
+ interface HTMLElementTagNameMap {
236
+ 'contract-card': ContractCard
237
+ }
238
+ }