mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 14:57:28 +00:00
feat(agentad): AgentAd Marketplace PRD + reference exchange (M5)
Add the AgentBBS M5 "AgentAd marketplace" spec and a working reference
implementation built on the existing @logicsrc/schemas AgentAd contracts.
- docs/agentad-marketplace.md: two-sided exchange PRD (buy/sell sides,
match -> auction -> pace -> serve -> meter -> settle, CoinPay settlement,
AgentBBS as reference publisher, milestones M5.0-M5.5).
- packages/agentad (@logicsrc/agentad): reference exchange
- builders that emit schema-valid, always-disclosed ad/campaign/placement docs
- HMAC-signed, single-use impression/click tracking tokens
- AgentAdExchange: targeting/format/category matching, second-price auction,
budget pacing + daily caps, frequency capping, token-driven metering
- pluggable settlement (InMemorySettlement) that can't overspend escrow
- runtime validation against the canonical agentad-*.schema.json
- 22 vitest cases (builders, tokens, full serve/meter/settle lifecycle).
- Wire package into root build; link the PRD from README + docs/agentad.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0bca203527
commit
7d62e3b661
18 changed files with 1862 additions and 2 deletions
78
packages/agentad/src/tokens.ts
Normal file
78
packages/agentad/src/tokens.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Signed, single-use tracking tokens for impressions and clicks. The exchange
|
||||
// mints an impression_token when it serves an ad; confirming that impression
|
||||
// mints a click_token. Tokens are HMAC-signed so a publisher cannot forge a
|
||||
// billable event, and they carry the pricing context needed to settle.
|
||||
|
||||
import { createHmac, timingSafeEqual } from "node:crypto";
|
||||
|
||||
export type TokenKind = "impression" | "click";
|
||||
|
||||
export interface TokenPayload {
|
||||
/** Token kind. */
|
||||
k: TokenKind;
|
||||
/** request_id the token was minted for. */
|
||||
rid: string;
|
||||
/** placement id. */
|
||||
pid: string;
|
||||
/** ad id. */
|
||||
aid: string;
|
||||
/** campaign id. */
|
||||
cid: string;
|
||||
/** pricing model of the winning ad. */
|
||||
model: "cpm" | "cpc" | "cpa" | "flat";
|
||||
/** per-unit charge for the winner at the second-price clearing level. */
|
||||
charge: number;
|
||||
/** currency code. */
|
||||
cur: string;
|
||||
/** unix ms expiry. */
|
||||
exp: number;
|
||||
/** random nonce to keep tokens unique + single-use. */
|
||||
n: string;
|
||||
}
|
||||
|
||||
function b64url(input: Buffer | string): string {
|
||||
return Buffer.from(input).toString("base64url");
|
||||
}
|
||||
|
||||
function sign(secret: string, body: string): string {
|
||||
return createHmac("sha256", secret).update(body).digest("base64url");
|
||||
}
|
||||
|
||||
export function mintToken(secret: string, payload: TokenPayload): string {
|
||||
const body = b64url(JSON.stringify(payload));
|
||||
return `${body}.${sign(secret, body)}`;
|
||||
}
|
||||
|
||||
export interface VerifyResult {
|
||||
ok: boolean;
|
||||
reason?: "malformed" | "bad_signature" | "expired";
|
||||
payload?: TokenPayload;
|
||||
}
|
||||
|
||||
export function verifyToken(secret: string, token: string, now = Date.now()): VerifyResult {
|
||||
const dot = token.indexOf(".");
|
||||
if (dot <= 0) return { ok: false, reason: "malformed" };
|
||||
|
||||
const body = token.slice(0, dot);
|
||||
const sig = token.slice(dot + 1);
|
||||
const expected = sign(secret, body);
|
||||
|
||||
const a = Buffer.from(sig);
|
||||
const b = Buffer.from(expected);
|
||||
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
||||
return { ok: false, reason: "bad_signature" };
|
||||
}
|
||||
|
||||
let payload: TokenPayload;
|
||||
try {
|
||||
payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8")) as TokenPayload;
|
||||
} catch {
|
||||
return { ok: false, reason: "malformed" };
|
||||
}
|
||||
|
||||
if (typeof payload.exp === "number" && payload.exp < now) {
|
||||
return { ok: false, reason: "expired", payload };
|
||||
}
|
||||
|
||||
return { ok: true, payload };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue