Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 53 additions & 27 deletions backend/tests/feed_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Body>,
) -> (StatusCode, Value) {
async fn response_json(router: Router, req: Request<Body>) -> (StatusCode, Value) {
let response = router.oneshot(req).await.expect("oneshot failed");
let status = response.status();
let bytes = response
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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");
Expand Down Expand Up @@ -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;
Expand All @@ -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");
Expand Down
19 changes: 12 additions & 7 deletions contracts/contracts/naira_token/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ 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");
Expand All @@ -25,12 +35,7 @@ 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()
Expand All @@ -43,7 +48,7 @@ impl NairaTokenContract {
}

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()
Expand Down
27 changes: 23 additions & 4 deletions contracts/contracts/social_payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
63 changes: 34 additions & 29 deletions mobileapp/app/transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -357,33 +356,39 @@ function TransferScreen() {
/>
)}
{/* Dropdown results */}
{transferType === "ZAPS" && showDropdown && searchResults.length > 0 && (
<View style={styles.dropdownContainer}>
{searchResults.map((user) => (
<TouchableOpacity
key={user.address}
style={styles.dropdownItem}
onPress={() => handleSelectUser(user)}
activeOpacity={0.75}
>
<View style={styles.dropdownAvatar}>
<Text style={styles.dropdownAvatarText}>
{user.username.charAt(0).toUpperCase()}
</Text>
</View>
<View style={styles.dropdownInfo}>
<Text style={styles.dropdownUsername}>
{user.username}
</Text>
<Text style={styles.dropdownAddress} numberOfLines={1}>
{user.address.slice(0, 10)}…{user.address.slice(-6)}
</Text>
</View>
<Ionicons name="chevron-forward" size={16} color="#BDBDBD" />
</TouchableOpacity>
))}
</View>
)}
{transferType === "ZAPS" &&
showDropdown &&
searchResults.length > 0 && (
<View style={styles.dropdownContainer}>
{searchResults.map((user) => (
<TouchableOpacity
key={user.address}
style={styles.dropdownItem}
onPress={() => handleSelectUser(user)}
activeOpacity={0.75}
>
<View style={styles.dropdownAvatar}>
<Text style={styles.dropdownAvatarText}>
{user.username.charAt(0).toUpperCase()}
</Text>
</View>
<View style={styles.dropdownInfo}>
<Text style={styles.dropdownUsername}>
{user.username}
</Text>
<Text style={styles.dropdownAddress} numberOfLines={1}>
{user.address.slice(0, 10)}…{user.address.slice(-6)}
</Text>
</View>
<Ionicons
name="chevron-forward"
size={16}
color="#BDBDBD"
/>
</TouchableOpacity>
))}
</View>
)}
</View>

{/* Custom Amount Display */}
Expand Down
Loading