@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 +46 -2
- package/dist/amounts.d.ts +2 -0
- package/dist/amounts.js +41 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/load.d.ts +3 -0
- package/dist/load.js +10 -0
- package/dist/schema.d.ts +49 -0
- package/dist/schema.js +50 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +1 -0
- package/package.json +54 -4
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @paystage/policy
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/amounts.js
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/load.d.ts
ADDED
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
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -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
|
+
});
|
package/dist/types.d.ts
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|