CarbonCreditToken issuance, a custodial purchase flow, and retirement with a queryable public registry.
What you’ll build
- An issuance endpoint that deploys a
CarbonCreditTokenper verified project - A purchase endpoint that sells credits to a buyer (held in platform custody on their behalf)
- A retirement endpoint that burns credits and records an immutable
RetirementRecord - A public retirement registry page, backed by both direct on-chain reads and
IndexerClientfor real-time updates
Prerequisites
- Node.js 18+
- A Stellar testnet account and secret key (
S...), funded via the Stellar Friendbot - A running
@ankarachain/indexerinstance (for the real-time registry step) — see IndexerClient npm install @ankarachain/sdk express
1
Scaffold the project
src/config.ts
2
Issue credits for a verified project
src/routes.ts
This custodial model keeps the tutorial simple — buyers never need their own Stellar wallet. If you’d rather issue credits directly to each buyer’s own address, mint to
buyerAddress instead of TREASURY_ADDRESS; retirement (next step) then has to be signed by the buyer’s own wallet via stellarSigner, since retire() always burns the caller’s own balance.3
Sell and retire credits
A purchase just moves the buyer’s internal ledger balance in your database — the credits stay in platform custody on-chain until retirement:
src/routes.ts (continued)
retire() is callable by any token holder — it’s not admin-gated — and it burns amount from the caller’s own balance. Because the platform’s custody signer is calling it here, the platform must actually hold at least amountCO2e of that token on-chain; the ledger debit above is what prevents a buyer from retiring more than they’ve purchased.4
Serve a per-project retirement history
Every retirement is stored on-chain as a
RetirementRecord you can read back directly — no indexer required for this per-token view:src/routes.ts (continued)
5
Add a live, cross-project registry with IndexerClient
For a public homepage widget showing retirements across every project in real time, register a webhook instead of polling each token individually:
src/routes.ts (continued)
The exact normalized
eventType string your indexer assigns to a retirement is a property of your indexer’s own event configuration — subscribing to ["*"] and checking the event’s data shape (as above) keeps this handler correct regardless of that naming. See IndexerClient for signature verification and the full webhook payload shape.Going further
- Non-custodial retirement: let corporate buyers connect their own Stellar wallet (Freighter) and call
retire()directly withstellarSigner, so the platform never has to hold credits on their behalf — see React Hooks: Using Stellar in the browser. - Selling credits with fiat: reuse the
RampManageron-ramp flow from the Farmland Investment Platform tutorial to accept card or bank payment for credits before minting/crediting the buyer’s ledger.
Next steps
AssetRegistry
Full reference for
retire, getRetirement, getTotalRetired, getTotalRetirements.IndexerClient
Event queries, webhook registration, and signature verification in full.
Asset Templates
The full
CarbonCreditMetadata schema and RetirementRecord shape.TokenFactory
Full reference for
deployCarbonCredit.