Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Koniverse/SubWallet-Extension/llms.txt

Use this file to discover all available pages before exploring further.

What are Cron Jobs?

Cron jobs in SubWallet Extension are scheduled background tasks that run periodically in the background environment. They handle time-sensitive operations like price updates, NFT refreshes, staking reward calculations, and other recurring data synchronization tasks. Cron jobs provide:
  • Automatic data refreshes without user interaction
  • Background processing that doesn’t block the UI
  • Resource optimization through scheduled execution
  • Event-driven updates based on application state changes

Cron Architecture

The cron system is managed by the KoniCron class located in packages/extension-base/src/koni/background/cron.ts.

KoniCron Class Structure

export class KoniCron {
  subscriptions: KoniSubscription;
  public status: 'pending' | 'running' | 'stopped' = 'pending';
  private serviceSubscription: Subscription | undefined;
  public dbService: DatabaseService;
  private state: KoniState;
  private cronMap: Record<string, any> = {};
  private subjectMap: Record<string, Subject<any>> = {};
  private eventHandler?: ((events: EventItem<EventType>[], eventTypes: EventType[]) => void);

  constructor (state: KoniState, subscriptions: KoniSubscription, dbService: DatabaseService) {
    this.subscriptions = subscriptions;
    this.dbService = dbService;
    this.state = state;
  }
}

Cron Job Methods

Adding a Cron Job

addCron = (name: string, callback: (param?: any) => void, interval: number, runFirst = true) => {
  if (runFirst) {
    callback();
  }

  this.cronMap[name] = setInterval(callback, interval);
};
Parameters:
  • name: Unique identifier for the cron job
  • callback: Function to execute on each interval
  • interval: Time in milliseconds between executions
  • runFirst: Whether to run immediately before scheduling

Adding a Subscribable Cron Job

addSubscribeCron = <T>(name: string, callback: (subject: Subject<T>) => void, interval: number) => {
  const sb = new Subject<T>();

  callback(sb);
  this.subjectMap[name] = sb;
  this.cronMap[name] = setInterval(callback, interval);
};
This creates a cron job that emits updates through an RxJS Subject, allowing components to subscribe to changes.

Removing a Cron Job

removeCron = (name: string) => {
  const interval = this.cronMap[name] as number;

  if (interval) {
    clearInterval(interval);
    delete this.cronMap[name];
  }
};

Starting the Cron System

start = async () => {
  if (this.status === 'running') {
    return;
  }

  await Promise.all([this.state.eventService.waitKeyringReady, this.state.eventService.waitAssetReady]);

  const currentAccountInfo = this.state.keyringService.context.currentAccount;

  // Setup event handlers for dynamic cron management
  this.eventHandler = (events, eventTypes) => {
    // Handle events and restart relevant crons
  };

  this.state.eventService.onLazy(this.eventHandler);

  // Initialize cron jobs
  this.addCron('fetchPoolInfo', this.fetchPoolInfo, CRON_REFRESH_CHAIN_STAKING_METADATA);
  this.addCron('fetchMktCampaignData', this.fetchMktCampaignData, CRON_REFRESH_MKT_CAMPAIGN_INTERVAL);

  if (currentAccountInfo?.proxyId) {
    this.resetNft(currentAccountInfo.proxyId);
    this.addCron('refreshNft', this.refreshNft(/* params */), CRON_REFRESH_NFT_INTERVAL);
    this.addCron('detectNft', this.detectEvmCollectionNft(currentAccountInfo.proxyId), CRON_NFT_DETECT_INTERVAL);
    this.addCron('syncMantaPay', this.syncMantaPay, CRON_SYNC_MANTA_PAY);
  }

  this.status = 'running';
};

Stopping the Cron System

stop = async () => {
  if (this.status === 'stopped') {
    return;
  }

  // Unsubscribe events
  if (this.eventHandler) {
    this.state.eventService.offLazy(this.eventHandler);
    this.eventHandler = undefined;
  }

  if (this.serviceSubscription) {
    this.serviceSubscription.unsubscribe();
    this.serviceSubscription = undefined;
  }

  this.removeAllCrons();
  this.stopPoolInfo();

  this.status = 'stopped';

  return Promise.resolve();
};

Common Cron Jobs in SubWallet

1. NFT Refresh Cron

refreshNft = (address: string, apiMap: ApiMap, smartContractNfts: _ChainAsset[], chainInfoMap: Record<string, _ChainInfo>) => {
  return () => {
    this.subscriptions.subscribeNft(address, apiMap.substrate, apiMap.evm, smartContractNfts, chainInfoMap);
  };
};

// Add the cron job
this.addCron(
  'refreshNft',
  this.refreshNft(address, apiMap, nfts, chainMap),
  CRON_REFRESH_NFT_INTERVAL // 30 seconds
);

