Notes stored in stable memory, remain across upgrades.
This flow makes the app feel like a central server exists — even though it doesn’t.
Key Challenges & Solutions
1. Cycle Cost & Write Efficiency
Problem:
Frequent writes to the canister were expensive and unnecessary.
Solution:
Added a batching layer
Compressed write payloads
Reduced API calls by ~80% during fast typing
Why it mattered:
Made the app practical and cheap to run.
2. “Certificate Not Found” in Local Development
Problem:
Local agents on the IC require fetching a root key.
Without it, calls fail and debugging becomes painful.
Solution:
Conditional initialization:
if (process.env.DFX_NETWORK === "local") {
const agent = new HttpAgent({ host: "http://localhost:4943" });
await agent.fetchRootKey();
dkeeper_backend = createActor(canisterId, { agent });
} else {
dkeeper_backend = createActor(canisterId, { agent: new HttpAgent() });
}
Impact:
Local DX became smooth and predictable.
3. Stable-Structure Design
Tradeoff:
Dynamic collections are flexible but error-prone during upgrades.
Decision:
Use a fixed-shape stable list:
predictable
upgrade-safe
ideal for small workloads
Outcomes / Learnings
Even though this project is private, it taught me:
stable-memory patterns are fundamental on the IC
optimistic UI dramatically improves UX
batching writes can reduce cycle burn significantly
backend agent configuration affects everything
Motoko upgrades require careful data planning
This project became a strong foundation for future decentralized work.
Final Thoughts
Keeper DApp helped me understand what “small, decentralized apps” really feel like to build.
It’s simple, but it taught me more about on-chain persistence and cycle-aware design than much larger projects.