|
| 1 | +<template> |
| 2 | + <div> |
| 3 | + <Loader v-model="isLoading" :status="status" /> |
| 4 | + <BaseCollectionForm v-bind.sync="base"> |
| 5 | + <template v-slot:main> |
| 6 | + <b-field class="mb-5" /> |
| 7 | + </template> |
| 8 | + <template v-slot:footer> |
| 9 | + <CustomAttributeInput |
| 10 | + :max="10" |
| 11 | + v-model="attributes" |
| 12 | + class="mb-3" |
| 13 | + visible="collapse.collection.attributes.show" |
| 14 | + hidden="collapse.collection.attributes.hide" /> |
| 15 | + <b-field> |
| 16 | + <p class="has-text-weight-medium is-size-6 has-text-warning"> |
| 17 | + {{ $t('mint.deposit') }}: |
| 18 | + <Money :value="collectionDeposit" inline /> |
| 19 | + </p> |
| 20 | + </b-field> |
| 21 | + <SubmitButton |
| 22 | + label="create collection" |
| 23 | + :disabled="disabled" |
| 24 | + :loading="isLoading" |
| 25 | + @click="submit" /> |
| 26 | + </template> |
| 27 | + </BaseCollectionForm> |
| 28 | + </div> |
| 29 | +</template> |
| 30 | + |
| 31 | +<script lang="ts"> |
| 32 | +import { Attribute } from '@/components/rmrk/types' |
| 33 | +import { |
| 34 | + getclassDeposit, |
| 35 | + getMetadataDeposit, |
| 36 | +} from '@/components/unique/apiConstants' |
| 37 | +import { getRandomValues, hasEnoughToken } from '@/components/unique/utils' |
| 38 | +import onApiConnect from '@/utils/api/general' |
| 39 | +import { uploadDirect } from '@/utils/directUpload' |
| 40 | +import formatBalance from '@/utils/formatBalance' |
| 41 | +import { mapToId } from '@/utils/mappers' |
| 42 | +import AuthMixin from '@/utils/mixins/authMixin' |
| 43 | +import ChainMixin from '@/utils/mixins/chainMixin' |
| 44 | +import MetaTransactionMixin from '@/utils/mixins/metaMixin' |
| 45 | +import PrefixMixin from '@/utils/mixins/prefixMixin' |
| 46 | +import { notificationTypes, showNotification } from '@/utils/notification' |
| 47 | +import { pinJson, PinningKey } from '@/utils/pinning' |
| 48 | +import resolveQueryPath from '@/utils/queryPathResolver' |
| 49 | +import { getImageTypeSafe, pinImageSafe } from '@/utils/safePin' |
| 50 | +import { estimate } from '@/utils/transactionExecutor' |
| 51 | +import { unwrapSafe } from '@/utils/uniquery' |
| 52 | +import { createMetadata, unSanitizeIpfsUrl } from '@kodadot1/minimark' |
| 53 | +import Connector from '@kodadot1/sub-api' |
| 54 | +import { Component, mixins } from 'nuxt-property-decorator' |
| 55 | +import { dummyIpfsCid } from '@/utils/ipfs' |
| 56 | +
|
| 57 | +type BaseCollectionType = { |
| 58 | + name: string |
| 59 | + file: File | null |
| 60 | + description: string |
| 61 | +} |
| 62 | +
|
| 63 | +const components = { |
| 64 | + Loader: () => import('@/components/shared/Loader.vue'), |
| 65 | + BasicInput: () => import('@/components/shared/form/BasicInput.vue'), |
| 66 | + BaseCollectionForm: () => import('@/components/base/BaseCollectionForm.vue'), |
| 67 | + BasicSwitch: () => import('@/components/shared/form/BasicSwitch.vue'), |
| 68 | + SubmitButton: () => import('@/components/base/SubmitButton.vue'), |
| 69 | + Money: () => import('@/components/shared/format/Money.vue'), |
| 70 | + CustomAttributeInput: () => |
| 71 | + import('@/components/rmrk/Create/CustomAttributeInput.vue'), |
| 72 | +} |
| 73 | +
|
| 74 | +@Component({ components }) |
| 75 | +export default class CreateCollection extends mixins( |
| 76 | + MetaTransactionMixin, |
| 77 | + ChainMixin, |
| 78 | + AuthMixin, |
| 79 | + PrefixMixin |
| 80 | +) { |
| 81 | + private base: BaseCollectionType = { |
| 82 | + name: '', |
| 83 | + file: null, |
| 84 | + description: '', |
| 85 | + } |
| 86 | + private hasSupport = true |
| 87 | + protected collectionDeposit = '' |
| 88 | + protected id = '0' |
| 89 | + protected attributes: Attribute[] = [] |
| 90 | +
|
| 91 | + public async created() { |
| 92 | + onApiConnect(() => { |
| 93 | + const classDeposit = getclassDeposit() |
| 94 | + const metadataDeposit = getMetadataDeposit() |
| 95 | + this.collectionDeposit = (classDeposit + metadataDeposit).toString() |
| 96 | + }) |
| 97 | + } |
| 98 | +
|
| 99 | + get disabled(): boolean { |
| 100 | + const { |
| 101 | + base: { name }, |
| 102 | + accountId, |
| 103 | + } = this |
| 104 | + return !(name && accountId) |
| 105 | + } |
| 106 | +
|
| 107 | + get balance(): string { |
| 108 | + return this.$store.getters.getAuthBalance |
| 109 | + } |
| 110 | +
|
| 111 | + public async constructMeta() { |
| 112 | + const { file, name, description } = this.base |
| 113 | +
|
| 114 | + const pinningKey: PinningKey = await this.$store.dispatch( |
| 115 | + 'pinning/fetchPinningKey', |
| 116 | + this.accountId |
| 117 | + ) |
| 118 | +
|
| 119 | + const imageHash = await pinImageSafe(file, pinningKey.token) |
| 120 | + const type = getImageTypeSafe(file) |
| 121 | + const attributes = this.attributes.map((val) => ({ |
| 122 | + ...val, |
| 123 | + display_type: null, |
| 124 | + })) |
| 125 | + const meta = createMetadata( |
| 126 | + name, |
| 127 | + description, |
| 128 | + imageHash, |
| 129 | + undefined, |
| 130 | + attributes, |
| 131 | + undefined, |
| 132 | + type |
| 133 | + ) |
| 134 | + const metaHash = await pinJson(meta, imageHash) |
| 135 | +
|
| 136 | + return unSanitizeIpfsUrl(metaHash) |
| 137 | + } |
| 138 | +
|
| 139 | + protected async generateNewCollectionId(): Promise<number> { |
| 140 | + const randomNumbers = getRandomValues(10).map(String) |
| 141 | + const query = await resolveQueryPath( |
| 142 | + this.urlPrefix, |
| 143 | + 'existingCollectionList' |
| 144 | + ) |
| 145 | + const cols = this.$apollo.query({ |
| 146 | + query: query.default, |
| 147 | + client: this.urlPrefix, |
| 148 | + variables: { |
| 149 | + ids: randomNumbers, |
| 150 | + }, |
| 151 | + }) |
| 152 | + const { |
| 153 | + data: { collectionEntities }, |
| 154 | + } = await cols |
| 155 | + const collectionList = unwrapSafe(collectionEntities) |
| 156 | + const existingIds = collectionList.map(mapToId) |
| 157 | + const newId = randomNumbers.find((id) => !existingIds.includes(id)) |
| 158 | + return Number(newId) |
| 159 | + } |
| 160 | +
|
| 161 | + protected cretateArgs( |
| 162 | + randomId: number, |
| 163 | + metadata: string |
| 164 | + ): [number, { Marketplace: null }, string] { |
| 165 | + return [randomId, { Marketplace: null }, metadata] |
| 166 | + } |
| 167 | +
|
| 168 | + protected tryToEstimateTx(): Promise<string> { |
| 169 | + const { api } = Connector.getInstance() |
| 170 | + const cb = api.tx.utility.batchAll |
| 171 | + const metadata = dummyIpfsCid() |
| 172 | + const randomId = 0 |
| 173 | + const args = [this.cretateArgs(randomId, metadata)] |
| 174 | + return estimate(this.accountId, cb, args) |
| 175 | + } |
| 176 | +
|
| 177 | + protected async checkBalanceBeforeTx(): Promise<void> { |
| 178 | + const estimated = await this.tryToEstimateTx() |
| 179 | + const deposit = this.collectionDeposit |
| 180 | + const hasTokens = hasEnoughToken(this.balance, estimated, deposit) |
| 181 | + this.$consola.log('hasTokens', hasTokens) |
| 182 | + if (!hasTokens) { |
| 183 | + throw new Error( |
| 184 | + `Not enough tokens: Currently have ${formatBalance( |
| 185 | + this.balance, |
| 186 | + this.decimals, |
| 187 | + this.unit |
| 188 | + )} tokens` |
| 189 | + ) |
| 190 | + } |
| 191 | + } |
| 192 | +
|
| 193 | + protected async submit(): Promise<void> { |
| 194 | + this.isLoading = true |
| 195 | + this.status = 'loader.checkBalance' |
| 196 | +
|
| 197 | + try { |
| 198 | + // await this.checkBalanceBeforeTx() |
| 199 | + showNotification( |
| 200 | + `Creating collection ${this.base.name}`, |
| 201 | + notificationTypes.info |
| 202 | + ) |
| 203 | + this.status = 'loader.ipfs' |
| 204 | + const metadata = await this.constructMeta() |
| 205 | + // const metadata = 'ipfs://ipfs/QmaCWgK91teVsQuwLDt56m2xaUfBCCJLeCsPeJyHEenoES' |
| 206 | + const { api } = Connector.getInstance() |
| 207 | + const cb = api.tx.nft.createClass |
| 208 | + const randomId = await this.generateNewCollectionId() |
| 209 | +
|
| 210 | + const args = this.cretateArgs(randomId, metadata) |
| 211 | +
|
| 212 | + if (this.base.file) { |
| 213 | + this.$consola.log('[UPLOADING FILE]') |
| 214 | + uploadDirect(this.base.file, this.accountId).catch(this.$consola.warn) |
| 215 | + } |
| 216 | +
|
| 217 | + await this.howAboutToExecute(this.accountId, cb, args, (blockNumber) => { |
| 218 | + showNotification( |
| 219 | + `[Collection] Saved ${this.base.name} in block ${blockNumber}`, |
| 220 | + notificationTypes.success |
| 221 | + ) |
| 222 | + }) |
| 223 | + } catch (e: any) { |
| 224 | + showNotification(`[ERR] ${e}`, notificationTypes.danger) |
| 225 | + this.$consola.error(e) |
| 226 | + this.isLoading = false |
| 227 | + } |
| 228 | + } |
| 229 | +} |
| 230 | +</script> |
0 commit comments