2020 RewardEstimate ,
2121 SessionStatus ,
2222 CircuitBreakerStatus ,
23+ # MIC Wallet schemas
24+ MICReason ,
25+ MICLedgerEntry ,
26+ WalletBalanceResponse ,
27+ WalletLedgerResponse ,
2328)
2429from app .services .learning_store import learning_store
2530from app .services .mic_minting import MICMintingService
31+ from app .services .mic_ledger_store import mic_ledger_store
2632
2733# Initialize services
2834mic_service = MICMintingService ()
@@ -1074,6 +1080,9 @@ async def complete_learning_session(session_id: str, req: SessionCompleteRequest
10741080 is_first_module = updated_progress ["modules_completed" ] == 1
10751081 )
10761082
1083+ # Get new wallet balance from ledger (DERIVED, never stored)
1084+ new_wallet_balance = mic_ledger_store .get_balance (user_id )
1085+
10771086 return SessionCompleteResponse (
10781087 session_id = session_id ,
10791088 module_id = module_id ,
@@ -1083,6 +1092,8 @@ async def complete_learning_session(session_id: str, req: SessionCompleteRequest
10831092 new_level = updated_progress ["level" ],
10841093 integrity_score = updated_progress .get ("integrity_score" , 0.85 ),
10851094 transaction_id = transaction_id ,
1095+ ledger_id = mint_result .get ("ledger_id" ), # Proof of earning
1096+ new_wallet_balance = new_wallet_balance , # Derived from ledger
10861097 status = SessionStatus .COMPLETED ,
10871098 rewards = {
10881099 "mic" : mic_earned ,
@@ -1189,6 +1200,146 @@ def get_learning_system_status():
11891200 }
11901201
11911202
1203+ # =============================================================================
1204+ # MIC WALLET API ENDPOINTS (C-151 MIC Ledger & Wallet Sync)
1205+ # =============================================================================
1206+
1207+ @app .get ("/api/v1/wallet/balance" )
1208+ def get_wallet_balance (user_id : str ):
1209+ """
1210+ Get user's MIC wallet balance.
1211+
1212+ CRITICAL: Balance is DERIVED from the ledger, never stored separately.
1213+ This ensures integrity and auditability of all MIC transactions.
1214+
1215+ Query Parameters:
1216+ - user_id: User ID to get balance for
1217+
1218+ Returns:
1219+ - balance: Total MIC balance (sum of all ledger entries)
1220+ - last_updated: Timestamp of last transaction
1221+ - recent_events: Last 10 ledger entries
1222+ """
1223+ # Get derived balance from ledger
1224+ balance = mic_ledger_store .get_balance (user_id )
1225+
1226+ # Get recent entries
1227+ recent_entries = mic_ledger_store .get_recent_entries (user_id , limit = 10 )
1228+
1229+ # Get last entry for timestamp
1230+ last_entry = mic_ledger_store .get_last_entry (user_id )
1231+ last_updated = last_entry .created_at if last_entry else None
1232+
1233+ # Format recent events for response
1234+ recent_events = [
1235+ {
1236+ "id" : entry .id ,
1237+ "amount" : entry .amount ,
1238+ "reason" : entry .reason .value ,
1239+ "module_id" : entry .module_id ,
1240+ "integrity_score" : entry .integrity_score ,
1241+ "timestamp" : entry .created_at .isoformat ()
1242+ }
1243+ for entry in recent_entries
1244+ ]
1245+
1246+ return WalletBalanceResponse (
1247+ user_id = user_id ,
1248+ balance = balance ,
1249+ last_updated = last_updated ,
1250+ recent_events = recent_events
1251+ )
1252+
1253+
1254+ @app .get ("/api/v1/wallet/ledger" )
1255+ def get_wallet_ledger (
1256+ user_id : str ,
1257+ limit : int = 50 ,
1258+ offset : int = 0
1259+ ):
1260+ """
1261+ Get full MIC ledger history for a user.
1262+
1263+ The ledger is append-only and auditable - entries are never modified.
1264+
1265+ Query Parameters:
1266+ - user_id: User ID to get ledger for
1267+ - limit: Number of entries to return (default 50, max 100)
1268+ - offset: Pagination offset
1269+
1270+ Returns:
1271+ - total_entries: Total number of ledger entries
1272+ - entries: List of ledger entries (most recent first)
1273+ """
1274+ # Cap limit at 100
1275+ limit = min (limit , 100 )
1276+
1277+ total , entries = mic_ledger_store .get_ledger (user_id , limit = limit , offset = offset )
1278+
1279+ return WalletLedgerResponse (
1280+ user_id = user_id ,
1281+ total_entries = total ,
1282+ entries = entries
1283+ )
1284+
1285+
1286+ @app .get ("/api/v1/wallet/breakdown" )
1287+ def get_wallet_breakdown (user_id : str ):
1288+ """
1289+ Get MIC balance breakdown by transaction type.
1290+
1291+ Shows how much MIC was earned through each channel:
1292+ - LEARN: Learning module completions
1293+ - EARN: Other earning activities
1294+ - BONUS: Streak and achievement bonuses
1295+ - CORRECTION: Manual adjustments
1296+
1297+ Query Parameters:
1298+ - user_id: User ID to get breakdown for
1299+
1300+ Returns:
1301+ - Breakdown by reason type
1302+ - Total balance
1303+ """
1304+ breakdown = mic_ledger_store .get_balance_breakdown (user_id )
1305+
1306+ return {
1307+ "user_id" : user_id ,
1308+ "breakdown" : breakdown ,
1309+ "balance" : breakdown .get ("total" , 0.0 )
1310+ }
1311+
1312+
1313+ @app .options ("/api/v1/wallet/balance" )
1314+ def wallet_balance_options ():
1315+ """CORS preflight for wallet balance endpoint."""
1316+ return JSONResponse (
1317+ content = {},
1318+ status_code = 204 ,
1319+ headers = {
1320+ "Access-Control-Allow-Origin" : "*" ,
1321+ "Access-Control-Allow-Methods" : "GET, OPTIONS" ,
1322+ "Access-Control-Allow-Headers" : "Content-Type, Authorization, X-Requested-With" ,
1323+ "Access-Control-Max-Age" : "86400"
1324+ }
1325+ )
1326+
1327+
1328+ @app .options ("/api/v1/wallet/ledger" )
1329+ def wallet_ledger_options ():
1330+ """CORS preflight for wallet ledger endpoint."""
1331+ return JSONResponse (
1332+ content = {},
1333+ status_code = 204 ,
1334+ headers = {
1335+ "Access-Control-Allow-Origin" : "*" ,
1336+ "Access-Control-Allow-Methods" : "GET, OPTIONS" ,
1337+ "Access-Control-Allow-Headers" : "Content-Type, Authorization, X-Requested-With" ,
1338+ "Access-Control-Max-Age" : "86400"
1339+ }
1340+ )
1341+
1342+
11921343# =============================================================================
11931344# ROOT ENDPOINT
11941345# =============================================================================
@@ -1221,6 +1372,9 @@ def root():
12211372 "learning_progress" : {"path" : "/api/learning/users/{id}/progress" , "method" : "GET" , "description" : "Get user progress" },
12221373 "learning_estimate" : {"path" : "/api/learning/estimate-reward" , "method" : "GET" , "description" : "Estimate MIC reward" },
12231374 "learning_status" : {"path" : "/api/learning/system-status" , "method" : "GET" , "description" : "System & circuit breaker status" },
1375+ "wallet_balance" : {"path" : "/api/v1/wallet/balance" , "method" : "GET" , "description" : "Get MIC wallet balance (derived from ledger)" },
1376+ "wallet_ledger" : {"path" : "/api/v1/wallet/ledger" , "method" : "GET" , "description" : "Get full MIC transaction history" },
1377+ "wallet_breakdown" : {"path" : "/api/v1/wallet/breakdown" , "method" : "GET" , "description" : "Get MIC balance breakdown by type" },
12241378 "debug_anthropic" : {"path" : "/api/debug/test-anthropic" , "method" : "GET" },
12251379 "debug_openai" : {"path" : "/api/debug/test-openai" , "method" : "GET" },
12261380 "agents_register" : {"path" : "/agents/register" , "method" : "POST" },
0 commit comments