@1001-digital/layers.evm 1.0.4 → 1.0.6

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.
@@ -1,12 +1,50 @@
1
1
  import { getPublicClient } from '@wagmi/core'
2
2
  import type { Config } from '@wagmi/vue'
3
3
 
4
+ type EnsMode = 'indexer' | 'chain'
5
+
4
6
  interface UseEnsOptions {
5
- mode?: MaybeRefOrGetter<'indexer' | 'chain' | undefined>
7
+ mode?: MaybeRefOrGetter<EnsMode | undefined>
8
+ }
9
+
10
+ interface EnsRuntimeConfig {
11
+ ens?: { indexer1?: string, indexer2?: string, indexer3?: string }
12
+ }
13
+
14
+ function getIndexerUrls(config: EnsRuntimeConfig): string[] {
15
+ if (!config.ens) return []
16
+ return [config.ens.indexer1, config.ens.indexer2, config.ens.indexer3].filter(Boolean) as string[]
17
+ }
18
+
19
+ async function resolve(
20
+ identifier: string,
21
+ strategies: EnsMode[],
22
+ indexerUrls: string[],
23
+ wagmi: Config,
24
+ chainKeys: string[],
25
+ ): Promise<EnsProfile> {
26
+ for (const strategy of strategies) {
27
+ try {
28
+ if (strategy === 'indexer') {
29
+ if (!indexerUrls.length) continue
30
+ return await fetchEnsFromIndexer(identifier, indexerUrls)
31
+ }
32
+
33
+ if (strategy === 'chain') {
34
+ const client = getPublicClient(wagmi, { chainId: 1 })
35
+ if (!client) continue
36
+ return await fetchEnsFromChain(identifier, client, chainKeys)
37
+ }
38
+ } catch {
39
+ continue
40
+ }
41
+ }
42
+
43
+ return { address: identifier, ens: null, data: null }
6
44
  }
7
45
 
