diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..5f6fb98
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,21 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{ts,tsx,js,jsx,cjs,mjs,json,yaml,yml,md}]
+indent_style = space
+indent_size = 2
+
+[*.rs]
+indent_style = space
+indent_size = 4
+
+[Makefile]
+indent_style = tab
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f6c7ae6..dd0ec67 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,6 +13,33 @@ on:
- staging
jobs:
+ format:
+ name: Formatting checks
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install dashboard dependencies
+ working-directory: dashboard
+ run: npm ci
+
+ - name: Check dashboard formatting (Prettier)
+ working-directory: dashboard
+ run: npm run format:check
+
+ - name: Install listener dependencies
+ working-directory: listener
+ run: npm ci
+
+ - name: Check listener formatting (Prettier)
+ working-directory: listener
+ run: npm run format:check
+
frontend:
name: Frontend (lint, typecheck, test)
runs-on: ubuntu-latest
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..332bffd
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,26 @@
+# Dependencies
+node_modules/
+dashboard/node_modules/
+listener/node_modules/
+
+# Build output
+dist/
+dashboard/dist/
+listener/dist/
+target/
+
+# Lock files (managed by package managers, not formatted)
+package-lock.json
+dashboard/package-lock.json
+listener/package-lock.json
+Cargo.lock
+
+# Generated / vendored
+*.wasm
+*.optimized.wasm
+reports/
+dashboard/reports/
+
+# Environment files
+.env
+.env.*
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..4d2523a
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "semi": true,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "printWidth": 100,
+ "tabWidth": 2,
+ "useTabs": false,
+ "arrowParens": "always",
+ "endOfLine": "lf"
+}
diff --git a/CODE_FORMATTING.md b/CODE_FORMATTING.md
new file mode 100644
index 0000000..ac2edfd
--- /dev/null
+++ b/CODE_FORMATTING.md
@@ -0,0 +1,103 @@
+# Code Formatting
+
+NotifyChain enforces consistent formatting across all languages via automated checks that run on every pull request.
+
+---
+
+## Tools
+
+| Language | Tool | Config |
+|---|---|---|
+| TypeScript / TSX (dashboard) | [Prettier](https://prettier.io) 3.x | `.prettierrc` (root) |
+| TypeScript (listener) | [Prettier](https://prettier.io) 3.x | `.prettierrc` (root) |
+| Rust (contracts) | `rustfmt` (stable) | default `rustfmt` rules |
+| All files | EditorConfig | `.editorconfig` (root) |
+
+---
+
+## Prettier rules (TypeScript / TSX)
+
+Defined in `.prettierrc` at the repository root.
+
+| Rule | Value |
+|---|---|
+| `semi` | `true` — semicolons required |
+| `singleQuote` | `true` — single quotes for strings |
+| `trailingComma` | `"all"` — trailing commas wherever valid |
+| `printWidth` | `100` — wrap lines at 100 characters |
+| `tabWidth` | `2` — two-space indentation |
+| `useTabs` | `false` — spaces, not tabs |
+| `arrowParens` | `"always"` — parentheses around arrow function parameters |
+| `endOfLine` | `"lf"` — Unix line endings |
+
+---
+
+## Rust formatting
+
+The `rust` CI job runs `cargo fmt --all -- --check`. This uses the default `rustfmt` rules (stable channel). No custom `rustfmt.toml` is required.
+
+---
+
+## EditorConfig
+
+`.editorconfig` enforces baseline rules in supported editors (VS Code, JetBrains, Vim, etc.) independently of any formatter:
+
+- UTF-8 encoding everywhere
+- LF line endings everywhere
+- Final newline on every file
+- Trailing whitespace trimmed (except Markdown)
+- 2-space indentation for TS/JS/JSON/YAML/Markdown
+- 4-space indentation for Rust
+
+---
+
+## CI enforcement
+
+The `format` job in `.github/workflows/ci.yml` runs on every pull request and push to `main` / `staging`:
+
+```
+format job
+ ├── dashboard: npm run format:check (Prettier --check)
+ └── listener: npm run format:check (Prettier --check)
+```
+
+The `rust` job also runs `cargo fmt --all -- --check`.
+
+A pull request **cannot be merged** if either check exits non-zero.
+
+---
+
+## Fixing formatting locally
+
+### TypeScript / TSX
+
+```bash
+# Fix dashboard
+cd dashboard
+npx prettier --write "src/**/*.{ts,tsx}" --config ../.prettierrc
+
+# Fix listener
+cd listener
+npx prettier --write "src/**/*.ts" --config ../.prettierrc
+```
+
+### Rust
+
+```bash
+cd contract
+cargo fmt --all
+```
+
+### VS Code auto-format on save
+
+Install the [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) extension and add to `.vscode/settings.json`:
+
+```json
+{
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[rust]": {
+ "editor.defaultFormatter": "rust-lang.rust-analyzer"
+ }
+}
+```
diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json
index 758e0d8..14ee216 100644
--- a/dashboard/package-lock.json
+++ b/dashboard/package-lock.json
@@ -30,6 +30,7 @@
"jest-axe": "^10.0.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^26.1.0",
+ "prettier": "3.3.3",
"ts-jest": "^29.2.5",
"typescript": "^5.8.3",
"vite": "^6.3.5"
@@ -972,23 +973,6 @@
"node": ">= 6"
}
},
- "node_modules/@creit.tech/stellar-wallets-kit/node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
- "extraneous": true,
- "license": "Apache-2.0",
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=4.2.0"
- }
- },
"node_modules/@creit.tech/xbull-wallet-connect": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@creit.tech/xbull-wallet-connect/-/xbull-wallet-connect-0.4.0.tgz",
@@ -7987,24 +7971,6 @@
"ws": "^7.5.1"
}
},
- "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/utf-8-validate": {
- "version": "5.0.10",
- "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
- "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
- "extraneous": true,
- "hasInstallScript": true,
- "license": "MIT",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "node-gyp-build": "^4.3.0"
- },
- "engines": {
- "node": ">=6.14.2"
- }
- },
"node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": {
"version": "7.5.11",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz",
@@ -13130,24 +13096,6 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
- "node_modules/jayson/node_modules/utf-8-validate": {
- "version": "5.0.10",
- "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
- "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
- "extraneous": true,
- "hasInstallScript": true,
- "license": "MIT",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "node-gyp-build": "^4.3.0"
- },
- "engines": {
- "node": ">=6.14.2"
- }
- },
"node_modules/jayson/node_modules/ws": {
"version": "7.5.11",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz",
@@ -16255,6 +16203,22 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
+ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
diff --git a/dashboard/package.json b/dashboard/package.json
index b0433d9..ac5d8c7 100644
--- a/dashboard/package.json
+++ b/dashboard/package.json
@@ -8,6 +8,7 @@
"preview": "node ./node_modules/vite/bin/vite.js preview",
"dev": "node ./node_modules/vite/bin/vite.js",
"lint": "node ./node_modules/eslint/bin/eslint.js \"src/**/*.{ts,tsx}\" --max-warnings=0",
+ "format:check": "node ./node_modules/prettier/bin/prettier.cjs --check \"src/**/*.{ts,tsx}\" --config ../.prettierrc",
"test": "node ./node_modules/jest/bin/jest.js",
"test:wallet": "node ./node_modules/jest/bin/jest.js src/__tests__/wallet-integration.test.tsx",
"benchmark": "node ./node_modules/jest/bin/jest.js src/benchmark"
@@ -19,13 +20,6 @@
"zustand": "^5.0.6"
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "^6.10.0",
- "@typescript-eslint/parser": "^6.10.0",
- "eslint": "^8.46.0",
- "eslint-plugin-react": "^7.33.0",
- "@testing-library/react": "^16.3.0",
- "@testing-library/user-event": "^14.6.1",
- "@types/jest": "^29.5.14",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
@@ -33,11 +27,16 @@
"@types/jest-axe": "^3.5.9",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
+ "@typescript-eslint/eslint-plugin": "^6.10.0",
+ "@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.7.0",
+ "eslint": "^8.46.0",
+ "eslint-plugin-react": "^7.33.0",
"jest": "^29.7.0",
"jest-axe": "^10.0.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^26.1.0",
+ "prettier": "3.3.3",
"ts-jest": "^29.2.5",
"typescript": "^5.8.3",
"vite": "^6.3.5"
diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx
index 967b943..bb38b34 100644
--- a/dashboard/src/App.tsx
+++ b/dashboard/src/App.tsx
@@ -32,18 +32,10 @@ export function App() {
('explorer');
-
- return (
-