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
19 changes: 17 additions & 2 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from "react";
import type { RiskAssessment } from "@custos/core";
import { requiresFriction, requiresHardBlock } from "@custos/core";
import { requiresFriction, requiresHardBlock, detectChainNetwork } from "@custos/core";
import { analyzeSendIntent } from "./compositionRoot.js";

export function App() {
Expand All @@ -9,12 +9,21 @@ export function App() {
const [context, setContext] = useState("");
const [assessment, setAssessment] = useState<RiskAssessment | null>(null);
const [busy, setBusy] = useState(false);
const [unrecognizedNetwork, setUnrecognizedNetwork] = useState(false);

async function handleCheck() {
const network = detectChainNetwork(address);
if (network === "other") {
setUnrecognizedNetwork(true);
setAssessment(null);
return;
}

setUnrecognizedNetwork(false);
setBusy(true);
try {
const result = await analyzeSendIntent.execute({
destination: { value: address, network: "tron" },
destination: { value: address, network },
amountUsdt: Number(amount) || 0,
context: context ? { text: context } : undefined,
});
Expand Down Expand Up @@ -62,6 +71,12 @@ export function App() {
{busy ? "Analyzing on-device..." : "Check before sending"}
</button>

{unrecognizedNetwork && (
<p style={{ marginTop: 12, color: "#c0392b" }}>
Unrecognized address format — doesn't match Tron or Ethereum. Double-check it before sending.
</p>
)}

{assessment && (
<div
style={{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import { detectChainNetwork } from "../detectChainNetwork.js";

describe("detectChainNetwork", () => {
it("detects a Tron base58 address", () => {
expect(detectChainNetwork("TAbc123456789abcdefghjkmnpqrstuvwx")).toBe("tron");
});

it("detects an Ethereum hex address", () => {
expect(detectChainNetwork("0x1234567890abcdef1234567890abcdef12345678")).toBe("ethereum");
});

it("detects an Ethereum hex address with uppercase digits", () => {
expect(detectChainNetwork("0xABCDEF1234567890ABCDEF1234567890ABCDEF12")).toBe("ethereum");
});

it("returns other for an address matching neither format", () => {
expect(detectChainNetwork("not-a-real-address")).toBe("other");
});

it("returns other for a Tron-like address of the wrong length", () => {
expect(detectChainNetwork("TAbc123")).toBe("other");
});

it("returns other for an Ethereum-like address missing the 0x prefix", () => {
expect(detectChainNetwork("1234567890abcdef1234567890abcdef12345678")).toBe("other");
});

it("returns other for an empty string", () => {
expect(detectChainNetwork("")).toBe("other");
});
});
16 changes: 16 additions & 0 deletions packages/core/src/domain/services/detectChainNetwork.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ChainNetwork } from "../entities/Address.js";

const TRON_ADDRESS = /^T[1-9A-HJ-NP-Za-km-z]{33}$/;
const ETHEREUM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;

/**
* Infers the chain network from an address's format alone. Returns "other"
* for anything matching neither Tron base58 nor Ethereum hex — callers must
* not treat "other" as a safe default, since `RiskListPort.lookup` keys on
* `network:value` and a wrong network silently skips the real risk list.
*/
export function detectChainNetwork(value: string): ChainNetwork {
if (TRON_ADDRESS.test(value)) return "tron";
if (ETHEREUM_ADDRESS.test(value)) return "ethereum";
return "other";
}
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from "./domain/ports/WalletPort.js";
export * from "./domain/ports/RiskListPort.js";
export * from "./domain/ports/AuditLogPort.js";
export * from "./domain/services/detectAddressPoisoning.js";
export * from "./domain/services/detectChainNetwork.js";

// Application
export * from "./application/use-cases/AnalyzeSendIntent.js";
Expand Down
Loading