"Don't trust, verify!" - Independently verify Safe transaction hashes before signing.
TypeScript tool for calculating and verifying Safe multisig transaction hashes with security analysis and protocol-specific decoding. Available as a CLI, web UI, and core library.
Based on pcaversaccio/safe-tx-hashes-util.
- Fetches transaction data from the Safe Transaction Service API
- Calculates EIP-712 hashes (Domain, Message, Safe TX) independently
- Verifies the calculated hash matches the API-provided hash
- Re-encodes decoded parameters and compares with raw calldata
- Runs security analysis (delegate calls, gas token attacks, owner modifications)
- Displays results with protocol-specific decoding when available
Safe versions: v0.1.0 through v1.5.0
Always pin the version. Unpinned
npxruns whatever the registry serves at request time, which means a malicious publish could be executed before anyone notices. The examples below pin to a known-good version — bump it intentionally when you upgrade. See SECURITY.md for the recommended verification flow (build from source) for high-assurance use.
npx @shield3/sky-safe-cli@0.3.0 verify \
--address 0xf65475e74C1Ed6d004d5240b06E3088724dFDA5d \
--nonce 520Or install globally with a pinned version:
npm install -g @shield3/sky-safe-cli@0.3.0
sky-safe verify --address 0x... --nonce 520- Cloudflare Pages: sky-safe-tx-decoder.pages.dev
- IPFS: sky-safe-tx-decoder
Or run locally:
pnpm install
pnpm --filter @shield3/sky-safe-ui devFor the highest-assurance setup — no hosting provider, no server, no npm to
run — build a single self-contained HTML file you open directly in a browser:
pnpm install
pnpm --filter @shield3/sky-safe-ui build:offline
# → packages/ui/dist-offline/index.html (double-click to open)Everything (JS + CSS) is inlined into that one file, so it runs from file://
with no external requests for app code. Audit it once, optionally pin its hash,
and run it from disk — the verification code is yours, not re-served by a CDN on
every visit. It still fetches transaction data from the Safe Transaction Service
API, so an internet connection is required; only the app code is local.
This tool is what you use to decide whether to sign a transaction, so the
integrity of the tool itself is security-critical: a tampered build could show
"✓ hashes match" when they don't and trick you into signing. So a release isn't
just a file you have to trust — it ships with a cryptographic proof of where it
came from. Each tagged release attaches the offline index.html, its .sha256,
and a keyless build-provenance attestation.
Three independent layers, weakest to strongest:
- SHA-256 checksum — integrity. Confirms the file wasn't altered versus the published value. (On its own, weak: whoever can swap the file can change the number next to it.)
- Build-provenance attestation — authenticity + origin. Cryptographically proves this exact file was produced by GitHub Actions, building this repo at a specific commit, via the official release workflow — not from someone's laptop or modified source. It is keyless (signed by the workflow run's verified identity via Sigstore + GitHub OIDC — no secret key a maintainer could leak) and recorded in a public transparency log.
- Reproducible build — don't trust, verify. The build is deterministic, so you can rebuild from source and get a byte-identical file. You don't have to trust the published binary at all.
After downloading, verify before trusting:
# Integrity — matches the published checksum
shasum -a 256 -c sky-safe-tx-decoder-<tag>.html.sha256
# Provenance — confirms GitHub Actions built this exact file from this repo/commit
gh attestation verify sky-safe-tx-decoder-<tag>.html --repo 0xShield3/sky-safe-tx-decoder
# Strongest — reproduce it yourself and compare the hash
git checkout <tag>
pnpm --filter @shield3/sky-safe-ui build:offline
shasum -a 256 packages/ui/dist-offline/index.html # should match the releaseThis protects against a swapped or backdoored release file, a hand-uploaded build from a compromised maintainer account, and tampering between source and binary. It does not prove the source code itself is bug-free (audit or trust the code separately), and verification is a one-time check before you open the file — the browser doesn't re-check it at runtime.
The web UI loads two independent, session-only CSV files by drag-and-drop. They share one schema but play different roles, so updating one never disturbs the other:
| File | Role | In the app |
|---|---|---|
| Address book | Managed externally (team-owned). Labels known addresses during review. | Read-only. Replace by loading a fresh file. |
| My Safes | Your personal Safe shortcuts (the home-page dropdown). | Editable (add/remove) and exportable. |
Each file self-identifies with a marker comment on the first line, so the UI can reject a file dropped on the wrong slot:
# sky-safe-config: address-book
address,label,verification_date,status
0xCe01C90dE7FD1bcFa39e237FE6D8D9F569e8A6a3,Sky LockstakeEngine,2026-05-01,active# sky-safe-config: my-safes
type,network,address,label,verification_date,status
safe,ethereum,0xf65475e74C1Ed6d004d5240b06E3088724dFDA5d,Treasury Safe,2026-05-10,activeSee examples/address-book-template.csv
and examples/my-safes-template.csv. Older
address books with only address,label,verification_date,status still load.
Config is kept in memory for the current browser session and is not persisted to local storage — the CSV files you keep externally are the source of truth. This is intentional for a signing tool: a cached copy could be silently tampered with or drift stale, so each session you re-load fresh files.
- Settings page (top-right link): add a Safe (network + address + label), edit a Safe's label, remove Safes, and export My Safes back to CSV.
- Capture an unsaved Safe: when you open a Safe that isn't in My Safes, a banner offers to label it and add it to the session list. Adding does not download anything — you export once, when you're ready, from Settings (or the config bar).
- Export (config bar or Settings): writes My Safes as a CSV that re-imports exactly (round-trip), with the kind marker. Remember to export to persist your in-session changes.
- Because the address book and My Safes are separate files, you can update the managed address book anytime without losing your Safes.
git clone https://github.com/0xShield3/sky-safe-tx-decoder
cd sky-safe-tx-decoder
pnpm install
pnpm build
# Run CLI
pnpm dev:cli verify --address 0x... --nonce 520
# Run UI dev server
pnpm --filter @shield3/sky-safe-ui dev| Network | Chain ID |
|---|---|
| Ethereum Mainnet | 1 |
| Base | 8453 |
| Sepolia Testnet | 11155111 |
packages/
core/ # @shield3/sky-safe-core - Hash calculation, decoding, security analysis
cli/ # @shield3/sky-safe-cli - Command-line interface
ui/ # @shield3/sky-safe-ui - Web interface (React + Vite)
The tool detects:
- Untrusted delegate calls - Flags delegate calls to contracts not on the trusted list
- Gas token attacks - Custom gas token + custom refund receiver combinations
- Owner/threshold modifications - Direct and nested (via MultiSend) changes to Safe owners
The examples/ directory contains crafted transactions that trigger each warning type:
sky-safe verify --file examples/gas-token-attack.json
sky-safe verify --file examples/untrusted-delegatecall.json
sky-safe verify --file examples/owner-modification.json
sky-safe verify --file examples/guard-set.json
sky-safe verify --file examples/module-enable.json
sky-safe verify --file examples/multiple-issues.jsonThe decoder registry provides protocol-specific human-readable transaction explanations. Currently included:
| Protocol | Contract | Functions |
|---|---|---|
| Sky Protocol LockstakeEngine | 0xCe01C90dE7FD1bcFa39e237FE6D8D9F569e8A6a3 |
13 (urn management, staking, borrowing, delegation) |
| MultiSend | Standard Safe MultiSend | Batch transaction decoding |
See the core package README for how to add your own.
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass (
pnpm test) - Submit a pull request
Use pnpm pack then npm publish on the tarball. This ensures workspace:* dependencies are resolved to real versions. Do not use npm publish directly from a package directory.
# Core (publish first)
cd packages/core && pnpm pack && npm publish shield3-sky-safe-core-*.tgz --access public && rm *.tgz
# CLI
cd ../cli && pnpm pack && npm publish shield3-sky-safe-cli-*.tgz --access public && rm *.tgzUsers must trust:
- This TypeScript implementation
- viem cryptographic library
- Safe Transaction Service API data — however, the tool independently re-encodes decoded parameters and verifies they match the raw calldata and EIP-712 hashes, so API-provided decoded data is not blindly trusted
- Hardware wallet secure screen
AGPL-3.0-only (matching upstream)