@@ -68,8 +62,8 @@
}
- {{ runeInfo[id]?.etching.symbol.isSome() ? runeInfo[id]?.etching.symbol.unwrap() : '' }}
+ {{ runeInfo[id]?.etching.symbol || '' }}
- {{ runeInfo[id]?.name }}
+ {{ runeInfo[id]?.etching.spacedName }}
\ No newline at end of file
diff --git a/frontend/src/app/components/ord-data/ord-data.component.ts b/frontend/src/app/components/ord-data/ord-data.component.ts
index 8d7eef973..233b8d243 100644
--- a/frontend/src/app/components/ord-data/ord-data.component.ts
+++ b/frontend/src/app/components/ord-data/ord-data.component.ts
@@ -1,9 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
-import { Runestone } from '../../shared/ord/rune/runestone';
-import { Etching } from '../../shared/ord/rune/etching';
-import { u128, u32, u8 } from '../../shared/ord/rune/integer';
import { HttpErrorResponse } from '@angular/common/http';
-import { SpacedRune } from '../../shared/ord/rune/spacedrune';
+import { Runestone, Etching } from '../../shared/ord/rune.utils';
export interface Inscription {
body?: Uint8Array;
@@ -22,79 +19,34 @@ export interface Inscription {
export class OrdDataComponent implements OnChanges {
@Input() inscriptions: Inscription[];
@Input() runestone: Runestone;
- @Input() runeInfo: { [id: string]: { etching: Etching; txid: string; name?: string; } };
+ @Input() runeInfo: { [id: string]: { etching: Etching; txid: string } };
@Input() error: HttpErrorResponse;
@Input() type: 'vin' | 'vout';
+ toNumber = (value: bigint): number => Number(value);
+
// Inscriptions
inscriptionsData: { [key: string]: { count: number, totalSize: number, text?: string; json?: JSON; tag?: string; delegate?: string } };
// Rune mints
minted: number;
- // Rune etching
- premined: number = -1;
- totalSupply: number = -1;
- etchedName: string;
- etchedSymbol: string;
// Rune transfers
- transferredRunes: { key: string; etching: Etching; txid: string; name?: string; }[] = [];
+ transferredRunes: { key: string; etching: Etching; txid: string }[] = [];
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
if (changes.runestone && this.runestone) {
-
- Object.keys(this.runeInfo).forEach((key) => {
- const rune = this.runeInfo[key].etching.rune.isSome() ? this.runeInfo[key].etching.rune.unwrap() : null;
- const spacers = this.runeInfo[key].etching.spacers.isSome() ? this.runeInfo[key].etching.spacers.unwrap() : u32(0);
- if (rune) {
- this.runeInfo[key].name = new SpacedRune(rune, Number(spacers)).toString();
- }
- this.transferredRunes.push({ key, ...this.runeInfo[key] });
- });
-
-
- if (this.runestone.mint.isSome() && this.runeInfo[this.runestone.mint.unwrap().toString()]) {
- const mint = this.runestone.mint.unwrap().toString();
+ this.transferredRunes = Object.entries(this.runeInfo).map(([key, runeInfo]) => ({ key, ...runeInfo }));
+ if (this.runestone.mint && this.runeInfo[this.runestone.mint.toString()]) {
+ const mint = this.runestone.mint.toString();
this.transferredRunes = this.transferredRunes.filter(rune => rune.key !== mint);
- const terms = this.runeInfo[mint].etching.terms.isSome() ? this.runeInfo[mint].etching.terms.unwrap() : null;
- let amount: u128;
- if (terms) {
- amount = terms.amount.isSome() ? terms.amount.unwrap() : u128(0);
- }
- const divisibility = this.runeInfo[mint].etching.divisibility.isSome() ? this.runeInfo[mint].etching.divisibility.unwrap() : u8(0);
+ const terms = this.runeInfo[mint].etching.terms;
+ const amount = terms?.amount;
+ const divisibility = this.runeInfo[mint].etching.divisibility;
if (amount) {
this.minted = this.getAmount(amount, divisibility);
}
}
-
- if (this.runestone.etching.isSome()) {
- const etching = this.runestone.etching.unwrap();
- const rune = etching.rune.isSome() ? etching.rune.unwrap() : null;
- const spacers = etching.spacers.isSome() ? etching.spacers.unwrap() : u32(0);
- if (rune) {
- this.etchedName = new SpacedRune(rune, Number(spacers)).toString();
- }
- this.etchedSymbol = etching.symbol.isSome() ? etching.symbol.unwrap() : '';
-
- const divisibility = etching.divisibility.isSome() ? etching.divisibility.unwrap() : u8(0);
- const premine = etching.premine.isSome() ? etching.premine.unwrap() : u128(0);
- if (premine) {
- this.premined = this.getAmount(premine, divisibility);
- } else {
- this.premined = 0;
- }
- const terms = etching.terms.isSome() ? etching.terms.unwrap() : null;
- let amount: u128;
- if (terms) {
- amount = terms.amount.isSome() ? terms.amount.unwrap() : u128(0);
- if (amount) {
- const cap = terms.cap.isSome() ? terms.cap.unwrap() : u128(0);
- this.totalSupply = this.premined + this.getAmount(amount, divisibility) * Number(cap);
- }
- } else {
- this.totalSupply = this.premined;
- }
- }
}
if (changes.inscriptions && this.inscriptions) {
@@ -131,7 +83,7 @@ export class OrdDataComponent implements OnChanges {
}
}
- getAmount(amount: u128 | bigint, divisibility: u8): number {
+ getAmount(amount: bigint, divisibility: number): number {
const divisor = BigInt(10) ** BigInt(divisibility);
const result = amount / divisor;
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts
index 1f45d5241..706ee9684 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.ts
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts
@@ -6,15 +6,14 @@ import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.inter
import { ElectrsApiService } from '../../services/electrs-api.service';
import { environment } from '../../../environments/environment';
import { AssetsService } from '../../services/assets.service';
-import { filter, map, tap, switchMap, shareReplay, catchError } from 'rxjs/operators';
+import { filter, map, tap, switchMap, catchError } from 'rxjs/operators';
import { BlockExtended } from '../../interfaces/node-api.interface';
import { ApiService } from '../../services/api.service';
import { PriceService } from '../../services/price.service';
import { StorageService } from '../../services/storage.service';
import { OrdApiService } from '../../services/ord-api.service';
import { Inscription } from '../ord-data/ord-data.component';
-import { Runestone } from '../../shared/ord/rune/runestone';
-import { Etching } from '../../shared/ord/rune/etching';
+import { Etching, Runestone } from '../../shared/ord/rune.utils';
@Component({
selector: 'app-transactions-list',
@@ -261,7 +260,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
tx.vout[i].isRunestone = true;
break;
}
- }
+ }
}
});
diff --git a/frontend/src/app/services/ord-api.service.ts b/frontend/src/app/services/ord-api.service.ts
index bc726e839..da75a74af 100644
--- a/frontend/src/app/services/ord-api.service.ts
+++ b/frontend/src/app/services/ord-api.service.ts
@@ -3,10 +3,9 @@ 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';
-import { Runestone } from '../shared/ord/rune/runestone';
-import { Etching } from '../shared/ord/rune/etching';
+import { decipherRunestone, Runestone, Etching, UNCOMMON_GOODS } from '../shared/ord/rune.utils';
import { ElectrsApiService } from './electrs-api.service';
-import { UNCOMMON_GOODS } from '../shared/ord/rune/runestone';
+
@Injectable({
providedIn: 'root'
@@ -18,27 +17,16 @@ export class OrdApiService {
) { }
decodeRunestone$(tx: Transaction): Observable<{ runestone: Runestone, runeInfo: { [id: string]: { etching: Etching; txid: string; } } }> {
- const runestoneTx = { vout: tx.vout.map(vout => ({ scriptpubkey: vout.scriptpubkey })) };
- const decipher = Runestone.decipher(runestoneTx);
-
- // For now, ignore cenotaphs
- let message = decipher.isSome() ? decipher.unwrap() : null;
- if (message?.type === 'cenotaph') {
- return of({ runestone: null, runeInfo: {} });
- }
-
- const runestone = message as Runestone;
+ const runestone = decipherRunestone(tx);
const runeInfo: { [id: string]: { etching: Etching; txid: string; } } = {};
const runesToFetch: Set
= new Set();
if (runestone) {
- if (runestone.mint.isSome()) {
- const mint = runestone.mint.unwrap().toString();
-
- if (mint === '1:0') {
- runeInfo[mint] = { etching: UNCOMMON_GOODS, txid: '0000000000000000000000000000000000000000000000000000000000000000' };
+ if (runestone.mint) {
+ if (runestone.mint.toString() === '1:0') {
+ runeInfo[runestone.mint.toString()] = { etching: UNCOMMON_GOODS, txid: '0000000000000000000000000000000000000000000000000000000000000000' };
} else {
- runesToFetch.add(mint);
+ runesToFetch.add(runestone.mint.toString());
}
}
@@ -65,9 +53,10 @@ export class OrdApiService {
})
);
}
+ return of({ runestone: runestone, runeInfo });
+ } else {
+ return of({ runestone: null, runeInfo: {} });
}
-
- return of({ runestone: runestone, runeInfo });
}
// Get etching from runeId by looking up the transaction that etched the rune
@@ -78,11 +67,11 @@ export class OrdApiService {
switchMap(blockHash => this.electrsApiService.getBlockTxId$(blockHash, parseInt(txIndex))),
switchMap(txId => this.electrsApiService.getTransaction$(txId)),
switchMap(tx => {
- const decipheredMessage = Runestone.decipher(tx);
- if (decipheredMessage.isSome()) {
- const message = decipheredMessage.unwrap();
- if (message?.type === 'runestone' && message.etching.isSome()) {
- return of({ etching: message.etching.unwrap(), txid: tx.txid });
+ const runestone = decipherRunestone(tx);
+ if (runestone) {
+ const etching = runestone.etching;
+ if (etching) {
+ return of({ etching, txid: tx.txid });
}
}
return of(null);
diff --git a/frontend/src/app/shared/ord/rune.utils.ts b/frontend/src/app/shared/ord/rune.utils.ts
new file mode 100644
index 000000000..a1f947b46
--- /dev/null
+++ b/frontend/src/app/shared/ord/rune.utils.ts
@@ -0,0 +1,258 @@
+import { Transaction } from '../../interfaces/electrs.interface';
+
+export const U128_MAX_BIGINT = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffffn;
+
+export class RuneId {
+ block: number;
+ index: number;
+
+ constructor(block: number, index: number) {
+ this.block = block;
+ this.index = index;
+ }
+
+ toString(): string {
+ return `${this.block}:${this.index}`;
+ }
+}
+
+export type Etching = {
+ divisibility?: number;
+ premine?: bigint;
+ symbol?: string;
+ terms?: {
+ cap?: bigint;
+ amount?: bigint;
+ offset?: {
+ start?: bigint;
+ end?: bigint;
+ };
+ height?: {
+ start?: bigint;
+ end?: bigint;
+ };
+ };
+ turbo?: boolean;
+ name?: string;
+ spacedName?: string;
+ supply?: bigint;
+};
+
+export type Edict = {
+ id: RuneId;
+ amount: bigint;
+ output: number;
+};
+
+export type Runestone = {
+ mint?: RuneId;
+ pointer?: number;
+ edicts?: Edict[];
+ etching?: Etching;
+};
+
+type Message = {
+ fields: Record;
+ edicts: Edict[];
+}
+
+export const UNCOMMON_GOODS: Etching = {
+ divisibility: 0,
+ premine: 0n,
+ symbol: '⧉',
+ terms: {
+ cap: U128_MAX_BIGINT,
+ amount: 1n,
+ offset: {
+ start: 0n,
+ end: 0n,
+ },
+ height: {
+ start: 840000n,
+ end: 1050000n,
+ },
+ },
+ turbo: false,
+ name: 'UNCOMMONGOODS',
+ spacedName: 'UNCOMMON•GOODS',
+ supply: U128_MAX_BIGINT,
+};
+
+enum Tag {
+ Body = 0,
+ Flags = 2,
+ Rune = 4,
+ Premine = 6,
+ Cap = 8,
+ Amount = 10,
+ HeightStart = 12,
+ HeightEnd = 14,
+ OffsetStart = 16,
+ OffsetEnd = 18,
+ Mint = 20,
+ Pointer = 22,
+ Cenotaph = 126,
+
+ Divisibility = 1,
+ Spacers = 3,
+ Symbol = 5,
+ Nop = 127,
+}
+
+const Flag = {
+ ETCHING: 1n,
+ TERMS: 1n << 1n,
+ TURBO: 1n << 2n,
+ CENOTAPH: 1n << 127n,
+};
+
+function hexToBytes(hex: string): Uint8Array {
+ return new Uint8Array(hex.match(/.{2}/g).map((byte) => parseInt(byte, 16)));
+}
+
+function decodeLEB128(bytes: Uint8Array): bigint[] {
+ const integers: bigint[] = [];
+ let index = 0;
+ while (index < bytes.length) {
+ let value = BigInt(0);
+ let shift = 0;
+ let byte: number;
+ do {
+ byte = bytes[index++];
+ value |= BigInt(byte & 0x7f) << BigInt(shift);
+ shift += 7;
+ } while (byte & 0x80);
+ integers.push(value);
+ }
+ return integers;
+}
+
+function integersToMessage(integers: bigint[]): Message {
+ const message = {
+ fields: {},
+ edicts: [],
+ };
+ let inBody = false;
+ while (integers.length) {
+ if (!inBody) {
+ // The integers are interpreted as a sequence of tag/value pairs, with duplicate tags appending their value to the field value.
+ const tag: Tag = Number(integers.shift());
+ if (tag === Tag.Body) {
+ inBody = true;
+ } else {
+ const value = integers.shift();
+ if (message.fields[tag]) {
+ message.fields[tag].push(value);
+ } else {
+ message.fields[tag] = [value];
+ }
+ }
+ } else {
+ // If a tag with value zero is encountered, all following integers are interpreted as a series of four-integer edicts, each consisting of a rune ID block height, rune ID transaction index, amount, and output.
+ const height = integers.shift();
+ const txIndex = integers.shift();
+ const amount = integers.shift();
+ const output = integers.shift();
+ message.edicts.push({
+ id: {
+ block: height,
+ index: txIndex,
+ },
+ amount,
+ output,
+ });
+ }
+ }
+ return message;
+}
+
+function parseRuneName(rune: bigint): string {
+ let name = '';
+ rune += 1n;
+ while (rune > 0n) {
+ name = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[Number((rune - 1n) % 26n)] + name;
+ rune = (rune - 1n) / 26n;
+ }
+ return name;
+}
+
+function spaceRuneName(name: string, spacers: bigint): string {
+ let i = 0;
+ let spacedName = '';
+ while (spacers > 0n || i < name.length) {
+ spacedName += name[i];
+ if (spacers & 1n) {
+ spacedName += '•';
+ }
+ if (spacers > 0n) {
+ spacers >>= 1n;
+ }
+ i++;
+ }
+ return spacedName;
+}
+
+function messageToRunestone(message: Message): Runestone {
+ let etching: Etching | undefined;
+ let mint: RuneId | undefined;
+ let pointer: number | undefined;
+
+ const flags = message.fields[Tag.Flags]?.[0] || 0n;
+ if (flags & Flag.ETCHING) {
+ const hasTerms = (flags & Flag.TERMS) > 0n;
+ const isTurbo = (flags & Flag.TURBO) > 0n;
+ const name = parseRuneName(message.fields[Tag.Rune][0]);
+ etching = {
+ divisibility: Number(message.fields[Tag.Divisibility][0]),
+ premine: message.fields[Tag.Premine]?.[0],
+ symbol: message.fields[Tag.Symbol]?.[0] ? String.fromCodePoint(Number(message.fields[Tag.Symbol][0])) : '¤',
+ terms: hasTerms ? {
+ cap: message.fields[Tag.Cap]?.[0],
+ amount: message.fields[Tag.Amount]?.[0],
+ offset: {
+ start: message.fields[Tag.OffsetStart]?.[0],
+ end: message.fields[Tag.OffsetEnd]?.[0],
+ },
+ height: {
+ start: message.fields[Tag.HeightStart]?.[0],
+ end: message.fields[Tag.HeightEnd]?.[0],
+ },
+ } : undefined,
+ turbo: isTurbo,
+ name,
+ spacedName: spaceRuneName(name, message.fields[Tag.Spacers]?.[0] ?? 0n),
+ };
+ etching.supply = (
+ (etching.terms?.cap ?? 0n) * (etching.terms?.amount ?? 0n)
+ ) + (etching.premine ?? 0n);
+ }
+ const mintField = message.fields[Tag.Mint];
+ if (mintField) {
+ mint = new RuneId(Number(mintField[0]), Number(mintField[1]));
+ }
+ const pointerField = message.fields[Tag.Pointer];
+ if (pointerField) {
+ pointer = Number(pointerField[0]);
+ }
+ return {
+ mint,
+ pointer,
+ edicts: message.edicts,
+ etching,
+ };
+}
+
+export function decipherRunestone(tx: Transaction): Runestone | void {
+ const payload = tx.vout.find((vout) => vout.scriptpubkey.startsWith('6a5d'))?.scriptpubkey_asm.replace(/OP_\w+|\s/g, '');
+ if (!payload) {
+ return;
+ }
+ try {
+ const integers = decodeLEB128(hexToBytes(payload));
+ const message = integersToMessage(integers);
+ return messageToRunestone(message);
+ } catch (error) {
+ console.error(error);
+ return;
+ }
+}
diff --git a/frontend/src/app/shared/ord/rune/artifact.ts b/frontend/src/app/shared/ord/rune/artifact.ts
deleted file mode 100644
index 2eba9f158..000000000
--- a/frontend/src/app/shared/ord/rune/artifact.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { Cenotaph } from './cenotaph';
-import { Runestone } from './runestone';
-
-export type Artifact = Cenotaph | Runestone;
diff --git a/frontend/src/app/shared/ord/rune/cenotaph.ts b/frontend/src/app/shared/ord/rune/cenotaph.ts
deleted file mode 100644
index 368a0f938..000000000
--- a/frontend/src/app/shared/ord/rune/cenotaph.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Flaw } from './flaw';
-import { None, Option } from './monads';
-import { Rune } from './rune';
-import { RuneId } from './runeid';
-
-export class Cenotaph {
- readonly type = 'cenotaph';
-
- constructor(
- readonly flaws: Flaw[],
- readonly etching: Option = None,
- readonly mint: Option = None
- ) {}
-}
diff --git a/frontend/src/app/shared/ord/rune/constants.ts b/frontend/src/app/shared/ord/rune/constants.ts
deleted file mode 100644
index 0e4bab116..000000000
--- a/frontend/src/app/shared/ord/rune/constants.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { u8 } from './integer';
-import { opcodes } from './script';
-
-export const MAX_DIVISIBILITY = u8(38);
-
-export const OP_RETURN = opcodes.OP_RETURN;
-export const MAGIC_NUMBER = opcodes.OP_13;
diff --git a/frontend/src/app/shared/ord/rune/edict.ts b/frontend/src/app/shared/ord/rune/edict.ts
deleted file mode 100644
index ede5865a6..000000000
--- a/frontend/src/app/shared/ord/rune/edict.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Option, Some, None } from './monads';
-import { RuneId } from './runeid';
-import { u128, u32 } from './integer';
-
-export type Edict = {
- id: RuneId;
- amount: u128;
- output: u32;
-};
-
-export namespace Edict {
- export function fromIntegers(
- numOutputs: number,
- id: RuneId,
- amount: u128,
- output: u128
- ): Option {
- if (id.block === 0n && id.tx > 0n) {
- return None;
- }
-
- const optionOutputU32 = u128.tryIntoU32(output);
- if (optionOutputU32.isNone()) {
- return None;
- }
- const outputU32 = optionOutputU32.unwrap();
-
- if (outputU32 > numOutputs) {
- return None;
- }
-
- return Some({ id, amount, output: outputU32 });
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/etching.ts b/frontend/src/app/shared/ord/rune/etching.ts
deleted file mode 100644
index edc245565..000000000
--- a/frontend/src/app/shared/ord/rune/etching.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { None, Option, Some } from './monads';
-import { Terms } from './terms';
-import { Rune } from './rune';
-import { u128, u32, u8 } from './integer';
-
-type RuneEtchingBase = {
- divisibility?: number;
- premine?: bigint;
- symbol?: string;
- terms?: {
- cap?: bigint;
- amount?: bigint;
- offset?: {
- start?: bigint;
- end?: bigint;
- };
- height?: {
- start?: bigint;
- end?: bigint;
- };
- };
- turbo?: boolean;
-};
-
-export type RuneEtchingSpec = RuneEtchingBase & { runeName?: string };
-
-export class Etching {
- readonly symbol: Option;
-
- constructor(
- readonly divisibility: Option,
- readonly rune: Option,
- readonly spacers: Option,
- symbol: Option,
- readonly terms: Option,
- readonly premine: Option,
- readonly turbo: boolean
- ) {
- this.symbol = symbol.andThen((value) => {
- const codePoint = value.codePointAt(0);
- return codePoint !== undefined ? Some(String.fromCodePoint(codePoint)) : None;
- });
- }
-
- get supply(): Option {
- const premine = this.premine.unwrapOr(u128(0));
- const cap = this.terms.andThen((terms) => terms.cap).unwrapOr(u128(0));
- const amount = this.terms.andThen((terms) => terms.amount).unwrapOr(u128(0));
-
- return u128
- .checkedMultiply(cap, amount)
- .andThen((multiplyResult) => u128.checkedAdd(premine, multiplyResult));
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/flag.ts b/frontend/src/app/shared/ord/rune/flag.ts
deleted file mode 100644
index 317c74ae5..000000000
--- a/frontend/src/app/shared/ord/rune/flag.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { u128 } from './integer';
-
-export enum Flag {
- ETCHING = 0,
- TERMS = 1,
- TURBO = 2,
- CENOTAPH = 127,
-}
-
-export namespace Flag {
- export function mask(flag: Flag): u128 {
- return u128(1n << BigInt(flag));
- }
-
- export function take(flags: u128, flag: Flag): { set: boolean; flags: u128 } {
- const mask = Flag.mask(flag);
- const set = (flags & mask) !== 0n;
- return { set, flags: set ? u128(flags - mask) : flags };
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/flaw.ts b/frontend/src/app/shared/ord/rune/flaw.ts
deleted file mode 100644
index 2ed5ea506..000000000
--- a/frontend/src/app/shared/ord/rune/flaw.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export enum Flaw {
- EDICT_OUTPUT,
- EDICT_RUNE_ID,
- INVALID_SCRIPT,
- OPCODE,
- SUPPLY_OVERFLOW,
- TRAILING_INTEGERS,
- TRUNCATED_FIELD,
- UNRECOGNIZED_EVEN_TAG,
- UNRECOGNIZED_FLAG,
- VARINT,
-}
diff --git a/frontend/src/app/shared/ord/rune/integer/index.ts b/frontend/src/app/shared/ord/rune/integer/index.ts
deleted file mode 100644
index 3c54a77e7..000000000
--- a/frontend/src/app/shared/ord/rune/integer/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export { u8 } from './u8';
-export { u32 } from './u32';
-export { u64 } from './u64';
-export { u128 } from './u128';
diff --git a/frontend/src/app/shared/ord/rune/integer/u128.ts b/frontend/src/app/shared/ord/rune/integer/u128.ts
deleted file mode 100644
index 78de8506f..000000000
--- a/frontend/src/app/shared/ord/rune/integer/u128.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import { None, Option, Some } from '../monads';
-import { SeekArray } from '../seekarray';
-import { u64 } from './u64';
-import { u32 } from './u32';
-import { u8 } from './u8';
-
-/**
- * A little utility type used for nominal typing.
- *
- * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
- */
-type BigTypedNumber = bigint & {
- /**
- * # !!! DO NOT USE THIS PROPERTY IN YOUR CODE !!!
- * ## This is just used to make each `BigTypedNumber` alias unique for Typescript and doesn't actually exist.
- * @ignore
- * @private
- * @readonly
- * @type {undefined}
- */
- readonly __kind__: T;
-};
-
-/**
- * ## 128-bit unsigned integer
- *
- * - **Value Range:** `0` to `340282366920938463463374607431768211455`
- * - **Size in bytes:** `16`
- * - **Web IDL type:** `bigint`
- * - **Equivalent C type:** `uint128_t`
- */
-export type u128 = BigTypedNumber<'u128'>;
-
-export const U128_MAX_BIGINT = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffffn;
-
-/**
- * Convert Number or BigInt to 128-bit unsigned integer.
- * @param num - The Number or BigInt to convert.
- * @returns - The resulting 128-bit unsigned integer (BigInt).
- */
-export function u128(num: number | bigint): u128 {
- if (typeof num == 'bigint') {
- if (num < 0n || num > U128_MAX_BIGINT) {
- throw new Error('num is out of range');
- }
- } else {
- if (!Number.isSafeInteger(num) || num < 0) {
- throw new Error('num is not a valid integer');
- }
- }
-
- return BigInt(num) as u128;
-}
-
-export namespace u128 {
- export const MAX = u128(U128_MAX_BIGINT);
-
- export function checkedAdd(x: u128, y: u128): Option {
- const result = x + y;
- if (result > u128.MAX) {
- return None;
- }
-
- return Some(u128(result));
- }
-
- export function checkedAddThrow(x: u128, y: u128): u128 {
- const option = u128.checkedAdd(x, y);
- if (option.isNone()) {
- throw new Error('checked add overflow');
- }
- return option.unwrap();
- }
-
- export function checkedSub(x: u128, y: u128): Option {
- const result = x - y;
- if (result < 0n) {
- return None;
- }
-
- return Some(u128(result));
- }
-
- export function checkedSubThrow(x: u128, y: u128): u128 {
- const option = u128.checkedSub(x, y);
- if (option.isNone()) {
- throw new Error('checked sub overflow');
- }
- return option.unwrap();
- }
-
- export function checkedMultiply(x: u128, y: u128): Option {
- const result = x * y;
- if (result > u128.MAX) {
- return None;
- }
-
- return Some(u128(result));
- }
-
- export function saturatingAdd(x: u128, y: u128): u128 {
- const result = x + y;
- return result > u128.MAX ? u128.MAX : u128(result);
- }
-
- export function saturatingMultiply(x: u128, y: u128): u128 {
- const result = x * y;
- return result > u128.MAX ? u128.MAX : u128(result);
- }
-
- export function saturatingSub(x: u128, y: u128): u128 {
- return u128(x < y ? 0 : x - y);
- }
-
- export function decodeVarInt(seekArray: SeekArray): Option {
- try {
- return Some(tryDecodeVarInt(seekArray));
- } catch (e) {
- return None;
- }
- }
-
- export function tryDecodeVarInt(seekArray: SeekArray): u128 {
- let result: u128 = u128(0);
- for (let i = 0; i <= 18; i++) {
- const byte = seekArray.readUInt8();
- if (byte === undefined) throw new Error('Unterminated or invalid data');
-
- // Ensure all operations are done in bigint domain.
- const byteBigint = BigInt(byte);
- const value = u128(byteBigint & 0x7Fn); // Ensure the 'value' is treated as u128.
-
- if (i === 18 && (value & 0x7Cn) !== 0n) throw new Error('Overflow');
-
- // Use bigint addition instead of bitwise OR to combine the results,
- // and ensure shifting is handled correctly within the bigint domain.
- result = u128(result + (value << (7n * BigInt(i))));
-
- if ((byte & 0x80) === 0) return result;
- }
- throw new Error('Overlong encoding');
- }
-
- export function encodeVarInt(value: u128): Uint8Array {
- const bytes = [];
- while (value >> 7n > 0n) {
- bytes.push(Number(value & 0x7Fn) | 0x80);
- value = u128(value >> 7n); // Explicitly cast the shifted value back to u128
- }
- bytes.push(Number(value & 0x7Fn));
- return new Uint8Array(bytes);
- }
-
- export function tryIntoU64(n: u128): Option {
- return n > u64.MAX ? None : Some(u64(n));
- }
-
- export function tryIntoU32(n: u128): Option {
- return n > u32.MAX ? None : Some(u32(n));
- }
-
- export function tryIntoU8(n: u128): Option {
- return n > u8.MAX ? None : Some(u8(n));
- }
-}
-
-export function* getAllU128(data: Uint8Array): Generator {
- const seekArray = new SeekArray(data);
- while (!seekArray.isFinished()) {
- const nextValue = u128.decodeVarInt(seekArray);
- if (nextValue.isNone()) {
- return;
- }
- yield nextValue.unwrap();
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/integer/u32.ts b/frontend/src/app/shared/ord/rune/integer/u32.ts
deleted file mode 100644
index 90e517bb8..000000000
--- a/frontend/src/app/shared/ord/rune/integer/u32.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { None, Option, Some } from '../monads';
-
-/**
- * A little utility type used for nominal typing.
- *
- * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
- */
-type BigTypedNumber = bigint & {
- /**
- * # !!! DO NOT USE THIS PROPERTY IN YOUR CODE !!!
- * ## This is just used to make each `BigTypedNumber` alias unique for Typescript and doesn't actually exist.
- * @ignore
- * @private
- * @readonly
- * @type {undefined}
- */
- readonly __kind__: T;
-};
-
-export type u32 = BigTypedNumber<'u32'>;
-
-export const U32_MAX_BIGINT = 0xffff_ffffn;
-
-export function u32(num: number | bigint): u32 {
- if (typeof num == 'bigint') {
- if (num < 0n || num > U32_MAX_BIGINT) {
- throw new Error('num is out of range');
- }
- } else {
- if (!Number.isSafeInteger(num) || num < 0) {
- throw new Error('num is not a valid integer');
- }
- }
-
- return BigInt(num) as u32;
-}
-
-export namespace u32 {
- export const MAX = u32(U32_MAX_BIGINT);
-
- export function checkedAdd(x: u32, y: u32): Option {
- const result = x + y;
- if (result > u32.MAX) {
- return None;
- }
-
- return Some(u32(result));
- }
-
- export function checkedSub(x: u32, y: u32): Option {
- const result = x - y;
- if (result < 0n) {
- return None;
- }
-
- return Some(u32(result));
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/integer/u64.ts b/frontend/src/app/shared/ord/rune/integer/u64.ts
deleted file mode 100644
index 8010dd99c..000000000
--- a/frontend/src/app/shared/ord/rune/integer/u64.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { None, Option, Some } from '../monads';
-
-/**
- * A little utility type used for nominal typing.
- *
- * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
- */
-type BigTypedNumber = bigint & {
- /**
- * # !!! DO NOT USE THIS PROPERTY IN YOUR CODE !!!
- * ## This is just used to make each `BigTypedNumber` alias unique for Typescript and doesn't actually exist.
- * @ignore
- * @private
- * @readonly
- * @type {undefined}
- */
- readonly __kind__: T;
-};
-
-export type u64 = BigTypedNumber<'u64'>;
-
-export const U64_MAX_BIGINT = 0xffff_ffff_ffff_ffffn;
-
-export function u64(num: number | bigint): u64 {
- if (typeof num == 'bigint') {
- if (num < 0n || num > U64_MAX_BIGINT) {
- throw new Error('num is out of range');
- }
- } else {
- if (!Number.isSafeInteger(num) || num < 0) {
- throw new Error('num is not a valid integer');
- }
- }
-
- return BigInt(num) as u64;
-}
-
-export namespace u64 {
- export const MAX = u64(U64_MAX_BIGINT);
-
- export function checkedAdd(x: u64, y: u64): Option {
- const result = x + y;
- if (result > u64.MAX) {
- return None;
- }
-
- return Some(u64(result));
- }
-
- export function checkedSub(x: u64, y: u64): Option {
- const result = x - y;
- if (result < 0n) {
- return None;
- }
-
- return Some(u64(result));
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/integer/u8.ts b/frontend/src/app/shared/ord/rune/integer/u8.ts
deleted file mode 100644
index 5676421b0..000000000
--- a/frontend/src/app/shared/ord/rune/integer/u8.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { None, Option, Some } from '../monads';
-
-/**
- * A little utility type used for nominal typing.
- *
- * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
- */
-type BigTypedNumber = bigint & {
- /**
- * # !!! DO NOT USE THIS PROPERTY IN YOUR CODE !!!
- * ## This is just used to make each `BigTypedNumber` alias unique for Typescript and doesn't actually exist.
- * @ignore
- * @private
- * @readonly
- * @type {undefined}
- */
- readonly __kind__: T;
-};
-
-export type u8 = BigTypedNumber<'u8'>;
-
-export const U8_MAX_BIGINT = 0xffn;
-
-export function u8(num: number | bigint): u8 {
- if (typeof num == 'bigint') {
- if (num < 0n || num > U8_MAX_BIGINT) {
- throw new Error('num is out of range');
- }
- } else {
- if (!Number.isSafeInteger(num) || num < 0) {
- throw new Error('num is not a valid integer');
- }
- }
-
- return BigInt(num) as u8;
-}
-
-export namespace u8 {
- export const MAX = u8(U8_MAX_BIGINT);
-
- export function checkedAdd(x: u8, y: u8): Option {
- const result = x + y;
- if (result > u8.MAX) {
- return None;
- }
-
- return Some(u8(result));
- }
-
- export function checkedSub(x: u8, y: u8): Option {
- const result = x - y;
- if (result < 0n) {
- return None;
- }
-
- return Some(u8(result));
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/message.ts b/frontend/src/app/shared/ord/rune/message.ts
deleted file mode 100644
index cad1a8ced..000000000
--- a/frontend/src/app/shared/ord/rune/message.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Edict } from './edict';
-import { Flaw } from './flaw';
-import { u128, u64, u32 } from './integer';
-import { RuneId } from './runeid';
-import { Tag } from './tag';
-
-export class Message {
- constructor(
- readonly flaws: Flaw[],
- readonly edicts: Edict[],
- readonly fields: Map
- ) {}
-
- static fromIntegers(numOutputs: number, payload: u128[]): Message {
- const edicts: Edict[] = [];
- const fields = new Map();
- const flaws: Flaw[] = [];
-
- for (const i of [...Array(Math.ceil(payload.length / 2)).keys()].map((n) => n * 2)) {
- const tag = payload[i];
-
- if (u128(Tag.BODY) === tag) {
- let id = new RuneId(u64(0), u32(0));
- const chunkSize = 4;
-
- const body = payload.slice(i + 1);
- for (let j = 0; j < body.length; j += chunkSize) {
- const chunk = body.slice(j, j + chunkSize);
- if (chunk.length !== chunkSize) {
- flaws.push(Flaw.TRAILING_INTEGERS);
- break;
- }
-
- const optionNext = id.next(chunk[0], chunk[1]);
- if (optionNext.isNone()) {
- flaws.push(Flaw.EDICT_RUNE_ID);
- break;
- }
- const next = optionNext.unwrap();
-
- const optionEdict = Edict.fromIntegers(numOutputs, next, chunk[2], chunk[3]);
- if (optionEdict.isNone()) {
- flaws.push(Flaw.EDICT_OUTPUT);
- break;
- }
- const edict = optionEdict.unwrap();
-
- id = next;
- edicts.push(edict);
- }
- break;
- }
-
- const value = payload[i + 1];
- if (value === undefined) {
- flaws.push(Flaw.TRUNCATED_FIELD);
- break;
- }
-
- const values = fields.get(tag) ?? [];
- values.push(value);
- fields.set(tag, values);
- }
-
- return new Message(flaws, edicts, fields);
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/monads.ts b/frontend/src/app/shared/ord/rune/monads.ts
deleted file mode 100644
index 7822acca9..000000000
--- a/frontend/src/app/shared/ord/rune/monads.ts
+++ /dev/null
@@ -1,392 +0,0 @@
-// Copied with MIT License from link below:
-// https://github.com/thames-technology/monads/blob/de957d3d68449d659518d99be4ea74bbb70dfc8e/src/option/option.ts
-
-/**
- * Type representing any value except 'undefined'.
- * This is useful when working with strict null checks, ensuring that a value can be null but not undefined.
- */
-type NonUndefined = {} | null; // eslint-disable-line @typescript-eslint/ban-types
-
-/**
- * Enum-like object to represent the type of an Option (Some or None).
- */
-export const OptionType = {
- Some: Symbol(':some'),
- None: Symbol(':none'),
-};
-
-/**
- * Interface for handling match operations on an Option.
- * Allows executing different logic based on the Option being Some or None.
- */
-interface Match {
- some: (val: A) => B;
- none: (() => B) | B;
-}
-
-/**
- * The Option interface representing an optional value.
- * An Option is either Some, holding a value, or None, indicating the absence of a value.
- */
-export interface Option {
- /**
- * Represents the type of the Option: either Some or None. Useful for debugging and runtime checks.
- */
- type: symbol;
-
- /**
- * Determines if the Option is a Some.
- *
- * @returns true if the Option is Some, otherwise false.
- *
- * #### Example
- *
- * ```ts
- * console.log(Some(5).isSome()); // true
- * console.log(None.isSome()); // false
- * ```
- */
- isSome(): boolean;
-
- /**
- * Determines if the Option is None.
- *
- * @returns true if the Option is None, otherwise false.
- *
- * #### Example
- *
- * ```ts
- * console.log(Some(5).isNone()); // false
- * console.log(None.isNone()); // true
- * ```
- */
- isNone(): boolean;
-
- /**
- * Performs a match operation on the Option, allowing for branching logic based on its state.
- * This method takes an object with functions for each case (Some or None) and executes
- * the corresponding function based on the Option's state, returning the result.
- *
- * @param fn An object containing two properties: `some` and `none`, which are functions
- * to handle the Some and None cases, respectively.
- * @returns The result of applying the corresponding function based on the Option's state.
- *
- * #### Example
- *
- * ```ts
- * const optionSome = Some(5);
- * const matchResultSome = optionSome.match({
- * some: (value) => `The value is ${value}.`,
- * none: () => 'There is no value.',
- * });
- * console.log(matchResultSome); // Outputs: "The value is 5."
- *
- * const optionNone = None;
- * const matchResultNone = optionNone.match({
- * some: (value) => `The value is ${value}.`,
- * none: () => 'There is no value.',
- * });
- * console.log(matchResultNone); // Outputs: "There is no value."
- * ```
- */
- match(fn: Match): U;
-
- /**
- * Applies a function to the contained value (if any), or returns a default if None.
- *
- * @param fn A function that takes a value of type T and returns a value of type U.
- * @returns An Option containing the function's return value if the original Option is Some, otherwise None.
- *
- * #### Examples
- *
- * ```ts
- * const length = Some("hello").map(s => s.length); // Some(5)
- * const noneLength = None.map(s => s.length); // None
- * ```
- */
- map(fn: (val: T) => U): Option;
-
- inspect(fn: (val: T) => void): Option;
-
- /**
- * Transforms the Option into another by applying a function to the contained value,
- * chaining multiple potentially failing operations.
- *
- * @param fn A function that takes a value of type T and returns an Option of type U.
- * @returns The Option returned by the function if the original Option is Some, otherwise None.
- *
- * #### Examples
- *
- * ```ts
- * const parse = (s: string) => {
- * const parsed = parseInt(s);
- * return isNaN(parsed) ? None : Some(parsed);
- * };
- * const result = Some("123").andThen(parse); // Some(123)
- * const noResult = Some("abc").andThen(parse); // None
- * ```
- */
- andThen(fn: (val: T) => Option): Option;
-
- /**
- * Returns this Option if it is Some, otherwise returns the option provided as a parameter.
- *
- * @param optb The alternative Option to return if the original Option is None.
- * @returns The original Option if it is Some, otherwise `optb`.
- *
- * #### Examples
- *
- * ```ts
- * const defaultOption = Some("default");
- * const someOption = Some("some").or(defaultOption); // Some("some")
- * const noneOption = None.or(defaultOption); // Some("default")
- * ```
- */
- or(optb: Option): Option;
-
- orElse(optb: () => Option): Option;
-
- /**
- * Returns the option provided as a parameter if the original Option is Some, otherwise returns None.
- *
- * @param optb The Option to return if the original Option is Some.
- * @returns `optb` if the original Option is Some, otherwise None.
- *
- * #### Examples
- *
- * ```ts
- * const anotherOption = Some("another");
- * const someOption = Some("some").and(anotherOption); // Some("another")
- * const noneOption = None.and(anotherOption); // None
- * ```
- */
- and(optb: Option): Option;
-
- /**
- * Returns the contained value if Some, otherwise returns the provided default value.
- *
- * @param def The default value to return if the Option is None.
- * @returns The contained value if Some, otherwise `def`.
- *
- * #### Examples
- *
- * ```ts
- * const someValue = Some("value").unwrapOr("default"); // "value"
- * const noneValue = None.unwrapOr("default"); // "default"
- * ```
- */
- unwrapOr(def: T): T;
-
- /**
- * Unwraps an Option, yielding the contained value if Some, otherwise throws an error.
- *
- * @returns The contained value.
- * @throws Error if the Option is None.
- *
- * #### Examples
- *
- * ```ts
- * console.log(Some("value").unwrap()); // "value"
- * console.log(None.unwrap()); // throws Error
- * ```
- */
- unwrap(): T | never;
-}
-
-/**
- * Implementation of Option representing a value (Some).
- */
-interface SomeOption extends Option {
- unwrap(): T;
-}
-
-/**
- * Implementation of Option representing the absence of a value (None).
- */
-interface NoneOption extends Option {
- unwrap(): never;
-}
-
-/**
- * Represents a Some value of Option.
- */
-class SomeImpl implements SomeOption {
- constructor(private readonly val: T) {}
-
- get type() {
- return OptionType.Some;
- }
-
- isSome() {
- return true;
- }
-
- isNone() {
- return false;
- }
-
- match(fn: Match): B {
- return fn.some(this.val);
- }
-
- map(fn: (val: T) => U): Option {
- return Some(fn(this.val));
- }
-
- inspect(fn: (val: T) => void): Option {
- fn(this.val);
- return this;
- }
-
- andThen(fn: (val: T) => Option): Option {
- return fn(this.val);
- }
-
- or(_optb: Option): Option {
- return this;
- }
-
- orElse(optb: () => Option): Option {
- return this;
- }
-
- and(optb: Option): Option {
- return optb;
- }
-
- unwrapOr(_def: T): T {
- return this.val;
- }
-
- unwrap(): T {
- return this.val;
- }
-}
-
-/**
- * Represents a None value of Option.
- */
-class NoneImpl implements NoneOption {
- get type() {
- return OptionType.None;
- }
-
- isSome() {
- return false;
- }
-
- isNone() {
- return true;
- }
-
- match({ none }: Match): U {
- if (typeof none === 'function') {
- return (none as () => U)();
- }
-
- return none;
- }
-
- map(_fn: (val: T) => U): Option {
- return new NoneImpl();
- }
-
- inspect(fn: (val: T) => void): Option {
- return this;
- }
-
- andThen(_fn: (val: T) => Option): Option {
- return new NoneImpl();
- }
-
- or(optb: Option): Option {
- return optb;
- }
-
- orElse(optb: () => Option): Option {
- return optb();
- }
-
- and(_optb: Option): Option {
- return new NoneImpl();
- }
-
- unwrapOr(def: T): T {
- return def;
- }
-
- unwrap(): never {
- throw new ReferenceError('Trying to unwrap None.');
- }
-}
-
-/**
- * Creates a Some instance of Option containing the given value.
- * This function is used to represent the presence of a value in an operation that may not always produce a value.
- *
- * @param val The value to be wrapped in a Some Option.
- * @returns An Option instance representing the presence of a value.
- *
- * #### Example
- *
- * ```ts
- * const option = Some(42);
- * console.log(option.unwrap()); // Outputs: 42
- * ```
- */
-export function Some(val: T): Option {
- return new SomeImpl(val);
-}
-
-/**
- * The singleton instance representing None, an Option with no value.
- * This constant is used to represent the absence of a value in operations that may not always produce a value.
- *
- * #### Example
- *
- * ```ts
- * const option = None;
- * console.log(option.isNone()); // Outputs: true
- * ```
- */
-export const None: Option = new NoneImpl(); // eslint-disable-line @typescript-eslint/no-explicit-any
-
-/**
- * Type guard to check if an Option is a Some value.
- * This function is used to narrow down the type of an Option to SomeOption in TypeScript's type system.
- *
- * @param val The Option to be checked.
- * @returns true if the provided Option is a SomeOption, false otherwise.
- *
- * #### Example
- *
- * ```ts
- * const option = Some('Success');
- * if (isSome(option)) {
- * console.log('Option has a value:', option.unwrap());
- * }
- * ```
- */
-export function isSome(val: Option): val is SomeOption {
- return val.isSome();
-}
-
-/**
- * Type guard to check if an Option is a None value.
- * This function is used to narrow down the type of an Option to NoneOption in TypeScript's type system.
- *
- * @param val The Option to be checked.
- * @returns true if the provided Option is a NoneOption, false otherwise.
- *
- * #### Example
- *
- * ```ts
- * const option = None;
- * if (isNone(option)) {
- * console.log('Option does not have a value.');
- * }
- * ```
- */
-export function isNone(val: Option): val is NoneOption {
- return val.isNone();
-}
diff --git a/frontend/src/app/shared/ord/rune/rune.ts b/frontend/src/app/shared/ord/rune/rune.ts
deleted file mode 100644
index c0dd96e1b..000000000
--- a/frontend/src/app/shared/ord/rune/rune.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { u128 } from './integer';
-
-export class Rune {
-
- constructor(readonly value: u128) {}
-
- toString() {
- let n = this.value;
-
- if (n === u128.MAX) {
- return 'BCGDENLQRQWDSLRUGSNLBTMFIJAV';
- }
-
- n = u128(n + 1n);
- let symbol = '';
- while (n > 0) {
- symbol = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[Number((n - 1n) % 26n)] + symbol;
- n = u128((n - 1n) / 26n);
- }
-
- return symbol;
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/runeid.ts b/frontend/src/app/shared/ord/rune/runeid.ts
deleted file mode 100644
index ca0e938b7..000000000
--- a/frontend/src/app/shared/ord/rune/runeid.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import { None, Option, Some } from './monads';
-import { u64, u32, u128 } from './integer';
-
-export class RuneId {
- constructor(readonly block: u64, readonly tx: u32) {}
-
- static new(block: u64, tx: u32): Option {
- const id = new RuneId(block, tx);
-
- if (id.block === 0n && id.tx > 0) {
- return None;
- }
-
- return Some(id);
- }
-
- static sort(runeIds: RuneId[]): RuneId[] {
- return [...runeIds].sort((x, y) => Number(x.block - y.block || x.tx - y.tx));
- }
-
- delta(next: RuneId): Option<[u128, u128]> {
- const optionBlock = u64.checkedSub(next.block, this.block);
- if (optionBlock.isNone()) {
- return None;
- }
- const block = optionBlock.unwrap();
-
- let tx: u32;
- if (block === 0n) {
- const optionTx = u32.checkedSub(next.tx, this.tx);
- if (optionTx.isNone()) {
- return None;
- }
- tx = optionTx.unwrap();
- } else {
- tx = next.tx;
- }
-
- return Some([u128(block), u128(tx)]);
- }
-
- next(block: u128, tx: u128): Option {
- const optionBlock = u128.tryIntoU64(block);
- const optionTx = u128.tryIntoU32(tx);
-
- if (optionBlock.isNone() || optionTx.isNone()) {
- return None;
- }
-
- const blockU64 = optionBlock.unwrap();
- const txU32 = optionTx.unwrap();
-
- const nextBlock = u64.checkedAdd(this.block, blockU64);
- if (nextBlock.isNone()) {
- return None;
- }
-
- let nextTx: u32;
- if (blockU64 === 0n) {
- const optionAdd = u32.checkedAdd(this.tx, txU32);
- if (optionAdd.isNone()) {
- return None;
- }
-
- nextTx = optionAdd.unwrap();
- } else {
- nextTx = txU32;
- }
-
- return RuneId.new(nextBlock.unwrap(), nextTx);
- }
-
- toString() {
- return `${this.block}:${this.tx}`;
- }
-
- static fromString(s: string) {
- const parts = s.split(':');
- if (parts.length !== 2) {
- throw new Error(`invalid rune ID: ${s}`);
- }
-
- const [block, tx] = parts;
- if (!/^\d+$/.test(block) || !/^\d+$/.test(tx)) {
- throw new Error(`invalid rune ID: ${s}`);
- }
- return new RuneId(u64(BigInt(block)), u32(BigInt(tx)));
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/runestone.ts b/frontend/src/app/shared/ord/rune/runestone.ts
deleted file mode 100644
index c71cdcd90..000000000
--- a/frontend/src/app/shared/ord/rune/runestone.ts
+++ /dev/null
@@ -1,258 +0,0 @@
-import { concatUint8Arrays, hexToBytes } from '../inscription.utils';
-import { Artifact } from './artifact';
-import { Cenotaph } from './cenotaph';
-import { MAGIC_NUMBER, MAX_DIVISIBILITY, OP_RETURN } from './constants';
-import { Edict } from './edict';
-import { Etching } from './etching';
-import { Flag } from './flag';
-import { Flaw } from './flaw';
-import { u128, u32, u64, u8 } from './integer';
-import { Message } from './message';
-import { None, Option, Some } from './monads';
-import { Rune } from './rune';
-import { RuneId } from './runeid';
-import { script } from './script';
-import { SeekArray } from './seekarray';
-import { Tag } from './tag';
-
-export const MAX_SPACERS = 0b00000111_11111111_11111111_11111111;
-
-export const UNCOMMON_GOODS = new Etching(
- Some(u8(0)),
- Some(new Rune(u128(2055900680524219742n))),
- Some(u32(128)),
- Some('⧉'),
- Some({
- amount: Some(u128(1)),
- cap: Some(u128(340282366920938463463374607431768211455n)),
- height: [Some(u64(840000)), Some(u64(1050000))],
- offset: [Some(u64(0)), Some(u64(0))],
- }),
- Some(u128(0)),
- false
-);
-
-// New: Esplora format instead of Bitcoin RPC format
-export type RunestoneTx = {
- vout: {
- scriptpubkey: string
- }[];
-};
-
-type Payload = Uint8Array | Flaw;
-
-export class Runestone {
- readonly type = 'runestone';
-
- constructor(
- readonly mint: Option,
- readonly pointer: Option,
- readonly edicts: Edict[],
- readonly etching: Option
- ) {}
-
- static decipher(transaction: RunestoneTx): Option {
- const optionPayload = Runestone.payload(transaction);
- if (optionPayload.isNone()) {
- return None;
- }
- const payload = optionPayload.unwrap();
- if (!(payload instanceof Uint8Array)) {
- return Some(new Cenotaph([payload]));
- }
-
- const optionIntegers = Runestone.integers(payload);
- if (optionIntegers.isNone()) {
- return Some(new Cenotaph([Flaw.VARINT]));
- }
-
- const { flaws, edicts, fields } = Message.fromIntegers(
- transaction.vout.length,
- optionIntegers.unwrap()
- );
-
- let flags = Tag.take(Tag.FLAGS, fields, 1, ([value]) => Some(value)).unwrapOr(u128(0));
-
- const etchingResult = Flag.take(flags, Flag.ETCHING);
- const etchingFlag = etchingResult.set;
- flags = etchingResult.flags;
-
- const etching: Option = etchingFlag
- ? (() => {
- const divisibility = Tag.take(
- Tag.DIVISIBILITY,
- fields,
- 1,
- ([value]): Option =>
- u128
- .tryIntoU8(value)
- .andThen((value) => (value <= MAX_DIVISIBILITY ? Some(value) : None))
- );
-
- const rune = Tag.take(Tag.RUNE, fields, 1, ([value]) => Some(new Rune(value)));
-
- const spacers = Tag.take(
- Tag.SPACERS,
- fields,
- 1,
- ([value]): Option =>
- u128.tryIntoU32(value).andThen((value) => (value <= MAX_SPACERS ? Some(value) : None))
- );
-
- const symbol = Tag.take(Tag.SYMBOL, fields, 1, ([value]) =>
- u128.tryIntoU32(value).andThen((value) => {
- try {
- return Some(String.fromCodePoint(Number(value)));
- } catch (e) {
- return None;
- }
- })
- );
-
- const termsResult = Flag.take(flags, Flag.TERMS);
- const termsFlag = termsResult.set;
- flags = termsResult.flags;
-
- const terms = termsFlag
- ? (() => {
- const amount = Tag.take(Tag.AMOUNT, fields, 1, ([value]) => Some(value));
-
- const cap = Tag.take(Tag.CAP, fields, 1, ([value]) => Some(value));
-
- const offset = [
- Tag.take(Tag.OFFSET_START, fields, 1, ([value]) => u128.tryIntoU64(value)),
- Tag.take(Tag.OFFSET_END, fields, 1, ([value]) => u128.tryIntoU64(value)),
- ] as const;
-
- const height = [
- Tag.take(Tag.HEIGHT_START, fields, 1, ([value]) => u128.tryIntoU64(value)),
- Tag.take(Tag.HEIGHT_END, fields, 1, ([value]) => u128.tryIntoU64(value)),
- ] as const;
-
- return Some({ amount, cap, offset, height });
- })()
- : None;
-
- const premine = Tag.take(Tag.PREMINE, fields, 1, ([value]) => Some(value));
-
- const turboResult = Flag.take(flags, Flag.TURBO);
- const turbo = etchingResult.set;
- flags = turboResult.flags;
-
- return Some(new Etching(divisibility, rune, spacers, symbol, terms, premine, turbo));
- })()
- : None;
-
- const mint = Tag.take(Tag.MINT, fields, 2, ([block, tx]): Option => {
- const optionBlockU64 = u128.tryIntoU64(block);
- const optionTxU32 = u128.tryIntoU32(tx);
-
- if (optionBlockU64.isNone() || optionTxU32.isNone()) {
- return None;
- }
-
- return RuneId.new(optionBlockU64.unwrap(), optionTxU32.unwrap());
- });
-
- const pointer = Tag.take(
- Tag.POINTER,
- fields,
- 1,
- ([value]): Option =>
- u128
- .tryIntoU32(value)
- .andThen((value) => (value < transaction.vout.length ? Some(value) : None))
- );
-
- if (etching.map((etching) => etching.supply.isNone()).unwrapOr(false)) {
- flaws.push(Flaw.SUPPLY_OVERFLOW);
- }
-
- if (flags !== 0n) {
- flaws.push(Flaw.UNRECOGNIZED_FLAG);
- }
-
- if ([...fields.keys()].find((tag) => tag % 2n === 0n) !== undefined) {
- flaws.push(Flaw.UNRECOGNIZED_EVEN_TAG);
- }
-
- if (flaws.length !== 0) {
- return Some(
- new Cenotaph(
- flaws,
- etching.andThen((etching) => etching.rune),
- mint
- )
- );
- }
-
- return Some(new Runestone(mint, pointer, edicts, etching));
- }
-
- static payload(transaction: RunestoneTx): Option {
- // search transaction outputs for payload
- for (const output of transaction.vout) {
- const instructions = script.decompile(hexToBytes(output.scriptpubkey));
- if (instructions === null) {
- throw new Error('unable to decompile');
- }
-
- // payload starts with OP_RETURN
- let nextInstructionResult = instructions.next();
- if (nextInstructionResult.done || nextInstructionResult.value !== OP_RETURN) {
- continue;
- }
-
- // followed by the protocol identifier
- nextInstructionResult = instructions.next();
- if (
- nextInstructionResult.done ||
- nextInstructionResult.value instanceof Uint8Array ||
- nextInstructionResult.value !== MAGIC_NUMBER
- ) {
- continue;
- }
-
- // construct the payload by concatinating remaining data pushes
- let payloads: Uint8Array[] = [];
-
- do {
- nextInstructionResult = instructions.next();
-
- if (nextInstructionResult.done) {
- const decodedSuccessfully = nextInstructionResult.value;
- if (!decodedSuccessfully) {
- return Some(Flaw.INVALID_SCRIPT);
- }
- break;
- }
-
- const instruction = nextInstructionResult.value;
- if (instruction instanceof Uint8Array) {
- payloads.push(instruction);
- } else {
- return Some(Flaw.OPCODE);
- }
- } while (true);
-
- return Some(concatUint8Arrays(payloads));
- }
-
- return None;
- }
-
- static integers(payload: Uint8Array): Option {
- const integers: u128[] = [];
-
- const seekArray = new SeekArray(payload);
- while (!seekArray.isFinished()) {
- const optionInt = u128.decodeVarInt(seekArray);
- if (optionInt.isNone()) {
- return None;
- }
- integers.push(optionInt.unwrap());
- }
-
- return Some(integers);
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/script.ts b/frontend/src/app/shared/ord/rune/script.ts
deleted file mode 100644
index 67d579ab8..000000000
--- a/frontend/src/app/shared/ord/rune/script.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-namespace pushdata {
- /**
- * Calculates the encoding length of a number used for push data in Bitcoin transactions.
- * @param i The number to calculate the encoding length for.
- * @returns The encoding length of the number.
- */
- export function encodingLength(i: number): number {
- return i < OPS.OP_PUSHDATA1 ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5;
- }
-
- /**
- * Decodes a byte array and returns information about the opcode, number, and size.
- * @param array - The byte array to decode.
- * @param offset - The offset within the array to start decoding.
- * @returns An object containing the opcode, number, and size, or null if decoding fails.
- */
- export function decode(
- array: Uint8Array,
- offset: number
- ): {
- opcode: number;
- number: number;
- size: number;
- } | null {
- const dataView = new DataView(array.buffer, array.byteOffset, array.byteLength);
- const opcode = dataView.getUint8(offset);
- let num: number;
- let size: number;
-
- // ~6 bit
- if (opcode < OPS.OP_PUSHDATA1) {
- num = opcode;
- size = 1;
-
- // 8 bit
- } else if (opcode === OPS.OP_PUSHDATA1) {
- if (offset + 2 > array.length) return null;
- num = dataView.getUint8(offset + 1);
- size = 2;
-
- // 16 bit
- } else if (opcode === OPS.OP_PUSHDATA2) {
- if (offset + 3 > array.length) return null;
- num = dataView.getUint16(offset + 1, true); // true for little-endian
- size = 3;
-
- // 32 bit
- } else {
- if (offset + 5 > array.length) return null;
- if (opcode !== OPS.OP_PUSHDATA4) throw new Error('Unexpected opcode');
-
- num = dataView.getUint32(offset + 1, true); // true for little-endian
- size = 5;
- }
-
- return {
- opcode,
- number: num,
- size,
- };
- }
-}
-
-const OPS = {
- OP_FALSE: 0,
- OP_0: 0,
- OP_PUSHDATA1: 76,
- OP_PUSHDATA2: 77,
- OP_PUSHDATA4: 78,
- OP_1NEGATE: 79,
- OP_RESERVED: 80,
- OP_TRUE: 81,
- OP_1: 81,
- OP_2: 82,
- OP_3: 83,
- OP_4: 84,
- OP_5: 85,
- OP_6: 86,
- OP_7: 87,
- OP_8: 88,
- OP_9: 89,
- OP_10: 90,
- OP_11: 91,
- OP_12: 92,
- OP_13: 93,
- OP_14: 94,
- OP_15: 95,
- OP_16: 96,
-
- OP_NOP: 97,
- OP_VER: 98,
- OP_IF: 99,
- OP_NOTIF: 100,
- OP_VERIF: 101,
- OP_VERNOTIF: 102,
- OP_ELSE: 103,
- OP_ENDIF: 104,
- OP_VERIFY: 105,
- OP_RETURN: 106,
-
- OP_TOALTSTACK: 107,
- OP_FROMALTSTACK: 108,
- OP_2DROP: 109,
- OP_2DUP: 110,
- OP_3DUP: 111,
- OP_2OVER: 112,
- OP_2ROT: 113,
- OP_2SWAP: 114,
- OP_IFDUP: 115,
- OP_DEPTH: 116,
- OP_DROP: 117,
- OP_DUP: 118,
- OP_NIP: 119,
- OP_OVER: 120,
- OP_PICK: 121,
- OP_ROLL: 122,
- OP_ROT: 123,
- OP_SWAP: 124,
- OP_TUCK: 125,
-
- OP_CAT: 126,
- OP_SUBSTR: 127,
- OP_LEFT: 128,
- OP_RIGHT: 129,
- OP_SIZE: 130,
-
- OP_INVERT: 131,
- OP_AND: 132,
- OP_OR: 133,
- OP_XOR: 134,
- OP_EQUAL: 135,
- OP_EQUALVERIFY: 136,
- OP_RESERVED1: 137,
- OP_RESERVED2: 138,
-
- OP_1ADD: 139,
- OP_1SUB: 140,
- OP_2MUL: 141,
- OP_2DIV: 142,
- OP_NEGATE: 143,
- OP_ABS: 144,
- OP_NOT: 145,
- OP_0NOTEQUAL: 146,
- OP_ADD: 147,
- OP_SUB: 148,
- OP_MUL: 149,
- OP_DIV: 150,
- OP_MOD: 151,
- OP_LSHIFT: 152,
- OP_RSHIFT: 153,
-
- OP_BOOLAND: 154,
- OP_BOOLOR: 155,
- OP_NUMEQUAL: 156,
- OP_NUMEQUALVERIFY: 157,
- OP_NUMNOTEQUAL: 158,
- OP_LESSTHAN: 159,
- OP_GREATERTHAN: 160,
- OP_LESSTHANOREQUAL: 161,
- OP_GREATERTHANOREQUAL: 162,
- OP_MIN: 163,
- OP_MAX: 164,
-
- OP_WITHIN: 165,
-
- OP_RIPEMD160: 166,
- OP_SHA1: 167,
- OP_SHA256: 168,
- OP_HASH160: 169,
- OP_HASH256: 170,
- OP_CODESEPARATOR: 171,
- OP_CHECKSIG: 172,
- OP_CHECKSIGVERIFY: 173,
- OP_CHECKMULTISIG: 174,
- OP_CHECKMULTISIGVERIFY: 175,
-
- OP_NOP1: 176,
-
- OP_NOP2: 177,
- OP_CHECKLOCKTIMEVERIFY: 177,
-
- OP_NOP3: 178,
- OP_CHECKSEQUENCEVERIFY: 178,
-
- OP_NOP4: 179,
- OP_NOP5: 180,
- OP_NOP6: 181,
- OP_NOP7: 182,
- OP_NOP8: 183,
- OP_NOP9: 184,
- OP_NOP10: 185,
-
- OP_CHECKSIGADD: 186,
-
- OP_PUBKEYHASH: 253,
- OP_PUBKEY: 254,
- OP_INVALIDOPCODE: 255,
-} as const;
-
-export const opcodes = OPS;
-
-export namespace script {
- export type Instruction = number | Uint8Array;
-
- export function* decompile(array: Uint8Array): Generator {
- let i = 0;
-
- while (i < array.length) {
- const opcode = array[i];
-
- // data chunk
- if (opcode >= OPS.OP_0 && opcode <= OPS.OP_PUSHDATA4) {
- const d = pushdata.decode(array, i);
-
- // did reading a pushDataInt fail?
- if (d === null) return false;
- i += d.size;
-
- // attempt to read too much data?
- if (i + d.number > array.length) return false;
-
- const data = array.subarray(i, i + d.number);
- i += d.number;
-
- yield data;
-
- // opcode
- } else {
- yield opcode;
-
- i += 1;
- }
- }
-
- return true;
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/seekarray.ts b/frontend/src/app/shared/ord/rune/seekarray.ts
deleted file mode 100644
index 1f465cbd3..000000000
--- a/frontend/src/app/shared/ord/rune/seekarray.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * This class provides a way to read data sequentially from a Uint8Array with automatic cursor management.
- * It utilizes DataView for handling multi-byte data types.
- *
- * This replaces the SeekBuffer from the original runestone-lib!
- */
-export class SeekArray {
-
- public seekIndex: number = 0;
- private dataView: DataView;
-
- /**
- * Constructs a SeekArray instance.
- *
- * @param array - The Uint8Array from which data will be read.
- */
- constructor(private array: Uint8Array) {
- this.dataView = new DataView(array.buffer, array.byteOffset, array.byteLength);
- }
-
- /**
- * Reads an unsigned 8-bit integer from the current position and advances the seek index by 1 byte.
- *
- * @returns The read value or undefined if reading beyond the end of the array.
- */
- readUInt8(): number | undefined {
- if (this.isFinished()) {
- return undefined;
- }
- const value = this.dataView.getUint8(this.seekIndex);
- this.seekIndex += 1;
- return value;
- }
-
- /**
- * Checks if the seek index has reached or surpassed the length of the underlying array.
- *
- * @returns true if there are no more bytes to read, false otherwise.
- */
- isFinished(): boolean {
- return this.seekIndex >= this.array.length;
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/spacedrune.ts b/frontend/src/app/shared/ord/rune/spacedrune.ts
deleted file mode 100644
index b00b0da3a..000000000
--- a/frontend/src/app/shared/ord/rune/spacedrune.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Rune } from './rune';
-
-export class SpacedRune {
- constructor(readonly rune: Rune, readonly spacers: number) {}
-
- toString(): string {
- const rune = this.rune.toString();
- let i = 0;
- let result = '';
- for (const c of rune) {
- result += c;
-
- if (i < rune.length - 1 && (this.spacers & (1 << i)) !== 0) {
- result += '•';
- }
- i++;
- }
-
- return result;
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/tag.ts b/frontend/src/app/shared/ord/rune/tag.ts
deleted file mode 100644
index 8e39925d4..000000000
--- a/frontend/src/app/shared/ord/rune/tag.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { None, Option, Some } from './monads';
-import { u128 } from './integer';
-import { FixedArray } from './utils';
-
-export enum Tag {
- BODY = 0,
- FLAGS = 2,
- RUNE = 4,
-
- PREMINE = 6,
- CAP = 8,
- AMOUNT = 10,
- HEIGHT_START = 12,
- HEIGHT_END = 14,
- OFFSET_START = 16,
- OFFSET_END = 18,
- MINT = 20,
- POINTER = 22,
- CENOTAPH = 126,
-
- DIVISIBILITY = 1,
- SPACERS = 3,
- SYMBOL = 5,
- NOP = 127,
-}
-
-export namespace Tag {
- export function take(
- tag: Tag,
- fields: Map,
- n: N,
- withFn: (values: FixedArray) => Option
- ): Option {
- const field = fields.get(u128(tag));
- if (field === undefined) {
- return None;
- }
-
- const values: u128[] = [];
- for (const i of [...Array(n).keys()]) {
- if (field[i] === undefined) {
- return None;
- }
- values[i] = field[i];
- }
-
- const optionValue = withFn(values as FixedArray);
- if (optionValue.isNone()) {
- return None;
- }
-
- field.splice(0, n);
-
- if (field.length === 0) {
- fields.delete(u128(tag));
- }
-
- return Some(optionValue.unwrap());
- }
-}
diff --git a/frontend/src/app/shared/ord/rune/terms.ts b/frontend/src/app/shared/ord/rune/terms.ts
deleted file mode 100644
index 464c166e0..000000000
--- a/frontend/src/app/shared/ord/rune/terms.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Option } from './monads';
-import { u128, u64 } from './integer';
-
-export type Terms = {
- amount: Option;
- cap: Option;
- height: readonly [Option, Option];
- offset: readonly [Option, Option];
-};
diff --git a/frontend/src/app/shared/ord/rune/utils.ts b/frontend/src/app/shared/ord/rune/utils.ts
deleted file mode 100644
index a6fa8e0a1..000000000
--- a/frontend/src/app/shared/ord/rune/utils.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-type GrowToSize = A['length'] extends N
- ? A
- : GrowToSize;
-
-export type FixedArray = GrowToSize;
-