ChainLaunch

Build a Fabric Supply Chain PoC with Claude Code and ChainLaunch

Build a Fabric Supply Chain PoC with Claude Code and ChainLaunch

David Viejo

Written by David Viejo

Hyperledger Fabric's operational complexity has long been the number-one barrier to PoC development. Before you write a single line of chaincode, you're already deep in crypto material generation, channel configuration files, anchor peer definitions, and a five-step chaincode lifecycle. Most enterprise blockchain projects stall at the infrastructure phase — never reaching actual business logic.

What if an AI assistant could handle all of that? Claude Code, paired with ChainLaunch, compresses what used to take two to three days into a single conversation. This post walks you through the full process: network creation, Go chaincode authoring, CCaaS deployment, and end-to-end ledger queries — all for a pharmaceutical supply chain tracker.

If you're new to Fabric network setup, start with our how to create a Hyperledger Fabric network guide.

TL;DR: Claude Code + ChainLaunch automates every painful part of Fabric PoC setup: org creation, CA provisioning, peer/orderer deployment, channel creation, anchor peers, and Go chaincode deployment via CCaaS. A multi-day setup becomes a single conversation.

What You'll Build

By the end of this tutorial, you'll have a fully operational Hyperledger Fabric network running a pharmaceutical supply chain tracker. Here's what that includes.

Network infrastructure:

  • Organization with its own Certificate Authority
  • Peer node joined to a shared channel
  • Three Raft orderers for crash fault tolerance
  • Anchor peers configured for cross-organization gossip

Chaincode:

  • A Go supply chain contract deployed via Chaincode-as-a-Service (CCaaS)
  • Full item lifecycle: InitLedger, CreateItem, UpdateStatus, TransferOwnership
  • Query functions: GetItem, GetHistory
  • Tracks pharmaceutical product movement from manufacturer to patient

The workflow:

  • You describe what you want to Claude Code in plain language
  • Claude Code calls ChainLaunch CLI to provision the entire network
  • Claude Code writes the Go chaincode, builds the binary, and deploys via CCaaS
  • You query the ledger in minutes

What Do You Need Before Starting?

Before you start, make sure you have:

  • ChainLaunch installedsee the quickstart guide or run curl -fsSL https://chainlaunch.dev/deploy.sh | bash
  • Claude Code CLI — install with npm install -g @anthropic-ai/claude-code
  • Go 1.21+ — needed to build the chaincode binary
  • Docker — the CCaaS chaincode server can run as a container or plain binary; Docker is needed for the container option

That's it. ChainLaunch handles all the Fabric binaries, cryptographic tooling, and networking internally.

Step 1: How Do You Create the Fabric Network?

ChainLaunch provisions a complete Fabric network — organizations, CAs, peers, orderers, and channels — from a single command. Manually bootstrapping a Fabric network requires generating crypto material with cryptogen, writing configtx.yaml, creating genesis blocks, and running a multi-step channel setup. ChainLaunch reduces all of that to one CLI call.

First, find your key provider ID (on a fresh install this is typically 1):

curl -s $CHAINLAUNCH_API_URL/key-providers \
  -u $CHAINLAUNCH_USER:$CHAINLAUNCH_PASSWORD | jq '.[0].id'

Then create the testnet:

chainlaunch testnet fabric \
  --name my-poc \
  --org "PharmaOrg" \
  --peerOrgs "PharmaOrg" \
  --ordererOrgs "OrdererOrg" \
  --peerCounts "PharmaOrg=1" \
  --ordererCounts "OrdererOrg=3" \
  --channels mychannel \
  --provider-id 1 \
  --mode docker

Replace --provider-id 1 with whatever ID the command above returned.

What happens under the hood:

  1. Organization creation — ChainLaunch creates "PharmaOrg" and "OrdererOrg" with their MSP IDs
  2. CA setup — Each org gets a Certificate Authority. ChainLaunch generates admin, peer, and orderer identities automatically
  3. Crypto material — All TLS certificates and MSP material are generated and stored
  4. Peer and orderer provisioning — 1 peer and 3 orderers start and connect to the shared network
  5. Channel creation — The mychannel application channel is created with Raft consensus
  6. Channel joining — The peer and all orderers join the channel
  7. Anchor peer setup — Anchor peers are configured for gossip discovery

