data flow - saltict/Demo-Docs GitHub Wiki

Data Flow Architecture

Comprehensive overview of data flow patterns, request/response cycles, and integration points within the SubWallet Services SDK.

📖 Navigation


Table of Contents

Request/Response Flow

High-Level Request Flow

%%{init: {'theme':'dark', 'themeVariables': { 
  'primaryColor': '#ff6b6b', 
  'primaryTextColor': '#fff', 
  'primaryBorderColor': '#ff6b6b', 
  'lineColor': '#ffa726', 
  'sectionBkColor': '#2d3748', 
  'altSectionBkColor': '#1a202c', 
  'gridColor': '#4a5568', 
  'secondaryColor': '#4a5568', 
  'tertiaryColor': '#2d3748'
}}}%%
sequenceDiagram
    participant Client
    participant SDK
    participant ServiceAPI
    participant BaseAPI
    participant HTTPClient
    participant SubWalletServices
    
    Client->>SDK: Method Call
    SDK->>ServiceAPI: Route to Service
    ServiceAPI->>ServiceAPI: Validate Parameters
    ServiceAPI->>BaseAPI: Format Request
    BaseAPI->>BaseAPI: Add Headers & Config
    BaseAPI->>HTTPClient: Execute Request
    HTTPClient->>SubWalletServices: HTTP Request
    SubWalletServices-->>HTTPClient: HTTP Response
    HTTPClient-->>BaseAPI: Raw Response
    BaseAPI->>BaseAPI: Process Response
    BaseAPI->>BaseAPI: Handle Errors
    BaseAPI-->>ServiceAPI: Typed Response
    ServiceAPI-->>SDK: Service Result
    SDK-->>Client: Final Result
Loading

Detailed Request Processing

1. Client Invocation

// Client initiates request
const priceHistory = await sdk.priceHistoryApi.getPriceHistory('DOT', '1W');

2. Service Layer Processing

// PriceHistoryApi processes the request
export class PriceHistoryApi extends BaseApi {
  async getPriceHistory(token: string, type: Timeframe) {
    // Parameter validation
    if (!token || !type) {
      throw new ValidationError('Token and type are required');
    }
    
    // Route to BaseApi
    return this.get<HistoryTokenPriceJSON>({
      params: { token, type },
      path: 'price-history'
    });
  }
}

3. Base API Processing

// BaseApi handles the HTTP communication
export class BaseApi {
  async get<T>(options: GetOptions): Promise<T> {
    return this.request<T>({
      method: 'GET',
      ...options
    });
  }
  
  async request<T>(options: RequestOptions): Promise<T> {
    // Build request URL with parameters
    let requestUrl = `${this.config.baseUrl}/${options.path}`;
    
    // Add query parameters
    if (options.params) {
      const queryParams = new URLSearchParams(options.params).toString();
      requestUrl += `?${queryParams}`;
    }
    
    // Execute HTTP request with configuration
    const response = await fetch(requestUrl, {
      method: options.method ?? 'GET',
      headers: {
        'Content-Type': 'application/json',
        ...this.config.headers,
        ...options.headers
      },
      body: options.data ? JSON.stringify(options.data) : undefined
    });
    
    // Process response
    return this.processResponse<T>(response);
  }
}

Configuration Flow

Configuration Propagation

%%{init: {'theme':'dark', 'themeVariables': { 
  'primaryColor': '#ff6b6b', 
  'primaryTextColor': '#fff', 
  'primaryBorderColor': '#ff6b6b', 
  'lineColor': '#ffa726', 
  'sectionBkColor': '#2d3748', 
  'altSectionBkColor': '#1a202c', 
  'gridColor': '#4a5568', 
  'secondaryColor': '#4a5568', 
  'tertiaryColor': '#2d3748'
}}}%%
graph TD
    A[Constants Module] --> B[Default Config]
    B --> C[SDK Instance Config]
    
    D[Client updateConfig] --> C
    E[Environment Variables] --> C
    
    C --> F[API Configuration]
    F --> G[Service Headers]
    
    F --> H[BalanceDetectionApi]
    F --> I[PriceHistoryApi] 
    F --> J[SwapApi]
    F --> K[XcmApi]
    F --> L[CardanoApi]
    
    G --> M[Platform Headers]
    G --> N[Version Headers]
    G --> O[Custom Headers]
Loading

Configuration Update Flow

// Client updates configuration
sdk.updateConfig({
  platform: 'mobile',
  baseUrl: 'https://staging-api.subwallet.app'
});

// SDK processes the update
updateConfig(newConfig: Partial<SDKOption>) {
  // Merge with existing configuration
  this.config = {
    ...this.config,
    ...newConfig
  };
  
  // Regenerate API configuration
  const apiConfig = this.apiConfig;
  
  // Update all services
  this.serviceList.forEach((service) => {
    service.updateConfig(apiConfig);
  });
}

