openclaw-overlay-plugin 0.7.32 → 0.7.34
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 +4 -2
- package/SKILL.md +15 -1
- package/dist/index.js +93 -59
- package/dist/src/core/wallet.js +4 -0
- package/dist/src/scripts/baemail/commands.js +17 -6
- package/dist/src/scripts/config.js +2 -2
- package/dist/src/scripts/overlay/discover.js +3 -1
- package/index.ts +88 -61
- package/openclaw.plugin.json +19 -0
- package/package.json +1 -1
- package/src/core/wallet.ts +6 -0
- package/src/scripts/baemail/commands.ts +17 -6
- package/src/scripts/config.ts +2 -2
- package/src/scripts/overlay/discover.ts +3 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ That's it. The plugin auto-initializes your wallet on first startup.
|
|
|
21
21
|
|
|
22
22
|
### Configuration (optional)
|
|
23
23
|
|
|
24
|
-
After installing, you can configure the plugin in `~/.openclaw/openclaw.json` under `plugins.entries.
|
|
24
|
+
After installing, you can configure the plugin in `~/.openclaw/openclaw.json` under `plugins.entries.openclaw-overlay.config`:
|
|
25
25
|
|
|
26
26
|
```json
|
|
27
27
|
{
|
|
@@ -33,7 +33,8 @@ After installing, you can configure the plugin in `~/.openclaw/openclaw.json` un
|
|
|
33
33
|
"agentDescription": "My agent on the overlay network",
|
|
34
34
|
"maxAutoPaySats": 200,
|
|
35
35
|
"dailyBudgetSats": 5000,
|
|
36
|
-
"chaintracksUrl": "https://chaintracks-us-1.bsvb.tech"
|
|
36
|
+
"chaintracksUrl": "https://chaintracks-us-1.bsvb.tech",
|
|
37
|
+
"arcUrl": "https://arcade.bsv.direct"
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -49,6 +50,7 @@ After installing, you can configure the plugin in `~/.openclaw/openclaw.json` un
|
|
|
49
50
|
| `walletDir` | `~/.openclaw/bsv-wallet` | Wallet storage directory |
|
|
50
51
|
| `overlayUrl` | `https://clawoverlay.com` | Overlay network server |
|
|
51
52
|
| `chaintracksUrl` | `https://chaintracks-us-1.bsvb.tech` | Custom SPV header server |
|
|
53
|
+
| `arcUrl` | (uses public ARC) | Custom ARC/Arcade server for broadcasting |
|
|
52
54
|
|
|
53
55
|
### Required: Enable Hooks
|
|
54
56
|
|
package/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: openclaw-overlay
|
|
3
3
|
description: >
|
|
4
4
|
Connect to the BSV Overlay Network — a decentralized agent marketplace for
|
|
5
5
|
discovering other AI agents and exchanging BSV micropayments for services.
|
|
@@ -45,6 +45,20 @@ On first run, the plugin auto-creates a wallet and wakes you. Guide the user thr
|
|
|
45
45
|
5. **Ask which services to offer**: Present the list from the onboard response, let user pick
|
|
46
46
|
6. **Advertise selected**: `overlay({ action: "advertise", ... })` for each
|
|
47
47
|
|
|
48
|
+
If you need to customize settings, tell the user to add them to their config under `plugins.entries["openclaw-overlay"]`:
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"plugins": {
|
|
52
|
+
"entries": {
|
|
53
|
+
"openclaw-overlay": {
|
|
54
|
+
"agentName": "...",
|
|
55
|
+
"overlayUrl": "..."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
48
62
|
Do NOT use defaults without asking. Do NOT skip the name/description questions.
|
|
49
63
|
|
|
50
64
|
## Requesting Services
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ function loadDailySpending(walletDir) {
|
|
|
38
38
|
return { date: today, totalSats: 0, transactions: [] };
|
|
39
39
|
}
|
|
40
40
|
function writeActivityEvent(event) {
|
|
41
|
-
const alertDir = path.join(process.env.HOME || '', '.openclaw', '
|
|
41
|
+
const alertDir = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay');
|
|
42
42
|
try {
|
|
43
43
|
fs.mkdirSync(alertDir, { recursive: true });
|
|
44
44
|
fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
|
|
@@ -91,39 +91,39 @@ async function startAutoImport(env, cliPath, logger) {
|
|
|
91
91
|
continue;
|
|
92
92
|
if (utxo.value < 200)
|
|
93
93
|
continue; // skip dust
|
|
94
|
-
logger?.info?.(`[
|
|
94
|
+
logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
|
|
95
95
|
try {
|
|
96
96
|
const importResult = await execFileAsync('node', [cliPath, 'import', utxo.tx_hash, String(utxo.tx_pos)], { env });
|
|
97
97
|
const importOutput = parseCliOutput(importResult.stdout);
|
|
98
98
|
if (importOutput.success) {
|
|
99
99
|
knownTxids.add(key);
|
|
100
|
-
logger?.info?.(`[
|
|
100
|
+
logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
|
|
101
101
|
// Clear onboarding flag since wallet is now funded
|
|
102
102
|
try {
|
|
103
|
-
const onboardingSentFile = path.join(process.env.HOME || '', '.openclaw', '
|
|
103
|
+
const onboardingSentFile = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
|
|
104
104
|
if (fs.existsSync(onboardingSentFile)) {
|
|
105
105
|
fs.unlinkSync(onboardingSentFile);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
catch { }
|
|
109
109
|
// Notify agent of successful import
|
|
110
|
-
wakeAgent(`💰 **Wallet Funded!**\n\nAuto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...\n\nNotify the user their wallet has been funded.`, logger, { sessionKey: 'hook:
|
|
110
|
+
wakeAgent(`💰 **Wallet Funded!**\n\nAuto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...\n\nNotify the user their wallet has been funded.`, logger, { sessionKey: 'hook:openclaw-overlay:import' });
|
|
111
111
|
// Check if registered, auto-register if not
|
|
112
112
|
try {
|
|
113
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
113
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
114
114
|
if (!fs.existsSync(regPath)) {
|
|
115
|
-
logger?.info?.('[
|
|
115
|
+
logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
|
|
116
116
|
const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
|
|
117
117
|
const regOutput = parseCliOutput(regResult.stdout);
|
|
118
118
|
if (regOutput.success) {
|
|
119
|
-
logger?.info?.('[
|
|
119
|
+
logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
|
|
120
120
|
// Auto-advertise services from config
|
|
121
121
|
await autoAdvertiseServices(env, cliPath, logger);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
catch (err) {
|
|
126
|
-
logger?.warn?.('[
|
|
126
|
+
logger?.warn?.('[openclaw-overlay] Auto-registration failed:', err.message);
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
}
|
|
@@ -139,7 +139,7 @@ async function startAutoImport(env, cliPath, logger) {
|
|
|
139
139
|
}, 30000); // Check every 30 seconds for faster onboarding
|
|
140
140
|
}
|
|
141
141
|
catch (err) {
|
|
142
|
-
logger?.warn?.('[
|
|
142
|
+
logger?.warn?.('[openclaw-overlay] Auto-import setup failed:', err.message);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
function stopAutoImport() {
|
|
@@ -162,7 +162,7 @@ async function autoAdvertiseServices(env, cliPath, logger) {
|
|
|
162
162
|
continue;
|
|
163
163
|
try {
|
|
164
164
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
165
|
-
const pluginConfig = config?.plugins?.entries?.['
|
|
165
|
+
const pluginConfig = config?.plugins?.entries?.['openclaw-overlay']?.config;
|
|
166
166
|
if (pluginConfig?.services && Array.isArray(pluginConfig.services)) {
|
|
167
167
|
servicesToAdvertise = pluginConfig.services;
|
|
168
168
|
break;
|
|
@@ -171,10 +171,10 @@ async function autoAdvertiseServices(env, cliPath, logger) {
|
|
|
171
171
|
catch { }
|
|
172
172
|
}
|
|
173
173
|
if (servicesToAdvertise.length === 0) {
|
|
174
|
-
logger?.info?.('[
|
|
174
|
+
logger?.info?.('[openclaw-overlay] No services configured for auto-advertising');
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
|
-
logger?.info?.(`[
|
|
177
|
+
logger?.info?.(`[openclaw-overlay] Auto-advertising ${servicesToAdvertise.length} services from config...`);
|
|
178
178
|
const advertised = [];
|
|
179
179
|
const failed = [];
|
|
180
180
|
for (const serviceId of servicesToAdvertise) {
|
|
@@ -194,12 +194,12 @@ async function autoAdvertiseServices(env, cliPath, logger) {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
if (advertised.length > 0)
|
|
197
|
-
logger?.info?.(`[
|
|
197
|
+
logger?.info?.(`[openclaw-overlay] Successfully advertised: ${advertised.join(', ')}`);
|
|
198
198
|
if (failed.length > 0)
|
|
199
|
-
logger?.warn?.(`[
|
|
199
|
+
logger?.warn?.(`[openclaw-overlay] Failed to advertise: ${failed.join(', ')}`);
|
|
200
200
|
}
|
|
201
201
|
catch (err) {
|
|
202
|
-
logger?.warn?.('[
|
|
202
|
+
logger?.warn?.('[openclaw-overlay] Auto-advertising failed:', err.message);
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
/**
|
|
@@ -207,11 +207,11 @@ async function autoAdvertiseServices(env, cliPath, logger) {
|
|
|
207
207
|
* This is the standard way to invoke an agent with a specific context.
|
|
208
208
|
*/
|
|
209
209
|
function wakeAgent(text, logger, options = {}) {
|
|
210
|
-
const sessionKey = options.sessionKey || `hook:
|
|
210
|
+
const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
|
|
211
211
|
const gatewayPort = getGatewayPort();
|
|
212
212
|
const httpToken = getHooksToken();
|
|
213
213
|
if (!httpToken) {
|
|
214
|
-
logger?.warn?.('[
|
|
214
|
+
logger?.warn?.('[openclaw-overlay] Skipped wakeAgent: OPENCLAW_HOOKS_TOKEN not set');
|
|
215
215
|
return;
|
|
216
216
|
}
|
|
217
217
|
const url = `http://localhost:${gatewayPort}/hooks/agent`;
|
|
@@ -228,15 +228,15 @@ function wakeAgent(text, logger, options = {}) {
|
|
|
228
228
|
})
|
|
229
229
|
.then(async (res) => {
|
|
230
230
|
if (res.ok) {
|
|
231
|
-
logger?.info?.(`[
|
|
231
|
+
logger?.info?.(`[openclaw-overlay] Agent invoked via /hooks/agent (session: ${sessionKey})`);
|
|
232
232
|
}
|
|
233
233
|
else {
|
|
234
234
|
const body = await res.text().catch(() => '');
|
|
235
|
-
logger?.warn?.(`[
|
|
235
|
+
logger?.warn?.(`[openclaw-overlay] /hooks/agent failed: ${res.status} ${body}`);
|
|
236
236
|
}
|
|
237
237
|
})
|
|
238
238
|
.catch((err) => {
|
|
239
|
-
logger?.warn?.('[
|
|
239
|
+
logger?.warn?.('[openclaw-overlay] /hooks/agent error:', err.message);
|
|
240
240
|
});
|
|
241
241
|
}
|
|
242
242
|
function getGatewayPort() {
|
|
@@ -292,15 +292,15 @@ function startBackgroundService(env, cliPath, logger) {
|
|
|
292
292
|
requestCleanupInterval = setInterval(async () => {
|
|
293
293
|
if (serviceRunning) {
|
|
294
294
|
wokenRequests.clear();
|
|
295
|
-
logger?.debug?.('[
|
|
295
|
+
logger?.debug?.('[openclaw-overlay] Cleared stale request IDs');
|
|
296
296
|
// Also clean up old queue entries
|
|
297
297
|
try {
|
|
298
298
|
const { cleanupServiceQueue } = await import('./src/scripts/utils/storage.js');
|
|
299
299
|
cleanupServiceQueue();
|
|
300
|
-
logger?.debug?.('[
|
|
300
|
+
logger?.debug?.('[openclaw-overlay] Cleaned up old queue entries');
|
|
301
301
|
}
|
|
302
302
|
catch (err) {
|
|
303
|
-
logger?.warn?.('[
|
|
303
|
+
logger?.warn?.('[openclaw-overlay] Queue cleanup failed:', err.message);
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
}, 5 * 60 * 1000);
|
|
@@ -317,8 +317,8 @@ function startBackgroundService(env, cliPath, logger) {
|
|
|
317
317
|
for (const line of lines) {
|
|
318
318
|
try {
|
|
319
319
|
const event = JSON.parse(line);
|
|
320
|
-
logger?.debug?.(`[
|
|
321
|
-
const alertDir = path.join(process.env.HOME || '', '.openclaw', '
|
|
320
|
+
logger?.debug?.(`[openclaw-overlay] ${event.event || event.type || 'message'}:`, JSON.stringify(event).slice(0, 200));
|
|
321
|
+
const alertDir = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay');
|
|
322
322
|
fs.mkdirSync(alertDir, { recursive: true });
|
|
323
323
|
// Detect queued-for-agent events — invoke agent via /hooks/agent
|
|
324
324
|
// This is the PROVIDER side: someone requested our service
|
|
@@ -326,18 +326,18 @@ function startBackgroundService(env, cliPath, logger) {
|
|
|
326
326
|
const requestId = event.id || `${event.from}-${Date.now()}`;
|
|
327
327
|
// Check if already woken to prevent duplicate processing
|
|
328
328
|
if (wokenRequests.has(requestId)) {
|
|
329
|
-
logger?.debug?.(`[
|
|
329
|
+
logger?.debug?.(`[openclaw-overlay] Request ${requestId} already woken, skipping duplicate`);
|
|
330
330
|
return;
|
|
331
331
|
}
|
|
332
332
|
// Skip wake-up for already processed requests unless they're pending
|
|
333
333
|
if (event.action?.startsWith('already-') && !event.action.includes('pending')) {
|
|
334
|
-
logger?.debug?.(`[
|
|
334
|
+
logger?.debug?.(`[openclaw-overlay] Request ${requestId} already processed (${event.action}), skipping`);
|
|
335
335
|
return;
|
|
336
336
|
}
|
|
337
337
|
wokenRequests.add(requestId);
|
|
338
|
-
logger?.info?.(`[
|
|
338
|
+
logger?.info?.(`[openclaw-overlay] ⚡ Incoming ${event.serviceId} request from ${event.from?.slice(0, 12)}...`);
|
|
339
339
|
const wakeText = `⚡ Incoming overlay service request!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nPaid: ${event.satoshisReceived || '?'} sats\n\nFulfill it now:\n1. overlay({ action: "pending-requests" })\n2. Process the ${event.serviceId} request using your capabilities\n3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
|
|
340
|
-
wakeAgent(wakeText, logger, { sessionKey: `hook:
|
|
340
|
+
wakeAgent(wakeText, logger, { sessionKey: `hook:openclaw-overlay:${event.id || Date.now()}` });
|
|
341
341
|
}
|
|
342
342
|
// Detect service-response events — invoke agent to notify user
|
|
343
343
|
// This is the REQUESTER side: we requested a service, response came back
|
|
@@ -347,9 +347,9 @@ function startBackgroundService(env, cliPath, logger) {
|
|
|
347
347
|
const from = event.from || 'unknown';
|
|
348
348
|
const formatted = event.formatted || '';
|
|
349
349
|
const resultJson = event.result ? JSON.stringify(event.result, null, 2) : '(no result data)';
|
|
350
|
-
logger?.info?.(`[
|
|
350
|
+
logger?.info?.(`[openclaw-overlay] 📬 Response received for ${svcId} from ${from?.slice(0, 12)}... — status: ${status}`);
|
|
351
351
|
const wakeText = `📬 Overlay service response received!\n\nService: ${svcId}\nFrom: ${from}\nStatus: ${status}\n${formatted ? `\nSummary: ${formatted}` : ''}\n\nFull result:\n${resultJson}\n\nNotify the user of this response in a clear, human-readable format.`;
|
|
352
|
-
wakeAgent(wakeText, logger, { sessionKey: `hook:
|
|
352
|
+
wakeAgent(wakeText, logger, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
|
|
353
353
|
}
|
|
354
354
|
// Write payment/activity notifications for ALL significant events
|
|
355
355
|
const notifEvent = categorizeEvent(event);
|
|
@@ -369,21 +369,21 @@ function startBackgroundService(env, cliPath, logger) {
|
|
|
369
369
|
try {
|
|
370
370
|
const event = JSON.parse(line);
|
|
371
371
|
if (event.event === 'connected') {
|
|
372
|
-
logger?.info?.('[
|
|
372
|
+
logger?.info?.('[openclaw-overlay] WebSocket relay connected');
|
|
373
373
|
}
|
|
374
374
|
else if (event.event === 'disconnected') {
|
|
375
|
-
logger?.warn?.('[
|
|
375
|
+
logger?.warn?.('[openclaw-overlay] WebSocket disconnected, reconnecting...');
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
catch {
|
|
379
|
-
logger?.debug?.(`[
|
|
379
|
+
logger?.debug?.(`[openclaw-overlay] ${line}`);
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
});
|
|
383
383
|
proc.on('exit', (code) => {
|
|
384
384
|
backgroundProcess = null;
|
|
385
385
|
if (serviceRunning) {
|
|
386
|
-
logger?.warn?.(`[
|
|
386
|
+
logger?.warn?.(`[openclaw-overlay] Background service exited (code ${code}), restarting in 5s...`);
|
|
387
387
|
setTimeout(spawnConnect, 5000);
|
|
388
388
|
}
|
|
389
389
|
});
|
|
@@ -541,7 +541,7 @@ export default function register(api) {
|
|
|
541
541
|
});
|
|
542
542
|
// Register background relay service
|
|
543
543
|
api.registerService({
|
|
544
|
-
id: "
|
|
544
|
+
id: "openclaw-overlay-relay",
|
|
545
545
|
start: async () => {
|
|
546
546
|
try {
|
|
547
547
|
api.logger.info("Starting BSV overlay WebSocket relay...");
|
|
@@ -568,7 +568,7 @@ export default function register(api) {
|
|
|
568
568
|
});
|
|
569
569
|
// Register a skill-style wake handler
|
|
570
570
|
api.registerHook({
|
|
571
|
-
id: "
|
|
571
|
+
id: "openclaw-overlay-wake",
|
|
572
572
|
event: "gateway:start",
|
|
573
573
|
handler: async (ctx) => {
|
|
574
574
|
// Auto-check for registration on startup
|
|
@@ -576,8 +576,8 @@ export default function register(api) {
|
|
|
576
576
|
try {
|
|
577
577
|
const env = buildEnvironment(pluginConfig);
|
|
578
578
|
const cliPath = path.join(__dirname, 'dist', 'cli.js');
|
|
579
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
580
|
-
const onboardSentFile = path.join(process.env.HOME || '', '.openclaw', '
|
|
579
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
580
|
+
const onboardSentFile = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
|
|
581
581
|
if (!fs.existsSync(regPath) && !fs.existsSync(onboardSentFile)) {
|
|
582
582
|
// Check if wallet exists
|
|
583
583
|
const walletPath = path.join(pluginConfig.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet'), 'wallet-identity.json');
|
|
@@ -598,7 +598,7 @@ export default function register(api) {
|
|
|
598
598
|
// Funded but not registered. Auto-register.
|
|
599
599
|
const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
|
|
600
600
|
if (parseCliOutput(regResult.stdout).success) {
|
|
601
|
-
ctx.logger.info('[
|
|
601
|
+
ctx.logger.info('[openclaw-overlay] Agent auto-registered on startup');
|
|
602
602
|
await autoAdvertiseServices(env, cliPath, ctx.logger);
|
|
603
603
|
}
|
|
604
604
|
}
|
|
@@ -606,7 +606,7 @@ export default function register(api) {
|
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
608
|
catch (err) {
|
|
609
|
-
api.log?.debug?.('[
|
|
609
|
+
api.log?.debug?.('[openclaw-overlay] Auto-setup/onboarding skipped:', err.message);
|
|
610
610
|
}
|
|
611
611
|
})();
|
|
612
612
|
}
|
|
@@ -669,13 +669,15 @@ export default function register(api) {
|
|
|
669
669
|
if (result.agents) {
|
|
670
670
|
console.log("\nAgents:");
|
|
671
671
|
result.agents.forEach((agent) => {
|
|
672
|
-
|
|
672
|
+
const name = agent.name || agent.agentName || 'Unknown Agent';
|
|
673
|
+
console.log(`- ${name} (${agent.identityKey.slice(0, 16)}...)`);
|
|
673
674
|
});
|
|
674
675
|
}
|
|
675
676
|
if (result.services) {
|
|
676
677
|
console.log("\nServices:");
|
|
677
678
|
result.services.forEach((service) => {
|
|
678
|
-
|
|
679
|
+
const name = service.name || service.agentName || 'Unknown Agent';
|
|
680
|
+
console.log(`- ${service.serviceId} - ${name} (${service.pricing?.amountSats || 0} sats) by ${service.identityKey.slice(0, 12)}`);
|
|
679
681
|
});
|
|
680
682
|
}
|
|
681
683
|
}
|
|
@@ -883,7 +885,7 @@ function validateConfirmToken(token, expectedAction) {
|
|
|
883
885
|
async function handleUnregister(params, env, cliPath) {
|
|
884
886
|
const { confirmToken } = params;
|
|
885
887
|
// Load current registration to show what will be deleted
|
|
886
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
888
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
887
889
|
let registration = null;
|
|
888
890
|
try {
|
|
889
891
|
if (fs.existsSync(regPath)) {
|
|
@@ -1025,24 +1027,53 @@ async function handleDiscover(params, env, cliPath) {
|
|
|
1025
1027
|
return output.data;
|
|
1026
1028
|
}
|
|
1027
1029
|
async function handleBalance(env, cliPath) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1030
|
+
try {
|
|
1031
|
+
const result = await execFileAsync('node', [cliPath, 'balance'], { env });
|
|
1032
|
+
const output = parseCliOutput(result.stdout);
|
|
1033
|
+
if (!output.success) {
|
|
1034
|
+
throw new Error(output.error);
|
|
1035
|
+
}
|
|
1036
|
+
return output.data;
|
|
1037
|
+
}
|
|
1038
|
+
catch (error) {
|
|
1039
|
+
if (error.message?.includes('Wallet not initialized')) {
|
|
1040
|
+
return { walletBalance: 0, error: 'Not Initialized' };
|
|
1041
|
+
}
|
|
1042
|
+
throw new Error(`Balance check failed: ${error.message}`);
|
|
1032
1043
|
}
|
|
1033
|
-
return output.data;
|
|
1034
1044
|
}
|
|
1035
1045
|
async function handleStatus(env, cliPath) {
|
|
1036
1046
|
try {
|
|
1037
1047
|
// Get identity
|
|
1038
|
-
|
|
1039
|
-
|
|
1048
|
+
let identity = { data: { identityKey: 'Not Initialized' } };
|
|
1049
|
+
try {
|
|
1050
|
+
const identityResult = await execFileAsync('node', [cliPath, 'identity'], { env });
|
|
1051
|
+
identity = parseCliOutput(identityResult.stdout);
|
|
1052
|
+
}
|
|
1053
|
+
catch (err) {
|
|
1054
|
+
if (!err.message?.includes('Wallet not initialized'))
|
|
1055
|
+
throw err;
|
|
1056
|
+
}
|
|
1040
1057
|
// Get balance
|
|
1041
|
-
|
|
1042
|
-
|
|
1058
|
+
let balance = { data: { walletBalance: 0 } };
|
|
1059
|
+
try {
|
|
1060
|
+
const balanceResult = await execFileAsync('node', [cliPath, 'balance'], { env });
|
|
1061
|
+
balance = parseCliOutput(balanceResult.stdout);
|
|
1062
|
+
}
|
|
1063
|
+
catch (err) {
|
|
1064
|
+
if (!err.message?.includes('Wallet not initialized'))
|
|
1065
|
+
throw err;
|
|
1066
|
+
}
|
|
1043
1067
|
// Get services
|
|
1044
|
-
|
|
1045
|
-
|
|
1068
|
+
let services = { data: [] };
|
|
1069
|
+
try {
|
|
1070
|
+
const servicesResult = await execFileAsync('node', [cliPath, 'services'], { env });
|
|
1071
|
+
services = parseCliOutput(servicesResult.stdout);
|
|
1072
|
+
}
|
|
1073
|
+
catch (err) {
|
|
1074
|
+
if (!err.message?.includes('Wallet not initialized'))
|
|
1075
|
+
throw err;
|
|
1076
|
+
}
|
|
1046
1077
|
return {
|
|
1047
1078
|
identity: identity.data,
|
|
1048
1079
|
balance: balance.data,
|
|
@@ -1111,7 +1142,7 @@ async function handleImport(params, env, cliPath) {
|
|
|
1111
1142
|
throw new Error(`Import failed: ${output.error}`);
|
|
1112
1143
|
}
|
|
1113
1144
|
// Check if we should auto-register after successful import
|
|
1114
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
1145
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
1115
1146
|
const isRegistered = fs.existsSync(regPath);
|
|
1116
1147
|
if (!isRegistered && output.data?.balance >= 1000) {
|
|
1117
1148
|
// Auto-register immediately after funding
|
|
@@ -1327,7 +1358,7 @@ async function handlePendingRequests(env, cliPath) {
|
|
|
1327
1358
|
if (!output.success)
|
|
1328
1359
|
throw new Error(`Queue check failed: ${output.error}`);
|
|
1329
1360
|
// Clear the alert file since we're checking now
|
|
1330
|
-
const alertPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
1361
|
+
const alertPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'pending-alert.jsonl');
|
|
1331
1362
|
try {
|
|
1332
1363
|
if (fs.existsSync(alertPath))
|
|
1333
1364
|
fs.unlinkSync(alertPath);
|
|
@@ -1336,7 +1367,7 @@ async function handlePendingRequests(env, cliPath) {
|
|
|
1336
1367
|
return output.data;
|
|
1337
1368
|
}
|
|
1338
1369
|
function handleActivity() {
|
|
1339
|
-
const feedPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
1370
|
+
const feedPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'activity-feed.jsonl');
|
|
1340
1371
|
if (!fs.existsSync(feedPath))
|
|
1341
1372
|
return { events: [], count: 0 };
|
|
1342
1373
|
const lines = fs.readFileSync(feedPath, 'utf-8').trim().split('\n').filter(Boolean);
|
|
@@ -1380,6 +1411,9 @@ function buildEnvironment(config) {
|
|
|
1380
1411
|
if (config.chaintracksUrl) {
|
|
1381
1412
|
env.BSV_CHAINTRACKS_URL = config.chaintracksUrl;
|
|
1382
1413
|
}
|
|
1414
|
+
if (config.arcUrl) {
|
|
1415
|
+
env.BSV_ARC_URL = config.arcUrl;
|
|
1416
|
+
}
|
|
1383
1417
|
// Set defaults
|
|
1384
1418
|
env.BSV_NETWORK = env.BSV_NETWORK || 'mainnet';
|
|
1385
1419
|
if (config.agentName) {
|
package/dist/src/core/wallet.js
CHANGED
|
@@ -176,7 +176,11 @@ export class BSVAgentWallet {
|
|
|
176
176
|
// 3. Network services (ARC broadcasting, chain tracking, etc.)
|
|
177
177
|
const serviceOptions = Services.createDefaultOptions(chain);
|
|
178
178
|
const chaintracksUrl = process.env.BSV_CHAINTRACKS_URL || 'https://chaintracks-us-1.bsvb.tech';
|
|
179
|
+
const arcUrl = process.env.BSV_ARC_URL;
|
|
179
180
|
serviceOptions.chaintracks = new ChaintracksServiceClient(chain, chaintracksUrl);
|
|
181
|
+
if (arcUrl) {
|
|
182
|
+
serviceOptions.arcUrl = arcUrl;
|
|
183
|
+
}
|
|
180
184
|
serviceOptions.taalApiKey = taalApiKey;
|
|
181
185
|
const services = new Services(serviceOptions);
|
|
182
186
|
// 4. Background monitor
|
|
@@ -224,12 +224,23 @@ export async function cmdBaemailRefund(requestId) {
|
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
await tx.sign();
|
|
227
|
-
// Broadcast
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
// Broadcast using configured ARC/Arcade URL or fallback to WhatsOnChain
|
|
228
|
+
const arcUrl = process.env.BSV_ARC_URL;
|
|
229
|
+
let broadcastResp;
|
|
230
|
+
if (arcUrl) {
|
|
231
|
+
broadcastResp = await fetchWithTimeout(`${arcUrl.replace(/\/$/, '')}/v1/tx`, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({ rawTx: tx.toHex() }),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
broadcastResp = await fetchWithTimeout('https://api.whatsonchain.com/v1/bsv/main/tx/raw', {
|
|
239
|
+
method: 'POST',
|
|
240
|
+
headers: { 'Content-Type': 'application/json' },
|
|
241
|
+
body: JSON.stringify({ txhex: tx.toHex() }),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
233
244
|
if (!broadcastResp.ok) {
|
|
234
245
|
const errBody = await broadcastResp.text();
|
|
235
246
|
return fail(`Broadcast failed: ${errBody}`);
|
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
// Auto-load .env from overlay state dir if it exists
|
|
8
|
-
const overlayEnvPath = path.join(os.homedir(), '.openclaw', '
|
|
8
|
+
const overlayEnvPath = path.join(os.homedir(), '.openclaw', 'openclaw-overlay', '.env');
|
|
9
9
|
try {
|
|
10
10
|
if (fs.existsSync(overlayEnvPath)) {
|
|
11
11
|
for (const line of fs.readFileSync(overlayEnvPath, 'utf-8').split('\n')) {
|
|
@@ -34,7 +34,7 @@ export const AGENT_DESCRIPTION = process.env.AGENT_DESCRIPTION ||
|
|
|
34
34
|
/** WhatsOnChain API key (optional, for rate limit bypass) */
|
|
35
35
|
export const WOC_API_KEY = process.env.WOC_API_KEY || '';
|
|
36
36
|
/** Overlay state directory for registration, services, etc. */
|
|
37
|
-
export const OVERLAY_STATE_DIR = path.join(os.homedir(), '.openclaw', '
|
|
37
|
+
export const OVERLAY_STATE_DIR = path.join(os.homedir(), '.openclaw', 'openclaw-overlay');
|
|
38
38
|
/** Protocol identifier for overlay transactions */
|
|
39
39
|
export const PROTOCOL_ID = 'openclaw overlay v1';
|
|
40
40
|
/** Topic managers for overlay submissions */
|
|
@@ -28,7 +28,9 @@ export async function cmdDiscover(args) {
|
|
|
28
28
|
try {
|
|
29
29
|
const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
|
|
30
30
|
if (data?.type === 'identity') {
|
|
31
|
-
|
|
31
|
+
// Handle both 'name' and 'agentName' for backward compatibility
|
|
32
|
+
const name = data.name || data.agentName || 'Unknown Agent';
|
|
33
|
+
results.agents.push({ ...data, name, txid });
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
catch { /* ignore */ }
|
package/index.ts
CHANGED
|
@@ -52,7 +52,7 @@ function loadDailySpending(walletDir: string): DailySpending {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function writeActivityEvent(event: any) {
|
|
55
|
-
const alertDir = path.join(process.env.HOME || '', '.openclaw', '
|
|
55
|
+
const alertDir = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay');
|
|
56
56
|
try {
|
|
57
57
|
fs.mkdirSync(alertDir, { recursive: true });
|
|
58
58
|
fs.appendFileSync(path.join(alertDir, 'activity-feed.jsonl'), JSON.stringify({ ...event, ts: Date.now() }) + '\n');
|
|
@@ -106,41 +106,41 @@ async function startAutoImport(env: any, cliPath: string, logger: any) {
|
|
|
106
106
|
if (knownTxids.has(key)) continue;
|
|
107
107
|
if (utxo.value < 200) continue; // skip dust
|
|
108
108
|
|
|
109
|
-
logger?.info?.(`[
|
|
109
|
+
logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
|
|
110
110
|
try {
|
|
111
111
|
const importResult = await execFileAsync('node', [cliPath, 'import', utxo.tx_hash, String(utxo.tx_pos)], { env });
|
|
112
112
|
const importOutput = parseCliOutput(importResult.stdout);
|
|
113
113
|
if (importOutput.success) {
|
|
114
114
|
knownTxids.add(key);
|
|
115
|
-
logger?.info?.(`[
|
|
115
|
+
logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
|
|
116
116
|
|
|
117
117
|
// Clear onboarding flag since wallet is now funded
|
|
118
118
|
try {
|
|
119
|
-
const onboardingSentFile = path.join(process.env.HOME || '', '.openclaw', '
|
|
119
|
+
const onboardingSentFile = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
|
|
120
120
|
if (fs.existsSync(onboardingSentFile)) {
|
|
121
121
|
fs.unlinkSync(onboardingSentFile);
|
|
122
122
|
}
|
|
123
123
|
} catch {}
|
|
124
124
|
|
|
125
125
|
// Notify agent of successful import
|
|
126
|
-
wakeAgent(`💰 **Wallet Funded!**\n\nAuto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...\n\nNotify the user their wallet has been funded.`, logger, { sessionKey: 'hook:
|
|
126
|
+
wakeAgent(`💰 **Wallet Funded!**\n\nAuto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...\n\nNotify the user their wallet has been funded.`, logger, { sessionKey: 'hook:openclaw-overlay:import' });
|
|
127
127
|
|
|
128
128
|
// Check if registered, auto-register if not
|
|
129
129
|
try {
|
|
130
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
130
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
131
131
|
if (!fs.existsSync(regPath)) {
|
|
132
|
-
logger?.info?.('[
|
|
132
|
+
logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
|
|
133
133
|
const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
|
|
134
134
|
const regOutput = parseCliOutput(regResult.stdout);
|
|
135
135
|
if (regOutput.success) {
|
|
136
|
-
logger?.info?.('[
|
|
136
|
+
logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
|
|
137
137
|
|
|
138
138
|
// Auto-advertise services from config
|
|
139
139
|
await autoAdvertiseServices(env, cliPath, logger);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
} catch (err: any) {
|
|
143
|
-
logger?.warn?.('[
|
|
143
|
+
logger?.warn?.('[openclaw-overlay] Auto-registration failed:', err.message);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
} catch (err) {
|
|
@@ -153,7 +153,7 @@ async function startAutoImport(env: any, cliPath: string, logger: any) {
|
|
|
153
153
|
}
|
|
154
154
|
}, 30000); // Check every 30 seconds for faster onboarding
|
|
155
155
|
} catch (err: any) {
|
|
156
|
-
logger?.warn?.('[
|
|
156
|
+
logger?.warn?.('[openclaw-overlay] Auto-import setup failed:', err.message);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -179,7 +179,7 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
|
179
179
|
if (!fs.existsSync(configPath)) continue;
|
|
180
180
|
try {
|
|
181
181
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
182
|
-
const pluginConfig = config?.plugins?.entries?.['
|
|
182
|
+
const pluginConfig = config?.plugins?.entries?.['openclaw-overlay']?.config;
|
|
183
183
|
if (pluginConfig?.services && Array.isArray(pluginConfig.services)) {
|
|
184
184
|
servicesToAdvertise = pluginConfig.services;
|
|
185
185
|
break;
|
|
@@ -188,11 +188,11 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
if (servicesToAdvertise.length === 0) {
|
|
191
|
-
logger?.info?.('[
|
|
191
|
+
logger?.info?.('[openclaw-overlay] No services configured for auto-advertising');
|
|
192
192
|
return;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
logger?.info?.(`[
|
|
195
|
+
logger?.info?.(`[openclaw-overlay] Auto-advertising ${servicesToAdvertise.length} services from config...`);
|
|
196
196
|
|
|
197
197
|
const advertised: string[] = [];
|
|
198
198
|
const failed: string[] = [];
|
|
@@ -214,11 +214,11 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
if (advertised.length > 0) logger?.info?.(`[
|
|
218
|
-
if (failed.length > 0) logger?.warn?.(`[
|
|
217
|
+
if (advertised.length > 0) logger?.info?.(`[openclaw-overlay] Successfully advertised: ${advertised.join(', ')}`);
|
|
218
|
+
if (failed.length > 0) logger?.warn?.(`[openclaw-overlay] Failed to advertise: ${failed.join(', ')}`);
|
|
219
219
|
|
|
220
220
|
} catch (err: any) {
|
|
221
|
-
logger?.warn?.('[
|
|
221
|
+
logger?.warn?.('[openclaw-overlay] Auto-advertising failed:', err.message);
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
|
|
@@ -227,12 +227,12 @@ async function autoAdvertiseServices(env: any, cliPath: string, logger: any) {
|
|
|
227
227
|
* This is the standard way to invoke an agent with a specific context.
|
|
228
228
|
*/
|
|
229
229
|
function wakeAgent(text: string, logger: any, options: { sessionKey?: string } = {}) {
|
|
230
|
-
const sessionKey = options.sessionKey || `hook:
|
|
230
|
+
const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
|
|
231
231
|
const gatewayPort = getGatewayPort();
|
|
232
232
|
const httpToken = getHooksToken();
|
|
233
233
|
|
|
234
234
|
if (!httpToken) {
|
|
235
|
-
logger?.warn?.('[
|
|
235
|
+
logger?.warn?.('[openclaw-overlay] Skipped wakeAgent: OPENCLAW_HOOKS_TOKEN not set');
|
|
236
236
|
return;
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -251,14 +251,14 @@ function wakeAgent(text: string, logger: any, options: { sessionKey?: string } =
|
|
|
251
251
|
})
|
|
252
252
|
.then(async (res) => {
|
|
253
253
|
if (res.ok) {
|
|
254
|
-
logger?.info?.(`[
|
|
254
|
+
logger?.info?.(`[openclaw-overlay] Agent invoked via /hooks/agent (session: ${sessionKey})`);
|
|
255
255
|
} else {
|
|
256
256
|
const body = await res.text().catch(() => '');
|
|
257
|
-
logger?.warn?.(`[
|
|
257
|
+
logger?.warn?.(`[openclaw-overlay] /hooks/agent failed: ${res.status} ${body}`);
|
|
258
258
|
}
|
|
259
259
|
})
|
|
260
260
|
.catch((err: any) => {
|
|
261
|
-
logger?.warn?.('[
|
|
261
|
+
logger?.warn?.('[openclaw-overlay] /hooks/agent error:', err.message);
|
|
262
262
|
});
|
|
263
263
|
}
|
|
264
264
|
|
|
@@ -323,15 +323,15 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
|
|
|
323
323
|
requestCleanupInterval = setInterval(async () => {
|
|
324
324
|
if (serviceRunning) {
|
|
325
325
|
wokenRequests.clear();
|
|
326
|
-
logger?.debug?.('[
|
|
326
|
+
logger?.debug?.('[openclaw-overlay] Cleared stale request IDs');
|
|
327
327
|
|
|
328
328
|
// Also clean up old queue entries
|
|
329
329
|
try {
|
|
330
330
|
const { cleanupServiceQueue } = await import('./src/scripts/utils/storage.js');
|
|
331
331
|
cleanupServiceQueue();
|
|
332
|
-
logger?.debug?.('[
|
|
332
|
+
logger?.debug?.('[openclaw-overlay] Cleaned up old queue entries');
|
|
333
333
|
} catch (err: any) {
|
|
334
|
-
logger?.warn?.('[
|
|
334
|
+
logger?.warn?.('[openclaw-overlay] Queue cleanup failed:', err.message);
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
}, 5 * 60 * 1000);
|
|
@@ -351,9 +351,9 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
|
|
|
351
351
|
for (const line of lines) {
|
|
352
352
|
try {
|
|
353
353
|
const event = JSON.parse(line);
|
|
354
|
-
logger?.debug?.(`[
|
|
354
|
+
logger?.debug?.(`[openclaw-overlay] ${event.event || event.type || 'message'}:`, JSON.stringify(event).slice(0, 200));
|
|
355
355
|
|
|
356
|
-
const alertDir = path.join(process.env.HOME || '', '.openclaw', '
|
|
356
|
+
const alertDir = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay');
|
|
357
357
|
fs.mkdirSync(alertDir, { recursive: true });
|
|
358
358
|
|
|
359
359
|
// Detect queued-for-agent events — invoke agent via /hooks/agent
|
|
@@ -363,20 +363,20 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
|
|
|
363
363
|
|
|
364
364
|
// Check if already woken to prevent duplicate processing
|
|
365
365
|
if (wokenRequests.has(requestId)) {
|
|
366
|
-
logger?.debug?.(`[
|
|
366
|
+
logger?.debug?.(`[openclaw-overlay] Request ${requestId} already woken, skipping duplicate`);
|
|
367
367
|
return;
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
// Skip wake-up for already processed requests unless they're pending
|
|
371
371
|
if (event.action?.startsWith('already-') && !event.action.includes('pending')) {
|
|
372
|
-
logger?.debug?.(`[
|
|
372
|
+
logger?.debug?.(`[openclaw-overlay] Request ${requestId} already processed (${event.action}), skipping`);
|
|
373
373
|
return;
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
wokenRequests.add(requestId);
|
|
377
|
-
logger?.info?.(`[
|
|
377
|
+
logger?.info?.(`[openclaw-overlay] ⚡ Incoming ${event.serviceId} request from ${event.from?.slice(0, 12)}...`);
|
|
378
378
|
const wakeText = `⚡ Incoming overlay service request!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nPaid: ${event.satoshisReceived || '?'} sats\n\nFulfill it now:\n1. overlay({ action: "pending-requests" })\n2. Process the ${event.serviceId} request using your capabilities\n3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
|
|
379
|
-
wakeAgent(wakeText, logger, { sessionKey: `hook:
|
|
379
|
+
wakeAgent(wakeText, logger, { sessionKey: `hook:openclaw-overlay:${event.id || Date.now()}` });
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
// Detect service-response events — invoke agent to notify user
|
|
@@ -388,9 +388,9 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
|
|
|
388
388
|
const formatted = event.formatted || '';
|
|
389
389
|
const resultJson = event.result ? JSON.stringify(event.result, null, 2) : '(no result data)';
|
|
390
390
|
|
|
391
|
-
logger?.info?.(`[
|
|
391
|
+
logger?.info?.(`[openclaw-overlay] 📬 Response received for ${svcId} from ${from?.slice(0, 12)}... — status: ${status}`);
|
|
392
392
|
const wakeText = `📬 Overlay service response received!\n\nService: ${svcId}\nFrom: ${from}\nStatus: ${status}\n${formatted ? `\nSummary: ${formatted}` : ''}\n\nFull result:\n${resultJson}\n\nNotify the user of this response in a clear, human-readable format.`;
|
|
393
|
-
wakeAgent(wakeText, logger, { sessionKey: `hook:
|
|
393
|
+
wakeAgent(wakeText, logger, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
// Write payment/activity notifications for ALL significant events
|
|
@@ -410,12 +410,12 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
|
|
|
410
410
|
try {
|
|
411
411
|
const event = JSON.parse(line);
|
|
412
412
|
if (event.event === 'connected') {
|
|
413
|
-
logger?.info?.('[
|
|
413
|
+
logger?.info?.('[openclaw-overlay] WebSocket relay connected');
|
|
414
414
|
} else if (event.event === 'disconnected') {
|
|
415
|
-
logger?.warn?.('[
|
|
415
|
+
logger?.warn?.('[openclaw-overlay] WebSocket disconnected, reconnecting...');
|
|
416
416
|
}
|
|
417
417
|
} catch {
|
|
418
|
-
logger?.debug?.(`[
|
|
418
|
+
logger?.debug?.(`[openclaw-overlay] ${line}`);
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
421
|
});
|
|
@@ -423,7 +423,7 @@ function startBackgroundService(env: any, cliPath: string, logger: any) {
|
|
|
423
423
|
proc.on('exit', (code) => {
|
|
424
424
|
backgroundProcess = null;
|
|
425
425
|
if (serviceRunning) {
|
|
426
|
-
logger?.warn?.(`[
|
|
426
|
+
logger?.warn?.(`[openclaw-overlay] Background service exited (code ${code}), restarting in 5s...`);
|
|
427
427
|
setTimeout(spawnConnect, 5000);
|
|
428
428
|
}
|
|
429
429
|
});
|
|
@@ -585,7 +585,7 @@ export default function register(api: any) {
|
|
|
585
585
|
|
|
586
586
|
// Register background relay service
|
|
587
587
|
api.registerService({
|
|
588
|
-
id: "
|
|
588
|
+
id: "openclaw-overlay-relay",
|
|
589
589
|
start: async () => {
|
|
590
590
|
try {
|
|
591
591
|
api.logger.info("Starting BSV overlay WebSocket relay...");
|
|
@@ -615,7 +615,7 @@ export default function register(api: any) {
|
|
|
615
615
|
|
|
616
616
|
// Register a skill-style wake handler
|
|
617
617
|
api.registerHook({
|
|
618
|
-
id: "
|
|
618
|
+
id: "openclaw-overlay-wake",
|
|
619
619
|
event: "gateway:start",
|
|
620
620
|
handler: async (ctx: any) => {
|
|
621
621
|
// Auto-check for registration on startup
|
|
@@ -624,8 +624,8 @@ export default function register(api: any) {
|
|
|
624
624
|
const env = buildEnvironment(pluginConfig);
|
|
625
625
|
const cliPath = path.join(__dirname, 'dist', 'cli.js');
|
|
626
626
|
|
|
627
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
628
|
-
const onboardSentFile = path.join(process.env.HOME || '', '.openclaw', '
|
|
627
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
628
|
+
const onboardSentFile = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'onboarding-sent.flag');
|
|
629
629
|
|
|
630
630
|
if (!fs.existsSync(regPath) && !fs.existsSync(onboardSentFile)) {
|
|
631
631
|
// Check if wallet exists
|
|
@@ -647,7 +647,7 @@ export default function register(api: any) {
|
|
|
647
647
|
// Funded but not registered. Auto-register.
|
|
648
648
|
const regResult = await execFileAsync('node', [cliPath, 'register'], { env, timeout: 60000 });
|
|
649
649
|
if (parseCliOutput(regResult.stdout).success) {
|
|
650
|
-
ctx.logger.info('[
|
|
650
|
+
ctx.logger.info('[openclaw-overlay] Agent auto-registered on startup');
|
|
651
651
|
await autoAdvertiseServices(env, cliPath, ctx.logger);
|
|
652
652
|
}
|
|
653
653
|
}
|
|
@@ -655,7 +655,7 @@ export default function register(api: any) {
|
|
|
655
655
|
}
|
|
656
656
|
|
|
657
657
|
} catch (err: any) {
|
|
658
|
-
api.log?.debug?.('[
|
|
658
|
+
api.log?.debug?.('[openclaw-overlay] Auto-setup/onboarding skipped:', err.message);
|
|
659
659
|
}
|
|
660
660
|
})();
|
|
661
661
|
}
|
|
@@ -721,14 +721,16 @@ export default function register(api: any) {
|
|
|
721
721
|
if (result.agents) {
|
|
722
722
|
console.log("\nAgents:");
|
|
723
723
|
result.agents.forEach((agent: any) => {
|
|
724
|
-
|
|
724
|
+
const name = agent.name || agent.agentName || 'Unknown Agent';
|
|
725
|
+
console.log(`- ${name} (${agent.identityKey.slice(0, 16)}...)`);
|
|
725
726
|
});
|
|
726
727
|
}
|
|
727
728
|
|
|
728
729
|
if (result.services) {
|
|
729
730
|
console.log("\nServices:");
|
|
730
731
|
result.services.forEach((service: any) => {
|
|
731
|
-
|
|
732
|
+
const name = service.name || service.agentName || 'Unknown Agent';
|
|
733
|
+
console.log(`- ${service.serviceId} - ${name} (${service.pricing?.amountSats || 0} sats) by ${service.identityKey.slice(0, 12)}`);
|
|
732
734
|
});
|
|
733
735
|
}
|
|
734
736
|
} catch (error: any) {
|
|
@@ -981,7 +983,7 @@ async function handleUnregister(params: any, env: any, cliPath: string) {
|
|
|
981
983
|
const { confirmToken } = params;
|
|
982
984
|
|
|
983
985
|
// Load current registration to show what will be deleted
|
|
984
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
986
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
985
987
|
let registration: any = null;
|
|
986
988
|
try {
|
|
987
989
|
if (fs.existsSync(regPath)) {
|
|
@@ -1148,29 +1150,51 @@ async function handleDiscover(params: any, env: any, cliPath: string) {
|
|
|
1148
1150
|
}
|
|
1149
1151
|
|
|
1150
1152
|
async function handleBalance(env: any, cliPath: string) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1153
|
+
try {
|
|
1154
|
+
const result = await execFileAsync('node', [cliPath, 'balance'], { env });
|
|
1155
|
+
const output = parseCliOutput(result.stdout);
|
|
1156
|
+
|
|
1157
|
+
if (!output.success) {
|
|
1158
|
+
throw new Error(output.error);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return output.data;
|
|
1162
|
+
} catch (error: any) {
|
|
1163
|
+
if (error.message?.includes('Wallet not initialized')) {
|
|
1164
|
+
return { walletBalance: 0, error: 'Not Initialized' };
|
|
1165
|
+
}
|
|
1166
|
+
throw new Error(`Balance check failed: ${error.message}`);
|
|
1156
1167
|
}
|
|
1157
|
-
|
|
1158
|
-
return output.data;
|
|
1159
1168
|
}
|
|
1160
1169
|
|
|
1161
1170
|
async function handleStatus(env: any, cliPath: string) {
|
|
1162
1171
|
try {
|
|
1163
1172
|
// Get identity
|
|
1164
|
-
|
|
1165
|
-
|
|
1173
|
+
let identity: any = { data: { identityKey: 'Not Initialized' } };
|
|
1174
|
+
try {
|
|
1175
|
+
const identityResult = await execFileAsync('node', [cliPath, 'identity'], { env });
|
|
1176
|
+
identity = parseCliOutput(identityResult.stdout);
|
|
1177
|
+
} catch (err: any) {
|
|
1178
|
+
if (!err.message?.includes('Wallet not initialized')) throw err;
|
|
1179
|
+
}
|
|
1166
1180
|
|
|
1167
1181
|
// Get balance
|
|
1168
|
-
|
|
1169
|
-
|
|
1182
|
+
let balance: any = { data: { walletBalance: 0 } };
|
|
1183
|
+
try {
|
|
1184
|
+
const balanceResult = await execFileAsync('node', [cliPath, 'balance'], { env });
|
|
1185
|
+
balance = parseCliOutput(balanceResult.stdout);
|
|
1186
|
+
} catch (err: any) {
|
|
1187
|
+
if (!err.message?.includes('Wallet not initialized')) throw err;
|
|
1188
|
+
}
|
|
1170
1189
|
|
|
1171
1190
|
// Get services
|
|
1172
|
-
|
|
1173
|
-
|
|
1191
|
+
let services: any = { data: [] };
|
|
1192
|
+
try {
|
|
1193
|
+
const servicesResult = await execFileAsync('node', [cliPath, 'services'], { env });
|
|
1194
|
+
services = parseCliOutput(servicesResult.stdout);
|
|
1195
|
+
} catch (err: any) {
|
|
1196
|
+
if (!err.message?.includes('Wallet not initialized')) throw err;
|
|
1197
|
+
}
|
|
1174
1198
|
|
|
1175
1199
|
return {
|
|
1176
1200
|
identity: identity.data,
|
|
@@ -1259,7 +1283,7 @@ async function handleImport(params: any, env: any, cliPath: string) {
|
|
|
1259
1283
|
}
|
|
1260
1284
|
|
|
1261
1285
|
// Check if we should auto-register after successful import
|
|
1262
|
-
const regPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
1286
|
+
const regPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'registration.json');
|
|
1263
1287
|
const isRegistered = fs.existsSync(regPath);
|
|
1264
1288
|
|
|
1265
1289
|
if (!isRegistered && output.data?.balance >= 1000) {
|
|
@@ -1515,14 +1539,14 @@ async function handlePendingRequests(env: any, cliPath: string) {
|
|
|
1515
1539
|
if (!output.success) throw new Error(`Queue check failed: ${output.error}`);
|
|
1516
1540
|
|
|
1517
1541
|
// Clear the alert file since we're checking now
|
|
1518
|
-
const alertPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
1542
|
+
const alertPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'pending-alert.jsonl');
|
|
1519
1543
|
try { if (fs.existsSync(alertPath)) fs.unlinkSync(alertPath); } catch {}
|
|
1520
1544
|
|
|
1521
1545
|
return output.data;
|
|
1522
1546
|
}
|
|
1523
1547
|
|
|
1524
1548
|
function handleActivity() {
|
|
1525
|
-
const feedPath = path.join(process.env.HOME || '', '.openclaw', '
|
|
1549
|
+
const feedPath = path.join(process.env.HOME || '', '.openclaw', 'openclaw-overlay', 'activity-feed.jsonl');
|
|
1526
1550
|
if (!fs.existsSync(feedPath)) return { events: [], count: 0 };
|
|
1527
1551
|
|
|
1528
1552
|
const lines = fs.readFileSync(feedPath, 'utf-8').trim().split('\n').filter(Boolean);
|
|
@@ -1568,6 +1592,9 @@ function buildEnvironment(config: any) {
|
|
|
1568
1592
|
if (config.chaintracksUrl) {
|
|
1569
1593
|
env.BSV_CHAINTRACKS_URL = config.chaintracksUrl;
|
|
1570
1594
|
}
|
|
1595
|
+
if (config.arcUrl) {
|
|
1596
|
+
env.BSV_ARC_URL = config.arcUrl;
|
|
1597
|
+
}
|
|
1571
1598
|
|
|
1572
1599
|
// Set defaults
|
|
1573
1600
|
env.BSV_NETWORK = env.BSV_NETWORK || 'mainnet';
|
package/openclaw.plugin.json
CHANGED
|
@@ -15,11 +15,21 @@
|
|
|
15
15
|
"default": "https://clawoverlay.com",
|
|
16
16
|
"description": "Overlay server URL for registration, discovery, and relay"
|
|
17
17
|
},
|
|
18
|
+
"network": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"enum": ["mainnet", "testnet"],
|
|
21
|
+
"default": "mainnet",
|
|
22
|
+
"description": "BSV network to use"
|
|
23
|
+
},
|
|
18
24
|
"chaintracksUrl": {
|
|
19
25
|
"type": "string",
|
|
20
26
|
"default": "https://chaintracks-us-1.bsvb.tech",
|
|
21
27
|
"description": "Custom Chaintracks server URL for SPV header verification"
|
|
22
28
|
},
|
|
29
|
+
"arcUrl": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Custom ARC/Arcade server URL for transaction broadcasting"
|
|
32
|
+
},
|
|
23
33
|
"agentName": {
|
|
24
34
|
"type": "string",
|
|
25
35
|
"description": "Display name for this agent on the overlay network (defaults to hostname)"
|
|
@@ -69,12 +79,21 @@
|
|
|
69
79
|
"placeholder": "https://clawoverlay.com",
|
|
70
80
|
"advanced": true
|
|
71
81
|
},
|
|
82
|
+
"network": {
|
|
83
|
+
"label": "BSV Network"
|
|
84
|
+
},
|
|
72
85
|
"chaintracksUrl": {
|
|
73
86
|
"label": "Chaintracks Server URL",
|
|
74
87
|
"placeholder": "https://chaintracks-us-1.bsvb.tech",
|
|
75
88
|
"help": "Custom server for SPV block header verification",
|
|
76
89
|
"advanced": true
|
|
77
90
|
},
|
|
91
|
+
"arcUrl": {
|
|
92
|
+
"label": "ARC/Arcade Server URL",
|
|
93
|
+
"placeholder": "https://arc.taal.com",
|
|
94
|
+
"help": "Custom server for transaction broadcasting",
|
|
95
|
+
"advanced": true
|
|
96
|
+
},
|
|
78
97
|
"agentName": {
|
|
79
98
|
"label": "Agent Name",
|
|
80
99
|
"placeholder": "my-agent",
|
package/package.json
CHANGED
package/src/core/wallet.ts
CHANGED
|
@@ -232,7 +232,13 @@ export class BSVAgentWallet {
|
|
|
232
232
|
// 3. Network services (ARC broadcasting, chain tracking, etc.)
|
|
233
233
|
const serviceOptions = Services.createDefaultOptions(chain);
|
|
234
234
|
const chaintracksUrl = process.env.BSV_CHAINTRACKS_URL || 'https://chaintracks-us-1.bsvb.tech';
|
|
235
|
+
const arcUrl = process.env.BSV_ARC_URL;
|
|
236
|
+
|
|
235
237
|
serviceOptions.chaintracks = new ChaintracksServiceClient(chain, chaintracksUrl);
|
|
238
|
+
if (arcUrl) {
|
|
239
|
+
serviceOptions.arcUrl = arcUrl;
|
|
240
|
+
}
|
|
241
|
+
|
|
236
242
|
serviceOptions.taalApiKey = taalApiKey;
|
|
237
243
|
const services = new Services(serviceOptions);
|
|
238
244
|
|
|
@@ -288,12 +288,23 @@ export async function cmdBaemailRefund(requestId: string | undefined): Promise<n
|
|
|
288
288
|
|
|
289
289
|
await tx.sign();
|
|
290
290
|
|
|
291
|
-
// Broadcast
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
// Broadcast using configured ARC/Arcade URL or fallback to WhatsOnChain
|
|
292
|
+
const arcUrl = process.env.BSV_ARC_URL;
|
|
293
|
+
let broadcastResp;
|
|
294
|
+
|
|
295
|
+
if (arcUrl) {
|
|
296
|
+
broadcastResp = await fetchWithTimeout(`${arcUrl.replace(/\/$/, '')}/v1/tx`, {
|
|
297
|
+
method: 'POST',
|
|
298
|
+
headers: { 'Content-Type': 'application/json' },
|
|
299
|
+
body: JSON.stringify({ rawTx: tx.toHex() }),
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
broadcastResp = await fetchWithTimeout('https://api.whatsonchain.com/v1/bsv/main/tx/raw', {
|
|
303
|
+
method: 'POST',
|
|
304
|
+
headers: { 'Content-Type': 'application/json' },
|
|
305
|
+
body: JSON.stringify({ txhex: tx.toHex() }),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
297
308
|
|
|
298
309
|
if (!broadcastResp.ok) {
|
|
299
310
|
const errBody = await broadcastResp.text();
|
package/src/scripts/config.ts
CHANGED
|
@@ -7,7 +7,7 @@ import os from 'node:os';
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
|
|
9
9
|
// Auto-load .env from overlay state dir if it exists
|
|
10
|
-
const overlayEnvPath = path.join(os.homedir(), '.openclaw', '
|
|
10
|
+
const overlayEnvPath = path.join(os.homedir(), '.openclaw', 'openclaw-overlay', '.env');
|
|
11
11
|
try {
|
|
12
12
|
if (fs.existsSync(overlayEnvPath)) {
|
|
13
13
|
for (const line of fs.readFileSync(overlayEnvPath, 'utf-8').split('\n')) {
|
|
@@ -43,7 +43,7 @@ export const AGENT_DESCRIPTION = process.env.AGENT_DESCRIPTION ||
|
|
|
43
43
|
export const WOC_API_KEY = process.env.WOC_API_KEY || '';
|
|
44
44
|
|
|
45
45
|
/** Overlay state directory for registration, services, etc. */
|
|
46
|
-
export const OVERLAY_STATE_DIR = path.join(os.homedir(), '.openclaw', '
|
|
46
|
+
export const OVERLAY_STATE_DIR = path.join(os.homedir(), '.openclaw', 'openclaw-overlay');
|
|
47
47
|
|
|
48
48
|
/** Protocol identifier for overlay transactions */
|
|
49
49
|
export const PROTOCOL_ID = 'openclaw overlay v1';
|
|
@@ -38,7 +38,9 @@ export async function cmdDiscover(args: string[]): Promise<never> {
|
|
|
38
38
|
try {
|
|
39
39
|
const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
|
|
40
40
|
if (data?.type === 'identity') {
|
|
41
|
-
|
|
41
|
+
// Handle both 'name' and 'agentName' for backward compatibility
|
|
42
|
+
const name = data.name || data.agentName || 'Unknown Agent';
|
|
43
|
+
results.agents.push({ ...data, name, txid });
|
|
42
44
|
}
|
|
43
45
|
} catch { /* ignore */ }
|
|
44
46
|
}
|