Added auto fee calculation

This commit is contained in:
sairaj mote 2023-10-23 19:36:09 +05:30
parent 062c48af1b
commit 354abe7cd6
8 changed files with 4928 additions and 4759 deletions

View File

@ -237,7 +237,7 @@ sm-input {
} }
sm-spinner { sm-spinner {
--size: 1.5rem; --size: 1.3rem;
--stroke-width: 0.1rem; --stroke-width: 0.1rem;
} }
@ -617,6 +617,7 @@ ul {
text-align: center; text-align: center;
align-items: center; align-items: center;
justify-items: center; justify-items: center;
isolation: isolate;
} }
.multi-state-button > * { .multi-state-button > * {
grid-area: 1/1/2/2; grid-area: 1/1/2/2;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -217,7 +217,7 @@ sm-input {
} }
sm-spinner { sm-spinner {
--size: 1.5rem; --size: 1.3rem;
--stroke-width: 0.1rem; --stroke-width: 0.1rem;
} }
@ -576,6 +576,7 @@ ul {
text-align: center; text-align: center;
align-items: center; align-items: center;
justify-items: center; justify-items: center;
isolation: isolate;
& > * { & > * {
grid-area: 1/1/2/2; grid-area: 1/1/2/2;
} }

View File

@ -53,7 +53,7 @@
<div class="nav-item__indicator"></div> <div class="nav-item__indicator"></div>
</a> </a>
</li> </li>
<li> <li class="hidden">
<a href="#/convert" class="nav-item interactive"> <a href="#/convert" class="nav-item interactive">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
width="24px" fill="#000000"> width="24px" fill="#000000">
@ -62,7 +62,8 @@
</svg> </svg>
<span class="nav-item__title"> <span class="nav-item__title">
Convert Convert
</span></a> </span>
</a>
</li> </li>
</ul> </ul>
</nav> </nav>
@ -569,6 +570,60 @@
} }
} }
} }
let currentSubscriber = null;
/**
* @param {any} initialValue - initial value for the signal
* @param {function} [Optional] callback - function to be called when the signal changes
* @returns {array} - array containing getter and setter for the signal
* @example
* const [getCount, setCount] = $signal(0);
*/
function $signal(initialValue, callback) {
let value = initialValue;
const subscribers = new Set();
let hasCustomSubscriber = false;
function getter(subscriber) {
if (currentSubscriber) {
subscribers.add(currentSubscriber);
}
if (!hasCustomSubscriber && subscriber) {
subscribers.add(subscriber)
hasCustomSubscriber = true
}
return value;
}
function setter(newValue) {
if (newValue === value) return;
value = newValue;
for (const subscriber of subscribers) {
subscriber();
}
}
return [getter, setter];
}
/**
*
* @param {function} fn - function that will run if any of its dependent signals change
* @example
* $effect(() => {
* console.log(count());
* }
* @returns {void}
*/
async function $effect(fn) {
currentSubscriber = fn;
const result = fn();
try {
if (result instanceof Promise) {
await result;
}
} catch (e) {
console.error(e)
} finally {
currentSubscriber = null;
}
}
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
window.smCompConfig = { window.smCompConfig = {
@ -576,7 +631,7 @@
{ {
selector: '[data-btc-address]', selector: '[data-btc-address]',
customValidation: (value) => { customValidation: (value) => {
if (!value) return { isValid: false, errorText: 'Please enter a FLO address' } if (!value) return { isValid: false, errorText: 'Please enter a BTC address' }
return { return {
isValid: btcOperator.validateAddress(value), isValid: btcOperator.validateAddress(value),
errorText: `Invalid address.<br> It usually starts with "1", "3" or "bc1"` errorText: `Invalid address.<br> It usually starts with "1", "3" or "bc1"`
@ -631,8 +686,31 @@
renderHome(state) renderHome(state)
}) })
router.addRoute('home', renderHome) router.addRoute('home', renderHome)
const [suggestedFee, setSuggestedFee] = $signal(0);
const [suggestedFeeStatus, setSuggestedFeeStatus] = $signal(['idle'])
const [feeType, setFeeType] = $signal('suggested')
function renderHome(state) { function renderHome(state) {
console.log(state) $effect(() => {
let feeSection = ''
if (feeType() === 'suggested') {
const [status, message] = suggestedFeeStatus()
if (status === 'idle') {
feeSection = html` <p>*Fee will be calculated after you enter all the details</p> `
} else if (status === 'calculating') {
feeSection = html`<div class="flex align-center"> Calculating fee...<sm-spinner></sm-spinner> </div>`
} else if (status === 'error') {
feeSection = html`<p class="error">${message}</p>`
} else if (status === 'success') {
feeSection = html`
<sm-input id="fee_input" placeholder="Suggested fee" type="number" value=${suggestedFee()} step="0.00000001" min="0.0000001" readonly animate required></sm-input>
`
}
} else {
feeSection = html`
<sm-input id="fee_input" placeholder="Custom fee" type="number" step="0.00000001" min="0.0000001" error-text="Minimum fee is 0.0000001BTC" animate required>
</sm-input>
`
}
renderElem(getRef('page_container'), html` renderElem(getRef('page_container'), html`
<div class="flex flex-direction-column gap-1-5"> <div class="flex flex-direction-column gap-1-5">
<menu class="flex gap-0-5"> <menu class="flex gap-0-5">
@ -652,7 +730,7 @@
<h3> <h3>
Perform Transaction Perform Transaction
</h3> </h3>
<sm-form> <sm-form id="send_tx_form" onvalid=${calculateSuggestedFee} oninvalid=${handleInvalidForm} ?skip-submit=${feeType() === 'suggested'}>
<div class="flex flex-direction-column gap-0-5"> <div class="flex flex-direction-column gap-0-5">
<div class="flex space-between align-center"> <div class="flex space-between align-center">
<h4>Sender</h4> <h4>Sender</h4>
@ -660,7 +738,7 @@
Check balance Check balance
</button> </button>
</div> </div>
<sm-input id="private_key_input" placeholder="Sender's private key" data-private-key class="password-field" type="password" oninput=${handlePrivateKeyInput} required> <sm-input id="private_key_input" placeholder="Sender's private key" data-private-key class="password-field" type="password" oninput=${handlePrivateKeyInput} animate required>
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg> <svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg>
<label slot="right" class="interact"> <label slot="right" class="interact">
<input type="checkbox" class="hidden" autocomplete="off" readonly="" onchange="togglePrivateKeyVisibility(this)"> <input type="checkbox" class="hidden" autocomplete="off" readonly="" onchange="togglePrivateKeyVisibility(this)">
@ -675,8 +753,8 @@
<h4>Receiver</h4> <h4>Receiver</h4>
<ul id="receivers_container" class="grid gap-1"> <ul id="receivers_container" class="grid gap-1">
<li class="grid gap-0-5 receiver-wrapper"> <li class="grid gap-0-5 receiver-wrapper">
<sm-input class="receiver-address" placeholder="Receiver's address" data-btc-address required></sm-input> <sm-input class="receiver-address" placeholder="Receiver's address" data-btc-address animate required></sm-input>
<sm-input class="receiver-amount" placeholder="Amount" type="number" step="0.00000001" min="0.0000001" error-text="Amount should be grater than 0.0000001 BTC" required> <sm-input class="receiver-amount" placeholder="Amount" type="number" step="0.00000001" min="0.0000001" error-text="Amount should be grater than 0.0000001 BTC" animate required>
<div class="currency-symbol flex" slot="icon"> <div class="currency-symbol flex" slot="icon">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg> <svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg>
</div> </div>
@ -687,7 +765,16 @@
Add receiver Add receiver
</button> </button>
</div> </div>
<sm-input id="fee_input" placeholder="Fee" required></sm-input> <div class="grid gap-0-5">
<div class="flex align-center space-between gap-1">
<h4>Fee</h4>
<sm-chips onchange=${e => { setFeeType(e.target.value); calculateSuggestedFee(); }}>
<sm-chip value="suggested" selected>Suggested</sm-chip>
<sm-chip value="custom">Custom</sm-chip>
</sm-chips>
</div>
${feeSection}
</div>
<div class="multi-state-button"> <div class="multi-state-button">
<button id="send_tx_button" class="button button--primary" type="submit" disabled onclick="sendTx()">Send</button> <button id="send_tx_button" class="button button--primary" type="submit" disabled onclick="sendTx()">Send</button>
</div> </div>
@ -695,6 +782,33 @@
</sm-form> </sm-form>
</div> </div>
`) `)
})
}
async function calculateSuggestedFee() {
try {
getRef('send_tx_button').disabled = true
if (feeType() === 'custom') {
} else {
if (!getRef('send_tx_form').isFormValid) return
const { senderPrivateKey, senderAddress, receivers, receiverAddresses, receiverAmounts } = getTransactionDetails()
if (!senderPrivateKey || !senderAddress || !receiverAddresses.length || !receiverAmounts.length)
return
setSuggestedFeeStatus(['calculating'])
const { fee } = await createTx(senderPrivateKey, receiverAddresses, receiverAmounts)
setSuggestedFee(fee)
setSuggestedFeeStatus(['success'])
getRef('send_tx_button').disabled = false
}
} catch (e) {
console.error(e)
setSuggestedFeeStatus(['error', e])
}
}
function handleInvalidForm() {
setSuggestedFee(0);
setSuggestedFeeStatus(['idle']);
getRef('send_tx_button').disabled = true
} }
router.addRoute('convert', (state) => { router.addRoute('convert', (state) => {
renderElem(getRef('page_container'), html` renderElem(getRef('page_container'), html`
@ -718,20 +832,25 @@
`) `)
const { tr: { address } } = getTaprootAddress(wif) const { tr: { address } } = getTaprootAddress(wif)
btcOperator.getBalance(address).then(balance => { btcOperator.getBalance(address).then(balance => {
console.log(balance)
renderElem(getRef('sender_balance_container'), html` renderElem(getRef('sender_balance_container'), html`
<div class="grid gap-1" style="padding: 1rem; border-radius: 0.5rem; border: solid thin rgba(var(--text-color),0.3)">
<div class="grid">
<p class="label">Sender address</p>
<sm-copy value=${address}><p>${address}<p></sm-copy>
</div>
<p>
Balance: <b>${formatAmount(balance)}</b> Balance: <b>${formatAmount(balance)}</b>
</p>
</div>
`) `)
}).catch(err => { }).catch(err => {
notify(e, 'error') notify(e, 'error')
}) })
} }
function handlePrivateKeyInput(e) { function handlePrivateKeyInput(e) {
getRef('check_balance_button').disabled = !e.target.isValid
if (!e.target.isValid) { if (!e.target.isValid) {
getRef('sender_balance_container').classList.add('hidden') getRef('sender_balance_container').classList.add('hidden')
getRef('check_balance_button').disabled = true
} else {
getRef('check_balance_button').disabled = false
} }
} }
getRef('convert_to_taproot_form').addEventListener('invalid', () => { getRef('convert_to_taproot_form').addEventListener('invalid', () => {
@ -758,7 +877,7 @@
}) })
} }
function addReceiver() { function addReceiver() {
getRef('receivers_container').append(html.node` getRef('receivers_container').append(html.node/*html*/`
<div class="grid gap-0-5 receiver-wrapper"> <div class="grid gap-0-5 receiver-wrapper">
<sm-input class="receiver-address" placeholder="Receiver's address" data-btc-address <sm-input class="receiver-address" placeholder="Receiver's address" data-btc-address
required></sm-input> required></sm-input>
@ -776,8 +895,11 @@
function removeReceiver(button) { function removeReceiver(button) {
button.closest('.receiver-wrapper').remove() button.closest('.receiver-wrapper').remove()
} }
async function sendTx() { function getTransactionDetails() {
const senderPrivateKey = getRef('private_key_input').value.trim(); const senderPrivateKey = getRef('private_key_input').value.trim();
const senderAddress = getTaprootAddress(senderPrivateKey).tr.address;
if (btcOperator.validateAddress(senderAddress) !== 'bech32m')
return notify('Sender address is not a Taproot address', 'error')
const receivers = [...getRef('receivers_container').children].reduce((receivers, receiver) => { const receivers = [...getRef('receivers_container').children].reduce((receivers, receiver) => {
const receiverAddress = receiver.querySelector('.receiver-address').value.trim() const receiverAddress = receiver.querySelector('.receiver-address').value.trim()
const amount = parseFloat(receiver.querySelector('.receiver-amount').value.trim()) const amount = parseFloat(receiver.querySelector('.receiver-amount').value.trim())
@ -786,19 +908,22 @@
receivers[receiverAddress] += amount receivers[receiverAddress] += amount
return receivers return receivers
}, {}) }, {})
console.log(receivers, Object.keys(receivers), Object.values(receivers)) const receiverAddresses = Object.keys(receivers)
const fee = parseFloat(getRef('fee_input').value.trim()) const receiverAmounts = Object.values(receivers)
const senderAddress = getTaprootAddress(senderPrivateKey).tr.address const fee = parseFloat(getRef('fee_input')?.value.trim()) || 0
if (btcOperator.validateAddress(senderAddress) !== 'bech32m') return { senderPrivateKey, senderAddress, receivers, receiverAddresses, receiverAmounts, fee }
return notify('Sender address is not a Taproot address', 'error') }
async function sendTx() {
try {
const { senderPrivateKey, senderAddress, receivers, receiverAddresses, receiverAmounts, fee } = getTransactionDetails()
const confirmation = await getConfirmation('Confirm transaction', { const confirmation = await getConfirmation('Confirm transaction', {
message: html` message: html`
<div class="grid gap-1-5"> <div class="grid gap-1-5">
<div class="grid gap-0-5"> <div class="grid">
<span class="label">Sender address</span> <span class="label">Sender address</span>
<sm-copy value=${senderAddress}></sm-copy> <sm-copy value=${senderAddress}></sm-copy>
</div> </div>
<div class="grid gap-0-5"> <div class="grid">
<span class="label">Receivers</span> <span class="label">Receivers</span>
<div class="grid gap-0-3"> <div class="grid gap-0-3">
${Object.entries(receivers).map(([address, amount]) => html.node` ${Object.entries(receivers).map(([address, amount]) => html.node`
@ -809,7 +934,7 @@
`)} `)}
</div> </div>
</div> </div>
<div class="grid gap-0-5"> <div class="grid">
<span class="label">Fee</span> <span class="label">Fee</span>
<b>${formatAmount(fee)}</b> <b>${formatAmount(fee)}</b>
</div> </div>
@ -820,20 +945,19 @@
if (!confirmation) if (!confirmation)
return; return;
buttonLoader('send_tx_button', true) buttonLoader('send_tx_button', true)
createTx(senderPrivateKey, Object.keys(receivers), Object.values(receivers), fee).then(txHex => { const { txHex } = await createTx(senderPrivateKey, receiverAddresses, receiverAmounts, fee)
console.log(txHex)
btcOperator.broadcastTx(txHex).then(txid => { btcOperator.broadcastTx(txHex).then(txid => {
notify(`Transaction sent successfully. Txid: ${txid}`, 'success')
showTransactionResult(true, txid) showTransactionResult(true, txid)
}).catch(err => { }).catch(err => {
notify(err, 'error')
showTransactionResult(false, err) showTransactionResult(false, err)
}).finally(() => { }).finally(() => {
buttonLoader('send_tx_button', false) buttonLoader('send_tx_button', false)
}) })
}).catch(err => { } catch (e) {
notify(err, 'error') notify(e, 'error')
buttonLoader('send_tx_button', false) buttonLoader('send_tx_button', false)
}) }
} }
function showTransactionResult(success, result, options = {}) { function showTransactionResult(success, result, options = {}) {
let { title, description } = options let { title, description } = options
@ -889,8 +1013,20 @@
const util = {}; const util = {};
util.Sat_to_BTC = value => BigInt(parseFloat((value / SATOSHI_IN_BTC).toFixed(8))); util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
util.BTC_to_Sat = value => BigInt(parseInt(value * SATOSHI_IN_BTC)); util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
function get_fee_rate() {
return new Promise((resolve, reject) => {
fetch('https://api.blockchain.info/mempool/fees').then(response => {
if (response.ok)
response.json()
.then(result => resolve(util.Sat_to_BTC(result.regular)))
.catch(error => reject(error));
else
reject(response);
}).catch(error => reject(error))
})
}
const fetch_api = function (api, json_res = true) { const fetch_api = function (api, json_res = true) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -951,39 +1087,48 @@
* @param {array} array of amounts in BTC * @param {array} array of amounts in BTC
* @param {number} fee in BTC * @param {number} fee in BTC
*/ */
async function createTx(senderPrivateKey, receivers = [], amounts = [], fee) { async function createTx(senderPrivateKey, receivers = [], amounts = [], fee = 0) {
console.log(amounts)
return new Promise(async (resolve, reject) => {
try { try {
const { tr: { address, script } } = getTaprootAddress(senderPrivateKey) const { tr } = getTaprootAddress(senderPrivateKey)
const { address, script } = tr
const opts = {}; const opts = {};
const tx = new taproot.Transaction(opts); const tx = new taproot.Transaction(opts);
const totalAmount = amounts.reduce((total, amount) => total + amount, 0) const totalAmount = amounts.reduce((total, amount) => total + amount, 0)
const amountInSat = util.BTC_to_Sat(totalAmount) const amountInSat = util.BTC_to_Sat(totalAmount)
// check if sender has enough balance // check if sender has enough balance
btcOperator.getBalance(address).then(balance => { const senderBalance = await btcOperator.getBalance(address)
if (balance < totalAmount + fee) const feeRate = await get_fee_rate();
throw new Error(`Insufficient balance. Balance: ${balance}, Required: ${totalAmount + fee}`) let calculatedFee = 0
}).catch(err => { const { input_size } = await addUTXOs(tx, tr, [address], util.BTC_to_Sat(totalAmount), feeRate)
throw new Error(err) calculatedFee += input_size
})
await addUTXOs(tx, tr, [address], util.BTC_to_Sat(amount))
// add receivers // add receivers
receivers.forEach((receiver, i) => { receivers.forEach((receiver, i) => {
tx.addOutputAddress(receiver, util.BTC_to_Sat(amounts[i])) tx.addOutputAddress(receiver, BigInt(util.BTC_to_Sat(amounts[i])))
calculatedFee += _sizePerOutput(receiver)
}) })
calculatedFee += _sizePerOutput(address)
calculatedFee = parseFloat((calculatedFee * feeRate).toFixed(8)) // convert to sat
fee = fee || calculatedFee; // if fee is not provided, pass calculated fee
// add change address // add change address
tx.addOutputAddress(address, tx.inputAmount - amountInSat - util.BTC_to_Sat(fee)); const changeAmount = senderBalance - (totalAmount + fee)
if (changeAmount < 0)
tx.sign(privKey_arrayform, undefined, new Uint8Array(32)); return reject(`Insufficient balance. Required: ${totalAmount + fee} BTC, Available: ${util.Sat_to_BTC(senderBalance)} BTC`)
tx.addOutputAddress(address, BigInt(util.BTC_to_Sat(changeAmount)));
const privKey = coinjs.wif2privkey(senderPrivateKey).privkey;
const privKey_arrayForm = hex.decode(privKey);
tx.sign(privKey_arrayForm, undefined, new Uint8Array(32));
tx.finalize() tx.finalize()
return tx.hex resolve({ txHex: tx.hex, fee })
} catch (e) { } catch (e) {
console.error(e) reject(e)
} }
})
} }
const testTaproot = 'bc1p05whkacavgmh77pgsr7v5k4tyg28x3pqyjanfnjkzzdngqur4yks39w8zk' //remove this later function addUTXOs(tx, tr, senders = [], required_amount, fee_rate, rec_args = {}) {
function addUTXOs(tx, tr, senders = [testTaproot], required_amount, fee_rate, rec_args = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
required_amount = parseFloat(required_amount.toFixed(8)); required_amount = parseFloat(required_amount);
if (typeof rec_args.n === "undefined") { if (typeof rec_args.n === "undefined") {
rec_args.n = 0; rec_args.n = 0;
rec_args.input_size = 0; rec_args.input_size = 0;
@ -996,25 +1141,25 @@
change_amount: required_amount * -1 //required_amount will be -ve of change_amount change_amount: required_amount * -1 //required_amount will be -ve of change_amount
}); });
else if (rec_args.n >= senders.length) else if (rec_args.n >= senders.length)
return reject("Insufficient Balance"); return reject(`Insufficient Balance.`);
let addr = senders[rec_args.n]; let addr = senders[rec_args.n];
let size_per_input = _sizePerInput(addr); let size_per_input = _sizePerInput(addr);
fetch_api(`unspent?active=${addr}`).then(result => { fetch_api(`unspent?active=${addr}`).then(result => {
let utxos = result.unspent_outputs; let utxos = result.unspent_outputs;
// console.debug("add-utxo", addr, required_amount, utxos); // console.debug("add-utxo", addr, required_amount, utxos);
for (let i = 0; i < utxos.length && required_amount > 0; i++) { for (let i = 0; i < utxos.length && required_amount > 0; i++) {
if (!utxos[i].confirmations) //ignore unconfirmed utxo const { tx_output_n, value, confirmations, tx_hash_big_endian } = utxos[i];
if (!confirmations) //ignore unconfirmed utxo
continue; continue;
const { tx_hash, tx_index, value } = utxos[i];
// tx.addinput(utxos[i].tx_hash_big_endian, utxos[i].tx_output_n, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
// changes for taproot // changes for taproot
const input = { txid: tx_hash, index: tx_index, script: tr.script, amount: value } const input = { txid: tx_hash_big_endian, index: tx_output_n, script: tr.script, amount: BigInt(value) }
tx.addInput({ ...input, ...tr, witnessUtxo: { script: input.script, amount: input.amount }, }); tx.addInput({ ...input, ...tr, witnessUtxo: { script: input.script, amount: input.amount }, });
//update track values //update track values
rec_args.input_size += size_per_input; // Adjust input size calculation rec_args.input_size += size_per_input; // Adjust input size calculation
rec_args.input_amount += util.Sat_to_BTC(utxos[i].value); rec_args.input_amount += value;
required_amount -= util.Sat_to_BTC(utxos[i].value); required_amount -= value;
if (fee_rate) //automatic fee calculation (dynamic) if (fee_rate) //automatic fee calculation (dynamic)
required_amount += size_per_input * fee_rate; required_amount += size_per_input * fee_rate;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4398,7 +4398,8 @@ function checkBounds(p, value, bits, signed) {
P.checkBounds = checkBounds; P.checkBounds = checkBounds;
// Wrap stream encoder into generic encoder // Wrap stream encoder into generic encoder
function wrap(inner) { function wrap(inner) {
return __assign(__assign({}, inner), { encode: function (value) { return __assign(__assign({}, inner), {
encode: function (value) {
var w = new Writer(); var w = new Writer();
inner.encodeStream(w, value); inner.encodeStream(w, value);
return w.buffer; return w.buffer;
@ -4407,7 +4408,8 @@ function wrap(inner) {
var res = inner.decodeStream(r); var res = inner.decodeStream(r);
r.finish(); r.finish();
return res; return res;
} }); }
});
} }
P.wrap = wrap; P.wrap = wrap;
function getPath(objPath, path) { function getPath(objPath, path) {
@ -5412,7 +5414,7 @@ taproot.NETWORK = {
taproot.PRECISION = 8; taproot.PRECISION = 8;
taproot.DEFAULT_VERSION = 2; taproot.DEFAULT_VERSION = 2;
taproot.DEFAULT_LOCKTIME = 0; taproot.DEFAULT_LOCKTIME = 0;
taproot.DEFAULT_SEQUENCE = 4294967295; taproot.DEFAULT_SEQUENCE = 4294967293;
var EMPTY32 = new Uint8Array(32); var EMPTY32 = new Uint8Array(32);
// Utils // Utils
//ROHIT taproot.Decimal = 8; //ROHIT taproot.Decimal = 8;
@ -6483,9 +6485,11 @@ function taprootAddPath(tree, path) {
return __assign(__assign({}, tree), { path: path }); return __assign(__assign({}, tree), { path: path });
if (tree.type !== 'branch') if (tree.type !== 'branch')
throw new Error("taprootAddPath: wrong type=".concat(tree)); throw new Error("taprootAddPath: wrong type=".concat(tree));
return __assign(__assign({}, tree), { path: path, return __assign(__assign({}, tree), {
path: path,
// Left element has right hash in path and otherwise // Left element has right hash in path and otherwise
left: taprootAddPath(tree.left, __spreadArray([tree.right.hash], path, true)), right: taprootAddPath(tree.right, __spreadArray([tree.left.hash], path, true)) }); left: taprootAddPath(tree.left, __spreadArray([tree.right.hash], path, true)), right: taprootAddPath(tree.right, __spreadArray([tree.left.hash], path, true))
});
} }
taproot.taprootAddPath = taprootAddPath; taproot.taprootAddPath = taprootAddPath;
@ -6527,18 +6531,24 @@ function p2tr(internalPubKey, tree, network, allowUnknowOutput) {
var _a = taprootTweakPubkey(pubKey, tapMerkleRoot || P.EMPTY), tweakedPubkey = _a[0], parity = _a[1]; var _a = taprootTweakPubkey(pubKey, tapMerkleRoot || P.EMPTY), tweakedPubkey = _a[0], parity = _a[1];
var leaves; var leaves;
if (hashedTree) { if (hashedTree) {
leaves = taprootWalkTree(hashedTree).map(function (l) { return (__assign(__assign({}, l), { controlBlock: taproot.TaprootControlBlock.encode({ leaves = taprootWalkTree(hashedTree).map(function (l) {
return (__assign(__assign({}, l), {
controlBlock: taproot.TaprootControlBlock.encode({
version: (l.version || taproot.TAP_LEAF_VERSION) + +parity, version: (l.version || taproot.TAP_LEAF_VERSION) + +parity,
internalKey: l.tapInternalKey || pubKey, internalKey: l.tapInternalKey || pubKey,
merklePath: l.path, merklePath: l.path,
}) })); }); })
}));
});
} }
var tapLeafScript; var tapLeafScript;
if (leaves) { if (leaves) {
tapLeafScript = leaves.map(function (l) { return [ tapLeafScript = leaves.map(function (l) {
return [
taproot.TaprootControlBlock.decode(l.controlBlock), taproot.TaprootControlBlock.decode(l.controlBlock),
concat(l.script, new Uint8Array([l.version || taproot.TAP_LEAF_VERSION])), concat(l.script, new Uint8Array([l.version || taproot.TAP_LEAF_VERSION])),
]; }); ];
});
} }
var res = { var res = {
type: 'tr', type: 'tr',
@ -6640,10 +6650,12 @@ var p2tr_ns = function (m, pubkeys, allowSamePubkeys) {
if (allowSamePubkeys === void 0) { allowSamePubkeys = false; } if (allowSamePubkeys === void 0) { allowSamePubkeys = false; }
if (!allowSamePubkeys) if (!allowSamePubkeys)
uniqPubkey(pubkeys); uniqPubkey(pubkeys);
return combinations(m, pubkeys).map(function (i) { return ({ return combinations(m, pubkeys).map(function (i) {
return ({
type: 'tr_ns', type: 'tr_ns',
script: taproot.OutScript.encode({ type: 'tr_ns', pubkeys: i }), script: taproot.OutScript.encode({ type: 'tr_ns', pubkeys: i }),
}); }); });
});
}; };
taproot.p2tr_ns = p2tr_ns; taproot.p2tr_ns = p2tr_ns;
// Taproot public key (case of p2tr_ns) // Taproot public key (case of p2tr_ns)

9
scripts/tap_combined.min.js vendored Normal file

File diff suppressed because one or more lines are too long