thebuyside-x402-agent 0.2.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.
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +148 -0
- package/dist/chains/adapter.d.ts +33 -0
- package/dist/chains/adapter.d.ts.map +1 -0
- package/dist/chains/adapter.js +15 -0
- package/dist/chains/adapter.js.map +1 -0
- package/dist/chains/base-usdc.d.ts +22 -0
- package/dist/chains/base-usdc.d.ts.map +1 -0
- package/dist/chains/base-usdc.js +60 -0
- package/dist/chains/base-usdc.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +32 -0
- package/dist/config.js.map +1 -0
- package/dist/gateway.d.ts +26 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +28 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +14 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +24 -0
- package/dist/log.js.map +1 -0
- package/dist/policy/allowlist.d.ts +36 -0
- package/dist/policy/allowlist.d.ts.map +1 -0
- package/dist/policy/allowlist.js +61 -0
- package/dist/policy/allowlist.js.map +1 -0
- package/dist/policy/caps.d.ts +34 -0
- package/dist/policy/caps.d.ts.map +1 -0
- package/dist/policy/caps.js +67 -0
- package/dist/policy/caps.js.map +1 -0
- package/dist/policy/confirm.d.ts +57 -0
- package/dist/policy/confirm.d.ts.map +1 -0
- package/dist/policy/confirm.js +131 -0
- package/dist/policy/confirm.js.map +1 -0
- package/dist/policy/format.d.ts +15 -0
- package/dist/policy/format.d.ts.map +1 -0
- package/dist/policy/format.js +40 -0
- package/dist/policy/format.js.map +1 -0
- package/dist/policy/receipts.d.ts +33 -0
- package/dist/policy/receipts.d.ts.map +1 -0
- package/dist/policy/receipts.js +78 -0
- package/dist/policy/receipts.js.map +1 -0
- package/dist/registry/lookup.d.ts +28 -0
- package/dist/registry/lookup.d.ts.map +1 -0
- package/dist/registry/lookup.js +92 -0
- package/dist/registry/lookup.js.map +1 -0
- package/dist/registry/seed.candidates.json +54 -0
- package/dist/registry/seed.json +54 -0
- package/dist/registry/types.d.ts +45 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +11 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +43 -0
- package/dist/server.js.map +1 -0
- package/dist/signer/env-key.d.ts +18 -0
- package/dist/signer/env-key.d.ts.map +1 -0
- package/dist/signer/env-key.js +24 -0
- package/dist/signer/env-key.js.map +1 -0
- package/dist/signer/signer.d.ts +32 -0
- package/dist/signer/signer.d.ts.map +1 -0
- package/dist/signer/signer.js +14 -0
- package/dist/signer/signer.js.map +1 -0
- package/dist/tools/discover.d.ts +11 -0
- package/dist/tools/discover.d.ts.map +1 -0
- package/dist/tools/discover.js +58 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/fetch.d.ts +17 -0
- package/dist/tools/fetch.d.ts.map +1 -0
- package/dist/tools/fetch.js +115 -0
- package/dist/tools/fetch.js.map +1 -0
- package/dist/tools/wallet_status.d.ts +10 -0
- package/dist/tools/wallet_status.d.ts.map +1 -0
- package/dist/tools/wallet_status.js +37 -0
- package/dist/tools/wallet_status.js.map +1 -0
- package/dist/x402/client.d.ts +85 -0
- package/dist/x402/client.d.ts.map +1 -0
- package/dist/x402/client.js +242 -0
- package/dist/x402/client.js.map +1 -0
- package/dist/x402/types.d.ts +60 -0
- package/dist/x402/types.d.ts.map +1 -0
- package/dist/x402/types.js +13 -0
- package/dist/x402/types.js.map +1 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* thebuyside-x402-agent — MCP gateway for x402-priced APIs.
|
|
4
|
+
* Entry point. See src/server.ts for the actual server.
|
|
5
|
+
*/
|
|
6
|
+
import { logger } from './log.js';
|
|
7
|
+
import { startServer } from './server.js';
|
|
8
|
+
startServer().catch((err) => {
|
|
9
|
+
logger.error('fatal startup error', { err: String(err) });
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACnC,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stderr-only structured logger.
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: MCP servers communicate over stdio — stdout is reserved for the
|
|
5
|
+
* JSON-RPC wire protocol. Any stray write to stdout will corrupt the stream
|
|
6
|
+
* and confuse the client. All logs MUST go to stderr.
|
|
7
|
+
*/
|
|
8
|
+
export declare const logger: {
|
|
9
|
+
info: (msg: string, fields?: Record<string, unknown>) => void;
|
|
10
|
+
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
11
|
+
error: (msg: string, fields?: Record<string, unknown>) => void;
|
|
12
|
+
debug: (msg: string, fields?: Record<string, unknown>) => void;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,eAAO,MAAM,MAAM;gBACL,MAAM,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBACxC,MAAM,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;iBACvC,MAAM,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;iBACxC,MAAM,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CACtD,CAAC"}
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stderr-only structured logger.
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: MCP servers communicate over stdio — stdout is reserved for the
|
|
5
|
+
* JSON-RPC wire protocol. Any stray write to stdout will corrupt the stream
|
|
6
|
+
* and confuse the client. All logs MUST go to stderr.
|
|
7
|
+
*/
|
|
8
|
+
function emit(level, msg, fields) {
|
|
9
|
+
const ts = new Date().toISOString();
|
|
10
|
+
const tail = fields
|
|
11
|
+
? ' ' +
|
|
12
|
+
Object.entries(fields)
|
|
13
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
14
|
+
.join(' ')
|
|
15
|
+
: '';
|
|
16
|
+
process.stderr.write(`[${ts}] ${level.toUpperCase().padEnd(5)} ${msg}${tail}\n`);
|
|
17
|
+
}
|
|
18
|
+
export const logger = {
|
|
19
|
+
info: (msg, fields) => emit('info', msg, fields),
|
|
20
|
+
warn: (msg, fields) => emit('warn', msg, fields),
|
|
21
|
+
error: (msg, fields) => emit('error', msg, fields),
|
|
22
|
+
debug: (msg, fields) => emit('debug', msg, fields),
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=log.js.map
|
package/dist/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,SAAS,IAAI,CAAC,KAAY,EAAE,GAAW,EAAE,MAAgC;IACvE,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM;QACjB,CAAC,CAAC,GAAG;YACH,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;iBACnB,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5C,IAAI,CAAC,GAAG,CAAC;QACd,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,CAAC,GAAW,EAAE,MAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC;IAClF,IAAI,EAAE,CAAC,GAAW,EAAE,MAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC;IAClF,KAAK,EAAE,CAAC,GAAW,EAAE,MAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC;IACpF,KAAK,EAAE,CAAC,GAAW,EAAE,MAAgC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC;CACrF,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host allowlist — the gateway only pays hosts in this set, unless the
|
|
3
|
+
* caller explicitly overrides with X402_ALLOW_UNVERIFIED=1.
|
|
4
|
+
*
|
|
5
|
+
* The default allowlist is derived from the registry (`src/registry/seed.json`)
|
|
6
|
+
* so it auto-tracks the curated set of x402-priced APIs we ship with.
|
|
7
|
+
*
|
|
8
|
+
* Override via env:
|
|
9
|
+
* X402_ALLOWLIST=news-ep.com,api.example.com (comma-separated hostnames)
|
|
10
|
+
* X402_ALLOW_UNVERIFIED=1 (bypass — for dev/testing)
|
|
11
|
+
*/
|
|
12
|
+
export type AllowDecision = {
|
|
13
|
+
ok: true;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
reason: string;
|
|
17
|
+
};
|
|
18
|
+
export declare class Allowlist {
|
|
19
|
+
private readonly hosts;
|
|
20
|
+
private readonly allowAny;
|
|
21
|
+
constructor(opts?: {
|
|
22
|
+
hosts?: string[];
|
|
23
|
+
allowAny?: boolean;
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Build an Allowlist from environment variables, with `defaultHosts`
|
|
27
|
+
* (typically the registry's host list) used when X402_ALLOWLIST is unset.
|
|
28
|
+
*/
|
|
29
|
+
static fromEnv(opts?: {
|
|
30
|
+
defaultHosts?: string[];
|
|
31
|
+
}): Allowlist;
|
|
32
|
+
check(url: string): AllowDecision;
|
|
33
|
+
get allowedHosts(): string[];
|
|
34
|
+
get isUnrestricted(): boolean;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../src/policy/allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;gBAEvB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAO;IAM/D;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;KAAO,GAAG,SAAS;IAUjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAsBjC,IAAI,YAAY,IAAI,MAAM,EAAE,CAE3B;IAED,IAAI,cAAc,IAAI,OAAO,CAE5B;CACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host allowlist — the gateway only pays hosts in this set, unless the
|
|
3
|
+
* caller explicitly overrides with X402_ALLOW_UNVERIFIED=1.
|
|
4
|
+
*
|
|
5
|
+
* The default allowlist is derived from the registry (`src/registry/seed.json`)
|
|
6
|
+
* so it auto-tracks the curated set of x402-priced APIs we ship with.
|
|
7
|
+
*
|
|
8
|
+
* Override via env:
|
|
9
|
+
* X402_ALLOWLIST=news-ep.com,api.example.com (comma-separated hostnames)
|
|
10
|
+
* X402_ALLOW_UNVERIFIED=1 (bypass — for dev/testing)
|
|
11
|
+
*/
|
|
12
|
+
const FALLBACK_HOSTS = ['news-ep.com'];
|
|
13
|
+
export class Allowlist {
|
|
14
|
+
hosts;
|
|
15
|
+
allowAny;
|
|
16
|
+
constructor(opts = {}) {
|
|
17
|
+
const hosts = opts.hosts && opts.hosts.length > 0 ? opts.hosts : FALLBACK_HOSTS;
|
|
18
|
+
this.hosts = new Set(hosts);
|
|
19
|
+
this.allowAny = opts.allowAny ?? false;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build an Allowlist from environment variables, with `defaultHosts`
|
|
23
|
+
* (typically the registry's host list) used when X402_ALLOWLIST is unset.
|
|
24
|
+
*/
|
|
25
|
+
static fromEnv(opts = {}) {
|
|
26
|
+
const fromEnv = process.env.X402_ALLOWLIST?.split(',')
|
|
27
|
+
.map((s) => s.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
return new Allowlist({
|
|
30
|
+
hosts: fromEnv && fromEnv.length > 0 ? fromEnv : opts.defaultHosts,
|
|
31
|
+
allowAny: process.env.X402_ALLOW_UNVERIFIED === '1',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
check(url) {
|
|
35
|
+
if (this.allowAny)
|
|
36
|
+
return { ok: true };
|
|
37
|
+
let host;
|
|
38
|
+
try {
|
|
39
|
+
host = new URL(url).hostname;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { ok: false, reason: `invalid URL: ${url}` };
|
|
43
|
+
}
|
|
44
|
+
if (this.hosts.has(host))
|
|
45
|
+
return { ok: true };
|
|
46
|
+
const sorted = [...this.hosts].sort();
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: `host "${host}" is not in the allowlist ` +
|
|
50
|
+
`(allowed: ${sorted.length > 0 ? sorted.join(', ') : '(none)'}). ` +
|
|
51
|
+
`Set X402_ALLOWLIST=<hosts> to permit, or X402_ALLOW_UNVERIFIED=1 to bypass.`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
get allowedHosts() {
|
|
55
|
+
return [...this.hosts].sort();
|
|
56
|
+
}
|
|
57
|
+
get isUnrestricted() {
|
|
58
|
+
return this.allowAny;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../src/policy/allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,cAAc,GAAG,CAAC,aAAa,CAAC,CAAC;AAIvC,MAAM,OAAO,SAAS;IACH,KAAK,CAAc;IACnB,QAAQ,CAAU;IAEnC,YAAY,OAAiD,EAAE;QAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,CAAC;QAChF,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,OAAoC,EAAE;QACnD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,GAAG,CAAC;aACnD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC,CAAC;QACnB,OAAO,IAAI,SAAS,CAAC;YACnB,KAAK,EAAE,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY;YAClE,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,GAAG;SACpD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QAEvC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,GAAG,EAAE,EAAE,CAAC;QACtD,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QAE9C,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EACJ,SAAS,IAAI,4BAA4B;gBACzC,aAAa,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK;gBAClE,6EAA6E;SAChF,CAAC;IACJ,CAAC;IAED,IAAI,YAAY;QACd,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend caps — per-call and sliding-24-hour daily limits.
|
|
3
|
+
*
|
|
4
|
+
* Both limits are atomic USDC (`bigint`, 6 decimals). Defaults are deliberately
|
|
5
|
+
* conservative for v0 ("safe out-of-the-box"). Override via env:
|
|
6
|
+
* X402_PER_CALL_LIMIT=0.05 (USDC, decimal) or 50000 (atomic units)
|
|
7
|
+
* X402_DAILY_LIMIT=1.00 ditto
|
|
8
|
+
*/
|
|
9
|
+
import type { Receipts } from './receipts.js';
|
|
10
|
+
export type CapConfig = {
|
|
11
|
+
perCallLimitAtomic: bigint;
|
|
12
|
+
dailyLimitAtomic: bigint;
|
|
13
|
+
};
|
|
14
|
+
export declare const DEFAULT_CAPS: CapConfig;
|
|
15
|
+
export type CapDecision = {
|
|
16
|
+
ok: true;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
reason: string;
|
|
20
|
+
spentSoFarAtomic: bigint;
|
|
21
|
+
};
|
|
22
|
+
export declare class CapPolicy {
|
|
23
|
+
readonly config: CapConfig;
|
|
24
|
+
private readonly receipts;
|
|
25
|
+
constructor(config: CapConfig, receipts: Receipts);
|
|
26
|
+
static fromEnv(receipts: Receipts): CapPolicy;
|
|
27
|
+
/**
|
|
28
|
+
* Check whether a payment of `amountAtomic` is allowed under both caps.
|
|
29
|
+
* Returns `{ ok: true }` if allowed, otherwise an explanatory reason.
|
|
30
|
+
*/
|
|
31
|
+
check(amountAtomic: bigint): Promise<CapDecision>;
|
|
32
|
+
spentTodayAtomic(): Promise<bigint>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=caps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caps.d.ts","sourceRoot":"","sources":["../../src/policy/caps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,SAG1B,CAAC;AAEF,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,qBAAa,SAAS;aAEF,MAAM,EAAE,SAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADT,MAAM,EAAE,SAAS,EAChB,QAAQ,EAAE,QAAQ;IAGrC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS;IAc7C;;;OAGG;IACG,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA+BjD,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC;CAG1C"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spend caps — per-call and sliding-24-hour daily limits.
|
|
3
|
+
*
|
|
4
|
+
* Both limits are atomic USDC (`bigint`, 6 decimals). Defaults are deliberately
|
|
5
|
+
* conservative for v0 ("safe out-of-the-box"). Override via env:
|
|
6
|
+
* X402_PER_CALL_LIMIT=0.05 (USDC, decimal) or 50000 (atomic units)
|
|
7
|
+
* X402_DAILY_LIMIT=1.00 ditto
|
|
8
|
+
*/
|
|
9
|
+
import { formatUsdcAtomic, parseUsdcLimit } from './format.js';
|
|
10
|
+
export const DEFAULT_CAPS = {
|
|
11
|
+
perCallLimitAtomic: 50000n, // $0.05
|
|
12
|
+
dailyLimitAtomic: 1000000n, // $1.00
|
|
13
|
+
};
|
|
14
|
+
export class CapPolicy {
|
|
15
|
+
config;
|
|
16
|
+
receipts;
|
|
17
|
+
constructor(config, receipts) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.receipts = receipts;
|
|
20
|
+
}
|
|
21
|
+
static fromEnv(receipts) {
|
|
22
|
+
return new CapPolicy({
|
|
23
|
+
perCallLimitAtomic: process.env.X402_PER_CALL_LIMIT
|
|
24
|
+
? parseUsdcLimit(process.env.X402_PER_CALL_LIMIT)
|
|
25
|
+
: DEFAULT_CAPS.perCallLimitAtomic,
|
|
26
|
+
dailyLimitAtomic: process.env.X402_DAILY_LIMIT
|
|
27
|
+
? parseUsdcLimit(process.env.X402_DAILY_LIMIT)
|
|
28
|
+
: DEFAULT_CAPS.dailyLimitAtomic,
|
|
29
|
+
}, receipts);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check whether a payment of `amountAtomic` is allowed under both caps.
|
|
33
|
+
* Returns `{ ok: true }` if allowed, otherwise an explanatory reason.
|
|
34
|
+
*/
|
|
35
|
+
async check(amountAtomic) {
|
|
36
|
+
if (amountAtomic <= 0n) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
reason: `payment amount must be positive, got ${amountAtomic}`,
|
|
40
|
+
spentSoFarAtomic: 0n,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (amountAtomic > this.config.perCallLimitAtomic) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason: `payment of $${formatUsdcAtomic(amountAtomic)} exceeds per-call cap of ` +
|
|
47
|
+
`$${formatUsdcAtomic(this.config.perCallLimitAtomic)} (set X402_PER_CALL_LIMIT to raise)`,
|
|
48
|
+
spentSoFarAtomic: 0n,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const spent = await this.receipts.spentSinceHours(24);
|
|
52
|
+
if (spent + amountAtomic > this.config.dailyLimitAtomic) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
reason: `payment of $${formatUsdcAtomic(amountAtomic)} would exceed daily cap ` +
|
|
56
|
+
`(already spent $${formatUsdcAtomic(spent)} of $${formatUsdcAtomic(this.config.dailyLimitAtomic)} ` +
|
|
57
|
+
`in the last 24h; set X402_DAILY_LIMIT to raise)`,
|
|
58
|
+
spentSoFarAtomic: spent,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { ok: true };
|
|
62
|
+
}
|
|
63
|
+
async spentTodayAtomic() {
|
|
64
|
+
return this.receipts.spentSinceHours(24);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=caps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caps.js","sourceRoot":"","sources":["../../src/policy/caps.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQ/D,MAAM,CAAC,MAAM,YAAY,GAAc;IACrC,kBAAkB,EAAE,MAAO,EAAE,QAAQ;IACrC,gBAAgB,EAAE,QAAU,EAAE,QAAQ;CACvC,CAAC;AAMF,MAAM,OAAO,SAAS;IAEF;IACC;IAFnB,YACkB,MAAiB,EAChB,QAAkB;QADnB,WAAM,GAAN,MAAM,CAAW;QAChB,aAAQ,GAAR,QAAQ,CAAU;IAClC,CAAC;IAEJ,MAAM,CAAC,OAAO,CAAC,QAAkB;QAC/B,OAAO,IAAI,SAAS,CAClB;YACE,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;gBACjD,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;gBACjD,CAAC,CAAC,YAAY,CAAC,kBAAkB;YACnC,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB;gBAC5C,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;gBAC9C,CAAC,CAAC,YAAY,CAAC,gBAAgB;SAClC,EACD,QAAQ,CACT,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CAAC,YAAoB;QAC9B,IAAI,YAAY,IAAI,EAAE,EAAE,CAAC;YACvB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,wCAAwC,YAAY,EAAE;gBAC9D,gBAAgB,EAAE,EAAE;aACrB,CAAC;QACJ,CAAC;QACD,IAAI,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAClD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EACJ,eAAe,gBAAgB,CAAC,YAAY,CAAC,2BAA2B;oBACxE,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,qCAAqC;gBAC3F,gBAAgB,EAAE,EAAE;aACrB,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QACtD,IAAI,KAAK,GAAG,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACxD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EACJ,eAAe,gBAAgB,CAAC,YAAY,CAAC,0BAA0B;oBACvE,mBAAmB,gBAAgB,CAAC,KAAK,CAAC,QAAQ,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG;oBACnG,iDAAiD;gBACnD,gBAAgB,EAAE,KAAK;aACxB,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirm policy — asks the human to approve a payment via MCP elicitation
|
|
3
|
+
* before the gateway signs it.
|
|
4
|
+
*
|
|
5
|
+
* Three modes via `X402_REQUIRE_CONFIRM`:
|
|
6
|
+
* - `always` (default): every payment, regardless of size
|
|
7
|
+
* - `never`: skip elicitation entirely; caps are the only gate
|
|
8
|
+
* - a USDC amount (e.g. `0.01`): confirm only at or above that amount
|
|
9
|
+
*
|
|
10
|
+
* Fallback when the client doesn't declare elicitation capability:
|
|
11
|
+
* - default: log a one-time warning and proceed (caps still apply)
|
|
12
|
+
* - `X402_CONFIRM_STRICT=1`: refuse the payment instead
|
|
13
|
+
*/
|
|
14
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
|
+
export type ConfirmMode = {
|
|
16
|
+
type: 'always';
|
|
17
|
+
} | {
|
|
18
|
+
type: 'never';
|
|
19
|
+
} | {
|
|
20
|
+
type: 'threshold';
|
|
21
|
+
minAtomic: bigint;
|
|
22
|
+
};
|
|
23
|
+
export type ConfirmConfig = {
|
|
24
|
+
mode: ConfirmMode;
|
|
25
|
+
strict: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare const DEFAULT_CONFIRM_MODE: ConfirmMode;
|
|
28
|
+
export type ConfirmDecision = {
|
|
29
|
+
ok: true;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
reason: string;
|
|
33
|
+
};
|
|
34
|
+
export type ConfirmAskArgs = {
|
|
35
|
+
amountAtomic: bigint;
|
|
36
|
+
host: string;
|
|
37
|
+
method: string;
|
|
38
|
+
url: string;
|
|
39
|
+
todaySpentAtomic: bigint;
|
|
40
|
+
dailyCapAtomic: bigint;
|
|
41
|
+
};
|
|
42
|
+
export declare class ConfirmPolicy {
|
|
43
|
+
readonly config: ConfirmConfig;
|
|
44
|
+
private warnedNoElicitation;
|
|
45
|
+
constructor(config: ConfirmConfig);
|
|
46
|
+
static fromEnv(): ConfirmPolicy;
|
|
47
|
+
/** Pure decision: would a payment of this amount need a prompt? */
|
|
48
|
+
shouldConfirm(amountAtomic: bigint): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Send an elicitation if needed. Returns `{ ok: true }` to proceed,
|
|
51
|
+
* `{ ok: false, reason }` to abort. Safe to call regardless of mode —
|
|
52
|
+
* if `shouldConfirm` is false, returns ok immediately without touching
|
|
53
|
+
* the server.
|
|
54
|
+
*/
|
|
55
|
+
ask(server: McpServer, args: ConfirmAskArgs): Promise<ConfirmDecision>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=confirm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm.d.ts","sourceRoot":"","sources":["../../src/policy/confirm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,WAAgC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,qBAAa,aAAa;aAGI,MAAM,EAAE,aAAa;IAFjD,OAAO,CAAC,mBAAmB,CAAS;gBAER,MAAM,EAAE,aAAa;IAEjD,MAAM,CAAC,OAAO,IAAI,aAAa;IAqB/B,mEAAmE;IACnE,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAW5C;;;;;OAKG;IACG,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CAyD7E"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirm policy — asks the human to approve a payment via MCP elicitation
|
|
3
|
+
* before the gateway signs it.
|
|
4
|
+
*
|
|
5
|
+
* Three modes via `X402_REQUIRE_CONFIRM`:
|
|
6
|
+
* - `always` (default): every payment, regardless of size
|
|
7
|
+
* - `never`: skip elicitation entirely; caps are the only gate
|
|
8
|
+
* - a USDC amount (e.g. `0.01`): confirm only at or above that amount
|
|
9
|
+
*
|
|
10
|
+
* Fallback when the client doesn't declare elicitation capability:
|
|
11
|
+
* - default: log a one-time warning and proceed (caps still apply)
|
|
12
|
+
* - `X402_CONFIRM_STRICT=1`: refuse the payment instead
|
|
13
|
+
*/
|
|
14
|
+
import { logger } from '../log.js';
|
|
15
|
+
import { formatUsdcAtomic, parseUsdcLimit } from './format.js';
|
|
16
|
+
export const DEFAULT_CONFIRM_MODE = { type: 'always' };
|
|
17
|
+
export class ConfirmPolicy {
|
|
18
|
+
config;
|
|
19
|
+
warnedNoElicitation = false;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
static fromEnv() {
|
|
24
|
+
const raw = process.env.X402_REQUIRE_CONFIRM?.trim().toLowerCase();
|
|
25
|
+
let mode;
|
|
26
|
+
if (raw === undefined || raw === '' || raw === 'always') {
|
|
27
|
+
mode = { type: 'always' };
|
|
28
|
+
}
|
|
29
|
+
else if (raw === 'never' || raw === 'off') {
|
|
30
|
+
mode = { type: 'never' };
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
try {
|
|
34
|
+
const minAtomic = parseUsdcLimit(raw);
|
|
35
|
+
mode = { type: 'threshold', minAtomic };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new Error(`X402_REQUIRE_CONFIRM must be 'always', 'never', or a USDC amount; got "${raw}"`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const strict = process.env.X402_CONFIRM_STRICT === '1';
|
|
42
|
+
return new ConfirmPolicy({ mode, strict });
|
|
43
|
+
}
|
|
44
|
+
/** Pure decision: would a payment of this amount need a prompt? */
|
|
45
|
+
shouldConfirm(amountAtomic) {
|
|
46
|
+
switch (this.config.mode.type) {
|
|
47
|
+
case 'never':
|
|
48
|
+
return false;
|
|
49
|
+
case 'always':
|
|
50
|
+
return true;
|
|
51
|
+
case 'threshold':
|
|
52
|
+
return amountAtomic >= this.config.mode.minAtomic;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Send an elicitation if needed. Returns `{ ok: true }` to proceed,
|
|
57
|
+
* `{ ok: false, reason }` to abort. Safe to call regardless of mode —
|
|
58
|
+
* if `shouldConfirm` is false, returns ok immediately without touching
|
|
59
|
+
* the server.
|
|
60
|
+
*/
|
|
61
|
+
async ask(server, args) {
|
|
62
|
+
if (!this.shouldConfirm(args.amountAtomic))
|
|
63
|
+
return { ok: true };
|
|
64
|
+
const caps = server.server.getClientCapabilities();
|
|
65
|
+
const elicitationSupported = caps?.elicitation !== undefined;
|
|
66
|
+
if (!elicitationSupported) {
|
|
67
|
+
if (this.config.strict) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
reason: "client doesn't support MCP elicitation but X402_CONFIRM_STRICT=1 is set",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!this.warnedNoElicitation) {
|
|
74
|
+
logger.warn('client does not support MCP elicitation; payments will proceed without per-call confirmation (caps still enforced)');
|
|
75
|
+
this.warnedNoElicitation = true;
|
|
76
|
+
}
|
|
77
|
+
return { ok: true };
|
|
78
|
+
}
|
|
79
|
+
let result;
|
|
80
|
+
try {
|
|
81
|
+
result = await server.server.elicitInput({
|
|
82
|
+
message: formatPrompt(args),
|
|
83
|
+
requestedSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
approve: {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
title: 'Approve',
|
|
89
|
+
description: `Approve $${formatUsdcAtomic(args.amountAtomic)} USDC payment to ${args.host}?`,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
required: ['approve'],
|
|
93
|
+
},
|
|
94
|
+
task: { ttl: 30_000 },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
+
logger.warn('elicitation request failed', { err: msg });
|
|
100
|
+
return { ok: false, reason: `confirmation failed: ${msg}` };
|
|
101
|
+
}
|
|
102
|
+
if (result.action === 'cancel') {
|
|
103
|
+
return { ok: false, reason: 'payment cancelled by user' };
|
|
104
|
+
}
|
|
105
|
+
if (result.action === 'decline') {
|
|
106
|
+
return { ok: false, reason: 'payment declined by user' };
|
|
107
|
+
}
|
|
108
|
+
if (result.content?.approve !== true) {
|
|
109
|
+
return { ok: false, reason: 'payment declined by user' };
|
|
110
|
+
}
|
|
111
|
+
return { ok: true };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function formatPrompt(args) {
|
|
115
|
+
const path = (() => {
|
|
116
|
+
try {
|
|
117
|
+
const u = new URL(args.url);
|
|
118
|
+
return u.pathname + u.search;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return args.url;
|
|
122
|
+
}
|
|
123
|
+
})();
|
|
124
|
+
return [
|
|
125
|
+
`Confirm payment to ${args.host}`,
|
|
126
|
+
` Amount: $${formatUsdcAtomic(args.amountAtomic)} USDC (Base mainnet)`,
|
|
127
|
+
` Resource: ${args.method} ${path}`,
|
|
128
|
+
` Today: $${formatUsdcAtomic(args.todaySpentAtomic)} of $${formatUsdcAtomic(args.dailyCapAtomic)} daily cap`,
|
|
129
|
+
].join('\n');
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=confirm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/policy/confirm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format helpers for atomic USDC amounts.
|
|
3
|
+
*
|
|
4
|
+
* USDC has 6 decimals: 1 USDC = 1,000,000 atomic units. We hold amounts as
|
|
5
|
+
* `bigint` of atomic units everywhere, and only format to dollars-and-cents
|
|
6
|
+
* strings at the I/O boundary (logs, error messages, MCP tool responses).
|
|
7
|
+
*/
|
|
8
|
+
/** Format an atomic USDC value (6 decimals) as `D.dddddd`. */
|
|
9
|
+
export declare function formatUsdcAtomic(atomic: bigint): string;
|
|
10
|
+
/**
|
|
11
|
+
* Parse a USDC limit string. Accepts either `0.05` (decimal USDC) or
|
|
12
|
+
* `50000` (atomic units). Returns `bigint` atomic units.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseUsdcLimit(raw: string): bigint;
|
|
15
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/policy/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBlD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format helpers for atomic USDC amounts.
|
|
3
|
+
*
|
|
4
|
+
* USDC has 6 decimals: 1 USDC = 1,000,000 atomic units. We hold amounts as
|
|
5
|
+
* `bigint` of atomic units everywhere, and only format to dollars-and-cents
|
|
6
|
+
* strings at the I/O boundary (logs, error messages, MCP tool responses).
|
|
7
|
+
*/
|
|
8
|
+
/** Format an atomic USDC value (6 decimals) as `D.dddddd`. */
|
|
9
|
+
export function formatUsdcAtomic(atomic) {
|
|
10
|
+
if (atomic < 0n)
|
|
11
|
+
return '-' + formatUsdcAtomic(-atomic);
|
|
12
|
+
const whole = atomic / 1000000n;
|
|
13
|
+
const frac = atomic % 1000000n;
|
|
14
|
+
return `${whole}.${frac.toString().padStart(6, '0')}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse a USDC limit string. Accepts either `0.05` (decimal USDC) or
|
|
18
|
+
* `50000` (atomic units). Returns `bigint` atomic units.
|
|
19
|
+
*/
|
|
20
|
+
export function parseUsdcLimit(raw) {
|
|
21
|
+
const trimmed = raw.trim();
|
|
22
|
+
if (trimmed.length === 0)
|
|
23
|
+
throw new Error('parseUsdcLimit: empty input');
|
|
24
|
+
if (trimmed.includes('.')) {
|
|
25
|
+
const [whole, frac = ''] = trimmed.split('.');
|
|
26
|
+
if (!/^\d+$/.test(whole) || !/^\d*$/.test(frac)) {
|
|
27
|
+
throw new Error(`parseUsdcLimit: invalid decimal "${raw}"`);
|
|
28
|
+
}
|
|
29
|
+
if (frac.length > 6) {
|
|
30
|
+
throw new Error(`parseUsdcLimit: USDC has 6 decimals, "${raw}" has more`);
|
|
31
|
+
}
|
|
32
|
+
const padded = (frac + '000000').slice(0, 6);
|
|
33
|
+
return BigInt(whole) * 1000000n + BigInt(padded);
|
|
34
|
+
}
|
|
35
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
36
|
+
throw new Error(`parseUsdcLimit: invalid integer "${raw}"`);
|
|
37
|
+
}
|
|
38
|
+
return BigInt(trimmed);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/policy/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8DAA8D;AAC9D,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,IAAI,MAAM,GAAG,EAAE;QAAE,OAAO,GAAG,GAAG,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,MAAM,GAAG,QAAU,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,GAAG,QAAU,CAAC;IACjC,OAAO,GAAG,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACzE,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,oCAAoC,GAAG,GAAG,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,YAAY,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,QAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,oCAAoC,GAAG,GAAG,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Receipts log — append-only JSON-lines record of every settled payment.
|
|
3
|
+
*
|
|
4
|
+
* One receipt per line keeps the file trivially inspectable and recoverable.
|
|
5
|
+
* No locking is needed because the gateway is a single-process MCP server.
|
|
6
|
+
*
|
|
7
|
+
* Default location: `<project>/.local/receipts.jsonl` (gitignored).
|
|
8
|
+
* Override with `X402_RECEIPTS_PATH` in the env.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_RECEIPTS_PATH: string;
|
|
11
|
+
export type Receipt = {
|
|
12
|
+
id: string;
|
|
13
|
+
ts: string;
|
|
14
|
+
host: string;
|
|
15
|
+
url: string;
|
|
16
|
+
method: string;
|
|
17
|
+
/** Atomic units of the asset, as a decimal string (e.g. "5000" = $0.005 USDC). */
|
|
18
|
+
amount_atomic: string;
|
|
19
|
+
asset: string;
|
|
20
|
+
chain: string;
|
|
21
|
+
tx_hash: string | null;
|
|
22
|
+
};
|
|
23
|
+
export type ReceiptInput = Omit<Receipt, 'id' | 'ts'>;
|
|
24
|
+
export declare class Receipts {
|
|
25
|
+
readonly filepath: string;
|
|
26
|
+
constructor(filepath?: string);
|
|
27
|
+
static fromEnv(): Receipts;
|
|
28
|
+
record(input: ReceiptInput): Promise<Receipt>;
|
|
29
|
+
list(): Promise<Receipt[]>;
|
|
30
|
+
/** Sum of `amount_atomic` for receipts in the last `hours` window. */
|
|
31
|
+
spentSinceHours(hours: number): Promise<bigint>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=receipts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipts.d.ts","sourceRoot":"","sources":["../../src/policy/receipts.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH,eAAO,MAAM,qBAAqB,QAAmD,CAAC;AAEtF,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;AAEtD,qBAAa,QAAQ;aACS,QAAQ,EAAE,MAAM;gBAAhB,QAAQ,GAAE,MAA8B;IAEpE,MAAM,CAAC,OAAO,IAAI,QAAQ;IAKpB,MAAM,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB7C,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAoBhC,sEAAsE;IAChE,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAWtD"}
|