openclaw-overlay-plugin 0.7.70 → 0.7.71

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/SKILL.md CHANGED
@@ -1,10 +1,10 @@
1
1
  ---
2
- name: openclaw-overlay
2
+ name: 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.
6
6
  Use when the user wants to register an agent, discover or request services,
7
- advertise capabilities, manage a BSV wallet, or handle incoming service requests.
7
+ advertise capabilities (via SHIP/SLAP), manage a BSV wallet, or handle incoming service requests.
8
8
  metadata: '{"openclaw": {"requires": {"bins": ["node"]}}}'
9
9
  ---
10
10
 
@@ -12,81 +12,62 @@ metadata: '{"openclaw": {"requires": {"bins": ["node"]}}}'
12
12
 
13
13
  | Action | Description |
14
14
  |--------|-------------|
15
+ | `status` | **Recommended**: Show identity key, balance, network, and services |
15
16
  | `onboard` | One-step setup: wallet, address, funding check, register |
16
17
  | `request` | Auto-discover cheapest provider and request a service |
17
18
  | `discover` | List agents and services on the network |
18
19
  | `balance` | Show wallet balance |
19
- | `status` | Show identity, balance, and services |
20
20
  | `pay` | Direct payment to an agent |
21
- | `setup` | Initialize wallet |
22
- | `address` | Show receive address |
23
- | `import` | Import funded UTXO by txid |
21
+ | `address` | Show receive address (network-aware) |
24
22
  | `register` | Register on overlay network |
25
23
  | `advertise` | Advertise a new service |
24
+ | `advertise-ship` | Advertise a Topic Manager (SHIP protocol) |
25
+ | `advertise-slap` | Advertise a Lookup Service (SLAP protocol) |
26
26
  | `readvertise` | Update service pricing/name/description |
27
27
  | `remove` | Remove an advertised service |
28
28
  | `services` | List our advertised services |
29
- | `send` | Send direct message to agent |
30
- | `inbox` | Check incoming messages |
31
- | `refund` | Sweep wallet to external address |
32
29
  | `pending-requests` | Check pending incoming service requests |
33
30
  | `fulfill` | Fulfill a pending service request |
31
+ | `setup` | Initialize wallet |
32
+ | `import` | Import funded UTXO by txid |
34
33
  | `unregister` | Remove agent from network (destructive, requires confirmation) |
35
- | `remove-service` | Remove a service from network (destructive, requires confirmation) |
36
34
 
37
- ## Onboarding
35
+ ## Network Support
38
36
 
39
- On first run, the plugin auto-creates a wallet and wakes you. Guide the user through setup as a conversation:
37
+ The plugin is network-aware and supports:
38
+ - **`mainnet`**: Production network (Babbage tracker: `https://overlay.babbage.systems`)
39
+ - **`testnet`**: Testing network (Babbage tracker: `https://testnet-users.bapp.dev`)
40
+ - **`local`**: Local development (default tracker: `http://localhost:8080`)
40
41
 
41
- 1. **Ask for agent name**: "What name do you want for your agent on the network?"
42
- 2. **Ask for description**: "Describe what your agent does in 1-2 sentences."
43
- 3. **Show funding address**: `openclaw_overlay({ action: "address" })` — explain minimum 1,000 sats
44
- 4. **After funding**: `openclaw_overlay({ action: "onboard", agentName: "...", agentDescription: "..." })`
45
- 5. **Ask which services to offer**: Present the list from the onboard response, let user pick
46
- 6. **Advertise selected**: `openclaw_overlay({ action: "advertise", ... })` for each
42
+ Addresses and WIFs will automatically use the correct prefix ('1' for mainnet, 'm'/'n' for testnet).
47
43
 
48
- If you need to customize settings, tell the user to add them to their config under `plugins.entries["openclaw-overlay-plugin"]`:
49
- ```json
50
- {
51
- "plugins": {
52
- "entries": {
53
- "openclaw-overlay-plugin": {
54
- "agentName": "...",
55
- "overlayUrl": "..."
56
- }
57
- }
58
- }
59
- }
60
- ```
44
+ ## Onboarding & Setup
61
45
 
62
- Do NOT use defaults without asking. Do NOT skip the name/description questions.
46
+ 1. **Check Status**: `overlay({ action: "status" })`
47
+ 2. **Initialize**: If not setup, ask for agent name/description.
48
+ 3. **Show Address**: `overlay({ action: "address" })` — explain minimum 1,000 sats
49
+ 4. **Fund & Onboard**: Once funded, `overlay({ action: "onboard" })`
50
+ 5. **Advertise**: `overlay({ action: "advertise", serviceId: "...", name: "...", priceSats: 500 })`
63
51
 
64
- ## Requesting Services
52
+ ## Discovery & Services
65
53
 
