@1001-digital/components.evm 1.2.2

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 (44) hide show
  1. package/package.json +33 -0
  2. package/src/assets/wallets/coinbase.svg +4 -0
  3. package/src/assets/wallets/in-app.svg +5 -0
  4. package/src/assets/wallets/metamask.svg +1 -0
  5. package/src/assets/wallets/phantom.svg +4 -0
  6. package/src/assets/wallets/rabby.svg +24 -0
  7. package/src/assets/wallets/rainbow.svg +59 -0
  8. package/src/assets/wallets/safe.png +0 -0
  9. package/src/assets/wallets/walletconnect.svg +1 -0
  10. package/src/components/EvmAccount.vue +28 -0
  11. package/src/components/EvmAvatar.vue +62 -0
  12. package/src/components/EvmConnect.vue +300 -0
  13. package/src/components/EvmConnectDialog.vue +74 -0
  14. package/src/components/EvmConnectionStatus.vue +13 -0
  15. package/src/components/EvmConnectorQR.vue +85 -0
  16. package/src/components/EvmInAppWalletSetup.vue +247 -0
  17. package/src/components/EvmMetaMaskQR.vue +33 -0
  18. package/src/components/EvmProfile.vue +183 -0
  19. package/src/components/EvmSeedPhraseInput.vue +193 -0
  20. package/src/components/EvmSiwe.vue +188 -0
  21. package/src/components/EvmSiweDialog.vue +92 -0
  22. package/src/components/EvmSwitchNetwork.vue +128 -0
  23. package/src/components/EvmTransactionFlow.vue +348 -0
  24. package/src/components/EvmWalletConnectQR.vue +13 -0
  25. package/src/components/EvmWalletConnectWallets.vue +197 -0
  26. package/src/composables/base.ts +7 -0
  27. package/src/composables/chainId.ts +42 -0
  28. package/src/composables/ens.ts +113 -0
  29. package/src/composables/gasPrice.ts +37 -0
  30. package/src/composables/priceFeed.ts +116 -0
  31. package/src/composables/siwe.ts +89 -0
  32. package/src/composables/uri.ts +12 -0
  33. package/src/composables/walletExplorer.ts +130 -0
  34. package/src/config.ts +35 -0
  35. package/src/connectors/inAppWallet.ts +5 -0
  36. package/src/index.ts +60 -0
  37. package/src/utils/addresses.ts +6 -0
  38. package/src/utils/cache.ts +59 -0
  39. package/src/utils/chains.ts +32 -0
  40. package/src/utils/ens.ts +116 -0
  41. package/src/utils/format-eth.ts +15 -0
  42. package/src/utils/price.ts +17 -0
  43. package/src/utils/siwe.ts +70 -0
  44. package/src/utils/uri.ts +24 -0
