bitverse-atomicals-js
Version:
Atomicals Javascript Library and CLI - atomicals.xyz
212 lines (211 loc) • 10.7 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.appendMintUpdateRevealScript = exports.workerPrepareCommitRevealConfig = void 0;
/**
This file was created by the user:
https://github.com/danieleth2/atomicals-js/commit/02e854cc71c0f6c6559ff35c2093dc8d526b5d72
*/
const worker_threads_1 = require("worker_threads");
const address_keypair_path_1 = require("./address-keypair-path");
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
const atomical_format_helpers_1 = require("./atomical-format-helpers");
const ecc = require("tiny-secp256k1");
const ecpair_1 = require("ecpair");
const tinysecp = require("tiny-secp256k1");
const bitcoin = require("bitcoinjs-lib");
const chalk = require("chalk");
bitcoin.initEccLib(ecc);
const bitcoinjs_lib_2 = require("bitcoinjs-lib");
(0, bitcoinjs_lib_2.initEccLib)(tinysecp);
const command_helpers_1 = require("../commands/command-helpers");
const atomical_operation_builder_1 = require("./atomical-operation-builder");
const protocol_tags_1 = require("../types/protocol-tags");
const file_utils_1 = require("./file-utils");
const ECPair = (0, ecpair_1.ECPairFactory)(tinysecp);
// This is the worker's message event listener
if (worker_threads_1.parentPort) {
worker_threads_1.parentPort.on("message", (message) => __awaiter(void 0, void 0, void 0, function* () {
// Extract parameters from the message
const { copiedData, seqStart, seqEnd, workerOptions, fundingWIF, fundingUtxo, fees, performBitworkForCommitTx, workerBitworkInfoCommit, iscriptP2TR, ihashLockP2TR, } = message;
let sequence = seqStart;
let workerPerformBitworkForCommitTx = performBitworkForCommitTx;
let scriptP2TR = iscriptP2TR;
let hashLockP2TR = ihashLockP2TR;
const fundingKeypairRaw = ECPair.fromWIF(fundingWIF);
const fundingKeypair = (0, address_keypair_path_1.getKeypairInfo)(fundingKeypairRaw);
copiedData["args"]["nonce"] = Math.floor(Math.random() * 10000000);
copiedData["args"]["time"] = Math.floor(Date.now() / 1000);
let atomPayload = new command_helpers_1.AtomicalsPayload(copiedData);
let updatedBaseCommit = (0, exports.workerPrepareCommitRevealConfig)(workerOptions.opType, fundingKeypair, atomPayload);
const tabInternalKey = Buffer.from(fundingKeypair.childNodeXOnlyPubkey);
const witnessUtxo = {
value: fundingUtxo.value,
script: Buffer.from(fundingKeypair.output, "hex"),
};
const totalInputsValue = fundingUtxo.value;
const totalOutputsValue = getOutputValueForCommit(fees);
const calculatedFee = totalInputsValue - totalOutputsValue;
let needChangeFeeOutput = false;
// In order to keep the fee-rate unchanged, we should add extra fee for the new added change output.
const expectedFee = fees.commitFeeOnly +
workerOptions.satsbyte * atomical_operation_builder_1.OUTPUT_BYTES_BASE;
// console.log('expectedFee', expectedFee);
const differenceBetweenCalculatedAndExpected = calculatedFee - expectedFee;
if (calculatedFee > 0 &&
differenceBetweenCalculatedAndExpected > 0 &&
differenceBetweenCalculatedAndExpected >= atomical_operation_builder_1.DUST_AMOUNT) {
// There were some excess satoshis, but let's verify that it meets the dust threshold to make change
needChangeFeeOutput = true;
}
let prelimTx;
let fixedOutput = {
address: updatedBaseCommit.scriptP2TR.address,
value: getOutputValueForCommit(fees),
};
let finalCopyData, finalPrelimTx, finalSequence;
// Start mining loop, terminates when a valid proof of work is found or stopped manually
do {
// Introduce a minor delay to avoid overloading the CPU
// await sleep(0);
// This worker has tried all assigned sequence range but it did not find solution.
if (sequence > seqEnd) {
finalSequence = -1;
}
if (sequence % 10000 == 0) {
console.log("Started mining for sequence: " +
sequence +
" - " +
Math.min(sequence + 10000, atomical_operation_builder_1.MAX_SEQUENCE));
}
// Create a new PSBT (Partially Signed Bitcoin Transaction)
let psbtStart = new bitcoinjs_lib_2.Psbt({ network: command_helpers_1.NETWORK });
psbtStart.setVersion(1);
// Add input and output to PSBT
psbtStart.addInput({
hash: fundingUtxo.txid,
index: fundingUtxo.index,
sequence: sequence,
tapInternalKey: tabInternalKey,
witnessUtxo: witnessUtxo,
});
psbtStart.addOutput(fixedOutput);
// Add change output if needed
if (needChangeFeeOutput) {
psbtStart.addOutput({
address: fundingKeypair.address,
value: differenceBetweenCalculatedAndExpected,
});
}
psbtStart.signInput(0, fundingKeypair.tweakedChildNode);
psbtStart.finalizeAllInputs();
// Extract the transaction and get its ID
prelimTx = psbtStart.extractTransaction();
const checkTxid = prelimTx.getId();
// Check if there is a valid proof of work
if (workerPerformBitworkForCommitTx &&
(0, atomical_format_helpers_1.hasValidBitwork)(checkTxid, workerBitworkInfoCommit === null || workerBitworkInfoCommit === void 0 ? void 0 : workerBitworkInfoCommit.prefix, workerBitworkInfoCommit === null || workerBitworkInfoCommit === void 0 ? void 0 : workerBitworkInfoCommit.ext)) {
// Valid proof of work found, log success message
console.log(chalk.green(prelimTx.getId(), ` sequence: (${sequence})`));
console.log("\nBitwork matches commit txid! ", prelimTx.getId(), `@ time: ${Math.floor(Date.now() / 1000)}`);
finalCopyData = copiedData;
finalPrelimTx = prelimTx;
finalSequence = sequence;
workerPerformBitworkForCommitTx = false;
break;
}
sequence++;
} while (workerPerformBitworkForCommitTx);
if (finalSequence && finalSequence != -1) {
// send a result or message back to the main thread
console.log("got one finalCopyData:" + JSON.stringify(finalCopyData));
console.log("got one finalPrelimTx:" + JSON.stringify(finalPrelimTx));
console.log("got one finalSequence:" + JSON.stringify(sequence));
worker_threads_1.parentPort.postMessage({
finalCopyData,
finalSequence: sequence,
});
}
}));
}
function getOutputValueForCommit(fees) {
let sum = 0;
// Note that `Additional inputs` refers to the additional inputs in a reveal tx.
return fees.revealFeePlusOutputs - sum;
}
function addCommitChangeOutputIfRequired(extraInputValue, fee, pbst, address, satsbyte) {
const totalInputsValue = extraInputValue;
const totalOutputsValue = getOutputValueForCommit(fee);
const calculatedFee = totalInputsValue - totalOutputsValue;
// It will be invalid, but at least we know we don't need to add change
if (calculatedFee <= 0) {
return;
}
// In order to keep the fee-rate unchanged, we should add extra fee for the new added change output.
const expectedFee = fee.commitFeeOnly + satsbyte * atomical_operation_builder_1.OUTPUT_BYTES_BASE;
// console.log('expectedFee', expectedFee);
const differenceBetweenCalculatedAndExpected = calculatedFee - expectedFee;
if (differenceBetweenCalculatedAndExpected <= 0) {
return;
}
// There were some excess satoshis, but let's verify that it meets the dust threshold to make change
if (differenceBetweenCalculatedAndExpected >= atomical_operation_builder_1.DUST_AMOUNT) {
pbst.addOutput({
address: address,
value: differenceBetweenCalculatedAndExpected,
});
}
}
const workerPrepareCommitRevealConfig = (opType, keypair, atomicalsPayload, log = true) => {
const revealScript = (0, exports.appendMintUpdateRevealScript)(opType, keypair, atomicalsPayload, log);
const hashscript = bitcoinjs_lib_1.script.fromASM(revealScript);
const scriptTree = {
output: hashscript,
};
const hash_lock_script = hashscript;
const hashLockRedeem = {
output: hash_lock_script,
redeemVersion: 192,
};
const buffer = Buffer.from(keypair.childNodeXOnlyPubkey);
const scriptP2TR = bitcoinjs_lib_1.payments.p2tr({
internalPubkey: buffer,
scriptTree,
network: command_helpers_1.NETWORK,
});
const hashLockP2TR = bitcoinjs_lib_1.payments.p2tr({
internalPubkey: buffer,
scriptTree,
redeem: hashLockRedeem,
network: command_helpers_1.NETWORK,
});
return {
scriptP2TR,
hashLockP2TR,
hashscript,
};
};
exports.workerPrepareCommitRevealConfig = workerPrepareCommitRevealConfig;
const appendMintUpdateRevealScript = (opType, keypair, payload, log = true) => {
let ops = `${Buffer.from(keypair.childNodeXOnlyPubkey, "utf8").toString("hex")} OP_CHECKSIG OP_0 OP_IF `;
ops += `${Buffer.from(protocol_tags_1.ATOMICALS_PROTOCOL_ENVELOPE_ID, "utf8").toString("hex")}`;
ops += ` ${Buffer.from(opType, "utf8").toString("hex")}`;
const chunks = (0, file_utils_1.chunkBuffer)(payload.cbor(), 520);
for (let chunk of chunks) {
ops += ` ${chunk.toString("hex")}`;
}
ops += ` OP_ENDIF`;
return ops;
};
exports.appendMintUpdateRevealScript = appendMintUpdateRevealScript;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}