@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.
@@ -35,7 +35,7 @@ jest.mock('../../script/index.js', () => {
35
35
  PushDrop: Object.assign(
36
36
  jest.fn().mockImplementation(() => ({
37
37
  lock: jest.fn().mockResolvedValue({ toHex: () => 'mockLockingScriptHex' }),
38
- unlock: jest.fn().mockResolvedValue({
38
+ unlock: jest.fn().mockReturnValue({
39
39
  sign: jest.fn().mockResolvedValue({
40
40
  toHex: () => 'mockUnlockingScriptHex'
41
41
  })
@@ -164,6 +164,11 @@ describe('RegistryClient', () => {
164
164
  }
165
165
 
166
166
  registryClient = new RegistryClient(walletMock as WalletInterface)
167
+
168
+ // Mock the resolver instance since it's now initialized in constructor
169
+ ; (registryClient as any).resolver = {
170
+ query: jest.fn().mockResolvedValue({ type: 'output-list', outputs: [] })
171
+ }
167
172
 
168
173
  jest.clearAllMocks()
169
174
  mockBroadcast.mockClear()
@@ -192,9 +197,9 @@ describe('RegistryClient', () => {
192
197
  ])
193
198
  })
194
199
  )
195
- expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], {
200
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], expect.objectContaining({
196
201
  networkPreset: 'main'
197
- })
202
+ }))
198
203
  expect(mockBroadcast).toHaveBeenCalledTimes(1)
199
204
  })
200
205
 
@@ -217,9 +222,9 @@ describe('RegistryClient', () => {
217
222
  })
218
223
  )
219
224
 
220
- expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_protomap'], {
225
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_protomap'], expect.objectContaining({
221
226
  networkPreset: 'main'
222
- })
227
+ }))
223
228
  expect(mockBroadcast).toHaveBeenCalledTimes(1)
224
229
  })
225
230
 
@@ -242,9 +247,9 @@ describe('RegistryClient', () => {
242
247
  })
243
248
  )
244
249
 
245
- expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_certmap'], {
250
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_certmap'], expect.objectContaining({
246
251
  networkPreset: 'main'
247
- })
252
+ }))
248
253
  expect(mockBroadcast).toHaveBeenCalledTimes(1)
249
254
  })
250
255
 
