@forum-labs/payfetch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +467 -0
  3. package/dist/bin/payfetch-mcp.d.ts +2 -0
  4. package/dist/bin/payfetch-mcp.js +9 -0
  5. package/dist/bin/payfetch.d.ts +2 -0
  6. package/dist/bin/payfetch.js +126 -0
  7. package/dist/src/config.d.ts +35 -0
  8. package/dist/src/config.js +170 -0
  9. package/dist/src/core/budget.d.ts +62 -0
  10. package/dist/src/core/budget.js +236 -0
  11. package/dist/src/core/constants.d.ts +66 -0
  12. package/dist/src/core/constants.js +115 -0
  13. package/dist/src/core/fs.d.ts +14 -0
  14. package/dist/src/core/fs.js +104 -0
  15. package/dist/src/core/integrity.d.ts +23 -0
  16. package/dist/src/core/integrity.js +150 -0
  17. package/dist/src/core/ledger.d.ts +149 -0
  18. package/dist/src/core/ledger.js +357 -0
  19. package/dist/src/core/notes.d.ts +22 -0
  20. package/dist/src/core/notes.js +24 -0
  21. package/dist/src/core/pipeline.d.ts +143 -0
  22. package/dist/src/core/pipeline.js +795 -0
  23. package/dist/src/core/policy.d.ts +57 -0
  24. package/dist/src/core/policy.js +224 -0
  25. package/dist/src/core/transport.d.ts +93 -0
  26. package/dist/src/core/transport.js +364 -0
  27. package/dist/src/guards/index.d.ts +4 -0
  28. package/dist/src/guards/index.js +3 -0
  29. package/dist/src/guards/internal.d.ts +17 -0
  30. package/dist/src/guards/internal.js +74 -0
  31. package/dist/src/guards/safety.d.ts +2 -0
  32. package/dist/src/guards/safety.js +94 -0
  33. package/dist/src/guards/trust.d.ts +2 -0
  34. package/dist/src/guards/trust.js +79 -0
  35. package/dist/src/guards/types.d.ts +59 -0
  36. package/dist/src/guards/types.js +20 -0
  37. package/dist/src/index.d.ts +58 -0
  38. package/dist/src/index.js +151 -0
  39. package/dist/src/mcp/server.d.ts +6 -0
  40. package/dist/src/mcp/server.js +125 -0
  41. package/dist/src/mcp/tools.d.ts +46 -0
  42. package/dist/src/mcp/tools.js +321 -0
  43. package/dist/src/payer/mpp.d.ts +7 -0
  44. package/dist/src/payer/mpp.js +13 -0
  45. package/dist/src/payer/parse402.d.ts +6 -0
  46. package/dist/src/payer/parse402.js +169 -0
  47. package/dist/src/payer/signer_cdp.d.ts +14 -0
  48. package/dist/src/payer/signer_cdp.js +64 -0
  49. package/dist/src/payer/signer_local.d.ts +8 -0
  50. package/dist/src/payer/signer_local.js +14 -0
  51. package/dist/src/payer/types.d.ts +99 -0
  52. package/dist/src/payer/types.js +10 -0
  53. package/dist/src/payer/x402.d.ts +23 -0
  54. package/dist/src/payer/x402.js +165 -0
  55. package/dist/src/report/report.d.ts +162 -0
  56. package/dist/src/report/report.js +220 -0
  57. package/mcpb/manifest.json +104 -0
  58. package/package.json +72 -0