2. Price Update Cron

Implemented in PriceService:
public refreshPriceData (priceIds?: Set<string>) {
  clearTimeout(this.refreshTimeout);
  this.priceIds = priceIds || this.getPriceIds();

  // Update token prices
  this.getTokenPrice(this.priceIds, DEFAULT_CURRENCY)
    .then(() => {
      this.refreshPriceMapByAction();
    })
    .catch((e) => {
      console.error(e);
    });

  this.refreshTimeout = setTimeout(
    this.refreshPriceData.bind(this),
    CRON_REFRESH_PRICE_INTERVAL // 60 seconds
  );
}

3. Staking Metadata Cron

fetchPoolInfo = () => {
  this.state.earningService.runSubscribePoolsInfo().catch(console.error);
};

this.addCron(
  'fetchPoolInfo',
  this.fetchPoolInfo,
  CRON_REFRESH_CHAIN_STAKING_METADATA // 10 minutes
);

4. MantaPay Sync Cron

syncMantaPay = () => {
  if (this.state.isMantaPayEnabled) {
    this.state.syncMantaPay().catch(console.warn);
  }
};

this.addCron(
  'syncMantaPay',
  this.syncMantaPay,
  CRON_SYNC_MANTA_PAY // 10 seconds
);

5. Marketing Campaign Cron

fetchMktCampaignData = () => {
  this.state.mktCampaignService.fetchMktCampaignData();
};

this.addCron(
  'fetchMktCampaignData',
  this.fetchMktCampaignData,
  CRON_REFRESH_MKT_CAMPAIGN_INTERVAL
);

Creating a New Cron Job

Step 1: Define the Cron Interval Constant

// constants/index.ts
export const CRON_REFRESH_CUSTOM_DATA = 5 * 60 * 1000; // 5 minutes

Step 2: Create the Cron Job Function

In KoniCron class:
fetchCustomData = () => {
  this.state.customService.refreshData()
    .then(() => {
      console.log('Custom data refreshed');
    })
    .catch((error) => {
      console.error('Failed to refresh custom data:', error);
    });
};

Step 3: Register the Cron Job in Start Method

start = async () => {
  // ... existing code ...

  // Add your cron job
  this.addCron(
    'fetchCustomData',
    this.fetchCustomData,
    CRON_REFRESH_CUSTOM_DATA,
    true // Run immediately on start
  );

  this.status = 'running';
};

Step 4: Handle Dynamic Restart (Optional)

If your cron job should restart based on events:
this.eventHandler = (events, eventTypes) => {
  const needReload = eventTypes.includes('custom.dataChanged');

  if (needReload) {
    this.removeCron('fetchCustomData');
    this.addCron(
      'fetchCustomData',
      this.fetchCustomData,
      CRON_REFRESH_CUSTOM_DATA
    );
  }
};

Event-Driven Cron Management

KoniCron uses an event-driven approach to dynamically manage cron jobs:
this.eventHandler = (events, eventTypes) => {
  const serviceInfo = this.state.getServiceInfo();
  const commonReload = eventTypes.some((eventType) => commonReloadEvents.includes(eventType));
  const chainUpdated = eventTypes.includes('chain.updateState');
  const reloadMantaPay = eventTypes.includes('mantaPay.submitTransaction') || eventTypes.includes('mantaPay.enable');

  if (!commonReload && !chainUpdated && !reloadMantaPay) {
    return;
  }

  const address = serviceInfo.currentAccountInfo?.proxyId;

  if (!address) {
    return;
  }

  // Restart relevant cron jobs based on events
  if (commonReload) {
    this.removeCron('refreshNft');
    this.removeCron('detectNft');
    this.resetNft(address);
    
    if (this.checkNetworkAvailable(serviceInfo)) {
      this.addCron('refreshNft', this.refreshNft(/* params */), CRON_REFRESH_NFT_INTERVAL);
      this.addCron('detectNft', this.detectEvmCollectionNft(address), CRON_NFT_DETECT_INTERVAL);
    }
  }

  if (reloadMantaPay) {
    this.removeCron('syncMantaPay');
    this.addCron('syncMantaPay', this.syncMantaPay, CRON_SYNC_MANTA_PAY);
  }
};

Service-Based Cron Jobs

Services can implement their own cron functionality through the CronServiceInterface:
export class InappNotificationService implements CronServiceInterface {
  private refeshAvailBridgeClaimTimeOut?: NodeJS.Timeout;

  async startCron (): Promise<void> {
    this.cronCreateBridgeClaimNotification();
  }

  async stopCron (): Promise<void> {
    clearTimeout(this.refeshAvailBridgeClaimTimeOut);
  }

