@visualizevalue/mint-app-base 0.1.126 → 0.2.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.
- package/LICENSE +21 -0
- package/components/Collection/Withdraw.client.vue +1 -1
- package/components/MintTokenBar.vue +1 -0
- package/components/Token/MintTimeline.client.vue +1 -43
- package/components/TransactionFlow.vue +29 -9
- package/components/TransactionToasts.vue +43 -0
- package/composables/collections.ts +57 -266
- package/composables/transactions.ts +51 -0
- package/nuxt.config.ts +1 -0
- package/package.json +29 -1
- package/plugins/2.5.dapp-query.client.ts +40 -0
- package/plugins/transaction-toasts.client.ts +13 -0
- package/queries/index.ts +144 -0
- package/queries/sources.ts +417 -0
- package/.env.example +0 -50
- package/.nuxtrc +0 -1
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import { getBalance, getPublicClient, readContract } from '@wagmi/core'
|
|
2
|
-
import { type
|
|
3
|
-
import { parseAbiItem, type PublicClient } from 'viem'
|
|
2
|
+
import { type PublicClient } from 'viem'
|
|
4
3
|
import type { MintEvent } from '~/utils/types'
|
|
4
|
+
import { INDEXER_SYNCED } from '~/queries/sources'
|
|
5
5
|
|
|
6
|
-
export const CURRENT_STATE_VERSION =
|
|
7
|
-
export const MAX_BLOCK_RANGE = 1800n
|
|
6
|
+
export const CURRENT_STATE_VERSION = 10
|
|
8
7
|
export const MINT_BLOCKS = BLOCKS_PER_DAY
|
|
9
8
|
export const MAX_RENDERERS = 20
|
|
10
9
|
|
|
11
|
-
const inflightRequests = new Map<string, Promise<any>>()
|
|
12
|
-
function dedupe<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
13
|
-
if (!inflightRequests.has(key)) {
|
|
14
|
-
inflightRequests.set(key, fn().finally(() => inflightRequests.delete(key)))
|
|
15
|
-
}
|
|
16
|
-
return inflightRequests.get(key)!
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
export const useOnchainStore = () => {
|
|
20
|
-
const { $wagmi } = useNuxtApp()
|
|
11
|
+
const { $wagmi, $queryClient, $queries } = useNuxtApp()
|
|
21
12
|
const appConfig = useAppConfig()
|
|
22
13
|
const chainId = useMainChainId()
|
|
23
14
|
|
|
@@ -103,54 +94,17 @@ export const useOnchainStore = () => {
|
|
|
103
94
|
|
|
104
95
|
if (!this.hasArtist(address)) this.initializeArtist(address)
|
|
105
96
|
|
|
106
|
-
await
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
await Promise.all([
|
|
98
|
+
this.fetchArtistProfile(address),
|
|
99
|
+
this.fetchCollections(address, factory),
|
|
100
|
+
])
|
|
109
101
|
},
|
|
110
102
|
|
|
111
103
|
async fetchArtistProfile (address: `0x${string}`): Promise<Artist> {
|
|
112
|
-
|
|
113
|
-
const block = await client.getBlockNumber()
|
|
114
|
-
|
|
115
|
-
// Only update once per hour
|
|
116
|
-
if (
|
|
117
|
-
this.hasArtist(address) &&
|
|
118
|
-
this.artist(address).profileUpdatedAtBlock > 0n &&
|
|
119
|
-
(block - this.artist(address).profileUpdatedAtBlock) < BLOCKS_PER_CACHE
|
|
120
|
-
) {
|
|
121
|
-
console.info(`Artist profile already fetched...`)
|
|
122
|
-
return this.artist(address)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.info(`Updating artist profile...`)
|
|
126
|
-
|
|
127
|
-
let ens, avatar, description,
|
|
128
|
-
url, email, twitter, github
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
ens = await client.getEnsName({ address })
|
|
132
|
-
|
|
133
|
-
if (ens) {
|
|
134
|
-
[avatar, description, url, email, twitter, github] = await Promise.all([
|
|
135
|
-
client.getEnsAvatar({ name: ens }),
|
|
136
|
-
client.getEnsText({ name: ens, key: 'description' }),
|
|
137
|
-
client.getEnsText({ name: ens, key: 'url' }),
|
|
138
|
-
client.getEnsText({ name: ens, key: 'email' }),
|
|
139
|
-
client.getEnsText({ name: ens, key: 'com.twitter' }),
|
|
140
|
-
client.getEnsText({ name: ens, key: 'com.github' }),
|
|
141
|
-
])
|
|
142
|
-
}
|
|
143
|
-
} catch (e) { }
|
|
144
|
-
|
|
145
|
-
this.artists[address].ens = ens
|
|
146
|
-
this.artists[address].avatar = avatar
|
|
147
|
-
this.artists[address].description = description
|
|
148
|
-
this.artists[address].url = url
|
|
149
|
-
this.artists[address].email = email
|
|
150
|
-
this.artists[address].twitter = twitter
|
|
151
|
-
this.artists[address].github = github
|
|
152
|
-
this.artists[address].profileUpdatedAtBlock = block
|
|
104
|
+
if (!this.hasArtist(address)) this.initializeArtist(address)
|
|
153
105
|
|
|
106
|
+
const profile = await $queryClient.fetch($queries.artistProfile, address)
|
|
107
|
+
Object.assign(this.artists[address], profile)
|
|
154
108
|
return this.artist(address)
|
|
155
109
|
},
|
|
156
110
|
|
|
@@ -158,95 +112,43 @@ export const useOnchainStore = () => {
|
|
|
158
112
|
artist: `0x${string}`,
|
|
159
113
|
factory: `0x${string}`
|
|
160
114
|
) {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
functionName: 'getCreatorCollections',
|
|
165
|
-
args: [artist],
|
|
166
|
-
chainId,
|
|
167
|
-
})).map((a: `0x${string}`) => a.toLowerCase() as `0x${string}`)
|
|
168
|
-
.filter((a: `0x${string}`) => !this.artists[artist].collections.includes(a))
|
|
115
|
+
const collections = await $queryClient.fetch($queries.artistCollections, artist)
|
|
116
|
+
const newCollections = collections.filter(c => !this.hasCollection(c.address))
|
|
117
|
+
await Promise.all(newCollections.map(c => this.addCollection(c)))
|
|
169
118
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} catch (e) {
|
|
175
|
-
console.error(e)
|
|
176
|
-
}
|
|
119
|
+
this.artists[artist].collections = Array.from(new Set([
|
|
120
|
+
...this.artists[artist].collections,
|
|
121
|
+
...collections.map(c => c.address),
|
|
122
|
+
]))
|
|
177
123
|
},
|
|
178
124
|
|
|
179
125
|
async fetchCollection (address: `0x${string}`): Promise<Collection> {
|
|
180
|
-
return dedupe(`collection:${address}`, () => this._fetchCollection(address))
|
|
181
|
-
},
|
|
182
|
-
|
|
183
|
-
async _fetchCollection (address: `0x${string}`): Promise<Collection> {
|
|
184
126
|
this.ensureStoreVersion()
|
|
185
127
|
|
|
186
|
-
|
|
187
|
-
return this.collection(address)
|
|
188
|
-
}
|
|
128
|
+
const fresh = await $queryClient.fetch($queries.collection, address)
|
|
189
129
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
address,
|
|
194
|
-
functionName: 'contractURI',
|
|
195
|
-
chainId,
|
|
196
|
-
}) as Promise<string>,
|
|
197
|
-
readContract($wagmi, {
|
|
198
|
-
abi: MINT_ABI,
|
|
199
|
-
address,
|
|
200
|
-
functionName: 'version',
|
|
201
|
-
chainId,
|
|
202
|
-
}) as Promise<bigint>,
|
|
203
|
-
readContract($wagmi, {
|
|
204
|
-
abi: MINT_ABI,
|
|
205
|
-
address,
|
|
206
|
-
functionName: 'initBlock',
|
|
207
|
-
chainId,
|
|
208
|
-
}) as Promise<bigint>,
|
|
209
|
-
readContract($wagmi, {
|
|
210
|
-
abi: MINT_ABI,
|
|
211
|
-
address,
|
|
212
|
-
functionName: 'latestTokenId',
|
|
213
|
-
chainId,
|
|
214
|
-
}) as Promise<bigint>,
|
|
215
|
-
readContract($wagmi, {
|
|
216
|
-
abi: MINT_ABI,
|
|
217
|
-
address,
|
|
218
|
-
functionName: 'owner',
|
|
219
|
-
chainId,
|
|
220
|
-
}) as Promise<`0x${string}`>,
|
|
221
|
-
getBalance($wagmi, {
|
|
222
|
-
address,
|
|
223
|
-
}) as Promise<GetBalanceReturnType>,
|
|
224
|
-
])
|
|
130
|
+
if (this.hasCollection(address)) {
|
|
131
|
+
const existing = this.collections[address]
|
|
132
|
+
const hadNewTokens = fresh.latestTokenId > existing.latestTokenId
|
|
225
133
|
|
|
226
|
-
|
|
227
|
-
|
|
134
|
+
// Update metadata without wiping tokens/renderers
|
|
135
|
+
existing.owner = fresh.owner
|
|
136
|
+
existing.name = fresh.name
|
|
137
|
+
existing.symbol = fresh.symbol
|
|
138
|
+
existing.description = fresh.description
|
|
139
|
+
existing.image = fresh.image
|
|
140
|
+
existing.latestTokenId = fresh.latestTokenId
|
|
141
|
+
existing.initBlock = fresh.initBlock
|
|
228
142
|
|
|
229
|
-
|
|
230
|
-
|
|
143
|
+
// Force token re-fetch when new tokens exist
|
|
144
|
+
if (hadNewTokens) {
|
|
145
|
+
await $queryClient.invalidate($queries.collectionTokens, address)
|
|
146
|
+
}
|
|
231
147
|
|
|
232
|
-
return
|
|
233
|
-
image: metadata.image,
|
|
234
|
-
name: metadata.name,
|
|
235
|
-
symbol: metadata.symbol,
|
|
236
|
-
version,
|
|
237
|
-
description: metadata.description,
|
|
238
|
-
address,
|
|
239
|
-
initBlock,
|
|
240
|
-
latestTokenId,
|
|
241
|
-
owner: artist,
|
|
242
|
-
tokens: {},
|
|
243
|
-
balance: balance.value,
|
|
244
|
-
renderers: [],
|
|
245
|
-
})
|
|
246
|
-
} catch (e) {
|
|
247
|
-
console.warn(`Error parsing collection`, e)
|
|
248
|
-
this.artists[artist].collections = this.artists[artist].collections.filter(c => c !== address)
|
|
148
|
+
return existing
|
|
249
149
|
}
|
|
150
|
+
|
|
151
|
+
return await this.addCollection(fresh)
|
|
250
152
|
},
|
|
251
153
|
|
|
252
154
|
async fetchCollectionBalance (address: `0x${string}`) {
|
|
@@ -271,11 +173,11 @@ export const useOnchainStore = () => {
|
|
|
271
173
|
const rendererArgs = { abi: RENDERER_ABI, address: rendererAddress, chainId }
|
|
272
174
|
|
|
273
175
|
const [name, version] = await Promise.all([
|
|
274
|
-
|
|
176
|
+
readContract($wagmi, {
|
|
275
177
|
...rendererArgs,
|
|
276
178
|
functionName: 'name',
|
|
277
179
|
}),
|
|
278
|
-
|
|
180
|
+
readContract($wagmi, {
|
|
279
181
|
...rendererArgs,
|
|
280
182
|
functionName: 'version',
|
|
281
183
|
}),
|
|
@@ -298,43 +200,25 @@ export const useOnchainStore = () => {
|
|
|
298
200
|
},
|
|
299
201
|
|
|
300
202
|
async fetchCollectionTokens (address: `0x${string}`): Promise<Token[]> {
|
|
301
|
-
|
|
302
|
-
abi: MINT_ABI,
|
|
303
|
-
address,
|
|
304
|
-
functionName: 'latestTokenId',
|
|
305
|
-
chainId,
|
|
306
|
-
}) as Promise<bigint>
|
|
307
|
-
|
|
308
|
-
const collection = this.collection(address)
|
|
309
|
-
|
|
310
|
-
const existingTokenIds = new Set(Object.keys(collection.tokens).map(id => BigInt(id)))
|
|
203
|
+
const tokens = await $queryClient.fetch($queries.collectionTokens, address)
|
|
311
204
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
// Collect missing token IDs
|
|
316
|
-
const missingIds: bigint[] = []
|
|
317
|
-
let id = collection.latestTokenId
|
|
318
|
-
while (id > 0n) {
|
|
319
|
-
if (!existingTokenIds.has(id)) {
|
|
320
|
-
missingIds.push(id)
|
|
205
|
+
for (const token of tokens) {
|
|
206
|
+
if (!this.collections[address].tokens[`${token.tokenId}`]) {
|
|
207
|
+
this.collections[address].tokens[`${token.tokenId}`] = token
|
|
321
208
|
}
|
|
322
|
-
id--
|
|
323
209
|
}
|
|
324
210
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
211
|
+
if (tokens.length > 0) {
|
|
212
|
+
const maxId = tokens.reduce((max, t) => t.tokenId > max ? t.tokenId : max, 0n)
|
|
213
|
+
if (maxId > this.collections[address].latestTokenId) {
|
|
214
|
+
this.collections[address].latestTokenId = maxId
|
|
215
|
+
}
|
|
328
216
|
}
|
|
329
217
|
|
|
330
218
|
return this.tokens(address)
|
|
331
219
|
},
|
|
332
220
|
|
|
333
221
|
async fetchToken (address: `0x${string}`, id: number | string | bigint, tries: number = 0): Promise<void> {
|
|
334
|
-
return dedupe(`token:${address}:${id}`, () => this._fetchToken(address, id, tries))
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
async _fetchToken (address: `0x${string}`, id: number | string | bigint, tries: number = 0): Promise<void> {
|
|
338
222
|
const client = getPublicClient($wagmi, { chainId }) as PublicClient
|
|
339
223
|
const mintContract = getContract({
|
|
340
224
|
address,
|
|
@@ -342,16 +226,13 @@ export const useOnchainStore = () => {
|
|
|
342
226
|
client,
|
|
343
227
|
})
|
|
344
228
|
|
|
345
|
-
if (this.collection(address)
|
|
346
|
-
console.info(`Token cached #${id}`)
|
|
229
|
+
if (this.collection(address)?.tokens[`${id}`]) {
|
|
347
230
|
return
|
|
348
231
|
}
|
|
349
232
|
|
|
350
|
-
// Normalize token ID
|
|
351
233
|
const tokenId = BigInt(id)
|
|
352
234
|
|
|
353
235
|
try {
|
|
354
|
-
console.info(`Fetching token #${tokenId}`)
|
|
355
236
|
const currentBlock = await client.getBlock()
|
|
356
237
|
|
|
357
238
|
const [data, dataUri] = await Promise.all([
|
|
@@ -369,12 +250,7 @@ export const useOnchainStore = () => {
|
|
|
369
250
|
const json = Buffer.from(dataUri.substring(29), `base64`).toString()
|
|
370
251
|
metadata = JSON.parse(json)
|
|
371
252
|
} catch (e) {
|
|
372
|
-
metadata = {
|
|
373
|
-
name: '',
|
|
374
|
-
description: '',
|
|
375
|
-
image: '',
|
|
376
|
-
animationUrl: '',
|
|
377
|
-
}
|
|
253
|
+
metadata = { name: '', description: '', image: '', animationUrl: '' }
|
|
378
254
|
console.warn(`Parsing data uri failed`, e)
|
|
379
255
|
}
|
|
380
256
|
|
|
@@ -385,10 +261,8 @@ export const useOnchainStore = () => {
|
|
|
385
261
|
description: metadata.description,
|
|
386
262
|
image: metadata.image,
|
|
387
263
|
animationUrl: metadata.animation_url,
|
|
388
|
-
|
|
389
|
-
mintedBlock: BigInt(`${mintedBlock}`), // Force bigint
|
|
264
|
+
mintedBlock: BigInt(`${mintedBlock}`),
|
|
390
265
|
closeAt,
|
|
391
|
-
|
|
392
266
|
mintsBackfilledUntilBlock: 0n,
|
|
393
267
|
mintsFetchedUntilBlock: 0n,
|
|
394
268
|
mints: []
|
|
@@ -396,17 +270,12 @@ export const useOnchainStore = () => {
|
|
|
396
270
|
|
|
397
271
|
this.collections[address].tokens[`${token.tokenId}`] = token
|
|
398
272
|
|
|
399
|
-
// Update latestTokenId if this token is newer than what's cached
|
|
400
273
|
if (tokenId > this.collections[address].latestTokenId) {
|
|
401
274
|
this.collections[address].latestTokenId = tokenId
|
|
402
275
|
}
|
|
403
276
|
} catch (e) {
|
|
404
|
-
// Retry 3 times
|
|
405
277
|
if (tries < 3) {
|
|
406
|
-
|
|
407
|
-
return await this._fetchToken(address, id, tries + 1)
|
|
408
|
-
} else {
|
|
409
|
-
// TODO: Handle impossible to load token
|
|
278
|
+
return await this.fetchToken(address, id, tries + 1)
|
|
410
279
|
}
|
|
411
280
|
}
|
|
412
281
|
},
|
|
@@ -439,91 +308,13 @@ export const useOnchainStore = () => {
|
|
|
439
308
|
|
|
440
309
|
async fetchTokenMints (token: Token) {
|
|
441
310
|
const storedToken = this.collections[token.collection].tokens[token.tokenId.toString()]
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
// We want to sync until now, or when the mint closed
|
|
447
|
-
const toBlock = currentBlock > untilBlock ? untilBlock : currentBlock
|
|
448
|
-
|
|
449
|
-
if (token.mintsFetchedUntilBlock >= toBlock) {
|
|
450
|
-
return console.info(`mints for #${token.tokenId} already fetched`)
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Initially, we want to sync backwards,
|
|
454
|
-
// but at most 5000 blocks (the general max range for an event query)
|
|
455
|
-
const maxRangeBlock = toBlock - MAX_BLOCK_RANGE
|
|
456
|
-
const fromBlock = token.mintsFetchedUntilBlock > maxRangeBlock // If we've already fetched
|
|
457
|
-
? token.mintsFetchedUntilBlock + 1n // we want to continue where we left off
|
|
458
|
-
: maxRangeBlock > token.mintedBlock // Otherwise we'll go back as far as possible
|
|
459
|
-
? maxRangeBlock // (to our max range)
|
|
460
|
-
: token.mintedBlock // (or all the way to when the token minted)
|
|
461
|
-
|
|
462
|
-
// Load mints in range
|
|
463
|
-
this.addTokenMints(token, await this.loadMintEvents(token, fromBlock, toBlock))
|
|
464
|
-
|
|
465
|
-
// Set sync status
|
|
466
|
-
storedToken.mintsFetchedUntilBlock = toBlock
|
|
467
|
-
|
|
468
|
-
// If this is our first fetch, mark until when we have backfilled
|
|
469
|
-
if (! token.mintsBackfilledUntilBlock) {
|
|
470
|
-
storedToken.mintsBackfilledUntilBlock = fromBlock
|
|
471
|
-
}
|
|
311
|
+
storedToken.mints = await $queryClient.fetch($queries.tokenMints, token.collection as `0x${string}`, token.tokenId)
|
|
312
|
+
storedToken.mintsFetchedUntilBlock = INDEXER_SYNCED
|
|
313
|
+
storedToken.mintsBackfilledUntilBlock = 0n
|
|
472
314
|
},
|
|
473
315
|
|
|
474
|
-
async backfillTokenMints (
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
// If we've backfilled all the way;
|
|
478
|
-
if (storedToken.mintsBackfilledUntilBlock <= token.mintedBlock) return
|
|
479
|
-
|
|
480
|
-
// We want to fetch the tokens up until where we stopped backfilling (excluding the last block)
|
|
481
|
-
const toBlock = storedToken.mintsBackfilledUntilBlock - 1n
|
|
482
|
-
|
|
483
|
-
// We want to fetch until our max range (5000), or until when the token minted
|
|
484
|
-
const fromBlock = toBlock - MAX_BLOCK_RANGE > token.mintedBlock ? toBlock - MAX_BLOCK_RANGE : token.mintedBlock
|
|
485
|
-
console.info(`Backfilling token mints blocks ${fromBlock}-${toBlock}`)
|
|
486
|
-
|
|
487
|
-
// Finally, we update our database
|
|
488
|
-
this.addTokenMints(token, await this.loadMintEvents(token, fromBlock, toBlock), 'append')
|
|
489
|
-
|
|
490
|
-
// And save until when we have backfilled our tokens.
|
|
491
|
-
storedToken.mintsBackfilledUntilBlock = fromBlock
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
async loadMintEvents (token: Token, fromBlock: bigint, toBlock: bigint): Promise<MintEvent[]> {
|
|
495
|
-
const client = getPublicClient($wagmi, { chainId }) as PublicClient
|
|
496
|
-
|
|
497
|
-
const logs = await client.getLogs({
|
|
498
|
-
address: token.collection,
|
|
499
|
-
event: parseAbiItem('event NewMint(uint256 indexed tokenId, uint256 unitPrice, uint256 amount, address minter)'),
|
|
500
|
-
args: {
|
|
501
|
-
tokenId: BigInt(token.tokenId),
|
|
502
|
-
},
|
|
503
|
-
fromBlock,
|
|
504
|
-
toBlock,
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
console.info(`Token mints fetched from ${fromBlock}-${toBlock}`)
|
|
508
|
-
|
|
509
|
-
return logs.map(l => ({
|
|
510
|
-
tokenId: token.tokenId,
|
|
511
|
-
address: l.args.minter,
|
|
512
|
-
block: l.blockNumber,
|
|
513
|
-
logIndex: l.logIndex,
|
|
514
|
-
tx: l.transactionHash,
|
|
515
|
-
unitPrice: l.args.unitPrice,
|
|
516
|
-
amount: l.args.amount,
|
|
517
|
-
price: ( l.args.amount || 0n ) * ( l.args.unitPrice || 0n ),
|
|
518
|
-
}) as MintEvent).reverse()
|
|
519
|
-
},
|
|
520
|
-
|
|
521
|
-
addTokenMints (token: Token, mints: MintEvent[], location: 'prepend'|'append' = 'prepend') {
|
|
522
|
-
const storedToken = this.collections[token.collection].tokens[token.tokenId.toString()]
|
|
523
|
-
|
|
524
|
-
storedToken.mints = location === 'prepend'
|
|
525
|
-
? [ ...mints, ...storedToken.mints ]
|
|
526
|
-
: [ ...storedToken.mints, ...mints ]
|
|
316
|
+
async backfillTokenMints (_token: Token) {
|
|
317
|
+
// Both indexer and RPC sources return all mints — nothing to backfill
|
|
527
318
|
},
|
|
528
319
|
|
|
529
320
|
async addCollection (collection: Collection) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { waitForTransactionReceipt } from '@wagmi/core'
|
|
2
|
+
import type { TransactionReceipt } from 'viem'
|
|
3
|
+
|
|
4
|
+
export interface TrackedTransaction {
|
|
5
|
+
hash: string
|
|
6
|
+
status: 'waiting' | 'complete' | 'error'
|
|
7
|
+
waitingText: string
|
|
8
|
+
completeText: string
|
|
9
|
+
txLink: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useTransactionStore = defineStore('transactions', () => {
|
|
13
|
+
const { $wagmi } = useNuxtApp()
|
|
14
|
+
const config = useRuntimeConfig()
|
|
15
|
+
|
|
16
|
+
const pending = ref<TrackedTransaction[]>([])
|
|
17
|
+
|
|
18
|
+
const track = (
|
|
19
|
+
hash: string,
|
|
20
|
+
waitingText: string,
|
|
21
|
+
completeText: string,
|
|
22
|
+
onComplete?: (receipt: TransactionReceipt) => void,
|
|
23
|
+
) => {
|
|
24
|
+
const entry: TrackedTransaction = reactive({
|
|
25
|
+
hash,
|
|
26
|
+
status: 'waiting',
|
|
27
|
+
waitingText,
|
|
28
|
+
completeText,
|
|
29
|
+
txLink: `${config.public.blockExplorer}/tx/${hash}`,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
pending.value.push(entry)
|
|
33
|
+
|
|
34
|
+
waitForTransactionReceipt($wagmi, { hash: hash as `0x${string}` })
|
|
35
|
+
.then((receipt) => {
|
|
36
|
+
entry.status = 'complete'
|
|
37
|
+
onComplete?.(receipt)
|
|
38
|
+
setTimeout(() => remove(hash), 4000)
|
|
39
|
+
})
|
|
40
|
+
.catch(() => {
|
|
41
|
+
entry.status = 'error'
|
|
42
|
+
setTimeout(() => remove(hash), 8000)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const remove = (hash: string) => {
|
|
47
|
+
pending.value = pending.value.filter((t) => t.hash !== hash)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { pending, track, remove }
|
|
51
|
+
})
|
package/nuxt.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visualizevalue/mint-app-base",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"main": "./nuxt.config.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"app",
|
|
9
|
+
"assets",
|
|
10
|
+
"components",
|
|
11
|
+
"composables",
|
|
12
|
+
"i18n.config.ts",
|
|
13
|
+
"index.d.ts",
|
|
14
|
+
"layouts",
|
|
15
|
+
"locales",
|
|
16
|
+
"middleware",
|
|
17
|
+
"pages",
|
|
18
|
+
"plugins",
|
|
19
|
+
"public",
|
|
20
|
+
"queries",
|
|
21
|
+
"server",
|
|
22
|
+
"utils",
|
|
23
|
+
"app.config.ts",
|
|
24
|
+
"app.vue",
|
|
25
|
+
"error.vue",
|
|
26
|
+
"nuxt.config.ts",
|
|
27
|
+
"tsconfig.json"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
6
32
|
"dependencies": {
|
|
33
|
+
"@1001-digital/dapp-query-core": "^1.1.0",
|
|
34
|
+
"@1001-digital/dapp-query-vue": "^1.1.0",
|
|
7
35
|
"@csstools/postcss-global-data": "^2.1.1",
|
|
8
36
|
"@nuxtjs/i18n": "^9.5.2",
|
|
9
37
|
"@pinia-plugin-persistedstate/nuxt": "^1.2.1",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createQueryClient } from '@1001-digital/dapp-query-core'
|
|
2
|
+
import { idbCache } from '@1001-digital/dapp-query-core'
|
|
3
|
+
import { dappQueryPlugin } from '@1001-digital/dapp-query-vue'
|
|
4
|
+
import { createQueries, type MintQueries } from '~/queries'
|
|
5
|
+
import type { QueryClient } from '@1001-digital/dapp-query-core'
|
|
6
|
+
|
|
7
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
8
|
+
const queryClient = createQueryClient({
|
|
9
|
+
cache: idbCache('mint-query'),
|
|
10
|
+
defaultStaleTime: 60_000,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const config = nuxtApp.$config.public
|
|
14
|
+
const endpoints = config.indexerEndpoints
|
|
15
|
+
? String(config.indexerEndpoints).split(/\s+/).filter(Boolean)
|
|
16
|
+
: []
|
|
17
|
+
|
|
18
|
+
const queries = createQueries({
|
|
19
|
+
wagmi: nuxtApp.$wagmi as any,
|
|
20
|
+
chainId: Number(config.chainId),
|
|
21
|
+
factory: config.factoryAddress as `0x${string}`,
|
|
22
|
+
endpoints,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
nuxtApp.vueApp.use(dappQueryPlugin, queryClient)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
provide: {
|
|
29
|
+
queryClient,
|
|
30
|
+
queries,
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
declare module '#app' {
|
|
36
|
+
interface NuxtApp {
|
|
37
|
+
$queryClient: QueryClient
|
|
38
|
+
$queries: MintQueries
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createVNode, render } from 'vue'
|
|
2
|
+
import TransactionToasts from '~/components/TransactionToasts.vue'
|
|
3
|
+
|
|
4
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
5
|
+
nuxtApp.hook('app:mounted', () => {
|
|
6
|
+
const container = document.createElement('div')
|
|
7
|
+
document.body.appendChild(container)
|
|
8
|
+
|
|
9
|
+
const vnode = createVNode(TransactionToasts)
|
|
10
|
+
vnode.appContext = nuxtApp.vueApp._context
|
|
11
|
+
render(vnode, container)
|
|
12
|
+
})
|
|
13
|
+
})
|