feat: add unified resale dashboard
This commit is contained in:
@@ -22,6 +22,20 @@ services:
|
|||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
volumes:
|
volumes:
|
||||||
- surplus-seller-state:/var/lib/surplus-manager
|
- surplus-seller-state:/var/lib/surplus-manager
|
||||||
|
networks:
|
||||||
|
- traefik-net
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=traefik-net"
|
||||||
|
- "traefik.http.routers.mp-openrouter-reseller-mainnet.entrypoints=web"
|
||||||
|
- "traefik.http.routers.mp-openrouter-reseller-mainnet.rule=Host(`mp-suppliers-openrouter-reseller.vector.apexfusion.org`)"
|
||||||
|
- "traefik.http.routers.mp-openrouter-reseller-mainnet.middlewares=mp-openrouter-reseller-mainnet-https"
|
||||||
|
- "traefik.http.middlewares.mp-openrouter-reseller-mainnet-https.redirectscheme.scheme=https"
|
||||||
|
- "traefik.http.middlewares.mp-openrouter-reseller-mainnet-https.redirectscheme.permanent=true"
|
||||||
|
- "traefik.http.routers.mp-openrouter-reseller-mainnet-tls.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.mp-openrouter-reseller-mainnet-tls.rule=Host(`mp-suppliers-openrouter-reseller.vector.apexfusion.org`)"
|
||||||
|
- "traefik.http.routers.mp-openrouter-reseller-mainnet-tls.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.mp-openrouter-reseller-mainnet.loadbalancer.server.port=8080"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1"]
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1"]
|
||||||
interval: 20s
|
interval: 20s
|
||||||
@@ -37,3 +51,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
surplus-seller-state:
|
surplus-seller-state:
|
||||||
name: marketplace-mainnet-surplus-seller-state
|
name: marketplace-mainnet-surplus-seller-state
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik-net:
|
||||||
|
external: true
|
||||||
|
name: traefik-net
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { createServer, type Server } from "node:http";
|
import {
|
||||||
|
createServer,
|
||||||
|
type IncomingMessage,
|
||||||
|
type Server,
|
||||||
|
type ServerResponse,
|
||||||
|
} from "node:http";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { loadSurplusManagerConfig } from "../surplus/config.js";
|
import { loadSurplusManagerConfig } from "../surplus/config.js";
|
||||||
@@ -6,6 +11,11 @@ import { SurplusClient } from "../surplus/client.js";
|
|||||||
import { SurplusSellerController } from "../surplus/controller.js";
|
import { SurplusSellerController } from "../surplus/controller.js";
|
||||||
import { OpenRouterKeyClient } from "../surplus/openRouterKey.js";
|
import { OpenRouterKeyClient } from "../surplus/openRouterKey.js";
|
||||||
import { FileSurplusStateStore } from "../surplus/state.js";
|
import { FileSurplusStateStore } from "../surplus/state.js";
|
||||||
|
import {
|
||||||
|
SurplusDashboardService,
|
||||||
|
type SurplusDashboardSnapshot,
|
||||||
|
} from "../surplus/dashboard.js";
|
||||||
|
import { renderSurplusDashboardPage } from "../surplus/dashboardPage.js";
|
||||||
|
|
||||||
export async function runSurplusSeller(
|
export async function runSurplusSeller(
|
||||||
env: Record<string, string | undefined>,
|
env: Record<string, string | undefined>,
|
||||||
@@ -29,7 +39,13 @@ export async function runSurplusSeller(
|
|||||||
stateStore,
|
stateStore,
|
||||||
log: (message) => console.log(message),
|
log: (message) => console.log(message),
|
||||||
});
|
});
|
||||||
const server = createStatusServer(controller);
|
const dashboard = new SurplusDashboardService({
|
||||||
|
controller,
|
||||||
|
client,
|
||||||
|
allowance,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
const server = createSurplusServer(controller, dashboard);
|
||||||
await new Promise<void>((resolveListen, rejectListen) => {
|
await new Promise<void>((resolveListen, rejectListen) => {
|
||||||
server.once("error", rejectListen);
|
server.once("error", rejectListen);
|
||||||
server.listen(config.port, "0.0.0.0", () => {
|
server.listen(config.port, "0.0.0.0", () => {
|
||||||
@@ -41,26 +57,81 @@ export async function runSurplusSeller(
|
|||||||
return { server, controller };
|
return { server, controller };
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStatusServer(controller: SurplusSellerController): Server {
|
interface DashboardSource {
|
||||||
|
getSnapshot(): Promise<SurplusDashboardSnapshot>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSurplusServer(
|
||||||
|
controller: Pick<SurplusSellerController, "snapshot" | "healthy">,
|
||||||
|
dashboard: DashboardSource,
|
||||||
|
): Server {
|
||||||
return createServer((request, response) => {
|
return createServer((request, response) => {
|
||||||
const status = controller.snapshot();
|
void handleRequest(controller, dashboard, request, response);
|
||||||
if (request.method === "GET" && request.url === "/healthz") {
|
});
|
||||||
const healthy = controller.healthy();
|
}
|
||||||
response.statusCode = healthy ? 200 : 503;
|
|
||||||
response.setHeader("content-type", "application/json");
|
async function handleRequest(
|
||||||
response.end(JSON.stringify({ ok: healthy, phase: status.phase }));
|
controller: Pick<SurplusSellerController, "snapshot" | "healthy">,
|
||||||
return;
|
dashboard: DashboardSource,
|
||||||
}
|
request: IncomingMessage,
|
||||||
if (request.method === "GET" && request.url === "/status") {
|
response: ServerResponse,
|
||||||
|
): Promise<void> {
|
||||||
|
const path = request.url?.split("?")[0] ?? "/";
|
||||||
|
response.setHeader("cache-control", "no-store");
|
||||||
|
response.setHeader("x-content-type-options", "nosniff");
|
||||||
|
response.setHeader("x-frame-options", "DENY");
|
||||||
|
response.setHeader("referrer-policy", "no-referrer");
|
||||||
|
if (request.method === "GET" && path === "/") {
|
||||||
|
response.statusCode = 302;
|
||||||
|
response.setHeader("location", "/reseller");
|
||||||
|
response.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
request.method === "GET" &&
|
||||||
|
(path === "/reseller" || path === "/reseller/")
|
||||||
|
) {
|
||||||
|
response.statusCode = 200;
|
||||||
|
response.setHeader("content-type", "text/html; charset=utf-8");
|
||||||
|
response.setHeader(
|
||||||
|
"content-security-policy",
|
||||||
|
"default-src 'self'; style-src 'self' 'unsafe-inline'; " +
|
||||||
|
"script-src 'self' 'unsafe-inline'; connect-src 'self'; " +
|
||||||
|
"img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'",
|
||||||
|
);
|
||||||
|
response.end(renderSurplusDashboardPage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const status = controller.snapshot();
|
||||||
|
if (request.method === "GET" && path === "/healthz") {
|
||||||
|
const healthy = controller.healthy();
|
||||||
|
response.statusCode = healthy ? 200 : 503;
|
||||||
|
response.setHeader("content-type", "application/json");
|
||||||
|
response.end(JSON.stringify({ ok: healthy, phase: status.phase }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.method === "GET" && path === "/status") {
|
||||||
|
response.statusCode = 200;
|
||||||
|
response.setHeader("content-type", "application/json");
|
||||||
|
response.end(JSON.stringify(status));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (request.method === "GET" && path === "/dashboard/api") {
|
||||||
|
try {
|
||||||
|
const snapshot = await dashboard.getSnapshot();
|
||||||
response.statusCode = 200;
|
response.statusCode = 200;
|
||||||
response.setHeader("content-type", "application/json");
|
response.setHeader("content-type", "application/json");
|
||||||
response.end(JSON.stringify(status));
|
response.end(JSON.stringify(snapshot));
|
||||||
return;
|
} catch {
|
||||||
|
response.statusCode = 503;
|
||||||
|
response.setHeader("content-type", "application/json");
|
||||||
|
response.end(JSON.stringify({ error: "dashboard_data_unavailable" }));
|
||||||
}
|
}
|
||||||
response.statusCode = 404;
|
return;
|
||||||
response.setHeader("content-type", "application/json");
|
}
|
||||||
response.end(JSON.stringify({ error: "not_found" }));
|
response.statusCode = 404;
|
||||||
});
|
response.setHeader("content-type", "application/json");
|
||||||
|
response.end(JSON.stringify({ error: "not_found" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function main(
|
export async function main(
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ export interface SurplusOrderBookOffer {
|
|||||||
healthy: boolean;
|
healthy: boolean;
|
||||||
trusted: boolean;
|
trusted: boolean;
|
||||||
trades24h: number;
|
trades24h: number;
|
||||||
|
rank: number;
|
||||||
|
capRemainingMicroUsd: number | null;
|
||||||
|
volume24hMicroUsd: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SurplusOrderBook {
|
export interface SurplusOrderBook {
|
||||||
@@ -64,17 +67,48 @@ export interface SurplusOfferPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SurplusSale {
|
export interface SurplusSale {
|
||||||
|
id: string;
|
||||||
model: string;
|
model: string;
|
||||||
offerId: string | null;
|
offerId: string | null;
|
||||||
settlementStatus: string;
|
settlementStatus: string;
|
||||||
createdAt: string | null;
|
createdAt: string | null;
|
||||||
sellerCostMicroUsd: number;
|
sellerCostMicroUsd: number;
|
||||||
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
|
cacheReadTokens: number;
|
||||||
|
effectiveInputUsdPer1m: number;
|
||||||
|
effectiveOutputUsdPer1m: number;
|
||||||
|
transactionHash: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SurplusDailyEarnings {
|
||||||
|
day: string;
|
||||||
|
earnedUsd: number;
|
||||||
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
|
requests: number;
|
||||||
|
totalTokens: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SurplusModelEarnings {
|
||||||
|
model: string;
|
||||||
|
earnedUsd: number;
|
||||||
|
requests: number;
|
||||||
|
totalTokens: number;
|
||||||
|
lastSaleAt: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SurplusEarnings {
|
export interface SurplusEarnings {
|
||||||
totalEarnedMicroUsd: number;
|
totalEarnedMicroUsd: number;
|
||||||
pendingMicroUsd: number;
|
pendingMicroUsd: number;
|
||||||
paidMicroUsd: number;
|
paidMicroUsd: number;
|
||||||
|
daily: SurplusDailyEarnings[];
|
||||||
|
byModel: SurplusModelEarnings[];
|
||||||
|
requestCount: number;
|
||||||
|
tokenCount: number;
|
||||||
|
topModel: string | null;
|
||||||
|
payoutHoldReason: string | null;
|
||||||
|
payoutHoldReleasesAt: string | null;
|
||||||
recentSales: SurplusSale[];
|
recentSales: SurplusSale[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,15 +297,43 @@ export class SurplusClient {
|
|||||||
async getEarnings(): Promise<SurplusEarnings> {
|
async getEarnings(): Promise<SurplusEarnings> {
|
||||||
const body = await this.requestJson("/v1/seller/earnings?range=lifetime");
|
const body = await this.requestJson("/v1/seller/earnings?range=lifetime");
|
||||||
const root = record(body, "Surplus earnings response");
|
const root = record(body, "Surplus earnings response");
|
||||||
const rawSales = root.recent_sales;
|
if (!Array.isArray(root.recent_sales)) {
|
||||||
if (!Array.isArray(rawSales)) {
|
|
||||||
throw new Error("Surplus earnings response.recent_sales must be an array");
|
throw new Error("Surplus earnings response.recent_sales must be an array");
|
||||||
}
|
}
|
||||||
|
if (!Array.isArray(root.daily)) {
|
||||||
|
throw new Error("Surplus earnings response.daily must be an array");
|
||||||
|
}
|
||||||
|
if (!Array.isArray(root.by_model)) {
|
||||||
|
throw new Error("Surplus earnings response.by_model must be an array");
|
||||||
|
}
|
||||||
|
const share = record(root.share, "Surplus earnings response.share");
|
||||||
|
const payoutHold = optionalRecord(root.payout_hold);
|
||||||
return {
|
return {
|
||||||
totalEarnedMicroUsd: microUsd(root.total_earned_usdc, "total_earned_usdc"),
|
totalEarnedMicroUsd: microUsd(root.total_earned_usdc, "total_earned_usdc"),
|
||||||
pendingMicroUsd: microUsd(root.pending_usdc, "pending_usdc"),
|
pendingMicroUsd: microUsd(root.pending_usdc, "pending_usdc"),
|
||||||
paidMicroUsd: microUsd(root.paid_usdc, "paid_usdc"),
|
paidMicroUsd: microUsd(root.paid_usdc, "paid_usdc"),
|
||||||
recentSales: rawSales.map((sale, index) => parseSale(sale, index)),
|
daily: root.daily.map((day, index) => parseDailyEarnings(day, index)),
|
||||||
|
byModel: root.by_model.map((model, index) =>
|
||||||
|
parseModelEarnings(model, index)
|
||||||
|
),
|
||||||
|
requestCount: nonNegativeSafeInteger(
|
||||||
|
share.requests,
|
||||||
|
"Surplus earnings share.requests",
|
||||||
|
),
|
||||||
|
tokenCount: nonNegativeSafeInteger(
|
||||||
|
share.tokens,
|
||||||
|
"Surplus earnings share.tokens",
|
||||||
|
),
|
||||||
|
topModel: optionalString(share.top_model),
|
||||||
|
payoutHoldReason: payoutHold
|
||||||
|
? optionalString(payoutHold.reason)
|
||||||
|
: null,
|
||||||
|
payoutHoldReleasesAt: payoutHold
|
||||||
|
? optionalTimestamp(payoutHold.releases_at, "payout_hold.releases_at")
|
||||||
|
: null,
|
||||||
|
recentSales: root.recent_sales.map((sale, index) =>
|
||||||
|
parseSale(sale, index)
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,17 +522,98 @@ function parseOrderBookOffer(value: unknown, index: number): SurplusOrderBookOff
|
|||||||
healthy: boolean(item.healthy, `Surplus order book offer ${index}.healthy`),
|
healthy: boolean(item.healthy, `Surplus order book offer ${index}.healthy`),
|
||||||
trusted: boolean(item.trusted, `Surplus order book offer ${index}.trusted`),
|
trusted: boolean(item.trusted, `Surplus order book offer ${index}.trusted`),
|
||||||
trades24h: nonNegativeSafeInteger(item.trades_24h, `Surplus order book offer ${index}.trades_24h`),
|
trades24h: nonNegativeSafeInteger(item.trades_24h, `Surplus order book offer ${index}.trades_24h`),
|
||||||
|
rank: positiveSafeInteger(item.rank, `Surplus order book offer ${index}.rank`),
|
||||||
|
capRemainingMicroUsd: optionalMicroUsd(
|
||||||
|
item.cap_remaining,
|
||||||
|
`Surplus order book offer ${index}.cap_remaining`,
|
||||||
|
),
|
||||||
|
volume24hMicroUsd: microUsd(
|
||||||
|
item.volume_24h,
|
||||||
|
`Surplus order book offer ${index}.volume_24h`,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSale(value: unknown, index: number): SurplusSale {
|
function parseSale(value: unknown, index: number): SurplusSale {
|
||||||
const item = record(value, `Surplus sale ${index}`);
|
const item = record(value, `Surplus sale ${index}`);
|
||||||
return {
|
return {
|
||||||
|
id: string(item.id, `Surplus sale ${index}.id`),
|
||||||
model: string(item.model, `Surplus sale ${index}.model`),
|
model: string(item.model, `Surplus sale ${index}.model`),
|
||||||
offerId: optionalString(item.offer_id),
|
offerId: optionalString(item.offer_id),
|
||||||
settlementStatus: string(item.settlement_status, `Surplus sale ${index}.settlement_status`),
|
settlementStatus: string(item.settlement_status, `Surplus sale ${index}.settlement_status`),
|
||||||
createdAt: optionalString(item.created_at),
|
createdAt: optionalTimestamp(item.created_at, `Surplus sale ${index}.created_at`),
|
||||||
sellerCostMicroUsd: microUsd(item.seller_cost_usdc, `Surplus sale ${index}.seller_cost_usdc`),
|
sellerCostMicroUsd: microUsd(item.seller_cost_usdc, `Surplus sale ${index}.seller_cost_usdc`),
|
||||||
|
inputTokens: optionalSafeInteger(item.input_tokens, `Surplus sale ${index}.input_tokens`),
|
||||||
|
outputTokens: optionalSafeInteger(item.output_tokens, `Surplus sale ${index}.output_tokens`),
|
||||||
|
cacheReadTokens: optionalSafeInteger(
|
||||||
|
item.cache_read_tokens,
|
||||||
|
`Surplus sale ${index}.cache_read_tokens`,
|
||||||
|
),
|
||||||
|
effectiveInputUsdPer1m: nonNegativeNumber(
|
||||||
|
item.effective_input_per_1m,
|
||||||
|
`Surplus sale ${index}.effective_input_per_1m`,
|
||||||
|
),
|
||||||
|
effectiveOutputUsdPer1m: nonNegativeNumber(
|
||||||
|
item.effective_output_per_1m,
|
||||||
|
`Surplus sale ${index}.effective_output_per_1m`,
|
||||||
|
),
|
||||||
|
transactionHash: optionalString(item.tx_hash),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDailyEarnings(
|
||||||
|
value: unknown,
|
||||||
|
index: number,
|
||||||
|
): SurplusDailyEarnings {
|
||||||
|
const item = record(value, `Surplus daily earnings ${index}`);
|
||||||
|
return {
|
||||||
|
day: string(item.day, `Surplus daily earnings ${index}.day`),
|
||||||
|
earnedUsd: nonNegativeNumber(
|
||||||
|
item.earned_usd,
|
||||||
|
`Surplus daily earnings ${index}.earned_usd`,
|
||||||
|
),
|
||||||
|
inputTokens: nonNegativeSafeInteger(
|
||||||
|
item.input_tokens,
|
||||||
|
`Surplus daily earnings ${index}.input_tokens`,
|
||||||
|
),
|
||||||
|
outputTokens: nonNegativeSafeInteger(
|
||||||
|
item.output_tokens,
|
||||||
|
`Surplus daily earnings ${index}.output_tokens`,
|
||||||
|
),
|
||||||
|
requests: nonNegativeSafeInteger(
|
||||||
|
item.requests,
|
||||||
|
`Surplus daily earnings ${index}.requests`,
|
||||||
|
),
|
||||||
|
totalTokens: nonNegativeSafeInteger(
|
||||||
|
item.total_tokens,
|
||||||
|
`Surplus daily earnings ${index}.total_tokens`,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseModelEarnings(
|
||||||
|
value: unknown,
|
||||||
|
index: number,
|
||||||
|
): SurplusModelEarnings {
|
||||||
|
const item = record(value, `Surplus model earnings ${index}`);
|
||||||
|
return {
|
||||||
|
model: string(item.model, `Surplus model earnings ${index}.model`),
|
||||||
|
earnedUsd: nonNegativeNumber(
|
||||||
|
item.earned_usd,
|
||||||
|
`Surplus model earnings ${index}.earned_usd`,
|
||||||
|
),
|
||||||
|
requests: nonNegativeSafeInteger(
|
||||||
|
item.requests,
|
||||||
|
`Surplus model earnings ${index}.requests`,
|
||||||
|
),
|
||||||
|
totalTokens: nonNegativeSafeInteger(
|
||||||
|
item.total_tokens,
|
||||||
|
`Surplus model earnings ${index}.total_tokens`,
|
||||||
|
),
|
||||||
|
lastSaleAt: optionalTimestamp(
|
||||||
|
item.last_sale_at,
|
||||||
|
`Surplus model earnings ${index}.last_sale_at`,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,6 +646,19 @@ function optionalString(value: unknown): string | null {
|
|||||||
return typeof value === "string" && value !== "" ? value : null;
|
return typeof value === "string" && value !== "" ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function optionalTimestamp(value: unknown, field: string): string | null {
|
||||||
|
if (value === null || value === undefined) return null;
|
||||||
|
const timestamp = typeof value === "number"
|
||||||
|
? value
|
||||||
|
: typeof value === "string"
|
||||||
|
? Date.parse(value)
|
||||||
|
: Number.NaN;
|
||||||
|
if (!Number.isFinite(timestamp) || timestamp < 0) {
|
||||||
|
throw new Error(`${field} must be a timestamp`);
|
||||||
|
}
|
||||||
|
return new Date(timestamp).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
function boolean(value: unknown, field: string): boolean {
|
function boolean(value: unknown, field: string): boolean {
|
||||||
if (typeof value !== "boolean") throw new Error(`${field} must be a boolean`);
|
if (typeof value !== "boolean") throw new Error(`${field} must be a boolean`);
|
||||||
return value;
|
return value;
|
||||||
@@ -560,6 +716,11 @@ function nonNegativeSafeInteger(value: unknown, field: string): number {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function optionalSafeInteger(value: unknown, field: string): number {
|
||||||
|
if (value === null || value === undefined) return 0;
|
||||||
|
return nonNegativeSafeInteger(value, field);
|
||||||
|
}
|
||||||
|
|
||||||
function microUsd(value: unknown, field: string): number {
|
function microUsd(value: unknown, field: string): number {
|
||||||
if (typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value)) {
|
if (typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(value)) {
|
||||||
const parsed = Number(value);
|
const parsed = Number(value);
|
||||||
|
|||||||
259
supplier/src/surplus/dashboard.ts
Normal file
259
supplier/src/surplus/dashboard.ts
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import type { SurplusManagerConfig } from "./config.js";
|
||||||
|
import type {
|
||||||
|
SurplusClient,
|
||||||
|
SurplusDailyEarnings,
|
||||||
|
SurplusEarnings,
|
||||||
|
SurplusOffer,
|
||||||
|
SurplusOrderBook,
|
||||||
|
} from "./client.js";
|
||||||
|
import type {
|
||||||
|
SurplusControllerStatus,
|
||||||
|
SurplusSellerController,
|
||||||
|
} from "./controller.js";
|
||||||
|
import type { OpenRouterKeyClient } from "./openRouterKey.js";
|
||||||
|
import { formatUsdNanos } from "../reseller/money.js";
|
||||||
|
import { formatMicroUsd } from "./policy.js";
|
||||||
|
|
||||||
|
export interface VectorMigrationSummary {
|
||||||
|
status: "retired";
|
||||||
|
model: string;
|
||||||
|
supplierWallet: string;
|
||||||
|
advertRef: string;
|
||||||
|
retirementTransaction: string;
|
||||||
|
retiredOn: string;
|
||||||
|
historicalAp3xEarned: string;
|
||||||
|
historicalSettledJobs: number;
|
||||||
|
historicalUpstreamSpendUsd: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SurplusDashboardSnapshot {
|
||||||
|
generatedAt: string;
|
||||||
|
controller: SurplusControllerStatus;
|
||||||
|
identity: {
|
||||||
|
sellerWallet: string;
|
||||||
|
payoutAddress: string;
|
||||||
|
};
|
||||||
|
provider: {
|
||||||
|
name: "OpenRouter";
|
||||||
|
hardLimitUsd: string;
|
||||||
|
remainingUsd: string;
|
||||||
|
usedUsd: string;
|
||||||
|
reserveUsd: string;
|
||||||
|
estimatedDailyExposureUsd: string;
|
||||||
|
};
|
||||||
|
surplus: {
|
||||||
|
offer: {
|
||||||
|
id: string | null;
|
||||||
|
model: string | null;
|
||||||
|
providerModel: string | null;
|
||||||
|
status: "active" | "inactive" | "missing";
|
||||||
|
dailyCapUsd: string | null;
|
||||||
|
costMultiplier: number | null;
|
||||||
|
inputUsdPer1m: string | null;
|
||||||
|
outputUsdPer1m: string | null;
|
||||||
|
rank: number | null;
|
||||||
|
available: boolean;
|
||||||
|
healthy: boolean;
|
||||||
|
trusted: boolean;
|
||||||
|
trades24h: number;
|
||||||
|
volume24hUsd: string;
|
||||||
|
capRemainingUsd: string | null;
|
||||||
|
};
|
||||||
|
earnings: {
|
||||||
|
totalUsd: string;
|
||||||
|
pendingUsd: string;
|
||||||
|
paidUsd: string;
|
||||||
|
requests: number;
|
||||||
|
tokens: number;
|
||||||
|
today: SurplusDailyEarnings | null;
|
||||||
|
topModel: string | null;
|
||||||
|
payoutHoldReason: string | null;
|
||||||
|
payoutHoldReleasesAt: string | null;
|
||||||
|
};
|
||||||
|
recentSales: Array<{
|
||||||
|
id: string;
|
||||||
|
model: string;
|
||||||
|
createdAt: string | null;
|
||||||
|
settlementStatus: string;
|
||||||
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
|
cacheReadTokens: number;
|
||||||
|
revenueUsd: string;
|
||||||
|
transactionHash: string | null;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
vector: VectorMigrationSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SurplusDashboardOptions {
|
||||||
|
controller: Pick<SurplusSellerController, "snapshot">;
|
||||||
|
client: Pick<SurplusClient, "listAllOffers" | "getEarnings" | "getOrderBook">;
|
||||||
|
allowance: Pick<OpenRouterKeyClient, "readAllowance">;
|
||||||
|
config: SurplusManagerConfig;
|
||||||
|
vector?: VectorMigrationSummary;
|
||||||
|
cacheMs?: number;
|
||||||
|
now?: () => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_VECTOR_MIGRATION: VectorMigrationSummary = {
|
||||||
|
status: "retired",
|
||||||
|
model: "deepseek/deepseek-v4-flash",
|
||||||
|
supplierWallet: "addr1v8zy05zsugau8su0lqn8q37xlryh39cww9mw9t6g7vt207c9xfc9d",
|
||||||
|
advertRef:
|
||||||
|
"29bc9e7ec9f4a26f33e419436cfe95239b5d06b75db637e8dbb4cbc478d1a445#0",
|
||||||
|
retirementTransaction:
|
||||||
|
"d7fdae603e5744c98efc20c25d73a83ebfef0308806026a370d9f81c975c11fa",
|
||||||
|
retiredOn: "2026-08-27",
|
||||||
|
historicalAp3xEarned: "1.600000",
|
||||||
|
historicalSettledJobs: 8,
|
||||||
|
historicalUpstreamSpendUsd: "0.000093",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SurplusDashboardService {
|
||||||
|
private readonly controller: Pick<SurplusSellerController, "snapshot">;
|
||||||
|
private readonly client: Pick<
|
||||||
|
SurplusClient,
|
||||||
|
"listAllOffers" | "getEarnings" | "getOrderBook"
|
||||||
|
>;
|
||||||
|
private readonly allowance: Pick<OpenRouterKeyClient, "readAllowance">;
|
||||||
|
private readonly config: SurplusManagerConfig;
|
||||||
|
private readonly vector: VectorMigrationSummary;
|
||||||
|
private readonly cacheMs: number;
|
||||||
|
private readonly now: () => number;
|
||||||
|
private cache: { fetchedAt: number; value: SurplusDashboardSnapshot } | null = null;
|
||||||
|
private inFlight: Promise<SurplusDashboardSnapshot> | null = null;
|
||||||
|
|
||||||
|
constructor(options: SurplusDashboardOptions) {
|
||||||
|
this.controller = options.controller;
|
||||||
|
this.client = options.client;
|
||||||
|
this.allowance = options.allowance;
|
||||||
|
this.config = options.config;
|
||||||
|
this.vector = options.vector ?? DEFAULT_VECTOR_MIGRATION;
|
||||||
|
this.cacheMs = options.cacheMs ?? 15_000;
|
||||||
|
this.now = options.now ?? Date.now;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSnapshot(): Promise<SurplusDashboardSnapshot> {
|
||||||
|
const now = this.now();
|
||||||
|
if (this.cache && now - this.cache.fetchedAt < this.cacheMs) {
|
||||||
|
return this.cache.value;
|
||||||
|
}
|
||||||
|
if (this.inFlight) return this.inFlight;
|
||||||
|
const load = this.loadSnapshot();
|
||||||
|
this.inFlight = load;
|
||||||
|
try {
|
||||||
|
const value = await load;
|
||||||
|
this.cache = { fetchedAt: this.now(), value };
|
||||||
|
return value;
|
||||||
|
} finally {
|
||||||
|
this.inFlight = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadSnapshot(): Promise<SurplusDashboardSnapshot> {
|
||||||
|
const controller = this.controller.snapshot();
|
||||||
|
const [offers, earnings, allowance, orderBook] = await Promise.all([
|
||||||
|
this.client.listAllOffers(),
|
||||||
|
this.client.getEarnings(),
|
||||||
|
this.allowance.readAllowance(),
|
||||||
|
controller.model ? this.client.getOrderBook(controller.model) : null,
|
||||||
|
]);
|
||||||
|
const offer = findOffer(offers, controller.offerId);
|
||||||
|
const marketOffer = findMarketOffer(orderBook, controller.offerId);
|
||||||
|
const usedUsdNanos = allowance.limitUsdNanos - allowance.remainingUsdNanos;
|
||||||
|
const estimatedDailyExposure =
|
||||||
|
Number(this.config.perOfferCapUsd) * 10_000 / this.config.recoveryBps;
|
||||||
|
const today = earnings.daily.find((day) =>
|
||||||
|
day.day === new Date(this.now()).toISOString().slice(0, 10)
|
||||||
|
) ?? null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
generatedAt: new Date(this.now()).toISOString(),
|
||||||
|
controller,
|
||||||
|
identity: {
|
||||||
|
sellerWallet: this.config.sellerWallet,
|
||||||
|
payoutAddress: this.config.payoutAddress,
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
name: "OpenRouter",
|
||||||
|
hardLimitUsd: formatUsdNanos(allowance.limitUsdNanos, 6),
|
||||||
|
remainingUsd: formatUsdNanos(allowance.remainingUsdNanos, 6),
|
||||||
|
usedUsd: formatUsdNanos(usedUsdNanos, 6),
|
||||||
|
reserveUsd: this.config.reserveUsd,
|
||||||
|
estimatedDailyExposureUsd: estimatedDailyExposure.toFixed(6),
|
||||||
|
},
|
||||||
|
surplus: {
|
||||||
|
offer: {
|
||||||
|
id: offer?.id ?? controller.offerId,
|
||||||
|
model: offer?.model ?? controller.model,
|
||||||
|
providerModel: controller.providerModelId,
|
||||||
|
status: offer?.status ?? "missing",
|
||||||
|
dailyCapUsd: offer?.capDailyUsd === null || offer === undefined
|
||||||
|
? null
|
||||||
|
: offer.capDailyUsd.toFixed(6),
|
||||||
|
costMultiplier: offer?.costMultiplierPpm === null || offer === undefined
|
||||||
|
? null
|
||||||
|
: offer.costMultiplierPpm / 1_000_000,
|
||||||
|
inputUsdPer1m: controller.inputUsdPer1m,
|
||||||
|
outputUsdPer1m: controller.outputUsdPer1m,
|
||||||
|
rank: marketOffer?.rank ?? null,
|
||||||
|
available: marketOffer?.available ?? false,
|
||||||
|
healthy: marketOffer?.healthy ?? false,
|
||||||
|
trusted: marketOffer?.trusted ?? false,
|
||||||
|
trades24h: marketOffer?.trades24h ?? 0,
|
||||||
|
volume24hUsd: formatMicroUsd(marketOffer?.volume24hMicroUsd ?? 0),
|
||||||
|
capRemainingUsd: marketOffer?.capRemainingMicroUsd === null ||
|
||||||
|
marketOffer === undefined
|
||||||
|
? null
|
||||||
|
: formatMicroUsd(marketOffer.capRemainingMicroUsd),
|
||||||
|
},
|
||||||
|
earnings: earningsSummary(earnings, today),
|
||||||
|
recentSales: earnings.recentSales.slice(0, 25).map((sale) => ({
|
||||||
|
id: sale.id,
|
||||||
|
model: sale.model,
|
||||||
|
createdAt: sale.createdAt,
|
||||||
|
settlementStatus: sale.settlementStatus,
|
||||||
|
inputTokens: sale.inputTokens,
|
||||||
|
outputTokens: sale.outputTokens,
|
||||||
|
cacheReadTokens: sale.cacheReadTokens,
|
||||||
|
revenueUsd: formatMicroUsd(sale.sellerCostMicroUsd),
|
||||||
|
transactionHash: sale.transactionHash,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
vector: this.vector,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findOffer(
|
||||||
|
offers: SurplusOffer[],
|
||||||
|
offerId: string | null,
|
||||||
|
): SurplusOffer | undefined {
|
||||||
|
return offerId ? offers.find((offer) => offer.id === offerId) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMarketOffer(
|
||||||
|
orderBook: SurplusOrderBook | null,
|
||||||
|
offerId: string | null,
|
||||||
|
) {
|
||||||
|
return offerId
|
||||||
|
? orderBook?.offers.find((offer) => offer.id === offerId)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function earningsSummary(
|
||||||
|
earnings: SurplusEarnings,
|
||||||
|
today: SurplusDailyEarnings | null,
|
||||||
|
): SurplusDashboardSnapshot["surplus"]["earnings"] {
|
||||||
|
return {
|
||||||
|
totalUsd: formatMicroUsd(earnings.totalEarnedMicroUsd),
|
||||||
|
pendingUsd: formatMicroUsd(earnings.pendingMicroUsd),
|
||||||
|
paidUsd: formatMicroUsd(earnings.paidMicroUsd),
|
||||||
|
requests: earnings.requestCount,
|
||||||
|
tokens: earnings.tokenCount,
|
||||||
|
today,
|
||||||
|
topModel: earnings.topModel,
|
||||||
|
payoutHoldReason: earnings.payoutHoldReason,
|
||||||
|
payoutHoldReleasesAt: earnings.payoutHoldReleasesAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
62
supplier/src/surplus/dashboardPage.ts
Normal file
62
supplier/src/surplus/dashboardPage.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
export function renderSurplusDashboardPage(): string {
|
||||||
|
return `<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<meta name="color-scheme" content="dark">
|
||||||
|
<title>Inference Resale Control Plane</title>
|
||||||
|
<style>
|
||||||
|
:root{--bg:#07110f;--surface:#0c1916;--surface-2:#10231e;--line:#244039;--text:#eef8f3;--muted:#91aaa1;--green:#57e39b;--teal:#5fd8cd;--blue:#79b8ff;--amber:#f3c969;--red:#ff827c;--shadow:0 24px 80px #0007}
|
||||||
|
*{box-sizing:border-box}html{min-width:320px}body{margin:0;background:radial-gradient(circle at 8% -5%,#17493b 0,transparent 29rem),radial-gradient(circle at 95% 20%,#15344b 0,transparent 34rem),var(--bg);color:var(--text);font:14px/1.5 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||||
|
body:before{content:"";position:fixed;inset:0;pointer-events:none;background-image:linear-gradient(#fff008 1px,transparent 1px),linear-gradient(90deg,#fff008 1px,transparent 1px);background-size:42px 42px;mask-image:linear-gradient(to bottom,#0008,transparent 55%)}
|
||||||
|
a{color:var(--blue);text-decoration:none}a:hover,a:focus-visible{text-decoration:underline}.shell{width:min(1240px,calc(100% - 32px));margin:auto;padding:38px 0 72px}.top{display:flex;justify-content:space-between;align-items:flex-start;gap:32px;margin-bottom:26px}.eyebrow{color:var(--green);font:700 11px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.16em;text-transform:uppercase}.top h1{margin:10px 0 8px;font-size:clamp(32px,5.5vw,58px);line-height:.98;letter-spacing:-.055em;max-width:780px}.top p{color:var(--muted);margin:0;max-width:660px;font-size:15px}.live-cluster{text-align:right;min-width:210px}.updated{color:var(--muted);font-variant-numeric:tabular-nums;margin-top:8px}.badge{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:999px;padding:7px 11px;background:#081511cc;font-size:12px;font-weight:700}.badge:before{content:"";width:7px;height:7px;border-radius:50%;background:var(--muted);box-shadow:0 0 0 3px #fff1}.badge.live:before{background:var(--green);box-shadow:0 0 18px var(--green)}.badge.retired:before{background:var(--amber)}
|
||||||
|
.panel{border:1px solid var(--line);border-radius:18px;background:linear-gradient(145deg,#10231ee8,#0a1714f2);box-shadow:var(--shadow)}.flow{display:grid;grid-template-columns:1fr 42px 1.1fr 42px 1.15fr;align-items:stretch;padding:14px;margin-bottom:18px;overflow:hidden}.node{padding:18px;border-radius:13px;background:#091612;border:1px solid #24443a;min-width:0}.node strong{display:block;font-size:18px;margin:5px 0 3px}.node small{color:var(--muted);display:block}.node .sub{font:12px ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.arrow{display:grid;place-items:center;color:var(--teal);font-size:22px}.markets{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:18px}.market-card{padding:22px;position:relative;overflow:hidden}.market-card:after{content:"";position:absolute;width:180px;height:180px;border-radius:50%;right:-90px;top:-110px;background:#57e39b0b}.market-head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.market-head h2{margin:3px 0 0;font-size:22px}.market-head p{margin:5px 0 0;color:var(--muted)}.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:16px 0 0}.metric{padding:14px;border:1px solid #254039;border-radius:12px;background:#07130f9c;min-width:0}.label{display:block;color:var(--muted);text-transform:uppercase;letter-spacing:.11em;font-size:10px;font-weight:700}.metric strong{display:block;font-size:clamp(18px,2.2vw,27px);letter-spacing:-.035em;margin-top:5px;font-variant-numeric:tabular-nums}.metric small{color:var(--muted);display:block;margin-top:2px}.good{color:var(--green)}.warn{color:var(--amber)}.retired-copy{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:16px}.fact{padding:12px 14px;border-left:2px solid var(--amber);background:#f3c9690a}.fact strong{display:block;margin-top:3px}
|
||||||
|
.section-head{display:flex;justify-content:space-between;align-items:end;gap:20px;margin:28px 2px 12px}.section-head h2{margin:0;font-size:22px;letter-spacing:-.025em}.section-head p{margin:0;color:var(--muted)}.table-wrap{overflow:auto}.sales{width:100%;border-collapse:collapse;min-width:820px}.sales th,.sales td{text-align:left;padding:12px 15px;border-bottom:1px solid #213a33}.sales th{position:sticky;top:0;background:#0d1b17;color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.12em}.sales tbody tr:hover{background:#ffffff05}.sales tbody tr:last-child td{border-bottom:0}.status-pill{display:inline-flex;border-radius:999px;padding:3px 8px;background:#ffffff0b;border:1px solid #ffffff13;font-size:11px}.status-pill.confirmed{color:var(--green);border-color:#57e39b55}.status-pill.accrued{color:var(--amber);border-color:#f3c96955}.empty{padding:34px!important;text-align:center!important;color:var(--muted)}.alert{display:none;margin-top:14px;padding:12px 14px;border-radius:11px;background:#ff827c10;border:1px solid #ff827c44;color:#ffc4bf}.alert.visible{display:block}.foot{display:flex;justify-content:space-between;gap:24px;color:var(--muted);margin-top:24px;font-size:12px}.foot span:last-child{text-align:right}
|
||||||
|
@media(max-width:980px){.metrics{grid-template-columns:repeat(2,1fr)}.markets{grid-template-columns:1fr}.flow{grid-template-columns:1fr}.arrow{transform:rotate(90deg);height:34px}.top{flex-direction:column}.live-cluster{text-align:left}.retired-copy{grid-template-columns:1fr 1fr}}
|
||||||
|
@media(max-width:560px){.shell{width:min(100% - 20px,1240px);padding-top:24px}.metrics,.retired-copy{grid-template-columns:1fr}.market-card{padding:17px}.top h1{font-size:38px}.foot{flex-direction:column}.foot span:last-child{text-align:left}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="shell">
|
||||||
|
<header class="top"><div><div class="eyebrow">Autonomous inference resale</div><h1>One capacity pool.<br>Two settlement markets.</h1><p>Live OpenRouter capacity sold through Surplus on Base, with the completed Vector AP3X proof preserved as the migration record.</p></div><div class="live-cluster"><span class="badge" id="controller-badge">Loading controller</span><div class="updated" id="updated">Waiting for current state</div></div></header>
|
||||||
|
<section class="panel flow" aria-label="Capacity routing flow">
|
||||||
|
<div class="node"><span class="label">Upstream capacity</span><strong>OpenRouter</strong><small id="flow-allowance">Allowance loading</small><small class="sub" id="flow-provider-model">provider model --</small></div><div class="arrow" aria-hidden="true">→</div>
|
||||||
|
<div class="node"><span class="label">Autonomous manager</span><strong>Seller agent</strong><small>Discovers · prices · caps · reconciles</small><small class="sub" id="flow-wallet">seller --</small></div><div class="arrow" aria-hidden="true">→</div>
|
||||||
|
<div class="node"><span class="label">Current settlement</span><strong>Surplus / Base USDC</strong><small id="flow-surplus">Offer loading</small><small class="sub" id="flow-offer">offer --</small></div>
|
||||||
|
</section>
|
||||||
|
<section class="markets">
|
||||||
|
<article class="panel market-card"><div class="market-head"><div><span class="label">Current market</span><h2>Surplus Intelligence</h2><p>Competitive model routing with USDC settlement on Base.</p></div><span class="badge" id="surplus-badge">Loading</span></div>
|
||||||
|
<div class="metrics"><div class="metric"><span class="label">Lifetime revenue</span><strong id="earned">--</strong><small id="earnings-state">--</small></div><div class="metric"><span class="label">Requests sold</span><strong id="requests">--</strong><small id="tokens">-- tokens</small></div><div class="metric"><span class="label">Market position</span><strong id="rank">--</strong><small id="market-state">--</small></div><div class="metric"><span class="label">Daily seller cap</span><strong id="daily-cap">--</strong><small id="cap-remaining">-- remaining</small></div></div>
|
||||||
|
<div class="alert" id="payout-hold"></div><div class="alert" id="controller-error"></div></article>
|
||||||
|
<article class="panel market-card"><div class="market-head"><div><span class="label">Migration record</span><h2>Vector marketplace</h2><p>AP3X escrow route retired after the cross-market proof.</p></div><span class="badge retired">Retired</span></div>
|
||||||
|
<div class="metrics"><div class="metric"><span class="label">AP3X earned</span><strong id="vector-earned">--</strong><small>historical</small></div><div class="metric"><span class="label">Settled jobs</span><strong id="vector-jobs">--</strong><small>Vector escrows</small></div><div class="metric"><span class="label">Upstream spend</span><strong id="vector-spend">--</strong><small>proof route</small></div><div class="metric"><span class="label">Retired on</span><strong id="vector-date">--</strong><small>advert closed</small></div></div>
|
||||||
|
<div class="retired-copy"><div class="fact"><span class="label">Retired advert</span><strong class="mono truncate" id="vector-advert">--</strong><a id="advert-link" target="_blank" rel="noopener noreferrer">Open creation transaction ↗</a></div><div class="fact"><span class="label">Retirement proof</span><strong class="mono truncate" id="vector-retirement">--</strong><a id="retirement-link" target="_blank" rel="noopener noreferrer">Open confirmed transaction ↗</a></div></div></article>
|
||||||
|
</section>
|
||||||
|
<section class="panel market-card"><div class="market-head"><div><span class="label">Economics</span><h2>Capacity and price controls</h2><p>The seller cap is discounted revenue. The agent converts it to estimated upstream exposure before it opens the route.</p></div><span class="badge live">Bounded</span></div>
|
||||||
|
<div class="metrics"><div class="metric"><span class="label">OpenRouter remaining</span><strong id="provider-remaining">--</strong><small id="provider-used">-- used</small></div><div class="metric"><span class="label">Protected reserve</span><strong id="provider-reserve">--</strong><small>fail-closed threshold</small></div><div class="metric"><span class="label">Daily upstream estimate</span><strong id="provider-exposure">--</strong><small>from recovery floor</small></div><div class="metric"><span class="label">Current multiplier</span><strong id="multiplier">--</strong><small id="prices">--</small></div></div></section>
|
||||||
|
<div class="section-head"><div><span class="label">Surplus activity</span><h2>Recent sales</h2></div><p id="sales-note">Loading settlement records</p></div>
|
||||||
|
<section class="panel table-wrap"><table class="sales"><thead><tr><th>Time</th><th>Status</th><th>Model</th><th>Input</th><th>Output</th><th>Cache read</th><th>Revenue</th><th>Base settlement</th></tr></thead><tbody id="sales-body"><tr><td colspan="8" class="empty">Loading sales</td></tr></tbody></table></section>
|
||||||
|
<footer class="foot"><span id="identity">Seller identity loading</span><span>Vector proves the agent-owned route. Surplus is the current USDC market.</span></footer>
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
(function(){'use strict';
|
||||||
|
var $=function(id){return document.getElementById(id)};
|
||||||
|
var set=function(id,value){$(id).textContent=value==null?'--':String(value)};
|
||||||
|
var usd=function(value){if(value==null||value==='')return'--';var n=Number(value);return Number.isFinite(n)?'$'+n.toFixed(n<.01?6:4):'--'};
|
||||||
|
var count=function(value){return new Intl.NumberFormat().format(Number(value)||0)};
|
||||||
|
var short=function(value){var s=String(value||'');return s.length>18?s.slice(0,10)+'…'+s.slice(-7):s||'--'};
|
||||||
|
var when=function(value){if(!value)return'--';var d=new Date(value);return Number.isNaN(d.getTime())?'--':d.toLocaleString()};
|
||||||
|
function statusBadge(id,label,live){var el=$(id);el.textContent=label;el.className='badge '+(live?'live':'');}
|
||||||
|
function link(id,href){var el=$(id);el.href=href;}
|
||||||
|
function txLink(row,hash){var td=document.createElement('td');if(typeof hash==='string'&&/^0x[0-9a-fA-F]{64}$/.test(hash)){var a=document.createElement('a');a.href='https://basescan.org/tx/'+hash;a.target='_blank';a.rel='noopener noreferrer';a.textContent=short(hash);td.appendChild(a)}else{td.textContent='Accruing'}row.appendChild(td)}
|
||||||
|
function cell(row,value,className){var td=document.createElement('td');if(className)td.className=className;td.textContent=value==null?'--':String(value);row.appendChild(td)}
|
||||||
|
function renderSales(sales){var body=$('sales-body');body.replaceChildren();if(!sales.length){var empty=document.createElement('tr');cell(empty,'No sales returned','empty');empty.firstChild.colSpan=8;body.appendChild(empty);return}sales.forEach(function(sale){var row=document.createElement('tr');cell(row,when(sale.createdAt));var status=document.createElement('td');var pill=document.createElement('span');pill.className='status-pill '+sale.settlementStatus;pill.textContent=sale.settlementStatus;status.appendChild(pill);row.appendChild(status);cell(row,sale.model,'mono');cell(row,count(sale.inputTokens));cell(row,count(sale.outputTokens));cell(row,count(sale.cacheReadTokens));cell(row,usd(sale.revenueUsd));txLink(row,sale.transactionHash);body.appendChild(row)})}
|
||||||
|
function render(data){var c=data.controller,o=data.surplus.offer,e=data.surplus.earnings,p=data.provider,v=data.vector;statusBadge('controller-badge',c.phase,c.ok);statusBadge('surplus-badge',o.status,o.available&&o.healthy);set('updated','Updated '+when(data.generatedAt));set('flow-allowance',usd(p.remainingUsd)+' of '+usd(p.hardLimitUsd)+' remaining');set('flow-provider-model',o.providerModel);set('flow-wallet',short(data.identity.sellerWallet));set('flow-surplus',(o.available?'Available':'Unavailable')+' · '+(o.healthy?'healthy':'not healthy'));set('flow-offer',o.id);set('earned',usd(e.totalUsd));set('earnings-state',usd(e.pendingUsd)+' pending · '+usd(e.paidUsd)+' paid');set('requests',count(e.requests));set('tokens',count(e.tokens)+' tokens');set('rank',o.rank==null?'--':'#'+o.rank);set('market-state',(o.trusted?'trusted':'untrusted')+' · '+count(o.trades24h)+' trades / 24h');set('daily-cap',usd(o.dailyCapUsd));set('cap-remaining',usd(o.capRemainingUsd)+' remaining');set('provider-remaining',usd(p.remainingUsd));set('provider-used',usd(p.usedUsd)+' used of '+usd(p.hardLimitUsd));set('provider-reserve',usd(p.reserveUsd));set('provider-exposure',usd(p.estimatedDailyExposureUsd));set('multiplier',o.costMultiplier==null?'--':(o.costMultiplier*100).toFixed(3)+'%');set('prices',usd(o.inputUsdPer1m)+' in · '+usd(o.outputUsdPer1m)+' out / 1M');set('vector-earned',v.historicalAp3xEarned+' AP3X');set('vector-jobs',count(v.historicalSettledJobs));set('vector-spend',usd(v.historicalUpstreamSpendUsd));set('vector-date',v.retiredOn);set('vector-advert',v.advertRef);set('vector-retirement',v.retirementTransaction);var advertHash=String(v.advertRef).split('#')[0];link('advert-link','https://vector.apexscan.org/en/transaction/'+advertHash+'/summary/');link('retirement-link','https://vector.apexscan.org/en/transaction/'+v.retirementTransaction+'/summary/');set('identity','Surplus '+short(data.identity.sellerWallet)+' · payout '+short(data.identity.payoutAddress));set('sales-note',count(e.requests)+' lifetime requests · '+count(e.tokens)+' tokens');var hold=$('payout-hold');if(e.payoutHoldReason){hold.className='alert visible';hold.textContent='Payout hold: '+e.payoutHoldReason+(e.payoutHoldReleasesAt?' · releases '+when(e.payoutHoldReleasesAt):'')}else{hold.className='alert'}var error=$('controller-error');if(c.lastError){error.className='alert visible';error.textContent='Controller: '+c.lastError}else{error.className='alert'}renderSales(data.surplus.recentSales)}
|
||||||
|
async function refresh(){try{var response=await fetch('/dashboard/api',{cache:'no-store'});if(!response.ok)throw new Error('Dashboard API returned '+response.status);render(await response.json())}catch(error){statusBadge('controller-badge','data unavailable',false);set('updated',error instanceof Error?error.message:'Dashboard data unavailable')}}
|
||||||
|
refresh();setInterval(refresh,15000);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
@@ -201,6 +201,9 @@ describe("SurplusClient", () => {
|
|||||||
healthy: true,
|
healthy: true,
|
||||||
trusted: true,
|
trusted: true,
|
||||||
trades_24h: 7,
|
trades_24h: 7,
|
||||||
|
rank: 1,
|
||||||
|
cap_remaining: null,
|
||||||
|
volume_24h: 0,
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -222,6 +225,79 @@ describe("SurplusClient", () => {
|
|||||||
expect(String(fetchFn.mock.calls[1][0])).toContain("alpha%3Amodel");
|
expect(String(fetchFn.mock.calls[1][0])).toContain("alpha%3Amodel");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes live earnings, payout hold, and settlement records", async () => {
|
||||||
|
const client = new SurplusClient({
|
||||||
|
apiBaseUrl: "https://api.surplusintelligence.ai",
|
||||||
|
sellerApiKey: SELLER_KEY,
|
||||||
|
timeoutMs: 1_000,
|
||||||
|
fetchFn: async () => Response.json({
|
||||||
|
total_earned_usdc: "186810",
|
||||||
|
pending_usdc: "176817",
|
||||||
|
paid_usdc: "9993",
|
||||||
|
daily: [{
|
||||||
|
day: "2026-08-27",
|
||||||
|
earned_usd: 0.18681,
|
||||||
|
input_tokens: 100,
|
||||||
|
output_tokens: 20,
|
||||||
|
requests: 3,
|
||||||
|
total_tokens: 120,
|
||||||
|
}],
|
||||||
|
by_model: [{
|
||||||
|
model: "alpha",
|
||||||
|
earned_usd: 0.18681,
|
||||||
|
requests: 3,
|
||||||
|
total_tokens: 120,
|
||||||
|
last_sale_at: 1787831146968,
|
||||||
|
}],
|
||||||
|
share: {
|
||||||
|
requests: 3,
|
||||||
|
tokens: 120,
|
||||||
|
top_model: "alpha",
|
||||||
|
},
|
||||||
|
recent_sales: [{
|
||||||
|
id: "sale-1",
|
||||||
|
model: "alpha",
|
||||||
|
offer_id: "offer-1",
|
||||||
|
settlement_status: "confirmed",
|
||||||
|
created_at: 1787831146968,
|
||||||
|
seller_cost_usdc: "83",
|
||||||
|
input_tokens: 10,
|
||||||
|
output_tokens: 2,
|
||||||
|
cache_read_tokens: 4,
|
||||||
|
effective_input_per_1m: 0.004,
|
||||||
|
effective_output_per_1m: 0.008,
|
||||||
|
tx_hash: "0x" + "a".repeat(64),
|
||||||
|
}],
|
||||||
|
payout_hold: {
|
||||||
|
reason: "new_seller",
|
||||||
|
releases_at: "2026-08-29T11:24:42.552Z",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const earnings = await client.getEarnings();
|
||||||
|
|
||||||
|
expect(earnings).toMatchObject({
|
||||||
|
totalEarnedMicroUsd: 186_810,
|
||||||
|
pendingMicroUsd: 176_817,
|
||||||
|
paidMicroUsd: 9_993,
|
||||||
|
requestCount: 3,
|
||||||
|
tokenCount: 120,
|
||||||
|
topModel: "alpha",
|
||||||
|
payoutHoldReason: "new_seller",
|
||||||
|
});
|
||||||
|
expect(earnings.daily[0]).toMatchObject({ requests: 3, totalTokens: 120 });
|
||||||
|
expect(earnings.recentSales[0]).toMatchObject({
|
||||||
|
id: "sale-1",
|
||||||
|
settlementStatus: "confirmed",
|
||||||
|
cacheReadTokens: 4,
|
||||||
|
sellerCostMicroUsd: 83,
|
||||||
|
});
|
||||||
|
expect(earnings.recentSales[0].createdAt).toBe(
|
||||||
|
new Date(1787831146968).toISOString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("sends one absolute cost-multiplier PATCH with the cap", async () => {
|
it("sends one absolute cost-multiplier PATCH with the cap", async () => {
|
||||||
let request: RequestInit | undefined;
|
let request: RequestInit | undefined;
|
||||||
const client = new SurplusClient({
|
const client = new SurplusClient({
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ class FakeSurplusClient implements SurplusControllerClient {
|
|||||||
healthy: true,
|
healthy: true,
|
||||||
trusted: true,
|
trusted: true,
|
||||||
trades24h: 0,
|
trades24h: 0,
|
||||||
|
rank: 1,
|
||||||
|
capRemainingMicroUsd: null,
|
||||||
|
volume24hMicroUsd: 0,
|
||||||
}];
|
}];
|
||||||
if (this.offer) {
|
if (this.offer) {
|
||||||
offers.push({
|
offers.push({
|
||||||
@@ -126,6 +129,9 @@ class FakeSurplusClient implements SurplusControllerClient {
|
|||||||
healthy: true,
|
healthy: true,
|
||||||
trusted: true,
|
trusted: true,
|
||||||
trades24h: this.trades24h,
|
trades24h: this.trades24h,
|
||||||
|
rank: 2,
|
||||||
|
capRemainingMicroUsd: 50_000,
|
||||||
|
volume24hMicroUsd: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { model, offers };
|
return { model, offers };
|
||||||
@@ -187,12 +193,26 @@ class FakeSurplusClient implements SurplusControllerClient {
|
|||||||
totalEarnedMicroUsd: this.settled ? 100 : 0,
|
totalEarnedMicroUsd: this.settled ? 100 : 0,
|
||||||
pendingMicroUsd: 0,
|
pendingMicroUsd: 0,
|
||||||
paidMicroUsd: this.settled ? 100 : 0,
|
paidMicroUsd: this.settled ? 100 : 0,
|
||||||
|
daily: [],
|
||||||
|
byModel: [],
|
||||||
|
requestCount: this.settled ? 1 : 0,
|
||||||
|
tokenCount: this.settled ? 2 : 0,
|
||||||
|
topModel: this.settled ? "beta-model" : null,
|
||||||
|
payoutHoldReason: null,
|
||||||
|
payoutHoldReleasesAt: null,
|
||||||
recentSales: this.settled ? [{
|
recentSales: this.settled ? [{
|
||||||
|
id: "sale-1",
|
||||||
model: "beta-model",
|
model: "beta-model",
|
||||||
offerId: "managed-offer",
|
offerId: "managed-offer",
|
||||||
settlementStatus: "confirmed",
|
settlementStatus: "confirmed",
|
||||||
createdAt: "2026-08-27T10:00:01.000Z",
|
createdAt: "2026-08-27T10:00:01.000Z",
|
||||||
sellerCostMicroUsd: 100,
|
sellerCostMicroUsd: 100,
|
||||||
|
inputTokens: 1,
|
||||||
|
outputTokens: 1,
|
||||||
|
cacheReadTokens: 0,
|
||||||
|
effectiveInputUsdPer1m: 0.003972,
|
||||||
|
effectiveOutputUsdPer1m: 0.007944,
|
||||||
|
transactionHash: "0x" + "a".repeat(64),
|
||||||
}] : [],
|
}] : [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
259
tests/unit/surplus-dashboard.test.ts
Normal file
259
tests/unit/surplus-dashboard.test.ts
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import request from "supertest";
|
||||||
|
import type { SurplusManagerConfig } from "../../supplier/src/surplus/config.js";
|
||||||
|
import {
|
||||||
|
SurplusDashboardService,
|
||||||
|
type SurplusDashboardSnapshot,
|
||||||
|
} from "../../supplier/src/surplus/dashboard.js";
|
||||||
|
import { renderSurplusDashboardPage } from "../../supplier/src/surplus/dashboardPage.js";
|
||||||
|
import { createSurplusServer } from "../../supplier/src/cli/surplus-seller.js";
|
||||||
|
import type { SurplusControllerStatus } from "../../supplier/src/surplus/controller.js";
|
||||||
|
|
||||||
|
const STATUS: SurplusControllerStatus = {
|
||||||
|
ok: true,
|
||||||
|
phase: "active",
|
||||||
|
live: true,
|
||||||
|
model: "deepseek-v4-flash",
|
||||||
|
providerModelId: "deepseek/deepseek-v4-flash",
|
||||||
|
offerId: "offer-1",
|
||||||
|
inputUsdPer1m: "0.003972",
|
||||||
|
outputUsdPer1m: "0.007944",
|
||||||
|
competitorId: "external",
|
||||||
|
remainingAllowanceUsd: "16.313443",
|
||||||
|
reserveThresholdUsd: "1.00",
|
||||||
|
totalEarnedUsd: "0.186810",
|
||||||
|
lastCycleAt: "2026-08-28T12:00:00.000Z",
|
||||||
|
lastSuccessAt: "2026-08-28T12:00:00.000Z",
|
||||||
|
lastError: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
function config(): SurplusManagerConfig {
|
||||||
|
return {
|
||||||
|
live: true,
|
||||||
|
apiBaseUrl: "https://api.surplusintelligence.ai",
|
||||||
|
sellerApiKey: "si_seller_test_secret",
|
||||||
|
providerApiKey: "sk-or-v1-test-secret",
|
||||||
|
providerBaseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
capacityBaseUrl: "https://openrouter.ai/api",
|
||||||
|
perOfferCapUsd: "0.05",
|
||||||
|
aggregateCapUsd: "0.05",
|
||||||
|
sellerWallet: "0x1111111111111111111111111111111111111111",
|
||||||
|
payoutAddress: "0x1111111111111111111111111111111111111111",
|
||||||
|
maxCandidateOrderBooks: 10,
|
||||||
|
reserveUsd: "1.00",
|
||||||
|
maxProviderLimitUsd: "20.00",
|
||||||
|
recoveryBps: 490,
|
||||||
|
undercutBps: 10,
|
||||||
|
pollIntervalMs: 300_000,
|
||||||
|
requestTimeoutMs: 120_000,
|
||||||
|
stopAfterTrades: 0,
|
||||||
|
settledStatuses: ["confirmed"],
|
||||||
|
statePath: "/tmp/surplus-dashboard-test.json",
|
||||||
|
port: 8080,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function snapshot(): SurplusDashboardSnapshot {
|
||||||
|
return {
|
||||||
|
generatedAt: "2026-08-28T12:00:00.000Z",
|
||||||
|
controller: STATUS,
|
||||||
|
identity: {
|
||||||
|
sellerWallet: "0x1111111111111111111111111111111111111111",
|
||||||
|
payoutAddress: "0x1111111111111111111111111111111111111111",
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
name: "OpenRouter",
|
||||||
|
hardLimitUsd: "20.000000",
|
||||||
|
remainingUsd: "16.313443",
|
||||||
|
usedUsd: "3.686557",
|
||||||
|
reserveUsd: "1.00",
|
||||||
|
estimatedDailyExposureUsd: "1.020408",
|
||||||
|
},
|
||||||
|
surplus: {
|
||||||
|
offer: {
|
||||||
|
id: "offer-1",
|
||||||
|
model: "deepseek-v4-flash",
|
||||||
|
providerModel: "deepseek/deepseek-v4-flash",
|
||||||
|
status: "active",
|
||||||
|
dailyCapUsd: "0.050000",
|
||||||
|
costMultiplier: 0.04995,
|
||||||
|
inputUsdPer1m: "0.003972",
|
||||||
|
outputUsdPer1m: "0.007944",
|
||||||
|
rank: 10,
|
||||||
|
available: true,
|
||||||
|
healthy: true,
|
||||||
|
trusted: true,
|
||||||
|
trades24h: 429,
|
||||||
|
volume24hUsd: "0.064030",
|
||||||
|
capRemainingUsd: "0.050000",
|
||||||
|
},
|
||||||
|
earnings: {
|
||||||
|
totalUsd: "0.186810",
|
||||||
|
pendingUsd: "0.176817",
|
||||||
|
paidUsd: "0.009993",
|
||||||
|
requests: 1_093,
|
||||||
|
tokens: 104_698_905,
|
||||||
|
today: null,
|
||||||
|
topModel: "deepseek-v4-flash",
|
||||||
|
payoutHoldReason: "new_seller",
|
||||||
|
payoutHoldReleasesAt: "2026-08-29T11:24:42.552Z",
|
||||||
|
},
|
||||||
|
recentSales: [],
|
||||||
|
},
|
||||||
|
vector: {
|
||||||
|
status: "retired",
|
||||||
|
model: "deepseek/deepseek-v4-flash",
|
||||||
|
supplierWallet: "addr1test",
|
||||||
|
advertRef: `${"a".repeat(64)}#0`,
|
||||||
|
retirementTransaction: "b".repeat(64),
|
||||||
|
retiredOn: "2026-08-27",
|
||||||
|
historicalAp3xEarned: "1.600000",
|
||||||
|
historicalSettledJobs: 8,
|
||||||
|
historicalUpstreamSpendUsd: "0.000093",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("SurplusDashboardService", () => {
|
||||||
|
it("combines safe live metrics with the Vector migration record", async () => {
|
||||||
|
const listAllOffers = vi.fn(async () => [{
|
||||||
|
id: "offer-1",
|
||||||
|
model: "deepseek-v4-flash",
|
||||||
|
sellerBaseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
status: "active" as const,
|
||||||
|
capDailyUsd: 0.05,
|
||||||
|
costMultiplierPpm: 49_950,
|
||||||
|
inputMicroUsdPer1m: 3_972,
|
||||||
|
outputMicroUsdPer1m: 7_944,
|
||||||
|
}]);
|
||||||
|
const getEarnings = vi.fn(async () => ({
|
||||||
|
totalEarnedMicroUsd: 186_810,
|
||||||
|
pendingMicroUsd: 176_817,
|
||||||
|
paidMicroUsd: 9_993,
|
||||||
|
daily: [{
|
||||||
|
day: "2026-08-28",
|
||||||
|
earnedUsd: 0.01,
|
||||||
|
inputTokens: 10,
|
||||||
|
outputTokens: 2,
|
||||||
|
requests: 1,
|
||||||
|
totalTokens: 12,
|
||||||
|
}],
|
||||||
|
byModel: [],
|
||||||
|
requestCount: 1_093,
|
||||||
|
tokenCount: 104_698_905,
|
||||||
|
topModel: "deepseek-v4-flash",
|
||||||
|
payoutHoldReason: "new_seller",
|
||||||
|
payoutHoldReleasesAt: "2026-08-29T11:24:42.552Z",
|
||||||
|
recentSales: [{
|
||||||
|
id: "sale-1",
|
||||||
|
model: "deepseek-v4-flash",
|
||||||
|
offerId: "offer-1",
|
||||||
|
settlementStatus: "confirmed",
|
||||||
|
createdAt: "2026-08-28T11:00:00.000Z",
|
||||||
|
sellerCostMicroUsd: 83,
|
||||||
|
inputTokens: 10,
|
||||||
|
outputTokens: 2,
|
||||||
|
cacheReadTokens: 3,
|
||||||
|
effectiveInputUsdPer1m: 0.003972,
|
||||||
|
effectiveOutputUsdPer1m: 0.007944,
|
||||||
|
transactionHash: "0x" + "c".repeat(64),
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
const getOrderBook = vi.fn(async () => ({
|
||||||
|
model: "deepseek-v4-flash",
|
||||||
|
offers: [{
|
||||||
|
id: "offer-1",
|
||||||
|
seller: "0x1111111111111111111111111111111111111111",
|
||||||
|
sellerBaseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
inputMicroUsdPer1m: 3_972,
|
||||||
|
outputMicroUsdPer1m: 7_944,
|
||||||
|
available: true,
|
||||||
|
healthy: true,
|
||||||
|
trusted: true,
|
||||||
|
trades24h: 429,
|
||||||
|
rank: 10,
|
||||||
|
capRemainingMicroUsd: 50_000,
|
||||||
|
volume24hMicroUsd: 64_030,
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
const service = new SurplusDashboardService({
|
||||||
|
controller: { snapshot: () => STATUS },
|
||||||
|
client: { listAllOffers, getEarnings, getOrderBook },
|
||||||
|
allowance: {
|
||||||
|
readAllowance: async () => ({
|
||||||
|
limitUsdNanos: 20_000_000_000n,
|
||||||
|
remainingUsdNanos: 16_313_443_131n,
|
||||||
|
reset: null,
|
||||||
|
checkedAtMs: Date.parse("2026-08-28T12:00:00.000Z"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
config: config(),
|
||||||
|
now: () => Date.parse("2026-08-28T12:00:00.000Z"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const first = await service.getSnapshot();
|
||||||
|
const second = await service.getSnapshot();
|
||||||
|
|
||||||
|
expect(first.surplus.offer).toMatchObject({
|
||||||
|
id: "offer-1",
|
||||||
|
rank: 10,
|
||||||
|
available: true,
|
||||||
|
dailyCapUsd: "0.050000",
|
||||||
|
});
|
||||||
|
expect(first.surplus.earnings).toMatchObject({
|
||||||
|
totalUsd: "0.186810",
|
||||||
|
requests: 1_093,
|
||||||
|
topModel: "deepseek-v4-flash",
|
||||||
|
});
|
||||||
|
expect(first.provider).toMatchObject({
|
||||||
|
usedUsd: "3.686556",
|
||||||
|
estimatedDailyExposureUsd: "1.020408",
|
||||||
|
});
|
||||||
|
expect(first.vector).toMatchObject({ status: "retired", historicalSettledJobs: 8 });
|
||||||
|
expect(first.surplus.recentSales[0]).toMatchObject({
|
||||||
|
settlementStatus: "confirmed",
|
||||||
|
revenueUsd: "0.000083",
|
||||||
|
});
|
||||||
|
expect(JSON.stringify(first)).not.toContain("si_seller_test_secret");
|
||||||
|
expect(JSON.stringify(first)).not.toContain("sk-or-v1-test-secret");
|
||||||
|
expect(second).toBe(first);
|
||||||
|
expect(listAllOffers).toHaveBeenCalledTimes(1);
|
||||||
|
expect(getEarnings).toHaveBeenCalledTimes(1);
|
||||||
|
expect(getOrderBook).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("unified dashboard HTTP surface", () => {
|
||||||
|
it("serves the dashboard, cached API data, and security headers", async () => {
|
||||||
|
const data = snapshot();
|
||||||
|
const server = createSurplusServer(
|
||||||
|
{ snapshot: () => STATUS, healthy: () => true },
|
||||||
|
{ getSnapshot: async () => data },
|
||||||
|
);
|
||||||
|
|
||||||
|
const page = await request(server).get("/reseller");
|
||||||
|
expect(page.status).toBe(200);
|
||||||
|
expect(page.text).toContain("One capacity pool.");
|
||||||
|
expect(page.text).toContain("Vector marketplace");
|
||||||
|
expect(page.headers["content-security-policy"]).toContain("frame-ancestors 'none'");
|
||||||
|
|
||||||
|
const api = await request(server).get("/dashboard/api");
|
||||||
|
expect(api.status).toBe(200);
|
||||||
|
expect(api.body).toEqual(data);
|
||||||
|
expect(JSON.stringify(api.body)).not.toContain("test-secret");
|
||||||
|
|
||||||
|
const root = await request(server).get("/");
|
||||||
|
expect(root.status).toBe(302);
|
||||||
|
expect(root.headers.location).toBe("/reseller");
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders static HTML without embedding credentials", () => {
|
||||||
|
const html = renderSurplusDashboardPage();
|
||||||
|
expect(html).toContain("/dashboard/api");
|
||||||
|
expect(html).toContain("Surplus Intelligence");
|
||||||
|
expect(html).toContain("Vector marketplace");
|
||||||
|
expect(html).not.toContain("si_seller_");
|
||||||
|
expect(html).not.toContain("sk-or-v1-");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user