@unspent/phi
Version:
a collection of anyone can spend contracts
211 lines • 8.33 kB
JavaScript
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