openclaw-overlay-plugin 0.8.15 → 0.8.17

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.
Files changed (191) hide show
  1. package/dist/index.js +3037 -328
  2. package/dist/index.js.map +7 -0
  3. package/dist/src/cli.js +3820 -11
  4. package/dist/src/cli.js.map +7 -0
  5. package/index.ts +32 -27
  6. package/openclaw.plugin.json +1 -1
  7. package/package.json +8 -6
  8. package/src/scripts/messaging/handlers.ts +1 -1
  9. package/src/scripts/overlay/advertisement.ts +1 -1
  10. package/src/scripts/overlay/registration.ts +1 -1
  11. package/src/scripts/overlay/services.ts +1 -1
  12. package/src/scripts/overlay/transaction.ts +1 -1
  13. package/src/scripts/payment/build.ts +1 -1
  14. package/src/scripts/payment/commands.ts +1 -1
  15. package/src/scripts/wallet/balance.ts +1 -1
  16. package/src/scripts/wallet/setup.ts +1 -1
  17. package/src/scripts/x-verification/commands.ts +5 -0
  18. package/src/test/identity-consistency.test.ts +1 -1
  19. package/src/test/wallet.test.ts +1 -2
  20. package/dist/index.d.ts +0 -9
  21. package/dist/src/cli-main.d.ts +0 -7
  22. package/dist/src/cli-main.js +0 -202
  23. package/dist/src/cli.d.ts +0 -8
  24. package/dist/src/compatibility.test.d.ts +0 -4
  25. package/dist/src/compatibility.test.js +0 -41
  26. package/dist/src/core/config.d.ts +0 -11
  27. package/dist/src/core/config.js +0 -15
  28. package/dist/src/core/index.d.ts +0 -25
  29. package/dist/src/core/index.js +0 -26
  30. package/dist/src/core/payment.d.ts +0 -16
  31. package/dist/src/core/payment.js +0 -94
  32. package/dist/src/core/types.d.ts +0 -94
  33. package/dist/src/core/types.js +0 -4
  34. package/dist/src/core/verify.d.ts +0 -28
  35. package/dist/src/core/verify.js +0 -104
  36. package/dist/src/core/wallet.d.ts +0 -105
  37. package/dist/src/core/wallet.js +0 -256
  38. package/dist/src/scripts/baemail/commands.d.ts +0 -35
  39. package/dist/src/scripts/baemail/commands.js +0 -282
  40. package/dist/src/scripts/baemail/handler.d.ts +0 -36
  41. package/dist/src/scripts/baemail/handler.js +0 -284
  42. package/dist/src/scripts/baemail/index.d.ts +0 -5
  43. package/dist/src/scripts/baemail/index.js +0 -5
  44. package/dist/src/scripts/config.d.ts +0 -52
  45. package/dist/src/scripts/config.js +0 -75
  46. package/dist/src/scripts/index.d.ts +0 -7
  47. package/dist/src/scripts/index.js +0 -7
  48. package/dist/src/scripts/messaging/connect.d.ts +0 -8
  49. package/dist/src/scripts/messaging/connect.js +0 -168
  50. package/dist/src/scripts/messaging/handlers.d.ts +0 -21
  51. package/dist/src/scripts/messaging/handlers.js +0 -334
  52. package/dist/src/scripts/messaging/inbox.d.ts +0 -11
  53. package/dist/src/scripts/messaging/inbox.js +0 -51
  54. package/dist/src/scripts/messaging/index.d.ts +0 -8
  55. package/dist/src/scripts/messaging/index.js +0 -8
  56. package/dist/src/scripts/messaging/poll.d.ts +0 -7
  57. package/dist/src/scripts/messaging/poll.js +0 -52
  58. package/dist/src/scripts/messaging/send.d.ts +0 -7
  59. package/dist/src/scripts/messaging/send.js +0 -43
  60. package/dist/src/scripts/output.d.ts +0 -13
  61. package/dist/src/scripts/output.js +0 -28
  62. package/dist/src/scripts/overlay/advertisement.d.ts +0 -16
  63. package/dist/src/scripts/overlay/advertisement.js +0 -122
  64. package/dist/src/scripts/overlay/discover.d.ts +0 -7
  65. package/dist/src/scripts/overlay/discover.js +0 -74
  66. package/dist/src/scripts/overlay/index.d.ts +0 -7
  67. package/dist/src/scripts/overlay/index.js +0 -7
  68. package/dist/src/scripts/overlay/registration.d.ts +0 -19
  69. package/dist/src/scripts/overlay/registration.js +0 -176
  70. package/dist/src/scripts/overlay/services.d.ts +0 -29
  71. package/dist/src/scripts/overlay/services.js +0 -167
  72. package/dist/src/scripts/overlay/transaction.d.ts +0 -42
  73. package/dist/src/scripts/overlay/transaction.js +0 -103
  74. package/dist/src/scripts/payment/build.d.ts +0 -24
  75. package/dist/src/scripts/payment/build.js +0 -54
  76. package/dist/src/scripts/payment/commands.d.ts +0 -15
  77. package/dist/src/scripts/payment/commands.js +0 -73
  78. package/dist/src/scripts/payment/index.d.ts +0 -6
  79. package/dist/src/scripts/payment/index.js +0 -6
  80. package/dist/src/scripts/payment/types.d.ts +0 -56
  81. package/dist/src/scripts/payment/types.js +0 -4
  82. package/dist/src/scripts/services/index.d.ts +0 -6
  83. package/dist/src/scripts/services/index.js +0 -6
  84. package/dist/src/scripts/services/queue.d.ts +0 -11
  85. package/dist/src/scripts/services/queue.js +0 -28
  86. package/dist/src/scripts/services/request.d.ts +0 -7
  87. package/dist/src/scripts/services/request.js +0 -82
  88. package/dist/src/scripts/services/respond.d.ts +0 -11
  89. package/dist/src/scripts/services/respond.js +0 -132
  90. package/dist/src/scripts/types.d.ts +0 -107
  91. package/dist/src/scripts/types.js +0 -4
  92. package/dist/src/scripts/utils/index.d.ts +0 -6
  93. package/dist/src/scripts/utils/index.js +0 -6
  94. package/dist/src/scripts/utils/merkle.d.ts +0 -12
  95. package/dist/src/scripts/utils/merkle.js +0 -47
  96. package/dist/src/scripts/utils/storage.d.ts +0 -66
  97. package/dist/src/scripts/utils/storage.js +0 -211
  98. package/dist/src/scripts/utils/woc.d.ts +0 -26
  99. package/dist/src/scripts/utils/woc.js +0 -91
  100. package/dist/src/scripts/wallet/balance.d.ts +0 -22
  101. package/dist/src/scripts/wallet/balance.js +0 -240
  102. package/dist/src/scripts/wallet/identity.d.ts +0 -71
  103. package/dist/src/scripts/wallet/identity.js +0 -152
  104. package/dist/src/scripts/wallet/index.d.ts +0 -6
  105. package/dist/src/scripts/wallet/index.js +0 -6
  106. package/dist/src/scripts/wallet/setup.d.ts +0 -19
  107. package/dist/src/scripts/wallet/setup.js +0 -119
  108. package/dist/src/scripts/x-verification/commands.d.ts +0 -27
  109. package/dist/src/scripts/x-verification/commands.js +0 -222
  110. package/dist/src/scripts/x-verification/index.d.ts +0 -4
  111. package/dist/src/scripts/x-verification/index.js +0 -4
  112. package/dist/src/services/built-in/api-proxy/index.d.ts +0 -6
  113. package/dist/src/services/built-in/api-proxy/index.js +0 -23
  114. package/dist/src/services/built-in/code-develop/index.d.ts +0 -6
  115. package/dist/src/services/built-in/code-develop/index.js +0 -23
  116. package/dist/src/services/built-in/code-review/index.d.ts +0 -10
  117. package/dist/src/services/built-in/code-review/index.js +0 -51
  118. package/dist/src/services/built-in/image-analysis/index.d.ts +0 -6
  119. package/dist/src/services/built-in/image-analysis/index.js +0 -33
  120. package/dist/src/services/built-in/memory-store/index.d.ts +0 -6
  121. package/dist/src/services/built-in/memory-store/index.js +0 -22
  122. package/dist/src/services/built-in/roulette/index.d.ts +0 -6
  123. package/dist/src/services/built-in/roulette/index.js +0 -27
  124. package/dist/src/services/built-in/summarize/index.d.ts +0 -6
  125. package/dist/src/services/built-in/summarize/index.js +0 -21
  126. package/dist/src/services/built-in/tell-joke/handler.d.ts +0 -7
  127. package/dist/src/services/built-in/tell-joke/handler.js +0 -122
  128. package/dist/src/services/built-in/tell-joke/index.d.ts +0 -9
  129. package/dist/src/services/built-in/tell-joke/index.js +0 -31
  130. package/dist/src/services/built-in/translate/index.d.ts +0 -6
  131. package/dist/src/services/built-in/translate/index.js +0 -21
  132. package/dist/src/services/built-in/web-research/index.d.ts +0 -9
  133. package/dist/src/services/built-in/web-research/index.js +0 -51
  134. package/dist/src/services/index.d.ts +0 -13
  135. package/dist/src/services/index.js +0 -14
  136. package/dist/src/services/loader.d.ts +0 -77
  137. package/dist/src/services/loader.js +0 -292
  138. package/dist/src/services/manager.d.ts +0 -86
  139. package/dist/src/services/manager.js +0 -255
  140. package/dist/src/services/registry.d.ts +0 -98
  141. package/dist/src/services/registry.js +0 -204
  142. package/dist/src/services/types.d.ts +0 -230
  143. package/dist/src/services/types.js +0 -30
  144. package/dist/src/test/cli.test.d.ts +0 -7
  145. package/dist/src/test/cli.test.js +0 -330
  146. package/dist/src/test/comprehensive-overlay.test.d.ts +0 -13
  147. package/dist/src/test/comprehensive-overlay.test.js +0 -593
  148. package/dist/src/test/identity-consistency.test.d.ts +0 -6
  149. package/dist/src/test/identity-consistency.test.js +0 -60
  150. package/dist/src/test/key-derivation.test.d.ts +0 -12
  151. package/dist/src/test/key-derivation.test.js +0 -86
  152. package/dist/src/test/network-address.test.d.ts +0 -9
  153. package/dist/src/test/network-address.test.js +0 -37
  154. package/dist/src/test/overlay-submit.test.d.ts +0 -10
  155. package/dist/src/test/overlay-submit.test.js +0 -460
  156. package/dist/src/test/request-response-flow.test.d.ts +0 -5
  157. package/dist/src/test/request-response-flow.test.js +0 -210
  158. package/dist/src/test/service-system.test.d.ts +0 -5
  159. package/dist/src/test/service-system.test.js +0 -190
  160. package/dist/src/test/taskflow.test.d.ts +0 -7
  161. package/dist/src/test/taskflow.test.js +0 -82
  162. package/dist/src/test/utils/server-logic.d.ts +0 -98
  163. package/dist/src/test/utils/server-logic.js +0 -286
  164. package/dist/src/test/wallet.test.d.ts +0 -7
  165. package/dist/src/test/wallet.test.js +0 -146
  166. package/src/core/README.md +0 -246
  167. package/src/core/config.d.ts +0 -12
  168. package/src/core/config.d.ts.map +0 -1
  169. package/src/core/config.js +0 -14
  170. package/src/core/config.js.map +0 -1
  171. package/src/core/config.ts +0 -22
  172. package/src/core/index.ts +0 -42
  173. package/src/core/payment.d.ts +0 -17
  174. package/src/core/payment.d.ts.map +0 -1
  175. package/src/core/payment.js +0 -95
  176. package/src/core/payment.js.map +0 -1
  177. package/src/core/payment.ts +0 -111
  178. package/src/core/types.d.ts +0 -95
  179. package/src/core/types.d.ts.map +0 -1
  180. package/src/core/types.js +0 -5
  181. package/src/core/types.js.map +0 -1
  182. package/src/core/types.ts +0 -102
  183. package/src/core/verify.d.ts +0 -29
  184. package/src/core/verify.d.ts.map +0 -1
  185. package/src/core/verify.js +0 -105
  186. package/src/core/verify.js.map +0 -1
  187. package/src/core/verify.ts +0 -119
  188. package/src/core/wallet.d.ts +0 -100
  189. package/src/core/wallet.d.ts.map +0 -1
  190. package/src/core/wallet.js.map +0 -1
  191. package/src/core/wallet.ts +0 -323
package/dist/index.js CHANGED
@@ -1,365 +1,3074 @@
1
- import path from 'node:path';
2
- import os from 'node:os';
3
- import fs from 'node:fs';
4
- import { initializeServiceSystem, serviceManager } from './src/services/index.js';
5
- // Direct imports of command logic
6
- import { cmdStatus, cmdSetup, cmdAddress, cmdIdentity } from './src/scripts/wallet/setup.js';
7
- import { cmdBalance, cmdImport } from './src/scripts/wallet/balance.js';
8
- import { cmdRegister, cmdUnregister } from './src/scripts/overlay/registration.js';
9
- import { cmdDiscover } from './src/scripts/overlay/discover.js';
10
- import { cmdRequestService } from './src/scripts/services/request.js';
11
- import { cmdServiceQueue } from './src/scripts/services/queue.js';
12
- import { cmdRespondService } from './src/scripts/services/respond.js';
13
- import { cmdConnect } from './src/scripts/messaging/connect.js';
14
- import { setNoExit } from './src/scripts/output.js';
15
- import debug from 'debug';
16
- const log = debug('openclaw:plugin:overlay');
17
- let isInitialized = false;
18
- // Track background service state
19
- let serviceRunning = false;
20
- let abortController = null;
21
- // Auto-import tracking
22
- let autoImportInterval = null;
23
- let knownTxids = new Set();
24
- // Track woken service requests to prevent duplicate processing
25
- let wokenRequests = new Set();
26
- let requestCleanupInterval = null;
27
- // Budget tracking
28
- const BUDGET_FILE = 'daily-spending.json';
29
- function getBudgetPath(walletDir) {
30
- return path.join(walletDir, BUDGET_FILE);
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/scripts/config.ts
12
+ var config_exports = {};
13
+ __export(config_exports, {
14
+ AGENT_DESCRIPTION: () => AGENT_DESCRIPTION,
15
+ AGENT_NAME: () => AGENT_NAME,
16
+ DEFAULT_SLAP_TRACKERS: () => DEFAULT_SLAP_TRACKERS,
17
+ LOOKUP_SERVICES: () => LOOKUP_SERVICES,
18
+ NETWORK: () => NETWORK,
19
+ OVERLAY_STATE_DIR: () => OVERLAY_STATE_DIR,
20
+ OVERLAY_URL: () => OVERLAY_URL,
21
+ PATHS: () => PATHS,
22
+ PROTOCOL_ID: () => PROTOCOL_ID,
23
+ TOPICS: () => TOPICS,
24
+ WALLET_DIR: () => WALLET_DIR,
25
+ WOC_API_KEY: () => WOC_API_KEY
26
+ });
27
+ import path2 from "node:path";
28
+ import os from "node:os";
29
+ import fs2 from "node:fs";
30
+ var overlayEnvPath, WALLET_DIR, NETWORK, OVERLAY_URL, AGENT_NAME, AGENT_DESCRIPTION, WOC_API_KEY, OVERLAY_STATE_DIR, PROTOCOL_ID, TOPICS, DEFAULT_SLAP_TRACKERS, LOOKUP_SERVICES, PATHS;
31
+ var init_config = __esm({
32
+ "src/scripts/config.ts"() {
33
+ "use strict";
34
+ overlayEnvPath = path2.join(os.homedir(), ".openclaw", "openclaw-overlay", ".env");
35
+ try {
36
+ if (fs2.existsSync(overlayEnvPath)) {
37
+ for (const line of fs2.readFileSync(overlayEnvPath, "utf-8").split("\n")) {
38
+ const match = line.match(/^([A-Z_]+)=(.+)$/);
39
+ if (match && !process["env"][match[1]]) {
40
+ process["env"][match[1]] = match[2]?.trim();
41
+ }
42
+ }
43
+ }
44
+ } catch {
45
+ }
46
+ WALLET_DIR = process["env"].BSV_WALLET_DIR || path2.join(os.homedir(), ".openclaw", "bsv-wallet");
47
+ NETWORK = process["env"].BSV_NETWORK || "mainnet";
48
+ OVERLAY_URL = process["env"].OVERLAY_URL || "https://clawoverlay.com";
49
+ AGENT_NAME = process["env"].AGENT_NAME || "openclaw-agent";
50
+ AGENT_DESCRIPTION = process["env"].AGENT_DESCRIPTION || `AI agent on the OpenClaw Overlay Network. Offers services for BSV micropayments.`;
51
+ WOC_API_KEY = process["env"].WOC_API_KEY || "";
52
+ OVERLAY_STATE_DIR = path2.join(os.homedir(), ".openclaw", "openclaw-overlay");
53
+ PROTOCOL_ID = "clawdbot-overlay-v1";
54
+ TOPICS = {
55
+ IDENTITY: "tm_clawdbot_identity",
56
+ SERVICES: "tm_clawdbot_services",
57
+ X_VERIFICATION: "tm_clawdbot_x_verification",
58
+ SHIP: "tm_ship",
59
+ SLAP: "tm_slap"
60
+ };
61
+ DEFAULT_SLAP_TRACKERS = {
62
+ mainnet: ["https://overlay.babbage.systems"],
63
+ testnet: ["https://testnet-users.bapp.dev"]
64
+ };
65
+ LOOKUP_SERVICES = {
66
+ AGENTS: "ls_clawdbot_agents",
67
+ SERVICES: "ls_clawdbot_services",
68
+ X_VERIFICATIONS: "ls_clawdbot_x_verifications"
69
+ };
70
+ PATHS = {
71
+ walletIdentity: path2.join(WALLET_DIR, "wallet-identity.json"),
72
+ registration: path2.join(OVERLAY_STATE_DIR, "registration.json"),
73
+ services: path2.join(OVERLAY_STATE_DIR, "services.json"),
74
+ latestChange: path2.join(OVERLAY_STATE_DIR, "latest-change.json"),
75
+ receivedPayments: path2.join(OVERLAY_STATE_DIR, "received-payments.jsonl"),
76
+ researchQueue: path2.join(OVERLAY_STATE_DIR, "research-queue.jsonl"),
77
+ serviceQueue: path2.join(OVERLAY_STATE_DIR, "service-queue.jsonl"),
78
+ notifications: path2.join(OVERLAY_STATE_DIR, "notifications.jsonl"),
79
+ xVerifications: path2.join(OVERLAY_STATE_DIR, "x-verifications.json"),
80
+ pendingXVerification: path2.join(OVERLAY_STATE_DIR, "pending-x-verification.json"),
81
+ xEngagementQueue: path2.join(OVERLAY_STATE_DIR, "x-engagement-queue.jsonl"),
82
+ memoryStore: path2.join(WALLET_DIR, "memory-store.json"),
83
+ baemailConfig: path2.join(OVERLAY_STATE_DIR, "baemail-config.json"),
84
+ baemailLog: path2.join(OVERLAY_STATE_DIR, "baemail-log.jsonl")
85
+ };
86
+ }
87
+ });
88
+
89
+ // src/scripts/output.ts
90
+ function setNoExit(value) {
91
+ noExitFlag = value;
31
92
  }
32
- function loadDailySpending(walletDir) {
33
- const today = new Date().toISOString().slice(0, 10);
34
- const budgetPath = getBudgetPath(walletDir);
93
+ function ok(data) {
94
+ if (noExitFlag) return { success: true, data };
95
+ console.log(JSON.stringify({ success: true, data }));
96
+ process.exit(0);
97
+ }
98
+ function fail(error) {
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ if (noExitFlag) return { success: false, error: message };
101
+ console.log(JSON.stringify({ success: false, error: message }));
102
+ process.exit(1);
103
+ }
104
+ var noExitFlag;
105
+ var init_output = __esm({
106
+ "src/scripts/output.ts"() {
107
+ "use strict";
108
+ noExitFlag = false;
109
+ }
110
+ });
111
+
112
+ // ../plugin-core/dist/config.js
113
+ function toChain(network) {
114
+ if (network === "testnet")
115
+ return "test";
116
+ return "main";
117
+ }
118
+ var DEFAULT_TAAL_API_KEYS, DEFAULT_DB_NAME;
119
+ var init_config2 = __esm({
120
+ "../plugin-core/dist/config.js"() {
121
+ "use strict";
122
+ DEFAULT_TAAL_API_KEYS = {
123
+ main: "mainnet_9596de07e92300c6287e4393594ae39c",
124
+ test: "testnet_0e6cf72133b43ea2d7861da2a38684e3"
125
+ };
126
+ DEFAULT_DB_NAME = "a2a_agent_wallet";
127
+ }
128
+ });
129
+
130
+ // ../plugin-core/dist/payment.js
131
+ import { Beef, Utils as Utils2 } from "@bsv/sdk";
132
+ import { randomBytesBase64, ScriptTemplateBRC29 } from "@bsv/wallet-toolbox";
133
+ async function buildPayment(setup, params) {
134
+ const { to, satoshis, description } = params;
135
+ const desc = normalizeDescription(description ?? "agent payment");
136
+ const derivationPrefix = randomBytesBase64(8);
137
+ const derivationSuffix = randomBytesBase64(8);
138
+ const keyDeriver = setup.keyDeriver;
139
+ const t = new ScriptTemplateBRC29({
140
+ derivationPrefix,
141
+ derivationSuffix,
142
+ keyDeriver
143
+ });
144
+ let recipientPubKey;
145
+ if (/^0[23][0-9a-fA-F]{64}$/.test(to)) {
146
+ recipientPubKey = to;
147
+ } else {
148
+ throw new Error("PaymentParams.to must be a compressed public key (hex) for BRC-29 payments. Raw BSV addresses are not supported \u2014 the recipient must share their identity key.");
149
+ }
150
+ const lockingScript = t.lock(setup.rootKey.toString(), recipientPubKey);
151
+ const label = "a2a-payment";
152
+ const car = await setup.wallet.createAction({
153
+ outputs: [
154
+ {
155
+ lockingScript: lockingScript.toHex(),
156
+ satoshis,
157
+ outputDescription: desc,
158
+ tags: ["relinquish"],
159
+ customInstructions: JSON.stringify({
160
+ derivationPrefix,
161
+ derivationSuffix,
162
+ type: "BRC29"
163
+ })
164
+ }
165
+ ],
166
+ options: {
167
+ randomizeOutputs: false,
168
+ acceptDelayedBroadcast: false
169
+ },
170
+ labels: [label],
171
+ description: desc
172
+ });
173
+ if (!car.tx) {
174
+ throw new Error("createAction did not return a transaction. Check wallet funding.");
175
+ }
176
+ const beef = Beef.fromBinary(car.tx);
177
+ const lastTx = beef.txs[beef.txs.length - 1];
178
+ const txid = lastTx.txid;
179
+ const atomicBinary = beef.toBinaryAtomic(txid);
180
+ const beefBase64 = Utils2.toBase64(atomicBinary);
181
+ return {
182
+ beef: beefBase64,
183
+ txid,
184
+ satoshis,
185
+ derivationPrefix,
186
+ derivationSuffix,
187
+ senderIdentityKey: setup.identityKey
188
+ };
189
+ }
190
+ function normalizeDescription(desc) {
191
+ if (desc.length < 5)
192
+ return desc.padEnd(5, " ");
193
+ if (desc.length > 50)
194
+ return desc.slice(0, 50);
195
+ return desc;
196
+ }
197
+ var init_payment = __esm({
198
+ "../plugin-core/dist/payment.js"() {
199
+ "use strict";
200
+ }
201
+ });
202
+
203
+ // ../plugin-core/dist/verify.js
204
+ import { Beef as Beef2, Utils as Utils3 } from "@bsv/sdk";
205
+ async function verifyPayment(params) {
206
+ const errors = [];
207
+ let txid = "";
208
+ let outputCount = 0;
209
+ try {
210
+ const binary = Utils3.toArray(params.beef, "base64");
211
+ const beef = Beef2.fromBinary(binary);
212
+ if (beef.txs.length === 0) {
213
+ errors.push("BEEF contains no transactions");
214
+ } else {
215
+ const lastTx = beef.txs[beef.txs.length - 1];
216
+ txid = lastTx.txid;
217
+ const tx = beef.findAtomicTransaction(txid);
218
+ if (tx) {
219
+ outputCount = tx.outputs.length;
220
+ try {
221
+ await tx.verify();
222
+ } catch (err) {
223
+ const message = err instanceof Error ? err.message : String(err);
224
+ errors.push(`SPV verification failed: ${message}`);
225
+ }
226
+ } else {
227
+ errors.push("Could not find atomic transaction in BEEF");
228
+ }
229
+ }
230
+ } catch (err) {
231
+ const message = err instanceof Error ? err.message : String(err);
232
+ errors.push(`BEEF parse error: ${message}`);
233
+ }
234
+ if (params.expectedSender) {
235
+ if (!/^0[23][0-9a-fA-F]{64}$/.test(params.expectedSender)) {
236
+ errors.push("expectedSender is not a valid compressed public key");
237
+ }
238
+ }
239
+ return {
240
+ valid: errors.length === 0,
241
+ txid,
242
+ outputCount,
243
+ errors
244
+ };
245
+ }
246
+ async function acceptPayment(setup, params) {
247
+ const desc = normalizeDescription2(params.description ?? "received payment");
248
+ const vout = params.vout ?? 0;
249
+ const binary = Utils3.toArray(params.beef, "base64");
250
+ const args = {
251
+ tx: binary,
252
+ outputs: [
253
+ {
254
+ outputIndex: vout,
255
+ protocol: "wallet payment",
256
+ paymentRemittance: {
257
+ derivationPrefix: params.derivationPrefix,
258
+ derivationSuffix: params.derivationSuffix,
259
+ senderIdentityKey: params.senderIdentityKey
260
+ }
261
+ }
262
+ ],
263
+ description: desc
264
+ };
265
+ const result = await setup.wallet.internalizeAction(args);
266
+ return {
267
+ accepted: result.accepted
268
+ };
269
+ }
270
+ function normalizeDescription2(desc) {
271
+ if (desc.length < 5)
272
+ return desc.padEnd(5, " ");
273
+ if (desc.length > 50)
274
+ return desc.slice(0, 50);
275
+ return desc;
276
+ }
277
+ var init_verify = __esm({
278
+ "../plugin-core/dist/verify.js"() {
279
+ "use strict";
280
+ }
281
+ });
282
+
283
+ // ../plugin-core/dist/wallet.js
284
+ import { PrivateKey, CachedKeyDeriver as CachedKeyDeriver2 } from "@bsv/sdk";
285
+ import { Wallet, WalletStorageManager, Services, Monitor, StorageKnex, randomBytesHex, ChaintracksServiceClient } from "@bsv/wallet-toolbox";
286
+ import knexLib from "knex";
287
+ import * as path3 from "node:path";
288
+ import * as fs4 from "node:fs";
289
+ import debug from "debug";
290
+ var log, IDENTITY_FILE, BSVAgentWallet;
291
+ var init_wallet = __esm({
292
+ "../plugin-core/dist/wallet.js"() {
293
+ "use strict";
294
+ init_config2();
295
+ init_payment();
296
+ init_verify();
297
+ log = debug("openclaw:plugin:overlay:wallet");
298
+ IDENTITY_FILE = "wallet-identity.json";
299
+ BSVAgentWallet = class _BSVAgentWallet {
300
+ /** @internal — exposed for advanced operations (e.g. direct internalizeAction) */
301
+ _setup;
302
+ constructor(setup) {
303
+ this._setup = setup;
304
+ }
305
+ // ---------------------------------------------------------------------------
306
+ // Factory methods
307
+ // ---------------------------------------------------------------------------
308
+ /**
309
+ * Create a new agent wallet. Generates a fresh root key and persists it.
310
+ * The SQLite database and identity file are written to `config.storageDir`.
311
+ */
312
+ static async create(config) {
313
+ log("Creating new wallet in: %s", config.storageDir);
314
+ const rootKeyHex = config.rootKeyHex ?? PrivateKey.fromRandom().toHex();
315
+ const rootKey = PrivateKey.fromHex(rootKeyHex);
316
+ const identityKey = rootKey.toPublicKey().toString();
317
+ fs4.mkdirSync(config.storageDir, { recursive: true });
318
+ const identity = {
319
+ rootKeyHex,
320
+ identityKey,
321
+ network: config.network
322
+ };
323
+ const identityPath = path3.join(config.storageDir, IDENTITY_FILE);
324
+ fs4.writeFileSync(identityPath, JSON.stringify(identity, null, 2), "utf-8");
325
+ const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
326
+ return new _BSVAgentWallet(setup);
327
+ }
328
+ /**
329
+ * Load an existing agent wallet from its storage directory.
330
+ * Reads the persisted identity file and re-initializes the wallet.
331
+ */
332
+ static async load(config) {
333
+ log("Loading wallet from: %s", config.storageDir);
334
+ const identityPath = path3.join(config.storageDir, IDENTITY_FILE);
335
+ if (!fs4.existsSync(identityPath)) {
336
+ if (config.createIfMissing === false) {
337
+ log("Wallet not found and createIfMissing is false");
338
+ throw new Error(`No wallet found in ${config.storageDir}`);
339
+ }
340
+ return this.create(config);
341
+ }
342
+ const identity = JSON.parse(fs4.readFileSync(identityPath, "utf-8"));
343
+ const rootKeyHex = config.rootKeyHex ?? identity.rootKeyHex;
344
+ const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
345
+ return new _BSVAgentWallet(setup);
346
+ }
347
+ // ---------------------------------------------------------------------------
348
+ // Wallet lifecycle
349
+ // ---------------------------------------------------------------------------
350
+ /**
351
+ * Get this wallet's public identity key (compressed hex, 33 bytes).
352
+ * This is the key other agents use to send payments to you.
353
+ */
354
+ async getIdentityKey() {
355
+ return this._setup.identityKey;
356
+ }
357
+ /**
358
+ * Get the wallet's current receive address for the active network.
359
+ */
360
+ async getAddress() {
361
+ const network = this._setup.network || "mainnet";
362
+ return this._setup.rootKey.toPublicKey().toAddress(network);
363
+ }
364
+ /**
365
+ * Get the wallet's current balance in satoshis.
366
+ *
367
+ * Uses the BRC-100 wallet's balance method which sums spendable outputs
368
+ * in the default basket.
369
+ */
370
+ async getBalance() {
371
+ return await this._setup.wallet.balance();
372
+ }
373
+ /**
374
+ * Cleanly shut down the wallet, releasing database connections and
375
+ * stopping the background monitor.
376
+ */
377
+ async destroy() {
378
+ if (this._setup.monitor) {
379
+ await this._setup.monitor.destroy();
380
+ }
381
+ if (this._setup.wallet) {
382
+ await this._setup.wallet.destroy();
383
+ }
384
+ await this._setup.storage.destroy();
385
+ }
386
+ // ---------------------------------------------------------------------------
387
+ // Payment creation (sender/payer side)
388
+ // ---------------------------------------------------------------------------
389
+ /**
390
+ * Build a BRC-29 payment to another agent.
391
+ *
392
+ * The transaction is created with `noSend: true` — the sender does NOT
393
+ * broadcast it. Instead, the Atomic BEEF and derivation metadata are
394
+ * returned so they can be transmitted to the recipient, who will
395
+ * verify and internalize (broadcast) the payment.
396
+ *
397
+ * @param params.to — Recipient's compressed public key (hex).
398
+ * @param params.satoshis — Amount in satoshis.
399
+ * @param params.description — Optional human-readable note.
400
+ */
401
+ async createPayment(params) {
402
+ return buildPayment(this._setup, params);
403
+ }
404
+ // ---------------------------------------------------------------------------
405
+ // Payment verification & acceptance (receiver/merchant side)
406
+ // ---------------------------------------------------------------------------
407
+ /**
408
+ * Verify an incoming Atomic BEEF payment.
409
+ *
410
+ * This performs structural validation and SPV verification via tx.verify().
411
+ */
412
+ async verifyPayment(params) {
413
+ return await verifyPayment(params);
414
+ }
415
+ /**
416
+ * Accept (internalize) a verified payment into this wallet.
417
+ *
418
+ * Uses the BRC-29 wallet payment protocol to derive the correct key
419
+ * and claim the output. This triggers SPV verification and, if the
420
+ * transaction hasn't been broadcast yet, broadcasts it.
421
+ */
422
+ async acceptPayment(params) {
423
+ return acceptPayment(this._setup, params);
424
+ }
425
+ // ---------------------------------------------------------------------------
426
+ // Access to underlying toolbox objects (for advanced use)
427
+ // ---------------------------------------------------------------------------
428
+ /** Get the underlying wallet-toolbox SetupWallet for advanced operations. */
429
+ getSetup() {
430
+ return this._setup;
431
+ }
432
+ // ---------------------------------------------------------------------------
433
+ // Private helpers
434
+ // ---------------------------------------------------------------------------
435
+ /**
436
+ * Internal: manually construct a BRC-100 wallet backed by SQLite.
437
+ *
438
+ * We build this by hand instead of using Setup.createWalletSQLite because
439
+ * the toolbox has a bug where its internal randomBytesHex is a stub.
440
+ * We use the same components but wire them up correctly.
441
+ */
442
+ static async buildSetup(config, rootKeyHex) {
443
+ const chain = toChain(config.network);
444
+ log("Building setup for chain: %s (network: %s)", chain, config.network);
445
+ const taalApiKey = config.taalApiKey ?? DEFAULT_TAAL_API_KEYS[chain];
446
+ const rootKey = PrivateKey.fromHex(rootKeyHex);
447
+ const identityKey = rootKey.toPublicKey().toString();
448
+ const keyDeriver = new CachedKeyDeriver2(rootKey);
449
+ const storage = new WalletStorageManager(identityKey);
450
+ const serviceOptions = Services.createDefaultOptions(chain);
451
+ const chaintracksUrl = process["env"].BSV_CHAINTRACKS_URL || "https://chaintracks-us-1.bsvb.tech";
452
+ const arcUrl = process["env"].BSV_ARC_URL;
453
+ const isTestMode = config.enableMonitor === false;
454
+ if (!isTestMode) {
455
+ serviceOptions.chaintracks = new ChaintracksServiceClient(chain, chaintracksUrl);
456
+ if (arcUrl) {
457
+ serviceOptions.arcUrl = arcUrl;
458
+ }
459
+ }
460
+ serviceOptions.taalApiKey = taalApiKey;
461
+ const services = new Services(serviceOptions);
462
+ const monopts = Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
463
+ const monitor = new Monitor(monopts);
464
+ if (!isTestMode) {
465
+ monitor.addDefaultTasks();
466
+ } else {
467
+ monitor.tasks = [];
468
+ }
469
+ const wallet = isTestMode ? void 0 : new Wallet({ chain, keyDeriver, storage, services, monitor });
470
+ const filePath = path3.join(config.storageDir, `${DEFAULT_DB_NAME}.sqlite`);
471
+ const knex = knexLib({
472
+ client: "sqlite3",
473
+ connection: { filename: filePath },
474
+ useNullAsDefault: true
475
+ });
476
+ const feeModelValue = config.feeModel ?? (process["env"].BSV_FEE_MODEL ? parseInt(process["env"].BSV_FEE_MODEL, 10) : 100);
477
+ const activeStorage = new StorageKnex({
478
+ chain,
479
+ knex,
480
+ commissionSatoshis: 0,
481
+ commissionPubKeyHex: void 0,
482
+ feeModel: { model: "sat/kb", value: feeModelValue }
483
+ });
484
+ await activeStorage.migrate(DEFAULT_DB_NAME, randomBytesHex(33));
485
+ await activeStorage.makeAvailable();
486
+ await storage.addWalletStorageProvider(activeStorage);
487
+ await activeStorage.findOrInsertUser(identityKey);
488
+ return {
489
+ rootKey,
490
+ identityKey,
491
+ keyDeriver,
492
+ chain,
493
+ network: config.network,
494
+ storage,
495
+ services: isTestMode ? void 0 : services,
496
+ monitor: isTestMode ? void 0 : monitor,
497
+ wallet
498
+ };
499
+ }
500
+ };
501
+ }
502
+ });
503
+
504
+ // ../plugin-core/dist/index.js
505
+ var init_dist = __esm({
506
+ "../plugin-core/dist/index.js"() {
507
+ "use strict";
508
+ init_wallet();
509
+ init_config2();
510
+ init_payment();
511
+ init_verify();
512
+ }
513
+ });
514
+
515
+ // src/scripts/utils/storage.ts
516
+ import fs7 from "node:fs";
517
+ function ensureStateDir() {
518
+ fs7.mkdirSync(OVERLAY_STATE_DIR, { recursive: true });
519
+ }
520
+ function loadRegistration() {
521
+ try {
522
+ if (fs7.existsSync(PATHS.registration)) {
523
+ return JSON.parse(fs7.readFileSync(PATHS.registration, "utf-8"));
524
+ }
525
+ } catch {
526
+ }
527
+ return null;
528
+ }
529
+ function saveRegistration(data) {
530
+ ensureStateDir();
531
+ fs7.writeFileSync(PATHS.registration, JSON.stringify(data, null, 2), "utf-8");
532
+ }
533
+ function deleteRegistration() {
534
+ try {
535
+ fs7.unlinkSync(PATHS.registration);
536
+ } catch {
537
+ }
538
+ }
539
+ function loadServices() {
540
+ try {
541
+ if (fs7.existsSync(PATHS.services)) {
542
+ return JSON.parse(fs7.readFileSync(PATHS.services, "utf-8"));
543
+ }
544
+ } catch {
545
+ }
546
+ return [];
547
+ }
548
+ function saveServices(services) {
549
+ ensureStateDir();
550
+ fs7.writeFileSync(PATHS.services, JSON.stringify(services, null, 2), "utf-8");
551
+ }
552
+ function appendToJsonl(filePath, entry) {
553
+ ensureStateDir();
554
+ fs7.appendFileSync(filePath, JSON.stringify(entry) + "\n");
555
+ }
556
+ function readJsonl(filePath) {
557
+ if (!fs7.existsSync(filePath)) return [];
558
+ const lines = fs7.readFileSync(filePath, "utf-8").trim().split("\n").filter(Boolean);
559
+ return lines.map((line) => {
35
560
  try {
36
- const data = JSON.parse(fs.readFileSync(budgetPath, 'utf-8'));
37
- if (data.date === today)
38
- return data;
561
+ return JSON.parse(line);
562
+ } catch {
563
+ return null;
39
564
  }
40
- catch {
41
- // Ignore parse errors
565
+ }).filter(Boolean);
566
+ }
567
+ function updateServiceQueueStatus(requestId, newStatus, additionalFields = {}) {
568
+ if (!fs7.existsSync(PATHS.serviceQueue)) return false;
569
+ const lines = fs7.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
570
+ let updated = false;
571
+ const updatedLines = lines.map((line) => {
572
+ try {
573
+ const entry = JSON.parse(line);
574
+ if (entry.requestId === requestId) {
575
+ updated = true;
576
+ return JSON.stringify({
577
+ ...entry,
578
+ status: newStatus,
579
+ ...additionalFields,
580
+ updatedAt: Date.now()
581
+ });
582
+ }
583
+ return line;
584
+ } catch {
585
+ return line;
42
586
  }
43
- return { date: today, totalSats: 0, transactions: [] };
587
+ });
588
+ if (updated) {
589
+ fs7.writeFileSync(PATHS.serviceQueue, updatedLines.join("\n") + "\n");
590
+ }
591
+ return updated;
44
592
  }
45
- function recordSpend(walletDir, sats, service, provider) {
46
- const spending = loadDailySpending(walletDir);
47
- spending.totalSats += sats;
48
- spending.transactions.push({ ts: Date.now(), sats, service, provider });
49
- fs.writeFileSync(getBudgetPath(walletDir), JSON.stringify(spending, null, 2));
593
+ var init_storage = __esm({
594
+ "src/scripts/utils/storage.ts"() {
595
+ "use strict";
596
+ init_config();
597
+ }
598
+ });
599
+
600
+ // src/scripts/overlay/transaction.ts
601
+ import { Utils as Utils4, PushDrop, Transaction } from "@bsv/sdk";
602
+ async function buildPushDropScript(wallet, payload) {
603
+ const jsonBytes = Utils4.toArray(JSON.stringify(payload), "utf8");
604
+ const fields = [jsonBytes];
605
+ const token = new PushDrop(wallet._setup.wallet);
606
+ const script = await token.lock(fields, [0, PROTOCOL_ID], "1", "self", true, true);
607
+ return script.toHex();
50
608
  }
51
- function checkBudget(walletDir, requestedSats, dailyLimit) {
52
- const spending = loadDailySpending(walletDir);
53
- const remaining = dailyLimit - spending.totalSats;
609
+ async function buildRealOverlayTransaction(payload, topic) {
610
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
611
+ const lockingScript = await buildPushDropScript(wallet, payload);
612
+ const response = await wallet._setup.wallet.createAction({
613
+ description: "topic manager submission",
614
+ outputs: [
615
+ {
616
+ lockingScript,
617
+ satoshis: 1,
618
+ outputDescription: "overlay",
619
+ basket: topic
620
+ // basket is the topic manager
621
+ }
622
+ ],
623
+ options: {
624
+ acceptDelayedBroadcast: false
625
+ }
626
+ });
627
+ const submitResp = await fetch(`${OVERLAY_URL}/submit`, {
628
+ method: "POST",
629
+ headers: {
630
+ "Content-Type": "application/octet-stream",
631
+ "X-Topics": JSON.stringify([topic])
632
+ },
633
+ body: new Uint8Array(response.tx)
634
+ });
635
+ if (!submitResp.ok) {
636
+ const errText = await submitResp.text();
637
+ throw new Error(`Overlay submission failed: ${submitResp.status} \u2014 ${errText}`);
638
+ }
639
+ const wocNet = NETWORK === "mainnet" ? "" : "test.";
640
+ return {
641
+ txid: response.txid,
642
+ funded: "stored-beef",
643
+ explorer: `https://${wocNet}whatsonchain.com/tx/${response.txid}`
644
+ };
645
+ }
646
+ async function lookupOverlay(service, query) {
647
+ const resp = await fetch(`${OVERLAY_URL}/lookup`, {
648
+ method: "POST",
649
+ headers: { "Content-Type": "application/json" },
650
+ body: JSON.stringify({ service, query })
651
+ });
652
+ if (!resp.ok) {
653
+ const errText = await resp.text();
654
+ throw new Error(`Lookup failed: ${resp.status} \u2014 ${errText}`);
655
+ }
656
+ return resp.json();
657
+ }
658
+ async function parseOverlayOutput(beefData, outputIndex) {
659
+ try {
660
+ const tx = Transaction.fromBEEF(beefData);
661
+ const txid = tx.id("hex");
662
+ const output = tx.outputs[outputIndex];
663
+ if (!output) return { data: null, txid: null };
664
+ const { fields } = PushDrop.decode(output.lockingScript);
665
+ return { data: JSON.parse(Utils4.toUTF8(fields[0])), txid };
666
+ } catch {
667
+ return { data: null, txid: null };
668
+ }
669
+ }
670
+ var init_transaction = __esm({
671
+ "src/scripts/overlay/transaction.ts"() {
672
+ "use strict";
673
+ init_config();
674
+ init_dist();
675
+ }
676
+ });
677
+
678
+ // src/scripts/overlay/services.ts
679
+ var services_exports = {};
680
+ __export(services_exports, {
681
+ cmdAdvertise: () => cmdAdvertise,
682
+ cmdReadvertise: () => cmdReadvertise,
683
+ cmdRemove: () => cmdRemove,
684
+ cmdServices: () => cmdServices
685
+ });
686
+ async function getBSVAgentWallet6() {
687
+ return BSVAgentWallet;
688
+ }
689
+ async function cmdServices() {
690
+ const services = loadServices();
691
+ return ok({ services, count: services.length });
692
+ }
693
+ async function cmdAdvertise(serviceId, name, priceSatsStr, description) {
694
+ if (!serviceId || !name || !priceSatsStr) {
695
+ return fail("Usage: advertise <serviceId> <name> <priceSats> [description]");
696
+ }
697
+ const priceSats = parseInt(priceSatsStr, 10);
698
+ if (isNaN(priceSats) || priceSats < 0) {
699
+ return fail("priceSats must be a non-negative integer");
700
+ }
701
+ const BSVAgentWallet2 = await getBSVAgentWallet6();
702
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
703
+ const identityKey = await wallet.getIdentityKey();
704
+ await wallet.destroy();
705
+ const services = loadServices();
706
+ const existing = services.find((s) => s.serviceId === serviceId);
707
+ if (existing) {
708
+ return fail(`Service '${serviceId}' already exists. Use 'readvertise' to update.`);
709
+ }
710
+ const newService = {
711
+ serviceId,
712
+ name,
713
+ description: description || `${name} service`,
714
+ priceSats,
715
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
716
+ };
717
+ const servicePayload = {
718
+ protocol: PROTOCOL_ID,
719
+ type: "service",
720
+ identityKey,
721
+ serviceId,
722
+ name,
723
+ description: newService.description,
724
+ pricing: {
725
+ model: "per-task",
726
+ amountSats: priceSats
727
+ },
728
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
729
+ };
730
+ try {
731
+ const result = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
732
+ newService.txid = result.txid;
733
+ services.push(newService);
734
+ saveServices(services);
735
+ return ok({
736
+ advertised: true,
737
+ service: newService,
738
+ txid: result.txid,
739
+ funded: result.funded
740
+ });
741
+ } catch (err) {
742
+ return fail(`Failed to advertise service: ${err.message}`);
743
+ }
744
+ }
745
+ async function cmdRemove(serviceId) {
746
+ if (!serviceId) {
747
+ return fail("Usage: remove <serviceId>");
748
+ }
749
+ const services = loadServices();
750
+ const idx = services.findIndex((s) => s.serviceId === serviceId);
751
+ if (idx === -1) {
752
+ return fail(`Service '${serviceId}' not found`);
753
+ }
754
+ const removed = services.splice(idx, 1)[0];
755
+ saveServices(services);
756
+ return ok({
757
+ removed: true,
758
+ service: removed,
759
+ note: "Removed from local registry. On-chain record remains (blockchain is immutable)."
760
+ });
761
+ }
762
+ async function cmdReadvertise(serviceId, name, priceSatsStr, description) {
763
+ if (!serviceId) {
764
+ return fail("Usage: readvertise <serviceId> [name] [priceSats] [description]");
765
+ }
766
+ const services = loadServices();
767
+ const existing = services.find((s) => s.serviceId === serviceId);
768
+ if (!existing) {
769
+ return fail(`Service '${serviceId}' not found. Use 'advertise' to create.`);
770
+ }
771
+ const BSVAgentWallet2 = await getBSVAgentWallet6();
772
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
773
+ const identityKey = await wallet.getIdentityKey();
774
+ await wallet.destroy();
775
+ if (name) existing.name = name;
776
+ if (priceSatsStr) {
777
+ const priceSats = parseInt(priceSatsStr, 10);
778
+ if (isNaN(priceSats) || priceSats < 0) {
779
+ return fail("priceSats must be a non-negative integer");
780
+ }
781
+ existing.priceSats = priceSats;
782
+ }
783
+ if (description) existing.description = description;
784
+ existing.registeredAt = (/* @__PURE__ */ new Date()).toISOString();
785
+ const servicePayload = {
786
+ protocol: PROTOCOL_ID,
787
+ type: "service",
788
+ identityKey,
789
+ serviceId,
790
+ name: existing.name,
791
+ description: existing.description,
792
+ pricing: {
793
+ model: "per-task",
794
+ amountSats: existing.priceSats
795
+ },
796
+ timestamp: existing.registeredAt
797
+ };
798
+ try {
799
+ const result = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
800
+ existing.txid = result.txid;
801
+ saveServices(services);
802
+ return ok({
803
+ readvertised: true,
804
+ service: existing,
805
+ txid: result.txid,
806
+ funded: result.funded
807
+ });
808
+ } catch (err) {
809
+ return fail(`Failed to readvertise service: ${err.message}`);
810
+ }
811
+ }
812
+ var init_services = __esm({
813
+ "src/scripts/overlay/services.ts"() {
814
+ "use strict";
815
+ init_config();
816
+ init_output();
817
+ init_storage();
818
+ init_transaction();
819
+ init_dist();
820
+ }
821
+ });
822
+
823
+ // index.ts
824
+ import path4 from "node:path";
825
+ import os2 from "node:os";
826
+ import fs13 from "node:fs";
827
+
828
+ // src/services/types.ts
829
+ var ServiceCategory = /* @__PURE__ */ ((ServiceCategory2) => {
830
+ ServiceCategory2["UTILITY"] = "utility";
831
+ ServiceCategory2["AI"] = "ai";
832
+ ServiceCategory2["BLOCKCHAIN"] = "blockchain";
833
+ ServiceCategory2["COMMUNICATION"] = "communication";
834
+ ServiceCategory2["DEVELOPMENT"] = "development";
835
+ ServiceCategory2["RESEARCH"] = "research";
836
+ ServiceCategory2["ENTERTAINMENT"] = "entertainment";
837
+ ServiceCategory2["CUSTOM"] = "custom";
838
+ return ServiceCategory2;
839
+ })(ServiceCategory || {});
840
+
841
+ // src/services/registry.ts
842
+ var DefaultServiceRegistry = class {
843
+ services = /* @__PURE__ */ new Map();
844
+ /**
845
+ * Register a new service definition.
846
+ */
847
+ register(service) {
848
+ this.validateServiceDefinition(service);
849
+ if (this.services.has(service.id)) {
850
+ throw new Error(`Service '${service.id}' is already registered`);
851
+ }
852
+ this.services.set(service.id, { ...service });
853
+ }
854
+ /**
855
+ * Get a service definition by ID.
856
+ */
857
+ get(serviceId) {
858
+ return this.services.get(serviceId);
859
+ }
860
+ /**
861
+ * List all registered services.
862
+ */
863
+ list() {
864
+ return Array.from(this.services.values());
865
+ }
866
+ /**
867
+ * List services by category.
868
+ */
869
+ listByCategory(category) {
870
+ return this.list().filter((service) => service.category === category);
871
+ }
872
+ /**
873
+ * Check if a service is registered.
874
+ */
875
+ has(serviceId) {
876
+ return this.services.has(serviceId);
877
+ }
878
+ /**
879
+ * Unregister a service.
880
+ */
881
+ unregister(serviceId) {
882
+ this.services.delete(serviceId);
883
+ }
884
+ /**
885
+ * Clear all services (useful for testing).
886
+ */
887
+ clear() {
888
+ this.services.clear();
889
+ }
890
+ /**
891
+ * Get service count.
892
+ */
893
+ count() {
894
+ return this.services.size;
895
+ }
896
+ /**
897
+ * Get services by price range.
898
+ */
899
+ getByPriceRange(minPrice, maxPrice) {
900
+ return this.list().filter(
901
+ (service) => service.defaultPrice >= minPrice && service.defaultPrice <= maxPrice
902
+ );
903
+ }
904
+ /**
905
+ * Search services by name or description.
906
+ */
907
+ search(query) {
908
+ const lowerQuery = query.toLowerCase();
909
+ return this.list().filter(
910
+ (service) => service.name.toLowerCase().includes(lowerQuery) || service.description.toLowerCase().includes(lowerQuery) || service.id.toLowerCase().includes(lowerQuery)
911
+ );
912
+ }
913
+ /**
914
+ * Validate a service definition.
915
+ */
916
+ validateServiceDefinition(service) {
917
+ if (!service.id) {
918
+ throw new Error("Service ID is required");
919
+ }
920
+ if (typeof service.id !== "string" || service.id.trim().length === 0) {
921
+ throw new Error("Service ID must be a non-empty string");
922
+ }
923
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(service.id)) {
924
+ throw new Error("Service ID must be in kebab-case format (lowercase, hyphens only)");
925
+ }
926
+ if (!service.name || typeof service.name !== "string" || service.name.trim().length === 0) {
927
+ throw new Error("Service name is required and must be a non-empty string");
928
+ }
929
+ if (!service.description || typeof service.description !== "string" || service.description.trim().length === 0) {
930
+ throw new Error("Service description is required and must be a non-empty string");
931
+ }
932
+ if (typeof service.defaultPrice !== "number" || service.defaultPrice < 0 || !Number.isInteger(service.defaultPrice)) {
933
+ throw new Error("Service defaultPrice must be a non-negative integer");
934
+ }
935
+ if (service.category && !Object.values(ServiceCategory).includes(service.category)) {
936
+ throw new Error(`Invalid service category: ${service.category}`);
937
+ }
938
+ if (service.inputSchema && typeof service.inputSchema !== "object") {
939
+ throw new Error("Service inputSchema must be an object");
940
+ }
941
+ if (service.handler) {
942
+ if (typeof service.handler.validate !== "function") {
943
+ throw new Error("Service handler must have a validate function");
944
+ }
945
+ if (typeof service.handler.process !== "function") {
946
+ throw new Error("Service handler must have a process function");
947
+ }
948
+ }
949
+ }
950
+ };
951
+ var serviceRegistry = new DefaultServiceRegistry();
952
+
953
+ // src/services/loader.ts
954
+ import fs from "node:fs";
955
+ import path from "node:path";
956
+ import { fileURLToPath } from "node:url";
957
+ var __filename = fileURLToPath(import.meta.url);
958
+ var __dirname = path.dirname(__filename);
959
+ var DefaultServiceLoader = class {
960
+ builtInDir;
961
+ customDir;
962
+ constructor() {
963
+ this.builtInDir = path.resolve(__dirname, "built-in");
964
+ const homeDir = process["env"].HOME || process["env"].USERPROFILE || "";
965
+ this.customDir = path.join(homeDir, ".openclaw", "services");
966
+ }
967
+ /**
968
+ * Load services from a directory.
969
+ */
970
+ async loadFromDirectory(directory) {
971
+ if (!fs.existsSync(directory)) {
972
+ return [];
973
+ }
974
+ const services = [];
975
+ const entries = fs.readdirSync(directory, { withFileTypes: true });
976
+ for (const entry of entries) {
977
+ if (!entry.isDirectory()) {
978
+ continue;
979
+ }
980
+ try {
981
+ const serviceDefinition = await this.loadServiceFromDirectory(
982
+ path.join(directory, entry.name)
983
+ );
984
+ if (serviceDefinition) {
985
+ services.push(serviceDefinition);
986
+ }
987
+ } catch (error) {
988
+ console.warn(`Failed to load service from ${entry.name}:`, error);
989
+ }
990
+ }
991
+ return services;
992
+ }
993
+ /**
994
+ * Load all built-in services.
995
+ */
996
+ async loadBuiltInServices() {
997
+ return this.loadFromDirectory(this.builtInDir);
998
+ }
999
+ /**
1000
+ * Load custom user services.
1001
+ */
1002
+ async loadCustomServices() {
1003
+ return this.loadFromDirectory(this.customDir);
1004
+ }
1005
+ /**
1006
+ * Load all services (built-in + custom).
1007
+ */
1008
+ async loadAllServices() {
1009
+ const [builtIn, custom] = await Promise.all([
1010
+ this.loadBuiltInServices(),
1011
+ this.loadCustomServices()
1012
+ ]);
1013
+ return [...builtIn, ...custom];
1014
+ }
1015
+ /**
1016
+ * Load a service definition from a directory.
1017
+ */
1018
+ async loadServiceFromDirectory(serviceDir) {
1019
+ const indexPath = path.join(serviceDir, "index.ts");
1020
+ const jsIndexPath = path.join(serviceDir, "index.js");
1021
+ const promptPath = path.join(serviceDir, "prompt.md");
1022
+ let modulePath;
1023
+ if (fs.existsSync(indexPath)) {
1024
+ modulePath = indexPath;
1025
+ } else if (fs.existsSync(jsIndexPath)) {
1026
+ modulePath = jsIndexPath;
1027
+ } else {
1028
+ throw new Error(`No index.ts or index.js found in ${serviceDir}`);
1029
+ }
1030
+ try {
1031
+ const serviceModule = await import(modulePath);
1032
+ const serviceDefinition = serviceModule.default || serviceModule;
1033
+ if (!serviceDefinition || typeof serviceDefinition !== "object") {
1034
+ throw new Error("Service must export a default ServiceDefinition object");
1035
+ }
1036
+ if (!serviceDefinition.id) {
1037
+ throw new Error("Service definition must have an id");
1038
+ }
1039
+ if (fs.existsSync(promptPath)) {
1040
+ serviceDefinition.promptFile = promptPath;
1041
+ }
1042
+ return serviceDefinition;
1043
+ } catch (error) {
1044
+ throw new Error(`Failed to import service from ${modulePath}: ${error}`);
1045
+ }
1046
+ }
1047
+ /**
1048
+ * Create a new custom service directory.
1049
+ */
1050
+ createCustomServiceDirectory(serviceId) {
1051
+ const serviceDir = path.join(this.customDir, serviceId);
1052
+ fs.mkdirSync(this.customDir, { recursive: true });
1053
+ if (fs.existsSync(serviceDir)) {
1054
+ throw new Error(`Service directory ${serviceId} already exists`);
1055
+ }
1056
+ fs.mkdirSync(serviceDir);
1057
+ return serviceDir;
1058
+ }
1059
+ /**
1060
+ * Create a basic service template.
1061
+ */
1062
+ createServiceTemplate(serviceId, options) {
1063
+ const serviceDir = this.createCustomServiceDirectory(serviceId);
1064
+ const indexContent = this.generateServiceTemplate(serviceId, options);
1065
+ fs.writeFileSync(path.join(serviceDir, "index.ts"), indexContent);
1066
+ const promptContent = this.generatePromptTemplate(serviceId, options);
1067
+ fs.writeFileSync(path.join(serviceDir, "prompt.md"), promptContent);
1068
+ if (options.hasHandler) {
1069
+ const handlerContent = this.generateHandlerTemplate(serviceId, options);
1070
+ fs.writeFileSync(path.join(serviceDir, "handler.ts"), handlerContent);
1071
+ }
1072
+ }
1073
+ /**
1074
+ * Generate service definition template.
1075
+ */
1076
+ generateServiceTemplate(serviceId, options) {
1077
+ return `/**
1078
+ * ${options.name} service definition.
1079
+ */
1080
+
1081
+ import { ServiceDefinition${options.hasHandler ? ", ServiceHandler" : ""} } from '../../types.js';
1082
+ ${options.hasHandler ? `import { ${serviceId.replace(/-/g, "")}Handler } from './handler.js';` : ""}
1083
+
1084
+ const ${serviceId.replace(/-/g, "")}Service: ServiceDefinition = {
1085
+ id: '${serviceId}',
1086
+ name: '${options.name}',
1087
+ description: '${options.description}',
1088
+ defaultPrice: ${options.defaultPrice},${options.category ? `
1089
+ category: '${options.category}',` : ""}
1090
+ inputSchema: {
1091
+ type: 'object',
1092
+ properties: {
1093
+ // Define your input schema here
1094
+ query: {
1095
+ type: 'string',
1096
+ description: 'Query or input for the service'
1097
+ }
1098
+ },
1099
+ required: ['query']
1100
+ }${options.hasHandler ? `,
1101
+ handler: ${serviceId.replace(/-/g, "")}Handler` : ""}
1102
+ };
1103
+
1104
+ export default ${serviceId.replace(/-/g, "")}Service;
1105
+ `;
1106
+ }
1107
+ /**
1108
+ * Generate prompt template.
1109
+ */
1110
+ generatePromptTemplate(serviceId, options) {
1111
+ return `# ${options.name} Service
1112
+
1113
+ You are processing a request for the "${serviceId}" service.
1114
+
1115
+ ## Service Description
1116
+ ${options.description}
1117
+
1118
+ ## Input
1119
+ The user has provided the following input:
1120
+ \`\`\`json
1121
+ {{input}}
1122
+ \`\`\`
1123
+
1124
+ ## Instructions
1125
+ Process the user's request and provide a helpful response based on the service description.
1126
+ Format your response as a structured result that can be easily parsed and used.
1127
+
1128
+ ## Response Format
1129
+ Provide your response in this format:
1130
+ \`\`\`json
1131
+ {
1132
+ "result": "your processed result here",
1133
+ "metadata": {
1134
+ "processingTime": "time taken",
1135
+ "version": "1.0"
1136
+ }
1137
+ }
1138
+ \`\`\`
1139
+ `;
1140
+ }
1141
+ /**
1142
+ * Generate handler template.
1143
+ */
1144
+ generateHandlerTemplate(serviceId, options) {
1145
+ return `/**
1146
+ * ${options.name} service handler.
1147
+ */
1148
+
1149
+ import { ServiceHandler, ValidationResult, ServiceContext, ServiceResult } from '../../types.js';
1150
+
1151
+ export const ${serviceId.replace(/-/g, "")}Handler: ServiceHandler = {
1152
+ /**
1153
+ * Validate service input.
1154
+ */
1155
+ validate(input: any): ValidationResult {
1156
+ if (!input || typeof input !== 'object') {
1157
+ return { valid: false, error: 'Input must be an object' };
1158
+ }
1159
+
1160
+ if (!input.query || typeof input.query !== 'string') {
1161
+ return { valid: false, error: 'Query must be a non-empty string' };
1162
+ }
1163
+
1164
+ // Add more validation as needed
1165
+ return { valid: true, sanitized: input };
1166
+ },
1167
+
1168
+ /**
1169
+ * Process the service request.
1170
+ */
1171
+ async process(input: any, context: ServiceContext): Promise<ServiceResult> {
1172
+ try {
1173
+ const startTime = Date.now();
1174
+
1175
+ // Your service logic here
1176
+ const result = {
1177
+ query: input.query,
1178
+ response: 'This is a template response. Implement your logic here.',
1179
+ timestamp: new Date().toISOString()
1180
+ };
1181
+
1182
+ const processingTime = Date.now() - startTime;
1183
+
1184
+ return {
1185
+ success: true,
1186
+ data: result,
1187
+ metadata: {
1188
+ processingTime,
1189
+ version: '1.0'
1190
+ }
1191
+ };
1192
+ } catch (error) {
1193
+ return {
1194
+ success: false,
1195
+ error: error instanceof Error ? error.message : String(error)
1196
+ };
1197
+ }
1198
+ }
1199
+ };
1200
+ `;
1201
+ }
1202
+ /**
1203
+ * Get the built-in services directory.
1204
+ */
1205
+ getBuiltInDirectory() {
1206
+ return this.builtInDir;
1207
+ }
1208
+ /**
1209
+ * Get the custom services directory.
1210
+ */
1211
+ getCustomDirectory() {
1212
+ return this.customDir;
1213
+ }
1214
+ /**
1215
+ * Check if a service directory exists.
1216
+ */
1217
+ serviceExists(serviceId, inCustom = false) {
1218
+ const baseDir = inCustom ? this.customDir : this.builtInDir;
1219
+ return fs.existsSync(path.join(baseDir, serviceId));
1220
+ }
1221
+ };
1222
+ var serviceLoader = new DefaultServiceLoader();
1223
+
1224
+ // src/services/manager.ts
1225
+ var DefaultServiceManager = class {
1226
+ registry;
1227
+ loader;
1228
+ initialized = false;
1229
+ constructor() {
1230
+ this.registry = serviceRegistry;
1231
+ this.loader = serviceLoader;
1232
+ }
1233
+ /**
1234
+ * Initialize the service system.
1235
+ */
1236
+ async initialize() {
1237
+ if (this.initialized) {
1238
+ return;
1239
+ }
1240
+ const services = await this.loader.loadAllServices();
1241
+ for (const service of services) {
1242
+ try {
1243
+ this.registry.register(service);
1244
+ } catch (error) {
1245
+ console.warn(`Failed to register service '${service.id}':`, error);
1246
+ }
1247
+ }
1248
+ this.initialized = true;
1249
+ console.log(`Service manager initialized with ${this.registry.count()} services`);
1250
+ }
1251
+ /**
1252
+ * Execute a service request.
1253
+ */
1254
+ async execute(serviceId, input, context) {
1255
+ if (!this.initialized) {
1256
+ throw new Error("Service manager not initialized");
1257
+ }
1258
+ const service = this.registry.get(serviceId);
1259
+ if (!service) {
1260
+ return {
1261
+ success: false,
1262
+ error: `Service '${serviceId}' not found`
1263
+ };
1264
+ }
1265
+ if (service.handler) {
1266
+ const validation = service.handler.validate(input);
1267
+ if (!validation.valid) {
1268
+ return {
1269
+ success: false,
1270
+ error: `Input validation failed: ${validation.error}`
1271
+ };
1272
+ }
1273
+ input = validation.sanitized || input;
1274
+ }
1275
+ try {
1276
+ if (service.handler) {
1277
+ return await service.handler.process(input, context);
1278
+ } else {
1279
+ return {
1280
+ success: true,
1281
+ data: {
1282
+ serviceId,
1283
+ input,
1284
+ mode: "agent",
1285
+ promptFile: service.promptFile
1286
+ },
1287
+ metadata: {
1288
+ version: "1.0",
1289
+ executionMode: "agent"
1290
+ }
1291
+ };
1292
+ }
1293
+ } catch (error) {
1294
+ return {
1295
+ success: false,
1296
+ error: error instanceof Error ? error.message : String(error),
1297
+ metadata: {
1298
+ serviceId,
1299
+ errorType: "execution_error"
1300
+ }
1301
+ };
1302
+ }
1303
+ }
1304
+ /**
1305
+ * Validate service input.
1306
+ */
1307
+ validate(serviceId, input) {
1308
+ const service = this.registry.get(serviceId);
1309
+ if (!service) {
1310
+ return {
1311
+ valid: false,
1312
+ error: `Service '${serviceId}' not found`
1313
+ };
1314
+ }
1315
+ if (service.handler) {
1316
+ return service.handler.validate(input);
1317
+ }
1318
+ if (service.inputSchema) {
1319
+ return this.validateAgainstSchema(input, service.inputSchema);
1320
+ }
1321
+ return { valid: true };
1322
+ }
1323
+ /**
1324
+ * Reload all services.
1325
+ */
1326
+ async reload() {
1327
+ this.registry.clear();
1328
+ this.initialized = false;
1329
+ await this.initialize();
1330
+ }
1331
+ /**
1332
+ * Get service for agent-mode processing.
1333
+ */
1334
+ getServiceForAgentMode(serviceId) {
1335
+ const service = this.registry.get(serviceId);
1336
+ if (!service) {
1337
+ return null;
1338
+ }
54
1339
  return {
55
- allowed: remaining >= requestedSats,
56
- remaining,
57
- spent: spending.totalSats
1340
+ service,
1341
+ promptFile: service.promptFile
58
1342
  };
59
- }
60
- function applyConfigToEnv(config) {
61
- process['env'].BSV_WALLET_DIR = config.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
62
- process['env'].OVERLAY_URL = config.overlayUrl || 'https://clawoverlay.com';
63
- process['env'].BSV_NETWORK = config.network || process['env'].BSV_NETWORK || 'mainnet';
64
- process['env'].BSV_ARC_URL = config.arcUrl || (process['env'].BSV_NETWORK === 'testnet' ? 'https://testnet.arc.gorillapool.io' : 'https://arc.gorillapool.io');
65
- process['env'].AGENT_NAME = config.agentName || 'openclaw-agent';
66
- setNoExit(true);
67
- }
68
- async function startAutoImport(config, api) {
1343
+ }
1344
+ /**
1345
+ * Check if service is available.
1346
+ */
1347
+ isServiceAvailable(serviceId) {
1348
+ return this.registry.has(serviceId);
1349
+ }
1350
+ /**
1351
+ * Get service execution mode.
1352
+ */
1353
+ getServiceMode(serviceId) {
1354
+ const service = this.registry.get(serviceId);
1355
+ if (!service) {
1356
+ return null;
1357
+ }
1358
+ return service.handler ? "handler" : "agent";
1359
+ }
1360
+ /**
1361
+ * Get all available services for discovery.
1362
+ */
1363
+ getAvailableServices() {
1364
+ return this.registry.list().map((service) => ({
1365
+ id: service.id,
1366
+ name: service.name,
1367
+ description: service.description,
1368
+ defaultPrice: service.defaultPrice,
1369
+ category: service.category,
1370
+ mode: service.handler ? "handler" : "agent"
1371
+ }));
1372
+ }
1373
+ /**
1374
+ * Validate input against JSON schema.
1375
+ */
1376
+ validateAgainstSchema(input, schema) {
69
1377
  try {
70
- applyConfigToEnv(config);
71
- const addrOutput = await cmdAddress();
72
- if (!addrOutput.success)
73
- return;
74
- const address = addrOutput.data?.address;
75
- if (!address)
76
- return;
77
- autoImportInterval = setInterval(async () => {
78
- try {
79
- const network = process['env'].BSV_NETWORK === 'testnet' ? 'test' : 'main';
80
- const controller = new AbortController();
81
- const timeout = setTimeout(() => controller.abort(), 15000);
82
- const resp = await fetch(`https://api.whatsonchain.com/v1/bsv/${network}/address/${address}/unspent/all`, { signal: controller.signal });
83
- clearTimeout(timeout);
84
- if (!resp.ok)
85
- return;
86
- const data = await resp.json();
87
- const utxos = data.result || [];
88
- for (const utxo of utxos) {
89
- const key = `${utxo.tx_hash}:${utxo.tx_pos}`;
90
- if (knownTxids.has(key))
91
- continue;
92
- if (utxo.value < 200)
93
- continue;
94
- api.logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
95
- try {
96
- applyConfigToEnv(config);
97
- const importOutput = await cmdImport(utxo.tx_hash, String(utxo.tx_pos));
98
- if (importOutput.success) {
99
- knownTxids.add(key);
100
- api.logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
101
- // Notify agent of successful import
102
- 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.`, api.logger, { sessionKey: 'hook:openclaw-overlay:import' });
103
- // Check if registered, auto-register if not
104
- try {
105
- const regPath = path.join(os.homedir(), '.openclaw', 'openclaw-overlay', 'registration.json');
106
- if (!fs.existsSync(regPath)) {
107
- api.logger?.info?.('[openclaw-overlay] Not yet registered — auto-registering...');
108
- applyConfigToEnv(config);
109
- const regOutput = await cmdRegister();
110
- if (regOutput.success) {
111
- api.logger?.info?.('[openclaw-overlay] Auto-registered on overlay network!');
112
- await autoAdvertiseServices(config, api.logger);
113
- }
114
- }
115
- }
116
- catch (err) {
117
- api.logger?.warn?.('[openclaw-overlay] Auto-registration failed:', err.message);
118
- }
119
- }
120
- }
121
- catch (err) {
122
- knownTxids.add(key);
123
- }
124
- }
1378
+ const schemaObj = schema;
1379
+ if (schemaObj.type === "object") {
1380
+ if (!input || typeof input !== "object") {
1381
+ return { valid: false, error: "Input must be an object" };
1382
+ }
1383
+ if (schemaObj.required && Array.isArray(schemaObj.required)) {
1384
+ for (const requiredProp of schemaObj.required) {
1385
+ if (!(requiredProp in input)) {
1386
+ return { valid: false, error: `Missing required property: ${requiredProp}` };
125
1387
  }
126
- catch (err) {
127
- // WoC API error
1388
+ }
1389
+ }
1390
+ if (schemaObj.properties) {
1391
+ for (const [propName, propSchema] of Object.entries(schemaObj.properties)) {
1392
+ if (propName in input) {
1393
+ const propType = propSchema.type;
1394
+ const actualType = typeof input[propName];
1395
+ if (propType && propType !== actualType) {
1396
+ return { valid: false, error: `Property '${propName}' must be of type ${propType}` };
1397
+ }
128
1398
  }
129
- }, 30000);
1399
+ }
1400
+ }
1401
+ }
1402
+ return { valid: true, sanitized: input };
1403
+ } catch (error) {
1404
+ return { valid: false, error: `Schema validation error: ${error}` };
130
1405
  }
131
- catch (err) {
132
- api.logger?.warn?.('[openclaw-overlay] Auto-import setup failed:', err.message);
1406
+ }
1407
+ /**
1408
+ * Get service statistics.
1409
+ */
1410
+ getStatistics() {
1411
+ const services = this.registry.list();
1412
+ const stats = {
1413
+ totalServices: services.length,
1414
+ handlerServices: 0,
1415
+ agentServices: 0,
1416
+ servicesByCategory: {}
1417
+ };
1418
+ for (const service of services) {
1419
+ if (service.handler) {
1420
+ stats.handlerServices++;
1421
+ } else {
1422
+ stats.agentServices++;
1423
+ }
1424
+ const category = service.category || "uncategorized";
1425
+ stats.servicesByCategory[category] = (stats.servicesByCategory[category] || 0) + 1;
133
1426
  }
1427
+ return stats;
1428
+ }
1429
+ };
1430
+ var serviceManager = new DefaultServiceManager();
1431
+ async function initializeServiceSystem() {
1432
+ await serviceManager.initialize();
134
1433
  }
135
- async function autoAdvertiseServices(config, logger) {
1434
+
1435
+ // src/scripts/wallet/setup.ts
1436
+ init_config();
1437
+ init_output();
1438
+ import fs5 from "node:fs";
1439
+
1440
+ // src/scripts/wallet/identity.ts
1441
+ init_config();
1442
+ import fs3 from "node:fs";
1443
+ import { CachedKeyDeriver, Utils } from "@bsv/sdk";
1444
+ import { brc29ProtocolID } from "@bsv/wallet-toolbox";
1445
+ var _sdk = null;
1446
+ async function getSdk() {
1447
+ if (_sdk) return _sdk;
1448
+ try {
1449
+ _sdk = await import("@bsv/sdk");
1450
+ return _sdk;
1451
+ } catch {
1452
+ const { fileURLToPath: fileURLToPath2 } = await import("node:url");
1453
+ const path5 = await import("node:path");
1454
+ const os3 = await import("node:os");
1455
+ const __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
1456
+ const candidates = [
1457
+ path5.resolve(__dirname2, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
1458
+ path5.resolve(__dirname2, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
1459
+ path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
1460
+ ];
1461
+ for (const p of candidates) {
1462
+ try {
1463
+ _sdk = await import(p);
1464
+ return _sdk;
1465
+ } catch {
1466
+ }
1467
+ }
1468
+ throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
1469
+ }
1470
+ }
1471
+ function loadWalletIdentity() {
1472
+ if (!fs3.existsSync(PATHS.walletIdentity)) {
1473
+ throw new Error("Wallet not initialized. Run: cli setup");
1474
+ }
1475
+ try {
1476
+ const fileMode = fs3.statSync(PATHS.walletIdentity).mode & 511;
1477
+ if (fileMode & 36) {
1478
+ console.error(`[security] WARNING: ${PATHS.walletIdentity} has permissive mode 0${fileMode.toString(8)}. Run: chmod 600 ${PATHS.walletIdentity}`);
1479
+ }
1480
+ } catch {
1481
+ }
1482
+ return JSON.parse(fs3.readFileSync(PATHS.walletIdentity, "utf-8"));
1483
+ }
1484
+ async function loadIdentity() {
1485
+ const identity = loadWalletIdentity();
1486
+ const sdk = await getSdk();
1487
+ const privKey = sdk.PrivateKey.fromHex(identity.rootKeyHex);
1488
+ return { identityKey: identity.identityKey, privKey };
1489
+ }
1490
+ async function signRelayMessage(privKey, to, type, payload) {
1491
+ const sdk = await getSdk();
1492
+ const preimage = to + type + JSON.stringify(payload);
1493
+ const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
1494
+ const sig = privKey.sign(msgHash);
1495
+ return Array.from(sig.toDER()).map((b) => b.toString(16).padStart(2, "0")).join("");
1496
+ }
1497
+ async function verifyRelaySignature(fromKey, to, type, payload, signatureHex) {
1498
+ if (!signatureHex) return { valid: false, reason: "no signature" };
1499
+ try {
1500
+ const sdk = await getSdk();
1501
+ const preimage = to + type + JSON.stringify(payload);
1502
+ const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
1503
+ const sigBytes = [];
1504
+ for (let i = 0; i < signatureHex.length; i += 2) {
1505
+ sigBytes.push(parseInt(signatureHex.substring(i, i + 2), 16));
1506
+ }
1507
+ const sig = sdk.Signature.fromDER(sigBytes);
1508
+ const pubKey = sdk.PublicKey.fromString(fromKey);
1509
+ return { valid: pubKey.verify(msgHash, sig) };
1510
+ } catch (err) {
1511
+ return { valid: false, reason: String(err) };
1512
+ }
1513
+ }
1514
+ async function deriveWalletAddress(privKey, network = NETWORK) {
1515
+ const keyDeriver = new CachedKeyDeriver(privKey);
1516
+ const pubKey = keyDeriver.derivePublicKey(
1517
+ brc29ProtocolID,
1518
+ Utils.toBase64(Utils.toArray("import")) + " " + Utils.toBase64(Utils.toArray("now")),
1519
+ "self",
1520
+ true
1521
+ );
1522
+ const address = pubKey.toAddress(network);
1523
+ const hash160 = Buffer.from(pubKey.toHash());
1524
+ return { address, hash160, pubKey };
1525
+ }
1526
+
1527
+ // src/scripts/wallet/setup.ts
1528
+ init_dist();
1529
+ async function getBSVAgentWallet() {
1530
+ return BSVAgentWallet;
1531
+ }
1532
+ var _sdk2 = null;
1533
+ async function getSdk2() {
1534
+ if (_sdk2) return _sdk2;
1535
+ try {
1536
+ _sdk2 = await import("@bsv/sdk");
1537
+ return _sdk2;
1538
+ } catch {
1539
+ const { fileURLToPath: fileURLToPath2 } = await import("node:url");
1540
+ const path5 = await import("node:path");
1541
+ const os3 = await import("node:os");
1542
+ const __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
1543
+ const candidates = [
1544
+ path5.resolve(__dirname2, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
1545
+ path5.resolve(__dirname2, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
1546
+ path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
1547
+ ];
1548
+ for (const p of candidates) {
1549
+ try {
1550
+ _sdk2 = await import(p);
1551
+ return _sdk2;
1552
+ } catch {
1553
+ }
1554
+ }
1555
+ throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
1556
+ }
1557
+ }
1558
+ async function cmdSetup() {
1559
+ const BSVAgentWallet2 = await getBSVAgentWallet();
1560
+ if (fs5.existsSync(PATHS.walletIdentity)) {
1561
+ const wallet2 = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
1562
+ const identityKey2 = await wallet2.getIdentityKey();
1563
+ await wallet2.destroy();
1564
+ return ok({
1565
+ identityKey: identityKey2,
1566
+ walletDir: WALLET_DIR,
1567
+ network: NETWORK,
1568
+ overlayUrl: OVERLAY_URL,
1569
+ alreadyExisted: true
1570
+ });
1571
+ }
1572
+ fs5.mkdirSync(WALLET_DIR, { recursive: true });
1573
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
1574
+ const identityKey = await wallet.getIdentityKey();
1575
+ await wallet.destroy();
1576
+ if (fs5.existsSync(PATHS.walletIdentity)) {
1577
+ fs5.chmodSync(PATHS.walletIdentity, 384);
1578
+ }
1579
+ return ok({
1580
+ identityKey,
1581
+ walletDir: WALLET_DIR,
1582
+ network: NETWORK,
1583
+ overlayUrl: OVERLAY_URL,
1584
+ alreadyExisted: false
1585
+ });
1586
+ }
1587
+ async function cmdIdentity() {
1588
+ const BSVAgentWallet2 = await getBSVAgentWallet();
1589
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
1590
+ const identityKey = await wallet.getIdentityKey();
1591
+ await wallet.destroy();
1592
+ return ok({ identityKey });
1593
+ }
1594
+ async function cmdStatus() {
1595
+ const BSVAgentWallet2 = await getBSVAgentWallet();
1596
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
1597
+ const identityKey = await wallet.getIdentityKey();
1598
+ const total = await wallet.getBalance();
1599
+ await wallet.destroy();
1600
+ return ok({
1601
+ identity: { identityKey, network: NETWORK },
1602
+ balance: { walletBalance: total }
1603
+ });
1604
+ }
1605
+ async function cmdAddress() {
1606
+ if (!fs5.existsSync(PATHS.walletIdentity)) {
1607
+ return fail("Wallet not initialized. Run: setup");
1608
+ }
1609
+ const sdk = await getSdk2();
1610
+ const identity = loadWalletIdentity();
1611
+ const privKey = sdk.PrivateKey.fromHex(identity.rootKeyHex);
1612
+ const { address } = await deriveWalletAddress(privKey);
1613
+ return ok({
1614
+ address,
1615
+ network: NETWORK,
1616
+ identityKey: identity.identityKey,
1617
+ note: NETWORK === "mainnet" ? `Fund this address at an exchange \u2014 Explorer: https://whatsonchain.com/address/${address}` : `Fund via faucet: https://witnessonchain.com/faucet/tbsv \u2014 Explorer: https://test.whatsonchain.com/address/${address}`
1618
+ });
1619
+ }
1620
+
1621
+ // src/scripts/wallet/balance.ts
1622
+ init_config();
1623
+ init_output();
1624
+ import fs6 from "node:fs";
1625
+
1626
+ // src/scripts/utils/woc.ts
1627
+ init_config();
1628
+ async function wocFetch(urlPath, options = {}, maxRetries = 3, timeoutMs = 3e4) {
1629
+ const wocNet = NETWORK === "mainnet" ? "main" : "test";
1630
+ const base = `https://api.whatsonchain.com/v1/bsv/${wocNet}`;
1631
+ const url = urlPath.startsWith("http") ? urlPath : `${base}${urlPath}`;
1632
+ const headers = { ...options.headers || {} };
1633
+ if (WOC_API_KEY) {
1634
+ headers["Authorization"] = `Bearer ${WOC_API_KEY}`;
1635
+ }
1636
+ let lastError;
1637
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1638
+ try {
1639
+ const controller = new AbortController();
1640
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1641
+ const resp = await fetch(url, { ...options, headers, signal: controller.signal });
1642
+ clearTimeout(timeout);
1643
+ if ((resp.status === 429 || resp.status >= 500) && attempt < maxRetries) {
1644
+ const delayMs = Math.min(1e3 * Math.pow(2, attempt), 8e3);
1645
+ await new Promise((r) => setTimeout(r, delayMs));
1646
+ continue;
1647
+ }
1648
+ return resp;
1649
+ } catch (err) {
1650
+ lastError = err;
1651
+ if (attempt < maxRetries) {
1652
+ const delayMs = Math.min(1e3 * Math.pow(2, attempt), 8e3);
1653
+ await new Promise((r) => setTimeout(r, delayMs));
1654
+ continue;
1655
+ }
1656
+ }
1657
+ }
1658
+ throw lastError || new Error("WoC fetch failed after retries");
1659
+ }
1660
+ async function fetchWithTimeout(url, options = {}, timeoutMs = 15e3) {
1661
+ const controller = new AbortController();
1662
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1663
+ try {
1664
+ const resp = await fetch(url, { ...options, signal: controller.signal });
1665
+ return resp;
1666
+ } finally {
1667
+ clearTimeout(timeout);
1668
+ }
1669
+ }
1670
+ async function fetchBeefFromWoC(txid) {
1671
+ try {
1672
+ const resp = await wocFetch(`/tx/${txid}/beef`);
1673
+ if (!resp.ok) return null;
1674
+ const hexStr = (await resp.text()).trim();
1675
+ if (!hexStr || hexStr.length < 8) return null;
1676
+ const bytes = hexStr.match(/.{2}/g).map((h) => parseInt(h, 16));
1677
+ return new Uint8Array(bytes);
1678
+ } catch {
1679
+ return null;
1680
+ }
1681
+ }
1682
+ function getExplorerBaseUrl() {
1683
+ return NETWORK === "mainnet" ? "https://whatsonchain.com" : "https://test.whatsonchain.com";
1684
+ }
1685
+
1686
+ // src/scripts/utils/merkle.ts
1687
+ var _MerklePath = null;
1688
+ async function getMerklePath() {
1689
+ if (_MerklePath) return _MerklePath;
1690
+ const sdk = await import("@bsv/sdk");
1691
+ _MerklePath = sdk.MerklePath;
1692
+ return _MerklePath;
1693
+ }
1694
+ async function buildMerklePathFromTSC(txid, txIndex, nodes, blockHeight) {
1695
+ const MerklePath = await getMerklePath();
1696
+ const treeHeight = nodes.length;
1697
+ const mpPath = [];
1698
+ const level0 = [
1699
+ { offset: txIndex, hash: txid, txid: true }
1700
+ ];
1701
+ if (nodes[0] === "*") {
1702
+ level0.push({ offset: txIndex ^ 1, duplicate: true });
1703
+ } else {
1704
+ level0.push({ offset: txIndex ^ 1, hash: nodes[0] });
1705
+ }
1706
+ level0.sort((a, b) => a.offset - b.offset);
1707
+ mpPath.push(level0);
1708
+ for (let i = 1; i < treeHeight; i++) {
1709
+ const siblingOffset = txIndex >> i ^ 1;
1710
+ if (nodes[i] === "*") {
1711
+ mpPath.push([{ offset: siblingOffset, duplicate: true }]);
1712
+ } else {
1713
+ mpPath.push([{ offset: siblingOffset, hash: nodes[i] }]);
1714
+ }
1715
+ }
1716
+ return new MerklePath(blockHeight, mpPath);
1717
+ }
1718
+
1719
+ // src/scripts/wallet/balance.ts
1720
+ init_dist();
1721
+ async function getBSVAgentWallet2() {
1722
+ return BSVAgentWallet;
1723
+ }
1724
+ var _sdk3 = null;
1725
+ async function getSdk3() {
1726
+ if (_sdk3) return _sdk3;
1727
+ try {
1728
+ _sdk3 = await import("@bsv/sdk");
1729
+ return _sdk3;
1730
+ } catch {
1731
+ const { fileURLToPath: fileURLToPath2 } = await import("node:url");
1732
+ const path5 = await import("node:path");
1733
+ const os3 = await import("node:os");
1734
+ const __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
1735
+ const candidates = [
1736
+ path5.resolve(__dirname2, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
1737
+ path5.resolve(__dirname2, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
1738
+ path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
1739
+ ];
1740
+ for (const p of candidates) {
1741
+ try {
1742
+ _sdk3 = await import(p);
1743
+ return _sdk3;
1744
+ } catch {
1745
+ }
1746
+ }
1747
+ throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
1748
+ }
1749
+ }
1750
+ function sleep(ms) {
1751
+ return new Promise((resolve) => setTimeout(resolve, ms));
1752
+ }
1753
+ async function cmdBalance() {
1754
+ const BSVAgentWallet2 = await getBSVAgentWallet2();
1755
+ const sdk = await getSdk3();
1756
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
1757
+ const total = await wallet.getBalance();
1758
+ await wallet.destroy();
1759
+ return ok({ walletBalance: total });
1760
+ }
1761
+ async function cmdImport(txidArg, voutStr) {
1762
+ if (!txidArg) {
1763
+ return fail("Usage: import <txid> [vout]");
1764
+ }
1765
+ const vout = parseInt(voutStr || "0", 10);
1766
+ const txid = txidArg.toLowerCase();
1767
+ if (!/^[0-9a-f]{64}$/.test(txid)) {
1768
+ return fail("Invalid txid \u2014 must be 64 hex characters");
1769
+ }
1770
+ const sdk = await getSdk3();
1771
+ const BSVAgentWallet2 = await getBSVAgentWallet2();
1772
+ let txInfo = null;
1773
+ const maxWaitMs = 6e4;
1774
+ const startTime = Date.now();
1775
+ let attempt = 0;
1776
+ while (Date.now() - startTime < maxWaitMs) {
1777
+ const txInfoResp = await wocFetch(`/tx/${txid}`, {}, 1, 1e4);
1778
+ if (txInfoResp.ok) {
1779
+ txInfo = await txInfoResp.json();
1780
+ break;
1781
+ } else if (txInfoResp.status === 404) {
1782
+ attempt++;
1783
+ const delayMs = Math.min(1e3 * Math.pow(1.5, attempt), 1e4);
1784
+ console.error(`[import] Transaction not on WoC yet, waiting ${Math.round(delayMs / 1e3)}s... (attempt ${attempt})`);
1785
+ await sleep(delayMs);
1786
+ continue;
1787
+ } else {
1788
+ return fail(`Failed to fetch tx info: ${txInfoResp.status}`);
1789
+ }
1790
+ }
1791
+ if (!txInfo) {
1792
+ return fail(`Transaction ${txid} not found on WhatsOnChain after ${Math.round((Date.now() - startTime) / 1e3)}s. The transaction may not have been broadcast yet, or the txid may be incorrect.`);
1793
+ }
1794
+ const isConfirmed = txInfo.confirmations && txInfo.confirmations >= 1;
1795
+ const blockHeight = txInfo.blockheight;
1796
+ if (!txInfo.vout || !txInfo.vout[vout]) {
1797
+ return fail(`Output index ${vout} not found in transaction (has ${txInfo.vout?.length || 0} outputs)`);
1798
+ }
1799
+ let atomicBeefBytes;
1800
+ const wocBeefBytes = await fetchBeefFromWoC(txid);
1801
+ if (wocBeefBytes) {
136
1802
  try {
137
- let servicesToAdvertise = [];
138
- if (config?.services && Array.isArray(config.services)) {
139
- servicesToAdvertise = config.services;
1803
+ const wocBeef = sdk.Beef.fromBinary(Array.from(wocBeefBytes));
1804
+ const foundTx = wocBeef.findTxid(txid);
1805
+ if (foundTx) {
1806
+ const txObj = foundTx.tx || foundTx._tx;
1807
+ if (txObj) {
1808
+ const output = txObj.outputs[vout];
1809
+ if (!output) {
1810
+ return fail(`Output index ${vout} not found in BEEF transaction (has ${txObj.outputs.length} outputs)`);
1811
+ }
140
1812
  }
141
- if (servicesToAdvertise.length === 0)
142
- return;
143
- for (const serviceId of servicesToAdvertise) {
144
- const serviceInfo = serviceManager.registry.get(serviceId);
145
- if (!serviceInfo)
146
- continue;
147
- try {
148
- const { cmdAdvertise } = await import('./src/scripts/overlay/services.js');
149
- applyConfigToEnv(config);
150
- await cmdAdvertise(serviceId, serviceInfo.name, String(serviceInfo.defaultPrice), serviceInfo.description);
1813
+ atomicBeefBytes = wocBeef.toBinaryAtomic(txid);
1814
+ }
1815
+ } catch (beefErr) {
1816
+ console.error(`[import] WoC BEEF parse failed: ${beefErr.message}`);
1817
+ }
1818
+ }
1819
+ if (!atomicBeefBytes && isConfirmed) {
1820
+ try {
1821
+ const rawTxResp = await wocFetch(`/tx/${txid}/hex`);
1822
+ if (!rawTxResp.ok) {
1823
+ return fail(`Failed to fetch raw transaction: ${rawTxResp.status}`);
1824
+ }
1825
+ const rawTxHex = await rawTxResp.text();
1826
+ const sourceTx = sdk.Transaction.fromHex(rawTxHex.trim());
1827
+ const proofResp = await wocFetch(`/tx/${txid}/proof/tsc`);
1828
+ if (!proofResp.ok) {
1829
+ return fail(`Failed to fetch merkle proof: ${proofResp.status}`);
1830
+ }
1831
+ const proofData = await proofResp.json();
1832
+ if (!Array.isArray(proofData) || proofData.length === 0) {
1833
+ return fail("Merkle proof not available from WoC");
1834
+ }
1835
+ const proof = proofData[0];
1836
+ const merklePath = await buildMerklePathFromTSC(txid, proof.index, proof.nodes, blockHeight);
1837
+ sourceTx.merklePath = merklePath;
1838
+ const beef = new sdk.Beef();
1839
+ beef.mergeTransaction(sourceTx);
1840
+ atomicBeefBytes = beef.toBinaryAtomic(txid);
1841
+ } catch (manualErr) {
1842
+ return fail(`Failed to construct BEEF manually: ${manualErr.message}`);
1843
+ }
1844
+ }
1845
+ if (!atomicBeefBytes) {
1846
+ if (isConfirmed) {
1847
+ return fail(`Transaction ${txid} is confirmed but BEEF construction failed. This is unexpected \u2014 please report this issue.`);
1848
+ } else {
1849
+ return fail(
1850
+ `Transaction ${txid} is unconfirmed (${txInfo.confirmations || 0} confirmations) and BEEF is not available.
1851
+
1852
+ This usually means the funding transaction spends from other unconfirmed transactions, creating a chain.
1853
+ Wait for 1 block confirmation (~10 minutes) and try again, or use a fresh UTXO as the funding source.`
1854
+ );
1855
+ }
1856
+ }
1857
+ const outputSatoshis = txInfo.vout[vout].value != null ? Math.round(txInfo.vout[vout].value * 1e8) : void 0;
1858
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
1859
+ const identityKey = await wallet.getIdentityKey();
1860
+ try {
1861
+ await wallet._setup.wallet.storage.internalizeAction({
1862
+ tx: Array.from(atomicBeefBytes),
1863
+ outputs: [{
1864
+ outputIndex: vout,
1865
+ protocol: "wallet payment",
1866
+ paymentRemittance: {
1867
+ derivationPrefix: sdk.Utils.toBase64(sdk.Utils.toArray("import", "utf8")),
1868
+ derivationSuffix: sdk.Utils.toBase64(sdk.Utils.toArray("now", "utf8")),
1869
+ senderIdentityKey: identityKey
1870
+ }
1871
+ }],
1872
+ description: "External funding import"
1873
+ });
1874
+ const balance = await wallet.getBalance();
1875
+ await wallet.destroy();
1876
+ const explorerBase = getExplorerBaseUrl();
1877
+ return ok({
1878
+ txid,
1879
+ vout,
1880
+ satoshis: outputSatoshis,
1881
+ blockHeight: blockHeight || null,
1882
+ confirmations: txInfo.confirmations || 0,
1883
+ imported: true,
1884
+ unconfirmed: !isConfirmed,
1885
+ balance,
1886
+ explorer: `${explorerBase}/tx/${txid}`
1887
+ });
1888
+ } catch (err) {
1889
+ await wallet.destroy();
1890
+ if (err.message?.includes("already") || err.message?.includes("duplicate")) {
1891
+ return fail(`UTXO ${txid}:${vout} appears to already be imported.`);
1892
+ }
1893
+ if (err.message?.includes("script") || err.message?.includes("locking")) {
1894
+ return fail(`UTXO ${txid}:${vout} does not belong to this wallet's address. Make sure you sent to the correct address.`);
1895
+ }
1896
+ return fail(`Failed to import UTXO: ${err.message}`);
1897
+ }
1898
+ }
1899
+
1900
+ // src/scripts/overlay/registration.ts
1901
+ init_config();
1902
+ init_output();
1903
+ init_storage();
1904
+ init_transaction();
1905
+ init_dist();
1906
+ import fs8 from "node:fs";
1907
+ import { Transaction as Transaction2, Beef as Beef3, Script, PushDrop as PushDrop2 } from "@bsv/sdk";
1908
+ async function getBSVAgentWallet3() {
1909
+ return BSVAgentWallet;
1910
+ }
1911
+ async function cmdRegister() {
1912
+ if (!fs8.existsSync(PATHS.walletIdentity)) {
1913
+ return fail("Wallet not initialized. Run: setup");
1914
+ }
1915
+ const BSVAgentWallet2 = await getBSVAgentWallet3();
1916
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
1917
+ const identityKey = await wallet.getIdentityKey();
1918
+ await wallet.destroy();
1919
+ const existingReg = loadRegistration();
1920
+ if (existingReg && existingReg.identityKey === identityKey) {
1921
+ return ok({
1922
+ alreadyRegistered: true,
1923
+ identityKey,
1924
+ identityTxid: existingReg.identityTxid,
1925
+ overlayUrl: OVERLAY_URL
1926
+ });
1927
+ }
1928
+ const agentName = AGENT_NAME;
1929
+ const agentDescription = AGENT_DESCRIPTION;
1930
+ const capabilities = ["services"];
1931
+ const services = loadServices();
1932
+ if (services.some((s) => s.serviceId === "tell-joke")) {
1933
+ capabilities.push("jokes");
1934
+ }
1935
+ const identityPayload = {
1936
+ protocol: PROTOCOL_ID,
1937
+ type: "identity",
1938
+ identityKey,
1939
+ name: agentName,
1940
+ description: agentDescription,
1941
+ channels: {
1942
+ overlay: OVERLAY_URL
1943
+ },
1944
+ capabilities,
1945
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1946
+ };
1947
+ let identityResult;
1948
+ try {
1949
+ identityResult = await buildRealOverlayTransaction(identityPayload, TOPICS.IDENTITY);
1950
+ } catch (err) {
1951
+ return fail(`Registration failed: ${err.message}`);
1952
+ }
1953
+ let serviceTxid = null;
1954
+ if (services.length > 0) {
1955
+ for (const service of services) {
1956
+ const servicePayload = {
1957
+ protocol: PROTOCOL_ID,
1958
+ type: "service",
1959
+ identityKey,
1960
+ serviceId: service.serviceId,
1961
+ name: service.name,
1962
+ description: service.description,
1963
+ pricing: {
1964
+ model: "per-task",
1965
+ amountSats: service.priceSats
1966
+ },
1967
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1968
+ };
1969
+ try {
1970
+ const serviceResult = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
1971
+ serviceTxid = serviceResult.txid;
1972
+ } catch {
1973
+ }
1974
+ }
1975
+ }
1976
+ const registration = {
1977
+ identityKey,
1978
+ agentName,
1979
+ agentDescription,
1980
+ overlayUrl: OVERLAY_URL,
1981
+ identityTxid: identityResult.txid,
1982
+ serviceTxid,
1983
+ funded: identityResult.funded,
1984
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
1985
+ };
1986
+ saveRegistration(registration);
1987
+ return ok({
1988
+ registered: true,
1989
+ identityKey,
1990
+ identityTxid: identityResult.txid,
1991
+ serviceTxid,
1992
+ overlayUrl: OVERLAY_URL,
1993
+ funded: identityResult.funded,
1994
+ stateFile: PATHS.registration
1995
+ });
1996
+ }
1997
+ async function cmdUnregister() {
1998
+ const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
1999
+ const { outputs, BEEF } = await wallet._setup.wallet.listOutputs({ basket: TOPICS.IDENTITY, include: "entire transactions" });
2000
+ const token = new PushDrop2(wallet._setup.wallet);
2001
+ const unlockingScriptTemplate = await token.unlock([0, PROTOCOL_ID], "1", "self", "none", true);
2002
+ const tempTx = new Transaction2();
2003
+ const beef = Beef3.fromBinary(BEEF);
2004
+ outputs.forEach((o) => {
2005
+ const [txid2, v] = o.outpoint.split(".");
2006
+ const sourceOutputIndex = Number(v);
2007
+ const sourceTransaction = beef.findTransactionForSigning(txid2);
2008
+ tempTx.addInput({
2009
+ unlockingScriptTemplate,
2010
+ sourceOutputIndex,
2011
+ sourceTransaction
2012
+ });
2013
+ });
2014
+ tempTx.addOutput({
2015
+ lockingScript: Script.fromASM("OP_FALSE OP_RETURN 330123"),
2016
+ satoshis: 0
2017
+ });
2018
+ await tempTx.sign();
2019
+ const response = await wallet._setup.wallet.createAction({
2020
+ inputBEEF: BEEF,
2021
+ description: "revoke registration token",
2022
+ inputs: tempTx.inputs.map((o) => ({
2023
+ inputDescription: "previous registration",
2024
+ outpoint: o.sourceTXID + "." + String(o.sourceOutputIndex),
2025
+ unlockingScript: o.unlockingScript?.toHex()
2026
+ }))
2027
+ });
2028
+ const txid = response.txid;
2029
+ const submitResp = await fetch(`${OVERLAY_URL}/submit`, {
2030
+ method: "POST",
2031
+ headers: {
2032
+ "Content-Type": "application/octet-stream",
2033
+ "X-Topics": JSON.stringify([TOPICS.IDENTITY])
2034
+ },
2035
+ body: new Uint8Array(response.tx)
2036
+ });
2037
+ if (!submitResp.ok) {
2038
+ const errText = await submitResp.text();
2039
+ throw new Error(`Overlay submission failed: ${submitResp.status} \u2014 ${errText}`);
2040
+ }
2041
+ deleteRegistration();
2042
+ return ok({
2043
+ unregistered: true,
2044
+ txid
2045
+ });
2046
+ }
2047
+
2048
+ // src/scripts/overlay/discover.ts
2049
+ init_config();
2050
+ init_output();
2051
+ init_transaction();
2052
+ async function cmdDiscover(args) {
2053
+ let serviceFilter = null;
2054
+ let agentFilter = null;
2055
+ for (let i = 0; i < args.length; i++) {
2056
+ if (args[i] === "--service" && args[i + 1]) serviceFilter = args[++i];
2057
+ else if (args[i] === "--agent" && args[i + 1]) agentFilter = args[++i];
2058
+ }
2059
+ const results = { agents: [], services: [] };
2060
+ if (!serviceFilter) {
2061
+ try {
2062
+ const agentQuery = agentFilter ? { name: agentFilter } : { type: "list" };
2063
+ const agentResult = await lookupOverlay(LOOKUP_SERVICES.AGENTS, agentQuery);
2064
+ if (agentResult.outputs) {
2065
+ for (const output of agentResult.outputs) {
2066
+ try {
2067
+ const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
2068
+ if (data?.type === "identity") {
2069
+ const name = data.name || data.agentName || "Unknown Agent";
2070
+ results.agents.push({ ...data, name, txid });
151
2071
  }
152
- catch { }
2072
+ } catch {
2073
+ }
153
2074
  }
2075
+ }
2076
+ } catch (err) {
2077
+ results.agentError = String(err);
154
2078
  }
155
- catch (err) {
156
- logger?.warn?.('[openclaw-overlay] Auto-advertising failed:', err.message);
157
- }
158
- }
159
- function wakeAgent(text, logger, options = {}) {
160
- const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
161
- const gatewayPort = process['env'].OPENCLAW_GATEWAY_PORT || '18789';
162
- const httpToken = process['env'].OPENCLAW_HOOKS_TOKEN || null;
163
- if (!httpToken)
164
- return;
165
- fetch(`http://localhost:${gatewayPort}/hooks/agent`, {
166
- method: 'POST',
167
- headers: { 'Content-Type': 'application/json', 'x-openclaw-token': httpToken },
168
- body: JSON.stringify({ prompt: text, sessionKey })
169
- }).catch(() => { });
170
- }
171
- async function startBackgroundService(config, api) {
172
- if (serviceRunning)
173
- return;
174
- serviceRunning = true;
175
- abortController = new AbortController();
176
- requestCleanupInterval = setInterval(() => {
177
- if (serviceRunning)
178
- wokenRequests.clear();
179
- }, 5 * 60 * 1000);
180
- applyConfigToEnv(config);
181
- // Start the connection directly as a library call
182
- // This bypasses the child_process detection and is more efficient
183
- cmdConnect((event) => {
184
- if ((event.action === 'queued-for-agent' || event.action === 'already-queued') && event.serviceId) {
185
- const rid = event.id || `${event.from}-${Date.now()}`;
186
- if (wokenRequests.has(rid))
187
- return;
188
- wokenRequests.add(rid);
189
- 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 request\n3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
190
- wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:${rid}` });
2079
+ }
2080
+ if (!agentFilter) {
2081
+ try {
2082
+ const serviceQuery = serviceFilter ? { serviceType: serviceFilter } : {};
2083
+ const serviceResult = await lookupOverlay(LOOKUP_SERVICES.SERVICES, serviceQuery);
2084
+ if (serviceResult.outputs) {
2085
+ for (const output of serviceResult.outputs) {
2086
+ try {
2087
+ const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
2088
+ if (data?.type === "service") {
2089
+ results.services.push({ ...data, txid });
2090
+ }
2091
+ } catch {
2092
+ }
191
2093
  }
192
- if (event.type === 'service-response' && event.action === 'received') {
193
- const wakeText = `📬 Overlay service response received!\n\nService: ${event.serviceId}\nFrom: ${event.from}\nStatus: ${event.status}\n\nFull result:\n${JSON.stringify(event.result, null, 2)}`;
194
- wakeAgent(wakeText, api.logger, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
2094
+ }
2095
+ } catch (err) {
2096
+ results.serviceError = String(err);
2097
+ }
2098
+ }
2099
+ return ok({
2100
+ overlayUrl: OVERLAY_URL,
2101
+ agentCount: results.agents.length,
2102
+ serviceCount: results.services.length,
2103
+ agents: results.agents,
2104
+ services: results.services,
2105
+ ...results.agentError && { agentError: results.agentError },
2106
+ ...results.serviceError && { serviceError: results.serviceError }
2107
+ });
2108
+ }
2109
+
2110
+ // src/scripts/services/request.ts
2111
+ init_config();
2112
+ init_output();
2113
+
2114
+ // src/scripts/payment/build.ts
2115
+ init_config();
2116
+ init_dist();
2117
+ async function getBSVAgentWallet4() {
2118
+ return BSVAgentWallet;
2119
+ }
2120
+ async function buildDirectPayment(recipientPubKey, sats, desc) {
2121
+ if (!/^0[23][0-9a-fA-F]{64}$/.test(recipientPubKey)) {
2122
+ throw new Error("Recipient must be a compressed public key (66 hex chars starting with 02 or 03)");
2123
+ }
2124
+ const BSVAgentWallet2 = await getBSVAgentWallet4();
2125
+ const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
2126
+ try {
2127
+ const result = await wallet.createPayment({
2128
+ to: recipientPubKey,
2129
+ satoshis: sats,
2130
+ description: desc || "agent payment"
2131
+ });
2132
+ return {
2133
+ beef: result.beef,
2134
+ txid: result.txid,
2135
+ satoshis: result.satoshis,
2136
+ derivationPrefix: result.derivationPrefix,
2137
+ derivationSuffix: result.derivationSuffix,
2138
+ senderIdentityKey: result.senderIdentityKey
2139
+ };
2140
+ } finally {
2141
+ await wallet.destroy();
2142
+ }
2143
+ }
2144
+
2145
+ // src/scripts/services/request.ts
2146
+ async function cmdRequestService(targetKey, serviceId, satsStr, inputJsonStr) {
2147
+ if (!targetKey || !serviceId) {
2148
+ return fail("Usage: request-service <identityKey> <serviceId> [sats] [inputJson]");
2149
+ }
2150
+ if (!/^0[23][0-9a-fA-F]{64}$/.test(targetKey)) {
2151
+ return fail("Target must be a compressed public key (66 hex chars, 02/03 prefix)");
2152
+ }
2153
+ const { identityKey, privKey } = await loadIdentity();
2154
+ const sats = parseInt(satsStr || "5", 10);
2155
+ let inputData = null;
2156
+ if (inputJsonStr) {
2157
+ try {
2158
+ inputData = JSON.parse(inputJsonStr);
2159
+ } catch {
2160
+ return fail("inputJson must be valid JSON");
2161
+ }
2162
+ }
2163
+ let paymentData = null;
2164
+ if (sats > 0) {
2165
+ try {
2166
+ const payment = await buildDirectPayment(targetKey, sats, `service-request: ${serviceId}`);
2167
+ paymentData = {
2168
+ beef: payment.beef,
2169
+ txid: payment.txid,
2170
+ satoshis: payment.satoshis,
2171
+ derivationPrefix: payment.derivationPrefix,
2172
+ derivationSuffix: payment.derivationSuffix,
2173
+ senderIdentityKey: payment.senderIdentityKey
2174
+ };
2175
+ } catch (err) {
2176
+ paymentData = { error: String(err.message || err) };
2177
+ }
2178
+ }
2179
+ const requestPayload = {
2180
+ serviceId,
2181
+ ...inputData ? { input: inputData } : {},
2182
+ payment: paymentData,
2183
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString()
2184
+ };
2185
+ const signature = await signRelayMessage(privKey, targetKey, "service-request", requestPayload);
2186
+ const resp = await fetch(`${OVERLAY_URL}/relay/send`, {
2187
+ method: "POST",
2188
+ headers: { "Content-Type": "application/json" },
2189
+ body: JSON.stringify({
2190
+ from: identityKey,
2191
+ to: targetKey,
2192
+ type: "service-request",
2193
+ payload: requestPayload,
2194
+ signature
2195
+ })
2196
+ });
2197
+ if (!resp.ok) {
2198
+ const body = await resp.text();
2199
+ return fail(`Relay send failed (${resp.status}): ${body}`);
2200
+ }
2201
+ const result = await resp.json();
2202
+ return ok({
2203
+ sent: true,
2204
+ requestId: result.id,
2205
+ to: targetKey,
2206
+ serviceId,
2207
+ paymentIncluded: paymentData && !paymentData.error,
2208
+ paymentTxid: paymentData?.txid || null,
2209
+ satoshis: paymentData?.satoshis || 0,
2210
+ note: "Poll for service-response to get the result"
2211
+ });
2212
+ }
2213
+
2214
+ // src/scripts/services/queue.ts
2215
+ init_config();
2216
+ init_output();
2217
+ init_storage();
2218
+ import fs9 from "node:fs";
2219
+ async function cmdServiceQueue() {
2220
+ if (!fs9.existsSync(PATHS.serviceQueue)) {
2221
+ return ok({ pending: [], count: 0 });
2222
+ }
2223
+ const entries = readJsonl(PATHS.serviceQueue);
2224
+ const pending = entries.filter((e) => e.status === "pending");
2225
+ return ok({ pending, count: pending.length, total: entries.length });
2226
+ }
2227
+
2228
+ // src/scripts/services/respond.ts
2229
+ init_config();
2230
+ init_output();
2231
+ import fs10 from "node:fs";
2232
+ init_storage();
2233
+ async function cmdRespondService(requestId, recipientKey, serviceId, resultJson) {
2234
+ if (!requestId || !recipientKey || !serviceId || !resultJson) {
2235
+ return fail("Usage: respond-service <requestId> <recipientKey> <serviceId> <resultJson>");
2236
+ }
2237
+ let result;
2238
+ try {
2239
+ result = JSON.parse(resultJson);
2240
+ } catch {
2241
+ return fail("resultJson must be valid JSON");
2242
+ }
2243
+ const { identityKey, privKey } = await loadIdentity();
2244
+ if (fs10.existsSync(PATHS.serviceQueue)) {
2245
+ const lines = fs10.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
2246
+ const finalStatuses = ["fulfilled", "rejected", "delivery_failed", "failed", "error"];
2247
+ for (const line of lines) {
2248
+ try {
2249
+ const entry = JSON.parse(line);
2250
+ if (entry.requestId === requestId && finalStatuses.includes(entry.status)) {
2251
+ return ok({
2252
+ sent: false,
2253
+ requestId,
2254
+ serviceId,
2255
+ to: recipientKey,
2256
+ message: `Request already processed with status: ${entry.status}`,
2257
+ alreadyProcessed: true,
2258
+ previousStatus: entry.status
2259
+ });
195
2260
  }
196
- }, abortController.signal).catch((err) => {
197
- if (serviceRunning && !abortController?.signal.aborted) {
198
- api.logger?.error?.(`[openclaw-overlay] WebSocket error: ${err.message}`);
2261
+ } catch {
2262
+ }
2263
+ }
2264
+ }
2265
+ const responsePayload = {
2266
+ requestId,
2267
+ serviceId,
2268
+ status: "fulfilled",
2269
+ result
2270
+ };
2271
+ const sig = await signRelayMessage(privKey, recipientKey, "service-response", responsePayload);
2272
+ const resp = await fetch(`${OVERLAY_URL}/relay/send`, {
2273
+ method: "POST",
2274
+ headers: { "Content-Type": "application/json" },
2275
+ body: JSON.stringify({
2276
+ from: identityKey,
2277
+ to: recipientKey,
2278
+ type: "service-response",
2279
+ payload: responsePayload,
2280
+ signature: sig
2281
+ })
2282
+ });
2283
+ if (!resp.ok) {
2284
+ updateServiceQueueStatus(requestId, "failed", {
2285
+ failedAt: Date.now(),
2286
+ error: `Relay send failed: ${resp.status}`
2287
+ });
2288
+ return fail(`Relay send failed: ${resp.status}`);
2289
+ }
2290
+ updateServiceQueueStatus(requestId, "fulfilled", {
2291
+ fulfilledAt: Date.now()
2292
+ });
2293
+ return ok({ sent: true, requestId, serviceId, to: recipientKey });
2294
+ }
2295
+
2296
+ // src/scripts/messaging/connect.ts
2297
+ init_config();
2298
+ init_output();
2299
+ import fs12 from "node:fs";
2300
+
2301
+ // src/scripts/messaging/handlers.ts
2302
+ init_config();
2303
+ import fs11 from "node:fs";
2304
+ init_storage();
2305
+ init_dist();
2306
+ var _sdk4 = null;
2307
+ async function getSdk4() {
2308
+ if (_sdk4) return _sdk4;
2309
+ try {
2310
+ _sdk4 = await import("@bsv/sdk");
2311
+ return _sdk4;
2312
+ } catch {
2313
+ const { fileURLToPath: fileURLToPath2 } = await import("node:url");
2314
+ const path5 = await import("node:path");
2315
+ const os3 = await import("node:os");
2316
+ const __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
2317
+ const candidates = [
2318
+ path5.resolve(__dirname2, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
2319
+ path5.resolve(__dirname2, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
2320
+ path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
2321
+ ];
2322
+ for (const p of candidates) {
2323
+ try {
2324
+ _sdk4 = await import(p);
2325
+ return _sdk4;
2326
+ } catch {
2327
+ }
2328
+ }
2329
+ throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
2330
+ }
2331
+ }
2332
+ async function getBSVAgentWallet5() {
2333
+ return BSVAgentWallet;
2334
+ }
2335
+ async function getNetwork() {
2336
+ const config = await Promise.resolve().then(() => (init_config(), config_exports));
2337
+ return config.NETWORK;
2338
+ }
2339
+ async function verifyAndAcceptPayment(payment, minSats, senderKey, serviceId, ourHash160) {
2340
+ if (!payment) {
2341
+ return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: "no payment" };
2342
+ }
2343
+ if (payment.error) {
2344
+ return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: payment.error };
2345
+ }
2346
+ if (!payment.beef || !payment.satoshis) {
2347
+ return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: "missing beef or satoshis" };
2348
+ }
2349
+ if (payment.satoshis < minSats) {
2350
+ return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `insufficient payment: ${payment.satoshis} < ${minSats}` };
2351
+ }
2352
+ const BSVAgentWallet2 = await getBSVAgentWallet5();
2353
+ const network = await getNetwork();
2354
+ const wallet = await BSVAgentWallet2.load({ network, storageDir: WALLET_DIR });
2355
+ try {
2356
+ const verifyResult = await wallet.verifyPayment({ beef: payment.beef });
2357
+ if (!verifyResult.valid) {
2358
+ await wallet.destroy();
2359
+ return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `verification failed: ${verifyResult.errors.join(", ")}` };
2360
+ }
2361
+ const acceptResult = await wallet.acceptPayment({
2362
+ beef: payment.beef,
2363
+ derivationPrefix: payment.derivationPrefix,
2364
+ derivationSuffix: payment.derivationSuffix,
2365
+ senderIdentityKey: payment.senderIdentityKey,
2366
+ description: `Payment for ${serviceId}`
2367
+ });
2368
+ await wallet.destroy();
2369
+ if (!acceptResult.accepted) {
2370
+ return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: "wallet rejected payment" };
2371
+ }
2372
+ return {
2373
+ accepted: true,
2374
+ txid: payment.txid,
2375
+ satoshis: payment.satoshis,
2376
+ outputIndex: 0,
2377
+ walletAccepted: true,
2378
+ error: null
2379
+ };
2380
+ } catch (err) {
2381
+ await wallet.destroy();
2382
+ return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: err.message };
2383
+ }
2384
+ }
2385
+ async function queueForAgent(msg, identityKey, privKey, serviceId) {
2386
+ if (fs11.existsSync(PATHS.serviceQueue)) {
2387
+ const lines = fs11.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
2388
+ for (const line of lines) {
2389
+ try {
2390
+ const entry = JSON.parse(line);
2391
+ if (entry.requestId === msg.id) {
2392
+ return {
2393
+ id: msg.id,
2394
+ type: "service-request",
2395
+ serviceId,
2396
+ action: entry.status === "pending" ? "already-queued" : `already-${entry.status}`,
2397
+ paymentAccepted: true,
2398
+ paymentTxid: entry.paymentTxid,
2399
+ satoshisReceived: entry.satoshisReceived,
2400
+ from: msg.from,
2401
+ ack: true
2402
+ };
199
2403
  }
2404
+ } catch {
2405
+ }
2406
+ }
2407
+ }
2408
+ const sdk = await getSdk4();
2409
+ const payment = msg.payload?.payment;
2410
+ const input = msg.payload?.input || msg.payload;
2411
+ const walletIdentity = loadWalletIdentity();
2412
+ const ourHash160 = sdk.Hash.hash160(sdk.PrivateKey.fromHex(walletIdentity.rootKeyHex).toPublicKey().encode(true));
2413
+ const serviceDefinition = serviceManager.registry.get(serviceId);
2414
+ let minPrice = 5;
2415
+ if (serviceDefinition) {
2416
+ minPrice = serviceDefinition.defaultPrice;
2417
+ const validation = serviceManager.validate(serviceId, input);
2418
+ if (!validation.valid) {
2419
+ const rejectPayload = {
2420
+ requestId: msg.id,
2421
+ serviceId,
2422
+ status: "rejected",
2423
+ reason: `Input validation failed: ${validation.error}`
2424
+ };
2425
+ const sig = await signRelayMessage(privKey, msg.from, "service-response", rejectPayload);
2426
+ await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
2427
+ method: "POST",
2428
+ headers: { "Content-Type": "application/json" },
2429
+ body: JSON.stringify({ from: identityKey, to: msg.from, type: "service-response", payload: rejectPayload, signature: sig })
2430
+ });
2431
+ const rejectedEntry = {
2432
+ status: "rejected",
2433
+ requestId: msg.id,
2434
+ serviceId,
2435
+ from: msg.from,
2436
+ identityKey,
2437
+ input,
2438
+ paymentTxid: null,
2439
+ satoshisReceived: 0,
2440
+ walletAccepted: false,
2441
+ error: validation.error,
2442
+ _ts: Date.now()
2443
+ };
2444
+ appendToJsonl(PATHS.serviceQueue, rejectedEntry);
2445
+ return {
2446
+ id: msg.id,
2447
+ type: "service-request",
2448
+ serviceId,
2449
+ action: "rejected",
2450
+ reason: validation.error || "input validation failed",
2451
+ from: msg.from,
2452
+ ack: true
2453
+ };
2454
+ }
2455
+ } else {
2456
+ const services = loadServices();
2457
+ const svc = services.find((s) => s.serviceId === serviceId);
2458
+ minPrice = svc?.priceSats || 5;
2459
+ }
2460
+ const payResult = await verifyAndAcceptPayment(payment, minPrice, msg.from, serviceId, ourHash160);
2461
+ if (!payResult.accepted) {
2462
+ const rejectPayload = { requestId: msg.id, serviceId, status: "rejected", reason: `Payment rejected: ${payResult.error}` };
2463
+ const sig = await signRelayMessage(privKey, msg.from, "service-response", rejectPayload);
2464
+ await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
2465
+ method: "POST",
2466
+ headers: { "Content-Type": "application/json" },
2467
+ body: JSON.stringify({ from: identityKey, to: msg.from, type: "service-response", payload: rejectPayload, signature: sig })
200
2468
  });
2469
+ const rejectedEntry = {
2470
+ status: "rejected",
2471
+ requestId: msg.id,
2472
+ serviceId,
2473
+ from: msg.from,
2474
+ identityKey,
2475
+ input,
2476
+ paymentTxid: null,
2477
+ satoshisReceived: 0,
2478
+ walletAccepted: false,
2479
+ error: payResult.error,
2480
+ _ts: Date.now()
2481
+ };
2482
+ appendToJsonl(PATHS.serviceQueue, rejectedEntry);
2483
+ return { id: msg.id, type: "service-request", serviceId, action: "rejected", reason: payResult.error || "payment rejected", from: msg.from, ack: true };
2484
+ }
2485
+ const queueEntry = {
2486
+ status: "pending",
2487
+ requestId: msg.id,
2488
+ serviceId,
2489
+ from: msg.from,
2490
+ identityKey,
2491
+ input,
2492
+ paymentTxid: payResult.txid,
2493
+ satoshisReceived: payResult.satoshis,
2494
+ walletAccepted: payResult.walletAccepted,
2495
+ _ts: Date.now()
2496
+ };
2497
+ appendToJsonl(PATHS.serviceQueue, queueEntry);
2498
+ return {
2499
+ id: msg.id,
2500
+ type: "service-request",
2501
+ serviceId,
2502
+ action: "queued-for-agent",
2503
+ paymentAccepted: true,
2504
+ paymentTxid: payResult.txid,
2505
+ satoshisReceived: payResult.satoshis,
2506
+ from: msg.from,
2507
+ ack: true
2508
+ };
201
2509
  }
202
- function stopBackgroundService() {
203
- serviceRunning = false;
204
- if (abortController) {
205
- abortController.abort();
206
- abortController = null;
207
- }
208
- if (requestCleanupInterval) {
209
- clearInterval(requestCleanupInterval);
210
- requestCleanupInterval = null;
211
- }
212
- wokenRequests.clear();
213
- if (autoImportInterval) {
214
- clearInterval(autoImportInterval);
215
- autoImportInterval = null;
216
- }
217
- }
218
- export function register(api) {
219
- const version = "0.8.15";
220
- if (isInitialized)
221
- return;
222
- isInitialized = true;
223
- api.logger?.info?.(`[openclaw-overlay] Initializing Plugin v${version}`);
224
- const entries = api.getConfig?.()?.plugins?.entries || {};
225
- const entry = entries['openclaw-overlay-plugin'] || entries['openclaw-overlay'] || {};
226
- const pluginConfig = { ...entry, ...(entry.config || {}), ...(api.config || {}) };
227
- // 1. Tool
228
- api.registerTool({
229
- name: "overlay",
230
- description: "Access the BSV agent marketplace",
231
- parameters: {
232
- type: "object",
233
- properties: {
234
- action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
235
- service: { type: "string" },
236
- input: { type: "object" },
237
- identityKey: { type: "string" },
238
- sats: { type: "number" },
239
- requestId: { type: "string" },
240
- recipientKey: { type: "string" },
241
- serviceId: { type: "string" },
242
- result: { type: "object" }
243
- },
244
- required: ["action"]
245
- },
246
- async execute(_id, params) {
247
- try {
248
- return await executeOverlayAction(params, pluginConfig, api);
249
- }
250
- catch (error) {
251
- return { content: [{ type: "text", text: `Error: ${error.message}` }] };
252
- }
2510
+ async function processMessage(msg, identityKey, privKey) {
2511
+ const sigCheck = msg.signature ? await verifyRelaySignature(msg.from, msg.to, msg.type, msg.payload, msg.signature) : { valid: null };
2512
+ if (msg.type === "service-request" && sigCheck.valid !== true) {
2513
+ console.error(JSON.stringify({ event: "signature-rejected", type: msg.type, from: msg.from, reason: sigCheck.reason || "missing signature" }));
2514
+ return {
2515
+ id: msg.id,
2516
+ type: msg.type,
2517
+ from: msg.from,
2518
+ action: "rejected",
2519
+ reason: "invalid-signature",
2520
+ signatureValid: sigCheck.valid,
2521
+ ack: true
2522
+ };
2523
+ }
2524
+ if (msg.type === "ping") {
2525
+ const pongPayload = {
2526
+ text: "pong",
2527
+ inReplyTo: msg.id,
2528
+ originalText: msg.payload?.text || null
2529
+ };
2530
+ const pongSig = await signRelayMessage(privKey, msg.from, "pong", pongPayload);
2531
+ await fetch(`${OVERLAY_URL}/relay/send`, {
2532
+ method: "POST",
2533
+ headers: { "Content-Type": "application/json" },
2534
+ body: JSON.stringify({
2535
+ from: identityKey,
2536
+ to: msg.from,
2537
+ type: "pong",
2538
+ payload: pongPayload,
2539
+ signature: pongSig
2540
+ })
2541
+ });
2542
+ return { id: msg.id, type: "ping", action: "replied-pong", from: msg.from, ack: true };
2543
+ }
2544
+ if (msg.type === "service-request") {
2545
+ const serviceId = msg.payload?.serviceId;
2546
+ if (process["env"].AGENT_ROUTED === "true") {
2547
+ return await queueForAgent(msg, identityKey, privKey, serviceId);
2548
+ }
2549
+ return await queueForAgent(msg, identityKey, privKey, serviceId);
2550
+ }
2551
+ if (msg.type === "pong") {
2552
+ return {
2553
+ id: msg.id,
2554
+ type: "pong",
2555
+ action: "received",
2556
+ from: msg.from,
2557
+ text: msg.payload?.text,
2558
+ inReplyTo: msg.payload?.inReplyTo,
2559
+ ack: true
2560
+ };
2561
+ }
2562
+ if (msg.type === "service-response") {
2563
+ const serviceId = msg.payload?.serviceId;
2564
+ const status = msg.payload?.status;
2565
+ const result = msg.payload?.result;
2566
+ return {
2567
+ id: msg.id,
2568
+ type: "service-response",
2569
+ action: "received",
2570
+ from: msg.from,
2571
+ serviceId,
2572
+ status,
2573
+ result,
2574
+ requestId: msg.payload?.requestId,
2575
+ direction: "incoming-response",
2576
+ ack: true
2577
+ };
2578
+ }
2579
+ return {
2580
+ id: msg.id,
2581
+ type: msg.type,
2582
+ from: msg.from,
2583
+ payload: msg.payload,
2584
+ signatureValid: sigCheck.valid,
2585
+ action: "unhandled",
2586
+ ack: false
2587
+ };
2588
+ }
2589
+
2590
+ // src/scripts/messaging/connect.ts
2591
+ init_storage();
2592
+ import debug2 from "debug";
2593
+ var log2 = debug2("openclaw:plugin:overlay:connect");
2594
+ async function cmdConnect(onMessage, signal) {
2595
+ let WebSocketClient;
2596
+ try {
2597
+ const ws = await import("ws");
2598
+ WebSocketClient = ws.default || ws.WebSocket || ws;
2599
+ } catch {
2600
+ return fail("WebSocket client not available. Install it: npm install ws");
2601
+ }
2602
+ const { identityKey, privKey } = await loadIdentity();
2603
+ const wsUrl = OVERLAY_URL.replace(/^http/, "ws") + "/relay/subscribe?identity=" + identityKey;
2604
+ log2("Connecting to WebSocket relay: %s", wsUrl);
2605
+ let reconnectDelay = 1e3;
2606
+ let shouldReconnect = true;
2607
+ let currentWs = null;
2608
+ function shutdown() {
2609
+ shouldReconnect = false;
2610
+ if (currentWs) {
2611
+ try {
2612
+ currentWs.close();
2613
+ } catch {
2614
+ }
2615
+ }
2616
+ if (!onMessage) {
2617
+ process.exit(0);
2618
+ }
2619
+ }
2620
+ if (!onMessage) {
2621
+ process.on("SIGINT", shutdown);
2622
+ process.on("SIGTERM", shutdown);
2623
+ }
2624
+ if (signal) {
2625
+ signal.addEventListener("abort", () => {
2626
+ shouldReconnect = false;
2627
+ if (currentWs) {
2628
+ try {
2629
+ currentWs.close();
2630
+ } catch {
253
2631
  }
2632
+ }
254
2633
  });
255
- // 2. Command
256
- api.registerCommand({
257
- name: "overlay",
258
- description: "BSV Overlay Marketplace commands",
259
- acceptsArgs: true,
260
- handler: async (ctx) => {
2634
+ }
2635
+ function connect() {
2636
+ if (signal?.aborted) return;
2637
+ const ws = new WebSocketClient(wsUrl);
2638
+ currentWs = ws;
2639
+ ws.on("open", () => {
2640
+ log2("WebSocket connection established!");
2641
+ reconnectDelay = 1e3;
2642
+ const logMsg = { event: "connected", identity: identityKey, overlay: OVERLAY_URL };
2643
+ if (onMessage) onMessage(logMsg);
2644
+ else console.error(JSON.stringify(logMsg));
2645
+ });
2646
+ ws.on("message", async (data) => {
2647
+ log2("Incoming WebSocket message received");
2648
+ try {
2649
+ const envelope = JSON.parse(data.toString());
2650
+ log2("Message type: %s", envelope.type);
2651
+ if (envelope.type === "message") {
2652
+ const result = await processMessage(envelope.message, identityKey, privKey);
2653
+ log2("Processed message: %s", result.id);
2654
+ if (onMessage) onMessage(result);
2655
+ else console.log(JSON.stringify(result));
2656
+ ensureStateDir();
2657
+ try {
2658
+ fs12.appendFileSync(PATHS.notifications, JSON.stringify({ ...result, _ts: Date.now() }) + "\n");
2659
+ } catch {
2660
+ }
2661
+ if (result.ack) {
261
2662
  try {
262
- api.logger?.info?.(`[openclaw-overlay] Command received with args: ${JSON.stringify(ctx.args)} (type: ${typeof ctx.args})`);
263
- const args = Array.isArray(ctx.args) ? ctx.args : (typeof ctx.args === 'string' ? ctx.args.split(' ').filter(Boolean) : []);
264
- const action = args[0] || 'status';
265
- const result = await executeOverlayAction({ action }, pluginConfig, api);
266
- return { text: `**Overlay ${action.toUpperCase()}**\n\n${typeof result === 'string' ? result : JSON.stringify(result, null, 2)}` };
267
- }
268
- catch (error) {
269
- return { text: `❌ Error: ${error.message}` };
2663
+ await fetch(OVERLAY_URL + "/relay/ack", {
2664
+ method: "POST",
2665
+ headers: { "Content-Type": "application/json" },
2666
+ body: JSON.stringify({ identity: identityKey, messageIds: [result.id] })
2667
+ });
2668
+ } catch (ackErr) {
2669
+ const log4 = { event: "ack-error", id: result.id, message: String(ackErr) };
2670
+ if (onMessage) onMessage(log4);
2671
+ else console.error(JSON.stringify(log4));
270
2672
  }
2673
+ }
271
2674
  }
2675
+ if (envelope.type === "service-announced") {
2676
+ const svc = envelope.service || {};
2677
+ const announcement = {
2678
+ event: "service-announced",
2679
+ serviceId: svc.serviceId,
2680
+ name: svc.name,
2681
+ description: svc.description,
2682
+ priceSats: svc.pricingSats,
2683
+ provider: svc.identityKey,
2684
+ txid: envelope.txid,
2685
+ _ts: Date.now()
2686
+ };
2687
+ if (onMessage) onMessage(announcement);
2688
+ else console.log(JSON.stringify(announcement));
2689
+ ensureStateDir();
2690
+ try {
2691
+ fs12.appendFileSync(PATHS.notifications, JSON.stringify(announcement) + "\n");
2692
+ } catch {
2693
+ }
2694
+ }
2695
+ } catch (err) {
2696
+ const log4 = { event: "process-error", message: String(err) };
2697
+ if (onMessage) onMessage(log4);
2698
+ else console.error(JSON.stringify(log4));
2699
+ }
272
2700
  });
273
- // 3. Service
274
- api.registerService({
275
- id: "openclaw-overlay-relay",
276
- start: async () => {
277
- try {
278
- await initializeServiceSystem();
279
- }
280
- catch { }
281
- await startBackgroundService(pluginConfig, api);
282
- await startAutoImport(pluginConfig, api);
283
- },
284
- stop: () => stopBackgroundService()
2701
+ ws.on("close", () => {
2702
+ currentWs = null;
2703
+ if (shouldReconnect && !signal?.aborted) {
2704
+ const log4 = { event: "disconnected", reconnectMs: reconnectDelay };
2705
+ if (onMessage) onMessage(log4);
2706
+ else console.error(JSON.stringify(log4));
2707
+ setTimeout(connect, reconnectDelay);
2708
+ reconnectDelay = Math.min(reconnectDelay * 2, 3e4);
2709
+ }
285
2710
  });
286
- // 4. CLI
287
- api.registerCli(({ program }) => {
288
- const overlay = program.command("overlay").description("BSV Overlay Network management");
289
- overlay.command("status").description("Show identity and balance").action(async () => {
290
- applyConfigToEnv(pluginConfig);
291
- const res = await cmdStatus();
292
- console.log(JSON.stringify(res.data, null, 2));
293
- });
294
- overlay.command("balance").description("Show current wallet balance").action(async () => {
295
- applyConfigToEnv(pluginConfig);
296
- const res = await cmdBalance();
297
- console.log(JSON.stringify(res.data, null, 2));
298
- });
299
- overlay.command("discover").description("Find agents and services").option("-s, --service <type>", "Filter by service type").option("-a, --agent <name>", "Filter by agent name").action(async (options) => {
300
- applyConfigToEnv(pluginConfig);
301
- const args = [];
302
- if (options.service)
303
- args.push('--service', options.service);
304
- if (options.agent)
305
- args.push('--agent', options.agent);
306
- const res = await cmdDiscover(args);
307
- console.log(JSON.stringify(res.data, null, 2));
308
- });
309
- }, { commands: ["overlay"] });
2711
+ ws.on("error", (err) => {
2712
+ const log4 = { event: "error", message: err.message };
2713
+ if (onMessage) onMessage(log4);
2714
+ else console.error(JSON.stringify(log4));
2715
+ });
2716
+ }
2717
+ connect();
2718
+ return new Promise((resolve) => {
2719
+ if (signal) {
2720
+ signal.addEventListener("abort", () => resolve());
2721
+ }
2722
+ });
310
2723
  }
311
- async function executeOverlayAction(params, config, api) {
312
- const { action } = params;
313
- log('Executing action: %s with params: %O', action, params);
2724
+
2725
+ // index.ts
2726
+ init_output();
2727
+ import debug3 from "debug";
2728
+ var log3 = debug3("openclaw:plugin:overlay");
2729
+ var isInitialized = false;
2730
+ var serviceRunning = false;
2731
+ var abortController = null;
2732
+ var autoImportInterval = null;
2733
+ var knownTxids = /* @__PURE__ */ new Set();
2734
+ var wokenRequests = /* @__PURE__ */ new Set();
2735
+ var requestCleanupInterval = null;
2736
+ var BUDGET_FILE = "daily-spending.json";
2737
+ function getBudgetPath(walletDir) {
2738
+ return path4.join(walletDir, BUDGET_FILE);
2739
+ }
2740
+ function loadDailySpending(walletDir) {
2741
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2742
+ const budgetPath = getBudgetPath(walletDir);
2743
+ try {
2744
+ if (fs13.existsSync(budgetPath)) {
2745
+ const data = JSON.parse(fs13.readFileSync(budgetPath, "utf-8"));
2746
+ if (data.date === today) return data;
2747
+ }
2748
+ } catch {
2749
+ }
2750
+ return { date: today, totalSats: 0, transactions: [] };
2751
+ }
2752
+ function recordSpend(walletDir, sats, service, provider) {
2753
+ const spending = loadDailySpending(walletDir);
2754
+ spending.totalSats += sats;
2755
+ spending.transactions.push({ ts: Date.now(), sats, service, provider });
2756
+ fs13.writeFileSync(getBudgetPath(walletDir), JSON.stringify(spending, null, 2));
2757
+ }
2758
+ function checkBudget(walletDir, requestedSats, dailyLimit) {
2759
+ const spending = loadDailySpending(walletDir);
2760
+ const remaining = dailyLimit - spending.totalSats;
2761
+ return {
2762
+ allowed: remaining >= requestedSats,
2763
+ remaining,
2764
+ spent: spending.totalSats
2765
+ };
2766
+ }
2767
+ function applyConfigToEnv(config) {
2768
+ process["env"].BSV_WALLET_DIR = config.walletDir || path4.join(os2.homedir(), ".openclaw", "bsv-wallet");
2769
+ process["env"].OVERLAY_URL = config.overlayUrl || "https://clawoverlay.com";
2770
+ process["env"].BSV_NETWORK = config.network || process["env"].BSV_NETWORK || "mainnet";
2771
+ process["env"].BSV_ARC_URL = config.arcUrl || (process["env"].BSV_NETWORK === "testnet" ? "https://testnet.arc.gorillapool.io" : "https://arc.gorillapool.io");
2772
+ process["env"].AGENT_NAME = config.agentName || "openclaw-agent";
2773
+ setNoExit(true);
2774
+ }
2775
+ function wakeAgent(text, logger, port, token, options = {}) {
2776
+ const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
2777
+ if (!token) return;
2778
+ const target = `http://localhost:${port}/hooks/agent`;
2779
+ fetch(target, {
2780
+ method: "POST",
2781
+ headers: { "Content-Type": "application/json", "x-openclaw-token": token },
2782
+ body: JSON.stringify({ prompt: text, sessionKey })
2783
+ }).catch(() => {
2784
+ });
2785
+ }
2786
+ async function startAutoImport(config, api, port, token) {
2787
+ try {
314
2788
  applyConfigToEnv(config);
315
- switch (action) {
316
- case "request": {
317
- const { service, input } = params;
318
- const walletDir = config?.walletDir || path.join(os.homedir(), '.openclaw', 'bsv-wallet');
319
- const discoverOutput = await cmdDiscover(['--service', service]);
320
- const providers = discoverOutput.data.services;
321
- if (!providers || providers.length === 0)
322
- throw new Error(`No providers found for ${service}`);
323
- providers.sort((a, b) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
324
- const best = providers[0];
325
- const price = best.pricing?.amountSats || 0;
326
- const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5000);
327
- if (!budget.allowed)
328
- throw new Error("Budget exceeded");
329
- const output = await cmdRequestService(best.identityKey, service, price.toString(), input ? JSON.stringify(input) : undefined);
330
- recordSpend(walletDir, price, service, best.name);
331
- return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
332
- }
333
- case "discover": return (await cmdDiscover(params.service ? ['--service', params.service] : [])).data;
334
- case "balance": return (await cmdBalance()).data;
335
- case "status": {
336
- const identity = await cmdIdentity();
337
- const balance = await cmdBalance();
338
- return { identity: identity.data, balance: balance.data };
339
- }
340
- case "onboard": {
341
- await cmdSetup();
342
- const addr = (await cmdAddress()).data.address;
343
- const bal = (await cmdBalance()).data.walletBalance;
344
- if (bal < 1000)
345
- return { funded: false, address: addr, message: "Please fund 1000 sats." };
346
- await cmdRegister();
347
- return { funded: true, registered: true, message: "Onboarding complete." };
348
- }
349
- case "pending-requests": return (await cmdServiceQueue()).data;
350
- case "fulfill": {
351
- const { requestId, recipientKey, serviceId, result } = params;
352
- return (await cmdRespondService(requestId, recipientKey, serviceId, JSON.stringify(result))).data;
2789
+ const addrOutput = await cmdAddress();
2790
+ if (!addrOutput.success) return;
2791
+ const address = addrOutput.data?.address;
2792
+ if (!address) return;
2793
+ autoImportInterval = setInterval(async () => {
2794
+ try {
2795
+ const network = process["env"].BSV_NETWORK === "testnet" ? "test" : "main";
2796
+ const controller = new AbortController();
2797
+ const timeout = setTimeout(() => controller.abort(), 15e3);
2798
+ const resp = await fetch(`https://api.whatsonchain.com/v1/bsv/${network}/address/${address}/unspent/all`, { signal: controller.signal });
2799
+ clearTimeout(timeout);
2800
+ if (!resp.ok) return;
2801
+ const data = await resp.json();
2802
+ const utxos = data.result || [];
2803
+ for (const utxo of utxos) {
2804
+ const key = `${utxo.tx_hash}:${utxo.tx_pos}`;
2805
+ if (knownTxids.has(key)) continue;
2806
+ if (utxo.value < 200) continue;
2807
+ api.logger?.info?.(`[openclaw-overlay] Auto-importing UTXO: ${utxo.tx_hash}:${utxo.tx_pos} (${utxo.value} sats)`);
2808
+ try {
2809
+ applyConfigToEnv(config);
2810
+ const importOutput = await cmdImport(utxo.tx_hash, String(utxo.tx_pos));
2811
+ if (importOutput.success) {
2812
+ knownTxids.add(key);
2813
+ api.logger?.info?.(`[openclaw-overlay] Auto-imported ${utxo.value} sats from ${utxo.tx_hash}`);
2814
+ wakeAgent(`\u{1F4B0} **Wallet Funded!**
2815
+
2816
+ Auto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...
2817
+
2818
+ Notify the user their wallet has been funded.`, api.logger, port, token, { sessionKey: "hook:openclaw-overlay:import" });
2819
+ try {
2820
+ const regPath = path4.join(os2.homedir(), ".openclaw", "openclaw-overlay", "registration.json");
2821
+ if (!fs13.existsSync(regPath)) {
2822
+ api.logger?.info?.("[openclaw-overlay] Not yet registered \u2014 auto-registering...");
2823
+ applyConfigToEnv(config);
2824
+ const regOutput = await cmdRegister();
2825
+ if (regOutput.success) {
2826
+ api.logger?.info?.("[openclaw-overlay] Auto-registered on overlay network!");
2827
+ await autoAdvertiseServices(config, api.logger);
2828
+ }
2829
+ }
2830
+ } catch (err) {
2831
+ api.logger?.warn?.("[openclaw-overlay] Auto-registration failed:", err.message);
2832
+ }
2833
+ }
2834
+ } catch (err) {
2835
+ knownTxids.add(key);
2836
+ }
353
2837
  }
354
- case "unregister": return (await cmdUnregister()).data;
355
- default: throw new Error(`Unknown action: ${action}`);
2838
+ } catch (err) {
2839
+ }
2840
+ }, 3e4);
2841
+ } catch (err) {
2842
+ api.logger?.warn?.("[openclaw-overlay] Auto-import setup failed:", err.message);
2843
+ }
2844
+ }
2845
+ async function autoAdvertiseServices(config, logger) {
2846
+ try {
2847
+ let servicesToAdvertise = [];
2848
+ if (config?.services && Array.isArray(config.services)) {
2849
+ servicesToAdvertise = config.services;
2850
+ }
2851
+ if (servicesToAdvertise.length === 0) return;
2852
+ for (const serviceId of servicesToAdvertise) {
2853
+ const serviceInfo = serviceManager.registry.get(serviceId);
2854
+ if (!serviceInfo) continue;
2855
+ try {
2856
+ const { cmdAdvertise: cmdAdvertise2 } = await Promise.resolve().then(() => (init_services(), services_exports));
2857
+ applyConfigToEnv(config);
2858
+ await cmdAdvertise2(serviceId, serviceInfo.name, String(serviceInfo.defaultPrice), serviceInfo.description);
2859
+ } catch {
2860
+ }
2861
+ }
2862
+ } catch (err) {
2863
+ logger?.warn?.("[openclaw-overlay] Auto-advertising failed:", err.message);
2864
+ }
2865
+ }
2866
+ async function startBackgroundService(config, api, port, token) {
2867
+ if (serviceRunning) return;
2868
+ serviceRunning = true;
2869
+ abortController = new AbortController();
2870
+ requestCleanupInterval = setInterval(() => {
2871
+ if (serviceRunning) wokenRequests.clear();
2872
+ }, 5 * 60 * 1e3);
2873
+ applyConfigToEnv(config);
2874
+ cmdConnect((event) => {
2875
+ if ((event.action === "queued-for-agent" || event.action === "already-queued") && event.serviceId) {
2876
+ const rid = event.id || `${event.from}-${Date.now()}`;
2877
+ if (wokenRequests.has(rid)) return;
2878
+ wokenRequests.add(rid);
2879
+ const wakeText = `\u26A1 Incoming overlay service request!
2880
+
2881
+ Service: ${event.serviceId}
2882
+ From: ${event.from}
2883
+ Paid: ${event.satoshisReceived || "?"} sats
2884
+
2885
+ Fulfill it now:
2886
+ 1. overlay({ action: "pending-requests" })
2887
+ 2. Process the request
2888
+ 3. overlay({ action: "fulfill", requestId: "${event.id}", recipientKey: "${event.from}", serviceId: "${event.serviceId}", result: { ... } })`;
2889
+ wakeAgent(wakeText, api.logger, port, token, { sessionKey: `hook:openclaw-overlay:${rid}` });
2890
+ }
2891
+ if (event.type === "service-response" && event.action === "received") {
2892
+ const wakeText = `\u{1F4EC} Overlay service response received!
2893
+
2894
+ Service: ${event.serviceId}
2895
+ From: ${event.from}
2896
+ Status: ${event.status}
2897
+
2898
+ Full result:
2899
+ ${JSON.stringify(event.result, null, 2)}`;
2900
+ wakeAgent(wakeText, api.logger, port, token, { sessionKey: `hook:openclaw-overlay:resp-${event.requestId || Date.now()}` });
356
2901
  }
2902
+ }, abortController.signal).catch((err) => {
2903
+ if (serviceRunning && !abortController?.signal.aborted) {
2904
+ api.logger?.error?.(`[openclaw-overlay] WebSocket error: ${err.message}`);
2905
+ }
2906
+ });
2907
+ }
2908
+ function stopBackgroundService() {
2909
+ serviceRunning = false;
2910
+ if (abortController) {
2911
+ abortController.abort();
2912
+ abortController = null;
2913
+ }
2914
+ if (requestCleanupInterval) {
2915
+ clearInterval(requestCleanupInterval);
2916
+ requestCleanupInterval = null;
2917
+ }
2918
+ wokenRequests.clear();
2919
+ if (autoImportInterval) {
2920
+ clearInterval(autoImportInterval);
2921
+ autoImportInterval = null;
2922
+ }
2923
+ }
2924
+ function register(api) {
2925
+ const version = "0.8.17";
2926
+ if (isInitialized) return;
2927
+ isInitialized = true;
2928
+ api.logger?.info?.(`[openclaw-overlay] Initializing Plugin v${version}`);
2929
+ const entries = api.getConfig?.()?.plugins?.entries || {};
2930
+ const entry = entries["openclaw-overlay-plugin"] || entries["openclaw-overlay"] || {};
2931
+ const pluginConfig = { ...entry, ...entry.config || {}, ...api.config || {} };
2932
+ api.registerTool({
2933
+ name: "overlay",
2934
+ description: "Access the BSV agent marketplace",
2935
+ parameters: {
2936
+ type: "object",
2937
+ properties: {
2938
+ action: { type: "string", enum: ["request", "discover", "balance", "status", "pay", "onboard", "pending-requests", "fulfill", "unregister"] },
2939
+ service: { type: "string" },
2940
+ input: { type: "object" },
2941
+ identityKey: { type: "string" },
2942
+ sats: { type: "number" },
2943
+ requestId: { type: "string" },
2944
+ recipientKey: { type: "string" },
2945
+ serviceId: { type: "string" },
2946
+ result: { type: "object" }
2947
+ },
2948
+ required: ["action"]
2949
+ },
2950
+ async execute(_id, params) {
2951
+ log3("Executing tool action: %s with params: %O", params.action, params);
2952
+ try {
2953
+ return await executeOverlayAction(params, pluginConfig, api);
2954
+ } catch (error) {
2955
+ return { content: [{ type: "text", text: `Error: ${error.message}` }] };
2956
+ }
2957
+ }
2958
+ });
2959
+ api.registerCommand({
2960
+ name: "overlay",
2961
+ description: "BSV Overlay Marketplace commands",
2962
+ acceptsArgs: true,
2963
+ handler: async (ctx) => {
2964
+ try {
2965
+ api.logger?.info?.(`[openclaw-overlay] Command received with args: ${JSON.stringify(ctx.args)} (type: ${typeof ctx.args})`);
2966
+ const args = Array.isArray(ctx.args) ? ctx.args : typeof ctx.args === "string" ? ctx.args.split(" ").filter(Boolean) : [];
2967
+ const action = args[0] || "status";
2968
+ const result = await executeOverlayAction({ action }, pluginConfig, api);
2969
+ return { text: `**Overlay ${action.toUpperCase()}**
2970
+
2971
+ ${typeof result === "string" ? result : JSON.stringify(result, null, 2)}` };
2972
+ } catch (error) {
2973
+ return { text: `\u274C Error: ${error.message}` };
2974
+ }
2975
+ }
2976
+ });
2977
+ api.registerService({
2978
+ id: "openclaw-overlay-relay",
2979
+ start: async () => {
2980
+ try {
2981
+ await initializeServiceSystem();
2982
+ } catch {
2983
+ }
2984
+ const gatewayPort = process["env"].OPENCLAW_GATEWAY_PORT || "18789";
2985
+ const httpToken = process["env"].OPENCLAW_HOOKS_TOKEN || "";
2986
+ await startBackgroundService(pluginConfig, api, gatewayPort, httpToken);
2987
+ await startAutoImport(pluginConfig, api, gatewayPort, httpToken);
2988
+ },
2989
+ stop: () => stopBackgroundService()
2990
+ });
2991
+ api.registerCli(({ program }) => {
2992
+ const overlay = program.command("overlay").description("BSV Overlay Network management");
2993
+ overlay.command("status").description("Show identity and balance").action(async () => {
2994
+ applyConfigToEnv(pluginConfig);
2995
+ const res = await cmdStatus();
2996
+ console.log(JSON.stringify(res.data, null, 2));
2997
+ });
2998
+ overlay.command("balance").description("Show current wallet balance").action(async () => {
2999
+ applyConfigToEnv(pluginConfig);
3000
+ const res = await cmdBalance();
3001
+ console.log(JSON.stringify(res.data, null, 2));
3002
+ });
3003
+ overlay.command("discover").description("Find agents and services").option("-s, --service <type>", "Filter by service type").option("-a, --agent <name>", "Filter by agent name").action(async (options) => {
3004
+ applyConfigToEnv(pluginConfig);
3005
+ const args = [];
3006
+ if (options.service) args.push("--service", options.service);
3007
+ if (options.agent) args.push("--agent", options.agent);
3008
+ const res = await cmdDiscover(args);
3009
+ console.log(JSON.stringify(res.data, null, 2));
3010
+ });
3011
+ }, { commands: ["overlay"] });
3012
+ }
3013
+ async function executeOverlayAction(params, config, api) {
3014
+ const { action } = params;
3015
+ applyConfigToEnv(config);
3016
+ switch (action) {
3017
+ case "request": {
3018
+ const { service, input } = params;
3019
+ const walletDir = config?.walletDir || path4.join(os2.homedir(), ".openclaw", "bsv-wallet");
3020
+ const discoverOutput = await cmdDiscover(["--service", service]);
3021
+ const providers = discoverOutput.data.services;
3022
+ if (!providers || providers.length === 0) throw new Error(`No providers found for ${service}`);
3023
+ providers.sort((a, b) => (a.pricing?.amountSats || 0) - (b.pricing?.amountSats || 0));
3024
+ const best = providers[0];
3025
+ const price = best.pricing?.amountSats || 0;
3026
+ const budget = checkBudget(walletDir, price, config.dailyBudgetSats || 5e3);
3027
+ if (!budget.allowed) throw new Error("Budget exceeded");
3028
+ const output = await cmdRequestService(best.identityKey, service, price.toString(), input ? JSON.stringify(input) : void 0);
3029
+ recordSpend(walletDir, price, service, best.name);
3030
+ return { status: "sent", requestId: output.data?.messageId, message: `Request sent to ${best.name} for ${price} sats.` };
3031
+ }
3032
+ case "discover":
3033
+ return (await cmdDiscover(params.service ? ["--service", params.service] : [])).data;
3034
+ case "balance":
3035
+ return (await cmdBalance()).data;
3036
+ case "status": {
3037
+ const identity = await cmdIdentity();
3038
+ const balance = await cmdBalance();
3039
+ return { identity: identity.data, balance: balance.data };
3040
+ }
3041
+ case "onboard": {
3042
+ await cmdSetup();
3043
+ const addr = (await cmdAddress()).data.address;
3044
+ const bal = (await cmdBalance()).data.walletBalance;
3045
+ if (bal < 1e3) return { funded: false, address: addr, message: "Please fund 1000 sats." };
3046
+ await cmdRegister();
3047
+ return { funded: true, registered: true, message: "Onboarding complete." };
3048
+ }
3049
+ case "pending-requests":
3050
+ return (await cmdServiceQueue()).data;
3051
+ case "fulfill": {
3052
+ const { requestId, recipientKey, serviceId, result } = params;
3053
+ return (await cmdRespondService(requestId, recipientKey, serviceId, JSON.stringify(result))).data;
3054
+ }
3055
+ case "unregister":
3056
+ return (await cmdUnregister()).data;
3057
+ default:
3058
+ throw new Error(`Unknown action: ${action}`);
3059
+ }
357
3060
  }
358
- export const plugin = {
359
- id: "openclaw-overlay-plugin",
360
- name: "BSV Overlay Network",
361
- description: "OpenClaw Overlay decentralized agent marketplace with BSV micropayments",
362
- activate: register,
363
- register: register
3061
+ var plugin = {
3062
+ id: "openclaw-overlay-plugin",
3063
+ name: "BSV Overlay Network",
3064
+ description: "OpenClaw Overlay \u2014 decentralized agent marketplace with BSV micropayments",
3065
+ activate: register,
3066
+ register
3067
+ };
3068
+ var index_default = register;
3069
+ export {
3070
+ index_default as default,
3071
+ plugin,
3072
+ register
364
3073
  };
365
- export default register;
3074
+ //# sourceMappingURL=index.js.map