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.
- package/dist/index.js +3037 -328
- package/dist/index.js.map +7 -0
- package/dist/src/cli.js +3820 -11
- package/dist/src/cli.js.map +7 -0
- package/index.ts +32 -27
- package/openclaw.plugin.json +1 -1
- package/package.json +8 -6
- package/src/scripts/messaging/handlers.ts +1 -1
- package/src/scripts/overlay/advertisement.ts +1 -1
- package/src/scripts/overlay/registration.ts +1 -1
- package/src/scripts/overlay/services.ts +1 -1
- package/src/scripts/overlay/transaction.ts +1 -1
- package/src/scripts/payment/build.ts +1 -1
- package/src/scripts/payment/commands.ts +1 -1
- package/src/scripts/wallet/balance.ts +1 -1
- package/src/scripts/wallet/setup.ts +1 -1
- package/src/scripts/x-verification/commands.ts +5 -0
- package/src/test/identity-consistency.test.ts +1 -1
- package/src/test/wallet.test.ts +1 -2
- package/dist/index.d.ts +0 -9
- package/dist/src/cli-main.d.ts +0 -7
- package/dist/src/cli-main.js +0 -202
- package/dist/src/cli.d.ts +0 -8
- package/dist/src/compatibility.test.d.ts +0 -4
- package/dist/src/compatibility.test.js +0 -41
- package/dist/src/core/config.d.ts +0 -11
- package/dist/src/core/config.js +0 -15
- package/dist/src/core/index.d.ts +0 -25
- package/dist/src/core/index.js +0 -26
- package/dist/src/core/payment.d.ts +0 -16
- package/dist/src/core/payment.js +0 -94
- package/dist/src/core/types.d.ts +0 -94
- package/dist/src/core/types.js +0 -4
- package/dist/src/core/verify.d.ts +0 -28
- package/dist/src/core/verify.js +0 -104
- package/dist/src/core/wallet.d.ts +0 -105
- package/dist/src/core/wallet.js +0 -256
- package/dist/src/scripts/baemail/commands.d.ts +0 -35
- package/dist/src/scripts/baemail/commands.js +0 -282
- package/dist/src/scripts/baemail/handler.d.ts +0 -36
- package/dist/src/scripts/baemail/handler.js +0 -284
- package/dist/src/scripts/baemail/index.d.ts +0 -5
- package/dist/src/scripts/baemail/index.js +0 -5
- package/dist/src/scripts/config.d.ts +0 -52
- package/dist/src/scripts/config.js +0 -75
- package/dist/src/scripts/index.d.ts +0 -7
- package/dist/src/scripts/index.js +0 -7
- package/dist/src/scripts/messaging/connect.d.ts +0 -8
- package/dist/src/scripts/messaging/connect.js +0 -168
- package/dist/src/scripts/messaging/handlers.d.ts +0 -21
- package/dist/src/scripts/messaging/handlers.js +0 -334
- package/dist/src/scripts/messaging/inbox.d.ts +0 -11
- package/dist/src/scripts/messaging/inbox.js +0 -51
- package/dist/src/scripts/messaging/index.d.ts +0 -8
- package/dist/src/scripts/messaging/index.js +0 -8
- package/dist/src/scripts/messaging/poll.d.ts +0 -7
- package/dist/src/scripts/messaging/poll.js +0 -52
- package/dist/src/scripts/messaging/send.d.ts +0 -7
- package/dist/src/scripts/messaging/send.js +0 -43
- package/dist/src/scripts/output.d.ts +0 -13
- package/dist/src/scripts/output.js +0 -28
- package/dist/src/scripts/overlay/advertisement.d.ts +0 -16
- package/dist/src/scripts/overlay/advertisement.js +0 -122
- package/dist/src/scripts/overlay/discover.d.ts +0 -7
- package/dist/src/scripts/overlay/discover.js +0 -74
- package/dist/src/scripts/overlay/index.d.ts +0 -7
- package/dist/src/scripts/overlay/index.js +0 -7
- package/dist/src/scripts/overlay/registration.d.ts +0 -19
- package/dist/src/scripts/overlay/registration.js +0 -176
- package/dist/src/scripts/overlay/services.d.ts +0 -29
- package/dist/src/scripts/overlay/services.js +0 -167
- package/dist/src/scripts/overlay/transaction.d.ts +0 -42
- package/dist/src/scripts/overlay/transaction.js +0 -103
- package/dist/src/scripts/payment/build.d.ts +0 -24
- package/dist/src/scripts/payment/build.js +0 -54
- package/dist/src/scripts/payment/commands.d.ts +0 -15
- package/dist/src/scripts/payment/commands.js +0 -73
- package/dist/src/scripts/payment/index.d.ts +0 -6
- package/dist/src/scripts/payment/index.js +0 -6
- package/dist/src/scripts/payment/types.d.ts +0 -56
- package/dist/src/scripts/payment/types.js +0 -4
- package/dist/src/scripts/services/index.d.ts +0 -6
- package/dist/src/scripts/services/index.js +0 -6
- package/dist/src/scripts/services/queue.d.ts +0 -11
- package/dist/src/scripts/services/queue.js +0 -28
- package/dist/src/scripts/services/request.d.ts +0 -7
- package/dist/src/scripts/services/request.js +0 -82
- package/dist/src/scripts/services/respond.d.ts +0 -11
- package/dist/src/scripts/services/respond.js +0 -132
- package/dist/src/scripts/types.d.ts +0 -107
- package/dist/src/scripts/types.js +0 -4
- package/dist/src/scripts/utils/index.d.ts +0 -6
- package/dist/src/scripts/utils/index.js +0 -6
- package/dist/src/scripts/utils/merkle.d.ts +0 -12
- package/dist/src/scripts/utils/merkle.js +0 -47
- package/dist/src/scripts/utils/storage.d.ts +0 -66
- package/dist/src/scripts/utils/storage.js +0 -211
- package/dist/src/scripts/utils/woc.d.ts +0 -26
- package/dist/src/scripts/utils/woc.js +0 -91
- package/dist/src/scripts/wallet/balance.d.ts +0 -22
- package/dist/src/scripts/wallet/balance.js +0 -240
- package/dist/src/scripts/wallet/identity.d.ts +0 -71
- package/dist/src/scripts/wallet/identity.js +0 -152
- package/dist/src/scripts/wallet/index.d.ts +0 -6
- package/dist/src/scripts/wallet/index.js +0 -6
- package/dist/src/scripts/wallet/setup.d.ts +0 -19
- package/dist/src/scripts/wallet/setup.js +0 -119
- package/dist/src/scripts/x-verification/commands.d.ts +0 -27
- package/dist/src/scripts/x-verification/commands.js +0 -222
- package/dist/src/scripts/x-verification/index.d.ts +0 -4
- package/dist/src/scripts/x-verification/index.js +0 -4
- package/dist/src/services/built-in/api-proxy/index.d.ts +0 -6
- package/dist/src/services/built-in/api-proxy/index.js +0 -23
- package/dist/src/services/built-in/code-develop/index.d.ts +0 -6
- package/dist/src/services/built-in/code-develop/index.js +0 -23
- package/dist/src/services/built-in/code-review/index.d.ts +0 -10
- package/dist/src/services/built-in/code-review/index.js +0 -51
- package/dist/src/services/built-in/image-analysis/index.d.ts +0 -6
- package/dist/src/services/built-in/image-analysis/index.js +0 -33
- package/dist/src/services/built-in/memory-store/index.d.ts +0 -6
- package/dist/src/services/built-in/memory-store/index.js +0 -22
- package/dist/src/services/built-in/roulette/index.d.ts +0 -6
- package/dist/src/services/built-in/roulette/index.js +0 -27
- package/dist/src/services/built-in/summarize/index.d.ts +0 -6
- package/dist/src/services/built-in/summarize/index.js +0 -21
- package/dist/src/services/built-in/tell-joke/handler.d.ts +0 -7
- package/dist/src/services/built-in/tell-joke/handler.js +0 -122
- package/dist/src/services/built-in/tell-joke/index.d.ts +0 -9
- package/dist/src/services/built-in/tell-joke/index.js +0 -31
- package/dist/src/services/built-in/translate/index.d.ts +0 -6
- package/dist/src/services/built-in/translate/index.js +0 -21
- package/dist/src/services/built-in/web-research/index.d.ts +0 -9
- package/dist/src/services/built-in/web-research/index.js +0 -51
- package/dist/src/services/index.d.ts +0 -13
- package/dist/src/services/index.js +0 -14
- package/dist/src/services/loader.d.ts +0 -77
- package/dist/src/services/loader.js +0 -292
- package/dist/src/services/manager.d.ts +0 -86
- package/dist/src/services/manager.js +0 -255
- package/dist/src/services/registry.d.ts +0 -98
- package/dist/src/services/registry.js +0 -204
- package/dist/src/services/types.d.ts +0 -230
- package/dist/src/services/types.js +0 -30
- package/dist/src/test/cli.test.d.ts +0 -7
- package/dist/src/test/cli.test.js +0 -330
- package/dist/src/test/comprehensive-overlay.test.d.ts +0 -13
- package/dist/src/test/comprehensive-overlay.test.js +0 -593
- package/dist/src/test/identity-consistency.test.d.ts +0 -6
- package/dist/src/test/identity-consistency.test.js +0 -60
- package/dist/src/test/key-derivation.test.d.ts +0 -12
- package/dist/src/test/key-derivation.test.js +0 -86
- package/dist/src/test/network-address.test.d.ts +0 -9
- package/dist/src/test/network-address.test.js +0 -37
- package/dist/src/test/overlay-submit.test.d.ts +0 -10
- package/dist/src/test/overlay-submit.test.js +0 -460
- package/dist/src/test/request-response-flow.test.d.ts +0 -5
- package/dist/src/test/request-response-flow.test.js +0 -210
- package/dist/src/test/service-system.test.d.ts +0 -5
- package/dist/src/test/service-system.test.js +0 -190
- package/dist/src/test/taskflow.test.d.ts +0 -7
- package/dist/src/test/taskflow.test.js +0 -82
- package/dist/src/test/utils/server-logic.d.ts +0 -98
- package/dist/src/test/utils/server-logic.js +0 -286
- package/dist/src/test/wallet.test.d.ts +0 -7
- package/dist/src/test/wallet.test.js +0 -146
- package/src/core/README.md +0 -246
- package/src/core/config.d.ts +0 -12
- package/src/core/config.d.ts.map +0 -1
- package/src/core/config.js +0 -14
- package/src/core/config.js.map +0 -1
- package/src/core/config.ts +0 -22
- package/src/core/index.ts +0 -42
- package/src/core/payment.d.ts +0 -17
- package/src/core/payment.d.ts.map +0 -1
- package/src/core/payment.js +0 -95
- package/src/core/payment.js.map +0 -1
- package/src/core/payment.ts +0 -111
- package/src/core/types.d.ts +0 -95
- package/src/core/types.d.ts.map +0 -1
- package/src/core/types.js +0 -5
- package/src/core/types.js.map +0 -1
- package/src/core/types.ts +0 -102
- package/src/core/verify.d.ts +0 -29
- package/src/core/verify.d.ts.map +0 -1
- package/src/core/verify.js +0 -105
- package/src/core/verify.js.map +0 -1
- package/src/core/verify.ts +0 -119
- package/src/core/wallet.d.ts +0 -100
- package/src/core/wallet.d.ts.map +0 -1
- package/src/core/wallet.js.map +0 -1
- package/src/core/wallet.ts +0 -323
package/dist/index.js
CHANGED
|
@@ -1,365 +1,3074 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
561
|
+
return JSON.parse(line);
|
|
562
|
+
} catch {
|
|
563
|
+
return null;
|
|
39
564
|
}
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
587
|
+
});
|
|
588
|
+
if (updated) {
|
|
589
|
+
fs7.writeFileSync(PATHS.serviceQueue, updatedLines.join("\n") + "\n");
|
|
590
|
+
}
|
|
591
|
+
return updated;
|
|
44
592
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
spent: spending.totalSats
|
|
1340
|
+
service,
|
|
1341
|
+
promptFile: service.promptFile
|
|
58
1342
|
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
if (!
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return { valid: true, sanitized: input };
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
return { valid: false, error: `Schema validation error: ${error}` };
|
|
130
1405
|
}
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
2072
|
+
} catch {
|
|
2073
|
+
}
|
|
153
2074
|
}
|
|
2075
|
+
}
|
|
2076
|
+
} catch (err) {
|
|
2077
|
+
results.agentError = String(err);
|
|
154
2078
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
3074
|
+
//# sourceMappingURL=index.js.map
|