Added BTC multisig transaction fee increase process
This commit is contained in:
parent
0d6c9f5fbe
commit
31d8be6aa7
@ -708,6 +708,10 @@ ol li::before {
|
||||
#prompt_popup .flex {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
#confirmation_popup sm-input,
|
||||
#prompt_popup sm-input {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.popup__header {
|
||||
position: relative;
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -725,6 +725,9 @@ ol {
|
||||
.flex {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
sm-input {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.popup__header {
|
||||
|
||||
131
index.html
131
index.html
@ -85,7 +85,7 @@
|
||||
<h4 id="prompt_title"></h4>
|
||||
<p id="prompt_message"></p>
|
||||
<sm-form>
|
||||
<sm-input id="prompt_input"></sm-input>
|
||||
<sm-input id="prompt_input" required></sm-input>
|
||||
<div class="flex align-center gap-0-5 margin-left-auto">
|
||||
<button class="button cancel-button">Cancel</button>
|
||||
<button class="button confirm-button button--primary" type="submit">OK</button>
|
||||
@ -1111,7 +1111,7 @@
|
||||
</template>
|
||||
|
||||
|
||||
<script src="scripts/components.js"></script>
|
||||
<script src="scripts/components.min.js"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
|
||||
<script>
|
||||
/*jshint esversion: 8 */
|
||||
@ -1526,15 +1526,22 @@
|
||||
}
|
||||
// displays a popup for asking user input. Use this instead of JS prompt
|
||||
function getPromptInput(title, message = '', options = {}) {
|
||||
let { placeholder = '', isPassword = false, cancelText = 'Cancel', confirmText = 'OK' } = options
|
||||
let { placeholder = '', isPassword = false, cancelText = 'Cancel', confirmText = 'OK', attributes = {} } = options
|
||||
getRef('prompt_title').innerText = title;
|
||||
getRef('prompt_message').innerText = message;
|
||||
const cancelButton = getRef('prompt_popup').querySelector('.cancel-button');
|
||||
const confirmButton = getRef('prompt_popup').querySelector('.confirm-button')
|
||||
// remove all attribute except id
|
||||
while (getRef('prompt_input').attributes.length > 0 && getRef('prompt_input').attributes[0].name !== 'id') {
|
||||
getRef('prompt_input').removeAttribute(getRef('prompt_input').attributes[0].name)
|
||||
}
|
||||
if (isPassword) {
|
||||
placeholder = 'Password'
|
||||
getRef('prompt_input').setAttribute("type", "password")
|
||||
}
|
||||
for (const attr in attributes) {
|
||||
getRef('prompt_input').setAttribute(attr, attributes[attr])
|
||||
}
|
||||
getRef('prompt_input').setAttribute("placeholder", placeholder)
|
||||
getRef('prompt_input').focusIn()
|
||||
cancelButton.textContent = cancelText;
|
||||
@ -1543,7 +1550,7 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
cancelButton.addEventListener('click', () => {
|
||||
closePopup()
|
||||
return null
|
||||
resolve(null)
|
||||
}, { once: true })
|
||||
confirmButton.addEventListener('click', () => {
|
||||
closePopup()
|
||||
@ -1633,7 +1640,13 @@
|
||||
window.addEventListener('hashchange', e => routeTo(window.location.hash))
|
||||
window.addEventListener("load", () => {
|
||||
document.body.classList.remove('hidden')
|
||||
document.querySelectorAll('sm-input[data-flo-address]').forEach(input => input.customValidation = floCrypto.validateAddr)
|
||||
document.querySelectorAll('sm-input[data-flo-address]').forEach(input => input.customValidation = (value) => {
|
||||
if (!value) return { isValid: false, errorText: 'Please enter a FLO address' }
|
||||
return {
|
||||
isValid: floCrypto.validateAddr(value),
|
||||
errorText: `Invalid FLO address.<br> It usually starts with "F"`
|
||||
}
|
||||
})
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closePopup()
|
||||
@ -2301,7 +2314,13 @@
|
||||
floGlobals.isPrivKeySecured = false;
|
||||
getRef('private_key_field').dataset.privateKey = ''
|
||||
getRef('private_key_field').setAttribute('placeholder', 'FLO/BTC private key');
|
||||
getRef('private_key_field').customValidation = floCrypto.getPubKeyHex;
|
||||
getRef('private_key_field').customValidation = (value) => {
|
||||
if (!value) return { isValid: false, errorText: 'Please enter a private key' }
|
||||
return {
|
||||
isValid: floCrypto.getPubKeyHex(value),
|
||||
errorText: `Invalid private key.<br> It's a long string of random characters usually starting with 'R'.`
|
||||
}
|
||||
};
|
||||
}
|
||||
if (!generalPages.find(page => window.location.hash.includes(page))) {
|
||||
location.hash = floGlobals.isPrivKeySecured ? '#/sign_in' : `#/landing`;
|
||||
@ -3861,6 +3880,32 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
async function initFeeIncrease(parsedTx, currentPipeID, e) {
|
||||
const button = e.target.closest('button')
|
||||
const { fee } = parsedTx;
|
||||
try {
|
||||
const newFee = await getPromptInput('Increase transaction fee', `Current fee: ${fee} BTC.\n *This will create a new group and disable current one.`, {
|
||||
confirmText: 'Increase',
|
||||
placeholder: 'New BTC transaction fee',
|
||||
attributes: {
|
||||
type: 'number',
|
||||
min: fee,
|
||||
step: '0.00000001',
|
||||
'error-text': `New fee should be greater than ${fee} BTC`,
|
||||
}
|
||||
})
|
||||
if (newFee <= fee)
|
||||
return notify('New fee should be greater than current fee', 'error')
|
||||
buttonLoader(button, true)
|
||||
const newPipeID = await messenger.editFee(parsedTx, newFee)
|
||||
highlightNewGroup(newPipeID)
|
||||
await messenger.disablePipeline(currentPipeID)
|
||||
notify('Created a new multisig group. Please collect required signs again.', 'success')
|
||||
} catch (error) {
|
||||
notify(error, 'error')
|
||||
buttonLoader(button, false)
|
||||
}
|
||||
}
|
||||
|
||||
let chatLazyLoader
|
||||
function renderMessages(floID) {
|
||||
@ -3971,21 +4016,45 @@
|
||||
}
|
||||
if (floGlobals.pipelineTxHex) {
|
||||
try {
|
||||
let details, currency
|
||||
let parsedTx, currency
|
||||
switch (messenger.pipeline[floID].model) {
|
||||
case 'flo_multisig':
|
||||
currency = 'FLO'
|
||||
details = await floBlockchainAPI.parseTransaction(floGlobals.pipelineTxHex)
|
||||
parsedTx = await floBlockchainAPI.parseTransaction(floGlobals.pipelineTxHex)
|
||||
break;
|
||||
case 'btc_multisig':
|
||||
currency = 'BTC'
|
||||
details = await btcOperator.parseTransaction(floGlobals.pipelineTxHex)
|
||||
parsedTx = await btcOperator.parseTransaction(floGlobals.pipelineTxHex)
|
||||
break;
|
||||
}
|
||||
const { inputs, outputs, fee, floData } = details
|
||||
const { inputs, outputs, fee, floData } = parsedTx
|
||||
const { s: signsDone, r: minSignsRequired, t: totalMembers } = inputs[0]?.signed || {}
|
||||
const pendingSigns = minSignsRequired - signsDone || 0;
|
||||
if ($('#transaction_details')) $('#transaction_details').remove()
|
||||
let retrySection = ''
|
||||
if (pendingSigns === 0 && !messenger.pipeline[floID].disabled) {
|
||||
switch (messenger.pipeline[floID].model) {
|
||||
case 'flo_multisig':
|
||||
retrySection = html`
|
||||
<div class="grid gap-0-5 margin-top-1">
|
||||
<strong> The transaction was not completed. Retry the transaction by clicking the button below.</strong>
|
||||
<div class="multi-state-button margin-right-auto">
|
||||
<button class="button button--primary cta" onclick="${(e) => broadcastTx(floID, e)}">Retry</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
break;
|
||||
case 'btc_multisig':
|
||||
retrySection = html`
|
||||
<div class="grid gap-0-5 margin-top-1">
|
||||
<strong> If transaction is taking too long to confirm, increase transaction fee.</strong>
|
||||
<div class="multi-state-button margin-right-auto">
|
||||
<button class="button button--primary cta" onclick="${(e) => initFeeIncrease(parsedTx, floID, e)}">Increase fee</button>
|
||||
</div>
|
||||
</div>`
|
||||
break;
|
||||
}
|
||||
}
|
||||
getRef('messages_container').prepend(html.node`
|
||||
<details id="transaction_details" class="grid gap-1 card" open>
|
||||
<summary>
|
||||
@ -4018,16 +4087,9 @@
|
||||
<div class="grid">
|
||||
<h5>FLO data</h5>
|
||||
<p>${floData}</p>
|
||||
</div>`: ''
|
||||
}
|
||||
${messenger.pipeline[floID].model === 'flo_multisig' && pendingSigns === 0 && !messenger.pipeline[floID].disabled ? html`
|
||||
<div class="grid gap-0-5 margin-top-1">
|
||||
<strong> The transaction was not completed. Retry the transaction by clicking the button below.</strong>
|
||||
<div class="multi-state-button margin-right-auto">
|
||||
<button class="button button--primary cta" onclick="${(e) => broadcastTx(floID, e)}">Retry</button>
|
||||
</div>
|
||||
</div>
|
||||
`: ''}
|
||||
${retrySection}
|
||||
</details>
|
||||
`)
|
||||
} catch (err) {
|
||||
@ -4787,12 +4849,24 @@
|
||||
if (multisigMode === 'flo') {
|
||||
getRef('receiver_container').querySelectorAll('.receiver-input').forEach(input => {
|
||||
input.setAttribute('error-text', 'Invalid FLO address')
|
||||
input.customValidation = floCrypto.validateFloID
|
||||
input.customValidation = (value) => {
|
||||
if (!value) return { isValid: false, errorText: 'Please enter a FLO address' }
|
||||
return {
|
||||
isValid: floCrypto.validateFloID(value),
|
||||
errorText: `Invalid FLO address.<br> It usually starts with "F"`
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (multisigMode === 'btc') {
|
||||
getRef('receiver_container').querySelectorAll('.receiver-input').forEach(input => {
|
||||
input.setAttribute('error-text', 'Invalid BTC address')
|
||||
input.customValidation = btcOperator.validateAddress
|
||||
input.customValidation = (value) => {
|
||||
if (!value) return { isValid: false, errorText: 'Please enter a BTC address' }
|
||||
return {
|
||||
isValid: btcOperator.validateAddr(value),
|
||||
errorText: `Invalid BTC address.<br> It usually starts with '1', '3' or 'bc1'`
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -4938,19 +5012,22 @@
|
||||
notify('New multisig group created', 'success')
|
||||
closePopup();
|
||||
getRef('send_tx').reset()
|
||||
getRef('feature_mode').firstElementChild.click();
|
||||
const createdPipelineCard = getRef('chats_list').querySelector(`[data-flo-address="${result}"]`)
|
||||
if (createdPipelineCard) {
|
||||
createdPipelineCard.scrollIntoView({ behavior: 'smooth' })
|
||||
createdPipelineCard.classList.add('highlight')
|
||||
setTimeout(_ => createdPipelineCard.classList.remove('highlight'), 2000)
|
||||
}
|
||||
highlightNewGroup(result)
|
||||
} catch (err) {
|
||||
notify(`Error intiating transaction \n ${err}`, 'error');
|
||||
} finally {
|
||||
buttonLoader('initiate_transaction', false)
|
||||
}
|
||||
}
|
||||
function highlightNewGroup(address) {
|
||||
getRef('feature_mode').firstElementChild.click();
|
||||
const createdPipelineCard = getRef('chats_list').querySelector(`[data-flo-address="${address}"]`)
|
||||
if (createdPipelineCard) {
|
||||
createdPipelineCard.scrollIntoView({ behavior: 'smooth' })
|
||||
createdPipelineCard.classList.add('highlight')
|
||||
setTimeout(_ => createdPipelineCard.classList.remove('highlight'), 2000)
|
||||
}
|
||||
}
|
||||
|
||||
function signTransaction(pipeID) {
|
||||
getConfirmation('Sign transaction', { message: 'Are you sure you want to sign this transaction?', confirmText: 'Sign' }).then(async (res) => {
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
scripts/components.min.js
vendored
Normal file
1
scripts/components.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1410,7 +1410,8 @@
|
||||
}
|
||||
|
||||
messenger.editFee = function (tx_id, new_fee, private_key, change_only = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
//? what to do about private_key param? It's not used in anywhere in the function
|
||||
//1. FIND REDEEMSCRIPT
|
||||
//2. CHANGE OUTPUT VALUES
|
||||
//3. Call modified version of MultiSig.createTx_BTC_1 where the input taken is txhex rather than senders etc
|
||||
@ -1419,8 +1420,15 @@
|
||||
|
||||
if (!Array.isArray(private_keys))
|
||||
private_keys = [private_keys];
|
||||
btcOperator.tx_fetch_for_editing(tx_id).then(tx => {
|
||||
btcOperator.parseTransaction(tx).then(tx_parsed => {
|
||||
try {
|
||||
let tx, tx_parsed;
|
||||
|
||||
if (typeof tx_id === 'string') {
|
||||
tx = await btcOperator.tx_fetch_for_editing(tx_id)
|
||||
tx_parsed = await btcOperator.parseTransaction(tx)
|
||||
} else if (tx_id.inputs) {
|
||||
tx_parsed = tx_id
|
||||
}
|
||||
if (tx_parsed.fee >= new_fee)
|
||||
return reject("Fees can only be increased");
|
||||
|
||||
@ -1538,13 +1546,10 @@
|
||||
.then(result => resolve(pipeline.id))
|
||||
.catch(error => reject(error)) //SENDRAW
|
||||
}).catch(error => reject(error)) //CREATE PIPELINE
|
||||
|
||||
|
||||
|
||||
|
||||
// resolve(tx.serialize()); //CHECK THIS -- NOT NEEDED
|
||||
}).catch(error => reject(error)) //PARSETRANSACTION
|
||||
}).catch(error => reject(error)) //TX_FETCH_FOR_EDITING
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
1
scripts/messenger.min.js
vendored
Normal file
1
scripts/messenger.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user