Development of Blockchain Supply Tracing System
Most existing supply tracking systems are just databases with web interfaces. Their problem is not technology—it is trust architecture: one party records data, others must trust them. When supply chain involves five parties from three different countries—this model doesn't work. Blockchain solves the real problem: immutability of records and verifiability without central arbiter.
But before designing, honest question: do you need public blockchain or permissioned network suffices? For corporate supply chains with known circle of participants—Hyperledger Fabric or Besu in IBFT-mode in most cases is more right than public Ethereum. For cases where public verifiability matters (consumer scans QR and sees product history on blockchain)—public L2 or Polygon.
Data Architecture: What to Store On-Chain, What Off-Chain
Main mistake of beginning projects—try storing everything on-chain. Result: expensive, slow, excessive. Rule:
On-chain stored: document hash/event, actor identifier (address), timestamp, status (enum), merkle root of batch data.
Off-chain stored: photos, PDF certificates, detailed sensor readings, large JSON objects. Link to storage (IPFS CID or URL) + content hash recorded on-chain.
// Tracking event: light on-chain, details in IPFS
struct TrackingEvent {
bytes32 batchId; // ID of batch/lot
bytes32 dataHash; // keccak256 of full JSON event
string ipfsCid; // CID of full data in IPFS
address actor; // who records (verified participant)
EventType eventType; // PRODUCED, SHIPPED, RECEIVED, INSPECTED, SOLD
uint256 timestamp;
bytes32 locationHash; // hash of GPS coordinates (privacy)
}
enum EventType { PRODUCED, SHIPPED, RECEIVED, INSPECTED, CERTIFIED, SOLD }
mapping(bytes32 => TrackingEvent[]) public batchHistory;
mapping(bytes32 => bool) public authorizedActors;
event BatchEvent(
bytes32 indexed batchId,
EventType indexed eventType,
address indexed actor,
bytes32 dataHash,
string ipfsCid
);
function recordEvent(
bytes32 batchId,
bytes32 dataHash,
string calldata ipfsCid,
EventType eventType
) external {
require(authorizedActors[keccak256(abi.encode(msg.sender, eventType))],
"Not authorized for this event type");
TrackingEvent memory evt = TrackingEvent({
batchId: batchId,
dataHash: dataHash,
ipfsCid: ipfsCid,
actor: msg.sender,
eventType: eventType,
timestamp: block.timestamp,
locationHash: bytes32(0)
});
batchHistory[batchId].push(evt);
emit BatchEvent(batchId, eventType, msg.sender, dataHash, ipfsCid);
}
Identity and Participant Authorization
Supply chain has several types of actors with different rights: producer, logistician, customs, retailer, inspector. Simple Ownable doesn't fit—need role-based system with delegation.
Participant Verification via DID
Decentralized Identifiers (DID) is W3C standard for decentralized identity. Each participant has DID tied to their smart contract addresses. Verification (KYB—Know Your Business) happens off-chain via accredited verifiers who issue Verifiable Credentials (VC).
// VC verification when registering participant
import { Resolver } from 'did-resolver'
import { getResolver as ethrResolver } from 'ethr-did-resolver'
import { verifyCredential } from 'did-jwt-vc'
async function verifyParticipantCredential(
vcJwt: string,
participantAddress: string
): Promise<boolean> {
const resolver = new Resolver({
...ethrResolver({ infuraProjectId: process.env.INFURA_ID })
})
const result = await verifyCredential(vcJwt, resolver)
// Check that VC is issued by accredited verifier
const trustedIssuers = await getTrustedIssuers() // from smart contract
if (!trustedIssuers.includes(result.issuer)) {
return false
}
// Check that VC refers to this address
return result.verifiableCredential.credentialSubject.ethereumAddress
.toLowerCase() === participantAddress.toLowerCase()
}
Role-Based Access with Time Windows
Participant can have right to record events only during specific period (time goods in transit):
struct ActorPermission {
bytes32 role; // PRODUCER_ROLE, SHIPPER_ROLE, etc.
uint256 validFrom;
uint256 validUntil;
bytes32[] allowedBatches; // empty array = all batches
}
mapping(address => ActorPermission[]) public permissions;
function isAuthorized(
address actor,
bytes32 role,
bytes32 batchId
) public view returns (bool) {
ActorPermission[] storage perms = permissions[actor];
for (uint i = 0; i < perms.length; i++) {
if (perms[i].role == role &&
perms[i].validFrom <= block.timestamp &&
perms[i].validUntil >= block.timestamp) {
if (perms[i].allowedBatches.length == 0) return true;
for (uint j = 0; j < perms[i].allowedBatches.length; j++) {
if (perms[i].allowedBatches[j] == batchId) return true;
}
}
}
return false;
}
IoT Integration: Linking Physical and Blockchain Worlds
Sensor data must reach on-chain automatically and immutably. This is architectural problem: IoT device cannot sign Ethereum transactions directly (insufficient RAM, battery for EVM-class crypto).
Pattern: Gateway + Oracle
[IoT Sensor] → [Edge Gateway] → [Oracle Service] → [Smart Contract]
↓
[IPFS / S3] ← full sensor readings
Edge Gateway (Raspberry Pi, industrial PC):
- Signs sensor data with its key
- Aggregates readings per period (e.g., every 5 minutes)
- Publishes aggregate to IPFS
- Sends hash + CID to oracle service
Oracle Service (off-chain backend):
- Verifies gateway signature
- Checks data for anomalies (outlier detection)
- Initiates contract transaction
# Oracle service: verification and sensor event recording
from web3 import Web3
from eth_account import Account
import ipfshttpclient
async def process_sensor_reading(gateway_id: str, payload: dict, signature: str):
# 1. Verify gateway signature
message = encode_defunct(text=json.dumps(payload, sort_keys=True))
recovered = w3.eth.account.recover_message(message, signature=signature)
gateway_address = await get_registered_gateway(gateway_id)
if recovered.lower() != gateway_address.lower():
raise ValueError("Invalid gateway signature")
# 2. Publish to IPFS
async with ipfshttpclient.connect() as ipfs:
cid = ipfs.add_json(payload)
# 3. Record on-chain
data_hash = Web3.keccak(text=json.dumps(payload, sort_keys=True))
tx = tracking_contract.functions.recordSensorEvent(
payload['batch_id'].encode(),
data_hash,
cid,
EventType.SENSOR_READING
).build_transaction({
'from': oracle_account.address,
'nonce': w3.eth.get_transaction_count(oracle_account.address),
'maxFeePerGas': await get_gas_price(),
})
signed = oracle_account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
return tx_hash.hex()
Tamper-Evident Hardware
For high trust requirements—Hardware Security Modules (HSM) or Trusted Execution Environment (TEE) right in device. Microchip ATECC608 is inexpensive chip with ECC key pair you cannot extract, stored in secure element. Device signs data with key physically protected from compromise.
Real Scenario Implementation: Pharma Supply Chain
Consider pharmaceutical supply chain (FDA DSCSA compliance requires electronic tracing):
Event 1: Production
- Producer records: series ID, manufacture date, composition, hash of CoA (Certificate of Analysis)
- QR code generated with batchId
Event 2: Shipment
- Logistician scans QR, records: carrier ID, tracking number, temperature range (for pharma critical)
- GPS tracker starts writing temperature/humidity readings every 10 minutes
Event 3: Customs Clearance
- Customs agent records: declaration number, status (cleared/held), inspector ID
Event 4: Receiving
- Recipient records: date, physical inspection (OK/damaged), quantity variance
- Verification: compare hash in blockchain with hash of downloaded IPFS document
Event 5: Consumer Sale
- Consumer scans QR → sees full batch history
// Consumer product verification (frontend)
async function verifyProduct(batchId: string): Promise<ProductHistory> {
const history = await trackingContract.getBatchHistory(batchId)
const verified = await Promise.all(history.map(async (event) => {
// Download data from IPFS
const ipfsData = await fetchFromIPFS(event.ipfsCid)
// Verify hash
const computedHash = ethers.keccak256(
ethers.toUtf8Bytes(JSON.stringify(ipfsData))
)
return {
...event,
ipfsData,
dataIntegrity: computedHash === event.dataHash,
actorName: await getActorName(event.actor), // from registry
}
}))
return verified
}
Network Choice
| Parameter | Public L2 (Polygon/Base) | Hyperledger Fabric | Besu (IBFT) |
|---|---|---|---|
| Public verifiability | Yes | No | No |
| Write cost | ~$0.001–$0.01/tx | Nearly 0 | Nearly 0 |
| Settlement speed | 2–5 sec | < 1 sec | 2–5 sec |
| Access control | Smart contracts | Native channel/MSP | Smart contracts |
| Regulatory requirements | Public blockchain | Private network | Private network |
For B2C scenarios (consumer sees history)—public L2. For B2B (only chain participants)—permissioned.
Development Phases
Phase 1 — Design (2–3 weeks): analyze business processes, define events, participants, access rights. Data model on/off-chain.
Phase 2 — Smart Contracts (3–4 weeks): tracking contracts, role system, tests.
Phase 3 — Oracle + IoT (3–4 weeks): gateway integration, oracle service, IPFS pipeline.
Phase 4 — API & Dashboard (3–4 weeks): REST/GraphQL API, admin panel, consumer-facing verifier.
Phase 5 — Integration & Pilot (2–4 weeks): ERP/WMS integration of participants, pilot with real data.
Total: 13–19 weeks. Most labor-intensive phase—integrating with legacy ERP systems of participants, not blockchain development.







