<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nova</title>
    <description>The latest articles on DEV Community by Nova (@novacont).</description>
    <link>https://dev.to/novacont</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4057427%2F4d721769-0dee-44a1-acf8-b2e3d5cad43e.png</url>
      <title>DEV Community: Nova</title>
      <link>https://dev.to/novacont</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/novacont"/>
    <language>en</language>
    <item>
      <title>The Gas Budget Problem Nobody Warns You About TON Contracts</title>
      <dc:creator>Nova</dc:creator>
      <pubDate>Tue, 04 Aug 2026 04:54:48 +0000</pubDate>
      <link>https://dev.to/novacont/the-gas-budget-problem-nobody-warns-you-about-ton-contracts-40ji</link>
      <guid>https://dev.to/novacont/the-gas-budget-problem-nobody-warns-you-about-ton-contracts-40ji</guid>
      <description>&lt;p&gt;If you come to TON from an EVM background, gas feels like a solved problem you already understand. You pay for computation, the caller covers it, transactions revert if they run out. You have done this a hundred times on Ethereum. So you write your first Tact contract, it compiles, it works in testing, and then in production something quietly goes wrong with the money math, or a contract slowly bleeds its balance, and you realize TON's model was never the same model you assumed.&lt;/p&gt;

&lt;p&gt;I ran a non-custodial escrow contract into most of these walls while building on TON. This post is the guide I wish I had read first: the gas and storage realities that do not exist in the EVM mental model, and the specific decisions they forced into my contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core surprise: your contract pays to exist
&lt;/h2&gt;

&lt;p&gt;On Ethereum, storage is a one-time cost. You pay gas to write it, and then it just sits there forever, for free, as long as the chain exists. Nobody charges you rent on a mapping.&lt;/p&gt;

&lt;p&gt;On TON, contracts pay storage rent. Your contract is charged over time for the data it holds, and that charge comes out of the contract's own balance. If the balance runs low enough, the contract can eventually be frozen or deleted. This single fact reshapes how you think about a contract that holds state, because now state is not a write-once cost, it is an ongoing liability against a balance you have to keep funded.&lt;/p&gt;

&lt;p&gt;For an escrow contract this is a genuine hazard, because the balance the storage rent eats into is, in the naive design, the same balance holding user funds. You do not want the mechanism that keeps your contract alive to be quietly nibbling at money that belongs to your users. That tension shaped almost every gas decision that followed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending money costs money, and you have to plan for it
&lt;/h2&gt;

&lt;p&gt;Here is the second thing the EVM mental model gets wrong on TON. On TON, sending value is done by dispatching a message, and dispatching a message has its own gas cost. That cost has to come from somewhere.&lt;/p&gt;

&lt;p&gt;The naive version: a user sends funds into your contract, you record the amount, and later you send funds back out. But the outbound send costs gas. If you recorded the full incoming amount as "owed" and then pay send fees out of the same pot, your accounting drifts. You are now paying message fees out of value you promised to someone else, and across many escrows that drift compounds into a real shortfall.&lt;/p&gt;

&lt;p&gt;So on TON you cannot treat "the amount the user sent" and "the amount you can pay out" as the same number by default. The gas for every future outbound message related to this deal has to be accounted for at the moment value comes in, not assumed away.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I handled it: an explicit, bounded gas budget
&lt;/h2&gt;

&lt;p&gt;The pattern I landed on was to make the gas budget an explicit part of every escrow, collected up front and bounded on both sides.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04jpbfb3t56gfzew90fc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04jpbfb3t56gfzew90fc.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When an escrow is created, the contract requires the incoming value to cover the agreed amount plus a gas budget, and it rejects anything outside a sane window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_GAS_BUDGET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 0.05 TON&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_GAS_BUDGET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 0.3 TON&lt;/span&gt;

&lt;span class="c1"&gt;// on escrow creation:&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;MIN_GAS_BUDGET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Insufficient deposit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;MAX_GAS_BUDGET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Overpayment: reduce attached value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two decisions are doing work here.&lt;/p&gt;

&lt;p&gt;The minimum guarantees every escrow arrives with enough attached value to fund the outbound messages it will eventually need for settlement. The contract is never in a position where it has to pay someone out but cannot afford the message to do it.&lt;/p&gt;

&lt;p&gt;The maximum is the less obvious one, and I added it after thinking about failure modes. Without an upper bound, a user fat-fingering an amount, or a buggy frontend, could attach a wildly excessive value. Bounding the top end turns a silent overpayment into an explicit, recoverable rejection. On a contract that handles money, a loud revert is almost always better than a quiet acceptance of the wrong number.&lt;/p&gt;

&lt;p&gt;The key mental shift: the deposit and the gas budget are two separate quantities that happen to arrive in the same transaction. Keeping them conceptually distinct is what keeps the fund accounting honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send modes are not boilerplate
&lt;/h2&gt;

&lt;p&gt;On TON, when you send a message you specify a mode, and the mode is a real decision with real consequences, not ceremony to copy from a template. Two flags mattered most in my settlement path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SendParameters&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SendPayGasSeparately&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;SendBounceIfActionFail&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SendPayGasSeparately&lt;/strong&gt; means the message's forward fees are paid from the contract balance rather than deducted from the value being sent. For a payout, this is what you want: the recipient should receive the amount you intended, not the amount minus whatever the network decided the forwarding cost was. If you skip this, the person you are paying quietly receives slightly less than the number in your logic, and your accounting and reality diverge again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SendBounceIfActionFail&lt;/strong&gt; is a safety belt. If the action fails, you want to know, through a bounce, rather than having value silently vanish into a failed send. On a contract holding user funds, "fail loudly" is the correct default, and choosing the mode that surfaces failures is part of writing money-safe TON code.&lt;/p&gt;

&lt;p&gt;The broader lesson: on TON, the send mode is part of your contract's correctness, not a detail. The difference between two mode flags can be the difference between correct payouts and a slow, invisible leak.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scaling trap I did not fully escape
&lt;/h2&gt;

&lt;p&gt;There is one more TON-specific reality worth naming, because it is the one I am still living with.&lt;/p&gt;

&lt;p&gt;The idiomatic TON design for something like escrow is a contract per item: a factory that spawns a separate child contract for each escrow, each with its own balance and its own storage rent. The reason is exactly the storage and gas model above. When every escrow is its own actor, the storage rent for each is funded by and isolated to that escrow, and the contract's growth does not turn into an ever-rising per-transaction cost.&lt;/p&gt;

&lt;p&gt;I did not start there. My first design kept all escrows in one contract, which is comfortable coming from the single-contract EVM habit, and it works, but it fights the grain of the platform. As the state grows, so does the cost of touching it, and the shared balance has to cover shared storage rent. On TON this is the anti-pattern, and knowing that earlier would have changed my first architecture.&lt;/p&gt;

&lt;p&gt;If you are starting a stateful TON project now, take the per-item contract model seriously from day one. It is not premature optimization on TON. It is the model the chain's economics are actually built around.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell my past self
&lt;/h2&gt;

&lt;p&gt;TON's gas and storage model is not EVM with different names. The differences are structural, and each one has a direct consequence for a contract that holds money:&lt;/p&gt;

&lt;p&gt;Storage costs rent. Design so that rent never eats user funds, and prefer per-item contracts so rent stays isolated.&lt;br&gt;
Outbound messages cost gas. Collect that gas budget up front, keep it conceptually separate from the deposit, and bound it on both ends.&lt;br&gt;
Send modes are correctness. Pay fees separately on payouts, and choose modes that fail loudly rather than leak quietly.&lt;/p&gt;

&lt;p&gt;None of this is exotic once you internalize that a TON contract is a living actor with its own balance and its own upkeep, not a static object that lives for free. But almost none of it is what the EVM mental model prepares you for, and the gap is exactly where the quiet, money-losing bugs live.&lt;/p&gt;




