I run 2Settle, a product with one stubborn goal: make crypto easy to spend on everyday things. Not trading, not wires. The coffee, the subscription, the bill you owe right now, paid with money you already hold.
v1 shipped on a Node stack and taught me what the product actually is. v2 is a ground-up build in Go. I want to share the reasoning, because "we rewrote it in a faster language" is the lazy version of the story and it is not what happened.
Why the language changed
The honest driver was not raw speed. It was the shape of the problem. A payment is not a request you can retry casually. It is real, final, and there is a person standing at a checkout waiting on it. The moment you take everyday spending seriously, your backend is doing many small, independent, time-sensitive things at once, and each one has money attached.
Go's model for concurrency, thousands of things happening at the same time without the code turning into a callback maze, maps almost exactly onto that. In v1 I was constantly working around the runtime to get predictable behavior under load. In v2 the runtime works with me. That is the whole trade.
Three things v1 taught me that shaped v2
What I am not doing this time
Not chasing every chain and token on day one. Not adding features v1 never validated. Not calling it done. It is still building, still being tested, shipped, and tested again.
Happy to answer anything about the Node to Go decision or building payment infrastructure the second time. What I will not get into is anything live-system specific, for obvious reasons.
the idempotency point below is the right worry for correctness. the one that got me on similar flows was afterward: a support ticket lands and someone needs to know why the balance is what it is. a current-balance table gives you the what, not the why. an append only trail of every state change makes that a 5 minute read instead of a log-diving afternoon
"Correctness is a product feature" is the line I'd frame on the wall. One push though: the correctness that bites at 2am usually isn't the concurrent request path Go just cleaned up for you. It's the async reconciliation after the payment. A crypto payment is final on-chain, but your backend hears about it through confirmations and webhooks that land late, out of order, or twice, and every so often a reorg unwinds one you'd already treated as done. Go makes the in-flight handling clean; it can't tell you whether your ledger's view of a payment is eventually consistent with the chain's.
When we built commission tracking on Stripe, every money-correctness bug lived in that same async layer: refunds clawing back a payout, a webhook firing twice, events arriving out of order. Never the request handler. So the v2 question I'm most curious about: what's your idempotency and reconciliation model for an event that arrives twice, or never? That's where "boring and predictable" earns its keep.
I'm curious what v2 has forced you to unlearn from v1. Was there a decision you were convinced was fundamental in the first version that you now see as solving the wrong problem?
The v1 to v2 leap you describe is exactly where most solo founders stall — they get stuck refactoring instead of shipping. I ran into this too. What helped was a hard rule: no rewrite until the existing code actively costs more in maintenance than the rewrite would take. Saved months.