@bsv/sdk 1.4.2 → 1.4.4

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.
@@ -23,7 +23,7 @@ jest.mock('../../overlay-tools/index.js', () => {
23
23
  broadcast: mockBroadcast
24
24
  })),
25
25
  LookupResolver: jest.fn().mockImplementation(() => ({
26
- query: jest.fn()
26
+ query: jest.fn() // We'll override in tests
27
27
  }))
28
28
  }
29
29
  })
@@ -42,8 +42,7 @@ jest.mock('../../script/index.js', () => {
42
42
  })
43
43
  })),
44
44
  {
45
- // Ensure decode is a Jest mock
46
- decode: jest.fn()
45
+ decode: jest.fn() // We'll override in tests
47
46
  }
48
47
  ),
49
48
  LockingScript: {
@@ -52,13 +51,11 @@ jest.mock('../../script/index.js', () => {
52
51
  }
53
52
  })
54
53
 
55
- // Ensure `PushDrop.decode` is recognized as a Jest mock
56
- ; (PushDrop as any).decode = jest.fn()
57
-
58
54
  jest.mock('../../transaction/index.js', () => {
59
55
  return {
60
56
  Transaction: {
61
57
  fromAtomicBEEF: jest.fn().mockImplementation((_tx: number[]) => ({
58
+ // minimal mock
62
59
  toHexBEEF: () => 'mockTxHexBEEF',
63
60
  outputs: [
64
61
  { lockingScript: 'mockLockingScriptObject0' },
@@ -66,7 +63,17 @@ jest.mock('../../transaction/index.js', () => {
66
63
  { lockingScript: 'mockLockingScriptObject2' }
67
64
  ]
68
65
  })),
69
- fromBEEF: jest.fn().mockImplementation((_tx: number[]) => ({}))
66
+ fromBEEF: jest.fn().mockImplementation((_tx: number[]) => ({
67
+ outputs: [
68
+ { lockingScript: 'decodedLockScript0' },
69
+ { lockingScript: 'decodedLockScript1' },
70
+ {
71
+ lockingScript: {
72
+ toHex: jest.fn().mockImplementation(() => 'decodedLockScript1AsHex')
73
+ }
74
+ }
75
+ ]
76
+ }))
70
77
  }
71
78
  }
72
79
  })
@@ -108,8 +115,7 @@ function buildDefinitionData(type: DefinitionType): DefinitionData {
108
115
  case 'protocol': {
109
116
  const data: ProtocolDefinitionData = {
110
117
  definitionType: 'protocol',
111
- protocolID: 'someProtocolId',
112
- securityLevel: 1,
118
+ protocolID: [1, 'someProtocolId'],
113
119
  name: 'Test Protocol',
114
120
  iconURL: 'https://someiconurl.com',
115
121
  description: 'Protocol Description',
@@ -159,7 +165,6 @@ describe('RegistryClient', () => {
159
165
 
160
166
  registryClient = new RegistryClient(walletMock as WalletInterface)
161
167
 
162
- // Clear all mock calls
163
168
  jest.clearAllMocks()
164
169
  mockBroadcast.mockClear()
165
170
  })
@@ -173,19 +178,23 @@ describe('RegistryClient', () => {
173
178
  const result = await registryClient.registerDefinition(data)
174
179
  expect(result).toBe('mockBroadcastSuccess')
175
180
 
176
- expect(walletMock.createAction).toHaveBeenCalledWith({
177
- description: 'Register a new basket item',
178
- outputs: expect.arrayContaining([
179
- expect.objectContaining({
180
- satoshis: 1,
181
- outputDescription: 'New basket registration token'
182
- })
183
- ])
184
- })
185
- expect(TopicBroadcaster).toHaveBeenCalledWith(
186
- ['tm_basketmap'],
187
- { networkPreset: 'main' }
181
+ // We use partial matching so extra fields (like options) are allowed
182
+ expect(walletMock.createAction).toHaveBeenCalledWith(
183
+ expect.objectContaining({
184
+ description: 'Register a new basket item',
185
+ outputs: expect.arrayContaining([
186
+ expect.objectContaining({
187
+ satoshis: 1,
188
+ outputDescription: 'New basket registration token',
189
+ basket: 'basketmap',
190
+ lockingScript: 'mockLockingScriptHex'
191
+ })
192
+ ])
193
+ })
188
194
  )
195
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], {
196
+ networkPreset: 'main'
197
+ })
189
198
  expect(mockBroadcast).toHaveBeenCalledTimes(1)
190
199
  })
191
200
 
@@ -194,20 +203,23 @@ describe('RegistryClient', () => {
194
203
  const result = await registryClient.registerDefinition(data)
195
204
  expect(result).toBe('mockBroadcastSuccess')
196
205
 
197
- expect(walletMock.createAction).toHaveBeenCalledWith({
198
- description: 'Register a new protocol item',
199
- outputs: expect.arrayContaining([
200
- expect.objectContaining({
201
- satoshis: 1,
202
- outputDescription: 'New protocol registration token'
203
- })
204
- ])
205
- })
206
-
207
- expect(TopicBroadcaster).toHaveBeenCalledWith(
208
- ['tm_protomap'],
209
- { networkPreset: 'main' }
206
+ expect(walletMock.createAction).toHaveBeenCalledWith(
207
+ expect.objectContaining({
208
+ description: 'Register a new protocol item',
209
+ outputs: expect.arrayContaining([
210
+ expect.objectContaining({
211
+ satoshis: 1,
212
+ outputDescription: 'New protocol registration token',
213
+ basket: 'protomap',
214
+ lockingScript: 'mockLockingScriptHex'
215
+ })
216
+ ])
217
+ })
210
218
  )
219
+
220
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_protomap'], {
221
+ networkPreset: 'main'
222
+ })
211
223
  expect(mockBroadcast).toHaveBeenCalledTimes(1)
212
224
  })
213
225
 
@@ -216,20 +228,23 @@ describe('RegistryClient', () => {
216
228
  const result = await registryClient.registerDefinition(data)
217
229
  expect(result).toBe('mockBroadcastSuccess')
218
230
 
219
- expect(walletMock.createAction).toHaveBeenCalledWith({
220
- description: 'Register a new certificate item',
221
- outputs: expect.arrayContaining([
222
- expect.objectContaining({
223
- satoshis: 1,
224
- outputDescription: 'New certificate registration token'
225
- })
226
- ])
227
- })
228
-
229
- expect(TopicBroadcaster).toHaveBeenCalledWith(
230
- ['tm_certmap'],
231
- { networkPreset: 'main' }
231
+ expect(walletMock.createAction).toHaveBeenCalledWith(
232
+ expect.objectContaining({
233
+ description: 'Register a new certificate item',
234
+ outputs: expect.arrayContaining([
235
+ expect.objectContaining({
236
+ satoshis: 1,
237
+ outputDescription: 'New certificate registration token',
238
+ basket: 'certmap',
239
+ lockingScript: 'mockLockingScriptHex'
240
+ })
241
+ ])
242
+ })
232
243
  )
244
+
245
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_certmap'], {
246
+ networkPreset: 'main'
247
+ })
233
248
  expect(mockBroadcast).toHaveBeenCalledTimes(1)
234
249
  })
235
250
 
@@ -244,9 +259,10 @@ describe('RegistryClient', () => {
244
259
  })
245
260
 
246
261
  it('should throw an error on invalid definition type', async () => {
247
- const invalidData = { definitionType: 'invalidType' } as any as DefinitionData
262
+ // We expect "Unsupported definition type" if that’s what your code throws
263
+ const invalidData = { definitionType: 'invalidType' } as unknown as DefinitionData
248
264
  await expect(registryClient.registerDefinition(invalidData)).rejects.toThrow(
249
- 'Invalid registry kind specified'
265
+ 'Unsupported definition type'
250
266
  )
251
267
  })
252
268
  })
@@ -256,8 +272,8 @@ describe('RegistryClient', () => {
256
272
  // ------------------------------------------------------------------
257
273
  describe('resolve', () => {
258
274
  it('should return empty array if resolver does not return output-list', async () => {
259
- (LookupResolver as jest.Mock).mockImplementation(() => ({
260
- query: jest.fn().mockResolvedValue({ type: 'unknown' })
275
+ ; (LookupResolver as jest.Mock).mockImplementation(() => ({
276
+ query: jest.fn().mockResolvedValue({ type: 'not-output-list' })
261
277
  }))
262
278
 
263
279
  const result = await registryClient.resolve('basket', { name: 'foo' })
@@ -268,26 +284,27 @@ describe('RegistryClient', () => {
268
284
  ; (LookupResolver as jest.Mock).mockImplementation(() => ({
269
285
  query: jest.fn().mockResolvedValue({
270
286
  type: 'output-list',
271
- outputs: [
272
- { beef: [9, 9, 9], outputIndex: 0 }
273
- ]
287
+ outputs: [{ beef: [9, 9, 9], outputIndex: 0 }]
274
288
  })
275
289
  }))
276
290
 
277
- // Mock decode once, so the code that does `PushDrop.decode(...)` returns some fields
291
+ // The code expects 7 fields for basket (6 definition fields + 1 extra signature field)
278
292
  ; (PushDrop.decode as jest.Mock).mockReturnValue({
279
293
  fields: [
280
- [98], // 'b'
281
- [97], // 'a'
294
+ [98], // 'b'
295
+ [97], // 'a'
282
296
  [115], // 's'
283
297
  [107], // 'k'
284
298
  [101], // 'e'
285
- [116] // 't' => operator
299
+ [116], // 't' => operator
300
+ [111] // extra signature field
286
301
  ]
287
302
  })
288
303
 
289
- // The final field must match the current wallet pubkey => 't'
290
- ; (walletMock.getPublicKey as jest.Mock).mockResolvedValueOnce({ publicKey: 't' })
304
+ // The final field must match the current wallet pubkey => 'mockPublicKey'
305
+ ; (walletMock.getPublicKey as jest.Mock).mockResolvedValueOnce({
306
+ publicKey: 't'
307
+ })
291
308
 
292
309
  const result = await registryClient.resolve('basket', { basketID: 'whatever' })
293
310
  expect(result).toHaveLength(1)
@@ -313,10 +330,10 @@ describe('RegistryClient', () => {
313
330
  })
314
331
  }))
315
332
 
316
- // We do two .mockReturnValueOnce calls so each decode returns something different.
333
+ // Return empty fields so parseLockingScript fails the length check
317
334
  ; (PushDrop.decode as jest.Mock)
318
- .mockReturnValueOnce({ fields: [] })
319
- .mockReturnValueOnce({ fields: [] })
335
+ .mockReturnValueOnce({ fields: [] }) // fail
336
+ .mockReturnValueOnce({ fields: [] }) // fail again
320
337
 
321
338
  const result = await registryClient.resolve('basket', { name: 'fooAgain' })
322
339
  expect(result).toEqual([])
@@ -328,51 +345,66 @@ describe('RegistryClient', () => {
328
345
  // ------------------------------------------------------------------
329
346
  describe('listOwnRegistryEntries', () => {
330
347
  it('should parse and return registry records from wallet outputs', async () => {
331
- // The wallet returns 3 outputs, but we skip any "spendable" or parse-failing ones
332
- ; (walletMock.listOutputs as jest.Mock).mockResolvedValue({
348
+ // The wallet returns 3 outputs; only one is spendable
349
+ (walletMock.listOutputs as jest.Mock).mockResolvedValue({
333
350
  outputs: [
334
351
  {
335
352
  outpoint: 'abc123.0',
336
353
  satoshis: 1000,
337
- lockingScript: 'someLockingScriptHex',
354
+ lockingScript: 'lsHexA',
338
355
  spendable: false
339
356
  },
340
357
  {
341
358
  outpoint: 'xyz999.1',
342
359
  satoshis: 500,
343
- lockingScript: 'badLockingScriptHex',
360
+ lockingScript: 'lsHexB',
344
361
  spendable: false
345
362
  },
346
363
  {
347
364
  outpoint: 'skipMe.2',
348
365
  satoshis: 200,
349
- lockingScript: 'skipLockingScriptHex',
366
+ lockingScript: {
367
+ toHex: jest.fn(() => 'lsHexC')
368
+ },
350
369
  spendable: true
351
370
  }
352
- ]
353
- })
371
+ ],
372
+ BEEF: [0, 1, 2, 3]
373
+ });
354
374
 
355
- // We decode the first output successfully => 6 fields => valid basket
356
- // Then the second decode => parse fails
357
- ; (PushDrop.decode as jest.Mock)
358
- .mockReturnValueOnce({ fields: Array(6).fill([97]) })
359
- .mockReturnValueOnce({ fields: [] })
360
- ; (walletMock.getPublicKey as jest.Mock).mockResolvedValueOnce({ publicKey: 'a' })
375
+ // Use a mockImplementation to inspect the lockingScript and return appropriate decoded fields.
376
+ (PushDrop.decode as jest.Mock).mockImplementation((scriptObj) => {
377
+ return {
378
+ fields: [
379
+ [98], // 'b'
380
+ [97], // 'a'
381
+ [115], // 's'
382
+ [107], // 'k'
383
+ [101], // 'e'
384
+ [116], // 't'
385
+ [111] // extra signature field
386
+ ]
387
+ }
388
+ });
361
389
 
362
- const records = await registryClient.listOwnRegistryEntries('basket')
390
+ (walletMock.getPublicKey as jest.Mock).mockResolvedValue({ publicKey: 't' }); // <-- Semicolon
391
+
392
+ const records = await registryClient.listOwnRegistryEntries('basket');
363
393
  expect(walletMock.listOutputs).toHaveBeenCalledWith({
364
394
  basket: 'basketmap',
365
- include: 'locking scripts'
366
- })
367
- expect(records).toHaveLength(1)
395
+ include: 'entire transactions'
396
+ });
397
+ // Only one spendable item should be returned if parsing succeeds.
398
+ expect(records).toHaveLength(1);
368
399
  expect(records[0]).toMatchObject({
369
400
  definitionType: 'basket',
370
- txid: 'abc123',
371
- outputIndex: 0,
372
- satoshis: 1000,
373
- lockingScript: 'someLockingScriptHex'
374
- })
375
- })
401
+ txid: 'skipMe',
402
+ outputIndex: 2,
403
+ satoshis: 200,
404
+ lockingScript: 'decodedLockScript1AsHex'
405
+ });
406
+ });
407
+
376
408
  })
377
409
 
378
410
  // ------------------------------------------------------------------
@@ -393,7 +425,8 @@ describe('RegistryClient', () => {
393
425
  outputIndex: 0,
394
426
  satoshis: 1000,
395
427
  lockingScript: 'someLockingScriptHex',
396
- registryOperator: 'mockPublicKey'
428
+ registryOperator: 'mockPublicKey',
429
+ beef: [0, 1, 2]
397
430
  }
398
431
  })
399
432
 
@@ -401,18 +434,22 @@ describe('RegistryClient', () => {
401
434
  const result = await registryClient.revokeOwnRegistryEntry(validRecord)
402
435
  expect(result).toBe('mockBroadcastSuccess')
403
436
 
404
- expect(walletMock.createAction).toHaveBeenCalledWith({
405
- description: 'Revoke basket item: myBasket',
406
- inputs: [
407
- {
408
- outpoint: 'someTxId.0',
409
- unlockingScriptLength: 73,
410
- inputDescription: 'Revoking basket token'
411
- }
412
- ]
413
- })
437
+ expect(walletMock.createAction).toHaveBeenCalledWith(
438
+ expect.objectContaining({
439
+ description: 'Revoke basket item: myBasket',
440
+ inputs: [
441
+ {
442
+ outpoint: 'someTxId.0',
443
+ unlockingScriptLength: 73,
444
+ inputDescription: 'Revoking basket token'
445
+ }
446
+ ]
447
+ })
448
+ )
414
449
 
415
- expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], { networkPreset: 'main' })
450
+ expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], {
451
+ networkPreset: 'main'
452
+ })
416
453
  expect(mockBroadcast).toHaveBeenCalled()
