@wonjm/tx-decoder-rc 0.12.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,569 @@
1
+ # Transaction Decoder
2
+
3
+ [![npm version](https://badge.fury.io/js/%40initia%2Ftx-decoder.svg)](https://badge.fury.io/js/%40initia%2Ftx-decoder)
4
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@initia/tx-decoder)](https://bundlephobia.com/package/@initia/tx-decoder)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@initia/tx-decoder)](https://www.npmjs.com/package/@initia/tx-decoder)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
+ [![Unit Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/initia-labs/tx-decoder/actions)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ A TypeScript library for decoding Cosmos SDK transactions, providing human-readable message decoding and balance change tracking.
11
+
12
+ ## Table of Contents
13
+
14
+ - [Features](#-features)
15
+ - [Installation](#-installation)
16
+ - [Quick Start](#-quick-start)
17
+ - [API Reference](#-api-reference)
18
+ - [Supported Message Types](#-supported-message-types)
19
+ - [Development](#-development)
20
+ - [License](#-license)
21
+
22
+ ## ✨ [Features](#-features)
23
+
24
+ - **Human-Readable Output**: Decodes Cosmos SDK transaction messages into a clear, human-readable JSON format.
25
+ - **Multi-VM Support**: Full support for Move VM, EVM, and WASM virtual machines with VM-specific balance tracking.
26
+ - **Balance Tracking**: Tracks fungible token deltas and correlates Move objects or EVM NFTs with specific tokenId tracking based on the originating VM.
27
+ - **Type-Safe**: Built with TypeScript and validated with Zod for robust, type-safe operations.
28
+ - **Extensible**: Features a flexible handler system that can be easily extended to support new message types.
29
+ - **Immutable State**: Uses Immer for safe and predictable state management.
30
+ - **ABI-driven EVM Support**: Uses `viem` to decode EVM event logs (ERC-20 and ERC-721 `Transfer` events) without relying on Cosmos `coin_spent` events.
31
+
32
+ ## 📦 [Installation](#-installation)
33
+
34
+ ```bash
35
+ # npm
36
+ npm install @initia/tx-decoder
37
+
38
+ # yarn
39
+ yarn add @initia/tx-decoder
40
+
41
+ # pnpm
42
+ pnpm add @initia/tx-decoder
43
+
44
+ # bun
45
+ bun add @initia/tx-decoder
46
+ ```
47
+
48
+ ## 🚀 [Quick Start](#-quick-start)
49
+
50
+ ```typescript
51
+ import { TxDecoder } from "@initia/tx-decoder";
52
+
53
+ const decoder = new TxDecoder({
54
+ registryUrl: "https://registry.initia.xyz/",
55
+ restUrl: "https://rest.initia.xyz"
56
+ });
57
+
58
+ // Decode a Cosmos transaction for L1 and Move L2
59
+ const decodedTx = await decoder.decodeCosmosTransaction(txResponse);
60
+ console.log(decodedTx);
61
+
62
+ // Decode a Cosmos transaction for EVM L2
63
+ const decodedEvmTx = await decoder.decodeCosmosEvmTransaction(txResponse);
64
+ console.log(decodedEvmTx);
65
+
66
+ // Decode a Cosmos transaction for WASM L2
67
+ const decodedWasmTx = await decoder.decodeCosmosWasmTransaction(txResponse);
68
+ console.log(decodedWasmTx);
69
+
70
+ // Decode a native Ethereum RPC transaction
71
+ const ethereumTx = await decoder.decodeEthereumTransaction({
72
+ tx: ethereumTransaction,
73
+ txReceipt: ethereumTransactionReceipt
74
+ });
75
+ console.log(ethereumTx);
76
+ ```
77
+
78
+ 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.
79
+
80
+ ## 📖 [API Reference](#-api-reference)
81
+
82
+ ### `TxDecoder`
83
+
84
+ The main class for decoding transactions.
85
+
86
+ #### Constructor
87
+
88
+ ```typescript
89
+ new TxDecoder(config: DecoderConfig)
90
+ ```
91
+
92
+ **Parameters:**
93
+
94
+ - `config: DecoderConfig` - Configuration object with the following properties:
95
+ - `registryUrl: string` - Registry URL to retrieve chain registries
96
+ - `restUrl: string` - REST endpoint of the Initia chain to query on-chain data
97
+ - `jsonRpcUrl?: string` - JSON-RPC endpoint for EVM L2 chains
98
+ - `timeoutMs?: number` - HTTP request timeout in milliseconds (default: 10000)
99
+
100
+ #### Methods
101
+
102
+ ##### `decodeCosmosTransaction(txResponse: TxResponse): Promise<DecodedTx>`
103
+
104
+ Decodes a Cosmos transaction response for L1 and Move L2 chains into a human-readable format.
105
+
106
+ **Parameters:**
107
+
108
+ - `txResponse: TxResponse` - The raw transaction response from the blockchain
109
+
110
+ **Returns:** `Promise<DecodedTx>` - A promise that resolves to a decoded transaction object
111
+
112
+ ##### `decodeCosmosEvmTransaction(txResponse: TxResponse): Promise<DecodedTx>`
113
+
114
+ Decodes a Cosmos transaction response for EVM L2 chains, processing only general message types (excludes `/initia.move` and `/opinit` prefixes).
115
+
116
+ **Note:** Requires providing `jsonRpcUrl` in `TxDecoder` config to resolve EVM denominations.
117
+
118
+ **Parameters:**
119
+
120
+ - `txResponse: TxResponse` - The raw transaction response from the blockchain
121
+
122
+ **Returns:** `Promise<DecodedTx>` - A promise that resolves to a decoded transaction object
123
+
124
+ ##### `decodeCosmosWasmTransaction(txResponse: TxResponse): Promise<DecodedTx>`
125
+
126
+ Decodes a Cosmos transaction response for WASM L2 chains. Balance changes are tracked from `transfer` events in transaction logs.
127
+
128
+ **Parameters:**
129
+
130
+ - `txResponse: TxResponse` - The raw transaction response from the blockchain
131
+
132
+ **Returns:** `Promise<DecodedTx>` - A promise that resolves to a decoded transaction object
133
+
134
+ ##### `decodeEthereumTransaction(payload: EthereumRpcPayload): Promise<DecodedEthereumTx>`
135
+
136
+ Decodes a native Ethereum RPC transaction with balance change tracking from transaction receipt logs.
137
+
138
+ **Parameters:**
139
+
140
+ - `payload: EthereumRpcPayload` - Object containing:
141
+ - `tx: EthereumTransaction` - The Ethereum transaction object from `eth_getTransactionByHash`
142
+ - `txReceipt: EthereumTransactionReceipt` - The transaction receipt from `eth_getTransactionReceipt`
143
+
144
+ **Returns:** `Promise<DecodedEthereumTx>` - A promise that resolves to a decoded Ethereum transaction object
145
+
146
+ ### Type Definitions
147
+
148
+ #### `DecodedTx` (Cosmos Transactions)
149
+
150
+ ```typescript
151
+ interface DecodedTx {
152
+ messages: ProcessedMessage[];
153
+ metadata: Metadata;
154
+ totalBalanceChanges: BalanceChanges;
155
+ }
156
+ ```
157
+
158
+ #### `DecodedEthereumTx` (Ethereum RPC Transactions)
159
+
160
+ ```typescript
161
+ interface DecodedEthereumTx {
162
+ decodedTransaction: DecodedEthereumCall;
163
+ metadata: Metadata;
164
+ totalBalanceChanges: EvmBalanceChanges;
165
+ }
166
+ ```
167
+
168
+ #### `DecodedEthereumCall`
169
+
170
+ ```typescript
171
+ type DecodedEthereumCall =
172
+ | DecodedErc20ApproveCall
173
+ | DecodedErc20TransferCall
174
+ | DecodedErc20TransferFromCall
175
+ | DecodedErc721ApproveCall
176
+ | DecodedErc721SafeTransferFromCall
177
+ | DecodedNotSupportedCall;
178
+ ```
179
+
180
+ #### `ProcessedMessage`
181
+
182
+ ```typescript
183
+ interface ProcessedMessage {
184
+ balanceChanges: BalanceChanges;
185
+ decodedMessage: DecodedMessage;
186
+ }
187
+ ```
188
+
189
+ #### `BalanceChanges`
190
+
191
+ ```typescript
192
+ interface BaseBalanceChanges {
193
+ ft: { [address: string]: FtChange };
194
+ }
195
+
196
+ interface MoveBalanceChanges extends BaseBalanceChanges {
197
+ vm: "move";
198
+ object: { [address: string]: ObjectChange };
199
+ }
200
+
201
+ interface EvmBalanceChanges extends BaseBalanceChanges {
202
+ vm: "evm";
203
+ nft: { [address: string]: NftChange };
204
+ }
205
+
206
+ interface WasmBalanceChanges extends BaseBalanceChanges {
207
+ vm: "wasm";
208
+ }
209
+
210
+ type BalanceChanges =
211
+ | MoveBalanceChanges
212
+ | EvmBalanceChanges
213
+ | WasmBalanceChanges;
214
+
215
+ // Type aliases
216
+ type FtChange = { [denom: string]: string };
217
+ type NftChange = { [contract: string]: { [tokenId: string]: string } };
218
+ type ObjectChange = { [address: string]: string };
219
+ ```
220
+
221
+ **Note on NFT Balance Changes:**
222
+
223
+ - NFT balance changes are tracked with 3-level nesting: `address → contract → tokenId → value`
224
+ - Each NFT transfer shows `"1"` for receiving and `"-1"` for sending
225
+ - This structure allows tracking specific NFT tokens rather than just contract-level changes
226
+ - Example: `nft["0x123..."]["0xNFTContract"]["42"] = "1"` means address `0x123...` received NFT token #42 from contract `0xNFTContract`
227
+
228
+ #### `DecodedMessage`
229
+
230
+ Please see [here](src/interfaces/decoded-messages.ts)
231
+
232
+ ### Response Structure
233
+
234
+ The decoder returns a structured object with the following format:
235
+
236
+ #### Move VM Example (Bank Send)
237
+
238
+ ```typescript
239
+ {
240
+ messages: [
241
+ {
242
+ balanceChanges: {
243
+ vm: "move",
244
+ ft: {
245
+ "init1...": { "uinit": "-1000000" },
246
+ "init1...": { "uinit": "1000000" }
247
+ },
248
+ object: {}
249
+ },
250
+ decodedMessage: {
251
+ action: "send",
252
+ data: {
253
+ from: "init1...",
254
+ to: "init1...",
255
+ coins: [
256
+ {
257
+ amount: "1000000",
258
+ denom: "uinit"
259
+ },
260
+ ]
261
+ },
262
+ isIbc: false,
263
+ isOp: false
264
+ }
265
+ }
266
+ ],
267
+ metadata: {},
268
+ totalBalanceChanges: {
269
+ vm: "move",
270
+ ft: {
271
+ "init1...": { "uinit": "-1000000" },
272
+ "init1...": { "uinit": "1000000" }
273
+ },
274
+ object: {}
275
+ }
276
+ }
277
+ ```
278
+
279
+ #### EVM Example (NFT Transfer)
280
+
281
+ ```typescript
282
+ {
283
+ messages: [
284
+ {
285
+ balanceChanges: {
286
+ vm: "evm",
287
+ ft: {
288
+ "0x19f8a98c...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "-24400000000001" },
289
+ "0xf1829676...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "737822500000" }
290
+ },
291
+ nft: {
292
+ "0x19f8a98c...": {
293
+ "0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
294
+ "4561": "-1"
295
+ }
296
+ },
297
+ "0x8f433715...": {
298
+ "0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
299
+ "4561": "1"
300
+ }
301
+ }
302
+ }
303
+ },
304
+ decodedMessage: {
305
+ action: "not_supported",
306
+ data: {
307
+ msgType: "/minievm.evm.v1.MsgCall"
308
+ },
309
+ isIbc: false,
310
+ isOp: false
311
+ }
312
+ }
313
+ ],
314
+ metadata: {},
315
+ totalBalanceChanges: {
316
+ vm: "evm",
317
+ ft: {
318
+ "0x19f8a98c...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "-24400000000001" },
319
+ "0xf1829676...": { "evm/E1Ff7038eAAAF027031688E1535a055B2Bac2546": "737822500000" }
320
+ },
321
+ nft: {
322
+ "0x19f8a98c...": {
323
+ "0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
324
+ "4561": "-1"
325
+ }
326
+ },
327
+ "0x8f433715...": {
328
+ "0x5d4376b62fa8AC16dFabe6a9861E11c33A48C677": {
329
+ "4561": "1"
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+ ```
336
+
337
+ ## 📝 [Supported Message Types](#-supported-message-types)
338
+
339
+ ### Cosmos Messages
340
+
341
+ #### Bank Messages
342
+
343
+ - `/cosmos.bank.v1beta1.MsgSend` (supported on Move, EVM, and WASM VMs)
344
+
345
+ #### Distribution Messages
346
+
347
+ - `/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward`
348
+
349
+ #### Staking Messages (Multi-Staking)
350
+
351
+ - `/initia.mstaking.v1.MsgDelegate`
352
+ - `/initia.mstaking.v1.MsgUndelegate`
353
+ - `/initia.mstaking.v1.MsgBeginRedelegate`
354
+
355
+ #### Authz Messages
356
+
357
+ - `/cosmos.authz.v1beta1.MsgExec` - Execute authorized messages on behalf of another account
358
+ - `/cosmos.authz.v1beta1.MsgGrant` - Grant authorization to another account
359
+ - `/cosmos.authz.v1beta1.MsgRevoke` - Revoke a previously granted authorization
360
+
361
+ #### Feegrant Messages
362
+
363
+ - `/cosmos.feegrant.v1beta1.MsgGrantAllowance` - Grant a fee allowance to another account
364
+ - `/cosmos.feegrant.v1beta1.MsgRevokeAllowance` - Revoke an existing fee allowance
365
+
366
+ **Note:** MsgExec recursively decodes the inner messages it contains. All currently supported message types can be wrapped within MsgExec and will be properly decoded.
367
+
368
+ ### Move VM Messages
369
+
370
+ - `/initia.move.v1.MsgExecute`
371
+ - `/initia.move.v1.MsgExecuteJSON`
372
+
373
+ #### Supported Move Scripts
374
+
375
+ **DEX & Liquidity:**
376
+
377
+ - `0x1::dex::swap_script` - Token swap
378
+ - `0x1::dex::provide_liquidity_script` - Provide liquidity
379
+ - `0x1::dex::provide_liquidity_and_stake` - Provide liquidity and stake LP tokens
380
+ - `0x1::dex::provide_liquidity_and_stake_lock` - Provide liquidity and stake-lock LP tokens
381
+ - `0x1::dex::withdraw_liquidity_script` - Withdraw liquidity
382
+ - `0x1::dex::extend_lock` - Extend LP lock period
383
+ - `0x1::dex::merge_lock` - Merge locked LP positions
384
+
385
+ **Stableswap:**
386
+
387
+ - `0x1::stableswap::swap_script` - Stableswap token swap
388
+ - `0x1::stableswap::provide` - Provide liquidity to stableswap
389
+ - `0x1::stableswap::withdraw` - Withdraw liquidity from stableswap
390
+
391
+ **Minitswap:**
392
+
393
+ - `0x1::minitswap::provide` - Provide liquidity to minitswap
394
+ - `0x1::minitswap::unbond` - Unbond from minitswap
395
+ - `0x1::minitswap::withdraw_unbond` - Withdraw unbonded tokens
396
+
397
+ **NFT:**
398
+
399
+ - `0x1::simple_nft::mint` - Mint NFT
400
+ - `0x1::simple_nft::burn` - Burn NFT
401
+
402
+ **Object Transfer:**
403
+
404
+ - `0x1::object::transfer_call` - Transfer Move objects
405
+
406
+ **Lock Staking:**
407
+
408
+ - `<module_address>::lock_staking::delegate` - Delegate with lock
409
+ - `<module_address>::lock_staking::undelegate` - Undelegate locked tokens
410
+ - `<module_address>::lock_staking::redelegate` - Redelegate locked tokens
411
+ - `<module_address>::lock_staking::withdraw_delegator_reward` - Withdraw rewards from locked stake
412
+ - `<module_address>::lock_staking::extend` - Extend lock period
413
+ - `<module_address>::lock_staking::batch_extend` - Batch extend lock periods
414
+
415
+ **VIP (Voting Incentive Program):**
416
+
417
+ - `<module_address>::vip::batch_claim_user_reward_script` - Claim VIP rewards
418
+ - `<module_address>::vip::batch_lock_stake_script` - Lock stake for VIP
419
+ - `<module_address>::weight_vote::vote` - Vote on gauge weights
420
+
421
+ ### OpInit Messages (L1 ↔ L2 Bridge)
422
+
423
+ - `/opinit.ophost.v1.MsgInitiateTokenDeposit` - Initiate deposit from L1 to L2
424
+ - `/opinit.ophost.v1.MsgFinalizeTokenWithdrawal` - Finalize withdrawal on L1
425
+ - `/opinit.opchild.v1.MsgFinalizeTokenDeposit` - Finalize deposit on L2
426
+ - `/opinit.opchild.v1.MsgInitiateTokenWithdrawal` - Initiate withdrawal from L2 to L1
427
+
428
+ ### IBC Messages
429
+
430
+ #### IBC Fungible Token Transfer
431
+
432
+ - `/ibc.applications.transfer.v1.MsgTransfer` - Send fungible tokens via IBC
433
+ - `/ibc.core.channel.v1.MsgRecvPacket` (with `transfer` application) - Receive fungible tokens via IBC
434
+
435
+ #### IBC Non-Fungible Token Transfer
436
+
437
+ - `/ibc.applications.nft_transfer.v1.MsgTransfer` - Send NFTs via IBC (Move & EVM)
438
+ - `/ibc.core.channel.v1.MsgRecvPacket` (with `nft-transfer` application) - Receive NFTs via IBC (Move & EVM)
439
+
440
+ ### Ethereum RPC Transactions
441
+
442
+ The library supports decoding native Ethereum RPC transactions (via `decodeEthereumTransaction`):
443
+
444
+ #### ERC-20 Functions
445
+
446
+ - `approve(address spender, uint256 amount)` - Approve ERC-20 spending
447
+ - `transfer(address to, uint256 amount)` - Transfer ERC-20 tokens
448
+ - `transferFrom(address from, address to, uint256 amount)` - Transfer ERC-20 tokens from approved address
449
+
450
+ #### ERC-721 Functions
451
+
452
+ - `approve(address to, uint256 tokenId)` - Approve NFT transfer
453
+ - `safeTransferFrom(address from, address to, uint256 tokenId)` - Safe transfer NFT
454
+
455
+ #### Native ETH Transfer
456
+
457
+ - Native ETH transfer (value transfer without function call)
458
+
459
+ #### Contract Deployment
460
+
461
+ - Contract creation transactions
462
+
463
+ #### Kami721 NFT
464
+
465
+ - `publicMint()` - Public mint function for Kami721 NFTs
466
+
467
+ ### WASM Event Processing
468
+
469
+ The library automatically processes WASM event logs for balance tracking:
470
+
471
+ - `transfer` event type with `recipient`, `sender`, and `amount` attributes
472
+
473
+ ### EVM Event Processing
474
+
475
+ The library automatically processes EVM event logs for balance tracking:
476
+
477
+ - `Transfer(address indexed from, address indexed to, uint256 value)` (ERC-20)
478
+ - `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` (ERC-721)
479
+
480
+ ## 💻 [Development](#-development)
481
+
482
+ ### Prerequisites
483
+
484
+ - Node.js >= 20
485
+ - pnpm
486
+
487
+ ```bash
488
+ # Clone the repository
489
+ git clone https://github.com/initia-labs/tx-decoder.git
490
+ cd tx-decoder
491
+
492
+ # Install dependencies
493
+ pnpm install
494
+
495
+ # Run tests
496
+ pnpm test
497
+
498
+ # Build
499
+ pnpm build
500
+ ```
501
+
502
+ ## 📁 Folder Structure
503
+
504
+ ```
505
+ tx-decoder/
506
+ ├── src/
507
+ │ ├── api/ # API client architecture
508
+ │ │ ├── api.ts # Main API client with service composition
509
+ │ │ └── services/ # Modular API services
510
+ │ │ ├── base.ts # Base service with caching
511
+ │ │ ├── cosmos.ts # Cosmos chain API interactions
512
+ │ │ ├── evm.ts # EVM-specific API calls (contract detection, etc.)
513
+ │ │ └── move.ts # Move VM API interactions
514
+ │ ├── balance-changes.ts # Balance aggregation helpers per VM
515
+ │ ├── constants/ # Application constants
516
+ │ │ ├── index.ts # Main constants export
517
+ │ │ ├── balance-changes.ts # Default balance change structures
518
+ │ │ ├── evm-abis.ts # EVM contract ABIs (ERC20, ERC721, etc.)
519
+ │ │ └── evm-selectors.ts # EVM function selectors and event signatures
520
+ │ ├── decoder.ts # Main transaction decoding logic
521
+ │ ├── decoder-registry.ts # Decoder registry arrays (Cosmos EVM, Cosmos Move, Ethereum)
522
+ │ ├── validation.ts # Transaction validation functions
523
+ │ ├── index.ts # Entry point for exports
524
+ │ ├── message-types.ts # Supported message types
525
+ │ ├── metadata-resolver.ts # Resolves and fetches NFT metadata for token addresses
526
+ │ ├── decoders/ # Message decoders
527
+ │ │ ├── cosmos/ # Cosmos SDK message decoders
528
+ │ │ ├── ethereum/ # Ethereum RPC transaction decoders
529
+ │ │ │ └── erc20/ # ERC-20 transfer and transferFrom decoders
530
+ │ │ ├── ibc/ # IBC message decoders
531
+ │ │ ├── move/ # Move message decoders
532
+ │ │ └── op-init/ # OpInit message decoders
533
+ │ ├── interfaces/ # TypeScript interfaces and discriminated unions
534
+ │ │ ├── balance-changes.ts # Balance change type definitions
535
+ │ │ ├── decoder.ts # Decoder interface definitions
536
+ │ │ ├── ethereum.ts # Ethereum transaction type definitions
537
+ │ │ └── processor.ts # Event processor interfaces
538
+ │ ├── processors/ # Event processors
539
+ │ │ ├── evm/ # ABI-driven EVM event processors
540
+ │ │ │ ├── index.ts # EVM processor registry
541
+ │ │ │ └── transfer.ts # ERC-20/ERC-721 Transfer event processor
542
+ │ │ └── move/ # Move event processors
543
+ │ ├── schema/ # Zod schemas for validation
544
+ │ │ ├── common.ts # Common validation schemas
545
+ │ │ ├── cosmos/ # Cosmos transaction schemas
546
+ │ │ ├── ethereum/ # Ethereum RPC transaction schemas
547
+ │ │ └── evm.ts # EVM log schemas
548
+ │ ├── utils/ # Utility helpers
549
+ │ │ ├── index.ts # Utility exports
550
+ │ │ ├── denom.ts # Denomination handling utilities
551
+ │ │ └── merge-balances.ts # Balance merging logic (supports 3-level NFT nesting)
552
+ │ └── tests/ # Jest unit tests grouped by domain
553
+ │ ├── _shared/ # Shared test utilities and helpers
554
+ │ ├── common/ # Common functionality tests
555
+ │ ├── cosmos/ # Cosmos message decoder tests
556
+ │ ├── ethereum/ # Ethereum RPC transaction tests
557
+ │ │ └── erc20/ # ERC-20 transfer and transferFrom tests
558
+ │ ├── protocols/ # Protocol-specific tests (IBC, OpInit)
559
+ │ └── utils/ # Utility function tests
560
+ ├── package.json # Project metadata and dependencies
561
+ ├── README.md # Project documentation
562
+ └── ... # Config and other files
563
+ ```
564
+
565
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
566
+
567
+ ## 📄 [License](#-license)
568
+
569
+ MIT