<?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: Oleksandr Dendeberia</title>
    <description>The latest articles on DEV Community by Oleksandr Dendeberia (@odendeberia).</description>
    <link>https://dev.to/odendeberia</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%2F1580584%2Fb8b7bcfe-5294-4549-8480-0457968d3e83.jpg</url>
      <title>DEV Community: Oleksandr Dendeberia</title>
      <link>https://dev.to/odendeberia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/odendeberia"/>
    <language>en</language>
    <item>
      <title>One Crypto Swap, Seven Failures: Learning Temporal Through Failure</title>
      <dc:creator>Oleksandr Dendeberia</dc:creator>
      <pubDate>Mon, 24 Aug 2026 12:04:10 +0000</pubDate>
      <link>https://dev.to/odendeberia/one-crypto-swap-seven-failures-learning-temporal-through-failure-p9e</link>
      <guid>https://dev.to/odendeberia/one-crypto-swap-seven-failures-learning-temporal-through-failure-p9e</guid>
      <description>&lt;p&gt;&lt;em&gt;Building a crypto swap that survives real-world failures — and using each failure to understand what Temporal actually does.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A crypto swap is easy to draw.&lt;/p&gt;

&lt;p&gt;A user wants to exchange 10,000 USDC for BTC. We get a quote, wait for the funds, execute the conversion, send BTC, and wait for the payout transaction to confirm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqcb566fuvbn3nnlnp4k.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqcb566fuvbn3nnlnp4k.jpeg" alt="Visual 1 — The happy path is almost boring. That is the point." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If every dependency responds, every process stays alive, and every message arrives exactly once, there is not much architecture to discuss.&lt;/p&gt;

&lt;p&gt;But those assumptions are precisely what a distributed system cannot make.&lt;/p&gt;

&lt;p&gt;The quote provider can return 500. The user can disappear for half an hour. A blockchain RPC can go down. The exchange can execute a trade while its HTTP response gets lost. The process coordinating the swap can crash. And a deployment can change the workflow code while thousands of old swaps are still running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwy1pbqv5jrpklkrefj3.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwy1pbqv5jrpklkrefj3.jpeg" alt="Visual 2 — The business flow did not become more complicated. Reality did." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the question I want to use to explore Temporal:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can we build one crypto swap that keeps its business intent intact as we deliberately break the world around it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not &lt;em&gt;every possible failure&lt;/em&gt;. Temporal cannot undo a confirmed Bitcoin transaction, fix wrong business logic, or force an external exchange to stay online. The more interesting question is where Temporal's guarantees begin — and where they stop.&lt;/p&gt;

&lt;p&gt;The swap in this article is intentionally conceptual. It is not a production exchange, custody, liquidity, or blockchain architecture. The point is to take one understandable business operation and use it as a lens for learning durable execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden system behind a simple workflow
&lt;/h2&gt;

&lt;p&gt;Without a workflow engine, the usual instinct is to persist the current state ourselves.&lt;/p&gt;

&lt;p&gt;The swap gets a status. We add retry counters. We need a scheduler for deadlines. A queue moves work between services. A recovery job finds transactions that stopped moving. A dead-letter queue catches messages that repeatedly fail. Idempotency keys protect side effects. Operators need a way to inspect and repair stuck swaps.&lt;/p&gt;

&lt;p&gt;None of these mechanisms is inherently bad. In many systems they are exactly the right tools.&lt;/p&gt;

&lt;p&gt;The problem is that, little by little, we have built a second system whose job is to remember what the first system was trying to do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1eivh4ayca3hid72zlgh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1eivh4ayca3hid72zlgh.jpeg" alt="Visual 3 — Reliability often becomes its own state machine." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where Temporal becomes interesting.&lt;/p&gt;

&lt;p&gt;Temporal's central idea is &lt;strong&gt;durable execution&lt;/strong&gt;. A Workflow describes the long-running business process. Temporal records the execution as an ordered Event History. Workers execute Workflow Tasks and Activities, but the lifetime of the business process is no longer tied to the lifetime of one worker process.&lt;/p&gt;

