pragma language_version >= 0.17.0;
import CompactStandardLibrary;
// Initialization state
export ledger _isInitialized: Boolean;
// Balances per token ID per account
export ledger _balances: Map<Uint<128>, Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>>;
// Operator approvals (all-or-nothing)
export ledger _operatorApprovals: Map<Either<ZswapCoinPublicKey, ContractAddress>, Map<Either<ZswapCoinPublicKey, ContractAddress>, Boolean>>;
// Token metadata URIs
export ledger _uris: Map<Uint<128>, Opaque<"string">>;
// Initialize the contract
export circuit initialize(baseUri: Opaque<"string">): [] {
assert(!_isInitialized, "Already initialized");
_isInitialized = disclose(true);
}
// Get balance of account for token ID
export circuit balanceOf(
account: Either<ZswapCoinPublicKey, ContractAddress>,
id: Uint<128>
): Uint<128> {
assert(_isInitialized, "Not initialized");
if (!_balances.member(disclose(id))) {
return 0;
}
const accountBalances = _balances.lookup(disclose(id));
if (!accountBalances.member(disclose(account))) {
return 0;
}
return accountBalances.lookup(disclose(account));
}
// Set operator approval for all token types
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(
account: Either<ZswapCoinPublicKey, ContractAddress>,
operator: Either<ZswapCoinPublicKey, ContractAddress>
): Boolean {
assert(_isInitialized, "Not initialized");
if (_operatorApprovals.member(disclose(account)) &&
_operatorApprovals.lookup(account).member(disclose(operator))) {
return _operatorApprovals.lookup(account).lookup(disclose(operator));
}
return false;
}
// Transfer single token type
export circuit safeTransferFrom(
from: Either<ZswapCoinPublicKey, ContractAddress>,
to: Either<ZswapCoinPublicKey, ContractAddress>,
id: Uint<128>,
value: Uint<128>
): [] {
assert(_isInitialized, "Not initialized");
const caller = left<ZswapCoinPublicKey, ContractAddress>(ownPublicKey());
assert(from == caller || isApprovedForAll(from, caller), "Not authorized");
assert(!isKeyOrAddressZero(to), "Invalid receiver");
_update(from, to, id, value);
}
// Get token URI
export circuit uri(tokenId: Uint<128>): Opaque<"string"> {
assert(_isInitialized, "Not initialized");
if (!_uris.member(disclose(tokenId))) {
return ""; // Empty string
}
return _uris.lookup(disclose(tokenId));
}
// Mint tokens
export circuit _mint(
to: Either<ZswapCoinPublicKey, ContractAddress>,
id: Uint<128>,
value: Uint<128>
): [] {
assert(_isInitialized, "Not initialized");
assert(!isKeyOrAddressZero(to), "Invalid receiver");
_update(burnAddress(), to, id, value);
}
// Burn tokens
export circuit _burn(
from: Either<ZswapCoinPublicKey, ContractAddress>,
id: Uint<128>,
value: Uint<128>
): [] {
assert(_isInitialized, "Not initialized");
assert(!isKeyOrAddressZero(from), "Invalid sender");
_update(from, burnAddress(), id, value);
}
// Set token URI
export circuit _setURI(id: Uint<128>, newuri: Opaque<"string">): [] {
assert(_isInitialized, "Not initialized");
_uris.insert(disclose(id), disclose(newuri));
}
// Internal functions
circuit _update(
from: Either<ZswapCoinPublicKey, ContractAddress>,
to: Either<ZswapCoinPublicKey, ContractAddress>,
id: Uint<128>,
value: Uint<128>
): [] {
if (!isKeyOrAddressZero(disclose(from))) {
const fromBalance = balanceOf(from, id);
assert(fromBalance >= value, "Insufficient balance");
const newBalance = fromBalance - value;
if (!_balances.member(disclose(id))) {
_balances.insert(disclose(id), default<Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>>);
}
_balances.lookup(id).insert(disclose(from), disclose(newBalance));
}
if (!isKeyOrAddressZero(disclose(to))) {
const toBalance = balanceOf(to, id);
// Check overflow
const MAX_UINT128 = 340282366920938463463374607431768211455 as Uint<128>;
assert(toBalance <= MAX_UINT128 - value, "Balance overflow");
const newBalance = toBalance + value;
if (!_balances.member(disclose(id))) {
_balances.insert(disclose(id), default<Map<Either<ZswapCoinPublicKey, ContractAddress>, Uint<128>>>);
}
_balances.lookup(id).insert(disclose(to), disclose(newBalance));
}
}
circuit _setApprovalForAll(
owner: Either<ZswapCoinPublicKey, ContractAddress>,
operator: Either<ZswapCoinPublicKey, ContractAddress>,
approved: Boolean
): [] {
assert(owner != operator, "Cannot approve self");
if (!_operatorApprovals.member(disclose(owner))) {
_operatorApprovals.insert(disclose(owner), default<Map<Either<ZswapCoinPublicKey, ContractAddress>, Boolean>>);
}
_operatorApprovals.lookup(owner).insert(disclose(operator), disclose(approved));
}
// 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>);
}