Merge pull request #5575 from mempool/mononaut/minimal-runes
replace rune parsing libraries with minimal reimplementation
This commit is contained in:
		
						commit
						3b9601a82e
					
				| @ -7,23 +7,23 @@ | |||||||
|     <ng-container i18n="ord.mint-n-runes"> |     <ng-container i18n="ord.mint-n-runes"> | ||||||
|       <span>Mint</span> |       <span>Mint</span> | ||||||
|       <span class="amount"> {{ minted >= 100000 ? (minted | amountShortener:undefined:undefined:true) : minted }} </span> |       <span class="amount"> {{ minted >= 100000 ? (minted | amountShortener:undefined:undefined:true) : minted }} </span> | ||||||
|       <ng-container *ngTemplateOutlet="runeName; context: { $implicit: runestone.mint.unwrap().toString() }"></ng-container> |       <ng-container *ngTemplateOutlet="runeName; context: { $implicit: runestone.mint.toString() }"></ng-container> | ||||||
|     </ng-container> |     </ng-container> | ||||||
|   } |   } | ||||||
|   @if (totalSupply > -1) { |   @if (runestone?.etching?.supply) { | ||||||
|     @if (premined > 0) { |     @if (runestone?.etching.premine > 0) { | ||||||
|       <ng-container i18n="ord.premine-n-runes"> |       <ng-container i18n="ord.premine-n-runes"> | ||||||
|         <span>Premine</span> |         <span>Premine</span> | ||||||
|         <span class="amount"> {{ premined >= 100000 ? (premined | amountShortener:undefined:undefined:true) : premined }} </span> |         <span class="amount"> {{ runestone.etching.premine >= 100000 ? (toNumber(runestone.etching.premine) | amountShortener:undefined:undefined:true) : runestone.etching.premine }} </span> | ||||||
|         {{ etchedSymbol }} |         {{ runestone.etching.symbol }} | ||||||
|         <span class="name">{{ etchedName }}</span> |         <span class="name">{{ runestone.etching.spacedName }}</span> | ||||||
|         <span> ({{ premined / totalSupply * 100 | amountShortener:0}}% of total supply)</span> |         <span> ({{ toNumber(runestone.etching.premine) / toNumber(runestone.etching.supply) * 100 | amountShortener:0}}% of total supply)</span> | ||||||
|       </ng-container> |       </ng-container> | ||||||
|     } @else { |     } @else { | ||||||
|       <ng-container i18n="ord.etch-rune"> |       <ng-container i18n="ord.etch-rune"> | ||||||
|         <span>Etching of</span> |         <span>Etching of</span> | ||||||
|         {{ etchedSymbol }} |         {{ runestone.etching.symbol }} | ||||||
|         <span class="name">{{ etchedName }}</span> |         <span class="name">{{ runestone.etching.spacedName }}</span> | ||||||
|       </ng-container> |       </ng-container> | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -36,12 +36,6 @@ | |||||||
|     </div> |     </div> | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   <!-- @if (runestone && !runestone?.etching && !runestone?.mint && !transferredRunes?.length && type === 'vout') { |  | ||||||
|     <div> |  | ||||||
|       <i>No content in this runestone</i> |  | ||||||
|     </div> |  | ||||||
|   } --> |  | ||||||
| 
 |  | ||||||
|   @if (inscriptions?.length && type === 'vin') { |   @if (inscriptions?.length && type === 'vin') { | ||||||
|     <div *ngFor="let contentType of inscriptionsData | keyvalue"> |     <div *ngFor="let contentType of inscriptionsData | keyvalue"> | ||||||
|       <div> |       <div> | ||||||
| @ -68,8 +62,8 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| <ng-template #runeName let-id> | <ng-template #runeName let-id> | ||||||
|   {{ runeInfo[id]?.etching.symbol.isSome() ? runeInfo[id]?.etching.symbol.unwrap() : '' }} |   {{ runeInfo[id]?.etching.symbol || '' }} | ||||||
|   <a [routerLink]="id !== '1:0' ? ['/tx' | relativeUrl, runeInfo[id]?.txid] : null" [class.rune-link]="id !== '1:0'" [class.disabled]="id === '1:0'"> |   <a [routerLink]="id !== '1:0' ? ['/tx' | relativeUrl, runeInfo[id]?.txid] : null" [class.rune-link]="id !== '1:0'" [class.disabled]="id === '1:0'"> | ||||||
|     <span class="name">{{ runeInfo[id]?.name }}</span> |     <span class="name">{{ runeInfo[id]?.etching.spacedName }}</span> | ||||||
|   </a> |   </a> | ||||||
| </ng-template> | </ng-template> | ||||||
| @ -1,9 +1,6 @@ | |||||||
| import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; | import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; | ||||||
| import { Runestone } from '../../shared/ord/rune/runestone'; |  | ||||||
| import { Etching } from '../../shared/ord/rune/etching'; |  | ||||||
| import { u128, u32, u8 } from '../../shared/ord/rune/integer'; |  | ||||||
| import { HttpErrorResponse } from '@angular/common/http'; | import { HttpErrorResponse } from '@angular/common/http'; | ||||||
| import { SpacedRune } from '../../shared/ord/rune/spacedrune'; | import { Runestone, Etching } from '../../shared/ord/rune.utils'; | ||||||
| 
 | 
 | ||||||
| export interface Inscription { | export interface Inscription { | ||||||
|   body?: Uint8Array; |   body?: Uint8Array; | ||||||
| @ -22,79 +19,34 @@ export interface Inscription { | |||||||
| export class OrdDataComponent implements OnChanges { | export class OrdDataComponent implements OnChanges { | ||||||
|   @Input() inscriptions: Inscription[]; |   @Input() inscriptions: Inscription[]; | ||||||
|   @Input() runestone: Runestone; |   @Input() runestone: Runestone; | ||||||
|   @Input() runeInfo: { [id: string]: { etching: Etching; txid: string; name?: string; } }; |   @Input() runeInfo: { [id: string]: { etching: Etching; txid: string } }; | ||||||
|   @Input() error: HttpErrorResponse; |   @Input() error: HttpErrorResponse; | ||||||
|   @Input() type: 'vin' | 'vout'; |   @Input() type: 'vin' | 'vout'; | ||||||
| 
 | 
 | ||||||
|  |   toNumber = (value: bigint): number => Number(value); | ||||||
|  | 
 | ||||||
|   // Inscriptions
 |   // Inscriptions
 | ||||||
|   inscriptionsData: { [key: string]: { count: number, totalSize: number, text?: string; json?: JSON; tag?: string; delegate?: string } }; |   inscriptionsData: { [key: string]: { count: number, totalSize: number, text?: string; json?: JSON; tag?: string; delegate?: string } }; | ||||||
|   // Rune mints
 |   // Rune mints
 | ||||||
|   minted: number; |   minted: number; | ||||||
|   // Rune etching
 |  | ||||||
|   premined: number = -1; |  | ||||||
|   totalSupply: number = -1; |  | ||||||
|   etchedName: string; |  | ||||||
|   etchedSymbol: string; |  | ||||||
|   // Rune transfers
 |   // Rune transfers
 | ||||||
|   transferredRunes: { key: string; etching: Etching; txid: string; name?: string; }[] = []; |   transferredRunes: { key: string; etching: Etching; txid: string }[] = []; | ||||||
| 
 | 
 | ||||||
|   constructor() { } |   constructor() { } | ||||||
| 
 | 
 | ||||||
|   ngOnChanges(changes: SimpleChanges): void { |   ngOnChanges(changes: SimpleChanges): void { | ||||||
|     if (changes.runestone && this.runestone) { |     if (changes.runestone && this.runestone) { | ||||||
| 
 |       this.transferredRunes = Object.entries(this.runeInfo).map(([key, runeInfo]) => ({ key, ...runeInfo })); | ||||||
|       Object.keys(this.runeInfo).forEach((key) => { |       if (this.runestone.mint && this.runeInfo[this.runestone.mint.toString()]) { | ||||||
|         const rune = this.runeInfo[key].etching.rune.isSome() ? this.runeInfo[key].etching.rune.unwrap() : null; |         const mint = this.runestone.mint.toString(); | ||||||
|         const spacers = this.runeInfo[key].etching.spacers.isSome() ? this.runeInfo[key].etching.spacers.unwrap() : u32(0); |  | ||||||
|         if (rune) { |  | ||||||
|           this.runeInfo[key].name = new SpacedRune(rune, Number(spacers)).toString(); |  | ||||||
|         } |  | ||||||
|         this.transferredRunes.push({ key, ...this.runeInfo[key] }); |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|       if (this.runestone.mint.isSome() && this.runeInfo[this.runestone.mint.unwrap().toString()]) { |  | ||||||
|         const mint = this.runestone.mint.unwrap().toString(); |  | ||||||
|         this.transferredRunes = this.transferredRunes.filter(rune => rune.key !== mint); |         this.transferredRunes = this.transferredRunes.filter(rune => rune.key !== mint); | ||||||
|         const terms = this.runeInfo[mint].etching.terms.isSome() ? this.runeInfo[mint].etching.terms.unwrap() : null; |         const terms = this.runeInfo[mint].etching.terms; | ||||||
|         let amount: u128; |         const amount = terms?.amount; | ||||||
|         if (terms) { |         const divisibility = this.runeInfo[mint].etching.divisibility; | ||||||
|           amount = terms.amount.isSome() ? terms.amount.unwrap() : u128(0); |  | ||||||
|         } |  | ||||||
|         const divisibility = this.runeInfo[mint].etching.divisibility.isSome() ? this.runeInfo[mint].etching.divisibility.unwrap() : u8(0); |  | ||||||
|         if (amount) { |         if (amount) { | ||||||
|           this.minted = this.getAmount(amount, divisibility); |           this.minted = this.getAmount(amount, divisibility); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 |  | ||||||
|       if (this.runestone.etching.isSome()) { |  | ||||||
|         const etching = this.runestone.etching.unwrap(); |  | ||||||
|         const rune = etching.rune.isSome() ? etching.rune.unwrap() : null; |  | ||||||
|         const spacers = etching.spacers.isSome() ? etching.spacers.unwrap() : u32(0); |  | ||||||
|         if (rune) { |  | ||||||
|           this.etchedName = new SpacedRune(rune, Number(spacers)).toString(); |  | ||||||
|         } |  | ||||||
|         this.etchedSymbol = etching.symbol.isSome() ? etching.symbol.unwrap() : ''; |  | ||||||
| 
 |  | ||||||
|         const divisibility = etching.divisibility.isSome() ? etching.divisibility.unwrap() : u8(0); |  | ||||||
|         const premine = etching.premine.isSome() ? etching.premine.unwrap() : u128(0); |  | ||||||
|         if (premine) { |  | ||||||
|           this.premined = this.getAmount(premine, divisibility); |  | ||||||
|         } else { |  | ||||||
|           this.premined = 0; |  | ||||||
|         } |  | ||||||
|         const terms = etching.terms.isSome() ? etching.terms.unwrap() : null; |  | ||||||
|         let amount: u128; |  | ||||||
|         if (terms) { |  | ||||||
|           amount = terms.amount.isSome() ? terms.amount.unwrap() : u128(0); |  | ||||||
|           if (amount) { |  | ||||||
|             const cap = terms.cap.isSome() ? terms.cap.unwrap() : u128(0); |  | ||||||
|             this.totalSupply = this.premined + this.getAmount(amount, divisibility) * Number(cap); |  | ||||||
|           } |  | ||||||
|         } else { |  | ||||||
|           this.totalSupply = this.premined; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (changes.inscriptions && this.inscriptions) { |     if (changes.inscriptions && this.inscriptions) { | ||||||
| @ -131,7 +83,7 @@ export class OrdDataComponent implements OnChanges { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getAmount(amount: u128 | bigint, divisibility: u8): number { |   getAmount(amount: bigint, divisibility: number): number { | ||||||
|     const divisor = BigInt(10) ** BigInt(divisibility); |     const divisor = BigInt(10) ** BigInt(divisibility); | ||||||
|     const result = amount / divisor; |     const result = amount / divisor; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,15 +6,14 @@ import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.inter | |||||||
| import { ElectrsApiService } from '../../services/electrs-api.service'; | import { ElectrsApiService } from '../../services/electrs-api.service'; | ||||||
| import { environment } from '../../../environments/environment'; | import { environment } from '../../../environments/environment'; | ||||||
| import { AssetsService } from '../../services/assets.service'; | import { AssetsService } from '../../services/assets.service'; | ||||||
| import { filter, map, tap, switchMap, shareReplay, catchError } from 'rxjs/operators'; | import { filter, map, tap, switchMap, catchError } from 'rxjs/operators'; | ||||||
| import { BlockExtended } from '../../interfaces/node-api.interface'; | import { BlockExtended } from '../../interfaces/node-api.interface'; | ||||||
| import { ApiService } from '../../services/api.service'; | import { ApiService } from '../../services/api.service'; | ||||||
| import { PriceService } from '../../services/price.service'; | import { PriceService } from '../../services/price.service'; | ||||||
| import { StorageService } from '../../services/storage.service'; | import { StorageService } from '../../services/storage.service'; | ||||||
| import { OrdApiService } from '../../services/ord-api.service'; | import { OrdApiService } from '../../services/ord-api.service'; | ||||||
| import { Inscription } from '../ord-data/ord-data.component'; | import { Inscription } from '../ord-data/ord-data.component'; | ||||||
| import { Runestone } from '../../shared/ord/rune/runestone'; | import { Etching, Runestone } from '../../shared/ord/rune.utils'; | ||||||
| import { Etching } from '../../shared/ord/rune/etching'; |  | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-transactions-list', |   selector: 'app-transactions-list', | ||||||
|  | |||||||
| @ -3,10 +3,9 @@ import { catchError, forkJoin, map, Observable, of, switchMap, tap } from 'rxjs' | |||||||
| import { Inscription } from '../components/ord-data/ord-data.component'; | import { Inscription } from '../components/ord-data/ord-data.component'; | ||||||
| import { Transaction } from '../interfaces/electrs.interface'; | import { Transaction } from '../interfaces/electrs.interface'; | ||||||
| import { getNextInscriptionMark, hexToBytes, extractInscriptionData } from '../shared/ord/inscription.utils'; | import { getNextInscriptionMark, hexToBytes, extractInscriptionData } from '../shared/ord/inscription.utils'; | ||||||
| import { Runestone } from '../shared/ord/rune/runestone'; | import { decipherRunestone, Runestone, Etching, UNCOMMON_GOODS } from '../shared/ord/rune.utils'; | ||||||
| import { Etching } from '../shared/ord/rune/etching'; |  | ||||||
| import { ElectrsApiService } from './electrs-api.service'; | import { ElectrsApiService } from './electrs-api.service'; | ||||||
| import { UNCOMMON_GOODS } from '../shared/ord/rune/runestone'; | 
 | ||||||
| 
 | 
 | ||||||
| @Injectable({ | @Injectable({ | ||||||
|   providedIn: 'root' |   providedIn: 'root' | ||||||
| @ -18,27 +17,16 @@ export class OrdApiService { | |||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   decodeRunestone$(tx: Transaction): Observable<{ runestone: Runestone, runeInfo: { [id: string]: { etching: Etching; txid: string; } } }> { |   decodeRunestone$(tx: Transaction): Observable<{ runestone: Runestone, runeInfo: { [id: string]: { etching: Etching; txid: string; } } }> { | ||||||
|     const runestoneTx = { vout: tx.vout.map(vout => ({ scriptpubkey: vout.scriptpubkey })) }; |     const runestone = decipherRunestone(tx); | ||||||
|     const decipher = Runestone.decipher(runestoneTx); |  | ||||||
| 
 |  | ||||||
|     // For now, ignore cenotaphs
 |  | ||||||
|     let message = decipher.isSome() ? decipher.unwrap() : null; |  | ||||||
|     if (message?.type === 'cenotaph') { |  | ||||||
|       return of({ runestone: null, runeInfo: {} }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const runestone = message as Runestone; |  | ||||||
|     const runeInfo: { [id: string]: { etching: Etching; txid: string; } } = {}; |     const runeInfo: { [id: string]: { etching: Etching; txid: string; } } = {}; | ||||||
|     const runesToFetch: Set<string> = new Set(); |     const runesToFetch: Set<string> = new Set(); | ||||||
| 
 | 
 | ||||||
|     if (runestone) { |     if (runestone) { | ||||||
|       if (runestone.mint.isSome()) { |       if (runestone.mint) { | ||||||
|         const mint = runestone.mint.unwrap().toString(); |         if (runestone.mint.toString() === '1:0') { | ||||||
| 
 |           runeInfo[runestone.mint.toString()] = { etching: UNCOMMON_GOODS, txid: '0000000000000000000000000000000000000000000000000000000000000000' }; | ||||||
|         if (mint === '1:0') { |  | ||||||
|           runeInfo[mint] = { etching: UNCOMMON_GOODS, txid: '0000000000000000000000000000000000000000000000000000000000000000' }; |  | ||||||
|         } else { |         } else { | ||||||
|           runesToFetch.add(mint); |           runesToFetch.add(runestone.mint.toString()); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -65,9 +53,10 @@ export class OrdApiService { | |||||||
|           }) |           }) | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|       return of({ runestone: runestone, runeInfo }); |       return of({ runestone: runestone, runeInfo }); | ||||||
|  |     } else { | ||||||
|  |       return of({ runestone: null, runeInfo: {} }); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Get etching from runeId by looking up the transaction that etched the rune
 |   // Get etching from runeId by looking up the transaction that etched the rune
 | ||||||
| @ -78,11 +67,11 @@ export class OrdApiService { | |||||||
|       switchMap(blockHash => this.electrsApiService.getBlockTxId$(blockHash, parseInt(txIndex))), |       switchMap(blockHash => this.electrsApiService.getBlockTxId$(blockHash, parseInt(txIndex))), | ||||||
|       switchMap(txId => this.electrsApiService.getTransaction$(txId)), |       switchMap(txId => this.electrsApiService.getTransaction$(txId)), | ||||||
|       switchMap(tx => { |       switchMap(tx => { | ||||||
|         const decipheredMessage = Runestone.decipher(tx); |         const runestone = decipherRunestone(tx); | ||||||
|         if (decipheredMessage.isSome()) { |         if (runestone) { | ||||||
|           const message = decipheredMessage.unwrap(); |           const etching = runestone.etching; | ||||||
|           if (message?.type === 'runestone' && message.etching.isSome()) { |           if (etching) { | ||||||
|             return of({ etching: message.etching.unwrap(), txid: tx.txid }); |             return of({ etching, txid: tx.txid }); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         return of(null); |         return of(null); | ||||||
|  | |||||||
							
								
								
									
										258
									
								
								frontend/src/app/shared/ord/rune.utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								frontend/src/app/shared/ord/rune.utils.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,258 @@ | |||||||
|  | import { Transaction } from '../../interfaces/electrs.interface'; | ||||||
|  | 
 | ||||||
|  | export const U128_MAX_BIGINT = 0xffff_ffff_ffff_ffff_ffff_ffff_ffff_ffffn; | ||||||
|  | 
 | ||||||
|  | export class RuneId { | ||||||
|  |   block: number; | ||||||
|  |   index: number; | ||||||
|  | 
 | ||||||
|  |   constructor(block: number, index: number) { | ||||||
|  |     this.block = block; | ||||||
|  |     this.index = index; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toString(): string { | ||||||
|  |     return `${this.block}:${this.index}`; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export type Etching = { | ||||||
|  |   divisibility?: number; | ||||||
|  |   premine?: bigint; | ||||||
|  |   symbol?: string; | ||||||
|  |   terms?: { | ||||||
|  |     cap?: bigint; | ||||||
|  |     amount?: bigint; | ||||||
|  |     offset?: { | ||||||
|  |       start?: bigint; | ||||||
|  |       end?: bigint; | ||||||
|  |     }; | ||||||
|  |     height?: { | ||||||
|  |       start?: bigint; | ||||||
|  |       end?: bigint; | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  |   turbo?: boolean; | ||||||
|  |   name?: string; | ||||||
|  |   spacedName?: string; | ||||||
|  |   supply?: bigint; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type Edict = { | ||||||
|  |   id: RuneId; | ||||||
|  |   amount: bigint; | ||||||
|  |   output: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type Runestone = { | ||||||
|  |   mint?: RuneId; | ||||||
|  |   pointer?: number; | ||||||
|  |   edicts?: Edict[]; | ||||||
|  |   etching?: Etching; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type Message = { | ||||||
|  |   fields: Record<number, bigint[]>; | ||||||
|  |   edicts: Edict[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const UNCOMMON_GOODS: Etching = { | ||||||
|  |   divisibility: 0, | ||||||
|  |   premine: 0n, | ||||||
|  |   symbol: '⧉', | ||||||
|  |   terms: { | ||||||
|  |     cap: U128_MAX_BIGINT, | ||||||
|  |     amount: 1n, | ||||||
|  |     offset: { | ||||||
|  |       start: 0n, | ||||||
|  |       end: 0n, | ||||||
|  |     }, | ||||||
|  |     height: { | ||||||
|  |       start: 840000n, | ||||||
|  |       end: 1050000n, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   turbo: false, | ||||||
|  |   name: 'UNCOMMONGOODS', | ||||||
|  |   spacedName: 'UNCOMMON•GOODS', | ||||||
|  |   supply: U128_MAX_BIGINT, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum Tag { | ||||||
|  |   Body = 0, | ||||||
|  |   Flags = 2, | ||||||
|  |   Rune = 4, | ||||||
|  |   Premine = 6, | ||||||
|  |   Cap = 8, | ||||||
|  |   Amount = 10, | ||||||
|  |   HeightStart = 12, | ||||||
|  |   HeightEnd = 14, | ||||||
|  |   OffsetStart = 16, | ||||||
|  |   OffsetEnd = 18, | ||||||
|  |   Mint = 20, | ||||||
|  |   Pointer = 22, | ||||||
|  |   Cenotaph = 126, | ||||||
|  | 
 | ||||||
|  |   Divisibility = 1, | ||||||
|  |   Spacers = 3, | ||||||
|  |   Symbol = 5, | ||||||
|  |   Nop = 127, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const Flag = { | ||||||
|  |   ETCHING: 1n, | ||||||
|  |   TERMS: 1n << 1n, | ||||||
|  |   TURBO: 1n << 2n, | ||||||
|  |   CENOTAPH: 1n << 127n, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | function hexToBytes(hex: string): Uint8Array { | ||||||
|  |   return new Uint8Array(hex.match(/.{2}/g).map((byte) => parseInt(byte, 16))); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function decodeLEB128(bytes: Uint8Array): bigint[] { | ||||||
|  |   const integers: bigint[] = []; | ||||||
|  |   let index = 0; | ||||||
|  |   while (index < bytes.length) { | ||||||
|  |     let value = BigInt(0); | ||||||
|  |     let shift = 0; | ||||||
|  |     let byte: number; | ||||||
|  |     do { | ||||||
|  |       byte = bytes[index++]; | ||||||
|  |       value |= BigInt(byte & 0x7f) << BigInt(shift); | ||||||
|  |       shift += 7; | ||||||
|  |     } while (byte & 0x80); | ||||||
|  |     integers.push(value); | ||||||
|  |   } | ||||||
|  |   return integers; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function integersToMessage(integers: bigint[]): Message { | ||||||
|  |   const message = { | ||||||
|  |     fields: {}, | ||||||
|  |     edicts: [], | ||||||
|  |   }; | ||||||
|  |   let inBody = false; | ||||||
|  |   while (integers.length) { | ||||||
|  |     if (!inBody) { | ||||||
|  |       // The integers are interpreted as a sequence of tag/value pairs, with duplicate tags appending their value to the field value.
 | ||||||
|  |       const tag: Tag = Number(integers.shift()); | ||||||
|  |       if (tag === Tag.Body) { | ||||||
|  |         inBody = true; | ||||||
|  |       } else { | ||||||
|  |         const value = integers.shift(); | ||||||
|  |         if (message.fields[tag]) { | ||||||
|  |           message.fields[tag].push(value); | ||||||
|  |         } else { | ||||||
|  |           message.fields[tag] = [value]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       // If a tag with value zero is encountered, all following integers are interpreted as a series of four-integer edicts, each consisting of a rune ID block height, rune ID transaction index, amount, and output.
 | ||||||
|  |       const height = integers.shift(); | ||||||
|  |       const txIndex = integers.shift(); | ||||||
|  |       const amount = integers.shift(); | ||||||
|  |       const output = integers.shift(); | ||||||
|  |       message.edicts.push({ | ||||||
|  |         id: { | ||||||
|  |           block: height, | ||||||
|  |           index: txIndex, | ||||||
|  |         }, | ||||||
|  |         amount, | ||||||
|  |         output, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return message; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function parseRuneName(rune: bigint): string { | ||||||
|  |   let name = ''; | ||||||
|  |   rune += 1n; | ||||||
|  |   while (rune > 0n) { | ||||||
|  |     name = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[Number((rune - 1n) % 26n)] + name; | ||||||
|  |     rune = (rune - 1n) / 26n; | ||||||
|  |   } | ||||||
|  |   return name; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function spaceRuneName(name: string, spacers: bigint): string { | ||||||
|  |   let i = 0; | ||||||
|  |   let spacedName = ''; | ||||||
|  |   while (spacers > 0n || i < name.length) { | ||||||
|  |     spacedName += name[i]; | ||||||
|  |     if (spacers & 1n) { | ||||||
|  |       spacedName += '•'; | ||||||
|  |     } | ||||||
|  |     if (spacers > 0n) { | ||||||
|  |       spacers >>= 1n; | ||||||
|  |     } | ||||||
|  |     i++; | ||||||
|  |   } | ||||||
|  |   return spacedName; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function messageToRunestone(message: Message): Runestone { | ||||||
|  |   let etching: Etching | undefined; | ||||||
|  |   let mint: RuneId | undefined; | ||||||
|  |   let pointer: number | undefined; | ||||||
|  | 
 | ||||||
|  |   const flags = message.fields[Tag.Flags]?.[0] || 0n; | ||||||
|  |   if (flags & Flag.ETCHING) { | ||||||
|  |     const hasTerms = (flags & Flag.TERMS) > 0n; | ||||||
|  |     const isTurbo = (flags & Flag.TURBO) > 0n; | ||||||
|  |     const name = parseRuneName(message.fields[Tag.Rune][0]); | ||||||
|  |     etching = { | ||||||
|  |       divisibility: Number(message.fields[Tag.Divisibility][0]), | ||||||
|  |       premine: message.fields[Tag.Premine]?.[0], | ||||||
|  |       symbol: message.fields[Tag.Symbol]?.[0] ? String.fromCodePoint(Number(message.fields[Tag.Symbol][0])) : '¤', | ||||||
|  |       terms: hasTerms ? { | ||||||
|  |         cap: message.fields[Tag.Cap]?.[0], | ||||||
|  |         amount: message.fields[Tag.Amount]?.[0], | ||||||
|  |         offset: { | ||||||
|  |           start: message.fields[Tag.OffsetStart]?.[0], | ||||||
|  |           end: message.fields[Tag.OffsetEnd]?.[0], | ||||||
|  |         }, | ||||||
|  |         height: { | ||||||
|  |           start: message.fields[Tag.HeightStart]?.[0], | ||||||
|  |           end: message.fields[Tag.HeightEnd]?.[0], | ||||||
|  |         }, | ||||||
|  |       } : undefined, | ||||||
|  |       turbo: isTurbo, | ||||||
|  |       name, | ||||||
|  |       spacedName: spaceRuneName(name, message.fields[Tag.Spacers]?.[0] ?? 0n), | ||||||
|  |     }; | ||||||
|  |     etching.supply = ( | ||||||
|  |       (etching.terms?.cap ?? 0n) * (etching.terms?.amount ?? 0n) | ||||||
|  |     ) + (etching.premine ?? 0n); | ||||||
|  |   } | ||||||
|  |   const mintField = message.fields[Tag.Mint]; | ||||||
|  |   if (mintField) { | ||||||
|  |     mint = new RuneId(Number(mintField[0]), Number(mintField[1])); | ||||||
|  |   } | ||||||
|  |   const pointerField = message.fields[Tag.Pointer]; | ||||||
|  |   if (pointerField) { | ||||||
|  |     pointer = Number(pointerField[0]); | ||||||
|  |   } | ||||||
|  |   return { | ||||||
|  |     mint, | ||||||
|  |     pointer, | ||||||
|  |     edicts: message.edicts, | ||||||
|  |     etching, | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function decipherRunestone(tx: Transaction): Runestone | void { | ||||||
|  |   const payload = tx.vout.find((vout) => vout.scriptpubkey.startsWith('6a5d'))?.scriptpubkey_asm.replace(/OP_\w+|\s/g, ''); | ||||||
|  |   if (!payload) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   try { | ||||||
|  |     const integers = decodeLEB128(hexToBytes(payload)); | ||||||
|  |     const message = integersToMessage(integers); | ||||||
|  |     return messageToRunestone(message); | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error(error); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,4 +0,0 @@ | |||||||
| import { Cenotaph } from './cenotaph'; |  | ||||||
| import { Runestone } from './runestone'; |  | ||||||
| 
 |  | ||||||
| export type Artifact = Cenotaph | Runestone; |  | ||||||
| @ -1,14 +0,0 @@ | |||||||
| import { Flaw } from './flaw'; |  | ||||||
| import { None, Option } from './monads'; |  | ||||||
| import { Rune } from './rune'; |  | ||||||
| import { RuneId } from './runeid'; |  | ||||||
| 
 |  | ||||||
| export class Cenotaph { |  | ||||||
|   readonly type = 'cenotaph'; |  | ||||||
| 
 |  | ||||||
|   constructor( |  | ||||||
|     readonly flaws: Flaw[], |  | ||||||
|     readonly etching: Option<Rune> = None, |  | ||||||
|     readonly mint: Option<RuneId> = None |  | ||||||
|   ) {} |  | ||||||
| } |  | ||||||
| @ -1,7 +0,0 @@ | |||||||
| import { u8 } from './integer'; |  | ||||||
| import { opcodes } from './script'; |  | ||||||
| 
 |  | ||||||
| export const MAX_DIVISIBILITY = u8(38); |  | ||||||
| 
 |  | ||||||
| export const OP_RETURN = opcodes.OP_RETURN; |  | ||||||
| export const MAGIC_NUMBER = opcodes.OP_13; |  | ||||||
| @ -1,34 +0,0 @@ | |||||||
| import { Option, Some, None } from './monads'; |  | ||||||
| import { RuneId } from './runeid'; |  | ||||||
| import { u128, u32 } from './integer'; |  | ||||||
| 
 |  | ||||||
| export type Edict = { |  | ||||||
|   id: RuneId; |  | ||||||
|   amount: u128; |  | ||||||
|   output: u32; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export namespace Edict { |  | ||||||
|   export function fromIntegers( |  | ||||||
|     numOutputs: number, |  | ||||||
|     id: RuneId, |  | ||||||
|     amount: u128, |  | ||||||
|     output: u128 |  | ||||||
|   ): Option<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 }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,54 +0,0 @@ | |||||||
| import { None, Option, Some } from './monads'; |  | ||||||
| import { Terms } from './terms'; |  | ||||||
| import { Rune } from './rune'; |  | ||||||
| import { u128, u32, u8 } from './integer'; |  | ||||||
| 
 |  | ||||||
| type RuneEtchingBase = { |  | ||||||
|   divisibility?: number; |  | ||||||
|   premine?: bigint; |  | ||||||
|   symbol?: string; |  | ||||||
|   terms?: { |  | ||||||
|     cap?: bigint; |  | ||||||
|     amount?: bigint; |  | ||||||
|     offset?: { |  | ||||||
|       start?: bigint; |  | ||||||
|       end?: bigint; |  | ||||||
|     }; |  | ||||||
|     height?: { |  | ||||||
|       start?: bigint; |  | ||||||
|       end?: bigint; |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
|   turbo?: boolean; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export type RuneEtchingSpec = RuneEtchingBase & { runeName?: string }; |  | ||||||
| 
 |  | ||||||
| export class Etching { |  | ||||||
|   readonly symbol: Option<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)); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,20 +0,0 @@ | |||||||
| import { u128 } from './integer'; |  | ||||||
| 
 |  | ||||||
| export enum Flag { |  | ||||||
|   ETCHING = 0, |  | ||||||
|   TERMS = 1, |  | ||||||
|   TURBO = 2, |  | ||||||
|   CENOTAPH = 127, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export namespace Flag { |  | ||||||
|   export function mask(flag: Flag): u128 { |  | ||||||
|     return u128(1n << BigInt(flag)); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   export function take(flags: u128, flag: Flag): { set: boolean; flags: u128 } { |  | ||||||
|     const mask = Flag.mask(flag); |  | ||||||
|     const set = (flags & mask) !== 0n; |  | ||||||
|     return { set, flags: set ? u128(flags - mask) : flags }; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| export enum Flaw { |  | ||||||
|   EDICT_OUTPUT, |  | ||||||
|   EDICT_RUNE_ID, |  | ||||||
|   INVALID_SCRIPT, |  | ||||||
|   OPCODE, |  | ||||||
|   SUPPLY_OVERFLOW, |  | ||||||
|   TRAILING_INTEGERS, |  | ||||||
|   TRUNCATED_FIELD, |  | ||||||
|   UNRECOGNIZED_EVEN_TAG, |  | ||||||
|   UNRECOGNIZED_FLAG, |  | ||||||
|   VARINT, |  | ||||||
| } |  | ||||||
| @ -1,4 +0,0 @@ | |||||||
| export { u8 } from './u8'; |  | ||||||
| export { u32 } from './u32'; |  | ||||||
| export { u64 } from './u64'; |  | ||||||
| export { u128 } from './u128'; |  | ||||||
| @ -1,176 +0,0 @@ | |||||||
| import { None, Option, Some } from '../monads'; |  | ||||||
| import { SeekArray } from '../seekarray'; |  | ||||||
| import { u64 } from './u64'; |  | ||||||
| import { u32 } from './u32'; |  | ||||||
| import { u8 } from './u8'; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * A little utility type used for nominal typing. |  | ||||||
|  * |  | ||||||
|  * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
 |  | ||||||
|  */ |  | ||||||
| type BigTypedNumber<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(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,58 +0,0 @@ | |||||||
| import { None, Option, Some } from '../monads'; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * A little utility type used for nominal typing. |  | ||||||
|  * |  | ||||||
|  * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
 |  | ||||||
|  */ |  | ||||||
| type BigTypedNumber<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)); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,58 +0,0 @@ | |||||||
| import { None, Option, Some } from '../monads'; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * A little utility type used for nominal typing. |  | ||||||
|  * |  | ||||||
|  * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
 |  | ||||||
|  */ |  | ||||||
| type BigTypedNumber<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)); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,58 +0,0 @@ | |||||||
| import { None, Option, Some } from '../monads'; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * A little utility type used for nominal typing. |  | ||||||
|  * |  | ||||||
|  * See {@link https://michalzalecki.com/nominal-typing-in-typescript/}
 |  | ||||||
|  */ |  | ||||||
| type BigTypedNumber<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)); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,67 +0,0 @@ | |||||||
| import { Edict } from './edict'; |  | ||||||
| import { Flaw } from './flaw'; |  | ||||||
| import { u128, u64, u32 } from './integer'; |  | ||||||
| import { RuneId } from './runeid'; |  | ||||||
| import { Tag } from './tag'; |  | ||||||
| 
 |  | ||||||
| export class Message { |  | ||||||
|   constructor( |  | ||||||
|     readonly flaws: Flaw[], |  | ||||||
|     readonly edicts: Edict[], |  | ||||||
|     readonly fields: Map<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); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,392 +0,0 @@ | |||||||
| // Copied with MIT License from link below:
 |  | ||||||
| // https://github.com/thames-technology/monads/blob/de957d3d68449d659518d99be4ea74bbb70dfc8e/src/option/option.ts
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Type representing any value except 'undefined'. |  | ||||||
|  * This is useful when working with strict null checks, ensuring that a value can be null but not undefined. |  | ||||||
|  */ |  | ||||||
| type NonUndefined = {} | null; // eslint-disable-line @typescript-eslint/ban-types
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Enum-like object to represent the type of an Option (Some or None). |  | ||||||
|  */ |  | ||||||
| export const OptionType = { |  | ||||||
|   Some: Symbol(':some'), |  | ||||||
|   None: Symbol(':none'), |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Interface for handling match operations on an Option. |  | ||||||
|  * Allows executing different logic based on the Option being Some or None. |  | ||||||
|  */ |  | ||||||
| interface Match<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(); |  | ||||||
| } |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| import { u128 } from './integer'; |  | ||||||
| 
 |  | ||||||
| export class Rune { |  | ||||||
| 
 |  | ||||||
|   constructor(readonly value: u128) {} |  | ||||||
| 
 |  | ||||||
|   toString() { |  | ||||||
|     let n = this.value; |  | ||||||
| 
 |  | ||||||
|     if (n === u128.MAX) { |  | ||||||
|       return 'BCGDENLQRQWDSLRUGSNLBTMFIJAV'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     n = u128(n + 1n); |  | ||||||
|     let symbol = ''; |  | ||||||
|     while (n > 0) { |  | ||||||
|       symbol = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[Number((n - 1n) % 26n)] + symbol; |  | ||||||
|       n = u128((n - 1n) / 26n); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return symbol; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,89 +0,0 @@ | |||||||
| import { None, Option, Some } from './monads'; |  | ||||||
| import { u64, u32, u128 } from './integer'; |  | ||||||
| 
 |  | ||||||
| export class RuneId { |  | ||||||
|   constructor(readonly block: u64, readonly tx: u32) {} |  | ||||||
| 
 |  | ||||||
|   static new(block: u64, tx: u32): Option<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))); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,258 +0,0 @@ | |||||||
| import { concatUint8Arrays, hexToBytes } from '../inscription.utils'; |  | ||||||
| import { Artifact } from './artifact'; |  | ||||||
| import { Cenotaph } from './cenotaph'; |  | ||||||
| import { MAGIC_NUMBER, MAX_DIVISIBILITY, OP_RETURN } from './constants'; |  | ||||||
| import { Edict } from './edict'; |  | ||||||
| import { Etching } from './etching'; |  | ||||||
| import { Flag } from './flag'; |  | ||||||
| import { Flaw } from './flaw'; |  | ||||||
| import { u128, u32, u64, u8 } from './integer'; |  | ||||||
| import { Message } from './message'; |  | ||||||
| import { None, Option, Some } from './monads'; |  | ||||||
| import { Rune } from './rune'; |  | ||||||
| import { RuneId } from './runeid'; |  | ||||||
| import { script } from './script'; |  | ||||||
| import { SeekArray } from './seekarray'; |  | ||||||
| import { Tag } from './tag'; |  | ||||||
| 
 |  | ||||||
| export const MAX_SPACERS = 0b00000111_11111111_11111111_11111111; |  | ||||||
| 
 |  | ||||||
| export const UNCOMMON_GOODS = new Etching( |  | ||||||
|   Some(u8(0)), |  | ||||||
|   Some(new Rune(u128(2055900680524219742n))), |  | ||||||
|   Some(u32(128)), |  | ||||||
|   Some('⧉'), |  | ||||||
|   Some({ |  | ||||||
|     amount: Some(u128(1)), |  | ||||||
|     cap: Some(u128(340282366920938463463374607431768211455n)), |  | ||||||
|     height: [Some(u64(840000)), Some(u64(1050000))], |  | ||||||
|     offset: [Some(u64(0)), Some(u64(0))], |  | ||||||
|   }), |  | ||||||
|   Some(u128(0)), |  | ||||||
|   false |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| // New: Esplora format instead of Bitcoin RPC format
 |  | ||||||
| export type RunestoneTx = { |  | ||||||
|   vout: { |  | ||||||
|     scriptpubkey: string |  | ||||||
|   }[]; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| type Payload = Uint8Array | Flaw; |  | ||||||
| 
 |  | ||||||
| export class Runestone { |  | ||||||
|   readonly type = 'runestone'; |  | ||||||
| 
 |  | ||||||
|   constructor( |  | ||||||
|     readonly mint: Option<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); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,237 +0,0 @@ | |||||||
| namespace pushdata { |  | ||||||
|   /** |  | ||||||
|    * Calculates the encoding length of a number used for push data in Bitcoin transactions. |  | ||||||
|    * @param i The number to calculate the encoding length for. |  | ||||||
|    * @returns The encoding length of the number. |  | ||||||
|    */ |  | ||||||
|   export function encodingLength(i: number): number { |  | ||||||
|     return i < OPS.OP_PUSHDATA1 ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * Decodes a byte array and returns information about the opcode, number, and size. |  | ||||||
|    * @param array - The byte array to decode. |  | ||||||
|    * @param offset - The offset within the array to start decoding. |  | ||||||
|    * @returns An object containing the opcode, number, and size, or null if decoding fails. |  | ||||||
|    */ |  | ||||||
|   export function decode( |  | ||||||
|     array: Uint8Array, |  | ||||||
|     offset: number |  | ||||||
|   ): { |  | ||||||
|     opcode: number; |  | ||||||
|     number: number; |  | ||||||
|     size: number; |  | ||||||
|   } | null { |  | ||||||
|     const dataView = new DataView(array.buffer, array.byteOffset, array.byteLength); |  | ||||||
|     const opcode = dataView.getUint8(offset); |  | ||||||
|     let num: number; |  | ||||||
|     let size: number; |  | ||||||
| 
 |  | ||||||
|     // ~6 bit
 |  | ||||||
|     if (opcode < OPS.OP_PUSHDATA1) { |  | ||||||
|       num = opcode; |  | ||||||
|       size = 1; |  | ||||||
| 
 |  | ||||||
|       // 8 bit
 |  | ||||||
|     } else if (opcode === OPS.OP_PUSHDATA1) { |  | ||||||
|       if (offset + 2 > array.length) return null; |  | ||||||
|       num = dataView.getUint8(offset + 1); |  | ||||||
|       size = 2; |  | ||||||
| 
 |  | ||||||
|       // 16 bit
 |  | ||||||
|     } else if (opcode === OPS.OP_PUSHDATA2) { |  | ||||||
|       if (offset + 3 > array.length) return null; |  | ||||||
|       num = dataView.getUint16(offset + 1, true); // true for little-endian
 |  | ||||||
|       size = 3; |  | ||||||
| 
 |  | ||||||
|       // 32 bit
 |  | ||||||
|     } else { |  | ||||||
|       if (offset + 5 > array.length) return null; |  | ||||||
|       if (opcode !== OPS.OP_PUSHDATA4) throw new Error('Unexpected opcode'); |  | ||||||
| 
 |  | ||||||
|       num = dataView.getUint32(offset + 1, true); // true for little-endian
 |  | ||||||
|       size = 5; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|       opcode, |  | ||||||
|       number: num, |  | ||||||
|       size, |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const OPS = { |  | ||||||
|   OP_FALSE: 0, |  | ||||||
|   OP_0: 0, |  | ||||||
|   OP_PUSHDATA1: 76, |  | ||||||
|   OP_PUSHDATA2: 77, |  | ||||||
|   OP_PUSHDATA4: 78, |  | ||||||
|   OP_1NEGATE: 79, |  | ||||||
|   OP_RESERVED: 80, |  | ||||||
|   OP_TRUE: 81, |  | ||||||
|   OP_1: 81, |  | ||||||
|   OP_2: 82, |  | ||||||
|   OP_3: 83, |  | ||||||
|   OP_4: 84, |  | ||||||
|   OP_5: 85, |  | ||||||
|   OP_6: 86, |  | ||||||
|   OP_7: 87, |  | ||||||
|   OP_8: 88, |  | ||||||
|   OP_9: 89, |  | ||||||
|   OP_10: 90, |  | ||||||
|   OP_11: 91, |  | ||||||
|   OP_12: 92, |  | ||||||
|   OP_13: 93, |  | ||||||
|   OP_14: 94, |  | ||||||
|   OP_15: 95, |  | ||||||
|   OP_16: 96, |  | ||||||
| 
 |  | ||||||
|   OP_NOP: 97, |  | ||||||
|   OP_VER: 98, |  | ||||||
|   OP_IF: 99, |  | ||||||
|   OP_NOTIF: 100, |  | ||||||
|   OP_VERIF: 101, |  | ||||||
|   OP_VERNOTIF: 102, |  | ||||||
|   OP_ELSE: 103, |  | ||||||
|   OP_ENDIF: 104, |  | ||||||
|   OP_VERIFY: 105, |  | ||||||
|   OP_RETURN: 106, |  | ||||||
| 
 |  | ||||||
|   OP_TOALTSTACK: 107, |  | ||||||
|   OP_FROMALTSTACK: 108, |  | ||||||
|   OP_2DROP: 109, |  | ||||||
|   OP_2DUP: 110, |  | ||||||
|   OP_3DUP: 111, |  | ||||||
|   OP_2OVER: 112, |  | ||||||
|   OP_2ROT: 113, |  | ||||||
|   OP_2SWAP: 114, |  | ||||||
|   OP_IFDUP: 115, |  | ||||||
|   OP_DEPTH: 116, |  | ||||||
|   OP_DROP: 117, |  | ||||||
|   OP_DUP: 118, |  | ||||||
|   OP_NIP: 119, |  | ||||||
|   OP_OVER: 120, |  | ||||||
|   OP_PICK: 121, |  | ||||||
|   OP_ROLL: 122, |  | ||||||
|   OP_ROT: 123, |  | ||||||
|   OP_SWAP: 124, |  | ||||||
|   OP_TUCK: 125, |  | ||||||
| 
 |  | ||||||
|   OP_CAT: 126, |  | ||||||
|   OP_SUBSTR: 127, |  | ||||||
|   OP_LEFT: 128, |  | ||||||
|   OP_RIGHT: 129, |  | ||||||
|   OP_SIZE: 130, |  | ||||||
| 
 |  | ||||||
|   OP_INVERT: 131, |  | ||||||
|   OP_AND: 132, |  | ||||||
|   OP_OR: 133, |  | ||||||
|   OP_XOR: 134, |  | ||||||
|   OP_EQUAL: 135, |  | ||||||
|   OP_EQUALVERIFY: 136, |  | ||||||
|   OP_RESERVED1: 137, |  | ||||||
|   OP_RESERVED2: 138, |  | ||||||
| 
 |  | ||||||
|   OP_1ADD: 139, |  | ||||||
|   OP_1SUB: 140, |  | ||||||
|   OP_2MUL: 141, |  | ||||||
|   OP_2DIV: 142, |  | ||||||
|   OP_NEGATE: 143, |  | ||||||
|   OP_ABS: 144, |  | ||||||
|   OP_NOT: 145, |  | ||||||
|   OP_0NOTEQUAL: 146, |  | ||||||
|   OP_ADD: 147, |  | ||||||
|   OP_SUB: 148, |  | ||||||
|   OP_MUL: 149, |  | ||||||
|   OP_DIV: 150, |  | ||||||
|   OP_MOD: 151, |  | ||||||
|   OP_LSHIFT: 152, |  | ||||||
|   OP_RSHIFT: 153, |  | ||||||
| 
 |  | ||||||
|   OP_BOOLAND: 154, |  | ||||||
|   OP_BOOLOR: 155, |  | ||||||
|   OP_NUMEQUAL: 156, |  | ||||||
|   OP_NUMEQUALVERIFY: 157, |  | ||||||
|   OP_NUMNOTEQUAL: 158, |  | ||||||
|   OP_LESSTHAN: 159, |  | ||||||
|   OP_GREATERTHAN: 160, |  | ||||||
|   OP_LESSTHANOREQUAL: 161, |  | ||||||
|   OP_GREATERTHANOREQUAL: 162, |  | ||||||
|   OP_MIN: 163, |  | ||||||
|   OP_MAX: 164, |  | ||||||
| 
 |  | ||||||
|   OP_WITHIN: 165, |  | ||||||
| 
 |  | ||||||
|   OP_RIPEMD160: 166, |  | ||||||
|   OP_SHA1: 167, |  | ||||||
|   OP_SHA256: 168, |  | ||||||
|   OP_HASH160: 169, |  | ||||||
|   OP_HASH256: 170, |  | ||||||
|   OP_CODESEPARATOR: 171, |  | ||||||
|   OP_CHECKSIG: 172, |  | ||||||
|   OP_CHECKSIGVERIFY: 173, |  | ||||||
|   OP_CHECKMULTISIG: 174, |  | ||||||
|   OP_CHECKMULTISIGVERIFY: 175, |  | ||||||
| 
 |  | ||||||
|   OP_NOP1: 176, |  | ||||||
| 
 |  | ||||||
|   OP_NOP2: 177, |  | ||||||
|   OP_CHECKLOCKTIMEVERIFY: 177, |  | ||||||
| 
 |  | ||||||
|   OP_NOP3: 178, |  | ||||||
|   OP_CHECKSEQUENCEVERIFY: 178, |  | ||||||
| 
 |  | ||||||
|   OP_NOP4: 179, |  | ||||||
|   OP_NOP5: 180, |  | ||||||
|   OP_NOP6: 181, |  | ||||||
|   OP_NOP7: 182, |  | ||||||
|   OP_NOP8: 183, |  | ||||||
|   OP_NOP9: 184, |  | ||||||
|   OP_NOP10: 185, |  | ||||||
| 
 |  | ||||||
|   OP_CHECKSIGADD: 186, |  | ||||||
| 
 |  | ||||||
|   OP_PUBKEYHASH: 253, |  | ||||||
|   OP_PUBKEY: 254, |  | ||||||
|   OP_INVALIDOPCODE: 255, |  | ||||||
| } as const; |  | ||||||
| 
 |  | ||||||
| export const opcodes = OPS; |  | ||||||
| 
 |  | ||||||
| export namespace script { |  | ||||||
|   export type Instruction = number | Uint8Array; |  | ||||||
| 
 |  | ||||||
|   export function* decompile(array: Uint8Array): Generator<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; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,43 +0,0 @@ | |||||||
| /** |  | ||||||
|  * This class provides a way to read data sequentially from a Uint8Array with automatic cursor management. |  | ||||||
|  * It utilizes DataView for handling multi-byte data types. |  | ||||||
|  * |  | ||||||
|  * This replaces the SeekBuffer from the original runestone-lib! |  | ||||||
|  */ |  | ||||||
| export class SeekArray { |  | ||||||
| 
 |  | ||||||
|   public seekIndex: number = 0; |  | ||||||
|   private dataView: DataView; |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * Constructs a SeekArray instance. |  | ||||||
|    * |  | ||||||
|    * @param array - The Uint8Array from which data will be read. |  | ||||||
|    */ |  | ||||||
|   constructor(private array: Uint8Array) { |  | ||||||
|     this.dataView = new DataView(array.buffer, array.byteOffset, array.byteLength); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * Reads an unsigned 8-bit integer from the current position and advances the seek index by 1 byte. |  | ||||||
|    * |  | ||||||
|    * @returns The read value or undefined if reading beyond the end of the array. |  | ||||||
|    */ |  | ||||||
|   readUInt8(): number | undefined { |  | ||||||
|     if (this.isFinished()) { |  | ||||||
|       return undefined; |  | ||||||
|     } |  | ||||||
|     const value = this.dataView.getUint8(this.seekIndex); |  | ||||||
|     this.seekIndex += 1; |  | ||||||
|     return value; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |  | ||||||
|    * Checks if the seek index has reached or surpassed the length of the underlying array. |  | ||||||
|    * |  | ||||||
|    * @returns true if there are no more bytes to read, false otherwise. |  | ||||||
|    */ |  | ||||||
|   isFinished(): boolean { |  | ||||||
|     return this.seekIndex >= this.array.length; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,21 +0,0 @@ | |||||||
| import { Rune } from './rune'; |  | ||||||
| 
 |  | ||||||
| export class SpacedRune { |  | ||||||
|   constructor(readonly rune: Rune, readonly spacers: number) {} |  | ||||||
| 
 |  | ||||||
|   toString(): string { |  | ||||||
|     const rune = this.rune.toString(); |  | ||||||
|     let i = 0; |  | ||||||
|     let result = ''; |  | ||||||
|     for (const c of rune) { |  | ||||||
|       result += c; |  | ||||||
| 
 |  | ||||||
|       if (i < rune.length - 1 && (this.spacers & (1 << i)) !== 0) { |  | ||||||
|         result += '•'; |  | ||||||
|       } |  | ||||||
|       i++; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| import { None, Option, Some } from './monads'; |  | ||||||
| import { u128 } from './integer'; |  | ||||||
| import { FixedArray } from './utils'; |  | ||||||
| 
 |  | ||||||
| export enum Tag { |  | ||||||
|   BODY = 0, |  | ||||||
|   FLAGS = 2, |  | ||||||
|   RUNE = 4, |  | ||||||
| 
 |  | ||||||
|   PREMINE = 6, |  | ||||||
|   CAP = 8, |  | ||||||
|   AMOUNT = 10, |  | ||||||
|   HEIGHT_START = 12, |  | ||||||
|   HEIGHT_END = 14, |  | ||||||
|   OFFSET_START = 16, |  | ||||||
|   OFFSET_END = 18, |  | ||||||
|   MINT = 20, |  | ||||||
|   POINTER = 22, |  | ||||||
|   CENOTAPH = 126, |  | ||||||
| 
 |  | ||||||
|   DIVISIBILITY = 1, |  | ||||||
|   SPACERS = 3, |  | ||||||
|   SYMBOL = 5, |  | ||||||
|   NOP = 127, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export namespace Tag { |  | ||||||
|   export function take<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()); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| 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>]; |  | ||||||
| }; |  | ||||||
| @ -1,6 +0,0 @@ | |||||||
| 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