@bsv/sdk 1.4.1 → 1.4.3
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 +112 -89
- package/dist/cjs/src/registry/RegistryClient.js.map +1 -1
- package/dist/cjs/src/storage/StorageDownloader.js +82 -0
- package/dist/cjs/src/storage/StorageDownloader.js.map +1 -0
- package/dist/cjs/src/storage/__test/StorageDownloader.test.js +144 -0
- package/dist/cjs/src/storage/__test/StorageDownloader.test.js.map +1 -0
- package/dist/cjs/src/storage/index.js +3 -1
- package/dist/cjs/src/storage/index.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/registry/RegistryClient.js +110 -88
- package/dist/esm/src/registry/RegistryClient.js.map +1 -1
- package/dist/esm/src/storage/StorageDownloader.js +75 -0
- package/dist/esm/src/storage/StorageDownloader.js.map +1 -0
- package/dist/esm/src/storage/__test/StorageDownloader.test.js +139 -0
- package/dist/esm/src/storage/__test/StorageDownloader.test.js.map +1 -0
- package/dist/esm/src/storage/index.js +1 -0
- package/dist/esm/src/storage/index.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/registry/RegistryClient.d.ts +24 -24
- package/dist/types/src/registry/RegistryClient.d.ts.map +1 -1
- package/dist/types/src/registry/types/index.d.ts +46 -19
- package/dist/types/src/registry/types/index.d.ts.map +1 -1
- package/dist/types/src/storage/StorageDownloader.d.ts +25 -0
- package/dist/types/src/storage/StorageDownloader.d.ts.map +1 -0
- package/dist/types/src/storage/__test/StorageDownloader.test.d.ts +2 -0
- package/dist/types/src/storage/__test/StorageDownloader.test.d.ts.map +1 -0
- package/dist/types/src/storage/index.d.ts +1 -0
- package/dist/types/src/storage/index.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/storage.md +51 -0
- package/package.json +1 -1
- package/src/registry/RegistryClient.ts +142 -114
- package/src/registry/__tests/RegistryClient.test.ts +136 -100
- package/src/registry/types/index.ts +50 -20
- package/src/storage/StorageDownloader.ts +91 -0
- package/src/storage/__test/StorageDownloader.test.ts +170 -0
- package/src/storage/index.ts +3 -0
package/docs/storage.md
CHANGED
|
@@ -6,6 +6,8 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
|
|
|
6
6
|
|
|
7
7
|
| |
|
|
8
8
|
| --- |
|
|
9
|
+
| [DownloadResult](#interface-downloadresult) |
|
|
10
|
+
| [DownloaderConfig](#interface-downloaderconfig) |
|
|
9
11
|
| [UploadFileResult](#interface-uploadfileresult) |
|
|
10
12
|
| [UploadableFile](#interface-uploadablefile) |
|
|
11
13
|
| [UploaderConfig](#interface-uploaderconfig) |
|
|
@@ -14,6 +16,29 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
|
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
19
|
+
### Interface: DownloadResult
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
export interface DownloadResult {
|
|
23
|
+
data: Buffer;
|
|
24
|
+
mimeType: string | null;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
### Interface: DownloaderConfig
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export interface DownloaderConfig {
|
|
35
|
+
networkPreset: string;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
|
|
40
|
+
|
|
41
|
+
---
|
|
17
42
|
### Interface: UploadFileResult
|
|
18
43
|
|
|
19
44
|
```ts
|
|
@@ -54,6 +79,32 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
|
|
|
54
79
|
---
|
|
55
80
|
## Classes
|
|
56
81
|
|
|
82
|
+
| |
|
|
83
|
+
| --- |
|
|
84
|
+
| [StorageDownloader](#class-storagedownloader) |
|
|
85
|
+
| [StorageUploader](#class-storageuploader) |
|
|
86
|
+
|
|
87
|
+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### Class: StorageDownloader
|
|
92
|
+
|
|
93
|
+
Locates HTTP URLs where content can be downloaded. It uses the passed or the default one.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
export class StorageDownloader {
|
|
97
|
+
constructor(config?: DownloaderConfig)
|
|
98
|
+
public async resolve(uhrpUrl: string): Promise<string[]>
|
|
99
|
+
public async download(uhrpUrl: string)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
See also: [DownloaderConfig](./storage.md#interface-downloaderconfig)
|
|
104
|
+
|
|
105
|
+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
|
|
106
|
+
|
|
107
|
+
---
|
|
57
108
|
### Class: StorageUploader
|
|
58
109
|
|
|
59
110
|
```ts
|
package/package.json
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WalletInterface,
|
|
3
3
|
WalletProtocol,
|
|
4
|
-
WalletClient
|
|
5
|
-
,
|
|
6
|
-
|
|
4
|
+
WalletClient,
|
|
5
|
+
PubKeyHex,
|
|
6
|
+
SecurityLevel
|
|
7
7
|
} from '../wallet/index.js'
|
|
8
|
-
import {
|
|
9
|
-
Utils
|
|
10
|
-
} from '../primitives/index.js'
|
|
8
|
+
import { Utils } from '../primitives/index.js'
|
|
11
9
|
import {
|
|
12
10
|
Transaction,
|
|
13
11
|
BroadcastResponse,
|
|
@@ -17,10 +15,7 @@ import {
|
|
|
17
15
|
LookupResolver,
|
|
18
16
|
TopicBroadcaster
|
|
19
17
|
} from '../overlay-tools/index.js'
|
|
20
|
-
import {
|
|
21
|
-
PushDrop,
|
|
22
|
-
LockingScript
|
|
23
|
-
} from '../script/index.js'
|
|
18
|
+
import { PushDrop, LockingScript } from '../script/index.js'
|
|
24
19
|
import {
|
|
25
20
|
CertificateFieldDescriptor,
|
|
26
21
|
DefinitionData,
|
|
@@ -33,18 +28,18 @@ const REGISTRANT_TOKEN_AMOUNT = 1
|
|
|
33
28
|
|
|
34
29
|
/**
|
|
35
30
|
* RegistryClient manages on-chain registry definitions for three types:
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
38
|
-
* -
|
|
31
|
+
* - basket (basket-based items)
|
|
32
|
+
* - protocol (protocol-based items)
|
|
33
|
+
* - certificate (certificate-based items)
|
|
39
34
|
*
|
|
40
35
|
* It provides methods to:
|
|
41
36
|
* - Register new definitions using pushdrop-based UTXOs.
|
|
42
|
-
* - Resolve existing definitions using a
|
|
37
|
+
* - Resolve existing definitions using a lookup service.
|
|
43
38
|
* - List registry entries associated with the operator's wallet.
|
|
44
39
|
* - Revoke an existing registry entry by spending its UTXO.
|
|
45
40
|
*
|
|
46
41
|
* Registry operators use this client to establish and manage
|
|
47
|
-
* canonical references for baskets, protocols, and
|
|
42
|
+
* canonical references for baskets, protocols, and certificate types.
|
|
48
43
|
*/
|
|
49
44
|
export class RegistryClient {
|
|
50
45
|
private network: 'mainnet' | 'testnet'
|
|
@@ -59,24 +54,21 @@ export class RegistryClient {
|
|
|
59
54
|
* Registry operators (i.e., identity key owners) can create these definitions
|
|
60
55
|
* to establish canonical references for basket IDs, protocol specs, or certificate schemas.
|
|
61
56
|
*
|
|
62
|
-
* @param data -
|
|
57
|
+
* @param data - Structured information about a 'basket', 'protocol', or 'certificate'.
|
|
63
58
|
* @returns A promise with the broadcast result or failure.
|
|
64
59
|
*/
|
|
65
60
|
async registerDefinition (data: DefinitionData): Promise<BroadcastResponse | BroadcastFailure> {
|
|
66
61
|
const registryOperator = (await this.wallet.getPublicKey({ identityKey: true })).publicKey
|
|
67
62
|
const pushdrop = new PushDrop(this.wallet)
|
|
68
63
|
|
|
69
|
-
//
|
|
64
|
+
// Convert definition data into PushDrop fields
|
|
70
65
|
const fields = this.buildPushDropFields(data, registryOperator)
|
|
71
|
-
const protocol = this.getWalletProtocol(data.definitionType)
|
|
72
66
|
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'self'
|
|
79
|
-
)
|
|
67
|
+
// Convert the user-friendly definitionType to the actual wallet protocol
|
|
68
|
+
const protocol = this.mapDefinitionTypeToWalletProtocol(data.definitionType)
|
|
69
|
+
|
|
70
|
+
// Lock the fields into a pushdrop-based UTXO
|
|
71
|
+
const lockingScript = await pushdrop.lock(fields, protocol, '1', 'anyone', true)
|
|
80
72
|
|
|
81
73
|
// Create a transaction
|
|
82
74
|
const { tx } = await this.wallet.createAction({
|
|
@@ -85,21 +77,24 @@ export class RegistryClient {
|
|
|
85
77
|
{
|
|
86
78
|
satoshis: REGISTRANT_TOKEN_AMOUNT,
|
|
87
79
|
lockingScript: lockingScript.toHex(),
|
|
88
|
-
outputDescription: `New ${data.definitionType} registration token
|
|
80
|
+
outputDescription: `New ${data.definitionType} registration token`,
|
|
81
|
+
basket: this.mapDefinitionTypeToBasketName(data.definitionType)
|
|
89
82
|
}
|
|
90
|
-
]
|
|
83
|
+
],
|
|
84
|
+
options: {
|
|
85
|
+
randomizeOutputs: false
|
|
86
|
+
}
|
|
91
87
|
})
|
|
92
88
|
|
|
93
89
|
if (tx === undefined) {
|
|
94
90
|
throw new Error(`Failed to create ${data.definitionType} registration transaction!`)
|
|
95
91
|
}
|
|
96
92
|
|
|
97
|
-
// Broadcast
|
|
98
|
-
|
|
93
|
+
// Broadcast to the relevant topic
|
|
99
94
|
const broadcaster = new TopicBroadcaster(
|
|
100
|
-
[this.
|
|
95
|
+
[this.mapDefinitionTypeToTopic(data.definitionType)],
|
|
101
96
|
{
|
|
102
|
-
networkPreset: this.network ??= (await
|
|
97
|
+
networkPreset: this.network ??= (await this.wallet.getNetwork({})).network
|
|
103
98
|
}
|
|
104
99
|
)
|
|
105
100
|
return await broadcaster.broadcast(Transaction.fromAtomicBEEF(tx))
|
|
@@ -112,7 +107,7 @@ export class RegistryClient {
|
|
|
112
107
|
* - For "basket", the query is of type BasketMapQuery:
|
|
113
108
|
* { basketID?: string; name?: string; registryOperators?: string[]; }
|
|
114
109
|
* - For "protocol", the query is of type ProtoMapQuery:
|
|
115
|
-
* { name?: string; registryOperators?: string[]; protocolID?:
|
|
110
|
+
* { name?: string; registryOperators?: string[]; protocolID?: WalletProtocol; }
|
|
116
111
|
* - For "certificate", the query is of type CertMapQuery:
|
|
117
112
|
* { type?: string; name?: string; registryOperators?: string[]; }
|
|
118
113
|
*
|
|
@@ -125,14 +120,10 @@ export class RegistryClient {
|
|
|
125
120
|
query: RegistryQueryMapping[T]
|
|
126
121
|
): Promise<DefinitionData[]> {
|
|
127
122
|
const resolver = new LookupResolver()
|
|
123
|
+
const serviceName = this.mapDefinitionTypeToServiceName(definitionType)
|
|
128
124
|
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
const result = await resolver.query({
|
|
132
|
-
service: serviceName,
|
|
133
|
-
query
|
|
134
|
-
})
|
|
135
|
-
|
|
125
|
+
// Make the lookup query
|
|
126
|
+
const result = await resolver.query({ service: serviceName, query })
|
|
136
127
|
if (result.type !== 'output-list') {
|
|
137
128
|
return []
|
|
138
129
|
}
|
|
@@ -140,11 +131,12 @@ export class RegistryClient {
|
|
|
140
131
|
const parsedRegistryRecords: DefinitionData[] = []
|
|
141
132
|
for (const output of result.outputs) {
|
|
142
133
|
try {
|
|
143
|
-
const parsedTx = Transaction.
|
|
144
|
-
const
|
|
134
|
+
const parsedTx = Transaction.fromBEEF(output.beef)
|
|
135
|
+
const lockingScript = parsedTx.outputs[output.outputIndex].lockingScript
|
|
136
|
+
const record = await this.parseLockingScript(definitionType, lockingScript)
|
|
145
137
|
parsedRegistryRecords.push(record)
|
|
146
138
|
} catch {
|
|
147
|
-
//
|
|
139
|
+
// Skip invalid or non-pushdrop outputs
|
|
148
140
|
}
|
|
149
141
|
}
|
|
150
142
|
return parsedRegistryRecords
|
|
@@ -159,29 +151,35 @@ export class RegistryClient {
|
|
|
159
151
|
* @returns A promise that resolves to an array of RegistryRecord objects.
|
|
160
152
|
*/
|
|
161
153
|
async listOwnRegistryEntries (definitionType: DefinitionType): Promise<RegistryRecord[]> {
|
|
162
|
-
const relevantBasketName = this.
|
|
163
|
-
const { outputs } = await this.wallet.listOutputs({
|
|
154
|
+
const relevantBasketName = this.mapDefinitionTypeToBasketName(definitionType)
|
|
155
|
+
const { outputs, BEEF } = await this.wallet.listOutputs({
|
|
164
156
|
basket: relevantBasketName,
|
|
165
|
-
include: '
|
|
157
|
+
include: 'entire transactions'
|
|
166
158
|
})
|
|
167
|
-
const results: RegistryRecord[] = []
|
|
168
159
|
|
|
160
|
+
const results: RegistryRecord[] = []
|
|
169
161
|
for (const output of outputs) {
|
|
170
|
-
if (output.spendable) {
|
|
162
|
+
if (!output.spendable) {
|
|
171
163
|
continue
|
|
172
164
|
}
|
|
173
165
|
try {
|
|
174
|
-
const record = await this.parseLockingScript(definitionType, LockingScript.fromHex(output.lockingScript as string))
|
|
175
166
|
const [txid, outputIndex] = output.outpoint.split('.')
|
|
167
|
+
const tx = Transaction.fromBEEF(BEEF as number[])
|
|
168
|
+
const lockingScript: LockingScript = tx.outputs[outputIndex].lockingScript
|
|
169
|
+
const record = await this.parseLockingScript(
|
|
170
|
+
definitionType,
|
|
171
|
+
lockingScript
|
|
172
|
+
)
|
|
176
173
|
results.push({
|
|
177
174
|
...record,
|
|
178
175
|
txid,
|
|
179
176
|
outputIndex: Number(outputIndex),
|
|
180
177
|
satoshis: output.satoshis,
|
|
181
|
-
lockingScript:
|
|
178
|
+
lockingScript: lockingScript.toHex(),
|
|
179
|
+
beef: BEEF as number[]
|
|
182
180
|
})
|
|
183
181
|
} catch {
|
|
184
|
-
//
|
|
182
|
+
// Ignore parse errors
|
|
185
183
|
}
|
|
186
184
|
}
|
|
187
185
|
|
|
@@ -191,29 +189,17 @@ export class RegistryClient {
|
|
|
191
189
|
/**
|
|
192
190
|
* Revokes a registry record by spending its associated UTXO.
|
|
193
191
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* builds a signable transaction, signs it, and then broadcasts the finalized transaction.
|
|
197
|
-
*
|
|
198
|
-
* @param registryRecord - The registry record to revoke. It must include a valid txid, outputIndex, and lockingScript.
|
|
199
|
-
* @returns A promise that resolves with either a BroadcastResponse upon success or a BroadcastFailure on error.
|
|
200
|
-
* @throws If required fields are missing or if transaction creation/signing fails.
|
|
192
|
+
* @param registryRecord - Must have valid txid, outputIndex, and lockingScript.
|
|
193
|
+
* @returns Broadcast success/failure.
|
|
201
194
|
*/
|
|
202
195
|
async revokeOwnRegistryEntry (
|
|
203
196
|
registryRecord: RegistryRecord
|
|
204
197
|
): Promise<BroadcastResponse | BroadcastFailure> {
|
|
205
198
|
if (registryRecord.txid === undefined || typeof registryRecord.outputIndex === 'undefined' || registryRecord.lockingScript === undefined) {
|
|
206
|
-
throw new Error('Invalid record. Missing txid, outputIndex, or lockingScript.')
|
|
199
|
+
throw new Error('Invalid registry record. Missing txid, outputIndex, or lockingScript.')
|
|
207
200
|
}
|
|
208
201
|
|
|
209
|
-
//
|
|
210
|
-
const pushdrop = new PushDrop(this.wallet)
|
|
211
|
-
const unlocker = await pushdrop.unlock(
|
|
212
|
-
this.getWalletProtocol(registryRecord.definitionType),
|
|
213
|
-
'1',
|
|
214
|
-
'anyone'
|
|
215
|
-
)
|
|
216
|
-
|
|
202
|
+
// Create a descriptive label for the item we’re revoking
|
|
217
203
|
const itemIdentifier =
|
|
218
204
|
registryRecord.definitionType === 'basket'
|
|
219
205
|
? registryRecord.basketID
|
|
@@ -223,13 +209,10 @@ export class RegistryClient {
|
|
|
223
209
|
? (registryRecord.name !== undefined ? registryRecord.name : registryRecord.type)
|
|
224
210
|
: 'unknown'
|
|
225
211
|
|
|
226
|
-
const description = `Revoke ${registryRecord.definitionType} item: ${String(itemIdentifier)}`
|
|
227
|
-
|
|
228
|
-
// Create a new transaction that spends the UTXO
|
|
229
212
|
const outpoint = `${registryRecord.txid}.${registryRecord.outputIndex}`
|
|
230
213
|
const { signableTransaction } = await this.wallet.createAction({
|
|
231
|
-
|
|
232
|
-
|
|
214
|
+
description: `Revoke ${registryRecord.definitionType} item: ${itemIdentifier}`,
|
|
215
|
+
inputBEEF: registryRecord.beef,
|
|
233
216
|
inputs: [
|
|
234
217
|
{
|
|
235
218
|
outpoint,
|
|
@@ -243,17 +226,33 @@ export class RegistryClient {
|
|
|
243
226
|
throw new Error('Failed to create signable transaction.')
|
|
244
227
|
}
|
|
245
228
|
|
|
246
|
-
|
|
247
|
-
const tx = Transaction.fromBEEF(signableTransaction.tx)
|
|
248
|
-
const finalUnlockScript = await unlocker.sign(tx, registryRecord.outputIndex)
|
|
229
|
+
const partialTx = Transaction.fromBEEF(signableTransaction.tx)
|
|
249
230
|
|
|
250
|
-
//
|
|
231
|
+
// Prepare the unlocker
|
|
232
|
+
const pushdrop = new PushDrop(this.wallet)
|
|
233
|
+
const unlocker = await pushdrop.unlock(
|
|
234
|
+
this.mapDefinitionTypeToWalletProtocol(registryRecord.definitionType),
|
|
235
|
+
'1',
|
|
236
|
+
'anyone',
|
|
237
|
+
'all',
|
|
238
|
+
false,
|
|
239
|
+
registryRecord.satoshis,
|
|
240
|
+
LockingScript.fromHex(registryRecord.lockingScript)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
// Convert to Transaction, apply signature
|
|
244
|
+
const finalUnlockScript = await unlocker.sign(partialTx, registryRecord.outputIndex)
|
|
245
|
+
|
|
246
|
+
// Complete signing with the final unlock script
|
|
251
247
|
const { tx: signedTx } = await this.wallet.signAction({
|
|
252
248
|
reference: signableTransaction.reference,
|
|
253
249
|
spends: {
|
|
254
250
|
[registryRecord.outputIndex]: {
|
|
255
251
|
unlockingScript: finalUnlockScript.toHex()
|
|
256
252
|
}
|
|
253
|
+
},
|
|
254
|
+
options: {
|
|
255
|
+
acceptDelayedBroadcast: false
|
|
257
256
|
}
|
|
258
257
|
})
|
|
259
258
|
|
|
@@ -263,21 +262,25 @@ export class RegistryClient {
|
|
|
263
262
|
|
|
264
263
|
// Broadcast
|
|
265
264
|
const broadcaster = new TopicBroadcaster(
|
|
266
|
-
[this.
|
|
265
|
+
[this.mapDefinitionTypeToTopic(registryRecord.definitionType)],
|
|
267
266
|
{
|
|
268
|
-
networkPreset: this.network ??= (await
|
|
267
|
+
networkPreset: this.network ??= (await this.wallet.getNetwork({})).network
|
|
269
268
|
}
|
|
270
269
|
)
|
|
271
270
|
return await broadcaster.broadcast(Transaction.fromAtomicBEEF(signedTx))
|
|
272
271
|
}
|
|
273
272
|
|
|
274
273
|
// --------------------------------------------------------------------------
|
|
275
|
-
// INTERNAL
|
|
274
|
+
// INTERNAL UTILITY METHODS
|
|
276
275
|
// --------------------------------------------------------------------------
|
|
277
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Convert definition data into an array of pushdrop fields (strings).
|
|
279
|
+
* Each definition type has a slightly different shape.
|
|
280
|
+
*/
|
|
278
281
|
private buildPushDropFields (
|
|
279
282
|
data: DefinitionData,
|
|
280
|
-
registryOperator:
|
|
283
|
+
registryOperator: PubKeyHex
|
|
281
284
|
): number[][] {
|
|
282
285
|
let fields: string[]
|
|
283
286
|
|
|
@@ -293,8 +296,7 @@ export class RegistryClient {
|
|
|
293
296
|
break
|
|
294
297
|
case 'protocol':
|
|
295
298
|
fields = [
|
|
296
|
-
data.
|
|
297
|
-
data.protocolID,
|
|
299
|
+
JSON.stringify(data.protocolID),
|
|
298
300
|
data.name,
|
|
299
301
|
data.iconURL,
|
|
300
302
|
data.description,
|
|
@@ -312,17 +314,17 @@ export class RegistryClient {
|
|
|
312
314
|
]
|
|
313
315
|
break
|
|
314
316
|
default:
|
|
315
|
-
throw new Error('
|
|
317
|
+
throw new Error('Unsupported definition type')
|
|
316
318
|
}
|
|
317
319
|
|
|
318
|
-
// Append the
|
|
320
|
+
// Append the operator's public identity key last
|
|
319
321
|
fields.push(registryOperator)
|
|
320
322
|
|
|
321
323
|
return fields.map(field => Utils.toArray(field))
|
|
322
324
|
}
|
|
323
325
|
|
|
324
326
|
/**
|
|
325
|
-
* Decodes a pushdrop locking script for a given
|
|
327
|
+
* Decodes a pushdrop locking script for a given definition type,
|
|
326
328
|
* returning a typed record with the appropriate fields.
|
|
327
329
|
*/
|
|
328
330
|
private async parseLockingScript (
|
|
@@ -335,15 +337,17 @@ export class RegistryClient {
|
|
|
335
337
|
}
|
|
336
338
|
|
|
337
339
|
let registryOperator: PubKeyHex
|
|
338
|
-
let
|
|
340
|
+
let parsedData: DefinitionData
|
|
341
|
+
|
|
339
342
|
switch (definitionType) {
|
|
340
343
|
case 'basket': {
|
|
341
|
-
if (decoded.fields.length !==
|
|
344
|
+
if (decoded.fields.length !== 7) {
|
|
342
345
|
throw new Error('Unexpected field count for basket type.')
|
|
343
346
|
}
|
|
344
347
|
const [basketID, name, iconURL, description, docURL, operator] = decoded.fields
|
|
345
348
|
registryOperator = Utils.toUTF8(operator)
|
|
346
|
-
|
|
349
|
+
|
|
350
|
+
parsedData = {
|
|
347
351
|
definitionType: 'basket',
|
|
348
352
|
basketID: Utils.toUTF8(basketID),
|
|
349
353
|
name: Utils.toUTF8(name),
|
|
@@ -353,12 +357,12 @@ export class RegistryClient {
|
|
|
353
357
|
}
|
|
354
358
|
break
|
|
355
359
|
}
|
|
360
|
+
|
|
356
361
|
case 'protocol': {
|
|
357
362
|
if (decoded.fields.length !== 7) {
|
|
358
|
-
throw new Error('Unexpected field count for
|
|
363
|
+
throw new Error('Unexpected field count for protocol type.')
|
|
359
364
|
}
|
|
360
365
|
const [
|
|
361
|
-
securityLevel,
|
|
362
366
|
protocolID,
|
|
363
367
|
name,
|
|
364
368
|
iconURL,
|
|
@@ -367,10 +371,10 @@ export class RegistryClient {
|
|
|
367
371
|
operator
|
|
368
372
|
] = decoded.fields
|
|
369
373
|
registryOperator = Utils.toUTF8(operator)
|
|
370
|
-
|
|
374
|
+
|
|
375
|
+
parsedData = {
|
|
371
376
|
definitionType: 'protocol',
|
|
372
|
-
|
|
373
|
-
protocolID: Utils.toUTF8(protocolID),
|
|
377
|
+
protocolID: deserializeWalletProtocol(Utils.toUTF8(protocolID)),
|
|
374
378
|
name: Utils.toUTF8(name),
|
|
375
379
|
iconURL: Utils.toUTF8(iconURL),
|
|
376
380
|
description: Utils.toUTF8(description),
|
|
@@ -378,8 +382,9 @@ export class RegistryClient {
|
|
|
378
382
|
}
|
|
379
383
|
break
|
|
380
384
|
}
|
|
385
|
+
|
|
381
386
|
case 'certificate': {
|
|
382
|
-
if (decoded.fields.length !==
|
|
387
|
+
if (decoded.fields.length !== 8) {
|
|
383
388
|
throw new Error('Unexpected field count for certificate type.')
|
|
384
389
|
}
|
|
385
390
|
const [
|
|
@@ -391,17 +396,16 @@ export class RegistryClient {
|
|
|
391
396
|
fieldsJSON,
|
|
392
397
|
operator
|
|
393
398
|
] = decoded.fields
|
|
394
|
-
|
|
395
399
|
registryOperator = Utils.toUTF8(operator)
|
|
396
400
|
|
|
397
|
-
let parsedFields: Record<string, CertificateFieldDescriptor>
|
|
401
|
+
let parsedFields: Record<string, CertificateFieldDescriptor> = {}
|
|
398
402
|
try {
|
|
399
403
|
parsedFields = JSON.parse(Utils.toUTF8(fieldsJSON))
|
|
400
404
|
} catch {
|
|
401
|
-
|
|
405
|
+
// If there's a JSON parse error, assume empty
|
|
402
406
|
}
|
|
403
407
|
|
|
404
|
-
|
|
408
|
+
parsedData = {
|
|
405
409
|
definitionType: 'certificate',
|
|
406
410
|
type: Utils.toUTF8(certType),
|
|
407
411
|
name: Utils.toUTF8(name),
|
|
@@ -412,25 +416,25 @@ export class RegistryClient {
|
|
|
412
416
|
}
|
|
413
417
|
break
|
|
414
418
|
}
|
|
419
|
+
|
|
415
420
|
default:
|
|
416
|
-
throw new Error(
|
|
421
|
+
throw new Error(`Unsupported definition type: ${definitionType as string}`)
|
|
417
422
|
}
|
|
418
423
|
|
|
424
|
+
// Enforce that the pushdrop belongs to the CURRENT identity key
|
|
419
425
|
const currentIdentityKey = (await this.wallet.getPublicKey({ identityKey: true })).publicKey
|
|
420
426
|
if (registryOperator !== currentIdentityKey) {
|
|
421
427
|
throw new Error('This registry token does not belong to the current wallet.')
|
|
422
428
|
}
|
|
423
429
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
registryOperator
|
|
427
|
-
}
|
|
430
|
+
// Return the typed data plus the operator key
|
|
431
|
+
return { ...parsedData, registryOperator }
|
|
428
432
|
}
|
|
429
433
|
|
|
430
434
|
/**
|
|
431
|
-
*
|
|
435
|
+
* Convert our definitionType to the wallet protocol format ([protocolID, keyID]).
|
|
432
436
|
*/
|
|
433
|
-
private
|
|
437
|
+
private mapDefinitionTypeToWalletProtocol (definitionType: DefinitionType): WalletProtocol {
|
|
434
438
|
switch (definitionType) {
|
|
435
439
|
case 'basket':
|
|
436
440
|
return [1, 'basketmap']
|
|
@@ -439,14 +443,14 @@ export class RegistryClient {
|
|
|
439
443
|
case 'certificate':
|
|
440
444
|
return [1, 'certmap']
|
|
441
445
|
default:
|
|
442
|
-
throw new Error(`Unknown
|
|
446
|
+
throw new Error(`Unknown definition type: ${definitionType as string}`)
|
|
443
447
|
}
|
|
444
448
|
}
|
|
445
449
|
|
|
446
450
|
/**
|
|
447
|
-
*
|
|
451
|
+
* Convert 'basket'|'protocol'|'certificate' to the basket name used by the wallet.
|
|
448
452
|
*/
|
|
449
|
-
private
|
|
453
|
+
private mapDefinitionTypeToBasketName (definitionType: DefinitionType): string {
|
|
450
454
|
switch (definitionType) {
|
|
451
455
|
case 'basket':
|
|
452
456
|
return 'basketmap'
|
|
@@ -455,14 +459,14 @@ export class RegistryClient {
|
|
|
455
459
|
case 'certificate':
|
|
456
460
|
return 'certmap'
|
|
457
461
|
default:
|
|
458
|
-
throw new Error(`Unknown
|
|
462
|
+
throw new Error(`Unknown definition type: ${definitionType as string}`)
|
|
459
463
|
}
|
|
460
464
|
}
|
|
461
465
|
|
|
462
466
|
/**
|
|
463
|
-
*
|
|
467
|
+
* Convert 'basket'|'protocol'|'certificate' to the broadcast topic name.
|
|
464
468
|
*/
|
|
465
|
-
private
|
|
469
|
+
private mapDefinitionTypeToTopic (definitionType: DefinitionType): string {
|
|
466
470
|
switch (definitionType) {
|
|
467
471
|
case 'basket':
|
|
468
472
|
return 'tm_basketmap'
|
|
@@ -471,14 +475,14 @@ export class RegistryClient {
|
|
|
471
475
|
case 'certificate':
|
|
472
476
|
return 'tm_certmap'
|
|
473
477
|
default:
|
|
474
|
-
throw new Error(`Unknown
|
|
478
|
+
throw new Error(`Unknown definition type: ${definitionType as string}`)
|
|
475
479
|
}
|
|
476
480
|
}
|
|
477
481
|
|
|
478
482
|
/**
|
|
479
|
-
*
|
|
483
|
+
* Convert 'basket'|'protocol'|'certificate' to the lookup service name.
|
|
480
484
|
*/
|
|
481
|
-
private
|
|
485
|
+
private mapDefinitionTypeToServiceName (definitionType: DefinitionType): string {
|
|
482
486
|
switch (definitionType) {
|
|
483
487
|
case 'basket':
|
|
484
488
|
return 'ls_basketmap'
|
|
@@ -487,7 +491,31 @@ export class RegistryClient {
|
|
|
487
491
|
case 'certificate':
|
|
488
492
|
return 'ls_certmap'
|
|
489
493
|
default:
|
|
490
|
-
throw new Error(`Unknown
|
|
494
|
+
throw new Error(`Unknown definition type: ${definitionType as string}`)
|
|
491
495
|
}
|
|
492
496
|
}
|
|
493
497
|
}
|
|
498
|
+
|
|
499
|
+
export function deserializeWalletProtocol (str: string): WalletProtocol {
|
|
500
|
+
// Parse the JSON string back into a JavaScript value.
|
|
501
|
+
const parsed = JSON.parse(str)
|
|
502
|
+
|
|
503
|
+
// Validate that the parsed value is an array with exactly two elements.
|
|
504
|
+
if (!Array.isArray(parsed) || parsed.length !== 2) {
|
|
505
|
+
throw new Error('Invalid wallet protocol format.')
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const [security, protocolString] = parsed
|
|
509
|
+
|
|
510
|
+
// Validate that the security level is one of the allowed numbers.
|
|
511
|
+
if (![0, 1, 2].includes(security)) {
|
|
512
|
+
throw new Error('Invalid security level.')
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Validate that the protocol string is a string and its length is within the allowed bounds.
|
|
516
|
+
if (typeof protocolString !== 'string') {
|
|
517
|
+
throw new Error('Invalid protocolID')
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return [security as SecurityLevel, protocolString]
|
|
521
|
+
}
|