Header Generation Flow

// Dynamic header generation based on configuration
get headers() {
  return {
    'SW-SDK-ChainList-Version': this.config.chainListVersion,
    'SW-SDK-Platform': this.config.platform,
    'SW-SDK-Version': this.config.sdkVersion
  };
}

get apiConfig(): ApiConfig {
  return {
    baseUrl: this.config.baseUrl,
    headers: this.headers
  };
}

Error Flow

Error Handling Pipeline

%%{init: {'theme':'dark', 'themeVariables': { 
  'primaryColor': '#ff6b6b', 
  'primaryTextColor': '#fff', 
  'primaryBorderColor': '#ff6b6b', 
  'lineColor': '#ffa726', 
  'sectionBkColor': '#2d3748', 
  'altSectionBkColor': '#1a202c', 
  'gridColor': '#4a5568', 
  'secondaryColor': '#4a5568', 
  'tertiaryColor': '#2d3748'
}}}%%
graph TD
    A[Error Source] --> B{Error Type}
    
    B --> C[Network Error]
    B --> D[HTTP Error]
    B --> E[API Error]
    B --> F[Validation Error]
    
    C --> G[Connection Issues]
    C --> H[Timeout]
    C --> I[DNS Resolution]
    
    D --> J[4xx Client Errors]
    D --> K[5xx Server Errors]
    
    E --> L[Business Logic Errors]
    E --> M[Service Unavailable]
    
    F --> N[Parameter Validation]
    F --> O[Type Validation]
    
    G --> P[SdkError Creation]
    H --> P
    I --> P
    J --> P
    K --> P
    L --> P
    M --> P
    N --> P
    O --> P
    
    P --> Q[Error Enrichment]
    Q --> R[Client Notification]
Loading

Error Processing Flow

// Error processing in BaseApi
async request<T>(options: RequestOptions): Promise<T> {
  try {
    const response = await fetch(requestUrl, fetchOptions);
    
    // Handle HTTP errors
    if (!response.ok) {
      const errorText = await response.text();
      throw new SdkError(errorText, { 
        code: response.status, 
        type: ErrorType.REQUEST_ERROR 
      });
    }
    
    const apiResponse = await response.json() as SWApiResponse<T>;
    
    // Handle API errors
    if (apiResponse.error) {
      throw new SdkError(apiResponse.error.message || 'Unknown error', { 
        code: apiResponse.error.code 
      });
    }
    
    return apiResponse.data || apiResponse.result;
    
  } catch (error) {
    // Transform and re-throw with context
    throw new Error(`Failed to fetch data: ${(error as Error).message}`);
  }
}

Service Integration

Service Communication Patterns

%%{init: {'theme':'dark', 'themeVariables': { 
  'primaryColor': '#ff6b6b', 
  'primaryTextColor': '#fff', 
  'primaryBorderColor': '#ff6b6b', 
  'lineColor': '#ffa726', 
  'sectionBkColor': '#2d3748', 
  'altSectionBkColor': '#1a202c', 
  'gridColor': '#4a5568', 
  'secondaryColor': '#4a5568', 
  'tertiaryColor': '#2d3748'
}}}%%
graph LR
    subgraph "Client Layer"
        A[Application Code]
    end
    
    subgraph "SDK Layer"
        B[SubWalletServiceSDK]
        C[Service Registry]
    end
    
    subgraph "Service Layer"
        D[BalanceDetectionApi]
        E[PriceHistoryApi]
        F[SwapApi]
        G[XcmApi]
        H[CardanoApi]
    end
    
    subgraph "Infrastructure"
        I[BaseApi]
        J[HTTP Client]
        K[Error Handler]
    end
    
    subgraph "External Services"
        L[SubWallet Backend]
        M[Blockchain Networks]
        N[Price Feeds]
    end
    
    A --> B
    B --> C
    C --> D
    C --> E
    C --> F
    C --> G
    C --> H
    
    D --> I
    E --> I
    F --> I
    G --> I
    H --> I
    
    I --> J
    I --> K
    
    J --> L
    J --> M
    J --> N
Loading

Cross-Service Data Flow

Some operations may involve multiple services working together:

// Example: Portfolio balance with price data
async getPortfolioValue(addresses: string[], chains: string[]) {
  // 1. Get balances from BalanceDetectionApi
  const balances = await sdk.balanceDetectionApi.getBalances({
    addresses,
    chains
  });
  
  // 2. Get current prices for detected tokens
  const pricePromises = balances.tokens.map(token => 
    sdk.priceHistoryApi.getCurrentPrice(token.symbol)
  );
  
  const prices = await Promise.all(pricePromises);
  
  // 3. Calculate total portfolio value
  return balances.tokens.map((token, index) => ({
    ...token,
    price: prices[index],
    value: token.balance * prices[index].current
  }));
}

Data Transformation