@@ -0,0 +1,170 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { homedir as osHomedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { NONCE_BYTES } from "./core/constants.js";
6
+ import { CdpServerWalletSigner } from "./payer/signer_cdp.js";
7
+ import { LocalKeySigner } from "./payer/signer_local.js";
8
+ export const ENV = {
9
+ PRIVATE_KEY: "PAYFETCH_PRIVATE_KEY",
10
+ KEY_FILE: "PAYFETCH_KEY_FILE",
11
+ CDP_API_KEY_ID: "PAYFETCH_CDP_API_KEY_ID",
12
+ CDP_API_KEY_SECRET: "PAYFETCH_CDP_API_KEY_SECRET",
13
+ CDP_WALLET_SECRET: "PAYFETCH_CDP_WALLET_SECRET",
14
+ CDP_ACCOUNT_NAME: "PAYFETCH_CDP_ACCOUNT_NAME",
15
+ DATA_DIR: "PAYFETCH_DATA_DIR",
16
+ TEST_MODE: "PAYFETCH_TEST_MODE",
17
+ APPROVER: "PAYFETCH_APPROVER",
18
+ VIA: "PAYFETCH_VIA",
19
+ };
20
+ export const DEFAULT_DATA_DIR_NAME = ".payfetch";
21
+ export const APPROVER_ENABLED_VALUE = "1";
22
+ const GROUP_WORLD_PERMS_MASK = 0o077;
23
+ const MIN_SCRUB_SECRET_LEN = 6;
24
+ export function realConfigIo() {
25
+ return {
26
+ readText: (path) => readFileSync(path, "utf8"),
27
+ statMode: (path) => {
28
+ try {
29
+ return statSync(path).mode;
30
+ }
31
+ catch (err) {
32
+ if (err.code === "ENOENT")
33
+ return null;
34
+ throw err;
35
+ }
36
+ },
37
+ homedir: () => osHomedir(),
38
+ fetch: (...args) => fetch(...args),
39
+ now: () => Date.now(),
40
+ random: () => Uint8Array.from(randomBytes(NONCE_BYTES)),
41
+ log: (msg, fields) => {
42
+ process.stderr.write(`${JSON.stringify({ ts: Date.now(), msg, ...(fields ?? {}) })}\n`);
43
+ },
44
+ };
45
+ }
46
+ export class ConfigError extends Error {
47
+ constructor(message) {
48
+ super(message);
49
+ this.name = "ConfigError";
50
+ }
51
+ }
52
+ export function scrubSecrets(text, secrets) {
53
+ let out = text;
54
+ for (const s of secrets) {
55
+ if (s && s.length >= MIN_SCRUB_SECRET_LEN)
56
+ out = out.split(s).join("[redacted]");
57
+ }
58
+ out = out.replace(/0x[0-9a-fA-F]{16,}/g, "0x[redacted]");
59
+ out = out.replace(/\b[0-9a-fA-F]{32,}\b/g, "[redacted]");
60
+ out = out.replace(/\b[A-Za-z0-9_-]{40,}\b/g, "[redacted]");
61
+ return out;
62
+ }
63
+ function nonEmpty(v) {
64
+ return typeof v === "string" && v.trim().length > 0;
65
+ }
66
+ const NO_SIGNER_MSG = "payfetch: no wallet signer configured. Set EXACTLY ONE of: " +
67
+ "(1) PAYFETCH_PRIVATE_KEY (0x-hex private key), " +
68
+ "(2) PAYFETCH_KEY_FILE (path to a mode-600 key file), or " +
69
+ "(3) PAYFETCH_CDP_API_KEY_ID + PAYFETCH_CDP_API_KEY_SECRET + PAYFETCH_CDP_WALLET_SECRET " +
70
+ "(Coinbase CDP server wallet). (SPEC §12)";
71
+ export function selectSignerSource(env) {
72
+ const hasPk = nonEmpty(env[ENV.PRIVATE_KEY]);
73
+ const hasKf = nonEmpty(env[ENV.KEY_FILE]);
74
+ const cdpParts = [
75
+ [ENV.CDP_API_KEY_ID, nonEmpty(env[ENV.CDP_API_KEY_ID])],
76
+ [ENV.CDP_API_KEY_SECRET, nonEmpty(env[ENV.CDP_API_KEY_SECRET])],
77
+ [ENV.CDP_WALLET_SECRET, nonEmpty(env[ENV.CDP_WALLET_SECRET])],
78
+ ];
79
+ const cdpAny = cdpParts.some(([, present]) => present);
80
+ const cdpAll = cdpParts.every(([, present]) => present);
81
+ const present = [];
82
+ if (hasPk)
83
+ present.push("private_key");
84
+ if (hasKf)
85
+ present.push("key_file");
86
+ if (cdpAny)
87
+ present.push("cdp");
88
+ if (present.length === 0)
89
+ throw new ConfigError(NO_SIGNER_MSG);
90
+ if (present.length > 1) {
91
+ throw new ConfigError(`payfetch: multiple wallet signer sources set (${present.join(", ")}). ` +
92
+ "Set EXACTLY ONE — payfetch never guesses which wallet to spend from. " +
93
+ "Options: PAYFETCH_PRIVATE_KEY | PAYFETCH_KEY_FILE | PAYFETCH_CDP_* . (SPEC §12)");
94
+ }
95
+ if (present[0] === "cdp" && !cdpAll) {
96
+ const missing = cdpParts.filter(([, p]) => !p).map(([name]) => name);
97
+ throw new ConfigError(`payfetch: CDP signer selected but incomplete — missing ${missing.join(", ")}. ` +
98
+ "All of PAYFETCH_CDP_API_KEY_ID, PAYFETCH_CDP_API_KEY_SECRET, " +
99
+ "PAYFETCH_CDP_WALLET_SECRET are required. (SPEC §12)");
100
+ }
101
+ return present[0];
102
+ }
103
+ function newLocalSigner(key, secrets) {
104
+ try {
105
+ return new LocalKeySigner(key);
106
+ }
107
+ catch (err) {
108
+ const scrubbed = scrubSecrets(`${err.message}`, secrets);
109
+ throw new ConfigError(`payfetch: invalid private key — ${scrubbed} (SPEC §12)`);
110
+ }
111
+ }
112
+ function buildSigner(source, env, io) {
113
+ if (source === "private_key") {
114
+ const key = env[ENV.PRIVATE_KEY].trim();
115
+ return newLocalSigner(key, [key]);
116
+ }
117
+ if (source === "key_file") {
118
+ const path = env[ENV.KEY_FILE];
119
+ const mode = io.statMode(path);
120
+ if (mode === null) {
121
+ throw new ConfigError(`payfetch: PAYFETCH_KEY_FILE not found at "${path}". (SPEC §12)`);
122
+ }
123
+ if ((mode & GROUP_WORLD_PERMS_MASK) !== 0) {
124
+ throw new ConfigError(`payfetch: refusing to start — PAYFETCH_KEY_FILE "${path}" is group/world-readable ` +
125
+ `(mode ${(mode & 0o777).toString(8).padStart(3, "0")}). ` +
126
+ "Run: chmod 600 <file>. (SPEC §12)");
127
+ }
128
+ let contents;
129
+ try {
130
+ contents = io.readText(path);
131
+ }
132
+ catch (err) {
133
+ throw new ConfigError(`payfetch: could not read PAYFETCH_KEY_FILE "${path}": ${err.message}. (SPEC §12)`);
134
+ }
135
+ const key = contents.trim();
136
+ return newLocalSigner(key, [key, contents]);
137
+ }
138
+ return new CdpServerWalletSigner({
139
+ apiKeyId: env[ENV.CDP_API_KEY_ID],
140
+ apiKeySecret: env[ENV.CDP_API_KEY_SECRET],
141
+ walletSecret: env[ENV.CDP_WALLET_SECRET],
142
+ accountName: nonEmpty(env[ENV.CDP_ACCOUNT_NAME]) ? env[ENV.CDP_ACCOUNT_NAME].trim() : undefined,
143
+ });
144
+ }
145
+ export function resolveDataDir(env, homedir = osHomedir) {
146
+ const d = env[ENV.DATA_DIR];
147
+ return nonEmpty(d) ? d.trim() : join(homedir(), DEFAULT_DATA_DIR_NAME);
148
+ }
149
+ export function buildFromEnv(env, io = realConfigIo()) {
150
+ const source = selectSignerSource(env);
151
+ const signer = buildSigner(source, env, io);
152
+ const dataDir = resolveDataDir(env, io.homedir);
153
+ const viaRaw = env[ENV.VIA];
154
+ const via = nonEmpty(viaRaw) ? viaRaw.trim() : null;
155
+ const deps = {
156
+ fetch: io.fetch,
157
+ signer,
158
+ now: io.now,
159
+ random: io.random,
160
+ dataDir,
161
+ log: io.log,
162
+ elicit: null,
163
+ };
164
+ return {
165
+ deps,
166
+ testMode: env[ENV.TEST_MODE] != null,
167
+ approver: env[ENV.APPROVER] === APPROVER_ENABLED_VALUE,
168
+ via,
169
+ };
170
+ }
@@ -0,0 +1,62 @@
1
+ import { Ledger, type ApprovalGrant, type Hold, type State, type StrikeClass } from "./ledger.js";
2
+ export type ReserveCaps = {
3
+ dailyUsd: number;
4
+ perHostDailyUsd: number;
5
+ totalUsd: number | null;
6
+ };
7
+ export type ExtraCap = {
8
+ key: string;
9
+ cap: number;
10
+ which: string;
11
+ };
12
+ export type ReserveRequest = {
13
+ holdId: string;
14
+ amountUsd: number;
15
+ host: string;
16
+ caps: ReserveCaps;
17
+ extra?: ExtraCap[];
18
+ };
19
+ export type ReserveResult = {
20
+ ok: true;
21
+ holdId: string;
22
+ } | {
23
+ ok: false;
24
+ which: string;
25
+ };
26
+ export type RemainingBudgets = {
27
+ dayRemainingUsd: number;
28
+ hostRemainingUsd: number;
29
+ totalRemainingUsd: number | null;
30
+ };
31
+ export declare class Budget {
32
+ #private;
33
+ constructor(state: State, ledger: Ledger, now: () => number);
34
+ get state(): State;
35
+ private persist;
36
+ private spentMicro;
37
+ private heldMicro;
38
+ private usedMicro;
39
+ remaining(caps: ReserveCaps, host: string): RemainingBudgets;
40
+ sweepExpired(): string[];
41
+ reserve(req: ReserveRequest): ReserveResult;
42
+ setHoldValidBefore(holdId: string, validBeforeTs: number): void;
43
+ settle(holdId: string, actualUsd: number): void;
44
+ release(holdId: string): void;
45
+ getHold(holdId: string): Hold | undefined;
46
+ isAutoDenied(host: string): boolean;
47
+ recordStrike(host: string, cls: StrikeClass): {
48
+ strikeCount: number;
49
+ engaged: boolean;
50
+ };
51
+ clearAutoDeny(host: string): boolean;
52
+ autoDeniedHosts(): {
53
+ host: string;
54
+ untilTs: number;
55
+ }[];
56
+ findGrant(host: string, amountUsd: number): ApprovalGrant | null;
57
+ consumeGrant(approvalId: string): void;
58
+ addPendingApproval(entry: Omit<ApprovalGrant, "status">): ApprovalGrant;
59
+ listApprovals(): ApprovalGrant[];
60
+ resolvePending(approvalId: string, approve: boolean): ApprovalGrant | null;
61
+ private pruneExpiredApprovals;
62
+ }
@@ -0,0 +1,236 @@
1
+ import { APPROVAL_QUEUE_TTL_S, HOLD_RELEASE_MARGIN_S, PAYMENT_VALIDITY_MAX_S, } from "./constants.js";
2
+ import { TOTAL_KEY, applyStrike, dayKey, hostDayKey, isHostAutoDenied, utcDate, } from "./ledger.js";
3
+ function micro(usd) {
4
+ return Math.round(usd * 1_000_000);
5
+ }
6
+ export class Budget {
7
+ #state;
8
+ #ledger;
9
+ #now;
10
+ constructor(state, ledger, now) {
11
+ this.#state = state;
12
+ this.#ledger = ledger;
13
+ this.#now = now;
14
+ }
15
+ get state() {
16
+ return this.#state;
17
+ }
18
+ persist() {
19
+ this.#ledger.saveState(this.#state);
20
+ }
21
+ spentMicro(key) {
22
+ return micro(this.#state.counters[key] ?? 0);
23
+ }
24
+ heldMicro(key) {
25
+ let sum = 0;
26
+ for (const h of this.#state.holds) {
27
+ if (h.counterKeys.includes(key))
28
+ sum += micro(h.amountUsd);
29
+ }
30
+ return sum;
31
+ }
32
+ usedMicro(key) {
33
+ return this.spentMicro(key) + this.heldMicro(key);
34
+ }
35
+ remaining(caps, host) {
36
+ const d = utcDate(this.#now());
37
+ const dayRem = (micro(caps.dailyUsd) - this.usedMicro(dayKey(d))) / 1_000_000;
38
+ const hostRem = (micro(caps.perHostDailyUsd) - this.usedMicro(hostDayKey(host, d))) / 1_000_000;
39
+ const totalRem = caps.totalUsd == null
40
+ ? null
41
+ : (micro(caps.totalUsd) - this.usedMicro(TOTAL_KEY)) / 1_000_000;
42
+ return {
43
+ dayRemainingUsd: dayRem,
44
+ hostRemainingUsd: hostRem,
45
+ totalRemainingUsd: totalRem,
46
+ };
47
+ }
48
+ sweepExpired() {
49
+ const now = this.#now();
50
+ const swept = [];
51
+ const kept = [];
52
+ for (const h of this.#state.holds) {
53
+ const expireAtMs = (h.validBeforeTs + HOLD_RELEASE_MARGIN_S) * 1000;
54
+ if (now < expireAtMs) {
55
+ kept.push(h);
56
+ continue;
57
+ }
58
+ swept.push(h.holdId);
59
+ if (h.signed !== false) {
60
+ for (const k of h.counterKeys) {
61
+ this.#state.counters[k] = (this.#state.counters[k] ?? 0) + h.amountUsd;
62
+ }
63
+ this.#ledger.appendAdjust({
64
+ schema: "p3f.adjust.v1",
65
+ receiptId: h.holdId,
66
+ ts: now,
67
+ kind: "hold_settled_expiry",
68
+ detail: { amountUsd: h.amountUsd, host: h.host, validBeforeTs: h.validBeforeTs },
69
+ });
70
+ }
71
+ else {
72
+ this.#ledger.appendAdjust({
73
+ schema: "p3f.adjust.v1",
74
+ receiptId: h.holdId,
75
+ ts: now,
76
+ kind: "hold_released_expiry",
77
+ detail: { amountUsd: h.amountUsd, host: h.host, validBeforeTs: h.validBeforeTs },
78
+ });
79
+ }
80
+ }
81
+ if (swept.length > 0) {
82
+ this.#state.holds = kept;
83
+ this.persist();
84
+ }
85
+ return swept;
86
+ }
87
+ reserve(req) {
88
+ const now = this.#now();
89
+ const d = utcDate(now);
90
+ const amt = micro(req.amountUsd);
91
+ const checks = [
92
+ { key: dayKey(d), capMicro: micro(req.caps.dailyUsd), which: "day" },
93
+ { key: hostDayKey(req.host, d), capMicro: micro(req.caps.perHostDailyUsd), which: "host" },
94
+ ];
95
+ if (req.caps.totalUsd != null) {
96
+ checks.push({ key: TOTAL_KEY, capMicro: micro(req.caps.totalUsd), which: "total" });
97
+ }
98
+ for (const e of req.extra ?? []) {
99
+ checks.push({ key: e.key, capMicro: micro(e.cap), which: e.which });
100
+ }
101
+ for (const c of checks) {
102
+ if (this.usedMicro(c.key) + amt > c.capMicro) {
103
+ return { ok: false, which: c.which };
104
+ }
105
+ }
106
+ const counterKeys = [
107
+ dayKey(d),
108
+ hostDayKey(req.host, d),
109
+ TOTAL_KEY,
110
+ ...(req.extra ?? []).map((e) => e.key),
111
+ ];
112
+ this.#state.holds.push({
113
+ holdId: req.holdId,
114
+ amountUsd: req.amountUsd,
115
+ host: req.host,
116
+ validBeforeTs: Math.floor(now / 1000) + PAYMENT_VALIDITY_MAX_S,
117
+ createdTs: now,
118
+ counterKeys,
119
+ signed: false,
120
+ });
121
+ this.persist();
122
+ return { ok: true, holdId: req.holdId };
123
+ }
124
+ setHoldValidBefore(holdId, validBeforeTs) {
125
+ const h = this.#state.holds.find((x) => x.holdId === holdId);
126
+ if (h) {
127
+ h.validBeforeTs = validBeforeTs;
128
+ h.signed = true;
129
+ this.persist();
130
+ }
131
+ }
132
+ settle(holdId, actualUsd) {
133
+ const h = this.#state.holds.find((x) => x.holdId === holdId);
134
+ if (!h)
135
+ return;
136
+ for (const k of h.counterKeys) {
137
+ this.#state.counters[k] = (this.#state.counters[k] ?? 0) + actualUsd;
138
+ }
139
+ this.#state.holds = this.#state.holds.filter((x) => x.holdId !== holdId);
140
+ this.persist();
141
+ }
142
+ release(holdId) {
143
+ const before = this.#state.holds.length;
144
+ this.#state.holds = this.#state.holds.filter((x) => x.holdId !== holdId);
145
+ if (this.#state.holds.length !== before)
146
+ this.persist();
147
+ }
148
+ getHold(holdId) {
149
+ return this.#state.holds.find((x) => x.holdId === holdId);
150
+ }
151
+ isAutoDenied(host) {
152
+ return isHostAutoDenied(this.#state.autoDeny, host, this.#now());
153
+ }
154
+ recordStrike(host, cls) {
155
+ const res = applyStrike(this.#state.autoDeny, host, this.#now(), cls);
156
+ if (res.engaged) {
157
+ this.#ledger.appendAdjust({
158
+ schema: "p3f.adjust.v1",
159
+ receiptId: host,
160
+ ts: this.#now(),
161
+ kind: "autodeny_set",
162
+ detail: {
163
+ host,
164
+ strikeCount: res.strikeCount,
165
+ deniedUntilTs: this.#state.autoDeny[host]?.deniedUntilTs ?? null,
166
+ },
167
+ });
168
+ }
169
+ this.persist();
170
+ return res;
171
+ }
172
+ clearAutoDeny(host) {
173
+ if (!(host in this.#state.autoDeny))
174
+ return false;
175
+ delete this.#state.autoDeny[host];
176
+ this.#ledger.appendAdjust({
177
+ schema: "p3f.adjust.v1",
178
+ receiptId: host,
179
+ ts: this.#now(),
180
+ kind: "autodeny_cleared",
181
+ detail: { host },
182
+ });
183
+ this.persist();
184
+ return true;
185
+ }
186
+ autoDeniedHosts() {
187
+ const now = this.#now();
188
+ const out = [];
189
+ for (const [host, e] of Object.entries(this.#state.autoDeny)) {
190
+ if (e.deniedUntilTs != null && now < e.deniedUntilTs) {
191
+ out.push({ host, untilTs: e.deniedUntilTs });
192
+ }
193
+ }
194
+ return out;
195
+ }
196
+ findGrant(host, amountUsd) {
197
+ this.pruneExpiredApprovals();
198
+ const amt = micro(amountUsd);
199
+ return (this.#state.approvals.find((g) => g.status === "granted" && g.host === host && micro(g.amountUsd) === amt) ?? null);
200
+ }
201
+ consumeGrant(approvalId) {
202
+ this.#state.approvals = this.#state.approvals.filter((g) => g.approvalId !== approvalId);
203
+ this.persist();
204
+ }
205
+ addPendingApproval(entry) {
206
+ const grant = { ...entry, status: "pending" };
207
+ this.#state.approvals.push(grant);
208
+ this.persist();
209
+ return grant;
210
+ }
211
+ listApprovals() {
212
+ this.pruneExpiredApprovals();
213
+ return [...this.#state.approvals];
214
+ }
215
+ resolvePending(approvalId, approve) {
216
+ this.pruneExpiredApprovals();
217
+ const g = this.#state.approvals.find((x) => x.approvalId === approvalId);
218
+ if (!g)
219
+ return null;
220
+ if (approve) {
221
+ g.status = "granted";
222
+ }
223
+ else {
224
+ this.#state.approvals = this.#state.approvals.filter((x) => x.approvalId !== approvalId);
225
+ }
226
+ this.persist();
227
+ return g;
228
+ }
229
+ pruneExpiredApprovals() {
230
+ const cutoff = this.#now() - APPROVAL_QUEUE_TTL_S * 1000;
231
+ const before = this.#state.approvals.length;
232
+ this.#state.approvals = this.#state.approvals.filter((g) => g.createdTs >= cutoff);
233
+ if (this.#state.approvals.length !== before)
234
+ this.persist();
235
+ }
236
+ }
@@ -0,0 +1,66 @@
1
+ export declare const CLIENT_VERSION = "p3f-1.0.0";
2
+ export declare const POLICY_VERSION = "p3f-policy-1.5.0";
3
+ export declare const PER_CALL_CAP_DEFAULT_USD = 1;
4
+ export declare const DAILY_CAP_DEFAULT_USD = 2;
5
+ export declare const PER_HOST_DAILY_CAP_DEFAULT_USD = 1;
6
+ export declare const TOTAL_CAP_DEFAULT_USD: number | null;
7
+ export declare const APPROVAL_THRESHOLD_DEFAULT_USD = 0.1;
8
+ export declare const APPROVAL_PREAPPROVED_UP_TO_DEFAULT_USD: number | null;
9
+ export declare const APPROVAL_ELICIT_TIMEOUT_S = 120;
10
+ export declare const APPROVAL_QUEUE_TTL_S = 3600;
11
+ export declare const MAX_PAYMENT_ATTEMPTS_PER_REQUEST = 1;
12
+ export declare const CLOCK_SKEW_S = 600;
13
+ export declare const PAYMENT_VALIDITY_DEFAULT_S = 300;
14
+ export declare const PAYMENT_VALIDITY_MAX_S = 600;
15
+ export declare const HOLD_RELEASE_MARGIN_S = 60;
16
+ export declare const AUTO_DENY_STRIKES = 2;
17
+ export declare const AUTO_DENY_UNKNOWN_ONLY_STRIKES = 4;
18
+ export declare const AUTO_DENY_WINDOW_DAYS = 7;
19
+ export declare const AUTO_DENY_TTL_DAYS = 7;
20
+ export declare const GUARD_SCREEN_BUDGET_MS = 13000;
21
+ export declare const GUARD_ADVISORY_BUDGET_MS = 2000;
22
+ export declare function guardBudgetMs(mode: "advisory" | "enforce"): number;
23
+ export declare const GUARD_RECURSION_DEPTH = 0;
24
+ export declare const GUARD_DAILY_BUDGET_DEFAULT_USD = 0;
25
+ export declare const GUARD_SEND_QUERY = false;
26
+ export declare const TRUST_BLOCK_VERDICTS_DEFAULT: readonly string[];
27
+ export declare const SAFETY_BLOCK_VERDICTS_DEFAULT: readonly string[];
28
+ export declare const SAFETY_BLOCK_DEPLOYER_VERDICTS_DEFAULT: readonly string[];
29
+ export declare const SAFETY_GUARD_DEPTH_DEFAULT: "basic";
30
+ export declare const SAFETY_ON_DEGRADED_DEFAULT: "block";
31
+ export declare const RAILS_ENABLED: readonly ["x402"];
32
+ export declare const SUPPORTED_SCHEMES: readonly ["exact"];
33
+ export declare const SUPPORTED_NETWORKS: readonly ["base", "base-sepolia"];
34
+ export type SupportedNetwork = (typeof SUPPORTED_NETWORKS)[number];
35
+ export declare const SUPPORTED_X402_VERSIONS: readonly number[];
36
+ export declare function isSupportedX402Version(v: number | null | undefined): boolean;
37
+ export declare const NETWORK_ALIASES: Readonly<Record<string, SupportedNetwork>>;
38
+ export declare function canonicalNetwork(declared: string | null | undefined): string | null;
39
+ export declare function chainIdForNetwork(canonical: string | null | undefined): number | null;
40
+ export declare const FETCH_TIMEOUT_MS = 30000;
41
+ export declare const MAX_REDIRECTS = 2;
42
+ export declare const RESPONSE_MAX_BYTES = 10485760;
43
+ export declare const RESPONSE_INLINE_MAX_BYTES = 102400;
44
+ export declare const LOCK_STALE_S = 300;
45
+ export declare const SCAFFOLD_BASE_URL = "https://api.forum-labs.com";
46
+ export declare const P2_TRUST_BASE_URL: string;
47
+ export declare const P1_SAFETY_BASE_URL: string;
48
+ export declare const INTEGRATION_HEADER = "X-P2-Integration";
49
+ export declare const NONCE_BYTES = 32;
50
+ export declare const X_PAYMENT_HEADER = "X-PAYMENT";
51
+ export declare const PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
52
+ export declare const PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
53
+ export declare const X_PAYMENT_RESPONSE_HEADER = "X-PAYMENT-RESPONSE";
54
+ export declare const DEFAULT_CDP_ACCOUNT_NAME = "payfetch";
55
+ export declare const USDC_DECIMALS = 6;
56
+ export type KnownAsset = {
57
+ readonly address: string;
58
+ readonly decimals: number;
59
+ readonly network: SupportedNetwork;
60
+ readonly label: string;
61
+ };
62
+ export declare const KNOWN_ASSETS: readonly KnownAsset[];
63
+ export declare function isKnownAsset(asset: string | null | undefined): boolean;
64
+ export declare function knownAssetDecimals(asset: string | null | undefined): number | null;
65
+ export declare function knownAssetNetwork(asset: string | null | undefined): SupportedNetwork | null;
66
+ export declare function deriveAmountUsd(amountAtomic: string | null | undefined, asset: string | null | undefined): number | null;
@@ -0,0 +1,115 @@
1
+ export const CLIENT_VERSION = "p3f-1.0.0";
2
+ export const POLICY_VERSION = "p3f-policy-1.5.0";
3
+ export const PER_CALL_CAP_DEFAULT_USD = 1.0;
4
+ export const DAILY_CAP_DEFAULT_USD = 2.0;
5
+ export const PER_HOST_DAILY_CAP_DEFAULT_USD = 1.0;
6
+ export const TOTAL_CAP_DEFAULT_USD = null;
7
+ export const APPROVAL_THRESHOLD_DEFAULT_USD = 0.1;
8
+ export const APPROVAL_PREAPPROVED_UP_TO_DEFAULT_USD = null;
9
+ export const APPROVAL_ELICIT_TIMEOUT_S = 120;
10
+ export const APPROVAL_QUEUE_TTL_S = 3_600;
11
+ export const MAX_PAYMENT_ATTEMPTS_PER_REQUEST = 1;
12
+ export const CLOCK_SKEW_S = 600;
13
+ export const PAYMENT_VALIDITY_DEFAULT_S = 300;
14
+ export const PAYMENT_VALIDITY_MAX_S = 600;
15
+ export const HOLD_RELEASE_MARGIN_S = 60;
16
+ export const AUTO_DENY_STRIKES = 2;
17
+ export const AUTO_DENY_UNKNOWN_ONLY_STRIKES = 4;
18
+ export const AUTO_DENY_WINDOW_DAYS = 7;
19
+ export const AUTO_DENY_TTL_DAYS = 7;
20
+ export const GUARD_SCREEN_BUDGET_MS = 13_000;
21
+ export const GUARD_ADVISORY_BUDGET_MS = 2_000;
22
+ export function guardBudgetMs(mode) {
23
+ return mode === "enforce" ? GUARD_SCREEN_BUDGET_MS : GUARD_ADVISORY_BUDGET_MS;
24
+ }
25
+ export const GUARD_RECURSION_DEPTH = 0;
26
+ export const GUARD_DAILY_BUDGET_DEFAULT_USD = 0;
27
+ export const GUARD_SEND_QUERY = false;
28
+ export const TRUST_BLOCK_VERDICTS_DEFAULT = Object.freeze(["unreliable"]);
29
+ export const SAFETY_BLOCK_VERDICTS_DEFAULT = Object.freeze(["danger"]);
30
+ export const SAFETY_BLOCK_DEPLOYER_VERDICTS_DEFAULT = Object.freeze([
31
+ "serial_rugger",
32
+ ]);
33
+ export const SAFETY_GUARD_DEPTH_DEFAULT = "basic";
34
+ export const SAFETY_ON_DEGRADED_DEFAULT = "block";
35
+ export const RAILS_ENABLED = Object.freeze(["x402"]);
36
+ export const SUPPORTED_SCHEMES = Object.freeze(["exact"]);
37
+ export const SUPPORTED_NETWORKS = Object.freeze([
38
+ "base",
39
+ "base-sepolia",
40
+ ]);
41
+ export const SUPPORTED_X402_VERSIONS = Object.freeze([1, 2]);
42
+ export function isSupportedX402Version(v) {
43
+ return v != null && SUPPORTED_X402_VERSIONS.includes(v);
44
+ }
45
+ export const NETWORK_ALIASES = Object.freeze({
46
+ "eip155:8453": "base",
47
+ "eip155:84532": "base-sepolia",
48
+ });
49
+ const CHAIN_ID_BY_CANONICAL = Object.freeze({
50
+ base: 8453,
51
+ "base-sepolia": 84532,
52
+ });
53
+ export function canonicalNetwork(declared) {
54
+ if (declared == null)
55
+ return null;
56
+ return NETWORK_ALIASES[declared] ?? declared;
57
+ }
58
+ export function chainIdForNetwork(canonical) {
59
+ if (canonical == null)
60
+ return null;
61
+ return CHAIN_ID_BY_CANONICAL[canonical] ?? null;
62
+ }
63
+ export const FETCH_TIMEOUT_MS = 30_000;
64
+ export const MAX_REDIRECTS = 2;
65
+ export const RESPONSE_MAX_BYTES = 10_485_760;
66
+ export const RESPONSE_INLINE_MAX_BYTES = 102_400;
67
+ export const LOCK_STALE_S = 300;
68
+ export const SCAFFOLD_BASE_URL = "https://api.forum-labs.com";
69
+ export const P2_TRUST_BASE_URL = SCAFFOLD_BASE_URL;
70
+ export const P1_SAFETY_BASE_URL = SCAFFOLD_BASE_URL;
71
+ export const INTEGRATION_HEADER = "X-P2-Integration";
72
+ export const NONCE_BYTES = 32;
73
+ export const X_PAYMENT_HEADER = "X-PAYMENT";
74
+ export const PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
75
+ export const PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
76
+ export const X_PAYMENT_RESPONSE_HEADER = "X-PAYMENT-RESPONSE";
77
+ export const DEFAULT_CDP_ACCOUNT_NAME = "payfetch";
78
+ export const USDC_DECIMALS = 6;
79
+ export const KNOWN_ASSETS = Object.freeze([
80
+ Object.freeze({
81
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
82
+ decimals: USDC_DECIMALS,
83
+ network: "base",
84
+ label: "USDC (Base)",
85
+ }),
86
+ Object.freeze({
87
+ address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
88
+ decimals: USDC_DECIMALS,
89
+ network: "base-sepolia",
90
+ label: "USDC (Base Sepolia)",
91
+ }),
92
+ ]);
93
+ const KNOWN_ASSET_DECIMALS_BY_LOWER = new Map(KNOWN_ASSETS.map((a) => [a.address.toLowerCase(), a.decimals]));
94
+ const KNOWN_ASSET_NETWORK_BY_LOWER = new Map(KNOWN_ASSETS.map((a) => [a.address.toLowerCase(), a.network]));
95
+ export function isKnownAsset(asset) {
96
+ return asset != null && KNOWN_ASSET_DECIMALS_BY_LOWER.has(asset.toLowerCase());
97
+ }
98
+ export function knownAssetDecimals(asset) {
99
+ if (asset == null)
100
+ return null;
101
+ return KNOWN_ASSET_DECIMALS_BY_LOWER.get(asset.toLowerCase()) ?? null;
102
+ }
103
+ export function knownAssetNetwork(asset) {
104
+ if (asset == null)
105
+ return null;
106
+ return KNOWN_ASSET_NETWORK_BY_LOWER.get(asset.toLowerCase()) ?? null;
107
+ }
108
+ export function deriveAmountUsd(amountAtomic, asset) {
109
+ if (amountAtomic == null || !/^\d+$/.test(amountAtomic))
110
+ return null;
111
+ const decimals = knownAssetDecimals(asset);
112
+ if (decimals == null)
113
+ return null;
114
+ return Number(amountAtomic) / 10 ** decimals;
115
+ }
@@ -0,0 +1,14 @@
1
+ export interface PayfetchFs {
2
+ ensureDir(dir: string): void;
3
+ readText(path: string): string | null;
4
+ writeText(path: string, data: string): void;
5
+ writeBytes(path: string, data: Uint8Array): void;
6
+ appendLine(path: string, line: string, opts: {
7
+ fsync: boolean;
8
+ }): void;
9
+ statMtimeMs(path: string): number | null;
10
+ listDir(dir: string): string[];
11
+ tryCreateExclusive(path: string, contents: string, mode?: number): boolean;
12
+ remove(path: string): void;
13
+ }
14
+ export declare const realFs: PayfetchFs;