@@ -0,0 +1,348 @@
1
+ <template>
2
+ <slot
3
+ :start="start"
4
+ :step="step"
5
+ :open="open"
6
+ name="start"
7
+ ></slot>
8
+
9
+ <Dialog
10
+ v-model:open="open"
11
+ :closable="canDismiss"
12
+ :click-outside="canDismiss"
13
+ :title="text.title[step]"
14
+ class="transaction-flow"
15
+ compat
16
+ >
17
+ <slot name="before" />
18
+
19
+ <Loading
20
+ v-if="step === 'requesting'"
21
+ spinner
22
+ stacked
23
+ :txt="connector?.name
24
+ ? `Requesting signature from ${connector.name}...`
25
+ : text.lead[step] || ''"
26
+ />
27
+
28
+ <p v-if="step !== 'requesting' && step !== 'error' && text.lead[step]">
29
+ {{ text.lead[step] }}
30
+ </p>
31
+
32
+ <Alert
33
+ v-if="error"
34
+ type="error"
35
+ >
36
+ <p v-if="text.lead[step]">{{ text.lead[step] }}</p>
37
+ <p>{{ error }}</p>
38
+ </Alert>
39
+
40
+ <slot
41
+ :name="step"
42
+ :cancel="cancel"
43
+ ></slot>
44
+
45
+ <template #footer>
46
+ <template v-if="step === 'chain'">
47
+ <Button
48
+ @click="cancel"
49
+ class="secondary"
50
+ >Cancel</Button
51
+ >
52
+ </template>
53
+
54
+ <template v-if="step === 'confirm' || step === 'error'">
55
+ <Button
56
+ @click="cancel"
57
+ class="secondary"
58
+ >Cancel</Button
59
+ >
60
+ <Button @click="() => initializeRequest()">
61
+ {{ text.action[step] || 'Execute' }}
62
+ </Button>
63
+ </template>
64
+
65
+ <slot
66
+ name="actions"
67
+ :step="step"
68
+ :cancel="cancel"
69
+ :execute="() => initializeRequest()"
70
+ :tx-link="txLink"
71
+ />
72
+ </template>
73
+ </Dialog>
74
+ </template>
75
+
76
+ <script setup lang="ts">
77
+ import { ref, computed, watch, onBeforeUnmount } from 'vue'
78
+ import { waitForTransactionReceipt, watchChainId } from '@wagmi/core'
79
+ import { useConfig, useConnection, type Config } from '@wagmi/vue'
80
+ import type { TransactionReceipt, Hash } from 'viem'
81
+ import { Dialog, Loading, Alert, Button, useToast, delay } from '@1001-digital/components'
82
+ import { useEnsureChainIdCheck, useBlockExplorer } from '../composables/chainId'
83
+
84
+ interface TextConfig {
85
+ title?: Record<string, string>
86
+ lead?: Record<string, string>
87
+ action?: Record<string, string>
88
+ }
89
+
90
+ type Step =
91
+ | 'idle'
92
+ | 'confirm'
93
+ | 'chain'
94
+ | 'requesting'
95
+ | 'waiting'
96
+ | 'complete'
97
+ | 'error'
98
+
99
+ const defaultText = {
100
+ title: {
101
+ confirm: 'Confirm Transaction',
102
+ chain: 'Switch Network',
103
+ requesting: 'Requesting',
104
+ waiting: 'Processing',
105
+ complete: 'Complete',
106
+ error: 'Error',
107
+ },
108
+ lead: {
109
+ confirm: 'Please review and confirm this transaction.',
110
+ chain: 'Please switch to the correct network to continue.',
111
+ requesting: 'Requesting transaction signature...',
112
+ waiting: 'Waiting for transaction confirmation...',
113
+ complete: 'Transaction confirmed successfully.',
114
+ },
115
+ action: {
116
+ confirm: 'Execute',
117
+ error: 'Try Again',
118
+ },
119
+ } satisfies TextConfig
120
+
121
+ const props = withDefaults(
122
+ defineProps<{
123
+ chain?: string
124
+ text?: TextConfig
125
+ request?: () => Promise<Hash>
126
+ delayAfter?: number
127
+ delayAutoclose?: number
128
+ skipConfirmation?: boolean
129
+ autoCloseSuccess?: boolean
130
+ dismissable?: boolean
131
+ }>(),
132
+ {
133
+ delayAfter: 2000,
134
+ delayAutoclose: 2000,
135
+ skipConfirmation: false,
136
+ autoCloseSuccess: true,
137
+ dismissable: true,
138
+ },
139
+ )
140
+
141
+ function isUserRejection(e: unknown): boolean {
142
+ const re = /reject|denied|cancel/i
143
+ let current = e as Record<string, unknown> | undefined
144
+ while (current) {
145
+ if ((current as { code?: number }).code === 4001) return true
146
+ if (re.test((current as { details?: string }).details || '')) return true
147
+ current = current.cause as Record<string, unknown> | undefined
148
+ }
149
+ return false
150
+ }
151
+
152
+ const checkChain = useEnsureChainIdCheck(props.chain)
153
+
154
+ const wagmiConfig = useConfig()
155
+ const { connector } = useConnection()
156
+ const blockExplorer = useBlockExplorer(props.chain)
157
+ const toast = useToast()
158
+
159
+ const emit = defineEmits<{
160
+ complete: [receipt: TransactionReceipt]
161
+ cancel: []
162
+ }>()
163
+
164
+ const text = computed<Required<TextConfig>>(() => ({
165
+ title: { ...defaultText.title, ...props.text?.title },
166
+ lead: { ...defaultText.lead, ...props.text?.lead },
167
+ action: { ...defaultText.action, ...props.text?.action },
168
+ }))
169
+
170
+ const step = ref<Step>('idle')
171
+
172
+ const open = computed({
173
+ get: () => step.value !== 'idle',
174
+ set: (v) => {
175
+ if (!v) {
176
+ step.value = 'idle'
177
+ error.value = ''
178
+ }
179
+ },
180
+ })
181
+
182
+ watchChainId(wagmiConfig as Config, {
183
+ async onChange() {
184
+ if (step.value !== 'chain') return
185
+
186
+ if (await checkChain()) {
187
+ initializeRequest()
188
+ }
189
+ },
190
+ })
191
+
192
+ const cachedRequest = ref(props.request)
193
+ watch(
194
+ () => props.request,
195
+ (v) => {
196
+ cachedRequest.value = v
197
+ },
198
+ )
199
+
200
+ const error = ref('')
201
+ const tx = ref<Hash | null>(null)
202
+ const receipt = ref<TransactionReceipt | null>(null)
203
+ const txLink = computed(() => `${blockExplorer}/tx/${tx.value}`)
204
+
205
+ let mounted = true
206
+ let progressTimer: ReturnType<typeof setInterval> | undefined
207
+
208
+ onBeforeUnmount(() => {
209
+ mounted = false
210
+ clearInterval(progressTimer)
211
+ })
212
+
213
+ const canDismiss = computed(
214
+ () => props.dismissable && step.value !== 'requesting',
215
+ )
216
+
217
+ const initializeRequest = async (request = cachedRequest.value) => {
218
+ cachedRequest.value = request
219
+ error.value = ''
220
+ tx.value = null
221
+ receipt.value = null
222
+ step.value = 'confirm'
223
+
224
+ if (!(await checkChain())) {
225
+ step.value = 'chain'
226
+ return
227
+ }
228
+
229
+ // Phase 1: Signing (dialog)
230
+ try {
231
+ step.value = 'requesting'
232
+ tx.value = await request!()
233
+ } catch (e: unknown) {
234
+ if (isUserRejection(e)) {
235
+ error.value = 'Transaction rejected by user.'
236
+ } else {
237
+ const err = e as { shortMessage?: string }
238
+ error.value = err.shortMessage || 'Error submitting transaction request.'
239
+ }
240
+ step.value = 'error'
241
+ console.log(e)
242
+ return
243
+ }
244
+
245
+ // Phase 2: Receipt (toast)
246
+ step.value = 'idle'
247
+
248
+ const link = `${blockExplorer}/tx/${tx.value}`
249
+ const toastId = toast.add({
250
+ variant: 'info',
251
+ title: text.value.title.waiting,
252
+ description: text.value.lead.waiting,
253
+ duration: Infinity,
254
+ loading: true,
255
+ progress: 0,
256
+ action: {
257
+ label: 'View on Block Explorer',
258
+ onClick: () => window.open(link, '_blank'),
259
+ persistent: true,
260
+ },
261
+ })
262
+
263
+ const start = Date.now()
264
+ progressTimer = setInterval(() => {
265
+ const elapsed = (Date.now() - start) / 1000
266
+ toast.update(toastId, { progress: Math.round(90 * (1 - Math.exp(-elapsed / 15))) })
267
+ }, 500)
268
+
269
+ try {
270
+ const receiptObject = await waitForTransactionReceipt(
271
+ wagmiConfig as Config,
272
+ { hash: tx.value },
273
+ )
274
+ clearInterval(progressTimer)
275
+ toast.update(toastId, { progress: 100, loading: false })
276
+ await delay(props.delayAfter)
277
+ receipt.value = receiptObject
278
+ emit('complete', receiptObject)
279
+
280
+ toast.update(toastId, {
281
+ variant: 'success',
282
+ title: text.value.title.complete,
283
+ description: text.value.lead.complete,
284
+ progress: false,
285
+ ...(props.autoCloseSuccess && { duration: props.delayAutoclose }),
286
+ })
287
+ } catch (e: unknown) {
288
+ clearInterval(progressTimer)
289
+ const err = e as { shortMessage?: string }
290
+ if (mounted) {
291
+ toast.dismiss(toastId)
292
+ error.value = err.shortMessage || 'Transaction failed.'
293
+ step.value = 'error'
294
+ } else {
295
+ toast.update(toastId, {
296
+ variant: 'error',
297
+ title: text.value.title.error,
298
+ description: err.shortMessage || 'Transaction failed.',
299
+ loading: false,
300
+ progress: false,
301
+ })
302
+ }
303
+ console.log(e)
304
+ }
305
+
306
+ return receipt.value
307
+ }
308
+
309
+ const start = () => {
310
+ if (props.skipConfirmation && step.value === 'idle') {
311
+ initializeRequest()
312
+ return
313
+ }
314
+
315
+ step.value = 'confirm'
316
+ }
317
+
318
+ const cancel = () => {
319
+ step.value = 'idle'
320
+ error.value = ''
321
+ emit('cancel')
322
+ }
323
+
324
+ defineExpose({
325
+ initializeRequest,
326
+ })
327
+ </script>
328
+
329
+ <style>
330
+ .transaction-flow > section {
331
+ display: grid;
332
+ gap: var(--spacer);
333
+
334
+ .text {
335
+ width: 100%;
336
+ height: min-content;
337
+ }
338
+
339
+ p {
340
+ white-space: pre-wrap;
341
+ width: 100%;
342
+
343
+ a {
344
+ text-decoration: underline;
345
+ }
346
+ }
347
+ }
348
+ </style>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <EvmConnectorQR :uri="uri">
3
+ <template #instruction> Scan the code in your wallet application </template>
4
+ </EvmConnectorQR>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import EvmConnectorQR from './EvmConnectorQR.vue'
9
+
10
+ defineProps<{
11
+ uri: string
12
+ }>()
13
+ </script>
@@ -0,0 +1,197 @@
1
+ <template>
2
+ <div class="wc-wallets">
3
+ <EvmConnectorQR :uri="uri">
4
+ <template #instruction> Scan with your wallet app </template>
5
+ </EvmConnectorQR>
6
+
7
+ <div class="separator">
8
+ <span>Or choose a wallet</span>
9
+ </div>
10
+
11
+ <FormItem>
12
+ <template #prefix>
13
+ <Icon type="lucide:search" />
14
+ </template>
15
+ <input
16
+ v-model="searchQuery"
17
+ type="text"
18
+ placeholder="Search wallets"
19
+ @input="onSearchInput"
20
+ />
21
+ </FormItem>
22
+
23
+ <div
24
+ v-if="displayedWallets.length > 0"
25
+ class="wallet-grid"
26
+ >
27
+ <Button
28
+ v-for="wallet in displayedWallets"
29
+ :key="wallet.id"
30
+ @click="openWallet(wallet)"
31
+ class="wallet-card tertiary"
32
+ >
33
+ <img
34
+ :src="explorer.imageUrl(wallet)"
35
+ :alt="wallet.name"
36
+ loading="lazy"
37
+ />
38
+ <span>{{ wallet.name }}</span>
39
+ </Button>
40
+ </div>
41
+
42
+ <p
43
+ v-if="
44
+ searchQuery &&
45
+ !explorer.searching.value &&
46
+ displayedWallets.length === 0
47
+ "
48
+ class="empty-state"
49
+ >
50
+ No wallets found
51
+ </p>
52
+
53
+ <Loading
54
+ v-if="explorer.loading.value || explorer.searching.value"
55
+ spinner
56
+ stacked
57
+ txt=""
58
+ />
59
+
60
+ <Button
61
+ class="link muted small"
62
+ @click="$emit('back')"
63
+ >
64
+ <Icon type="chevron-left" />
65
+ <span>Back</span>
66
+ </Button>
67
+ </div>
68
+ </template>
69
+
70
+ <script setup lang="ts">
71
+ import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
72
+ import { Button, FormItem, Icon, Loading } from '@1001-digital/components'
73
+ import EvmConnectorQR from './EvmConnectorQR.vue'
74
+ import {
75
+ useWalletExplorer,
76
+ type ExplorerWallet,
77
+ } from '../composables/walletExplorer'
78
+
79
+ const props = defineProps<{
80
+ uri: string
81
+ }>()
82
+
83
+ defineEmits<{
84
+ back: []
85
+ }>()
86
+
87
+ const explorer = useWalletExplorer()
88
+ const searchQuery = ref('')
89
+ let searchTimeout: ReturnType<typeof setTimeout>
90
+
91
+ const TOP_COUNT = 9
92
+
93
+ const displayedWallets = computed(() => {
94
+ if (searchQuery.value) return explorer.searchResults.value
95
+
96
+ const recents = explorer.recentWallets.value
97
+ const recentIds = new Set(recents.map((w) => w.id))
98
+ const rest = explorer.wallets.value
99
+ .filter((w) => !recentIds.has(w.id))
100
+ .slice(0, TOP_COUNT - recents.length)
101
+
102
+ return [...recents, ...rest]
103
+ })
104
+
105
+ function openWallet(wallet: ExplorerWallet) {
106
+ const href = explorer.walletHref(wallet, props.uri)
107
+ if (href) {
108
+ window.open(href, '_blank', 'noreferrer')
109
+ }
110
+ explorer.addRecent(wallet.id)
111
+ }
112
+
113
+ function onSearchInput() {
114
+ clearTimeout(searchTimeout)
115
+ searchTimeout = setTimeout(() => {
116
+ if (searchQuery.value) {
117
+ explorer.search(searchQuery.value)
118
+ }
119
+ }, 300)
120
+ }
121
+
122
+ onBeforeUnmount(() => {
123
+ clearTimeout(searchTimeout)
124
+ })
125
+
126
+ onMounted(() => {
127
+ explorer.fetchNextPage()
128
+ })
129
+ </script>
130
+
131
+ <style scoped>
132
+ .wc-wallets {
133
+ display: grid;
134
+ gap: var(--spacer);
135
+ }
136
+
137
+ /* Separator */
138
+ .separator {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: var(--spacer-sm);
142
+ @mixin ui-font;
143
+ color: var(--muted);
144
+ font-size: var(--font-sm);
145
+
146
+ &::before,
147
+ &::after {
148
+ content: '';
149
+ flex: 1;
150
+ border-top: var(--border);
151
+ }
152
+ }
153
+
154
+ /* Wallet grid */
155
+ .wallet-grid {
156
+ display: grid;
157
+ grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
158
+ gap: var(--spacer-sm);
159
+ }
160
+
161
+ .wallet-card {
162
+ display: flex;
163
+ flex-direction: column;
164
+ align-items: center;
165
+ gap: var(--spacer-sm);
166
+ block-size: auto;
167
+ min-inline-size: 0;
168
+ inline-size: 100%;
169
+
170
+ img {
171
+ width: var(--size-6);
172
+ height: var(--size-6);
173
+ border-radius: var(--border-radius);
174
+ margin-top: var(--spacer-xs);
175
+ }
176
+
177
+ span {
178
+ font-size: var(--font-xs);
179
+ text-align: center;
180
+ overflow: hidden;
181
+ text-overflow: ellipsis;
182
+ white-space: nowrap;
183
+ max-width: 100%;
184
+ }
185
+ }
186
+
187
+ .link.muted {
188
+ justify-self: center;
189
+ }
190
+
191
+ /* Empty state */
192
+ .empty-state {
193
+ text-align: center;
194
+ @mixin ui-font;
195
+ color: var(--muted);
196
+ }
197
+ </style>
@@ -0,0 +1,7 @@
1
+ import { useEvmConfig } from '../config'
2
+
3
+ export const useBaseURL = () => {
4
+ const config = useEvmConfig()
5
+ const base = config.baseURL || '/'
6
+ return base.endsWith('/') ? base : base + '/'
7
+ }
@@ -0,0 +1,42 @@
1
+ import { useConnection, useSwitchChain } from '@wagmi/vue'
2
+ import { useEvmConfig } from '../config'
3
+
4
+ interface ChainConfig {
5
+ id: number
6
+ blockExplorer: string
7
+ }
8
+
9
+ export const useChainConfig = (key?: string): ChainConfig => {
10
+ const evmConfig = useEvmConfig()
11
+ const resolvedKey = key || evmConfig.defaultChain || 'mainnet'
12
+ const chain = evmConfig.chains[resolvedKey]
13
+
14
+ return {
15
+ id: chain?.id ?? 1,
16
+ blockExplorer: chain?.blockExplorer ?? 'https://etherscan.io',
17
+ }
18
+ }
19
+
20
+ export const useMainChainId = () => useChainConfig().id
21
+
22
+ export const useBlockExplorer = (key?: string) =>
23
+ useChainConfig(key).blockExplorer
24
+
25
+ export const useEnsureChainIdCheck = (key?: string) => {
26
+ const chainId = useChainConfig(key).id
27
+ const { mutateAsync: switchChain } = useSwitchChain()
28
+ const { chainId: currentChainId } = useConnection()
29
+
30
+ return async () => {
31
+ if (chainId === currentChainId.value) {
32
+ return true
33
+ }
34
+
35
+ try {
36
+ await switchChain({ chainId })
37
+ return true
38
+ } catch {
39
+ return false
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,113 @@
1
+ import { ref, computed, watchEffect, toValue, type MaybeRefOrGetter, type Ref } from 'vue'
2
+ import { getPublicClient } from '@wagmi/core'
3
+ import { useConfig, type Config } from '@wagmi/vue'
4
+ import { useEvmConfig } from '../config'
5
+ import {
6
+ ensCache,
7
+ fetchEnsFromIndexer,
8
+ fetchEnsFromChain,
9
+ ENS_KEYS_AVATAR,
10
+ ENS_KEYS_PROFILE,
11
+ } from '../utils/ens'
12
+ import type { EnsProfile } from '../utils/ens'
13
+
14
+ type EnsMode = 'indexer' | 'chain'
15
+
16
+ interface UseEnsOptions {
17
+ mode?: MaybeRefOrGetter<EnsMode | undefined>
18
+ }
19
+
20
+ async function resolve(
21
+ identifier: string,
22
+ strategies: EnsMode[],
23
+ indexerUrls: string[],
24
+ wagmi: Config,
25
+ chainKeys: string[],
26
+ ): Promise<EnsProfile> {
27
+ for (const strategy of strategies) {
28
+ try {
29
+ if (strategy === 'indexer') {
30
+ if (!indexerUrls.length) continue
31
+ return await fetchEnsFromIndexer(identifier, indexerUrls)
32
+ }
33
+
34
+ if (strategy === 'chain') {
35
+ const client = getPublicClient(wagmi, { chainId: 1 })
36
+ if (!client) continue
37
+ return await fetchEnsFromChain(identifier, client, chainKeys)
38
+ }
39
+ } catch {
40
+ continue
41
+ }
42
+ }
43
+
44
+ return { address: identifier, ens: null, data: null }
45
+ }
46
+
47
+ function useEnsBase(
48
+ tier: string,
49
+ identifier: MaybeRefOrGetter<string | undefined>,
50
+ chainKeys: string[],
51
+ options: UseEnsOptions = {},
52
+ ) {
53
+ const config = useConfig()
54
+ const evmConfig = useEvmConfig()
55
+
56
+ const mode = computed<EnsMode>(
57
+ () => toValue(options.mode) || evmConfig.ens?.mode || 'indexer',
58
+ )
59
+ const indexerUrls = computed(() => evmConfig.ens?.indexerUrls || [])
60
+ const cacheKey = computed(() => `ens-${tier}-${toValue(identifier)}`)
61
+
62
+ const data: Ref<EnsProfile | null | undefined> = ref(
63
+ ensCache.get(cacheKey.value) ?? undefined,
64
+ )
65
+ const pending = ref(false)
66
+
67
+ watchEffect(async () => {
68
+ const id = toValue(identifier)
69
+ if (!id) {
70
+ data.value = null
71
+ pending.value = false
72
+ return
73
+ }
74
+
75
+ const cached = ensCache.get(cacheKey.value)
76
+ if (cached) {
77
+ data.value = cached
78
+ pending.value = false
79
+ return
80
+ }
81
+
82
+ const strategies: EnsMode[] =
83
+ mode.value === 'indexer' ? ['indexer', 'chain'] : ['chain', 'indexer']
84
+
85
+ pending.value = true
86
+ try {
87
+ data.value = await ensCache.fetch(cacheKey.value, () =>
88
+ resolve(id, strategies, indexerUrls.value, config, chainKeys),
89
+ )
90
+ } catch {
91
+ data.value = null
92
+ } finally {
93
+ pending.value = false
94
+ }
95
+ })
96
+
97
+ return { data, pending }
98
+ }
99
+
100
+ export const useEns = (
101
+ identifier: MaybeRefOrGetter<string | undefined>,
102
+ options?: UseEnsOptions,
103
+ ) => useEnsBase('resolve', identifier, [], options)
104
+
105
+ export const useEnsWithAvatar = (
106
+ identifier: MaybeRefOrGetter<string | undefined>,
107
+ options?: UseEnsOptions,
108
+ ) => useEnsBase('avatar', identifier, [...ENS_KEYS_AVATAR], options)
109
+
110
+ export const useEnsProfile = (
111
+ identifier: MaybeRefOrGetter<string | undefined>,
112
+ options?: UseEnsOptions,
113
+ ) => useEnsBase('profile', identifier, [...ENS_KEYS_PROFILE], options)