pragma language_version >= 0.17.0;
import CompactStandardLibrary;
// Initialization state
export ledger _isInitialized: Boolean;
// Token metadata
export sealed ledger _name: Opaque<"string">;
export sealed ledger _symbol: Opaque<"string">;
// NFT ownership and approvals
export ledger _owners: Map<Uint<128>, Either<ZswapCoinPublicKey, ContractAddress>>;
export ledger _balances: Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>;
export ledger _tokenApprovals: Map<Uint<128>, Either<ZswapCoinPublicKey, ContractAddress>>;
export ledger _operatorApprovals: Map<Either<ZswapCoinPublicKey, ContractAddress>, Map<Either<ZswapCoinPublicKey, ContractAddress>, Boolean>>;
// Token metadata URIs
export ledger _tokenURIs: Map<Uint<128>, Opaque<"string">>;
// Initialize the NFT collection (can only be called once)
export circuit initialize(name_: Opaque<"string">, symbol_: Opaque<"string">): [] {
assert(!_isInitialized, "Already initialized");
_isInitialized = disclose(true);
_name = disclose(name_);
_symbol = disclose(symbol_);
}
// Get collection name
export circuit name(): Opaque<"string"> {
assert(_isInitialized, "Not initialized");
return _name;
}
// Get collection symbol
export circuit symbol(): Opaque<"string"> {
assert(_isInitialized, "Not initialized");
return _symbol;
}
// Get token owner
export circuit ownerOf(tokenId: Uint<128>): Either<ZswapCoinPublicKey, ContractAddress> {
assert(_isInitialized, "Not initialized");
return _requireOwned(tokenId);
}
// Get owner's token count
export circuit balanceOf(owner: Either<ZswapCoinPublicKey, ContractAddress>): Uint<128> {
assert(_isInitialized, "Not initialized");
if (!_balances.member(disclose(owner))) {
return 0;
}
return _balances.lookup(disclose(owner));
}
// Get token metadata URI
export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> {
assert(_isInitialized, "Not initialized");
_requireOwned(tokenId);
if (!_tokenURIs.member(disclose(tokenId))) {
return ""; // Empty string
}
return _tokenURIs.lookup(disclose(tokenId));
}
// Approve address to transfer token
export circuit approve(
to: Either<ZswapCoinPublicKey, ContractAddress>,
tokenId: Uint<128>
): [] {
assert(_isInitialized, "Not initialized");
const auth = left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey());
_approve(to, tokenId, auth);
}
// Get approved address for token
export circuit getApproved(tokenId: Uint<128>): Either<ZswapCoinPublicKey, ContractAddress> {
assert(_isInitialized, "Not initialized");
_requireOwned(tokenId);
return _getApproved(tokenId);
}
// Set operator approval for all tokens
export circuit setApprovalForAll(
operator: Either<ZswapCoinPublicKey, ContractAddress>,
approved: Boolean
): [] {
assert(_isInitialized, "Not initialized");
const owner = left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey());
_setApprovalForAll(owner, operator, approved);
}
// Check if operator is approved
export circuit isApprovedForAll(
owner: Either<ZswapCoinPublicKey, ContractAddress>,
operator: Either<ZswapCoinPublicKey, ContractAddress>
): Boolean {
assert(_isInitialized, "Not initialized");
if (_operatorApprovals.member(disclose(owner)) &&
_operatorApprovals.lookup(owner).member(disclose(operator))) {
return _operatorApprovals.lookup(owner).lookup(disclose(operator));
}
return false;
}
// Transfer token
export circuit transferFrom(
from: Either<ZswapCoinPublicKey, ContractAddress>,
to: Either<ZswapCoinPublicKey, ContractAddress>,
tokenId: Uint<128>
): [] {
assert(_isInitialized, "Not initialized");
assert(!isKeyOrAddressZero(to), "Invalid receiver");
const caller = left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey());
const previousOwner = _update(to, tokenId, caller);
assert(previousOwner == from, "Incorrect owner");
}
// Mint new NFT
export circuit _mint(
to: Either<ZswapCoinPublicKey, ContractAddress>,
tokenId: Uint<128>
): [] {
assert(_isInitialized, "Not initialized");
assert(!isKeyOrAddressZero(to), "Invalid receiver");
const previousOwner = _update(to, tokenId, burnAddress());
assert(isKeyOrAddressZero(previousOwner), "Token already minted");
}
// Burn NFT
export circuit _burn(tokenId: Uint<128>): [] {
assert(_isInitialized, "Not initialized");
const previousOwner = _update(burnAddress(), tokenId, burnAddress());
assert(!isKeyOrAddressZero(previousOwner), "Token does not exist");
}
// Set token URI
export circuit _setTokenURI(tokenId: Uint<128>, uri: Opaque<"string">): [] {
assert(_isInitialized, "Not initialized");
_requireOwned(tokenId);
_tokenURIs.insert(disclose(tokenId), disclose(uri));
}
// Internal functions
circuit _requireOwned(tokenId: Uint<128>): Either<ZswapCoinPublicKey, ContractAddress> {
const owner = _ownerOf(tokenId);
assert(!isKeyOrAddressZero(owner), "Token does not exist");
return owner;
}
circuit _ownerOf(tokenId: Uint<128>): Either<ZswapCoinPublicKey, ContractAddress> {
if (!_owners.member(disclose(tokenId))) {
return burnAddress();
}
return _owners.lookup(disclose(tokenId));
}
circuit _getApproved(tokenId: Uint<128>): Either<ZswapCoinPublicKey, ContractAddress> {
if (!_tokenApprovals.member(disclose(tokenId))) {
return burnAddress();
}
return _tokenApprovals.lookup(disclose(tokenId));
}
circuit _approve(
to: Either<ZswapCoinPublicKey, ContractAddress>,
tokenId: Uint<128>,
auth: Either<ZswapCoinPublicKey, ContractAddress>
): [] {
if (!isKeyOrAddressZero(disclose(auth))) {
const owner = _requireOwned(tokenId);
assert((owner == disclose(auth) || isApprovedForAll(owner, auth)), "Not authorized");
}
_tokenApprovals.insert(disclose(tokenId), disclose(to));
}
circuit _setApprovalForAll(
owner: Either<ZswapCoinPublicKey, ContractAddress>,
operator: Either<ZswapCoinPublicKey, ContractAddress>,
approved: Boolean
): [] {
assert(!isKeyOrAddressZero(operator), "Invalid operator");
if (!_operatorApprovals.member(disclose(owner))) {
_operatorApprovals.insert(disclose(owner), default<Map<Either<ZswapCoinPublicKey, ContractAddress>, Boolean>>);
}
_operatorApprovals.lookup(owner).insert(disclose(operator), disclose(approved));
}
circuit _update(
to: Either<ZswapCoinPublicKey, ContractAddress>,
tokenId: Uint<128>,
auth: Either<ZswapCoinPublicKey, ContractAddress>
): Either<ZswapCoinPublicKey, ContractAddress> {
const from = _ownerOf(tokenId);
// Check authorization
if (!isKeyOrAddressZero(disclose(auth))) {
_checkAuthorized(from, auth, tokenId);
}
// Update balances and ownership
if (!isKeyOrAddressZero(disclose(from))) {
_approve(burnAddress(), tokenId, burnAddress());
const newBalance = _balances.lookup(disclose(from)) - 1 as Uint<128>;
_balances.insert(disclose(from), disclose(newBalance));
}
if (!isKeyOrAddressZero(disclose(to))) {
if (!_balances.member(disclose(to))) {
_balances.insert(disclose(to), 0);
}
const newBalance = _balances.lookup(disclose(to)) + 1 as Uint<128>;
_balances.insert(disclose(to), disclose(newBalance));
}
_owners.insert(disclose(tokenId), disclose(to));
return from;
}
circuit _checkAuthorized(
owner: Either<ZswapCoinPublicKey, ContractAddress>,
spender: Either<ZswapCoinPublicKey, ContractAddress>,
tokenId: Uint<128>
): [] {
if (!_isAuthorized(owner, spender, tokenId)) {
assert(!isKeyOrAddressZero(owner), "Token does not exist");
assert(false, "Not authorized");
}
}
circuit _isAuthorized(
owner: Either<ZswapCoinPublicKey, ContractAddress>,
spender: Either<ZswapCoinPublicKey, ContractAddress>,
tokenId: Uint<128>
): Boolean {
return (
!isKeyOrAddressZero(disclose(spender)) &&
(disclose(owner) == disclose(spender) ||
isApprovedForAll(owner, spender) ||
_getApproved(tokenId) == disclose(spender))
);
}
// Helper Functions
// These would typically be imported from OpenZeppelin's Utils module
circuit isKeyOrAddressZero(keyOrAddress: Either<ZswapCoinPublicKey, ContractAddress>): Boolean {
return isContractAddress(keyOrAddress)
? default<ContractAddress> == keyOrAddress.right
: default<ZswapCoinPublicKey> == keyOrAddress.left;
}
circuit isContractAddress(keyOrAddress: Either<ZswapCoinPublicKey, ContractAddress>): Boolean {
return !keyOrAddress.is_left;
}
circuit burnAddress(): Either<ZswapCoinPublicKey, ContractAddress> {
return left<ZswapCoinPublicKey, ContractAddress>(default<ZswapCoinPublicKey>);
}