UNPKG

@ledgerhq/coin-tezos

Version:
1,359 lines 52.2 kB
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0

import type { TezosCoinConfig } from '../config'
// Import fetchBlockTransactions / fetchBlockTokenTransfers / fetchBlockDelegations through the index re-export
// so that network/index.ts line 4 is exercised and its coverage is tracked.
import type {
  APIAccount,
  APIBlock,
  APIDelegationType,
  APIOperation,
  APIStakingType,
  APITokenBalance,
  APITokenTransfer,
  APITransactionType,
} from './types'
// tzkt.ts imports `network` from "@ledgerhq/live-network" (the package root, not the /network
// subpath). We mock the same path so Jest's module registry intercepts those calls.
import network from '@ledgerhq/live-network'
import { log } from '@ledgerhq/logs'
import { mockConfig } from '../test/config'
import { createTzktApi } from './tzkt'
import {
  fetchAllTransactions,
  fetchBlockDelegations,
  fetchBlockStaking,
  fetchBlockTokenTransfers,
  fetchBlockTransactions,
} from '.'

jest.mock('@ledgerhq/live-network')
jest.mock('@ledgerhq/logs')
const mockedNetwork = jest.mocked(network)
const mockedLog = jest.mocked(log)

// The tzkt module is NOT mocked here. `createTzktApi(config)` returns a plain object whose methods
// close over `config.explorer.url` and delegate to the mocked `network`. Each method also calls its
// sibling methods through this same object (closure, not `this`), so spying on the instance's
// methods (see `spyOnApi`) intercepts intra-object calls such as `getAccountTokenTransfers` →
// `getOperationsTransactions`.
//
// The paginated helpers (`fetchAllTransactions`, `fetchBlock*`) are top-level functions that build
// their OWN fresh `createTzktApi(config)` instance internally, so spying on `api` cannot intercept
// them. Those tests therefore drive the mocked `network` directly.
const config = mockConfig as TezosCoinConfig
const api = createTzktApi(config)

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Wraps raw data into the shape that @ledgerhq/live-network resolves with. */
function networkResponse<T>(data: T) {
  return Promise.resolve({
    data,
    status: 200,
    headers: {} as any,
    statusText: '',
    config: { headers: {} as any },
  })
}

function makeApiBlock(level = 100): APIBlock {
  return { level, hash: `BLk${level}`, timestamp: '2024-01-01T00:00:00Z' } as APIBlock
}

function makeTxItem(id: number): APITransactionType {
  return { id } as APITransactionType
}

function makeTokenItem(id: number): APITokenTransfer {
  return { id } as APITokenTransfer
}

function makeDelegationItem(id: number): APIDelegationType {
  return { id } as APIDelegationType
}

function makeStakingItem(id: number): APIStakingType {
  return { id } as APIStakingType
}

function makeOperation(id: number): APIOperation {
  return {
    id,
    type: 'transaction',
    timestamp: '2024-01-01T00:00:00Z',
    level: 1,
    block: 'blk',
    amount: 0,
    counter: 0,
    initiator: null,
    sender: null,
    target: null,
  } as APIOperation
}

function makeTokenWithTxId(id: number, transactionId?: number): APITokenTransfer {
  return { ...makeTokenItem(id), transactionId } as APITokenTransfer
}

function makeTokenWithOrigId(id: number, originationId?: number): APITokenTransfer {
  return { ...makeTokenItem(id), originationId } as APITokenTransfer
}

const localSpies: jest.SpyInstance[] = []

function spyOnApi<K extends keyof typeof api>(method: K) {
  const spy = jest.spyOn(api, method)
  localSpies.push(spy)
  return spy
}

// ---------------------------------------------------------------------------
// Suite
// ---------------------------------------------------------------------------

