mainnet-js 1.1.21 → 1.1.23
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/index.html +1 -1
- package/dist/{mainnet-1.1.21.js → mainnet-1.1.23.js} +3 -3
- package/dist/module/network/ElectrumNetworkProvider.d.ts.map +1 -1
- package/dist/module/network/ElectrumNetworkProvider.js +2 -1
- package/dist/module/network/ElectrumNetworkProvider.js.map +1 -1
- package/dist/module/network/constant.js +1 -1
- package/dist/module/network/constant.js.map +1 -1
- package/dist/module/network/default.js +1 -1
- package/dist/module/network/default.js.map +1 -1
- package/dist/module/wallet/Bcmr.d.ts +17 -0
- package/dist/module/wallet/Bcmr.d.ts.map +1 -1
- package/dist/module/wallet/Bcmr.js +168 -89
- package/dist/module/wallet/Bcmr.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/network/ElectrumNetworkProvider.ts +2 -1
- package/src/network/constant.ts +1 -1
- package/src/network/default.ts +1 -1
- package/src/wallet/Bcmr.test.ts +115 -2
- package/src/wallet/Bcmr.ts +196 -108
package/src/wallet/Bcmr.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
binToHex,
|
|
3
|
-
binToNumberUint16LE,
|
|
4
3
|
binToUtf8,
|
|
5
4
|
decodeTransaction,
|
|
6
5
|
hexToBin,
|
|
@@ -25,7 +24,7 @@ export interface AuthChainElement {
|
|
|
25
24
|
|
|
26
25
|
export type AuthChain = AuthChainElement[];
|
|
27
26
|
|
|
28
|
-
// Implementation of CHIP-BCMR
|
|
27
|
+
// Implementation of CHIP-BCMR v2.0.0-draft, refer to https://github.com/bitjson/chip-bcmr
|
|
29
28
|
export class BCMR {
|
|
30
29
|
// List of tracked registries
|
|
31
30
|
public static metadataRegistries: Registry[] = [];
|
|
@@ -114,6 +113,109 @@ export class BCMR {
|
|
|
114
113
|
this.addMetadataRegistry(registry);
|
|
115
114
|
}
|
|
116
115
|
|
|
116
|
+
// helper function to enforce the constraints on the 0th output, decode the BCMR's OP_RETURN data
|
|
117
|
+
// returns resolved AuthChainElement
|
|
118
|
+
public static makeAuthChainElement(
|
|
119
|
+
rawTx: ElectrumRawTransaction | Transaction,
|
|
120
|
+
hash: string
|
|
121
|
+
): AuthChainElement {
|
|
122
|
+
let opReturns: string[];
|
|
123
|
+
let spends0thOutput = false;
|
|
124
|
+
if (rawTx.hasOwnProperty("vout")) {
|
|
125
|
+
const electrumTransaction = rawTx as ElectrumRawTransaction;
|
|
126
|
+
opReturns = electrumTransaction.vout
|
|
127
|
+
.filter((val) => val.scriptPubKey.type === "nulldata")
|
|
128
|
+
.map((val) => val.scriptPubKey.hex);
|
|
129
|
+
spends0thOutput = electrumTransaction.vin.some((val) => val.vout === 0);
|
|
130
|
+
} else {
|
|
131
|
+
const libauthTransaction = rawTx as Transaction;
|
|
132
|
+
opReturns = libauthTransaction.outputs
|
|
133
|
+
.map((val) => binToHex(val.lockingBytecode))
|
|
134
|
+
.filter((val) => val.indexOf("6a") === 0);
|
|
135
|
+
spends0thOutput = libauthTransaction.inputs.some(
|
|
136
|
+
(val) => val.outpointIndex === 0
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!spends0thOutput) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
"Invalid authchain transaction (does not spend 0th output of previous transaction)"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const bcmrOpReturns = opReturns.filter(
|
|
147
|
+
(val) =>
|
|
148
|
+
val.indexOf("6a0442434d52") === 0 ||
|
|
149
|
+
val.indexOf("6a4c0442434d52") === 0 ||
|
|
150
|
+
val.indexOf("6a4d040042434d52") === 0 ||
|
|
151
|
+
val.indexOf("6a4e0400000042434d52") === 0
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (bcmrOpReturns.length === 0) {
|
|
155
|
+
return {
|
|
156
|
+
txHash: hash,
|
|
157
|
+
contentHash: "",
|
|
158
|
+
uris: [],
|
|
159
|
+
httpsUrl: "",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const opReturnHex = opReturns[0];
|
|
164
|
+
const chunks = OpReturnData.parseBinary(hexToBin(opReturnHex));
|
|
165
|
+
if (chunks.length < 2) {
|
|
166
|
+
throw new Error(`Malformed BCMR output: ${opReturnHex}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const result: AuthChainElement = {
|
|
170
|
+
txHash: hash,
|
|
171
|
+
contentHash: "",
|
|
172
|
+
uris: [],
|
|
173
|
+
httpsUrl: "",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (chunks.length === 2) {
|
|
177
|
+
// IPFS Publication Output
|
|
178
|
+
result.contentHash = binToHex(chunks[1]);
|
|
179
|
+
const ipfsCid = binToUtf8(chunks[1]);
|
|
180
|
+
result.uris = [`ipfs://${ipfsCid}`];
|
|
181
|
+
result.httpsUrl = `https://dweb.link/ipfs/${ipfsCid}`;
|
|
182
|
+
} else {
|
|
183
|
+
// URI Publication Output
|
|
184
|
+
// content hash is in OP_SHA256 byte order per spec
|
|
185
|
+
result.contentHash = binToHex(chunks[1].slice());
|
|
186
|
+
|
|
187
|
+
const uris = chunks.slice(2);
|
|
188
|
+
|
|
189
|
+
for (const uri of uris) {
|
|
190
|
+
const uriString = binToUtf8(uri);
|
|
191
|
+
result.uris.push(uriString);
|
|
192
|
+
|
|
193
|
+
if (result.httpsUrl) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (uriString.indexOf("https://") === 0) {
|
|
198
|
+
result.httpsUrl = uriString;
|
|
199
|
+
} else if (uriString.indexOf("https://") === -1) {
|
|
200
|
+
result.httpsUrl = uriString;
|
|
201
|
+
|
|
202
|
+
// case for domain name specifier, like example.com
|
|
203
|
+
if (uriString.indexOf("/") === -1) {
|
|
204
|
+
result.httpsUrl = `${result.httpsUrl}/.well-known/bitcoin-cash-metadata-registry.json`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
result.httpsUrl = `https://${result.httpsUrl}`;
|
|
208
|
+
} else if (uriString.indexOf("ipfs://") === 0) {
|
|
209
|
+
const ipfsCid = uriString.replace("ipfs://", "");
|
|
210
|
+
result.httpsUrl = `https://dweb.link/ipfs/${ipfsCid}`;
|
|
211
|
+
} else {
|
|
212
|
+
throw new Error(`Unsupported uri type: ${uriString}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
};
|
|
218
|
+
|
|
117
219
|
/**
|
|
118
220
|
* buildAuthChain Build an authchain - Zeroth-Descendant Transaction Chain, refer to https://github.com/bitjson/chip-bcmr#zeroth-descendant-transaction-chains
|
|
119
221
|
* The authchain in this implementation is specific to resolve to a valid metadata registry
|
|
@@ -196,113 +298,10 @@ export class BCMR {
|
|
|
196
298
|
return undefined;
|
|
197
299
|
};
|
|
198
300
|
|
|
199
|
-
// helper function to enforce the constraints on the 0th output, decode the BCMR's OP_RETURN data
|
|
200
|
-
// returns resolved AuthChainElement
|
|
201
|
-
const makeAuthChainElement = (
|
|
202
|
-
rawTx: ElectrumRawTransaction | Transaction,
|
|
203
|
-
hash: string
|
|
204
|
-
): AuthChainElement => {
|
|
205
|
-
let opReturns: string[];
|
|
206
|
-
let spends0thOutput = false;
|
|
207
|
-
if (rawTx.hasOwnProperty("vout")) {
|
|
208
|
-
const electrumTransaction = rawTx as ElectrumRawTransaction;
|
|
209
|
-
opReturns = electrumTransaction.vout
|
|
210
|
-
.filter((val) => val.scriptPubKey.type === "nulldata")
|
|
211
|
-
.map((val) => val.scriptPubKey.hex);
|
|
212
|
-
spends0thOutput = electrumTransaction.vin.some((val) => val.vout === 0);
|
|
213
|
-
} else {
|
|
214
|
-
const libauthTransaction = rawTx as Transaction;
|
|
215
|
-
opReturns = libauthTransaction.outputs
|
|
216
|
-
.map((val) => binToHex(val.lockingBytecode))
|
|
217
|
-
.filter((val) => val.indexOf("6a") === 0);
|
|
218
|
-
spends0thOutput = libauthTransaction.inputs.some(
|
|
219
|
-
(val) => val.outpointIndex === 0
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (!spends0thOutput) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
"Invalid authchain transaction (does not spend 0th output of previous transaction)"
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const bcmrOpReturns = opReturns.filter(
|
|
230
|
-
(val) =>
|
|
231
|
-
val.indexOf("6a0442434d52") === 0 ||
|
|
232
|
-
val.indexOf("6a4c0442434d52") === 0 ||
|
|
233
|
-
val.indexOf("6a4d040042434d52") === 0 ||
|
|
234
|
-
val.indexOf("6a4e0400000042434d52") === 0
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
if (bcmrOpReturns.length === 0) {
|
|
238
|
-
return {
|
|
239
|
-
txHash: hash,
|
|
240
|
-
contentHash: "",
|
|
241
|
-
uris: [],
|
|
242
|
-
httpsUrl: "",
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const opReturnHex = opReturns[0];
|
|
247
|
-
const chunks = OpReturnData.parseBinary(hexToBin(opReturnHex));
|
|
248
|
-
if (chunks.length < 2) {
|
|
249
|
-
throw new Error(`Malformed BCMR output: ${opReturnHex}`);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const result: AuthChainElement = {
|
|
253
|
-
txHash: hash,
|
|
254
|
-
contentHash: "",
|
|
255
|
-
uris: [],
|
|
256
|
-
httpsUrl: "",
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
if (chunks.length === 2) {
|
|
260
|
-
// IPFS Publication Output
|
|
261
|
-
result.contentHash = binToHex(chunks[1]);
|
|
262
|
-
const ipfsCid = binToUtf8(chunks[1]);
|
|
263
|
-
result.uris = [`ipfs://${ipfsCid}`];
|
|
264
|
-
result.httpsUrl = `https://dweb.link/ipfs/${ipfsCid}`;
|
|
265
|
-
} else {
|
|
266
|
-
// URI Publication Output
|
|
267
|
-
// content hash is in OP_SHA256 byte order per spec
|
|
268
|
-
result.contentHash = binToHex(chunks[1].slice());
|
|
269
|
-
|
|
270
|
-
const uris = chunks.slice(2);
|
|
271
|
-
|
|
272
|
-
for (const uri of uris) {
|
|
273
|
-
const uriString = binToUtf8(uri);
|
|
274
|
-
result.uris.push(uriString);
|
|
275
|
-
|
|
276
|
-
if (result.httpsUrl) {
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (uriString.indexOf("https://") === 0) {
|
|
281
|
-
result.httpsUrl = uriString;
|
|
282
|
-
} else if (uriString.indexOf("https://") === -1) {
|
|
283
|
-
result.httpsUrl = uriString;
|
|
284
|
-
|
|
285
|
-
// case for domain name specifier, like example.com
|
|
286
|
-
if (uriString.indexOf("/") === -1) {
|
|
287
|
-
result.httpsUrl = `${result.httpsUrl}/.well-known/bitcoin-cash-metadata-registry.json`;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
result.httpsUrl = `https://${result.httpsUrl}`;
|
|
291
|
-
} else if (uriString.indexOf("ipfs://") === 0) {
|
|
292
|
-
const ipfsCid = uriString.replace("ipfs://", "");
|
|
293
|
-
result.httpsUrl = `https://dweb.link/ipfs/${ipfsCid}`;
|
|
294
|
-
} else {
|
|
295
|
-
throw new Error(`Unsupported uri type: ${uriString}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return result;
|
|
300
|
-
};
|
|
301
|
-
|
|
302
301
|
// make authchain element and combine with the rest obtained
|
|
303
302
|
let element: AuthChainElement;
|
|
304
303
|
try {
|
|
305
|
-
element = makeAuthChainElement(options.rawTx, options.rawTx.hash);
|
|
304
|
+
element = BCMR.makeAuthChainElement(options.rawTx, options.rawTx.hash);
|
|
306
305
|
} catch (error) {
|
|
307
306
|
// special case for cashtoken authchain lookup by categoryId - allow to fail first lookup and inspect the genesis transaction
|
|
308
307
|
// follow authchain to head and look for BCMR outputs
|
|
@@ -349,7 +348,7 @@ export class BCMR {
|
|
|
349
348
|
);
|
|
350
349
|
return { decoded, hash };
|
|
351
350
|
})
|
|
352
|
-
.map(({ decoded, hash }) => makeAuthChainElement(decoded, hash));
|
|
351
|
+
.map(({ decoded, hash }) => BCMR.makeAuthChainElement(decoded, hash));
|
|
353
352
|
} else {
|
|
354
353
|
// simply go back in history towards authhead
|
|
355
354
|
let stop = false;
|
|
@@ -359,7 +358,7 @@ export class BCMR {
|
|
|
359
358
|
const vin = tx.vin.find((val) => val.vout === 0);
|
|
360
359
|
tx = await provider.getRawTransactionObject(vin!.txid);
|
|
361
360
|
try {
|
|
362
|
-
const pastElement = makeAuthChainElement(tx, tx.hash);
|
|
361
|
+
const pastElement = BCMR.makeAuthChainElement(tx, tx.hash);
|
|
363
362
|
chainBase.unshift(pastElement);
|
|
364
363
|
maxElements--;
|
|
365
364
|
} catch {
|
|
@@ -394,6 +393,95 @@ export class BCMR {
|
|
|
394
393
|
return [...chainBase, element].filter((val) => val.httpsUrl.length);
|
|
395
394
|
}
|
|
396
395
|
|
|
396
|
+
/**
|
|
397
|
+
* fetchAuthChainFromChaingraph Fetch the authchain information from a trusted external indexer
|
|
398
|
+
* The authchain in this implementation is specific to resolve to a valid metadata registry
|
|
399
|
+
*
|
|
400
|
+
* @param {string} options.chaingraphUrl (required) URL of a chaingraph indexer instance to fetch info from
|
|
401
|
+
* @param {string} options.transactionHash (required) transaction hash from which to build the auth chain
|
|
402
|
+
* @param {string?} options.network (default=mainnet) network to query the data from, specific to the queried instance, can be mainnet, chipnet, or anything else
|
|
403
|
+
*
|
|
404
|
+
* @returns {AuthChain} returns the resolved authchain
|
|
405
|
+
*/
|
|
406
|
+
public static async fetchAuthChainFromChaingraph(options: {
|
|
407
|
+
chaingraphUrl: string;
|
|
408
|
+
transactionHash: string;
|
|
409
|
+
network?: string;
|
|
410
|
+
}): Promise<AuthChain> {
|
|
411
|
+
if (!options.chaingraphUrl) {
|
|
412
|
+
throw new Error("Provide `chaingraphUrl` param.")
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (options.network === undefined) {
|
|
416
|
+
options.network = "mainnet";
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const response = await axios.post(options.chaingraphUrl, {
|
|
420
|
+
operationName: null,
|
|
421
|
+
variables: {},
|
|
422
|
+
query:
|
|
423
|
+
`{
|
|
424
|
+
transaction(
|
|
425
|
+
where: {
|
|
426
|
+
hash:{_eq:"\\\\x${options.transactionHash}"},
|
|
427
|
+
block_inclusions:{block:{accepted_by:{node:{name:{_ilike:"%${options.network}%"}}}}}
|
|
428
|
+
}
|
|
429
|
+
) {
|
|
430
|
+
hash
|
|
431
|
+
authchains {
|
|
432
|
+
authchain_length
|
|
433
|
+
migrations(
|
|
434
|
+
where: {
|
|
435
|
+
transaction: {
|
|
436
|
+
outputs: { locking_bytecode_pattern: { _like: "6a04%" } }
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
) {
|
|
440
|
+
transaction {
|
|
441
|
+
hash
|
|
442
|
+
inputs(where:{ outpoint_index: { _eq:"0" } }){
|
|
443
|
+
outpoint_index
|
|
444
|
+
}
|
|
445
|
+
outputs(where: { locking_bytecode_pattern: { _like: "6a04%" } }) {
|
|
446
|
+
output_index
|
|
447
|
+
locking_bytecode
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}`
|
|
454
|
+
}, {
|
|
455
|
+
responseType: "json",
|
|
456
|
+
headers: {
|
|
457
|
+
Accept: "*/*",
|
|
458
|
+
"Content-Type": "application/json",
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const result: AuthChain = [];
|
|
463
|
+
const migrations = response.data.data.transaction[0]?.authchains[0].migrations;
|
|
464
|
+
if (!migrations) {
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
for (const migration of migrations) {
|
|
469
|
+
const transaction = migration.transaction[0];
|
|
470
|
+
if (!transaction) {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
transaction.inputs.forEach(input => input.outpointIndex = Number(input.outpoint_index));
|
|
474
|
+
transaction.outputs.forEach(output => {
|
|
475
|
+
output.outputIndex = Number(output.output_index);
|
|
476
|
+
output.lockingBytecode = hexToBin(output.locking_bytecode.replace("\\x", ""));
|
|
477
|
+
});
|
|
478
|
+
const txHash = transaction.hash.replace("\\x", "");
|
|
479
|
+
result.push(BCMR.makeAuthChainElement(transaction as Transaction, txHash));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
484
|
+
|
|
397
485
|
/**
|
|
398
486
|
* addMetadataRegistryAuthChain Add BCMR metadata registry by resolving an authchain
|
|
399
487
|
*
|