66
- Use `openclaw_overlay({ action: "request", service: "<id>", input: {...} })` to auto-discover the cheapest provider, pay, and send the request. The response arrives asynchronously via the background WebSocket service — you'll be woken when it comes back.
67
-
68
- Set `maxPrice` to cap spending. Requests within `maxAutoPaySats` (default 200) auto-pay.
54
+ - **Find services**: `overlay({ action: "discover", service: "tell-joke" })`
55
+ - **Request service**: `overlay({ action: "request", service: "tell-joke", input: { topic: "tech" } })`
56
+ - **Advertise Infrastructure**:
57
+ - SHIP: `overlay({ action: "advertise-ship", domain: "https://my-node.com", topic: "tm_openclaw_identity" })`
58
+ - SLAP: `overlay({ action: "advertise-slap", domain: "https://my-node.com", service: "ls_openclaw_agents" })`
69
59
 
70
60
  ## Fulfilling Requests
71
61
 
72
- The background service queues incoming requests and wakes you automatically.
73
-
74
- 1. `openclaw_overlay({ action: "pending-requests" })` — see what needs handling
75
- 2. Process each request using your full capabilities
76
- 3. `openclaw_overlay({ action: "fulfill", requestId: "...", recipientKey: "...", serviceId: "...", result: {...} })` — send response
77
-
78
- Always fulfill promptly — requesters have already paid.
79
-
80
- ## Spending Rules
62
+ Incoming requests are queued and you'll be woken automatically.
81
63
 
82
- - **Auto-pay**: Requests under `maxAutoPaySats` (default 200 sats) pay automatically
83
- - **Budget**: Daily spending capped at `dailyBudgetSats` (default 1,000 sats/day)
84
- - **Over limit**: Returns an error get user confirmation before retrying with `maxPrice`
85
- - **Destructive actions** (`unregister`, `remove-service`): Require a two-step confirmation token
64
+ 1. `overlay({ action: "pending-requests" })` see what needs handling
65
+ 2. Process the request payload.
66
+ 3. `overlay({ action: "fulfill", requestId: "...", recipientKey: "...", serviceId: "...", result: {...} })`
86
67
 
87
- ## References
68
+ ## Spending & Security
88
69
 
