@1001-digital/components 1.2.2 → 1.3.0

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