@@ -272,32 +277,30 @@ describe('RegistryClient', () => {
272
277
  // ------------------------------------------------------------------
273
278
  describe('resolve', () => {
274
279
  it('should return empty array if resolver does not return output-list', async () => {
275
- ; (LookupResolver as jest.Mock).mockImplementation(() => ({
276
- query: jest.fn().mockResolvedValue({ type: 'not-output-list' })
277
- }))
280
+ // Mock the instance resolver
281
+ ; (registryClient as any).resolver.query = jest.fn().mockResolvedValue({ type: 'not-output-list' })
278
282
 
279
283
  const result = await registryClient.resolve('basket', { name: 'foo' })
280
284
  expect(result).toEqual([])
281
285
  })
282
286
 
283
287
  it('should parse outputs from resolver if type is output-list', async () => {
284
- ; (LookupResolver as jest.Mock).mockImplementation(() => ({
285
- query: jest.fn().mockResolvedValue({
286
- type: 'output-list',
287
- outputs: [{ beef: [9, 9, 9], outputIndex: 0 }]
288
- })
289
- }))
288
+ // Mock the instance resolver
289
+ ; (registryClient as any).resolver.query = jest.fn().mockResolvedValue({
290
+ type: 'output-list',
291
+ outputs: [{ beef: [9, 9, 9], outputIndex: 0 }]
292
+ })
290
293
 
291
- // The code expects 7 fields for basket (6 definition fields + 1 extra signature field)
294
+ // Basket has 7 fields: 5 data fields + operator + signature
292
295
  ; (PushDrop.decode as jest.Mock).mockReturnValue({
293
296
  fields: [
294
- [98], // 'b'
295
- [97], // 'a'
296
- [115], // 's'
297
- [107], // 'k'
298
- [101], // 'e'
299
- [116], // 't' => operator
300
- [111] // extra signature field
297
+ [98], // 'b' - basketID
298
+ [97], // 'a' - name
299
+ [115], // 's' - iconURL
300
+ [107], // 'k' - description
301
+ [101], // 'e' - documentationURL
302
+ [116], // 't' - operator
303
+ [111] // signature field
301
304
  ]
302
305
  })
303
306
 
@@ -320,15 +323,14 @@ describe('RegistryClient', () => {
320
323
  })
321
324
 
322
325
  it('should skip outputs that fail parseLockingScript', async () => {
323
- ; (LookupResolver as jest.Mock).mockImplementation(() => ({
324
- query: jest.fn().mockResolvedValue({
325
- type: 'output-list',
326
- outputs: [
327
- { beef: [1, 1, 1], outputIndex: 0 },
328
- { beef: [2, 2, 2], outputIndex: 1 }
329
- ]
330
- })
331
- }))
326
+ // Mock the instance resolver
327
+ ; (registryClient as any).resolver.query = jest.fn().mockResolvedValue({
328
+ type: 'output-list',
329
+ outputs: [
330
+ { beef: [1, 1, 1], outputIndex: 0 },
331
+ { beef: [2, 2, 2], outputIndex: 1 }
332
+ ]
333
+ })
332
334
 
333
335
  // Return empty fields so parseLockingScript fails the length check
334
336
  ; (PushDrop.decode as jest.Mock)
@@ -373,22 +375,21 @@ describe('RegistryClient', () => {
373
375
  });
374
376
 
375
377
  // Use a mockImplementation to inspect the lockingScript and return appropriate decoded fields.
378
+ // Basket has 7 fields: 5 data fields + operator + signature
376
379
  (PushDrop.decode as jest.Mock).mockImplementation((scriptObj) => {
377
380
  return {
378
381
  fields: [
379
- [98], // 'b'
380
- [97], // 'a'
381
- [115], // 's'
382
- [107], // 'k'
383
- [101], // 'e'
384
- [116], // 't'
385
- [111] // extra signature field
382
+ [98], // 'b' - basketID
383
+ [97], // 'a' - name
384
+ [115], // 's' - iconURL
385
+ [107], // 'k' - description
386
+ [101], // 'e' - documentationURL
387
+ [116], // 't' - operator
388
+ [111] // signature field
386
389
  ]
387
390
  }
388
391
  });
389
392
 
390
- (walletMock.getPublicKey as jest.Mock).mockResolvedValue({ publicKey: 't' }); // <-- Semicolon
391
-
392
393
  const records = await registryClient.listOwnRegistryEntries('basket');
393
394
  expect(walletMock.listOutputs).toHaveBeenCalledWith({
394
395
  basket: 'basketmap',
@@ -408,9 +409,9 @@ describe('RegistryClient', () => {
408
409
  })
409
410
 
410
411
  // ------------------------------------------------------------------
411
- // revokeOwnRegistryEntry
412
+ // removeDefinition
412
413
  // ------------------------------------------------------------------
413
- describe('revokeOwnRegistryEntry', () => {
414
+ describe('removeDefinition', () => {
414
415
  let validRecord: RegistryRecord
415
416
 
416
417
  beforeEach(() => {
@@ -430,26 +431,26 @@ describe('RegistryClient', () => {
430
431
  }
431
432
  })
432
433
 
433
- it('should revoke a record successfully (networkPreset=main)', async () => {
434
- const result = await registryClient.revokeOwnRegistryEntry(validRecord)
434
+ it('should remove a record successfully (networkPreset=main)', async () => {
435
+ const result = await registryClient.removeDefinition(validRecord)
435
436
  expect(result).toBe('mockBroadcastSuccess')
436
437
 
437
438
  expect(walletMock.createAction).toHaveBeenCalledWith(
438
439
  expect.objectContaining({
439
- description: 'Revoke basket item: myBasket',
440
+ description: 'Remove basket item: myBasket',
440
441
  inputs: [
441
442
  {
442
443
  outpoint: 'someTxId.0',
443
- unlockingScriptLength: 73,
444
- inputDescription: 'Revoking basket token'
444
+ unlockingScriptLength: 74,
445
+ inputDescription: 'Removing basket token'
445
446
  }
446
447
  ]
447
448
  })
448
449
  )
449
450
 
450
- expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], {
451
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], expect.objectContaining({
451
452
  networkPreset: 'main'
452
- })
453
+ }))
453
454
  expect(mockBroadcast).toHaveBeenCalled()
454
455
  })
455
456
 
@@ -458,23 +459,179 @@ describe('RegistryClient', () => {
458
459
  tx: [1, 2, 3],
459
460
  signableTransaction: undefined
460
461
  })
461
- await expect(registryClient.revokeOwnRegistryEntry(validRecord)).rejects.toThrow(
462
+ await expect(registryClient.removeDefinition(validRecord)).rejects.toThrow(
462
463
  'Failed to create signable transaction.'
463
464
  )
464
465
  })
465
466
 
466
467
  it('should throw if signAction returns no signedTx', async () => {
467
468
  ; (walletMock.signAction as jest.Mock).mockResolvedValueOnce({ tx: undefined })
468
- await expect(registryClient.revokeOwnRegistryEntry(validRecord)).rejects.toThrow(
469
+ await expect(registryClient.removeDefinition(validRecord)).rejects.toThrow(
469
470
  'Failed to finalize the transaction signature.'
470
471
  )
471
472
  })
472
473
 
473
474
  it('should propagate broadcast errors', async () => {
474
475
  mockBroadcast.mockRejectedValueOnce(new Error('Broadcast failure!'))
475
- await expect(registryClient.revokeOwnRegistryEntry(validRecord)).rejects.toThrow(
476
+ await expect(registryClient.removeDefinition(validRecord)).rejects.toThrow(
476
477
  'Broadcast failure!'
477
478
  )
478
479
  })
480
+
481
+ it('should throw if registry record is missing required fields', async () => {
482
+ const invalidRecord = { ...validRecord, txid: undefined }
483
+ await expect(registryClient.removeDefinition(invalidRecord as any)).rejects.toThrow(
484
+ 'Invalid registry record. Missing txid, outputIndex, or lockingScript.'
485
+ )
486
+ })
487
+
488
+ it('should throw if registry record has undefined outputIndex', async () => {
489
+ const invalidRecord = { ...validRecord, outputIndex: undefined }
490
+ await expect(registryClient.removeDefinition(invalidRecord as any)).rejects.toThrow(
491
+ 'Invalid registry record. Missing txid, outputIndex, or lockingScript.'
492
+ )
493
+ })
494
+
495
+ it('should throw if registry record is missing lockingScript', async () => {
496
+ const invalidRecord = { ...validRecord, lockingScript: undefined }
497
+ await expect(registryClient.removeDefinition(invalidRecord as any)).rejects.toThrow(
498
+ 'Invalid registry record. Missing txid, outputIndex, or lockingScript.'
499
+ )
500
+ })
501
+
502
+ it('should throw if registry record does not belong to current wallet', async () => {
503
+ const otherUserRecord = { ...validRecord, registryOperator: 'differentPublicKey' }
504
+ await expect(registryClient.removeDefinition(otherUserRecord)).rejects.toThrow(
505
+ 'This registry token does not belong to the current wallet.'
506
+ )
507
+ })
508
+
509
+ it('should handle protocol definition removal with correct description', async () => {
510
+ const protocolRecord: RegistryRecord = {
511
+ definitionType: 'protocol',
512
+ protocolID: [1, 'testProtocol'],
513
+ name: 'Test Protocol',
514
+ iconURL: 'url',
515
+ description: 'desc',
516
+ documentationURL: 'docURL',
517
+ txid: 'protocolTxId',
518
+ outputIndex: 1,
519
+ satoshis: 1000,
520
+ lockingScript: 'protocolLockingScript',
521
+ registryOperator: 'mockPublicKey',
522
+ beef: [0, 1, 2]
523
+ }
524
+
525
+ const result = await registryClient.removeDefinition(protocolRecord)
526
+ expect(result).toBe('mockBroadcastSuccess')
527
+
528
+ expect(walletMock.createAction).toHaveBeenCalledWith(
529
+ expect.objectContaining({
530
+ description: 'Remove protocol item: Test Protocol',
531
+ inputs: [
532
+ {
533
+ outpoint: 'protocolTxId.1',
534
+ unlockingScriptLength: 74,
535
+ inputDescription: 'Removing protocol token'
536
+ }
537
+ ]
538
+ })
539
+ )
540
+
541
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_protomap'], expect.objectContaining({
542
+ networkPreset: 'main'
543
+ }))
544
+ })
545
+
546
+ it('should handle certificate definition removal with correct description', async () => {
547
+ const certificateRecord: RegistryRecord = {
548
+ definitionType: 'certificate',
549
+ type: 'testCert',
550
+ name: 'Test Certificate',
551
+ iconURL: 'url',
552
+ description: 'desc',
553
+ documentationURL: 'docURL',
554
+ fields: {},
555
+ txid: 'certTxId',
556
+ outputIndex: 2,
557
+ satoshis: 1000,
558
+ lockingScript: 'certLockingScript',
559
+ registryOperator: 'mockPublicKey',
560
+ beef: [0, 1, 2]
561
+ }
562
+
563
+ const result = await registryClient.removeDefinition(certificateRecord)
564
+ expect(result).toBe('mockBroadcastSuccess')
565
+
566
+ expect(walletMock.createAction).toHaveBeenCalledWith(
567
+ expect.objectContaining({
568
+ description: 'Remove certificate item: Test Certificate',
569
+ inputs: [
570
+ {
571
+ outpoint: 'certTxId.2',
572
+ unlockingScriptLength: 74,
573
+ inputDescription: 'Removing certificate token'
574
+ }
575
+ ]
576
+ })
577
+ )
578
+
579
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_certmap'], expect.objectContaining({
580
+ networkPreset: 'main'
581
+ }))
582
+ })
583
+
584
+ it('should use certificate type as identifier when name is undefined', async () => {
585
+ const certificateRecord: RegistryRecord = {
586
+ definitionType: 'certificate',
587
+ type: 'testCertType',
588
+ name: undefined as any,
589
+ iconURL: 'url',
590
+ description: 'desc',
591
+ documentationURL: 'docURL',
592
+ fields: {},
593
+ txid: 'certTxId',
594
+ outputIndex: 2,
595
+ satoshis: 1000,
596
+ lockingScript: 'certLockingScript',
597
+ registryOperator: 'mockPublicKey',
598
+ beef: [0, 1, 2]
599
+ }
600
+
601
+ const result = await registryClient.removeDefinition(certificateRecord)
602
+ expect(result).toBe('mockBroadcastSuccess')
603
+
604
+ expect(walletMock.createAction).toHaveBeenCalledWith(
605
+ expect.objectContaining({
606
+ description: 'Remove certificate item: testCertType'
607
+ })
608
+ )
609
+ })
610
+
611
+ it('should use acceptDelayedBroadcast setting from constructor', async () => {
612
+ // Create a new client with acceptDelayedBroadcast: true
613
+ const clientWithDelayedBroadcast = new RegistryClient(walletMock as WalletInterface, { acceptDelayedBroadcast: true })
614
+ ; (clientWithDelayedBroadcast as any).resolver = {
615
+ query: jest.fn().mockResolvedValue({ type: 'output-list', outputs: [] })
616
+ }
617
+
618
+ await clientWithDelayedBroadcast.removeDefinition(validRecord)
619
+
620
+ expect(walletMock.createAction).toHaveBeenCalledWith(
621
+ expect.objectContaining({
622
+ options: expect.objectContaining({
623
+ acceptDelayedBroadcast: true
624
+ })
625
+ })
626
+ )
627
+
628
+ expect(walletMock.signAction).toHaveBeenCalledWith(
629
+ expect.objectContaining({
630
+ options: expect.objectContaining({
631
+ acceptDelayedBroadcast: true
632
+ })
633
+ })
634
+ )
635
+ })
479
636
  })
480
637
  })