@1001-digital/layers.evm 1.0.1 → 1.0.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.
@@ -1,11 +1,11 @@
1
1
  export default defineAppConfig({
2
2
  evm: {
3
3
  title: 'EVM Layer Playground',
4
- defaultChain: 'mainnet',
4
+ defaultChain: 'sepolia',
5
5
  chains: {
6
- mainnet: {
7
- id: 1,
8
- blockExplorer: 'https://etherscan.io',
6
+ sepolia: {
7
+ id: 11155111,
8
+ blockExplorer: 'https://sepolia.etherscan.io',
9
9
  },
10
10
  },
11
11
  },
@@ -14,26 +14,19 @@
14
14
  <h2>Transaction Flow Example</h2>
15
15
  <p>Send 0 ETH to your own address</p>
16
16
 
17
- <EvmTransactionFlow :request="sendTransaction" :text="{
18
- title: {
19
- confirm: 'Send Transaction',
20
- requesting: 'Requesting...',
21
- waiting: 'Waiting for confirmation...',
22
- complete: 'Transaction Complete!',
23
- error: 'Transaction Error',
24
- },
25
- lead: {
26
- confirm: 'This will send 0 ETH to your address as a test transaction.',
27
- requesting: 'Please confirm the transaction in your wallet.',
28
- waiting: 'Your transaction is being processed...',
29
- complete: 'Your transaction has been confirmed on-chain.',
30
- error: 'An error occurred while processing your transaction.',
31
- },
32
- action: {
33
- confirm: 'Send Transaction',
34
- error: 'Try Again',
35
- },
36
- }" @complete="onTransactionComplete" @cancel="onTransactionCancel">
17
+ <EvmTransactionFlow
18
+ :request="sendTransaction"
19
+ :text="{
20
+ title: { confirm: 'Send Transaction' },
21
+ lead: {
22
+ confirm:
23
+ 'This will send 0 ETH to your address as a test transaction.',
24
+ },
25
+ action: { confirm: 'Send Transaction' },
26
+ }"
27
+ @complete="onTransactionComplete"
28
+ @cancel="onTransactionCancel"
29
+ >
37
30
  <template #start="{ start }">
38
31
  <Actions>
39
32
  <Button @click="start">Start Transaction</Button>
@@ -47,10 +40,6 @@
47
40
  <p><strong>Chain:</strong> {{ chainId }}</p>
48
41
  </div>
49
42
  </template>
50
-
51
- <template #complete>
52
- <p class="success">Transaction confirmed successfully!</p>
53
- </template>
54
43
  </EvmTransactionFlow>
55
44
  </Card>
56
45
  </template>
@@ -1,12 +1,19 @@
1
1
  import { fileURLToPath } from 'node:url'
2
2
 
3
+ const layerDir = fileURLToPath(new URL('..', import.meta.url))
4
+
3
5
  export default defineNuxtConfig({
4
6
  extends: ['@1001-digital/layers.base', '..'],
5
7
  modules: ['@nuxt/eslint'],
6
8
  eslint: {
7
9
  config: {
8
10
  // Use the generated ESLint config for lint root project as well
9
- rootDir: fileURLToPath(new URL('..', import.meta.url))
11
+ rootDir: layerDir,
10
12
  }
11
- }
13
+ },
14
+ hooks: {
15
+ 'vite:serverCreated': (server) => {
16
+ server.watcher.add(layerDir)
17
+ },
18
+ },
12
19
  })
@@ -1,35 +1,81 @@
1
1
  <template>
2
- <Button v-if="showConnect" @click="chooseModalOpen = true" :class="className">
2
+ <Button
3
+ v-if="showConnect"
4
+ @click="chooseModalOpen = true"
5
+ :class="className"
6
+ >
3
7
  <slot>Connect Wallet</slot>
4
8
  </Button>
5
- <slot v-else name="connected" :address="address">
9
+ <slot
10
+ v-else
11
+ name="connected"
12
+ :address="address"
13
+ >
6
14
  <EvmAccount :address="address" />
