@1sat/sweep-ui 0.0.95 → 0.0.96

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.
@@ -1,28 +1,30 @@
1
1
  import {
2
+ type Bsv20Balance,
3
+ SWEEP_BATCH_SIZE,
4
+ bsv21SweepBatches,
2
5
  createContext,
6
+ groupBsv20Tokens,
3
7
  prepareSweepInputs,
4
8
  sweepBsv,
9
+ sweepBsv20,
5
10
  sweepBsv21,
6
11
  sweepOrdinals,
7
12
  } from '@1sat/actions'
8
13
  import type { IndexedOutput } from '@1sat/types'
9
14
  import type { PrivateKey, WalletInterface } from '@bsv/sdk'
10
- import type { TokenBalance } from './scanner'
15
+ import type { ScannedAssets, TokenBalance } from './scanner'
11
16
  import { getServices } from './services'
12
17
 
13
- /** Page size, select-page size, and createAction batch size for ordinal/OpNS sweeps. */
14
- export const SWEEP_BATCH_SIZE = 25
18
+ export { SWEEP_BATCH_SIZE }
15
19
 
16
20
  export interface SweepResult {
17
21
  bsvTxid?: string
18
22
  ordinalTxids: string[]
19
- listingTxids: string[]
23
+ bsv20Txids: string[]
20
24
  bsv21Txids: string[]
21
25
  errors: string[]
22
- /** Outpoints successfully swept (ordinals/OpNS). */
26
+ /** Outpoints successfully swept (ordinals/OpNS, including listed OrdLocks). */
23
27
  sweptOutpoints: string[]
24
- /** Listing outpoints cancelled into the BRC-100 wallet. */
25
- cancelledListings: string[]
26
28
  }
27
29
 
