@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,251 +0,0 @@
1
- <template>
2
- <div class="in-app-wallet-setup">
3
- <!-- Step: Choose -->
4
- <div
5
- v-if="step === 'choose'"
6
- class="setup-step"
7
- >
8
- <p class="muted font-sm">{{ note }}</p>
9
-
10
- <div class="setup-options">
11
- <Button
12
- class="block"
13
- @click="startGenerate"
14
- >
15
- <Icon type="plus" />
16
- <span>Create New Wallet</span>
17
- </Button>
18
- <Button
19
- class="block"
20
- @click="step = 'restore'"
21
- >
22
- <Icon type="key" />
23
- <span>Use Existing Recovery Key</span>
24
- </Button>
25
- </div>
26
- <Button
27
- class="link muted small"
28
- @click="$emit('back')"
29
- >
30
- <Icon type="chevron-left" />
31
- <span>Back</span>
32
- </Button>
33
- </div>
34
-
35
- <!-- Step: Generate -->
36
- <div
37
- v-else-if="step === 'generate'"
38
- class="setup-step"
39
- >
40
- <p class="muted font-sm">
41
- Write down these 12 words in order. Think of them as your secure
42
- password so keep them safe - you will need them to restore your account.
43
- They will not be shown again.
44
- </p>
45
-
46
- <div class="generated-words">
47
- <div
48
- v-for="(word, i) in generatedWords"
49
- :key="i"
50
- class="generated-word"
51
- >
52
- <span class="word-number">{{ i + 1 }}</span>
53
- <span class="word-text">{{ word }}</span>
54
- </div>
55
- </div>
56
-
57
- <div>
58
- <FormCheckbox v-model="backupConfirmed">
59
- I've saved my seed phrase
60
- </FormCheckbox>
61
- </div>
62
-
63
- <Button
64
- class="block"
65
- :disabled="!backupConfirmed"
66
- @click="confirmGenerated"
67
- >
68
- Continue
69
- </Button>
70
- <Button
71
- class="link muted small"
72
- @click="step = 'choose'"
73
- >
74
- <Icon type="chevron-left" />
75
- <span>Back</span>
76
- </Button>
77
- </div>
78
-
79
- <!-- Step: Restore -->
80
- <div
81
- v-else-if="step === 'restore'"
82
- class="setup-step"
83
- >
84
- <p class="muted font-sm">
85
- Enter your 12-word seed phrase to restore your wallet.
86
- </p>
87
-
88
- <EvmSeedPhraseInput
89
- v-model="restorePhrase"
90
- @valid="restoreValid = $event"
91
- @submit="restoreWallet"
92
- />
93
-
94
- <Button
95
- class="block"
96
- :disabled="!restoreValid"
97
- @click="restoreWallet"
98
- >
99
- Restore Wallet
100
- </Button>
101
- <Button
102
- class="link muted small"
103
- @click="step = 'choose'"
104
- >
105
- <Icon type="chevron-left" />
106
- <span>Back</span>
107
- </Button>
108
- </div>
109
-
110
- <!-- Step: Connecting -->
111
- <div
112
- v-else-if="step === 'connecting'"
113
- class="setup-step"
114
- >
115
- <Loading
116
- txt="Connecting wallet..."
117
- spinner
118
- stacked
119
- />
120
- </div>
121
- </div>
122
- </template>
123
-
124
- <script setup lang="ts">
125
- import { ref, computed } from 'vue'
126
- import { useConnect, useConnectors } from '@wagmi/vue'
127
- import Button from '../../base/components/Button.vue'
128
- import Icon from '../../base/components/Icon.vue'
129
- import Alert from '../../base/components/Alert.vue'
130
- import FormCheckbox from '../../base/components/FormCheckbox.vue'
131
- import Loading from '../../base/components/Loading.vue'
132
- import EvmSeedPhraseInput from './EvmSeedPhraseInput.vue'
133
- import { prepareInAppWallet } from '../connectors/inAppWallet'
134
-
135
- const props = withDefaults(
136
- defineProps<{
137
- note?: string
138
- }>(),
139
- {
140
- note: 'Create a browser-based wallet stored locally on this device. Only you have access to your keys.',
141
- },
142
- )
143
-
144
- const emit = defineEmits<{
145
- connected: []
146
- back: []
147
- }>()
148
-
149
- const connectors = useConnectors()
150
- const { mutateAsync: connectAsync } = useConnect()
151
- const inAppConnector = computed(() =>
152
- connectors.value.find((c) => c.type === 'inAppWallet'),
153
- )
154
-
155
- type Step = 'choose' | 'generate' | 'restore' | 'connecting'
156
- const step = ref<Step>('choose')
157
-
158
- // Generate state
159
- const generatedMnemonic = ref('')
160
- const generatedWords = ref<string[]>([])
161
- const backupConfirmed = ref(false)
162
-
163
- // Restore state
164
- const restorePhrase = ref('')
165
- const restoreValid = ref(false)
166
-
167
- async function startGenerate() {
168
- const { generateMnemonic, english } = await import('viem/accounts')
169
- generatedMnemonic.value = generateMnemonic(english)
170
- generatedWords.value = generatedMnemonic.value.split(' ')
171
- backupConfirmed.value = false
172
- step.value = 'generate'
173
- }
174
-
175
- async function connectWithMnemonic(mnemonic: string) {
176
- await prepareInAppWallet(mnemonic)
177
- await connectAsync({ connector: inAppConnector.value! })
178
- }
179
-
180
- async function confirmGenerated() {
181
- step.value = 'connecting'
182
- try {
183
- await connectWithMnemonic(generatedMnemonic.value)
184
- emit('connected')
185
- } catch (e) {
186
- console.error('Failed to connect in-app wallet:', e)
187
- step.value = 'generate'
188
- }
189
- }
190
-
191
- async function restoreWallet() {
192
- if (!restoreValid.value) return
193
- step.value = 'connecting'
194
- try {
195
- await connectWithMnemonic(restorePhrase.value)
196
- emit('connected')
197
- } catch (e) {
198
- console.error('Failed to restore in-app wallet:', e)
199
- step.value = 'restore'
200
- }
201
- }
202
- </script>
203
-
204
- <style scoped>
205
- .in-app-wallet-setup {
206
- display: grid;
207
- gap: var(--spacer);
208
- }
209
-
210
- .setup-step {
211
- display: grid;
212
- gap: var(--spacer);
213
- }
214
-
215
- .setup-options {
216
- display: grid;
217
- gap: var(--spacer);
218
- }
219
-
220
- .generated-words {
221
- display: grid;
222
- grid-template-columns: repeat(3, 1fr);
223
- gap: var(--spacer-sm);
224
- }
225
-
226
- .generated-word {
227
- display: flex;
228
- align-items: center;
229
- gap: var(--spacer-sm);
230
- border: var(--border);
231
- border-radius: var(--border-radius);
232
- padding: var(--spacer-sm);
233
-
234
- .word-number {
235
- font-size: var(--font-xs);
236
- color: var(--muted);
237
- min-width: 1.5em;
238
- text-align: right;
239
- user-select: none;
240
- }
241
-
242
- .word-text {
243
- font-size: var(--font-sm);
244
- font-family: var(--font-mono, monospace);
245
- }
246
- }
247
-
248
- .link.muted {
249
- justify-self: center;
250
- }
251
- </style>
@@ -1,34 +0,0 @@
1
- <template>
2
- <EvmConnectorQR :uri="uri">
3
- <template #instruction>
4
- Scan the code in your MetaMask mobile app
5
- </template>
6
- </EvmConnectorQR>
7
- <Button
8
- class="link muted small"
9
- @click="$emit('back')"
10
- >
11
- <Icon type="chevron-left" />
12
- <span>Back</span>
13
- </Button>
14
- </template>
15
-
16
- <script setup lang="ts">
17
- import EvmConnectorQR from './EvmConnectorQR.vue'
18
- import Button from '../../base/components/Button.vue'
19
- import Icon from '../../base/components/Icon.vue'
20
-
21
- defineProps<{
22
- uri: string
23
- }>()
24
-
25
- defineEmits<{
26
- back: []
27
- }>()
28
- </script>
29
-
30
- <style scoped>
31
- .link.muted {
32
- justify-self: center;
33
- }
34
- </style>
@@ -1,186 +0,0 @@
1
- <template>
2
- <Button
3
- @click="dialogOpen = true"
4
- :class="className"
5
- >
6
- <slot
7
- :address="address"
8
- :display="display"
9
- :ens-name="ensName"
10
- :ens-avatar="ensAvatar"
11
- >
12
- {{ display }}
13
- </slot>
14
- </Button>
15
-
16
- <Dialog
17
- v-model:open="dialogOpen"
18
- class="evm-profile"
19
- title="Account"
20
- compat
21
- >
22
- <div class="profile-header">
23
- <div
24
- class="banner"
25
- :style="
26
- ensHeader ? { backgroundImage: `url(${ensHeader})` } : undefined
27
- "
28
- />
29
- <div class="avatar-wrapper">
30
- <EvmAvatar
31
- :address="address"
32
- large
33
- />
34
- </div>
35
- </div>
36
-
37
- <div class="profile-identity">
38
- <strong v-if="ensName">{{ ensName }}</strong>
39
- <Button
40
- class="link muted small"
41
- @click="copyAddress"
42
- >
43
- <span>{{ shortAddr }}</span>
44
- <Icon :type="copied ? 'check' : 'copy'" />
45
- </Button>
46
- </div>
47
-
48
- <div class="profile-actions">
49
- <slot name="actions" />
50
-
51
- <EvmSwitchNetwork class-name="block">
52
- <template #default="{ currentChain }">
53
- <Icon type="wallet" />
54
- <span>Switch Network ({{ currentChain?.name || 'Unknown' }})</span>
55
- </template>
56
- </EvmSwitchNetwork>
57
-
58
- <Button
59
- v-if="ensName"
60
- class="block"
61
- :to="`https://app.ens.domains/${ensName}`"
62
- target="_blank"
63
- >
64
- <Icon type="link" />
65
- <span>Manage ENS</span>
66
- </Button>
67
-
68
- <Button
69
- class="block danger"
70
- @click="disconnect"
71
- >
72
- <span>Disconnect</span>
73
- </Button>
74
- </div>
75
- </Dialog>
76
- </template>
77
-
78
- <script setup lang="ts">
79
- import { ref, computed, nextTick } from 'vue'
80
- import { useConnection, useDisconnect } from '@wagmi/vue'
81
- import { useClipboard } from '@vueuse/core'
82
- import { useConfirm } from '../../base/composables/confirm'
83
- import { useEnsProfile } from '../composables/ens'
84
- import { useResolveUri } from '../composables/uri'
85
- import { shortAddress } from '../utils/addresses'
86
- import Button from '../../base/components/Button.vue'
87
- import Dialog from '../../base/components/Dialog.vue'
88
- import Icon from '../../base/components/Icon.vue'
89
- import EvmAvatar from './EvmAvatar.vue'
90
- import EvmSwitchNetwork from './EvmSwitchNetwork.vue'
91
-
92
- defineProps<{
93
- className?: string
94
- }>()
95
-
96
- const emit = defineEmits<{
97
- disconnected: []
98
- }>()
99
-
100
- const { address } = useConnection()
101
- const { mutate: disconnectWallet } = useDisconnect()
102
- const { confirm } = useConfirm()
103
- const { data: ensData } = useEnsProfile(address)
104
-
105
- const { copy, copied } = useClipboard()
106
- const resolve = useResolveUri()
107
-
108
- const ensName = computed(() => ensData.value?.ens || null)
109
- const ensAvatar = computed(() => resolve(ensData.value?.data?.avatar))
110
- const ensHeader = computed(() => resolve(ensData.value?.data?.header))
111
-
112
- const shortAddr = computed(() =>
113
- address.value ? shortAddress(address.value) : '',
114
- )
115
-
116
- const display = computed(() => ensName.value || shortAddr.value)
117
-
118
- const dialogOpen = ref(false)
119
-
120
- const copyAddress = () => {
121
- if (address.value) copy(address.value)
122
- }
123
-
124
- const disconnect = async () => {
125
- const confirmed = await confirm({
126
- title: 'Disconnect Wallet',
127
- description: 'Are you sure you want to disconnect your wallet?',
128
- okText: 'Disconnect',
129
- })
130
-
131
- if (!confirmed) return
132
-
133
- dialogOpen.value = false
134
- await nextTick()
135
- disconnectWallet()
136
- emit('disconnected')
137
- }
138
- </script>
139
-
140
- <style scoped>
141
- .profile-header {
142
- position: relative;
143
- margin: calc(var(--spacer) * -3) calc(var(--spacer) * -1) 0;
144
- margin-bottom: 0;
145
- z-index: -1;
146
-
147
- .banner {
148
- aspect-ratio: 3 / 1;
149
- width: 100%;
150
- height: auto;
151
- background-color: var(--gray-z-1);
152
- background-size: cover;
153
- background-position: center;
154
- border-bottom: var(--border);
155
- }
156
-
157
- .avatar-wrapper {
158
- display: flex;
159
- justify-content: center;
160
- margin-top: -10%;
161
- position: relative;
162
- z-index: 1;
163
- }
164
-
165
- :deep(.evm-avatar) {
166
- border: 3px solid var(--background);
167
- }
168
- }
169
-
170
- .profile-identity {
171
- display: grid;
172
- justify-items: center;
173
- gap: var(--spacer-sm);
174
-
175
- > strong {
176
- font-size: var(--font-lg);
177
- }
178
- }
179
-
180
- .profile-actions {
181
- display: grid;
182
- gap: var(--spacer);
183
- padding-block-start: var(--spacer);
184
-
185
- }
186
- </style>
@@ -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>