@paynodelabs/sdk-js 2.0.1 → 2.1.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
@@ -33,7 +33,7 @@ async function main() {
33
33
  main();
34
34
  ```
35
35
 
36
- ### Key Features (v2.0)
36
+ ### Key Features (v2.1)
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`.
package/dist/client.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare class PayNodeAgentClient {
7
7
  private provider;
8
8
  private rpcUrls;
9
9
  private maxRetries;
10
+ private nonceLock;
10
11
  private ERC20_ABI;
11
12
  private ROUTER_ABI;
12
13
  constructor(privateKey: string, rpcUrls?: string | string[], maxRetries?: number);
@@ -15,12 +16,13 @@ export declare class PayNodeAgentClient {
15
16
  private _handleX402V2;
16
17
  signTransferWithAuthorization(tokenAddr: string, to: string, amount: bigint, validAfter: number, validBefore: number, nonce: string, extra?: Record<string, any>): Promise<ExactEVMPayload>;
17
18
  pay(contractAddr: string, tokenAddr: string, merchantAddr: string, amount: bigint, orderId: string): Promise<string>;
18
- payWithPermit(contractAddr: string, tokenAddr: string, merchantAddr: string, amount: bigint, orderId: string): Promise<string>;
19
- signPermit(tokenAddr: string, spenderAddr: string, amount: bigint, deadlineSeconds?: number): Promise<{
19
+ payWithPermit(contractAddr: string, tokenAddr: string, merchantAddr: string, amount: bigint, orderId: string, version?: string): Promise<string>;
20
+ signPermit(tokenAddr: string, spenderAddr: string, amount: bigint, deadlineSeconds?: number, version?: string): Promise<{
20
21
  deadline: number;
21
22
  v: 27 | 28;
22
23
  r: string;
23
24
  s: string;
24
25
  }>;
26
+ private _lockNonce;
25
27
  }
26
28
  //# sourceMappingURL=client.d.ts.map
@@ -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;IAE3B,OAAO,CAAC,SAAS,CAMf;IAEF,OAAO,CAAC,UAAU,CAGhB;gBAEU,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;YAiDjE,aAAa;IAqFrB,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;IAepH,aAAa,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;IAwB9H,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAE,MAAa;;;;;;CAwCxG"}
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"}
package/dist/client.js CHANGED
@@ -10,6 +10,7 @@ class PayNodeAgentClient {
10
10
  provider;
11
11
  rpcUrls;
12
12
  maxRetries;
13
+ nonceLock = Promise.resolve();
13
14
  ERC20_ABI = [
14
15
  "function approve(address spender, uint256 value) public returns (bool)",
15
16
  "function allowance(address owner, address spender) public view returns (uint256)",
@@ -17,10 +18,7 @@ class PayNodeAgentClient {
17
18
  "function name() view returns (string)",
18
19
  "function nonces(address owner) view returns (uint256)"
19
20
  ];
20
- ROUTER_ABI = [
21
- "function pay(address token, address merchant, uint256 amount, bytes32 orderId) public",
22
- "function payWithPermit(address payer, address token, address merchant, uint256 amount, bytes32 orderId, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public"
23
- ];
21
+ ROUTER_ABI = constants_1.PAYNODE_ROUTER_ABI;
24
22
  constructor(privateKey, rpcUrls = constants_1.BASE_RPC_URLS, maxRetries = 3) {
25
23
  this.rpcUrls = Array.isArray(rpcUrls) ? rpcUrls : [rpcUrls];
26
24
  this.maxRetries = maxRetries;
@@ -38,7 +36,7 @@ class PayNodeAgentClient {
38
36
  for (let attempt = 0; attempt < this.maxRetries; attempt++) {
39
37
  try {
40
38
  const response = await fetch(url, options);
41
- if (!RETRYABLE_STATUS_CODES.has(response.status)) {
39
+ if (!response || !RETRYABLE_STATUS_CODES.has(response.status)) {
42
40
  return response;
43
41
  }
44
42
  if (attempt < this.maxRetries - 1) {
@@ -82,7 +80,10 @@ class PayNodeAgentClient {
82
80
  }
83
81
  else if (b64Required) {
84
82
  try {
85
- body = JSON.parse(Buffer.from(b64Required, 'base64').toString());
83
+ const decoded = typeof globalThis.Buffer !== 'undefined'
84
+ ? globalThis.Buffer.from(b64Required, 'base64').toString()
85
+ : atob(b64Required);
86
+ body = JSON.parse(decoded);
86
87
  }
87
88
  catch (e) {
88
89
  console.debug('⚠️ [PayNode-JS] Failed to parse X-402-Required header:', e);
@@ -112,7 +113,12 @@ class PayNodeAgentClient {
112
113
  // Select suitable requirement
113
114
  const requirement = requirements.accepts.find((req) => req.network === caip2ChainId);
114
115
  if (!requirement) {
115
- throw new errors_1.PayNodeException(errors_1.ErrorCode.InternalError, `No compatible payment requirement found for network ${caip2ChainId}`);
116
+ throw new errors_1.PayNodeException(errors_1.ErrorCode.TransactionFailed, `No compatible payment requirement found for network ${caip2ChainId}`);
117
+ }
118
+ // 🛡️ Token Whitelist Check (Case-insensitive)
119
+ const chainTokens = constants_1.ACCEPTED_TOKENS[chainId]?.map(t => t.toLowerCase());
120
+ if (chainTokens && !chainTokens.includes(requirement.asset.toLowerCase())) {
121
+ throw new errors_1.PayNodeException(errors_1.ErrorCode.TokenNotAccepted, `Token ${requirement.asset} is not in the whitelist for chain ${chainId}`);
116
122
  }
117
123
  const orderId = requirement.orderId || requirements.orderId || url;
118
124
  // Dust limit check
@@ -144,10 +150,16 @@ class PayNodeAgentClient {
144
150
  const allowance = await tokenContract.allowance(this.wallet.address, routerAddr);
145
151
  let txHash;
146
152
  if (allowance >= amount) {
147
- txHash = await this.pay(routerAddr, requirement.asset, requirement.payTo, amount, orderId);
153
+ try {
154
+ txHash = await this.pay(routerAddr, requirement.asset, requirement.payTo, amount, orderId);
155
+ }
156
+ catch (e) {
157
+ console.warn(`⚠️ [PayNode-JS] Direct pay failed (possibly allowance race), falling back to permit:`, e);
158
+ txHash = await this.payWithPermit(routerAddr, requirement.asset, requirement.payTo, amount, orderId, requirement.extra?.version || '2');
159
+ }
148
160
  }
149
161
  else {
150
- txHash = await this.payWithPermit(routerAddr, requirement.asset, requirement.payTo, amount, orderId);
162
+ txHash = await this.payWithPermit(routerAddr, requirement.asset, requirement.payTo, amount, orderId, requirement.extra?.version || '2');
151
163
  }
152
164
  payload = {
153
165
  version: "3.1",
@@ -156,7 +168,10 @@ class PayNodeAgentClient {
156
168
  payload: { txHash }
157
169
  };
158
170
  }
159
- const b64Payload = Buffer.from(JSON.stringify(payload)).toString('base64');
171
+ const json = JSON.stringify(payload);
172
+ const b64Payload = typeof globalThis.Buffer !== 'undefined'
173
+ ? globalThis.Buffer.from(json).toString('base64')
174
+ : btoa(json);
160
175
  const retryOptions = {
161
176
  ...options,
162
177
  headers: {
@@ -166,7 +181,11 @@ class PayNodeAgentClient {
166
181
  'X-402-Order-Id': orderId
167
182
  }
168
183
  };
169
- return await this._fetchWithRetry(url, retryOptions);
184
+ const retryResponse = await this._fetchWithRetry(url, retryOptions);
185
+ if (retryResponse.status === 402) {
186
+ throw new errors_1.PayNodeException(errors_1.ErrorCode.TransactionFailed, "Still 402 after payment attempt. The server may have rejected the payment or authorization.");
187
+ }
188
+ return retryResponse;
170
189
  }
171
190
  async signTransferWithAuthorization(tokenAddr, to, amount, validAfter, validBefore, nonce, extra = {}) {
172
191
  const network = await this.provider.getNetwork();
@@ -208,28 +227,42 @@ class PayNodeAgentClient {
208
227
  };
209
228
  }
210
229
  async pay(contractAddr, tokenAddr, merchantAddr, amount, orderId) {
211
- const router = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
212
- const orderIdBytes = ethers_1.ethers.id(orderId);
213
- const feeData = await this.provider.getFeeData();
214
- const gasPrice = (feeData.gasPrice * 120n) / 100n;
215
- const tx = await router.pay(tokenAddr, merchantAddr, amount, orderIdBytes, {
216
- gasPrice,
217
- gasLimit: 200000
218
- });
219
- const receipt = await tx.wait();
220
- return receipt.hash;
230
+ const unlock = await this._lockNonce();
231
+ try {
232
+ const router = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
233
+ // convention: we hash the raw orderId string to bytes32 internally
234
+ const orderIdBytes = ethers_1.ethers.id(orderId);
235
+ const feeData = await this.provider.getFeeData();
236
+ const gasPrice = (feeData.gasPrice * 120n) / 100n;
237
+ const tx = await router.pay(tokenAddr, merchantAddr, amount, orderIdBytes, {
238
+ gasPrice,
239
+ gasLimit: 200000
240
+ });
241
+ const receipt = await tx.wait();
242
+ return receipt.hash;
243
+ }
244
+ finally {
245
+ unlock();
246
+ }
221
247
  }
222
- async payWithPermit(contractAddr, tokenAddr, merchantAddr, amount, orderId) {
223
- const sig = await this.signPermit(tokenAddr, contractAddr, amount);
224
- const router = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
225
- const orderIdBytes = ethers_1.ethers.id(orderId);
226
- const feeData = await this.provider.getFeeData();
227
- const gasPrice = (feeData.gasPrice * 120n) / 100n;
228
- const tx = await router.payWithPermit(this.wallet.address, tokenAddr, merchantAddr, amount, orderIdBytes, sig.deadline, sig.v, sig.r, sig.s, { gasPrice, gasLimit: 300000 });
229
- const receipt = await tx.wait();
230
- return receipt.hash;
248
+ async payWithPermit(contractAddr, tokenAddr, merchantAddr, amount, orderId, version = '2') {
249
+ const unlock = await this._lockNonce();
250
+ try {
251
+ const sig = await this.signPermit(tokenAddr, contractAddr, amount, 3600, version);
252
+ const router = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
253
+ // convention: we hash the raw orderId string to bytes32 internally
254
+ const orderIdBytes = ethers_1.ethers.id(orderId);
255
+ const feeData = await this.provider.getFeeData();
256
+ const gasPrice = (feeData.gasPrice * 120n) / 100n;
257
+ const tx = await router.payWithPermit(this.wallet.address, tokenAddr, merchantAddr, amount, orderIdBytes, sig.deadline, sig.v, sig.r, sig.s, { gasPrice, gasLimit: 300000 });
258
+ const receipt = await tx.wait();
259
+ return receipt.hash;
260
+ }
261
+ finally {
262
+ unlock();
263
+ }
231
264
  }
232
- async signPermit(tokenAddr, spenderAddr, amount, deadlineSeconds = 3600) {
265
+ async signPermit(tokenAddr, spenderAddr, amount, deadlineSeconds = 3600, version = '2') {
233
266
  const deadline = Math.floor(Date.now() / 1000) + deadlineSeconds;
234
267
  const token = new ethers_1.ethers.Contract(tokenAddr, this.ERC20_ABI, this.wallet);
235
268
  const [name, nonce, network] = await Promise.all([
@@ -239,7 +272,7 @@ class PayNodeAgentClient {
239
272
  ]);
240
273
  const domain = {
241
274
  name,
242
- version: '1',
275
+ version,
243
276
  chainId: Number(network.chainId),
244
277
  verifyingContract: tokenAddr
245
278
  };
@@ -263,5 +296,15 @@ class PayNodeAgentClient {
263
296
  const { v, r, s } = ethers_1.ethers.Signature.from(signature);
264
297
  return { deadline, v, r, s };
265
298
  }
299
+ async _lockNonce() {
300
+ let resolver;
301
+ const nextLock = new Promise((resolve) => {
302
+ resolver = resolve;
303
+ });
304
+ const currentLock = this.nonceLock;
305
+ this.nonceLock = nextLock;
306
+ await currentLock;
307
+ return resolver;
308
+ }
266
309
  }
267
310
  exports.PayNodeAgentClient = PayNodeAgentClient;
@@ -2,11 +2,12 @@
2
2
  export declare const PAYNODE_ROUTER_ADDRESS = "0x4A73696ccF76E7381b044cB95127B3784369Ed63";
3
3
  export declare const PAYNODE_ROUTER_ADDRESS_SANDBOX = "0x24cD8b68aaC209217ff5a6ef1Bf55a59f2c8Ca6F";
4
4
  export declare const BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
5
- export declare const BASE_USDC_ADDRESS_SANDBOX = "0x109AEddD656Ed2761d1e210E179329105039c784";
5
+ export declare const BASE_USDC_ADDRESS_SANDBOX = "0x65c088EfBDB0E03185Dbe8e258Ad0cf4Ab7946b0";
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
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
13
  //# 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"}
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"}
package/dist/constants.js CHANGED
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ACCEPTED_TOKENS = exports.BASE_RPC_URLS_SANDBOX = exports.BASE_RPC_URLS = 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.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 scripts/sync-config.py */
5
5
  exports.PAYNODE_ROUTER_ADDRESS = "0x4A73696ccF76E7381b044cB95127B3784369Ed63";
6
6
  exports.PAYNODE_ROUTER_ADDRESS_SANDBOX = "0x24cD8b68aaC209217ff5a6ef1Bf55a59f2c8Ca6F";
7
7
  exports.BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
8
- exports.BASE_USDC_ADDRESS_SANDBOX = "0x109AEddD656Ed2761d1e210E179329105039c784";
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);
@@ -13,5 +13,6 @@ exports.BASE_RPC_URLS = ["https://mainnet.base.org", "https://base.meowrpc.com",
13
13
  exports.BASE_RPC_URLS_SANDBOX = ["https://sepolia.base.org", "https://base-sepolia-rpc.publicnode.com"];
14
14
  exports.ACCEPTED_TOKENS = {
15
15
  8453: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
16
- 84532: ["0x109AEddD656Ed2761d1e210E179329105039c784"]
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)"];
@@ -15,6 +15,4 @@ export interface PayNodeMiddlewareOptions {
15
15
  maxTimeoutSeconds?: number;
16
16
  }
17
17
  export declare const x402Gate: (options: PayNodeMiddlewareOptions) => (req: Request | any, res: Response | any, next: NextFunction) => Promise<any>;
18
- /** @deprecated Use x402Gate instead. */
19
- export declare const x402_gate: (options: PayNodeMiddlewareOptions) => (req: Request | any, res: Response | any, next: NextFunction) => Promise<any>;
20
18
  //# sourceMappingURL=x402.d.ts.map
@@ -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,MAwB1C,KAAK,OAAO,GAAG,GAAG,EAAE,KAAK,QAAQ,GAAG,GAAG,EAAE,MAAM,YAAY,iBA6F1E,CAAC;AAEF,wCAAwC;AACxC,eAAO,MAAM,SAAS,YAxHY,wBAAwB,MAwB1C,KAAK,OAAO,GAAG,GAAG,EAAE,KAAK,QAAQ,GAAG,GAAG,EAAE,MAAM,YAAY,iBAgG1C,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,iBA8F1E,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.x402_gate = exports.x402Gate = void 0;
3
+ exports.x402Gate = void 0;
4
4
  const errors_1 = require("../errors");
5
5
  const verifier_1 = require("../utils/verifier");
6
6
  const ethers_1 = require("ethers");
@@ -23,7 +23,12 @@ const x402Gate = (options) => {
23
23
  rawAmount = (0, ethers_1.parseUnits)(options.price, decimals);
24
24
  }
25
25
  catch (e) {
26
- rawAmount = BigInt(Math.floor(parseFloat(options.price) * (10 ** decimals)));
26
+ // Robust fallback for non-standard number strings (avoiding floating point math)
27
+ const parts = options.price.split('.');
28
+ const integerPart = parts[0] || '0';
29
+ let fractionPart = parts[1] || '0';
30
+ fractionPart = fractionPart.slice(0, decimals).padEnd(decimals, '0');
31
+ rawAmount = BigInt(integerPart + fractionPart);
27
32
  }
28
33
  const defaultOrderIdGen = (req) => `agent_js_${Date.now()}`;
29
34
  return async (req, res, next) => {
@@ -56,7 +61,7 @@ const x402Gate = (options) => {
56
61
  tokenAddress: tokenAddress,
57
62
  amount: rawAmount.toString(),
58
63
  orderId: orderId
59
- }, unifiedPayload.type === 'eip3009' ? unifiedPayload.payload?.extra : {});
64
+ }, unifiedPayload.type === 'eip3009' ? { name: currency, version: "2" } : {});
60
65
  if (result.isValid) {
61
66
  req.paynode = { unifiedPayload, orderId };
62
67
  return next();
@@ -107,10 +112,9 @@ const x402Gate = (options) => {
107
112
  const b64Required = Buffer.from(JSON.stringify(v2Response)).toString('base64');
108
113
  if (res.set) {
109
114
  res.set('X-402-Required', b64Required);
115
+ res.set('X-402-Order-Id', orderId);
110
116
  }
111
117
  return res.status(402).json(v2Response);
112
118
  };
113
119
  };
114
120
  exports.x402Gate = x402Gate;
115
- /** @deprecated Use x402Gate instead. */
116
- exports.x402_gate = exports.x402Gate;
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/utils/idempotency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElE;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,KAAK,CAAkC;;IAOzC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYjE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,OAAO,CAAC,OAAO;CAQhB;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,gBAAgB;IAC5D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAS;gBAEX,WAAW,EAAE,KAAK,EAAE,MAAM,GAAE,MAAsB;IAKxD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMjE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI5C"}
1
+ {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/utils/idempotency.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElE;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED;;;;GAIG;AACH,qBAAa,sBAAuB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,KAAK,CAAkC;;IAUzC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYjE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,OAAO,CAAC,OAAO;CAQhB;AAED;;;GAGG;AACH,qBAAa,qBAAsB,YAAW,gBAAgB;IAC5D,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,MAAM,CAAS;gBAEX,WAAW,EAAE,KAAK,EAAE,MAAM,GAAE,MAAsB;IAKxD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMjE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI5C"}
@@ -10,7 +10,10 @@ class MemoryIdempotencyStore {
10
10
  cache = new Map();
11
11
  constructor() {
12
12
  // Basic cleanup interval
13
- setInterval(() => this.cleanup(), 60000);
13
+ const interval = setInterval(() => this.cleanup(), 60000);
14
+ if (interval.unref) {
15
+ interval.unref();
16
+ }
14
17
  }
15
18
  async checkAndSet(txHash, ttlSeconds) {
16
19
  const now = Math.floor(Date.now() / 1000);
@@ -20,7 +20,7 @@ export declare class PayNodeVerifier {
20
20
  private contractAddress;
21
21
  private chainId?;
22
22
  private store?;
23
- private acceptedTokens?;
23
+ private acceptedTokens;
24
24
  constructor(config: PayNodeVerifierConfig);
25
25
  private static ROUTER_ABI;
26
26
  verify(unifiedPayload: UnifiedPaymentPayload, expected: ExpectedPayment, extra?: any): Promise<{
@@ -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,CAAc;gBAEzB,MAAM,EAAE,qBAAqB;IAmCzC,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;IAkCpD,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;IAiDlH;;;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;CA6F3D"}
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"}
@@ -40,9 +40,10 @@ class PayNodeVerifier {
40
40
  else if (config.chainId) {
41
41
  tokenList = constants_1.ACCEPTED_TOKENS[config.chainId];
42
42
  }
43
- if (tokenList && tokenList.length > 0) {
44
- this.acceptedTokens = new Set(tokenList.map(t => t.toLowerCase()));
43
+ if (!tokenList || tokenList.length === 0) {
44
+ throw new errors_1.PayNodeException(errors_1.ErrorCode.InternalError, "Verifier requires either a valid chainId or acceptedTokens to initialize its whitelist");
45
45
  }
46
+ this.acceptedTokens = new Set(tokenList.map(t => t.toLowerCase()));
46
47
  }
47
48
  static ROUTER_ABI = [
48
49
  "event PaymentReceived(bytes32 indexed orderId, address indexed merchant, address indexed payer, address token, uint256 amount, uint256 fee, uint256 chainId)"
@@ -51,12 +52,8 @@ class PayNodeVerifier {
51
52
  try {
52
53
  const { type, payload, orderId } = unifiedPayload;
53
54
  if (type === 'eip3009') {
54
- const tokenAddr = expected.tokenAddress;
55
- if (!tokenAddr) {
56
- return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.TokenNotAccepted, "tokenAddress is required for eip3009 verification") };
57
- }
58
55
  const actualPayload = payload;
59
- return await this.verifyTransferWithAuthorization(tokenAddr, actualPayload, {
56
+ return await this.verifyTransferWithAuthorization(expected.tokenAddress, actualPayload, {
60
57
  to: expected.merchantAddress,
61
58
  value: expected.amount
62
59
  }, extra);
@@ -85,24 +82,43 @@ class PayNodeVerifier {
85
82
  }
86
83
  async verifyOnchainPayment(txHash, expected) {
87
84
  try {
85
+ // 1. Security Checks
86
+ if (BigInt(expected.amount) < constants_1.MIN_PAYMENT_AMOUNT) {
87
+ return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.AmountTooLow) };
88
+ }
89
+ if (!this.acceptedTokens.has(expected.tokenAddress.toLowerCase())) {
90
+ return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.TokenNotAccepted) };
91
+ }
88
92
  const receipt = await this.provider.getTransactionReceipt(txHash);
89
93
  if (!receipt || receipt.status === 0) {
90
94
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.TransactionNotFound) };
91
95
  }
92
96
  const router = new ethers_1.ethers.Interface(PayNodeVerifier.ROUTER_ABI);
97
+ // convention: we hash the raw orderId string to bytes32 internally
93
98
  const targetOrderId = ethers_1.ethers.id(expected.orderId);
94
99
  let validEventFound = false;
100
+ let routerInteracted = false;
101
+ let orderIdMismatchFound = false;
95
102
  for (const log of receipt.logs) {
96
103
  try {
104
+ if (log.address.toLowerCase() !== this.contractAddress.toLowerCase())
105
+ continue;
106
+ routerInteracted = true;
97
107
  const parsed = router.parseLog(log);
98
108
  if (parsed && parsed.name === 'PaymentReceived') {
99
109
  const { merchant, token, amount, orderId } = parsed.args;
100
- if (merchant.toLowerCase() === expected.merchantAddress.toLowerCase() &&
101
- token.toLowerCase() === expected.tokenAddress.toLowerCase() &&
102
- BigInt(amount) >= BigInt(expected.amount) &&
103
- orderId === targetOrderId) {
104
- validEventFound = true;
105
- break;
110
+ const isMerchantMatch = merchant.toLowerCase() === expected.merchantAddress.toLowerCase();
111
+ const isTokenMatch = token.toLowerCase() === expected.tokenAddress.toLowerCase();
112
+ const isAmountMatch = BigInt(amount) >= BigInt(expected.amount);
113
+ const isOrderMatch = orderId === targetOrderId;
114
+ if (isMerchantMatch && isTokenMatch && isAmountMatch) {
115
+ if (isOrderMatch) {
116
+ validEventFound = true;
117
+ break;
118
+ }
119
+ else {
120
+ orderIdMismatchFound = true;
121
+ }
106
122
  }
107
123
  }
108
124
  }
@@ -110,7 +126,13 @@ class PayNodeVerifier {
110
126
  // Skip
111
127
  }
112
128
  }
129
+ if (!routerInteracted) {
130
+ return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.WrongContract, "Transaction did not interact with the configured PayNodeRouter contract") };
131
+ }
113
132
  if (!validEventFound) {
133
+ if (orderIdMismatchFound) {
134
+ return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.OrderMismatch, "Payment log found but orderId does not match") };
135
+ }
114
136
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.InvalidReceipt, "No matching PaymentReceived event found") };
115
137
  }
116
138
  if (this.store) {
@@ -131,11 +153,18 @@ class PayNodeVerifier {
131
153
  */
132
154
  async verifyTransferWithAuthorization(tokenAddr, payload, expected, extra = {}) {
133
155
  try {
156
+ // 1. Security Checks
157
+ if (BigInt(expected.value) < constants_1.MIN_PAYMENT_AMOUNT) {
158
+ return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.AmountTooLow) };
159
+ }
160
+ if (!this.acceptedTokens.has(tokenAddr.toLowerCase())) {
161
+ return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.TokenNotAccepted) };
162
+ }
134
163
  const { signature, authorization } = payload;
135
164
  const { from, to, value, validAfter, validBefore, nonce } = authorization;
136
165
  const expectedValue = BigInt(expected.value);
137
166
  const payloadValue = BigInt(value);
138
- // 1. 基础字段与金额校验 (防粉尘攻击)
167
+ // 2. 基础字段与金额校验 (防粉尘攻击)
139
168
  if (to.toLowerCase() !== expected.to.toLowerCase()) {
140
169
  return { isValid: false, error: new errors_1.PayNodeException(errors_1.ErrorCode.InvalidReceipt, "Recipient mismatch") };
141
170
  }
@@ -8,7 +8,7 @@ export interface WebhookConfig {
8
8
  contractAddress?: string;
9
9
  /** The merchant's webhook endpoint URL */
10
10
  webhookUrl: string;
11
- /** Secret key for HMAC-SHA256 signature (header: x-paynode-signature) */
11
+ /** Secret key for HMAC-SHA256 signature (header: X-402-Signature) */
12
12
  webhookSecret: string;
13
13
  /** Optional: chain ID for payload enrichment */
14
14
  chainId?: number;
@@ -50,7 +50,7 @@ export interface PaymentEvent {
50
50
  * ```ts
51
51
  * const notifier = new PayNodeWebhookNotifier({
52
52
  * rpcUrl: 'https://mainnet.base.org',
53
- * contractAddress: '0x92e20164FC457a2aC35f53D06268168e6352b200',
53
+ * contractAddress: '0x4A73696ccF76E7381b044cB95127B3784369Ed63',
54
54
  * webhookUrl: 'https://myshop.com/api/paynode-webhook',
55
55
  * webhookSecret: 'whsec_mysecretkey123',
56
56
  * });
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/utils/webhook.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,uDAAuD;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACtD,0DAA0D;IAC1D,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,MAAM,CAAoG;IAClH,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,YAAY,CAAkB;gBAE1B,MAAM,EAAE,aAAa;IAgBjC;;;OAGG;IACG,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9C;;OAEG;IACH,IAAI,IAAI,IAAI;YAQE,IAAI;IAgClB,OAAO,CAAC,UAAU;IAmBlB;;OAEG;YACW,OAAO;CA8CtB"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/utils/webhook.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,aAAa,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,uDAAuD;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACtD,0DAA0D;IAC1D,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,MAAM,CAAoG;IAClH,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,YAAY,CAAkB;gBAE1B,MAAM,EAAE,aAAa;IAgBjC;;;OAGG;IACG,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY9C;;OAEG;IACH,IAAI,IAAI,IAAI;YAQE,IAAI;IAgClB,OAAO,CAAC,UAAU;IAmBlB;;OAEG;YACW,OAAO;CA8CtB"}
@@ -54,7 +54,7 @@ const PAYNODE_ABI = [
54
54
  * ```ts
55
55
  * const notifier = new PayNodeWebhookNotifier({
56
56
  * rpcUrl: 'https://mainnet.base.org',
57
- * contractAddress: '0x92e20164FC457a2aC35f53D06268168e6352b200',
57
+ * contractAddress: '0x4A73696ccF76E7381b044cB95127B3784369Ed63',
58
58
  * webhookUrl: 'https://myshop.com/api/paynode-webhook',
59
59
  * webhookSecret: 'whsec_mysecretkey123',
60
60
  * });
@@ -169,9 +169,9 @@ class PayNodeWebhookNotifier {
169
169
  .digest('hex');
170
170
  const headers = {
171
171
  'Content-Type': 'application/json',
172
- 'x-paynode-signature': `sha256=${signature}`,
173
- 'x-paynode-event': 'payment.received',
174
- 'x-paynode-delivery-id': `${event.txHash}-${attempt}`,
172
+ 'X-402-Signature': `sha256=${signature}`,
173
+ 'X-402-Event': 'payment.received',
174
+ 'X-402-Delivery-Id': `${event.txHash}-${attempt}`,
175
175
  ...(this.config.customHeaders || {})
176
176
  };
177
177
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paynodelabs/sdk-js",
3
- "version": "2.0.1",
3
+ "version": "2.1.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",
@@ -39,5 +39,8 @@
39
39
  "ts-jest": "^29.1.4",
40
40
  "ts-node": "^10.9.2",
41
41
  "typescript": "^5.4.5"
42
+ },
43
+ "overrides": {
44
+ "brace-expansion": "^5.0.5"
42
45
  }
43
46
  }