npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tbc-tx-parser

v0.1.10

Published

Decode TBC txraw hex into structured transaction facts.

Readme

tbc-tx-parser

An SDK for decoding raw TBC transactions (txraw) into on-chain facts and asset flows for wallets, block explorers, and debugging tools.

Installation

npm install tbc-tx-parser
pnpm add tbc-tx-parser

Supported Features

Currently supported:

  • Native TBC transfers, coinbase rewards, source-linked returns, fees, and common locking-script outputs.
  • FT / StableCoin mint, transfer, merge, and mixed FT/TBC transactions.
  • NFT collection mint, ordinary NFT mint/transfer, and hash-only Hold transfer destinations.
  • OrderBook placement, cancellation, update, and matching semantics.
  • PoolNFT create, init, increase liquidity, consume liquidity, TBC-to-FT swaps, FT-to-TBC swaps, merge FT in pool, standalone LP merge, and LP burn.
  • PiggyBank freeze / unfreeze.

The parser never accesses the network or fetches prevouts by txid. The caller must provide inputUtxos for every transaction input. Coinbase reward transactions have no spendable input prevouts and are the only exception that may omit inputUtxos.

Basic Usage

import { decodeTxRaw } from 'tbc-tx-parser';

const result = await decodeTxRaw(txraw, {
  inputUtxos,
});

Classification TBC display unit

Native TBC values in classification.description use satoshis by default. Callers may request TBC display values without changing any structured amount fields:

const result = await decodeTxRaw(txraw, {
  inputUtxos,
  display: {
    tbcUnit: 'tbc', // "satoshi" (default) or "tbc"
  },
});

For example, the same description can be rendered as either:

Sends 100,000,000 satoshis to address ...; network fee: 80 satoshis.
Sends 100 TBC to address ...; network fee: 0.000080 TBC.

This option affects only native amounts embedded in classification.description. Fields such as chain.outputs[].satoshis, chain.fee.satoshis, and assetFlows[].amount.satoshis always remain exact satoshi values. Normal TBC amounts use up to six decimal places with trailing zeroes removed; network fees use exactly six decimal places.

To switch units after a transaction has already been decoded, render the description directly from the existing result:

import { renderTransactionDescription } from 'tbc-tx-parser';

const description = renderTransactionDescription(result, {
  tbcUnit: 'tbc',
});

renderTransactionDescription is synchronous and does not parse txraw, fetch input UTXOs, mutate the result, or repeat protocol classification. This makes it suitable for a UI unit selector while keeping structured amounts unchanged.

Input UTXOs

Asset-flow decoding requires the prevout for every input. Passing only txraw throws because the parser cannot reliably determine where assets originated:

const result = await decodeTxRaw(txraw, {
  inputUtxos: {
    'prevTxid:0': {
      satoshis: 1000000,
      lockingScript: '76a914...88ac',
    },
  },
});

The key format is:

`${prevTxid}:${vout}`

An array ordered by input may also be used:

const result = await decodeTxRaw(txraw, {
  inputUtxos: [
    {
      satoshis: 1000000,
      lockingScript: '76a914...88ac',
    },
  ],
});

Contract-asset inputs also require their paired unspendable tape prevouts:

  • FT / StableCoin Code input: associatedTape is vout + 1 in the same transaction and must be a zero-satoshi FTape.
  • StableCoin transfer/merge Code inputs: also provide the canonical StableCoin ID as contractId. The complete ID is not directly encoded in the current StableCoin script, so obtain it from an authoritative indexer or by tracing the StableCoin UTXO lineage; never substitute the current transaction ID, an OrderBook ID, or a Pool LP ID.
  • NFT Code input: associatedTape is vout + 2 in the same transaction and must be a zero-satoshi NTape; the same spend must also contain the corresponding NFT Hold input. associatedOrigin should provide the origin UTXO referenced by the Code script. A Mint NHold origin proves collection membership, while an ordinary P2PKH origin proves a standalone NFT that does not use a collection UTXO.
  • PoolNFT state input: when the state prevout does not directly carry the PoolNFT tape, provide the adjacent PoolNFT OP_RETURN tape at vout + 1 as associatedTape so the parser can compare the previous and current pool states.

