Use this script as a conversational guide while walking the CMO and DevRel through the architecture, diagrams, minting process, security model, and everything in between. Every section is grounded in the actual code.
What you are showing: The first diagram (System Architecture Flowchart).
Your Script:
"Let's start by looking at how the system is wired together. We didn't just build a smart contract; we built a highly scalable, enterprise-grade hybrid architecture with four distinct layers.
On the far left, we have the Client Layer. Our Next.js frontend uses MetaMask for cryptographic identity and enforces Role-Based Access Control (RBAC) at the UI level—meaning a Transporter's wallet simply cannot see or access the Manufacturer's dashboard.
Instead of forcing the frontend to constantly read slow blockchain nodes, every action routes through our NestJS Backend Engine in the middle. The backend has two jobs: it talks to Supabase PostgreSQL for instant data reads via Prisma ORM, and it acts as a Relayer, signing and broadcasting transactions to the blockchain on behalf of users.
The magic in between is BullMQ backed by Upstash Redis. When a transaction is submitted, the backend drops it into a task queue. This means if the MST Testnet RPC is momentarily congested, our application never freezes—the queue retries with exponential backoff until the transaction goes through.
On the far right, all roads lead to the MST Testnet, where our 8 modular smart contracts live as the absolute, immutable source of truth."
What you are showing: The Unified User & System Flow diagram.
Your Script:
"Let me now walk you through the complete lifecycle of a product in our system, from the perspective of every role, and tell you exactly what happens on the frontend and the backend at each step.
Think of this as a relay race. The Manufacturer starts the race, hands the baton to the Transporter, who hands it to the Retailer. The Auditor watches the entire race from the sidelines. Each handoff is permanently recorded."
Frontend: An admin registers a corporate wallet on the Identity screen.
Behind the Hood (Backend): The backend calls IdentityRegistry.sol → verifyIdentity(). This records the company's business name, tax ID, jurisdiction, and primary role directly on-chain. This is a gating mechanism — a wallet that has not been registered here simply cannot call mintBatch(). The BatchRegistry.sol checks identityRegistry.isVerified(msg.sender) before minting anything.
"Think of it as on-chain KYC. No anonymous actors can participate in this supply chain."
Frontend: The Manufacturer logs in, navigates to the Mint Batch screen, enters:
- Product Name (e.g.,
Premium Organic Coffee Beans) - GS1 GTIN (
00012345678905) — the global product barcode standard - Origin Facility (
Bogota Highland Farms, Colombia) - Quantity & Unit (
500 kg) - Production Date & Expiry Date
- Internal Batch Number
- Handling Instructions (
Keep Refrigerated)
The form also allows document uploads (Bill of Lading, certificates).
Behind the Hood (Backend):
- The Next.js form submits a
POST /api/batchrequest. - The NestJS controller validates every field.
- The Relayer Wallet (a backend server wallet with
SUPPLIER_ROLE) signs and callsBatchRegistry.sol → mintBatch(). - On-chain, a
uint256 batchIdis auto-incremented (nextBatchId++), and aBatchDatastruct is written into thebatchesmapping withstage: BatchStage.Minted. - A
BatchMintedevent is emitted with thebatchId,gtin, andmanufactureraddress. - The backend listens for this event, extracts the
batchIdfrom the transaction receipt, and mirrors all the data into Supabase via Prisma with thetxHashas the cryptographic link. - A QR code containing the
batchIdis generated and displayed to the Manufacturer.
"The key word here is 'minting'. Just like an NFT, minting a batch creates a permanent, unique digital identity for a physical product on the blockchain. The
batchIdis the product's DNA."
Frontend: The Retailer sees the batch details and clicks Fund Escrow, entering the amount in tMST native tokens.
Behind the Hood:
- This is a direct MetaMask transaction — the user pays from their own wallet.
- The call goes to
EscrowRegistry.sol → fundEscrow(batchId, seller_address)withmsg.value > 0. - The contract verifies the batch exists in
BatchRegistry, then locks the funds in theEscrowAgreementstruct withisFunded: true. - An
EscrowFundedevent is emitted. - The backend listens and updates the Supabase
Escrowtable toFUNDED.
"The Retailer's payment is locked in a smart contract. Neither the Manufacturer nor any third party can touch it until the goods are physically delivered and the batch reaches
RetailReadystage. This is zero-trust commerce."
Frontend: The Transporter opens the QR Scanner screen (built with html5-qrcode), scans the batch QR, which decodes the batchId, and presents a Confirm Handover button with a GPS location field.
Behind the Hood:
POST /api/checkpointis sent with{ batchId, location }.- The backend computes an
erpHash— akeccak256hash of the location and timestamp — as a tamper-proof fingerprint. - The Relayer calls
SupplyChain.sol → transferCustody(batchId, location, status, erpHash). This appends a newCheckpointstruct to the batch'sjourney[]array on-chain and updatescurrentOwnerto the Transporter's address. - Data is also written to the
Checkpointtable in Supabase with thetxHash.
"Every time the goods change hands physically, a new checkpoint is permanently appended to the batch's journey on the blockchain. It is impossible to delete or modify a past checkpoint."
Frontend: The Transporter's portal receives automated temperature and humidity readings from IoT sensors.
Behind the Hood — The Smart Hashing Pattern:
POST /api/telemetrysends raw sensor data (temperature: 4.5°C,humidity: 78%).- The backend stores the raw values in Supabase for fast queries and dashboards.
- The backend then computes
keccak256(abi.encodePacked(batchId, temperature, humidity, timestamp))— this produces a unique 32-byte hash. - Only this hash is sent to
TelemetryRegistry.sol → anchorTelemetry(). - If the temperature exceeds a threshold (e.g.,
> 8°Cfor cold chain),isBreached: trueis passed, and the contract emits aComplianceBreachedevent on-chain. This can automatically trigger the batch's stage to change toDisputedinBatchRegistry.
"We store 1 MB of raw sensor data in Postgres and anchor 32 bytes on the blockchain. We get the security guarantees of the blockchain without paying for expensive on-chain storage. If anyone tampers with the Postgres data, the hash will never match the anchored value on-chain."
Frontend: The Transporter fills in Distance (km), Vehicle Type (CARGO_SHIP, TRUCK, etc.).
Behind the Hood:
POST /api/carbonis called.- The NestJS CarbonService applies DEFRA (UK Government Greenhouse Gas Protocols) emission factors:
emissionsKg = distanceKm × weightTonnes × DEFRAFactor[vehicleType]. - The Relayer calls
CarbonRegistry.sol → logEmissions(batchId, distanceKm, emissionsKg, vehicleType). - The contract appends a
CarbonLogstruct and accumulatestotalCarbonEmissions[batchId].
"Every transit leg's carbon footprint is on-chain, permanently. This is real ESG compliance data that a company can present to regulators — not self-reported, but cryptographically anchored."
Frontend: A Customs Agent role attaches compliance documents (phytosanitary certificates, Bills of Lading).
Behind the Hood:
- Documents are uploaded to IPFS, returning a CID (Content Identifier).
- The CID is submitted to
DocumentRegistry.sol → attachDocument(). The contract validatesbytes(ipfsCid).length > 0before storing. - In
SupplyChain.sol,attachCustomsDocument()auto-computeskeccak256(abi.encodePacked(docType, ipfsCid))as a hash for the state-transition event, and moves the batch stage toCustomsCleared.
Frontend: The Retailer confirms receipt of goods on their dashboard.
Behind the Hood — The Automatic Payment Trigger:
POST /api/escrow/releaseis sent.- The Relayer calls
SupplyChain.sol → receiveAtRetail()which updates the stage toRetailReady. - Inside
receiveAtRetail(), the contract automatically calls_releaseEscrow(batchId)internally — no separate transaction is needed. EscrowRegistry.sol → releaseFunds()checksbatch.stage == BatchStage.RetailReadyas a safety condition, then uses the Checks-Effects-Interactions (CEI) pattern: it setsisReleased = trueBEFORE thecall{value}()transfer, preventing reentrancy attacks.- The tMST tokens flow directly to the Manufacturer's wallet.
"The retailer confirms delivery. The smart contract automatically releases the payment. No bank, no intermediary, no 30-day payment cycle. It settles in seconds."
Frontend: An Auditor role has a read-only dashboard. They see the full batch journey: every checkpoint, every IoT reading, every document, and every carbon log.
Behind the Hood:
- The frontend queries
GET /api/audit/:batchId, which reads from Supabase for speed. - For zero-trust verification, the auditor can copy any
txHashfrom the UI and paste it directly into the MST Testnet Block Explorer to independently verify the transaction. - They can also call
BatchRegistry.sol → getBatch()orSupplyChain.sol → getBatchHistory()directly via the explorer to confirm the on-chain state without trusting our backend at all.
Your Script:
"One of the most common questions is: 'What exactly does minting mean here?'
Minting, in our context, is not about creating a token like an NFT. It is about creating a unique, permanent digital identity for a physical product batch on the blockchain.
When
mintBatch()is called, the smart contract runsuint256 batchId = nextBatchId++. This is a simple on-chain counter. The first batch ever minted getsbatchId = 1. The second gets2, and so on. This ID is globally unique and can never be reused or deleted.This
batchIdis then the key into amapping(uint256 => BatchData)on the blockchain. Every subsequent action — checkpoints, telemetry, carbon logs, escrow — is tied to thisbatchId.The physical product gets a QR code printed with this
batchId. Scanning that QR code at any point in the supply chain instantly retrieves the product's entire immutable history from the blockchain."
Your Script:
"Our project does not introduce a new token. We deliberately use tMST, the native gas and value token of the MST Testnet. This is an intentional design choice. Here is why:
No Token Risk. We are not asking companies to buy and hold a proprietary token that could lose value. The Escrow system uses the native chain currency, which is familiar and predictable.
Gas Model. Small actions — like logging a checkpoint or anchoring telemetry — consume minimal gas in tMST. The backend Relayer Wallet covers this gas cost, so end users (Transporters, IoT devices) never see a gas bill.
Escrow Value. The Retailer deposits real tMST value into the
EscrowRegistry. This is programmable money: it is locked by code, not by a bank's policy, and released automatically by code when a condition is met.Future Potential. The
CarbonRegistrytokenizes the carbon footprint of each batch. This creates a permanent, verifiable on-chain record of emissions that can form the basis of a future carbon credit trading mechanism — verified emissions data is the hardest part of any carbon market, and we already have it."
Your Script:
"Security is not an afterthought in this system. Let me walk you through every layer."
1. OpenZeppelin AccessControl (GovernanceRegistry)
- We use OpenZeppelin's battle-tested
AccessControl.solas the root of trust for the entire ecosystem. - Every role is a
keccak256hash:SUPPLIER_ROLE,LOGISTICS_ROLE,CUSTOMS_ROLE,RETAILER_ROLE, andSYSTEM_ROLE(for the backend relayer). - All 6 registries query
GovernanceRegistry.hasRole()before executing any state-changing function. A wallet with onlyLOGISTICS_ROLEcannot callmintBatch().
2. Identity Gating
BatchRegistry.sol → mintBatch()callsidentityRegistry.isVerified(msg.sender)BEFORE minting.- A wallet that has not been KYC-verified by an admin on-chain will be rejected with a revert.
- Identities can also be revoked via
revokeIdentity()if fraud is detected, immediately barring that wallet from all future actions.
3. Stage Sequencing Guards (Reentrancy & Logic)
- Every stage transition is protected by
require()statements that enforce strict ordering. - Example:
processManufacturing()requiresstage == Stage.Supplied. Calling it on a batch that is alreadyInTransitwill revert withSC_ERR: Invalid stage sequence. - You cannot skip stages. You cannot go backwards.
4. Checks-Effects-Interactions (CEI) Pattern in EscrowRegistry
- In
releaseFunds(), we setescrow.isReleased = truebefore thecall{value}()transfer. This is the standard defence against reentrancy attacks. - A malicious contract attempting to re-enter
releaseFunds()during the transfer would seeisReleased = trueand revert immediately.
5. Zero-Value Protection
depositEscrow()hasrequire(msg.value > 0). Empty deposits revert.CarbonRegistry → logEmissions()hasrequire(emissionsKg > 0). Zero-emission entries revert.BatchRegistry → getBatch()hasrequire(batchId > 0 && batchId < nextBatchId). Out-of-bounds queries revert.
6. IPFS CID Length Validation
- In
SupplyChain.sol → attachCustomsDocument():require(bytes(ipfsCid).length == 46). An IPFS v1 CID is exactly 46 characters. Submitting a malformed or empty CID is rejected at the contract level.
7. Dispute Resolution
EscrowRegistryhas amarkDisputed()function callable only by theDEFAULT_ADMIN_ROLE. If counterfeit goods or fraud is detected, an admin can freeze the escrow, preventing release, and then callrefundBuyer()to return funds.
8. Input Validation (NestJS Pipes)
- Every API endpoint uses NestJS's
ValidationPipewithclass-validatordecorators. Malformed DTOs are rejected at the HTTP level before reaching any business logic.
9. Relayer Wallet Isolation
- The backend Relayer private key is stored as an environment variable (
RELAYER_PRIVATE_KEY) and is never exposed in API responses or logs. Only the backend service has access to it.
10. Rate Limiting
- BullMQ ensures that if many requests arrive at once, they are queued and dispatched to the blockchain at a controlled rate, preventing RPC node overload and failed transactions.
11. Cryptographic Tethering
- Every Supabase record stores a
txHash. This links every database row to a blockchain transaction. Database tampering is detectable because the hash on-chain will never match a modified record.
12. Row-Level Security (RLS)
- Supabase supports PostgreSQL Row-Level Security policies, ensuring that API calls only retrieve data belonging to the authenticated organization.
Q: Why use a backend Relayer instead of having users pay gas directly?
"To eliminate all Web3 friction for enterprise users. A Transporter driving a truck should not need to understand gas fees. They just scan a QR code and press Submit. Our backend relayer wallet holds the tMST gas budget and covers all transaction costs silently. This is a standard pattern called 'Meta-Transactions' in the Web3 industry."
Q: If you store data in Supabase, how is it truly decentralized?
"Supabase is a performance cache, not the source of truth. The source of truth is the MST Testnet. If we deleted our entire database tomorrow, we could reconstruct every record by replaying the events emitted by our smart contracts —
BatchMinted,CustodyTransferred,EscrowFunded,CarbonLogged, etc. Every on-chain event contains all the data needed for reconstruction."
Q: What happens if the temperature of a cold-chain batch exceeds the safe threshold?
"This is where our
TelemetryRegistryis exceptional. When IoT sensors detect a breach, the backend callsanchorTelemetry()withisBreached: true. The contract emits aComplianceBreachedevent on-chain with the reason. This is an immutable, timestamped record of the violation. It can automatically trigger the batch stage to move toDisputedinBatchRegistry, preventing the escrow from releasing and flagging the goods for inspection."
Q: Is the escrow secure against double-spending?
"Yes.
EscrowRegistry.releaseFunds()uses the Checks-Effects-Interactions pattern. TheisReleasedflag is set totruebefore any Ether transfer, making reentrancy attacks impossible. Additionally,require(!escrow.isReleased)at the top ensures the function can only execute once per batch."
Q: Can the smart contracts be upgraded?
"Currently, the contracts are immutable by design, which is the strongest possible trust guarantee for our counterparties. However, the modular registry pattern we have built — where each concern (Identity, Batch, Escrow, Carbon, Telemetry, Documents) lives in its own contract — means we can deploy a new version of, say,
CarbonRegistryand simply point the system to the new address without redeploying everything. This is the foundation of a proxy-upgradeable pattern."
Q: How do we prevent a Manufacturer from minting fake batches?
"Two layers of protection. First, only wallets with
SUPPLIER_ROLEinGovernanceRegistrycan callmintBatch(). Second, that wallet must also pass theidentityRegistry.isVerified(msg.sender)check. Getting both requires approval from the platform admin. If fraud is detected post-verification, admin can callrevokeIdentity(), which immediately blacklists the wallet from all future on-chain actions."
Q: How does this work for large enterprises that already have ERP systems like SAP?
"The
erpHashparameter in every stage-transition function is designed exactly for this. An enterprise can pass akeccak256hash of their internal ERP record ID into every blockchain call. This creates a permanent cryptographic link between their internal ERP database and our blockchain records, without exposing any proprietary ERP data on-chain."