P2P Crypto Exchange on Polygon — Case Study
This is the case study for the peer-to-peer crypto exchange I built on Polygon with a Solidity escrow contract underneath. Users can trade fiat for crypto (and crypto for crypto) directly with one another, with the platform contract holding the assets in escrow until both sides confirm — or until a dispute is resolved. The Solidity contract for the escrow is open-source and was adopted by other projects in the same space. Spain-born, Paraguay-based, working across the on-chain and off-chain layers.
What was the problem?
Centralized exchanges work well at the top of the market but they leave whole regions unserved — places where local payment rails do not connect to the global liquidity, where KYC requirements lock out legitimate users, or where the trust model of a custodial exchange is itself a problem. The traditional response was to use Telegram groups and trust your counterparty, which fails as soon as the trade gets above the size people can afford to lose.
The product was a marketplace where buyers and sellers found each other, agreed terms, and let a smart contract hold the crypto leg of the trade in escrow while the fiat leg moved through whatever local rail made sense. The on-chain layer existed to make the trust assumption explicit and small: nobody runs off with the funds because the contract will not let them.
The constraints:
- Gas cost low enough that small trades were viable.
- Dispute resolution that worked when the two parties disagreed.
- Multi-currency support so different markets could use different local currencies on the fiat side.
- A contract simple enough to audit and reuse.
What was the approach?
The build separated concerns into two layers with a clear interface between them.
On-chain (Solidity, Polygon). The escrow contract held funds, enforced the resolution rules, and was the source of truth for any value movement. It was deliberately small — fewer lines, fewer attack surfaces, fewer reasons for a re-audit on every product change. The on-chain code knew about escrows, releases, refunds, and disputes; it did not know about chat messages, payment methods, or KYC.
Off-chain (Node.js + Postgres + React). Everything that did not need consensus lived here — user accounts, KYC artifacts, payment-method metadata, listings, dispute evidence, support workflows. The off-chain system could be redeployed, rewritten, even rewritten in a different language without touching the on-chain contract. That separation is the entire point.
The phased build went:
- Contract first. Escrow logic, deposit, release, refund, dispute, arbiter role. Test suite in Hardhat covering every path including the unhappy ones. The Hardhat documentation covers the testing harness I leaned on.
- Off-chain skeleton. Listings, matching, chat, KYC, dispute UI. End-to-end vertical slice that connected to the contract on a testnet.
- Multi-currency expansion. The contract did not change for new fiat currencies — only the off-chain metadata did. That was the whole reason to keep currencies off-chain in the first place.
- Mainnet deploy and hardening. Audit pass, key management, monitoring on the contract address, runbook for the dispute role.
The thing that made this work was refusing to let on-chain scope creep in. Every time a new feature came up the first question was "does this need to be on-chain?" Most of the time the answer was no.
What is in the stack?
- Polygon. EVM-compatible, low gas cost per transaction, mature tooling. Polygon's documentation was the reference I sent the team when we onboarded.
- Solidity + Hardhat. Solidity for the escrow contract, Hardhat for testing, deployment scripts, and the local network. Coverage tooling caught a few branches the unit tests missed.
- OpenZeppelin libraries. Standard implementations for access control, reentrancy guards, and ERC-20 interactions. Rolling your own is a category-error for production contract code; OpenZeppelin is the boring choice that prevents whole classes of bugs.
- React on the front-end. Wallet connection through standard EVM wallet adapters, contract calls through ethers.js, optimistic UI for the off-chain actions and confirmation-required UI for the on-chain ones.
- Node.js + Express on the back-end. REST API, JWT auth, postgres-backed sessions, integration layer between the off-chain database and the contract events emitted by the chain.
- PostgreSQL. Listings, users, KYC artifacts, dispute history, audit logs for the off-chain parts of the workflow.
- The Graph or direct event indexing. For querying contract state efficiently rather than hitting a node for every page load. Standard pattern in the EVM ecosystem.
The contract surface area stayed deliberately small. The off-chain surface area carried the product complexity. That is the right division of labor for almost any DeFi product that does not need to be fully on-chain.
What was the outcome?
The exchange shipped. The escrow contract is open-source and has been picked up by other projects in the P2P-exchange space — that is the most honest signal of code quality I have, and I lean on it rather than usage numbers I cannot independently verify. Specific names of adopting projects are not on this page because publishing them is not my call.
Qualitative notes worth flagging:
- The dispute system worked without becoming a black hole of operator time. Most trades resolved without dispute; the disputes that did happen had enough structured evidence — chat logs, payment screenshots, KYC artifacts — that the arbiter call was usually obvious.
- The multi-currency expansion was painless because currencies lived off-chain. Adding a new local market meant database rows and UI strings, not a new contract deploy.
- The audit pass found small issues and no large ones, which is the goal — small fixes are cheap, large fixes require a redesign.
- The team continued running and extending the product after my engagement.
What did I learn?
Refuse to let on-chain scope grow. Every "this should also be on-chain" conversation should start with "what bug do we prevent by putting it on-chain?" If the answer is not a specific trust violation, the feature belongs off-chain where it is cheaper to ship and easier to change.
Open-source the contract on purpose, early. The other projects that picked up the escrow contract did so because it was readable, audited, and licensed permissively. That open-sourcing was a marketing decision as much as a technical one. It builds trust faster than any landing page can.
OpenZeppelin and Hardhat are not optional. The pattern of "we wrote our own access-control library" is how Solidity contracts get exploited. The boring tooling — OpenZeppelin libraries, Hardhat test suite, slither static analysis — is what keeps the unhappy paths from becoming incidents.
Audit findings should be small. Going into an audit with a contract that already has 95% test coverage and uses standard libraries means the audit returns small issues. Going in with a hand-rolled contract means the audit returns a redesign. Plan for the first outcome by working that way from day one.
For the chain trade-offs, Solana vs Polygon for dapps is the article I send to anyone deciding. The blockchain development service page covers scope and pricing for similar engagements. For the broader sequencing approach, build a SaaS MVP fast describes how I split work across layers the same way I described above.
Email hello@ignax.dev if you are working on a contract-backed product and want to discuss the architecture before you commit to a stack. The repository style is at github.com/ignaxdev.
Frequently asked questions
What stack did you ship this on?
Polygon as the L2 chain, Solidity for the escrow and dispute contracts, Hardhat for the development and testing toolchain, React on the front-end, Node.js for the off-chain orchestration layer, and PostgreSQL for the parts that did not need to be on-chain (KYC artifacts, dispute history, support tickets). The on-chain pieces stayed small and verifiable; the off-chain pieces handled everything that did not need consensus.
Why Polygon instead of Ethereum mainnet?
Transaction cost. A P2P exchange has a high baseline rate of small actions — escrow deposits, releases, dispute openings — that would be prohibitively expensive on Ethereum mainnet at the time. Polygon kept gas costs at a level where the product was usable for the actual trade sizes our users worked with. The [Polygon documentation](https://docs.polygon.technology/) covers the EVM compatibility that made the port painless.
How does the dispute system work?
When a trade goes wrong, either party can open a dispute that freezes the escrow. A pre-defined arbiter role — held by the platform operator — reviews the evidence submitted off-chain (chat logs, payment screenshots, KYC artifacts) and triggers an on-chain release or refund. The arbiter cannot move funds outside the escrow rules; they can only choose between the resolution paths the contract already allows. That constraint is the whole point of doing this on-chain.
Is the contract actually open-source?
Yes — the escrow contract is published under a permissive license and other projects in the P2P-exchange space picked it up. I treat that adoption as the most honest signal of code quality. Specific company names go on this page only if those companies want to be cited; absence here is deliberate, not because there is nothing to point at.
Could you build something like this for me?
Yes. The pattern — Solidity escrow + off-chain orchestration + dispute role — is reusable for any product that needs trustless intermediation of value. The [blockchain development service](/services/blockchain-development) covers scope and pricing for engagements in this shape. If you are still weighing chains, [Solana vs Polygon for dapps](/articles/solana-vs-polygon-dapps) walks through the trade-offs I use to decide.
What would you change if you started over?
I would invest more in the test harness for the dispute paths from day one. The happy-path escrow flows were well-tested early; the dispute permutations grew bug surface over time and the tests caught up later than they should have. I would also keep more of the audit trail on-chain rather than in the off-chain database — verifiability is the value proposition of the whole product.