A contract + off-chain helper system for tracking blockchain events and delivering real-time notifications.
NotifyChain is an open-source event monitoring and notification system designed for smart contracts. It combines on-chain event emission with an off-chain listener service to track contract activity and trigger custom actions such as notifications, webhooks, emails, or integrations with external applications.
The project enables developers to build reactive decentralized applications without continuously polling the blockchain.
- Architecture Overview
- Project Structure
- Event Flow
- Local Development Guide
- Smart Contract Upgrade Guide
- Contract API Examples
- Freighter Troubleshooting
- Wallet UX States
- Features
- Use Cases
- Tech Stack
- Contributing
- License
Listener service docs:
- API Contract and Event Reference
- API Usage Cookbook
- Notification Failure Recovery — retry lifecycle, configuration, and troubleshooting. Notification Lifecycle — creation, delivery, acknowledgment semantics, retries, and archival. Notification Failure Recovery — retry lifecycle, configuration, and troubleshooting. Listener service docs: Notification Failure Recovery — retry lifecycle, configuration, and troubleshooting.
Event reference: Smart Contract Event Reference Guide — all emitted events, parameters, data types, and usage recommendations for indexers and listeners.
NotifyChain is built from three cooperating layers. On-chain contracts emit events, an off-chain listener turns those events into notifications and a queryable feed, and a dashboard renders that feed for humans. Each layer can be run and developed independently.
| Component | Location | Tech | Responsibility |
|---|---|---|---|
| Smart Contracts | contract/, Documents/Task Bounty/ |
Soroban / Rust | Execute business logic and emit a structured event for every important state change |
| Listener Service | listener/ |
Node.js / TypeScript | Poll the Stellar network for contract events, deduplicate them, push notifications, and expose an HTTP events API |
| Dashboard | dashboard/ |
React + Vite | Fetch the listener's events API and display real-time contract activity |
On-chain Off-chain
┌────────────────────┐ ┌──────────────────────────────┐
│ Soroban Contracts │ │ Listener Service │
│ (TaskBounty, │ emit │ ┌────────────────────────┐ │
│ AutoShare) │ ──────► │ │ EventSubscriber (poll) │ │
│ │ events │ └───────────┬────────────┘ │
└────────────────────┘ │ ▼ │
▲ │ ┌────────────────────────┐ │
│ invoke │ │ Deduplicator + Registry │ │
│ │ └───────────┬────────────┘ │
┌────────────────────┐ │ ┌────────┴────────┐ │
│ Users / dApps │ │ ▼ ▼ │
└────────────────────┘ │ Discord /api/events │
│ webhook HTTP API │
└──────────────────────┬─────────┘
│ fetch
▼
┌────────────────────┐
│ React Dashboard │
└────────────────────┘
A visual system architecture diagram with Mermaid diagrams spanning all layers is available in
SYSTEM_ARCHITECTURE.md. It provides the quickest way to understand the full system at a glance.A high-level, contributor-facing architecture guide lives in
ARCHITECTURE_OVERVIEW.md. It walks new contributors through the on-chain, off-chain, and dashboard layers, the end-to-end data flow, and links out to every subsystem doc.A more detailed, contract-level architecture write-up lives in
Documents/Task Bounty/ARCHITECTURE.md.
The on-chain layer is the source of truth. Each contract owns its own state and emits typed events (see Event Flow) that the off-chain layer consumes. Two example contracts ship with the project:
A decentralized task and reward board that allows users to:
- Create tasks with escrowed rewards
- Submit work
- Approve/reject submissions
- Raise disputes
- Manage payouts automatically
Key Modules:
types.rs: Data structures and enumsstorage.rs: Storage access patternstask.rs: Task creation and managementsubmission.rs: Work submission handlingdispute.rs: Dispute resolutionevents.rs: Event emission
A subscription and group management contract that allows users to:
- Create sharing groups
- Manage group members
- Handle subscription payments
- Track usage
- Admin management
- Expose contract version for verification
Key Modules:
base/types.rs: Data structuresbase/errors.rs: Error definitionsbase/events.rs: Event emissioninterfaces/autoshare.rs: Interface definitionsautoshare_logic.rs: Core business logicmock_token.rs: Mock token for testingtests/: Comprehensive test suite
Notify-Chain/
├── contract/ # Soroban contract workspace
│ ├── contracts/
│ │ └── hello-world/ # AutoShare contract implementation
│ │ ├── src/
│ │ │ ├── base/
│ │ │ │ ├── errors.rs # Error definitions
│ │ │ │ ├── events.rs # Event types
│ │ │ │ └── types.rs # Data structures
│ │ │ ├── interfaces/
│ │ │ │ └── autoshare.rs # Interfaces
│ │ │ ├── tests/
│ │ │ │ ├── autoshare_test.rs
│ │ │ │ ├── mock_token_test.rs
│ │ │ │ ├── pause_test.rs
│ │ │ │ ├── test_utils.rs
│ │ │ │ └── test_utils_test.rs
│ │ │ ├── autoshare_logic.rs # Core contract logic
│ │ │ ├── lib.rs # Contract entry point
│ │ │ └── mock_token.rs # Mock token for testing
│ │ ├── Cargo.toml
│ │ ├── Makefile
│ │ └── build_log.txt
│ ├── Cargo.toml # Workspace configuration
│ └── README.md
├── listener/ # Off-chain listener service (Node + TS)
│ └── src/
│ ├── api/ # Events HTTP API (/api/events, /health)
│ ├── services/ # Subscriber, deduplicator, Discord notifier
│ ├── store/ # In-memory event registry
│ ├── utils/ # Logging, formatting, helpers
│ └── index.ts # Service entry point
├── dashboard/ # Real-time event dashboard (React + Vite)
│ └── src/
│ ├── components/ # Event list / card / filter UI
│ ├── services/ # Events API client
│ └── store/ # Client-side event store (Zustand)
├── Documents/
│ ├── Task Bounty/ # TaskBounty contract and docs
│ │ ├── src/
│ │ │ ├── dispute.rs
│ │ │ ├── events.rs
│ │ │ ├── lib.rs
│ │ │ ├── storage.rs
│ │ │ ├── submission.rs
│ │ │ ├── task.rs
│ │ │ ├── test.rs
│ │ │ └── types.rs
│ │ ├── API.md
│ │ ├── ARCHITECTURE.md
│ │ ├── CONTRIBUTING.md
│ │ ├── PROJECT_CHECKLIST.md
│ │ ├── PROJECT_OVERVIEW.md
│ │ ├── README.md
│ │ ├── SETUP.md
│ │ ├── SUMMARY.md
│ │ ├── WORKFLOWS.md
│ │ └── Cargo.toml
│ └── Stellar-save/
├── .vscode/
│ └── settings.json
├── README.md # This file
├── ARCHITECTURE_OVERVIEW.md # High-level architecture guide (issue #137)
├── SYSTEM_ARCHITECTURE.md # Visual system architecture with Mermaid diagrams (issue #97)
└── .gitignore
This is how a single on-chain action becomes a delivered notification:
1. A user invokes a contract function (e.g. create_task)
↓
2. The contract updates state and emits a typed event
↓
3. The listener's EventSubscriber polls the Stellar RPC and picks up the event
↓
4. The event is validated, parsed, and recorded in the in-memory event registry
↓
5. The deduplicator drops events already seen (by contract + event id)
↓
6. A Discord notification is sent (if a webhook is configured)
↓
7. The dashboard fetches GET /api/events and renders the new activity
Key pieces of the off-chain pipeline:
EventSubscriber(listener/src/services/event-subscriber.ts) polls the configured contracts on an interval and reconnects on failure.NotificationDeduplicator(listener/src/services/notification-deduplicator.ts) prevents the same event from being notified twice.DiscordNotificationService(listener/src/services/discord-notification.ts) formats and delivers notifications.- Events API (
listener/src/api/events-server.ts) exposesGET /api/eventsfor the dashboard andGET /healthfor monitoring.
The contract events that drive this flow are listed below.
| Event | Trigger | Data Included |
|---|---|---|
TaskCreated |
When a new task is created | Task ID, Poster address, Title, Reward, Deadline |
WorkSubmitted |
When work is submitted for a task | Task ID, Submission ID, Contributor, Work URL |
SubmissionApproved |
When a submission is approved | Task ID, Submission ID, Contributor, Reward |
SubmissionRejected |
When a submission is rejected | Task ID, Submission ID, Contributor |
TaskCancelled |
When a task is cancelled | Task ID, Poster address |
DisputeRaised |
When a dispute is raised | Task ID, Submission ID, Raiser, Reason |
1. Poster calls create_task()
↓
2. Contract escrows reward tokens
↓
3. Contract emits TaskCreated event
↓
4. Contributor calls submit_work()
↓
5. Contract emits WorkSubmitted event
↓
6. Poster calls approve_submission()
↓
7. Contract transfers reward to contributor
↓
8. Contract emits SubmissionApproved event
| Event | Trigger | Data Included |
|---|---|---|
AutoshareCreated |
When a new AutoShare group is created | Creator address, Group ID |
AutoshareUpdated |
When a group is updated | Updater address, Group ID |
ContractPaused |
When the contract is paused | N/A |
ContractUnpaused |
When the contract is unpaused | N/A |
GroupDeactivated |
When a group is deactivated | Creator address, Group ID |
GroupActivated |
When a group is activated | Creator address, Group ID |
AdminTransferred |
When admin rights are transferred | Old admin, New admin |
Withdrawal |
When tokens are withdrawn | Token address, Recipient, Amount |
Before getting started, make sure you have the following installed:
- Rust: The programming language used for Soroban contracts
- WebAssembly Target: For compiling to Wasm
- Stellar CLI: For interacting with Soroban contracts
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/envVerify installation:
rustc --version
cargo --versionrustup target add wasm32-unknown-unknowncargo install --locked stellar-cli --features optVerify installation:
stellar --version-
Clone the repository:
git clone https://github.com/your-org/notify-chain.git cd notify-chain -
Building the AutoShare contract:
cd contract stellar contract build -
Running tests for AutoShare contract:
cd contracts/hello-world cargo test
-
Building the TaskBounty contract:
cd ../../Documents/Task\ Bounty cargo build --target wasm32-unknown-unknown --release # Or using Stellar CLI: stellar contract build
-
Running tests for TaskBounty contract:
cargo test
stellar keys generate test-user --network testnetstellar keys fund test-user --network testnetcd contract/contracts/hello-world
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/hello_world.wasm \
--source test-user \
--network testnetstellar contract invoke \
--id <CONTRACT_ID> \
--source test-user \
--network testnet \
-- \
initialize_admin \
--admin <ADMIN_ADDRESS>| Command | Purpose |
|---|---|
stellar contract build |
Build a contract |
cargo test |
Run tests |
stellar contract deploy |
Deploy a contract |
stellar contract invoke |
Call a contract function |
stellar contract optimize |
Optimize contract for deployment |
stellar keys list |
List your identities |
Install the following extensions for a smooth development experience:
- rust-analyzer - Rust language support
- CodeLLDB - Debugger
- Better TOML - TOML file support
Add this to .vscode/settings.json:
{
"rust-analyzer.cargo.target": "wasm32-unknown-unknown",
"rust-analyzer.checkOnSave.allTargets": false
}Before changing contract storage, public methods, event schemas, authorization rules, or deployment artifacts, read the Smart Contract Upgrade Guide. It documents the recommended upgrade workflow, prerequisites, testnet verification steps, rollback procedures, risk checklist, and PR template for NotifyChain contract changes.
Full examples with concrete parameter values live in docs/contract-api.md.
Quick reference below.
stellar contract invoke \
--id <CONTRACT_ID> --source creator-key --network testnet \
-- create \
--id 0000000000000000000000000000000000000000000000000000000000000001 \
--name "Team Alpha Plan" \
--creator GABC1234...XYZ \
--usage_count 100 \
--payment_token CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSCExpected: group stored on-chain with is_active = true; AutoshareCreated event emitted; creator debited 100 × usage_fee tokens.
stellar contract invoke \
--id <CONTRACT_ID> --source payer-key --network testnet \
-- topup_subscription \
--id 0000000000000000000000000000000000000000000000000000000000000001 \
--additional_usages 50 \
--payment_token CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC \
--payer GABC1234...XYZExpected: usage_count increases by 50; payer debited 50 × usage_fee; PaymentHistory record appended.
stellar contract invoke \
--id <CONTRACT_ID> --source creator-key --network testnet \
-- deactivate_group \
--id 0000000000000000000000000000000000000000000000000000000000000001 \
--caller GABC1234...XYZExpected: is_active set to false; GroupDeactivated event emitted; subsequent payment calls will fail.
Common Freighter wallet issues and how to resolve them.
Symptom: window.freighter is undefined after page load.
Steps:
- Install the Freighter extension for Chrome or Firefox.
- Refresh the page — Freighter injects
window.freighteron load. - If still undefined, check that the extension is enabled in your browser's extension manager.
- Disable other wallet extensions temporarily; some conflict with Freighter's injection.
Symptom: Calling freighter.requestAccess() returns but no popup opens.
Steps:
- Click the Freighter icon in the browser toolbar and unlock the wallet with your password.
- Check that the site URL matches the allowed origins in Freighter → Settings → Connected Apps.
- Disable popup-blocking for
localhostor your dApp domain. - Try in an incognito window with only Freighter enabled.
Symptom: freighter.requestAccess() throws or returns { error: "User declined" }.
Steps:
- Re-prompt the user — the rejection is not permanent.
- If auto-rejected, go to Freighter → Connected Apps and remove the site entry, then retry.
Symptom: freighter.signTransaction() never resolves.
Steps:
- Unlock Freighter and switch to the correct network (Testnet / Mainnet).
- Check that the transaction's
networkPassphrasematches the network selected in Freighter. - Ensure the transaction fee is sufficient — underfunded transactions are silently dropped.
- Rebuild the transaction with a fresh sequence number if the account state changed.
Symptom: Transaction is signed but fails with txBAD_SEQ or similar.
Steps:
- Open Freighter and switch to the matching network (Testnet for development, Mainnet for production).
- Verify
Networks.TESTNET/Networks.MAINNETpassphrase is passed to the transaction builder.
Symptom: Freighter asks for access each session.
Steps:
- Call
freighter.isConnected()beforerequestAccess(); skip the prompt when already connected. - Ensure your dApp is served over HTTPS (or
localhost) — Freighter restricts persistent permissions to secure origins.
The frontend models four wallet connection states. Every UI that depends on the wallet must handle all of them.
| State | Description | User-facing message |
|---|---|---|
disconnected |
No wallet connected or access not yet granted | "Connect your Freighter wallet to continue." |
connected |
Wallet access granted, public key available, no pending action | "Wallet connected: G…XYZ" |
waiting_for_signature |
Transaction built and sent to Freighter, awaiting user approval | "Check Freighter — please approve the transaction." |
error |
Connection failed, user rejected, or transaction error | "Wallet error: <message>. Please try again." |
disconnected
│ user clicks "Connect"
▼
[requestAccess()]
│ granted │ rejected / error
▼ ▼
connected error ──► disconnected (retry)
│ user submits form
▼
[signTransaction()]
│ pending approval
▼
waiting_for_signature
│ approved │ rejected / timeout
▼ ▼
connected error
See frontend/src/components/SubscriptionForm.tsx for a working example of all four states.
- 📡 Real-time blockchain event monitoring
- 🔗 Smart contract event emission
- ⚡ Off-chain listener service
- 🔔 Custom notification triggers
- 🌐 Webhook support for external integrations
- 📝 Event logging and processing
- 🛠️ Easy integration into existing dApps
- 🔒 Trustless and transparent event tracking
- Task completion notifications
- Escrow payment updates
- NFT mint alerts
- DAO proposal events
- Bounty submissions
- Token transfers
- Marketplace purchases
- Governance voting updates
- DeFi protocol monitoring
- Custom application events
- Soroban (Stellar smart contracts)
- Rust
- Node.js
- TypeScript
- Stellar SDK
- React + Vite (dashboard)
- Discord (implemented)
- Email, Telegram, Slack, Webhooks, Push Notifications (planned)
Contributions are welcome! Please follow these steps (or start with the canonical workflow guide):
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to your branch
- Open a Pull Request
Please follow the project's coding standards and include tests where applicable.
For more detailed contribution guidelines, check:
Documents/Task Bounty/CONTRIBUTING.md
This project is licensed under the MIT License.
NotifyChain is built to simplify event-driven blockchain development by bridging smart contract events with off-chain automation and notification systems.
To run the staging environment locally:
- Export environment variables:
export $(cat listener/.env.staging | xargs) - Build and run listener:
cd listener && npm ci && npm run build && npm start - Run health check:
./scripts/health-check.sh