@payroute/x402-sdk 1.0.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 +110 -0
- package/dist/index.d.mts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +234 -0
- package/dist/index.mjs +207 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Payroute x402 SDK
|
|
2
|
+
|
|
3
|
+
The official Node.js and TypeScript SDK for the **Payroute Protocol** on Mantle Network.
|
|
4
|
+
|
|
5
|
+
This SDK abstracts the complexity of **HTTP 402 Pay-Per-Hit** workflows, enabling seamless autonomous payments for premium APIs, static content, and AI Agents. It handles wallet management, token approvals, on-chain escrow transactions, and automatic request retries.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **Full x402 Abstraction**: Automates the HTTP 402 -> Payment -> Retry loop.
|
|
10
|
+
- 💸 **Mantle Network Support**: Built for Mantle Mainnet and Sepolia Testnet.
|
|
11
|
+
- 🤖 **AI Agent Ready**: Dedicated methods for interacting with Payroute-enabled AI Agents.
|
|
12
|
+
- 📦 **Token Management**: Automatically handles ERC20 (MUSD) approvals and transfers.
|
|
13
|
+
- 🛡️ **Type-Safe**: Written in TypeScript with full type definitions.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install payroute-x402-sdk ethers
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
_Note: `ethers` peer dependency (v6) is required._
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Initialize the Service
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { PaymentService } from "payroute-x402-sdk";
|
|
29
|
+
|
|
30
|
+
const service = new PaymentService({
|
|
31
|
+
privateKey: process.env.WALLET_PRIVATE_KEY, // Your EVM private key
|
|
32
|
+
network: "mantle", // 'mantle' | 'mantleTestnet' | 'localhost'
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Consume a Paid Proxy Endpoint
|
|
37
|
+
|
|
38
|
+
Access premium content served behind a Payroute gateway.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
try {
|
|
42
|
+
const content = await service.getProxyEndpoint("my-premium-blog-post");
|
|
43
|
+
console.log("Accessed Content:", content);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("Payment or Fetch Failed:", error);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Interact with a Paid AI Agent
|
|
50
|
+
|
|
51
|
+
Send messages to an AI agent that requires per-message payments.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
try {
|
|
55
|
+
const response = await service.generateAIResponse(
|
|
56
|
+
"finance-advisor-agent",
|
|
57
|
+
"What is the outlook for MNT?"
|
|
58
|
+
);
|
|
59
|
+
console.log("AI Response:", response);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("Agent Interaction Failed:", error);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Advanced Usage
|
|
66
|
+
|
|
67
|
+
### Custom RPC Provider
|
|
68
|
+
|
|
69
|
+
You can override the default RPC URLs for custom configuration.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const service = new PaymentService({
|
|
73
|
+
privateKey: "...",
|
|
74
|
+
rpcUrl: "https://rpc.ankr.com/mantle",
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Generic Pay-And-Retry
|
|
79
|
+
|
|
80
|
+
If you are building a custom integration that follows the x402 generic pattern but doesn't fit the standard gateway/agent flow, you can use the low-level `payAndRetry` method.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const response = await service.payAndRetry({
|
|
84
|
+
paymentData: {
|
|
85
|
+
amount: "1000000000000000000", // 1 ETH/MNT in wei
|
|
86
|
+
recipient: "0x123...",
|
|
87
|
+
},
|
|
88
|
+
retryRequest: async (headers) => {
|
|
89
|
+
// Perform your custom retry logic here using the provided headers
|
|
90
|
+
// headers['X-Payment-Tx'] will contain the transaction hash
|
|
91
|
+
return fetch("https://api.custom.com/resource", { headers });
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Architecture
|
|
97
|
+
|
|
98
|
+
The SDK implements the **Payroute x402 Protocol**:
|
|
99
|
+
|
|
100
|
+
1. **Request**: SDK attempts to access a resource.
|
|
101
|
+
2. **Challenge (402)**: Server responds with `402 Payment Required` and payment details (Escrow contract, Amount, Transaction ID).
|
|
102
|
+
3. **Approval**: SDK approves the required token (MUSD) for the escrow contract.
|
|
103
|
+
4. **Payment**: SDK calls the Escrow contract's `createTx` function.
|
|
104
|
+
5. **Confirmation**: SDK waits for blockchain confirmation.
|
|
105
|
+
6. **Retry**: SDK retries the original request with the transaction hash in the `x-payment-tx` header.
|
|
106
|
+
7. **Response**: Server validates the transaction and returns the content.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT © [Payroute Protocol](https://github.com/payroute-protocol)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PaymentData interface for transaction details
|
|
3
|
+
*/
|
|
4
|
+
interface PaymentData {
|
|
5
|
+
amount: string;
|
|
6
|
+
recipient: string;
|
|
7
|
+
currency?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for the PaymentService
|
|
11
|
+
*/
|
|
12
|
+
interface PaymentServiceConfig {
|
|
13
|
+
privateKey: string;
|
|
14
|
+
network?: 'mantle' | 'mantleTestnet' | 'localhost';
|
|
15
|
+
rpcUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* PaymentService class
|
|
19
|
+
* Handles EVM wallet initialization, transaction signing, and payment flow with retries.
|
|
20
|
+
*/
|
|
21
|
+
declare class PaymentService {
|
|
22
|
+
private wallet;
|
|
23
|
+
private provider;
|
|
24
|
+
constructor(config: PaymentServiceConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Performs a payment on-chain and retries the original request with proof of payment.
|
|
27
|
+
*
|
|
28
|
+
* @param params.paymentData - The payment details (amount, recipient).
|
|
29
|
+
* @param params.retryRequest - A callback that performs the HTTP request. Must accept headers.
|
|
30
|
+
* @returns The response from the retried request.
|
|
31
|
+
*/
|
|
32
|
+
payAndRetry<T>(params: {
|
|
33
|
+
paymentData: PaymentData;
|
|
34
|
+
retryRequest: (headers: Record<string, string>) => Promise<T>;
|
|
35
|
+
}): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Get AI response
|
|
38
|
+
*
|
|
39
|
+
* @param agentSlug
|
|
40
|
+
* @returns
|
|
41
|
+
*/
|
|
42
|
+
generateAIResponse<T = any>(agentSlug: string, message: string): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Get proxy endpoint
|
|
45
|
+
*
|
|
46
|
+
* @param gatewaySlug
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
getProxyEndpoint<T = any>(gatewaySlug: string): Promise<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Helper to get the current wallet address
|
|
52
|
+
*/
|
|
53
|
+
getAddress(): string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { type PaymentData, PaymentService, type PaymentServiceConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PaymentData interface for transaction details
|
|
3
|
+
*/
|
|
4
|
+
interface PaymentData {
|
|
5
|
+
amount: string;
|
|
6
|
+
recipient: string;
|
|
7
|
+
currency?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for the PaymentService
|
|
11
|
+
*/
|
|
12
|
+
interface PaymentServiceConfig {
|
|
13
|
+
privateKey: string;
|
|
14
|
+
network?: 'mantle' | 'mantleTestnet' | 'localhost';
|
|
15
|
+
rpcUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* PaymentService class
|
|
19
|
+
* Handles EVM wallet initialization, transaction signing, and payment flow with retries.
|
|
20
|
+
*/
|
|
21
|
+
declare class PaymentService {
|
|
22
|
+
private wallet;
|
|
23
|
+
private provider;
|
|
24
|
+
constructor(config: PaymentServiceConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Performs a payment on-chain and retries the original request with proof of payment.
|
|
27
|
+
*
|
|
28
|
+
* @param params.paymentData - The payment details (amount, recipient).
|
|
29
|
+
* @param params.retryRequest - A callback that performs the HTTP request. Must accept headers.
|
|
30
|
+
* @returns The response from the retried request.
|
|
31
|
+
*/
|
|
32
|
+
payAndRetry<T>(params: {
|
|
33
|
+
paymentData: PaymentData;
|
|
34
|
+
retryRequest: (headers: Record<string, string>) => Promise<T>;
|
|
35
|
+
}): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Get AI response
|
|
38
|
+
*
|
|
39
|
+
* @param agentSlug
|
|
40
|
+
* @returns
|
|
41
|
+
*/
|
|
42
|
+
generateAIResponse<T = any>(agentSlug: string, message: string): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Get proxy endpoint
|
|
45
|
+
*
|
|
46
|
+
* @param gatewaySlug
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
getProxyEndpoint<T = any>(gatewaySlug: string): Promise<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Helper to get the current wallet address
|
|
52
|
+
*/
|
|
53
|
+
getAddress(): string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { type PaymentData, PaymentService, type PaymentServiceConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
PaymentService: () => PaymentService
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/PaymentService.ts
|
|
28
|
+
var import_ethers = require("ethers");
|
|
29
|
+
var NETWORKS = {
|
|
30
|
+
mantle: {
|
|
31
|
+
name: "Mantle Mainnet",
|
|
32
|
+
rpc: "https://rpc.mantle.xyz",
|
|
33
|
+
chainId: 5e3
|
|
34
|
+
},
|
|
35
|
+
mantleTestnet: {
|
|
36
|
+
name: "Mantle Testnet",
|
|
37
|
+
rpc: "https://rpc.sepolia.mantle.xyz",
|
|
38
|
+
chainId: 5003
|
|
39
|
+
},
|
|
40
|
+
localhost: {
|
|
41
|
+
name: "Localhost",
|
|
42
|
+
rpc: "http://127.0.0.1:8545",
|
|
43
|
+
chainId: 31337
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var BASE_ENDPOINT = "https://x402-services.vercel.app";
|
|
47
|
+
var ESCROW_ABI = [
|
|
48
|
+
"function createTx(string txId, address creator, uint256 amount) external payable"
|
|
49
|
+
];
|
|
50
|
+
var MUSD_ADDRESS = "0x4dABf45C8cF333Ef1e874c3FDFC3C86799af80c8";
|
|
51
|
+
var ERC20_ABI = [
|
|
52
|
+
"function approve(address spender, uint256 amount) external returns (bool)"
|
|
53
|
+
];
|
|
54
|
+
var PaymentService = class {
|
|
55
|
+
wallet;
|
|
56
|
+
provider;
|
|
57
|
+
constructor(config) {
|
|
58
|
+
const networkKey = config.network || "mantle";
|
|
59
|
+
const networkConfig = NETWORKS[networkKey];
|
|
60
|
+
if (!networkConfig && !config.rpcUrl) {
|
|
61
|
+
throw new Error(`Invalid network: ${networkKey} and no RPC URL provided.`);
|
|
62
|
+
}
|
|
63
|
+
const rpcUrl = config.rpcUrl || networkConfig?.rpc;
|
|
64
|
+
this.provider = new import_ethers.ethers.JsonRpcProvider(rpcUrl);
|
|
65
|
+
try {
|
|
66
|
+
this.wallet = new import_ethers.ethers.Wallet(config.privateKey, this.provider);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new Error("Invalid private key provided.");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Performs a payment on-chain and retries the original request with proof of payment.
|
|
73
|
+
*
|
|
74
|
+
* @param params.paymentData - The payment details (amount, recipient).
|
|
75
|
+
* @param params.retryRequest - A callback that performs the HTTP request. Must accept headers.
|
|
76
|
+
* @returns The response from the retried request.
|
|
77
|
+
*/
|
|
78
|
+
async payAndRetry(params) {
|
|
79
|
+
const { paymentData, retryRequest } = params;
|
|
80
|
+
try {
|
|
81
|
+
if (!import_ethers.ethers.isAddress(paymentData.recipient)) {
|
|
82
|
+
throw new Error(`Invalid recipient address: ${paymentData.recipient}`);
|
|
83
|
+
}
|
|
84
|
+
const txRequest = {
|
|
85
|
+
to: paymentData.recipient,
|
|
86
|
+
value: BigInt(paymentData.amount)
|
|
87
|
+
// Gas limit/price will be estimated by provider/wallet
|
|
88
|
+
};
|
|
89
|
+
const txResponse = await this.wallet.sendTransaction(txRequest);
|
|
90
|
+
const receipt = await txResponse.wait(1);
|
|
91
|
+
if (!receipt || receipt.status !== 1) {
|
|
92
|
+
throw new Error("Transaction failed or was reverted on-chain.");
|
|
93
|
+
}
|
|
94
|
+
const txHash = receipt.hash;
|
|
95
|
+
const headers = {
|
|
96
|
+
"X-Payment-Tx": txHash,
|
|
97
|
+
"Content-Type": "application/json"
|
|
98
|
+
// Default content type, extendable if needed
|
|
99
|
+
};
|
|
100
|
+
return await retryRequest(headers);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred during payment flow";
|
|
103
|
+
throw new Error(`PaymentService Failed: ${errorMessage}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get AI response
|
|
108
|
+
*
|
|
109
|
+
* @param agentSlug
|
|
110
|
+
* @returns
|
|
111
|
+
*/
|
|
112
|
+
async generateAIResponse(agentSlug, message) {
|
|
113
|
+
try {
|
|
114
|
+
const initialResponse = await fetch(`${BASE_ENDPOINT}/agent/${agentSlug}/chat`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"Content-Type": "application/json"
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
message
|
|
121
|
+
})
|
|
122
|
+
});
|
|
123
|
+
if (!initialResponse.ok && initialResponse.status !== 402) {
|
|
124
|
+
throw new Error(`Unexpected status code: ${initialResponse.status}`);
|
|
125
|
+
}
|
|
126
|
+
const paymentDetail = await initialResponse.json();
|
|
127
|
+
const txId = paymentDetail.transactionId;
|
|
128
|
+
const escrowAddress = paymentDetail.escrowAddress;
|
|
129
|
+
const amountPayment = paymentDetail.amountPayment;
|
|
130
|
+
const contractAddress = paymentDetail.contractAddress;
|
|
131
|
+
if (!contractAddress) {
|
|
132
|
+
throw new Error("Contract address not found in payment details.");
|
|
133
|
+
}
|
|
134
|
+
const musdContract = new import_ethers.ethers.Contract(MUSD_ADDRESS, ERC20_ABI, this.wallet);
|
|
135
|
+
const approveTx = await musdContract.approve(contractAddress, amountPayment);
|
|
136
|
+
await approveTx.wait(1);
|
|
137
|
+
const contract = new import_ethers.ethers.Contract(contractAddress, ESCROW_ABI, this.wallet);
|
|
138
|
+
const txResponse = await contract.createTx(
|
|
139
|
+
txId,
|
|
140
|
+
escrowAddress,
|
|
141
|
+
amountPayment
|
|
142
|
+
);
|
|
143
|
+
const receipt = await txResponse.wait(1);
|
|
144
|
+
if (!receipt || receipt.status !== 1) {
|
|
145
|
+
throw new Error("Escrow transaction failed.");
|
|
146
|
+
}
|
|
147
|
+
const finalTxHash = receipt.hash;
|
|
148
|
+
const headers = {
|
|
149
|
+
"x-payment-tx": finalTxHash,
|
|
150
|
+
"Content-Type": "application/json"
|
|
151
|
+
};
|
|
152
|
+
const retryResponse = await fetch(`${BASE_ENDPOINT}/agent/${agentSlug}/chat`, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers,
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
message
|
|
157
|
+
})
|
|
158
|
+
});
|
|
159
|
+
if (!retryResponse.ok) {
|
|
160
|
+
const errorText = await retryResponse.text();
|
|
161
|
+
throw new Error(`Retry failed: ${retryResponse.status} - ${errorText}`);
|
|
162
|
+
}
|
|
163
|
+
return await retryResponse.json();
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred during payment flow";
|
|
166
|
+
throw new Error(`PaymentService Failed: ${errorMessage}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get proxy endpoint
|
|
171
|
+
*
|
|
172
|
+
* @param gatewaySlug
|
|
173
|
+
* @returns
|
|
174
|
+
*/
|
|
175
|
+
async getProxyEndpoint(gatewaySlug) {
|
|
176
|
+
try {
|
|
177
|
+
const initialResponse = await fetch(`${BASE_ENDPOINT}/escrow/${gatewaySlug}`);
|
|
178
|
+
if (initialResponse.ok) {
|
|
179
|
+
return await initialResponse.json();
|
|
180
|
+
}
|
|
181
|
+
if (initialResponse.status !== 402) {
|
|
182
|
+
throw new Error(`Unexpected status code: ${initialResponse.status}`);
|
|
183
|
+
}
|
|
184
|
+
const paymentDetail = await initialResponse.json();
|
|
185
|
+
const txId = paymentDetail.transactionId;
|
|
186
|
+
const escrowAddress = paymentDetail.escrowAddress;
|
|
187
|
+
const amountPayment = paymentDetail.amountPayment;
|
|
188
|
+
const contractAddress = paymentDetail.contractAddress;
|
|
189
|
+
if (!contractAddress) {
|
|
190
|
+
throw new Error("Contract address not found in payment details.");
|
|
191
|
+
}
|
|
192
|
+
const musdContract = new import_ethers.ethers.Contract(MUSD_ADDRESS, ERC20_ABI, this.wallet);
|
|
193
|
+
const approveTx = await musdContract.approve(contractAddress, amountPayment);
|
|
194
|
+
await approveTx.wait(1);
|
|
195
|
+
const contract = new import_ethers.ethers.Contract(contractAddress, ESCROW_ABI, this.wallet);
|
|
196
|
+
const txResponse = await contract.createTx(
|
|
197
|
+
txId,
|
|
198
|
+
escrowAddress,
|
|
199
|
+
amountPayment
|
|
200
|
+
);
|
|
201
|
+
const receipt = await txResponse.wait(1);
|
|
202
|
+
if (!receipt || receipt.status !== 1) {
|
|
203
|
+
throw new Error("Escrow transaction failed.");
|
|
204
|
+
}
|
|
205
|
+
const finalTxHash = receipt.hash;
|
|
206
|
+
const headers = {
|
|
207
|
+
"x-payment-tx": finalTxHash,
|
|
208
|
+
"Content-Type": "application/json"
|
|
209
|
+
};
|
|
210
|
+
const retryResponse = await fetch(`${BASE_ENDPOINT}/${gatewaySlug}`, {
|
|
211
|
+
method: "GET",
|
|
212
|
+
headers
|
|
213
|
+
});
|
|
214
|
+
if (!retryResponse.ok) {
|
|
215
|
+
const errorText = await retryResponse.text();
|
|
216
|
+
throw new Error(`Retry failed: ${retryResponse.status} - ${errorText}`);
|
|
217
|
+
}
|
|
218
|
+
return await retryResponse.json();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error in getProxyEndpoint";
|
|
221
|
+
throw new Error(`Proxy Endpoint Failed: ${errorMessage}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Helper to get the current wallet address
|
|
226
|
+
*/
|
|
227
|
+
getAddress() {
|
|
228
|
+
return this.wallet.address;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
232
|
+
0 && (module.exports = {
|
|
233
|
+
PaymentService
|
|
234
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// src/PaymentService.ts
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
var NETWORKS = {
|
|
4
|
+
mantle: {
|
|
5
|
+
name: "Mantle Mainnet",
|
|
6
|
+
rpc: "https://rpc.mantle.xyz",
|
|
7
|
+
chainId: 5e3
|
|
8
|
+
},
|
|
9
|
+
mantleTestnet: {
|
|
10
|
+
name: "Mantle Testnet",
|
|
11
|
+
rpc: "https://rpc.sepolia.mantle.xyz",
|
|
12
|
+
chainId: 5003
|
|
13
|
+
},
|
|
14
|
+
localhost: {
|
|
15
|
+
name: "Localhost",
|
|
16
|
+
rpc: "http://127.0.0.1:8545",
|
|
17
|
+
chainId: 31337
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var BASE_ENDPOINT = "https://x402-services.vercel.app";
|
|
21
|
+
var ESCROW_ABI = [
|
|
22
|
+
"function createTx(string txId, address creator, uint256 amount) external payable"
|
|
23
|
+
];
|
|
24
|
+
var MUSD_ADDRESS = "0x4dABf45C8cF333Ef1e874c3FDFC3C86799af80c8";
|
|
25
|
+
var ERC20_ABI = [
|
|
26
|
+
"function approve(address spender, uint256 amount) external returns (bool)"
|
|
27
|
+
];
|
|
28
|
+
var PaymentService = class {
|
|
29
|
+
wallet;
|
|
30
|
+
provider;
|
|
31
|
+
constructor(config) {
|
|
32
|
+
const networkKey = config.network || "mantle";
|
|
33
|
+
const networkConfig = NETWORKS[networkKey];
|
|
34
|
+
if (!networkConfig && !config.rpcUrl) {
|
|
35
|
+
throw new Error(`Invalid network: ${networkKey} and no RPC URL provided.`);
|
|
36
|
+
}
|
|
37
|
+
const rpcUrl = config.rpcUrl || networkConfig?.rpc;
|
|
38
|
+
this.provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
39
|
+
try {
|
|
40
|
+
this.wallet = new ethers.Wallet(config.privateKey, this.provider);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error("Invalid private key provided.");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Performs a payment on-chain and retries the original request with proof of payment.
|
|
47
|
+
*
|
|
48
|
+
* @param params.paymentData - The payment details (amount, recipient).
|
|
49
|
+
* @param params.retryRequest - A callback that performs the HTTP request. Must accept headers.
|
|
50
|
+
* @returns The response from the retried request.
|
|
51
|
+
*/
|
|
52
|
+
async payAndRetry(params) {
|
|
53
|
+
const { paymentData, retryRequest } = params;
|
|
54
|
+
try {
|
|
55
|
+
if (!ethers.isAddress(paymentData.recipient)) {
|
|
56
|
+
throw new Error(`Invalid recipient address: ${paymentData.recipient}`);
|
|
57
|
+
}
|
|
58
|
+
const txRequest = {
|
|
59
|
+
to: paymentData.recipient,
|
|
60
|
+
value: BigInt(paymentData.amount)
|
|
61
|
+
// Gas limit/price will be estimated by provider/wallet
|
|
62
|
+
};
|
|
63
|
+
const txResponse = await this.wallet.sendTransaction(txRequest);
|
|
64
|
+
const receipt = await txResponse.wait(1);
|
|
65
|
+
if (!receipt || receipt.status !== 1) {
|
|
66
|
+
throw new Error("Transaction failed or was reverted on-chain.");
|
|
67
|
+
}
|
|
68
|
+
const txHash = receipt.hash;
|
|
69
|
+
const headers = {
|
|
70
|
+
"X-Payment-Tx": txHash,
|
|
71
|
+
"Content-Type": "application/json"
|
|
72
|
+
// Default content type, extendable if needed
|
|
73
|
+
};
|
|
74
|
+
return await retryRequest(headers);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred during payment flow";
|
|
77
|
+
throw new Error(`PaymentService Failed: ${errorMessage}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get AI response
|
|
82
|
+
*
|
|
83
|
+
* @param agentSlug
|
|
84
|
+
* @returns
|
|
85
|
+
*/
|
|
86
|
+
async generateAIResponse(agentSlug, message) {
|
|
87
|
+
try {
|
|
88
|
+
const initialResponse = await fetch(`${BASE_ENDPOINT}/agent/${agentSlug}/chat`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
"Content-Type": "application/json"
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
message
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
if (!initialResponse.ok && initialResponse.status !== 402) {
|
|
98
|
+
throw new Error(`Unexpected status code: ${initialResponse.status}`);
|
|
99
|
+
}
|
|
100
|
+
const paymentDetail = await initialResponse.json();
|
|
101
|
+
const txId = paymentDetail.transactionId;
|
|
102
|
+
const escrowAddress = paymentDetail.escrowAddress;
|
|
103
|
+
const amountPayment = paymentDetail.amountPayment;
|
|
104
|
+
const contractAddress = paymentDetail.contractAddress;
|
|
105
|
+
if (!contractAddress) {
|
|
106
|
+
throw new Error("Contract address not found in payment details.");
|
|
107
|
+
}
|
|
108
|
+
const musdContract = new ethers.Contract(MUSD_ADDRESS, ERC20_ABI, this.wallet);
|
|
109
|
+
const approveTx = await musdContract.approve(contractAddress, amountPayment);
|
|
110
|
+
await approveTx.wait(1);
|
|
111
|
+
const contract = new ethers.Contract(contractAddress, ESCROW_ABI, this.wallet);
|
|
112
|
+
const txResponse = await contract.createTx(
|
|
113
|
+
txId,
|
|
114
|
+
escrowAddress,
|
|
115
|
+
amountPayment
|
|
116
|
+
);
|
|
117
|
+
const receipt = await txResponse.wait(1);
|
|
118
|
+
if (!receipt || receipt.status !== 1) {
|
|
119
|
+
throw new Error("Escrow transaction failed.");
|
|
120
|
+
}
|
|
121
|
+
const finalTxHash = receipt.hash;
|
|
122
|
+
const headers = {
|
|
123
|
+
"x-payment-tx": finalTxHash,
|
|
124
|
+
"Content-Type": "application/json"
|
|
125
|
+
};
|
|
126
|
+
const retryResponse = await fetch(`${BASE_ENDPOINT}/agent/${agentSlug}/chat`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers,
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
message
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
if (!retryResponse.ok) {
|
|
134
|
+
const errorText = await retryResponse.text();
|
|
135
|
+
throw new Error(`Retry failed: ${retryResponse.status} - ${errorText}`);
|
|
136
|
+
}
|
|
137
|
+
return await retryResponse.json();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred during payment flow";
|
|
140
|
+
throw new Error(`PaymentService Failed: ${errorMessage}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get proxy endpoint
|
|
145
|
+
*
|
|
146
|
+
* @param gatewaySlug
|
|
147
|
+
* @returns
|
|
148
|
+
*/
|
|
149
|
+
async getProxyEndpoint(gatewaySlug) {
|
|
150
|
+
try {
|
|
151
|
+
const initialResponse = await fetch(`${BASE_ENDPOINT}/escrow/${gatewaySlug}`);
|
|
152
|
+
if (initialResponse.ok) {
|
|
153
|
+
return await initialResponse.json();
|
|
154
|
+
}
|
|
155
|
+
if (initialResponse.status !== 402) {
|
|
156
|
+
throw new Error(`Unexpected status code: ${initialResponse.status}`);
|
|
157
|
+
}
|
|
158
|
+
const paymentDetail = await initialResponse.json();
|
|
159
|
+
const txId = paymentDetail.transactionId;
|
|
160
|
+
const escrowAddress = paymentDetail.escrowAddress;
|
|
161
|
+
const amountPayment = paymentDetail.amountPayment;
|
|
162
|
+
const contractAddress = paymentDetail.contractAddress;
|
|
163
|
+
if (!contractAddress) {
|
|
164
|
+
throw new Error("Contract address not found in payment details.");
|
|
165
|
+
}
|
|
166
|
+
const musdContract = new ethers.Contract(MUSD_ADDRESS, ERC20_ABI, this.wallet);
|
|
167
|
+
const approveTx = await musdContract.approve(contractAddress, amountPayment);
|
|
168
|
+
await approveTx.wait(1);
|
|
169
|
+
const contract = new ethers.Contract(contractAddress, ESCROW_ABI, this.wallet);
|
|
170
|
+
const txResponse = await contract.createTx(
|
|
171
|
+
txId,
|
|
172
|
+
escrowAddress,
|
|
173
|
+
amountPayment
|
|
174
|
+
);
|
|
175
|
+
const receipt = await txResponse.wait(1);
|
|
176
|
+
if (!receipt || receipt.status !== 1) {
|
|
177
|
+
throw new Error("Escrow transaction failed.");
|
|
178
|
+
}
|
|
179
|
+
const finalTxHash = receipt.hash;
|
|
180
|
+
const headers = {
|
|
181
|
+
"x-payment-tx": finalTxHash,
|
|
182
|
+
"Content-Type": "application/json"
|
|
183
|
+
};
|
|
184
|
+
const retryResponse = await fetch(`${BASE_ENDPOINT}/${gatewaySlug}`, {
|
|
185
|
+
method: "GET",
|
|
186
|
+
headers
|
|
187
|
+
});
|
|
188
|
+
if (!retryResponse.ok) {
|
|
189
|
+
const errorText = await retryResponse.text();
|
|
190
|
+
throw new Error(`Retry failed: ${retryResponse.status} - ${errorText}`);
|
|
191
|
+
}
|
|
192
|
+
return await retryResponse.json();
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error in getProxyEndpoint";
|
|
195
|
+
throw new Error(`Proxy Endpoint Failed: ${errorMessage}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Helper to get the current wallet address
|
|
200
|
+
*/
|
|
201
|
+
getAddress() {
|
|
202
|
+
return this.wallet.address;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
export {
|
|
206
|
+
PaymentService
|
|
207
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@payroute/x402-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SDK for automation payment gateway with x402",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/payroute-protocol/payroute-x402-sdk.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"x402",
|
|
17
|
+
"payroute",
|
|
18
|
+
"payment",
|
|
19
|
+
"gateway",
|
|
20
|
+
"sdk"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/payroute-protocol/payroute-x402-sdk/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/payroute-protocol/payroute-x402-sdk#readme",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^4.0.16"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"ethers": "^6.16.0"
|
|
35
|
+
}
|
|
36
|
+
}
|