2024-10-07 20:03:36 +09:00
|
|
|
import { Injectable } from '@angular/core';
|
|
|
|
import { catchError, forkJoin, map, Observable, of, switchMap, tap } from 'rxjs';
|
|
|
|
import { Inscription } from '../components/ord-data/ord-data.component';
|
|
|
|
import { Transaction } from '../interfaces/electrs.interface';
|
|
|
|
import { getNextInscriptionMark, hexToBytes, extractInscriptionData } from '../shared/ord/inscription.utils';
|
2024-10-08 01:41:35 +00:00
|
|
|
import { decipherRunestone, Runestone, Etching, UNCOMMON_GOODS } from '../shared/ord/rune.utils';
|
2024-10-07 20:03:36 +09:00
|
|
|
import { ElectrsApiService } from './electrs-api.service';
|
2024-10-08 01:41:35 +00:00
|
|
|
|
2024-10-07 20:03:36 +09:00
|
|
|
|
|
|
|
@Injectable({
|
|
|
|
providedIn: 'root'
|
|
|
|
})
|
|
|
|
export class OrdApiService {
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
private electrsApiService: ElectrsApiService,
|
|
|
|
) { }
|
|
|
|
|
|
|
|
decodeRunestone$(tx: Transaction): Observable<{ runestone: Runestone, runeInfo: { [id: string]: { etching: Etching; txid: string; } } }> {
|
2024-10-08 01:41:35 +00:00
|
|
|
const runestone = decipherRunestone(tx);
|
2024-10-07 20:03:36 +09:00
|
|
|
const runeInfo: { [id: string]: { etching: Etching; txid: string; } } = {};
|
|
|
|
|
|
|
|
if (runestone) {
|
2024-10-08 12:38:12 +09:00
|
|
|
const runesToFetch: Set<string> = new Set();
|
|
|
|
|
2024-10-08 01:41:35 +00:00
|
|
|
if (runestone.mint) {
|
2024-10-08 12:38:12 +09:00
|
|
|
runesToFetch.add(runestone.mint.toString());
|
2024-10-07 20:03:36 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
if (runestone.edicts.length) {
|
|
|
|
runestone.edicts.forEach(edict => {
|
|
|
|
runesToFetch.add(edict.id.toString());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (runesToFetch.size) {
|
2024-10-08 12:38:12 +09:00
|
|
|
const runeEtchingObservables = Array.from(runesToFetch).map(runeId => this.getEtchingFromRuneId$(runeId));
|
2024-10-07 20:03:36 +09:00
|
|
|
|
|
|
|
return forkJoin(runeEtchingObservables).pipe(
|
2024-10-08 12:38:12 +09:00
|
|
|
map((etchings) => {
|
|
|
|
etchings.forEach((el) => {
|
|
|
|
if (el) {
|
|
|
|
runeInfo[el.runeId] = { etching: el.etching, txid: el.txid };
|
|
|
|
}
|
|
|
|
});
|
2024-10-07 20:03:36 +09:00
|
|
|
return { runestone: runestone, runeInfo };
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
2024-10-08 01:41:35 +00:00
|
|
|
return of({ runestone: runestone, runeInfo });
|
|
|
|
} else {
|
|
|
|
return of({ runestone: null, runeInfo: {} });
|
2024-10-07 20:03:36 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get etching from runeId by looking up the transaction that etched the rune
|
2024-10-08 12:38:12 +09:00
|
|
|
getEtchingFromRuneId$(runeId: string): Observable<{ runeId: string; etching: Etching; txid: string; }> {
|
|
|
|
if (runeId === '1:0') {
|
|
|
|
return of({ runeId, etching: UNCOMMON_GOODS, txid: '0000000000000000000000000000000000000000000000000000000000000000' });
|
|
|
|
} else {
|
|
|
|
const [blockNumber, txIndex] = runeId.split(':');
|
|
|
|
return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockNumber)).pipe(
|
|
|
|
switchMap(blockHash => this.electrsApiService.getBlockTxId$(blockHash, parseInt(txIndex))),
|
|
|
|
switchMap(txId => this.electrsApiService.getTransaction$(txId)),
|
|
|
|
switchMap(tx => {
|
|
|
|
const runestone = decipherRunestone(tx);
|
|
|
|
if (runestone) {
|
|
|
|
const etching = runestone.etching;
|
|
|
|
if (etching) {
|
|
|
|
return of({ runeId, etching, txid: tx.txid });
|
|
|
|
}
|
2024-10-07 20:03:36 +09:00
|
|
|
}
|
2024-10-08 12:38:12 +09:00
|
|
|
return of(null);
|
|
|
|
}),
|
|
|
|
catchError(() => of(null))
|
|
|
|
);
|
|
|
|
}
|
2024-10-07 20:03:36 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
decodeInscriptions(witness: string): Inscription[] | null {
|
|
|
|
|
|
|
|
const inscriptions: Inscription[] = [];
|
|
|
|
const raw = hexToBytes(witness);
|
|
|
|
let startPosition = 0;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
const pointer = getNextInscriptionMark(raw, startPosition);
|
|
|
|
if (pointer === -1) break;
|
|
|
|
|
|
|
|
const inscription = extractInscriptionData(raw, pointer);
|
|
|
|
if (inscription) {
|
|
|
|
inscriptions.push(inscription);
|
|
|
|
}
|
|
|
|
|
|
|
|
startPosition = pointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return inscriptions;
|
|
|
|
}
|
|
|
|
}
|