Merge pull request #11 from ranchimall/dev
Blockbook based UI and backend
This commit is contained in:
commit
9983059012
File diff suppressed because one or more lines are too long
26
css/main.css
26
css/main.css
@ -776,7 +776,7 @@ h3 {
|
||||
}
|
||||
|
||||
.transaction {
|
||||
gap: 1rem;
|
||||
gap: 1.2rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
border-radius: 0.3rem;
|
||||
@ -792,7 +792,29 @@ h3 {
|
||||
}
|
||||
.transaction__receiver {
|
||||
margin-left: 0.5rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
color: rgba(var(--text-color), 0.9);
|
||||
font-weight: 500;
|
||||
}
|
||||
.transaction__amount {
|
||||
font-weight: 700;
|
||||
}
|
||||
.transaction.mined .transaction__icon .icon, .transaction.received .transaction__icon .icon, .transaction.self .transaction__icon .icon {
|
||||
fill: var(--green);
|
||||
}
|
||||
.transaction.mined .transaction__amount, .transaction.received .transaction__amount, .transaction.self .transaction__amount {
|
||||
color: var(--green);
|
||||
}
|
||||
.transaction.mined .transaction__amount::before, .transaction.received .transaction__amount::before, .transaction.self .transaction__amount::before {
|
||||
content: "+ ";
|
||||
}
|
||||
.transaction.sent .transaction__icon .icon {
|
||||
fill: var(--danger-color);
|
||||
}
|
||||
.transaction.sent .transaction__amount {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
.transaction.sent .transaction__amount::before {
|
||||
content: "- ";
|
||||
}
|
||||
.transaction p {
|
||||
font-size: 0.9rem;
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -558,17 +558,17 @@ h3 {
|
||||
#main_header {
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
.app-brand{
|
||||
.app-brand {
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
align-items: center;
|
||||
.icon{
|
||||
.icon {
|
||||
height: 1.7rem;
|
||||
width: 1.7rem;
|
||||
}
|
||||
}
|
||||
.app-name{
|
||||
&__company{
|
||||
.app-name {
|
||||
&__company {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
@ -741,7 +741,7 @@ h3 {
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
.transaction {
|
||||
gap: 1rem;
|
||||
gap: 1.2rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
border-radius: 0.3rem;
|
||||
@ -758,7 +758,39 @@ h3 {
|
||||
}
|
||||
&__receiver {
|
||||
margin-left: 0.5rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
color: rgba(var(--text-color), 0.9);
|
||||
font-weight: 500;
|
||||
}
|
||||
&__amount {
|
||||
font-weight: 700;
|
||||
}
|
||||
&.mined,
|
||||
&.received,
|
||||
&.self {
|
||||
.transaction__icon {
|
||||
.icon {
|
||||
fill: var(--green);
|
||||
}
|
||||
}
|
||||
.transaction__amount {
|
||||
color: var(--green);
|
||||
&::before {
|
||||
content: "+ ";
|
||||
}
|
||||
}
|
||||
}
|
||||
&.sent {
|
||||
.transaction__icon {
|
||||
.icon {
|
||||
fill: var(--danger-color);
|
||||
}
|
||||
}
|
||||
.transaction__amount {
|
||||
color: var(--danger-color);
|
||||
&::before {
|
||||
content: "- ";
|
||||
}
|
||||
}
|
||||
}
|
||||
p {
|
||||
font-size: 0.9rem;
|
||||
|
||||
127
index.html
127
index.html
@ -187,13 +187,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<section class="grid gap-1">
|
||||
<div class="flex align-center space-between sticky top-0"
|
||||
style="background-color: rgba(var(--foreground-color), 1);transition: background-color .3s;">
|
||||
<div class="flex align-center space-between sticky top-0 flex-wrap gap-1"
|
||||
style="background-color: rgba(var(--foreground-color), 1);transition: background-color .3s; padding-bottom: 0.5rem;">
|
||||
<h4>Transactions</h4>
|
||||
<sm-chips id="filter_selector" class="hidden">
|
||||
<sm-chip value="sent" selected>Sent</sm-chip>
|
||||
<sm-chip value="all" selected>All</sm-chip>
|
||||
<sm-chip value="sent">Sent</sm-chip>
|
||||
<sm-chip value="received">Received</sm-chip>
|
||||
<sm-chip value="all">All</sm-chip>
|
||||
<sm-chip value="mined">Mined</sm-chip>
|
||||
</sm-chips>
|
||||
</div>
|
||||
<ul id="queried_address_transactions" class="observe-empty-state"></ul>
|
||||
@ -830,7 +831,14 @@
|
||||
<div class="transaction__icon"></div>
|
||||
<div class="transaction__receiver breakable"></div>
|
||||
</div>
|
||||
<p class="transaction__flo-data breakable"></p>
|
||||
<div class="grid gap-0-3">
|
||||
<div class="label">Amount</div>
|
||||
<div class="transaction__amount"></div>
|
||||
</div>
|
||||
<div class="grid gap-0-3">
|
||||
<div class="label">FLO Data</div>
|
||||
<p class="transaction__flo-data breakable"></p>
|
||||
</div>
|
||||
<div class="flex align-center space-between">
|
||||
<a href="" class="transaction__link flex align-center" target="_blank" rel="noopener noreferrer">
|
||||
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px"
|
||||
@ -1208,15 +1216,15 @@
|
||||
page = parseInt(page)
|
||||
if (floGlobals.query.string !== query) {
|
||||
checkBalance(query)
|
||||
fetchTransactions(query).then(() => {
|
||||
filterFetchedTransactions()
|
||||
render.paginatedTransactions(page)
|
||||
})
|
||||
} else if (page % Math.ceil(1000 / txsPerPage) === 0 && floGlobals.query.transactions.length <= page * txsPerPage) {
|
||||
}/* else if (floGlobals.query.totalPages <= page * txsPerPage) {
|
||||
loadMoreTransactions()
|
||||
} else {
|
||||
render.paginatedTransactions(page)
|
||||
}
|
||||
}*/
|
||||
fetchTransactions(query, page).then(() => {
|
||||
filterFetchedTransactions()
|
||||
render.paginatedTransactions(page)
|
||||
})
|
||||
} catch (err) {
|
||||
notify(err, 'error')
|
||||
}
|
||||
@ -1877,45 +1885,55 @@
|
||||
})
|
||||
},
|
||||
transactionCard(details) {
|
||||
const { sender, receiver, floData, time, txid } = details
|
||||
const { sender, receiver, floData, time, txid, netValue, mine } = details
|
||||
const { query: queriedFloId } = pagesData.params
|
||||
const clone = getRef('transaction_template').content.cloneNode(true).firstElementChild;
|
||||
if (sender === receiver) {
|
||||
if (mine) {
|
||||
clone.classList.add('mined')
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M22.0861 14.6534C22.3924 14.3472 22.7898 14.1486 23.2186 14.0876L44.6946 11.0319C45.2676 10.9504 45.8455 11.1432 46.2547 11.5524C47.2381 12.5358 46.8168 14.2128 45.4853 14.6146L28.6869 19.6843C28.3711 19.7796 28.0838 19.9515 27.8505 20.1848L27.0092 21.0261L28.3236 22.3404C29.0306 23.0475 29.0976 24.1522 28.5245 24.9346L52.2562 48.6662C53.0372 49.4473 53.0372 50.7136 52.2562 51.4946L51.4947 52.2561C50.7137 53.0372 49.4473 53.0372 48.6663 52.2561L24.9346 28.5245C24.1522 29.0976 23.0475 29.0306 22.3404 28.3236L21.0261 27.0092L20.1848 27.8505C19.9515 28.0838 19.7796 28.3711 19.6843 28.6869L14.6146 45.4853C14.2128 46.8168 12.5358 47.2381 11.5524 46.2547C11.1432 45.8455 10.9504 45.2675 11.0319 44.6946L14.0876 23.2186C14.1486 22.7898 14.3472 22.3924 14.6534 22.0861L15.3949 21.3447C14.8777 20.6386 14.8818 19.67 15.4072 18.9681C14.8209 18.1848 14.8837 17.0693 15.5958 16.3572L16.3573 15.5958C17.0694 14.8837 18.1848 14.8208 18.9681 15.4072C19.6701 14.8818 20.6386 14.8777 21.3447 15.3949L22.0861 14.6534Z"/> </svg>`;
|
||||
clone.querySelector('.transaction__receiver').textContent = 'Coinbase'
|
||||
} else if (sender === receiver) {
|
||||
clone.classList.add('self')
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><title>sender and receiver is same</title><path d="M.01 0h24v24h-24V0z" fill="none"/><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>`;
|
||||
clone.querySelector('.transaction__receiver').textContent = receiver
|
||||
} else if (queriedFloId === sender) {
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/></svg>`;
|
||||
clone.classList.add('sent')
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<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>`;
|
||||
clone.querySelector('.transaction__receiver').textContent = receiver
|
||||
} else {
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 5.41L18.59 4 7 15.59V9H5v10h10v-2H8.41z"/></svg>`;
|
||||
clone.classList.add('received')
|
||||
clone.querySelector('.transaction__icon').innerHTML = `<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="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`;
|
||||
clone.querySelector('.transaction__receiver').textContent = sender
|
||||
}
|
||||
if (netValue) {
|
||||
clone.querySelector('.transaction__amount').textContent = `${netValue} FLO`
|
||||
} else {
|
||||
clone.querySelector('.transaction__amount').parentNode.remove()
|
||||
}
|
||||
if (floData) {
|
||||
clone.querySelector('.transaction__flo-data').textContent = floData
|
||||
} else {
|
||||
clone.querySelector('.transaction__flo-data').parentNode.remove()
|
||||
}
|
||||
clone.querySelector('.transaction__receiver').textContent = queriedFloId === sender ? receiver : sender
|
||||
clone.querySelector('.transaction__flo-data').textContent = floData
|
||||
clone.querySelector('.transaction__link').href = `${floBlockchainAPI.current_server}tx/${txid}`
|
||||
clone.querySelector('.transaction__time').textContent = getFormattedTime(time * 1000)
|
||||
return clone
|
||||
},
|
||||
paginatedTransactions(page = parseInt(pagesData.params.page) || 1) {
|
||||
const { transactions, string: address, filteredTransactions } = floGlobals.query
|
||||
let startingIndex = ((page - 1) * txsPerPage)
|
||||
if ((filteredTransactions?.length || transactions.length) < startingIndex) {
|
||||
startingIndex = 0;
|
||||
window.history.replaceState({}, '', `#/search?type=address&query=${address}&page=1`)
|
||||
pagesData.params.page = page = 1;
|
||||
}
|
||||
const endingIndex = startingIndex + txsPerPage
|
||||
const { transactions, string: address, filteredTransactions, totalPages } = floGlobals.query
|
||||
const renderedTransactions = (filteredTransactions || transactions)
|
||||
.slice(startingIndex, endingIndex)
|
||||
.map(transaction => render.transactionCard(transaction))
|
||||
renderElem(getRef('queried_address_transactions'), html`${renderedTransactions}`)
|
||||
getRef('transactions_hero_section').scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
})
|
||||
if (floGlobals.query.transactions.length) {
|
||||
if (transactions.length) {
|
||||
getRef('filter_selector').classList.remove('hidden')
|
||||
} else {
|
||||
getRef('filter_selector').classList.add('hidden')
|
||||
}
|
||||
const paginationSegments = (filteredTransactions || transactions) ? Math.ceil((filteredTransactions || transactions).length / txsPerPage) : 0;
|
||||
const paginationSegments = totalPages;
|
||||
let pagination = []
|
||||
let startingPage = page - 2;
|
||||
let showTill = page + 2;
|
||||
@ -1956,11 +1974,11 @@
|
||||
} else {
|
||||
getRef('pagination_wrapper').classList.add('hidden')
|
||||
}
|
||||
if (filteredTransactions && paginationSegments === page && filteredTransactions.length % txsPerPage !== 0 && transactions.length % txsPerPage === 0) {
|
||||
/* if (filteredTransactions && paginationSegments === page && filteredTransactions.length % txsPerPage !== 0 && transactions.length % txsPerPage === 0) {
|
||||
document.getElementById('load_more_transactions').classList.remove('hidden')
|
||||
} else {
|
||||
document.getElementById('load_more_transactions').classList.add('hidden')
|
||||
}
|
||||
} */
|
||||
},
|
||||
availableAssetOptions() {
|
||||
return (floGlobals.tokens || []).map(token => html` <sm-option value=${token}>${token}</sm-option> `)
|
||||
@ -2496,12 +2514,12 @@
|
||||
getRef('address_details_wrapper').classList.remove('hidden')
|
||||
floWebWallet.getLabels().then(allLabels => {
|
||||
if (allLabels[queriedFloId]) {
|
||||
renderElem(getRef('queried_flo_address'), html`<h4>${allLabels[queriedFloId]}</h4> <sm-copy clip-text value=${queriedFloId}></sm-copy>`)
|
||||
getRef('queried_flo_address').innerHTML = `<h4>${allLabels[queriedFloId]}</h4> <sm-copy clip-text value=${queriedFloId}></sm-copy>`;
|
||||
} else {
|
||||
renderElem(getRef('queried_flo_address'), html`
|
||||
getRef('queried_flo_address').innerHTML = `
|
||||
<p class="label">FLO Address </p>
|
||||
<h4><sm-copy clip-text value=${queriedFloId}></sm-copy></h4>
|
||||
`)
|
||||
`;
|
||||
}
|
||||
})
|
||||
const queriedFloId = address || getRef('search_query_input').value.trim()
|
||||
@ -2524,7 +2542,7 @@
|
||||
getRef('token_list_wrapper').classList.remove('hidden')
|
||||
}
|
||||
// retrieve FLO balance
|
||||
getRef('flo_balance').textContent = `${parseFloat(floBalance.toFixed(3))} FLO`;
|
||||
getRef('flo_balance').textContent = `${parseFloat(floBalance.toFixed(8))} FLO`;
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@ -2551,8 +2569,8 @@
|
||||
string: '',
|
||||
filteredTransactions: null
|
||||
}
|
||||
const txsPerPage = 25;
|
||||
async function fetchTransactions(address, loadOlder = false) {
|
||||
const txsPerPage = 100;
|
||||
async function fetchTransactions(address, page = 1) {
|
||||
try {
|
||||
document.getElementById('load_more_transactions').classList.add('hidden')
|
||||
renderElem(getRef('pagination_wrapper'), html``)
|
||||
@ -2562,23 +2580,12 @@
|
||||
<span>Loading transactions...</span>
|
||||
</div>
|
||||
`)
|
||||
if (loadOlder) {
|
||||
const { items, initItem } = await floWebWallet.listTransactions.syncOld(address, floGlobals.query.initItem)
|
||||
floGlobals.query = {
|
||||
transactions: [...items, ...floGlobals.query.transactions],
|
||||
string: address,
|
||||
initItem,
|
||||
filteredTransactions: null
|
||||
}
|
||||
} else {
|
||||
const { items, lastItem, initItem } = await floWebWallet.listTransactions(address)
|
||||
floGlobals.query = {
|
||||
transactions: items,
|
||||
string: address,
|
||||
lastItem,
|
||||
initItem,
|
||||
filteredTransactions: null
|
||||
}
|
||||
const { items, totalPages } = await floWebWallet.listTransactions(address, { pageSize: txsPerPage, page })
|
||||
floGlobals.query = {
|
||||
transactions: items,
|
||||
string: address,
|
||||
filteredTransactions: null,
|
||||
totalPages
|
||||
}
|
||||
} catch (err) {
|
||||
renderElem(getRef('queried_address_transactions'), html` <span>Failed to load transactions</span> `)
|
||||
@ -2588,7 +2595,19 @@
|
||||
function filterFetchedTransactions() {
|
||||
const filter = getRef('filter_selector').value;
|
||||
if (filter !== 'all') {
|
||||
floGlobals.query.filteredTransactions = floGlobals.query.transactions.filter(t => filter === 'sent' ? t.sender === floGlobals.query.string : t.receiver === floGlobals.query.string)
|
||||
floGlobals.query.filteredTransactions = floGlobals.query.transactions.filter(t => {
|
||||
switch (filter) {
|
||||
case 'sent':
|
||||
return t.sender === floGlobals.query.string
|
||||
break
|
||||
case 'received':
|
||||
return t.receiver === floGlobals.query.string
|
||||
break
|
||||
case 'mined':
|
||||
return t.mined
|
||||
break
|
||||
}
|
||||
})
|
||||
} else {
|
||||
floGlobals.query.filteredTransactions = null
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -48,46 +48,76 @@
|
||||
})
|
||||
}
|
||||
|
||||
function listTransactions_raw(address, options = {}) {
|
||||
function formatTx(address, tx) {
|
||||
let result = {
|
||||
time: tx.time,
|
||||
block: tx.blockheight,
|
||||
blockhash: tx.blockhash,
|
||||
txid: tx.txid,
|
||||
floData: tx.floData,
|
||||
confirmations: tx.confirmations
|
||||
}
|
||||
|
||||
//format receivers
|
||||
let receivers = {};
|
||||
for (let vout of tx.vout) {
|
||||
if (vout.scriptPubKey.isAddress) {
|
||||
let id = vout.scriptPubKey.addresses[0];
|
||||
if (id in receivers)
|
||||
receivers[id] += vout.value;
|
||||
else receivers[id] = vout.value;
|
||||
}
|
||||
}
|
||||
result.receivers = receivers;
|
||||
//format senders (or mined)
|
||||
if (!tx.vin[0].isAddress) { //mined (ie, coinbase)
|
||||
let coinbase = tx.vin[0].coinbase;
|
||||
result.mine = coinbase;
|
||||
result.mined = { [coinbase]: tx.valueOut }
|
||||
} else {
|
||||
result.sender = tx.vin[0].addresses[0];
|
||||
result.receiver = tx.vout[0].scriptPubKey.addresses[0];
|
||||
result.fees = tx.fees;
|
||||
let senders = {};
|
||||
for (let vin of tx.vin) {
|
||||
if (vin.isAddress) {
|
||||
let id = vin.addresses[0];
|
||||
if (id in senders)
|
||||
senders[id] += vin.value;
|
||||
else senders[id] = vin.value;
|
||||
}
|
||||
}
|
||||
result.senders = senders;
|
||||
|
||||
//remove change amounts
|
||||
for (let id in senders) {
|
||||
if (id in receivers) {
|
||||
if (senders[id] > receivers[id]) {
|
||||
senders[id] -= receivers[id];
|
||||
delete receivers[id];
|
||||
} else if (senders[id] < receivers[id]) { //&& id != address
|
||||
receivers[id] -= senders[id];
|
||||
delete senders[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
floWebWallet.listTransactions = function (address, page_options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
options.latest = true;
|
||||
let options = {};
|
||||
if (Number.isInteger(page_options.page))
|
||||
options.page = page_options.page;
|
||||
if (Number.isInteger(page_options.pageSize))
|
||||
options.pageSize = page_options.pageSize;
|
||||
floBlockchainAPI.readTxs(address, options).then(response => {
|
||||
const result = {}
|
||||
result.items = response.items.map(({ time, txid, floData, isCoinBase, vin, vout }) => ({
|
||||
time, txid, floData, isCoinBase,
|
||||
sender: isCoinBase ? `(mined)${vin[0].coinbase}` : vin[0].addr,
|
||||
receiver: isCoinBase ? address : vout[0].scriptPubKey.addresses[0]
|
||||
}));
|
||||
result.lastItem = response.lastItem;
|
||||
result.initItem = response.initItem;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
floWebWallet.listTransactions = function (address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
listTransactions_raw(address)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
floWebWallet.listTransactions.syncNew = function (address, lastItem) {
|
||||
return new Promise((resolve, reject) => {
|
||||
listTransactions_raw(address, { after: lastItem }).then(result => {
|
||||
delete result.initItem;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floWebWallet.listTransactions.syncOld = function (address, initItem) {
|
||||
return new Promise((resolve, reject) => {
|
||||
listTransactions_raw(address, { before: initItem }).then(result => {
|
||||
delete result.lastItem;
|
||||
result.items = response.txs.map(tx => formatTx(address, tx));
|
||||
result.page = response.page;
|
||||
result.totalPages = response.totalPages;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
(function (EXPORTS) { //floBlockchainAPI v2.5.6a
|
||||
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
||||
(function (EXPORTS) { //floBlockchainAPI v3.0.1b
|
||||
/* FLO Blockchain Operator to send/receive data from blockchain using API calls via FLO Blockbook*/
|
||||
'use strict';
|
||||
const floBlockchainAPI = EXPORTS;
|
||||
|
||||
const DEFAULT = {
|
||||
blockchain: floGlobals.blockchain,
|
||||
apiURL: {
|
||||
FLO: ['https://flosight.ranchimall.net/'],
|
||||
FLO_TEST: ['https://flosight-testnet.ranchimall.net/']
|
||||
FLO: ['https://blockbook.ranchimall.net/'],
|
||||
FLO_TEST: []
|
||||
},
|
||||
sendAmt: 0.0003,
|
||||
fee: 0.0002,
|
||||
@ -61,9 +61,9 @@
|
||||
var serverList = Array.from(allServerList);
|
||||
var curPos = floCrypto.randInt(0, serverList.length - 1);
|
||||
|
||||
function fetch_retry(apicall, rm_flosight) {
|
||||
function fetch_retry(apicall, rm_node) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let i = serverList.indexOf(rm_flosight)
|
||||
let i = serverList.indexOf(rm_node)
|
||||
if (i != -1) serverList.splice(i, 1);
|
||||
curPos = floCrypto.randInt(0, serverList.length - 1);
|
||||
fetch_api(apicall, false)
|
||||
@ -82,19 +82,19 @@
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
} else
|
||||
reject("No floSight server working");
|
||||
reject("No FLO blockbook server working");
|
||||
} else {
|
||||
let flosight = serverList[curPos];
|
||||
fetch(flosight + apicall).then(response => {
|
||||
let serverURL = serverList[curPos];
|
||||
fetch(serverURL + apicall).then(response => {
|
||||
if (response.ok)
|
||||
response.json().then(data => resolve(data));
|
||||
else {
|
||||
fetch_retry(apicall, flosight)
|
||||
fetch_retry(apicall, serverURL)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
}
|
||||
}).catch(error => {
|
||||
fetch_retry(apicall, flosight)
|
||||
fetch_retry(apicall, serverURL)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
@ -124,43 +124,27 @@
|
||||
}
|
||||
|
||||
//Get balance for the given Address
|
||||
const getBalance = floBlockchainAPI.getBalance = function (addr, after = null) {
|
||||
const getBalance = floBlockchainAPI.getBalance = function (addr) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let api = `api/addr/${addr}/balance`, query_params = {};
|
||||
if (after) {
|
||||
if (typeof after === 'string' && /^[0-9a-z]{64}$/i.test(after))
|
||||
query_params.after = after;
|
||||
else return reject("Invalid 'after' parameter");
|
||||
}
|
||||
promisedAPI(api, query_params).then(result => {
|
||||
if (typeof result === 'object' && result.lastItem) {
|
||||
getBalance(addr, result.lastItem)
|
||||
.then(r => resolve(util.toFixed(r + result.data)))
|
||||
.catch(error => reject(error))
|
||||
} else resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
let api = `api/address/${addr}`;
|
||||
promisedAPI(api, { details: "basic" })
|
||||
.then(result => resolve(result["balance"]))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
const getUTXOs = address => new Promise((resolve, reject) => {
|
||||
promisedAPI(`api/addr/${address}/utxo`)
|
||||
.then(utxo => resolve(utxo))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
function getScriptPubKey(address) {
|
||||
var tx = bitjs.transaction();
|
||||
tx.addoutput(address, 0);
|
||||
let outputBuffer = tx.outputs.pop().script;
|
||||
return Crypto.util.bytesToHex(outputBuffer)
|
||||
}
|
||||
|
||||
const getUnconfirmedSpent = address => new Promise((resolve, reject) => {
|
||||
readTxs(address, { mempool: "only" }).then(result => {
|
||||
let unconfirmedSpent = {};
|
||||
for (let tx of result.items)
|
||||
if (tx.confirmations == 0)
|
||||
for (let vin of tx.vin)
|
||||
if (vin.addr === address) {
|
||||
if (Array.isArray(unconfirmedSpent[vin.txid]))
|
||||
unconfirmedSpent[vin.txid].push(vin.vout);
|
||||
else
|
||||
unconfirmedSpent[vin.txid] = [vin.vout];
|
||||
}
|
||||
resolve(unconfirmedSpent);
|
||||
const getUTXOs = address => new Promise((resolve, reject) => {
|
||||
promisedAPI(`api/utxo/${address}`, { confirmed: true }).then(utxos => {
|
||||
let scriptPubKey = getScriptPubKey(address);
|
||||
utxos.forEach(u => u.scriptPubKey = scriptPubKey);
|
||||
resolve(utxos);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
|
||||
@ -180,32 +164,28 @@
|
||||
var fee = DEFAULT.fee;
|
||||
if (balance < sendAmt + fee)
|
||||
return reject("Insufficient FLO balance!");
|
||||
getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
|
||||
getUTXOs(senderAddr).then(utxos => {
|
||||
//form/construct the transaction data
|
||||
var trx = bitjs.transaction();
|
||||
var utxoAmt = 0.0;
|
||||
for (var i = utxos.length - 1;
|
||||
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
||||
//use only utxos with confirmations (strict_utxo mode)
|
||||
if (utxos[i].confirmations || !strict_utxo) {
|
||||
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
|
||||
continue; //A transaction has already used the utxo, but is unconfirmed.
|
||||
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||
utxoAmt += utxos[i].amount;
|
||||
};
|
||||
}
|
||||
if (utxoAmt < sendAmt + fee)
|
||||
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||
else {
|
||||
trx.addoutput(receiverAddr, sendAmt);
|
||||
var change = utxoAmt - sendAmt - fee;
|
||||
if (change > DEFAULT.minChangeAmt)
|
||||
trx.addoutput(senderAddr, change);
|
||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||
resolve(trx);
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
getUTXOs(senderAddr).then(utxos => {
|
||||
//form/construct the transaction data
|
||||
var trx = bitjs.transaction();
|
||||
var utxoAmt = 0.0;
|
||||
for (var i = utxos.length - 1;
|
||||
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
||||
//use only utxos with confirmations (strict_utxo mode)
|
||||
if (utxos[i].confirmations || !strict_utxo) {
|
||||
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||
utxoAmt += utxos[i].amount;
|
||||
};
|
||||
}
|
||||
if (utxoAmt < sendAmt + fee)
|
||||
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||
else {
|
||||
trx.addoutput(receiverAddr, sendAmt);
|
||||
var change = utxoAmt - sendAmt - fee;
|
||||
if (change > DEFAULT.minChangeAmt)
|
||||
trx.addoutput(senderAddr, change);
|
||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||
resolve(trx);
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
@ -293,34 +273,30 @@
|
||||
if (balance < totalAmt + fee)
|
||||
return reject("Insufficient FLO balance!");
|
||||
//get unconfirmed tx list
|
||||
getUnconfirmedSpent(floID).then(unconfirmedSpent => {
|
||||
getUTXOs(floID).then(utxos => {
|
||||
var trx = bitjs.transaction();
|
||||
var utxoAmt = 0.0;
|
||||
for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + fee); i--) {
|
||||
//use only utxos with confirmations (strict_utxo mode)
|
||||
if (utxos[i].confirmations || !strict_utxo) {
|
||||
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
|
||||
continue; //A transaction has already used the utxo, but is unconfirmed.
|
||||
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||
utxoAmt += utxos[i].amount;
|
||||
};
|
||||
}
|
||||
if (utxoAmt < totalAmt + fee)
|
||||
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||
else {
|
||||
for (let i = 0; i < count; i++)
|
||||
trx.addoutput(floID, splitAmt);
|
||||
var change = utxoAmt - totalAmt - fee;
|
||||
if (change > DEFAULT.minChangeAmt)
|
||||
trx.addoutput(floID, change);
|
||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||
var signedTxHash = trx.sign(privKey, 1);
|
||||
broadcastTx(signedTxHash)
|
||||
.then(txid => resolve(txid))
|
||||
.catch(error => reject(error))
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
getUTXOs(floID).then(utxos => {
|
||||
var trx = bitjs.transaction();
|
||||
var utxoAmt = 0.0;
|
||||
for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + fee); i--) {
|
||||
//use only utxos with confirmations (strict_utxo mode)
|
||||
if (utxos[i].confirmations || !strict_utxo) {
|
||||
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||
utxoAmt += utxos[i].amount;
|
||||
};
|
||||
}
|
||||
if (utxoAmt < totalAmt + fee)
|
||||
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||
else {
|
||||
for (let i = 0; i < count; i++)
|
||||
trx.addoutput(floID, splitAmt);
|
||||
var change = utxoAmt - totalAmt - fee;
|
||||
if (change > DEFAULT.minChangeAmt)
|
||||
trx.addoutput(floID, change);
|
||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||
var signedTxHash = trx.sign(privKey, 1);
|
||||
broadcastTx(signedTxHash)
|
||||
.then(txid => resolve(txid))
|
||||
.catch(error => reject(error))
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
@ -551,33 +527,29 @@
|
||||
var fee = DEFAULT.fee;
|
||||
if (balance < sendAmt + fee)
|
||||
return reject("Insufficient FLO balance!");
|
||||
getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
|
||||
getUTXOs(senderAddr).then(utxos => {
|
||||
//form/construct the transaction data
|
||||
var trx = bitjs.transaction();
|
||||
var utxoAmt = 0.0;
|
||||
for (var i = utxos.length - 1;
|
||||
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
||||
//use only utxos with confirmations (strict_utxo mode)
|
||||
if (utxos[i].confirmations || !strict_utxo) {
|
||||
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
|
||||
continue; //A transaction has already used the utxo, but is unconfirmed.
|
||||
trx.addinput(utxos[i].txid, utxos[i].vout, redeemScript); //for multisig, script=redeemScript
|
||||
utxoAmt += utxos[i].amount;
|
||||
};
|
||||
}
|
||||
if (utxoAmt < sendAmt + fee)
|
||||
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||
else {
|
||||
for (let i in receivers)
|
||||
trx.addoutput(receivers[i], amounts[i]);
|
||||
var change = utxoAmt - sendAmt - fee;
|
||||
if (change > DEFAULT.minChangeAmt)
|
||||
trx.addoutput(senderAddr, change);
|
||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||
resolve(trx);
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
getUTXOs(senderAddr).then(utxos => {
|
||||
//form/construct the transaction data
|
||||
var trx = bitjs.transaction();
|
||||
var utxoAmt = 0.0;
|
||||
for (var i = utxos.length - 1;
|
||||
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
||||
//use only utxos with confirmations (strict_utxo mode)
|
||||
if (utxos[i].confirmations || !strict_utxo) {
|
||||
trx.addinput(utxos[i].txid, utxos[i].vout, redeemScript); //for multisig, script=redeemScript
|
||||
utxoAmt += utxos[i].amount;
|
||||
};
|
||||
}
|
||||
if (utxoAmt < sendAmt + fee)
|
||||
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||
else {
|
||||
for (let i in receivers)
|
||||
trx.addoutput(receivers[i], amounts[i]);
|
||||
var change = utxoAmt - sendAmt - fee;
|
||||
if (change > DEFAULT.minChangeAmt)
|
||||
trx.addoutput(senderAddr, change);
|
||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||
resolve(trx);
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
@ -773,20 +745,11 @@
|
||||
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (signedTxHash.length < 1)
|
||||
return reject("Empty Signature");
|
||||
var url = serverList[curPos] + 'api/tx/send';
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: `{"rawtx":"${signedTxHash}"}`
|
||||
}).then(response => {
|
||||
if (response.ok)
|
||||
response.json().then(data => resolve(data.txid.result));
|
||||
else
|
||||
response.text().then(data => resolve(data));
|
||||
}).catch(error => reject(error));
|
||||
return reject("Empty Transaction Data");
|
||||
|
||||
promisedAPI('/api/sendtx/' + signedTxHash)
|
||||
.then(response => resolve(response["result"]))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
@ -825,61 +788,96 @@
|
||||
})
|
||||
}
|
||||
|
||||
//Read Txs of Address between from and to
|
||||
//Read Txs of Address
|
||||
const readTxs = floBlockchainAPI.readTxs = function (addr, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let api = `api/addrs/${addr}/txs`;
|
||||
//API options
|
||||
let query_params = {};
|
||||
if (!isUndefined(options.after) || !isUndefined(options.before)) {
|
||||
if (!isUndefined(options.after))
|
||||
query_params.after = options.after;
|
||||
if (!isUndefined(options.before))
|
||||
query_params.before = options.before;
|
||||
} else {
|
||||
if (!isUndefined(options.from))
|
||||
query_params.from = options.from;
|
||||
if (!isUndefined(options.to))
|
||||
query_params.to = options.to;
|
||||
}
|
||||
if (!isUndefined(options.latest))
|
||||
query_params.latest = options.latest;
|
||||
if (!isUndefined(options.mempool))
|
||||
query_params.mempool = options.mempool;
|
||||
promisedAPI(api, query_params)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
let query_params = { details: 'txs' };
|
||||
//page options
|
||||
if (!isUndefined(options.page) && Number.isInteger(options.page))
|
||||
query_params.page = options.page;
|
||||
if (!isUndefined(options.pageSize) && Number.isInteger(options.pageSize))
|
||||
query_params.pageSize = options.pageSize;
|
||||
//only confirmed tx
|
||||
if (options.confirmed) //Default is false in server, so only add confirmed filter if confirmed has a true value
|
||||
query_params.confirmed = true;
|
||||
|
||||
promisedAPI(`api/address/${addr}`, query_params).then(response => {
|
||||
if (!Array.isArray(response.txs)) //set empty array if address doesnt have any tx
|
||||
response.txs = [];
|
||||
resolve(response)
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
//backward support (floBlockchainAPI < v2.5.6)
|
||||
function readAllTxs_oldSupport(addr, options, ignoreOld = 0, cacheTotal = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
readTxs(addr, options).then(response => {
|
||||
cacheTotal += response.txs.length;
|
||||
let n_remaining = response.txApperances - cacheTotal
|
||||
if (n_remaining < ignoreOld) { // must remove tx that would have been fetch during prev call
|
||||
let n_remove = ignoreOld - n_remaining;
|
||||
resolve(response.txs.slice(0, -n_remove));
|
||||
} else if (response.page == response.totalPages) //last page reached
|
||||
resolve(response.txs);
|
||||
else {
|
||||
options.page = response.page + 1;
|
||||
readAllTxs_oldSupport(addr, options, ignoreOld, cacheTotal)
|
||||
.then(result => resolve(response.txs.concat(result)))
|
||||
.catch(error => reject(error))
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function readAllTxs_new(addr, options, lastItem) {
|
||||
return new Promise((resolve, reject) => {
|
||||
readTxs(addr, options).then(response => {
|
||||
let i = response.txs.findIndex(t => t.txid === lastItem);
|
||||
if (i != -1) //found lastItem
|
||||
resolve(response.txs.slice(0, i))
|
||||
else if (response.page == response.totalPages) //last page reached
|
||||
resolve(response.txs);
|
||||
else {
|
||||
options.page = response.page + 1;
|
||||
readAllTxs_new(addr, options, lastItem)
|
||||
.then(result => resolve(response.txs.concat(result)))
|
||||
.catch(error => reject(error))
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//Read All Txs of Address (newest first)
|
||||
const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
readTxs(addr, options).then(response => {
|
||||
if (response.incomplete) {
|
||||
let next_options = Object.assign({}, options);
|
||||
if (options.latest)
|
||||
next_options.before = response.initItem; //update before for chain query (latest 1st)
|
||||
else
|
||||
next_options.after = response.lastItem; //update after for chain query (oldest 1st)
|
||||
readAllTxs(addr, next_options).then(r => {
|
||||
r.items = r.items.concat(response.items); //latest tx are 1st in array
|
||||
resolve(r);
|
||||
}).catch(error => reject(error))
|
||||
} else
|
||||
if (Number.isInteger(options.ignoreOld)) //backward support: data from floBlockchainAPI < v2.5.6
|
||||
readAllTxs_oldSupport(addr, options, options.ignoreOld).then(txs => {
|
||||
let last_tx = txs.find(t => t.confirmations > 0);
|
||||
let new_lastItem = last_tx ? last_tx.txid : options.ignoreOld;
|
||||
resolve({
|
||||
lastItem: response.lastItem || options.after,
|
||||
items: response.items
|
||||
});
|
||||
})
|
||||
});
|
||||
lastItem: new_lastItem,
|
||||
items: txs
|
||||
})
|
||||
|
||||
}).catch(error => reject(error))
|
||||
else //New format for floBlockchainAPI >= v2.5.6
|
||||
readAllTxs_new(addr, options, options.after).then(txs => {
|
||||
let last_tx = txs.find(t => t.confirmations > 0);
|
||||
let new_lastItem = last_tx ? last_tx.txid : options.after;
|
||||
resolve({
|
||||
lastItem: new_lastItem,
|
||||
items: txs
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
/*Read flo Data from txs of given Address
|
||||
options can be used to filter data
|
||||
after : query after the given txid
|
||||
before : query before the given txid
|
||||
mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx)
|
||||
confirmed : query only confirmed tx or not (options same as readAllTx, DEFAULT=true: only_confirmed_tx)
|
||||
ignoreOld : ignore old txs (deprecated: support for backward compatibility only, cannot be used with 'after')
|
||||
sentOnly : filters only sent data
|
||||
receivedOnly: filters only received data
|
||||
@ -894,18 +892,14 @@
|
||||
|
||||
//fetch options
|
||||
let query_options = {};
|
||||
query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx
|
||||
if (!isUndefined(options.after) || !isUndefined(options.before)) {
|
||||
if (!isUndefined(options.ignoreOld)) //Backward support
|
||||
return reject("Invalid options: cannot use after/before and ignoreOld in same query");
|
||||
//use passed after and/or before options (options remain undefined if not passed)
|
||||
query_options.after = options.after;
|
||||
query_options.before = options.before;
|
||||
}
|
||||
readAllTxs(addr, query_options).then(response => {
|
||||
query_options.confirmed = isUndefined(options.confirmed) ? true : options.confirmed; //DEFAULT: ignore unconfirmed tx
|
||||
|
||||
if (Number.isInteger(options.ignoreOld)) //backward support, cannot be used with options.after or options.before
|
||||
response.items.splice(-options.ignoreOld); //negative to count from end of the array
|
||||
if (!isUndefined(options.after))
|
||||
query_options.after = options.after;
|
||||
else if (!isUndefined(options.ignoreOld))
|
||||
query_options.ignoreOld = options.ignoreOld;
|
||||
|
||||
readAllTxs(addr, query_options).then(response => {
|
||||
|
||||
if (typeof options.senders === "string") options.senders = [options.senders];
|
||||
if (typeof options.receivers === "string") options.receivers = [options.receivers];
|
||||
@ -916,9 +910,9 @@
|
||||
if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query
|
||||
return false;
|
||||
|
||||
if (options.sentOnly && !tx.vin.some(vin => vin.addr === addr))
|
||||
if (options.sentOnly && !tx.vin.some(vin => vin.addresses[0] === addr))
|
||||
return false;
|
||||
else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addr)))
|
||||
else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addresses[0])))
|
||||
return false;
|
||||
|
||||
if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr))
|
||||
@ -944,7 +938,7 @@
|
||||
txid: tx.txid,
|
||||
time: tx.time,
|
||||
blockheight: tx.blockheight,
|
||||
senders: new Set(tx.vin.map(v => v.addr)),
|
||||
senders: new Set(tx.vin.map(v => v.addresses[0])),
|
||||
receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])),
|
||||
data: tx.floData
|
||||
} : tx.floData);
|
||||
@ -964,8 +958,7 @@
|
||||
caseFn: (function) flodata => return bool value
|
||||
options can be used to filter data
|
||||
after : query after the given txid
|
||||
before : query before the given txid
|
||||
mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx)
|
||||
confirmed : query only confirmed tx or not (options same as readAllTx, DEFAULT=true: only_confirmed_tx)
|
||||
sentOnly : filters only sent data
|
||||
receivedOnly: filters only received data
|
||||
tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details)
|
||||
@ -975,23 +968,37 @@
|
||||
const getLatestData = floBlockchainAPI.getLatestData = function (addr, caseFn, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//fetch options
|
||||
let query_options = { latest: true };
|
||||
query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx
|
||||
if (!isUndefined(options.after)) query_options.after = options.after;
|
||||
if (!isUndefined(options.before)) query_options.before = options.before;
|
||||
let query_options = {};
|
||||
query_options.confirmed = isUndefined(options.confirmed) ? true : options.confirmed; //DEFAULT: confirmed tx only
|
||||
if (!isUndefined(options.page))
|
||||
query_options.page = options.page;
|
||||
//if (!isUndefined(options.after)) query_options.after = options.after;
|
||||
|
||||
let new_lastItem;
|
||||
readTxs(addr, query_options).then(response => {
|
||||
|
||||
//lastItem confirmed tx checked
|
||||
if (!new_lastItem) {
|
||||
let last_tx = response.items.find(t => t.confirmations > 0);
|
||||
if (last_tx)
|
||||
new_lastItem = last_tx.txid;
|
||||
}
|
||||
|
||||
if (typeof options.senders === "string") options.senders = [options.senders];
|
||||
if (typeof options.receivers === "string") options.receivers = [options.receivers];
|
||||
|
||||
//check if `after` txid is in the response
|
||||
let i_after = response.txs.findIndex(t => t.txid === options.after);
|
||||
if (i_after != -1) //found lastItem, hence remove it and all txs before that
|
||||
response.items.splice(i_after);
|
||||
|
||||
var item = response.items.find(tx => {
|
||||
if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query
|
||||
return false;
|
||||
|
||||
if (options.sentOnly && !tx.vin.some(vin => vin.addr === addr))
|
||||
if (options.sentOnly && !tx.vin.some(vin => vin.addresses[0] === addr))
|
||||
return false;
|
||||
else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addr)))
|
||||
else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addresses[0])))
|
||||
return false;
|
||||
|
||||
if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr))
|
||||
@ -1004,32 +1011,31 @@
|
||||
|
||||
//if item found, then resolve the result
|
||||
if (!isUndefined(item)) {
|
||||
const result = { lastItem: response.lastItem };
|
||||
const result = { lastItem: new_lastItem || item.txid };
|
||||
if (options.tx) {
|
||||
result.item = {
|
||||
txid: tx.txid,
|
||||
time: tx.time,
|
||||
blockheight: tx.blockheight,
|
||||
senders: new Set(tx.vin.map(v => v.addr)),
|
||||
receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])),
|
||||
data: tx.floData
|
||||
txid: item.txid,
|
||||
time: item.time,
|
||||
blockheight: item.blockheight,
|
||||
senders: new Set(item.vin.map(v => v.addresses[0])),
|
||||
receivers: new Set(item.vout.map(v => v.scriptPubKey.addresses[0])),
|
||||
data: item.floData
|
||||
}
|
||||
} else
|
||||
result.data = tx.floData;
|
||||
result.data = item.floData;
|
||||
return resolve(result);
|
||||
}
|
||||
|
||||
if (response.page == response.totalPages || i_after != -1) //reached last page to check
|
||||
resolve({ lastItem: new_lastItem || options.after }); //no data match the caseFn, resolve just the lastItem
|
||||
|
||||
//else if address needs chain query
|
||||
else if (response.incomplete) {
|
||||
let next_options = Object.assign({}, options);
|
||||
options.before = response.initItem; //this fn uses latest option, so using before to chain query
|
||||
getLatestData(addr, caseFn, next_options).then(r => {
|
||||
r.lastItem = response.lastItem; //update last key as it should be the newest tx
|
||||
resolve(r);
|
||||
}).catch(error => reject(error))
|
||||
else {
|
||||
options.page = response.page + 1;
|
||||
getLatestData(addr, caseFn, options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}
|
||||
//no data match the caseFn, resolve just the lastItem
|
||||
else
|
||||
resolve({ lastItem: response.lastItem });
|
||||
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
|
||||
11088
testnet.html
11088
testnet.html
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user