417
454
  })
418
455
 
@@ -435,7 +472,6 @@ describe('RegistryClient', () => {
435
472
 
436
473
  it('should propagate broadcast errors', async () => {
437
474
  mockBroadcast.mockRejectedValueOnce(new Error('Broadcast failure!'))
438
-
439
475
  await expect(registryClient.revokeOwnRegistryEntry(validRecord)).rejects.toThrow(
440
476
  'Broadcast failure!'
441
477
  )
@@ -1,10 +1,8 @@
1
- import { PubKeyHex } from '../../wallet/index.js'
1
+ import { BEEF, PubKeyHex, WalletProtocol } from '../../wallet/index.js'
2
2
 
3
3
  /**
4
- * Determines which category of registry item we are working with.
5
- * - "basket" corresponds to BasketMap
6
- * - "protocol" corresponds to ProtoMap
7
- * - "certificate" corresponds to CertMap
4
+ * We unify the registry “type” to these three strings everywhere:
5
+ * 'basket' | 'protocol' | 'certificate'
8
6
  */
9
7
  export type DefinitionType = 'basket' | 'protocol' | 'certificate'
10
8
 
@@ -19,7 +17,7 @@ export interface CertificateFieldDescriptor {
19
17
  }
20
18
 
21
19
  /**
22
- * Registry data for a Basket-style record (BasketMap).
20
+ * Registry data for a Basket-style record.
23
21
  */
24
22
  export interface BasketDefinitionData {
25
23
  definitionType: 'basket'
@@ -32,12 +30,11 @@ export interface BasketDefinitionData {
32
30
  }
33
31
 
34
32
  /**
35
- * Registry data for a Proto-style record (ProtoMap).
33
+ * Registry data for a Protocol-style record.
36
34
  */
37
35
  export interface ProtocolDefinitionData {
38
36
  definitionType: 'protocol'
39
- protocolID: string
40
- securityLevel: 0 | 1 | 2
37
+ protocolID: WalletProtocol
41
38
  name: string
42
39
  iconURL: string
43
40
  description: string
@@ -46,7 +43,7 @@ export interface ProtocolDefinitionData {
46
43
  }
47
44
 
48
45
  /**
49
- * Registry data for a Cert-style record (CertMap).
46
+ * Registry data for a Certificate-style record.
50
47
  */
51
48
  export interface CertificateDefinitionData {
52
49
  definitionType: 'certificate'
@@ -59,43 +56,76 @@ export interface CertificateDefinitionData {
59
56
  registryOperator?: PubKeyHex
60
57
  }
61
58
 
59
+ /**
60
+ * Union of all possible definition data objects.
61
+ */
62
62
  export type DefinitionData =
63
63
  | BasketDefinitionData
64
64
  | ProtocolDefinitionData
65
65
  | CertificateDefinitionData
66
66
 
67
+ /**
68
+ * Common info for the on-chain token/UTXO that points to a registry entry.
69
+ */
67
70
  export interface TokenData {
68
71
  txid: string
69
72
  outputIndex: number
70
73
  satoshis: number
71
- lockingScript: string
74
+ lockingScript: string,
75
+ beef: BEEF
72
76
  }
73
77
 
78
+ /**
79
+ * A registry record is a combination of the typed definition data
80
+ * plus the on-chain token data for the UTXO holding it.
81
+ */
74
82
  export type RegistryRecord = DefinitionData & TokenData
75
83
 
76
- // Lookup Query Types (Note: can be shared types with lookup service)
84
+ // -------------------------------------------------------------------------
85
+ // Query type definitions
86
+ // -------------------------------------------------------------------------
77
87
 
78
- interface BasketMapQuery {
88
+ /**
89
+ * When searching for basket definitions, we can filter by:
90
+ * - basketID
91
+ * - registryOperators
92
+ * - name
93
+ */
94
+ export interface BasketQuery {
79
95
  basketID?: string
80
96
  registryOperators?: string[]
81
97
  name?: string
82
98
  }
83
99
 
84
- interface ProtoMapQuery {
100
+ /**
101
+ * When searching for protocol definitions, we can filter by:
102
+ * - name
103
+ * - registryOperators
104
+ * - protocolID
105
+ */
106
+ export interface ProtocolQuery {
85
107
  name?: string
86
108
  registryOperators?: string[]
87
- protocolID?: string
88
- securityLevel?: number
109
+ protocolID?: WalletProtocol
89
110
  }
90
111
 
91
- interface CertMapQuery {
112
+ /**
113
+ * When searching for certificate definitions, we can filter by:
114
+ * - type
115
+ * - name
116
+ * - registryOperators
117
+ */
118
+ export interface CertificateQuery {
92
119
  type?: string
93
120
  name?: string
94
121
  registryOperators?: string[]
95
122
  }
96
123
 
124
+ /**
125
+ * A lookup-service mapping of queries by each definition type.
126
+ */
97
127
  export interface RegistryQueryMapping {
98
- basket: BasketMapQuery
99
- protocol: ProtoMapQuery
100
- certificate: CertMapQuery
128
+ basket: BasketQuery
129
+ protocol: ProtocolQuery
130
+ certificate: CertificateQuery
101
131
  }