Response Processing Pipeline

%%{init: {'theme':'dark', 'themeVariables': { 
  'primaryColor': '#ff6b6b', 
  'primaryTextColor': '#fff', 
  'primaryBorderColor': '#ff6b6b', 
  'lineColor': '#ffa726', 
  'sectionBkColor': '#2d3748', 
  'altSectionBkColor': '#1a202c', 
  'gridColor': '#4a5568', 
  'secondaryColor': '#4a5568', 
  'tertiaryColor': '#2d3748'
}}}%%
graph LR
    A[Raw HTTP Response] --> B[JSON Parsing]
    B --> C[Schema Validation]
    C --> D[Type Transformation]
    D --> E[Data Normalization]
    E --> F[Client Response]
    
    G[Error Detection] --> H[Error Classification]
    H --> I[Error Enrichment]
    I --> J[Error Response]
    
    B --> G
    C --> G
    D --> G
Loading

Data Transformation Examples

Price History Transformation

// Raw API response
interface RawPriceResponse {
  history: Array<{
    time: number;
    value: number;
  }>;
}

// Transformed client response
interface PriceChartPoint {
  time: number;
  value: number;
  formatted?: {
    time: string;
    value: string;
  };
}

// Transformation logic
private transformPriceHistory(raw: RawPriceResponse): HistoryTokenPriceJSON {
  return {
    history: raw.history.map(point => ({
      time: point.time,
      value: point.value,
      formatted: {
        time: new Date(point.time).toISOString(),
        value: `$${point.value.toFixed(2)}`
      }
    }))
  };
}

Balance Data Transformation

// Raw balance response normalization
interface RawBalanceResponse {
  balances: Array<{
    address: string;
    chain: string;
    tokens: Array<{
      symbol: string;
      balance: string;
      decimals: number;
    }>;
  }>;
}

// Client-friendly format
interface NormalizedBalance {
  address: string;
  chain: string;
  tokens: Array<{
    symbol: string;
    balance: bigint;
    displayBalance: string;
    decimals: number;
  }>;
}

Caching and Optimization

Request Optimization Flow

%%{init: {'theme':'dark', 'themeVariables': { 
  'primaryColor': '#ff6b6b', 
  'primaryTextColor': '#fff', 
  'primaryBorderColor': '#ff6b6b', 
  'lineColor': '#ffa726', 
  'sectionBkColor': '#2d3748', 
  'altSectionBkColor': '#1a202c', 
  'gridColor': '#4a5568', 
  'secondaryColor': '#4a5568', 
  'tertiaryColor': '#2d3748'
}}}%%
graph TD
    A[Request Initiated] --> B{Cache Check}
    B -->|Hit| C[Return Cached Data]
    B -->|Miss| D[Execute Request]
    
    D --> E[Network Request]
    E --> F[Response Processing]
    F --> G[Cache Storage]
    G --> H[Return Fresh Data]
    
    I[Cache Invalidation] --> J[TTL Expiry]
    I --> K[Manual Invalidation]
    I --> L[Error-based Invalidation]
    
    J --> B
    K --> B
    L --> B
Loading

Performance Optimizations

Connection Pooling

// HTTP client with connection reuse
class OptimizedHttpClient {
  private connectionPool: Map<string, Connection> = new Map();
  
  async request(url: string, options: RequestOptions) {
    const connection = this.getOrCreateConnection(url);
    return connection.request(options);
  }
  
  private getOrCreateConnection(url: string): Connection {
    const baseUrl = new URL(url).origin;
    
    if (!this.connectionPool.has(baseUrl)) {
      this.connectionPool.set(baseUrl, new Connection(baseUrl));
    }
    
    return this.connectionPool.get(baseUrl)!;
  }
}

Request Batching

// Batch multiple requests to the same endpoint
class BatchingLayer {
  private pendingRequests: Map<string, Promise<any>> = new Map();
  
  async batchRequest<T>(key: string, requestFn: () => Promise<T>): Promise<T> {
    if (this.pendingRequests.has(key)) {
      return this.pendingRequests.get(key) as Promise<T>;
    }
    
    const promise = requestFn();
    this.pendingRequests.set(key, promise);
    
    // Clean up after request completes
    promise.finally(() => {
      this.pendingRequests.delete(key);
    });
    
    return promise;
  }
}

Response Caching

// Service-level caching implementation
class CachingService {
  private cache: Map<string, CacheEntry> = new Map();
  
  async cachedRequest<T>(
    key: string, 
    requestFn: () => Promise<T>,
    ttl: number = 60000
  ): Promise<T> {
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < ttl) {
      return cached.data as T;
    }
    
    const fresh = await requestFn();
    this.cache.set(key, {
      data: fresh,
      timestamp: Date.now()
    });
    
    return fresh;
  }
}

🔗 Related Documentation

⚠️ **GitHub.com Fallback** ⚠️