n8n-nodes-dominusnode 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.
Files changed (42) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +21 -0
  3. package/README.md +99 -0
  4. package/dist/credentials/DominusNodeApi.credentials.d.ts +7 -0
  5. package/dist/credentials/DominusNodeApi.credentials.js +42 -0
  6. package/dist/credentials/DominusNodeApi.credentials.js.map +1 -0
  7. package/dist/index.d.ts +4 -0
  8. package/dist/index.js +12 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/nodes/DominusNodeProxy/DominusNodeProxy.node.d.ts +24 -0
  11. package/dist/nodes/DominusNodeProxy/DominusNodeProxy.node.js +436 -0
  12. package/dist/nodes/DominusNodeProxy/DominusNodeProxy.node.js.map +1 -0
  13. package/dist/nodes/DominusNodeUsage/DominusNodeUsage.node.d.ts +13 -0
  14. package/dist/nodes/DominusNodeUsage/DominusNodeUsage.node.js +105 -0
  15. package/dist/nodes/DominusNodeUsage/DominusNodeUsage.node.js.map +1 -0
  16. package/dist/nodes/DominusNodeWallet/DominusNodeWallet.node.d.ts +33 -0
  17. package/dist/nodes/DominusNodeWallet/DominusNodeWallet.node.js +656 -0
  18. package/dist/nodes/DominusNodeWallet/DominusNodeWallet.node.js.map +1 -0
  19. package/dist/shared/auth.d.ts +74 -0
  20. package/dist/shared/auth.js +264 -0
  21. package/dist/shared/auth.js.map +1 -0
  22. package/dist/shared/constants.d.ts +9 -0
  23. package/dist/shared/constants.js +13 -0
  24. package/dist/shared/constants.js.map +1 -0
  25. package/dist/shared/ssrf.d.ts +42 -0
  26. package/dist/shared/ssrf.js +252 -0
  27. package/dist/shared/ssrf.js.map +1 -0
  28. package/package.json +41 -0
  29. package/src/credentials/DominusNodeApi.credentials.ts +39 -0
  30. package/src/index.ts +4 -0
  31. package/src/nodes/DominusNodeProxy/DominusNodeProxy.node.ts +459 -0
  32. package/src/nodes/DominusNodeUsage/DominusNodeUsage.node.ts +130 -0
  33. package/src/nodes/DominusNodeWallet/DominusNodeWallet.node.ts +898 -0
  34. package/src/shared/auth.ts +272 -0
  35. package/src/shared/constants.ts +11 -0
  36. package/src/shared/ssrf.ts +257 -0
  37. package/tests/DominusNodeProxy.test.ts +281 -0
  38. package/tests/DominusNodeUsage.test.ts +250 -0
  39. package/tests/DominusNodeWallet.test.ts +591 -0
  40. package/tests/ssrf.test.ts +238 -0
  41. package/tsconfig.json +18 -0
  42. package/vitest.config.ts +8 -0
