Skip to content
Available on private testnetTestnet: yesMainnet: Not availableSoftware: Core bad4a06ea · protocol pqwerai-v8-testnet/1Updated

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 + id in 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 / safeTransferFrom call onERC721Received / onERC1155Received when the recipient is a contract; a contract without the hook makes the transfer revert (ERC721InvalidReceiver, ERC1155InvalidReceiver).
  • Minting is owner-only in these examples (OwnableUnauthorizedAccount otherwise).
  • The explorer indexes ERC-721 transfers; ERC-1155 TransferSingle/TransferBatch are not decoded yet (known gaps).

Found a problem? Report a documentation issue · Build testnet-rc1+72057fe3ec