Add Web of Trust reputation on profiles via Vertex DVM

Query Vertex Verify Reputation API (kind 5312) for each profile and
display "Followed by people you trust" with clickable top-follower
avatars. Includes in-memory cache, request dedup, and graceful
fallback when logged out or API unreachable.
This commit is contained in:
Jure
2026-03-29 10:42:19 +02:00
parent 8bc38c11e6
commit 2c17361e50
6 changed files with 178 additions and 2 deletions

View File

@@ -12,3 +12,5 @@ export type { AdvancedSearchResults } from "./search";
export { fetchUserRelayList, publishRelayList, fetchRelayRecommendations } from "./relays";
export type { UserRelayList } from "./relays";
export { fetchTrendingCandidates, fetchTrendingHashtags } from "./trending";
export { verifyReputation } from "./vertex";
export type { ReputationResult, ReputationEntry } from "./vertex";

77
src/lib/nostr/vertex.ts Normal file
View File

@@ -0,0 +1,77 @@
import { fetch } from "@tauri-apps/plugin-http";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { getNDK } from "./core";
import { debug } from "../debug";
const VERTEX_API = "https://relay.vertexlab.io/api/v1/dvms";
const VERTEX_TIMEOUT = 10000;
export interface ReputationEntry {
pubkey: string;
rank: number;
follows?: number;
followers?: number;
}
export interface ReputationResult {
topFollowers: ReputationEntry[];
totalNodes: number;
}
/**
* Call Vertex Verify Reputation DVM (kind 5312).
* Returns personalized reputation data for a target pubkey.
*/
export async function verifyReputation(targetPubkey: string, limit = 7): Promise<ReputationResult | null> {
const ndk = getNDK();
if (!ndk.signer) return null;
try {
// Build and sign the DVM request event
const event = new NDKEvent(ndk);
event.kind = 5312;
event.content = "";
event.tags = [
["param", "target", targetPubkey],
["param", "limit", String(limit)],
];
await event.sign();
const raw = event.rawEvent();
debug.log("vertex:verifyReputation", targetPubkey.slice(0, 8), "requesting...");
const resp = await fetch(VERTEX_API, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(raw),
connectTimeout: VERTEX_TIMEOUT,
});
if (!resp.ok) {
debug.warn("vertex:verifyReputation HTTP", resp.status);
return null;
}
const responseEvent = await resp.json();
// Parse the kind 6312 response
if (responseEvent.kind === 7000) {
debug.warn("vertex:verifyReputation DVM error:", responseEvent.content);
return null;
}
const totalNodes = parseInt(
responseEvent.tags?.find((t: string[]) => t[0] === "nodes")?.[1] ?? "0",
10,
);
const topFollowers: ReputationEntry[] = JSON.parse(responseEvent.content || "[]");
debug.log("vertex:verifyReputation", targetPubkey.slice(0, 8), "→", topFollowers.length, "followers, nodes:", totalNodes);
return { topFollowers, totalNodes };
} catch (err) {
debug.warn("vertex:verifyReputation failed:", err);
return null;
}
}