Compare commits

..

No commits in common. "7b56defa5c01aa50c17f0a8d0be10c76d8b323be" and "68aaf56fde201c0a3aabe28e2e56be3c7bade22f" have entirely different histories.

9 changed files with 298 additions and 681 deletions

View File

@ -966,8 +966,8 @@ ol li::before {
#background_image {
z-index: -2;
-o-object-fit: fill;
object-fit: fill;
-o-object-fit: cover;
object-fit: cover;
filter: blur(var(--blur, 1rem));
transform: scale(calc(1 + var(--scale, 1.1) / 10));
}
@ -2157,6 +2157,9 @@ sm-chip .badge {
padding-bottom: 6rem;
}
.has-bg-image .received::after {
border-color: transparent rgba(var(--foreground-color), 0.6) transparent transparent;
}
.has-bg-image #emoji_picker {
--background: rgba(var(--foreground-color), 0.6);
}

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -973,9 +973,10 @@ ol {
#background_image {
z-index: -2;
object-fit:fill;
object-fit: cover;
filter: blur(var(--blur, 1rem));
transform: scale(calc(1 + (var(--scale, 1.1) / 10)));
&[src=""],
&:not([src]) {
display: none;
@ -2205,6 +2206,11 @@ sm-chip {
}
.has-bg-image {
.received::after {
border-color: transparent rgba(var(--foreground-color), 0.6) transparent
transparent;
}
#emoji_picker {
--background: rgba(var(--foreground-color), 0.6);
}

View File

@ -29,44 +29,15 @@
<script src="scripts/messengerEthereum.js"></script>
<script src="scripts/messenger.min.js"></script>
<script id="onLoadStartUp">
function canConnectToCloud() {
const nodes = Object.values(floCloudAPI.nodes);
// check if any node is online with websocket
return new Promise((resolve, reject) => {
let online = false;
let promises = [];
nodes.forEach(node => {
promises.push(new Promise((resolve, reject) => {
let ws = new WebSocket('wss://' + node.uri);
ws.onopen = () => {
online = true;
resolve(true);
ws.close();
}
ws.onerror = () => {
resolve(false);
}
}))
})
Promise.all(promises).then(results => {
resolve(online);
})
})
}
async function onLoadStartUp() {
function onLoadStartUp() {
routeTo('loading')
document.body.classList.remove('hidden')
floDapps.setCustomPrivKeyInput(getSignedIn)
getRef('emoji_picker').shadowRoot.append(style);
//invoke the startup functions
try {
const result = await floDapps.launchStartUp();
floDapps.launchStartUp().then(result => {
console.log(result)
if (!await canConnectToCloud()) {
document.body.prepend(document.createElement('adblocker-warning'))
return
}
floGlobals.myFloID = floCrypto.getFloID(floDapps.user.public);
floGlobals.myBtcID = btcOperator.convert.legacy2bech(floGlobals.myFloID)
floGlobals.myEthID = floEthereum.ethAddressFromCompressedPublicKey(floDapps.user.public)
@ -97,9 +68,7 @@
notify(error, "error")
}
})
} catch (e) {
notify(error, "error")
}
}).catch(error => notify(error, "error"))
}
</script>
</head>
@ -801,7 +770,7 @@
</header>
<div class="scrolling-wrapper grid gap-1-5">
<div class="grid gap-1">
<sm-input id="search_contacts" type="search" placeholder="Search name or address"> </sm-input>
<sm-input id="search_contacts" type="search" placeholder="Search"> </sm-input>
<fieldset id="all_contacts_options" class="flex align-center flex-wrap gap-0-5">
<button id="create_group_option" class="interactive button button--colored button--small gap-0-5"
onclick="openCreationPopup('group')">
@ -831,7 +800,7 @@
<div id="contacts_container" class="observe-empty-state"></div>
<div class="empty-state">
<h4 class="margin-bottom-0-5">No saved contacts</h4>
<p>Use 'Add contact' to add new FLO/BTC/ETH address as a contact</p>
<p>Use 'Add contact' to add new FLO/BTC/ETH address as a contact.</p>
</div>
</div>
</div>
@ -1442,6 +1411,11 @@
isRemovingMember = false
break
case 'new_message_popup':
if (Object.keys(floGlobals.contacts).length) {
getRef('search_contacts').classList.remove('hidden')
} else {
getRef('search_contacts').classList.add('hidden')
}
renderContactList()
break
case 'creation_popup': {
@ -2495,9 +2469,8 @@
`
},
contactCard(contactAddress, options = {}) {
let { type, prepend = false, markUnread = false, ref, name } = options
if (!name)
name = getContactName(contactAddress)
let { type, prepend = false, markUnread = false, ref } = options
let name = getContactName(contactAddress)
let initial
if (type === 'group') {
initial = html`<svg class="icon group-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M13.61,28.09c-1.63,0-4.72-2.35-5.33-3.58a21.65,21.65,0,0,1-1.35-7.32s-.26-6.07,6.68-6.07a6.38,6.38,0,0,1,6.69,6.07A21.65,21.65,0,0,1,19,24.51c-.62,1.23-3.7,3.58-5.34,3.58"/><path d="M50.39,28.09c-1.64,0-4.72-2.35-5.34-3.58a21.9,21.9,0,0,1-1.35-7.32s-.26-6.07,6.69-6.07a6.37,6.37,0,0,1,6.68,6.07,21.65,21.65,0,0,1-1.35,7.32c-.61,1.23-3.7,3.58-5.33,3.58"/><path d="M32,31.74c-2.21,0-6.37-3.17-7.2-4.83A29.3,29.3,0,0,1,23,17s-.35-8.21,9-8.21c8.68,0,9,8.21,9,8.21a29.3,29.3,0,0,1-1.83,9.88c-.82,1.66-5,4.83-7.2,4.83"/><path d="M48.29,38.58c-4.16-1.83-8.57-3.08-10.34-6.4a12,12,0,0,1-6,3.73,12,12,0,0,1-5.95-3.73c-1.77,3.32-6.18,4.57-10.34,6.4-1.7.71-3.11,9.88-1.13,9.88A33.06,33.06,0,0,0,31.23,53h1.54a33.06,33.06,0,0,0,16.65-4.53C51.4,48.46,50,39.29,48.29,38.58Z"/><path d="M14.82,36.57c.76-.33,1.54-.65,2.3-1,2.49-1,4.85-2,6.22-3.44C21.07,31.23,19,30.25,18,28.41a8.83,8.83,0,0,1-4.41,2.76,8.83,8.83,0,0,1-4.4-2.76c-1.31,2.46-4.58,3.38-7.66,4.74-1.26.52-2.3,7.31-.84,7.31a24.55,24.55,0,0,0,10.86,3.31C11.89,40.81,12.86,37.39,14.82,36.57Z"/><path d="M62.45,33.15c-3.08-1.36-6.35-2.28-7.66-4.74a8.83,8.83,0,0,1-4.4,2.76A8.83,8.83,0,0,1,46,28.41c-1,1.84-3,2.82-5.32,3.76,1.37,1.43,3.73,2.41,6.22,3.44.76.31,1.54.63,2.26,1,2,.83,3,4.25,3.29,7.21a24.55,24.55,0,0,0,10.86-3.31C64.75,40.46,63.71,33.67,62.45,33.15Z"/></svg> `
@ -3299,19 +3272,12 @@
getRef('search_contacts').addEventListener('input', function () {
const contacts = {}
const searchQuery = this.value.trim()
for (contact in floGlobals.contacts) {
if (contact.toLowerCase().includes(searchQuery.toLowerCase()) || floGlobals.contacts[contact].toLowerCase().includes(searchQuery.toLowerCase())) {
if (contact.toLowerCase().includes(this.value.toLowerCase()) || floGlobals.contacts[contact].toLowerCase().includes(this.value.toLowerCase())) {
contacts[contact] = floGlobals.contacts[contact]
}
}
if (Object.keys(contacts).length) {
renderContactList(contacts)
} else if (floCrypto.validateAddr(searchQuery) || validateEthAddress(searchQuery)) {
renderElem(getRef('contacts_container'), render.contactCard(searchQuery, {
type: 'contact', ref: getRef('contacts_container'), name: 'Start a chat with'
}))
}
renderContactList(contacts)
})
document.addEventListener('click', e => {
// detect click outside emoji panel and emoji button
@ -3622,13 +3588,7 @@
getRef('emoji_picker').addEventListener('emoji-click', e => {
const clickedEmoji = e.detail.unicode
// append emoji to input field where cursor was
const cursorPosition = getRef('type_message').textarea.selectionStart;
const textBeforeCursor = getRef('type_message').value.substring(0, cursorPosition)
const textAfterCursor = getRef('type_message').value.substring(cursorPosition)
getRef('type_message').value = textBeforeCursor + clickedEmoji + textAfterCursor
getRef('type_message').textarea.selectionStart = cursorPosition + clickedEmoji.length
getRef('type_message').textarea.selectionEnd = cursorPosition + clickedEmoji.length
getRef('type_message').value += clickedEmoji
if (!floGlobals.isMobileView) {
setTimeout(() => {
getRef('type_message').focusIn()
@ -4451,10 +4411,7 @@
}
}
function handleGroupDescriptionChange(e) {
const description = e.detail.value.trim();
if (description === '') return notify('Description cannot be empty', 'error')
if (description.length > 100) return notify('Description cannot be more than 100 characters', 'error')
rmMessenger.changeGroupDescription(floGlobals.viewingDetailsOfAddress, description)
rmMessenger.changeGroupDescription(floGlobals.viewingDetailsOfAddress, e.detail.value.trim())
.then(res => {
notify('Changed group description', 'success')
})
@ -4671,12 +4628,9 @@
document.body.style.setProperty('--accent-color', color);
})
getRef('select_bg_image').addEventListener('change', function (e) {
if (!this.files.length) return
Promise.all([
compactIDB.writeData('userSettings', true, 'hasSelectedBg'),
compactIDB.writeData('userSettings', this.files[0], 'bgImage')
])
getRef('select_bg_image').addEventListener('change', async function (e) {
await compactIDB.writeData('userSettings', true, 'hasSelectedBg')
compactIDB.writeData('userSettings', this.files[0], 'bgImage')
.then(async res => {
setBgImage()
notify('Background applied', 'success')
@ -4700,12 +4654,12 @@
})
async function setBgImage() {
try {
const { hasSelectedBg = false, bgImage, bgOpacity, bgBlur } = await compactIDB.readAllData('userSettings')
if (bgImage) {
const url = URL.createObjectURL(bgImage)
const hasSelectedBg = await compactIDB.readData('userSettings', 'hasSelectedBg')
const image = await compactIDB.readData('userSettings', 'bgImage')
if (image) {
const url = URL.createObjectURL(image)
if (hasSelectedBg) {
getRef('background_image').src = url
getRef('selected_bg_preview').click()
addClass(['#chat_view', '#mail', '#chat_preview'], 'has-bg-image')
getRef('select_bg_button').textContent = 'Change background'
getRef('selected_bg_preview').classList.add('bg-preview--selected')
@ -4717,6 +4671,7 @@
getRef('selected_bg_preview').classList.remove('hidden')
getRef('selected_bg_preview').querySelector('img').src = url
}
const [bgOpacity, bgBlur] = await Promise.all([compactIDB.readData('userSettings', 'bgOpacity'), compactIDB.readData('userSettings', 'bgBlur')])
if (bgOpacity !== undefined) {
getRef('backdrop_opacity').value = bgOpacity * 100
getRef('backdrop_opacity_value').value = `${parseInt(bgOpacity * 100)}%`
@ -4727,6 +4682,7 @@
getRef('backdrop_blur_value').value = `${parseInt(bgBlur * 100)}%`
getRef('background_image').style.setProperty('--blur', `${bgBlur}rem`)
getRef('background_image').style.setProperty('--scale', bgBlur)
}
}
catch (err) {
@ -5282,4 +5238,4 @@
</script>
</body>
</html>
</html>

View File

@ -1,371 +1,19 @@
(function (EXPORTS) { //btcOperator v1.2.5
(function (EXPORTS) { //btcOperator v1.1.3c
/* BTC Crypto and API Operator */
const btcOperator = EXPORTS;
const SATOSHI_IN_BTC = 1e8;
const util = btcOperator.util = {};
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
const checkIfTor = btcOperator.checkIfTor = () => {
return fetch('https://check.torproject.org/api/ip', {
mode: 'no-cors'
})
.then(response => response.json())
.then(result => result.IsTor)
.catch(error => false)
}
let isTor = false;
checkIfTor().then(result => isTor = result);
// NOTE: some APIs may not support all functions properly hence they are omitted
const APIs = btcOperator.APIs = [
{
url: 'https://api.blockcypher.com/v1/btc/main/',
name: 'Blockcypher',
balance({ addr }) {
return fetch_api(`addrs/${addr}/balance`, { url: this.url })
.then(result => util.Sat_to_BTC(result.balance))
},
async block({ id }) {
try {
let block = await fetch_api(`blocks/${id}`, { url: this.url })
return formatBlock(block)
} catch (e) {
console.log(e)
}
},
broadcast({ rawTxHex, url }) {
return fetch(`${this.url}txs/push`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ tx: rawTxHex })
}).then(response => response.json())
.then(result => result.hash)
}
},
{
url: 'https://blockstream.info/api/',
name: 'Blockstream',
hasOnion: true,
onionUrl: `http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/api/`,
balance({ addr, url }) {
return fetch_api(`address/${addr}/utxo`, { url: url || this.url })
.then(result => {
const balance = result.reduce((t, u) => t + u.value, 0)
return util.Sat_to_BTC(balance)
})
},
// tx({ txid, url }) {
// return fetch_api(`tx/${txid}`, { url: url || this.url })
// .then(result => formatTx(result))
// },
// txHex({ txid, url }) {
// return fetch_api(`tx/${txid}/hex`, { url: url || this.url, asText: true })
// },
// txs({ addr, before, after, url }) {
// return fetch_api(`address/${addr}/txs${before ? `?before=${before}` : ''}${after ? `?after=${after}` : ''}`, { url: url || this.url })
// },
async block({ id, url }) {
// if id is hex string then it is block hash
try {
let blockHash = id
if (!/^[0-9a-f]{64}$/i.test(id))
blockHash = await fetch_api(`block-height/${id}`, { url: url || this.url, asText: true })
const block = await fetch_api(`block/${blockHash}`, { url: url || this.url })
return formatBlock(block)
} catch (e) {
console.error(e)
}
},
broadcast({ rawTxHex, url }) {
return fetch(`${url || this.url}tx`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ tx: rawTxHex })
}).then(response => response.text())
}
},
{
url: 'https://mempool.space/api/',
name: 'Mempool',
balance({ addr }) {
return fetch_api(`address/${addr}`, { url: this.url })
.then(result => util.Sat_to_BTC(result.chain_stats.funded_txo_sum - result.chain_stats.spent_txo_sum))
},
tx({ txid }) {
return fetch_api(`tx/${txid}`, { url: this.url })
.then(result => formatTx(result))
},
txHex({ txid }) {
return fetch_api(`tx/${txid}/hex`, { url: this.url, asText: true })
},
txs({ addr, before, after }) {
return fetch_api(`address/${addr}/txs${before ? `?before=${before}` : ''}${after ? `?after=${after}` : ''}`, { url: this.url })
},
async block({ id }) {
// if id is hex string then it is block hash
try {
let blockHash = id
if (!/^[0-9a-f]{64}$/i.test(id))
blockHash = await fetch_api(`block-height/${id}`, { url: this.url, asText: true })
const block = await fetch_api(`block/${blockHash}`, { url: this.url })
return formatBlock(block)
} catch (e) {
console.error(e)
}
},
broadcast({ rawTxHex, url }) {
return fetch(`${this.url}tx`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ tx: rawTxHex })
}).then(response => response.text())
}
},
{
url: 'https://blockchain.info/',
name: 'Blockchain',
balance({ addr }) {
return fetch_api(`q/addressbalance/${addr}`, { url: this.url })
.then(result => util.Sat_to_BTC(result))
},
unspent({ addr, allowUnconfirmedUtxos = false }) {
return fetch_api(`unspent?active=${addr}`, { url: this.url })
.then(result => formatUtxos(result.unspent_outputs, allowUnconfirmedUtxos))
},
tx({ txid }) {
return fetch_api(`rawtx/${txid}`, { url: this.url })
.then(result => formatTx(result))
},
txHex({ txid }) {
return fetch_api(`rawtx/${txid}?format=hex`, { url: this.url, asText: true })
},
txs({ addr, before, after }) {
return fetch_api(`rawaddr/${addr}${before ? `?before=${before}` : ''}${after ? `?after=${after}` : ''}`, { url: this.url })
.then(result => result.txs)
},
async block({ id }) {
try {
let block
// if id is hex string then it is block hash
if (/^[0-9a-f]{64}$/i.test(id))
block = await fetch_api(`rawblock/${id}`, { url: this.url })
else {
const result = await fetch_api(`block-height/${id}?format=json`, { url: this.url })
block = result.blocks[0]
}
return formatBlock(block)
} catch (e) {
console.error(e)
}
},
async blockTxs({ id }) {
try {
let block
// if id is hex string then it is block hash
if (/^[0-9a-f]{64}$/i.test(id))
block = await fetch_api(`rawblock/${id}`, { url: this.url })
else {
const result = await fetch_api(`block-height/${id}?format=json`, { url: this.url })
block = result.blocks[0]
}
return block.tx
} catch (e) {
console.error(e)
}
}
}
]
btcOperator.util.format = {} // functions to homogenize API results
const formatBlock = btcOperator.util.format.block = async (block) => {
try {
const { height, hash, id, time, timestamp, mrkl_root, merkle_root, prev_block, next_block, size } = block;
const details = {
height,
hash: hash || id,
time: (time || timestamp) * 1000,
merkle_root: merkle_root || mrkl_root,
size,
}
if (prev_block)
details.prev_block = prev_block
if (next_block)
details.next_block = next_block[0]
return details
} catch (e) {
console.error(e)
}
}
const formatUtxos = btcOperator.util.format.utxos = async (utxos, allowUnconfirmedUtxos = false) => {
try {
if (!allowUnconfirmedUtxos && !utxos || !Array.isArray(utxos))
throw {
message: "No utxos found",
code: 1000 //error code for when issue is not from API but situational (like no utxos found)
}
return utxos.map(utxo => {
const { tx_hash, tx_hash_big_endian, txid, tx_output_n, vout, value, script, confirmations, status: { confirmed } = {} } = utxo;
return {
confirmations: confirmations || confirmed,
tx_hash_big_endian: tx_hash_big_endian || tx_hash || txid,
tx_output_n: tx_output_n || vout,
value,
script
}
})
} catch (e) {
throw e
}
}
const formatTx = btcOperator.util.format.tx = async (tx) => {
try {
const { txid, hash, time, block_height, fee, fees, received,
confirmed, size, double_spend, block_hash, confirmations,
status: { block_height: statusBlockHeight, block_hash: statusBlockHash, block_time } = {}
} = tx;
const inputs = tx.vin || tx.inputs;
const outputs = tx.vout || tx.outputs || tx.out;
return {
hash: hash || txid,
size: size,
fee: fee || fees,
double_spend,
time: (time * 1000) || new Date(confirmed || received).getTime() || block_time * 1000 || Date.now(),
block_height: block_height || statusBlockHeight,
block_hash: block_hash || statusBlockHash,
confirmations,
inputs: inputs.map(input => {
return {
index: input.n || input.output_index || input.vout,
prev_out: {
addr: input.prev_out?.addr || input.addresses?.[0] || input.prev_out?.address || input.addr || input.prevout.scriptpubkey_address,
value: input.prev_out?.value || input.output_value || input.prevout.value,
},
}
}),
out: outputs.map(output => {
return {
addr: output.scriptpubkey_address || output.addresses?.[0] || output.scriptpubkey_address || output.addr,
value: output.value || output.scriptpubkey_value,
}
})
}
} catch (e) {
console.error(e)
}
}
const multiApi = btcOperator.multiApi = async (fnName, { index = 0, ...args } = {}) => {
try {
let triedOnion = false;
while (index < APIs.length) {
if (!APIs[index][fnName] || (APIs[index].coolDownTime && APIs[index].coolDownTime > new Date().getTime())) {
index += 1;
continue;
}
return await APIs[index][fnName](args);
}
if (isTor && !triedOnion) {
triedOnion = true;
index = 0;
while (index < APIs.length) {
if (!APIs[index].hasOnion || (APIs[index].coolDownTime && APIs[index].coolDownTime > new Date().getTime())) {
index += 1;
continue;
}
return await multiApi(fnName, { index: index + 1, ...args, url: APIs[index].onionUrl });
}
}
throw "No API available"
} catch (error) {
if (error.code && error.code === 1000) {
throw error.message;
} else {
console.debug(error);
APIs[index].coolDownTime = new Date().getTime() + 1000 * 60 * 10; // 10 minutes
return multiApi(fnName, { index: index + 1, ...args });
}
}
};
function parseTx(tx, addressOfTx) {
const { txid, hash, time, block_height, inputs, outputs, out, vin, vout, fee, fees, received, confirmed, status: { block_height: statusBlockHeight, block_time } = {} } = tx;
let parsedTx = {
txid: hash || txid,
time: (time * 1000) || new Date(confirmed || received).getTime() || block_time * 1000 || Date.now(),
block: block_height || statusBlockHeight,
}
//sender list
parsedTx.tx_senders = {};
(inputs || vin).forEach(i => {
const address = i.prev_out?.addr || i.addresses?.[0] || i.prev_out?.address || i.addr || i.prevout.scriptpubkey_address;
const value = i.prev_out?.value || i.output_value || i.value || i.prevout.value;
if (address in parsedTx.tx_senders)
parsedTx.tx_senders[address] += value;
else parsedTx.tx_senders[address] = value;
});
parsedTx.tx_input_value = 0;
for (let senderAddr in parsedTx.tx_senders) {
let val = parsedTx.tx_senders[senderAddr];
parsedTx.tx_senders[senderAddr] = util.Sat_to_BTC(val);
parsedTx.tx_input_value += val;
}
parsedTx.tx_input_value = util.Sat_to_BTC(parsedTx.tx_input_value);
//receiver list
parsedTx.tx_receivers = {};
(outputs || out || vout).forEach(o => {
const address = o.scriptpubkey_address || o.addresses?.[0] || o.scriptpubkey_address || o.addr;
const value = o.value || o.scriptpubkey_value;
if (address in parsedTx.tx_receivers)
parsedTx.tx_receivers[address] += value;
else parsedTx.tx_receivers[address] = value;
});
parsedTx.tx_output_value = 0;
for (let receiverAddr in parsedTx.tx_receivers) {
let val = parsedTx.tx_receivers[receiverAddr];
parsedTx.tx_receivers[receiverAddr] = util.Sat_to_BTC(val);
parsedTx.tx_output_value += val;
}
parsedTx.tx_output_value = util.Sat_to_BTC(parsedTx.tx_output_value);
// tx fee
parsedTx.tx_fee = util.Sat_to_BTC(fee || fees || (parsedTx.tx_input_value - parsedTx.tx_output_value));
//detect tx type (in, out, self)
if (Object.keys(parsedTx.tx_receivers).length === 1 && Object.keys(parsedTx.tx_senders).length === 1 && Object.keys(parsedTx.tx_senders)[0] === Object.keys(parsedTx.tx_receivers)[0]) {
parsedTx.type = 'self';
parsedTx.amount = parsedTx.tx_receivers[addressOfTx];
parsedTx.address = addressOfTx;
} else if (addressOfTx in parsedTx.tx_senders && Object.keys(parsedTx.tx_receivers).some(addr => addr !== addressOfTx)) {
parsedTx.type = 'out';
parsedTx.receiver = Object.keys(parsedTx.tx_receivers).filter(addr => addr != addressOfTx);
parsedTx.amount = parsedTx.receiver.reduce((t, addr) => t + parsedTx.tx_receivers[addr], 0) + parsedTx.tx_fee;
} else {
parsedTx.type = 'in';
parsedTx.sender = Object.keys(parsedTx.tx_senders).filter(addr => addr != addressOfTx);
parsedTx.amount = parsedTx.tx_receivers[addressOfTx];
}
return parsedTx;
}
//This library uses API provided by chain.so (https://chain.so/)
const URL = "https://blockchain.info/";
const DUST_AMT = 546,
MIN_FEE_UPDATE = 219;
const fetch_api = btcOperator.fetch = function (api, { asText = false, url = 'https://blockchain.info/' } = {}) {
const fetch_api = btcOperator.fetch = function (api, json_res = true) {
return new Promise((resolve, reject) => {
console.debug(url + api);
fetch(url + api).then(response => {
console.debug(URL + api);
fetch(URL + api).then(response => {
if (response.ok) {
(asText ? response.text() : response.json())
(json_res ? response.json() : response.text())
.then(result => resolve(result))
.catch(error => reject(error))
} else {
@ -377,6 +25,13 @@
})
};
const SATOSHI_IN_BTC = 1e8;
const util = btcOperator.util = {};
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
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 => {
@ -391,7 +46,6 @@
}
const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => {
console.log('txHex:', rawTxHex)
let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction';
fetch(url, {
method: 'POST',
@ -400,7 +54,6 @@
},
body: "rawtx=" + rawTxHex
}).then(response => {
// multiApi('broadcast', { rawTxHex }).then(response => {
response.text().then(resultText => {
let r = resultText.match(/<result>.*<\/result>/);
if (!r)
@ -411,12 +64,7 @@
let txid = resultText.match(/<txid>.*<\/txid>/).pop().replace('<txid>', '').replace('</txid>', '');
resolve(txid);
} else if (r == '0') {
let error
if (resultText.includes('<message>')) {
error = resultText.match(/<message>.*<\/message>/).pop().replace('<message>', '').replace('</message>', '');
} else {
error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
}
let error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
reject(decodeURIComponent(error.replace(/\+/g, " ")));
} else reject(resultText);
}
@ -619,11 +267,10 @@
}
//BTC blockchain APIs
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
if (!validateAddress(addr))
return reject("Invalid address");
multiApi('balance', { addr })
.then(result => resolve(result))
fetch_api(`q/addressbalance/${addr}`)
.then(result => resolve(util.Sat_to_BTC(result)))
.catch(error => reject(error))
});
@ -747,16 +394,12 @@
}
btcOperator.validateTxParameters = validateTxParameters;
const createTransaction = btcOperator.createTransaction = ({
senders, redeemScripts, receivers, amounts, fee, change_address,
fee_from_receiver, allowUnconfirmedUtxos = false, sendingTx = false,
hasInsufficientBalance = false
}) => {
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) {
return new Promise((resolve, reject) => {
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
const tx = coinjs.transaction();
let output_size = addOutputs(tx, receivers, amounts, change_address);
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos).then(result => {
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
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
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
@ -784,21 +427,16 @@
result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0);
result.total_size = BASE_TX_SIZE + output_size + result.input_size;
result.transaction = tx;
if (sendingTx && result.hasOwnProperty('hasInsufficientBalance') && result.hasInsufficientBalance)
reject({
message: "Insufficient balance",
...result
});
else
resolve(result);
resolve(result);
}).catch(error => reject(error))
})
}
btcOperator.createTransaction = createTransaction;
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos = false) {
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) {
return new Promise((resolve, reject) => {
if (fee !== null) {
addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false, { allowUnconfirmedUtxos }).then(result => {
addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => {
result.fee = fee;
resolve(result);
}).catch(error => reject(error))
@ -807,8 +445,8 @@
let net_fee = BASE_TX_SIZE * fee_rate;
net_fee += (output_size * fee_rate);
(fee_from_receiver ?
addUTXOs(tx, senders, redeemScripts, total_amount, false, { allowUnconfirmedUtxos }) :
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate, { allowUnconfirmedUtxos })
addUTXOs(tx, senders, redeemScripts, total_amount, false) :
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate)
).then(result => {
result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8));
result.fee_rate = fee_rate;
@ -820,7 +458,7 @@
}
btcOperator.addInputs = addInputs;
function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = { allowUnconfirmedUtxos: false }) {
function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) {
return new Promise((resolve, reject) => {
required_amount = parseFloat(required_amount.toFixed(8));
if (typeof rec_args.n === "undefined") {
@ -834,24 +472,17 @@
input_amount: rec_args.input_amount,
change_amount: required_amount * -1 //required_amount will be -ve of change_amount
});
else if (rec_args.n >= senders.length) {
return resolve({
hasInsufficientBalance: true,
input_size: rec_args.input_size,
input_amount: rec_args.input_amount,
change_amount: required_amount * -1
});
}
else if (rec_args.n >= senders.length)
return reject("Insufficient Balance");
let addr = senders[rec_args.n],
rs = redeemScripts[rec_args.n];
let addr_type = coinjs.addressDecode(addr).type;
let size_per_input = _sizePerInput(addr, rs);
multiApi('unspent', { addr, allowUnconfirmedUtxos: rec_args.allowUnconfirmedUtxos }).then(utxos => {
fetch_api(`unspent?active=${addr}`).then(result => {
let utxos = result.unspent_outputs;
//console.debug("add-utxo", addr, rs, required_amount, utxos);
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
if (utxos.length === 1 && rec_args.allowUnconfirmedUtxos) {
console.log('allowing unconfirmed utxos')
} else if (!utxos[i].confirmations) //ignore unconfirmed utxo
if (!utxos[i].confirmations) //ignore unconfirmed utxo
continue;
var script;
if (!rs || !rs.length) //legacy script
@ -894,6 +525,43 @@
}
btcOperator.addOutputs = addOutputs;
/*
function autoFeeCalc(tx) {
return new Promise((resolve, reject) => {
get_fee_rate().then(fee_rate => {
let tx_size = tx.size();
for (var i = 0; i < this.ins.length; i++)
switch (tx.extractScriptKey(i).type) {
case 'scriptpubkey':
tx_size += SIGN_SIZE;
break;
case 'segwit':
case 'multisig':
tx_size += SIGN_SIZE * 0.25;
break;
default:
console.warn('Unknown script-type');
tx_size += SIGN_SIZE;
}
resolve(tx_size * fee_rate);
}).catch(error => reject(error))
})
}
function editFee(tx, current_fee, target_fee, index = -1) {
//values are in satoshi
index = parseInt(index >= 0 ? index : tx.outs.length - index);
if (index < 0 || index >= tx.outs.length)
throw "Invalid index";
let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places
current_value = tx.outs[index].value; //could be BigInterger
if (edit_value < 0 && edit_value > current_value)
throw "Insufficient value at vout";
tx.outs[index].value = current_value instanceof BigInteger ?
current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value);
}
*/
function tx_fetch_for_editing(tx) {
return new Promise((resolve, reject) => {
if (typeof tx == 'string' && /^[0-9a-f]{64}$/i.test(tx)) { //tx is txid
@ -1006,119 +674,119 @@
})
}
btcOperator.editFee_corewallet = function (tx_hex, new_fee, private_keys, change_only = true) {
return new Promise((resolve, reject) => {
if (!Array.isArray(private_keys))
private_keys = [private_keys];
tx_fetch_for_editing(tx_hex).then(tx => {
parseTransaction(tx).then(tx_parsed => {
if (tx_parsed.fee >= new_fee)
return reject("Fees can only be increased");
btcOperator.editFee_corewallet = function(tx_hex, new_fee, private_keys, change_only = true) {
return new Promise((resolve, reject) => {
if (!Array.isArray(private_keys))
private_keys = [private_keys];
tx_fetch_for_editing(tx_hex).then(tx => {
parseTransaction(tx).then(tx_parsed => {
if (tx_parsed.fee >= new_fee)
return reject("Fees can only be increased");
//editable addresses in output values (for fee increase)
var edit_output_address = new Set();
if (change_only === true) //allow only change values (ie, sender address) to be edited to inc fee
tx_parsed.inputs.forEach(inp => edit_output_address.add(inp.address));
else if (change_only === false) //allow all output values to be edited
tx_parsed.outputs.forEach(out => edit_output_address.add(out.address));
else if (typeof change_only == 'string') // allow only given receiver id output to be edited
edit_output_address.add(change_only);
else if (Array.isArray(change_only)) //allow only given set of receiver id outputs to be edited
change_only.forEach(id => edit_output_address.add(id));
//editable addresses in output values (for fee increase)
var edit_output_address = new Set();
if (change_only === true) //allow only change values (ie, sender address) to be edited to inc fee
tx_parsed.inputs.forEach(inp => edit_output_address.add(inp.address));
else if (change_only === false) //allow all output values to be edited
tx_parsed.outputs.forEach(out => edit_output_address.add(out.address));
else if (typeof change_only == 'string') // allow only given receiver id output to be edited
edit_output_address.add(change_only);
else if (Array.isArray(change_only)) //allow only given set of receiver id outputs to be edited
change_only.forEach(id => edit_output_address.add(id));
//edit output values to increase fee
let inc_fee = util.BTC_to_Sat(new_fee - tx_parsed.fee);
if (inc_fee < MIN_FEE_UPDATE)
return reject(`Insufficient additional fee. Minimum increment: ${MIN_FEE_UPDATE}`);
for (let i = tx.outs.length - 1; i >= 0 && inc_fee > 0; i--) //reduce in reverse order
if (edit_output_address.has(tx_parsed.outputs[i].address)) {
let current_value = tx.outs[i].value;
if (current_value instanceof BigInteger) //convert BigInteger class to inv value
current_value = current_value.intValue();
//edit the value as required
if (current_value > inc_fee) {
tx.outs[i].value = current_value - inc_fee;
inc_fee = 0;
} else {
inc_fee -= current_value;
tx.outs[i].value = 0;
}
//edit output values to increase fee
let inc_fee = util.BTC_to_Sat(new_fee - tx_parsed.fee);
if (inc_fee < MIN_FEE_UPDATE)
return reject(`Insufficient additional fee. Minimum increment: ${MIN_FEE_UPDATE}`);
for (let i = tx.outs.length - 1; i >= 0 && inc_fee > 0; i--) //reduce in reverse order
if (edit_output_address.has(tx_parsed.outputs[i].address)) {
let current_value = tx.outs[i].value;
if (current_value instanceof BigInteger) //convert BigInteger class to inv value
current_value = current_value.intValue();
//edit the value as required
if (current_value > inc_fee) {
tx.outs[i].value = current_value - inc_fee;
inc_fee = 0;
} else {
inc_fee -= current_value;
tx.outs[i].value = 0;
}
if (inc_fee > 0) {
let max_possible_fee = util.BTC_to_Sat(new_fee) - inc_fee; //in satoshi
return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`);
}
tx.outs = tx.outs.filter(o => o.value >= DUST_AMT); //remove all output with value less than DUST amount
if (inc_fee > 0) {
let max_possible_fee = util.BTC_to_Sat(new_fee) - inc_fee; //in satoshi
return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`);
}
tx.outs = tx.outs.filter(o => o.value >= DUST_AMT); //remove all output with value less than DUST amount
//remove existing signatures and reset the scripts
let wif_keys = [];
let witness_position = 0;
for (let i in tx.ins) {
var addr = tx_parsed.inputs[i].address,
value = util.BTC_to_Sat(tx_parsed.inputs[i].value);
let addr_decode = coinjs.addressDecode(addr);
//remove existing signatures and reset the scripts
let wif_keys = [];
let witness_position = 0;
for (let i in tx.ins) {
var addr = tx_parsed.inputs[i].address,
value = util.BTC_to_Sat(tx_parsed.inputs[i].value);
let addr_decode = coinjs.addressDecode(addr);
//find the correct key for addr
var privKey = private_keys.find(pk => verifyKey(addr, pk));
if (!privKey)
return reject(`Private key missing for ${addr}`);
//find redeemScript (if any)
const rs = _redeemScript(addr, privKey);
rs === false ? wif_keys.unshift(privKey) : wif_keys.push(privKey); //sorting private-keys (wif)
//reset the script for re-signing
var script;
if (!rs || !rs.length) {
//legacy script (derive from address)
let s = coinjs.script();
s.writeOp(118); //OP_DUP
s.writeOp(169); //OP_HASH160
s.writeBytes(addr_decode.bytes);
s.writeOp(136); //OP_EQUALVERIFY
s.writeOp(172); //OP_CHECKSIG
script = Crypto.util.bytesToHex(s.buffer);
} else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) {
//redeemScript for segwit/bech32
let s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(rs));
s.writeOp(0);
s.writeBytes(coinjs.numToBytes(value.toFixed(0), 8));
script = Crypto.util.bytesToHex(s.buffer);
if (addr_decode == "bech32") { witness_position = witness_position + 1; } //bech32 has witness
} else if (addr_decode.type === 'multisigBech32') {
var rs_array = [];
rs_array = btcOperator.extractLastHexStrings(tx.witness);
let redeemScript = rs_array[witness_position];
witness_position = witness_position + 1;
//redeemScript multisig (bech32)
let s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(redeemScript));
s.writeOp(0);
s.writeBytes(coinjs.numToBytes(value.toFixed(0), 8));
script = Crypto.util.bytesToHex(s.buffer);
} else //redeemScript for multisig (segwit)
script = rs;
tx.ins[i].script = coinjs.script(script);
}
tx.witness = false; //remove all witness signatures
console.debug("Unsigned:", tx.serialize());
//re-sign the transaction
new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF
if (btcOperator.checkSigned(tx)) {
resolve(tx.serialize());
//find the correct key for addr
var privKey = private_keys.find(pk => verifyKey(addr, pk));
if (!privKey)
return reject(`Private key missing for ${addr}`);
//find redeemScript (if any)
const rs = _redeemScript(addr, privKey);
rs === false ? wif_keys.unshift(privKey) : wif_keys.push(privKey); //sorting private-keys (wif)
//reset the script for re-signing
var script;
if (!rs || !rs.length) {
//legacy script (derive from address)
let s = coinjs.script();
s.writeOp(118); //OP_DUP
s.writeOp(169); //OP_HASH160
s.writeBytes(addr_decode.bytes);
s.writeOp(136); //OP_EQUALVERIFY
s.writeOp(172); //OP_CHECKSIG
script = Crypto.util.bytesToHex(s.buffer);
} else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) {
//redeemScript for segwit/bech32
let s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(rs));
s.writeOp(0);
s.writeBytes(coinjs.numToBytes(value.toFixed(0), 8));
script = Crypto.util.bytesToHex(s.buffer);
if (addr_decode == "bech32") {witness_position = witness_position + 1;} //bech32 has witness
} else if (addr_decode.type === 'multisigBech32') {
var rs_array = [];
rs_array = btcOperator.extractLastHexStrings(tx.witness);
let redeemScript = rs_array[witness_position];
witness_position = witness_position + 1;
//redeemScript multisig (bech32)
let s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(redeemScript));
s.writeOp(0);
s.writeBytes(coinjs.numToBytes(value.toFixed(0), 8));
script = Crypto.util.bytesToHex(s.buffer);
} else //redeemScript for multisig (segwit)
script = rs;
tx.ins[i].script = coinjs.script(script);
}
tx.witness = false; //remove all witness signatures
console.debug("Unsigned:", tx.serialize());
//re-sign the transaction
new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/ )); //Sign the tx using private key WIF
if (btcOperator.checkSigned(tx)) {
resolve(tx.serialize());
} else {
reject("All private keys not present");
reject("All private keys not present");
}
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
}).catch(error => reject(error))
})
}
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
options.sendingTx = true;
return new Promise((resolve, reject) => {
createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => {
// debugger;
broadcastTx(result.transaction.serialize())
.then(txid => resolve(txid))
.catch(error => reject(error));
@ -1140,7 +808,7 @@
receivers,
amounts,
fee,
...options
change_address: options.change_address
}));
} catch (e) {
return reject(e)
@ -1155,11 +823,7 @@
if (redeemScripts.includes(null)) //TODO: segwit
return reject("Unable to get redeem-script");
//create transaction
createTransaction({
senders, redeemScripts, receivers, amounts, fee,
change_address: options.change_address || senders[0],
...options
}).then(result => {
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
let tx = result.transaction;
console.debug("Unsigned:", tx.serialize());
new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF
@ -1169,9 +833,7 @@
})
}
btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {
allowUnconfirmedUtxos: false
}) {
btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) {
return new Promise((resolve, reject) => {
try {
({
@ -1192,11 +854,7 @@
if (redeemScripts.includes(null)) //TODO: segwit
return reject("Unable to get redeem-script");
//create transaction
createTransaction({
senders, redeemScripts, receivers, amounts, fee,
change_address: options.change_address || senders[0],
...options
}).then(result => {
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
result.tx_hex = result.transaction.serialize();
delete result.transaction;
resolve(result);
@ -1232,12 +890,7 @@
return reject(e)
}
//create transaction
createTransaction({
senders: [sender], redeemScripts: [redeemScript],
receivers, amounts, fee,
change_address: options.change_address || sender,
...options
}).then(result => {
createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => {
result.tx_hex = result.transaction.serialize();
delete result.transaction;
resolve(result);
@ -1312,7 +965,7 @@
}
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
multiApi('tx', { txid })
fetch_api(`rawtx/${txid}`)
.then(result => resolve(result.out[i]))
.catch(error => reject(error))
});
@ -1374,40 +1027,87 @@
.catch(error => reject(error))
})
const getTx = btcOperator.getTx = txid => new Promise(async (resolve, reject) => {
try {
const result = await multiApi('tx', { txid });
if (!result.hasOwnProperty('confirmations'))
result.confirmations = await getLatestBlock() - result.block_height;
resolve({
const getTx = btcOperator.getTx = txid => new Promise((resolve, reject) => {
fetch_api(`rawtx/${txid}`).then(result => {
getLatestBlock().then(latest_block => resolve({
block: result.block_height,
txid: result.hash,
time: result.time,
time: result.time * 1000,
confirmations: result.block_height === null ? 0 : latest_block - result.block_height, //calculate confirmations using latest block number as api doesnt relay it
size: result.size,
fee: util.Sat_to_BTC(result.fee),
inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)),
})
} catch (error) {
reject(error)
}
}).catch(error => reject(error))
}))
}).catch(error => reject(error))
});
getTx.hex = btcOperator.getTx.hex = txid => multiApi('txHex', { txid });
getTx.hex = btcOperator.getTx.hex = txid => new Promise((resolve, reject) => {
fetch_api(`rawtx/${txid}?format=hex`, false)
.then(result => resolve(result))
.catch(error => reject(error))
})
btcOperator.getAddressData = address => new Promise((resolve, reject) => {
Promise.all([
multiApi('balance', { addr: address }),
multiApi('txs', { addr: address })
]).then(([balance, txs]) => {
const parsedTxs = txs.map(tx => parseTx(tx, address));
resolve({
address,
balance,
txs: parsedTxs
});
fetch_api(`rawaddr/${address}`).then(data => {
let details = {};
details.balance = util.Sat_to_BTC(data.final_balance);
details.address = data.address;
details.txs = data.txs.map(tx => {
let d = {
txid: tx.hash,
time: tx.time * 1000, //s to ms
block: tx.block_height,
}
//sender list
d.tx_senders = {};
tx.inputs.forEach(i => {
if (i.prev_out.addr in d.tx_senders)
d.tx_senders[i.prev_out.addr] += i.prev_out.value;
else d.tx_senders[i.prev_out.addr] = i.prev_out.value;
});
d.tx_input_value = 0;
for (let s in d.tx_senders) {
let val = d.tx_senders[s];
d.tx_senders[s] = util.Sat_to_BTC(val);
d.tx_input_value += val;
}
d.tx_input_value = util.Sat_to_BTC(d.tx_input_value);
//receiver list
d.tx_receivers = {};
tx.out.forEach(o => {
if (o.addr in d.tx_receivers)
d.tx_receivers[o.addr] += o.value;
else d.tx_receivers[o.addr] = o.value;
});
d.tx_output_value = 0;
for (let r in d.tx_receivers) {
let val = d.tx_receivers[r];
d.tx_receivers[r] = util.Sat_to_BTC(val);
d.tx_output_value += val;
}
d.tx_output_value = util.Sat_to_BTC(d.tx_output_value);
d.tx_fee = util.Sat_to_BTC(tx.fee);
//tx type
if (tx.result > 0) { //net > 0, balance inc => type=in
d.type = "in";
d.amount = util.Sat_to_BTC(tx.result);
d.sender = Object.keys(d.tx_senders).filter(s => s !== address);
} else if (Object.keys(d.tx_receivers).some(r => r !== address)) { //net < 0, balance dec & receiver present => type=out
d.type = "out";
d.amount = util.Sat_to_BTC(tx.result * -1);
d.receiver = Object.keys(d.tx_receivers).filter(r => r !== address);
d.fee = d.tx_fee;
} else { //net < 0 (fee) & no other id in receiver list => type=self
d.type = "self";
d.amount = d.tx_receivers[address];
d.address = address
}
return d;
})
resolve(details);
}).catch(error => reject(error))
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long