@1sat/wallet-toolbox 0.0.75 → 0.0.76
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/api/broadcast/index.js +2 -1
- package/dist/api/inscriptions/index.js +1 -0
- package/dist/api/locks/index.js +2 -0
- package/dist/api/ordinals/index.js +4 -0
- package/dist/api/payments/index.js +2 -0
- package/dist/api/signing/index.js +1 -0
- package/dist/api/sweep/index.js +23 -2
- package/dist/api/tokens/index.js +80 -54
- package/dist/indexers/Bsv21Indexer.js +1 -2
- package/dist/services/client/Bsv21Client.d.ts +36 -0
- package/dist/services/client/Bsv21Client.js +45 -0
- package/dist/services/client/index.d.ts +1 -1
- package/dist/wallet/factory.d.ts +5 -12
- package/dist/wallet/factory.js +13 -17
- package/package.json +1 -1
|
@@ -65,8 +65,9 @@ export const broadcast = {
|
|
|
65
65
|
return { txid };
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
|
+
console.error("[broadcast]", error);
|
|
68
69
|
return {
|
|
69
|
-
error: error instanceof Error ? error.message : "
|
|
70
|
+
error: error instanceof Error ? error.message : "unknown-error",
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
},
|
package/dist/api/locks/index.js
CHANGED
|
@@ -143,6 +143,7 @@ export const lockBsv = {
|
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
145
|
catch (error) {
|
|
146
|
+
console.error("[lockBsv]", error);
|
|
146
147
|
return {
|
|
147
148
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
148
149
|
};
|
|
@@ -280,6 +281,7 @@ export const unlockBsv = {
|
|
|
280
281
|
};
|
|
281
282
|
}
|
|
282
283
|
catch (error) {
|
|
284
|
+
console.error("[unlockBsv]", error);
|
|
283
285
|
return {
|
|
284
286
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
285
287
|
};
|
|
@@ -454,6 +454,7 @@ export const transferOrdinals = {
|
|
|
454
454
|
};
|
|
455
455
|
}
|
|
456
456
|
catch (error) {
|
|
457
|
+
console.error("[transferOrdinals]", error);
|
|
457
458
|
return {
|
|
458
459
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
459
460
|
};
|
|
@@ -523,6 +524,7 @@ export const listOrdinal = {
|
|
|
523
524
|
};
|
|
524
525
|
}
|
|
525
526
|
catch (error) {
|
|
527
|
+
console.error("[listOrdinal]", error);
|
|
526
528
|
return {
|
|
527
529
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
528
530
|
};
|
|
@@ -661,6 +663,7 @@ export const cancelListing = {
|
|
|
661
663
|
};
|
|
662
664
|
}
|
|
663
665
|
catch (error) {
|
|
666
|
+
console.error("[cancelListing]", error);
|
|
664
667
|
return {
|
|
665
668
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
666
669
|
};
|
|
@@ -827,6 +830,7 @@ export const purchaseOrdinal = {
|
|
|
827
830
|
};
|
|
828
831
|
}
|
|
829
832
|
catch (error) {
|
|
833
|
+
console.error("[purchaseOrdinal]", error);
|
|
830
834
|
return {
|
|
831
835
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
832
836
|
};
|
|
@@ -126,6 +126,7 @@ export const sendBsv = {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
catch (error) {
|
|
129
|
+
console.error("[sendBsv]", error);
|
|
129
130
|
return {
|
|
130
131
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
131
132
|
};
|
|
@@ -178,6 +179,7 @@ export const sendAllBsv = {
|
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
catch (error) {
|
|
182
|
+
console.error("[sendAllBsv]", error);
|
|
181
183
|
return {
|
|
182
184
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
183
185
|
};
|
package/dist/api/sweep/index.js
CHANGED
|
@@ -216,10 +216,11 @@ export const sweepBsv = {
|
|
|
216
216
|
};
|
|
217
217
|
}
|
|
218
218
|
catch (error) {
|
|
219
|
+
console.error("[sweepBsv]", error);
|
|
219
220
|
// Log detailed error info for WERR_REVIEW_ACTIONS
|
|
220
221
|
if (error && typeof error === "object" && "sendWithResults" in error) {
|
|
221
222
|
const werr = error;
|
|
222
|
-
console.error("[
|
|
223
|
+
console.error("[sweepBsv] WERR_REVIEW_ACTIONS details:", {
|
|
223
224
|
message: werr.message,
|
|
224
225
|
txid: werr.txid,
|
|
225
226
|
sendWithResults: JSON.stringify(werr.sendWithResults, null, 2),
|
|
@@ -509,6 +510,7 @@ export const sweepOrdinals = {
|
|
|
509
510
|
};
|
|
510
511
|
}
|
|
511
512
|
catch (error) {
|
|
513
|
+
console.error("[sweepOrdinals]", error);
|
|
512
514
|
return {
|
|
513
515
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
514
516
|
};
|
|
@@ -588,6 +590,16 @@ export const sweepBsv21 = {
|
|
|
588
590
|
return { error: "token-not-active" };
|
|
589
591
|
}
|
|
590
592
|
const { fee_address, fee_per_output } = tokenDetails.status;
|
|
593
|
+
// Validate all input outpoints exist in the overlay
|
|
594
|
+
const candidateOutpoints = inputs.map((i) => i.outpoint);
|
|
595
|
+
const validated = await ctx.services.bsv21.validateOutputs(tokenId, candidateOutpoints, { unspent: true });
|
|
596
|
+
const validSet = new Set(validated.map((v) => v.outpoint));
|
|
597
|
+
const invalidInputs = inputs.filter((i) => !validSet.has(i.outpoint));
|
|
598
|
+
if (invalidInputs.length > 0) {
|
|
599
|
+
return {
|
|
600
|
+
error: `unvalidated-inputs: ${invalidInputs.map((i) => i.outpoint).join(", ")}`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
591
603
|
// Parse WIF
|
|
592
604
|
const privateKey = PrivateKey.fromWif(wif);
|
|
593
605
|
// Sum all input amounts
|
|
@@ -635,7 +647,15 @@ export const sweepBsv21 = {
|
|
|
635
647
|
satoshis: 1,
|
|
636
648
|
outputDescription: `Sweep ${totalAmount} tokens`,
|
|
637
649
|
basket: BSV21_BASKET,
|
|
638
|
-
tags: [
|
|
650
|
+
tags: [
|
|
651
|
+
`id:${tokenId}`,
|
|
652
|
+
`amt:${totalAmount}`,
|
|
653
|
+
`dec:${tokenDetails.token.dec}`,
|
|
654
|
+
...(tokenDetails.token.sym ? [`sym:${tokenDetails.token.sym}`] : []),
|
|
655
|
+
...(tokenDetails.token.icon
|
|
656
|
+
? [`icon:${tokenDetails.token.icon}`]
|
|
657
|
+
: []),
|
|
658
|
+
],
|
|
639
659
|
customInstructions: JSON.stringify({
|
|
640
660
|
protocolID: BSV21_PROTOCOL,
|
|
641
661
|
keyID,
|
|
@@ -716,6 +736,7 @@ export const sweepBsv21 = {
|
|
|
716
736
|
};
|
|
717
737
|
}
|
|
718
738
|
catch (error) {
|
|
739
|
+
console.error("[sweepBsv21]", error);
|
|
719
740
|
return {
|
|
720
741
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
721
742
|
};
|
package/dist/api/tokens/index.js
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { BSV21, OrdLock } from "@bopen-io/templates";
|
|
7
7
|
import { BigNumber, LockingScript, OP, P2PKH, PublicKey, Transaction, TransactionSignature, UnlockingScript, Utils, } from "@bsv/sdk";
|
|
8
|
-
import {
|
|
9
|
-
import { BSV21_BASKET, BSV21_FEE_SATS, BSV21_PROTOCOL } from "../constants";
|
|
8
|
+
import { BSV21_BASKET, BSV21_PROTOCOL } from "../constants";
|
|
10
9
|
// ============================================================================
|
|
11
10
|
// Internal helpers
|
|
12
11
|
// ============================================================================
|
|
@@ -105,31 +104,19 @@ export const getBsv21Balances = {
|
|
|
105
104
|
const amtTag = o.tags?.find((t) => t.startsWith("amt:"))?.slice(4);
|
|
106
105
|
if (!idTag || !amtTag)
|
|
107
106
|
continue;
|
|
108
|
-
const
|
|
109
|
-
const lastColonIdx = idContent.lastIndexOf(":");
|
|
110
|
-
if (lastColonIdx === -1)
|
|
111
|
-
continue;
|
|
112
|
-
const tokenId = idContent.slice(0, lastColonIdx);
|
|
113
|
-
const status = idContent.slice(lastColonIdx + 1);
|
|
114
|
-
if (status === "invalid")
|
|
115
|
-
continue;
|
|
116
|
-
const isConfirmed = status === "valid";
|
|
107
|
+
const tokenId = idTag.slice(3);
|
|
117
108
|
const amt = BigInt(amtTag);
|
|
118
109
|
const dec = Number.parseInt(o.tags?.find((t) => t.startsWith("dec:"))?.slice(4) || "0", 10);
|
|
119
110
|
const symTag = o.tags?.find((t) => t.startsWith("sym:"))?.slice(4);
|
|
120
111
|
const iconTag = o.tags?.find((t) => t.startsWith("icon:"))?.slice(5);
|
|
121
112
|
const existing = balanceMap.get(tokenId);
|
|
122
113
|
if (existing) {
|
|
123
|
-
|
|
124
|
-
existing.confirmed += amt;
|
|
125
|
-
else
|
|
126
|
-
existing.pending += amt;
|
|
114
|
+
existing.amt += amt;
|
|
127
115
|
}
|
|
128
116
|
else {
|
|
129
117
|
balanceMap.set(tokenId, {
|
|
130
118
|
id: tokenId,
|
|
131
|
-
|
|
132
|
-
pending: isConfirmed ? 0n : amt,
|
|
119
|
+
amt,
|
|
133
120
|
sym: symTag,
|
|
134
121
|
icon: iconTag,
|
|
135
122
|
dec,
|
|
@@ -140,11 +127,11 @@ export const getBsv21Balances = {
|
|
|
140
127
|
p: "bsv-20",
|
|
141
128
|
op: "transfer",
|
|
142
129
|
dec: b.dec,
|
|
143
|
-
amt:
|
|
130
|
+
amt: b.amt.toString(),
|
|
144
131
|
id: b.id,
|
|
145
132
|
sym: b.sym,
|
|
146
133
|
icon: b.icon,
|
|
147
|
-
all: { confirmed: b.
|
|
134
|
+
all: { confirmed: b.amt, pending: 0n },
|
|
148
135
|
listed: { confirmed: 0n, pending: 0n },
|
|
149
136
|
}));
|
|
150
137
|
},
|
|
@@ -191,6 +178,14 @@ export const sendBsv21 = {
|
|
|
191
178
|
!/^\d+$/.test(parts[1])) {
|
|
192
179
|
return { error: "invalid-token-id-format" };
|
|
193
180
|
}
|
|
181
|
+
if (!ctx.services) {
|
|
182
|
+
return { error: "services-required" };
|
|
183
|
+
}
|
|
184
|
+
const tokenDetails = await ctx.services.bsv21.getTokenDetails(tokenId);
|
|
185
|
+
if (!tokenDetails.status.is_active) {
|
|
186
|
+
return { error: "token-not-active" };
|
|
187
|
+
}
|
|
188
|
+
const { fee_address, fee_per_output } = tokenDetails.status;
|
|
194
189
|
const result = await ctx.wallet.listOutputs({
|
|
195
190
|
basket: BSV21_BASKET,
|
|
196
191
|
includeTags: true,
|
|
@@ -201,16 +196,24 @@ export const sendBsv21 = {
|
|
|
201
196
|
const idTag = o.tags?.find((t) => t.startsWith("id:"));
|
|
202
197
|
if (!idTag)
|
|
203
198
|
return false;
|
|
204
|
-
|
|
205
|
-
const lastColonIdx = idContent.lastIndexOf(":");
|
|
206
|
-
if (lastColonIdx === -1)
|
|
207
|
-
return false;
|
|
208
|
-
const id = idContent.slice(0, lastColonIdx);
|
|
209
|
-
const status = idContent.slice(lastColonIdx + 1);
|
|
210
|
-
return id === tokenId && status !== "invalid";
|
|
199
|
+
return idTag.slice(3) === tokenId;
|
|
211
200
|
});
|
|
212
|
-
|
|
213
|
-
|
|
201
|
+
// Batch-validate all candidate outpoints against the overlay
|
|
202
|
+
const validOutpoints = new Set();
|
|
203
|
+
let overlayValidated = false;
|
|
204
|
+
if (ctx.services?.bsv21) {
|
|
205
|
+
const candidateOutpoints = tokenUtxos.map((o) => o.outpoint);
|
|
206
|
+
try {
|
|
207
|
+
const validated = await ctx.services.bsv21.validateOutputs(tokenId, candidateOutpoints, { unspent: true });
|
|
208
|
+
overlayValidated = true;
|
|
209
|
+
for (const v of validated) {
|
|
210
|
+
validOutpoints.add(v.outpoint);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
console.error("[sendBsv21] overlay validation error:", e);
|
|
215
|
+
return { error: "overlay-validation-failed" };
|
|
216
|
+
}
|
|
214
217
|
}
|
|
215
218
|
const selected = [];
|
|
216
219
|
let totalIn = 0n;
|
|
@@ -221,23 +224,15 @@ export const sendBsv21 = {
|
|
|
221
224
|
if (!amtTag)
|
|
222
225
|
continue;
|
|
223
226
|
const utxoAmount = BigInt(amtTag.slice(4));
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const validation = await ctx.services.bsv21.getTokenByTxid(tokenId, txid);
|
|
228
|
-
const outputData = validation.outputs.find((o) => `${validation.txid}_${o.vout}` === utxo.outpoint);
|
|
229
|
-
if (!outputData)
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
227
|
+
// Skip UTXOs not confirmed in the overlay
|
|
228
|
+
if (overlayValidated && !validOutpoints.has(utxo.outpoint)) {
|
|
229
|
+
continue;
|
|
235
230
|
}
|
|
236
231
|
selected.push(utxo);
|
|
237
232
|
totalIn += utxoAmount;
|
|
238
233
|
}
|
|
239
234
|
if (totalIn < amount) {
|
|
240
|
-
return { error: "insufficient-
|
|
235
|
+
return { error: "insufficient-tokens" };
|
|
241
236
|
}
|
|
242
237
|
let recipientAddress;
|
|
243
238
|
if (counterparty) {
|
|
@@ -267,16 +262,10 @@ export const sendBsv21 = {
|
|
|
267
262
|
satoshis: 1,
|
|
268
263
|
outputDescription: `Send ${amount} tokens`,
|
|
269
264
|
});
|
|
270
|
-
// Fee output to overlay fund address
|
|
271
|
-
const fundAddress = deriveFundAddress(tokenId);
|
|
272
|
-
outputs.push({
|
|
273
|
-
lockingScript: p2pkh.lock(fundAddress).toHex(),
|
|
274
|
-
satoshis: BSV21_FEE_SATS,
|
|
275
|
-
outputDescription: "Overlay processing fee",
|
|
276
|
-
tags: [],
|
|
277
|
-
});
|
|
278
265
|
const change = totalIn - amount;
|
|
266
|
+
let tokenOutputCount = 1;
|
|
279
267
|
if (change > 0n) {
|
|
268
|
+
tokenOutputCount = 2;
|
|
280
269
|
const changeKeyID = `${tokenId}-${Date.now()}`;
|
|
281
270
|
const { publicKey } = await ctx.wallet.getPublicKey({
|
|
282
271
|
protocolID: BSV21_PROTOCOL,
|
|
@@ -292,15 +281,31 @@ export const sendBsv21 = {
|
|
|
292
281
|
satoshis: 1,
|
|
293
282
|
outputDescription: "Token change",
|
|
294
283
|
basket: BSV21_BASKET,
|
|
295
|
-
tags: [
|
|
284
|
+
tags: [
|
|
285
|
+
`id:${tokenId}`,
|
|
286
|
+
`amt:${change}`,
|
|
287
|
+
`dec:${tokenDetails.token.dec}`,
|
|
288
|
+
...(tokenDetails.token.sym
|
|
289
|
+
? [`sym:${tokenDetails.token.sym}`]
|
|
290
|
+
: []),
|
|
291
|
+
...(tokenDetails.token.icon
|
|
292
|
+
? [`icon:${tokenDetails.token.icon}`]
|
|
293
|
+
: []),
|
|
294
|
+
],
|
|
296
295
|
customInstructions: JSON.stringify({
|
|
297
296
|
protocolID: BSV21_PROTOCOL,
|
|
298
297
|
keyID: changeKeyID,
|
|
299
298
|
}),
|
|
300
299
|
});
|
|
301
300
|
}
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
// Fee output to overlay fund address (per token output)
|
|
302
|
+
outputs.push({
|
|
303
|
+
lockingScript: p2pkh.lock(fee_address).toHex(),
|
|
304
|
+
satoshis: fee_per_output * tokenOutputCount,
|
|
305
|
+
outputDescription: "Overlay processing fee",
|
|
306
|
+
tags: [],
|
|
307
|
+
});
|
|
308
|
+
const symbol = tokenDetails.token.sym || tokenId.slice(0, 8);
|
|
304
309
|
const createResult = await ctx.wallet.createAction({
|
|
305
310
|
description: `Send ${amount} ${symbol}`,
|
|
306
311
|
inputs: selected.map((o) => ({
|
|
@@ -340,6 +345,7 @@ export const sendBsv21 = {
|
|
|
340
345
|
};
|
|
341
346
|
}
|
|
342
347
|
catch (error) {
|
|
348
|
+
console.error("[sendBsv21]", error);
|
|
343
349
|
return {
|
|
344
350
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
345
351
|
};
|
|
@@ -393,11 +399,13 @@ export const purchaseBsv21 = {
|
|
|
393
399
|
const [txid, voutStr] = parts;
|
|
394
400
|
const vout = Number.parseInt(voutStr, 10);
|
|
395
401
|
try {
|
|
396
|
-
await ctx.services.bsv21.
|
|
402
|
+
await ctx.services.bsv21.validateOutput(tokenId, outpoint);
|
|
397
403
|
}
|
|
398
|
-
catch {
|
|
404
|
+
catch (e) {
|
|
405
|
+
console.error("[purchaseBsv21] overlay validation error:", e);
|
|
399
406
|
return { error: "listing-not-found-in-overlay" };
|
|
400
407
|
}
|
|
408
|
+
const tokenDetails = await ctx.services.bsv21.getTokenDetails(tokenId);
|
|
401
409
|
const beef = await ctx.services.getBeefForTxid(txid);
|
|
402
410
|
const listingBeefTx = beef.findTxid(txid);
|
|
403
411
|
if (!listingBeefTx?.tx) {
|
|
@@ -428,7 +436,15 @@ export const purchaseBsv21 = {
|
|
|
428
436
|
satoshis: 1,
|
|
429
437
|
outputDescription: "Purchased tokens",
|
|
430
438
|
basket: BSV21_BASKET,
|
|
431
|
-
tags: [
|
|
439
|
+
tags: [
|
|
440
|
+
`id:${tokenId}`,
|
|
441
|
+
`amt:${tokenAmount}`,
|
|
442
|
+
`dec:${tokenDetails.token.dec}`,
|
|
443
|
+
...(tokenDetails.token.sym ? [`sym:${tokenDetails.token.sym}`] : []),
|
|
444
|
+
...(tokenDetails.token.icon
|
|
445
|
+
? [`icon:${tokenDetails.token.icon}`]
|
|
446
|
+
: []),
|
|
447
|
+
],
|
|
432
448
|
customInstructions: JSON.stringify({
|
|
433
449
|
protocolID: BSV21_PROTOCOL,
|
|
434
450
|
keyID: bsv21KeyID,
|
|
@@ -456,6 +472,15 @@ export const purchaseBsv21 = {
|
|
|
456
472
|
});
|
|
457
473
|
}
|
|
458
474
|
}
|
|
475
|
+
// Fee output to overlay fund address
|
|
476
|
+
if (tokenDetails.status.is_active) {
|
|
477
|
+
outputs.push({
|
|
478
|
+
lockingScript: p2pkh.lock(tokenDetails.status.fee_address).toHex(),
|
|
479
|
+
satoshis: tokenDetails.status.fee_per_output,
|
|
480
|
+
outputDescription: "Overlay processing fee",
|
|
481
|
+
tags: [],
|
|
482
|
+
});
|
|
483
|
+
}
|
|
459
484
|
const createResult = await ctx.wallet.createAction({
|
|
460
485
|
description: `Purchase ${tokenAmount} tokens for ${payoutSatoshis} sats`,
|
|
461
486
|
inputBEEF: beef.toBinary(),
|
|
@@ -503,6 +528,7 @@ export const purchaseBsv21 = {
|
|
|
503
528
|
};
|
|
504
529
|
}
|
|
505
530
|
catch (error) {
|
|
531
|
+
console.error("[purchaseBsv21]", error);
|
|
506
532
|
return {
|
|
507
533
|
error: error instanceof Error ? error.message : "unknown-error",
|
|
508
534
|
};
|
|
@@ -77,8 +77,7 @@ export class Bsv21Indexer extends Indexer {
|
|
|
77
77
|
return;
|
|
78
78
|
const tags = [];
|
|
79
79
|
if (txo.owner && this.owners.has(txo.owner)) {
|
|
80
|
-
|
|
81
|
-
tags.push(`id:${bsv21.id}:${bsv21.status}`);
|
|
80
|
+
tags.push(`id:${bsv21.id}`);
|
|
82
81
|
tags.push(`amt:${bsv21.amt.toString()}`);
|
|
83
82
|
// Add metadata tags for efficient querying
|
|
84
83
|
if (bsv21.sym)
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import type { Bsv21TransactionData, ClientOptions, IndexedOutput, TokenDetailResponse } from "../types";
|
|
2
2
|
import { BaseClient } from "./BaseClient";
|
|
3
|
+
/**
|
|
4
|
+
* Query options for /outputs validation endpoints.
|
|
5
|
+
* All flags default to false on the server.
|
|
6
|
+
*/
|
|
7
|
+
export interface OutputQueryOptions {
|
|
8
|
+
/** Filter for unspent outputs only */
|
|
9
|
+
unspent?: boolean;
|
|
10
|
+
/** Include spend txid */
|
|
11
|
+
spend?: boolean;
|
|
12
|
+
/** Include satoshis */
|
|
13
|
+
sats?: boolean;
|
|
14
|
+
/** Include events array */
|
|
15
|
+
events?: boolean;
|
|
16
|
+
/** Include block info */
|
|
17
|
+
block?: boolean;
|
|
18
|
+
/** Comma-separated data tags to include (e.g. 'bsv21') */
|
|
19
|
+
tags?: string;
|
|
20
|
+
}
|
|
3
21
|
/**
|
|
4
22
|
* Client for /1sat/bsv21/* routes.
|
|
5
23
|
* Provides BSV21 token queries.
|
|
@@ -66,6 +84,24 @@ export declare class Bsv21Client extends BaseClient {
|
|
|
66
84
|
* @param addresses - Array of addresses (max 100)
|
|
67
85
|
*/
|
|
68
86
|
getHistoryMulti(tokenId: string, lockType: string, addresses: string[]): Promise<IndexedOutput[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Validate specific outpoints against the token's overlay topic.
|
|
89
|
+
* Returns only those found in the overlay. By default returns minimal data
|
|
90
|
+
* (outpoint + score). Use opts to include additional fields.
|
|
91
|
+
* @param tokenId - Token ID (txid_vout format)
|
|
92
|
+
* @param outpoints - Array of outpoints to validate (max 1000)
|
|
93
|
+
* @param opts - Optional query flags for additional data
|
|
94
|
+
*/
|
|
95
|
+
validateOutputs(tokenId: string, outpoints: string[], opts?: OutputQueryOptions): Promise<IndexedOutput[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Validate a single outpoint against the token's overlay topic.
|
|
98
|
+
* Returns 404 if not found. By default returns minimal data (outpoint + score).
|
|
99
|
+
* @param tokenId - Token ID (txid_vout format)
|
|
100
|
+
* @param outpoint - Outpoint to validate (txid_vout or txid:vout)
|
|
101
|
+
* @param opts - Optional query flags for additional data
|
|
102
|
+
*/
|
|
103
|
+
validateOutput(tokenId: string, outpoint: string, opts?: OutputQueryOptions): Promise<IndexedOutput>;
|
|
104
|
+
private buildOutputQuery;
|
|
69
105
|
/**
|
|
70
106
|
* Clear the token details cache
|
|
71
107
|
*/
|
|
@@ -100,6 +100,51 @@ export class Bsv21Client extends BaseClient {
|
|
|
100
100
|
body: JSON.stringify(addresses),
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Validate specific outpoints against the token's overlay topic.
|
|
105
|
+
* Returns only those found in the overlay. By default returns minimal data
|
|
106
|
+
* (outpoint + score). Use opts to include additional fields.
|
|
107
|
+
* @param tokenId - Token ID (txid_vout format)
|
|
108
|
+
* @param outpoints - Array of outpoints to validate (max 1000)
|
|
109
|
+
* @param opts - Optional query flags for additional data
|
|
110
|
+
*/
|
|
111
|
+
async validateOutputs(tokenId, outpoints, opts) {
|
|
112
|
+
const params = this.buildOutputQuery(opts);
|
|
113
|
+
return this.request(`/${tokenId}/outputs${params}`, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { "Content-Type": "application/json" },
|
|
116
|
+
body: JSON.stringify(outpoints),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Validate a single outpoint against the token's overlay topic.
|
|
121
|
+
* Returns 404 if not found. By default returns minimal data (outpoint + score).
|
|
122
|
+
* @param tokenId - Token ID (txid_vout format)
|
|
123
|
+
* @param outpoint - Outpoint to validate (txid_vout or txid:vout)
|
|
124
|
+
* @param opts - Optional query flags for additional data
|
|
125
|
+
*/
|
|
126
|
+
async validateOutput(tokenId, outpoint, opts) {
|
|
127
|
+
const params = this.buildOutputQuery(opts);
|
|
128
|
+
return this.request(`/${tokenId}/outputs/${outpoint}${params}`);
|
|
129
|
+
}
|
|
130
|
+
buildOutputQuery(opts) {
|
|
131
|
+
if (!opts)
|
|
132
|
+
return "";
|
|
133
|
+
const parts = [];
|
|
134
|
+
if (opts.unspent)
|
|
135
|
+
parts.push("unspent=true");
|
|
136
|
+
if (opts.spend)
|
|
137
|
+
parts.push("spend=true");
|
|
138
|
+
if (opts.sats)
|
|
139
|
+
parts.push("sats=true");
|
|
140
|
+
if (opts.events)
|
|
141
|
+
parts.push("events=true");
|
|
142
|
+
if (opts.block)
|
|
143
|
+
parts.push("block=true");
|
|
144
|
+
if (opts.tags)
|
|
145
|
+
parts.push(`tags=${encodeURIComponent(opts.tags)}`);
|
|
146
|
+
return parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
147
|
+
}
|
|
103
148
|
/**
|
|
104
149
|
* Clear the token details cache
|
|
105
150
|
*/
|
|
@@ -5,5 +5,5 @@ export { ArcadeClient } from "./ArcadeClient";
|
|
|
5
5
|
export { TxoClient } from "./TxoClient";
|
|
6
6
|
export { OwnerClient } from "./OwnerClient";
|
|
7
7
|
export { OrdfsClient } from "./OrdfsClient";
|
|
8
|
-
export { Bsv21Client } from "./Bsv21Client";
|
|
8
|
+
export { Bsv21Client, type OutputQueryOptions } from "./Bsv21Client";
|
|
9
9
|
export { OverlayClient } from "./OverlayClient";
|
package/dist/wallet/factory.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* (browser extension) and 1sat-website (React app).
|
|
6
6
|
*/
|
|
7
7
|
import { PrivateKey } from "@bsv/sdk";
|
|
8
|
-
import { Monitor,
|
|
8
|
+
import { Monitor, StorageClient, Wallet, WalletStorageManager } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
|
|
9
9
|
import { OneSatServices } from "../services/OneSatServices";
|
|
10
10
|
import { type FullSyncResult, type FullSyncStage } from "./fullSync";
|
|
11
11
|
type Chain = "main" | "test";
|
|
@@ -17,10 +17,6 @@ export interface WebWalletConfig {
|
|
|
17
17
|
privateKey: PrivateKey | string;
|
|
18
18
|
/** Network: 'main' or 'test' */
|
|
19
19
|
chain: Chain;
|
|
20
|
-
/** Admin originator that bypasses permission checks (e.g., chrome-extension://id or https://wallet.example.com) */
|
|
21
|
-
adminOriginator: string;
|
|
22
|
-
/** Permission configuration for WalletPermissionsManager */
|
|
23
|
-
permissionsConfig: PermissionsManagerConfig;
|
|
24
20
|
/** Fee model. Default: { model: 'sat/kb', value: 100 } */
|
|
25
21
|
feeModel?: {
|
|
26
22
|
model: "sat/kb";
|
|
@@ -39,10 +35,8 @@ export interface WebWalletConfig {
|
|
|
39
35
|
* Result of wallet creation.
|
|
40
36
|
*/
|
|
41
37
|
export interface WebWalletResult {
|
|
42
|
-
/** Wallet instance
|
|
43
|
-
wallet:
|
|
44
|
-
/** Underlying wallet without permission checks (for trusted contexts like sweep-ui) */
|
|
45
|
-
rawWallet: Wallet;
|
|
38
|
+
/** Wallet instance */
|
|
39
|
+
wallet: Wallet;
|
|
46
40
|
/** 1Sat services for API access */
|
|
47
41
|
services: OneSatServices;
|
|
48
42
|
/** Monitor for transaction lifecycle (not started - call monitor.startTasks() when ready) */
|
|
@@ -57,15 +51,14 @@ export interface WebWalletResult {
|
|
|
57
51
|
remoteStorage?: StorageClient;
|
|
58
52
|
}
|
|
59
53
|
/**
|
|
60
|
-
* Create a web wallet with storage, services,
|
|
54
|
+
* Create a web wallet with storage, services, and monitor.
|
|
61
55
|
*
|
|
62
56
|
* @example
|
|
63
57
|
* ```typescript
|
|
64
58
|
* const { wallet, services, monitor, destroy } = await createWebWallet({
|
|
65
59
|
* privateKey: identityWif,
|
|
66
60
|
* chain: 'main',
|
|
67
|
-
*
|
|
68
|
-
* permissionsConfig: DEFAULT_PERMISSIONS_CONFIG,
|
|
61
|
+
* storageIdentityKey: 'device-unique-id',
|
|
69
62
|
* });
|
|
70
63
|
*
|
|
71
64
|
* // Wire up monitor callbacks
|
package/dist/wallet/factory.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* (browser extension) and 1sat-website (React app).
|
|
6
6
|
*/
|
|
7
7
|
import { KeyDeriver, PrivateKey } from "@bsv/sdk";
|
|
8
|
-
import { Monitor, Services, StorageClient, StorageIdb, StorageProvider, Wallet,
|
|
8
|
+
import { Monitor, Services, StorageClient, StorageIdb, StorageProvider, Wallet, WalletStorageManager, } from "@bsv/wallet-toolbox-mobile/out/src/index.client.js";
|
|
9
9
|
import { OneSatServices } from "../services/OneSatServices";
|
|
10
10
|
import { fullSync } from "./fullSync";
|
|
11
11
|
// Default database name for IndexedDB storage
|
|
@@ -39,15 +39,14 @@ function parsePrivateKey(input) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* Create a web wallet with storage, services,
|
|
42
|
+
* Create a web wallet with storage, services, and monitor.
|
|
43
43
|
*
|
|
44
44
|
* @example
|
|
45
45
|
* ```typescript
|
|
46
46
|
* const { wallet, services, monitor, destroy } = await createWebWallet({
|
|
47
47
|
* privateKey: identityWif,
|
|
48
48
|
* chain: 'main',
|
|
49
|
-
*
|
|
50
|
-
* permissionsConfig: DEFAULT_PERMISSIONS_CONFIG,
|
|
49
|
+
* storageIdentityKey: 'device-unique-id',
|
|
51
50
|
* });
|
|
52
51
|
*
|
|
53
52
|
* // Wire up monitor callbacks
|
|
@@ -58,7 +57,7 @@ function parsePrivateKey(input) {
|
|
|
58
57
|
* ```
|
|
59
58
|
*/
|
|
60
59
|
export async function createWebWallet(config) {
|
|
61
|
-
const { chain
|
|
60
|
+
const { chain } = config;
|
|
62
61
|
const feeModel = config.feeModel ?? DEFAULT_FEE_MODEL;
|
|
63
62
|
// 1. Parse private key and create KeyDeriver
|
|
64
63
|
const privateKey = parsePrivateKey(config.privateKey);
|
|
@@ -75,8 +74,8 @@ export async function createWebWallet(config) {
|
|
|
75
74
|
// 4. Create storage manager with local-only storage initially (empty backups)
|
|
76
75
|
const storage = new WalletStorageManager(identityPubKey, localStorage, []);
|
|
77
76
|
await storage.makeAvailable();
|
|
78
|
-
// 5. Create
|
|
79
|
-
const
|
|
77
|
+
// 5. Create wallet (needed before StorageClient for signing)
|
|
78
|
+
const wallet = new Wallet({
|
|
80
79
|
chain,
|
|
81
80
|
keyDeriver,
|
|
82
81
|
storage,
|
|
@@ -89,7 +88,7 @@ export async function createWebWallet(config) {
|
|
|
89
88
|
if (config.remoteStorageUrl) {
|
|
90
89
|
console.log(`[createWebWallet] Attempting remote storage connection to ${config.remoteStorageUrl}`);
|
|
91
90
|
try {
|
|
92
|
-
remoteClient = new StorageClient(
|
|
91
|
+
remoteClient = new StorageClient(wallet, config.remoteStorageUrl);
|
|
93
92
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Remote storage connection timeout")), DEFAULT_REMOTE_STORAGE_TIMEOUT));
|
|
94
93
|
await Promise.race([remoteClient.makeAvailable(), timeoutPromise]);
|
|
95
94
|
// Add remote to storage manager - it will partition as conflicting if another device is active
|
|
@@ -137,8 +136,8 @@ export async function createWebWallet(config) {
|
|
|
137
136
|
// bypass the monitor's onTransactionBroadcasted callback. We detect broadcasts
|
|
138
137
|
// by checking for txid in the result and sync to backup immediately.
|
|
139
138
|
if (remoteClient) {
|
|
140
|
-
const originalCreateAction =
|
|
141
|
-
|
|
139
|
+
const originalCreateAction = wallet.createAction.bind(wallet);
|
|
140
|
+
wallet.createAction = async (args) => {
|
|
142
141
|
const result = await originalCreateAction(args);
|
|
143
142
|
if (result.txid) {
|
|
144
143
|
console.log("[createWebWallet] Broadcast detected in createAction:", result.txid);
|
|
@@ -148,8 +147,8 @@ export async function createWebWallet(config) {
|
|
|
148
147
|
}
|
|
149
148
|
return result;
|
|
150
149
|
};
|
|
151
|
-
const originalSignAction =
|
|
152
|
-
|
|
150
|
+
const originalSignAction = wallet.signAction.bind(wallet);
|
|
151
|
+
wallet.signAction = async (args) => {
|
|
153
152
|
const result = await originalSignAction(args);
|
|
154
153
|
if (result.txid) {
|
|
155
154
|
console.log("[createWebWallet] Broadcast detected in signAction:", result.txid);
|
|
@@ -160,9 +159,7 @@ export async function createWebWallet(config) {
|
|
|
160
159
|
return result;
|
|
161
160
|
};
|
|
162
161
|
}
|
|
163
|
-
// 9.
|
|
164
|
-
const wallet = new WalletPermissionsManager(underlyingWallet, adminOriginator, permissionsConfig);
|
|
165
|
-
// 10. Create monitor (not started - consumer calls startTasks() when ready)
|
|
162
|
+
// 9. Create monitor (not started - consumer calls startTasks() when ready)
|
|
166
163
|
const monitor = new Monitor({
|
|
167
164
|
chain,
|
|
168
165
|
services: oneSatServices,
|
|
@@ -228,7 +225,7 @@ export async function createWebWallet(config) {
|
|
|
228
225
|
const destroy = async () => {
|
|
229
226
|
monitor.stopTasks();
|
|
230
227
|
await monitor.destroy();
|
|
231
|
-
await
|
|
228
|
+
await wallet.destroy();
|
|
232
229
|
};
|
|
233
230
|
// 13. Create fullSync function if remote storage is connected
|
|
234
231
|
const fullSyncFn = remoteClient
|
|
@@ -243,7 +240,6 @@ export async function createWebWallet(config) {
|
|
|
243
240
|
: undefined;
|
|
244
241
|
return {
|
|
245
242
|
wallet,
|
|
246
|
-
rawWallet: underlyingWallet,
|
|
247
243
|
services: oneSatServices,
|
|
248
244
|
monitor,
|
|
249
245
|
destroy,
|