Non-Fungible Token (NFT)
Código is an AI-Powered Code Generation Platform for blockchain developers and web3 teams that saves development time and increases the code's security across various blockchains.
Getting started
- You can immediately start using Código Studio. Código Studio is a web-based IDE environment that comes with all the tools and programs to develop Solana programs using the CIDL.
- You can work from your local environment by downloading the latest version of the Código CLI that targets your operating system.
CIDL Quickstart
In this Quickstart guide, you’ll learn how to start with Código’s Interface Description Language (CIDL) by building a simple Solana basic NFT program.
If you are following along from your local environment, this guide assumes you have successfully installed and configured the Solana tool suite. If you are working from Código Studio, you don’t need to worry; the Solana tool suite comes installed and configured.
1. NFT Contract
Create an nft.cidl
file and copy and paste the following CIDL.
cidl: "0.8"
info:
name: nft_program
title: NFT Program
version: 0.0.0
contact:
name: Codigo
web: https://codigo.ai
git: https://github.com/Codigo-ai
email: [email protected]
license:
name: Codigo
url: https://codigo.ai/documents/Codigo---Terms-of-Service.pdf
solana:
seeds:
Metadata:
items:
- name: "gem"
- name: mint
type: sol:pubkey
types:
Gem:
fields:
- name: color
type: string
attributes: [ cap=16 ]
- name: rarity
type: string
attributes: [ cap=16 ]
- name: short_description
type: string
attributes: [ cap=255 ]
- name: mint
type: sol:pubkey
- name: assoc_account
type: sol:pubkey?
methods:
- name: mint
uses:
- csl_spl_token.initialize_mint2
- csl_spl_assoc_token.create
- csl_spl_token.mint_to
- csl_spl_token.set_authority
inputs:
- name: mint
type: sol:account<csl_spl_token.Mint>
attributes: [ sol:init ]
- name: gem
type: sol:account<Gem, seeds.Metadata(mint=mint)>
attributes: [ sol:init ]
- name: color
type: string
- name: rarity
type: string
- name: short_description
type: string
- name: transfer
uses:
- csl_spl_assoc_token.create
- csl_spl_token.transfer_checked
inputs:
- name: mint
type: sol:account<csl_spl_token.Mint>
- name: gem
type: sol:account<Gem, seeds.Metadata(mint=mint)>
attributes: [ sol:writable ]
- name: burn
uses:
- csl_spl_token.burn
inputs:
- name: mint
type: sol:account<csl_spl_token.Mint>
- name: gem
type: sol:account<Gem, seeds.Metadata(mint=mint)>
attributes: [ sol:writable ]
2. Generate the Solana program and client library
Open the terminal and type the following command
codigo solana generate nft.cidl
After generating the Solana program and client library, two new directories will be created relative to the nft.cidl
file named program_client
and program
.
3. Implement the business logic
In the program
directory, you will find a directory called src
; inside this directory, there will be three .rs
files named mint.rs
, burn.rs
and transfer.rs
Mint business logic
Copy and paste the following code just below the comment line // Implement your business logic here...
in the
file mint.rs
gem.data.color = color;
gem.data.rarity = rarity;
gem.data.short_description = short_description;
gem.data.mint = *mint.info.key;
gem.data.assoc_account = Some(*assoc_token_account.key);
csl_spl_token::src::cpi::initialize_mint2(for_initialize_mint2, 0, *wallet.key, None)?;
csl_spl_assoc_token::src::cpi::create(for_create)?;
csl_spl_token::src::cpi::mint_to(for_mint_to, 1)?;
csl_spl_token::src::cpi::set_authority(for_set_authority, 0, None)?;
Burn business logic
Copy and paste the following code just below the comment line // Implement your business logic here...
in the
file burn.rs
gem.data.assoc_account = None;
csl_spl_token::src::cpi::burn(for_burn, 1)?;
Transfer business logic
Copy and paste the following code just below the comment line // Implement your business logic here...
in the
file transfer.rs
gem.data.assoc_account = Some(*destination.key);
// Create the ATA account for new owner if it hasn't been created
if assoc_token_account.lamports() == 0 {
csl_spl_assoc_token::src::cpi::create(for_create)?;
}
csl_spl_token::src::cpi::transfer_checked(for_transfer_checked, 1, 0)?;
4. Build and deploy the program
Open the terminal and navigate to the program directory by executing the command cd program
, from there execute the
following command:
cargo build-sbf
Run a local Solana validator by opening a new terminal and typing the command:
Don’t close this terminal because it is required for the following steps
solana-test-validator
Deploy the program by opening a new terminal and navigating to the program
directory; from there, execute the
following command:
solana program deploy target/deploy/nft_program.so
After deploying the program, you will receive the program id; copy and paste it somewhere for later.
5. Test your contract
Create a new file named app.ts
inside the directory program_client
and copy and paste the following code into
the app.ts
file:
Replace the “PASTE_YOUR_PROGRAM_ID” with the program id you got when deploying the Solana program.
import {Connection, Keypair, PublicKey, sendAndConfirmTransaction, SystemProgram, Transaction,} from "@solana/web3.js";
import * as fs from "fs/promises";
import * as path from "path";
import * as os from "os";
import {
burnSendAndConfirm,
CslSplTokenPDAs,
deriveMetadataPDA,
getGem,
initializeClient,
mintSendAndConfirm,
transferSendAndConfirm,
} from "./index";
import {getMinimumBalanceForRentExemptAccount, getMint, TOKEN_PROGRAM_ID,} from "@solana/spl-token";
async function main(feePayer: Keypair) {
const connection = new Connection("http://127.0.0.1:8899", {
commitment: "confirmed"
});
const progId = new PublicKey("PASTE_YOUR_PROGRAM_ID");
initializeClient(progId, connection);
/**
* Create a keypair for the mint
*/
const mint = Keypair.generate();
console.info("+==== Mint Address ====+");
console.info(mint.publicKey.toBase58());
/**
* Create two wallets
*/
const johnDoeWallet = Keypair.generate();
console.info("+==== John Doe Wallet ====+");
console.info(johnDoeWallet.publicKey.toBase58());
const janeDoeWallet = Keypair.generate();
console.info("+==== Jane Doe Wallet ====+");
console.info(janeDoeWallet.publicKey.toBase58());
const rent = await getMinimumBalanceForRentExemptAccount(connection);
await sendAndConfirmTransaction(
connection,
new Transaction()
.add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey,
newAccountPubkey: johnDoeWallet.publicKey,
space: 0,
lamports: rent,
programId: SystemProgram.programId,
}),
)
.add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey,
newAccountPubkey: janeDoeWallet.publicKey,
space: 0,
lamports: rent,
programId: SystemProgram.programId,
}),
),
[feePayer, johnDoeWallet, janeDoeWallet],
);
/**
* Derive the Gem Metadata so we can retrieve it later
*/
const [gemPub] = deriveMetadataPDA(
{
mint: mint.publicKey,
},
progId,
);
console.info("+==== Gem Metadata Address ====+");
console.info(gemPub.toBase58());
/**
* Derive the John Doe's Associated Token Account, this account will be
* holding the minted NFT.
*/
const [johnDoeATA] = CslSplTokenPDAs.deriveAccountPDA({
wallet: johnDoeWallet.publicKey,
mint: mint.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
}, new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"));
console.info("+==== John Doe ATA ====+");
console.info(johnDoeATA.toBase58());
/**
* Derive the Jane Doe's Associated Token Account, this account will be
* holding the minted NFT when John Doe transfer it
*/
const [janeDoeATA] = CslSplTokenPDAs.deriveAccountPDA({
wallet: janeDoeWallet.publicKey,
mint: mint.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
}, new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"));
console.info("+==== Jane Doe ATA ====+");
console.info(janeDoeATA.toBase58());
/**
* Mint a new NFT into John's wallet (technically, the Associated Token Account)
*/
console.info("+==== Minting... ====+");
await mintSendAndConfirm({
wallet: johnDoeWallet.publicKey,
color: "Purple",
rarity: "Rare",
shortDescription: "Only possible to collect from the lost temple event",
signers: {
feePayer: feePayer,
funding: feePayer,
mint: mint,
owner: johnDoeWallet,
},
});
console.info("+==== Minted ====+");
/**
* Get the minted token
*/
let mintAccount = await getMint(connection, mint.publicKey);
console.info("+==== Mint ====+");
console.info(mintAccount);
/**
* Get the Gem Metadata
*/
let gem = await getGem(gemPub);
console.info("+==== Gem Metadata ====+");
console.info(gem);
console.assert(gem!.assocAccount!.toBase58(), johnDoeATA.toBase58());
/**
* Transfer John Doe's NFT to Jane Doe Wallet (technically, the Associated Token Account)
*/
console.info("+==== Transferring... ====+");
await transferSendAndConfirm({
wallet: janeDoeWallet.publicKey,
mint: mint.publicKey,
source: johnDoeATA,
destination: janeDoeATA,
signers: {
feePayer: feePayer,
funding: feePayer,
authority: johnDoeWallet,
},
});
console.info("+==== Transferred ====+");
/**
* Get the minted token
*/
mintAccount = await getMint(connection, mint.publicKey);
console.info("+==== Mint ====+");
console.info(mintAccount);
/**
* Get the Gem Metadata
*/
gem = await getGem(gemPub);
console.info("+==== Gem Metadata ====+");
console.info(gem);
console.assert(gem!.assocAccount!.toBase58(), janeDoeATA.toBase58());
/**
* Burn the NFT
*/
console.info("+==== Burning... ====+");
await burnSendAndConfirm({
mint: mint.publicKey,
wallet: janeDoeWallet.publicKey,
signers: {
feePayer: feePayer,
owner: janeDoeWallet,
},
});
console.info("+==== Burned ====+");
/**
* Get the minted token
*/
mintAccount = await getMint(connection, mint.publicKey);
console.info("+==== Mint ====+");
console.info(mintAccount);
/**
* Get the Gem Metadata
*/
gem = await getGem(gemPub);
console.info("+==== Gem Metadata ====+");
console.info(gem);
console.assert(typeof gem!.assocAccount, "undefined");
}
fs.readFile(path.join(os.homedir(), ".config/solana/id.json")).then((file) =>
main(Keypair.fromSecretKey(new Uint8Array(JSON.parse(file.toString())))),
);
Open the terminal, navigate to the program_client
directory, and execute the following commands:
npm install ts-node @solana/spl-token --save-dev
Finally, to run the test, execute the following command:
npx ts-node app.ts
Next steps
Congratulations! 🎉👏 you created your first Solana program using the CIDL and integrated the generated TypeScript client library with an application. To summarize what we learned:
- CIDL stands for Código Interface Description Language, and it is the input for Código’s Generator.
- After completing the CIDL, developers only need to concentrate on implementing the business logic of the smart contract. 100% of the client libraries and smart contracts boilerplate are automatically generated.
These links may help you on your journey to writing smart contracts with the CIDL: