UNPKG

@unspent/phi

Version:

a collection of anyone can spend contracts

212 lines 7.66 kB
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