@paynodelabs/sdk-js 2.2.3 → 2.4.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,14 +1,32 @@
1
- # PayNode JavaScript SDK
1
+ # PayNode Master SDK (JS/TS)
2
+
3
+ > [!NOTE]
4
+ > Compatibility policy & legacy header aliases are documented in [COMPATIBILITY.md](./COMPATIBILITY.md).
2
5
 
3
6
  [![Official Documentation](https://img.shields.io/badge/Docs-docs.paynode.dev-00ff88?style=for-the-badge&logo=readthedocs)](https://docs.paynode.dev)
4
7
  [![NPM Version](https://img.shields.io/npm/v/@paynodelabs/sdk-js.svg?style=for-the-badge)](https://www.npmjs.com/package/@paynodelabs/sdk-js)
8
+ [![License](https://img.shields.io/github/license/paynodeprotocol/paynode?style=for-the-badge)](LICENSE)
9
+
10
+ The **Master SDK** for the [PayNode Protocol](https://paynode.dev). This JavaScript/TypeScript implementation serves as the **Reference Implementation** for the x402 V2 protocol. All future SDKs (Python, Go, Rust, etc.) are built following the architectural patterns and logic established in this codebase.
11
+
12
+ The current baseline and cleanup requirements for turning `sdk-js` into the formal multi-language source of truth are documented in [`meta/MULTI_LANGUAGE_BASELINE.md`](../../meta/MULTI_LANGUAGE_BASELINE.md). The protocol source of truth lives in [`meta/SPEC.md`](../../meta/SPEC.md).
5
13
 
6
- The official TypeScript/JavaScript SDK for the **PayNode Protocol (v2.2.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).
14
+ PayNode is a stateless, non-custodial M2M payment gateway that standardizes the HTTP 402 "Payment Required" flow for AI Agents, enabling seamless USDC settlement on Base L2.
7
15
 
8
- ## 📖 Read the Docs
16
+ ---
17
+
18
+ ## 🏗️ Reference Implementation Status
9
19
 
10
- **For complete installation guides, advanced usage, API references, and architecture details, please visit our official documentation:**
11
- 👉 **[docs.paynode.dev](https://docs.paynode.dev)**
20
+ As the **Master SDK**, `sdk-js` defines the gold standard for:
21
+ - **x402 V2 Handshake**: The JSON-based negotiation protocol between Agents and Merchants.
22
+ - **Dual-Mode Settlement**: Seamless switching between high-speed off-chain signatures (EIP-3009) and robust on-chain receipts.
23
+ - **Proof-of-Intent**: Standardized cryptographic signatures for M2M commerce.
24
+ - **Stateless Verification**: Logic for verifying payments without requiring complex backend databases.
25
+
26
+ > [!TIP]
27
+ > Developers building PayNode SDKs for other languages should treat this repository as the **Source of Truth** for internal logic and protocol compliance.
28
+
29
+ ---
12
30
 
13
31
  ## ⚡ Quick Start
14
32
 
@@ -19,102 +37,92 @@ npm install @paynodelabs/sdk-js ethers
19
37
  ```
20
38
 
21
39
  ### Agent Client (Payer)
40
+ The `PayNodeAgentClient` automatically identifies 402 challenges and executes the best settlement path (On-chain or EIP-3009).
22
41
 
23
42
  ```typescript
24
43
  import { PayNodeAgentClient } from "@paynodelabs/sdk-js";
25
44
 
26
- const client = new PayNodeAgentClient("YOUR_AGENT_PRIVATE_KEY", ["https://mainnet.base.org", "https://rpc.ankr.com/base"]);
45
+ // Initialize with Private Key and Base RPC
46
+ const client = new PayNodeAgentClient("YOUR_PRIVATE_KEY", ["https://mainnet.base.org"]);
27
47
 
28
48
  async function main() {
29
- // Automatically handles 402 challenges, pays USDC, and retries the request
49
+ // Automatically handles 402 challenges, settles USDC, and retries the request
30
50
  const response = await client.requestGate("https://api.merchant.com/premium-data");
31
- console.log(await response.text());
51
+ const result = await response.json();
52
+ console.log("Success:", result);
32
53
  }
33
54
  main();
34
55
  ```
35
56
 
36
- ### Key Features (v2.2.1)
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
- - **Double-Spend Protection**:
39
- - **L1 (Memory)**: High-speed local replay protection via `IdempotencyStore`.
40
- - **L2 (RPC)**: Real-time on-chain `authorizationState` verification.
41
- - **Empty-Wallet Proof**: Integrated `balanceOf` probes to block malicious agents using empty wallets to generate valid signatures.
42
- - **EIP-3009 Support**: Sign payments off-chain using `TransferWithAuthorization`.
43
- - **X402 V2 Protocol**: JSON-based handshake for structured agent interaction.
44
- - **Dual Flow**: Automatic switch between V1 (on-chain receipts) and V2 (off-chain signatures).
45
-
46
- ## 🗺️ Roadmap
47
- - **TRON Support**: USDT (TRC-20) payment integration.
48
- - **Solana Support**: SPL USDC/USDT payment integration.
49
- - **Cross-chain**: Universal settlement via bridges.
50
-
51
- ## 🚀 Run the Demo
52
-
53
- The SDK includes a full merchant/agent demonstration in the `examples/` directory.
57
+ ### Merchant Gateway (Seller)
58
+ Starting from **v2.3.0**, the SDK provides a unified middleware for **PayNode Market Proxy** requests. It handles discovery probes, signature verification, and body unwrap in a single line.
54
59
 
55
- ### 1. Setup Environment
56
-
57
- ```bash
58
- cp .env.example .env
59
- # Edit .env with your PRIVATE_KEY and RPC_URL
60
- ```
61
-
62
- ### 2. Get Test Tokens (Required for Base Sepolia)
63
-
64
- If you're testing on Sepolia, run the helper script to mint 1,000 mock USDC:
65
-
66
- ```bash
67
- npx ts-node examples/mint-test-tokens.ts
60
+ ```typescript
61
+ import { PayNodeMerchant } from "@paynodelabs/sdk-js";
62
+ import express from "express";
63
+
64
+ const app = express();
65
+ const merchant = new PayNodeMerchant({
66
+ sharedSecret: "YOUR_MARKET_SECRET" // Obtainable from PayNode Market Hub
67
+ });
68
+
69
+ // Unified Middleware (Manifest + Auth + Discovery)
70
+ app.post("/api/v1/tools", merchant.middleware({
71
+ manifest: {
72
+ slug: "gpt-researcher",
73
+ name: "GPT Researcher",
74
+ description: "Research endpoint",
75
+ price_per_call: "0.05",
76
+ currency: "USDC"
77
+ },
78
+ price: "0.05" // Also set price directly — manifest.price_per_call is for Market Hub sync only
79
+ }), (req, res) => {
80
+ // req.body is automatically unwrapped for valid Market Proxy requests
81
+ res.json({ result: "Data for " + req.body.query });
82
+ }
83
+ );
68
84
  ```
69
85
 
70
- ### 3. Run the Merchant Server (Express)
86
+ ---
71
87
 
72
- ```bash
73
- npx ts-node examples/express-server.ts
74
- ```
88
+ ## 🚀 Key Features
75
89
 
76
- ### 4. Run the Agent Client
90
+ - **Zero-Wait Checkout**: Off-chain EIP-3009 signatures allow **<50ms** checkout times by bypassing block confirmation delays.
91
+ - **Autonomous Recovery**: Built-in RPC failover and retry logic for high-availability agent environments.
92
+ - **Double-Spend Protection**: Memory-efficient `IdempotencyStore` for high-frequency replay protection.
93
+ - **EIP-2612 & EIP-3009**: Native support for gasless approvals and off-chain transfers.
94
+ - **Market Proxy Middleware**: Drop-in Express support for PayNode Market discovery, authentication, and payload unwrap.
77
95
 
78
- In another terminal:
96
+ ---
79
97
 
80
- ```bash
81
- npx ts-node examples/agent-client.ts
82
- ```
98
+ ## 🧭 Roadmap & Ecosystem
83
99
 
84
- The demo will perform a full loop: `402 Handshake -> On-chain Payment -> 200 Verification`.
100
+ As the lead SDK, this package drives the protocol forward. Current initiatives:
101
+ - 🐍 **Python SDK**: Porting `sdk-js` logic to Python for LangChain/AutoGPT integration.
102
+ - 🦀 **Rust SDK**: High-performance implementation for edge compute.
103
+ - 📡 **Cross-chain Settlement**: Integration with Solana (USDC) and TRON (USDT).
85
104
 
86
105
  ---
87
106
 
88
- ## 📦 Publishing to NPM
107
+ ## 🛠️ Development & Testing
89
108
 
90
- To publish a new version of the SDK:
91
-
92
- 1. **Build the project**:
93
- ```bash
94
- npm run build
95
- ```
96
- 2. **Login to NPM** (if not already):
97
- ```bash
98
- npm login
99
- ```
100
- 3. **Publish**:
101
- ```bash
102
- npm publish --access public
103
- ```
109
+ ### Run the Demo
110
+ 1. **Setup Env**: `cp .env.example .env` and set `CLIENT_PRIVATE_KEY`. If you want to run the merchant demo too, also set `MERCHANT_ADDRESS`.
111
+ 2. **Mint Test USDC**: `npx ts-node examples/mint-test-tokens.ts` (Base Sepolia).
112
+ 3. **Start Merchant**: `npm run example:server`
113
+ 4. **Run Agent**: `npx ts-node examples/agent-client.ts`
104
114
 
105
115
  ---
106
116
 
107
- ## Versioning Guide
108
-
109
- PayNode uses two distinct versioning schemes:
117
+ ## 📖 Versioning
110
118
 
111
- | Version Type | Description | Current Value |
112
- | :--- | :--- | :--- |
113
- | **Protocol Version** | On-chain contracts & core x402 data schema. | `1.4.0` |
114
- | **SDK/Product Version** | Software package version (npm/SemVer). | `2.2.2` |
119
+ | Component | Current Version |
120
+ | :--- | :--- |
121
+ | **Protocol (x402)** | `v2` |
122
+ | **SDK Implementation** | `v2.4.0` |
115
123
 
116
- > [!NOTE]
117
- > Protocol Version changes imply breaking changes in verification or contract interfaces. SDK Version changes may include features, fixes, or performance improvements without altering protocol logic.
124
+ > [!IMPORTANT]
125
+ > Protocol and SDK package versioning are not the same thing. Treat the protocol as `x402 v2`, and treat `2.4.0` as the current JS SDK implementation version.
118
126
 
119
127
  ---
120
128
 
package/dist/client.d.ts CHANGED
@@ -2,15 +2,22 @@ import { ExactEVMPayload } from './types/x402';
2
2
  export interface RequestOptions extends RequestInit {
3
3
  json?: any;
4
4
  }
5
+ export interface AgentClientOptions {
6
+ rpcUrls?: string | string[];
7
+ maxRetries?: number;
8
+ quiet?: boolean;
9
+ }
5
10
  export declare class PayNodeAgentClient {
6
11
  private wallet;
7
12
  private provider;
8
13
  private rpcUrls;
9
14
  private maxRetries;
15
+ private options;
10
16
  private nonceLock;
11
17
  private ERC20_ABI;
12
18
  private ROUTER_ABI;
13
19
  constructor(privateKey: string, rpcUrls?: string | string[], maxRetries?: number);
20
+ constructor(privateKey: string, options?: AgentClientOptions);
14
21
  private _fetchWithRetry;
15
22
  requestGate(url: string, options?: RequestOptions): Promise<Response>;
16
23
  private _handleX402V2;
@@ -1 +1 @@
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;YAuEjE,aAAa;IAkKrB,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;IA+CrB,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;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAKD,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,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAoC;IAErD,OAAO,CAAC,SAAS,CAMf;IAEF,OAAO,CAAC,UAAU,CAAsB;gBAE5B,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,UAAU,CAAC,EAAE,MAAM;gBACpE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB;YAuB9C,eAAe;IAoCvB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;YAuEjE,aAAa;IAoKrB,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;IA+CrB,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
@@ -10,6 +10,7 @@ class PayNodeAgentClient {
10
10
  provider;
11
11
  rpcUrls;
12
12
  maxRetries;
13
+ options;
13
14
  nonceLock = Promise.resolve();
14
15
  ERC20_ABI = [
15
16
  "function approve(address spender, uint256 value) public returns (bool)",
@@ -19,9 +20,17 @@ class PayNodeAgentClient {
19
20
  "function nonces(address owner) view returns (uint256)"
20
21
  ];
21
22
  ROUTER_ABI = constants_1.PAYNODE_ROUTER_ABI;
22
- constructor(privateKey, rpcUrls = constants_1.BASE_RPC_URLS, maxRetries = 3) {
23
- this.rpcUrls = Array.isArray(rpcUrls) ? rpcUrls : [rpcUrls];
24
- this.maxRetries = maxRetries;
23
+ constructor(privateKey, arg2, arg3) {
24
+ if (typeof arg2 === 'object' && !Array.isArray(arg2)) {
25
+ this.options = { ...arg2 };
26
+ this.rpcUrls = Array.isArray(this.options.rpcUrls) ? this.options.rpcUrls : (this.options.rpcUrls ? [this.options.rpcUrls] : constants_1.BASE_RPC_URLS);
27
+ this.maxRetries = this.options.maxRetries ?? 3;
28
+ }
29
+ else {
30
+ this.rpcUrls = Array.isArray(arg2) ? arg2 : (arg2 ? [arg2] : constants_1.BASE_RPC_URLS);
31
+ this.maxRetries = arg3 ?? 3;
32
+ this.options = { rpcUrls: this.rpcUrls, maxRetries: this.maxRetries };
33
+ }
25
34
  const configs = this.rpcUrls.map((url, index) => ({
26
35
  provider: new ethers_1.ethers.JsonRpcProvider(url, undefined, { staticNetwork: true }),
27
36
  priority: index,
@@ -44,7 +53,8 @@ class PayNodeAgentClient {
44
53
  }
45
54
  if (attempt < this.maxRetries - 1) {
46
55
  const backoffMs = Math.pow(2, attempt) * 1000;
47
- console.warn(`⚠️ [PayNode-JS] ${response.status} received. Retrying in ${backoffMs}ms...`);
56
+ if (!this.options.quiet)
57
+ console.warn(`⚠️ [PayNode-JS] ${response.status} received. Retrying in ${backoffMs}ms...`);
48
58
  await new Promise(resolve => setTimeout(resolve, backoffMs));
49
59
  continue;
50
60
  }
@@ -54,7 +64,8 @@ class PayNodeAgentClient {
54
64
  lastError = error;
55
65
  if (attempt < this.maxRetries - 1) {
56
66
  const backoffMs = Math.pow(2, attempt) * 1000;
57
- console.warn(`⚠️ [PayNode-JS] Request failed: ${error.message}. Retrying in ${backoffMs}ms...`);
67
+ if (!this.options.quiet)
68
+ console.warn(`⚠️ [PayNode-JS] Request failed: ${error.message}. Retrying in ${backoffMs}ms...`);
58
69
  await new Promise(resolve => setTimeout(resolve, backoffMs));
59
70
  }
60
71
  }
@@ -79,7 +90,8 @@ class PayNodeAgentClient {
79
90
  try {
80
91
  let response = await this._fetchWithRetry(url, fetchOptions);
81
92
  if (response.status === 402) {
82
- console.log(`💡 [PayNode-JS] 402 Payment Required detected. Analyzing protocol version...`);
93
+ if (!this.options.quiet)
94
+ console.log(`💡 [PayNode-JS] 402 Payment Required detected. Analyzing protocol version...`);
83
95
  const contentType = response.headers.get('content-type');
84
96
  const b64Required = response.headers.get('PAYMENT-REQUIRED') || response.headers.get('X-402-Required');
85
97
  const orderId = response.headers.get('X-402-Order-Id');
@@ -93,7 +105,8 @@ class PayNodeAgentClient {
93
105
  headerBody = JSON.parse(decoded);
94
106
  }
95
107
  catch (e) {
96
- console.debug('⚠️ [PayNode-JS] Failed to parse PAYMENT-REQUIRED header:', e);
108
+ if (!this.options.quiet)
109
+ console.debug('⚠️ [PayNode-JS] Failed to parse PAYMENT-REQUIRED header:', e);
97
110
  }
98
111
  }
99
112
  if (contentType && contentType.includes('application/json')) {
@@ -107,7 +120,8 @@ class PayNodeAgentClient {
107
120
  body = { ...body, ...headerBody };
108
121
  }
109
122
  if (body && body.x402Version === 2) {
110
- console.log(`🚀 [PayNode-JS] x402 v2 detected. Handling autonomous payment...`);
123
+ if (!this.options.quiet)
124
+ console.log(`🚀 [PayNode-JS] x402 v2 detected. Handling autonomous payment...`);
111
125
  if (orderId && !body.orderId)
112
126
  body.orderId = orderId;
113
127
  return await this._handleX402V2(url, fetchOptions, body);
@@ -119,7 +133,8 @@ class PayNodeAgentClient {
119
133
  catch (error) {
120
134
  if (error instanceof errors_1.PayNodeException || error?.name === "PayNodeException")
121
135
  throw error;
122
- console.error(`❌ [PayNode-JS] Critical error in requestGate:`, error);
136
+ if (!this.options.quiet)
137
+ console.error(`❌ [PayNode-JS] Critical error in requestGate:`, error);
123
138
  throw new errors_1.PayNodeException(errors_1.ErrorCode.RpcError, undefined, error);
124
139
  }
125
140
  }
@@ -132,7 +147,8 @@ class PayNodeAgentClient {
132
147
  if (!requirement) {
133
148
  throw new errors_1.PayNodeException(errors_1.ErrorCode.TransactionFailed, `No compatible payment requirement found for network ${caip2ChainId}`);
134
149
  }
135
- console.log(`💡 [PayNode-JS] Selected payment method: ${requirement.type || 'onchain'} on ${requirement.network}`);
150
+ if (!this.options.quiet)
151
+ console.log(`💡 [PayNode-JS] Selected payment method: ${requirement.type || 'onchain'} on ${requirement.network}`);
136
152
  // 🛡️ Token Whitelist Check (Case-insensitive)
137
153
  const chainTokens = constants_1.ACCEPTED_TOKENS[chainId]?.map(t => t.toLowerCase());
138
154
  if (chainTokens && !chainTokens.includes(requirement.asset.toLowerCase())) {
@@ -154,6 +170,7 @@ class PayNodeAgentClient {
154
170
  resource: requirements.resource,
155
171
  accepted: {
156
172
  scheme: requirement.scheme,
173
+ type: 'eip3009',
157
174
  network: requirement.network,
158
175
  amount: requirement.amount,
159
176
  asset: requirement.asset,
@@ -163,7 +180,7 @@ class PayNodeAgentClient {
163
180
  },
164
181
  payload: authorization,
165
182
  _paynode: {
166
- version: constants_1.SDK_VERSION,
183
+ sdkVersion: constants_1.SDK_VERSION,
167
184
  type: 'eip3009',
168
185
  orderId: orderId
169
186
  }
@@ -175,7 +192,8 @@ class PayNodeAgentClient {
175
192
  if (!routerAddr) {
176
193
  throw new errors_1.PayNodeException(errors_1.ErrorCode.InternalError, "On-chain payment required but no router address provided.");
177
194
  }
178
- console.log(`⚡ [PayNode-JS] Executing on-chain payment to ${requirement.payTo}...`);
195
+ if (!this.options.quiet)
196
+ console.log(`⚡ [PayNode-JS] Executing on-chain payment to ${requirement.payTo}...`);
179
197
  const amount = BigInt(requirement.amount);
180
198
  const tokenContract = new ethers_1.ethers.Contract(requirement.asset, this.ERC20_ABI, this.wallet);
181
199
  const allowance = await tokenContract.allowance(this.wallet.address, routerAddr);
@@ -185,7 +203,8 @@ class PayNodeAgentClient {
185
203
  txHash = await this.pay(routerAddr, requirement.asset, requirement.payTo, amount, orderId);
186
204
  }
187
205
  catch (e) {
188
- console.warn(`⚠️ [PayNode-JS] Direct pay failed (possibly allowance race), falling back to permit:`, e);
206
+ if (!this.options.quiet)
207
+ console.warn(`⚠️ [PayNode-JS] Direct pay failed (possibly allowance race), falling back to permit:`, e);
189
208
  txHash = await this.payWithPermit(routerAddr, requirement.asset, requirement.payTo, amount, orderId, requirement.extra?.version || '2');
190
209
  }
191
210
  }
@@ -197,6 +216,7 @@ class PayNodeAgentClient {
197
216
  resource: requirements.resource,
198
217
  accepted: {
199
218
  scheme: requirement.scheme,
219
+ type: 'onchain',
200
220
  network: requirement.network,
201
221
  amount: requirement.amount,
202
222
  asset: requirement.asset,
@@ -207,7 +227,7 @@ class PayNodeAgentClient {
207
227
  },
208
228
  payload: { txHash },
209
229
  _paynode: {
210
- version: constants_1.SDK_VERSION,
230
+ sdkVersion: constants_1.SDK_VERSION,
211
231
  type: 'onchain',
212
232
  orderId: orderId
213
233
  }
@@ -248,14 +268,17 @@ class PayNodeAgentClient {
248
268
  }
249
269
  const settleData = JSON.parse(decoded);
250
270
  if (settleData.success) {
251
- console.log(`✅ [PayNode-JS] Settlement confirmed: ${settleData.transaction}`);
271
+ if (!this.options.quiet)
272
+ console.log(`✅ [PayNode-JS] Settlement confirmed: ${settleData.transaction}`);
252
273
  }
253
274
  else {
254
- console.warn(`⚠️ [PayNode-JS] Settlement failed: ${settleData.errorReason || 'Unknown error'}`);
275
+ if (!this.options.quiet)
276
+ console.warn(`⚠️ [PayNode-JS] Settlement failed: ${settleData.errorReason || 'Unknown error'}`);
255
277
  }
256
278
  }
257
279
  catch (e) {
258
- console.warn(`⚠️ [PayNode-JS] Failed to parse settlement response:`, e);
280
+ if (!this.options.quiet)
281
+ console.warn(`⚠️ [PayNode-JS] Failed to parse settlement response:`, e);
259
282
  }
260
283
  }
261
284
  return retryResponse;
@@ -6,7 +6,8 @@ export declare const BASE_USDC_ADDRESS_SANDBOX = "0x65c088EfBDB0E03185Dbe8e258Ad
6
6
  export declare const PROTOCOL_TREASURY = "0x598bF63F5449876efafa7b36b77Deb2070621C0E";
7
7
  export declare const PROTOCOL_FEE_BPS = 100;
8
8
  export declare const MIN_PAYMENT_AMOUNT: bigint;
9
- export declare const SDK_VERSION = "2.2.2";
9
+ export declare const PROTOCOL_VERSION = 2;
10
+ export declare const SDK_VERSION = "2.4.0";
10
11
  export declare const BASE_RPC_URLS: string[];
11
12
  export declare const BASE_RPC_URLS_SANDBOX: string[];
12
13
  export declare const ACCEPTED_TOKENS: Record<number, string[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,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;AAC/C,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,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"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,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;AAC/C,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,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
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PAYNODE_ROUTER_ABI = exports.ACCEPTED_TOKENS = exports.BASE_RPC_URLS_SANDBOX = exports.BASE_RPC_URLS = exports.SDK_VERSION = exports.MIN_PAYMENT_AMOUNT = exports.PROTOCOL_FEE_BPS = exports.PROTOCOL_TREASURY = exports.BASE_USDC_ADDRESS_SANDBOX = exports.BASE_USDC_ADDRESS = exports.PAYNODE_ROUTER_ADDRESS_SANDBOX = exports.PAYNODE_ROUTER_ADDRESS = void 0;
3
+ exports.PAYNODE_ROUTER_ABI = exports.ACCEPTED_TOKENS = exports.BASE_RPC_URLS_SANDBOX = exports.BASE_RPC_URLS = exports.SDK_VERSION = exports.PROTOCOL_VERSION = exports.MIN_PAYMENT_AMOUNT = exports.PROTOCOL_FEE_BPS = exports.PROTOCOL_TREASURY = exports.BASE_USDC_ADDRESS_SANDBOX = exports.BASE_USDC_ADDRESS = exports.PAYNODE_ROUTER_ADDRESS_SANDBOX = exports.PAYNODE_ROUTER_ADDRESS = void 0;
4
4
  /** Generated by meta/scripts/sync-config.py */
5
5
  exports.PAYNODE_ROUTER_ADDRESS = "0x4A73696ccF76E7381b044cB95127B3784369Ed63";
6
6
  exports.PAYNODE_ROUTER_ADDRESS_SANDBOX = "0x24cD8b68aaC209217ff5a6ef1Bf55a59f2c8Ca6F";
@@ -9,7 +9,8 @@ exports.BASE_USDC_ADDRESS_SANDBOX = "0x65c088EfBDB0E03185Dbe8e258Ad0cf4Ab7946b0"
9
9
  exports.PROTOCOL_TREASURY = "0x598bF63F5449876efafa7b36b77Deb2070621C0E";
10
10
  exports.PROTOCOL_FEE_BPS = 100;
11
11
  exports.MIN_PAYMENT_AMOUNT = BigInt(1000);
12
- exports.SDK_VERSION = "2.2.2";
12
+ exports.PROTOCOL_VERSION = 2;
13
+ exports.SDK_VERSION = "2.4.0";
13
14
  exports.BASE_RPC_URLS = ["https://mainnet.base.org", "https://base.meowrpc.com", "https://1rpc.io/base"];
14
15
  exports.BASE_RPC_URLS_SANDBOX = ["https://sepolia.base.org", "https://base-sepolia-rpc.publicnode.com"];
15
16
  exports.ACCEPTED_TOKENS = {
package/dist/index.d.ts CHANGED
@@ -8,4 +8,6 @@ export * from './constants';
8
8
  export * from './utils/payload';
9
9
  export * from './types/x402';
10
10
  export { ethers } from 'ethers';
11
+ export * from './merchant';
12
+ export * from './merchant/types';
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -26,3 +26,5 @@ __exportStar(require("./utils/payload"), exports);
26
26
  __exportStar(require("./types/x402"), exports);
27
27
  var ethers_1 = require("ethers");
28
28
  Object.defineProperty(exports, "ethers", { enumerable: true, get: function () { return ethers_1.ethers; } });
29
+ __exportStar(require("./merchant"), exports);
30
+ __exportStar(require("./merchant/types"), exports);
@@ -0,0 +1,37 @@
1
+ import { MerchantConfig, MerchantMiddlewareOptions, ApiManifest } from './types';
2
+ import { PayNodeMiddlewareOptions } from '../middleware/x402';
3
+ /**
4
+ * PayNodeMerchant: The high-level SDK class for Merchant Integration.
5
+ */
6
+ export declare class PayNodeMerchant {
7
+ private config;
8
+ constructor(config: MerchantConfig);
9
+ /**
10
+ * Registers or syncs the API manifest with the PayNode Market.
11
+ * This ensures the market shows the correct price and input schema.
12
+ */
13
+ sync(manifest: ApiManifest): Promise<boolean>;
14
+ /**
15
+ * Returns a unified middleware that handles:
16
+ * 1. Market Proxy (Strict Signature Check + Body Unwrap)
17
+ * 2. Auto-Discovery (Market Sync Probe)
18
+ * 3. (Optional) Direct X402 payment
19
+ */
20
+ middleware(options?: MerchantMiddlewareOptions & Partial<PayNodeMiddlewareOptions>): (req: import("express").Request | any, res: import("express").Response | any, next: import("express").NextFunction) => Promise<any>;
21
+ /**
22
+ * Manual verification for Next.js or other non-Express environments.
23
+ * Extracts headers and verifies signature. Returns the unwrapped body and context.
24
+ */
25
+ verify(req: any): Promise<{
26
+ isValid: boolean;
27
+ error: string;
28
+ body?: undefined;
29
+ paynodeContext?: undefined;
30
+ } | {
31
+ isValid: boolean;
32
+ body: any;
33
+ paynodeContext: any;
34
+ error?: undefined;
35
+ }>;
36
+ }
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/merchant/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,yBAAyB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAG9D;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,EAAE,cAAc;IAOlC;;;OAGG;IACG,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BnD;;;;;OAKG;IACH,UAAU,CAAC,OAAO,GAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAM;IAOtF;;;OAGG;IACG,MAAM,CAAC,GAAG,EAAE,GAAG;;;;;;;;;;;CAiDtB"}
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PayNodeMerchant = void 0;
4
+ const middleware_1 = require("./middleware");
5
+ const signature_1 = require("../utils/signature");
6
+ /**
7
+ * PayNodeMerchant: The high-level SDK class for Merchant Integration.
8
+ */
9
+ class PayNodeMerchant {
10
+ config;
11
+ constructor(config) {
12
+ this.config = {
13
+ marketUrl: 'https://mk.paynode.dev',
14
+ ...config
15
+ };
16
+ }
17
+ /**
18
+ * Registers or syncs the API manifest with the PayNode Market.
19
+ * This ensures the market shows the correct price and input schema.
20
+ */
21
+ async sync(manifest) {
22
+ if (!this.config.quiet)
23
+ console.log(`[PayNode-SDK] Syncing API manifest for ${manifest.slug} to ${this.config.marketUrl}`);
24
+ try {
25
+ const response = await fetch(`${this.config.marketUrl}/api/v1/merchant/apis`, {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json' },
28
+ body: JSON.stringify({
29
+ ...manifest,
30
+ gateway_url: manifest.slug
31
+ })
32
+ });
33
+ const result = await response.json();
34
+ if (response.ok && result.success) {
35
+ if (!this.config.quiet)
36
+ console.log(`[PayNode-SDK] Successfully synced ${manifest.slug}. Status: ${result.api_id}`);
37
+ return true;
38
+ }
39
+ else {
40
+ if (!this.config.quiet)
41
+ console.warn(`[PayNode-SDK] Sync failed for ${manifest.slug}: ${result.error || response.statusText}`);
42
+ return false;
43
+ }
44
+ }
45
+ catch (err) {
46
+ if (!this.config.quiet)
47
+ console.error(`[PayNode-SDK] Network error during sync for ${manifest.slug}:`, err.message);
48
+ return false;
49
+ }
50
+ }
51
+ /**
52
+ * Returns a unified middleware that handles:
53
+ * 1. Market Proxy (Strict Signature Check + Body Unwrap)
54
+ * 2. Auto-Discovery (Market Sync Probe)
55
+ * 3. (Optional) Direct X402 payment
56
+ */
57
+ middleware(options = {}) {
58
+ return (0, middleware_1.createMerchantMiddleware)(this.config, {
59
+ price: options.manifest?.price_per_call || '0.01',
60
+ ...options
61
+ });
62
+ }
63
+ /**
64
+ * Manual verification for Next.js or other non-Express environments.
65
+ * Extracts headers and verifies signature. Returns the unwrapped body and context.
66
+ */
67
+ async verify(req) {
68
+ const getHeader = (name) => {
69
+ if (typeof req.getHeader === 'function')
70
+ return req.getHeader(name);
71
+ if (typeof req.get === 'function')
72
+ return req.get(name);
73
+ if (req.headers?.get && typeof req.headers.get === 'function')
74
+ return req.headers.get(name);
75
+ if (req.headers)
76
+ return req.headers[name.toLowerCase()] || req.headers[name];
77
+ return null;
78
+ };
79
+ const signature = getHeader('X-PayNode-Signature');
80
+ const timestamp = getHeader('X-PayNode-Timestamp');
81
+ const requestId = getHeader('X-PayNode-Request-Id') || getHeader('X-402-Order-Id') || getHeader('PAYMENT-SIGNATURE'); // Try fallback
82
+ const isValid = (0, signature_1.verifyMarketSignature)({
83
+ signature,
84
+ orderId: requestId,
85
+ timestamp,
86
+ sharedSecret: this.config.sharedSecret,
87
+ });
88
+ if (!isValid) {
89
+ return { isValid: false, error: 'Invalid PayNode Market Signature' };
90
+ }
91
+ // Handle Body Unwrap if it's a JSON body
92
+ let body = {};
93
+ try {
94
+ if (typeof req.json === 'function') {
95
+ body = await req.json();
96
+ }
97
+ else {
98
+ body = req.body;
99
+ }
100
+ }
101
+ catch (e) { }
102
+ let paynodeContext = { orderId: requestId };
103
+ if (body && body.payload && typeof body.payload === 'object') {
104
+ paynodeContext = {
105
+ ...paynodeContext,
106
+ txHash: getHeader('X-PayNode-Transaction-Hash') || body.tx_hash,
107
+ amount: getHeader('X-PayNode-Amount') || body.amount,
108
+ network: getHeader('X-PayNode-Network') || body.network,
109
+ chainId: getHeader('X-PayNode-Chain-Id') || body.chain_id?.toString(),
110
+ };
111
+ body = body.payload;
112
+ }
113
+ return { isValid: true, body, paynodeContext };
114
+ }
115
+ }
116
+ exports.PayNodeMerchant = PayNodeMerchant;
@@ -0,0 +1,14 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { MerchantConfig, MerchantMiddlewareOptions } from './types';
3
+ import { PayNodeMiddlewareOptions } from '../middleware/x402';
4
+ /**
5
+ * Unified PayNode Merchant Middleware
6
+ * Handles:
7
+ * 1. Market Proxy (Strict HMAC Signature + Body Unwrapping)
8
+ * 2. Discovery Probes (Auto-respond with API Manifest)
9
+ *
10
+ * Note: Standalone direct X402 payment flow should be handled
11
+ * via x402Gate component.
12
+ */
13
+ export declare const createMerchantMiddleware: (config: MerchantConfig, options: MerchantMiddlewareOptions & PayNodeMiddlewareOptions) => (req: Request | any, res: Response | any, next: NextFunction) => Promise<any>;
14
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/merchant/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAEpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,QAAQ,cAAc,EAAE,SAAS,yBAAyB,GAAG,wBAAwB,MAG9G,KAAK,OAAO,GAAG,GAAG,EAAE,KAAK,QAAQ,GAAG,GAAG,EAAE,MAAM,YAAY,iBAiE1E,CAAC"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMerchantMiddleware = void 0;
4
+ const signature_1 = require("../utils/signature");
5
+ /**
6
+ * Unified PayNode Merchant Middleware
7
+ * Handles:
8
+ * 1. Market Proxy (Strict HMAC Signature + Body Unwrapping)
9
+ * 2. Discovery Probes (Auto-respond with API Manifest)
10
+ *
11
+ * Note: Standalone direct X402 payment flow should be handled
12
+ * via x402Gate component.
13
+ */
14
+ const createMerchantMiddleware = (config, options) => {
15
+ const { manifest } = options;
16
+ return async (req, res, next) => {
17
+ // 1. Check for Market Proxy Headers
18
+ const signature = req.header('X-PayNode-Signature');
19
+ const timestamp = req.header('X-PayNode-Timestamp');
20
+ const requestId = req.header('X-PayNode-Request-Id') || req.header('X-402-Order-Id');
21
+ const isDiscovery = req.header('X-PayNode-Discovery') === 'true';
22
+ if (signature && requestId && timestamp) {
23
+ // ✅ Verify Signature from PayNode Market
24
+ const isValid = (0, signature_1.verifyMarketSignature)({
25
+ signature,
26
+ orderId: requestId,
27
+ timestamp,
28
+ sharedSecret: config.sharedSecret,
29
+ });
30
+ if (!isValid) {
31
+ if (!config.quiet)
32
+ console.error(`[PayNode-SDK] Invalid Market Proxy Signature for request ${requestId}`);
33
+ return res.status(401).json({ error: 'unauthorized', message: 'PayNode Market Signature verification failed.' });
34
+ }
35
+ // --- Scene A: Discovery Probe ---
36
+ if (isDiscovery) {
37
+ return res.status(200).json({
38
+ status: 'DISCOVERED',
39
+ x402Version: 2,
40
+ manifest: manifest || {},
41
+ last_synced: new Date().toISOString()
42
+ });
43
+ }
44
+ // --- Scene B: Proxy Flow - Body Unwrapping ---
45
+ // The Market Proxy wraps original body in { payload: { ... } }
46
+ if (req.body && req.body.payload && typeof req.body.payload === 'object') {
47
+ const metadata = { ...req.body };
48
+ delete metadata.payload;
49
+ // Enrich request context with Proxy details
50
+ req.paynode = {
51
+ orderId: requestId,
52
+ txHash: req.header('X-PayNode-Transaction-Hash') || req.body.tx_hash,
53
+ amount: req.header('X-PayNode-Amount') || req.body.amount,
54
+ network: req.header('X-PayNode-Network') || req.body.network,
55
+ chainId: req.header('X-PayNode-Chain-Id') || req.body.chain_id?.toString(),
56
+ proxyMetadata: metadata
57
+ };
58
+ // Transparently Unwrap Body
59
+ req.body = req.body.payload;
60
+ }
61
+ else {
62
+ // Direct call via Proxy (unlikely for POST, but possible for some flows)
63
+ req.paynode = { orderId: requestId };
64
+ }
65
+ return next();
66
+ }
67
+ // 2. Scene C: Direct Agent Call (Rejected)
68
+ // PayNodeMerchant component REQUIRES Market Proxy to ensure protocol consistency and fee collection.
69
+ // Use x402Gate directly for standalone/direct 402 integration.
70
+ return res.status(403).json({
71
+ error: 'forbidden',
72
+ message: 'PayNode Market Auth required. This API must be accessed via PayNode Market Proxy for verification.'
73
+ });
74
+ };
75
+ };
76
+ exports.createMerchantMiddleware = createMerchantMiddleware;
@@ -0,0 +1,27 @@
1
+ export interface MerchantConfig {
2
+ sharedSecret: string;
3
+ marketUrl?: string;
4
+ quiet?: boolean;
5
+ }
6
+ export interface ApiManifest {
7
+ slug: string;
8
+ name: string;
9
+ description: string;
10
+ price_per_call: string;
11
+ currency?: string;
12
+ network?: 'mainnet' | 'testnet';
13
+ input_schema?: Record<string, any>;
14
+ sample_response?: Record<string, any>;
15
+ }
16
+ export interface PayNodeRequestContext {
17
+ orderId: string;
18
+ txHash?: string;
19
+ payer?: string;
20
+ amount?: string;
21
+ network?: string;
22
+ chainId?: string;
23
+ }
24
+ export interface MerchantMiddlewareOptions {
25
+ manifest?: Partial<ApiManifest>;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/merchant/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,iBA0J1E,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;AAgBxD,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,iBAkK1E,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('PAYMENT-SIGNATURE') || getHeader('X-402-Payload');
43
+ const v2PayloadHeader = getHeader('PAYMENT-SIGNATURE') || getHeader('X-402-Payload'); // COMPAT: X-402-Payload is a legacy alias for PAYMENT-SIGNATURE
44
44
  let orderId = getHeader('X-402-Order-Id');
45
45
  if (!orderId) {
46
46
  orderId = (options.generateOrderId || defaultOrderIdGen)(req);
@@ -63,23 +63,29 @@ const x402Gate = (options) => {
63
63
  inferredType = "onchain";
64
64
  }
65
65
  unifiedPayload = {
66
- version: "2.2.2",
66
+ x402Version: constants_1.PROTOCOL_VERSION,
67
67
  type: parsed._paynode?.type || inferredType,
68
68
  orderId: internalOrderId,
69
69
  router: parsed.accepted?.router,
70
- payload: parsed.payload
70
+ payload: parsed.payload,
71
+ _paynode: {
72
+ sdkVersion: constants_1.SDK_VERSION
73
+ }
71
74
  };
72
75
  orderId = internalOrderId;
73
76
  }
74
- else if (typeof parsed.version === 'string' && (parsed.version.startsWith("2.3") || parsed.version.startsWith("2.2"))) {
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
- }
77
+ else if (typeof (parsed.version || parsed.x402Version) === 'string' || typeof parsed.version === 'string') {
78
+ // Legacy PayNode format or old x402 V2 drafts
79
+ unifiedPayload = {
80
+ x402Version: constants_1.PROTOCOL_VERSION,
81
+ type: parsed.type || (parsed.payload?.txHash ? "onchain" : "eip3009"),
82
+ orderId: parsed.orderId || parsed.order_id || orderId || `legacy_${Date.now()}`,
83
+ payload: parsed.payload,
84
+ _paynode: {
85
+ sdkVersion: constants_1.SDK_VERSION
86
+ }
87
+ };
88
+ orderId = unifiedPayload.orderId;
83
89
  }
84
90
  }
85
91
  catch (e) {
@@ -104,7 +110,7 @@ const x402Gate = (options) => {
104
110
  const b64Response = Buffer.from(JSON.stringify(settleResponse)).toString('base64');
105
111
  if (res.set) {
106
112
  res.set('PAYMENT-RESPONSE', b64Response);
107
- res.set('X-PAYMENT-RESPONSE', b64Response); // Compatibility
113
+ res.set('X-PAYMENT-RESPONSE', b64Response); // COMPAT (legacy): deprecated alias for PAYMENT-RESPONSE
108
114
  }
109
115
  req.paynode = { unifiedPayload, orderId };
110
116
  return next();
@@ -120,7 +126,7 @@ const x402Gate = (options) => {
120
126
  const b64Response = Buffer.from(JSON.stringify(settleResponse)).toString('base64');
121
127
  if (res.set) {
122
128
  res.set('PAYMENT-RESPONSE', b64Response);
123
- res.set('X-PAYMENT-RESPONSE', b64Response); // Compatibility
129
+ res.set('X-PAYMENT-RESPONSE', b64Response); // COMPAT (legacy): deprecated alias for PAYMENT-RESPONSE
124
130
  }
125
131
  return res.status(403).json({
126
132
  error: "Forbidden",
@@ -131,13 +137,14 @@ const x402Gate = (options) => {
131
137
  }
132
138
  // No valid payment found, return 402 with appropriate headers
133
139
  const v2Response = {
134
- x402Version: 2,
140
+ x402Version: constants_1.PROTOCOL_VERSION,
135
141
  error: "Payment Required by PayNode",
136
142
  resource: {
137
143
  url: req.protocol + '://' + req.get('host') + (req.originalUrl || req.url),
138
144
  description: options.description || "Protected Resource",
139
145
  mimeType: getHeader('accept') || "application/json"
140
146
  },
147
+ orderId: orderId || undefined,
141
148
  accepts: [
142
149
  {
143
150
  scheme: "exact",
@@ -167,7 +174,7 @@ const x402Gate = (options) => {
167
174
  const b64Required = Buffer.from(JSON.stringify(v2Response)).toString('base64');
168
175
  if (res.set) {
169
176
  res.set('PAYMENT-REQUIRED', b64Required);
170
- res.set('X-402-Required', b64Required);
177
+ res.set('X-402-Required', b64Required); // COMPAT (legacy): deprecated alias for PAYMENT-REQUIRED
171
178
  res.set('X-402-Order-Id', orderId);
172
179
  }
173
180
  return res.status(402).json(v2Response);
@@ -4,6 +4,19 @@ export interface ResourceInfo {
4
4
  description?: string;
5
5
  mimeType?: string;
6
6
  }
7
+ /**
8
+ * PaymentRequirements describes a single payment option returned
9
+ * in the `accepts[]` array of a 402 challenge.
10
+ *
11
+ * NOTE: The `orderId` (legacy) is DEPRECATED. All SDKs MUST read
12
+ * and write the request ID exclusively via the
13
+ * `X-402-Order-Id` HTTP header. This field exists only for
14
+ * backward compatibility with paynode-js <= 2.2.x and must
15
+ * not be used by new implementations.
16
+ *
17
+ * Protocol: x402 v2
18
+ * Canonical orderId transport: X-402-Order-Id header
19
+ */
7
20
  export interface PaymentRequirements {
8
21
  scheme: string;
9
22
  type?: "onchain" | "eip3009";
@@ -12,6 +25,7 @@ export interface PaymentRequirements {
12
25
  asset: string;
13
26
  payTo: string;
14
27
  router?: string;
28
+ /** @deprecated Use X-402-Order-Id header exclusively. Legacy alias only. */
15
29
  orderId?: string;
16
30
  maxTimeoutSeconds: number;
17
31
  extra?: Record<string, any>;
@@ -37,7 +51,7 @@ export interface PaymentPayload {
37
51
  };
38
52
  extensions?: Record<string, any>;
39
53
  _paynode?: {
40
- version: string;
54
+ sdkVersion: string;
41
55
  type: "onchain" | "eip3009";
42
56
  orderId: string;
43
57
  };
@@ -67,7 +81,7 @@ export interface VerifyResponse {
67
81
  payer?: string;
68
82
  }
69
83
  export interface UnifiedPaymentPayload {
70
- version: "2.2.2";
84
+ x402Version: X402Version;
71
85
  type: "onchain" | "eip3009";
72
86
  orderId: string;
73
87
  router?: string;
@@ -76,6 +90,9 @@ export interface UnifiedPaymentPayload {
76
90
  signature?: string;
77
91
  authorization?: any;
78
92
  } | ExactEVMPayload;
93
+ _paynode?: {
94
+ sdkVersion: string;
95
+ };
79
96
  }
80
97
  export interface SupportedKind {
81
98
  x402Version: X402Version;
@@ -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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,qBAAa,iBAAiB;IAC5B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,qBAAqB;CAuCtF"}
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,qBAAa,iBAAiB;IAC5B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,qBAAqB;CA8CtF"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.X402PayloadHelper = void 0;
4
+ const constants_1 = require("../constants");
4
5
  class X402PayloadHelper {
5
6
  /**
6
7
  * Normalizes a raw payment payload (usually from PAYMENT-SIGNATURE or X-402-Payload headers)
@@ -29,18 +30,25 @@ class X402PayloadHelper {
29
30
  inferredType = "onchain";
30
31
  }
31
32
  return {
32
- version: "2.2.2",
33
+ x402Version: 2,
33
34
  type: parsed._paynode?.type || inferredType,
34
35
  orderId: parsed._paynode?.orderId || fallbackOrderId || "",
35
36
  router: parsed.accepted?.router || parsed.router,
36
- payload: parsed.payload
37
+ payload: parsed.payload,
38
+ _paynode: {
39
+ sdkVersion: constants_1.SDK_VERSION
40
+ }
37
41
  };
38
42
  }
39
43
  // 2. Handle Legacy or already Unified Format
40
44
  if (typeof parsed.version === 'string' && (parsed.version.startsWith("2.2") || parsed.version.startsWith("2.3"))) {
41
45
  return {
42
46
  ...parsed,
43
- orderId: parsed.orderId || parsed.order_id || fallbackOrderId || ""
47
+ x402Version: parsed.x402Version || 2,
48
+ orderId: parsed.orderId || parsed.order_id || fallbackOrderId || "",
49
+ _paynode: parsed._paynode || {
50
+ sdkVersion: parsed.version
51
+ }
44
52
  };
45
53
  }
46
54
  // 3. Fallback for raw internal format
@@ -0,0 +1,13 @@
1
+ export interface SignatureContext {
2
+ signature: string;
3
+ orderId: string;
4
+ timestamp: string;
5
+ sharedSecret: string;
6
+ now?: number;
7
+ driftWindow?: number;
8
+ }
9
+ /**
10
+ * Verifies the HMAC-SHA256 signature from PayNode Market Proxy
11
+ */
12
+ export declare function verifyMarketSignature(context: SignatureContext): boolean;
13
+ //# sourceMappingURL=signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/utils/signature.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAuCxE"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.verifyMarketSignature = verifyMarketSignature;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ /**
9
+ * Verifies the HMAC-SHA256 signature from PayNode Market Proxy
10
+ */
11
+ function verifyMarketSignature(context) {
12
+ const { signature, orderId, timestamp, sharedSecret } = context;
13
+ if (!signature || !orderId || !timestamp || !sharedSecret) {
14
+ return false;
15
+ }
16
+ // Check for timestamp drift (default 5 minutes to prevent replay)
17
+ const tsDate = new Date(timestamp);
18
+ const checkTime = context.now || Date.now();
19
+ const driftWindow = context.driftWindow || 5 * 60 * 1000;
20
+ // Accept both ISO string and milliseconds
21
+ const tsMs = isNaN(tsDate.getTime()) ? parseInt(timestamp) : tsDate.getTime();
22
+ if (isNaN(tsMs))
23
+ return false;
24
+ const drift = Math.abs(checkTime - tsMs);
25
+ if (drift > driftWindow) {
26
+ if (driftWindow > 0) {
27
+ console.warn(`[PayNode-SDK] Signature timestamp drift too high: ${drift}ms`);
28
+ return false;
29
+ }
30
+ }
31
+ const expectedSig = crypto_1.default
32
+ .createHmac('sha256', sharedSecret)
33
+ .update(`${orderId}:${timestamp}`)
34
+ .digest('hex');
35
+ // Use constant-time comparison to prevent timing attacks
36
+ try {
37
+ return crypto_1.default.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expectedSig, 'hex'));
38
+ }
39
+ catch (e) {
40
+ return false;
41
+ }
42
+ }
@@ -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,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"}
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/utils/verifier.ts"],"names":[],"mappings":"AAEA,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;IAqFlI;;;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;CA+G3E"}
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.PayNodeVerifier = void 0;
4
7
  const ethers_1 = require("ethers");
8
+ const crypto_1 = __importDefault(require("crypto"));
5
9
  const errors_1 = require("../errors");
6
10
  const idempotency_1 = require("./idempotency");
7
11
  const constants_1 = require("../constants");
@@ -108,10 +112,16 @@ class PayNodeVerifier {
108
112
  const parsed = router.parseLog(log);
109
113
  if (parsed && parsed.name === 'PaymentReceived') {
110
114
  const { merchant, token, amount, orderId, payer } = parsed.args;
111
- const isMerchantMatch = merchant.toLowerCase() === expected.merchantAddress.toLowerCase();
112
- const isTokenMatch = token.toLowerCase() === expected.tokenAddress.toLowerCase();
115
+ const merchantBuf = Buffer.from(merchant.toLowerCase().replace('0x', ''), 'hex');
116
+ const expectedMerchantBuf = Buffer.from(expected.merchantAddress.toLowerCase().replace('0x', ''), 'hex');
117
+ const isMerchantMatch = merchantBuf.length === expectedMerchantBuf.length && crypto_1.default.timingSafeEqual(merchantBuf, expectedMerchantBuf);
118
+ const tokenBuf = Buffer.from(token.toLowerCase().replace('0x', ''), 'hex');
119
+ const expectedTokenBuf = Buffer.from(expected.tokenAddress.toLowerCase().replace('0x', ''), 'hex');
120
+ const isTokenMatch = tokenBuf.length === expectedTokenBuf.length && crypto_1.default.timingSafeEqual(tokenBuf, expectedTokenBuf);
113
121
  const isAmountMatch = BigInt(amount) >= BigInt(expected.amount);
114
- const isOrderMatch = orderId === targetOrderId;
122
+ const orderIdBuf = Buffer.from(orderId.replace('0x', ''), 'hex');
123
+ const targetOrderIdBuf = Buffer.from(targetOrderId.replace('0x', ''), 'hex');
124
+ const isOrderMatch = orderIdBuf.length === targetOrderIdBuf.length && crypto_1.default.timingSafeEqual(orderIdBuf, targetOrderIdBuf);
115
125
  if (isMerchantMatch && isTokenMatch && isAmountMatch) {
116
126
  if (isOrderMatch) {
117
127
  validEventFound = true;
@@ -169,7 +179,9 @@ class PayNodeVerifier {
169
179
  const expectedValue = BigInt(expected.value);
170
180
  const payloadValue = BigInt(value);
171
181
  // 2. 基础字段与金额校验 (防粉尘攻击)
172
- if (to.toLowerCase() !== expected.to.toLowerCase()) {
182
+ const toBuf = Buffer.from(to.toLowerCase().replace('0x', ''), 'hex');
183
+ const expectedToBuf = Buffer.from(expected.to.toLowerCase().replace('0x', ''), 'hex');
184
+ if (toBuf.length !== expectedToBuf.length || !crypto_1.default.timingSafeEqual(toBuf, expectedToBuf)) {
173
185
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.InvalidReceipt, "Recipient mismatch") };
174
186
  }
175
187
  if (payloadValue < expectedValue) {
@@ -202,7 +214,9 @@ class PayNodeVerifier {
202
214
  ]
203
215
  };
204
216
  const recoveredAddress = ethers_1.ethers.verifyTypedData(domain, types, authorization, signature);
205
- if (recoveredAddress.toLowerCase() !== from.toLowerCase()) {
217
+ const recoveredBuf = Buffer.from(recoveredAddress.toLowerCase().replace('0x', ''), 'hex');
218
+ const fromBuf = Buffer.from(from.toLowerCase().replace('0x', ''), 'hex');
219
+ if (recoveredBuf.length !== fromBuf.length || !crypto_1.default.timingSafeEqual(recoveredBuf, fromBuf)) {
206
220
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.InvalidReceipt, "Invalid signature: recovered address mismatch") };
207
221
  }
208
222
  // 4. 内存幂等性校验 (防高频重放)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paynodelabs/sdk-js",
3
- "version": "2.2.3",
3
+ "version": "2.4.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",