From 77fa9dc37f564c8743687e9d5e398634841cacd4 Mon Sep 17 00:00:00 2001 From: Justice Date: Fri, 26 Jun 2026 13:21:21 +0100 Subject: [PATCH 1/2] fix: gate naira mint and burn by admin --- contracts/contracts/naira_token/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/contracts/naira_token/src/lib.rs b/contracts/contracts/naira_token/src/lib.rs index 2d32ff4..498107d 100644 --- a/contracts/contracts/naira_token/src/lib.rs +++ b/contracts/contracts/naira_token/src/lib.rs @@ -15,6 +15,12 @@ pub struct NairaTokenContract; #[contractimpl] impl NairaTokenContract { + fn require_admin(env: &Env) -> Address { + let admin: Address = env.storage().instance().get(&ADMIN_KEY).expect("not initialized"); + admin.require_auth(); + admin + } + pub fn initialize(env: Env, admin: Address, _name: String, _symbol: String) { if env.storage().instance().has(&ADMIN_KEY) { panic!("already initialized"); @@ -23,15 +29,14 @@ impl NairaTokenContract { } pub fn mint(env: Env, to: Address, amount: i128) { - let admin: Address = env.storage().instance().get(&ADMIN_KEY).expect("not initialized"); - admin.require_auth(); + Self::require_admin(&env); assert!(amount > 0, "amount must be positive"); let bal: i128 = env.storage().persistent().get(&DataKey::Balance(to.clone())).unwrap_or(0); env.storage().persistent().set(&DataKey::Balance(to), &(bal + amount)); } pub fn burn(env: Env, from: Address, amount: i128) { - from.require_auth(); + Self::require_admin(&env); assert!(amount > 0, "amount must be positive"); let bal: i128 = env.storage().persistent().get(&DataKey::Balance(from.clone())).unwrap_or(0); assert!(bal >= amount, "insufficient balance"); From af9a1a676c355eca1628de56f12217108a334eb7 Mon Sep 17 00:00:00 2001 From: Justice Date: Fri, 26 Jun 2026 13:36:57 +0100 Subject: [PATCH 2/2] style: format ci-triggered files --- backend/tests/feed_integration.rs | 80 ++++++++++++------- contracts/contracts/naira_token/src/lib.rs | 6 +- contracts/contracts/social_payment/src/lib.rs | 27 ++++++- mobileapp/app/transfer.tsx | 63 ++++++++------- 4 files changed, 115 insertions(+), 61 deletions(-) diff --git a/backend/tests/feed_integration.rs b/backend/tests/feed_integration.rs index c3a5adc..bccc467 100644 --- a/backend/tests/feed_integration.rs +++ b/backend/tests/feed_integration.rs @@ -104,10 +104,7 @@ async fn seed_friendship(pool: &PgPool, user_id: Uuid, friend_id: Uuid) { } /// Parse the JSON response body from a oneshot call. -async fn response_json( - router: Router, - req: Request, -) -> (StatusCode, Value) { +async fn response_json(router: Router, req: Request) -> (StatusCode, Value) { let response = router.oneshot(req).await.expect("oneshot failed"); let status = response.status(); let bytes = response @@ -142,8 +139,14 @@ async fn test_public_feed_pagination() { // Use stable, collision-resistant address suffixes based on a random run id let run = Uuid::new_v4().to_string().replace('-', "")[..8].to_string(); - let sender_addr = format!("GSENDER{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", run); - let receiver_addr = format!("GRECEIVER{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", run); + let sender_addr = format!( + "GSENDER{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + run + ); + let receiver_addr = format!( + "GRECEIVER{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + run + ); let sender_id = seed_user(&pool, &format!("sender_{run}"), &sender_addr).await; let receiver_id = seed_user(&pool, &format!("receiver_{run}"), &receiver_addr).await; @@ -159,25 +162,27 @@ async fn test_public_feed_pagination() { let router = feed_router(pool.clone()); // Page 1 - let (status, body) = response_json( - router.clone(), - get_req("/public?limit=10&offset=0", None), - ) - .await; + let (status, body) = + response_json(router.clone(), get_req("/public?limit=10&offset=0", None)).await; assert_eq!(status, StatusCode::OK, "page 1 status"); let items = body.as_array().expect("expected array on page 1"); - assert!(items.len() >= 10, "page 1 should have at least 10 items; got {}", items.len()); + assert!( + items.len() >= 10, + "page 1 should have at least 10 items; got {}", + items.len() + ); // Page 2 (different offset) - let (status2, body2) = response_json( - router.clone(), - get_req("/public?limit=10&offset=10", None), - ) - .await; + let (status2, body2) = + response_json(router.clone(), get_req("/public?limit=10&offset=10", None)).await; assert_eq!(status2, StatusCode::OK, "page 2 status"); let items2 = body2.as_array().expect("expected array on page 2"); // Must have at least 5 from our seed; the exact count depends on other data in the DB - assert!(items2.len() >= 5, "page 2 should have at least 5 items; got {}", items2.len()); + assert!( + items2.len() >= 5, + "page 2 should have at least 5 items; got {}", + items2.len() + ); // Cleanup for id in &payment_ids { @@ -212,8 +217,14 @@ async fn test_friends_feed_privacy() { let pool = test_pool().await; let run = Uuid::new_v4().to_string().replace('-', "")[..8].to_string(); - let addr_a = format!("GUSERA{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", run); - let addr_b = format!("GUSERB{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", run); + let addr_a = format!( + "GUSERA{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + run + ); + let addr_b = format!( + "GUSERB{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + run + ); let user_a = seed_user(&pool, &format!("user_a_{run}"), &addr_a).await; let user_b = seed_user(&pool, &format!("user_b_{run}"), &addr_b).await; @@ -232,8 +243,11 @@ async fn test_friends_feed_privacy() { let token_a = addr_a.clone(); let router = feed_router(pool.clone()); - let (status, body) = - response_json(router, get_req("/friends?limit=50&offset=0", Some(&token_a))).await; + let (status, body) = response_json( + router, + get_req("/friends?limit=50&offset=0", Some(&token_a)), + ) + .await; assert_eq!(status, StatusCode::OK, "friends feed status"); let items = body.as_array().expect("expected array"); @@ -290,9 +304,18 @@ async fn test_private_feed_isolation() { let pool = test_pool().await; let run = Uuid::new_v4().to_string().replace('-', "")[..8].to_string(); - let addr_a = format!("GISOA{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", run); - let addr_b = format!("GISOB{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", run); - let addr_c = format!("GISOC{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", run); + let addr_a = format!( + "GISOA{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + run + ); + let addr_b = format!( + "GISOB{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + run + ); + let addr_c = format!( + "GISOC{}XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + run + ); let user_a = seed_user(&pool, &format!("iso_a_{run}"), &addr_a).await; let user_b = seed_user(&pool, &format!("iso_b_{run}"), &addr_b).await; @@ -305,8 +328,11 @@ async fn test_private_feed_isolation() { let token_a = addr_a.clone(); let router = feed_router(pool.clone()); - let (status, body) = - response_json(router, get_req("/private?limit=50&offset=0", Some(&token_a))).await; + let (status, body) = response_json( + router, + get_req("/private?limit=50&offset=0", Some(&token_a)), + ) + .await; assert_eq!(status, StatusCode::OK, "private feed status"); let items = body.as_array().expect("expected array"); diff --git a/contracts/contracts/naira_token/src/lib.rs b/contracts/contracts/naira_token/src/lib.rs index 6e528e5..5502e4b 100644 --- a/contracts/contracts/naira_token/src/lib.rs +++ b/contracts/contracts/naira_token/src/lib.rs @@ -18,7 +18,11 @@ pub struct NairaTokenContract; #[contractimpl] impl NairaTokenContract { fn require_admin(env: &Env) -> Address { - let admin: Address = env.storage().instance().get(&ADMIN_KEY).expect("not initialized"); + let admin: Address = env + .storage() + .instance() + .get(&ADMIN_KEY) + .expect("not initialized"); admin.require_auth(); admin } diff --git a/contracts/contracts/social_payment/src/lib.rs b/contracts/contracts/social_payment/src/lib.rs index 03e6d36..461d0c4 100644 --- a/contracts/contracts/social_payment/src/lib.rs +++ b/contracts/contracts/social_payment/src/lib.rs @@ -50,7 +50,11 @@ impl SocialPaymentContract { } pub fn set_fee_coefficient(env: Env, fee_coef: u32) { - let admin: Address = env.storage().instance().get(&ADMIN_KEY).expect("not initialized"); + let admin: Address = env + .storage() + .instance() + .get(&ADMIN_KEY) + .expect("not initialized"); admin.require_auth(); if fee_coef > 10000 { panic!("fee coefficient cannot exceed 10000 basis points"); @@ -80,8 +84,16 @@ impl SocialPaymentContract { let token_client = soroban_sdk::token::Client::new(&env, &token); if visibility == Visibility::Public { - let treasury: Address = env.storage().instance().get(&TREAS_KEY).expect("treasury not initialized"); - let fee_coef = env.storage().instance().get(&FEE_COEFF_KEY).unwrap_or(10u32); + let treasury: Address = env + .storage() + .instance() + .get(&TREAS_KEY) + .expect("treasury not initialized"); + let fee_coef = env + .storage() + .instance() + .get(&FEE_COEFF_KEY) + .unwrap_or(10u32); let fee = amount * (fee_coef as i128) / 10000; let receiver_amount = amount - fee; token_client.transfer(&sender, &receiver, &receiver_amount); @@ -311,7 +323,14 @@ mod tests { client.set_fee_coefficient(&50); assert_eq!(client.fee_coefficient(), 50); - client.pay(&sender, &receiver, &token, &1000, &String::from_str(&env, "Public payment"), &Visibility::Public); + client.pay( + &sender, + &receiver, + &token, + &1000, + &String::from_str(&env, "Public payment"), + &Visibility::Public, + ); // 1000 * 50 / 10000 = 5 fee, 995 receiver assert_eq!(token_client.balance(&receiver), 995); diff --git a/mobileapp/app/transfer.tsx b/mobileapp/app/transfer.tsx index 2d6a924..fa21c60 100644 --- a/mobileapp/app/transfer.tsx +++ b/mobileapp/app/transfer.tsx @@ -102,8 +102,7 @@ const TokenSelectCard = ({ ); const API_BASE = - (typeof process !== "undefined" && - process.env?.EXPO_PUBLIC_API_URL) || + (typeof process !== "undefined" && process.env?.EXPO_PUBLIC_API_URL) || "http://localhost:8080"; interface ZapsUser { @@ -357,33 +356,39 @@ function TransferScreen() { /> )} {/* Dropdown results */} - {transferType === "ZAPS" && showDropdown && searchResults.length > 0 && ( - - {searchResults.map((user) => ( - handleSelectUser(user)} - activeOpacity={0.75} - > - - - {user.username.charAt(0).toUpperCase()} - - - - - {user.username} - - - {user.address.slice(0, 10)}…{user.address.slice(-6)} - - - - - ))} - - )} + {transferType === "ZAPS" && + showDropdown && + searchResults.length > 0 && ( + + {searchResults.map((user) => ( + handleSelectUser(user)} + activeOpacity={0.75} + > + + + {user.username.charAt(0).toUpperCase()} + + + + + {user.username} + + + {user.address.slice(0, 10)}…{user.address.slice(-6)} + + + + + ))} + + )} {/* Custom Amount Display */}