The --ordererCounts "OrdererOrg=3" flag uses Raft consensus. With three orderers, your network tolerates one orderer failure — enough for a PoC you might demo under real conditions.

The --mode docker flag runs nodes as Docker containers. Use --mode service if you prefer systemd-managed services that persist across restarts.

How does this compare to doing it manually? We've timed both approaches internally. Manual Fabric bootstrap (for someone who's done it before) takes about 45 minutes to an hour. First-timers can easily spend half a day. ChainLaunch finishes in about 2 minutes.

For a comparison of deployment approaches, see our Kaleido vs ChainLaunch vs Kubernetes comparison.

Step 2: How Do You Write the Go Chaincode?

Claude Code generates production-ready Go chaincode from a plain-language description. Tell it: "create a supply chain tracker chaincode for Hyperledger Fabric that tracks pharmaceutical products." Here's the chaincode it produces.

One thing we've learned from building supply chain PoCs: the GetHistory function is the one that consistently impresses enterprise stakeholders most. Showing a full audit trail — who touched this asset, when, and what changed — lands better than any architecture diagram.

Create the chaincode directory and initialize a Go module:

mkdir -p supplychain-cc && cd supplychain-cc
go mod init supplychain-cc
go get github.com/hyperledger/fabric-chaincode-go/shim
go get github.com/hyperledger/fabric-contract-api-go/contractapi

All three code blocks below go into a single main.go file.

The Data Model

package main
 
import (
    "encoding/json"
    "fmt"
    "time"
 
    "github.com/hyperledger/fabric-contract-api-go/contractapi"
)
 
// Item represents a pharmaceutical product in the supply chain
type Item struct {
    ID        string  `json:"id"`
    Name      string  `json:"name"`
    Owner     string  `json:"owner"`
    Status    string  `json:"status"`
    Location  string  `json:"location"`
    History   []Event `json:"history"`
    CreatedAt string  `json:"createdAt"`
    UpdatedAt string  `json:"updatedAt"`
}
 
// Event captures a state change in the item's lifecycle
type Event struct {
    Action    string `json:"action"`
    Actor     string `json:"actor"`
    Location  string `json:"location"`
    Status    string `json:"status"`
    Timestamp string `json:"timestamp"`
    Notes     string `json:"notes"`
}

The Contract Methods

// SupplyChainContract provides the chaincode methods
type SupplyChainContract struct {
    contractapi.Contract
}
 
// InitLedger populates the ledger with sample pharmaceutical items
func (s *SupplyChainContract) InitLedger(
    ctx contractapi.TransactionContextInterface,
) error {
    items := []Item{
        {
            ID:       "DRUG-001",
            Name:     "Amoxicillin 500mg",
            Owner:    "PharmaCo",
            Status:   "manufactured",
            Location: "Manufacturing Plant A",
            History: []Event{{
                Action: "created", Actor: "PharmaCo",
                Location: "Manufacturing Plant A",
                Status: "manufactured",
                Timestamp: time.Now().UTC().Format(time.RFC3339),
            }},
            CreatedAt: time.Now().UTC().Format(time.RFC3339),
            UpdatedAt: time.Now().UTC().Format(time.RFC3339),
        },
        {
            ID:       "DRUG-002",
            Name:     "Metformin 850mg",
            Owner:    "PharmaCo",
            Status:   "manufactured",
            Location: "Manufacturing Plant B",
            History: []Event{{
                Action: "created", Actor: "PharmaCo",
                Location: "Manufacturing Plant B",
                Status: "manufactured",
                Timestamp: time.Now().UTC().Format(time.RFC3339),
            }},
            CreatedAt: time.Now().UTC().Format(time.RFC3339),
            UpdatedAt: time.Now().UTC().Format(time.RFC3339),
        },
    }
 
    for _, item := range items {
        itemBytes, err := json.Marshal(item)
        if err != nil {
            return fmt.Errorf("failed to marshal item %s: %w", item.ID, err)
        }
        if err := ctx.GetStub().PutState(item.ID, itemBytes); err != nil {
            return fmt.Errorf("failed to put item %s: %w", item.ID, err)
        }
    }
    return nil
}
 
