<?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: Obinna Victor</title>
    <description>The latest articles on DEV Community by Obinna Victor (@sopuruchii).</description>
    <link>https://dev.to/sopuruchii</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%2F3958363%2F3ad1f999-7860-4e9a-861a-3c6ca445179c.jpg</url>
      <title>DEV Community: Obinna Victor</title>
      <link>https://dev.to/sopuruchii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sopuruchii"/>
    <language>en</language>
    <item>
      <title>Idempotency Is Not Just UUIDs: Handling Ambiguous Payment Outcomes</title>
      <dc:creator>Obinna Victor</dc:creator>
      <pubDate>Thu, 02 Jul 2026 10:35:12 +0000</pubDate>
      <link>https://dev.to/sopuruchii/idempotency-is-not-just-uuids-handling-ambiguous-payment-outcomes-2dg3</link>
      <guid>https://dev.to/sopuruchii/idempotency-is-not-just-uuids-handling-ambiguous-payment-outcomes-2dg3</guid>
      <description>&lt;p&gt;Most developers learn idempotency as:&lt;/p&gt;

&lt;p&gt;“Send an idempotency key so the same request does not happen twice.”&lt;/p&gt;

&lt;p&gt;That is true.&lt;/p&gt;

&lt;p&gt;But in financial systems, it is not enough.&lt;/p&gt;

&lt;p&gt;Idempotency is not just about generating a UUID.&lt;/p&gt;

&lt;p&gt;It is about making sure the same financial action cannot be executed twice, even when workers crash, providers timeout, webhooks arrive late, and clients retry.&lt;/p&gt;

&lt;p&gt;This becomes even more important when the action touches money:&lt;/p&gt;

&lt;p&gt;payment&lt;br&gt;
transfer&lt;br&gt;
refund&lt;br&gt;
stablecoin transfer&lt;br&gt;
blockchain transaction submission&lt;br&gt;
AI-agent requested financial action&lt;/p&gt;

&lt;p&gt;This is one of the core problems I’m thinking about while building Azums, a non-custodial durable financial execution layer for APIs and AI agents.&lt;/p&gt;

&lt;p&gt;Azums launches with Paystack payments, Paystack transfers, Paystack refunds, Solana transactions, and Solana USDT transfers.&lt;/p&gt;

&lt;p&gt;But before talking about Azums, let’s talk about the real problem.&lt;/p&gt;

&lt;p&gt;The dangerous payment flow&lt;/p&gt;

&lt;p&gt;Imagine this flow:&lt;/p&gt;

&lt;p&gt;Client&lt;br&gt;
  → Your API&lt;br&gt;
    → Payment Provider&lt;br&gt;
      → Provider processes request&lt;/p&gt;

&lt;p&gt;Your system calls the provider.&lt;/p&gt;

&lt;p&gt;Then the request times out.&lt;/p&gt;

&lt;p&gt;What do you do?&lt;/p&gt;

&lt;p&gt;Many systems do this:&lt;/p&gt;

&lt;p&gt;timeout = failed&lt;br&gt;
retry = try again&lt;/p&gt;

&lt;p&gt;That is dangerous.&lt;/p&gt;

&lt;p&gt;Because the provider may have already received and processed the request.&lt;/p&gt;

&lt;p&gt;So your system may be in this state:&lt;/p&gt;

&lt;p&gt;Your API thinks: failed&lt;br&gt;
Provider state: maybe succeeded&lt;br&gt;
Customer state: unknown&lt;/p&gt;

&lt;p&gt;That is not failure.&lt;/p&gt;

&lt;p&gt;That is an ambiguous outcome.&lt;/p&gt;

&lt;p&gt;And ambiguous outcomes require a different design.&lt;/p&gt;

&lt;p&gt;The wrong way to use idempotency&lt;/p&gt;

&lt;p&gt;A common mistake is generating a new UUID for every retry.&lt;/p&gt;

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

&lt;p&gt;const idempotencyKey = crypto.randomUUID();&lt;/p&gt;

