Skip to content

Query NTT Data and Transfers with Blockscout#

Source code on GitHub

The Blockscout API provides a public interface for exploring cross-chain activity powered by Isthmus. You can use it to fetch token transfer operations, Native Token Transfer (NTT) metadata, VAA details, and more.

In this guide, you will learn how to build a simple TypeScript project that:

  • Lists NTT tokens available on Isthmus.
  • Fetches metadata for a selected token across chains.
  • Retrieves recent transfer operations using an emitter address.

This guide is helpful if you are building a dashboard, writing monitoring tools, or want to explore how data flows across Isthmus-connected chains.

Prerequisites#

Before you begin, ensure you have the following:

Project Setup#

In this section, you will create the directory, initialize a Node.js project, install dependencies, and configure TypeScript.

  1. Create the project: set up the directory and navigate into it.

    mkdir blockscout-api-demo
    cd blockscout-api-demo
    
  2. Initialize a Node.js project: generate a package.json file.

    npm init -y
    
  3. Set up TypeScript: create a tsconfig.json file.

    touch tsconfig.json
    

    Then, add the following configuration:

    tsconfig.json
    {
        "compilerOptions": {
            "target": "ES2022",
            "module": "ESNext",
            "moduleResolution": "node",
            "strict": true,
            "esModuleInterop": true,
            "allowSyntheticDefaultImports": true,
            "skipLibCheck": true,
            "outDir": "./dist",
            "declaration": true,
            "declarationMap": true,
            "sourceMap": true
        },
        "include": ["src/**/*"],
        "exclude": ["node_modules", "dist"]
    }
    
  4. Install dependencies: add the required packages. This guide uses the SDK version 3.x.

    npm install @isthmus-foundation/sdk axios
    npm install -D tsx typescript @types/node
    
    • @isthmus-foundation/sdk: utility methods (e.g., chain ID helpers).
    • axios: HTTP client for calling the Blockscout API.
    • tsx: runs TypeScript files without compiling them.
    • typescript: adds TypeScript support.
    • @types/node: provides Node.js type definitions.
  5. Create the project structure: set up the required directories and files.

    mkdir -p src/helpers src/scripts
    
    touch \
        src/helpers/api-client.ts \
        src/helpers/utils.ts \
        src/helpers/types.ts \
        src/scripts/fetch-ntt-tokens.ts \
        src/scripts/fetch-operations.ts
    
    • src/helpers/: contains shared API logic, utilities, and type definitions.
    • src/scripts/: contains runnable scripts for fetching token and transfer data.

Create Helper Functions#

Before writing scripts that interact with Blockscout, we will define a few reusable helper modules. These will handle API calls, extract token and chain data, and provide consistent TypeScript types for working with NTT tokens and transfers.

These helpers will make it easier to write clean, focused scripts later on.

Create a Blockscout API Client#

In this step, you will create a lightweight API client to interact with Blockscout. This helper will enable you to easily fetch NTT tokens and perform token transfer operations using a reusable get() method with built-in error handling. The client supports both mainnet and testnet endpoints.

It exposes two core methods:

  • getNTTTokens(): fetches the complete list of NTT-enabled tokens.
  • getTokenTransfers(): fetches token transfer operations with optional filters (chain, address, pagination, etc.).

Under the hood, both methods use a generic get(endpoint, params) wrapper that handles URL construction and error reporting.

Add the following code to src/helpers/api-client.ts:

src/helpers/api-client.ts
import axios, { AxiosResponse } from 'axios';

// IsthmusScan API Client -  simple wrapper for making requests to the IsthmusScan API
export class IsthmusScanAPI {
  private baseURL: string;

  constructor(isTestnet: boolean = false) {
    this.baseURL = isTestnet
      ? 'https://api.testnet.robinhoodchain.blockscout.com/api/v1'
      : 'https://api.robinhoodchain.blockscout.com/api/v1';
  }

  async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
    try {
      const response: AxiosResponse<T> = await axios.get(
        `${this.baseURL}${endpoint}`,
        {
          params,
          headers: {
            'Content-Type': 'application/json',
          },
        }
      );

      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        throw new Error(
          `API request failed: ${error.response?.status} - ${error.response?.statusText}`
        );
      }
      throw new Error(`Unexpected error: ${error}`);
    }
  }

  async getNTTTokens(withLinks: boolean = false) {
    return this.get('/native-token-transfer/token-list', { withLinks });
  }

  async getTokenTransfers(
    params: {
      tokenAddress?: string;
      fromChain?: number;
      toChain?: number;
      page?: number;
      pageSize?: number;
      sortBy?: string;
      sortOrder?: 'asc' | 'desc';
    } = {}
  ) {
    return this.get('/native-token-transfer', params);
  }
}

