@1001-digital/layers.evm 1.0.9 → 1.0.10

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.
@@ -15,6 +15,11 @@
15
15
  </Card>
16
16
 
17
17
  <SignedIn />
18
+
19
+ <Card>
20
+ <h2>Pages</h2>
21
+ <NuxtLink to="/transaction-flow">Transaction Flow States</NuxtLink>
22
+ </Card>
18
23
  </div>
19
24
  </template>
20
25
 
@@ -0,0 +1,225 @@
1
+ <template>
2
+ <div class="playground">
3
+ <h1>EvmTransactionFlow States</h1>
4
+ <p><NuxtLink to="/">&larr; Back</NuxtLink></p>
5
+
6
+ <Card>
7
+ <h2>Confirm</h2>
8
+ <p>Opens the dialog at the confirmation step.</p>
9
+
10
+ <EvmTransactionFlow
11
+ :request="hangingRequest"
12
+ :text="{
13
+ title: { confirm: 'Confirm Transfer' },
14
+ lead: {
15
+ confirm: 'You are about to send 1 ETH to vitalik.eth.',
16
+ },
17
+ action: { confirm: 'Send' },
18
+ }"
19
+ >
20
+ <template #start="{ start }">
21
+ <Actions>
22
+ <Button @click="start">Open Confirm</Button>
23
+ </Actions>
24
+ </template>
25
+
26
+ <template #confirm>
27
+ <div class="tx-details">
28
+ <p><strong>To:</strong> vitalik.eth</p>
29
+ <p><strong>Amount:</strong> 1 ETH</p>
30
+ <p><strong>Network:</strong> Sepolia</p>
31
+ </div>
32
+ </template>
33
+ </EvmTransactionFlow>
34
+ </Card>
35
+
36
+ <Card>
37
+ <h2>Requesting</h2>
38
+ <p>Skips confirmation, shows the loading spinner while awaiting wallet signature.</p>
39
+
40
+ <EvmTransactionFlow
41
+ skip-confirmation
42
+ :dismissable="false"
43
+ :request="hangingRequest"
44
+ :text="{
45
+ title: { requesting: 'Awaiting Signature' },
46
+ lead: { requesting: 'Please confirm in your wallet...' },
47
+ }"
48
+ >
49
+ <template #start="{ start }">
50
+ <Actions>
51
+ <Button @click="start">Open Requesting</Button>
52
+ </Actions>
53
+ </template>
54
+ </EvmTransactionFlow>
55
+ </Card>
56
+
57
+ <Card>
58
+ <h2>Error: User Rejected</h2>
59
+ <p>Simulates a user rejecting the transaction in their wallet.</p>
60
+
61
+ <EvmTransactionFlow
62
+ skip-confirmation
63
+ :request="rejectedRequest"
64
+ >
65
+ <template #start="{ start }">
66
+ <Actions>
67
+ <Button @click="start">Open Rejected</Button>
68
+ </Actions>
69
+ </template>
70
+ </EvmTransactionFlow>
71
+ </Card>
72
+
73
+ <Card>
74
+ <h2>Error: Transaction Failed</h2>
75
+ <p>Simulates a generic transaction error.</p>
76
+
77
+ <EvmTransactionFlow
78
+ skip-confirmation
79
+ :request="failedRequest"
80
+ :text="{
81
+ title: { error: 'Transfer Failed' },
82
+ action: { error: 'Retry' },
83
+ }"
84
+ >
85
+ <template #start="{ start }">
86
+ <Actions>
87
+ <Button @click="start">Open Failed</Button>
88
+ </Actions>
89
+ </template>
90
+ </EvmTransactionFlow>
91
+ </Card>
92
+
93
+ <Card>
94
+ <h2>Waiting &amp; Complete (Toast)</h2>
95
+ <p>
96
+ After signing, the dialog closes and a toast tracks the receipt.
97
+ This mock resolves instantly and shows the success toast.
98
+ </p>
99
+
100
+ <EvmTransactionFlow
101
+ skip-confirmation
102
+ :request="successRequest"
103
+ :delay-after="0"
104
+ :delay-autoclose="5000"
105
+ :text="{
106
+ title: {
107
+ waiting: 'Transfer Pending',
108
+ complete: 'Transfer Complete',
109
+ },
110
+ lead: {
111
+ waiting: 'Waiting for on-chain confirmation...',
112
+ complete: 'Your transfer has been confirmed.',
113
+ },
114
+ }"
115
+ >
116
+ <template #start="{ start }">
117
+ <Actions>
118
+ <Button @click="start">Open Waiting/Complete</Button>
119
+ </Actions>
120
+ </template>
121
+ </EvmTransactionFlow>
122
+ </Card>
123
+
124
+ <Card>
125
+ <h2>Chain Switch</h2>
126
+ <p>
127
+ Shown when the connected wallet is on a different chain.
128
+ Connect to a non-Sepolia network to see this state.
129
+ </p>
130
+
131
+ <EvmTransactionFlow
132
+ :request="hangingRequest"
133
+ :text="{
134
+ title: { chain: 'Wrong Network' },
135
+ lead: { chain: 'Please switch to Sepolia to continue.' },
136
+ }"
137
+ >
138
+ <template #start="{ start }">
139
+ <Actions>
140
+ <Button @click="start">Open Chain Switch</Button>
141
+ </Actions>
142
+ </template>
143
+ </EvmTransactionFlow>
144
+ </Card>
145
+
146
+ <Card>
147
+ <h2>Custom Actions Slot</h2>
148
+ <p>Uses the <code>actions</code> slot to add custom footer buttons.</p>
149
+
150
+ <EvmTransactionFlow
151
+ :request="hangingRequest"
152
+ :text="{
153
+ title: { confirm: 'Custom Actions' },
154
+ lead: { confirm: 'This dialog has custom footer actions.' },
155
+ }"
156
+ >
157
+ <template #start="{ start }">
158
+ <Actions>
159
+ <Button @click="start">Open Custom Actions</Button>
160
+ </Actions>
161
+ </template>
162
+
163
+ <template #actions="{ cancel, step }">
164
+ <template v-if="step === 'confirm'">
165
+ <Button class="secondary" @click="cancel">Nevermind</Button>
166
+ <Button class="secondary">Save Draft</Button>
167
+ <Button>Approve &amp; Send</Button>
168
+ </template>
169
+ </template>
170
+ </EvmTransactionFlow>
171
+ </Card>
172
+ </div>
173
+ </template>
174
+
175
+ <script setup lang="ts">
176
+ import type { Hash } from 'viem'
177
+
178
+ const delay = (ms: number) => new Promise((r) => setTimeout(r, ms))
179
+
180
+ const hangingRequest = () => new Promise<Hash>(() => {})
181
+
182
+ const rejectedRequest = async (): Promise<Hash> => {
183
+ await delay(500)
184
+ const err = new Error('User rejected the request.')
185
+ ;(err as any).cause = { code: 4001 }
186
+ ;(err as any).shortMessage = 'User rejected the request.'
187
+ throw err
188
+ }
189
+
190
+ const failedRequest = async (): Promise<Hash> => {
191
+ await delay(500)
192
+ const err = new Error('Insufficient funds for gas.')
193
+ ;(err as any).shortMessage = 'Insufficient funds for gas.'
194
+ throw err
195
+ }
196
+
197
+ const successRequest = async (): Promise<Hash> => {
198
+ await delay(500)
199
+ return '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as Hash
200
+ }
201
+ </script>
202
+
203
+ <style scoped>
204
+ .playground {
205
+ max-width: 50rem;
206
+ margin: 0 auto;
207
+ padding: var(--spacer);
208
+ display: grid;
209
+ gap: var(--spacer);
210
+ }
211
+
212
+ .tx-details {
213
+ padding: var(--size-4);
214
+ background: var(--gray-z-1);
215
+ border-radius: var(--radius);
216
+ display: grid;
217
+ gap: var(--size-3);
218
+ }
219
+
220
+ .tx-details p {
221
+ margin: 0;
222
+ font-family: var(--font-mono);
223
+ font-size: var(--font-sm);
224
+ }
225
+ </style>
@@ -1,7 +1,9 @@
1
1
  import { fileURLToPath } from 'node:url'
