UNPKG

@unspent/phi

Version:

a collection of anyone can spend contracts

211 lines 8.33 kB
import { binToHex, hexToBin, lockingBytecodeToBase58Address, lockingBytecodeToCashAddress, } from "@bitauth/libauth"; import { Contract as CashScriptContract } from "cashscript"; import { getDefaultProvider } from "./network.js"; import { binToNumber, createOpReturnData, decodeNullDataScript, deriveLockingBytecode, deriveLockingBytecodeHex, getPrefixFromNetwork, sum } from "./util.js"; import { DELIMITER, PROTOCOL_ID, _PROTOCOL_ID } from "./constant.js"; export class BaseUtxPhiContract { constructor(network, artifact, constructorArguments) { const defaultProvider = getDefaultProvider(network); this.provider = defaultProvider; this.testnet = this.provider.network == "mainnet" ? false : true; this.artifact = artifact; this.addressType = this.artifact.compiler.version.startsWith("0.7") ? 'p2sh20' : 'p2sh32'; this.contract = new CashScriptContract(artifact, [...constructorArguments], { provider: this.provider, addressType: this.addressType }); } _refresh(constructorArguments) { this.contract = new CashScriptContract(this.artifact, [...constructorArguments], { provider: this.provider, addressType: this.addressType }); } static parseSerializedString(str, network = "mainnet") { const components = str.split(this.delimiter); // if the contract shortcode doesn't match, error const code = components.shift(); const version = parseInt(components.shift()); const lockingBytecode = components.splice(-1)[0]; const args = [...components]; const options = { version: version, network: network }; const prefix = getPrefixFromNetwork(network); const CashAddrResult = lockingBytecodeToCashAddress({ prefix: prefix, bytecode: hexToBin(lockingBytecode) }); if (typeof CashAddrResult === "string") throw Error("non-standard address" + CashAddrResult); return { code: code, options: options, args: args, lockingBytecode: lockingBytecode, address: CashAddrResult.address, }; } static parseOpReturn(opReturn, network = "mainnet") { // transform to binary if (typeof opReturn == "string") { opReturn = hexToBin(opReturn); } // decode data const components = decodeNullDataScript(opReturn); const protocol = binToHex(components.shift()); if (protocol !== PROTOCOL_ID) throw Error(`Protocol specified in OpReturn didn't match the PROTOCOL_ID: ${protocol} v ${PROTOCOL_ID}`); // if the contract shortcode doesn't match, error const code = String.fromCharCode(components.shift()[0]); const version = binToNumber(components.shift()); const lockingBytecode = components.splice(-1)[0]; const args = [...components]; const options = { version: version, network: network }; const prefix = getPrefixFromNetwork(network); const CashAddrResult = lockingBytecodeToCashAddress({ prefix: prefix, bytecode: lockingBytecode }); if (typeof CashAddrResult === "string") throw Error("non-standard address:" + CashAddrResult); return { code: code, options: options, args: args, lockingBytecode: lockingBytecode, address: CashAddrResult.address, }; } static parseOutputs(opReturn) { // transform to binary if (typeof opReturn == "string") { opReturn = hexToBin(opReturn); } // decode data const components = decodeNullDataScript(opReturn); binToHex(components.shift()); // if the contract shortcode doesn't match, error String.fromCharCode(components.shift()[0]); binToNumber(components.shift()); const lockingBytecode = components.splice(-1)[0]; const args = [...components].filter(b => b.length == 23 || b.length == 25); return [...args, lockingBytecode]; } // @ts-ignore // static async getSpendable(opReturn: Uint8Array | string, network = "mainnet", networkProvider: NetworkProvider, blockHeight?: number): Promise<number> { // throw Error("Cannot get spendable amount from base class"); // } static getExecutorAllowance(opReturn, network = "mainnet") { throw Error(`Cannot get executor allowance from base class, ${opReturn} on ${network}`); } static async getSpendableBalance(opReturn, network = "mainnet", networkProvider, blockHeight) { networkProvider; blockHeight; throw Error(`Cannot get spendable amount from base class, ${opReturn} on ${network}`); } static async getBalance(address, networkProvider) { const balance = (await networkProvider.getUtxos(address)).map(utxo => utxo.satoshis).filter((x) => x > 0).reduce(sum, 0n); return balance; } async getBalance() { const bal = await this.contract.getBalance(); return bal; } getAddress() { return this.contract.address; } getLegacyAddress() { const addr = lockingBytecodeToBase58Address(this.getLockingBytecode(false), this.testnet ? "testnet" : "mainnet"); if (typeof addr !== "string") throw addr; return addr; } async getUtxos(ageFilter) { if (ageFilter) { let utxos = await this.provider?.getUtxos(this.getAddress()); let nextHeight = await this.provider?.getBlockHeight() + 1; return utxos?.filter(u => { // @ts-ignore if (u.height <= 0) { // @ts-ignore if (ageFilter == u.height) { return true; } else { return false; } } // @ts-ignore if (u.height && nextHeight) { // @ts-ignore return ((nextHeight - u.height) >= ageFilter); } else { return false; } }); } else { return await this.provider?.getUtxos(this.getAddress()); } } getLockingBytecode(hex = true) { if (hex) return deriveLockingBytecodeHex(this.contract.address); return deriveLockingBytecode(this.contract.address); } checkLockingBytecode(lockingBytecode) { if (!lockingBytecode) throw Error("Attempted to check an empty locking bytecode"); if (typeof lockingBytecode != "string") { lockingBytecode = binToHex(lockingBytecode); } if (lockingBytecode !== this.getLockingBytecode()) throw `Deserialization resulted in different contract public key hash`; return true; } asText() { throw Error("Cannot get contract text description from base class"); } asCommand() { throw Error("Cannot get command from base class"); } asSeries() { throw Error("Cannot get contract series from base class"); } getRedeemScriptHex() { return this.contract.bytecode; } getFunction(fn) { return this.contract.functions[fn]; } isTestnet() { return this.testnet; } asOpReturn(chunks, hex) { const opReturn = createOpReturnData(chunks); if (hex) { return binToHex(opReturn); } else { return opReturn; } } async isFunded() { return (await this.getBalance()) > 0; } async info(cat = true) { const bal = await this.getBalance(); const info = `# ${this.asText()}\n` + `# ${this.toString()}\n` + `address: ${this.getAddress()}\n` + `balance: ${bal}\n`; if (cat) { console.log(info); return; } else { return info; } } } BaseUtxPhiContract.delimiter = DELIMITER; BaseUtxPhiContract._PROTOCOL_ID = _PROTOCOL_ID; BaseUtxPhiContract.PROTOCOL_ID = PROTOCOL_ID; //# sourceMappingURL=contract.js.map