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

@scallop-io/sui-move-bytecode-verifier

v0.1.0

Published

Tool to verify Sui Move on-chain packages

Readme

Sui Move Bytecode Verifier

A CLI tool to verify Sui Move bytecodes across different sources: local directories, GitHub repositories, on-chain packages, and publish/upgrade transactions. It compiles source, fetches bytecodes, and compares them byte-for-byte.

Prerequisites

  • Node.js >= 18
  • Sui CLI installed (install guide)
  • Git
  • (Optional) suiup for automatic compiler version switching

Setup

git clone <this-repo>
cd contract-verifier
npm install

Build (optional)

npm run build

This produces dist/index.js which can be run directly or linked globally:

npm link
sui-move-bytecode-verifier --help

Usage

The verify command compares bytecodes from any two sources. Specify exactly two of: --local, --repo, --on-chain, --tx-bytes.

1. Local vs On-chain

Check if your local Move source code matches an on-chain package:

npx tsx src/index.ts verify \
  --local ./my-package \
  --on-chain 0x1234...abcd \
  --network mainnet

2. Local vs GitHub

Check if your local code matches a specific version in a GitHub repo:

npx tsx src/index.ts verify \
  --local ./my-package \
  --repo https://github.com/example-org/protocol \
  --ref v1.0.0

3. GitHub vs On-chain

Verify that a GitHub repo's source matches the on-chain deployed package:

npx tsx src/index.ts verify \
  --repo https://github.com/example-org/protocol \
  --ref v1.0.0 \
  --on-chain 0x1234...abcd \
  --network mainnet

4. Transaction bytes vs GitHub

Verify that the module bytes in a publish/upgrade transaction match a GitHub repo. This lets you check what you're about to sign before submitting:

# From a base64 file (e.g. saved from sui client or SDK)
npx tsx src/index.ts verify \
  --tx-bytes ./unsigned-tx.base64 \
  --repo https://github.com/example-org/protocol \
  --ref v1.0.0

# Or pass base64 inline
npx tsx src/index.ts verify \
  --tx-bytes "AAAC..." \
  --repo https://github.com/example-org/protocol \
  --ref v1.0.0

The --tx-bytes flag accepts either a file path (containing base64-encoded transaction bytes) or an inline base64 string. The tool parses the BCS-encoded TransactionData, finds the Publish or Upgrade command, and extracts the module bytecodes for comparison.

Source flags

| Flag | Description | |---|---| | --local <path> | Local Move package directory | | -r, --repo <url> | GitHub repository URL (use with --ref and optionally --path) | | --on-chain <id> | On-chain Sui package object ID (0x...) | | --tx-bytes <base64\|file> | Transaction bytes: inline base64 or path to a file containing base64 |

Other options

| Option | Description | Default | |---|---|---| | --ref <ref> | Git ref for --repo: branch, tag, or commit hash | main | | --path <path> | Path to Move package within repo | auto-detect | | -n, --network <network> | mainnet, testnet, or devnet | mainnet | | --rpc-url <url> | Custom RPC endpoint (overrides --network) | | | --sui-version <version> | Override Sui CLI version for compilation | from Move.lock | | --no-version-check | Skip compiler version matching | | | -o, --output <format> | table or json | table | | -v, --verbose | Show build logs and byte diff details | | | --no-cache | Skip cache, always re-clone and re-build | | | --cache-dir <dir> | Custom cache directory | ~/.cache/sui-verify |

Batch verification

Verify multiple packages at once using a YAML config file:

npx tsx src/index.ts verify-batch --config packages.yaml

packages.yaml:

network: mainnet
packages:
  - id: "0x1234...abcd"
    repo: "https://github.com/example-org/protocol"
    ref: "v1.0.0"
    path: "contracts/core"
  - id: "0x5678...ef01"
    repo: "https://github.com/example-org/token"
    ref: "main"

Cache management

Cloned repositories are cached to speed up repeated verifications. Cached repos automatically expire after 7 days — on the next run, expired caches are purged and the repo is re-cloned fresh. On-chain bytecode is cached indefinitely since Sui packages are immutable.

Use --no-cache to bypass the cache entirely for a single run.

# Show cache size and location
npx tsx src/index.ts cache info

# Clear all cached data
npx tsx src/index.ts cache clean

How it works

Overview

The tool resolves each of the two sources into a set of module bytecodes, then compares them:

| Source | How it resolves | |---|---| | --local | Builds the local Move package via sui move build, collects .mv files | | --repo | Clones the GitHub repo at the specified ref, then builds like --local | | --on-chain | Fetches the package object from Sui RPC, extracts each module's bytecode | | --tx-bytes | Parses BCS transaction bytes locally, finds the Publish/Upgrade command, extracts module bytes |