28
30
  function getOwner(output: IndexedOutput): string | undefined {
@@ -54,65 +56,27 @@ function chunk<T>(items: T[], size: number): T[][] {
54
56
  }
55
57
 
56
58
  /**
57
- * Sweep BSV funding and ordinals into the connected wallet.
58
- * Ordinals are processed in batches of {@link SWEEP_BATCH_SIZE}; stops on first batch error.
59
+ * Sweep BSV funding then ordinals into the connected wallet.
60
+ * Same class order as `1sat sweep import` for these two classes.
61
+ * Listed OrdLocks belong in `ordinals` and cancel in that spend.
59
62
  */
60
63
  export async function executeSweep(params: {
61
64
  wallet: WalletInterface
62
65
  keys: Map<string, PrivateKey>
63
66
  funding: IndexedOutput[]
64
67
  ordinals: IndexedOutput[]
65
- /** OPL-4696: listed OrdLock UTXOs — cancelled into BRC-100 via sweepOrdinals. */
66
- listings?: IndexedOutput[]
67
68
  amount?: number
68
69
  onProgress: (stage: string) => void
69
70
  }): Promise<SweepResult> {
70
- const {
71
- wallet,
72
- keys,
73
- funding,
74
- ordinals,
75
- listings = [],
76
- amount,
77
- onProgress,
78
- } = params
71
+ const { wallet, keys, funding, ordinals, amount, onProgress } = params
79
72
  const ctx = createContext(wallet, { services: getServices(), chain: 'main' })
80
73
 
81
74
  const result: SweepResult = {
82
75
  ordinalTxids: [],
83
- listingTxids: [],
76
+ bsv20Txids: [],
84
77
  bsv21Txids: [],
85
78
  errors: [],
86
79
  sweptOutpoints: [],
87
- cancelledListings: [],
88
- }
89
-
90
- if (listings.length > 0) {
91
- const batches = chunk(listings, SWEEP_BATCH_SIZE)
92
- for (let b = 0; b < batches.length; b++) {
93
- const batch = batches[b]
94
- onProgress(
95
- `Cancelling ${batch.length} OrdLock listing${batch.length !== 1 ? 's' : ''} into wallet...`,
96
- )
97
- try {
98
- const inputs = await prepareSweepInputs(ctx, batch)
99
- const cancelResult = await sweepOrdinals.execute(ctx, {
100
- inputs,
101
- keys: buildKeys(batch, keys, inputs),
102
- })
103
- if (cancelResult.error) throw new Error(cancelResult.error)
104
- const txid = cancelResult.txid?.trim()
105
- if (!txid) throw new Error('Cancellation returned no transaction ID')
106
- result.listingTxids.push(txid)
107
- result.cancelledListings.push(...batch.map((o) => o.outpoint))
108
- } catch (e) {
109
- result.errors.push(
110
- `Listings batch ${b + 1}: ${e instanceof Error ? e.message : String(e)}`,
111
- )
112
- onProgress('Sweep stopped with errors')
113
- return result
114
- }
115
- }
116
80
  }
117
81
 
118
82
  if (funding.length > 0) {
@@ -171,19 +135,64 @@ export async function executeSweep(params: {
171
135
  }
172
136
 
173
137
  /**
174
- * Sweep a single BSV-21 token into the connected wallet.
175
- * Each token requires its own transaction since all inputs must share a tokenId.
138
+ * Sweep one BSV-21 token. Listed OrdLocks are each their own tx so one
139
+ * invalid listing cannot sink the rest. Unlisted UTXOs of the token share a spend.
176
140
  */
177
141
  export async function sweepBsv21Token(params: {
178
142
  wallet: WalletInterface
179
143
  keys: Map<string, PrivateKey>
180
144
  token: TokenBalance
181
145
  onProgress: (stage: string) => void
146
+ }): Promise<{ txid?: string; txids: string[]; error?: string }> {
147
+ const { wallet, keys, token, onProgress } = params
148
+ const ctx = createContext(wallet, { services: getServices(), chain: 'main' })
149
+ const txids: string[] = []
150
+ const errors: string[] = []
151
+ const name = token.symbol ?? token.tokenId.slice(0, 8)
152
+
153
+ for (const batch of bsv21SweepBatches(token.outputs)) {
154
+ onProgress(`Sweeping ${name}...`)
155
+ try {
156
+ const sweepInputs = await prepareSweepInputs(ctx, batch)
157
+ const sweepInputMap = new Map(sweepInputs.map((s) => [s.outpoint, s]))
158
+ const inputs = batch.map((out) => {
159
+ const base = sweepInputMap.get(out.outpoint)
160
+ if (!base) throw new Error(`Missing sweep input for ${out.outpoint}`)
161
+ return {
162
+ ...base,
163
+ tokenId: token.tokenId,
164
+ amount: token.amounts.get(out.outpoint) ?? '0',
165
+ }
166
+ })
167
+ const result = await sweepBsv21.execute(ctx, {
168
+ inputs,
169
+ keys: buildKeys(batch, keys),
170
+ })
171
+ if (result.error) errors.push(result.error)
172
+ else if (result.txid) txids.push(result.txid)
173
+ } catch (e) {
174
+ errors.push(e instanceof Error ? e.message : String(e))
175
+ }
176
+ }
177
+
178
+ return {
179
+ txid: txids.at(-1),
180
+ txids,
181
+ error: errors[0],
182
+ }
183
+ }
184
+
185
+ /** Sweep one BSV-20 ticker into the connected wallet. */
186
+ export async function sweepBsv20Token(params: {
187
+ wallet: WalletInterface
188
+ keys: Map<string, PrivateKey>
189
+ token: Bsv20Balance
190
+ onProgress: (stage: string) => void
182
191
  }): Promise<{ txid?: string; error?: string }> {
183
192
  const { wallet, keys, token, onProgress } = params
184
193
  const ctx = createContext(wallet, { services: getServices(), chain: 'main' })
185
194
 
186
- onProgress(`Sweeping ${token.symbol ?? token.tokenId.slice(0, 8)}...`)
195
+ onProgress(`Sweeping ${token.tick}...`)
187
196
 
188
197
  try {
189
198
  const sweepInputs = await prepareSweepInputs(ctx, token.outputs)
@@ -194,17 +203,94 @@ export async function sweepBsv21Token(params: {
194
203
  if (!base) throw new Error(`Missing sweep input for ${out.outpoint}`)
195
204
  return {
196
205
  ...base,
197
- tokenId: token.tokenId,
206
+ tick: token.tick,
198
207
  amount: token.amounts.get(out.outpoint) ?? '0',
199
208
  }
200
209
  })
201
210
 
202
- const tokenKeys = buildKeys(token.outputs, keys)
203
-
204
- const result = await sweepBsv21.execute(ctx, { inputs, keys: tokenKeys })
211
+ const result = await sweepBsv20.execute(ctx, {
212
+ inputs,
213
+ keys: buildKeys(token.outputs, keys),
214
+ })
205
215
  if (result.error) return { error: result.error }
206
216
  return { txid: result.txid }
207
217
  } catch (e) {
208
218
  return { error: e instanceof Error ? e.message : String(e) }
209
219
  }
210
220
  }
221
+
222
+ /** Same class order as `1sat sweep import`. Continues later classes if one fails. */
223
+ export async function sweepAllClasses(params: {
224
+ wallet: WalletInterface
225
+ keys: Map<string, PrivateKey>
226
+ assets: ScannedAssets
227
+ amount?: number
228
+ onProgress: (stage: string) => void
229
+ }): Promise<SweepResult> {
230
+ const { wallet, keys, assets, amount, onProgress } = params
231
+ const result: SweepResult = {
232
+ ordinalTxids: [],
233
+ bsv20Txids: [],
234
+ bsv21Txids: [],
235
+ errors: [],
236
+ sweptOutpoints: [],
237
+ }
238
+
239
+ const first = await executeSweep({
240
+ wallet,
241
+ keys,
242
+ funding: assets.funding,
243
+ ordinals: assets.ordinals,
244
+ amount,
245
+ onProgress,
246
+ })
247
+ result.bsvTxid = first.bsvTxid
248
+ result.ordinalTxids.push(...first.ordinalTxids)
249
+ result.sweptOutpoints.push(...first.sweptOutpoints)
250
+ result.errors.push(...first.errors)
251
+
252
+ if (assets.opnsNames.length > 0) {
253
+ const opns = await executeSweep({
254
+ wallet,
255
+ keys,
256
+ funding: [],
257
+ ordinals: assets.opnsNames,
258
+ onProgress,
259
+ })
260
+ result.ordinalTxids.push(...opns.ordinalTxids)
261
+ result.sweptOutpoints.push(...opns.sweptOutpoints)
262
+ result.errors.push(...opns.errors)
263
+ }
264
+
265
+ for (const token of groupBsv20Tokens(assets.bsv20Tokens)) {
266
+ const swept = await sweepBsv20Token({
267
+ wallet,
268
+ keys,
269
+ token,
270
+ onProgress,
271
+ })
272
+ if (swept.error) result.errors.push(`BSV-20 ${token.tick}: ${swept.error}`)
273
+ else if (swept.txid) result.bsv20Txids.push(swept.txid)
274
+ }
275
+
276
+ for (const token of assets.bsv21Tokens) {
277
+ if (token.outputs.length === 0) continue
278
+ const swept = await sweepBsv21Token({
279
+ wallet,
280
+ keys,
281
+ token,
282
+ onProgress,
283
+ })
284
+ result.bsv21Txids.push(...swept.txids)
285
+ if (swept.error) {
286
+ result.errors.push(
287
+ `BSV-21 ${token.symbol ?? token.tokenId.slice(0, 8)}: ${swept.error}`,
288
+ )
289
+ }
290
+ }
291
+
292
+ onProgress(
293
+ result.errors.length > 0 ? 'Sweep stopped with errors' : 'Sweep complete',
294
+ )
295
+ return result
296
+ }