&lt;p&gt;I build &lt;a href="https://www.novacont.tech/" rel="noopener noreferrer"&gt;NovaCont&lt;/a&gt;, a non-custodial escrow protocol on Base and TON. Contract addresses and security notes are public. &lt;a href="https://novacont.gitbook.io/nova-docs" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>smartcontract</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why I Split My Escrow Into Two Contracts</title>
      <dc:creator>Nova</dc:creator>
      <pubDate>Mon, 03 Aug 2026 01:51:47 +0000</pubDate>
      <link>https://dev.to/novacont/why-i-split-my-escrow-into-two-contracts-583n</link>
      <guid>https://dev.to/novacont/why-i-split-my-escrow-into-two-contracts-583n</guid>
      <description>&lt;p&gt;When I started building an on-chain escrow protocol, the obvious design was one contract. Funds go in, parties interact, disputes get resolved, funds come out. One deployment, one address, one place to reason about. It is the design almost every tutorial reaches for, and for a lot of use cases it is fine.&lt;/p&gt;

&lt;p&gt;I did not ship that. The escrow logic and the dispute-resolution logic live in two separate contracts that talk to each other across a deliberately narrow interface. This post is about why, because the reasoning generalizes well beyond escrow, and because "just put it all in one contract" is a decision most people make by default rather than on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing an escrow contract is actually for
&lt;/h2&gt;

&lt;p&gt;An escrow contract has one job that matters above all others: hold funds and release them under exactly the agreed conditions. Everything else is secondary to that. If the release logic is correct, the product works. If the release logic is wrong, someone loses money that was supposed to be safe.&lt;/p&gt;

&lt;p&gt;So the code that holds custody of funds should be the code you trust most, which means it should be the code that is smallest, simplest, and easiest to audit. Every additional line in that contract is another line that can contain the bug that drains it. The custody logic wants to be boring.&lt;/p&gt;

&lt;p&gt;Dispute resolution is the opposite of boring. It involves roles, assignment, voting or review, timeouts, counter-parties paying fees, evidence handling, edge cases on top of edge cases. It is inherently the most complex and most frequently changed part of the system, because arbitration is where all the messy human disagreement lands.&lt;/p&gt;

&lt;p&gt;Put those two facts next to each other and the tension is obvious. The part that must be simplest to keep funds safe is coupled to the part that is inherently the most complex and volatile. If they live in the same contract, the complexity of the second contaminates the safety of the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blast radius
&lt;/h2&gt;

&lt;p&gt;The security concept that made the decision for me is blast radius: when something goes wrong in one component, how far can the damage reach?&lt;/p&gt;

&lt;p&gt;In a single-contract design, the blast radius of a dispute-logic bug is the entire contract, including the custody of every escrow it holds. A flaw in how you handle, say, evidence submission or fee accounting during a dispute is not contained to disputes. It sits in the same storage, behind the same functions, with the same access to the same funds. The most complex, most-edited code in the system has a direct path to the money.&lt;/p&gt;

&lt;p&gt;Splitting the contracts draws a hard boundary through the middle of that. The escrow contract holds the funds and exposes a small, fixed surface for settling a dispute. The dispute contract does all the messy arbitration work and, at the end, tells the escrow contract the outcome. A bug in the arbitration contract can produce a wrong decision, which is bad, but it cannot reach in and move funds outside the escrow contract's own release rules. Two contracts, two blast radii, and the smaller one is wrapped around the money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interface is the whole point
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxb2tfxpbi5uiadufbu5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxb2tfxpbi5uiadufbu5r.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The value of the split lives entirely in how narrow the boundary between the two contracts is. A wide, chatty interface would throw the benefit away, because you would just be spreading one tangled system across two addresses.&lt;/p&gt;