&lt;p&gt;That distinction is the foundation for everything that follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcgmzyjg5mxr1q1tg1q1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwcgmzyjg5mxr1q1tg1q1.jpeg" alt="Visual 4 — The worker is execution capacity. The Workflow's durable history lives elsewhere." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Temporal's documentation describes the Event History as the record that allows a Workflow to be reconstructed after failure. When a Workflow needs to resume, Temporal does not simply deserialize an in-memory snapshot. The Workflow code can be replayed against its recorded history until the previous state is reconstructed. &lt;a href="https://docs.temporal.io/workflows" rel="noopener noreferrer"&gt;(Temporal: Workflows)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That sounds abstract, so let's start breaking our swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure #1: the quote provider returns 500
&lt;/h2&gt;

&lt;p&gt;Our first dependency is a quote service. It is also our first failure.&lt;/p&gt;

&lt;p&gt;The request fails with HTTP 500.&lt;/p&gt;

&lt;p&gt;A second attempt fails too.&lt;/p&gt;

&lt;p&gt;The third succeeds.&lt;/p&gt;

&lt;p&gt;In Temporal, work that talks to the outside world is typically modeled as an &lt;strong&gt;Activity&lt;/strong&gt;. Activities are where network calls, database writes, and other side effects belong. They are allowed to fail. Temporal can apply a Retry Policy and schedule another Activity attempt after a failure. By default, Activities are retried with increasing delays unless configured otherwise. &lt;a href="https://docs.temporal.io/encyclopedia/retry-policies" rel="noopener noreferrer"&gt;(Temporal: Retry Policies)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncga2kk57feapr9o1qzg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fncga2kk57feapr9o1qzg.jpeg" alt="Visual 5 — A transient infrastructure failure does not have to become a business failure." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This sounds like a small feature. After all, anyone can write a retry loop.&lt;/p&gt;

&lt;p&gt;But a retry loop inside a process only works while that process exists. Once retries are part of the durable execution model, the question changes from "how do I retry this HTTP request?" to "what should this business operation do while a dependency is temporarily unavailable?"&lt;/p&gt;

&lt;p&gt;That is a more useful level of abstraction.&lt;/p&gt;

&lt;p&gt;It also immediately forces us to think about policy. Which errors are retryable? How long should we back off? When should we give up? What happens after the final attempt? A Retry Policy does not remove those decisions; it gives them a durable place to live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure #2: the user does nothing
&lt;/h2&gt;

&lt;p&gt;The quote is shown, a deposit address is created, and then the user goes for lunch.&lt;/p&gt;

&lt;p&gt;Nothing happens for thirty minutes.&lt;/p&gt;

&lt;p&gt;In a conventional request/response application, this is an awkward kind of work because there is no useful thread to keep alive. We normally persist a deadline somewhere and arrange for another process to notice it later.&lt;/p&gt;

&lt;p&gt;A Temporal Workflow can instead create a &lt;strong&gt;durable Timer&lt;/strong&gt; and wait.&lt;br&gt;
The important word is &lt;em&gt;durable&lt;/em&gt;. A Temporal timer is persisted. It is not a sleeping Java thread. The Java SDK documentation explicitly notes that Workflow.sleep() is resource-light and that timers can survive worker or Temporal service downtime and continue when the system is available again. &lt;a href="https://docs.temporal.io/develop/java/workflows/timers" rel="noopener noreferrer"&gt;(Temporal: Java Timers)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The swap can now express a business rule such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait up to 30 minutes for funding. If no valid deposit arrives, expire the swap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Time has become part of the workflow instead of infrastructure wrapped around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure #3: the deposit arrives from somewhere else
&lt;/h2&gt;

&lt;p&gt;Five minutes later, a blockchain watcher sees the USDC deposit.&lt;/p&gt;

