Skip to content

Instantly share code, notes, and snippets.

@nazt
Last active February 12, 2026 09:30
Show Gist options
  • Select an option

  • Save nazt/2e90ecfad9e65099e9b357d35e68c9bc to your computer and use it in GitHub Desktop.

Select an option

Save nazt/2e90ecfad9e65099e9b357d35e68c9bc to your computer and use it in GitHub Desktop.
Proposed Rules for Scaling Blockchain-Based Carbon Market Trading in Thailand — MDPI Paper Draft (Section 3+4)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title CarbonCreditRegistry
/// @notice Participant registration and carbon credit certification for Thai carbon market
/// @dev Merkle-based verification of TGO (Thailand Greenhouse Gas Management Organization) certification
contract CarbonCreditRegistry {
// --- Types ---
struct Participant {
address wallet;
string tgoId; // TGO-registered organization ID
uint256 creditBalance; // Available carbon credits (tonnes CO2e, 18 decimals)
bytes32 merkleRoot; // Verification tree of certified projects
bool isVerified; // TGO third-party verification status
uint8 sector; // 0=energy, 1=industry, 2=transport, 3=agriculture, 4=sme
}
// --- State ---
address public admin;
mapping(address => Participant) public participants;
address[] public participantList;
uint256 public constant CREDIT_DECIMALS = 1e18; // 1 credit = 1 tonne CO2e
// --- Events ---
event ParticipantRegistered(address indexed wallet, string tgoId, uint8 sector);
event ParticipantVerified(address indexed wallet, bytes32 merkleRoot);
event CreditsMinted(address indexed to, uint256 amount, string projectId);
event CreditsBurned(address indexed from, uint256 amount, string reason);
event CreditsTransferred(address indexed from, address indexed to, uint256 amount);
// --- Errors ---
error NotAdmin();
error AlreadyRegistered();
error NotRegistered();
error NotVerified();
error InsufficientCredits();
error InvalidProof();
// --- Modifiers ---
modifier onlyAdmin() {
if (msg.sender != admin) revert NotAdmin();
_;
}
modifier onlyRegistered() {
if (participants[msg.sender].wallet == address(0)) revert NotRegistered();
_;
}
modifier onlyVerified() {
if (!participants[msg.sender].isVerified) revert NotVerified();
_;
}
// --- Constructor ---
constructor() {
admin = msg.sender;
}
// --- Registration ---
/// @notice Register a new participant with TGO organization ID
/// @param tgoId TGO-registered organization identifier
/// @param sector Sector code: 0=energy, 1=industry, 2=transport, 3=agriculture, 4=sme
function register(string calldata tgoId, uint8 sector) external {
if (participants[msg.sender].wallet != address(0)) revert AlreadyRegistered();
require(sector <= 4, "Invalid sector");
participants[msg.sender] = Participant({
wallet: msg.sender,
tgoId: tgoId,
creditBalance: 0,
merkleRoot: bytes32(0),
isVerified: false,
sector: sector
});
participantList.push(msg.sender);
emit ParticipantRegistered(msg.sender, tgoId, sector);
}
/// @notice Admin verifies participant and sets their project Merkle root
/// @param wallet Participant address
/// @param merkleRoot Root of Merkle tree containing certified project hashes
function verify(address wallet, bytes32 merkleRoot) external onlyAdmin {
if (participants[wallet].wallet == address(0)) revert NotRegistered();
participants[wallet].isVerified = true;
participants[wallet].merkleRoot = merkleRoot;
emit ParticipantVerified(wallet, merkleRoot);
}
// --- Credit Management ---
/// @notice Mint carbon credits for a verified participant (TGO-certified reduction)
/// @param to Recipient address
/// @param tonnes Amount in tonnes CO2e (will be scaled by CREDIT_DECIMALS)
/// @param projectId Identifier of the certified reduction project
/// @param proof Merkle proof that projectId is in participant's certified tree
function mint(
address to,
uint256 tonnes,
string calldata projectId,
bytes32[] calldata proof
) external onlyAdmin {
if (!participants[to].isVerified) revert NotVerified();
// Verify project is in participant's certified tree
bytes32 leaf = keccak256(abi.encodePacked(to, projectId));
if (!_verifyProof(proof, participants[to].merkleRoot, leaf)) revert InvalidProof();
uint256 amount = tonnes * CREDIT_DECIMALS;
participants[to].creditBalance += amount;
emit CreditsMinted(to, amount, projectId);
}
/// @notice Burn (retire) carbon credits — irreversible
/// @param amount Credits to burn (with decimals)
/// @param reason Retirement reason (e.g., "offset Q1 2026")
function burn(uint256 amount, string calldata reason) external onlyRegistered {
if (participants[msg.sender].creditBalance < amount) revert InsufficientCredits();
participants[msg.sender].creditBalance -= amount;
emit CreditsBurned(msg.sender, amount, reason);
}
/// @notice Transfer credits between participants (used by CarbonTrading contract)
/// @param from Seller
/// @param to Buyer
/// @param amount Credits to transfer
function transferCredits(address from, address to, uint256 amount) external {
// Only callable by approved trading contract or admin
require(msg.sender == admin || _isTradingContract(msg.sender), "Unauthorized");
if (participants[from].creditBalance < amount) revert InsufficientCredits();
participants[from].creditBalance -= amount;
participants[to].creditBalance += amount;
emit CreditsTransferred(from, to, amount);
}
// --- Views ---
function balanceOf(address wallet) external view returns (uint256) {
return participants[wallet].creditBalance;
}
function isVerified(address wallet) external view returns (bool) {
return participants[wallet].isVerified;
}
function participantCount() external view returns (uint256) {
return participantList.length;
}
function getSector(address wallet) external view returns (uint8) {
return participants[wallet].sector;
}
// --- Internal ---
address public tradingContract;
function setTradingContract(address _trading) external onlyAdmin {
tradingContract = _trading;
}
function _isTradingContract(address addr) internal view returns (bool) {
return addr == tradingContract;
}
function _verifyProof(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash == root;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title CarbonDataLogger
/// @notice Immutable on-chain record of emissions, trades, and retirements
/// @dev Append-only — Nothing is Deleted
contract CarbonDataLogger {
// --- Types ---
struct EmissionRecord {
string projectId;
string latitude;
string longitude;
uint256 co2Tonnes; // 18 decimals
uint256 verificationScore; // 0-100
string logDatetime;
address reporter;
uint64 blockTimestamp;
}
struct TradeRecord {
address seller;
address buyer;
uint256 quantity; // tonnes CO2e, 18 decimals
uint256 price; // THB equivalent, 18 decimals
uint64 timestamp;
}
struct RetirementRecord {
address holder;
uint256 quantity; // tonnes CO2e, 18 decimals
string reason;
uint64 timestamp;
}
// --- State ---
address public admin;
mapping(address => bool) public authorizedReporters;
EmissionRecord[] public emissions;
TradeRecord[] public trades;
RetirementRecord[] public retirements;
// --- Events ---
event EmissionLogged(
uint256 indexed recordId,
string projectId,
uint256 co2Tonnes,
uint256 verificationScore,
address indexed reporter
);
event TradeLogged(
uint256 indexed recordId,
address indexed seller,
address indexed buyer,
uint256 quantity,
uint256 price
);
event RetirementLogged(
uint256 indexed recordId,
address indexed holder,
uint256 quantity,
string reason
);
// --- Errors ---
error NotAdmin();
error NotAuthorized();
// --- Modifiers ---
modifier onlyAdmin() {
if (msg.sender != admin) revert NotAdmin();
_;
}
modifier onlyAuthorized() {
if (!authorizedReporters[msg.sender] && msg.sender != admin) revert NotAuthorized();
_;
}
// --- Constructor ---
constructor() {
admin = msg.sender;
}
// --- Admin ---
function setAuthorizedReporter(address reporter, bool authorized) external onlyAdmin {
authorizedReporters[reporter] = authorized;
}
// --- Logging ---
/// @notice Log an emission measurement — append-only
function logEmission(
string calldata projectId,
string calldata latitude,
string calldata longitude,
uint256 co2Tonnes,
uint256 verificationScore,
string calldata logDatetime
) external onlyAuthorized {
require(verificationScore <= 100, "Score must be 0-100");
uint256 recordId = emissions.length;
emissions.push(EmissionRecord({
projectId: projectId,
latitude: latitude,
longitude: longitude,
co2Tonnes: co2Tonnes,
verificationScore: verificationScore,
logDatetime: logDatetime,
reporter: msg.sender,
blockTimestamp: uint64(block.timestamp)
}));
emit EmissionLogged(recordId, projectId, co2Tonnes, verificationScore, msg.sender);
}
/// @notice Log a trade — called by CarbonTrading contract
function logTrade(
address seller,
address buyer,
uint256 quantity,
uint256 price
) external onlyAuthorized {
uint256 recordId = trades.length;
trades.push(TradeRecord({
seller: seller,
buyer: buyer,
quantity: quantity,
price: price,
timestamp: uint64(block.timestamp)
}));
emit TradeLogged(recordId, seller, buyer, quantity, price);
}
/// @notice Log a credit retirement — called by CarbonCreditRegistry or CarbonTrading
function logRetirement(
address holder,
uint256 quantity,
string calldata reason
) external onlyAuthorized {
uint256 recordId = retirements.length;
retirements.push(RetirementRecord({
holder: holder,
quantity: quantity,
reason: reason,
timestamp: uint64(block.timestamp)
}));
emit RetirementLogged(recordId, holder, quantity, reason);
}
// --- Views ---
function emissionCount() external view returns (uint256) {
return emissions.length;
}
function tradeCount() external view returns (uint256) {
return trades.length;
}
function retirementCount() external view returns (uint256) {
return retirements.length;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {CarbonCreditRegistry} from "./CarbonCreditRegistry.sol";
import {CarbonDataLogger} from "./CarbonDataLogger.sol";
/// @title CarbonTrading
/// @notice Algorithm 1: Carbon Credit Cap and Trade for Thailand
/// @dev Implements sorted offer matrix with lowest-price-first matching
contract CarbonTrading {
// --- Types ---
struct SellOffer {
address seller;
uint256 pricePerTonne; // THB equivalent, 18 decimals
uint256 quantity; // tonnes CO2e, 18 decimals
uint64 timestamp;
bool active;
}
// --- State ---
CarbonCreditRegistry public registry;
CarbonDataLogger public dataLogger;
address public admin;
uint256 public floorPrice; // gamma_floor — minimum credit price
uint256 public currentPeriod; // quarterly period counter
SellOffer[] public offers; // sorted by pricePerTonne ascending
uint256 public activeOfferCount;
mapping(uint8 => uint256) public sectorCaps; // sector => cap (tonnes CO2e, 18 decimals)
// --- Events ---
event SellOfferCreated(uint256 indexed offerId, address indexed seller, uint256 price, uint256 quantity);
event SellOfferCancelled(uint256 indexed offerId, address indexed seller);
event TradeExecuted(
address indexed seller,
address indexed buyer,
uint256 quantity,
uint256 pricePerTonne,
uint256 totalCost
);
event PeriodEnded(uint256 indexed period, uint256 timestamp);
event FloorPriceUpdated(uint256 oldPrice, uint256 newPrice);
event SectorCapUpdated(uint8 indexed sector, uint256 cap);
// --- Errors ---
error NotAdmin();
error NotRegistered();
error NotVerified();
error BelowFloorPrice();
error InsufficientCredits();
error ZeroQuantity();
error OfferNotActive();
error NotOfferOwner();
// --- Modifiers ---
modifier onlyAdmin() {
if (msg.sender != admin) revert NotAdmin();
_;
}
// --- Constructor ---
/// @param _registry CarbonCreditRegistry address
/// @param _dataLogger CarbonDataLogger address
/// @param _floorPrice Initial floor price (THB/tonne, 18 decimals)
constructor(
address _registry,
address _dataLogger,
uint256 _floorPrice
) {
admin = msg.sender;
registry = CarbonCreditRegistry(_registry);
dataLogger = CarbonDataLogger(_dataLogger);
floorPrice = _floorPrice;
currentPeriod = 1;
}
// --- Seller: Create Offer (Algorithm 1, lines 15-24) ---
/// @notice Create a sell offer — inserted in price-sorted position
/// @param pricePerTonne Asking price (THB/tonne, 18 decimals) — must be >= floorPrice
/// @param quantity Carbon credits to sell (tonnes CO2e, 18 decimals)
function createSellOffer(uint256 pricePerTonne, uint256 quantity) external {
if (!registry.isVerified(msg.sender)) revert NotVerified();
if (pricePerTonne < floorPrice) revert BelowFloorPrice();
if (quantity == 0) revert ZeroQuantity();
if (registry.balanceOf(msg.sender) < quantity) revert InsufficientCredits();
// Transfer credits to this contract (escrow)
registry.transferCredits(msg.sender, address(this), quantity);
uint256 offerId = offers.length;
offers.push(SellOffer({
seller: msg.sender,
pricePerTonne: pricePerTonne,
quantity: quantity,
timestamp: uint64(block.timestamp),
active: true
}));
activeOfferCount++;
emit SellOfferCreated(offerId, msg.sender, pricePerTonne, quantity);
}
/// @notice Cancel an active sell offer and return escrowed credits
function cancelSellOffer(uint256 offerId) external {
SellOffer storage offer = offers[offerId];
if (!offer.active) revert OfferNotActive();
if (offer.seller != msg.sender) revert NotOfferOwner();
offer.active = false;
activeOfferCount--;
// Return escrowed credits
registry.transferCredits(address(this), msg.sender, offer.quantity);
emit SellOfferCancelled(offerId, msg.sender);
}
// --- Buyer: Match Offers (Algorithm 1, lines 25-49) ---
/// @notice Buy credits — matches against offers lowest-price-first
/// @param maxPrice Maximum willingness to pay (THB/tonne, 18 decimals)
/// @param quantity Carbon credits needed (tonnes CO2e, 18 decimals)
/// @return totalFilled Actual quantity matched
/// @return totalCost Total cost of matched credits
function buyCredits(uint256 maxPrice, uint256 quantity)
external
returns (uint256 totalFilled, uint256 totalCost)
{
if (quantity == 0) revert ZeroQuantity();
// Build sorted indices (ascending price)
uint256[] memory sortedIndices = _getSortedOfferIndices();
uint256 remaining = quantity;
for (uint256 i = 0; i < sortedIndices.length && remaining > 0; i++) {
SellOffer storage offer = offers[sortedIndices[i]];
if (!offer.active || offer.quantity == 0) continue;
if (offer.pricePerTonne > maxPrice) continue;
uint256 fillAmount;
if (offer.quantity >= remaining) {
// Full fill from this seller
fillAmount = remaining;
} else {
// Partial fill — take all available
fillAmount = offer.quantity;
}
uint256 cost = fillAmount * offer.pricePerTonne / 1e18;
// Transfer credits from escrow to buyer
registry.transferCredits(address(this), msg.sender, fillAmount);
// Log trade
dataLogger.logTrade(offer.seller, msg.sender, fillAmount, offer.pricePerTonne);
emit TradeExecuted(offer.seller, msg.sender, fillAmount, offer.pricePerTonne, cost);
offer.quantity -= fillAmount;
if (offer.quantity == 0) {
offer.active = false;
activeOfferCount--;
}
remaining -= fillAmount;
totalCost += cost;
totalFilled += fillAmount;
}
}
// --- Admin Functions ---
/// @notice End current trading period and deactivate all remaining offers (Algorithm 1, lines 2-14)
function endPeriod() external onlyAdmin {
// Deactivate all offers and return escrowed credits
for (uint256 i = 0; i < offers.length; i++) {
if (offers[i].active && offers[i].quantity > 0) {
registry.transferCredits(address(this), offers[i].seller, offers[i].quantity);
offers[i].active = false;
}
}
activeOfferCount = 0;
emit PeriodEnded(currentPeriod, block.timestamp);
currentPeriod++;
}
/// @notice Set floor price — Rule R3
function setFloorPrice(uint256 _floorPrice) external onlyAdmin {
emit FloorPriceUpdated(floorPrice, _floorPrice);
floorPrice = _floorPrice;
}
/// @notice Set sector cap — Rule R2
function setSectorCap(uint8 sector, uint256 cap) external onlyAdmin {
require(sector <= 4, "Invalid sector");
sectorCaps[sector] = cap;
emit SectorCapUpdated(sector, cap);
}
// --- Views ---
function offerCount() external view returns (uint256) {
return offers.length;
}
function getOffer(uint256 offerId) external view returns (SellOffer memory) {
return offers[offerId];
}
// --- Internal ---
/// @dev Returns indices of active offers sorted by price ascending
function _getSortedOfferIndices() internal view returns (uint256[] memory) {
// Collect active offer indices
uint256[] memory active = new uint256[](activeOfferCount);
uint256 count = 0;
for (uint256 i = 0; i < offers.length && count < activeOfferCount; i++) {
if (offers[i].active && offers[i].quantity > 0) {
active[count] = i;
count++;
}
}
// Insertion sort by price ascending (fine for expected offer counts)
for (uint256 i = 1; i < count; i++) {
uint256 key = active[i];
uint256 keyPrice = offers[key].pricePerTonne;
uint256 j = i;
while (j > 0 && offers[active[j - 1]].pricePerTonne > keyPrice) {
active[j] = active[j - 1];
j--;
}
active[j] = key;
}
return active;
}
}

Proposed Rules for Scaling Blockchain-Based Carbon Market Trading in Thailand

Draft for MDPI Paper Section 3 (Results) + Partial Section 4 (Discussion) Date: 2026-02-12

Authors:

  • Ekkaporn Nawapanan ^1
  • Nat Weerawan ^2
  • Hisam Samae ^1
  • Phuchiwan Suriyawong ^1
  • [Firstname Lastname] ^2,* (placeholder in docx)

Affiliations:

  1. Research unit for Energy Economics & Ecological management, Multidisciplinary Research Institute, CMU
  2. Program in Integrated Science, Multidisciplinary and Interdisciplinary School (MIdS), CMU
  3. Department of Industrial Engineering, Faculty of Engineering, CMU — sate@eng.cmu.ac.th
  4. Department of Geography, Faculty of Social Sciences, CMU — worradorn.ph@cmu.ac.th

Corresponding: sate@eng.cmu.ac.th; worradorn.ph@cmu.ac.th | Tel: +66 899446998


3. Results

3.1. Proposed System Architecture

The proposed carbon credit trading system for Thailand operates on a two-layer blockchain architecture deployed on JibChain, a Thai-developed EVM-compatible network. The architecture separates settlement (Layer 1) from high-throughput trading operations (Layer 2), following the OP Stack rollup design pattern.

Table 1. Network Configuration

Parameter JibChain L1 Carbon Trading L2
Chain ID 8899 555888
Consensus Proof-of-Stake OP Stack Sequencer
Block Time 5 seconds 10 seconds
Gas Token JBC (native) ETH (bridged)
Purpose Settlement, finality Trading, data logging

The L2 chain inherits security from L1 through batch submission of transaction data, providing both scalability for frequent trading operations and finality guarantees through the L1 consensus mechanism. This approach aligns with the layered ecosystem framework proposed by Baiz [5], where the transaction layer is separated from the registry and physical measurement layers.

3.2. Smart Contract Design

Three smart contracts govern the carbon credit lifecycle: registration, trading, and data logging. The design follows the two-contract pattern established by Aoun et al. [3] but extends it with a dedicated data integrity layer informed by IoT sensor verification.

Contract 1: CarbonCreditRegistry.sol

The registry contract manages participant registration, carbon credit certification, and Merkle-based identity verification. Each participant (seller or buyer) must register with a verified wallet address and organizational identifier aligned with Thailand's Greenhouse Gas Management Organization (TGO) requirements.

struct Participant {
    address wallet;
    string tgoId;           // TGO-registered organization ID
    uint256 creditBalance;  // Available carbon credits (tonnes CO2e)
    bytes32 merkleRoot;     // Verification tree of certified projects
    bool isVerified;        // TGO third-party verification status
}

Credit minting is restricted to TGO-certified reduction projects. Each credit represents one tonne of CO2 equivalent, following the ICVCM Core Carbon Principles [5] for additionality, permanence, and no double counting. The Merkle proof pattern enables efficient on-chain verification of project certification without storing the full certification data on-chain.

Contract 2: CarbonTrading.sol

The trading contract implements a modified version of the Energy Cap and Trade algorithm (Algorithm 1, Aoun et al. [3]), adapted for carbon credit markets with the following key modifications:

  1. Energy units (kWh) are replaced by carbon credits (tonnes CO2e)
  2. Billing periods are extended from monthly to quarterly, aligned with TGO reporting cycles
  3. Cap allocation uses sector-specific benchmarks derived from Thailand's NDC commitments
  4. A floor price mechanism prevents carbon credit devaluation below regulatory minimums

Contract 3: CarbonDataLogger.sol

The data logger provides an immutable on-chain record of all environmental measurements, emissions calculations, and trading transactions. This addresses the "garbage in, garbage out" challenge identified by Baiz [5], where blockchain immutability is only valuable if the underlying data is trustworthy.

function logEmission(
    string projectId,
    string latitude,
    string longitude,
    uint256 co2Tonnes,
    uint256 verificationScore,
    string logDatetime
) external onlyVerifiedParticipants

The verificationScore field (0-100) enables downstream quality filtering, ensuring that only high-confidence measurements are used for credit minting decisions.

Table 1b. Deployed Contract Addresses on DustBoy L2 (Chain 555888)

Contract Address Bytecode Size
CarbonCreditRegistry 0x5FbDB2315678afecb367f032d93F642f64180aa3 4,174 bytes
CarbonDataLogger 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 4,436 bytes
CarbonTrading 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 4,980 bytes

Table 1c. Gas Costs per Operation (Measured on DustBoy L2)

Operation Gas Used Rule
Deployment
Deploy CarbonCreditRegistry 978,346
Deploy CarbonDataLogger 1,042,997
Deploy CarbonTrading 1,171,220
Contract wiring (setTradingContract) 46,107
Contract wiring (setAuthorizedReporter) 46,391
Set sector cap (per sector) 47,615 R2
Total deployment + setup 3,523,136
Trading Operations
Register participant 121,108–123,908 R1
Verify via Merkle root (admin) 55,347–72,459 R1
Mint carbon credits 55,043 R1
Create sell offer (with escrow) 202,732 R3, R4
Buy credits (match + trade log) 225,862 R4, R5, R10
Retire/burn credits 32,430 R6
Log emission data 209,959 R10

At the L2 gas price of 1 Gwei (0.001 Gwei base fee + priority), the most expensive trading operation (buy credits with matching) costs approximately 225,862 gas × 1 Gwei = 0.000226 ETH. At typical ETH/THB rates, this represents a negligible transaction cost compared to the value of carbon credits being traded, supporting the feasibility of Rule R8 (gasless participation via paymaster).

3.3. Proposed Algorithm: Carbon Credit Cap and Trade

Algorithm 1 presents the proposed rules for carbon credit trading on JibChain. The algorithm extends the energy cap-and-trade mechanism of Aoun et al. [3] with carbon-specific features: TGO verification, sector-based caps, floor pricing, and credit retirement.


Algorithm 1: Carbon Credit Cap and Trade for Thailand

INPUT:
  P = set of registered participants
  K_s = carbon cap for sector s (tonnes CO2e per quarter)
  gamma_floor = minimum carbon credit price (THB/tonne)
  R(m, n) = offer matrix (m = [price, quantity], n = sellers)

1:  Initiate algorithm at period t (quarterly)
2:  If t = end of trading period then
3:      For each participant p in P:
4:          Calculate actual emissions E_actual(p)
5:          If E_actual(p) > K_s then
6:              surplus_charge(p) = gamma_surcharge * (E_actual(p) - K_s - credits_purchased(p))
7:          Else
8:              credits_earned(p) = K_s - E_actual(p)
9:          End if
10:     End for
11:     R(m, n) = 0          // Reset offer matrix
12:     t = 0
13:     Goto 1
14: End if

    --- SELLER REGISTRATION ---
15: If participant role = Seller then
16:     Verify TGO certification via Merkle proof
17:     Verify project additionality (CCP #5)
18:     Seller sets:
19:         gamma_i^S = asking price per credit (THB/tonne)
20:         C_i^S = carbon credits available (tonnes CO2e)
21:     Require: gamma_i^S >= gamma_floor
22:     Store offer: R(1, i) = gamma_i^S, R(2, i) = C_i^S
23:     Sort R by ascending gamma_i^S (lowest price first)
24:     i = i + 1

    --- BUYER REGISTRATION ---
25: Else (participant role = Buyer)
26:     Verify buyer registration and TGO compliance
27:     Buyer sets:
28:         gamma_B = maximum willingness to pay (THB/tonne)
29:         C_B = carbon credits needed (tonnes CO2e)
30:     Initialize j = 1
31:     While j <= i do        // Iterate through sellers
32:         If R(1, j) <= gamma_B then         // Price acceptable
33:             If R(2, j) >= C_B then          // Sufficient credits
34:                 Execute trade: buy C_B credits at R(1, j)
35:                 R(2, j) = R(2, j) - C_B    // Update seller inventory
36:                 Log transaction on CarbonDataLogger
37:                 Transfer payment via smart contract
38:                 Break
39:             Else                             // Partial fill
40:                 Execute trade: buy R(2, j) credits at R(1, j)
41:                 C_B = C_B - R(2, j)         // Reduce buyer demand
42:                 R(2, j) = 0                  // Seller exhausted
43:                 j = j + 1
44:             End if
45:         Else
46:             j = j + 1                        // Skip expensive seller
47:         End if
48:     End while
49: End if

    --- CREDIT RETIREMENT ---
50: When credit is used for offset:
51:     Burn token on-chain (irreversible)
52:     Record retirement in CarbonDataLogger
53:     Emit RetirementEvent(projectId, tonnes, retiredBy, timestamp)
54:     Update TGO registry via oracle bridge
55: End retirement

56: t = t + 1
57: Goto 2

3.4. Key Design Rules

Table 2. Proposed Rules for Carbon Credit Trading on JibChain

Rule Description Rationale
R1: TGO Verification Required All sellers must hold valid TGO certification verified on-chain via Merkle proof Prevents fraudulent credit creation; aligns with ICVCM CCP #4 (third-party validation) [5]
R2: Sector-Based Cap Allocation Caps set per sector (energy, industry, transport, agriculture) using benchmarking method Reflects Thailand's NDC commitments; adapts Aoun et al. [3] percentile-based cap strategy
R3: Floor Price Enforcement Minimum credit price enforced at smart contract level Prevents race-to-bottom pricing; maintains market integrity per VCMI guidelines
R4: Sorted Offer Matching Offers sorted by ascending price; buyers matched to cheapest available Maximizes buyer utility while ensuring fair seller access (Algorithm 1, lines 23, 31-48)
R5: Partial Fill Allowed Large buy orders can span multiple sellers Improves liquidity; follows Aoun et al. [3] multi-seller matching pattern
R6: Irreversible Retirement Used credits are burned on-chain with permanent audit trail Prevents double counting per ICVCM CCP #8; addresses Verra/Toucan controversy [5]
R7: Quarterly Settlement Trading periods aligned with TGO reporting quarters Balances market activity frequency with regulatory reporting cycles
R8: Gasless Participation via Paymaster ERC-4337 paymaster sponsors gas fees for verified participants Removes barrier for Thai SMEs; enables participation without cryptocurrency holdings
R9: L1-L2 Bridge Settlement Final settlement on JibChain L1; high-frequency trading on L2 Inherits L1 security while enabling scalable operations
R10: On-Chain Data Logging All transactions, emissions data, and retirements recorded immutably Provides transparent audit trail for TGO and international compliance

3.5. Gas Abstraction for Inclusive Participation

A significant barrier to blockchain adoption in developing carbon markets is the requirement for participants to hold cryptocurrency for transaction fees. The proposed system addresses this through an ERC-4337 Account Abstraction paymaster deployed on the L2 chain.

The paymaster flow operates as follows:

  1. Participant constructs a UserOperation (trading request) without holding ETH
  2. The UserOperation is submitted to a bundler service
  3. The bundler calls the EntryPoint contract, which validates the operation
  4. The DSTPaymaster contract verifies the participant's TGO registration and approves gas sponsorship
  5. The trading transaction executes on-chain
  6. The paymaster's ETH deposit at the EntryPoint covers the gas cost

This pattern enables Thai organizations, particularly SMEs and community-based projects, to participate in carbon trading without the technical complexity of managing cryptocurrency wallets and gas tokens.

3.6. Carbon Credit Billing Model

Adapting the billing equation from Aoun et al. [3] (Equation 6), the quarterly carbon cost for organization i under the proposed cap-and-trade system is:

TC_i = gamma_base * min(E_i, K_s)
     + gamma_surcharge * max(0, E_i - K_s - CB_i) * a_i
     - gamma_sell * CS_i
     + gamma_buy * CB_i

Where:

  • TC_i = Total carbon cost for organization i (THB)
  • gamma_base = Base carbon tax rate (THB/tonne CO2e)
  • E_i = Actual emissions for organization i (tonnes CO2e)
  • K_s = Sector cap allocation (tonnes CO2e per quarter)
  • CB_i = Carbon credits bought by organization i
  • CS_i = Carbon credits sold by organization i
  • gamma_surcharge = Penalty rate for uncovered excess emissions
  • gamma_sell = Revenue rate for sold credits
  • gamma_buy = Purchase cost of bought credits
  • a_i = 1 if (E_i - K_s) > CB_i (insufficient credits to cover overage), else 0

Table 3. Proposed Cap Strategies for Thai Carbon Market

Strategy Cap Percentile Surcharge Rate Floor Credit Price Target Sector
Aggressive 15th percentile 500 THB/tonne 200 THB/tonne Power generation
Moderate 30th percentile 300 THB/tonne 150 THB/tonne Industrial manufacturing
Soft 45th percentile 150 THB/tonne 100 THB/tonne SMEs, agriculture

The tiered approach allows Thailand to progressively tighten caps while maintaining economic feasibility for smaller emitters, consistent with the phased approach recommended for developing economies.

3.7. Integration with Environmental Monitoring Infrastructure

The proposed system connects to Thailand's existing environmental monitoring infrastructure through oracle bridges. IoT sensor networks (such as the DustBoy PM2.5 network with 1,148+ stations) can serve as independent verification sources for environmental impact claims.

The data flow from sensor to blockchain follows this path:

IoT Sensors (PM2.5, CO2, temperature)
    |
    v
MQTT Broker (real-time data ingestion)
    |
    v
Data Forwarder (batch processing, quality scoring)
    |
    v
CarbonDataLogger Contract (immutable on-chain record)
    |
    v
CarbonCreditRegistry (verification input for credit minting)

Sensor data quality is assessed using a multi-factor confidence scoring system before acceptance for blockchain recording. This approach addresses the "weakest link" problem identified by Baiz [5], where blockchain immutability is meaningless without underlying data quality assurance.


4. Discussion

4.1. Comparison with Existing Models

The proposed rules build upon and extend two primary models from the literature:

Versus Aoun et al. [3] (Energy Cap and Trade): The original algorithm operates at the household level with monthly billing cycles for energy consumption. Our adaptation scales to organizational carbon emissions with quarterly cycles aligned to TGO reporting. The core matching algorithm (sorted offer matrix, partial fill) is preserved but enhanced with TGO verification requirements and floor price enforcement that the energy model does not require.

Versus Boumaiza [4] (P2P Carbon and Energy Trading): The three-level hierarchical framework (prosumer, microgrid trader, P2P platform) is simplified in our model to two levels (registered participant, trading platform) because Thailand's carbon market currently lacks the microgrid-level aggregation infrastructure present in European energy markets. However, the dual-token concept (energy + carbon) from Boumaiza [4] could be integrated as the Thai energy market matures.

4.2. Thailand-Specific Considerations

Several design decisions reflect Thailand's unique regulatory and infrastructure context:

  1. TGO as Central Authority: Unlike fully decentralized models, the proposed system maintains TGO as the certification authority. This hybrid approach (decentralized trading, centralized certification) is pragmatic for Thailand's regulatory environment where TGO holds legal authority over carbon credit verification.

  2. JibChain as Infrastructure: Using a Thai-developed blockchain (JibChain, Chain ID 8899) ensures data sovereignty and regulatory compliance under Thai law. The OP Stack L2 provides scalability without requiring international chain dependencies.

  3. Floor Price Mechanism: Thailand's voluntary carbon market is still developing. Without floor prices, early-stage markets risk devaluation that discourages participation. The smart contract-enforced floor ensures minimum credit value regardless of market conditions.

  4. Gasless Transactions: The ERC-4337 paymaster removes the cryptocurrency barrier. Thai SMEs and community organizations can participate using familiar web interfaces without understanding blockchain mechanics.

4.3. Compliance with ICVCM Core Carbon Principles

Table 4. Mapping of Proposed Rules to ICVCM Core Carbon Principles

CCP Principle System Implementation
CCP #1 Effective Governance TGO oversight + smart contract automation
CCP #2 Tracking On-chain CarbonDataLogger with immutable records
CCP #3 Transparency Public blockchain explorer (Blockscout)
CCP #4 Third-Party Validation Merkle proof of TGO certification (Rule R1)
CCP #5 Additionality Verified at registration (Algorithm 1, line 17)
CCP #6 Permanence Irreversible burning mechanism (Rule R6)
CCP #7 Robust Quantification Multi-factor confidence scoring from sensor data
CCP #8 No Double Counting Single-chain token lifecycle: mint → trade → retire/burn
CCP #9 Sustainable Development Sector-specific caps support Thailand's NDC targets
CCP #10 Net-Zero Contribution Progressive cap tightening (Table 3 strategies)

4.4. Gas Cost Analysis and Comparison

Table 5. Gas Cost Comparison: Proposed System vs. Aoun et al. [3]

Operation Proposed System (DustBoy L2) Aoun et al. [3] (Ethereum)
Register participant 121,108–123,908 ~50,000 (simpler struct)
Create sell offer 202,732 ~180,000 (no escrow)
Buy/match credits 225,862 ~200,000 (no data logging)
Retire/burn 32,430 N/A (no retirement)
Log environmental data 209,959 N/A (no data logger)

The proposed system incurs moderately higher gas costs per operation compared to Aoun et al. [3], primarily due to: (1) the escrow mechanism in sell offers that transfers credits to the trading contract for atomic settlement, (2) automatic trade logging to the CarbonDataLogger during buy operations, and (3) Merkle proof verification during participant certification.

However, these additional costs are justified by the security and compliance benefits they provide. The escrow pattern eliminates settlement risk (a seller cannot renege after matching), the automatic logging satisfies Rule R10 (on-chain audit trail), and the Merkle verification enables Rule R1 (TGO certification) without storing full certification data on-chain.

On the DustBoy L2, total deployment cost (3 contracts + wiring + 5 sector caps) is 3,523,136 gas. At 1 Gwei gas price, this equals 0.00352 ETH — orders of magnitude cheaper than equivalent deployment on Ethereum mainnet, where the same deployment would cost approximately 0.035 ETH at 10 Gwei or 0.352 ETH at 100 Gwei. The L2 architecture (Rule R9) thus provides a 10-100x cost reduction for deployment and ongoing operations while maintaining security guarantees through L1 batch settlement.

4.5. Limitations and Future Work

  1. Simulation-Based Validation: The proposed rules have not yet been validated through large-scale simulation. Future work will test with datasets from Thailand's existing T-VER (Thailand Voluntary Emission Reduction) program.

  2. Cross-Border Trading: The current design operates within Thailand's jurisdiction. Article 6 of the Paris Agreement enables international carbon market linkages, which would require cross-chain bridge protocols not yet addressed.

  3. Dynamic Cap Adjustment: Caps are currently set per quarter. An adaptive mechanism that adjusts caps based on real-time market conditions and emissions trends would improve responsiveness.

  4. Oracle Bridge Reliability: The connection between off-chain environmental monitoring and on-chain records depends on oracle services. The design assumes trusted data forwarders; a decentralized oracle network would strengthen this assumption.


Key References Used

  • [3] Aoun, A.; Ibrahim, H.; Ghandour, M.; Ilinca, A. Blockchain-Enabled Energy Demand Side Management Cap and Trade Model. Energies 2021, 14, 8600.
  • [4] Boumaiza, A. Carbon and Energy Trading Integration within a Blockchain-Powered Peer-to-Peer Framework. Energies 2024, 17, 2473.
  • [5] Baiz, P. Blockchain and Carbon Markets: Standards Overview. arXiv 2024, 2403.03865.

Notes for HS (Hisam)

This draft provides:

  1. Algorithm 1 — The tokenization rules you asked for (Oct 13), adapted from the Aoun paper you showed me
  2. 10 Rules Table (Table 2) — Clear, citable rules for the paper
  3. Billing Model — Adapted from Aoun Equation 6 with Thai parameters
  4. Smart Contract Design — 3-contract pattern with Solidity-style structs
  5. ICVCM Mapping (Table 4) — Shows compliance with international standards

What still needs work:

  • Real simulation data (Table 3 values are illustrative, not from real Thai market data)
  • TGO-specific regulatory citations (Thai law references)
  • Figures: System architecture diagram, trading flow diagram
  • Integration with Materials and Methods section you drafted
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment