@true402.dev/openclaw-swap-guard 1.0.1 → 1.0.3
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 +10 -5
- package/dist/index.js +42 -5
- package/openclaw.plugin.json +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,13 +28,15 @@ It recognises buys in shell commands the way trading skills run them (`--token-o
|
|
|
28
28
|
|
|
29
29
|
No setup is needed for a few checks a day: the guard uses true402's free daily allowance.
|
|
30
30
|
|
|
31
|
-
For more, give it a **
|
|
31
|
+
For more, give it a **dedicated, low-balance** Base wallet holding a little USDC (gas is sponsored):
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
export TRUE402_PAYER_PRIVATE_KEY=0x…
|
|
34
|
+
export TRUE402_PAYER_PRIVATE_KEY=0x…
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Each check then pays the exact quoted price over [x402](https://x402.org)
|
|
37
|
+
Only this dedicated variable is read — never a generic `PAYER_PRIVATE_KEY` another tool may have set. Each check then pays the exact quoted price over [x402](https://x402.org), no account and no API key. The key stays on your machine (it authorizes each USDC payment locally) and is never written to OpenClaw's config.
|
|
38
|
+
|
|
39
|
+
Spending is bounded twice: **at most $0.02 per check** (`maxAmountUsd`) and **at most 50 paid checks per UTC day** (`maxPaidChecksPerDay`) — at most $1.00 a day by default. The day's count is kept in `$OPENCLAW_STATE_DIR/true402-swap-guard/spend.json` (default `~/.openclaw/…`), so restarting OpenClaw does not reset it; if that file cannot be written, no payment is made. Past the limit only free checks run, and the guard logs a warning. The limit is per machine: several OpenClaw installs with their own state directories each have their own.
|
|
38
40
|
|
|
39
41
|
## Settings
|
|
40
42
|
|
|
@@ -47,11 +49,14 @@ All optional, under the plugin's config (`true402-swap-guard`):
|
|
|
47
49
|
| `onUnchecked` | `allow` | `ask` or `block` when a token could not be checked |
|
|
48
50
|
| `allowTokens` | `[]` | Token addresses never checked |
|
|
49
51
|
| `timeoutMs` | `15000` | Give up on a free check after this long (then it is "unchecked"). A paid check is never abandoned once paid — you get the answer you paid for |
|
|
50
|
-
| `maxAmountUsd` | `0.
|
|
52
|
+
| `maxAmountUsd` | `0.02` | Never pay more than this for one check |
|
|
53
|
+
| `maxPaidChecksPerDay` | `50` | Paid checks per UTC day; after that only free checks run |
|
|
51
54
|
|
|
52
55
|
## What is sent
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
A **free check** sends only the token address and its chain to `true402.dev` — nothing about your wallet, your prompts or the trade.
|
|
58
|
+
|
|
59
|
+
A **paid check** also sends a signed USDC payment authorization. That necessarily reveals the paying wallet's **address** to true402, and the payment is settled on Base, where it is public. That is why the wallet should be a dedicated, low-balance one. The private key itself never leaves your machine.
|
|
55
60
|
|
|
56
61
|
## License
|
|
57
62
|
|
package/dist/index.js
CHANGED
|
@@ -267,6 +267,11 @@ function clean(s, max = 160) {
|
|
|
267
267
|
return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
// src/check.ts
|
|
271
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
272
|
+
import { homedir } from "os";
|
|
273
|
+
import { dirname, join } from "path";
|
|
274
|
+
|
|
270
275
|
// src/x402.ts
|
|
271
276
|
import { randomBytes } from "crypto";
|
|
272
277
|
import { createPublicClient, http, getAddress } from "viem";
|
|
@@ -274,7 +279,7 @@ import { privateKeyToAccount } from "viem/accounts";
|
|
|
274
279
|
import { base } from "viem/chains";
|
|
275
280
|
|
|
276
281
|
// src/client.ts
|
|
277
|
-
var CLIENT = { id: "openclaw-guard", version: "1.0.
|
|
282
|
+
var CLIENT = { id: "openclaw-guard", version: "1.0.3", package: "@true402.dev/openclaw-swap-guard", install: "openclaw plugins install @true402.dev/openclaw-swap-guard" };
|
|
278
283
|
|
|
279
284
|
// src/x402.ts
|
|
280
285
|
var USDC_ABI = [
|
|
@@ -447,6 +452,24 @@ async function payStall(path, payload, opts) {
|
|
|
447
452
|
}
|
|
448
453
|
|
|
449
454
|
// src/check.ts
|
|
455
|
+
function fileSpendStore(path = join(process.env.OPENCLAW_STATE_DIR || join(homedir(), ".openclaw"), "true402-swap-guard", "spend.json")) {
|
|
456
|
+
return {
|
|
457
|
+
read() {
|
|
458
|
+
try {
|
|
459
|
+
const v = JSON.parse(readFileSync(path, "utf8"));
|
|
460
|
+
return { day: typeof v.day === "string" ? v.day : "", count: typeof v.count === "number" && v.count >= 0 ? v.count : 0 };
|
|
461
|
+
} catch {
|
|
462
|
+
return { day: "", count: 0 };
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
write(v) {
|
|
466
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
467
|
+
writeFileSync(path, JSON.stringify(v), { mode: 384 });
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
var DEFAULT_MAX_PER_CHECK_USD = 0.02;
|
|
472
|
+
var DEFAULT_MAX_PAID_CHECKS_PER_DAY = 50;
|
|
450
473
|
var NOT_A_TOKEN = /* @__PURE__ */ new Set(["not_a_contract", "mint_unreadable", "mint_not_initialized"]);
|
|
451
474
|
var SOL_NEVER_SELLABLE = /* @__PURE__ */ new Set(["non_transferable", "permanent_delegate"]);
|
|
452
475
|
var SOL_SELL_BLOCKABLE = /* @__PURE__ */ new Set(["freeze_authority_active", "pausable", "transfer_hook", "default_frozen"]);
|
|
@@ -490,6 +513,10 @@ function strings(v) {
|
|
|
490
513
|
function createChecker(cfg = {}) {
|
|
491
514
|
const baseUrl = cfg.baseUrl ?? "https://true402.dev/api";
|
|
492
515
|
const timeoutMs = cfg.timeoutMs ?? 15e3;
|
|
516
|
+
const perCheck = cfg.maxAmountUsd ?? DEFAULT_MAX_PER_CHECK_USD;
|
|
517
|
+
const perDay = cfg.maxPaidChecksPerDay ?? DEFAULT_MAX_PAID_CHECKS_PER_DAY;
|
|
518
|
+
const now = cfg.now ?? Date.now;
|
|
519
|
+
const store = cfg.spendStore ?? fileSpendStore();
|
|
493
520
|
async function free(path, token) {
|
|
494
521
|
const res = await fetch(`${baseUrl}${path}`, {
|
|
495
522
|
method: "POST",
|
|
@@ -507,8 +534,17 @@ function createChecker(cfg = {}) {
|
|
|
507
534
|
const toVerdict = (body2) => t.chain === "solana" ? solanaVerdict(body2) : evmVerdict(body2, t.chain);
|
|
508
535
|
const body = await free(path, t.token);
|
|
509
536
|
if (body !== null) return toVerdict(body);
|
|
510
|
-
if (!cfg.payerPrivateKey) throw new Error("the free daily checks are used up \u2014 set
|
|
511
|
-
|
|
537
|
+
if (!cfg.payerPrivateKey) throw new Error("the free daily checks are used up \u2014 set TRUE402_PAYER_PRIVATE_KEY to pay per check");
|
|
538
|
+
const today = new Date(now()).toISOString().slice(0, 10);
|
|
539
|
+
const saved = store.read();
|
|
540
|
+
const paidToday = saved.day === today ? saved.count : 0;
|
|
541
|
+
if (paidToday >= perDay) throw new Error(`the daily limit of ${perDay} paid checks is reached (maxPaidChecksPerDay)`);
|
|
542
|
+
try {
|
|
543
|
+
store.write({ day: today, count: paidToday + 1 });
|
|
544
|
+
} catch {
|
|
545
|
+
throw new Error("could not record the paid-check count, so no payment was made");
|
|
546
|
+
}
|
|
547
|
+
return toVerdict(await payStall(path, { token: t.token }, { payerPrivateKey: cfg.payerPrivateKey, baseUrl, maxAmountUsd: perCheck }));
|
|
512
548
|
};
|
|
513
549
|
}
|
|
514
550
|
|
|
@@ -524,7 +560,8 @@ function readConfig(raw) {
|
|
|
524
560
|
onUnchecked: pick(c.onUnchecked, ["allow", "ask", "block"]),
|
|
525
561
|
allowTokens: Array.isArray(c.allowTokens) ? c.allowTokens.filter((x) => typeof x === "string") : void 0,
|
|
526
562
|
timeoutMs: num(c.timeoutMs),
|
|
527
|
-
maxAmountUsd: num(c.maxAmountUsd)
|
|
563
|
+
maxAmountUsd: num(c.maxAmountUsd),
|
|
564
|
+
maxPaidChecksPerDay: num(c.maxPaidChecksPerDay)
|
|
528
565
|
};
|
|
529
566
|
}
|
|
530
567
|
var index_default = definePluginEntry({
|
|
@@ -533,7 +570,7 @@ var index_default = definePluginEntry({
|
|
|
533
570
|
description: "Blocks token buys when true402 finds a honeypot or a rug in progress (Base, Ethereum, BSC, Solana).",
|
|
534
571
|
register(api) {
|
|
535
572
|
const cfg = readConfig(api.pluginConfig);
|
|
536
|
-
const payerPrivateKey = process.env.TRUE402_PAYER_PRIVATE_KEY ||
|
|
573
|
+
const payerPrivateKey = process.env.TRUE402_PAYER_PRIVATE_KEY || void 0;
|
|
537
574
|
const warn = (m) => api.logger?.warn ? api.logger.warn(m) : console.warn(m);
|
|
538
575
|
const guard = createGuard(createChecker({ ...cfg, payerPrivateKey }), { ...cfg, warn });
|
|
539
576
|
api.on("before_tool_call", (event, ctx) => guard(event.toolName, event.params ?? {}, ctx?.abortSignal), { priority: 60 });
|
package/openclaw.plugin.json
CHANGED
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"onUnchecked": { "type": "string", "enum": ["allow", "ask", "block"], "description": "When a token could not be checked (API unreachable, free checks used up): allow (default), ask, or block." },
|
|
16
16
|
"allowTokens": { "type": "array", "items": { "type": "string" }, "description": "Token addresses never checked." },
|
|
17
17
|
"timeoutMs": { "type": "number", "description": "Give up on one check after this many ms (default 15000)." },
|
|
18
|
-
"maxAmountUsd": { "type": "number", "description": "Never
|
|
18
|
+
"maxAmountUsd": { "type": "number", "description": "Never pay more than this for one check, in USDC (default 0.02)." },
|
|
19
|
+
"maxPaidChecksPerDay": { "type": "number", "description": "At most this many paid checks per day; beyond it only free checks run (default 50)." }
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@true402.dev/openclaw-swap-guard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "OpenClaw plugin: before any tool call that buys a token, check it with true402 (executed buy/sell honeypot simulation) and block honeypots and rugs. Base, Ethereum, BSC, Solana. Free daily checks; pay-per-check over x402.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|