@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
package/queries/index.ts
ADDED
|
@@ -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,417 @@
|
|
|
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 }) as PublicClient
|
|
155
|
+
const block = await client.getBlockNumber()
|
|
156
|
+
|
|
157
|
+
let ens, avatar, description, url, email, twitter, github
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
ens = await client.getEnsName({ address })
|
|
161
|
+
if (ens) {
|
|
162
|
+
[avatar, description, url, email, twitter, github] = await Promise.all([
|
|
163
|
+
client.getEnsAvatar({ name: ens }),
|
|
164
|
+
client.getEnsText({ name: ens, key: 'description' }),
|
|
165
|
+
client.getEnsText({ name: ens, key: 'url' }),
|
|
166
|
+
client.getEnsText({ name: ens, key: 'email' }),
|
|
167
|
+
client.getEnsText({ name: ens, key: 'com.twitter' }),
|
|
168
|
+
client.getEnsText({ name: ens, key: 'com.github' }),
|
|
169
|
+
])
|
|
170
|
+
}
|
|
171
|
+
} catch (e) { }
|
|
172
|
+
|
|
173
|
+
return { ens, avatar, description, url, email, twitter, github, profileUpdatedAtBlock: block }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function rpcFetchCollections (
|
|
177
|
+
wagmi: Config,
|
|
178
|
+
chainId: number,
|
|
179
|
+
artist: `0x${string}`,
|
|
180
|
+
factory: `0x${string}`,
|
|
181
|
+
): Promise<Collection[]> {
|
|
182
|
+
const addresses: `0x${string}`[] = (await readContract(wagmi, {
|
|
183
|
+
abi: FACTORY_ABI,
|
|
184
|
+
address: factory,
|
|
185
|
+
functionName: 'getCreatorCollections',
|
|
186
|
+
args: [artist],
|
|
187
|
+
chainId,
|
|
188
|
+
})).map((a: `0x${string}`) => a.toLowerCase() as `0x${string}`)
|
|
189
|
+
|
|
190
|
+
const collections = await Promise.all(addresses.map(a => rpcFetchCollection(wagmi, chainId, a)))
|
|
191
|
+
return collections.filter((c): c is Collection => c !== null)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function rpcFetchCollection (
|
|
195
|
+
wagmi: Config,
|
|
196
|
+
chainId: number,
|
|
197
|
+
address: `0x${string}`,
|
|
198
|
+
): Promise<Collection | null> {
|
|
199
|
+
try {
|
|
200
|
+
const [data, version, initBlock, latestTokenId, owner, balance] = await Promise.all([
|
|
201
|
+
readContract(wagmi, { abi: MINT_ABI, address, functionName: 'contractURI', chainId }) as Promise<string>,
|
|
202
|
+
readContract(wagmi, { abi: MINT_ABI, address, functionName: 'version', chainId }) as Promise<bigint>,
|
|
203
|
+
readContract(wagmi, { abi: MINT_ABI, address, functionName: 'initBlock', chainId }) as Promise<bigint>,
|
|
204
|
+
readContract(wagmi, { abi: MINT_ABI, address, functionName: 'latestTokenId', chainId }) as Promise<bigint>,
|
|
205
|
+
readContract(wagmi, { abi: MINT_ABI, address, functionName: 'owner', chainId }) as Promise<`0x${string}`>,
|
|
206
|
+
getBalance(wagmi, { address }) as Promise<GetBalanceReturnType>,
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
const artistAddr = owner.toLowerCase() as `0x${string}`
|
|
210
|
+
const json = Buffer.from(data.substring(29), 'base64').toString()
|
|
211
|
+
const metadata = JSON.parse(json)
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
image: metadata.image,
|
|
215
|
+
name: metadata.name,
|
|
216
|
+
symbol: metadata.symbol,
|
|
217
|
+
version,
|
|
218
|
+
description: metadata.description,
|
|
219
|
+
address,
|
|
220
|
+
initBlock,
|
|
221
|
+
latestTokenId,
|
|
222
|
+
owner: artistAddr,
|
|
223
|
+
tokens: {},
|
|
224
|
+
balance: balance.value,
|
|
225
|
+
renderers: [],
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
console.warn(`RPC: Error fetching collection ${address}`, e)
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function rpcFetchCollectionTokens (
|
|
234
|
+
wagmi: Config,
|
|
235
|
+
chainId: number,
|
|
236
|
+
collection: `0x${string}`,
|
|
237
|
+
): Promise<Token[]> {
|
|
238
|
+
const client = getPublicClient(wagmi, { chainId }) as PublicClient
|
|
239
|
+
const [latestTokenId, currentBlock] = await Promise.all([
|
|
240
|
+
readContract(wagmi, {
|
|
241
|
+
abi: MINT_ABI,
|
|
242
|
+
address: collection,
|
|
243
|
+
functionName: 'latestTokenId',
|
|
244
|
+
chainId,
|
|
245
|
+
}) as Promise<bigint>,
|
|
246
|
+
client.getBlock(),
|
|
247
|
+
])
|
|
248
|
+
|
|
249
|
+
const mintContract = getContract({ address: collection, abi: MINT_ABI, client })
|
|
250
|
+
const tokens: Token[] = []
|
|
251
|
+
const ids: bigint[] = []
|
|
252
|
+
for (let id = latestTokenId; id > 0n; id--) ids.push(id)
|
|
253
|
+
|
|
254
|
+
for (const chunk of chunkArray(ids, 10)) {
|
|
255
|
+
const results = await Promise.allSettled(
|
|
256
|
+
chunk.map(id => rpcFetchSingleToken(mintContract, collection, id, currentBlock))
|
|
257
|
+
)
|
|
258
|
+
for (const r of results) {
|
|
259
|
+
if (r.status === 'fulfilled' && r.value) tokens.push(r.value)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return tokens
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function rpcFetchSingleToken (
|
|
267
|
+
mintContract: ReturnType<typeof getContract>,
|
|
268
|
+
collection: `0x${string}`,
|
|
269
|
+
tokenId: bigint,
|
|
270
|
+
currentBlock: Awaited<ReturnType<PublicClient['getBlock']>>,
|
|
271
|
+
tries: number = 0,
|
|
272
|
+
): Promise<Token | null> {
|
|
273
|
+
try {
|
|
274
|
+
const [data, dataUri] = await Promise.all([
|
|
275
|
+
mintContract.read.get([tokenId]) as Promise<[string, string, `0x${string}`[], bigint, bigint, bigint, bigint]>,
|
|
276
|
+
mintContract.read.uri([tokenId], {
|
|
277
|
+
gas: 100_000_000_000,
|
|
278
|
+
gasPrice: currentBlock.baseFeePerGas,
|
|
279
|
+
}) as Promise<string>,
|
|
280
|
+
])
|
|
281
|
+
|
|
282
|
+
const [_name, _description, _artifact, _renderer, mintedBlock, closeAt, _extraData] = data
|
|
283
|
+
|
|
284
|
+
let metadata
|
|
285
|
+
try {
|
|
286
|
+
const json = Buffer.from(dataUri.substring(29), 'base64').toString()
|
|
287
|
+
metadata = JSON.parse(json)
|
|
288
|
+
} catch (e) {
|
|
289
|
+
metadata = { name: '', description: '', image: '', animation_url: '' }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
tokenId,
|
|
294
|
+
collection,
|
|
295
|
+
name: metadata.name,
|
|
296
|
+
description: metadata.description,
|
|
297
|
+
image: metadata.image,
|
|
298
|
+
animationUrl: metadata.animation_url,
|
|
299
|
+
mintedBlock: BigInt(`${mintedBlock}`),
|
|
300
|
+
closeAt,
|
|
301
|
+
mintsBackfilledUntilBlock: 0n,
|
|
302
|
+
mintsFetchedUntilBlock: 0n,
|
|
303
|
+
mints: [],
|
|
304
|
+
}
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (tries < 3) return rpcFetchSingleToken(mintContract, collection, tokenId, currentBlock, tries + 1)
|
|
307
|
+
return null
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function rpcFetchTokenMints (
|
|
312
|
+
wagmi: Config,
|
|
313
|
+
chainId: number,
|
|
314
|
+
collection: `0x${string}`,
|
|
315
|
+
tokenId: bigint,
|
|
316
|
+
): Promise<MintEvent[]> {
|
|
317
|
+
const client = getPublicClient(wagmi, { chainId }) as PublicClient
|
|
318
|
+
const mintContract = getContract({ address: collection, abi: MINT_ABI, client })
|
|
319
|
+
|
|
320
|
+
const [tokenData, currentBlock] = await Promise.all([
|
|
321
|
+
mintContract.read.get([tokenId]) as Promise<[string, string, `0x${string}`[], bigint, bigint, bigint, bigint]>,
|
|
322
|
+
client.getBlockNumber(),
|
|
323
|
+
])
|
|
324
|
+
|
|
325
|
+
const mintedBlock = BigInt(`${tokenData[4]}`)
|
|
326
|
+
const untilBlock = mintedBlock + BLOCKS_PER_DAY
|
|
327
|
+
const toBlock = currentBlock > untilBlock ? untilBlock : currentBlock
|
|
328
|
+
|
|
329
|
+
return loadMintEventsRange(client, collection, tokenId, mintedBlock, toBlock)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function loadMintEventsRange (
|
|
333
|
+
client: PublicClient,
|
|
334
|
+
collection: `0x${string}`,
|
|
335
|
+
tokenId: bigint,
|
|
336
|
+
fromBlock: bigint,
|
|
337
|
+
toBlock: bigint,
|
|
338
|
+
): Promise<MintEvent[]> {
|
|
339
|
+
try {
|
|
340
|
+
const logs = await client.getLogs({
|
|
341
|
+
address: collection,
|
|
342
|
+
event: parseAbiItem('event NewMint(uint256 indexed tokenId, uint256 unitPrice, uint256 amount, address minter)'),
|
|
343
|
+
args: { tokenId },
|
|
344
|
+
fromBlock,
|
|
345
|
+
toBlock,
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
return logs.map(l => ({
|
|
349
|
+
tokenId,
|
|
350
|
+
address: l.args.minter,
|
|
351
|
+
block: l.blockNumber,
|
|
352
|
+
logIndex: l.logIndex,
|
|
353
|
+
tx: l.transactionHash,
|
|
354
|
+
unitPrice: l.args.unitPrice,
|
|
355
|
+
amount: l.args.amount,
|
|
356
|
+
price: (l.args.amount || 0n) * (l.args.unitPrice || 0n),
|
|
357
|
+
}) as MintEvent).reverse()
|
|
358
|
+
} catch (e) {
|
|
359
|
+
if (toBlock - fromBlock > 100n) {
|
|
360
|
+
const mid = fromBlock + (toBlock - fromBlock) / 2n
|
|
361
|
+
const [a, b] = await Promise.all([
|
|
362
|
+
loadMintEventsRange(client, collection, tokenId, fromBlock, mid),
|
|
363
|
+
loadMintEventsRange(client, collection, tokenId, mid + 1n, toBlock),
|
|
364
|
+
])
|
|
365
|
+
return [...a, ...b]
|
|
366
|
+
}
|
|
367
|
+
throw e
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
function toCollection (raw: PonderCollection): Collection {
|
|
373
|
+
return {
|
|
374
|
+
address: raw.address.toLowerCase() as `0x${string}`,
|
|
375
|
+
owner: (raw.owner || raw.artist.address).toLowerCase() as `0x${string}`,
|
|
376
|
+
version: 0n,
|
|
377
|
+
image: raw.image || '',
|
|
378
|
+
name: raw.name || '',
|
|
379
|
+
symbol: raw.symbol || '',
|
|
380
|
+
description: raw.description || '',
|
|
381
|
+
initBlock: BigInt(raw.init_block || '0'),
|
|
382
|
+
latestTokenId: BigInt(raw.latest_token_id || '0'),
|
|
383
|
+
balance: 0n,
|
|
384
|
+
tokens: {},
|
|
385
|
+
renderers: [],
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function toToken (raw: PonderArtifact): Token {
|
|
390
|
+
const mintedBlock = BigInt(raw.created_block || '0')
|
|
391
|
+
return {
|
|
392
|
+
collection: raw.collection.address.toLowerCase() as `0x${string}`,
|
|
393
|
+
tokenId: BigInt(raw.id),
|
|
394
|
+
name: raw.name || '',
|
|
395
|
+
description: raw.description || '',
|
|
396
|
+
image: raw.image || '',
|
|
397
|
+
animationUrl: raw.animation_url || undefined,
|
|
398
|
+
closeAt: mintedBlock + BLOCKS_PER_DAY,
|
|
399
|
+
mintedBlock,
|
|
400
|
+
mintsFetchedUntilBlock: 0n,
|
|
401
|
+
mintsBackfilledUntilBlock: 0n,
|
|
402
|
+
mints: [],
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function toMintEvent (raw: PonderMint): MintEvent {
|
|
407
|
+
return {
|
|
408
|
+
tokenId: BigInt(raw.artifact.id),
|
|
409
|
+
address: raw.account.toLowerCase() as `0x${string}`,
|
|
410
|
+
block: BigInt(raw.block_number),
|
|
411
|
+
logIndex: raw.log_index,
|
|
412
|
+
tx: raw.hash,
|
|
413
|
+
unitPrice: BigInt(raw.unit_price || '0'),
|
|
414
|
+
amount: BigInt(raw.amount || '0'),
|
|
415
|
+
price: BigInt(raw.price || '0'),
|
|
416
|
+
}
|
|
417
|
+
}
|
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
|