&lt;p&gt;This event did not originate from the Workflow. It came from the outside world, asynchronously, at an unpredictable time.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of interaction Temporal models with &lt;strong&gt;Signals&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Signals are asynchronous write messages sent to a running Workflow. They can change the Workflow's state or affect its control flow. Temporal also has Queries for reading Workflow state and Updates for synchronous, tracked writes, but our deposit notification fits the Signal model naturally. &lt;a href="https://docs.temporal.io/encyclopedia/workflow-message-passing" rel="noopener noreferrer"&gt;(Temporal: Workflow Message Passing)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice what disappeared from the mental model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd518twch9wjao5s2rc2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd518twch9wjao5s2rc2.jpeg" alt="Visual 6: The Workflow Can Be Quiet for Minutes or Hours, Then React to an External Event&lt;br&gt;
" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are no longer asking which pod is "waiting for the deposit." There does not need to be one. The business process is waiting, not a particular machine.&lt;/p&gt;

&lt;p&gt;That difference is subtle, but it is one of the reasons durable execution can simplify long-running operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure #4: kill the worker
&lt;/h2&gt;

&lt;p&gt;Now we reach the experiment that makes Temporal click for many people.&lt;/p&gt;

&lt;p&gt;The deposit has been confirmed. The Workflow is moving toward the trade. Then the worker process disappears.&lt;/p&gt;

&lt;p&gt;Kill the container. Restart the host. Lose the VM. It does not matter which failure we imagine; the interesting part is that the process holding the Workflow's in-memory state is gone.&lt;/p&gt;

&lt;p&gt;What happens next?&lt;/p&gt;

&lt;p&gt;The tempting explanation is that Temporal "continues from the line where the worker crashed." That is useful intuition, but technically misleading.&lt;/p&gt;

&lt;p&gt;Temporal reconstructs Workflow state through &lt;strong&gt;replay&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Event History contains what has already happened: the Workflow started, the quote Activity completed, the deposit Signal arrived, an Activity was scheduled, and so on. A worker can run the Workflow code again and match the commands it produces against the existing history. Recorded Activity results are reused during replay instead of executing those Activities again. Once replay reaches a point for which there are no recorded events, execution can move forward again. &lt;a href="https://docs.temporal.io/encyclopedia/event-history/event-history-java" rel="noopener noreferrer"&gt;(Temporal: Event History walkthrough for Java)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm9ukpuv36jan823x3we.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwm9ukpuv36jan823x3we.jpeg" alt="Visual 7: Temporal Rebuilds Workflow State From History; It Does Not Resurrect a Dead Process&lt;br&gt;
" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is an important distinction here between a &lt;strong&gt;Workflow&lt;/strong&gt; and an &lt;strong&gt;Activity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Workflow replay is about reconstructing deterministic orchestration state. An in-flight Activity is different. If a worker disappears after receiving an Activity Task, Temporal does not magically know at that instant what happened inside the process. Activity loss is detected using timeouts; if the Activity times out and its Retry Policy allows another attempt, Temporal schedules a new one. &lt;a href="https://docs.temporal.io/activity-execution" rel="noopener noreferrer"&gt;(Temporal: Activity Execution)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That detail becomes critical in our next failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure #5: the trade happened, but the response disappeared
&lt;/h2&gt;

&lt;p&gt;The exchange receives our request to convert USDC to BTC.&lt;/p&gt;

&lt;p&gt;It executes the trade successfully.&lt;/p&gt;

&lt;p&gt;Then the network connection dies before our Activity receives the response.&lt;/p&gt;

&lt;p&gt;From the exchange's perspective, the operation succeeded.&lt;/p&gt;

&lt;p&gt;From our perspective, it timed out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4wuccnhpk1u1nax8do8c.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4wuccnhpk1u1nax8do8c.jpeg" alt="Visual 8: This Is Not Merely an Availability Problem. It Is an Uncertainty Problem." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Should Temporal retry the Activity?&lt;/p&gt;

&lt;p&gt;If the exchange treats a second request as a new trade, a blind retry could execute the conversion twice. Temporal cannot inspect an opaque external system and infer whether the first side effect happened.&lt;/p&gt;

