@bsv/sdk 1.9.0 → 1.9.2

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.
@@ -205,22 +205,27 @@ It provides methods to:
205
205
  - Register new definitions using pushdrop-based UTXOs.
206
206
  - Resolve existing definitions using a lookup service.
207
207
  - List registry entries associated with the operator's wallet.
208
- - Revoke an existing registry entry by spending its UTXO.
208
+ - Remove existing registry entries by spending their UTXOs.
209
+ - Update existing registry entries.
209
210
 
210
211
  Registry operators use this client to establish and manage
211
212
  canonical references for baskets, protocols, and certificate types.
212
213
 
213
214
  ```ts
214
215
  export class RegistryClient {
215
- constructor(private readonly wallet: WalletInterface = new WalletClient())
216
+ constructor(private readonly wallet: WalletInterface = new WalletClient(), options: {
217
+ acceptDelayedBroadcast?: boolean;
218
+ resolver?: LookupResolver;
219
+ } = {})
216
220
  async registerDefinition(data: DefinitionData): Promise<BroadcastResponse | BroadcastFailure>
217
221
  async resolve<T extends DefinitionType>(definitionType: T, query: RegistryQueryMapping[T]): Promise<DefinitionData[]>
218
222
  async listOwnRegistryEntries(definitionType: DefinitionType): Promise<RegistryRecord[]>
219
- async revokeOwnRegistryEntry(registryRecord: RegistryRecord): Promise<BroadcastResponse | BroadcastFailure>
223
+ async removeDefinition(registryRecord: RegistryRecord): Promise<BroadcastResponse | BroadcastFailure>
224
+ async updateDefinition(registryRecord: RegistryRecord, updatedData: DefinitionData): Promise<BroadcastResponse | BroadcastFailure>
220
225
  }
221
226
  ```
222
227
 
