@1sat/sweep-ui 0.0.14 → 0.0.16
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 -269
- 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 -224
- 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,187 +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
|
-
const amtStr = getEvent(events, "amt:");
|
|
402
|
-
const amount = amtStr ? BigInt(amtStr) : 0n;
|
|
403
|
-
let group = groups.get(tokenId);
|
|
404
|
-
if (!group) {
|
|
405
|
-
group = { outputs: [], totalAmount: 0n };
|
|
406
|
-
groups.set(tokenId, group);
|
|
407
|
-
}
|
|
408
|
-
group.outputs.push(out);
|
|
409
|
-
group.totalAmount += amount;
|
|
410
|
-
}
|
|
411
|
-
if (groups.size === 0)
|
|
412
|
-
return [];
|
|
413
|
-
const services = getServices();
|
|
414
|
-
const tokenIds = [...groups.keys()];
|
|
415
|
-
let details = [];
|
|
416
|
-
try {
|
|
417
|
-
details = await services.bsv21.lookupTokens(tokenIds);
|
|
418
|
-
} catch {}
|
|
419
|
-
const detailMap = new Map(details.map((d) => [d.tokenId, d]));
|
|
420
|
-
const balances = [];
|
|
421
|
-
for (const [tokenId, group] of groups) {
|
|
422
|
-
const detail = detailMap.get(tokenId);
|
|
423
|
-
const iconOutpoint = resolveIconOutpoint(tokenId, detail?.token?.icon);
|
|
424
|
-
balances.push({
|
|
425
|
-
tokenId,
|
|
426
|
-
symbol: detail?.token?.sym,
|
|
427
|
-
icon: iconOutpoint ? services.ordfs.getContentUrl(iconOutpoint) : "",
|
|
428
|
-
decimals: Number(detail?.token?.dec ?? 0),
|
|
429
|
-
totalAmount: group.totalAmount,
|
|
430
|
-
outputs: group.outputs,
|
|
431
|
-
isActive: detail?.status?.is_active ?? false
|
|
432
|
-
});
|
|
392
|
+
outpoint = `${txid}${icon}`;
|
|
433
393
|
}
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const bsv20Tokens = [];
|
|
442
|
-
const locked = [];
|
|
443
|
-
for (const out of outputs) {
|
|
444
|
-
const events = out.events ?? [];
|
|
445
|
-
const sats = out.satoshis ?? 0;
|
|
446
|
-
if (events.some((e) => e.startsWith("bsv21:"))) {
|
|
447
|
-
bsv21Raw.push(out);
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
if (events.some((e) => e.startsWith("lock:"))) {
|
|
451
|
-
locked.push(out);
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
if (events.some((e) => e === "type:application/bsv-20" || e === "type:Token")) {
|
|
455
|
-
bsv20Tokens.push(out);
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
|
-
if (sats === 1) {
|
|
459
|
-
if (events.some((e) => e === "type:application/op-ns")) {
|
|
460
|
-
opnsRaw.push(out);
|
|
461
|
-
} else {
|
|
462
|
-
rawOrdinals.push(out);
|
|
463
|
-
}
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
466
|
-
if (sats > 1) {
|
|
467
|
-
funding.push(out);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
const run = [];
|
|
471
|
-
const cleanFunding = [];
|
|
472
|
-
if (funding.length > 0) {
|
|
473
|
-
const runTxids = await detectRunTransactions(funding);
|
|
474
|
-
for (const f of funding) {
|
|
475
|
-
const { txid } = parseOutpoint(f.outpoint);
|
|
476
|
-
if (runTxids.has(txid)) {
|
|
477
|
-
run.push(f);
|
|
478
|
-
} else {
|
|
479
|
-
cleanFunding.push(f);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return {
|
|
484
|
-
funding: cleanFunding,
|
|
485
|
-
ordinals: rawOrdinals.map(enrichOrdinal),
|
|
486
|
-
opnsNames: opnsRaw.map(enrichOrdinal),
|
|
487
|
-
bsv21Tokens: await groupBsv21Tokens(bsv21Raw),
|
|
488
|
-
bsv20Tokens,
|
|
489
|
-
locked,
|
|
490
|
-
run,
|
|
491
|
-
totalBsv: cleanFunding.reduce((sum, o) => sum + (o.satoshis ?? 0), 0)
|
|
492
|
-
};
|
|
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
|
+
}));
|
|
493
401
|
}
|
|
494
402
|
async function scanAddress(address, onProgress) {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
for await (const event of services.owner.getTxos(address, { refresh: true, limit: 1 })) {
|
|
498
|
-
if (event.type === "sync") {
|
|
499
|
-
const p = event.data;
|
|
500
|
-
onProgress?.({
|
|
501
|
-
phase: "sync",
|
|
502
|
-
detail: `${p.phase}: ${p.processed ?? 0}/${p.total ?? "?"}`
|
|
503
|
-
});
|
|
504
|
-
} else if (event.type === "done" || event.type === "error") {
|
|
505
|
-
break;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
onProgress?.({ phase: "search", detail: "Searching for assets..." });
|
|
509
|
-
const allOutputs = await services.txo.search(`own:${address}`, {
|
|
510
|
-
unspent: true,
|
|
511
|
-
events: true,
|
|
512
|
-
sats: true,
|
|
513
|
-
limit: 0
|
|
514
|
-
});
|
|
515
|
-
onProgress?.({ phase: "categorize", detail: "Loading token details..." });
|
|
516
|
-
return await categorizeOutputs(allOutputs ?? []);
|
|
403
|
+
const result = await coreScanAddresses(getServices(), [address], onProgress);
|
|
404
|
+
return toScannedAssets(result);
|
|
517
405
|
}
|
|
518
406
|
async function scanAddresses(addresses, onProgress) {
|
|
519
407
|
const unique = [...new Set(addresses)];
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
408
|
+
const result = await coreScanAddresses(getServices(), unique, onProgress);
|
|
409
|
+
return toScannedAssets(result);
|
|
410
|
+
}
|
|
411
|
+
function toScannedAssets(result) {
|
|
525
412
|
return {
|
|
526
|
-
funding:
|
|
527
|
-
ordinals:
|
|
528
|
-
opnsNames:
|
|
529
|
-
bsv21Tokens:
|
|
530
|
-
bsv20Tokens:
|
|
531
|
-
locked:
|
|
532
|
-
run:
|
|
533
|
-
|
|
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
|
|
534
422
|
};
|
|
535
423
|
}
|
|
536
|
-
async function detectRunTransactions(funding) {
|
|
537
|
-
const services = getServices();
|
|
538
|
-
const txids = [...new Set(funding.map((f) => parseOutpoint(f.outpoint).txid))];
|
|
539
|
-
const runTxids = new Set;
|
|
540
|
-
for (const txid of txids) {
|
|
541
|
-
try {
|
|
542
|
-
const beef = await services.getBeefForTxid(txid);
|
|
543
|
-
const beefTx = beef.findTxid(txid);
|
|
544
|
-
if (!beefTx?.tx)
|
|
545
|
-
continue;
|
|
546
|
-
for (const output of beefTx.tx.outputs) {
|
|
547
|
-
const script = output.lockingScript?.toBinary();
|
|
548
|
-
if (script && hasRunPrefix(script)) {
|
|
549
|
-
runTxids.add(txid);
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
} catch {}
|
|
554
|
-
}
|
|
555
|
-
return runTxids;
|
|
556
|
-
}
|
|
557
|
-
function hasRunPrefix(script) {
|
|
558
|
-
if (script.length < RUN_PREFIX.length)
|
|
559
|
-
return false;
|
|
560
|
-
for (let i = 0;i < RUN_PREFIX.length; i++) {
|
|
561
|
-
if (script[i] !== RUN_PREFIX[i])
|
|
562
|
-
return false;
|
|
563
|
-
}
|
|
564
|
-
return true;
|
|
565
|
-
}
|
|
566
424
|
|
|
567
425
|
// src/components/wif-input.tsx
|
|
568
426
|
import { deriveIdentityKey } from "@1sat/utils";
|
|
@@ -1384,63 +1242,72 @@ function OrdinalsSection({ ordinals, selectedOrdinals, onToggle, onSelectAll, on
|
|
|
1384
1242
|
]
|
|
1385
1243
|
});
|
|
1386
1244
|
}
|
|
1387
|
-
function TokenRow({ tb }) {
|
|
1388
|
-
return /* @__PURE__ */
|
|
1245
|
+
function TokenRow({ tb, onSweep, walletConnected }) {
|
|
1246
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
1389
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"}`,
|
|
1390
|
-
children:
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
e
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
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
|
-
|
|
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
|
+
]
|
|
1441
1308
|
});
|
|
1442
1309
|
}
|
|
1443
|
-
function Bsv21Section({ tokens }) {
|
|
1310
|
+
function Bsv21Section({ tokens, onSweep, walletConnected }) {
|
|
1444
1311
|
if (tokens.length === 0)
|
|
1445
1312
|
return null;
|
|
1446
1313
|
const active = tokens.filter((t) => t.isActive);
|
|
@@ -1464,7 +1331,9 @@ function Bsv21Section({ tokens }) {
|
|
|
1464
1331
|
className: "space-y-3",
|
|
1465
1332
|
children: [
|
|
1466
1333
|
active.map((tb) => /* @__PURE__ */ jsx8(TokenRow, {
|
|
1467
|
-
tb
|
|
1334
|
+
tb,
|
|
1335
|
+
onSweep,
|
|
1336
|
+
walletConnected
|
|
1468
1337
|
}, tb.tokenId)),
|
|
1469
1338
|
inactive.length > 0 && active.length > 0 && /* @__PURE__ */ jsx8("div", {
|
|
1470
1339
|
className: "border-t border-purple-500/10 pt-3 mt-3",
|
|
@@ -1478,7 +1347,8 @@ function Bsv21Section({ tokens }) {
|
|
|
1478
1347
|
})
|
|
1479
1348
|
}),
|
|
1480
1349
|
inactive.map((tb) => /* @__PURE__ */ jsx8(TokenRow, {
|
|
1481
|
-
tb
|
|
1350
|
+
tb,
|
|
1351
|
+
walletConnected
|
|
1482
1352
|
}, tb.tokenId))
|
|
1483
1353
|
]
|
|
1484
1354
|
})
|
|
@@ -1893,7 +1763,7 @@ function buildKeys(outputs, keyMap) {
|
|
|
1893
1763
|
});
|
|
1894
1764
|
}
|
|
1895
1765
|
async function executeSweep(params) {
|
|
1896
|
-
const { wallet, keys, funding, ordinals,
|
|
1766
|
+
const { wallet, keys, funding, ordinals, amount, onProgress } = params;
|
|
1897
1767
|
const ctx = createContext2(wallet, { services: getServices(), chain: "main" });
|
|
1898
1768
|
const result = {
|
|
1899
1769
|
ordinalTxids: [],
|
|
@@ -1926,42 +1796,31 @@ async function executeSweep(params) {
|
|
|
1926
1796
|
result.errors.push(`Ordinals: ${e instanceof Error ? e.message : String(e)}`);
|
|
1927
1797
|
}
|
|
1928
1798
|
}
|
|
1929
|
-
if (bsv21Tokens.length > 0) {
|
|
1930
|
-
const groups = new Map;
|
|
1931
|
-
for (const token of bsv21Tokens) {
|
|
1932
|
-
const tokenEvent = token.events?.find((e) => e.startsWith("tokenId:"));
|
|
1933
|
-
const tokenId = tokenEvent?.slice(8) ?? "unknown";
|
|
1934
|
-
const group = groups.get(tokenId) ?? [];
|
|
1935
|
-
group.push(token);
|
|
1936
|
-
groups.set(tokenId, group);
|
|
1937
|
-
}
|
|
1938
|
-
for (const [tokenId, tokens] of groups) {
|
|
1939
|
-
onProgress(`Sweeping ${tokens.length} tokens (${tokenId.slice(0, 8)}...)...`);
|
|
1940
|
-
try {
|
|
1941
|
-
const inputs = await prepareSweepInputs(ctx, tokens);
|
|
1942
|
-
const tokenResult = await sweepBsv21.execute(ctx, {
|
|
1943
|
-
inputs: inputs.map((inp) => ({
|
|
1944
|
-
...inp,
|
|
1945
|
-
tokenId,
|
|
1946
|
-
amount: "0"
|
|
1947
|
-
})),
|
|
1948
|
-
keys: buildKeys(tokens, keys)
|
|
1949
|
-
});
|
|
1950
|
-
if (tokenResult.error)
|
|
1951
|
-
result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${tokenResult.error}`);
|
|
1952
|
-
else if (tokenResult.txid)
|
|
1953
|
-
result.bsv21Txids.push(tokenResult.txid);
|
|
1954
|
-
} catch (e) {
|
|
1955
|
-
result.errors.push(`BSV-21 (${tokenId.slice(0, 8)}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
1799
|
onProgress("Sweep complete");
|
|
1960
1800
|
return result;
|
|
1961
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
|
+
}
|
|
1962
1821
|
|
|
1963
1822
|
// src/lib/legacy-send.ts
|
|
1964
|
-
import { parseOutpoint
|
|
1823
|
+
import { parseOutpoint } from "@1sat/utils";
|
|
1965
1824
|
import { MAP_PREFIX } from "@1sat/types";
|
|
1966
1825
|
import { OP, P2PKH, PrivateKey as PrivateKey2, Script, Transaction, Utils } from "@bsv/sdk";
|
|
1967
1826
|
async function fetchSourceTx(txid) {
|
|
@@ -2008,7 +1867,7 @@ async function legacySendBsv(params) {
|
|
|
2008
1867
|
const p2pkh = new P2PKH;
|
|
2009
1868
|
const tx = new Transaction;
|
|
2010
1869
|
for (const utxo of funding) {
|
|
2011
|
-
const { txid, vout } =
|
|
1870
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
2012
1871
|
const key = keyForOutput(utxo, keyMap, payKey);
|
|
2013
1872
|
tx.addInput({
|
|
2014
1873
|
sourceTXID: txid,
|
|
@@ -2056,7 +1915,7 @@ async function legacySendOrdinals(params) {
|
|
|
2056
1915
|
const p2pkh = new P2PKH;
|
|
2057
1916
|
const tx = new Transaction;
|
|
2058
1917
|
for (const ord of ordinals) {
|
|
2059
|
-
const { txid, vout } =
|
|
1918
|
+
const { txid, vout } = parseOutpoint(ord.outpoint);
|
|
2060
1919
|
const key = keyForOutput(ord, keyMap, payKey);
|
|
2061
1920
|
tx.addInput({
|
|
2062
1921
|
sourceTXID: txid,
|
|
@@ -2073,7 +1932,7 @@ async function legacySendOrdinals(params) {
|
|
|
2073
1932
|
});
|
|
2074
1933
|
}
|
|
2075
1934
|
for (const utxo of funding) {
|
|
2076
|
-
const { txid, vout } =
|
|
1935
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
2077
1936
|
const key = keyForOutput(utxo, keyMap, payKey);
|
|
2078
1937
|
tx.addInput({
|
|
2079
1938
|
sourceTXID: txid,
|
|
@@ -2106,7 +1965,7 @@ async function legacyBurnOrdinals(params) {
|
|
|
2106
1965
|
const p2pkh = new P2PKH;
|
|
2107
1966
|
const tx = new Transaction;
|
|
2108
1967
|
for (const ord of ordinals) {
|
|
2109
|
-
const { txid, vout } =
|
|
1968
|
+
const { txid, vout } = parseOutpoint(ord.outpoint);
|
|
2110
1969
|
const key = keyForOutput(ord, keyMap, payKey);
|
|
2111
1970
|
tx.addInput({
|
|
2112
1971
|
sourceTXID: txid,
|
|
@@ -2117,7 +1976,7 @@ async function legacyBurnOrdinals(params) {
|
|
|
2117
1976
|
});
|
|
2118
1977
|
}
|
|
2119
1978
|
for (const utxo of funding) {
|
|
2120
|
-
const { txid, vout } =
|
|
1979
|
+
const { txid, vout } = parseOutpoint(utxo.outpoint);
|
|
2121
1980
|
const key = keyForOutput(utxo, keyMap, payKey);
|
|
2122
1981
|
tx.addInput({
|
|
2123
1982
|
sourceTXID: txid,
|
|
@@ -2308,7 +2167,7 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2308
2167
|
if (!wallet || !legacyKeys || !assets)
|
|
2309
2168
|
return;
|
|
2310
2169
|
await runOperation("Sweep BSV", async () => {
|
|
2311
|
-
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 });
|
|
2312
2171
|
if (result.errors.length > 0)
|
|
2313
2172
|
throw new Error(result.errors[0]);
|
|
2314
2173
|
return result.bsvTxid ?? "";
|
|
@@ -2330,7 +2189,7 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2330
2189
|
if (selected.length === 0)
|
|
2331
2190
|
return;
|
|
2332
2191
|
await runOperation(`Sweep ${selected.length} ordinal${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2333
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected,
|
|
2192
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected, onProgress: setSweepProgress });
|
|
2334
2193
|
if (result.errors.length > 0)
|
|
2335
2194
|
throw new Error(result.errors[0]);
|
|
2336
2195
|
return result.ordinalTxids[0] ?? "";
|
|
@@ -2366,7 +2225,7 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2366
2225
|
if (selected.length === 0)
|
|
2367
2226
|
return;
|
|
2368
2227
|
await runOperation(`Sweep ${selected.length} domain${selected.length !== 1 ? "s" : ""}`, async () => {
|
|
2369
|
-
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected,
|
|
2228
|
+
const result = await executeSweep({ wallet, keys: keyMap, funding: [], ordinals: selected, onProgress: setSweepProgress });
|
|
2370
2229
|
if (result.errors.length > 0)
|
|
2371
2230
|
throw new Error(result.errors[0]);
|
|
2372
2231
|
return result.ordinalTxids[0] ?? "";
|
|
@@ -2394,6 +2253,20 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2394
2253
|
return result.txid;
|
|
2395
2254
|
});
|
|
2396
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]);
|
|
2397
2270
|
return /* @__PURE__ */ jsxs6("div", {
|
|
2398
2271
|
className: "min-h-screen bg-background text-foreground",
|
|
2399
2272
|
children: [
|
|
@@ -2492,7 +2365,9 @@ function SweepApp({ legacyKeys: initialKeys, wallet: externalWallet, sweepOnly }
|
|
|
2492
2365
|
/* @__PURE__ */ jsx11(TabsContent, {
|
|
2493
2366
|
value: "bsv21",
|
|
2494
2367
|
children: /* @__PURE__ */ jsx11(Bsv21Section, {
|
|
2495
|
-
tokens: assets.bsv21Tokens
|
|
2368
|
+
tokens: assets.bsv21Tokens,
|
|
2369
|
+
onSweep: handleSweepBsv21Token,
|
|
2370
|
+
walletConnected
|
|
2496
2371
|
})
|
|
2497
2372
|
}),
|
|
2498
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.16",
|
|
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.75",
|
|
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.16",
|
|
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,165 +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
|
-
|
|
56
|
+
outpoint = `${txid}${icon}`;
|
|
71
57
|
}
|
|
72
|
-
return
|
|
58
|
+
return getServices().ordfs.getContentUrl(outpoint);
|
|
73
59
|
}
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const tokenId = getEvent(events, "bsv21:");
|
|
81
|
-
if (!tokenId) continue;
|
|
82
|
-
|
|
83
|
-
const amtStr = getEvent(events, "amt:");
|
|
84
|
-
const amount = amtStr ? BigInt(amtStr) : 0n;
|
|
85
|
-
|
|
86
|
-
let group = groups.get(tokenId);
|
|
87
|
-
if (!group) {
|
|
88
|
-
group = { outputs: [], totalAmount: 0n };
|
|
89
|
-
groups.set(tokenId, group);
|
|
90
|
-
}
|
|
91
|
-
group.outputs.push(out);
|
|
92
|
-
group.totalAmount += amount;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (groups.size === 0) return [];
|
|
96
|
-
|
|
97
|
-
const services = getServices();
|
|
98
|
-
const tokenIds = [...groups.keys()];
|
|
99
|
-
|
|
100
|
-
let details: Array<{ tokenId: string; token?: { sym?: string; dec?: string; icon?: string }; status?: { is_active?: boolean } }> = [];
|
|
101
|
-
try {
|
|
102
|
-
details = await services.bsv21.lookupTokens(tokenIds);
|
|
103
|
-
} catch {
|
|
104
|
-
// BSV21 service may not be available
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const detailMap = new Map(details.map((d) => [d.tokenId, d]));
|
|
108
|
-
|
|
109
|
-
const balances: TokenBalance[] = [];
|
|
110
|
-
for (const [tokenId, group] of groups) {
|
|
111
|
-
const detail = detailMap.get(tokenId);
|
|
112
|
-
const iconOutpoint = resolveIconOutpoint(tokenId, detail?.token?.icon);
|
|
113
|
-
|
|
114
|
-
balances.push({
|
|
115
|
-
tokenId,
|
|
116
|
-
symbol: detail?.token?.sym,
|
|
117
|
-
icon: iconOutpoint ? services.ordfs.getContentUrl(iconOutpoint) : "",
|
|
118
|
-
decimals: Number(detail?.token?.dec ?? 0),
|
|
119
|
-
totalAmount: group.totalAmount,
|
|
120
|
-
outputs: group.outputs,
|
|
121
|
-
isActive: detail?.status?.is_active ?? false,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
return balances;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function categorizeOutputs(outputs: IndexedOutput[]): Promise<ScannedAssets> {
|
|
128
|
-
const funding: IndexedOutput[] = [];
|
|
129
|
-
const rawOrdinals: IndexedOutput[] = [];
|
|
130
|
-
const opnsRaw: IndexedOutput[] = [];
|
|
131
|
-
const bsv21Raw: IndexedOutput[] = [];
|
|
132
|
-
const bsv20Tokens: IndexedOutput[] = [];
|
|
133
|
-
const locked: IndexedOutput[] = [];
|
|
134
|
-
|
|
135
|
-
for (const out of outputs) {
|
|
136
|
-
const events = out.events ?? [];
|
|
137
|
-
const sats = out.satoshis ?? 0;
|
|
138
|
-
|
|
139
|
-
if (events.some((e) => e.startsWith("bsv21:"))) {
|
|
140
|
-
bsv21Raw.push(out);
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (events.some((e) => e.startsWith("lock:"))) {
|
|
145
|
-
locked.push(out);
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (events.some((e) => e === "type:application/bsv-20" || e === "type:Token")) {
|
|
150
|
-
bsv20Tokens.push(out);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (sats === 1) {
|
|
155
|
-
if (events.some((e) => e === "type:application/op-ns")) {
|
|
156
|
-
opnsRaw.push(out);
|
|
157
|
-
} else {
|
|
158
|
-
rawOrdinals.push(out);
|
|
159
|
-
}
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (sats > 1) {
|
|
164
|
-
funding.push(out);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Check funding outputs for RUN token transactions
|
|
169
|
-
const run: IndexedOutput[] = [];
|
|
170
|
-
const cleanFunding: IndexedOutput[] = [];
|
|
171
|
-
|
|
172
|
-
if (funding.length > 0) {
|
|
173
|
-
const runTxids = await detectRunTransactions(funding);
|
|
174
|
-
for (const f of funding) {
|
|
175
|
-
const { txid } = parseOutpoint(f.outpoint);
|
|
176
|
-
if (runTxids.has(txid)) {
|
|
177
|
-
run.push(f);
|
|
178
|
-
} else {
|
|
179
|
-
cleanFunding.push(f);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
funding: cleanFunding,
|
|
186
|
-
ordinals: rawOrdinals.map(enrichOrdinal),
|
|
187
|
-
opnsNames: opnsRaw.map(enrichOrdinal),
|
|
188
|
-
bsv21Tokens: await groupBsv21Tokens(bsv21Raw),
|
|
189
|
-
bsv20Tokens,
|
|
190
|
-
locked,
|
|
191
|
-
run,
|
|
192
|
-
totalBsv: cleanFunding.reduce((sum, o) => sum + (o.satoshis ?? 0), 0),
|
|
193
|
-
};
|
|
61
|
+
function enrichTokenBalances(tokens: TokenBalance[]): TokenBalance[] {
|
|
62
|
+
return tokens.map((t) => ({
|
|
63
|
+
...t,
|
|
64
|
+
icon: resolveIconUrl(t.tokenId, t.icon),
|
|
65
|
+
}));
|
|
194
66
|
}
|
|
195
67
|
|
|
196
68
|
export async function scanAddress(
|
|
197
69
|
address: string,
|
|
198
70
|
onProgress?: (p: ScanProgress) => void,
|
|
199
71
|
): Promise<ScannedAssets> {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
onProgress?.({ phase: "sync", detail: "Syncing address..." });
|
|
203
|
-
for await (const event of services.owner.getTxos(address, { refresh: true, limit: 1 })) {
|
|
204
|
-
if (event.type === "sync") {
|
|
205
|
-
const p = event.data;
|
|
206
|
-
onProgress?.({
|
|
207
|
-
phase: "sync",
|
|
208
|
-
detail: `${p.phase}: ${p.processed ?? 0}/${p.total ?? "?"}`,
|
|
209
|
-
});
|
|
210
|
-
} else if (event.type === "done" || event.type === "error") {
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
onProgress?.({ phase: "search", detail: "Searching for assets..." });
|
|
216
|
-
const allOutputs = await services.txo.search(`own:${address}`, {
|
|
217
|
-
unspent: true,
|
|
218
|
-
events: true,
|
|
219
|
-
sats: true,
|
|
220
|
-
limit: 0,
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
onProgress?.({ phase: "categorize", detail: "Loading token details..." });
|
|
224
|
-
return await categorizeOutputs(allOutputs ?? []);
|
|
72
|
+
const result = await coreScanAddresses(getServices(), [address], onProgress);
|
|
73
|
+
return toScannedAssets(result);
|
|
225
74
|
}
|
|
226
75
|
|
|
227
76
|
export async function scanAddresses(
|
|
@@ -229,59 +78,20 @@ export async function scanAddresses(
|
|
|
229
78
|
onProgress?: (p: ScanProgress) => void,
|
|
230
79
|
): Promise<ScannedAssets> {
|
|
231
80
|
const unique = [...new Set(addresses)];
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
onProgress?.({ phase: "sync", detail: `Scanning ${addr.slice(0, 8)}...` });
|
|
236
|
-
allResults.push(await scanAddress(addr, onProgress));
|
|
237
|
-
}
|
|
81
|
+
const result = await coreScanAddresses(getServices(), unique, onProgress);
|
|
82
|
+
return toScannedAssets(result);
|
|
83
|
+
}
|
|
238
84
|
|
|
85
|
+
function toScannedAssets(result: ScanResult): ScannedAssets {
|
|
239
86
|
return {
|
|
240
|
-
funding:
|
|
241
|
-
ordinals:
|
|
242
|
-
opnsNames:
|
|
243
|
-
bsv21Tokens:
|
|
244
|
-
bsv20Tokens:
|
|
245
|
-
locked:
|
|
246
|
-
run:
|
|
247
|
-
|
|
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,
|
|
248
96
|
};
|
|
249
97
|
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Check source transactions for the RUN protocol OP_RETURN pattern.
|
|
253
|
-
* Returns the set of txids that contain a RUN OP_RETURN output.
|
|
254
|
-
*/
|
|
255
|
-
async function detectRunTransactions(funding: IndexedOutput[]): Promise<Set<string>> {
|
|
256
|
-
const services = getServices();
|
|
257
|
-
const txids = [...new Set(funding.map((f) => parseOutpoint(f.outpoint).txid))];
|
|
258
|
-
const runTxids = new Set<string>();
|
|
259
|
-
|
|
260
|
-
for (const txid of txids) {
|
|
261
|
-
try {
|
|
262
|
-
const beef = await services.getBeefForTxid(txid);
|
|
263
|
-
const beefTx = beef.findTxid(txid);
|
|
264
|
-
if (!beefTx?.tx) continue;
|
|
265
|
-
|
|
266
|
-
for (const output of beefTx.tx.outputs) {
|
|
267
|
-
const script = output.lockingScript?.toBinary();
|
|
268
|
-
if (script && hasRunPrefix(script)) {
|
|
269
|
-
runTxids.add(txid);
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
} catch {
|
|
274
|
-
// If we can't fetch the tx, leave the output in funding
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return runTxids;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function hasRunPrefix(script: number[]): boolean {
|
|
282
|
-
if (script.length < RUN_PREFIX.length) return false;
|
|
283
|
-
for (let i = 0; i < RUN_PREFIX.length; i++) {
|
|
284
|
-
if (script[i] !== RUN_PREFIX[i]) return false;
|
|
285
|
-
}
|
|
286
|
-
return true;
|
|
287
|
-
}
|
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
|
+
}
|