Skip to main content
A reforestation project in the Congo Basin generates verified carbon offsets. A corporate buyer in Lagos wants to retire ten tonnes of CO₂e against its Scope 1 emissions and needs a public, tamper-proof record it can cite in a sustainability report. This tutorial builds that flow: CarbonCreditToken issuance, a custodial purchase flow, and retirement with a queryable public registry.

What you’ll build

  • An issuance endpoint that deploys a CarbonCreditToken per 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 IndexerClient for real-time updates

Prerequisites

  • Node.js 18+
  • A Stellar testnet account and secret key (S...), funded via the Stellar Friendbot
  • A running @ankarachain/indexer instance (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 with stellarSigner, 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 RampManager on-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.