Searchโ€ฆ
Cardano NFT Collection Tutorial ๐Ÿ‘›

Prerequisites

If you haven't already, please watch our video from the previous NFT tutorial ๐Ÿ˜Ž

Clone the cardano-minter repo if you haven't already...

1
git clone https://github.com/armada-alliance/cardano-minter
2
cd cardano-minter
Copied!

Install additional dependencies

1
npm install form-data dotenv axios lodash sharp promise-parallel-throttle --save
Copied!

Now, let's start with the tutorial ๐Ÿ˜Š

1. Create our initial assets

    While in the "cardano-minter" directory create a script that will generate our assets in a nicely formatted JSON file called "assets.json".
1
nano create-initial-assets-json.js
Copied!
1
/**
2
* This script is responsible for generating the initial
3
* assets.json that can later be adjusted to fit your specific needs
4
*
5
* You can define:
6
* 1. amount of assets
7
* 2. whether you want to start the collection with either 1 or 0
8
* 3. what the mimeType is (jpeg, png or gif)
9
*/
10
โ€‹
11
const times = require('lodash/times')
12
const fs = require("fs").promises
13
โ€‹
14
const AMOUNT_OF_ASSETS = 15
15
const START_WITH_ZERO = true
16
const MIME_TYPE = 'image/png'
17
โ€‹
18
async function main() {
19
โ€‹
20
const assets = times(AMOUNT_OF_ASSETS).map(i => {
21
โ€‹
22
const number = START_WITH_ZERO ? i : i + 1
23
const id = `PIADA${number}` // PIADA0
24
โ€‹
25
const [extension] = MIME_TYPE.split("/").reverse() // png
26
โ€‹
27
return {
28
id,
29
name: `PIADA #${number}`,
30
// description: "",
31
image: `images/${id}_thumbnail.${extension}`, // images/PIADA0_thumbnail.png
32
src: `images/${id}.${extension}`, // images/PIADA0.png
33
type: MIME_TYPE,
34
// add whatever like below
35
authors: ["PIADA", "SBLYR"],
36
website: "https://ada-pi.io"
37
}
38
})
39
โ€‹
40
await fs.writeFile(__dirname + '/assets.json', JSON.stringify(assets, null, 2))
41
}
42
โ€‹
43
main()
Copied!
1
node src/create-initial-assets-json.js
Copied!
    Your assets.json file should look like this.

2. Download random images for testing

    Make a folder called images to download the test images into
    Create a script that will go and grab the images from the internet and download them into the images folder
1
cd src
2
nano download-test-images.js
Copied!
1
/**
2
* This script expect the assets.json to exist
3
* inside the src directory there should be a reference
4
* to a filepath on the local file system relative to the `src` dir
5
*/
6
โ€‹
7
const random = require('lodash/random')
8
const axios = require('axios')
9
const fs = require('fs').promises
10
โ€‹
11
const assets = require("./assets.json")
12
โ€‹
13
async function main() {
14
โ€‹
15
await Promise.all(
16
assets.map(async asset => {
17
โ€‹
18
const { data } = await axios.get(`https://source.unsplash.com/640x400?cat&v=${random()}`, { responseType: 'arraybuffer' })
19
console.log(`[${asset.name}] downloaded random cat image`)
20
โ€‹
21
await fs.writeFile(__dirname + '/' + asset.src, data)
22
console.log(`[${asset.name}] image saved to "${asset.src}"`)
23
})
24
)
25
}
26
โ€‹
27
main()
Copied!
1
node src/download-test-images.js
Copied!

3. Extend metadata.json with thumbnails (optional)

    generate thumbnails based on images from the metadata.json and give them the same name with _thumbnail tag added to the name
1
cd src
2
nano generate-thumbnails.js
Copied!
1
const fs = require('fs').promises
2
const sharp = require("sharp")
3
โ€‹
4
const generateThumbnail = (filePath, data) => new Promise(resolve => {
5
โ€‹
6
sharp(data)
7
.resize(300) // 640x400 (640 -> 200)
8
.toFile(filePath, resolve)
9
})
10
โ€‹
11
const assets = require('./assets.json')
12
โ€‹
13
async function main() {
14
โ€‹
15
await Promise.all(
16
assets.map(async asset => {
17
โ€‹
18
const data = await fs.readFile(__dirname + '/' + asset.src)
19
โ€‹
20
await generateThumbnail(__dirname + "/" + asset.image, data)
21
console.log(`[${asset.name}] thumbnail generated at "${asset.image}"`)
22
})
23
)
24
}
25
โ€‹
26
main()
Copied!
1
node src/generate-thumbnails.js
Copied!

