@1sat/sweep-ui 0.0.15 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SweepApp.d.ts.map +1 -1
- package/dist/components/asset-preview.d.ts +3 -1
- package/dist/components/asset-preview.d.ts.map +1 -1
- package/dist/index.js +144 -280
- package/dist/lib/scanner.d.ts +3 -18
- package/dist/lib/scanner.d.ts.map +1 -1
- package/dist/lib/sweeper.d.ts +17 -1
- package/dist/lib/sweeper.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/SweepApp.tsx +17 -5
- package/src/components/asset-preview.tsx +9 -4
- package/src/lib/scanner.ts +34 -241
- package/src/lib/sweeper.ts +37 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SweepApp.d.ts","sourceRoot":"","sources":["../../src/components/SweepApp.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAI5D,MAAM,WAAW,aAAa;IAC7B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"SweepApp.d.ts","sourceRoot":"","sources":["../../src/components/SweepApp.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAI5D,MAAM,WAAW,aAAa;IAC7B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,EAAE,aAAa,2CA0RrG"}
|
|
@@ -20,8 +20,10 @@ export declare function OrdinalsSection({ ordinals, selectedOrdinals, onToggle,
|
|
|
20
20
|
onBurn?: () => void;
|
|
21
21
|
walletConnected: boolean;
|
|
22
22
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
23
|
-
export declare function Bsv21Section({ tokens }: {
|
|
23
|
+
export declare function Bsv21Section({ tokens, onSweep, walletConnected }: {
|
|
24
24
|
tokens: TokenBalance[];
|
|
25
|
+
onSweep?: (tokenId: string) => void;
|
|
26
|
+
walletConnected: boolean;
|
|
25
27
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
26
28
|
export declare function Bsv20Section({ tokens }: {
|
|
27
29
|
tokens: IndexedOutput[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-preview.d.ts","sourceRoot":"","sources":["../../src/components/asset-preview.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA8CjD,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE;IACzH,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,eAAe,EAAE,OAAO,CAAC;CACpN,kDAqCA;AAED,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE;IAC/I,QAAQ,EAAE,eAAe,EAAE,CAAC;IAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAAC,eAAe,EAAE,OAAO,CAAC;CACjQ,kDAsDA;
|
|
1
|
+
{"version":3,"file":"asset-preview.d.ts","sourceRoot":"","sources":["../../src/components/asset-preview.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA8CjD,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE;IACzH,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,eAAe,EAAE,OAAO,CAAC;CACpN,kDAqCA;AAED,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE;IAC/I,QAAQ,EAAE,eAAe,EAAE,CAAC;IAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAAC,eAAe,EAAE,OAAO,CAAC;CACjQ,kDAsDA;AA+BD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE;IAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,kDAsB3J;AAED,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,aAAa,EAAE,CAAA;CAAE,kDAmBnE;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,aAAa,EAAE,CAAA;CAAE,kDAapE;AAED,wBAAgB,UAAU,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,kDAc3D"}
|
package/dist/index.js
CHANGED
|
@@ -341,7 +341,9 @@ import {
|
|
|
341
341
|
|
|
342
342
|
// src/lib/scanner.ts
|
|
343
343
|
import { PrivateKey } from "@bsv/sdk";
|
|
344
|
-
import {
|
|
344
|
+
import {
|
|
345
|
+
scanAddresses as coreScanAddresses
|
|
346
|
+
} from "@1sat/actions";
|
|
345
347
|
|
|
346
348
|
// src/lib/services.ts
|
|
347
349
|
import { OneSatServices } from "@1sat/client";
|
|
@@ -362,12 +364,11 @@ function getServices() {
|
|
|
362
364
|
}
|
|
363
365
|
|
|
364
366
|
// src/lib/scanner.ts
|
|
365
|
-
var RUN_PREFIX = Uint8Array.from([0, 106, 3, 114, 117, 110]);
|
|
366
367
|
function deriveAddress(wif) {
|
|
367
368
|
return PrivateKey.fromWif(wif.trim()).toPublicKey().toAddress();
|
|
368
369
|
}
|
|
369
370
|
function getEvent(events, prefix) {
|
|
370
|
-
const e = events.find((
|
|
371
|
+
const e = events.find((ev) => ev.startsWith(prefix));
|
|
371
372
|
return e ? e.slice(prefix.length) : undefined;
|
|
372
373
|
}
|
|
373
374
|
function getEvents(events, prefix) {
|
|
@@ -382,198 +383,44 @@ function enrichOrdinal(out) {
|
|
|
382
383
|
const contentUrl = getServices().ordfs.getContentUrl(origin ?? out.outpoint, { raw: true });
|
|
383
384
|
return { ...out, origin, contentType, name, contentUrl };
|
|
384
385
|
}
|
|
385
|
-
function
|
|
386
|
+
function resolveIconUrl(tokenId, icon) {
|
|
386
387
|
if (!icon)
|
|
387
|
-
return;
|
|
388
|
+
return "";
|
|
389
|
+
let outpoint = icon;
|
|
388
390
|
if (icon.startsWith("_")) {
|
|
389
391
|
const txid = tokenId.split("_")[0];
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
return icon;
|
|
393
|
-
}
|
|
394
|
-
async function groupBsv21Tokens(outputs) {
|
|
395
|
-
const groups = new Map;
|
|
396
|
-
for (const out of outputs) {
|
|
397
|
-
const events = out.events ?? [];
|
|
398
|
-
const tokenId = getEvent(events, "bsv21:");
|
|
399
|
-
if (!tokenId)
|
|
400
|
-
continue;
|
|
401
|
-
let group = groups.get(tokenId);
|
|
402
|
-
if (!group) {
|
|
403
|
-
group = [];
|
|
404
|
-
groups.set(tokenId, group);
|
|
405
|
-
}
|
|
406
|
-
group.push(out);
|
|
407
|
-
}
|
|
408
|
-
if (groups.size === 0)
|
|
409
|
-
return [];
|
|
410
|
-
const services = getServices();
|
|
411
|
-
const tokenIds = [...groups.keys()];
|
|
412
|
-
let details = [];
|
|
413
|
-
try {
|
|
414
|
-
details = await services.bsv21.lookupTokens(tokenIds);
|
|
415
|
-
} catch {}
|
|
416
|
-
const detailMap = new Map(details.map((d) => [d.tokenId, d]));
|
|
417
|
-
const balances = [];
|
|
418
|
-
for (const [tokenId, outs] of groups) {
|
|
419
|
-
const detail = detailMap.get(tokenId);
|
|
420
|
-
const isActive = detail?.status?.is_active ?? false;
|
|
421
|
-
const iconOutpoint = resolveIconOutpoint(tokenId, detail?.token?.icon);
|
|
422
|
-
let totalAmount = 0n;
|
|
423
|
-
let validatedOutputs = outs;
|
|
424
|
-
if (isActive) {
|
|
425
|
-
try {
|
|
426
|
-
const outpoints = outs.map((o) => o.outpoint);
|
|
427
|
-
const validated = await services.bsv21.validateOutputs(tokenId, outpoints, { unspent: true });
|
|
428
|
-
totalAmount = validated.reduce((sum, v) => {
|
|
429
|
-
const bsv21 = v.data?.bsv21;
|
|
430
|
-
return sum + (bsv21?.amt ? BigInt(bsv21.amt) : 0n);
|
|
431
|
-
}, 0n);
|
|
432
|
-
validatedOutputs = validated;
|
|
433
|
-
} catch {}
|
|
434
|
-
}
|
|
435
|
-
balances.push({
|
|
436
|
-
tokenId,
|
|
437
|
-
symbol: detail?.token?.sym,
|
|
438
|
-
icon: iconOutpoint ? services.ordfs.getContentUrl(iconOutpoint) : "",
|
|
439
|
-
decimals: Number(detail?.token?.dec ?? 0),
|
|
440
|
-
totalAmount,
|
|
441
|
-
outputs: validatedOutputs,
|
|
442
|
-
isActive
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
return balances;
|
|
446
|
-
}
|
|
447
|
-
async function categorizeOutputs(outputs) {
|
|
448
|
-
const funding = [];
|
|
449
|
-
const rawOrdinals = [];
|
|
450
|
-
const opnsRaw = [];
|
|
451
|
-
const bsv21Raw = [];
|
|
452
|
-
const bsv20Tokens = [];
|
|
453
|
-
const locked = [];
|
|
454
|
-
for (const out of outputs) {
|
|
455
|
-
const events = out.events ?? [];
|
|
456
|
-
const sats = out.satoshis ?? 0;
|
|
457
|
-
if (events.some((e) => e.startsWith("bsv21:"))) {
|
|
458
|
-
bsv21Raw.push(out);
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
if (events.some((e) => e.startsWith("lock:"))) {
|
|
462
|
-
locked.push(out);
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
if (events.some((e) => e === "type:application/bsv-20" || e === "type:Token")) {
|
|
466
|
-
bsv20Tokens.push(out);
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
if (sats === 1) {
|
|
470
|
-
if (events.some((e) => e === "type:application/op-ns")) {
|
|
471
|
-
opnsRaw.push(out);
|
|
472
|
-
} else {
|
|
473
|
-
rawOrdinals.push(out);
|
|
474
|
-
}
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (sats > 1) {
|
|
478
|
-
funding.push(out);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
const run = [];
|
|
482
|
-
const cleanFunding = [];
|
|
483
|
-
if (funding.length > 0) {
|
|
484
|
-
const runTxids = await detectRunTransactions(funding);
|
|
485
|
-
for (const f of funding) {
|
|
486
|
-
const { txid } = parseOutpoint(f.outpoint);
|
|
487
|
-
if (runTxids.has(txid)) {
|
|
488
|
-
run.push(f);
|
|
489
|
-
} else {
|
|
490
|
-
cleanFunding.push(f);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
392
|
+
outpoint = `${txid}${icon}`;
|
|
493
393
|
}
|
|
494
|
-
return
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
run,
|
|
502
|
-
totalBsv: cleanFunding.reduce((sum, o) => sum + (o.satoshis ?? 0), 0)
|
|
503
|
-
};
|
|
394
|
+
return getServices().ordfs.getContentUrl(outpoint);
|
|
395
|
+
}
|
|
396
|
+
function enrichTokenBalances(tokens) {
|
|
397
|
+
return tokens.map((t) => ({
|
|
398
|
+
...t,
|
|
399
|
+
icon: resolveIconUrl(t.tokenId, t.icon)
|
|
400
|
+
}));
|
|
504
401
|
}
|
|
505
402
|
async function scanAddress(address, onProgress) {
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
for await (const event of services.owner.getTxos(address, { refresh: true, limit: 1 })) {
|
|
509
|
-
if (event.type === "sync") {
|
|
510
|
-
const p = event.data;
|
|
511
|
-
onProgress?.({
|
|
512
|
-
phase: "sync",
|
|
513
|
-
detail: `${p.phase}: ${p.processed ?? 0}/${p.total ?? "?"}`
|
|
514
|
-
});
|
|
515
|
-
} else if (event.type === "done" || event.type === "error") {
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
onProgress?.({ phase: "search", detail: "Searching for assets..." });
|
|
520
|
-
const allOutputs = await services.txo.search(`own:${address}`, {
|
|
521
|
-
unspent: true,
|
|
522
|
-
events: true,
|
|
523
|
-
sats: true,
|
|
524
|
-
limit: 0
|
|
525
|
-
});
|
|
526
|
-
onProgress?.({ phase: "categorize", detail: "Loading token details..." });
|
|
527
|
-
return await categorizeOutputs(allOutputs ?? []);
|
|
403
|
+
const result = await coreScanAddresses(getServices(), [address], onProgress);
|
|
404
|
+
return toScannedAssets(result);
|
|
528
405
|
}
|
|
529
406
|
async function scanAddresses(addresses, onProgress) {
|
|
530
407
|
const unique = [...new Set(addresses)];
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
408
|
+
const result = await coreScanAddresses(getServices(), unique, onProgress);
|
|
409
|
+
return toScannedAssets(result);
|
|
410
|
+
}
|
|
411
|
+
function toScannedAssets(result) {
|
|
536
412
|
return {
|
|
537
|
-
funding:
|
|
538
|
-
ordinals:
|
|
539
|
-
opnsNames:
|
|
540
|
-
bsv21Tokens:
|
|
541
|
-
bsv20Tokens:
|
|
542
|
-
locked:
|
|
543
|
-
run:
|
|
544
|
-
|
|
413
|
+
funding: result.funding,
|
|
414
|
+
ordinals: result.ordinals.map(enrichOrdinal),
|
|
415
|
+
opnsNames: result.opnsNames.map(enrichOrdinal),
|
|
416
|
+
bsv21Tokens: enrichTokenBalances(result.bsv21Tokens),
|
|
417
|
+
bsv20Tokens: result.bsv20Tokens,
|
|
418
|
+
locked: result.locked,
|
|
419
|
+
run: result.run,
|
|
420
|
+
totalFundingSats: result.totalFundingSats,
|
|
421
|
+
totalBsv: result.totalFundingSats
|
|
545
422
|
};
|
|
546
423
|
}
|
|
547
|
-
async function detectRunTransactions(funding) {
|
|
548
|
-
const services = getServices();
|
|
549
|
-
const txids = [...new Set(funding.map((f) => parseOutpoint(f.outpoint).txid))];
|
|
550
|
-
const runTxids = new Set;
|
|
551
|
-
for (const txid of txids) {
|
|
552
|
-
try {
|
|
553
|
-
const beef = await services.getBeefForTxid(txid);
|
|
554
|
-
const beefTx = beef.findTxid(txid);
|
|
555
|
-
if (!beefTx?.tx)
|
|
556
|
-
continue;
|
|
557
|
-
for (const output of beefTx.tx.outputs) {
|
|
558
|
-
const script = output.lockingScript?.toBinary();
|
|
559
|
-
if (script && hasRunPrefix(script)) {
|
|
560
|
-
runTxids.add(txid);
|
|
561
|
-
break;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
} catch {}
|
|
565
|
-
}
|
|
566
|
-
return runTxids;
|
|
567
|
-
}
|
|
568
|
-
function hasRunPrefix(script) {
|
|
569
|
-
if (script.length < RUN_PREFIX.length)
|
|
570
|
-
return false;
|
|
571
|
-
for (let i = 0;i < RUN_PREFIX.length; i++) {
|
|
572
|
-
if (script[i] !== RUN_PREFIX[i])
|
|
573
|
-
return false;
|
|
574
|
-
}
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
424
|
|
|
578
425
|
// src/components/wif-input.tsx
|
|
579
426
|
import { deriveIdentityKey } from "@1sat/utils";
|
|
@@ -1395,63 +1242,72 @@ function OrdinalsSection({ ordinals, selectedOrdinals, onToggle, onSelectAll, on
|
|
|
1395
1242
|
]
|
|
1396
1243
|
});
|
|
1397
1244
|
}
|
|
1398
|
-
function TokenRow({ tb }) {
|
|
1399
|
-
return /* @__PURE__ */
|
|
1245
|
+
function TokenRow({ tb, onSweep, walletConnected }) {
|
|
1246
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1400
1247
|
className: `flex items-center justify-between p-3 rounded-lg border ${tb.isActive ? "bg-black/20 border-purple-500/10" : "bg-black/10 border-muted/20 opacity-60"}`,
|
|
1401
|
-
children:
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
e
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1248
|
+
children: [
|
|
1249
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1250
|
+
className: "flex items-center gap-3",
|
|
1251
|
+
children: [
|
|
1252
|
+
/* @__PURE__ */ jsx8("img", {
|
|
1253
|
+
src: tb.icon,
|
|
1254
|
+
alt: tb.symbol || "Token",
|
|
1255
|
+
className: "w-8 h-8 rounded-full object-cover",
|
|
1256
|
+
onError: (e) => {
|
|
1257
|
+
e.target.style.display = "none";
|
|
1258
|
+
}
|
|
1259
|
+
}),
|
|
1260
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1261
|
+
children: [
|
|
1262
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1263
|
+
className: "flex items-center gap-2",
|
|
1264
|
+
children: [
|
|
1265
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1266
|
+
className: "font-medium text-foreground",
|
|
1267
|
+
children: tb.symbol || tb.tokenId.slice(0, 8) + "..."
|
|
1268
|
+
}),
|
|
1269
|
+
tb.isActive ? /* @__PURE__ */ jsx8("span", {
|
|
1270
|
+
className: "px-1.5 py-0.5 text-[9px] rounded bg-green-600/20 text-green-700 dark:text-green-400",
|
|
1271
|
+
children: "active"
|
|
1272
|
+
}) : /* @__PURE__ */ jsx8("span", {
|
|
1273
|
+
className: "px-1.5 py-0.5 text-[9px] rounded bg-muted text-muted-foreground",
|
|
1274
|
+
children: "inactive"
|
|
1275
|
+
})
|
|
1276
|
+
]
|
|
1277
|
+
}),
|
|
1278
|
+
/* @__PURE__ */ jsxs3("div", {
|
|
1279
|
+
className: "text-xs text-muted-foreground",
|
|
1280
|
+
children: [
|
|
1281
|
+
formatTokenAmount(tb.totalAmount.toString(), tb.decimals),
|
|
1282
|
+
" ",
|
|
1283
|
+
tb.symbol || "",
|
|
1284
|
+
/* @__PURE__ */ jsxs3("span", {
|
|
1285
|
+
className: "ml-2",
|
|
1286
|
+
children: [
|
|
1287
|
+
"(",
|
|
1288
|
+
tb.outputs.length,
|
|
1289
|
+
" output",
|
|
1290
|
+
tb.outputs.length !== 1 ? "s" : "",
|
|
1291
|
+
")"
|
|
1292
|
+
]
|
|
1293
|
+
})
|
|
1294
|
+
]
|
|
1295
|
+
})
|
|
1296
|
+
]
|
|
1297
|
+
})
|
|
1298
|
+
]
|
|
1299
|
+
}),
|
|
1300
|
+
tb.isActive && onSweep && /* @__PURE__ */ jsx8(Button, {
|
|
1301
|
+
size: "sm",
|
|
1302
|
+
onClick: () => onSweep(tb.tokenId),
|
|
1303
|
+
disabled: !walletConnected,
|
|
1304
|
+
title: walletConnected ? undefined : "Connect BRC-100 wallet to sweep",
|
|
1305
|
+
children: "Sweep to Wallet"
|
|
1306
|
+
})
|
|
1307
|
+
]
|
|
1452
1308
|
});
|
|
1453
1309
|
}
|
|
1454
|
-
function Bsv21Section({ tokens }) {
|
|
1310
|
+
function Bsv21Section({ tokens, onSweep, walletConnected }) {
|
|
1455
1311
|
if (tokens.length === 0)
|
|
1456
1312
|
return null;
|
|
1457
1313
|
const active = tokens.filter((t) => t.isActive);
|
|
@@ -1475,7 +1331,9 @@ function Bsv21Section({ tokens }) {
|
|
|
1475
1331
|
className: "space-y-3",
|
|
1476
1332
|
children: [
|
|
1477
1333
|
active.map((tb) => /* @__PURE__ */ jsx8(TokenRow, {
|
|
1478
|
-
tb
|
|
1334
|
+
tb,
|
|
1335
|
+
onSweep,
|
|
1336
|
+
walletConnected
|
|
1479
1337
|
}, tb.tokenId)),
|
|
1480
1338
|
inactive.length > 0 && active.length > 0 && /* @__PURE__ */ jsx8("div", {
|
|
1481
1339
|
className: "border-t border-purple-500/10 pt-3 mt-3",
|
|
@@ -1489,7 +1347,8 @@ function Bsv21Section({ tokens }) {
|
|
|
1489
1347
|
})
|
|
1490
1348
|
}),
|
|
1491
1349
|
inactive.map((tb) => /* @__PURE__ */ jsx8(TokenRow, {
|
|
1492
|
-
tb
|
|
1350
|
+
tb,
|
|
1351
|
+
walletConnected
|
|
1493
1352
|
}, tb.tokenId))
|
|
1494
1353
|
]
|
|
1495
1354
|
})
|
|
@@ -1904,7 +1763,7 @@ function buildKeys(outputs, keyMap) {
|
|
|
1904
1763
|
});
|
|
1905
1764
|
}
|
|
1906
1765
|
async function executeSweep(params) {
|
|
1907
|
-
const { wallet, keys, funding, ordinals,
|
|
1766
|
+
const { wallet, keys, funding, ordinals, amount, onProgress } = params;
|
|
1908
1767
|
const ctx = createContext2(wallet, { services: getServices(), chain: "main" });
|
|
1909
1768
|
const result = {
|
|
1910
1769
|
ordinalTxids: [],
|
|
@@ -1937,42 +1796,31 @@ async function executeSweep(params) {
|
|
|
1937
1796
|
result.errors.push(`Ordinals: ${e instanceof Error ? e.message : String(e)}`);
|
|
1938
1797
|
}
|
|
1939
1798
|
}
|
|
1940
|
-
if (bsv21Tokens.length > 0) {
|
|
1941
|
-
const groups = new Map;
|
|
1942
|
-
for (const token of bsv21Tokens) {
|
|
1943
|
-
const tokenEvent = token.events?.find((e) => e.startsWith("tokenId:"));
|
|
1944
|
-
const tokenId = tokenEvent?.slice(8) ?? "unknown";
|
|
1945
|
-
const group = groups.get(tokenId) ?? [];
|
|
1946
|
-
group.push(token);
|
|
1947
|
-
groups.set(tokenId, group);
|
|
1948
|
-
}
|
|
1949
|
-
for (const [tokenId, tokens] of groups) {
|
|
1950
|
-
onProgress(`Sweeping ${tokens.length} tokens (${tokenId.slice(0, 8)}...)...`);
|
|
1951
|
-
try {
|
|
1952
|
-
const inputs = await prepareSweepInputs(ctx, tokens);
|
|
1953
|
-
const tokenResult = await sweepBsv21.execute(ctx, {
|
|
1954
|
-
inputs: inputs.map((inp) => ({
|
|
1955
|
-
...inp,
|
|
1956
|
-
tokenId,
|
|
1957
|
-
amount: "0"
|
|
1958
|
-
})),
|
|
1959
|
-
keys: buildKeys(tokens, keys)
|
|
1960
|
-
});
|
|
1961
|
-
if (tokenResult.error)
|
|
1962
|
-
result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${tokenResult.error}`);
|
|
1963
|
-
else if (tokenResult.txid)
|
|
1964
|
-
result.bsv21Txids.push(tokenResult.txid);
|
|
1965
|
-
} catch (e) {
|
|
1966
|
-
result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
1799
|
onProgress("Sweep complete");
|
|
1971
1800
|
return result;
|
|
1972
1801
|
}
|
|
1802
|
+
async function sweepBsv21Token(params) {
|
|
1803
|
+
const { wallet, keys, token, onProgress } = params;
|
|
1804
|
+
const ctx = createContext2(wallet, { services: getServices(), chain: "main" });
|
|
1805
|
+
onProgress(`Sweeping ${token.symbol ?? token.tokenId.slice(0, 8)}...`);
|
|
1806
|
+
try {
|
|
1807
|
+
const inputs = token.outputs.map((out) => ({
|
|
1808
|
+
outpoint: out.outpoint,
|
|
1809
|
+
tokenId: token.tokenId,
|
|
1810
|
+
amount: token.amounts.get(out.outpoint) ?? "0"
|
|
1811
|
+
}));
|
|
1812
|
+
const tokenKeys = buildKeys(token.outputs, keys);
|
|
1813
|
+
const result = await sweepBsv21.execute(ctx, { inputs, keys: tokenKeys });
|
|
1814
|
+
if (result.error)
|
|
1815
|
+
return { error: result.error };
|
|
1816
|
+
return { txid: result.txid };
|
|
1817
|
+
} catch (e) {
|
|
1818
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1973
1821
|
|
|
1974
1822
|
// src/lib/legacy-send.ts
|
|
1975
|
-
import { parseOutpoint
|
|
1823
|
+
import { parseOutpoint } from "@1sat/utils";
|
|
1976
1824
|
import { MAP_PREFIX } from "@1sat/types";
|
|
1977
1825
|
import { OP, P2PKH, PrivateKey as PrivateKey2, Script, Transaction, Utils } from "@bsv/sdk";
|
|
1978
1826
|
async function fetchSourceTx(txid) {
|
|
@@ -2019,7 +1867,7 @@ async function legacySendBsv(params) {
|
|
|
2019
1867
|
const p2pkh = new P2PKH;
|
|
2020
1868
|
const tx = new Transaction;
|
|
2021
1869
|
for (const utxo of funding) {
|
|
2022
|
-
const { txid, vout } =
|
|
1870
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
2023
1871
|
const key = keyForOutput(utxo, keyMap, payKey);
|
|
2024
1872
|
tx.addInput({
|
|
2025
1873
|
sourceTXID: txid,
|
|
@@ -2067,7 +1915,7 @@ async function legacySendOrdinals(params) {
|
|
|
2067
1915
|
const p2pkh = new P2PKH;
|
|
2068
1916
|
const tx = new Transaction;
|
|
2069
1917
|
for (const ord of ordinals) {
|
|
2070
|
-
const { txid, vout } =
|
|
1918
|
+
const { txid, vout } = parseOutpoint(ord.outpoint);
|
|
2071
1919
|
const key = keyForOutput(ord, keyMap, payKey);
|
|
2072
1920
|
tx.addInput({
|
|
2073
1921
|
sourceTXID: txid,
|
|
@@ -2084,7 +1932,7 @@ async function legacySendOrdinals(params) {
|
|
|
2084
1932
|
});
|
|
2085
1933
|
}
|
|
2086
1934
|
for (const utxo of funding) {
|
|
2087
|
-
const { txid, vout } =
|
|
1935
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
2088
1936
|
const key = keyForOutput(utxo, keyMap, payKey);
|
|
2089
1937
|
tx.addInput({
|
|
2090
1938
|
sourceTXID: txid,
|
|
@@ -2117,7 +1965,7 @@ async function legacyBurnOrdinals(params) {
|
|
|
2117
1965
|
const p2pkh = new P2PKH;
|
|
2118
1966
|
const tx = new Transaction;
|
|
2119
1967
|
for (const ord of ordinals) {
|
|
2120
|
-
const { txid, vout } =
|
|
1968
|
+
const { txid, vout } = parseOutpoint(ord.outpoint);
|
|
2121
1969
|
const key = keyForOutput(ord, keyMap, payKey);
|
|
2122
1970
|
tx.addInput({
|
|
2123
1971
|
sourceTXID: txid,
|
|
@@ -2128,7 +1976,7 @@ async function legacyBurnOrdinals(params) {
|
|
|
2128
1976
|
});
|
|
2129
1977
|
}
|
|
2130
1978
|
for (const utxo of funding) {
|
|
2131
|
-
const { txid, vout } =
|
|
1979
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
2132
1980
|
const key = keyForOutput(utxo, keyMap, payKey);
|
|
2133
1981
|
tx.addInput({
|
|
2134
1982
|
sourceTXID: txid,
|
|
@@ -2319,7 +2167,7 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2319
2167
|
if (!wallet || !legacyKeys || !assets)
|
|
2320
2168
|
return;
|
|
2321
2169
|
await runOperation("Sweep BSV", async () => {
|
|
2322
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: getSelectedFunding(), ordinals: [],
|
|
2170
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: getSelectedFunding(), ordinals: [], amount: sweepAmount ?? undefined, onProgress: setSweepProgress });
|
|
2323
2171
|
if (result.errors.length > 0)
|
|
2324
2172
|
throw new Error(result.errors[0]);
|
|
2325
2173
|
return result.bsvTxid ?? "";
|
|
@@ -2341,7 +2189,7 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2341
2189
|
if (selected.length === 0)
|
|
2342
2190
|
return;
|
|
2343
2191
|
await runOperation(`Sweep ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2344
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected,
|
|
2192
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected, onProgress: setSweepProgress });
|
|
2345
2193
|
if (result.errors.length > 0)
|
|
2346
2194
|
throw new Error(result.errors[0]);
|
|
2347
2195
|
return result.ordinalTxids[0] ?? "";
|
|
@@ -2377,7 +2225,7 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2377
2225
|
if (selected.length === 0)
|
|
2378
2226
|
return;
|
|
2379
2227
|
await runOperation(`Sweep ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2380
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected,
|
|
2228
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected, onProgress: setSweepProgress });
|
|
2381
2229
|
if (result.errors.length > 0)
|
|
2382
2230
|
throw new Error(result.errors[0]);
|
|
2383
2231
|
return result.ordinalTxids[0] ?? "";
|
|
@@ -2405,6 +2253,20 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2405
2253
|
return result.txid;
|
|
2406
2254
|
});
|
|
2407
2255
|
}, [legacyKeys, assets, selectedOpns, runOperation]);
|
|
2256
|
+
const handleSweepBsv21Token = useCallback2(async (tokenId) => {
|
|
2257
|
+
const wallet = resolveWallet();
|
|
2258
|
+
if (!wallet || !assets)
|
|
2259
|
+
return;
|
|
2260
|
+
const token = assets.bsv21Tokens.find((t) => t.tokenId === tokenId);
|
|
2261
|
+
if (!token)
|
|
2262
|
+
return;
|
|
2263
|
+
await runOperation(`Sweep ${token.symbol ?? tokenId.slice(0, 8)}`, async () => {
|
|
2264
|
+
const result = await sweepBsv21Token({ wallet, keys: keyMap, token, onProgress: setSweepProgress });
|
|
2265
|
+
if (result.error)
|
|
2266
|
+
throw new Error(result.error);
|
|
2267
|
+
return result.txid ?? "";
|
|
2268
|
+
});
|
|
2269
|
+
}, [resolveWallet, assets, keyMap, runOperation]);
|
|
2408
2270
|
return /* @__PURE__ */ jsxs6("div", {
|
|
2409
2271
|
className: "min-h-screen bg-background text-foreground",
|
|
2410
2272
|
children: [
|
|
@@ -2503,7 +2365,9 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2503
2365
|
/* @__PURE__ */ jsx11(TabsContent, {
|
|
2504
2366
|
value: "bsv21",
|
|
2505
2367
|
children: /* @__PURE__ */ jsx11(Bsv21Section, {
|
|
2506
|
-
tokens: assets.bsv21Tokens
|
|
2368
|
+
tokens: assets.bsv21Tokens,
|
|
2369
|
+
onSweep: handleSweepBsv21Token,
|
|
2370
|
+
walletConnected
|
|
2507
2371
|
})
|
|
2508
2372
|
}),
|
|
2509
2373
|
/* @__PURE__ */ jsx11(TabsContent, {
|
package/dist/lib/scanner.d.ts
CHANGED
|
@@ -1,33 +1,18 @@
|
|
|
1
1
|
import type { IndexedOutput } from "@1sat/types";
|
|
2
|
+
import { type ScanResult, type ScanProgress, type TokenBalance } from "@1sat/actions";
|
|
3
|
+
export type { TokenBalance, ScanProgress, ScanResult };
|
|
2
4
|
export interface EnrichedOrdinal extends IndexedOutput {
|
|
3
5
|
origin?: string;
|
|
4
6
|
contentType?: string;
|
|
5
7
|
name?: string;
|
|
6
8
|
contentUrl: string;
|
|
7
9
|
}
|
|
8
|
-
export interface
|
|
9
|
-
tokenId: string;
|
|
10
|
-
symbol?: string;
|
|
11
|
-
icon: string;
|
|
12
|
-
decimals: number;
|
|
13
|
-
totalAmount: bigint;
|
|
14
|
-
outputs: IndexedOutput[];
|
|
15
|
-
isActive: boolean;
|
|
16
|
-
}
|
|
17
|
-
export interface ScannedAssets {
|
|
18
|
-
funding: IndexedOutput[];
|
|
10
|
+
export interface ScannedAssets extends Omit<ScanResult, "ordinals" | "opnsNames"> {
|
|
19
11
|
ordinals: EnrichedOrdinal[];
|
|
20
12
|
opnsNames: EnrichedOrdinal[];
|
|
21
13
|
bsv21Tokens: TokenBalance[];
|
|
22
|
-
bsv20Tokens: IndexedOutput[];
|
|
23
|
-
locked: IndexedOutput[];
|
|
24
|
-
run: IndexedOutput[];
|
|
25
14
|
totalBsv: number;
|
|
26
15
|
}
|
|
27
|
-
export interface ScanProgress {
|
|
28
|
-
phase: string;
|
|
29
|
-
detail?: string;
|
|
30
|
-
}
|
|
31
16
|
export declare function deriveAddress(wif: string): string;
|
|
32
17
|
export declare function scanAddress(address: string, onProgress?: (p: ScanProgress) => void): Promise<ScannedAssets>;
|
|
33
18
|
export declare function scanAddresses(addresses: string[], onProgress?: (p: ScanProgress) => void): Promise<ScannedAssets>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAEN,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;AAEvD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,CAAC;IAChF,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAuCD,wBAAsB,WAAW,CAChC,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CAGxB;AAED,wBAAsB,aAAa,CAClC,SAAS,EAAE,MAAM,EAAE,EACnB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GACpC,OAAO,CAAC,aAAa,CAAC,CAIxB"}
|
package/dist/lib/sweeper.d.ts
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
import type { IndexedOutput } from "@1sat/types";
|
|
2
2
|
import { PrivateKey, type WalletInterface } from "@bsv/sdk";
|
|
3
|
+
import type { TokenBalance } from "./scanner";
|
|
3
4
|
export interface SweepResult {
|
|
4
5
|
bsvTxid?: string;
|
|
5
6
|
ordinalTxids: string[];
|
|
6
7
|
bsv21Txids: string[];
|
|
7
8
|
errors: string[];
|
|
8
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Sweep BSV funding and ordinals into the connected wallet.
|
|
12
|
+
*/
|
|
9
13
|
export declare function executeSweep(params: {
|
|
10
14
|
wallet: WalletInterface;
|
|
11
15
|
keys: Map<string, PrivateKey>;
|
|
12
16
|
funding: IndexedOutput[];
|
|
13
17
|
ordinals: IndexedOutput[];
|
|
14
|
-
bsv21Tokens: IndexedOutput[];
|
|
15
18
|
amount?: number;
|
|
16
19
|
onProgress: (stage: string) => void;
|
|
17
20
|
}): Promise<SweepResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Sweep a single BSV-21 token into the connected wallet.
|
|
23
|
+
* Each token requires its own transaction since all inputs must share a tokenId.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sweepBsv21Token(params: {
|
|
26
|
+
wallet: WalletInterface;
|
|
27
|
+
keys: Map<string, PrivateKey>;
|
|
28
|
+
token: TokenBalance;
|
|
29
|
+
onProgress: (stage: string) => void;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
txid?: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
}>;
|
|
18
34
|
//# sourceMappingURL=sweeper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sweeper.d.ts","sourceRoot":"","sources":["../../src/lib/sweeper.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"sweeper.d.ts","sourceRoot":"","sources":["../../src/lib/sweeper.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,MAAM,WAAW,WAAW;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAeD;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAC1C,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,GAAG,OAAO,CAAC,WAAW,CAAC,CAoCvB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9B,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,GAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAqB7C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1sat/sweep-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "Sweep UI components for migrating legacy BSV assets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
"keywords": ["1sat", "bsv", "ordinals", "sweep", "migration", "react"],
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@1sat/actions": "0.0.
|
|
22
|
+
"@1sat/actions": "0.0.79",
|
|
23
23
|
"@1sat/client": "0.0.20",
|
|
24
24
|
"@1sat/connect": "0.0.27",
|
|
25
|
-
"@1sat/sweep-ui": "0.0.
|
|
25
|
+
"@1sat/sweep-ui": "0.0.17",
|
|
26
26
|
"@1sat/types": "0.0.14",
|
|
27
27
|
"@1sat/utils": "0.0.12",
|
|
28
28
|
"bitcoin-backup": "^0.0.11",
|
|
@@ -8,7 +8,7 @@ import { FundingSection, OrdinalsSection, Bsv21Section, Bsv20Section, LockedSect
|
|
|
8
8
|
import { OpnsSection } from "./opns-section";
|
|
9
9
|
import { TxHistory, type TxRecord } from "./tx-history";
|
|
10
10
|
import { deriveAddress, scanAddresses, type ScannedAssets } from "../lib/scanner";
|
|
11
|
-
import { executeSweep } from "../lib/sweeper";
|
|
11
|
+
import { executeSweep, sweepBsv21Token } from "../lib/sweeper";
|
|
12
12
|
import { legacySendBsv, legacySendOrdinals, legacyBurnOrdinals } from "../lib/legacy-send";
|
|
13
13
|
import { getWallet } from "../lib/wallet";
|
|
14
14
|
import type { LegacyKeys } from "../types";
|
|
@@ -160,7 +160,7 @@ export function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, swee
|
|
|
160
160
|
const wallet = resolveWallet();
|
|
161
161
|
if (!wallet || !legacyKeys || !assets) return;
|
|
162
162
|
await runOperation("Sweep BSV", async () => {
|
|
163
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: getSelectedFunding(), ordinals: [],
|
|
163
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: getSelectedFunding(), ordinals: [], amount: sweepAmount ?? undefined, onProgress: setSweepProgress });
|
|
164
164
|
if (result.errors.length > 0) throw new Error(result.errors[0]);
|
|
165
165
|
return result.bsvTxid ?? "";
|
|
166
166
|
});
|
|
@@ -180,7 +180,7 @@ export function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, swee
|
|
|
180
180
|
const selected = assets.ordinals.filter((o) => selectedOrdinals.has(o.outpoint));
|
|
181
181
|
if (selected.length === 0) return;
|
|
182
182
|
await runOperation(`Sweep ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
183
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected,
|
|
183
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected, onProgress: setSweepProgress });
|
|
184
184
|
if (result.errors.length > 0) throw new Error(result.errors[0]);
|
|
185
185
|
return result.ordinalTxids[0] ?? "";
|
|
186
186
|
});
|
|
@@ -212,7 +212,7 @@ export function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, swee
|
|
|
212
212
|
const selected = assets.opnsNames.filter((o) => selectedOpns.has(o.outpoint));
|
|
213
213
|
if (selected.length === 0) return;
|
|
214
214
|
await runOperation(`Sweep ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
215
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected,
|
|
215
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected, onProgress: setSweepProgress });
|
|
216
216
|
if (result.errors.length > 0) throw new Error(result.errors[0]);
|
|
217
217
|
return result.ordinalTxids[0] ?? "";
|
|
218
218
|
});
|
|
@@ -238,6 +238,18 @@ export function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, swee
|
|
|
238
238
|
});
|
|
239
239
|
}, [legacyKeys, assets, selectedOpns, runOperation]);
|
|
240
240
|
|
|
241
|
+
const handleSweepBsv21Token = useCallback(async (tokenId: string) => {
|
|
242
|
+
const wallet = resolveWallet();
|
|
243
|
+
if (!wallet || !assets) return;
|
|
244
|
+
const token = assets.bsv21Tokens.find((t) => t.tokenId === tokenId);
|
|
245
|
+
if (!token) return;
|
|
246
|
+
await runOperation(`Sweep ${token.symbol ?? tokenId.slice(0, 8)}`, async () => {
|
|
247
|
+
const result = await sweepBsv21Token({ wallet, keys: keyMap, token, onProgress: setSweepProgress });
|
|
248
|
+
if (result.error) throw new Error(result.error);
|
|
249
|
+
return result.txid ?? "";
|
|
250
|
+
});
|
|
251
|
+
}, [resolveWallet, assets, keyMap, runOperation]);
|
|
252
|
+
|
|
241
253
|
return (
|
|
242
254
|
<div className="min-h-screen bg-background text-foreground">
|
|
243
255
|
<Toaster position="top-right" />
|
|
@@ -279,7 +291,7 @@ export function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, swee
|
|
|
279
291
|
<TabsContent value="opns">
|
|
280
292
|
<OpnsSection opnsNames={assets.opnsNames} selectedOpns={selectedOpns} onToggle={handleToggleOpns} onSelectAll={handleSelectAllOpns} onDeselectAll={handleDeselectAllOpns} onSweep={handleSweepOpns} onSend={sweepOnly ? undefined : handleSendOpns} onBurn={sweepOnly ? undefined : handleBurnOpns} walletConnected={walletConnected} />
|
|
281
293
|
</TabsContent>
|
|
282
|
-
<TabsContent value="bsv21"><Bsv21Section tokens={assets.bsv21Tokens} /></TabsContent>
|
|
294
|
+
<TabsContent value="bsv21"><Bsv21Section tokens={assets.bsv21Tokens} onSweep={handleSweepBsv21Token} walletConnected={walletConnected} /></TabsContent>
|
|
283
295
|
<TabsContent value="bsv20"><Bsv20Section tokens={assets.bsv20Tokens} /></TabsContent>
|
|
284
296
|
<TabsContent value="locks"><LockedSection locked={assets.locked} /></TabsContent>
|
|
285
297
|
<TabsContent value="run"><RunSection run={assets.run} /></TabsContent>
|
|
@@ -149,7 +149,7 @@ export function OrdinalsSection({ ordinals, selectedOrdinals, onToggle, onSelect
|
|
|
149
149
|
);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function TokenRow({ tb }: { tb: TokenBalance }) {
|
|
152
|
+
function TokenRow({ tb, onSweep, walletConnected }: { tb: TokenBalance; onSweep?: (tokenId: string) => void; walletConnected: boolean }) {
|
|
153
153
|
return (
|
|
154
154
|
<div className={`flex items-center justify-between p-3 rounded-lg border ${tb.isActive ? "bg-black/20 border-purple-500/10" : "bg-black/10 border-muted/20 opacity-60"}`}>
|
|
155
155
|
<div className="flex items-center gap-3">
|
|
@@ -169,11 +169,16 @@ function TokenRow({ tb }: { tb: TokenBalance }) {
|
|
|
169
169
|
</div>
|
|
170
170
|
</div>
|
|
171
171
|
</div>
|
|
172
|
+
{tb.isActive && onSweep && (
|
|
173
|
+
<Button size="sm" onClick={() => onSweep(tb.tokenId)} disabled={!walletConnected} title={walletConnected ? undefined : "Connect BRC-100 wallet to sweep"}>
|
|
174
|
+
Sweep to Wallet
|
|
175
|
+
</Button>
|
|
176
|
+
)}
|
|
172
177
|
</div>
|
|
173
178
|
);
|
|
174
179
|
}
|
|
175
180
|
|
|
176
|
-
export function Bsv21Section({ tokens }: { tokens: TokenBalance[] }) {
|
|
181
|
+
export function Bsv21Section({ tokens, onSweep, walletConnected }: { tokens: TokenBalance[]; onSweep?: (tokenId: string) => void; walletConnected: boolean }) {
|
|
177
182
|
if (tokens.length === 0) return null;
|
|
178
183
|
const active = tokens.filter((t) => t.isActive);
|
|
179
184
|
const inactive = tokens.filter((t) => !t.isActive);
|
|
@@ -185,13 +190,13 @@ export function Bsv21Section({ tokens }: { tokens: TokenBalance[] }) {
|
|
|
185
190
|
<span className="text-sm font-semibold text-purple-500">BSV-21 Tokens</span>
|
|
186
191
|
</div>
|
|
187
192
|
<div className="space-y-3">
|
|
188
|
-
{active.map((tb) => (<TokenRow key={tb.tokenId} tb={tb} />))}
|
|
193
|
+
{active.map((tb) => (<TokenRow key={tb.tokenId} tb={tb} onSweep={onSweep} walletConnected={walletConnected} />))}
|
|
189
194
|
{inactive.length > 0 && active.length > 0 && (
|
|
190
195
|
<div className="border-t border-purple-500/10 pt-3 mt-3">
|
|
191
196
|
<div className="text-xs text-muted-foreground mb-2">Inactive overlays ({inactive.length}) — cannot be swept</div>
|
|
192
197
|
</div>
|
|
193
198
|
)}
|
|
194
|
-
{inactive.map((tb) => (<TokenRow key={tb.tokenId} tb={tb} />))}
|
|
199
|
+
{inactive.map((tb) => (<TokenRow key={tb.tokenId} tb={tb} walletConnected={walletConnected} />))}
|
|
195
200
|
</div>
|
|
196
201
|
</div>
|
|
197
202
|
);
|
package/src/lib/scanner.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { PrivateKey } from "@bsv/sdk";
|
|
2
2
|
import type { IndexedOutput } from "@1sat/types";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
scanAddresses as coreScanAddresses,
|
|
5
|
+
type ScanResult,
|
|
6
|
+
type ScanProgress,
|
|
7
|
+
type TokenBalance,
|
|
8
|
+
} from "@1sat/actions";
|
|
4
9
|
import { getServices } from "./services";
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
const RUN_PREFIX = Uint8Array.from([0x00, 0x6a, 0x03, 0x72, 0x75, 0x6e]);
|
|
11
|
+
export type { TokenBalance, ScanProgress, ScanResult };
|
|
8
12
|
|
|
9
13
|
export interface EnrichedOrdinal extends IndexedOutput {
|
|
10
14
|
origin?: string;
|
|
@@ -13,38 +17,19 @@ export interface EnrichedOrdinal extends IndexedOutput {
|
|
|
13
17
|
contentUrl: string;
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
export interface
|
|
17
|
-
tokenId: string;
|
|
18
|
-
symbol?: string;
|
|
19
|
-
icon: string;
|
|
20
|
-
decimals: number;
|
|
21
|
-
totalAmount: bigint;
|
|
22
|
-
outputs: IndexedOutput[];
|
|
23
|
-
isActive: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ScannedAssets {
|
|
27
|
-
funding: IndexedOutput[];
|
|
20
|
+
export interface ScannedAssets extends Omit<ScanResult, "ordinals" | "opnsNames"> {
|
|
28
21
|
ordinals: EnrichedOrdinal[];
|
|
29
22
|
opnsNames: EnrichedOrdinal[];
|
|
30
23
|
bsv21Tokens: TokenBalance[];
|
|
31
|
-
bsv20Tokens: IndexedOutput[];
|
|
32
|
-
locked: IndexedOutput[];
|
|
33
|
-
run: IndexedOutput[];
|
|
34
24
|
totalBsv: number;
|
|
35
25
|
}
|
|
36
26
|
|
|
37
|
-
export interface ScanProgress {
|
|
38
|
-
phase: string;
|
|
39
|
-
detail?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
27
|
export function deriveAddress(wif: string): string {
|
|
43
28
|
return PrivateKey.fromWif(wif.trim()).toPublicKey().toAddress();
|
|
44
29
|
}
|
|
45
30
|
|
|
46
31
|
function getEvent(events: string[], prefix: string): string | undefined {
|
|
47
|
-
const e = events.find((
|
|
32
|
+
const e = events.find((ev) => ev.startsWith(prefix));
|
|
48
33
|
return e ? e.slice(prefix.length) : undefined;
|
|
49
34
|
}
|
|
50
35
|
|
|
@@ -63,182 +48,29 @@ function enrichOrdinal(out: IndexedOutput): EnrichedOrdinal {
|
|
|
63
48
|
return { ...out, origin, contentType, name, contentUrl };
|
|
64
49
|
}
|
|
65
50
|
|
|
66
|
-
function
|
|
67
|
-
if (!icon) return
|
|
51
|
+
function resolveIconUrl(tokenId: string, icon?: string): string {
|
|
52
|
+
if (!icon) return "";
|
|
53
|
+
let outpoint = icon;
|
|
68
54
|
if (icon.startsWith("_")) {
|
|
69
55
|
const txid = tokenId.split("_")[0];
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
return icon;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function groupBsv21Tokens(outputs: IndexedOutput[]): Promise<TokenBalance[]> {
|
|
76
|
-
// Group outputs by token ID from general indexer events
|
|
77
|
-
const groups = new Map<string, IndexedOutput[]>();
|
|
78
|
-
|
|
79
|
-
for (const out of outputs) {
|
|
80
|
-
const events = out.events ?? [];
|
|
81
|
-
const tokenId = getEvent(events, "bsv21:");
|
|
82
|
-
if (!tokenId) continue;
|
|
83
|
-
|
|
84
|
-
let group = groups.get(tokenId);
|
|
85
|
-
if (!group) {
|
|
86
|
-
group = [];
|
|
87
|
-
groups.set(tokenId, group);
|
|
88
|
-
}
|
|
89
|
-
group.push(out);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (groups.size === 0) return [];
|
|
93
|
-
|
|
94
|
-
const services = getServices();
|
|
95
|
-
const tokenIds = [...groups.keys()];
|
|
96
|
-
|
|
97
|
-
// Get token metadata and active status from overlay
|
|
98
|
-
let details: Array<{ tokenId: string; token?: { sym?: string; dec?: string; icon?: string }; status?: { is_active?: boolean } }> = [];
|
|
99
|
-
try {
|
|
100
|
-
details = await services.bsv21.lookupTokens(tokenIds);
|
|
101
|
-
} catch {
|
|
102
|
-
// BSV21 service may not be available
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const detailMap = new Map(details.map((d) => [d.tokenId, d]));
|
|
106
|
-
|
|
107
|
-
const balances: TokenBalance[] = [];
|
|
108
|
-
for (const [tokenId, outs] of groups) {
|
|
109
|
-
const detail = detailMap.get(tokenId);
|
|
110
|
-
const isActive = detail?.status?.is_active ?? false;
|
|
111
|
-
const iconOutpoint = resolveIconOutpoint(tokenId, detail?.token?.icon);
|
|
112
|
-
|
|
113
|
-
let totalAmount = 0n;
|
|
114
|
-
let validatedOutputs = outs;
|
|
115
|
-
|
|
116
|
-
// For active tokens, validate outputs against the overlay to get real amounts
|
|
117
|
-
if (isActive) {
|
|
118
|
-
try {
|
|
119
|
-
const outpoints = outs.map((o) => o.outpoint);
|
|
120
|
-
const validated = await services.bsv21.validateOutputs(tokenId, outpoints, { unspent: true });
|
|
121
|
-
totalAmount = validated.reduce((sum, v) => {
|
|
122
|
-
const bsv21 = v.data?.bsv21 as { amt?: string } | undefined;
|
|
123
|
-
return sum + (bsv21?.amt ? BigInt(bsv21.amt) : 0n);
|
|
124
|
-
}, 0n);
|
|
125
|
-
validatedOutputs = validated;
|
|
126
|
-
} catch {
|
|
127
|
-
// Validation failed — show outputs without amounts
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
balances.push({
|
|
132
|
-
tokenId,
|
|
133
|
-
symbol: detail?.token?.sym,
|
|
134
|
-
icon: iconOutpoint ? services.ordfs.getContentUrl(iconOutpoint) : "",
|
|
135
|
-
decimals: Number(detail?.token?.dec ?? 0),
|
|
136
|
-
totalAmount,
|
|
137
|
-
outputs: validatedOutputs,
|
|
138
|
-
isActive,
|
|
139
|
-
});
|
|
56
|
+
outpoint = `${txid}${icon}`;
|
|
140
57
|
}
|
|
141
|
-
return
|
|
58
|
+
return getServices().ordfs.getContentUrl(outpoint);
|
|
142
59
|
}
|
|
143
60
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const bsv20Tokens: IndexedOutput[] = [];
|
|
150
|
-
const locked: IndexedOutput[] = [];
|
|
151
|
-
|
|
152
|
-
for (const out of outputs) {
|
|
153
|
-
const events = out.events ?? [];
|
|
154
|
-
const sats = out.satoshis ?? 0;
|
|
155
|
-
|
|
156
|
-
if (events.some((e) => e.startsWith("bsv21:"))) {
|
|
157
|
-
bsv21Raw.push(out);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (events.some((e) => e.startsWith("lock:"))) {
|
|
162
|
-
locked.push(out);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (events.some((e) => e === "type:application/bsv-20" || e === "type:Token")) {
|
|
167
|
-
bsv20Tokens.push(out);
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (sats === 1) {
|
|
172
|
-
if (events.some((e) => e === "type:application/op-ns")) {
|
|
173
|
-
opnsRaw.push(out);
|
|
174
|
-
} else {
|
|
175
|
-
rawOrdinals.push(out);
|
|
176
|
-
}
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (sats > 1) {
|
|
181
|
-
funding.push(out);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Check funding outputs for RUN token transactions
|
|
186
|
-
const run: IndexedOutput[] = [];
|
|
187
|
-
const cleanFunding: IndexedOutput[] = [];
|
|
188
|
-
|
|
189
|
-
if (funding.length > 0) {
|
|
190
|
-
const runTxids = await detectRunTransactions(funding);
|
|
191
|
-
for (const f of funding) {
|
|
192
|
-
const { txid } = parseOutpoint(f.outpoint);
|
|
193
|
-
if (runTxids.has(txid)) {
|
|
194
|
-
run.push(f);
|
|
195
|
-
} else {
|
|
196
|
-
cleanFunding.push(f);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
funding: cleanFunding,
|
|
203
|
-
ordinals: rawOrdinals.map(enrichOrdinal),
|
|
204
|
-
opnsNames: opnsRaw.map(enrichOrdinal),
|
|
205
|
-
bsv21Tokens: await groupBsv21Tokens(bsv21Raw),
|
|
206
|
-
bsv20Tokens,
|
|
207
|
-
locked,
|
|
208
|
-
run,
|
|
209
|
-
totalBsv: cleanFunding.reduce((sum, o) => sum + (o.satoshis ?? 0), 0),
|
|
210
|
-
};
|
|
61
|
+
function enrichTokenBalances(tokens: TokenBalance[]): TokenBalance[] {
|
|
62
|
+
return tokens.map((t) => ({
|
|
63
|
+
...t,
|
|
64
|
+
icon: resolveIconUrl(t.tokenId, t.icon),
|
|
65
|
+
}));
|
|
211
66
|
}
|
|
212
67
|
|
|
213
68
|
export async function scanAddress(
|
|
214
69
|
address: string,
|
|
215
70
|
onProgress?: (p: ScanProgress) => void,
|
|
216
71
|
): Promise<ScannedAssets> {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
onProgress?.({ phase: "sync", detail: "Syncing address..." });
|
|
220
|
-
for await (const event of services.owner.getTxos(address, { refresh: true, limit: 1 })) {
|
|
221
|
-
if (event.type === "sync") {
|
|
222
|
-
const p = event.data;
|
|
223
|
-
onProgress?.({
|
|
224
|
-
phase: "sync",
|
|
225
|
-
detail: `${p.phase}: ${p.processed ?? 0}/${p.total ?? "?"}`,
|
|
226
|
-
});
|
|
227
|
-
} else if (event.type === "done" || event.type === "error") {
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
onProgress?.({ phase: "search", detail: "Searching for assets..." });
|
|
233
|
-
const allOutputs = await services.txo.search(`own:${address}`, {
|
|
234
|
-
unspent: true,
|
|
235
|
-
events: true,
|
|
236
|
-
sats: true,
|
|
237
|
-
limit: 0,
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
onProgress?.({ phase: "categorize", detail: "Loading token details..." });
|
|
241
|
-
return await categorizeOutputs(allOutputs ?? []);
|
|
72
|
+
const result = await coreScanAddresses(getServices(), [address], onProgress);
|
|
73
|
+
return toScannedAssets(result);
|
|
242
74
|
}
|
|
243
75
|
|
|
244
76
|
export async function scanAddresses(
|
|
@@ -246,59 +78,20 @@ export async function scanAddresses(
|
|
|
246
78
|
onProgress?: (p: ScanProgress) => void,
|
|
247
79
|
): Promise<ScannedAssets> {
|
|
248
80
|
const unique = [...new Set(addresses)];
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
onProgress?.({ phase: "sync", detail: `Scanning ${addr.slice(0, 8)}...` });
|
|
253
|
-
allResults.push(await scanAddress(addr, onProgress));
|
|
254
|
-
}
|
|
81
|
+
const result = await coreScanAddresses(getServices(), unique, onProgress);
|
|
82
|
+
return toScannedAssets(result);
|
|
83
|
+
}
|
|
255
84
|
|
|
85
|
+
function toScannedAssets(result: ScanResult): ScannedAssets {
|
|
256
86
|
return {
|
|
257
|
-
funding:
|
|
258
|
-
ordinals:
|
|
259
|
-
opnsNames:
|
|
260
|
-
bsv21Tokens:
|
|
261
|
-
bsv20Tokens:
|
|
262
|
-
locked:
|
|
263
|
-
run:
|
|
264
|
-
|
|
87
|
+
funding: result.funding,
|
|
88
|
+
ordinals: result.ordinals.map(enrichOrdinal),
|
|
89
|
+
opnsNames: result.opnsNames.map(enrichOrdinal),
|
|
90
|
+
bsv21Tokens: enrichTokenBalances(result.bsv21Tokens),
|
|
91
|
+
bsv20Tokens: result.bsv20Tokens,
|
|
92
|
+
locked: result.locked,
|
|
93
|
+
run: result.run,
|
|
94
|
+
totalFundingSats: result.totalFundingSats,
|
|
95
|
+
totalBsv: result.totalFundingSats,
|
|
265
96
|
};
|
|
266
97
|
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Check source transactions for the RUN protocol OP_RETURN pattern.
|
|
270
|
-
* Returns the set of txids that contain a RUN OP_RETURN output.
|
|
271
|
-
*/
|
|
272
|
-
async function detectRunTransactions(funding: IndexedOutput[]): Promise<Set<string>> {
|
|
273
|
-
const services = getServices();
|
|
274
|
-
const txids = [...new Set(funding.map((f) => parseOutpoint(f.outpoint).txid))];
|
|
275
|
-
const runTxids = new Set<string>();
|
|
276
|
-
|
|
277
|
-
for (const txid of txids) {
|
|
278
|
-
try {
|
|
279
|
-
const beef = await services.getBeefForTxid(txid);
|
|
280
|
-
const beefTx = beef.findTxid(txid);
|
|
281
|
-
if (!beefTx?.tx) continue;
|
|
282
|
-
|
|
283
|
-
for (const output of beefTx.tx.outputs) {
|
|
284
|
-
const script = output.lockingScript?.toBinary();
|
|
285
|
-
if (script && hasRunPrefix(script)) {
|
|
286
|
-
runTxids.add(txid);
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch {
|
|
291
|
-
// If we can't fetch the tx, leave the output in funding
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return runTxids;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function hasRunPrefix(script: number[]): boolean {
|
|
299
|
-
if (script.length < RUN_PREFIX.length) return false;
|
|
300
|
-
for (let i = 0; i < RUN_PREFIX.length; i++) {
|
|
301
|
-
if (script[i] !== RUN_PREFIX[i]) return false;
|
|
302
|
-
}
|
|
303
|
-
return true;
|
|
304
|
-
}
|
package/src/lib/sweeper.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "@1sat/actions";
|
|
8
8
|
import type { IndexedOutput } from "@1sat/types";
|
|
9
9
|
import { PrivateKey, type WalletInterface } from "@bsv/sdk";
|
|
10
|
+
import type { TokenBalance } from "./scanner";
|
|
10
11
|
import { getServices } from "./services";
|
|
11
12
|
|
|
12
13
|
export interface SweepResult {
|
|
@@ -29,16 +30,18 @@ function buildKeys(outputs: IndexedOutput[], keyMap: Map<string, PrivateKey>): P
|
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Sweep BSV funding and ordinals into the connected wallet.
|
|
35
|
+
*/
|
|
32
36
|
export async function executeSweep(params: {
|
|
33
37
|
wallet: WalletInterface;
|
|
34
38
|
keys: Map<string, PrivateKey>;
|
|
35
39
|
funding: IndexedOutput[];
|
|
36
40
|
ordinals: IndexedOutput[];
|
|
37
|
-
bsv21Tokens: IndexedOutput[];
|
|
38
41
|
amount?: number;
|
|
39
42
|
onProgress: (stage: string) => void;
|
|
40
43
|
}): Promise<SweepResult> {
|
|
41
|
-
const { wallet, keys, funding, ordinals,
|
|
44
|
+
const { wallet, keys, funding, ordinals, amount, onProgress } = params;
|
|
42
45
|
const ctx = createContext(wallet, { services: getServices(), chain: "main" });
|
|
43
46
|
|
|
44
47
|
const result: SweepResult = {
|
|
@@ -71,36 +74,38 @@ export async function executeSweep(params: {
|
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
if (bsv21Tokens.length > 0) {
|
|
75
|
-
const groups = new Map<string, IndexedOutput[]>();
|
|
76
|
-
for (const token of bsv21Tokens) {
|
|
77
|
-
const tokenEvent = token.events?.find((e) => e.startsWith("tokenId:"));
|
|
78
|
-
const tokenId = tokenEvent?.slice(8) ?? "unknown";
|
|
79
|
-
const group = groups.get(tokenId) ?? [];
|
|
80
|
-
group.push(token);
|
|
81
|
-
groups.set(tokenId, group);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
for (const [tokenId, tokens] of groups) {
|
|
85
|
-
onProgress(`Sweeping ${tokens.length} tokens (${tokenId.slice(0, 8)}...)...`);
|
|
86
|
-
try {
|
|
87
|
-
const inputs = await prepareSweepInputs(ctx, tokens);
|
|
88
|
-
const tokenResult = await sweepBsv21.execute(ctx, {
|
|
89
|
-
inputs: inputs.map((inp) => ({
|
|
90
|
-
...inp,
|
|
91
|
-
tokenId,
|
|
92
|
-
amount: "0",
|
|
93
|
-
})),
|
|
94
|
-
keys: buildKeys(tokens, keys),
|
|
95
|
-
});
|
|
96
|
-
if (tokenResult.error) result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${tokenResult.error}`);
|
|
97
|
-
else if (tokenResult.txid) result.bsv21Txids.push(tokenResult.txid);
|
|
98
|
-
} catch (e) {
|
|
99
|
-
result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${e instanceof Error ? e.message : String(e)}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
77
|
onProgress("Sweep complete");
|
|
105
78
|
return result;
|
|
106
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Sweep a single BSV-21 token into the connected wallet.
|
|
83
|
+
* Each token requires its own transaction since all inputs must share a tokenId.
|
|
84
|
+
*/
|
|
85
|
+
export async function sweepBsv21Token(params: {
|
|
86
|
+
wallet: WalletInterface;
|
|
87
|
+
keys: Map<string, PrivateKey>;
|
|
88
|
+
token: TokenBalance;
|
|
89
|
+
onProgress: (stage: string) => void;
|
|
90
|
+
}): Promise<{ txid?: string; error?: string }> {
|
|
91
|
+
const { wallet, keys, token, onProgress } = params;
|
|
92
|
+
const ctx = createContext(wallet, { services: getServices(), chain: "main" });
|
|
93
|
+
|
|
94
|
+
onProgress(`Sweeping ${token.symbol ?? token.tokenId.slice(0, 8)}...`);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const inputs = token.outputs.map((out) => ({
|
|
98
|
+
outpoint: out.outpoint,
|
|
99
|
+
tokenId: token.tokenId,
|
|
100
|
+
amount: token.amounts.get(out.outpoint) ?? "0",
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
const tokenKeys = buildKeys(token.outputs, keys);
|
|
104
|
+
|
|
105
|
+
const result = await sweepBsv21.execute(ctx, { inputs, keys: tokenKeys });
|
|
106
|
+
if (result.error) return { error: result.error };
|
|
107
|
+
return { txid: result.txid };
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
110
|
+
}
|
|
111
|
+
}
|