Skip to content

Repository files navigation

neow3j-react-starter

A working starter for Neo N3 dApps with a Java/Kotlin smart contract compiled via neow3j and a React/Vite UI that talks to the chain via neon-js + Reown AppKit. Forks cleanly into a real project.

What's in the box

contract/ Java contract (CounterContract — placeholder), compiled to NEF
via the neow3j gradle plugin. Has a JUnit 5 test suite that
runs against a Dockerized single-node privatenet via
neow3j-test (Docker must be running).
ui/ React + Vite SPA. Connects via NeoLine (extension) and/or
WalletConnect (Reown AppKit). Reads + writes the contract.
Vitest unit tests for the chain glue + a Playwright e2e suite
(no wallet — see "Testing").
localnet/ Convenience scripts to spin up an AxLabs neo3-privatenet via
docker for local end-to-end work.
.github/ CI: gradle test; ui unit tests, typecheck, build, e2e,
bundle-size budget gate.

Quickstart

# 1. Compile the contract (writes NEF + manifest into contract/build/neow3j/)
./gradlew :contract:neow3jCompile
# 2. Run the contract tests (JDK 17 or 21; not 25 — see STACK_NOTES.md)
./gradlew :contract:test
# 3. Start the UI (auto-runs sync scripts that bundle the freshly-compiled NEF)cd ui
npm install
npm run dev

The UI defaults to mainnet RPC. For local end-to-end:

./localnet/start.sh # docker compose up -d
VITE_NETWORK=localnet npm run dev # uses the Vite /__rpc proxy

Forking this template

Search-and-replace these tokens, in order:

  1. CounterContract → your contract class name (Java + tests + sync scripts).
  2. com.example.counter → your Java package. Also set the @ManifestExtraAuthor value in CounterContract.java (ships as your-name-here).
  3. neow3j-react-starter → your repo / app name (root settings.gradle.kts, ui/package.json, ui/src/lib/appkit.ts metadata).
  4. neow3jstarter.* storage-key prefixes in ui/src/lib/connection.tsx and ui/src/lib/rpc.ts.

Then write your contract, replace the methods in ui/src/lib/contract.ts (and its tests in ui/src/lib/contract.test.ts, plus the stubbed methods in ui/e2e/mock-rpc.ts), and rewrite App.tsx around them. Everything else (vite config, gradle, CI, wallet adapters, the test harnesses) stays.

Configuration

Copy ui/.env.example to ui/.env.local and fill in what you need. All vars are optional — with none set, the UI runs against mainnet RPC in NeoLine-only mode.

VITE_NETWORK=mainnet # mainnet | testnet | localnet
VITE_RPC_URL= # optional override (any neo-cli RPC)
VITE_WC_PROJECT_ID= # Reown project id; empty = NeoLine-only
VITE_CONTRACT_HASH=0x… # default contract for the UI to read

Contract:

  • gradle.properties: neow3jVersion, JVM flags.
  • contract/build.gradle.kts: className must point at your contract.

Testing

Contract — JUnit 5 against a Dockerized single-node privatenet (Docker must be running):

./gradlew :contract:test # full suite
./gradlew :contract:test --tests CounterContractTest.increment_byOwner_bumpsCountAndFiresEvent

The test extension boots a Neo node in a Docker container, deploys all @ContractTest contracts as the genesis multi-sig, and exposes them via ext.getDeployedContract. See helpers/TestHelper.java for the genesis-funded GAS helper and the assertAborted pattern (read FAULT messages from the application log).

UI — two layers:

cd ui
npm run test# vitest: unit tests of src/lib (RPC mocked) — fast, no chain
npm run e2e:install # one-time: download the chromium binary
npm run e2e # playwright: builds, serves dist/, drives a real browser

vitest covers the chain glue in src/lib — stack-item decoding, contractExists, waitForTx, and the shape of the invocation increment() hands the wallet — with the RPC layer mocked.

playwright (e2e/) drives the built bundle in a headless browser. Its job is less "UI coverage" and more "did the polyfill / manualChunks config in vite.config.ts survive" — that config only fails at runtime, so a build that passes can still ship a blank page. It also exercises the read path against a tiny stub RPC (e2e/mock-rpc.ts).

There's no wallet-driven e2e on purpose. NeoLine is a browser extension and WalletConnect needs a real wallet on the other end; automating either buys little over the increment() unit test and is a flake magnet. If you need it, inject a fake window.NEOLineN3 via page.addInitScript whose invoke signs with a funded localnet key — but for most forks the unit + no-wallet-e2e split is enough.

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages