@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.
@@ -1,202 +1,19 @@
1
1
  <template>
2
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
- <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
- <form @submit.stop.prevent="mint" class="card">
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
- const { $wagmi } = useNuxtApp()
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 mode = ref('file')
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
- artifact: string
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
+ }