@1001-digital/layers.evm 1.0.7 → 1.0.8
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.
- package/.claude/settings.local.json +1 -3
- package/.playground/app/app.vue +1 -0
- package/.playground/app/pages/index.vue +1 -2
- package/.playground/nuxt.config.ts +3 -1
- package/AGENTS.md +2 -2
- package/README.md +1 -1
- package/app/composables/base.ts +1 -5
- package/app/composables/chainId.ts +6 -42
- package/app/plugins/wagmi.ts +40 -3
- package/app/utils/addresses.ts +1 -4
- package/app/utils/chains.ts +1 -13
- package/app/utils/format-eth.ts +1 -12
- package/nuxt.config.ts +33 -0
- package/package.json +5 -6
- package/app/components/EvmAccount.client.vue +0 -26
- package/app/components/EvmConnect.client.vue +0 -238
- package/app/components/EvmConnectorQR.client.vue +0 -108
- package/app/components/EvmMetaMaskQR.client.vue +0 -13
- package/app/components/EvmTransactionFlow.vue +0 -293
- package/app/components/EvmWalletConnectQR.client.vue +0 -13
- package/app/composables/clipboard.ts +0 -26
- package/app/composables/ens.ts +0 -88
- package/app/composables/gasPrice.ts +0 -36
- package/app/composables/helpers.ts +0 -1
- package/app/composables/priceFeed.ts +0 -103
- package/app/plugins/price-feed.client.ts +0 -7
- package/app/utils/cache.ts +0 -59
- package/app/utils/ens.ts +0 -98
- package/app/utils/price.ts +0 -15
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<p>
|
|
3
|
-
<slot name="instruction">Scan the code in your wallet application</slot>
|
|
4
|
-
</p>
|
|
5
|
-
<div class="qr-frame">
|
|
6
|
-
<canvas ref="qrCanvas"></canvas>
|
|
7
|
-
</div>
|
|
8
|
-
<p class="uri-label">Or copy the connection URI:</p>
|
|
9
|
-
<div class="uri-display">
|
|
10
|
-
<code>{{ uri }}</code>
|
|
11
|
-
<Button @click="copyUri" class="copy-button" :class="{ copied: isCopied }">
|
|
12
|
-
<Icon :type="isCopied ? 'check' : 'copy'" />
|
|
13
|
-
</Button>
|
|
14
|
-
</div>
|
|
15
|
-
</template>
|
|
16
|
-
|
|
17
|
-
<script setup lang="ts">
|
|
18
|
-
import QRCode from 'qrcode'
|
|
19
|
-
|
|
20
|
-
const props = defineProps<{
|
|
21
|
-
uri: string
|
|
22
|
-
}>()
|
|
23
|
-
|
|
24
|
-
const qrCanvas = ref<HTMLCanvasElement | null>(null)
|
|
25
|
-
const { copy, copied: isCopied } = useClipboard()
|
|
26
|
-
|
|
27
|
-
const generateQR = async () => {
|
|
28
|
-
if (!qrCanvas.value || !props.uri) return
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
await QRCode.toCanvas(qrCanvas.value, props.uri, {
|
|
32
|
-
width: 300,
|
|
33
|
-
margin: 2,
|
|
34
|
-
color: {
|
|
35
|
-
dark: '#000000',
|
|
36
|
-
light: '#FFFFFF',
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error('Failed to generate QR code:', error)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const copyUri = () => copy(props.uri)
|
|
45
|
-
|
|
46
|
-
watch(() => props.uri, generateQR, { immediate: true })
|
|
47
|
-
|
|
48
|
-
onMounted(() => {
|
|
49
|
-
generateQR()
|
|
50
|
-
})
|
|
51
|
-
</script>
|
|
52
|
-
|
|
53
|
-
<style scoped>
|
|
54
|
-
p {
|
|
55
|
-
text-align: center;
|
|
56
|
-
@mixin ui-font;
|
|
57
|
-
color: var(--muted);
|
|
58
|
-
font-size: var(--font-sm);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.qr-frame {
|
|
62
|
-
background: white;
|
|
63
|
-
padding: var(--spacer-sm);
|
|
64
|
-
max-width: 15rem;
|
|
65
|
-
max-height: 15rem;
|
|
66
|
-
border: var(--border);
|
|
67
|
-
border-radius: var(--border-radius);
|
|
68
|
-
margin: 0 auto;
|
|
69
|
-
|
|
70
|
-
canvas {
|
|
71
|
-
width: 100% !important;
|
|
72
|
-
height: 100% !important;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.uri-display {
|
|
77
|
-
display: flex;
|
|
78
|
-
align-items: center;
|
|
79
|
-
gap: var(--spacer-xs);
|
|
80
|
-
background: var(--color-bg-secondary);
|
|
81
|
-
border: var(--border);
|
|
82
|
-
border-radius: var(--border-radius-sm);
|
|
83
|
-
overflow: hidden;
|
|
84
|
-
height: min-content;
|
|
85
|
-
padding: 0;
|
|
86
|
-
|
|
87
|
-
code {
|
|
88
|
-
flex: 1;
|
|
89
|
-
font-size: var(--font-xs);
|
|
90
|
-
font-family: monospace;
|
|
91
|
-
white-space: nowrap;
|
|
92
|
-
overflow: hidden;
|
|
93
|
-
padding: 0 var(--spacer-sm);
|
|
94
|
-
color: var(--muted);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.copy-button {
|
|
98
|
-
flex-shrink: 0;
|
|
99
|
-
padding: var(--spacer-xs);
|
|
100
|
-
min-width: auto;
|
|
101
|
-
margin: -1px;
|
|
102
|
-
|
|
103
|
-
&.copied {
|
|
104
|
-
color: var(--color-success);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
</style>
|
|
@@ -1,293 +0,0 @@
|
|
|
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
|
-
>
|
|
16
|
-
<slot name="before" />
|
|
17
|
-
|
|
18
|
-
<Loading
|
|
19
|
-
v-if="step === 'requesting' || step === 'waiting'"
|
|
20
|
-
spinner
|
|
21
|
-
stacked
|
|
22
|
-
:txt="text.lead[step] || ''"
|
|
23
|
-
/>
|
|
24
|
-
|
|
25
|
-
<p
|
|
26
|
-
v-if="
|
|
27
|
-
step !== 'requesting' &&
|
|
28
|
-
step !== 'waiting' &&
|
|
29
|
-
step !== 'error' &&
|
|
30
|
-
text.lead[step]
|
|
31
|
-
"
|
|
32
|
-
>
|
|
33
|
-
{{ text.lead[step] }}
|
|
34
|
-
</p>
|
|
35
|
-
|
|
36
|
-
<Alert
|
|
37
|
-
v-if="error"
|
|
38
|
-
type="error"
|
|
39
|
-
>
|
|
40
|
-
<p v-if="text.lead[step]">{{ text.lead[step] }}</p>
|
|
41
|
-
<p>{{ error }}</p>
|
|
42
|
-
</Alert>
|
|
43
|
-
|
|
44
|
-
<Button
|
|
45
|
-
v-if="step === 'waiting'"
|
|
46
|
-
:to="txLink"
|
|
47
|
-
target="_blank"
|
|
48
|
-
class="link muted small centered"
|
|
49
|
-
>
|
|
50
|
-
<Icon type="link" />
|
|
51
|
-
<span>View on Block Explorer</span>
|
|
52
|
-
</Button>
|
|
53
|
-
|
|
54
|
-
<slot
|
|
55
|
-
:name="step"
|
|
56
|
-
:cancel="cancel"
|
|
57
|
-
></slot>
|
|
58
|
-
|
|
59
|
-
<template #footer>
|
|
60
|
-
<template v-if="step === 'chain'">
|
|
61
|
-
<Button
|
|
62
|
-
@click="cancel"
|
|
63
|
-
class="secondary"
|
|
64
|
-
>Cancel</Button
|
|
65
|
-
>
|
|
66
|
-
</template>
|
|
67
|
-
|
|
68
|
-
<template v-if="step === 'confirm' || step === 'error'">
|
|
69
|
-
<Button
|
|
70
|
-
@click="cancel"
|
|
71
|
-
class="secondary"
|
|
72
|
-
>Cancel</Button
|
|
73
|
-
>
|
|
74
|
-
<Button @click="() => initializeRequest()">
|
|
75
|
-
{{ text.action[step] || 'Execute' }}
|
|
76
|
-
</Button>
|
|
77
|
-
</template>
|
|
78
|
-
|
|
79
|
-
<slot
|
|
80
|
-
name="actions"
|
|
81
|
-
:step="step"
|
|
82
|
-
:cancel="cancel"
|
|
83
|
-
:execute="() => initializeRequest()"
|
|
84
|
-
:tx-link="txLink"
|
|
85
|
-
/>
|
|
86
|
-
</template>
|
|
87
|
-
</Dialog>
|
|
88
|
-
</template>
|
|
89
|
-
|
|
90
|
-
<script setup lang="ts">
|
|
91
|
-
import { waitForTransactionReceipt, watchChainId } from '@wagmi/core'
|
|
92
|
-
import type { Config } from '@wagmi/vue'
|
|
93
|
-
import type { TransactionReceipt, Hash } from 'viem'
|
|
94
|
-
|
|
95
|
-
interface TextConfig {
|
|
96
|
-
title?: Record<string, string>
|
|
97
|
-
lead?: Record<string, string>
|
|
98
|
-
action?: Record<string, string>
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
type Step =
|
|
102
|
-
| 'idle'
|
|
103
|
-
| 'confirm'
|
|
104
|
-
| 'chain'
|
|
105
|
-
| 'requesting'
|
|
106
|
-
| 'waiting'
|
|
107
|
-
| 'complete'
|
|
108
|
-
| 'error'
|
|
109
|
-
|
|
110
|
-
const defaultText = {
|
|
111
|
-
title: {
|
|
112
|
-
confirm: 'Confirm Transaction',
|
|
113
|
-
chain: 'Switch Network',
|
|
114
|
-
requesting: 'Requesting',
|
|
115
|
-
waiting: 'Processing',
|
|
116
|
-
complete: 'Complete',
|
|
117
|
-
error: 'Error',
|
|
118
|
-
},
|
|
119
|
-
lead: {
|
|
120
|
-
confirm: 'Please review and confirm this transaction.',
|
|
121
|
-
chain: 'Please switch to the correct network to continue.',
|
|
122
|
-
requesting: 'Requesting transaction signature...',
|
|
123
|
-
waiting: 'Waiting for transaction confirmation...',
|
|
124
|
-
complete: 'Transaction confirmed successfully.',
|
|
125
|
-
},
|
|
126
|
-
action: {
|
|
127
|
-
confirm: 'Execute',
|
|
128
|
-
error: 'Try Again',
|
|
129
|
-
},
|
|
130
|
-
} satisfies TextConfig
|
|
131
|
-
|
|
132
|
-
const slots = useSlots()
|
|
133
|
-
const checkChain = useEnsureChainIdCheck()
|
|
134
|
-
|
|
135
|
-
const { $wagmi } = useNuxtApp()
|
|
136
|
-
const blockExplorer = useBlockExplorer()
|
|
137
|
-
|
|
138
|
-
const props = withDefaults(
|
|
139
|
-
defineProps<{
|
|
140
|
-
text?: TextConfig
|
|
141
|
-
request?: () => Promise<Hash>
|
|
142
|
-
delayAfter?: number
|
|
143
|
-
delayAutoclose?: number
|
|
144
|
-
skipConfirmation?: boolean
|
|
145
|
-
autoCloseSuccess?: boolean
|
|
146
|
-
dismissable?: boolean
|
|
147
|
-
}>(),
|
|
148
|
-
{
|
|
149
|
-
delayAfter: 2000,
|
|
150
|
-
delayAutoclose: 2000,
|
|
151
|
-
skipConfirmation: false,
|
|
152
|
-
autoCloseSuccess: true,
|
|
153
|
-
dismissable: true,
|
|
154
|
-
},
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
const emit = defineEmits<{
|
|
158
|
-
complete: [receipt: TransactionReceipt]
|
|
159
|
-
cancel: []
|
|
160
|
-
}>()
|
|
161
|
-
|
|
162
|
-
const text = computed<Required<TextConfig>>(() => ({
|
|
163
|
-
title: { ...defaultText.title, ...props.text?.title },
|
|
164
|
-
lead: { ...defaultText.lead, ...props.text?.lead },
|
|
165
|
-
action: { ...defaultText.action, ...props.text?.action },
|
|
166
|
-
}))
|
|
167
|
-
|
|
168
|
-
const step = ref<Step>('idle')
|
|
169
|
-
|
|
170
|
-
const open = computed({
|
|
171
|
-
get: () => step.value !== 'idle',
|
|
172
|
-
set: (v) => {
|
|
173
|
-
if (!v) {
|
|
174
|
-
step.value = 'idle'
|
|
175
|
-
error.value = ''
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
watchChainId($wagmi as Config, {
|
|
181
|
-
async onChange() {
|
|
182
|
-
if (step.value !== 'chain') return
|
|
183
|
-
|
|
184
|
-
if (await checkChain()) {
|
|
185
|
-
initializeRequest()
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
const cachedRequest = ref(props.request)
|
|
191
|
-
watch(
|
|
192
|
-
() => props.request,
|
|
193
|
-
(v) => {
|
|
194
|
-
cachedRequest.value = v
|
|
195
|
-
},
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
const error = ref('')
|
|
199
|
-
const tx = ref<Hash | null>(null)
|
|
200
|
-
const receipt = ref<TransactionReceipt | null>(null)
|
|
201
|
-
const txLink = computed(() => `${blockExplorer}/tx/${tx.value}`)
|
|
202
|
-
|
|
203
|
-
const canDismiss = computed(
|
|
204
|
-
() =>
|
|
205
|
-
props.dismissable &&
|
|
206
|
-
step.value !== 'requesting' &&
|
|
207
|
-
step.value !== 'waiting',
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
const initializeRequest = async (request = cachedRequest.value) => {
|
|
211
|
-
cachedRequest.value = request
|
|
212
|
-
error.value = ''
|
|
213
|
-
tx.value = null
|
|
214
|
-
receipt.value = null
|
|
215
|
-
step.value = 'confirm'
|
|
216
|
-
|
|
217
|
-
if (!(await checkChain())) {
|
|
218
|
-
step.value = 'chain'
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
step.value = 'requesting'
|
|
224
|
-
tx.value = await request!()
|
|
225
|
-
step.value = 'waiting'
|
|
226
|
-
const receiptObject = await waitForTransactionReceipt($wagmi as Config, {
|
|
227
|
-
hash: tx.value,
|
|
228
|
-
})
|
|
229
|
-
await delay(props.delayAfter)
|
|
230
|
-
receipt.value = receiptObject
|
|
231
|
-
emit('complete', receiptObject)
|
|
232
|
-
step.value = 'complete'
|
|
233
|
-
} catch (e: unknown) {
|
|
234
|
-
const err = e as { cause?: { code?: number }; shortMessage?: string }
|
|
235
|
-
if (err?.cause?.code === 4001) {
|
|
236
|
-
error.value = 'Transaction rejected by user.'
|
|
237
|
-
step.value = 'error'
|
|
238
|
-
} else {
|
|
239
|
-
error.value = err.shortMessage || 'Error submitting transaction request.'
|
|
240
|
-
step.value = 'error'
|
|
241
|
-
}
|
|
242
|
-
console.log(e)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (props.autoCloseSuccess && step.value === 'complete') {
|
|
246
|
-
await delay(props.delayAutoclose)
|
|
247
|
-
step.value = 'idle'
|
|
248
|
-
await delay(300)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return receipt.value
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const start = () => {
|
|
255
|
-
if (props.skipConfirmation && step.value === 'idle') {
|
|
256
|
-
initializeRequest()
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
step.value = 'confirm'
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const cancel = () => {
|
|
264
|
-
step.value = 'idle'
|
|
265
|
-
error.value = ''
|
|
266
|
-
emit('cancel')
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
defineExpose({
|
|
270
|
-
initializeRequest,
|
|
271
|
-
})
|
|
272
|
-
</script>
|
|
273
|
-
|
|
274
|
-
<style>
|
|
275
|
-
.transaction-flow > section {
|
|
276
|
-
display: grid;
|
|
277
|
-
gap: var(--spacer);
|
|
278
|
-
|
|
279
|
-
.text {
|
|
280
|
-
width: 100%;
|
|
281
|
-
height: min-content;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
p {
|
|
285
|
-
white-space: pre-wrap;
|
|
286
|
-
width: 100%;
|
|
287
|
-
|
|
288
|
-
a {
|
|
289
|
-
text-decoration: underline;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
</style>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export const useClipboard = () => {
|
|
2
|
-
const copied = ref(false)
|
|
3
|
-
let timeout: ReturnType<typeof setTimeout> | null = null
|
|
4
|
-
|
|
5
|
-
const copy = async (text: string) => {
|
|
6
|
-
try {
|
|
7
|
-
await navigator.clipboard.writeText(text)
|
|
8
|
-
copied.value = true
|
|
9
|
-
|
|
10
|
-
if (timeout) clearTimeout(timeout)
|
|
11
|
-
timeout = setTimeout(() => {
|
|
12
|
-
copied.value = false
|
|
13
|
-
}, 2000)
|
|
14
|
-
|
|
15
|
-
return true
|
|
16
|
-
} catch (error) {
|
|
17
|
-
console.error('Failed to copy to clipboard:', error)
|
|
18
|
-
return false
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
copy,
|
|
24
|
-
copied,
|
|
25
|
-
}
|
|
26
|
-
}
|
package/app/composables/ens.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { getPublicClient } from '@wagmi/core'
|
|
2
|
-
import type { Config } from '@wagmi/vue'
|
|
3
|
-
|
|
4
|
-
type EnsMode = 'indexer' | 'chain'
|
|
5
|
-
|
|
6
|
-
interface UseEnsOptions {
|
|
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 }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function useEnsBase(
|
|
47
|
-
tier: string,
|
|
48
|
-
identifier: MaybeRefOrGetter<string | undefined>,
|
|
49
|
-
chainKeys: string[],
|
|
50
|
-
options: UseEnsOptions = {},
|
|
51
|
-
) {
|
|
52
|
-
const { $wagmi } = useNuxtApp()
|
|
53
|
-
const appConfig = useAppConfig()
|
|
54
|
-
const runtimeConfig = useRuntimeConfig()
|
|
55
|
-
|
|
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)}`)
|
|
59
|
-
|
|
60
|
-
return useAsyncData(
|
|
61
|
-
cacheKey.value,
|
|
62
|
-
async () => {
|
|
63
|
-
const id = toValue(identifier)
|
|
64
|
-
if (!id) return null
|
|
65
|
-
|
|
66
|
-
const strategies: EnsMode[] = mode.value === 'indexer'
|
|
67
|
-
? ['indexer', 'chain']
|
|
68
|
-
: ['chain', 'indexer']
|
|
69
|
-
|
|
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,
|
|
77
|
-
},
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const useEns = (identifier: MaybeRefOrGetter<string | undefined>, options?: UseEnsOptions) =>
|
|
82
|
-
useEnsBase('resolve', identifier, [], options)
|
|
83
|
-
|
|
84
|
-
export const useEnsWithAvatar = (identifier: MaybeRefOrGetter<string | undefined>, options?: UseEnsOptions) =>
|
|
85
|
-
useEnsBase('avatar', identifier, [...ENS_KEYS_AVATAR], options)
|
|
86
|
-
|
|
87
|
-
export const useEnsProfile = (identifier: MaybeRefOrGetter<string | undefined>, options?: UseEnsOptions) =>
|
|
88
|
-
useEnsBase('profile', identifier, [...ENS_KEYS_PROFILE], options)
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { WatchStopHandle } from 'vue'
|
|
2
|
-
import { formatEther, formatGwei } from 'viem'
|
|
3
|
-
import { getGasPrice } from '@wagmi/core'
|
|
4
|
-
import { useConfig, useBlockNumber } from '@wagmi/vue'
|
|
5
|
-
|
|
6
|
-
let priceWatcher: WatchStopHandle | null = null
|
|
7
|
-
const price: Ref<bigint> = ref(0n)
|
|
8
|
-
|
|
9
|
-
export const useGasPrice = () => {
|
|
10
|
-
const config = useConfig()
|
|
11
|
-
const { data: blockNumber } = useBlockNumber()
|
|
12
|
-
|
|
13
|
-
const updatePrice = async () => {
|
|
14
|
-
price.value = await getGasPrice(config)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (!priceWatcher) {
|
|
18
|
-
updatePrice()
|
|
19
|
-
priceWatcher = watch(blockNumber, () => updatePrice())
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const unitPrice = computed(() => ({
|
|
23
|
-
wei: price.value,
|
|
24
|
-
gwei: formatGwei(price.value),
|
|
25
|
-
eth: formatEther(price.value),
|
|
26
|
-
|
|
27
|
-
formatted: {
|
|
28
|
-
gwei: price.value > 2_000_000_000_000n
|
|
29
|
-
? Math.round(parseFloat(formatGwei(price.value)))
|
|
30
|
-
: parseFloat(formatGwei(price.value)).toFixed(1),
|
|
31
|
-
eth: formatEther(price.value),
|
|
32
|
-
},
|
|
33
|
-
}))
|
|
34
|
-
|
|
35
|
-
return unitPrice
|
|
36
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { readContract } from '@wagmi/core'
|
|
2
|
-
|
|
3
|
-
const CHAINLINK_ETH_USD_ABI = [
|
|
4
|
-
{
|
|
5
|
-
inputs: [],
|
|
6
|
-
name: 'latestRoundData',
|
|
7
|
-
outputs: [
|
|
8
|
-
{ internalType: 'uint80', name: 'roundId', type: 'uint80' },
|
|
9
|
-
{ internalType: 'int256', name: 'answer', type: 'int256' },
|
|
10
|
-
{ internalType: 'uint256', name: 'startedAt', type: 'uint256' },
|
|
11
|
-
{ internalType: 'uint256', name: 'updatedAt', type: 'uint256' },
|
|
12
|
-
{ internalType: 'uint80', name: 'answeredInRound', type: 'uint80' },
|
|
13
|
-
],
|
|
14
|
-
stateMutability: 'view',
|
|
15
|
-
type: 'function',
|
|
16
|
-
},
|
|
17
|
-
] as const
|
|
18
|
-
|
|
19
|
-
const CHAINLINK_ETH_USD = '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
|
|
20
|
-
const STORAGE_KEY = 'evm:price-feed'
|
|
21
|
-
const CACHE_TTL = 3_600 // 1 hour in seconds
|
|
22
|
-
|
|
23
|
-
interface PriceFeedState {
|
|
24
|
-
ethUSDRaw: bigint | null
|
|
25
|
-
lastUpdated: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const state = reactive<PriceFeedState>({
|
|
29
|
-
ethUSDRaw: null,
|
|
30
|
-
lastUpdated: 0,
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
function loadFromStorage() {
|
|
34
|
-
if (!import.meta.client) return
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const stored = localStorage.getItem(STORAGE_KEY)
|
|
38
|
-
if (!stored) return
|
|
39
|
-
|
|
40
|
-
const parsed = parseJSON(stored) as PriceFeedState
|
|
41
|
-
if (parsed.ethUSDRaw) state.ethUSDRaw = parsed.ethUSDRaw
|
|
42
|
-
if (parsed.lastUpdated) state.lastUpdated = parsed.lastUpdated
|
|
43
|
-
} catch {
|
|
44
|
-
// Ignore corrupted storage
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function saveToStorage() {
|
|
49
|
-
if (!import.meta.client) return
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
localStorage.setItem(STORAGE_KEY, stringifyJSON({
|
|
53
|
-
ethUSDRaw: state.ethUSDRaw,
|
|
54
|
-
lastUpdated: state.lastUpdated,
|
|
55
|
-
}))
|
|
56
|
-
} catch {
|
|
57
|
-
// Ignore storage errors
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const usePriceFeed = () => {
|
|
62
|
-
const { $wagmi } = useNuxtApp()
|
|
63
|
-
|
|
64
|
-
// Load cached data on first use
|
|
65
|
-
if (!state.lastUpdated) loadFromStorage()
|
|
66
|
-
|
|
67
|
-
const ethUSD = computed(() => state.ethUSDRaw ? state.ethUSDRaw / BigInt(1e8) : 0n)
|
|
68
|
-
const ethUSC = computed(() => state.ethUSDRaw ? state.ethUSDRaw / BigInt(1e6) : 0n)
|
|
69
|
-
const ethUSDFormatted = computed(() => formatPrice(Number(ethUSC.value) / 100, 2))
|
|
70
|
-
|
|
71
|
-
const weiToUSD = (wei: bigint) => {
|
|
72
|
-
const cents = (wei * (state.ethUSDRaw || 0n)) / (10n ** 18n) / (10n ** 6n)
|
|
73
|
-
return formatPrice(Number(cents) / 100, 2)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function fetchPrice() {
|
|
77
|
-
if (nowInSeconds() - state.lastUpdated < CACHE_TTL) return
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const [, answer] = await readContract($wagmi, {
|
|
81
|
-
address: CHAINLINK_ETH_USD,
|
|
82
|
-
abi: CHAINLINK_ETH_USD_ABI,
|
|
83
|
-
functionName: 'latestRoundData',
|
|
84
|
-
chainId: 1,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
state.ethUSDRaw = answer
|
|
88
|
-
state.lastUpdated = nowInSeconds()
|
|
89
|
-
saveToStorage()
|
|
90
|
-
} catch (error) {
|
|
91
|
-
console.warn('Error fetching ETH/USD price:', error)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
ethUSDRaw: computed(() => state.ethUSDRaw),
|
|
97
|
-
ethUSD,
|
|
98
|
-
ethUSC,
|
|
99
|
-
ethUSDFormatted,
|
|
100
|
-
weiToUSD,
|
|
101
|
-
fetchPrice,
|
|
102
|
-
}
|
|
103
|
-
}
|