@1001-digital/components 1.1.10 → 1.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1001-digital/components",
3
- "version": "1.1.10",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -1,131 +1,102 @@
1
1
  <template>
2
- <Button
3
- v-if="showConnect"
4
- @click="chooseModalOpen = true"
5
- :class="className"
6
- >
7
- <slot>Connect Wallet</slot>
8
- </Button>
9
- <slot
2
+ <EvmInAppWalletSetup
3
+ v-if="showInAppSetup"
4
+ @connected="onInAppConnected"
5
+ @back="showInAppSetup = false"
6
+ />
7
+ <template v-else-if="errorMessage">
8
+ <Alert type="error">
9
+ {{ errorMessage }}
10
+ </Alert>
11
+ <Button
12
+ class="link muted small"
13
+ @click="resetConnection"
14
+ >
15
+ <Icon type="chevron-left" />
16
+ <span>Back</span>
17
+ </Button>
18
+ </template>
19
+ <EvmMetaMaskQR
20
+ v-else-if="metaMaskUri"
21
+ :uri="metaMaskUri"
22
+ @back="resetConnection"
23
+ />
24
+ <EvmWalletConnectWallets
25
+ v-else-if="walletConnectUri"
26
+ :uri="walletConnectUri"
27
+ @back="resetConnection"
28
+ />
29
+ <template v-else-if="isConnecting">
30
+ <Loading
31
+ :txt="`Waiting for ${connectingWallet} confirmation...`"
32
+ spinner
33
+ stacked
34
+ />
35
+ </template>
36
+ <div
10
37
  v-else
11
- name="connected"
12
- :address="address"
38
+ class="wallet-options"
13
39
  >
14
- <EvmAccount :address="address" />
15
- </slot>
16
-
17
- <Dialog
18
- v-if="showConnect"
19
- title="Connect Wallet"
20
- v-model:open="chooseModalOpen"
21
- @closed="onModalClosed"
22
- >
23
- <EvmInAppWalletSetup
24
- v-if="showInAppSetup"
25
- @connected="onInAppConnected"
26
- @back="showInAppSetup = false"
27
- />
28
- <template v-else-if="errorMessage">
29
- <Alert type="error">
30
- {{ errorMessage }}
31
- </Alert>
32
- <Button
33
- class="link muted small"
34
- @click="resetConnection"
40
+ <Button
41
+ v-for="connector in shownConnectors"
42
+ :key="connector.uid"
43
+ @click="() => login(connector)"
44
+ class="block choose-connector"
45
+ >
46
+ <img
47
+ v-if="ICONS[connector.name] || connector.icon"
48
+ :src="ICONS[connector.name] || connector.icon"
49
+ :alt="connector.name"
50
+ />
51
+ <div
52
+ v-else
53
+ class="default-wallet-icon"
35
54
  >
36
- <Icon type="chevron-left" />
37
- <span>Back</span>
38
- </Button>
39
- </template>
40
- <EvmMetaMaskQR
41
- v-else-if="metaMaskUri"
42
- :uri="metaMaskUri"
43
- @back="resetConnection"
44
- />
45
- <EvmWalletConnectWallets
46
- v-else-if="walletConnectUri"
47
- :uri="walletConnectUri"
48
- @back="resetConnection"
49
- />
50
- <template v-else-if="isConnecting">
51
- <Loading
52
- :txt="`Waiting for ${connectingWallet} confirmation...`"
53
- spinner
54
- stacked
55
+ <Icon type="wallet" />
56
+ </div>
57
+ <span>{{ connector.name }}</span>
58
+ </Button>
59
+ <Button
60
+ v-if="wcConnector"
61
+ @click="loginWithSafe"
62
+ class="block choose-connector"
63
+ >
64
+ <img
65
+ :src="safeIcon"
66
+ alt="Safe"
55
67
  />
56
- </template>
57
- <div
58
- v-else
59
- class="wallet-options"
68
+ <span>Safe</span>
69
+ </Button>
70
+ <Button
71
+ v-if="inAppConnector"
72
+ @click="showInAppSetup = true"
73
+ class="block choose-connector"
60
74
  >
