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/src/cli.js
CHANGED
|
@@ -1,14 +1,3823 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/scripts/output.ts
|
|
13
|
+
function ok(data) {
|
|
14
|
+
if (noExitFlag) return { success: true, data };
|
|
15
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
function fail(error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
if (noExitFlag) return { success: false, error: message };
|
|
21
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
var noExitFlag;
|
|
25
|
+
var init_output = __esm({
|
|
26
|
+
"src/scripts/output.ts"() {
|
|
27
|
+
"use strict";
|
|
28
|
+
noExitFlag = false;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// src/scripts/config.ts
|
|
33
|
+
var config_exports = {};
|
|
34
|
+
__export(config_exports, {
|
|
35
|
+
AGENT_DESCRIPTION: () => AGENT_DESCRIPTION,
|
|
36
|
+
AGENT_NAME: () => AGENT_NAME,
|
|
37
|
+
DEFAULT_SLAP_TRACKERS: () => DEFAULT_SLAP_TRACKERS,
|
|
38
|
+
LOOKUP_SERVICES: () => LOOKUP_SERVICES,
|
|
39
|
+
NETWORK: () => NETWORK,
|
|
40
|
+
OVERLAY_STATE_DIR: () => OVERLAY_STATE_DIR,
|
|
41
|
+
OVERLAY_URL: () => OVERLAY_URL,
|
|
42
|
+
PATHS: () => PATHS,
|
|
43
|
+
PROTOCOL_ID: () => PROTOCOL_ID,
|
|
44
|
+
TOPICS: () => TOPICS,
|
|
45
|
+
WALLET_DIR: () => WALLET_DIR,
|
|
46
|
+
WOC_API_KEY: () => WOC_API_KEY
|
|
47
|
+
});
|
|
48
|
+
import path from "node:path";
|
|
49
|
+
import os from "node:os";
|
|
50
|
+
import fs from "node:fs";
|
|
51
|
+
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;
|
|
52
|
+
var init_config = __esm({
|
|
53
|
+
"src/scripts/config.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
overlayEnvPath = path.join(os.homedir(), ".openclaw", "openclaw-overlay", ".env");
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(overlayEnvPath)) {
|
|
58
|
+
for (const line of fs.readFileSync(overlayEnvPath, "utf-8").split("\n")) {
|
|
59
|
+
const match = line.match(/^([A-Z_]+)=(.+)$/);
|
|
60
|
+
if (match && !process["env"][match[1]]) {
|
|
61
|
+
process["env"][match[1]] = match[2]?.trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
WALLET_DIR = process["env"].BSV_WALLET_DIR || path.join(os.homedir(), ".openclaw", "bsv-wallet");
|
|
68
|
+
NETWORK = process["env"].BSV_NETWORK || "mainnet";
|
|
69
|
+
OVERLAY_URL = process["env"].OVERLAY_URL || "https://clawoverlay.com";
|
|
70
|
+
AGENT_NAME = process["env"].AGENT_NAME || "openclaw-agent";
|
|
71
|
+
AGENT_DESCRIPTION = process["env"].AGENT_DESCRIPTION || `AI agent on the OpenClaw Overlay Network. Offers services for BSV micropayments.`;
|
|
72
|
+
WOC_API_KEY = process["env"].WOC_API_KEY || "";
|
|
73
|
+
OVERLAY_STATE_DIR = path.join(os.homedir(), ".openclaw", "openclaw-overlay");
|
|
74
|
+
PROTOCOL_ID = "clawdbot-overlay-v1";
|
|
75
|
+
TOPICS = {
|
|
76
|
+
IDENTITY: "tm_clawdbot_identity",
|
|
77
|
+
SERVICES: "tm_clawdbot_services",
|
|
78
|
+
X_VERIFICATION: "tm_clawdbot_x_verification",
|
|
79
|
+
SHIP: "tm_ship",
|
|
80
|
+
SLAP: "tm_slap"
|
|
81
|
+
};
|
|
82
|
+
DEFAULT_SLAP_TRACKERS = {
|
|
83
|
+
mainnet: ["https://overlay.babbage.systems"],
|
|
84
|
+
testnet: ["https://testnet-users.bapp.dev"]
|
|
85
|
+
};
|
|
86
|
+
LOOKUP_SERVICES = {
|
|
87
|
+
AGENTS: "ls_clawdbot_agents",
|
|
88
|
+
SERVICES: "ls_clawdbot_services",
|
|
89
|
+
X_VERIFICATIONS: "ls_clawdbot_x_verifications"
|
|
90
|
+
};
|
|
91
|
+
PATHS = {
|
|
92
|
+
walletIdentity: path.join(WALLET_DIR, "wallet-identity.json"),
|
|
93
|
+
registration: path.join(OVERLAY_STATE_DIR, "registration.json"),
|
|
94
|
+
services: path.join(OVERLAY_STATE_DIR, "services.json"),
|
|
95
|
+
latestChange: path.join(OVERLAY_STATE_DIR, "latest-change.json"),
|
|
96
|
+
receivedPayments: path.join(OVERLAY_STATE_DIR, "received-payments.jsonl"),
|
|
97
|
+
researchQueue: path.join(OVERLAY_STATE_DIR, "research-queue.jsonl"),
|
|
98
|
+
serviceQueue: path.join(OVERLAY_STATE_DIR, "service-queue.jsonl"),
|
|
99
|
+
notifications: path.join(OVERLAY_STATE_DIR, "notifications.jsonl"),
|
|
100
|
+
xVerifications: path.join(OVERLAY_STATE_DIR, "x-verifications.json"),
|
|
101
|
+
pendingXVerification: path.join(OVERLAY_STATE_DIR, "pending-x-verification.json"),
|
|
102
|
+
xEngagementQueue: path.join(OVERLAY_STATE_DIR, "x-engagement-queue.jsonl"),
|
|
103
|
+
memoryStore: path.join(WALLET_DIR, "memory-store.json"),
|
|
104
|
+
baemailConfig: path.join(OVERLAY_STATE_DIR, "baemail-config.json"),
|
|
105
|
+
baemailLog: path.join(OVERLAY_STATE_DIR, "baemail-log.jsonl")
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// src/scripts/wallet/identity.ts
|
|
111
|
+
import fs2 from "node:fs";
|
|
112
|
+
import { CachedKeyDeriver, Utils } from "@bsv/sdk";
|
|
113
|
+
import { brc29ProtocolID } from "@bsv/wallet-toolbox";
|
|
114
|
+
async function getSdk() {
|
|
115
|
+
if (_sdk) return _sdk;
|
|
116
|
+
try {
|
|
117
|
+
_sdk = await import("@bsv/sdk");
|
|
118
|
+
return _sdk;
|
|
119
|
+
} catch {
|
|
120
|
+
const { fileURLToPath: fileURLToPath3 } = await import("node:url");
|
|
121
|
+
const path5 = await import("node:path");
|
|
122
|
+
const os3 = await import("node:os");
|
|
123
|
+
const __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
|
|
124
|
+
const candidates = [
|
|
125
|
+
path5.resolve(__dirname3, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
126
|
+
path5.resolve(__dirname3, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
127
|
+
path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
|
|
128
|
+
];
|
|
129
|
+
for (const p of candidates) {
|
|
130
|
+
try {
|
|
131
|
+
_sdk = await import(p);
|
|
132
|
+
return _sdk;
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function loadWalletIdentity() {
|
|
140
|
+
if (!fs2.existsSync(PATHS.walletIdentity)) {
|
|
141
|
+
throw new Error("Wallet not initialized. Run: cli setup");
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const fileMode = fs2.statSync(PATHS.walletIdentity).mode & 511;
|
|
145
|
+
if (fileMode & 36) {
|
|
146
|
+
console.error(`[security] WARNING: ${PATHS.walletIdentity} has permissive mode 0${fileMode.toString(8)}. Run: chmod 600 ${PATHS.walletIdentity}`);
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
return JSON.parse(fs2.readFileSync(PATHS.walletIdentity, "utf-8"));
|
|
151
|
+
}
|
|
152
|
+
async function loadIdentity() {
|
|
153
|
+
const identity = loadWalletIdentity();
|
|
154
|
+
const sdk = await getSdk();
|
|
155
|
+
const privKey = sdk.PrivateKey.fromHex(identity.rootKeyHex);
|
|
156
|
+
return { identityKey: identity.identityKey, privKey };
|
|
157
|
+
}
|
|
158
|
+
async function signRelayMessage(privKey, to, type, payload) {
|
|
159
|
+
const sdk = await getSdk();
|
|
160
|
+
const preimage = to + type + JSON.stringify(payload);
|
|
161
|
+
const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
|
|
162
|
+
const sig = privKey.sign(msgHash);
|
|
163
|
+
return Array.from(sig.toDER()).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
164
|
+
}
|
|
165
|
+
async function verifyRelaySignature(fromKey, to, type, payload, signatureHex) {
|
|
166
|
+
if (!signatureHex) return { valid: false, reason: "no signature" };
|
|
167
|
+
try {
|
|
168
|
+
const sdk = await getSdk();
|
|
169
|
+
const preimage = to + type + JSON.stringify(payload);
|
|
170
|
+
const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
|
|
171
|
+
const sigBytes = [];
|
|
172
|
+
for (let i = 0; i < signatureHex.length; i += 2) {
|
|
173
|
+
sigBytes.push(parseInt(signatureHex.substring(i, i + 2), 16));
|
|
174
|
+
}
|
|
175
|
+
const sig = sdk.Signature.fromDER(sigBytes);
|
|
176
|
+
const pubKey = sdk.PublicKey.fromString(fromKey);
|
|
177
|
+
return { valid: pubKey.verify(msgHash, sig) };
|
|
178
|
+
} catch (err) {
|
|
179
|
+
return { valid: false, reason: String(err) };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function deriveWalletAddress(privKey, network = NETWORK) {
|
|
183
|
+
const keyDeriver = new CachedKeyDeriver(privKey);
|
|
184
|
+
const pubKey = keyDeriver.derivePublicKey(
|
|
185
|
+
brc29ProtocolID,
|
|
186
|
+
Utils.toBase64(Utils.toArray("import")) + " " + Utils.toBase64(Utils.toArray("now")),
|
|
187
|
+
"self",
|
|
188
|
+
true
|
|
189
|
+
);
|
|
190
|
+
const address = pubKey.toAddress(network);
|
|
191
|
+
const hash160 = Buffer.from(pubKey.toHash());
|
|
192
|
+
return { address, hash160, pubKey };
|
|
193
|
+
}
|
|
194
|
+
var _sdk;
|
|
195
|
+
var init_identity = __esm({
|
|
196
|
+
"src/scripts/wallet/identity.ts"() {
|
|
197
|
+
"use strict";
|
|
198
|
+
init_config();
|
|
199
|
+
_sdk = null;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ../plugin-core/dist/config.js
|
|
204
|
+
function toChain(network) {
|
|
205
|
+
if (network === "testnet")
|
|
206
|
+
return "test";
|
|
207
|
+
return "main";
|
|
208
|
+
}
|
|
209
|
+
var DEFAULT_TAAL_API_KEYS, DEFAULT_DB_NAME;
|
|
210
|
+
var init_config2 = __esm({
|
|
211
|
+
"../plugin-core/dist/config.js"() {
|
|
212
|
+
"use strict";
|
|
213
|
+
DEFAULT_TAAL_API_KEYS = {
|
|
214
|
+
main: "mainnet_9596de07e92300c6287e4393594ae39c",
|
|
215
|
+
test: "testnet_0e6cf72133b43ea2d7861da2a38684e3"
|
|
216
|
+
};
|
|
217
|
+
DEFAULT_DB_NAME = "a2a_agent_wallet";
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ../plugin-core/dist/payment.js
|
|
222
|
+
import { Beef, Utils as Utils2 } from "@bsv/sdk";
|
|
223
|
+
import { randomBytesBase64, ScriptTemplateBRC29 } from "@bsv/wallet-toolbox";
|
|
224
|
+
async function buildPayment(setup, params) {
|
|
225
|
+
const { to, satoshis, description } = params;
|
|
226
|
+
const desc = normalizeDescription(description ?? "agent payment");
|
|
227
|
+
const derivationPrefix = randomBytesBase64(8);
|
|
228
|
+
const derivationSuffix = randomBytesBase64(8);
|
|
229
|
+
const keyDeriver = setup.keyDeriver;
|
|
230
|
+
const t = new ScriptTemplateBRC29({
|
|
231
|
+
derivationPrefix,
|
|
232
|
+
derivationSuffix,
|
|
233
|
+
keyDeriver
|
|
234
|
+
});
|
|
235
|
+
let recipientPubKey;
|
|
236
|
+
if (/^0[23][0-9a-fA-F]{64}$/.test(to)) {
|
|
237
|
+
recipientPubKey = to;
|
|
238
|
+
} else {
|
|
239
|
+
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.");
|
|
240
|
+
}
|
|
241
|
+
const lockingScript = t.lock(setup.rootKey.toString(), recipientPubKey);
|
|
242
|
+
const label = "a2a-payment";
|
|
243
|
+
const car = await setup.wallet.createAction({
|
|
244
|
+
outputs: [
|
|
245
|
+
{
|
|
246
|
+
lockingScript: lockingScript.toHex(),
|
|
247
|
+
satoshis,
|
|
248
|
+
outputDescription: desc,
|
|
249
|
+
tags: ["relinquish"],
|
|
250
|
+
customInstructions: JSON.stringify({
|
|
251
|
+
derivationPrefix,
|
|
252
|
+
derivationSuffix,
|
|
253
|
+
type: "BRC29"
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
options: {
|
|
258
|
+
randomizeOutputs: false,
|
|
259
|
+
acceptDelayedBroadcast: false
|
|
260
|
+
},
|
|
261
|
+
labels: [label],
|
|
262
|
+
description: desc
|
|
263
|
+
});
|
|
264
|
+
if (!car.tx) {
|
|
265
|
+
throw new Error("createAction did not return a transaction. Check wallet funding.");
|
|
266
|
+
}
|
|
267
|
+
const beef = Beef.fromBinary(car.tx);
|
|
268
|
+
const lastTx = beef.txs[beef.txs.length - 1];
|
|
269
|
+
const txid = lastTx.txid;
|
|
270
|
+
const atomicBinary = beef.toBinaryAtomic(txid);
|
|
271
|
+
const beefBase64 = Utils2.toBase64(atomicBinary);
|
|
272
|
+
return {
|
|
273
|
+
beef: beefBase64,
|
|
274
|
+
txid,
|
|
275
|
+
satoshis,
|
|
276
|
+
derivationPrefix,
|
|
277
|
+
derivationSuffix,
|
|
278
|
+
senderIdentityKey: setup.identityKey
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function normalizeDescription(desc) {
|
|
282
|
+
if (desc.length < 5)
|
|
283
|
+
return desc.padEnd(5, " ");
|
|
284
|
+
if (desc.length > 50)
|
|
285
|
+
return desc.slice(0, 50);
|
|
286
|
+
return desc;
|
|
287
|
+
}
|
|
288
|
+
var init_payment = __esm({
|
|
289
|
+
"../plugin-core/dist/payment.js"() {
|
|
290
|
+
"use strict";
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ../plugin-core/dist/verify.js
|
|
295
|
+
import { Beef as Beef2, Utils as Utils3 } from "@bsv/sdk";
|
|
296
|
+
async function verifyPayment(params) {
|
|
297
|
+
const errors = [];
|
|
298
|
+
let txid = "";
|
|
299
|
+
let outputCount = 0;
|
|
300
|
+
try {
|
|
301
|
+
const binary = Utils3.toArray(params.beef, "base64");
|
|
302
|
+
const beef = Beef2.fromBinary(binary);
|
|
303
|
+
if (beef.txs.length === 0) {
|
|
304
|
+
errors.push("BEEF contains no transactions");
|
|
305
|
+
} else {
|
|
306
|
+
const lastTx = beef.txs[beef.txs.length - 1];
|
|
307
|
+
txid = lastTx.txid;
|
|
308
|
+
const tx = beef.findAtomicTransaction(txid);
|
|
309
|
+
if (tx) {
|
|
310
|
+
outputCount = tx.outputs.length;
|
|
311
|
+
try {
|
|
312
|
+
await tx.verify();
|
|
313
|
+
} catch (err) {
|
|
314
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
315
|
+
errors.push(`SPV verification failed: ${message}`);
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
errors.push("Could not find atomic transaction in BEEF");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
323
|
+
errors.push(`BEEF parse error: ${message}`);
|
|
324
|
+
}
|
|
325
|
+
if (params.expectedSender) {
|
|
326
|
+
if (!/^0[23][0-9a-fA-F]{64}$/.test(params.expectedSender)) {
|
|
327
|
+
errors.push("expectedSender is not a valid compressed public key");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
valid: errors.length === 0,
|
|
332
|
+
txid,
|
|
333
|
+
outputCount,
|
|
334
|
+
errors
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
async function acceptPayment(setup, params) {
|
|
338
|
+
const desc = normalizeDescription2(params.description ?? "received payment");
|
|
339
|
+
const vout = params.vout ?? 0;
|
|
340
|
+
const binary = Utils3.toArray(params.beef, "base64");
|
|
341
|
+
const args2 = {
|
|
342
|
+
tx: binary,
|
|
343
|
+
outputs: [
|
|
344
|
+
{
|
|
345
|
+
outputIndex: vout,
|
|
346
|
+
protocol: "wallet payment",
|
|
347
|
+
paymentRemittance: {
|
|
348
|
+
derivationPrefix: params.derivationPrefix,
|
|
349
|
+
derivationSuffix: params.derivationSuffix,
|
|
350
|
+
senderIdentityKey: params.senderIdentityKey
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
],
|
|
354
|
+
description: desc
|
|
355
|
+
};
|
|
356
|
+
const result = await setup.wallet.internalizeAction(args2);
|
|
357
|
+
return {
|
|
358
|
+
accepted: result.accepted
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function normalizeDescription2(desc) {
|
|
362
|
+
if (desc.length < 5)
|
|
363
|
+
return desc.padEnd(5, " ");
|
|
364
|
+
if (desc.length > 50)
|
|
365
|
+
return desc.slice(0, 50);
|
|
366
|
+
return desc;
|
|
367
|
+
}
|
|
368
|
+
var init_verify = __esm({
|
|
369
|
+
"../plugin-core/dist/verify.js"() {
|
|
370
|
+
"use strict";
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// ../plugin-core/dist/wallet.js
|
|
375
|
+
import { PrivateKey, CachedKeyDeriver as CachedKeyDeriver2 } from "@bsv/sdk";
|
|
376
|
+
import { Wallet, WalletStorageManager, Services, Monitor, StorageKnex, randomBytesHex, ChaintracksServiceClient } from "@bsv/wallet-toolbox";
|
|
377
|
+
import knexLib from "knex";
|
|
378
|
+
import * as path2 from "node:path";
|
|
379
|
+
import * as fs3 from "node:fs";
|
|
380
|
+
import debug from "debug";
|
|
381
|
+
var log, IDENTITY_FILE, BSVAgentWallet;
|
|
382
|
+
var init_wallet = __esm({
|
|
383
|
+
"../plugin-core/dist/wallet.js"() {
|
|
384
|
+
"use strict";
|
|
385
|
+
init_config2();
|
|
386
|
+
init_payment();
|
|
387
|
+
init_verify();
|
|
388
|
+
log = debug("openclaw:plugin:overlay:wallet");
|
|
389
|
+
IDENTITY_FILE = "wallet-identity.json";
|
|
390
|
+
BSVAgentWallet = class _BSVAgentWallet {
|
|
391
|
+
/** @internal — exposed for advanced operations (e.g. direct internalizeAction) */
|
|
392
|
+
_setup;
|
|
393
|
+
constructor(setup) {
|
|
394
|
+
this._setup = setup;
|
|
395
|
+
}
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// Factory methods
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
/**
|
|
400
|
+
* Create a new agent wallet. Generates a fresh root key and persists it.
|
|
401
|
+
* The SQLite database and identity file are written to `config.storageDir`.
|
|
402
|
+
*/
|
|
403
|
+
static async create(config) {
|
|
404
|
+
log("Creating new wallet in: %s", config.storageDir);
|
|
405
|
+
const rootKeyHex = config.rootKeyHex ?? PrivateKey.fromRandom().toHex();
|
|
406
|
+
const rootKey = PrivateKey.fromHex(rootKeyHex);
|
|
407
|
+
const identityKey = rootKey.toPublicKey().toString();
|
|
408
|
+
fs3.mkdirSync(config.storageDir, { recursive: true });
|
|
409
|
+
const identity = {
|
|
410
|
+
rootKeyHex,
|
|
411
|
+
identityKey,
|
|
412
|
+
network: config.network
|
|
413
|
+
};
|
|
414
|
+
const identityPath = path2.join(config.storageDir, IDENTITY_FILE);
|
|
415
|
+
fs3.writeFileSync(identityPath, JSON.stringify(identity, null, 2), "utf-8");
|
|
416
|
+
const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
|
|
417
|
+
return new _BSVAgentWallet(setup);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Load an existing agent wallet from its storage directory.
|
|
421
|
+
* Reads the persisted identity file and re-initializes the wallet.
|
|
422
|
+
*/
|
|
423
|
+
static async load(config) {
|
|
424
|
+
log("Loading wallet from: %s", config.storageDir);
|
|
425
|
+
const identityPath = path2.join(config.storageDir, IDENTITY_FILE);
|
|
426
|
+
if (!fs3.existsSync(identityPath)) {
|
|
427
|
+
if (config.createIfMissing === false) {
|
|
428
|
+
log("Wallet not found and createIfMissing is false");
|
|
429
|
+
throw new Error(`No wallet found in ${config.storageDir}`);
|
|
430
|
+
}
|
|
431
|
+
return this.create(config);
|
|
432
|
+
}
|
|
433
|
+
const identity = JSON.parse(fs3.readFileSync(identityPath, "utf-8"));
|
|
434
|
+
const rootKeyHex = config.rootKeyHex ?? identity.rootKeyHex;
|
|
435
|
+
const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
|
|
436
|
+
return new _BSVAgentWallet(setup);
|
|
437
|
+
}
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
// Wallet lifecycle
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
/**
|
|
442
|
+
* Get this wallet's public identity key (compressed hex, 33 bytes).
|
|
443
|
+
* This is the key other agents use to send payments to you.
|
|
444
|
+
*/
|
|
445
|
+
async getIdentityKey() {
|
|
446
|
+
return this._setup.identityKey;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get the wallet's current receive address for the active network.
|
|
450
|
+
*/
|
|
451
|
+
async getAddress() {
|
|
452
|
+
const network = this._setup.network || "mainnet";
|
|
453
|
+
return this._setup.rootKey.toPublicKey().toAddress(network);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get the wallet's current balance in satoshis.
|
|
457
|
+
*
|
|
458
|
+
* Uses the BRC-100 wallet's balance method which sums spendable outputs
|
|
459
|
+
* in the default basket.
|
|
460
|
+
*/
|
|
461
|
+
async getBalance() {
|
|
462
|
+
return await this._setup.wallet.balance();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Cleanly shut down the wallet, releasing database connections and
|
|
466
|
+
* stopping the background monitor.
|
|
467
|
+
*/
|
|
468
|
+
async destroy() {
|
|
469
|
+
if (this._setup.monitor) {
|
|
470
|
+
await this._setup.monitor.destroy();
|
|
471
|
+
}
|
|
472
|
+
if (this._setup.wallet) {
|
|
473
|
+
await this._setup.wallet.destroy();
|
|
474
|
+
}
|
|
475
|
+
await this._setup.storage.destroy();
|
|
476
|
+
}
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
// Payment creation (sender/payer side)
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
/**
|
|
481
|
+
* Build a BRC-29 payment to another agent.
|
|
482
|
+
*
|
|
483
|
+
* The transaction is created with `noSend: true` — the sender does NOT
|
|
484
|
+
* broadcast it. Instead, the Atomic BEEF and derivation metadata are
|
|
485
|
+
* returned so they can be transmitted to the recipient, who will
|
|
486
|
+
* verify and internalize (broadcast) the payment.
|
|
487
|
+
*
|
|
488
|
+
* @param params.to — Recipient's compressed public key (hex).
|
|
489
|
+
* @param params.satoshis — Amount in satoshis.
|
|
490
|
+
* @param params.description — Optional human-readable note.
|
|
491
|
+
*/
|
|
492
|
+
async createPayment(params) {
|
|
493
|
+
return buildPayment(this._setup, params);
|
|
494
|
+
}
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
// Payment verification & acceptance (receiver/merchant side)
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
/**
|
|
499
|
+
* Verify an incoming Atomic BEEF payment.
|
|
500
|
+
*
|
|
501
|
+
* This performs structural validation and SPV verification via tx.verify().
|
|
502
|
+
*/
|
|
503
|
+
async verifyPayment(params) {
|
|
504
|
+
return await verifyPayment(params);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Accept (internalize) a verified payment into this wallet.
|
|
508
|
+
*
|
|
509
|
+
* Uses the BRC-29 wallet payment protocol to derive the correct key
|
|
510
|
+
* and claim the output. This triggers SPV verification and, if the
|
|
511
|
+
* transaction hasn't been broadcast yet, broadcasts it.
|
|
512
|
+
*/
|
|
513
|
+
async acceptPayment(params) {
|
|
514
|
+
return acceptPayment(this._setup, params);
|
|
515
|
+
}
|
|
516
|
+
// ---------------------------------------------------------------------------
|
|
517
|
+
// Access to underlying toolbox objects (for advanced use)
|
|
518
|
+
// ---------------------------------------------------------------------------
|
|
519
|
+
/** Get the underlying wallet-toolbox SetupWallet for advanced operations. */
|
|
520
|
+
getSetup() {
|
|
521
|
+
return this._setup;
|
|
522
|
+
}
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// Private helpers
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
/**
|
|
527
|
+
* Internal: manually construct a BRC-100 wallet backed by SQLite.
|
|
528
|
+
*
|
|
529
|
+
* We build this by hand instead of using Setup.createWalletSQLite because
|
|
530
|
+
* the toolbox has a bug where its internal randomBytesHex is a stub.
|
|
531
|
+
* We use the same components but wire them up correctly.
|
|
532
|
+
*/
|
|
533
|
+
static async buildSetup(config, rootKeyHex) {
|
|
534
|
+
const chain = toChain(config.network);
|
|
535
|
+
log("Building setup for chain: %s (network: %s)", chain, config.network);
|
|
536
|
+
const taalApiKey = config.taalApiKey ?? DEFAULT_TAAL_API_KEYS[chain];
|
|
537
|
+
const rootKey = PrivateKey.fromHex(rootKeyHex);
|
|
538
|
+
const identityKey = rootKey.toPublicKey().toString();
|
|
539
|
+
const keyDeriver = new CachedKeyDeriver2(rootKey);
|
|
540
|
+
const storage = new WalletStorageManager(identityKey);
|
|
541
|
+
const serviceOptions = Services.createDefaultOptions(chain);
|
|
542
|
+
const chaintracksUrl = process["env"].BSV_CHAINTRACKS_URL || "https://chaintracks-us-1.bsvb.tech";
|
|
543
|
+
const arcUrl = process["env"].BSV_ARC_URL;
|
|
544
|
+
const isTestMode = config.enableMonitor === false;
|
|
545
|
+
if (!isTestMode) {
|
|
546
|
+
serviceOptions.chaintracks = new ChaintracksServiceClient(chain, chaintracksUrl);
|
|
547
|
+
if (arcUrl) {
|
|
548
|
+
serviceOptions.arcUrl = arcUrl;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
serviceOptions.taalApiKey = taalApiKey;
|
|
552
|
+
const services = new Services(serviceOptions);
|
|
553
|
+
const monopts = Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
|
|
554
|
+
const monitor = new Monitor(monopts);
|
|
555
|
+
if (!isTestMode) {
|
|
556
|
+
monitor.addDefaultTasks();
|
|
557
|
+
} else {
|
|
558
|
+
monitor.tasks = [];
|
|
559
|
+
}
|
|
560
|
+
const wallet = isTestMode ? void 0 : new Wallet({ chain, keyDeriver, storage, services, monitor });
|
|
561
|
+
const filePath = path2.join(config.storageDir, `${DEFAULT_DB_NAME}.sqlite`);
|
|
562
|
+
const knex = knexLib({
|
|
563
|
+
client: "sqlite3",
|
|
564
|
+
connection: { filename: filePath },
|
|
565
|
+
useNullAsDefault: true
|
|
566
|
+
});
|
|
567
|
+
const feeModelValue = config.feeModel ?? (process["env"].BSV_FEE_MODEL ? parseInt(process["env"].BSV_FEE_MODEL, 10) : 100);
|
|
568
|
+
const activeStorage = new StorageKnex({
|
|
569
|
+
chain,
|
|
570
|
+
knex,
|
|
571
|
+
commissionSatoshis: 0,
|
|
572
|
+
commissionPubKeyHex: void 0,
|
|
573
|
+
feeModel: { model: "sat/kb", value: feeModelValue }
|
|
574
|
+
});
|
|
575
|
+
await activeStorage.migrate(DEFAULT_DB_NAME, randomBytesHex(33));
|
|
576
|
+
await activeStorage.makeAvailable();
|
|
577
|
+
await storage.addWalletStorageProvider(activeStorage);
|
|
578
|
+
await activeStorage.findOrInsertUser(identityKey);
|
|
579
|
+
return {
|
|
580
|
+
rootKey,
|
|
581
|
+
identityKey,
|
|
582
|
+
keyDeriver,
|
|
583
|
+
chain,
|
|
584
|
+
network: config.network,
|
|
585
|
+
storage,
|
|
586
|
+
services: isTestMode ? void 0 : services,
|
|
587
|
+
monitor: isTestMode ? void 0 : monitor,
|
|
588
|
+
wallet
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// ../plugin-core/dist/index.js
|
|
596
|
+
var init_dist = __esm({
|
|
597
|
+
"../plugin-core/dist/index.js"() {
|
|
598
|
+
"use strict";
|
|
599
|
+
init_wallet();
|
|
600
|
+
init_config2();
|
|
601
|
+
init_payment();
|
|
602
|
+
init_verify();
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// src/scripts/wallet/setup.ts
|
|
607
|
+
import fs4 from "node:fs";
|
|
608
|
+
async function getBSVAgentWallet() {
|
|
609
|
+
return BSVAgentWallet;
|
|
610
|
+
}
|
|
611
|
+
async function getSdk2() {
|
|
612
|
+
if (_sdk2) return _sdk2;
|
|
613
|
+
try {
|
|
614
|
+
_sdk2 = await import("@bsv/sdk");
|
|
615
|
+
return _sdk2;
|
|
616
|
+
} catch {
|
|
617
|
+
const { fileURLToPath: fileURLToPath3 } = await import("node:url");
|
|
618
|
+
const path5 = await import("node:path");
|
|
619
|
+
const os3 = await import("node:os");
|
|
620
|
+
const __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
|
|
621
|
+
const candidates = [
|
|
622
|
+
path5.resolve(__dirname3, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
623
|
+
path5.resolve(__dirname3, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
624
|
+
path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
|
|
625
|
+
];
|
|
626
|
+
for (const p of candidates) {
|
|
627
|
+
try {
|
|
628
|
+
_sdk2 = await import(p);
|
|
629
|
+
return _sdk2;
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async function cmdSetup() {
|
|
637
|
+
const BSVAgentWallet2 = await getBSVAgentWallet();
|
|
638
|
+
if (fs4.existsSync(PATHS.walletIdentity)) {
|
|
639
|
+
const wallet2 = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
640
|
+
const identityKey2 = await wallet2.getIdentityKey();
|
|
641
|
+
await wallet2.destroy();
|
|
642
|
+
return ok({
|
|
643
|
+
identityKey: identityKey2,
|
|
644
|
+
walletDir: WALLET_DIR,
|
|
645
|
+
network: NETWORK,
|
|
646
|
+
overlayUrl: OVERLAY_URL,
|
|
647
|
+
alreadyExisted: true
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
fs4.mkdirSync(WALLET_DIR, { recursive: true });
|
|
651
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
652
|
+
const identityKey = await wallet.getIdentityKey();
|
|
653
|
+
await wallet.destroy();
|
|
654
|
+
if (fs4.existsSync(PATHS.walletIdentity)) {
|
|
655
|
+
fs4.chmodSync(PATHS.walletIdentity, 384);
|
|
656
|
+
}
|
|
657
|
+
return ok({
|
|
658
|
+
identityKey,
|
|
659
|
+
walletDir: WALLET_DIR,
|
|
660
|
+
network: NETWORK,
|
|
661
|
+
overlayUrl: OVERLAY_URL,
|
|
662
|
+
alreadyExisted: false
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
async function cmdIdentity() {
|
|
666
|
+
const BSVAgentWallet2 = await getBSVAgentWallet();
|
|
667
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
668
|
+
const identityKey = await wallet.getIdentityKey();
|
|
669
|
+
await wallet.destroy();
|
|
670
|
+
return ok({ identityKey });
|
|
671
|
+
}
|
|
672
|
+
async function cmdStatus() {
|
|
673
|
+
const BSVAgentWallet2 = await getBSVAgentWallet();
|
|
674
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
675
|
+
const identityKey = await wallet.getIdentityKey();
|
|
676
|
+
const total = await wallet.getBalance();
|
|
677
|
+
await wallet.destroy();
|
|
678
|
+
return ok({
|
|
679
|
+
identity: { identityKey, network: NETWORK },
|
|
680
|
+
balance: { walletBalance: total }
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
async function cmdAddress() {
|
|
684
|
+
if (!fs4.existsSync(PATHS.walletIdentity)) {
|
|
685
|
+
return fail("Wallet not initialized. Run: setup");
|
|
686
|
+
}
|
|
687
|
+
const sdk = await getSdk2();
|
|
688
|
+
const identity = loadWalletIdentity();
|
|
689
|
+
const privKey = sdk.PrivateKey.fromHex(identity.rootKeyHex);
|
|
690
|
+
const { address } = await deriveWalletAddress(privKey);
|
|
691
|
+
return ok({
|
|
692
|
+
address,
|
|
693
|
+
network: NETWORK,
|
|
694
|
+
identityKey: identity.identityKey,
|
|
695
|
+
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}`
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
var _sdk2;
|
|
699
|
+
var init_setup = __esm({
|
|
700
|
+
"src/scripts/wallet/setup.ts"() {
|
|
701
|
+
"use strict";
|
|
702
|
+
init_config();
|
|
703
|
+
init_output();
|
|
704
|
+
init_identity();
|
|
705
|
+
init_dist();
|
|
706
|
+
_sdk2 = null;
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
// src/scripts/utils/woc.ts
|
|
711
|
+
async function wocFetch(urlPath, options = {}, maxRetries = 3, timeoutMs = 3e4) {
|
|
712
|
+
const wocNet = NETWORK === "mainnet" ? "main" : "test";
|
|
713
|
+
const base = `https://api.whatsonchain.com/v1/bsv/${wocNet}`;
|
|
714
|
+
const url = urlPath.startsWith("http") ? urlPath : `${base}${urlPath}`;
|
|
715
|
+
const headers = { ...options.headers || {} };
|
|
716
|
+
if (WOC_API_KEY) {
|
|
717
|
+
headers["Authorization"] = `Bearer ${WOC_API_KEY}`;
|
|
718
|
+
}
|
|
719
|
+
let lastError;
|
|
720
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
721
|
+
try {
|
|
722
|
+
const controller = new AbortController();
|
|
723
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
724
|
+
const resp = await fetch(url, { ...options, headers, signal: controller.signal });
|
|
725
|
+
clearTimeout(timeout);
|
|
726
|
+
if ((resp.status === 429 || resp.status >= 500) && attempt < maxRetries) {
|
|
727
|
+
const delayMs = Math.min(1e3 * Math.pow(2, attempt), 8e3);
|
|
728
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
return resp;
|
|
732
|
+
} catch (err) {
|
|
733
|
+
lastError = err;
|
|
734
|
+
if (attempt < maxRetries) {
|
|
735
|
+
const delayMs = Math.min(1e3 * Math.pow(2, attempt), 8e3);
|
|
736
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
throw lastError || new Error("WoC fetch failed after retries");
|
|
742
|
+
}
|
|
743
|
+
async function fetchWithTimeout(url, options = {}, timeoutMs = 15e3) {
|
|
744
|
+
const controller = new AbortController();
|
|
745
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
746
|
+
try {
|
|
747
|
+
const resp = await fetch(url, { ...options, signal: controller.signal });
|
|
748
|
+
return resp;
|
|
749
|
+
} finally {
|
|
750
|
+
clearTimeout(timeout);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async function fetchBeefFromWoC(txid) {
|
|
754
|
+
try {
|
|
755
|
+
const resp = await wocFetch(`/tx/${txid}/beef`);
|
|
756
|
+
if (!resp.ok) return null;
|
|
757
|
+
const hexStr = (await resp.text()).trim();
|
|
758
|
+
if (!hexStr || hexStr.length < 8) return null;
|
|
759
|
+
const bytes = hexStr.match(/.{2}/g).map((h) => parseInt(h, 16));
|
|
760
|
+
return new Uint8Array(bytes);
|
|
761
|
+
} catch {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function getExplorerBaseUrl() {
|
|
766
|
+
return NETWORK === "mainnet" ? "https://whatsonchain.com" : "https://test.whatsonchain.com";
|
|
767
|
+
}
|
|
768
|
+
var init_woc = __esm({
|
|
769
|
+
"src/scripts/utils/woc.ts"() {
|
|
770
|
+
"use strict";
|
|
771
|
+
init_config();
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// src/scripts/utils/merkle.ts
|
|
776
|
+
async function getMerklePath() {
|
|
777
|
+
if (_MerklePath) return _MerklePath;
|
|
778
|
+
const sdk = await import("@bsv/sdk");
|
|
779
|
+
_MerklePath = sdk.MerklePath;
|
|
780
|
+
return _MerklePath;
|
|
781
|
+
}
|
|
782
|
+
async function buildMerklePathFromTSC(txid, txIndex, nodes, blockHeight) {
|
|
783
|
+
const MerklePath = await getMerklePath();
|
|
784
|
+
const treeHeight = nodes.length;
|
|
785
|
+
const mpPath = [];
|
|
786
|
+
const level0 = [
|
|
787
|
+
{ offset: txIndex, hash: txid, txid: true }
|
|
788
|
+
];
|
|
789
|
+
if (nodes[0] === "*") {
|
|
790
|
+
level0.push({ offset: txIndex ^ 1, duplicate: true });
|
|
791
|
+
} else {
|
|
792
|
+
level0.push({ offset: txIndex ^ 1, hash: nodes[0] });
|
|
793
|
+
}
|
|
794
|
+
level0.sort((a, b) => a.offset - b.offset);
|
|
795
|
+
mpPath.push(level0);
|
|
796
|
+
for (let i = 1; i < treeHeight; i++) {
|
|
797
|
+
const siblingOffset = txIndex >> i ^ 1;
|
|
798
|
+
if (nodes[i] === "*") {
|
|
799
|
+
mpPath.push([{ offset: siblingOffset, duplicate: true }]);
|
|
800
|
+
} else {
|
|
801
|
+
mpPath.push([{ offset: siblingOffset, hash: nodes[i] }]);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return new MerklePath(blockHeight, mpPath);
|
|
805
|
+
}
|
|
806
|
+
var _MerklePath;
|
|
807
|
+
var init_merkle = __esm({
|
|
808
|
+
"src/scripts/utils/merkle.ts"() {
|
|
809
|
+
"use strict";
|
|
810
|
+
_MerklePath = null;
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
// src/scripts/wallet/balance.ts
|
|
815
|
+
import fs5 from "node:fs";
|
|
816
|
+
async function getBSVAgentWallet2() {
|
|
817
|
+
return BSVAgentWallet;
|
|
818
|
+
}
|
|
819
|
+
async function getSdk3() {
|
|
820
|
+
if (_sdk3) return _sdk3;
|
|
821
|
+
try {
|
|
822
|
+
_sdk3 = await import("@bsv/sdk");
|
|
823
|
+
return _sdk3;
|
|
824
|
+
} catch {
|
|
825
|
+
const { fileURLToPath: fileURLToPath3 } = await import("node:url");
|
|
826
|
+
const path5 = await import("node:path");
|
|
827
|
+
const os3 = await import("node:os");
|
|
828
|
+
const __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
|
|
829
|
+
const candidates = [
|
|
830
|
+
path5.resolve(__dirname3, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
831
|
+
path5.resolve(__dirname3, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
832
|
+
path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
|
|
833
|
+
];
|
|
834
|
+
for (const p of candidates) {
|
|
835
|
+
try {
|
|
836
|
+
_sdk3 = await import(p);
|
|
837
|
+
return _sdk3;
|
|
838
|
+
} catch {
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function sleep(ms) {
|
|
845
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
846
|
+
}
|
|
847
|
+
async function cmdBalance() {
|
|
848
|
+
const BSVAgentWallet2 = await getBSVAgentWallet2();
|
|
849
|
+
const sdk = await getSdk3();
|
|
850
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
851
|
+
const total = await wallet.getBalance();
|
|
852
|
+
await wallet.destroy();
|
|
853
|
+
return ok({ walletBalance: total });
|
|
854
|
+
}
|
|
855
|
+
async function cmdImport(txidArg, voutStr) {
|
|
856
|
+
if (!txidArg) {
|
|
857
|
+
return fail("Usage: import <txid> [vout]");
|
|
858
|
+
}
|
|
859
|
+
const vout = parseInt(voutStr || "0", 10);
|
|
860
|
+
const txid = txidArg.toLowerCase();
|
|
861
|
+
if (!/^[0-9a-f]{64}$/.test(txid)) {
|
|
862
|
+
return fail("Invalid txid \u2014 must be 64 hex characters");
|
|
863
|
+
}
|
|
864
|
+
const sdk = await getSdk3();
|
|
865
|
+
const BSVAgentWallet2 = await getBSVAgentWallet2();
|
|
866
|
+
let txInfo = null;
|
|
867
|
+
const maxWaitMs = 6e4;
|
|
868
|
+
const startTime = Date.now();
|
|
869
|
+
let attempt = 0;
|
|
870
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
871
|
+
const txInfoResp = await wocFetch(`/tx/${txid}`, {}, 1, 1e4);
|
|
872
|
+
if (txInfoResp.ok) {
|
|
873
|
+
txInfo = await txInfoResp.json();
|
|
874
|
+
break;
|
|
875
|
+
} else if (txInfoResp.status === 404) {
|
|
876
|
+
attempt++;
|
|
877
|
+
const delayMs = Math.min(1e3 * Math.pow(1.5, attempt), 1e4);
|
|
878
|
+
console.error(`[import] Transaction not on WoC yet, waiting ${Math.round(delayMs / 1e3)}s... (attempt ${attempt})`);
|
|
879
|
+
await sleep(delayMs);
|
|
880
|
+
continue;
|
|
881
|
+
} else {
|
|
882
|
+
return fail(`Failed to fetch tx info: ${txInfoResp.status}`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (!txInfo) {
|
|
886
|
+
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.`);
|
|
887
|
+
}
|
|
888
|
+
const isConfirmed = txInfo.confirmations && txInfo.confirmations >= 1;
|
|
889
|
+
const blockHeight = txInfo.blockheight;
|
|
890
|
+
if (!txInfo.vout || !txInfo.vout[vout]) {
|
|
891
|
+
return fail(`Output index ${vout} not found in transaction (has ${txInfo.vout?.length || 0} outputs)`);
|
|
892
|
+
}
|
|
893
|
+
let atomicBeefBytes;
|
|
894
|
+
const wocBeefBytes = await fetchBeefFromWoC(txid);
|
|
895
|
+
if (wocBeefBytes) {
|
|
896
|
+
try {
|
|
897
|
+
const wocBeef = sdk.Beef.fromBinary(Array.from(wocBeefBytes));
|
|
898
|
+
const foundTx = wocBeef.findTxid(txid);
|
|
899
|
+
if (foundTx) {
|
|
900
|
+
const txObj = foundTx.tx || foundTx._tx;
|
|
901
|
+
if (txObj) {
|
|
902
|
+
const output = txObj.outputs[vout];
|
|
903
|
+
if (!output) {
|
|
904
|
+
return fail(`Output index ${vout} not found in BEEF transaction (has ${txObj.outputs.length} outputs)`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
atomicBeefBytes = wocBeef.toBinaryAtomic(txid);
|
|
908
|
+
}
|
|
909
|
+
} catch (beefErr) {
|
|
910
|
+
console.error(`[import] WoC BEEF parse failed: ${beefErr.message}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (!atomicBeefBytes && isConfirmed) {
|
|
914
|
+
try {
|
|
915
|
+
const rawTxResp = await wocFetch(`/tx/${txid}/hex`);
|
|
916
|
+
if (!rawTxResp.ok) {
|
|
917
|
+
return fail(`Failed to fetch raw transaction: ${rawTxResp.status}`);
|
|
918
|
+
}
|
|
919
|
+
const rawTxHex = await rawTxResp.text();
|
|
920
|
+
const sourceTx = sdk.Transaction.fromHex(rawTxHex.trim());
|
|
921
|
+
const proofResp = await wocFetch(`/tx/${txid}/proof/tsc`);
|
|
922
|
+
if (!proofResp.ok) {
|
|
923
|
+
return fail(`Failed to fetch merkle proof: ${proofResp.status}`);
|
|
924
|
+
}
|
|
925
|
+
const proofData = await proofResp.json();
|
|
926
|
+
if (!Array.isArray(proofData) || proofData.length === 0) {
|
|
927
|
+
return fail("Merkle proof not available from WoC");
|
|
928
|
+
}
|
|
929
|
+
const proof = proofData[0];
|
|
930
|
+
const merklePath = await buildMerklePathFromTSC(txid, proof.index, proof.nodes, blockHeight);
|
|
931
|
+
sourceTx.merklePath = merklePath;
|
|
932
|
+
const beef = new sdk.Beef();
|
|
933
|
+
beef.mergeTransaction(sourceTx);
|
|
934
|
+
atomicBeefBytes = beef.toBinaryAtomic(txid);
|
|
935
|
+
} catch (manualErr) {
|
|
936
|
+
return fail(`Failed to construct BEEF manually: ${manualErr.message}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (!atomicBeefBytes) {
|
|
940
|
+
if (isConfirmed) {
|
|
941
|
+
return fail(`Transaction ${txid} is confirmed but BEEF construction failed. This is unexpected \u2014 please report this issue.`);
|
|
942
|
+
} else {
|
|
943
|
+
return fail(
|
|
944
|
+
`Transaction ${txid} is unconfirmed (${txInfo.confirmations || 0} confirmations) and BEEF is not available.
|
|
945
|
+
|
|
946
|
+
This usually means the funding transaction spends from other unconfirmed transactions, creating a chain.
|
|
947
|
+
Wait for 1 block confirmation (~10 minutes) and try again, or use a fresh UTXO as the funding source.`
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const outputSatoshis = txInfo.vout[vout].value != null ? Math.round(txInfo.vout[vout].value * 1e8) : void 0;
|
|
952
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
953
|
+
const identityKey = await wallet.getIdentityKey();
|
|
954
|
+
try {
|
|
955
|
+
await wallet._setup.wallet.storage.internalizeAction({
|
|
956
|
+
tx: Array.from(atomicBeefBytes),
|
|
957
|
+
outputs: [{
|
|
958
|
+
outputIndex: vout,
|
|
959
|
+
protocol: "wallet payment",
|
|
960
|
+
paymentRemittance: {
|
|
961
|
+
derivationPrefix: sdk.Utils.toBase64(sdk.Utils.toArray("import", "utf8")),
|
|
962
|
+
derivationSuffix: sdk.Utils.toBase64(sdk.Utils.toArray("now", "utf8")),
|
|
963
|
+
senderIdentityKey: identityKey
|
|
964
|
+
}
|
|
965
|
+
}],
|
|
966
|
+
description: "External funding import"
|
|
967
|
+
});
|
|
968
|
+
const balance = await wallet.getBalance();
|
|
969
|
+
await wallet.destroy();
|
|
970
|
+
const explorerBase = getExplorerBaseUrl();
|
|
971
|
+
return ok({
|
|
972
|
+
txid,
|
|
973
|
+
vout,
|
|
974
|
+
satoshis: outputSatoshis,
|
|
975
|
+
blockHeight: blockHeight || null,
|
|
976
|
+
confirmations: txInfo.confirmations || 0,
|
|
977
|
+
imported: true,
|
|
978
|
+
unconfirmed: !isConfirmed,
|
|
979
|
+
balance,
|
|
980
|
+
explorer: `${explorerBase}/tx/${txid}`
|
|
981
|
+
});
|
|
982
|
+
} catch (err) {
|
|
983
|
+
await wallet.destroy();
|
|
984
|
+
if (err.message?.includes("already") || err.message?.includes("duplicate")) {
|
|
985
|
+
return fail(`UTXO ${txid}:${vout} appears to already be imported.`);
|
|
986
|
+
}
|
|
987
|
+
if (err.message?.includes("script") || err.message?.includes("locking")) {
|
|
988
|
+
return fail(`UTXO ${txid}:${vout} does not belong to this wallet's address. Make sure you sent to the correct address.`);
|
|
989
|
+
}
|
|
990
|
+
return fail(`Failed to import UTXO: ${err.message}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
async function cmdRefund(targetAddress) {
|
|
994
|
+
if (!targetAddress) {
|
|
995
|
+
return fail("Usage: refund <address>");
|
|
996
|
+
}
|
|
997
|
+
if (!fs5.existsSync(PATHS.walletIdentity)) {
|
|
998
|
+
return fail("Wallet not initialized. Run: setup");
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
var _sdk3;
|
|
1002
|
+
var init_balance = __esm({
|
|
1003
|
+
"src/scripts/wallet/balance.ts"() {
|
|
1004
|
+
"use strict";
|
|
1005
|
+
init_config();
|
|
1006
|
+
init_output();
|
|
1007
|
+
init_woc();
|
|
1008
|
+
init_merkle();
|
|
1009
|
+
init_dist();
|
|
1010
|
+
_sdk3 = null;
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
// src/scripts/utils/storage.ts
|
|
1015
|
+
import fs6 from "node:fs";
|
|
1016
|
+
function ensureStateDir() {
|
|
1017
|
+
fs6.mkdirSync(OVERLAY_STATE_DIR, { recursive: true });
|
|
1018
|
+
}
|
|
1019
|
+
function loadRegistration() {
|
|
1020
|
+
try {
|
|
1021
|
+
if (fs6.existsSync(PATHS.registration)) {
|
|
1022
|
+
return JSON.parse(fs6.readFileSync(PATHS.registration, "utf-8"));
|
|
1023
|
+
}
|
|
1024
|
+
} catch {
|
|
1025
|
+
}
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
function saveRegistration(data) {
|
|
1029
|
+
ensureStateDir();
|
|
1030
|
+
fs6.writeFileSync(PATHS.registration, JSON.stringify(data, null, 2), "utf-8");
|
|
1031
|
+
}
|
|
1032
|
+
function deleteRegistration() {
|
|
1033
|
+
try {
|
|
1034
|
+
fs6.unlinkSync(PATHS.registration);
|
|
1035
|
+
} catch {
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function loadServices() {
|
|
1039
|
+
try {
|
|
1040
|
+
if (fs6.existsSync(PATHS.services)) {
|
|
1041
|
+
return JSON.parse(fs6.readFileSync(PATHS.services, "utf-8"));
|
|
1042
|
+
}
|
|
1043
|
+
} catch {
|
|
1044
|
+
}
|
|
1045
|
+
return [];
|
|
1046
|
+
}
|
|
1047
|
+
function saveServices(services) {
|
|
1048
|
+
ensureStateDir();
|
|
1049
|
+
fs6.writeFileSync(PATHS.services, JSON.stringify(services, null, 2), "utf-8");
|
|
1050
|
+
}
|
|
1051
|
+
function loadXVerifications() {
|
|
1052
|
+
try {
|
|
1053
|
+
if (fs6.existsSync(PATHS.xVerifications)) {
|
|
1054
|
+
return JSON.parse(fs6.readFileSync(PATHS.xVerifications, "utf-8"));
|
|
1055
|
+
}
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
return [];
|
|
1059
|
+
}
|
|
1060
|
+
function saveXVerifications(verifications) {
|
|
1061
|
+
ensureStateDir();
|
|
1062
|
+
fs6.writeFileSync(PATHS.xVerifications, JSON.stringify(verifications, null, 2), "utf-8");
|
|
1063
|
+
}
|
|
1064
|
+
function appendToJsonl(filePath, entry) {
|
|
1065
|
+
ensureStateDir();
|
|
1066
|
+
fs6.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
1067
|
+
}
|
|
1068
|
+
function readJsonl(filePath) {
|
|
1069
|
+
if (!fs6.existsSync(filePath)) return [];
|
|
1070
|
+
const lines = fs6.readFileSync(filePath, "utf-8").trim().split("\n").filter(Boolean);
|
|
1071
|
+
return lines.map((line) => {
|
|
1072
|
+
try {
|
|
1073
|
+
return JSON.parse(line);
|
|
1074
|
+
} catch {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}).filter(Boolean);
|
|
1078
|
+
}
|
|
1079
|
+
function updateServiceQueueStatus(requestId, newStatus, additionalFields = {}) {
|
|
1080
|
+
if (!fs6.existsSync(PATHS.serviceQueue)) return false;
|
|
1081
|
+
const lines = fs6.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
|
|
1082
|
+
let updated = false;
|
|
1083
|
+
const updatedLines = lines.map((line) => {
|
|
1084
|
+
try {
|
|
1085
|
+
const entry = JSON.parse(line);
|
|
1086
|
+
if (entry.requestId === requestId) {
|
|
1087
|
+
updated = true;
|
|
1088
|
+
return JSON.stringify({
|
|
1089
|
+
...entry,
|
|
1090
|
+
status: newStatus,
|
|
1091
|
+
...additionalFields,
|
|
1092
|
+
updatedAt: Date.now()
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return line;
|
|
1096
|
+
} catch {
|
|
1097
|
+
return line;
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
if (updated) {
|
|
1101
|
+
fs6.writeFileSync(PATHS.serviceQueue, updatedLines.join("\n") + "\n");
|
|
1102
|
+
}
|
|
1103
|
+
return updated;
|
|
1104
|
+
}
|
|
1105
|
+
var init_storage = __esm({
|
|
1106
|
+
"src/scripts/utils/storage.ts"() {
|
|
1107
|
+
"use strict";
|
|
1108
|
+
init_config();
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
// src/scripts/overlay/transaction.ts
|
|
1113
|
+
import { Utils as Utils4, PushDrop, Transaction } from "@bsv/sdk";
|
|
1114
|
+
async function buildPushDropScript(wallet, payload) {
|
|
1115
|
+
const jsonBytes = Utils4.toArray(JSON.stringify(payload), "utf8");
|
|
1116
|
+
const fields = [jsonBytes];
|
|
1117
|
+
const token = new PushDrop(wallet._setup.wallet);
|
|
1118
|
+
const script = await token.lock(fields, [0, PROTOCOL_ID], "1", "self", true, true);
|
|
1119
|
+
return script.toHex();
|
|
1120
|
+
}
|
|
1121
|
+
async function buildRealOverlayTransaction(payload, topic) {
|
|
1122
|
+
const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1123
|
+
const lockingScript = await buildPushDropScript(wallet, payload);
|
|
1124
|
+
const response = await wallet._setup.wallet.createAction({
|
|
1125
|
+
description: "topic manager submission",
|
|
1126
|
+
outputs: [
|
|
1127
|
+
{
|
|
1128
|
+
lockingScript,
|
|
1129
|
+
satoshis: 1,
|
|
1130
|
+
outputDescription: "overlay",
|
|
1131
|
+
basket: topic
|
|
1132
|
+
// basket is the topic manager
|
|
1133
|
+
}
|
|
1134
|
+
],
|
|
1135
|
+
options: {
|
|
1136
|
+
acceptDelayedBroadcast: false
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
const submitResp = await fetch(`${OVERLAY_URL}/submit`, {
|
|
1140
|
+
method: "POST",
|
|
1141
|
+
headers: {
|
|
1142
|
+
"Content-Type": "application/octet-stream",
|
|
1143
|
+
"X-Topics": JSON.stringify([topic])
|
|
1144
|
+
},
|
|
1145
|
+
body: new Uint8Array(response.tx)
|
|
1146
|
+
});
|
|
1147
|
+
if (!submitResp.ok) {
|
|
1148
|
+
const errText = await submitResp.text();
|
|
1149
|
+
throw new Error(`Overlay submission failed: ${submitResp.status} \u2014 ${errText}`);
|
|
1150
|
+
}
|
|
1151
|
+
const wocNet = NETWORK === "mainnet" ? "" : "test.";
|
|
1152
|
+
return {
|
|
1153
|
+
txid: response.txid,
|
|
1154
|
+
funded: "stored-beef",
|
|
1155
|
+
explorer: `https://${wocNet}whatsonchain.com/tx/${response.txid}`
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
async function lookupOverlay(service, query) {
|
|
1159
|
+
const resp = await fetch(`${OVERLAY_URL}/lookup`, {
|
|
1160
|
+
method: "POST",
|
|
1161
|
+
headers: { "Content-Type": "application/json" },
|
|
1162
|
+
body: JSON.stringify({ service, query })
|
|
1163
|
+
});
|
|
1164
|
+
if (!resp.ok) {
|
|
1165
|
+
const errText = await resp.text();
|
|
1166
|
+
throw new Error(`Lookup failed: ${resp.status} \u2014 ${errText}`);
|
|
1167
|
+
}
|
|
1168
|
+
return resp.json();
|
|
1169
|
+
}
|
|
1170
|
+
async function parseOverlayOutput(beefData, outputIndex) {
|
|
1171
|
+
try {
|
|
1172
|
+
const tx = Transaction.fromBEEF(beefData);
|
|
1173
|
+
const txid = tx.id("hex");
|
|
1174
|
+
const output = tx.outputs[outputIndex];
|
|
1175
|
+
if (!output) return { data: null, txid: null };
|
|
1176
|
+
const { fields } = PushDrop.decode(output.lockingScript);
|
|
1177
|
+
return { data: JSON.parse(Utils4.toUTF8(fields[0])), txid };
|
|
1178
|
+
} catch {
|
|
1179
|
+
return { data: null, txid: null };
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
var init_transaction = __esm({
|
|
1183
|
+
"src/scripts/overlay/transaction.ts"() {
|
|
1184
|
+
"use strict";
|
|
1185
|
+
init_config();
|
|
1186
|
+
init_dist();
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
// src/scripts/overlay/registration.ts
|
|
1191
|
+
import fs7 from "node:fs";
|
|
1192
|
+
import { Transaction as Transaction2, Beef as Beef3, Script, PushDrop as PushDrop2 } from "@bsv/sdk";
|
|
1193
|
+
async function getBSVAgentWallet3() {
|
|
1194
|
+
return BSVAgentWallet;
|
|
1195
|
+
}
|
|
1196
|
+
async function cmdRegister() {
|
|
1197
|
+
if (!fs7.existsSync(PATHS.walletIdentity)) {
|
|
1198
|
+
return fail("Wallet not initialized. Run: setup");
|
|
1199
|
+
}
|
|
1200
|
+
const BSVAgentWallet2 = await getBSVAgentWallet3();
|
|
1201
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1202
|
+
const identityKey = await wallet.getIdentityKey();
|
|
1203
|
+
await wallet.destroy();
|
|
1204
|
+
const existingReg = loadRegistration();
|
|
1205
|
+
if (existingReg && existingReg.identityKey === identityKey) {
|
|
1206
|
+
return ok({
|
|
1207
|
+
alreadyRegistered: true,
|
|
1208
|
+
identityKey,
|
|
1209
|
+
identityTxid: existingReg.identityTxid,
|
|
1210
|
+
overlayUrl: OVERLAY_URL
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
const agentName = AGENT_NAME;
|
|
1214
|
+
const agentDescription = AGENT_DESCRIPTION;
|
|
1215
|
+
const capabilities = ["services"];
|
|
1216
|
+
const services = loadServices();
|
|
1217
|
+
if (services.some((s) => s.serviceId === "tell-joke")) {
|
|
1218
|
+
capabilities.push("jokes");
|
|
1219
|
+
}
|
|
1220
|
+
const identityPayload = {
|
|
1221
|
+
protocol: PROTOCOL_ID,
|
|
1222
|
+
type: "identity",
|
|
1223
|
+
identityKey,
|
|
1224
|
+
name: agentName,
|
|
1225
|
+
description: agentDescription,
|
|
1226
|
+
channels: {
|
|
1227
|
+
overlay: OVERLAY_URL
|
|
1228
|
+
},
|
|
1229
|
+
capabilities,
|
|
1230
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1231
|
+
};
|
|
1232
|
+
let identityResult;
|
|
1233
|
+
try {
|
|
1234
|
+
identityResult = await buildRealOverlayTransaction(identityPayload, TOPICS.IDENTITY);
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
return fail(`Registration failed: ${err.message}`);
|
|
1237
|
+
}
|
|
1238
|
+
let serviceTxid = null;
|
|
1239
|
+
if (services.length > 0) {
|
|
1240
|
+
for (const service of services) {
|
|
1241
|
+
const servicePayload = {
|
|
1242
|
+
protocol: PROTOCOL_ID,
|
|
1243
|
+
type: "service",
|
|
1244
|
+
identityKey,
|
|
1245
|
+
serviceId: service.serviceId,
|
|
1246
|
+
name: service.name,
|
|
1247
|
+
description: service.description,
|
|
1248
|
+
pricing: {
|
|
1249
|
+
model: "per-task",
|
|
1250
|
+
amountSats: service.priceSats
|
|
1251
|
+
},
|
|
1252
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1253
|
+
};
|
|
1254
|
+
try {
|
|
1255
|
+
const serviceResult = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
|
|
1256
|
+
serviceTxid = serviceResult.txid;
|
|
1257
|
+
} catch {
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const registration = {
|
|
1262
|
+
identityKey,
|
|
1263
|
+
agentName,
|
|
1264
|
+
agentDescription,
|
|
1265
|
+
overlayUrl: OVERLAY_URL,
|
|
1266
|
+
identityTxid: identityResult.txid,
|
|
1267
|
+
serviceTxid,
|
|
1268
|
+
funded: identityResult.funded,
|
|
1269
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1270
|
+
};
|
|
1271
|
+
saveRegistration(registration);
|
|
1272
|
+
return ok({
|
|
1273
|
+
registered: true,
|
|
1274
|
+
identityKey,
|
|
1275
|
+
identityTxid: identityResult.txid,
|
|
1276
|
+
serviceTxid,
|
|
1277
|
+
overlayUrl: OVERLAY_URL,
|
|
1278
|
+
funded: identityResult.funded,
|
|
1279
|
+
stateFile: PATHS.registration
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
async function cmdUnregister() {
|
|
1283
|
+
const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1284
|
+
const { outputs, BEEF } = await wallet._setup.wallet.listOutputs({ basket: TOPICS.IDENTITY, include: "entire transactions" });
|
|
1285
|
+
const token = new PushDrop2(wallet._setup.wallet);
|
|
1286
|
+
const unlockingScriptTemplate = await token.unlock([0, PROTOCOL_ID], "1", "self", "none", true);
|
|
1287
|
+
const tempTx = new Transaction2();
|
|
1288
|
+
const beef = Beef3.fromBinary(BEEF);
|
|
1289
|
+
outputs.forEach((o) => {
|
|
1290
|
+
const [txid2, v] = o.outpoint.split(".");
|
|
1291
|
+
const sourceOutputIndex = Number(v);
|
|
1292
|
+
const sourceTransaction = beef.findTransactionForSigning(txid2);
|
|
1293
|
+
tempTx.addInput({
|
|
1294
|
+
unlockingScriptTemplate,
|
|
1295
|
+
sourceOutputIndex,
|
|
1296
|
+
sourceTransaction
|
|
1297
|
+
});
|
|
1298
|
+
});
|
|
1299
|
+
tempTx.addOutput({
|
|
1300
|
+
lockingScript: Script.fromASM("OP_FALSE OP_RETURN 330123"),
|
|
1301
|
+
satoshis: 0
|
|
1302
|
+
});
|
|
1303
|
+
await tempTx.sign();
|
|
1304
|
+
const response = await wallet._setup.wallet.createAction({
|
|
1305
|
+
inputBEEF: BEEF,
|
|
1306
|
+
description: "revoke registration token",
|
|
1307
|
+
inputs: tempTx.inputs.map((o) => ({
|
|
1308
|
+
inputDescription: "previous registration",
|
|
1309
|
+
outpoint: o.sourceTXID + "." + String(o.sourceOutputIndex),
|
|
1310
|
+
unlockingScript: o.unlockingScript?.toHex()
|
|
1311
|
+
}))
|
|
1312
|
+
});
|
|
1313
|
+
const txid = response.txid;
|
|
1314
|
+
const submitResp = await fetch(`${OVERLAY_URL}/submit`, {
|
|
1315
|
+
method: "POST",
|
|
1316
|
+
headers: {
|
|
1317
|
+
"Content-Type": "application/octet-stream",
|
|
1318
|
+
"X-Topics": JSON.stringify([TOPICS.IDENTITY])
|
|
1319
|
+
},
|
|
1320
|
+
body: new Uint8Array(response.tx)
|
|
1321
|
+
});
|
|
1322
|
+
if (!submitResp.ok) {
|
|
1323
|
+
const errText = await submitResp.text();
|
|
1324
|
+
throw new Error(`Overlay submission failed: ${submitResp.status} \u2014 ${errText}`);
|
|
1325
|
+
}
|
|
1326
|
+
deleteRegistration();
|
|
1327
|
+
return ok({
|
|
1328
|
+
unregistered: true,
|
|
1329
|
+
txid
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
var init_registration = __esm({
|
|
1333
|
+
"src/scripts/overlay/registration.ts"() {
|
|
1334
|
+
"use strict";
|
|
1335
|
+
init_config();
|
|
1336
|
+
init_output();
|
|
1337
|
+
init_storage();
|
|
1338
|
+
init_transaction();
|
|
1339
|
+
init_dist();
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
// src/scripts/overlay/services.ts
|
|
1344
|
+
async function getBSVAgentWallet4() {
|
|
1345
|
+
return BSVAgentWallet;
|
|
1346
|
+
}
|
|
1347
|
+
async function cmdServices() {
|
|
1348
|
+
const services = loadServices();
|
|
1349
|
+
return ok({ services, count: services.length });
|
|
1350
|
+
}
|
|
1351
|
+
async function cmdAdvertise(serviceId, name, priceSatsStr, description) {
|
|
1352
|
+
if (!serviceId || !name || !priceSatsStr) {
|
|
1353
|
+
return fail("Usage: advertise <serviceId> <name> <priceSats> [description]");
|
|
1354
|
+
}
|
|
1355
|
+
const priceSats = parseInt(priceSatsStr, 10);
|
|
1356
|
+
if (isNaN(priceSats) || priceSats < 0) {
|
|
1357
|
+
return fail("priceSats must be a non-negative integer");
|
|
1358
|
+
}
|
|
1359
|
+
const BSVAgentWallet2 = await getBSVAgentWallet4();
|
|
1360
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1361
|
+
const identityKey = await wallet.getIdentityKey();
|
|
1362
|
+
await wallet.destroy();
|
|
1363
|
+
const services = loadServices();
|
|
1364
|
+
const existing = services.find((s) => s.serviceId === serviceId);
|
|
1365
|
+
if (existing) {
|
|
1366
|
+
return fail(`Service '${serviceId}' already exists. Use 'readvertise' to update.`);
|
|
1367
|
+
}
|
|
1368
|
+
const newService = {
|
|
1369
|
+
serviceId,
|
|
1370
|
+
name,
|
|
1371
|
+
description: description || `${name} service`,
|
|
1372
|
+
priceSats,
|
|
1373
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1374
|
+
};
|
|
1375
|
+
const servicePayload = {
|
|
1376
|
+
protocol: PROTOCOL_ID,
|
|
1377
|
+
type: "service",
|
|
1378
|
+
identityKey,
|
|
1379
|
+
serviceId,
|
|
1380
|
+
name,
|
|
1381
|
+
description: newService.description,
|
|
1382
|
+
pricing: {
|
|
1383
|
+
model: "per-task",
|
|
1384
|
+
amountSats: priceSats
|
|
1385
|
+
},
|
|
1386
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1387
|
+
};
|
|
1388
|
+
try {
|
|
1389
|
+
const result = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
|
|
1390
|
+
newService.txid = result.txid;
|
|
1391
|
+
services.push(newService);
|
|
1392
|
+
saveServices(services);
|
|
1393
|
+
return ok({
|
|
1394
|
+
advertised: true,
|
|
1395
|
+
service: newService,
|
|
1396
|
+
txid: result.txid,
|
|
1397
|
+
funded: result.funded
|
|
1398
|
+
});
|
|
1399
|
+
} catch (err) {
|
|
1400
|
+
return fail(`Failed to advertise service: ${err.message}`);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
async function cmdRemove(serviceId) {
|
|
1404
|
+
if (!serviceId) {
|
|
1405
|
+
return fail("Usage: remove <serviceId>");
|
|
1406
|
+
}
|
|
1407
|
+
const services = loadServices();
|
|
1408
|
+
const idx = services.findIndex((s) => s.serviceId === serviceId);
|
|
1409
|
+
if (idx === -1) {
|
|
1410
|
+
return fail(`Service '${serviceId}' not found`);
|
|
1411
|
+
}
|
|
1412
|
+
const removed = services.splice(idx, 1)[0];
|
|
1413
|
+
saveServices(services);
|
|
1414
|
+
return ok({
|
|
1415
|
+
removed: true,
|
|
1416
|
+
service: removed,
|
|
1417
|
+
note: "Removed from local registry. On-chain record remains (blockchain is immutable)."
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
async function cmdReadvertise(serviceId, name, priceSatsStr, description) {
|
|
1421
|
+
if (!serviceId) {
|
|
1422
|
+
return fail("Usage: readvertise <serviceId> [name] [priceSats] [description]");
|
|
1423
|
+
}
|
|
1424
|
+
const services = loadServices();
|
|
1425
|
+
const existing = services.find((s) => s.serviceId === serviceId);
|
|
1426
|
+
if (!existing) {
|
|
1427
|
+
return fail(`Service '${serviceId}' not found. Use 'advertise' to create.`);
|
|
1428
|
+
}
|
|
1429
|
+
const BSVAgentWallet2 = await getBSVAgentWallet4();
|
|
1430
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1431
|
+
const identityKey = await wallet.getIdentityKey();
|
|
1432
|
+
await wallet.destroy();
|
|
1433
|
+
if (name) existing.name = name;
|
|
1434
|
+
if (priceSatsStr) {
|
|
1435
|
+
const priceSats = parseInt(priceSatsStr, 10);
|
|
1436
|
+
if (isNaN(priceSats) || priceSats < 0) {
|
|
1437
|
+
return fail("priceSats must be a non-negative integer");
|
|
1438
|
+
}
|
|
1439
|
+
existing.priceSats = priceSats;
|
|
1440
|
+
}
|
|
1441
|
+
if (description) existing.description = description;
|
|
1442
|
+
existing.registeredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1443
|
+
const servicePayload = {
|
|
1444
|
+
protocol: PROTOCOL_ID,
|
|
1445
|
+
type: "service",
|
|
1446
|
+
identityKey,
|
|
1447
|
+
serviceId,
|
|
1448
|
+
name: existing.name,
|
|
1449
|
+
description: existing.description,
|
|
1450
|
+
pricing: {
|
|
1451
|
+
model: "per-task",
|
|
1452
|
+
amountSats: existing.priceSats
|
|
1453
|
+
},
|
|
1454
|
+
timestamp: existing.registeredAt
|
|
1455
|
+
};
|
|
1456
|
+
try {
|
|
1457
|
+
const result = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
|
|
1458
|
+
existing.txid = result.txid;
|
|
1459
|
+
saveServices(services);
|
|
1460
|
+
return ok({
|
|
1461
|
+
readvertised: true,
|
|
1462
|
+
service: existing,
|
|
1463
|
+
txid: result.txid,
|
|
1464
|
+
funded: result.funded
|
|
1465
|
+
});
|
|
1466
|
+
} catch (err) {
|
|
1467
|
+
return fail(`Failed to readvertise service: ${err.message}`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
var init_services = __esm({
|
|
1471
|
+
"src/scripts/overlay/services.ts"() {
|
|
1472
|
+
"use strict";
|
|
1473
|
+
init_config();
|
|
1474
|
+
init_output();
|
|
1475
|
+
init_storage();
|
|
1476
|
+
init_transaction();
|
|
1477
|
+
init_dist();
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
// src/scripts/overlay/discover.ts
|
|
1482
|
+
async function cmdDiscover(args2) {
|
|
1483
|
+
let serviceFilter = null;
|
|
1484
|
+
let agentFilter = null;
|
|
1485
|
+
for (let i = 0; i < args2.length; i++) {
|
|
1486
|
+
if (args2[i] === "--service" && args2[i + 1]) serviceFilter = args2[++i];
|
|
1487
|
+
else if (args2[i] === "--agent" && args2[i + 1]) agentFilter = args2[++i];
|
|
1488
|
+
}
|
|
1489
|
+
const results = { agents: [], services: [] };
|
|
1490
|
+
if (!serviceFilter) {
|
|
1491
|
+
try {
|
|
1492
|
+
const agentQuery = agentFilter ? { name: agentFilter } : { type: "list" };
|
|
1493
|
+
const agentResult = await lookupOverlay(LOOKUP_SERVICES.AGENTS, agentQuery);
|
|
1494
|
+
if (agentResult.outputs) {
|
|
1495
|
+
for (const output of agentResult.outputs) {
|
|
1496
|
+
try {
|
|
1497
|
+
const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
|
|
1498
|
+
if (data?.type === "identity") {
|
|
1499
|
+
const name = data.name || data.agentName || "Unknown Agent";
|
|
1500
|
+
results.agents.push({ ...data, name, txid });
|
|
1501
|
+
}
|
|
1502
|
+
} catch {
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
results.agentError = String(err);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
if (!agentFilter) {
|
|
1511
|
+
try {
|
|
1512
|
+
const serviceQuery = serviceFilter ? { serviceType: serviceFilter } : {};
|
|
1513
|
+
const serviceResult = await lookupOverlay(LOOKUP_SERVICES.SERVICES, serviceQuery);
|
|
1514
|
+
if (serviceResult.outputs) {
|
|
1515
|
+
for (const output of serviceResult.outputs) {
|
|
1516
|
+
try {
|
|
1517
|
+
const { data, txid } = await parseOverlayOutput(output.beef, output.outputIndex);
|
|
1518
|
+
if (data?.type === "service") {
|
|
1519
|
+
results.services.push({ ...data, txid });
|
|
1520
|
+
}
|
|
1521
|
+
} catch {
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
} catch (err) {
|
|
1526
|
+
results.serviceError = String(err);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return ok({
|
|
1530
|
+
overlayUrl: OVERLAY_URL,
|
|
1531
|
+
agentCount: results.agents.length,
|
|
1532
|
+
serviceCount: results.services.length,
|
|
1533
|
+
agents: results.agents,
|
|
1534
|
+
services: results.services,
|
|
1535
|
+
...results.agentError && { agentError: results.agentError },
|
|
1536
|
+
...results.serviceError && { serviceError: results.serviceError }
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
var init_discover = __esm({
|
|
1540
|
+
"src/scripts/overlay/discover.ts"() {
|
|
1541
|
+
"use strict";
|
|
1542
|
+
init_config();
|
|
1543
|
+
init_output();
|
|
1544
|
+
init_transaction();
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
// src/scripts/overlay/advertisement.ts
|
|
1549
|
+
import { PushDrop as PushDrop3, Utils as Utils5 } from "@bsv/sdk";
|
|
1550
|
+
async function cmdAdvertiseSHIP(domain, topic) {
|
|
1551
|
+
if (!domain || !topic) {
|
|
1552
|
+
return fail("Usage: advertise-ship <domain> <topic>");
|
|
1553
|
+
}
|
|
1554
|
+
if (!topic.startsWith("tm_")) {
|
|
1555
|
+
return fail('Topic must start with "tm_"');
|
|
1556
|
+
}
|
|
1557
|
+
try {
|
|
1558
|
+
const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1559
|
+
const token = new PushDrop3(wallet._setup.wallet);
|
|
1560
|
+
const fields = [Utils5.toArray(domain, "utf8")];
|
|
1561
|
+
const lockingScript = (await token.lock(fields, [0, topic], "1", "self", true, true)).toHex();
|
|
1562
|
+
const response = await wallet._setup.wallet.createAction({
|
|
1563
|
+
description: `advertise SHIP for ${topic}`,
|
|
1564
|
+
outputs: [{
|
|
1565
|
+
lockingScript,
|
|
1566
|
+
satoshis: 1,
|
|
1567
|
+
outputDescription: "SHIP advertisement",
|
|
1568
|
+
basket: TOPICS.SHIP
|
|
1569
|
+
}]
|
|
1570
|
+
});
|
|
1571
|
+
const trackers = [
|
|
1572
|
+
...DEFAULT_SLAP_TRACKERS[NETWORK]
|
|
1573
|
+
];
|
|
1574
|
+
const results = await broadcastToTrackers(response.tx, [TOPICS.SHIP, topic], trackers);
|
|
1575
|
+
return ok({
|
|
1576
|
+
advertised: "SHIP",
|
|
1577
|
+
topic,
|
|
1578
|
+
domain,
|
|
1579
|
+
txid: response.txid,
|
|
1580
|
+
broadcasts: results
|
|
1581
|
+
});
|
|
1582
|
+
} catch (err) {
|
|
1583
|
+
return fail(`SHIP advertisement failed: ${err.message}`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
async function cmdAdvertiseSLAP(domain, service) {
|
|
1587
|
+
if (!domain || !service) {
|
|
1588
|
+
return fail("Usage: advertise-slap <domain> <service>");
|
|
1589
|
+
}
|
|
1590
|
+
if (!service.startsWith("ls_")) {
|
|
1591
|
+
return fail('Service must start with "ls_"');
|
|
1592
|
+
}
|
|
1593
|
+
try {
|
|
1594
|
+
const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1595
|
+
const token = new PushDrop3(wallet._setup.wallet);
|
|
1596
|
+
const fields = [Utils5.toArray(domain, "utf8")];
|
|
1597
|
+
const lockingScript = (await token.lock(fields, [0, service], "1", "self", true, true)).toHex();
|
|
1598
|
+
const response = await wallet._setup.wallet.createAction({
|
|
1599
|
+
description: `advertise SLAP for ${service}`,
|
|
1600
|
+
outputs: [{
|
|
1601
|
+
lockingScript,
|
|
1602
|
+
satoshis: 1,
|
|
1603
|
+
outputDescription: "SLAP advertisement",
|
|
1604
|
+
basket: TOPICS.SLAP
|
|
1605
|
+
}]
|
|
1606
|
+
});
|
|
1607
|
+
const trackers = [
|
|
1608
|
+
...DEFAULT_SLAP_TRACKERS[NETWORK]
|
|
1609
|
+
];
|
|
1610
|
+
const results = await broadcastToTrackers(response.tx, [TOPICS.SLAP, service], trackers);
|
|
1611
|
+
return ok({
|
|
1612
|
+
advertised: "SLAP",
|
|
1613
|
+
service,
|
|
1614
|
+
domain,
|
|
1615
|
+
txid: response.txid,
|
|
1616
|
+
broadcasts: results
|
|
1617
|
+
});
|
|
1618
|
+
} catch (err) {
|
|
1619
|
+
return fail(`SLAP advertisement failed: ${err.message}`);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async function broadcastToTrackers(tx, topics, trackers) {
|
|
1623
|
+
const results = {};
|
|
1624
|
+
const body = new Uint8Array(tx);
|
|
1625
|
+
for (const url of trackers) {
|
|
1626
|
+
try {
|
|
1627
|
+
const resp = await fetch(`${url.replace(/\/$/, "")}/submit`, {
|
|
1628
|
+
method: "POST",
|
|
1629
|
+
headers: {
|
|
1630
|
+
"Content-Type": "application/octet-stream",
|
|
1631
|
+
"X-Topics": JSON.stringify(topics)
|
|
1632
|
+
},
|
|
1633
|
+
body
|
|
1634
|
+
});
|
|
1635
|
+
results[url] = resp.ok ? "success" : `error: ${resp.status}`;
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
results[url] = `failed: ${err.message}`;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return results;
|
|
1641
|
+
}
|
|
1642
|
+
var init_advertisement = __esm({
|
|
1643
|
+
"src/scripts/overlay/advertisement.ts"() {
|
|
1644
|
+
"use strict";
|
|
1645
|
+
init_config();
|
|
1646
|
+
init_dist();
|
|
1647
|
+
init_output();
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
// src/scripts/payment/build.ts
|
|
1652
|
+
async function getBSVAgentWallet5() {
|
|
1653
|
+
return BSVAgentWallet;
|
|
1654
|
+
}
|
|
1655
|
+
async function buildDirectPayment(recipientPubKey, sats, desc) {
|
|
1656
|
+
if (!/^0[23][0-9a-fA-F]{64}$/.test(recipientPubKey)) {
|
|
1657
|
+
throw new Error("Recipient must be a compressed public key (66 hex chars starting with 02 or 03)");
|
|
1658
|
+
}
|
|
1659
|
+
const BSVAgentWallet2 = await getBSVAgentWallet5();
|
|
1660
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1661
|
+
try {
|
|
1662
|
+
const result = await wallet.createPayment({
|
|
1663
|
+
to: recipientPubKey,
|
|
1664
|
+
satoshis: sats,
|
|
1665
|
+
description: desc || "agent payment"
|
|
1666
|
+
});
|
|
1667
|
+
return {
|
|
1668
|
+
beef: result.beef,
|
|
1669
|
+
txid: result.txid,
|
|
1670
|
+
satoshis: result.satoshis,
|
|
1671
|
+
derivationPrefix: result.derivationPrefix,
|
|
1672
|
+
derivationSuffix: result.derivationSuffix,
|
|
1673
|
+
senderIdentityKey: result.senderIdentityKey
|
|
1674
|
+
};
|
|
1675
|
+
} finally {
|
|
1676
|
+
await wallet.destroy();
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
var init_build = __esm({
|
|
1680
|
+
"src/scripts/payment/build.ts"() {
|
|
1681
|
+
"use strict";
|
|
1682
|
+
init_config();
|
|
1683
|
+
init_dist();
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
// src/scripts/payment/commands.ts
|
|
1688
|
+
async function getBSVAgentWallet6() {
|
|
1689
|
+
return BSVAgentWallet;
|
|
1690
|
+
}
|
|
1691
|
+
async function cmdPay(pubkey, satoshis, description) {
|
|
1692
|
+
if (!pubkey || !satoshis) {
|
|
1693
|
+
return fail("Usage: pay <pubkey> <satoshis> [description]");
|
|
1694
|
+
}
|
|
1695
|
+
const sats = parseInt(satoshis, 10);
|
|
1696
|
+
if (isNaN(sats) || sats <= 0) {
|
|
1697
|
+
return fail("satoshis must be a positive integer");
|
|
1698
|
+
}
|
|
1699
|
+
try {
|
|
1700
|
+
const payment = await buildDirectPayment(pubkey, sats, description || "agent payment");
|
|
1701
|
+
return ok(payment);
|
|
1702
|
+
} catch (err) {
|
|
1703
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
async function cmdVerify(beefBase64) {
|
|
1707
|
+
if (!beefBase64) {
|
|
1708
|
+
return fail("Usage: verify <beef_base64>");
|
|
1709
|
+
}
|
|
1710
|
+
const BSVAgentWallet2 = await getBSVAgentWallet6();
|
|
1711
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1712
|
+
try {
|
|
1713
|
+
const result = wallet.verifyPayment({ beef: beefBase64 });
|
|
1714
|
+
await wallet.destroy();
|
|
1715
|
+
return ok(result);
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
await wallet.destroy();
|
|
1718
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
async function cmdAccept(beef, derivationPrefix, derivationSuffix, senderIdentityKey, description) {
|
|
1722
|
+
if (!beef || !derivationPrefix || !derivationSuffix || !senderIdentityKey) {
|
|
1723
|
+
return fail("Usage: accept <beef> <prefix> <suffix> <senderKey> [description]");
|
|
1724
|
+
}
|
|
1725
|
+
const BSVAgentWallet2 = await getBSVAgentWallet6();
|
|
1726
|
+
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1727
|
+
try {
|
|
1728
|
+
const receipt = await wallet.acceptPayment({
|
|
1729
|
+
beef,
|
|
1730
|
+
derivationPrefix,
|
|
1731
|
+
derivationSuffix,
|
|
1732
|
+
senderIdentityKey,
|
|
1733
|
+
description: description || void 0
|
|
1734
|
+
});
|
|
1735
|
+
await wallet.destroy();
|
|
1736
|
+
return ok(receipt);
|
|
1737
|
+
} catch (err) {
|
|
1738
|
+
await wallet.destroy();
|
|
1739
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
var init_commands = __esm({
|
|
1743
|
+
"src/scripts/payment/commands.ts"() {
|
|
1744
|
+
"use strict";
|
|
1745
|
+
init_config();
|
|
1746
|
+
init_output();
|
|
1747
|
+
init_build();
|
|
1748
|
+
init_dist();
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
|
|
1752
|
+
// src/scripts/messaging/send.ts
|
|
1753
|
+
async function cmdSend(targetKey, type, payloadStr) {
|
|
1754
|
+
if (!targetKey || !type || !payloadStr) {
|
|
1755
|
+
return fail("Usage: send <identityKey> <type> <json_payload>");
|
|
1756
|
+
}
|
|
1757
|
+
if (!/^0[23][0-9a-fA-F]{64}$/.test(targetKey)) {
|
|
1758
|
+
return fail("Target must be a compressed public key (66 hex chars, 02/03 prefix)");
|
|
1759
|
+
}
|
|
1760
|
+
let payload;
|
|
1761
|
+
try {
|
|
1762
|
+
payload = JSON.parse(payloadStr);
|
|
1763
|
+
} catch {
|
|
1764
|
+
return fail("payload must be valid JSON");
|
|
1765
|
+
}
|
|
1766
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
1767
|
+
const signature = await signRelayMessage(privKey, targetKey, type, payload);
|
|
1768
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/send`, {
|
|
1769
|
+
method: "POST",
|
|
1770
|
+
headers: { "Content-Type": "application/json" },
|
|
1771
|
+
body: JSON.stringify({
|
|
1772
|
+
from: identityKey,
|
|
1773
|
+
to: targetKey,
|
|
1774
|
+
type,
|
|
1775
|
+
payload,
|
|
1776
|
+
signature
|
|
1777
|
+
})
|
|
1778
|
+
});
|
|
1779
|
+
if (!resp.ok) {
|
|
1780
|
+
const body = await resp.text();
|
|
1781
|
+
return fail(`Relay send failed (${resp.status}): ${body}`);
|
|
1782
|
+
}
|
|
1783
|
+
const result = await resp.json();
|
|
1784
|
+
return ok({ sent: true, messageId: result.id, to: targetKey, type, signed: true });
|
|
1785
|
+
}
|
|
1786
|
+
var init_send = __esm({
|
|
1787
|
+
"src/scripts/messaging/send.ts"() {
|
|
1788
|
+
"use strict";
|
|
1789
|
+
init_config();
|
|
1790
|
+
init_output();
|
|
1791
|
+
init_identity();
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
// src/scripts/messaging/inbox.ts
|
|
1796
|
+
async function cmdInbox(args2) {
|
|
1797
|
+
const { identityKey } = await loadIdentity();
|
|
1798
|
+
let since = "";
|
|
1799
|
+
for (let i = 0; i < args2.length; i++) {
|
|
1800
|
+
if (args2[i] === "--since" && args2[i + 1]) since = `&since=${args2[++i]}`;
|
|
1801
|
+
}
|
|
1802
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/inbox?identity=${identityKey}${since}`);
|
|
1803
|
+
if (!resp.ok) {
|
|
1804
|
+
const body = await resp.text();
|
|
1805
|
+
return fail(`Relay inbox failed (${resp.status}): ${body}`);
|
|
1806
|
+
}
|
|
1807
|
+
const result = await resp.json();
|
|
1808
|
+
const messages = await Promise.all(
|
|
1809
|
+
result.messages.map(async (msg) => ({
|
|
1810
|
+
...msg,
|
|
1811
|
+
signatureValid: msg.signature ? (await verifyRelaySignature(msg.from, msg.to, msg.type, msg.payload, msg.signature)).valid : null
|
|
1812
|
+
}))
|
|
1813
|
+
);
|
|
1814
|
+
return ok({ messages, count: messages.length, identityKey });
|
|
1815
|
+
}
|
|
1816
|
+
async function cmdAck(messageIds) {
|
|
1817
|
+
if (!messageIds || messageIds.length === 0) {
|
|
1818
|
+
return fail("Usage: ack <messageId> [messageId2 ...]");
|
|
1819
|
+
}
|
|
1820
|
+
const { identityKey } = await loadIdentity();
|
|
1821
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/ack`, {
|
|
1822
|
+
method: "POST",
|
|
1823
|
+
headers: { "Content-Type": "application/json" },
|
|
1824
|
+
body: JSON.stringify({ identity: identityKey, messageIds })
|
|
1825
|
+
});
|
|
1826
|
+
if (!resp.ok) {
|
|
1827
|
+
const body = await resp.text();
|
|
1828
|
+
return fail(`Relay ack failed (${resp.status}): ${body}`);
|
|
1829
|
+
}
|
|
1830
|
+
const result = await resp.json();
|
|
1831
|
+
return ok({ acked: result.acked, messageIds });
|
|
1832
|
+
}
|
|
1833
|
+
var init_inbox = __esm({
|
|
1834
|
+
"src/scripts/messaging/inbox.ts"() {
|
|
1835
|
+
"use strict";
|
|
1836
|
+
init_config();
|
|
1837
|
+
init_output();
|
|
1838
|
+
init_identity();
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
// src/services/types.ts
|
|
1843
|
+
var ServiceCategory;
|
|
1844
|
+
var init_types = __esm({
|
|
1845
|
+
"src/services/types.ts"() {
|
|
1846
|
+
"use strict";
|
|
1847
|
+
ServiceCategory = /* @__PURE__ */ ((ServiceCategory2) => {
|
|
1848
|
+
ServiceCategory2["UTILITY"] = "utility";
|
|
1849
|
+
ServiceCategory2["AI"] = "ai";
|
|
1850
|
+
ServiceCategory2["BLOCKCHAIN"] = "blockchain";
|
|
1851
|
+
ServiceCategory2["COMMUNICATION"] = "communication";
|
|
1852
|
+
ServiceCategory2["DEVELOPMENT"] = "development";
|
|
1853
|
+
ServiceCategory2["RESEARCH"] = "research";
|
|
1854
|
+
ServiceCategory2["ENTERTAINMENT"] = "entertainment";
|
|
1855
|
+
ServiceCategory2["CUSTOM"] = "custom";
|
|
1856
|
+
return ServiceCategory2;
|
|
1857
|
+
})(ServiceCategory || {});
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
|
|
1861
|
+
// src/services/registry.ts
|
|
1862
|
+
var DefaultServiceRegistry, serviceRegistry;
|
|
1863
|
+
var init_registry = __esm({
|
|
1864
|
+
"src/services/registry.ts"() {
|
|
1865
|
+
"use strict";
|
|
1866
|
+
init_types();
|
|
1867
|
+
DefaultServiceRegistry = class {
|
|
1868
|
+
services = /* @__PURE__ */ new Map();
|
|
1869
|
+
/**
|
|
1870
|
+
* Register a new service definition.
|
|
1871
|
+
*/
|
|
1872
|
+
register(service) {
|
|
1873
|
+
this.validateServiceDefinition(service);
|
|
1874
|
+
if (this.services.has(service.id)) {
|
|
1875
|
+
throw new Error(`Service '${service.id}' is already registered`);
|
|
1876
|
+
}
|
|
1877
|
+
this.services.set(service.id, { ...service });
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Get a service definition by ID.
|
|
1881
|
+
*/
|
|
1882
|
+
get(serviceId) {
|
|
1883
|
+
return this.services.get(serviceId);
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* List all registered services.
|
|
1887
|
+
*/
|
|
1888
|
+
list() {
|
|
1889
|
+
return Array.from(this.services.values());
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* List services by category.
|
|
1893
|
+
*/
|
|
1894
|
+
listByCategory(category) {
|
|
1895
|
+
return this.list().filter((service) => service.category === category);
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Check if a service is registered.
|
|
1899
|
+
*/
|
|
1900
|
+
has(serviceId) {
|
|
1901
|
+
return this.services.has(serviceId);
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Unregister a service.
|
|
1905
|
+
*/
|
|
1906
|
+
unregister(serviceId) {
|
|
1907
|
+
this.services.delete(serviceId);
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Clear all services (useful for testing).
|
|
1911
|
+
*/
|
|
1912
|
+
clear() {
|
|
1913
|
+
this.services.clear();
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Get service count.
|
|
1917
|
+
*/
|
|
1918
|
+
count() {
|
|
1919
|
+
return this.services.size;
|
|
1920
|
+
}
|
|
1921
|
+
/**
|
|
1922
|
+
* Get services by price range.
|
|
1923
|
+
*/
|
|
1924
|
+
getByPriceRange(minPrice, maxPrice) {
|
|
1925
|
+
return this.list().filter(
|
|
1926
|
+
(service) => service.defaultPrice >= minPrice && service.defaultPrice <= maxPrice
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Search services by name or description.
|
|
1931
|
+
*/
|
|
1932
|
+
search(query) {
|
|
1933
|
+
const lowerQuery = query.toLowerCase();
|
|
1934
|
+
return this.list().filter(
|
|
1935
|
+
(service) => service.name.toLowerCase().includes(lowerQuery) || service.description.toLowerCase().includes(lowerQuery) || service.id.toLowerCase().includes(lowerQuery)
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Validate a service definition.
|
|
1940
|
+
*/
|
|
1941
|
+
validateServiceDefinition(service) {
|
|
1942
|
+
if (!service.id) {
|
|
1943
|
+
throw new Error("Service ID is required");
|
|
1944
|
+
}
|
|
1945
|
+
if (typeof service.id !== "string" || service.id.trim().length === 0) {
|
|
1946
|
+
throw new Error("Service ID must be a non-empty string");
|
|
1947
|
+
}
|
|
1948
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(service.id)) {
|
|
1949
|
+
throw new Error("Service ID must be in kebab-case format (lowercase, hyphens only)");
|
|
1950
|
+
}
|
|
1951
|
+
if (!service.name || typeof service.name !== "string" || service.name.trim().length === 0) {
|
|
1952
|
+
throw new Error("Service name is required and must be a non-empty string");
|
|
1953
|
+
}
|
|
1954
|
+
if (!service.description || typeof service.description !== "string" || service.description.trim().length === 0) {
|
|
1955
|
+
throw new Error("Service description is required and must be a non-empty string");
|
|
1956
|
+
}
|
|
1957
|
+
if (typeof service.defaultPrice !== "number" || service.defaultPrice < 0 || !Number.isInteger(service.defaultPrice)) {
|
|
1958
|
+
throw new Error("Service defaultPrice must be a non-negative integer");
|
|
1959
|
+
}
|
|
1960
|
+
if (service.category && !Object.values(ServiceCategory).includes(service.category)) {
|
|
1961
|
+
throw new Error(`Invalid service category: ${service.category}`);
|
|
1962
|
+
}
|
|
1963
|
+
if (service.inputSchema && typeof service.inputSchema !== "object") {
|
|
1964
|
+
throw new Error("Service inputSchema must be an object");
|
|
1965
|
+
}
|
|
1966
|
+
if (service.handler) {
|
|
1967
|
+
if (typeof service.handler.validate !== "function") {
|
|
1968
|
+
throw new Error("Service handler must have a validate function");
|
|
1969
|
+
}
|
|
1970
|
+
if (typeof service.handler.process !== "function") {
|
|
1971
|
+
throw new Error("Service handler must have a process function");
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
};
|
|
1976
|
+
serviceRegistry = new DefaultServiceRegistry();
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
// src/services/loader.ts
|
|
1981
|
+
import fs8 from "node:fs";
|
|
1982
|
+
import path3 from "node:path";
|
|
1983
|
+
import { fileURLToPath } from "node:url";
|
|
1984
|
+
var __filename, __dirname, DefaultServiceLoader, serviceLoader;
|
|
1985
|
+
var init_loader = __esm({
|
|
1986
|
+
"src/services/loader.ts"() {
|
|
1987
|
+
"use strict";
|
|
1988
|
+
__filename = fileURLToPath(import.meta.url);
|
|
1989
|
+
__dirname = path3.dirname(__filename);
|
|
1990
|
+
DefaultServiceLoader = class {
|
|
1991
|
+
builtInDir;
|
|
1992
|
+
customDir;
|
|
1993
|
+
constructor() {
|
|
1994
|
+
this.builtInDir = path3.resolve(__dirname, "built-in");
|
|
1995
|
+
const homeDir = process["env"].HOME || process["env"].USERPROFILE || "";
|
|
1996
|
+
this.customDir = path3.join(homeDir, ".openclaw", "services");
|
|
1997
|
+
}
|
|
1998
|
+
/**
|
|
1999
|
+
* Load services from a directory.
|
|
2000
|
+
*/
|
|
2001
|
+
async loadFromDirectory(directory) {
|
|
2002
|
+
if (!fs8.existsSync(directory)) {
|
|
2003
|
+
return [];
|
|
2004
|
+
}
|
|
2005
|
+
const services = [];
|
|
2006
|
+
const entries = fs8.readdirSync(directory, { withFileTypes: true });
|
|
2007
|
+
for (const entry of entries) {
|
|
2008
|
+
if (!entry.isDirectory()) {
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
try {
|
|
2012
|
+
const serviceDefinition = await this.loadServiceFromDirectory(
|
|
2013
|
+
path3.join(directory, entry.name)
|
|
2014
|
+
);
|
|
2015
|
+
if (serviceDefinition) {
|
|
2016
|
+
services.push(serviceDefinition);
|
|
2017
|
+
}
|
|
2018
|
+
} catch (error) {
|
|
2019
|
+
console.warn(`Failed to load service from ${entry.name}:`, error);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return services;
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Load all built-in services.
|
|
2026
|
+
*/
|
|
2027
|
+
async loadBuiltInServices() {
|
|
2028
|
+
return this.loadFromDirectory(this.builtInDir);
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Load custom user services.
|
|
2032
|
+
*/
|
|
2033
|
+
async loadCustomServices() {
|
|
2034
|
+
return this.loadFromDirectory(this.customDir);
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Load all services (built-in + custom).
|
|
2038
|
+
*/
|
|
2039
|
+
async loadAllServices() {
|
|
2040
|
+
const [builtIn, custom] = await Promise.all([
|
|
2041
|
+
this.loadBuiltInServices(),
|
|
2042
|
+
this.loadCustomServices()
|
|
2043
|
+
]);
|
|
2044
|
+
return [...builtIn, ...custom];
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Load a service definition from a directory.
|
|
2048
|
+
*/
|
|
2049
|
+
async loadServiceFromDirectory(serviceDir) {
|
|
2050
|
+
const indexPath = path3.join(serviceDir, "index.ts");
|
|
2051
|
+
const jsIndexPath = path3.join(serviceDir, "index.js");
|
|
2052
|
+
const promptPath = path3.join(serviceDir, "prompt.md");
|
|
2053
|
+
let modulePath;
|
|
2054
|
+
if (fs8.existsSync(indexPath)) {
|
|
2055
|
+
modulePath = indexPath;
|
|
2056
|
+
} else if (fs8.existsSync(jsIndexPath)) {
|
|
2057
|
+
modulePath = jsIndexPath;
|
|
2058
|
+
} else {
|
|
2059
|
+
throw new Error(`No index.ts or index.js found in ${serviceDir}`);
|
|
2060
|
+
}
|
|
2061
|
+
try {
|
|
2062
|
+
const serviceModule = await import(modulePath);
|
|
2063
|
+
const serviceDefinition = serviceModule.default || serviceModule;
|
|
2064
|
+
if (!serviceDefinition || typeof serviceDefinition !== "object") {
|
|
2065
|
+
throw new Error("Service must export a default ServiceDefinition object");
|
|
2066
|
+
}
|
|
2067
|
+
if (!serviceDefinition.id) {
|
|
2068
|
+
throw new Error("Service definition must have an id");
|
|
2069
|
+
}
|
|
2070
|
+
if (fs8.existsSync(promptPath)) {
|
|
2071
|
+
serviceDefinition.promptFile = promptPath;
|
|
2072
|
+
}
|
|
2073
|
+
return serviceDefinition;
|
|
2074
|
+
} catch (error) {
|
|
2075
|
+
throw new Error(`Failed to import service from ${modulePath}: ${error}`);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Create a new custom service directory.
|
|
2080
|
+
*/
|
|
2081
|
+
createCustomServiceDirectory(serviceId) {
|
|
2082
|
+
const serviceDir = path3.join(this.customDir, serviceId);
|
|
2083
|
+
fs8.mkdirSync(this.customDir, { recursive: true });
|
|
2084
|
+
if (fs8.existsSync(serviceDir)) {
|
|
2085
|
+
throw new Error(`Service directory ${serviceId} already exists`);
|
|
2086
|
+
}
|
|
2087
|
+
fs8.mkdirSync(serviceDir);
|
|
2088
|
+
return serviceDir;
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Create a basic service template.
|
|
2092
|
+
*/
|
|
2093
|
+
createServiceTemplate(serviceId, options) {
|
|
2094
|
+
const serviceDir = this.createCustomServiceDirectory(serviceId);
|
|
2095
|
+
const indexContent = this.generateServiceTemplate(serviceId, options);
|
|
2096
|
+
fs8.writeFileSync(path3.join(serviceDir, "index.ts"), indexContent);
|
|
2097
|
+
const promptContent = this.generatePromptTemplate(serviceId, options);
|
|
2098
|
+
fs8.writeFileSync(path3.join(serviceDir, "prompt.md"), promptContent);
|
|
2099
|
+
if (options.hasHandler) {
|
|
2100
|
+
const handlerContent = this.generateHandlerTemplate(serviceId, options);
|
|
2101
|
+
fs8.writeFileSync(path3.join(serviceDir, "handler.ts"), handlerContent);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Generate service definition template.
|
|
2106
|
+
*/
|
|
2107
|
+
generateServiceTemplate(serviceId, options) {
|
|
2108
|
+
return `/**
|
|
2109
|
+
* ${options.name} service definition.
|
|
7
2110
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2111
|
+
|
|
2112
|
+
import { ServiceDefinition${options.hasHandler ? ", ServiceHandler" : ""} } from '../../types.js';
|
|
2113
|
+
${options.hasHandler ? `import { ${serviceId.replace(/-/g, "")}Handler } from './handler.js';` : ""}
|
|
2114
|
+
|
|
2115
|
+
const ${serviceId.replace(/-/g, "")}Service: ServiceDefinition = {
|
|
2116
|
+
id: '${serviceId}',
|
|
2117
|
+
name: '${options.name}',
|
|
2118
|
+
description: '${options.description}',
|
|
2119
|
+
defaultPrice: ${options.defaultPrice},${options.category ? `
|
|
2120
|
+
category: '${options.category}',` : ""}
|
|
2121
|
+
inputSchema: {
|
|
2122
|
+
type: 'object',
|
|
2123
|
+
properties: {
|
|
2124
|
+
// Define your input schema here
|
|
2125
|
+
query: {
|
|
2126
|
+
type: 'string',
|
|
2127
|
+
description: 'Query or input for the service'
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
required: ['query']
|
|
2131
|
+
}${options.hasHandler ? `,
|
|
2132
|
+
handler: ${serviceId.replace(/-/g, "")}Handler` : ""}
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2135
|
+
export default ${serviceId.replace(/-/g, "")}Service;
|
|
2136
|
+
`;
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Generate prompt template.
|
|
2140
|
+
*/
|
|
2141
|
+
generatePromptTemplate(serviceId, options) {
|
|
2142
|
+
return `# ${options.name} Service
|
|
2143
|
+
|
|
2144
|
+
You are processing a request for the "${serviceId}" service.
|
|
2145
|
+
|
|
2146
|
+
## Service Description
|
|
2147
|
+
${options.description}
|
|
2148
|
+
|
|
2149
|
+
## Input
|
|
2150
|
+
The user has provided the following input:
|
|
2151
|
+
\`\`\`json
|
|
2152
|
+
{{input}}
|
|
2153
|
+
\`\`\`
|
|
2154
|
+
|
|
2155
|
+
## Instructions
|
|
2156
|
+
Process the user's request and provide a helpful response based on the service description.
|
|
2157
|
+
Format your response as a structured result that can be easily parsed and used.
|
|
2158
|
+
|
|
2159
|
+
## Response Format
|
|
2160
|
+
Provide your response in this format:
|
|
2161
|
+
\`\`\`json
|
|
2162
|
+
{
|
|
2163
|
+
"result": "your processed result here",
|
|
2164
|
+
"metadata": {
|
|
2165
|
+
"processingTime": "time taken",
|
|
2166
|
+
"version": "1.0"
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
\`\`\`
|
|
2170
|
+
`;
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Generate handler template.
|
|
2174
|
+
*/
|
|
2175
|
+
generateHandlerTemplate(serviceId, options) {
|
|
2176
|
+
return `/**
|
|
2177
|
+
* ${options.name} service handler.
|
|
2178
|
+
*/
|
|
2179
|
+
|
|
2180
|
+
import { ServiceHandler, ValidationResult, ServiceContext, ServiceResult } from '../../types.js';
|
|
2181
|
+
|
|
2182
|
+
export const ${serviceId.replace(/-/g, "")}Handler: ServiceHandler = {
|
|
2183
|
+
/**
|
|
2184
|
+
* Validate service input.
|
|
2185
|
+
*/
|
|
2186
|
+
validate(input: any): ValidationResult {
|
|
2187
|
+
if (!input || typeof input !== 'object') {
|
|
2188
|
+
return { valid: false, error: 'Input must be an object' };
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
if (!input.query || typeof input.query !== 'string') {
|
|
2192
|
+
return { valid: false, error: 'Query must be a non-empty string' };
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// Add more validation as needed
|
|
2196
|
+
return { valid: true, sanitized: input };
|
|
2197
|
+
},
|
|
2198
|
+
|
|
2199
|
+
/**
|
|
2200
|
+
* Process the service request.
|
|
2201
|
+
*/
|
|
2202
|
+
async process(input: any, context: ServiceContext): Promise<ServiceResult> {
|
|
2203
|
+
try {
|
|
2204
|
+
const startTime = Date.now();
|
|
2205
|
+
|
|
2206
|
+
// Your service logic here
|
|
2207
|
+
const result = {
|
|
2208
|
+
query: input.query,
|
|
2209
|
+
response: 'This is a template response. Implement your logic here.',
|
|
2210
|
+
timestamp: new Date().toISOString()
|
|
2211
|
+
};
|
|
2212
|
+
|
|
2213
|
+
const processingTime = Date.now() - startTime;
|
|
2214
|
+
|
|
2215
|
+
return {
|
|
2216
|
+
success: true,
|
|
2217
|
+
data: result,
|
|
2218
|
+
metadata: {
|
|
2219
|
+
processingTime,
|
|
2220
|
+
version: '1.0'
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
return {
|
|
2225
|
+
success: false,
|
|
2226
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
`;
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Get the built-in services directory.
|
|
2235
|
+
*/
|
|
2236
|
+
getBuiltInDirectory() {
|
|
2237
|
+
return this.builtInDir;
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Get the custom services directory.
|
|
2241
|
+
*/
|
|
2242
|
+
getCustomDirectory() {
|
|
2243
|
+
return this.customDir;
|
|
2244
|
+
}
|
|
2245
|
+
/**
|
|
2246
|
+
* Check if a service directory exists.
|
|
2247
|
+
*/
|
|
2248
|
+
serviceExists(serviceId, inCustom = false) {
|
|
2249
|
+
const baseDir = inCustom ? this.customDir : this.builtInDir;
|
|
2250
|
+
return fs8.existsSync(path3.join(baseDir, serviceId));
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
serviceLoader = new DefaultServiceLoader();
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
2256
|
+
|
|
2257
|
+
// src/services/manager.ts
|
|
2258
|
+
var DefaultServiceManager, serviceManager;
|
|
2259
|
+
var init_manager = __esm({
|
|
2260
|
+
"src/services/manager.ts"() {
|
|
2261
|
+
"use strict";
|
|
2262
|
+
init_registry();
|
|
2263
|
+
init_loader();
|
|
2264
|
+
DefaultServiceManager = class {
|
|
2265
|
+
registry;
|
|
2266
|
+
loader;
|
|
2267
|
+
initialized = false;
|
|
2268
|
+
constructor() {
|
|
2269
|
+
this.registry = serviceRegistry;
|
|
2270
|
+
this.loader = serviceLoader;
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Initialize the service system.
|
|
2274
|
+
*/
|
|
2275
|
+
async initialize() {
|
|
2276
|
+
if (this.initialized) {
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
const services = await this.loader.loadAllServices();
|
|
2280
|
+
for (const service of services) {
|
|
2281
|
+
try {
|
|
2282
|
+
this.registry.register(service);
|
|
2283
|
+
} catch (error) {
|
|
2284
|
+
console.warn(`Failed to register service '${service.id}':`, error);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
this.initialized = true;
|
|
2288
|
+
console.log(`Service manager initialized with ${this.registry.count()} services`);
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Execute a service request.
|
|
2292
|
+
*/
|
|
2293
|
+
async execute(serviceId, input, context) {
|
|
2294
|
+
if (!this.initialized) {
|
|
2295
|
+
throw new Error("Service manager not initialized");
|
|
2296
|
+
}
|
|
2297
|
+
const service = this.registry.get(serviceId);
|
|
2298
|
+
if (!service) {
|
|
2299
|
+
return {
|
|
2300
|
+
success: false,
|
|
2301
|
+
error: `Service '${serviceId}' not found`
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
if (service.handler) {
|
|
2305
|
+
const validation = service.handler.validate(input);
|
|
2306
|
+
if (!validation.valid) {
|
|
2307
|
+
return {
|
|
2308
|
+
success: false,
|
|
2309
|
+
error: `Input validation failed: ${validation.error}`
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
input = validation.sanitized || input;
|
|
2313
|
+
}
|
|
2314
|
+
try {
|
|
2315
|
+
if (service.handler) {
|
|
2316
|
+
return await service.handler.process(input, context);
|
|
2317
|
+
} else {
|
|
2318
|
+
return {
|
|
2319
|
+
success: true,
|
|
2320
|
+
data: {
|
|
2321
|
+
serviceId,
|
|
2322
|
+
input,
|
|
2323
|
+
mode: "agent",
|
|
2324
|
+
promptFile: service.promptFile
|
|
2325
|
+
},
|
|
2326
|
+
metadata: {
|
|
2327
|
+
version: "1.0",
|
|
2328
|
+
executionMode: "agent"
|
|
2329
|
+
}
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
return {
|
|
2334
|
+
success: false,
|
|
2335
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2336
|
+
metadata: {
|
|
2337
|
+
serviceId,
|
|
2338
|
+
errorType: "execution_error"
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Validate service input.
|
|
2345
|
+
*/
|
|
2346
|
+
validate(serviceId, input) {
|
|
2347
|
+
const service = this.registry.get(serviceId);
|
|
2348
|
+
if (!service) {
|
|
2349
|
+
return {
|
|
2350
|
+
valid: false,
|
|
2351
|
+
error: `Service '${serviceId}' not found`
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
if (service.handler) {
|
|
2355
|
+
return service.handler.validate(input);
|
|
2356
|
+
}
|
|
2357
|
+
if (service.inputSchema) {
|
|
2358
|
+
return this.validateAgainstSchema(input, service.inputSchema);
|
|
2359
|
+
}
|
|
2360
|
+
return { valid: true };
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Reload all services.
|
|
2364
|
+
*/
|
|
2365
|
+
async reload() {
|
|
2366
|
+
this.registry.clear();
|
|
2367
|
+
this.initialized = false;
|
|
2368
|
+
await this.initialize();
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Get service for agent-mode processing.
|
|
2372
|
+
*/
|
|
2373
|
+
getServiceForAgentMode(serviceId) {
|
|
2374
|
+
const service = this.registry.get(serviceId);
|
|
2375
|
+
if (!service) {
|
|
2376
|
+
return null;
|
|
2377
|
+
}
|
|
2378
|
+
return {
|
|
2379
|
+
service,
|
|
2380
|
+
promptFile: service.promptFile
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* Check if service is available.
|
|
2385
|
+
*/
|
|
2386
|
+
isServiceAvailable(serviceId) {
|
|
2387
|
+
return this.registry.has(serviceId);
|
|
2388
|
+
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Get service execution mode.
|
|
2391
|
+
*/
|
|
2392
|
+
getServiceMode(serviceId) {
|
|
2393
|
+
const service = this.registry.get(serviceId);
|
|
2394
|
+
if (!service) {
|
|
2395
|
+
return null;
|
|
2396
|
+
}
|
|
2397
|
+
return service.handler ? "handler" : "agent";
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Get all available services for discovery.
|
|
2401
|
+
*/
|
|
2402
|
+
getAvailableServices() {
|
|
2403
|
+
return this.registry.list().map((service) => ({
|
|
2404
|
+
id: service.id,
|
|
2405
|
+
name: service.name,
|
|
2406
|
+
description: service.description,
|
|
2407
|
+
defaultPrice: service.defaultPrice,
|
|
2408
|
+
category: service.category,
|
|
2409
|
+
mode: service.handler ? "handler" : "agent"
|
|
2410
|
+
}));
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Validate input against JSON schema.
|
|
2414
|
+
*/
|
|
2415
|
+
validateAgainstSchema(input, schema) {
|
|
2416
|
+
try {
|
|
2417
|
+
const schemaObj = schema;
|
|
2418
|
+
if (schemaObj.type === "object") {
|
|
2419
|
+
if (!input || typeof input !== "object") {
|
|
2420
|
+
return { valid: false, error: "Input must be an object" };
|
|
2421
|
+
}
|
|
2422
|
+
if (schemaObj.required && Array.isArray(schemaObj.required)) {
|
|
2423
|
+
for (const requiredProp of schemaObj.required) {
|
|
2424
|
+
if (!(requiredProp in input)) {
|
|
2425
|
+
return { valid: false, error: `Missing required property: ${requiredProp}` };
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
if (schemaObj.properties) {
|
|
2430
|
+
for (const [propName, propSchema] of Object.entries(schemaObj.properties)) {
|
|
2431
|
+
if (propName in input) {
|
|
2432
|
+
const propType = propSchema.type;
|
|
2433
|
+
const actualType = typeof input[propName];
|
|
2434
|
+
if (propType && propType !== actualType) {
|
|
2435
|
+
return { valid: false, error: `Property '${propName}' must be of type ${propType}` };
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
return { valid: true, sanitized: input };
|
|
2442
|
+
} catch (error) {
|
|
2443
|
+
return { valid: false, error: `Schema validation error: ${error}` };
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Get service statistics.
|
|
2448
|
+
*/
|
|
2449
|
+
getStatistics() {
|
|
2450
|
+
const services = this.registry.list();
|
|
2451
|
+
const stats = {
|
|
2452
|
+
totalServices: services.length,
|
|
2453
|
+
handlerServices: 0,
|
|
2454
|
+
agentServices: 0,
|
|
2455
|
+
servicesByCategory: {}
|
|
2456
|
+
};
|
|
2457
|
+
for (const service of services) {
|
|
2458
|
+
if (service.handler) {
|
|
2459
|
+
stats.handlerServices++;
|
|
2460
|
+
} else {
|
|
2461
|
+
stats.agentServices++;
|
|
2462
|
+
}
|
|
2463
|
+
const category = service.category || "uncategorized";
|
|
2464
|
+
stats.servicesByCategory[category] = (stats.servicesByCategory[category] || 0) + 1;
|
|
2465
|
+
}
|
|
2466
|
+
return stats;
|
|
2467
|
+
}
|
|
2468
|
+
};
|
|
2469
|
+
serviceManager = new DefaultServiceManager();
|
|
2470
|
+
}
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
// src/services/index.ts
|
|
2474
|
+
var init_services2 = __esm({
|
|
2475
|
+
"src/services/index.ts"() {
|
|
2476
|
+
"use strict";
|
|
2477
|
+
init_types();
|
|
2478
|
+
init_registry();
|
|
2479
|
+
init_loader();
|
|
2480
|
+
init_manager();
|
|
2481
|
+
init_registry();
|
|
2482
|
+
init_loader();
|
|
2483
|
+
init_manager();
|
|
2484
|
+
}
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2487
|
+
// src/scripts/messaging/handlers.ts
|
|
2488
|
+
import fs9 from "node:fs";
|
|
2489
|
+
async function getSdk4() {
|
|
2490
|
+
if (_sdk4) return _sdk4;
|
|
2491
|
+
try {
|
|
2492
|
+
_sdk4 = await import("@bsv/sdk");
|
|
2493
|
+
return _sdk4;
|
|
2494
|
+
} catch {
|
|
2495
|
+
const { fileURLToPath: fileURLToPath3 } = await import("node:url");
|
|
2496
|
+
const path5 = await import("node:path");
|
|
2497
|
+
const os3 = await import("node:os");
|
|
2498
|
+
const __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
|
|
2499
|
+
const candidates = [
|
|
2500
|
+
path5.resolve(__dirname3, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
2501
|
+
path5.resolve(__dirname3, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
2502
|
+
path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
|
|
2503
|
+
];
|
|
2504
|
+
for (const p of candidates) {
|
|
2505
|
+
try {
|
|
2506
|
+
_sdk4 = await import(p);
|
|
2507
|
+
return _sdk4;
|
|
2508
|
+
} catch {
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
async function getBSVAgentWallet7() {
|
|
2515
|
+
return BSVAgentWallet;
|
|
2516
|
+
}
|
|
2517
|
+
async function getNetwork() {
|
|
2518
|
+
const config = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2519
|
+
return config.NETWORK;
|
|
2520
|
+
}
|
|
2521
|
+
async function verifyAndAcceptPayment(payment, minSats, senderKey, serviceId, ourHash160) {
|
|
2522
|
+
if (!payment) {
|
|
2523
|
+
return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: "no payment" };
|
|
2524
|
+
}
|
|
2525
|
+
if (payment.error) {
|
|
2526
|
+
return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: payment.error };
|
|
2527
|
+
}
|
|
2528
|
+
if (!payment.beef || !payment.satoshis) {
|
|
2529
|
+
return { accepted: false, txid: null, satoshis: 0, outputIndex: 0, walletAccepted: false, error: "missing beef or satoshis" };
|
|
2530
|
+
}
|
|
2531
|
+
if (payment.satoshis < minSats) {
|
|
2532
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `insufficient payment: ${payment.satoshis} < ${minSats}` };
|
|
2533
|
+
}
|
|
2534
|
+
const BSVAgentWallet2 = await getBSVAgentWallet7();
|
|
2535
|
+
const network = await getNetwork();
|
|
2536
|
+
const wallet = await BSVAgentWallet2.load({ network, storageDir: WALLET_DIR });
|
|
2537
|
+
try {
|
|
2538
|
+
const verifyResult = await wallet.verifyPayment({ beef: payment.beef });
|
|
2539
|
+
if (!verifyResult.valid) {
|
|
2540
|
+
await wallet.destroy();
|
|
2541
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: `verification failed: ${verifyResult.errors.join(", ")}` };
|
|
2542
|
+
}
|
|
2543
|
+
const acceptResult = await wallet.acceptPayment({
|
|
2544
|
+
beef: payment.beef,
|
|
2545
|
+
derivationPrefix: payment.derivationPrefix,
|
|
2546
|
+
derivationSuffix: payment.derivationSuffix,
|
|
2547
|
+
senderIdentityKey: payment.senderIdentityKey,
|
|
2548
|
+
description: `Payment for ${serviceId}`
|
|
2549
|
+
});
|
|
2550
|
+
await wallet.destroy();
|
|
2551
|
+
if (!acceptResult.accepted) {
|
|
2552
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: "wallet rejected payment" };
|
|
2553
|
+
}
|
|
2554
|
+
return {
|
|
2555
|
+
accepted: true,
|
|
2556
|
+
txid: payment.txid,
|
|
2557
|
+
satoshis: payment.satoshis,
|
|
2558
|
+
outputIndex: 0,
|
|
2559
|
+
walletAccepted: true,
|
|
2560
|
+
error: null
|
|
2561
|
+
};
|
|
2562
|
+
} catch (err) {
|
|
2563
|
+
await wallet.destroy();
|
|
2564
|
+
return { accepted: false, txid: payment.txid || null, satoshis: payment.satoshis, outputIndex: 0, walletAccepted: false, error: err.message };
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
async function queueForAgent(msg, identityKey, privKey, serviceId) {
|
|
2568
|
+
if (fs9.existsSync(PATHS.serviceQueue)) {
|
|
2569
|
+
const lines = fs9.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
|
|
2570
|
+
for (const line of lines) {
|
|
2571
|
+
try {
|
|
2572
|
+
const entry = JSON.parse(line);
|
|
2573
|
+
if (entry.requestId === msg.id) {
|
|
2574
|
+
return {
|
|
2575
|
+
id: msg.id,
|
|
2576
|
+
type: "service-request",
|
|
2577
|
+
serviceId,
|
|
2578
|
+
action: entry.status === "pending" ? "already-queued" : `already-${entry.status}`,
|
|
2579
|
+
paymentAccepted: true,
|
|
2580
|
+
paymentTxid: entry.paymentTxid,
|
|
2581
|
+
satoshisReceived: entry.satoshisReceived,
|
|
2582
|
+
from: msg.from,
|
|
2583
|
+
ack: true
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
} catch {
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
const sdk = await getSdk4();
|
|
2591
|
+
const payment = msg.payload?.payment;
|
|
2592
|
+
const input = msg.payload?.input || msg.payload;
|
|
2593
|
+
const walletIdentity = loadWalletIdentity();
|
|
2594
|
+
const ourHash160 = sdk.Hash.hash160(sdk.PrivateKey.fromHex(walletIdentity.rootKeyHex).toPublicKey().encode(true));
|
|
2595
|
+
const serviceDefinition = serviceManager.registry.get(serviceId);
|
|
2596
|
+
let minPrice = 5;
|
|
2597
|
+
if (serviceDefinition) {
|
|
2598
|
+
minPrice = serviceDefinition.defaultPrice;
|
|
2599
|
+
const validation = serviceManager.validate(serviceId, input);
|
|
2600
|
+
if (!validation.valid) {
|
|
2601
|
+
const rejectPayload = {
|
|
2602
|
+
requestId: msg.id,
|
|
2603
|
+
serviceId,
|
|
2604
|
+
status: "rejected",
|
|
2605
|
+
reason: `Input validation failed: ${validation.error}`
|
|
2606
|
+
};
|
|
2607
|
+
const sig = await signRelayMessage(privKey, msg.from, "service-response", rejectPayload);
|
|
2608
|
+
await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
|
|
2609
|
+
method: "POST",
|
|
2610
|
+
headers: { "Content-Type": "application/json" },
|
|
2611
|
+
body: JSON.stringify({ from: identityKey, to: msg.from, type: "service-response", payload: rejectPayload, signature: sig })
|
|
2612
|
+
});
|
|
2613
|
+
const rejectedEntry = {
|
|
2614
|
+
status: "rejected",
|
|
2615
|
+
requestId: msg.id,
|
|
2616
|
+
serviceId,
|
|
2617
|
+
from: msg.from,
|
|
2618
|
+
identityKey,
|
|
2619
|
+
input,
|
|
2620
|
+
paymentTxid: null,
|
|
2621
|
+
satoshisReceived: 0,
|
|
2622
|
+
walletAccepted: false,
|
|
2623
|
+
error: validation.error,
|
|
2624
|
+
_ts: Date.now()
|
|
2625
|
+
};
|
|
2626
|
+
appendToJsonl(PATHS.serviceQueue, rejectedEntry);
|
|
2627
|
+
return {
|
|
2628
|
+
id: msg.id,
|
|
2629
|
+
type: "service-request",
|
|
2630
|
+
serviceId,
|
|
2631
|
+
action: "rejected",
|
|
2632
|
+
reason: validation.error || "input validation failed",
|
|
2633
|
+
from: msg.from,
|
|
2634
|
+
ack: true
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
} else {
|
|
2638
|
+
const services = loadServices();
|
|
2639
|
+
const svc = services.find((s) => s.serviceId === serviceId);
|
|
2640
|
+
minPrice = svc?.priceSats || 5;
|
|
2641
|
+
}
|
|
2642
|
+
const payResult = await verifyAndAcceptPayment(payment, minPrice, msg.from, serviceId, ourHash160);
|
|
2643
|
+
if (!payResult.accepted) {
|
|
2644
|
+
const rejectPayload = { requestId: msg.id, serviceId, status: "rejected", reason: `Payment rejected: ${payResult.error}` };
|
|
2645
|
+
const sig = await signRelayMessage(privKey, msg.from, "service-response", rejectPayload);
|
|
2646
|
+
await fetchWithTimeout(`${OVERLAY_URL}/relay/send`, {
|
|
2647
|
+
method: "POST",
|
|
2648
|
+
headers: { "Content-Type": "application/json" },
|
|
2649
|
+
body: JSON.stringify({ from: identityKey, to: msg.from, type: "service-response", payload: rejectPayload, signature: sig })
|
|
2650
|
+
});
|
|
2651
|
+
const rejectedEntry = {
|
|
2652
|
+
status: "rejected",
|
|
2653
|
+
requestId: msg.id,
|
|
2654
|
+
serviceId,
|
|
2655
|
+
from: msg.from,
|
|
2656
|
+
identityKey,
|
|
2657
|
+
input,
|
|
2658
|
+
paymentTxid: null,
|
|
2659
|
+
satoshisReceived: 0,
|
|
2660
|
+
walletAccepted: false,
|
|
2661
|
+
error: payResult.error,
|
|
2662
|
+
_ts: Date.now()
|
|
2663
|
+
};
|
|
2664
|
+
appendToJsonl(PATHS.serviceQueue, rejectedEntry);
|
|
2665
|
+
return { id: msg.id, type: "service-request", serviceId, action: "rejected", reason: payResult.error || "payment rejected", from: msg.from, ack: true };
|
|
2666
|
+
}
|
|
2667
|
+
const queueEntry = {
|
|
2668
|
+
status: "pending",
|
|
2669
|
+
requestId: msg.id,
|
|
2670
|
+
serviceId,
|
|
2671
|
+
from: msg.from,
|
|
2672
|
+
identityKey,
|
|
2673
|
+
input,
|
|
2674
|
+
paymentTxid: payResult.txid,
|
|
2675
|
+
satoshisReceived: payResult.satoshis,
|
|
2676
|
+
walletAccepted: payResult.walletAccepted,
|
|
2677
|
+
_ts: Date.now()
|
|
2678
|
+
};
|
|
2679
|
+
appendToJsonl(PATHS.serviceQueue, queueEntry);
|
|
2680
|
+
return {
|
|
2681
|
+
id: msg.id,
|
|
2682
|
+
type: "service-request",
|
|
2683
|
+
serviceId,
|
|
2684
|
+
action: "queued-for-agent",
|
|
2685
|
+
paymentAccepted: true,
|
|
2686
|
+
paymentTxid: payResult.txid,
|
|
2687
|
+
satoshisReceived: payResult.satoshis,
|
|
2688
|
+
from: msg.from,
|
|
2689
|
+
ack: true
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
async function processMessage(msg, identityKey, privKey) {
|
|
2693
|
+
const sigCheck = msg.signature ? await verifyRelaySignature(msg.from, msg.to, msg.type, msg.payload, msg.signature) : { valid: null };
|
|
2694
|
+
if (msg.type === "service-request" && sigCheck.valid !== true) {
|
|
2695
|
+
console.error(JSON.stringify({ event: "signature-rejected", type: msg.type, from: msg.from, reason: sigCheck.reason || "missing signature" }));
|
|
2696
|
+
return {
|
|
2697
|
+
id: msg.id,
|
|
2698
|
+
type: msg.type,
|
|
2699
|
+
from: msg.from,
|
|
2700
|
+
action: "rejected",
|
|
2701
|
+
reason: "invalid-signature",
|
|
2702
|
+
signatureValid: sigCheck.valid,
|
|
2703
|
+
ack: true
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
if (msg.type === "ping") {
|
|
2707
|
+
const pongPayload = {
|
|
2708
|
+
text: "pong",
|
|
2709
|
+
inReplyTo: msg.id,
|
|
2710
|
+
originalText: msg.payload?.text || null
|
|
2711
|
+
};
|
|
2712
|
+
const pongSig = await signRelayMessage(privKey, msg.from, "pong", pongPayload);
|
|
2713
|
+
await fetch(`${OVERLAY_URL}/relay/send`, {
|
|
2714
|
+
method: "POST",
|
|
2715
|
+
headers: { "Content-Type": "application/json" },
|
|
2716
|
+
body: JSON.stringify({
|
|
2717
|
+
from: identityKey,
|
|
2718
|
+
to: msg.from,
|
|
2719
|
+
type: "pong",
|
|
2720
|
+
payload: pongPayload,
|
|
2721
|
+
signature: pongSig
|
|
2722
|
+
})
|
|
2723
|
+
});
|
|
2724
|
+
return { id: msg.id, type: "ping", action: "replied-pong", from: msg.from, ack: true };
|
|
2725
|
+
}
|
|
2726
|
+
if (msg.type === "service-request") {
|
|
2727
|
+
const serviceId = msg.payload?.serviceId;
|
|
2728
|
+
if (process["env"].AGENT_ROUTED === "true") {
|
|
2729
|
+
return await queueForAgent(msg, identityKey, privKey, serviceId);
|
|
2730
|
+
}
|
|
2731
|
+
return await queueForAgent(msg, identityKey, privKey, serviceId);
|
|
2732
|
+
}
|
|
2733
|
+
if (msg.type === "pong") {
|
|
2734
|
+
return {
|
|
2735
|
+
id: msg.id,
|
|
2736
|
+
type: "pong",
|
|
2737
|
+
action: "received",
|
|
2738
|
+
from: msg.from,
|
|
2739
|
+
text: msg.payload?.text,
|
|
2740
|
+
inReplyTo: msg.payload?.inReplyTo,
|
|
2741
|
+
ack: true
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
if (msg.type === "service-response") {
|
|
2745
|
+
const serviceId = msg.payload?.serviceId;
|
|
2746
|
+
const status = msg.payload?.status;
|
|
2747
|
+
const result = msg.payload?.result;
|
|
2748
|
+
return {
|
|
2749
|
+
id: msg.id,
|
|
2750
|
+
type: "service-response",
|
|
2751
|
+
action: "received",
|
|
2752
|
+
from: msg.from,
|
|
2753
|
+
serviceId,
|
|
2754
|
+
status,
|
|
2755
|
+
result,
|
|
2756
|
+
requestId: msg.payload?.requestId,
|
|
2757
|
+
direction: "incoming-response",
|
|
2758
|
+
ack: true
|
|
2759
|
+
};
|
|
2760
|
+
}
|
|
2761
|
+
return {
|
|
2762
|
+
id: msg.id,
|
|
2763
|
+
type: msg.type,
|
|
2764
|
+
from: msg.from,
|
|
2765
|
+
payload: msg.payload,
|
|
2766
|
+
signatureValid: sigCheck.valid,
|
|
2767
|
+
action: "unhandled",
|
|
2768
|
+
ack: false
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
var _sdk4;
|
|
2772
|
+
var init_handlers = __esm({
|
|
2773
|
+
"src/scripts/messaging/handlers.ts"() {
|
|
2774
|
+
"use strict";
|
|
2775
|
+
init_config();
|
|
2776
|
+
init_identity();
|
|
2777
|
+
init_storage();
|
|
2778
|
+
init_woc();
|
|
2779
|
+
init_services2();
|
|
2780
|
+
init_dist();
|
|
2781
|
+
_sdk4 = null;
|
|
2782
|
+
}
|
|
2783
|
+
});
|
|
2784
|
+
|
|
2785
|
+
// src/scripts/messaging/poll.ts
|
|
2786
|
+
async function cmdPoll() {
|
|
2787
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
2788
|
+
const inboxResp = await fetch(`${OVERLAY_URL}/relay/inbox?identity=${identityKey}`);
|
|
2789
|
+
if (!inboxResp.ok) {
|
|
2790
|
+
const body = await inboxResp.text();
|
|
2791
|
+
return fail(`Relay inbox failed (${inboxResp.status}): ${body}`);
|
|
2792
|
+
}
|
|
2793
|
+
const inbox = await inboxResp.json();
|
|
2794
|
+
if (inbox.count === 0) {
|
|
2795
|
+
return ok({ processed: 0, messages: [], summary: "No pending messages." });
|
|
2796
|
+
}
|
|
2797
|
+
const processed = [];
|
|
2798
|
+
const ackedIds = [];
|
|
2799
|
+
const unhandled = [];
|
|
2800
|
+
for (const msg of inbox.messages) {
|
|
2801
|
+
const result = await processMessage(msg, identityKey, privKey);
|
|
2802
|
+
if (result.ack) {
|
|
2803
|
+
ackedIds.push(result.id);
|
|
2804
|
+
processed.push(result);
|
|
2805
|
+
} else {
|
|
2806
|
+
unhandled.push(result);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
if (ackedIds.length > 0) {
|
|
2810
|
+
await fetch(`${OVERLAY_URL}/relay/ack`, {
|
|
2811
|
+
method: "POST",
|
|
2812
|
+
headers: { "Content-Type": "application/json" },
|
|
2813
|
+
body: JSON.stringify({ identity: identityKey, messageIds: ackedIds })
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
return ok({
|
|
2817
|
+
processed: processed.length,
|
|
2818
|
+
unhandled: unhandled.length,
|
|
2819
|
+
total: inbox.count,
|
|
2820
|
+
messages: processed,
|
|
2821
|
+
unhandledMessages: unhandled,
|
|
2822
|
+
ackedIds
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
var init_poll = __esm({
|
|
2826
|
+
"src/scripts/messaging/poll.ts"() {
|
|
2827
|
+
"use strict";
|
|
2828
|
+
init_config();
|
|
2829
|
+
init_output();
|
|
2830
|
+
init_identity();
|
|
2831
|
+
init_handlers();
|
|
2832
|
+
}
|
|
2833
|
+
});
|
|
2834
|
+
|
|
2835
|
+
// src/scripts/messaging/connect.ts
|
|
2836
|
+
import fs10 from "node:fs";
|
|
2837
|
+
import debug2 from "debug";
|
|
2838
|
+
async function cmdConnect(onMessage, signal) {
|
|
2839
|
+
let WebSocketClient;
|
|
2840
|
+
try {
|
|
2841
|
+
const ws = await import("ws");
|
|
2842
|
+
WebSocketClient = ws.default || ws.WebSocket || ws;
|
|
2843
|
+
} catch {
|
|
2844
|
+
return fail("WebSocket client not available. Install it: npm install ws");
|
|
2845
|
+
}
|
|
2846
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
2847
|
+
const wsUrl = OVERLAY_URL.replace(/^http/, "ws") + "/relay/subscribe?identity=" + identityKey;
|
|
2848
|
+
log2("Connecting to WebSocket relay: %s", wsUrl);
|
|
2849
|
+
let reconnectDelay = 1e3;
|
|
2850
|
+
let shouldReconnect = true;
|
|
2851
|
+
let currentWs = null;
|
|
2852
|
+
function shutdown() {
|
|
2853
|
+
shouldReconnect = false;
|
|
2854
|
+
if (currentWs) {
|
|
2855
|
+
try {
|
|
2856
|
+
currentWs.close();
|
|
2857
|
+
} catch {
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
if (!onMessage) {
|
|
2861
|
+
process.exit(0);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
if (!onMessage) {
|
|
2865
|
+
process.on("SIGINT", shutdown);
|
|
2866
|
+
process.on("SIGTERM", shutdown);
|
|
2867
|
+
}
|
|
2868
|
+
if (signal) {
|
|
2869
|
+
signal.addEventListener("abort", () => {
|
|
2870
|
+
shouldReconnect = false;
|
|
2871
|
+
if (currentWs) {
|
|
2872
|
+
try {
|
|
2873
|
+
currentWs.close();
|
|
2874
|
+
} catch {
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
function connect() {
|
|
2880
|
+
if (signal?.aborted) return;
|
|
2881
|
+
const ws = new WebSocketClient(wsUrl);
|
|
2882
|
+
currentWs = ws;
|
|
2883
|
+
ws.on("open", () => {
|
|
2884
|
+
log2("WebSocket connection established!");
|
|
2885
|
+
reconnectDelay = 1e3;
|
|
2886
|
+
const logMsg = { event: "connected", identity: identityKey, overlay: OVERLAY_URL };
|
|
2887
|
+
if (onMessage) onMessage(logMsg);
|
|
2888
|
+
else console.error(JSON.stringify(logMsg));
|
|
2889
|
+
});
|
|
2890
|
+
ws.on("message", async (data) => {
|
|
2891
|
+
log2("Incoming WebSocket message received");
|
|
2892
|
+
try {
|
|
2893
|
+
const envelope = JSON.parse(data.toString());
|
|
2894
|
+
log2("Message type: %s", envelope.type);
|
|
2895
|
+
if (envelope.type === "message") {
|
|
2896
|
+
const result = await processMessage(envelope.message, identityKey, privKey);
|
|
2897
|
+
log2("Processed message: %s", result.id);
|
|
2898
|
+
if (onMessage) onMessage(result);
|
|
2899
|
+
else console.log(JSON.stringify(result));
|
|
2900
|
+
ensureStateDir();
|
|
2901
|
+
try {
|
|
2902
|
+
fs10.appendFileSync(PATHS.notifications, JSON.stringify({ ...result, _ts: Date.now() }) + "\n");
|
|
2903
|
+
} catch {
|
|
2904
|
+
}
|
|
2905
|
+
if (result.ack) {
|
|
2906
|
+
try {
|
|
2907
|
+
await fetch(OVERLAY_URL + "/relay/ack", {
|
|
2908
|
+
method: "POST",
|
|
2909
|
+
headers: { "Content-Type": "application/json" },
|
|
2910
|
+
body: JSON.stringify({ identity: identityKey, messageIds: [result.id] })
|
|
2911
|
+
});
|
|
2912
|
+
} catch (ackErr) {
|
|
2913
|
+
const log3 = { event: "ack-error", id: result.id, message: String(ackErr) };
|
|
2914
|
+
if (onMessage) onMessage(log3);
|
|
2915
|
+
else console.error(JSON.stringify(log3));
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
if (envelope.type === "service-announced") {
|
|
2920
|
+
const svc = envelope.service || {};
|
|
2921
|
+
const announcement = {
|
|
2922
|
+
event: "service-announced",
|
|
2923
|
+
serviceId: svc.serviceId,
|
|
2924
|
+
name: svc.name,
|
|
2925
|
+
description: svc.description,
|
|
2926
|
+
priceSats: svc.pricingSats,
|
|
2927
|
+
provider: svc.identityKey,
|
|
2928
|
+
txid: envelope.txid,
|
|
2929
|
+
_ts: Date.now()
|
|
2930
|
+
};
|
|
2931
|
+
if (onMessage) onMessage(announcement);
|
|
2932
|
+
else console.log(JSON.stringify(announcement));
|
|
2933
|
+
ensureStateDir();
|
|
2934
|
+
try {
|
|
2935
|
+
fs10.appendFileSync(PATHS.notifications, JSON.stringify(announcement) + "\n");
|
|
2936
|
+
} catch {
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
} catch (err) {
|
|
2940
|
+
const log3 = { event: "process-error", message: String(err) };
|
|
2941
|
+
if (onMessage) onMessage(log3);
|
|
2942
|
+
else console.error(JSON.stringify(log3));
|
|
2943
|
+
}
|
|
2944
|
+
});
|
|
2945
|
+
ws.on("close", () => {
|
|
2946
|
+
currentWs = null;
|
|
2947
|
+
if (shouldReconnect && !signal?.aborted) {
|
|
2948
|
+
const log3 = { event: "disconnected", reconnectMs: reconnectDelay };
|
|
2949
|
+
if (onMessage) onMessage(log3);
|
|
2950
|
+
else console.error(JSON.stringify(log3));
|
|
2951
|
+
setTimeout(connect, reconnectDelay);
|
|
2952
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 3e4);
|
|
2953
|
+
}
|
|
2954
|
+
});
|
|
2955
|
+
ws.on("error", (err) => {
|
|
2956
|
+
const log3 = { event: "error", message: err.message };
|
|
2957
|
+
if (onMessage) onMessage(log3);
|
|
2958
|
+
else console.error(JSON.stringify(log3));
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
connect();
|
|
2962
|
+
return new Promise((resolve) => {
|
|
2963
|
+
if (signal) {
|
|
2964
|
+
signal.addEventListener("abort", () => resolve());
|
|
2965
|
+
}
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
var log2;
|
|
2969
|
+
var init_connect = __esm({
|
|
2970
|
+
"src/scripts/messaging/connect.ts"() {
|
|
2971
|
+
"use strict";
|
|
2972
|
+
init_config();
|
|
2973
|
+
init_output();
|
|
2974
|
+
init_identity();
|
|
2975
|
+
init_handlers();
|
|
2976
|
+
init_storage();
|
|
2977
|
+
log2 = debug2("openclaw:plugin:overlay:connect");
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
|
|
2981
|
+
// src/scripts/services/request.ts
|
|
2982
|
+
async function cmdRequestService(targetKey, serviceId, satsStr, inputJsonStr) {
|
|
2983
|
+
if (!targetKey || !serviceId) {
|
|
2984
|
+
return fail("Usage: request-service <identityKey> <serviceId> [sats] [inputJson]");
|
|
2985
|
+
}
|
|
2986
|
+
if (!/^0[23][0-9a-fA-F]{64}$/.test(targetKey)) {
|
|
2987
|
+
return fail("Target must be a compressed public key (66 hex chars, 02/03 prefix)");
|
|
2988
|
+
}
|
|
2989
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
2990
|
+
const sats = parseInt(satsStr || "5", 10);
|
|
2991
|
+
let inputData = null;
|
|
2992
|
+
if (inputJsonStr) {
|
|
2993
|
+
try {
|
|
2994
|
+
inputData = JSON.parse(inputJsonStr);
|
|
2995
|
+
} catch {
|
|
2996
|
+
return fail("inputJson must be valid JSON");
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
let paymentData = null;
|
|
3000
|
+
if (sats > 0) {
|
|
3001
|
+
try {
|
|
3002
|
+
const payment = await buildDirectPayment(targetKey, sats, `service-request: ${serviceId}`);
|
|
3003
|
+
paymentData = {
|
|
3004
|
+
beef: payment.beef,
|
|
3005
|
+
txid: payment.txid,
|
|
3006
|
+
satoshis: payment.satoshis,
|
|
3007
|
+
derivationPrefix: payment.derivationPrefix,
|
|
3008
|
+
derivationSuffix: payment.derivationSuffix,
|
|
3009
|
+
senderIdentityKey: payment.senderIdentityKey
|
|
3010
|
+
};
|
|
3011
|
+
} catch (err) {
|
|
3012
|
+
paymentData = { error: String(err.message || err) };
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
const requestPayload = {
|
|
3016
|
+
serviceId,
|
|
3017
|
+
...inputData ? { input: inputData } : {},
|
|
3018
|
+
payment: paymentData,
|
|
3019
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3020
|
+
};
|
|
3021
|
+
const signature = await signRelayMessage(privKey, targetKey, "service-request", requestPayload);
|
|
3022
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/send`, {
|
|
3023
|
+
method: "POST",
|
|
3024
|
+
headers: { "Content-Type": "application/json" },
|
|
3025
|
+
body: JSON.stringify({
|
|
3026
|
+
from: identityKey,
|
|
3027
|
+
to: targetKey,
|
|
3028
|
+
type: "service-request",
|
|
3029
|
+
payload: requestPayload,
|
|
3030
|
+
signature
|
|
3031
|
+
})
|
|
3032
|
+
});
|
|
3033
|
+
if (!resp.ok) {
|
|
3034
|
+
const body = await resp.text();
|
|
3035
|
+
return fail(`Relay send failed (${resp.status}): ${body}`);
|
|
3036
|
+
}
|
|
3037
|
+
const result = await resp.json();
|
|
3038
|
+
return ok({
|
|
3039
|
+
sent: true,
|
|
3040
|
+
requestId: result.id,
|
|
3041
|
+
to: targetKey,
|
|
3042
|
+
serviceId,
|
|
3043
|
+
paymentIncluded: paymentData && !paymentData.error,
|
|
3044
|
+
paymentTxid: paymentData?.txid || null,
|
|
3045
|
+
satoshis: paymentData?.satoshis || 0,
|
|
3046
|
+
note: "Poll for service-response to get the result"
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
var init_request = __esm({
|
|
3050
|
+
"src/scripts/services/request.ts"() {
|
|
3051
|
+
"use strict";
|
|
3052
|
+
init_config();
|
|
3053
|
+
init_output();
|
|
3054
|
+
init_identity();
|
|
3055
|
+
init_build();
|
|
3056
|
+
}
|
|
3057
|
+
});
|
|
3058
|
+
|
|
3059
|
+
// src/scripts/services/respond.ts
|
|
3060
|
+
import fs11 from "node:fs";
|
|
3061
|
+
async function cmdRespondService(requestId, recipientKey, serviceId, resultJson) {
|
|
3062
|
+
if (!requestId || !recipientKey || !serviceId || !resultJson) {
|
|
3063
|
+
return fail("Usage: respond-service <requestId> <recipientKey> <serviceId> <resultJson>");
|
|
3064
|
+
}
|
|
3065
|
+
let result;
|
|
3066
|
+
try {
|
|
3067
|
+
result = JSON.parse(resultJson);
|
|
3068
|
+
} catch {
|
|
3069
|
+
return fail("resultJson must be valid JSON");
|
|
3070
|
+
}
|
|
3071
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
3072
|
+
if (fs11.existsSync(PATHS.serviceQueue)) {
|
|
3073
|
+
const lines = fs11.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
|
|
3074
|
+
const finalStatuses = ["fulfilled", "rejected", "delivery_failed", "failed", "error"];
|
|
3075
|
+
for (const line of lines) {
|
|
3076
|
+
try {
|
|
3077
|
+
const entry = JSON.parse(line);
|
|
3078
|
+
if (entry.requestId === requestId && finalStatuses.includes(entry.status)) {
|
|
3079
|
+
return ok({
|
|
3080
|
+
sent: false,
|
|
3081
|
+
requestId,
|
|
3082
|
+
serviceId,
|
|
3083
|
+
to: recipientKey,
|
|
3084
|
+
message: `Request already processed with status: ${entry.status}`,
|
|
3085
|
+
alreadyProcessed: true,
|
|
3086
|
+
previousStatus: entry.status
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
3089
|
+
} catch {
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
const responsePayload = {
|
|
3094
|
+
requestId,
|
|
3095
|
+
serviceId,
|
|
3096
|
+
status: "fulfilled",
|
|
3097
|
+
result
|
|
3098
|
+
};
|
|
3099
|
+
const sig = await signRelayMessage(privKey, recipientKey, "service-response", responsePayload);
|
|
3100
|
+
const resp = await fetch(`${OVERLAY_URL}/relay/send`, {
|
|
3101
|
+
method: "POST",
|
|
3102
|
+
headers: { "Content-Type": "application/json" },
|
|
3103
|
+
body: JSON.stringify({
|
|
3104
|
+
from: identityKey,
|
|
3105
|
+
to: recipientKey,
|
|
3106
|
+
type: "service-response",
|
|
3107
|
+
payload: responsePayload,
|
|
3108
|
+
signature: sig
|
|
3109
|
+
})
|
|
3110
|
+
});
|
|
3111
|
+
if (!resp.ok) {
|
|
3112
|
+
updateServiceQueueStatus(requestId, "failed", {
|
|
3113
|
+
failedAt: Date.now(),
|
|
3114
|
+
error: `Relay send failed: ${resp.status}`
|
|
3115
|
+
});
|
|
3116
|
+
return fail(`Relay send failed: ${resp.status}`);
|
|
3117
|
+
}
|
|
3118
|
+
updateServiceQueueStatus(requestId, "fulfilled", {
|
|
3119
|
+
fulfilledAt: Date.now()
|
|
3120
|
+
});
|
|
3121
|
+
return ok({ sent: true, requestId, serviceId, to: recipientKey });
|
|
3122
|
+
}
|
|
3123
|
+
async function cmdResearchRespond(resultJsonPath) {
|
|
3124
|
+
if (!resultJsonPath) return fail("Usage: research-respond <resultJsonFile>");
|
|
3125
|
+
if (!fs11.existsSync(resultJsonPath)) return fail(`File not found: ${resultJsonPath}`);
|
|
3126
|
+
const result = JSON.parse(fs11.readFileSync(resultJsonPath, "utf-8"));
|
|
3127
|
+
const { requestId, from: recipientKey, query, research } = result;
|
|
3128
|
+
if (!requestId || !recipientKey || !research) {
|
|
3129
|
+
return fail("Result JSON must have: requestId, from, query, research");
|
|
3130
|
+
}
|
|
3131
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
3132
|
+
const responsePayload = {
|
|
3133
|
+
requestId,
|
|
3134
|
+
serviceId: "web-research",
|
|
3135
|
+
status: "fulfilled",
|
|
3136
|
+
result: research,
|
|
3137
|
+
paymentAccepted: true,
|
|
3138
|
+
paymentTxid: result.paymentTxid || null,
|
|
3139
|
+
satoshisReceived: result.satoshisReceived || 0,
|
|
3140
|
+
walletAccepted: result.walletAccepted ?? true
|
|
3141
|
+
};
|
|
3142
|
+
const sig = await signRelayMessage(privKey, recipientKey, "service-response", responsePayload);
|
|
3143
|
+
const sendResp = await fetch(`${OVERLAY_URL}/relay/send`, {
|
|
3144
|
+
method: "POST",
|
|
3145
|
+
headers: { "Content-Type": "application/json" },
|
|
3146
|
+
body: JSON.stringify({
|
|
3147
|
+
from: identityKey,
|
|
3148
|
+
to: recipientKey,
|
|
3149
|
+
type: "service-response",
|
|
3150
|
+
payload: responsePayload,
|
|
3151
|
+
signature: sig
|
|
3152
|
+
})
|
|
3153
|
+
});
|
|
3154
|
+
if (!sendResp.ok) {
|
|
3155
|
+
return fail(`Failed to send response: ${await sendResp.text()}`);
|
|
3156
|
+
}
|
|
3157
|
+
const sendResult = await sendResp.json();
|
|
3158
|
+
if (fs11.existsSync(PATHS.researchQueue)) {
|
|
3159
|
+
const lines = fs11.readFileSync(PATHS.researchQueue, "utf-8").trim().split("\n").filter(Boolean);
|
|
3160
|
+
const remaining = lines.filter((l) => {
|
|
3161
|
+
try {
|
|
3162
|
+
return JSON.parse(l).requestId !== requestId;
|
|
3163
|
+
} catch {
|
|
3164
|
+
return true;
|
|
3165
|
+
}
|
|
3166
|
+
});
|
|
3167
|
+
fs11.writeFileSync(PATHS.researchQueue, remaining.length ? remaining.join("\n") + "\n" : "");
|
|
3168
|
+
}
|
|
3169
|
+
return ok({ responded: true, requestId, to: recipientKey, query, pushed: sendResult.pushed });
|
|
3170
|
+
}
|
|
3171
|
+
var init_respond = __esm({
|
|
3172
|
+
"src/scripts/services/respond.ts"() {
|
|
3173
|
+
"use strict";
|
|
3174
|
+
init_config();
|
|
3175
|
+
init_output();
|
|
3176
|
+
init_identity();
|
|
3177
|
+
init_storage();
|
|
3178
|
+
}
|
|
3179
|
+
});
|
|
3180
|
+
|
|
3181
|
+
// src/scripts/services/queue.ts
|
|
3182
|
+
import fs12 from "node:fs";
|
|
3183
|
+
async function cmdServiceQueue() {
|
|
3184
|
+
if (!fs12.existsSync(PATHS.serviceQueue)) {
|
|
3185
|
+
return ok({ pending: [], count: 0 });
|
|
3186
|
+
}
|
|
3187
|
+
const entries = readJsonl(PATHS.serviceQueue);
|
|
3188
|
+
const pending = entries.filter((e) => e.status === "pending");
|
|
3189
|
+
return ok({ pending, count: pending.length, total: entries.length });
|
|
3190
|
+
}
|
|
3191
|
+
async function cmdResearchQueue() {
|
|
3192
|
+
if (!fs12.existsSync(PATHS.researchQueue)) {
|
|
3193
|
+
return ok({ pending: [] });
|
|
3194
|
+
}
|
|
3195
|
+
const entries = readJsonl(PATHS.researchQueue);
|
|
3196
|
+
return ok({ pending: entries, count: entries.length });
|
|
3197
|
+
}
|
|
3198
|
+
var init_queue = __esm({
|
|
3199
|
+
"src/scripts/services/queue.ts"() {
|
|
3200
|
+
"use strict";
|
|
3201
|
+
init_config();
|
|
3202
|
+
init_output();
|
|
3203
|
+
init_storage();
|
|
3204
|
+
}
|
|
3205
|
+
});
|
|
3206
|
+
|
|
3207
|
+
// src/scripts/x-verification/commands.ts
|
|
3208
|
+
import fs13 from "node:fs";
|
|
3209
|
+
async function getSdk5() {
|
|
3210
|
+
if (_sdk5) return _sdk5;
|
|
3211
|
+
try {
|
|
3212
|
+
_sdk5 = await import("@bsv/sdk");
|
|
3213
|
+
return _sdk5;
|
|
3214
|
+
} catch {
|
|
3215
|
+
const { fileURLToPath: fileURLToPath3 } = await import("node:url");
|
|
3216
|
+
const path5 = await import("node:path");
|
|
3217
|
+
const os3 = await import("node:os");
|
|
3218
|
+
const __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
|
|
3219
|
+
const candidates = [
|
|
3220
|
+
path5.resolve(__dirname3, "..", "..", "..", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
3221
|
+
path5.resolve(__dirname3, "..", "..", "..", "..", "..", "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js"),
|
|
3222
|
+
path5.resolve(os3.homedir(), "a2a-bsv", "packages", "core", "node_modules", "@bsv", "sdk", "dist", "esm", "mod.js")
|
|
3223
|
+
];
|
|
3224
|
+
for (const p of candidates) {
|
|
3225
|
+
try {
|
|
3226
|
+
_sdk5 = await import(p);
|
|
3227
|
+
return _sdk5;
|
|
3228
|
+
} catch {
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
async function cmdXVerifyStart(handleArg) {
|
|
3235
|
+
if (!handleArg) {
|
|
3236
|
+
return fail("Usage: x-verify-start <@handle>");
|
|
3237
|
+
}
|
|
3238
|
+
const sdk = await getSdk5();
|
|
3239
|
+
const handle = handleArg.startsWith("@") ? handleArg : `@${handleArg}`;
|
|
3240
|
+
const { identityKey, privKey } = await loadIdentity();
|
|
3241
|
+
const message = `Verify ${identityKey}`;
|
|
3242
|
+
const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(message)));
|
|
3243
|
+
const sig = privKey.sign(msgHash);
|
|
3244
|
+
const signatureHex = Array.from(sig.toDER()).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3245
|
+
const pending = {
|
|
3246
|
+
identityKey,
|
|
3247
|
+
handle,
|
|
3248
|
+
signature: signatureHex,
|
|
3249
|
+
message,
|
|
3250
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3251
|
+
};
|
|
3252
|
+
ensureStateDir();
|
|
3253
|
+
fs13.writeFileSync(PATHS.pendingXVerification, JSON.stringify(pending, null, 2));
|
|
3254
|
+
const tweetText = `BSV Agent Verify: ${identityKey.slice(0, 10)}...${identityKey.slice(-10)} sig:${signatureHex.slice(0, 40)}`;
|
|
3255
|
+
return ok({
|
|
3256
|
+
tweetText,
|
|
3257
|
+
handle,
|
|
3258
|
+
identityKey,
|
|
3259
|
+
signature: signatureHex,
|
|
3260
|
+
note: `Post the tweet above from ${handle}, then run: x-verify-complete <tweet_url>`
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
async function cmdXVerifyComplete(tweetUrl) {
|
|
3264
|
+
if (!tweetUrl) return fail("Usage: x-verify-complete <tweet-url>");
|
|
3265
|
+
if (!fs13.existsSync(PATHS.pendingXVerification)) {
|
|
3266
|
+
return fail("No pending X verification. Run x-verify-start first.");
|
|
3267
|
+
}
|
|
3268
|
+
const pending = JSON.parse(fs13.readFileSync(PATHS.pendingXVerification, "utf-8"));
|
|
3269
|
+
const tweetIdMatch = tweetUrl.match(/status\/(\d+)/);
|
|
3270
|
+
if (!tweetIdMatch) return fail("Invalid tweet URL. Expected format: https://x.com/user/status/123456789");
|
|
3271
|
+
const tweetId = tweetIdMatch[1];
|
|
3272
|
+
let tweetData;
|
|
3273
|
+
try {
|
|
3274
|
+
return fail('X verification via "bird" CLI is currently disabled for security compliance. Please verify manually.');
|
|
3275
|
+
} catch (err) {
|
|
3276
|
+
return fail(`Failed to fetch tweet: ${err.message}. Make sure bird CLI is configured.`);
|
|
3277
|
+
}
|
|
3278
|
+
const tweetText = tweetData.text || tweetData.full_text || "";
|
|
3279
|
+
if (!tweetText.includes(pending.identityKey.slice(0, 10))) {
|
|
3280
|
+
return fail("Tweet does not contain the expected identity key.");
|
|
3281
|
+
}
|
|
3282
|
+
if (!tweetText.includes(pending.signature.slice(0, 40))) {
|
|
3283
|
+
return fail("Tweet does not contain the expected verification signature prefix.");
|
|
3284
|
+
}
|
|
3285
|
+
const xUserId = tweetData.user?.id_str || tweetData.authorId || tweetData.author?.id || tweetData.user_id;
|
|
3286
|
+
const xHandle = tweetData.user?.screen_name || tweetData.author?.username || tweetData.author?.name || pending.handle.replace("@", "");
|
|
3287
|
+
if (!xUserId) {
|
|
3288
|
+
return fail("Could not extract X user ID from tweet data.");
|
|
3289
|
+
}
|
|
3290
|
+
const verificationPayload = {
|
|
3291
|
+
protocol: PROTOCOL_ID,
|
|
3292
|
+
type: "x-verification",
|
|
3293
|
+
identityKey: pending.identityKey,
|
|
3294
|
+
xHandle: `@${xHandle}`,
|
|
3295
|
+
xUserId,
|
|
3296
|
+
tweetId,
|
|
3297
|
+
tweetUrl,
|
|
3298
|
+
signature: pending.signature,
|
|
3299
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3300
|
+
};
|
|
3301
|
+
let result = { txid: null, funded: "pending-server-support" };
|
|
3302
|
+
let onChainStored = false;
|
|
3303
|
+
try {
|
|
3304
|
+
result = await buildRealOverlayTransaction(verificationPayload, TOPICS.X_VERIFICATION);
|
|
3305
|
+
onChainStored = true;
|
|
3306
|
+
} catch (err) {
|
|
3307
|
+
console.error(`[x-verify] On-chain storage failed: ${err.message}`);
|
|
3308
|
+
console.error("[x-verify] Storing verification locally.");
|
|
3309
|
+
}
|
|
3310
|
+
const verifications = loadXVerifications();
|
|
3311
|
+
verifications.push({
|
|
3312
|
+
...verificationPayload,
|
|
3313
|
+
txid: result.txid
|
|
3314
|
+
});
|
|
3315
|
+
saveXVerifications(verifications);
|
|
3316
|
+
fs13.unlinkSync(PATHS.pendingXVerification);
|
|
3317
|
+
return ok({
|
|
3318
|
+
verified: true,
|
|
3319
|
+
identityKey: pending.identityKey,
|
|
3320
|
+
xHandle: `@${xHandle}`,
|
|
3321
|
+
xUserId,
|
|
3322
|
+
tweetId,
|
|
3323
|
+
txid: result.txid,
|
|
3324
|
+
funded: result.funded,
|
|
3325
|
+
onChainStored,
|
|
3326
|
+
note: onChainStored ? void 0 : "Stored locally. On-chain anchoring pending server topic manager deployment."
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
async function cmdXVerifications() {
|
|
3330
|
+
const verifications = loadXVerifications();
|
|
3331
|
+
return ok({ verifications, count: verifications.length });
|
|
3332
|
+
}
|
|
3333
|
+
async function cmdXLookup(query) {
|
|
3334
|
+
try {
|
|
3335
|
+
const lookupQuery = query ? query.startsWith("@") ? { xHandle: query } : { identityKey: query } : { type: "list" };
|
|
3336
|
+
const response = await lookupOverlay(LOOKUP_SERVICES.X_VERIFICATIONS, lookupQuery);
|
|
3337
|
+
return ok({ verifications: response.outputs || response || [], query: lookupQuery });
|
|
3338
|
+
} catch {
|
|
3339
|
+
return ok({ verifications: [], query, note: "X verification lookup service may not be deployed yet." });
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
async function cmdXEngagementQueue() {
|
|
3343
|
+
if (!fs13.existsSync(PATHS.xEngagementQueue)) {
|
|
3344
|
+
return ok({ queue: [], count: 0 });
|
|
3345
|
+
}
|
|
3346
|
+
const queue = readJsonl(PATHS.xEngagementQueue).filter((e) => e.status === "pending");
|
|
3347
|
+
return ok({ queue, count: queue.length });
|
|
3348
|
+
}
|
|
3349
|
+
async function cmdXEngagementFulfill(requestId, proofUrl) {
|
|
3350
|
+
if (!requestId) return fail("Usage: x-engagement-fulfill <requestId> [proofUrl]");
|
|
3351
|
+
if (!fs13.existsSync(PATHS.xEngagementQueue)) {
|
|
3352
|
+
return fail("No engagement queue found.");
|
|
3353
|
+
}
|
|
3354
|
+
const queue = readJsonl(PATHS.xEngagementQueue);
|
|
3355
|
+
const entryIndex = queue.findIndex((e) => e.requestId === requestId);
|
|
3356
|
+
if (entryIndex === -1) {
|
|
3357
|
+
return fail(`Request ${requestId} not found in queue.`);
|
|
3358
|
+
}
|
|
3359
|
+
queue[entryIndex].status = "fulfilled";
|
|
3360
|
+
queue[entryIndex].fulfilledAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3361
|
+
queue[entryIndex].proofUrl = proofUrl || null;
|
|
3362
|
+
fs13.writeFileSync(PATHS.xEngagementQueue, queue.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
3363
|
+
return ok({
|
|
3364
|
+
fulfilled: true,
|
|
3365
|
+
requestId,
|
|
3366
|
+
entry: queue[entryIndex]
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
var _sdk5;
|
|
3370
|
+
var init_commands2 = __esm({
|
|
3371
|
+
"src/scripts/x-verification/commands.ts"() {
|
|
3372
|
+
"use strict";
|
|
3373
|
+
init_config();
|
|
3374
|
+
init_output();
|
|
3375
|
+
init_identity();
|
|
3376
|
+
init_storage();
|
|
3377
|
+
init_transaction();
|
|
3378
|
+
_sdk5 = null;
|
|
3379
|
+
}
|
|
3380
|
+
});
|
|
3381
|
+
|
|
3382
|
+
// src/scripts/baemail/commands.ts
|
|
3383
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3384
|
+
import path4 from "node:path";
|
|
3385
|
+
import os2 from "node:os";
|
|
3386
|
+
import fs14 from "node:fs";
|
|
3387
|
+
import process2 from "node:process";
|
|
3388
|
+
async function loadBaemailConfig() {
|
|
3389
|
+
const defaults = {
|
|
3390
|
+
enabled: true,
|
|
3391
|
+
priceSats: 100,
|
|
3392
|
+
autoRefund: true,
|
|
3393
|
+
blocklist: [],
|
|
3394
|
+
maxMessageLength: 4e3,
|
|
3395
|
+
deliveryChannel: "agent-hook",
|
|
3396
|
+
tiers: {
|
|
3397
|
+
standard: 100,
|
|
3398
|
+
priority: 500,
|
|
3399
|
+
urgent: 1e3
|
|
3400
|
+
}
|
|
3401
|
+
};
|
|
3402
|
+
if (!fs14.existsSync(PATHS2.baemailConfig)) {
|
|
3403
|
+
return defaults;
|
|
3404
|
+
}
|
|
3405
|
+
try {
|
|
3406
|
+
const config = JSON.parse(fs14.readFileSync(PATHS2.baemailConfig, "utf-8"));
|
|
3407
|
+
return { ...defaults, ...config };
|
|
3408
|
+
} catch {
|
|
3409
|
+
return defaults;
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
async function fetchWithTimeout2(url, options = {}) {
|
|
3413
|
+
const { timeout = 15e3 } = options;
|
|
3414
|
+
const controller = new AbortController();
|
|
3415
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
3416
|
+
try {
|
|
3417
|
+
const response = await fetch(url, {
|
|
3418
|
+
...options,
|
|
3419
|
+
signal: controller.signal
|
|
3420
|
+
});
|
|
3421
|
+
clearTimeout(id);
|
|
3422
|
+
return response;
|
|
3423
|
+
} catch (err) {
|
|
3424
|
+
clearTimeout(id);
|
|
3425
|
+
throw err;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
async function cmdBaemailLog(limitStr) {
|
|
3429
|
+
const limit = parseInt(limitStr || "20", 10) || 20;
|
|
3430
|
+
if (!fs14.existsSync(PATHS2.baemailLog)) {
|
|
3431
|
+
return ok({ log: [], count: 0 });
|
|
3432
|
+
}
|
|
3433
|
+
const lines = fs14.readFileSync(PATHS2.baemailLog, "utf-8").split("\n").filter((l) => l.trim());
|
|
3434
|
+
const entries = lines.map((l) => {
|
|
3435
|
+
try {
|
|
3436
|
+
return JSON.parse(l);
|
|
3437
|
+
} catch {
|
|
3438
|
+
return null;
|
|
3439
|
+
}
|
|
3440
|
+
}).filter(Boolean);
|
|
3441
|
+
const recent = entries.slice(-limit).reverse();
|
|
3442
|
+
return ok({ log: recent, count: entries.length, showing: recent.length });
|
|
3443
|
+
}
|
|
3444
|
+
async function cmdBaemailSetup(priceSatsStr, prioritySats, urgentSats, channel) {
|
|
3445
|
+
const standard = parseInt(priceSatsStr, 10) || 100;
|
|
3446
|
+
const priority = parseInt(prioritySats || "", 10) || standard * 5;
|
|
3447
|
+
const urgent = parseInt(urgentSats || "", 10) || standard * 10;
|
|
3448
|
+
const config = {
|
|
3449
|
+
enabled: true,
|
|
3450
|
+
priceSats: standard,
|
|
3451
|
+
autoRefund: true,
|
|
3452
|
+
deliveryChannel: channel || "agent-hook",
|
|
3453
|
+
tiers: { standard, priority, urgent }
|
|
3454
|
+
};
|
|
3455
|
+
const dir = path4.dirname(PATHS2.baemailConfig);
|
|
3456
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
3457
|
+
fs14.writeFileSync(PATHS2.baemailConfig, JSON.stringify(config, null, 2));
|
|
3458
|
+
return ok({ message: `Baemail setup complete.`, config });
|
|
3459
|
+
}
|
|
3460
|
+
async function cmdBaemailConfig() {
|
|
3461
|
+
const config = await loadBaemailConfig();
|
|
3462
|
+
return ok(config);
|
|
3463
|
+
}
|
|
3464
|
+
async function cmdBaemailBlock(pubkey) {
|
|
3465
|
+
if (!pubkey) return fail("Usage: baemail-block <pubkey>");
|
|
3466
|
+
let blocklist = [];
|
|
3467
|
+
if (fs14.existsSync(PATHS2.baemailBlocklist)) {
|
|
3468
|
+
try {
|
|
3469
|
+
blocklist = JSON.parse(fs14.readFileSync(PATHS2.baemailBlocklist, "utf-8"));
|
|
3470
|
+
} catch {
|
|
3471
|
+
blocklist = [];
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
if (!blocklist.includes(pubkey)) blocklist.push(pubkey);
|
|
3475
|
+
fs14.writeFileSync(PATHS2.baemailBlocklist, JSON.stringify(blocklist, null, 2));
|
|
3476
|
+
return ok({ blocked: true, pubkey, count: blocklist.length });
|
|
3477
|
+
}
|
|
3478
|
+
async function cmdBaemailUnblock(pubkey) {
|
|
3479
|
+
if (!pubkey) return fail("Usage: baemail-unblock <pubkey>");
|
|
3480
|
+
let blocklist = [];
|
|
3481
|
+
if (fs14.existsSync(PATHS2.baemailBlocklist)) {
|
|
3482
|
+
try {
|
|
3483
|
+
blocklist = JSON.parse(fs14.readFileSync(PATHS2.baemailBlocklist, "utf-8"));
|
|
3484
|
+
} catch {
|
|
3485
|
+
blocklist = [];
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
blocklist = blocklist.filter((p) => p !== pubkey);
|
|
3489
|
+
fs14.writeFileSync(PATHS2.baemailBlocklist, JSON.stringify(blocklist, null, 2));
|
|
3490
|
+
return ok({ unblocked: true, pubkey, count: blocklist.length });
|
|
3491
|
+
}
|
|
3492
|
+
async function cmdBaemailRefund(requestId) {
|
|
3493
|
+
if (!requestId) return fail("Usage: baemail-refund <requestId>");
|
|
3494
|
+
if (!fs14.existsSync(PATHS2.baemailLog)) {
|
|
3495
|
+
return fail("No baemail log found");
|
|
3496
|
+
}
|
|
3497
|
+
const lines = fs14.readFileSync(PATHS2.baemailLog, "utf-8").split("\n").filter((l) => l.trim());
|
|
3498
|
+
const entries = lines.map((l, idx) => {
|
|
3499
|
+
try {
|
|
3500
|
+
return { ...JSON.parse(l), _lineIdx: idx };
|
|
3501
|
+
} catch {
|
|
3502
|
+
return null;
|
|
3503
|
+
}
|
|
3504
|
+
}).filter(Boolean);
|
|
3505
|
+
const entry = entries.find((e) => e.requestId === requestId);
|
|
3506
|
+
if (!entry) {
|
|
3507
|
+
return fail(`Request ${requestId} not found in baemail log`);
|
|
3508
|
+
}
|
|
3509
|
+
if (entry.deliverySuccess) {
|
|
3510
|
+
return fail("This delivery was successful \u2014 no refund needed");
|
|
3511
|
+
}
|
|
3512
|
+
if (entry.refundStatus === "completed") {
|
|
3513
|
+
return fail("Refund already processed for this request");
|
|
3514
|
+
}
|
|
3515
|
+
const { identityKey, privKey: rootKey } = await loadIdentity();
|
|
3516
|
+
const walletIdentityRaw = fs14.readFileSync(PATHS2.walletIdentity, "utf-8");
|
|
3517
|
+
const walletIdentity = JSON.parse(walletIdentityRaw);
|
|
3518
|
+
let sdk;
|
|
3519
|
+
try {
|
|
3520
|
+
sdk = await import("@bsv/sdk");
|
|
3521
|
+
} catch {
|
|
3522
|
+
return fail("Cannot load @bsv/sdk for refund transaction");
|
|
3523
|
+
}
|
|
3524
|
+
const { Transaction: Transaction3, P2PKH, PrivateKey: PrivateKey2, PublicKey, Hash } = sdk;
|
|
3525
|
+
const refundSats = entry.paidSats - 1;
|
|
3526
|
+
if (refundSats < 1) {
|
|
3527
|
+
return fail("Amount too small to refund");
|
|
3528
|
+
}
|
|
3529
|
+
const senderPubKey = PublicKey.fromString(entry.from);
|
|
3530
|
+
const refundAddress = senderPubKey.toAddress(NETWORK).toString();
|
|
3531
|
+
try {
|
|
3532
|
+
const { address } = await deriveWalletAddress(rootKey);
|
|
3533
|
+
const wocNet = NETWORK === "mainnet" ? "main" : "test";
|
|
3534
|
+
const utxosResp = await fetchWithTimeout2(`https://api.whatsonchain.com/v1/bsv/${wocNet}/address/${address}/unspent/all`);
|
|
3535
|
+
const data = await utxosResp.json();
|
|
3536
|
+
const utxos = data.result || [];
|
|
3537
|
+
if (!utxos || utxos.length === 0) {
|
|
3538
|
+
return fail(`No UTXOs available for refund at ${address}`);
|
|
3539
|
+
}
|
|
3540
|
+
const tx = new Transaction3();
|
|
3541
|
+
let totalInput = 0;
|
|
3542
|
+
for (const utxo of utxos) {
|
|
3543
|
+
if (totalInput >= refundSats + 50) break;
|
|
3544
|
+
tx.addInput({
|
|
3545
|
+
sourceTXID: utxo.tx_hash,
|
|
3546
|
+
sourceOutputIndex: utxo.tx_pos,
|
|
3547
|
+
sourceSatoshis: utxo.value,
|
|
3548
|
+
script: new P2PKH().lock(rootKey.toPublicKey().toAddress(NETWORK)).toHex(),
|
|
3549
|
+
unlockingScriptTemplate: new P2PKH().unlock(rootKey)
|
|
3550
|
+
});
|
|
3551
|
+
totalInput += utxo.value;
|
|
3552
|
+
}
|
|
3553
|
+
if (totalInput < refundSats + 10) {
|
|
3554
|
+
return fail("Insufficient funds for refund");
|
|
3555
|
+
}
|
|
3556
|
+
tx.addOutput({
|
|
3557
|
+
satoshis: refundSats,
|
|
3558
|
+
lockingScript: new P2PKH().lock(refundAddress)
|
|
3559
|
+
});
|
|
3560
|
+
const fee = 10;
|
|
3561
|
+
const change = totalInput - refundSats - fee;
|
|
3562
|
+
if (change > 1) {
|
|
3563
|
+
tx.addOutput({
|
|
3564
|
+
satoshis: change,
|
|
3565
|
+
lockingScript: new P2PKH().lock(rootKey.toPublicKey().toAddress(NETWORK))
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3568
|
+
await tx.sign();
|
|
3569
|
+
const arcUrl = process2["env"].BSV_ARC_URL;
|
|
3570
|
+
let broadcastResp;
|
|
3571
|
+
if (arcUrl) {
|
|
3572
|
+
broadcastResp = await fetchWithTimeout2(`${arcUrl.replace(/\/$/, "")}/v1/tx`, {
|
|
3573
|
+
method: "POST",
|
|
3574
|
+
headers: { "Content-Type": "application/json" },
|
|
3575
|
+
body: JSON.stringify({ rawTx: tx.toHex() })
|
|
3576
|
+
});
|
|
3577
|
+
} else {
|
|
3578
|
+
broadcastResp = await fetchWithTimeout2(`https://api.whatsonchain.com/v1/bsv/${wocNet}/tx/raw`, {
|
|
3579
|
+
method: "POST",
|
|
3580
|
+
headers: { "Content-Type": "application/json" },
|
|
3581
|
+
body: JSON.stringify({ txhex: tx.toHex() })
|
|
3582
|
+
});
|
|
3583
|
+
}
|
|
3584
|
+
if (!broadcastResp.ok) {
|
|
3585
|
+
const errBody = await broadcastResp.text();
|
|
3586
|
+
return fail(`Broadcast failed: ${errBody}`);
|
|
3587
|
+
}
|
|
3588
|
+
const txid = tx.id("hex");
|
|
3589
|
+
const updatedLines = lines.map((l, idx) => {
|
|
3590
|
+
if (idx === entry._lineIdx) {
|
|
3591
|
+
const updated = { ...JSON.parse(l), refundStatus: "completed", refundTxid: txid, refundedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3592
|
+
return JSON.stringify(updated);
|
|
3593
|
+
}
|
|
3594
|
+
return l;
|
|
3595
|
+
});
|
|
3596
|
+
fs14.writeFileSync(PATHS2.baemailLog, updatedLines.join("\n") + "\n");
|
|
3597
|
+
return ok({
|
|
3598
|
+
refunded: true,
|
|
3599
|
+
requestId,
|
|
3600
|
+
refundSats,
|
|
3601
|
+
refundAddress,
|
|
3602
|
+
txid,
|
|
3603
|
+
note: `Refunded ${refundSats} sats to sender`
|
|
3604
|
+
});
|
|
3605
|
+
} catch (err) {
|
|
3606
|
+
return fail(`Refund failed: ${err.message}`);
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
var __filename2, __dirname2, PATHS2;
|
|
3610
|
+
var init_commands3 = __esm({
|
|
3611
|
+
"src/scripts/baemail/commands.ts"() {
|
|
3612
|
+
"use strict";
|
|
3613
|
+
init_output();
|
|
3614
|
+
init_identity();
|
|
3615
|
+
init_config();
|
|
3616
|
+
__filename2 = fileURLToPath2(import.meta.url);
|
|
3617
|
+
__dirname2 = path4.dirname(__filename2);
|
|
3618
|
+
PATHS2 = {
|
|
3619
|
+
walletIdentity: path4.join(os2.homedir(), ".openclaw", "bsv-wallet", "wallet-identity.json"),
|
|
3620
|
+
baemailLog: path4.join(os2.homedir(), ".openclaw", "openclaw-overlay", "baemail-deliveries.jsonl"),
|
|
3621
|
+
baemailConfig: path4.join(os2.homedir(), ".openclaw", "openclaw-overlay", "baemail-config.json"),
|
|
3622
|
+
baemailBlocklist: path4.join(os2.homedir(), ".openclaw", "openclaw-overlay", "baemail-blocklist.json")
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
});
|
|
3626
|
+
|
|
3627
|
+
// src/cli-main.ts
|
|
3628
|
+
var cli_main_exports = {};
|
|
3629
|
+
async function main() {
|
|
3630
|
+
try {
|
|
3631
|
+
switch (command) {
|
|
3632
|
+
// Help
|
|
3633
|
+
case "help":
|
|
3634
|
+
case "--help":
|
|
3635
|
+
case "-h":
|
|
3636
|
+
ok({
|
|
3637
|
+
usage: "sv_overlay <command> [args...]",
|
|
3638
|
+
commands: {
|
|
3639
|
+
wallet: ["setup", "identity", "address", "balance", "import <txid> [vout]", "refund <address>"],
|
|
3640
|
+
registration: ["register", "unregister"],
|
|
3641
|
+
services: ["services", "advertise <id> <name> <priceSats> [desc]", "readvertise <id> [name] [priceSats] [desc]", "remove <id>"],
|
|
3642
|
+
discovery: ["discover [--service <type>] [--agent <name>]", "advertise-ship <domain> <topic>", "advertise-slap <domain> <service>"],
|
|
3643
|
+
payments: ["pay <pubkey> <sats> [desc]", "verify <beef>", "accept <beef> <prefix> <suffix> <senderKey> [desc]"],
|
|
3644
|
+
messaging: ["send <key> <type> <json>", "inbox", "ack", "poll", "connect"],
|
|
3645
|
+
"service-requests": ["request-service <key> <serviceId> <sats> [input]", "service-queue", "respond-service <reqId> <key> <serviceId> <result>"],
|
|
3646
|
+
research: ["research-queue", "research-respond <reqId>"],
|
|
3647
|
+
"x-verification": ["x-verify-start <handle>", "x-verify-complete <handle>", "x-verifications", "x-lookup <handle>", "x-engagement-queue", "x-engagement-fulfill <reqId> <result>"],
|
|
3648
|
+
baemail: ["baemail-setup <price> <xHandle> <forwardEmail> <greeting>", "baemail-config", "baemail-block <sender>", "baemail-unblock <sender>", "baemail-log [limit]", "baemail-refund <msgId>"]
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
3651
|
+
break;
|
|
3652
|
+
// Wallet
|
|
3653
|
+
case "status":
|
|
3654
|
+
await cmdStatus();
|
|
3655
|
+
break;
|
|
3656
|
+
case "setup":
|
|
3657
|
+
await cmdSetup();
|
|
3658
|
+
break;
|
|
3659
|
+
case "identity":
|
|
3660
|
+
await cmdIdentity();
|
|
3661
|
+
break;
|
|
3662
|
+
case "address":
|
|
3663
|
+
await cmdAddress();
|
|
3664
|
+
break;
|
|
3665
|
+
case "balance":
|
|
3666
|
+
await cmdBalance();
|
|
3667
|
+
break;
|
|
3668
|
+
case "import":
|
|
3669
|
+
await cmdImport(args[0], args[1]);
|
|
3670
|
+
break;
|
|
3671
|
+
case "refund":
|
|
3672
|
+
await cmdRefund(args[0]);
|
|
3673
|
+
break;
|
|
3674
|
+
// Overlay registration
|
|
3675
|
+
case "register":
|
|
3676
|
+
await cmdRegister();
|
|
3677
|
+
break;
|
|
3678
|
+
case "unregister":
|
|
3679
|
+
await cmdUnregister();
|
|
3680
|
+
break;
|
|
3681
|
+
// Services
|
|
3682
|
+
case "services":
|
|
3683
|
+
await cmdServices();
|
|
3684
|
+
break;
|
|
3685
|
+
case "advertise":
|
|
3686
|
+
await cmdAdvertise(args[0], args[1], args[2], args[3]);
|
|
3687
|
+
break;
|
|
3688
|
+
case "remove":
|
|
3689
|
+
await cmdRemove(args[0]);
|
|
3690
|
+
break;
|
|
3691
|
+
case "readvertise":
|
|
3692
|
+
await cmdReadvertise(args[0], args[1], args[2], args.slice(3).join(" ") || void 0);
|
|
3693
|
+
break;
|
|
3694
|
+
// Discovery
|
|
3695
|
+
case "discover":
|
|
3696
|
+
await cmdDiscover(args);
|
|
3697
|
+
break;
|
|
3698
|
+
case "advertise-ship":
|
|
3699
|
+
await cmdAdvertiseSHIP(args[0], args[1]);
|
|
3700
|
+
break;
|
|
3701
|
+
case "advertise-slap":
|
|
3702
|
+
await cmdAdvertiseSLAP(args[0], args[1]);
|
|
3703
|
+
break;
|
|
3704
|
+
// Payments
|
|
3705
|
+
case "pay":
|
|
3706
|
+
await cmdPay(args[0], args[1], args.slice(2).join(" ") || void 0);
|
|
3707
|
+
break;
|
|
3708
|
+
case "verify":
|
|
3709
|
+
await cmdVerify(args[0]);
|
|
3710
|
+
break;
|
|
3711
|
+
case "accept":
|
|
3712
|
+
await cmdAccept(args[0], args[1], args[2], args[3], args.slice(4).join(" ") || void 0);
|
|
3713
|
+
break;
|
|
3714
|
+
// Messaging (relay)
|
|
3715
|
+
case "send":
|
|
3716
|
+
await cmdSend(args[0], args[1], args[2]);
|
|
3717
|
+
break;
|
|
3718
|
+
case "inbox":
|
|
3719
|
+
await cmdInbox(args);
|
|
3720
|
+
break;
|
|
3721
|
+
case "ack":
|
|
3722
|
+
await cmdAck(args);
|
|
3723
|
+
break;
|
|
3724
|
+
case "poll":
|
|
3725
|
+
await cmdPoll();
|
|
3726
|
+
break;
|
|
3727
|
+
case "connect":
|
|
3728
|
+
await cmdConnect();
|
|
3729
|
+
break;
|
|
3730
|
+
case "request-service":
|
|
3731
|
+
await cmdRequestService(args[0], args[1], args[2], args[3]);
|
|
3732
|
+
break;
|
|
3733
|
+
case "research-respond":
|
|
3734
|
+
await cmdResearchRespond(args[0]);
|
|
3735
|
+
break;
|
|
3736
|
+
case "research-queue":
|
|
3737
|
+
await cmdResearchQueue();
|
|
3738
|
+
break;
|
|
3739
|
+
case "service-queue":
|
|
3740
|
+
await cmdServiceQueue();
|
|
3741
|
+
break;
|
|
3742
|
+
case "respond-service":
|
|
3743
|
+
await cmdRespondService(args[0], args[1], args[2], args.slice(3).join(" "));
|
|
3744
|
+
break;
|
|
3745
|
+
// X Account Verification
|
|
3746
|
+
case "x-verify-start":
|
|
3747
|
+
await cmdXVerifyStart(args[0]);
|
|
3748
|
+
break;
|
|
3749
|
+
case "x-verify-complete":
|
|
3750
|
+
await cmdXVerifyComplete(args[0]);
|
|
3751
|
+
break;
|
|
3752
|
+
case "x-verifications":
|
|
3753
|
+
await cmdXVerifications();
|
|
3754
|
+
break;
|
|
3755
|
+
case "x-lookup":
|
|
3756
|
+
await cmdXLookup(args[0]);
|
|
3757
|
+
break;
|
|
3758
|
+
// X Engagement Service
|
|
3759
|
+
case "x-engagement-queue":
|
|
3760
|
+
await cmdXEngagementQueue();
|
|
3761
|
+
break;
|
|
3762
|
+
case "x-engagement-fulfill":
|
|
3763
|
+
await cmdXEngagementFulfill(args[0], args[1]);
|
|
3764
|
+
break;
|
|
3765
|
+
// Baemail Service
|
|
3766
|
+
case "baemail-setup":
|
|
3767
|
+
await cmdBaemailSetup(args[0], args[1], args[2], args[3]);
|
|
3768
|
+
break;
|
|
3769
|
+
case "baemail-config":
|
|
3770
|
+
await cmdBaemailConfig();
|
|
3771
|
+
break;
|
|
3772
|
+
case "baemail-block":
|
|
3773
|
+
await cmdBaemailBlock(args[0]);
|
|
3774
|
+
break;
|
|
3775
|
+
case "baemail-unblock":
|
|
3776
|
+
await cmdBaemailUnblock(args[0]);
|
|
3777
|
+
break;
|
|
3778
|
+
case "baemail-log":
|
|
3779
|
+
await cmdBaemailLog(args[0]);
|
|
3780
|
+
break;
|
|
3781
|
+
case "baemail-refund":
|
|
3782
|
+
await cmdBaemailRefund(args[0]);
|
|
3783
|
+
break;
|
|
3784
|
+
default:
|
|
3785
|
+
fail(
|
|
3786
|
+
`Unknown command: ${command || "(none)"}. Commands: setup, identity, address, balance, import, refund, register, unregister, services, advertise, readvertise, remove, discover, pay, verify, accept, send, inbox, ack, poll, connect, request-service, research-queue, research-respond, service-queue, respond-service, x-verify-start, x-verify-complete, x-verifications, x-lookup, x-engagement-queue, x-engagement-fulfill, baemail-setup, baemail-config, baemail-block, baemail-unblock, baemail-log, baemail-refund`
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3789
|
+
} catch (err) {
|
|
3790
|
+
fail(err.message || String(err));
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
var command, args;
|
|
3794
|
+
var init_cli_main = __esm({
|
|
3795
|
+
"src/cli-main.ts"() {
|
|
3796
|
+
"use strict";
|
|
3797
|
+
init_output();
|
|
3798
|
+
init_setup();
|
|
3799
|
+
init_balance();
|
|
3800
|
+
init_registration();
|
|
3801
|
+
init_services();
|
|
3802
|
+
init_discover();
|
|
3803
|
+
init_advertisement();
|
|
3804
|
+
init_commands();
|
|
3805
|
+
init_send();
|
|
3806
|
+
init_inbox();
|
|
3807
|
+
init_poll();
|
|
3808
|
+
init_connect();
|
|
3809
|
+
init_request();
|
|
3810
|
+
init_respond();
|
|
3811
|
+
init_queue();
|
|
3812
|
+
init_commands2();
|
|
3813
|
+
init_commands3();
|
|
3814
|
+
[, , command, ...args] = process.argv;
|
|
3815
|
+
main();
|
|
3816
|
+
}
|
|
3817
|
+
});
|
|
3818
|
+
|
|
3819
|
+
// src/cli.ts
|
|
3820
|
+
process["env"].DOTENV_CONFIG_QUIET = "true";
|
|
3821
|
+
await Promise.resolve().then(() => (init_cli_main(), cli_main_exports));
|
|
13
3822
|
globalThis.window = { fetch: globalThis.fetch };
|
|
14
|
-
|
|
3823
|
+
//# sourceMappingURL=cli.js.map
|