89
- - [Service catalog and input schemas](references/services.md)
90
- - [Configuration, environment variables, and CLI commands](references/configuration.md)
91
- - [Wallet operations, funding, budget, and import details](references/wallet-operations.md)
92
- - [Overlay protocol specification](references/protocol.md)
70
+ - **Auto-pay**: Requests under `maxAutoPaySats` (default 200 sats) pay automatically.
71
+ - **Budget**: Daily spending is capped (default 5,000 sats/day).
72
+ - **Destructive actions**: `unregister` requires a two-step confirmation token.
73
+ - **Privacy**: Private keys are stored locally in `~/.openclaw/bsv-wallet`. Never share `wallet-identity.json`.
package/dist/index.js CHANGED
@@ -302,7 +302,7 @@ function getCliPath() {
302
302
  * Decentralized agent marketplace with BSV micropayments.
303
303
  */
304
304
  export const plugin = {
305
- id: "openclaw-overlay-plugin",
305
+ id: "overlay",
306
306
  name: "BSV Overlay Network",
307
307
  description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
308
308
  async activate(api) {
@@ -313,7 +313,7 @@ export const plugin = {
313
313
  return;
314
314
  isInitialized = true;
315
315
  const entries = api.getConfig?.()?.plugins?.entries || {};
316
- const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
316
+ const entry = entries['overlay'] || entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
317
317
  const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
318
318
  // 1. Tool
319
319
  api.registerTool({
@@ -322,8 +322,10 @@ export const plugin = {
322
322
  parameters: {
323
323
  type: "object",
324
324
  properties: {
325
- action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
325
+ action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister", "advertise-ship", "advertise-slap"] },
326
326
  service: { type: "string" },
327
+ topic: { type: "string" },
328
+ domain: { type: "string" },
327
329
  input: { type: "object" },
328
330
  identityKey: { type: "string" },
329
331
  sats: { type: "number" },
@@ -353,10 +355,29 @@ export const plugin = {
353
355
  try {
354
356
  const action = ctx.args?.[0] || 'status';
355
357
  if (action === 'help') {
356
- return { text: `🛰️ **Overlay Help**\n\n**Subcommands**:\n- \`status\`: Show identity and wallet balance\n- \`balance\`: Show current satoshis\n- \`onboard\`: Start discovery setup\n- \`discover <serviceId>\`: Find providers on network\n- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
358
+ return { text: `🛰️ **Overlay Help**\n\n**Subcommands**:\n- \`status\`: Show identity and wallet balance\n- \`balance\`: Show current satoshis\n- \`onboard\`: Start discovery setup\n- \`discover <serviceId>\`: Find providers on network\n- \`advertise-ship <domain> <topic>\`: Advertise a topic manager\n- \`advertise-slap <domain> <service>\`: Advertise a lookup service\n- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
357
359
  }
358
- const result = await executeOverlayAction({ action }, pluginConfig, api);
359
- return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
360
+ const params = { action };
361
+ if (action === 'discover') {
362
+ params.service = ctx.args[1];
363
+ }
364
+ else if (action === 'advertise-ship') {
365
+ params.domain = ctx.args[1];
366
+ params.topic = ctx.args[2];
367
+ }
368
+ else if (action === 'advertise-slap') {
369
+ params.domain = ctx.args[1];
370
+ params.service = ctx.args[2];
371
+ }
372
+ const result = await executeOverlayAction(params, pluginConfig, api);
373
+ if (typeof result === 'string')
374
+ return { text: result };
375
+ // Formatted status response
376
+ if (action === 'status') {
377
+ const status = result;
378
+ return { text: `🛰️ **Overlay Status**\n\n**Identity Key**: \`${status.identity?.identityKey || 'Not setup'}\`\n**Balance**: ${status.balance?.walletBalance || 0} satoshis\n**Network**: ${status.identity?.network || 'mainnet'}` };
379
+ }
380
+ return { text: `**Overlay ${action.toUpperCase()}**\n\n${JSON.stringify(result, null, 2)}` };
360
381
  }
361
382
  catch (error) {
362
383
  return { text: `❌ Error: ${error.message}` };
@@ -404,9 +425,21 @@ async function executeOverlayAction(params, config, api) {
404
425
  case "onboard": return await handleOnboard(params, env, cliPath);
405
426
  case "pending-requests": return await handlePendingRequests(env, cliPath);
406
427
  case "fulfill": return await handleFulfill(params, env, cliPath);
428
+ case "advertise-ship": return await handleAdvertiseSHIP(params, env, cliPath);
429
+ case "advertise-slap": return await handleAdvertiseSLAP(params, env, cliPath);
407
430
  default: throw new Error(`Unknown action: ${action}`);
408
431
  }
409
432
  }
433
+ async function handleAdvertiseSHIP(params, env, cliPath) {
434
+ const { domain, topic } = params;
435
+ const result = await execFileAsync('node', [cliPath, 'advertise-ship', domain, topic], { env });
436
+ return parseCliOutput(result.stdout).data;
437
+ }
438
+ async function handleAdvertiseSLAP(params, env, cliPath) {
439
+ const { domain, service } = params;
440
+ const result = await execFileAsync('node', [cliPath, 'advertise-slap', domain, service], { env });
441
+ return parseCliOutput(result.stdout).data;
442
+ }
410
443
  async function handleServiceRequest(params, env, cliPath, config, api) {
411
444
  const { service, identityKey: targetKey, input } = params;
412
445
  const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { ok, fail } from './scripts/output.js';
8
8
  // Wallet commands
9
- import { cmdSetup, cmdIdentity, cmdAddress } from './scripts/wallet/setup.js';
9
+ import { cmdSetup, cmdIdentity, cmdAddress, cmdStatus } from './scripts/wallet/setup.js';
10
10
  import { cmdBalance, cmdImport, cmdRefund } from './scripts/wallet/balance.js';
11
11
  // Overlay registration commands
12
12
  import { cmdRegister, cmdUnregister } from './scripts/overlay/registration.js';
@@ -14,6 +14,7 @@ import { cmdRegister, cmdUnregister } from './scripts/overlay/registration.js';
14
14
  import { cmdServices, cmdAdvertise, cmdRemove, cmdReadvertise } from './scripts/overlay/services.js';
15
15
  // Discovery commands
16
16
  import { cmdDiscover } from './scripts/overlay/discover.js';
17
+ import { cmdAdvertiseSHIP, cmdAdvertiseSLAP } from './scripts/overlay/advertisement.js';
17
18
  // Payment commands
18
19
  import { cmdPay, cmdVerify, cmdAccept } from './scripts/payment/commands.js';
19
20
  // Messaging commands
@@ -43,7 +44,7 @@ async function main() {
43
44
  wallet: ['setup', 'identity', 'address', 'balance', 'import <txid> [vout]', 'refund <address>'],
44
45
  registration: ['register', 'unregister'],
45
46
  services: ['services', 'advertise <id> <name> <priceSats> [desc]', 'readvertise <id> [name] [priceSats] [desc]', 'remove <id>'],
46
- discovery: ['discover [--service <type>] [--agent <name>]'],
47
+ discovery: ['discover [--service <type>] [--agent <name>]', 'advertise-ship <domain> <topic>', 'advertise-slap <domain> <service>'],
47
48
  payments: ['pay <pubkey> <sats> [desc]', 'verify <beef>', 'accept <beef> <prefix> <suffix> <senderKey> [desc]'],
48
49
  messaging: ['send <key> <type> <json>', 'inbox', 'ack', 'poll', 'connect'],
49
50
  'service-requests': ['request-service <key> <serviceId> <sats> [input]', 'service-queue', 'respond-service <reqId> <key> <serviceId> <result>'],
@@ -54,6 +55,9 @@ async function main() {
54
55
  });
55
56
  break;
56
57
  // Wallet
58
+ case 'status':
59
+ await cmdStatus();
60
+ break;
57
61
  case 'setup':
58
62
  await cmdSetup();
59
63
  break;
@@ -96,6 +100,12 @@ async function main() {
96
100
  case 'discover':
97
101
  await cmdDiscover(args);
98
102
  break;
103
+ case 'advertise-ship':
104
+ await cmdAdvertiseSHIP(args[0], args[1]);
105
+ break;
106
+ case 'advertise-slap':
107
+ await cmdAdvertiseSLAP(args[0], args[1]);
108
+ break;
99
109
  // Payments
100
110
  case 'pay':
101
111
  await cmdPay(args[0], args[1], args.slice(2).join(' ') || undefined);
@@ -22,7 +22,11 @@ export declare const TOPICS: {
22
22
  readonly IDENTITY: "tm_openclaw_identity";
23
23
  readonly SERVICES: "tm_openclaw_services";
24
24
  readonly X_VERIFICATION: "tm_openclaw_x_verification";
25
+ readonly SHIP: "tm_ship";
26
+ readonly SLAP: "tm_slap";
25
27
  };
28
+ /** Default SLAP trackers */
29
+ export declare const DEFAULT_SLAP_TRACKERS: Record<'mainnet' | 'testnet', string[]>;
26
30
  /** Lookup services for overlay queries */
27
31
  export declare const LOOKUP_SERVICES: {
28
32
  readonly AGENTS: "ls_openclaw_agents";
@@ -42,6 +42,13 @@ export const TOPICS = {
42
42
  IDENTITY: 'tm_openclaw_identity',
43
43
  SERVICES: 'tm_openclaw_services',
44
44
  X_VERIFICATION: 'tm_openclaw_x_verification',
45
+ SHIP: 'tm_ship',
46
+ SLAP: 'tm_slap',
47
+ };
48
+ /** Default SLAP trackers */
49
+ export const DEFAULT_SLAP_TRACKERS = {
50
+ mainnet: ['https://overlay.babbage.systems'],
51
+ testnet: ['https://testnet-users.bapp.dev'],
45
52
  };
46
53
  /** Lookup services for overlay queries */
47
54
  export const LOOKUP_SERVICES = {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SHIP and SLAP advertisement commands.
3
+ *
4
+ * SHIP: Service Health & Information Protocol (tm_ship)
5
+ * SLAP: Service Level Agreement Protocol (tm_slap)
6
+ */
7
+ /**
8
+ * Advertise a SHIP record.
9
+ * Announce that you host a specific Topic Manager (tm_).
10
+ */
11
+ export declare function cmdAdvertiseSHIP(domain?: string, topic?: string): Promise<never>;
12
+ /**
13
+ * Advertise a SLAP record.
14
+ * Announce that you host a specific Lookup Service (ls_).
15
+ */
16
+ export declare function cmdAdvertiseSLAP(domain?: string, service?: string): Promise<never>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * SHIP and SLAP advertisement commands.
3
+ *
4
+ * SHIP: Service Health & Information Protocol (tm_ship)
5
+ * SLAP: Service Level Agreement Protocol (tm_slap)
6
+ */
7
+ import { PushDrop, Utils } from '@bsv/sdk';
8
+ import { NETWORK, WALLET_DIR, TOPICS, DEFAULT_SLAP_TRACKERS } from '../config.js';
9
+ import { BSVAgentWallet } from '../../core/wallet.js';
10
+ import { ok, fail } from '../output.js';
11
+ /**
12
+ * Advertise a SHIP record.
13
+ * Announce that you host a specific Topic Manager (tm_).
14
+ */
15
+ export async function cmdAdvertiseSHIP(domain, topic) {
16
+ if (!domain || !topic) {
17
+ return fail('Usage: advertise-ship <domain> <topic>');
18
+ }
19
+ if (!topic.startsWith('tm_')) {
20
+ return fail('Topic must start with "tm_"');
21
+ }
22
+ try {
23
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
24
+ const token = new PushDrop(wallet._setup.wallet);
25
+ // SHIP format: Payload is the domain hosting the topic
26
+ const fields = [Utils.toArray(domain, 'utf8')];
27
+ // Context is [0, topic] to identify the topic manager being advertised
28
+ const lockingScript = (await token.lock(fields, [0, topic], '1', 'self', true, true)).toHex();
29
+ const response = await wallet._setup.wallet.createAction({
30
+ description: `advertise SHIP for ${topic}`,
31
+ outputs: [{
32
+ lockingScript,
33
+ satoshis: 1,
34
+ outputDescription: 'SHIP advertisement',
35
+ basket: TOPICS.SHIP
36
+ }]
37
+ });
38
+ // Broadcast to primary overlay and SLAP trackers
39
+ const trackers = [
40
+ ...DEFAULT_SLAP_TRACKERS[NETWORK]
41
+ ];
42
+ const results = await broadcastToTrackers(response.tx, [TOPICS.SHIP, topic], trackers);
43
+ return ok({
44
+ advertised: 'SHIP',
45
+ topic,
46
+ domain,
47
+ txid: response.txid,
48
+ broadcasts: results
49
+ });
50
+ }
51
+ catch (err) {
52
+ return fail(`SHIP advertisement failed: ${err.message}`);
53
+ }
54
+ }
55
+ /**
56
+ * Advertise a SLAP record.
57
+ * Announce that you host a specific Lookup Service (ls_).
58
+ */
59
+ export async function cmdAdvertiseSLAP(domain, service) {
60
+ if (!domain || !service) {
61
+ return fail('Usage: advertise-slap <domain> <service>');
62
+ }
63
+ if (!service.startsWith('ls_')) {
64
+ return fail('Service must start with "ls_"');
65
+ }
66
+ try {
67
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
68
+ const token = new PushDrop(wallet._setup.wallet);
69
+ // SLAP format: Payload is the domain hosting the lookup service
70
+ const fields = [Utils.toArray(domain, 'utf8')];
71
+ // Context is [0, service] to identify the lookup service being advertised
72
+ const lockingScript = (await token.lock(fields, [0, service], '1', 'self', true, true)).toHex();
73
+ const response = await wallet._setup.wallet.createAction({
74
+ description: `advertise SLAP for ${service}`,
75
+ outputs: [{
76
+ lockingScript,
77
+ satoshis: 1,
78
+ outputDescription: 'SLAP advertisement',
79
+ basket: TOPICS.SLAP
80
+ }]
81
+ });
82
+ // Broadcast to primary overlay and SLAP trackers
83
+ const trackers = [
84
+ ...DEFAULT_SLAP_TRACKERS[NETWORK]
85
+ ];
86
+ const results = await broadcastToTrackers(response.tx, [TOPICS.SLAP, service], trackers);
87
+ return ok({
88
+ advertised: 'SLAP',
89
+ service,
90
+ domain,
91
+ txid: response.txid,
92
+ broadcasts: results
93
+ });
94
+ }
95
+ catch (err) {
96
+ return fail(`SLAP advertisement failed: ${err.message}`);
97
+ }
98
+ }
99
+ /**
100
+ * Helper to broadcast BEEF to multiple trackers/overlays.
101
+ */
102
+ async function broadcastToTrackers(tx, topics, trackers) {
103
+ const results = {};
104
+ const body = new Uint8Array(tx);
105
+ for (const url of trackers) {
106
+ try {
107
+ const resp = await fetch(`${url.replace(/\/$/, '')}/submit`, {
108
+ method: 'POST',
109
+ headers: {
110
+ 'Content-Type': 'application/octet-stream',
111
+ 'X-Topics': JSON.stringify(topics)
112
+ },
113
+ body
114
+ });
115
+ results[url] = resp.ok ? 'success' : `error: ${resp.status}`;
116
+ }
117
+ catch (err) {
118
+ results[url] = `failed: ${err.message}`;
119
+ }
120
+ }
121
+ return results;
122
+ }
@@ -9,6 +9,10 @@ export declare function cmdSetup(): Promise<never>;
9
9
  * Identity command: show identity public key.
10
10
  */
11
11
  export declare function cmdIdentity(): Promise<never>;
12
+ /**
13
+ * Status command: show identity and balance.
14
+ */
15
+ export declare function cmdStatus(): Promise<never>;
12
16
  /**
13
17
  * Address command: show P2PKH receive address.
14
18
  */
@@ -83,6 +83,20 @@ export async function cmdIdentity() {
83
83
  await wallet.destroy();
84
84
  return ok({ identityKey });
85
85
  }
86
+ /**
87
+ * Status command: show identity and balance.
88
+ */
89
+ export async function cmdStatus() {
90
+ const BSVAgentWallet = await getBSVAgentWallet();
91
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
92
+ const identityKey = await wallet.getIdentityKey();
93
+ const total = await wallet.getBalance();
94
+ await wallet.destroy();
95
+ return ok({
96
+ identity: { identityKey, network: NETWORK },
97
+ balance: { walletBalance: total }
98
+ });
99
+ }
86
100
  /**
87
101
  * Address command: show P2PKH receive address.
88
102
  */
package/index.ts CHANGED
@@ -314,7 +314,7 @@ function getCliPath() {
314
314
  * Decentralized agent marketplace with BSV micropayments.
315
315
  */
316
316
  export const plugin = {
317
- id: "openclaw-overlay-plugin",
317
+ id: "overlay",
318
318
  name: "BSV Overlay Network",
319
319
  description: "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
320
320
 
@@ -327,7 +327,7 @@ export const plugin = {
327
327
  isInitialized = true;
328
328
 
329
329
  const entries = api.getConfig?.()?.plugins?.entries || {};
330
- const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
330
+ const entry = entries['overlay'] || entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
331
331
  const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
332
332
 
333
333
  // 1. Tool
@@ -337,8 +337,10 @@ export const plugin = {
337
337
  parameters: {
338
338
  type: "object",
339
339
  properties: {
340
- action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
340
+ action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister", "advertise-ship", "advertise-slap"] },
341
341
  service: { type: "string" },
342
+ topic: { type: "string" },
343
+ domain: { type: "string" },
342
344
  input: { type: "object" },
343
345
  identityKey: { type: "string" },
344
346
  sats: { type: "number" },
@@ -369,11 +371,31 @@ export const plugin = {
369
371
  const action = ctx.args?.[0] || 'status';
370
372
 
371
373
  if (action === 'help') {
372
- return { text: `🛰️ **Overlay Help**\n\n**Subcommands**:\n- \`status\`: Show identity and wallet balance\n- \`balance\`: Show current satoshis\n- \`onboard\`: Start discovery setup\n- \`discover <serviceId>\`: Find providers on network\n- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
374
+ return { text: `🛰️ **Overlay Help**\n\n**Subcommands**:\n- \`status\`: Show identity and wallet balance\n- \`balance\`: Show current satoshis\n- \`onboard\`: Start discovery setup\n- \`discover <serviceId>\`: Find providers on network\n- \`advertise-ship <domain> <topic>\`: Advertise a topic manager\n- \`advertise-slap <domain> <service>\`: Advertise a lookup service\n- \`pending-requests\`: See incoming service jobs\n- \`fulfill\`: Complete a request (Agent only)` };
373
375
  }
374
376
 
375
- const result = await executeOverlayAction({ action }, pluginConfig, api);
376
- return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
377
+ const params: any = { action };
378
+ if (action === 'discover') {
379
+ params.service = ctx.args[1];
380
+ } else if (action === 'advertise-ship') {
381
+ params.domain = ctx.args[1];
382
+ params.topic = ctx.args[2];
383
+ } else if (action === 'advertise-slap') {
384
+ params.domain = ctx.args[1];
385
+ params.service = ctx.args[2];
386
+ }
387
+
388
+ const result = await executeOverlayAction(params, pluginConfig, api);
389
+
390
+ if (typeof result === 'string') return { text: result };
391
+
392
+ // Formatted status response
393
+ if (action === 'status') {
394
+ const status = result as any;
395
+ return { text: `🛰️ **Overlay Status**\n\n**Identity Key**: \`${status.identity?.identityKey || 'Not setup'}\`\n**Balance**: ${status.balance?.walletBalance || 0} satoshis\n**Network**: ${status.identity?.network || 'mainnet'}` };
396
+ }
397
+
398
+ return { text: `**Overlay ${action.toUpperCase()}**\n\n${JSON.stringify(result, null, 2)}` };
377
399
  } catch (error: any) {
378
400
  return { text: `❌ Error: ${error.message}` };
379
401
  }
@@ -425,10 +447,24 @@ async function executeOverlayAction(params: any, config: any, api: any) {
425
447
  case "onboard": return await handleOnboard(params, env, cliPath);
426
448
  case "pending-requests": return await handlePendingRequests(env, cliPath);
427
449
  case "fulfill": return await handleFulfill(params, env, cliPath);
450
+ case "advertise-ship": return await handleAdvertiseSHIP(params, env, cliPath);
451
+ case "advertise-slap": return await handleAdvertiseSLAP(params, env, cliPath);
428
452
  default: throw new Error(`Unknown action: ${action}`);
429
453
  }
430
454
  }
431
455
 
456
+ async function handleAdvertiseSHIP(params: any, env: any, cliPath: string) {
457
+ const { domain, topic } = params;
458
+ const result = await execFileAsync('node', [cliPath, 'advertise-ship', domain, topic], { env });
459
+ return parseCliOutput(result.stdout).data;
460
+ }
461
+
462
+ async function handleAdvertiseSLAP(params: any, env: any, cliPath: string) {
463
+ const { domain, service } = params;
464
+ const result = await execFileAsync('node', [cliPath, 'advertise-slap', domain, service], { env });
465
+ return parseCliOutput(result.stdout).data;
466
+ }
467
+
432
468
  async function handleServiceRequest(params: any, env: any, cliPath: string, config: any, api: any) {
433
469
  const { service, identityKey: targetKey, input } = params;
434
470
  const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
@@ -1,8 +1,8 @@
1
1
  {
2
- "id": "openclaw-overlay-plugin",
2
+ "id": "overlay",
3
3
  "name": "BSV Overlay Network",
4
4
  "description": "OpenClaw Overlay — decentralized agent marketplace with BSV micropayments",
5
- "version": "0.7.67",
5
+ "version": "0.7.70",
6
6
  "skills": [
7
7
  "./SKILL.md"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-overlay-plugin",
3
- "version": "0.7.70",
3
+ "version": "0.7.71",
4
4
  "description": "Openclaw BSV Overlay — agent discovery, service marketplace, and micropayments on the BSV blockchain",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/cli-main.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import { ok, fail } from './scripts/output.js';
9
9
 
10
10
  // Wallet commands
11
- import { cmdSetup, cmdIdentity, cmdAddress } from './scripts/wallet/setup.js';
11
+ import { cmdSetup, cmdIdentity, cmdAddress, cmdStatus } from './scripts/wallet/setup.js';
12
12
  import { cmdBalance, cmdImport, cmdRefund } from './scripts/wallet/balance.js';
13
13
 
14
14
  // Overlay registration commands
@@ -19,6 +19,7 @@ import { cmdServices, cmdAdvertise, cmdRemove, cmdReadvertise } from './scripts/
19
19
 
20
20
  // Discovery commands
21
21
  import { cmdDiscover } from './scripts/overlay/discover.js';
22
+ import { cmdAdvertiseSHIP, cmdAdvertiseSLAP } from './scripts/overlay/advertisement.js';
22
23
 
23
24
  // Payment commands
24
25
  import { cmdPay, cmdVerify, cmdAccept } from './scripts/payment/commands.js';
@@ -69,7 +70,7 @@ async function main() {
69
70
  wallet: ['setup', 'identity', 'address', 'balance', 'import <txid> [vout]', 'refund <address>'],
70
71
  registration: ['register', 'unregister'],
71
72
  services: ['services', 'advertise <id> <name> <priceSats> [desc]', 'readvertise <id> [name] [priceSats] [desc]', 'remove <id>'],
72
- discovery: ['discover [--service <type>] [--agent <name>]'],
73
+ discovery: ['discover [--service <type>] [--agent <name>]', 'advertise-ship <domain> <topic>', 'advertise-slap <domain> <service>'],
73
74
  payments: ['pay <pubkey> <sats> [desc]', 'verify <beef>', 'accept <beef> <prefix> <suffix> <senderKey> [desc]'],
74
75
  messaging: ['send <key> <type> <json>', 'inbox', 'ack', 'poll', 'connect'],
75
76
  'service-requests': ['request-service <key> <serviceId> <sats> [input]', 'service-queue', 'respond-service <reqId> <key> <serviceId> <result>'],
@@ -81,6 +82,9 @@ async function main() {
81
82
  break;
82
83
 
83
84
  // Wallet
85
+ case 'status':
86
+ await cmdStatus();
87
+ break;
84
88
  case 'setup':
85
89
  await cmdSetup();
86
90
  break;
@@ -126,6 +130,12 @@ async function main() {
126
130
  case 'discover':
127
131
  await cmdDiscover(args);
128
132
  break;
133
+ case 'advertise-ship':
134
+ await cmdAdvertiseSHIP(args[0], args[1]);
135
+ break;
136
+ case 'advertise-slap':
137
+ await cmdAdvertiseSLAP(args[0], args[1]);
138
+ break;
129
139
 
130
140
  // Payments
131
141
  case 'pay':
@@ -53,8 +53,16 @@ export const TOPICS = {
53
53
  IDENTITY: 'tm_openclaw_identity',
54
54
  SERVICES: 'tm_openclaw_services',
55
55
  X_VERIFICATION: 'tm_openclaw_x_verification',
56
+ SHIP: 'tm_ship',
57
+ SLAP: 'tm_slap',
56
58
  } as const;
57
59
 
60
+ /** Default SLAP trackers */
61
+ export const DEFAULT_SLAP_TRACKERS: Record<'mainnet' | 'testnet', string[]> = {
62
+ mainnet: ['https://overlay.babbage.systems'],
63
+ testnet: ['https://testnet-users.bapp.dev'],
64
+ };
65
+
58
66
  /** Lookup services for overlay queries */
59
67
  export const LOOKUP_SERVICES = {
60
68
  AGENTS: 'ls_openclaw_agents',
@@ -0,0 +1,138 @@
1
+ /**
2
+ * SHIP and SLAP advertisement commands.
3
+ *
4
+ * SHIP: Service Health & Information Protocol (tm_ship)
5
+ * SLAP: Service Level Agreement Protocol (tm_slap)
6
+ */
7
+
8
+ import { PushDrop, Utils } from '@bsv/sdk';
9
+ import { NETWORK, WALLET_DIR, TOPICS, DEFAULT_SLAP_TRACKERS } from '../config.js';
10
+ import { BSVAgentWallet } from '../../core/wallet.js';
11
+ import { ok, fail } from '../output.js';
12
+
13
+ /**
14
+ * Advertise a SHIP record.
15
+ * Announce that you host a specific Topic Manager (tm_).
16
+ */
17
+ export async function cmdAdvertiseSHIP(domain?: string, topic?: string): Promise<never> {
18
+ if (!domain || !topic) {
19
+ return fail('Usage: advertise-ship <domain> <topic>');
20
+ }
21
+
22
+ if (!topic.startsWith('tm_')) {
23
+ return fail('Topic must start with "tm_"');
24
+ }
25
+
26
+ try {
27
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
28
+ const token = new PushDrop(wallet._setup.wallet);
29
+
30
+ // SHIP format: Payload is the domain hosting the topic
31
+ const fields = [Utils.toArray(domain, 'utf8')];
32
+ // Context is [0, topic] to identify the topic manager being advertised
33
+ const lockingScript = (await token.lock(fields, [0, topic], '1', 'self', true, true)).toHex();
34
+
35
+ const response = await wallet._setup.wallet.createAction({
36
+ description: `advertise SHIP for ${topic}`,
37
+ outputs: [{
38
+ lockingScript,
39
+ satoshis: 1,
40
+ outputDescription: 'SHIP advertisement',
41
+ basket: TOPICS.SHIP
42
+ }]
43
+ });
44
+
45
+ // Broadcast to primary overlay and SLAP trackers
46
+ const trackers = [
47
+ ...DEFAULT_SLAP_TRACKERS[NETWORK]
48
+ ];
49
+
50
+ const results = await broadcastToTrackers(response.tx as number[], [TOPICS.SHIP, topic], trackers);
51
+
52
+ return ok({
53
+ advertised: 'SHIP',
54
+ topic,
55
+ domain,
56
+ txid: response.txid,
57
+ broadcasts: results
58
+ });
59
+ } catch (err: any) {
60
+ return fail(`SHIP advertisement failed: ${err.message}`);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Advertise a SLAP record.
66
+ * Announce that you host a specific Lookup Service (ls_).
67
+ */
68
+ export async function cmdAdvertiseSLAP(domain?: string, service?: string): Promise<never> {
69
+ if (!domain || !service) {
70
+ return fail('Usage: advertise-slap <domain> <service>');
71
+ }
72
+
73
+ if (!service.startsWith('ls_')) {
74
+ return fail('Service must start with "ls_"');
75
+ }
76
+
77
+ try {
78
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
79
+ const token = new PushDrop(wallet._setup.wallet);
80
+
81
+ // SLAP format: Payload is the domain hosting the lookup service
82
+ const fields = [Utils.toArray(domain, 'utf8')];
83
+ // Context is [0, service] to identify the lookup service being advertised
84
+ const lockingScript = (await token.lock(fields, [0, service], '1', 'self', true, true)).toHex();
85
+
86
+ const response = await wallet._setup.wallet.createAction({
87
+ description: `advertise SLAP for ${service}`,
88
+ outputs: [{
89
+ lockingScript,
90
+ satoshis: 1,
91
+ outputDescription: 'SLAP advertisement',
92
+ basket: TOPICS.SLAP
93
+ }]
94
+ });
95
+
96
+ // Broadcast to primary overlay and SLAP trackers
97
+ const trackers = [
98
+ ...DEFAULT_SLAP_TRACKERS[NETWORK]
99
+ ];
100
+
101
+ const results = await broadcastToTrackers(response.tx as number[], [TOPICS.SLAP, service], trackers);
102
+
103
+ return ok({
104
+ advertised: 'SLAP',
105
+ service,
106
+ domain,
107
+ txid: response.txid,
108
+ broadcasts: results
109
+ });
110
+ } catch (err: any) {
111
+ return fail(`SLAP advertisement failed: ${err.message}`);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Helper to broadcast BEEF to multiple trackers/overlays.
117
+ */
118
+ async function broadcastToTrackers(tx: number[], topics: string[], trackers: string[]) {
119
+ const results: Record<string, any> = {};
120
+ const body = new Uint8Array(tx);
121
+
122
+ for (const url of trackers) {
123
+ try {
124
+ const resp = await fetch(`${url.replace(/\/$/, '')}/submit`, {
125
+ method: 'POST',
126
+ headers: {
127
+ 'Content-Type': 'application/octet-stream',
128
+ 'X-Topics': JSON.stringify(topics)
129
+ },
130
+ body
131
+ });
132
+ results[url] = resp.ok ? 'success' : `error: ${resp.status}`;
133
+ } catch (err: any) {
134
+ results[url] = `failed: ${err.message}`;
135
+ }
136
+ }
137
+ return results;
138
+ }
@@ -97,6 +97,22 @@ export async function cmdIdentity(): Promise<never> {
97
97
  return ok({ identityKey });
98
98
  }
99
99
 
100
+ /**
101
+ * Status command: show identity and balance.
102
+ */
103
+ export async function cmdStatus(): Promise<never> {
104
+ const BSVAgentWallet = await getBSVAgentWallet();
105
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
106
+ const identityKey = await wallet.getIdentityKey();
107
+ const total = await wallet.getBalance();
108
+ await wallet.destroy();
109
+
110
+ return ok({
111
+ identity: { identityKey, network: NETWORK },
112
+ balance: { walletBalance: total }
113
+ });
114
+ }
115
+
100
116
  /**
101
117
  * Address command: show P2PKH receive address.
102
118
  */