getTXOs

Returns transaction outputs for the given query parameters.

Type

type TXORecord = {
  rev: string
  address: string
  satoshis: bigint
  asm: string
  expHash?: string
  mod?: string
  isObject?: boolean
  previous?: string
  blockHash?: string
  blockHeight?: number
  blockIndex?: number
}

type TXOQuery = {
  verbosity?: number
  limit?: number
  order?: 'ASC' | 'DESC'
  offset?: number
  isSpent?: boolean
  isConfirmed?: boolean
  publicKey?: PublicKeyString
  exp?: string
} & Partial<Omit<TXORecord, 'expHash'>>

async getTXOs(q: TXOQuery & { verbosity?: 0 }): Promise<string[]>
async getTXOs(q: TXOQuery & { verbosity: 1 }): Promise<TXORecord[]>
async getTXOs(q: TXOQuery): Promise<string[] | TXORecord[]>

Parameters

query

An object with the query parameters.

Key Description
verbosity 0 for revision strings, 1 for rows from the Output table
rev Return TXOs with this exact revision
address Return TXOs that belong to this address. The matching address is computed from the output script (see below)
satoshis Return TXOs with this exact satoshis amount
asm Return TXOs matching the asm script
isObject Return TXOs that are (or are not) smart objects
mod Return TXOs that are smart objects created with this module or a descendant of it
previous Return TXOs whose previous revision is the provided parameter
exp Giving an expression, return TXOs that matches the hash of that expression
blockHash Return TXOs that are included in the block with this hash
isSpent Return TXOs that are (or are not) spent
isConfirmed Return TXOs that are (or are not) included in a block
publicKey Return TXOs whose asm contains this public key
limit Limit the number of TXOs returned
offset Return results starting from offset
order Order results in ascending or descending order

Return Value

An array of either revision strings or rows from the database Output table, depending on the verbosity level specified in the query.

Description

The getTXOs function retrieves transaction outputs (TXOs) from the database based on customizable query parameters. It supports filtering by attributes such as address, satoshi amount, script assembly (ASM), smart object status, ownership via public key, and more. Results can be returned in two formats depending on the verbosity level: an array of revision strings (for verbosity: 0) or an array of detailed TXORecord objects (for verbosity: 1).

Key Column Details

  • address: Derived from the output script using address.fromOutputScript from the @bitcoin-computer/nakamotoJs library. If the script cannot be converted (e.g., non-standard P2PKH or P2SH scripts), this value is set to null. When filtering by address, only TXOs with matching convertible scripts are included.
  • asm: The assembly (ASM) string representation of the output script, computed as script.toASM(script). Use this for exact script-based filtering.
  • Other columns: Include rev (revision string), satoshis (output value as bigint), isObject (boolean indicating if it's a smart object), mod (module identifier for smart objects), exp (expression hash), previous (prior TXO reference), blockHash (inclusion in a specific block), and spent (spend status).

Filtering Behaviors

  • By public key (publicKey): Matches TXOs where the output asm script contains specified public key, enabling ownership-based queries. The value does not necessary have to be a public key, the query works for any string. For example if you query for OP_TRUE the query will return all outputs whose script contains OP_TRUE.
  • By smart object status (isObject): Set to true to retrieve all on-chain smart objects.
  • By module (mod): Filters for smart objects created with the given module specifier. Modules are inherited in descendant objects within the Bitcoin Computer ecosystem, except during new object creation or explicit module overrides in expressions. This is particularly useful for tracking token transfers or module-specific lineages—refer to the examples for practical demonstrations.
  • Spent status (spent): Includes or excludes spent TXOs; combine with other filters for UTXO-like queries.
  • Pagination and ordering: Use limit, offset, and order ('ASC' or 'DESC') for controlled, sorted result sets.

For security and efficiency, always pair this with publicKey to scope results to a specific owner, and apply limit/offset to manage result volume.

To retrieve Unspent Transaction Outputs, see the syntactic sugar function getUTXOs, which internally calls getTXOs with the isSpent: false parameter.

To retrieve Bitcoin Computer objects, see the syntactic sugar function getOTXOs, which internally calls getTXOs with the isObject: true parameter.

To retrieve unspent Bitcoin Computer objects, see the syntactic sugar function getUOTXOs, which internally calls getTXOs with the parameters isObject: true and isSpent: false.

Example

import { Computer, Contract } from '@bitcoin-computer/lib'
import { crypto, script } from '@bitcoin-computer/nakamotojs'
import { chain, expect, network, sleep, url } from '../../utils/index.js'

describe('getTXOs', () => {
  let computer: Computer

  class Counter extends Contract {
    x: number
    constructor() {
      super({ x: 0 })
    }
    inc() {
      this.x += 1
    }
  }
  class Token extends Contract {
    amount: bigint
    constructor(to: string, amount: bigint) {
      super({ _owners: [to], amount })
    }

    transfer(to: string, amount: bigint): Token {
      if (this.amount >= amount) {
        // Send partial amount in a new object
        this.amount -= amount
        return new Token(to, amount)
      }
      throw new Error('Insufficient funds')
    }
  }

  class Payment extends Contract {
    constructor(_satoshis: bigint) {
      super({ _satoshis })
    }
    transfer(to: string) {
      this._owners = [to]
    }
    setSatoshis(a: bigint) {
      this._satoshis = a
    }
  }

  before('Fund computer', async () => {
    computer = new Computer({ chain, network, url })
    await computer.faucet(3e8)
    const c = await computer.new(Counter, [])
    await c.inc()
  })

  describe('Error handling', () => {
    it('Should throw for an empty query string', async () => {
      let error
      try {
        await computer.getTXOs({})
      } catch (e: any) {
        error = e
      }
      expect(error).to.be.instanceOf(Error)
      expect(error.message).to.eq('At least one query parameter must be provided')
    })
    it('Should throw when the query string only includes verbosity', async () => {
      let error
      try {
        await computer.getTXOs({ verbosity: 1 })
      } catch (e: any) {
        error = e
      }
      expect(error).to.be.instanceOf(Error)
      expect(error.message).to.eq('At least one query parameter must be provided')
      try {
        await computer.getTXOs({ verbosity: 0 })
      } catch (e: any) {
        error = e
      }
      expect(error).to.be.instanceOf(Error)
      expect(error.message).to.eq('At least one query parameter must be provided')
    })
  })
  describe('Get by rev', () => {
    it('Should get TXOs by rev', async () => {
      const c3 = await computer.new(Counter, [])
      const txos = await computer.getTXOs({ rev: c3._rev })
      expect(txos.length).to.eq(1)
      expect(txos[0]).to.eq(c3._rev)

      const txosJson = await computer.getTXOs({ rev: c3._rev, verbosity: 1 })
      expect(txosJson.length).to.eq(1)
      expect(txosJson[0].rev).to.eq(c3._rev)
      expect(txosJson[0].isObject).to.eq(true)
      expect(txosJson[0].satoshis).to.be.a('bigint')
      expect(txosJson[0].asm).to.be.a('string')
    })
  })
  describe('Get by address', () => {
    it('Should get TXOs by address and return an array of strings with the default verbosity', async () => {
      const txos = await computer.getTXOs({ address: computer.getAddress() })
      expect(txos.length).to.be.greaterThan(0)
      txos.forEach((txo) => {
        expect(txo.split(':').length).to.eq(2)
        expect(txo.split(':')[0].length).to.eq(64)
      })
    })

    it('Should  get TXOs by address and return all attributes with verbosity 1', async () => {
      const txos = await computer.getTXOs({ verbosity: 1, address: computer.getAddress() })
      expect(txos.length).to.be.greaterThan(0)
      txos.forEach((txo) => {
        expect(txo.address).to.eq(computer.getAddress())
        expect(txo.satoshis).to.be.a('bigint')
        expect(txo.rev).to.be.a('string')
        expect(txo.asm).to.be.a('string')
        expect(txo.isObject).to.be.a('boolean')
      })
    })

    it('Should get TXOs by address and return unpent txos only with spent=false', async () => {
      const allTxos = await computer.getTXOs({ address: computer.getAddress() })
      const unspentTxos = await computer.getTXOs({
        address: computer.getAddress(),
        isSpent: false,
      })
      unspentTxos.forEach((txo) => {
        expect(allTxos).to.include(txo)
      })
    })

    it('Should get TXOs by address and compute the balance for the computer address', async () => {
      const txos = await computer.getTXOs({
        address: computer.getAddress(),
        isSpent: false,
        verbosity: 1,
      })
      const balance: bigint = txos.reduce((acc: bigint, txo) => {
        return acc + txo.satoshis
      }, 0n)

      const computerBalance = await computer.getBalance()
      expect(balance).to.be.a('bigint')
      expect(balance).to.be.greaterThan(0)
      expect(balance).to.eq(computerBalance.balance)
    })

    it('Should get TXOs by address and omit on chain objects', async () => {
      const c2 = await computer.new(Counter, [])
      const txos = await computer.getTXOs({
        address: computer.getAddress(),
      })
      expect(txos.length).to.be.greaterThan(0)
      // On chain objects do not belong to the address query
      expect(txos).to.not.include(c2._rev)
    })
  })
  describe('Get by satoshis', () => {
    it('Should get TXOs by satoshis amount', async () => {
      const c1 = await computer.new(Payment, [1000000n])
      await computer.faucet(2e8)
      const satoshis = 2_000_000n
      await c1.setSatoshis(satoshis)

      const txos = await computer.getTXOs({ satoshis })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.not.include(c1._id)
      expect(txos).to.include(c1._rev)
    })
  })
  describe('Get by asm', () => {
    it('Should get TXOs by asm substring', async () => {
      class True extends Contract {
        constructor(script: string) {
          super({ _owners: script })
        }
      }
      const scriptString = 'OP_TRUE'
      const c2 = await computer.new(True, [scriptString])
      const hash = crypto.hash160(script.fromASM(scriptString)).toString('hex')
      const asm = `OP_HASH160 ${hash} OP_EQUAL`

      const txos = await computer.getTXOs({ asm })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.include(c2._rev)
    })
  })
  describe('Get by mod', () => {
    it('Should get TXOs by mod for creation and object function calls', async () => {
      const m = await computer.deploy('')
      const c4 = await computer.new(Counter, [], m)
      await c4.inc()
      await sleep(1000)
      const txos = await computer.getTXOs({ mod: m })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.include(c4._id)
      expect(txos).to.include(c4._rev)
    })

    it('Should get TXOs by mod for objects created directly with .new()', async () => {
      const m = await computer.deploy(`export ${Token}`)
      const t = await computer.new(Token, [computer.getPublicKey(), 100n], m)

      const txos = await computer.getTXOs({ mod: m })

      expect(txos).to.include(t._id)
    })

    it('Should NOT return TXO revisions created by .transfer() when filtering by mod', async () => {
      const m = await computer.deploy(`export ${Token}`)
      const t = await computer.new(Token, [computer.getPublicKey(), 100n], m)
      const computer2 = new Computer({ chain, network, url })
      const newToken = await t.transfer(computer2.getPublicKey(), 40n)

      const txos = await computer.getTXOs({ mod: m })

      expect(txos).not.to.include(newToken._rev)
    })

    it('Should get TXOs by mod on token transfers with explicit different mod parameter', async () => {
      const m = await computer.deploy(`export ${Token}`)
      const m1 = await computer.deploy(``) // different mod
      const t = await computer.new(Token, [computer.getPublicKey(), 100n], m)
      const computer2 = new Computer({ chain, network, url })
      const transferTx = await computer.encode({
        exp: `a.transfer('${computer2.getPublicKey()}', 40n)`,
        env: { a: t._rev },
        mod: m1, // explicitly specifying the mod here
      })
      await computer.broadcast(transferTx.tx!)

      // sync to the new token
      const newToken = await computer.sync<typeof Token>(`${transferTx.tx!.getId()}:0`)

      await computer.waitForIndexed(newToken._rev)
      const txos = await computer.getTXOs({ mod: m1 })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.not.include(t._id)
      expect(txos).to.include(newToken._rev)
    })
  })
  describe('Get by isObject', () => {
    it('Should get TXOs by isObject key', async () => {
      const computer2 = new Computer({ chain, network, url })
      await computer2.faucet(1e8)
      const c2 = await computer2.new(Counter, [])
      await computer2.waitForIndexed(c2._rev)
      const objectTxos = await computer2.getTXOs({ isObject: true })
      expect(objectTxos.length).to.be.greaterThan(0)
      expect(objectTxos).to.include(c2._rev)

      const nonObjectTxos = await computer2.getTXOs({ isObject: false })
      expect(nonObjectTxos.length).to.be.greaterThan(0)
      expect(nonObjectTxos).to.not.include(c2._rev)

      // It is a good practice to combine isObject with publicKey to narrow down the search
      const objectTxosByPubKey = await computer2.getTXOs({
        isObject: true,
        publicKey: computer2.getPublicKey(),
      })
      expect(objectTxosByPubKey.length).to.be.greaterThan(0)
      expect(objectTxosByPubKey).to.include(c2._rev)

      // Lets spend the on chain object to test isObject filter
      await c2.inc()

      const objectTxosAfterSpend = await computer2.getTXOs({
        isObject: true,
        publicKey: computer2.getPublicKey(),
      })

      expect(objectTxosAfterSpend.length).to.greaterThan(0)
      const spent = objectTxosAfterSpend.filter((txo) => txo === c2._id)
      expect(spent.length).to.eq(1)
      const unspent = objectTxosAfterSpend.filter((txo) => txo === c2._rev)
      expect(unspent.length).to.eq(1)
    })
  })
  describe('Get by previous', () => {
    it('Should get TXOs by previous', async () => {
      const c2 = await computer.new(Counter, [])
      await c2.inc()

      const txos = await computer.getTXOs({ previous: c2._id })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.include(c2._rev)
    })
  })
  describe('Get by exp', () => {
    it('Should get TXOs by exp', async () => {
      class C extends Contract {}

      const exp = `${C} new ${C.name}()`
      const { tx } = await computer.encode({ exp })
      await computer.broadcast(tx!)

      const txos = await computer.getTXOs({ exp })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.include(`${tx!.getId()}:0`)
    })
  })
  describe('Get by blockHash', () => {
    it('Should get TXOs by blockHash', async () => {
      const c2 = await computer.new(Counter, [])
      const computer2 = new Computer({ chain, network, url })

      // mine a block to confirm the transaction
      const blockHex = await computer.rpc('generateToAddress', `1 ${computer2.getAddress()}`)
      await sleep(1000)
      const txos = await computer.getTXOs({ blockHash: blockHex.result[0] })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.include(c2._rev)
    })
  })
  describe('Get by blockHeight', () => {
    it('Should get TXOs by blockHeight', async () => {
      const c2 = await computer.new(Counter, [])
      const computer2 = new Computer({ chain, network, url })

      // mine a block to confirm the transaction
      const blockHex = await computer.rpc('generateToAddress', `1 ${computer2.getAddress()}`)
      const blockInfo = await computer.rpc('getBlock', `${blockHex.result[0]} 1`)
      await sleep(1000)
      const txos = await computer.getTXOs({ blockHeight: blockInfo.result.height })
      expect(txos.length).to.be.greaterThan(0)
      expect(txos).to.include(c2._rev)
    })
  })
  describe('Get TXOs by blockIndex', () => {
    it('Should get TXOs by blockIndex', async () => {
      const c2 = await computer.new(Counter, [])
      await c2.inc()
      const computer2 = new Computer({ chain, network, url })

      // mine a block to confirm the transaction
      const blockHex = await computer.rpc('generateToAddress', `1 ${computer2.getAddress()}`)
      const blockInfo = await computer.rpc('getBlock', `${blockHex.result[0]} 1`)
      await sleep(1000)

      // check tx index 0 and 1
      const txosIdx0 = await computer.getTXOs({ blockIndex: 0, blockHash: blockInfo.result.hash })
      expect(txosIdx0.length).to.be.greaterThan(0)
      expect(txosIdx0).to.include(`${blockInfo.result.tx[0]}:0`)
      const txosIdx1 = await computer.getTXOs({ blockIndex: 1, blockHash: blockInfo.result.hash })
      expect(txosIdx1.length).to.be.greaterThan(0)
      expect(txosIdx1).to.include(`${blockInfo.result.tx[1]}:0`)
    })
  })
  describe('Get by publicKey', () => {
    it('Should get TXOs by public key and include on chain objects', async () => {
      const c2 = await computer.new(Counter, [])
      const txos = await computer.getTXOs({ publicKey: computer.getPublicKey() })
      expect(txos.length).to.be.greaterThan(0)
      // On chain objects belong to the computer's public key
      expect(txos).includes(c2._rev)
    })
  })
  describe('Get by isSpent', () => {
    it('Should get TXOs by isSpent key', async () => {
      const c2 = await computer.new(Counter, [])
      await c2.inc()

      const unspentTxos = await computer.getTXOs({
        isSpent: false,
        publicKey: computer.getPublicKey(),
      })
      expect(unspentTxos.length).to.be.greaterThan(0)
      expect(unspentTxos).to.include(c2._rev)
      expect(unspentTxos).to.not.include(c2._id)

      const spentTxos = await computer.getTXOs({
        isSpent: true,
        publicKey: computer.getPublicKey(),
      })
      expect(spentTxos.length).to.be.greaterThan(0)
      expect(spentTxos).to.include(c2._id)
      expect(spentTxos).to.not.include(c2._rev)
    })
  })
  describe('Get by isConfirmed', () => {
    it('Should get TXOs by isConfirmed key', async () => {
      const computer2 = new Computer({ chain, network, url })
      await computer2.faucet(1e8)
      const c2 = await computer2.new(Counter, [])

      const unconfirmedTxos = await computer2.getTXOs({
        isConfirmed: false,
        publicKey: computer2.getPublicKey(),
      })
      expect(unconfirmedTxos.length).to.be.greaterThan(0)
      expect(unconfirmedTxos).to.include(c2._rev)

      // mine a block to confirm the transaction
      await computer2.rpc('generateToAddress', `1 ${computer2.getAddress()}`)

      await sleep(1000)
      const confirmedTxos = await computer2.getTXOs({
        isConfirmed: true,
        publicKey: computer2.getPublicKey(),
      })
      expect(confirmedTxos.length).to.be.greaterThan(0)
      expect(confirmedTxos).to.include(c2._rev)
    })
  })
  describe('Get by limit, offset and order', () => {
    it('Should get TXOs by limit, offset and order', async () => {
      const txoIds: string[] = []
      for (let i = 0; i < 5; i++) {
        const c = await computer.new(Counter, [])
        txoIds.push(c._rev)
      }

      const allTxos = await computer.getTXOs({
        publicKey: computer.getPublicKey(),
        order: 'DESC',
      })
      const limitedTxos = await computer.getTXOs({
        publicKey: computer.getPublicKey(),
        limit: 2,
        offset: 1,
        order: 'DESC',
      })

      expect(limitedTxos.length).to.eq(2)
      expect(limitedTxos[0]).to.eq(allTxos[1])
      expect(limitedTxos[1]).to.eq(allTxos[2])
    })
  })
  describe('Should work with deletions', () => {
    it('Deleted objects should not be included in utxos', async () => {
      const c2 = await computer.new(Counter, [])
      await c2.inc()
      await computer.delete([c2._rev])

      const utxos = await computer.getTXOs({
        publicKey: computer.getPublicKey(),
        isSpent: false,
      })
      expect(utxos.length).to.be.greaterThan(0)
      expect(utxos).to.not.include(c2._rev)
    })
  })
})

Source