@dexterai/opendexter-plugin 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 ADDED
@@ -0,0 +1,97 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Dexter-DAO/dexter-x402-sdk/main/assets/dexter-wordmark.svg" alt="Dexter" width="360">
3
+ </p>
4
+
5
+ <h1 align="center">@dexterai/opendexter-plugin</h1>
6
+
7
+ <p align="center">
8
+ <strong>OpenDexter plugin for OpenClaw — search, price-check, and pay for x402 APIs with automatic USDC settlement.</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@dexterai/opendexter-plugin"><img src="https://img.shields.io/npm/v/@dexterai/opendexter-plugin.svg" alt="npm"></a>
13
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E=18-brightgreen.svg" alt="Node"></a>
14
+ <a href="https://clawhub.ai/skills/opendexter"><img src="https://img.shields.io/badge/ClawHub-opendexter-blue" alt="ClawHub"></a>
15
+ <a href="https://dexter.cash/opendexter"><img src="https://img.shields.io/badge/Marketplace-OpenDexter-success" alt="Marketplace"></a>
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## What It Does
21
+
22
+ Gives any OpenClaw agent access to the x402 API marketplace. The agent can discover paid APIs, preview pricing, and call endpoints with automatic USDC payment — no API keys or subscriptions needed for the endpoints themselves.
23
+
24
+ | Tool | Purpose |
25
+ |------|---------|
26
+ | `x402_search` | Search the OpenDexter marketplace for paid APIs |
27
+ | `x402_check` | Probe an endpoint for pricing without paying |
28
+ | `x402_fetch` | Call any x402 API with automatic payment |
29
+ | `x402_pay` | Alias for `x402_fetch` |
30
+ | `x402_wallet` | Show wallet configuration and status |
31
+
32
+ Payments work across **Solana, Base, Polygon, Arbitrum, Optimism, and Avalanche**. The plugin auto-selects the best-funded chain.
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ### From ClawHub
39
+
40
+ ```bash
41
+ openclaw plugins install @dexterai/opendexter-plugin
42
+ ```
43
+
44
+ ### From npm
45
+
46
+ ```bash
47
+ npm install @dexterai/opendexter-plugin
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Configuration
53
+
54
+ Set wallet keys in the OpenClaw plugin config or as environment variables:
55
+
56
+ | Config Key | Env Var | Description |
57
+ |-----------|---------|-------------|
58
+ | `svmPrivateKey` | `SVM_PRIVATE_KEY` | Solana wallet (base58) |
59
+ | `evmPrivateKey` | `EVM_PRIVATE_KEY` | EVM wallet (hex, 0x...) |
60
+ | `defaultNetwork` | `DEFAULT_NETWORK` | Preferred chain (default: auto) |
61
+ | `maxPaymentUSDC` | `MAX_PAYMENT_USDC` | Per-call spending limit (default: $0.50) |
62
+ | `facilitatorUrl` | `FACILITATOR_URL` | Settlement processor (default: https://x402.dexter.cash) |
63
+ | `marketplaceUrl` | `MARKETPLACE_URL` | Marketplace API endpoint |
64
+
65
+ At minimum, configure one wallet key (Solana or EVM) and fund it with USDC.
66
+
67
+ ---
68
+
69
+ ## How It Works
70
+
71
+ ```
72
+ User: "Find me a sentiment analysis API"
73
+ → x402_search("sentiment analysis")
74
+ → Shows results with prices and quality scores
75
+
76
+ User: "How much does that one cost?"
77
+ → x402_check(url)
78
+ → Shows per-chain pricing
79
+
80
+ User: "Call it"
81
+ → x402_fetch(url, params)
82
+ → Signs USDC payment, calls endpoint, returns data + receipt
83
+ ```
84
+
85
+ Settlement goes through the [Dexter facilitator](https://x402.dexter.cash) — zero fees, zero gas for the caller.
86
+
87
+ ---
88
+
89
+ ## Pinata Agent Template
90
+
91
+ This plugin powers the [OpenDexter Agent](https://agents.pinata.cloud) template for Pinata. Deploy a fully configured x402 agent in one click.
92
+
93
+ ---
94
+
95
+ ## Built By
96
+
97
+ [Dexter Intelligence](https://dexter.cash) — the payment, discovery, and monetization layer for the agent economy.
package/index.ts ADDED
@@ -0,0 +1,649 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { wrapFetch, type WrapFetchOptions } from "@dexterai/x402/client";
4
+
5
+ // =============================================================================
6
+ // Types
7
+ // =============================================================================
8
+
9
+ type PluginConfig = {
10
+ svmPrivateKey?: string;
11
+ evmPrivateKey?: string;
12
+ defaultNetwork?: string;
13
+ maxPaymentUSDC?: string;
14
+ facilitatorUrl?: string;
15
+ marketplaceUrl?: string;
16
+ };
17
+
18
+ type MarketplaceResource = {
19
+ name: string;
20
+ url: string;
21
+ method: string;
22
+ price: string;
23
+ network: string | null;
24
+ description: string;
25
+ category: string;
26
+ qualityScore: number | null;
27
+ verified: boolean;
28
+ totalCalls: number;
29
+ totalVolume: string | null;
30
+ seller: string | null;
31
+ sellerReputation: number | null;
32
+ };
33
+
34
+ // =============================================================================
35
+ // Constants
36
+ // =============================================================================
37
+
38
+ const DEFAULT_FACILITATOR_URL = "https://x402.dexter.cash";
39
+ const DEFAULT_MARKETPLACE_URL =
40
+ "https://x402.dexter.cash/api/facilitator/marketplace/resources";
41
+
42
+ const NETWORK_TO_CAIP2: Record<string, string> = {
43
+ solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
44
+ "solana-devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
45
+ base: "eip155:8453",
46
+ "base-sepolia": "eip155:84532",
47
+ polygon: "eip155:137",
48
+ arbitrum: "eip155:42161",
49
+ optimism: "eip155:10",
50
+ avalanche: "eip155:43114",
51
+ };
52
+
53
+ // =============================================================================
54
+ // Marketplace Search
55
+ // =============================================================================
56
+
57
+ async function searchMarketplace(
58
+ query?: string,
59
+ options?: {
60
+ network?: string;
61
+ verified?: boolean;
62
+ category?: string;
63
+ maxPriceUsdc?: number;
64
+ sort?: string;
65
+ limit?: number;
66
+ marketplaceUrl?: string;
67
+ }
68
+ ): Promise<{ resources: MarketplaceResource[]; total: number }> {
69
+ const baseUrl = options?.marketplaceUrl || DEFAULT_MARKETPLACE_URL;
70
+ const params = new URLSearchParams();
71
+
72
+ if (query) params.set("search", query);
73
+ if (options?.network) params.set("network", options.network);
74
+ if (options?.verified) params.set("verified", "true");
75
+ if (options?.category) params.set("category", options.category);
76
+ if (options?.maxPriceUsdc != null)
77
+ params.set("maxPrice", String(options.maxPriceUsdc));
78
+ params.set("sort", options?.sort || "marketplace");
79
+ params.set("order", "desc");
80
+ params.set("limit", String(Math.min(options?.limit || 20, 50)));
81
+
82
+ const response = await fetch(`${baseUrl}?${params}`, {
83
+ headers: { Accept: "application/json" },
84
+ signal: AbortSignal.timeout(15_000),
85
+ });
86
+
87
+ if (!response.ok) {
88
+ throw new Error(
89
+ `Marketplace search failed: ${response.status} ${response.statusText}`
90
+ );
91
+ }
92
+
93
+ const data = (await response.json()) as {
94
+ ok?: boolean;
95
+ resources?: Array<Record<string, unknown>>;
96
+ total?: number;
97
+ };
98
+
99
+ const resources = (data.resources || []).map(
100
+ (r: Record<string, unknown>) => ({
101
+ name: (r.displayName as string) || (r.resourceUrl as string),
102
+ url: r.resourceUrl as string,
103
+ method: (r.method as string) || "GET",
104
+ price:
105
+ (r.priceLabel as string) ||
106
+ (r.priceUsdc != null ? `$${Number(r.priceUsdc).toFixed(2)}` : "free"),
107
+ network: (r.priceNetwork as string) || null,
108
+ description: (r.description as string) || "",
109
+ category: (r.category as string) || "uncategorized",
110
+ qualityScore: (r.qualityScore as number) ?? null,
111
+ verified: r.verificationStatus === "pass",
112
+ totalCalls: (r.totalSettlements as number) ?? 0,
113
+ totalVolume:
114
+ r.totalVolumeUsdc != null
115
+ ? `$${Number(r.totalVolumeUsdc).toLocaleString()}`
116
+ : null,
117
+ seller:
118
+ ((r.seller as Record<string, unknown>)?.displayName as string) || null,
119
+ sellerReputation: (r.reputationScore as number) ?? null,
120
+ })
121
+ );
122
+
123
+ return { resources, total: data.total || resources.length };
124
+ }
125
+
126
+ // =============================================================================
127
+ // x402 Fetch Client
128
+ // =============================================================================
129
+
130
+ function buildX402Fetch(config: PluginConfig): typeof fetch | null {
131
+ if (!config.svmPrivateKey && !config.evmPrivateKey) return null;
132
+
133
+ const opts: WrapFetchOptions = { verbose: false };
134
+
135
+ if (config.svmPrivateKey) opts.walletPrivateKey = config.svmPrivateKey;
136
+ if (config.evmPrivateKey) opts.evmPrivateKey = config.evmPrivateKey;
137
+ if (config.facilitatorUrl) opts.facilitatorUrl = config.facilitatorUrl;
138
+ if (config.defaultNetwork) {
139
+ opts.preferredNetwork =
140
+ NETWORK_TO_CAIP2[config.defaultNetwork] || config.defaultNetwork;
141
+ }
142
+ if (config.maxPaymentUSDC) {
143
+ const [whole, fraction = ""] = config.maxPaymentUSDC.split(".");
144
+ opts.maxAmountAtomic = `${whole}${(fraction + "000000").slice(0, 6)}`;
145
+ }
146
+
147
+ return wrapFetch(globalThis.fetch, opts);
148
+ }
149
+
150
+ // =============================================================================
151
+ // Plugin Entry
152
+ // =============================================================================
153
+
154
+ export default definePluginEntry({
155
+ id: "opendexter",
156
+ name: "OpenDexter",
157
+ description:
158
+ "x402 marketplace access for OpenClaw agents. Search, price-check, and pay for paid APIs with USDC across Solana, Base, Polygon, Arbitrum, Optimism, and Avalanche.",
159
+
160
+ register(api) {
161
+ const raw = (api.pluginConfig || {}) as PluginConfig;
162
+
163
+ // Merge plugin config with environment variable fallbacks.
164
+ // This lets Pinata secrets (env vars) feed the plugin when the
165
+ // plugin config UI isn't available.
166
+ const config: PluginConfig = {
167
+ svmPrivateKey: raw.svmPrivateKey || process.env.SVM_PRIVATE_KEY,
168
+ evmPrivateKey: raw.evmPrivateKey || process.env.EVM_PRIVATE_KEY,
169
+ defaultNetwork: raw.defaultNetwork || process.env.DEFAULT_NETWORK,
170
+ maxPaymentUSDC: raw.maxPaymentUSDC || process.env.MAX_PAYMENT_USDC || "0.50",
171
+ facilitatorUrl: raw.facilitatorUrl || process.env.FACILITATOR_URL,
172
+ marketplaceUrl: raw.marketplaceUrl || process.env.MARKETPLACE_URL,
173
+ };
174
+
175
+ let cachedFetch: typeof fetch | null | undefined;
176
+
177
+ const getClient = () => {
178
+ if (cachedFetch !== undefined) return cachedFetch;
179
+ cachedFetch = buildX402Fetch(config);
180
+ return cachedFetch;
181
+ };
182
+
183
+ // ----- x402_search -----
184
+ api.registerTool({
185
+ name: "x402_search",
186
+ description:
187
+ "Search the OpenDexter marketplace for paid APIs. Returns quality-ranked results with pricing, verification status, and seller reputation.",
188
+ parameters: Type.Object({
189
+ query: Type.Optional(
190
+ Type.String({
191
+ description:
192
+ 'Search query (e.g. "token analytics", "sentiment", "image generation")',
193
+ })
194
+ ),
195
+ network: Type.Optional(
196
+ Type.String({
197
+ description:
198
+ "Filter by network: solana, base, polygon, arbitrum, optimism, avalanche",
199
+ })
200
+ ),
201
+ verified: Type.Optional(
202
+ Type.Boolean({ description: "Only return quality-verified endpoints" })
203
+ ),
204
+ category: Type.Optional(
205
+ Type.String({ description: "Filter by category" })
206
+ ),
207
+ maxPriceUsdc: Type.Optional(
208
+ Type.Number({ description: "Maximum price per call in USDC" })
209
+ ),
210
+ sort: Type.Optional(
211
+ Type.String({
212
+ description:
213
+ "Sort: marketplace (default), relevance, quality_score, settlements, volume, recent",
214
+ })
215
+ ),
216
+ limit: Type.Optional(
217
+ Type.Number({ description: "Max results (default 20, max 50)" })
218
+ ),
219
+ }),
220
+
221
+ async execute(_id, input) {
222
+ try {
223
+ const result = await searchMarketplace(input.query, {
224
+ network: input.network,
225
+ verified: input.verified,
226
+ category: input.category,
227
+ maxPriceUsdc: input.maxPriceUsdc,
228
+ sort: input.sort,
229
+ limit: Math.min(input.limit || 20, 50),
230
+ marketplaceUrl: config.marketplaceUrl,
231
+ });
232
+
233
+ return {
234
+ content: [
235
+ {
236
+ type: "text" as const,
237
+ text: JSON.stringify(
238
+ {
239
+ success: true,
240
+ total: result.total,
241
+ showing: result.resources.length,
242
+ resources: result.resources,
243
+ source: "OpenDexter (https://dexter.cash)",
244
+ },
245
+ null,
246
+ 2
247
+ ),
248
+ },
249
+ ],
250
+ };
251
+ } catch (error) {
252
+ return {
253
+ content: [
254
+ {
255
+ type: "text" as const,
256
+ text: JSON.stringify({
257
+ success: false,
258
+ error:
259
+ error instanceof Error ? error.message : String(error),
260
+ }),
261
+ },
262
+ ],
263
+ };
264
+ }
265
+ },
266
+ });
267
+
268
+ // ----- x402_check -----
269
+ api.registerTool({
270
+ name: "x402_check",
271
+ description:
272
+ "Probe an x402 endpoint to see its pricing per chain without paying. Use before x402_fetch to show the user what a call will cost.",
273
+ parameters: Type.Object({
274
+ url: Type.String({ description: "The URL to check" }),
275
+ method: Type.Optional(
276
+ Type.String({ description: "HTTP method to probe (default: GET)" })
277
+ ),
278
+ }),
279
+
280
+ async execute(_id, input) {
281
+ const url = input.url;
282
+ const method = (input.method || "GET").toUpperCase();
283
+
284
+ try {
285
+ const hasBody = ["POST", "PUT", "PATCH"].includes(method);
286
+ const res = await fetch(url, {
287
+ method,
288
+ ...(hasBody
289
+ ? { headers: { "Content-Type": "application/json" }, body: "{}" }
290
+ : {}),
291
+ signal: AbortSignal.timeout(15_000),
292
+ });
293
+
294
+ if (res.status === 401 || res.status === 403) {
295
+ return {
296
+ content: [
297
+ {
298
+ type: "text" as const,
299
+ text: JSON.stringify({
300
+ error: true,
301
+ statusCode: res.status,
302
+ authRequired: true,
303
+ message:
304
+ "Provider authentication required before x402 payment.",
305
+ }),
306
+ },
307
+ ],
308
+ };
309
+ }
310
+
311
+ if (res.status !== 402) {
312
+ return {
313
+ content: [
314
+ {
315
+ type: "text" as const,
316
+ text: JSON.stringify({
317
+ requiresPayment: false,
318
+ statusCode: res.status,
319
+ free: res.ok,
320
+ }),
321
+ },
322
+ ],
323
+ };
324
+ }
325
+
326
+ let body: Record<string, unknown> | null = null;
327
+ try {
328
+ body = (await res.json()) as Record<string, unknown>;
329
+ } catch {
330
+ // non-JSON 402 body
331
+ }
332
+
333
+ const accepts =
334
+ (body?.accepts as Array<Record<string, unknown>>) || [];
335
+ const paymentOptions = accepts.map((a) => {
336
+ const amount = Number(a.amount || a.maxAmountRequired || 0);
337
+ const decimals = Number(
338
+ (a.extra as Record<string, unknown>)?.decimals ?? 6
339
+ );
340
+ return {
341
+ price: amount / Math.pow(10, decimals),
342
+ priceFormatted: `$${(amount / Math.pow(10, decimals)).toFixed(2)}`,
343
+ network: a.network,
344
+ scheme: a.scheme,
345
+ asset: a.asset,
346
+ payTo: a.payTo,
347
+ };
348
+ });
349
+
350
+ return {
351
+ content: [
352
+ {
353
+ type: "text" as const,
354
+ text: JSON.stringify(
355
+ {
356
+ requiresPayment: true,
357
+ statusCode: 402,
358
+ x402Version: body?.x402Version ?? 2,
359
+ paymentOptions,
360
+ resource: body?.resource ?? null,
361
+ schema: accepts[0]?.outputSchema ?? null,
362
+ },
363
+ null,
364
+ 2
365
+ ),
366
+ },
367
+ ],
368
+ };
369
+ } catch (error) {
370
+ return {
371
+ content: [
372
+ {
373
+ type: "text" as const,
374
+ text: JSON.stringify({
375
+ error: true,
376
+ message:
377
+ error instanceof Error ? error.message : String(error),
378
+ }),
379
+ },
380
+ ],
381
+ };
382
+ }
383
+ },
384
+ });
385
+
386
+ // ----- Shared fetch+pay execution -----
387
+ const executeFetch = async (input: Record<string, unknown>) => {
388
+ const url = input.url as string;
389
+ const method = ((input.method as string) || "GET").toUpperCase();
390
+
391
+ const fetchClient = getClient();
392
+ if (!fetchClient) {
393
+ return {
394
+ content: [
395
+ {
396
+ type: "text" as const,
397
+ text: JSON.stringify({
398
+ success: false,
399
+ error:
400
+ "No wallet configured. Set svmPrivateKey or evmPrivateKey in the OpenDexter plugin config.",
401
+ }),
402
+ },
403
+ ],
404
+ };
405
+ }
406
+
407
+ try {
408
+ let requestUrl = url;
409
+ let body: string | undefined;
410
+ const requestHeaders: Record<string, string> = {
411
+ Accept: "application/json",
412
+ "User-Agent": "opendexter-plugin/1.0",
413
+ ...((input.headers as Record<string, string>) || {}),
414
+ };
415
+
416
+ if (
417
+ method === "GET" &&
418
+ input.params &&
419
+ typeof input.params === "object"
420
+ ) {
421
+ const urlObj = new URL(url);
422
+ for (const [key, value] of Object.entries(
423
+ input.params as Record<string, unknown>
424
+ )) {
425
+ if (value !== undefined && value !== null) {
426
+ urlObj.searchParams.set(key, String(value));
427
+ }
428
+ }
429
+ requestUrl = urlObj.toString();
430
+ } else if (input.params !== undefined && input.params !== null) {
431
+ body =
432
+ typeof input.params === "string"
433
+ ? input.params
434
+ : JSON.stringify(input.params);
435
+ if (!requestHeaders["Content-Type"]) {
436
+ requestHeaders["Content-Type"] = "application/json";
437
+ }
438
+ }
439
+
440
+ const response = await fetchClient(requestUrl, {
441
+ method,
442
+ headers: requestHeaders,
443
+ body,
444
+ signal: AbortSignal.timeout(30_000),
445
+ });
446
+
447
+ const contentType = response.headers.get("content-type") || "";
448
+ let data: unknown;
449
+ if (contentType.includes("application/json")) {
450
+ data = await response.json().catch(() => null);
451
+ } else if (contentType.startsWith("text/")) {
452
+ data = await response.text();
453
+ } else {
454
+ data = `[Binary: ${contentType}, ${response.headers.get("content-length") || "unknown"} bytes]`;
455
+ }
456
+
457
+ let paidUsdc: string | null = null;
458
+ const paymentHeader =
459
+ response.headers.get("PAYMENT-RESPONSE") ||
460
+ response.headers.get("x-payment-response");
461
+ if (paymentHeader) {
462
+ try {
463
+ const payment = JSON.parse(atob(paymentHeader));
464
+ const rawAmount =
465
+ payment.amount || payment.paidAmount || payment.value;
466
+ if (rawAmount) {
467
+ paidUsdc = (Number(rawAmount) / 1_000_000).toFixed(6);
468
+ }
469
+ } catch {
470
+ // ignore malformed receipt
471
+ }
472
+ }
473
+
474
+ return {
475
+ content: [
476
+ {
477
+ type: "text" as const,
478
+ text: JSON.stringify(
479
+ {
480
+ success: response.ok,
481
+ statusCode: response.status,
482
+ data,
483
+ ...(paidUsdc ? { paidUsdc: `$${paidUsdc}` } : {}),
484
+ network: config.defaultNetwork || "auto",
485
+ },
486
+ null,
487
+ 2
488
+ ),
489
+ },
490
+ ],
491
+ };
492
+ } catch (error) {
493
+ const msg = error instanceof Error ? error.message : String(error);
494
+
495
+ if (msg.includes("amount_exceeds_max")) {
496
+ return {
497
+ content: [
498
+ {
499
+ type: "text" as const,
500
+ text: JSON.stringify({
501
+ success: false,
502
+ error: `Payment exceeds configured max of $${config.maxPaymentUSDC || "0.50"} USDC. Adjust maxPaymentUSDC in plugin config.`,
503
+ blocked: true,
504
+ }),
505
+ },
506
+ ],
507
+ };
508
+ }
509
+
510
+ if (msg.includes("insufficient_balance")) {
511
+ return {
512
+ content: [
513
+ {
514
+ type: "text" as const,
515
+ text: JSON.stringify({
516
+ success: false,
517
+ error: "Insufficient USDC balance.",
518
+ help: "Fund the wallet with USDC on the appropriate network.",
519
+ }),
520
+ },
521
+ ],
522
+ };
523
+ }
524
+
525
+ return {
526
+ content: [
527
+ {
528
+ type: "text" as const,
529
+ text: JSON.stringify({ success: false, error: msg }),
530
+ },
531
+ ],
532
+ };
533
+ }
534
+ };
535
+
536
+ const fetchParams = Type.Object({
537
+ url: Type.String({ description: "The x402 endpoint URL to call" }),
538
+ method: Type.Optional(
539
+ Type.String({ description: "HTTP method (default: GET)" })
540
+ ),
541
+ params: Type.Optional(
542
+ Type.Unknown({
543
+ description: "Query params (GET) or JSON body (POST/PUT/PATCH)",
544
+ })
545
+ ),
546
+ headers: Type.Optional(
547
+ Type.Unknown({ description: "Custom request headers" })
548
+ ),
549
+ });
550
+
551
+ // ----- x402_fetch -----
552
+ api.registerTool({
553
+ name: "x402_fetch",
554
+ description:
555
+ "Call any x402-protected API with automatic USDC payment. Returns the API response and payment receipt.",
556
+ parameters: fetchParams,
557
+ async execute(_id, input) {
558
+ return executeFetch(input);
559
+ },
560
+ });
561
+
562
+ // ----- x402_pay -----
563
+ api.registerTool({
564
+ name: "x402_pay",
565
+ description:
566
+ "Alias for x402_fetch. Call any x402 API with automatic payment.",
567
+ parameters: fetchParams,
568
+ async execute(_id, input) {
569
+ return executeFetch(input);
570
+ },
571
+ });
572
+
573
+ // ----- x402_wallet -----
574
+ api.registerTool({
575
+ name: "x402_wallet",
576
+ description:
577
+ "Show wallet configuration: active networks, spending limit, facilitator. Use when the user asks about their wallet setup.",
578
+ parameters: Type.Object({}),
579
+
580
+ async execute() {
581
+ if (!config.svmPrivateKey && !config.evmPrivateKey) {
582
+ return {
583
+ content: [
584
+ {
585
+ type: "text" as const,
586
+ text: JSON.stringify({
587
+ configured: false,
588
+ error: "No wallet configured.",
589
+ help: "Set svmPrivateKey or evmPrivateKey in the OpenDexter plugin config.",
590
+ }),
591
+ },
592
+ ],
593
+ };
594
+ }
595
+
596
+ const wallets: Array<{
597
+ type: string;
598
+ networks: string[];
599
+ configured: boolean;
600
+ }> = [];
601
+
602
+ if (config.svmPrivateKey) {
603
+ wallets.push({
604
+ type: "solana",
605
+ networks: ["solana"],
606
+ configured: true,
607
+ });
608
+ }
609
+
610
+ if (config.evmPrivateKey) {
611
+ wallets.push({
612
+ type: "evm",
613
+ networks: ["base", "polygon", "arbitrum", "optimism", "avalanche"],
614
+ configured: true,
615
+ });
616
+ }
617
+
618
+ return {
619
+ content: [
620
+ {
621
+ type: "text" as const,
622
+ text: JSON.stringify(
623
+ {
624
+ configured: true,
625
+ wallets,
626
+ defaultNetwork:
627
+ config.defaultNetwork || "auto",
628
+ maxPaymentPerCall: `$${config.maxPaymentUSDC || "0.50"} USDC`,
629
+ facilitator:
630
+ config.facilitatorUrl || DEFAULT_FACILITATOR_URL,
631
+ },
632
+ null,
633
+ 2
634
+ ),
635
+ },
636
+ ],
637
+ };
638
+ },
639
+ });
640
+
641
+ api.logger.info("OpenDexter plugin registered");
642
+ api.logger.info(
643
+ ` Wallet: ${config.svmPrivateKey || config.evmPrivateKey ? "configured" : "not configured"}`
644
+ );
645
+ api.logger.info(
646
+ ` Marketplace: ${config.marketplaceUrl || DEFAULT_MARKETPLACE_URL}`
647
+ );
648
+ },
649
+ });
package/openclaw.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ declare module "openclaw/plugin-sdk/plugin-entry" {
2
+ interface PluginToolDef {
3
+ name: string;
4
+ description: string;
5
+ parameters: unknown;
6
+ execute: (...args: any[]) => Promise<any>;
7
+ }
8
+
9
+ interface PluginApi {
10
+ pluginConfig: Record<string, unknown>;
11
+ logger: {
12
+ info: (msg: string) => void;
13
+ warn: (msg: string) => void;
14
+ error: (msg: string) => void;
15
+ debug: (msg: string) => void;
16
+ };
17
+ registerTool: (tool: PluginToolDef) => void;
18
+ }
19
+
20
+ interface PluginEntryOptions {
21
+ id: string;
22
+ name: string;
23
+ description: string;
24
+ kind?: string;
25
+ configSchema?: unknown;
26
+ register: (api: PluginApi) => void;
27
+ }
28
+
29
+ export function definePluginEntry(options: PluginEntryOptions): unknown;
30
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "id": "opendexter",
3
+ "name": "OpenDexter",
4
+ "description": "x402 marketplace access for OpenClaw agents. Search paid APIs, preview pricing, and auto-pay with USDC across Solana, Base, Polygon, Arbitrum, Optimism, and Avalanche.",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "properties": {
9
+ "svmPrivateKey": {
10
+ "type": "string",
11
+ "description": "Solana wallet private key (base58) for USDC payments on Solana"
12
+ },
13
+ "evmPrivateKey": {
14
+ "type": "string",
15
+ "description": "EVM wallet private key (hex, with or without 0x) for USDC payments on Base, Polygon, Arbitrum, Optimism, Avalanche"
16
+ },
17
+ "defaultNetwork": {
18
+ "type": "string",
19
+ "enum": ["solana", "base", "polygon", "arbitrum", "optimism", "avalanche"],
20
+ "default": "solana",
21
+ "description": "Preferred payment network when an endpoint accepts multiple"
22
+ },
23
+ "maxPaymentUSDC": {
24
+ "type": "string",
25
+ "default": "0.50",
26
+ "description": "Maximum USDC to spend per API call (e.g. '0.50' = $0.50)"
27
+ },
28
+ "facilitatorUrl": {
29
+ "type": "string",
30
+ "default": "https://x402.dexter.cash",
31
+ "description": "x402 facilitator URL for payment settlement"
32
+ },
33
+ "marketplaceUrl": {
34
+ "type": "string",
35
+ "default": "https://x402.dexter.cash/api/facilitator/marketplace/resources",
36
+ "description": "OpenDexter marketplace API endpoint"
37
+ }
38
+ }
39
+ },
40
+ "uiHints": {
41
+ "svmPrivateKey": {
42
+ "label": "Solana Private Key",
43
+ "sensitive": true,
44
+ "help": "Base58-encoded Solana private key. Your agent uses this wallet to pay for x402 API calls on Solana."
45
+ },
46
+ "evmPrivateKey": {
47
+ "label": "EVM Private Key",
48
+ "sensitive": true,
49
+ "help": "Hex private key (0x...). Same key works on Base, Polygon, Arbitrum, Optimism, and Avalanche."
50
+ },
51
+ "maxPaymentUSDC": {
52
+ "label": "Max Payment Per Call (USDC)",
53
+ "help": "Safety limit. Rejects any single API call costing more than this amount."
54
+ },
55
+ "facilitatorUrl": {
56
+ "label": "Facilitator URL",
57
+ "advanced": true,
58
+ "help": "Override the x402 payment processor. Default: https://x402.dexter.cash"
59
+ },
60
+ "marketplaceUrl": {
61
+ "label": "Marketplace API URL",
62
+ "advanced": true,
63
+ "help": "Override the OpenDexter marketplace search endpoint."
64
+ }
65
+ },
66
+ "skills": ["skills"]
67
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@dexterai/opendexter-plugin",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "OpenDexter plugin for OpenClaw — search, price-check, and pay for any x402 API with automatic USDC settlement across Solana, Base, Polygon, Arbitrum, Optimism, and Avalanche.",
6
+ "keywords": [
7
+ "openclaw",
8
+ "opendexter",
9
+ "x402",
10
+ "dexter",
11
+ "solana",
12
+ "base",
13
+ "evm",
14
+ "payments",
15
+ "usdc",
16
+ "marketplace",
17
+ "ai-agents",
18
+ "micropayments"
19
+ ],
20
+ "author": "Dexter AI <dev@dexter.cash>",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/Dexter-DAO/opendexter-plugin"
25
+ },
26
+ "homepage": "https://dexter.cash/opendexter",
27
+ "openclaw": {
28
+ "extensions": [
29
+ "./index.ts"
30
+ ],
31
+ "compat": {
32
+ "pluginApi": ">=2026.3.24-beta.2",
33
+ "minGatewayVersion": "2026.3.24-beta.2"
34
+ },
35
+ "build": {
36
+ "openclawVersion": "2026.4.5",
37
+ "pluginSdkVersion": "2026.4.5"
38
+ }
39
+ },
40
+ "dependencies": {
41
+ "@dexterai/x402": "^2.0.0",
42
+ "@sinclair/typebox": "^0.32.0"
43
+ },
44
+ "peerDependencies": {
45
+ "openclaw": "*"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "openclaw": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^25.5.2",
54
+ "openclaw": "^2026.4.8"
55
+ }
56
+ }
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: opendexter
3
+ description: "Use OpenDexter to search, price-check, and pay for any x402 API. Trigger whenever the user wants to find paid APIs, call an x402 endpoint, check pricing, see wallet info, or interact with the x402 marketplace."
4
+ ---
5
+
6
+ # OpenDexter — x402 API Marketplace
7
+
8
+ OpenDexter gives you access to the x402 API marketplace across Solana and EVM chains (Base, Polygon, Arbitrum, Optimism, Avalanche). Search, preview pricing, and call any endpoint with automatic USDC payment. No API keys or subscriptions needed for the endpoints — you just pay per call.
9
+
10
+ ## Tools
11
+
12
+ ### `x402_search` — Find APIs
13
+
14
+ Search the OpenDexter marketplace. Results are ranked by a composite score (quality, usage, freshness, reputation, reliability).
15
+
16
+ Each result includes: name, price (USDC), network, qualityScore (0-100), verified status, seller, and call count.
17
+
18
+ Highlight verified endpoints (qualityScore 75+). Search is fuzzy — typos still find results.
19
+
20
+ ### `x402_check` — Preview Pricing
21
+
22
+ Probe an endpoint for payment requirements without paying. Returns per-chain pricing options. Always use before `x402_fetch` so the user knows the cost upfront.
23
+
24
+ ### `x402_fetch` — Call and Pay
25
+
26
+ Call any x402 endpoint with automatic payment from the configured wallet. The plugin checks USDC balances across all funded chains and picks the best option. Returns the API response data plus a payment receipt.
27
+
28
+ If the wallet has no funds on any accepted chain, tell the user to fund their wallet.
29
+
30
+ ### `x402_pay` — Alias for `x402_fetch`
31
+
32
+ Same tool, alternate name.
33
+
34
+ ### `x402_wallet` — Wallet Status
35
+
36
+ Shows which wallets are configured (Solana, EVM), the per-call spending limit, and the active network. Use when the user asks about their wallet or setup.
37
+
38
+ ## Workflows
39
+
40
+ ### "Find me an API for X"
41
+ 1. `x402_search` with their query
42
+ 2. Present top results with prices and quality scores
43
+ 3. `x402_check` on their pick
44
+ 4. Show per-chain pricing
45
+ 5. `x402_fetch` to call it
46
+
47
+ ### "Call this URL"
48
+ 1. `x402_check` to show the price
49
+ 2. `x402_fetch` to call and pay
50
+
51
+ ### "What's my wallet setup?"
52
+ 1. `x402_wallet`
53
+
54
+ ## Quality Scores
55
+ - 90-100: Excellent. Verified, reliable, well-documented.
56
+ - 75-89: Good. Passed verification.
57
+ - 50-74: Mediocre. May have issues.
58
+ - Below 50: Untested or poor.
59
+
60
+ ## Tips
61
+ - Most endpoints cost $0.01-$0.10 per call.
62
+ - Use `verified: true` to filter to tested endpoints.
63
+ - The plugin auto-selects the cheapest chain that the endpoint accepts and the wallet has funds on.
64
+ - If no wallet is configured, tools return a config help message.
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "declaration": true,
11
+ "sourceMap": false,
12
+ "types": ["node"]
13
+ },
14
+ "include": ["index.ts", "openclaw.d.ts"]
15
+ }