([]);
28 | useEffect(() => {
29 | fetchData(setNfts);
30 | }, []);
31 |
32 | if (!nfts) {
33 | return ;
34 | }
35 |
36 | console.log(nfts)
37 |
38 | return (
39 |
40 |
41 |
Create Next App
42 |
43 |
44 |
45 |
46 |
47 |
48 | {nfts.filter(nft => nft.collection === 'd43593c715a56da27d-CHNK').map((nft, index) => (
49 |
50 | {nft.sn.slice(nft.sn.length - 4)}
51 |
52 |
53 | ))}
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default Home;
61 |
--------------------------------------------------------------------------------
/projects/scripts/pinata-utils.ts:
--------------------------------------------------------------------------------
1 | import {sleep} from "./utils";
2 |
3 | require('dotenv').config();
4 | import { Metadata } from 'rmrk-tools/dist/tools/types';
5 | import pLimit from 'p-limit';
6 | import { Readable } from 'stream';
7 | import fs from 'fs';
8 | // @ts-ignore
9 | import pinataSDK, { PinataOptions, PinataPinOptions } from '@pinata/sdk';
10 |
11 | const defaultOptions: Partial = {
12 | pinataOptions: {
13 | cidVersion: 1,
14 | },
15 | };
16 |
17 | export const pinata = pinataSDK(process.env.PINATA_KEY, process.env.PINATA_SECRET);
18 |
19 | const fsPromises = fs.promises;
20 | export type StreamPinata = Readable & {
21 | path?: string;
22 | };
23 | const limit = pLimit(1);
24 |
25 | export const pinFileStreamToIpfs = async (file: StreamPinata, name?: string) => {
26 | const options = { ...defaultOptions, pinataMetadata: { name } };
27 | const result = await pinata.pinFileToIPFS(file, options);
28 | return result.IpfsHash;
29 | };
30 |
31 | export const uploadAndPinIpfsMetadata = async (metadataFields: Metadata): Promise => {
32 | const options = {
33 | ...defaultOptions,
34 | pinataMetadata: { name: metadataFields.name },
35 | };
36 | try {
37 | const metadata = { ...metadataFields };
38 | const metadataHashResult = await pinata.pinJSONToIPFS(metadata, options);
39 | return `ipfs://ipfs/${metadataHashResult.IpfsHash}`;
40 | } catch (error) {
41 | return '';
42 | }
43 | };
44 |
45 | export const pinSingleMetadataFromDir = async (
46 | dir: string,
47 | path: string,
48 | name: string,
49 | metadataBase: Partial,
50 | ) => {
51 | try {
52 | const imageFile = await fsPromises.readFile(`${process.cwd()}${dir}/${path}`);
53 | if (!imageFile) {
54 | throw new Error('No image file');
55 | }
56 |
57 | const stream: StreamPinata = Readable.from(imageFile);
58 | stream.path = path;
59 |
60 | const imageCid = await pinFileStreamToIpfs(stream, name);
61 | console.log(`NFT ${path} IMAGE CID: `, imageCid);
62 | const metadata: Metadata = { ...metadataBase, name, mediaUri: `ipfs://ipfs/${imageCid}` };
63 | const metadataCid = await uploadAndPinIpfsMetadata(metadata);
64 | await sleep(500);
65 | console.log(`NFT ${name} METADATA: `, metadataCid);
66 | return metadataCid;
67 | } catch (error) {
68 | console.log(error);
69 | console.log(JSON.stringify(error));
70 | return '';
71 | }
72 | };
73 |
--------------------------------------------------------------------------------
/projects/react-demo/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | height: 100vh;
9 | }
10 |
11 | .main {
12 | padding: 5rem 0;
13 | flex: 1;
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: center;
17 | align-items: center;
18 | }
19 |
20 | .footer {
21 | width: 100%;
22 | height: 100px;
23 | border-top: 1px solid #eaeaea;
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | }
28 |
29 | .footer a {
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | flex-grow: 1;
34 | }
35 |
36 | .title a {
37 | color: #0070f3;
38 | text-decoration: none;
39 | }
40 |
41 | .title a:hover,
42 | .title a:focus,
43 | .title a:active {
44 | text-decoration: underline;
45 | }
46 |
47 | .title {
48 | margin: 0;
49 | line-height: 1.15;
50 | font-size: 4rem;
51 | }
52 |
53 | .title,
54 | .description {
55 | text-align: center;
56 | }
57 |
58 | .description {
59 | line-height: 1.5;
60 | font-size: 1.5rem;
61 | }
62 |
63 | .code {
64 | background: #fafafa;
65 | border-radius: 5px;
66 | padding: 0.75rem;
67 | font-size: 1.1rem;
68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
69 | Bitstream Vera Sans Mono, Courier New, monospace;
70 | }
71 |
72 | .grid {
73 | display: flex;
74 | align-items: center;
75 | justify-content: center;
76 | flex-wrap: wrap;
77 | max-width: 800px;
78 | margin-top: 3rem;
79 | }
80 |
81 | .card {
82 | margin: 1rem;
83 | padding: 1.5rem;
84 | text-align: left;
85 | color: inherit;
86 | text-decoration: none;
87 | border: 1px solid #eaeaea;
88 | border-radius: 10px;
89 | transition: color 0.15s ease, border-color 0.15s ease;
90 | width: 45%;
91 | }
92 |
93 | .card:hover,
94 | .card:focus,
95 | .card:active {
96 | color: #0070f3;
97 | border-color: #0070f3;
98 | }
99 |
100 | .card h2 {
101 | margin: 0 0 1rem 0;
102 | font-size: 1.5rem;
103 | }
104 |
105 | .card p {
106 | margin: 0;
107 | font-size: 1.25rem;
108 | line-height: 1.5;
109 | }
110 |
111 | .logo {
112 | height: 1em;
113 | margin-left: 0.5rem;
114 | }
115 |
116 | @media (max-width: 600px) {
117 | .grid {
118 | width: 100%;
119 | flex-direction: column;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_bone_left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_bone_right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_flag_left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_flag_right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_spear_right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_spear_left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/react-demo/components/rmrk-svg-composer.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect, useState } from 'react';
2 | import { Flex } from '@chakra-ui/react';
3 | import SVG from 'react-inlinesvg';
4 | import {
5 | ConsolidatorReturnType,
6 | NFTConsolidated,
7 | } from 'rmrk-tools/dist/tools/consolidator/consolidator';
8 | import { getBaseParts } from '../lib/get-base-parts';
9 | import { IBasePart } from 'rmrk-tools/dist/classes/base';
10 | import { arePropsEqual } from '../lib/are-props-equal';
11 |
12 | interface IProps {
13 | nft: NFTConsolidated;
14 | }
15 |
16 | export const getEquippedInventoryItems = async (
17 | setInventory: (basePart: Partial[]) => void,
18 | resources_parts?: IBasePart[],
19 | children?: NFTConsolidated['children'],
20 | ) => {
21 | const payload = await fetch('/chunky-dump.json');
22 | const data: ConsolidatorReturnType = await payload.json();
23 |
24 | const equippedChildren = (children || []).map((child) => {
25 | const nft = data?.nfts[child.id];
26 | const matchingResource = nft.resources.find((resource) => resource.slot === child.equipped);
27 |
28 | return matchingResource;
29 | });
30 |
31 | const slotParts = (resources_parts || []).map((resources_part) => {
32 | // Find base slot for each equipped children
33 | const matchingResource = equippedChildren.find(
34 | (resource) => resource?.slot && resource.slot.split('.')[1] === resources_part.id,
35 | );
36 |
37 | if (resources_part.type !== 'slot') {
38 | return null;
39 | }
40 |
41 | return {
42 | z: resources_part.z,
43 | src: matchingResource?.src || resources_part.src,
44 | id: resources_part.id,
45 | };
46 | });
47 |
48 | const filteredParts = slotParts.filter((part): part is { z: number; src: string; id: string } =>
49 | Boolean(part),
50 | );
51 |
52 | setInventory(filteredParts);
53 | };
54 |
55 | const SvgResourceComposer = ({ nft }: IProps) => {
56 | const [baseParts, setBaseParts] = useState();
57 | const [equippedInventory, setEquippedInventory] = useState[]>();
58 |
59 | useEffect(() => {
60 | getBaseParts(
61 | setBaseParts,
62 | nft.resources.find((resource) => Boolean(resource.base)),
63 | );
64 | }, []);
65 |
66 | useEffect(() => {
67 | if (baseParts) {
68 | getEquippedInventoryItems(setEquippedInventory, baseParts, nft.children);
69 | }
70 | }, [baseParts]);
71 |
72 | const fixedParts = (baseParts || []).filter((resources_part) => resources_part.type === 'fixed');
73 |
74 | const parts = [...(equippedInventory || []), ...fixedParts].sort(
75 | (first, second) => first?.z! - second.z!,
76 | );
77 |
78 | return (
79 |
85 |
105 |
106 | );
107 | };
108 |
109 | export default memo(SvgResourceComposer, arePropsEqual);
110 |
--------------------------------------------------------------------------------
/projects/scripts/get-polkadot-api.ts:
--------------------------------------------------------------------------------
1 | import { ApiPromise, WsProvider } from '@polkadot/api';
2 | import { sleep } from './utils';
3 | import {RPC_ENDPOINTS} from "./constants";
4 |
5 | const MAX_RETRIES = 5;
6 | const WS_DISCONNECT_TIMEOUT_SECONDS = 20;
7 |
8 | let wsProvider: WsProvider;
9 | let polkadotApi: ApiPromise;
10 | let healthCheckInProgress = false;
11 |
12 | /**
13 | *
14 | * @param wsEndpoints - array of rpc ws endpoints. In the order of their priority
15 | */
16 | const providerHealthCheck = async (wsEndpoints: string[]) => {
17 | const [primaryEndpoint, secondaryEndpoint, ...otherEndpoints] = wsEndpoints;
18 | console.log(
19 | `Performing ${WS_DISCONNECT_TIMEOUT_SECONDS} seconds health check for WS Provider fro rpc ${primaryEndpoint}.`,
20 | );
21 | healthCheckInProgress = true;
22 | await sleep(WS_DISCONNECT_TIMEOUT_SECONDS * 1000);
23 | if (wsProvider.isConnected) {
24 | console.log(`All good. Connected back to ${primaryEndpoint}`);
25 | healthCheckInProgress = false;
26 | return true;
27 | } else {
28 | console.log(
29 | `rpc endpoint ${primaryEndpoint} still disconnected after ${WS_DISCONNECT_TIMEOUT_SECONDS} seconds. Disconnecting from ${primaryEndpoint} and switching to a backup rpc endpoint ${secondaryEndpoint}`,
30 | );
31 | await wsProvider.disconnect();
32 |
33 | healthCheckInProgress = false;
34 | throw new Error(
35 | `rpc endpoint ${primaryEndpoint} still disconnected after ${WS_DISCONNECT_TIMEOUT_SECONDS} seconds.`,
36 | );
37 | }
38 | };
39 |
40 | /**
41 | *
42 | * @param wsEndpoints - array of rpc ws endpoints. In the order of their priority
43 | */
44 | const getProvider = async (wsEndpoints: string[]) => {
45 | const [primaryEndpoint, ...otherEndpoints] = wsEndpoints;
46 | return await new Promise((resolve, reject) => {
47 | wsProvider = new WsProvider(primaryEndpoint);
48 | wsProvider.on('disconnected', async () => {
49 | console.log(`WS provider for rpc ${primaryEndpoint} disconnected!`);
50 | if (!healthCheckInProgress) {
51 | try {
52 | await providerHealthCheck(wsEndpoints);
53 | resolve(wsProvider);
54 | } catch (error: any) {
55 | reject(error);
56 | }
57 | }
58 | });
59 | wsProvider.on('connected', () => {
60 | console.log(`WS provider for rpc ${primaryEndpoint} connected`);
61 | resolve(wsProvider);
62 | });
63 | wsProvider.on('error', async () => {
64 | console.log(`Error thrown for rpc ${primaryEndpoint}`);
65 | if (!healthCheckInProgress) {
66 | try {
67 | await providerHealthCheck(wsEndpoints);
68 | resolve(wsProvider);
69 | } catch (error: any) {
70 | reject(error);
71 | }
72 | }
73 | });
74 | });
75 | };
76 |
77 | /**
78 | *
79 | * @param wsEndpoints - array of rpc ws endpoints. In the order of their priority
80 | * @param retry - retry count
81 | */
82 | export const getApi = async (
83 | wsEndpoints: string[] = RPC_ENDPOINTS,
84 | retry = 0,
85 | ): Promise => {
86 | if (wsProvider && polkadotApi && polkadotApi.isConnected) return polkadotApi;
87 | const [primaryEndpoint, secondaryEndpoint, ...otherEndpoints] = wsEndpoints;
88 |
89 | try {
90 | const provider = await getProvider(wsEndpoints);
91 | polkadotApi = await ApiPromise.create({ provider });
92 | await polkadotApi.isReady;
93 | return polkadotApi;
94 | } catch (error: any) {
95 | if (retry < MAX_RETRIES) {
96 | // If we have reached maximum number of retries on the primaryEndpoint, let's move it to the end of array and try the secondary endpoint
97 | return await getApi([secondaryEndpoint, ...otherEndpoints, primaryEndpoint], retry + 1);
98 | } else {
99 | return polkadotApi;
100 | }
101 | }
102 | };
103 |
--------------------------------------------------------------------------------
/projects/scripts/create-base.ts:
--------------------------------------------------------------------------------
1 | import { IBasePart } from "rmrk-tools/dist/classes/base";
2 | import {
3 | ASSETS_CID,
4 | CHUNKY_BASE_SYMBOL,
5 | CHUNKY_ITEMS_COLLECTION_SYMBOL,
6 | WS_URL,
7 | } from "./constants";
8 | import { cryptoWaitReady, encodeAddress } from "@polkadot/util-crypto";
9 | import { getKeyringFromUri, getKeys } from "./utils";
10 | import { Collection, Base } from "rmrk-tools";
11 | import { u8aToHex } from "@polkadot/util";
12 | import {getApi} from "./get-polkadot-api";
13 | import {signAndSendWithRetry} from "./sign-and-send-with-retry";
14 |
15 | export const fixedParts: IBasePart[] = [
16 | {
17 | type: "fixed",
18 | id: "chunky_body_1",
19 | src: `ipfs://ipfs/${ASSETS_CID}/v1/Chunky_body_v1.svg`,
20 | z: 0,
21 | },
22 | {
23 | type: "fixed",
24 | id: "chunky_body_2",
25 | src: `ipfs://ipfs/${ASSETS_CID}/v2/Chunky_body_v2.svg`,
26 | z: 0,
27 | },
28 | {
29 | type: "fixed",
30 | id: "chunky_body_3",
31 | src: `ipfs://ipfs/${ASSETS_CID}/v3/Chunky_body_v3.svg`,
32 | z: 0,
33 | },
34 | {
35 | type: "fixed",
36 | id: "chunky_body_4",
37 | src: `ipfs://ipfs/${ASSETS_CID}/v4/Chunky_body_v4.svg`,
38 | z: 0,
39 | },
40 | {
41 | type: "fixed",
42 | id: "chunky_head_1",
43 | src: `ipfs://ipfs/${ASSETS_CID}/v1/Chunky_head_v1.svg`,
44 | z: 4,
45 | },
46 | {
47 | type: "fixed",
48 | id: "chunky_head_2",
49 | src: `ipfs://ipfs/${ASSETS_CID}/v2/Chunky_head_v2.svg`,
50 | z: 4,
51 | },
52 | {
53 | type: "fixed",
54 | id: "chunky_head_3",
55 | src: `ipfs://ipfs/${ASSETS_CID}/v3/Chunky_head_v3.svg`,
56 | z: 4,
57 | },
58 | {
59 | type: "fixed",
60 | id: "chunky_head_4",
61 | src: `ipfs://ipfs/${ASSETS_CID}/v4/Chunky_head_v4.svg`,
62 | z: 4,
63 | },
64 | {
65 | type: "fixed",
66 | id: "chunky_hand_1",
67 | src: `ipfs://ipfs/${ASSETS_CID}/v1/Chunky_hand_v1.svg`,
68 | z: 3,
69 | },
70 | {
71 | type: "fixed",
72 | id: "chunky_hand_2",
73 | src: `ipfs://ipfs/${ASSETS_CID}/v2/Chunky_hand_v2.svg`,
74 | z: 3,
75 | },
76 | {
77 | type: "fixed",
78 | id: "chunky_hand_3",
79 | src: `ipfs://ipfs/${ASSETS_CID}/v3/Chunky_hand_v3.svg`,
80 | z: 3,
81 | },
82 | {
83 | type: "fixed",
84 | id: "chunky_hand_4",
85 | src: `ipfs://ipfs/${ASSETS_CID}/v4/Chunky_hand_v4.svg`,
86 | z: 3,
87 | },
88 | ];
89 |
90 | const getSlotKanariaParts = (equippable: string[] | "*" = []): IBasePart[] => {
91 | return [
92 | {
93 | type: "slot",
94 | id: "chunky_objectLeft",
95 | equippable,
96 | z: 1,
97 | },
98 | {
99 | type: "slot",
100 | id: "chunky_objectRight",
101 | equippable,
102 | z: 2,
103 | },
104 | ];
105 | };
106 |
107 | export const createBase = async () => {
108 | try {
109 | console.log("CREATE CHUNKY BASE START -------");
110 | await cryptoWaitReady();
111 | const accounts = getKeys();
112 | const ws = WS_URL;
113 | const phrase = process.env.PRIVAKE_KEY;
114 | const api = await getApi();
115 | const kp = getKeyringFromUri(phrase);
116 |
117 | const collectionId = Collection.generateId(
118 | u8aToHex(accounts[0].publicKey),
119 | CHUNKY_ITEMS_COLLECTION_SYMBOL
120 | );
121 | console.log("collectionId", collectionId);
122 |
123 | const baseParts = [...fixedParts, ...getSlotKanariaParts([collectionId])];
124 |
125 | const baseEntity = new Base(
126 | 0,
127 | CHUNKY_BASE_SYMBOL,
128 | encodeAddress(kp.address, 2),
129 | "svg",
130 | baseParts
131 | );
132 |
133 | const { block } = await signAndSendWithRetry(
134 | api.tx.system.remark(baseEntity.base()),
135 | kp
136 | );
137 | console.log("Chunky Base created at block: ", block);
138 | return block;
139 | return block;
140 | } catch (error: any) {
141 | console.error(error);
142 | }
143 | };
144 |
--------------------------------------------------------------------------------
/projects/scripts/sign-and-send-with-retry.ts:
--------------------------------------------------------------------------------
1 | import { SubmittableExtrinsic } from '@polkadot/api/types';
2 | import { ISubmittableResult } from '@polkadot/types/types';
3 | import { KeyringPair } from '@polkadot/keyring/types';
4 | import { getApi } from './get-polkadot-api';
5 | import { sleep } from './utils';
6 | import { CodecHash, EventRecord } from '@polkadot/types/interfaces';
7 |
8 | const MAX_RETRIES = 5;
9 | const RETRY_DELAY_SECONDS = 4;
10 |
11 | interface ISendTxReturnType {
12 | success: boolean;
13 | hash?: CodecHash;
14 | included: EventRecord[];
15 | finalized: EventRecord[];
16 | block: number;
17 | }
18 |
19 | /**
20 | *
21 | * @param tx - polkadot.js api tx
22 | * @param account - Account keypair
23 | * @param resolvedOnFinalizedOnly - If you don't want to wait for promise to resolve only when the block is finalized,
24 | * it can resolve as soon as tx is added to a block. This doesn't guarantee that transaction block will be included in finalised chain.
25 | * true by default
26 | * @param retry - retry count in case of failure.
27 | */
28 | export const signAndSendWithRetry = async (
29 | tx: SubmittableExtrinsic<'promise', ISubmittableResult>,
30 | account: KeyringPair,
31 | resolvedOnFinalizedOnly = true,
32 | retry = 0,
33 | ): Promise => {
34 | return new Promise(async (resolve, reject) => {
35 | const api = await getApi();
36 |
37 | const returnObject: ISendTxReturnType = { success: false, hash: undefined, included: [], finalized: [], block: 0 }
38 |
39 | try {
40 | const unsubscribe = await tx.signAndSend(
41 | account,
42 | { nonce: -1 },
43 | async ({ events = [], status, dispatchError }) => {
44 | returnObject.success = !dispatchError;
45 | returnObject.included = [...events];
46 | returnObject.hash = status.hash;
47 |
48 | const rejectPromise = (error: any) => {
49 | console.error(`Error sending tx`, error);
50 | console.log(`tx for the error above`, tx.toHuman());
51 | unsubscribe();
52 | reject(error);
53 | }
54 |
55 | if (status.isInBlock) {
56 | console.log(
57 | `📀 Transaction ${tx.meta.name} included at blockHash ${status.asInBlock} [success = ${!dispatchError}]`,
58 | );
59 |
60 | // Get block number that this tx got into, to return back to user
61 | const signedBlock = await api.rpc.chain.getBlock(status.asInBlock);
62 | returnObject.block = signedBlock.block.header.number.toNumber();
63 |
64 | // If we don't care about waiting for this tx to get into a finalized block, we can return early.
65 | if (!resolvedOnFinalizedOnly && !dispatchError) {
66 | unsubscribe();
67 | resolve(returnObject);
68 | }
69 | } else if (status.isBroadcast) {
70 | console.log(`🚀 Transaction broadcasted.`);
71 | } else if (status.isFinalized) {
72 | console.log(
73 | `💯 Transaction ${tx.meta.name}(..) Finalized at blockHash ${status.asFinalized}`,
74 | );
75 | if (returnObject.block === 0) {
76 | const signedBlock = await api.rpc.chain.getBlock(status.asInBlock);
77 | returnObject.block = signedBlock.block.header.number.toNumber();
78 | }
79 |
80 | unsubscribe();
81 | resolve(returnObject);
82 | } else if (status.isReady) {
83 | // let's not be too noisy..
84 | } else if (status.isInvalid) {
85 | rejectPromise(new Error(`Extrinsic isInvalid`))
86 | } else {
87 | console.log(`🤷 Other status ${status}`);
88 | }
89 | },
90 | );
91 | } catch (error: any) {
92 | console.log(
93 | `Error sending tx. Error: "${error.message}". TX: ${JSON.stringify(tx.toHuman())}`,
94 | );
95 | if (retry < MAX_RETRIES) {
96 | console.log(`sendAndFinalize Retry #${retry} of ${MAX_RETRIES}`);
97 | await sleep(RETRY_DELAY_SECONDS * 1000);
98 | const result = await signAndSendWithRetry(tx, account, resolvedOnFinalizedOnly, retry + 1);
99 | resolve(result);
100 | } else {
101 | console.error(`Error initiating tx signAndSend`, error);
102 | reject(error);
103 | }
104 | }
105 | });
106 | };
107 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_pencil_right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/Chunky Items/Chunky_pencil_left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/mint-chunky.ts:
--------------------------------------------------------------------------------
1 | import { cryptoWaitReady, encodeAddress } from "@polkadot/util-crypto";
2 | import { getKeyringFromUri, getKeys } from "./utils";
3 | import {
4 | ASSETS_CID,
5 | CHUNKY_BASE_SYMBOL,
6 | CHUNKY_COLLECTION_SYMBOL,
7 | WS_URL,
8 | } from "./constants";
9 | import { Base, Collection, NFT } from "rmrk-tools";
10 | import { u8aToHex } from "@polkadot/util";
11 | import { pinSingleMetadataFromDir } from "./pinata-utils";
12 | import { nanoid } from "nanoid";
13 | import {getApi} from "./get-polkadot-api";
14 | import {signAndSendWithRetry} from "./sign-and-send-with-retry";
15 |
16 | export const addBaseResource = async (
17 | chunkyBlock: number,
18 | baseBlock: number
19 | ) => {
20 | try {
21 | console.log("ADD BASE RESOURCE TO CHUNKY NFT START -------");
22 | await cryptoWaitReady();
23 | const accounts = getKeys();
24 | const phrase = process.env.PRIVAKE_KEY;
25 | const kp = getKeyringFromUri(phrase);
26 |
27 | const collectionId = Collection.generateId(
28 | u8aToHex(accounts[0].publicKey),
29 | CHUNKY_COLLECTION_SYMBOL
30 | );
31 |
32 | const api = await getApi();
33 | const serialNumbers = [1, 2, 3, 4];
34 |
35 | const baseEntity = new Base(
36 | baseBlock,
37 | CHUNKY_BASE_SYMBOL,
38 | encodeAddress(kp.address, 2),
39 | "svg"
40 | );
41 |
42 | const BASE_ID = baseEntity.getId();
43 |
44 | const resourceRemarks = [];
45 |
46 | serialNumbers.forEach((sn) => {
47 | const nft = new NFT({
48 | block: chunkyBlock,
49 | collection: collectionId,
50 | symbol: `chunky_${sn}`,
51 | transferable: 1,
52 | sn: `${sn}`.padStart(8, "0"),
53 | owner: encodeAddress(accounts[0].address, 2),
54 | metadata: "",
55 | });
56 |
57 | const baseResId = nanoid(8);
58 |
59 | resourceRemarks.push(
60 | nft.resadd({
61 | base: BASE_ID,
62 | id: baseResId,
63 | parts: [
64 | `chunky_body_${sn}`,
65 | `chunky_head_${sn}`,
66 | `chunky_hand_${sn}`,
67 | "chunky_objectLeft",
68 | "chunky_objectRight",
69 | ],
70 | thumb: `ipfs://ipfs/${ASSETS_CID}/Chunky%20Preview.png`,
71 | })
72 | );
73 |
74 | if (sn === 4) {
75 | const secondaryArtResId = nanoid(8);
76 | resourceRemarks.push(
77 | nft.resadd({
78 | src: `ipfs://ipfs/${ASSETS_CID}/chunky_altresource.jpg`,
79 | thumb: `ipfs://ipfs/${ASSETS_CID}/chunky_altresource.jpg`,
80 | id: secondaryArtResId,
81 | })
82 | );
83 |
84 | resourceRemarks.push(nft.setpriority([secondaryArtResId, baseResId]));
85 | }
86 | });
87 |
88 | const txs = resourceRemarks.map((remark) => api.tx.system.remark(remark));
89 | const tx = api.tx.utility.batch(txs);
90 | const { block } = await signAndSendWithRetry(tx, kp);
91 | console.log("Chunky base resources added at block: ", block);
92 | } catch (error: any) {
93 | console.error(error);
94 | }
95 | };
96 |
97 | export const createChunkyCollection = async () => {
98 | try {
99 | console.log("CREATE CHUNKY COLLECTION START -------");
100 | await cryptoWaitReady();
101 | const accounts = getKeys();
102 | const phrase = process.env.PRIVAKE_KEY;
103 | const api = await getApi();
104 | const kp = getKeyringFromUri(phrase);
105 |
106 | const collectionId = Collection.generateId(
107 | u8aToHex(accounts[0].publicKey),
108 | CHUNKY_COLLECTION_SYMBOL
109 | );
110 |
111 | const collectionMetadataCid = await pinSingleMetadataFromDir(
112 | "/assets/chunky",
113 | "Chunky Preview.png",
114 | "RMRK2 demo chunky collection",
115 | {
116 | description: "This is Chunky! RMRK2 demo nested NFT",
117 | externalUri: "https://rmrk.app",
118 | properties: {},
119 | }
120 | );
121 |
122 | const ItemsCollection = new Collection(
123 | 0,
124 | 10000,
125 | encodeAddress(accounts[0].address, 2),
126 | CHUNKY_COLLECTION_SYMBOL,
127 | collectionId,
128 | collectionMetadataCid
129 | );
130 |
131 | const { block } = await signAndSendWithRetry(
132 | api.tx.system.remark(ItemsCollection.create()),
133 | kp
134 | );
135 | console.log("COLLECTION CREATION REMARK: ", ItemsCollection.create());
136 | console.log("Chunky collection created at block: ", block);
137 |
138 | return block;
139 | } catch (error: any) {
140 | console.error(error);
141 | }
142 | };
143 |
144 | export const mintChunky = async () => {
145 | try {
146 | console.log("CREATE CHUNKY NFT START -------");
147 | await cryptoWaitReady();
148 | const accounts = getKeys();
149 | const phrase = process.env.PRIVAKE_KEY;
150 | const kp = getKeyringFromUri(phrase);
151 |
152 | const collectionId = Collection.generateId(
153 | u8aToHex(accounts[0].publicKey),
154 | CHUNKY_COLLECTION_SYMBOL
155 | );
156 |
157 | await createChunkyCollection();
158 |
159 | const api = await getApi();
160 |
161 | const serialNumbers = [1, 2, 3, 4];
162 |
163 | const promises = serialNumbers.map(async (sn) => {
164 | const metadataCid = await pinSingleMetadataFromDir(
165 | "/assets/chunky",
166 | "Chunky Preview.png",
167 | `RMRK2 demo chunky NFT #${sn}`,
168 | {
169 | description: `This is Chunky #${sn}! RMRK2 demo nested NFT`,
170 | externalUri: "https://rmrk.app",
171 | properties: {
172 | rarity: {
173 | type: "string",
174 | value: sn === 4 ? "epic" : "common",
175 | },
176 | },
177 | }
178 | );
179 |
180 | const nft = new NFT({
181 | block: 0,
182 | collection: collectionId,
183 | symbol: `chunky_${sn}`,
184 | transferable: 1,
185 | sn: `${sn}`.padStart(8, "0"),
186 | owner: encodeAddress(accounts[0].address, 2),
187 | metadata: metadataCid,
188 | });
189 |
190 | return nft.mint();
191 | });
192 |
193 | const remarks = await Promise.all(promises);
194 |
195 | const txs = remarks.map((remark) => api.tx.system.remark(remark));
196 | const tx = api.tx.utility.batchAll(txs);
197 | const { block } = await signAndSendWithRetry(tx, kp);
198 | console.log("Chunky NFT minted at block: ", block);
199 | return block;
200 | } catch (error: any) {
201 | console.error(error);
202 | }
203 | };
204 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v1/Chunky_head_v1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/mint-chunky-items.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ASSETS_CID,
3 | CHUNKY_COLLECTION_SYMBOL,
4 | CHUNKY_ITEMS_COLLECTION_SYMBOL,
5 | WS_URL,
6 | CHUNKY_BASE_SYMBOL
7 | } from "./constants";
8 | import { cryptoWaitReady } from "@polkadot/util-crypto";
9 | import { getKeyringFromUri, getKeys } from "./utils";
10 | import { Collection, NFT, Base } from "rmrk-tools";
11 | import { u8aToHex } from "@polkadot/util";
12 | import { encodeAddress } from "@polkadot/keyring";
13 | import { nanoid } from "nanoid";
14 | import {pinSingleMetadataFromDir} from "./pinata-utils";
15 | import {getApi} from "./get-polkadot-api";
16 | import {signAndSendWithRetry} from "./sign-and-send-with-retry";
17 |
18 | const chunkyItems = [
19 | {
20 | symbol: "chunky_bone",
21 | thumb: "Chunky_bone_thumb.png",
22 | resources: ["Chunky_bone_left.svg", "Chunky_bone_right.svg"],
23 | name: "The Bone",
24 | description: "Chunky likes his bone!",
25 | },
26 | {
27 | symbol: "chunky_flag",
28 | thumb: "Chunky_flag_thumb.png",
29 | resources: ["Chunky_flag_left.svg", "Chunky_flag_right.svg"],
30 | name: "The Flag",
31 | description: "Chunky likes his flag!",
32 | },
33 | {
34 | symbol: "chunky_pencil",
35 | thumb: "Chunky_pencil_thumb.png",
36 | resources: ["Chunky_pencil_left.svg", "Chunky_pencil_right.svg"],
37 | name: "The Pencil",
38 | description: "Chunky likes his pencil!",
39 | },
40 | {
41 | symbol: "chunky_spear",
42 | thumb: "Chunky_spear_thumb.png",
43 | resources: ["Chunky_spear_left.svg", "Chunky_spear_right.svg"],
44 | name: "The Spear",
45 | description: "Chunky likes his spear!",
46 | },
47 | ];
48 |
49 | export const mintItems = async (chunkyBlock: number, baseBlock: number) => {
50 | try {
51 | console.log("CREATE CHUNKY ITEMS START -------");
52 | await cryptoWaitReady();
53 | const accounts = getKeys();
54 | const ws = WS_URL;
55 | const phrase = process.env.PRIVAKE_KEY;
56 | const api = await getApi();
57 | const kp = getKeyringFromUri(phrase);
58 |
59 | const collectionId = Collection.generateId(
60 | u8aToHex(accounts[0].publicKey),
61 | CHUNKY_ITEMS_COLLECTION_SYMBOL
62 | );
63 |
64 | const chunkyCollectionId = Collection.generateId(
65 | u8aToHex(accounts[0].publicKey),
66 | CHUNKY_COLLECTION_SYMBOL
67 | );
68 |
69 | const baseEntity = new Base(
70 | baseBlock,
71 | CHUNKY_BASE_SYMBOL,
72 | encodeAddress(kp.address, 2),
73 | "svg"
74 | );
75 |
76 | await createItemsCollection();
77 |
78 | const promises = chunkyItems.map(async (item, index) => {
79 | const sn = index + 1;
80 |
81 | const metadataCid = await pinSingleMetadataFromDir(
82 | "/assets/chunky/Chunky Items",
83 | item.thumb,
84 | item.name,
85 | {
86 | description: item.description,
87 | externalUri: "https://rmrk.app",
88 | }
89 | );
90 |
91 | const nft = new NFT({
92 | block: 0,
93 | sn: sn.toString().padStart(8, "0"),
94 | owner: encodeAddress(accounts[0].address, 2),
95 | transferable: 1,
96 | metadata: metadataCid,
97 | collection: collectionId,
98 | symbol: item.symbol,
99 | });
100 |
101 | return nft.mint();
102 | });
103 |
104 | const remarks = await Promise.all(promises);
105 |
106 | const txs = remarks.map((remark) => api.tx.system.remark(remark));
107 | const batch = api.tx.utility.batch(txs);
108 | const { block } = await signAndSendWithRetry(batch, kp);
109 | console.log("CHUNKY ITEMS MINTED AT BLOCK: ", block);
110 |
111 | const resaddSendRemarks = [];
112 |
113 | chunkyItems.forEach((item, index) => {
114 | const sn = index + 1;
115 | const nft = new NFT({
116 | block,
117 | sn: sn.toString().padStart(8, "0"),
118 | owner: encodeAddress(accounts[0].address, 2),
119 | transferable: 1,
120 | metadata: `ipfs://ipfs/trololo`,
121 | collection: collectionId,
122 | symbol: item.symbol,
123 | });
124 |
125 | item.resources.forEach((resource) => {
126 | resaddSendRemarks.push(
127 | nft.resadd({
128 | src: `ipfs://ipfs/${ASSETS_CID}/Chunky Items/${resource}`,
129 | thumb: `ipfs://ipfs/${ASSETS_CID}/Chunky Items/${item.thumb}`,
130 | id: nanoid(8),
131 | slot: resource.includes("left")
132 | ? `${baseEntity.getId()}.chunky_objectLeft`
133 | : `${baseEntity.getId()}.chunky_objectRight`,
134 | })
135 | );
136 | });
137 |
138 | const chunkyNft = new NFT({
139 | block: chunkyBlock,
140 | collection: chunkyCollectionId,
141 | symbol: `chunky_${sn}`,
142 | transferable: 1,
143 | sn: `${sn}`.padStart(8, "0"),
144 | owner: encodeAddress(accounts[0].address, 2),
145 | metadata: "",
146 | });
147 |
148 | resaddSendRemarks.push(nft.send(chunkyNft.getId()));
149 | resaddSendRemarks.push(
150 | nft.equip(
151 | `${baseEntity.getId()}.${
152 | index % 2 ? "chunky_objectLeft" : "chunky_objectRight"
153 | }`
154 | )
155 | );
156 | });
157 |
158 | const restxs = resaddSendRemarks.map((remark) =>
159 | api.tx.system.remark(remark)
160 | );
161 | const resbatch = api.tx.utility.batch(restxs);
162 | const { block: resaddSendBlock } = await signAndSendWithRetry(resbatch, kp);
163 | console.log("CHUNKY ITEMS RESOURCE ADDED AND SENT: ", resaddSendBlock);
164 | return true;
165 | } catch (error: any) {
166 | console.error(error);
167 | }
168 | };
169 |
170 | export const createItemsCollection = async () => {
171 | try {
172 | console.log("CREATE CHUNKY ITEMS COLLECTION START -------");
173 | await cryptoWaitReady();
174 | const accounts = getKeys();
175 | const phrase = process.env.PRIVAKE_KEY;
176 | const api = await getApi();
177 | const kp = getKeyringFromUri(phrase);
178 |
179 | const collectionId = Collection.generateId(
180 | u8aToHex(accounts[0].publicKey),
181 | CHUNKY_ITEMS_COLLECTION_SYMBOL
182 | );
183 |
184 | const collectionMetadataCid = await pinSingleMetadataFromDir(
185 | "/assets/chunky",
186 | "Chunky Preview.png",
187 | "RMRK2 demo chunky items collection",
188 | {
189 | description: "This is Chunky items! RMRK2 demo nested NFTs",
190 | externalUri: "https://rmrk.app",
191 | properties: {},
192 | }
193 | );
194 |
195 | const ItemsCollection = new Collection(
196 | 0,
197 | 0,
198 | encodeAddress(accounts[0].address, 2),
199 | CHUNKY_ITEMS_COLLECTION_SYMBOL,
200 | collectionId,
201 | collectionMetadataCid
202 | );
203 |
204 | const { block } = await signAndSendWithRetry(
205 | api.tx.system.remark(ItemsCollection.create()),
206 | kp
207 | );
208 | console.log("Chunky items collection created at block: ", block);
209 |
210 | return block;
211 | } catch (error: any) {
212 | console.error(error);
213 | }
214 | };
215 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v1/Chunky_hand_v1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v2/Chunky_hand_v2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v3/Chunky_hand_v3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v4/Chunky_hand_v4.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v4/Chunky_head_v4.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/react-demo/public/chunky-dump.json:
--------------------------------------------------------------------------------
1 | {"nfts":{"209-d43593c715a56da27d-CHNK-chunky_bird_1-00000001":{"changes":[],"children":[{"id":"526-d43593c715a56da27d-CHNKITMS-chunky_bone-00000001","pending":false,"equipped":"base-13-CHNKBS.chunky_objectRight"}],"resources":[{"pending":false,"id":"nCkINZNg","base":"base-13-CHNKBS","parts":["chunky_body_1","chunky_head_1","chunky_hand_1","chunky_objectLeft","chunky_objectRight"],"thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Preview.png"}],"block":209,"collection":"d43593c715a56da27d-CHNK","symbol":"chunky_bird_1","transferable":1,"sn":"00000001","metadata":"ipfs://ipfs/bafkreiefmtvvjnqnlqs2dzavsyble5ft5ly7xtu4pg7tpo5gf4sovputwy","priority":["nCkINZNg"],"owner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"209-d43593c715a56da27d-CHNK-chunky_bird_1-00000001"},"209-d43593c715a56da27d-CHNK-chunky_bird_2-00000002":{"changes":[],"children":[{"id":"526-d43593c715a56da27d-CHNKITMS-chunky_flag-00000002","pending":false,"equipped":"base-13-CHNKBS.chunky_objectLeft"}],"resources":[{"pending":false,"id":"b4HIImJD","base":"base-13-CHNKBS","parts":["chunky_body_2","chunky_head_2","chunky_hand_2","chunky_objectLeft","chunky_objectRight"],"thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Preview.png"}],"block":209,"collection":"d43593c715a56da27d-CHNK","symbol":"chunky_bird_2","transferable":1,"sn":"00000002","metadata":"ipfs://ipfs/bafkreidfmr2jukykcxdnkt4qtgttr64ob5oidpi3gakxqqnx7k5zeiinvi","priority":["b4HIImJD"],"owner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"209-d43593c715a56da27d-CHNK-chunky_bird_2-00000002"},"209-d43593c715a56da27d-CHNK-chunky_bird_3-00000003":{"changes":[],"children":[{"id":"526-d43593c715a56da27d-CHNKITMS-chunky_pencil-00000003","pending":false,"equipped":"base-13-CHNKBS.chunky_objectRight"}],"resources":[{"pending":false,"id":"gIs2u9fw","base":"base-13-CHNKBS","parts":["chunky_body_3","chunky_head_3","chunky_hand_3","chunky_objectLeft","chunky_objectRight"],"thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Preview.png"}],"block":209,"collection":"d43593c715a56da27d-CHNK","symbol":"chunky_bird_3","transferable":1,"sn":"00000003","metadata":"ipfs://ipfs/bafkreia45odhqo53gqzi7gfa7a56zp6m6vmjidgurrvrlg5i7ces7b7xri","priority":["gIs2u9fw"],"owner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"209-d43593c715a56da27d-CHNK-chunky_bird_3-00000003"},"209-d43593c715a56da27d-CHNK-chunky_bird_4-00000004":{"changes":[],"children":[{"id":"526-d43593c715a56da27d-CHNKITMS-chunky_spear-00000004","pending":false,"equipped":"base-13-CHNKBS.chunky_objectLeft"}],"resources":[{"pending":false,"id":"JoFWZKXr","base":"base-13-CHNKBS","parts":["chunky_body_4","chunky_head_4","chunky_hand_4","chunky_objectLeft","chunky_objectRight"],"thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Preview.png"},{"pending":false,"id":"pibnOWHo","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/chunky_altresource.jpg","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/chunky_altresource.jpg"}],"block":209,"collection":"d43593c715a56da27d-CHNK","symbol":"chunky_bird_4","transferable":1,"sn":"00000004","metadata":"ipfs://ipfs/bafkreiafyggf5wgxgpoftd4xzjdkyxwdmr2qhg75nx5y3kfelmj234eq7m","priority":["pibnOWHo","JoFWZKXr"],"owner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"209-d43593c715a56da27d-CHNK-chunky_bird_4-00000004"},"526-d43593c715a56da27d-CHNKITMS-chunky_bone-00000001":{"changes":[{"field":"owner","old":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","new":"209-d43593c715a56da27d-CHNK-chunky_bird_1-00000001","caller":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","block":529,"opType":"SEND"}],"children":[],"resources":[{"pending":false,"id":"8DicBEHt","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_bone_left.svg","slot":"base-13-CHNKBS.chunky_objectLeft","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_bone_thumb.png"},{"pending":false,"id":"wKnVWhlT","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_bone_right.svg","slot":"base-13-CHNKBS.chunky_objectRight","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_bone_thumb.png"}],"block":526,"collection":"d43593c715a56da27d-CHNKITMS","symbol":"chunky_bone","transferable":1,"sn":"00000001","metadata":"ipfs://ipfs/bafkreifbdlldfpfz4vpuv6jmgbtcvbux3djgeps6n4ldhobagdp6ntan54","priority":["8DicBEHt","wKnVWhlT"],"owner":"209-d43593c715a56da27d-CHNK-chunky_bird_1-00000001","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"526-d43593c715a56da27d-CHNKITMS-chunky_bone-00000001"},"526-d43593c715a56da27d-CHNKITMS-chunky_flag-00000002":{"changes":[{"field":"owner","old":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","new":"209-d43593c715a56da27d-CHNK-chunky_bird_2-00000002","caller":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","block":529,"opType":"SEND"}],"children":[],"resources":[{"pending":false,"id":"Ntc9RUMD","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_flag_left.svg","slot":"base-13-CHNKBS.chunky_objectLeft","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_flag_thumb.png"},{"pending":false,"id":"7rPrE7OV","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_flag_right.svg","slot":"base-13-CHNKBS.chunky_objectRight","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_flag_thumb.png"}],"block":526,"collection":"d43593c715a56da27d-CHNKITMS","symbol":"chunky_flag","transferable":1,"sn":"00000002","metadata":"ipfs://ipfs/bafkreicbipz6whc3zzr35sxmxe5acmovlwbf6hklklwueg3fqi4quecnq4","priority":["Ntc9RUMD","7rPrE7OV"],"owner":"209-d43593c715a56da27d-CHNK-chunky_bird_2-00000002","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"526-d43593c715a56da27d-CHNKITMS-chunky_flag-00000002"},"526-d43593c715a56da27d-CHNKITMS-chunky_pencil-00000003":{"changes":[{"field":"owner","old":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","new":"209-d43593c715a56da27d-CHNK-chunky_bird_3-00000003","caller":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","block":529,"opType":"SEND"}],"children":[],"resources":[{"pending":false,"id":"P4DrAeb9","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_pencil_left.svg","slot":"base-13-CHNKBS.chunky_objectLeft","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_pencil_thumb.png"},{"pending":false,"id":"cC72og79","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_pencil_right.svg","slot":"base-13-CHNKBS.chunky_objectRight","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_pencil_thumb.png"}],"block":526,"collection":"d43593c715a56da27d-CHNKITMS","symbol":"chunky_pencil","transferable":1,"sn":"00000003","metadata":"ipfs://ipfs/bafkreibvrtwnap4uxqszojrbluju6pgaogji4rtznce6nl6mesqn63745i","priority":["P4DrAeb9","cC72og79"],"owner":"209-d43593c715a56da27d-CHNK-chunky_bird_3-00000003","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"526-d43593c715a56da27d-CHNKITMS-chunky_pencil-00000003"},"526-d43593c715a56da27d-CHNKITMS-chunky_spear-00000004":{"changes":[{"field":"owner","old":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","new":"209-d43593c715a56da27d-CHNK-chunky_bird_4-00000004","caller":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","block":529,"opType":"SEND"}],"children":[],"resources":[{"pending":false,"id":"deOEn2mu","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_spear_left.svg","slot":"base-13-CHNKBS.chunky_objectLeft","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_spear_thumb.png"},{"pending":false,"id":"IPfzmKE3","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_spear_right.svg","slot":"base-13-CHNKBS.chunky_objectRight","thumb":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/Chunky Items/Chunky_spear_thumb.png"}],"block":526,"collection":"d43593c715a56da27d-CHNKITMS","symbol":"chunky_spear","transferable":1,"sn":"00000004","metadata":"ipfs://ipfs/bafkreihk23kbw5sfn7vcd777vaj3d7jz6nw5zocwt6gvfvnun7gipuly7m","priority":["deOEn2mu","IPfzmKE3"],"owner":"209-d43593c715a56da27d-CHNK-chunky_bird_4-00000004","rootowner":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","reactions":{},"forsale":"0","burned":"","properties":{},"pending":false,"id":"526-d43593c715a56da27d-CHNKITMS-chunky_spear-00000004"}},"collections":{"d43593c715a56da27d-CHNK":{"changes":[],"block":204,"max":10000,"issuer":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","symbol":"CHNK","id":"d43593c715a56da27d-CHNK","metadata":"ipfs://ipfs/bafkreibzeyetfssguxrzoltvluyjac7hp3bzvzgpa27jkbpek23tqkfpmi"},"d43593c715a56da27d-CHNKITMS":{"changes":[],"block":522,"max":0,"issuer":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","symbol":"CHNKITMS","id":"d43593c715a56da27d-CHNKITMS","metadata":"ipfs://ipfs/bafkreidaprw5rjxkmnahoqtxnfvlj6zw4sje7ogqqq4itbzjgkp3sqkomq"}},"bases":{"base-13-CHNKBS":{"changes":[],"block":13,"symbol":"CHNKBS","type":"svg","issuer":"HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F","parts":[{"type":"fixed","id":"chunky_body_1","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v1/Chunky_body_v1.svg","z":0},{"type":"fixed","id":"chunky_body_2","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v2/Chunky_body_v2.svg","z":0},{"type":"fixed","id":"chunky_body_3","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v3/Chunky_body_v3.svg","z":0},{"type":"fixed","id":"chunky_body_4","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v4/Chunky_body_v4.svg","z":0},{"type":"fixed","id":"chunky_head_1","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v1/Chunky_head_v1.svg","z":4},{"type":"fixed","id":"chunky_head_2","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v2/Chunky_head_v2.svg","z":4},{"type":"fixed","id":"chunky_head_3","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v3/Chunky_head_v3.svg","z":4},{"type":"fixed","id":"chunky_head_4","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v4/Chunky_head_v4.svg","z":4},{"type":"fixed","id":"chunky_hand_1","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v1/Chunky_hand_v1.svg","z":3},{"type":"fixed","id":"chunky_hand_2","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v2/Chunky_hand_v2.svg","z":3},{"type":"fixed","id":"chunky_hand_3","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v3/Chunky_hand_v3.svg","z":3},{"type":"fixed","id":"chunky_hand_4","src":"ipfs://ipfs/Qmax4X3v382ZsWY6msE7vcnRw351BKMo5uZ8G8oBBQBDKT/v4/Chunky_hand_v4.svg","z":3},{"type":"slot","id":"chunky_objectLeft","equippable":["d43593c715a56da27d-CHNKITMS"],"z":1},{"type":"slot","id":"chunky_objectRight","equippable":["d43593c715a56da27d-CHNKITMS"],"z":2}],"id":"base-13-CHNKBS"}},"invalid":[],"lastBlock":535}
2 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v3/Chunky_head_v3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/assets/chunky/v2/Chunky_head_v2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/projects/scripts/mint-ukraine-stamp-book.ts:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | import { Base, Collection, NFT } from "rmrk-tools";
3 | import {
4 | chunkArray,
5 | getKeyringFromUri,
6 | sleep,
7 | } from "./utils";
8 | import { u8aToHex } from "@polkadot/util";
9 | import { encodeAddress } from "@polkadot/keyring";
10 | import getKeys from "./devaccs";
11 | import { uploadAndPinIpfsMetadata } from "./pinata-utils";
12 | import { WS_URL } from "./constants";
13 | import { IBasePart } from "rmrk-tools/dist/classes/base";
14 | import { cryptoWaitReady } from "@polkadot/util-crypto";
15 | import { nanoid } from "nanoid";
16 | import { Resource } from "rmrk-tools/dist/classes/nft";
17 | import { signAndSendWithRetry } from './sign-and-send-with-retry';
18 | import { getApi } from './get-polkadot-api';
19 |
20 | const STAMPS_FOR_UKRAINE_ASSETS_CID =
21 | "QmW8kMq1rQEj7ayiPU3ii1fVhR2gN5MFTohVxwLENTCMv6";
22 |
23 | const SYMBOL = "STAMPFRUKRN";
24 | const SYMBOL_BOOK = "STMPBK";
25 | const SYMBOL_STAMP = "STMP";
26 |
27 | const slotParts = {
28 | "Slot 1": 100,
29 | "Slot 2": 100,
30 | "Slot 3": 100,
31 | "Slot 4": 100,
32 | "Slot 5": 100,
33 | "Slot 6": 100,
34 | "Slot 7": 100,
35 | "Slot 8": 100,
36 | "Slot 9": 100,
37 | "Slot 10": 100,
38 | "Slot 11 deluxe": 50,
39 | "Slot 12 deluxe": 25,
40 | };
41 |
42 | const stampMetadata = {
43 | "Slot 1": {
44 | name: "St. Andrews Church, Kyiv",
45 | description:
46 | "To support the Ukrainian people in these and future days.\n" +
47 | "Thank you",
48 | thumbs: ["Stamp_1_preview_b_n.png", "Stamp_1_preview_b_y.png"],
49 | },
50 | "Slot 2": {
51 | name: "Monument of the Founders of KYIV",
52 | description:
53 | "To support the Ukrainian people in these and future days.\n" +
54 | "Thank you",
55 | thumbs: ["Stamp_2_preview_b_n.png", "Stamp_2_preview_b_y.png"],
56 | },
57 | "Slot 3": {
58 | name: "Sofiyivka park, Uman",
59 | description:
60 | "To support the Ukrainian people in these and future days.\n" +
61 | "Thank you",
62 | thumbs: ["Stamp_3_preview_b_n.png", "Stamp_3_preview_b_y.png"],
63 | },
64 | "Slot 4": {
65 | name: "Swallow's Nest, Gaspra",
66 | description:
67 | "To support the Ukrainian people in these and future days.\n" +
68 | "Thank you",
69 | thumbs: ["Stamp_4_preview_b_n.png", "Stamp_4_preview_b_y.png"],
70 | },
71 | "Slot 5": {
72 | name: "Kamianets podilskyi castle",
73 | description:
74 | "To support the Ukrainian people in these and future days.\n" +
75 | "Thank you",
76 | thumbs: ["Stamp_5_preview_b_n.png", "Stamp_5_preview_b_y.png"],
77 | },
78 | "Slot 6": {
79 | name: "Catherine's Church, Chernihiv",
80 | description:
81 | "To support the Ukrainian people in these and future days.\n" +
82 | "Thank you",
83 | thumbs: ["Stamp_6_preview_b_n.png", "Stamp_6_preview_b_y.png"],
84 | },
85 | "Slot 7": {
86 | name: "Independence Square, Kyiv",
87 | description:
88 | "To support the Ukrainian people in these and future days.\n" +
89 | "Thank you",
90 | thumbs: ["Stamp_7_preview_b_n.png", "Stamp_7_preview_b_y.png"],
91 | },
92 | "Slot 8": {
93 | name: "Lviv National Academic Opera",
94 | description:
95 | "To support the Ukrainian people in these and future days.\n" +
96 | "Thank you",
97 | thumbs: ["Stamp_8_preview_b_n.png", "Stamp_8_preview_b_y.png"],
98 | },
99 | "Slot 9": {
100 | name: "Museum of Zaporizhian Cossacks",
101 | description:
102 | "To support the Ukrainian people in these and future days.\n" +
103 | "Thank you",
104 | thumbs: ["Stamp_9_preview_b_n.png", "Stamp_9_preview_b_y.png"],
105 | },
106 | "Slot 10": {
107 | name: "Odessa National Academic Theater of Opera and Ballet",
108 | description:
109 | "To support the Ukrainian people in these and future days.\n" +
110 | "Thank you",
111 | thumbs: ["Stamp_10_preview_y_b.png", "Stamp_10_preview_y_n.png"],
112 | },
113 | "Slot 11 deluxe": {
114 | name: "Ukraine!",
115 | description:
116 | "To support the Ukrainian people in these and future days.\n" +
117 | "Thank you",
118 | thumbs: ["Stamp_11_preview.png"],
119 | },
120 | "Slot 12 deluxe": {
121 | name: "Ukraine!",
122 | description:
123 | "To support the Ukrainian people in these and future days.\n" +
124 | "Thank you",
125 | thumbs: ["Stamp_12_preview.png"],
126 | },
127 | };
128 |
129 | export const createBase = async () => {
130 | try {
131 | await cryptoWaitReady();
132 |
133 | const ws = WS_URL;
134 | const phrase = process.env.STAND_WITH_UKRAINE_SEED;
135 |
136 | const kp = getKeyringFromUri(phrase);
137 | const accounts = getKeys(phrase);
138 | const issuer = encodeAddress(accounts[0].address, 2);
139 |
140 | const collectionId = Collection.generateId(
141 | u8aToHex(accounts[0].publicKey),
142 | SYMBOL
143 | );
144 | const collectionMetadataCid = await uploadAndPinIpfsMetadata({
145 | mediaUri: `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/PFP.png`,
146 | description:
147 | "Postage Stamp for Ukraine is a collection with the purpose of fundraising for the people of Ukraine who suffered from the war.\n" +
148 | "\n" +
149 | "The collection uses the RMRK2.0 standard, and consists of a book on which stamps can be collected. Each stamp is from a symbolic place of Ukraine and are limited edition. Other stamps can always be created for the purpose of the Ukrainian people.\n" +
150 | "\n" +
151 | "We must not stand still in front of the events of these days, supporting the Ukrainian people is a moral duty, even a small gesture is important. All the funds raised will be sent directly to Ukrainian associations which will use them to support the population.\n" +
152 | "\n" +
153 | "Support Ukraine too\n" +
154 | "Thank you",
155 | name: "Stamp For Ukraine",
156 | });
157 |
158 | const baseMetadataCid = await uploadAndPinIpfsMetadata({
159 | mediaUri: `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/PFP.png`,
160 | name: "Stamp For Ukraine",
161 | });
162 |
163 | const Cl = new Collection(
164 | 0,
165 | 0,
166 | issuer,
167 | SYMBOL,
168 | collectionId,
169 | collectionMetadataCid
170 | );
171 |
172 | const remarks = [Cl.create()];
173 | const baseParts: IBasePart[] = [
174 | {
175 | type: "fixed",
176 | id: "Book",
177 | src: `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/Book_empty.svg`,
178 | z: 0,
179 | },
180 | {
181 | type: "slot",
182 | id: "Stamp 1",
183 | equippable: [collectionId],
184 | z: 1,
185 | },
186 | {
187 | type: "slot",
188 | id: "Stamp 2",
189 | equippable: [collectionId],
190 | z: 2,
191 | },
192 | {
193 | type: "slot",
194 | id: "Stamp 3",
195 | equippable: [collectionId],
196 | z: 3,
197 | },
198 | {
199 | type: "slot",
200 | id: "Stamp 4",
201 | equippable: [collectionId],
202 | z: 4,
203 | },
204 | {
205 | type: "slot",
206 | id: "Stamp 5",
207 | equippable: [collectionId],
208 | z: 5,
209 | },
210 | {
211 | type: "slot",
212 | id: "Stamp 6",
213 | equippable: [collectionId],
214 | z: 6,
215 | },
216 | ];
217 |
218 | const base = new Base(
219 | 0,
220 | SYMBOL,
221 | issuer,
222 | "svg",
223 | baseParts,
224 | undefined,
225 | baseMetadataCid
226 | );
227 |
228 | remarks.push(base.base());
229 | const api = await getApi();
230 |
231 | const txs = remarks.map((remark) => api.tx.system.remark(remark));
232 | const tx = api.tx.utility.batch(txs);
233 | const { block } = await signAndSendWithRetry(tx, kp);
234 |
235 | console.log("done at block:", block);
236 |
237 | return block;
238 | } catch (error: any) {
239 | console.error(error);
240 | process.exit(1);
241 | }
242 | };
243 |
244 | const CHUNK_SIZE = 70;
245 |
246 | export const mintBooks = async () => {
247 | try {
248 | const baseBlock = await createBase();
249 | await cryptoWaitReady();
250 |
251 | const ws = WS_URL;
252 | const phrase = process.env.STAND_WITH_UKRAINE_SEED;
253 |
254 | const kp = getKeyringFromUri(phrase);
255 | const accounts = getKeys(phrase);
256 | const issuer = encodeAddress(accounts[0].address, 2);
257 |
258 | const collectionId = Collection.generateId(
259 | u8aToHex(accounts[0].publicKey),
260 | SYMBOL
261 | );
262 | const base = new Base(baseBlock, SYMBOL, issuer, "png", []);
263 |
264 | const metadataCid = await uploadAndPinIpfsMetadata({
265 | mediaUri: `ipfs://ipfs/QmQCTD9h9ejWtnPyR6tDNwJzhLHDFq8FtABM7NceBA5tpo`,
266 | description:
267 | "Create your stamp collection!\n" +
268 | "\n" +
269 | "To support the Ukrainian people in these and future days.\n" +
270 | "Thank you",
271 | name: "Collector's book",
272 | });
273 |
274 | const total = 200;
275 |
276 | const arrayForTheSakeOfIt = Array(total).fill(issuer);
277 |
278 | let recipientsChunked = chunkArray(arrayForTheSakeOfIt, CHUNK_SIZE);
279 | let chunkIndex = 0;
280 |
281 | for (const recipientChunk of recipientsChunked) {
282 | await sleep(1500);
283 | const remarks = [];
284 |
285 | recipientChunk.forEach((recipient, index) => {
286 | const sn = `${chunkIndex * CHUNK_SIZE + index + 1}`.padStart(8, "0");
287 |
288 | const nft = new NFT({
289 | block: 0,
290 | collection: collectionId,
291 | symbol: SYMBOL_BOOK,
292 | transferable: 1,
293 | sn,
294 | owner: issuer,
295 | metadata: metadataCid,
296 | });
297 |
298 | remarks.push(nft.mint());
299 | });
300 |
301 | const api = await getApi();
302 | const txs = remarks.map((remark) => api.tx.system.remark(remark));
303 |
304 | const tx = api.tx.utility.batch(txs);
305 | const { block } = await signAndSendWithRetry(tx, kp);
306 |
307 | const resaddAndSendRemarks = [];
308 | recipientChunk.forEach((recipient, index) => {
309 | const sn = `${chunkIndex * CHUNK_SIZE + index + 1}`.padStart(8, "0");
310 | const nft = new NFT({
311 | block,
312 | collection: collectionId,
313 | symbol: SYMBOL_BOOK,
314 | transferable: 1,
315 | sn,
316 | owner: issuer,
317 | metadata: metadataCid,
318 | });
319 |
320 | const res: Resource = {
321 | thumb: `ipfs://ipfs/QmQCTD9h9ejWtnPyR6tDNwJzhLHDFq8FtABM7NceBA5tpo`,
322 | base: base.getId(),
323 | parts: [
324 | "Book",
325 | "Stamp 1",
326 | "Stamp 2",
327 | "Stamp 3",
328 | "Stamp 4",
329 | "Stamp 5",
330 | "Stamp 6",
331 | ],
332 | id: `${SYMBOL}_${nanoid(8)}`,
333 | metadata: metadataCid,
334 | };
335 |
336 | resaddAndSendRemarks.push(nft.resadd(res));
337 | });
338 |
339 | const resaddtxs = resaddAndSendRemarks.map((remark) =>
340 | api.tx.system.remark(remark)
341 | );
342 | let rmrksChunked = chunkArray(resaddtxs, 100);
343 |
344 | console.log(resaddAndSendRemarks);
345 |
346 | for (const rmrkChunk of rmrksChunked) {
347 | console.log(`Chunk size: ${rmrkChunk.length}`);
348 | const tx = api.tx.utility.batch(rmrkChunk);
349 | console.log("tx create");
350 | const { block } = await signAndSendWithRetry(tx, kp);
351 | console.log("Book base resource added: ", block);
352 | }
353 |
354 | chunkIndex = chunkIndex + 1;
355 | }
356 |
357 | console.log("ALL MINTED");
358 | return baseBlock;
359 | } catch (error: any) {
360 | console.error(error);
361 | process.exit(1);
362 | }
363 | };
364 |
365 | export const mintStamps = async () => {
366 | try {
367 | const baseBlock = await mintBooks();
368 | await cryptoWaitReady();
369 |
370 | const ws = WS_URL;
371 | const phrase = process.env.STAND_WITH_UKRAINE_SEED;
372 |
373 | const kp = getKeyringFromUri(phrase);
374 | const accounts = getKeys(phrase);
375 | const issuer = encodeAddress(accounts[0].address, 2);
376 |
377 | const collectionId = Collection.generateId(
378 | u8aToHex(accounts[0].publicKey),
379 | SYMBOL
380 | );
381 | const base = new Base(baseBlock, SYMBOL, issuer, "png", []);
382 |
383 | for (const slotPartId of Object.keys(slotParts)) {
384 | await sleep(1500);
385 | const stampIndex = Object.keys(slotParts).indexOf(slotPartId);
386 | const total = slotParts[slotPartId];
387 | const metadata = stampMetadata[slotPartId];
388 |
389 | const metadataCidEven = await uploadAndPinIpfsMetadata({
390 | mediaUri: `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/${metadata.thumbs[0]}`,
391 | description: metadata.description,
392 | name: metadata.name,
393 | });
394 |
395 | const metadataCidOdd = await uploadAndPinIpfsMetadata({
396 | mediaUri: `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/${metadata.thumbs[1]}`,
397 | description: metadata.description,
398 | name: metadata.name,
399 | });
400 |
401 | const arrayForTheSakeOfIt = Array(total).fill(issuer);
402 |
403 | let recipientsChunked = chunkArray(arrayForTheSakeOfIt, CHUNK_SIZE);
404 | let chunkIndex = 0;
405 |
406 | for (const recipientChunk of recipientsChunked) {
407 | await sleep(1500);
408 | const remarks = [];
409 |
410 | recipientChunk.forEach((recipient, index) => {
411 | const sn = `${chunkIndex * CHUNK_SIZE + index + 1}`.padStart(8, "0");
412 | const isOdd = index % 2;
413 | const nft = new NFT({
414 | block: 0,
415 | collection: collectionId,
416 | symbol: SYMBOL_STAMP,
417 | transferable: 1,
418 | sn,
419 | owner: issuer,
420 | metadata: isOdd ? metadataCidOdd : metadataCidEven,
421 | });
422 |
423 | remarks.push(nft.mint());
424 | });
425 |
426 | const api = await getApi();
427 | const txs = remarks.map((remark) => api.tx.system.remark(remark));
428 |
429 | const tx = api.tx.utility.batch(txs);
430 | const { block } = await signAndSendWithRetry(tx, kp);
431 |
432 | const resaddAndSendRemarks = [];
433 | recipientChunk.forEach((recipient, index) => {
434 | const sn = `${chunkIndex * CHUNK_SIZE + index + 1}`.padStart(8, "0");
435 | const isOdd = index % 2;
436 | const nft = new NFT({
437 | block,
438 | collection: collectionId,
439 | symbol: SYMBOL_STAMP,
440 | transferable: 1,
441 | sn,
442 | owner: issuer,
443 | metadata: isOdd ? metadataCidOdd : metadataCidEven,
444 | });
445 |
446 | for (let i = 0; i < 6; i++) {
447 | const res: Resource = {
448 | thumb: isOdd
449 | ? `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/${metadata.thumbs[0]}`
450 | : `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/${metadata.thumbs[1]}`,
451 | src: `ipfs://ipfs/${STAMPS_FOR_UKRAINE_ASSETS_CID}/Stamp_${
452 | stampIndex + 1
453 | }_slot_position_${i + 1}.svg`,
454 | id: `${SYMBOL_STAMP}_${nanoid(8)}`,
455 | metadata: isOdd ? metadataCidOdd : metadataCidEven,
456 | slot: `${base.getId()}.Stamp ${i + 1}`,
457 | };
458 |
459 | resaddAndSendRemarks.push(nft.resadd(res));
460 | }
461 | });
462 |
463 | const resaddtxs = resaddAndSendRemarks.map((remark) =>
464 | api.tx.system.remark(remark)
465 | );
466 | let rmrksChunked = chunkArray(resaddtxs, 100);
467 |
468 | console.log(resaddAndSendRemarks);
469 |
470 | for (const rmrkChunk of rmrksChunked) {
471 | console.log(`Chunk size: ${rmrkChunk.length}`);
472 | const tx = api.tx.utility.batch(rmrkChunk);
473 | console.log("tx create");
474 | const { block } = await signAndSendWithRetry(tx, kp);
475 | console.log("Stamp base resource added: ", block);
476 | }
477 |
478 | chunkIndex = chunkIndex + 1;
479 | }
480 | }
481 |
482 | console.log("ALL MINTED");
483 | process.exit(0);
484 | } catch (error: any) {
485 | console.error(error);
486 | process.exit(1);
487 | }
488 | };
489 |
490 | mintStamps();
491 |
492 | // createBase();
493 |
--------------------------------------------------------------------------------
/projects/scripts/mint-in-chunks.ts:
--------------------------------------------------------------------------------
1 | import {getApi} from "./get-polkadot-api";
2 |
3 | require("dotenv").config();
4 |
5 | import { Resource } from "rmrk-tools/dist/classes/nft";
6 | import { cryptoWaitReady } from "@polkadot/util-crypto";
7 | import { isProd, WS_URL } from "./constants";
8 | import {
9 | chunkArray,
10 | getKeyringFromUri,
11 | getKeys,
12 | sleep,
13 | } from "./utils";
14 | import { Collection, NFT } from "rmrk-tools";
15 | import { encodeAddress } from "@polkadot/keyring";
16 | import { nanoid } from "nanoid";
17 | import fs from "fs";
18 | import {
19 | uploadAndPinIpfsMetadata,
20 | pinFileStreamToIpfs,
21 | StreamPinata,
22 | } from "./pinata-utils";
23 | import { IProperties } from "rmrk-tools/dist/tools/types";
24 | import { Readable } from "stream";
25 | import { u8aToHex } from "@polkadot/util";
26 | import {signAndSendWithRetry} from "./sign-and-send-with-retry";
27 | const BASE_ID = isProd ? "CHANGEME" : "CHANGEME";
28 |
29 | const fsPromises = fs.promises;
30 |
31 | interface TomoMintingResource {
32 | description: string;
33 | base?: string;
34 | src: string;
35 | slot?: string;
36 | thumb?: string;
37 | }
38 |
39 | interface IMintingRoyalties {
40 | royaltyPercentFloat: string;
41 | reciever: string;
42 | }
43 |
44 | const BANNERVERSE_COLLECTION_ID = "e0b9bdcc456a36497a-RMRKBNNRS";
45 | const BANNERVERSE_COLLECTION_SYMBOL = "RMRKBNNRS";
46 | const COMMISSION_ADDRESS = "CHANGEME";
47 | const MINTER_ADDRESS = "CHANGEME";
48 |
49 | interface BatchMintingObject {
50 | collection: {
51 | id: string;
52 | symbol: string;
53 | };
54 | symbol: string;
55 | title: string;
56 | description: string;
57 | recipients: Record;
58 | properties: { context?: string; type?: string }[];
59 | royalties: IMintingRoyalties[];
60 | resources: TomoMintingResource[];
61 | }
62 |
63 | const mintObject: Record = {
64 | 1: {
65 | collection: {
66 | id: BANNERVERSE_COLLECTION_ID,
67 | symbol: BANNERVERSE_COLLECTION_SYMBOL,
68 | },
69 | symbol: "BNNRX",
70 | title: "X",
71 | description:
72 | "February '22 RMRK Banner. The search continues. Where is the treasure buried? X Marks the Spot.",
73 | properties: [],
74 | royalties: [
75 | {
76 | reciever: COMMISSION_ADDRESS,
77 | royaltyPercentFloat: "5",
78 | },
79 | ],
80 | resources: [
81 | {
82 | description:
83 | "February '22 RMRK Banner. The search continues. Where is the treasure buried? X Marks the Spot.",
84 | src: "banners_02_22/2022 Feb_Banner 01 - Multiresource.png",
85 | thumb: "banners_02_22/2022 Feb_Banner 01 - Multiresource.png",
86 | },
87 | ],
88 | recipients: {
89 | [MINTER_ADDRESS]: "30",
90 | },
91 | },
92 | 2: {
93 | collection: {
94 | id: BANNERVERSE_COLLECTION_ID,
95 | symbol: BANNERVERSE_COLLECTION_SYMBOL,
96 | },
97 | symbol: "BNNRCND",
98 | title: "Candyland",
99 | description:
100 | "February '22 RMRK Banner. Sometimes all you need is candy and color.",
101 | properties: [],
102 | royalties: [
103 | {
104 | reciever: MINTER_ADDRESS,
105 | royaltyPercentFloat: "5",
106 | },
107 | ],
108 | resources: [
109 | {
110 | description:
111 | "February '22 RMRK Banner. Sometimes all you need is candy and color.",
112 | src: "banners_02_22/2022 Feb_Banner 02 - Candy.png",
113 | thumb: "banners_02_22/2022 Feb_Banner 02 - Candy.png",
114 | },
115 | ],
116 | recipients: {
117 | [MINTER_ADDRESS]: "47",
118 | DhvJ4ZEKr75kBtr3VSwem84jbZCfmjUCZ771sf33Z5mX8Ta: "1",
119 | H2SwfhnSStX91iGZGye9L3wetRKahk1BZMCuSk7aXU66aFQ: "1",
120 | CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp: "1",
121 | },
122 | },
123 | 3: {
124 | collection: {
125 | id: BANNERVERSE_COLLECTION_ID,
126 | symbol: BANNERVERSE_COLLECTION_SYMBOL,
127 | },
128 | symbol: "BNNRDSTRT",
129 | title: "Distort",
130 | description:
131 | "February '22 RMRK Banner. Distortion. Disturbance. Deformity. What do you see?",
132 | properties: [],
133 | royalties: [
134 | {
135 | reciever: MINTER_ADDRESS,
136 | royaltyPercentFloat: "5",
137 | },
138 | ],
139 | resources: [
140 | {
141 | description:
142 | "February '22 RMRK Banner. Distortion. Disturbance. Deformity. What do you see?",
143 | src: "banners_02_22/2022 Feb_Banner 03 - RMRK 2.0.png",
144 | thumb: "banners_02_22/2022 Feb_Banner 03 - RMRK 2.0.png",
145 | },
146 | ],
147 | recipients: {
148 | [MINTER_ADDRESS]: "95",
149 | DhvJ4ZEKr75kBtr3VSwem84jbZCfmjUCZ771sf33Z5mX8Ta: "1",
150 | H2SwfhnSStX91iGZGye9L3wetRKahk1BZMCuSk7aXU66aFQ: "1",
151 | CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp: "1",
152 | DJiT2VbsbvA6EMiUjtoXLuBXNCscexS3GUx3fxR1JwK8KMr: "1",
153 | EDQwsrUJQbBRxc5K5p1xKb55jhsr18fngdHghQQeTxEZLoM: "1",
154 | },
155 | },
156 | 4: {
157 | collection: {
158 | id: BANNERVERSE_COLLECTION_ID,
159 | symbol: BANNERVERSE_COLLECTION_SYMBOL,
160 | },
161 | symbol: "BNNRDRK2",
162 | title: "RMRK2 Dark",
163 | description:
164 | "February '22 RMRK Banner. Finally, it's here! Darkly commemorating the launch of RMRK2 on Singular. But what's that in the back?",
165 | properties: [],
166 | royalties: [
167 | {
168 | reciever: MINTER_ADDRESS,
169 | royaltyPercentFloat: "5",
170 | },
171 | ],
172 | resources: [
173 | {
174 | description:
175 | "February '22 RMRK Banner. Finally, it's here! Darkly commemorating the launch of RMRK2 on Singular. But what's that in the back?",
176 | src: "banners_02_22/2022 Feb_Banner 04 - On singular dark.png",
177 | thumb: "banners_02_22/2022 Feb_Banner 04 - On singular dark.png",
178 | },
179 | ],
180 | recipients: {
181 | [MINTER_ADDRESS]: "228",
182 | DhvJ4ZEKr75kBtr3VSwem84jbZCfmjUCZ771sf33Z5mX8Ta: "1",
183 | H2SwfhnSStX91iGZGye9L3wetRKahk1BZMCuSk7aXU66aFQ: "1",
184 | CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp: "1",
185 | DJiT2VbsbvA6EMiUjtoXLuBXNCscexS3GUx3fxR1JwK8KMr: "1",
186 | EDQwsrUJQbBRxc5K5p1xKb55jhsr18fngdHghQQeTxEZLoM: "1",
187 | EB1oqZ5MEnEwxhJ5DySCH3pyY55a2CUDfAbYKmLz2QcqWgx: "1",
188 | EuJAYheXvPywhDqB9YYG9RYbp2iENUqT261FPRhhTioPxSu: "1",
189 | ErU55Vp3AGFGEbrL5Tj1Kv47xYBDU1McBA1ALPewgVjRZDn: "1",
190 | CmkHUWRBZWp8vh3t9ZMyddx3DHjqfv2jErXrECtcizxoGc2: "1",
191 | GhQgLK4MAj564SUQgbV5EeQRva8L9AUEkqT28mZsmCS1egv: "1",
192 | HpbFHQjLCCuBpBwS2wJe8pB4ZrgVxD6w2SGEE1ckzT3Uxs4: "1",
193 | GbqtkjvQKWsgovW69Ga4WBnJxH3fSv3XkP7yLWC33j4yaQB: "1",
194 | G2e4YrQ8CQ4yw7iy5yu9yB5vHxJjfS3q4T46ojazqsYkKjV: "1",
195 | HZXYG4jYFXwqwBrhnbjMUSXH49zV2CW8T1pEuHeQTfBNfq5: "1",
196 | HnnwFDsJiyThghLCmkbbQ2mtVrwzxjF5pWFUJbzWngDTvzj: "1",
197 | Fr6ovaQd8ysmjXEF76gQVCErsU8UYzXjYVKV2cTw9myK7A3: "1",
198 | HydwuGTL6vbThMEyZq5ygYqMeHvmSpTLRsZdLx313rcPv6M: "1",
199 | FRqF9tyZmKdSGoULHZC16GTf9RVNDvjhquq8tNwuDRjAaic: "1",
200 | G28YgXSEHW4EE7uR9N7vi7ULF2NEEtgsMCJLARD5Yj5a1Y7: "1",
201 | F2toN3eA5EWiWhggt14wDrmAiercTJvF8ye1tqHorQPyEM6: "1",
202 | EZv3htNfDpYt6m42xBaEbLr2Y2ZcPjHvY9y6tvZEcWBmefJ: "1",
203 | Fys7d6gikP6rLDF9dvhCJcAMaPrrLuHbGZRVgqLPn26fWmr: "1",
204 | },
205 | },
206 | 5: {
207 | collection: {
208 | id: BANNERVERSE_COLLECTION_ID,
209 | symbol: BANNERVERSE_COLLECTION_SYMBOL,
210 | },
211 | symbol: "BNNRLGHT2",
212 | title: "RMRK2 Light",
213 | description:
214 | "February '22 RMRK Banner. Finally, it's here! Brightly commemorating the launch of RMRK2 on Singular. But what's that in the back?",
215 | properties: [],
216 | royalties: [
217 | {
218 | reciever: MINTER_ADDRESS,
219 | royaltyPercentFloat: "5",
220 | },
221 | ],
222 | resources: [
223 | {
224 | description:
225 | "February '22 RMRK Banner. Finally, it's here! Brightly commemorating the launch of RMRK2 on Singular. But what's that in the back?",
226 | src: "banners_02_22/2022 Feb_Banner 05 - On singular light.png",
227 | thumb: "banners_02_22/2022 Feb_Banner 05 - On singular light.png",
228 | },
229 | ],
230 | recipients: {
231 | [MINTER_ADDRESS]: "228",
232 | DhvJ4ZEKr75kBtr3VSwem84jbZCfmjUCZ771sf33Z5mX8Ta: "1",
233 | H2SwfhnSStX91iGZGye9L3wetRKahk1BZMCuSk7aXU66aFQ: "1",
234 | CpjsLDC1JFyrhm3ftC9Gs4QoyrkHKhZKtK7YqGTRFtTafgp: "1",
235 | DJiT2VbsbvA6EMiUjtoXLuBXNCscexS3GUx3fxR1JwK8KMr: "1",
236 | EDQwsrUJQbBRxc5K5p1xKb55jhsr18fngdHghQQeTxEZLoM: "1",
237 | EB1oqZ5MEnEwxhJ5DySCH3pyY55a2CUDfAbYKmLz2QcqWgx: "1",
238 | EuJAYheXvPywhDqB9YYG9RYbp2iENUqT261FPRhhTioPxSu: "1",
239 | ErU55Vp3AGFGEbrL5Tj1Kv47xYBDU1McBA1ALPewgVjRZDn: "1",
240 | CmkHUWRBZWp8vh3t9ZMyddx3DHjqfv2jErXrECtcizxoGc2: "1",
241 | GhQgLK4MAj564SUQgbV5EeQRva8L9AUEkqT28mZsmCS1egv: "1",
242 | HpbFHQjLCCuBpBwS2wJe8pB4ZrgVxD6w2SGEE1ckzT3Uxs4: "1",
243 | GbqtkjvQKWsgovW69Ga4WBnJxH3fSv3XkP7yLWC33j4yaQB: "1",
244 | G2e4YrQ8CQ4yw7iy5yu9yB5vHxJjfS3q4T46ojazqsYkKjV: "1",
245 | HZXYG4jYFXwqwBrhnbjMUSXH49zV2CW8T1pEuHeQTfBNfq5: "1",
246 | HnnwFDsJiyThghLCmkbbQ2mtVrwzxjF5pWFUJbzWngDTvzj: "1",
247 | Fr6ovaQd8ysmjXEF76gQVCErsU8UYzXjYVKV2cTw9myK7A3: "1",
248 | HydwuGTL6vbThMEyZq5ygYqMeHvmSpTLRsZdLx313rcPv6M: "1",
249 | FRqF9tyZmKdSGoULHZC16GTf9RVNDvjhquq8tNwuDRjAaic: "1",
250 | G28YgXSEHW4EE7uR9N7vi7ULF2NEEtgsMCJLARD5Yj5a1Y7: "1",
251 | F2toN3eA5EWiWhggt14wDrmAiercTJvF8ye1tqHorQPyEM6: "1",
252 | EZv3htNfDpYt6m42xBaEbLr2Y2ZcPjHvY9y6tvZEcWBmefJ: "1",
253 | Fys7d6gikP6rLDF9dvhCJcAMaPrrLuHbGZRVgqLPn26fWmr: "1",
254 | },
255 | },
256 | };
257 |
258 | const CHUNK_SIZE = 70;
259 |
260 | export const mintInChunksDemo = async () => {
261 | try {
262 | console.log("CREATE batched NFTs START -------");
263 | const mintedIds = [];
264 | await cryptoWaitReady();
265 |
266 | const ws = WS_URL;
267 |
268 | const chunksMintDemoMint: Record = mintObject;
269 | const chunksMintDemoMintArray = Object.values(chunksMintDemoMint);
270 |
271 | const accounts = getKeys();
272 | const kp = getKeyringFromUri(process.env.PRIVATE_KEY as string);
273 |
274 | for (const chunksMintDemoMintItem of chunksMintDemoMintArray) {
275 | const collectionId = Collection.generateId(
276 | u8aToHex(accounts[0].publicKey),
277 | chunksMintDemoMintItem.collection.symbol
278 | );
279 | const owner = encodeAddress(accounts[0].address, 2);
280 | const recipients = Object.keys(chunksMintDemoMintItem.recipients)
281 | .map((recipient) => {
282 | return Array(parseInt(chunksMintDemoMintItem.recipients[recipient])).fill(
283 | recipient
284 | );
285 | })
286 | .flat();
287 |
288 | const thumbFile = await fsPromises.readFile(
289 | `${process.cwd()}/data/${chunksMintDemoMintItem.resources[0].thumb}`
290 | );
291 | const thumbStream: StreamPinata = Readable.from(thumbFile);
292 | thumbStream.path = chunksMintDemoMintItem.resources[0].thumb;
293 | const thumbCid = await pinFileStreamToIpfs(
294 | thumbStream,
295 | chunksMintDemoMintItem.title
296 | );
297 |
298 | const properties: IProperties = {};
299 |
300 | const typeProperty = chunksMintDemoMintItem.properties.find((property) =>
301 | Boolean(property.type)
302 | );
303 | if (typeProperty) {
304 | properties.type = {
305 | value: typeProperty.type,
306 | type: "string",
307 | };
308 | }
309 |
310 | const contextProperty = chunksMintDemoMintItem.properties.find((property) =>
311 | Boolean(property.context)
312 | );
313 | if (contextProperty) {
314 | properties.context = {
315 | value: contextProperty.context,
316 | type: "string",
317 | };
318 | }
319 |
320 | const metadataCid = await uploadAndPinIpfsMetadata({
321 | thumbnailUri: `ipfs://ipfs/${thumbCid}`,
322 | description: chunksMintDemoMintItem.description,
323 | name: chunksMintDemoMintItem.title,
324 | properties,
325 | });
326 |
327 | let recipientsChunked = chunkArray(recipients, CHUNK_SIZE);
328 | let chunkIndex = 0;
329 |
330 | for (const recipientChunk of recipientsChunked) {
331 | await sleep(1500);
332 | const remarks = [];
333 |
334 | recipientChunk.forEach((recipient, index) => {
335 | const sn = `${chunkIndex * CHUNK_SIZE + index + 1}`.padStart(8, "0");
336 |
337 | const isOdd = index % 2;
338 | const multipleRoyaltyRecipients =
339 | chunksMintDemoMintItem.royalties.length > 1;
340 | const royalties = multipleRoyaltyRecipients
341 | ? isOdd
342 | ? chunksMintDemoMintItem.royalties[1]
343 | : chunksMintDemoMintItem.royalties[0]
344 | : chunksMintDemoMintItem.royalties[0];
345 |
346 | const nft = new NFT({
347 | block: 0,
348 | collection: collectionId,
349 | symbol: chunksMintDemoMintItem.symbol,
350 | transferable: 1,
351 | sn,
352 | owner,
353 | metadata: metadataCid,
354 | properties: {
355 | royaltyInfo: {
356 | type: "royalty",
357 | value: {
358 | royaltyPercentFloat: parseFloat(
359 | royalties.royaltyPercentFloat
360 | ),
361 | receiver: royalties.reciever,
362 | },
363 | },
364 | },
365 | });
366 |
367 | remarks.push(nft.mint());
368 | });
369 |
370 | const api = await getApi();
371 | const txs = remarks.map((remark) => api.tx.system.remark(remark));
372 |
373 | const tx = api.tx.utility.batch(txs);
374 | const { block } = await signAndSendWithRetry(tx, kp);
375 | console.log("Chunks mint demo NFTs minted at block: ", block);
376 |
377 | const resourcesPinned: TomoMintingResource[] = [];
378 | for (const resource of chunksMintDemoMintItem.resources) {
379 | const thumbFile = await fsPromises.readFile(
380 | `${process.cwd()}/data/${resource.thumb}`
381 | );
382 | const thumbStream: StreamPinata = Readable.from(thumbFile);
383 | thumbStream.path = resource.thumb;
384 | const thumbCid = await pinFileStreamToIpfs(
385 | thumbStream,
386 | chunksMintDemoMintItem.title
387 | );
388 |
389 | const mediaFile = await fsPromises.readFile(
390 | `${process.cwd()}/data/${resource.src}`
391 | );
392 | const mediaStream: StreamPinata = Readable.from(mediaFile);
393 | mediaStream.path = resource.src;
394 | const mediaCid = await pinFileStreamToIpfs(
395 | mediaStream,
396 | chunksMintDemoMintItem.title
397 | );
398 |
399 | resourcesPinned.push({
400 | ...resource,
401 | src: `ipfs://ipfs/${mediaCid}`,
402 | thumb: `ipfs://ipfs/${thumbCid}`,
403 | });
404 | }
405 |
406 | const resaddAndSendRemarks = [];
407 | recipientChunk.forEach((recipient, index) => {
408 | const sn = `${chunkIndex * CHUNK_SIZE + index + 1}`.padStart(8, "0");
409 | const nft = new NFT({
410 | block,
411 | collection: collectionId,
412 | symbol: chunksMintDemoMintItem.symbol,
413 | transferable: 1,
414 | sn,
415 | owner,
416 | metadata: metadataCid,
417 | });
418 |
419 | mintedIds.push(nft.getId());
420 |
421 | resourcesPinned.forEach((resource) => {
422 | const res: Resource = {
423 | src: resource.src,
424 | thumb: resource.thumb,
425 | id: `${chunksMintDemoMintItem.symbol}_${nanoid(8)}`,
426 | metadata: metadataCid,
427 | };
428 |
429 | if (resource.slot) {
430 | res.slot = `${BASE_ID}.${resource.slot}`;
431 | }
432 | resaddAndSendRemarks.push(nft.resadd(res));
433 | });
434 |
435 | if (owner !== recipient) {
436 | resaddAndSendRemarks.push(nft.send(recipient));
437 | }
438 | });
439 |
440 | const resaddtxs = resaddAndSendRemarks.map((remark) =>
441 | api.tx.system.remark(remark)
442 | );
443 | let rmrksChunked = chunkArray(resaddtxs, 100);
444 |
445 | console.log(resaddAndSendRemarks);
446 |
447 | for (const rmrkChunk of rmrksChunked) {
448 | console.log(`Chunk size: ${rmrkChunk.length}`);
449 | const tx = api.tx.utility.batch(rmrkChunk);
450 | console.log("tx create");
451 | const { block } = await signAndSendWithRetry(tx, kp);
452 | console.log("Chunks mint demo resource added: ", block);
453 | }
454 |
455 | chunkIndex = chunkIndex + 1;
456 | }
457 | }
458 |
459 | console.log("ALL MINTED");
460 | fs.writeFileSync(
461 | `${process.cwd()}/data/minted-item-ids.json`,
462 | JSON.stringify(mintedIds)
463 | );
464 |
465 | process.exit(0);
466 | } catch (error: any) {
467 | console.error(error);
468 | process.exit(1);
469 | }
470 | };
471 |
472 | mintInChunksDemo();
473 |
--------------------------------------------------------------------------------