A TypeScript library for decoding Cosmos SDK transactions, providing human-readable message decoding and balance change tracking.
β¨ Features
- Human-Readable Output: Decodes Cosmos SDK transaction messages into a clear, human-readable JSON format.
- Multi-VM Support: Full support for Move VM, EVM, and WASM virtual machines with VM-specific balance tracking.
- Balance Tracking: Tracks fungible token deltas and correlates Move objects or EVM NFTs with specific tokenId tracking based on the originating VM.
- Type-Safe: Built with TypeScript and validated with Zod for robust, type-safe operations.
- Extensible: Features a flexible handler system that can be easily extended to support new message types.
- Immutable State: Uses Immer for safe and predictable state management.
- ABI-driven EVM Support: Uses
viemto decode EVM event logs (ERC-20 and ERC-721Transferevents) without relying on Cosmoscoin_spentevents.
π¦ Installation
# npm
npm install @initia/tx-decoder
# yarn
yarn add @initia/tx-decoder
# pnpm
pnpm add @initia/tx-decoder
# bun
bun add @initia/tx-decoderπ Quick Start
import { TxDecoder } from "@initia/tx-decoder";
const decoder = new TxDecoder({
registryUrl: "https://registry.initia.xyz/",
restUrl: "https://rest.initia.xyz"
});
// Decode a Cosmos transaction for L1 and Move L2
const decodedTx = await decoder.decodeCosmosTransaction(txResponse);
console.log(decodedTx);
// Decode a Cosmos transaction for EVM L2
const decodedEvmTx = await decoder.decodeCosmosEvmTransaction(txResponse);
console.log(decodedEvmTx);
// Decode a Cosmos transaction for WASM L2
const decodedWasmTx = await decoder.decodeCosmosWasmTransaction(txResponse);
console.log(decodedWasmTx);
// Decode a native Ethereum RPC transaction
const ethereumTx = await decoder.decodeEthereumTransaction({
tx: ethereumTransaction,
txReceipt: ethereumTransactionReceipt
});
console.log(ethereumTx);Each decoded message includes a balanceChanges object tagged with vm: "move", vm: "evm", or vm: "wasm". EVM balance deltas are sourced from decoded log events via viem rather than Cosmos bank events.
π API Reference
The main class for decoding transactions.
new TxDecoder(config: DecoderConfig)Parameters:
config: DecoderConfig- Configuration object with the following properties:registryUrl: string- Registry URL to retrieve chain registriesrestUrl: string- REST endpoint of the Initia chain to query on-chain datajsonRpcUrl?: string- JSON-RPC endpoint for EVM L2 chainstimeoutMs?: number- HTTP request timeout in milliseconds (default: 10000)
Decodes a Cosmos transaction response for L1 and Move L2 chains into a human-readable format.
Parameters:
txResponse: TxResponse- The raw transaction response from the blockchain
Returns: Promise<DecodedTx> - A promise that resolves to a decoded transaction object
Decodes a Cosmos transaction response for EVM L2 chains, processing only general message types (excludes /initia.move and /opinit prefixes).
Note: Requires providing jsonRpcUrl in TxDecoder config to resolve EVM denominations.
Parameters:
txResponse: TxResponse- The raw transaction response from the blockchain
Returns: Promise<DecodedTx> - A promise that resolves to a decoded transaction object
Decodes a Cosmos transaction response for WASM L2 chains. Balance changes are tracked from transfer events in transaction logs.
Parameters:
txResponse: TxResponse- The raw transaction response from the blockchain
Returns: Promise<DecodedTx> - A promise that resolves to a decoded transaction object
Decodes a native Ethereum RPC transaction with balance change tracking from transaction receipt logs.
Parameters:
payload: EthereumRpcPayload- Object containing:tx: EthereumTransaction- The Ethereum transaction object frometh_getTransactionByHashtxReceipt: EthereumTransactionReceipt- The transaction receipt frometh_getTransactionReceipt
Returns: Promise<DecodedEthereumTx> - A promise that resolves to a decoded Ethereum transaction object
interface DecodedTx {
messages: ProcessedMessage[];
metadata: Metadata;
totalBalanceChanges: BalanceChanges;
}interface DecodedEthereumTx {
decodedTransaction: DecodedEthereumCall;
metadata: Metadata;
totalBalanceChanges: EvmBalanceChanges;
}type DecodedEthereumCall =
| DecodedErc20ApproveCall
| DecodedErc20TransferCall
| DecodedErc20TransferFromCall
| DecodedErc721ApproveCall
| DecodedErc721SafeTransferFromCall
| DecodedNotSupportedCall;interface ProcessedMessage {
balanceChanges: BalanceChanges;
decodedMessage: DecodedMessage;
}interface BaseBalanceChanges {
ft: { [address: string]: FtChange };
}
interface MoveBalanceChanges extends BaseBalanceChanges {
vm: "move";
object: { [address: string]: ObjectChange };
}
interface EvmBalanceChanges extends BaseBalanceChanges {
vm: "evm";
nft: { [address: string]: NftChange };
}
interface WasmBalanceChanges extends BaseBalanceChanges {
vm: "wasm";
}
type BalanceChanges =
| MoveBalanceChanges
| EvmBalanceChanges
| WasmBalanceChanges;
// Type aliases
type FtChange = { [denom: string]: string };
type NftChange = { [contract: string]: { [tokenId: string]: string } };
type ObjectChange = { [address: string]: string };Note on NFT Balance Changes:
- NFT balance changes are tracked with 3-level nesting:
address β contract β tokenId β value - Each NFT transfer shows
"1"for receiving and"-1"for sending - This structure allows tracking specific NFT tokens rather than just contract-level changes
- Example:
nft["0x123..."]["0xNFTContract"]["42"] = "1"means address0x123...received NFT token #42 from contract0xNFTContract
Please see here
The decoder returns a structured object with the following format:
{
messages: [
{
balanceChanges: {
vm: "move",
ft: {
"init1...": { "uinit": "-1000000" },
"init1...": { "uinit": "1000000" }
},
object: {}
},
decodedMessage: {
action: "send",
data: {
from: "init1...",
to: "init1...",
coins: [
{
amount: "1000000",
denom: "uinit"
},
]
},
isIbc: false,
isOp: false
}
}
],
metadata: {},
totalBalanceChanges: {
vm: "move",
ft: {
"init1...": { "uinit": "-1000000" },
"init1...": { "uinit": "1000000" }
},
object: {}
}
}{
messages: [
{
balanceChanges: {
vm: "evm",
ft: {
"0x19f8a98c...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "-24400000000001" },
"0xf1829676...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "737822500000" }
},
nft: {
"0x19f8a98c...": {
"0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
"4561": "-1"
}
},
"0x8f433715...": {
"0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
"4561": "1"
}
}
}
},
decodedMessage: {
action: "not_supported",
data: {
msgType: "/minievm.evm.v1.MsgCall"
},
isIbc: false,
isOp: false
}
}
],
metadata: {},
totalBalanceChanges: {
vm: "evm",
ft: {
"0x19f8a98c...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "-24400000000001" },
"0xf1829676...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "737822500000" }
},
nft: {
"0x19f8a98c...": {
"0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
"4561": "-1"
}
},
"0x8f433715...": {
"0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
"4561": "1"
}
}
}
}
}/cosmos.bank.v1beta1.MsgSend(supported on Move, EVM, and WASM VMs)
/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward
/initia.mstaking.v1.MsgDelegate/initia.mstaking.v1.MsgUndelegate/initia.mstaking.v1.MsgBeginRedelegate
/cosmos.authz.v1beta1.MsgExec- Execute authorized messages on behalf of another account/cosmos.authz.v1beta1.MsgGrant- Grant authorization to another account/cosmos.authz.v1beta1.MsgRevoke- Revoke a previously granted authorization
/cosmos.feegrant.v1beta1.MsgGrantAllowance- Grant a fee allowance to another account/cosmos.feegrant.v1beta1.MsgRevokeAllowance- Revoke an existing fee allowance
Note: MsgExec recursively decodes the inner messages it contains. All currently supported message types can be wrapped within MsgExec and will be properly decoded.
/initia.move.v1.MsgExecute/initia.move.v1.MsgExecuteJSON
DEX & Liquidity:
0x1::dex::swap_script- Token swap0x1::dex::provide_liquidity_script- Provide liquidity0x1::dex::provide_liquidity_and_stake- Provide liquidity and stake LP tokens0x1::dex::provide_liquidity_and_stake_lock- Provide liquidity and stake-lock LP tokens0x1::dex::withdraw_liquidity_script- Withdraw liquidity0x1::dex::extend_lock- Extend LP lock period0x1::dex::merge_lock- Merge locked LP positions
Stableswap:
0x1::stableswap::swap_script- Stableswap token swap0x1::stableswap::provide- Provide liquidity to stableswap0x1::stableswap::withdraw- Withdraw liquidity from stableswap
Minitswap:
0x1::minitswap::provide- Provide liquidity to minitswap0x1::minitswap::unbond- Unbond from minitswap0x1::minitswap::withdraw_unbond- Withdraw unbonded tokens
NFT:
0x1::simple_nft::mint- Mint NFT0x1::simple_nft::burn- Burn NFT
Object Transfer:
0x1::object::transfer_call- Transfer Move objects
Lock Staking:
<module_address>::lock_staking::delegate- Delegate with lock<module_address>::lock_staking::undelegate- Undelegate locked tokens<module_address>::lock_staking::redelegate- Redelegate locked tokens<module_address>::lock_staking::withdraw_delegator_reward- Withdraw rewards from locked stake<module_address>::lock_staking::extend- Extend lock period<module_address>::lock_staking::batch_extend- Batch extend lock periods
VIP (Voting Incentive Program):
<module_address>::vip::batch_claim_user_reward_script- Claim VIP rewards<module_address>::vip::batch_lock_stake_script- Lock stake for VIP<module_address>::weight_vote::vote- Vote on gauge weights
/opinit.ophost.v1.MsgInitiateTokenDeposit- Initiate deposit from L1 to L2/opinit.ophost.v1.MsgFinalizeTokenWithdrawal- Finalize withdrawal on L1/opinit.opchild.v1.MsgFinalizeTokenDeposit- Finalize deposit on L2/opinit.opchild.v1.MsgInitiateTokenWithdrawal- Initiate withdrawal from L2 to L1
/ibc.applications.transfer.v1.MsgTransfer- Send fungible tokens via IBC/ibc.core.channel.v1.MsgRecvPacket(withtransferapplication) - Receive fungible tokens via IBC
/ibc.applications.nft_transfer.v1.MsgTransfer- Send NFTs via IBC (Move & EVM)/ibc.core.channel.v1.MsgRecvPacket(withnft-transferapplication) - Receive NFTs via IBC (Move & EVM)
The library supports decoding native Ethereum RPC transactions (via decodeEthereumTransaction):
approve(address spender, uint256 amount)- Approve ERC-20 spendingtransfer(address to, uint256 amount)- Transfer ERC-20 tokenstransferFrom(address from, address to, uint256 amount)- Transfer ERC-20 tokens from approved address
approve(address to, uint256 tokenId)- Approve NFT transfersafeTransferFrom(address from, address to, uint256 tokenId)- Safe transfer NFT
- Native ETH transfer (value transfer without function call)
- Contract creation transactions
publicMint()- Public mint function for Kami721 NFTs
The library automatically processes WASM event logs for balance tracking:
transferevent type withrecipient,sender, andamountattributes
The library automatically processes EVM event logs for balance tracking:
Transfer(address indexed from, address indexed to, uint256 value)(ERC-20)Transfer(address indexed from, address indexed to, uint256 indexed tokenId)(ERC-721)
π» Development
- Node.js >= 20
- pnpm
# Clone the repository
git clone https://github.com/initia-labs/tx-decoder.git
cd tx-decoder
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build
pnpm buildtx-decoder/
βββ src/
β βββ api/ # API client architecture
β β βββ api.ts # Main API client with service composition
β β βββ services/ # Modular API services
β β βββ base.ts # Base service with caching
β β βββ cosmos.ts # Cosmos chain API interactions
β β βββ evm.ts # EVM-specific API calls (contract detection, etc.)
β β βββ move.ts # Move VM API interactions
β βββ balance-changes.ts # Balance aggregation helpers per VM
β βββ constants/ # Application constants
β β βββ index.ts # Main constants export
β β βββ balance-changes.ts # Default balance change structures
β β βββ evm-abis.ts # EVM contract ABIs (ERC20, ERC721, etc.)
β β βββ evm-selectors.ts # EVM function selectors and event signatures
β βββ decoder.ts # Main transaction decoding logic
β βββ decoder-registry.ts # Decoder registry arrays (Cosmos EVM, Cosmos Move, Ethereum)
β βββ validation.ts # Transaction validation functions
β βββ index.ts # Entry point for exports
β βββ message-types.ts # Supported message types
β βββ metadata-resolver.ts # Resolves and fetches NFT metadata for token addresses
β βββ decoders/ # Message decoders
β β βββ cosmos/ # Cosmos SDK message decoders
β β βββ ethereum/ # Ethereum RPC transaction decoders
β β β βββ erc20/ # ERC-20 transfer and transferFrom decoders
β β βββ ibc/ # IBC message decoders
β β βββ move/ # Move message decoders
β β βββ op-init/ # OpInit message decoders
β βββ interfaces/ # TypeScript interfaces and discriminated unions
β β βββ balance-changes.ts # Balance change type definitions
β β βββ decoder.ts # Decoder interface definitions
β β βββ ethereum.ts # Ethereum transaction type definitions
β β βββ processor.ts # Event processor interfaces
β βββ processors/ # Event processors
β β βββ evm/ # ABI-driven EVM event processors
β β β βββ index.ts # EVM processor registry
β β β βββ transfer.ts # ERC-20/ERC-721 Transfer event processor
β β βββ move/ # Move event processors
β βββ schema/ # Zod schemas for validation
β β βββ common.ts # Common validation schemas
β β βββ cosmos/ # Cosmos transaction schemas
β β βββ ethereum/ # Ethereum RPC transaction schemas
β β βββ evm.ts # EVM log schemas
β βββ utils/ # Utility helpers
β β βββ index.ts # Utility exports
β β βββ denom.ts # Denomination handling utilities
β β βββ merge-balances.ts # Balance merging logic (supports 3-level NFT nesting)
β βββ tests/ # Jest unit tests grouped by domain
β βββ _shared/ # Shared test utilities and helpers
β βββ common/ # Common functionality tests
β βββ cosmos/ # Cosmos message decoder tests
β βββ ethereum/ # Ethereum RPC transaction tests
β β βββ erc20/ # ERC-20 transfer and transferFrom tests
β βββ protocols/ # Protocol-specific tests (IBC, OpInit)
β βββ utils/ # Utility function tests
βββ package.json # Project metadata and dependencies
βββ README.md # Project documentation
βββ ... # Config and other files
See CONTRIBUTING.md for development setup and guidelines.
π License
MIT