&lt;p&gt;This is the point where the phrase "exactly once" needs care.&lt;/p&gt;

&lt;p&gt;Temporal recommends making Activities idempotent: executing the operation multiple times should have the same effective result as executing it once. &lt;a href="https://docs.temporal.io/activity-definition#idempotency" rel="noopener noreferrer"&gt;(Temporal: Activity Idempotency)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For our swap, that could mean every trade request carries a stable business idempotency key such as the swap ID. If the first request succeeded but the response was lost, a retry with the same key should return the result of the original trade rather than create a second one.&lt;/p&gt;

&lt;p&gt;The broader lesson is more important than the crypto example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Durable orchestration does not make external side effects exactly-once. It gives you a reliable place to coordinate retries; your side-effect boundary still has to be designed safely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a feature of the mental model, not a weakness to hide. A useful Temporal article should make this boundary explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure #6: we converted the money, but cannot deliver the BTC
&lt;/h2&gt;

&lt;p&gt;Suppose the USDC deposit is confirmed and the conversion to BTC succeeds.&lt;/p&gt;

&lt;p&gt;Then the payout step fails permanently.&lt;/p&gt;

&lt;p&gt;Now what?&lt;/p&gt;

&lt;p&gt;We cannot pretend the earlier steps never happened. The service owns BTC that belongs economically to the user, while the user has not received it.&lt;/p&gt;

&lt;p&gt;This is where compensation enters the story.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe4l7idhkc7uauveu71fg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe4l7idhkc7uauveu71fg.jpeg" alt="Visual 9 — Compensation is a new action, not a rewind button." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Saga pattern models a multi-step transaction as a sequence of operations with compensating actions that can be invoked if later work cannot complete. Temporal documents Saga-style compensation as a distributed transaction pattern and recommends that compensations themselves be idempotent. &lt;a href="https://docs.temporal.io/design-patterns/saga-pattern" rel="noopener noreferrer"&gt;(Temporal: Saga Pattern)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our fictional service, the correct compensation might be to credit the user's internal BTC balance, return funds where possible, route the case to manual review, or keep retrying a recoverable payout path.&lt;/p&gt;

&lt;p&gt;The exact answer is deliberately domain-specific.&lt;/p&gt;

&lt;p&gt;A confirmed blockchain transaction cannot be rolled back because our Workflow changed its mind. Temporal makes &lt;em&gt;the decision and execution of compensation durable&lt;/em&gt;. It does not make irreversible operations reversible.&lt;/p&gt;

&lt;p&gt;That distinction is one of the most useful ways to think about Sagas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure #7: we deploy V2 while V1 swaps are still running
&lt;/h2&gt;

&lt;p&gt;Our service is successful. Thousands of swaps can live for minutes or hours.&lt;/p&gt;

&lt;p&gt;Then we deploy a new version of the Workflow.&lt;/p&gt;

&lt;p&gt;V1 looked like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quote → Deposit → Swap → Payout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;V2 introduces a new step:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quote → Risk Check → Deposit → Swap → Payout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Normally, deploying new code means future requests run the new code. With replayable long-running Workflows, the situation is more interesting: old executions may later be replayed using code from a newer deployment.&lt;/p&gt;

&lt;p&gt;Replay only works if Workflow code remains compatible with the history it is replaying. Temporal describes a Workflow as deterministic when the same input and history lead it to produce the same sequence of commands. A code change that causes replay to expect a different command sequence can break that assumption. &lt;a href="https://docs.temporal.io/workflow-definition" rel="noopener noreferrer"&gt;(Temporal: Workflow Definition and determinism)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Temporal provides &lt;strong&gt;Worker Versioning&lt;/strong&gt; to help deploy new Workflow code safely. Its current documentation recommends Worker Versioning as the default approach for safe Worker deployments, allowing Workflows to be pinned to compatible Worker Deployment Versions. &lt;a href="https://docs.temporal.io/production-deployment/worker-deployments" rel="noopener noreferrer"&gt;(Temporal: Worker Deployments)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the point where Temporal stops looking like "retries plus a state machine" and starts looking like a different programming model for long-running business processes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ntm4m8q5opq1lztsw0c.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3ntm4m8q5opq1lztsw0c.jpeg" alt="https://docs.temporal.io/production-deployment/worker-deployments" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fact that code may be replayed changes what code is allowed to do. Non-deterministic choices such as reading arbitrary local time, generating randomness directly, or changing command-producing control flow need Temporal-aware handling.&lt;/p&gt;