&lt;p&gt;await provider.transfer({&lt;br&gt;
  amount,&lt;br&gt;
  recipient,&lt;br&gt;
  idempotencyKey,&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;This looks fine until retry happens.&lt;/p&gt;

&lt;p&gt;If the client or server creates a new key on retry, the provider may treat it as a new financial action.&lt;/p&gt;

&lt;p&gt;That defeats the point of idempotency.&lt;/p&gt;

&lt;p&gt;The key idea is:&lt;/p&gt;

&lt;p&gt;Same financial action must map to the same idempotency identity.&lt;/p&gt;

&lt;p&gt;For many systems, a better key is derived from stable business identity:&lt;/p&gt;

&lt;p&gt;workspace_id + action_type + external_reference&lt;/p&gt;

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

&lt;p&gt;workspace_123:paystack_transfer:order_789&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;workspace_123:refund:payment_ref_456:refund_001&lt;/p&gt;

&lt;p&gt;The key should represent the action, not the attempt.&lt;/p&gt;

&lt;p&gt;Action vs attempt&lt;/p&gt;

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

&lt;p&gt;A financial action is the thing the user or system wants:&lt;/p&gt;

&lt;p&gt;Refund ₦10,000 for payment X&lt;br&gt;
Transfer ₦50,000 to recipient Y&lt;br&gt;
Submit signed Solana USDT transfer Z&lt;/p&gt;

&lt;p&gt;An attempt is one try to execute that action.&lt;/p&gt;

&lt;p&gt;One action may have multiple attempts.&lt;/p&gt;

&lt;p&gt;But it should have only one final financial execution.&lt;/p&gt;

&lt;p&gt;A simplified model:&lt;/p&gt;

&lt;p&gt;financial_actions (&lt;br&gt;
  id,&lt;br&gt;
  workspace_id,&lt;br&gt;
  action_type,&lt;br&gt;
  idempotency_key,&lt;br&gt;
  payload_hash,&lt;br&gt;
  status,&lt;br&gt;
  provider_reference,&lt;br&gt;
  created_at,&lt;br&gt;
  updated_at&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;execution_attempts (&lt;br&gt;
  id,&lt;br&gt;
  financial_action_id,&lt;br&gt;
  attempt_number,&lt;br&gt;
  status,&lt;br&gt;
  started_at,&lt;br&gt;
  finished_at,&lt;br&gt;
  error_class,&lt;br&gt;
  provider_reference&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;The action is the root object.&lt;/p&gt;

&lt;p&gt;Attempts are evidence.&lt;/p&gt;

&lt;p&gt;Do not bill, receipt, or finalize based only on attempts.&lt;/p&gt;

&lt;p&gt;Finalize based on the root financial action and external truth.&lt;/p&gt;

&lt;p&gt;Payload fingerprinting&lt;/p&gt;

&lt;p&gt;The same idempotency key should not allow different payloads.&lt;/p&gt;

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

&lt;p&gt;First request:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "amount": 10000,&lt;br&gt;
  "recipient": "recipient_A"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Retry with same key but changed payload:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "amount": 50000,&lt;br&gt;
  "recipient": "recipient_B"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This should not execute.&lt;/p&gt;

&lt;p&gt;The system should return conflict.&lt;/p&gt;

&lt;p&gt;A common pattern:&lt;/p&gt;

&lt;p&gt;idempotency_key + payload_hash&lt;/p&gt;

&lt;p&gt;If the key exists and the payload hash matches, return the existing transaction.&lt;/p&gt;

&lt;p&gt;If the key exists and the payload hash differs, reject with conflict.&lt;/p&gt;

&lt;p&gt;Example behavior:&lt;/p&gt;

&lt;p&gt;same key + same payload = return existing transaction&lt;br&gt;
same key + different payload = 409 conflict&lt;br&gt;
new key + valid payload = create new transaction&lt;/p&gt;

&lt;p&gt;This prevents accidental or malicious mutation of a financial action.&lt;/p&gt;

&lt;p&gt;Retries need execution boundaries&lt;/p&gt;

&lt;p&gt;Retries are not always safe.&lt;/p&gt;

&lt;p&gt;A worker can fail at different points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;before provider call&lt;/li&gt;
&lt;li&gt;during provider call&lt;/li&gt;
&lt;li&gt;after provider accepted request&lt;/li&gt;
&lt;li&gt;after provider completed request&lt;/li&gt;
&lt;li&gt;before local database update&lt;/li&gt;
&lt;li&gt;before receipt creation&lt;/li&gt;
&lt;li&gt;before callback delivery&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each point requires different handling.&lt;/p&gt;

&lt;p&gt;If failure happens before provider submission, retry may be safe.&lt;/p&gt;

&lt;p&gt;If failure happens after provider submission, retry may duplicate money movement unless the system first checks provider truth.&lt;/p&gt;

&lt;p&gt;So the system needs an execution boundary:&lt;/p&gt;

&lt;p&gt;Before provider submission:&lt;br&gt;
  safe to retry&lt;/p&gt;

&lt;p&gt;After provider submission:&lt;br&gt;
  do not blindly retry&lt;br&gt;
  verify provider state first&lt;/p&gt;

&lt;p&gt;This is where many systems break.&lt;/p&gt;

&lt;p&gt;Webhooks are not enough&lt;/p&gt;

&lt;p&gt;Webhooks are useful, but they are not enough.&lt;/p&gt;

&lt;p&gt;A webhook can be:&lt;/p&gt;

&lt;p&gt;delayed&lt;br&gt;
duplicated&lt;br&gt;
missed&lt;br&gt;
delivered out of order&lt;br&gt;
received before internal state is ready&lt;/p&gt;

&lt;p&gt;A strong system treats webhook events as signals, not final truth.&lt;/p&gt;

&lt;p&gt;The safer model:&lt;/p&gt;

&lt;p&gt;Webhook = fast signal&lt;br&gt;
Provider verification = direct check&lt;br&gt;
Reconciliation = external truth comparison&lt;/p&gt;

&lt;p&gt;For Paystack, that means verifying payment, transfer, or refund status against Paystack.&lt;/p&gt;

&lt;p&gt;For Solana, that means checking signature, confirmation, finality, token movement, amount, recipient, and network.&lt;/p&gt;

&lt;p&gt;For Solana USDT, that also means verifying the correct token mint and token account movement.&lt;/p&gt;

&lt;p&gt;Receipt creation should happen after truth&lt;/p&gt;

&lt;p&gt;A receipt should not just mean:&lt;/p&gt;

&lt;p&gt;“Our worker finished.”&lt;/p&gt;

&lt;p&gt;A receipt should mean:&lt;/p&gt;

&lt;p&gt;“We have enough evidence to state the final outcome of this financial action.”&lt;/p&gt;

&lt;p&gt;That receipt should include:&lt;/p&gt;

&lt;p&gt;transaction id&lt;br&gt;
action type&lt;br&gt;
amount or asset&lt;br&gt;
rail&lt;br&gt;
provider or chain reference&lt;br&gt;
final outcome&lt;br&gt;
policy decision&lt;br&gt;
approval reference if needed&lt;br&gt;
reconciliation status&lt;br&gt;
receipt hash&lt;br&gt;
created timestamp&lt;/p&gt;

&lt;p&gt;This is important because customers do not only need logs.&lt;/p&gt;

&lt;p&gt;They need proof.&lt;/p&gt;

&lt;p&gt;What about AI agents?&lt;/p&gt;

&lt;p&gt;AI agents make this problem more urgent.&lt;/p&gt;

&lt;p&gt;If an AI agent can request a refund or transfer, the system must answer:&lt;/p&gt;

&lt;p&gt;Who is the agent?&lt;br&gt;
What can it request?&lt;br&gt;
What amount is allowed?&lt;br&gt;
Does this require approval?&lt;br&gt;
Was a one-use permission created?&lt;br&gt;
Was the permission consumed?&lt;br&gt;
Was execution submitted once?&lt;br&gt;
Was the outcome verified?&lt;br&gt;
Was a receipt created?&lt;/p&gt;

&lt;p&gt;An AI agent should not directly call the payment provider.&lt;/p&gt;

&lt;p&gt;It should request a financial action through a control layer.&lt;/p&gt;

&lt;p&gt;That control layer should enforce policy, approval, idempotency, receipts, reconciliation, and exceptions.&lt;/p&gt;

&lt;p&gt;This is one of the reasons I’m building Azums.&lt;/p&gt;

&lt;p&gt;A better lifecycle&lt;/p&gt;

&lt;p&gt;A safer financial execution lifecycle looks like this:&lt;/p&gt;

&lt;p&gt;Request received&lt;br&gt;
→ identity verified&lt;br&gt;
→ idempotency checked&lt;br&gt;
→ payload fingerprinted&lt;br&gt;
→ policy evaluated&lt;br&gt;
→ approval required if needed&lt;br&gt;
→ one-use permission created if needed&lt;br&gt;
→ execution queued&lt;br&gt;
→ provider or chain execution submitted once&lt;br&gt;
→ outcome tracked&lt;br&gt;
→ receipt created&lt;br&gt;
→ callback delivered&lt;br&gt;
→ reconciliation checked&lt;br&gt;
→ exception opened if reality disagrees&lt;/p&gt;

&lt;p&gt;This is more work than simply calling a provider API.&lt;/p&gt;

&lt;p&gt;But this is the work that makes financial execution trustworthy.&lt;/p&gt;

&lt;p&gt;How Azums approaches this&lt;/p&gt;

&lt;p&gt;Azums is being built as a durable financial execution layer for APIs and AI agents.&lt;/p&gt;

&lt;p&gt;The first launch scope is narrow:&lt;/p&gt;

&lt;p&gt;Paystack payments&lt;br&gt;
Paystack transfers&lt;br&gt;
Paystack refunds&lt;br&gt;
Solana transactions&lt;br&gt;
Solana USDT transfers&lt;/p&gt;

&lt;p&gt;Azums is non-custodial.&lt;/p&gt;

&lt;p&gt;It does not hold customer funds or private keys.&lt;/p&gt;

&lt;p&gt;The goal is to control execution around financial actions:&lt;/p&gt;

&lt;p&gt;identity&lt;br&gt;
idempotency&lt;br&gt;
policy&lt;br&gt;
approval&lt;br&gt;
one-use permissions&lt;br&gt;
provider or chain execution&lt;br&gt;
receipts&lt;br&gt;
callbacks&lt;br&gt;
reconciliation&lt;br&gt;
exceptions&lt;br&gt;
audit proof&lt;/p&gt;

&lt;p&gt;The key belief is:&lt;/p&gt;

&lt;p&gt;Financial systems should never silently fail, duplicate money movement, or pretend uncertainty is success.&lt;/p&gt;

&lt;p&gt;Final thought&lt;/p&gt;

&lt;p&gt;Idempotency is not just a UUID.&lt;/p&gt;

&lt;p&gt;It is a correctness contract.&lt;/p&gt;

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

&lt;p&gt;This financial action has one identity, one payload, one lifecycle, and one final truth.&lt;/p&gt;

&lt;p&gt;When you combine idempotency with durable execution, receipts, reconciliation, and exceptions, you get a system that can survive the messy reality of financial infrastructure.&lt;/p&gt;

&lt;p&gt;That is the kind of system I believe more teams will need as APIs and AI agents start touching real money.&lt;/p&gt;

&lt;p&gt;I’m building Azums to explore that future.&lt;/p&gt;

&lt;p&gt;Follow the build at &lt;a href="https://www.getazums.com" rel="noopener noreferrer"&gt;getazums.com&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%2F1szn9zqd5nhxhs4p0x1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1szn9zqd5nhxhs4p0x1r.png" alt=" " width="799" height="368"&gt;&lt;/a&gt;&lt;br&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%2Flk6zqjjn9eh3whr5u52t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flk6zqjjn9eh3whr5u52t.png" alt=" " width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>fintech</category>
      <category>systemdesign</category>
      <category>webdev</category>
    </item>
    <item>
      <title>One RPC Provider Is Not Blockchain Reliability</title>
      <dc:creator>Obinna Victor</dc:creator>
      <pubDate>Sun, 28 Jun 2026 19:30:33 +0000</pubDate>
      <link>https://dev.to/sopuruchii/one-rpc-provider-is-not-blockchain-reliability-2i60</link>
      <guid>https://dev.to/sopuruchii/one-rpc-provider-is-not-blockchain-reliability-2i60</guid>
      <description>&lt;p&gt;-One RPC Provider Is Not Blockchain Reliability&lt;/p&gt;

&lt;p&gt;A lot of blockchain applications start with a very simple backend setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BACKEND_RPC_URL=https://some-rpc-provider.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then everything goes through that one provider.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;balance checks&lt;/li&gt;
&lt;li&gt;account reads&lt;/li&gt;
&lt;li&gt;transaction lookups&lt;/li&gt;
&lt;li&gt;latest block/slot checks&lt;/li&gt;
&lt;li&gt;transaction simulation&lt;/li&gt;
&lt;li&gt;transaction submission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, this feels fine.&lt;/p&gt;

&lt;p&gt;The app works.&lt;br&gt;
The backend can read chain state.&lt;br&gt;
The frontend can show balances.&lt;br&gt;
Transactions can be submitted.&lt;/p&gt;

&lt;p&gt;But in production, one RPC URL can quietly become a hidden source of fragility.&lt;/p&gt;

&lt;p&gt;Because one RPC provider is not the blockchain itself.&lt;/p&gt;

&lt;p&gt;It is only one gateway into the blockchain.&lt;/p&gt;

&lt;p&gt;If your entire backend depends on that one gateway, then your app is not only trusting the blockchain.&lt;/p&gt;

&lt;p&gt;It is trusting one provider’s availability, freshness, latency, rate limits, supported methods, and view of the chain.&lt;/p&gt;

&lt;p&gt;That is not reliability.&lt;/p&gt;

&lt;p&gt;That is a single point of failure.&lt;/p&gt;

&lt;p&gt;What RPC means in blockchain&lt;/p&gt;

&lt;p&gt;RPC means Remote Procedure Call.&lt;/p&gt;

&lt;p&gt;In general backend terms, RPC is a way for one program to ask another program or server to execute an operation and return a result.&lt;/p&gt;

&lt;p&gt;In blockchain systems, an RPC endpoint lets your application talk to a blockchain node.&lt;/p&gt;

&lt;p&gt;For example, your app may call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getBalance
getAccountInfo
getTransaction
getLatestBlockhash
sendTransaction
simulateTransaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your app asks the RPC node a question or submits a transaction.&lt;/p&gt;

&lt;p&gt;The RPC node responds or broadcasts the transaction to the network.&lt;/p&gt;

&lt;p&gt;So a typical design looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend / Backend
        ↓
RPC Provider
        ↓
Blockchain Network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is normal.&lt;/p&gt;

&lt;p&gt;The problem starts when this becomes your entire reliability model.&lt;/p&gt;

&lt;p&gt;The naive architecture&lt;/p&gt;

&lt;p&gt;A lot of apps are designed like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  ↓
Frontend
  ↓
Backend
  ↓
One RPC URL
  ↓
Blockchain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend trusts whatever the provider says.&lt;/p&gt;

&lt;p&gt;If the provider responds, the backend assumes the response is enough.&lt;/p&gt;

&lt;p&gt;If the provider times out, the backend treats it like failure.&lt;/p&gt;

&lt;p&gt;If the provider cannot find a transaction, the backend assumes the transaction is missing.&lt;/p&gt;

&lt;p&gt;If the provider is rate-limited, the app becomes degraded.&lt;/p&gt;

&lt;p&gt;This works on the happy path.&lt;/p&gt;

&lt;p&gt;It does not handle production reality well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure mode 1: the RPC provider is slow
&lt;/h2&gt;

&lt;p&gt;Imagine the backend needs a response within two seconds.&lt;/p&gt;

&lt;p&gt;The provider responds after ten seconds.&lt;/p&gt;

&lt;p&gt;Your backend times out.&lt;/p&gt;

&lt;p&gt;A beginner system may treat that timeout as failure.&lt;/p&gt;

&lt;p&gt;But timeout does not mean the blockchain failed.&lt;/p&gt;

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

&lt;p&gt;My system did not receive an answer in time.&lt;/p&gt;

&lt;p&gt;That is not the same thing as:&lt;/p&gt;

&lt;p&gt;The operation failed on-chain.&lt;/p&gt;

&lt;p&gt;This distinction matters a lot.&lt;/p&gt;

&lt;p&gt;Especially when the request is related to transaction status or submission.&lt;/p&gt;

&lt;p&gt;Failure mode 2: the provider is rate-limited&lt;/p&gt;

&lt;p&gt;Now imagine your product starts getting more usage.&lt;/p&gt;

&lt;p&gt;Many users are checking balances.&lt;br&gt;
Workers are checking transaction status.&lt;br&gt;
Background jobs are polling chain state.&lt;br&gt;
The dashboard is refreshing operational data.&lt;/p&gt;

&lt;p&gt;Then the RPC provider starts returning rate-limit errors.&lt;/p&gt;

&lt;p&gt;If your backend only has one provider, your whole system becomes dependent on that provider’s quota.&lt;/p&gt;

&lt;p&gt;A better system should be able to fail over, shed load, cache safe reads, or route intelligently.&lt;br&gt;
 Failure mode 3: the provider is stale&lt;/p&gt;

&lt;p&gt;This is one of the most dangerous cases.&lt;/p&gt;

&lt;p&gt;Suppose your backend checks whether a transaction landed.&lt;/p&gt;

&lt;p&gt;Provider A says:&lt;/p&gt;

&lt;p&gt;Transaction not found&lt;/p&gt;

&lt;p&gt;But Provider B can already see it.&lt;/p&gt;

&lt;p&gt;If your backend only trusts Provider A, it may mark the transaction as failed or unknown too early.&lt;/p&gt;

&lt;p&gt;In blockchain systems, stale reads can create bad product behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wrong user balances&lt;/li&gt;
&lt;li&gt;wrong transaction status&lt;/li&gt;
&lt;li&gt;incorrect failure messages&lt;/li&gt;
&lt;li&gt;unnecessary retries&lt;/li&gt;
&lt;li&gt;confused operators&lt;/li&gt;
&lt;li&gt;broken reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One provider’s view is not always enough.&lt;/p&gt;

&lt;p&gt;Failure mode 4: provider-specific errors&lt;/p&gt;

&lt;p&gt;Sometimes one provider returns an error while another provider would have succeeded.&lt;/p&gt;

&lt;p&gt;This can happen because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provider outage&lt;/li&gt;
&lt;li&gt;regional latency&lt;/li&gt;
&lt;li&gt;method support differences&lt;/li&gt;
&lt;li&gt;rate limits&lt;/li&gt;
&lt;li&gt;stale indexers&lt;/li&gt;
&lt;li&gt;degraded nodes&lt;/li&gt;
&lt;li&gt;provider-specific bugs&lt;/li&gt;
&lt;li&gt;chain lag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the problem is not simply:&lt;/p&gt;

&lt;p&gt;Did the blockchain work?&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;Is this provider giving my backend a reliable view of the blockchain?&lt;/p&gt;

&lt;p&gt;The dangerous part: knowing what actually happened&lt;/p&gt;

&lt;p&gt;In blockchain systems, the dangerous part is not only sending a transaction.&lt;/p&gt;

&lt;p&gt;The dangerous part is knowing what actually happened after the transaction was sent.&lt;/p&gt;

&lt;p&gt;Questions your backend should be able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the transaction land?&lt;/li&gt;
&lt;li&gt;Did it fail?&lt;/li&gt;
&lt;li&gt;Is it still pending?&lt;/li&gt;
&lt;li&gt;Did the provider timeout before returning the signature?&lt;/li&gt;
&lt;li&gt;Did the provider return stale data?&lt;/li&gt;
&lt;li&gt;Did another provider see the transaction?&lt;/li&gt;
&lt;li&gt;Was the backend trusting incomplete information?&lt;/li&gt;
&lt;li&gt;Is it safe to retry?&lt;/li&gt;
&lt;li&gt;What evidence do we have?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your system cannot answer those questions, your operators are blind.&lt;/p&gt;

&lt;p&gt;And if operators are blind, users eventually feel it.&lt;/p&gt;

&lt;p&gt;A better architecture: RPC reliability layer&lt;/p&gt;

&lt;p&gt;A better architecture introduces an RPC reliability layer.&lt;/p&gt;

&lt;p&gt;User&lt;br&gt;
  ↓&lt;br&gt;
Frontend&lt;br&gt;
  ↓&lt;br&gt;
Backend&lt;br&gt;
  ↓&lt;br&gt;
RPC Gateway / Reliability Layer&lt;br&gt;
  ↓&lt;br&gt;
Provider A&lt;br&gt;
Provider B&lt;br&gt;
Provider C&lt;br&gt;
  ↓&lt;br&gt;
Blockchain Network&lt;/p&gt;

&lt;p&gt;The backend no longer directly trusts one provider.&lt;/p&gt;

&lt;p&gt;Instead, it routes through infrastructure that understands provider failure.&lt;/p&gt;

&lt;p&gt;This RPC layer can handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provider health checks&lt;/li&gt;
&lt;li&gt;request timeouts&lt;/li&gt;
&lt;li&gt;failover&lt;/li&gt;
&lt;li&gt;provider scoring&lt;/li&gt;
&lt;li&gt;method-aware routing&lt;/li&gt;
&lt;li&gt;safe caching&lt;/li&gt;
&lt;li&gt;request coalescing&lt;/li&gt;
&lt;li&gt;cross-provider validation&lt;/li&gt;
&lt;li&gt;operational response headers&lt;/li&gt;
&lt;li&gt;status visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference is mindset.&lt;/p&gt;

&lt;p&gt;The naive system says:&lt;/p&gt;

&lt;p&gt;I trust this one RPC URL.&lt;/p&gt;

&lt;p&gt;The better system says:&lt;/p&gt;

&lt;p&gt;I route through infrastructure that understands RPC failure.&lt;/p&gt;

&lt;p&gt;Provider failover&lt;/p&gt;

&lt;p&gt;Provider failover means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If Provider A fails, try Provider B.
If Provider B fails, try Provider C.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple idea, big impact.&lt;/p&gt;

&lt;p&gt;One provider being down should not mean your whole blockchain app is down.&lt;/p&gt;

&lt;p&gt;The system should track things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which provider is healthy&lt;/li&gt;
&lt;li&gt;which provider is slow&lt;/li&gt;
&lt;li&gt;which provider recently failed&lt;/li&gt;
&lt;li&gt;which provider is behind&lt;/li&gt;
&lt;li&gt;which provider is rate-limited&lt;/li&gt;
&lt;li&gt;which provider has better success rate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it should route intelligently.&lt;/p&gt;

&lt;p&gt;Timeout handling&lt;/p&gt;

&lt;p&gt;Timeouts should be treated carefully.&lt;/p&gt;

&lt;p&gt;A timeout is not proof of failure.&lt;/p&gt;

&lt;p&gt;It is proof that the backend did not receive an answer in time.&lt;/p&gt;

&lt;p&gt;For read requests, retries are usually safer.&lt;/p&gt;

&lt;p&gt;For write requests like &lt;code&gt;sendTransaction&lt;/code&gt;, retry behavior needs more care.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because transaction submission and transaction status are not the same as reading account data.&lt;/p&gt;

&lt;p&gt;Your system needs method awareness.&lt;/p&gt;

&lt;p&gt;Method policy&lt;/p&gt;

&lt;p&gt;Not all RPC methods should be treated the same.&lt;/p&gt;

&lt;p&gt;Some methods are read-only.&lt;/p&gt;

&lt;p&gt;Some submit transactions.&lt;/p&gt;

&lt;p&gt;Some can be cached.&lt;/p&gt;

&lt;p&gt;Some should never be cached.&lt;/p&gt;

&lt;p&gt;Some are more important for correctness.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
getBalance              -&amp;gt; read&lt;br&gt;
getAccountInfo          -&amp;gt; read&lt;br&gt;
getTransaction          -&amp;gt; status/evidence read&lt;br&gt;
getLatestBlockhash      -&amp;gt; time-sensitive read&lt;br&gt;
simulateTransaction     -&amp;gt; simulation&lt;br&gt;
sendTransaction         -&amp;gt; write/broadcast&lt;/p&gt;

&lt;p&gt;A serious RPC gateway should have method policy.&lt;/p&gt;

&lt;p&gt;That policy can answer:&lt;/p&gt;

&lt;p&gt;Can this method be cached?&lt;br&gt;
Should this method be validated?&lt;br&gt;
Is this method consensus-critical?&lt;br&gt;
Is retry safe?&lt;br&gt;
Should this method use multiple providers?&lt;/p&gt;

&lt;p&gt;This is how you move from random RPC calls to infrastructure design.&lt;/p&gt;

&lt;p&gt;Request coalescing&lt;/p&gt;

&lt;p&gt;Request coalescing is another useful pattern.&lt;/p&gt;

&lt;p&gt;Imagine 100 requests ask for the same data at the same time.&lt;/p&gt;

&lt;p&gt;The naive system sends 100 identical upstream calls to the RPC provider.&lt;/p&gt;

&lt;p&gt;That increases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;load&lt;/li&gt;
&lt;li&gt;cost&lt;/li&gt;
&lt;li&gt;rate-limit risk&lt;/li&gt;
&lt;li&gt;latency pressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A better gateway can notice that the requests are identical.&lt;/p&gt;

&lt;p&gt;It sends one upstream request and shares the result with the waiting callers.&lt;/p&gt;

&lt;p&gt;100 identical local requests&lt;br&gt;
          ↓&lt;br&gt;
1 upstream RPC call&lt;br&gt;
          ↓&lt;br&gt;
shared result&lt;/p&gt;

&lt;p&gt;That is request coalescing.&lt;/p&gt;

&lt;p&gt;It helps the backend stay stable under traffic.&lt;/p&gt;

&lt;p&gt;Caching, but carefully&lt;/p&gt;

&lt;p&gt;Caching can help, but it must be method-aware.&lt;/p&gt;

&lt;p&gt;The mistake is caching everything blindly.&lt;/p&gt;

&lt;p&gt;If you cache the wrong data, your app may show stale state.&lt;/p&gt;

&lt;p&gt;If you cache nothing, your app may hit rate limits faster and waste money.&lt;/p&gt;

&lt;p&gt;So caching should depend on the method.&lt;/p&gt;

&lt;p&gt;Questions to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this method safe to cache?&lt;/li&gt;
&lt;li&gt;How long should it be cached?&lt;/li&gt;
&lt;li&gt;Is this data user-facing?&lt;/li&gt;
&lt;li&gt;Is this data used for a financial or execution decision?&lt;/li&gt;
&lt;li&gt;Is stale data dangerous here?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Caching is not just a performance feature.&lt;/p&gt;

&lt;p&gt;In blockchain infrastructure, it is also a correctness decision.&lt;/p&gt;

&lt;p&gt;Cross-provider validation&lt;/p&gt;

&lt;p&gt;For some important reads, one provider may not be enough.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provider A: transaction not found
Provider B: transaction found
Provider C: transaction found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should the backend believe?&lt;/p&gt;

&lt;p&gt;For every request, cross-provider validation may be too expensive.&lt;/p&gt;

&lt;p&gt;But for important reads around execution state, settlement, wallet safety, or transaction status, it can protect your system from trusting one stale provider.&lt;/p&gt;

&lt;p&gt;The point is not to call every provider all the time.&lt;/p&gt;

&lt;p&gt;The point is to know when one signal is too weak.&lt;/p&gt;

&lt;p&gt;Operator visibility&lt;/p&gt;

&lt;p&gt;A good RPC reliability layer should not hide what happened.&lt;/p&gt;

&lt;p&gt;It should expose operational truth.&lt;/p&gt;

&lt;p&gt;When something goes wrong, operators should be able to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which provider did we use?&lt;/li&gt;
&lt;li&gt;Did the provider timeout?&lt;/li&gt;
&lt;li&gt;Did we retry another provider?&lt;/li&gt;
&lt;li&gt;Was the response from cache?&lt;/li&gt;
&lt;li&gt;Was the request coalesced?&lt;/li&gt;
&lt;li&gt;Did we validate across providers?&lt;/li&gt;
&lt;li&gt;Did providers disagree?&lt;/li&gt;
&lt;li&gt;Was this method considered critical?&lt;/li&gt;
&lt;li&gt;Did the backend make a decision from enough evidence?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why response metadata matters.&lt;/p&gt;

&lt;p&gt;Infrastructure should not only return data.&lt;/p&gt;

&lt;p&gt;It should explain how the data was obtained.&lt;/p&gt;

&lt;p&gt;My RPC Gateway project&lt;/p&gt;

&lt;p&gt;I explored this idea in my RPC Gateway project.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/BlockForge-Dev/RPC-Gateway" rel="noopener noreferrer"&gt;https://github.com/BlockForge-Dev/RPC-Gateway&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal of the project is not just to forward JSON-RPC requests.&lt;/p&gt;

&lt;p&gt;The goal is to make provider failure, latency variance, stale reads, and read disagreement visible.&lt;/p&gt;

&lt;p&gt;The project explores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-provider failover&lt;/li&gt;
&lt;li&gt;adaptive hedging&lt;/li&gt;
&lt;li&gt;predictive provider scoring&lt;/li&gt;
&lt;li&gt;Solana method policy&lt;/li&gt;
&lt;li&gt;request coalescing&lt;/li&gt;
&lt;li&gt;method-aware caching&lt;/li&gt;
&lt;li&gt;consensus validation for important reads&lt;/li&gt;
&lt;li&gt;provider health tracking&lt;/li&gt;
&lt;li&gt;response headers that explain selected provider, attempts, cache behavior, hedging, and validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also recorded a video explaining the idea here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/Mv7R9ISm0rA" rel="noopener noreferrer"&gt;https://youtu.be/Mv7R9ISm0rA&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bigger lesson
&lt;/h2&gt;

&lt;p&gt;Blockchain apps are not reliable just because they use a blockchain.&lt;/p&gt;

&lt;p&gt;Your backend can still be fragile.&lt;/p&gt;

&lt;p&gt;Your RPC provider can fail.&lt;br&gt;
Your worker can crash.&lt;br&gt;
Your webhook can arrive late.&lt;br&gt;
Your provider can be stale.&lt;br&gt;
Your transaction can be ambiguous.&lt;br&gt;
Your UI can show the wrong state.&lt;/p&gt;

&lt;p&gt;So serious blockchain infrastructure needs to design for failure from the beginning.&lt;/p&gt;

&lt;p&gt;Not after users complain.&lt;/p&gt;

&lt;p&gt;Not after funds are stuck.&lt;/p&gt;

&lt;p&gt;Not after operators are confused.&lt;/p&gt;

&lt;p&gt;From the beginning.&lt;/p&gt;

&lt;p&gt;Final thought&lt;/p&gt;

&lt;p&gt;One RPC provider is not blockchain reliability.&lt;/p&gt;

&lt;p&gt;One RPC URL is just one view into the chain.&lt;/p&gt;

&lt;p&gt;If that view is slow, stale, rate-limited, or down, your backend needs a better plan.&lt;/p&gt;

&lt;p&gt;That better plan includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple providers&lt;/li&gt;
&lt;li&gt;health checks&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;failover&lt;/li&gt;
&lt;li&gt;method policy&lt;/li&gt;
&lt;li&gt;safe retries&lt;/li&gt;
&lt;li&gt;request coalescing&lt;/li&gt;
&lt;li&gt;caching with care&lt;/li&gt;
&lt;li&gt;cross-provider validation for important reads&lt;/li&gt;
&lt;li&gt;status visibility&lt;/li&gt;
&lt;li&gt;receipts&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of backend and blockchain infrastructure I build.&lt;/p&gt;

&lt;p&gt;I build systems around reliable transaction execution, RPC reliability, operator truth, receipts, and reconciliation.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/BlockForge-Dev" rel="noopener noreferrer"&gt;https://github.com/BlockForge-Dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>web3</category>
      <category>backend</category>
      <category>rust</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Why I treat API timeouts as "unknown", not failures</title>
      <dc:creator>Obinna Victor</dc:creator>
      <pubDate>Fri, 29 May 2026 12:23:31 +0000</pubDate>
      <link>https://dev.to/sopuruchii/why-i-treat-api-timeouts-as-unknown-not-failures-509a</link>
      <guid>https://dev.to/sopuruchii/why-i-treat-api-timeouts-as-unknown-not-failures-509a</guid>
      <description>&lt;p&gt;Every payment gateway I've ever worked on had the same hidden bug.&lt;/p&gt;

&lt;p&gt;A provider API times out. The code says "failure". So you retry. But the original request actually succeeded – the provider just took too long to respond. Now you've double‑charged the customer.&lt;/p&gt;

&lt;p&gt;I built &lt;code&gt;Azums&lt;/code&gt;, an open‑source payment gateway in Rust, specifically to stop this pattern.&lt;/p&gt;

&lt;p&gt;_&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;_The fix: make ambiguity explicit.&lt;/p&gt;

&lt;p&gt;Instead of &lt;code&gt;pending → success/fail&lt;/code&gt;, I designed a state machine with five states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pending&lt;/code&gt; (request sent, waiting)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;succeeded&lt;/code&gt; (confirmed success)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failed&lt;/code&gt; (confirmed failure)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retryable&lt;/code&gt; (temporary error, safe to retry)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;unknown&lt;/code&gt;&lt;/strong&gt; (timeout or ambiguous response – needs investigation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a timeout happens, the system doesn't guess. It marks the transaction as &lt;code&gt;unknown&lt;/code&gt; and stops. No blind retries. No double charges.&lt;/p&gt;

&lt;p&gt;Why this matters beyond payments&lt;/p&gt;

&lt;p&gt;This same pattern applies anywhere you talk to unreliable external systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blockchain RPCs that timeout after the transaction was submitted&lt;/li&gt;
&lt;li&gt;AI agent API calls that hang but may have executed&lt;/li&gt;
&lt;li&gt;Messaging queues that lose acknowledgements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treating ambiguity as a real state is the difference between a system that guesses and a system that you can trust.&lt;/p&gt;

&lt;p&gt;My full implementation is on GitHub: &lt;a href="https://github.com/BlockForge-Dev/Azums" rel="noopener noreferrer"&gt;BlockForge-Dev/Azums&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's your worst "timeout caused a disaster" story? Let me know in the comments.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>distributedsystems</category>
      <category>sre</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
