StableOps
Concepts

Confirmations

Understanding blockchain confirmations and the four-stage state machine.

Blockchain confirmations are the process by which transactions become increasingly secure and irreversible. StableOps tracks payments through four distinct confirmation stages to balance speed with security.

Overview

When a payment is sent on a blockchain, it goes through multiple stages before it's considered final:

DETECTED → CONFIRMED → FINALIZED
   ↓           ↓
REVERTED    REVERTED

Each stage represents a different level of confidence that the payment is permanent:

  • DETECTED: Transaction seen in a queryable on-chain block
  • CONFIRMED: Transaction has required confirmations (chain-specific)
  • FINALIZED: Transaction reached StableOps' configured final confirmation depth
  • REVERTED: Transaction was reversed due to reorg or failure

Why Confirmations Matter

Blockchains are distributed systems where multiple nodes compete to add blocks. This can lead to:

  • Blockchain reorganizations (reorgs): A competing chain becomes longer, reversing recent blocks
  • Transaction failures: Smart contract execution fails or runs out of gas
  • Double-spend attempts: Malicious actors try to reverse transactions

Confirmations protect against these risks by waiting for the transaction to be buried deep enough in the blockchain that reversal becomes computationally infeasible.

Confirmation Stages

DETECTED

The transaction has been included in a block that StableOps can scan and match to a payment order. StableOps does not promote orders from mempool data alone.

On most chains this is effectively "first block seen". On some networks, StableOps may intentionally scan slightly behind the head to avoid RPC log-index lag, so detected should be treated as "seen on-chain" rather than "exactly 0 confirmations".

Confidence Level: Low (5-10%)

Risk:

  • Transaction could be replaced (if using RBF)
  • Block could be orphaned in a reorg
  • Transaction could fail execution

When to Use:

  • Show "Payment received, confirming..." in UI
  • Update order status to "pending"
  • Do not fulfill the order yet

CONFIRMED (Chain-Specific Confirmations)

The transaction has received the required number of confirmations for the specific blockchain.

Confidence Level: High (95-99%)

Times below are estimates derived from StableOps' current default thresholds and approximate block times. They are not protocol guarantees and should not be treated as an SLA.

Confirmation Requirements by Chain:

ChainConfirmationsTimeRationale
Base2 blocks~4 secondsOptimistic rollup, low reorg risk
Optimism2 blocks~4 secondsOptimistic rollup, low reorg risk
Ethereum6 blocks~1.2 minutesStandard for exchanges
Arbitrum1 block~0.3 secondsOptimistic rollup
Polygon20 blocks~40 secondsHigher reorg risk
BNB Chain7 blocks~21 secondsPoSA + BEP-126 Fast Finality
TRON1 block~3 secondsDPOS consensus
Solana1 slot~0.4 secondsUses confirmed slot

Solana and Solana Devnet currently use the same thresholds: 1 slot to enter confirmed, 32 slots to enter finalized.

Risk:

  • Deep reorgs are still theoretically possible
  • Very rare in practice (< 0.01% of transactions)

When to Use:

  • Low-value transactions (< $100)
  • Digital goods delivery
  • Account credits
  • Time-sensitive fulfillment

FINALIZED (Final Confirmation Depth Reached)

The transaction has reached StableOps' configured final confirmation depth. StableOps treats FINALIZED as a terminal order state and recommends it for production fulfillment, but this depth-based product threshold is not a guarantee of protocol-level or L1 finality.

Confidence Level: Very high

Times below are estimates derived from StableOps' current default thresholds and approximate block times. Real-world timing can vary with network conditions and RPC behavior.

Final Confirmation Depths by Chain:

ChainDepthTime
Base18 blocks~36 seconds
Optimism18 blocks~36 seconds
Ethereum64 blocks~13 minutes
Arbitrum20 blocks~5 seconds
Polygon128 blocks~4.3 minutes
BNB Chain21 blocks~63 seconds
TRON19 blocks~1 minute
Solana32 slots~13 seconds

Risk: Low residual chain, RPC, and operational risk remains; use your own controls for high-value or irreversible fulfillment.

When to Use:

  • High-value transactions (> $100)
  • Physical goods shipment
  • Irreversible actions (account upgrades, subscriptions)
  • Recommended for all production fulfillment

REVERTED (Transaction Reversed)

The transaction was reversed due to a blockchain reorganization or execution failure.

