feature and bug fixes
-- added option to allow unconfirmed UTXOs while creating transaction
This commit is contained in:
parent
dc0e199d58
commit
ba549b787f
17
css/main.css
17
css/main.css
@ -929,7 +929,15 @@ ol li::before {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
}
|
}
|
||||||
.transaction .pending-badge {
|
.transaction__amount {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.transaction__id {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-badge {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0.2rem 0.5rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
@ -938,13 +946,6 @@ ol li::before {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
justify-self: flex-start;
|
justify-self: flex-start;
|
||||||
}
|
}
|
||||||
.transaction__amount {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.transaction__id {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tx-participant:not(:last-of-type) {
|
.tx-participant:not(:last-of-type) {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
|
|||||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -868,15 +868,6 @@ ol {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
}
|
}
|
||||||
.pending-badge {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background-color: var(--yellow);
|
|
||||||
color: rgba(0 0 0/ 0.8);
|
|
||||||
font-weight: 500;
|
|
||||||
justify-self: flex-start;
|
|
||||||
}
|
|
||||||
&__amount {
|
&__amount {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -885,6 +876,15 @@ ol {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.pending-badge {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: var(--yellow);
|
||||||
|
color: rgba(0 0 0/ 0.8);
|
||||||
|
font-weight: 500;
|
||||||
|
justify-self: flex-start;
|
||||||
|
}
|
||||||
.tx-participant {
|
.tx-participant {
|
||||||
&:not(:last-of-type) {
|
&:not(:last-of-type) {
|
||||||
&::after {
|
&::after {
|
||||||
|
|||||||
47
index.html
47
index.html
@ -957,13 +957,16 @@
|
|||||||
|
|
||||||
function buttonLoader(id, show) {
|
function buttonLoader(id, show) {
|
||||||
const button = typeof id === 'string' ? document.getElementById(id) : id;
|
const button = typeof id === 'string' ? document.getElementById(id) : id;
|
||||||
button.disabled = show;
|
if (!button) return
|
||||||
|
if (!button.dataset.hasOwnProperty('wasDisabled'))
|
||||||
|
button.dataset.wasDisabled = button.disabled
|
||||||
const animOptions = {
|
const animOptions = {
|
||||||
duration: 200,
|
duration: 200,
|
||||||
fill: 'both',
|
fill: 'forwards',
|
||||||
easing: 'ease'
|
easing: 'ease'
|
||||||
}
|
}
|
||||||
if (show) {
|
if (show) {
|
||||||
|
button.disabled = true
|
||||||
button.parentNode.append(document.createElement('sm-spinner'))
|
button.parentNode.append(document.createElement('sm-spinner'))
|
||||||
button.animate([
|
button.animate([
|
||||||
{
|
{
|
||||||
@ -974,11 +977,22 @@
|
|||||||
},
|
},
|
||||||
], animOptions)
|
], animOptions)
|
||||||
} else {
|
} else {
|
||||||
button.getAnimations().forEach(anim => anim.cancel())
|
button.disabled = button.dataset.wasDisabled === 'true';
|
||||||
|
button.animate([
|
||||||
|
{
|
||||||
|
clipPath: 'circle(0)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clipPath: 'circle(100%)',
|
||||||
|
},
|
||||||
|
], animOptions).onfinish = (e) => {
|
||||||
|
button.removeAttribute('data-original-state')
|
||||||
|
}
|
||||||
const potentialTarget = button.parentNode.querySelector('sm-spinner')
|
const potentialTarget = button.parentNode.querySelector('sm-spinner')
|
||||||
if (potentialTarget) potentialTarget.remove();
|
if (potentialTarget) potentialTarget.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let isMobileView = false
|
let isMobileView = false
|
||||||
const mobileQuery = window.matchMedia('(max-width: 40rem)')
|
const mobileQuery = window.matchMedia('(max-width: 40rem)')
|
||||||
function handleMobileChange(e) {
|
function handleMobileChange(e) {
|
||||||
@ -1166,7 +1180,7 @@
|
|||||||
const queriedAddress = pagesData.params?.query || getRef('search_query_input').value.trim()
|
const queriedAddress = pagesData.params?.query || getRef('search_query_input').value.trim()
|
||||||
const isSender = type === 'out' || type === 'self'
|
const isSender = type === 'out' || type === 'self'
|
||||||
const className = `transaction grid ${type} ${block === null ? 'unconfirmed-tx' : ''}`
|
const className = `transaction grid ${type} ${block === null ? 'unconfirmed-tx' : ''}`
|
||||||
return html.node`
|
return html.node/*html*/`
|
||||||
<li class="${className}" data-txid="${txid}" data-transacting-addresses=${transactingAddresses.slice(2)}>
|
<li class="${className}" data-txid="${txid}" data-transacting-addresses=${transactingAddresses.slice(2)}>
|
||||||
<div class="transaction__icon">${icon}</div>
|
<div class="transaction__icon">${icon}</div>
|
||||||
<div class="grid gap-0-5">
|
<div class="grid gap-0-5">
|
||||||
@ -1185,7 +1199,7 @@
|
|||||||
</a>
|
</a>
|
||||||
${isSender && !block ? html`
|
${isSender && !block ? html`
|
||||||
<div class="multi-state-button">
|
<div class="multi-state-button">
|
||||||
<button class="button button--small gap-0-3" onclick=${initFeeChange} title="Resend transaction with greater fees to reduce confirmation time">
|
<button class="button button--small gap-0-3" onclick=${(e) => initFeeChange(e, txid)} title="Resend transaction with greater fees to reduce confirmation time">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>
|
||||||
Increase fee
|
Increase fee
|
||||||
</button>
|
</button>
|
||||||
@ -1261,6 +1275,22 @@
|
|||||||
Unconfirmed
|
Unconfirmed
|
||||||
</h4> ` : ''}
|
</h4> ` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
${!block ? html`
|
||||||
|
<div class="flex flex-direction-column gap-0-5" style="padding: 1rem;border-radius:0.5rem; border: solid thin rgba(var(--text-color),0.3); background-color: rgba(var(--text-color),0.02)">
|
||||||
|
<h3>
|
||||||
|
Taking too long to confirm?
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
You can increase the fee to speed up confirmation.
|
||||||
|
</p>
|
||||||
|
<div class="multi-state-button margin-right-auto">
|
||||||
|
<button class="button gap-0-3 button--primary" onclick=${e => initFeeChange(e, txid)} title="Resend transaction with greater fees to reduce confirmation time">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>
|
||||||
|
Increase fee
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
<div id="tx_technicals" class="justify-self-center details-wrapper">
|
<div id="tx_technicals" class="justify-self-center details-wrapper">
|
||||||
<div class="tx-detail">
|
<div class="tx-detail">
|
||||||
<div class="flex align-center gap-0-3">
|
<div class="flex align-center gap-0-3">
|
||||||
@ -1988,10 +2018,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let changingFeeOf = null
|
let changingFeeOf = null
|
||||||
async function initFeeChange(e) {
|
async function initFeeChange(e, txid) {
|
||||||
const button = e.target.closest('button')
|
const button = e.target.closest('button')
|
||||||
buttonLoader(button, true)
|
buttonLoader(button, true)
|
||||||
const txid = button.closest('li').dataset.txid
|
|
||||||
changingFeeOf = txid
|
changingFeeOf = txid
|
||||||
try {
|
try {
|
||||||
const { inputs, outputs, fee: previousFee } = await btcOperator.getTx(txid)
|
const { inputs, outputs, fee: previousFee } = await btcOperator.getTx(txid)
|
||||||
@ -2020,7 +2049,7 @@
|
|||||||
const amounts = outputs.map(output => 0.00000005)
|
const amounts = outputs.map(output => 0.00000005)
|
||||||
let recommendedFee = null
|
let recommendedFee = null
|
||||||
if (!isMultisig) {
|
if (!isMultisig) {
|
||||||
const { fee } = await btcOperator.createTx(senders, receivers, amounts)
|
const { fee } = await btcOperator.createTx(senders, receivers, amounts, null, { allowUnconfirmedUtxos: true })
|
||||||
if (fee > previousFee)
|
if (fee > previousFee)
|
||||||
recommendedFee = fee
|
recommendedFee = fee
|
||||||
}
|
}
|
||||||
@ -2076,7 +2105,7 @@
|
|||||||
document.getElementById('new_fee').querySelector('.currency-symbol').innerHTML = currencyIcons[selectedCurrency]
|
document.getElementById('new_fee').querySelector('.currency-symbol').innerHTML = currencyIcons[selectedCurrency]
|
||||||
openPopup('increase_fee_popup')
|
openPopup('increase_fee_popup')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
notify(e, 'error')
|
||||||
} finally {
|
} finally {
|
||||||
buttonLoader(button, false)
|
buttonLoader(button, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(function (EXPORTS) { //btcOperator v1.1.3d
|
(function (EXPORTS) { //btcOperator v1.1.4
|
||||||
/* BTC Crypto and API Operator */
|
/* BTC Crypto and API Operator */
|
||||||
const btcOperator = EXPORTS;
|
const btcOperator = EXPORTS;
|
||||||
|
|
||||||
@ -400,12 +400,12 @@
|
|||||||
}
|
}
|
||||||
btcOperator.validateTxParameters = validateTxParameters;
|
btcOperator.validateTxParameters = validateTxParameters;
|
||||||
|
|
||||||
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) {
|
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver, allowUnconfirmedUtxos = false) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
|
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
|
||||||
const tx = coinjs.transaction();
|
const tx = coinjs.transaction();
|
||||||
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
||||||
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos).then(result => {
|
||||||
if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change)
|
if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change)
|
||||||
tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi
|
tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi
|
||||||
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
||||||
@ -439,10 +439,10 @@
|
|||||||
}
|
}
|
||||||
btcOperator.createTransaction = createTransaction;
|
btcOperator.createTransaction = createTransaction;
|
||||||
|
|
||||||
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) {
|
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos = false) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (fee !== null) {
|
if (fee !== null) {
|
||||||
addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => {
|
addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false, { allowUnconfirmedUtxos }).then(result => {
|
||||||
result.fee = fee;
|
result.fee = fee;
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
@ -451,8 +451,8 @@
|
|||||||
let net_fee = BASE_TX_SIZE * fee_rate;
|
let net_fee = BASE_TX_SIZE * fee_rate;
|
||||||
net_fee += (output_size * fee_rate);
|
net_fee += (output_size * fee_rate);
|
||||||
(fee_from_receiver ?
|
(fee_from_receiver ?
|
||||||
addUTXOs(tx, senders, redeemScripts, total_amount, false) :
|
addUTXOs(tx, senders, redeemScripts, total_amount, false, { allowUnconfirmedUtxos }) :
|
||||||
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate)
|
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate, { allowUnconfirmedUtxos })
|
||||||
).then(result => {
|
).then(result => {
|
||||||
result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8));
|
result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8));
|
||||||
result.fee_rate = fee_rate;
|
result.fee_rate = fee_rate;
|
||||||
@ -464,7 +464,7 @@
|
|||||||
}
|
}
|
||||||
btcOperator.addInputs = addInputs;
|
btcOperator.addInputs = addInputs;
|
||||||
|
|
||||||
function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) {
|
function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = { allowUnconfirmedUtxos: false }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
required_amount = parseFloat(required_amount.toFixed(8));
|
required_amount = parseFloat(required_amount.toFixed(8));
|
||||||
if (typeof rec_args.n === "undefined") {
|
if (typeof rec_args.n === "undefined") {
|
||||||
@ -478,8 +478,9 @@
|
|||||||
input_amount: rec_args.input_amount,
|
input_amount: rec_args.input_amount,
|
||||||
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],
|
||||||
rs = redeemScripts[rec_args.n];
|
rs = redeemScripts[rec_args.n];
|
||||||
let addr_type = coinjs.addressDecode(addr).type;
|
let addr_type = coinjs.addressDecode(addr).type;
|
||||||
@ -488,7 +489,9 @@
|
|||||||
let utxos = result.unspent_outputs;
|
let utxos = result.unspent_outputs;
|
||||||
//console.debug("add-utxo", addr, rs, required_amount, utxos);
|
//console.debug("add-utxo", addr, rs, 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
|
if (utxos.length === 1 && rec_args.allowUnconfirmedUtxos) {
|
||||||
|
console.log('allowing unconfirmed utxos')
|
||||||
|
} else if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
||||||
continue;
|
continue;
|
||||||
var script;
|
var script;
|
||||||
if (!rs || !rs.length) //legacy script
|
if (!rs || !rs.length) //legacy script
|
||||||
@ -838,7 +841,9 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) {
|
btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {
|
||||||
|
allowUnconfirmedUtxos: false
|
||||||
|
}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
({
|
({
|
||||||
@ -859,7 +864,7 @@
|
|||||||
if (redeemScripts.includes(null)) //TODO: segwit
|
if (redeemScripts.includes(null)) //TODO: segwit
|
||||||
return reject("Unable to get redeem-script");
|
return reject("Unable to get redeem-script");
|
||||||
//create transaction
|
//create transaction
|
||||||
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
|
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver, options.allowUnconfirmedUtxos).then(result => {
|
||||||
result.tx_hex = result.transaction.serialize();
|
result.tx_hex = result.transaction.serialize();
|
||||||
delete result.transaction;
|
delete result.transaction;
|
||||||
resolve(result);
|
resolve(result);
|
||||||
|
|||||||
2
scripts/btcOperator.min.js
vendored
2
scripts/btcOperator.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user