&lt;p&gt;Durability has constraints. That is the trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what did Temporal actually give us?
&lt;/h2&gt;

&lt;p&gt;We began with a seven-step crypto swap and kept changing only one thing: the world around it became less cooperative.&lt;/p&gt;

&lt;p&gt;The quote API failed. The user took too long. A deposit arrived asynchronously. A worker died. An exchange response disappeared after a side effect. A payout became impossible. New Workflow code was deployed while old executions still existed.&lt;/p&gt;

&lt;p&gt;Each failure exposed a Temporal concept:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Quote API returns 500 → Activities and Retry Policies&lt;/li&gt;
&lt;li&gt;User does not fund the swap → Durable Timers&lt;/li&gt;
&lt;li&gt;Deposit arrives asynchronously → Signals&lt;/li&gt;
&lt;li&gt;Worker disappears → Event History and Replay&lt;/li&gt;
&lt;li&gt;Trade result becomes uncertain → Idempotency&lt;/li&gt;
&lt;li&gt;Forward progress becomes impossible → Saga / Compensation&lt;/li&gt;
&lt;li&gt;Workflow code evolves → Determinism and Versioning&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flrfecd3tilc5quq94whr.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flrfecd3tilc5quq94whr.jpeg" alt="Visual 11 — Temporal is easier to understand as a map of failure modes than as a list of SDK features.&lt;br&gt;
" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that gives us a more precise definition of Temporal's value.&lt;/p&gt;

&lt;p&gt;It is not primarily that Temporal lets us draw a workflow. We could draw a state machine ourselves.&lt;/p&gt;

&lt;p&gt;It is not primarily that Temporal retries requests. We could build a retry library ourselves.&lt;/p&gt;

&lt;p&gt;The interesting part is that the execution of the &lt;strong&gt;business process becomes durable&lt;/strong&gt;. Waiting, retries, external events, progress, recovery decisions, and compensation can all participate in one long-lived execution model whose state is not tied to one application process.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Temporal does not do
&lt;/h2&gt;

&lt;p&gt;There is a dangerous version of the Temporal story where the conclusion becomes: "Put your code in a Workflow and failures go away."&lt;/p&gt;

&lt;p&gt;That is not what happened in our swap.&lt;/p&gt;

&lt;p&gt;Temporal helped preserve the execution of the process. It did not eliminate the distributed-system boundaries around it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8s3q8yb5dkv69ujsk7v.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr8s3q8yb5dkv69ujsk7v.jpeg" alt="Visual 12 — Durable execution is powerful precisely because its boundary is explicit." width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Temporal can help us remember that a swap exists after a process crash. It can durably coordinate retry policy. It can wait without holding an application thread. It can receive external messages. It can replay Workflow state. It can help us encode compensation and safely evolve long-running Workflow code.&lt;/p&gt;

&lt;p&gt;But it cannot undo a confirmed blockchain transfer. It cannot determine whether an arbitrary external API performed an operation if that API provides no way to resolve the ambiguity. It cannot make a non-idempotent side effect safe by retrying it. And it cannot choose the correct business compensation for us.&lt;/p&gt;

&lt;p&gt;Those remain architecture and domain problems.&lt;/p&gt;

&lt;p&gt;That boundary gives me a useful heuristic for when Temporal becomes interesting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Temporal earns its complexity when the lifetime of a business operation is meaningfully longer — and less reliable — than the lifetime of the process currently executing it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A CRUD endpoint that reads a row and returns JSON probably does not need it.&lt;/p&gt;