Causes:

  • Blockchain reorg: A competing chain became longer
  • Transaction failure: Smart contract execution failed
  • Insufficient gas: Transaction ran out of gas
  • Receipt not found: Transaction disappeared from blockchain

Frequency: Very rare (< 0.01% of confirmed transactions)

When This Happens:

  1. StableOps detects the reorg or failure
  2. Order status changes to REVERTED
  3. payment.reverted webhook is sent
  4. Address is released back to the pool (becomes AVAILABLE for reuse)

See the Payment Events API reference for the full payload schemas.

How to Handle:

app.post('/webhooks/stableops', async (req, res) => {
  const event = req.body

  if (event.type === 'payment.reverted') {
    const orderId = event.data.payment_order_id

    // 1. Reverse any fulfillment (if already done)
    await reverseOrderFulfillment(orderId)

    // 2. Notify customer
    await sendEmail(customer, 'Payment failed, please try again')

    // 3. Update your database
    await db.orders.update({
      id: orderId,
      status: 'payment_failed',
      reason: event.data.reason,
    })

    // 4. Create new payment order (optional)
    const newOrder = await stableops.paymentOrders.create({
      merchantOrderId: `${orderId}_retry`,
      amount: originalAmount,
      expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
      // ...
    })
  }

  res.sendStatus(200)
})

Choosing the Right Confirmation Level

Decision Matrix

Transaction ValueFulfillment TypeRecommended StageRationale
< $10Digital goodsCONFIRMEDFast, low risk
$10 - $100Digital goodsCONFIRMEDBalanced speed/security
$100 - $1,000Digital goodsFINALIZEDHigher security needed
> $1,000AnyFINALIZEDMaximum security
AnyPhysical goodsFINALIZEDIrreversible shipping
AnyAccount upgradesFINALIZEDIrreversible changes
AnySubscriptionsFINALIZEDRecurring billing

Risk Tolerance

Low Risk Tolerance (Financial services, high-value goods):

  • Always wait for FINALIZED
  • Never fulfill on DETECTED
  • Consider additional fraud checks

Medium Risk Tolerance (E-commerce, SaaS):

  • FINALIZED for > $100
  • CONFIRMED for < $100
  • DETECTED only for UI updates

High Risk Tolerance (Gaming, low-value digital goods):

  • CONFIRMED for most transactions
  • FINALIZED for account changes
  • Accept occasional reversals

Monitoring Confirmations

Real-Time Updates

StableOps continuously monitors the blockchain and sends webhooks as confirmations increase:

// Timeline of webhooks for a single payment
12:00:00 - payment.detected
12:00:12 - payment.confirmed
12:13:00 - payment.finalized

Polling Alternative

If you prefer polling over webhooks:

const checkPaymentStatus = async (orderId: string) => {
  const order = await stableops.paymentOrders.retrieve(orderId)

  switch (order.status) {
    case 'detected':
      console.log('Payment seen, waiting for confirmations...')
      break
    case 'confirmed':
      console.log('Payment confirmed, waiting for finality...')
      break
    case 'finalized':
      console.log('Payment finalized, safe to fulfill!')
      await fulfillOrder(order)
      break
    case 'reverted':
      console.log('Payment reverted, handle failure')
      await handleRevert(order)
      break
  }
}

// Poll every 5 seconds
const interval = setInterval(() => checkPaymentStatus(orderId), 5000)

Blockchain Reorganizations

What is a Reorg?

A blockchain reorganization occurs when a competing chain becomes longer than the current chain, causing recent blocks to be replaced.

Before Reorg:
Block 100 → Block 101 → Block 102 (your transaction)
                     ↘ Block 102' (competing chain)

After Reorg:
Block 100 → Block 101 → Block 102' → Block 103'
                     ✗ Block 102 (orphaned)

Reorg Frequency by Chain

ChainReorg DepthFrequencyNotes
Ethereum1-2 blocksDailyUsually harmless
Ethereum> 3 blocksRareRequires investigation
Base1 blockOccasionalL2 reorgs are rare
Optimism1 blockOccasionalL2 reorgs are rare
Polygon1-5 blocksCommonHigher reorg risk
BNB Chain1-3 blocksOccasionalPoSA, deep reorgs rare
TRON1 blockRareDPOS consensus
Solana1–2 slotsOccasionalFast confirmation