4. Create our pinata.cloud account to get our API keys

    1.
    Create an account
    2.
    Create API keys

5. Need to safely store our API keys

    create .env file and paste in our keys
Make sure the .env file is in the cardano-minter directory but not in the src folder
1
nano .env
Copied!
1
PINATA_API_KEY='Enter Your API Key'
2
PINATA_API_SECRET='Enter Your API Secret Key'
Copied!

6. Upload and pin our data to IPFS

Read this article to learn more about why we want to Pin our NFTs to IPFS.
    First, we need to make a script called pin-to-ipfs.js, this script will "upload" and Pin our images to IPFS using the pinata.cloud API.
1
nano pin-to-ipfs.js
Copied!
1
const dotenv = require('dotenv')
2
dotenv.config()
3
const axios = require("axios")
4
const FormData = require('form-data')
5
const fs = require('fs')
6
โ€‹
7
const pinata = axios.create({
8
baseURL: 'https://api.pinata.cloud',
9
headers: {
10
pinata_api_key: process.env.PINATA_API_KEY,
11
pinata_secret_api_key: process.env.PINATA_API_SECRET
12
}
13
})
14
โ€‹
15
module.exports = async (name, filePath) => {
16
โ€‹
17
let data = new FormData()
18
data.append('file', fs.createReadStream(filePath))
19
โ€‹
20
const metadata = JSON.stringify({
21
name
22
})
23
โ€‹
24
data.append('pinataMetaData', metadata)
25
โ€‹
26
const pinataOptions = JSON.stringify({
27
cidVersion: 0,
28
customPinPolicy: {
29
regions: [
30
{
31
id: 'FRA1',
32
desiredReplicationCount: 1
33
},
34
{
35
id: 'NYC1',
36
desiredReplicationCount: 1
37
}
38
]
39
}
40
})
41
โ€‹
42
data.append('pinataOptions', pinataOptions)
43
โ€‹
44
const response = await pinata.post('/pinning/pinFileToIPFS', data, {
45
maxBodyLength: 'Infinity', // this is needed to prevent axios from erroring out with large files
46
headers: {
47
'Content-Type': `multipart/form-data; boundary=${data._boundary}`
48
}
49
}).catch(e => {
50
โ€‹
51
if (e.response) {
52
console.log(e.response.error)
53
} else {
54
console.log(e.message)
55
}
56
})
57
โ€‹
58
const hash = response.data.IpfsHash
59
โ€‹
60
return {
61
hash,
62
ipfsLink: `ipfs://${hash}`,
63
httpLink: `https://ipfs.io/ipfs/${hash}`
64
}
65
}
Copied!
1
cd ..
2
node src/pin-to-ipfs.js
Copied!
    Next, we can create a script called pin-images-to-ipfs.js, this will run through our images/assets and "pin" the images to IPFS using our local node.
1
cd src
2
nano pin-images-to-ipfs.js
Copied!
1
const fs = require("fs").promises
2
const pinToIpfs = require("./pin-to-ipfs")
3
const Throttle = require("promise-parallel-throttle")
4
โ€‹
5
const assets = require("./assets.json")
6
โ€‹
7
async function main() {
8
โ€‹
9
const updated_assets = await Throttle.sync(
10
assets.map(asset => async () => {
11
โ€‹
12
const { ipfsLink: image, httpLink: imageLink } = await pinToIpfs(`${asset.id}_image`, __dirname + "/" + asset.image)
13
console.log(`[${asset.name}] pinned image to ipfs (${imageLink})`)
14
โ€‹
15
const { ipfsLink: src, httpLink: srcLink } = await pinToIpfs(`${asset.id}_src`, __dirname + "/" + asset.src)
16
console.log(`[${asset.name}] pinned image to ipfs (${srcLink})`)
17
โ€‹
18
return {
19
...asset,
20
image,
21
src
22
}
23
})
24
)
25
โ€‹
26
// write updated assets to assets.json
27
await fs.writeFile(__dirname + '/assets.json', JSON.stringify(updated_assets, null, 2))
28
โ€‹
29
console.log('written updates to assets.json')
30
}
31
โ€‹
32
main()
Copied!
1
node src/pin-images-to-ipfs.js
Copied!

Before you continue to the minting process, please understand the importance of minting policies and their scripts!

Read the Cardano Documentation on "Scripts" and/or watch a video we made discussing the subject:

7. Create an "open" or "unlocked" minting policy and script (Optional)

    We will create an open minting policy script and export it in JSON and TXT format.
1
cd src
2
nano create-mint-policy.js
Copied!
1
const fs = require("fs")
2
const cardano = require("./cardano")
3
โ€‹
4
const wallet = cardano.wallet("PIADA")
5
โ€‹
6
const mintScript = {
7
keyHash: cardano.addressKeyHash(wallet.name),
8
type: "sig"
9
}
10
โ€‹
11
fs.writeFileSync(__dirname + "/mint-policy.json", JSON.stringify(mintScript, null, 2))
12
fs.writeFileSync(__dirname + "/mint-policy-id.txt", cardano.transactionPolicyid(mintScript))
Copied!
1
node src/create-mint-policy.js
Copied!
    Create a "time-locked" minting policy script and export it in JSON and TXT format.
1
cd src
2
nano create-time-locked-mint-policy.js
Copied!
1
const fs = require("fs")
2
const cardano = require("./cardano")
3
โ€‹
4
const wallet = cardano.wallet("PIADA")
5
โ€‹
6
const { slot } = cardano.queryTip()
7
โ€‹
8
const SLOTS_PER_EPOCH = 5 * 24 * 60 * 60 // 432000
9
โ€‹
10
const mintScript = {
11
type: "all",
12
scripts: [
13
{
14
slot: slot + (SLOTS_PER_EPOCH * 5),
15
type: "before"
16
},
17
{
18
keyHash: cardano.addressKeyHash(wallet.name),
19
type: "sig"
20
}
21
]
22
}
23
โ€‹
24
fs.writeFileSync(__dirname + "/mint-policy.json", JSON.stringify(mintScript, null, 2))
25
fs.writeFileSync(__dirname + "/mint-policy-id.txt", cardano.transactionPolicyid(mintScript))
Copied!
1
node src/create-time-locked-mint-policy.js
Copied!

9. Create a script to get our policy ID

    We want to make a script that can get our Policy ID to be used in other parts of our program
1
cd src
2
nano get-policy-id.js
Copied!
1
const cardano = require("./cardano")
2
const mintScript = require("./mint-policy.json")
3
โ€‹
4
module.exports = () => {
5
โ€‹
6
const policyId = cardano.transactionPolicyid(mintScript)
7
โ€‹
8
return {
9
policyId,
10
mintScript
11
}
12
}
Copied!
1
node src/get-policy-id.js
Copied!

9. Define the mint transaction

    1.
    build mint transaction with metadata.json
    2.
    calc fee
    3.
    rebuild
    4.
    sign
    5.
    submit
1
cd src
2
nano mint-multiple-assets.js
Copied!
1
const cardano = require("./cardano")
2
const getPolicyId = require("./get-policy-id")
3
const assets = require("./assets.json")
4
โ€‹
5
const wallet = cardano.wallet("PIADA")
6
โ€‹
7
const { policyId: POLICY_ID, mintScript } = getPolicyId()
8
โ€‹
9
const metadata_assets = assets.reduce((result, asset) => {
10
โ€‹
11
const ASSET_ID = asset.id // PIADA0
12
โ€‹
13
// remove id property from the asset metadata
14
const asset_metadata = {
15
...asset
16
}
17
โ€‹
18
delete asset_metadata.id
19
โ€‹
20
return {
21
...result,
22
[ASSET_ID]: asset_metadata
23
}
24
}, {})
25
โ€‹
26
const metadata = {
27
721: {
28
[POLICY_ID]: {
29
...metadata_assets
30
}
31
}
32
}
33
โ€‹
34
const txOut_value = assets.reduce((result, asset) => {
35
โ€‹
36
const ASSET_ID = POLICY_ID + "." + asset.id
37
result[ASSET_ID] = 1
38
return result
39
โ€‹
40
}, {
41
...wallet.balance().value
42
})
43
โ€‹
44
const mint_actions = assets.map(asset => ({ action: "mint", amount: 1, token: POLICY_ID + "." + asset.id }))
45
โ€‹
46
const tx = {
47
txIn: wallet.balance().utxo,
48
txOut: [
49
{
50
address: wallet.paymentAddr,
51
amount: txOut_value
52
}
53
],
54
mint: {
55
actions: mint_actions,
56
script: [mintScript]
57
},
58
metadata,
59
witnessCount: 2
60
}
61
โ€‹
62
const buildTransaction = (tx) => {
63
โ€‹
64
const raw = cardano.transactionBuildRaw(tx)
65
const fee = cardano.transactionCalculateMinFee({
66
...tx,
67
txBody: raw
68
})
69
โ€‹
70
tx.txOut[0].value.lovelace -= fee
71
โ€‹
72
return cardano.transactionBuildRaw({ ...tx, fee })
73
}
74
โ€‹
75
const raw = buildTransaction(tx)
76
โ€‹
77
// 9. Sign transaction
78
โ€‹
79
const signTransaction = (wallet, tx, script) => {
80
โ€‹
81
return cardano.transactionSign({
82
signingKeys: [wallet.payment.skey, wallet.payment.skey],
83
txBody: tx
84
})
85
}
86
โ€‹
87
const signed = signTransaction(wallet, raw, mintScript)
88
โ€‹
89
// 10. Submit transaction
90
โ€‹
91
const txHash = cardano.transactionSubmit(signed)
92
โ€‹
93
console.log(txHash)
Copied!
1
node src/mint-multiple-assets.js
Copied!

10. Send assets back to wallet

    Make a script to send multiple assets back to a wallet in a single transaction.
1
cd src
2
nano send-multiple-assets-back-to-wallet.js
Copied!
1
const cardano = require("./cardano")
2
const assets = require("./assets.json")
3
const getPolicyId = require('./get-policy-id')
4
โ€‹
5
const sender = cardano.wallet("PIADA")
6
โ€‹
7
console.log(
8
"Balance of Sender address" +
9
cardano.toAda(sender.balance().amount.lovelace) + " ADA"
10
)
11
โ€‹
12
const { policyId: POLICY_ID } = getPolicyId()
13
โ€‹
14
function sendAssets({ receiver, assets }) {
15
โ€‹
16
const txOut_amount_sender = assets.reduce((result, asset) => {
17
โ€‹
18
const ASSET_ID = POLICY_ID + "." + asset
19
delete result[ASSET_ID]
20
return result
21
}, {
22
...sender.balance().amount
23
})
24
โ€‹
25
const txOut_amount_receiver = assets.reduce((result, asset) => {
26
โ€‹
27
const ASSET_ID = POLICY_ID + "." + asset
28
result[ASSET_ID] = 1
29
return result
30
}, {})
31
โ€‹
32
// This is depedent at the network, try to increase this amount of ADA
33
// if you get an error saying: OutputTooSmallUTxO
34
const MIN_ADA = 3
35
โ€‹
36
const txInfo = {
37
txIn: cardano.queryUtxo(sender.paymentAddr),
38
txOut: [
39
{
40
address: sender.paymentAddr,
41
amount: {
42
...txOut_amount_sender,
43
lovelace: txOut_amount_sender.lovelace - cardano.toLovelace(MIN_ADA)
44
}
45
},
46
{
47
address: receiver,
48
amount: {
49
lovelace: cardano.toLovelace(MIN_ADA),
50
...txOut_amount_receiver
51
}
52
}
53
]
54
}
55
โ€‹
56
const raw = cardano.transactionBuildRaw(txInfo)
57
โ€‹
58
const fee = cardano.transactionCalculateMinFee({
59
...txInfo,
60
txBody: raw,
61
witnessCount: 1
62
})
63
โ€‹
64
txInfo.txOut[0].amount.lovelace -= fee
65
โ€‹
66
const tx = cardano.transactionBuildRaw({ ...txInfo, fee })
67
โ€‹
68
const txSigned = cardano.transactionSign({
69
txBody: tx,
70
signingKeys: [sender.payment.skey]
71
})
72
โ€‹
73
const txHash = cardano.transactionSubmit(txSigned)
74
โ€‹
75
console.log(txHash)
76
}
77
โ€‹
78
sendAssets({
79
receiver: "addr1qylm539axczhyvdh90f6c09ptrz8asa4hgq8u5shkw3v9vjae9ftypmc8tmd2rrwngdxm4sr3tpzmxw4zyg3z7vttpwsl0alww",
80
assets: assets.map(asset => asset.id)
81
})
Copied!
1
node src/send-multiple-assets-back-to-wallet.js
Copied!
Last modified 2mo ago