If a paired tape is missing, scripts do not match, or an input prevout is not a valid script for the asset, the result does not emit strongly verified protocol asset flows. Related positive-value locking outputs are displayed as TBC flowing to an address, a known contract, or an unknown locking script.

Return Value

const result = await decodeTxRaw(txraw, { inputUtxos });

result.txid;
result.version;
result.locktime;
result.size;
result.chain.inputs;
result.chain.outputs;
result.chain.fee;
result.assetFlows;
result.unspendableAssetFlows;
result.classification;

Common fields:

  • chain.inputs: transaction inputs and decoded prevout facts.
  • chain.outputs: transaction outputs, addresses, script classifications, and protocol tape state.
  • chain.fee: transaction fee.
  • assetFlows: primary asset-flow data for application displays.
  • unspendableAssetFlows: optional FT declarations excluded from normal asset flows after a verified token-amount conservation violation.
  • classification: deterministic transaction type, protocol conformance, and an English result description.

The SDK exposes transaction classification separately from asset flows so applications can show a concise conclusion without treating weak protocol evidence as definitive:

result.classification.protocol;    // e.g. "orderBook"
result.classification.action;      // e.g. "match"
result.classification.conformance; // standard | nonstandard | indeterminate
result.classification.description; // fact-based English conclusion for this transaction
result.classification.issues;      // optional stable issue codes

standard means the transaction matches a supported protocol action and every invariant currently implemented by this SDK for that action. It is not a consensus-validity or spendability guarantee. nonstandard means the protocol family or action is recognizable but an implemented rule is violated. indeterminate means the available chain evidence is insufficient to run the relevant checks; missing associated prevouts are not reported as protocol violations.

For non-mint FT transactions, classification includes token-amount conservation. If declared FT outputs exceed fully verified FT inputs, the result is nonstandard with issue code FT_OUTPUT_AMOUNT_EXCEEDS_VERIFIED_INPUT. Those FT declarations are excluded from assetFlows and returned in unspendableAssetFlows; their decoded scripts also remain available in chain.outputs. The field name describes the SDK's conservative application view and does not independently prove consensus-level unspendability. If the relevant FT input amounts are incomplete, conformance is indeterminate instead of reporting a violation.

protocol and action form the structured transaction type. Their TypeScript definitions are correlated, so unsupported combinations are rejected at compile time. Classification confidence remains an internal parsing concern rather than a public result field.

description is generated from the classified action together with the transaction's verified assetFlows, endpoint types, and network fee. It summarizes the primary business action instead of replacing the full chain or asset-flow data, for example: Sends 1,000 PFT to address 1Pz2eAg3JjXQSqs5nGQj7XGhPzvBEbaiP9; network fee: 445 satoshis. A single verified destination includes its full address, multisignature address, pool ID, or hash; callers decide whether to shorten it in the UI. Multiple destinations are deduplicated and summarized using one total amount and a destination count, never per-destination amounts. Source-linked self flows remain available in assetFlows, but are omitted from descriptions that already contain an external transfer. Transactions containing only self flows use neutral Internal transfer ...; no funds were sent ... wording without describing UTXO split/merge mechanics or unknown attached data. Protocol carrier values are omitted from material secondary transfers unless they also match a standalone payment output. When evidence is incomplete, the wording becomes explicitly cautious and does not invent amounts or actions. The final network fee clause is the chain transaction fee only; protocol-specific trading or liquidity fees are not folded into it.

The first classification release is intentionally fixture-driven: it covers native TBC coinbase, transfer, and MultiSig transactions; StableCoin bootstrap and token actions; and the FT, NFT, PiggyBank, OrderBook, and PoolNFT actions already represented by the SDK test corpus. Unknown contracts remain indeterminate instead of receiving a guessed definitive type.

