@visualizevalue/mint-app-base 0.1.127 → 0.2.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Visualize Value
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -16,7 +16,7 @@
16
16
  :block="currentBlock"
17
17
  >
18
18
  <template #after>
19
- <TokenMintTimelineItem v-if="backfillComplete">
19
+ <TokenMintTimelineItem>
20
20
  <Account :address="collection.owner" class="account" />
21
21
 
22
22
  <span class="amount">1<span>×</span></span>
@@ -34,17 +34,12 @@
34
34
  </TokenMintTimelineVirtualScroller>
35
35
  </template>
36
36
 
37
- <div v-if="! backfillComplete" v-show="! loading" ref="loadMore" class="load-more">
38
- <Button @click="backfill">{{ $t('token.load_more')}}</Button>
39
- </div>
40
-
41
37
  <Loading v-if="loading || ! currentBlock" :txt="$t('token.loading_mint_history')" />
42
38
  </slot>
43
39
  </section>
44
40
  </template>
45
41
 
46
42
  <script setup>
47
- import { useElementVisibility } from '@vueuse/core'
48
43
  import { useBlockNumber } from '@wagmi/vue'
49
44
 
50
45
  const config = useRuntimeConfig()
@@ -58,7 +53,6 @@ const { token, collection } = defineProps({
58
53
  const state = useOnchainStore()
59
54
 
60
55
  const mints = computed(() => state.tokenMints(token.collection, token.tokenId))
61
- const backfillComplete = computed(() => token.mintsBackfilledUntilBlock <= token.mintedBlock)
62
56
 
63
57
  const sortBy = ref('recent')
64
58
  const sortedMints = computed(() => {
@@ -72,46 +66,17 @@ const sortedMints = computed(() => {
72
66
  })
73
67
 
74
68
  const loading = ref(true)
75
- const loadMore = ref()
76
- const loadMoreVisible = useElementVisibility(loadMore)
77
- const backfill = async () => {
78
- loading.value = true
79
-
80
- try {
81
- await state.backfillTokenMints(token)
82
-
83
- // If we're not fully backfilled and we have less than 20 mints loaded,
84
- // continue backfilling events.
85
- while (! backfillComplete.value && mints.value?.length < 20) {
86
- await delay(250)
87
- await state.backfillTokenMints(token)
88
- }
89
- } catch (e) {
90
- console.error(`Issue during backfill`, e)
91
- }
92
-
93
- loading.value = false
94
- }
95
69
 
96
70
  onMounted(async () => {
97
71
  loading.value = true
98
72
  try {
99
- console.info(`Attempting to load + backfill token mints for #${token.tokenId}`)
100
73
  await state.fetchTokenMints(token)
101
- await backfill()
102
74
  } catch (e) {
103
75
  console.error(e)
104
76
  }
105
77
  loading.value = false
106
78
  })
107
79
 
108
- watch(loadMoreVisible, () => {
109
- // Skip if we have enough mints for the viewport or we're already loading
110
- if (! loadMoreVisible.value || loading.value) return
111
-
112
- backfill()
113
- })
114
-
115
80
  watch(currentBlock, () => {
116
81
  if (loading.value) return
117
82
 
@@ -171,11 +136,4 @@ watch(currentBlock, () => {
171
136
  }
172
137
  }
173
138
 
174
-
175
- .load-more {
176
- .button {
177
- display: block;
178
- width: 100%;
179
- }
180
- }
181
139
  </style>
@@ -1,23 +1,14 @@
1
1
  import { getBalance, getPublicClient, readContract } from '@wagmi/core'
2
- import { type GetBalanceReturnType } from '@wagmi/core'
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 = 9
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 this.fetchArtistProfile(address)
107
-
108
- await this.fetchCollections(address, factory)
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
- const client = getPublicClient($wagmi, { chainId: 1 }) as PublicClient
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 collectionAddresses: `0x${string}`[] = (await readContract($wagmi, {
162
- abi: FACTORY_ABI,
163
- address: factory,
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
- try {
171
- await Promise.all(collectionAddresses.map(address => this.fetchCollection(address)))
172
-
173
- this.artists[artist].collections = Array.from(new Set([...this.artists[artist].collections, ...collectionAddresses]))
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
- if (this.hasCollection(address) && this.collection(address).latestTokenId > 0n) {
187
- return this.collection(address)
188
- }
128
+ const fresh = await $queryClient.fetch($queries.collection, address)
189
129
 
190
- const [data, version, initBlock, latestTokenId, owner, balance] = await Promise.all([
191
- readContract($wagmi, {
192
- abi: MINT_ABI,
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
- const artist = owner.toLowerCase() as `0x${string}`
227
- const json = Buffer.from(data.substring(29), `base64`).toString()
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
- try {
230
- const metadata = JSON.parse(json)
143
+ // Force token re-fetch when new tokens exist
144
+ if (hadNewTokens) {
145
+ await $queryClient.invalidate($queries.collectionTokens, address)
146
+ }
231
147
 
232
- return await this.addCollection({
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
- await readContract($wagmi, {
176
+ readContract($wagmi, {
275
177
  ...rendererArgs,
276
178
  functionName: 'name',
277
179
  }),
278
- await readContract($wagmi, {
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
- this.collections[address].latestTokenId = await readContract($wagmi, {
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
- // If we have all tokens we don't need to do anything
313
- if (BigInt(existingTokenIds.size) === collection.latestTokenId) return this.tokens(address)
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
- // Fetch in parallel chunks of 5
326
- for (const chunk of chunkArray(missingIds, 5)) {
327
- await Promise.all(chunk.map(tokenId => this.fetchToken(address, tokenId)))
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).tokens[`${id}`]) {
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
- console.info(`Retrying fetching data ${tries + 1}`)
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
- const client = getPublicClient($wagmi, { chainId }) as PublicClient
443
- const currentBlock = await client.getBlockNumber()
444
- const untilBlock = token.mintedBlock + BLOCKS_PER_DAY
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 (token: Token) {
475
- const storedToken = this.collections[token.collection].tokens[token.tokenId.toString()]
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) {
package/nuxt.config.ts CHANGED
@@ -27,6 +27,7 @@ export default defineNuxtConfig({
27
27
  mintAmount: 1,
28
28
  mintValue: 0,
29
29
  title: 'Mint',
30
+ indexerEndpoints: '',
30
31
  walletConnectProjectId: '',
31
32
  },
32
33
  },
package/package.json CHANGED
@@ -1,9 +1,37 @@
1
1
  {
2
2
  "name": "@visualizevalue/mint-app-base",
3
- "version": "0.1.127",
3
+ "version": "0.2.1",
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,144 @@
1
+ import { graphqlSource, customSource } from '@1001-digital/dapp-query-core'
2
+ import type { QueryDefinition } from '@1001-digital/dapp-query-core'
3
+ import type { Config } from '@wagmi/core'
4
+ import type { Collection, Token, MintEvent } from '~/utils/types'
5
+ import {
6
+ COLLECTIONS_BY_ARTIST, COLLECTION_BY_ADDRESS,
7
+ ARTIFACTS_BY_COLLECTION, MINTS_BY_ARTIFACT,
8
+ transformCollections, transformCollection,
9
+ transformArtifacts, transformMints, transformProfile,
10
+ rpcFetchProfile, rpcFetchCollections,
11
+ rpcFetchCollection, rpcFetchCollectionTokens,
12
+ rpcFetchTokenMints,
13
+ } from './sources'
14
+
15
+ export interface MintQueries {
16
+ artistProfile: QueryDefinition<Partial<Artist>, [`0x${string}`]>
17
+ artistCollections: QueryDefinition<Collection[], [`0x${string}`]>
18
+ collection: QueryDefinition<Collection, [`0x${string}`]>
19
+ collectionTokens: QueryDefinition<Token[], [`0x${string}`]>
20
+ tokenMints: QueryDefinition<MintEvent[], [`0x${string}`, bigint]>
21
+ }
22
+
23
+ interface CreateQueriesConfig {
24
+ wagmi: Config
25
+ chainId: number
26
+ factory: `0x${string}`
27
+ endpoints: string[]
28
+ }
29
+
30
+ export function createQueries (config: CreateQueriesConfig): MintQueries {
31
+ const { wagmi, chainId, factory, endpoints } = config
32
+ const hasIndexer = endpoints.length > 0
33
+
34
+ const indexerProfileSource = hasIndexer
35
+ ? customSource<Partial<Artist>>({
36
+ id: 'profile-indexer',
37
+ fetch: async (address: unknown) => {
38
+ // Multi-endpoint REST failover
39
+ let lastError: Error | undefined
40
+ for (const endpoint of endpoints) {
41
+ try {
42
+ const base = endpoint.replace(/\/$/, '')
43
+ const res = await fetch(`${base}/profiles/${address}`)
44
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
45
+ return transformProfile(await res.json())
46
+ } catch (e) {
47
+ lastError = e instanceof Error ? e : new Error(String(e))
48
+ }
49
+ }
50
+ throw lastError ?? new Error('All indexer endpoints failed')
51
+ },
52
+ })
53
+ : null
54
+
55
+ return {
56
+ artistProfile: {
57
+ key: (address) => `profile:${address}`,
58
+ staleTime: 30 * 60 * 1000, // 30 min
59
+ sources: [
60
+ ...(indexerProfileSource ? [indexerProfileSource] : []),
61
+ customSource<Partial<Artist>>({
62
+ id: 'profile-rpc',
63
+ fetch: (address: unknown) => rpcFetchProfile(wagmi, address as `0x${string}`),
64
+ }),
65
+ ],
66
+ },
67
+
68
+ artistCollections: {
69
+ key: (artist) => `collections:${artist}`,
70
+ staleTime: 5 * 60 * 1000,
71
+ sources: [
72
+ ...(hasIndexer ? [graphqlSource<Collection[]>({
73
+ endpoints,
74
+ query: COLLECTIONS_BY_ARTIST,
75
+ variables: (artist: unknown) => ({ artist: (artist as string).toLowerCase() }),
76
+ transform: transformCollections,
77
+ })] : []),
78
+ customSource<Collection[]>({
79
+ id: 'collections-rpc',
80
+ fetch: (artist: unknown) => rpcFetchCollections(wagmi, chainId, artist as `0x${string}`, factory),
81
+ }),
82
+ ],
83
+ },
84
+
85
+ collection: {
86
+ key: (address) => `collection:${address}`,
87
+ staleTime: 5 * 60 * 1000,
88
+ sources: [
89
+ ...(hasIndexer ? [graphqlSource<Collection>({
90
+ endpoints,
91
+ query: COLLECTION_BY_ADDRESS,
92
+ variables: (address: unknown) => ({ address: (address as string).toLowerCase() }),
93
+ transform: transformCollection,
94
+ })] : []),
95
+ customSource<Collection>({
96
+ id: 'collection-rpc',
97
+ fetch: async (address: unknown) => {
98
+ const collection = await rpcFetchCollection(wagmi, chainId, address as `0x${string}`)
99
+ if (!collection) throw new Error('Collection not found via RPC')
100
+ return collection
101
+ },
102
+ }),
103
+ ],
104
+ },
105
+
106
+ collectionTokens: {
107
+ key: (collection) => `tokens:${collection}`,
108
+ staleTime: 5 * 60 * 1000,
109
+ sources: [
110
+ ...(hasIndexer ? [graphqlSource<Token[]>({
111
+ endpoints,
112
+ query: ARTIFACTS_BY_COLLECTION,
113
+ variables: (collection: unknown) => ({ collection: (collection as string).toLowerCase() }),
114
+ transform: transformArtifacts,
115
+ })] : []),
116
+ customSource<Token[]>({
117
+ id: 'tokens-rpc',
118
+ fetch: (collection: unknown) => rpcFetchCollectionTokens(wagmi, chainId, collection as `0x${string}`),
119
+ }),
120
+ ],
121
+ },
122
+
123
+ tokenMints: {
124
+ key: (collection, tokenId) => `mints:${collection}:${tokenId}`,
125
+ staleTime: 60 * 1000, // 1 min — mints change frequently during live mint
126
+ sources: [
127
+ ...(hasIndexer ? [graphqlSource<MintEvent[]>({
128
+ endpoints,
129
+ query: MINTS_BY_ARTIFACT,
130
+ variables: (collection: unknown, tokenId: unknown) => ({
131
+ collection: (collection as string).toLowerCase(),
132
+ artifact: String(tokenId),
133
+ }),
134
+ transform: transformMints,
135
+ })] : []),
136
+ customSource<MintEvent[]>({
137
+ id: 'mints-rpc',
138
+ fetch: (collection: unknown, tokenId: unknown) =>
139
+ rpcFetchTokenMints(wagmi, chainId, collection as `0x${string}`, BigInt(tokenId as string | bigint)),
140
+ }),
141
+ ],
142
+ },
143
+ }
144
+ }
@@ -0,0 +1,419 @@
1
+ import { getBalance, getPublicClient, readContract } from '@wagmi/core'
2
+ import { type GetBalanceReturnType } from '@wagmi/core'
3
+ import { parseAbiItem, type PublicClient } from 'viem'
4
+ import type { Config } from '@wagmi/core'
5
+ import type { Collection, Token, MintEvent } from '~/utils/types'
6
+ import { BLOCKS_PER_DAY } from '@visualizevalue/mint-utils/time'
7
+
8
+ // Sentinel: marks data as fully synced from the indexer.
9
+ export const INDEXER_SYNCED = BigInt(Number.MAX_SAFE_INTEGER)
10
+
11
+ // Ponder response types (bigints come as strings from GraphQL)
12
+
13
+ interface PonderCollection {
14
+ address: string
15
+ artist: { address: string }
16
+ owner: string
17
+ image: string
18
+ name: string
19
+ symbol: string
20
+ description: string
21
+ init_block: string
22
+ latest_token_id: string
23
+ }
24
+
25
+ interface PonderArtifact {
26
+ collection: { address: string }
27
+ id: string
28
+ name: string
29
+ description: string
30
+ image: string
31
+ animation_url: string
32
+ created_block: string
33
+ }
34
+
35
+ interface PonderMint {
36
+ artifact: { id: string }
37
+ hash: string
38
+ block_number: string
39
+ log_index: number
40
+ amount: string
41
+ unit_price: string
42
+ price: string
43
+ account: string
44
+ }
45
+
46
+ interface PonderProfile {
47
+ ens: string | null
48
+ data: {
49
+ avatar: string
50
+ description: string
51
+ links: {
52
+ url: string
53
+ email: string
54
+ twitter: string
55
+ github: string
56
+ }
57
+ }
58
+ }
59
+
60
+
61
+ export const COLLECTIONS_BY_ARTIST = `
62
+ query($artist: String!) {
63
+ collections(
64
+ where: { artist: $artist }
65
+ orderBy: "init_block"
66
+ orderDirection: "desc"
67
+ limit: 1000
68
+ ) {
69
+ items {
70
+ address artist { address } owner image name symbol description
71
+ init_block latest_token_id
72
+ }
73
+ }
74
+ }
75
+ `
76
+
77
+ export const COLLECTION_BY_ADDRESS = `
78
+ query($address: String!) {
79
+ collection(address: $address) {
80
+ address artist { address } owner image name symbol description
81
+ init_block latest_token_id
82
+ }
83
+ }
84
+ `
85
+
86
+ export const ARTIFACTS_BY_COLLECTION = `
87
+ query($collection: String!) {
88
+ artifacts(
89
+ where: { collection: $collection }
90
+ orderBy: "id"
91
+ orderDirection: "desc"
92
+ limit: 1000
93
+ ) {
94
+ items {
95
+ collection { address } id name description image animation_url created_block
96
+ }
97
+ }
98
+ }
99
+ `
100
+
101
+ export const MINTS_BY_ARTIFACT = `
102
+ query($collection: String!, $artifact: BigInt!) {
103
+ mints(
104
+ where: { collection: $collection, artifact: $artifact }
105
+ orderBy: "block_number"
106
+ orderDirection: "desc"
107
+ limit: 1000
108
+ ) {
109
+ items {
110
+ artifact { id } hash block_number log_index
111
+ amount unit_price price account
112
+ }
113
+ }
114
+ }
115
+ `
116
+
117
+
118
+ export function transformCollections (data: { collections: { items: PonderCollection[] } }): Collection[] {
119
+ return data.collections.items.map(toCollection)
120
+ }
121
+
122
+ export function transformCollection (data: { collection: PonderCollection | null }): Collection {
123
+ if (!data.collection) throw new Error('Collection not found in indexer')
124
+ return toCollection(data.collection)
125
+ }
126
+
127
+ export function transformArtifacts (data: { artifacts: { items: PonderArtifact[] } }): Token[] {
128
+ return data.artifacts.items.map(toToken)
129
+ }
130
+
131
+ export function transformMints (data: { mints: { items: PonderMint[] } }): MintEvent[] {
132
+ return data.mints.items.map(toMintEvent)
133
+ }
134
+
135
+
136
+ export function transformProfile (raw: PonderProfile): Partial<Artist> {
137
+ return {
138
+ ens: raw.ens || '',
139
+ avatar: raw.data?.avatar || '',
140
+ description: raw.data?.description || '',
141
+ url: raw.data?.links?.url || '',
142
+ email: raw.data?.links?.email || '',
143
+ twitter: raw.data?.links?.twitter || '',
144
+ github: raw.data?.links?.github || '',
145
+ profileUpdatedAtBlock: INDEXER_SYNCED,
146
+ }
147
+ }
148
+
149
+
150
+ export async function rpcFetchProfile (
151
+ wagmi: Config,
152
+ address: `0x${string}`,
153
+ ): Promise<Partial<Artist>> {
154
+ const client = getPublicClient(wagmi, { chainId: 1 })
155
+ if (!client) return {}
156
+
157
+ const block = await client.getBlockNumber()
158
+
159
+ let ens, avatar, description, url, email, twitter, github
160
+
161
+ try {
162
+ ens = await client.getEnsName({ address })
163
+ if (ens) {
164
+ [avatar, description, url, email, twitter, github] = await Promise.all([
165
+ client.getEnsAvatar({ name: ens }),
166
+ client.getEnsText({ name: ens, key: 'description' }),
167
+ client.getEnsText({ name: ens, key: 'url' }),
168
+ client.getEnsText({ name: ens, key: 'email' }),
169
+ client.getEnsText({ name: ens, key: 'com.twitter' }),
170
+ client.getEnsText({ name: ens, key: 'com.github' }),
171
+ ])
172
+ }
173
+ } catch (e) { }
174
+
175
+ return { ens, avatar, description, url, email, twitter, github, profileUpdatedAtBlock: block }
176
+ }
177
+
178
+ export async function rpcFetchCollections (
179
+ wagmi: Config,
180
+ chainId: number,
181
+ artist: `0x${string}`,
182
+ factory: `0x${string}`,
183
+ ): Promise<Collection[]> {
184
+ const addresses: `0x${string}`[] = (await readContract(wagmi, {
185
+ abi: FACTORY_ABI,
186
+ address: factory,
187
+ functionName: 'getCreatorCollections',
188
+ args: [artist],
189
+ chainId,
190
+ })).map((a: `0x${string}`) => a.toLowerCase() as `0x${string}`)
191
+
192
+ const collections = await Promise.all(addresses.map(a => rpcFetchCollection(wagmi, chainId, a)))
193
+ return collections.filter((c): c is Collection => c !== null)
194
+ }
195
+
196
+ export async function rpcFetchCollection (
197
+ wagmi: Config,
198
+ chainId: number,
199
+ address: `0x${string}`,
200
+ ): Promise<Collection | null> {
201
+ try {
202
+ const [data, version, initBlock, latestTokenId, owner, balance] = await Promise.all([
203
+ readContract(wagmi, { abi: MINT_ABI, address, functionName: 'contractURI', chainId }) as Promise<string>,
204
+ readContract(wagmi, { abi: MINT_ABI, address, functionName: 'version', chainId }) as Promise<bigint>,
205
+ readContract(wagmi, { abi: MINT_ABI, address, functionName: 'initBlock', chainId }) as Promise<bigint>,
206
+ readContract(wagmi, { abi: MINT_ABI, address, functionName: 'latestTokenId', chainId }) as Promise<bigint>,
207
+ readContract(wagmi, { abi: MINT_ABI, address, functionName: 'owner', chainId }) as Promise<`0x${string}`>,
208
+ getBalance(wagmi, { address }) as Promise<GetBalanceReturnType>,
209
+ ])
210
+
211
+ const artistAddr = owner.toLowerCase() as `0x${string}`
212
+ const json = Buffer.from(data.substring(29), 'base64').toString()
213
+ const metadata = JSON.parse(json)
214
+
215
+ return {
216
+ image: metadata.image,
217
+ name: metadata.name,
218
+ symbol: metadata.symbol,
219
+ version,
220
+ description: metadata.description,
221
+ address,
222
+ initBlock,
223
+ latestTokenId,
224
+ owner: artistAddr,
225
+ tokens: {},
226
+ balance: balance.value,
227
+ renderers: [],
228
+ }
229
+ } catch (e) {
230
+ console.warn(`RPC: Error fetching collection ${address}`, e)
231
+ return null
232
+ }
233
+ }
234
+
235
+ export async function rpcFetchCollectionTokens (
236
+ wagmi: Config,
237
+ chainId: number,
238
+ collection: `0x${string}`,
239
+ ): Promise<Token[]> {
240
+ const client = getPublicClient(wagmi, { chainId }) as PublicClient
241
+ const [latestTokenId, currentBlock] = await Promise.all([
242
+ readContract(wagmi, {
243
+ abi: MINT_ABI,
244
+ address: collection,
245
+ functionName: 'latestTokenId',
246
+ chainId,
247
+ }) as Promise<bigint>,
248
+ client.getBlock(),
249
+ ])
250
+
251
+ const mintContract = getContract({ address: collection, abi: MINT_ABI, client })
252
+ const tokens: Token[] = []
253
+ const ids: bigint[] = []
254
+ for (let id = latestTokenId; id > 0n; id--) ids.push(id)
255
+
256
+ for (const chunk of chunkArray(ids, 10)) {
257
+ const results = await Promise.allSettled(
258
+ chunk.map(id => rpcFetchSingleToken(mintContract, collection, id, currentBlock))
259
+ )
260
+ for (const r of results) {
261
+ if (r.status === 'fulfilled' && r.value) tokens.push(r.value)
262
+ }
263
+ }
264
+
265
+ return tokens
266
+ }
267
+
268
+ async function rpcFetchSingleToken (
269
+ mintContract: ReturnType<typeof getContract>,
270
+ collection: `0x${string}`,
271
+ tokenId: bigint,
272
+ currentBlock: Awaited<ReturnType<PublicClient['getBlock']>>,
273
+ tries: number = 0,
274
+ ): Promise<Token | null> {
275
+ try {
276
+ const [data, dataUri] = await Promise.all([
277
+ mintContract.read.get([tokenId]) as Promise<[string, string, `0x${string}`[], bigint, bigint, bigint, bigint]>,
278
+ mintContract.read.uri([tokenId], {
279
+ gas: 100_000_000_000,
280
+ gasPrice: currentBlock.baseFeePerGas,
281
+ }) as Promise<string>,
282
+ ])
283
+
284
+ const [_name, _description, _artifact, _renderer, mintedBlock, closeAt, _extraData] = data
285
+
286
+ let metadata
287
+ try {
288
+ const json = Buffer.from(dataUri.substring(29), 'base64').toString()
289
+ metadata = JSON.parse(json)
290
+ } catch (e) {
291
+ metadata = { name: '', description: '', image: '', animation_url: '' }
292
+ }
293
+
294
+ return {
295
+ tokenId,
296
+ collection,
297
+ name: metadata.name,
298
+ description: metadata.description,
299
+ image: metadata.image,
300
+ animationUrl: metadata.animation_url,
301
+ mintedBlock: BigInt(`${mintedBlock}`),
302
+ closeAt,
303
+ mintsBackfilledUntilBlock: 0n,
304
+ mintsFetchedUntilBlock: 0n,
305
+ mints: [],
306
+ }
307
+ } catch (e) {
308
+ if (tries < 3) return rpcFetchSingleToken(mintContract, collection, tokenId, currentBlock, tries + 1)
309
+ return null
310
+ }
311
+ }
312
+
313
+ export async function rpcFetchTokenMints (
314
+ wagmi: Config,
315
+ chainId: number,
316
+ collection: `0x${string}`,
317
+ tokenId: bigint,
318
+ ): Promise<MintEvent[]> {
319
+ const client = getPublicClient(wagmi, { chainId }) as PublicClient
320
+ const mintContract = getContract({ address: collection, abi: MINT_ABI, client })
321
+
322
+ const [tokenData, currentBlock] = await Promise.all([
323
+ mintContract.read.get([tokenId]) as Promise<[string, string, `0x${string}`[], bigint, bigint, bigint, bigint]>,
324
+ client.getBlockNumber(),
325
+ ])
326
+
327
+ const mintedBlock = BigInt(`${tokenData[4]}`)
328
+ const untilBlock = mintedBlock + BLOCKS_PER_DAY
329
+ const toBlock = currentBlock > untilBlock ? untilBlock : currentBlock
330
+
331
+ return loadMintEventsRange(client, collection, tokenId, mintedBlock, toBlock)
332
+ }
333
+
334
+ async function loadMintEventsRange (
335
+ client: PublicClient,
336
+ collection: `0x${string}`,
337
+ tokenId: bigint,
338
+ fromBlock: bigint,
339
+ toBlock: bigint,
340
+ ): Promise<MintEvent[]> {
341
+ try {
342
+ const logs = await client.getLogs({
343
+ address: collection,
344
+ event: parseAbiItem('event NewMint(uint256 indexed tokenId, uint256 unitPrice, uint256 amount, address minter)'),
345
+ args: { tokenId },
346
+ fromBlock,
347
+ toBlock,
348
+ })
349
+
350
+ return logs.map(l => ({
351
+ tokenId,
352
+ address: l.args.minter,
353
+ block: l.blockNumber,
354
+ logIndex: l.logIndex,
355
+ tx: l.transactionHash,
356
+ unitPrice: l.args.unitPrice,
357
+ amount: l.args.amount,
358
+ price: (l.args.amount || 0n) * (l.args.unitPrice || 0n),
359
+ }) as MintEvent).reverse()
360
+ } catch (e) {
361
+ if (toBlock - fromBlock > 100n) {
362
+ const mid = fromBlock + (toBlock - fromBlock) / 2n
363
+ const [a, b] = await Promise.all([
364
+ loadMintEventsRange(client, collection, tokenId, fromBlock, mid),
365
+ loadMintEventsRange(client, collection, tokenId, mid + 1n, toBlock),
366
+ ])
367
+ return [...a, ...b]
368
+ }
369
+ throw e
370
+ }
371
+ }
372
+
373
+
374
+ function toCollection (raw: PonderCollection): Collection {
375
+ return {
376
+ address: raw.address.toLowerCase() as `0x${string}`,
377
+ owner: (raw.owner || raw.artist.address).toLowerCase() as `0x${string}`,
378
+ version: 0n,
379
+ image: raw.image || '',
380
+ name: raw.name || '',
381
+ symbol: raw.symbol || '',
382
+ description: raw.description || '',
383
+ initBlock: BigInt(raw.init_block || '0'),
384
+ latestTokenId: BigInt(raw.latest_token_id || '0'),
385
+ balance: 0n,
386
+ tokens: {},
387
+ renderers: [],
388
+ }
389
+ }
390
+
391
+ function toToken (raw: PonderArtifact): Token {
392
+ const mintedBlock = BigInt(raw.created_block || '0')
393
+ return {
394
+ collection: raw.collection.address.toLowerCase() as `0x${string}`,
395
+ tokenId: BigInt(raw.id),
396
+ name: raw.name || '',
397
+ description: raw.description || '',
398
+ image: raw.image || '',
399
+ animationUrl: raw.animation_url || undefined,
400
+ closeAt: mintedBlock + BLOCKS_PER_DAY,
401
+ mintedBlock,
402
+ mintsFetchedUntilBlock: 0n,
403
+ mintsBackfilledUntilBlock: 0n,
404
+ mints: [],
405
+ }
406
+ }
407
+
408
+ function toMintEvent (raw: PonderMint): MintEvent {
409
+ return {
410
+ tokenId: BigInt(raw.artifact.id),
411
+ address: raw.account.toLowerCase() as `0x${string}`,
412
+ block: BigInt(raw.block_number),
413
+ logIndex: raw.log_index,
414
+ tx: raw.hash,
415
+ unitPrice: BigInt(raw.unit_price || '0'),
416
+ amount: BigInt(raw.amount || '0'),
417
+ price: BigInt(raw.price || '0'),
418
+ }
419
+ }
package/.env.example DELETED
@@ -1,50 +0,0 @@
1
- # =========================
2
- # BASE
3
- # =========================
4
- NITRO_PRESET=node_cluster
5
- NUXT_SSR=false
6
- NUXT_DEVTOOLS=true
7
- NUXT_PUBLIC_DOMAIN=localhost
8
- NUXT_PUBLIC_TITLE=Mint
9
- NUXT_PUBLIC_DESCRIPTION=To mint is a human right.
10
-
11
- # =========================
12
- # MINT DEFAULTS
13
- # =========================
14
- # NUXT_PUBLIC_MINT_AMOUNT=1
15
- # NUXT_PUBLIC_MINT_VALUE=5
16
-
17
- # =========================
18
- # ARTIST SCOPE
19
- # =========================
20
- NUXT_PUBLIC_CREATOR_ADDRESS=0xc8f8e2f59dd95ff67c3d39109eca2e2a017d4c8a
21
-
22
- # =========================
23
- # SERVICES
24
- # =========================
25
- NUXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
26
- NUXT_PUBLIC_IPFS_GATEWAY=
27
- NUXT_PUBLIC_ARWEAVE_GATEWAY=
28
-
29
- # =========================
30
- # MAINNET
31
- # =========================
32
- # NUXT_PUBLIC_BLOCK_EXPLORER=https://etherscan.io
33
- # NUXT_PUBLIC_FACTORY_ADDRESS=0xd717Fe677072807057B03705227EC3E3b467b670
34
- # NUXT_PUBLIC_CHAIN_ID=1
35
- # NUXT_PUBLIC_RPC1=https://eth.llamarpc.com
36
- # NUXT_PUBLIC_RPC2=https://eth.drpc.org
37
- # NUXT_PUBLIC_RPC3=https://1rpc.io/eth
38
-
39
- # This should always be present if on other networks, so we can fetch ENS data
40
- NUXT_PUBLIC_MAINNET_RPC1=https://eth.llamarpc.com
41
-
42
- # =========================
43
- # SEPOLIA
44
- # =========================
45
- NUXT_PUBLIC_BLOCK_EXPLORER=https://sepolia.etherscan.io
46
- NUXT_PUBLIC_FACTORY_ADDRESS=0x750C5a6CFD40C9CaA48C31D87AC2a26101Acd517
47
- NUXT_PUBLIC_CHAIN_ID=11155111
48
- NUXT_PUBLIC_RPC1=https://ethereum-sepolia-rpc.publicnode.com
49
- NUXT_PUBLIC_RPC2=https://sepolia.drpc.org
50
- NUXT_PUBLIC_RPC3=https://1rpc.io/sepolia
package/.nuxtrc DELETED
@@ -1 +0,0 @@
1
- typescript.includeWorkspace = true