Skip to content

Repository files navigation

Splitsy — Decentralized Bill Splitter on the Internet Computer

Splitsy is a Web3 bill-splitting dApp (decentralized application) built on the Internet Computer Protocol (ICP) blockchain. Users authenticate with their Internet Identity (ICP's native, passwordless identity system), create a shared bill, invite participants, and record payments — all on-chain with no central server or database.


Table of Contents


How It Works

  1. Sign In — The user authenticates via Internet Identity (ICP's decentralized identity provider). No username or password is stored anywhere.
  2. Create a Bill — Enter a total amount and a comma-separated list of participant names. The backend canister calculates an equal split and stores the bill on-chain.
  3. Pay a Bill — A participant enters their name and the amount they are paying. The canister records the payment against the active bill.
  4. Check Status — Query the canister to see who has paid and who still has a pending balance.

All state (the active bill and all payments) lives in a Rust canister deployed on ICP — not in a traditional database or cloud service.


Tech Stack

Layer Technology
Blockchain Internet Computer Protocol (ICP)
Backend Canister Rust (ic-cdk 0.17, candid 0.10)
Identity Internet Identity (pulled canister rdmx6-jaaaa-aaaaa-aaadq-cai)
Frontend React 18, Vite, React Router v7
ICP SDK DFX (Dfinity SDK)
Styling Plain CSS + inline styles (Tailwind classes partially used)
Auth Client @dfinity/auth-client ^2.3.0

Project Structure

BillSplitter/
├── dfx.json                        # DFX config — defines all canisters
├── Cargo.toml                      # Rust workspace
├── package.json                    # Root npm workspace
├── deps/                           # Pulled Internet Identity canister
│   ├── pulled.json
│   └── candid/rdmx6-jaaaa-aaaaa-aaadq-cai.did
└── src/
    ├── hackproj_backend/           # Rust canister (on-chain logic)
    │   ├── Cargo.toml
    │   ├── hackproj_backend.did    # Candid interface
    │   └── src/lib.rs              # All backend logic
    └── hackproj_frontend/          # React frontend
        ├── vite.config.js
        ├── index.html
        └── src/
            ├── main.jsx
            ├── App.jsx             # Routing + Internet Identity login
            ├── index.scss
            └── components/
                ├── LandingPage.jsx     # Unauthenticated home / sign-in prompt
                ├── BillManagement.jsx  # Main app UI (create, pay, status, wallet)
                ├── Dashboard.jsx       # Alternate/legacy dashboard UI
                ├── CreateBill.jsx      # Unused duplicate component
                ├── LoginPage.jsx       # Unused legacy username/password form
                ├── navbar.jsx          # Top navigation bar
                └── navbar.css

Prerequisites

  • DFX SDKdfx CLI
  • Rust with the wasm32-unknown-unknown target:
    rustup target add wasm32-unknown-unknown
  • Node.js >= 16 and npm >= 7

Local Development Setup

# 1. Install frontend dependencies
npm install

# 2. Pull the Internet Identity canister dependency
dfx deps pull

# 3. Start the local ICP replica in the background
dfx start --background --clean

# 4. Initialize the pulled Internet Identity canister
dfx deps init

# 5. Deploy all canisters (backend + frontend + Internet Identity)
dfx deploy

# 6. Start the frontend dev server
npm start

Once deployed, visit the URL printed by dfx deploy (usually http://localhost:4943?canisterId=<frontend_canister_id>).

To regenerate the Candid JS bindings after backend changes:

npm run generate

Backend Canister API

Defined in src/hackproj_backend/hackproj_backend.did and implemented in src/hackproj_backend/src/lib.rs.

Function Type Signature Description
create_bill update (nat64, vec text) -> (text) Creates a new active bill with a total amount and list of participant names. Splits evenly.
pay_bill update (text) -> (text) Records a payment for the named user against the active bill.
check_status query () -> (text) Returns which participants still have unpaid balances.

Note: The canister holds only one active bill at a time. Creating a new bill overwrites the previous one.


Known Bugs & Issues

See the fixes needed section below for a full list. A summary:

  • The .did file declares pay_bill as taking one text argument, but the Rust implementation accepts (text, nat64) — this mismatch will cause deployment errors.
  • BillManagement.jsx calls backActor.pay_bill(user, amount, receiver) with three arguments, but the canister only accepts two.
  • BillManagement.jsx calls backActor.create_bill and backActor.pay_bill without await, so responses are never actually used.
  • The "Check Status" button in BillManagement.jsx checks the local wallet balance instead of calling backActor.check_status().
  • The wallet top-up and wallet balance are entirely local state — they are not connected to any on-chain token or ICP ledger.
  • The App.jsx routing is inverted: authenticated users are sent to the landing page and unauthenticated users are redirected to /billmanagement.
  • The Navbar's isLoggedIn state is always false — it is never updated after a successful Internet Identity login.
  • LoginPage.jsx and CreateBill.jsx (exported as BillManagement) are dead code — they are never used in routing.
  • CreateBill.jsx has a duplicate message key in its styles object, which silently discards one of the values.
  • BillManagement.jsx passes a receiver field to pay_bill that the canister does not support.

Deployment to ICP Mainnet

# Make sure you have ICP cycles in your wallet, then:
dfx deploy --network ic

The internet_identity canister does not need to be deployed by you — it already exists on mainnet at rdmx6-jaaaa-aaaaa-aaadq-cai. DFX handles this automatically via the pull type in dfx.json.

About

ICP Decentralised Bill Splitter

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages