Skip to content
Documentation

Web3 add-on guide

Test a smart contract end-to-end

Read this before you pin a contract or trigger a scan. Each chain has its own setup, its own tool chain, and its own gotchas. Five minutes here saves a confused triage later.

Chain support matrix

We are honest about what we ship today versus what is on the roadmap. Customers procure on what runs in production, not on what is planned.

ChainStatusCoverage today
EthereumSupportedSlither + Mythril + Echidna + NFT/ERC-20 audit + token economy + honeypot + opcode + OFAC + governance + audit registry
PolygonSupportedSame as Ethereum (Polygon is EVM-compatible)
BSCSupportedSame as Ethereum
ArbitrumSupportedSame as Ethereum
OptimismSupportedSame as Ethereum
BaseSupportedSame as Ethereum
AvalancheSupportedSame as Ethereum
Sui (Move)Beta19 Move static detectors + UpgradeCap owner classification + Move test discovery. Move Prover sidecar + Coin economy probes still on roadmap.
SolanaBetaV2 baseline: verified-build lookup (verify.osec.io), upgrade authority via BPFLoaderUpgradeable, Anchor on-chain IDL probe, SPL mint + freeze authority audit. PDA constraint analysis + Cargo audit still on roadmap.
Aptos / Cosmos / StarkNetNot on roadmapNo customer demand signal yet. Reach out if you need them.

NANOTESTING is not a certified smart contract audit. It is an automated pre-audit baseline that catches the obvious and repeatable issues so your human audit budget goes further. Manual code review by a specialist firm remains the gold standard for business-logic + economic-invariant flaws.

What the Web3 add-on covers

The add-on layers on top of the standard repo_security_scan for a GitHub repository target. Pinning a deployed contract on that target switches on a chain-specific set of extra checks:

  • EVM chains (Ethereum, Polygon, BSC, Arbitrum, Optimism, Base, Avalanche): on-chain Slither via Etherscan source fetch, Mythril symbolic execution, NFT (ERC-721 / ERC-1155) audit, ERC-20 token audit, honeypot simulation, fee-on-transfer detection, LP-token lock detection, bytecode opcode scan, OFAC SDN lookup, audit-registry lookup, Echidna fuzzing of in-repo properties, Foundry invariant test discovery.
  • Sui (Move): Move static analyzer against the published package via the public Sui mainnet RPC. Detectors flag capability leaks, store-able caps, drop-able coin-shaped structs, public functions that return capabilities without privileged input, and sensitive entry points with no cap parameter.
  • Solana: V2 baseline against the public Solana mainnet RPC plus the Solana Foundation verified-build registry. We confirm whether the deployed bytecode matches a public source repo, follow BPFLoaderUpgradeable to read the upgrade authority (single keypair vs Option::None immutable vs unknown), derive the Anchor IDL PDA and parse the zlib payload to count instruction handlers and account types, and parse SPL mint accounts for active mint / freeze authority.

Both paths run during the same repo_security_scan job. You always get the source-side findings (osv-scanner + gitleaks + Trivy + Semgrep + Slither / Mythril / Echidna on the repo); the on-chain side is the add-on bonus.

