@1001-digital/components 1.2.1 → 1.2.3
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/package.json +1 -21
- package/src/index.ts +0 -3
- package/src/evm/assets/wallets/coinbase.svg +0 -4
- package/src/evm/assets/wallets/in-app.svg +0 -5
- package/src/evm/assets/wallets/metamask.svg +0 -1
- package/src/evm/assets/wallets/phantom.svg +0 -4
- package/src/evm/assets/wallets/rabby.svg +0 -24
- package/src/evm/assets/wallets/rainbow.svg +0 -59
- package/src/evm/assets/wallets/safe.png +0 -0
- package/src/evm/assets/wallets/walletconnect.svg +0 -1
- package/src/evm/components/EvmAccount.vue +0 -28
- package/src/evm/components/EvmAvatar.vue +0 -62
- package/src/evm/components/EvmConnect.vue +0 -301
- package/src/evm/components/EvmConnectDialog.vue +0 -75
- package/src/evm/components/EvmConnectionStatus.vue +0 -13
- package/src/evm/components/EvmConnectorQR.vue +0 -86
- package/src/evm/components/EvmInAppWalletSetup.vue +0 -251
- package/src/evm/components/EvmMetaMaskQR.vue +0 -34
- package/src/evm/components/EvmProfile.vue +0 -186
- package/src/evm/components/EvmSeedPhraseInput.vue +0 -193
- package/src/evm/components/EvmSiwe.vue +0 -190
- package/src/evm/components/EvmSiweDialog.vue +0 -93
- package/src/evm/components/EvmSwitchNetwork.vue +0 -132
- package/src/evm/components/EvmTransactionFlow.vue +0 -353
- package/src/evm/components/EvmWalletConnectQR.vue +0 -13
- package/src/evm/components/EvmWalletConnectWallets.vue +0 -200
- package/src/evm/composables/base.ts +0 -7
- package/src/evm/composables/chainId.ts +0 -42
- package/src/evm/composables/ens.ts +0 -113
- package/src/evm/composables/gasPrice.ts +0 -37
- package/src/evm/composables/priceFeed.ts +0 -116
- package/src/evm/composables/siwe.ts +0 -89
- package/src/evm/composables/uri.ts +0 -12
- package/src/evm/composables/walletExplorer.ts +0 -130
- package/src/evm/config.ts +0 -35
- package/src/evm/connectors/inAppWallet.ts +0 -5
- package/src/evm/index.ts +0 -60
- package/src/evm/utils/addresses.ts +0 -6
- package/src/evm/utils/cache.ts +0 -59
- package/src/evm/utils/chains.ts +0 -32
- package/src/evm/utils/ens.ts +0 -116
- package/src/evm/utils/format-eth.ts +0 -15
- package/src/evm/utils/price.ts +0 -17
- package/src/evm/utils/siwe.ts +0 -70
- package/src/evm/utils/uri.ts +0 -24
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="seed-phrase-input">
|
|
3
|
-
<div class="seed-phrase-grid">
|
|
4
|
-
<div
|
|
5
|
-
v-for="(_, i) in 12"
|
|
6
|
-
:key="i"
|
|
7
|
-
class="seed-word"
|
|
8
|
-
:class="{ invalid: words[i] && !isValidWord(words[i]) }"
|
|
9
|
-
>
|
|
10
|
-
<label :for="`seed-word-${i}`">{{ i + 1 }}</label>
|
|
11
|
-
<input
|
|
12
|
-
:id="`seed-word-${i}`"
|
|
13
|
-
:ref="
|
|
14
|
-
(el) => {
|
|
15
|
-
if (el) inputRefs[i] = el as HTMLInputElement
|
|
16
|
-
}
|
|
17
|
-
"
|
|
18
|
-
v-model="words[i]"
|
|
19
|
-
type="text"
|
|
20
|
-
autocomplete="off"
|
|
21
|
-
autocapitalize="none"
|
|
22
|
-
spellcheck="false"
|
|
23
|
-
:disabled="disabled"
|
|
24
|
-
@keydown="onKeydown($event, i)"
|
|
25
|
-
@paste="onPaste($event, i)"
|
|
26
|
-
@input="onInput(i)"
|
|
27
|
-
/>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<script setup lang="ts">
|
|
34
|
-
import { ref, watch, computed, onMounted } from 'vue'
|
|
35
|
-
import { english } from 'viem/accounts'
|
|
36
|
-
|
|
37
|
-
const props = defineProps<{
|
|
38
|
-
modelValue?: string
|
|
39
|
-
disabled?: boolean
|
|
40
|
-
}>()
|
|
41
|
-
|
|
42
|
-
const emit = defineEmits<{
|
|
43
|
-
'update:modelValue': [value: string]
|
|
44
|
-
valid: [isValid: boolean]
|
|
45
|
-
submit: []
|
|
46
|
-
}>()
|
|
47
|
-
|
|
48
|
-
const wordSet = new Set(english)
|
|
49
|
-
|
|
50
|
-
const words = ref<string[]>(Array.from({ length: 12 }, () => ''))
|
|
51
|
-
const inputRefs = ref<HTMLInputElement[]>([])
|
|
52
|
-
|
|
53
|
-
function isValidWord(word: string): boolean {
|
|
54
|
-
return wordSet.has(word.trim().toLowerCase())
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const isValid = computed(() =>
|
|
58
|
-
words.value.every((w) => w.trim() !== '' && isValidWord(w)),
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
const phrase = computed(() =>
|
|
62
|
-
words.value.map((w) => w.trim().toLowerCase()).join(' '),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
watch(
|
|
66
|
-
() => props.modelValue,
|
|
67
|
-
(val) => {
|
|
68
|
-
if (!val) return
|
|
69
|
-
const incoming = val.trim().split(/\s+/)
|
|
70
|
-
if (incoming.length === 12) {
|
|
71
|
-
for (let i = 0; i < 12; i++) {
|
|
72
|
-
words.value[i] = incoming[i]!
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
{ immediate: true },
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
watch(phrase, (val) => {
|
|
80
|
-
emit('update:modelValue', val)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
watch(isValid, (val) => {
|
|
84
|
-
emit('valid', val)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
function focusInput(index: number) {
|
|
88
|
-
const el = inputRefs.value[index]
|
|
89
|
-
if (el) {
|
|
90
|
-
el.focus()
|
|
91
|
-
el.select()
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function onKeydown(event: KeyboardEvent, index: number) {
|
|
96
|
-
if (event.key === ' ' || (event.key === 'Enter' && index < 11)) {
|
|
97
|
-
event.preventDefault()
|
|
98
|
-
focusInput(index + 1)
|
|
99
|
-
} else if (event.key === 'Enter' && index === 11 && isValid.value) {
|
|
100
|
-
event.preventDefault()
|
|
101
|
-
emit('submit')
|
|
102
|
-
} else if (
|
|
103
|
-
event.key === 'Backspace' &&
|
|
104
|
-
words.value[index] === '' &&
|
|
105
|
-
index > 0
|
|
106
|
-
) {
|
|
107
|
-
event.preventDefault()
|
|
108
|
-
focusInput(index - 1)
|
|
109
|
-
} else if (event.key === 'Tab' && !event.shiftKey && index === 11) {
|
|
110
|
-
// Allow natural tab out
|
|
111
|
-
} else if (event.key === 'Tab' && event.shiftKey && index === 0) {
|
|
112
|
-
// Allow natural tab out
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function onPaste(event: ClipboardEvent, index: number) {
|
|
117
|
-
const text = event.clipboardData?.getData('text')
|
|
118
|
-
if (!text) return
|
|
119
|
-
|
|
120
|
-
const pasted = text.trim().split(/\s+/)
|
|
121
|
-
if (pasted.length > 1) {
|
|
122
|
-
event.preventDefault()
|
|
123
|
-
for (let i = 0; i < pasted.length && index + i < 12; i++) {
|
|
124
|
-
words.value[index + i] = pasted[i]!.toLowerCase()
|
|
125
|
-
}
|
|
126
|
-
const nextIndex = Math.min(index + pasted.length, 11)
|
|
127
|
-
focusInput(nextIndex)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function onInput(index: number) {
|
|
132
|
-
// Auto-advance if the word contains a space (mobile autocomplete)
|
|
133
|
-
const val = words.value[index]
|
|
134
|
-
if (val?.includes(' ')) {
|
|
135
|
-
const parts = val.trim().split(/\s+/)
|
|
136
|
-
words.value[index] = parts[0]!
|
|
137
|
-
if (parts.length > 1 && index < 11) {
|
|
138
|
-
for (let i = 1; i < parts.length && index + i < 12; i++) {
|
|
139
|
-
words.value[index + i] = parts[i]!
|
|
140
|
-
}
|
|
141
|
-
focusInput(Math.min(index + parts.length, 11))
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
onMounted(() => {
|
|
147
|
-
if (!props.disabled) {
|
|
148
|
-
focusInput(0)
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
</script>
|
|
152
|
-
|
|
153
|
-
<style scoped>
|
|
154
|
-
.seed-phrase-grid {
|
|
155
|
-
display: grid;
|
|
156
|
-
grid-template-columns: repeat(3, 1fr);
|
|
157
|
-
gap: var(--spacer-sm);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.seed-word {
|
|
162
|
-
display: flex;
|
|
163
|
-
align-items: center;
|
|
164
|
-
gap: var(--spacer-sm);
|
|
165
|
-
border: var(--border);
|
|
166
|
-
border-radius: var(--border-radius);
|
|
167
|
-
padding: var(--spacer-sm);
|
|
168
|
-
transition: border-color var(--speed);
|
|
169
|
-
|
|
170
|
-
&:focus-within {
|
|
171
|
-
border-color: var(--accent);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
&.invalid {
|
|
175
|
-
border-color: var(--error);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
label {
|
|
179
|
-
font-size: var(--font-xs);
|
|
180
|
-
color: var(--muted);
|
|
181
|
-
min-width: 1.5em;
|
|
182
|
-
text-align: right;
|
|
183
|
-
user-select: none;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
input {
|
|
187
|
-
all: unset;
|
|
188
|
-
width: 100%;
|
|
189
|
-
font-size: var(--font-sm);
|
|
190
|
-
font-family: var(--font-mono, monospace);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
</style>
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Loading
|
|
3
|
-
v-if="step === 'signing'"
|
|
4
|
-
spinner
|
|
5
|
-
stacked
|
|
6
|
-
:txt="connector?.name
|
|
7
|
-
? `Requesting signature from ${connector.name}...`
|
|
8
|
-
: 'Requesting signature...'"
|
|
9
|
-
/>
|
|
10
|
-
|
|
11
|
-
<Loading
|
|
12
|
-
v-else-if="step === 'verifying'"
|
|
13
|
-
spinner
|
|
14
|
-
stacked
|
|
15
|
-
txt="Verifying signature..."
|
|
16
|
-
/>
|
|
17
|
-
|
|
18
|
-
<template v-else-if="step === 'complete'">
|
|
19
|
-
<slot name="complete">
|
|
20
|
-
<Alert type="info">
|
|
21
|
-
<p>Successfully signed in.</p>
|
|
22
|
-
</Alert>
|
|
23
|
-
</slot>
|
|
24
|
-
</template>
|
|
25
|
-
|
|
26
|
-
<template v-else-if="step === 'error'">
|
|
27
|
-
<Alert type="error">
|
|
28
|
-
<p>{{ errorMessage }}</p>
|
|
29
|
-
</Alert>
|
|
30
|
-
<Button
|
|
31
|
-
class="secondary"
|
|
32
|
-
@click="signIn"
|
|
33
|
-
>Try Again</Button>
|
|
34
|
-
</template>
|
|
35
|
-
|
|
36
|
-
<template v-else>
|
|
37
|
-
<slot name="idle">
|
|
38
|
-
<p v-if="props.statement">{{ props.statement }}</p>
|
|
39
|
-
</slot>
|
|
40
|
-
<Button @click="signIn">
|
|
41
|
-
Sign In
|
|
42
|
-
</Button>
|
|
43
|
-
</template>
|
|
44
|
-
</template>
|
|
45
|
-
|
|
46
|
-
<script setup lang="ts">
|
|
47
|
-
import { ref } from 'vue'
|
|
48
|
-
import { signMessage } from '@wagmi/core'
|
|
49
|
-
import { useConfig, useConnection } from '@wagmi/vue'
|
|
50
|
-
import type { Config } from '@wagmi/vue'
|
|
51
|
-
import Button from '../../base/components/Button.vue'
|
|
52
|
-
import Alert from '../../base/components/Alert.vue'
|
|
53
|
-
import Loading from '../../base/components/Loading.vue'
|
|
54
|
-
import { createSiweMessage } from '../utils/siwe'
|
|
55
|
-
import { useSiwe } from '../composables/siwe'
|
|
56
|
-
|
|
57
|
-
type Step = 'idle' | 'signing' | 'verifying' | 'complete' | 'error'
|
|
58
|
-
|
|
59
|
-
const props = defineProps<{
|
|
60
|
-
getNonce: () => Promise<string>
|
|
61
|
-
verify: (message: string, signature: string) => Promise<boolean>
|
|
62
|
-
domain?: string
|
|
63
|
-
statement?: string
|
|
64
|
-
uri?: string
|
|
65
|
-
resources?: string[]
|
|
66
|
-
requestId?: string
|
|
67
|
-
expirationTime?: string
|
|
68
|
-
}>()
|
|
69
|
-
|
|
70
|
-
const emit = defineEmits<{
|
|
71
|
-
authenticated: [{ address: `0x${string}`; chainId: number }]
|
|
72
|
-
error: [error: string]
|
|
73
|
-
}>()
|
|
74
|
-
|
|
75
|
-
function isUserRejection(e: unknown): boolean {
|
|
76
|
-
const re = /reject|denied|cancel/i
|
|
77
|
-
let current = e as Record<string, unknown> | undefined
|
|
78
|
-
while (current) {
|
|
79
|
-
if ((current as { code?: number }).code === 4001) return true
|
|
80
|
-
if (re.test((current as { details?: string }).details || '')) return true
|
|
81
|
-
if (re.test((current as { message?: string }).message || '')) return true
|
|
82
|
-
current = current.cause as Record<string, unknown> | undefined
|
|
83
|
-
}
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const config = useConfig()
|
|
88
|
-
const { address, chainId, connector } = useConnection()
|
|
89
|
-
const { setSession } = useSiwe()
|
|
90
|
-
|
|
91
|
-
const step = ref<Step>('idle')
|
|
92
|
-
const errorMessage = ref('')
|
|
93
|
-
|
|
94
|
-
const signIn = async () => {
|
|
95
|
-
const currentAddress = address.value
|
|
96
|
-
const currentChainId = chainId.value
|
|
97
|
-
|
|
98
|
-
if (!currentAddress || !currentChainId) {
|
|
99
|
-
errorMessage.value = 'Wallet not connected.'
|
|
100
|
-
step.value = 'error'
|
|
101
|
-
emit('error', errorMessage.value)
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
errorMessage.value = ''
|
|
106
|
-
|
|
107
|
-
// Get nonce
|
|
108
|
-
let nonce: string
|
|
109
|
-
try {
|
|
110
|
-
nonce = await props.getNonce()
|
|
111
|
-
} catch {
|
|
112
|
-
errorMessage.value = 'Failed to get authentication nonce.'
|
|
113
|
-
step.value = 'error'
|
|
114
|
-
emit('error', errorMessage.value)
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Sign message
|
|
119
|
-
step.value = 'signing'
|
|
120
|
-
const message = createSiweMessage({
|
|
121
|
-
domain: props.domain || window.location.host,
|
|
122
|
-
address: currentAddress,
|
|
123
|
-
uri: props.uri || window.location.origin,
|
|
124
|
-
chainId: currentChainId,
|
|
125
|
-
nonce,
|
|
126
|
-
statement: props.statement,
|
|
127
|
-
expirationTime: props.expirationTime,
|
|
128
|
-
requestId: props.requestId,
|
|
129
|
-
resources: props.resources,
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
let signature: string
|
|
133
|
-
try {
|
|
134
|
-
signature = await signMessage(config as Config, { message })
|
|
135
|
-
} catch (e: unknown) {
|
|
136
|
-
if (isUserRejection(e)) {
|
|
137
|
-
errorMessage.value = 'Signature rejected by user.'
|
|
138
|
-
} else {
|
|
139
|
-
const err = e as { shortMessage?: string; message?: string }
|
|
140
|
-
errorMessage.value = err.shortMessage || err.message || 'Failed to sign message.'
|
|
141
|
-
}
|
|
142
|
-
step.value = 'error'
|
|
143
|
-
emit('error', errorMessage.value)
|
|
144
|
-
console.error('SIWE signing error:', e)
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Verify with backend
|
|
149
|
-
step.value = 'verifying'
|
|
150
|
-
try {
|
|
151
|
-
const verified = await props.verify(message, signature)
|
|
152
|
-
|
|
153
|
-
if (!verified) {
|
|
154
|
-
throw new Error('Signature verification failed')
|
|
155
|
-
}
|
|
156
|
-
} catch (e: unknown) {
|
|
157
|
-
const err = e as { message?: string }
|
|
158
|
-
errorMessage.value = err.message || 'Verification failed.'
|
|
159
|
-
step.value = 'error'
|
|
160
|
-
emit('error', errorMessage.value)
|
|
161
|
-
console.error('SIWE verification error:', e)
|
|
162
|
-
return
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Update shared authentication state
|
|
166
|
-
setSession({
|
|
167
|
-
address: currentAddress,
|
|
168
|
-
chainId: currentChainId,
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
step.value = 'complete'
|
|
172
|
-
emit('authenticated', {
|
|
173
|
-
address: currentAddress,
|
|
174
|
-
chainId: currentChainId,
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const reset = () => {
|
|
179
|
-
step.value = 'idle'
|
|
180
|
-
errorMessage.value = ''
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
defineExpose({ reset })
|
|
184
|
-
</script>
|
|
185
|
-
|
|
186
|
-
<style scoped>
|
|
187
|
-
.secondary {
|
|
188
|
-
margin-top: var(--spacer-sm);
|
|
189
|
-
}
|
|
190
|
-
</style>
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Button
|
|
3
|
-
v-if="!isAuthenticated"
|
|
4
|
-
@click="open = true"
|
|
5
|
-
:class="className"
|
|
6
|
-
>
|
|
7
|
-
<slot>Sign In</slot>
|
|
8
|
-
</Button>
|
|
9
|
-
<slot
|
|
10
|
-
v-else
|
|
11
|
-
name="authenticated"
|
|
12
|
-
:address="session?.address"
|
|
13
|
-
:sign-out="handleSignOut"
|
|
14
|
-
>
|
|
15
|
-
<Button
|
|
16
|
-
@click="handleSignOut"
|
|
17
|
-
:class="className"
|
|
18
|
-
>Sign Out</Button>
|
|
19
|
-
</slot>
|
|
20
|
-
|
|
21
|
-
<Dialog
|
|
22
|
-
v-if="!isAuthenticated"
|
|
23
|
-
title="Sign In with Ethereum"
|
|
24
|
-
v-model:open="open"
|
|
25
|
-
@closed="onClosed"
|
|
26
|
-
>
|
|
27
|
-
<EvmSiwe
|
|
28
|
-
ref="siweRef"
|
|
29
|
-
:get-nonce="getNonce"
|
|
30
|
-
:verify="verify"
|
|
31
|
-
:domain="domain"
|
|
32
|
-
:statement="statement"
|
|
33
|
-
:uri="uri"
|
|
34
|
-
:resources="resources"
|
|
35
|
-
:request-id="requestId"
|
|
36
|
-
:expiration-time="expirationTime"
|
|
37
|
-
@authenticated="onAuthenticated"
|
|
38
|
-
@error="(e) => emit('error', e)"
|
|
39
|
-
/>
|
|
40
|
-
</Dialog>
|
|
41
|
-
</template>
|
|
42
|
-
|
|
43
|
-
<script setup lang="ts">
|
|
44
|
-
import { ref, watch } from 'vue'
|
|
45
|
-
import Button from '../../base/components/Button.vue'
|
|
46
|
-
import Dialog from '../../base/components/Dialog.vue'
|
|
47
|
-
import EvmSiwe from './EvmSiwe.vue'
|
|
48
|
-
import { useSiwe } from '../composables/siwe'
|
|
49
|
-
|
|
50
|
-
const props = defineProps<{
|
|
51
|
-
className?: string
|
|
52
|
-
getNonce: () => Promise<string>
|
|
53
|
-
verify: (message: string, signature: string) => Promise<boolean>
|
|
54
|
-
domain?: string
|
|
55
|
-
statement?: string
|
|
56
|
-
uri?: string
|
|
57
|
-
resources?: string[]
|
|
58
|
-
requestId?: string
|
|
59
|
-
expirationTime?: string
|
|
60
|
-
}>()
|
|
61
|
-
|
|
62
|
-
const emit = defineEmits<{
|
|
63
|
-
authenticated: [{ address: `0x${string}`; chainId: number }]
|
|
64
|
-
signedOut: []
|
|
65
|
-
error: [error: string]
|
|
66
|
-
}>()
|
|
67
|
-
|
|
68
|
-
const { isAuthenticated, session, signOut } = useSiwe()
|
|
69
|
-
|
|
70
|
-
const open = ref(false)
|
|
71
|
-
const siweRef = ref<InstanceType<typeof EvmSiwe> | null>(null)
|
|
72
|
-
|
|
73
|
-
const onAuthenticated = (data: { address: `0x${string}`; chainId: number }) => {
|
|
74
|
-
open.value = false
|
|
75
|
-
emit('authenticated', data)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const onClosed = () => {
|
|
79
|
-
siweRef.value?.reset()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const handleSignOut = () => {
|
|
83
|
-
signOut()
|
|
84
|
-
emit('signedOut')
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
watch(isAuthenticated, (authenticated) => {
|
|
88
|
-
if (!authenticated) {
|
|
89
|
-
open.value = false
|
|
90
|
-
siweRef.value?.reset()
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
</script>
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<template v-if="chains.length > 1">
|
|
3
|
-
<Button
|
|
4
|
-
@click="dialogOpen = true"
|
|
5
|
-
:class="className"
|
|
6
|
-
>
|
|
7
|
-
<slot :current-chain="currentChain">
|
|
8
|
-
{{ currentChain?.name || 'Unknown Network' }}
|
|
9
|
-
</slot>
|
|
10
|
-
</Button>
|
|
11
|
-
|
|
12
|
-
<Dialog
|
|
13
|
-
title="Switch Network"
|
|
14
|
-
v-model:open="dialogOpen"
|
|
15
|
-
@closed="onClosed"
|
|
16
|
-
compat
|
|
17
|
-
>
|
|
18
|
-
<Alert
|
|
19
|
-
v-if="errorMessage"
|
|
20
|
-
type="error"
|
|
21
|
-
>
|
|
22
|
-
{{ errorMessage }}
|
|
23
|
-
</Alert>
|
|
24
|
-
|
|
25
|
-
<Loading
|
|
26
|
-
v-if="switching"
|
|
27
|
-
spinner
|
|
28
|
-
stacked
|
|
29
|
-
:txt="`Switching to ${switchingTo}...`"
|
|
30
|
-
/>
|
|
31
|
-
|
|
32
|
-
<div
|
|
33
|
-
v-if="!switching"
|
|
34
|
-
class="chain-list"
|
|
35
|
-
>
|
|
36
|
-
<Button
|
|
37
|
-
v-for="chain in chains"
|
|
38
|
-
:key="chain.id"
|
|
39
|
-
:disabled="chain.id === currentChainId || undefined"
|
|
40
|
-
:class="['block', 'chain-item', { active: chain.id === currentChainId }]"
|
|
41
|
-
@click="() => switchTo(chain)"
|
|
42
|
-
>
|
|
43
|
-
<span>{{ chain.name }}</span>
|
|
44
|
-
<Icon
|
|
45
|
-
v-if="chain.id === currentChainId"
|
|
46
|
-
type="check"
|
|
47
|
-
/>
|
|
48
|
-
</Button>
|
|
49
|
-
</div>
|
|
50
|
-
</Dialog>
|
|
51
|
-
</template>
|
|
52
|
-
</template>
|
|
53
|
-
|
|
54
|
-
<script setup lang="ts">
|
|
55
|
-
import { ref, computed } from 'vue'
|
|
56
|
-
import type { Chain } from 'viem'
|
|
57
|
-
import { useConfig, useConnection, useSwitchChain } from '@wagmi/vue'
|
|
58
|
-
import Button from '../../base/components/Button.vue'
|
|
59
|
-
import Dialog from '../../base/components/Dialog.vue'
|
|
60
|
-
import Icon from '../../base/components/Icon.vue'
|
|
61
|
-
import Alert from '../../base/components/Alert.vue'
|
|
62
|
-
import Loading from '../../base/components/Loading.vue'
|
|
63
|
-
|
|
64
|
-
defineProps<{
|
|
65
|
-
className?: string
|
|
66
|
-
}>()
|
|
67
|
-
|
|
68
|
-
const emit = defineEmits<{
|
|
69
|
-
switched: [{ chainId: number; name: string }]
|
|
70
|
-
error: [{ message: string }]
|
|
71
|
-
}>()
|
|
72
|
-
|
|
73
|
-
const config = useConfig()
|
|
74
|
-
const { chainId: currentChainId } = useConnection()
|
|
75
|
-
const { mutateAsync: switchChainAsync } = useSwitchChain()
|
|
76
|
-
|
|
77
|
-
const chains = computed<readonly Chain[]>(() => config.chains)
|
|
78
|
-
const currentChain = computed(() =>
|
|
79
|
-
chains.value.find((c) => c.id === currentChainId.value),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
const dialogOpen = ref(false)
|
|
83
|
-
const switching = ref(false)
|
|
84
|
-
const switchingTo = ref('')
|
|
85
|
-
const errorMessage = ref('')
|
|
86
|
-
|
|
87
|
-
const switchTo = async (chain: Chain) => {
|
|
88
|
-
if (chain.id === currentChainId.value) return
|
|
89
|
-
|
|
90
|
-
switching.value = true
|
|
91
|
-
switchingTo.value = chain.name
|
|
92
|
-
errorMessage.value = ''
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
await switchChainAsync({ chainId: chain.id })
|
|
96
|
-
emit('switched', { chainId: chain.id, name: chain.name })
|
|
97
|
-
dialogOpen.value = false
|
|
98
|
-
} catch (e: unknown) {
|
|
99
|
-
const message = e instanceof Error ? e.message : 'Failed to switch network.'
|
|
100
|
-
errorMessage.value =
|
|
101
|
-
message.includes('rejected') || message.includes('denied')
|
|
102
|
-
? 'Network switch cancelled.'
|
|
103
|
-
: 'Failed to switch network. Please try again.'
|
|
104
|
-
emit('error', { message: errorMessage.value })
|
|
105
|
-
} finally {
|
|
106
|
-
switching.value = false
|
|
107
|
-
switchingTo.value = ''
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const onClosed = () => {
|
|
112
|
-
switching.value = false
|
|
113
|
-
switchingTo.value = ''
|
|
114
|
-
errorMessage.value = ''
|
|
115
|
-
}
|
|
116
|
-
</script>
|
|
117
|
-
|
|
118
|
-
<style scoped>
|
|
119
|
-
.chain-list {
|
|
120
|
-
display: grid;
|
|
121
|
-
gap: var(--spacer-sm);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.chain-item {
|
|
125
|
-
justify-content: space-between;
|
|
126
|
-
|
|
127
|
-
&.active {
|
|
128
|
-
pointer-events: none;
|
|
129
|
-
opacity: 1;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
</style>
|