Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b076e01
Update README.md
JustAnotherDevv Oct 13, 2024
d2e6c25
init smart contract
Spoyte Oct 13, 2024
685dc51
Merge pull request #2 from JustAnotherDevv/passkey_auth
JustAnotherDevv Oct 13, 2024
0e4569f
Merge pull request #3 from JustAnotherDevv/Spoyte/smart-contract
JustAnotherDevv Oct 13, 2024
0a52dd5
Improved animations
JustAnotherDevv Oct 13, 2024
4c98c71
new test, set_member is the only one working
Spoyte Oct 13, 2024
2051736
user profile
JustAnotherDevv Oct 13, 2024
894596e
Merge pull request #4 from JustAnotherDevv/nevvdevv/animations
JustAnotherDevv Oct 13, 2024
57dd0e4
fix
JustAnotherDevv Oct 13, 2024
91a9ff6
small changes
JustAnotherDevv Oct 13, 2024
bfb5f95
Merge pull request #5 from JustAnotherDevv/nevvdevv/animations
JustAnotherDevv Oct 13, 2024
f2f8e13
added IPFS image upload
JustAnotherDevv Oct 13, 2024
ef0bea5
get and set mostly working
Spoyte Oct 13, 2024
cafaaa3
Merge pull request #6 from JustAnotherDevv/Spoyte/smart-contract
Spoyte Oct 13, 2024
3fd4334
fix
JustAnotherDevv Oct 13, 2024
c43429f
Update README.md
cindytrang Oct 13, 2024
9c673a1
Merge pull request #7 from JustAnotherDevv/nevvdevv/fix
JustAnotherDevv Oct 13, 2024
dff0c23
Update README.md
JustAnotherDevv Oct 13, 2024
cc4e1d4
Update README.md
cindytrang Oct 13, 2024
1a47b28
Update README.md
cindytrang Oct 13, 2024
666eb87
Update README.md
cindytrang Oct 13, 2024
7e4e2ec
Update README.md
cindytrang Oct 13, 2024
f024ba4
Update README.md
cindytrang Oct 13, 2024
c0993a1
landing page login + register
JustAnotherDevv Oct 13, 2024
7cb2659
Added the api post to the ocr recepit system
cindytrang Oct 13, 2024
c3beb8d
Update README.md
cindytrang Oct 13, 2024
8b5fa88
Update README.md
cindytrang Oct 13, 2024
487776e
Update README.md
cindytrang Oct 13, 2024
be47b39
update invoker auth
Spoyte Oct 13, 2024
5fd2f2d
Update README.md
cindytrang Oct 13, 2024
41407db
Update README.md
cindytrang Oct 13, 2024
2b6425f
Update README.md
cindytrang Oct 13, 2024
f7a5390
Update README.md
cindytrang Oct 13, 2024
e19b9e7
Update StellarPay2.rs
Spoyte Oct 13, 2024
791cfca
fix
JustAnotherDevv Oct 13, 2024
6b0dad5
Update README.md
cindytrang Oct 13, 2024
02fa1ff
Update README
Spoyte Oct 13, 2024
92a8f2b
Merge pull request #8 from JustAnotherDevv/Spoyte-test-auth
Spoyte Oct 13, 2024
b63ea4e
Merge pull request #9 from JustAnotherDevv/nevvdevv/fix
JustAnotherDevv Oct 13, 2024
a6fda7c
Merge pull request #10 from JustAnotherDevv/ocr_branch
JustAnotherDevv Oct 13, 2024
e4e0228
Update README.md
JustAnotherDevv Oct 13, 2024
880bdae
Update README.md
JustAnotherDevv Oct 17, 2024
9f3e503
Update README.md
JustAnotherDevv Oct 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 88 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,101 @@
## Payment Splitter
## Splitter Tab

## Description
<p align="center">
<br />
<img src="https://github.com/user-attachments/assets/c80a8cf2-0769-49a9-b277-9cdcc30e77dd" width="400" alt="logo"/>
<br />
</p>
<p align="center"><strong style="font-size: 24px;">Group Splitting Payment App.</strong></p>
<p align="center" style="display: flex; justify-content: center; align-items: center;">
<span style="display: inline-flex; align-items: center; background-color: #1c1c1c; padding: 5px; border-radius: 6px;">
<img src="https://img.shields.io/github/stars/jjjutla/melodot?style=social" alt="GitHub stars"/>
<span style="margin: 0 10px; color: white; font-size: 14px;"></span>
<a href="https://www.easya.io/">
<img src="https://github.com/user-attachments/assets/09cfc307-f04f-4225-8c3b-bc96c47583a6" alt="EasyA" style="height: 21px;"/>
</a>
</span>
</p>