The current version also keeps internal semantic, protocolDetails, and unsupported data private. These remain internal protocol-classification and diagnostic capabilities and can later be exposed through an optional diagnostic result if an explorer or protocol-details view needs them.

Core assetFlows structure:

result.assetFlows[0]?.purpose;   // transfer | self | fee | lock | release | mint | burn | state | unknown
result.assetFlows[0]?.direction; // in | out | self | unknown
result.assetFlows[0]?.asset;     // { type: 'tbc' | 'ft' | 'stablecoin' | 'nft', ... }
result.assetFlows[0]?.amount;    // Present for divisible assets such as TBC/FT; absent from NFT flows
result.assetFlows[0]?.from;      // address / addresses / hash / poolId / txid+vout / outpoints
result.assetFlows[0]?.to;        // address / addresses / hash / poolId / script / txid+vout
result.assetFlows[0]?.context;   // optional { protocol, recognized, action?, markers? }

Canonical flow combinations and endpoint rules:

| Purpose | Direction | Endpoints | | --- | --- | --- | | self | self | to matches a verified source endpoint; this covers address returns and consolidations without asserting wallet intent | | transfer | out | both from and to are present | | mint | in | no from; to is present only when a recipient is decoded | | burn | out | from is present; no to | | lock | out | both from and to are present | | release | in | both from and to are present |

The parser never emits public purpose: "transfer" with direction: "self"; a verified source-linked return is normalized to self/self. A self flow retains both endpoints even when they are equal, but its presence alone does not make the whole transaction a self-transfer. When a consumed asset's source address or hash is unavailable, from uses the verified input { txid, vout }, or outpoints for multiple source UTXOs, instead of guessing an address. Explicitly partial unknown flows may keep optional endpoints.

A mint may omit to when asset creation is verified but no recipient identity is decoded. PoolNFT create is the canonical example: its Pool Code output is a state anchor, so the contract outpoint remains in chain.outputs and is not presented as an asset-flow destination.

Protocol-specific flows retain a generic purpose and may include context. For example, PiggyBank freeze and OrderBook placement both use purpose: "lock", while context.protocol and context.action distinguish piggyBank/freeze from orderBook/placeBuy or orderBook/placeSell. Recognized OrderBook context is attached only to placement locks. Match settlement and match fee-recipient payments are plain transfers, cancellation refunds are releases, and recreated partial-fill or update order state is read from chain.outputs[].script.data.orderBook rather than exposed as an asset flow. OrderBook fee-recipient payments use purpose: "transfer". PoolNFT reserve snapshots are likewise read from chain.outputs[].script.data.poolNft; assetFlows contains only the pool's observable transfers, LP mint/burn, source-linked returns, and visible fee flows. Unknown contract carriers remain purpose: "transfer" and use protocol: "unknown" with recognized: false; marker evidence may be included without guessing a protocol name. When no source address or hash can be extracted from a locking script, the destination is represented by to.script.

Display guidance:

  • Summarize what a transaction does from assetFlows, script facts, and application context.
  • Use assetFlows as the primary source for showing how assets move.
  • Treat self as a source-linked structural flow. Do not present it as change or returned funds; omit it from the primary description when external transfer flows are also present.
  • Read chain.inputs / chain.outputs script.data when displaying contract state or protocol tape fields.

Amount Units

TBC amounts use satoshis:

result.chain.outputs[0].satoshis;
result.chain.fee?.satoshis;

FT / StableCoin / PoolNFT LP amounts use raw smallest-unit strings:

result.assetFlows[0]?.amount;

NFTs are indivisible, so ordinary NFT flows do not contain amount. supply is collection-creation metadata and does not represent a quantity for an individual NFT. An NFT minted from a collection Mint NHold may include collectionId; a standalone NFT originating from ordinary P2PKH retains only its own contractId and has no collection fields.

PoolNFT Notes

PoolNFT is a composite protocol. Decoding considers all of the following rather than relying only on the PoolNFT tape:

  • Previous pool state input.
  • Current pool state output.
  • PoolNFT OP_RETURN tape.
  • FT A / FTLP movements.
  • TBC reserve delta, payout, and fee output.
  • LP token identity, partial hash, and burn receiver.

The active PoolNFT fixture corpus contains exactly 18 real testnet broadcasts: the nine operations above for one unlocked and one public-key-locked PoolNFT v2 pool. Both modes use independent ordinary FT contracts and independent pools. The locked cases have withLock: true; the unlocked cases have withLock: false; all 18 cases use withLockTime: false.

Current PoolNFT coverage includes the primary paths and several false-positive protections:

  • Reserve delta, FT A amount, and fee relationships for both swap directions.
  • FT A, LP, and TBC relationships for increase / consume liquidity.
  • Returned LP amounts, no-visible-fee cases, and small TBC rounding boundaries during consume.
  • Current FT A reserve validation for merge FT in pool.
  • Standalone FTLP merges remain FT self flows without recreating PoolNFT state.
  • Standalone FTLP burns remain FT burn flows without recreating PoolNFT state.
  • Multiple PoolNFT tapes or multiple previous PoolNFT state inputs are currently treated as unsupported and do not produce PoolNFT movements.

If the caller omits the previous pool state's associated tape, PoolNFT may still be recognized as heuristic, but a complete state delta cannot be calculated.

Regenerating PoolNFT broadcast fixtures

The runner is testnet-only and resumable. Its key file, public transaction journal, and generated candidates remain ignored under scripts/. Preserve the journal until fixture generation finishes: it records both the 18 target transactions and the preparation transactions needed to resume safely. Preparation transactions are never promoted as fixtures.

Run the read-only checks first:

TBC_KEYS_FILE=/absolute/path/to/tbc-test-addresses.json \
TBC_POOL_NFT_JOURNAL=/absolute/path/to/pool-nft-broadcast-journal.json \
npm run pool-nft:preflight

TBC_POOL_NFT_JOURNAL=/absolute/path/to/pool-nft-broadcast-journal.json \
npm run pool-nft:status

Broadcast each lifecycle only when testnet spending is intended. TBC_BROADCAST=1 is an explicit mutation guard and these commands spend testnet UTXOs:

TBC_BROADCAST=1 TBC_KEYS_FILE=/absolute/path/to/tbc-test-addresses.json \
TBC_POOL_NFT_JOURNAL=/absolute/path/to/pool-nft-broadcast-journal.json \
npm run pool-nft:run -- unlocked

TBC_BROADCAST=1 TBC_KEYS_FILE=/absolute/path/to/tbc-test-addresses.json \
TBC_POOL_NFT_JOURNAL=/absolute/path/to/pool-nft-broadcast-journal.json \
npm run pool-nft:run -- locked

After status reports all 18 targets complete, generate reviewed candidates without broadcasting:

TBC_POOL_NFT_JOURNAL=/absolute/path/to/pool-nft-broadcast-journal.json \
npm run pool-nft:fixtures

The generator re-fetches every target raw transaction from testnet, verifies its txid, decodes its prevouts, validates lock flags, rejects public purpose: "state" flows, and writes candidates under scripts/pool-nft-generated/. Pool reserve state belongs under chain.outputs[].script.data.poolNft; only observable transfers, mint/burn, source-linked returns, and visible fees belong in assetFlows.

Notes

  • The SDK does not access the network.
  • The SDK does not accept a network parameter.
  • The SDK does not query transactions by txid.
  • The SDK does not fetch input prevouts; callers must provide inputUtxos.
  • The SDK exposes top-level classification; internal semantic, protocolDetails, and unsupported fields remain private.
  • TBC addresses use canonical TBC address encoding.
  • tbc-lib-js and tbc-contract are runtime dependencies installed with the npm package.
  • docs/, scripts/, and tests/ are not published to npm; the package contains only dist, README.md, and package.json.