<?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: Azalea</title>
    <description>The latest articles on DEV Community by Azalea (@azaleakuts).</description>
    <link>https://dev.to/azaleakuts</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%2F3979286%2Fa8f84aca-1b3a-4612-ae34-63b30587e774.png</url>
      <title>DEV Community: Azalea</title>
      <link>https://dev.to/azaleakuts</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/azaleakuts"/>
    <language>en</language>
    <item>
      <title>Private In, Public Out: What an XMR to ETH Swap Looks Like From the Inside</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Tue, 20 Oct 2026 22:46:07 +0000</pubDate>
      <link>https://dev.to/azaleakuts/private-in-public-out-what-an-xmr-to-eth-swap-looks-like-from-the-inside-2fei</link>
      <guid>https://dev.to/azaleakuts/private-in-public-out-what-an-xmr-to-eth-swap-looks-like-from-the-inside-2fei</guid>
      <description>&lt;p&gt;Last week a privacy coin had a bad few days. On October 14, Pirate Chain found an exploit that let an attacker mint about 39.8 million ARRR out of thin air, roughly a fifth of the supply. The new coins went straight into the shielded pool, where the chain's mandatory privacy makes them impossible to fully trace. The team &lt;a href="https://www.cryptotimes.io/2026/10/16/pirate-chain-exploit-mints-39-8m-arrr-rollback-plan-dropped/" rel="noopener noreferrer"&gt;dropped its rollback plan&lt;/a&gt; and asked exchanges and pools to pause.&lt;/p&gt;

&lt;p&gt;That's the dramatic version of a trade-off every privacy chain lives with. Privacy hides the user, and it can also hide the problem.&lt;/p&gt;

&lt;p&gt;Most people will never touch an exploit like that. What they do touch is a much more boring moment: moving value out of a private chain and into a public one. Swapping Monero for Ether is the most common example. If you build wallets, payment flows or anything with a "Send" button, that boundary has its own timing, its own costs and a couple of states that most interfaces never show.&lt;/p&gt;

&lt;p&gt;Here's what happens inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ledgers with opposite defaults
&lt;/h2&gt;

&lt;p&gt;Monero hides the sender, the receiver and the amount by default. Ethereum shows all three to anyone with a block explorer.&lt;/p&gt;

&lt;p&gt;So an XMR to ETH swap is a one-way door. Whatever happens on the Monero side stays fairly private. Whatever happens after the ETH lands is public forever: the receiving address, every transfer it makes afterward, and every contract it touches.&lt;/p&gt;

&lt;p&gt;I wrote earlier about &lt;a href="https://dev.to/azaleakuts/what-a-public-crypto-wallet-can-reveal-4kjm"&gt;what a public crypto wallet can reveal&lt;/a&gt;. The short version applies here too. If the ETH address is already linked to your name through an exchange account or an ENS name, the swapped ETH is linked to you as well. Privacy on the way in doesn't carry over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the time goes
&lt;/h2&gt;

&lt;p&gt;People expect the swap itself to be the slow part. It isn't. Most of the waiting happens on the Monero side, before any exchange runs.&lt;/p&gt;

&lt;p&gt;Three things set the clock:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your own balance. Monero locks newly received coins for 10 blocks before they can be spent. If you got the XMR twenty minutes ago, you may not be able to send it yet.&lt;/li&gt;
&lt;li&gt;Block pace. Monero produces a block &lt;a href="https://www.getmonero.org/resources/moneropedia/block.html" rel="noopener noreferrer"&gt;about every 2 minutes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The service's threshold. Each swap service decides how many confirmations it waits for before accepting a deposit. Five confirmations is about ten minutes. Twenty is forty minutes or more. Large or unusual orders often get a higher threshold.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Ethereum side rarely slows anything down. Ethereum runs on &lt;a href="https://ethereum.org/developers/docs/consensus-mechanisms/pos" rel="noopener noreferrer"&gt;12-second slots&lt;/a&gt;, so once the payout is sent, it shows up quickly.&lt;/p&gt;

&lt;p&gt;For builders, this has a simple consequence: a spinner is the wrong UI here. Users wait more calmly when they see "3 of 5 confirmations" than when they stare at a loading animation for twelve minutes with no idea whether anything is happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the money goes
&lt;/h2&gt;

&lt;p&gt;There's no single fee in an XMR to ETH swap. There are four:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Monero network fee on your deposit, which you pay from your own wallet;&lt;/li&gt;
&lt;li&gt;the provider's trading costs with its liquidity partners;&lt;/li&gt;
&lt;li&gt;the spread, the gap between the market price and the rate you're offered;&lt;/li&gt;
&lt;li&gt;the Ethereum network fee on the payout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On most instant swap services, only the first one is visible to you as a separate charge. The rest is folded into the quote. This &lt;a href="https://coinmarketcal.com/news/xmr-to-eth-exchange-fees-compared-what-to-expect-in-2026" rel="noopener noreferrer"&gt;breakdown of XMR to ETH exchange fees&lt;/a&gt; puts it well: the number worth comparing is the ETH you actually receive, not the headline rate.&lt;/p&gt;

&lt;p&gt;A few details from the protocol docs are worth knowing.&lt;/p&gt;

&lt;p&gt;Monero wallets offer &lt;a href="https://www.getmonero.org/resources/user-guides/make-payment.html" rel="noopener noreferrer"&gt;four fee priority levels&lt;/a&gt;. The fee depends on congestion and transaction size, not on how much XMR you send.&lt;/p&gt;

&lt;p&gt;On Ethereum, the fee is gas used times the base fee plus a priority fee. A plain ETH transfer uses 21,000 gas, and the base fee can move by up to 12.5% per block as demand changes (&lt;a href="https://ethereum.org/developers/docs/gas/" rel="noopener noreferrer"&gt;ethereum.org&lt;/a&gt;). That's why the same swap can cost more to pay out on a busy afternoon.&lt;/p&gt;

&lt;p&gt;Then there's the minimum amount. Network costs are roughly fixed per transaction, so on a tiny swap they eat a large share. Near the minimum, the ETH you receive can look noticeably worse than the market rate.&lt;/p&gt;

&lt;p&gt;The choice between fixed and floating rates is a trade-off too. A fixed rate locks the amount for a short window, and the provider usually builds a small buffer into it for price swings. A floating rate follows the market until execution and can land a bit above or below the estimate. On a volatile day, the certainty may be worth the buffer. On a calm day, often it isn't.&lt;/p&gt;

&lt;p&gt;For builders: show the final received amount as the main number. Show the rate second. If your quote is an estimate, say so in plain words, right next to the figure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The state nobody designs for
&lt;/h2&gt;

&lt;p&gt;Every swap flow has the happy path: waiting for deposit, confirming, exchanging, sending, done. Most also have a state that rarely shows up in mockups: on hold for a compliance review.&lt;/p&gt;

&lt;p&gt;"No account" and "no checks" are not the same thing. Account-free services still run risk-based reviews. Reports of stolen funds, unusual transactions or local law can trigger identity verification, and in some countries verification is required before any order at all.&lt;/p&gt;

&lt;p&gt;The wider rules are getting tighter, and they differ a lot by country. Dubai banned privacy coin activity in February 2023. Kraken &lt;a href="https://cointelegraph.com/news/kraken-ends-monero-support-european-economic-area" rel="noopener noreferrer"&gt;ended Monero trading and deposits&lt;/a&gt; for clients in the European Economic Area in October 2024. And from July 10, 2027, &lt;a href="https://eur-lex.europa.eu/eli/reg/2024/1624/oj" rel="noopener noreferrer"&gt;Article 79 of the EU's Anti-Money Laundering Regulation&lt;/a&gt; bars regulated crypto service providers from keeping accounts that allow anonymised transactions, including through anonymity-enhancing coins.&lt;/p&gt;

&lt;p&gt;Read that last one carefully. It applies to providers. It doesn't stop individuals from holding XMR in their own wallets. If you want the country-by-country picture, this overview of &lt;a href="https://blockspot.io/is-it-safe-legal-to-exchange-monero-for-ethereum/" rel="noopener noreferrer"&gt;whether it's safe and legal to exchange Monero for Ethereum&lt;/a&gt; collects the main rules in one table.&lt;/p&gt;

&lt;p&gt;For builders, the lesson is to treat "verification requested" as a first-class state. Say what triggered it, what the user needs to do, and how long the request stays open. A held order with a clear explanation is annoying. A held order with no explanation feels like a scam, and users will treat it like one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist if you're building this flow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Show confirmation progress as a count, not a spinner.&lt;/li&gt;
&lt;li&gt;Lead with the amount the user will receive. Put the rate second.&lt;/li&gt;
&lt;li&gt;Label estimates as estimates, and say what's already included.&lt;/li&gt;
&lt;li&gt;Warn when the amount is close to the minimum, where fees weigh most.&lt;/li&gt;
&lt;li&gt;Make the payout network impossible to miss. Mainnet, Base and Arbitrum addresses look identical.&lt;/li&gt;
&lt;li&gt;Design the compliance-hold screen before you need it.&lt;/li&gt;
&lt;li&gt;Give the user an order ID they can save, and a status page that works with nothing else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you're on the user side: send already-unlocked XMR, compare the full ETH address character by character, and assume the receiving address will be public for good. Because it will be.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>privacy</category>
      <category>ethereum</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Why Sending USDT Still Requires a Second Coin</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Fri, 16 Oct 2026 21:33:40 +0000</pubDate>
      <link>https://dev.to/azaleakuts/why-sending-usdt-still-requires-a-second-coin-gim</link>
      <guid>https://dev.to/azaleakuts/why-sending-usdt-still-requires-a-second-coin-gim</guid>
      <description>&lt;p&gt;If you've worked on a stablecoin wallet, you've probably seen this support ticket. A user has 500 USDT on TRON, presses Send, and gets an error. The balance is right there on the screen. What's missing is TRX, a coin they never bought and didn't know they needed.&lt;/p&gt;

&lt;p&gt;Every chain that carries stablecoins charges its fees in its own native token. So "send 50 USDT" is quietly a two-asset operation, and the second asset is the one users forget. My last post looked at why a swap screen shows the wrong number. This one is about the coin that never shows up on the send screen at all, until it blocks the transfer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On TRON, Solana, Stellar, TON and Ethereum, a USDT or USDC transfer costs something in the chain's native token, and each chain charges it differently.&lt;/li&gt;
&lt;li&gt;The cost often depends on the recipient. Sending USDT on TRON to an address that has never held USDT needs about twice the Energy. On Solana, a missing token account adds rent.&lt;/li&gt;
&lt;li&gt;Fee sponsorship exists on every one of these chains: TRON Energy delegation, Solana fee payers, Stellar fee-bump transactions, TON gasless transfers, Ethereum paymasters.&lt;/li&gt;
&lt;li&gt;Gasless doesn't mean free. Someone still pays the network, and the cost usually comes out of the stablecoin.&lt;/li&gt;
&lt;li&gt;A wallet should check the native balance before the user types an amount, not after they press Send.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Five chains, five billing models
&lt;/h2&gt;

&lt;p&gt;A comparison of the &lt;a href="https://www.digitalconnectmag.com/best-blockchains-for-crypto-payments/" rel="noopener noreferrer"&gt;best blockchains for crypto payments&lt;/a&gt; published this week scores TRON, Solana, TON, Stellar and Ethereum on cost per transfer, finality, stablecoin liquidity, real payment use and access. Its definition of access includes how hard it is to get the gas token. I think that criterion deserves more attention than it usually gets, because it's where first-time users get stuck.&lt;/p&gt;

&lt;p&gt;Here is what each chain actually charges for a stablecoin transfer, checked against the networks' own documentation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chain&lt;/th&gt;
&lt;th&gt;Native token&lt;/th&gt;
&lt;th&gt;What a stablecoin transfer costs&lt;/th&gt;
&lt;th&gt;What depends on the recipient&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TRON&lt;/td&gt;
&lt;td&gt;TRX&lt;/td&gt;
&lt;td&gt;About 65,000 Energy, or 6.5 TRX if burned at 100 sun per Energy&lt;/td&gt;
&lt;td&gt;About 131,000 Energy if the recipient has never held USDT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solana&lt;/td&gt;
&lt;td&gt;SOL&lt;/td&gt;
&lt;td&gt;5,000 lamports per signature, plus an optional priority fee&lt;/td&gt;
&lt;td&gt;About 0.00204 SOL rent if the recipient has no token account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stellar&lt;/td&gt;
&lt;td&gt;XLM&lt;/td&gt;
&lt;td&gt;100 stroops per operation (0.00001 XLM)&lt;/td&gt;
&lt;td&gt;The recipient needs a trustline, which locks 0.5 XLM of their reserve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TON&lt;/td&gt;
&lt;td&gt;Gram (formerly Toncoin)&lt;/td&gt;
&lt;td&gt;A small fee in Gram, or in USDT through gasless transfers&lt;/td&gt;
&lt;td&gt;Depends on the wallet, see below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethereum and L2s&lt;/td&gt;
&lt;td&gt;ETH&lt;/td&gt;
&lt;td&gt;Gas units times gas price, paid in ETH on mainnet, Base and Arbitrum alike&lt;/td&gt;
&lt;td&gt;More gas if the recipient holds none of the token yet, since a new storage slot is written&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rows don't line up, so a wallet that supports all five needs five different answers to "can this user send right now?"&lt;/p&gt;

&lt;h2&gt;
  
  
  TRON: Energy, Bandwidth and the first-time recipient
&lt;/h2&gt;

&lt;p&gt;TRON doesn't charge a gas price in the usual sense. A USDT transfer consumes two resources. Energy pays for running the token contract, and Bandwidth pays for the bytes of the transaction (&lt;a href="https://docs.getblock.io/tron-energy/tron-fee-model" rel="noopener noreferrer"&gt;GetBlock docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A transfer to an address that already holds USDT takes about 65,000 Energy, because it updates an existing storage slot. A transfer to an address that has never held USDT takes about 131,000, because the contract has to create one. If the sender has no Energy, the network burns TRX instead, at 100 sun per Energy. That works out to about 6.5 TRX for the first case and 13.1 TRX for the second.&lt;/p&gt;

&lt;p&gt;Bandwidth is easier on the user. Every account gets 600 free Bandwidth points a day, and a USDT transfer uses about 345, so one transfer a day is covered.&lt;/p&gt;

&lt;p&gt;There are cheaper routes, but none of them helps a user who has zero TRX:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Staking TRX gives Energy in proportion to the stake. Unstaking takes 14 days.&lt;/li&gt;
&lt;li&gt;Energy rental markets delegate Energy from large stakers, typically at 30 to 50 sun per unit.&lt;/li&gt;
&lt;li&gt;In February 2025 Justin Sun announced a gas-free option for sending USDT on TRON without holding TRX. Whether you can use it depends on the wallet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The doubled cost for new recipients catches people out. The amount the wallet quoted yesterday for sending to a friend can be half of what it charges today for paying someone new.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solana: cheap fees, plus rent
&lt;/h2&gt;

&lt;p&gt;Solana's base fee is 5,000 lamports per signature, or 0.000005 SOL. A priority fee on top is optional (&lt;a href="https://solana.com/docs/core/fees" rel="noopener noreferrer"&gt;Solana docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The surprise is rent. USDC and USDT on Solana live in token accounts, one per owner per token. If the recipient doesn't have one yet, the sender's transaction usually creates it, and every account must hold a minimum balance proportional to its size. The formula in Solana's docs is &lt;code&gt;(account_size + 128) * 3,480 lamports * 2 years&lt;/code&gt;. A token account is 165 bytes, so that comes to 2,039,280 lamports, about 0.00204 SOL (&lt;a href="https://solana.com/docs/core/accounts" rel="noopener noreferrer"&gt;Solana docs&lt;/a&gt;). The balance is returned when the account is closed.&lt;/p&gt;

&lt;p&gt;That is a small number, but it is about 400 times the base fee. A wallet that shows "fee: 0.000005 SOL" and then takes 0.002 more has a trust problem, even if the amount is tiny.&lt;/p&gt;

&lt;p&gt;Solana also lets a different account pay the fee for a transaction, which is how many apps sponsor their users' transfers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stellar: reserves and trustlines
&lt;/h2&gt;

&lt;p&gt;Stellar's fees are close to nothing: the network minimum is 100 stroops per operation, which is 0.00001 XLM, and it only rises when the network is congested (&lt;a href="https://developers.stellar.org/docs/learn/fundamentals/fees-resource-limits-metering" rel="noopener noreferrer"&gt;Stellar docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The catch is the minimum balance. Every account must keep two base reserves, currently 1 XLM, plus 0.5 XLM for each subentry (&lt;a href="https://developers.stellar.org/docs/learn/fundamentals/lumens" rel="noopener noreferrer"&gt;Stellar docs&lt;/a&gt;). To hold USDC, an account needs a trustline to the issuer, and that trustline is a subentry. So a wallet holding only USDC still has to keep 1.5 XLM locked.&lt;/p&gt;

&lt;p&gt;The recipient side matters even more. An account without a USDC trustline can't receive USDC at all. On TRON or Ethereum a payment to a fresh address just works. On Stellar it fails until the recipient opts in.&lt;/p&gt;

