It's the web app that helps for the NFT collection owners to prepare their collections for the data migration from the old Uniques pallet to the new NFTs pallet on Polkadot/Kusama Asset Hub.
For the NFT holders this app simplifies the NFTs migration.
In order to understand the migration process better, please check this repo.
The code is based on /~https://github.com/paritytech/nft-portal
Install netlify CLI globally
npm install netlify-cli -g
Create a copy of .env file, name it .env.development and fill in the required data:
REACT_APP_IPFS_GATEWAY
- IPFS gateway to fetch the snapshot data.REACT_APP_METADATA_GATEWAY
- IPFS gateway to fetch the collections and items metadata.REACT_APP_IMAGES_GATEWAY
- IPFS gateway to fetch the images.
npm install
npm start
import type { ApiPromise } from '@polkadot/api';
import type { KeyringPair } from '@polkadot/keyring/types';
interface SignNFT {
id: string;
owner: string;
metadata: string;
}
const mnemonic = '';
const keyring = new Keyring({type: 'sr25519'});
const signingPair = keyring.createFromUri(mnemonic);
async function signItems(api: ApiPromise, signingPair: KeyringPair, signNfts: SignNFT[], targetCollection: string) {
const lastBlock = (await api.rpc.chain.getHeader()).number.toNumber();
const deadline = lastBlock + 10 * 60 * 24 * 365; // one year from now
const sigs = signNfts.map(({ id, owner, metadata }) => {
const preSignedMint = api.createType('PalletNftsPreSignedMint', {
collection: targetCollection,
item: id,
attributes: [],
metadata,
onlyAccount: owner,
deadline,
mintPrice: null,
});
const dataHex = preSignedMint.toHex();
const sig = signingPair.sign(dataHex);
return {
data: dataHex,
signature: u8aToHex(sig),
};
});
return sigs;
}
/*
Types:
preSignInfo: PalletNftsPreSignedMint
signature: string // 0x...
signer: string // account address
*/
await api.tx.nfts
.mintPreSigned(preSignInfo, { Sr25519: signature }, signer);
import type { PalletNftsPreSignedMint } from '@polkadot/types/lookup';
const signature = {
data: '0x00000000000000000000011cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c3020510000',
signature: '0x629aa837684efb0....19308b8f'
};
const preSignedMint: PalletNftsPreSignedMint = api.createType('PalletNftsPreSignedMint', signature.data);
console.log(preSignedMint.toJSON());
/*
{
collection: 0,
item: 0,
attributes: [],
metadata: '0x',
onlyAccount: '5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL',
deadline: 5316656,
mintPrice: null
}
*/
Retrieve collection roles
const query = await api.query.nfts.collectionRoleOf.entries(collectionId);
const collectionRoles = query.map(
([
{
args: [, account],
},
data,
]) => ({
account,
roles: data.unwrapOrDefault().toNumber(),
}),
);
result = {};
if (collectionRoles.length) {
type CollectionRole = PalletNftsCollectionRole['type'];
const roles = initNftBitFlags<CollectionRole>(api, 'PalletNftsCollectionRole');
for (const record of collectionRoles) {
if (roles.has('Admin', record.roles)) {
result.admin = record.account;
}
if (roles.has('Issuer', record.roles)) {
result.issuer = record.account;
}
if (roles.has('Freezer', record.roles)) {
result.freezer = record.account;
}
}
}
Validate a user has a specific role
type CollectionRole = PalletNftsCollectionRole['type'];
const roles = initNftBitFlags<CollectionRole>(api, 'PalletNftsCollectionRole');
const accountRoles = (await api.query.nfts.collectionRoleOf(collectionId, userAddress))
.unwrapOrDefault()
.toNumber();
if (roles.has('Admin', accountRoles)) {
//...
}
Construct bitflags for collection settings
valuesToNftBitFlags(
[
transferableItemsRef.current.checked,
unlockedMetadataRef.current.checked,
unlockedAttributesRef.current.checked,
unlockedMaxSupplyRef.current.checked,
],
'PalletNftsCollectionSetting',
)
Read collection settings
type CollectionSetting = PalletNftsCollectionSetting['type'];
const config = await api.query.nfts.collectionConfigOf(collectionId);
if (config.isSome) {
const collectionConfig = config.unwrap().toJSON() as unknown as CollectionConfigJson;
const settings = initNftBitFlags<CollectionSetting>(api, 'PalletNftsCollectionSetting');
const metadataIsLocked = !settings.has('UnlockedMetadata', collectionConfig.settings);
const attributesAreLocked = !settings.has('UnlockedAttributes', collectionConfig.settings);
}
Read collection attributes
const query = await api.query.nfts.attribute.entries(collectionId, null, 'CollectionOwner');
const attributes = query.map(
([
{
args: [, , , key],
},
data,
]) => {
// NOTE: `.toString()` returns the hex value for the attribute's key/value
// use `.toPrimitive()` to get the decoded UTF-8 value
const value = data.isSome ? data.unwrap()[0].toPrimitive() : '';
return {
key: key.toPrimitive() as string,
value: value as string,
};
},
);
Update collection attributes
enum SUPPORTED_ATTRIBUTE_KEYS {
SNAPSHOT = 'offchain-mint-snapshot',
PROVIDER = 'offchain-mint-ipfs-provider',
}
await api.tx.nfts
.setAttribute(collectionId, null, 'CollectionOwner', attributeKey, attributeValue)
.signAndSend(/*....*/);
MIT