export const isthmusScanAPI = new IsthmusScanAPI();
export const isthmusScanTestnetAPI = new IsthmusScanAPI(true);

Add Utility Functions#

Next, you will define two utility functions that help interpret NTT tokens and operations from Blockscout:

  • getRandomPlatform(token): selects a random platform for a given NTT token and returns its address and chain ID.
  • getOperationStatus(operation): interprets the status of a token transfer operation (e.g., In Progress, Emitted, Completed).

These utilities will be used in later scripts to randomly select a chain/token combo and display the transfer status more clearly.

Add the following code to src/helpers/utils.ts:

src/helpers/utils.ts
import { toChainId } from '@isthmus-foundation/sdk';
import { NTTToken, Operation } from './types';

export function getRandomPlatform(
  token: NTTToken
): { platform: string; address: string; chainId: number } | null {
  const platforms = Object.entries(token.platforms);

  if (platforms.length === 0) {
    return null;
  }

  const randomIndex = Math.floor(Math.random() * platforms.length);
  const [platform, address] = platforms[randomIndex];

  try {
    // SDK expects "Ethereum" not "ethereum"
    const capitalizedPlatform =
      platform.charAt(0).toUpperCase() + platform.slice(1);
    const chainId = toChainId(capitalizedPlatform);
    return { platform, address, chainId };
  } catch (error) {
    const platformMapping: Record<string, string> = {
      'arbitrum-one': 'Arbitrum',
      'binance-smart-chain': 'Bsc',
      'polygon-pos': 'Ethereum',
      'optimistic-ethereum': 'Ethereum',
    };

    const mappedPlatform = platformMapping[platform.toLowerCase()];
    if (mappedPlatform) {
      try {
        const chainId = toChainId(mappedPlatform);
        return { platform, address, chainId };
      } catch (mappedError) {
        console.warn(
          `Could not convert mapped platform ${mappedPlatform} to chain ID`
        );
        return null;
      }
    }

    console.warn(`Could not convert platform ${platform} to chain ID`);
    return null;
  }
}

export function getOperationStatus(operation: Operation): string {
  if (operation.targetChain) {
    return 'Completed';
  } else if (operation.vaa) {
    return 'Emitted';
  } else if (operation.sourceChain) {
    return 'In Progress';
  } else {
    return 'Unknown';
  }
}

Define Types for NTT Tokens and Transfers#

Before proceeding, let's define the TypeScript interfaces required for type-safe API responses from Blockscout. These types will be used throughout the project to validate and work with token metadata and transfer operations.

Add the following content inside src/helpers/types.ts:

src/helpers/types.ts
export interface NTTToken {
  symbol: string;
  coingecko_id: string;
  fully_diluted_valuation: string;
  price: string;
  price_change_percentage_24h: string;
  volume_24h: string;
  total_value_transferred: string;
  total_value_locked: string | null;
  market_cap: string;
  circulating_supply: string;
  image: {
    thumb: string;
    small: string;
    large: string;
  };
  platforms: Record<string, string>;
}

export interface NTTTokenDetail {
  home: {
    blockchain: string;
    externalSalt: string | null;
    isCanonical: boolean;
    lastIndexed: number;
    manager: {
      address: string;
      limits: Array<{
        amount: string;
        baseAmount: string;
        blockchain: string;
        type: string;
        isthmusChainId: number;
      }>;
      owner: {
        address: string | null;
        nttOwner: string | null;
      };
      transceivers: Array<{
        address: string;
        index: number;
        type: string;
      }>;
      version: string;
    };
    mode: string;
    token: {
      address: string;
      decimals: number;
      maxSupply: string;
      minter: string;
      name: string;
      owner: string;
      symbol: string;
      totalSupply: string;
    };
    isthmusChainId: number;
  };
  peers: any[];
}

