This is a blockchain-integrated smart contract system designed to improve transparency, data integrity, and verifiability in stunting prevention programs. The contract provides an immutable audit trail for health data modifications, ensuring accountability and trust in data management systems.
This project implements a simplified, gas-efficient architecture focused on audit logging for health data systems.
A lightweight, gas-efficient contract for recording data modifications on the blockchain:
- Immutable Audit Trail: Records all data modifications using blockchain-native event logs
- Zero On-Chain Storage: Maximum gas efficiency by leveraging event logs instead of storage
- Minimal Blockchain Pattern: Only stores what absolutely needs to be on-chain
- Gasless Transactions: Supports meta-transactions through EIP-712 signed messages, allowing relayers to pay gas fees
- EIP-2771 Compatible: Standard implementation for trusted forwarder pattern
- Simplified Architecture: Single-purpose contract focused on audit logging
- Gas Efficiency: Event-based logging reduces costs by up to 90% compared to storage operations
- Gasless Transactions: Users can sign transactions without holding native tokens; relayers submit on their behalf
- EIP-712 Support: Type-safe signing standard for better security and wallet integration
- Zero Storage Cost: Leverages blockchain's immutable event log system
- Verifiable & Transparent: All audit records are publicly verifiable on the blockchain
- Future-Proof: Designed to integrate with off-chain indexing solutions (The Graph, Etherscan, custom indexers)
git clone https://github.com/MandalaChain/stunting-smartcontract.git
cd stunting-smartcontract
yarn install
# Compile contracts
yarn compile
# Run local Hardhat node (required for local deployment and testing)
yarn local-node
# In a new terminal, deploy to localhost
yarn deploy-localhost
# Test the logging functionality (after deployment)
yarn test-log --network localhost
# Run tests
yarn test
# Run extended tests
yarn test-extended
# Generate gas usage report
yarn test-gas
# Check code formatting
yarn format:check
# Auto-format code
yarn format
The contract can be deployed to various networks as configured in hardhat.config.ts. Ensure you have:
- Configured RPC endpoints in your environment
- Sufficient funds in the deployer wallet
- Updated network-specific parameters in the config files
All tests pass successfully! The comprehensive test suite verifies:
- ✅ Contract deployment and initialization
- ✅ Direct logging functionality
- ✅ Meta-transaction execution and validation
- ✅ Error handling for invalid nonces and signatures
- ✅ Event emission and data integrity
- ✅ Gas optimization benchmarks
Gas usage is highly optimized through the event-based logging approach, making it cost-effective for high-frequency audit logging.
// Hash the data you want to record
const metadata = {
childId: "123456",
weight: "7.5",
height: "63",
measurementDate: "2023-05-12",
location: "Puskesmas A"
};
// Create a unique identifier for this data entry
const resourceId = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(JSON.stringify(metadata))
);
// Log the modification on-chain
const tx = await auditRegistry.logModification(resourceId);
const receipt = await tx.wait();
// The transaction hash serves as immutable proof
console.log("Audit record transaction:", tx.hash);
console.log("Block number:", receipt.blockNumber);
console.log("Gas used:", receipt.gasUsed.toString());
This approach allows users to sign transactions without paying gas fees. A relayer submits the transaction on behalf of the user.
// 1. Prepare the function call data
const resourceId = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(JSON.stringify(metadata))
);
const functionCall = auditRegistry.interface.encodeFunctionData(
"logModification",
[resourceId]
);
// 2. Get the current nonce for the user (prevents replay attacks)
const nonce = await auditRegistry.nonces(userAddress);
// 3. Create the meta transaction data structure
const metaTxValue = {
from: userAddress,
nonce: nonce.toNumber(),
functionCall: functionCall
};
// 4. User signs the meta transaction (EIP-712 typed data)
const signature = await userWallet._signTypedData(
metaTxDomain,
metaTxTypes,
metaTxValue
);
// 5. Relayer executes the meta transaction (pays gas fees)
const tx = await auditRegistry
.connect(relayer)
.executeMetaTransaction(
metaTxValue.from,
metaTxValue.nonce,
metaTxValue.functionCall,
signature
);
await tx.wait();
// Result: Transaction is processed with relayer paying gas
// Important:
// - DataModified event shows the contract address as the actor
// - MetaTransactionExecuted event records both the original user and the relayer
// - This enables gasless user experience while maintaining accountability
├── config/ # Configuration files
│ ├── AuditConfig.ts # Audit system configuration
│ └── ContractArguments.ts # Deployment arguments
├── contracts/ # Solidity smart contracts
│ ├── AuditRegistry.sol # Main audit registry contract
│ ├── Interface/ # Contract interfaces
│ │ └── IAuditRegistry.sol
│ └── utils/ # Utility contracts
│ └── MetaTransaction.sol # Meta-transaction support
├── lib/ # TypeScript libraries
│ ├── AuditConfigInterface.ts
│ ├── AuditRegistryProvider.ts
│ ├── NetworkConfigInterface.ts
│ └── Networks.ts
├── scripts/ # Deployment and testing scripts
│ ├── 1_deploy.ts # Main deployment script
│ ├── 2_deploy_localhost.ts # Local deployment
│ └── 3_testLogModification.ts # Test logging functionality
├── test/ # Test suite
│ └── index.ts
├── typechain-types/ # Auto-generated TypeScript types
├── hardhat.config.ts # Hardhat configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── DOCS.md # Additional documentation
├── PLAN.md # Project planning
└── README.md # This file
logModification(bytes32 resourceId)
- Description: Records a data modification event on the blockchain
- Parameters:
resourceId: A unique identifier (hash) representing the modified data
- Access: Public
- Returns: Transaction receipt
- Emits:
DataModified(address indexed actor, bytes32 indexed resourceId)
executeMetaTransaction(address from, uint256 nonce, bytes functionCall, bytes signature)
- Description: Executes a function call via meta transaction (gasless transaction)
- Parameters:
from: The address of the original signernonce: Current nonce for replay protectionfunctionCall: Encoded function call datasignature: EIP-712 signature from the user
- Access: Public
- Returns: Transaction receipt
- Emits:
MetaTransactionExecuted(address indexed from, address indexed relayer, bytes functionCall)
nonces(address user)
- Description: Gets the current nonce for a user address
- Parameters:
user: The address to query
- Returns:
uint256- Current nonce value - Access: Public view
DataModified(address indexed actor, bytes32 indexed resourceId)
- Emitted when: A data modification is logged
- Parameters:
actor: The address that performed the modification (or contract address for meta-tx)resourceId: The unique identifier of the modified data
MetaTransactionExecuted(address indexed from, address indexed relayer, bytes functionCall)
- Emitted when: A meta transaction is successfully executed
- Parameters:
from: The original signer of the transactionrelayer: The address that submitted the transactionfunctionCall: The encoded function call that was executed
- Data Modification: When data is created or updated in your backend system
- Hash Generation: Generate a unique
resourceIdby hashing relevant metadata - Blockchain Logging: Call
logModification(resourceId)to record the audit trail - Transaction Receipt: Retrieve the transaction hash from the receipt
- Database Storage: Store the transaction hash alongside the data for verification
- Verification: Users can verify the audit trail using:
- Blockchain explorers (Etherscan, etc.)
- Direct RPC queries to the node
- Event indexing services (The Graph, custom indexers)
// Backend service example
async function recordDataModification(data: HealthData) {
// 1. Save data to database
const savedData = await database.save(data);
// 2. Generate resource ID
const resourceId = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(JSON.stringify({
id: savedData.id,
timestamp: savedData.updatedAt,
checksum: savedData.checksum
}))
);
// 3. Log to blockchain
const tx = await auditRegistry.logModification(resourceId);
const receipt = await tx.wait();
// 4. Update database with blockchain proof
await database.update(savedData.id, {
auditTxHash: receipt.transactionHash,
auditBlockNumber: receipt.blockNumber
});
return {
dataId: savedData.id,
txHash: receipt.transactionHash
};
}
// Verify data integrity
async function verifyDataIntegrity(dataId: string) {
const data = await database.findById(dataId);
// Get transaction from blockchain
const tx = await provider.getTransaction(data.auditTxHash);
const receipt = await provider.getTransactionReceipt(data.auditTxHash);
// Parse logs to get the DataModified event
const event = receipt.logs
.map(log => auditRegistry.interface.parseLog(log))
.find(parsed => parsed.name === 'DataModified');
// Reconstruct expected resource ID
const expectedResourceId = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(JSON.stringify({
id: data.id,
timestamp: data.updatedAt,
checksum: data.checksum
}))
);
// Verify
return {
verified: event.args.resourceId === expectedResourceId,
blockNumber: receipt.blockNumber,
timestamp: (await provider.getBlock(receipt.blockNumber)).timestamp
};
}
- Cost Efficiency: Events cost ~2,000 gas vs ~20,000 gas for storage operations
- Immutability: Events are permanent and cannot be altered or deleted
- Indexability: Events are easily indexed by blockchain explorers and custom indexers
- Scalability: Minimal on-chain footprint allows for high-volume logging
- Immutable Records: Once logged, audit trails cannot be modified or deleted
- Cryptographic Proof: Transaction hashes provide cryptographic proof of logging
- Replay Protection: Nonce-based system prevents meta-transaction replay attacks
- Signature Verification: EIP-712 ensures secure, type-safe transaction signing
- No PII On-Chain: Only hashes are stored, keeping sensitive data off-chain
- Health data audit trails for stunting prevention programs
- Supply chain tracking and verification
- Document modification history
- Compliance and regulatory reporting
- Data integrity verification for sensitive records
- Multi-party data sharing with accountability
Create a .env file in the root directory:
# Network RPC URLs
ETHEREUM_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
POLYGON_RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/YOUR_KEY
BSC_RPC_URL=https://bsc-dataseed1.binance.org
# Deployer Private Key (NEVER commit this!)
PRIVATE_KEY=your_private_key_here
# Block Explorer API Keys (for verification)
ETHERSCAN_API_KEY=your_etherscan_key
POLYGONSCAN_API_KEY=your_polygonscan_key
BSCSCAN_API_KEY=your_bscscan_key
# Gas Configuration
GAS_PRICE=50
GAS_LIMIT=3000000
Edit hardhat.config.ts to configure networks, gas settings, and compiler options.
# Terminal 1: Start local node
yarn local-node
# Terminal 2: Deploy
yarn deploy-localhost
# Deploy to a specific network
npx hardhat run scripts/1_deploy.ts --network <network-name>
# Example: Deploy to Polygon Mumbai testnet
npx hardhat run scripts/1_deploy.ts --network mumbai
After deployment, the contract address will be saved in the deployment artifacts. You can verify the contract on block explorers:
npx hardhat verify --network <network> <contract-address>
The contract is optimized for minimal gas consumption:
- logModification(): ~2,500 gas (event only, no storage)
- executeMetaTransaction(): ~5,000 gas (signature verification + event)
For comparison, traditional storage-based audit logging would cost 10-20x more.
Issue: "Nonce too low" error in meta-transactions
- Solution: Ensure you're fetching the latest nonce before signing
Issue: "Invalid signature" error
- Solution: Verify the EIP-712 domain and types match the contract
Issue: Deployment fails with "insufficient funds"
- Solution: Ensure the deployer wallet has enough native tokens
Issue: Events not showing in logs
- Solution: Make sure you're waiting for the transaction receipt
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Write comprehensive tests for new features
- Follow the existing code style (run
yarn format) - Update documentation as needed
- Ensure all tests pass before submitting PR
- Built with Hardhat
- Uses OpenZeppelin contracts
- Implements EIP-712 for typed signatures
- Inspired by minimal blockchain design patterns