8
46
  function useEnsBase(
9
- key: string,
47
+ tier: string,
10
48
  identifier: MaybeRefOrGetter<string | undefined>,
11
49
  chainKeys: string[],
12
50
  options: UseEnsOptions = {},
@@ -15,53 +53,35 @@ function useEnsBase(
15
53
  const appConfig = useAppConfig()
16
54
  const runtimeConfig = useRuntimeConfig()
17
55
 
18
- const mode = computed(() => toValue(options.mode) || appConfig.evm?.ens?.mode || 'indexer')
19
-
20
- const indexerUrls = computed(() => {
21
- const ens = (runtimeConfig.public.evm as { ens?: { indexer1?: string, indexer2?: string, indexer3?: string } }).ens
22
- if (!ens) return []
23
- return [ens.indexer1, ens.indexer2, ens.indexer3].filter(Boolean) as string[]
24
- })
25
-
26
- const strategies = computed(() => {
27
- const primary = mode.value
28
- const fallback = primary === 'indexer' ? 'chain' : 'indexer'
29
- return [primary, fallback] as const
30
- })
56
+ const mode = computed<EnsMode>(() => toValue(options.mode) || appConfig.evm?.ens?.mode || 'indexer')
57
+ const indexerUrls = computed(() => getIndexerUrls(runtimeConfig.public.evm as EnsRuntimeConfig))
58
+ const cacheKey = computed(() => `ens-${tier}-${toValue(identifier)}`)
31
59
 
32
60
  return useAsyncData(
33
- `ens-${key}-${toValue(identifier)}`,
61
+ cacheKey.value,
34
62
  async () => {
35
63
  const id = toValue(identifier)
36
64
  if (!id) return null
37
65
 
38
- for (const strategy of strategies.value) {
39
- try {
40
- if (strategy === 'indexer') {
41
- if (!indexerUrls.value.length) continue
42
- return await fetchEnsFromIndexer(id, indexerUrls.value)
43
- }
44
-
45
- if (strategy === 'chain') {
46
- const client = getPublicClient($wagmi as Config, { chainId: 1 })
47
- if (!client) continue
48
- return await fetchEnsFromChain(id, client, chainKeys)
49
- }
50
- } catch {
51
- continue
52
- }
53
- }
66
+ const strategies: EnsMode[] = mode.value === 'indexer'
67
+ ? ['indexer', 'chain']
68
+ : ['chain', 'indexer']
54
69
 
55
- return null
70
+ return ensCache.fetch(cacheKey.value, () =>
71
+ resolve(id, strategies, indexerUrls.value, $wagmi as Config, chainKeys),
72
+ )
73
+ },
74
+ {
75
+ watch: [() => toValue(identifier)],
76
+ getCachedData: () => ensCache.get(cacheKey.value) ?? undefined,
56
77
  },
57
- { watch: [() => toValue(identifier)] },
58
78
  )
59
79
  }
60
80
 
61
81
  export const useEns = (identifier: MaybeRefOrGetter<string | undefined>, options?: UseEnsOptions) =>
62
82
  useEnsBase('resolve', identifier, [], options)
63
83
 
64
- export const useEnsAvatar = (identifier: MaybeRefOrGetter<string | undefined>, options?: UseEnsOptions) =>
84
+ export const useEnsWithAvatar = (identifier: MaybeRefOrGetter<string | undefined>, options?: UseEnsOptions) =>
65
85
  useEnsBase('avatar', identifier, [...ENS_KEYS_AVATAR], options)
66
86
 
67
87
  export const useEnsProfile = (identifier: MaybeRefOrGetter<string | undefined>, options?: UseEnsOptions) =>
@@ -0,0 +1,59 @@
1
+ interface CacheEntry<T> {
2
+ data: T
3
+ expiresAt: number
4
+ }
5
+
6
+ export function createCache<T>(ttl: number, max: number) {
7
+ const entries = new Map<string, CacheEntry<T>>()
8
+ const pending = new Map<string, Promise<T>>()
9
+
10
+ function prune() {
11
+ const now = Date.now()
12
+ for (const [key, entry] of entries) {
13
+ if (entry.expiresAt <= now) entries.delete(key)
14
+ }
15
+
16
+ if (entries.size > max) {
17
+ const excess = entries.size - max
18
+ const keys = entries.keys()
19
+ for (let i = 0; i < excess; i++) {
20
+ entries.delete(keys.next().value!)
21
+ }
22
+ }
23
+ }
24
+
25
+ function get(key: string): T | undefined {
26
+ const entry = entries.get(key)
27
+ if (!entry) return undefined
28
+ if (entry.expiresAt <= Date.now()) {
29
+ entries.delete(key)
30
+ return undefined
31
+ }
32
+ return entry.data
33
+ }
34
+
35
+ function fetch(key: string, fn: () => Promise<T>): Promise<T> {
36
+ const cached = get(key)
37
+ if (cached) return Promise.resolve(cached)
38
+
39
+ const inflight = pending.get(key)
40
+ if (inflight) return inflight
41
+
42
+ const promise = fn()
43
+ .then((result) => {
44
+ entries.set(key, { data: result, expiresAt: Date.now() + ttl })
45
+ pending.delete(key)
46
+ if (entries.size > max) prune()
47
+ return result
48
+ })
49
+ .catch((err) => {
50
+ pending.delete(key)
51
+ throw err
52
+ })
53
+
54
+ pending.set(key, promise)
55
+ return promise
56
+ }
57
+
58
+ return { get, fetch }
59
+ }
package/app/utils/ens.ts CHANGED
@@ -1,5 +1,7 @@
1
- import type { PublicClient, Address } from 'viem'
2
- import { isAddress, normalize } from 'viem/ens'
1
+ import { isAddress, type PublicClient, type Address } from 'viem'
2
+ import { normalize } from 'viem/ens'
3
+
4
+ // --- Types ---
3
5
 
4
6
  export interface EnsProfile {
5
7
  address: string
@@ -17,26 +19,18 @@ export interface EnsProfile {
17
19
  } | null
18
20
  }
19
21
 
20
- const TEXT_RECORD_KEYS = ['avatar', 'header', 'description', 'url', 'email', 'com.twitter', 'com.github'] as const
22
+ // --- Text record keys ---
21
23
 
22
- function buildData(keys: string[], results: string[]): EnsProfile['data'] {
23
- const get = (key: string) => {
24
- const i = keys.indexOf(key)
25
- return i >= 0 ? results[i] || '' : ''
26
- }
24
+ const ALL_KEYS = ['avatar', 'header', 'description', 'url', 'email', 'com.twitter', 'com.github'] as const
27
25
 
28
- return {
29
- avatar: get('avatar'),
30
- header: get('header'),
31
- description: get('description'),
32
- links: {
33
- url: get('url'),
34
- email: get('email'),
35
- twitter: get('com.twitter'),
36
- github: get('com.github'),
37
- },
38
- }
39
- }
26
+ export const ENS_KEYS_AVATAR = ['avatar'] as const
27
+ export const ENS_KEYS_PROFILE = [...ALL_KEYS]
28
+
29
+ // --- Cache ---
30
+
31
+ export const ensCache = createCache<EnsProfile>(5 * 60 * 1000, 500)
32
+
33
+ // --- Fetchers ---
40
34
 
41
35
  export async function fetchEnsFromIndexer(
42
36
  identifier: string,
@@ -79,11 +73,26 @@ export async function fetchEnsFromChain(
79
73
 
80
74
  const name = normalize(ens)
81
75
  const results = await Promise.all(
82
- keys.map(key => client.getEnsText({ name, key }).catch(() => '')),
76
+ keys.map(key => client.getEnsText({ name, key }).catch(() => null)),
83
77
  )
84
78
 
85
- return { address, ens, data: buildData(keys, results) }
79
+ return { address, ens, data: toProfileData(keys, results.map(r => r || '')) }
86
80
  }
87
81
 
88
- export const ENS_KEYS_AVATAR = ['avatar'] as const
89
- export const ENS_KEYS_PROFILE = [...TEXT_RECORD_KEYS]
82
+ // --- Helpers ---
83
+
84
+ function toProfileData(keys: string[], results: string[]): EnsProfile['data'] {
85
+ const get = (key: string) => results[keys.indexOf(key)] || ''
86
+
87
+ return {
88
+ avatar: get('avatar'),
89
+ header: get('header'),
90
+ description: get('description'),
91
+ links: {
92
+ url: get('url'),
93
+ email: get('email'),
94
+ twitter: get('com.twitter'),
95
+ github: get('com.github'),
96
+ },
97
+ }
98
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@1001-digital/layers.evm",
3
3
  "type": "module",
4
- "version": "1.0.4",
4
+ "version": "1.0.6",
5
5
  "main": "./nuxt.config.ts",
6
6
  "devDependencies": {
7
7
  "@nuxt/eslint": "latest",