@paynodelabs/sdk-js 2.1.0 → 2.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
@@ -3,7 +3,7 @@
3
3
  [![Official Documentation](https://img.shields.io/badge/Docs-docs.paynode.dev-00ff88?style=for-the-badge&logo=readthedocs)](https://docs.paynode.dev)
4
4
  [![NPM Version](https://img.shields.io/npm/v/@paynodelabs/sdk-js.svg?style=for-the-badge)](https://www.npmjs.com/package/@paynodelabs/sdk-js)
5
5
 
6
- The official TypeScript/JavaScript SDK for the **PayNode Protocol (v3.1)**. PayNode is a stateless, non-custodial M2M payment gateway that standardizes the HTTP 402 "Payment Required" flow for AI Agents, with support for both on-chain receipts and off-chain signatures (EIP-3009).
6
+ The official TypeScript/JavaScript SDK for the **PayNode Protocol (v2.2.0)**. PayNode is a stateless, non-custodial M2M payment gateway that standardizes the HTTP 402 "Payment Required" flow for AI Agents, with support for both on-chain receipts and off-chain signatures (EIP-3009).
7
7
 
8
8
  ## 📖 Read the Docs
9
9
 
@@ -33,7 +33,7 @@ async function main() {
33
33
  main();
34
34
  ```
35
35
 
36
- ### Key Features (v2.1)
36
+ ### Key Features (v2.2.0)
37
37
  - **Zero-Wait Checkout**: API response speed drops from 5 seconds to **under 50ms** by using local signatures instead of waiting for on-chain inclusion.
38
38
  - **Double-Spend Protection**:
39
39
  - **L1 (Memory)**: High-speed local replay protection via `IdempotencyStore`.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,eAAe,EAEhB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAoC;IAErD,OAAO,CAAC,SAAS,CAMf;IAEF,OAAO,CAAC,UAAU,CAAsB;gBAE5B,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,GAAG,MAAM,EAAkB,EAAE,UAAU,GAAE,MAAU;YAepF,eAAe;IAgCvB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;YAoDjE,aAAa;IAuGrB,6BAA6B,CACjC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC9B,OAAO,CAAC,eAAe,CAAC;IA6CrB,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBpH,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IA8BrJ,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAE,MAAa,EAAE,OAAO,GAAE,MAAY;;;;;;YAyChH,UAAU;CAYzB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,eAAe,EAEhB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAID,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,OAAO,CAAW;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAoC;IAErD,OAAO,CAAC,SAAS,CAMf;IAEF,OAAO,CAAC,UAAU,CAAsB;gBAE5B,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,GAAG,MAAM,EAAkB,EAAE,UAAU,GAAE,MAAU;YAepF,eAAe;IAoCvB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;YAoDjE,aAAa;IAwJrB,6BAA6B,CACjC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC9B,OAAO,CAAC,eAAe,CAAC;IA6CrB,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqBpH,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IA8BrJ,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAE,MAAa,EAAE,OAAO,GAAE,MAAY;;;;;;YAyChH,UAAU;CAYzB"}
package/dist/client.js CHANGED
@@ -36,7 +36,10 @@ class PayNodeAgentClient {
36
36
  for (let attempt = 0; attempt < this.maxRetries; attempt++) {
37
37
  try {
38
38
  const response = await fetch(url, options);
39
- if (!response || !RETRYABLE_STATUS_CODES.has(response.status)) {
39
+ if (!response) {
40
+ throw new Error('fetch returned undefined');
41
+ }
42
+ if (!RETRYABLE_STATUS_CODES.has(response.status)) {
40
43
  return response;
41
44
  }
42
45
  if (attempt < this.maxRetries - 1) {
@@ -72,7 +75,7 @@ class PayNodeAgentClient {
72
75
  if (response.status === 402) {
73
76
  console.log(`💡 [PayNode-JS] 402 Payment Required detected. Analyzing protocol version...`);
74
77
  const contentType = response.headers.get('content-type');
75
- const b64Required = response.headers.get('X-402-Required');
78
+ const b64Required = response.headers.get('PAYMENT-REQUIRED') || response.headers.get('X-402-Required');
76
79
  const orderId = response.headers.get('X-402-Order-Id');
77
80
  let body = null;
78
81
  if (contentType && contentType.includes('application/json')) {
@@ -86,12 +89,12 @@ class PayNodeAgentClient {
86
89
  body = JSON.parse(decoded);
87
90
  }
88
91
  catch (e) {
89
- console.debug('⚠️ [PayNode-JS] Failed to parse X-402-Required header:', e);
92
+ console.debug('⚠️ [PayNode-JS] Failed to parse PAYMENT-REQUIRED header:', e);
90
93
  }
91
94
  }
92
95
  if (body && body.x402Version === 2) {
93
96
  console.log(`🚀 [PayNode-JS] x402 v2 detected. Handling autonomous payment...`);
94
- if (orderId)
97
+ if (orderId && !body.orderId)
95
98
  body.orderId = orderId;
96
99
  return await this._handleX402V2(url, fetchOptions, body);
97
100
  }
@@ -132,10 +135,23 @@ class PayNodeAgentClient {
132
135
  const nonce = ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(32));
133
136
  const authorization = await this.signTransferWithAuthorization(requirement.asset, requirement.payTo, BigInt(requirement.amount), validAfter, validBefore, nonce, requirement.extra);
134
137
  payload = {
135
- version: "3.1",
136
- type: 'eip3009',
137
- orderId,
138
- payload: authorization
138
+ x402Version: 2,
139
+ resource: requirements.resource,
140
+ accepted: {
141
+ scheme: requirement.scheme,
142
+ network: requirement.network,
143
+ amount: requirement.amount,
144
+ asset: requirement.asset,
145
+ payTo: requirement.payTo,
146
+ maxTimeoutSeconds: requirement.maxTimeoutSeconds,
147
+ extra: requirement.extra || {}
148
+ },
149
+ payload: authorization,
150
+ _paynode: {
151
+ version: "2.2.0",
152
+ type: 'eip3009',
153
+ orderId: orderId
154
+ }
139
155
  };
140
156
  }
141
157
  else {
@@ -162,22 +178,37 @@ class PayNodeAgentClient {
162
178
  txHash = await this.payWithPermit(routerAddr, requirement.asset, requirement.payTo, amount, orderId, requirement.extra?.version || '2');
163
179
  }
164
180
  payload = {
165
- version: "3.1",
166
- type: 'onchain',
167
- orderId,
168
- payload: { txHash }
181
+ x402Version: 2,
182
+ resource: requirements.resource,
183
+ accepted: {
184
+ scheme: requirement.scheme,
185
+ network: requirement.network,
186
+ amount: requirement.amount,
187
+ asset: requirement.asset,
188
+ payTo: requirement.payTo,
189
+ maxTimeoutSeconds: requirement.maxTimeoutSeconds,
190
+ router: requirement.router,
191
+ extra: requirement.extra || {}
192
+ },
193
+ payload: { txHash },
194
+ _paynode: {
195
+ version: "2.2.0",
196
+ type: 'onchain',
197
+ orderId: orderId
198
+ }
169
199
  };
170
200
  }
171
- const json = JSON.stringify(payload);
201
+ const payloadJson = JSON.stringify(payload);
172
202
  const b64Payload = typeof globalThis.Buffer !== 'undefined'
173
- ? globalThis.Buffer.from(json).toString('base64')
174
- : btoa(json);
203
+ ? globalThis.Buffer.from(payloadJson).toString('base64')
204
+ : btoa(payloadJson);
175
205
  const retryOptions = {
176
206
  ...options,
177
207
  headers: {
178
208
  ...options.headers,
179
209
  'Content-Type': 'application/json',
180
- 'X-402-Payload': b64Payload,
210
+ 'PAYMENT-SIGNATURE': b64Payload,
211
+ 'X-402-Payload': b64Payload, // Keep for backward compatibility
181
212
  'X-402-Order-Id': orderId
182
213
  }
183
214
  };
@@ -185,6 +216,25 @@ class PayNodeAgentClient {
185
216
  if (retryResponse.status === 402) {
186
217
  throw new errors_1.PayNodeException(errors_1.ErrorCode.TransactionFailed, "Still 402 after payment attempt. The server may have rejected the payment or authorization.");
187
218
  }
219
+ // Attempt to parse PAYMENT-RESPONSE header
220
+ const settleHeader = retryResponse.headers.get('PAYMENT-RESPONSE') || retryResponse.headers.get('X-PAYMENT-RESPONSE');
221
+ if (settleHeader) {
222
+ try {
223
+ const decoded = typeof globalThis.Buffer !== 'undefined'
224
+ ? globalThis.Buffer.from(settleHeader, 'base64').toString()
225
+ : atob(settleHeader);
226
+ const settleData = JSON.parse(decoded);
227
+ if (settleData.success) {
228
+ console.log(`✅ [PayNode-JS] Settlement confirmed: ${settleData.transaction}`);
229
+ }
230
+ else {
231
+ console.warn(`⚠️ [PayNode-JS] Settlement failed: ${settleData.errorReason || 'Unknown error'}`);
232
+ }
233
+ }
234
+ catch (e) {
235
+ console.warn(`⚠️ [PayNode-JS] Failed to parse settlement response:`, e);
236
+ }
237
+ }
188
238
  return retryResponse;
189
239
  }
190
240
  async signTransferWithAuthorization(tokenAddr, to, amount, validAfter, validBefore, nonce, extra = {}) {
@@ -9,5 +9,61 @@ export declare const MIN_PAYMENT_AMOUNT: bigint;
9
9
  export declare const BASE_RPC_URLS: string[];
10
10
  export declare const BASE_RPC_URLS_SANDBOX: string[];
11
11
  export declare const ACCEPTED_TOKENS: Record<number, string[]>;
12
- export declare const PAYNODE_ROUTER_ABI: string[];
12
+ export declare const PAYNODE_ROUTER_ABI: ({
13
+ type: string;
14
+ inputs: {
15
+ name: string;
16
+ type: string;
17
+ internalType: string;
18
+ }[];
19
+ stateMutability: string;
20
+ name?: undefined;
21
+ outputs?: undefined;
22
+ anonymous?: undefined;
23
+ } | {
24
+ type: string;
25
+ name: string;
26
+ inputs: never[];
27
+ outputs: {
28
+ name: string;
29
+ type: string;
30
+ internalType: string;
31
+ }[];
32
+ stateMutability: string;
33
+ anonymous?: undefined;
34
+ } | {
35
+ type: string;
36
+ name: string;
37
+ inputs: {
38
+ name: string;
39
+ type: string;
40
+ internalType: string;
41
+ }[];
42
+ outputs: never[];
43
+ stateMutability: string;
44
+ anonymous?: undefined;
45
+ } | {
46
+ type: string;
47
+ name: string;
48
+ inputs: {
49
+ name: string;
50
+ type: string;
51
+ indexed: boolean;
52
+ internalType: string;
53
+ }[];
54
+ anonymous: boolean;
55
+ stateMutability?: undefined;
56
+ outputs?: undefined;
57
+ } | {
58
+ type: string;
59
+ name: string;
60
+ inputs: {
61
+ name: string;
62
+ type: string;
63
+ internalType: string;
64
+ }[];
65
+ stateMutability?: undefined;
66
+ outputs?: undefined;
67
+ anonymous?: undefined;
68
+ })[];
13
69
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,+CAA+C,CAAC;AACnF,eAAO,MAAM,8BAA8B,+CAA+C,CAAC;AAC3F,eAAO,MAAM,iBAAiB,+CAA+C,CAAC;AAC9E,eAAO,MAAM,yBAAyB,+CAA+C,CAAC;AACtF,eAAO,MAAM,iBAAiB,+CAA+C,CAAC;AAC9E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,kBAAkB,QAAe,CAAC;AAE/C,eAAO,MAAM,aAAa,UAAmF,CAAC;AAC9G,eAAO,MAAM,qBAAqB,UAA0E,CAAC;AAE7G,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAGpD,CAAC;AACF,eAAO,MAAM,kBAAkB,UAAqsC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,eAAO,MAAM,sBAAsB,+CAA+C,CAAC;AACnF,eAAO,MAAM,8BAA8B,+CAA+C,CAAC;AAC3F,eAAO,MAAM,iBAAiB,+CAA+C,CAAC;AAC9E,eAAO,MAAM,yBAAyB,+CAA+C,CAAC;AACtF,eAAO,MAAM,iBAAiB,+CAA+C,CAAC;AAC9E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,kBAAkB,QAAe,CAAC;AAE/C,eAAO,MAAM,aAAa,UAAmF,CAAC;AAC9G,eAAO,MAAM,qBAAqB,UAA0E,CAAC;AAE7G,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAGpD,CAAC;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAA25K,CAAC"}
package/dist/constants.js CHANGED
@@ -15,4 +15,4 @@ exports.ACCEPTED_TOKENS = {
15
15
  8453: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
16
16
  84532: ["0x65c088EfBDB0E03185Dbe8e258Ad0cf4Ab7946b0"]
17
17
  };
18
- exports.PAYNODE_ROUTER_ABI = ["function MAX_BPS() public", "function MIN_PAYMENT_AMOUNT() public", "function PROTOCOL_FEE_BPS() public", "function acceptOwnership() public", "function owner() public", "function pause() public", "function paused() public", "function pay(address token, address merchant, uint256 amount, bytes32 orderId) public", "function payWithPermit(address payer, address token, address merchant, uint256 amount, bytes32 orderId, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public", "function pendingOwner() public", "function protocolTreasury() public", "function renounceOwnership() public", "function transferOwnership(address newOwner) public", "function unpause() public", "function updateTreasury(address _newTreasury) public", "event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)", "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", "event Paused(address account)", "event PaymentReceived(bytes32 indexed orderId, address indexed merchant, address indexed payer, address token, uint256 amount, uint256 fee, uint256 chainId)", "event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury)", "event Unpaused(address account)"];
18
+ exports.PAYNODE_ROUTER_ABI = [{ "type": "constructor", "inputs": [{ "name": "_protocolTreasury", "type": "address", "internalType": "address" }], "stateMutability": "nonpayable" }, { "type": "function", "name": "MAX_BPS", "inputs": [], "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view" }, { "type": "function", "name": "MIN_PAYMENT_AMOUNT", "inputs": [], "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view" }, { "type": "function", "name": "PROTOCOL_FEE_BPS", "inputs": [], "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "view" }, { "type": "function", "name": "acceptOwnership", "inputs": [], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "owner", "inputs": [], "outputs": [{ "name": "", "type": "address", "internalType": "address" }], "stateMutability": "view" }, { "type": "function", "name": "pause", "inputs": [], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "paused", "inputs": [], "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], "stateMutability": "view" }, { "type": "function", "name": "pay", "inputs": [{ "name": "token", "type": "address", "internalType": "address" }, { "name": "merchant", "type": "address", "internalType": "address" }, { "name": "amount", "type": "uint256", "internalType": "uint256" }, { "name": "orderId", "type": "bytes32", "internalType": "bytes32" }], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "payWithPermit", "inputs": [{ "name": "payer", "type": "address", "internalType": "address" }, { "name": "token", "type": "address", "internalType": "address" }, { "name": "merchant", "type": "address", "internalType": "address" }, { "name": "amount", "type": "uint256", "internalType": "uint256" }, { "name": "orderId", "type": "bytes32", "internalType": "bytes32" }, { "name": "deadline", "type": "uint256", "internalType": "uint256" }, { "name": "v", "type": "uint8", "internalType": "uint8" }, { "name": "r", "type": "bytes32", "internalType": "bytes32" }, { "name": "s", "type": "bytes32", "internalType": "bytes32" }], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "pendingOwner", "inputs": [], "outputs": [{ "name": "", "type": "address", "internalType": "address" }], "stateMutability": "view" }, { "type": "function", "name": "protocolTreasury", "inputs": [], "outputs": [{ "name": "", "type": "address", "internalType": "address" }], "stateMutability": "view" }, { "type": "function", "name": "renounceOwnership", "inputs": [], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "transferOwnership", "inputs": [{ "name": "newOwner", "type": "address", "internalType": "address" }], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "unpause", "inputs": [], "outputs": [], "stateMutability": "nonpayable" }, { "type": "function", "name": "updateTreasury", "inputs": [{ "name": "_newTreasury", "type": "address", "internalType": "address" }], "outputs": [], "stateMutability": "nonpayable" }, { "type": "event", "name": "OwnershipTransferStarted", "inputs": [{ "name": "previousOwner", "type": "address", "indexed": true, "internalType": "address" }, { "name": "newOwner", "type": "address", "indexed": true, "internalType": "address" }], "anonymous": false }, { "type": "event", "name": "OwnershipTransferred", "inputs": [{ "name": "previousOwner", "type": "address", "indexed": true, "internalType": "address" }, { "name": "newOwner", "type": "address", "indexed": true, "internalType": "address" }], "anonymous": false }, { "type": "event", "name": "Paused", "inputs": [{ "name": "account", "type": "address", "indexed": false, "internalType": "address" }], "anonymous": false }, { "type": "event", "name": "PaymentReceived", "inputs": [{ "name": "orderId", "type": "bytes32", "indexed": true, "internalType": "bytes32" }, { "name": "merchant", "type": "address", "indexed": true, "internalType": "address" }, { "name": "payer", "type": "address", "indexed": true, "internalType": "address" }, { "name": "token", "type": "address", "indexed": false, "internalType": "address" }, { "name": "amount", "type": "uint256", "indexed": false, "internalType": "uint256" }, { "name": "fee", "type": "uint256", "indexed": false, "internalType": "uint256" }, { "name": "chainId", "type": "uint256", "indexed": false, "internalType": "uint256" }], "anonymous": false }, { "type": "event", "name": "TreasuryUpdated", "inputs": [{ "name": "oldTreasury", "type": "address", "indexed": true, "internalType": "address" }, { "name": "newTreasury", "type": "address", "indexed": true, "internalType": "address" }], "anonymous": false }, { "type": "event", "name": "Unpaused", "inputs": [{ "name": "account", "type": "address", "indexed": false, "internalType": "address" }], "anonymous": false }, { "type": "error", "name": "AmountTooLow", "inputs": [] }, { "type": "error", "name": "EnforcedPause", "inputs": [] }, { "type": "error", "name": "ExpectedPause", "inputs": [] }, { "type": "error", "name": "InvalidAddress", "inputs": [] }, { "type": "error", "name": "OwnableInvalidOwner", "inputs": [{ "name": "owner", "type": "address", "internalType": "address" }] }, { "type": "error", "name": "OwnableUnauthorizedAccount", "inputs": [{ "name": "account", "type": "address", "internalType": "address" }] }, { "type": "error", "name": "SafeERC20FailedOperation", "inputs": [{ "name": "token", "type": "address", "internalType": "address" }] }, { "type": "error", "name": "UnauthorizedCaller", "inputs": [] }];
@@ -29,7 +29,7 @@ exports.ERROR_MESSAGES = {
29
29
  "transaction_not_found": "Transaction not found on-chain.",
30
30
  "wrong_contract": "Payment event was not emitted by the official PayNode contract.",
31
31
  "order_mismatch": "OrderId in receipt does not match requested ID.",
32
- "missing_receipt": "Please pay to PayNode contract and provide 'X-402-Payload' header.",
32
+ "missing_receipt": "Please pay to PayNode contract and provide 'PAYMENT-SIGNATURE' header.",
33
33
  };
34
34
  class PayNodeException extends Error {
35
35
  code;
@@ -1 +1 @@
1
- {"version":3,"file":"x402.d.ts","sourceRoot":"","sources":["../../src/middleware/x402.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAcxD,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,KAAK,MAAM,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,QAAQ,GAAI,SAAS,wBAAwB,MA6B1C,KAAK,OAAO,GAAG,GAAG,EAAE,KAAK,QAAQ,GAAG,GAAG,EAAE,MAAM,YAAY,iBA8F1E,CAAC"}
1
+ {"version":3,"file":"x402.d.ts","sourceRoot":"","sources":["../../src/middleware/x402.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAcxD,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,KAAK,MAAM,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,QAAQ,GAAI,SAAS,wBAAwB,MA6B1C,KAAK,OAAO,GAAG,GAAG,EAAE,KAAK,QAAQ,GAAG,GAAG,EAAE,MAAM,YAAY,iBA0J1E,CAAC"}
@@ -40,7 +40,7 @@ const x402Gate = (options) => {
40
40
  return req.headers[name.toLowerCase()] || req.headers[name];
41
41
  return null;
42
42
  };
43
- const v2PayloadHeader = getHeader('X-402-Payload');
43
+ const v2PayloadHeader = getHeader('PAYMENT-SIGNATURE') || getHeader('X-402-Payload');
44
44
  let orderId = getHeader('X-402-Order-Id');
45
45
  if (!orderId) {
46
46
  orderId = (options.generateOrderId || defaultOrderIdGen)(req);
@@ -49,10 +49,41 @@ const x402Gate = (options) => {
49
49
  let unifiedPayload = null;
50
50
  if (v2PayloadHeader) {
51
51
  try {
52
- unifiedPayload = JSON.parse(Buffer.from(v2PayloadHeader, 'base64').toString());
52
+ const parsed = JSON.parse(Buffer.from(v2PayloadHeader, 'base64').toString());
53
+ if (parsed.x402Version === 2 && parsed.accepted) {
54
+ // Official X402 V2 format - convert to internal format
55
+ const internalOrderId = parsed._paynode?.orderId
56
+ || orderId
57
+ || `auto_${Date.now()}`;
58
+ let inferredType = "onchain";
59
+ if (parsed.payload?.signature || parsed.payload?.authorization) {
60
+ inferredType = "eip3009";
61
+ }
62
+ else if (parsed.payload?.txHash) {
63
+ inferredType = "onchain";
64
+ }
65
+ unifiedPayload = {
66
+ version: "2.2.0",
67
+ type: parsed._paynode?.type || inferredType,
68
+ orderId: internalOrderId,
69
+ router: parsed.accepted?.router,
70
+ payload: parsed.payload
71
+ };
72
+ orderId = internalOrderId;
73
+ }
74
+ else if (parsed.version === "2.2.0") {
75
+ // Legacy PayNode format
76
+ unifiedPayload = parsed;
77
+ if (unifiedPayload?.orderId) {
78
+ orderId = unifiedPayload.orderId;
79
+ }
80
+ else if (unifiedPayload?.order_id) {
81
+ orderId = unifiedPayload.order_id;
82
+ }
83
+ }
53
84
  }
54
85
  catch (e) {
55
- console.error("❌ [PayNode-Middleware] Failed to decode X-402-Payload header:", e);
86
+ console.error("❌ [PayNode-Middleware] Failed to decode payment payload header:", e);
56
87
  }
57
88
  }
58
89
  if (unifiedPayload) {
@@ -60,28 +91,52 @@ const x402Gate = (options) => {
60
91
  merchantAddress: options.merchantAddress,
61
92
  tokenAddress: tokenAddress,
62
93
  amount: rawAmount.toString(),
63
- orderId: orderId
94
+ orderId: orderId || undefined
64
95
  }, unifiedPayload.type === 'eip3009' ? { name: currency, version: "2" } : {});
65
96
  if (result.isValid) {
97
+ // Construct settlement response header
98
+ const settleResponse = {
99
+ success: true,
100
+ transaction: unifiedPayload.payload.txHash || "",
101
+ network: `eip155:${chainId}`,
102
+ payer: result.payer || ""
103
+ };
104
+ const b64Response = Buffer.from(JSON.stringify(settleResponse)).toString('base64');
105
+ if (res.set) {
106
+ res.set('PAYMENT-RESPONSE', b64Response);
107
+ res.set('X-PAYMENT-RESPONSE', b64Response); // Compatibility
108
+ }
66
109
  req.paynode = { unifiedPayload, orderId };
67
110
  return next();
68
111
  }
69
112
  else {
113
+ const errorReason = result.error?.code || errors_1.ErrorCode.InvalidReceipt;
114
+ const settleResponse = {
115
+ success: false,
116
+ errorReason: errorReason,
117
+ transaction: "",
118
+ network: `eip155:${chainId}`
119
+ };
120
+ const b64Response = Buffer.from(JSON.stringify(settleResponse)).toString('base64');
121
+ if (res.set) {
122
+ res.set('PAYMENT-RESPONSE', b64Response);
123
+ res.set('X-PAYMENT-RESPONSE', b64Response); // Compatibility
124
+ }
70
125
  return res.status(403).json({
71
126
  error: "Forbidden",
72
- code: result.error?.code || errors_1.ErrorCode.InvalidReceipt,
127
+ code: errorReason,
73
128
  message: result.error?.message || "Invalid X402 payment payload"
74
129
  });
75
130
  }
76
131
  }
77
- // No valid payment found, return 402 with X-402-Required
132
+ // No valid payment found, return 402 with appropriate headers
78
133
  const v2Response = {
79
134
  x402Version: 2,
80
135
  error: "Payment Required by PayNode",
81
136
  resource: {
82
- url: req.protocol + '://' + req.get('host') + req.originalUrl,
137
+ url: req.protocol + '://' + req.get('host') + (req.originalUrl || req.url),
83
138
  description: options.description || "Protected Resource",
84
- mimeType: req.header('accept') || "application/json"
139
+ mimeType: getHeader('accept') || "application/json"
85
140
  },
86
141
  accepts: [
87
142
  {
@@ -111,6 +166,7 @@ const x402Gate = (options) => {
111
166
  };
112
167
  const b64Required = Buffer.from(JSON.stringify(v2Response)).toString('base64');
113
168
  if (res.set) {
169
+ res.set('PAYMENT-REQUIRED', b64Required);
114
170
  res.set('X-402-Required', b64Required);
115
171
  res.set('X-402-Order-Id', orderId);
116
172
  }
@@ -32,8 +32,15 @@ export interface PaymentPayload {
32
32
  x402Version: X402Version;
33
33
  resource?: ResourceInfo;
34
34
  accepted: PaymentRequirements;
35
- payload: Record<string, any>;
35
+ payload: Record<string, any> | ExactEVMPayload | {
36
+ txHash: string;
37
+ };
36
38
  extensions?: Record<string, any>;
39
+ _paynode?: {
40
+ version: string;
41
+ type: "onchain" | "eip3009";
42
+ orderId: string;
43
+ };
37
44
  }
38
45
  export interface ExactEVMPayload {
39
46
  signature: string;
@@ -60,9 +67,10 @@ export interface VerifyResponse {
60
67
  payer?: string;
61
68
  }
62
69
  export interface UnifiedPaymentPayload {
63
- version: "3.1";
70
+ version: "2.2.0";
64
71
  type: "onchain" | "eip3009";
65
72
  orderId: string;
73
+ router?: string;
66
74
  payload: {
67
75
  txHash?: string;
68
76
  signature?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"x402.d.ts","sourceRoot":"","sources":["../../src/types/x402.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"x402.d.ts","sourceRoot":"","sources":["../../src/types/x402.ts"],"names":[],"mappings":"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"}
@@ -26,10 +26,12 @@ export declare class PayNodeVerifier {
26
26
  verify(unifiedPayload: UnifiedPaymentPayload, expected: ExpectedPayment, extra?: any): Promise<{
27
27
  isValid: boolean;
28
28
  error?: PayNodeException;
29
+ payer?: string;
29
30
  }>;
30
31
  verifyOnchainPayment(txHash: string, expected: any): Promise<{
31
32
  isValid: boolean;
32
33
  error?: PayNodeException;
34
+ payer?: string;
33
35
  }>;
34
36
  /**
35
37
  * 亚秒级离线签名验证 (V2 核心)
@@ -41,6 +43,7 @@ export declare class PayNodeVerifier {
41
43
  }, extra?: Record<string, any>): Promise<{
42
44
  isValid: boolean;
43
45
  error?: PayNodeException;
46
+ payer?: string;
44
47
  }>;
45
48
  }
46
49
  //# sourceMappingURL=verifier.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/utils/verifier.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAA0B,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEvE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,oGAAoG;IACpG,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,KAAK,CAAC,CAAmB;IACjC,OAAO,CAAC,cAAc,CAAc;gBAExB,MAAM,EAAE,qBAAqB;IAyCzC,OAAO,CAAC,MAAM,CAAC,UAAU,CAEvB;IAEI,MAAM,CACV,cAAc,EAAE,qBAAqB,EACrC,QAAQ,EAAE,eAAe,EACzB,KAAK,CAAC,EAAE,GAAG,GACV,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAC;IA8BpD,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAC;IA0ElH;;;OAGG;IACG,+BAA+B,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;KACjC,EACD,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC9B,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAC;CAqG3D"}
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/utils/verifier.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAA0B,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEvE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,oGAAoG;IACpG,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,KAAK,CAAC,CAAmB;IACjC,OAAO,CAAC,cAAc,CAAc;gBAExB,MAAM,EAAE,qBAAqB;IAyCzC,OAAO,CAAC,MAAM,CAAC,UAAU,CAEvB;IAEI,MAAM,CACV,cAAc,EAAE,qBAAqB,EACrC,QAAQ,EAAE,eAAe,EACzB,KAAK,CAAC,EAAE,GAAG,GACV,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IA8BpE,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IA4ElI;;;OAGG;IACG,+BAA+B,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;KACjC,EACD,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC9B,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CA0G3E"}
@@ -97,6 +97,7 @@ class PayNodeVerifier {
97
97
  // convention: we hash the raw orderId string to bytes32 internally
98
98
  const targetOrderId = ethers_1.ethers.id(expected.orderId);
99
99
  let validEventFound = false;
100
+ let foundPayer = undefined;
100
101
  let routerInteracted = false;
101
102
  let orderIdMismatchFound = false;
102
103
  for (const log of receipt.logs) {
@@ -106,7 +107,7 @@ class PayNodeVerifier {
106
107
  routerInteracted = true;
107
108
  const parsed = router.parseLog(log);
108
109
  if (parsed && parsed.name === 'PaymentReceived') {
109
- const { merchant, token, amount, orderId } = parsed.args;
110
+ const { merchant, token, amount, orderId, payer } = parsed.args;
110
111
  const isMerchantMatch = merchant.toLowerCase() === expected.merchantAddress.toLowerCase();
111
112
  const isTokenMatch = token.toLowerCase() === expected.tokenAddress.toLowerCase();
112
113
  const isAmountMatch = BigInt(amount) >= BigInt(expected.amount);
@@ -114,6 +115,7 @@ class PayNodeVerifier {
114
115
  if (isMerchantMatch && isTokenMatch && isAmountMatch) {
115
116
  if (isOrderMatch) {
116
117
  validEventFound = true;
118
+ foundPayer = payer;
117
119
  break;
118
120
  }
119
121
  else {
@@ -141,7 +143,7 @@ class PayNodeVerifier {
141
143
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.DuplicateTransaction) };
142
144
  }
143
145
  }
144
- return { isValid: true };
146
+ return { isValid: true, payer: foundPayer };
145
147
  }
146
148
  catch (error) {
147
149
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.RpcError, undefined, error) };
@@ -152,6 +154,9 @@ class PayNodeVerifier {
152
154
  * 耗时: < 50ms (仅需一次 RPC Read)
153
155
  */
154
156
  async verifyTransferWithAuthorization(tokenAddr, payload, expected, extra = {}) {
157
+ let isLocked = false;
158
+ const { signature, authorization } = payload;
159
+ const nonce = authorization?.nonce;
155
160
  try {
156
161
  // 1. Security Checks
157
162
  if (BigInt(expected.value) < constants_1.MIN_PAYMENT_AMOUNT) {
@@ -160,8 +165,7 @@ class PayNodeVerifier {
160
165
  if (!this.acceptedTokens.has(tokenAddr.toLowerCase())) {
161
166
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.TokenNotAccepted) };
162
167
  }
163
- const { signature, authorization } = payload;
164
- const { from, to, value, validAfter, validBefore, nonce } = authorization;
168
+ const { from, to, value, validAfter, validBefore } = authorization;
165
169
  const expectedValue = BigInt(expected.value);
166
170
  const payloadValue = BigInt(value);
167
171
  // 2. 基础字段与金额校验 (防粉尘攻击)
@@ -205,8 +209,9 @@ class PayNodeVerifier {
205
209
  if (this.store) {
206
210
  const isNew = await this.store.checkAndSet(nonce, 86400); // 锁定 24 小时
207
211
  if (!isNew) {
208
- return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.DuplicateTransaction, "Nonce already used in local memory") };
212
+ return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.DuplicateTransaction, "Nonce already used or transaction already consumed") };
209
213
  }
214
+ isLocked = true;
210
215
  }
211
216
  // ================= 核心补全:RPC 状态只读校验 (<50ms) =================
212
217
  const tokenContract = new ethers_1.ethers.Contract(tokenAddr, [
@@ -222,21 +227,24 @@ class PayNodeVerifier {
222
227
  ]);
223
228
  // 5. 校验真实余额 (防止空钱包签署有效签名)
224
229
  if (BigInt(balance) < payloadValue) {
225
- // 如果验签失败,释放内存锁
226
- if (this.store)
230
+ if (isLocked && this.store)
227
231
  await this.store.delete(nonce);
228
232
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.InvalidReceipt, "Insufficient token balance") };
229
233
  }
230
234
  // 6. 校验链上 Nonce 状态 (防止该签名已被打包结算)
231
235
  if (isNonceUsedOnChain) {
232
- if (this.store)
236
+ if (isLocked && this.store)
233
237
  await this.store.delete(nonce);
234
238
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.DuplicateTransaction, "Nonce already consumed on-chain") };
235
239
  }
236
240
  // =======================================================================
237
- return { isValid: true };
241
+ return { isValid: true, payer: from };
238
242
  }
239
243
  catch (e) {
244
+ if (isLocked && this.store)
245
+ await this.store.delete(nonce);
246
+ if (e instanceof errors_1.PayNodeException)
247
+ return { isValid: false, error: e };
240
248
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.InternalError, e.message) };
241
249
  }
242
250
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paynodelabs/sdk-js",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "The official JavaScript/TypeScript SDK for PayNode x402 protocol on Base L2.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -26,8 +26,15 @@
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
28
  "ethers": "^6.13.0",
29
- "ioredis": "^5.10.1",
30
- "express": "^4.19.2"
29
+ "ioredis": "^5.10.1"
30
+ },
31
+ "peerDependencies": {
32
+ "express": ">=4.0.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "express": {
36
+ "optional": true
37
+ }
31
38
  },
32
39
  "devDependencies": {
33
40
  "@types/express": "^5.0.6",
@@ -35,6 +42,7 @@
35
42
  "@types/jest": "^29.5.12",
36
43
  "@types/node": "^20.12.12",
37
44
  "dotenv": "^16.4.5",
45
+ "express": "^4.19.2",
38
46
  "jest": "^29.7.0",
39
47
  "ts-jest": "^29.1.4",
40
48
  "ts-node": "^10.9.2",
@@ -43,4 +51,4 @@
43
51
  "overrides": {
44
52
  "brace-expansion": "^5.0.5"
45
53
  }
46
- }
54
+ }