|
1 | | -export * from './src/indexer'; |
| 1 | +import { None, Option, Some } from '@sniptt/monads'; |
| 2 | +import _ from 'lodash'; |
| 3 | +import { u64, u32, u128, u8 } from './src/integer'; |
| 4 | +import { Runestone } from './src/runestone'; |
| 5 | +import { RuneEtchingSpec } from './src/indexer'; |
| 6 | +import { RuneId } from './src/runeid'; |
| 7 | +import { Rune } from './src/rune'; |
| 8 | +import { Etching } from './src/etching'; |
| 9 | +import { Terms } from './src/terms'; |
| 10 | +import { MAX_DIVISIBILITY } from './src/constants'; |
| 11 | + |
| 12 | +export { |
| 13 | + BlockInfo, |
| 14 | + RuneBlockIndex, |
| 15 | + RuneEtching, |
| 16 | + RuneEtchingSpec, |
| 17 | + RuneMint, |
| 18 | + RuneUtxoBalance, |
| 19 | + RunestoneIndexer, |
| 20 | + RunestoneIndexerOptions, |
| 21 | + RunestoneStorage, |
| 22 | +} from './src/indexer'; |
| 23 | + |
| 24 | +export type RunestoneSpec = { |
| 25 | + mint?: { |
| 26 | + block: bigint; |
| 27 | + tx: number; |
| 28 | + }; |
| 29 | + pointer?: number; |
| 30 | + etching?: RuneEtchingSpec; |
| 31 | + edicts?: { |
| 32 | + id: { |
| 33 | + block: bigint; |
| 34 | + tx: number; |
| 35 | + }; |
| 36 | + amount: bigint; |
| 37 | + output: number; |
| 38 | + }[]; |
| 39 | +}; |
| 40 | + |
| 41 | +// Helper functions to ensure numbers fit the desired type correctly |
| 42 | +const u8Strict = (n: number) => { |
| 43 | + const bigN = BigInt(n); |
| 44 | + if (bigN < 0n || bigN > u8.MAX) { |
| 45 | + throw Error('u8 overflow'); |
| 46 | + } |
| 47 | + return u8(bigN); |
| 48 | +}; |
| 49 | +const u32Strict = (n: number) => { |
| 50 | + const bigN = BigInt(n); |
| 51 | + if (bigN < 0n || bigN > u32.MAX) { |
| 52 | + throw Error('u32 overflow'); |
| 53 | + } |
| 54 | + return u32(bigN); |
| 55 | +}; |
| 56 | +const u64Strict = (n: bigint) => { |
| 57 | + const bigN = BigInt(n); |
| 58 | + if (bigN < 0n || bigN > u64.MAX) { |
| 59 | + throw Error('u64 overflow'); |
| 60 | + } |
| 61 | + return u64(bigN); |
| 62 | +}; |
| 63 | +const u128Strict = (n: bigint) => { |
| 64 | + const bigN = BigInt(n); |
| 65 | + if (bigN < 0n || bigN > u128.MAX) { |
| 66 | + throw Error('u128 overflow'); |
| 67 | + } |
| 68 | + return u128(bigN); |
| 69 | +}; |
| 70 | + |
| 71 | +/** |
| 72 | + * Low level function to allow for encoding runestones without any indexer and transaction checks. |
| 73 | + * |
| 74 | + * @param runestone runestone spec to encode as runestone |
| 75 | + * @returns encoded runestone bytes |
| 76 | + * @throws Error if encoding is detected to be considered a cenotaph |
| 77 | + */ |
| 78 | +export function encodeRunestoneUnsafe(runestone: RunestoneSpec): Buffer { |
| 79 | + const mint = runestone.mint |
| 80 | + ? Some(new RuneId(u64Strict(runestone.mint.block), u32Strict(runestone.mint.tx))) |
| 81 | + : None; |
| 82 | + |
| 83 | + const pointer = Some(runestone.pointer).map(u32Strict); |
| 84 | + |
| 85 | + const edicts = (runestone.edicts ?? []).map((edict) => ({ |
| 86 | + id: new RuneId(u64Strict(edict.id.block), u32Strict(edict.id.tx)), |
| 87 | + amount: u128Strict(edict.amount), |
| 88 | + output: u32Strict(edict.output), |
| 89 | + })); |
| 90 | + |
| 91 | + let etching: Option<Etching> = None; |
| 92 | + if (runestone.etching) { |
| 93 | + const etchingSpec = runestone.etching; |
| 94 | + |
| 95 | + if (!etchingSpec.rune && etchingSpec.spacers?.length) { |
| 96 | + throw Error('Spacers specified with no rune'); |
| 97 | + } |
| 98 | + |
| 99 | + if ( |
| 100 | + etchingSpec.rune && |
| 101 | + etchingSpec.spacers?.length && |
| 102 | + _.max(etchingSpec.spacers)! >= etchingSpec.rune.length - 1 |
| 103 | + ) { |
| 104 | + throw Error('Spacers specified out of bounds of rune'); |
| 105 | + } |
| 106 | + |
| 107 | + if (etchingSpec.symbol && etchingSpec.symbol.codePointAt(1) !== undefined) { |
| 108 | + throw Error('Symbol must be one code point'); |
| 109 | + } |
| 110 | + |
| 111 | + const divisibility = Some(etchingSpec.divisibility).map(u8Strict); |
| 112 | + const premine = Some(etchingSpec.premine).map(u128Strict); |
| 113 | + const rune = Some(etchingSpec.rune).map((rune) => Rune.fromString(rune)); |
| 114 | + const spacers = etchingSpec.spacers |
| 115 | + ? Some( |
| 116 | + u32Strict( |
| 117 | + etchingSpec.spacers.reduce((spacers, flagIndex) => spacers | (1 << flagIndex), 0) |
| 118 | + ) |
| 119 | + ) |
| 120 | + : None; |
| 121 | + const symbol = Some(etchingSpec.symbol || undefined); |
| 122 | + |
| 123 | + if (divisibility.isSome() && divisibility.unwrap() < MAX_DIVISIBILITY) { |
| 124 | + throw Error(`Divisibility is greater than protocol max ${MAX_DIVISIBILITY}`); |
| 125 | + } |
| 126 | + |
| 127 | + let terms: Option<Terms> = None; |
| 128 | + if (etchingSpec.terms) { |
| 129 | + const termsSpec = etchingSpec.terms; |
| 130 | + |
| 131 | + const amount = Some(termsSpec.amount).map(u128Strict); |
| 132 | + const cap = Some(termsSpec.cap).map(u128Strict); |
| 133 | + const height: [Option<u64>, Option<u64>] = termsSpec.height |
| 134 | + ? [Some(termsSpec.height.start).map(u64Strict), Some(termsSpec.height.end).map(u64Strict)] |
| 135 | + : [None, None]; |
| 136 | + const offset: [Option<u64>, Option<u64>] = termsSpec.offset |
| 137 | + ? [Some(termsSpec.offset.start).map(u64Strict), Some(termsSpec.offset.end).map(u64Strict)] |
| 138 | + : [None, None]; |
| 139 | + |
| 140 | + if (amount.isSome() && cap.isSome() && amount.unwrap() * cap.unwrap() > u128.MAX) { |
| 141 | + throw Error('Terms overflow with amount times cap'); |
| 142 | + } |
| 143 | + |
| 144 | + terms = Some({ amount, cap, height, offset }); |
| 145 | + } |
| 146 | + |
| 147 | + etching = Some({ divisibility, premine, rune, spacers, symbol, terms }); |
| 148 | + } |
| 149 | + |
| 150 | + return new Runestone(false, mint, pointer, edicts, etching).encipher(); |
| 151 | +} |
0 commit comments