@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
165 lines (151 loc) • 5.74 kB
text/typescript
import {BTC_NETWORK, isBytes, PubT, validatePubkey} from "@scure/btc-signer/utils";
import {Buffer} from "buffer";
import {Address, OutScript, Transaction} from "@scure/btc-signer";
import {CoinselectAddressTypes} from "../bitcoin/coinselect2";
import { randomBytes } from "./Utils";
export function fromOutputScript(network: BTC_NETWORK, outputScriptHex: string): string {
return Address(network).encode(OutScript.decode(Buffer.from(outputScriptHex, "hex")));
}
export function toOutputScript(network: BTC_NETWORK, address: string): Buffer {
const outputScript = Address(network).decode(address);
switch(outputScript.type) {
case "pkh":
case "sh":
case "wpkh":
case "wsh":
return Buffer.from(OutScript.encode({
type: outputScript.type,
hash: outputScript.hash
}));
case "tr":
try {
return Buffer.from(OutScript.encode({
type: "tr",
pubkey: outputScript.pubkey
}));
} catch (e: any) {
let msg = "";
if(e.name!=null) msg += ": "+e.name;
if(e.message!=null) msg += ": "+e.message;
if(typeof(e)==="string") msg += ": "+e;
msg += ", isBytes: "+isBytes(outputScript.pubkey);
try {
validatePubkey(outputScript.pubkey, PubT.schnorr)
msg += ", validatePubkey: success";
} catch (e: any) {
msg += ", validatePubkeyError: ";
if(e.name!=null) msg += ": "+e.name;
if(e.message!=null) msg += ": "+e.message;
if(typeof(e)==="string") msg += ": "+e;
}
throw new Error(msg);
}
}
throw new Error(`Unrecognized output script type: ${outputScript.type}`);
}
export function toCoinselectAddressType(outputScript: Uint8Array): CoinselectAddressTypes {
const data = OutScript.decode(outputScript);
switch(data.type) {
case "pkh":
return "p2pkh";
case "sh":
return "p2sh-p2wpkh";
case "wpkh":
return "p2wpkh"
case "wsh":
return "p2wsh"
case "tr":
return "p2tr"
}
throw new Error("Unrecognized address type!");
}
function getDummySpec(type: CoinselectAddressTypes) {
switch(type) {
case "p2pkh":
return {
type: "pkh",
hash: randomBytes(20)
} as const;
case "p2sh-p2wpkh":
return {
type: "sh",
hash: randomBytes(20)
} as const;
case "p2wpkh":
return {
type: "wpkh",
hash: randomBytes(20)
} as const;
case "p2wsh":
return {
type: "wsh",
hash: randomBytes(32)
} as const;
case "p2tr":
return {
type: "tr",
pubkey: Buffer.from("0101010101010101010101010101010101010101010101010101010101010101", "hex")
} as const;
}
throw new Error("Unrecognized address type!");
}
export function getDummyOutputScript(type: CoinselectAddressTypes): Uint8Array {
return OutScript.encode(getDummySpec(type));
}
export function getDummyAddress(network: BTC_NETWORK, type: CoinselectAddressTypes): string {
return Address(network).encode(getDummySpec(type));
}
/**
* General parsers for PSBTs, can parse hex or base64 encoded PSBTs
* @param _psbt
*/
export function parsePsbtTransaction(_psbt: Transaction | string): Transaction {
if (typeof (_psbt) === "string") {
let rawPsbt: Buffer;
if (/^(?:[0-9a-fA-F]{2})+$/.test(_psbt)) {
//Hex
rawPsbt = Buffer.from(_psbt, "hex");
} else if (/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(_psbt)) {
//Base64
rawPsbt = Buffer.from(_psbt, "base64");
} else {
throw new Error("Provided psbt string not base64 nor hex encoded!");
}
return Transaction.fromPSBT(rawPsbt, {
allowUnknownOutputs: true,
allowUnknownInputs: true,
allowLegacyWitnessUtxo: true,
});
} else {
return _psbt;
}
}
export function getVoutIndex(psbt: Transaction, network: BTC_NETWORK, address: string, amount: bigint): number | undefined {
const script = toOutputScript(network, address);
for(let i=0;i<psbt.outputsLength;i++) {
const output = psbt.getOutput(i);
if(
output.amount===amount &&
output.script!=null &&
script.equals(Buffer.from(output.script))
) {
return i;
}
}
}
export function getSenderAddress(psbt: Transaction, network: BTC_NETWORK, inputIndex: number = 0): string | undefined {
if(psbt.inputsLength<=inputIndex) return undefined;
const input = psbt.getInput(inputIndex);
let script: Uint8Array | undefined;
if(input.witnessUtxo?.script!=null) {
script = input.witnessUtxo.script as Uint8Array;
} else if(input.nonWitnessUtxo!=null && input.index!=null) {
script = input.nonWitnessUtxo.outputs[input.index]?.script;
}
if(script==null) return undefined;
try {
return Address(network).encode(OutScript.decode(script));
} catch (e) {
return Buffer.from(script).toString("hex");
}
}