&lt;p&gt;Stellar has two built-in ways around this. Fee-bump transactions let another account pay the fee, and sponsored reserves let an app cover a user's reserves.&lt;/p&gt;

&lt;h2&gt;
  
  
  TON: gasless, with a fee in USDT
&lt;/h2&gt;

&lt;p&gt;On TON, fees are paid in the native token, which was renamed from Toncoin to Gram in June 2026. Tonkeeper's gasless mode lets a W5 wallet send USDT with no Gram in it. The user signs a message, a relayer wraps it in a transaction and pays the network, and the fee is taken from the USDT being sent (&lt;a href="https://keeperwallet.com/en/article/how-tonkeeper-brings-gasless" rel="noopener noreferrer"&gt;Tonkeeper&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Tonkeeper's own explainer is honest about this: gas doesn't disappear, it just gets paid in a different token. Its example, at a price of $7 per token, puts a gasless USDT transfer at about $0.14, against about $0.10 through its prepaid Battery. Users get convenience and pay a small premium for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ethereum and L2s: paymasters
&lt;/h2&gt;

&lt;p&gt;On Ethereum mainnet, Base and Arbitrum, gas is paid in ETH. L2s made transfers cheap, but they didn't remove the need to hold ETH on that specific network. Someone with USDC on Base and ETH only on mainnet still can't send.&lt;/p&gt;

