@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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/kvstore/LocalKVStore.js +7 -7
- package/dist/cjs/src/kvstore/LocalKVStore.js.map +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/kvstore/LocalKVStore.js +7 -7
- package/dist/esm/src/kvstore/LocalKVStore.js.map +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/kvstore/LocalKVStore.ts +7 -7
- package/src/kvstore/__tests/LocalKVStore.test.ts +17 -17
- package/src/registry/RegistryClient.ts +180 -36
- package/src/registry/__tests/RegistryClient.test.ts +211 -54
|
@@ -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
|
-
-
|
|
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
|
|
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
|
|
318
|
+
#### Method updateDefinition
|
|
296
319
|
|
|
297
|
-
|
|
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
|
|
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
|
-
+
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
* -
|
|
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 =
|
|
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,
|
|
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:
|
|
128
|
+
networkPreset: await this.getNetwork(),
|
|
129
|
+
resolver: this.resolver
|
|
98
130
|
}
|
|
99
131
|
)
|
|
100
|
-
|
|
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
|
-
*
|
|
222
|
+
* Removes a registry definition by spending its associated UTXO.
|
|
191
223
|
*
|
|
192
|
-
* @param registryRecord -
|
|
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
|
|
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 =
|
|
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 we
|
|
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: `
|
|
252
|
+
description: `Remove ${registryRecord.definitionType} item: ${itemIdentifier}`,
|
|
221
253
|
inputBEEF: registryRecord.beef,
|
|
222
254
|
inputs: [
|
|
223
255
|
{
|
|
224
256
|
outpoint,
|
|
225
|
-
unlockingScriptLength:
|
|
226
|
-
inputDescription: `
|
|
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 =
|
|
274
|
+
const unlocker = pushdrop.unlock(
|
|
240
275
|
this.mapDefinitionTypeToWalletProtocol(registryRecord.definitionType),
|
|
241
|
-
|
|
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,
|
|
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
|
-
|
|
399
|
+
0: {
|
|
257
400
|
unlockingScript: finalUnlockScript.toHex()
|
|
258
401
|
}
|
|
259
402
|
},
|
|
260
403
|
options: {
|
|
261
|
-
acceptDelayedBroadcast:
|
|
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:
|
|
416
|
+
networkPreset: await this.getNetwork(),
|
|
417
|
+
resolver: this.resolver
|
|
274
418
|
}
|
|
275
419
|
)
|
|
276
420
|
return await broadcaster.broadcast(Transaction.fromAtomicBEEF(signedTx))
|