  cronCreateBridgeClaimNotification () {
    this.checkAndCreateBridgeClaimNotifications()
      .catch(console.error);

    this.refeshAvailBridgeClaimTimeOut = setTimeout(
      this.cronCreateBridgeClaimNotification.bind(this),
      CRON_LISTEN_AVAIL_BRIDGE_CLAIM
    );
  }
}

Best Practices

1. Choose Appropriate Intervals

// Good: Different intervals for different data types
const CRON_REFRESH_PRICE_INTERVAL = 60 * 1000;        // 1 minute - frequently changing
const CRON_REFRESH_NFT_INTERVAL = 30 * 1000;          // 30 seconds - user-visible
const CRON_REFRESH_CHAIN_STAKING_METADATA = 10 * 60 * 1000; // 10 minutes - slow-changing

// Bad: Everything updates too frequently
const CRON_REFRESH_ALL = 5 * 1000; // 5 seconds - wastes resources

2. Error Handling

// Good: Catch errors, log, and continue
fetchData = () => {
  this.dataService.refresh()
    .catch((error) => {
      console.error('Cron job failed, will retry next interval:', error);
      // Don't throw - let the cron continue
    });
};

// Bad: Unhandled errors stop the cron
fetchData = () => {
  this.dataService.refresh(); // No error handling
};

3. Conditional Execution

// Good: Check preconditions
syncMantaPay = () => {
  if (this.state.isMantaPayEnabled) {
    this.state.syncMantaPay().catch(console.warn);
  }
};

// Bad: Wasteful execution
syncMantaPay = () => {
  this.state.syncMantaPay().catch(console.warn); // Runs even when disabled
};

4. Resource Cleanup

// Good: Store interval IDs and clean them up
private cronMap: Record<string, any> = {};

removeAllCrons = () => {
  Object.entries(this.cronMap).forEach(([key, interval]) => {
    clearInterval(interval as number);
    delete this.cronMap[key];
  });
};

// Bad: No cleanup
addCron = (callback, interval) => {
  setInterval(callback, interval); // Lost reference, can't clean up
};

5. Run Immediately When Needed

// Good: Run first for immediate feedback
this.addCron(
  'refreshNft',
  this.refreshNft,
  CRON_REFRESH_NFT_INTERVAL,
  true // Run immediately
);

// Sometimes Good: Don't run first to stagger load
this.addCron(
  'backgroundSync',
  this.backgroundSync,
  CRON_BACKGROUND_SYNC,
  false // Wait for first interval
);

6. Wait for Dependencies

// Good: Wait for required services
start = async () => {
  await Promise.all([
    this.state.eventService.waitKeyringReady,
    this.state.eventService.waitAssetReady
  ]);

  // Now safe to start cron jobs that depend on keyring and assets
  this.addCron('refreshBalances', this.refreshBalances, INTERVAL);
};

// Bad: Start before dependencies ready
start = async () => {
  this.addCron('refreshBalances', this.refreshBalances, INTERVAL); // May fail
};

Debugging Cron Jobs

View Active Crons

// In console
console.log(Object.keys(koniState.cron.cronMap));
// Output: ['refreshNft', 'detectNft', 'syncMantaPay', 'fetchPoolInfo']

Monitor Cron Execution

fetchData = () => {
  const startTime = Date.now();
  console.log('[Cron] Starting fetchData');
  
  this.dataService.refresh()
    .then(() => {
      console.log(`[Cron] fetchData completed in ${Date.now() - startTime}ms`);
    })
    .catch((error) => {
      console.error('[Cron] fetchData failed:', error);
    });
};

Test Cron Jobs Manually

// In console or test environment
await koniState.cron.fetchPoolInfo();
await koniState.cron.syncMantaPay();

Cron Job Lifecycle

Common Cron Intervals

// From extension-base/src/constants/
export const CRON_REFRESH_PRICE_INTERVAL = 60 * 1000;                    // 1 minute
export const CRON_REFRESH_NFT_INTERVAL = 30 * 1000;                     // 30 seconds
export const CRON_NFT_DETECT_INTERVAL = 3 * 60 * 1000;                  // 3 minutes
export const CRON_REFRESH_CHAIN_STAKING_METADATA = 10 * 60 * 1000;      // 10 minutes
export const CRON_SYNC_MANTA_PAY = 10 * 1000;                           // 10 seconds
export const CRON_REFRESH_MKT_CAMPAIGN_INTERVAL = 24 * 60 * 60 * 1000;  // 24 hours
export const CRON_LISTEN_AVAIL_BRIDGE_CLAIM = 5 * 60 * 1000;            // 5 minutes
  • Services - Implement cron logic in services
  • APIs - Cron jobs call APIs to fetch data
  • Stores - Cache cron job results in stores

Build docs developers (and LLMs) love