2
2
 
3
3
  const layerDir = fileURLToPath(new URL('..', import.meta.url))
4
- const componentsDir = fileURLToPath(new URL('../../components/src', import.meta.url))
4
+ const componentsDir = fileURLToPath(
5
+ new URL('../../components/src', import.meta.url),
6
+ )
5
7
 
6
8
  export default defineNuxtConfig({
7
9
  extends: ['@1001-digital/layers.base', '..'],
@@ -1 +1,5 @@
1
- export { useEns, useEnsWithAvatar, useEnsProfile } from '@1001-digital/components'
1
+ export {
2
+ useEns,
3
+ useEnsWithAvatar,
4
+ useEnsProfile,
5
+ } from '@1001-digital/components'
@@ -1,7 +1,11 @@
1
- export default defineNuxtPlugin(() => {
2
- const priceFeed = usePriceFeed()
1
+ export default defineNuxtPlugin({
2
+ name: 'price-feed',
3
+ dependsOn: ['wagmi'],
4
+ setup() {
5
+ const priceFeed = usePriceFeed()
3
6
 
4
- priceFeed.fetchPrice()
7
+ priceFeed.fetchPrice()
5
8
 
6
- setInterval(() => priceFeed.fetchPrice(), 60 * 60 * 1000)
9
+ setInterval(() => priceFeed.fetchPrice(), 60 * 60 * 1000)
10
+ },
7
11
  })
@@ -22,104 +22,107 @@ import {
22
22
  type EvmConfig,
23
23
  } from '@1001-digital/components'
24
24
 
25
- export default defineNuxtPlugin((nuxtApp) => {
26
- const appConfig = useAppConfig()
27
- const runtimeConfig = nuxtApp.$config.public.evm as {
28
- walletConnectProjectId: string
29
- chains: Record<string, { rpc1?: string; rpc2?: string; rpc3?: string }>
30
- ens: { indexer1?: string; indexer2?: string; indexer3?: string }
31
- }
25
+ export default defineNuxtPlugin({
26
+ name: 'wagmi',
27
+ setup(nuxtApp) {
28
+ const appConfig = useAppConfig()
29
+ const runtimeConfig = nuxtApp.$config.public.evm as {
30
+ walletConnectProjectId: string
31
+ chains: Record<string, { rpc1?: string; rpc2?: string; rpc3?: string }>
32
+ ens: { indexer1?: string; indexer2?: string; indexer3?: string }
33
+ }
32
34
 
33
- const title = appConfig.evm?.title || 'EVM Layer'
34
- const chainEntries = appConfig.evm?.chains || {}
35
+ const title = appConfig.evm?.title || 'EVM Layer'
36
+ const chainEntries = appConfig.evm?.chains || {}
35
37
 
36
- // Build chains and transports from config
37
- const chains: [Chain, ...Chain[]] = [] as unknown as [Chain, ...Chain[]]
38
- const transports: Record<number, Transport> = {}
38
+ // Build chains and transports from config
39
+ const chains: [Chain, ...Chain[]] = [] as unknown as [Chain, ...Chain[]]
40
+ const transports: Record<number, Transport> = {}
39
41
 
40
- for (const [key, entry] of Object.entries(chainEntries)) {
41
- const chain = resolveChain(entry.id!)
42
- chains.push(chain)
42
+ for (const [key, entry] of Object.entries(chainEntries)) {
43
+ const chain = resolveChain(entry.id!)
44
+ chains.push(chain)
43
45
 
44
- const rpcs = runtimeConfig.chains?.[key]
45
- const transportList = []
46
- if (rpcs?.rpc1) transportList.push(http(rpcs.rpc1))
47
- if (rpcs?.rpc2) transportList.push(http(rpcs.rpc2))
48
- if (rpcs?.rpc3) transportList.push(http(rpcs.rpc3))
49
- transportList.push(http())
46
+ const rpcs = runtimeConfig.chains?.[key]
47
+ const transportList = []
48
+ if (rpcs?.rpc1) transportList.push(http(rpcs.rpc1))
49
+ if (rpcs?.rpc2) transportList.push(http(rpcs.rpc2))
50
+ if (rpcs?.rpc3) transportList.push(http(rpcs.rpc3))
51
+ transportList.push(http())
50
52
 
51
- transports[chain.id] = fallback(transportList)
52
- }
53
+ transports[chain.id] = fallback(transportList)
54
+ }
53
55
 
54
- // Connectors
55
- const connectors: CreateConnectorFn[] = [
56
- injected(),
57
- coinbaseWallet({
58
- appName: title,
59
- appLogoUrl: '',
60
- }),
61
- metaMask({
62
- headless: true,
63
- dappMetadata: {
64
- name: title,
65
- iconUrl: '',
66
- url: '',
67
- },
68
- }),
69
- ]
70
-
71
- if (import.meta.client && runtimeConfig.walletConnectProjectId)
72
- connectors.push(
73
- walletConnect({
74
- projectId: runtimeConfig.walletConnectProjectId,
75
- showQrModal: false,
56
+ // Connectors
57
+ const connectors: CreateConnectorFn[] = [
58
+ injected(),
59
+ coinbaseWallet({
60
+ appName: title,
61
+ appLogoUrl: '',
76
62
  }),
77
- )
63
+ metaMask({
64
+ headless: true,
65
+ dappMetadata: {
66
+ name: title,
67
+ iconUrl: '',
68
+ url: '',
69
+ },
70
+ }),
71
+ ]
72
+
73
+ if (import.meta.client && runtimeConfig.walletConnectProjectId)
74
+ connectors.push(
75
+ walletConnect({
76
+ projectId: runtimeConfig.walletConnectProjectId,
77
+ showQrModal: false,
78
+ }),
79
+ )
78
80
 
79
- const wagmiConfig: Config = createConfig({
80
- chains,
81
- batch: {
82
- multicall: true,
83
- },
84
- connectors,
85
- storage: createStorage({
86
- storage: cookieStorage,
87
- }),
88
- ssr: true,
89
- transports,
90
- })
81
+ const wagmiConfig: Config = createConfig({
82
+ chains,
83
+ batch: {
84
+ multicall: true,
85
+ },
86
+ connectors,
87
+ storage: createStorage({
88
+ storage: cookieStorage,
89
+ }),
90
+ ssr: true,
91
+ transports,
92
+ })
91
93
 
92
- // Build EvmConfig from Nuxt app/runtime config
93
- const indexerUrls = [
94
- runtimeConfig.ens?.indexer1,
95
- runtimeConfig.ens?.indexer2,
96
- runtimeConfig.ens?.indexer3,
97
- ].filter(Boolean) as string[]
94
+ // Build EvmConfig from Nuxt app/runtime config
95
+ const indexerUrls = [
96
+ runtimeConfig.ens?.indexer1,
97
+ runtimeConfig.ens?.indexer2,
98
+ runtimeConfig.ens?.indexer3,
99
+ ].filter(Boolean) as string[]
98
100
 
99
- const evmConfig: EvmConfig = {
100
- title,
101
- defaultChain: appConfig.evm?.defaultChain || 'mainnet',
102
- chains: Object.fromEntries(
103
- Object.entries(chainEntries).map(([key, entry]) => [
104
- key,
105
- { id: entry.id!, blockExplorer: entry.blockExplorer },
106
- ]),
107
- ),
108
- ens: {
109
- mode: appConfig.evm?.ens?.mode || 'indexer',
110
- indexerUrls,
111
- },
112
- baseURL: nuxtApp.$config.app.baseURL,
113
- }
101
+ const evmConfig: EvmConfig = {
102
+ title,
103
+ defaultChain: appConfig.evm?.defaultChain || 'mainnet',
104
+ chains: Object.fromEntries(
105
+ Object.entries(chainEntries).map(([key, entry]) => [
106
+ key,
107
+ { id: entry.id!, blockExplorer: entry.blockExplorer },
108
+ ]),
109
+ ),
110
+ ens: {
111
+ mode: appConfig.evm?.ens?.mode || 'indexer',
112
+ indexerUrls,
113
+ },
114
+ baseURL: nuxtApp.$config.app.baseURL,
115
+ }
114
116
 
115
- nuxtApp.vueApp
116
- .use(WagmiPlugin, { config: wagmiConfig })
117
- .use(VueQueryPlugin, {})
118
- .provide(EvmConfigKey, evmConfig)
117
+ nuxtApp.vueApp
118
+ .use(WagmiPlugin, { config: wagmiConfig })
119
+ .use(VueQueryPlugin, {})
120
+ .provide(EvmConfigKey, evmConfig)
119
121
 
120
- return {
121
- provide: {
122
- wagmi: wagmiConfig,
123
- },
124
- }
122
+ return {
123
+ provide: {
124
+ wagmi: wagmiConfig,
125
+ },
126
+ }
127
+ },
125
128
  })
package/app/utils/ens.ts CHANGED
@@ -1,2 +1,8 @@
1
- export { ensCache, fetchEnsFromIndexer, fetchEnsFromChain, ENS_KEYS_AVATAR, ENS_KEYS_PROFILE } from '@1001-digital/components'
1
+ export {
2
+ ensCache,
3
+ fetchEnsFromIndexer,
4
+ fetchEnsFromChain,
5
+ ENS_KEYS_AVATAR,
6
+ ENS_KEYS_PROFILE,
7
+ } from '@1001-digital/components'
2
8
  export type { EnsProfile } from '@1001-digital/components'
package/nuxt.config.ts CHANGED
@@ -1,14 +1,24 @@
1
1
  import { fileURLToPath } from 'node:url'
2
+ import { createRequire } from 'node:module'
3
+ import { dirname } from 'node:path'
4
+
5
+ const require = createRequire(import.meta.url)
2
6
 
3
7
  const componentsDir = fileURLToPath(
4
8
  new URL('../components/src/evm/components', import.meta.url),
5
9
  )
6
10
 
11
+ // Force all @wagmi/vue imports to resolve to a single copy.
12
+ // pnpm creates separate instances per dependency set, each with its own
13
+ // Symbol-based configKey — breaking Vue's provide/inject across packages.
14
+ const wagmiVue = dirname(require.resolve('@wagmi/vue/package.json'))
15
+
7
16
  const clientOnlyComponents = [
8
17
  'EvmAccount',
9
18
  'EvmConnect',
10
19
  'EvmConnectorQR',
11
20
  'EvmMetaMaskQR',
21
+ 'EvmTransactionFlow',
12
22
  'EvmWalletConnectQR',
13
23
  ]
14
24
 
@@ -54,7 +64,9 @@ export default defineNuxtConfig({
54
64
 
55
65
  vite: {
56
66
  resolve: {
57
- dedupe: ['@wagmi/vue', '@wagmi/core', 'viem'],
67
+ alias: {
68
+ '@wagmi/vue': wagmiVue,
69
+ },
58
70
  },
59
71
  optimizeDeps: {
60
72
  include: [
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.9",
4
+ "version": "1.0.10",
5
5
  "main": "./nuxt.config.ts",
6
6
  "devDependencies": {
7
7
  "@nuxt/eslint": "latest",
@@ -10,19 +10,18 @@
10
10
  "nuxt": "^4.3.0",
11
11
  "typescript": "^5.9.3",
12
12
  "vue": "latest",
13
- "@1001-digital/layers.base": "^0.0.32"
13
+ "@1001-digital/layers.base": "^0.0.33"
14
14
  },
15
15
  "peerDependencies": {
16
- "@1001-digital/layers.base": "^0.0.32"
16
+ "@1001-digital/layers.base": "^0.0.33"
17
17
  },
18
18
  "dependencies": {
19
- "@metamask/sdk": "~0.34.0",
19
+ "@metamask/sdk": "~0.33.1",
20
20
  "@tanstack/vue-query": "^5.92.9",
21
- "@wagmi/core": "^3.3.2",
22
- "@wagmi/vue": "^0.4.15",
21
+ "@wagmi/vue": "^0.5.0",
23
22
  "@walletconnect/ethereum-provider": "~2.23.4",
24
23
  "viem": "~2.45.1",
25
- "@1001-digital/components": "^0.0.2"
24
+ "@1001-digital/components": "^0.0.3"
26
25
  },
27
26
  "scripts": {
28
27
  "dev": "nuxi dev .playground",