Skip to content

Commit f16d6e9

Browse files
authored
Merge pull request #99 from kodadot/one-by-one
Ability to mint single NFT
2 parents e0303bd + 734b98a commit f16d6e9

File tree

6 files changed

+98
-20
lines changed

6 files changed

+98
-20
lines changed

dashboard/src/components/rmrk/Create/CreateCollection.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import { Collection, CollectionMetadata } from '../service/scheme';
8585
import { pinFile, pinJson, unSanitizeIpfsUrl } from '@/pinata';
8686
import { decodeAddress } from '@polkadot/keyring';
8787
import { u8aToHex } from '@polkadot/util';
88+
import { generateId } from '@/components/rmrk/service/Consolidator'
8889
8990
9091
const components = {
@@ -106,7 +107,7 @@ export default class CreateCollection extends Mixins(SubscribeMixin) {
106107
private password: string = '';
107108
108109
get rmrkId(): string {
109-
return this.generateId(this.accountIdToPubKey);
110+
return generateId(this.accountId, this.rmrkMint?.symbol || '')
110111
}
111112
112113
get accountIdToPubKey() {

dashboard/src/components/rmrk/Create/CreateToken.vue

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@
33
<div class="box">
44
<b-loading is-full-page v-model="isLoading" :can-cancel="true"></b-loading>
55
<AccountSelect :label="$i18n.t('Account')" v-model="accountId" />
6-
<b-field grouped v-if="accountId" :label="$i18n.t('Collection')">
6+
<template v-if="accountId">
7+
<b-switch v-model="oneByOne"
8+
passive-type="is-dark"
9+
:rounded="false">
10+
{{ oneByOne ? 'Single NFT' : 'NFT(s) in collection' }}
11+
</b-switch>
12+
<b-field grouped v-if="!oneByOne" :label="$i18n.t('Collection')">
713
<b-select placeholder="Select a collection" v-model="selectedCollection" expanded>
814
<option v-for="option in data" :value="option" :key="option.id">
915
{{ option.name }} {{ option.id }}
1016
</option>
1117
</b-select>
1218
<Tooltip :label="$i18n.t('Select collection where do you want mint your token')" />
1319
</b-field>
20+
<b-field v-else grouped :label="$i18n.t('Symbol')">
21+
<b-input v-model="symbol" expanded></b-input>
22+
<Tooltip :label="$i18n.t('Symbol you want to trade it under')" />
23+
</b-field>
24+
</template>
1425
<b-field>
1526
<PasswordInput v-if="canSubmit" v-model="password" :account="accountId" />
1627
</b-field>
@@ -28,7 +39,7 @@
2839
<b-field grouped>
2940
<b-field position="is-left" expanded>
3041
<b-button
31-
v-if="selectedCollection"
42+
v-if="accountId && (selectedCollection || singleNFTValid)"
3243
type="is-info"
3344
icon-left="plus"
3445
@click="handleAdd"
@@ -64,13 +75,13 @@ import {
6475
Collection,
6576
NFT,
6677
NFTMetadata,
67-
NFTWithMeta,
68-
getNftId
6978
} from '../service/scheme';
7079
import { pinFile, pinJson, unSanitizeIpfsUrl } from '@/pinata';
7180
import PasswordInput from '@/components/shared/PasswordInput.vue';
7281
import slugify from 'slugify'
7382
import { fetchCollectionMetadata } from '../utils';
83+
import { generateId } from '@/components/rmrk/service/Consolidator'
84+
import NFTUtils from '../service/NftUtils';
7485
7586
const shouldUpdate = (val: string, oldVal: string) => val && val !== oldVal;
7687
@@ -97,6 +108,8 @@ export default class CreateToken extends Vue {
97108
private isLoading: boolean = false;
98109
private password: string = '';
99110
private alreadyMinted = 0;
111+
private oneByOne: boolean = true;
112+
private symbol: string = '';
100113
101114
@Watch('accountId')
102115
hasAccount(value: string, oldVal: string) {
@@ -136,8 +149,13 @@ export default class CreateToken extends Vue {
136149
return this.added.length
137150
}
138151
152+
get singleNFTValid() {
153+
return this.oneByOne && this.symbol
154+
}
155+
139156
get disabled() {
140-
return this.selectedCollection?.max === this.added.length + this.alreadyMinted;
157+
const max = this.oneByOne ? 1 : this.selectedCollection?.max
158+
return max === this.added.length + this.alreadyMinted;
141159
}
142160
143161
private handleUpdate(item: { view: NFTAndMeta; index: number }) {
@@ -162,8 +180,6 @@ export default class CreateToken extends Vue {
162180
...nftForMint,
163181
metadata: metaHash,
164182
currentOwner: this.accountId,
165-
// id,
166-
// _id: id,
167183
transferable: Number(nftForMint.transferable),
168184
instance: slugify(nftForMint.name, '_').toUpperCase()
169185
};
@@ -209,16 +225,21 @@ export default class CreateToken extends Vue {
209225
return unSanitizeIpfsUrl(metaHash);
210226
}
211227
212-
private async submit() {
228+
protected async submit() {
213229
this.isLoading = true;
214230
const { api } = Connector.getInstance();
215-
const remarks: string[] = await Promise.all(this.added
216-
.map(this.makeItSexy)
217-
.map(async mint => this.toMintFormat(await mint)
218-
));
219-
console.log('remarks', remarks);
231+
const nfts: NFT[] = await Promise.all(this.added.map(this.makeItSexy));
232+
233+
const remarks: string[] = nfts.map(this.toMintFormat)
234+
235+
if (!remarks.length) {
236+
throw new RangeError('Unable to process empty NFTs!')
237+
}
238+
239+
const firstNFT = nfts[0];
240+
const collectionToMint: string[] = this.oneByOne ? [NFTUtils.encodeCollection(NFTUtils.collectionFromNFT(this.symbol, firstNFT, this.version), this.version)] : []
220241
221-
const batchMethods: any[] = remarks.map(this.toRemark)
242+
const batchMethods = [...collectionToMint.map(this.toRemark), ...remarks.map(this.toRemark)]
222243
console.log('batchMethods', batchMethods)
223244
const rmrkService = getInstance();
224245
@@ -233,7 +254,7 @@ export default class CreateToken extends Vue {
233254
execResultValue(tx)
234255
const header = await api.rpc.chain.getHeader(result.status.asFinalized);
235256
const blockNumber = header.number.toString();
236-
remarks.forEach(async (rmrk, index) => {
257+
for (const [index, rmrk] of [...collectionToMint, ...remarks].entries()) {
237258
this.isLoading = true;
238259
try {
239260
const res = await rmrkService?.resolve(rmrk, this.accountId, blockNumber)
@@ -243,7 +264,7 @@ export default class CreateToken extends Vue {
243264
console.warn(`Failed Indexing ${index} with err ${e}`);
244265
}
245266
this.isLoading = false;
246-
})
267+
}
247268
}
248269
});
249270
console.warn('TX IN', tx);
@@ -259,7 +280,7 @@ export default class CreateToken extends Vue {
259280
260281
protected handleAdd() {
261282
const rmrk = emptyObject<NFTAndMeta>();
262-
rmrk.collection = this.selectedCollection?.id || '';
283+
rmrk.collection = this.oneByOne ? generateId(this.accountId, this.symbol) : this.selectedCollection?.id || '';
263284
rmrk.sn = this.calculateSerialNumber(this.added.length);
264285
rmrk.meta = emptyObject<NFTMetadata>();
265286
rmrk.transferable = 1;

dashboard/src/components/rmrk/Gallery/GalleryItem.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
class="subtitle is-size-5">
2929
{{ nft.description }}
3030
</p>
31-
<b-skeleton count="3" size="is-large" :active="isLoading"></b-skeleton>
31+
<b-skeleton :count="3" size="is-large" :active="isLoading"></b-skeleton>
3232
</p>
3333
</div>
3434
</div>

dashboard/src/components/rmrk/service/Consolidator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ function accountIdToPubKey(accountId: string) {
105105
return (accountId && u8aToHex(decodeAddress(accountId))) || '';
106106
}
107107

108-
export function generateId(pubkey: string, symbol: string): string {
108+
export function generateId(caller: string, symbol: string): string {
109+
const pubkey = caller.startsWith('0x') ? caller : accountIdToPubKey(caller);
109110
return (
110111
pubkey?.substr(2, 10) +
111112
pubkey?.substring(pubkey.length - 8) +

dashboard/src/components/rmrk/service/NftUtils.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { hexToString, isHex } from '@polkadot/util';
22
import { RmrkEvent, RMRK, RmrkInteraction } from '../types';
33
import { SQUARE } from '../utils'
4+
import { generateId } from '../service/Consolidator'
5+
import { Collection, NFT } from './scheme';
46

57
class NFTUtils {
68
public static decode(value: string) {
@@ -22,8 +24,51 @@ class NFTUtils {
2224
} catch(e) {
2325
throw e
2426
}
27+
}
28+
29+
public static toString(rmrkType: NFT | Collection, version: string = 'RMRK1.0.0'): string {
30+
if (NFTUtils.isCollection(rmrkType)) {
31+
return NFTUtils.encodeCollection(rmrkType, version)
32+
}
33+
34+
if (NFTUtils.isNFT(rmrkType)) {
35+
return NFTUtils.encodeNFT(rmrkType, version)
36+
}
37+
38+
return ''
39+
}
2540

41+
public static encodeCollection(collection: Collection, version: string) {
42+
return `RMRK::MINT::${version}::${encodeURIComponent(
43+
JSON.stringify(collection)
44+
)}`;
45+
}
46+
47+
protected static encodeNFT(nft: NFT, version: string) {
48+
return `RMRK::MINTNFT::${version}::${encodeURIComponent(
49+
JSON.stringify(nft)
50+
)}`
51+
}
52+
53+
public static collectionFromNFT(symbol: string, nft: NFT, version: string = 'RMRK1.0.0'): Collection {
54+
return {
55+
id: generateId(nft.currentOwner, symbol),
56+
_id: '',
57+
symbol,
58+
issuer: nft.currentOwner,
59+
version,
60+
name: nft.name,
61+
max: 1,
62+
metadata: nft.metadata
63+
}
64+
}
65+
66+
public static isCollection(object: Collection | NFT): object is Collection {
67+
return 'issuer' in object && 'symbol' in object;
68+
}
2669

70+
public static isNFT(object: Collection | NFT): object is NFT {
71+
return 'currentOwner' in object && 'instance' in object;
2772
}
2873

2974
public static decodeAndConvert(rmrkString: string) {

dashboard/src/components/rmrk/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ import { RmrkType } from './service/RmrkService';
1313
import { NFTMetadata } from './service/scheme';
1414

1515
export const SQUARE = '::'
16+
export const DEFAULT_IPFS_PROVIDER = 'https://ipfs.io/';
17+
18+
export const ipfsProviders: Record<string, string> = {
19+
pinata: 'https://gateway.pinata.cloud/',
20+
cloudflare: 'https://cloudflare-ipfs.com/',
21+
ipfs: DEFAULT_IPFS_PROVIDER,
22+
'': 'https://cloudflare-ipfs.com/'
23+
}
24+
25+
const resolveProvider = (key?: string) => ipfsProviders[key || '']
1626

1727
export const zip = <T1, T2, T3>(a: T1[], b: T2[], cb?: (el: (T1 | T2)[]) => T3): T3[] | (T1 | T2)[][] => {
1828
const res = a.map((k, i) => [k, b[i]]);

0 commit comments

Comments
 (0)