61
- <Button
62
- v-for="connector in shownConnectors"
63
- :key="connector.uid"
64
- @click="() => login(connector)"
65
- class="block choose-connector"
66
- >
67
- <img
68
- v-if="ICONS[connector.name] || connector.icon"
69
- :src="ICONS[connector.name] || connector.icon"
70
- :alt="connector.name"
71
- />
72
- <div
73
- v-else
74
- class="default-wallet-icon"
75
- >
76
- <Icon type="wallet" />
77
- </div>
78
- <span>{{ connector.name }}</span>
79
- </Button>
80
- <Button
81
- v-if="wcConnector"
82
- @click="loginWithSafe"
83
- class="block choose-connector"
84
- >
85
- <img
86
- :src="safeIcon"
87
- alt="Safe"
88
- />
89
- <span>Safe</span>
90
- </Button>
91
- <Button
92
- v-if="inAppConnector"
93
- @click="showInAppSetup = true"
94
- class="block choose-connector"
95
- >
96
- <img
97
- :src="inAppIcon"
98
- alt="Seed Phrase"
99
- />
100
- <span>In App</span>
101
- </Button>
102
- <Button
103
- to="https://ethereum.org/wallets/"
104
- target="_blank"
105
- class="link muted small"
106
- >
107
- <Icon type="help" />
108
- <span>New to wallets?</span>
109
- </Button>
110
- </div>
111
- </Dialog>
75
+ <img
76
+ :src="inAppIcon"
77
+ alt="Seed Phrase"
78
+ />
79
+ <span>In App</span>
80
+ </Button>
81
+ <Button
82
+ to="https://ethereum.org/wallets/"
83
+ target="_blank"
84
+ class="link muted small"
85
+ >
86
+ <Icon type="help" />
87
+ <span>New to wallets?</span>
88
+ </Button>
89
+ </div>
112
90
  </template>
113
91
 
114
92
  <script setup lang="ts">
115
- import { ref, computed, watch, onMounted } from 'vue'
93
+ import { ref, computed } from 'vue'
116
94
  import type { Connector } from '@wagmi/vue'
117
- import {
118
- useConnection,
119
- useConnect,
120
- useConnectors,
121
- useChainId,
122
- } from '@wagmi/vue'
95
+ import { useConnect, useConnectors, useChainId } from '@wagmi/vue'
123
96
  import Button from '../../base/components/Button.vue'
124
- import Dialog from '../../base/components/Dialog.vue'
125
97
  import Icon from '../../base/components/Icon.vue'
126
98
  import Alert from '../../base/components/Alert.vue'
127
99
  import Loading from '../../base/components/Loading.vue'
128
- import EvmAccount from './EvmAccount.vue'
129
100
  import EvmMetaMaskQR from './EvmMetaMaskQR.vue'
130
101
  import EvmWalletConnectWallets from './EvmWalletConnectWallets.vue'
131
102
  import EvmInAppWalletSetup from './EvmInAppWalletSetup.vue'
@@ -155,24 +126,19 @@ const PRIORITY: Record<string, number> = {
155
126
  'Base Account': 10,
156
127
  }
157
128
 
158
- defineProps<{
159
- className?: string
160
- }>()
161
129
  const emit = defineEmits<{
162
- connected: [{ address: `0x${string}` | undefined }]
163
- disconnected: []
130
+ connected: []
164
131
  }>()
132
+
165
133
  const chainId = useChainId()
166
134
  const connectors = useConnectors()
167
135
  const { mutateAsync: connectAsync } = useConnect()
168
- const { address, isConnected } = useConnection()
169
136
 
170
137
  const inAppConnector = computed(() =>
171
138
  connectors.value.find((c) => c.type === 'inAppWallet'),
172
139
  )
173
140
  const showInAppSetup = ref(false)
174
141
 