7
15
  </slot>
8
16
 
9
- <Teleport to="body">
10
- <Dialog v-if="showConnect" title="Connect Wallet" v-model:open="chooseModalOpen" @closed="onModalClosed">
11
- <Alert v-if="errorMessage" type="error">
17
+ <Dialog
18
+ v-if="showConnect"
19
+ title="Connect Wallet"
20
+ v-model:open="chooseModalOpen"
21
+ @closed="onModalClosed"
22
+ >
23
+ <Alert
24
+ v-if="errorMessage"
25
+ type="error"
26
+ >
12
27
  {{ errorMessage }}
13
28
  </Alert>
14
- <EvmWalletConnectQR v-if="walletConnectUri" :uri="walletConnectUri" />
15
- <EvmMetaMaskQR v-else-if="metaMaskUri" :uri="metaMaskUri" />
29
+ <EvmWalletConnectQR
30
+ v-if="walletConnectUri"
31
+ :uri="walletConnectUri"
32
+ />
33
+ <EvmMetaMaskQR
34
+ v-else-if="metaMaskUri"
35
+ :uri="metaMaskUri"
36
+ />
16
37
  <template v-else-if="isConnecting">
17
- <Loading txt="Waiting for wallet confirmation..." spinner />
38
+ <Loading
39
+ txt="Waiting for wallet confirmation..."
40
+ spinner
41
+ stacked
42
+ />
18
43
  </template>
19
- <div v-else class="wallet-options">
20
- <Button v-for="connector in shownConnectors" :key="connector.uid" @click="() => login(connector)"
21
- class="choose-connector">
22
- <img v-if="ICONS[connector.name]" :src="connector.icon || `${base}icons/wallets/${ICONS[connector.name]}`"
23
- :alt="connector.name" />
44
+ <div
45
+ v-else
46
+ class="wallet-options"
47
+ >
48
+ <Button
49
+ v-for="connector in shownConnectors"
50
+ :key="connector.uid"
51
+ @click="() => login(connector)"
52
+ class="choose-connector"
53
+ >
54
+ <img
55
+ v-if="ICONS[connector.name]"
56
+ :src="
57
+ connector.icon || `${base}icons/wallets/${ICONS[connector.name]}`
58
+ "
59
+ :alt="connector.name"
60
+ />
61
+ <div
62
+ v-else
63
+ class="default-wallet-icon"
64
+ >
65
+ <Icon type="wallet" />
66
+ </div>
24
67
  <span>{{ connector.name }}</span>
25
68
  </Button>
26
- <Button to="https://ethereum.org/wallets/" target="_blank" class="link muted small">
69
+ <Button
70
+ to="https://ethereum.org/wallets/"
71
+ target="_blank"
72
+ class="link muted small"
73
+ >
27
74
  <Icon type="help" />
28
75
  <span>New to wallets?</span>
29
76
  </Button>
30
77
  </div>
31
- </Dialog>
32
- </Teleport>
78
+ </Dialog>
33
79
  </template>
34
80
 
35
81
  <script setup lang="ts">
@@ -46,7 +92,7 @@ const ICONS: Record<string, string> = {
46
92
  }
47
93
 
48
94
  const PRIORITY: Record<string, number> = {
49
- 'WalletConnect': 20,
95
+ WalletConnect: 20,
50
96
  'Coinbase Wallet': 10,
51
97
  }
52
98
 
@@ -66,10 +112,13 @@ const { address, isConnected } = useConnection()
66
112
  const showConnect = computed(() => !isConnected.value)
67
113
  const shownConnectors = computed(() => {
68
114
  const unique = Array.from(
69
- new Map(connectors?.map((connector) => [connector.name, connector])).values(),
115
+ new Map(
116
+ connectors?.map((connector) => [connector.name, connector]),
117
+ ).values(),
70
118
  )
71
119
 
72
- const filtered = unique.length > 1 ? unique.filter((c) => c.id !== 'injected') : unique
120
+ const filtered =
121
+ unique.length > 1 ? unique.filter((c) => c.id !== 'injected') : unique
73
122
 
74
123
  return filtered.sort((a, b) => {
75
124
  const priorityA = PRIORITY[a.name] ?? 5
@@ -119,7 +168,11 @@ const login = async (connector: Connector) => {
119
168
  metaMaskUri.value = ''
120
169
 
121
170
  const errorMsg = error instanceof Error ? error.message : ''
122
- if (errorMsg.includes('User rejected') || errorMsg.includes('rejected') || errorMsg.includes('denied')) {
171
+ if (
172
+ errorMsg.includes('User rejected') ||
173
+ errorMsg.includes('rejected') ||
174
+ errorMsg.includes('denied')
175
+ ) {
123
176
  errorMessage.value = 'Connection cancelled. Please try again.'
124
177
  } else {
125
178
  errorMessage.value = 'Failed to connect. Please try again.'
@@ -140,7 +193,9 @@ const onModalClosed = () => {
140
193
  }
141
194
 
142
195
  const check = () =>
143
- isConnected.value ? emit('connected', { address: address.value }) : emit('disconnected')
196
+ isConnected.value
197
+ ? emit('connected', { address: address.value })
198
+ : emit('disconnected')
144
199
  watch(isConnected, () => check())
145
200
  onMounted(() => check())
146
201
  </script>
@@ -155,18 +210,25 @@ onMounted(() => check())
155
210
  inline-size: auto;
156
211
  justify-content: flex-start;
157
212
 
158
- img {
213
+ img,
214
+ .default-wallet-icon {
159
215
  margin: -1rem 0 -1rem -0.6rem;
160
216
  width: var(--size-5);
161
217
  height: var(--size-5);
162
218
  }
163
219
 
164
- span {
220
+ .default-wallet-icon {
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: center;
224
+ background: var(--gray-z-2);
225
+ }
226
+
227
+ span:last-child {
165
228
  border-left: var(--border);
166
229
  padding-left: var(--spacer-sm);
167
230
  }
168
231
  }
169
-
170
232
  }
171
233
 
172
234
  .link.muted {
@@ -8,57 +8,82 @@
8
8
 
9
9
  <Dialog
10
10
  v-model:open="open"
11
- :x-close="canDismiss"
11
+ :closable="canDismiss"
12
12
  :click-outside="canDismiss"
13
+ :title="text.title[step]"
13
14
  class="transaction-flow"
14
15
  >
15
16
  <slot name="before" />
16
17
 
17
- <h1 v-if="text.title[step]">{{ text.title[step] }}</h1>
18
-
19
18
  <Loading
20
19
  v-if="step === 'requesting' || step === 'waiting'"
21
20
  spinner
22
- txt=""
21
+ stacked
22
+ :txt="text.lead[step] || ''"
23
23
  />
24
24
 
25
- <div class="text">
26
- <p v-if="text.lead[step]">{{ text.lead[step] }}</p>
27
- <p v-if="error">{{ error }}</p>
28
- </div>
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>
29
35
 
30
- <slot
31
- :name="step"
32
- :cancel="cancel"
33
- ></slot>
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>
34
43
 
35
44
  <Button
36
45
  v-if="step === 'waiting'"
37
46
  :to="txLink"
38
47
  target="_blank"
39
- class="block-explorer"
48
+ class="link muted small centered"
40
49
  >
41
- View on Block Explorer
50
+ <Icon type="link" />
51
+ <span>View on Block Explorer</span>
42
52
  </Button>
43
53
 
44
- <Actions v-if="step === 'chain'">
45
- <Button
46
- @click="cancel"
47
- class="secondary"
48
- >Cancel</Button
49
- >
50
- </Actions>
51
-
52
- <Actions v-if="step === 'confirm' || step === 'error'">
53
- <Button
54
- @click="cancel"
55
- class="secondary"
56
- >Cancel</Button
57
- >
58
- <Button @click="() => initializeRequest()">
59
- {{ text.action[step] || 'Execute' }}
60
- </Button>
61
- </Actions>
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>
62
87
  </Dialog>
63
88
  </template>
64
89
 
@@ -68,9 +93,9 @@ import type { Config } from '@wagmi/vue'
68
93
  import type { TransactionReceipt, Hash } from 'viem'
69
94
 
70
95
  interface TextConfig {
71
- title: Record<string, string>
72
- lead: Record<string, string>
73
- action: Record<string, string>
96
+ title?: Record<string, string>
97
+ lead?: Record<string, string>
98
+ action?: Record<string, string>
74
99
  }
75
100
 
76
101
  type Step =
@@ -82,6 +107,29 @@ type Step =
82
107
  | 'complete'
83
108
  | 'error'
84
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()
85
133
  const checkChain = useEnsureChainIdCheck()
86
134
 
87
135
  const { $wagmi } = useNuxtApp()
@@ -98,17 +146,6 @@ const props = withDefaults(
98
146
  dismissable?: boolean
99
147
  }>(),
100
148
  {
101
- text: () => ({
102
- title: {
103
- confirm: 'Confirm Transaction',
104
- },
105
- lead: {
106
- confirm: 'Please review and confirm this transaction.',
107
- },
108
- action: {
109
- confirm: 'Execute',
110
- },
111
- }),
112
149
  delayAfter: 2000,
113
150
  delayAutoclose: 2000,
114
151
  skipConfirmation: false,
@@ -122,12 +159,21 @@ const emit = defineEmits<{
122
159
  cancel: []
123
160
  }>()
124
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
+
125
168
  const step = ref<Step>('idle')
126
169
 
127
170
  const open = computed({
128
171
  get: () => step.value !== 'idle',
129
172
  set: (v) => {
130
- if (!v) step.value = 'idle'
173
+ if (!v) {
174
+ step.value = 'idle'
175
+ error.value = ''
176
+ }
131
177
  },
132
178
  })
133
179
 
@@ -187,7 +233,8 @@ const initializeRequest = async (request = cachedRequest.value) => {
187
233
  } catch (e: unknown) {
188
234
  const err = e as { cause?: { code?: number }; shortMessage?: string }
189
235
  if (err?.cause?.code === 4001) {
190
- step.value = 'idle'
236
+ error.value = 'Transaction rejected by user.'
237
+ step.value = 'error'
191
238
  } else {
192
239
  error.value = err.shortMessage || 'Error submitting transaction request.'
193
240
  step.value = 'error'
@@ -215,6 +262,7 @@ const start = () => {
215
262
 
216
263
  const cancel = () => {
217
264
  step.value = 'idle'
265
+ error.value = ''
218
266
  emit('cancel')
219
267
  }
220
268
 
@@ -224,7 +272,7 @@ defineExpose({
224
272
  </script>
225
273
 
226
274
  <style>
227
- .transaction-flow {
275
+ .transaction-flow > section {
228
276
  display: grid;
229
277
  gap: var(--spacer);
230
278
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@1001-digital/layers.evm",
3
3
  "type": "module",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "main": "./nuxt.config.ts",
6
6
  "devDependencies": {
7
7
  "@nuxt/eslint": "latest",
@@ -10,10 +10,10 @@
10
10
  "nuxt": "^4.3.0",
11
11
  "typescript": "^5.9.3",
12
12
  "vue": "latest",
13
- "@1001-digital/layers.base": "^0.0.17"
13
+ "@1001-digital/layers.base": "^0.0.19"
14
14
  },
15
15
  "peerDependencies": {
16
- "@1001-digital/layers.base": "^0.0.17"
16
+ "@1001-digital/layers.base": "^0.0.19"
17
17
  },
18
18
  "dependencies": {
19
19
  "@types/qrcode": "^1.5.6",