🚧 Bloque documentation is under development

State Machines

Learn about the state machines that manage the lifecycle of entities in the Bloque SDK.

Overview

The Bloque SDK uses well-defined state machines to manage the lifecycle of all major entities. Understanding these states and their transitions is essential for building robust integrations.

Main State Machines

πŸ“‹ Accounts

Accounts manage user funds and follow a well-defined lifecycle from creation to deletion.

** Key states:**

  • creation_in_progress β†’ active β†’ disabled/frozen/deleted
  • Handles errors: creation_failed

Complete documentation: Virtual Accounts | Virtual Cards

πŸ’Έ Transfers

Transfers move funds between accounts and are processed asynchronously.

Key states:

  • queued β†’ processing β†’ completed/failed

Complete documentation: Transfers

🏒 Organizations

Organizations represent legal entities (businesses or individuals) with compliance requirements.

Key states:

  • awaiting_compliance_verification β†’ active β†’ suspended/closed

Complete documentation: Organizations

πŸ” KYC/Compliance

Manages the identity verification and compliance process.

Key states:

  • awaiting_compliance_verification β†’ approved/rejected

Complete documentation: Compliance & KYC

πŸ”„ Swap

Manages asset swap orders between different mediums and currencies.

Key states:

  • Orders: pending β†’ in_progress β†’ completed/failed
  • Execution: pending β†’ running β†’ completed/failed

Complete documentation: Swap

Common Patterns

πŸ”„ Transient vs Final States

Transient States:

  • creation_in_progress, processing, pending, awaiting_*
  • Require action or time to change
  • Usually have associated timeouts

Final States:

  • completed, failed, deleted, closed, approved, rejected
  • Don't change without external intervention
  • Represent the outcome of a process

⏱️ Polling Pattern

For states that resolve asynchronously:

async function waitForCompletion(getStatus: () => Promise<{status: string}>) {
  let attempts = 0;
  const maxAttempts = 30; // 30 attempts
  
  while (attempts < maxAttempts) {
    const result = await getStatus();
    
    if (['completed', 'failed', 'approved', 'rejected'].includes(result.status)) {
      return result; // Final state reached
    }
    
    // Wait 2 seconds between attempts
    await new Promise(resolve => setTimeout(resolve, 2000));
    attempts++;
  }
  
  throw new Error('Timeout waiting for completion');
}

πŸͺ Webhook Pattern

For real-time notifications of state changes:

// Set webhook URL when creating entities
const account = await session.accounts.virtual.create({
  firstName: 'John',
  lastName: 'Doe',
  webhookUrl: 'https://api.example.com/webhooks/account-status'
});

// Handle state changes
app.post('/webhooks/account-status', (req, res) => {
  const { type, data } = req.body;
  
  if (type === 'account.status_changed') {
    console.log(`Account ${data.urn} changed to: ${data.status}`);
    
    // Update local database
    updateLocalAccount(data.urn, { status: data.status });
  }
  
  res.status(200).send('OK');
});

Error Handling by State

Common Error States

ErrorAssociated StatesSolution
creation_failedAccounts, CardsVerify data, retry
failedTransfers, SwapCheck funds, validate parameters
rejectedKYC, OrganizationsContact support, review documentation
suspendedOrganizations, AccountsResolve compliance issue

Recovery Strategies

async function handleFailedTransfer(sourceUrn: string, destinationUrn: string) {
  // Verify accounts still exist and are active
  const source = await bloque.accounts.get(sourceUrn);
  const dest = await bloque.accounts.get(destinationUrn);

  if (source.status !== 'active' || dest.status !== 'active') {
    throw new Error('Source or destination account is not active.');
  }

  // Re-check available balance before retrying
  const balances = await bloque.accounts.balance(sourceUrn);
  if (!balances['DUSD/6'] || balances['DUSD/6'].current === '0') {
    throw new Error('Insufficient funds. Check your balance.');
  }
}

Best Practices

1. Check States Before Operations

// βœ… Good: verify state before transfer
const account = await session.accounts.get(accountUrn);
if (account.status !== 'active') {
  throw new Error('Account must be active to transfer');
}

await session.accounts.transfer({
  sourceUrn: accountUrn,
  destinationUrn: destUrn,
  amount: '1000000',
  asset: 'DUSD/6'
});

2. Handle Transient States with Timeouts

async function waitForActive(accountUrn: string, timeoutMs = 30000) {
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeoutMs) {
    const account = await session.accounts.get(accountUrn);
    
    if (account.status === 'active') {
      return account;
    }
    
    if (account.status === 'creation_failed') {
      throw new Error('Account creation failed');
    }
    
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
  
  throw new Error('Timeout waiting for account activation');
}

3. Implement Exponential Backoff Retries

async function retryOperation<T>(
  operation: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000
): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      const delay = baseDelay * Math.pow(2, attempt - 1);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw new Error('Max retries exceeded');
}

Quick References

State Summary

EntityKey StatesTypical Resolution Time
Accounts6 states1-30 seconds
Transfers4 states5-60 seconds
Organizations4 states1-3 days (manual)
KYC3 states5-30 minutes
Swap5 states (orders)1-10 minutes

Flow Diagrams

For visualizing state transitions, check the specific entity guides where you'll find detailed transition tables and practical examples.

Next Steps