05 semi fungible tokens
What is Semi-Fungible?
In carbon credits, a "unit" must be:
Interchangeable within the same project/vintage (easy to buy/sell/retire in bulk)
Not interchangeable across different projects/vintages (auditors need origin traceability)
The Rule:
1 unit of Project A 2024 = interchangeable with any other unit of Project A 2024
1 unit of Project A 2024 ≠ 1 unit of Project B 2023This "fungible inside, non-fungible across" behavior is semi-fungible.
Why Semi-Fungible?
Traditional Approaches Have Limitations
Fully Fungible Tokens:
❌ Credits become interchangeable across projects
❌ Breaks audit traceability
❌ High risk of credit mixing
❌ Not regulator-friendly
NFT-Only Models:
❌ Each credit is a separate NFT
❌ Poor scalability (millions of NFTs)
❌ No fractional ownership
❌ Enterprise bulk operations are complex
Semi-Fungible Solution:
✅ Fungible within project (easy operations)
✅ Non-fungible across projects (traceability preserved)
✅ Supports fractional ownership
✅ Scales efficiently
✅ Enterprise-friendly
Contract Architecture
Option A: Multi-Token Contract (Recommended)
A single contract that supports many token IDs, where:
tokenIdrepresents the project (or project + vintage)Balances stored as
balanceOf(user, tokenId)
Advantages:
Scales to millions of credits
Supports enterprise bulk operations
Preserves project separation
Supports decimals via units design
Standard: ERC-1155 (Multi-Token Standard)
Option B: Separate ERC-20 Per Project
Each project becomes its own ERC-20 token contract.
Disadvantages:
Many contracts to manage
Complex UX (many token addresses)
Harder to scale
More gas costs
Recommendation: Use Option A for platform scalability.
Token ID Strategy
Units & Decimals
The Challenge
ERC-1155 uses integer balances. Carbon credits need decimal precision (e.g., 0.25 tons, 12,437.62 tons).
The Solution: Fixed-Point Units
Define UNIT = 1e6 (or 1e18 for maximum precision).
Conversion:
UI Convention:
Store on-chain as integers
Display in UI as decimals (divide by UNIT)
Example:
Contract Design: CarbonCredit1155
Storage (Per tokenId)
Transfer Policy Enum
Roles
Core Functions
setProjectConfig
Checks:
Project must have PoAI approved
Cap must be > 0
mintCredits
Enforces:
Project must be active
issued[tokenId] + amountUnits <= cap[tokenId]Increments
issued[tokenId]
retire
Enforces:
balanceOf(msg.sender, tokenId) >= amountUnitsBurns units from user
Increments
retired[tokenId]Emits
CreditsRetiredevent
balanceOf
Standard ERC-1155 balance query.
Events
How PoAI Plugs In (Gating)
PoAI is NOT inside the ERC-1155 contract; it's the approval precondition.
Enforced Gating Rule: A tokenId can be configured (cap/active) only if:
Project has PoAI approved on-chain in
ProjectRegistry, ORGovernance provides PoAI hashes as part of config transaction
Implementation:
Result: No PoAI → no activation → no mint.
Complete Platform Flow
Project Onboarding → PoAI → Governance Approval
Issuer submits project (off-chain)
PoAI module verifies:
Asset integrity
Data integrity
Process integrity
PoAI produces:
bundleURI(encrypted JSON)bundleHash,assetHash,dataHash,processHash
Governance proposal: Approve project + issuance cap + policy
On-chain:
Mint
ProjectRecord(projectId)Attach PoAI hashes to
ProjectRegistryCall
CarbonCredit1155.setProjectConfig(projectId, capUnits, policy, active=true)
Result: Project tokenId is now "live" and can issue credits.
Retail Purchase
Pattern A (Non-custodial on-chain):
User pays in stablecoin/native token
Marketplace contract transfers credit units from treasury to user
Pattern B (Off-chain payment):
User pays via fiat/off-chain
Backend instructs issuer wallet to transfer/mint credits to user
On-chain result: balanceOf(user, projectId) increases
Retirement (Burn) + Certificate
User/Enterprise selects:
projectIdAmount to retire
Call
retire(projectId, amountUnits)Contract:
Burns balance
Increments
retiredcounterEmits event
Indexer listens to
CreditsRetiredReporting service generates:
Certificate PDF + JSON
Includes tx hash, project metadata, PoAI
bundleHashreference
Result: Retirement is permanent and auditable (prevents double counting).
Marketplace / Transfer Policy
Per-Project Policy
OPEN: Normal ERC-1155 transfers allowed
RESTRICTED: Transfers only to/from approved operators (marketplace, custody, escrow)
DISABLED: No transfers at all; only mint to buyer and retire
Enforcement
Override _beforeTokenTransfer and check:
This is a big "enterprise-friendly" lever for compliance.
Metadata Structure
uri(tokenId) should return JSON containing:
This makes your ERC-1155 tokenId self-describing and audit-ready.
Operational Advantages
Retail Side
Small purchases possible (0.01, 0.25, etc.)
Simple holdings view by project
Retirement gives clean proof
Enterprise Side
Bulk purchase and retirement without handling thousands of NFTs
Exact accounting down to decimals
Reports remain project-specific for audits
Compliance
Each unit always points back to a specific
ProjectRecord(origin preserved)Prevents "credit mixing" that breaks ESG audits
Implementation Decision Points
One-Liner Explanation
"We make each project a unique record, and the credits under that project behave like units you can buy in fractions and retire easily like tokens, but still traceable per project like NFTs."
Last updated