payid 0.3.2 → 0.3.3

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/dist/index.js CHANGED
@@ -1,202 +1,41 @@
1
- // src/evaluate.ts
2
- import { executeRule } from "payid-rule-engine";
3
-
4
- // src/normalize.ts
5
- function normalizeContext(ctx) {
6
- return {
7
- ...ctx,
8
- tx: {
9
- ...ctx.tx,
10
- sender: ctx.tx.sender?.toLowerCase(),
11
- receiver: ctx.tx.receiver?.toLowerCase(),
12
- asset: ctx.tx.asset.toUpperCase()
13
- }
14
- };
15
- }
16
-
17
- // src/evaluate.ts
18
- import { preprocessContextV2 } from "payid-rule-engine";
19
- async function evaluate(wasmBinary, context, ruleConfig, options) {
20
- if (!context || typeof context !== "object") {
21
- throw new Error("evaluate(): context is required");
22
- }
23
- if (!context.tx) {
24
- throw new Error("evaluate(): context.tx is required");
25
- }
26
- if (!ruleConfig || typeof ruleConfig !== "object") {
27
- throw new Error("evaluate(): ruleConfig is required");
28
- }
29
- let result;
30
- try {
31
- const preparedContext = options?.trustedIssuers ? preprocessContextV2(context, ruleConfig, options.trustedIssuers) : context;
32
- const normalized = normalizeContext(preparedContext);
33
- result = await executeRule(wasmBinary, normalized, ruleConfig);
34
- } catch (err) {
35
- return {
36
- decision: "REJECT",
37
- code: "CONTEXT_OR_ENGINE_ERROR",
38
- reason: err?.message ?? "rule evaluation failed"
39
- };
40
- }
41
- if (result.decision !== "ALLOW" && result.decision !== "REJECT") {
42
- return {
43
- decision: "REJECT",
44
- code: "INVALID_ENGINE_OUTPUT",
45
- reason: "invalid decision value"
46
- };
47
- }
48
- return {
49
- decision: result.decision,
50
- code: result.code || "UNKNOWN",
51
- reason: result.reason
52
- };
53
- }
54
-
55
- // src/decision-proof/generate.ts
56
- import { randomBytes } from "crypto";
57
-
58
- // src/decision-proof/hash.ts
59
- import { keccak256 } from "ethers";
60
- function stableStringify(obj) {
61
- if (Array.isArray(obj)) {
62
- return `[${obj.map(stableStringify).join(",")}]`;
63
- }
64
- if (obj && typeof obj === "object") {
65
- return `{${Object.keys(obj).sort().map(
66
- (k) => `"${k}":${stableStringify(obj[k])}`
67
- ).join(",")}}`;
68
- }
69
- return JSON.stringify(obj);
70
- }
71
- function hashContext(context) {
72
- return keccak256(Buffer.from(stableStringify(context)));
73
- }
74
- function hashRuleSet(ruleConfig) {
75
- return keccak256(Buffer.from(stableStringify(ruleConfig)));
76
- }
77
-
78
- // src/decision-proof/sign.ts
79
- import "ethers";
80
- async function signDecision(signer, chainId, verifyingContract, payload) {
81
- const domain = {
82
- name: "PAY.ID Decision",
83
- version: "1",
84
- chainId,
85
- verifyingContract
86
- };
87
- const types = {
88
- Decision: [
89
- { name: "version", type: "string" },
90
- { name: "payId", type: "string" },
91
- { name: "owner", type: "address" },
92
- { name: "decision", type: "uint8" },
93
- { name: "contextHash", type: "bytes32" },
94
- { name: "ruleSetHash", type: "bytes32" },
95
- { name: "issuedAt", type: "uint64" },
96
- { name: "expiresAt", type: "uint64" },
97
- { name: "nonce", type: "bytes32" }
98
- ]
99
- };
100
- if (typeof signer.signTypedData === "function") {
101
- return await signer.signTypedData(domain, types, payload);
102
- }
103
- if (typeof signer._signTypedData === "function") {
104
- return await signer._signTypedData(domain, types, payload);
105
- }
106
- throw new Error(
107
- "Signer does not support EIP-712 signing (signTypedData)"
108
- );
109
- }
110
-
111
- // src/decision-proof/generate.ts
112
- import "ethers";
113
- async function generateDecisionProof(params) {
114
- const issuedAt = Math.floor(Date.now() / 1e3);
115
- const expiresAt = issuedAt + (params.ttlSeconds ?? 60);
116
- const payload = {
117
- version: "payid.decision.v1",
118
- payId: params.payId,
119
- owner: params.owner,
120
- decision: params.decision === "ALLOW" ? 1 : 0,
121
- contextHash: hashContext(params.context),
122
- ruleSetHash: hashRuleSet(params.ruleConfig),
123
- issuedAt,
124
- expiresAt,
125
- nonce: `0x${randomBytes(32).toString("hex")}`
126
- };
127
- const signature = await signDecision(
128
- params.signer,
129
- params.chainId,
130
- params.verifyingContract,
131
- payload
132
- );
133
- return { payload, signature };
134
- }
135
-
136
- // src/resolver/utils.ts
137
- import { keccak256 as keccak2562 } from "ethers";
138
- function verifyHash(content, expectedHash) {
139
- if (!expectedHash) return;
140
- const actual = keccak2562(Buffer.from(content));
141
- if (actual !== expectedHash) {
142
- throw new Error("RULE_HASH_MISMATCH");
143
- }
144
- }
145
-
146
- // src/resolver/http.ts
147
- async function resolveHttpRule(uri, expectedHash) {
148
- const res = await fetch(uri);
149
- if (!res.ok) {
150
- throw new Error(`HTTP_RULE_FETCH_FAILED: ${res.status}`);
151
- }
152
- const text = await res.text();
153
- verifyHash(text, expectedHash);
154
- return JSON.parse(text);
155
- }
156
-
157
- // src/resolver/ipfs.ts
158
- var DEFAULT_GATEWAY = "https://ipfs.io/ipfs/";
159
- async function resolveIpfsRule(uri, expectedHash, gateway = DEFAULT_GATEWAY) {
160
- const cid = uri.replace("ipfs://", "");
161
- const url = `${gateway}${cid}`;
162
- const res = await fetch(url);
163
- if (!res.ok) {
164
- throw new Error(`IPFS_RULE_FETCH_FAILED: ${res.status}`);
165
- }
166
- const text = await res.text();
167
- verifyHash(text, expectedHash);
168
- return JSON.parse(text);
169
- }
170
-
171
- // src/resolver/resolver.ts
172
- async function resolveRule(source) {
173
- const { uri, hash } = source;
174
- let config;
175
- if (uri.startsWith("ipfs://")) {
176
- config = await resolveIpfsRule(uri, hash);
177
- } else if (uri.startsWith("http://") || uri.startsWith("https://")) {
178
- config = await resolveHttpRule(uri, hash);
179
- } else {
180
- throw new Error("UNSUPPORTED_RULE_URI");
181
- }
182
- return {
183
- config,
184
- source
185
- };
186
- }
187
-
188
- // src/payid.ts
189
- import "ethers";
190
- import * as fs from "fs";
1
+ import {
2
+ context_exports
3
+ } from "./chunk-RCXMRX4F.js";
4
+ import {
5
+ issuer_exports
6
+ } from "./chunk-AOKLY2QN.js";
7
+ import "./chunk-7U3P7XJE.js";
8
+ import {
9
+ rule_exports
10
+ } from "./chunk-JRVCGSKK.js";
11
+ import {
12
+ sessionPolicy_exports
13
+ } from "./chunk-ATWJEWZH.js";
14
+ import {
15
+ client_exports
16
+ } from "./chunk-XWOB3JVE.js";
17
+ import "./chunk-QYH3FNQ4.js";
18
+ import "./chunk-MXKZJKXE.js";
19
+ import "./chunk-JJEWYFOV.js";
20
+ import {
21
+ server_exports
22
+ } from "./chunk-FIMJNWJ3.js";
23
+ import {
24
+ evaluate,
25
+ generateDecisionProof,
26
+ resolveRule
27
+ } from "./chunk-4MPVXPLM.js";
28
+ import "./chunk-5ZEKI5Y2.js";
29
+ import "./chunk-R5U7XKVJ.js";
191
30
 
