@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,27 @@
|
|
|
1
|
+
export const useSubdomain = () => useState<string|null>('subdomain', () => null)
|
|
2
|
+
|
|
3
|
+
export const useArtistScope = () => {
|
|
4
|
+
const subdomain = useSubdomain()
|
|
5
|
+
const creatorAddress = useRuntimeConfig().public.creatorAddress
|
|
6
|
+
|
|
7
|
+
return creatorAddress || subdomain.value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useShowArtistInHeader = () => {
|
|
11
|
+
const isMe = useIsMe()
|
|
12
|
+
const scope = useArtistScope()
|
|
13
|
+
|
|
14
|
+
return computed(() => scope || isMe.value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const useArtistId = () => {
|
|
18
|
+
const route = useRoute()
|
|
19
|
+
const artist = useArtistScope()
|
|
20
|
+
|
|
21
|
+
return computed(() => artist
|
|
22
|
+
? artist as `0x${string}`
|
|
23
|
+
: route.params.id
|
|
24
|
+
? (route.params.id as string).toLowerCase() as `0x${string}`
|
|
25
|
+
: null
|
|
26
|
+
)
|
|
27
|
+
}
|
package/app/error.vue
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="display">{{ error?.statusCode }}</h1>
|
|
4
|
+
<p class="lead">{{ error?.message }}</p>
|
|
5
|
+
<Button to="/">Go back home</Button>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import type { NuxtError } from '#app'
|
|
11
|
+
|
|
12
|
+
defineProps({
|
|
13
|
+
error: Object as () => NuxtError
|
|
14
|
+
})
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<style scoped>
|
|
18
|
+
div {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
gap: var(--spacer);
|
|
24
|
+
height: 100dvh;
|
|
25
|
+
|
|
26
|
+
.button {
|
|
27
|
+
display: flex;
|
|
28
|
+
margin-top: var(--size-6);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<AppHeader />
|
|
4
|
+
|
|
5
|
+
<main>
|
|
6
|
+
<slot />
|
|
7
|
+
</main>
|
|
8
|
+
|
|
9
|
+
<Navbar />
|
|
10
|
+
|
|
11
|
+
<ToggleDarkMode />
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup>
|
|
16
|
+
// Fetch and update price feed
|
|
17
|
+
const priceFeed = usePriceFeedStore()
|
|
18
|
+
onMounted(() => {
|
|
19
|
+
priceFeed.fetchEthUsdPrice()
|
|
20
|
+
|
|
21
|
+
setInterval(() => priceFeed.fetchEthUsdPrice(), 60 * 60 * 1000)
|
|
22
|
+
})
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style scoped>
|
|
26
|
+
main {
|
|
27
|
+
display: grid;
|
|
28
|
+
gap: var(--spacer);
|
|
29
|
+
min-height: 100dvh;
|
|
30
|
+
|
|
31
|
+
&:not(:has(> .frame-sm)) {
|
|
32
|
+
grid-auto-rows: min-content;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Frame space around navbars */
|
|
36
|
+
padding: var(--navbar-height) 0 var(--navbar-height);
|
|
37
|
+
|
|
38
|
+
@media (--md) {
|
|
39
|
+
padding: var(--navbar-height) 0 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware((to) => lowercaseRouteParam(to, 'id'))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware((to) => lowercaseRouteParam(to, 'address'))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useAccount } from "@wagmi/vue"
|
|
2
|
+
|
|
3
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
4
|
+
if (import.meta.server) return
|
|
5
|
+
|
|
6
|
+
const { isConnected, address } = useAccount()
|
|
7
|
+
|
|
8
|
+
if (to.name === 'index' && isConnected.value) {
|
|
9
|
+
return navigateTo({ name: 'id', params: { id: address.value }}, {
|
|
10
|
+
replace: true
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageFrame :title="breadcrumb" class="full">
|
|
3
|
+
<TokenDetail :token=token />
|
|
4
|
+
</PageFrame>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { useAccount } from '@wagmi/vue'
|
|
9
|
+
|
|
10
|
+
const id = useArtistId()
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
const { address, isConnected } = useAccount()
|
|
13
|
+
const props = defineProps(['collection', 'token'])
|
|
14
|
+
const collection = computed(() => props.collection)
|
|
15
|
+
const token = computed(() => props.token)
|
|
16
|
+
const tokenId = computed(() => BigInt(route.params.tokenId))
|
|
17
|
+
const store = useOnchainStore()
|
|
18
|
+
|
|
19
|
+
// Keep track of account token balance
|
|
20
|
+
const ownedBalance = computed(() => store.tokenBalance(collection.value.address, token.value.tokenId))
|
|
21
|
+
const maybeCheckBalance = async (force = false) => {
|
|
22
|
+
if (isConnected.value && (ownedBalance.value === null || force)) {
|
|
23
|
+
await store.fetchTokenBalance(token.value, address.value)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
watch(isConnected, () => maybeCheckBalance(true))
|
|
27
|
+
|
|
28
|
+
// Navigation guards
|
|
29
|
+
onMounted(async () => {
|
|
30
|
+
if (collection.value.latestTokenId < tokenId.value) {
|
|
31
|
+
return navigateTo({ name: 'id-collection', params: { id: id.value, collection: collection.value.address }}, {
|
|
32
|
+
replace: true
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await maybeCheckBalance()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const hideArtist = useShowArtistInHeader()
|
|
40
|
+
const breadcrumb = computed(() => {
|
|
41
|
+
const path = hideArtist.value ? [] : [
|
|
42
|
+
{
|
|
43
|
+
text: store.displayName(id.value),
|
|
44
|
+
to: { name: 'id', params: { id: id.value } }
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
return [
|
|
49
|
+
...path,
|
|
50
|
+
{
|
|
51
|
+
text: `${ collection.value.name }`,
|
|
52
|
+
to: { name: 'id-collection', params: { id: id.value, collection: collection.value.address } }
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
text: `#${ tokenId.value }`
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
useMetaData({
|
|
61
|
+
title: `${ token.value?.name } (#${tokenId.value}) | ${collection.value.name}`,
|
|
62
|
+
})
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<style scoped>
|
|
66
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Loading v-if="loading" />
|
|
3
|
+
<NuxtPage v-else :collection="collection" :token="token" />
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup>
|
|
7
|
+
const props = defineProps(['collection'])
|
|
8
|
+
const route = useRoute()
|
|
9
|
+
const collection = computed(() => props.collection)
|
|
10
|
+
|
|
11
|
+
const store = useOnchainStore()
|
|
12
|
+
const token = computed(() => collection.value.tokens[route.params.tokenId])
|
|
13
|
+
const loading = ref(true)
|
|
14
|
+
|
|
15
|
+
const load = async () => {
|
|
16
|
+
loading.value = true
|
|
17
|
+
|
|
18
|
+
await store.fetchToken(collection.value.address, route.params.tokenId)
|
|
19
|
+
|
|
20
|
+
loading.value = false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onMounted(() => load())
|
|
24
|
+
watch(route, () => load())
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<PageFrame :title="breadcrumb">
|
|
3
|
+
<CollectionIntro :collection="collection" />
|
|
4
|
+
|
|
5
|
+
<TokenOverviewCard v-for="token of tokens" :key="token.tokenId" :token="token" />
|
|
6
|
+
|
|
7
|
+
<Loading v-if="loading" />
|
|
8
|
+
<div v-if="! tokens.length && !loading" class="centered">
|
|
9
|
+
<p class="muted">No tokens yet</p>
|
|
10
|
+
</div>
|
|
11
|
+
</PageFrame>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup>
|
|
15
|
+
const props = defineProps(['collection'])
|
|
16
|
+
const collection = computed(() => props.collection)
|
|
17
|
+
const id = useArtistId()
|
|
18
|
+
const store = useOnchainStore()
|
|
19
|
+
|
|
20
|
+
const hideArtist = useShowArtistInHeader()
|
|
21
|
+
const breadcrumb = computed(() => {
|
|
22
|
+
const path = hideArtist.value ? [] : [
|
|
23
|
+
{
|
|
24
|
+
text: store.displayName(id.value),
|
|
25
|
+
to: { name: 'id', params: { id } }
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
...path,
|
|
31
|
+
{
|
|
32
|
+
text: collection.value.name
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
useMetaData({
|
|
38
|
+
title: `${collection.value.name}`,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const tokens = computed(() => store.tokens(collection.value.address))
|
|
42
|
+
const loading = ref(false)
|
|
43
|
+
onMounted(async () => {
|
|
44
|
+
loading.value = true
|
|
45
|
+
await store.fetchCollectionTokens(collection.value.address)
|
|
46
|
+
loading.value = false
|
|
47
|
+
})
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<style scoped>
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Authenticated>
|
|
3
|
+
<PageFrame :title="breadcrumb" class="inset wide" id="mint-token">
|
|
4
|
+
<article class="preview">
|
|
5
|
+
<Image v-if="image" :src="image" alt="Preview" />
|
|
6
|
+
<VisualImagePreview v-else />
|
|
7
|
+
<h1 :class="{ 'muted-light': !name }">{{ name || 'Token' }}</h1>
|
|
8
|
+
<p :class="{ 'muted-light': !description }">
|
|
9
|
+
{{ description || 'No description' }}
|
|
10
|
+
</p>
|
|
11
|
+
</article>
|
|
12
|
+
|
|
13
|
+
<form @submit.stop.prevent="mint" class="card">
|
|
14
|
+
<Actions>
|
|
15
|
+
<select class="select small choose-mode" v-model="mode">
|
|
16
|
+
<option value="file" title="Data URI Encoded File Upload">DATA-URI</option>
|
|
17
|
+
<option value="ipfs" title="Interplanetary File System">IPFS</option>
|
|
18
|
+
<option value="http" title="Hypertext Transfer Protocol" disabled>HTTP</option>
|
|
19
|
+
<option value="svg" title="Scalable Vector Graphic" disabled>SVG</option>
|
|
20
|
+
</select>
|
|
21
|
+
</Actions>
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<div v-if="mode === 'file'">
|
|
25
|
+
<FormSelectFile @change="setImage" />
|
|
26
|
+
<p v-if="! isSmall" class="muted">
|
|
27
|
+
<small>
|
|
28
|
+
Note: This should be a small file, prefferably an SVG like <a href="https://presence.art/tokens/perspective.svg" target="_blank">this one (810 bytes)</a>.
|
|
29
|
+
If it is larger than what we can store within one transaction, the token creation will be split up into multiple transactions.
|
|
30
|
+
</small>
|
|
31
|
+
</p>
|
|
32
|
+
</div>
|
|
33
|
+
<FormInput v-else-if="mode === 'ipfs'" v-model="ipfsCid" placeholder="CID (qmx...)" prefix="ipfs://" required />
|
|
34
|
+
|
|
35
|
+
<FormInput v-model="name" placeholder="Title" required />
|
|
36
|
+
<FormInput v-model="description" placeholder="Description" />
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<Actions>
|
|
40
|
+
<Button>Mint</Button>
|
|
41
|
+
</Actions>
|
|
42
|
+
<TransactionFlow
|
|
43
|
+
ref="txFlow"
|
|
44
|
+
:text="{
|
|
45
|
+
title: {
|
|
46
|
+
chain: 'Switch Chain',
|
|
47
|
+
requesting: 'Confirm In Wallet',
|
|
48
|
+
waiting: 'Transaction Submitted',
|
|
49
|
+
complete: 'Success!'
|
|
50
|
+
},
|
|
51
|
+
lead: {
|
|
52
|
+
chain: 'Requesting to switch chain...',
|
|
53
|
+
requesting: 'Requesting Signature...',
|
|
54
|
+
waiting: 'Checking mint Transaction...',
|
|
55
|
+
complete: `New token minted...`,
|
|
56
|
+
},
|
|
57
|
+
action: {
|
|
58
|
+
confirm: 'Mint',
|
|
59
|
+
error: 'Retry',
|
|
60
|
+
complete: 'OK',
|
|
61
|
+
},
|
|
62
|
+
}"
|
|
63
|
+
skip-confirmation
|
|
64
|
+
auto-close-success
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
</form>
|
|
68
|
+
|
|
69
|
+
</PageFrame>
|
|
70
|
+
</Authenticated>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script setup>
|
|
74
|
+
const { $wagmi } = useNuxtApp()
|
|
75
|
+
const id = useArtistId()
|
|
76
|
+
const chainId = useMainChainId()
|
|
77
|
+
|
|
78
|
+
const props = defineProps(['collection'])
|
|
79
|
+
const store = useOnchainStore()
|
|
80
|
+
const collection = computed(() => props.collection)
|
|
81
|
+
|
|
82
|
+
const mode = ref('file')
|
|
83
|
+
const ipfsCid = ref('')
|
|
84
|
+
const image = ref('')
|
|
85
|
+
const name = ref('')
|
|
86
|
+
const description = ref('')
|
|
87
|
+
|
|
88
|
+
const imageSize = ref(0)
|
|
89
|
+
const isSmall = computed(() => imageSize.value / 1024 < 10)
|
|
90
|
+
const setImage = async (file) => {
|
|
91
|
+
try {
|
|
92
|
+
image.value = await imageFileToDataUri(file)
|
|
93
|
+
} catch (e) {
|
|
94
|
+
image.value = ''
|
|
95
|
+
}
|
|
96
|
+
imageSize.value = file.size
|
|
97
|
+
}
|
|
98
|
+
watch(ipfsCid, () => {
|
|
99
|
+
const validated = validateCID(ipfsCid.value)
|
|
100
|
+
if (! validated) {
|
|
101
|
+
image.value = ''
|
|
102
|
+
} else {
|
|
103
|
+
image.value = ipfsToHttpURI(`ipfs://${validated}`)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
watch(mode, () => image.value = '')
|
|
107
|
+
|
|
108
|
+
const txFlow = ref()
|
|
109
|
+
const txFlowKey = ref(0)
|
|
110
|
+
const minting = ref(false)
|
|
111
|
+
const mint = async () => {
|
|
112
|
+
const artifact = toByteArray(image.value)
|
|
113
|
+
const artifactChunks = chunkArray(artifact, 4)
|
|
114
|
+
const multiTransactionPrepare = artifactChunks.length > 1
|
|
115
|
+
|
|
116
|
+
minting.value = true
|
|
117
|
+
|
|
118
|
+
if (multiTransactionPrepare) {
|
|
119
|
+
if (! confirm(`Due to the large artifact size, we have to split it into ${artifactChunks.length} chunks and store them in separate transactions. You will be prompted with multiple transaction requests before minting the final token.`)) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// On the first iteration we want to clear existing artifact data
|
|
124
|
+
let clearExisting = true
|
|
125
|
+
|
|
126
|
+
for (const chunk of artifactChunks) {
|
|
127
|
+
await txFlow.value.initializeRequest(() => writeContract($wagmi, {
|
|
128
|
+
abi: MINT_ABI,
|
|
129
|
+
chainId,
|
|
130
|
+
address: collection.value.address,
|
|
131
|
+
functionName: 'prepareArtifact',
|
|
132
|
+
args: [
|
|
133
|
+
collection.value.latestTokenId + 1n,
|
|
134
|
+
chunk,
|
|
135
|
+
clearExisting
|
|
136
|
+
],
|
|
137
|
+
}))
|
|
138
|
+
|
|
139
|
+
// Make sure to rerender the tx flow component
|
|
140
|
+
txFlowKey.value ++
|
|
141
|
+
|
|
142
|
+
// On following iterations we want to keep existing artifact data
|
|
143
|
+
clearExisting = false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const receipt = await txFlow.value.initializeRequest(() => writeContract($wagmi, {
|
|
148
|
+
abi: MINT_ABI,
|
|
149
|
+
chainId,
|
|
150
|
+
address: collection.value.address,
|
|
151
|
+
functionName: 'create',
|
|
152
|
+
args: [
|
|
153
|
+
name.value,
|
|
154
|
+
description.value,
|
|
155
|
+
multiTransactionPrepare ? [] : artifact,
|
|
156
|
+
0,
|
|
157
|
+
0n,
|
|
158
|
+
],
|
|
159
|
+
}))
|
|
160
|
+
|
|
161
|
+
const logs = receipt.logs.map(log => decodeEventLog({
|
|
162
|
+
abi: MINT_ABI,
|
|
163
|
+
data: log.data,
|
|
164
|
+
topics: log.topics,
|
|
165
|
+
strict: false,
|
|
166
|
+
}))
|
|
167
|
+
|
|
168
|
+
const mintedEvent = logs.find(log => log.eventName === 'TransferSingle')
|
|
169
|
+
|
|
170
|
+
await store.fetchToken(collection.value.address, mintedEvent.args.id)
|
|
171
|
+
|
|
172
|
+
// Force update the collection mint ID
|
|
173
|
+
store.collections[collection.value.address].latestTokenId = mintedEvent.args.id
|
|
174
|
+
|
|
175
|
+
await navigateTo({
|
|
176
|
+
name: 'id-collection-tokenId',
|
|
177
|
+
params: { id: id.value, collection: collection.value.address, tokenId: mintedEvent.args.id }
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
minting.value = false
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const subdomain = useSubdomain()
|
|
184
|
+
const isMe = useIsMe()
|
|
185
|
+
|
|
186
|
+
const breadcrumb = computed(() => {
|
|
187
|
+
const path = subdomain.value || isMe.value ? [] : [
|
|
188
|
+
{
|
|
189
|
+
text: store.displayName(id.value),
|
|
190
|
+
to: { name: 'id', params: { id: id.value } }
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
return [
|
|
195
|
+
...path,
|
|
196
|
+
{
|
|
197
|
+
text: `${ collection.value.name }`,
|
|
198
|
+
to: { name: 'id-collection', params: { id: id.value, collection: collection.value.address } }
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
text: `New Mint`
|
|
202
|
+
},
|
|
203
|
+
]
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
useMetaData({
|
|
207
|
+
title: `Mint New Token | ${collection.value.name}`,
|
|
208
|
+
})
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<style scoped>
|
|
212
|
+
#mint-token {
|
|
213
|
+
display: grid;
|
|
214
|
+
|
|
215
|
+
@media (--md) {
|
|
216
|
+
grid-template-columns: 40% 1fr;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@media (--lg) {
|
|
220
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.preview {
|
|
225
|
+
height: 100%;
|
|
226
|
+
place-content: center;
|
|
227
|
+
|
|
228
|
+
.image,
|
|
229
|
+
svg {
|
|
230
|
+
border-radius: var(--border-radius);
|
|
231
|
+
border: var(--border);
|
|
232
|
+
margin-bottom: var(--spacer-sm);
|
|
233
|
+
width: 100%;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
h1 {
|
|
237
|
+
display: flex;
|
|
238
|
+
gap: var(--spacer-sm);
|
|
239
|
+
align-items: baseline;
|
|
240
|
+
font-size: var(--font-lg);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
p {
|
|
244
|
+
color: var(--muted);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
form {
|
|
249
|
+
width: 100%;
|
|
250
|
+
|
|
251
|
+
> div {
|
|
252
|
+
display: grid;
|
|
253
|
+
gap: var(--spacer);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
select.choose-mode {
|
|
258
|
+
width: fit-content;
|
|
259
|
+
}
|
|
260
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Loading v-if="loading" />
|
|
3
|
+
<NuxtPage v-else :collection="collection" />
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup>
|
|
7
|
+
const route = useRoute()
|
|
8
|
+
const address = computed(() => route.params.collection.toLowerCase())
|
|
9
|
+
|
|
10
|
+
const store = useOnchainStore()
|
|
11
|
+
const loading = ref(true)
|
|
12
|
+
const collection = ref(null)
|
|
13
|
+
|
|
14
|
+
const load = async () => {
|
|
15
|
+
loading.value = true
|
|
16
|
+
|
|
17
|
+
collection.value = await store.fetchCollection(address.value)
|
|
18
|
+
|
|
19
|
+
loading.value = false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onMounted(() => load())
|
|
23
|
+
watch(address, () => load())
|
|
24
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Authenticated>
|
|
3
|
+
<PageFrame :title="[
|
|
4
|
+
{
|
|
5
|
+
text: `Add existing`
|
|
6
|
+
}
|
|
7
|
+
]">
|
|
8
|
+
<form @submit.stop.prevent="add">
|
|
9
|
+
<FormInput v-model="address" placeholder="Contract Address (0x...)" />
|
|
10
|
+
|
|
11
|
+
<Button>Add</Button>
|
|
12
|
+
</form>
|
|
13
|
+
</PageFrame>
|
|
14
|
+
</Authenticated>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup>
|
|
18
|
+
const id = useArtistId()
|
|
19
|
+
|
|
20
|
+
const address = ref('')
|
|
21
|
+
|
|
22
|
+
const add = async () => {
|
|
23
|
+
if (! isAddress(address.value)) {
|
|
24
|
+
alert('Not an address')
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await navigateTo(`/${id.value}/${address.value}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
useMetaData({
|
|
32
|
+
title: `Add Existing Collection`,
|
|
33
|
+
})
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<style scoped>
|
|
37
|
+
form {
|
|
38
|
+
width: 100%;
|
|
39
|
+
}
|
|
40
|
+
</style>
|