How StableOps Handles Reorgs

  1. Continuous Monitoring: StableOps re-checks the transaction receipt on every confirmation; chains that expose blockHash additionally compare block hashes
  2. Reorg Detection: A changed blockHash, or a missing/failed receipt, is treated as a revert
  3. Status Update: Order status changes to REVERTED
  4. Webhook Notification: payment.reverted webhook is sent
  5. Address Release: Address is released back to the pool (AVAILABLE)

Reorg Protection

StableOps implements multiple layers of reorg protection:

  • Block hash verification: Compare stored blockHash with current chain (not used on TRON / Solana, which don't populate this field)
  • Receipt validation: Verify transaction receipt still exists
  • Confirmation counting: Only count blocks on the canonical chain
  • Final confirmation depth: Wait for the configured chain-specific depth

Best Practices

1. Always Wait for FINALIZED

// ✅ Good - wait for finality
app.post('/webhooks/stableops', async (req, res) => {
  const event = req.body

  if (event.type === 'payment.finalized') {
    await fulfillOrder(event.data.payment_order_id)
  }

  res.sendStatus(200)
})

// ❌ Bad - fulfill on detection
app.post('/webhooks/stableops', async (req, res) => {
  const event = req.body

  if (event.type === 'payment.detected') {
    await fulfillOrder(event.data.payment_order_id) // Risky!
  }

  res.sendStatus(200)
})

2. Handle All States

const handlePaymentWebhook = async (event: WebhookEvent) => {
  switch (event.type) {
    case 'payment.detected':
      await updateUI('Payment received, confirming...')
      break
    case 'payment.confirmed':
      await updateUI('Payment confirmed, finalizing...')
      break
    case 'payment.finalized':
      await fulfillOrder(event.data.payment_order_id)
      break
    case 'payment.reverted':
      await handleRevert(event.data.payment_order_id)
      break
  }
}

3. Show Progress to Users

// Update UI based on confirmation stage
const PaymentStatus = ({ order }) => {
  switch (order.status) {
    case 'detected':
      return (
        <div>
          <Spinner />
          <p>Payment received, confirming...</p>
        </div>
      )
    case 'confirmed':
      return (
        <div>
          <Spinner />
          <p>Payment confirmed, finalizing...</p>
        </div>
      )
    case 'finalized':
      return (
        <div>
          <CheckIcon />
          <p>Payment complete! Your order is being processed.</p>
        </div>
      )
  }
}

4. Log Everything

// Log all confirmation events for debugging
app.post('/webhooks/stableops', async (req, res) => {
  const event = req.body

  await db.webhookLogs.create({
    type: event.type,
    orderId: event.data.payment_order_id,
    fromStatus: event.data.from_status,
    toStatus: event.data.to_status,
    timestamp: new Date(),
  })

  // Process event...

  res.sendStatus(200)
})

5. Test Reorg Scenarios

// Simulate reorg handling in tests
describe('Payment reorg handling', () => {
  it('should reverse fulfillment on reorg', async () => {
    // 1. Create order
    const order = await createOrder()

    // 2. Simulate payment detected
    await handleWebhook({ type: 'payment.detected', data: order })

    // 3. Simulate payment confirmed
    await handleWebhook({ type: 'payment.confirmed', data: order })

    // 4. Simulate reorg
    await handleWebhook({ type: 'payment.reverted', data: order })

    // 5. Verify fulfillment was reversed
    const dbOrder = await db.orders.findOne({ id: order.id })
    expect(dbOrder.status).toBe('payment_failed')
  })
})

Common Patterns

Progressive Fulfillment

Fulfill parts of the order at different confirmation stages:

app.post('/webhooks/stableops', async (req, res) => {
  const event = req.body
  const orderId = event.data.payment_order_id

  switch (event.type) {
    case 'payment.confirmed':
      // Grant temporary access
      await grantTrialAccess(orderId)
      break

    case 'payment.finalized':
      // Upgrade to full access
      await grantFullAccess(orderId)
      break

    case 'payment.reverted':
      // Revoke all access
      await revokeAccess(orderId)
      break
  }

  res.sendStatus(200)
})

Conditional Fulfillment

Choose confirmation level based on transaction value:

const shouldFulfill = (order: PaymentOrder): boolean => {
  const amount = parseFloat(order.amount)

  if (amount < 100) {
    // Low value: fulfill on confirmed
    return order.status === 'confirmed' || order.status === 'finalized'
  } else {
    // High value: wait for finalized
    return order.status === 'finalized'
  }
}

Next Steps

How is this guide?

Last updated

On this page