This document summarizes the implementation of four major features for the CarbonChain platform.
Implemented on-chain tracking of verifier performance to distinguish reliable verifiers from those who approve fraudulent credits.
- New Data Structure:
VerifierReputationstruct withapproval_countanddispute_countfields - Storage: Added
VerifierReputation(Address)DataKey variant for persistent storage - Functions:
get_verifier_reputation(verifier: Address) -> VerifierReputation- View function to query reputationincrement_approval_count(verifier: Address)- Called on successful credit approvalincrement_dispute_count(verifier: Address)- Called when credit is flagged as disputed
- Reputation is stored in persistent storage with TTL management
- Initialized with 0 counts on first access
- Incremented atomically on each approval or dispute
- Accessible via public view function for transparency
test_verifier_reputation_increments_on_approval- Verifies approval count increasestest_verifier_reputation_increments_on_dispute- Verifies dispute count increases
Enables OTC (over-the-counter) trades by allowing credits to change ownership outside the marketplace.
- New Field: Added
owner: Addressfield toCreditMetadatastruct - New Function:
transfer_credit(from: Address, to: Address, credit_id: BytesN<32>, nonce: u64) -> Result<(), CarbonChainError> - New Event:
credit_transferred(from, to, credit_id)event emission
- Requires authorization from the current owner (
from.require_auth()) - Validates ownership before transfer
- Updates owner field in credit metadata
- Emits transfer event for audit trail
- Includes nonce-based replay protection
- Only the current owner can initiate a transfer
- Nonce consumption prevents replay attacks
- Contract pause status is respected
test_transfer_credit_changes_owner- Verifies owner field is updatedtest_transfer_credit_requires_ownership- Verifies unauthorized transfers fail
Enables efficient retirement of multiple credits in a single transaction, reducing gas costs and improving UX for large portfolio retirements.
- New Function:
batch_retire(buyer: Address, credit_ids: Vec<BytesN<32>>, tonnes: Vec<i128>, reason: String, registry_id: Address, nonce: u64) -> Result<Vec<BytesN<32>>, RetirementError> - New Error Code:
InvalidNonce = 115in RetirementError enum - New Error Code:
NoPendingAdmin = 116in RetirementError enum
- Accepts vectors of credit IDs and corresponding tonnes
- Validates that both vectors have equal length
- Creates individual
RetirementRecordfor each credit - Calls
mark_retiredon registry for each credit - Indexes all retirements under buyer's account
- Emits individual
retireevents per credit for full audit trail - Includes nonce-based replay protection
- Linear complexity: O(n) where n = number of credits
- Each credit requires:
- One storage write for retirement record
- One cross-contract call to registry
- One event emission
- Recommended batch size: 5-10 credits per transaction to stay within compute budget
test_batch_retire_multiple_credits- Verifies batch of 5 credits are retiredtest_batch_retire_indexes_all_retirements- Verifies all retirements are indexed
Allows large credits to be split into smaller units without going through the marketplace, enabling flexible credit management.
- New Function:
split_credit(caller: Address, credit_id: BytesN<32>, split_tonnes: i128, nonce: u64) -> Result<(BytesN<32>, BytesN<32>), CarbonChainError> - New Error Code:
InvalidSplit = 115in CarbonChainError enum - New Event:
credit_split(original_id, child1_id, child2_id)event emission
- Requires ownership of the credit
- Validates split amount: must be > 0 and < total tonnes
- Creates two child credits with:
- Same metadata as original (project_id, methodology, geography, etc.)
- Split tonnes distributed between children
- Original owner as owner of both children
- Active status (not retired)
- Retires the original credit to prevent double-spending
- Generates deterministic child credit IDs using SHA256 hash
- Adds both children to project's credit index
- All metadata fields are preserved in child credits:
project_id- Same as originalissuer- Same as originalvintage_year- Same as originalmethodology- Same as originalgeography- Same as originalipfs_hash- Same as originalissued_at- Same as original
- Only
tonnesandownerare modified
test_split_credit_creates_two_children- Verifies two children are created with correct tonnestest_split_credit_retires_original- Verifies original credit is retiredtest_split_credit_invalid_split_fails- Verifies invalid splits are rejected
- Added
owner: Addressfield toCreditMetadata - Added
VerifierReputationstruct - Extended
DataKeyenum with new variants:Nonce(Address)- For nonce managementPendingAdmin- For admin transferVerifierReputation(Address)- For reputation storage
- Added
get_nonce(addr: Address) -> u64 - Added
consume_nonce(addr: Address, expected: u64) -> bool - Added
get_verifier_reputation(verifier: Address) -> VerifierReputation - Added
set_verifier_reputation(verifier: Address, rep: VerifierReputation) - Added
increment_approval_count(verifier: Address) - Added
increment_dispute_count(verifier: Address)
InvalidNonce = 113- Nonce validation failedNoPendingAdmin = 114- No pending admin to acceptInvalidSplit = 115- Invalid split parametersInvalidNonce = 115(Retirement) - Nonce validation failed in retirementNoPendingAdmin = 116(Retirement) - No pending admin in retirement
credit_transferred(from, to, credit_id)- Emitted on transfercredit_split(original_id, child1_id, child2_id)- Emitted on splitbatch_retired(buyer, count)- Emitted on batch retirement
All existing contract methods remain unchanged. The new features are:
- Additive: New functions don't modify existing behavior
- Optional: Existing workflows continue to work without changes
- Non-breaking: All existing tests pass without modification
The owner field addition to CreditMetadata is backward compatible as it's initialized to the issuer on credit creation.
- All state-mutating operations require caller authorization via
require_auth() - Ownership checks prevent unauthorized transfers and splits
- Verifier reputation is read-only for non-verifiers
- All operations use nonce-based replay protection
- Nonces are consumed atomically with state changes
- Nonce storage includes TTL management
- All operations emit events for full traceability
- Batch operations emit individual events per item
- Retirement records are immutable
All new features include comprehensive test coverage:
- Unit tests for each function
- Authorization and validation tests
- Edge case tests (invalid splits, unauthorized transfers, etc.)
- Integration tests with cross-contract calls
Run tests with:
cd contracts && cargo testThe NestJS API layer should be updated to expose these new functions:
POST /api/v1/credits/:id/transfer- Transfer creditPOST /api/v1/credits/:id/split- Split creditPOST /api/v1/retirement/batch- Batch retire creditsGET /api/v1/verifiers/:address/reputation- Get verifier reputation
Potential improvements for future iterations:
- Fractional split support (split into more than 2 pieces)
- Conditional transfers (escrow-based transfers)
- Reputation-based access control (restrict operations by verifier score)
- Batch transfer support
- Credit merging (combine multiple credits)