@unspent/phi
Version:
a collection of anyone can spend contracts
212 lines • 7.66 kB
JavaScript
import { DefaultOptions, DUST_UTXO_THRESHOLD } from "../../common/constant.js";
import { BaseUtxPhiContract } from "../../common/contract.js";
import { binToNumber, sum, toHex, parseBigInt, binToBigInt } from "../../common/util.js";
import { artifact as v0 } from "./cash/v0.js";
import { artifact as v1 } from "./cash/v1.js";
import { artifact as v2 } from "./cash/v2.js";
export class Faucet extends BaseUtxPhiContract {
constructor(period = 1n, payout = 1000n, index = 1n, options = DefaultOptions) {
let script;
if (options.version === 2) {
script = v2;
if (payout < Faucet.minPayout)
throw Error("Payout below dust threshold");
}
else if (options.version === 1) {
script = v1;
}
else if (options.version === 0) {
script = v0;
}
else {
throw Error("Unrecognized Faucet Version");
}
super(options.network, script, [BigInt(period), BigInt(payout), BigInt(index)]);
this.period = period;
this.payout = payout;
this.index = index;
this.options = options;
this.options = options;
}
refresh() {
this._refresh([BigInt(this.period), BigInt(this.payout), BigInt(this.index)]);
}
static fromString(str, network = "mainnet") {
const p = this.parseSerializedString(str, network);
// if the contract shortcode doesn't match, error
if (!(Faucet.c == p.code))
throw "non-faucet serialized string passed to faucet constructor";
if (![0, 1, 2].includes(p.options.version))
throw Error("faucet contract version not recognized");
if (p.args.length != 3)
throw `invalid number of arguments ${p.args.length}`;
const [period, payout, index] = [...p.args.map((i) => parseBigInt(i))];
const faucet = new Faucet(period, payout, index, p.options);
faucet.checkLockingBytecode(p.lockingBytecode);
return faucet;
}
// Create a Faucet 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 (![0, 1, 2].includes(p.options.version))
throw Error(`Wrong version code passed to ${this.name} class: ${p.options.version}`);
// parse arguments
if (p.args.length != 3)
throw `invalid number of arguments ${p.args.length}`;
const [period, payout, index] = [
...p.args.map((i) => binToBigInt(i)),
];
const faucet = new Faucet(period, payout, index, p.options);
faucet.checkLockingBytecode(p.lockingBytecode);
return faucet;
}
static async getSpendableBalance(opReturn, network = "mainnet", networkProvider, blockHeight) {
const p = this.parseOpReturn(opReturn, network);
const period = binToNumber(p.args.shift());
const payout = binToNumber(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);
// pop the index to get to the payout
p.args.pop();
return binToBigInt(p.args.pop());
}
toString() {
return [
`${Faucet.c}`,
`${this.options.version}`,
`${this.period}`,
`${this.payout}`,
`${this.index}`,
`${this.getLockingBytecode()}`,
].join(Faucet.delimiter);
}
asText() {
return `A faucet paying ${this.payout} (sat), every ${this.period} blocks`;
}
asCommand() {
let chipnetFlag = this.options.network == 'mainnet' ? '' : "--chipnet ";
return `unspent faucet ${chipnetFlag} --version ${this.options.version} --address $CASHADDR --period ${this.period} --payout ${this.payout} --index ${this.index}`;
}
toOpReturn(hex = false) {
const chunks = [
Faucet._PROTOCOL_ID,
Faucet.c,
toHex(this.options.version),
toHex(this.period),
toHex(this.payout),
toHex(this.index),
"0x" + this.getLockingBytecode(true),
];
return this.asOpReturn(chunks, hex);
}
getOutputLockingBytecodes(hex = true) {
hex;
return [];
}
isSpecial() {
return false;
}
async execute(exAddress, fee, utxos, debug) {
let balance = 0n;
// Filter to inputs of sufficient age
if (!utxos)
utxos = await this.getUtxos(Number(this.period));
// If the contract is version 2 or higher, restrict to one input.
if (utxos) {
if (this.options.version >= 2 && utxos.length > 1)
utxos = utxos.slice(-1);
}
if (utxos && utxos?.length > 0) {
balance = utxos.reduce((a, b) => a + b.satoshis, 0n);
}
else {
balance = await this.getBalance();
}
if (balance == 0n) {
throw Error("No funds on contract");
}
const fn = this.getFunction(Faucet.fn);
let tx = fn();
if (utxos)
tx = tx.from(utxos);
const newPrincipal = balance - BigInt(this.payout);
const minerFee = fee ? fee : 253n;
let sendAmount = BigInt(this.payout) - minerFee;
const to = [];
// if enough remains for an additional payout
if (balance > this.payout)
to.push({
to: this.getAddress(),
amount: newPrincipal,
});
if (exAddress)
to.push({
to: exAddress,
amount: 577n,
});
const size = await tx.to(to).withAge(Number(this.period)).withoutChange().build();
if (exAddress) {
if (balance < this.payout) {
sendAmount = balance;
}
else {
sendAmount = BigInt(this.payout);
}
const minerFee = fee ? fee : BigInt(size.length) / 2n;
sendAmount -= (minerFee + 10n);
// remove the old executor amount
// replace with new fee
to.pop();
to.push({
to: exAddress,
amount: sendAmount,
});
}
tx = fn();
if (utxos)
tx = tx.from(utxos);
tx.to(to)
.withAge(Number(this.period))
.withoutChange();
let txn = "";
if (debug) {
txn = await tx.bitauthUri();
}
else {
txn = (await tx.send()).txid;
}
return txn;
}
}
Faucet.c = "F";
Faucet.fn = "drip";
Faucet.minPayout = 158n + DUST_UTXO_THRESHOLD + 10n;
//# sourceMappingURL=Faucet.js.map