Files
agents-marketplace/buyer/scripts/monitor-wallets.ts
david e8261d9500 feat(deploy): swap retired Hetzner suppliers for Qwen3.8-27B pair
Hetzner Inference dropped Kimi-K2.7-Code, GLM-5.2-NVFP4 and
DeepSeek-V4-Flash-0731 from /v1/models; the 6 suppliers serving them are
deregistered (adverts retired on-chain 2026-08-21, containers removed):

  kimi-code          3f37082166faf2b2220e6476235bae8723af9a3434a6aaf60277366ff1f62bfb
  kimi-code-chat     3d1b43645c00bdb7b429f29ced798bc4af206c9dc56074e955d84b603b64f50c
  glm                bf3ee48addb32e2b0726066f1806b9d84ad64b86c7d41d379abd4b439c522696
  glm-chat           b910327023edb6a3f31696b7feaa9448a151c671c2ff303c4c3dd5a07d03c6b5
  ds-flash-htz       61dd30deb1cd3d6c3f73792bda46ecea6890735237d8329ade2b8449d298f24e
  ds-flash-htz-chat  348a278c81a2bf664ea65705a6245df31e81f64c0ffa6e04a59b248fcdae3d9f

New qwen38 + qwen38-chat suppliers serve Hetzner's new Qwen3.8-27B
(ctx 262144; ping + tools round-trip verified 2026-08-21), reusing the
retired kimi-code / kimi-code-chat wallets. Adverts:

  qwen38       bb8a7078098dc5e60b3f97b040e38f05e6cca662497d4bea471e1dff19c1e1ae#0
  qwen38-chat  31e3a6e258ceaa0d1eaabe61fa3fbe3abcbbb400a7b885e05c8dbcdcf4924eda#0
2026-08-21 13:46:03 +02:00

520 lines
16 KiB
TypeScript