// CreateItem adds a new pharmaceutical product to the ledger
func (s *SupplyChainContract) CreateItem(
    ctx contractapi.TransactionContextInterface,
    id, name, owner, location string,
) error {
    existing, err := ctx.GetStub().GetState(id)
    if err != nil {
        return fmt.Errorf("failed to read state: %w", err)
    }
    if existing != nil {
        return fmt.Errorf("item %s already exists", id)
    }
 
    now := time.Now().UTC().Format(time.RFC3339)
    item := Item{
        ID: id, Name: name, Owner: owner,
        Status: "manufactured", Location: location,
        History: []Event{{
            Action: "created", Actor: owner,
            Location: location, Status: "manufactured",
            Timestamp: now,
        }},
        CreatedAt: now, UpdatedAt: now,
    }
 
    itemBytes, err := json.Marshal(item)
    if err != nil {
        return err
    }
    return ctx.GetStub().PutState(id, itemBytes)
}
 
// UpdateStatus moves an item to a new status with location tracking
func (s *SupplyChainContract) UpdateStatus(
    ctx contractapi.TransactionContextInterface,
    id, newStatus, location, actor, notes string,
) error {
    item, err := s.getItem(ctx, id)
    if err != nil {
        return err
    }
 
    now := time.Now().UTC().Format(time.RFC3339)
    item.History = append(item.History, Event{
        Action: "status_update", Actor: actor,
        Location: location, Status: newStatus,
        Timestamp: now, Notes: notes,
    })
    item.Status = newStatus
    item.Location = location
    item.UpdatedAt = now
 
    itemBytes, err := json.Marshal(item)
    if err != nil {
        return err
    }
    return ctx.GetStub().PutState(id, itemBytes)
}
 
// TransferOwnership changes the owner of a pharmaceutical item
func (s *SupplyChainContract) TransferOwnership(
    ctx contractapi.TransactionContextInterface,
    id, newOwner, location, notes string,
) error {
    item, err := s.getItem(ctx, id)
    if err != nil {
        return err
    }
 
    now := time.Now().UTC().Format(time.RFC3339)
    item.History = append(item.History, Event{
        Action: "ownership_transfer", Actor: item.Owner,
        Location: location, Status: item.Status,
        Timestamp: now,
        Notes: fmt.Sprintf("transferred to %s. %s", newOwner, notes),
    })
    item.Owner = newOwner
    item.Location = location
    item.UpdatedAt = now
 
    itemBytes, err := json.Marshal(item)
    if err != nil {
        return err
    }
    return ctx.GetStub().PutState(id, itemBytes)
}
 
// GetItem retrieves a single item by ID
func (s *SupplyChainContract) GetItem(
    ctx contractapi.TransactionContextInterface, id string,
) (*Item, error) {
    return s.getItem(ctx, id)
}
 
// GetHistory returns the full audit trail for an item
func (s *SupplyChainContract) GetHistory(
    ctx contractapi.TransactionContextInterface, id string,
) ([]Event, error) {
    item, err := s.getItem(ctx, id)
    if err != nil {
        return nil, err
    }
    return item.History, nil
}
 
func (s *SupplyChainContract) getItem(
    ctx contractapi.TransactionContextInterface, id string,
) (*Item, error) {
    itemBytes, err := ctx.GetStub().GetState(id)
    if err != nil {
        return nil, fmt.Errorf("failed to read state for %s: %w", id, err)
    }
    if itemBytes == nil {
        return nil, fmt.Errorf("item %s does not exist", id)
    }
    var item Item
    if err := json.Unmarshal(itemBytes, &item); err != nil {
        return nil, err
    }
    return &item, nil
}

The CCaaS Entry Point

package main
 
import (
    "log"
    "os"
 
    "github.com/hyperledger/fabric-chaincode-go/shim"
    "github.com/hyperledger/fabric-contract-api-go/contractapi"
)
 