223
- See also: [BroadcastFailure](./transaction.md#interface-broadcastfailure), [BroadcastResponse](./transaction.md#interface-broadcastresponse), [DefinitionData](./registry.md#type-definitiondata), [DefinitionType](./registry.md#type-definitiontype), [RegistryQueryMapping](./registry.md#interface-registryquerymapping), [RegistryRecord](./registry.md#type-registryrecord), [WalletClient](./wallet.md#class-walletclient), [WalletInterface](./wallet.md#interface-walletinterface)
228
+ See also: [BroadcastFailure](./transaction.md#interface-broadcastfailure), [BroadcastResponse](./transaction.md#interface-broadcastresponse), [DefinitionData](./registry.md#type-definitiondata), [DefinitionType](./registry.md#type-definitiontype), [LookupResolver](./overlay-tools.md#class-lookupresolver), [RegistryQueryMapping](./registry.md#interface-registryquerymapping), [RegistryRecord](./registry.md#type-registryrecord), [WalletClient](./wallet.md#class-walletclient), [WalletInterface](./wallet.md#interface-walletinterface)
224
229
 
225
230
  #### Method listOwnRegistryEntries
226
231
 
@@ -264,6 +269,24 @@ Argument Details
264
269
  + **data**
265
270
  + Structured information about a 'basket', 'protocol', or 'certificate'.
266
271
 
272
+ #### Method removeDefinition
273
+
274
+ Removes a registry definition by spending its associated UTXO.
275
+
276
+ ```ts
277
+ async removeDefinition(registryRecord: RegistryRecord): Promise<BroadcastResponse | BroadcastFailure>
278
+ ```
279
+ See also: [BroadcastFailure](./transaction.md#interface-broadcastfailure), [BroadcastResponse](./transaction.md#interface-broadcastresponse), [RegistryRecord](./registry.md#type-registryrecord)
280
+
281
+ Returns
282
+
283
+ Broadcast success/failure.
284
+
285
+ Argument Details
286
+
287
+ + **registryRecord**
288
+ + The registry record to remove (must have valid txid, outputIndex, and lockingScript).
289
+
267
290
  #### Method resolve
268
291
 
269
292
  Resolves registrant tokens of a particular type using a lookup service.
@@ -292,14 +315,14 @@ Argument Details
292
315
  + **query**
293
316
  + The query object used to filter registry records, whose shape is determined by the registry type.
294
317
 
295
- #### Method revokeOwnRegistryEntry
318
+ #### Method updateDefinition
296
319
 
297
- Revokes a registry record by spending its associated UTXO.
320
+ Updates an existing registry record by spending its UTXO and creating a new one with updated data.
298
321
 
299
322
  ```ts
300
- async revokeOwnRegistryEntry(registryRecord: RegistryRecord): Promise<BroadcastResponse | BroadcastFailure>
323
+ async updateDefinition(registryRecord: RegistryRecord, updatedData: DefinitionData): Promise<BroadcastResponse | BroadcastFailure>
301
324
  ```
302
- See also: [BroadcastFailure](./transaction.md#interface-broadcastfailure), [BroadcastResponse](./transaction.md#interface-broadcastresponse), [RegistryRecord](./registry.md#type-registryrecord)
325
+ See also: [BroadcastFailure](./transaction.md#interface-broadcastfailure), [BroadcastResponse](./transaction.md#interface-broadcastresponse), [DefinitionData](./registry.md#type-definitiondata), [RegistryRecord](./registry.md#type-registryrecord)
303
326
 
304
327
  Returns
305
328
 
@@ -308,7 +331,9 @@ Broadcast success/failure.
308
331
  Argument Details
309
332
 
310
333
  + **registryRecord**
311
- + Must have valid txid, outputIndex, and lockingScript.
334
+ + The existing registry record to update (must have valid txid, outputIndex, and lockingScript).
335
+ + **updatedData**
336
+ + The new definition data to replace the old record.
312
337
 
313
338
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
314
339
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -115,7 +115,7 @@ export default class LocalKVStore {
115
115
  tagQueryMode: 'all',
116
116
  include: 'entire transactions',
117
117
  limit
118
- })
118
+ }, this.originator)
119
119
  return results
120
120
  }
121
121
 
@@ -175,7 +175,7 @@ export default class LocalKVStore {
175
175
  const { plaintext } = await this.wallet.decrypt({
176
176
  ...this.getProtocol(key),
177
177
  ciphertext: field
178
- })
178
+ }, this.originator)
179
179
  r.value = Utils.toUTF8(plaintext)
180
180
  }
181
181
  return r
@@ -241,7 +241,7 @@ export default class LocalKVStore {
241
241
  const { ciphertext } = await this.wallet.encrypt({
242
242
  ...protocol,
243
243
  plaintext: valueAsArray
244
- })
244
+ }, this.originator)
245
245
  valueAsArray = ciphertext
246
246
  }
247
247
 
@@ -272,7 +272,7 @@ export default class LocalKVStore {
272
272
  acceptDelayedBroadcast: this.acceptDelayedBroadcast,
273
273
  randomizeOutputs: false
274
274
  }
275
- })
275
+ }, this.originator)
276
276
 
277
277
  if (outputs.length > 0 && typeof signableTransaction !== 'object') {
278
278
  throw new Error('Wallet did not return a signable transaction when expected.')
@@ -285,7 +285,7 @@ export default class LocalKVStore {
285
285
  const { txid } = await this.wallet.signAction({
286
286
  reference: signableTransaction.reference,
287
287
  spends
288
- })
288
+ }, this.originator)
289
289
  outpoint = `${txid as string}.0`
290
290
  }
291
291
  } catch (error) {
@@ -326,7 +326,7 @@ export default class LocalKVStore {
326
326
  options: {
327
327
  acceptDelayedBroadcast: this.acceptDelayedBroadcast
328
328
  }
329
- })
329
+ }, this.originator)
330
330
  if (typeof signableTransaction !== 'object') {
331
331
  throw new Error('Wallet did not return a signable transaction when expected.')
332
332
  }
@@ -334,7 +334,7 @@ export default class LocalKVStore {
334
334
  const { txid } = await this.wallet.signAction({
335
335
  reference: signableTransaction.reference,
336
336
  spends
337
- })
337
+ }, this.originator)
338
338
  if (txid === undefined) { throw new Error('signAction must return a valid txid') }
339
339
  txids.push(txid)
340
340
  } catch (error) {
@@ -104,7 +104,7 @@ describe('localKVStore', () => {
104
104
  // const testUnlockingScriptHex = 'mockUnlockingScriptHex'; // Defined above
105
105
 
106
106
  beforeEach(() => {
107
- // Reset mocks before each test (clears calls and resets implementations)
107
+ // Reset mocks before each test (clears calls and resets implementations)
108
108
  jest.clearAllMocks()
109
109
 
110
110
  // Create a fresh mock wallet for each test
@@ -122,7 +122,7 @@ describe('localKVStore', () => {
122
122
  // --- Constructor Tests ---
123
123
  describe('constructor', () => {
124
124
  it('should create an instance with default wallet and encrypt=true', () => {
125
- // We need to mock the default WalletClient if the SUT uses it
125
+ // We need to mock the default WalletClient if the SUT uses it
126
126
  const MockedWalletClient = require('../../../mod.js').WalletClient
127
127
  const store = new LocalKVStore(undefined, 'default-context')
128
128
  expect(store).toBeInstanceOf(LocalKVStore)
@@ -200,7 +200,7 @@ describe('localKVStore', () => {
200
200
  let pushDropInstance: PushDrop // To access the instance methods
201
201
 
202
202
  beforeEach(() => {
203
- // Get the mock instance that will be created by `new PushDrop()`
203
+ // Get the mock instance that will be created by `new PushDrop()`
204
204
  pushDropInstance = new (PushDrop as any)()
205
205
  })
206
206
 
@@ -223,7 +223,7 @@ describe('localKVStore', () => {
223
223
  plaintext: valueArray, // Should be Array<number>
224
224
  protocolID: [2, testContext],
225
225
  keyID: testKey
226
- })
226
+ }, undefined)
227
227
  // Check the mock instance's lock method
228
228
  expect(mockPDInstance.lock).toHaveBeenCalledWith(
229
229
  // The lock function expects Array<number[] | Uint8Array>
@@ -250,7 +250,7 @@ describe('localKVStore', () => {
250
250
  acceptDelayedBroadcast: false,
251
251
  randomizeOutputs: false
252
252
  }
253
- })
253
+ }, undefined)
254
254
  expect(mockWallet.signAction).not.toHaveBeenCalled()
255
255
  expect(mockWallet.relinquishOutput).not.toHaveBeenCalled()
256
256
  })
@@ -293,7 +293,7 @@ describe('localKVStore', () => {
293
293
  acceptDelayedBroadcast: false,
294
294
  randomizeOutputs: false
295
295
  }
296
- })
296
+ }, undefined)
297
297
  expect(mockWallet.signAction).not.toHaveBeenCalled()
298
298
  expect(mockWallet.relinquishOutput).not.toHaveBeenCalled()
299
299
  })
@@ -301,7 +301,7 @@ describe('localKVStore', () => {
301
301
  it('should update an existing output (spend and create)', async () => {
302
302
  const existingOutpoint = 'oldTxId.0'
303
303
  const existingOutput = { outpoint: existingOutpoint, txid: 'oldTxId', vout: 0, lockingScript: 'oldScriptHex' } // Added script
304
- const mockBEEF = [1,2,3,4,5,6]
304
+ const mockBEEF = [1, 2, 3, 4, 5, 6]
305
305
  const signableRef = 'signableTxRef123'
306
306
  const signableTx = []
307
307
  const updatedTxId = 'updatedTxId'
@@ -366,7 +366,7 @@ describe('localKVStore', () => {
366
366
  outputs: expect.arrayContaining([ // Check outputs array
367
367
  expect.objectContaining({ lockingScript: testLockingScriptHex }) // Check the new output script
368
368
  ])
369
- }))
369
+ }), undefined)
370
370
 
371
371
  // Verify signing steps
372
372
  expect(MockedTransaction.fromAtomicBEEF).toHaveBeenCalledWith(signableTx)
@@ -383,7 +383,7 @@ describe('localKVStore', () => {
383
383
  spends: {
384
384
  0: { unlockingScript: testUnlockingScriptHex } // Check unlocking script from mock sign result
385
385
  }
386
- })
386
+ }, undefined)
387
387
  expect(mockWallet.relinquishOutput).not.toHaveBeenCalled()
388
388
  })
389
389
 
@@ -395,7 +395,7 @@ describe('localKVStore', () => {
395
395
  const existingOutpoint2 = 'oldTxId2.1'
396
396
  const existingOutput1 = { outpoint: existingOutpoint1, txid: 'oldTxId1', vout: 0, lockingScript: 's1' }
397
397
  const existingOutput2 = { outpoint: existingOutpoint2, txid: 'oldTxId2', vout: 1, lockingScript: 's2' }
398
- const mockBEEF = [1,2,3,4,5,6]
398
+ const mockBEEF = [1, 2, 3, 4, 5, 6]
399
399
  const signableRef = 'signableTxRefMulti'
400
400
  const signableTx = []
401
401
  const updatedTxId = 'updatedTxIdMulti'
@@ -457,7 +457,7 @@ describe('localKVStore', () => {
457
457
  outputs: expect.arrayContaining([
458
458
  expect.objectContaining({ lockingScript: testLockingScriptHex })
459
459
  ])
460
- }))
460
+ }), undefined)
461
461
 
462
462
  // Verify signing loop
463
463
  expect(MockedTransaction.fromAtomicBEEF).toHaveBeenCalledWith(signableTx)
@@ -478,14 +478,14 @@ describe('localKVStore', () => {
478
478
  0: { unlockingScript: testUnlockingScriptHex }, // Same mock script for both
479
479
  1: { unlockingScript: testUnlockingScriptHex }
480
480
  }
481
- })
481
+ }, undefined)
482
482
  expect(mockWallet.relinquishOutput).not.toHaveBeenCalled()
483
483
  })
484
484
 
485
485
  it('should preserve original error message when createAction fails', async () => {
486
486
  const originalErrorMessage = 'Network connection timeout while creating transaction'
487
487
  const originalError = new Error(originalErrorMessage)
488
-
488
+
489
489
  // Mock the lookupValue to return a value that differs from what we're setting
490
490
  // to ensure set() will attempt to create a transaction
491
491
  const mockedLor: ListOutputsResult = {
@@ -533,7 +533,7 @@ describe('localKVStore', () => {
533
533
  let pushDropInstance: PushDrop // To access the instance methods
534
534
 
535
535
  beforeEach(() => {
536
- // Get the mock instance that will be created by `new PushDrop()`
536
+ // Get the mock instance that will be created by `new PushDrop()`
537
537
  pushDropInstance = new (PushDrop as any)()
538
538
  })
539
539
 
@@ -598,7 +598,7 @@ describe('localKVStore', () => {
598
598
  options: {
599
599
  acceptDelayedBroadcast: false
600
600
  }
601
- })
601
+ }, undefined)
602
602
  // Check that outputs key is absent
603
603
  expect(mockWallet.createAction.mock.calls[0][0]).not.toHaveProperty('outputs')
604
604
 
@@ -619,7 +619,7 @@ describe('localKVStore', () => {
619
619
  0: { unlockingScript: testUnlockingScriptHex },
620
620
  1: { unlockingScript: testUnlockingScriptHex }
621
621
  }
622
- })
622
+ }, undefined)
623
623
  expect(mockWallet.relinquishOutput).not.toHaveBeenCalled()
624
624
  })
625
625
 
@@ -659,7 +659,7 @@ describe('localKVStore', () => {
659
659
  it('should preserve original error message when wallet operations fail during removal', async () => {
660
660
  const originalErrorMessage = 'Insufficient funds to cover transaction fees'
661
661
  const originalError = new Error(originalErrorMessage)
662
-
662
+
663
663
  const existingOutpoint = 'failTxId.0'
664
664
  const existingOutput = { outpoint: existingOutpoint, txid: 'failTxId', vout: 0, lockingScript: 's1' }
665
665
  const mockBEEF = Buffer.from('mockBEEFFail')
@@ -25,6 +25,7 @@ import {
25
25
  } from './types/index.js'
26
26
 
27
27
  const REGISTRANT_TOKEN_AMOUNT = 1
28
+ const REGISTRANT_KEY_ID = '1'
28
29
 
29
30
  /**
30
31
  * RegistryClient manages on-chain registry definitions for three types:
@@ -36,16 +37,47 @@ const REGISTRANT_TOKEN_AMOUNT = 1
36
37
  * - Register new definitions using pushdrop-based UTXOs.
37
38
  * - Resolve existing definitions using a lookup service.
38
39
  * - List registry entries associated with the operator's wallet.
39
- * - Revoke an existing registry entry by spending its UTXO.
40
+ * - Remove existing registry entries by spending their UTXOs.
41
+ * - Update existing registry entries.
40
42
  *
41
43
  * Registry operators use this client to establish and manage
42
44
  * canonical references for baskets, protocols, and certificate types.
43
45
  */
44
46
  export class RegistryClient {
45
- private network: 'mainnet' | 'testnet'
47
+ private network: 'mainnet' | 'testnet' | undefined
48
+ private readonly resolver: LookupResolver
49
+ private cachedIdentityKey: PubKeyHex | undefined
50
+ private readonly acceptDelayedBroadcast: boolean
51
+
46
52
  constructor (
47
- private readonly wallet: WalletInterface = new WalletClient()
48
- ) { }
53
+ private readonly wallet: WalletInterface = new WalletClient(),
54
+ options: { acceptDelayedBroadcast?: boolean, resolver?: LookupResolver } = {}
55
+ ) {
56
+ this.acceptDelayedBroadcast = options.acceptDelayedBroadcast ?? false
57
+ this.resolver = options.resolver ?? new LookupResolver()
58
+ }
59
+
60
+ /**
61
+ * Gets the wallet's identity key, caching it after the first call.
62
+ * @returns The public identity key as a hex string.
63
+ */
64
+ private async getIdentityKey (): Promise<PubKeyHex> {
65
+ if (this.cachedIdentityKey === undefined) {
66
+ this.cachedIdentityKey = (await this.wallet.getPublicKey({ identityKey: true })).publicKey
67
+ }
68
+ return this.cachedIdentityKey
69
+ }
70
+
71
+ /**
72
+ * Gets the network, initializing and caching it on first call.
73
+ * @returns The network type ('mainnet' or 'testnet').
74
+ */
75
+ private async getNetwork (): Promise<'mainnet' | 'testnet'> {
76
+ if (this.network === undefined) {
77
+ this.network = (await this.wallet.getNetwork({})).network
78
+ }
79
+ return this.network
80
+ }
49
81
 
50
82
  /**
51
83
  * Publishes a new on-chain definition for baskets, protocols, or certificates.
@@ -58,7 +90,7 @@ export class RegistryClient {
58
90
  * @returns A promise with the broadcast result or failure.
59
91
  */
60
92
  async registerDefinition (data: DefinitionData): Promise<BroadcastResponse | BroadcastFailure> {
61
- const registryOperator = (await this.wallet.getPublicKey({ identityKey: true })).publicKey
93
+ const registryOperator = await this.getIdentityKey()
62
94
  const pushdrop = new PushDrop(this.wallet)
63
95
 
64
96
  // Convert definition data into PushDrop fields
@@ -68,7 +100,7 @@ export class RegistryClient {
68
100
  const protocol = this.mapDefinitionTypeToWalletProtocol(data.definitionType)
69
101
 
70
102
  // Lock the fields into a pushdrop-based UTXO
71
- const lockingScript = await pushdrop.lock(fields, protocol, '1', 'anyone', true)
103
+ const lockingScript = await pushdrop.lock(fields, protocol, REGISTRANT_KEY_ID, 'anyone', true)
72
104
 
73
105
  // Create a transaction
74
106
  const { tx } = await this.wallet.createAction({
@@ -82,22 +114,23 @@ export class RegistryClient {
82
114
  }
83
115
  ],
84
116
  options: {
117
+ acceptDelayedBroadcast: this.acceptDelayedBroadcast,
85
118
  randomizeOutputs: false
86
119
  }
87
120
  })
88
-
89
121
  if (tx === undefined) {
90
122
  throw new Error(`Failed to create ${data.definitionType} registration transaction!`)
91
123
  }
92
-
93
124
  // Broadcast to the relevant topic
94
125
  const broadcaster = new TopicBroadcaster(
95
126
  [this.mapDefinitionTypeToTopic(data.definitionType)],
96
127
  {
97
- networkPreset: this.network ??= (await this.wallet.getNetwork({})).network
128
+ networkPreset: await this.getNetwork(),
129
+ resolver: this.resolver
98
130
  }
99
131
  )
100
- return await broadcaster.broadcast(Transaction.fromAtomicBEEF(tx))
132
+ const result = await broadcaster.broadcast(Transaction.fromAtomicBEEF(tx))
133
+ return result
101
134
  }
102
135
 
103
136
  /**
@@ -119,11 +152,10 @@ export class RegistryClient {
119
152
  definitionType: T,
120
153
  query: RegistryQueryMapping[T]
121
154
  ): Promise<DefinitionData[]> {
122
- const resolver = new LookupResolver()
123
155
  const serviceName = this.mapDefinitionTypeToServiceName(definitionType)
124
156
 
125
157
  // Make the lookup query
126
- const result = await resolver.query({ service: serviceName, query })
158
+ const result = await this.resolver.query({ service: serviceName, query })
127
159
  if (result.type !== 'output-list') {
128
160
  return []
129
161
  }
@@ -164,8 +196,8 @@ export class RegistryClient {
164
196
  }
165
197
  try {
166
198
  const [txid, outputIndex] = output.outpoint.split('.')
167
- const tx = Transaction.fromBEEF(BEEF as number[])
168
- const lockingScript: LockingScript = tx.outputs[outputIndex].lockingScript
199
+ const tx = Transaction.fromBEEF(BEEF as number[], txid)
200
+ const lockingScript: LockingScript = tx.outputs[Number(outputIndex)].lockingScript
169
201
  const record = await this.parseLockingScript(
170
202
  definitionType,
171
203
  lockingScript
@@ -187,12 +219,12 @@ export class RegistryClient {
187
219
  }
188
220
 
189
221
  /**
190
- * Revokes a registry record by spending its associated UTXO.
222
+ * Removes a registry definition by spending its associated UTXO.
191
223
  *
192
- * @param registryRecord - Must have valid txid, outputIndex, and lockingScript.
224
+ * @param registryRecord - The registry record to remove (must have valid txid, outputIndex, and lockingScript).
193
225
  * @returns Broadcast success/failure.
194
226
  */
195
- async revokeOwnRegistryEntry (
227
+ async removeDefinition (
196
228
  registryRecord: RegistryRecord
197
229
  ): Promise<BroadcastResponse | BroadcastFailure> {
198
230
  if (registryRecord.txid === undefined || typeof registryRecord.outputIndex === 'undefined' || registryRecord.lockingScript === undefined) {
@@ -200,12 +232,12 @@ export class RegistryClient {
200
232
  }
201
233
 
202
234
  // Check if the registry record belongs to the current user
203
- const currentIdentityKey = (await this.wallet.getPublicKey({ identityKey: true })).publicKey
235
+ const currentIdentityKey = await this.getIdentityKey()
204
236
  if (registryRecord.registryOperator !== currentIdentityKey) {
205
237
  throw new Error('This registry token does not belong to the current wallet.')
206
238
  }
207
239
 
208
- // Create a descriptive label for the item were revoking
240
+ // Create a descriptive label for the item we're removing
209
241
  const itemIdentifier =
210
242
  registryRecord.definitionType === 'basket'
211
243
  ? registryRecord.basketID
@@ -217,48 +249,159 @@ export class RegistryClient {
217
249
 
218
250
  const outpoint = `${registryRecord.txid}.${registryRecord.outputIndex}`
219
251
  const { signableTransaction } = await this.wallet.createAction({
220
- description: `Revoke ${registryRecord.definitionType} item: ${itemIdentifier}`,
252
+ description: `Remove ${registryRecord.definitionType} item: ${itemIdentifier}`,
221
253
  inputBEEF: registryRecord.beef,
222
254
  inputs: [
223
255
  {
224
256
  outpoint,
225
- unlockingScriptLength: 73,
226
- inputDescription: `Revoking ${registryRecord.definitionType} token`
257
+ unlockingScriptLength: 74,
258
+ inputDescription: `Removing ${registryRecord.definitionType} token`
227
259
  }
228
- ]
260
+ ],
261
+ options: {
262
+ acceptDelayedBroadcast: this.acceptDelayedBroadcast,
263
+ randomizeOutputs: false
264
+ }
229
265
  })
230
266
 
231
267
  if (signableTransaction === undefined) {
232
268
  throw new Error('Failed to create signable transaction.')
233
269
  }
234
-
235
- const partialTx = Transaction.fromBEEF(signableTransaction.tx)
270
+ const partialTx = Transaction.fromAtomicBEEF(signableTransaction.tx)
236
271
 
237
272
  // Prepare the unlocker
238
273
  const pushdrop = new PushDrop(this.wallet)
239
- const unlocker = await pushdrop.unlock(
274
+ const unlocker = pushdrop.unlock(
240
275
  this.mapDefinitionTypeToWalletProtocol(registryRecord.definitionType),
241
- '1',
242
- 'anyone',
243
- 'all',
244
- false,
245
- registryRecord.satoshis,
246
- LockingScript.fromHex(registryRecord.lockingScript)
276
+ REGISTRANT_KEY_ID,
277
+ 'anyone'
247
278
  )
248
279
 
249
280
  // Convert to Transaction, apply signature
250
- const finalUnlockScript = await unlocker.sign(partialTx, registryRecord.outputIndex)
281
+ const finalUnlockScript = await unlocker.sign(partialTx, 0)
282
+
283
+ // Complete signing with the final unlock script
284
+ const { tx: signedTx } = await this.wallet.signAction({
285
+ reference: signableTransaction.reference,
286
+ spends: {
287
+ 0: {
288
+ unlockingScript: finalUnlockScript.toHex()
289
+ }
290
+ },
291
+ options: {
292
+ acceptDelayedBroadcast: this.acceptDelayedBroadcast
293
+ }
294
+ })
295
+
296
+ if (signedTx === undefined) {
297
+ throw new Error('Failed to finalize the transaction signature.')
298
+ }
299
+ // Broadcast
300
+ const broadcaster = new TopicBroadcaster(
301
+ [this.mapDefinitionTypeToTopic(registryRecord.definitionType)],
302
+ {
303
+ networkPreset: await this.getNetwork(),
304
+ resolver: this.resolver
305
+ }
306
+ )
307
+ const result = await broadcaster.broadcast(Transaction.fromAtomicBEEF(signedTx))
308
+ return result
309
+ }
310
+
311
+ /**
312
+ * Updates an existing registry record by spending its UTXO and creating a new one with updated data.
313
+ *
314
+ * @param registryRecord - The existing registry record to update (must have valid txid, outputIndex, and lockingScript).
315
+ * @param updatedData - The new definition data to replace the old record.
316
+ * @returns Broadcast success/failure.
317
+ */
318
+ async updateDefinition (
319
+ registryRecord: RegistryRecord,
320
+ updatedData: DefinitionData
321
+ ): Promise<BroadcastResponse | BroadcastFailure> {
322
+ if (registryRecord.txid === undefined || typeof registryRecord.outputIndex === 'undefined' || registryRecord.lockingScript === undefined) {
323
+ throw new Error('Invalid registry record. Missing txid, outputIndex, or lockingScript.')
324
+ }
325
+
326
+ // Verify the updated data matches the record type
327
+ if (registryRecord.definitionType !== updatedData.definitionType) {
328
+ throw new Error(`Cannot change definition type from ${registryRecord.definitionType} to ${updatedData.definitionType}`)
329
+ }
330
+
331
+ // Check if the registry record belongs to the current user
332
+ const currentIdentityKey = await this.getIdentityKey()
333
+ if (registryRecord.registryOperator !== currentIdentityKey) {
334
+ throw new Error('This registry token does not belong to the current wallet.')
335
+ }
336
+
337
+ // Create a descriptive label for the item we're updating
338
+ const itemIdentifier =
339
+ registryRecord.definitionType === 'basket'
340
+ ? registryRecord.basketID
341
+ : registryRecord.definitionType === 'protocol'
342
+ ? registryRecord.name
343
+ : registryRecord.definitionType === 'certificate'
344
+ ? (registryRecord.name !== undefined ? registryRecord.name : registryRecord.type)
345
+ : 'unknown'
346
+
347
+ const pushdrop = new PushDrop(this.wallet)
348
+
349
+ // Build the new locking script with updated data
350
+ const fields = this.buildPushDropFields(updatedData, currentIdentityKey)
351
+ const protocol = this.mapDefinitionTypeToWalletProtocol(updatedData.definitionType)
352
+ const newLockingScript = await pushdrop.lock(fields, protocol, REGISTRANT_KEY_ID, 'anyone', true)
353
+
354
+ const outpoint = `${registryRecord.txid}.${registryRecord.outputIndex}`
355
+ const { signableTransaction } = await this.wallet.createAction({
356
+ description: `Update ${registryRecord.definitionType} item: ${itemIdentifier}`,
357
+ inputBEEF: registryRecord.beef,
358
+ inputs: [
359
+ {
360
+ outpoint,
361
+ unlockingScriptLength: 74,
362
+ inputDescription: `Updating ${registryRecord.definitionType} token`
363
+ }
364
+ ],
365
+ outputs: [
366
+ {
367
+ satoshis: REGISTRANT_TOKEN_AMOUNT,
368
+ lockingScript: newLockingScript.toHex(),
369
+ outputDescription: `Updated ${registryRecord.definitionType} registration token`,
370
+ basket: this.mapDefinitionTypeToBasketName(registryRecord.definitionType)
371
+ }
372
+ ],
373
+ options: {
374
+ acceptDelayedBroadcast: this.acceptDelayedBroadcast,
375
+ randomizeOutputs: false
376
+ }
377
+ })
378
+
379
+ if (signableTransaction === undefined) {
380
+ throw new Error('Failed to create signable transaction.')
381
+ }
382
+
383
+ const partialTx = Transaction.fromAtomicBEEF(signableTransaction.tx)
384
+
385
+ // Prepare the unlocker for the input
386
+ const unlocker = pushdrop.unlock(
387
+ this.mapDefinitionTypeToWalletProtocol(registryRecord.definitionType),
388
+ REGISTRANT_KEY_ID,
389
+ 'anyone'
390
+ )
391
+
392
+ // Sign the input
393
+ const finalUnlockScript = await unlocker.sign(partialTx, 0)
251
394
 
252
395
  // Complete signing with the final unlock script
253
396
  const { tx: signedTx } = await this.wallet.signAction({
254
397
  reference: signableTransaction.reference,
255
398
  spends: {
256
- [registryRecord.outputIndex]: {
399
+ 0: {
257
400
  unlockingScript: finalUnlockScript.toHex()
258
401
  }
259
402
  },
260
403
  options: {
261
- acceptDelayedBroadcast: false
404
+ acceptDelayedBroadcast: this.acceptDelayedBroadcast
262
405
  }
263
406
  })
264
407
 
@@ -270,7 +413,8 @@ export class RegistryClient {
270
413
  const broadcaster = new TopicBroadcaster(
271
414
  [this.mapDefinitionTypeToTopic(registryRecord.definitionType)],
272
415
  {
273
- networkPreset: this.network ??= (await this.wallet.getNetwork({})).network
416
+ networkPreset: await this.getNetwork(),
417
+ resolver: this.resolver
274
418
  }
275
419
  )
276
420
  return await broadcaster.broadcast(Transaction.fromAtomicBEEF(signedTx))