feat: add unified resale dashboard
This commit is contained in:
@@ -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 { resolve } from "node:path";
|
||||
import { loadSurplusManagerConfig } from "../surplus/config.js";
|
||||
@@ -6,6 +11,11 @@ import { SurplusClient } from "../surplus/client.js";
|
||||
import { SurplusSellerController } from "../surplus/controller.js";
|
||||
import { OpenRouterKeyClient } from "../surplus/openRouterKey.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(
|
||||
env: Record<string, string | undefined>,
|
||||
@@ -29,7 +39,13 @@ export async function runSurplusSeller(
|
||||
stateStore,
|
||||
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) => {
|
||||
server.once("error", rejectListen);
|
||||
server.listen(config.port, "0.0.0.0", () => {
|
||||
@@ -41,26 +57,81 @@ export async function runSurplusSeller(
|
||||
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) => {
|
||||
const status = controller.snapshot();
|
||||
if (request.method === "GET" && request.url === "/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" && request.url === "/status") {
|
||||
void handleRequest(controller, dashboard, request, response);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRequest(
|
||||
controller: Pick<SurplusSellerController, "snapshot" | "healthy">,
|
||||
dashboard: DashboardSource,
|
||||
request: IncomingMessage,
|
||||
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.setHeader("content-type", "application/json");
|
||||
response.end(JSON.stringify(status));
|
||||
return;
|
||||
response.end(JSON.stringify(snapshot));
|
||||
} catch {
|
||||
response.statusCode = 503;
|
||||
response.setHeader("content-type", "application/json");
|
||||
response.end(JSON.stringify({ error: "dashboard_data_unavailable" }));
|
||||
}
|
||||
response.statusCode = 404;
|
||||
response.setHeader("content-type", "application/json");
|
||||
response.end(JSON.stringify({ error: "not_found" }));
|
||||
});
|
||||
return;
|
||||
}
|
||||
response.statusCode = 404;
|
||||
response.setHeader("content-type", "application/json");
|
||||
response.end(JSON.stringify({ error: "not_found" }));
|
||||
}
|
||||
|
||||
export async function main(
|
||||
|
||||
@@ -40,6 +40,9 @@ export interface SurplusOrderBookOffer {
|
||||
healthy: boolean;
|
||||
trusted: boolean;
|
||||
trades24h: number;
|
||||
rank: number;
|
||||
capRemainingMicroUsd: number | null;
|
||||
volume24hMicroUsd: number;
|
||||
}
|
||||
|
||||
export interface SurplusOrderBook {
|
||||
@@ -64,17 +67,48 @@ export interface SurplusOfferPatch {
|
||||
}
|
||||
|
||||
export interface SurplusSale {
|
||||
id: string;
|
||||
model: string;
|
||||
offerId: string | null;
|
||||
settlementStatus: string;
|
||||
createdAt: string | null;
|
||||
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 {
|
||||
totalEarnedMicroUsd: number;
|
||||
pendingMicroUsd: number;
|
||||
paidMicroUsd: number;
|
||||
daily: SurplusDailyEarnings[];
|
||||
byModel: SurplusModelEarnings[];
|
||||
requestCount: number;
|
||||
tokenCount: number;
|
||||
topModel: string | null;
|
||||
payoutHoldReason: string | null;
|
||||
payoutHoldReleasesAt: string | null;
|
||||
recentSales: SurplusSale[];
|
||||
}
|
||||
|
||||
@@ -263,15 +297,43 @@ export class SurplusClient {
|
||||
async getEarnings(): Promise<SurplusEarnings> {
|
||||
const body = await this.requestJson("/v1/seller/earnings?range=lifetime");
|
||||
const root = record(body, "Surplus earnings response");
|
||||
const rawSales = root.recent_sales;
|
||||
if (!Array.isArray(rawSales)) {
|
||||
if (!Array.isArray(root.recent_sales)) {
|
||||
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 {
|
||||
totalEarnedMicroUsd: microUsd(root.total_earned_usdc, "total_earned_usdc"),
|
||||
pendingMicroUsd: microUsd(root.pending_usdc, "pending_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`),
|
||||
trusted: boolean(item.trusted, `Surplus order book offer ${index}.trusted`),
|
||||
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 {
|
||||
const item = record(value, `Surplus sale ${index}`);
|
||||
return {
|
||||
id: string(item.id, `Surplus sale ${index}.id`),
|
||||
model: string(item.model, `Surplus sale ${index}.model`),
|
||||
offerId: optionalString(item.offer_id),
|
||||
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`),
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
if (typeof value !== "boolean") throw new Error(`${field} must be a boolean`);
|
||||
return value;
|
||||
@@ -560,6 +716,11 @@ function nonNegativeSafeInteger(value: unknown, field: string): number {
|
||||
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 {
|
||||
if (typeof value === "string" && /^(?:0|[1-9]\d*)$/.test(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>`;
|
||||
}
|
||||
Reference in New Issue
Block a user