Five layers, twelve thousand lines of TypeScript, and the four Bitcoin design decisions that mattered most.

Open-Sourced a Production Bitcoin Escrow Platform — Here's the Architecture Behind It
This year I spent three weeks reading Bitcoin escrow codebases on GitHub. Not because I had nothing better to do — because Tecneural was about to build one, and I wanted to know what mistakes had already been made so I would not repeat them.
The findings were sobering. Most of what is published as "open Bitcoin escrow" is one of three things: a Solidity contract that is not Bitcoin at all, a tutorial-grade script that handles one happy path and breaks on every edge case, or a 2017-era multisig demo that has not been touched in five years. Almost nothing in the wild is production-ready.
So we built one. It runs in production at tapvault.tecneural.com. It uses modern Bitcoin primitives. It handles the edge cases. And we open-sourced it.
This post is the architecture walkthrough — the actual technical design, the tradeoffs we made, and the parts that turned out to matter more than expected. If you are evaluating whether to fork TapVault or build from scratch, this is the read.
The architecture, top-down
TapVault is a five-layer system. Each layer is independent enough to swap out, but designed to interlock cleanly with the others.

A breakdown of the layers and their footprints:
| Layer | What it does | Tech | Lines |
|---|---|---|---|
| Frontend | React UI, PSBT signing, key encryption | Next.js 16, React 19, Tailwind v4, DaisyUI | ~4,500 |
| Backend API | Escrow coordination, address derivation, PSBT building | Next.js API routes, NextAuth v5 | ~3,800 |
| Bitcoin layer | Taproot scripts, Schnorr signing, MAST trees | bitcoinjs-lib, BIP-86 / BIP-341 | ~1,200 |
| Data + Indexer | State, audit log, deposit detection | MongoDB + Mongoose, Blockbook | ~1,800 |
| Billing + Email | Subscription tiers, transactional email | Stripe, Mailjet | ~700 |
Total: roughly 12,000 lines of TypeScript. Small enough that one engineer can read the whole codebase in a week. Large enough that it actually does something.
The Bitcoin layer — where the interesting decisions are
Most of the code in TapVault is application logic — UI, auth, billing, dashboards. The genuinely Bitcoin-specific code is about 1,200 lines, but every one of those lines was a deliberate choice. Let me walk through the four most important.
Decision 1 — Taproot, not legacy multisig
We could have used P2SH or P2WSH multisig. They are simpler, well-documented, and supported everywhere. We chose Taproot anyway, for three reasons:
- Privacy — Taproot multisig outputs are indistinguishable from single-sig spends in the cooperative case. Legacy multisig advertises itself on-chain.
- Cost — Taproot key-path spends are cheaper than legacy multisig spends. For the happy path (most escrows complete cooperatively), users save real money.
- MAST — the Merklized script tree lets us hide unused spend paths. A 4-path script tree only reveals one branch on spending, keeping the other three private forever
The tradeoff: bitcoinjs-lib's Taproot APIs are newer and have sharper edges than the P2WSH APIs. We hit two bugs while building. They are documented in the README so future implementers do not hit them again.
Decision 2 — NUMS internal key, not platform-controlled
Taproot has a key path (one signature) and script paths (script-defined). The internal key controls the key path. Most Taproot tutorials use a real, signed key here. We do not.
If the internal key were platform-controlled, the platform could spend any escrow address with one signature — bypassing the entire 2-of-3 multisig. The only way to make a Taproot multisig genuinely 2-of-3 is to make the key path unspendable. We use the BIP-341 NUMS point (a deterministic, provably-unsigned key).
If you fork TapVault and one of your changes makes the internal key signable, you have just turned a non-custodial product into a custodial one. This is the single most important invariant in the codebase. We have a regression test that fails if anyone accidentally breaks it.
Decision 3 — Per-escrow address derivation, not address reuse
Every escrow gets its own Taproot address, derived from a BIP-86 path with the escrow ID. We do not reuse addresses across escrows. Three reasons:
- Privacy — reusing addresses links every transaction to one entity on-chain. Per-escrow addresses make chain analysis dramatically harder.
- Recoverability — if the database burns down, we can re-derive every address from the arbitrator seed and reconstruct state by scanning the chain. With reuse, we would lose the mapping between escrows and outputs.
- Failure isolation — a bug in one escrow's script construction does not contaminate other escrows.
Decision 4 — CSV timelock as the fourth spend path
Three of the four spend paths require a platform signature. The fourth — the timelock auto-refund — does not. After a configurable number of blocks (default: 4,320 blocks ≈ 30 days), the buyer alone can sweep the funds.
This is the failsafe. If the platform disappears, gets hacked, refuses to arbitrate, or otherwise becomes unavailable, the buyer's funds are not stuck. They wait for the timelock and reclaim what is theirs.
The number is configurable per-escrow because different use cases have different reasonable timeframes. A 2-hour P2P trade should not have a 30-day timelock — that is too long. A 90-day rental deposit should not have a 7-day timelock — that is too short. The platform admin sets the default per template.
Edge cases that production code has to handle
These are the parts that tutorials never cover and that broke real platforms when they hit production.
Fee bumps and stuck transactions
If a release transaction is broadcast at 2 sat/vB and the mempool clears at 50 sat/vB, that transaction sits forever. TapVault implements RBF (Replace-By-Fee) on platform-broadcast transactions and surfaces a "bump fee" button to users for their own broadcasts. We also fetch live fee estimates from mempool.space and Blockbook on every transaction build.
Reorgs and confirmation tracking
A buyer's deposit gets one confirmation. The platform marks the escrow funded. Then a 1-block reorg orphans the deposit. What now? TapVault does not mark an escrow funded until 1 confirmation, but it does not mark it "safe" until 6. State transitions are gated by confirmation thresholds, and any state machine we re-run from chain history (after a database loss) takes the same conservative view. If a reorg invalidates a deposit, the platform automatically rolls back the escrow state and notifies the parties.
Concurrent signing
Both the buyer and the seller try to co-sign a release at the same time. Both submit valid signatures. Now what? PSBTs are designed for this — they are partial transactions that combine cleanly. TapVault's coordinator merges concurrent signatures, validates that the combined PSBT is valid, and broadcasts. If two competing PSBTs exist (e.g., a release and a refund), the platform applies a deterministic precedence rule and rejects the loser with an explanation.
Browser key encryption
User keys are stored client-side, AES-256-encrypted with a password. The encryption happens in WebCrypto, the key never goes to the server, and the password is salted with the user's account ID so a leaked database doesn't enable offline cracking against multiple users in parallel.
The cost: users have to type their signing password for every transaction. The benefit: the server quite literally cannot sign on behalf of users, even if it wanted to. This is part of what makes the system genuinely non-custodial.
Why we open-sourced it
This question came up in nearly every conversation while we built it. "Why not sell it on CodeCanyon for $59 and book the revenue?"
- The Bitcoin ecosystem benefits when high-quality non-custodial primitives are accessible. Custodial dominance is bad for users.
- Tecneural's actual business is harder than escrow — Bitcoin Layer-2 design with BitVM, Cosmos and EVM bridges, custom AI model builders for industries. TapVault is the proof-of-quality artifact, not the product.
- Open-sourcing forces us to build cleaner. We knew engineers would read this code, and that pressure raised the bar throughout.
If you are a developer, agency, or team building anything on Bitcoin, take the code. Read it. Fork it. Improve it. We will support you on deployment if you ask. And if you eventually need help with something harder than escrow, you know where we are.
How to get it
The repository is shipped to qualified teams on request. Reach out to Tecneural with a short note about what you are building and we will send you the code, the deployment guide, and a 30-minute setup call. There is no charge.
If you are evaluating, run through the live demo first at tapvault.tecneural.com. Create a testnet escrow, fund it, complete it. The whole flow takes about 10 minutes and tells you more than any sales pitch.
What's next
Tecneural's near-term roadmap on TapVault includes: lightning-network instant settlement, a hardware wallet signing flow (Coldcard, Trezor, Ledger), Cosmos and EVM bridge templates so users can escrow non-Bitcoin assets backed by Bitcoin collateral, and a federation mode where multiple arbitrators FROST-sign the platform key for institutional use cases.
If any of those interest you specifically, contact us — we sometimes accelerate features when there is real demand and a willing partner.
GET TAPVAULT — IT'S FREE
TapVault is open and free. Reach out to Tecneural and we will send you the source code, deployment guide, and a 30-minute setup call.
Live demo: tapvault.tecneural.com
Get the code: Contact Tecneural — we ship the repository to qualified teams
Larger projects: Bitcoin L2, BitVM bridges, custom AI models — also Tecneural
About the author
Jeyakumar S — CEO at Tecneural. 16+ years building Bitcoin Layer-2 infrastructure, threshold signature systems (FROST, Schnorr aggregation), validator coordination, and cross-chain bridge primitives. Specializes in Rust and C++ cryptographic systems and consensus-layer engineering.
Contact Us
- 📞 Phone: +91 96555 17034
- 📧 Email: support@tecneural.com
- 🌐 Website: www.tecneural.com