192
31
  // src/erc4337/build.ts
193
- import { ethers as ethers3 } from "ethers";
32
+ import { ethers } from "ethers";
194
33
  function buildPayCallData(contractAddress, proof) {
195
- const iface = new ethers3.Interface([
34
+ const iface = new ethers.Interface([
196
35
  "function pay(bytes payload, bytes signature)"
197
36
  ]);
198
37
  return iface.encodeFunctionData("pay", [
199
- ethers3.toUtf8Bytes(JSON.stringify(proof.payload)),
38
+ ethers.toUtf8Bytes(JSON.stringify(proof.payload)),
200
39
  proof.signature
201
40
  ]);
202
41
  }
@@ -219,96 +58,246 @@ function buildUserOperation(params) {
219
58
  };
220
59
  }
221
60
 
222
- // src/payid.ts
61
+ // src/core/payid.ts
62
+ function isRuleSource(rule) {
63
+ return typeof rule === "object" && rule !== null && "uri" in rule;
64
+ }
223
65
  var PayID = class {
224
- wasm;
225
- constructor(wasmPath) {
226
- this.wasm = fs.readFileSync(wasmPath);
66
+ constructor(wasm, debugTrace, trustedIssuers) {
67
+ this.wasm = wasm;
68
+ this.debugTrace = debugTrace;
69
+ this.trustedIssuers = trustedIssuers;
227
70
  }
228
- async evaluate(context, ruleConfig) {
229
- return evaluate(this.wasm, context, ruleConfig);
71
+ /**
72
+ * Evaluate a rule set against a given context using the PayID WASM engine.
73
+ *
74
+ * ## Responsibility
75
+ *
76
+ * This method performs **pure rule evaluation only**:
77
+ * - Resolves the rule configuration (inline or via RuleSource)
78
+ * - Executes the rule engine
79
+ * - Returns an ALLOW / REJECT decision
80
+ *
81
+ * This method does NOT:
82
+ * - Generate decision proofs
83
+ * - Interact with on-chain rule registries
84
+ * - Enforce rule ownership or authority
85
+ * - Perform any signing
86
+ *
87
+ * ## Environment
88
+ *
89
+ * This method is **client-safe** and may be called from:
90
+ * - Browsers
91
+ * - Mobile apps
92
+ * - Edge runtimes
93
+ * - Backend services
94
+ *
95
+ * ## Rule source behavior
96
+ *
97
+ * - If `rule` is a `RuleConfig`, it is evaluated directly.
98
+ * - If `rule` is a `RuleSource`, it is resolved off-chain
99
+ * before evaluation.
100
+ *
101
+ * @param context
102
+ * Rule execution context (transaction data, payId, etc.).
103
+ *
104
+ * @param rule
105
+ * Rule configuration to evaluate, either:
106
+ * - An inline `RuleConfig`, or
107
+ * - A `RuleSource` that resolves to a `RuleConfig`.
108
+ *
109
+ * @returns
110
+ * A `RuleResult` indicating whether the rule allows or
111
+ * rejects the given context.
112
+ *
113
+ * @throws
114
+ * May throw if rule resolution or evaluation fails.
115
+ */
116
+ async evaluate(context, rule) {
117
+ const config = isRuleSource(rule) ? (await resolveRule(rule)).config : rule;
118
+ return evaluate(this.wasm, context, config, { debug: this.debugTrace, trustedIssuers: this.trustedIssuers });
230
119
  }
120
+ /**
121
+ * Evaluate a payment intent against PayID rules and (if allowed)
122
+ * generate an off-chain Decision Proof for on-chain verification.
123
+ *
124
+ * ## Conceptual model
125
+ *
126
+ * - `authorityRule` defines the **authoritative rule set**
127
+ * registered on-chain (NFT / combined rule).
128
+ * - `evaluationRule` (optional) is an **off-chain override**
129
+ * used only for evaluation (e.g. QR / session / intent rule).
130
+ * - On-chain verification ALWAYS references `authorityRule`.
131
+ *
132
+ * Invariant:
133
+ * - Evaluation may use `authorityRule ∧ evaluationRule`
134
+ * - Proof MUST reference `authorityRule` only
135
+ *
136
+ * @param params
137
+ * @param params.context
138
+ * Normalized rule execution context (tx, payId, etc.)
139
+ *
140
+ * @param params.authorityRule
141
+ * The authoritative rule set owned by the receiver and
142
+ * registered in the on-chain rule registry.
143
+ * This rule defines on-chain sovereignty.
144
+ *
145
+ * @param params.evaluationRule
146
+ * Optional evaluation override applied off-chain only
147
+ * (e.g. QR rule, ephemeral constraint).
148
+ * If omitted, `authorityRule` is used for evaluation.
149
+ *
150
+ * @param params.payId
151
+ * PayID identifier associated with the receiver.
152
+ *
153
+ * @param params.payer
154
+ * Address initiating the payment and signing the decision proof.
155
+ *
156
+ * @param params.receiver
157
+ * Address receiving the payment and owning the authoritative rule.
158
+ *
159
+ * @param params.asset
160
+ * Asset address to be transferred (address(0) for native ETH).
161
+ *
162
+ * @param params.amount
163
+ * Amount to be transferred (uint256 semantics).
164
+ *
165
+ * @param params.signer
166
+ * Signer corresponding to `payer`, used to sign the EIP-712
167
+ * decision proof payload.
168
+ *
169
+ * @param params.ruleRegistryContract
170
+ * Address of the on-chain rule registry / storage contract
171
+ * used by the verifier to resolve `ruleSetHash`.
172
+ *
173
+ * @param params.ttlSeconds
174
+ * Optional proof validity duration (seconds).
175
+ * Defaults to implementation-defined TTL.
176
+ *
177
+ * @returns
178
+ * An object containing:
179
+ * - `result`: rule evaluation result (ALLOW / REJECT)
180
+ * - `proof`: signed decision proof if allowed, otherwise `null`
181
+ *
182
+ * @throws
183
+ * May throw if rule resolution, evaluation, or signing fails.
184
+ */
231
185
  async evaluateAndProve(params) {
232
- const result = await this.evaluate(params.context, params.ruleConfig);
186
+ const authorityConfig = isRuleSource(params.authorityRule) ? (await resolveRule(params.authorityRule)).config : params.authorityRule;
187
+ const evalConfig = params.evaluationRule ?? authorityConfig;
188
+ const result = await evaluate(
189
+ this.wasm,
190
+ params.context,
191
+ evalConfig,
192
+ {
193
+ debug: this.debugTrace,
194
+ trustedIssuers: this.trustedIssuers
195
+ }
196
+ );
197
+ if (result.decision !== "ALLOW") {
198
+ return { result, proof: null };
199
+ }
233
200
  const proof = await generateDecisionProof({
234
201
  payId: params.payId,
235
- owner: params.owner,
236
- decision: result.decision,
202
+ payer: params.payer,
203
+ receiver: params.receiver,
204
+ asset: params.asset,
205
+ amount: params.amount,
237
206
  context: params.context,
238
- ruleConfig: params.ruleConfig,
207
+ ruleConfig: authorityConfig,
239
208
  signer: params.signer,
240
- chainId: params.chainId,
241
209
  verifyingContract: params.verifyingContract,
210
+ ruleRegistryContract: params.ruleRegistryContract,
242
211
  ttlSeconds: params.ttlSeconds
243
212
  });
244
213
  return { result, proof };
245
214
  }
246
- async evaluateWithRuleSource(context, ruleSource) {
247
- try {
248
- const { config } = await resolveRule(ruleSource);
249
- return await this.evaluate(context, config);
250
- } catch (err) {
251
- return {
252
- decision: "REJECT",
253
- code: "RULE_RESOLVE_ERROR",
254
- reason: err?.message ?? "failed to resolve rule"
255
- };
256
- }
257
- }
258
- async evaluateAndProveFromSource(params) {
259
- try {
260
- const { config } = await resolveRule(params.ruleSource);
261
- const result = await this.evaluate(params.context, config);
262
- const proof = await generateDecisionProof({
263
- payId: params.payId,
264
- owner: params.owner,
265
- decision: result.decision,
266
- context: params.context,
267
- ruleConfig: config,
268
- signer: params.signer,
269
- chainId: params.chainId,
270
- verifyingContract: params.verifyingContract,
271
- ttlSeconds: params.ttlSeconds
272
- });
273
- return { result, proof };
274
- } catch (err) {
275
- return {
276
- result: {
277
- decision: "REJECT",
278
- code: "RULE_RESOLVE_ERROR",
279
- reason: err?.message ?? "rule resolve failed"
280
- },
281
- proof: null
282
- };
283
- }
284
- }
285
- async evaluateProveAndBuildUserOp(params) {
286
- const { result, proof } = await this.evaluateAndProveFromSource({
287
- context: params.context,
288
- ruleSource: params.ruleSource,
289
- payId: params.payId,
290
- owner: params.owner,
291
- signer: params.signer,
292
- chainId: params.chainId,
293
- verifyingContract: params.verifyingContract
294
- });
295
- if (result.decision !== "ALLOW" || !proof) {
296
- return { result, userOp: null, proof };
215
+ /**
216
+ * Build an ERC-4337 UserOperation that executes a PayID payment
217
+ * using a previously generated Decision Proof.
218
+ *
219
+ * ## Responsibility
220
+ *
221
+ * This function:
222
+ * - Encodes the PayID `pay(...)` calldata using the provided proof
223
+ * - Wraps it into an ERC-4337 UserOperation
224
+ *
225
+ * This function does NOT:
226
+ * - Evaluate rules
227
+ * - Generate decision proofs
228
+ * - Perform any signature validation
229
+ *
230
+ * ## Environment constraint
231
+ *
232
+ * This function is **server-only** and MUST NOT be called in a browser:
233
+ * - It is intended for bundlers, relayers, or backend services
234
+ * - Client-side apps should only generate the proof
235
+ *
236
+ * A runtime guard is enforced to prevent accidental browser usage.
237
+ *
238
+ * @param params
239
+ * @param params.proof
240
+ * A valid Decision Proof generated by `evaluateAndProve`.
241
+ *
242
+ * @param params.smartAccount
243
+ * The ERC-4337 smart account address that will submit the UserOperation.
244
+ *
245
+ * @param params.nonce
246
+ * Current nonce of the smart account.
247
+ *
248
+ * @param params.gas
249
+ * Gas parameters for the UserOperation
250
+ * (callGasLimit, verificationGasLimit, preVerificationGas,
251
+ * maxFeePerGas, maxPriorityFeePerGas).
252
+ *
253
+ * @param params.targetContract
254
+ * Address of the PayID-compatible payment contract
255
+ * (e.g. PayWithPayID).
256
+ *
257
+ * @param params.paymasterAndData
258
+ * Optional paymaster data for sponsored transactions.
259
+ *
260
+ * @returns
261
+ * A fully constructed ERC-4337 UserOperation ready to be
262
+ * submitted to a bundler.
263
+ *
264
+ * @throws
265
+ * Throws if called in a browser environment.
266
+ */
267
+ buildUserOperation(params) {
268
+ if (typeof globalThis !== "undefined" && "document" in globalThis) {
269
+ throw new Error(
270
+ "buildUserOperation must not be called in browser"
271
+ );
297
272
  }
298
273
  const callData = buildPayCallData(
299
274
  params.targetContract,
300
- proof
275
+ params.proof
301
276
  );
302
- const userOp = buildUserOperation({
277
+ return buildUserOperation({
303
278
  sender: params.smartAccount,
304
279
  nonce: params.nonce,
305
280
  callData,
306
281
  gas: params.gas,
307
282
  paymasterAndData: params.paymasterAndData
308
283
  });
309
- return { result, userOp, proof };
310
284
  }
311
285
  };
286
+
287
+ // src/factory.ts
288
+ function createPayID(params) {
289
+ return new PayID(
290
+ params.wasm,
291
+ params.debugTrace ?? false,
292
+ params.trustedIssuers
293
+ );
294
+ }
312
295
  export {
313
- PayID
296
+ server_exports as client,
297
+ context_exports as context,
298
+ createPayID,
299
+ issuer_exports as issuer,
300
+ rule_exports as rule,
301
+ client_exports as server,
302
+ sessionPolicy_exports as sessionPolicy
314
303
  };
@@ -0,0 +1,3 @@
1
+ export { a as issueEnvContext, b as issueOracleContext, c as issueRiskContext, d as issueStateContext, s as signAttestation } from '../index-2JCvey4-.js';
2
+ import 'payid-types';
3
+ import 'ethers';
@@ -0,0 +1,16 @@
1
+ import "../chunk-AOKLY2QN.js";
2
+ import {
3
+ issueEnvContext,
4
+ issueOracleContext,
5
+ issueRiskContext,
6
+ issueStateContext,
7
+ signAttestation
8
+ } from "../chunk-7U3P7XJE.js";
9
+ import "../chunk-R5U7XKVJ.js";
10
+ export {
11
+ issueEnvContext,
12
+ issueOracleContext,
13
+ issueRiskContext,
14
+ issueStateContext,
15
+ signAttestation
16
+ };
@@ -0,0 +1,2 @@
1
+ export { a as canonicalizeRuleSet, c as combineRules, h as hashRuleSet } from '../index-C7vziL_Z.js';
2
+ import 'payid-types';
@@ -0,0 +1,15 @@
1
+ import {
2
+ hashRuleSet
3
+ } from "../chunk-JRVCGSKK.js";
4
+ import {
5
+ combineRules
6
+ } from "../chunk-QYH3FNQ4.js";
7
+ import {
8
+ canonicalizeRuleSet
9
+ } from "../chunk-JJEWYFOV.js";
10
+ import "../chunk-R5U7XKVJ.js";
11
+ export {
12
+ canonicalizeRuleSet,
13
+ combineRules,
14
+ hashRuleSet
15
+ };
@@ -0,0 +1,4 @@
1
+ export { c as createSessionPolicyPayload, d as decodeSessionPolicy } from '../index-DuOeYzN2.js';
2
+ export { P as PayIDSessionPolicyPayloadV1 } from '../types-DKt-zH0P.js';
3
+ import 'ethers';
4
+ import 'payid-types';
@@ -0,0 +1,13 @@
1
+ import {
2
+ createSessionPolicyPayload
3
+ } from "../chunk-ATWJEWZH.js";
4
+ import {
5
+ decodeSessionPolicy
6
+ } from "../chunk-MXKZJKXE.js";
7
+ import "../chunk-JJEWYFOV.js";
8
+ import "../chunk-5ZEKI5Y2.js";
9
+ import "../chunk-R5U7XKVJ.js";
10
+ export {
11
+ createSessionPolicyPayload,
12
+ decodeSessionPolicy
13
+ };
@@ -0,0 +1,21 @@
1
+ interface DecisionPayload {
2
+ version: string;
3
+ payId: string;
4
+ payer: string;
5
+ receiver: string;
6
+ asset: string;
7
+ amount: bigint;
8
+ contextHash: string;
9
+ ruleSetHash: string;
10
+ ruleAuthority: string;
11
+ issuedAt: bigint;
12
+ expiresAt: bigint;
13
+ nonce: string;
14
+ requiresAttestation: boolean;
15
+ }
16
+ interface DecisionProof {
17
+ payload: DecisionPayload;
18
+ signature: string;
19
+ }
20
+
21
+ export type { DecisionProof as D };
@@ -0,0 +1,15 @@
1
+ interface PayIDSessionPolicyPayloadV1 {
2
+ version: "payid.session.policy.v1" | string;
3
+ receiver: string;
4
+ rule: {
5
+ version: string;
6
+ logic: "AND" | "OR";
7
+ rules: any[];
8
+ };
9
+ expiresAt: number;
10
+ nonce: string;
11
+ issuedAt: number;
12
+ signature: string;
13
+ }
14
+
15
+ export type { PayIDSessionPolicyPayloadV1 as P };