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