@goplausible/openclaw-algorand-plugin 1.9.5 → 2.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/index.ts +74 -529
- package/lib/mcporter.ts +75 -0
- package/lib/workspace.ts +216 -0
- package/openclaw.plugin.json +25 -2
- package/package.json +12 -3
- package/setup.ts +7 -23
- package/scripts/backup-keyring.js +0 -117
- package/scripts/setup-keyring.sh +0 -428
package/lib/mcporter.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
import { ALGORAND_MCP } from "./mcp-servers.js";
|
|
6
|
+
|
|
7
|
+
export function getMcpBinaryPath(pluginRoot: string): string {
|
|
8
|
+
const pluginBin = join(pluginRoot, "node_modules", ".bin", "algorand-mcp");
|
|
9
|
+
return existsSync(pluginBin) ? pluginBin : "algorand-mcp";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isMcpBinaryBundled(pluginRoot: string): boolean {
|
|
13
|
+
return existsSync(join(pluginRoot, "node_modules", ".bin", "algorand-mcp"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function mcporterConfigPath(): string {
|
|
17
|
+
return join(homedir(), ".mcporter", "mcporter.json");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type McporterServerEntry = {
|
|
21
|
+
command: string;
|
|
22
|
+
args?: string[];
|
|
23
|
+
env?: Record<string, string>;
|
|
24
|
+
description?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type McporterConfig = {
|
|
28
|
+
mcpServers: Record<string, McporterServerEntry>;
|
|
29
|
+
imports: string[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function isMcporterConfigured(): boolean {
|
|
33
|
+
const cfgPath = mcporterConfigPath();
|
|
34
|
+
if (!existsSync(cfgPath)) return false;
|
|
35
|
+
try {
|
|
36
|
+
const cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
37
|
+
return Boolean(cfg.mcpServers?.[ALGORAND_MCP.id]);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function upsertMcporterConfig(pluginRoot: string): { success: boolean; message: string } {
|
|
44
|
+
const cfgPath = mcporterConfigPath();
|
|
45
|
+
const cfgDir = dirname(cfgPath);
|
|
46
|
+
|
|
47
|
+
if (!existsSync(cfgDir)) mkdirSync(cfgDir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
let cfg: McporterConfig = { mcpServers: {}, imports: [] };
|
|
50
|
+
if (existsSync(cfgPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
53
|
+
cfg = {
|
|
54
|
+
mcpServers: parsed.mcpServers ?? {},
|
|
55
|
+
imports: Array.isArray(parsed.imports) ? parsed.imports : [],
|
|
56
|
+
};
|
|
57
|
+
} catch (err) {
|
|
58
|
+
return { success: false, message: `Failed to parse ${cfgPath}: ${err}` };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const entry: McporterServerEntry = {
|
|
63
|
+
command: getMcpBinaryPath(pluginRoot),
|
|
64
|
+
description: "Algorand blockchain MCP (GoPlausible)",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const existing = cfg.mcpServers[ALGORAND_MCP.id];
|
|
68
|
+
if (existing?.command === entry.command && existing?.description === entry.description) {
|
|
69
|
+
return { success: true, message: `algorand-mcp already registered in ${cfgPath}` };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
cfg.mcpServers[ALGORAND_MCP.id] = entry;
|
|
73
|
+
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
74
|
+
return { success: true, message: `algorand-mcp registered in ${cfgPath}` };
|
|
75
|
+
}
|
package/lib/workspace.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
import { upsertMcporterConfig } from "./mcporter.js";
|
|
6
|
+
|
|
7
|
+
const PLUGIN_ID = "openclaw-algorand-plugin";
|
|
8
|
+
|
|
9
|
+
export type WorkspaceApi = {
|
|
10
|
+
logger: { info: (m: string) => void; warn: (m: string) => void; error: (m: string) => void };
|
|
11
|
+
runtime?: {
|
|
12
|
+
agent?: {
|
|
13
|
+
resolveAgentWorkspaceDir?: () => string;
|
|
14
|
+
ensureAgentWorkspace?: () => Promise<string> | string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
config: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function resolveWorkspaceDir(api: WorkspaceApi): string {
|
|
21
|
+
const rt = api.runtime?.agent;
|
|
22
|
+
if (rt?.resolveAgentWorkspaceDir) {
|
|
23
|
+
try { return rt.resolveAgentWorkspaceDir(); } catch { /* fall through */ }
|
|
24
|
+
}
|
|
25
|
+
const cfg = api.config as { agents?: { defaults?: { workspace?: string } } };
|
|
26
|
+
return cfg.agents?.defaults?.workspace ?? join(homedir(), ".openclaw", "workspace");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function writeMemoryFile(pluginRoot: string, workspacePath: string): { success: boolean; message: string } {
|
|
30
|
+
const sourceFile = join(pluginRoot, "memory", "algorand-plugin.md");
|
|
31
|
+
const memoryDir = join(workspacePath, "memory");
|
|
32
|
+
const targetFile = join(memoryDir, "algorand-plugin.md");
|
|
33
|
+
|
|
34
|
+
if (!existsSync(sourceFile)) {
|
|
35
|
+
return { success: false, message: `Source memory/algorand-plugin.md not found at ${sourceFile}` };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
|
|
39
|
+
|
|
40
|
+
writeFileSync(targetFile, readFileSync(sourceFile, "utf-8"));
|
|
41
|
+
return { success: true, message: `Plugin memory written to ${targetFile}` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ensureWorkspaceMemoryIndex(pluginRoot: string, workspacePath: string): { success: boolean; message: string } {
|
|
45
|
+
const templateFile = join(pluginRoot, "memory", "MEMORY.md");
|
|
46
|
+
|
|
47
|
+
if (!existsSync(templateFile)) {
|
|
48
|
+
return { success: false, message: "Template MEMORY.md not found in plugin" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const templateContent = readFileSync(templateFile, "utf-8");
|
|
52
|
+
|
|
53
|
+
const neverForgetMatch = templateContent.match(/## NEVER FORGET\n([\s\S]*?)(?=\n## (?!NEVER)|$)/);
|
|
54
|
+
if (!neverForgetMatch) {
|
|
55
|
+
return { success: false, message: "No NEVER FORGET section found in template MEMORY.md" };
|
|
56
|
+
}
|
|
57
|
+
const templateNeverForget = neverForgetMatch[1].trimEnd();
|
|
58
|
+
|
|
59
|
+
const memoryMdPath = join(workspacePath, "MEMORY.md");
|
|
60
|
+
const memoryMdLower = join(workspacePath, "memory.md");
|
|
61
|
+
|
|
62
|
+
const existingPath = existsSync(memoryMdPath) ? memoryMdPath
|
|
63
|
+
: existsSync(memoryMdLower) ? memoryMdLower
|
|
64
|
+
: null;
|
|
65
|
+
|
|
66
|
+
if (!existingPath) {
|
|
67
|
+
if (!existsSync(workspacePath)) mkdirSync(workspacePath, { recursive: true });
|
|
68
|
+
writeFileSync(memoryMdPath, templateContent);
|
|
69
|
+
return { success: true, message: `Created ${memoryMdPath} with NEVER FORGET section` };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let existing = readFileSync(existingPath, "utf-8");
|
|
73
|
+
|
|
74
|
+
if (!/## NEVER FORGET/i.test(existing)) {
|
|
75
|
+
const firstHeadingEnd = existing.match(/^# .+\n/m);
|
|
76
|
+
if (firstHeadingEnd) {
|
|
77
|
+
const insertPos = (firstHeadingEnd.index ?? 0) + firstHeadingEnd[0].length;
|
|
78
|
+
existing = existing.slice(0, insertPos) + "\n## NEVER FORGET\n" + templateNeverForget + "\n\n" + existing.slice(insertPos);
|
|
79
|
+
} else {
|
|
80
|
+
existing = "# OpenClaw Agent Long-Term Memory\n\n## NEVER FORGET\n" + templateNeverForget + "\n\n" + existing;
|
|
81
|
+
}
|
|
82
|
+
writeFileSync(existingPath, existing);
|
|
83
|
+
return { success: true, message: `Added NEVER FORGET section to ${existingPath}` };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const parseSubsections = (text: string): { header: string; content: string }[] => {
|
|
87
|
+
const sections: { header: string; content: string }[] = [];
|
|
88
|
+
const lines = text.split("\n");
|
|
89
|
+
let currentHeader = "";
|
|
90
|
+
let currentLines: string[] = [];
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
if (line.startsWith("### ")) {
|
|
93
|
+
if (currentHeader) sections.push({ header: currentHeader, content: currentLines.join("\n").trimEnd() });
|
|
94
|
+
currentHeader = line;
|
|
95
|
+
currentLines = [];
|
|
96
|
+
} else if (currentHeader) {
|
|
97
|
+
currentLines.push(line);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (currentHeader) sections.push({ header: currentHeader, content: currentLines.join("\n").trimEnd() });
|
|
101
|
+
return sections;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const templateSections = parseSubsections(templateNeverForget);
|
|
105
|
+
const nfSectionMatch = existing.match(/(## NEVER FORGET\n)([\s\S]*?)(?=\n## (?!#)|$)/);
|
|
106
|
+
if (!nfSectionMatch) {
|
|
107
|
+
return { success: true, message: `NEVER FORGET section in ${existingPath} is up to date` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let nfContent = nfSectionMatch[2];
|
|
111
|
+
let updated = false;
|
|
112
|
+
|
|
113
|
+
for (const templateSec of templateSections) {
|
|
114
|
+
if (templateSec.header === "### Never Do This") {
|
|
115
|
+
const neverDoRegex = /(### Never Do This\n)([\s\S]*?)(?=\n### |$)/;
|
|
116
|
+
const existingNeverDoMatch = nfContent.match(neverDoRegex);
|
|
117
|
+
|
|
118
|
+
if (!existingNeverDoMatch) {
|
|
119
|
+
nfContent = nfContent.trimEnd() + "\n\n" + templateSec.header + "\n" + templateSec.content + "\n";
|
|
120
|
+
updated = true;
|
|
121
|
+
} else {
|
|
122
|
+
let existingBullets = existingNeverDoMatch[2];
|
|
123
|
+
const templateBullets = templateSec.content.split("\n").filter((l: string) => l.startsWith("* "));
|
|
124
|
+
const existingBulletLines = existingBullets.split("\n").filter((l: string) => l.startsWith("* "));
|
|
125
|
+
|
|
126
|
+
for (const bullet of templateBullets) {
|
|
127
|
+
const fingerprint = bullet.slice(2, 52).trim();
|
|
128
|
+
const existingMatch = existingBulletLines.find((l: string) => l.includes(fingerprint));
|
|
129
|
+
if (existingMatch) {
|
|
130
|
+
if (existingMatch !== bullet) {
|
|
131
|
+
nfContent = nfContent.replace(existingMatch, bullet);
|
|
132
|
+
updated = true;
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
existingBullets = existingBullets.trimEnd() + "\n" + bullet;
|
|
136
|
+
nfContent = nfContent.replace(existingNeverDoMatch[2], existingBullets);
|
|
137
|
+
updated = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
const escapedHeader = templateSec.header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
143
|
+
const sectionRegex = new RegExp("(" + escapedHeader + "\\n)([\\s\\S]*?)(?=\\n### |$)");
|
|
144
|
+
const existingSecMatch = nfContent.match(sectionRegex);
|
|
145
|
+
|
|
146
|
+
if (existingSecMatch) {
|
|
147
|
+
if (existingSecMatch[2].trimEnd() !== templateSec.content) {
|
|
148
|
+
nfContent = nfContent.replace(existingSecMatch[0], templateSec.header + "\n" + templateSec.content);
|
|
149
|
+
updated = true;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
const neverDoPos = nfContent.indexOf("### Never Do This");
|
|
153
|
+
if (neverDoPos !== -1) {
|
|
154
|
+
nfContent = nfContent.slice(0, neverDoPos) + templateSec.header + "\n" + templateSec.content + "\n\n" + nfContent.slice(neverDoPos);
|
|
155
|
+
} else {
|
|
156
|
+
nfContent = nfContent.trimEnd() + "\n\n" + templateSec.header + "\n" + templateSec.content + "\n";
|
|
157
|
+
}
|
|
158
|
+
updated = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!updated) return { success: true, message: `NEVER FORGET section in ${existingPath} is up to date` };
|
|
164
|
+
|
|
165
|
+
existing = existing.replace(nfSectionMatch[2], nfContent);
|
|
166
|
+
writeFileSync(existingPath, existing);
|
|
167
|
+
return { success: true, message: `Updated NEVER FORGET subsections in ${existingPath}` };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function runFirstLoadInit(api: WorkspaceApi, pluginRoot: string, workspacePath: string): void {
|
|
171
|
+
const markerPath = join(workspacePath, ".openclaw", `${PLUGIN_ID}.initialized`);
|
|
172
|
+
if (existsSync(markerPath)) return;
|
|
173
|
+
|
|
174
|
+
const markerDir = dirname(markerPath);
|
|
175
|
+
if (!existsSync(markerDir)) mkdirSync(markerDir, { recursive: true });
|
|
176
|
+
|
|
177
|
+
const mem = writeMemoryFile(pluginRoot, workspacePath);
|
|
178
|
+
if (mem.success) api.logger.info(`[algorand-plugin] ${mem.message}`);
|
|
179
|
+
else api.logger.warn(`[algorand-plugin] ${mem.message}`);
|
|
180
|
+
|
|
181
|
+
const memIdx = ensureWorkspaceMemoryIndex(pluginRoot, workspacePath);
|
|
182
|
+
if (memIdx.success) api.logger.info(`[algorand-plugin] ${memIdx.message}`);
|
|
183
|
+
else api.logger.warn(`[algorand-plugin] ${memIdx.message}`);
|
|
184
|
+
|
|
185
|
+
const mcp = upsertMcporterConfig(pluginRoot);
|
|
186
|
+
if (mcp.success) api.logger.info(`[algorand-plugin] ${mcp.message}`);
|
|
187
|
+
else api.logger.warn(`[algorand-plugin] ${mcp.message}`);
|
|
188
|
+
|
|
189
|
+
writeFileSync(markerPath, new Date().toISOString());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function writePluginConfig(pluginConfig: Record<string, unknown>): { success: boolean; error?: string } {
|
|
193
|
+
try {
|
|
194
|
+
const configPath = join(homedir(), ".openclaw", "openclaw.json");
|
|
195
|
+
const configDir = dirname(configPath);
|
|
196
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
197
|
+
|
|
198
|
+
let config: Record<string, any> = {};
|
|
199
|
+
if (existsSync(configPath)) {
|
|
200
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
config.plugins ??= {};
|
|
204
|
+
config.plugins.entries ??= {};
|
|
205
|
+
config.plugins.entries[PLUGIN_ID] ??= {};
|
|
206
|
+
config.plugins.entries[PLUGIN_ID].config = pluginConfig;
|
|
207
|
+
|
|
208
|
+
config.plugins.allow ??= [];
|
|
209
|
+
if (!config.plugins.allow.includes(PLUGIN_ID)) config.plugins.allow.push(PLUGIN_ID);
|
|
210
|
+
|
|
211
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
212
|
+
return { success: true };
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return { success: false, error: String(err) };
|
|
215
|
+
}
|
|
216
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-algorand-plugin",
|
|
3
3
|
"name": "Algorand Integration",
|
|
4
4
|
"description": "Algorand blockchain integration with MCP and skills — by GoPlausible",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "2.0.0",
|
|
6
6
|
"skills": [
|
|
7
7
|
"skills/algorand-development",
|
|
8
8
|
"skills/algorand-typescript",
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
"skills/haystack-router-interaction",
|
|
15
15
|
"skills/alpha-arcade-interaction"
|
|
16
16
|
],
|
|
17
|
+
"activation": {
|
|
18
|
+
"commands": ["algorand-plugin"]
|
|
19
|
+
},
|
|
20
|
+
"commandAliases": [
|
|
21
|
+
{
|
|
22
|
+
"name": "algorand-plugin",
|
|
23
|
+
"description": "Algorand blockchain integration (GoPlausible)"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
17
26
|
"configSchema": {
|
|
18
27
|
"type": "object",
|
|
19
28
|
"additionalProperties": false,
|
|
@@ -21,7 +30,7 @@
|
|
|
21
30
|
"enableX402": {
|
|
22
31
|
"type": "boolean",
|
|
23
32
|
"default": true,
|
|
24
|
-
"description": "Enable x402 micropayments skills"
|
|
33
|
+
"description": "Enable x402 micropayments skills and x402_fetch tool"
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
},
|
|
@@ -29,5 +38,19 @@
|
|
|
29
38
|
"enableX402": {
|
|
30
39
|
"label": "Enable x402 Micropayments"
|
|
31
40
|
}
|
|
41
|
+
},
|
|
42
|
+
"setup": {
|
|
43
|
+
"summary": "Configure Algorand blockchain integration",
|
|
44
|
+
"steps": [
|
|
45
|
+
{
|
|
46
|
+
"id": "x402",
|
|
47
|
+
"label": "x402 Micropayments",
|
|
48
|
+
"description": "Enable the x402 HTTP-native payment protocol support (x402_fetch tool + skills).",
|
|
49
|
+
"configKey": "enableX402"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"postSetup": {
|
|
53
|
+
"message": "Restart the OpenClaw gateway to apply changes. algorand-mcp is auto-registered in ~/.mcporter/mcporter.json on first load."
|
|
54
|
+
}
|
|
32
55
|
}
|
|
33
56
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goplausible/openclaw-algorand-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -27,16 +27,25 @@
|
|
|
27
27
|
"index.ts",
|
|
28
28
|
"setup.ts",
|
|
29
29
|
"lib/",
|
|
30
|
-
"scripts/",
|
|
31
30
|
"skills/",
|
|
32
31
|
"memory/",
|
|
33
32
|
"openclaw.plugin.json"
|
|
34
33
|
],
|
|
35
34
|
"openclaw": {
|
|
36
|
-
"extensions": ["./index.ts"]
|
|
35
|
+
"extensions": ["./index.ts"],
|
|
36
|
+
"compat": {
|
|
37
|
+
"pluginApi": ">=2026.3.14",
|
|
38
|
+
"minGatewayVersion": "2026.3.14"
|
|
39
|
+
}
|
|
37
40
|
},
|
|
38
41
|
"dependencies": {
|
|
39
42
|
"@clack/prompts": "^0.7.0",
|
|
40
43
|
"@goplausible/algorand-mcp": "latest"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"openclaw": ">=2026.3.14"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^20.0.0"
|
|
41
50
|
}
|
|
42
51
|
}
|
package/setup.ts
CHANGED
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
3
|
-
import { join, dirname } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
2
|
import { ALGORAND_MCP, GOPLAUSIBLE_SERVICES } from "./lib/mcp-servers.js";
|
|
6
3
|
|
|
7
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
|
|
9
4
|
export interface AlgorandPluginConfig {
|
|
10
5
|
enableX402: boolean;
|
|
11
6
|
}
|
|
12
7
|
|
|
13
8
|
export async function runSetup(
|
|
14
|
-
existingConfig?: Partial<AlgorandPluginConfig
|
|
9
|
+
existingConfig?: Partial<AlgorandPluginConfig>,
|
|
15
10
|
): Promise<AlgorandPluginConfig | null> {
|
|
16
11
|
p.intro("🔷 Algorand Plugin Setup — powered by GoPlausible");
|
|
17
12
|
|
|
18
|
-
// Step 1: Keyring persistence check & setup
|
|
19
|
-
p.log.step("Checking keyring persistence for wallet storage...");
|
|
20
|
-
try {
|
|
21
|
-
const scriptPath = join(__dirname, "scripts", "setup-keyring.sh");
|
|
22
|
-
execSync(`bash "${scriptPath}" --setup`, { stdio: "inherit" });
|
|
23
|
-
} catch {
|
|
24
|
-
p.log.warn("Keyring setup script failed — you may need to configure manually.");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Step 2: x402 integration
|
|
28
13
|
const enableX402 = await p.confirm({
|
|
29
14
|
message: "Enable x402 micropayments integration?",
|
|
30
15
|
initialValue: existingConfig?.enableX402 ?? true,
|
|
@@ -35,23 +20,22 @@ export async function runSetup(
|
|
|
35
20
|
return null;
|
|
36
21
|
}
|
|
37
22
|
|
|
38
|
-
// Summary
|
|
39
23
|
const config: AlgorandPluginConfig = {
|
|
40
24
|
enableX402: enableX402 as boolean,
|
|
41
25
|
};
|
|
42
26
|
|
|
43
27
|
p.note(
|
|
44
28
|
`x402 Micropayments: ${config.enableX402 ? "Enabled" : "Disabled"}\n\n` +
|
|
45
|
-
`MCP Server
|
|
46
|
-
`
|
|
47
|
-
`
|
|
29
|
+
`MCP Server:\n` +
|
|
30
|
+
` ${ALGORAND_MCP.name} (${ALGORAND_MCP.command}) — 107 blockchain tools.\n` +
|
|
31
|
+
` Registered in ~/.mcporter/mcporter.json on first load.\n` +
|
|
32
|
+
` x402 micropayment and AP2 mandate flows are fully supported.\n`,
|
|
48
33
|
);
|
|
49
34
|
|
|
50
35
|
p.outro(
|
|
51
36
|
`🔷 Algorand plugin configured!\n\n` +
|
|
52
|
-
` Next
|
|
53
|
-
`
|
|
54
|
-
` GoPlausible website: ${GOPLAUSIBLE_SERVICES.website}`
|
|
37
|
+
` Next step: restart OpenClaw gateway.\n\n` +
|
|
38
|
+
` Docs: ${GOPLAUSIBLE_SERVICES.website}`,
|
|
55
39
|
);
|
|
56
40
|
|
|
57
41
|
return config;
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Usage:
|
|
3
|
-
// node backup-keyring.js backup <output-file> — dump address\tmnemonic pairs
|
|
4
|
-
// node backup-keyring.js restore <input-file> — restore mnemonics from backup
|
|
5
|
-
// node backup-keyring.js count — print number of accounts in wallet.db
|
|
6
|
-
// node backup-keyring.js verify — check which accounts have mnemonics in keyring
|
|
7
|
-
|
|
8
|
-
const { Entry } = require('@napi-rs/keyring');
|
|
9
|
-
const initSqlJs = require('sql.js');
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const os = require('os');
|
|
13
|
-
|
|
14
|
-
const KEYCHAIN_SERVICE = 'algorand-mcp';
|
|
15
|
-
const WALLET_DB = path.join(os.homedir(), '.algorand-mcp', 'wallet.db');
|
|
16
|
-
|
|
17
|
-
async function getAccounts() {
|
|
18
|
-
if (!fs.existsSync(WALLET_DB)) return [];
|
|
19
|
-
const SQL = await initSqlJs();
|
|
20
|
-
const buf = fs.readFileSync(WALLET_DB);
|
|
21
|
-
const db = new SQL.Database(buf);
|
|
22
|
-
const rows = db.exec('SELECT address FROM accounts');
|
|
23
|
-
db.close();
|
|
24
|
-
if (!rows.length || !rows[0].values.length) return [];
|
|
25
|
-
return rows[0].values.map(r => r[0]);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getMnemonic(address) {
|
|
29
|
-
try {
|
|
30
|
-
const entry = new Entry(KEYCHAIN_SERVICE, address);
|
|
31
|
-
const pw = entry.getPassword();
|
|
32
|
-
return pw || null;
|
|
33
|
-
} catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function setMnemonic(address, mnemonic) {
|
|
39
|
-
const entry = new Entry(KEYCHAIN_SERVICE, address);
|
|
40
|
-
entry.setPassword(mnemonic);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function main() {
|
|
44
|
-
const mode = process.argv[2];
|
|
45
|
-
const file = process.argv[3];
|
|
46
|
-
|
|
47
|
-
if (mode === 'count') {
|
|
48
|
-
const accounts = await getAccounts();
|
|
49
|
-
console.log(accounts.length);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (mode === 'verify') {
|
|
54
|
-
const accounts = await getAccounts();
|
|
55
|
-
let ok = 0, missing = 0;
|
|
56
|
-
for (const addr of accounts) {
|
|
57
|
-
if (getMnemonic(addr)) {
|
|
58
|
-
console.log('OK ' + addr);
|
|
59
|
-
ok++;
|
|
60
|
-
} else {
|
|
61
|
-
console.log('MISSING ' + addr);
|
|
62
|
-
missing++;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
console.log(`TOTAL=${accounts.length} OK=${ok} MISSING=${missing}`);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (mode === 'backup') {
|
|
70
|
-
if (!file) { console.error('Usage: backup-keyring.js backup <output-file>'); process.exit(1); }
|
|
71
|
-
const accounts = await getAccounts();
|
|
72
|
-
if (accounts.length === 0) {
|
|
73
|
-
console.log('NO_ACCOUNTS');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
let backed = 0, failed = 0;
|
|
77
|
-
const lines = [];
|
|
78
|
-
for (const addr of accounts) {
|
|
79
|
-
const mnemonic = getMnemonic(addr);
|
|
80
|
-
if (mnemonic) {
|
|
81
|
-
lines.push(addr + '\t' + mnemonic);
|
|
82
|
-
backed++;
|
|
83
|
-
} else {
|
|
84
|
-
console.error('MISSING ' + addr);
|
|
85
|
-
failed++;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (lines.length > 0) {
|
|
89
|
-
fs.writeFileSync(file, lines.join('\n') + '\n', { mode: 0o600 });
|
|
90
|
-
}
|
|
91
|
-
console.log(`BACKED_UP=${backed} FAILED=${failed}`);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (mode === 'restore') {
|
|
96
|
-
if (!file) { console.error('Usage: backup-keyring.js restore <input-file>'); process.exit(1); }
|
|
97
|
-
if (!fs.existsSync(file)) { console.error('File not found: ' + file); process.exit(1); }
|
|
98
|
-
const content = fs.readFileSync(file, 'utf-8').trim();
|
|
99
|
-
if (!content) { console.log('RESTORED=0'); return; }
|
|
100
|
-
let restored = 0;
|
|
101
|
-
for (const line of content.split('\n')) {
|
|
102
|
-
const [addr, ...rest] = line.split('\t');
|
|
103
|
-
const mnemonic = rest.join('\t');
|
|
104
|
-
if (addr && mnemonic) {
|
|
105
|
-
setMnemonic(addr, mnemonic);
|
|
106
|
-
restored++;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
console.log(`RESTORED=${restored}`);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
console.error('Usage: backup-keyring.js <backup|restore|count|verify> [file]');
|
|
114
|
-
process.exit(1);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
main().catch(err => { console.error(err.message); process.exit(1); });
|