Prerequisites

  1. Organization is on Growth or Agency plan, with the Web3 add-on enabled at $299 / month on top. Owners can flip it on under Billing. Admins can grant it for free via the admin console (used for partners + audits).
  2. You have a GitHub repository target created and verified. If the repo also lives off-chain (a DApp frontend + backend), add the domain as a separate website target.
  3. You know the deployed contract / package address (the production one - never the testnet copy) and the chain it lives on.
  4. EVM chains only: you have an Etherscan v2 API key. Etherscan's May 2024 unified API means one key covers every supported EVM chain; you don't need a per-chain key any more.
  5. Sui: nothing. We hit the public Sui mainnet RPC (https://fullnode.mainnet.sui.io:443) which is open and unauthenticated.

EVM setup (Ethereum, Polygon, BSC, Arbitrum, Optimism, Base, Avalanche)

1. Get an Etherscan v2 API key

  1. Sign in to etherscan.io/myapikey.
  2. Click Add and name the key something you can recognize (e.g. nanotesting-prod).
  3. Copy the long string. It looks like XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.

The free tier (5 calls / sec) is more than enough for our scanner - we make 2-4 calls per scan per chain. We never share the key, and we never make it client-visible.

2. Store the key under Credentials

  1. Open your GitHub repo target's detail page and switch to the Credentials tab.
  2. Click Add credentials. Pick the auth type Etherscan API key (Web3).
  3. Paste the key into Secret. Username + Login URL stay empty. Notes are optional but help your team know which key is which.
  4. Submit. The key is encrypted at rest with AES-256-GCM using a key the worker process loads from ENCRYPTION_KEY. The plaintext never returns to the browser after save.

3. Pin the deployed contract

  1. From the target's Overview tab, find the On-chain pin (Web3 add-on) card.
  2. Paste the deployed address. EVM addresses are 0x + 40 hex characters(20 bytes). The form rejects anything that doesn't match before submit.
  3. Pick the chain from the dropdown.
  4. Click Save pin. The change is audit-logged.

You can also pin during target creation: open Add target, pick GitHub repository, then expand the Web3 panel and fill in address + chain there.

4. Trigger the scan

Hit Run scan on the target. The dispatcher picks repo_security_scan and the deep-scan step runs in parallel with the new on-chain steps. Typical wall-clock time on a medium-sized repo + pinned EVM contract is 4-12 minutes; the dependency CVE pass is usually the long pole.

What runs against an EVM contract

  • onchain_audit - Slither against the source code Etherscan returns for the address. We run with --exclude-informational --exclude-optimization --exclude-low; a full HIGH/MEDIUM detector pass. Detectors include reentrancy, tx.origin, uninitialized storage, weak PRNG, unchecked return values, shadowing, and ~80 more.
  • nft_metadata_audit - supportsInterface probe to detect ERC-721 / 1155, then a sample tokenURI fetch, IPFS pin check, and ERC-2981 royalty info. Flags off-chain metadata mutability, missing royalty endpoint, and royalty receiver pointing to a high-percentage centralized address.
  • erc20_audit- if the contract isn't an NFT, we sniff the ERC-20 interface and look for owner-controlled mint, burn, pause, and per-address blacklist functions.
  • token_economy- on-chain reads of total supply, biggest holders, DEX liquidity, and LP token lock state. Flags >50% supply concentration in a single non-burn address, low / unlocked LP, or no DEX listing at all.
  • fee_on_transfer - simulates a small transfer in a forked state to detect SafeMoon-style tax tokens (rebases + taxes pulled into a treasury on every transfer).
  • honeypot - eth_getLogs + simulated sell against the contract to flag dump-only tokens (you can buy but never sell).
  • owner_classification - resolves the on-chain owner. Detects Gnosis Safe (multisig), OpenZeppelin Timelock, EOA, or a pre-baked governance contract. Calls out owners that are a single EOA on a high-TVL contract.
  • bytecode_opcode_scan - looks for SELFDESTRUCT and CALLCODE opcodes in the deployed bytecode. Both are deprecated and almost always indicate either a kill switch or legacy proxy that should be replaced.
  • ofac_sdn - cross-checks the deployed address, owner, and royalty receiver against the US Treasury SDN list. Critical hit if any of them are sanctioned.
  • audit_registry - pulls public audit reports we have indexed (Trail of Bits, OpenZeppelin, Quantstamp, Halborn, Certik, etc.) and surfaces matches. Positive signal, not a finding.
  • If your repo contains Solidity source: Mythril symbolic execution, Echidna property fuzz (with the AGPL sidecar), Foundry invariant + test discovery, proxy storage collision check. All run independently of the on-chain pin.

Sui setup (Move language packages)

1. Identify the package id

Sui has two concepts that often get confused. A package id is the on-chain hash of the published Move modules. An object id is one instance of a struct produced by those modules. We test packages - the module code itself - not individual objects. Find the package id on SuiScan, SuiVision, or your deployment script's output.

Sui addresses are 32 bytes - 0x + up to 64 hex characters. Leading zeroes are usually elided; the framework is just 0x2.

2. Pin the package

  1. On the GitHub repo target, open the On-chain pin card.
  2. Pick Sui (Move) from the chain dropdown.
  3. Paste the package id. The form auto-relabels the input to Deployed package id and accepts the Sui address format.
  4. Save.

No API key step. You do not need to add an Etherscan key for a Sui target.

3. Trigger the scan

Same Run scan button. The dispatcher detects deployed_chain == "sui" and routes through three dedicated Sui steps in parallel with the regular repo deep-scan:

  • sui_static_analysis - 19 Move detectors over the normalized module AST (see catalog below).
  • sui_owner_classification- resolves the package's UpgradeCap holder via sui_getTransactionBlock on the publish tx and classifies it: Immutable / Shared / AddressOwner / ObjectOwner. Shared is a critical finding because anyone can then call package::commit_upgrade.
  • sui_move_test_discovery - walks the cloned repo for Move.toml + /tests/*.move files. Counts #[test], test_fuzz_, and prop_/invariant_ functions. Surfaces an informational finding either way (no tests is a soft negative; property tests is a soft positive).

EVM-only steps (Slither / Mythril / NFT / ERC-20 / honeypot / opcode / OFAC) self-skip because Sui addresses fail their 20-byte length check, so the report stays clean.

What our Sui Move analyzer checks

The catalog covers 19 high-precision Move detectors that operate on the published normalized module AST. Every detector documents its remediation inline; severity + name below.

Struct-level detectors

  • capability-with-copy-ability (CRITICAL). A cap-shaped struct that declares copy can be duplicated freely. There is no legitimate Move design that accepts this; we flag it as critical with high confidence.
  • capability-with-store-ability (HIGH). Cap-shaped struct with storecan be embedded in another module's struct and re-published, which is equivalent to leaking the cap. Idiomatic Sui caps hold key only.
  • coin-with-drop-ability (HIGH). A coin / token / asset / balance shaped struct with drop lets any caller silently destroy a balance, breaking total-supply accounting. Canonical pattern is coin::burn against the TreasuryCap.
  • upgrade-cap-wrapper-with-store (HIGH). A struct holding an UpgradeCap field AND declaring store means the upgrade authority can be moved off the module without notice.
  • treasurycap-wrapper-with-store (HIGH). Same shape for TreasuryCap. Transferable mint privilege.
  • supply-wrapper-with-store (HIGH). A struct holding Supply<T> with store. Supply is the underlying total-supply tracker; store-ability means the mint authority can be transferred or embedded into third-party modules.
  • upgrade-cap-wrapped (MEDIUM, low conf). Any struct that holds an UpgradeCapfield at all. Worth a manual review of the wrapper's access control (false-positive when the wrapper is owned + non-share-able).
  • multiple-caps-in-struct (MEDIUM). A single wrapper that bundles 2+ capability-shaped fields concentrates blast radius. Consider per-cap owned objects so they can be rotated independently.
  • key-struct-missing-uid (MEDIUM, low conf). Struct declares key but does not contain an id: UID field. Sui compiler would reject this normally; seeing it in normalized output flags a publish path worth review.

Module-level detectors

  • otw-without-init (HIGH). Module declares a struct shaped like a one-time witness (ALL_CAPS name, no fields, drop only) but exposes no init function. The witness can never be consumed, which means the cap-setup path was probably never wired up - intended privileges are unreachable.
  • coin-type-without-init(MEDIUM, low conf). Module references Sui's Coin<T> but exposes no init function. Coin setup conventionally happens in init via coin::create_currency; missing it usually means supply is being minted in an unexpected way.
  • display-without-transfer-policy (MEDIUM). Module declares Display<T> (NFT metadata configuration) but no TransferPolicy<T>. Without a policy the collection can be moved freely - no on-chain royalty enforcement is possible.
  • transfer-policy-without-royalty-rule (MEDIUM). Module declares TransferPolicy<T> but no royalty rule. The policy is in place but does nothing, giving marketplaces a false sense that the collection is protected.

Function-level detectors

  • public-fn-returns-capability (HIGH). A public function returning a capability-shaped struct and accepting no capability as input. Anybody can mint a cap.
  • public-fn-returns-mut-txcontext (HIGH). A public function returning &mut TxContext. TxContext is meant to be a borrow scoped to one PTB command; returning a mut ref is an escape hatch.
  • public-supply-mut-without-cap (HIGH). A public function takes &mut Supply<T> but no TreasuryCap<T>. Mint authority mutated without the cap-gated path.
  • entry-takes-upgrade-cap (HIGH). A public entry function that accepts UpgradeCap directly. Standard flow is to call package::authorize_upgrade from a privileged module, not to thread the cap through a PTB entry.
  • public-share-or-transfer-cap (HIGH). A public function with name share_* / transfer_* that accepts a capability. The legitimate caller of these helpers is init only.
  • entry-without-cap (MEDIUM). A public entry with a sensitive name (mint / withdraw / burn / set_ / pause) but no cap-shaped parameter. False-positive possible when the access check lives in the body via tx_context::sender; treat as "please review".
  • public-init-function (CRITICAL). The initfunction appears in the module's exposed surface (public or friend). init must be private; the runtime calls it once at publish time and no external caller should be able to re-trigger it. A re-callable init is a full takeover primitive.
  • friend-function-without-friend-decl (LOW). A function declares public(friend) but the module has no friend declarations. The function is unreachable from any caller - dead code at best, misunderstood access intent at worst.

All Sui findings land in the standard Web3 category with source_tool = sui-move-analyzer, so you can filter the dashboard the same way you filter Slither or Trivy output.

What we do not test

Honest list. If any of these are in scope for your audit, you will need a human auditor on top of this scanner.

  • Economic invariants. We do not formally verify that your AMM's constant-product holds under adversarial trades, or that your lending market is oracle-safe under price manipulation. Those need a custom model.
  • Game-theoretic incentives. We will not tell you if your staking rewards can be drained by Sybils, or if your governance can be flash-loan-attacked.
  • Off-chain pieces. Sequencers, relayers, RPC allowlists, multisig signer hygiene - none of that is in scope. Run a normal pentest on those.
  • Compiler-level bugs. We assume solc / move produce what the source intends. If a specific compiler version has a known mis-compilation bug, only Mythril + Echidna at runtime might surface it.
  • Proxy / upgrade governance. We detect proxy patterns + opcode-level kill switches, but whether an upgrade is safe for your users is a governance question, not a static-analysis question.
  • Non-mainnet deployments. Today we only hit mainnet RPCs (Etherscan v2 mainnet, Sui mainnet). Testnet and devnet are explicitly skipped because production customers should be paying for production-grade audit.
  • Sui function bodies. The Sui RPC's normalized-module endpoint returns struct shapes and function signatures, not function bodies. Bytecode decompilation of Move packages is on the roadmap (item below); until then, body-level patterns like "sender check is missing inside the function" need a manual review.

Sui roadmap

What ships next on the Sui side, in priority order. We update this list as we ship.

Shipped

  • 19 Move static detectors against the normalized AST. Covers capability, coin, upgrade, display, policy, init visibility, and friend-hygiene patterns.
  • Owner classification. Resolves the UpgradeCap holder via Sui RPC and classifies as Immutable / Shared / AddressOwner / ObjectOwner. Shared = critical (anyone can call package::commit_upgrade).
  • Move test discovery. File walk over the cloned repo counts #[test], test_fuzz_, and prop_/invariant_ functions. Soft signal: no property tests = informational nudge; property tests present = positive confirmation.

In progress / next

  • Move Prover sidecar. Symbolic verification of user-declared invariants on Move packages. BSD-licensed tool from Aptos / Move-stdlib; deploys as its own Fly app like our Mythril + Echidna sidecars. Requires Rust toolchain image so it sits separate from the worker.
  • Sui Coin economy probes. Top-holder concentration, mint authority status, DeepBook liquidity presence. Same shape as our EVM token-economy probe.
  • Display + Royalty Rule on-chain audit. The AST analyzer already catches missing TransferPolicy and missing royalty rule at the source level. The on-chain version reads the live policy + rule objects and reports royalty BPS, receiver address, and policy mutability.
  • OFAC SDN for Sui. Separate dataset; the existing US Treasury list is EVM-keyed. We are tracking Sui-specific sanctions actions and will add a curated dataset as it grows.
  • Move bytecode disassembly. For packages where source is not available (rare on Sui; the normalized API gives us most of what we need today).
  • Cross-package analysis. Follow friend declarations and call graph across multiple packages so a finding in package A can flag a caller in package B.

Reading the report

  • Web3 findings land in the Findings tab with category Web3. Use the source_tool column to filter to a specific tool: slither, mythril, nft-audit, erc20-audit, ofac-sdn, echidna, sui-move-analyzer.
  • The Web3 Scorecard on the target overview summarizes every Web3 hit into a 0-100 score and a band (clean / caution / risky / dangerous). It only renders when the org has the add-on enabled - we never show a score from a baseline scan that didn't actually run Web3 probes.
  • The Reportstab gives you a PDF that includes a dedicated Web3 section with the same content. That's the deliverable you send to a partner exchange or a launch-pad due-diligence team.
  • Each finding has Evidence + Remediation inline. For Slither / Mythril findings, the Evidence line includes the source file + line so an engineer can jump straight to the offending code.

Troubleshooting

Slither step says "no report produced"

Etherscan didn't return source for the address. Three common causes: the contract is unverified, the contract is a proxy and you pinned the proxy address instead of the implementation, or your API key is rate-limited / wrong chain slug. Open the contract on Etherscan and confirm Contract Source Code Verifiedis shown. If it's a proxy, re-pin to the implementation address.

Sui static analysis says "package has no modules"

You pinned an object id instead of the package id. Object ids are also 0x... but they represent struct instances, not Move bytecode. Re-fetch the package id from SuiScan or your deployment script.

The Web3 Score card is missing

The add-on entitlement is off. Confirm billing_profiles.web3_addon_enabled = true for the org on the Billing page. The pin is also a no-op for non-github_repository target types (e.g. you pinned on a domain target by mistake).

NFT / ERC-20 audit didn't produce findings on a Sui target

Expected. NFT + ERC-20 detectors are ERC-* specific; Sui uses Move's own object + coin model. Sui findings come out of the sui_static_analysis step under the sui-move-analyzer tag.

Where is OFAC SDN for Sui?

Not in v1 of the Sui pass. The OFAC SDN list maps EVM-style addresses; mapping Sui's 32-byte object / package identifiers to sanctioned entities needs a separate dataset we're building. Until then, run an off-platform check via the US Treasury site directly for the package owner.

Pricing

  • $299 / month, layered on top of any Growth or Agency plan. Cancellable at period end through the same Billing page.
  • Unlimited contracts per org - pin as many deployed contracts as you have github_repository targets on your plan.
  • Etherscan v2 cost: free tier is enough. You only pay if you also use the same key from your own tooling and exceed 5 calls / sec.
  • Sui RPC cost: zero. We use the public fullnode endpoint, which is rate-limited but free.

Authorization + safety policy

Every Web3 check is read-only. No on-chain transactions are broadcast. The honeypot detector uses eth_call against a simulated forked state, not a real swap. Fee-on-transfer detection uses the same simulation primitive. We never run aggressive crawls or send adversarial payloads to the contract.

See the full safe-scanning policy for the organization-wide authorization contract and data handling terms.