@visualizevalue/mint-app-base 0.1.47 → 0.1.49
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/app.config.ts +12 -0
- package/components/CodeEditor.client.vue +49 -0
- package/components/Collection/Actions.vue +19 -0
- package/components/Collection/Intro.vue +1 -12
- package/components/Embed.vue +33 -0
- package/components/Form/Group.vue +1 -1
- package/components/Form/SelectFile.vue +4 -0
- package/components/Icon.vue +1 -0
- package/components/Mint/Action.client.vue +51 -0
- package/components/Mint/Detail.client.vue +49 -0
- package/components/Mint/Preview.client.vue +58 -0
- package/components/Mint/Renderer/Base.client.vue +107 -0
- package/components/Mint/Renderer/P5.client.vue +67 -0
- package/components/Mint/SelectRenderer.client.vue +52 -0
- package/components/Page/Frame.vue +1 -0
- package/components/Renderer/InstallButton.vue +62 -0
- package/components/Renderer/InstallCustom.client.vue +91 -0
- package/components/Renderer/Overview.client.vue +75 -0
- package/components/Renderer/OverviewCard.vue +32 -0
- package/components/Tabs.vue +37 -0
- package/components/Token/Detail.client.vue +2 -1
- package/components/Token/OverviewCard.vue +2 -1
- package/components/TransactionFlow.vue +5 -3
- package/composables/collections.ts +50 -4
- package/composables/createMint.ts +155 -0
- package/index.d.ts +11 -0
- package/nuxt.config.ts +1 -1
- package/package.json +4 -1
- package/pages/[id]/[collection]/mint.vue +5 -240
- package/pages/[id]/[collection]/renderers.vue +43 -0
- package/utils/abis.ts +6 -1
- package/utils/p5Script.ts +33 -0
- package/utils/types.ts +13 -2
|
@@ -1,202 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Authenticated>
|
|
3
|
-
<PageFrame :title="breadcrumb" class="inset wide"
|
|
4
|
-
<article class="preview">
|
|
5
|
-
<Image v-if="image" :src="image" alt="Preview" />
|
|
6
|
-
<ImagePreview v-else />
|
|
7
|
-
<h1 :class="{ '': !name }">{{ name || 'Token' }}</h1>
|
|
8
|
-
<p :class="{ '': !description }">
|
|
9
|
-
{{ description || 'No description' }}
|
|
10
|
-
</p>
|
|
11
|
-
</article>
|
|
3
|
+
<PageFrame :title="breadcrumb" class="inset wide">
|
|
12
4
|
|
|
13
|
-
<
|
|
14
|
-
<Actions>
|
|
15
|
-
<select class="select 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="ar" title="Arweave">ARWEAVE</option>
|
|
19
|
-
<option value="http" title="Hypertext Transfer Protocol" disabled>HTTP</option>
|
|
20
|
-
<option value="svg" title="Scalable Vector Graphic" disabled>SVG</option>
|
|
21
|
-
</select>
|
|
22
|
-
</Actions>
|
|
23
|
-
|
|
24
|
-
<div>
|
|
25
|
-
<div v-if="mode === 'file'">
|
|
26
|
-
<FormSelectFile @change="setImage" />
|
|
27
|
-
<p v-if="! isSmall" class="muted">
|
|
28
|
-
<small>
|
|
29
|
-
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>.
|
|
30
|
-
If it is larger than what we can store within one transaction, the token creation will be split up into multiple transactions.
|
|
31
|
-
</small>
|
|
32
|
-
</p>
|
|
33
|
-
</div>
|
|
34
|
-
<FormInput v-else-if="mode === 'ipfs'" v-model="ipfsCid" placeholder="CID (qmx...)" prefix="ipfs://" required />
|
|
35
|
-
<FormInput v-else-if="mode === 'ar'" v-model="arTxId" placeholder="TX ID (frV...)" prefix="ar://" required />
|
|
36
|
-
|
|
37
|
-
<FormInput v-model="name" placeholder="Title" required />
|
|
38
|
-
<FormInput v-model="description" placeholder="Description" />
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<Actions>
|
|
42
|
-
<Button>Mint</Button>
|
|
43
|
-
</Actions>
|
|
44
|
-
<TransactionFlow
|
|
45
|
-
ref="txFlow"
|
|
46
|
-
:text="{
|
|
47
|
-
title: {
|
|
48
|
-
chain: 'Switch Chain',
|
|
49
|
-
requesting: 'Confirm In Wallet',
|
|
50
|
-
waiting: 'Transaction Submitted',
|
|
51
|
-
complete: 'Success!'
|
|
52
|
-
},
|
|
53
|
-
lead: {
|
|
54
|
-
chain: 'Requesting to switch chain...',
|
|
55
|
-
requesting: 'Requesting Signature...',
|
|
56
|
-
waiting: 'Checking mint Transaction...',
|
|
57
|
-
complete: `New token minted...`,
|
|
58
|
-
},
|
|
59
|
-
action: {
|
|
60
|
-
confirm: 'Mint',
|
|
61
|
-
error: 'Retry',
|
|
62
|
-
complete: 'OK',
|
|
63
|
-
},
|
|
64
|
-
}"
|
|
65
|
-
skip-confirmation
|
|
66
|
-
auto-close-success
|
|
67
|
-
/>
|
|
68
|
-
|
|
69
|
-
</form>
|
|
5
|
+
<MintDetail :collection="collection" class="borderless" />
|
|
70
6
|
|
|
71
7
|
</PageFrame>
|
|
72
8
|
</Authenticated>
|
|
73
9
|
</template>
|
|
74
10
|
|
|
75
11
|
<script setup>
|
|
76
|
-
|
|
77
|
-
const id = useArtistId()
|
|
78
|
-
const chainId = useMainChainId()
|
|
79
|
-
|
|
12
|
+
// Prepare breadcrumbs
|
|
80
13
|
const props = defineProps(['collection'])
|
|
81
|
-
const store = useOnchainStore()
|
|
82
14
|
const collection = computed(() => props.collection)
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const ipfsCid = ref('')
|
|
86
|
-
// TODO: Rework to plugin architecture. Or at least per renderer logic.
|
|
87
|
-
const arTxId= ref('')
|
|
88
|
-
const image = ref('')
|
|
89
|
-
const name = ref('')
|
|
90
|
-
const description = ref('')
|
|
91
|
-
|
|
92
|
-
const imageSize = ref(0)
|
|
93
|
-
const isSmall = computed(() => imageSize.value / 1024 < 10)
|
|
94
|
-
const setImage = async (file) => {
|
|
95
|
-
try {
|
|
96
|
-
image.value = await imageFileToDataUri(file)
|
|
97
|
-
imageSize.value = file.size
|
|
98
|
-
} catch (e) {
|
|
99
|
-
image.value = ''
|
|
100
|
-
imageSize.value = 0
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
watch(ipfsCid, () => {
|
|
104
|
-
const validated = validateCID(ipfsCid.value)
|
|
105
|
-
if (! validated) {
|
|
106
|
-
image.value = ''
|
|
107
|
-
} else {
|
|
108
|
-
image.value = ipfsToHttpURI(`ipfs://${validated}`)
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
watch(arTxId, () => {
|
|
112
|
-
image.value = `https://arweave.net/${arTxId.value}`
|
|
113
|
-
})
|
|
114
|
-
watch(mode, () => image.value = '')
|
|
115
|
-
|
|
116
|
-
const txFlow = ref()
|
|
117
|
-
const txFlowKey = ref(0)
|
|
118
|
-
const minting = ref(false)
|
|
119
|
-
const mint = async () => {
|
|
120
|
-
if (! image.value) {
|
|
121
|
-
alert(`Empty image data. Please try again.`)
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const artifact = toByteArray(image.value)
|
|
126
|
-
const artifactChunks = chunkArray(artifact, 4)
|
|
127
|
-
const multiTransactionPrepare = artifactChunks.length > 1
|
|
128
|
-
|
|
129
|
-
minting.value = true
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
if (multiTransactionPrepare) {
|
|
133
|
-
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.`)) {
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// On the first iteration we want to clear existing artifact data
|
|
138
|
-
let clearExisting = true
|
|
139
|
-
|
|
140
|
-
for (const chunk of artifactChunks) {
|
|
141
|
-
await txFlow.value.initializeRequest(() => writeContract($wagmi, {
|
|
142
|
-
abi: MINT_ABI,
|
|
143
|
-
chainId,
|
|
144
|
-
address: collection.value.address,
|
|
145
|
-
functionName: 'prepareArtifact',
|
|
146
|
-
args: [
|
|
147
|
-
collection.value.latestTokenId + 1n,
|
|
148
|
-
chunk,
|
|
149
|
-
clearExisting
|
|
150
|
-
],
|
|
151
|
-
}))
|
|
152
|
-
|
|
153
|
-
// Make sure to rerender the tx flow component
|
|
154
|
-
txFlowKey.value ++
|
|
155
|
-
|
|
156
|
-
// On following iterations we want to keep existing artifact data
|
|
157
|
-
clearExisting = false
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const receipt = await txFlow.value.initializeRequest(() => writeContract($wagmi, {
|
|
162
|
-
abi: MINT_ABI,
|
|
163
|
-
chainId,
|
|
164
|
-
address: collection.value.address,
|
|
165
|
-
functionName: 'create',
|
|
166
|
-
args: [
|
|
167
|
-
name.value,
|
|
168
|
-
description.value,
|
|
169
|
-
multiTransactionPrepare ? [] : artifact,
|
|
170
|
-
0,
|
|
171
|
-
0n,
|
|
172
|
-
],
|
|
173
|
-
}))
|
|
174
|
-
|
|
175
|
-
const logs = receipt.logs.map(log => decodeEventLog({
|
|
176
|
-
abi: MINT_ABI,
|
|
177
|
-
data: log.data,
|
|
178
|
-
topics: log.topics,
|
|
179
|
-
strict: false,
|
|
180
|
-
}))
|
|
181
|
-
|
|
182
|
-
const mintedEvent = logs.find(log => log.eventName === 'TransferSingle')
|
|
183
|
-
|
|
184
|
-
await store.fetchToken(collection.value.address, mintedEvent.args.id)
|
|
185
|
-
|
|
186
|
-
// Force update the collection mint ID
|
|
187
|
-
store.collections[collection.value.address].latestTokenId = mintedEvent.args.id
|
|
188
|
-
|
|
189
|
-
await navigateTo({
|
|
190
|
-
name: 'id-collection-tokenId',
|
|
191
|
-
params: { id: id.value, collection: collection.value.address, tokenId: mintedEvent.args.id }
|
|
192
|
-
})
|
|
193
|
-
} catch (e) {
|
|
194
|
-
console.error(e)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
minting.value = false
|
|
198
|
-
}
|
|
199
|
-
|
|
15
|
+
const id = useArtistId()
|
|
16
|
+
const store = useOnchainStore()
|
|
200
17
|
const subdomain = useSubdomain()
|
|
201
18
|
const isMe = useIsMe()
|
|
202
19
|
|
|
@@ -225,55 +42,3 @@ useMetaData({
|
|
|
225
42
|
})
|
|
226
43
|
</script>
|
|
227
44
|
|
|
228
|
-
<style scoped>
|
|
229
|
-
#mint-token {
|
|
230
|
-
display: grid;
|
|
231
|
-
|
|
232
|
-
@media (--md) {
|
|
233
|
-
grid-template-columns: 40% 1fr;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
@media (--lg) {
|
|
237
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.preview {
|
|
242
|
-
height: 100%;
|
|
243
|
-
place-content: center;
|
|
244
|
-
|
|
245
|
-
.image,
|
|
246
|
-
svg {
|
|
247
|
-
margin-bottom: var(--spacer-sm);
|
|
248
|
-
width: 100%;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
svg {
|
|
252
|
-
box-shadow: var(--border-shadow);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
h1 {
|
|
256
|
-
display: flex;
|
|
257
|
-
gap: var(--spacer-sm);
|
|
258
|
-
align-items: baseline;
|
|
259
|
-
font-size: var(--font-lg);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
p {
|
|
263
|
-
color: var(--muted);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
form {
|
|
268
|
-
width: 100%;
|
|
269
|
-
|
|
270
|
-
> div {
|
|
271
|
-
display: grid;
|
|
272
|
-
gap: var(--spacer);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
select.choose-mode {
|
|
277
|
-
width: fit-content;
|
|
278
|
-
}
|
|
279
|
-
</style>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Authenticated>
|
|
3
|
+
<PageFrame :title="breadcrumb" class="inset" id="manage-renderers">
|
|
4
|
+
<RendererOverview :collection="collection" />
|
|
5
|
+
</PageFrame>
|
|
6
|
+
</Authenticated>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup>
|
|
10
|
+
const id = useArtistId()
|
|
11
|
+
|
|
12
|
+
const props = defineProps(['collection'])
|
|
13
|
+
const store = useOnchainStore()
|
|
14
|
+
const collection = computed(() => props.collection)
|
|
15
|
+
|
|
16
|
+
const subdomain = useSubdomain()
|
|
17
|
+
const isMe = useIsMe()
|
|
18
|
+
|
|
19
|
+
const breadcrumb = computed(() => {
|
|
20
|
+
const path = subdomain.value || isMe.value ? [] : [
|
|
21
|
+
{
|
|
22
|
+
text: store.displayName(id.value),
|
|
23
|
+
to: { name: 'id', params: { id: id.value } }
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
return [
|
|
28
|
+
...path,
|
|
29
|
+
{
|
|
30
|
+
text: `${ collection.value.name }`,
|
|
31
|
+
to: { name: 'id-collection', params: { id: id.value, collection: collection.value.address } }
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
text: `Manage Renderers`
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
useMetaData({
|
|
40
|
+
title: `Manage Renderers | ${collection.value.name}`,
|
|
41
|
+
})
|
|
42
|
+
</script>
|
|
43
|
+
|
package/utils/abis.ts
CHANGED
|
@@ -19,7 +19,6 @@ export const FACTORY_ABI = parseAbi([
|
|
|
19
19
|
'function version() pure returns (uint256)'
|
|
20
20
|
])
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
export const MINT_ABI = parseAbi([
|
|
24
23
|
'error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId)',
|
|
25
24
|
'error ERC1155InvalidApprover(address approver)',
|
|
@@ -75,3 +74,9 @@ export const MINT_ABI = parseAbi([
|
|
|
75
74
|
'function version() view returns (uint256)',
|
|
76
75
|
'function withdraw()'
|
|
77
76
|
])
|
|
77
|
+
|
|
78
|
+
export const RENDERER_ABI = parseAbi([
|
|
79
|
+
'function name() external pure returns (string memory)',
|
|
80
|
+
'function version() external pure returns (uint version)',
|
|
81
|
+
])
|
|
82
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const DEFAULT_P5_SCRIPT =
|
|
2
|
+
`let dimension
|
|
3
|
+
|
|
4
|
+
function setup() {
|
|
5
|
+
dimension = Math.min(windowWidth, windowHeight)
|
|
6
|
+
|
|
7
|
+
createCanvas(dimension, dimension)
|
|
8
|
+
|
|
9
|
+
background(150)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function draw() {
|
|
13
|
+
// ...
|
|
14
|
+
}
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
export const getP5Html = (title: string, script: string, isUri: boolean = false) =>
|
|
18
|
+
`<html>
|
|
19
|
+
<head>
|
|
20
|
+
<title>${title}</title>
|
|
21
|
+
<link rel="stylesheet" href="data:text/css;base64,aHRtbHtoZWlnaHQ6MTAwJX1ib2R5e21pbi1oZWlnaHQ6MTAwJTttYXJnaW46MDtwYWRkaW5nOjB9Y2FudmFze3BhZGRpbmc6MDttYXJnaW46YXV0bztkaXNwbGF5OmJsb2NrO3Bvc2l0aW9uOmFic29sdXRlO3RvcDowO2JvdHRvbTowO2xlZnQ6MDtyaWdodDowfQ==">
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
|
|
25
|
+
${
|
|
26
|
+
isUri ? `<script src="${script}"></script>` : `<script>${script}</script>`
|
|
27
|
+
}
|
|
28
|
+
</body>
|
|
29
|
+
</html>`
|
|
30
|
+
|
|
31
|
+
export const getP5HtmlUri = (title: string, script: string, isUri: boolean = false) =>
|
|
32
|
+
`data:text/html;base64,${Buffer.from(getP5Html(title, script, isUri)).toString('base64')}`
|
|
33
|
+
|
package/utils/types.ts
CHANGED
|
@@ -34,8 +34,9 @@ export interface Collection {
|
|
|
34
34
|
description: string
|
|
35
35
|
initBlock: bigint
|
|
36
36
|
latestTokenId: bigint
|
|
37
|
-
tokens: { [key: string]: Token }
|
|
38
37
|
balance: bigint
|
|
38
|
+
tokens: { [key: string]: Token }
|
|
39
|
+
renderers: Renderer[]
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export interface Token {
|
|
@@ -43,7 +44,9 @@ export interface Token {
|
|
|
43
44
|
tokenId: bigint
|
|
44
45
|
name: string
|
|
45
46
|
description: string
|
|
46
|
-
|
|
47
|
+
image: string,
|
|
48
|
+
animationUrl?: string,
|
|
49
|
+
scriptUrl?: string,
|
|
47
50
|
untilBlock: bigint
|
|
48
51
|
mintsFetchedUntilBlock: bigint
|
|
49
52
|
mintsBackfilledUntilBlock: bigint
|
|
@@ -60,3 +63,11 @@ export interface MintEvent {
|
|
|
60
63
|
amount: bigint
|
|
61
64
|
price: bigint
|
|
62
65
|
}
|
|
66
|
+
|
|
67
|
+
export interface Renderer {
|
|
68
|
+
address: `0x${string}`
|
|
69
|
+
name: string
|
|
70
|
+
description?: string
|
|
71
|
+
component?: string
|
|
72
|
+
version: bigint
|
|
73
|
+
}
|