n8n-nodes-agnicwallet 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 +305 -0
- package/dist/credentials/AgnicWalletApi.credentials.d.ts +8 -0
- package/dist/credentials/AgnicWalletApi.credentials.js +42 -0
- package/dist/credentials/AgnicWalletOAuth2Api.credentials.d.ts +8 -0
- package/dist/credentials/AgnicWalletOAuth2Api.credentials.js +92 -0
- package/dist/nodes/X402HttpRequest/X402HttpRequest.node.d.ts +5 -0
- package/dist/nodes/X402HttpRequest/X402HttpRequest.node.js +325 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# n8n-nodes-agnicwallet
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/n8n-nodes-agnicwallet)
|
|
4
|
+
[](https://github.com/agnicpay/agnicwallet-project/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
N8N community node for AgnicWallet - automated Web3 payments for X402-enabled APIs.
|
|
7
|
+
|
|
8
|
+
Make HTTP requests to X402-enabled APIs with automatic blockchain payment handling via AgnicWallet. No manual wallet management, no complex crypto operations - just authenticate and automate.
|
|
9
|
+
|
|
10
|
+
## What is X402?
|
|
11
|
+
|
|
12
|
+
[X402](https://www.x402.org/) is an open standard for HTTP-based payments. APIs return a `402 Payment Required` status with payment details, and your wallet automatically handles the payment to unlock the resource.
|
|
13
|
+
|
|
14
|
+
## What is AgnicWallet?
|
|
15
|
+
|
|
16
|
+
AgnicWallet is a non-custodial Web3 wallet service built on [Privy](https://privy.io) that handles blockchain payments automatically. Connect once, then all X402 payments happen seamlessly in the background.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- ✅ **Automatic X402 payment handling** - No manual wallet interactions
|
|
21
|
+
- ✅ **OAuth2 authentication** - Secure connection to your AgnicWallet
|
|
22
|
+
- ✅ **API Key support** - For programmatic access and CI/CD
|
|
23
|
+
- ✅ **Multiple HTTP methods** - GET, POST, PUT, DELETE
|
|
24
|
+
- ✅ **Custom headers** - Add any headers your API needs
|
|
25
|
+
- ✅ **JSON body support** - For POST/PUT requests
|
|
26
|
+
- ✅ **Payment metadata** - See exactly what you paid and when
|
|
27
|
+
- ✅ **Monthly spending limits** - Built-in safety controls
|
|
28
|
+
- ✅ **Base Sepolia support** - Currently supports USDC on Base Sepolia testnet
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
### For self-hosted N8N:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g n8n-nodes-agnicwallet
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then restart N8N:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
n8n start
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### For Docker:
|
|
45
|
+
|
|
46
|
+
Add to your Dockerfile:
|
|
47
|
+
|
|
48
|
+
```dockerfile
|
|
49
|
+
FROM n8nio/n8n:latest
|
|
50
|
+
|
|
51
|
+
USER root
|
|
52
|
+
RUN npm install -g n8n-nodes-agnicwallet
|
|
53
|
+
USER node
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### For n8n.cloud:
|
|
57
|
+
|
|
58
|
+
Not yet available. This node must first be approved as a community node by the N8N team.
|
|
59
|
+
|
|
60
|
+
## Setup
|
|
61
|
+
|
|
62
|
+
### 1. Create AgnicWallet Account
|
|
63
|
+
|
|
64
|
+
1. Go to [AgnicWallet](https://agnicwallet-project.vercel.app)
|
|
65
|
+
2. Sign up with email/social login (powered by Privy)
|
|
66
|
+
3. Your embedded wallet is created automatically
|
|
67
|
+
|
|
68
|
+
### 2. Connect to N8N
|
|
69
|
+
|
|
70
|
+
#### Option A: OAuth2 (Recommended)
|
|
71
|
+
|
|
72
|
+
1. In N8N, add the **AgnicWallet X402 Request** node
|
|
73
|
+
2. Select **OAuth2** authentication
|
|
74
|
+
3. Click **Connect my account**
|
|
75
|
+
4. Authorize AgnicWallet in the popup
|
|
76
|
+
5. Done! Your wallet is connected
|
|
77
|
+
|
|
78
|
+
#### Option B: API Key
|
|
79
|
+
|
|
80
|
+
1. Log in to [AgnicWallet](https://agnicwallet-project.vercel.app)
|
|
81
|
+
2. Go to **Settings** → **API Tokens**
|
|
82
|
+
3. Generate a new token
|
|
83
|
+
4. In N8N, select **API Key** authentication
|
|
84
|
+
5. Paste your token
|
|
85
|
+
6. Done!
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
### Basic Example: ChatGPT via X402
|
|
90
|
+
|
|
91
|
+
Configure the node:
|
|
92
|
+
|
|
93
|
+
- **Method:** `POST`
|
|
94
|
+
- **URL:** `https://agnicbillo-proxy.asad-safari.workers.dev/v1/custom/chatgpt/v1/chat/completions`
|
|
95
|
+
- **Headers:**
|
|
96
|
+
- Name: `X-AgnicBillo-Key`
|
|
97
|
+
- Value: `agb_qroy8w9rb5lnpcla8ydl`
|
|
98
|
+
- **Body:**
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"model": "gpt-3.5-turbo",
|
|
102
|
+
"messages": [
|
|
103
|
+
{"role": "user", "content": "Hello!"}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**What happens:**
|
|
109
|
+
1. Node makes POST request
|
|
110
|
+
2. Receives `402 Payment Required`
|
|
111
|
+
3. Automatically signs payment with your wallet
|
|
112
|
+
4. Retries with payment proof
|
|
113
|
+
5. Returns ChatGPT response with payment metadata
|
|
114
|
+
|
|
115
|
+
**Response:**
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"id": "chatcmpl-...",
|
|
119
|
+
"choices": [
|
|
120
|
+
{
|
|
121
|
+
"message": {
|
|
122
|
+
"role": "assistant",
|
|
123
|
+
"content": "Hello! How can I help you?"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"_agnicWallet": {
|
|
128
|
+
"paymentMade": true,
|
|
129
|
+
"amountPaid": 0.01,
|
|
130
|
+
"timestamp": "2025-10-28T13:46:44.079Z"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Example: Protected Content
|
|
136
|
+
|
|
137
|
+
Simple GET request with payment:
|
|
138
|
+
|
|
139
|
+
- **Method:** `GET`
|
|
140
|
+
- **URL:** `https://www.x402.org/protected`
|
|
141
|
+
|
|
142
|
+
Node automatically handles the $0.01 payment and returns the protected content.
|
|
143
|
+
|
|
144
|
+
### Example: Dynamic Content
|
|
145
|
+
|
|
146
|
+
Use N8N expressions for dynamic requests:
|
|
147
|
+
|
|
148
|
+
- **Body:**
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"model": "gpt-3.5-turbo",
|
|
152
|
+
"messages": [
|
|
153
|
+
{"role": "user", "content": "{{ $json.userMessage }}"}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This pulls the message from previous nodes in your workflow.
|
|
159
|
+
|
|
160
|
+
## Configuration Options
|
|
161
|
+
|
|
162
|
+
### Authentication
|
|
163
|
+
- **OAuth2** (recommended) - Secure account connection
|
|
164
|
+
- **API Key** - For automation and CI/CD
|
|
165
|
+
|
|
166
|
+
### HTTP Methods
|
|
167
|
+
- GET
|
|
168
|
+
- POST
|
|
169
|
+
- PUT
|
|
170
|
+
- DELETE
|
|
171
|
+
|
|
172
|
+
### Headers
|
|
173
|
+
Add multiple custom headers as key-value pairs
|
|
174
|
+
|
|
175
|
+
### Body (POST/PUT only)
|
|
176
|
+
JSON body for sending data to the API
|
|
177
|
+
|
|
178
|
+
## How It Works
|
|
179
|
+
|
|
180
|
+
1. **Make request** - Node sends HTTP request to X402 API
|
|
181
|
+
2. **Check for 402** - If API returns `402 Payment Required`, node extracts payment details
|
|
182
|
+
3. **Sign payment** - Node calls AgnicWallet backend to sign payment with your wallet
|
|
183
|
+
4. **Retry request** - Node retries with `X-PAYMENT` header containing payment proof
|
|
184
|
+
5. **Return response** - API validates payment and returns protected resource
|
|
185
|
+
|
|
186
|
+
## Payment Details
|
|
187
|
+
|
|
188
|
+
- **Network:** Base Sepolia (testnet)
|
|
189
|
+
- **Token:** USDC
|
|
190
|
+
- **Typical cost:** $0.01 per request
|
|
191
|
+
- **Monthly limit:** $100 (configurable)
|
|
192
|
+
- **Payment method:** EIP-3009 TransferWithAuthorization (gasless)
|
|
193
|
+
|
|
194
|
+
## Security
|
|
195
|
+
|
|
196
|
+
- **Non-custodial** - Your keys, your crypto (via Privy)
|
|
197
|
+
- **OAuth2** - Industry-standard authentication
|
|
198
|
+
- **Spending limits** - Monthly caps prevent overspending
|
|
199
|
+
- **Payment review** - See all payments in AgnicWallet dashboard
|
|
200
|
+
- **Revocable access** - Disconnect N8N anytime
|
|
201
|
+
|
|
202
|
+
## Troubleshooting
|
|
203
|
+
|
|
204
|
+
### "Payment signing failed"
|
|
205
|
+
|
|
206
|
+
**Causes:**
|
|
207
|
+
- AgnicWallet credentials not connected
|
|
208
|
+
- Insufficient USDC balance
|
|
209
|
+
- Backend server down
|
|
210
|
+
|
|
211
|
+
**Solutions:**
|
|
212
|
+
1. Reconnect credentials in N8N
|
|
213
|
+
2. Check balance at [AgnicWallet](https://agnicwallet-project.vercel.app)
|
|
214
|
+
3. Check backend status
|
|
215
|
+
|
|
216
|
+
### "Request failed after payment"
|
|
217
|
+
|
|
218
|
+
**Causes:**
|
|
219
|
+
- Invalid URL
|
|
220
|
+
- Wrong headers
|
|
221
|
+
- Malformed JSON body
|
|
222
|
+
|
|
223
|
+
**Solutions:**
|
|
224
|
+
1. Verify URL is correct
|
|
225
|
+
2. Check required headers for the API
|
|
226
|
+
3. Validate JSON syntax
|
|
227
|
+
|
|
228
|
+
### "Body field not visible"
|
|
229
|
+
|
|
230
|
+
The body field only appears when Method is **POST** or **PUT**. For GET requests, no body is shown (HTTP standard).
|
|
231
|
+
|
|
232
|
+
## Examples & Workflows
|
|
233
|
+
|
|
234
|
+
See [example workflows](https://github.com/agnicpay/agnicwallet-project/tree/main/examples) in the GitHub repository.
|
|
235
|
+
|
|
236
|
+
## API Reference
|
|
237
|
+
|
|
238
|
+
### Node Properties
|
|
239
|
+
|
|
240
|
+
| Property | Type | Required | Description |
|
|
241
|
+
|----------|------|----------|-------------|
|
|
242
|
+
| authentication | options | Yes | OAuth2 or API Key |
|
|
243
|
+
| method | options | Yes | HTTP method |
|
|
244
|
+
| url | string | Yes | X402 API endpoint |
|
|
245
|
+
| headers | collection | No | Custom headers |
|
|
246
|
+
| body | json | No* | JSON body (*POST/PUT only) |
|
|
247
|
+
|
|
248
|
+
## Limitations
|
|
249
|
+
|
|
250
|
+
- **Networks:** Currently Base Sepolia only (mainnet coming soon)
|
|
251
|
+
- **Body types:** JSON only (form-encoded coming soon)
|
|
252
|
+
- **Query params:** Use in URL for now (dedicated field coming soon)
|
|
253
|
+
|
|
254
|
+
See [ENHANCEMENT_PLAN.md](https://github.com/agnicpay/agnicwallet-project/blob/main/n8n-nodes-agnicwallet/nodes/X402HttpRequest/ENHANCEMENT_PLAN.md) for upcoming features.
|
|
255
|
+
|
|
256
|
+
## Contributing
|
|
257
|
+
|
|
258
|
+
Contributions welcome! Please open issues or PRs at [GitHub](https://github.com/agnicpay/agnicwallet-project).
|
|
259
|
+
|
|
260
|
+
## Support
|
|
261
|
+
|
|
262
|
+
- **Issues:** [GitHub Issues](https://github.com/agnicpay/agnicwallet-project/issues)
|
|
263
|
+
- **Docs:** [Full Documentation](https://github.com/agnicpay/agnicwallet-project)
|
|
264
|
+
- **X402 Standard:** [x402.org](https://www.x402.org/)
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
MIT - See [LICENSE](https://github.com/agnicpay/agnicwallet-project/blob/main/LICENSE)
|
|
269
|
+
|
|
270
|
+
## Links
|
|
271
|
+
|
|
272
|
+
- [NPM Package](https://www.npmjs.com/package/n8n-nodes-agnicwallet)
|
|
273
|
+
- [GitHub Repository](https://github.com/agnicpay/agnicwallet-project)
|
|
274
|
+
- [AgnicWallet Dashboard](https://agnicwallet-project.vercel.app)
|
|
275
|
+
- [X402 Protocol](https://www.x402.org/)
|
|
276
|
+
- [N8N Documentation](https://docs.n8n.io)
|
|
277
|
+
|
|
278
|
+
## Roadmap
|
|
279
|
+
|
|
280
|
+
### v1.1.0
|
|
281
|
+
- Query parameters support
|
|
282
|
+
- Form-encoded body
|
|
283
|
+
- Timeout configuration
|
|
284
|
+
|
|
285
|
+
### v1.2.0
|
|
286
|
+
- Multiple networks (Base mainnet, Polygon, etc.)
|
|
287
|
+
- Retry logic
|
|
288
|
+
- Rate limiting
|
|
289
|
+
|
|
290
|
+
### v2.0.0
|
|
291
|
+
- File upload support
|
|
292
|
+
- Streaming responses
|
|
293
|
+
- Webhook triggers
|
|
294
|
+
|
|
295
|
+
## Acknowledgments
|
|
296
|
+
|
|
297
|
+
Built with:
|
|
298
|
+
- [N8N](https://n8n.io) - Workflow automation
|
|
299
|
+
- [Privy](https://privy.io) - Embedded wallets
|
|
300
|
+
- [X402 Protocol](https://www.x402.org/) - HTTP payment standard
|
|
301
|
+
- [Base](https://base.org) - L2 blockchain
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
Made with ❤️ by the AgnicWallet team
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IAuthenticateGeneric, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class AgnicWalletApi implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
documentationUrl: string;
|
|
6
|
+
properties: INodeProperties[];
|
|
7
|
+
authenticate: IAuthenticateGeneric;
|
|
8
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgnicWalletApi = void 0;
|
|
4
|
+
class AgnicWalletApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'agnicWalletApi';
|
|
7
|
+
this.displayName = 'AgnicWallet API';
|
|
8
|
+
this.documentationUrl = 'https://docs.agnicwallet.com/n8n';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'User ID',
|
|
12
|
+
name: 'userId',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
required: true,
|
|
16
|
+
description: 'Your AgnicWallet user ID (from dashboard)',
|
|
17
|
+
placeholder: 'user_2kX9mNz8pQw7VbC',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'API Token',
|
|
21
|
+
name: 'apiToken',
|
|
22
|
+
type: 'string',
|
|
23
|
+
typeOptions: {
|
|
24
|
+
password: true,
|
|
25
|
+
},
|
|
26
|
+
default: '',
|
|
27
|
+
required: true,
|
|
28
|
+
description: 'Your AgnicWallet API token (starts with agnic_tok_)',
|
|
29
|
+
placeholder: 'agnic_tok_sk_live_...',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
this.authenticate = {
|
|
33
|
+
type: 'generic',
|
|
34
|
+
properties: {
|
|
35
|
+
headers: {
|
|
36
|
+
'X-Agnic-Token': '={{$credentials.apiToken}}',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.AgnicWalletApi = AgnicWalletApi;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgnicWalletOAuth2Api = void 0;
|
|
4
|
+
class AgnicWalletOAuth2Api {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'agnicWalletOAuth2Api';
|
|
7
|
+
this.extends = ['oAuth2Api'];
|
|
8
|
+
this.displayName = 'AgnicWallet OAuth2 API';
|
|
9
|
+
this.documentationUrl = 'https://docs.agnicwallet.com/oauth';
|
|
10
|
+
this.properties = [
|
|
11
|
+
{
|
|
12
|
+
displayName: 'Grant Type',
|
|
13
|
+
name: 'grantType',
|
|
14
|
+
type: 'hidden',
|
|
15
|
+
default: 'authorizationCode',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
displayName: 'Authorization URL',
|
|
19
|
+
name: 'authUrl',
|
|
20
|
+
type: 'string',
|
|
21
|
+
default: 'https://agnicwallet-project.onrender.com/oauth/authorize',
|
|
22
|
+
required: true,
|
|
23
|
+
description: 'The OAuth2 authorization endpoint',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
displayName: 'Access Token URL',
|
|
27
|
+
name: 'accessTokenUrl',
|
|
28
|
+
type: 'string',
|
|
29
|
+
default: 'https://agnicwallet-project.onrender.com/oauth/token',
|
|
30
|
+
required: true,
|
|
31
|
+
description: 'The OAuth2 token endpoint',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
displayName: 'Client ID',
|
|
35
|
+
name: 'clientId',
|
|
36
|
+
type: 'string',
|
|
37
|
+
default: 'n8n_default',
|
|
38
|
+
required: true,
|
|
39
|
+
description: 'OAuth2 Client ID (default: n8n_default)',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
displayName: 'Client Secret',
|
|
43
|
+
name: 'clientSecret',
|
|
44
|
+
type: 'string',
|
|
45
|
+
typeOptions: {
|
|
46
|
+
password: true,
|
|
47
|
+
},
|
|
48
|
+
default: '',
|
|
49
|
+
description: 'OAuth2 Client Secret (optional for PKCE)',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
displayName: 'Scope',
|
|
53
|
+
name: 'scope',
|
|
54
|
+
type: 'string',
|
|
55
|
+
default: 'payments:sign balance:read',
|
|
56
|
+
description: 'OAuth2 scopes',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
displayName: 'Auth URI Query Parameters',
|
|
60
|
+
name: 'authQueryParameters',
|
|
61
|
+
type: 'string',
|
|
62
|
+
default: '',
|
|
63
|
+
description: 'Additional query parameters for authorization URL',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
displayName: 'Authentication',
|
|
67
|
+
name: 'authentication',
|
|
68
|
+
type: 'options',
|
|
69
|
+
options: [
|
|
70
|
+
{
|
|
71
|
+
name: 'Body',
|
|
72
|
+
value: 'body',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'Header',
|
|
76
|
+
value: 'header',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
default: 'body',
|
|
80
|
+
description: 'How to send credentials',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
displayName: 'Use PKCE',
|
|
84
|
+
name: 'usePKCE',
|
|
85
|
+
type: 'boolean',
|
|
86
|
+
default: true,
|
|
87
|
+
description: 'Whether to use PKCE (Proof Key for Code Exchange)',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.AgnicWalletOAuth2Api = AgnicWalletOAuth2Api;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class X402HttpRequest implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.X402HttpRequest = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
class X402HttpRequest {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.description = {
|
|
8
|
+
displayName: 'AgnicWallet X402 Request',
|
|
9
|
+
name: 'agnicWalletX402Request',
|
|
10
|
+
group: ['transform'],
|
|
11
|
+
version: 1,
|
|
12
|
+
description: 'Make HTTP requests to X402-enabled APIs with automatic payment via AgnicWallet',
|
|
13
|
+
defaults: {
|
|
14
|
+
name: 'AgnicWallet X402',
|
|
15
|
+
},
|
|
16
|
+
usableAsTool: true,
|
|
17
|
+
inputs: ['main'],
|
|
18
|
+
outputs: ['main'],
|
|
19
|
+
credentials: [
|
|
20
|
+
{
|
|
21
|
+
name: 'agnicWalletOAuth2Api',
|
|
22
|
+
required: false,
|
|
23
|
+
displayOptions: {
|
|
24
|
+
show: {
|
|
25
|
+
authentication: ['oAuth2'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'agnicWalletApi',
|
|
31
|
+
required: false,
|
|
32
|
+
displayOptions: {
|
|
33
|
+
show: {
|
|
34
|
+
authentication: ['apiKey'],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
properties: [
|
|
40
|
+
{
|
|
41
|
+
displayName: 'Authentication',
|
|
42
|
+
name: 'authentication',
|
|
43
|
+
type: 'options',
|
|
44
|
+
options: [
|
|
45
|
+
{
|
|
46
|
+
name: 'OAuth2',
|
|
47
|
+
value: 'oAuth2',
|
|
48
|
+
description: 'Recommended: Connect your account',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'API Key',
|
|
52
|
+
value: 'apiKey',
|
|
53
|
+
description: 'For CI/CD or programmatic access',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
default: 'oAuth2',
|
|
57
|
+
description: 'How to authenticate with AgnicWallet',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
displayName: 'Method',
|
|
61
|
+
name: 'method',
|
|
62
|
+
type: 'options',
|
|
63
|
+
options: [
|
|
64
|
+
{
|
|
65
|
+
name: 'GET',
|
|
66
|
+
value: 'GET',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'POST',
|
|
70
|
+
value: 'POST',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'PUT',
|
|
74
|
+
value: 'PUT',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'DELETE',
|
|
78
|
+
value: 'DELETE',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
default: 'GET',
|
|
82
|
+
description: 'The HTTP method to use',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
displayName: 'URL',
|
|
86
|
+
name: 'url',
|
|
87
|
+
type: 'string',
|
|
88
|
+
default: '',
|
|
89
|
+
required: true,
|
|
90
|
+
placeholder: 'https://api.example.com/endpoint',
|
|
91
|
+
description: 'The URL of the X402-enabled API',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
displayName: 'Headers',
|
|
95
|
+
name: 'headers',
|
|
96
|
+
type: 'fixedCollection',
|
|
97
|
+
typeOptions: {
|
|
98
|
+
multipleValues: true,
|
|
99
|
+
},
|
|
100
|
+
default: {},
|
|
101
|
+
options: [
|
|
102
|
+
{
|
|
103
|
+
name: 'header',
|
|
104
|
+
displayName: 'Header',
|
|
105
|
+
values: [
|
|
106
|
+
{
|
|
107
|
+
displayName: 'Name',
|
|
108
|
+
name: 'name',
|
|
109
|
+
type: 'string',
|
|
110
|
+
default: '',
|
|
111
|
+
description: 'Name of the header',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
displayName: 'Value',
|
|
115
|
+
name: 'value',
|
|
116
|
+
type: 'string',
|
|
117
|
+
default: '',
|
|
118
|
+
description: 'Value of the header',
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
description: 'Additional headers to send with the request',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
displayName: 'Body',
|
|
127
|
+
name: 'body',
|
|
128
|
+
type: 'json',
|
|
129
|
+
default: '{}',
|
|
130
|
+
displayOptions: {
|
|
131
|
+
show: {
|
|
132
|
+
method: ['POST', 'PUT'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
description: 'JSON body to send with the request',
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async execute() {
|
|
141
|
+
const items = this.getInputData();
|
|
142
|
+
const returnData = [];
|
|
143
|
+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
144
|
+
try {
|
|
145
|
+
// Get authentication type
|
|
146
|
+
const authentication = this.getNodeParameter('authentication', itemIndex);
|
|
147
|
+
let apiBaseUrl;
|
|
148
|
+
let authHeader;
|
|
149
|
+
// Use AgnicWallet backend API endpoint (production cloud by default)
|
|
150
|
+
// Can override with AGNICWALLET_API_URL environment variable for local development
|
|
151
|
+
apiBaseUrl = process.env.AGNICWALLET_API_URL || 'https://agnicwallet-project.onrender.com';
|
|
152
|
+
if (authentication === 'oAuth2') {
|
|
153
|
+
// OAuth2 authentication
|
|
154
|
+
const credentials = await this.getCredentials('agnicWalletOAuth2Api', itemIndex);
|
|
155
|
+
authHeader = `Bearer ${credentials.oauthTokenData.access_token}`;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// API Key authentication
|
|
159
|
+
const credentials = await this.getCredentials('agnicWalletApi', itemIndex);
|
|
160
|
+
const { apiToken } = credentials;
|
|
161
|
+
authHeader = apiToken;
|
|
162
|
+
}
|
|
163
|
+
// Get parameters
|
|
164
|
+
const method = this.getNodeParameter('method', itemIndex);
|
|
165
|
+
const url = this.getNodeParameter('url', itemIndex);
|
|
166
|
+
const headersConfig = this.getNodeParameter('headers', itemIndex, {});
|
|
167
|
+
const body = this.getNodeParameter('body', itemIndex, '{}');
|
|
168
|
+
// Build headers
|
|
169
|
+
const headers = {
|
|
170
|
+
'Content-Type': 'application/json',
|
|
171
|
+
};
|
|
172
|
+
if (headersConfig.header) {
|
|
173
|
+
headersConfig.header.forEach((h) => {
|
|
174
|
+
headers[h.name] = h.value;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// Make request with X402 payment handling
|
|
178
|
+
const response = await makeX402Request(this, {
|
|
179
|
+
url,
|
|
180
|
+
method,
|
|
181
|
+
headers,
|
|
182
|
+
body: method === 'POST' || method === 'PUT' ? JSON.parse(body) : undefined,
|
|
183
|
+
agnicWalletApi: apiBaseUrl,
|
|
184
|
+
authHeader,
|
|
185
|
+
isOAuth: authentication === 'oAuth2',
|
|
186
|
+
});
|
|
187
|
+
returnData.push({
|
|
188
|
+
json: response,
|
|
189
|
+
pairedItem: {
|
|
190
|
+
item: itemIndex,
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
196
|
+
if (this.continueOnFail()) {
|
|
197
|
+
returnData.push({
|
|
198
|
+
json: {
|
|
199
|
+
error: errorMessage,
|
|
200
|
+
},
|
|
201
|
+
pairedItem: {
|
|
202
|
+
item: itemIndex,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage, {
|
|
208
|
+
itemIndex,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return [returnData];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.X402HttpRequest = X402HttpRequest;
|
|
216
|
+
// Helper function: Make X402 request with automatic payment
|
|
217
|
+
async function makeX402Request(context, config) {
|
|
218
|
+
const { url, method, headers, body, agnicWalletApi, authHeader, isOAuth } = config;
|
|
219
|
+
try {
|
|
220
|
+
// 1. Try the request first (may return 402)
|
|
221
|
+
context.logger.info(`Making initial request to: ${url}`);
|
|
222
|
+
let response = await fetch(url, {
|
|
223
|
+
method,
|
|
224
|
+
headers,
|
|
225
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
226
|
+
});
|
|
227
|
+
context.logger.info(`Response status: ${response.status}`);
|
|
228
|
+
// 2. If 402 Payment Required, use AgnicWallet to sign
|
|
229
|
+
if (response.status === 402) {
|
|
230
|
+
context.logger.info('402 Payment Required detected, parsing payment requirements...');
|
|
231
|
+
// Get response text first for debugging
|
|
232
|
+
const responseText = await response.text();
|
|
233
|
+
context.logger.info(`Payment requirements response: ${responseText}`);
|
|
234
|
+
let paymentRequirements;
|
|
235
|
+
try {
|
|
236
|
+
paymentRequirements = JSON.parse(responseText);
|
|
237
|
+
}
|
|
238
|
+
catch (parseError) {
|
|
239
|
+
throw new Error(`Failed to parse payment requirements: ${parseError instanceof Error ? parseError.message : 'Unknown error'}. Response: ${responseText}`);
|
|
240
|
+
}
|
|
241
|
+
context.logger.info(`Payment required: ${JSON.stringify(paymentRequirements)}`);
|
|
242
|
+
// 3. Call AgnicWallet signing API
|
|
243
|
+
context.logger.info(`Calling AgnicWallet API at: ${agnicWalletApi}/api/sign-payment`);
|
|
244
|
+
// Build auth headers based on auth type
|
|
245
|
+
const authHeaders = {
|
|
246
|
+
'Content-Type': 'application/json',
|
|
247
|
+
};
|
|
248
|
+
if (isOAuth) {
|
|
249
|
+
// OAuth: Use Authorization header with Bearer token
|
|
250
|
+
authHeaders['Authorization'] = authHeader;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// API Key: Use X-Agnic-Token header
|
|
254
|
+
authHeaders['X-Agnic-Token'] = authHeader;
|
|
255
|
+
}
|
|
256
|
+
const signingResponse = await fetch(`${agnicWalletApi}/api/sign-payment`, {
|
|
257
|
+
method: 'POST',
|
|
258
|
+
headers: authHeaders,
|
|
259
|
+
body: JSON.stringify({
|
|
260
|
+
paymentRequirements,
|
|
261
|
+
requestData: { url, method, body },
|
|
262
|
+
}),
|
|
263
|
+
});
|
|
264
|
+
if (!signingResponse.ok) {
|
|
265
|
+
const errorText = await signingResponse.text();
|
|
266
|
+
context.logger.error(`AgnicWallet signing failed: ${errorText}`);
|
|
267
|
+
throw new Error(`Payment signing failed (${signingResponse.status}): ${errorText}`);
|
|
268
|
+
}
|
|
269
|
+
const signingResult = await signingResponse.json();
|
|
270
|
+
const { paymentHeader, amountPaid } = signingResult;
|
|
271
|
+
context.logger.info(`Payment signed successfully: $${amountPaid}`);
|
|
272
|
+
context.logger.info(`Payment header (Base64): ${paymentHeader}`);
|
|
273
|
+
// 4. Retry original request with payment header
|
|
274
|
+
// The paymentHeader is already Base64-encoded and X402-compliant
|
|
275
|
+
context.logger.info('Retrying request with X402 payment header...');
|
|
276
|
+
response = await fetch(url, {
|
|
277
|
+
method,
|
|
278
|
+
headers: {
|
|
279
|
+
...headers,
|
|
280
|
+
'X-PAYMENT': paymentHeader,
|
|
281
|
+
},
|
|
282
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
283
|
+
});
|
|
284
|
+
context.logger.info(`Retry response status: ${response.status}`);
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
const errorText = await response.text();
|
|
287
|
+
throw new Error(`Request failed after payment (${response.status}): ${errorText}`);
|
|
288
|
+
}
|
|
289
|
+
const resultText = await response.text();
|
|
290
|
+
let result;
|
|
291
|
+
try {
|
|
292
|
+
result = JSON.parse(resultText);
|
|
293
|
+
}
|
|
294
|
+
catch (parseError) {
|
|
295
|
+
context.logger.warn(`Response is not JSON: ${resultText}`);
|
|
296
|
+
result = { data: resultText };
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
...(typeof result === 'object' && result !== null ? result : {}),
|
|
300
|
+
_agnicWallet: {
|
|
301
|
+
paymentMade: true,
|
|
302
|
+
amountPaid,
|
|
303
|
+
timestamp: new Date().toISOString(),
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
// 5. If no payment required, just return the result
|
|
308
|
+
if (!response.ok) {
|
|
309
|
+
const errorText = await response.text();
|
|
310
|
+
throw new Error(`Request failed (${response.status} ${response.statusText}): ${errorText}`);
|
|
311
|
+
}
|
|
312
|
+
const resultText = await response.text();
|
|
313
|
+
try {
|
|
314
|
+
return JSON.parse(resultText);
|
|
315
|
+
}
|
|
316
|
+
catch (parseError) {
|
|
317
|
+
context.logger.warn(`Response is not JSON: ${resultText}`);
|
|
318
|
+
return { data: resultText };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
context.logger.error(`Error in makeX402Request: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-agnicwallet",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "n8n community node for AgnicWallet - automated Web3 payments for X402 APIs",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n",
|
|
8
|
+
"agnicwallet",
|
|
9
|
+
"x402",
|
|
10
|
+
"web3",
|
|
11
|
+
"crypto",
|
|
12
|
+
"payments"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://agnicwallet.com",
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "AgnicWallet",
|
|
18
|
+
"email": "support@agnicwallet.com"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/agnicpay/agnicwallet-project.git"
|
|
23
|
+
},
|
|
24
|
+
"main": "index.js",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc && gulp build:icons",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"format": "prettier nodes credentials --write",
|
|
29
|
+
"lint": "tslint -p tsconfig.json -c tslint.json",
|
|
30
|
+
"lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
|
31
|
+
"prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json",
|
|
32
|
+
"test": "jest"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"n8n": {
|
|
38
|
+
"n8nNodesApiVersion": 1,
|
|
39
|
+
"credentials": [
|
|
40
|
+
"dist/credentials/AgnicWalletApi.credentials.js",
|
|
41
|
+
"dist/credentials/AgnicWalletOAuth2Api.credentials.js"
|
|
42
|
+
],
|
|
43
|
+
"nodes": [
|
|
44
|
+
"dist/nodes/X402HttpRequest/X402HttpRequest.node.js"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.10.0",
|
|
49
|
+
"@typescript-eslint/parser": "^6.13.0",
|
|
50
|
+
"eslint": "^8.54.0",
|
|
51
|
+
"eslint-plugin-n8n-nodes-base": "^1.16.1",
|
|
52
|
+
"gulp": "^4.0.2",
|
|
53
|
+
"n8n-workflow": "^1.40.0",
|
|
54
|
+
"prettier": "^3.1.0",
|
|
55
|
+
"tslint": "^5.20.1",
|
|
56
|
+
"typescript": "^5.3.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"n8n-workflow": "*"
|
|
60
|
+
}
|
|
61
|
+
}
|