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