@paystage/policy 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,47 @@
1
- # Temporary Holding Version
1
+ # @paystage/policy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shared spending-policy schema and loader for PayStage agent payment tools.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @paystage/policy
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { parsePolicy, loadPolicy, humanToAtomic } from "@paystage/policy";
15
+
16
+ // Parse an in-memory object (validates against the schema).
17
+ const policy = parsePolicy({
18
+ version: 1,
19
+ name: "standard",
20
+ allowed_networks: ["eip155:8453"],
21
+ allowed_assets: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
22
+ allowed_pay_to: ["0x209693Bc6afc0C5328bA36FaF03C514EF312287C"],
23
+ max_amount_per_call: "1.00",
24
+ max_amount_per_session: "10.00",
25
+ require_human_above: "0.50",
26
+ max_timeout_seconds: 300,
27
+ default_verdict: "review",
28
+ });
29
+
30
+ // Load and validate from disk.
31
+ const fromDisk = await loadPolicy("./policy.json");
32
+
33
+ // Convert human-readable USDC to atomic units (6 decimals).
34
+ humanToAtomic("0.50"); // "500000"
35
+ ```
36
+
37
+ ## API
38
+
39
+ - `parsePolicy(input)` — validate an object, returns a typed `Policy`.
40
+ - `loadPolicy(path)` — read + parse a JSON policy file.
41
+ - `humanToAtomic(amount)` — convert `"0.50"` → `"500000"` (USDC, 6 decimals).
42
+
43
+ ## Links
44
+
45
+ - Website: <https://paystage.xyz>
46
+ - Source: <https://github.com/base-buzz/paystage>
47
+ - License: MIT
@@ -0,0 +1,2 @@
1
+ export declare function humanToAtomic(human: string, decimals: number): bigint;
2
+ export declare function atomicToHuman(atomic: bigint, decimals: number): string;
@@ -0,0 +1,41 @@
1
+ export function humanToAtomic(human, decimals) {
2
+ if (!/^\d+(?:\.\d+)?$/.test(human)) {
3
+ throw new Error(`Invalid decimal amount: ${human}`);
4
+ }
5
+ if (!Number.isInteger(decimals) || decimals < 0) {
6
+ throw new Error("decimals must be a non-negative integer");
7
+ }
8
+ const [whole, fraction = ""] = human.split(".");
9
+ if (fraction.length > decimals) {
10
+ throw new Error(`Too many decimal places: ${human} has ${fraction.length}, expected at most ${decimals}`);
11
+ }
12
+ const wholePart = BigInt(whole);
13
+ const factor = 10n ** BigInt(decimals);
14
+ const fractionPart = fraction.length > 0 ? BigInt(fraction.padEnd(decimals, "0")) : 0n;
15
+ return wholePart * factor + fractionPart;
16
+ }
17
+ export function atomicToHuman(atomic, decimals) {
18
+ if (typeof atomic !== "bigint") {
19
+ throw new TypeError("atomic must be a bigint");
20
+ }
21
+ if (!Number.isInteger(decimals) || decimals < 0) {
22
+ throw new Error("decimals must be a non-negative integer");
23
+ }
24
+ if (atomic < 0n) {
25
+ throw new Error("atomic amount cannot be negative");
26
+ }
27
+ if (decimals === 0) {
28
+ return atomic.toString();
29
+ }
30
+ const factor = 10n ** BigInt(decimals);
31
+ const whole = atomic / factor;
32
+ const remainder = atomic % factor;
33
+ if (remainder === 0n) {
34
+ return whole.toString();
35
+ }
36
+ const fraction = remainder
37
+ .toString()
38
+ .padStart(decimals, "0")
39
+ .replace(/0+$/, "");
40
+ return `${whole}.${fraction}`;
41
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./schema.js";
3
+ export * from "./amounts.js";
4
+ export * from "./load.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./types.js";
2
+ export * from "./schema.js";
3
+ export * from "./amounts.js";
4
+ export * from "./load.js";
package/dist/load.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { Policy } from "./types.js";
2
+ export declare function parsePolicy(input: unknown): Policy;
3
+ export declare function loadPolicy(path: string): Promise<Policy>;
package/dist/load.js ADDED
@@ -0,0 +1,10 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { PolicySchema } from "./schema.js";
3
+ export function parsePolicy(input) {
4
+ return PolicySchema.parse(input);
5
+ }
6
+ export async function loadPolicy(path) {
7
+ const raw = await readFile(path, "utf8");
8
+ const parsed = JSON.parse(raw);
9
+ return parsePolicy(parsed);
10
+ }
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import type { Policy } from "./types.js";
3
+ export declare const PolicySchema: z.ZodObject<{
4
+ version: z.ZodLiteral<1>;
5
+ max_amount: z.ZodObject<{
6
+ value: z.ZodString;
7
+ asset: z.ZodString;
8
+ decimals: z.ZodNumber;
9
+ }, z.core.$strip>;
10
+ daily_cap: z.ZodOptional<z.ZodObject<{
11
+ value: z.ZodString;
12
+ asset: z.ZodString;
13
+ decimals: z.ZodNumber;
14
+ }, z.core.$strip>>;
15
+ allowed_pay_to: z.ZodArray<z.ZodString>;
16
+ allowed_assets: z.ZodArray<z.ZodObject<{
17
+ symbol: z.ZodString;
18
+ decimals: z.ZodNumber;
19
+ address: z.ZodString;
20
+ network: z.ZodString;
21
+ }, z.core.$strip>>;
22
+ allowed_networks: z.ZodArray<z.ZodString>;
23
+ max_timeout_seconds: z.ZodNumber;
24
+ require_human_above: z.ZodOptional<z.ZodObject<{
25
+ value: z.ZodString;
26
+ asset: z.ZodString;
27
+ decimals: z.ZodNumber;
28
+ }, z.core.$strip>>;
29
+ unknown_payee: z.ZodEnum<{
30
+ review: "review";
31
+ deny: "deny";
32
+ }>;
33
+ max_auth_validity_seconds: z.ZodOptional<z.ZodNumber>;
34
+ allowed_spenders: z.ZodOptional<z.ZodArray<z.ZodString>>;
35
+ disallow_unlimited_approvals: z.ZodOptional<z.ZodBoolean>;
36
+ velocity: z.ZodOptional<z.ZodObject<{
37
+ windows: z.ZodArray<z.ZodObject<{
38
+ window_seconds: z.ZodNumber;
39
+ max_count: z.ZodOptional<z.ZodNumber>;
40
+ max_amount: z.ZodOptional<z.ZodObject<{
41
+ value: z.ZodString;
42
+ decimals: z.ZodNumber;
43
+ asset: z.ZodOptional<z.ZodString>;
44
+ }, z.core.$strip>>;
45
+ }, z.core.$strip>>;
46
+ }, z.core.$strip>>;
47
+ }, z.core.$strip>;
48
+ export type PolicyFromSchema = z.infer<typeof PolicySchema>;
49
+ export type ParsedPolicy = Policy;
package/dist/schema.js ADDED
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ const addressSchema = z
3
+ .string()
4
+ .regex(/^0x[a-fA-F0-9]{40}$/, "Expected a 0x-prefixed Ethereum address");
5
+ const amountSpecSchema = z.object({
6
+ value: z
7
+ .string()
8
+ .regex(/^\d+(?:\.\d+)?$/, "Amount value must be a non-negative decimal string"),
9
+ asset: z.string().min(1),
10
+ decimals: z.number().int().min(0)
11
+ });
12
+ const assetSchema = z.object({
13
+ symbol: z.string().min(1),
14
+ decimals: z.number().int().min(0),
15
+ address: addressSchema,
16
+ network: z
17
+ .string()
18
+ .regex(/^eip155:\d+$/, "Expected a network id like eip155:8453")
19
+ });
20
+ const velocityAmountSchema = z.object({
21
+ value: z
22
+ .string()
23
+ .regex(/^\d+(?:\.\d+)?$/, "Amount value must be a non-negative decimal string"),
24
+ decimals: z.number().int().min(0),
25
+ asset: z.string().min(1).optional(),
26
+ });
27
+ const velocityWindowSchema = z.object({
28
+ window_seconds: z.number().int().positive(),
29
+ max_count: z.number().int().nonnegative().optional(),
30
+ max_amount: velocityAmountSchema.optional(),
31
+ });
32
+ export const PolicySchema = z.object({
33
+ version: z.literal(1),
34
+ max_amount: amountSpecSchema,
35
+ daily_cap: amountSpecSchema.optional(),
36
+ allowed_pay_to: z.array(addressSchema).min(1),
37
+ allowed_assets: z.array(assetSchema).min(1),
38
+ allowed_networks: z
39
+ .array(z.string().regex(/^eip155:\d+$/))
40
+ .min(1),
41
+ max_timeout_seconds: z.number().int().positive(),
42
+ require_human_above: amountSpecSchema.optional(),
43
+ unknown_payee: z.enum(["review", "deny"]),
44
+ max_auth_validity_seconds: z.number().int().positive().optional(),
45
+ allowed_spenders: z.array(addressSchema).optional(),
46
+ disallow_unlimited_approvals: z.boolean().optional(),
47
+ velocity: z
48
+ .object({ windows: z.array(velocityWindowSchema).min(1) })
49
+ .optional()
50
+ });
@@ -0,0 +1,43 @@
1
+ export type UnknownPayee = "review" | "deny";
2
+ export interface AmountSpec {
3
+ value: string;
4
+ asset: string;
5
+ decimals: number;
6
+ }
7
+ export interface Asset {
8
+ symbol: string;
9
+ decimals: number;
10
+ address: string;
11
+ network: string;
12
+ }
13
+ export interface VelocityAmountSpec {
14
+ value: string;
15
+ decimals: number;
16
+ asset?: string;
17
+ }
18
+ export interface VelocityWindow {
19
+ /** Rolling window length in seconds (e.g. 300, 1800, 18000). */
20
+ window_seconds: number;
21
+ /** Max number of transactions allowed in the window. */
22
+ max_count?: number;
23
+ /** Max aggregate amount allowed in the window. */
24
+ max_amount?: VelocityAmountSpec;
25
+ }
26
+ export interface Policy {
27
+ version: 1;
28
+ max_amount: AmountSpec;
29
+ daily_cap?: AmountSpec;
30
+ allowed_pay_to: string[];
31
+ allowed_assets: Asset[];
32
+ allowed_networks: string[];
33
+ max_timeout_seconds: number;
34
+ require_human_above?: AmountSpec;
35
+ unknown_payee: UnknownPayee;
36
+ max_auth_validity_seconds?: number;
37
+ allowed_spenders?: string[];
38
+ disallow_unlimited_approvals?: boolean;
39
+ /** Optional rolling-window rate and spend-pattern limits. */
40
+ velocity?: {
41
+ windows: VelocityWindow[];
42
+ };
43
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@paystage/policy",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Shared spending policy schema and loader for PayStage agent payment tools",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "!dist/**/*.map"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "keywords": [
23
+ "mcp",
24
+ "x402",
25
+ "agent-payments",
26
+ "usdc",
27
+ "base",
28
+ "policy"
29
+ ],
30
+ "homepage": "https://paystage.xyz",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/base-buzz/paystage.git",
34
+ "directory": "packages/policy"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/base-buzz/paystage/issues"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "test": "vitest run",
45
+ "typecheck": "tsc --noEmit",
46
+ "prepublishOnly": "npm run typecheck && npm run build"
47
+ },
48
+ "dependencies": {
49
+ "zod": "^4.6.7"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.14.0",
53
+ "typescript": "^5.5.4",
54
+ "vitest": "^4.1.12"
55
+ }
56
+ }