describe('tzkt network API', () => {
  afterEach(() => {
    localSpies.splice(0).forEach((spy) => spy.mockRestore())
    jest.clearAllMocks()
  })

  // -------------------------------------------------------------------------
  // api.getBlockCount
  // -------------------------------------------------------------------------

  describe('api.getBlockCount', () => {
    it('calls the blocks count endpoint and returns the number', async () => {
      mockedNetwork.mockReturnValue(networkResponse(12_345) as ReturnType<typeof network>)

      const result = await api.getBlockCount()

      expect(result).toBe(12_345)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringMatching(/\/v1\/blocks\/count$/),
        })
      )
    })
  })

  // -------------------------------------------------------------------------
  // api.getLastBlock
  // -------------------------------------------------------------------------

  describe('api.getLastBlock', () => {
    it('returns hash, level and date from the first block in the sorted list', async () => {
      const blocks = [makeApiBlock(9_001)]
      mockedNetwork.mockReturnValue(networkResponse(blocks) as ReturnType<typeof network>)

      const result = await api.getLastBlock()

      expect(result).toEqual({
        hash: blocks[0].hash,
        level: 9_001,
        date: new Date(blocks[0].timestamp),
      })
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/blocks'),
          params: { 'sort.desc': 'level' },
        })
      )
    })
  })

  // -------------------------------------------------------------------------
  // api.getAccountByAddress
  // -------------------------------------------------------------------------

  describe('api.getAccountByAddress', () => {
    it('calls the account endpoint and returns the account payload', async () => {
      const account = { type: 'empty', address: 'tz1abc', counter: 0 } as APIAccount
      mockedNetwork.mockReturnValue(networkResponse(account) as ReturnType<typeof network>)

      const result = await api.getAccountByAddress('tz1abc')

      expect(result).toEqual(account)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/accounts/tz1abc'),
        })
      )
    })

    it('coalesces concurrent calls for the same address into a single request', async () => {
      const account = { type: 'user', address: 'tz1dup', revealed: true } as APIAccount
      mockedNetwork.mockReturnValue(networkResponse(account) as ReturnType<typeof network>)

      const [first, second] = await Promise.all([
        api.getAccountByAddress('tz1dup'),
        api.getAccountByAddress('tz1dup'),
      ])

      expect(first).toEqual(account)
      expect(second).toBe(first)
      expect(mockedNetwork).toHaveBeenCalledTimes(1)
    })
  })

  // -------------------------------------------------------------------------
  // api.getAccountOperations
  // -------------------------------------------------------------------------

  describe('api.getAccountOperations', () => {
    it('requests operations with serialized query', async () => {
      const ops: APIOperation[] = [makeOperation(1)]
      mockedNetwork.mockReturnValue(networkResponse(ops) as ReturnType<typeof network>)

      const result = await api.getAccountOperations('tz1addr', {
        lastId: 10,
        limit: 50,
        sort: 'Ascending',
        'level.ge': 0,
      })

      expect(result).toEqual(ops)
      const call = mockedNetwork.mock.calls[0][0] as { url: string }
      expect(call.url).toContain('/v1/accounts/tz1addr/operations')
      expect(call.url).toContain('lastId=10')
      expect(call.url).toContain('limit=50')
      expect(call.url).toContain('sort=Ascending')
      expect(call.url).toContain('level.ge=0')
    })

    it('omits undefined optional fields from the serialized query', async () => {
      const ops: APIOperation[] = [makeOperation(1)]
      mockedNetwork.mockReturnValue(networkResponse(ops) as ReturnType<typeof network>)
      const query = {
        lastId: undefined as number | undefined,
        limit: 40,
        sort: 'Ascending' as const,
        'level.ge': 0,
      }

      await api.getAccountOperations('tz1addr', query)

      const call = mockedNetwork.mock.calls[0][0] as { url: string }
      expect(call.url).not.toMatch(/lastId=/)
      expect(call.url).toContain('limit=40')
    })
  })

  // -------------------------------------------------------------------------
  // api.getOperationsTransactions
  // -------------------------------------------------------------------------

  describe('api.getOperationsTransactions', () => {
    it('fetches without cursor and strips undefined extra args', async () => {
      const txs: APITransactionType[] = [makeTxItem(1)]
      mockedNetwork.mockReturnValue(networkResponse(txs) as ReturnType<typeof network>)

      const result = await api.getOperationsTransactions(100, undefined, {
        foo: undefined,
        bar: 'x',
      })

      expect(result).toEqual(txs)
      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({
        'level.gte': 100,
        limit: 1000,
        'sort.asc': 'id',
        bar: 'x',
      })
      expect(params).not.toHaveProperty('foo')
      expect(params).not.toHaveProperty('offset.cr')
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/operations/transactions'),
        })
      )
    })

    it('includes offset.cr when cursor is set', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getOperationsTransactions(5, 999)

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params['offset.cr']).toBe(999)
    })
  })

  // -------------------------------------------------------------------------
  // api.getOperationsOrigination
  // -------------------------------------------------------------------------

  describe('api.getOperationsOrigination', () => {
    it('fetches without cursor and strips undefined extra args', async () => {
      const originations: (APITransactionType & { block: string; hash: string })[] = [
        { id: 1, hash: 'h1', block: 'b1' } as APITransactionType & { hash: string; block: string },
      ]
      mockedNetwork.mockReturnValue(networkResponse(originations) as ReturnType<typeof network>)

      const result = await api.getOperationsOrigination(100, undefined, {
        foo: undefined,
        bar: 'x',
      })

      expect(result).toEqual(originations)
      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({
        'level.gte': 100,
        limit: 1000,
        'sort.asc': 'id',
        bar: 'x',
      })
      expect(params).not.toHaveProperty('foo')
      expect(params).not.toHaveProperty('offset.cr')
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/operations/originations'),
        })
      )
    })

    it('includes offset.cr when cursor is set', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getOperationsOrigination(5, 999)

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params['offset.cr']).toBe(999)
    })
  })

  // -------------------------------------------------------------------------
  // api.getTokenTransfers
  // -------------------------------------------------------------------------

  describe('api.getTokenTransfers', () => {
    it('passes through params and strips undefined extra args', async () => {
      const transfers: APITokenTransfer[] = [makeTokenItem(1)]
      mockedNetwork.mockReturnValue(networkResponse(transfers) as ReturnType<typeof network>)

      const result = await api.getTokenTransfers({
        'level.gte': 200,
        unused: undefined,
        select: 'id',
      })

      expect(result).toEqual(transfers)
      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({
        'level.gte': 200,
        select: 'id',
      })
      expect(params).not.toHaveProperty('unused')
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/tokens/transfers'),
        })
      )
    })
  })

  // -------------------------------------------------------------------------
  // api.getAccountTokenTransfers
  // -------------------------------------------------------------------------

  describe('api.getAccountTokenTransfers', () => {
    it('returns empty hash when no numeric transaction ids are present', async () => {
      const spyTx = spyOnApi('getOperationsTransactions')
      const spyOrig = spyOnApi('getOperationsOrigination')

      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse([
            makeTokenWithTxId(1, undefined),
            makeTokenWithTxId(2, undefined),
          ]) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })

      const result = await api.getAccountTokenTransfers('tz1x', {
        'level.ge': 10,
      })

      expect(spyTx).not.toHaveBeenCalled()
      expect(spyOrig).not.toHaveBeenCalled()
      expect(result).toEqual([])
      const firstCall = mockedNetwork.mock.calls[0]?.[0] as {
        url: string
        params?: Record<string, unknown>
      }
      expect(firstCall.params).toMatchObject({
        'level.ge': 10,
        'sort.asc': 'id',
        'anyof.from.to': 'tz1x',
        'token.standard': 'fa2',
      })
      expect(firstCall.params).not.toHaveProperty('token.tokenId')
    })

    it('joins operation hashes and uses empty string when no match', async () => {
      const spyTx = spyOnApi('getOperationsTransactions')
      const spyOrig = spyOnApi('getOperationsOrigination')
      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse([
            makeTokenWithTxId(1, 100),
            makeTokenWithTxId(2, 200),
          ]) as ReturnType<typeof network>
        }
        if (config.url.includes('/v1/operations/transactions')) {
          return networkResponse([
            { id: 100, hash: 'h100', block: 'BLK100' } as APITransactionType & {
              hash: string
              block: string
            },
          ]) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })

      const result = await api.getAccountTokenTransfers('tz1y', {
        'level.ge': 0,
      })

      expect(result).toEqual([
        expect.objectContaining({ id: 1, hash: 'h100', block: 'BLK100' }),
        expect.objectContaining({ id: 2, hash: '', block: '' }),
      ])
      expect(spyTx).toHaveBeenCalledWith(0, undefined, {
        'id.in': '100,200',
      })
      expect(spyOrig).not.toHaveBeenCalled()
    })

    it('attaches parent hash and block from originations when only originationIds are present', async () => {
      const spyTx = spyOnApi('getOperationsTransactions')
      const spyOrig = spyOnApi('getOperationsOrigination')
      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse([
            makeTokenWithOrigId(1, 100),
            makeTokenWithOrigId(2, 200),
          ]) as ReturnType<typeof network>
        }
        if (config.url.includes('/v1/operations/originations')) {
          return networkResponse([
            { id: 100, hash: 'h100', block: 'BLK100' } as APITransactionType & {
              hash: string
              block: string
            },
            { id: 200, hash: 'h200', block: 'BLK200' } as APITransactionType & {
              hash: string
              block: string
            },
          ]) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })

      const result = await api.getAccountTokenTransfers('tz1o', {
        'level.ge': 0,
      })

      expect(spyTx).not.toHaveBeenCalled()
      expect(spyOrig).toHaveBeenCalledWith(0, undefined, {
        'id.in': '100,200',
      })
      expect(result).toEqual([
        expect.objectContaining({ id: 1, hash: 'h100', block: 'BLK100' }),
        expect.objectContaining({ id: 2, hash: 'h200', block: 'BLK200' }),
      ])
    })

    it('joins both transactions and originations when both ids are present', async () => {
      const spyTx = spyOnApi('getOperationsTransactions')
      const spyOrig = spyOnApi('getOperationsOrigination')
      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse([
            makeTokenWithTxId(1, 100),
            makeTokenWithOrigId(2, 500),
          ]) as ReturnType<typeof network>
        }
        if (config.url.includes('/v1/operations/transactions')) {
          return networkResponse([
            { id: 100, hash: 'hTx', block: 'BLK_TX' } as APITransactionType & {
              hash: string
              block: string
            },
          ]) as ReturnType<typeof network>
        }
        if (config.url.includes('/v1/operations/originations')) {
          return networkResponse([
            { id: 500, hash: 'hOrig', block: 'BLK_ORIG' } as APITransactionType & {
              hash: string
              block: string
            },
          ]) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })

      const result = await api.getAccountTokenTransfers('tz1mix', {
        'level.ge': 0,
      })

      expect(spyTx).toHaveBeenCalledWith(0, undefined, {
        'id.in': '100',
      })
      expect(spyOrig).toHaveBeenCalledWith(0, undefined, {
        'id.in': '500',
      })
      expect(result).toEqual([
        expect.objectContaining({ id: 1, hash: 'hTx', block: 'BLK_TX' }),
        expect.objectContaining({ id: 2, hash: 'hOrig', block: 'BLK_ORIG' }),
      ])
    })

    it('keeps partial hash/block from the parent transaction without dropping transfers', async () => {
      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse([
            makeTokenWithTxId(1, 100),
            makeTokenWithTxId(2, 200),
            makeTokenWithTxId(3, 300),
          ]) as ReturnType<typeof network>
        }
        if (config.url.includes('/v1/operations/transactions')) {
          return networkResponse([
            { id: 100, hash: 'h100', block: '' } as APITransactionType & {
              hash: string
              block: string
            },
            { id: 200, hash: '', block: 'BLK200' } as APITransactionType & {
              hash: string
              block: string
            },
            { id: 300, hash: 'h300', block: 'BLK300' } as APITransactionType & {
              hash: string
              block: string
            },
          ]) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })
      const spyOrig = jest.spyOn(api, 'getOperationsOrigination')

      const result = await api.getAccountTokenTransfers('tz1z', {
        'level.ge': 0,
      })

      expect(result).toEqual([
        expect.objectContaining({ id: 1, hash: 'h100', block: '' }),
        expect.objectContaining({ id: 2, hash: '', block: 'BLK200' }),
        expect.objectContaining({ id: 3, hash: 'h300', block: 'BLK300' }),
      ])
      expect(spyOrig).not.toHaveBeenCalled()
    })

    it('keeps partial hash/block from the parent origination without dropping transfers', async () => {
      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse([
            makeTokenWithOrigId(1, 100),
            makeTokenWithOrigId(2, 200),
            makeTokenWithOrigId(3, 300),
          ]) as ReturnType<typeof network>
        }
        if (config.url.includes('/v1/operations/originations')) {
          return networkResponse([
            { id: 100, hash: 'h100', block: '' } as APITransactionType & {
              hash: string
              block: string
            },
            { id: 200, hash: '', block: 'BLK200' } as APITransactionType & {
              hash: string
              block: string
            },
            { id: 300, hash: 'h300', block: 'BLK300' } as APITransactionType & {
              hash: string
              block: string
            },
          ]) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })
      const spyTx = jest.spyOn(api, 'getOperationsTransactions')

      const result = await api.getAccountTokenTransfers('tz1origZ', {
        'level.ge': 0,
      })

      expect(result).toEqual([
        expect.objectContaining({ id: 1, hash: 'h100', block: '' }),
        expect.objectContaining({ id: 2, hash: '', block: 'BLK200' }),
        expect.objectContaining({ id: 3, hash: 'h300', block: 'BLK300' }),
      ])
      expect(spyTx).not.toHaveBeenCalled()
    })

    it('uses sort.desc=id when sort is Descending and forwards level.lt / level.gt', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getAccountTokenTransfers('tz1sort', {
        sort: 'Descending',
        'level.ge': 1,
        'level.lt': 9_000_000,
        limit: 50,
      })

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({
        'sort.desc': 'id',
        'level.ge': 1,
        'level.lt': 9_000_000,
        limit: 50,
      })
      expect(params).not.toHaveProperty('sort.asc')
    })

    it('forwards level.gt for ascending continuation windows', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getAccountTokenTransfers('tz1asc', {
        sort: 'Ascending',
        'level.ge': 100,
        'level.gt': 500,
      })

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({
        'sort.asc': 'id',
        'level.ge': 100,
        'level.gt': 500,
      })
    })

    it('batches transaction id.in lookups in chunks of 100 when more than 100 ids are present', async () => {
      const spyTx = spyOnApi('getOperationsTransactions')
      const spyOrig = spyOnApi('getOperationsOrigination')

      const tokens = Array.from({ length: 150 }, (_, i) => makeTokenWithTxId(i + 1, i + 1))

      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse(tokens) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })

      spyTx.mockImplementation(async (_level, _cursor, params) => {
        const ids = (params?.['id.in'] as string).split(',').map(Number)
        return ids.map((id) => ({
          id,
          hash: `h${id}`,
          block: `BLK${id}`,
        })) as (APITransactionType & { hash: string; block: string })[]
      })

      const result = await api.getAccountTokenTransfers('tz1batch', {
        'level.ge': 0,
      })

      expect(spyTx).toHaveBeenCalledTimes(2)
      expect(spyOrig).not.toHaveBeenCalled()
      expect(spyTx).toHaveBeenNthCalledWith(1, 0, undefined, {
        'id.in': Array.from({ length: 100 }, (_, i) => i + 1).join(','),
      })
      expect(spyTx).toHaveBeenNthCalledWith(2, 0, undefined, {
        'id.in': Array.from({ length: 50 }, (_, i) => i + 101).join(','),
      })
      expect(result).toHaveLength(150)
      expect(result[0]).toEqual(expect.objectContaining({ id: 1, hash: 'h1', block: 'BLK1' }))
      expect(result[149]).toEqual(
        expect.objectContaining({ id: 150, hash: 'h150', block: 'BLK150' })
      )
    })

    it('batches origination id.in lookups in chunks of 100 when more than 100 ids are present', async () => {
      const spyTx = spyOnApi('getOperationsTransactions')
      const spyOrig = spyOnApi('getOperationsOrigination')

      const tokens = Array.from({ length: 150 }, (_, i) => makeTokenWithOrigId(i + 1, i + 1))

      mockedNetwork.mockImplementation(async (config: { url: string }) => {
        if (config.url.includes('/v1/tokens/transfers')) {
          return networkResponse(tokens) as ReturnType<typeof network>
        }
        throw new Error(`unexpected url: ${config.url}`)
      })

      spyOrig.mockImplementation(async (_level, _cursor, params) => {
        const ids = (params?.['id.in'] as string).split(',').map(Number)
        return ids.map((id) => ({
          id,
          hash: `h${id}`,
          block: `BLK${id}`,
        })) as (APITransactionType & { hash: string; block: string })[]
      })

      const result = await api.getAccountTokenTransfers('tz1batchOrig', {
        'level.ge': 0,
      })

      expect(spyTx).not.toHaveBeenCalled()
      expect(spyOrig).toHaveBeenCalledTimes(2)
      expect(spyOrig).toHaveBeenNthCalledWith(1, 0, undefined, {
        'id.in': Array.from({ length: 100 }, (_, i) => i + 1).join(','),
      })
      expect(spyOrig).toHaveBeenNthCalledWith(2, 0, undefined, {
        'id.in': Array.from({ length: 50 }, (_, i) => i + 101).join(','),
      })
      expect(result).toHaveLength(150)
      expect(result[0]).toEqual(expect.objectContaining({ id: 1, hash: 'h1', block: 'BLK1' }))
      expect(result[149]).toEqual(
        expect.objectContaining({ id: 150, hash: 'h150', block: 'BLK150' })
      )
    })
  })

  // -------------------------------------------------------------------------
  // api.getUnstakeRequestsFinalizable
  // -------------------------------------------------------------------------

  describe('api.getUnstakeRequestsFinalizable', () => {
    it('queries finalizable unstake requests and sums actualAmount', async () => {
      mockedNetwork.mockReturnValue(networkResponse([100, 250, 7]) as ReturnType<typeof network>)

      const result = await api.getUnstakeRequestsFinalizable('tz1abc')

      expect(result).toEqual(357n)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/staking/unstake_requests'),
          params: {
            'staker.eq': 'tz1abc',
            status: 'finalizable',
            'select.values': 'actualAmount',
            limit: 1000,
          },
        })
      )
    })

    it('returns 0n when no finalizable requests exist', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      const result = await api.getUnstakeRequestsFinalizable('tz1empty')

      expect(result).toEqual(0n)
    })
  })

  // -------------------------------------------------------------------------
  // api.getUnstakeRequests
  // -------------------------------------------------------------------------

  describe('api.getUnstakeRequests', () => {
    it('queries pending and finalizable requests in chronological order', async () => {
      const fixture = [
        {
          id: 1,
          cycle: 100,
          baker: { address: 'tz1baker' },
          staker: { address: 'tz1abc' },
          firstTime: '2026-05-01T00:00:00Z',
          status: 'pending',
          actualAmount: 60,
        },
        {
          id: 2,
          cycle: 99,
          baker: { address: 'tz1baker' },
          staker: { address: 'tz1abc' },
          firstTime: '2026-04-25T00:00:00Z',
          status: 'finalizable',
          actualAmount: 40,
        },
      ]
      mockedNetwork.mockReturnValue(networkResponse(fixture) as ReturnType<typeof network>)

      const result = await api.getUnstakeRequests('tz1abc')

      expect(result).toEqual(fixture)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/staking/unstake_requests'),
          params: {
            'staker.eq': 'tz1abc',
            'status.ne': 'finalized',
            'sort.asc': 'id',
            limit: 1000,
          },
        })
      )
    })

    it('returns an empty array when no requests match', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      const result = await api.getUnstakeRequests('tz1empty')

      expect(result).toEqual([])
    })
  })

  // -------------------------------------------------------------------------
  // api.getTokensBalances
  // -------------------------------------------------------------------------

  describe('api.getTokensBalances', () => {
    it('requests all FA2 balances for the account when no filter is passed', async () => {
      const balances = [{ id: 1 } as APITokenBalance]
      mockedNetwork.mockReturnValue(networkResponse(balances) as ReturnType<typeof network>)

      const result = await api.getTokensBalances('tz1bal')

      expect(result).toEqual(balances)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/tokens/balances'),
          params: {
            account: 'tz1bal',
            'token.standard': 'fa2',
          },
        })
      )
    })

    it('requests a specific FA2 contract and token id when a filter is passed', async () => {
      const balances = [{ id: 2 } as APITokenBalance]
      mockedNetwork.mockReturnValue(networkResponse(balances) as ReturnType<typeof network>)

      const result = await api.getTokensBalances('tz1bal', {
        contractAddress: 'KT1abc',
        tokenId: 7,
      })

      expect(result).toEqual(balances)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/tokens/balances'),
          params: {
            account: 'tz1bal',
            'token.standard': 'fa2',
            'token.contract': 'KT1abc',
            'token.tokenId': '7',
          },
        })
      )
    })
  })

  // -------------------------------------------------------------------------
  // fetchAllTransactions
  // -------------------------------------------------------------------------

  // `fetchAllTransactions` builds its own internal `createTzktApi(config)` instance and calls
  // `getAccountOperations` on it, so a spy on the local `api` cannot intercept it. Instead we drive
  // the mocked `network`: each `getAccountOperations` call maps to exactly one `network(...)` whose
  // `.data` is the page. The page cursor (`lastId`) is serialized into the request URL.
  describe('fetchAllTransactions', () => {
    it('returns empty when the first page is empty', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      const result = await fetchAllTransactions(config, 'tz1acc')

      expect(result).toEqual([])
    })

    it('concatenates pages until an empty response', async () => {
      mockedNetwork
        .mockReturnValueOnce(networkResponse([makeOperation(1)]) as ReturnType<typeof network>)
        .mockReturnValueOnce(networkResponse([makeOperation(2)]) as ReturnType<typeof network>)
        .mockReturnValueOnce(networkResponse([]) as ReturnType<typeof network>)

      const result = await fetchAllTransactions(config, 'tz1acc')

      expect(result).toEqual([makeOperation(1), makeOperation(2)])
      expect(mockedNetwork).toHaveBeenCalledTimes(3)
      // First page: no cursor (lastId undefined is stripped from the query), sort ascending.
      const firstUrl = (mockedNetwork.mock.calls[0][0] as { url: string }).url
      expect(firstUrl).toContain('/v1/accounts/tz1acc/operations')
      expect(firstUrl).not.toMatch(/lastId=/)
      expect(firstUrl).toContain('sort=Ascending')
      expect(firstUrl).toContain('level.ge=0')
      // Second page: cursor advances to the id of the last op returned by the first page.
      const secondUrl = (mockedNetwork.mock.calls[1][0] as { url: string }).url
      expect(secondUrl).toContain('lastId=1')
      expect(secondUrl).toContain('sort=Ascending')
      expect(secondUrl).toContain('level.ge=0')
    })

    it('logs and returns when the last operation has a falsy id', async () => {
      const opMissingId = { ...makeOperation(1), id: 0 } as APIOperation
      mockedNetwork.mockReturnValue(networkResponse([opMissingId]) as ReturnType<typeof network>)

      const result = await fetchAllTransactions(config, 'tz1acc')

      expect(result).toEqual([opMissingId])
      expect(mockedLog).toHaveBeenCalledWith('tezos', 'id missing!')
    })

    it('stops after maxTxQuery iterations', async () => {
      // Always return one op with a truthy id so the loop would continue without the cap. The id
      // increases every call so cursors keep advancing (matching the previous spy implementation).
      let nextId = 0
      mockedNetwork.mockImplementation(
        () => networkResponse([makeOperation(++nextId)]) as ReturnType<typeof network>
      )

      const result = await fetchAllTransactions(config, 'tz1acc')

      expect(mockedNetwork).toHaveBeenCalledTimes(mockConfig.explorer.maxTxQuery)
      expect(result).toHaveLength(mockConfig.explorer.maxTxQuery)
    })
  })

  // -------------------------------------------------------------------------
  // api.getBlockByLevel
  // -------------------------------------------------------------------------

  describe('api.getBlockByLevel', () => {
    it('calls the correct URL and returns the block', async () => {
      const block = makeApiBlock(999)
      mockedNetwork.mockReturnValue(networkResponse(block) as ReturnType<typeof network>)

      const result = await api.getBlockByLevel(999)

      expect(result).toEqual(block)
      expect(mockedNetwork).toHaveBeenCalledTimes(1)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/blocks/999'),
        })
      )
    })
  })

  // -------------------------------------------------------------------------
  // api.getBlockHashesByLevels
  // -------------------------------------------------------------------------

  describe('api.getBlockHashesByLevels', () => {
    it('returns an empty map without calling the network when given no levels', async () => {
      const result = await api.getBlockHashesByLevels([])

      expect(result).toEqual(new Map())
      expect(mockedNetwork).not.toHaveBeenCalled()
    })

    it('batches all levels in one request and returns a level→hash map', async () => {
      mockedNetwork.mockReturnValue(
        networkResponse([
          [1234, 'BL-hash-1234'],
          [1235, 'BL-hash-1235'],
        ]) as ReturnType<typeof network>
      )

      const result = await api.getBlockHashesByLevels([1234, 1235])

      expect(result).toEqual(
        new Map([
          [1234, 'BL-hash-1234'],
          [1235, 'BL-hash-1235'],
        ])
      )
      expect(mockedNetwork).toHaveBeenCalledTimes(1)
      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringMatching(/\/v1\/blocks$/),
          params: { 'level.in': '1234,1235', 'select.values': 'level,hash', limit: 2 },
        })
      )
    })

    it('omits levels that the API did not return', async () => {
      mockedNetwork.mockReturnValue(
        networkResponse([[1234, 'BL-hash-1234']]) as ReturnType<typeof network>
      )

      const result = await api.getBlockHashesByLevels([1234, 9999])

      expect(result).toEqual(new Map([[1234, 'BL-hash-1234']]))
      expect(result.has(9999)).toBe(false)
    })
  })

  // -------------------------------------------------------------------------
  // api.getBlockTransactionsPage
  // -------------------------------------------------------------------------

  describe('api.getBlockTransactionsPage', () => {
    it('fetches a page without a cursor', async () => {
      const txs: APITransactionType[] = [makeTxItem(1), makeTxItem(2)]
      mockedNetwork.mockReturnValue(networkResponse(txs) as ReturnType<typeof network>)

      const result = await api.getBlockTransactionsPage(200)

      expect(result).toEqual(txs)
      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({ level: 200, limit: 1000, 'sort.asc': 'id' })
      expect(params).not.toHaveProperty('offset.cr')
    })

    it('includes offset.cr in params when cursor is provided', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getBlockTransactionsPage(200, 42)

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params['offset.cr']).toBe(42)
    })
  })

  // -------------------------------------------------------------------------
  // api.getBlockTokenTransfersPage
  // -------------------------------------------------------------------------

  describe('api.getBlockTokenTransfersPage', () => {
    it('fetches a page without a cursor', async () => {
      const items: APITokenTransfer[] = [makeTokenItem(10)]
      mockedNetwork.mockReturnValue(networkResponse(items) as ReturnType<typeof network>)

      const result = await api.getBlockTokenTransfersPage(300)

      expect(result).toEqual(items)
      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({ level: 300, limit: 1000, 'sort.asc': 'id' })
      expect(params).not.toHaveProperty('offset.cr')
    })

    it('includes offset.cr in params when cursor is provided', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getBlockTokenTransfersPage(300, 77)

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params['offset.cr']).toBe(77)
    })
  })

  // -------------------------------------------------------------------------
  // fetchBlockTransactions — pagination logic
  // -------------------------------------------------------------------------

  // `fetchBlockTransactions` builds its own internal `createTzktApi(config)` and calls
  // `getBlockTransactionsPage` on it, so a spy on the local `api` cannot intercept it. Each page
  // fetch maps to one `network(...)`; the cursor is passed through as the `offset.cr` param.
  describe('fetchBlockTransactions', () => {
    it('returns an empty array when the first page is empty', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      const result = await fetchBlockTransactions(config, 100)

      expect(result).toEqual([])
    })

    it('returns a single page when the page is smaller than the page size', async () => {
      const txs = [makeTxItem(1), makeTxItem(2)]
      mockedNetwork.mockReturnValue(networkResponse(txs) as ReturnType<typeof network>)

      const result = await fetchBlockTransactions(config, 100)

      expect(result).toEqual(txs)
    })

    it('fetches a second page when the first page is full (1 000 items)', async () => {
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeTxItem(i + 1))
      const lastPage = [makeTxItem(1001), makeTxItem(1002)]

      mockedNetwork
        .mockReturnValueOnce(networkResponse(fullPage) as ReturnType<typeof network>)
        .mockReturnValueOnce(networkResponse(lastPage) as ReturnType<typeof network>)

      const result = await fetchBlockTransactions(config, 100)

      expect(result).toHaveLength(1002)
      expect(mockedNetwork).toHaveBeenCalledTimes(2)
      const firstParams = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> })
        .params
      expect(firstParams).toMatchObject({ level: 100 })
      expect(firstParams).not.toHaveProperty('offset.cr')
      // cursor = last item id of first page = 1000
      const secondParams = (mockedNetwork.mock.calls[1][0] as { params: Record<string, unknown> })
        .params
      expect(secondParams).toMatchObject({ level: 100, 'offset.cr': 1000 })
    })

    it('stops after maxTxQuery iterations regardless of page length', async () => {
      // maxTxQuery = 100 (from mockConfig); every call returns a full page to keep the loop going
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeTxItem(i + 1))
      mockedNetwork.mockReturnValue(networkResponse(fullPage) as ReturnType<typeof network>)

      const result = await fetchBlockTransactions(config, 100)

      expect(mockedNetwork).toHaveBeenCalledTimes(100)
      expect(result).toHaveLength(100_000)
      expect(mockedLog).toHaveBeenCalledWith(
        'tezos',
        expect.stringContaining('fetchBlockTransactions: maxTxQuery limit reached at level 100')
      )
    })
  })

  // -------------------------------------------------------------------------
  // fetchBlockTokenTransfers — pagination logic
  // -------------------------------------------------------------------------

  // Driven through the mocked `network` (see the note on `fetchBlockTransactions`).
  describe('fetchBlockTokenTransfers', () => {
    it('returns an empty array when the first page is empty', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      const result = await fetchBlockTokenTransfers(config, 100)

      expect(result).toEqual([])
    })

    it('returns a single page when the page is smaller than the page size', async () => {
      const items = [makeTokenItem(1), makeTokenItem(2)]
      mockedNetwork.mockReturnValue(networkResponse(items) as ReturnType<typeof network>)

      const result = await fetchBlockTokenTransfers(config, 100)

      expect(result).toEqual(items)
    })

    it('fetches a second page when the first page is full (1 000 items)', async () => {
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeTokenItem(i + 1))
      const lastPage = [makeTokenItem(1001)]

      mockedNetwork
        .mockReturnValueOnce(networkResponse(fullPage) as ReturnType<typeof network>)
        .mockReturnValueOnce(networkResponse(lastPage) as ReturnType<typeof network>)

      const result = await fetchBlockTokenTransfers(config, 100)

      expect(result).toHaveLength(1001)
      expect(mockedNetwork).toHaveBeenCalledTimes(2)
      const firstParams = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> })
        .params
      expect(firstParams).toMatchObject({ level: 100 })
      expect(firstParams).not.toHaveProperty('offset.cr')
      const secondParams = (mockedNetwork.mock.calls[1][0] as { params: Record<string, unknown> })
        .params
      expect(secondParams).toMatchObject({ level: 100, 'offset.cr': 1000 })
    })

    it('stops after maxTxQuery iterations regardless of page length', async () => {
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeTokenItem(i + 1))
      mockedNetwork.mockReturnValue(networkResponse(fullPage) as ReturnType<typeof network>)

      const result = await fetchBlockTokenTransfers(config, 100)

      expect(mockedNetwork).toHaveBeenCalledTimes(100)
      expect(result).toHaveLength(100_000)
      expect(mockedLog).toHaveBeenCalledWith(
        'tezos',
        expect.stringContaining('fetchBlockTokenTransfers: maxTxQuery limit reached at level 100')
      )
    })
  })

  // -------------------------------------------------------------------------
  // api.getBlockDelegationsPage
  // -------------------------------------------------------------------------

  describe('api.getBlockDelegationsPage', () => {
    it('fetches a page without a cursor', async () => {
      const items: APIDelegationType[] = [makeDelegationItem(10)]
      mockedNetwork.mockReturnValue(networkResponse(items) as ReturnType<typeof network>)

      const result = await api.getBlockDelegationsPage(400)

      expect(result).toEqual(items)
      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({ level: 400, limit: 1000, 'sort.asc': 'id' })
      expect(params).not.toHaveProperty('offset.cr')
    })

    it('includes offset.cr in params when cursor is provided', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getBlockDelegationsPage(400, 88)

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params['offset.cr']).toBe(88)
    })

    it('calls the delegations endpoint', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getBlockDelegationsPage(500)

      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/operations/delegations'),
        })
      )
    })
  })

  // -------------------------------------------------------------------------
  // fetchBlockDelegations — pagination logic
  // -------------------------------------------------------------------------

  // Driven through the mocked `network` (see the note on `fetchBlockTransactions`).
  describe('fetchBlockDelegations', () => {
    it('returns an empty array when the first page is empty', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      const result = await fetchBlockDelegations(config, 100)

      expect(result).toEqual([])
    })

    it('returns a single page when the page is smaller than the page size', async () => {
      const items = [makeDelegationItem(1), makeDelegationItem(2)]
      mockedNetwork.mockReturnValue(networkResponse(items) as ReturnType<typeof network>)

      const result = await fetchBlockDelegations(config, 100)

      expect(result).toEqual(items)
    })

    it('fetches a second page when the first page is full (1 000 items)', async () => {
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeDelegationItem(i + 1))
      const lastPage = [makeDelegationItem(1001)]

      mockedNetwork
        .mockReturnValueOnce(networkResponse(fullPage) as ReturnType<typeof network>)
        .mockReturnValueOnce(networkResponse(lastPage) as ReturnType<typeof network>)

      const result = await fetchBlockDelegations(config, 100)

      expect(result).toHaveLength(1001)
      expect(mockedNetwork).toHaveBeenCalledTimes(2)
      const firstParams = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> })
        .params
      expect(firstParams).toMatchObject({ level: 100 })
      expect(firstParams).not.toHaveProperty('offset.cr')
      const secondParams = (mockedNetwork.mock.calls[1][0] as { params: Record<string, unknown> })
        .params
      expect(secondParams).toMatchObject({ level: 100, 'offset.cr': 1000 })
    })

    it('stops after maxTxQuery iterations regardless of page length', async () => {
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeDelegationItem(i + 1))
      mockedNetwork.mockReturnValue(networkResponse(fullPage) as ReturnType<typeof network>)

      const result = await fetchBlockDelegations(config, 100)

      expect(mockedNetwork).toHaveBeenCalledTimes(100)
      expect(result).toHaveLength(100_000)
      expect(mockedLog).toHaveBeenCalledWith(
        'tezos',
        expect.stringContaining('fetchBlockDelegations: maxTxQuery limit reached at level 100')
      )
    })
  })

  // -------------------------------------------------------------------------
  // api.getBlockStakingPage
  // -------------------------------------------------------------------------

  describe('api.getBlockStakingPage', () => {
    it('fetches a page without a cursor', async () => {
      const items: APIStakingType[] = [makeStakingItem(10)]
      mockedNetwork.mockReturnValue(networkResponse(items) as ReturnType<typeof network>)

      const result = await api.getBlockStakingPage(400)

      expect(result).toEqual(items)
      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params).toMatchObject({ level: 400, limit: 1000, 'sort.asc': 'id' })
      expect(params).not.toHaveProperty('offset.cr')
    })

    it('includes offset.cr in params when cursor is provided', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getBlockStakingPage(400, 88)

      const params = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> }).params
      expect(params['offset.cr']).toBe(88)
    })

    it('calls the staking endpoint', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      await api.getBlockStakingPage(500)

      expect(mockedNetwork).toHaveBeenCalledWith(
        expect.objectContaining({
          url: expect.stringContaining('/v1/operations/staking'),
        })
      )
    })
  })

  // -------------------------------------------------------------------------
  // fetchBlockStaking — pagination logic
  // -------------------------------------------------------------------------

  // Driven through the mocked `network` (see the note on `fetchBlockTransactions`).
  describe('fetchBlockStaking', () => {
    it('returns an empty array when the first page is empty', async () => {
      mockedNetwork.mockReturnValue(networkResponse([]) as ReturnType<typeof network>)

      const result = await fetchBlockStaking(config, 100)

      expect(result).toEqual([])
    })

    it('returns a single page when the page is smaller than the page size', async () => {
      const items = [makeStakingItem(1), makeStakingItem(2)]
      mockedNetwork.mockReturnValue(networkResponse(items) as ReturnType<typeof network>)

      const result = await fetchBlockStaking(config, 100)

      expect(result).toEqual(items)
    })

    it('fetches a second page when the first page is full (1 000 items)', async () => {
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeStakingItem(i + 1))
      const lastPage = [makeStakingItem(1001)]

      mockedNetwork
        .mockReturnValueOnce(networkResponse(fullPage) as ReturnType<typeof network>)
        .mockReturnValueOnce(networkResponse(lastPage) as ReturnType<typeof network>)

      const result = await fetchBlockStaking(config, 100)

      expect(result).toHaveLength(1001)
      expect(mockedNetwork).toHaveBeenCalledTimes(2)
      const firstParams = (mockedNetwork.mock.calls[0][0] as { params: Record<string, unknown> })
        .params
      expect(firstParams).toMatchObject({ level: 100 })
      expect(firstParams).not.toHaveProperty('offset.cr')
      const secondParams = (mockedNetwork.mock.calls[1][0] as { params: Record<string, unknown> })
        .params
      expect(secondParams).toMatchObject({ level: 100, 'offset.cr': 1000 })
    })

    it('stops after maxTxQuery iterations regardless of page length', async () => {
      const fullPage = Array.from({ length: 1000 }, (_, i) => makeStakingItem(i + 1))
      mockedNetwork.mockReturnValue(networkResponse(fullPage) as ReturnType<typeof network>)

      const result = await fetchBlockStaking(config, 100)

      expect(mockedNetwork).toHaveBeenCalledTimes(100)
      expect(result).toHaveLength(100_000)
      expect(mockedLog).toHaveBeenCalledWith(
        'tezos',
        expect.stringContaining('fetchBlockStaking: maxTxQuery limit reached at level 100')
      )
    })
  })
})