func main() {
    contract := new(SupplyChainContract)
    cc, err := contractapi.NewChaincode(contract)
    if err != nil {
        log.Fatalf("failed to create chaincode: %v", err)
    }
 
    server := &shim.ChaincodeServer{
        CCID:    os.Getenv("CHAINCODE_ID"),
        Address: os.Getenv("CHAINCODE_ADDRESS"),
        CC:      cc,
        TLSProps: shim.TLSProperties{Disabled: true},
    }
 
    if err := server.Start(); err != nil {
        log.Fatalf("failed to start chaincode server: %v", err)
    }
}

The shim.ChaincodeServer is what makes this CCaaS-compatible. Instead of the peer managing the chaincode process inside a Docker container, your chaincode runs as an independent gRPC server. The peer dials into it. This means you can restart, rebuild, and debug chaincode without ever touching the peer — a massive time saver during PoC iteration.

For more on Fabric chaincode patterns, see our Fabric chaincode tutorial.

Step 3: How Do You Deploy via CCaaS?

Chaincode-as-a-Service (CCaaS) is the recommended deployment model for Fabric 2.4+. It decouples chaincode execution from peer container lifecycle, so you can restart or update your chaincode without touching the peer.

Pull the Network Config

First, pull the network connection profile. This YAML file contains peer endpoints, orderer addresses, TLS certificates, and admin credentials — everything the SDK needs to interact with your network.

chainlaunch fabric network-config pull \
  --msp-id PharmaOrg \
  --network my-poc \
  --url $CHAINLAUNCH_API_URL \
  -u $CHAINLAUNCH_USER \
  -p $CHAINLAUNCH_PASSWORD \
  -f conn.yaml

Build the Chaincode

cd supplychain-cc
go build -o supply-chain-server .

Install and Approve the Chaincode

The chainlaunch fabric install command runs the full Fabric 2.x lifecycle — package, install, approve, and commit — in one shot. The --chaincodeAddress tells the peer where to find your chaincode server (use host.docker.internal if your peer runs in Docker and the chaincode runs on the host):

chainlaunch fabric install \
  --config conn.yaml \
  --chaincode supply-chain \
  --channel mychannel \
  --chaincodeAddress "host.docker.internal:9999" \
  --local \
  -o PharmaOrg \
  -u admin \
  --policy "OR('PharmaOrg.member')"

ChainLaunch handles all five lifecycle steps under this single command:

  1. Packages the chaincode with connection metadata for CCaaS
  2. Installs the package on all peers in the specified organizations
  3. Queries the installed package ID
  4. Approves the chaincode definition for each organization
  5. Commits the definition to the channel

That's the five-step lifecycle from the Fabric documentation, compressed into one command. Doing these steps manually requires running 5 separate CLI commands with carefully matched package IDs, sequence numbers, and endorsement policies. Getting any of those wrong means starting the sequence over.

Start the Chaincode Server

The install command outputs the chaincode package ID. Use it to start the server:

CHAINCODE_ID="supply-chain:<hash-from-install-output>" \
CHAINCODE_ADDRESS=0.0.0.0:9999 \
./supply-chain-server

The server starts listening on port 9999. Once it's running, the peer connects and starts routing transactions to your chaincode.

During internal testing of ChainLaunch's CCaaS install flow, the biggest time savings came from automatic package ID propagation between the install, approve, and commit stages. Manually, you'd copy-paste a long hash between commands — one typo and you start over.

Step 4: How Do You Initialize and Query the Ledger?

With the chaincode committed and the server running, you're ready to run the full supply chain lifecycle. This is where the PoC becomes a real demonstration — you can walk stakeholders through pharmaceutical product tracking from manufacture to dispensation.

Initialize with Sample Data

chainlaunch fabric invoke \
  --config conn.yaml \
  --channel mychannel \
  --chaincode supply-chain \
  --mspID PharmaOrg \
  --user admin \
  --fcn InitLedger

This populates the ledger with sample pharmaceutical items (Amoxicillin and Metformin from the InitLedger function above).

Create a New Product

chainlaunch fabric invoke \
  --config conn.yaml \
  --channel mychannel \
  --chaincode supply-chain \
  --mspID PharmaOrg \
  --user admin \
  --fcn CreateItem \
  -a "DRUG-003" -a "Ibuprofen 200mg" -a "PharmaCo" -a "Frankfurt Facility"

