Feat: Wire frontend to backend, add wallet API endpoints

- Replace frontend mock with real fetch calls to POST /api/wallet/analyze and GET /api/wallet/{id}/utxos
- Add Vite dev proxy for /api to avoid CORS in development
- Implement WalletResource.java with the two endpoints
- Add WalletMockData.java with the 5-UTXO dataset
- Configure CORS and port in application.properties
- Add backend/requests/wallet.http with kulala tests (29 assertions, all passing)
This commit is contained in:
LORDBABUINO
2026-02-26 23:14:19 -03:00
parent e6a8e77134
commit 1f7ecf321c
5 changed files with 207 additions and 4 deletions

View File

@@ -0,0 +1,67 @@
package org.backend.stealth.controller;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.backend.stealth.mocks.WalletMockData;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@Path("/api/wallet")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class WalletResource {
private static final Map<String, String> sessions = new ConcurrentHashMap<>();
// DTOs
public record AnalyzeRequest(String descriptor) {}
public record AnalyzeResponse(String analysisId) {}
public record VulnerabilityData(String type, String severity, String description) {}
public record UtxoData(
String txid,
int vout,
String address,
double amountBtc,
int confirmations,
List<VulnerabilityData> vulnerabilities
) {}
public record SummaryData(int total, int clean, int vulnerable) {}
public record ReportResponse(String descriptor, SummaryData summary, List<UtxoData> utxos) {}
// Endpoints
@POST
@Path("/analyze")
public Response analyze(AnalyzeRequest req) {
if (req == null || req.descriptor() == null || req.descriptor().isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "descriptor is required"))
.build();
}
String analysisId = UUID.randomUUID().toString();
sessions.put(analysisId, req.descriptor());
return Response.ok(new AnalyzeResponse(analysisId)).build();
}
@GET
@Path("/{analysisId}/utxos")
public Response getUtxos(@PathParam("analysisId") String analysisId) {
String descriptor = sessions.get(analysisId);
if (descriptor == null) {
return Response.status(Response.Status.NOT_FOUND)
.entity(Map.of("error", "analysisId not found"))
.build();
}
return Response.ok(WalletMockData.buildReport(descriptor)).build();
}
}

View File

@@ -0,0 +1,75 @@
package org.backend.stealth.mocks;
import org.backend.stealth.controller.WalletResource.ReportResponse;
import org.backend.stealth.controller.WalletResource.SummaryData;
import org.backend.stealth.controller.WalletResource.UtxoData;
import org.backend.stealth.controller.WalletResource.VulnerabilityData;
import java.util.List;
public class WalletMockData {
public static ReportResponse buildReport(String descriptor) {
List<UtxoData> utxos = List.of(
new UtxoData(
"3a7f2b8c1d4e9f0a6b5c2d7e8f3a1b4c9d2e5f0a7b8c1d4e9f2a5b6c3d7e8f1",
0,
"bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
0.05234891,
1842,
List.of()
),
new UtxoData(
"b4c8e2f6a1d5b9c3e7f1a5d9b3c7e1f5a9d3b7c1e5f9a3d7b1c5e9f3a7d1b5",
1,
"bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
0.00023000,
312,
List.of(
new VulnerabilityData("DUST_SPEND", "medium",
"This UTXO is near the dust threshold. Spending it may cost more in fees than its value, and dust outputs are often used as tracking vectors by chain surveillance companies."),
new VulnerabilityData("ADDRESS_REUSE", "high",
"This address has received funds in 3 separate transactions. Address reuse breaks the one-time-address privacy model and allows observers to link all deposits to the same wallet.")
)
),
new UtxoData(
"f9e3d7c1b5a9f3d7c1b5a9f3d7c1b5a9f3d7c1b5a9f3d7c1b5a9f3d7c1b5a9",
0,
"bc1q9h7garjcdkl4h5khfz2yxkhsmhep5j7g4cjtch",
0.12000000,
4521,
List.of(
new VulnerabilityData("CONSOLIDATION", "medium",
"This UTXO was created by consolidating 7 inputs in a single transaction. Consolidation reveals that all input addresses belong to the same wallet, reducing privacy significantly.")
)
),
new UtxoData(
"2c6e0a4f8b2d6e0a4f8b2d6e0a4f8b2d6e0a4f8b2d6e0a4f8b2d6e0a4f8b2d",
2,
"bc1qm34mqf4vn8f5vhf0q3djg2zuzfm9aap6e3n4j",
0.87654321,
98,
List.of(
new VulnerabilityData("CIOH", "high",
"Common Input Ownership Heuristic (CIOH): this UTXO was spent alongside UTXOs from different derivation paths in the same transaction, strongly suggesting to analysts that all inputs share a common owner."),
new VulnerabilityData("ADDRESS_REUSE", "high",
"This address appears in 5 transactions as both sender and receiver, a pattern that severely compromises wallet privacy and makes cluster analysis trivial.")
)
),
new UtxoData(
"7d1b5e9f3a7d1b5e9f3a7d1b5e9f3a7d1b5e9f3a7d1b5e9f3a7d1b5e9f3a7d",
0,
"bc1qcr8te4kr609gcawutmrza0j4xv80jy8zeqchgx",
0.00500000,
2103,
List.of(
new VulnerabilityData("DUST_SPEND", "low",
"A small dust amount was received at this address in a prior transaction. While the dust has not been spent, its presence could be used to track this UTXO if included in a future transaction.")
)
)
);
SummaryData summary = new SummaryData(5, 1, 4);
return new ReportResponse(descriptor, summary, utxos);
}
}