Create NFTs (ERC-721 and ERC-1155)¶
| Standard | Use it for | Tooling on PQWERAI |
|---|---|---|
| ERC-721 | Unique items, one owner per token ID | Foundry/Hardhat (tested); Windows Wallet Studio NFT collection template; wallet shows and transfers ERC-721 |
| ERC-1155 | Many token types in one contract (fungible and unique) | Foundry/Hardhat (tested). The Windows Wallet 0.6.0 does not display ERC-1155 balances; a Studio wizard exists only on an unreleased branch |
Metadata¶
The chain stores only a URI. Host JSON metadata and images on IPFS (content-addressed, preferred) or HTTPS.
metadata-1.json
{
"name": "Tutorial NFT #1",
"description": "ERC-721 metadata example. Host it on IPFS or HTTPS; the chain stores only the URI.",
"image": "ipfs://<your-image-CID>",
"attributes": [{"trait_type": "Network", "value": "PQWERAI testnet 77240"}]
}
- ERC-721:
tokenURI(id)=baseURI + idin this example (ipfs://<folder-CID>/1). - ERC-1155:
uri(id)returns a template with{id}; clients substitute the 64-character lower-case hex ID (…/0000…0007.json).
Contracts¶
src/PQCollection.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/// Example ERC-721 collection: the owner mints sequential IDs; metadata lives at baseURI + tokenId.
/// Unaudited tutorial code.
contract PQCollection is ERC721, Ownable {
uint256 public nextId = 1;
string private base;
constructor(string memory baseURI_, address owner_) ERC721("Tutorial Collection", "TCOL") Ownable(owner_) {
base = baseURI_;
}
function mint(address to) external onlyOwner returns (uint256 id) {
id = nextId++;
_safeMint(to, id);
}
function _baseURI() internal view override returns (string memory) {
return base;
}
}
src/PQItems.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/// Example ERC-1155 multi-token: one contract, many fungible or non-fungible IDs. The URI uses the {id}
/// substitution defined by ERC-1155 (64 lower-case hex characters). Unaudited tutorial code.
contract PQItems is ERC1155, Ownable {
constructor(string memory uri_, address owner_) ERC1155(uri_) Ownable(owner_) {}
function mint(address to, uint256 id, uint256 amount) external onlyOwner {
_mint(to, id, amount, "");
}
}
Deploy, mint and transfer¶
Uses the same foundry.toml and OpenZeppelin install as Create a token.
deploy-nfts.sh
ME=$(cast wallet address --keystore "$KEYSTORE" --password-file "$PASSWORD_FILE")
BOB=0x000000000000000000000000000000000000bEEF
W="--keystore $KEYSTORE --password-file $PASSWORD_FILE --rpc-url $RPC"
forge build
# --- ERC-721: deploy, mint #1, read owner and URI, transfer ---
forge create src/PQCollection.sol:PQCollection --broadcast $W \
--constructor-args "ipfs://<metadata-folder-CID>/" "$ME" | tee c721.log
NFT=$(sed -n 's/^Deployed to: //p' c721.log)
cast receipt "$(cast send "$NFT" "mint(address)" "$ME" --async $W)" status --rpc-url "$RPC"
cast call "$NFT" "ownerOf(uint256)(address)" 1 --rpc-url "$RPC"
cast call "$NFT" "tokenURI(uint256)(string)" 1 --rpc-url "$RPC"
cast receipt "$(cast send "$NFT" "safeTransferFrom(address,address,uint256)" "$ME" "$BOB" 1 --async $W)" status --rpc-url "$RPC"
cast call "$NFT" "ownerOf(uint256)(address)" 1 --rpc-url "$RPC"
# --- ERC-1155: deploy, mint 100 of id 7, transfer 40, read balances and URI ---
forge create src/PQItems.sol:PQItems --broadcast $W \
--constructor-args "ipfs://<metadata-folder-CID>/{id}.json" "$ME" | tee c1155.log
ITEMS=$(sed -n 's/^Deployed to: //p' c1155.log)
cast receipt "$(cast send "$ITEMS" "mint(address,uint256,uint256)" "$ME" 7 100 --async $W)" status --rpc-url "$RPC"
cast receipt "$(cast send "$ITEMS" "safeTransferFrom(address,address,uint256,uint256,bytes)" "$ME" "$BOB" 7 40 0x --async $W)" status --rpc-url "$RPC"
cast call "$ITEMS" "balanceOf(address,uint256)(uint256)" "$ME" 7 --rpc-url "$RPC"
cast call "$ITEMS" "balanceOf(address,uint256)(uint256)" "$BOB" 7 --rpc-url "$RPC"
cast call "$ITEMS" "uri(uint256)(string)" 7 --rpc-url "$RPC"
Expected output (addresses differ):
true
0x85dbc49268BFB4C8EBad1ac24f0E1084074ddC8b # ownerOf(1) after mint
"ipfs://<metadata-folder-CID>/1"
true
0x000000000000000000000000000000000000bEEF # ownerOf(1) after transfer
…
true
true
60 # ERC-1155 balance of id 7 (me)
40 # ERC-1155 balance of id 7 (recipient)
"ipfs://<metadata-folder-CID>/{id}.json"
Notes and errors¶
_safeMint/safeTransferFromcallonERC721Received/onERC1155Receivedwhen the recipient is a contract; a contract without the hook makes the transfer revert (ERC721InvalidReceiver,ERC1155InvalidReceiver).- Minting is owner-only in these examples (
OwnableUnauthorizedAccountotherwise). - The explorer indexes ERC-721 transfers; ERC-1155
TransferSingle/TransferBatchare not decoded yet (known gaps).
Sources at 72057fe3ec: developers/examples/nft/src/PQCollection.sol · developers/examples/nft/src/PQItems.sol · developers/examples/nft/metadata-1.json · developers/examples/nft/deploy-nfts.sh
Found a problem? Report a documentation issue · Build testnet-rc1+72057fe3ec