@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
222 lines (200 loc) • 7.91 kB
text/typescript
import {Buffer} from "buffer";
import {randomBytes as randomBytesNoble} from "@noble/hashes/utils";
import {sha256} from "@noble/hashes/sha2";
import {BigIntBufferUtils, ChainType} from "@atomiqlabs/base";
import {IFromBTCLNWrapper} from "../swaps/escrow_swaps/frombtc/IFromBTCLNWrapper";
import {UserError} from "../errors/UserError";
/**
* Returns a promise that rejects if the passed promise resolves to `undefined` or `null`
*
* @param promise Promise to check resolve value of
* @param msg Optional message to pass to the thrown `Error`
* @category Utilities
*/
export function throwIfUndefined<T>(promise: Promise<T | undefined>, msg?: string): Promise<T> {
return promise.then(val => {
if(val==undefined) throw new Error(msg ?? "Promise value is undefined!");
return val;
})
}
/**
* Returns a promise that resolves when any of the passed promises resolves, and rejects if all the underlying
* promises fail with an array of errors returned by the respective promises
*
* @param promises A list of promises
* @category Utilities
*/
export function promiseAny<T>(promises: Promise<T>[]): Promise<T> {
return new Promise<T>((resolve: ((val: T) => void) | null, reject) => {
let numRejected = 0;
const rejectReasons = Array(promises.length);
promises.forEach((promise, index) => {
promise.then((val) => {
if(resolve!=null) resolve(val);
resolve = null;
}).catch(err => {
rejectReasons[index] = err;
numRejected++;
if(numRejected===promises.length) {
reject(rejectReasons);
}
})
})
});
}
/**
* Maps an array to object properties using the translation function
*
* @param array
* @param translator
*/
export function mapArrayToObject<T extends string[], O>(array: T, translator: (key: T[number]) => O): {[key in T[number]]: O} {
const obj: any = {};
array.forEach((item) => {
obj[item] = translator(item);
});
return obj;
}
/**
* Maps a JS object to another JS object based on the translation function, the translation function is called for every
* property (value/key) of the old object and returns the new value of for this property
*
* @param obj
* @param translator
*/
export function objectMap<
InputObject extends {[key in string]: any},
OutputObject extends {[key in keyof InputObject]: any}
>(
obj: InputObject,
translator: <InputKey extends Extract<keyof InputObject, string>>(
value: InputObject[InputKey],
key: InputKey
) => OutputObject[InputKey]
): {[key in keyof InputObject]: OutputObject[key]} {
const resp: {[key in keyof InputObject]?: OutputObject[key]} = {};
for(let key in obj) {
resp[key] = translator(obj[key], key);
}
return resp as {[key in keyof InputObject]: OutputObject[key]};
}
/**
* Maps the entries from the map to the array using the translator function
*
* @param map
* @param translator
*/
export function mapToArray<K, V, Output>(map: Map<K, V>, translator: (key: K, value: V) => Output): Output[] {
const arr: Output[] = Array(map.size);
let pointer = 0;
for(let entry of map.entries()) {
arr[pointer++] = translator(entry[0], entry[1]);
}
return arr;
}
/**
* Creates a new abort controller that will abort if the passed abort signal aborts
*
* @param abortSignal
* @param timeoutSeconds
* @param timeoutMessage
*/
export function extendAbortController(abortSignal?: AbortSignal, timeoutSeconds?: number, timeoutMessage?: string) {
const _abortController = new AbortController();
if(abortSignal!=null) {
abortSignal.throwIfAborted();
abortSignal.onabort = () => _abortController.abort(abortSignal.reason);
}
if(timeoutSeconds!=null) {
const timeout = setTimeout(
() => _abortController.abort(new Error(timeoutMessage ?? "Timed out")),
timeoutSeconds * 1000
);
_abortController.signal.addEventListener("abort", () => clearTimeout(timeout));
}
return _abortController;
}
export function bigIntMin(a: bigint, b: bigint): bigint;
export function bigIntMin(a?: bigint, b?: bigint): bigint | undefined;
export function bigIntMin(a?: bigint, b?: bigint): bigint | undefined {
if(a==null) return b;
if(b==null) return a;
return a > b ? b : a;
}
export function bigIntMax(a: bigint, b: bigint): bigint;
export function bigIntMax(a?: bigint, b?: bigint): bigint | undefined;
export function bigIntMax(a?: bigint, b?: bigint): bigint | undefined {
if(a==null) return b;
if(b==null) return a;
return b > a ? b : a;
}
export function bigIntCompare(a: bigint, b: bigint): -1 | 0 | 1 {
return a > b ? 1 : a===b ? 0 : -1;
}
export function toBigInt(value: string): bigint;
export function toBigInt(value: undefined): undefined;
export function toBigInt(value: string | undefined): bigint | undefined {
if(value==null) return undefined;
return BigInt(value);
}
export function randomBytes(bytesLength: number): Buffer {
return Buffer.from(randomBytesNoble(bytesLength));
}
export function getTxoHash(outputScriptHex: string, value: number) {
return Buffer.from(sha256(Buffer.concat([
BigIntBufferUtils.toBuffer(BigInt(value), "le", 8),
Buffer.from(outputScriptHex, "hex")
])));
}
export function fromDecimal(amount: string, decimalCount: number) {
if (amount.includes(".")) {
const [before, after] = amount.split(".");
if (decimalCount < 0) {
return BigInt(before.substring(0, before.length + decimalCount));
}
if (after.length > decimalCount) {
//Cut the last digits
return BigInt((before === "0" ? "" : before) + after.substring(0, decimalCount));
}
return BigInt((before === "0" ? "" : before) + after.padEnd(decimalCount, "0"));
} else {
if (decimalCount < 0) {
return BigInt(amount.substring(0, amount.length + decimalCount));
} else {
return BigInt(amount + "0".repeat(decimalCount));
}
}
}
export function toDecimal(amount: bigint, decimalCount: number, cut?: boolean, displayDecimals?: number) {
if (decimalCount <= 0) {
return amount.toString(10) + "0".repeat(-decimalCount);
}
const amountStr = amount.toString(10).padStart(decimalCount + 1, "0");
const splitPoint = amountStr.length - decimalCount;
const decimalPart = amountStr.substring(splitPoint, amountStr.length);
let cutTo = decimalPart.length;
if (cut && cutTo > 0) {
for (let i = decimalPart.length - 1; i--; i >= 0) {
if (decimalPart.charAt(i) === "0") {
cutTo = i;
} else break;
}
if (cutTo === 0) cutTo = 1;
}
if (displayDecimals === 0) return amountStr.substring(0, splitPoint);
if (displayDecimals != null && cutTo > displayDecimals) cutTo = displayDecimals;
return amountStr.substring(0, splitPoint) + "." + decimalPart.substring(0, cutTo);
}
export function parseHashValueExact32Bytes(value?: Buffer | string, variableName?: string): Buffer | undefined {
let hash: Buffer | undefined;
if(typeof(value)==="string") {
if(value.length!==64)
throw new UserError(`Invalid ${variableName} length, must be exactly 64 hexadecimal characters!`)
hash = Buffer.from(value, "hex");
} else {
hash = value;
}
if(hash!=null && hash.length!==32)
throw new UserError(`Invalid ${variableName} length, must be exactly 32 bytes!`);
return hash;
}