When one of the sources is --on-chain, address substitution is applied: the compiled bytecode's zero-address placeholder is replaced with the real package address before comparison (matching Sui CLI's verify-source behavior).

Steps for a typical GitHub-vs-on-chain verification:

  1. Fetch on-chain bytecode via Sui RPC
  2. Clone repo at the specified ref (cached, 7-day TTL)
  3. Detect compiler version from Move.lock, switch via suiup if needed
  4. Prepare source — rewrite self-address to 0x0 in Move.toml
  5. Build via sui move build, collect .mv files
  6. Address substitution & compare — substitute real address into compiled bytecode's address_identifiers table, compare byte-for-byte
  7. Report with per-module match/mismatch classification

How bytecode comparison works (in detail)

The comparison is not just a size check — it is a full byte-for-byte comparison of the entire bytecode content for every module. The approach follows the same logic as the official Sui CLI's verify-source command. Here is exactly what happens:

The address problem

When you compile a Move package locally, the self-address in Move.toml is set to "0x0". This means the compiled .mv bytecode files contain 0x0000...0000 (32 zero bytes) in the module's address_identifiers table where the package references its own address.

When that same package is published on-chain, Sui replaces the zero-address placeholder with the actual package object ID. So the on-chain bytecode contains the real address (e.g. 0xabcd...1234) in those same positions.

If we compared them directly, they would always mismatch — even if the source code is identical.

The address substitution step

To solve this, the tool substitutes the real package address into the compiled bytecode before comparing — the same approach the official Sui CLI uses:

  1. Parse the compiled .mv bytecode binary header to locate the address_identifiers table. Move bytecode has a well-defined binary layout:
    • 4 bytes magic (0xA11CEB0B)
    • 4 bytes version (u32 little-endian)
    • Table count (ULEB128)
    • Table headers: each has a kind byte + offset + count (ULEB128-encoded)
    • Table data sections follow
  2. Find the table with kind 5 (ADDRESS_IDENTIFIERS). Each entry is exactly 32 bytes (an AccountAddress).
  3. For each entry in that table that is the zero-address (0x00...00), replace it with the real package address.
  4. The result is a "substituted" compiled bytecode that should now be identical to the on-chain bytecode.
Compiled bytecode:           [..., address_identifiers: [0x0000...0000, 0x0000...0002], ...]
                                                         ^^^^^^^^^^^^^
                                                         self-address = 0x0 → substitute real address
                                                         vvvvvvvvvvvvv
After substitution:          [..., address_identifiers: [0xabcd...1234, 0x0000...0002], ...]
On-chain bytecode:           [..., address_identifiers: [0xabcd...1234, 0x0000...0002], ...]
                             ✔ exact match

This is more precise than doing a blanket find-and-replace across the entire bytecode. It only touches entries in the address_identifiers table, so it will never accidentally replace bytes that happen to be zero elsewhere in the bytecode (e.g. in instruction operands or string data).

Why this matches the Sui CLI approach

The official Sui CLI's verify-source (implemented in the sui-source-validation crate) does the same thing:

  1. Compiles local source (self-address = 0x0)
  2. Deserializes the compiled bytecode into a CompiledModule struct
  3. Locates the self-address entry in the address_identifiers table
  4. Substitutes 0x0 → real package address
  5. Compares the result against the on-chain deserialized CompiledModule using structural equality

Our tool does the equivalent in TypeScript: we parse the binary header to find the address_identifiers table and perform the same substitution, then compare the raw bytes directly. The Sui CLI operates on deserialized Rust structs; we operate on the raw binary — but since Move bytecode serialization is deterministic, the result is the same.

The comparison

After address substitution, the tool compares module-by-module:

  1. Module name matching — Check that both sides have the exact same set of module names. If the on-chain package has a module lending but the compiled source does not (or vice versa), that is a mismatch.

  2. Byte-for-byte comparison — For each module present in both, compare the substituted compiled bytes against the on-chain bytes at every single byte position. Both the length and content must be identical. Even a single byte difference means the module does not match.

  3. Diff reporting — If a module does not match, the tool records the byte offsets where differences occur (up to 20). With --verbose, these are shown to help diagnose the cause (e.g. a different compiler version typically produces differences throughout the bytecode, while a source code change produces localized differences).

What this proves

If all modules pass the byte-for-byte comparison:

  • The on-chain bytecode was compiled from the exact same source code at the given git ref.
  • Using the same compiler version and same dependencies.
  • No code was added, removed, or modified between the source and what was published.

If any module fails:

  • The source code may have been modified after the commit that was published.
  • A different compiler version may have been used (different versions produce different bytecode even from identical source).
  • Dependencies may have changed (different dependency versions produce different bytecode).

Why only the self-address is substituted

Dependency addresses (e.g. 0x2 for Sui Framework) are the same in both compiled and on-chain bytecode — they are real published addresses specified in Move.toml. Only the package's own address differs between compilation (0x0) and on-chain (real address), so that is the only address that needs substitution. The tool specifically targets zero-address entries in the address_identifiers table and leaves all other addresses untouched.

CI/CD integration

The CLI uses exit codes for automation:

| Exit code | Meaning | |---|---| | 0 | All modules verified | | 1 | One or more modules failed verification | | 2 | Error (invalid input, network failure, build failure) |

Use --output json for machine-readable output:

npx tsx src/index.ts verify 0x1234...abcd \
  --repo https://github.com/example-org/protocol \
  --ref v1.0.0 \
  --output json

Private repositories

Set the GITHUB_TOKEN environment variable to clone private repos:

GITHUB_TOKEN=ghp_xxx npx tsx src/index.ts verify ...

Troubleshooting

Compiler version mismatch

If verification fails, the most common cause is a compiler version mismatch. The tool reads the expected version from Move.lock and attempts to switch automatically via suiup. If that fails:

# Install the required version manually
suiup install [email protected]

# Or skip version checking (may produce false negatives)
npx tsx src/index.ts verify ... --no-version-check

Multiple Move packages in repository

If the repo contains multiple Move.toml files, specify which package to verify:

npx tsx src/index.ts verify ... --path contracts/core

Build failures

Ensure all dependencies in Move.toml are accessible. Local path dependencies must be contained within the repository. Git dependencies must point to valid URLs and revisions.

Using as a library

Install as a dependency in your project:

npm install sui-move-bytecode-verifier

High-level: full verification

import { verifyPackage } from "sui-move-bytecode-verifier";

const result = await verifyPackage({
  sourceA: { kind: "repo", url: "https://github.com/org/repo", ref: "v1.0.0" },
  sourceB: { kind: "onchain", packageId: "0xabc..." },
  network: "mainnet",
  noVersionCheck: false,
  verbose: false,
  noCache: false,
  cacheDir: "/tmp/sui-verify-cache",
});

console.log(result.verified); // true or false
console.log(result.comparison.moduleResults); // per-module details

Low-level: individual steps

Use the building blocks directly for custom pipelines:

import {
  createSuiClient,
  fetchOnChainPackage,
  buildPackage,
  compareModules,
  extractTxModules,
  prepareForVerification,
  parseMoveLock,
} from "sui-move-bytecode-verifier";

// Fetch on-chain bytecodes
const client = createSuiClient("mainnet");
const onChainPkg = await fetchOnChainPackage("0xabc...", client);

// Build a local package
prepareForVerification("./my-pkg/Move.toml", "0xabc...");
const buildResult = await buildPackage("./my-pkg");

// Compare
const result = compareModules(
  buildResult.modules,
  onChainPkg.moduleMap,
  "0xabc...",
);

console.log(result.match);
for (const mod of result.moduleResults) {
  console.log(`${mod.name}: ${mod.mismatchKind}`);
}

// Extract modules from unsigned transaction bytes
const txModules = extractTxModules(base64TxBytes);

// Parse Move.lock for compiler version
const lockInfo = parseMoveLock("./my-pkg/Move.lock");
console.log(lockInfo?.compilerVersion);

Exported API

| Export | Description | |---|---| | verifyPackage(options) | High-level: resolve two sources, compare, return result | | fetchOnChainPackage(id, client) | Fetch package bytecodes from Sui RPC | | extractTxModules(base64Bytes) | Extract module bytecodes from BCS transaction bytes | | buildPackage(path, suiBinary?) | Run sui move build and collect .mv files | | compareModules(a, b, packageId?) | Compare two module maps with mismatch classification | | cloneAndCheckout(url, ref, cacheDir, noCache) | Clone a git repo with caching | | parseMoveToml(path) | Parse a Move.toml file | | prepareForVerification(path, packageId) | Rewrite self-address to 0x0 for compilation | | parseMoveLock(path) | Parse Move.lock for compiler version | | createSuiClient(network, rpcUrl?) | Create a SuiClient for a network | | ensureSuiVersion(version, noCheck) | Match sui CLI version via suiup | | setVerbose(enabled) | Enable/disable verbose logging | | Error classes | VerifierError, NetworkError, BuildError, ConfigError, etc. |

Development

# Run tests
npm test

# Type check
npm run typecheck

# Build library + CLI
npm run build

# Run CLI in dev mode
npx tsx src/index.ts verify --help