Add runestone protocol implementation
This commit is contained in:
		
							parent
							
								
									d31c2665ee
								
							
						
					
					
						commit
						4143a5f593
					
				
							
								
								
									
										4
									
								
								frontend/src/app/shared/ord/rune/artifact.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								frontend/src/app/shared/ord/rune/artifact.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					import { Cenotaph } from './cenotaph';
 | 
				
			||||||
 | 
					import { Runestone } from './runestone';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Artifact = Cenotaph | Runestone;
 | 
				
			||||||
							
								
								
									
										14
									
								
								frontend/src/app/shared/ord/rune/cenotaph.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								frontend/src/app/shared/ord/rune/cenotaph.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					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<Rune> = None,
 | 
				
			||||||
 | 
					    readonly mint: Option<RuneId> = None
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								frontend/src/app/shared/ord/rune/constants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/src/app/shared/ord/rune/constants.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
							
								
								
									
										34
									
								
								frontend/src/app/shared/ord/rune/edict.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								frontend/src/app/shared/ord/rune/edict.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					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<Edict> {
 | 
				
			||||||
 | 
					    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 });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								frontend/src/app/shared/ord/rune/etching.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								frontend/src/app/shared/ord/rune/etching.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					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<string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    readonly divisibility: Option<u8>,
 | 
				
			||||||
 | 
					    readonly rune: Option<Rune>,
 | 
				
			||||||
 | 
					    readonly spacers: Option<u32>,
 | 
				
			||||||
 | 
					    symbol: Option<string>,
 | 
				
			||||||
 | 
					    readonly terms: Option<Terms>,
 | 
				
			||||||
 | 
					    readonly premine: Option<u128>,
 | 
				
			||||||
 | 
					    readonly turbo: boolean
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.symbol = symbol.andThen((value) => {
 | 
				
			||||||
 | 
					      const codePoint = value.codePointAt(0);
 | 
				
			||||||
 | 
					      return codePoint !== undefined ? Some(String.fromCodePoint(codePoint)) : None;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get supply(): Option<u128> {
 | 
				
			||||||
 | 
					    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));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								frontend/src/app/shared/ord/rune/flag.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								frontend/src/app/shared/ord/rune/flag.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					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 };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								frontend/src/app/shared/ord/rune/flaw.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								frontend/src/app/shared/ord/rune/flaw.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					export enum Flaw {
 | 
				
			||||||
 | 
					  EDICT_OUTPUT,
 | 
				
			||||||
 | 
					  EDICT_RUNE_ID,
 | 
				
			||||||
 | 
					  INVALID_SCRIPT,
 | 
				
			||||||
 | 
					  OPCODE,
 | 
				
			||||||
 | 
					  SUPPLY_OVERFLOW,
 | 
				
			||||||
 | 
					  TRAILING_INTEGERS,
 | 
				
			||||||
 | 
					  TRUNCATED_FIELD,
 | 
				
			||||||
 | 
					  UNRECOGNIZED_EVEN_TAG,
 | 
				
			||||||
 | 
					  UNRECOGNIZED_FLAG,
 | 
				
			||||||
 | 
					  VARINT,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								frontend/src/app/shared/ord/rune/integer/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								frontend/src/app/shared/ord/rune/integer/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					export { u8 } from './u8';
 | 
				
			||||||
 | 
					export { u32 } from './u32';
 | 
				
			||||||
 | 
					export { u64 } from './u64';
 | 
				
			||||||
 | 
					export { u128 } from './u128';
 | 
				
			||||||
							
								
								
									
										176
									
								
								frontend/src/app/shared/ord/rune/integer/u128.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								frontend/src/app/shared/ord/rune/integer/u128.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,176 @@
 | 
				
			|||||||
 | 
					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<T> = 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<u128> {
 | 
				
			||||||
 | 
					    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<u128> {
 | 
				
			||||||
 | 
					    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<u128> {
 | 
				
			||||||
 | 
					    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<u128> {
 | 
				
			||||||
 | 
					    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<u64> {
 | 
				
			||||||
 | 
					    return n > u64.MAX ? None : Some(u64(n));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export function tryIntoU32(n: u128): Option<u32> {
 | 
				
			||||||
 | 
					    return n > u32.MAX ? None : Some(u32(n));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export function tryIntoU8(n: u128): Option<u8> {
 | 
				
			||||||
 | 
					    return n > u8.MAX ? None : Some(u8(n));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function* getAllU128(data: Uint8Array): Generator<u128> {
 | 
				
			||||||
 | 
					  const seekArray = new SeekArray(data);
 | 
				
			||||||
 | 
					  while (!seekArray.isFinished()) {
 | 
				
			||||||
 | 
					    const nextValue = u128.decodeVarInt(seekArray);
 | 
				
			||||||
 | 
					    if (nextValue.isNone()) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    yield nextValue.unwrap();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								frontend/src/app/shared/ord/rune/integer/u32.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								frontend/src/app/shared/ord/rune/integer/u32.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					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<T> = 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<u32> {
 | 
				
			||||||
 | 
					    const result = x + y;
 | 
				
			||||||
 | 
					    if (result > u32.MAX) {
 | 
				
			||||||
 | 
					      return None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Some(u32(result));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export function checkedSub(x: u32, y: u32): Option<u32> {
 | 
				
			||||||
 | 
					    const result = x - y;
 | 
				
			||||||
 | 
					    if (result < 0n) {
 | 
				
			||||||
 | 
					      return None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Some(u32(result));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								frontend/src/app/shared/ord/rune/integer/u64.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								frontend/src/app/shared/ord/rune/integer/u64.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					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<T> = 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<u64> {
 | 
				
			||||||
 | 
					    const result = x + y;
 | 
				
			||||||
 | 
					    if (result > u64.MAX) {
 | 
				
			||||||
 | 
					      return None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Some(u64(result));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export function checkedSub(x: u64, y: u64): Option<u64> {
 | 
				
			||||||
 | 
					    const result = x - y;
 | 
				
			||||||
 | 
					    if (result < 0n) {
 | 
				
			||||||
 | 
					      return None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Some(u64(result));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								frontend/src/app/shared/ord/rune/integer/u8.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								frontend/src/app/shared/ord/rune/integer/u8.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					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<T> = 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<u8> {
 | 
				
			||||||
 | 
					    const result = x + y;
 | 
				
			||||||
 | 
					    if (result > u8.MAX) {
 | 
				
			||||||
 | 
					      return None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Some(u8(result));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export function checkedSub(x: u8, y: u8): Option<u8> {
 | 
				
			||||||
 | 
					    const result = x - y;
 | 
				
			||||||
 | 
					    if (result < 0n) {
 | 
				
			||||||
 | 
					      return None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Some(u8(result));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										67
									
								
								frontend/src/app/shared/ord/rune/message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								frontend/src/app/shared/ord/rune/message.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					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<u128, u128[]>
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static fromIntegers(numOutputs: number, payload: u128[]): Message {
 | 
				
			||||||
 | 
					    const edicts: Edict[] = [];
 | 
				
			||||||
 | 
					    const fields = new Map<u128, u128[]>();
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										392
									
								
								frontend/src/app/shared/ord/rune/monads.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								frontend/src/app/shared/ord/rune/monads.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,392 @@
 | 
				
			|||||||
 | 
					// 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<A, B> {
 | 
				
			||||||
 | 
					  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<T extends NonUndefined> {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 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<U extends NonUndefined>(fn: Match<T, U>): 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<U extends NonUndefined>(fn: (val: T) => U): Option<U>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  inspect(fn: (val: T) => void): Option<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 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<U extends NonUndefined>(fn: (val: T) => Option<U>): Option<U>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 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<T>): Option<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  orElse(optb: () => Option<T>): Option<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 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<U extends NonUndefined>(optb: Option<U>): Option<U>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 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<T extends NonUndefined> extends Option<T> {
 | 
				
			||||||
 | 
					  unwrap(): T;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Implementation of Option representing the absence of a value (None).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					interface NoneOption<T extends NonUndefined> extends Option<T> {
 | 
				
			||||||
 | 
					  unwrap(): never;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Represents a Some value of Option.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class SomeImpl<T extends NonUndefined> implements SomeOption<T> {
 | 
				
			||||||
 | 
					  constructor(private readonly val: T) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get type() {
 | 
				
			||||||
 | 
					    return OptionType.Some;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isSome() {
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isNone() {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  match<B>(fn: Match<T, B>): B {
 | 
				
			||||||
 | 
					    return fn.some(this.val);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  map<U extends NonUndefined>(fn: (val: T) => U): Option<U> {
 | 
				
			||||||
 | 
					    return Some(fn(this.val));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  inspect(fn: (val: T) => void): Option<T> {
 | 
				
			||||||
 | 
					    fn(this.val);
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  andThen<U extends NonUndefined>(fn: (val: T) => Option<U>): Option<U> {
 | 
				
			||||||
 | 
					    return fn(this.val);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  or<U extends NonUndefined>(_optb: Option<U>): Option<T> {
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  orElse(optb: () => Option<T>): Option<T> {
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  and<U extends NonUndefined>(optb: Option<U>): Option<U> {
 | 
				
			||||||
 | 
					    return optb;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unwrapOr(_def: T): T {
 | 
				
			||||||
 | 
					    return this.val;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  unwrap(): T {
 | 
				
			||||||
 | 
					    return this.val;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Represents a None value of Option.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class NoneImpl<T extends NonUndefined> implements NoneOption<T> {
 | 
				
			||||||
 | 
					  get type() {
 | 
				
			||||||
 | 
					    return OptionType.None;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isSome() {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isNone() {
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  match<U>({ none }: Match<T, U>): U {
 | 
				
			||||||
 | 
					    if (typeof none === 'function') {
 | 
				
			||||||
 | 
					      return (none as () => U)();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  map<U extends NonUndefined>(_fn: (val: T) => U): Option<U> {
 | 
				
			||||||
 | 
					    return new NoneImpl<U>();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  inspect(fn: (val: T) => void): Option<T> {
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  andThen<U extends NonUndefined>(_fn: (val: T) => Option<U>): Option<U> {
 | 
				
			||||||
 | 
					    return new NoneImpl<U>();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  or<U extends NonUndefined>(optb: Option<U>): Option<U> {
 | 
				
			||||||
 | 
					    return optb;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  orElse(optb: () => Option<T>): Option<T> {
 | 
				
			||||||
 | 
					    return optb();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  and<U extends NonUndefined>(_optb: Option<U>): Option<U> {
 | 
				
			||||||
 | 
					    return new NoneImpl<U>();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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<T extends NonUndefined>(val: T): Option<T> {
 | 
				
			||||||
 | 
					  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<any> = 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<T extends NonUndefined>(val: Option<T>): val is SomeOption<T> {
 | 
				
			||||||
 | 
					  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<T extends NonUndefined>(val: Option<T>): val is NoneOption<T> {
 | 
				
			||||||
 | 
					  return val.isNone();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								frontend/src/app/shared/ord/rune/rune.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/src/app/shared/ord/rune/rune.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										89
									
								
								frontend/src/app/shared/ord/rune/runeid.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								frontend/src/app/shared/ord/rune/runeid.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					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<RuneId> {
 | 
				
			||||||
 | 
					    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<RuneId> {
 | 
				
			||||||
 | 
					    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)));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										258
									
								
								frontend/src/app/shared/ord/rune/runestone.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								frontend/src/app/shared/ord/rune/runestone.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,258 @@
 | 
				
			|||||||
 | 
					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<RuneId>,
 | 
				
			||||||
 | 
					    readonly pointer: Option<u32>,
 | 
				
			||||||
 | 
					    readonly edicts: Edict[],
 | 
				
			||||||
 | 
					    readonly etching: Option<Etching>
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static decipher(transaction: RunestoneTx): Option<Artifact> {
 | 
				
			||||||
 | 
					    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<Etching> = etchingFlag
 | 
				
			||||||
 | 
					      ? (() => {
 | 
				
			||||||
 | 
					          const divisibility = Tag.take(
 | 
				
			||||||
 | 
					            Tag.DIVISIBILITY,
 | 
				
			||||||
 | 
					            fields,
 | 
				
			||||||
 | 
					            1,
 | 
				
			||||||
 | 
					            ([value]): Option<u8> =>
 | 
				
			||||||
 | 
					              u128
 | 
				
			||||||
 | 
					                .tryIntoU8(value)
 | 
				
			||||||
 | 
					                .andThen<u8>((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<u32> =>
 | 
				
			||||||
 | 
					              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<RuneId> => {
 | 
				
			||||||
 | 
					      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<u32> =>
 | 
				
			||||||
 | 
					        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<Payload> {
 | 
				
			||||||
 | 
					    // 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<u128[]> {
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										237
									
								
								frontend/src/app/shared/ord/rune/script.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								frontend/src/app/shared/ord/rune/script.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,237 @@
 | 
				
			|||||||
 | 
					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<Instruction, boolean> {
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										43
									
								
								frontend/src/app/shared/ord/rune/seekarray.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								frontend/src/app/shared/ord/rune/seekarray.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								frontend/src/app/shared/ord/rune/spacedrune.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/src/app/shared/ord/rune/spacedrune.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								frontend/src/app/shared/ord/rune/tag.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								frontend/src/app/shared/ord/rune/tag.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					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<N extends number, T extends {}>(
 | 
				
			||||||
 | 
					    tag: Tag,
 | 
				
			||||||
 | 
					    fields: Map<u128, u128[]>,
 | 
				
			||||||
 | 
					    n: N,
 | 
				
			||||||
 | 
					    withFn: (values: FixedArray<u128, N>) => Option<T>
 | 
				
			||||||
 | 
					  ): Option<T> {
 | 
				
			||||||
 | 
					    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<u128, N>);
 | 
				
			||||||
 | 
					    if (optionValue.isNone()) {
 | 
				
			||||||
 | 
					      return None;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    field.splice(0, n);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (field.length === 0) {
 | 
				
			||||||
 | 
					      fields.delete(u128(tag));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Some(optionValue.unwrap());
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								frontend/src/app/shared/ord/rune/terms.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								frontend/src/app/shared/ord/rune/terms.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { Option } from './monads';
 | 
				
			||||||
 | 
					import { u128, u64 } from './integer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Terms = {
 | 
				
			||||||
 | 
					  amount: Option<u128>;
 | 
				
			||||||
 | 
					  cap: Option<u128>;
 | 
				
			||||||
 | 
					  height: readonly [Option<u64>, Option<u64>];
 | 
				
			||||||
 | 
					  offset: readonly [Option<u64>, Option<u64>];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										6
									
								
								frontend/src/app/shared/ord/rune/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/src/app/shared/ord/rune/utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					type GrowToSize<T, N extends number, A extends T[]> = A['length'] extends N
 | 
				
			||||||
 | 
					  ? A
 | 
				
			||||||
 | 
					  : GrowToSize<T, N, [...A, T]>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type FixedArray<T, N extends number> = GrowToSize<T, N, []>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user