@paynodelabs/sdk-js 1.1.0 → 1.1.1
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/LICENSE +21 -0
- package/README.md +47 -18
- package/dist/client.d.ts +5 -26
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +80 -71
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +8 -1
- package/dist/errors/index.d.ts +15 -6
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +23 -6
- package/dist/utils/verifier.d.ts +4 -6
- package/dist/utils/verifier.d.ts.map +1 -1
- package/dist/utils/verifier.js +22 -26
- package/package.json +8 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PayNode Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -21,35 +21,64 @@ npm install @paynodelabs/sdk-js ethers
|
|
|
21
21
|
### Agent Client (Payer)
|
|
22
22
|
|
|
23
23
|
```typescript
|
|
24
|
-
import {
|
|
24
|
+
import { PayNodeAgentClient } from "@paynodelabs/sdk-js";
|
|
25
25
|
|
|
26
|
-
const client = new
|
|
26
|
+
const client = new PayNodeAgentClient("YOUR_AGENT_PRIVATE_KEY", ["https://mainnet.base.org", "https://rpc.ankr.com/base"]);
|
|
27
27
|
|
|
28
28
|
async function main() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Automatically handles 402 challenges, pays USDC, and retries the request
|
|
30
|
+
const response = await client.requestGate("https://api.merchant.com/premium-data");
|
|
31
|
+
console.log(await response.text());
|
|
32
32
|
}
|
|
33
33
|
main();
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
## 🚀 Run the Demo
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
import express from 'express';
|
|
40
|
-
import { createPayNodeMiddleware } from '@paynodelabs/sdk-js';
|
|
38
|
+
The SDK includes a full merchant/agent demonstration in the `examples/` directory.
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
### 1. Setup Environment
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
```bash
|
|
43
|
+
cp .env.example .env
|
|
44
|
+
# Edit .env with your private key and RPC URLs
|
|
45
|
+
```
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
### 2. Run the Merchant Server (Express)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx ts-node examples/express-server.ts
|
|
52
51
|
```
|
|
53
52
|
|
|
53
|
+
### 3. Run the Agent Client
|
|
54
|
+
|
|
55
|
+
In another terminal:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx ts-node examples/agent-client.ts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The demo will perform a full loop: `402 Handshake -> On-chain Payment -> 200 Verification`.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 📦 Publishing to NPM
|
|
66
|
+
|
|
67
|
+
To publish a new version of the SDK:
|
|
68
|
+
|
|
69
|
+
1. **Build the project**:
|
|
70
|
+
```bash
|
|
71
|
+
npm run build
|
|
72
|
+
```
|
|
73
|
+
2. **Login to NPM** (if not already):
|
|
74
|
+
```bash
|
|
75
|
+
npm login
|
|
76
|
+
```
|
|
77
|
+
3. **Publish**:
|
|
78
|
+
```bash
|
|
79
|
+
npm publish --access public
|
|
80
|
+
```
|
|
81
|
+
|
|
54
82
|
---
|
|
55
|
-
|
|
83
|
+
|
|
84
|
+
_Built for the Autonomous AI Economy by PayNodeLabs._
|
package/dist/client.d.ts
CHANGED
|
@@ -4,38 +4,17 @@ export interface RequestOptions extends RequestInit {
|
|
|
4
4
|
export declare class PayNodeAgentClient {
|
|
5
5
|
private wallet;
|
|
6
6
|
private provider;
|
|
7
|
+
private rpcUrls;
|
|
7
8
|
private ERC20_ABI;
|
|
8
9
|
private ROUTER_ABI;
|
|
9
|
-
constructor(privateKey: string,
|
|
10
|
-
/**
|
|
11
|
-
* Executes a fetch request and automatically handles the 402 Payment loop if encountered.
|
|
12
|
-
*/
|
|
10
|
+
constructor(privateKey: string, rpcUrls: string | string[]);
|
|
13
11
|
requestGate(url: string, options?: RequestOptions): Promise<Response>;
|
|
14
12
|
private handlePaymentAndRetry;
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
* Executes a payment using EIP-2612 Permit — single-tx approve + pay.
|
|
18
|
-
* The payer signs the permit offline, and any relayer (e.g. AI Agent) can submit it on-chain.
|
|
19
|
-
* @param contractAddr PayNode Router address
|
|
20
|
-
* @param payerAddress The address that holds the tokens and signed the permit
|
|
21
|
-
* @param tokenAddr ERC20 token with EIP-2612 support (e.g. USDC)
|
|
22
|
-
* @param merchantAddr Merchant receiving 99% of payment
|
|
23
|
-
* @param amount Token amount in smallest unit (e.g. 1000000 = 1 USDC)
|
|
24
|
-
* @param orderId Order identifier as bytes32
|
|
25
|
-
* @param deadline Unix timestamp after which the permit is invalid
|
|
26
|
-
* @param v ECDSA recovery id
|
|
27
|
-
* @param r ECDSA signature component
|
|
28
|
-
* @param s ECDSA signature component
|
|
29
|
-
*/
|
|
30
|
-
payWithPermit(contractAddr: string, payerAddress: string, tokenAddr: string, merchantAddr: string, amount: bigint, orderId: string, deadline: number, v: number, r: string, s: string): Promise<string>;
|
|
31
|
-
/**
|
|
32
|
-
* Helper: Generate an EIP-2612 Permit signature for USDC/ERC20.
|
|
33
|
-
* The wallet that calls this must be the token holder (payer).
|
|
34
|
-
* @returns { deadline, v, r, s } to pass to payWithPermit
|
|
35
|
-
*/
|
|
13
|
+
private executeStandardPay;
|
|
14
|
+
private executePermitPay;
|
|
36
15
|
signPermit(tokenAddr: string, spenderAddr: string, amount: bigint, deadlineSeconds?: number): Promise<{
|
|
37
16
|
deadline: number;
|
|
38
|
-
v:
|
|
17
|
+
v: 27 | 28;
|
|
39
18
|
r: string;
|
|
40
19
|
s: string;
|
|
41
20
|
}>;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,OAAO,CAAW;IAE1B,OAAO,CAAC,SAAS,CAMf;IAEF,OAAO,CAAC,UAAU,CAGhB;gBAEU,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAcpD,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC;YA0BjE,qBAAqB;YAwDrB,kBAAkB;YAelB,gBAAgB;IAwBxB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAE,MAAa;;;;;;CAwCxG"}
|
package/dist/client.js
CHANGED
|
@@ -2,25 +2,33 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PayNodeAgentClient = void 0;
|
|
4
4
|
const ethers_1 = require("ethers");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
5
6
|
class PayNodeAgentClient {
|
|
6
7
|
wallet;
|
|
7
8
|
provider;
|
|
9
|
+
rpcUrls;
|
|
8
10
|
ERC20_ABI = [
|
|
9
11
|
"function approve(address spender, uint256 value) public returns (bool)",
|
|
10
12
|
"function allowance(address owner, address spender) public view returns (uint256)",
|
|
11
|
-
"function balanceOf(address account) public view returns (uint256)"
|
|
13
|
+
"function balanceOf(address account) public view returns (uint256)",
|
|
14
|
+
"function name() view returns (string)",
|
|
15
|
+
"function nonces(address owner) view returns (uint256)"
|
|
12
16
|
];
|
|
13
17
|
ROUTER_ABI = [
|
|
14
18
|
"function pay(address token, address merchant, uint256 amount, bytes32 orderId) public",
|
|
15
19
|
"function payWithPermit(address payer, address token, address merchant, uint256 amount, bytes32 orderId, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public"
|
|
16
20
|
];
|
|
17
|
-
constructor(privateKey,
|
|
18
|
-
this.
|
|
21
|
+
constructor(privateKey, rpcUrls) {
|
|
22
|
+
this.rpcUrls = Array.isArray(rpcUrls) ? rpcUrls : [rpcUrls];
|
|
23
|
+
const configs = this.rpcUrls.map((url, index) => ({
|
|
24
|
+
provider: new ethers_1.ethers.JsonRpcProvider(url),
|
|
25
|
+
priority: index,
|
|
26
|
+
weight: 1,
|
|
27
|
+
stallTimeout: 3000
|
|
28
|
+
}));
|
|
29
|
+
this.provider = new ethers_1.ethers.FallbackProvider(configs);
|
|
19
30
|
this.wallet = new ethers_1.ethers.Wallet(privateKey, this.provider);
|
|
20
31
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Executes a fetch request and automatically handles the 402 Payment loop if encountered.
|
|
23
|
-
*/
|
|
24
32
|
async requestGate(url, options = {}) {
|
|
25
33
|
const fetchOptions = { ...options };
|
|
26
34
|
if (options.json && !fetchOptions.body) {
|
|
@@ -30,12 +38,19 @@ class PayNodeAgentClient {
|
|
|
30
38
|
...fetchOptions.headers
|
|
31
39
|
};
|
|
32
40
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
try {
|
|
42
|
+
let response = await fetch(url, fetchOptions);
|
|
43
|
+
if (response.status === 402) {
|
|
44
|
+
console.log(`💡 [PayNode-JS] 402 Payment Required detected. Handling autonomous payment...`);
|
|
45
|
+
return await this.handlePaymentAndRetry(url, fetchOptions, response.headers);
|
|
46
|
+
}
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof errors_1.PayNodeException)
|
|
51
|
+
throw error;
|
|
52
|
+
throw new errors_1.PayNodeException(`Failed to connect to any provided RPC nodes.`, errors_1.ErrorCode.RPC_ERROR, error);
|
|
37
53
|
}
|
|
38
|
-
return response;
|
|
39
54
|
}
|
|
40
55
|
async handlePaymentAndRetry(url, options, headers) {
|
|
41
56
|
const contractAddr = headers.get('x-paynode-contract');
|
|
@@ -44,11 +59,37 @@ class PayNodeAgentClient {
|
|
|
44
59
|
const tokenAddr = headers.get('x-paynode-token-address');
|
|
45
60
|
const orderIdStr = headers.get('x-paynode-order-id');
|
|
46
61
|
if (!contractAddr || !merchantAddr || !amountStr || !tokenAddr || !orderIdStr) {
|
|
47
|
-
throw new
|
|
62
|
+
throw new errors_1.PayNodeException("Malformed 402 headers: missing metadata", errors_1.ErrorCode.INTERNAL_ERROR);
|
|
48
63
|
}
|
|
49
64
|
const amount = BigInt(amountStr);
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
// v1.3 Constraint: Min payment protection
|
|
66
|
+
if (amount < 1000n) {
|
|
67
|
+
throw new errors_1.PayNodeException("Payment amount is below the protocol minimum (1000).", errors_1.ErrorCode.AMOUNT_TOO_LOW);
|
|
68
|
+
}
|
|
69
|
+
let txHash;
|
|
70
|
+
try {
|
|
71
|
+
const tokenContract = new ethers_1.ethers.Contract(tokenAddr, this.ERC20_ABI, this.wallet);
|
|
72
|
+
const [balance, allowance] = await Promise.all([
|
|
73
|
+
tokenContract.balanceOf(this.wallet.address),
|
|
74
|
+
tokenContract.allowance(this.wallet.address, contractAddr)
|
|
75
|
+
]);
|
|
76
|
+
if (balance < amount) {
|
|
77
|
+
throw new errors_1.PayNodeException("Wallet lacks USDC or ETH for gas.", errors_1.ErrorCode.INSUFFICIENT_FUNDS);
|
|
78
|
+
}
|
|
79
|
+
// Protocol v1.3: Permit-First Execution
|
|
80
|
+
if (allowance >= amount) {
|
|
81
|
+
txHash = await this.executeStandardPay(contractAddr, tokenAddr, merchantAddr, amount, orderIdStr);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(`⚡ [PayNode-JS] Insufficient allowance. Attempting Permit-First payment...`);
|
|
85
|
+
txHash = await this.executePermitPay(contractAddr, tokenAddr, merchantAddr, amount, orderIdStr);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof errors_1.PayNodeException)
|
|
90
|
+
throw error;
|
|
91
|
+
throw new errors_1.PayNodeException(`On-chain transaction reverted or failed.`, errors_1.ErrorCode.TRANSACTION_FAILED, error);
|
|
92
|
+
}
|
|
52
93
|
console.log(`✅ [PayNode-JS] Payment confirmed on-chain: ${txHash}`);
|
|
53
94
|
const retryOptions = {
|
|
54
95
|
...options,
|
|
@@ -60,72 +101,40 @@ class PayNodeAgentClient {
|
|
|
60
101
|
};
|
|
61
102
|
return await fetch(url, retryOptions);
|
|
62
103
|
}
|
|
63
|
-
async
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const currentAllowance = await tokenContract.allowance(this.wallet.address, contractAddr);
|
|
72
|
-
if (currentAllowance < amount) {
|
|
73
|
-
console.log(`🔐 [PayNode-JS] Allowance too low (${currentAllowance}). Granting Infinite Approval to Router...`);
|
|
74
|
-
const approveTx = await tokenContract.approve(contractAddr, ethers_1.ethers.MaxUint256);
|
|
75
|
-
await approveTx.wait();
|
|
76
|
-
console.log(`🔓 [PayNode-JS] Infinite Approval confirmed.`);
|
|
77
|
-
}
|
|
78
|
-
// 3. Execute Payment
|
|
79
|
-
const routerContract = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
|
|
80
|
-
// Use manually specified gas limit to avoid estimateGas issues with some RPCs
|
|
81
|
-
const payTx = await routerContract.pay(tokenAddr, merchantAddr, amount, orderId, {
|
|
82
|
-
gasLimit: 200000 // Safe overhead for Base
|
|
104
|
+
async executeStandardPay(contractAddr, tokenAddr, merchantAddr, amount, orderId) {
|
|
105
|
+
const router = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
|
|
106
|
+
const orderIdBytes = ethers_1.ethers.id(orderId);
|
|
107
|
+
const feeData = await this.provider.getFeeData();
|
|
108
|
+
const gasPrice = (feeData.gasPrice * 120n) / 100n; // GasPrice * 1.2
|
|
109
|
+
const tx = await router.pay(tokenAddr, merchantAddr, amount, orderIdBytes, {
|
|
110
|
+
gasPrice,
|
|
111
|
+
gasLimit: 200000
|
|
83
112
|
});
|
|
84
|
-
const receipt = await
|
|
113
|
+
const receipt = await tx.wait();
|
|
85
114
|
return receipt.hash;
|
|
86
115
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* @param amount Token amount in smallest unit (e.g. 1000000 = 1 USDC)
|
|
95
|
-
* @param orderId Order identifier as bytes32
|
|
96
|
-
* @param deadline Unix timestamp after which the permit is invalid
|
|
97
|
-
* @param v ECDSA recovery id
|
|
98
|
-
* @param r ECDSA signature component
|
|
99
|
-
* @param s ECDSA signature component
|
|
100
|
-
*/
|
|
101
|
-
async payWithPermit(contractAddr, payerAddress, tokenAddr, merchantAddr, amount, orderId, deadline, v, r, s) {
|
|
102
|
-
const routerContract = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
|
|
103
|
-
const tx = await routerContract.payWithPermit(payerAddress, tokenAddr, merchantAddr, amount, orderId, deadline, v, r, s, { gasLimit: 300000 });
|
|
116
|
+
async executePermitPay(contractAddr, tokenAddr, merchantAddr, amount, orderId) {
|
|
117
|
+
const sig = await this.signPermit(tokenAddr, contractAddr, amount);
|
|
118
|
+
const router = new ethers_1.ethers.Contract(contractAddr, this.ROUTER_ABI, this.wallet);
|
|
119
|
+
const orderIdBytes = ethers_1.ethers.id(orderId);
|
|
120
|
+
const feeData = await this.provider.getFeeData();
|
|
121
|
+
const gasPrice = (feeData.gasPrice * 120n) / 100n;
|
|
122
|
+
const tx = await router.payWithPermit(this.wallet.address, tokenAddr, merchantAddr, amount, orderIdBytes, sig.deadline, sig.v, sig.r, sig.s, { gasPrice, gasLimit: 300000 });
|
|
104
123
|
const receipt = await tx.wait();
|
|
105
124
|
return receipt.hash;
|
|
106
125
|
}
|
|
107
|
-
/**
|
|
108
|
-
* Helper: Generate an EIP-2612 Permit signature for USDC/ERC20.
|
|
109
|
-
* The wallet that calls this must be the token holder (payer).
|
|
110
|
-
* @returns { deadline, v, r, s } to pass to payWithPermit
|
|
111
|
-
*/
|
|
112
126
|
async signPermit(tokenAddr, spenderAddr, amount, deadlineSeconds = 3600) {
|
|
113
127
|
const deadline = Math.floor(Date.now() / 1000) + deadlineSeconds;
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
], this.wallet);
|
|
120
|
-
const [name, nonce, chainId] = await Promise.all([
|
|
121
|
-
tokenContract.name(),
|
|
122
|
-
tokenContract.nonces(this.wallet.address),
|
|
123
|
-
this.provider.getNetwork().then(n => n.chainId)
|
|
128
|
+
const token = new ethers_1.ethers.Contract(tokenAddr, this.ERC20_ABI, this.wallet);
|
|
129
|
+
const [name, nonce, network] = await Promise.all([
|
|
130
|
+
token.name(),
|
|
131
|
+
token.nonces(this.wallet.address),
|
|
132
|
+
this.provider.getNetwork()
|
|
124
133
|
]);
|
|
125
134
|
const domain = {
|
|
126
135
|
name,
|
|
127
|
-
version: '
|
|
128
|
-
chainId: Number(chainId),
|
|
136
|
+
version: '1', // USDC on Base uses version 1
|
|
137
|
+
chainId: Number(network.chainId),
|
|
129
138
|
verifyingContract: tokenAddr
|
|
130
139
|
};
|
|
131
140
|
const types = {
|
|
@@ -144,8 +153,8 @@ class PayNodeAgentClient {
|
|
|
144
153
|
nonce,
|
|
145
154
|
deadline
|
|
146
155
|
};
|
|
147
|
-
const
|
|
148
|
-
const { v, r, s } = ethers_1.ethers.Signature.from(
|
|
156
|
+
const signature = await this.wallet.signTypedData(domain, types, value);
|
|
157
|
+
const { v, r, s } = ethers_1.ethers.Signature.from(signature);
|
|
149
158
|
return { deadline, v, r, s };
|
|
150
159
|
}
|
|
151
160
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -5,4 +5,8 @@ export declare const BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA0
|
|
|
5
5
|
export declare const BASE_USDC_ADDRESS_SANDBOX = "0xeAC1f2C7099CdaFfB91Aa3b8Ffd653Ef16935798";
|
|
6
6
|
export declare const PROTOCOL_TREASURY = "0x598bF63F5449876efafa7b36b77Deb2070621C0E";
|
|
7
7
|
export declare const PROTOCOL_FEE_BPS = 100;
|
|
8
|
+
export declare const MIN_PAYMENT_AMOUNT: bigint;
|
|
9
|
+
export declare const BASE_RPC_URLS: string[];
|
|
10
|
+
export declare const BASE_RPC_URLS_SANDBOX: string[];
|
|
11
|
+
export declare const ACCEPTED_TOKENS: Record<number, string[]>;
|
|
8
12
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -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"}
|
|
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"}
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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.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 = "0x92e20164FC457a2aC35f53D06268168e6352b200";
|
|
6
6
|
exports.PAYNODE_ROUTER_ADDRESS_SANDBOX = "0xB587Bc36aaCf65962eCd6Ba59e2DA76f2f575408";
|
|
@@ -8,3 +8,10 @@ exports.BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
|
8
8
|
exports.BASE_USDC_ADDRESS_SANDBOX = "0xeAC1f2C7099CdaFfB91Aa3b8Ffd653Ef16935798";
|
|
9
9
|
exports.PROTOCOL_TREASURY = "0x598bF63F5449876efafa7b36b77Deb2070621C0E";
|
|
10
10
|
exports.PROTOCOL_FEE_BPS = 100;
|
|
11
|
+
exports.MIN_PAYMENT_AMOUNT = BigInt(1000);
|
|
12
|
+
exports.BASE_RPC_URLS = ["https://mainnet.base.org", "https://base.meowrpc.com", "https://1rpc.io/base"];
|
|
13
|
+
exports.BASE_RPC_URLS_SANDBOX = ["https://sepolia.base.org", "https://base-sepolia-rpc.publicnode.com"];
|
|
14
|
+
exports.ACCEPTED_TOKENS = {
|
|
15
|
+
8453: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
|
|
16
|
+
84532: ["0xeAC1f2C7099CdaFfB91Aa3b8Ffd653Ef16935798"]
|
|
17
|
+
};
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
export declare enum ErrorCode {
|
|
2
|
-
|
|
2
|
+
RPC_ERROR = "RPC_ERROR",
|
|
3
|
+
INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS",
|
|
4
|
+
AMOUNT_TOO_LOW = "AMOUNT_TOO_LOW",
|
|
5
|
+
TOKEN_NOT_ACCEPTED = "TOKEN_NOT_ACCEPTED",
|
|
3
6
|
TRANSACTION_FAILED = "TRANSACTION_FAILED",
|
|
7
|
+
DUPLICATE_TRANSACTION = "DUPLICATE_TRANSACTION",
|
|
8
|
+
INVALID_RECEIPT = "INVALID_RECEIPT",
|
|
9
|
+
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
10
|
+
TRANSACTION_NOT_FOUND = "TRANSACTION_NOT_FOUND",
|
|
4
11
|
WRONG_CONTRACT = "WRONG_CONTRACT",
|
|
5
12
|
ORDER_MISMATCH = "ORDER_MISMATCH",
|
|
6
|
-
INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS",
|
|
7
13
|
RECEIPT_ALREADY_USED = "RECEIPT_ALREADY_USED",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
MISSING_RECEIPT = "MISSING_RECEIPT"
|
|
15
|
+
}
|
|
16
|
+
export declare class PayNodeException extends Error {
|
|
17
|
+
message: string;
|
|
18
|
+
code: ErrorCode;
|
|
19
|
+
details?: any | undefined;
|
|
20
|
+
constructor(message: string, code: ErrorCode, details?: any | undefined);
|
|
12
21
|
}
|
|
13
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,qBAAqB,0BAA0B;IAC/C,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,SAAS,cAAc;IACvB,kBAAkB,uBAAuB;IACzC,cAAc,mBAAmB;IACjC,kBAAkB,uBAAuB;IACzC,kBAAkB,uBAAuB;IACzC,qBAAqB,0BAA0B;IAC/C,eAAe,oBAAoB;IACnC,cAAc,mBAAmB;IAEjC,qBAAqB,0BAA0B;IAC/C,cAAc,mBAAmB;IACjC,cAAc,mBAAmB;IACjC,oBAAoB,yBAAyB;IAC7C,eAAe,oBAAoB;CACpC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACtB,OAAO,EAAE,MAAM;IAAS,IAAI,EAAE,SAAS;IAAS,OAAO,CAAC,EAAE,GAAG;gBAA7D,OAAO,EAAE,MAAM,EAAS,IAAI,EAAE,SAAS,EAAS,OAAO,CAAC,EAAE,GAAG,YAAA;CAIjF"}
|
package/dist/errors/index.js
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ErrorCode = void 0;
|
|
3
|
+
exports.PayNodeException = exports.ErrorCode = void 0;
|
|
4
4
|
var ErrorCode;
|
|
5
5
|
(function (ErrorCode) {
|
|
6
|
-
ErrorCode["
|
|
6
|
+
ErrorCode["RPC_ERROR"] = "RPC_ERROR";
|
|
7
|
+
ErrorCode["INSUFFICIENT_FUNDS"] = "INSUFFICIENT_FUNDS";
|
|
8
|
+
ErrorCode["AMOUNT_TOO_LOW"] = "AMOUNT_TOO_LOW";
|
|
9
|
+
ErrorCode["TOKEN_NOT_ACCEPTED"] = "TOKEN_NOT_ACCEPTED";
|
|
7
10
|
ErrorCode["TRANSACTION_FAILED"] = "TRANSACTION_FAILED";
|
|
11
|
+
ErrorCode["DUPLICATE_TRANSACTION"] = "DUPLICATE_TRANSACTION";
|
|
12
|
+
ErrorCode["INVALID_RECEIPT"] = "INVALID_RECEIPT";
|
|
13
|
+
ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
14
|
+
// Verification specific
|
|
15
|
+
ErrorCode["TRANSACTION_NOT_FOUND"] = "TRANSACTION_NOT_FOUND";
|
|
8
16
|
ErrorCode["WRONG_CONTRACT"] = "WRONG_CONTRACT";
|
|
9
17
|
ErrorCode["ORDER_MISMATCH"] = "ORDER_MISMATCH";
|
|
10
|
-
ErrorCode["INSUFFICIENT_FUNDS"] = "INSUFFICIENT_FUNDS";
|
|
11
18
|
ErrorCode["RECEIPT_ALREADY_USED"] = "RECEIPT_ALREADY_USED";
|
|
12
|
-
ErrorCode["INVALID_RECEIPT"] = "INVALID_RECEIPT";
|
|
13
19
|
ErrorCode["MISSING_RECEIPT"] = "MISSING_RECEIPT";
|
|
14
|
-
ErrorCode["TOKEN_NOT_ACCEPTED"] = "TOKEN_NOT_ACCEPTED";
|
|
15
|
-
ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
16
20
|
})(ErrorCode || (exports.ErrorCode = ErrorCode = {}));
|
|
21
|
+
class PayNodeException extends Error {
|
|
22
|
+
message;
|
|
23
|
+
code;
|
|
24
|
+
details;
|
|
25
|
+
constructor(message, code, details) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.message = message;
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.details = details;
|
|
30
|
+
this.name = "PayNodeException";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.PayNodeException = PayNodeException;
|
package/dist/utils/verifier.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PayNodeException } from '../errors';
|
|
2
2
|
import { IdempotencyStore } from './idempotency';
|
|
3
3
|
/**
|
|
4
4
|
* Default accepted token addresses across supported chains.
|
|
@@ -6,6 +6,8 @@ import { IdempotencyStore } from './idempotency';
|
|
|
6
6
|
* preventing fake-token attacks at the verification layer.
|
|
7
7
|
*/
|
|
8
8
|
export declare const ACCEPTED_TOKENS: Record<string, string[]>;
|
|
9
|
+
/** Minimum allowed payment amount to prevent dust exploits (1000 = 0.001 USDC) */
|
|
10
|
+
export declare const MIN_PAYMENT_AMOUNT = 1000n;
|
|
9
11
|
export interface PayNodeVerifierConfig {
|
|
10
12
|
rpcUrls: string | string[];
|
|
11
13
|
chainId?: number;
|
|
@@ -18,7 +20,6 @@ export interface ExpectedPayment {
|
|
|
18
20
|
tokenAddress: string;
|
|
19
21
|
amount: string | number | bigint;
|
|
20
22
|
orderId?: string;
|
|
21
|
-
verifyDepegPrice?: boolean;
|
|
22
23
|
}
|
|
23
24
|
export declare class PayNodeVerifier {
|
|
24
25
|
private provider;
|
|
@@ -28,10 +29,7 @@ export declare class PayNodeVerifier {
|
|
|
28
29
|
constructor(config: PayNodeVerifierConfig);
|
|
29
30
|
verifyPayment(txHash: string, expected: ExpectedPayment): Promise<{
|
|
30
31
|
isValid: boolean;
|
|
31
|
-
error?:
|
|
32
|
-
code: ErrorCode;
|
|
33
|
-
message: string;
|
|
34
|
-
};
|
|
32
|
+
error?: PayNodeException;
|
|
35
33
|
}>;
|
|
36
34
|
}
|
|
37
35
|
//# sourceMappingURL=verifier.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/utils/verifier.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/utils/verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CASpD,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAExC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,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;AAQD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,KAAK,CAAC,CAAmB;IACjC,OAAO,CAAC,cAAc,CAAC,CAAc;gBAEzB,MAAM,EAAE,qBAAqB;IAiCnC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAC;CA6ExH"}
|
package/dist/utils/verifier.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PayNodeVerifier = exports.ACCEPTED_TOKENS = void 0;
|
|
3
|
+
exports.PayNodeVerifier = exports.MIN_PAYMENT_AMOUNT = exports.ACCEPTED_TOKENS = void 0;
|
|
4
4
|
const errors_1 = require("../errors");
|
|
5
5
|
const ethers_1 = require("ethers");
|
|
6
6
|
/**
|
|
@@ -12,13 +12,14 @@ exports.ACCEPTED_TOKENS = {
|
|
|
12
12
|
// Base Mainnet (chainId: 8453)
|
|
13
13
|
'8453': [
|
|
14
14
|
'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
|
|
15
|
-
'0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2', // USDT
|
|
16
15
|
],
|
|
17
16
|
// Base Sepolia (chainId: 84532)
|
|
18
17
|
'84532': [
|
|
19
18
|
'0xeAC1f2C7099CdaFfB91Aa3b8Ffd653Ef16935798', // USDC (Sandbox)
|
|
20
19
|
],
|
|
21
20
|
};
|
|
21
|
+
/** Minimum allowed payment amount to prevent dust exploits (1000 = 0.001 USDC) */
|
|
22
|
+
exports.MIN_PAYMENT_AMOUNT = 1000n;
|
|
22
23
|
const PAYNODE_ABI = [
|
|
23
24
|
"event PaymentReceived(bytes32 indexed orderId, address indexed merchant, address indexed payer, address token, uint256 amount, uint256 fee, uint256 chainId)"
|
|
24
25
|
];
|
|
@@ -30,7 +31,7 @@ class PayNodeVerifier {
|
|
|
30
31
|
acceptedTokens;
|
|
31
32
|
constructor(config) {
|
|
32
33
|
if (!config.rpcUrls || (Array.isArray(config.rpcUrls) && config.rpcUrls.length === 0)) {
|
|
33
|
-
throw new
|
|
34
|
+
throw new errors_1.PayNodeException("Failed to connect to any provided RPC nodes.", errors_1.ErrorCode.RPC_ERROR);
|
|
34
35
|
}
|
|
35
36
|
// Support RpcPool / FallbackProvider
|
|
36
37
|
if (Array.isArray(config.rpcUrls)) {
|
|
@@ -49,8 +50,6 @@ class PayNodeVerifier {
|
|
|
49
50
|
}
|
|
50
51
|
this.chainId = config.chainId;
|
|
51
52
|
this.store = config.store;
|
|
52
|
-
// Build accepted token set: user-provided or chain-default
|
|
53
|
-
// acceptedTokens=undefined → use chain default; acceptedTokens=[] → explicitly disable whitelist
|
|
54
53
|
let tokenList;
|
|
55
54
|
if (config.acceptedTokens !== undefined) {
|
|
56
55
|
tokenList = config.acceptedTokens;
|
|
@@ -64,25 +63,29 @@ class PayNodeVerifier {
|
|
|
64
63
|
}
|
|
65
64
|
async verifyPayment(txHash, expected) {
|
|
66
65
|
try {
|
|
67
|
-
// 0.
|
|
66
|
+
// 0. Dust Exploit Check (Minimum Payment)
|
|
67
|
+
const expectedAmount = BigInt(expected.amount);
|
|
68
|
+
if (expectedAmount < exports.MIN_PAYMENT_AMOUNT) {
|
|
69
|
+
return { isValid: false, error: new errors_1.PayNodeException("Payment amount is below the protocol minimum (1000).", errors_1.ErrorCode.AMOUNT_TOO_LOW) };
|
|
70
|
+
}
|
|
71
|
+
// 1. Token Whitelist Check (Anti-FakeToken)
|
|
68
72
|
if (this.acceptedTokens && !this.acceptedTokens.has(expected.tokenAddress.toLowerCase())) {
|
|
69
|
-
return { isValid: false, error:
|
|
73
|
+
return { isValid: false, error: new errors_1.PayNodeException("The provided token address is not in the whitelist.", errors_1.ErrorCode.TOKEN_NOT_ACCEPTED) };
|
|
70
74
|
}
|
|
71
75
|
// 1. Idempotency Check
|
|
72
76
|
if (this.store) {
|
|
73
|
-
// Assume TTL of 24 hours for replay protection
|
|
74
77
|
const isNew = await this.store.checkAndSet(txHash, 86400);
|
|
75
78
|
if (!isNew) {
|
|
76
|
-
return { isValid: false, error:
|
|
79
|
+
return { isValid: false, error: new errors_1.PayNodeException("This transaction hash has already been consumed.", errors_1.ErrorCode.DUPLICATE_TRANSACTION) };
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
// 2. Fetch Receipt
|
|
80
83
|
const receipt = await this.provider.getTransactionReceipt(txHash);
|
|
81
84
|
if (!receipt) {
|
|
82
|
-
return { isValid: false, error:
|
|
85
|
+
return { isValid: false, error: new errors_1.PayNodeException("The provided receipt (TxHash) is malformed or invalid.", errors_1.ErrorCode.INVALID_RECEIPT) };
|
|
83
86
|
}
|
|
84
87
|
if (receipt.status !== 1) {
|
|
85
|
-
return { isValid: false, error:
|
|
88
|
+
return { isValid: false, error: new errors_1.PayNodeException("On-chain transaction reverted or failed.", errors_1.ErrorCode.TRANSACTION_FAILED) };
|
|
86
89
|
}
|
|
87
90
|
// 3. Parse Logs
|
|
88
91
|
let paymentLog = null;
|
|
@@ -99,39 +102,32 @@ class PayNodeVerifier {
|
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
if (!paymentLog) {
|
|
102
|
-
return { isValid: false, error:
|
|
105
|
+
return { isValid: false, error: new errors_1.PayNodeException("No valid PaymentReceived event found in transaction.", errors_1.ErrorCode.INVALID_RECEIPT) };
|
|
103
106
|
}
|
|
104
107
|
const args = paymentLog.parsed.args;
|
|
105
108
|
// 4. Verify Merchant
|
|
106
109
|
if (args.merchant.toLowerCase() !== expected.merchantAddress.toLowerCase()) {
|
|
107
|
-
return { isValid: false, error:
|
|
110
|
+
return { isValid: false, error: new errors_1.PayNodeException("Payment went to a different merchant.", errors_1.ErrorCode.INVALID_RECEIPT) };
|
|
108
111
|
}
|
|
109
112
|
// 5. Verify Token
|
|
110
113
|
if (args.token.toLowerCase() !== expected.tokenAddress.toLowerCase()) {
|
|
111
|
-
return { isValid: false, error:
|
|
114
|
+
return { isValid: false, error: new errors_1.PayNodeException("Payment used unexpected token.", errors_1.ErrorCode.INVALID_RECEIPT) };
|
|
112
115
|
}
|
|
113
116
|
// 6. Verify Amount
|
|
114
117
|
if (BigInt(args.amount) < BigInt(expected.amount)) {
|
|
115
|
-
return { isValid: false, error:
|
|
118
|
+
return { isValid: false, error: new errors_1.PayNodeException("Payment amount is below required price.", errors_1.ErrorCode.INVALID_RECEIPT) };
|
|
116
119
|
}
|
|
117
120
|
// 7. Verify ChainId (Cross-chain replay protection)
|
|
118
121
|
const expectedChainId = BigInt(this.chainId || (await this.provider.getNetwork()).chainId);
|
|
119
122
|
if (BigInt(args.chainId) !== expectedChainId) {
|
|
120
|
-
return { isValid: false, error:
|
|
121
|
-
}
|
|
122
|
-
// 8. Order Id Check (Optional)
|
|
123
|
-
if (expected.orderId) {
|
|
124
|
-
// Contract orderId is bytes32. Just comparing strings directly if it was passed cleanly, or checking startsWith etc.
|
|
125
|
-
// Ethers returns bytes32 as 0x-prefixed hex string. We should format expected to bytes32 if it's text.
|
|
126
|
-
// For simplicity, assume they match format or we enforce formatting in the caller.
|
|
127
|
-
if (args.orderId !== expected.orderId) {
|
|
128
|
-
return { isValid: false, error: { code: errors_1.ErrorCode.ORDER_MISMATCH, message: "OrderId mismatch." } };
|
|
129
|
-
}
|
|
123
|
+
return { isValid: false, error: new errors_1.PayNodeException("ChainId mismatch. Invalid network.", errors_1.ErrorCode.INVALID_RECEIPT) };
|
|
130
124
|
}
|
|
131
125
|
return { isValid: true };
|
|
132
126
|
}
|
|
133
127
|
catch (e) {
|
|
134
|
-
|
|
128
|
+
if (e instanceof errors_1.PayNodeException)
|
|
129
|
+
return { isValid: false, error: e };
|
|
130
|
+
return { isValid: false, error: new errors_1.PayNodeException(`An unexpected error occurred: ${e.message}`, errors_1.ErrorCode.INTERNAL_ERROR) };
|
|
135
131
|
}
|
|
136
132
|
}
|
|
137
133
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paynodelabs/sdk-js",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
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",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"prepublishOnly": "npm run build",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "jest",
|
|
15
|
+
"example:server": "ts-node examples/express-server.ts"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"paynode",
|
|
@@ -25,13 +26,17 @@
|
|
|
25
26
|
"license": "MIT",
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"ethers": "^6.13.0",
|
|
28
|
-
"ioredis": "^5.10.1"
|
|
29
|
+
"ioredis": "^5.10.1",
|
|
30
|
+
"express": "^4.19.2"
|
|
29
31
|
},
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"@types/express": "^5.0.6",
|
|
32
34
|
"@types/ioredis": "^4.28.10",
|
|
35
|
+
"@types/jest": "^29.5.12",
|
|
33
36
|
"@types/node": "^20.12.12",
|
|
34
37
|
"dotenv": "^16.4.5",
|
|
38
|
+
"jest": "^29.7.0",
|
|
39
|
+
"ts-jest": "^29.1.4",
|
|
35
40
|
"ts-node": "^10.9.2",
|
|
36
41
|
"typescript": "^5.4.5"
|
|
37
42
|
}
|