/**
* monitor-wallets.ts — show AP3X balances of every operator-owned wallet
* (across vector-marketplace + open-webui) plus every supplier currently
* registered on the mp-indexer, on Vector mainnet. Operator wallets below
* a per-role threshold are marked LOW so the operator can top them up.
*
* Reads private keys via `ssh <host> "grep ^VAR= <path>"`. The hex value
* lives only in the in-memory derivation result; it is never written to
* local disk, stdout, or JSON output.
*
* Run:
* pnpm tsx buyer/scripts/monitor-wallets.ts
* pnpm tsx buyer/scripts/monitor-wallets.ts --json
* pnpm tsx buyer/scripts/monitor-wallets.ts --buyer-min 15 --supplier-min 5
*/
/* eslint-disable no-console */
import { execFileSync } from "child_process";
import { createHash } from "crypto";
import * as ed from "@noble/ed25519";
import { blake2b } from "@noble/hashes/blake2b";
import { bech32 } from "bech32";
import { ReadOnlyOgmiosProvider } from "@marketplace/shared/chain";
ed.etc.sha512Sync = (...messages: Uint8Array[]): Uint8Array => {
const h = createHash("sha512");
for (const m of messages) h.update(m);
return new Uint8Array(h.digest());
};
const MAINNET_OGMIOS = "https://ogmios.vector.mainnet.apexfusion.org";
const INDEXER_URL = "https://mp-indexer.vector.apexfusion.org";
const NETWORK_ID = 1 as const;
interface OperatorSource {
label: string;
host: string;
envPath: string;
keyVar: "BUYER_PRIV_KEY_HEX" | "SUPPLIER_PRIV_KEY_HEX";
role: "buyer" | "supplier";
}
const OPERATOR_SOURCES: OperatorSource[] = [
{
label: "supplier-hf-llama33@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.hf-llama33",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "buyer@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/buyer/.env",
keyVar: "BUYER_PRIV_KEY_HEX",
role: "buyer",
},
{
label: "supplier-ollama@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-gpt@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.gpt",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-tts@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.tts",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-deepseek@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.deepseek",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-kimi@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.kimi",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-kimi-2@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.kimi-2",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-kimi-3@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.kimi-3",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-kimi-4@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.kimi-4",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-deepseek-flash-1@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.deepseek-flash-1",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-deepseek-flash-2@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.deepseek-flash-2",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-deepseek-flash-3@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.deepseek-flash-3",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-qwen38@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.qwen38",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-qwen38-chat@vector-marketplace",
host: "root@91.98.147.172",
envPath: "/root/agents-marketplace/supplier/.env.qwen38-chat",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-local@inference-proxy",
host: "inference-proxy",
envPath: "/root/agents-marketplace/supplier/.env.local",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "supplier-openclaw@inference-proxy",
host: "inference-proxy",
envPath: "/root/agents-marketplace/supplier/.env.openclaw",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "buyer@open-webui",
host: "open-webui",
envPath: "/root/marketplace-buyer-mainnet/.env",
keyVar: "BUYER_PRIV_KEY_HEX",
role: "buyer",
},
{
label: "supplier@open-webui",
host: "open-webui",
// open-webui's mainnet supplier uses 'supplier.env' (no leading dot) —
// a `find -name '.env*'` sweep silently misses it.
envPath: "/root/marketplace-mainnet-supplier/supplier.env",
keyVar: "SUPPLIER_PRIV_KEY_HEX",
role: "supplier",
},
{
label: "buyer-a@vector-modules-simulation",
host: "user@178.105.3.133",
envPath: "/home/user/marketplace-agents/buyer-a.env",
keyVar: "BUYER_PRIV_KEY_HEX",
role: "buyer",
},
{
label: "buyer-b@vector-modules-simulation",
host: "user@178.105.3.133",
envPath: "/home/user/marketplace-agents/buyer-b.env",
keyVar: "BUYER_PRIV_KEY_HEX",
role: "buyer",
},
];
// ── argv ──────────────────────────────────────────────────────────────
interface Args {
json: boolean;
buyerMin: bigint;
supplierMin: bigint;
}
function parseArgs(argv: string[]): Args {
let json = false;
let buyerMin = 10n * 1_000_000n;
let supplierMin = 3n * 1_000_000n;
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === "--") continue;
if (a === "--json") json = true;
else if (a === "--buyer-min") buyerMin = ap3xToLovelace(argv[++i]);
else if (a === "--supplier-min") supplierMin = ap3xToLovelace(argv[++i]);
else if (a === "-h" || a === "--help") {
console.log(
"usage: monitor-wallets [--json] [--buyer-min AP3X] [--supplier-min AP3X]",
);
process.exit(0);
} else {
console.error(`unknown arg: ${a}`);
process.exit(2);
}
}
return { json, buyerMin, supplierMin };
}
function ap3xToLovelace(s: string | undefined): bigint {
if (s === undefined) throw new Error("expected AP3X value");
const n = Number(s);
if (!Number.isFinite(n) || n < 0) throw new Error(`invalid AP3X: ${s}`);
return BigInt(Math.round(n * 1_000_000));
}
// ── wallet derivation (mirrors buyer/src/index.ts:deriveWalletKey) ───
function hexToBytes(hex: string): Uint8Array {
const clean = hex.replace(/^0x/, "");
if (clean.length % 2 !== 0) throw new Error(`odd-length hex: ${clean.length}`);
const bytes = new Uint8Array(clean.length / 2);
for (let i = 0; i < clean.length; i += 2) {
bytes[i / 2] = parseInt(clean.substring(i, i + 2), 16);
}
return bytes;
}
function bytesToHex(b: Uint8Array): string {
let out = "";
for (let i = 0; i < b.length; i++) out += b[i].toString(16).padStart(2, "0");
return out;
}
function deriveAddressFromPrivKey(
privHex: string,
networkId: 0 | 1,
): { address: string; pkhHex: string } {
const priv = hexToBytes(privHex);
const pub = ed.getPublicKey(priv);
const pkh = blake2b(pub, { dkLen: 28 });
const pkhHex = bytesToHex(pkh);
return { address: pkhToAddress(pkhHex, networkId), pkhHex };
}
function pkhToAddress(pkhHex: string, networkId: 0 | 1): string {
const pkh = hexToBytes(pkhHex);
if (pkh.length !== 28)
throw new Error(`pkh must be 28 bytes, got ${pkh.length}`);
const header = networkId === 0 ? 0x60 : 0x61;
const payload = new Uint8Array(29);
payload[0] = header;
payload.set(pkh, 1);
const hrp = networkId === 0 ? "addr_test" : "addr";
return bech32.encode(hrp, bech32.toWords(payload), 1023);
}
// ── ssh fetcher ──────────────────────────────────────────────────────
function fetchPrivKeyOverSsh(src: OperatorSource): string {
const remoteCmd = `grep -E '^${src.keyVar}=' ${src.envPath} | head -n 1`;
const out = execFileSync(
"ssh",
["-o", "BatchMode=yes", "-o", "ConnectTimeout=10", src.host, remoteCmd],
{ encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] },
);
const line = out.trim();
if (!line)
throw new Error(`${src.keyVar} not found in ${src.host}:${src.envPath}`);
const eq = line.indexOf("=");
if (eq === -1) throw new Error(`malformed env line at ${src.host}:${src.envPath}`);
let value = line.slice(eq + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
if (!/^[0-9a-fA-F]+$/.test(value) || value.length < 32) {
throw new Error(
`${src.keyVar} at ${src.host}:${src.envPath} doesn't look like hex`,
);
}
return value;
}
// ── indexer ──────────────────────────────────────────────────────────
interface IndexerSupplier {
supplier_pkh: string;
endpoint_url?: string;
model?: string;
status?: string;
advert_status?: string;
}
async function fetchOnChainSuppliers(): Promise<IndexerSupplier[]> {
const res = await fetch(`${INDEXER_URL}/suppliers`, {
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) throw new Error(`indexer /suppliers HTTP ${res.status}`);
const j = (await res.json()) as IndexerSupplier[];
if (!Array.isArray(j)) throw new Error(`indexer /suppliers: expected array`);
return j;
}
// ── main ──────────────────────────────────────────────────────────────
type Kind = "operator" | "third-party-supplier";
interface Row {
kind: Kind;
label: string;
address: string;
pkhHex: string;
role?: "buyer" | "supplier";
lovelace: bigint;
minLovelace: bigint | null;
note: string;
error: string | null;
}
async function main(): Promise<void> {
const args = parseArgs(process.argv.slice(2));
const provider = new ReadOnlyOgmiosProvider({ ogmiosUrl: MAINNET_OGMIOS });
const operatorRows: Row[] = [];
const operatorPkhs = new Map<string, OperatorSource>();
for (const src of OPERATOR_SOURCES) {
const min = src.role === "buyer" ? args.buyerMin : args.supplierMin;
try {
const hex = fetchPrivKeyOverSsh(src);
const { address, pkhHex } = deriveAddressFromPrivKey(hex, NETWORK_ID);
operatorPkhs.set(pkhHex, src);
operatorRows.push({
kind: "operator",
label: src.label,
address,
pkhHex,
role: src.role,
lovelace: 0n,
minLovelace: min,
note: "",
error: null,
});
} catch (e) {
operatorRows.push({
kind: "operator",
label: src.label,
address: "",
pkhHex: "",
role: src.role,
lovelace: 0n,
minLovelace: min,
note: "",
error: e instanceof Error ? e.message : String(e),
});
}
}
const thirdPartyRows: Row[] = [];
let suppliers: IndexerSupplier[] = [];
let indexerError: string | null = null;
try {
suppliers = await fetchOnChainSuppliers();
} catch (e) {
indexerError = e instanceof Error ? e.message : String(e);
}
for (const s of suppliers) {
if (operatorPkhs.has(s.supplier_pkh)) continue;
let address = "";
let note = s.endpoint_url ? `endpoint=${s.endpoint_url}` : "";
try {
address = pkhToAddress(s.supplier_pkh, NETWORK_ID);
} catch (e) {
note = `[skip] ${e instanceof Error ? e.message : String(e)}`;
}
thirdPartyRows.push({
kind: "third-party-supplier",
label: `supplier-${s.supplier_pkh.slice(0, 8)}`,
address,
pkhHex: s.supplier_pkh,
lovelace: 0n,
minLovelace: null,
note,
error: null,
});
}
const allRows = [...operatorRows, ...thirdPartyRows];
await Promise.all(
allRows.map(async (row) => {
if (row.error || !row.address) return;
try {
const utxos = await provider.queryUtxosByAddress(row.address);
row.lovelace = utxos.reduce((a, u) => a + BigInt(u.lovelace), 0n);
} catch (e) {
row.error = e instanceof Error ? e.message : String(e);
}
}),
);
for (const row of allRows) {
if (row.kind !== "operator" || row.error || row.minLovelace === null) continue;
if (row.lovelace < row.minLovelace) {
row.note = row.lovelace === 0n ? "LOW (empty)" : "LOW";
}
}
const cmpAsc = (a: Row, b: Row): number => {
if (a.lovelace === b.lovelace) return 0;
return a.lovelace < b.lovelace ? -1 : 1;
};
const opSorted = operatorRows.slice().sort(cmpAsc);
const tpSorted = thirdPartyRows.slice().sort(cmpAsc);
const sortedRows = [...opSorted, ...tpSorted];
if (args.json) {
const payload = sortedRows.map((r) => ({
kind: r.kind,
label: r.label,
role: r.role ?? null,
address: r.address,
pkh: r.pkhHex,
ap3x: Number(r.lovelace) / 1e6,
lovelace: r.lovelace.toString(),
min_ap3x: r.minLovelace === null ? null : Number(r.minLovelace) / 1e6,
note: r.note,
error: r.error,
}));
console.log(
JSON.stringify({ rows: payload, indexer_error: indexerError }, null, 2),
);
} else {
printTable(sortedRows, indexerError);
}
const anyOperatorIssue = operatorRows.some(
(r) =>
r.error !== null ||
(r.minLovelace !== null && r.lovelace < r.minLovelace),
);
if (anyOperatorIssue) process.exitCode = 1;
}
function fmtAp3x(lovelace: bigint): string {
return (Number(lovelace) / 1e6).toFixed(3);
}
function padL(s: string, n: number): string {
return s.length >= n ? s : s + " ".repeat(n - s.length);
}
function padR(s: string, n: number): string {
return s.length >= n ? s : " ".repeat(n - s.length) + s;
}
function printTable(rows: Row[], indexerError: string | null): void {
const headers = ["kind", "label", "AP3X", "min", "address", "note"];
const cells: string[][] = [headers];
for (const r of rows) {
const ap3x = r.error ? "—" : fmtAp3x(r.lovelace);
const min = r.minLovelace === null ? "—" : fmtAp3x(r.minLovelace);
const addr = r.address || "—";
const note = r.error ? `ERROR: ${r.error}` : r.note;
cells.push([
r.kind === "operator" ? "operator" : "3rd-party",
r.label,
ap3x,
min,
addr,
note,
]);
}
const widths = headers.map((_, c) =>
Math.max(...cells.map((r) => r[c]?.length ?? 0)),
);
for (const r of cells) {
const line = r
.map((cell, c) => (c === 2 || c === 3 ? padR(cell, widths[c]) : padL(cell, widths[c])))
.join(" ");
console.log(line);
}
const opCount = rows.filter((r) => r.kind === "operator").length;
const opLow = rows.filter(
(r) =>
r.kind === "operator" &&
!r.error &&
r.minLovelace !== null &&
r.lovelace < r.minLovelace,
).length;
const opErr = rows.filter((r) => r.kind === "operator" && r.error).length;
const tpCount = rows.filter((r) => r.kind === "third-party-supplier").length;
console.log(
`\ntotal: ${opCount} operator wallets (${opLow} LOW, ${opErr} ERROR), ${tpCount} third-party suppliers`,
);
if (indexerError) console.log(`\nindexer error: ${indexerError}`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});