Track the Product Through the Supply Chain

# Item leaves the manufacturer
chainlaunch fabric invoke \
  --config conn.yaml \
  --channel mychannel \
  --chaincode supply-chain \
  --mspID PharmaOrg \
  --user admin \
  --fcn UpdateStatus \
  -a "DRUG-003" -a "in-transit" -a "DHL Hub Frankfurt" -a "PharmaCo" -a "Batch QC passed"
 
# Transfer to distributor
chainlaunch fabric invoke \
  --config conn.yaml \
  --channel mychannel \
  --chaincode supply-chain \
  --mspID PharmaOrg \
  --user admin \
  --fcn TransferOwnership \
  -a "DRUG-003" -a "EuroMed Distribution" -a "Hamburg Warehouse" -a "Quarterly transfer"
 
# Delivered to pharmacy
chainlaunch fabric invoke \
  --config conn.yaml \
  --channel mychannel \
  --chaincode supply-chain \
  --mspID PharmaOrg \
  --user admin \
  --fcn UpdateStatus \
  -a "DRUG-003" -a "delivered" -a "City Pharmacy Berlin" -a "EuroMed Distribution" -a "Cold chain maintained"

Query Current State and History

# Get current item state
chainlaunch fabric query \
  --config conn.yaml \
  --channel mychannel \
  --chaincode supply-chain \
  --mspID PharmaOrg \
  --user admin \
  --fcn GetItem \
  -a "DRUG-003"
 
# Get the complete audit trail
chainlaunch fabric query \
  --config conn.yaml \
  --channel mychannel \
  --chaincode supply-chain \
  --mspID PharmaOrg \
  --user admin \
  --fcn GetHistory \
  -a "DRUG-003"

The GetHistory output shows every state transition: who made it, when, and where the product was. That's the audit trail that enterprise stakeholders — compliance teams, auditors, regulators — actually care about. It's the feature that turns a technical demo into a business conversation.

How Does Claude Code Orchestrate All of This?

The real productivity multiplier isn't any single tool — it's Claude Code chaining all of them together. Here's what the actual conversation looks like.

You open Claude Code and say:

"Create a Hyperledger Fabric network for a pharmaceutical supply chain PoC. I need one peer, three orderers. Then write a Go chaincode that tracks drug products through manufacturing, transit, and delivery — with full ownership transfer and history. Deploy it via CCaaS."

Claude Code then:

  1. Calls chainlaunch testnet fabric to provision the network
  2. Writes the Go chaincode — the full contract you saw above, including the CCaaS entry point
  3. Pulls the network config and creates go.mod with the correct dependency versions
  4. Runs chainlaunch fabric install to package, install, approve, and commit
  5. Builds the binary and starts the CCaaS server with the right environment variables
  6. Runs InitLedger and a sample query to verify everything works

Is this magic? No. Claude Code understands both the ChainLaunch CLI and Fabric's chaincode model. The key is that ChainLaunch exposes a clean, scriptable CLI — which makes it straightforward for an AI assistant to orchestrate.

The combination of a clean CLI and an AI code agent creates something neither tool delivers alone. ChainLaunch without Claude Code still requires you to know the right commands and flags. Claude Code without ChainLaunch means writing your own Fabric provisioning scripts from scratch. Together, they cover both infrastructure complexity and code generation in one workflow.

Should You Choose Fabric or Besu for Your PoC?

Your choice of blockchain protocol should match your use case — not your team's familiarity. Both Fabric and Besu are production-grade, and ChainLaunch supports both.

Choose Fabric when your use case requires:

  • Private data collections — channel-scoped data invisible to non-members
  • Fine-grained endorsement policies — rules like "3 of 5 orgs must sign"
  • Go, Node.js, or Java chaincode — mature SDKs for all three
  • Permissioned network with distinct org identities

Choose Besu when:

  • Your team already knows Solidity
  • You want EVM compatibility (Hardhat, Foundry, MetaMask work out of the box)
  • You need fast deployment cycles — eth_sendTransaction is simpler than Fabric's 5-step lifecycle
  • You're building a public/permissioned hybrid