---

Make group payments faster, and effortless, and delay worries about extending funds with just a few clicks.

The Payment Splitter dApp is designed to simplify group payments and make sharing costs as easy as possible for anyone, whether splitting a bill, organizing a group gift, or settling expenses after a trip. With a sleek mobile-first design, the app allows users to interact intuitively and ensures that everyone can quickly settle up.

The app offers multiple features for better usability, including user-friendly payment splitting, expense management, and automatic cost calculations.

```mermaid
graph TD
A[User] -->|Interacts with| B[React Mobile-first dApp]
B -->|Sends requests to| D[Stellar Network]
D -->|Executes| E[Smart Contract]
E -->|Manages| F[Payment Splitting Logic]
F -->|Sends| G[Stablecoins]
G -->|To| H[Recipients]

subgraph Frontend
B -->|State Management| I[React Hooks/Context]
B -->|UI Components| J[shadcn/ui]
B -->|Auth| K[Passkeys / Freighter]
end

subgraph Stellar
D -->|Account Management| N[Stellar Accounts]
D -->|Transaction Handling| O[Stellar SDK]
end
```

### Links

- [Live Demo](https://splittertab.vercel.app/)
- [Demo](https://www.loom.com/share/930ee89b5d7a4f12b8a0923dd61c431c?sid=c239ff8f-8ca2-4d30-b24b-99ee91baf506))
- [Pitch Deck](https://www.canva.com/design/DAGTabHuIc4/BDLopYPbazWElCHx-Q-Z7A/edit?utm_content=DAGTabHuIc4&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton
)

### Problem

### Solution
While blockchain technology is becoming increasingly popular in the finance sector and among software engineers, there remains a significant divide between everyday users and those more experienced with cryptocurrencies. The payment splitting app aims to bridge this gap by offering a simple, intuitive tool that can be adopted by a mainstream audience.

### Impact
One key challenge faced by users in big groups or those in a rush is the time and effort required to manually calculate and track individual expenses. This app solves that problem by offering fast and efficient payment splitting without needing detailed input from each participant. Users can quickly divide costs, even for large groups, without the hassle of collecting too much information.

## Features
The app’s streamlined process allows users to split expenses and execute payments on the go, making it perfect for situations where time is of the essence, like dining with friends, travelling, or group activities. By leveraging blockchain technology and automation, the app ensures smooth, secure transactions while minimizing the complexity for everyday users.

### Images

<img width="1728" alt="Screenshot 2024-10-13 at 11 50 51" src="https://github.com/user-attachments/assets/3577fe7d-3677-4736-b71b-48b428ea3a70">

-
<img width="1728" alt="image" src="https://github.com/user-attachments/assets/6ae7d45f-e28d-47da-af84-02fe3f3ce967">

<img width="1728" alt="image" src="https://github.com/user-attachments/assets/d7585a3a-dd8e-45ef-83d6-75919dfd2747">

<img width="1728" alt="image" src="https://github.com/user-attachments/assets/7dfc9f20-2144-4d6d-a1ce-8aff8e76d929">

<img width="1728" alt="Screenshot 2024-10-13 at 11 53 27" src="https://github.com/user-attachments/assets/e800d840-7844-46ff-a3f8-89d0e766c4e8">


## Features

- Payment Splitting (By Full, By Item, Evenly)
- Lending and Borrowing Compatibility
- Creating Groups/Expenses
- Receipt OCR system to extract the specific costs
- Simple and intuitive UI thanks to Passkeys Integration

## Tech Stack

-
- **Dapp** - React + Vite + shadcn/ui
- **Web3** - Stellar + Soroban smart contracts
- **Auth** - Passkeys / Freigther

## Tech Team

- @nevvdevv
- **NOÉ DE LARMINAT** - noe.de.91@gmail.com | @NodeLarminat
- **CINDY CIENIEK** - cindycieniek@gmail.com | @ciencck28593

## Getting Started

### Getting Started
- Install dependenciex - `pnpm i`
- Build smart Soroban smart contracts and deploy to chosen network(optional)
- Copy content of `.env.example` to `.env` and fill it out with your variables
- Start dapp with command `pnpm run dev`
9 changes: 9 additions & 0 deletions contracts/README
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ stellar network add \
--global testnet \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015"


Contracts deployed:
Testnet:
Upload: https://futurenet.stellarchain.io/transactions/56678a618ce44df498de9a2e433ce9b2ca310a8fff7d2ca4ae0493964c1dd36e
Create: https://futurenet.stellarchain.io/transactions/415a8ec3f1d16425f4ecfc1539749b6c9c2ea564259adaacc2f04942aa1c9ca3
Futurnet:
Upload: https://testnet.stellarchain.io/transactions/0c429f3f64a4a0a6064b7c231e6078a9c0b0a70d5e272970543ccf24b56e945d
Create: https://testnet.stellarchain.io/transactions/ef96c6e47417ecec40743ccf7c4f5b383afa42e09ee1c3689d97997bcd630025
210 changes: 210 additions & 0 deletions contracts/StellarPay.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Vec, Map, Symbol, log, panic_with_error, contracterror};

#[derive(Clone, Debug)]
#[contracttype]
pub struct Group {
group_id: u64,
owner: Address,
total_amount: u64,
members: Vec<Symbol>,
}

#[derive(Clone)]
#[contracttype]
pub struct Member {
user_id: Symbol,
address: Address,
group_balances: Map<u64, u64>,
}

#[derive(Clone)]
#[contracttype]
pub struct Transaction {
user_id: Symbol,
group_id: u64,
amount: u64,
proof: Symbol, // IPFS link
approvals: Vec<Symbol>,
}

#[derive(Clone, PartialEq, Eq)]
#[contracttype]
pub enum DataKey {
Group(u64),
Member(Symbol),
Transaction(u64),
LastGroupId,
LastTransactionId,
GroupList, // List of all groups
}

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Error {
GroupAlreadyExists = 1,
GroupNotFound = 2,
Unauthorized = 3,
MemberAlreadyExists = 4,
MemberNotFound = 5,
UserIdAlreadyExists = 6,
GroupCreationFailed = 7,
}

#[contract]
pub struct MappingContract;

#[contractimpl]
impl MappingContract {
pub fn create_group(env: &Env, owner: Address) -> u64 {
log!(env, "Entering create_group function");
log!(env, "Owner address: {:?}", owner);

let group_id = Self::get_next_group_id(env);
log!(env, "Generated new group_id: {}", group_id);

let owner_symbol = Symbol::short("owner");
log!(env, "Created owner symbol: {:?}", owner_symbol);

let members = Vec::from_array(env, [owner_symbol]);
log!(env, "Created members vector: {:?}", members);

let group = Group {
group_id,
owner: owner.clone(),
total_amount: 0,
members: Vec::new(env),
};
log!(env, "Created new group: {:?}", group);

// Store the individual group
env.storage().persistent().set(&DataKey::Group(group_id), &group);
log!(env, "Stored individual group in persistent storage");

// Update the list of all groups
let mut group_list = env.storage().persistent().get::<DataKey, Vec<Group>>(&DataKey::GroupList)
.unwrap_or_else(|| {
log!(env, "Group list not found, creating new list");
Vec::new(env)
});
log!(env, "Current group list size: {}", group_list.len());

group_list.push_back(group);
log!(env, "Added new group to list. New size: {}", group_list.len());

env.storage().persistent().set(&DataKey::GroupList, &group_list);
log!(env, "Stored updated group list in persistent storage");

log!(env, "Exiting create_group function. Returning group_id: {}", group_id);
group_id
}

pub fn create_member(env: &Env, user_id: Symbol, address: Address) {
if env.storage().persistent().has(&DataKey::Member(user_id.clone())) {
panic_with_error!(env, Error::UserIdAlreadyExists);
}
let member = Member {
user_id: user_id.clone(),
address,
group_balances: Map::new(env),
};
env.storage().persistent().set(&DataKey::Member(user_id), &member);
}

pub fn add_member_to_group(env: &Env, group_id: u64, user_id: Symbol, caller: Address) {
let mut group = env.storage().persistent().get::<DataKey, Group>(&DataKey::Group(group_id))
.unwrap_or_else(|| panic_with_error!(env, Error::GroupNotFound));

if group.owner != caller {
panic_with_error!(env, Error::Unauthorized);
}

if group.members.contains(&user_id) {
panic_with_error!(env, Error::MemberAlreadyExists);
}

group.members.push_back(user_id.clone());
env.storage().persistent().set(&DataKey::Group(group_id), &group);

let mut member = env.storage().persistent().get::<DataKey, Member>(&DataKey::Member(user_id.clone()))
.unwrap_or_else(|| panic_with_error!(env, Error::MemberNotFound));
member.group_balances.set(group_id, 0);
env.storage().persistent().set(&DataKey::Member(user_id), &member);
}

pub fn add_transaction(env: &Env, user_id: Symbol, group_id: u64, amount: u64, proof: Symbol) -> u64 {
let tx_id = Self::get_next_transaction_id(env);
let transaction = Transaction {
user_id: user_id.clone(),
group_id,
amount,
proof,
approvals: Vec::new(env),
};
env.storage().persistent().set(&DataKey::Transaction(tx_id), &transaction);

// Update group total amount
let mut group = env.storage().persistent().get::<DataKey, Group>(&DataKey::Group(group_id))
.unwrap_or_else(|| panic_with_error!(env, Error::GroupNotFound));
group.total_amount += amount;
env.storage().persistent().set(&DataKey::Group(group_id), &group);

// Update member's balance in the group
let mut member = env.storage().persistent().get::<DataKey, Member>(&DataKey::Member(user_id.clone()))
.unwrap_or_else(|| panic_with_error!(env, Error::MemberNotFound));
let new_balance = member.group_balances.get(group_id).unwrap_or(0) + amount;
member.group_balances.set(group_id, new_balance);
env.storage().persistent().set(&DataKey::Member(user_id.clone()), &member);

tx_id
}

pub fn approve_transaction(env: &Env, tx_id: u64, approver_id: Symbol) {
let mut transaction = env.storage().persistent().get::<DataKey, Transaction>(&DataKey::Transaction(tx_id))
.unwrap_or_else(|| panic_with_error!(env, Error::GroupNotFound));

let group = env.storage().persistent().get::<DataKey, Group>(&DataKey::Group(transaction.group_id))
.unwrap_or_else(|| panic_with_error!(env, Error::GroupNotFound));

if !group.members.contains(&approver_id) {
panic_with_error!(env, Error::Unauthorized);
}

if !transaction.approvals.contains(&approver_id) {
transaction.approvals.push_back(approver_id);
env.storage().persistent().set(&DataKey::Transaction(tx_id), &transaction);
}
}

pub fn get_all_groups(env: &Env) -> Vec<Group> {
env.storage().persistent().get::<DataKey, Vec<Group>>(&DataKey::GroupList)
.unwrap_or_else(|| Vec::new(env))
}

fn get_next_group_id(env: &Env) -> u64 {
log!(env, "Entering get_next_group_id function");

let last_id = env.storage().persistent().get::<DataKey, u64>(&DataKey::LastGroupId).unwrap_or(0);
log!(env, "Last group ID: {}", last_id);

let new_id = last_id.checked_add(1).unwrap_or_else(|| {
log!(env, "Group ID overflow occurred");
panic_with_error!(env, Error::GroupCreationFailed);
});
log!(env, "New group ID: {}", new_id);

env.storage().persistent().set(&DataKey::LastGroupId, &new_id);
log!(env, "Stored new last group ID in persistent storage");

log!(env, "Exiting get_next_group_id function. Returning new_id: {}", new_id);
new_id
}

fn get_next_transaction_id(env: &Env) -> u64 {
let last_id = env.storage().persistent().get::<DataKey, u64>(&DataKey::LastTransactionId).unwrap_or(0);
let new_id = last_id.checked_add(1).unwrap_or_else(|| panic_with_error!(env, Error::GroupCreationFailed));
env.storage().persistent().set(&DataKey::LastTransactionId, &new_id);
new_id
}
}
Loading