@1001-digital/layers.evm 1.0.0 → 1.0.2

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,32 @@
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: {
21
+ confirm: 'Send Transaction',
22
+ requesting: 'Requesting...',
23
+ waiting: 'Waiting for confirmation...',
24
+ complete: 'Transaction Complete!',
25
+ error: 'Transaction Error',
26
+ },
27
+ lead: {
28
+ confirm:
29
+ 'This will send 0 ETH to your address as a test transaction.',
30
+ requesting: 'Please confirm the transaction in your wallet.',
31
+ waiting: 'Your transaction is being processed...',
32
+ complete: 'Your transaction has been confirmed on-chain.',
33
+ error: 'An error occurred while processing your transaction.',
34
+ },
35
+ action: {
36
+ confirm: 'Send Transaction',
37
+ error: 'Try Again',
38
+ },
39
+ }"
40
+ @complete="onTransactionComplete"
41
+ @cancel="onTransactionCancel"
42
+ >
37
43
  <template #start="{ start }">
38
44
  <Actions>
39
45
  <Button @click="start">Start Transaction</Button>
@@ -47,10 +53,6 @@
47
53
  <p><strong>Chain:</strong> {{ chainId }}</p>
48
54
  </div>
49
55
  </template>
50
-
51
- <template #complete>
52
- <p class="success">Transaction confirmed successfully!</p>
53
- </template>
54
56
  </EvmTransactionFlow>
55
57
  </Card>
56
58
  </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 {
@@ -1,82 +1,138 @@
1
1
  <template>
2
- <slot :start="start" :step="step" :open="open" name="start"></slot>
2
+ <slot
3
+ :start="start"
4
+ :step="step"
5
+ :open="open"
6
+ name="start"
7
+ ></slot>
3
8
 
4
9
  <Dialog
5
10
  v-model:open="open"
6
- :x-close="canDismiss"
11
+ :closable="canDismiss"
7
12
  :click-outside="canDismiss"
13
+ :title="text.title[step]"
8
14
  class="transaction-flow"
9
15
  >
10
16
  <slot name="before" />
11
17
 
12
- <h1 v-if="text.title[step]">{{ text.title[step] }}</h1>
18
+ <Loading
19
+ v-if="step === 'requesting' || step === 'waiting'"
20
+ spinner
21
+ stacked
22
+ :txt="text.lead[step] || ''"
23
+ />
24
+
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>
13
35
 
14
- <div class="text">
36
+ <Alert
37
+ v-if="error"
38
+ type="error"
39
+ >
15
40
  <p v-if="text.lead[step]">{{ text.lead[step] }}</p>
16
- <p v-if="error">{{ error }}</p>
17
- </div>
18
-
19
- <slot :name="step" :cancel="cancel"></slot>
41
+ <p>{{ error }}</p>
42
+ </Alert>
20
43
 
21
44
  <Button
22
45
  v-if="step === 'waiting'"
23
46
  :to="txLink"
24
47
  target="_blank"
25
- class="block-explorer"
48
+ class="link muted small centered"
26
49
  >
27
- <Icon type="loader" class="spin" />
50
+ <Icon type="link" />
28
51
  <span>View on Block Explorer</span>
29
52
  </Button>
30
53
 
31
- <Actions v-if="step === 'chain'">
32
- <Button @click="cancel" class="secondary">Cancel</Button>
33
- </Actions>
34
-
35
- <Actions v-if="step === 'confirm' || step === 'error'">
36
- <Button @click="cancel" class="secondary">Cancel</Button>
37
- <Button @click="() => initializeRequest()">{{
38
- text.action[step] || "Execute"
39
- }}</Button>
40
- </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>
41
87
  </Dialog>
42
88
  </template>
43
89
 
44
90
  <script setup lang="ts">
45
- import { waitForTransactionReceipt, watchChainId } from "@wagmi/core";
46
- import type { Config } from "@wagmi/vue";
47
- import type { TransactionReceipt, Hash } from "viem";
91
+ import { waitForTransactionReceipt, watchChainId } from '@wagmi/core'
92
+ import type { Config } from '@wagmi/vue'
93
+ import type { TransactionReceipt, Hash } from 'viem'
48
94
 
49
95
  interface TextConfig {
50
- title: Record<string, string>;
51
- lead: Record<string, string>;
52
- action: Record<string, string>;
96
+ title: Record<string, string>
97
+ lead: Record<string, string>
98
+ action: Record<string, string>
53
99
  }
54
100
 
55
- const checkChain = useEnsureChainIdCheck();
101
+ type Step =
102
+ | 'idle'
103
+ | 'confirm'
104
+ | 'chain'
105
+ | 'requesting'
106
+ | 'waiting'
107
+ | 'complete'
108
+ | 'error'
56
109
 
57
- const { $wagmi } = useNuxtApp();
58
- const blockExplorer = useBlockExplorer();
110
+ const slots = useSlots()
111
+ const checkChain = useEnsureChainIdCheck()
112
+
113
+ const { $wagmi } = useNuxtApp()
114
+ const blockExplorer = useBlockExplorer()
59
115
 
60
116
  const props = withDefaults(
61
117
  defineProps<{
62
- text?: TextConfig;
63
- request?: () => Promise<Hash>;
64
- delayAfter?: number;
65
- delayAutoclose?: number;
66
- skipConfirmation?: boolean;
67
- autoCloseSuccess?: boolean;
68
- dismissable?: boolean;
118
+ text?: TextConfig
119
+ request?: () => Promise<Hash>
120
+ delayAfter?: number
121
+ delayAutoclose?: number
122
+ skipConfirmation?: boolean
123
+ autoCloseSuccess?: boolean
124
+ dismissable?: boolean
69
125
  }>(),
70
126
  {
71
127
  text: () => ({
72
128
  title: {
73
- confirm: "Confirm Transaction",
129
+ confirm: 'Confirm Transaction',
74
130
  },
75
131
  lead: {
76
- confirm: "Please review and confirm this transaction.",
132
+ confirm: 'Please review and confirm this transaction.',
77
133
  },
78
134
  action: {
79
- confirm: "Execute",
135
+ confirm: 'Execute',
80
136
  },
81
137
  }),
82
138
  delayAfter: 2000,
@@ -85,170 +141,129 @@ const props = withDefaults(
85
141
  autoCloseSuccess: true,
86
142
  dismissable: true,
87
143
  },
88
- );
144
+ )
89
145
 
90
146
  const emit = defineEmits<{
91
- complete: [receipt: TransactionReceipt];
92
- cancel: [];
93
- }>();
94
-
95
- const open = ref(false);
147
+ complete: [receipt: TransactionReceipt]
148
+ cancel: []
149
+ }>()
150
+
151
+ const step = ref<Step>('idle')
152
+
153
+ const open = computed({
154
+ get: () => step.value !== 'idle',
155
+ set: (v) => {
156
+ if (!v) {
157
+ step.value = 'idle'
158
+ error.value = ''
159
+ }
160
+ },
161
+ })
96
162
 
97
- const switchChain = ref(false);
98
163
  watchChainId($wagmi as Config, {
99
164
  async onChange() {
100
- if (!switchChain.value) return;
165
+ if (step.value !== 'chain') return
101
166
 
102
167
  if (await checkChain()) {
103
- switchChain.value = false;
104
- initializeRequest();
105
- } else {
106
- switchChain.value = true;
168
+ initializeRequest()
107
169
  }
108
170
  },
109
- });
171
+ })
110
172
 
111
- const cachedRequest = ref(props.request);
173
+ const cachedRequest = ref(props.request)
112
174
  watch(
113
175
  () => props.request,
114
- () => {
115
- cachedRequest.value = props.request;
176
+ (v) => {
177
+ cachedRequest.value = v
116
178
  },
117
- );
118
-
119
- const requesting = ref(false);
120
- const waiting = ref(false);
121
- const complete = ref(false);
122
- const error = ref("");
123
- const tx = ref<Hash | null>(null);
124
- const receipt = ref<TransactionReceipt | null>(null);
125
- const txLink = computed(() => `${blockExplorer}/tx/${tx.value}`);
126
-
127
- const step = computed(() => {
128
- if (
129
- open.value &&
130
- !requesting.value &&
131
- !switchChain.value &&
132
- !waiting.value &&
133
- !complete.value
134
- ) {
135
- return "confirm";
136
- }
137
-
138
- if (switchChain.value) {
139
- return "chain";
140
- }
141
-
142
- if (requesting.value) {
143
- return "requesting";
144
- }
179
+ )
145
180
 
146
- if (waiting.value) {
147
- return "waiting";
148
- }
149
-
150
- if (complete.value) {
151
- return "complete";
152
- }
153
-
154
- return "error";
155
- });
181
+ const error = ref('')
182
+ const tx = ref<Hash | null>(null)
183
+ const receipt = ref<TransactionReceipt | null>(null)
184
+ const txLink = computed(() => `${blockExplorer}/tx/${tx.value}`)
156
185
 
157
186
  const canDismiss = computed(
158
- () => props.dismissable && step.value !== "requesting",
159
- );
187
+ () =>
188
+ props.dismissable &&
189
+ step.value !== 'requesting' &&
190
+ step.value !== 'waiting',
191
+ )
160
192
 
161
193
  const initializeRequest = async (request = cachedRequest.value) => {
162
- cachedRequest.value = request;
163
- complete.value = false;
164
- open.value = true;
165
- error.value = "";
166
- tx.value = null;
167
- receipt.value = null;
194
+ cachedRequest.value = request
195
+ error.value = ''
196
+ tx.value = null
197
+ receipt.value = null
198
+ step.value = 'confirm'
168
199
 
169
200
  if (!(await checkChain())) {
170
- switchChain.value = true;
171
- return;
172
- } else {
173
- switchChain.value = false;
201
+ step.value = 'chain'
202
+ return
174
203
  }
175
204
 
176
- if (requesting.value) return;
177
-
178
205
  try {
179
- requesting.value = true;
180
- tx.value = await request!();
181
- requesting.value = false;
182
- waiting.value = true;
183
- const [receiptObject] = await Promise.all([
184
- waitForTransactionReceipt($wagmi as Config, { hash: tx.value }),
185
- ]);
186
- await delay(props.delayAfter);
187
- receipt.value = receiptObject;
188
- emit("complete", receiptObject);
189
- complete.value = true;
206
+ step.value = 'requesting'
207
+ tx.value = await request!()
208
+ step.value = 'waiting'
209
+ const receiptObject = await waitForTransactionReceipt($wagmi as Config, {
210
+ hash: tx.value,
211
+ })
212
+ await delay(props.delayAfter)
213
+ receipt.value = receiptObject
214
+ emit('complete', receiptObject)
215
+ step.value = 'complete'
190
216
  } catch (e: unknown) {
191
- const err = e as { cause?: { code?: number }; shortMessage?: string };
217
+ const err = e as { cause?: { code?: number }; shortMessage?: string }
192
218
  if (err?.cause?.code === 4001) {
193
- open.value = false;
219
+ error.value = 'Transaction rejected by user.'
220
+ step.value = 'error'
194
221
  } else {
195
- error.value = err.shortMessage || "Error submitting transaction request.";
222
+ error.value = err.shortMessage || 'Error submitting transaction request.'
223
+ step.value = 'error'
196
224
  }
197
- console.log(e);
225
+ console.log(e)
198
226
  }
199
227
 
200
- requesting.value = false;
201
- waiting.value = false;
202
-
203
- if (props.autoCloseSuccess && step.value === "complete") {
204
- await delay(props.delayAutoclose);
205
- open.value = false;
206
- await delay(300);
228
+ if (props.autoCloseSuccess && step.value === 'complete') {
229
+ await delay(props.delayAutoclose)
230
+ step.value = 'idle'
231
+ await delay(300)
207
232
  }
208
233
 
209
- return receipt.value;
210
- };
234
+ return receipt.value
235
+ }
211
236
 
212
237
  const start = () => {
213
- if (props.skipConfirmation && !open.value) {
214
- initializeRequest();
238
+ if (props.skipConfirmation && step.value === 'idle') {
239
+ initializeRequest()
240
+ return
215
241
  }
216
242
 
217
- open.value = true;
218
- };
243
+ step.value = 'confirm'
244
+ }
219
245
 
220
246
  const cancel = () => {
221
- open.value = false;
222
-
223
- emit("cancel");
224
- };
247
+ step.value = 'idle'
248
+ error.value = ''
249
+ emit('cancel')
250
+ }
225
251
 
226
252
  defineExpose({
227
253
  initializeRequest,
228
- });
254
+ })
229
255
  </script>
230
256
 
231
257
  <style>
232
- .transaction-flow {
258
+ .transaction-flow > section {
233
259
  display: grid;
234
260
  gap: var(--spacer);
235
261
 
236
- .spinner {
237
- width: var(--size-7);
238
- height: var(--size-7);
239
- margin: calc(-1 * var(--size-4)) 0 var(--size-3);
240
- }
241
-
242
262
  .text {
243
263
  width: 100%;
244
264
  height: min-content;
245
265
  }
246
266
 
247
- h1 {
248
- font-size: var(--font-lg);
249
- margin-bottom: var(--size-4);
250
- }
251
-
252
267
  p {
253
268
  white-space: pre-wrap;
254
269
  width: 100%;
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.0",
4
+ "version": "1.0.2",
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",