Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/.misc/dev-guides/explorer/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# TON Explorer Architecture

This document describes the explorer command architecture in `pkg/ton/codec/debug/explorer`.

## Overview

The explorer flow is organized into five stages:

1. **CLI input normalization**
2. **Network/API connection setup**
3. **Transaction and trace discovery**
4. **Trace enrichment (actors/contracts)**
5. **Rendering/output**

## Module layout

- `explorer.go`: command wiring, `client` lifecycle, trace orchestration, actor discovery.
- `cli_args.go`: positional argument and URL/hash parsing integration (`parseCLIInput`).
- `utils.go`: explorer URL parsing (`ParseURL`).
- `format.go`: visualization format validation (`parseFormat`).
- `network_connect.go`: TON connection bootstrap (`connect`).
- `network_mylocalton.go`: Docker inspection helpers for `mylocalton`.
- `tx_lookup.go`: tx hash decoding, toncenter metadata lookups, tx search/fallback logic.
- `browser.go`: OS-specific browser opening for sequence URL mode.

## Request lifecycle

`GenerateExplorerCmd` parses args and flags, creates a `client` with `Connect`, and runs `PrintTrace`.

`PrintTrace` performs:

1. Resolve root tx hash from toncenter when supported (`mainnet`/`testnet`).
2. Resolve sender address:
- from user input when provided,
- from toncenter when hash-only mode is used on supported networks.
3. Locate transaction from account history (paged liteclient scan), with toncenter metadata fallback when available.
4. Convert transaction to trace root (`tracetracking.MapToReceivedMessage`) and wait for full trace (`WaitForTrace`).
5. Query contract actors via `typeAndVersion` getter.
6. Render either tree or sequence output.
7. For sequence URL mode, open Mermaid URL in browser.

## Toncenter behavior

Toncenter is treated as an optional dependency by network:

- `mainnet`/`testnet`: toncenter is used for trace-root and tx metadata resolution.
- `mylocalton` or custom config URL networks: toncenter fallback is unavailable.
- Hash-only mode requires explicit source address in these environments.

## Extension points

For maintainability, keep future changes aligned with existing seams:

- Input parsing changes in `cli_args.go`/`utils.go`.
- New visualization output options in `format.go` + rendering branch in `PrintTrace`.
- Network-specific bootstrap logic in `network_connect.go`.
- External metadata providers in `tx_lookup.go`.
- Browser side-effects in `browser.go`.

## Compatibility contract

Current CLI contract intentionally remains:

- `explorer <url>`
- `explorer <tx-hash> <address>`
- `explorer <tx-hash>` (works when address can be resolved via toncenter)

`--address` and `--tx` flags were removed because they were unused and misleading.
6 changes: 4 additions & 2 deletions docs/.misc/dev-guides/explorer/development.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# TON Explorer Development Guide

For adding support to more contracts, you need to register your decoder in [`defaultDecoders`](../../../../pkg/ton/debug/pretty_print.go). Decoders implement [`ContractDecoder`](../../../../pkg/ton/debug/lib/lib.go) interface:
For explorer architecture and module boundaries, read [TON Explorer Architecture](./architecture.md).

For adding support to more contracts, you need to register your decoder in [`defaultDecoders`](../../../../pkg/ton/codec/debug/pretty_print.go). Decoders implement [`ContractDecoder`](../../../../pkg/ton/codec/debug/lib/lib.go) interface:

```go
type ContractDecoder interface {
Expand Down Expand Up @@ -30,7 +32,7 @@ type BodyInfo interface {
}
```

Your decoder should go in `pkg/ton/debug/decoders/<domain>` package. If it is a ccip contract, then in `pkg/ton/debug/decoders/ccip`. E.g. `pkg/ton/debug/decoders/ccip/feequoter/feequoter.go`.
Your decoder should go in `pkg/ton/codec/debug/decoders/<domain>` package. If it is a ccip contract, then in `pkg/ton/codec/debug/decoders/ccip`. E.g. `pkg/ton/codec/debug/decoders/ccip/feequoter/feequoter.go`.

