From a Real-World Kefir Handover to an On-Chain Commitment with Node.js, SHA-256 and Base
Most blockchain demos begin with a smart contract.
This one began with a real person handing kefir culture to another person.
While building MyZubster, I wanted to test a practical question:
How can we connect a real-world event to public blockchain evidence without storing personal data on-chain — and without pretending that blockchain proves the physical event itself?
We now have a working end-to-end experiment.
The architecture
The flow is intentionally simple:
Physical handover
↓
MarketplaceHandover
↓
RECORDED
↓
Canonical payload
↓
SHA-256
↓
Blockchain commitment
↓
Base Sepolia
↓
Transaction verification
↓
Blockchain receipt stored in MyZubster
The important part is that the blockchain is not the database.
MongoDB maintains the application state and structured evidence. The public blockchain provides an independent timestamped commitment.
- Creating the handover record
The physical kefir exchange progressed through explicit application states:
ACCEPTED
↓
HANDED_OVER
↓
RECEIVED
↓
RECORDED
Only after reaching RECORDED do we allow preparation of the blockchain commitment.
That distinction matters.
Creating a hash does not mean something has been anchored.
Preparing a transaction does not mean it has been confirmed.
And storing a transaction ID does not automatically mean the expected commitment actually exists on-chain.
- Building the commitment
For the first implementation we defined:
myzubster.marketplace-handover.v1
The committed payload contains the identifiers, handover method, state and relevant event timestamps.
From its deterministic representation we calculate a SHA-256 digest.
For this real handover:
ba9973f08ce86d16a3611c3cccbb9cc2cc779b9ea1cb6fd87a2e5864e557b6b9
Instead of publishing the original record, the transaction contains:
MZ-HANDOVER-V1:
ba9973f08ce86d16a3611c3cccbb9cc2cc779b9ea1cb6fd87a2e5864e557b6b9
This means the public chain receives the cryptographic commitment, not the participant's personal information.
- Anchoring it on Base Sepolia
We then performed the actual transaction.
Network: Base Sepolia
Chain ID: 84532
Value: 0 ETH
Block: 46933575
Gas used: 24160
Status: CONFIRMED
Confirmed: 2026-09-17T08:57:18.000Z
Transaction:
0x998a98b1733312e248f74a1387319dae30aab8123a6115c517e4ffe0ef9584bf
You don't have to trust this article.
You can inspect the transaction yourself:
https://sepolia.basescan.org/tx/0x998a98b1733312e248f74a1387319dae30aab8123a6115c517e4ffe0ef9584bf
- Verifying before trusting
Before writing the blockchain receipt back into MyZubster, we verified the transaction against the chain.
The verification included the expected chain ID, transaction, block, calldata commitment and confirmation.
Only after those checks did MyZubster persist the blockchain metadata.
The resulting state is conceptually:
{
state: "RECORDED",
blockchainCommitment: {
schema: "myzubster.marketplace-handover.v1",
algorithm: "SHA-256",
hash: "ba9973f08ce86d16a3611c3cccbb9cc2cc779b9ea1cb6fd87a2e5864e557b6b9",
network: "base-sepolia",
txId: "0x998a98b1733312e248f74a1387319dae30aab8123a6115c517e4ffe0ef9584bf",
confirmedAt: "2026-09-17T08:57:18.000Z"
}
}
And the application derives:
const onchainRecorded = Boolean(
commitment.network &&
commitment.txId &&
commitment.confirmedAt
);
So we deliberately keep these concepts separate:
state = RECORDED
onchainRecorded = true
ONCHAIN_RECORDED does not replace the domain state.
It adds another evidence layer.
- Moving the experiment into reusable code
A one-off transaction isn't enough.
We therefore added blockchain commitment metadata to the MarketplaceHandover model and implemented an authenticated endpoint:
POST /:handoverId/prepare-blockchain-commitment
The model can now represent:
blockchainCommitment: {
schema,
algorithm,
hash,
preparedAt,
network,
txId,
anchoredAt,
confirmedAt
}
The endpoint requires an eligible RECORDED handover and checks participant authorization before generating the commitment.
The implementation is public here:
https://github.com/MyZubster-Ecosystem/myzubster/pull/1230
Main MyZubster repository:
https://github.com/MyZubster-Ecosystem/myzubster
- Testing it
After installing the repository's declared dependencies, we ran the relevant marketplace test suites:
PASS tests/marketplaceFlow.test.js
PASS tests/marketplaceEvidence.test.js
Test Suites: 2 passed, 2 total
Tests: 4 passed, 4 total
TEST_EXIT=0
The point wasn't simply to get a transaction onto a block explorer.
The blockchain representation also needed to fit the existing marketplace/evidence architecture without breaking the application flow.
What does the blockchain actually prove?
This is the most important part.
The transaction does not prove that the kefir was safe.
It does not provide microbiological validation.
It does not independently prove that the physical handover occurred exactly as described.
And it certainly doesn't turn an observation into scientific evidence.
What it gives us is much narrower:
A public, timestamped cryptographic commitment that can later be compared with the corresponding digital record.
That's useful because it allows us to ask a precise technical question:
Does the record I have now produce the same commitment that was publicly anchored at that point in time?
Why I like this architecture
Instead of asking blockchain to become the source of all truth, we can separate responsibilities:
Human action
↓
Application evidence
↓
Structured representation
↓
Cryptographic commitment
↓
Public timestamp / receipt
Each layer proves something different.
That's a much more useful model for provenance than simply putting a "verified on blockchain" badge on an application.
Next step: independent verification
The natural next step is a verifier that can reproduce the process independently:
record
→ canonicalize()
→ sha256()
→ expectedCommitment
→ fetchTransaction()
→ decodeCalldata()
→ compare()
→ MATCH | NO_MATCH
Eventually, I'd like verification to require as little trust in the MyZubster application itself as practical.
Project links
MyZubster:
https://www.myzubster.com
Main source code:
https://github.com/MyZubster-Ecosystem/myzubster
MyZubster GitHub organization:
https://github.com/MyZubster-Ecosystem
On-chain handover integration — PR #1230:
https://github.com/MyZubster-Ecosystem/myzubster/pull/1230
Kefir / fermentation knowledge repository:
https://github.com/DanielIoni-creator/Myzubster-fermentation-kefir
MyZubster Marketplace:
https://github.com/DanielIoni-creator/MyZubster-Marketplace
Base Sepolia transaction:
https://sepolia.basescan.org/tx/0x998a98b1733312e248f74a1387319dae30aab8123a6115c517e4ffe0ef9584bf
What started as one person sharing kefir culture with another became a useful test of how physical events, application state, cryptographic evidence and public blockchain infrastructure can coexist without confusing one for another.
There is still a lot to build, particularly around canonicalization, independent verification, batching and evidence schemas.
But now we have something much more valuable than a diagram:
a real event, a real application record, real code, a reproducible SHA-256 commitment and a real confirmed public transaction.
And every layer can be inspected.
MyZubster — Share → Try → Observe → Improve → Share again.
nodejs #blockchain #web3 #opensource #mongodb #sha256 #base #cryptography #softwarearchitecture #provenance