&lt;p&gt;A multi-step process that crosses services, waits for humans or external events, survives deployments, performs expensive side effects, and must not silently disappear is a much stronger candidate.&lt;/p&gt;

&lt;p&gt;Our crypto swap was useful not because crypto is special, but because it puts all of those forces into one small picture.&lt;/p&gt;

&lt;p&gt;The happy path took one diagram.&lt;/p&gt;

&lt;p&gt;The interesting architecture began when we started breaking it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/workflows" rel="noopener noreferrer"&gt;Temporal — Workflows and replay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/encyclopedia/event-history/event-history-java" rel="noopener noreferrer"&gt;Temporal — Event History walkthrough for Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/activity-execution" rel="noopener noreferrer"&gt;Temporal — Activity Execution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/encyclopedia/retry-policies" rel="noopener noreferrer"&gt;Temporal — Retry Policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/encyclopedia/workflow-message-passing" rel="noopener noreferrer"&gt;Temporal — Workflow message passing: Signals, Queries, and Updates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/develop/java/workflows/timers" rel="noopener noreferrer"&gt;Temporal — Durable Timers in the Java SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/activity-definition#idempotency" rel="noopener noreferrer"&gt;Temporal — Activity idempotency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.temporal.io/design-patterns/saga-pattern" rel="noopener noreferrer"&gt;Temporal — Saga Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.toTemporal%20%E2%80%94%20Worker%20Versioning"&gt;Temporal — Worker Versioning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>temporal</category>
      <category>java</category>
      <category>distributedsystems</category>
      <category>backend</category>
    </item>
    <item>
      <title>How to Validate Crypto Wallet Addresses Offline in Java</title>
      <dc:creator>Oleksandr Dendeberia</dc:creator>
      <pubDate>Sat, 22 Aug 2026 07:53:34 +0000</pubDate>
      <link>https://dev.to/odendeberia/how-to-validate-crypto-wallet-addresses-offline-in-java-4lkc</link>
      <guid>https://dev.to/odendeberia/how-to-validate-crypto-wallet-addresses-offline-in-java-4lkc</guid>
      <description>&lt;p&gt;Crypto address validation is one of those small backend features that becomes&lt;br&gt;
important very quickly.&lt;/p&gt;

&lt;p&gt;If you are building a wallet, exchange flow, payment form, fintech dashboard, or&lt;br&gt;
internal operations tool, you usually want to reject clearly invalid addresses&lt;br&gt;
before anything touches an RPC node, explorer API, queue, or withdrawal system.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to validate crypto wallet addresses offline in Java&lt;br&gt;
using Chainwarden.&lt;/p&gt;

&lt;p&gt;Chainwarden is an open-source Java library for local crypto address validation.&lt;br&gt;
It validates address syntax, encoding, prefixes, decoded lengths, and checksums&lt;br&gt;
without calling remote nodes, explorers, RPC endpoints, or exchange APIs.&lt;/p&gt;
&lt;h2&gt;
  
  
  What offline validation can check
&lt;/h2&gt;

&lt;p&gt;Offline validation can catch many common mistakes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrong address length&lt;/li&gt;
&lt;li&gt;invalid Base58/Base64/hex characters&lt;/li&gt;
&lt;li&gt;wrong network prefix&lt;/li&gt;
&lt;li&gt;invalid checksum&lt;/li&gt;
&lt;li&gt;invalid EIP-55 checksum casing&lt;/li&gt;
&lt;li&gt;unsupported address format for a chain&lt;/li&gt;
&lt;li&gt;leading or trailing whitespace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is fast, deterministic, and works without network access.&lt;/p&gt;
&lt;h2&gt;
  
  
  What offline validation cannot check
&lt;/h2&gt;

&lt;p&gt;Offline validation does not prove that an address is safe to use.&lt;/p&gt;