&lt;p&gt;So the escrow contract exposes the minimum. The dispute contract can create a case and, when a case concludes, report a result. That is close to the entire surface. On the escrow side, the calls that the dispute system is allowed to make are gated behind a single hard check: the caller must be the known dispute contract, and the dispute system must be active. Anything else is rejected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;modifier onlyJuryContract() {
    require(msg.sender == juryContract, "Only NovaJury");
    require(isJurySystemActive, "Jury system not active");
    _;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That modifier is the entire trust relationship between the two contracts, written down in two lines. The escrow contract does not trust the dispute contract to be correct about arbitration. It only trusts it to be the one specific address allowed to report an outcome, and only while the dispute system is switched on. Everything the dispute contract does internally, all its complexity, stays on its own side of that line.&lt;/p&gt;

&lt;p&gt;Narrow interfaces are not a style preference here. The narrower the boundary, the smaller the surface an attacker or a bug can use to cross from the volatile contract into the one holding funds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unexpected benefit: I can evolve one without risking the other
&lt;/h2&gt;

&lt;p&gt;The split was a security decision, but it paid off somewhere I did not plan for: change.&lt;/p&gt;

&lt;p&gt;Arbitration is the part of the system I most expect to revise. My dispute mechanism might start simple and later move to something more decentralized, with independent participants instead of a designated reviewer. In a single-contract world, every one of those changes is a change to the same contract that holds live funds, which is exactly the contract you least want to keep redeploying and re-auditing.&lt;/p&gt;

&lt;p&gt;With the split, the escrow contract can stay stable while the dispute side evolves. The escrow side even carries a switch: dispute resolution can be routed to a designated resolver, or activated to hand off to the separate arbitration contract, without touching the custody logic. The most volatile part of the product can iterate behind a fixed interface, while the part holding the money stays put. Stability where it protects funds, flexibility where the requirements actually move.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you do not need this
&lt;/h2&gt;

&lt;p&gt;I want to be honest about the tradeoffs, because splitting is not free. Two contracts mean more deployment complexity, a cross-contract call path to reason about, and an interface you have to get right. For a simple escrow with no dispute mechanism at all, or a throwaway prototype, one contract is the correct call and the split is over-engineering.&lt;/p&gt;

&lt;p&gt;The split earns its cost specifically when you have a component that is both complex and volatile sitting next to a component that must be simple and stable. Escrow plus arbitration is a textbook case: custody wants to be boring and fixed, arbitration wants to be rich and changing. Any time you find that pattern, a component that must be trusted coupled to a component that must be flexible, the boundary is worth drawing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general principle
&lt;/h2&gt;

&lt;p&gt;Strip away the escrow specifics and the rule is this: keep the code that holds value simpler than the code that makes decisions about it, and put a hard, narrow boundary between them.&lt;/p&gt;

&lt;p&gt;You see the same idea everywhere once you look for it. Privilege separation in operating systems. Keeping keys in an HSM instead of in application memory. Microservice boundaries drawn around the data that matters most. It is all the same move: shrink the trusted core, wrap it in a minimal interface, and keep the complicated, fast-changing stuff on the outside where its blast radius is contained.&lt;/p&gt;

&lt;p&gt;For escrow, that meant two contracts instead of one. The money lives in the boring one.&lt;/p&gt;




&lt;p&gt;I build &lt;a href="https://www.novacont.tech/" rel="noopener noreferrer"&gt;NovaCont&lt;/a&gt;, a non-custodial escrow protocol on Base and TON. Contract addresses and security notes are public. &lt;a href="https://www.novacont.tech/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>crypto</category>
      <category>security</category>
    </item>
    <item>
      <title>I Built the Same Escrow on Two Chains. The Architectures Couldn't Be More Different.</title>
      <dc:creator>Nova</dc:creator>
      <pubDate>Sun, 02 Aug 2026 06:17:32 +0000</pubDate>
      <link>https://dev.to/novacont/i-built-the-same-escrow-on-two-chains-the-architectures-couldnt-be-more-different-2b9b</link>
      <guid>https://dev.to/novacont/i-built-the-same-escrow-on-two-chains-the-architectures-couldnt-be-more-different-2b9b</guid>
      <description>&lt;p&gt;I maintain a non-custodial escrow protocol that runs on two blockchains: Base, an Ethereum L2, and TON, the chain behind Telegram. Same product, same core logic on paper: lock funds, deliver work, release on approval, handle disputes.&lt;/p&gt;

&lt;p&gt;I expected the second implementation to be a port. It wasn't. The two chains disagree so fundamentally about how a contract should be structured that writing the same feature twice forced me to rethink what "the same" even means. This post is about those differences, the design decisions each model pushed me toward, and what I'd tell anyone about to make the same jump.&lt;/p&gt;

&lt;p&gt;Light on code, heavy on the reasoning. The interesting part was never the syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two mental models
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyxg9v5u9um07wlrjlmi9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyxg9v5u9um07wlrjlmi9.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The single most important difference is not the language. It's the execution model underneath.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Base&lt;/strong&gt;, a contract is an object with shared state. You write Solidity, and it behaves like a class instance sitting in memory that everyone calls into. A user calls a function, the function reads and writes contract storage synchronously, and either the whole thing succeeds or it reverts atomically. All my escrows live in one contract, in one big mapping, and every call reaches straight into that shared state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On TON&lt;/strong&gt;, a contract is an actor that receives messages. You write Tact, and it behaves like an isolated process with a mailbox. You don't call a function; you send a message, and the contract handles it in its own turn. There is no synchronous cross-contract call in the EVM sense. Interaction between contracts is asynchronous message passing, and you design around that or you fight it the whole way.&lt;/p&gt;

&lt;p&gt;The shape of the entry point tells the whole story. On Base, an external caller invokes a named function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function approveWork(uint256 _contractId)
    external validContract(_contractId) nonReentrant whenNotPaused
{
    // reads and writes shared contract storage, synchronously
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On TON, nothing "calls" the contract. A message arrives, and a handler consumes it in the actor's own turn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receive(msg: ClaimTimeout) {
    let escrow: EscrowData = self.requireEscrow(msg.escrowId);
    require(context().sender == escrow.provider, "Only provider can claim timeout");
    // ... handled to completion, no synchronous caller waiting on the stack
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything downstream flows from this one distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reentrancy: a problem on one chain, a non-problem on the other
&lt;/h2&gt;

&lt;p&gt;On Base, the first thing I reached for was ReentrancyGuard. Every state-changing function that moves money carries the guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract NovaCont is ReentrancyGuard, Ownable2Step, Pausable {
    // every fund-moving function is marked nonReentrant:
    //   ... external payable whenNotPaused nonReentrant
    //   ... external validContract(id) nonReentrant whenNotPaused
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not optional hygiene in the EVM world; the synchronous call model is exactly what makes reentrancy possible. When your contract calls out to transfer funds, control can re-enter before your state settles. The entire discipline of "checks-effects-interactions," pull-payment patterns, and reentrancy guards exists to tame that one property.&lt;/p&gt;

&lt;p&gt;On TON, I went looking for the equivalent guard and slowly realised I didn't need one. The actor model doesn't have the synchronous re-entry surface in the same shape. A message is handled to completion in its turn; an outbound transfer is itself just another message queued for later, not a synchronous callout that hands control to attacker code mid-execution. The reentrancy attack that dominates EVM security thinking simply doesn't map onto the model the same way.&lt;/p&gt;

&lt;p&gt;That was the first moment the "port" mentality broke. A whole category of defensive code on one chain was answering a question the other chain doesn't ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  How funds leave the contract
&lt;/h2&gt;

&lt;p&gt;This difference is subtle and it shaped both designs.&lt;/p&gt;

&lt;p&gt;On Base I use a pull-payment pattern. When someone is owed money, I don't push it to them inside the function. I credit a balance they withdraw later, in a separate transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// settlement doesn't send; it credits a balance&lt;/span&gt;
&lt;span class="nx"&gt;pendingWithdrawals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// the recipient later calls withdraw() themselves&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is deliberate. Pushing funds to an arbitrary address inside your logic hands control to that address, which reintroduces exactly the reentrancy and gas-griefing risks you're trying to avoid. Pull-payment isolates the value transfer from the state change. It's more steps for the user, but it's the safe EVM idiom, and withdrawal even stays available while the contract is paused, because a pause should stop new agreements, not trap funds people already earned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On TON&lt;/strong&gt;, sending is just emitting a message, so the calculus changes. A payout is dispatched inline as part of settling, and because that send is itself a queued message rather than a synchronous callout, it doesn't hand control to the recipient's code mid-execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SendParameters&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;escrow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;providerNet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SendPayGasSeparately&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;SendBounceIfActionFail&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pressure that makes push-payment dangerous on the EVM is largely absent. The design leans into sending directly, with the gas budget for those outbound messages accounted for explicitly at creation time rather than assumed.&lt;/p&gt;

&lt;p&gt;Same goal, "get funds to the right party safely," and the safe path is shaped differently on each chain because the danger is shaped differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  One contract vs many
&lt;/h2&gt;

&lt;p&gt;Here's where the models pull hardest in opposite directions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Base rewards centralisation&lt;/strong&gt;. Because state is shared and calls are synchronous, putting every escrow in one contract with one mapping is natural and cheap to reason about. One deployment, one address, one place for the dispute logic to reach into. The cost, and it is a real cost I carry, is that all state accumulates in a single contract, and per-transaction cost and storage rent grow with it. But the model makes the centralised design the path of least resistance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TON rewards the opposite&lt;/strong&gt;. The idiomatic TON design is a contract per item: a factory that spawns a child contract for each escrow, each an actor with its own storage and its own lifecycle. This is not a stylistic preference; it's how the chain wants to scale, because isolated actors parallelise where shared state serialises. I did not start there, and the single-contract approach is the sharpest architectural debt in the TON build precisely because it runs against the grain of the platform.&lt;/p&gt;

&lt;p&gt;The lesson: the "obvious" structure on one chain is the anti-pattern on the other. Centralised is comfortable on Base and a scaling ceiling on TON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separating arbitration from escrow
&lt;/h2&gt;

&lt;p&gt;One decision did survive the crossing, and it's worth calling out because it's chain-independent.&lt;/p&gt;

&lt;p&gt;On Base, dispute resolution lives in a separate contract, an independent arbitration system that the escrow contract talks to across a tightly controlled interface, rather than being welded into the escrow logic itself. The reasoning is isolation: a bug or an exploit in the dispute machinery shouldn't be able to reach into and compromise the custody of funds in escrow. Two contracts, two blast radii.&lt;/p&gt;

&lt;p&gt;That principle, keep the thing that holds the money simpler than the thing that makes judgments about it, held up on both chains, even though the two express "another contract" so differently (a synchronous interface on one, message passing on the other). When a design principle survives a paradigm shift intact, that's usually a sign it was a real principle and not just a local convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone about to do this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't port. Re-derive.&lt;/strong&gt; The temptation is to translate your Solidity into Tact line by line. Resist it. Start from the execution model and ask what the right structure is on the target chain. Half of what you wrote on the first chain is answering questions the second chain doesn't have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let the danger model define the safe pattern.&lt;/strong&gt; Reentrancy guards, pull-payments, and reentrancy-driven ordering are EVM answers to EVM threats. On a different model, some of that is dead weight, and a different set of concerns (message ordering, gas accounting for outbound messages, storage rent) takes its place. Learn the new threat model before you copy the old defenses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep custody boundaries simple, everywhere&lt;/strong&gt;. The one thing worth holding constant across chains is that the code holding funds should be as small and as boring as possible, with the complex logic pushed to the side. That travels.&lt;/p&gt;

&lt;p&gt;Two chains, two paradigms, one product. The parts I thought would transfer (the defensive patterns) mostly didn't, and the part I almost undervalued (a clean separation between custody and judgment) turned out to be the only thing that was truly portable.&lt;/p&gt;




&lt;p&gt;I build &lt;a href="https://www.novacont.tech/" rel="noopener noreferrer"&gt;NovaCont&lt;/a&gt;, a non-custodial escrow protocol on Base and TON. Contract addresses and security notes are public. &lt;a href="https://novacont.gitbook.io/nova-docs" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>security</category>
    </item>
    <item>
      <title>You Delivered the Work. The Client Vanished. Here's How to Stop That Happening Again.</title>
      <dc:creator>Nova</dc:creator>
      <pubDate>Sat, 01 Aug 2026 04:13:43 +0000</pubDate>
      <link>https://dev.to/novacont/you-delivered-the-work-the-client-vanished-heres-how-to-stop-that-happening-again-289e</link>
      <guid>https://dev.to/novacont/you-delivered-the-work-the-client-vanished-heres-how-to-stop-that-happening-again-289e</guid>
      <description>&lt;p&gt;You sent the final files. The client said "looks great, will send payment tomorrow." Tomorrow became next week. Next week became silence. Then the account was gone.&lt;/p&gt;

&lt;p&gt;If you have freelanced for any length of time, you have either lived this or watched it happen to someone in your network. It is one of the most common and least talked about risks in independent work: you carry all the delivery risk, and the client carries none.&lt;/p&gt;

&lt;p&gt;This article is about why that happens, what actually protects you, and where each option falls short. It is not a pitch. Some of the best protection costs nothing. But you should understand the full picture before you take on your next client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why freelancers keep absorbing the risk
&lt;/h2&gt;

&lt;p&gt;The core problem is structural. In most freelance arrangements, the work is delivered before the money moves. That single fact puts the entire risk on you.&lt;/p&gt;

&lt;p&gt;A client who wants to disappear has a simple playbook: agree enthusiastically, extract the deliverable, then stop responding. By the time you realise what has happened, they already have what they wanted, and you are left chasing an invoice that no one intends to pay.&lt;/p&gt;

&lt;p&gt;This is worse in cross-border work. Your client might be in another country, under another legal system, using a name you never verified. Small claims court is not a realistic option when the amount is a few hundred dollars and the defendant is on another continent. The cost of pursuing the money exceeds the money itself, and non-paying clients know it.&lt;/p&gt;

&lt;p&gt;Payment platforms help in some cases, but they created new gaps of their own. A client can file a chargeback weeks after approving your work. A marketplace can freeze a disputed balance and leave you waiting. Direct bank transfers and crypto payments are fast, but they are also final in the wrong direction: once you have delivered and the client has not paid, there is no button that pulls the money back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The protections that actually exist
&lt;/h2&gt;

&lt;p&gt;There is no single perfect answer. What works is layering a few of these, matched to the size and risk of the job.&lt;/p&gt;

&lt;p&gt;Upfront deposits. Asking for 30 to 50 percent before starting is the oldest protection in the trade, and still one of the best. It filters out clients who never intended to pay, because someone planning to disappear rarely funds the work first. The limit is obvious: the back half of your fee is still exposed, and on a large project that is a lot of exposure.&lt;/p&gt;

&lt;p&gt;Milestone payments. Break the work into stages, get paid at the end of each. This caps your loss to a single milestone rather than the whole project. It works well for larger engagements, but it adds overhead, and a determined client can still take the last milestone's work and vanish.&lt;/p&gt;

&lt;p&gt;Platform escrow. Marketplaces like Upwork and Fiverr hold the client's funds and release them on approval. This is genuinely useful and, for many freelancers, enough. The trade-offs are the platform fee, the requirement that both sides stay inside that platform, and the fact that dispute outcomes are decided entirely by the platform on its own terms.&lt;/p&gt;

&lt;p&gt;Written contracts. A clear scope, payment terms, and a signature give you legal standing. This is worth doing on every serious job. But a contract is only as strong as your ability to enforce it, and enforcement across borders for small sums is often impractical.&lt;/p&gt;

&lt;p&gt;Escrow as its own tool. This is the piece most freelancers overlook, and it is worth understanding on its own, because it is the only option on this list that neutralises the core problem directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What escrow actually does
&lt;/h2&gt;

&lt;p&gt;Escrow solves the delivery-before-payment problem by removing the moment of trust entirely.&lt;/p&gt;

&lt;p&gt;Instead of the client promising to pay after delivery, the client places the funds with a neutral third party before you start. You do the work knowing the money already exists and is committed. When you deliver and the client approves, the funds are released to you. If the client simply disappears at that point, a good escrow arrangement still releases the money to you after a set waiting period, precisely so that client silence cannot trap your payment.&lt;/p&gt;

&lt;p&gt;Notice what this changes. The client can no longer extract your work and then decide whether to pay, because the decision to fund was already made, before any work existed. The incentive to disappear is gone, because disappearing no longer keeps the money from you.&lt;/p&gt;

&lt;p&gt;The historical catch with escrow was that you had to trust the escrow agent. You were no longer trusting the client, but you were now trusting whoever held the funds not to run off with them or to rule unfairly in a dispute. For large transactions handled by established escrow companies, that trust is reasonable. For a three hundred dollar logo project, hiring a traditional escrow service was never realistic.&lt;/p&gt;

&lt;p&gt;This is the gap that smart contract escrow was built to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart contract escrow, in plain terms
&lt;/h2&gt;

&lt;p&gt;A smart contract is a small program that runs on a blockchain and does exactly what its code says, no more and no less. In an escrow contract, the funds are held by the code itself, not by a company or a person.&lt;/p&gt;

&lt;p&gt;That distinction matters. When the funds sit in a smart contract, no operator can spend them, freeze them, or quietly walk away with them, because the operator was never holding them in the first place. The rules for releasing the money, on approval, after a timeout, or through a dispute process, are written into the contract before anyone commits a cent, and they execute the same way every time.&lt;/p&gt;

&lt;p&gt;The result is escrow without needing to trust an escrow agent. You trust code that anyone can read, rather than a middleman you have to take on faith. For small, cross-border, one-off freelance jobs, the kind where traditional escrow was never worth the effort, this is a genuine shift.&lt;/p&gt;

&lt;p&gt;It is not magic, and it is worth being clear about the limits. You still have to describe the work clearly, because a contract cannot judge quality it was never told to expect. Disputes still need a resolution mechanism, and different services handle that differently, some with a support team, some with independent jurors. And the funds are usually held in a cryptocurrency, which means you are exposed to that asset's price while the job runs. These are real considerations, not dealbreakers, but you should go in with your eyes open.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose protection for your next job
&lt;/h2&gt;

&lt;p&gt;Match the tool to the risk.&lt;/p&gt;

&lt;p&gt;For a small job with a client you already trust, an upfront deposit and a clear written scope are often enough. For a larger project, milestone payments cap your exposure stage by stage. When you are working with someone new, across a border, or outside any marketplace, that is exactly where escrow earns its place, because it removes the trust problem instead of just managing it.&lt;/p&gt;

&lt;p&gt;The one thing you should stop doing is carrying all the risk yourself by default. Delivering first and hoping the client pays is not a payment method. It is a bet, and you are the only one who loses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where NovaCont fits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft867e1h43q1v07l56stv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft867e1h43q1v07l56stv.gif" alt=" " width="759" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We built &lt;a href="https://www.novacont.tech/" rel="noopener noreferrer"&gt;NovaCont&lt;/a&gt; because this problem was worth solving properly. It is a non-custodial smart contract escrow: the client locks the funds before you begin, you deliver, and the funds release on approval. Neither side, and not us, can unilaterally move money that is locked in the contract. If a client goes silent after you deliver, you can claim the funds after the review period. If there is a genuine disagreement, there is a dispute process, and a backstop that settles the contract even if no decision is reached, so your money is never trapped indefinitely.&lt;/p&gt;

&lt;p&gt;There are two versions. NovaCont runs on Base for web, and NovaCont Lite runs on TON as a Telegram Mini App, for deals that happen inside Telegram with no setup. The fee is small and only applies when you actually get paid.&lt;/p&gt;

&lt;p&gt;It is one option among the several in this article, and for the right job it is a good one. Whatever you choose, choose something. The days of delivering first and hoping should be behind you.&lt;/p&gt;

</description>
      <category>security</category>
      <category>crypto</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
