@visualizevalue/mint-app-base 0.0.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/.env.example +26 -0
- package/README.md +24 -0
- package/app/app.vue +7 -0
- package/app/assets/styles/animation.css +50 -0
- package/app/assets/styles/base.css +34 -0
- package/app/assets/styles/cards.css +20 -0
- package/app/assets/styles/custom-media.css +4 -0
- package/app/assets/styles/custom-selectors.css +1 -0
- package/app/assets/styles/forms.css +183 -0
- package/app/assets/styles/index.css +11 -0
- package/app/assets/styles/normalize.css +541 -0
- package/app/assets/styles/prose.css +166 -0
- package/app/assets/styles/scroll.css +13 -0
- package/app/assets/styles/text.css +14 -0
- package/app/assets/styles/utils.css +24 -0
- package/app/assets/styles/variables.css +195 -0
- package/app/assets/styles/web3-modals.css +26 -0
- package/app/components/Account.client.vue +20 -0
- package/app/components/Actions.vue +25 -0
- package/app/components/AppHeader.vue +99 -0
- package/app/components/Authenticated.client.vue +17 -0
- package/app/components/Avatar.vue +61 -0
- package/app/components/BlocksTimeAgo.client.vue +20 -0
- package/app/components/Breadcrumbs.vue +51 -0
- package/app/components/Button.vue +98 -0
- package/app/components/CardLink.vue +38 -0
- package/app/components/CheckSpinner.vue +39 -0
- package/app/components/Collection/Intro.vue +111 -0
- package/app/components/Collection/OverviewCard.vue +73 -0
- package/app/components/Collection/Withdraw.client.vue +61 -0
- package/app/components/CollectionsOverview.client.vue +58 -0
- package/app/components/Connect.client.vue +88 -0
- package/app/components/CountDown.vue +153 -0
- package/app/components/DialogFrame.vue +96 -0
- package/app/components/ExpandableText.vue +50 -0
- package/app/components/Form/Errors.vue +18 -0
- package/app/components/Form/Group.vue +57 -0
- package/app/components/Form/Input.vue +48 -0
- package/app/components/Form/SelectFile.vue +60 -0
- package/app/components/GasPrice.client.vue +9 -0
- package/app/components/HeaderSection.vue +18 -0
- package/app/components/Icon.vue +37 -0
- package/app/components/IconLink.vue +29 -0
- package/app/components/Image.client.vue +120 -0
- package/app/components/Loading.vue +79 -0
- package/app/components/MintGasPrice.client.vue +20 -0
- package/app/components/MintGasPricePopover.client.vue +69 -0
- package/app/components/MintToken.vue +89 -0
- package/app/components/MintTokenBar.vue +79 -0
- package/app/components/Modal.vue +36 -0
- package/app/components/Navbar.client.vue +86 -0
- package/app/components/Page/Frame.vue +77 -0
- package/app/components/Page/FrameSM.vue +33 -0
- package/app/components/Popover.client.vue +119 -0
- package/app/components/Profile/Header.client.vue +96 -0
- package/app/components/QueryDialog.vue +38 -0
- package/app/components/ToggleDarkMode.client.vue +58 -0
- package/app/components/Token/Detail.client.vue +194 -0
- package/app/components/Token/MintTimeline.client.vue +110 -0
- package/app/components/Token/MintTimelineItem.vue +33 -0
- package/app/components/Token/OverviewCard.vue +140 -0
- package/app/components/TransactionFlow.vue +225 -0
- package/app/components/Visual/ImagePreview.vue +8 -0
- package/app/composables/account.ts +21 -0
- package/app/composables/app.ts +15 -0
- package/app/composables/artistData.ts +22 -0
- package/app/composables/chainId.ts +25 -0
- package/app/composables/collections.ts +435 -0
- package/app/composables/darkMode.ts +1 -0
- package/app/composables/gasPrice.ts +46 -0
- package/app/composables/head.ts +29 -0
- package/app/composables/priceFeed.ts +80 -0
- package/app/composables/subdomain.ts +27 -0
- package/app/error.vue +31 -0
- package/app/layouts/default.vue +42 -0
- package/app/middleware/lowercaseId.ts +1 -0
- package/app/middleware/lowercaseProfileAddress.ts +1 -0
- package/app/middleware/redirectUserScope.ts +13 -0
- package/app/pages/[id]/[collection]/[tokenId]/index.vue +66 -0
- package/app/pages/[id]/[collection]/[tokenId].vue +25 -0
- package/app/pages/[id]/[collection]/index.vue +51 -0
- package/app/pages/[id]/[collection]/mint.vue +260 -0
- package/app/pages/[id]/[collection].vue +24 -0
- package/app/pages/[id]/add.vue +40 -0
- package/app/pages/[id]/create.vue +177 -0
- package/app/pages/[id]/index.vue +43 -0
- package/app/pages/[id].vue +9 -0
- package/app/pages/index.vue +47 -0
- package/app/pages/profile/[address]/index.vue +51 -0
- package/app/pages/profile/[address].vue +9 -0
- package/app/pages/profile/index.vue +28 -0
- package/app/plugins/1.polyfill.client.ts +12 -0
- package/app/plugins/2.wagmi.ts +57 -0
- package/app/router.options.ts +25 -0
- package/app/utils/abis.ts +77 -0
- package/app/utils/arrays.ts +1 -0
- package/app/utils/artifact.ts +21 -0
- package/app/utils/breakpoints.ts +11 -0
- package/app/utils/dates.ts +23 -0
- package/app/utils/format.ts +60 -0
- package/app/utils/images.ts +27 -0
- package/app/utils/ipfs.ts +13 -0
- package/app/utils/lowercaseRouteParam.ts +10 -0
- package/app/utils/serializer.ts +18 -0
- package/app/utils/strings.ts +30 -0
- package/app/utils/time.ts +23 -0
- package/app/utils/types.ts +62 -0
- package/app/utils/urls.ts +43 -0
- package/nuxt.config.ts +130 -0
- package/package.json +44 -0
- package/public/apple-touch-icon-512x512.png +0 -0
- package/public/example-contract-icon-original.svg +5 -0
- package/public/example-contract-icon.svg +5 -0
- package/public/favicon.ico +0 -0
- package/public/icon.svg +8 -0
- package/public/icons/check.svg +3 -0
- package/public/icons/opepen.svg +264 -0
- package/public/icons/wallets/coinbase.svg +4 -0
- package/public/icons/wallets/metamask.svg +1 -0
- package/public/icons/wallets/rainbow.svg +59 -0
- package/public/icons/wallets/walletconnect.svg +1 -0
- package/public/maskable-icon-512x512.png +0 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/pwa-64x64.png +0 -0
- package/server/middleware/log.ts +3 -0
- package/server/middleware/subdomain.ts +12 -0
- package/server/tsconfig.json +3 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Authenticated>
|
|
3
|
+
<PageFrame
|
|
4
|
+
:title="[
|
|
5
|
+
{
|
|
6
|
+
text: `Create New`
|
|
7
|
+
}
|
|
8
|
+
]"
|
|
9
|
+
class="inset"
|
|
10
|
+
>
|
|
11
|
+
|
|
12
|
+
<article class="preview">
|
|
13
|
+
<div class="visual">
|
|
14
|
+
<Image v-if="image" :src="image" alt="Preview" />
|
|
15
|
+
<VisualImagePreview v-else />
|
|
16
|
+
</div>
|
|
17
|
+
<h1>
|
|
18
|
+
<span :class="{ 'muted-light': !title }">{{ title || 'Token' }}</span>
|
|
19
|
+
<small :class="{ 'muted-light': !symbol }">{{ symbol || '$T' }}</small>
|
|
20
|
+
</h1>
|
|
21
|
+
<p :class="{ 'muted-light': !description }">
|
|
22
|
+
{{ description || 'No description' }}
|
|
23
|
+
</p>
|
|
24
|
+
</article>
|
|
25
|
+
|
|
26
|
+
<form @submit.stop.prevent="deploy" class="card borderless">
|
|
27
|
+
<div class="card">
|
|
28
|
+
<div>
|
|
29
|
+
<FormSelectFile @change="setImage" />
|
|
30
|
+
<p v-if="! isSmall" class="muted"><small>Note: This should be a small file, prefferably a simple SVG like <a href="/example-contract-icon.svg" target="_blank">this one (273 bytes)</a>. Try to make it less than 10kb.</small></p>
|
|
31
|
+
</div>
|
|
32
|
+
<FormGroup>
|
|
33
|
+
<FormInput v-model="title" placeholder="Title" required class="title" />
|
|
34
|
+
<FormInput v-model="symbol" placeholder="Symbol" required />
|
|
35
|
+
</FormGroup>
|
|
36
|
+
<FormInput v-model="description" placeholder="Description" />
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<Actions class="borderless">
|
|
40
|
+
<TransactionFlow
|
|
41
|
+
:request="deployRequest"
|
|
42
|
+
:text="{
|
|
43
|
+
title: {
|
|
44
|
+
chain: 'Switch Chain',
|
|
45
|
+
requesting: 'Confirm In Wallet',
|
|
46
|
+
waiting: '2. Transaction Submitted',
|
|
47
|
+
complete: '3. Success!'
|
|
48
|
+
},
|
|
49
|
+
lead: {
|
|
50
|
+
chain: 'Requesting to switch chain...',
|
|
51
|
+
requesting: 'Requesting Signature...',
|
|
52
|
+
waiting: 'Checking Deployment Transaction...',
|
|
53
|
+
complete: `New Collection Created...`,
|
|
54
|
+
},
|
|
55
|
+
action: {
|
|
56
|
+
confirm: 'Mint',
|
|
57
|
+
error: 'Retry',
|
|
58
|
+
complete: 'OK',
|
|
59
|
+
},
|
|
60
|
+
}"
|
|
61
|
+
@complete="deployed"
|
|
62
|
+
skip-confirmation
|
|
63
|
+
auto-close-success
|
|
64
|
+
>
|
|
65
|
+
<template #start="{ start }">
|
|
66
|
+
<Button @click="start">
|
|
67
|
+
Deploy
|
|
68
|
+
</Button>
|
|
69
|
+
</template>
|
|
70
|
+
</TransactionFlow>
|
|
71
|
+
</Actions>
|
|
72
|
+
</form>
|
|
73
|
+
|
|
74
|
+
</PageFrame>
|
|
75
|
+
</Authenticated>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup>
|
|
79
|
+
const config = useRuntimeConfig()
|
|
80
|
+
const store = useOnchainStore()
|
|
81
|
+
const chainId = useMainChainId()
|
|
82
|
+
|
|
83
|
+
const image = ref('')
|
|
84
|
+
const title = ref('')
|
|
85
|
+
const symbol = ref('')
|
|
86
|
+
const description = ref('')
|
|
87
|
+
|
|
88
|
+
const { $wagmi } = useNuxtApp()
|
|
89
|
+
const id = useArtistId()
|
|
90
|
+
|
|
91
|
+
const imageSize = ref(0)
|
|
92
|
+
const isSmall = computed(() => imageSize.value / 1024 < 10)
|
|
93
|
+
const setImage = async (file) => {
|
|
94
|
+
try {
|
|
95
|
+
image.value = await imageFileToDataUri(file)
|
|
96
|
+
} catch (e) {
|
|
97
|
+
image.value = ''
|
|
98
|
+
}
|
|
99
|
+
imageSize.value = file.size
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const deployRequest = computed(() => async () => {
|
|
103
|
+
return await writeContract($wagmi, {
|
|
104
|
+
abi: FACTORY_ABI,
|
|
105
|
+
chainId,
|
|
106
|
+
address: config.public.factoryAddress,
|
|
107
|
+
functionName: 'create',
|
|
108
|
+
args: [
|
|
109
|
+
title.value,
|
|
110
|
+
symbol.value,
|
|
111
|
+
description.value,
|
|
112
|
+
toByteArray(image.value),
|
|
113
|
+
],
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const deployed = async (receipt) => {
|
|
118
|
+
const logs = receipt.logs.map(log => decodeEventLog({
|
|
119
|
+
abi: [...FACTORY_ABI, ...MINT_ABI],
|
|
120
|
+
data: log.data,
|
|
121
|
+
topics: log.topics,
|
|
122
|
+
strict: false,
|
|
123
|
+
}))
|
|
124
|
+
|
|
125
|
+
const createdEvent = logs.find(log => log.eventName === 'Created')
|
|
126
|
+
|
|
127
|
+
const artist = store.artist(id.value)
|
|
128
|
+
await store.fetchCollections(id.value, config.public.factoryAddress, artist.collectionsFetchedUntilBlock)
|
|
129
|
+
await navigateTo(`/${id.value}/${createdEvent.args.contractAddress}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
useMetaData({
|
|
133
|
+
title: `Create New Collection`,
|
|
134
|
+
})
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<style scoped>
|
|
138
|
+
.preview {
|
|
139
|
+
.visual {
|
|
140
|
+
width: 5rem;
|
|
141
|
+
margin-bottom: var(--spacer-sm);
|
|
142
|
+
|
|
143
|
+
svg {
|
|
144
|
+
border-radius: var(--border-radius);
|
|
145
|
+
border: var(--border);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
h1 {
|
|
150
|
+
display: flex;
|
|
151
|
+
gap: var(--spacer-sm);
|
|
152
|
+
align-items: baseline;
|
|
153
|
+
font-size: var(--font-lg);
|
|
154
|
+
|
|
155
|
+
small {
|
|
156
|
+
font-size: var(--font-base);
|
|
157
|
+
color: var(--muted);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
p {
|
|
162
|
+
color: var(--muted);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
form {
|
|
167
|
+
width: 100%;
|
|
168
|
+
|
|
169
|
+
:deep(fieldset) {
|
|
170
|
+
@container (min-width: 30rem) {
|
|
171
|
+
.title {
|
|
172
|
+
width: 100%;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
</style>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageFrame :title="breadcrumb">
|
|
3
|
+
<ProfileHeader :address="id" />
|
|
4
|
+
|
|
5
|
+
<CollectionsOverview :id="id" :key="`${isMe}-${id}`">
|
|
6
|
+
<template #before="{ collections }">
|
|
7
|
+
<HeaderSection v-if="isMe && collections.length">
|
|
8
|
+
<h1>Your Collections</h1>
|
|
9
|
+
<Actions>
|
|
10
|
+
<Button :to="{ name: `id-create`, params: { id } }" class="small">
|
|
11
|
+
<Icon type="plus" />
|
|
12
|
+
<span>Collection</span>
|
|
13
|
+
</Button>
|
|
14
|
+
</Actions>
|
|
15
|
+
</HeaderSection>
|
|
16
|
+
</template>
|
|
17
|
+
</CollectionsOverview>
|
|
18
|
+
</PageFrame>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
const id = useArtistId()
|
|
23
|
+
const isMe = useIsMe()
|
|
24
|
+
const store = useOnchainStore()
|
|
25
|
+
|
|
26
|
+
const hideArtist = useShowArtistInHeader()
|
|
27
|
+
const breadcrumb = computed(() => {
|
|
28
|
+
if (hideArtist.value) return []
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
text: store.displayName(id.value)
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
useMetaData({
|
|
38
|
+
title: store.displayName(id.value),
|
|
39
|
+
})
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style scoped>
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageFrame>
|
|
3
|
+
<header>
|
|
4
|
+
<p>{{ config.public.description }}</p>
|
|
5
|
+
|
|
6
|
+
<ClientOnly>
|
|
7
|
+
<Connect @connected="$event => navigateTo({ name: 'id', params: { id: $event.address } }, { replace: true })">
|
|
8
|
+
<template #connected>
|
|
9
|
+
<span></span>
|
|
10
|
+
</template>
|
|
11
|
+
</Connect>
|
|
12
|
+
<template #fallback>
|
|
13
|
+
<Button>Connect</Button>
|
|
14
|
+
</template>
|
|
15
|
+
</ClientOnly>
|
|
16
|
+
</header>
|
|
17
|
+
</PageFrame>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup>
|
|
21
|
+
const config = useRuntimeConfig()
|
|
22
|
+
|
|
23
|
+
useMetaData({
|
|
24
|
+
title: config.public.title,
|
|
25
|
+
description: config.public.description,
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<style scoped>
|
|
30
|
+
header {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: var(--spacer);
|
|
36
|
+
|
|
37
|
+
p {
|
|
38
|
+
text-align: center;
|
|
39
|
+
color: var(--muted);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* HEIGHT */
|
|
44
|
+
header {
|
|
45
|
+
height: calc(100dvh - var(--navbar-height) * 3);
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Loading v-if="! artist" />
|
|
3
|
+
<PageFrame v-else title="Profile">
|
|
4
|
+
|
|
5
|
+
<ProfileHeader :address="address">
|
|
6
|
+
<template v-if="isMe" #before>
|
|
7
|
+
<Actions>
|
|
8
|
+
<Button :to="`https://app.ens.domains/${address}`" class="small">
|
|
9
|
+
<Icon type="edit-2" />
|
|
10
|
+
<span>Edit Profile</span>
|
|
11
|
+
</Button>
|
|
12
|
+
</Actions>
|
|
13
|
+
</template>
|
|
14
|
+
</ProfileHeader>
|
|
15
|
+
|
|
16
|
+
<section v-if="artistScope" class="centered muted">
|
|
17
|
+
<p>Collected art from <Account :address="artistScope" /> will be indexed on profiles soon...</p>
|
|
18
|
+
<p v-if="isMyArtistScope">Manage Your collections <NuxtLink :to="{ name: 'id', params: { id: address } }">here</NuxtLink>.</p>
|
|
19
|
+
<p v-else>You can mint your own art on <NuxtLink :to="config.public.platformUrl" target="_blank">{{ getMainDomain(config.public.platformUrl) }}</NuxtLink>.</p>
|
|
20
|
+
</section>
|
|
21
|
+
<section v-else class="centered">
|
|
22
|
+
<p class="muted">Collected art and curation options for your profile will come soon...</p>
|
|
23
|
+
</section>
|
|
24
|
+
</PageFrame>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
const config = useRuntimeConfig()
|
|
29
|
+
const route = useRoute()
|
|
30
|
+
const address = computed(() => route.params.address)
|
|
31
|
+
const store = useOnchainStore()
|
|
32
|
+
const isMe = useIsMeCheck(address.value)
|
|
33
|
+
const artistScope = useArtistScope()
|
|
34
|
+
const isMyArtistScope = useIsMeCheck(artistScope)
|
|
35
|
+
|
|
36
|
+
const artist = ref(null)
|
|
37
|
+
|
|
38
|
+
const load = async () => {
|
|
39
|
+
await store.fetchArtistScope(address.value, config.public.factoryAddress)
|
|
40
|
+
|
|
41
|
+
artist.value = store.artist(address.value)
|
|
42
|
+
}
|
|
43
|
+
onMounted(() => load())
|
|
44
|
+
|
|
45
|
+
useMetaData({
|
|
46
|
+
title: artist.value?.ens || shortAddress(address.value),
|
|
47
|
+
})
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<style scoped>
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageFrame>
|
|
3
|
+
<header>
|
|
4
|
+
<Connect v-if="! isConnected" />
|
|
5
|
+
</header>
|
|
6
|
+
</PageFrame>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup>
|
|
10
|
+
import { useAccount } from '@wagmi/vue'
|
|
11
|
+
|
|
12
|
+
const { address, isConnected } = useAccount()
|
|
13
|
+
|
|
14
|
+
const redirect = () => {
|
|
15
|
+
if (isConnected) navigateTo({ name: 'profile-address', params: { address: address.value?.toLowerCase() }}, {
|
|
16
|
+
replace: true
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
onMounted(() => redirect())
|
|
20
|
+
watch(isConnected, () => redirect())
|
|
21
|
+
|
|
22
|
+
useMetaData({
|
|
23
|
+
title: `Profile`,
|
|
24
|
+
})
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<style scoped>
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from '#app'
|
|
2
|
+
import buffer from 'buffer'
|
|
3
|
+
|
|
4
|
+
// This also uses the server `polyfill` plugin...
|
|
5
|
+
export default defineNuxtPlugin({
|
|
6
|
+
name: 'client-polyfill',
|
|
7
|
+
enforce: 'pre',
|
|
8
|
+
async setup () {
|
|
9
|
+
window.global = window
|
|
10
|
+
window.Buffer = buffer.Buffer
|
|
11
|
+
}
|
|
12
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// import { custom, fallback } from 'viem'
|
|
2
|
+
import { VueQueryPlugin } from '@tanstack/vue-query'
|
|
3
|
+
import { http, cookieStorage, createConfig, createStorage, WagmiPlugin } from '@wagmi/vue'
|
|
4
|
+
import { mainnet, sepolia, holesky, localhost } from '@wagmi/vue/chains'
|
|
5
|
+
import { coinbaseWallet, injected, metaMask, walletConnect } from '@wagmi/vue/connectors'
|
|
6
|
+
|
|
7
|
+
// const isBrowser = typeof window !== 'undefined' && window !== null
|
|
8
|
+
// const transports = isBrowser && window.ethereum
|
|
9
|
+
// ? fallback([ custom(window.ethereum!), http() ])
|
|
10
|
+
// : http()
|
|
11
|
+
const transports = http()
|
|
12
|
+
|
|
13
|
+
export default defineNuxtPlugin(nuxtApp => {
|
|
14
|
+
const title = nuxtApp.$config.public.title || 'Mint'
|
|
15
|
+
|
|
16
|
+
const wagmiConfig = createConfig({
|
|
17
|
+
chains: [mainnet, sepolia, holesky, localhost],
|
|
18
|
+
batch: {
|
|
19
|
+
multicall: true,
|
|
20
|
+
},
|
|
21
|
+
connectors: [
|
|
22
|
+
injected(),
|
|
23
|
+
walletConnect({
|
|
24
|
+
projectId: nuxtApp.$config.public.walletConnectProjectId,
|
|
25
|
+
}),
|
|
26
|
+
coinbaseWallet({
|
|
27
|
+
appName: title,
|
|
28
|
+
appLogoUrl: '',
|
|
29
|
+
}),
|
|
30
|
+
metaMask({
|
|
31
|
+
dappMetadata: {
|
|
32
|
+
name: title,
|
|
33
|
+
iconUrl: '',
|
|
34
|
+
url: '',
|
|
35
|
+
}
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
storage: createStorage({
|
|
39
|
+
storage: cookieStorage,
|
|
40
|
+
}),
|
|
41
|
+
ssr: true,
|
|
42
|
+
transports: {
|
|
43
|
+
[mainnet.id]: transports,
|
|
44
|
+
[sepolia.id]: transports,
|
|
45
|
+
[holesky.id]: transports,
|
|
46
|
+
[localhost.id]: transports,
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
nuxtApp.vueApp.use(WagmiPlugin, { config: wagmiConfig }).use(VueQueryPlugin, {})
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
provide: {
|
|
54
|
+
wagmi: wagmiConfig,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RouterConfig } from '@nuxt/schema'
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
routes: (_routes) => {
|
|
5
|
+
const { ssrContext } = useNuxtApp()
|
|
6
|
+
const subdomain = useSubdomain()
|
|
7
|
+
|
|
8
|
+
if (ssrContext?.event.context.subdomain) subdomain.value = ssrContext?.event.context.subdomain?.toLowerCase()
|
|
9
|
+
|
|
10
|
+
const address = useArtistScope()
|
|
11
|
+
|
|
12
|
+
if (address) {
|
|
13
|
+
const normalizedRoutes = _routes
|
|
14
|
+
.map((i) => ({
|
|
15
|
+
...i,
|
|
16
|
+
path: i.path === '/:id()' ? i.path.replace('/:id()', '/') : i.path.replace('/:id()/', '/'),
|
|
17
|
+
}))
|
|
18
|
+
.filter(r => r.name !== 'index')
|
|
19
|
+
|
|
20
|
+
return normalizedRoutes
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return _routes
|
|
24
|
+
},
|
|
25
|
+
} satisfies RouterConfig
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parseAbi } from 'viem'
|
|
2
|
+
|
|
3
|
+
export const FACTORY_ABI = parseAbi([
|
|
4
|
+
'error ERC1167FailedCreateClone()',
|
|
5
|
+
'error OwnableInvalidOwner(address owner)',
|
|
6
|
+
'error OwnableUnauthorizedAccount(address account)',
|
|
7
|
+
'event Created(address indexed ownerAddress, address contractAddress)',
|
|
8
|
+
'event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)',
|
|
9
|
+
'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',
|
|
10
|
+
'function acceptOwnership()',
|
|
11
|
+
'function clone(string name, string symbol, string description, bytes[] image) returns (address)',
|
|
12
|
+
'function create(string name, string symbol, string description, bytes[] image) returns (address)',
|
|
13
|
+
'function getCreatorCollections(address creator) view returns (address[])',
|
|
14
|
+
'function initialize(address mint, address renderer)',
|
|
15
|
+
'function owner() view returns (address)',
|
|
16
|
+
'function pendingOwner() view returns (address)',
|
|
17
|
+
'function renounceOwnership()',
|
|
18
|
+
'function transferOwnership(address newOwner)',
|
|
19
|
+
'function version() pure returns (uint256)'
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export const MINT_ABI = parseAbi([
|
|
24
|
+
'error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId)',
|
|
25
|
+
'error ERC1155InvalidApprover(address approver)',
|
|
26
|
+
'error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength)',
|
|
27
|
+
'error ERC1155InvalidOperator(address operator)',
|
|
28
|
+
'error ERC1155InvalidReceiver(address receiver)',
|
|
29
|
+
'error ERC1155InvalidSender(address sender)',
|
|
30
|
+
'error ERC1155MissingApprovalForAll(address operator, address owner)',
|
|
31
|
+
'error Initialized()',
|
|
32
|
+
'error MintClosed()',
|
|
33
|
+
'error MintPriceNotMet()',
|
|
34
|
+
'error NonExistentRenderer()',
|
|
35
|
+
'error NonExistentToken()',
|
|
36
|
+
'error OwnableInvalidOwner(address owner)',
|
|
37
|
+
'error OwnableUnauthorizedAccount(address account)',
|
|
38
|
+
'error TokenAlreadyMinted()',
|
|
39
|
+
'event ApprovalForAll(address indexed account, address indexed operator, bool approved)',
|
|
40
|
+
'event NewMint(uint256 indexed tokenId, uint256 unitPrice, uint256 amount, address minter)',
|
|
41
|
+
'event NewRenderer(address indexed renderer, uint256 indexed index)',
|
|
42
|
+
'event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)',
|
|
43
|
+
'event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)',
|
|
44
|
+
'event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)',
|
|
45
|
+
'event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)',
|
|
46
|
+
'event URI(string value, uint256 indexed id)',
|
|
47
|
+
'event Withdrawal(uint256 amount)',
|
|
48
|
+
'function acceptOwnership()',
|
|
49
|
+
'function artifact(uint256 tokenId) view returns (bytes content)',
|
|
50
|
+
'function balanceOf(address account, uint256 id) view returns (uint256)',
|
|
51
|
+
'function balanceOfBatch(address[] accounts, uint256[] ids) view returns (uint256[])',
|
|
52
|
+
'function burn(address account, uint256 tokenId, uint256 amount)',
|
|
53
|
+
'function contractURI() view returns (string)',
|
|
54
|
+
'function create(string tokenName, string tokenDescription, bytes[] tokenArtifact, uint32 tokenRenderer, uint192 tokenData)',
|
|
55
|
+
'function init(string contractName, string contractSymbol, string contractDescription, bytes[] contractImage, address renderer, address owner)',
|
|
56
|
+
'function initBlock() view returns (uint256)',
|
|
57
|
+
'function isApprovedForAll(address account, address operator) view returns (bool)',
|
|
58
|
+
'function latestTokenId() view returns (uint256)',
|
|
59
|
+
'function metadata() view returns (string name, string symbol, string description)',
|
|
60
|
+
'function mint(uint256 tokenId, uint256 amount) payable',
|
|
61
|
+
'function mintOpenUntil(uint256 tokenId) view returns (uint256)',
|
|
62
|
+
'function owner() view returns (address)',
|
|
63
|
+
'function pendingOwner() view returns (address)',
|
|
64
|
+
'function prepareArtifact(uint256 tokenId, bytes[] tokenArtifact, bool clear)',
|
|
65
|
+
'function registerRenderer(address renderer) returns (uint256)',
|
|
66
|
+
'function renderers(uint256) view returns (address)',
|
|
67
|
+
'function renounceOwnership()',
|
|
68
|
+
'function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] values, bytes data)',
|
|
69
|
+
'function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes data)',
|
|
70
|
+
'function setApprovalForAll(address operator, bool approved)',
|
|
71
|
+
'function supportsInterface(bytes4 interfaceId) view returns (bool)',
|
|
72
|
+
'function tokens(uint256) view returns (string name, string description, uint32 renderer, uint32 blocks, uint192 data)',
|
|
73
|
+
'function transferOwnership(address newOwner)',
|
|
74
|
+
'function uri(uint256 tokenId) view returns (string)',
|
|
75
|
+
'function version() view returns (uint256)',
|
|
76
|
+
'function withdraw()'
|
|
77
|
+
])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { chunkArray } from '@visualizevalue/mint-utils'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { toByteArray } from '@visualizevalue/mint-utils'
|
|
2
|
+
|
|
3
|
+
export const imageFileToDataUri = (file: File): Promise<string> => {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const reader = new FileReader()
|
|
6
|
+
|
|
7
|
+
reader.onload = (event) => {
|
|
8
|
+
if (event.target && typeof event.target.result === 'string') {
|
|
9
|
+
resolve(event.target.result)
|
|
10
|
+
} else {
|
|
11
|
+
reject(new Error('Failed to convert image to data URI'))
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
reader.onerror = (error) => {
|
|
16
|
+
reject(error)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
reader.readAsDataURL(file)
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DateTime } from 'luxon'
|
|
2
|
+
|
|
3
|
+
export const formatDate = (date: string) => {
|
|
4
|
+
return DateTime.fromISO(date)
|
|
5
|
+
.setLocale('en')
|
|
6
|
+
.toLocaleString(DateTime.DATE_MED)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const formatDateTime = (date: string) => {
|
|
10
|
+
return DateTime.fromISO(date).setLocale('en-US').toLocaleString({ year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const formatTime = (date: string) => {
|
|
14
|
+
return DateTime.fromISO(date).setLocale('en-US').toLocaleString({ hour: 'numeric', minute: 'numeric' })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const timeAgo = (date: string) => {
|
|
18
|
+
const parsed = DateTime.fromISO(date)
|
|
19
|
+
|
|
20
|
+
return DateTime.now().diff(parsed).as('days') > 2
|
|
21
|
+
? formatDate(date)
|
|
22
|
+
: parsed.toRelative({ style: 'short', locale: 'us' })
|
|
23
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { formatEther, formatGwei } from 'viem'
|
|
2
|
+
|
|
3
|
+
export const formatNumber = (i: number, digits?: number) => i?.toLocaleString('en-US', { maximumFractionDigits: digits })
|
|
4
|
+
|
|
5
|
+
export const toFloat = (number: string, digits: number = 2) => parseFloat(number).toFixed(digits)
|
|
6
|
+
|
|
7
|
+
export const roundNumber = (number: string) => parseInt(Math.round(parseFloat(number)).toString())
|
|
8
|
+
|
|
9
|
+
export const shortString = (str: string, max: number = 40, length: number = 10) => str?.length > max
|
|
10
|
+
? str.substring(0, length) + '...' : str
|
|
11
|
+
|
|
12
|
+
export const shortAddress = (address: string, length: number = 3) => address.substring(0, length + 2) +
|
|
13
|
+
'...' +
|
|
14
|
+
address.substring(address.length - length)
|
|
15
|
+
|
|
16
|
+
export const customGweiFormat = (price: bigint, digits?: number) => price > 20000000000n
|
|
17
|
+
? formatNumber(roundNumber(formatGwei(price)), digits)
|
|
18
|
+
: formatNumber(parseFloat(formatGwei(price)), digits)
|
|
19
|
+
|
|
20
|
+
export const customFormatEther = (value: bigint, digits: number = 4) => {
|
|
21
|
+
const format = value > 100_000_000_000_000n ? 'ETH' : 'GWEI'
|
|
22
|
+
|
|
23
|
+
const formatted = format === 'ETH'
|
|
24
|
+
? formatNumber(parseFloat(formatEther(value)), digits)
|
|
25
|
+
: customGweiFormat(value)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
value: formatted,
|
|
29
|
+
format,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface FormattedSize {
|
|
34
|
+
B: string
|
|
35
|
+
KB: string
|
|
36
|
+
MB: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const formatBytesObject = (bytes: number): FormattedSize => {
|
|
40
|
+
const KB = bytes / 1024
|
|
41
|
+
const MB = KB / 1024
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
B: bytes.toLocaleString('en-US'),
|
|
45
|
+
KB: KB.toLocaleString('en-US', { maximumFractionDigits: 2 }),
|
|
46
|
+
MB: MB.toLocaleString('en-US', { maximumFractionDigits: 2 }),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const formatBytes = (bytes: number): string => {
|
|
51
|
+
const formatted = formatBytesObject(bytes)
|
|
52
|
+
|
|
53
|
+
if (bytes < 1024) {
|
|
54
|
+
return `${formatted.B} B`
|
|
55
|
+
} else if (bytes < 1024 * 1024) {
|
|
56
|
+
return `${formatted.KB} KB`
|
|
57
|
+
} else {
|
|
58
|
+
return `${formatted.MB} MB`
|
|
59
|
+
}
|
|
60
|
+
}
|