I suggest not placing any business logic in the decoder. Instead, create a separate package for that, e.g. `pkg/ccip/bindings/feequoter/codec.go` and use it from the decoder.

Expand Down
8 changes: 6 additions & 2 deletions docs/.misc/dev-guides/explorer/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

Command-line tool for analyzing TON blockchain transactions and traces.

Read [TON Explorer Architecture](./architecture.md) for internal module layout and execution flow.

## Usage

Three ways to run:

1. **URL**: `./explorer <tonscan-url>`
2. **Hash + Address**: `./explorer <tx-hash> <address>`
3. **Hash only**: `./explorer <tx-hash>` (testnet/mainnet only)
3. **Hash only**: `./explorer <tx-hash>` (testnet/mainnet only unless sender address is provided separately)

## Run with Nix

Expand Down Expand Up @@ -37,7 +39,7 @@ go build
# Hash + address
./explorer <tx-hash> <address> [--net testnet|mainnet|mylocalton|http://custom-domain/global.config.json]

# Hash only (auto-resolves address)
# Hash only (auto-resolves address via toncenter on testnet/mainnet)
./explorer <tx-hash> [--net testnet|mainnet|mylocalton|http://custom-domain/global.config.json]
```

Expand Down Expand Up @@ -72,6 +74,8 @@ Display message trace as a tree structure with `--visualization tree`.
--page-size 10 --max-pages 10 # Control transaction search pagination
```

Note: `--address` and `--tx` flags are not supported; use positional arguments.

## Environment injection

The same cli is exposed in [chainlink-deployments's repo](https://github.com/smartcontractkit/chainlink-deployments/tree/main/domains/ccip/cmd) which injects contract metadata from the DataStore.
Expand Down
3 changes: 3 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

- [Nix - Getting Started](.misc/dev-guides/nix/getting-started.md)
- [Nix - Builds](.misc/dev-guides/nix/builds.md)
- [Explorer - Usage](.misc/dev-guides/explorer/usage.md)
- [Explorer - Architecture](.misc/dev-guides/explorer/architecture.md)
- [Explorer - Development](.misc/dev-guides/explorer/development.md)

## CCIP Product E2E Tests

Expand Down
27 changes: 27 additions & 0 deletions pkg/ton/codec/debug/explorer/browser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package explorer

import (
"context"
"errors"
"os/exec"
"runtime"
"strings"
)

func openInBrowser(ctx context.Context, targetURL string) error {
if strings.TrimSpace(targetURL) == "" {
return errors.New("empty url")
}

var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.CommandContext(ctx, "open", targetURL)
case "windows":
cmd = exec.CommandContext(ctx, "rundll32", "url.dll,FileProtocolHandler", targetURL)
default:
cmd = exec.CommandContext(ctx, "xdg-open", targetURL)
}

return cmd.Start()
}
44 changes: 44 additions & 0 deletions pkg/ton/codec/debug/explorer/cli_args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package explorer

import (
"encoding/hex"
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"
)

type cliInput struct {
txHash string
address string
net string
}

func parseCLIInput(cmd *cobra.Command, args []string) (cliInput, error) {
urlOrTx := args[0]
txHash, address, parsedNet, parseURLErr := ParseURL(urlOrTx)
if parseURLErr == nil {
if cmd.Flags().Changed("net") {
return cliInput{}, errors.New("cannot specify network flag when using URL")
}
if len(args) == 2 {
address = args[1]
}
return cliInput{txHash: txHash, address: address, net: parsedNet}, nil
}

if len(urlOrTx) != 64 && (len(urlOrTx) != 66 || !strings.HasPrefix(urlOrTx, "0x")) {
return cliInput{}, fmt.Errorf("failed to parse URL: %w", parseURLErr)
}

if _, err := hex.DecodeString(strings.TrimPrefix(urlOrTx, "0x")); err != nil {
return cliInput{}, fmt.Errorf("invalid transaction hash or url: %w", err)
}

if len(args) == 2 {
address = args[1]
}

return cliInput{txHash: urlOrTx, address: address}, nil
}
Loading
Loading