<?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: bolt_util</title>
    <description>The latest articles on DEV Community by bolt_util (@bolt_util).</description>
    <link>https://dev.to/bolt_util</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%2F4102085%2F042d9110-c928-4af6-bee8-169765b695b6.jpg</url>
      <title>DEV Community: bolt_util</title>
      <link>https://dev.to/bolt_util</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bolt_util"/>
    <language>en</language>
    <item>
      <title>How to Handle Underpaid USDT Orders Without Auto-Settling the Wrong Payment</title>
      <dc:creator>bolt_util</dc:creator>
      <pubDate>Mon, 31 Aug 2026 10:43:34 +0000</pubDate>
      <link>https://dev.to/bolt_util/how-to-handle-underpaid-usdt-orders-without-auto-settling-the-wrong-payment-4pp</link>
      <guid>https://dev.to/bolt_util/how-to-handle-underpaid-usdt-orders-without-auto-settling-the-wrong-payment-4pp</guid>
      <description>&lt;p&gt;A customer creates an order for &lt;strong&gt;1.01 USDT&lt;/strong&gt;, but accidentally transfers &lt;strong&gt;1.00 USDT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The payment is real and confirmed on-chain, but a strict payment gateway cannot complete the order because the received amount does not equal the expected amount.&lt;/p&gt;

&lt;p&gt;Automatically accepting it sounds convenient. It is also dangerous.&lt;/p&gt;

&lt;p&gt;If multiple open orders use the same wallet or have similar amounts, loose matching logic can settle the wrong order, trigger the wrong webhook, and make the accounting problem much harder to reverse.&lt;/p&gt;

&lt;p&gt;While building BoltUtil, I designed the underpayment flow around one principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The system may find evidence and suggest a match, but it must never guess which order should be completed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why amount-only matching is unsafe
&lt;/h2&gt;

&lt;p&gt;A transfer amount is not a unique order identifier.&lt;/p&gt;

&lt;p&gt;Imagine three customers creating orders around the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order A: 1.01 USDT&lt;/li&gt;
&lt;li&gt;Order B: 1.00 USDT&lt;/li&gt;
&lt;li&gt;Order C: 1.02 USDT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A transfer of 1.00 USDT arrives at the merchant wallet.&lt;/p&gt;

&lt;p&gt;Matching it to the closest amount could select Order B, even if the transfer was actually an underpayment for Order A.&lt;/p&gt;

&lt;p&gt;The same problem becomes more serious when a wallet processes many concurrent payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The matching conditions
&lt;/h2&gt;

&lt;p&gt;A transfer becomes a reconciliation candidate only when all important attributes agree:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blockchain network&lt;/li&gt;
&lt;li&gt;Token contract or asset&lt;/li&gt;
&lt;li&gt;Receiving wallet&lt;/li&gt;
&lt;li&gt;Order creation and expiration window&lt;/li&gt;
&lt;li&gt;Transfer confirmation state&lt;/li&gt;
&lt;li&gt;Allowed absolute and percentage difference&lt;/li&gt;
&lt;li&gt;Transfer and order processing state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After filtering, the system checks how many safe candidates remain.&lt;/p&gt;

&lt;p&gt;If there is exactly one candidate, a reconciliation case can be created.&lt;/p&gt;

&lt;p&gt;If there are zero candidates, the transfer remains unmatched.&lt;/p&gt;

&lt;p&gt;If there is more than one candidate, the system refuses to choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve the blockchain evidence first
&lt;/h2&gt;

&lt;p&gt;The original transfer should be stored before reconciliation begins.&lt;/p&gt;

&lt;p&gt;Useful evidence includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction hash&lt;/li&gt;
&lt;li&gt;Network&lt;/li&gt;
&lt;li&gt;From and to addresses&lt;/li&gt;
&lt;li&gt;Raw token amount&lt;/li&gt;
&lt;li&gt;Decimal-adjusted amount&lt;/li&gt;
&lt;li&gt;Block number&lt;/li&gt;
&lt;li&gt;Confirmation state&lt;/li&gt;
&lt;li&gt;First observation time&lt;/li&gt;
&lt;li&gt;Matching status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the process auditable and prevents the matching feature from changing or replacing the original on-chain facts.&lt;/p&gt;

&lt;p&gt;A unique transaction constraint also helps prevent the same transfer from being processed twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  A candidate is not a completed payment
&lt;/h2&gt;

&lt;p&gt;Even after the system finds one safe candidate, it does not automatically complete the order.&lt;/p&gt;

&lt;p&gt;Instead, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a reconciliation case.&lt;/li&gt;
&lt;li&gt;Links the transfer evidence and candidate order.&lt;/li&gt;
&lt;li&gt;Notifies the merchant.&lt;/li&gt;
&lt;li&gt;Shows the expected amount, received amount and difference.&lt;/li&gt;
&lt;li&gt;Lets the merchant accept or reject the case.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only an explicit merchant decision can settle the order.&lt;/p&gt;

&lt;p&gt;Every decision is recorded so the result can be reviewed later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting the existing payment flow
&lt;/h2&gt;

&lt;p&gt;The reconciliation path should be separate from the normal exact-payment path.&lt;/p&gt;

&lt;p&gt;A correctly paid order continues through the standard process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect the confirmed transfer.&lt;/li&gt;
&lt;li&gt;Match the exact order.&lt;/li&gt;
&lt;li&gt;Complete the order.&lt;/li&gt;
&lt;li&gt;Deliver the signed webhook.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The underpayment logic runs only when normal matching does not complete the payment. It therefore should not delay or replace exact payment processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production validation matters
&lt;/h2&gt;

&lt;p&gt;The core monitoring and reconciliation architecture supports multiple networks, but each network still needs a minimal real-payment end-to-end test before being described as fully production-proven.&lt;/p&gt;

&lt;p&gt;For my current deployment, the TRC20 payment flow has processed roughly 50 real completed orders. The remaining supported networks are being validated separately with real transfers, confirmation checks, order completion and webhook verification.&lt;/p&gt;

&lt;p&gt;Unit tests and RPC connectivity checks are useful, but they are not substitutes for real payment acceptance testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;This approach does not make underpayments magically correct.&lt;/p&gt;

&lt;p&gt;It does something more useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finds a possible relationship safely&lt;/li&gt;
&lt;li&gt;Preserves the original blockchain evidence&lt;/li&gt;
&lt;li&gt;Rejects ambiguous matches&lt;/li&gt;
&lt;li&gt;Prevents automatic settlement&lt;/li&gt;
&lt;li&gt;Gives the merchant the final decision&lt;/li&gt;
&lt;li&gt;Keeps the normal payment path unchanged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am building this flow into &lt;a href="https://boltutil.com/" rel="noopener noreferrer"&gt;BoltUtil&lt;/a&gt;, a non-custodial crypto payment API where funds go directly to the merchant wallet.&lt;/p&gt;

&lt;p&gt;I would be interested to hear how other payment developers handle small underpayments, especially when several orders share the same receiving wallet.&lt;/p&gt;

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