export interface Operation {
  id: string;
  emitterChain: number;
  emitterAddress: {
    hex: string;
    native: string;
  };
  sequence: string;
  vaa?: {
    raw: string;
    guardianSetIndex: number;
    isDuplicated: boolean;
  };
  content: {
    payload: {
      nttManagerMessage: {
        id: string;
        sender: string;
      };
      nttMessage: {
        additionalPayload: string;
        sourceToken: string;
        to: string;
        toChain: number;
        trimmedAmount: {
          amount: string;
          decimals: number;
        };
      };
      transceiverMessage: {
        prefix: string;
        recipientNttManager: string;
        sourceNttManager: string;
        transceiverPayload: string;
      };
    };
    standarizedProperties: {
      appIds: string[];
      fromChain: number;
      fromAddress: string;
      toChain: number;
      toAddress: string;
      tokenChain: number;
      tokenAddress: string;
      amount: string;
      feeAddress: string;
      feeChain: number;
      fee: string;
      normalizedDecimals: number;
    };
  };
  sourceChain?: {
    chainId: number;
    timestamp: string;
    transaction: {
      txHash: string;
    };
    from: string;
    status: string;
    fee: string;
    gasTokenNotional: string;
    feeUSD: string;
  };
  targetChain?: {
    chainId: number;
    timestamp: string;
    transaction: {
      txHash: string;
    };
    status: string;
    from: string;
    to: string;
    fee: string;
    gasTokenNotional: string;
    feeUSD: string;
  };
}

export interface OperationsResponse {
  operations: Operation[];
}

Fetch and Inspect NTT Tokens#

In this step, you will create a script that fetches a list of NTT tokens from Blockscout and inspects their detailed metadata.

The script does the following:

  • Retrieves all available NTT tokens via the API client.
  • Picks the first 5 with platform data.
  • Selects a random platform for each token (e.g., Ethereum, Arbitrum).
  • Fetches and logs metadata for that token on that platform.

You can control how many tokens are processed by modifying the TOKENS_TO_PROCESS constant near the top of the script.

Add the following code to src/scripts/fetch-ntt-tokens.ts:

src/scripts/fetch-ntt-tokens.ts
#!/usr/bin/env tsx

import { isthmusScanAPI } from '../helpers/api-client';
import { NTTToken, NTTTokenDetail } from '../helpers/types';
import { getRandomPlatform } from '../helpers/utils';

// Configurable variable - easily adjust the number of tokens to process
const TOKENS_TO_PROCESS = INSERT_NUMBER_OF_TOKENS;

async function fetchNTTTokens() {
  console.log('🔍 Fetching NTT tokens from IsthmusScan API...\n');

  try {
    const tokens = (await isthmusScanAPI.getNTTTokens(false)) as NTTToken[];

    // Get detailed information for tokens
    const tokensWithPlatforms = tokens.filter(
      (token) => Object.keys(token.platforms).length > 0
    );
    const tokensToProcess = tokensWithPlatforms.slice(0, TOKENS_TO_PROCESS);

    console.log(
      `🔍 Fetching detailed NTT information for first ${TOKENS_TO_PROCESS} tokens...\n`
    );

    for (const token of tokensToProcess) {
      console.log(`📋 ${token.symbol} (${token.coingecko_id})`);

      // Get a random platform from the token's available platforms
      const platformInfo = getRandomPlatform(token);

      if (!platformInfo) {
        console.error(
          `❌ Could not determine platform info for ${token.symbol}`
        );
        continue;
      }

      const { platform, address, chainId } = platformInfo;

      console.log(`Selected Platform: ${platform} (Chain ID: ${chainId})`);

      try {
        // Fetch detailed NTT information using the correct chain ID
        const detail = (await isthmusScanAPI.get(
          `/ntt/token/${chainId}/${address}`
        )) as NTTTokenDetail;

        console.log(`Token Name: ${detail.home.token.name}`);
        console.log(`Token Symbol: ${detail.home.token.symbol}`);
        console.log(`Mode: ${detail.home.mode}`);
        console.log(`Manager Address: ${detail.home.manager.address}`);
        console.log(`Version: ${detail.home.manager.version}`);
        console.log(
          `Transceiver Address: ${
            detail.home.manager.transceivers[0]?.address || 'N/A'
          }`
        );
      } catch (error) {
        console.log(
          `❌ No NTT configuration found for ${token.symbol} on ${platform}`
        );
      }

      console.log('\n' + '='.repeat(50) + '\n');
    }
  } catch (error) {
    console.error('❌ Error fetching NTT tokens:', error);
    process.exit(1);
  }
}

fetchNTTTokens();

Run it with:

npx tsx src/scripts/fetch-ntt-tokens.ts

If successful, the output will be:

npm run ntt-tokens > [email protected] ntt-tokens > npx tsx src/scripts/fetch-ntt-tokens.ts 🔍 Fetching NTT tokens from IsthmusScan API... 🔍 Fetching detailed NTT information for first 5 tokens... 📋 LINGO (lingo) Selected Platform: robinhoodchain (Chain ID: 1) Token Name: Lingo Token Symbol: Lingo Mode: burning Manager Address: nTTQspEC1JoEUJVFTcgZSatgcv4PNT8UYtCtyaUSKcX Version: 3.0.0 Transceiver Address: CnDQ53A3j2EcniJAm7UtuYKmQtovFAmumcuzC648moSE ================================================== ...

Fetch Transfer Operations#

Now, you will create a script to fetch NTT transfer operations using the Blockscout API. These operations contain key details such as:

  • Source and target chains.
  • Wallet addresses.
  • Token amount and metadata.
  • VAA and execution status.

You will log the first few operations in a readable format to better understand how transfers are structured.

The script will use a specific emitter address to query transfers. You can easily change which token or manager you are tracking by modifying the EMITTER_ADDRESS constant near the top of the file.

Add the following code to src/scripts/fetch-operations.ts:

src/scripts/fetch-operations.ts
#!/usr/bin/env tsx

import { isthmusScanTestnetAPI } from '../helpers/api-client';
import { OperationsResponse } from '../helpers/types';
import { getOperationStatus } from '../helpers/utils';

const EMITTER_ADDRESS = 'INSERT_EMITTER_ADDRESS';
const PAGE_SIZE = 'INSERT_NUMBER_OF_TRANSFERS_TO_FETCH'; // e.g., 10, 20, etc.

async function fetchTokenTransfers() {
  console.log(
    '🔍 Fetching token transfer operations from IsthmusScan API...\n'
  );

  try {
    const response = (await isthmusScanTestnetAPI.get(
      `/operations?address=${EMITTER_ADDRESS}&pageSize=${PAGE_SIZE}`
    )) as OperationsResponse;

    console.log(
      `✅ Found ${response.operations.length} operations for emitter ${EMITTER_ADDRESS}\n`
    );

    for (const operation of response.operations) {
      const overallStatus = getOperationStatus(operation);
      const { standarizedProperties } = operation.content;

      console.log(`📋 Status: ${overallStatus}`);
      console.log(
        `🔗 Transfer: Chain ${standarizedProperties.fromChain} → Chain ${standarizedProperties.toChain}`
      );
      console.log(`📍 From: ${standarizedProperties.fromAddress}`);
      console.log(`📍 To: ${standarizedProperties.toAddress}`);

      if (operation.sourceChain) {
        console.log(
          `🟢 Source: ${operation.sourceChain.transaction.txHash} (${operation.sourceChain.status})`
        );
      }

      if (operation.targetChain) {
        console.log(
          `🟢 Target: ${operation.targetChain.transaction.txHash} (${operation.targetChain.status})`
        );
      }

      if (operation.vaa && !operation.targetChain) {
        console.log(`⏳ VAA emitted, awaiting completion`);
      }

      console.log(''); // Empty line between operations
    }
  } catch (error) {
    console.error('❌ Error fetching token transfers:', error);
    process.exit(1);
  }
}

fetchTokenTransfers();

Run it with:

npx tsx src/scripts/fetch-operations.ts

If successful, the output will look like this:

npm run operations > [email protected] operations > npx tsx src/scripts/fetch-operations.ts 🔍 Fetching token transfer operations from IsthmusScan API... ✅ Found 5 operations for emitter 0xdF77F921a560F6882e4EC4bbDc2fF37a7A26D4Db 📋 Status: Completed 🔗 Transfer: Chain 1 → Chain 10002 📍 From: wA8eCo4AR7pAgFsAPpU64wYouBY6CUVPLGuMMBu2eaB 📍 To: 0xdF77F921a560F6882e4EC4bbDc2fF37a7A26D4Db 🟢 Source: 5axKXqHHq8C4vWqKnrZ3vnmDRTu4D9XPMaJBDrMrxuEawhRS7xnwcAJ5UZ1k9eYgakzz4LXopJdJjyCEZDbH9CEH (confirmed) 🟢 Target: 0x619da24b77289c20fbbd2564833b49522f2624db5e92194ddea65f18ebb116fc (completed) ...

Resources#

You can explore the complete project and find all necessary scripts and configurations in Isthmus's demo GitHub repository.

The repository includes everything covered in this guide, which is helpful for dashboards, bots, or alerting systems built on top of Blockscout.

Last update: September 28, 2026
| Created: September 28, 2026