@bsv/sdk 1.4.0 → 1.4.1
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/mod.js +2 -0
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/identity/IdentityClient.js +258 -0
- package/dist/cjs/src/identity/IdentityClient.js.map +1 -0
- package/dist/cjs/src/identity/index.js +19 -0
- package/dist/cjs/src/identity/index.js.map +1 -0
- package/dist/cjs/src/identity/types/index.js +30 -0
- package/dist/cjs/src/identity/types/index.js.map +1 -0
- package/dist/cjs/src/registry/RegistryClient.js +392 -0
- package/dist/cjs/src/registry/RegistryClient.js.map +1 -0
- package/dist/cjs/src/registry/index.js +19 -0
- package/dist/cjs/src/registry/index.js.map +1 -0
- package/dist/cjs/src/registry/types/index.js +3 -0
- package/dist/cjs/src/registry/types/index.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/mod.js +2 -0
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/identity/IdentityClient.js +255 -0
- package/dist/esm/src/identity/IdentityClient.js.map +1 -0
- package/dist/esm/src/identity/index.js +3 -0
- package/dist/esm/src/identity/index.js.map +1 -0
- package/dist/esm/src/identity/types/index.js +27 -0
- package/dist/esm/src/identity/types/index.js.map +1 -0
- package/dist/esm/src/registry/RegistryClient.js +388 -0
- package/dist/esm/src/registry/RegistryClient.js.map +1 -0
- package/dist/esm/src/registry/index.js +3 -0
- package/dist/esm/src/registry/index.js.map +1 -0
- package/dist/esm/src/registry/types/index.js +2 -0
- package/dist/esm/src/registry/types/index.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/mod.d.ts +2 -0
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/identity/IdentityClient.d.ts +50 -0
- package/dist/types/src/identity/IdentityClient.d.ts.map +1 -0
- package/dist/types/src/identity/index.d.ts +3 -0
- package/dist/types/src/identity/index.d.ts.map +1 -0
- package/dist/types/src/identity/types/index.d.ts +30 -0
- package/dist/types/src/identity/types/index.d.ts.map +1 -0
- package/dist/types/src/registry/RegistryClient.d.ts +94 -0
- package/dist/types/src/registry/RegistryClient.d.ts.map +1 -0
- package/dist/types/src/registry/index.d.ts +3 -0
- package/dist/types/src/registry/index.d.ts.map +1 -0
- package/dist/types/src/registry/types/index.d.ts +86 -0
- package/dist/types/src/registry/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/mod.ts +3 -1
- package/package.json +2 -2
- package/src/identity/IdentityClient.ts +305 -0
- package/src/identity/README.md +93 -0
- package/src/identity/__tests/IdentityClient.test.ts +278 -0
- package/src/identity/index.ts +2 -0
- package/src/identity/types/index.ts +46 -0
- package/src/registry/RegistryClient.ts +493 -0
- package/src/registry/__tests/RegistryClient.test.ts +444 -0
- package/src/registry/index.ts +2 -0
- package/src/registry/types/index.ts +101 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { RegistryClient } from '../RegistryClient'
|
|
2
|
+
import { WalletInterface } from '../../wallet/index.js'
|
|
3
|
+
import { TopicBroadcaster, LookupResolver } from '../../overlay-tools/index.js'
|
|
4
|
+
import { PushDrop } from '../../script/index.js'
|
|
5
|
+
import {
|
|
6
|
+
DefinitionType,
|
|
7
|
+
DefinitionData,
|
|
8
|
+
BasketDefinitionData,
|
|
9
|
+
ProtocolDefinitionData,
|
|
10
|
+
CertificateDefinitionData,
|
|
11
|
+
RegistryRecord,
|
|
12
|
+
CertificateFieldDescriptor
|
|
13
|
+
} from '../types/index.js'
|
|
14
|
+
|
|
15
|
+
// -------------------- Mocks Setup -------------------- //
|
|
16
|
+
|
|
17
|
+
// 1) A top-level broadcast mock function
|
|
18
|
+
const mockBroadcast = jest.fn().mockResolvedValue('mockBroadcastSuccess')
|
|
19
|
+
|
|
20
|
+
jest.mock('../../overlay-tools/index.js', () => {
|
|
21
|
+
return {
|
|
22
|
+
TopicBroadcaster: jest.fn().mockImplementation(() => ({
|
|
23
|
+
broadcast: mockBroadcast
|
|
24
|
+
})),
|
|
25
|
+
LookupResolver: jest.fn().mockImplementation(() => ({
|
|
26
|
+
query: jest.fn()
|
|
27
|
+
}))
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
jest.mock('../../script/index.js', () => {
|
|
32
|
+
const actualScriptModule = jest.requireActual('../../script/index.js')
|
|
33
|
+
return {
|
|
34
|
+
...actualScriptModule,
|
|
35
|
+
PushDrop: Object.assign(
|
|
36
|
+
jest.fn().mockImplementation(() => ({
|
|
37
|
+
lock: jest.fn().mockResolvedValue({ toHex: () => 'mockLockingScriptHex' }),
|
|
38
|
+
unlock: jest.fn().mockResolvedValue({
|
|
39
|
+
sign: jest.fn().mockResolvedValue({
|
|
40
|
+
toHex: () => 'mockUnlockingScriptHex'
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
})),
|
|
44
|
+
{
|
|
45
|
+
// Ensure decode is a Jest mock
|
|
46
|
+
decode: jest.fn()
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
LockingScript: {
|
|
50
|
+
fromHex: jest.fn().mockImplementation((hex: string) => ({ hex }))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// Ensure `PushDrop.decode` is recognized as a Jest mock
|
|
56
|
+
; (PushDrop as any).decode = jest.fn()
|
|
57
|
+
|
|
58
|
+
jest.mock('../../transaction/index.js', () => {
|
|
59
|
+
return {
|
|
60
|
+
Transaction: {
|
|
61
|
+
fromAtomicBEEF: jest.fn().mockImplementation((_tx: number[]) => ({
|
|
62
|
+
toHexBEEF: () => 'mockTxHexBEEF',
|
|
63
|
+
outputs: [
|
|
64
|
+
{ lockingScript: 'mockLockingScriptObject0' },
|
|
65
|
+
{ lockingScript: 'mockLockingScriptObject1' },
|
|
66
|
+
{ lockingScript: 'mockLockingScriptObject2' }
|
|
67
|
+
]
|
|
68
|
+
})),
|
|
69
|
+
fromBEEF: jest.fn().mockImplementation((_tx: number[]) => ({}))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
jest.mock('../../primitives/index.js', () => {
|
|
75
|
+
return {
|
|
76
|
+
Utils: {
|
|
77
|
+
toArray: jest.fn().mockImplementation((str: string) =>
|
|
78
|
+
Array.from(str).map((c) => c.charCodeAt(0))
|
|
79
|
+
),
|
|
80
|
+
toUTF8: jest.fn().mockImplementation((arr: number[] | string) => {
|
|
81
|
+
if (Array.isArray(arr)) {
|
|
82
|
+
return arr.map((n) => String.fromCharCode(n)).join('')
|
|
83
|
+
}
|
|
84
|
+
return arr
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
let walletMock: Partial<WalletInterface>
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build minimal valid DefinitionData for each type
|
|
94
|
+
*/
|
|
95
|
+
function buildDefinitionData(type: DefinitionType): DefinitionData {
|
|
96
|
+
switch (type) {
|
|
97
|
+
case 'basket': {
|
|
98
|
+
const data: BasketDefinitionData = {
|
|
99
|
+
definitionType: 'basket',
|
|
100
|
+
basketID: 'someBasketId',
|
|
101
|
+
name: 'Test Basket',
|
|
102
|
+
iconURL: 'https://someiconurl.com',
|
|
103
|
+
description: 'Basket Description',
|
|
104
|
+
documentationURL: 'https://docs.basket.com'
|
|
105
|
+
}
|
|
106
|
+
return data
|
|
107
|
+
}
|
|
108
|
+
case 'protocol': {
|
|
109
|
+
const data: ProtocolDefinitionData = {
|
|
110
|
+
definitionType: 'protocol',
|
|
111
|
+
protocolID: 'someProtocolId',
|
|
112
|
+
securityLevel: 1,
|
|
113
|
+
name: 'Test Protocol',
|
|
114
|
+
iconURL: 'https://someiconurl.com',
|
|
115
|
+
description: 'Protocol Description',
|
|
116
|
+
documentationURL: 'https://docs.protocol.com'
|
|
117
|
+
}
|
|
118
|
+
return data
|
|
119
|
+
}
|
|
120
|
+
case 'certificate': {
|
|
121
|
+
const fields: Record<string, CertificateFieldDescriptor> = {
|
|
122
|
+
myField: {
|
|
123
|
+
friendlyName: 'Friendly Field Name',
|
|
124
|
+
description: 'some field description',
|
|
125
|
+
type: 'text',
|
|
126
|
+
fieldIcon: 'https://someiconurl.com/icons/myField.png'
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const data: CertificateDefinitionData = {
|
|
130
|
+
definitionType: 'certificate',
|
|
131
|
+
type: 'someCertType',
|
|
132
|
+
name: 'Test Certificate',
|
|
133
|
+
iconURL: 'https://someiconurl.com',
|
|
134
|
+
description: 'Certificate Description',
|
|
135
|
+
documentationURL: 'https://docs.certificate.com',
|
|
136
|
+
fields
|
|
137
|
+
}
|
|
138
|
+
return data
|
|
139
|
+
}
|
|
140
|
+
default:
|
|
141
|
+
throw new Error(`Invalid test usage: unsupported DefinitionType "${type}"`)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
describe('RegistryClient', () => {
|
|
146
|
+
let registryClient: RegistryClient
|
|
147
|
+
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
walletMock = {
|
|
150
|
+
getPublicKey: jest.fn().mockResolvedValue({ publicKey: 'mockPublicKey' }),
|
|
151
|
+
createAction: jest.fn().mockResolvedValue({
|
|
152
|
+
tx: [1, 2, 3],
|
|
153
|
+
signableTransaction: { tx: [1, 2, 3], reference: 'someRef' }
|
|
154
|
+
}),
|
|
155
|
+
signAction: jest.fn().mockResolvedValue({ tx: [4, 5, 6] }),
|
|
156
|
+
listOutputs: jest.fn().mockResolvedValue({ outputs: [] }),
|
|
157
|
+
getNetwork: jest.fn().mockResolvedValue({ network: 'main' })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
registryClient = new RegistryClient(walletMock as WalletInterface)
|
|
161
|
+
|
|
162
|
+
// Clear all mock calls
|
|
163
|
+
jest.clearAllMocks()
|
|
164
|
+
mockBroadcast.mockClear()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// ------------------------------------------------------------------
|
|
168
|
+
// registerDefinition
|
|
169
|
+
// ------------------------------------------------------------------
|
|
170
|
+
describe('registerDefinition', () => {
|
|
171
|
+
it('should register a basket definition and broadcast with networkPreset=main', async () => {
|
|
172
|
+
const data = buildDefinitionData('basket')
|
|
173
|
+
const result = await registryClient.registerDefinition(data)
|
|
174
|
+
expect(result).toBe('mockBroadcastSuccess')
|
|
175
|
+
|
|
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' }
|
|
188
|
+
)
|
|
189
|
+
expect(mockBroadcast).toHaveBeenCalledTimes(1)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('should register a protocol definition and broadcast with networkPreset=main', async () => {
|
|
193
|
+
const data = buildDefinitionData('protocol')
|
|
194
|
+
const result = await registryClient.registerDefinition(data)
|
|
195
|
+
expect(result).toBe('mockBroadcastSuccess')
|
|
196
|
+
|
|
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' }
|
|
210
|
+
)
|
|
211
|
+
expect(mockBroadcast).toHaveBeenCalledTimes(1)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('should register a certificate definition and broadcast with networkPreset=main', async () => {
|
|
215
|
+
const data = buildDefinitionData('certificate')
|
|
216
|
+
const result = await registryClient.registerDefinition(data)
|
|
217
|
+
expect(result).toBe('mockBroadcastSuccess')
|
|
218
|
+
|
|
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' }
|
|
232
|
+
)
|
|
233
|
+
expect(mockBroadcast).toHaveBeenCalledTimes(1)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should throw if createAction returns undefined tx', async () => {
|
|
237
|
+
(walletMock.createAction as jest.Mock).mockResolvedValueOnce({
|
|
238
|
+
tx: undefined
|
|
239
|
+
})
|
|
240
|
+
const data = buildDefinitionData('basket')
|
|
241
|
+
await expect(registryClient.registerDefinition(data)).rejects.toThrow(
|
|
242
|
+
'Failed to create basket registration transaction!'
|
|
243
|
+
)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('should throw an error on invalid definition type', async () => {
|
|
247
|
+
const invalidData = { definitionType: 'invalidType' } as any as DefinitionData
|
|
248
|
+
await expect(registryClient.registerDefinition(invalidData)).rejects.toThrow(
|
|
249
|
+
'Invalid registry kind specified'
|
|
250
|
+
)
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// ------------------------------------------------------------------
|
|
255
|
+
// resolve
|
|
256
|
+
// ------------------------------------------------------------------
|
|
257
|
+
describe('resolve', () => {
|
|
258
|
+
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' })
|
|
261
|
+
}))
|
|
262
|
+
|
|
263
|
+
const result = await registryClient.resolve('basket', { name: 'foo' })
|
|
264
|
+
expect(result).toEqual([])
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should parse outputs from resolver if type is output-list', async () => {
|
|
268
|
+
; (LookupResolver as jest.Mock).mockImplementation(() => ({
|
|
269
|
+
query: jest.fn().mockResolvedValue({
|
|
270
|
+
type: 'output-list',
|
|
271
|
+
outputs: [
|
|
272
|
+
{ beef: [9, 9, 9], outputIndex: 0 }
|
|
273
|
+
]
|
|
274
|
+
})
|
|
275
|
+
}))
|
|
276
|
+
|
|
277
|
+
// Mock decode once, so the code that does `PushDrop.decode(...)` returns some fields
|
|
278
|
+
; (PushDrop.decode as jest.Mock).mockReturnValue({
|
|
279
|
+
fields: [
|
|
280
|
+
[98], // 'b'
|
|
281
|
+
[97], // 'a'
|
|
282
|
+
[115], // 's'
|
|
283
|
+
[107], // 'k'
|
|
284
|
+
[101], // 'e'
|
|
285
|
+
[116] // 't' => operator
|
|
286
|
+
]
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// The final field must match the current wallet pubkey => 't'
|
|
290
|
+
; (walletMock.getPublicKey as jest.Mock).mockResolvedValueOnce({ publicKey: 't' })
|
|
291
|
+
|
|
292
|
+
const result = await registryClient.resolve('basket', { basketID: 'whatever' })
|
|
293
|
+
expect(result).toHaveLength(1)
|
|
294
|
+
expect(result[0]).toMatchObject({
|
|
295
|
+
definitionType: 'basket',
|
|
296
|
+
basketID: 'b',
|
|
297
|
+
name: 'a',
|
|
298
|
+
iconURL: 's',
|
|
299
|
+
description: 'k',
|
|
300
|
+
documentationURL: 'e',
|
|
301
|
+
registryOperator: 't'
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('should skip outputs that fail parseLockingScript', async () => {
|
|
306
|
+
; (LookupResolver as jest.Mock).mockImplementation(() => ({
|
|
307
|
+
query: jest.fn().mockResolvedValue({
|
|
308
|
+
type: 'output-list',
|
|
309
|
+
outputs: [
|
|
310
|
+
{ beef: [1, 1, 1], outputIndex: 0 },
|
|
311
|
+
{ beef: [2, 2, 2], outputIndex: 1 }
|
|
312
|
+
]
|
|
313
|
+
})
|
|
314
|
+
}))
|
|
315
|
+
|
|
316
|
+
// We do two .mockReturnValueOnce calls so each decode returns something different.
|
|
317
|
+
; (PushDrop.decode as jest.Mock)
|
|
318
|
+
.mockReturnValueOnce({ fields: [] })
|
|
319
|
+
.mockReturnValueOnce({ fields: [] })
|
|
320
|
+
|
|
321
|
+
const result = await registryClient.resolve('basket', { name: 'fooAgain' })
|
|
322
|
+
expect(result).toEqual([])
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
// ------------------------------------------------------------------
|
|
327
|
+
// listOwnRegistryEntries
|
|
328
|
+
// ------------------------------------------------------------------
|
|
329
|
+
describe('listOwnRegistryEntries', () => {
|
|
330
|
+
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({
|
|
333
|
+
outputs: [
|
|
334
|
+
{
|
|
335
|
+
outpoint: 'abc123.0',
|
|
336
|
+
satoshis: 1000,
|
|
337
|
+
lockingScript: 'someLockingScriptHex',
|
|
338
|
+
spendable: false
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
outpoint: 'xyz999.1',
|
|
342
|
+
satoshis: 500,
|
|
343
|
+
lockingScript: 'badLockingScriptHex',
|
|
344
|
+
spendable: false
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
outpoint: 'skipMe.2',
|
|
348
|
+
satoshis: 200,
|
|
349
|
+
lockingScript: 'skipLockingScriptHex',
|
|
350
|
+
spendable: true
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
})
|
|
354
|
+
|
|
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' })
|
|
361
|
+
|
|
362
|
+
const records = await registryClient.listOwnRegistryEntries('basket')
|
|
363
|
+
expect(walletMock.listOutputs).toHaveBeenCalledWith({
|
|
364
|
+
basket: 'basketmap',
|
|
365
|
+
include: 'locking scripts'
|
|
366
|
+
})
|
|
367
|
+
expect(records).toHaveLength(1)
|
|
368
|
+
expect(records[0]).toMatchObject({
|
|
369
|
+
definitionType: 'basket',
|
|
370
|
+
txid: 'abc123',
|
|
371
|
+
outputIndex: 0,
|
|
372
|
+
satoshis: 1000,
|
|
373
|
+
lockingScript: 'someLockingScriptHex'
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
// ------------------------------------------------------------------
|
|
379
|
+
// revokeOwnRegistryEntry
|
|
380
|
+
// ------------------------------------------------------------------
|
|
381
|
+
describe('revokeOwnRegistryEntry', () => {
|
|
382
|
+
let validRecord: RegistryRecord
|
|
383
|
+
|
|
384
|
+
beforeEach(() => {
|
|
385
|
+
validRecord = {
|
|
386
|
+
definitionType: 'basket',
|
|
387
|
+
basketID: 'myBasket',
|
|
388
|
+
name: 'whatever',
|
|
389
|
+
iconURL: 'url',
|
|
390
|
+
description: 'desc',
|
|
391
|
+
documentationURL: 'docURL',
|
|
392
|
+
txid: 'someTxId',
|
|
393
|
+
outputIndex: 0,
|
|
394
|
+
satoshis: 1000,
|
|
395
|
+
lockingScript: 'someLockingScriptHex',
|
|
396
|
+
registryOperator: 'mockPublicKey'
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('should revoke a record successfully (networkPreset=main)', async () => {
|
|
401
|
+
const result = await registryClient.revokeOwnRegistryEntry(validRecord)
|
|
402
|
+
expect(result).toBe('mockBroadcastSuccess')
|
|
403
|
+
|
|
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
|
+
})
|
|
414
|
+
|
|
415
|
+
expect(TopicBroadcaster).toHaveBeenCalledWith(['tm_basketmap'], { networkPreset: 'main' })
|
|
416
|
+
expect(mockBroadcast).toHaveBeenCalled()
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('should throw if createAction returns no signableTransaction', async () => {
|
|
420
|
+
; (walletMock.createAction as jest.Mock).mockResolvedValueOnce({
|
|
421
|
+
tx: [1, 2, 3],
|
|
422
|
+
signableTransaction: undefined
|
|
423
|
+
})
|
|
424
|
+
await expect(registryClient.revokeOwnRegistryEntry(validRecord)).rejects.toThrow(
|
|
425
|
+
'Failed to create signable transaction.'
|
|
426
|
+
)
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it('should throw if signAction returns no signedTx', async () => {
|
|
430
|
+
; (walletMock.signAction as jest.Mock).mockResolvedValueOnce({ tx: undefined })
|
|
431
|
+
await expect(registryClient.revokeOwnRegistryEntry(validRecord)).rejects.toThrow(
|
|
432
|
+
'Failed to finalize the transaction signature.'
|
|
433
|
+
)
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it('should propagate broadcast errors', async () => {
|
|
437
|
+
mockBroadcast.mockRejectedValueOnce(new Error('Broadcast failure!'))
|
|
438
|
+
|
|
439
|
+
await expect(registryClient.revokeOwnRegistryEntry(validRecord)).rejects.toThrow(
|
|
440
|
+
'Broadcast failure!'
|
|
441
|
+
)
|
|
442
|
+
})
|
|
443
|
+
})
|
|
444
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { PubKeyHex } from '../../wallet/index.js'
|
|
2
|
+
|
|
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
|
|
8
|
+
*/
|
|
9
|
+
export type DefinitionType = 'basket' | 'protocol' | 'certificate'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Describes a re-usable structure for certificate fields (used by CertMap).
|
|
13
|
+
*/
|
|
14
|
+
export interface CertificateFieldDescriptor {
|
|
15
|
+
friendlyName: string
|
|
16
|
+
description: string
|
|
17
|
+
type: 'text' | 'imageURL' | 'other'
|
|
18
|
+
fieldIcon: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registry data for a Basket-style record (BasketMap).
|
|
23
|
+
*/
|
|
24
|
+
export interface BasketDefinitionData {
|
|
25
|
+
definitionType: 'basket'
|
|
26
|
+
basketID: string
|
|
27
|
+
name: string
|
|
28
|
+
iconURL: string
|
|
29
|
+
description: string
|
|
30
|
+
documentationURL: string
|
|
31
|
+
registryOperator?: PubKeyHex
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Registry data for a Proto-style record (ProtoMap).
|
|
36
|
+
*/
|
|
37
|
+
export interface ProtocolDefinitionData {
|
|
38
|
+
definitionType: 'protocol'
|
|
39
|
+
protocolID: string
|
|
40
|
+
securityLevel: 0 | 1 | 2
|
|
41
|
+
name: string
|
|
42
|
+
iconURL: string
|
|
43
|
+
description: string
|
|
44
|
+
documentationURL: string
|
|
45
|
+
registryOperator?: PubKeyHex
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Registry data for a Cert-style record (CertMap).
|
|
50
|
+
*/
|
|
51
|
+
export interface CertificateDefinitionData {
|
|
52
|
+
definitionType: 'certificate'
|
|
53
|
+
type: string
|
|
54
|
+
name: string
|
|
55
|
+
iconURL: string
|
|
56
|
+
description: string
|
|
57
|
+
documentationURL: string
|
|
58
|
+
fields: Record<string, CertificateFieldDescriptor>
|
|
59
|
+
registryOperator?: PubKeyHex
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type DefinitionData =
|
|
63
|
+
| BasketDefinitionData
|
|
64
|
+
| ProtocolDefinitionData
|
|
65
|
+
| CertificateDefinitionData
|
|
66
|
+
|
|
67
|
+
export interface TokenData {
|
|
68
|
+
txid: string
|
|
69
|
+
outputIndex: number
|
|
70
|
+
satoshis: number
|
|
71
|
+
lockingScript: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type RegistryRecord = DefinitionData & TokenData
|
|
75
|
+
|
|
76
|
+
// Lookup Query Types (Note: can be shared types with lookup service)
|
|
77
|
+
|
|
78
|
+
interface BasketMapQuery {
|
|
79
|
+
basketID?: string
|
|
80
|
+
registryOperators?: string[]
|
|
81
|
+
name?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ProtoMapQuery {
|
|
85
|
+
name?: string
|
|
86
|
+
registryOperators?: string[]
|
|
87
|
+
protocolID?: string
|
|
88
|
+
securityLevel?: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface CertMapQuery {
|
|
92
|
+
type?: string
|
|
93
|
+
name?: string
|
|
94
|
+
registryOperators?: string[]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface RegistryQueryMapping {
|
|
98
|
+
basket: BasketMapQuery
|
|
99
|
+
protocol: ProtoMapQuery
|
|
100
|
+
certificate: CertMapQuery
|
|
101
|
+
}
|