For supply chain specifically, Fabric's private data collections and org-based endorsement policies make it a stronger fit for multi-party enterprise scenarios. But if your PoC doesn't need private data, Besu's simpler contract deployment model gets you to a demo faster.

For the full comparison, read our Hyperledger Fabric vs Besu guide or our which blockchain for supply chain analysis.

Frequently Asked Questions

What is Chaincode-as-a-Service (CCaaS)?

CCaaS is Fabric's external chaincode execution model, available since Fabric 2.4. Instead of the peer managing the chaincode process inside a Docker container, your chaincode runs as an independent gRPC server that the peer dials into. This makes iteration faster — you can restart the chaincode without touching the peer — and debugging easier since you can attach a standard Go debugger. See our chaincode tutorial for more on CCaaS patterns.

Can I add more organizations to the network after it's created?

Yes. ChainLaunch supports dynamic org addition to an existing channel. You'd generate the new org's MSP material, create a channel config update transaction, and get signatures from existing channel members. ChainLaunch's CLI handles each step. The process follows the standard Fabric channel update flow, so existing tooling works alongside ChainLaunch.

How does ChainLaunch compare to Fabric's test-network scripts?

Fabric's test-network is a shell script that bootstraps a minimal network for local development. It's not designed for persistence, multi-org management, or anything resembling production. ChainLaunch manages the full lifecycle: network creation, org management, channel operations, chaincode lifecycle, node monitoring, and key management. It persists state across restarts and exposes a REST API and Terraform provider for infrastructure-as-code. Think of test-network as a learning tool and ChainLaunch as the platform you'd actually run.

Does Claude Code work with other blockchain platforms?

Claude Code is a general-purpose AI coding assistant — it works with any CLI or SDK. For ChainLaunch specifically, Claude Code can provision and manage both Fabric and Besu networks. The quality of the AI-assisted workflow depends on how scriptable the underlying platform is. ChainLaunch's clean CLI design is what makes it pair well with Claude Code.

Go 1.21 or later is recommended for fabric-contract-api-go v1.2.x and fabric-chaincode-go. The CCaaS shim.ChaincodeServer API is stable as of Fabric 2.4. If you're on an older Fabric version (2.2 LTS), the chaincode API is compatible but CCaaS support is limited — upgrade to 2.4+ for the best experience.

What Should You Do Next?

Fabric's reputation for operational complexity is well-earned — but it doesn't have to block your PoC. ChainLaunch and Claude Code remove the biggest friction points: network bootstrap, crypto material management, and the five-step chaincode lifecycle.

What you've built here — a pharmaceutical supply chain tracker with full audit history, ownership transfers, and status tracking — is a PoC that enterprise stakeholders can actually evaluate. It's not a hello-world chaincode. It's a working demonstration of Fabric's core value: tamper-evident, multi-party, permissioned record-keeping.

Key takeaways:

  • CCaaS is the modern Fabric deployment model. Faster iteration, easier debugging than container-managed chaincode.
  • ChainLaunch's testnet fabric command handles everything from org creation to anchor peer setup. You don't need to learn configtxgen.
  • Claude Code works best as an orchestrator. Give it a high-level goal and let it call the right CLI commands in order.
  • GetHistory wins demos. A full audit trail resonates with compliance-focused enterprise buyers more than any other feature.

Ready to try it? Install ChainLaunch and run your first chainlaunch testnet fabric command. For enterprise features like RBAC, SSO, and automated backups, check out ChainLaunch Pro. If you'd rather start with Besu, read our companion post on building a Besu supply chain PoC.


David Viejo is the founder of ChainLaunch and a Hyperledger Foundation contributor. He created the Bevel Operator Fabric project and has been building blockchain infrastructure tooling since 2020.

Related Articles

Ready to Transform Your Blockchain Workflow?

Deploy Fabric & Besu in minutes, not weeks. AI-powered chaincode, real-time monitoring, and enterprise security with Vault.

ChainLaunch Pro: $60,000/year   Includes premium support, unlimited networks, advanced AI tools, and priority updates.

Questions? Contact us at support@chainlaunch.dev