Tokens & DeFi
Panda supports fungible tokens (PRC-20), non-fungible tokens (PRC-721), and DeFi primitives like AMMs and marketplaces -- all written in Python.
PRC-20: Fungible Tokens
The PRC-20 standard provides mint, transfer, and approve functionality using the panda.token module. The reference implementation includes freeze/thaw authority, zero-value transfers, and proper event emission.
# Source: contracts/tokens/prc20_token.py
"""
PRC-20 reference contract — uses ``panda.token.FungibleToken``.
See ``docs/PRC20.md``. Deploy one contract per fungible token.
"""
from panda import contract, constructor, call, query, event
from panda.token import FungibleToken, TokenError
@contract
class PRC20Token:
class State:
token: dict = {}
mint_authority: str = ""
freeze_authority: str = ""
frozen: dict = {}
def _t(self) -> FungibleToken:
return FungibleToken(self.state.token)
def _not_frozen(self, addr: str):
if self.state.frozen.get(addr, False):
raise TokenError(f"frozen: {addr}")
def _init_token(
self,
ctx,
name: str,
symbol: str,
decimals: int = 18,
max_supply: int = 0,
initial_supply: int = 0,
):
t = self._t()
if t.name() or t.total_supply() > 0:
raise TokenError("already initialized")
t.configure(name=name, symbol=symbol, decimals=decimals, max_supply=max_supply)
self.state.mint_authority = ctx.sender
self.state.freeze_authority = ctx.sender
if initial_supply > 0:
t.mint(ctx.sender, initial_supply)
self.state.token = t.to_dict()
self.emit(
event.PRC20Initialized(
deployer=ctx.sender,
name=name,
symbol=symbol,
decimals=decimals,
initial_supply=initial_supply,
)
)
print(
f"PRC20 deployed: {name} ({symbol}) decimals={decimals} initial_supply={initial_supply} by {ctx.sender}"
)
@constructor
def deploy(
self,
ctx,
name: str,
symbol: str,
decimals: int = 18,
max_supply: int = 0,
initial_supply: int = 0,
):
"""Runs at deploy; pass matching JSON to ``panda deploy --args``."""
self._init_token(ctx, name, symbol, decimals, max_supply, initial_supply)
@call
def transfer(self, ctx, to: str, value: int):
self._not_frozen(ctx.sender)
self._not_frozen(to)
t = self._t()
if value == 0:
self.state.token = t.to_dict()
self.emit(event.Transfer(_from=ctx.sender, _to=to, _value=0))
print(f"Transfer: {ctx.sender} -> {to} amount=0")
return
t.transfer(ctx.sender, to, value)
self.state.token = t.to_dict()
self.emit(event.Transfer(_from=ctx.sender, _to=to, _value=value))
print(f"Transfer: {ctx.sender} -> {to} amount={value}")
@call
def approve(self, ctx, spender: str, value: int):
self._not_frozen(ctx.sender)
t = self._t()
t.approve(ctx.sender, spender, value)
self.state.token = t.to_dict()
self.emit(event.Approval(_owner=ctx.sender, _spender=spender, _value=value))
print(f"Approval: {ctx.sender} approved {spender} for {value}")
@call
def transfer_from(self, ctx, owner: str, to: str, value: int):
self._not_frozen(owner)
self._not_frozen(to)
t = self._t()
if value == 0:
self.state.token = t.to_dict()
self.emit(event.Transfer(_from=owner, _to=to, _value=0))
return
t.transfer_from(ctx.sender, owner, to, value)
self.state.token = t.to_dict()
self.emit(event.Transfer(_from=owner, _to=to, _value=value))
print(f"TransferFrom: {owner} -> {to} amount={value} (spender={ctx.sender})")
@call
def mint(self, ctx, to: str, amount: int):
if ctx.sender != self.state.mint_authority:
raise TokenError("not mint authority")
t = self._t()
t.mint(to, amount)
self.state.token = t.to_dict()
self.emit(
event.Transfer(
_from="0x0000000000000000000000000000000000000000",
_to=to,
_value=amount,
)
)
print(f"Minted {amount} to {to} by authority {ctx.sender}")
@query
def balance_of(self, owner: str) -> int:
return self._t().balance_of(owner)
@query
def total_supply(self) -> int:
return self._t().total_supply()
@call
def freeze_account(self, ctx, target: str):
if ctx.sender != self.state.freeze_authority:
raise TokenError("not freeze authority")
f = dict(self.state.frozen)
f[target] = True
self.state.frozen = f
print(f"Account frozen: {target} by authority {ctx.sender}")
@call
def thaw_account(self, ctx, target: str):
if ctx.sender != self.state.freeze_authority:
raise TokenError("not freeze authority")
f = dict(self.state.frozen)
f[target] = False
self.state.frozen = f
print(f"Account thawed: {target} by authority {ctx.sender}")
Key patterns in the real contract:
- Helper methods like
_t()and_not_frozen()keep the code DRY --_t()wrapsFungibleTokendeserialization,_not_frozen()checks the freeze list - Freeze/thaw authority is separate from mint authority, allowing different admin roles
- Zero-value transfers are handled explicitly (skip the
FungibleTokenlogic but still emit the event) _init_token()guards against double-init by checking ifname()ortotal_supply()is already set
FungibleToken API
| Method | Description |
|---|---|
configure(name, symbol, decimals, max_supply) | Initialize token metadata |
mint(to, amount) | Create new tokens |
transfer(from_addr, to_addr, amount) | Move tokens between addresses |
approve(owner, spender, amount) | Grant spend allowance |
transfer_from(spender, from_addr, to_addr, amount) | Spend approved tokens |
balance_of(owner) | Check balance |
total_supply() | Get total minted supply |
to_dict() / constructor | Serialize/deserialize state |
See the full PRC-20 Standard for the complete specification.
PRC-721: Non-Fungible Tokens
NFTs represent unique digital assets. Each token has a unique ID and optional metadata URI. The reference implementation includes per-token approvals, operator approvals (approve-for-all), and safe transfers.
# Source: contracts/tokens/prc721_collection.py
"""
PRC-721 reference collection — ownership, approvals, and transfers.
See ``docs/PRC721.md``. One deployed contract = one NFT collection.
"""
from panda import contract, constructor, call, query, event
ZERO = "0x0000000000000000000000000000000000000000"
def _is_authorized_transfer(
owner: str, sender: str, token_id: int, approvals: dict, op: dict
) -> bool:
if sender == owner:
return True
if approvals.get(str(token_id)) == sender:
return True
ops = op.get(owner, {})
return bool(ops.get(sender, False))
def _can_approve(owner: str, sender: str, op: dict) -> bool:
if sender == owner:
return True
inner = op.get(owner, {})
return bool(inner.get(sender, False))
@contract
class PRC721Collection:
class State:
name: str = ""
symbol: str = ""
base_uri: str = ""
tokens: dict = {}
owner_balances: dict = {}
token_approvals: dict = {}
operator_approvals: dict = {}
def _token_owner(self, token_id: int) -> str:
key = str(token_id)
if key not in self.state.tokens:
raise ValueError("token does not exist")
return self.state.tokens[key]["owner"]
def _set_owner_balance(self, owner: str, delta: int):
b = dict(self.state.owner_balances)
cur = b.get(owner, 0)
b[owner] = cur + delta
self.state.owner_balances = b
@constructor
def deploy(self, ctx, name: str, symbol: str, base_uri: str = ""):
self.state.name = name
self.state.symbol = symbol
self.state.base_uri = base_uri
print(f"PRC721 collection deployed: {name} ({symbol}) by {ctx.sender}")
@call
def mint(self, ctx, to: str, token_uri: str = ""):
tid = len(self.state.tokens) + 1
key = str(tid)
tok = dict(self.state.tokens)
tok[key] = {"owner": to, "uri": token_uri}
self.state.tokens = tok
self._set_owner_balance(to, 1)
self.emit(event.Transfer(_from=ZERO, _to=to, _token_id=tid))
print(f"Minted NFT #{tid} to {to}")
@call
def approve(self, ctx, to: str, token_id: int):
owner = self._token_owner(token_id)
if not _can_approve(owner, ctx.sender, dict(self.state.operator_approvals)):
raise ValueError("not authorized")
ta = dict(self.state.token_approvals)
ta[str(token_id)] = to
self.state.token_approvals = ta
self.emit(event.Approval(_owner=owner, _approved=to, _token_id=token_id))
print(f"Approved {to} for NFT #{token_id} (owner={owner})")
@call
def transfer_from(self, ctx, from_addr: str, to_addr: str, token_id: int):
owner = self._token_owner(token_id)
if owner != from_addr:
raise ValueError("from mismatch")
if not _is_authorized_transfer(
owner,
ctx.sender,
token_id,
dict(self.state.token_approvals),
dict(self.state.operator_approvals),
):
raise ValueError("not authorized")
key = str(token_id)
tok = dict(self.state.tokens)
tok[key] = {**tok[key], "owner": to_addr}
self.state.tokens = tok
self._set_owner_balance(from_addr, -1)
self._set_owner_balance(to_addr, 1)
ta = dict(self.state.token_approvals)
if str(token_id) in ta:
del ta[str(token_id)]
self.state.token_approvals = ta
self.emit(event.Transfer(_from=from_addr, _to=to_addr, _token_id=token_id))
print(f"NFT #{token_id} transferred: {from_addr} -> {to_addr}")
@query
def balance_of(self, owner: str) -> int:
if owner == ZERO:
raise ValueError("invalid owner")
return self.state.owner_balances.get(owner, 0)
@query
def owner_of(self, token_id: int) -> str:
return self._token_owner(token_id)
@query
def token_uri(self, token_id: int) -> str:
key = str(token_id)
if key not in self.state.tokens:
raise ValueError("token does not exist")
meta = self.state.tokens[key].get("uri", "")
base = self.state.base_uri
if base and not meta:
return base.rstrip("/") + "/" + key
return meta
Key patterns:
- Authorization is multi-layered: owner, per-token approval, and operator approval (approve-for-all) are all checked via
_is_authorized_transfer() - Token IDs use
str()keys because JSON only supports string keys - Always create a new dict with
dict(self.state.X)before mutating transfer_fromclears per-token approval after transfer (prevents stale approvals)base_uriprovides automatic token URI generation when individual URIs are not set
See the full PRC-721 Standard for the complete specification.
NFT Marketplace
The NFT Marketplace demonstrates cross-contract composition: it calls PRC-721 contracts to verify ownership and transfer NFTs, and PRC-20 contracts to handle payments -- all atomically within a single transaction.
# Source: contracts/tokens/nft_marketplace.py
"""
NFT Marketplace — Cross-contract call example: buy/sell NFTs using PRC-20 tokens.
Demonstrates cross-contract interaction between PRC-721 (NFT) and PRC-20 (payment)
contracts using the Pythonic ``Contract`` and ``PRC20`` handles.
"""
from panda import contract, constructor, call, query, event, Contract, PRC20
@contract
class NFTMarketplace:
"""Marketplace for buying/selling PRC-721 NFTs with PRC-20 payment."""
class State:
payment_token: str = ""
listings: dict = {}
fee_bps: int = 250 # 2.5% marketplace fee
fee_recipient: str = ""
total_volume: int = 0
total_sales: int = 0
@constructor
def deploy(self, ctx, payment_token: str, fee_bps: int = 250):
self.state.payment_token = payment_token
self.state.fee_bps = fee_bps
self.state.fee_recipient = ctx.sender
print(
f"NFTMarketplace deployed: payment_token={payment_token} fee={fee_bps}bps by {ctx.sender}"
)
@call
def list_nft(self, ctx, nft_contract: str, token_id: int, price: int):
"""List an NFT for sale. Seller must have approved the marketplace."""
if price <= 0:
raise ValueError("price must be positive")
# Verify the caller owns the NFT
owner = Contract(nft_contract).owner_of(token_id=token_id)
if owner != ctx.sender:
raise ValueError("caller does not own this NFT")
listing_id = f"{nft_contract}:{token_id}"
listings = dict(self.state.listings)
listings[listing_id] = {
"seller": ctx.sender,
"nft_contract": nft_contract,
"token_id": token_id,
"price": price,
"active": True,
}
self.state.listings = listings
self.emit(
event.NFTListed(
seller=ctx.sender,
nft_contract=nft_contract,
token_id=token_id,
price=price,
)
)
print(f"NFT listed: {nft_contract}#{token_id} by {ctx.sender} for {price}")
@call
def buy_nft(self, ctx, nft_contract: str, token_id: int):
"""Buy a listed NFT. Buyer must have approved payment token spending."""
listing_id = f"{nft_contract}:{token_id}"
listings = dict(self.state.listings)
listing = listings.get(listing_id)
if not listing or not listing["active"]:
raise ValueError("listing not found or inactive")
price = listing["price"]
seller = listing["seller"]
# Calculate fees
fee = (price * self.state.fee_bps) // 10000
seller_proceeds = price - fee
# Transfer payment from buyer to seller
payment = PRC20(self.state.payment_token)
payment.transfer_from(owner=ctx.sender, to=seller, value=seller_proceeds)
# Transfer fee to fee recipient
if fee > 0:
payment.transfer_from(owner=ctx.sender, to=self.state.fee_recipient, value=fee)
# Transfer NFT from seller to buyer
Contract(nft_contract).transfer_from(
from_addr=seller, to_addr=ctx.sender, token_id=token_id
)
# Mark listing as inactive
listing["active"] = False
listings[listing_id] = listing
self.state.listings = listings
self.state.total_volume = self.state.total_volume + price
self.state.total_sales = self.state.total_sales + 1
self.emit(
event.NFTSold(
buyer=ctx.sender,
seller=seller,
nft_contract=nft_contract,
token_id=token_id,
price=price,
fee=fee,
)
)
@call
def cancel_listing(self, ctx, nft_contract: str, token_id: int):
"""Cancel an active listing. Only the seller can cancel."""
listing_id = f"{nft_contract}:{token_id}"
listings = dict(self.state.listings)
listing = listings.get(listing_id)
if not listing or not listing["active"]:
raise ValueError("listing not found or inactive")
if listing["seller"] != ctx.sender:
raise ValueError("only seller can cancel")
listing["active"] = False
listings[listing_id] = listing
self.state.listings = listings
self.emit(
event.ListingCancelled(
seller=ctx.sender,
nft_contract=nft_contract,
token_id=token_id,
)
)
@query
def get_listing(self, nft_contract: str, token_id: int) -> dict:
listing_id = f"{nft_contract}:{token_id}"
listing = self.state.listings.get(listing_id)
if not listing:
return {}
return listing
@query
def get_stats(self) -> dict:
return {
"total_volume": self.state.total_volume,
"total_sales": self.state.total_sales,
"fee_bps": self.state.fee_bps,
}
Cross-contract handles are the key pattern here:
Contract(address)gives a generic handle -- call any method on any deployed contractPRC20(address)gives a typed handle with.transfer(),.transfer_from(),.balance_of()etc.buy_nftperforms three cross-contract calls atomically: payment to seller, fee to recipient, and NFT transfer to buyer
Token Swap (AMM)
The TokenSwap contract implements a constant-product automated market maker (AMM) for swapping between two PRC-20 tokens. It uses the formula x * y = k to price trades.
# Source: contracts/tokens/token_swap.py
"""
Token Swap — Cross-contract call example: simple AMM-like token swap.
Each pool holds reserves of two PRC-20 tokens (token_a, token_b).
Swaps use the constant-product formula: x * y = k.
"""
from panda import contract, constructor, call, query, event, PRC20
@contract
class TokenSwap:
"""Simple AMM swap pool for two PRC-20 tokens via cross-contract calls."""
class State:
token_a: str = ""
token_b: str = ""
reserve_a: int = 0
reserve_b: int = 0
total_lp: int = 0
lp_balances: dict = {}
fee_bps: int = 30 # 0.3% fee in basis points
owner: str = ""
@constructor
def deploy(self, ctx, token_a: str, token_b: str, fee_bps: int = 30):
if token_a == token_b:
raise ValueError("tokens must be different")
self.state.token_a = token_a
self.state.token_b = token_b
self.state.fee_bps = fee_bps
self.state.owner = ctx.sender
print(
f"TokenSwap pool deployed: token_a={token_a} token_b={token_b} fee={fee_bps}bps by {ctx.sender}"
)
@call
def add_liquidity(self, ctx, amount_a: int, amount_b: int):
"""Add liquidity to the pool. Transfers tokens from sender via cross-call."""
if amount_a <= 0 or amount_b <= 0:
raise ValueError("amounts must be positive")
# Transfer tokens into the pool
PRC20(self.state.token_a).transfer_from(
owner=ctx.sender, to=ctx.contract_address, value=amount_a
)
PRC20(self.state.token_b).transfer_from(
owner=ctx.sender, to=ctx.contract_address, value=amount_b
)
# Calculate LP tokens to mint
if self.state.total_lp == 0:
# First deposit: LP = sqrt(a * b) approximated as (a + b) / 2
lp_amount = (amount_a + amount_b) // 2
else:
lp_a = amount_a * self.state.total_lp // self.state.reserve_a
lp_b = amount_b * self.state.total_lp // self.state.reserve_b
lp_amount = min(lp_a, lp_b)
if lp_amount <= 0:
raise ValueError("insufficient liquidity minted")
self.state.reserve_a = self.state.reserve_a + amount_a
self.state.reserve_b = self.state.reserve_b + amount_b
self.state.total_lp = self.state.total_lp + lp_amount
lp = dict(self.state.lp_balances)
lp[ctx.sender] = lp.get(ctx.sender, 0) + lp_amount
self.state.lp_balances = lp
self.emit(
event.LiquidityAdded(
provider=ctx.sender,
amount_a=amount_a,
amount_b=amount_b,
lp_minted=lp_amount,
)
)
@call
def swap_a_for_b(self, ctx, amount_in: int):
"""Swap token_a for token_b using constant-product formula."""
if amount_in <= 0:
raise ValueError("amount must be positive")
if self.state.reserve_a == 0 or self.state.reserve_b == 0:
raise ValueError("no liquidity")
# Apply fee
amount_in_with_fee = amount_in * (10000 - self.state.fee_bps)
amount_out = (amount_in_with_fee * self.state.reserve_b) // (
self.state.reserve_a * 10000 + amount_in_with_fee
)
if amount_out <= 0:
raise ValueError("insufficient output amount")
# Transfer token_a from sender into pool
PRC20(self.state.token_a).transfer_from(
owner=ctx.sender, to=ctx.contract_address, value=amount_in
)
# Transfer token_b from pool to sender
PRC20(self.state.token_b).transfer(to=ctx.sender, value=amount_out)
self.state.reserve_a = self.state.reserve_a + amount_in
self.state.reserve_b = self.state.reserve_b - amount_out
self.emit(
event.Swap(
sender=ctx.sender,
token_in=self.state.token_a,
amount_in=amount_in,
amount_out=amount_out,
)
)
@call
def swap_b_for_a(self, ctx, amount_in: int):
"""Swap token_b for token_a using constant-product formula."""
if amount_in <= 0:
raise ValueError("amount must be positive")
if self.state.reserve_a == 0 or self.state.reserve_b == 0:
raise ValueError("no liquidity")
amount_in_with_fee = amount_in * (10000 - self.state.fee_bps)
amount_out = (amount_in_with_fee * self.state.reserve_a) // (
self.state.reserve_b * 10000 + amount_in_with_fee
)
if amount_out <= 0:
raise ValueError("insufficient output amount")
PRC20(self.state.token_b).transfer_from(
owner=ctx.sender, to=ctx.contract_address, value=amount_in
)
PRC20(self.state.token_a).transfer(to=ctx.sender, value=amount_out)
self.state.reserve_b = self.state.reserve_b + amount_in
self.state.reserve_a = self.state.reserve_a - amount_out
self.emit(
event.Swap(
sender=ctx.sender,
token_in=self.state.token_b,
amount_in=amount_in,
amount_out=amount_out,
)
)
@query
def get_reserves(self) -> dict:
return {
"reserve_a": self.state.reserve_a,
"reserve_b": self.state.reserve_b,
"total_lp": self.state.total_lp,
}
@query
def get_lp_balance(self, owner: str) -> int:
return self.state.lp_balances.get(owner, 0)
@query
def quote_swap_a_for_b(self, amount_in: int) -> int:
"""Preview how much token_b you'd get for amount_in of token_a."""
if self.state.reserve_a == 0 or self.state.reserve_b == 0:
return 0
amount_in_with_fee = amount_in * (10000 - self.state.fee_bps)
return (amount_in_with_fee * self.state.reserve_b) // (
self.state.reserve_a * 10000 + amount_in_with_fee
)
How the AMM works:
- The constant-product formula
x * y = kensures that larger trades get progressively worse prices (slippage), which protects liquidity providers - Fees are applied before the swap calculation using basis points (30 bps = 0.3%)
- LP tokens track each provider's share of the pool -- first deposit uses
(a + b) / 2, subsequent deposits use proportional minting viamin(lp_a, lp_b) quote_swap_a_for_blets users preview their output amount without executing
Token Interfaces (IPRC-20 / IPRC-721)
Panda defines abstract interfaces for token standards, analogous to Solidity's interface keyword. Contracts can inherit from these to guarantee compliance. All methods raise NotImplementedError -- subclasses must override them.
IPRC20 Interface
<!-- Source: contracts/tokens/iprc20.py -->from panda import contract, call, query
@contract
class IPRC20:
"""Abstract PRC-20 interface. Inherit and override all methods."""
class State:
_interface_marker: str = "IPRC20"
# ---- Metadata (queries) ----
@query
def name(self) -> str:
raise NotImplementedError("IPRC20.name")
@query
def symbol(self) -> str:
raise NotImplementedError("IPRC20.symbol")
@query
def decimals(self) -> int:
raise NotImplementedError("IPRC20.decimals")
@query
def total_supply(self) -> int:
raise NotImplementedError("IPRC20.total_supply")
@query
def balance_of(self, owner: str) -> int:
raise NotImplementedError("IPRC20.balance_of")
@query
def allowance(self, owner: str, spender: str) -> int:
raise NotImplementedError("IPRC20.allowance")
# ---- Mutations (calls) ----
@call
def transfer(self, ctx, to: str, value: int):
raise NotImplementedError("IPRC20.transfer")
@call
def approve(self, ctx, spender: str, value: int):
raise NotImplementedError("IPRC20.approve")
@call
def transfer_from(self, ctx, owner: str, to: str, value: int):
raise NotImplementedError("IPRC20.transfer_from")
IPRC721 Interface
<!-- Source: contracts/tokens/iprc721.py -->from panda import contract, call, query
@contract
class IPRC721:
"""Abstract PRC-721 interface. Inherit and override all methods."""
class State:
_interface_marker: str = "IPRC721"
@query
def name(self) -> str:
raise NotImplementedError("IPRC721.name")
@query
def symbol(self) -> str:
raise NotImplementedError("IPRC721.symbol")
@query
def balance_of(self, owner: str) -> int:
raise NotImplementedError("IPRC721.balance_of")
@query
def owner_of(self, token_id: int) -> str:
raise NotImplementedError("IPRC721.owner_of")
@query
def get_approved(self, token_id: int) -> str:
raise NotImplementedError("IPRC721.get_approved")
@query
def is_approved_for_all(self, owner: str, operator: str) -> bool:
raise NotImplementedError("IPRC721.is_approved_for_all")
@query
def token_uri(self, token_id: int) -> str:
raise NotImplementedError("IPRC721.token_uri")
@call
def mint(self, ctx, to: str, token_uri: str = ""):
raise NotImplementedError("IPRC721.mint")
@call
def approve(self, ctx, to: str, token_id: int):
raise NotImplementedError("IPRC721.approve")
@call
def set_approval_for_all(self, ctx, operator: str, approved: bool):
raise NotImplementedError("IPRC721.set_approval_for_all")
@call
def transfer_from(self, ctx, from_addr: str, to_addr: str, token_id: int):
raise NotImplementedError("IPRC721.transfer_from")
@call
def safe_transfer_from(self, ctx, from_addr: str, to_addr: str, token_id: int):
raise NotImplementedError("IPRC721.safe_transfer_from")
Implementing an Interface
The PRC20WithInterface contract shows how to implement the IPRC20 interface. It uses panda.token.FungibleToken for core logic and overrides every interface method:
from panda import contract, constructor, call, query, event
from panda.token import FungibleToken, TokenError
@contract
class PRC20WithInterface:
"""PRC-20 token that explicitly implements the IPRC20 interface."""
class State:
token: dict = {}
owner: str = ""
@constructor
def deploy(self, ctx, name: str, symbol: str, decimals: int = 18, initial_supply: int = 0):
t = FungibleToken()
t.configure(name=name, symbol=symbol, decimals=decimals)
if initial_supply > 0:
t.mint(ctx.sender, initial_supply)
self.state.token = t.to_dict()
self.state.owner = ctx.sender
self.emit(
event.Transfer(
_from="0x0000000000000000000000000000000000000000",
_to=ctx.sender,
_value=initial_supply,
)
)
# ---- IPRC20 interface implementation ----
@query
def name(self) -> str:
return FungibleToken(self.state.token).name()
@query
def symbol(self) -> str:
return FungibleToken(self.state.token).symbol()
@query
def decimals(self) -> int:
return FungibleToken(self.state.token).decimals()
@query
def total_supply(self) -> int:
return FungibleToken(self.state.token).total_supply()
@query
def balance_of(self, owner: str) -> int:
return FungibleToken(self.state.token).balance_of(owner)
@query
def allowance(self, owner: str, spender: str) -> int:
return FungibleToken(self.state.token).allowance(owner, spender)
@call
def transfer(self, ctx, to: str, value: int):
t = FungibleToken(self.state.token)
t.transfer(ctx.sender, to, value)
self.state.token = t.to_dict()
self.emit(event.Transfer(_from=ctx.sender, _to=to, _value=value))
@call
def approve(self, ctx, spender: str, value: int):
t = FungibleToken(self.state.token)
t.approve(ctx.sender, spender, value)
self.state.token = t.to_dict()
self.emit(event.Approval(_owner=ctx.sender, _spender=spender, _value=value))
@call
def transfer_from(self, ctx, owner: str, to: str, value: int):
t = FungibleToken(self.state.token)
t.transfer_from(ctx.sender, owner, to, value)
self.state.token = t.to_dict()
self.emit(event.Transfer(_from=owner, _to=to, _value=value))
# ---- Additional methods beyond IPRC20 ----
@call
def mint(self, ctx, to: str, amount: int):
if ctx.sender != self.state.owner:
raise TokenError("only owner can mint")
t = FungibleToken(self.state.token)
t.mint(to, amount)
self.state.token = t.to_dict()
self.emit(
event.Transfer(
_from="0x0000000000000000000000000000000000000000",
_to=to,
_value=amount,
)
)
@call
def burn(self, ctx, amount: int):
t = FungibleToken(self.state.token)
t.burn(ctx.sender, amount)
self.state.token = t.to_dict()
self.emit(
event.Transfer(
_from=ctx.sender,
_to="0x0000000000000000000000000000000000000000",
_value=amount,
)
)
The interface pattern ensures compliance: any contract that implements all IPRC20 methods is a valid PRC-20 token. The PRC721WithInterface contract follows the same pattern for NFTs, implementing all IPRC721 methods with ownership tracking, per-token approvals, operator approvals, and burn support.
Try It
- Open the Playground and select the PRC-20 Token, PRC-721 NFT, NFT Marketplace, or Token Swap template
- Follow the Minting NFTs tutorial for a guided walkthrough
- Follow the DeFi: Token Swap tutorial to build an AMM