@@ -0,0 +1,898 @@
1
+ /**
2
+ * DomiNode Wallet n8n community node.
3
+ *
4
+ * Operations:
5
+ * - Check Balance: Get current wallet balance
6
+ * - Top Up (Stripe): Create a Stripe checkout session
7
+ * - Top Up (Crypto): Create a crypto payment invoice
8
+ * - Top Up (PayPal): Create a PayPal checkout session via Stripe
9
+ * - Create Agentic Wallet: Create a sub-wallet with spending limits
10
+ * - Fund Agentic Wallet: Transfer funds to an agentic wallet
11
+ * - Get Agentic Wallet Balance: Check an agentic wallet's balance
12
+ * - List Agentic Wallets: List all agentic wallets
13
+ * - Get Agentic Transactions: Get transaction history for an agentic wallet
14
+ * - Freeze Agentic Wallet: Freeze an agentic wallet
15
+ * - Unfreeze Agentic Wallet: Unfreeze an agentic wallet
16
+ * - Delete Agentic Wallet: Delete an agentic wallet
17
+ * - Update Wallet Policy: Update agentic wallet spending policy
18
+ * - Create Team: Create a new team
19
+ * - List Teams: List all teams
20
+ * - Team Details: Get team details
21
+ * - Fund Team: Fund a team wallet
22
+ * - Create Team Key: Create an API key for a team
23
+ * - Team Usage: Get team wallet transaction history
24
+ * - Update Team: Update team name/max members
25
+ * - Update Team Member Role: Update a team member's role
26
+ *
27
+ * @module
28
+ */
29
+
30
+ import {
31
+ IDataObject,
32
+ IExecuteFunctions,
33
+ INodeExecutionData,
34
+ INodeType,
35
+ INodeTypeDescription,
36
+ NodeOperationError,
37
+ } from "n8n-workflow";
38
+
39
+ import { DominusNodeAuth, sanitizeError } from "../../shared/auth";
40
+
41
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
42
+ const CONTROL_CHAR_RE = /[\x00-\x1f\x7f]/;
43
+ const DOMAIN_RE = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
44
+ const VALID_CRYPTO_CURRENCIES = new Set([
45
+ "btc", "eth", "ltc", "xmr", "zec", "usdc", "sol", "usdt", "dai", "bnb",
46
+ ]);
47
+
48
+ export class DominusNodeWallet implements INodeType {
49
+ description: INodeTypeDescription = {
50
+ displayName: "DomiNode Wallet",
51
+ name: "dominusNodeWallet",
52
+ icon: "file:dominusnode.svg",
53
+ group: ["transform"],
54
+ version: 1,
55
+ subtitle: '={{$parameter["operation"]}}',
56
+ description: "Manage DomiNode wallet, agentic wallets, and teams",
57
+ defaults: { name: "DomiNode Wallet" },
58
+ inputs: ["main"],
59
+ outputs: ["main"],
60
+ credentials: [{ name: "dominusNodeApi", required: true }],
61
+ properties: [
62
+ {
63
+ displayName: "Operation",
64
+ name: "operation",
65
+ type: "options",
66
+ noDataExpression: true,
67
+ options: [
68
+ // Wallet
69
+ { name: "Check Balance", value: "checkBalance", description: "Get current wallet balance", action: "Check balance" },
70
+ { name: "Top Up (Stripe)", value: "topUpStripe", description: "Create a Stripe checkout session", action: "Top up stripe" },
71
+ { name: "Top Up (Crypto)", value: "topUpCrypto", description: "Create a crypto payment invoice", action: "Top up crypto" },
72
+ { name: "Top Up (PayPal)", value: "topUpPaypal", description: "Create a PayPal checkout session", action: "Top up paypal" },
73
+ // Agentic Wallets
74
+ { name: "Create Agentic Wallet", value: "createAgenticWallet", description: "Create a sub-wallet with spending limits", action: "Create agentic wallet" },
75
+ { name: "Fund Agentic Wallet", value: "fundAgenticWallet", description: "Transfer funds to an agentic wallet", action: "Fund agentic wallet" },
76
+ { name: "Get Agentic Wallet Balance", value: "getAgenticBalance", description: "Get agentic wallet balance", action: "Get agentic wallet balance" },
77
+ { name: "List Agentic Wallets", value: "listAgenticWallets", description: "List all agentic wallets", action: "List agentic wallets" },
78
+ { name: "Get Agentic Transactions", value: "getAgenticTransactions", description: "Get agentic wallet transactions", action: "Get agentic transactions" },
79
+ { name: "Freeze Agentic Wallet", value: "freezeAgenticWallet", description: "Freeze an agentic wallet", action: "Freeze agentic wallet" },
80
+ { name: "Unfreeze Agentic Wallet", value: "unfreezeAgenticWallet", description: "Unfreeze an agentic wallet", action: "Unfreeze agentic wallet" },
81
+ { name: "Delete Agentic Wallet", value: "deleteAgenticWallet", description: "Delete an agentic wallet", action: "Delete agentic wallet" },
82
+ { name: "Update Wallet Policy", value: "updateWalletPolicy", description: "Update agentic wallet spending policy", action: "Update wallet policy" },
83
+ // Teams
84
+ { name: "Create Team", value: "createTeam", description: "Create a new team", action: "Create team" },
85
+ { name: "List Teams", value: "listTeams", description: "List all teams", action: "List teams" },
86
+ { name: "Team Details", value: "teamDetails", description: "Get team details", action: "Team details" },
87
+ { name: "Fund Team", value: "fundTeam", description: "Fund a team wallet", action: "Fund team" },
88
+ { name: "Create Team Key", value: "createTeamKey", description: "Create an API key for a team", action: "Create team key" },
89
+ { name: "Team Usage", value: "teamUsage", description: "Get team wallet transactions", action: "Team usage" },
90
+ { name: "Update Team", value: "updateTeam", description: "Update team settings", action: "Update team" },
91
+ { name: "Update Team Member Role", value: "updateTeamMemberRole", description: "Update a team member role", action: "Update team member role" },
92
+ // x402
93
+ { name: "x402 Info", value: "x402Info", description: "Get x402 micropayment protocol information", action: "Get x402 info" },
94
+ ],
95
+ default: "checkBalance",
96
+ },
97
+
98
+ // --- Stripe top-up ---
99
+ {
100
+ displayName: "Amount (Cents)",
101
+ name: "amountCents",
102
+ type: "number",
103
+ default: 500,
104
+ required: true,
105
+ description: "Amount in cents (e.g., 500 = $5.00). Minimum 500 ($5).",
106
+ displayOptions: { show: { operation: ["topUpStripe", "topUpPaypal"] } },
107
+ },
108
+
109
+ // --- Crypto top-up ---
110
+ {
111
+ displayName: "Amount (USD)",
112
+ name: "amountUsd",
113
+ type: "number",
114
+ default: 10,
115
+ required: true,
116
+ description: "Amount in USD. Minimum $5.",
117
+ displayOptions: { show: { operation: ["topUpCrypto"] } },
118
+ },
119
+ {
120
+ displayName: "Currency",
121
+ name: "currency",
122
+ type: "options",
123
+ options: [
124
+ { name: "Bitcoin (BTC)", value: "btc" },
125
+ { name: "Ethereum (ETH)", value: "eth" },
126
+ { name: "Litecoin (LTC)", value: "ltc" },
127
+ { name: "Monero (XMR)", value: "xmr" },
128
+ { name: "Zcash (ZEC)", value: "zec" },
129
+ { name: "USDC", value: "usdc" },
130
+ { name: "Solana (SOL)", value: "sol" },
131
+ { name: "Tether (USDT)", value: "usdt" },
132
+ { name: "DAI", value: "dai" },
133
+ { name: "BNB", value: "bnb" },
134
+ ],
135
+ default: "btc",
136
+ description: "Cryptocurrency to pay with",
137
+ displayOptions: { show: { operation: ["topUpCrypto"] } },
138
+ },
139
+
140
+ // --- Agentic wallet params ---
141
+ {
142
+ displayName: "Label",
143
+ name: "agenticLabel",
144
+ type: "string",
145
+ default: "",
146
+ required: true,
147
+ description: "Label for the agentic wallet (max 100 chars)",
148
+ displayOptions: { show: { operation: ["createAgenticWallet"] } },
149
+ },
150
+ {
151
+ displayName: "Spending Limit (Cents)",
152
+ name: "spendingLimitCents",
153
+ type: "number",
154
+ default: 1000,
155
+ required: true,
156
+ description: "Per-transaction spending limit in cents",
157
+ displayOptions: { show: { operation: ["createAgenticWallet"] } },
158
+ },
159
+ {
160
+ displayName: "Daily Limit (Cents)",
161
+ name: "dailyLimitCents",
162
+ type: "number",
163
+ default: 0,
164
+ description: "Optional daily budget cap in cents (0 = no limit, max 1,000,000)",
165
+ displayOptions: { show: { operation: ["createAgenticWallet"] } },
166
+ },
167
+ {
168
+ displayName: "Allowed Domains",
169
+ name: "allowedDomains",
170
+ type: "string",
171
+ default: "",
172
+ description: "Comma-separated domain allowlist (e.g., \"example.com,api.example.org\"). Leave empty for no restriction.",
173
+ displayOptions: { show: { operation: ["createAgenticWallet"] } },
174
+ },
175
+ {
176
+ displayName: "Wallet ID",
177
+ name: "policyWalletId",
178
+ type: "string",
179
+ default: "",
180
+ required: true,
181
+ description: "Agentic wallet UUID to update policy for",
182
+ displayOptions: { show: { operation: ["updateWalletPolicy"] } },
183
+ },
184
+ {
185
+ displayName: "Daily Limit (Cents)",
186
+ name: "policyDailyLimitCents",
187
+ type: "number",
188
+ default: 0,
189
+ description: "Daily budget cap in cents (0 = no limit, max 1,000,000). Set to -1 to remove.",
190
+ displayOptions: { show: { operation: ["updateWalletPolicy"] } },
191
+ },
192
+ {
193
+ displayName: "Allowed Domains",
194
+ name: "policyAllowedDomains",
195
+ type: "string",
196
+ default: "",
197
+ description: "Comma-separated domain allowlist. Leave empty to skip, set to \"*\" to remove restriction.",
198
+ displayOptions: { show: { operation: ["updateWalletPolicy"] } },
199
+ },
200
+ {
201
+ displayName: "Wallet ID",
202
+ name: "walletId",
203
+ type: "string",
204
+ default: "",
205
+ required: true,
206
+ description: "Agentic wallet UUID",
207
+ displayOptions: {
208
+ show: {
209
+ operation: [
210
+ "fundAgenticWallet",
211
+ "getAgenticBalance",
212
+ "getAgenticTransactions",
213
+ "freezeAgenticWallet",
214
+ "unfreezeAgenticWallet",
215
+ "deleteAgenticWallet",
216
+ ],
217
+ },
218
+ },
219
+ },
220
+ {
221
+ displayName: "Amount (Cents)",
222
+ name: "fundAmountCents",
223
+ type: "number",
224
+ default: 100,
225
+ required: true,
226
+ description: "Amount in cents to transfer to the agentic wallet",
227
+ displayOptions: { show: { operation: ["fundAgenticWallet"] } },
228
+ },
229
+ {
230
+ displayName: "Limit",
231
+ name: "transactionLimit",
232
+ type: "number",
233
+ default: 20,
234
+ description: "Number of transactions to return (1-100)",
235
+ displayOptions: { show: { operation: ["getAgenticTransactions", "teamUsage"] } },
236
+ },
237
+
238
+ // --- Team params ---
239
+ {
240
+ displayName: "Team Name",
241
+ name: "teamName",
242
+ type: "string",
243
+ default: "",
244
+ required: true,
245
+ description: "Name for the team (max 100 chars)",
246
+ displayOptions: { show: { operation: ["createTeam"] } },
247
+ },
248
+ {
249
+ displayName: "Max Members",
250
+ name: "maxMembers",
251
+ type: "number",
252
+ default: 10,
253
+ description: "Maximum team members (1-100)",
254
+ displayOptions: { show: { operation: ["createTeam"] } },
255
+ },
256
+ {
257
+ displayName: "Team ID",
258
+ name: "teamId",
259
+ type: "string",
260
+ default: "",
261
+ required: true,
262
+ description: "Team UUID",
263
+ displayOptions: {
264
+ show: {
265
+ operation: [
266
+ "teamDetails",
267
+ "fundTeam",
268
+ "createTeamKey",
269
+ "teamUsage",
270
+ "updateTeam",
271
+ "updateTeamMemberRole",
272
+ ],
273
+ },
274
+ },
275
+ },
276
+ {
277
+ displayName: "Amount (Cents)",
278
+ name: "teamFundAmountCents",
279
+ type: "number",
280
+ default: 500,
281
+ required: true,
282
+ description: "Amount in cents to fund the team wallet. Min 100 ($1), max 1,000,000 ($10,000).",
283
+ displayOptions: { show: { operation: ["fundTeam"] } },
284
+ },
285
+ {
286
+ displayName: "Key Label",
287
+ name: "keyLabel",
288
+ type: "string",
289
+ default: "",
290
+ required: true,
291
+ description: "Label for the team API key (max 100 chars)",
292
+ displayOptions: { show: { operation: ["createTeamKey"] } },
293
+ },
294
+ {
295
+ displayName: "New Team Name",
296
+ name: "updateTeamName",
297
+ type: "string",
298
+ default: "",
299
+ description: "New name for the team",
300
+ displayOptions: { show: { operation: ["updateTeam"] } },
301
+ },
302
+ {
303
+ displayName: "New Max Members",
304
+ name: "updateMaxMembers",
305
+ type: "number",
306
+ default: 0,
307
+ description: "New max members (1-100). Set to 0 to skip.",
308
+ displayOptions: { show: { operation: ["updateTeam"] } },
309
+ },
310
+ {
311
+ displayName: "User ID",
312
+ name: "userId",
313
+ type: "string",
314
+ default: "",
315
+ required: true,
316
+ description: "User UUID whose role to update",
317
+ displayOptions: { show: { operation: ["updateTeamMemberRole"] } },
318
+ },
319
+ {
320
+ displayName: "Role",
321
+ name: "role",
322
+ type: "options",
323
+ options: [
324
+ { name: "Admin", value: "admin" },
325
+ { name: "Member", value: "member" },
326
+ ],
327
+ default: "member",
328
+ description: "New role for the team member",
329
+ displayOptions: { show: { operation: ["updateTeamMemberRole"] } },
330
+ },
331
+ ],
332
+ };
333
+
334
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
335
+ const items = this.getInputData();
336
+ const returnData: INodeExecutionData[] = [];
337
+ const credentials = await this.getCredentials("dominusNodeApi");
338
+
339
+ const apiKey = credentials.apiKey as string;
340
+ const baseUrl = (credentials.baseUrl as string) || "https://api.dominusnode.com";
341
+
342
+ if (!apiKey) {
343
+ throw new NodeOperationError(this.getNode(), "API Key is required");
344
+ }
345
+
346
+ const auth = new DominusNodeAuth(apiKey, baseUrl);
347
+ const operation = this.getNodeParameter("operation", 0) as string;
348
+
349
+ for (let i = 0; i < items.length; i++) {
350
+ try {
351
+ let result: unknown;
352
+
353
+ switch (operation) {
354
+ // ----- Wallet -----
355
+ case "checkBalance": {
356
+ result = await auth.apiRequest("GET", "/api/wallet");
357
+ break;
358
+ }
359
+
360
+ case "topUpStripe": {
361
+ const amountCents = this.getNodeParameter("amountCents", i) as number;
362
+ if (!Number.isInteger(amountCents) || amountCents < 500 || amountCents > 1_000_000) {
363
+ throw new NodeOperationError(
364
+ this.getNode(),
365
+ "amountCents must be an integer between 500 ($5) and 1,000,000 ($10,000)",
366
+ { itemIndex: i },
367
+ );
368
+ }
369
+ result = await auth.apiRequest("POST", "/api/wallet/topup/stripe", { amountCents });
370
+ break;
371
+ }
372
+
373
+ case "topUpCrypto": {
374
+ const amountUsd = this.getNodeParameter("amountUsd", i) as number;
375
+ const currency = this.getNodeParameter("currency", i) as string;
376
+ if (typeof amountUsd !== "number" || amountUsd < 5 || amountUsd > 10_000) {
377
+ throw new NodeOperationError(
378
+ this.getNode(),
379
+ "amountUsd must be between $5 and $10,000",
380
+ { itemIndex: i },
381
+ );
382
+ }
383
+ if (!VALID_CRYPTO_CURRENCIES.has(currency)) {
384
+ throw new NodeOperationError(
385
+ this.getNode(),
386
+ `Invalid currency. Valid options: ${[...VALID_CRYPTO_CURRENCIES].join(", ")}`,
387
+ { itemIndex: i },
388
+ );
389
+ }
390
+ result = await auth.apiRequest("POST", "/api/wallet/topup/crypto", {
391
+ amountUsd,
392
+ currency,
393
+ });
394
+ break;
395
+ }
396
+
397
+ case "topUpPaypal": {
398
+ const amountCents = this.getNodeParameter("amountCents", i) as number;
399
+ if (!Number.isInteger(amountCents) || amountCents < 500 || amountCents > 1_000_000) {
400
+ throw new NodeOperationError(
401
+ this.getNode(),
402
+ "amountCents must be an integer between 500 ($5) and 1,000,000 ($10,000)",
403
+ { itemIndex: i },
404
+ );
405
+ }
406
+ result = await auth.apiRequest("POST", "/api/wallet/topup/stripe", {
407
+ amountCents,
408
+ paymentMethod: "paypal",
409
+ });
410
+ break;
411
+ }
412
+
413
+ // ----- Agentic Wallets -----
414
+ case "createAgenticWallet": {
415
+ const label = this.getNodeParameter("agenticLabel", i) as string;
416
+ const spendingLimitCents = this.getNodeParameter("spendingLimitCents", i) as number;
417
+ const dailyLimitCents = this.getNodeParameter("dailyLimitCents", i, 0) as number;
418
+ const allowedDomainsRaw = this.getNodeParameter("allowedDomains", i, "") as string;
419
+
420
+ if (!label || typeof label !== "string") {
421
+ throw new NodeOperationError(this.getNode(), "Label is required", { itemIndex: i });
422
+ }
423
+ if (label.length > 100) {
424
+ throw new NodeOperationError(
425
+ this.getNode(),
426
+ "Label must be 100 characters or fewer",
427
+ { itemIndex: i },
428
+ );
429
+ }
430
+ if (CONTROL_CHAR_RE.test(label)) {
431
+ throw new NodeOperationError(
432
+ this.getNode(),
433
+ "Label contains invalid control characters",
434
+ { itemIndex: i },
435
+ );
436
+ }
437
+ if (
438
+ !Number.isInteger(spendingLimitCents) ||
439
+ spendingLimitCents <= 0 ||
440
+ spendingLimitCents > 2_147_483_647
441
+ ) {
442
+ throw new NodeOperationError(
443
+ this.getNode(),
444
+ "Spending limit must be a positive integer",
445
+ { itemIndex: i },
446
+ );
447
+ }
448
+
449
+ const body: Record<string, unknown> = { label, spendingLimitCents };
450
+
451
+ if (dailyLimitCents > 0) {
452
+ if (!Number.isInteger(dailyLimitCents) || dailyLimitCents > 1_000_000) {
453
+ throw new NodeOperationError(
454
+ this.getNode(),
455
+ "dailyLimitCents must be an integer between 1 and 1,000,000",
456
+ { itemIndex: i },
457
+ );
458
+ }
459
+ body.dailyLimitCents = dailyLimitCents;
460
+ }
461
+
462
+ if (allowedDomainsRaw && allowedDomainsRaw.trim().length > 0) {
463
+ const domains = allowedDomainsRaw.split(",").map((d) => d.trim()).filter(Boolean);
464
+ if (domains.length > 100) {
465
+ throw new NodeOperationError(
466
+ this.getNode(),
467
+ "allowedDomains may contain at most 100 entries",
468
+ { itemIndex: i },
469
+ );
470
+ }
471
+ for (const d of domains) {
472
+ if (d.length > 253 || !DOMAIN_RE.test(d)) {
473
+ throw new NodeOperationError(
474
+ this.getNode(),
475
+ `Invalid domain: "${d}". Must be a valid domain name (max 253 chars).`,
476
+ { itemIndex: i },
477
+ );
478
+ }
479
+ }
480
+ body.allowedDomains = domains;
481
+ }
482
+
483
+ result = await auth.apiRequest("POST", "/api/agent-wallet", body);
484
+ break;
485
+ }
486
+
487
+ case "fundAgenticWallet": {
488
+ const walletId = this.getNodeParameter("walletId", i) as string;
489
+ const amountCents = this.getNodeParameter("fundAmountCents", i) as number;
490
+ validateUuid(this, walletId, "walletId", i);
491
+
492
+ if (
493
+ !Number.isInteger(amountCents) ||
494
+ amountCents <= 0 ||
495
+ amountCents > 2_147_483_647
496
+ ) {
497
+ throw new NodeOperationError(
498
+ this.getNode(),
499
+ "Amount must be a positive integer",
500
+ { itemIndex: i },
501
+ );
502
+ }
503
+
504
+ result = await auth.apiRequest(
505
+ "POST",
506
+ `/api/agent-wallet/${encodeURIComponent(walletId)}/fund`,
507
+ { amountCents },
508
+ );
509
+ break;
510
+ }
511
+
512
+ case "getAgenticBalance": {
513
+ const walletId = this.getNodeParameter("walletId", i) as string;
514
+ validateUuid(this, walletId, "walletId", i);
515
+ result = await auth.apiRequest(
516
+ "GET",
517
+ `/api/agent-wallet/${encodeURIComponent(walletId)}`,
518
+ );
519
+ break;
520
+ }
521
+
522
+ case "listAgenticWallets": {
523
+ result = await auth.apiRequest("GET", "/api/agent-wallet");
524
+ break;
525
+ }
526
+
527
+ case "getAgenticTransactions": {
528
+ const walletId = this.getNodeParameter("walletId", i) as string;
529
+ const limit = this.getNodeParameter("transactionLimit", i, 20) as number;
530
+ validateUuid(this, walletId, "walletId", i);
531
+ validateLimit(this, limit, i);
532
+
533
+ const params = new URLSearchParams();
534
+ params.set("limit", String(limit));
535
+ result = await auth.apiRequest(
536
+ "GET",
537
+ `/api/agent-wallet/${encodeURIComponent(walletId)}/transactions?${params.toString()}`,
538
+ );
539
+ break;
540
+ }
541
+
542
+ case "freezeAgenticWallet": {
543
+ const walletId = this.getNodeParameter("walletId", i) as string;
544
+ validateUuid(this, walletId, "walletId", i);
545
+ result = await auth.apiRequest(
546
+ "POST",
547
+ `/api/agent-wallet/${encodeURIComponent(walletId)}/freeze`,
548
+ );
549
+ break;
550
+ }
551
+
552
+ case "unfreezeAgenticWallet": {
553
+ const walletId = this.getNodeParameter("walletId", i) as string;
554
+ validateUuid(this, walletId, "walletId", i);
555
+ result = await auth.apiRequest(
556
+ "POST",
557
+ `/api/agent-wallet/${encodeURIComponent(walletId)}/unfreeze`,
558
+ );
559
+ break;
560
+ }
561
+
562
+ case "deleteAgenticWallet": {
563
+ const walletId = this.getNodeParameter("walletId", i) as string;
564
+ validateUuid(this, walletId, "walletId", i);
565
+ result = await auth.apiRequest(
566
+ "DELETE",
567
+ `/api/agent-wallet/${encodeURIComponent(walletId)}`,
568
+ );
569
+ break;
570
+ }
571
+
572
+ case "updateWalletPolicy": {
573
+ const walletId = this.getNodeParameter("policyWalletId", i) as string;
574
+ const dailyLimitCents = this.getNodeParameter("policyDailyLimitCents", i, 0) as number;
575
+ const allowedDomainsRaw = this.getNodeParameter("policyAllowedDomains", i, "") as string;
576
+
577
+ validateUuid(this, walletId, "policyWalletId", i);
578
+
579
+ const body: Record<string, unknown> = {};
580
+
581
+ if (dailyLimitCents === -1) {
582
+ body.dailyLimitCents = null;
583
+ } else if (dailyLimitCents > 0) {
584
+ if (!Number.isInteger(dailyLimitCents) || dailyLimitCents > 1_000_000) {
585
+ throw new NodeOperationError(
586
+ this.getNode(),
587
+ "dailyLimitCents must be an integer between 1 and 1,000,000 (or -1 to remove)",
588
+ { itemIndex: i },
589
+ );
590
+ }
591
+ body.dailyLimitCents = dailyLimitCents;
592
+ }
593
+
594
+ if (allowedDomainsRaw === "*") {
595
+ body.allowedDomains = null;
596
+ } else if (allowedDomainsRaw && allowedDomainsRaw.trim().length > 0) {
597
+ const domains = allowedDomainsRaw.split(",").map((d) => d.trim()).filter(Boolean);
598
+ if (domains.length > 100) {
599
+ throw new NodeOperationError(
600
+ this.getNode(),
601
+ "allowedDomains may contain at most 100 entries",
602
+ { itemIndex: i },
603
+ );
604
+ }
605
+ for (const d of domains) {
606
+ if (d.length > 253 || !DOMAIN_RE.test(d)) {
607
+ throw new NodeOperationError(
608
+ this.getNode(),
609
+ `Invalid domain: "${d}". Must be a valid domain name (max 253 chars).`,
610
+ { itemIndex: i },
611
+ );
612
+ }
613
+ }
614
+ body.allowedDomains = domains;
615
+ }
616
+
617
+ if (Object.keys(body).length === 0) {
618
+ throw new NodeOperationError(
619
+ this.getNode(),
620
+ "At least one of dailyLimitCents or allowedDomains must be provided",
621
+ { itemIndex: i },
622
+ );
623
+ }
624
+
625
+ result = await auth.apiRequest(
626
+ "PATCH",
627
+ `/api/agent-wallet/${encodeURIComponent(walletId)}/policy`,
628
+ body,
629
+ );
630
+ break;
631
+ }
632
+
633
+ // ----- Teams -----
634
+ case "createTeam": {
635
+ const name = this.getNodeParameter("teamName", i) as string;
636
+ const maxMembers = this.getNodeParameter("maxMembers", i, 10) as number;
637
+
638
+ if (!name || typeof name !== "string") {
639
+ throw new NodeOperationError(this.getNode(), "Team name is required", { itemIndex: i });
640
+ }
641
+ if (name.length > 100) {
642
+ throw new NodeOperationError(
643
+ this.getNode(),
644
+ "Team name must be 100 characters or fewer",
645
+ { itemIndex: i },
646
+ );
647
+ }
648
+ if (CONTROL_CHAR_RE.test(name)) {
649
+ throw new NodeOperationError(
650
+ this.getNode(),
651
+ "Team name contains invalid control characters",
652
+ { itemIndex: i },
653
+ );
654
+ }
655
+
656
+ const body: Record<string, unknown> = { name };
657
+ if (maxMembers) {
658
+ if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
659
+ throw new NodeOperationError(
660
+ this.getNode(),
661
+ "maxMembers must be an integer between 1 and 100",
662
+ { itemIndex: i },
663
+ );
664
+ }
665
+ body.maxMembers = maxMembers;
666
+ }
667
+
668
+ result = await auth.apiRequest("POST", "/api/teams", body);
669
+ break;
670
+ }
671
+
672
+ case "listTeams": {
673
+ result = await auth.apiRequest("GET", "/api/teams");
674
+ break;
675
+ }
676
+
677
+ case "teamDetails": {
678
+ const teamId = this.getNodeParameter("teamId", i) as string;
679
+ validateUuid(this, teamId, "teamId", i);
680
+ result = await auth.apiRequest("GET", `/api/teams/${encodeURIComponent(teamId)}`);
681
+ break;
682
+ }
683
+
684
+ case "fundTeam": {
685
+ const teamId = this.getNodeParameter("teamId", i) as string;
686
+ const amountCents = this.getNodeParameter("teamFundAmountCents", i) as number;
687
+ validateUuid(this, teamId, "teamId", i);
688
+
689
+ if (
690
+ !Number.isInteger(amountCents) ||
691
+ amountCents < 100 ||
692
+ amountCents > 1_000_000
693
+ ) {
694
+ throw new NodeOperationError(
695
+ this.getNode(),
696
+ "amountCents must be between 100 ($1) and 1,000,000 ($10,000)",
697
+ { itemIndex: i },
698
+ );
699
+ }
700
+
701
+ result = await auth.apiRequest(
702
+ "POST",
703
+ `/api/teams/${encodeURIComponent(teamId)}/wallet/fund`,
704
+ { amountCents },
705
+ );
706
+ break;
707
+ }
708
+
709
+ case "createTeamKey": {
710
+ const teamId = this.getNodeParameter("teamId", i) as string;
711
+ const label = this.getNodeParameter("keyLabel", i) as string;
712
+ validateUuid(this, teamId, "teamId", i);
713
+
714
+ if (!label || typeof label !== "string") {
715
+ throw new NodeOperationError(this.getNode(), "Key label is required", { itemIndex: i });
716
+ }
717
+ if (label.length > 100) {
718
+ throw new NodeOperationError(
719
+ this.getNode(),
720
+ "Key label must be 100 characters or fewer",
721
+ { itemIndex: i },
722
+ );
723
+ }
724
+ if (CONTROL_CHAR_RE.test(label)) {
725
+ throw new NodeOperationError(
726
+ this.getNode(),
727
+ "Key label contains invalid control characters",
728
+ { itemIndex: i },
729
+ );
730
+ }
731
+
732
+ result = await auth.apiRequest(
733
+ "POST",
734
+ `/api/teams/${encodeURIComponent(teamId)}/keys`,
735
+ { label },
736
+ );
737
+ break;
738
+ }
739
+
740
+ case "teamUsage": {
741
+ const teamId = this.getNodeParameter("teamId", i) as string;
742
+ const limit = this.getNodeParameter("transactionLimit", i, 20) as number;
743
+ validateUuid(this, teamId, "teamId", i);
744
+ validateLimit(this, limit, i);
745
+
746
+ const params = new URLSearchParams();
747
+ params.set("limit", String(limit));
748
+ result = await auth.apiRequest(
749
+ "GET",
750
+ `/api/teams/${encodeURIComponent(teamId)}/wallet/transactions?${params.toString()}`,
751
+ );
752
+ break;
753
+ }
754
+
755
+ case "updateTeam": {
756
+ const teamId = this.getNodeParameter("teamId", i) as string;
757
+ validateUuid(this, teamId, "teamId", i);
758
+
759
+ const body: Record<string, unknown> = {};
760
+ const newName = this.getNodeParameter("updateTeamName", i, "") as string;
761
+ const newMax = this.getNodeParameter("updateMaxMembers", i, 0) as number;
762
+
763
+ if (newName) {
764
+ if (newName.length > 100) {
765
+ throw new NodeOperationError(
766
+ this.getNode(),
767
+ "Team name must be 100 characters or fewer",
768
+ { itemIndex: i },
769
+ );
770
+ }
771
+ if (CONTROL_CHAR_RE.test(newName)) {
772
+ throw new NodeOperationError(
773
+ this.getNode(),
774
+ "Team name contains invalid control characters",
775
+ { itemIndex: i },
776
+ );
777
+ }
778
+ body.name = newName;
779
+ }
780
+
781
+ if (newMax > 0) {
782
+ if (!Number.isInteger(newMax) || newMax < 1 || newMax > 100) {
783
+ throw new NodeOperationError(
784
+ this.getNode(),
785
+ "maxMembers must be an integer between 1 and 100",
786
+ { itemIndex: i },
787
+ );
788
+ }
789
+ body.maxMembers = newMax;
790
+ }
791
+
792
+ if (Object.keys(body).length === 0) {
793
+ throw new NodeOperationError(
794
+ this.getNode(),
795
+ "At least one of name or maxMembers must be provided",
796
+ { itemIndex: i },
797
+ );
798
+ }
799
+
800
+ result = await auth.apiRequest(
801
+ "PATCH",
802
+ `/api/teams/${encodeURIComponent(teamId)}`,
803
+ body,
804
+ );
805
+ break;
806
+ }
807
+
808
+ case "updateTeamMemberRole": {
809
+ const teamId = this.getNodeParameter("teamId", i) as string;
810
+ const userId = this.getNodeParameter("userId", i) as string;
811
+ const role = this.getNodeParameter("role", i) as string;
812
+
813
+ validateUuid(this, teamId, "teamId", i);
814
+ validateUuid(this, userId, "userId", i);
815
+
816
+ if (role !== "member" && role !== "admin") {
817
+ throw new NodeOperationError(
818
+ this.getNode(),
819
+ "Role must be 'member' or 'admin'",
820
+ { itemIndex: i },
821
+ );
822
+ }
823
+
824
+ result = await auth.apiRequest(
825
+ "PATCH",
826
+ `/api/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`,
827
+ { role },
828
+ );
829
+ break;
830
+ }
831
+
832
+ case "x402Info": {
833
+ result = await auth.apiRequest("GET", "/api/x402/info");
834
+ break;
835
+ }
836
+
837
+ default:
838
+ throw new NodeOperationError(
839
+ this.getNode(),
840
+ `Unknown operation: ${operation}`,
841
+ { itemIndex: i },
842
+ );
843
+ }
844
+
845
+ returnData.push({ json: (result ?? {}) as IDataObject });
846
+ } catch (err) {
847
+ if (this.continueOnFail()) {
848
+ returnData.push({
849
+ json: {
850
+ error: sanitizeError(err instanceof Error ? err.message : String(err)),
851
+ },
852
+ });
853
+ continue;
854
+ }
855
+ if (err instanceof NodeOperationError) throw err;
856
+ throw new NodeOperationError(
857
+ this.getNode(),
858
+ sanitizeError(err instanceof Error ? err.message : String(err)),
859
+ { itemIndex: i },
860
+ );
861
+ }
862
+ }
863
+
864
+ return [returnData];
865
+ }
866
+ }
867
+
868
+ // ---------------------------------------------------------------------------
869
+ // Validation helpers
870
+ // ---------------------------------------------------------------------------
871
+
872
+ function validateUuid(
873
+ ctx: IExecuteFunctions,
874
+ value: string,
875
+ fieldName: string,
876
+ itemIndex: number,
877
+ ): void {
878
+ if (!value || typeof value !== "string") {
879
+ throw new NodeOperationError(ctx.getNode(), `${fieldName} is required`, { itemIndex });
880
+ }
881
+ if (!UUID_RE.test(value)) {
882
+ throw new NodeOperationError(ctx.getNode(), `${fieldName} must be a valid UUID`, { itemIndex });
883
+ }
884
+ }
885
+
886
+ function validateLimit(
887
+ ctx: IExecuteFunctions,
888
+ limit: number,
889
+ itemIndex: number,
890
+ ): void {
891
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
892
+ throw new NodeOperationError(
893
+ ctx.getNode(),
894
+ "Limit must be an integer between 1 and 100",
895
+ { itemIndex },
896
+ );
897
+ }
898
+ }