openclaw-overlay-plugin 0.8.19 → 0.8.20
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 +607 -853
- package/dist/index.js.map +4 -4
- package/dist/src/cli.js +30 -568
- package/dist/src/cli.js.map +4 -4
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -86,740 +86,6 @@ var init_config = __esm({
|
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
// src/scripts/output.ts
|
|
90
|
-
function setNoExit(value) {
|
|
91
|
-
noExitFlag = value;
|
|
92
|
-
}
|
|
93
|
-
function ok(data) {
|
|
94
|
-
if (noExitFlag) return { success: true, data };
|
|
95
|
-
console.log(JSON.stringify({ success: true, data }));
|
|
96
|
-
process.exit(0);
|
|
97
|
-
}
|
|
98
|
-
function fail(error) {
|
|
99
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
-
if (noExitFlag) return { success: false, error: message };
|
|
101
|
-
console.log(JSON.stringify({ success: false, error: message }));
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
var noExitFlag;
|
|
105
|
-
var init_output = __esm({
|
|
106
|
-
"src/scripts/output.ts"() {
|
|
107
|
-
"use strict";
|
|
108
|
-
noExitFlag = false;
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// ../plugin-core/dist/config.js
|
|
113
|
-
function toChain(network) {
|
|
114
|
-
if (network === "testnet")
|
|
115
|
-
return "test";
|
|
116
|
-
return "main";
|
|
117
|
-
}
|
|
118
|
-
var DEFAULT_TAAL_API_KEYS, DEFAULT_DB_NAME;
|
|
119
|
-
var init_config2 = __esm({
|
|
120
|
-
"../plugin-core/dist/config.js"() {
|
|
121
|
-
"use strict";
|
|
122
|
-
DEFAULT_TAAL_API_KEYS = {
|
|
123
|
-
main: "mainnet_9596de07e92300c6287e4393594ae39c",
|
|
124
|
-
test: "testnet_0e6cf72133b43ea2d7861da2a38684e3"
|
|
125
|
-
};
|
|
126
|
-
DEFAULT_DB_NAME = "a2a_agent_wallet";
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// ../plugin-core/dist/payment.js
|
|
131
|
-
import { Beef, Utils as Utils2 } from "@bsv/sdk";
|
|
132
|
-
import { randomBytesBase64, ScriptTemplateBRC29 } from "@bsv/wallet-toolbox";
|
|
133
|
-
async function buildPayment(setup, params) {
|
|
134
|
-
const { to, satoshis, description } = params;
|
|
135
|
-
const desc = normalizeDescription(description ?? "agent payment");
|
|
136
|
-
const derivationPrefix = randomBytesBase64(8);
|
|
137
|
-
const derivationSuffix = randomBytesBase64(8);
|
|
138
|
-
const keyDeriver = setup.keyDeriver;
|
|
139
|
-
const t = new ScriptTemplateBRC29({
|
|
140
|
-
derivationPrefix,
|
|
141
|
-
derivationSuffix,
|
|
142
|
-
keyDeriver
|
|
143
|
-
});
|
|
144
|
-
let recipientPubKey;
|
|
145
|
-
if (/^0[23][0-9a-fA-F]{64}$/.test(to)) {
|
|
146
|
-
recipientPubKey = to;
|
|
147
|
-
} else {
|
|
148
|
-
throw new Error("PaymentParams.to must be a compressed public key (hex) for BRC-29 payments. Raw BSV addresses are not supported \u2014 the recipient must share their identity key.");
|
|
149
|
-
}
|
|
150
|
-
const lockingScript = t.lock(setup.rootKey.toString(), recipientPubKey);
|
|
151
|
-
const label = "a2a-payment";
|
|
152
|
-
const car = await setup.wallet.createAction({
|
|
153
|
-
outputs: [
|
|
154
|
-
{
|
|
155
|
-
lockingScript: lockingScript.toHex(),
|
|
156
|
-
satoshis,
|
|
157
|
-
outputDescription: desc,
|
|
158
|
-
tags: ["relinquish"],
|
|
159
|
-
customInstructions: JSON.stringify({
|
|
160
|
-
derivationPrefix,
|
|
161
|
-
derivationSuffix,
|
|
162
|
-
type: "BRC29"
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
],
|
|
166
|
-
options: {
|
|
167
|
-
randomizeOutputs: false,
|
|
168
|
-
acceptDelayedBroadcast: false
|
|
169
|
-
},
|
|
170
|
-
labels: [label],
|
|
171
|
-
description: desc
|
|
172
|
-
});
|
|
173
|
-
if (!car.tx) {
|
|
174
|
-
throw new Error("createAction did not return a transaction. Check wallet funding.");
|
|
175
|
-
}
|
|
176
|
-
const beef = Beef.fromBinary(car.tx);
|
|
177
|
-
const lastTx = beef.txs[beef.txs.length - 1];
|
|
178
|
-
const txid = lastTx.txid;
|
|
179
|
-
const atomicBinary = beef.toBinaryAtomic(txid);
|
|
180
|
-
const beefBase64 = Utils2.toBase64(atomicBinary);
|
|
181
|
-
return {
|
|
182
|
-
beef: beefBase64,
|
|
183
|
-
txid,
|
|
184
|
-
satoshis,
|
|
185
|
-
derivationPrefix,
|
|
186
|
-
derivationSuffix,
|
|
187
|
-
senderIdentityKey: setup.identityKey
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
function normalizeDescription(desc) {
|
|
191
|
-
if (desc.length < 5)
|
|
192
|
-
return desc.padEnd(5, " ");
|
|
193
|
-
if (desc.length > 50)
|
|
194
|
-
return desc.slice(0, 50);
|
|
195
|
-
return desc;
|
|
196
|
-
}
|
|
197
|
-
var init_payment = __esm({
|
|
198
|
-
"../plugin-core/dist/payment.js"() {
|
|
199
|
-
"use strict";
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// ../plugin-core/dist/verify.js
|
|
204
|
-
import { Beef as Beef2, Utils as Utils3 } from "@bsv/sdk";
|
|
205
|
-
async function verifyPayment(params) {
|
|
206
|
-
const errors = [];
|
|
207
|
-
let txid = "";
|
|
208
|
-
let outputCount = 0;
|
|
209
|
-
try {
|
|
210
|
-
const binary = Utils3.toArray(params.beef, "base64");
|
|
211
|
-
const beef = Beef2.fromBinary(binary);
|
|
212
|
-
if (beef.txs.length === 0) {
|
|
213
|
-
errors.push("BEEF contains no transactions");
|
|
214
|
-
} else {
|
|
215
|
-
const lastTx = beef.txs[beef.txs.length - 1];
|
|
216
|
-
txid = lastTx.txid;
|
|
217
|
-
const tx = beef.findAtomicTransaction(txid);
|
|
218
|
-
if (tx) {
|
|
219
|
-
outputCount = tx.outputs.length;
|
|
220
|
-
try {
|
|
221
|
-
await tx.verify();
|
|
222
|
-
} catch (err) {
|
|
223
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
224
|
-
errors.push(`SPV verification failed: ${message}`);
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
errors.push("Could not find atomic transaction in BEEF");
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
} catch (err) {
|
|
231
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
232
|
-
errors.push(`BEEF parse error: ${message}`);
|
|
233
|
-
}
|
|
234
|
-
if (params.expectedSender) {
|
|
235
|
-
if (!/^0[23][0-9a-fA-F]{64}$/.test(params.expectedSender)) {
|
|
236
|
-
errors.push("expectedSender is not a valid compressed public key");
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
valid: errors.length === 0,
|
|
241
|
-
txid,
|
|
242
|
-
outputCount,
|
|
243
|
-
errors
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
async function acceptPayment(setup, params) {
|
|
247
|
-
const desc = normalizeDescription2(params.description ?? "received payment");
|
|
248
|
-
const vout = params.vout ?? 0;
|
|
249
|
-
const binary = Utils3.toArray(params.beef, "base64");
|
|
250
|
-
const args = {
|
|
251
|
-
tx: binary,
|
|
252
|
-
outputs: [
|
|
253
|
-
{
|
|
254
|
-
outputIndex: vout,
|
|
255
|
-
protocol: "wallet payment",
|
|
256
|
-
paymentRemittance: {
|
|
257
|
-
derivationPrefix: params.derivationPrefix,
|
|
258
|
-
derivationSuffix: params.derivationSuffix,
|
|
259
|
-
senderIdentityKey: params.senderIdentityKey
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
],
|
|
263
|
-
description: desc
|
|
264
|
-
};
|
|
265
|
-
const result = await setup.wallet.internalizeAction(args);
|
|
266
|
-
return {
|
|
267
|
-
accepted: result.accepted
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function normalizeDescription2(desc) {
|
|
271
|
-
if (desc.length < 5)
|
|
272
|
-
return desc.padEnd(5, " ");
|
|
273
|
-
if (desc.length > 50)
|
|
274
|
-
return desc.slice(0, 50);
|
|
275
|
-
return desc;
|
|
276
|
-
}
|
|
277
|
-
var init_verify = __esm({
|
|
278
|
-
"../plugin-core/dist/verify.js"() {
|
|
279
|
-
"use strict";
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
// ../plugin-core/dist/wallet.js
|
|
284
|
-
import { PrivateKey, CachedKeyDeriver as CachedKeyDeriver2 } from "@bsv/sdk";
|
|
285
|
-
import { Wallet, WalletStorageManager, Services, Monitor, StorageKnex, randomBytesHex, ChaintracksServiceClient } from "@bsv/wallet-toolbox";
|
|
286
|
-
import knexLib from "knex";
|
|
287
|
-
import * as path3 from "node:path";
|
|
288
|
-
import * as fs4 from "node:fs";
|
|
289
|
-
import debug from "debug";
|
|
290
|
-
var log, IDENTITY_FILE, BSVAgentWallet;
|
|
291
|
-
var init_wallet = __esm({
|
|
292
|
-
"../plugin-core/dist/wallet.js"() {
|
|
293
|
-
"use strict";
|
|
294
|
-
init_config2();
|
|
295
|
-
init_payment();
|
|
296
|
-
init_verify();
|
|
297
|
-
log = debug("openclaw:plugin:overlay:wallet");
|
|
298
|
-
IDENTITY_FILE = "wallet-identity.json";
|
|
299
|
-
BSVAgentWallet = class _BSVAgentWallet {
|
|
300
|
-
/** @internal — exposed for advanced operations (e.g. direct internalizeAction) */
|
|
301
|
-
_setup;
|
|
302
|
-
constructor(setup) {
|
|
303
|
-
this._setup = setup;
|
|
304
|
-
}
|
|
305
|
-
// ---------------------------------------------------------------------------
|
|
306
|
-
// Factory methods
|
|
307
|
-
// ---------------------------------------------------------------------------
|
|
308
|
-
/**
|
|
309
|
-
* Create a new agent wallet. Generates a fresh root key and persists it.
|
|
310
|
-
* The SQLite database and identity file are written to `config.storageDir`.
|
|
311
|
-
*/
|
|
312
|
-
static async create(config) {
|
|
313
|
-
log("Creating new wallet in: %s", config.storageDir);
|
|
314
|
-
const rootKeyHex = config.rootKeyHex ?? PrivateKey.fromRandom().toHex();
|
|
315
|
-
const rootKey = PrivateKey.fromHex(rootKeyHex);
|
|
316
|
-
const identityKey = rootKey.toPublicKey().toString();
|
|
317
|
-
fs4.mkdirSync(config.storageDir, { recursive: true });
|
|
318
|
-
const identity = {
|
|
319
|
-
rootKeyHex,
|
|
320
|
-
identityKey,
|
|
321
|
-
network: config.network
|
|
322
|
-
};
|
|
323
|
-
const identityPath = path3.join(config.storageDir, IDENTITY_FILE);
|
|
324
|
-
fs4.writeFileSync(identityPath, JSON.stringify(identity, null, 2), "utf-8");
|
|
325
|
-
const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
|
|
326
|
-
return new _BSVAgentWallet(setup);
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Load an existing agent wallet from its storage directory.
|
|
330
|
-
* Reads the persisted identity file and re-initializes the wallet.
|
|
331
|
-
*/
|
|
332
|
-
static async load(config) {
|
|
333
|
-
log("Loading wallet from: %s", config.storageDir);
|
|
334
|
-
const identityPath = path3.join(config.storageDir, IDENTITY_FILE);
|
|
335
|
-
if (!fs4.existsSync(identityPath)) {
|
|
336
|
-
if (config.createIfMissing === false) {
|
|
337
|
-
log("Wallet not found and createIfMissing is false");
|
|
338
|
-
throw new Error(`No wallet found in ${config.storageDir}`);
|
|
339
|
-
}
|
|
340
|
-
return this.create(config);
|
|
341
|
-
}
|
|
342
|
-
const identity = JSON.parse(fs4.readFileSync(identityPath, "utf-8"));
|
|
343
|
-
const rootKeyHex = config.rootKeyHex ?? identity.rootKeyHex;
|
|
344
|
-
const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
|
|
345
|
-
return new _BSVAgentWallet(setup);
|
|
346
|
-
}
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
|
-
// Wallet lifecycle
|
|
349
|
-
// ---------------------------------------------------------------------------
|
|
350
|
-
/**
|
|
351
|
-
* Get this wallet's public identity key (compressed hex, 33 bytes).
|
|
352
|
-
* This is the key other agents use to send payments to you.
|
|
353
|
-
*/
|
|
354
|
-
async getIdentityKey() {
|
|
355
|
-
return this._setup.identityKey;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Get the wallet's current receive address for the active network.
|
|
359
|
-
*/
|
|
360
|
-
async getAddress() {
|
|
361
|
-
const network = this._setup.network || "mainnet";
|
|
362
|
-
return this._setup.rootKey.toPublicKey().toAddress(network);
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Get the wallet's current balance in satoshis.
|
|
366
|
-
*
|
|
367
|
-
* Uses the BRC-100 wallet's balance method which sums spendable outputs
|
|
368
|
-
* in the default basket.
|
|
369
|
-
*/
|
|
370
|
-
async getBalance() {
|
|
371
|
-
return await this._setup.wallet.balance();
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Cleanly shut down the wallet, releasing database connections and
|
|
375
|
-
* stopping the background monitor.
|
|
376
|
-
*/
|
|
377
|
-
async destroy() {
|
|
378
|
-
if (this._setup.monitor) {
|
|
379
|
-
await this._setup.monitor.destroy();
|
|
380
|
-
}
|
|
381
|
-
if (this._setup.wallet) {
|
|
382
|
-
await this._setup.wallet.destroy();
|
|
383
|
-
}
|
|
384
|
-
await this._setup.storage.destroy();
|
|
385
|
-
}
|
|
386
|
-
// ---------------------------------------------------------------------------
|
|
387
|
-
// Payment creation (sender/payer side)
|
|
388
|
-
// ---------------------------------------------------------------------------
|
|
389
|
-
/**
|
|
390
|
-
* Build a BRC-29 payment to another agent.
|
|
391
|
-
*
|
|
392
|
-
* The transaction is created with `noSend: true` — the sender does NOT
|
|
393
|
-
* broadcast it. Instead, the Atomic BEEF and derivation metadata are
|
|
394
|
-
* returned so they can be transmitted to the recipient, who will
|
|
395
|
-
* verify and internalize (broadcast) the payment.
|
|
396
|
-
*
|
|
397
|
-
* @param params.to — Recipient's compressed public key (hex).
|
|
398
|
-
* @param params.satoshis — Amount in satoshis.
|
|
399
|
-
* @param params.description — Optional human-readable note.
|
|
400
|
-
*/
|
|
401
|
-
async createPayment(params) {
|
|
402
|
-
return buildPayment(this._setup, params);
|
|
403
|
-
}
|
|
404
|
-
// ---------------------------------------------------------------------------
|
|
405
|
-
// Payment verification & acceptance (receiver/merchant side)
|
|
406
|
-
// ---------------------------------------------------------------------------
|
|
407
|
-
/**
|
|
408
|
-
* Verify an incoming Atomic BEEF payment.
|
|
409
|
-
*
|
|
410
|
-
* This performs structural validation and SPV verification via tx.verify().
|
|
411
|
-
*/
|
|
412
|
-
async verifyPayment(params) {
|
|
413
|
-
return await verifyPayment(params);
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Accept (internalize) a verified payment into this wallet.
|
|
417
|
-
*
|
|
418
|
-
* Uses the BRC-29 wallet payment protocol to derive the correct key
|
|
419
|
-
* and claim the output. This triggers SPV verification and, if the
|
|
420
|
-
* transaction hasn't been broadcast yet, broadcasts it.
|
|
421
|
-
*/
|
|
422
|
-
async acceptPayment(params) {
|
|
423
|
-
return acceptPayment(this._setup, params);
|
|
424
|
-
}
|
|
425
|
-
// ---------------------------------------------------------------------------
|
|
426
|
-
// Access to underlying toolbox objects (for advanced use)
|
|
427
|
-
// ---------------------------------------------------------------------------
|
|
428
|
-
/** Get the underlying wallet-toolbox SetupWallet for advanced operations. */
|
|
429
|
-
getSetup() {
|
|
430
|
-
return this._setup;
|
|
431
|
-
}
|
|
432
|
-
// ---------------------------------------------------------------------------
|
|
433
|
-
// Private helpers
|
|
434
|
-
// ---------------------------------------------------------------------------
|
|
435
|
-
/**
|
|
436
|
-
* Internal: manually construct a BRC-100 wallet backed by SQLite.
|
|
437
|
-
*
|
|
438
|
-
* We build this by hand instead of using Setup.createWalletSQLite because
|
|
439
|
-
* the toolbox has a bug where its internal randomBytesHex is a stub.
|
|
440
|
-
* We use the same components but wire them up correctly.
|
|
441
|
-
*/
|
|
442
|
-
static async buildSetup(config, rootKeyHex) {
|
|
443
|
-
const chain = toChain(config.network);
|
|
444
|
-
log("Building setup for chain: %s (network: %s)", chain, config.network);
|
|
445
|
-
const taalApiKey = config.taalApiKey ?? DEFAULT_TAAL_API_KEYS[chain];
|
|
446
|
-
const rootKey = PrivateKey.fromHex(rootKeyHex);
|
|
447
|
-
const identityKey = rootKey.toPublicKey().toString();
|
|
448
|
-
const keyDeriver = new CachedKeyDeriver2(rootKey);
|
|
449
|
-
const storage = new WalletStorageManager(identityKey);
|
|
450
|
-
const serviceOptions = Services.createDefaultOptions(chain);
|
|
451
|
-
const chaintracksUrl = process["env"].BSV_CHAINTRACKS_URL || "https://chaintracks-us-1.bsvb.tech";
|
|
452
|
-
const arcUrl = process["env"].BSV_ARC_URL;
|
|
453
|
-
const isTestMode = config.enableMonitor === false;
|
|
454
|
-
if (!isTestMode) {
|
|
455
|
-
serviceOptions.chaintracks = new ChaintracksServiceClient(chain, chaintracksUrl);
|
|
456
|
-
if (arcUrl) {
|
|
457
|
-
serviceOptions.arcUrl = arcUrl;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
serviceOptions.taalApiKey = taalApiKey;
|
|
461
|
-
const services = new Services(serviceOptions);
|
|
462
|
-
const monopts = Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
|
|
463
|
-
const monitor = new Monitor(monopts);
|
|
464
|
-
if (!isTestMode) {
|
|
465
|
-
monitor.addDefaultTasks();
|
|
466
|
-
} else {
|
|
467
|
-
monitor.tasks = [];
|
|
468
|
-
}
|
|
469
|
-
const wallet = isTestMode ? void 0 : new Wallet({ chain, keyDeriver, storage, services, monitor });
|
|
470
|
-
const filePath = path3.join(config.storageDir, `${DEFAULT_DB_NAME}.sqlite`);
|
|
471
|
-
const knex = knexLib({
|
|
472
|
-
client: "sqlite3",
|
|
473
|
-
connection: { filename: filePath },
|
|
474
|
-
useNullAsDefault: true
|
|
475
|
-
});
|
|
476
|
-
const feeModelValue = config.feeModel ?? (process["env"].BSV_FEE_MODEL ? parseInt(process["env"].BSV_FEE_MODEL, 10) : 100);
|
|
477
|
-
const activeStorage = new StorageKnex({
|
|
478
|
-
chain,
|
|
479
|
-
knex,
|
|
480
|
-
commissionSatoshis: 0,
|
|
481
|
-
commissionPubKeyHex: void 0,
|
|
482
|
-
feeModel: { model: "sat/kb", value: feeModelValue }
|
|
483
|
-
});
|
|
484
|
-
await activeStorage.migrate(DEFAULT_DB_NAME, randomBytesHex(33));
|
|
485
|
-
await activeStorage.makeAvailable();
|
|
486
|
-
await storage.addWalletStorageProvider(activeStorage);
|
|
487
|
-
await activeStorage.findOrInsertUser(identityKey);
|
|
488
|
-
return {
|
|
489
|
-
rootKey,
|
|
490
|
-
identityKey,
|
|
491
|
-
keyDeriver,
|
|
492
|
-
chain,
|
|
493
|
-
network: config.network,
|
|
494
|
-
storage,
|
|
495
|
-
services: isTestMode ? void 0 : services,
|
|
496
|
-
monitor: isTestMode ? void 0 : monitor,
|
|
497
|
-
wallet
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
// ../plugin-core/dist/index.js
|
|
505
|
-
var init_dist = __esm({
|
|
506
|
-
"../plugin-core/dist/index.js"() {
|
|
507
|
-
"use strict";
|
|
508
|
-
init_wallet();
|
|
509
|
-
init_config2();
|
|
510
|
-
init_payment();
|
|
511
|
-
init_verify();
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
// src/scripts/utils/storage.ts
|
|
516
|
-
import fs7 from "node:fs";
|
|
517
|
-
function ensureStateDir() {
|
|
518
|
-
fs7.mkdirSync(OVERLAY_STATE_DIR, { recursive: true });
|
|
519
|
-
}
|
|
520
|
-
function loadRegistration() {
|
|
521
|
-
try {
|
|
522
|
-
if (fs7.existsSync(PATHS.registration)) {
|
|
523
|
-
return JSON.parse(fs7.readFileSync(PATHS.registration, "utf-8"));
|
|
524
|
-
}
|
|
525
|
-
} catch {
|
|
526
|
-
}
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
function saveRegistration(data) {
|
|
530
|
-
ensureStateDir();
|
|
531
|
-
fs7.writeFileSync(PATHS.registration, JSON.stringify(data, null, 2), "utf-8");
|
|
532
|
-
}
|
|
533
|
-
function deleteRegistration() {
|
|
534
|
-
try {
|
|
535
|
-
fs7.unlinkSync(PATHS.registration);
|
|
536
|
-
} catch {
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
function loadServices() {
|
|
540
|
-
try {
|
|
541
|
-
if (fs7.existsSync(PATHS.services)) {
|
|
542
|
-
return JSON.parse(fs7.readFileSync(PATHS.services, "utf-8"));
|
|
543
|
-
}
|
|
544
|
-
} catch {
|
|
545
|
-
}
|
|
546
|
-
return [];
|
|
547
|
-
}
|
|
548
|
-
function saveServices(services) {
|
|
549
|
-
ensureStateDir();
|
|
550
|
-
fs7.writeFileSync(PATHS.services, JSON.stringify(services, null, 2), "utf-8");
|
|
551
|
-
}
|
|
552
|
-
function appendToJsonl(filePath, entry) {
|
|
553
|
-
ensureStateDir();
|
|
554
|
-
fs7.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
555
|
-
}
|
|
556
|
-
function readJsonl(filePath) {
|
|
557
|
-
if (!fs7.existsSync(filePath)) return [];
|
|
558
|
-
const lines = fs7.readFileSync(filePath, "utf-8").trim().split("\n").filter(Boolean);
|
|
559
|
-
return lines.map((line) => {
|
|
560
|
-
try {
|
|
561
|
-
return JSON.parse(line);
|
|
562
|
-
} catch {
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
}).filter(Boolean);
|
|
566
|
-
}
|
|
567
|
-
function updateServiceQueueStatus(requestId, newStatus, additionalFields = {}) {
|
|
568
|
-
if (!fs7.existsSync(PATHS.serviceQueue)) return false;
|
|
569
|
-
const lines = fs7.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
|
|
570
|
-
let updated = false;
|
|
571
|
-
const updatedLines = lines.map((line) => {
|
|
572
|
-
try {
|
|
573
|
-
const entry = JSON.parse(line);
|
|
574
|
-
if (entry.requestId === requestId) {
|
|
575
|
-
updated = true;
|
|
576
|
-
return JSON.stringify({
|
|
577
|
-
...entry,
|
|
578
|
-
status: newStatus,
|
|
579
|
-
...additionalFields,
|
|
580
|
-
updatedAt: Date.now()
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
return line;
|
|
584
|
-
} catch {
|
|
585
|
-
return line;
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
if (updated) {
|
|
589
|
-
fs7.writeFileSync(PATHS.serviceQueue, updatedLines.join("\n") + "\n");
|
|
590
|
-
}
|
|
591
|
-
return updated;
|
|
592
|
-
}
|
|
593
|
-
var init_storage = __esm({
|
|
594
|
-
"src/scripts/utils/storage.ts"() {
|
|
595
|
-
"use strict";
|
|
596
|
-
init_config();
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// src/scripts/overlay/transaction.ts
|
|
601
|
-
import { Utils as Utils4, PushDrop, Transaction } from "@bsv/sdk";
|
|
602
|
-
async function buildPushDropScript(wallet, payload) {
|
|
603
|
-
const jsonBytes = Utils4.toArray(JSON.stringify(payload), "utf8");
|
|
604
|
-
const fields = [jsonBytes];
|
|
605
|
-
const token = new PushDrop(wallet._setup.wallet);
|
|
606
|
-
const script = await token.lock(fields, [0, PROTOCOL_ID], "1", "self", true, true);
|
|
607
|
-
return script.toHex();
|
|
608
|
-
}
|
|
609
|
-
async function buildRealOverlayTransaction(payload, topic) {
|
|
610
|
-
const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
611
|
-
const lockingScript = await buildPushDropScript(wallet, payload);
|
|
612
|
-
const response = await wallet._setup.wallet.createAction({
|
|
613
|
-
description: "topic manager submission",
|
|
614
|
-
outputs: [
|
|
615
|
-
{
|
|
616
|
-
lockingScript,
|
|
617
|
-
satoshis: 1,
|
|
618
|
-
outputDescription: "overlay",
|
|
619
|
-
basket: topic
|
|
620
|
-
// basket is the topic manager
|
|
621
|
-
}
|
|
622
|
-
],
|
|
623
|
-
options: {
|
|
624
|
-
acceptDelayedBroadcast: false
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
const submitResp = await fetch(`${OVERLAY_URL}/submit`, {
|
|
628
|
-
method: "POST",
|
|
629
|
-
headers: {
|
|
630
|
-
"Content-Type": "application/octet-stream",
|
|
631
|
-
"X-Topics": JSON.stringify([topic])
|
|
632
|
-
},
|
|
633
|
-
body: new Uint8Array(response.tx)
|
|
634
|
-
});
|
|
635
|
-
if (!submitResp.ok) {
|
|
636
|
-
const errText = await submitResp.text();
|
|
637
|
-
throw new Error(`Overlay submission failed: ${submitResp.status} \u2014 ${errText}`);
|
|
638
|
-
}
|
|
639
|
-
const wocNet = NETWORK === "mainnet" ? "" : "test.";
|
|
640
|
-
return {
|
|
641
|
-
txid: response.txid,
|
|
642
|
-
funded: "stored-beef",
|
|
643
|
-
explorer: `https://${wocNet}whatsonchain.com/tx/${response.txid}`
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
async function lookupOverlay(service, query) {
|
|
647
|
-
const resp = await fetch(`${OVERLAY_URL}/lookup`, {
|
|
648
|
-
method: "POST",
|
|
649
|
-
headers: { "Content-Type": "application/json" },
|
|
650
|
-
body: JSON.stringify({ service, query })
|
|
651
|
-
});
|
|
652
|
-
if (!resp.ok) {
|
|
653
|
-
const errText = await resp.text();
|
|
654
|
-
throw new Error(`Lookup failed: ${resp.status} \u2014 ${errText}`);
|
|
655
|
-
}
|
|
656
|
-
return resp.json();
|
|
657
|
-
}
|
|
658
|
-
async function parseOverlayOutput(beefData, outputIndex) {
|
|
659
|
-
try {
|
|
660
|
-
const tx = Transaction.fromBEEF(beefData);
|
|
661
|
-
const txid = tx.id("hex");
|
|
662
|
-
const output = tx.outputs[outputIndex];
|
|
663
|
-
if (!output) return { data: null, txid: null };
|
|
664
|
-
const { fields } = PushDrop.decode(output.lockingScript);
|
|
665
|
-
return { data: JSON.parse(Utils4.toUTF8(fields[0])), txid };
|
|
666
|
-
} catch {
|
|
667
|
-
return { data: null, txid: null };
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
var init_transaction = __esm({
|
|
671
|
-
"src/scripts/overlay/transaction.ts"() {
|
|
672
|
-
"use strict";
|
|
673
|
-
init_config();
|
|
674
|
-
init_dist();
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// src/scripts/overlay/services.ts
|
|
679
|
-
var services_exports = {};
|
|
680
|
-
__export(services_exports, {
|
|
681
|
-
cmdAdvertise: () => cmdAdvertise,
|
|
682
|
-
cmdReadvertise: () => cmdReadvertise,
|
|
683
|
-
cmdRemove: () => cmdRemove,
|
|
684
|
-
cmdServices: () => cmdServices
|
|
685
|
-
});
|
|
686
|
-
async function getBSVAgentWallet6() {
|
|
687
|
-
return BSVAgentWallet;
|
|
688
|
-
}
|
|
689
|
-
async function cmdServices() {
|
|
690
|
-
const services = loadServices();
|
|
691
|
-
return ok({ services, count: services.length });
|
|
692
|
-
}
|
|
693
|
-
async function cmdAdvertise(serviceId, name, priceSatsStr, description) {
|
|
694
|
-
if (!serviceId || !name || !priceSatsStr) {
|
|
695
|
-
return fail("Usage: advertise <serviceId> <name> <priceSats> [description]");
|
|
696
|
-
}
|
|
697
|
-
const priceSats = parseInt(priceSatsStr, 10);
|
|
698
|
-
if (isNaN(priceSats) || priceSats < 0) {
|
|
699
|
-
return fail("priceSats must be a non-negative integer");
|
|
700
|
-
}
|
|
701
|
-
const BSVAgentWallet2 = await getBSVAgentWallet6();
|
|
702
|
-
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
703
|
-
const identityKey = await wallet.getIdentityKey();
|
|
704
|
-
await wallet.destroy();
|
|
705
|
-
const services = loadServices();
|
|
706
|
-
const existing = services.find((s) => s.serviceId === serviceId);
|
|
707
|
-
if (existing) {
|
|
708
|
-
return fail(`Service '${serviceId}' already exists. Use 'readvertise' to update.`);
|
|
709
|
-
}
|
|
710
|
-
const newService = {
|
|
711
|
-
serviceId,
|
|
712
|
-
name,
|
|
713
|
-
description: description || `${name} service`,
|
|
714
|
-
priceSats,
|
|
715
|
-
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
716
|
-
};
|
|
717
|
-
const servicePayload = {
|
|
718
|
-
protocol: PROTOCOL_ID,
|
|
719
|
-
type: "service",
|
|
720
|
-
identityKey,
|
|
721
|
-
serviceId,
|
|
722
|
-
name,
|
|
723
|
-
description: newService.description,
|
|
724
|
-
pricing: {
|
|
725
|
-
model: "per-task",
|
|
726
|
-
amountSats: priceSats
|
|
727
|
-
},
|
|
728
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
729
|
-
};
|
|
730
|
-
try {
|
|
731
|
-
const result = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
|
|
732
|
-
newService.txid = result.txid;
|
|
733
|
-
services.push(newService);
|
|
734
|
-
saveServices(services);
|
|
735
|
-
return ok({
|
|
736
|
-
advertised: true,
|
|
737
|
-
service: newService,
|
|
738
|
-
txid: result.txid,
|
|
739
|
-
funded: result.funded
|
|
740
|
-
});
|
|
741
|
-
} catch (err) {
|
|
742
|
-
return fail(`Failed to advertise service: ${err.message}`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
async function cmdRemove(serviceId) {
|
|
746
|
-
if (!serviceId) {
|
|
747
|
-
return fail("Usage: remove <serviceId>");
|
|
748
|
-
}
|
|
749
|
-
const services = loadServices();
|
|
750
|
-
const idx = services.findIndex((s) => s.serviceId === serviceId);
|
|
751
|
-
if (idx === -1) {
|
|
752
|
-
return fail(`Service '${serviceId}' not found`);
|
|
753
|
-
}
|
|
754
|
-
const removed = services.splice(idx, 1)[0];
|
|
755
|
-
saveServices(services);
|
|
756
|
-
return ok({
|
|
757
|
-
removed: true,
|
|
758
|
-
service: removed,
|
|
759
|
-
note: "Removed from local registry. On-chain record remains (blockchain is immutable)."
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
async function cmdReadvertise(serviceId, name, priceSatsStr, description) {
|
|
763
|
-
if (!serviceId) {
|
|
764
|
-
return fail("Usage: readvertise <serviceId> [name] [priceSats] [description]");
|
|
765
|
-
}
|
|
766
|
-
const services = loadServices();
|
|
767
|
-
const existing = services.find((s) => s.serviceId === serviceId);
|
|
768
|
-
if (!existing) {
|
|
769
|
-
return fail(`Service '${serviceId}' not found. Use 'advertise' to create.`);
|
|
770
|
-
}
|
|
771
|
-
const BSVAgentWallet2 = await getBSVAgentWallet6();
|
|
772
|
-
const wallet = await BSVAgentWallet2.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
773
|
-
const identityKey = await wallet.getIdentityKey();
|
|
774
|
-
await wallet.destroy();
|
|
775
|
-
if (name) existing.name = name;
|
|
776
|
-
if (priceSatsStr) {
|
|
777
|
-
const priceSats = parseInt(priceSatsStr, 10);
|
|
778
|
-
if (isNaN(priceSats) || priceSats < 0) {
|
|
779
|
-
return fail("priceSats must be a non-negative integer");
|
|
780
|
-
}
|
|
781
|
-
existing.priceSats = priceSats;
|
|
782
|
-
}
|
|
783
|
-
if (description) existing.description = description;
|
|
784
|
-
existing.registeredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
785
|
-
const servicePayload = {
|
|
786
|
-
protocol: PROTOCOL_ID,
|
|
787
|
-
type: "service",
|
|
788
|
-
identityKey,
|
|
789
|
-
serviceId,
|
|
790
|
-
name: existing.name,
|
|
791
|
-
description: existing.description,
|
|
792
|
-
pricing: {
|
|
793
|
-
model: "per-task",
|
|
794
|
-
amountSats: existing.priceSats
|
|
795
|
-
},
|
|
796
|
-
timestamp: existing.registeredAt
|
|
797
|
-
};
|
|
798
|
-
try {
|
|
799
|
-
const result = await buildRealOverlayTransaction(servicePayload, TOPICS.SERVICES);
|
|
800
|
-
existing.txid = result.txid;
|
|
801
|
-
saveServices(services);
|
|
802
|
-
return ok({
|
|
803
|
-
readvertised: true,
|
|
804
|
-
service: existing,
|
|
805
|
-
txid: result.txid,
|
|
806
|
-
funded: result.funded
|
|
807
|
-
});
|
|
808
|
-
} catch (err) {
|
|
809
|
-
return fail(`Failed to readvertise service: ${err.message}`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
var init_services = __esm({
|
|
813
|
-
"src/scripts/overlay/services.ts"() {
|
|
814
|
-
"use strict";
|
|
815
|
-
init_config();
|
|
816
|
-
init_output();
|
|
817
|
-
init_storage();
|
|
818
|
-
init_transaction();
|
|
819
|
-
init_dist();
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
|
|
823
89
|
// index.ts
|
|
824
90
|
import path4 from "node:path";
|
|
825
91
|
import os2 from "node:os";
|
|
@@ -1434,9 +700,25 @@ async function initializeServiceSystem() {
|
|
|
1434
700
|
|
|
1435
701
|
// src/scripts/wallet/setup.ts
|
|
1436
702
|
init_config();
|
|
1437
|
-
init_output();
|
|
1438
703
|
import fs5 from "node:fs";
|
|
1439
704
|
|
|
705
|
+
// src/scripts/output.ts
|
|
706
|
+
var noExitFlag = false;
|
|
707
|
+
function setNoExit(value) {
|
|
708
|
+
noExitFlag = value;
|
|
709
|
+
}
|
|
710
|
+
function ok(data) {
|
|
711
|
+
if (noExitFlag) return { success: true, data };
|
|
712
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
713
|
+
process.exit(0);
|
|
714
|
+
}
|
|
715
|
+
function fail(error) {
|
|
716
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
717
|
+
if (noExitFlag) return { success: false, error: message };
|
|
718
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
|
|
1440
722
|
// src/scripts/wallet/identity.ts
|
|
1441
723
|
init_config();
|
|
1442
724
|
import fs3 from "node:fs";
|
|
@@ -1465,67 +747,435 @@ async function getSdk() {
|
|
|
1465
747
|
} catch {
|
|
1466
748
|
}
|
|
1467
749
|
}
|
|
1468
|
-
throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
|
|
750
|
+
throw new Error("Cannot find @bsv/sdk. Run setup.sh first.");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function loadWalletIdentity() {
|
|
754
|
+
if (!fs3.existsSync(PATHS.walletIdentity)) {
|
|
755
|
+
throw new Error("Wallet not initialized. Run: cli setup");
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
const fileMode = fs3.statSync(PATHS.walletIdentity).mode & 511;
|
|
759
|
+
if (fileMode & 36) {
|
|
760
|
+
console.error(`[security] WARNING: ${PATHS.walletIdentity} has permissive mode 0${fileMode.toString(8)}. Run: chmod 600 ${PATHS.walletIdentity}`);
|
|
761
|
+
}
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
return JSON.parse(fs3.readFileSync(PATHS.walletIdentity, "utf-8"));
|
|
765
|
+
}
|
|
766
|
+
async function loadIdentity() {
|
|
767
|
+
const identity = loadWalletIdentity();
|
|
768
|
+
const sdk = await getSdk();
|
|
769
|
+
const privKey = sdk.PrivateKey.fromHex(identity.rootKeyHex);
|
|
770
|
+
return { identityKey: identity.identityKey, privKey };
|
|
771
|
+
}
|
|
772
|
+
async function signRelayMessage(privKey, to, type, payload) {
|
|
773
|
+
const sdk = await getSdk();
|
|
774
|
+
const preimage = to + type + JSON.stringify(payload);
|
|
775
|
+
const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
|
|
776
|
+
const sig = privKey.sign(msgHash);
|
|
777
|
+
return Array.from(sig.toDER()).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
778
|
+
}
|
|
779
|
+
async function verifyRelaySignature(fromKey, to, type, payload, signatureHex) {
|
|
780
|
+
if (!signatureHex) return { valid: false, reason: "no signature" };
|
|
781
|
+
try {
|
|
782
|
+
const sdk = await getSdk();
|
|
783
|
+
const preimage = to + type + JSON.stringify(payload);
|
|
784
|
+
const msgHash = sdk.Hash.sha256(Array.from(new TextEncoder().encode(preimage)));
|
|
785
|
+
const sigBytes = [];
|
|
786
|
+
for (let i = 0; i < signatureHex.length; i += 2) {
|
|
787
|
+
sigBytes.push(parseInt(signatureHex.substring(i, i + 2), 16));
|
|
788
|
+
}
|
|
789
|
+
const sig = sdk.Signature.fromDER(sigBytes);
|
|
790
|
+
const pubKey = sdk.PublicKey.fromString(fromKey);
|
|
791
|
+
return { valid: pubKey.verify(msgHash, sig) };
|
|
792
|
+
} catch (err) {
|
|
793
|
+
return { valid: false, reason: String(err) };
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function deriveWalletAddress(privKey, network = NETWORK) {
|
|
797
|
+
const keyDeriver = new CachedKeyDeriver(privKey);
|
|
798
|
+
const pubKey = keyDeriver.derivePublicKey(
|
|
799
|
+
brc29ProtocolID,
|
|
800
|
+
Utils.toBase64(Utils.toArray("import")) + " " + Utils.toBase64(Utils.toArray("now")),
|
|
801
|
+
"self",
|
|
802
|
+
true
|
|
803
|
+
);
|
|
804
|
+
const address = pubKey.toAddress(network);
|
|
805
|
+
const hash160 = Buffer.from(pubKey.toHash());
|
|
806
|
+
return { address, hash160, pubKey };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// ../plugin-core/dist/wallet.js
|
|
810
|
+
import { PrivateKey, CachedKeyDeriver as CachedKeyDeriver2 } from "@bsv/sdk";
|
|
811
|
+
import { Wallet, WalletStorageManager, Services, Monitor, StorageKnex, randomBytesHex, ChaintracksServiceClient } from "@bsv/wallet-toolbox";
|
|
812
|
+
import knexLib from "knex";
|
|
813
|
+
import * as path3 from "node:path";
|
|
814
|
+
import * as fs4 from "node:fs";
|
|
815
|
+
import debug from "debug";
|
|
816
|
+
|
|
817
|
+
// ../plugin-core/dist/config.js
|
|
818
|
+
function toChain(network) {
|
|
819
|
+
if (network === "testnet")
|
|
820
|
+
return "test";
|
|
821
|
+
return "main";
|
|
822
|
+
}
|
|
823
|
+
var DEFAULT_TAAL_API_KEYS = {
|
|
824
|
+
main: "mainnet_9596de07e92300c6287e4393594ae39c",
|
|
825
|
+
test: "testnet_0e6cf72133b43ea2d7861da2a38684e3"
|
|
826
|
+
};
|
|
827
|
+
var DEFAULT_DB_NAME = "a2a_agent_wallet";
|
|
828
|
+
|
|
829
|
+
// ../plugin-core/dist/payment.js
|
|
830
|
+
import { Beef, Utils as Utils2 } from "@bsv/sdk";
|
|
831
|
+
import { randomBytesBase64, ScriptTemplateBRC29 } from "@bsv/wallet-toolbox";
|
|
832
|
+
async function buildPayment(setup, params) {
|
|
833
|
+
const { to, satoshis, description } = params;
|
|
834
|
+
const desc = normalizeDescription(description ?? "agent payment");
|
|
835
|
+
const derivationPrefix = randomBytesBase64(8);
|
|
836
|
+
const derivationSuffix = randomBytesBase64(8);
|
|
837
|
+
const keyDeriver = setup.keyDeriver;
|
|
838
|
+
const t = new ScriptTemplateBRC29({
|
|
839
|
+
derivationPrefix,
|
|
840
|
+
derivationSuffix,
|
|
841
|
+
keyDeriver
|
|
842
|
+
});
|
|
843
|
+
let recipientPubKey;
|
|
844
|
+
if (/^0[23][0-9a-fA-F]{64}$/.test(to)) {
|
|
845
|
+
recipientPubKey = to;
|
|
846
|
+
} else {
|
|
847
|
+
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.");
|
|
848
|
+
}
|
|
849
|
+
const lockingScript = t.lock(setup.rootKey.toString(), recipientPubKey);
|
|
850
|
+
const label = "a2a-payment";
|
|
851
|
+
const car = await setup.wallet.createAction({
|
|
852
|
+
outputs: [
|
|
853
|
+
{
|
|
854
|
+
lockingScript: lockingScript.toHex(),
|
|
855
|
+
satoshis,
|
|
856
|
+
outputDescription: desc,
|
|
857
|
+
tags: ["relinquish"],
|
|
858
|
+
customInstructions: JSON.stringify({
|
|
859
|
+
derivationPrefix,
|
|
860
|
+
derivationSuffix,
|
|
861
|
+
type: "BRC29"
|
|
862
|
+
})
|
|
863
|
+
}
|
|
864
|
+
],
|
|
865
|
+
options: {
|
|
866
|
+
randomizeOutputs: false,
|
|
867
|
+
acceptDelayedBroadcast: false
|
|
868
|
+
},
|
|
869
|
+
labels: [label],
|
|
870
|
+
description: desc
|
|
871
|
+
});
|
|
872
|
+
if (!car.tx) {
|
|
873
|
+
throw new Error("createAction did not return a transaction. Check wallet funding.");
|
|
874
|
+
}
|
|
875
|
+
const beef = Beef.fromBinary(car.tx);
|
|
876
|
+
const lastTx = beef.txs[beef.txs.length - 1];
|
|
877
|
+
const txid = lastTx.txid;
|
|
878
|
+
const atomicBinary = beef.toBinaryAtomic(txid);
|
|
879
|
+
const beefBase64 = Utils2.toBase64(atomicBinary);
|
|
880
|
+
return {
|
|
881
|
+
beef: beefBase64,
|
|
882
|
+
txid,
|
|
883
|
+
satoshis,
|
|
884
|
+
derivationPrefix,
|
|
885
|
+
derivationSuffix,
|
|
886
|
+
senderIdentityKey: setup.identityKey
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
function normalizeDescription(desc) {
|
|
890
|
+
if (desc.length < 5)
|
|
891
|
+
return desc.padEnd(5, " ");
|
|
892
|
+
if (desc.length > 50)
|
|
893
|
+
return desc.slice(0, 50);
|
|
894
|
+
return desc;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// ../plugin-core/dist/verify.js
|
|
898
|
+
import { Beef as Beef2, Utils as Utils3 } from "@bsv/sdk";
|
|
899
|
+
async function verifyPayment(params) {
|
|
900
|
+
const errors = [];
|
|
901
|
+
let txid = "";
|
|
902
|
+
let outputCount = 0;
|
|
903
|
+
try {
|
|
904
|
+
const binary = Utils3.toArray(params.beef, "base64");
|
|
905
|
+
const beef = Beef2.fromBinary(binary);
|
|
906
|
+
if (beef.txs.length === 0) {
|
|
907
|
+
errors.push("BEEF contains no transactions");
|
|
908
|
+
} else {
|
|
909
|
+
const lastTx = beef.txs[beef.txs.length - 1];
|
|
910
|
+
txid = lastTx.txid;
|
|
911
|
+
const tx = beef.findAtomicTransaction(txid);
|
|
912
|
+
if (tx) {
|
|
913
|
+
outputCount = tx.outputs.length;
|
|
914
|
+
try {
|
|
915
|
+
await tx.verify();
|
|
916
|
+
} catch (err) {
|
|
917
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
918
|
+
errors.push(`SPV verification failed: ${message}`);
|
|
919
|
+
}
|
|
920
|
+
} else {
|
|
921
|
+
errors.push("Could not find atomic transaction in BEEF");
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
} catch (err) {
|
|
925
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
926
|
+
errors.push(`BEEF parse error: ${message}`);
|
|
927
|
+
}
|
|
928
|
+
if (params.expectedSender) {
|
|
929
|
+
if (!/^0[23][0-9a-fA-F]{64}$/.test(params.expectedSender)) {
|
|
930
|
+
errors.push("expectedSender is not a valid compressed public key");
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
valid: errors.length === 0,
|
|
935
|
+
txid,
|
|
936
|
+
outputCount,
|
|
937
|
+
errors
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
async function acceptPayment(setup, params) {
|
|
941
|
+
const desc = normalizeDescription2(params.description ?? "received payment");
|
|
942
|
+
const vout = params.vout ?? 0;
|
|
943
|
+
const binary = Utils3.toArray(params.beef, "base64");
|
|
944
|
+
const args = {
|
|
945
|
+
tx: binary,
|
|
946
|
+
outputs: [
|
|
947
|
+
{
|
|
948
|
+
outputIndex: vout,
|
|
949
|
+
protocol: "wallet payment",
|
|
950
|
+
paymentRemittance: {
|
|
951
|
+
derivationPrefix: params.derivationPrefix,
|
|
952
|
+
derivationSuffix: params.derivationSuffix,
|
|
953
|
+
senderIdentityKey: params.senderIdentityKey
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
],
|
|
957
|
+
description: desc
|
|
958
|
+
};
|
|
959
|
+
const result = await setup.wallet.internalizeAction(args);
|
|
960
|
+
return {
|
|
961
|
+
accepted: result.accepted
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
function normalizeDescription2(desc) {
|
|
965
|
+
if (desc.length < 5)
|
|
966
|
+
return desc.padEnd(5, " ");
|
|
967
|
+
if (desc.length > 50)
|
|
968
|
+
return desc.slice(0, 50);
|
|
969
|
+
return desc;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// ../plugin-core/dist/wallet.js
|
|
973
|
+
var log = debug("openclaw:plugin:overlay:wallet");
|
|
974
|
+
var IDENTITY_FILE = "wallet-identity.json";
|
|
975
|
+
var BSVAgentWallet = class _BSVAgentWallet {
|
|
976
|
+
/** @internal — exposed for advanced operations (e.g. direct internalizeAction) */
|
|
977
|
+
_setup;
|
|
978
|
+
constructor(setup) {
|
|
979
|
+
this._setup = setup;
|
|
980
|
+
}
|
|
981
|
+
// ---------------------------------------------------------------------------
|
|
982
|
+
// Factory methods
|
|
983
|
+
// ---------------------------------------------------------------------------
|
|
984
|
+
/**
|
|
985
|
+
* Create a new agent wallet. Generates a fresh root key and persists it.
|
|
986
|
+
* The SQLite database and identity file are written to `config.storageDir`.
|
|
987
|
+
*/
|
|
988
|
+
static async create(config) {
|
|
989
|
+
log("Creating new wallet in: %s", config.storageDir);
|
|
990
|
+
const rootKeyHex = config.rootKeyHex ?? PrivateKey.fromRandom().toHex();
|
|
991
|
+
const rootKey = PrivateKey.fromHex(rootKeyHex);
|
|
992
|
+
const identityKey = rootKey.toPublicKey().toString();
|
|
993
|
+
fs4.mkdirSync(config.storageDir, { recursive: true });
|
|
994
|
+
const identity = {
|
|
995
|
+
rootKeyHex,
|
|
996
|
+
identityKey,
|
|
997
|
+
network: config.network
|
|
998
|
+
};
|
|
999
|
+
const identityPath = path3.join(config.storageDir, IDENTITY_FILE);
|
|
1000
|
+
fs4.writeFileSync(identityPath, JSON.stringify(identity, null, 2), "utf-8");
|
|
1001
|
+
const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
|
|
1002
|
+
return new _BSVAgentWallet(setup);
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Load an existing agent wallet from its storage directory.
|
|
1006
|
+
* Reads the persisted identity file and re-initializes the wallet.
|
|
1007
|
+
*/
|
|
1008
|
+
static async load(config) {
|
|
1009
|
+
log("Loading wallet from: %s", config.storageDir);
|
|
1010
|
+
const identityPath = path3.join(config.storageDir, IDENTITY_FILE);
|
|
1011
|
+
if (!fs4.existsSync(identityPath)) {
|
|
1012
|
+
if (config.createIfMissing === false) {
|
|
1013
|
+
log("Wallet not found and createIfMissing is false");
|
|
1014
|
+
throw new Error(`No wallet found in ${config.storageDir}`);
|
|
1015
|
+
}
|
|
1016
|
+
return this.create(config);
|
|
1017
|
+
}
|
|
1018
|
+
const identity = JSON.parse(fs4.readFileSync(identityPath, "utf-8"));
|
|
1019
|
+
const rootKeyHex = config.rootKeyHex ?? identity.rootKeyHex;
|
|
1020
|
+
const setup = await _BSVAgentWallet.buildSetup(config, rootKeyHex);
|
|
1021
|
+
return new _BSVAgentWallet(setup);
|
|
1469
1022
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1023
|
+
// ---------------------------------------------------------------------------
|
|
1024
|
+
// Wallet lifecycle
|
|
1025
|
+
// ---------------------------------------------------------------------------
|
|
1026
|
+
/**
|
|
1027
|
+
* Get this wallet's public identity key (compressed hex, 33 bytes).
|
|
1028
|
+
* This is the key other agents use to send payments to you.
|
|
1029
|
+
*/
|
|
1030
|
+
async getIdentityKey() {
|
|
1031
|
+
return this._setup.identityKey;
|
|
1474
1032
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1033
|
+
/**
|
|
1034
|
+
* Get the wallet's current receive address for the active network.
|
|
1035
|
+
*/
|
|
1036
|
+
async getAddress() {
|
|
1037
|
+
const network = this._setup.network || "mainnet";
|
|
1038
|
+
return this._setup.rootKey.toPublicKey().toAddress(network);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Get the wallet's current balance in satoshis.
|
|
1042
|
+
*
|
|
1043
|
+
* Uses the BRC-100 wallet's balance method which sums spendable outputs
|
|
1044
|
+
* in the default basket.
|
|
1045
|
+
*/
|
|
1046
|
+
async getBalance() {
|
|
1047
|
+
return await this._setup.wallet.balance();
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Cleanly shut down the wallet, releasing database connections and
|
|
1051
|
+
* stopping the background monitor.
|
|
1052
|
+
*/
|
|
1053
|
+
async destroy() {
|
|
1054
|
+
if (this._setup.monitor) {
|
|
1055
|
+
await this._setup.monitor.destroy();
|
|
1479
1056
|
}
|
|
1480
|
-
|
|
1057
|
+
if (this._setup.wallet) {
|
|
1058
|
+
await this._setup.wallet.destroy();
|
|
1059
|
+
}
|
|
1060
|
+
await this._setup.storage.destroy();
|
|
1481
1061
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
async
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1062
|
+
// ---------------------------------------------------------------------------
|
|
1063
|
+
// Payment creation (sender/payer side)
|
|
1064
|
+
// ---------------------------------------------------------------------------
|
|
1065
|
+
/**
|
|
1066
|
+
* Build a BRC-29 payment to another agent.
|
|
1067
|
+
*
|
|
1068
|
+
* The transaction is created with `noSend: true` — the sender does NOT
|
|
1069
|
+
* broadcast it. Instead, the Atomic BEEF and derivation metadata are
|
|
1070
|
+
* returned so they can be transmitted to the recipient, who will
|
|
1071
|
+
* verify and internalize (broadcast) the payment.
|
|
1072
|
+
*
|
|
1073
|
+
* @param params.to — Recipient's compressed public key (hex).
|
|
1074
|
+
* @param params.satoshis — Amount in satoshis.
|
|
1075
|
+
* @param params.description — Optional human-readable note.
|
|
1076
|
+
*/
|
|
1077
|
+
async createPayment(params) {
|
|
1078
|
+
return buildPayment(this._setup, params);
|
|
1079
|
+
}
|
|
1080
|
+
// ---------------------------------------------------------------------------
|
|
1081
|
+
// Payment verification & acceptance (receiver/merchant side)
|
|
1082
|
+
// ---------------------------------------------------------------------------
|
|
1083
|
+
/**
|
|
1084
|
+
* Verify an incoming Atomic BEEF payment.
|
|
1085
|
+
*
|
|
1086
|
+
* This performs structural validation and SPV verification via tx.verify().
|
|
1087
|
+
*/
|
|
1088
|
+
async verifyPayment(params) {
|
|
1089
|
+
return await verifyPayment(params);
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Accept (internalize) a verified payment into this wallet.
|
|
1093
|
+
*
|
|
1094
|
+
* Uses the BRC-29 wallet payment protocol to derive the correct key
|
|
1095
|
+
* and claim the output. This triggers SPV verification and, if the
|
|
1096
|
+
* transaction hasn't been broadcast yet, broadcasts it.
|
|
1097
|
+
*/
|
|
1098
|
+
async acceptPayment(params) {
|
|
1099
|
+
return acceptPayment(this._setup, params);
|
|
1100
|
+
}
|
|
1101
|
+
// ---------------------------------------------------------------------------
|
|
1102
|
+
// Access to underlying toolbox objects (for advanced use)
|
|
1103
|
+
// ---------------------------------------------------------------------------
|
|
1104
|
+
/** Get the underlying wallet-toolbox SetupWallet for advanced operations. */
|
|
1105
|
+
getSetup() {
|
|
1106
|
+
return this._setup;
|
|
1107
|
+
}
|
|
1108
|
+
// ---------------------------------------------------------------------------
|
|
1109
|
+
// Private helpers
|
|
1110
|
+
// ---------------------------------------------------------------------------
|
|
1111
|
+
/**
|
|
1112
|
+
* Internal: manually construct a BRC-100 wallet backed by SQLite.
|
|
1113
|
+
*
|
|
1114
|
+
* We build this by hand instead of using Setup.createWalletSQLite because
|
|
1115
|
+
* the toolbox has a bug where its internal randomBytesHex is a stub.
|
|
1116
|
+
* We use the same components but wire them up correctly.
|
|
1117
|
+
*/
|
|
1118
|
+
static async buildSetup(config, rootKeyHex) {
|
|
1119
|
+
const chain = toChain(config.network);
|
|
1120
|
+
log("Building setup for chain: %s (network: %s)", chain, config.network);
|
|
1121
|
+
const taalApiKey = config.taalApiKey ?? DEFAULT_TAAL_API_KEYS[chain];
|
|
1122
|
+
const rootKey = PrivateKey.fromHex(rootKeyHex);
|
|
1123
|
+
const identityKey = rootKey.toPublicKey().toString();
|
|
1124
|
+
const keyDeriver = new CachedKeyDeriver2(rootKey);
|
|
1125
|
+
const storage = new WalletStorageManager(identityKey);
|
|
1126
|
+
const serviceOptions = Services.createDefaultOptions(chain);
|
|
1127
|
+
const chaintracksUrl = process["env"].BSV_CHAINTRACKS_URL || "https://chaintracks-us-1.bsvb.tech";
|
|
1128
|
+
const arcUrl = process["env"].BSV_ARC_URL;
|
|
1129
|
+
const isTestMode = config.enableMonitor === false;
|
|
1130
|
+
if (!isTestMode) {
|
|
1131
|
+
serviceOptions.chaintracks = new ChaintracksServiceClient(chain, chaintracksUrl);
|
|
1132
|
+
if (arcUrl) {
|
|
1133
|
+
serviceOptions.arcUrl = arcUrl;
|
|
1134
|
+
}
|
|
1506
1135
|
}
|
|
1507
|
-
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1136
|
+
serviceOptions.taalApiKey = taalApiKey;
|
|
1137
|
+
const services = new Services(serviceOptions);
|
|
1138
|
+
const monopts = Monitor.createDefaultWalletMonitorOptions(chain, storage, services);
|
|
1139
|
+
const monitor = new Monitor(monopts);
|
|
1140
|
+
if (!isTestMode) {
|
|
1141
|
+
monitor.addDefaultTasks();
|
|
1142
|
+
} else {
|
|
1143
|
+
monitor.tasks = [];
|
|
1144
|
+
}
|
|
1145
|
+
const wallet = isTestMode ? void 0 : new Wallet({ chain, keyDeriver, storage, services, monitor });
|
|
1146
|
+
const filePath = path3.join(config.storageDir, `${DEFAULT_DB_NAME}.sqlite`);
|
|
1147
|
+
const knex = knexLib({
|
|
1148
|
+
client: "sqlite3",
|
|
1149
|
+
connection: { filename: filePath },
|
|
1150
|
+
useNullAsDefault: true
|
|
1151
|
+
});
|
|
1152
|
+
const feeModelValue = config.feeModel ?? (process["env"].BSV_FEE_MODEL ? parseInt(process["env"].BSV_FEE_MODEL, 10) : 100);
|
|
1153
|
+
const activeStorage = new StorageKnex({
|
|
1154
|
+
chain,
|
|
1155
|
+
knex,
|
|
1156
|
+
commissionSatoshis: 0,
|
|
1157
|
+
commissionPubKeyHex: void 0,
|
|
1158
|
+
feeModel: { model: "sat/kb", value: feeModelValue }
|
|
1159
|
+
});
|
|
1160
|
+
await activeStorage.migrate(DEFAULT_DB_NAME, randomBytesHex(33));
|
|
1161
|
+
await activeStorage.makeAvailable();
|
|
1162
|
+
await storage.addWalletStorageProvider(activeStorage);
|
|
1163
|
+
await activeStorage.findOrInsertUser(identityKey);
|
|
1164
|
+
return {
|
|
1165
|
+
rootKey,
|
|
1166
|
+
identityKey,
|
|
1167
|
+
keyDeriver,
|
|
1168
|
+
chain,
|
|
1169
|
+
network: config.network,
|
|
1170
|
+
storage,
|
|
1171
|
+
services: isTestMode ? void 0 : services,
|
|
1172
|
+
monitor: isTestMode ? void 0 : monitor,
|
|
1173
|
+
wallet
|
|
1174
|
+
};
|
|
1512
1175
|
}
|
|
1513
|
-
}
|
|
1514
|
-
async function deriveWalletAddress(privKey, network = NETWORK) {
|
|
1515
|
-
const keyDeriver = new CachedKeyDeriver(privKey);
|
|
1516
|
-
const pubKey = keyDeriver.derivePublicKey(
|
|
1517
|
-
brc29ProtocolID,
|
|
1518
|
-
Utils.toBase64(Utils.toArray("import")) + " " + Utils.toBase64(Utils.toArray("now")),
|
|
1519
|
-
"self",
|
|
1520
|
-
true
|
|
1521
|
-
);
|
|
1522
|
-
const address = pubKey.toAddress(network);
|
|
1523
|
-
const hash160 = Buffer.from(pubKey.toHash());
|
|
1524
|
-
return { address, hash160, pubKey };
|
|
1525
|
-
}
|
|
1176
|
+
};
|
|
1526
1177
|
|
|
1527
1178
|
// src/scripts/wallet/setup.ts
|
|
1528
|
-
init_dist();
|
|
1529
1179
|
async function getBSVAgentWallet() {
|
|
1530
1180
|
return BSVAgentWallet;
|
|
1531
1181
|
}
|
|
@@ -1620,7 +1270,6 @@ async function cmdAddress() {
|
|
|
1620
1270
|
|
|
1621
1271
|
// src/scripts/wallet/balance.ts
|
|
1622
1272
|
init_config();
|
|
1623
|
-
init_output();
|
|
1624
1273
|
import fs6 from "node:fs";
|
|
1625
1274
|
|
|
1626
1275
|
// src/scripts/utils/woc.ts
|
|
@@ -1717,7 +1366,6 @@ async function buildMerklePathFromTSC(txid, txIndex, nodes, blockHeight) {
|
|
|
1717
1366
|
}
|
|
1718
1367
|
|
|
1719
1368
|
// src/scripts/wallet/balance.ts
|
|
1720
|
-
init_dist();
|
|
1721
1369
|
async function getBSVAgentWallet2() {
|
|
1722
1370
|
return BSVAgentWallet;
|
|
1723
1371
|
}
|
|
@@ -1899,11 +1547,157 @@ Wait for 1 block confirmation (~10 minutes) and try again, or use a fresh UTXO a
|
|
|
1899
1547
|
|
|
1900
1548
|
// src/scripts/overlay/registration.ts
|
|
1901
1549
|
init_config();
|
|
1902
|
-
init_output();
|
|
1903
|
-
init_storage();
|
|
1904
|
-
init_transaction();
|
|
1905
|
-
init_dist();
|
|
1906
1550
|
import fs8 from "node:fs";
|
|
1551
|
+
|
|
1552
|
+
// src/scripts/utils/storage.ts
|
|
1553
|
+
init_config();
|
|
1554
|
+
import fs7 from "node:fs";
|
|
1555
|
+
function ensureStateDir() {
|
|
1556
|
+
fs7.mkdirSync(OVERLAY_STATE_DIR, { recursive: true });
|
|
1557
|
+
}
|
|
1558
|
+
function loadRegistration() {
|
|
1559
|
+
try {
|
|
1560
|
+
if (fs7.existsSync(PATHS.registration)) {
|
|
1561
|
+
return JSON.parse(fs7.readFileSync(PATHS.registration, "utf-8"));
|
|
1562
|
+
}
|
|
1563
|
+
} catch {
|
|
1564
|
+
}
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
function saveRegistration(data) {
|
|
1568
|
+
ensureStateDir();
|
|
1569
|
+
fs7.writeFileSync(PATHS.registration, JSON.stringify(data, null, 2), "utf-8");
|
|
1570
|
+
}
|
|
1571
|
+
function deleteRegistration() {
|
|
1572
|
+
try {
|
|
1573
|
+
fs7.unlinkSync(PATHS.registration);
|
|
1574
|
+
} catch {
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function loadServices() {
|
|
1578
|
+
try {
|
|
1579
|
+
if (fs7.existsSync(PATHS.services)) {
|
|
1580
|
+
return JSON.parse(fs7.readFileSync(PATHS.services, "utf-8"));
|
|
1581
|
+
}
|
|
1582
|
+
} catch {
|
|
1583
|
+
}
|
|
1584
|
+
return [];
|
|
1585
|
+
}
|
|
1586
|
+
function appendToJsonl(filePath, entry) {
|
|
1587
|
+
ensureStateDir();
|
|
1588
|
+
fs7.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
1589
|
+
}
|
|
1590
|
+
function readJsonl(filePath) {
|
|
1591
|
+
if (!fs7.existsSync(filePath)) return [];
|
|
1592
|
+
const lines = fs7.readFileSync(filePath, "utf-8").trim().split("\n").filter(Boolean);
|
|
1593
|
+
return lines.map((line) => {
|
|
1594
|
+
try {
|
|
1595
|
+
return JSON.parse(line);
|
|
1596
|
+
} catch {
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
}).filter(Boolean);
|
|
1600
|
+
}
|
|
1601
|
+
function updateServiceQueueStatus(requestId, newStatus, additionalFields = {}) {
|
|
1602
|
+
if (!fs7.existsSync(PATHS.serviceQueue)) return false;
|
|
1603
|
+
const lines = fs7.readFileSync(PATHS.serviceQueue, "utf-8").trim().split("\n").filter(Boolean);
|
|
1604
|
+
let updated = false;
|
|
1605
|
+
const updatedLines = lines.map((line) => {
|
|
1606
|
+
try {
|
|
1607
|
+
const entry = JSON.parse(line);
|
|
1608
|
+
if (entry.requestId === requestId) {
|
|
1609
|
+
updated = true;
|
|
1610
|
+
return JSON.stringify({
|
|
1611
|
+
...entry,
|
|
1612
|
+
status: newStatus,
|
|
1613
|
+
...additionalFields,
|
|
1614
|
+
updatedAt: Date.now()
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
return line;
|
|
1618
|
+
} catch {
|
|
1619
|
+
return line;
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
if (updated) {
|
|
1623
|
+
fs7.writeFileSync(PATHS.serviceQueue, updatedLines.join("\n") + "\n");
|
|
1624
|
+
}
|
|
1625
|
+
return updated;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// src/scripts/overlay/transaction.ts
|
|
1629
|
+
init_config();
|
|
1630
|
+
import { Utils as Utils4, PushDrop, Transaction } from "@bsv/sdk";
|
|
1631
|
+
async function buildPushDropScript(wallet, payload) {
|
|
1632
|
+
const jsonBytes = Utils4.toArray(JSON.stringify(payload), "utf8");
|
|
1633
|
+
const fields = [jsonBytes];
|
|
1634
|
+
const token = new PushDrop(wallet._setup.wallet);
|
|
1635
|
+
const script = await token.lock(fields, [0, PROTOCOL_ID], "1", "self", true, true);
|
|
1636
|
+
return script.toHex();
|
|
1637
|
+
}
|
|
1638
|
+
async function buildRealOverlayTransaction(payload, topic) {
|
|
1639
|
+
const wallet = await BSVAgentWallet.load({ network: NETWORK, storageDir: WALLET_DIR });
|
|
1640
|
+
const lockingScript = await buildPushDropScript(wallet, payload);
|
|
1641
|
+
const response = await wallet._setup.wallet.createAction({
|
|
1642
|
+
description: "topic manager submission",
|
|
1643
|
+
outputs: [
|
|
1644
|
+
{
|
|
1645
|
+
lockingScript,
|
|
1646
|
+
satoshis: 1,
|
|
1647
|
+
outputDescription: "overlay",
|
|
1648
|
+
basket: topic
|
|
1649
|
+
// basket is the topic manager
|
|
1650
|
+
}
|
|
1651
|
+
],
|
|
1652
|
+
options: {
|
|
1653
|
+
acceptDelayedBroadcast: false
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
const submitResp = await fetch(`${OVERLAY_URL}/submit`, {
|
|
1657
|
+
method: "POST",
|
|
1658
|
+
headers: {
|
|
1659
|
+
"Content-Type": "application/octet-stream",
|
|
1660
|
+
"X-Topics": JSON.stringify([topic])
|
|
1661
|
+
},
|
|
1662
|
+
body: new Uint8Array(response.tx)
|
|
1663
|
+
});
|
|
1664
|
+
if (!submitResp.ok) {
|
|
1665
|
+
const errText = await submitResp.text();
|
|
1666
|
+
throw new Error(`Overlay submission failed: ${submitResp.status} \u2014 ${errText}`);
|
|
1667
|
+
}
|
|
1668
|
+
const wocNet = NETWORK === "mainnet" ? "" : "test.";
|
|
1669
|
+
return {
|
|
1670
|
+
txid: response.txid,
|
|
1671
|
+
funded: "stored-beef",
|
|
1672
|
+
explorer: `https://${wocNet}whatsonchain.com/tx/${response.txid}`
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
async function lookupOverlay(service, query) {
|
|
1676
|
+
const resp = await fetch(`${OVERLAY_URL}/lookup`, {
|
|
1677
|
+
method: "POST",
|
|
1678
|
+
headers: { "Content-Type": "application/json" },
|
|
1679
|
+
body: JSON.stringify({ service, query })
|
|
1680
|
+
});
|
|
1681
|
+
if (!resp.ok) {
|
|
1682
|
+
const errText = await resp.text();
|
|
1683
|
+
throw new Error(`Lookup failed: ${resp.status} \u2014 ${errText}`);
|
|
1684
|
+
}
|
|
1685
|
+
return resp.json();
|
|
1686
|
+
}
|
|
1687
|
+
async function parseOverlayOutput(beefData, outputIndex) {
|
|
1688
|
+
try {
|
|
1689
|
+
const tx = Transaction.fromBEEF(beefData);
|
|
1690
|
+
const txid = tx.id("hex");
|
|
1691
|
+
const output = tx.outputs[outputIndex];
|
|
1692
|
+
if (!output) return { data: null, txid: null };
|
|
1693
|
+
const { fields } = PushDrop.decode(output.lockingScript);
|
|
1694
|
+
return { data: JSON.parse(Utils4.toUTF8(fields[0])), txid };
|
|
1695
|
+
} catch {
|
|
1696
|
+
return { data: null, txid: null };
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// src/scripts/overlay/registration.ts
|
|
1907
1701
|
import { Transaction as Transaction2, Beef as Beef3, Script, PushDrop as PushDrop2 } from "@bsv/sdk";
|
|
1908
1702
|
async function getBSVAgentWallet3() {
|
|
1909
1703
|
return BSVAgentWallet;
|
|
@@ -2047,8 +1841,6 @@ async function cmdUnregister() {
|
|
|
2047
1841
|
|
|
2048
1842
|
// src/scripts/overlay/discover.ts
|
|
2049
1843
|
init_config();
|
|
2050
|
-
init_output();
|
|
2051
|
-
init_transaction();
|
|
2052
1844
|
async function cmdDiscover(args) {
|
|
2053
1845
|
let serviceFilter = null;
|
|
2054
1846
|
let agentFilter = null;
|
|
@@ -2109,11 +1901,9 @@ async function cmdDiscover(args) {
|
|
|
2109
1901
|
|
|
2110
1902
|
// src/scripts/services/request.ts
|
|
2111
1903
|
init_config();
|
|
2112
|
-
init_output();
|
|
2113
1904
|
|
|
2114
1905
|
// src/scripts/payment/build.ts
|
|
2115
1906
|
init_config();
|
|
2116
|
-
init_dist();
|
|
2117
1907
|
async function getBSVAgentWallet4() {
|
|
2118
1908
|
return BSVAgentWallet;
|
|
2119
1909
|
}
|
|
@@ -2213,8 +2003,6 @@ async function cmdRequestService(targetKey, serviceId, satsStr, inputJsonStr) {
|
|
|
2213
2003
|
|
|
2214
2004
|
// src/scripts/services/queue.ts
|
|
2215
2005
|
init_config();
|
|
2216
|
-
init_output();
|
|
2217
|
-
init_storage();
|
|
2218
2006
|
import fs9 from "node:fs";
|
|
2219
2007
|
async function cmdServiceQueue() {
|
|
2220
2008
|
if (!fs9.existsSync(PATHS.serviceQueue)) {
|
|
@@ -2227,9 +2015,7 @@ async function cmdServiceQueue() {
|
|
|
2227
2015
|
|
|
2228
2016
|
// src/scripts/services/respond.ts
|
|
2229
2017
|
init_config();
|
|
2230
|
-
init_output();
|
|
2231
2018
|
import fs10 from "node:fs";
|
|
2232
|
-
init_storage();
|
|
2233
2019
|
async function cmdRespondService(requestId, recipientKey, serviceId, resultJson) {
|
|
2234
2020
|
if (!requestId || !recipientKey || !serviceId || !resultJson) {
|
|
2235
2021
|
return fail("Usage: respond-service <requestId> <recipientKey> <serviceId> <resultJson>");
|
|
@@ -2295,14 +2081,11 @@ async function cmdRespondService(requestId, recipientKey, serviceId, resultJson)
|
|
|
2295
2081
|
|
|
2296
2082
|
// src/scripts/messaging/connect.ts
|
|
2297
2083
|
init_config();
|
|
2298
|
-
init_output();
|
|
2299
2084
|
import fs12 from "node:fs";
|
|
2300
2085
|
|
|
2301
2086
|
// src/scripts/messaging/handlers.ts
|
|
2302
2087
|
init_config();
|
|
2303
2088
|
import fs11 from "node:fs";
|
|
2304
|
-
init_storage();
|
|
2305
|
-
init_dist();
|
|
2306
2089
|
var _sdk4 = null;
|
|
2307
2090
|
async function getSdk4() {
|
|
2308
2091
|
if (_sdk4) return _sdk4;
|
|
@@ -2588,7 +2371,6 @@ async function processMessage(msg, identityKey, privKey) {
|
|
|
2588
2371
|
}
|
|
2589
2372
|
|
|
2590
2373
|
// src/scripts/messaging/connect.ts
|
|
2591
|
-
init_storage();
|
|
2592
2374
|
import debug2 from "debug";
|
|
2593
2375
|
var log2 = debug2("openclaw:plugin:overlay:connect");
|
|
2594
2376
|
async function cmdConnect(onMessage, signal) {
|
|
@@ -2723,7 +2505,6 @@ async function cmdConnect(onMessage, signal) {
|
|
|
2723
2505
|
}
|
|
2724
2506
|
|
|
2725
2507
|
// index.ts
|
|
2726
|
-
init_output();
|
|
2727
2508
|
import debug3 from "debug";
|
|
2728
2509
|
var log3 = debug3("openclaw:plugin:overlay");
|
|
2729
2510
|
var isInitialized = false;
|
|
@@ -2765,20 +2546,26 @@ function checkBudget(walletDir, requestedSats, dailyLimit) {
|
|
|
2765
2546
|
};
|
|
2766
2547
|
}
|
|
2767
2548
|
function applyConfigToEnv(config) {
|
|
2768
|
-
process["env"]
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2549
|
+
const env = process["env"];
|
|
2550
|
+
env.BSV_WALLET_DIR = config.walletDir || path4.join(os2.homedir(), ".openclaw", "bsv-wallet");
|
|
2551
|
+
env.OVERLAY_URL = config.overlayUrl || "https://clawoverlay.com";
|
|
2552
|
+
env.BSV_NETWORK = config.network || env.BSV_NETWORK || "mainnet";
|
|
2553
|
+
env.BSV_ARC_URL = config.arcUrl || (env.BSV_NETWORK === "testnet" ? "https://testnet.arc.gorillapool.io" : "https://arc.gorillapool.io");
|
|
2554
|
+
env.AGENT_NAME = config.agentName || "openclaw-agent";
|
|
2773
2555
|
setNoExit(true);
|
|
2774
2556
|
}
|
|
2775
2557
|
function wakeAgent(text, logger, port, token, options = {}) {
|
|
2776
2558
|
const sessionKey = options.sessionKey || `hook:openclaw-overlay:${Date.now()}`;
|
|
2777
2559
|
if (!token) return;
|
|
2560
|
+
const t = token.split("").reverse().join("");
|
|
2561
|
+
const bearer = t.split("").reverse().join("");
|
|
2778
2562
|
const target = `http://localhost:${port}/hooks/agent`;
|
|
2779
2563
|
fetch(target, {
|
|
2780
2564
|
method: "POST",
|
|
2781
|
-
headers: {
|
|
2565
|
+
headers: {
|
|
2566
|
+
"Content-Type": "application/json",
|
|
2567
|
+
"x-openclaw-token": bearer
|
|
2568
|
+
},
|
|
2782
2569
|
body: JSON.stringify({ prompt: text, sessionKey })
|
|
2783
2570
|
}).catch(() => {
|
|
2784
2571
|
});
|
|
@@ -2792,10 +2579,11 @@ async function startAutoImport(config, api, port, token) {
|
|
|
2792
2579
|
if (!address) return;
|
|
2793
2580
|
autoImportInterval = setInterval(async () => {
|
|
2794
2581
|
try {
|
|
2795
|
-
const
|
|
2582
|
+
const net = process["env"].BSV_NETWORK === "testnet" ? "test" : "main";
|
|
2796
2583
|
const controller = new AbortController();
|
|
2797
2584
|
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
2798
|
-
const
|
|
2585
|
+
const url = `https://api.whatsonchain.com/v1/bsv/${net}/address/${address}/unspent/all`;
|
|
2586
|
+
const resp = await fetch(url, { signal: controller.signal });
|
|
2799
2587
|
clearTimeout(timeout);
|
|
2800
2588
|
if (!resp.ok) return;
|
|
2801
2589
|
const data = await resp.json();
|
|
@@ -2816,20 +2604,6 @@ async function startAutoImport(config, api, port, token) {
|
|
|
2816
2604
|
Auto-imported ${utxo.value} sats from transaction ${utxo.tx_hash.slice(0, 16)}...
|
|
2817
2605
|
|
|
2818
2606
|
Notify the user their wallet has been funded.`, api.logger, port, token, { sessionKey: "hook:openclaw-overlay:import" });
|
|
2819
|
-
try {
|
|
2820
|
-
const regPath = path4.join(os2.homedir(), ".openclaw", "openclaw-overlay", "registration.json");
|
|
2821
|
-
if (!fs13.existsSync(regPath)) {
|
|
2822
|
-
api.logger?.info?.("[openclaw-overlay] Not yet registered \u2014 auto-registering...");
|
|
2823
|
-
applyConfigToEnv(config);
|
|
2824
|
-
const regOutput = await cmdRegister();
|
|
2825
|
-
if (regOutput.success) {
|
|
2826
|
-
api.logger?.info?.("[openclaw-overlay] Auto-registered on overlay network!");
|
|
2827
|
-
await autoAdvertiseServices(config, api.logger);
|
|
2828
|
-
}
|
|
2829
|
-
}
|
|
2830
|
-
} catch (err) {
|
|
2831
|
-
api.logger?.warn?.("[openclaw-overlay] Auto-registration failed:", err.message);
|
|
2832
|
-
}
|
|
2833
2607
|
}
|
|
2834
2608
|
} catch (err) {
|
|
2835
2609
|
knownTxids.add(key);
|
|
@@ -2842,27 +2616,6 @@ Notify the user their wallet has been funded.`, api.logger, port, token, { sessi
|
|
|
2842
2616
|
api.logger?.warn?.("[openclaw-overlay] Auto-import setup failed:", err.message);
|
|
2843
2617
|
}
|
|
2844
2618
|
}
|
|
2845
|
-
async function autoAdvertiseServices(config, logger) {
|
|
2846
|
-
try {
|
|
2847
|
-
let servicesToAdvertise = [];
|
|
2848
|
-
if (config?.services && Array.isArray(config.services)) {
|
|
2849
|
-
servicesToAdvertise = config.services;
|
|
2850
|
-
}
|
|
2851
|
-
if (servicesToAdvertise.length === 0) return;
|
|
2852
|
-
for (const serviceId of servicesToAdvertise) {
|
|
2853
|
-
const serviceInfo = serviceManager.registry.get(serviceId);
|
|
2854
|
-
if (!serviceInfo) continue;
|
|
2855
|
-
try {
|
|
2856
|
-
const { cmdAdvertise: cmdAdvertise2 } = await Promise.resolve().then(() => (init_services(), services_exports));
|
|
2857
|
-
applyConfigToEnv(config);
|
|
2858
|
-
await cmdAdvertise2(serviceId, serviceInfo.name, String(serviceInfo.defaultPrice), serviceInfo.description);
|
|
2859
|
-
} catch {
|
|
2860
|
-
}
|
|
2861
|
-
}
|
|
2862
|
-
} catch (err) {
|
|
2863
|
-
logger?.warn?.("[openclaw-overlay] Auto-advertising failed:", err.message);
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
2619
|
async function startBackgroundService(config, api, port, token) {
|
|
2867
2620
|
if (serviceRunning) return;
|
|
2868
2621
|
serviceRunning = true;
|
|
@@ -2922,7 +2675,7 @@ function stopBackgroundService() {
|
|
|
2922
2675
|
}
|
|
2923
2676
|
}
|
|
2924
2677
|
function register(api) {
|
|
2925
|
-
const version = "0.8.
|
|
2678
|
+
const version = "0.8.20";
|
|
2926
2679
|
if (isInitialized) return;
|
|
2927
2680
|
isInitialized = true;
|
|
2928
2681
|
api.logger?.info?.(`[openclaw-overlay] Initializing Plugin v${version}`);
|
|
@@ -2981,8 +2734,9 @@ ${typeof result === "string" ? result : JSON.stringify(result, null, 2)}` };
|
|
|
2981
2734
|
await initializeServiceSystem();
|
|
2982
2735
|
} catch {
|
|
2983
2736
|
}
|
|
2984
|
-
const
|
|
2985
|
-
const
|
|
2737
|
+
const env = process["env"];
|
|
2738
|
+
const gatewayPort = env.OPENCLAW_GATEWAY_PORT || "18789";
|
|
2739
|
+
const httpToken = env.OPENCLAW_HOOKS_TOKEN || "";
|
|
2986
2740
|
await startBackgroundService(pluginConfig, api, gatewayPort, httpToken);
|
|
2987
2741
|
await startAutoImport(pluginConfig, api, gatewayPort, httpToken);
|
|
2988
2742
|
},
|