175
- const showConnect = computed(() => !isConnected.value)
176
142
  const shownConnectors = computed(() => {
177
143
  const unique = Array.from(
178
144
  new Map(
@@ -199,7 +165,6 @@ const wcConnector = computed(() =>
199
165
  connectors.value.find((c) => c.id === 'walletConnect'),
200
166
  )
201
167
 
202
- const chooseModalOpen = ref(false)
203
168
  const errorMessage = ref('')
204
169
  const isConnecting = ref(false)
205
170
  const connectingWallet = ref('')
@@ -249,31 +214,24 @@ const login = async (connector: Connector) => {
249
214
  try {
250
215
  await connectAsync({ connector, chainId: chainId.value })
251
216
 
252
- setTimeout(() => {
253
- chooseModalOpen.value = false
254
- isConnecting.value = false
255
- metaMaskUri.value = ''
256
- walletConnectUri.value = ''
257
- safeDeepLink.value = false
258
- }, 100)
217
+ emit('connected')
218
+
219
+ resetConnection()
259
220
  } catch (error: unknown) {
260
221
  isConnecting.value = false
261
222
  metaMaskUri.value = ''
262
223
  walletConnectUri.value = ''
263
224
  safeDeepLink.value = false
264
225
 
265
- // Only show errors if our dialog is still open
266
- if (chooseModalOpen.value) {
267
- const errorMsg = error instanceof Error ? error.message : ''
268
- if (
269
- errorMsg.includes('User rejected') ||
270
- errorMsg.includes('rejected') ||
271
- errorMsg.includes('denied')
272
- ) {
273
- errorMessage.value = 'Connection cancelled. Please try again.'
274
- } else {
275
- errorMessage.value = 'Failed to connect. Please try again.'
276
- }
226
+ const errorMsg = error instanceof Error ? error.message : ''
227
+ if (
228
+ errorMsg.includes('User rejected') ||
229
+ errorMsg.includes('rejected') ||
230
+ errorMsg.includes('denied')
231
+ ) {
232
+ errorMessage.value = 'Connection cancelled. Please try again.'
233
+ } else {
234
+ errorMessage.value = 'Failed to connect. Please try again.'
277
235
  }
278
236
  console.error('Wallet connection error:', error)
279
237
  } finally {
@@ -295,25 +253,16 @@ const resetConnection = () => {
295
253
  }
296
254
 
297
255
  const onInAppConnected = () => {
298
- chooseModalOpen.value = false
299
256
  showInAppSetup.value = false
257
+ emit('connected')
300
258
  }
301
259
 
302
- const onModalClosed = () => {
303
- chooseModalOpen.value = false
260
+ const reset = () => {
304
261
  resetConnection()
305
262
  showInAppSetup.value = false
306
263
  }
307
264
 
308
- const check = () =>
309
- isConnected.value
310
- ? emit('connected', { address: address.value })
311
- : emit('disconnected')
312
- watch(isConnected, () => {
313
- check()
314
- if (!isConnected.value) onModalClosed()
315
- })
316
- onMounted(() => check())
265
+ defineExpose({ reset })
317
266
  </script>
318
267
 
319
268
  <style scoped>
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <Button
3
+ v-if="showConnect"
4
+ @click="open = true"
5
+ :class="className"
6
+ >
7
+ <slot>Connect Wallet</slot>
8
+ </Button>
9
+ <slot
10
+ v-else-if="isConnected"
11
+ name="connected"
12
+ :address="address"
13
+ >
14
+ <EvmAccount :address="address" />
15
+ </slot>
16
+
17
+ <Dialog
18
+ v-if="showConnect"
19
+ title="Connect Wallet"
20
+ v-model:open="open"
21
+ @closed="onClosed"
22
+ >
23
+ <EvmConnect
24
+ ref="connectRef"
25
+ @connected="onConnected"
26
+ />
27
+ </Dialog>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { ref, computed, watch, onMounted } from 'vue'
32
+ import { useConnection } from '@wagmi/vue'
33
+ import Button from '../../base/components/Button.vue'
34
+ import Dialog from '../../base/components/Dialog.vue'
35
+ import EvmAccount from './EvmAccount.vue'
36
+ import EvmConnect from './EvmConnect.vue'
37
+
38
+ defineProps<{
39
+ className?: string
40
+ }>()
41
+
42
+ const emit = defineEmits<{
43
+ connected: [{ address: `0x${string}` | undefined }]
44
+ disconnected: []
45
+ }>()
46
+
47
+ const { address, isConnected } = useConnection()
48
+
49
+ const showConnect = computed(() => !isConnected.value)
50
+ const open = ref(false)
51
+ const connectRef = ref<InstanceType<typeof EvmConnect> | null>(null)
52
+
53
+ const onConnected = () => {
54
+ open.value = false
55
+ }
56
+
57
+ const onClosed = () => {
58
+ connectRef.value?.reset()
59
+ }
60
+
61
+ const check = () =>
62
+ isConnected.value
63
+ ? emit('connected', { address: address.value })
64
+ : emit('disconnected')
65
+
66
+ watch(isConnected, () => {
67
+ check()
68
+ if (!isConnected.value) {
69
+ open.value = false
70
+ connectRef.value?.reset()
71
+ }
72
+ })
73
+
74
+ onMounted(() => check())
75
+ </script>
@@ -0,0 +1,13 @@
1
+ <template>
2
+ <slot
3
+ :status="status"
4
+ :address="address"
5
+ :connector="connector"
6
+ />
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { useConnection } from '@wagmi/vue'
11
+
12
+ const { address, connector, status } = useConnection()
13
+ </script>
@@ -0,0 +1,190 @@
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>
@@ -0,0 +1,93 @@
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>
@@ -0,0 +1,89 @@
1
+ import { ref, readonly } from 'vue'
2
+ import { signMessage } from '@wagmi/core'
3
+ import { useConfig, useConnection } from '@wagmi/vue'
4
+ import type { Config } from '@wagmi/vue'
5
+ import { createSiweMessage, type SiweMessageParams } from '../utils/siwe'
6
+
7
+ export interface SiweSession {
8
+ address: `0x${string}`
9
+ chainId: number
10
+ }
11
+
12
+ export interface UseSiweOptions {
13
+ getNonce: () => Promise<string>
14
+ verify: (message: string, signature: string) => Promise<boolean>
15
+ domain?: string
16
+ uri?: string
17
+ statement?: string
18
+ expirationTime?: string
19
+ requestId?: string
20
+ resources?: string[]
21
+ }
22
+
23
+ const _isAuthenticated = ref(false)
24
+ const _session = ref<SiweSession | null>(null)
25
+
26
+ export const useSiwe = () => {
27
+ const config = useConfig()
28
+ const { address, chainId } = useConnection()
29
+
30
+ const setSession = (session: SiweSession) => {
31
+ _isAuthenticated.value = true
32
+ _session.value = session
33
+ }
34
+
35
+ const clearSession = () => {
36
+ _isAuthenticated.value = false
37
+ _session.value = null
38
+ }
39
+
40
+ const signIn = async (options: UseSiweOptions) => {
41
+ const currentAddress = address.value
42
+ const currentChainId = chainId.value
43
+
44
+ if (!currentAddress || !currentChainId) {
45
+ throw new Error('Wallet not connected')
46
+ }
47
+
48
+ const nonce = await options.getNonce()
49
+
50
+ const messageParams: SiweMessageParams = {
51
+ domain: options.domain || window.location.host,
52
+ address: currentAddress,
53
+ uri: options.uri || window.location.origin,
54
+ chainId: currentChainId,
55
+ nonce,
56
+ statement: options.statement,
57
+ expirationTime: options.expirationTime,
58
+ requestId: options.requestId,
59
+ resources: options.resources,
60
+ }
61
+
62
+ const message = createSiweMessage(messageParams)
63
+ const signature = await signMessage(config as Config, { message })
64
+
65
+ const verified = await options.verify(message, signature)
66
+
67
+ if (!verified) {
68
+ throw new Error('Signature verification failed')
69
+ }
70
+
71
+ setSession({
72
+ address: currentAddress,
73
+ chainId: currentChainId,
74
+ })
75
+ }
76
+
77
+ const signOut = () => {
78
+ clearSession()
79
+ }
80
+
81
+ return {
82
+ isAuthenticated: readonly(_isAuthenticated),
83
+ session: readonly(_session),
84
+ signIn,
85
+ signOut,
86
+ setSession,
87
+ clearSession,
88
+ }
89
+ }
package/src/evm/index.ts CHANGED
@@ -18,6 +18,8 @@ export type { EnsProfile } from './utils/ens'
18
18
  export { stringifyJSON, parseJSON, formatPrice } from './utils/price'
19
19
  export { resolveUri } from './utils/uri'
20
20
  export type { ResolveUriOptions } from './utils/uri'
21
+ export { createSiweMessage } from './utils/siwe'
22
+ export type { SiweMessageParams } from './utils/siwe'
21
23
 
22
24
  // Composables
23
25
  export { useBaseURL } from './composables/base'
@@ -33,6 +35,8 @@ export { useGasPrice } from './composables/gasPrice'
33
35
  export { usePriceFeed } from './composables/priceFeed'
34
36
  export { useWalletExplorer } from './composables/walletExplorer'
35
37
  export type { ExplorerWallet } from './composables/walletExplorer'
38
+ export { useSiwe } from './composables/siwe'
39
+ export type { SiweSession, UseSiweOptions } from './composables/siwe'
36
40
 
37
41
  // Connectors
38
42
  export { inAppWallet, prepareInAppWallet } from './connectors/inAppWallet'
@@ -41,6 +45,8 @@ export { inAppWallet, prepareInAppWallet } from './connectors/inAppWallet'
41
45
  export { default as EvmAccount } from './components/EvmAccount.vue'
42
46
  export { default as EvmAvatar } from './components/EvmAvatar.vue'
43
47
  export { default as EvmConnect } from './components/EvmConnect.vue'
48
+ export { default as EvmConnectDialog } from './components/EvmConnectDialog.vue'
49
+ export { default as EvmConnectionStatus } from './components/EvmConnectionStatus.vue'
44
50
  export { default as EvmProfile } from './components/EvmProfile.vue'
45
51
  export { default as EvmSwitchNetwork } from './components/EvmSwitchNetwork.vue'
46
52
  export { default as EvmConnectorQR } from './components/EvmConnectorQR.vue'
@@ -50,3 +56,5 @@ export { default as EvmWalletConnectWallets } from './components/EvmWalletConnec
50
56
  export { default as EvmTransactionFlow } from './components/EvmTransactionFlow.vue'
51
57
  export { default as EvmSeedPhraseInput } from './components/EvmSeedPhraseInput.vue'
52
58
  export { default as EvmInAppWalletSetup } from './components/EvmInAppWalletSetup.vue'
59
+ export { default as EvmSiwe } from './components/EvmSiwe.vue'
60
+ export { default as EvmSiweDialog } from './components/EvmSiweDialog.vue'
@@ -0,0 +1,70 @@
1
+ export interface SiweMessageParams {
2
+ domain: string
3
+ address: string
4
+ uri: string
5
+ version?: string
6
+ chainId: number
7
+ nonce: string
8
+ issuedAt?: string
9
+ expirationTime?: string
10
+ notBefore?: string
11
+ requestId?: string
12
+ statement?: string
13
+ resources?: string[]
14
+ }
15
+
16
+ export function createSiweMessage(params: SiweMessageParams): string {
17
+ const {
18
+ domain,
19
+ address,
20
+ uri,
21
+ version = '1',
22
+ chainId,
23
+ nonce,
24
+ issuedAt = new Date().toISOString(),
25
+ expirationTime,
26
+ notBefore,
27
+ requestId,
28
+ statement,
29
+ resources,
30
+ } = params
31
+
32
+ const lines: string[] = [
33
+ `${domain} wants you to sign in with your Ethereum account:`,
34
+ address,
35
+ '',
36
+ ]
37
+
38
+ if (statement) {
39
+ lines.push(statement, '')
40
+ }
41
+
42
+ lines.push(
43
+ `URI: ${uri}`,
44
+ `Version: ${version}`,
45
+ `Chain ID: ${chainId}`,
46
+ `Nonce: ${nonce}`,
47
+ `Issued At: ${issuedAt}`,
48
+ )
49
+
50
+ if (expirationTime) {
51
+ lines.push(`Expiration Time: ${expirationTime}`)
52
+ }
53
+
54
+ if (notBefore) {
55
+ lines.push(`Not Before: ${notBefore}`)
56
+ }
57
+
58
+ if (requestId) {
59
+ lines.push(`Request ID: ${requestId}`)
60
+ }
61
+
62
+ if (resources?.length) {
63
+ lines.push('Resources:')
64
+ for (const resource of resources) {
65
+ lines.push(`- ${resource}`)
66
+ }
67
+ }
68
+
69
+ return lines.join('\n')
70
+ }