mainnet-js 1.1.22 → 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.
@@ -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 v1.0.0, refer to https://github.com/bitjson/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
  *