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

THIS GUIDE IS DEPRECATED

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. 1.
    Create an account
  2. 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. 1.
    build mint transaction with metadata.json
  2. 2.
    calc fee
  3. 3.
    rebuild
  4. 4.
    sign
  5. 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 11d ago