When I started building my NFT marketplace in Solidity, my main goal was simple:
Make it work.
But as the project grew, I realized that writing Solidity code that works is only part of the job.
Here are three things I learned while building the project with Solidity, Foundry, and OpenZeppelin.
1. Working code doesn't mean secure code
A function can work perfectly and still be vulnerable.
For example, a marketplace purchase function may look simple:
function buy(uint256 listingId) external payable {
Listing storage listing = listings[listingId];
require(msg.value == listing.price);
// Transfer NFT
// Transfer payment
// Update listing
}
But then you have to ask:
What if the listing was already purchased?
What if the seller no longer owns the NFT?
What if approval was revoked?
What happens during an external call?
Can the function be reentered?
This changed the way I approach smart contracts.
I don't just ask:
"Does this function work?"
I also ask:
"How could someone break it?"
2. Architecture becomes important very quickly
At the beginning, putting everything into one contract seems fine.
But an NFT marketplace can eventually include:
Listings
Purchases
Offers
Auctions
Rentals
Fees
Administration
Queries
Putting all of this into one huge contract quickly becomes difficult to maintain.
For my project, I started separating responsibilities into different components:
MarketCore.sol
MarketAdmin.sol
MarketValidation.sol
MarketEvents.sol
MarketErrors.sol
MarketQuery.sol
MarketplaceStorage.sol
The important lesson wasn't simply "use more contracts."
It was:
Give each part of the system a clear responsibility.
This makes the code easier to understand, test, and audit.
3. Testing shouldn't only prove that things work
With Foundry, it's easy to write tests for the happy path:
Create listing
↓
Buy NFT
↓
NFT transferred
But smart contract testing should go further.
I also want to know what happens when:
Wrong payment
Unauthorized caller
Invalid listing
Revoked approval
Already purchased NFT
Repeated calls
Failed external call
This is where testing starts becoming security research.
Instead of only testing:
"Can a user do this?"
I try to test:
"Can a malicious user make this behave differently than intended?"
Final Thoughts
Building this project taught me that smart contract development isn't just about writing Solidity.
It's about thinking about:
Architecture → State → Edge cases → Testing → Security
And the biggest lesson I've learned so far is:
A smart contract isn't good just because it works. You need to understand how it behaves when someone tries to break it.
I'm continuing to improve the project and dive deeper into smart contract security and auditing.
More experiments and lessons coming soon.
Top comments (0)