@unspent/phi
Version:
a collection of anyone can spend contracts
238 lines • 9.04 kB
JavaScript
import { binToHex, hexToBin, bigIntToBinUintLE, instantiateSha256, } from "@bitauth/libauth";
import { DefaultOptions, DUST_UTXO_THRESHOLD } from "../../common/constant.js";
import { BaseUtxPhiContract } from "../../common/contract.js";
import { toHex, getRandomIntWeak, sum, sumNumber, decodeNullDataScript, binToBigInt, } from "../../common/util.js";
import { artifact as v1 } from "./cash/v1.js";
export class Mine extends BaseUtxPhiContract {
constructor(period = 1n, payout = 5000n, difficulty = 3n, canary = binToHex(new Uint8Array(7)), options = DefaultOptions) {
let script;
if (options.version === 1) {
script = v1;
}
else {
throw Error(`Unrecognized Mine Contract Version`);
}
if (payout < Mine.minPayout)
throw Error(`Payout below minimum usable level ${Mine.minPayout}`);
super(options.network, script, [
BigInt(period),
BigInt(payout),
BigInt(difficulty),
hexToBin(canary),
]);
this.period = period;
this.payout = payout;
this.difficulty = difficulty;
this.canary = canary;
this.options = options;
this.options = options;
}
static fromString(str, network = "mainnet") {
const p = this.parseSerializedString(str, network);
// if the contract shortcode doesn't match, error
if (!(this.c == p.code))
throw `non-${this.name} serialized string passed to ${this.name} constructor`;
if (p.options.version != 1)
throw Error(`${this.name} contract version not recognized`);
if (p.args.length != 4)
throw `invalid number of arguments ${p.args.length}`;
const period = BigInt(parseInt(p.args.shift()));
const payout = BigInt(parseInt(p.args.shift()));
const difficulty = BigInt(parseInt(p.args.shift()));
const canary = p.args.shift();
const mine = new Mine(period, payout, difficulty, canary, p.options);
// check that the address is correct
mine.checkLockingBytecode(p.lockingBytecode);
return mine;
}
// Create a Mine contract from an OpReturn by building a serialized string.
static fromOpReturn(opReturn, network = "mainnet") {
const p = this.parseOpReturn(opReturn, network);
// check code
if (p.code !== this.c)
throw Error(`Wrong short code passed to ${this.name} class: ${p.code}`);
// version
if (p.options.version !== 1)
throw Error(`Wrong version code passed to ${this.name} class: ${p.options.version}`);
const period = binToBigInt(p.args.shift());
const payout = binToBigInt(p.args.shift());
const difficulty = binToBigInt(p.args.shift());
const canary = binToHex(p.args.shift());
const mine = new Mine(period, payout, difficulty, canary, p.options);
// check that the address
mine.checkLockingBytecode(p.lockingBytecode);
return mine;
}
static async getSpendableBalance(opReturn, network = "mainnet", networkProvider, blockHeight) {
const p = this.parseOpReturn(opReturn, network);
const period = binToBigInt(p.args.shift());
const payout = binToBigInt(p.args.shift());
const utxos = await networkProvider.getUtxos(p.address);
const spendableUtxos = utxos.map((u) => {
// @ts-ignore
if (u.height !== 0) {
// @ts-ignore
if (blockHeight - u.height > period) {
return u.satoshis;
}
else {
return 0n;
}
}
else {
return 0n;
}
});
const spendable = spendableUtxos.length > 0 ? spendableUtxos.reduce(sum) : 0n;
if (spendable > payout) {
return spendable;
}
else {
return 0n;
}
}
static getExecutorAllowance(opReturn, network = "mainnet") {
const p = this.parseOpReturn(opReturn, network);
return binToBigInt(p.args.at(1));
}
toString() {
return [
`${Mine.c}`,
`${this.options.version}`,
`${this.period}`,
`${this.payout}`,
`${this.difficulty}`,
`${this.canary}`,
`${this.getLockingBytecode()}`,
].join(Mine.delimiter);
}
asText() {
return `A mineable contract, with difficulty ${this.difficulty}, paying ${this.payout} (sat), every ${this.period} blocks`;
}
toOpReturn(hex = false) {
const chunks = [
Mine._PROTOCOL_ID,
Mine.c,
toHex(this.options.version),
toHex(this.period),
toHex(this.payout),
toHex(this.difficulty),
`0x${this.canary}`,
"0x" + this.getLockingBytecode(true),
];
return this.asOpReturn(chunks, hex);
}
async getNonce(verbose = false) {
let nonce = new Uint8Array([]);
let result = new Uint8Array([]);
let mined = false;
let best = 9007199254740991;
const sha256 = await instantiateSha256();
if (verbose)
console.log("mining...");
// keep mining 'til the number of zeros are reached
while (!mined) {
const nonceNumber = getRandomIntWeak(9007199254740991);
nonce = bigIntToBinUintLE(BigInt(nonceNumber));
const msg = new Uint8Array([
...hexToBin(this.getRedeemScriptHex()),
...nonce,
]);
result = sha256.hash(msg);
const newBest = result.slice(0, Number(this.difficulty)).reduce(sumNumber);
if (newBest <= best) {
best = newBest;
if (verbose)
console.log(newBest, result.slice(0, Number(this.difficulty)));
}
if (result.slice(0, Number(this.difficulty)).reduce(sumNumber) === 0)
mined = true;
}
// if the number is smaller than the space allowed, prepend it by adding zeros to the right
if (nonce.length < this.canary.length / 2) {
const zeros = this.canary.length / 2 - nonce.length;
nonce = new Uint8Array([...nonce, ...new Uint8Array(zeros)]);
}
const nonceHex = binToHex(nonce);
if (verbose)
console.log("success: ", nonceHex);
return nonceHex;
}
getOutputLockingBytecodes(hex = true) {
hex;
return [];
}
isSpecial() {
return false;
}
async execute(exAddress, fee, utxos, debug, nonce, verbose = false) {
const balance = await this.getBalance();
let fn = this.getFunction(Mine.fn);
const newPrincipal = balance - BigInt(this.payout);
const minerFee = fee ? fee : BigInt(400);
const reward = BigInt(this.payout) - minerFee;
if (!nonce) {
this.canary = await this.getNonce(verbose);
}
else {
this.canary = typeof nonce === "string" ? nonce : binToHex(nonce);
}
const nextContract = new Mine(this.period, this.payout, this.difficulty, this.canary, this.options);
const opReturn = nextContract.toOpReturn(false);
const chunks = decodeNullDataScript(opReturn).map((c) => "0x" + binToHex(c));
const to = [
{
to: nextContract.getAddress(),
amount: BigInt(newPrincipal),
},
];
if (exAddress)
to.push({
to: exAddress,
amount: BigInt(reward),
});
const canaryHex = "0x" + this.canary;
fn = this.getFunction(Mine.fn);
let tx = fn(canaryHex);
if (utxos)
tx = tx.from(utxos);
const size = await tx
.withOpReturn(chunks)
.to(to)
.withAge(Number(this.period))
.withHardcodedFee(minerFee)
.build();
if (exAddress) {
const minerFee = fee ? fee : BigInt(size.length) / 2n;
//console.log(minerFee)
const reward = BigInt(this.payout) - (minerFee + 10n);
to.pop();
to.push({
to: exAddress,
amount: reward,
});
}
// assure cluster is connected
// @ts-ignore
await this.provider?.connectCluster();
tx = fn(canaryHex);
if (utxos)
tx = tx.from(utxos);
tx.withOpReturn(chunks)
.to(to)
.withAge(Number(this.period))
.withoutChange();
let txn = "";
if (debug) {
txn = await tx.bitauthUri();
}
else {
txn = (await tx.send()).txid;
}
return txn;
}
}
Mine.c = "M";
Mine.fn = "execute";
Mine.minPayout = DUST_UTXO_THRESHOLD + 392n + 10n;
//# sourceMappingURL=Mine.js.map