&lt;p&gt;Two standards changed this. ERC-4337 introduced paymasters, contracts that pay gas on a user's behalf. EIP-7702, activated with the Pectra upgrade in May 2025, lets regular wallets delegate to smart contract code, so they can use paymasters too. Circle's Paymaster, for example, checks that the sender has enough USDC and pays the gas, charging the user in USDC instead of ETH (&lt;a href="https://www.circle.com/blog/how-the-pectra-upgrade-is-unlocking-gasless-usdc-transactions-with-eip-7702" rel="noopener noreferrer"&gt;Circle&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A different approach is a chain built around one stablecoin. Plasma launched in September 2025 with zero-fee USDT transfers during its initial rollout. That removes the problem for USDT on that chain, but users still have to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check before the user types an amount
&lt;/h2&gt;

&lt;p&gt;Every chain above has a fix, but the user only benefits if the wallet checks first. Here is a preflight check that returns how much of the native token a stablecoin transfer needs. It ignores some edge cases on purpose, such as TRON Bandwidth beyond the free daily allowance:&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;type&lt;/span&gt; &lt;span class="nx"&gt;SendCheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;recipientHasUsdt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;availableEnergy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;solana&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;recipientHasTokenAccount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;priorityFeeLamports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stellar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;recipientHasTrustline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;senderSubentries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;evm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;gasLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;maxFeePerGasWei&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SUN_PER_ENERGY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_ACCOUNT_RENT_LAMPORTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_039_280n&lt;/span&gt; &lt;span class="c1"&gt;// 165-byte token account&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_RESERVE_STROOPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000_000n&lt;/span&gt; &lt;span class="c1"&gt;// 0.5 XLM&lt;/span&gt;

&lt;span class="c1"&gt;// Native amount the sender must hold, in the chain's smallest unit&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nativeNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SendCheck&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;energy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipientHasUsdt&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;131&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;energy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;availableEnergy&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;energy&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;availableEnergy&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;SUN_PER_ENERGY&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;solana&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipientHasTokenAccount&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_ACCOUNT_RENT_LAMPORTS&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priorityFeeLamports&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rent&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stellar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipientHasTrustline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Recipient has no trustline for this asset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minBalance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;senderSubentries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;BASE_RESERVE_STROOPS&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;minBalance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;evm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gasLimit&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxFeePerGasWei&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few outputs to sanity-check against the table: a TRON transfer to a new recipient with no Energy returns 13,100,000 sun, or 13.1 TRX. A Solana transfer that creates a token account returns 2,044,280 lamports. A Stellar sender with one USDC trustline needs 15,000,100 stroops, about 1.5 XLM, most of which stays locked as reserve.&lt;/p&gt;

&lt;p&gt;The real work is in the inputs. &lt;code&gt;recipientHasUsdt&lt;/code&gt; and &lt;code&gt;recipientHasTokenAccount&lt;/code&gt; need a lookup on the recipient before the quote, which is easy to skip and expensive to get wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist for the send screen
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Show the native balance next to the stablecoin balance, with the network name.&lt;/li&gt;
&lt;li&gt;[ ] Run the preflight check as soon as the recipient is known, before the amount.&lt;/li&gt;
&lt;li&gt;[ ] Look up the recipient: USDT history on TRON, a token account on Solana, a trustline on Stellar.&lt;/li&gt;
&lt;li&gt;[ ] Show the total fee, including rent or reserves, in both the native token and dollars.&lt;/li&gt;
&lt;li&gt;[ ] If the user has no native token, offer the fix in place: sponsored fee, gasless mode or a small top-up.&lt;/li&gt;
&lt;li&gt;[ ] When the fee is paid in the stablecoin, say so before signing and show the amount the recipient will get.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why can't I send USDT if I have enough USDT?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the network fee is charged in the chain's native token, not in USDT. On TRON you need TRX or Energy, on Solana SOL, on Ethereum and its L2s ETH, on Stellar XLM and on TON Gram. Your USDT balance can cover the amount but not the fee. Some wallets now offer gasless sending, where a relayer or paymaster pays the network and takes the fee from your USDT, so check whether yours supports it. Otherwise, buying a small amount of the native token on the same network solves the problem for many future transfers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why did my USDT transfer on TRON cost twice as much this time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most likely the recipient had never held USDT before. Sending to such an address takes about 131,000 Energy instead of about 65,000, because the token contract has to create a new storage entry for the recipient. If you have no staked or rented Energy, the network burns TRX to cover it, so the cost roughly doubles. Later transfers to the same address go back to the normal amount. Staking TRX or renting Energy lowers the cost for both cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is a gasless transfer free?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. The network still charges a fee, and someone pays it. In gasless setups a relayer or paymaster pays the network in the native token and recovers the cost from you, usually in the stablecoin you're sending, sometimes with a small markup. Some apps cover the fee themselves as a feature and make the money back elsewhere. Gasless removes the need to hold a second coin. It doesn't remove the cost, so check the final amount the recipient gets before signing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Sending a stablecoin still means managing two balances, and most of the confusion comes from wallets that show only one of them. All the tools for hiding the second coin already exist on these five chains. What's left is wiring the check into the moment the user picks a recipient.&lt;/p&gt;

&lt;p&gt;If you've shipped gasless sending or fee sponsorship, which part took the most work: the relayer, the pricing, or explaining it to users? I'd like to compare notes in the comments.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>fintech</category>
      <category>blockchain</category>
      <category>ux</category>
    </item>
    <item>
      <title>Your Swap Screen Is Showing the Wrong Number</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Wed, 14 Oct 2026 23:10:30 +0000</pubDate>
      <link>https://dev.to/azaleakuts/your-swap-screen-is-showing-the-wrong-number-jd9</link>
      <guid>https://dev.to/azaleakuts/your-swap-screen-is-showing-the-wrong-number-jd9</guid>
      <description>&lt;p&gt;A month ago I wrote about &lt;a href="https://dev.to/azaleakuts/what-a-crypto-app-should-do-before-you-press-send-3cp5"&gt;what a crypto app should do before you press "Send"&lt;/a&gt;. There's one more thing that happens before that button, and most swap interfaces still get it wrong: the number they put in the biggest font.&lt;/p&gt;

&lt;p&gt;Usually it's the exchange rate. 1 XMR = 0.12 ETH. Clean, comparable, easy to sort.&lt;/p&gt;

&lt;p&gt;It's also the least useful figure on the screen.&lt;/p&gt;

&lt;p&gt;What lands in the user's wallet is an amount. Between the rate and that amount sit several costs the rate doesn't show.&lt;/p&gt;

&lt;h2&gt;
  
  
  One quote, four costs
&lt;/h2&gt;

&lt;p&gt;Cross-chain swaps make this easy to see, because each leg has its own costs. Take Monero to Ethereum. CoinMarketCal recently published &lt;a href="https://coinmarketcal.com/news/xmr-to-eth-exchange-fees-compared-what-to-expect-in-2026" rel="noopener noreferrer"&gt;a breakdown of XMR to ETH exchange fees&lt;/a&gt; and counted four separate costs behind a single quote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XMR network fee   → paid by the user, from their own wallet
Provider fees     → trading fees charged by liquidity sources
Spread            → gap between the market price and the offered rate
ETH payout fee    → gas to send ETH to the user's address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the first one is under the user's control. The Monero fee depends on network load and transaction size, not on the amount sent, and wallets let you choose from &lt;a href="https://www.getmonero.org/resources/user-guides/make-payment.html" rel="noopener noreferrer"&gt;four priority levels&lt;/a&gt;. Everything else is decided by the provider.&lt;/p&gt;

&lt;p&gt;The Ethereum side has its own formula. According to &lt;a href="https://ethereum.org/developers/docs/gas/" rel="noopener noreferrer"&gt;ethereum.org&lt;/a&gt;, a transaction costs gas units multiplied by the base fee plus a priority fee. A plain ETH transfer uses 21,000 gas. The base fee can move by up to 12.5% per block with demand, so the same payout can cost noticeably more at a busy moment.&lt;/p&gt;

&lt;p&gt;None of this shows up in "1 XMR = 0.12 ETH".&lt;/p&gt;

&lt;h2&gt;
  
  
  Better rate, worse result
&lt;/h2&gt;

&lt;p&gt;Here's a simple case. Two quotes for a 2 XMR swap (the numbers are illustrative):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quote A shows a rate of 0.12000 ETH, charges a 0.1% trading fee, and takes a 0.002 ETH withdrawal fee afterwards.&lt;/li&gt;
&lt;li&gt;Quote B shows 0.11925 ETH, with every cost after the deposit already included.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sort by rate and A wins. Now compute what actually arrives:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rankByReceived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quotes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;quotes&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountIn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tradingFee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fixedFees&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;rankByReceived&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tradingFee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fixedFees&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.002&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.11925&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tradingFee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fixedFees&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// [ { name: "B", received: 0.2385 }, { name: "A", received: 0.23776 } ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B pays out more. The gap is small here, but a UI that ranks by rate picks the wrong option every time this pattern appears. And it appears a lot, because "no fees" usually means the fees moved into the spread or into a step that comes later.&lt;/p&gt;

&lt;p&gt;The rate wasn't lying. It answered a question the user didn't ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small swaps hurt the most
&lt;/h2&gt;

&lt;p&gt;Network fees are roughly fixed per transaction. They don't scale with the amount, so their share grows as the swap gets smaller.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feeShare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;receivedEth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fixedFeesEth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fixedFeesEth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;receivedEth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;feeShare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.002&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ≈ 0.0083 → 0.8%&lt;/span&gt;
&lt;span class="nf"&gt;feeShare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.002&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ≈ 0.0667 → 6.7%&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same fees, eight times the impact. This is also why providers set minimum amounts: below a certain point, fees eat most of the order. A screen that shows only the rate hides this completely. A screen that shows the received amount makes it visible the moment someone types a small number.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the quote object should carry
&lt;/h2&gt;

&lt;p&gt;If I were designing the API behind a swap screen, the response would put the received amount first and say what's inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amountIn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"asset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XMR"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amountOut"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2385"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"asset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ETH"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rateMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"floating"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includedInQuote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"spread"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"provider fees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ETH payout fee"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paidSeparately"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"XMR network fee"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expiresAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-15T12:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;amountOut&lt;/code&gt; is the headline. Anyone who wants the rate can derive it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;includedInQuote&lt;/code&gt; and &lt;code&gt;paidSeparately&lt;/code&gt; matter more than they look. Two quotes are only comparable if they cover the same costs. Without these fields, the frontend has no way to tell whether it's comparing like with like.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rateMode&lt;/code&gt; tells the user what kind of promise they're getting. A floating quote follows the market until execution and can land slightly above or below the estimate. A fixed quote locks the amount for a limited window, usually with a small buffer priced in for volatility. Both are fine. Hiding which one it is isn't.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;expiresAt&lt;/code&gt; is there for fixed quotes. A locked amount with no visible deadline is a support ticket waiting to happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thin liquidity widens the gap
&lt;/h2&gt;

&lt;p&gt;For pairs with deep markets, the difference between the rate and the received amount stays small. For thinner pairs it grows, and Monero is a good example. Kraken &lt;a href="https://cointelegraph.com/news/kraken-ends-monero-support-european-economic-area" rel="noopener noreferrer"&gt;stopped XMR trading and deposits for clients in the European Economic Area&lt;/a&gt; from October 31, 2024. Fewer venues usually means wider spreads, and the spread is exactly the cost a rate-first screen never shows.&lt;/p&gt;

&lt;p&gt;So the less liquid the asset, the more misleading the headline rate gets. That's the opposite of what users need.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist for swap screens
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Lead with the amount the user will receive.&lt;/li&gt;
&lt;li&gt;Say whether that number is an estimate or a guaranteed amount, and for how long.&lt;/li&gt;
&lt;li&gt;List what's already included and what the user still pays.&lt;/li&gt;
&lt;li&gt;Flag amounts close to the minimum, where fixed fees dominate.&lt;/li&gt;
&lt;li&gt;Compare quotes by received amount, never by rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this needs new infrastructure. The provider already calculates every one of these numbers to build the quote. The only question is whether the interface passes them on.&lt;/p&gt;

&lt;p&gt;People spend what arrives in their wallet. The screen should start there.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>fintech</category>
      <category>ux</category>
    </item>
    <item>
      <title>What an Exchange’s Proof of Reserves Actually Proves</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Fri, 09 Oct 2026 15:55:49 +0000</pubDate>
      <link>https://dev.to/azaleakuts/what-an-exchanges-proof-of-reserves-actually-proves-1mn3</link>
      <guid>https://dev.to/azaleakuts/what-an-exchanges-proof-of-reserves-actually-proves-1mn3</guid>
      <description>&lt;p&gt;On September 24, 2026, attackers drained about $387.5 million from Bitget. Five days later, the snapshot for its 47th proof of reserves report put the overall reserve ratio at 131%. Withdrawals were still coming back one asset at a time.&lt;/p&gt;

&lt;p&gt;I found that pair of numbers more instructive than any explainer. The exchange had lost a large sum and was still holding more than it owed, at least at 09:00 UTC on one Tuesday. What the ratio couldn't say was how the attackers got in, or what the balance sheet would look like the following week.&lt;/p&gt;

&lt;p&gt;My last two posts dealt with a single transfer, before and after you press Send. This one is about the platform that holds the balance in the first place. If you build on exchange APIs, or simply keep coins on an exchange, you should know what a proof of reserves report covers and where it stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proof of reserves has two halves: proof of assets (the exchange controls these wallets) and proof of liabilities (this is what it owes customers). A ratio needs both.&lt;/li&gt;
&lt;li&gt;A Merkle sum tree lets each user check that their own balance was counted, without the exchange publishing everyone's balances.&lt;/li&gt;
&lt;li&gt;The classic weak spot is a fake negative balance hidden in the tree. Zero-knowledge proofs close it, which is why OKX moved to zk-STARK proofs.&lt;/li&gt;
&lt;li&gt;A report is a snapshot. It doesn't cover borrowed assets moved in for the day, fiat, off-chain liabilities or operational security.&lt;/li&gt;
&lt;li&gt;Bitget's 131% after a $387.5 million theft is a good case for seeing what a ratio shows and what it leaves out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Two halves of one number
&lt;/h2&gt;

&lt;p&gt;A reserve ratio is assets divided by liabilities, calculated per asset, and each side has to be proven separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proof of assets&lt;/strong&gt; shows that the exchange controls specific on-chain addresses. Usually it publishes the addresses and signs a message with each key. OKX, for example, provides open-source tools to verify signed "I am an OKX address" messages and to compare balances against a block-height snapshot (&lt;a href="https://www.okx.com/proof-of-reserves" rel="noopener noreferrer"&gt;OKX&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proof of liabilities&lt;/strong&gt; shows what the exchange owes its users, and it's the harder half. Publishing every customer balance would wreck privacy, so the usual approach is a Merkle sum tree, which Vitalik Buterin walked through in his 2022 post on &lt;a href="https://vitalik.eth.limo/general/2022/11/19/proof_of_solvency.html" rel="noopener noreferrer"&gt;proof of solvency&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;OKX's 47th report lists 109% for BTC, 101% for ETH, 105% for USDT and 100% for USDC. You can only read those figures as reserve ratios because assets and liabilities were proven for the same snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a Merkle sum tree proof works
&lt;/h2&gt;

&lt;p&gt;Each user becomes a leaf: a hash of a salted user identifier plus that user's balance. Every parent node stores a hash of its children and the sum of their balances, so the root ends up committing to total liabilities.&lt;/p&gt;

&lt;p&gt;The exchange publishes only the root. You get a proof made of the sibling nodes along the path from your leaf to the root. A verification tool recomputes that path, and if it lands on the published root, your balance was counted.&lt;/p&gt;

&lt;p&gt;Here is the check in TypeScript. Real exchanges use their own leaf formats, so read this as the shape of the verification rather than a drop-in tool:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;siblingOnLeft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userIdHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`leaf|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userIdHash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Negative sum in tree&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`node|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyInclusion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;siblingOnLeft&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;siblingOnLeft&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sibling&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Balances are &lt;code&gt;bigint&lt;/code&gt; in the smallest unit of the asset, since floating point has no business near money. The sum also goes into every hash, so changing a subtotal anywhere breaks every path that runs through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The negative balance trick
&lt;/h2&gt;

&lt;p&gt;Suppose an exchange holds 890 ETH but owes customers 1,390 ETH. It can add a fake account with a balance of -500 ETH, and the root will show liabilities of 890 ETH, matched exactly by reserves.&lt;/p&gt;

&lt;p&gt;In a plain Merkle sum tree, the defense is the check you see in &lt;code&gt;parent&lt;/code&gt;: a negative sum throws. A user whose path passes near the fake account sees the negative value and rejects the proof. But that only helps if someone in that branch actually runs the check, and an exchange can bet that nobody will. Buterin points out that quietly leaving those users out of the tree would work just as well for a dishonest exchange.&lt;/p&gt;

&lt;p&gt;Zero-knowledge proofs take that bet off the table. A zk proof can show that every balance in the tree is non-negative and that they add up to the claimed total, without revealing any individual balance. OKX moved its liabilities proof to zk-STARKs. For reports since September 2024, users download an inclusion proof file and run it through an open-source validator, and a separate file proves the total and the non-negative constraint (&lt;a href="https://www.okx.com/proof-of-reserves" rel="noopener noreferrer"&gt;OKX&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What a reserve ratio can't tell you
&lt;/h2&gt;

&lt;p&gt;Proof of reserves became the standard reassurance after FTX collapsed in November 2022. A month later the accounting firm Mazars paused this work for all its crypto clients, citing concerns about "the way these reports are understood by the public." Its reports followed agreed-upon procedures, were not audits, and described a single past point in time (&lt;a href="https://www.cnbc.com/2022/12/16/mazars-suspends-all-work-with-crypto-clients-including-binance-cryptocom.html" rel="noopener noreferrer"&gt;CNBC&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Four years on, the gaps are the same:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any day other than the snapshot. Assets can be borrowed for the snapshot and returned afterwards. Buterin's suggested fix is frequent or coordinated proofs, so two exchanges can't count the same funds.&lt;/li&gt;
&lt;li&gt;Fiat. Bank balances can't be proven cryptographically. At best they rest on bank confirmations and an auditor's attestation.&lt;/li&gt;
&lt;li&gt;Liabilities outside the tree. Loans, obligations to affiliates or market makers, and anything else that isn't a customer balance won't show up in a customer liabilities tree.&lt;/li&gt;
&lt;li&gt;Key security. A wallet can be fully reserved and badly protected. Bybit lost about $1.5 billion in ETH in February 2025, in an attack the FBI attributed to North Korea.&lt;/li&gt;
&lt;li&gt;Your own inclusion. If you never check that your balance is in the tree, the proof did nothing for you.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bitget as a worked example
&lt;/h2&gt;

&lt;p&gt;After the September 24 theft, Bitget covered the losses from its protection fund, which had held 5,500 BTC beforehand. On September 30 it said the fund was back at $309 million. Withdrawals resumed for BTC on September 28, ETH on September 29 and USDT on September 30, with other tokens, fiat and P2P following on October 2 (&lt;a href="https://www.cryptotimes.io/2026/09/30/bitget-protection-fund-back-above-300m-after-387-5m-hack-p2p-withdrawals-return-october-2/" rel="noopener noreferrer"&gt;The Crypto Times&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The 47th report, snapshot at 09:00 UTC on September 29, gave an overall ratio of 131% across 19 assets, down from 135% in the previous report. BTC stood at 142%, ETH at 110%, USDT at 107% and USDC at 154%. In the 24 hours before the snapshot, net outflows came to about $463 million.&lt;/p&gt;

&lt;p&gt;So Bitget stayed solvent through a theft of that size and a run of withdrawals, and the report shows it. For how the attackers got in, how fast the fund gets rebuilt, or what the ratio looked like on September 23, you need other sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reserves are one column in the table
&lt;/h2&gt;

&lt;p&gt;A recent &lt;a href="https://www.thailand-business-news.com/thailand-prnews/top-crypto-exchanges-overall-in-2026-what-works-for-users-in-asia" rel="noopener noreferrer"&gt;comparison of top crypto exchanges for users in Asia&lt;/a&gt; scores six platforms on five public checks: trading volume share, regulatory access in Asian markets, proof of reserves and protection funds, base spot fees, and incident record since 2025. I like that reserves and incidents sit side by side there. Bitget's 131% reads differently once you know the exchange had just paid out $387.5 million from its own fund.&lt;/p&gt;

&lt;p&gt;If you build for Asia, the regulatory column deserves the same attention, because access changes from one country to the next. The comparison tracks platforms that are licensed in one market, blocked in another and winding down in a third.&lt;/p&gt;

&lt;p&gt;I also noticed how the table handles a non-custodial swap service: its reserves column is marked not applicable. That's the right call. A service that doesn't hold user balances has no liabilities tree to publish, and the risk sits in the transfer itself, which is where my last two posts went.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist before you trust a ratio
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you keep a balance on an exchange&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Find the latest report and note the snapshot time as well as the publication date.&lt;/li&gt;
&lt;li&gt;[ ] Check your own inclusion with the exchange's proof file or validator.&lt;/li&gt;
&lt;li&gt;[ ] Look at the ratio for each asset you actually hold, not only the headline figure.&lt;/li&gt;
&lt;li&gt;[ ] Compare the last few reports. A falling trend tells you more than a single number.&lt;/li&gt;
&lt;li&gt;[ ] Read the incident history and the size of any protection fund next to the ratio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If you build on exchange APIs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Show users when reserves were last proven, with the snapshot date.&lt;/li&gt;
&lt;li&gt;[ ] Treat withdrawal pauses as a normal state in your app, with clear messaging.&lt;/li&gt;
&lt;li&gt;[ ] Track where each venue is licensed or restricted, per user region.&lt;/li&gt;
&lt;li&gt;[ ] Don't present a reserve ratio as a safety rating. It is one input.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does a proof of reserves report mean the exchange was audited?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Most reports are attestations or self-published proofs, not financial audits. An audit looks at the whole company: all liabilities, internal controls, related parties and accounting policies. A proof of reserves covers a defined set of customer balances and wallets at one moment. When Mazars paused this work in December 2022, it stressed that its reports followed agreed-upon procedures and did not give an audit opinion. A report backs one specific claim: at the snapshot time, customer balances in scope were matched by on-chain assets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why would a reserve ratio be above 100% after a hack?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ratio compares what the exchange holds with what it owes customers, and both sides move after an incident. If the exchange covers stolen funds from its own capital or a protection fund, customer balances stay whole while the company's own money shrinks. Many users also withdraw after a hack, which lowers liabilities and assets together. Bitget's 131% on September 29 reflected all of that. The incident still cost the company hundreds of millions; the ratio just shows customers were covered at the snapshot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I verify my own balance was included?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Log in to the exchange and open its proof of reserves page, which usually links a per-user proof file for each report. Download the file for the latest report and run it through the verification tool the exchange provides, ideally an open-source one you can inspect. For Merkle tree reports, the tool rebuilds the path from your leaf to the published root. For zk-based reports, it checks the proof against the published commitment. If your balance isn't there, contact support and keep a screenshot of the report date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Most of what an exchange tells you about itself has to be taken on trust. A reserve report is one of the rare exceptions, because you can run the check yourself. I'd still read it next to the snapshot date, your own inclusion proof and the incident history, since the ratio alone covers one moment and one kind of risk.&lt;/p&gt;

&lt;p&gt;Do you check your inclusion proof when a new report comes out, or has anyone built that check into an app? I'd like to hear how others handle it.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>cryptocurrency</category>
      <category>fintech</category>
      <category>security</category>
    </item>
    <item>
      <title>What Happens After You Press Send on a Bitcoin-to-Monero Swap</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Tue, 06 Oct 2026 13:40:50 +0000</pubDate>
      <link>https://dev.to/azaleakuts/what-happens-after-you-press-send-on-a-bitcoin-to-monero-swap-4f62</link>
      <guid>https://dev.to/azaleakuts/what-happens-after-you-press-send-on-a-bitcoin-to-monero-swap-4f62</guid>
      <description>&lt;p&gt;My last post covered what a transfer flow should do before the user signs. A reader replied with the obvious follow-up: make the settlement state visible before the user thinks the transfer is done. Fair point. Most crypto apps put real effort into the confirmation screen and then hand the user a spinner labeled "Pending".&lt;/p&gt;

&lt;p&gt;To show what belongs in place of that spinner, I picked the least convenient everyday case I could find: swapping Bitcoin for Monero. It has two chains with different block times, one leg anyone can inspect and one nobody can, a waiting period after the funds arrive, and a protocol upgrade that is in public testing this week. A status model that copes with this pair has an easy time with a stablecoin transfer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Seen", "confirmed", "spendable" and "final" are separate states. On a BTC to XMR swap the gap between two of them can run from minutes to hours.&lt;/li&gt;
&lt;li&gt;A Bitcoin deposit sitting in the mempool is replaceable by default since Bitcoin Core 28.0, so treat it as information and wait for a block.&lt;/li&gt;
&lt;li&gt;A Monero payout can't be verified in a block explorer. You need the recipient's wallet, or the transaction ID together with the transaction key and the address.&lt;/li&gt;
&lt;li&gt;Ten Monero confirmations make funds spendable, which is a weaker promise than final. In September 2025 an 18-block reorg invalidated 115 transactions.&lt;/li&gt;
&lt;li&gt;FCMP++ is still not on Monero mainnet as of October 6, 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this pair is a good stress test
&lt;/h2&gt;

&lt;p&gt;You can't send BTC to a Monero address. Bitcoin keys live on the secp256k1 curve and Monero keys on ed25519, the address formats have nothing in common, and neither chain knows the other exists. A &lt;a href="https://docs.getmonero.org/public-address/standard-address/" rel="noopener noreferrer"&gt;standard Monero address&lt;/a&gt; packs two public keys and a checksum into 95 characters. Moving value between the two networks always means two separate transactions with something in between, usually a swap service.&lt;/p&gt;

&lt;p&gt;TechBullion recently published a step-by-step guide on &lt;a href="https://techbullion.com/how-to-transfer-bitcoin-to-monero/" rel="noopener noreferrer"&gt;how to transfer Bitcoin to Monero&lt;/a&gt; that describes the flow from the user's chair: create a fresh Monero subaddress, open an order, send BTC to the deposit address, save the order ID, wait. I read it as a list of promises made to the user. The guide quotes 15 to 60 minutes for the swap on the service it uses as an example and about 20 more minutes before the XMR can be spent. It also names four usual reasons for a slow transfer: a low Bitcoin fee, a congested network, a deposit that doesn't match the order amount, or a compliance hold.&lt;/p&gt;

&lt;p&gt;Every one of those sentences is a state the backend has to track and the interface has to show. The two legs behave very differently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Bitcoin leg (deposit)&lt;/th&gt;
&lt;th&gt;Monero leg (payout)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average block interval&lt;/td&gt;
&lt;td&gt;About 10 minutes&lt;/td&gt;
&lt;td&gt;About 2 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What an outsider can read&lt;/td&gt;
&lt;td&gt;Sender, recipient, amount&lt;/td&gt;
&lt;td&gt;That the transaction exists and how many confirmations it has&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How a payment is verified&lt;/td&gt;
&lt;td&gt;Transaction ID in any explorer or node&lt;/td&gt;
&lt;td&gt;Recipient's wallet, or transaction ID + transaction key + address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before the first confirmation&lt;/td&gt;
&lt;td&gt;Replaceable by default (full RBF)&lt;/td&gt;
&lt;td&gt;In the pool; the wallet reports a &lt;code&gt;double_spend_seen&lt;/code&gt; flag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When received funds can be spent&lt;/td&gt;
&lt;td&gt;No protocol waiting period&lt;/td&gt;
&lt;td&gt;Once the output is 10 blocks old&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Model the swap as a state machine
&lt;/h2&gt;

&lt;p&gt;A single "pending" status sends every user to support with the same question. An explicit state machine answers it on the order page and gives your own team a shared vocabulary for incidents.&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;type&lt;/span&gt; &lt;span class="nx"&gt;SwapState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;awaiting_deposit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deposit_seen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;        &lt;span class="c1"&gt;// BTC tx in the mempool, still replaceable&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deposit_confirming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;// in a block, below your confirmation threshold&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exchanging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payout_sent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;         &lt;span class="c1"&gt;// XMR tx broadcast, txid known&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payout_confirming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;   &lt;span class="c1"&gt;// in a block, outputs still locked&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spendable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;           &lt;span class="c1"&gt;// the 10-block lock has passed&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on_hold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;             &lt;span class="c1"&gt;// compliance review, user action required&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refunding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refunded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transitions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SwapState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SwapState&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;awaiting_deposit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deposit_seen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deposit_confirming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;deposit_seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deposit_confirming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;awaiting_deposit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// replaced or dropped&lt;/span&gt;
  &lt;span class="na"&gt;deposit_confirming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exchanging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on_hold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refunding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deposit_seen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;exchanging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payout_sent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on_hold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refunding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;payout_sent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payout_confirming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exchanging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;        &lt;span class="c1"&gt;// never mined, send again&lt;/span&gt;
  &lt;span class="na"&gt;payout_confirming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spendable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payout_sent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;               &lt;span class="c1"&gt;// reorg&lt;/span&gt;
  &lt;span class="na"&gt;spendable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payout_confirming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;                      &lt;span class="c1"&gt;// deep reorg&lt;/span&gt;
  &lt;span class="na"&gt;on_hold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exchanging&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refunding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;refunding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refunded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;refunded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;           &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;expired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SwapState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SwapState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transitions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the arrows that point backwards. Each one stands for a real event: a replaced deposit, a payout that never got mined, a chain reorganization. If your model has no backward transitions, it will report "completed" for money that has just disappeared from the recipient's wallet.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the Bitcoin leg, seen is not confirmed
&lt;/h2&gt;

&lt;p&gt;The deposit is the public half, so tracking it is mostly a matter of discipline about wording.&lt;/p&gt;

&lt;p&gt;A transaction in the mempool tells you the user did something, and nothing is settled yet. Since version 28.0, released in October 2024, Bitcoin Core ships with &lt;code&gt;mempoolfullrbf=1&lt;/code&gt; as the default (&lt;a href="https://bitcoincore.org/en/releases/28.0/" rel="noopener noreferrer"&gt;release notes&lt;/a&gt;). Any unconfirmed transaction can be replaced by one that pays a higher fee, whether or not it signaled replaceability. Show "deposit seen" to calm the user down, and start the swap only after the confirmations you require.&lt;/p&gt;

&lt;p&gt;With an Esplora-compatible API the check takes two requests. &lt;code&gt;GET /tx/:txid&lt;/code&gt; returns the transaction with a &lt;code&gt;status&lt;/code&gt; object, and &lt;code&gt;GET /blocks/tip/height&lt;/code&gt; returns the current height (&lt;a href="https://github.com/Blockstream/esplora/blob/master/API.md" rel="noopener noreferrer"&gt;Esplora API&lt;/a&gt;).&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;ESPLORA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://blockstream.info/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BtcDeposit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not_found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// never broadcast, dropped or replaced&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mempool&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;confirmations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;btcDepositStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BtcDeposit&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ESPLORA&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tx/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not_found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Esplora responded with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Also check here that a vout pays your deposit address the expected value&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;block_height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirmed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;block_height&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mempool&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tipRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ESPLORA&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blocks/tip/height`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tipRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirmed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;confirmations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tip&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;block_height&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&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 details matter in the interface. Show progress as "1 of 2 confirmations" so the user can see the order moving. When a transaction you had already seen comes back as &lt;code&gt;not_found&lt;/code&gt;, say that the deposit was replaced or dropped and tell the user what to do next. The Bitcoin side tends to be the slowest part of a BTC to XMR order, and a low fee can leave the deposit waiting for several blocks. The deposit screen is the right place to say so, before anything is sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the Monero leg, someone has to tell you
&lt;/h2&gt;

&lt;p&gt;On the payout side the public data runs out. A Monero explorer can confirm that a transaction exists and count its confirmations. The amount and the recipient are hidden by design, so "paste the hash into an explorer" is not an answer you can give a user.&lt;/p&gt;

&lt;p&gt;There are two ways to verify a payout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From the recipient's wallet.&lt;/strong&gt; The wallet scans the chain with its own keys and lists what arrived. In &lt;code&gt;monero-wallet-rpc&lt;/code&gt;, &lt;code&gt;get_transfers&lt;/code&gt; with &lt;code&gt;in&lt;/code&gt; and &lt;code&gt;pool&lt;/code&gt; set to true returns each incoming transfer with &lt;code&gt;confirmations&lt;/code&gt;, &lt;code&gt;locked&lt;/code&gt;, &lt;code&gt;double_spend_seen&lt;/code&gt; and &lt;code&gt;suggested_confirmations_threshold&lt;/code&gt;. Amounts come in atomic units, and 1 XMR equals 1e12 of them (&lt;a href="https://docs.getmonero.org/rpc-library/wallet-rpc/" rel="noopener noreferrer"&gt;wallet RPC docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From a payment proof.&lt;/strong&gt; The sender shares three things: the transaction ID, the destination address and the one-time transaction key. Anyone with a synced wallet can then check how much that transaction paid to that address (&lt;a href="https://www.getmonero.org/resources/user-guides/prove-payment.html" rel="noopener noreferrer"&gt;Monero user guide&lt;/a&gt;). Over RPC the method is &lt;code&gt;check_tx_key&lt;/code&gt;:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;walletRpc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Add digest auth if monero-wallet-rpc runs with --rpc-login&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:18088/json_rpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Empty RPC response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;XMR_LOCK_BLOCKS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkXmrPayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;walletRpc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;in_pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;confirmations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;check_tx_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;txid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tx_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;txKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Atomic units. Parse as BigInt in production: values above 2^53 lose precision&lt;/span&gt;
    &lt;span class="na"&gt;receivedXmr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;inPool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;in_pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;confirmations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirmations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pastDefaultLock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;in_pool&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirmations&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;XMR_LOCK_BLOCKS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the sending side, three habits save a lot of support time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request the key when you send (&lt;code&gt;get_tx_key: true&lt;/code&gt; in &lt;code&gt;transfer&lt;/code&gt;) and store it with the order. The docs warn that &lt;code&gt;rescan_blockchain&lt;/code&gt; throws away anything that can't be rebuilt from the chain, and transaction keys are on that list.&lt;/li&gt;
&lt;li&gt;Show the XMR transaction ID on the order page and hand out the key on request, so the user can run the check in their own wallet.&lt;/li&gt;
&lt;li&gt;Ask users for a fresh subaddress per order and create one per deposit on your own side (&lt;code&gt;create_address&lt;/code&gt;). Reconciliation then becomes a lookup by &lt;code&gt;subaddr_index&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One caveat from the same documentation: a valid proof shows that funds were sent to an address. It does not guarantee that they are spendable. The recipient's wallet has the last word, and &lt;code&gt;get_balance&lt;/code&gt; reports &lt;code&gt;blocks_to_unlock&lt;/code&gt; and &lt;code&gt;time_to_unlock&lt;/code&gt; for exactly that purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seen, confirmed, spendable, final
&lt;/h2&gt;

&lt;p&gt;Status pages tend to use these four words as synonyms. On Monero the last two are the ones that get mixed up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spendable&lt;/strong&gt; is a consensus rule. A Monero transaction can only spend an output that was included at least 10 blocks earlier, which works out to about 20 minutes at one block every two minutes. An honest order page says "Arrived, spendable in about 14 minutes" and counts down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final&lt;/strong&gt; is a judgment call. On September 14, 2025, the Qubic mining pool pushed an 18-block reorganization onto Monero's main chain. Researcher Rucknium's analysis counts 115 invalidated transactions, while 456 others from the orphaned blocks were mined again on the new chain (&lt;a href="https://rucknium.me/posts/monero-18-block-reorg/" rel="noopener noreferrer"&gt;Rucknium&lt;/a&gt;). The coins in the invalidated transactions went back to the senders' wallets. The 10-block lock only protects against reorgs up to 9 blocks deep, so in that hour "10 confirmations" stopped meaning "done". A month earlier Kraken had already reopened XMR deposits with a requirement of 720 confirmations, roughly a day of blocks (&lt;a href="https://www.fxstreet.com/cryptocurrencies/news/monero-price-forecast-xmr-eyes-wedge-breakout-rally-amid-qubic-hashrate-dominance-202508180526" rel="noopener noreferrer"&gt;FXStreet&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For a developer this leads to three decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the confirmation threshold in configuration, per asset and per amount. The wallet RPC even suggests a number through &lt;code&gt;suggested_confirmations_threshold&lt;/code&gt;, which scales with the amount received.&lt;/li&gt;
&lt;li&gt;Keep polling after &lt;code&gt;spendable&lt;/code&gt;. If a payout disappears, first look for the same transaction ID on the new chain, because most transactions survive a reorg. Create a new payout only once you know the original is gone, otherwise you pay twice.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;locked&lt;/code&gt; and &lt;code&gt;unlock_time&lt;/code&gt; on incoming transfers. According to the &lt;a href="https://www.getmonero.org/2026/05/10/deprecating-unlock-time.html" rel="noopener noreferrer"&gt;Monero blog&lt;/a&gt;, merchant software has lost money by assuming that received XMR was spendable when a custom unlock time said otherwise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check where FCMP++ stands before you plan around it
&lt;/h2&gt;

&lt;p&gt;Several explainers currently online state that Monero's FCMP++ upgrade went live in January 2026. The project's own channels say otherwise.&lt;/p&gt;

&lt;p&gt;Here is the status as of October 6, 2026. A beta stressnet release tagged &lt;code&gt;v0.19.0.0-beta.3.0&lt;/code&gt; came out on September 25, with that test network scheduled to fork from testnet on October 5 at block 3,102,800. It is separate from mainnet (&lt;a href="https://www.tokenpost.com/news/technology/25090" rel="noopener noreferrer"&gt;TokenPost&lt;/a&gt;). The newest mainnet software is 0.18.5.3, published on October 6 as a bug-fix release in the current series (&lt;a href="https://www.getmonero.org/2026/10/06/monero-0.18.5.3-released.html" rel="noopener noreferrer"&gt;getmonero.org&lt;/a&gt;). The last official word on timing, from May 10, was that no FCMP++ fork date had been set (same Monero blog post as above).&lt;/p&gt;

&lt;p&gt;FCMP++ replaces ring signatures, where the real input hides among 16 outputs, with a membership proof over the whole set of more than 150 million outputs. CARROT, the addressing upgrade shipping with it, is designed to stay compatible with existing addresses. For anyone maintaining swap or payment code, the practical points are these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom transaction unlock times are being removed at consensus with the fork. A relay rule already rejects new transactions that set one.&lt;/li&gt;
&lt;li&gt;The 10-block lock keeps its role. Rucknium's analysis describes a trade-off: the privacy leak that follows a reorg today goes away, but every transaction caught in a reorg deeper than the lock would be invalidated, where at present only some are.&lt;/li&gt;
&lt;li&gt;The current stressnet build ships without transaction proofs, multisig and hardware wallet support. If your order page relies on &lt;code&gt;check_tx_key&lt;/code&gt;, put that flow on the retest list for the day a mainnet release candidate appears.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Holds and availability are states too
&lt;/h2&gt;

&lt;p&gt;The TechBullion guide makes a point that product teams often leave out of their diagrams: a swap without an account can still involve identity checks. Verification on such services is risk-based. A flagged transfer is put on hold, and the user may be asked for documents before the order continues.&lt;/p&gt;

&lt;p&gt;From the user's side an unexplained hold looks exactly like a lost payment. That is why &lt;code&gt;on_hold&lt;/code&gt; is a separate state in the model above, with a reason, a next action and a deadline attached. It should never be rendered as a longer "exchanging".&lt;/p&gt;

&lt;p&gt;Availability deserves the same treatment. From July 10, 2027, Article 79 of the EU Anti-Money Laundering Regulation prohibits crypto-asset service providers from keeping accounts that allow the anonymisation of transactions, including through anonymity-enhancing coins (&lt;a href="https://eur-lex.europa.eu/eli/reg/2024/1624/oj" rel="noopener noreferrer"&gt;EUR-Lex&lt;/a&gt;). The text names no specific coins and addresses providers, not people holding XMR in their own wallets. In code, pair availability becomes configuration by jurisdiction with an effective date, and the "not available in your region" answer has to come before a deposit address is shown.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on atomic swaps
&lt;/h2&gt;

&lt;p&gt;Swap services are one way to connect the two chains. Atomic swaps are another. Monero has no scripting, so the hashed timelock contracts used elsewhere don't apply. Joël Gugger's 2020 protocol and the COMIT team's follow-up work solve this with adaptor signatures on the Bitcoin side (&lt;a href="https://eprint.iacr.org/2020/1126" rel="noopener noreferrer"&gt;ePrint 2020/1126&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2101.12332" rel="noopener noreferrer"&gt;arXiv 2101.12332&lt;/a&gt;). The state machine gets longer, with lock, redeem, cancel and refund steps on the Bitcoin side. The observability rules from this post stay the same: the Bitcoin half is public, and the Monero half has to be checked from a wallet.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist for a swap status page
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every order has an explicit state, and "pending" is not one of them.&lt;/li&gt;
&lt;li&gt;[ ] Backward transitions exist for a replaced deposit, an unmined payout and a reorg.&lt;/li&gt;
&lt;li&gt;[ ] A mempool sighting is shown as "seen" and never triggers the payout.&lt;/li&gt;
&lt;li&gt;[ ] Bitcoin progress is displayed as "n of N confirmations".&lt;/li&gt;
&lt;li&gt;[ ] The XMR transaction ID is shown, and the transaction key is stored and available on request.&lt;/li&gt;
&lt;li&gt;[ ] "Arrived" and "spendable" are separate, with a countdown for the 10-block lock.&lt;/li&gt;
&lt;li&gt;[ ] Confirmation thresholds live in configuration and depend on the amount.&lt;/li&gt;
&lt;li&gt;[ ] Polling continues after "spendable", and a vanished payout is looked up again before it is re-sent.&lt;/li&gt;
&lt;li&gt;[ ] A hold has its own state with a reason and a next step.&lt;/li&gt;
&lt;li&gt;[ ] Pair availability is checked per jurisdiction before the deposit address appears.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why can't I look up my XMR payout in a block explorer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monero hides the sender, the recipient and the amount of every transaction. An explorer can tell you that a transaction ID exists and how many confirmations it has, and nothing more. To confirm that a payout reached you, open your own synced wallet, which recognizes incoming outputs with your keys. If nothing shows up, ask the sender for the transaction ID and the transaction key. With those two values and your address, the Prove/Check tool in the Monero GUI, or &lt;code&gt;check_tx_key&lt;/code&gt; over RPC, shows how much that transaction paid to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are 10 confirmations final on Monero?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ten blocks is the point where received outputs become spendable under the consensus rules. Finality is a separate question that each service answers for itself. The 18-block reorganization of September 14, 2025 went deeper than the lock and invalidated 115 transactions that already looked settled. Services reacted by raising their own thresholds, in Kraken's case to 720 confirmations for deposits. If you accept XMR, pick a threshold that fits the amount at risk, keep it configurable and be ready to move an order back a state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is FCMP++ live on Monero mainnet?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. As of October 6, 2026, FCMP++ and CARROT run only on a beta stressnet, whose fork from testnet was scheduled for October 5. Mainnet runs the 0.18 series, and the latest release, 0.18.5.3, is a bug-fix update. The Monero blog stated in May 2026 that no fork date had been set, and I found no later announcement of one. Before changing wallet or payment code for the upgrade, check the blog on getmonero.org and the release notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;A Bitcoin to Monero swap takes away the shortcut of pointing people to an explorer, so the order page has to say what the service knows at each step. I think that discipline is worth keeping for transfers where an explorer link does exist, because most users never open it.&lt;/p&gt;

&lt;p&gt;Which states does your product show between "sent" and "done"? I'd like to compare notes in the comments.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>bitcoin</category>
      <category>fintech</category>
      <category>web3</category>
    </item>
    <item>
      <title>Irreversible by Design: Building Safer Crypto Transfer Flows</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Thu, 01 Oct 2026 16:33:31 +0000</pubDate>
      <link>https://dev.to/azaleakuts/irreversible-by-design-building-safer-crypto-transfer-flows-3fg6</link>
      <guid>https://dev.to/azaleakuts/irreversible-by-design-building-safer-crypto-transfer-flows-3fg6</guid>
      <description>&lt;p&gt;On December 20, 2025, a trader withdrew USDT from Binance and did everything the security guides recommend. They sent a 50 USDT test transfer to their own wallet first. About 26 minutes later they sent the remaining 49,999,950 USDT, and it landed in a scammer's wallet.&lt;/p&gt;

&lt;p&gt;The test transfer was the trigger. An automated script spotted it, generated an address with the same first five and last four characters, and pushed a tiny transaction into the victim's history. When the trader came back to send the full amount, they copied the address from that history. The two addresses differed only in the middle characters, the part most wallet UIs hide behind an ellipsis. Within 30 minutes the funds were swapped to DAI, then ETH, then sent through Tornado Cash (&lt;a href="https://www.theblock.co/news/ecosystems/2025-12-20-crypto-trader-loses-50-million-in-address-poisoning-attack-offers-1-million-bounty-for-return-383423" rel="noopener noreferrer"&gt;The Block&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;No bank would let a $50M transfer go through on a lookalike account number without a second check. On a blockchain there is no chargeback and no operator who can roll a confirmed transaction back. The only place to catch the mistake is the interface where the user pastes an address and taps Send, and that interface is something we, as developers, build.&lt;/p&gt;

&lt;p&gt;Users notice the difference. A recent overview of &lt;a href="https://www.davestravelcorner.com/journals/destination-other/from-branches-to-blockchains-where-digital-money-habits-are-heading-in-2026/" rel="noopener noreferrer"&gt;where digital money habits are heading in 2026&lt;/a&gt; points out that people now choose a crypto exchange or swap service the way they choose a banking app: they compare fees, KYC requirements and supported assets. The same piece makes a point that matters for anyone shipping a send screen. People look for the platform where a wrong-network or wrong-address transfer is harder to make, and the lowest fee is only part of the decision.&lt;/p&gt;

&lt;p&gt;This post walks through four layers of protection you can build into a crypto transfer flow, with code you can adapt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate addresses properly.&lt;/li&gt;
&lt;li&gt;Bind every address to a network, memo and tag included.&lt;/li&gt;
&lt;li&gt;Defend against address poisoning and clipboard swaps.&lt;/li&gt;
&lt;li&gt;Simulate and preview the transaction before the user signs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A checksum catches typos, but a poisoned lookalike address passes every checksum.&lt;/li&gt;
&lt;li&gt;Most recoverable mistakes are the ones where the user or an exchange still holds the destination key; transfers to a stranger's address are almost always lost.&lt;/li&gt;
&lt;li&gt;Wallets like MetaMask and explorers like Etherscan already ship lookalike-address warnings and hide zero-value transfers, so users will expect the same from your app.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eth_simulateV1&lt;/code&gt; lets you show what a transaction will do before it is signed, on Ethereum and a growing list of other chains.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A taxonomy of transfer mistakes
&lt;/h2&gt;

&lt;p&gt;Whether a mistaken transfer can be recovered depends on one question: who controls the private key of the destination address? If it is the user or an exchange, there is often a path back. If it is a stranger, the funds are almost always gone. The table below maps the common mistakes to that question and to the place in your code where each one can be stopped.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;What actually happens&lt;/th&gt;
&lt;th&gt;Recovery odds&lt;/th&gt;
&lt;th&gt;Where you stop it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typo in the address&lt;/td&gt;
&lt;td&gt;Funds go to an address nobody controls, or to a stranger&lt;/td&gt;
&lt;td&gt;Close to zero&lt;/td&gt;
&lt;td&gt;Checksum validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Address poisoning&lt;/td&gt;
&lt;td&gt;User copies a lookalike address from their own history&lt;/td&gt;
&lt;td&gt;Close to zero&lt;/td&gt;
&lt;td&gt;Full address display, lookalike detection, hiding dust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clipboard hijacking&lt;/td&gt;
&lt;td&gt;Malware swaps the copied address for the attacker's&lt;/td&gt;
&lt;td&gt;Close to zero&lt;/td&gt;
&lt;td&gt;Showing the pasted address in full, hardware wallet confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong network, own address (EVM to EVM)&lt;/td&gt;
&lt;td&gt;Funds sit on the same 0x address on another chain&lt;/td&gt;
&lt;td&gt;Usually high, the user adds the network to their wallet&lt;/td&gt;
&lt;td&gt;Explicit network selection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrong network, exchange address&lt;/td&gt;
&lt;td&gt;The exchange received the funds but did not credit them&lt;/td&gt;
&lt;td&gt;Depends on the exchange, often a fee and days or weeks of waiting&lt;/td&gt;
&lt;td&gt;Deposit addresses bound to asset plus network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing memo or destination tag&lt;/td&gt;
&lt;td&gt;Funds land in the exchange's shared omnibus account with no owner attached&lt;/td&gt;
&lt;td&gt;Usually possible through support&lt;/td&gt;
&lt;td&gt;Mandatory memo field, protocol-level flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens sent to a token contract&lt;/td&gt;
&lt;td&gt;Tokens get stuck in a contract that cannot send them out&lt;/td&gt;
&lt;td&gt;Close to zero&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;eth_getCode&lt;/code&gt; check and a warning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The memo row shows how expensive a single missing input can be. When a deposit arrives without the required memo, Blockchain.com may ask the user to make another minimum deposit of 1 XLM or XRP from the same address, this time with the correct memo, to prove ownership before any funds are returned (&lt;a href="https://support.blockchain.com/hc/en-us/articles/8219099905436-Crypto-Recovery-for-Unsupported-or-Incorrect-Transfers" rel="noopener noreferrer"&gt;Blockchain.com Support&lt;/a&gt;). A required field on the send screen would have avoided the whole support ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1. Validate addresses properly
&lt;/h2&gt;

&lt;p&gt;Format validation is the minimum, and it only protects against typos. A checksum tells you the string was not mistyped. It says nothing about whether the address belongs to the person the user meant to pay, because a poisoned lookalike address is a perfectly valid address with a perfectly valid checksum.&lt;/p&gt;

&lt;p&gt;Each chain family handles this differently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chain / format&lt;/th&gt;
&lt;th&gt;How the checksum works&lt;/th&gt;
&lt;th&gt;What it guarantees&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ethereum and EVM chains (EIP-55)&lt;/td&gt;
&lt;td&gt;Mixed-case hex: a letter is uppercase if the matching bit of the keccak256 hash is 1&lt;/td&gt;
&lt;td&gt;About 15 check bits; a random typo slips through 0.0247% of the time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitcoin legacy and P2SH (Base58Check)&lt;/td&gt;
&lt;td&gt;Base58 plus 4 bytes of double SHA-256&lt;/td&gt;
&lt;td&gt;Typos are almost always caught&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitcoin SegWit (Bech32) and Taproot (Bech32m)&lt;/td&gt;
&lt;td&gt;Lowercase only, no lookalike characters like 1, b, i, o&lt;/td&gt;
&lt;td&gt;Detects any error affecting up to 4 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tron&lt;/td&gt;
&lt;td&gt;Base58Check with a 0x41 version byte, addresses start with T&lt;/td&gt;
&lt;td&gt;Checksum included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solana&lt;/td&gt;
&lt;td&gt;Base58-encoded 32-byte ed25519 public key&lt;/td&gt;
&lt;td&gt;No checksum at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The EIP-55 figures come from the &lt;a href="https://eips.ethereum.org/EIPS/eip-55" rel="noopener noreferrer"&gt;ERC-55 specification&lt;/a&gt;. The Solana row deserves a warning in your UI: a typo there can produce another valid address, so format validation tells the user nothing about correctness.&lt;/p&gt;

&lt;p&gt;A few rules that save real money:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A regex like &lt;code&gt;^0x[a-fA-F0-9]{40}$&lt;/code&gt; is not validation. It accepts addresses with a broken checksum.&lt;/li&gt;
&lt;li&gt;A mixed-case address with an invalid checksum is an error. Do not silently lowercase it and continue.&lt;/li&gt;
&lt;li&gt;Validate the address against the selected network. A Bitcoin address in a BSC field should fail even though the string is valid somewhere.&lt;/li&gt;
&lt;li&gt;Check whether the recipient is a contract (&lt;code&gt;eth_getCode&lt;/code&gt; returns something other than &lt;code&gt;0x&lt;/code&gt;) and warn before sending tokens to a token contract.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With viem, the EVM part takes a few lines:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createPublicClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;viem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mainnet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;viem/chains&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPublicClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mainnet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateEvmRecipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isAddress&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid address or checksum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAddress&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="c1"&gt;// checksummed form to show back to the user&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other chains, use the chain's own library: bitcoinjs-lib (&lt;code&gt;address.toOutputScript(addr, network)&lt;/code&gt;), TronWeb (&lt;code&gt;isAddress&lt;/code&gt;), &lt;code&gt;@solana/web3.js&lt;/code&gt; (&lt;code&gt;new PublicKey()&lt;/code&gt;) and ripple-address-codec for XRP. Generic multi-coin validators are handy for a first pass, but they check format only and know nothing about the network the user picked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2. Bind every address to a network
&lt;/h2&gt;

&lt;p&gt;Hundreds of L2s use the same address format as Ethereum mainnet, so a &lt;code&gt;0x&lt;/code&gt; address on its own no longer tells you which chain it belongs to. Your send flow has to make the network an explicit, visible part of the destination, and for some assets the memo or tag too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the interface&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treat the network as a required choice. For large amounts, avoid a preselected default.&lt;/li&gt;
&lt;li&gt;Show the network name and icon on the confirmation screen right next to the address, not tucked away in a header.&lt;/li&gt;
&lt;li&gt;Ask for the receiving side's network first. The recipient knows which network their platform supports; the sender usually picks whatever is cheapest.&lt;/li&gt;
&lt;li&gt;On exchanges and swap services, bind each deposit address to an asset plus network pair.&lt;/li&gt;
&lt;li&gt;Use the address format to narrow the choice. &lt;code&gt;T…&lt;/code&gt; means Tron, &lt;code&gt;bc1…&lt;/code&gt; means Bitcoin, &lt;code&gt;0x…&lt;/code&gt; means some EVM chain, and in that last case you still ask which one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Memo and destination tags&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;XRP, XLM, TON, ATOM and EOS deposits to exchanges usually need a memo or tag, because the exchange routes everyone's deposits through one shared address. Make the field mandatory when the recipient is an exchange, and require an explicit "this recipient does not need a memo" confirmation to skip it. Validate the format as well: an XRP destination tag is an unsigned 32-bit integer (0 to 4,294,967,295), and a Stellar text memo is limited to 28 bytes.&lt;/p&gt;

&lt;p&gt;Both ledgers also give you protocol-level help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XRP Ledger.&lt;/strong&gt; An account can set the &lt;code&gt;RequireDest&lt;/code&gt; flag, and the network then rejects payments without a destination tag. Query &lt;code&gt;account_info&lt;/code&gt; before sending and enforce the tag in your UI when the flag is set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stellar SEP-29.&lt;/strong&gt; An account can publish a &lt;code&gt;config.memo_required&lt;/code&gt; data entry, and wallets are expected to check it before submitting a payment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XRP X-addresses.&lt;/strong&gt; They pack the classic address and the tag into one string, so the user cannot forget the tag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where the standards are heading&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two Ethereum proposals tackle the network problem at the address level. &lt;a href="https://eips.ethereum.org/EIPS/eip-7930" rel="noopener noreferrer"&gt;ERC-7930&lt;/a&gt; defines a binary format that carries the chain type, chain reference and address together, and it covers non-EVM chains too. &lt;a href="https://eips.ethereum.org/EIPS/eip-7828" rel="noopener noreferrer"&gt;ERC-7828&lt;/a&gt; adds a human-readable form, &lt;code&gt;&amp;lt;address&amp;gt;@&amp;lt;chain&amp;gt;#&amp;lt;checksum&amp;gt;&lt;/code&gt;, so something like &lt;code&gt;alice.eth@base&lt;/code&gt; names both the account and the chain. Its optional checksum is meant to help against homoglyph and spoofing attacks. Both are still drafts, so treat them as a direction to watch and keep your own network checks in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3. Defend against address poisoning and clipboard swaps
&lt;/h2&gt;

&lt;p&gt;Address poisoning exploits truncated addresses: users compare the first and last few characters, and the attacker matches exactly those. Nobody can stop a stranger from sending dust to a public address, so the defense lives entirely in the wallet and app UI.&lt;/p&gt;

&lt;p&gt;The scale is real. Researchers counted 270 million poisoning attempts and 50 million lookalike addresses on Ethereum and BNB Smart Chain, with about $83.8M in confirmed losses across more than 6,600 cases (&lt;a href="https://arxiv.org/html/2501.16681v1" rel="noopener noreferrer"&gt;arXiv 2501.16681&lt;/a&gt;). On Bitcoin, Casa's Jameson Lopp identified 48,000 suspected attacks since 2023 (&lt;a href="https://www.theblock.co/news/ecosystems/2025-12-20-crypto-trader-loses-50-million-in-address-poisoning-attack-offers-1-million-bounty-for-return-383423" rel="noopener noreferrer"&gt;The Block&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Attackers use three techniques (&lt;a href="https://arxiv.org/pdf/2607.24172" rel="noopener noreferrer"&gt;arXiv 2607.24172&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-value transfers.&lt;/strong&gt; A &lt;code&gt;transferFrom&lt;/code&gt; call for 0 tokens emits a real-looking Transfer event without moving anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dust transfers.&lt;/strong&gt; A negligible amount of ETH or tokens makes the entry look more legitimate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fake-token transfers.&lt;/strong&gt; An attacker-deployed ERC-20 mimics the amount of the victim's real transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Large players already ship countermeasures, which sets user expectations for everyone else. MetaMask shows a blocking warning when the destination closely resembles an address from the user's history, for example when the first four and last four characters match and the middle differs, plus a separate warning for first-time recipients (&lt;a href="https://support.metamask.io/stay-safe/protect-yourself/wallet-and-hardware/address-poisoning-scams/" rel="noopener noreferrer"&gt;MetaMask Support&lt;/a&gt;). In September 2026 it expanded those warnings to mobile and the extension and added a feature that reverts transactions that do not match their preview (&lt;a href="https://decrypt.co/378117/metamask-adds-wallet-protections" rel="noopener noreferrer"&gt;Decrypt&lt;/a&gt;). Etherscan hides zero-value transfers and low-reputation tokens by default, highlights similar addresses and shows more characters of each address (&lt;a href="https://info.etherscan.com/what-is-address-poisoning/" rel="noopener noreferrer"&gt;Etherscan&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;What to build into your own flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never truncate the address on the confirmation screen. In lists, show at least six characters on each side.&lt;/li&gt;
&lt;li&gt;Hide or flag zero-value transfers, dust and transfers of unknown tokens in the transaction history.&lt;/li&gt;
&lt;li&gt;Do not offer a one-tap "copy address" on incoming transactions from unknown senders. Point users to the address book instead.&lt;/li&gt;
&lt;li&gt;Compare the recipient against history and contacts, and block the send when the edges match but the full address does not.&lt;/li&gt;
&lt;li&gt;Warn on the first transfer to any new address, and more loudly for large amounts.&lt;/li&gt;
&lt;li&gt;Against clipboard malware, show the pasted address in full and ask the user to check it against the source. For large amounts, confirm on a hardware wallet screen.&lt;/li&gt;
&lt;li&gt;After a test transfer, take the address for the main transfer from the address book, never from history. The $50M case shows why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A minimal lookalike check:&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="c1"&gt;// Flags a recipient that shares its first and last characters&lt;/span&gt;
&lt;span class="c1"&gt;// with a known address but is not the same address&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findLookalike&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;known&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;edge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;norm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^0x/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;norm&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;known&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage: block the send and show both addresses side by side&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findLookalike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;addressBook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;recentRecipients&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showBlockingWarning&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;entered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;similarTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production you would also compare a few more characters (attackers generate vanity addresses with longer matching edges as they get cheaper to compute) and highlight the differing middle part in the warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4. Simulate and preview before signing
&lt;/h2&gt;

&lt;p&gt;The confirmation screen is the last moment a mistake can be caught, so it should describe what will happen on-chain rather than echo what the user typed. Transaction simulation gives you that description.&lt;/p&gt;

&lt;p&gt;Two RPC methods do most of the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;eth_call&lt;/code&gt;&lt;/strong&gt; answers "will this succeed?". If the call reverts, the real transaction would revert with the same reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;eth_simulateV1&lt;/code&gt;&lt;/strong&gt; runs one transaction or a chain of them (approve plus swap, for example) on top of a block's state, supports state overrides and transfer tracing, and returns logs you can decode into a human-readable diff of balance changes (&lt;a href="https://docs.chainstack.com/docs/ethereum-simulate-a-uniswap-swap" rel="noopener noreferrer"&gt;Chainstack Docs&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Support goes beyond Ethereum mainnet. RPC providers expose &lt;code&gt;eth_simulateV1&lt;/code&gt; on chains like Arbitrum and Monad, and java-tron has a pull request implementing a base version specifically so wallets can show users token movements before they sign (&lt;a href="https://github.com/tronprotocol/java-tron/pull/6785" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;).&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Interface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parseUnits&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RPC_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;erc20&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Interface&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function transfer(address to, uint256 amount)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;previewTokenTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;erc20&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeFunctionData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transfer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;parseUnits&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;decimals&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eth_simulateV1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;blockStateCalls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&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;data&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;traceTransfers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;willSucceed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gasUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gasUsed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// decode Transfer events into a "you send / they receive" summary&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;decimals&lt;/code&gt; parameter. USDT and USDC on EVM chains usually use 6 decimals while most ERC-20 tokens use 18, so read &lt;code&gt;decimals()&lt;/code&gt; from the contract instead of hardcoding a table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the confirmation screen should show&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The full recipient address in checksummed form, with the address book label if there is one.&lt;/li&gt;
&lt;li&gt;The network name and icon, next to the address.&lt;/li&gt;
&lt;li&gt;The memo or tag, or a clear note that none is set when the recipient is an exchange.&lt;/li&gt;
&lt;li&gt;The amount in tokens and in fiat, the network fee and the final "you send / they receive" figures from the simulation.&lt;/li&gt;
&lt;li&gt;Warnings for a first-time recipient, a lookalike address, a contract address, or an amount far above the user's usual transfers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Extra safeguards worth adding&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A built-in test transfer flow: send a small amount, save the address, then send the rest to the saved entry.&lt;/li&gt;
&lt;li&gt;Withdrawal allowlists with a waiting period before a newly added address becomes usable, a pattern many exchanges already use.&lt;/li&gt;
&lt;li&gt;For ENS and other names, show the resolved address, normalize names per ENSIP-15 to block homoglyphs, and resolve the name again right before signing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A checklist for your next send screen
&lt;/h2&gt;

&lt;p&gt;Copy this into your next PR description or design review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Addresses are validated with the chain's checksum, not a regex.&lt;/li&gt;
&lt;li&gt;[ ] Invalid mixed-case EVM addresses are rejected, not lowercased.&lt;/li&gt;
&lt;li&gt;[ ] The address is validated against the selected network.&lt;/li&gt;
&lt;li&gt;[ ] Solana recipients get an extra "double-check this address" prompt, since the format has no checksum.&lt;/li&gt;
&lt;li&gt;[ ] Contract recipients trigger a warning (&lt;code&gt;eth_getCode&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;[ ] Network selection is explicit and shown next to the address on the confirmation screen.&lt;/li&gt;
&lt;li&gt;[ ] Memo or tag is mandatory for exchange deposits on XRP, XLM, TON, ATOM and EOS; &lt;code&gt;RequireDest&lt;/code&gt; and SEP-29 are checked.&lt;/li&gt;
&lt;li&gt;[ ] Zero-value, dust and unknown-token transfers are hidden or flagged in history.&lt;/li&gt;
&lt;li&gt;[ ] Lookalike recipients are blocked with both addresses shown in full.&lt;/li&gt;
&lt;li&gt;[ ] First-time recipients and unusually large amounts trigger a warning.&lt;/li&gt;
&lt;li&gt;[ ] The transaction is simulated, and the confirmation screen shows the resulting balance changes.&lt;/li&gt;
&lt;li&gt;[ ] Token amounts use &lt;code&gt;decimals()&lt;/code&gt; from the contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does a test transaction protect users from address poisoning?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On its own, no, and it can make things worse. Poisoning bots watch for small outgoing transfers and respond within minutes with a lookalike address that lands in the sender's history. In the December 2025 case, the attack followed a 50 USDT test transfer. A test transaction protects against typos and wrong-network mistakes. To protect against poisoning, the address for the main transfer has to come from a saved contact or the original source, never from the transaction history. If your app offers a test-transfer flow, save the address after the test and reuse that saved entry for the full amount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can funds sent on the wrong network be recovered?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It depends on who holds the key to the destination address. If a user sent tokens to their own 0x address on a different EVM chain, they can usually add that network to their wallet and access the funds. If the destination was an exchange deposit address, only the exchange can help, and the outcome depends on its policy: some recover deposits for a fee and after days or weeks, some cannot. Funds sent to a stranger's address on any network are almost always lost. That is why network selection belongs in the UI before the transaction, not in the support queue after it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is checksum validation enough to stop mistakes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. A checksum catches accidental typos, and for EIP-55 a random typo passes the check only about 0.0247% of the time. Address poisoning and clipboard malware use real, valid addresses that the attacker controls, so they pass every checksum. Validation is the first of several layers. You still need network binding, lookalike detection against the user's history and contacts, a full-address confirmation screen and, ideally, a simulation of what the transaction will do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Users already judge crypto platforms by fees, verification and supported assets, and safety at the moment of sending is part of that comparison. Every layer in this post is a few dozen lines of code or a single design decision. Put together, they turn "transactions are irreversible" from a disclaimer into a constraint your send flow is built around.&lt;/p&gt;

&lt;p&gt;What does your team do on the send screen that is not on this list? Share it in the comments.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>blockchain</category>
      <category>fintech</category>
      <category>ai</category>
    </item>
    <item>
      <title>What Changed in Crypto Product Design in 2026?</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Wed, 23 Sep 2026 14:48:18 +0000</pubDate>
      <link>https://dev.to/azaleakuts/what-changed-in-crypto-product-design-in-2026-2i38</link>
      <guid>https://dev.to/azaleakuts/what-changed-in-crypto-product-design-in-2026-2i38</guid>
      <description>&lt;p&gt;Crypto apps are becoming easier to use because more blockchain complexity is moving out of the interface.&lt;/p&gt;

&lt;p&gt;Users still interact with wallets, networks and transactions, but the product increasingly handles the technical steps behind them. A broader overview of &lt;a href="https://techiadd.it.com/crypto-apps-in-2026-whats-actually-changed-for-everyday-users/?k=daa817bf807fdabbf32c28f6c54bd817" rel="noopener noreferrer"&gt;what changed for everyday crypto app users in 2026&lt;/a&gt; points to the same shift across authentication, custody and payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Passkeys and embedded wallets simplify onboarding.&lt;/li&gt;
&lt;li&gt;Smart accounts can combine several blockchain actions into one flow.&lt;/li&gt;
&lt;li&gt;Gas sponsorship reduces the need to manage network tokens manually.&lt;/li&gt;
&lt;li&gt;Chain abstraction hides some multichain complexity.&lt;/li&gt;
&lt;li&gt;Stablecoins are being integrated into familiar payment flows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Passkeys Are Changing Onboarding&lt;/p&gt;

&lt;p&gt;Crypto onboarding has often required passwords, email verification and separate wallet setup.&lt;/p&gt;

&lt;p&gt;Passkeys offer a simpler authentication flow based on public-key cryptography. Users can authenticate with a device PIN, fingerprint or face recognition instead of entering a password. Google documents passkeys as a phishing-resistant alternative to traditional password login.&lt;/p&gt;

&lt;p&gt;Embedded wallets take this further by putting wallet functionality inside the application. A user can create or access an onchain wallet without starting with a separate wallet installation and connection flow.&lt;/p&gt;

&lt;p&gt;For developers, authentication and wallet creation are becoming part of the same product experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart Accounts Reduce Manual Steps
&lt;/h2&gt;

&lt;p&gt;Traditional Ethereum wallets expose many blockchain operations directly to the user.&lt;/p&gt;

&lt;p&gt;Smart account infrastructure allows developers to combine transactions, sponsor gas and introduce more flexible permissions.&lt;/p&gt;

&lt;p&gt;A flow that previously required several approvals can sometimes become a single user action. The underlying transactions still happen, but the interface can present the whole operation as one task.&lt;/p&gt;

&lt;p&gt;This makes the application responsible for more of the transaction flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gas Can Move Into the Background
&lt;/h2&gt;

&lt;p&gt;Users should not need to understand gas mechanics just to complete a basic action.&lt;/p&gt;

&lt;p&gt;Account abstraction supports sponsored transactions, allowing an application or another party to cover transaction costs in supported flows. EIP-7702 also introduces mechanisms for sponsored execution.&lt;/p&gt;

&lt;p&gt;The fee still exists. The product can decide how it is handled and presented instead of forcing every user to maintain a balance of the network's native token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chain Switching Is Becoming Less Visible
&lt;/h2&gt;

&lt;p&gt;Multichain products add another layer of friction. An asset can be available on one network while the user needs it on another.&lt;/p&gt;

&lt;p&gt;Chain abstraction and intent-based systems aim to hide some of this complexity. Instead of selecting networks, managing bridges and handling routing manually, users can express the outcome they want while the infrastructure handles execution.&lt;/p&gt;

&lt;p&gt;For developers, this means designing around the user's goal rather than exposing every blockchain step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stablecoin Payments Look More Like Fintech
&lt;/h2&gt;

&lt;p&gt;Stablecoins are increasingly being integrated into payment products rather than presented as a separate crypto experience.&lt;/p&gt;

&lt;p&gt;Stripe, for example, is expanding infrastructure around stablecoin payments, payouts and cards. The customer-facing flow can remain familiar while blockchain settlement happens underneath.&lt;/p&gt;

&lt;p&gt;A payment interface does not need to expose every wallet, network and settlement detail to the customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance Is Part of the Product
&lt;/h2&gt;

&lt;p&gt;Regulation also affects product design.&lt;/p&gt;

&lt;p&gt;Jurisdiction, identity verification, asset availability and transaction limits can change what a user is allowed to do. Those requirements appear directly in onboarding and transaction flows.&lt;/p&gt;

&lt;p&gt;Developers therefore have to account for compliance when designing the product itself, not only during backend implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Design Problem Has Changed
&lt;/h2&gt;

&lt;p&gt;Crypto products are moving toward a simple model:&lt;/p&gt;

&lt;p&gt;User chooses the outcome.&lt;br&gt;
The product handles more of the mechanics.&lt;/p&gt;

&lt;p&gt;Passkeys handle authentication. Embedded wallets reduce onboarding friction. Smart accounts handle more complex transactions. Sponsored gas removes one manual step. Chain abstraction hides network differences. Stablecoin infrastructure can make blockchain payments look like ordinary fintech.&lt;/p&gt;

&lt;p&gt;The challenge for developers is deciding what to hide and what users still need to see before they approve an action.&lt;/p&gt;

&lt;p&gt;The blockchain can stay in the backend without making the product feel opaque.&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>fintech</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Your Crypto Wallet Is Not Your Account: Understanding What the App Actually Controls</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Mon, 21 Sep 2026 23:23:13 +0000</pubDate>
      <link>https://dev.to/azaleakuts/your-crypto-wallet-is-not-your-account-understanding-what-the-app-actually-controls-1ffp</link>
      <guid>https://dev.to/azaleakuts/your-crypto-wallet-is-not-your-account-understanding-what-the-app-actually-controls-1ffp</guid>
      <description>&lt;p&gt;A crypto wallet can look a lot like a regular banking app. You open it, see a balance, copy an address, and press Send.&lt;/p&gt;

&lt;p&gt;But your wallet app is not your blockchain account, and your crypto is not literally stored inside the app.&lt;/p&gt;

&lt;p&gt;Understanding this difference makes crypto much easier to reason about – especially when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wallet is the interface
&lt;/h2&gt;

&lt;p&gt;On Ethereum, an account is an on-chain entity that can hold ETH and tokens and interact with smart contracts. An externally owned account (EOA) is controlled by private keys.&lt;/p&gt;

&lt;p&gt;The wallet app is the software you use to interact with that account.&lt;/p&gt;

&lt;p&gt;It can:&lt;/p&gt;

&lt;p&gt;show your balance by reading blockchain data;&lt;br&gt;
create transactions;&lt;br&gt;
ask you to approve an action;&lt;br&gt;
sign transactions;&lt;br&gt;
broadcast them to the network.&lt;/p&gt;

&lt;p&gt;The assets themselves remain recorded on the blockchain. Ethereum's documentation puts it simply: users don't store the cryptocurrency inside the wallet – they control the keys that allow them to act on the account.&lt;/p&gt;

&lt;p&gt;So deleting a wallet app does not automatically delete your crypto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then what actually gives you control?
&lt;/h2&gt;

&lt;p&gt;For a typical self-custodial wallet, the important pieces are the Secret Recovery Phrase and the private keys.&lt;/p&gt;

&lt;p&gt;A recovery phrase can be used to restore the wallet and generate its accounts. Each account has its own private key, which is used to control that particular account.&lt;/p&gt;

&lt;p&gt;That's why your wallet password is not the same thing as ownership of the assets.&lt;/p&gt;

&lt;p&gt;For example, in MetaMask, a password can unlock the wallet on a device, while the Secret Recovery Phrase is what allows the wallet to be restored.&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;/p&gt;

&lt;p&gt;Wallet app → interface&lt;br&gt;
Account → blockchain identity&lt;br&gt;
Private key → authorization&lt;br&gt;
Blockchain → asset state&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-custody changes the model
&lt;/h2&gt;

&lt;p&gt;This is also why a self-custody wallet feels different from an exchange account.&lt;/p&gt;

&lt;p&gt;With a self-custody wallet, you control the private keys. With a custodial exchange, the platform manages the keys and processes transactions on your behalf.&lt;/p&gt;

&lt;p&gt;That creates a very different recovery model.&lt;/p&gt;

&lt;p&gt;Forgot your exchange password? There may be a standard account recovery process.&lt;/p&gt;

&lt;p&gt;Lost the credentials that control a self-custodial wallet? There may be no company that can simply reset access for you. MetaMask explicitly warns that it cannot recover a wallet when the required recovery credentials are lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when you press Send?
&lt;/h2&gt;

&lt;p&gt;The wallet does not physically move coins from one app to another.&lt;/p&gt;

&lt;p&gt;Instead, it prepares a transaction and asks you to authorize it. For a traditional Ethereum account, the transaction is signed with the account's private key and then submitted to the network.&lt;/p&gt;

&lt;p&gt;The blockchain processes that transaction and updates its state.&lt;/p&gt;

&lt;p&gt;This is why crypto security is about more than protecting an app password. The bigger question is:&lt;/p&gt;

&lt;p&gt;What exactly are you authorizing with your wallet?&lt;/p&gt;

&lt;p&gt;A wallet may be just the interface, but the signature you approve can change what happens to your assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more complication: smart wallets
&lt;/h2&gt;

&lt;p&gt;The private-key model is not the only approach anymore.&lt;/p&gt;

&lt;p&gt;Ethereum also supports contract-based accounts, where authorization can be defined by smart-contract code rather than a single traditional private key. This is part of the broader account abstraction model.&lt;/p&gt;

&lt;p&gt;So the statement “your wallet is your private key” is useful as a beginner's shortcut, but it is no longer a complete description of every modern wallet.&lt;/p&gt;

&lt;p&gt;The simplest way to think about it is this:&lt;/p&gt;

&lt;p&gt;Your wallet is the tool you use to access and authorize actions. Your account is the identity on the blockchain. The blockchain records the assets and transactions.&lt;/p&gt;

&lt;p&gt;Once these pieces are separated, crypto wallets become much less mysterious – and the security risks become much easier to understand.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>fintech</category>
      <category>blockchain</category>
      <category>ai</category>
    </item>
    <item>
      <title>What a Crypto App Should Do Before You Press “Send”</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Tue, 15 Sep 2026 17:22:35 +0000</pubDate>
      <link>https://dev.to/azaleakuts/what-a-crypto-app-should-do-before-you-press-send-3cp5</link>
      <guid>https://dev.to/azaleakuts/what-a-crypto-app-should-do-before-you-press-send-3cp5</guid>
      <description>&lt;p&gt;A crypto transfer can look almost trivial on a phone.&lt;/p&gt;

&lt;p&gt;Choose an asset. Enter an address. Type an amount. Press Send.&lt;/p&gt;

&lt;p&gt;The blockchain sees something very different.&lt;/p&gt;

&lt;p&gt;On Ethereum, a transaction is a signed instruction that can contain a recipient address, value, nonce, gas parameters and input data. Once signed and broadcast, it enters the transaction pool and waits for a validator to include it in a block. Eventually, the block can become finalized.&lt;/p&gt;

&lt;p&gt;The interesting part is that the blockchain cannot tell whether the user meant to send the funds to that particular address.&lt;/p&gt;

&lt;p&gt;That decision happens before the transaction reaches the network.&lt;/p&gt;

&lt;p&gt;This makes the wallet interface more than a convenient way to access a blockchain. It is one of the last places where a human can catch a mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  The address is not enough
&lt;/h2&gt;

&lt;p&gt;Wallet addresses are not exactly designed for humans.&lt;/p&gt;

&lt;p&gt;An Ethereum address is a hexadecimal string. Copying one from a previous transaction is easier than trying to read it character by character. That convenience creates an opportunity for attackers.&lt;/p&gt;

&lt;p&gt;A technique known as address poisoning exploits this behavior.&lt;/p&gt;

&lt;p&gt;An attacker creates an address that looks similar to one the victim has used before and sends a transaction designed to make the malicious address appear in the victim's transaction history. If the victim later copies the wrong address from that history, the funds go somewhere else.&lt;/p&gt;

&lt;p&gt;A 2025 study that measured address poisoning on Ethereum and BSC over two years identified 270 million attack attempts targeting 17 million victims. The researchers linked 6,633 incidents to at least $83.8 million in losses.&lt;/p&gt;

&lt;p&gt;That's a useful reminder that "the user entered the wrong address" is not always a user problem.&lt;/p&gt;

&lt;p&gt;Sometimes the interface has made the wrong address too easy to trust.&lt;/p&gt;

&lt;p&gt;A wallet should make the recipient difficult to misread.&lt;/p&gt;

&lt;p&gt;That can mean showing a recognizable name when one is available, making the beginning and end of the address easy to compare, supporting verified address books, and warning users when a destination looks suspicious or has not been used before.&lt;/p&gt;

&lt;p&gt;The final confirmation screen should also show the actual destination, not just a shortened label.&lt;/p&gt;

&lt;h2&gt;
  
  
  The network should be impossible to miss
&lt;/h2&gt;

&lt;p&gt;"Send USDT" is not enough information.&lt;/p&gt;

&lt;p&gt;The asset and the network are separate pieces of information, and the same token can exist across multiple networks.&lt;/p&gt;

&lt;p&gt;A useful confirmation screen should therefore make the combination explicit:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;500 USDT&lt;br&gt;
Ethereum&lt;br&gt;
To: 0x71...92F4&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That seems like a small interface decision, but it removes an entire category of ambiguity.&lt;/p&gt;

&lt;p&gt;The same principle applies to mobile apps. A smaller screen creates pressure to hide information behind menus, but transaction confirmation is probably the wrong place to optimize for minimalism.&lt;/p&gt;

&lt;p&gt;For a practical look at how mobile crypto apps combine wallet access, transfers, QR codes and transaction management, &lt;a href="https://changenow.io/blog/mobile-crypto-app-benefits" rel="noopener noreferrer"&gt;the ChangeNOW guide to mobile crypto app features is a useful reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The goal isn't to show every technical detail.&lt;/p&gt;

&lt;p&gt;It is to show the details that can change the outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show what the transaction will actually cost
&lt;/h2&gt;

&lt;p&gt;Fees are another part of the transaction that should not be left for the user to decode.&lt;/p&gt;

&lt;p&gt;On Ethereum, gas measures the computational work required to execute a transaction. A simple ETH transfer requires 21,000 units of gas, while interactions with smart contracts can require considerably more.&lt;/p&gt;

&lt;p&gt;The fee also isn't just an arbitrary number produced by the wallet.&lt;/p&gt;

&lt;p&gt;Ethereum's fee model includes a base fee and a priority fee. The amount a user ultimately pays depends on gas used and the applicable fee parameters.&lt;/p&gt;

&lt;p&gt;A good interface should translate that into something a person can understand:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sending: 1 ETH&lt;br&gt;
Network fee: 0.00025 ETH&lt;br&gt;
Total deducted: 1.00025 ETH&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The user shouldn't have to understand maxFeePerGas before sending money.&lt;/p&gt;

&lt;p&gt;That's the wallet's job.&lt;/p&gt;

&lt;h2&gt;
  
  
  "What am I signing?" is a better question
&lt;/h2&gt;

&lt;p&gt;There is another problem that becomes more important once a wallet does more than simple transfers.&lt;/p&gt;

&lt;p&gt;A transaction doesn't necessarily mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Send this amount to this person.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can also mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Interact with this smart contract.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ethereum documentation distinguishes ordinary transfers from transactions that execute deployed smart contracts. In the latter case, the to address can belong to a contract, while the transaction's input data determines what the contract is asked to do.&lt;/p&gt;

&lt;p&gt;That changes the UX problem.&lt;/p&gt;

&lt;p&gt;Showing a contract address and a button labeled Sign doesn't tell most users much.&lt;/p&gt;

&lt;p&gt;For a swap, approval, DeFi interaction or other contract call, the wallet should explain the intended effect in human-readable terms whenever it can.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;You are authorizing:&lt;br&gt;
Swap 500 USDC for ETH&lt;/p&gt;

&lt;p&gt;Estimated result:&lt;br&gt;
≈ 0.18 ETH&lt;/p&gt;

&lt;p&gt;Network fee:&lt;br&gt;
≈ $1.40&lt;/p&gt;

&lt;p&gt;Contract:&lt;br&gt;
Verified contract name, if available&lt;/p&gt;

&lt;p&gt;That is much more useful than presenting a wall of hexadecimal data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulation is useful, but it isn't magic
&lt;/h2&gt;

&lt;p&gt;Transaction simulation is becoming an important part of wallet security.&lt;/p&gt;

&lt;p&gt;Instead of asking the user to sign first and find out what happened later, a wallet can simulate the transaction and estimate the expected balance changes or contract effects before broadcasting it.&lt;/p&gt;

&lt;p&gt;That is a significant improvement in the user experience.&lt;/p&gt;

&lt;p&gt;But simulation shouldn't be treated as a guarantee.&lt;/p&gt;

&lt;p&gt;A 2026 study of transaction simulation phishing found more than 4,000 phishing contracts deployed between August 2024 and June 2025. The researchers identified more than 5,700 victims and approximately $3.48 million in losses, with 91.5% of those losses occurring on Ethereum.&lt;/p&gt;

&lt;p&gt;The problem is subtle.&lt;/p&gt;

&lt;p&gt;Some malicious contracts can behave differently during simulation and actual execution because their behavior depends on changing blockchain state. A simulation can therefore show a benign result while the eventual transaction behaves differently.&lt;/p&gt;

&lt;p&gt;So the lesson isn't that simulation doesn't work.&lt;/p&gt;

&lt;p&gt;It's that one security signal shouldn't become the entire security model.&lt;/p&gt;

&lt;p&gt;Simulation is one layer. Address checks, contract reputation, readable signing information and user confirmation are other layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warnings need to tell the user what to do
&lt;/h2&gt;

&lt;p&gt;A red warning isn't automatically a useful warning.&lt;/p&gt;

&lt;p&gt;Compare these two messages:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Suspicious transaction&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ This is the first time you've sent funds to this address. Verify the full address before continuing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second one gives the user a decision to make.&lt;/p&gt;

&lt;p&gt;That's important because security warnings often appear at exactly the moment when the user wants to finish the task. If the warning is vague, people are more likely to treat it as another piece of interface noise.&lt;/p&gt;

&lt;p&gt;Research on wallet usability shows that this is still a real problem.&lt;/p&gt;

&lt;p&gt;A 2025 study evaluated 53 popular Ethereum wallets specifically in the context of address poisoning. The researchers found that 16 wallets created a high risk by displaying fake token phishing transfers, while only three wallets displayed an explicit warning when users attempted to transfer funds to a phishing address.&lt;/p&gt;

&lt;p&gt;That doesn't mean those three wallets are universally safe or that the other wallets are universally insecure.&lt;/p&gt;

&lt;p&gt;It does show how much room there is for improvement in the way wallets communicate transaction risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  The confirmation screen is a security feature
&lt;/h2&gt;

&lt;p&gt;The best confirmation screen doesn't need to expose every blockchain parameter.&lt;/p&gt;

&lt;p&gt;It needs to answer the questions a user actually has before signing:&lt;/p&gt;

&lt;p&gt;What am I sending?&lt;/p&gt;

&lt;p&gt;500 USDC&lt;/p&gt;

&lt;p&gt;Which network?&lt;/p&gt;

&lt;p&gt;Ethereum&lt;/p&gt;

&lt;p&gt;Where is it going?&lt;/p&gt;

&lt;p&gt;Alice&lt;br&gt;
0x71...92F4&lt;/p&gt;

&lt;p&gt;What will it cost?&lt;/p&gt;

&lt;p&gt;Estimated network fee: $2.14&lt;/p&gt;

&lt;p&gt;What am I authorizing?&lt;/p&gt;

&lt;p&gt;Transfer / swap / approval / contract interaction&lt;/p&gt;

&lt;p&gt;Did the wallet detect anything unusual?&lt;/p&gt;

&lt;p&gt;First-time recipient / suspicious contract / simulation result&lt;/p&gt;

&lt;p&gt;Then comes the final action:&lt;/p&gt;

&lt;p&gt;Review and Sign&lt;/p&gt;

&lt;p&gt;This is a better model than treating confirmation as the last visual step before a button click.&lt;/p&gt;

&lt;p&gt;It is a checkpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good crypto UX should slow you down at the right moment
&lt;/h2&gt;

&lt;p&gt;Most of the time, a wallet should feel fast.&lt;/p&gt;

&lt;p&gt;You shouldn't need three screens to send funds to an address you've verified dozens of times.&lt;/p&gt;

&lt;p&gt;But when something changes – a new address, a different network, an unusual contract interaction or a potentially risky approval – the interface should become more deliberate.&lt;/p&gt;

&lt;p&gt;That's where good crypto UX differs from simply making transactions faster.&lt;/p&gt;

&lt;p&gt;The objective isn't to remove friction everywhere.&lt;/p&gt;

&lt;p&gt;It is to remove unnecessary friction while keeping the friction that helps a user catch an expensive mistake.&lt;/p&gt;

&lt;p&gt;A blockchain will execute a valid signed transaction because the signature is valid. It doesn't know what the user intended to do.&lt;/p&gt;

&lt;p&gt;The wallet sits between those two worlds.&lt;/p&gt;

&lt;p&gt;That makes the Send screen one of the most important security surfaces in a crypto application.&lt;/p&gt;

&lt;p&gt;The best crypto app isn't necessarily the one that gets you from balance to transaction hash in the fewest taps.&lt;/p&gt;

&lt;p&gt;It's the one that gives you a good reason to pause before the tap that actually matters.&lt;/p&gt;

</description>
      <category>app</category>
      <category>cryptocurrency</category>
      <category>crypto</category>
      <category>fintech</category>
    </item>
    <item>
      <title>AI Agents Can Make Payments. The Hard Part Is Proving They Should.</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Mon, 14 Sep 2026 15:58:13 +0000</pubDate>
      <link>https://dev.to/azaleakuts/ai-agents-can-make-payments-the-hard-part-is-proving-they-should-30ne</link>
      <guid>https://dev.to/azaleakuts/ai-agents-can-make-payments-the-hard-part-is-proving-they-should-30ne</guid>
      <description>&lt;p&gt;AI agents are moving beyond answering questions and recommending products. They can already search for items, compare options, interact with merchants and, in some cases, complete purchases on a user's behalf.&lt;/p&gt;

&lt;p&gt;That creates a problem payment systems were not originally designed for: how does a merchant know that an AI agent is actually authorized to make a particular payment?&lt;/p&gt;

&lt;p&gt;This is becoming an infrastructure problem, not just an AI problem.&lt;/p&gt;

&lt;p&gt;In September 2026, Ant International, Mastercard and Visa announced that they had started working on a shared Know Your Agent (KYA) interoperability framework. The goal is to help payment networks, wallets, agent platforms and marketplaces identify and verify AI agents while allowing each participant to maintain its own risk and approval processes.&lt;/p&gt;

&lt;p&gt;The interesting part is not the name KYA. It is the trust layer that has to sit between an AI agent and a financial transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payment flow is changing
&lt;/h2&gt;

&lt;p&gt;A conventional online purchase is relatively straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → Merchant → Payment Provider → Bank/Card Network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agentic transaction adds another layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → AI Agent → Merchant → Payment Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent may be acting on the user's behalf, but the merchant still needs to understand what that authority actually means.&lt;/p&gt;

&lt;p&gt;Suppose a user tells an agent:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find a laptop under $1,500 with at least 16GB of RAM and buy it if you find a suitable one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The agent has to search, compare products, choose an option and potentially complete the purchase without asking the user to approve every step.&lt;/p&gt;

&lt;p&gt;The payment system needs more than an answer to “Is this a real agent?”&lt;/p&gt;

&lt;p&gt;It needs to know who authorized it, what it was authorized to do and whether the final transaction stays within those limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identity is only the first layer
&lt;/h2&gt;

&lt;p&gt;Visa, Mastercard and Ant International already have technologies aimed at different parts of this problem.&lt;/p&gt;

&lt;p&gt;Visa's Trusted Agent Protocol is designed to help merchants verify AI agents and establish the context of their requests.&lt;/p&gt;

&lt;p&gt;Mastercard has been developing Verifiable Intent, which focuses on verifying that an agent's action corresponds to an instruction authorized by the user.&lt;/p&gt;

&lt;p&gt;Ant International has developed its own Agentic Mobile Protocol for transactions involving digital wallets.&lt;/p&gt;

&lt;p&gt;The new KYA initiative is therefore not simply about creating another payment method. It is about making different agent ecosystems work together.&lt;/p&gt;

&lt;p&gt;That interoperability could become important for developers. If every payment network, wallet and marketplace uses a different system for agent identity and authorization, developers could end up maintaining another fragmented set of integrations.&lt;/p&gt;

&lt;p&gt;A common set of trust signals could reduce that complexity while allowing individual networks to keep their own risk controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication does not mean authorization
&lt;/h2&gt;

&lt;p&gt;This is where the architecture gets more interesting.&lt;/p&gt;

&lt;p&gt;Imagine an agent is successfully authenticated. The payment system knows which agent it is and who operates it.&lt;/p&gt;

&lt;p&gt;That still does not answer the most important question:&lt;/p&gt;

&lt;p&gt;What is the agent actually allowed to do?&lt;/p&gt;

&lt;p&gt;A useful way to think about the problem is to separate four layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent Identity
      ↓
User Authorization
      ↓
Transaction Intent
      ↓
Execution + Monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer answers a different question.&lt;/p&gt;

&lt;p&gt;Identity: Who is this agent?&lt;/p&gt;

&lt;p&gt;Authorization: Which user or organization delegated authority to it?&lt;/p&gt;

&lt;p&gt;Intent: What action was the agent authorized to perform?&lt;/p&gt;

&lt;p&gt;Execution: Did the final transaction stay within those limits?&lt;/p&gt;

&lt;p&gt;This separation matters because a legitimate agent can still make an unauthorized transaction.&lt;/p&gt;

&lt;p&gt;For example, an agent might have permission to spend up to $1,500 but attempt to complete a $1,900 purchase after the final price changes.&lt;/p&gt;

&lt;p&gt;The identity check would pass. The authorization check should not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spending permissions may become part of the API
&lt;/h2&gt;

&lt;p&gt;For developers, this could eventually mean that an AI agent's payment credentials look less like a traditional card number and more like a set of constrained permissions.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shopping-agent-42&lt;/span&gt;
&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;verified-user&lt;/span&gt;
&lt;span class="na"&gt;Maximum transaction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$1,500&lt;/span&gt;
&lt;span class="na"&gt;Allowed category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Electronics&lt;/span&gt;
&lt;span class="na"&gt;Allowed merchants&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Approved merchants&lt;/span&gt;
&lt;span class="na"&gt;Expiration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24 hours&lt;/span&gt;
&lt;span class="na"&gt;Additional approval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Required above limit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact implementation will vary by payment network, and the KYA framework is still being developed. There is no universal schema that developers can simply implement today.&lt;/p&gt;

&lt;p&gt;But the underlying design problem is already clear.&lt;/p&gt;

&lt;p&gt;Agents need enough authority to be useful without receiving unrestricted access to a user's money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same problem is appearing elsewhere
&lt;/h2&gt;

&lt;p&gt;This is not limited to Visa and Mastercard.&lt;/p&gt;

&lt;p&gt;India's National Payments Corporation is reportedly developing a registry for AI agents that will make transactions through UPI. The initial focus is on frequent, relatively small payments, with more complex use cases potentially coming later.&lt;/p&gt;

&lt;p&gt;The direction is becoming clear: once an AI system can initiate financial transactions without a user manually approving every step, agent identity and delegated authority become part of payment infrastructure.&lt;/p&gt;

&lt;p&gt;That also changes the security model.&lt;/p&gt;

&lt;p&gt;A compromised card is one problem. A compromised agent with permission to make hundreds of transactions could be a very different one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the agent gets it wrong?
&lt;/h2&gt;

&lt;p&gt;This may be the hardest part of the problem.&lt;/p&gt;

&lt;p&gt;Authentication and authorization can be implemented as technical mechanisms. Liability is harder.&lt;/p&gt;

&lt;p&gt;Imagine an agent follows a user's instruction but misunderstands the product. Or a merchant's page contains content designed to manipulate the agent. Or the agent makes a purchase after its authorization should have expired.&lt;/p&gt;

&lt;p&gt;Who is responsible?&lt;/p&gt;

&lt;p&gt;The user?&lt;/p&gt;

&lt;p&gt;The agent provider?&lt;/p&gt;

&lt;p&gt;The merchant?&lt;/p&gt;

&lt;p&gt;The payment network?&lt;/p&gt;

&lt;p&gt;The answer will influence how these systems are designed.&lt;/p&gt;

&lt;p&gt;There are other open questions too: how quickly permissions can be revoked, how transactions are audited, what information merchants actually need to see and how privacy is maintained when several systems exchange identity and authorization data.&lt;/p&gt;

&lt;p&gt;These issues become more serious when agents operate at machine speed.&lt;/p&gt;

&lt;p&gt;A human might notice a suspicious purchase after one transaction. An automated system could repeat the same mistake many times before anyone intervenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next payment layer may be a trust layer
&lt;/h2&gt;

&lt;p&gt;Payment infrastructure is starting to account for an actor that is neither a traditional customer nor simply a machine.&lt;/p&gt;

&lt;p&gt;An AI agent represents a person or business, interprets their instructions and takes actions on their behalf.&lt;/p&gt;

&lt;p&gt;Payment systems therefore need to establish more than whether a transaction is technically valid.&lt;/p&gt;

&lt;p&gt;They need to establish whether the agent had the right to make it.&lt;/p&gt;

&lt;p&gt;That is why KYA is worth watching. The framework is still being developed, and there is no universal standard yet. But the direction is clear: agent identity, authorization, intent, permissions and auditability are becoming part of the conversation as AI moves from recommending actions to executing them.&lt;/p&gt;

&lt;p&gt;The difficult part of agentic payments may not be teaching AI how to transact.&lt;/p&gt;

&lt;p&gt;It may be building systems that can prove when it should.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>fintech</category>
      <category>crypto</category>
    </item>
    <item>
      <title>MiCA After July 2026: What Actually Changes for Crypto Users and Developers</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Thu, 10 Sep 2026 15:26:35 +0000</pubDate>
      <link>https://dev.to/azaleakuts/mica-after-july-2026-what-actually-changes-for-crypto-users-and-developers-3ljj</link>
      <guid>https://dev.to/azaleakuts/mica-after-july-2026-what-actually-changes-for-crypto-users-and-developers-3ljj</guid>
      <description>&lt;p&gt;MiCA After July 2026: What Actually Changes for Crypto Users and Developers&lt;/p&gt;

&lt;p&gt;For a long time, MiCA was something crypto companies talked about as a future compliance problem.&lt;/p&gt;

&lt;p&gt;That future has arrived.&lt;/p&gt;

&lt;p&gt;The EU's Markets in Crypto-Assets Regulation has been fully applicable since December 2024, but 2026 was an important transition point for crypto-asset service providers. By July 1, 2026, the transitional period for firms operating under previous national regimes had ended at EU level. After that date, a provider offering crypto-asset services in the EU generally needs to operate under the MiCA framework or another applicable legal basis.&lt;/p&gt;

&lt;p&gt;For developers, this changes the way compliance fits into a crypto product. It is no longer something that can sit entirely with the legal team. In many cases, regulatory requirements eventually become product requirements, database fields, API checks and user flows.&lt;/p&gt;

&lt;p&gt;And for users, those changes are much more visible than the regulation itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  MiCA is more than a licensing framework
&lt;/h2&gt;

&lt;p&gt;MiCA creates a harmonised EU framework for crypto-assets and the companies that issue them or provide related services. It covers areas including crypto-asset service providers, stablecoins, disclosure, market conduct and consumer protection.&lt;/p&gt;

&lt;p&gt;A developer does not need to read hundreds of pages of regulation to understand the practical idea.&lt;/p&gt;

&lt;p&gt;If a platform is doing something with crypto on behalf of its users, the first question is increasingly:&lt;/p&gt;

&lt;p&gt;What exactly is the service, and under which regulatory framework is it allowed to operate?&lt;/p&gt;

&lt;p&gt;That question affects exchanges, custodians, brokers, wallet-related services and other crypto businesses.&lt;/p&gt;

&lt;p&gt;It also affects how products are designed.&lt;/p&gt;

&lt;p&gt;Consider a simple withdrawal flow.&lt;/p&gt;

&lt;p&gt;Before regulatory requirements become product requirements, the flow might look like:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;User → Enter address → Confirm → Transaction sent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In a regulated environment, the actual flow can be more complicated:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;User&lt;br&gt;
  ↓&lt;br&gt;
Enter destination&lt;br&gt;
  ↓&lt;br&gt;
Compliance checks&lt;br&gt;
  ↓&lt;br&gt;
Additional information if required&lt;br&gt;
  ↓&lt;br&gt;
Risk decision&lt;br&gt;
  ↓&lt;br&gt;
Transaction processing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The blockchain transaction itself has not changed.&lt;/p&gt;

&lt;p&gt;The surrounding software has.&lt;/p&gt;

&lt;h2&gt;
  
  
  July 1, 2026 changed the practical landscape
&lt;/h2&gt;

&lt;p&gt;The end of the transition period is one of the most relevant dates for anyone building or using a crypto service in the EU.&lt;/p&gt;

&lt;p&gt;ESMA stated that the transitional period would expire across the EU on July 1, 2026. After that point, firms providing crypto-asset services without the required authorisation can no longer rely on the old transitional arrangements.&lt;/p&gt;

&lt;p&gt;That does not mean every crypto company suddenly disappeared on July 1.&lt;/p&gt;

&lt;p&gt;It means the legal basis for providing regulated crypto services became much clearer.&lt;/p&gt;

&lt;p&gt;For product teams, that creates a fairly practical task: the service needs to know where it is allowed to operate, what authorisation covers it, and what happens when a user falls outside the supported regulatory scope.&lt;/p&gt;

&lt;p&gt;This is where regulation starts looking less like a legal document and more like system architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does this mean for crypto users?
&lt;/h2&gt;

&lt;p&gt;The biggest change for an ordinary user is not likely to be a message saying "MiCA compliance enabled."&lt;/p&gt;

&lt;p&gt;It is more likely to be another verification step.&lt;/p&gt;

&lt;p&gt;A platform may ask for additional information during onboarding. A transaction may require an additional compliance check. A withdrawal to a self-hosted wallet may involve questions that did not appear in the same form before.&lt;/p&gt;

&lt;p&gt;These processes are not all caused by MiCA alone. Crypto businesses also operate under broader AML/CFT and transfer-of-funds requirements.&lt;/p&gt;

&lt;p&gt;But from a user's perspective, they can appear in the same place: the interface.&lt;/p&gt;

&lt;p&gt;That is why understanding the practical side of MiCA is often more useful than simply knowing what the acronym stands for. A practical overview of the regulation, its licensing framework, stablecoin rules and the way compliance can affect users is available in &lt;a href="https://changenow.io/blog/mica-regulation-explained" rel="noopener noreferrer"&gt;this guide to MiCA&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The important part is not that users should expect every transaction to trigger a check.&lt;/p&gt;

&lt;p&gt;They should understand why a regulated platform may sometimes need more information before processing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-custody is a different case
&lt;/h2&gt;

&lt;p&gt;One of the easiest things to misunderstand about European crypto regulation is self-custody.&lt;/p&gt;

&lt;p&gt;If you hold the private keys to your wallet, that does not automatically turn your wallet into a regulated crypto service.&lt;/p&gt;

&lt;p&gt;MiCA is primarily concerned with regulated activities and intermediaries. A user controlling their own assets is different from a company holding or transferring crypto-assets on behalf of clients.&lt;/p&gt;

&lt;p&gt;This distinction becomes especially important when designing wallet products.&lt;/p&gt;

&lt;p&gt;A developer building a non-custodial wallet is not necessarily building the same regulatory product as a company providing custody.&lt;/p&gt;

&lt;p&gt;The interface may look similar.&lt;/p&gt;

&lt;p&gt;The legal responsibilities can be very different.&lt;/p&gt;

&lt;p&gt;There is also another layer to consider: a self-hosted wallet does not make compliance obligations disappear when it interacts with a regulated service. A user may still encounter additional checks when moving assets between a regulated provider and a self-hosted address, depending on the transaction and the applicable AML/transfer-of-funds requirements.&lt;/p&gt;

&lt;p&gt;So "self-custody" should not be treated as a synonym for "outside regulation."&lt;/p&gt;

&lt;h2&gt;
  
  
  Stablecoins have their own regulatory logic
&lt;/h2&gt;

&lt;p&gt;MiCA also treats stablecoins differently from many other crypto-assets.&lt;/p&gt;

&lt;p&gt;The framework distinguishes between asset-referenced tokens and e-money tokens, with specific requirements around issuance, governance, reserves and information provided to users. The European Commission describes these requirements as part of MiCA's prudential and consumer-protection framework.&lt;/p&gt;

&lt;p&gt;This matters technically because stablecoins are increasingly used as infrastructure rather than simply as trading assets.&lt;/p&gt;

&lt;p&gt;They can sit inside:&lt;/p&gt;

&lt;p&gt;payment flows&lt;br&gt;
treasury systems&lt;br&gt;
exchange infrastructure&lt;br&gt;
settlement systems&lt;br&gt;
trading applications&lt;br&gt;
automated financial workflows&lt;/p&gt;

&lt;p&gt;When an asset becomes part of infrastructure, its regulatory status becomes an engineering concern too.&lt;/p&gt;

&lt;p&gt;For example, a payment application may need to know whether a particular token can be offered to EU users, whether the relevant issuer meets the applicable requirements, and what restrictions apply to the service built around it.&lt;/p&gt;

&lt;p&gt;The smart contract does not answer those questions.&lt;/p&gt;

&lt;p&gt;The product architecture has to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance becomes an engineering problem
&lt;/h2&gt;

&lt;p&gt;This is probably the most interesting consequence of MiCA for developers.&lt;/p&gt;

&lt;p&gt;Regulatory requirements eventually become software.&lt;/p&gt;

&lt;p&gt;A legal requirement can turn into:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;if provider_is_authorized:&lt;br&gt;
    allow_service()&lt;br&gt;
else:&lt;br&gt;
    restrict_service()&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A reporting obligation can become an event pipeline.&lt;/p&gt;

&lt;p&gt;A customer verification requirement can become an onboarding state machine.&lt;/p&gt;

&lt;p&gt;A risk-control requirement can become a transaction screening service.&lt;/p&gt;

&lt;p&gt;An audit requirement can become structured logs that cannot simply be deleted after the transaction is complete.&lt;/p&gt;

&lt;p&gt;None of this means developers should try to translate legislation into code by themselves.&lt;/p&gt;

&lt;p&gt;It means engineering teams increasingly need to understand the assumptions behind the systems they are building.&lt;/p&gt;

&lt;p&gt;A compliance rule that arrives at the end of development is expensive.&lt;/p&gt;

&lt;p&gt;A compliance requirement considered during architecture is usually much easier to handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should developers think about a MiCA-ready product?
&lt;/h2&gt;

&lt;p&gt;I would start with four questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. What service are we actually providing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Crypto platform" is too vague.&lt;/p&gt;

&lt;p&gt;Are you providing custody? Exchange? Execution? Transfers? Advice? Something else?&lt;/p&gt;

&lt;p&gt;The answer determines which regulatory questions matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Who is the user?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Geography is not just a marketing parameter anymore.&lt;/p&gt;

&lt;p&gt;If the product is available to EU users, the team needs to understand whether the service falls within MiCA or another regulatory framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Where does compliance happen?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "somewhere in the backend," that is probably not enough.&lt;/p&gt;

&lt;p&gt;Developers should know which API, service or workflow makes the relevant decision and what happens when the decision is negative or inconclusive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Can the system explain what happened?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is easy to overlook.&lt;/p&gt;

&lt;p&gt;A compliance system that simply returns true or false may not be enough for a real production environment. Teams may need an auditable record of which checks were performed, when they were performed and what information was used.&lt;/p&gt;

&lt;p&gt;That is where good engineering practices become particularly valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  MiCA is not finished yet
&lt;/h2&gt;

&lt;p&gt;There is another reason not to treat MiCA as a completely finished rulebook.&lt;/p&gt;

&lt;p&gt;The European Commission is currently reviewing the regulation and is consulting stakeholders on whether MiCA remains fit for purpose in light of implementation experience and developments in crypto markets and policy. The Commission's targeted consultation is open until September 30, 2026.&lt;/p&gt;

&lt;p&gt;So the regulatory environment is still evolving.&lt;/p&gt;

&lt;p&gt;That is not necessarily a bad thing.&lt;/p&gt;

&lt;p&gt;Crypto infrastructure is changing quickly, and regulation built around an earlier version of the market will inevitably need to be tested against new products, stablecoins, tokenisation models and financial applications.&lt;/p&gt;

&lt;p&gt;For developers, this means one thing above all: avoid building compliance assumptions too deeply into the product.&lt;/p&gt;

&lt;p&gt;Make them configurable where possible.&lt;/p&gt;

&lt;p&gt;Keep regulatory logic separate from core transaction logic.&lt;/p&gt;

&lt;p&gt;Document why a particular restriction exists.&lt;/p&gt;

&lt;p&gt;And make it possible to update the system when the rules change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;MiCA is often discussed as a question of licences and regulation.&lt;/p&gt;

&lt;p&gt;For developers, the more interesting question is what happens after the licence is obtained.&lt;/p&gt;

&lt;p&gt;The real work moves into product design.&lt;/p&gt;

&lt;p&gt;Who can access the service?&lt;br&gt;
Which assets can be offered?&lt;br&gt;
When is additional verification required?&lt;br&gt;
How are transactions screened?&lt;br&gt;
What information has to be stored?&lt;br&gt;
How can the system demonstrate what happened?&lt;/p&gt;

&lt;p&gt;Those are software questions as much as legal ones.&lt;/p&gt;

&lt;p&gt;And that may be the biggest shift MiCA brings to European crypto infrastructure: compliance is becoming part of the product itself, rather than something that happens around the product.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>web3</category>
      <category>fintech</category>
      <category>mica</category>
    </item>
    <item>
      <title>Claude in Fintech: 5 Workflows Where AI Agents Can Actually Save Time</title>
      <dc:creator>Azalea</dc:creator>
      <pubDate>Wed, 09 Sep 2026 17:12:06 +0000</pubDate>
      <link>https://dev.to/azaleakuts/claude-in-fintech-5-workflows-where-ai-agents-can-actually-save-time-3c02</link>
      <guid>https://dev.to/azaleakuts/claude-in-fintech-5-workflows-where-ai-agents-can-actually-save-time-3c02</guid>
      <description>&lt;p&gt;Financial teams have plenty of work that looks small on paper and takes hours in practice.&lt;/p&gt;

&lt;p&gt;A compliance analyst may need to review a customer file, collect information from several documents, check transactions, prepare a case summary, and then pass everything to another person for review. The same pattern appears in financial research, fraud investigations and reporting.&lt;/p&gt;

&lt;p&gt;This is where AI agents are becoming more interesting than a basic chatbot.&lt;/p&gt;

&lt;p&gt;Anthropic has introduced financial-services agents that can work across multi-step tasks, use external tools and data, and keep a human involved before an important action is taken. The company has also announced integrations with financial data providers and examples involving KYC, fraud, financial research, modelling and AML investigations.&lt;/p&gt;

&lt;p&gt;Here are five workflows where that approach can reduce manual work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. KYC and customer due diligence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;KYC is a natural candidate for workflow automation because the work often starts with documents and ends with a reviewable case.&lt;/p&gt;

&lt;p&gt;Anthropic lists KYC screening among its financial-services agent templates. The workflow can involve building an entity file, reviewing source documents and preparing an escalation package for a compliance professional.&lt;/p&gt;

&lt;p&gt;The interesting part is what happens between those steps.&lt;/p&gt;

&lt;p&gt;An agent can work through the available material and organize the relevant information before a compliance analyst makes the final call. That leaves the person with a prepared case instead of a collection of documents that still needs to be sorted manually.&lt;/p&gt;

&lt;p&gt;There is a concrete example from Parcha. According to Anthropic, the company used Claude for customer due diligence and reduced one workflow from three months to five minutes.&lt;/p&gt;

&lt;p&gt;That figure comes from a specific customer case, so it should not be treated as a general benchmark for KYC teams. Still, it shows why document-heavy compliance work is being considered for agentic systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fraud investigation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fraud detection is another area where the investigation itself can involve a lot of repetitive work.&lt;/p&gt;

&lt;p&gt;A suspicious case may require looking at transaction information, documents and other risk signals before an analyst can understand what happened. An AI agent can help assemble that context and produce a case summary for review.&lt;/p&gt;

&lt;p&gt;Inscribe is one example cited by Anthropic. The company uses Claude for fraud detection, document verification, risk analysis and KYC/KYB checks. Anthropic reports that the time required for a fraud review went from 30 minutes to 90 seconds in the cited case.&lt;/p&gt;

&lt;p&gt;In another customer example, Inscribe reported a 70-fold increase in processing output.&lt;/p&gt;

&lt;p&gt;Again, these are company-specific results rather than an industry-wide measurement. They are useful mainly because they show the type of work being delegated to an AI system: collecting and assessing information around a fraud case, rather than simply generating a piece of text.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A fraud analyst still needs to understand the evidence and decide what should happen to the case. The agent can take care of part of the preparation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Financial research&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Financial research presents a slightly different problem.&lt;/p&gt;

&lt;p&gt;The difficult part is often not writing the final report. It is getting the right information first.&lt;/p&gt;

&lt;p&gt;Anthropic has connected Claude with financial research and data platforms including FactSet, S&amp;amp;P Capital IQ, MSCI, PitchBook, Morningstar, LSEG and Daloopa. Other connectors include Dun &amp;amp; Bradstreet, Fiscal AI and Financial Modeling Prep.&lt;/p&gt;

&lt;p&gt;A research workflow can therefore look something like this:&lt;/p&gt;

&lt;p&gt;Research question → financial data → analysis → draft report → analyst review&lt;/p&gt;

&lt;p&gt;The model does not need to rely entirely on information already inside the model. It can work with external sources provided through the available tools.&lt;/p&gt;

&lt;p&gt;This also connects with research from the Bank for International Settlements. BIS has discussed the use of generative AI in economic analysis, financial supervision and payment oversight, and has pointed to retrieval-augmented generation (RAG) as one way to improve the reliability of outputs by connecting models with verified and specialized information.&lt;/p&gt;

&lt;p&gt;Cabe destacar one practical point here: source access does not remove the need to check the result. It changes where some of the manual work happens.&lt;/p&gt;

&lt;p&gt;Instead of searching through several systems and then starting the analysis from scratch, an analyst can review the material assembled by the workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Financial models and reporting&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spreadsheets are another place where AI assistance can fit into an existing financial workflow.&lt;/p&gt;

&lt;p&gt;Anthropic says Claude can work with Excel, PowerPoint and Word through Microsoft 365 add-ins. Context can also move between applications. For example, work started in a financial model can continue when preparing a presentation.&lt;/p&gt;

&lt;p&gt;Citadel is one of the examples Anthropic gives. Its investment professionals use Claude for Excel to create and update coverage models, separate signal from noise and pressure-test their work.&lt;/p&gt;

&lt;p&gt;The useful part here is not simply asking an AI to “analyze an Excel file.”&lt;/p&gt;

&lt;p&gt;A more practical workflow is closer to:&lt;/p&gt;

&lt;p&gt;Financial model → analysis → scenario work → checks → summary → presentation&lt;/p&gt;

&lt;p&gt;Some of those steps are repetitive. Others require financial judgment.&lt;/p&gt;

&lt;p&gt;The division between them is important. A model can help with the preparation and analysis while the professional remains responsible for the financial interpretation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. AML and compliance investigations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AML investigations combine several of the problems found in the other workflows: large amounts of information, repeated checks and a need to document what happened.&lt;/p&gt;

&lt;p&gt;Anthropic says FIS is working with the company on an agent intended to reduce AML investigations from days to minutes. The companies are also working on agents for areas including credit decisioning, fraud prevention and deposit retention.&lt;/p&gt;

&lt;p&gt;The proposed workflow is fairly straightforward:&lt;/p&gt;

&lt;p&gt;Alert → gather relevant information → review transactions and context → organize evidence → prepare the case → compliance review&lt;/p&gt;

&lt;p&gt;The last step should not be overlooked.&lt;/p&gt;

&lt;p&gt;Financial institutions operate in an environment where decisions can have regulatory and customer consequences. An agent preparing an investigation is a different proposition from an agent independently deciding what happens to an account.&lt;/p&gt;

&lt;p&gt;That is one reason human review remains part of the financial-services agent model described by Anthropic.&lt;/p&gt;

&lt;p&gt;Where should the human stay in the workflow?&lt;/p&gt;

&lt;p&gt;This is probably the most important part of the discussion.&lt;/p&gt;

&lt;p&gt;The Financial Stability Board has identified several risks associated with AI in financial services, including model risk, data quality, cyber risk, third-party dependencies and concentration around service providers.&lt;/p&gt;

&lt;p&gt;The FSB has also noted that generative AI in financial institutions has so far been used heavily for operational efficiency.&lt;/p&gt;

&lt;p&gt;That suggests a practical way to think about agentic AI in finance.&lt;/p&gt;

&lt;p&gt;For a lower-risk task, an agent might prepare a draft that someone checks.&lt;/p&gt;

&lt;p&gt;For an investigation, it might gather evidence and organize the case before a specialist makes the decision.&lt;/p&gt;

&lt;p&gt;For a consequential financial or compliance decision, the level of human oversight needs to be much higher.&lt;/p&gt;

&lt;p&gt;The Office of the Comptroller of the Currency, together with the Federal Reserve and FDIC, issued revised model risk management guidance in 2026. The guidance emphasizes areas such as validation, monitoring, governance, access controls, data management and auditability. It also notes that generative and agentic AI are developing quickly and are not directly within the scope of that particular guidance.&lt;/p&gt;

&lt;p&gt;So the regulatory picture is still developing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes a good fintech AI workflow?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Looking at these examples, a pattern appears.&lt;/p&gt;

&lt;p&gt;The strongest candidates for agentic workflows tend to involve:&lt;/p&gt;

&lt;p&gt;repetitive manual steps;&lt;br&gt;
large amounts of financial or documentary information;&lt;br&gt;
several tools or data sources;&lt;br&gt;
outputs that can be reviewed;&lt;br&gt;
a clear point where a human takes responsibility.&lt;/p&gt;

&lt;p&gt;That does not mean every financial process should become autonomous.&lt;/p&gt;

&lt;p&gt;Quite the opposite. For regulated work, knowing where the agent stops can be just as important as knowing what it can do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude's financial-services use cases are mostly interesting when they are treated as workflows rather than isolated AI features.&lt;/p&gt;

&lt;p&gt;KYC can involve document review and case preparation. Fraud teams can use AI to speed up investigations. Researchers can connect Claude with financial data providers. Analysts can work with financial models and reporting tools. AML teams can use agents to prepare investigation material.&lt;/p&gt;

&lt;p&gt;The common thread is fairly simple: the agent handles part of the work around a decision, while the professional remains responsible for the decision itself.&lt;/p&gt;

&lt;p&gt;For fintech teams, that may be a more useful starting point than asking whether AI can replace an entire role.&lt;/p&gt;

&lt;p&gt;Sometimes saving time means removing the five steps nobody wanted to do manually in the first place.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>agents</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