&lt;p&gt;It cannot verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account existence&lt;/li&gt;
&lt;li&gt;account balance&lt;/li&gt;
&lt;li&gt;address ownership&lt;/li&gt;
&lt;li&gt;smart contract status&lt;/li&gt;
&lt;li&gt;whether an exchange requires a memo or destination tag&lt;/li&gt;
&lt;li&gt;whether a token can be received at that destination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of offline validation as the first gate: it answers "does this look like a&lt;br&gt;
valid address for this chain?", not "is this a correct withdrawal destination?"&lt;/p&gt;
&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Add Chainwarden from Maven Central:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.chainwarden&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;chainwarden-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Gradle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.chainwarden:chainwarden-core:0.1.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Validate one address
&lt;/h2&gt;

&lt;p&gt;The main entry point is &lt;code&gt;AddressValidators.validate(chain, address)&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.AddressValidators&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.Chain&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.common.validation.AddressValidationResult&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidateBitcoinAddress&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;AddressValidationResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AddressValidators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BITCOIN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Valid "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" address"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Format: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid address"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Reason: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Valid bitcoin address
Format: BECH32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;valid()&lt;/code&gt; - whether validation succeeded&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chain()&lt;/code&gt; - canonical chain id, such as &lt;code&gt;bitcoin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;format()&lt;/code&gt; - detected format, such as &lt;code&gt;BECH32&lt;/code&gt;, &lt;code&gt;EIP55&lt;/code&gt;, or &lt;code&gt;BASE58CHECK&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;error()&lt;/code&gt; - stable machine-readable error code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reason()&lt;/code&gt; - human-readable diagnostic text&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Validate by chain id
&lt;/h2&gt;

&lt;p&gt;If the chain comes from a user request, config file, database row, or API&lt;br&gt;
payload, you can use a string chain id:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.AddressValidators&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.common.validation.AddressValidationResult&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidateByChainId&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;AddressValidationResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AddressValidators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"ethereum"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true
ethereum
EIP55
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aliases are also supported. For example, BNB Smart Chain can be validated with&lt;br&gt;
&lt;code&gt;bnb-smart-chain&lt;/code&gt;, &lt;code&gt;bsc&lt;/code&gt;, or &lt;code&gt;BNB_SMART_CHAIN&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AddressValidators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isValid&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"bsc"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handle invalid addresses
&lt;/h2&gt;

&lt;p&gt;For production code, avoid returning only &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;. The structured&lt;br&gt;
result is more useful for logging, analytics, API responses, and support tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.AddressValidators&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.common.validation.AddressValidationError&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.common.validation.AddressValidationResult&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidationMessage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;messageFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;AddressValidationResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AddressValidators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Address is valid"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;UNSUPPORTED_CHAIN&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"This chain is not supported yet"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;EMPTY&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address is required"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;SURROUNDING_WHITESPACE&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Remove leading or trailing spaces"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;INVALID_CHECKSUM&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address checksum is invalid"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;INVALID_PREFIX&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address prefix does not match the selected chain"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;INVALID_LENGTH&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address length is invalid"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;INVALID_CHARACTER&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address contains invalid characters"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;INVALID_WORKCHAIN&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"TON workchain is not supported"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;INVALID_ENCODING&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address encoding is invalid"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;INVALID_FORMAT&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address format is invalid"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;NONE&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"Address is valid"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the validation layer friendlier without losing machine-readable error&lt;br&gt;
codes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Validate several chains
&lt;/h2&gt;

&lt;p&gt;Here is a compact example validating several different address formats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.AddressValidators&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.chainwarden.common.validation.AddressValidationResult&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultiChainValidation&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"bitcoin"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bc1qpjult34k9spjfym8hss2jrwjgf0xjf40ze0pp8"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"ethereum"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"tron"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"solana"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"11111111111111111111111111111111"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"ton"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0:0000000000000000000000000000000000000000000000000000000000000000"&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;addresses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&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="nc"&gt;AddressValidationResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AddressValidators&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"%s -&amp;gt; valid=%s, format=%s, error=%s%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Supported chains
&lt;/h2&gt;

