@bsv/sdk 1.9.1 → 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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/registry/RegistryClient.js +144 -25
- package/dist/cjs/src/registry/RegistryClient.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/registry/RegistryClient.js +148 -26
- package/dist/esm/src/registry/RegistryClient.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/registry/RegistryClient.d.ts +31 -5
- package/dist/types/src/registry/RegistryClient.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -3
- package/dist/umd/bundle.js.map +1 -1
- package/docs/reference/registry.md +34 -9
- package/package.json +1 -1
- package/src/registry/RegistryClient.ts +180 -36
- package/src/registry/__tests/RegistryClient.test.ts +211 -54
|
@@ -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().
|
|
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
|
-
|
|
276
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
//
|
|
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'
|
|
300
|
-
[111] //
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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] //
|
|
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
|
-
//
|
|
412
|
+
// removeDefinition
|
|
412
413
|
// ------------------------------------------------------------------
|
|
413
|
-
describe('
|
|
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
|
|
434
|
-
const result = await registryClient.
|
|
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: '
|
|
440
|
+
description: 'Remove basket item: myBasket',
|
|
440
441
|
inputs: [
|
|
441
442
|
{
|
|
442
443
|
outpoint: 'someTxId.0',
|
|
443
|
-
unlockingScriptLength:
|
|
444
|
-
inputDescription: '
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
})
|