Skip to content
Open
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
69 changes: 54 additions & 15 deletions contracts/invoice_nft/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ pub struct Invoice {

#[contracttype]
pub enum DataKey {
Invoice(u64), // Maps ID -> Invoice
TokenId, // Tracks the next available ID
Invoice(u64), // Maps ID -> Invoice
TokenId, // Tracks the next available ID
BackendPubkey, // Backend public key for signature verification
}

Expand All @@ -32,7 +32,10 @@ impl InvoiceContract {

// Helper function to check admin authorization
fn require_admin(env: &Env) {
let admin: Address = env.storage().instance().get(&DataKey::BackendPubkey)
let admin: Address = env
.storage()
.instance()
.get(&DataKey::BackendPubkey)
.expect("Backend pubkey not set");
admin.require_auth();
}
Expand All @@ -41,13 +44,24 @@ impl InvoiceContract {
pub fn set_backend_pubkey(env: Env, pubkey: BytesN<32>) {
// For simplicity, we'll allow anyone to set this initially
// In production, this should be admin-only
env.storage().instance().set(&DataKey::BackendPubkey, &pubkey);
env.storage()
.instance()
.set(&DataKey::BackendPubkey, &pubkey);
Self::extend_storage_ttl(&env);
}

// Helper function to verify backend signature
fn verify_signature(env: &Env, user: &Address, amount: i128, risk_score: u32, signature: &BytesN<64>) -> bool {
let backend_pubkey: BytesN<32> = env.storage().instance().get(&DataKey::BackendPubkey)
fn verify_signature(
env: &Env,
_user: &Address,
amount: i128,
risk_score: u32,
signature: &BytesN<64>,
) -> bool {
let backend_pubkey: BytesN<32> = env
.storage()
.instance()
.get(&DataKey::BackendPubkey)
.expect("Backend pubkey not set");

// Create message payload: (user_address, invoice_amount, risk_score)
Expand All @@ -60,7 +74,14 @@ impl InvoiceContract {
}

// 1. MINT: Create a new Invoice NFT with signature verification
pub fn mint(env: Env, owner: Address, amount: i128, due_date: u64, risk_score: u32, signature: BytesN<64>) -> u64 {
pub fn mint(
env: Env,
owner: Address,
amount: i128,
due_date: u64,
risk_score: u32,
signature: BytesN<64>,
) -> u64 {
owner.require_auth(); // Ensure the caller is who they say they are

// Check if invoice is expired
Expand All @@ -75,7 +96,11 @@ impl InvoiceContract {
}

// Get the current ID count
let mut current_id = env.storage().instance().get(&DataKey::TokenId).unwrap_or(0u64);
let mut current_id = env
.storage()
.instance()
.get(&DataKey::TokenId)
.unwrap_or(0u64);
current_id += 1;

// Create the invoice object
Expand All @@ -88,7 +113,9 @@ impl InvoiceContract {
};

// Save to storage
env.storage().instance().set(&DataKey::Invoice(current_id), &invoice);
env.storage()
.instance()
.set(&DataKey::Invoice(current_id), &invoice);
env.storage().instance().set(&DataKey::TokenId, &current_id);
Self::extend_storage_ttl(&env);

Expand All @@ -99,22 +126,34 @@ impl InvoiceContract {
}

// 2. GET: Read invoice details
pub fn get_invoice(env: Env, id: u64) -> Option<Invoice> {
env.storage().instance().get(&DataKey::Invoice(id))
/// Get invoice details by ID (read-only view function)
///
/// Panics with "InvoiceNotFound" if the ID does not exist
pub fn get_invoice(env: Env, id: u64) -> Invoice {
env.storage()
.instance()
.get(&DataKey::Invoice(id))
.unwrap_or_else(|| panic!("InvoiceNotFound"))
}

// 3. REPAY: Mark the invoice as paid
pub fn repay(env: Env, id: u64) {
let mut invoice: Invoice = env.storage().instance().get(&DataKey::Invoice(id)).expect("Invoice not found");

let mut invoice: Invoice = env
.storage()
.instance()
.get(&DataKey::Invoice(id))
.expect("Invoice not found");

invoice.owner.require_auth(); // Only the owner can repay

// (In a real app, we would transfer USDC here. For MVP, we just flip the switch.)
invoice.is_repaid = true;

env.storage().instance().set(&DataKey::Invoice(id), &invoice);
env.storage()
.instance()
.set(&DataKey::Invoice(id), &invoice);
Self::extend_storage_ttl(&env);

env.events().publish((symbol_short!("repay"), invoice.owner), id);
}
}
}
6 changes: 3 additions & 3 deletions contracts/invoice_nft/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use soroban_sdk::contracterror;
use crate::InvoiceContract;
use soroban_sdk::contracterror;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
Expand All @@ -15,8 +15,8 @@ pub enum Error {
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{testutils::Address as TestAddress, testutils::Bytes as TestBytes, Bytes};
use soroban_sdk::contractclient::InvoiceContractClient;
use soroban_sdk::{testutils::Address as TestAddress, testutils::Bytes as TestBytes, Bytes};

#[test]
fn test_mint_invoice_success() {
Expand All @@ -30,7 +30,7 @@ mod tests {

// Create a valid signature (mock)
let signature = [2u8; 64];

let due_date = env.ledger().timestamp() + 86400; // Tomorrow
let invoice_id = client.mint(&owner, &1000, &due_date, &750, &signature);

Expand Down
Loading
Loading