&lt;p&gt;At the time of writing, Chainwarden supports:&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;Chain id&lt;/th&gt;
&lt;th&gt;Formats&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bitcoin&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bitcoin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base58Check, Bech32, Bech32m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethereum&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ethereum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVM &lt;code&gt;0x&lt;/code&gt;, EIP-55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BNB Smart Chain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bnb-smart-chain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVM &lt;code&gt;0x&lt;/code&gt;, EIP-55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;&lt;code&gt;base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVM &lt;code&gt;0x&lt;/code&gt;, EIP-55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arbitrum One&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arbitrum-one&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVM &lt;code&gt;0x&lt;/code&gt;, EIP-55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Polygon PoS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;polygon-pos&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVM &lt;code&gt;0x&lt;/code&gt;, EIP-55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avalanche C-Chain&lt;/td&gt;
&lt;td&gt;&lt;code&gt;avalanche-c-chain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;EVM &lt;code&gt;0x&lt;/code&gt;, EIP-55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TRON&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tron&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base58Check with TRON prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solana&lt;/td&gt;
&lt;td&gt;&lt;code&gt;solana&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base58-encoded 32-byte public key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XRP Ledger&lt;/td&gt;
&lt;td&gt;&lt;code&gt;xrp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classic address, mainnet X-address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TON&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ton&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raw and user-friendly formats&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Example API response
&lt;/h2&gt;

&lt;p&gt;If you expose validation through your own backend, a response can stay very&lt;br&gt;
simple:&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;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bitcoin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BECH32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NONE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"valid"&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;For invalid input:&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;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ethereum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UNKNOWN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INVALID_CHECKSUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid EIP-55 checksum"&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;This shape is useful because clients can display a human message while still&lt;br&gt;
using &lt;code&gt;error&lt;/code&gt; as a stable programmatic value.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use offline validation
&lt;/h2&gt;

&lt;p&gt;Offline validation is a good fit for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;signup and onboarding forms&lt;/li&gt;
&lt;li&gt;withdrawal address forms&lt;/li&gt;
&lt;li&gt;address books&lt;/li&gt;
&lt;li&gt;transaction preflight checks&lt;/li&gt;
&lt;li&gt;admin dashboards&lt;/li&gt;
&lt;li&gt;support tooling&lt;/li&gt;
&lt;li&gt;import jobs&lt;/li&gt;
&lt;li&gt;backend API validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It should be combined with chain-specific business rules when money movement is&lt;br&gt;
involved.&lt;/p&gt;

&lt;p&gt;For example, XRP may require a destination tag depending on the exchange or&lt;br&gt;
custodian. Offline address validation can tell you whether the XRP address shape&lt;br&gt;
is valid. It cannot tell you whether a destination tag is required by the&lt;br&gt;
recipient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Address validation should be boring infrastructure. It should be fast,&lt;br&gt;
deterministic, testable, and explicit about what it can and cannot prove.&lt;/p&gt;

&lt;p&gt;Chainwarden tries to keep that boundary clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validate local address properties&lt;/li&gt;
&lt;li&gt;avoid network calls&lt;/li&gt;
&lt;li&gt;return structured results&lt;/li&gt;
&lt;li&gt;keep chain-specific behavior isolated&lt;/li&gt;
&lt;li&gt;expose a small Java facade for application code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website and demo: &lt;a href="https://chainwarden.org" rel="noopener noreferrer"&gt;https://chainwarden.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Maven Central: &lt;a href="https://central.sonatype.com/artifact/org.chainwarden/chainwarden-core" rel="noopener noreferrer"&gt;https://central.sonatype.com/artifact/org.chainwarden/chainwarden-core&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API docs: &lt;a href="https://api.chainwarden.org/q/swagger-ui/" rel="noopener noreferrer"&gt;https://api.chainwarden.org/q/swagger-ui/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>cryptocurrency</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
