Feature update and code optimizations

-- added option for lender to liquidate or pre-liquidate loan if conditions are met
-- Reduced repeated processing of requests
-- added request caching
This commit is contained in:
sairaj mote 2023-09-11 02:30:32 +05:30
parent 5d40939171
commit c6affb6913
5 changed files with 285 additions and 260 deletions

View File

@ -50,10 +50,6 @@ strong {
line-height: 1.7; line-height: 1.7;
color: rgba(var(--text-color), 0.9); color: rgba(var(--text-color), 0.9);
} }
p:not(:last-of-type),
strong:not(:last-of-type) {
margin-bottom: 1.5rem;
}
a { a {
text-decoration: none; text-decoration: none;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -48,10 +48,6 @@ strong {
max-width: 65ch; max-width: 65ch;
line-height: 1.7; line-height: 1.7;
color: rgba(var(--text-color), 0.9); color: rgba(var(--text-color), 0.9);
&:not(:last-of-type) {
margin-bottom: 1.5rem;
}
} }
a { a {
text-decoration: none; text-decoration: none;

View File

@ -643,7 +643,7 @@
`: html``} `: html``}
` `
}, },
loanProcess(details = {}) { loanProcess(processId) {
const { const {
type, type,
loanOpeningProcessID, loanOpeningProcessID,
@ -653,7 +653,7 @@
collateralRequestID, loanResponseID, collateralLockRequestID, collateralLockAckID, loanID, closingTxID, collateralRequestID, loanResponseID, collateralLockRequestID, collateralLockAckID, loanID, closingTxID,
unlockCollateralRequestTime, hasRequestedCollateralUnlock, unlockCollateralAckTime, hasRefundedCollateral, unlockCollateralRequestTime, hasRequestedCollateralUnlock, unlockCollateralAckTime, hasRefundedCollateral,
unlockTxHex unlockTxHex
} = details } = uiGlobals.inProcessRequests[processId]
if (type === 'loanOpening') { if (type === 'loanOpening') {
return Component(() => { return Component(() => {
const [verifyingCollateral, setVerifyCollateral] = useState(false) const [verifyingCollateral, setVerifyCollateral] = useState(false)
@ -1086,6 +1086,8 @@
lender, lender_sign, loan_amount, loan_id, loan_opening_process_id, loan_transfer_id, lender, lender_sign, loan_amount, loan_id, loan_opening_process_id, loan_transfer_id,
open_time, close_time, policy_id } = btcMortgage.loans[loanID]; open_time, close_time, policy_id } = btcMortgage.loans[loanID];
const { hasRepaidLoan, hasRefundedCollateral, collateralUnlockAckTime, hasRequestedCollateralLiquidation, hasRequestedCollateralPreLiquidation } = uiGlobals.activeLoanQueries[loanID] || {}
const amountDue = btcMortgage.util.calcDueAmount(loan_amount, policy_id, open_time) const amountDue = btcMortgage.util.calcDueAmount(loan_amount, policy_id, open_time)
const [isRepayingLoan, setIsRepayingLoan] = useState(false) const [isRepayingLoan, setIsRepayingLoan] = useState(false)
async function initLoanRepayment() { async function initLoanRepayment() {
@ -1109,10 +1111,9 @@
const isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id) const isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
const isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id) const isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
const isLender = floCrypto.isSameAddr(lender, floDapps.user.id) const isLender = floCrypto.isSameAddr(lender, floDapps.user.id)
let hasStartedRepayment = false
let loanStatus = ''; let loanStatus = '';
let loanStatusBadge = ''; let loanStatusBadge = '';
if (close_time) { if (close_time || hasRefundedCollateral) {
loanStatus = 'Closed' loanStatus = 'Closed'
loanStatusBadge = html` loanStatusBadge = html`
<div class="status"> <div class="status">
@ -1121,12 +1122,7 @@
</div> </div>
` `
} else { } else {
hasStartedRepayment = Object.values({ if (hasRepaidLoan) {
...floGlobals.myInbox,
...floGlobals.myOutbox
})
.find(request => request.message.loan_id === loanID)
if (hasStartedRepayment) {
loanStatus = 'Repaying' loanStatus = 'Repaying'
loanStatusBadge = html` loanStatusBadge = html`
<div class="status"> <div class="status">
@ -1160,6 +1156,22 @@
setIsClaimingCollateral(false) setIsClaimingCollateral(false)
} }
} }
const hasGoneBelowThreshold = btcMortgage.util.calcRateRatio(floGlobals.btcRate, btcMortgage.policies[policy_id]?.pre_liquidation_threshold) || false;
const [isRequestingCollateralPreLiquidation, setIsRequestingCollateralPreLiquidation] = useState(false)
async function requestCollateralPreLiquidation() {
const confirmation = await getConfirmation('Request collateral pre-liquidation?', { message: `You are about to request for collateral pre-liquidation. Continue?`, confirmText: 'Request', cancelText: 'Cancel' })
if (!confirmation)
return;
setIsRequestingCollateralPreLiquidation(true)
try {
await btcMortgage.requestBanker.preLiquidateCollateral(loan_id, await floDapps.user.private)
notify('Collateral pre-liquidation request sent successfully', 'success')
} catch (err) {
notify(err, 'error')
} finally {
setIsRequestingCollateralPreLiquidation(false)
}
}
return html` return html`
<li class="loan"> <li class="loan">
${loanStatusBadge} ${loanStatusBadge}
@ -1167,40 +1179,44 @@
<p>Loan ID: ${loan_id}</p> <p>Loan ID: ${loan_id}</p>
</sm-copy> </sm-copy>
<div class="flex flex-wrap gap-1-5 align-items-start"> <div class="flex flex-wrap gap-1-5 align-items-start">
<div class="grid gap-0-3"> <div class="flex flex-wrap gap-1-5 align-items-start">
<p>Loan amount</p>
<b>${formatAmount(loan_amount, 'usd')}</b>
</div>
<div class="grid gap-0-3">
<p>Collateral amount</p>
<b>${formatAmount(collateral_value)}</b>
</div>
<div class="grid gap-0-3">
<p>Duration</p>
<b>${btcMortgage.policies[policy_id]?.duration}</b>
</div>
<div class="grid gap-0-3">
<p>Interest</p>
<b>${btcMortgage.policies[policy_id]?.interest * 100}% p.a</b>
</div>
<div class="grid gap-0-3">
<p>Opening date</p>
<b>${getFormattedTime(open_time)}</b>
</div>
${close_time ? html`
<div class="grid gap-0-3"> <div class="grid gap-0-3">
<p>Closing date</p> <p>Loan amount</p>
<b>${getFormattedTime(close_time)}</b> <b>${formatAmount(loan_amount, 'usd')}</b>
</div> </div>
`: html`
<div class="grid gap-0-3"> <div class="grid gap-0-3">
<p>Loan deadline</p> <p>Collateral amount</p>
<b>${getFormattedTime(open_time + decodeDateStringToMilliseconds(btcMortgage.policies[policy_id]?.duration))}</b> <b>${formatAmount(collateral_value)}</b>
</div> </div>
`} <div class="grid gap-0-3">
<p>Duration</p>
<b>${btcMortgage.policies[policy_id]?.duration}</b>
</div>
<div class="grid gap-0-3">
<p>Interest</p>
<b>${btcMortgage.policies[policy_id]?.interest * 100}% p.a</b>
</div>
</div>
<div class="flex flex-wrap gap-1-5 align-items-start">
<div class="grid gap-0-3">
<p>Opening date</p>
<b>${getFormattedTime(open_time)}</b>
</div>
${loanStatus === 'Closed' ? html`
<div class="grid gap-0-3">
<p>Closing date</p>
<b>${getFormattedTime(close_time || collateralUnlockAckTime)}</b>
</div>
`: html`
<div class="grid gap-0-3">
<p>Loan deadline</p>
<b>${getFormattedTime(open_time + decodeDateStringToMilliseconds(btcMortgage.policies[policy_id]?.duration))}</b>
</div>
`}
</div>
</div> </div>
<div class="grid gap-0-3"> <div class="grid gap-0-3">
<p>Interest ${isLender ? 'earned' : close_time ? 'paid' : 'accrued'}</p> <p>Interest ${isLender ? 'earned' : loanStatus === 'Closed' ? 'paid' : 'accrued'}</p>
<b style=${`color: ${isLender ? 'var(--green)' : 'var(--danger-color)'}`}>${formatAmount(amountDue - loan_amount, 'usd')}</b> <b style=${`color: ${isLender ? 'var(--green)' : 'var(--danger-color)'}`}>${formatAmount(amountDue - loan_amount, 'usd')}</b>
</div> </div>
<details> <details>
@ -1241,20 +1257,27 @@
</button> </button>
</div> </div>
`: ''} `: ''}
${isLender && isLoanOverdue ? html` ${loanStatus === 'closed' && isLender && isLoanOverdue ? html`
<div class="flex align-center space-between gap-1-5"> <div class="flex align-center space-between gap-1-5">
<div class="grid gap-0-3"> ${hasRequestedCollateralLiquidation ? html`
<h4>Loan repayment overdue</h4> <div class="grid gap-0-3">
<p>Borrower has not repaid the loan even after the deadline. You can now claim the collateral.</p> <h4>Collateral liquidation requested</h4>
</div> <p>Our banker will resolve your request within 48hrs.</p>
<button class="button button--primary margin-left-auto" disabled=${isClaimingCollateral} onclick=${claimCollateral}> </div>
${isClaimingCollateral ? html` `: html`
Claiming collateral <div class="grid gap-0-3">
<sm-spinner class="margin-left-0-5"></sm-spinner> <h4>Loan repayment overdue</h4>
`: html` <p>Borrower has not repaid the loan even after the deadline. You can now claim the collateral.</p>
Claim collateral </div>
`} <button class="button button--primary margin-left-auto" disabled=${isClaimingCollateral} onclick=${claimCollateral}>
</button> ${isClaimingCollateral ? html`
Claiming collateral
<sm-spinner class="margin-left-0-5"></sm-spinner>
`: html`
Claim collateral
`}
</button>
`}
</div> </div>
`: ''} `: ''}
</li> </li>
@ -1421,7 +1444,7 @@
} }
let userAddressTimeInterval; let userAddressTimeInterval;
function renderHome(appState) { function renderHome(appState) {
const { lastPage, page, params: { } = {}, wildcards: [section = 'my-loans'] = [] } = appState || {}; const { lastPage, page, params: { } = {}, wildcards = [] } = appState || {};
const balanceCard = Component(() => { const balanceCard = Component(() => {
const [btcBalance, setBtcBalance] = useState(floGlobals.memoBalance['BTC']) const [btcBalance, setBtcBalance] = useState(floGlobals.memoBalance['BTC'])
@ -1505,7 +1528,7 @@
case 'type_liquate_collateral_request': case 'type_liquate_collateral_request':
break; break;
case 'type_preliquate_collateral_request': case 'type_pre_liquidate_collateral_request':
break; break;
default: default:
@ -1536,18 +1559,14 @@
} else { } else {
// user homepage // user homepage
const inbox = Component(() => { const inbox = Component(() => {
const loanRequestsInProcess = Object.values(floGlobals.inProcessRequests).sort((a, b) => {
const aTime = a.initiationTime || a.loanResponseTime || a.loanClosedAckTime || a.unlockCollateralRequestTime;
const bTime = b.initiationTime || b.loanResponseTime || b.loanClosedAckTime || b.unlockCollateralRequestTime;
return bTime - aTime
})
const { borrowed, coBorrowed, lent } = groupLoans() const { borrowed, coBorrowed, lent } = groupLoans()
const [view, setView] = useState(section) const defaultView = wildcards.length ? wildcards[0] : uiGlobals.sortedProcesses.length ? 'in-process' : 'my-loans'
const [view, setView] = useState(defaultView)
let loanRequests = []; let loanRequests = [];
for (const key in floGlobals.loanRequests) { for (const key in floGlobals.loanRequests) {
const { message: { borrower, coborrower, loan_opening_process_id } } = floGlobals.loanRequests[key] const { message: { borrower, coborrower, loan_opening_process_id } } = floGlobals.loanRequests[key]
if (!loan_opening_process_id) continue // TODO: remove this check after all requests are updated if (!loan_opening_process_id) continue // TODO: remove this check after all requests are updated
if (floGlobals.loansOpeningInProcess.has(loan_opening_process_id)) continue // if loan request is in process, don't show loan request if (floGlobals.hasAgreedToLend.has(loan_opening_process_id)) continue // if user has already agreed to lend, don't show the request
loanRequests.push(key) loanRequests.push(key)
} }
function handleChange(e) { function handleChange(e) {
@ -1566,10 +1585,10 @@
return html` return html`
<section class="grid gap-1"> <section class="grid gap-1">
<sm-chips onchange=${handleChange}> <sm-chips onchange=${handleChange}>
${loanRequestsInProcess.length ? html` ${uiGlobals.sortedProcesses.length ? html`
<sm-chip value="in-process" selected=${view === 'in-process'}> <sm-chip value="in-process" selected=${view === 'in-process'}>
In process In process
<span class="badge">${loanRequestsInProcess.length}</span> <span class="badge">${uiGlobals.sortedProcesses.length}</span>
</sm-chip> </sm-chip>
` : ''} ` : ''}
<sm-chip value="my-loans" selected=${view === 'my-loans'}> <sm-chip value="my-loans" selected=${view === 'my-loans'}>
@ -1612,9 +1631,9 @@
</div> </div>
`} `}
`: ''} `: ''}
${view === 'in-process' && loanRequestsInProcess.length ? html` ${view === 'in-process' && uiGlobals.sortedProcesses.length ? html`
<ul class="grid gap-1"> <ul class="grid gap-1">
${loanRequestsInProcess.map(loan => render.loanProcess(loan))} ${uiGlobals.sortedProcesses.map(processId => render.loanProcess(processId))}
</ul> </ul>
`: ''} `: ''}
${view === 'coBorrowed' && coBorrowed.length ? html` ${view === 'coBorrowed' && coBorrowed.length ? html`
@ -1732,10 +1751,8 @@
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(floGlobals.myFloID) floGlobals.isSubAdmin = floGlobals.subAdmins.includes(floGlobals.myFloID)
floGlobals.isAdmin = floGlobals.myFloID === floGlobals.adminID floGlobals.isAdmin = floGlobals.myFloID === floGlobals.adminID
floGlobals.loaded = false floGlobals.loaded = false
floGlobals.myInbox = {}
floGlobals.myOutbox = {}
floGlobals.loanRequests = {} floGlobals.loanRequests = {}
floGlobals.requestTypes = {} uiGlobals.activeLoanQueries = {}
floGlobals.btcRate = 1 floGlobals.btcRate = 1
floGlobals.memoBalance = { floGlobals.memoBalance = {
'FLO': 0, 'FLO': 0,
@ -1759,24 +1776,23 @@
callback: (d, e) => { callback: (d, e) => {
if (e) return if (e) return
for (const key in d) { for (const key in d) {
if ( switch (d[key].type) {
d[key].type === 'type_unlock_collateral_request' || case 'type_unlock_collateral_request':
d[key].type === 'type_unlock_collateral_ack' || case 'type_unlock_collateral_ack':
d[key].type === 'type_refund_collateral_request' || case 'type_refund_collateral_request':
d[key].type === 'type_refund_collateral_ack' || case 'type_refund_collateral_ack':
d[key].type === 'type_liquate_collateral_request' || case 'type_liquate_collateral_request':
d[key].type === 'type_liquate_collateral_ack' || case 'type_liquate_collateral_ack':
d[key].type === 'type_preliquate_collateral_request' || case 'type_pre_liquidate_collateral_request':
d[key].type === 'type_preliquate_collateral_ack' case 'type_preLiquidate_collateral_ack':
) { floGlobals.bankerRequests = {
floGlobals.bankerRequests = { ...(floGlobals.bankerRequests || {}),
...floGlobals.bankerRequests || {}, [key]: d[key]
[key]: d[key] };
} break;
} }
} }
console.log('LOAN REQUESTS', d) console.log('LOAN REQUESTS', d)
processInProcessRequests()
if (floGlobals.loaded) { if (floGlobals.loaded) {
router.routeTo(window.location.hash) router.routeTo(window.location.hash)
} }
@ -1785,14 +1801,10 @@
} else { } else {
const result = await Promise.allSettled([ const result = await Promise.allSettled([
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
btcMortgage.viewMyInbox((d, e) => { btcMortgage.viewMyInbox((requests, e) => {
if (e) return if (e) return
floGlobals.myInbox = { console.log('MY INBOX', requests)
...floGlobals.myInbox, processInProcessRequests(requests, { isInbox: true })
...d
}
console.log('MY INBOX', d)
processInProcessRequests()
if (floGlobals.loaded) { if (floGlobals.loaded) {
router.routeTo(window.location.hash) router.routeTo(window.location.hash)
} }
@ -1802,14 +1814,15 @@
}) })
}), }),
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
btcMortgage.listLoanRequests((d, e) => { btcMortgage.listLoanRequests((requests, e) => {
if (e) return if (e) return
console.log('LOAN REQUESTS', requests)
floGlobals.loanRequests = { floGlobals.loanRequests = {
...floGlobals.loanRequests, ...floGlobals.loanRequests,
...d ...requests
} }
console.log('LOAN REQUESTS', d) // if loan requests are from me, process them
processInProcessRequests() processInProcessRequests(requests, { areLoanRequests: true })
if (floGlobals.loaded) { if (floGlobals.loaded) {
router.routeTo(window.location.hash) router.routeTo(window.location.hash)
} }
@ -1820,11 +1833,10 @@
}), }),
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
floCloudAPI.requestApplicationData('in_process_loan_request', { floCloudAPI.requestApplicationData('in_process_loan_request', {
callback: (d, e) => { callback: (requests, e) => {
if (e) return if (e) return
parseInProcessRequests(d) parseInProcessRequests(requests)
console.log('IN PROCESS LOAN REQUEST', d) console.log('IN PROCESS LOAN REQUEST', requests)
processInProcessRequests()
if (floGlobals.loaded) { if (floGlobals.loaded) {
router.routeTo(window.location.hash) router.routeTo(window.location.hash)
} }
@ -1835,19 +1847,15 @@
]) ])
} }
await Promise.all([...getAllInvolvedAddresses()].map(address => { await Promise.all([...uiGlobals.relatedAddresses].map(address => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
floCloudAPI.requestApplicationData(null, { floCloudAPI.requestApplicationData(null, {
senderID: floDapps.user.id, senderID: floDapps.user.id,
receiverID: address, receiverID: address,
callback: (d, e) => { callback: (requests, e) => {
if (e) return if (e) return
floGlobals.myOutbox = { console.log('MY OUTBOX', requests)
...floGlobals.myOutbox || {}, processInProcessRequests(requests)
...d
}
console.log('MY OUTBOX', d)
processInProcessRequests()
if (floGlobals.loaded) { if (floGlobals.loaded) {
router.routeTo(window.location.hash) router.routeTo(window.location.hash)
} }
@ -1857,7 +1865,6 @@
}) })
})) }))
console.log(result) console.log(result)
processInProcessRequests()
if (['#/landing', '#/sign_in', '#/sign_up'].includes(window.location.hash)) { if (['#/landing', '#/sign_in', '#/sign_up'].includes(window.location.hash)) {
history.replaceState(null, null, '#/home') history.replaceState(null, null, '#/home')
router.routeTo('home') router.routeTo('home')
@ -1880,31 +1887,20 @@
}) })
}).catch(error => console.error(error)) }).catch(error => console.error(error))
} }
/**
* @param {Object} requests
*/
function parseInProcessRequests(requests) { function parseInProcessRequests(requests) {
if (!floGlobals.loansOpeningInProcess) if (!floGlobals.hasAgreedToLend)
floGlobals.loansOpeningInProcess = new Set() floGlobals.hasAgreedToLend = new Set()
for (const key in requests) { for (const key in requests) {
const { message: { loan_opening_process_id, closing_txid } } = requests[key] const { message: { loan_opening_process_id, closing_txid } } = requests[key]
floGlobals.loansOpeningInProcess.add(loan_opening_process_id) floGlobals.hasAgreedToLend.add(loan_opening_process_id)
} }
} }
function getAllInvolvedAddresses() { // get all addresses involved in loan process (except my address) /**
const addresses = new Set() * @param {string} inputString
for (const key in floGlobals.myInbox) { */
const { message: { borrower, coborrower, lender } } = floGlobals.myInbox[key]
if (borrower)
addresses.add(borrower)
if (coborrower)
addresses.add(coborrower)
if (lender)
addresses.add(lender)
}
if (addresses.has(floGlobals.myFloID))
addresses.delete(floGlobals.myFloID)
if (addresses.has(floGlobals.myBtcID))
addresses.delete(floGlobals.myBtcID)
return addresses
}
function decodeDateStringToMilliseconds(inputString) { function decodeDateStringToMilliseconds(inputString) {
// Define a mapping for units to milliseconds (Y: years, M: months, D: days) // Define a mapping for units to milliseconds (Y: years, M: months, D: days)
const unitToMilliseconds = { const unitToMilliseconds = {
@ -1931,22 +1927,18 @@
return null; return null;
} }
const processedRequests = new Set() const completedLoanProcess = new Set()
floGlobals.inProcessRequests = {} uiGlobals.inProcessRequests = {}
function processInProcessRequests() { uiGlobals.sortedProcesses = []
let myLoanRequests = {} uiGlobals.relatedAddresses = new Set()
for (const key in floGlobals.loanRequests) { /**
const { message: { borrower } } = floGlobals.loanRequests[key] * @param {Object} newRequests
if (floCrypto.isSameAddr(borrower, floGlobals.myFloID)) { * @param {Object} options
myLoanRequests[key] = floGlobals.loanRequests[key] * @param {Boolean} options.isInbox
} */
} function processInProcessRequests(newRequests = {}, options = {}) {
const allMessages = { const { isInbox = false, areLoanRequests = false } = options
...floGlobals.myInbox, for (const key in newRequests) {
...myLoanRequests,
...floGlobals.myOutbox
}
for (const key in allMessages) {
const { const {
message: { message: {
borrower, coborrower, lender, borrower, coborrower, lender,
@ -1954,16 +1946,17 @@
loan_id, closing_txid, unlock_tx_hex, unlock_collateral_id, type_unlock_collateral_ack loan_id, closing_txid, unlock_tx_hex, unlock_collateral_id, type_unlock_collateral_ack
} = {}, } = {},
type, time type, time
} = allMessages[key]; } = newRequests[key];
if (processedRequests.has(loan_opening_process_id) || Object.values(btcMortgage.loans).find(loan => loan.loan_opening_process_id === loan_opening_process_id)) { if (borrower && areLoanRequests && !floCrypto.isSameAddr(borrower, floDapps.user.id)) continue // if loan request is not from me, ignore
processedRequests.add(loan_opening_process_id)
continue // Request has been processed
}
if (loan_id && btcMortgage.loans[loan_id] && btcMortgage.loans[loan_id].close_time) if (loan_id && btcMortgage.loans[loan_id] && btcMortgage.loans[loan_id].close_time)
continue // Loan closed continue // Loan closed
if (loan_opening_process_id) { if (loan_opening_process_id) {
if (!floGlobals.inProcessRequests[loan_opening_process_id]) { if (completedLoanProcess.has(loan_opening_process_id) || Object.values(btcMortgage.loans).find(loan => loan.loan_opening_process_id === loan_opening_process_id)) {
floGlobals.inProcessRequests[loan_opening_process_id] = { completedLoanProcess.add(loan_opening_process_id)
continue // Request has been processed
}
if (!uiGlobals.inProcessRequests[loan_opening_process_id]) {
uiGlobals.inProcessRequests[loan_opening_process_id] = {
loanOpeningProcessID: loan_opening_process_id, loanOpeningProcessID: loan_opening_process_id,
hasProvidedCollateral: false, hasProvidedCollateral: false,
hasAgreedToLend: false, hasAgreedToLend: false,
@ -1972,113 +1965,154 @@
} }
} }
if (borrower) { if (borrower) {
floGlobals.inProcessRequests[loan_opening_process_id].borrower = borrower uiGlobals.inProcessRequests[loan_opening_process_id].borrower = borrower
floGlobals.inProcessRequests[loan_opening_process_id].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id) uiGlobals.inProcessRequests[loan_opening_process_id].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
if (isInbox)
uiGlobals.relatedAddresses.add(borrower)
} }
if (coborrower) { if (coborrower) {
floGlobals.inProcessRequests[loan_opening_process_id].coborrower = coborrower uiGlobals.inProcessRequests[loan_opening_process_id].coborrower = coborrower
floGlobals.inProcessRequests[loan_opening_process_id].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id) uiGlobals.inProcessRequests[loan_opening_process_id].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
} if (isInbox)
if (loan_amount) { uiGlobals.relatedAddresses.add(coborrower)
floGlobals.inProcessRequests[loan_opening_process_id].loanAmount = loan_amount
}
if (policy_id) {
floGlobals.inProcessRequests[loan_opening_process_id].policyID = policy_id
} }
if (lender) { if (lender) {
floGlobals.inProcessRequests[loan_opening_process_id].lender = lender uiGlobals.inProcessRequests[loan_opening_process_id].lender = lender
floGlobals.inProcessRequests[loan_opening_process_id].isLender = floCrypto.isSameAddr(lender, floDapps.user.id) uiGlobals.inProcessRequests[loan_opening_process_id].isLender = floCrypto.isSameAddr(lender, floDapps.user.id)
if (isInbox)
uiGlobals.relatedAddresses.add(lender)
}
if (loan_amount) {
uiGlobals.inProcessRequests[loan_opening_process_id].loanAmount = loan_amount
}
if (policy_id) {
uiGlobals.inProcessRequests[loan_opening_process_id].policyID = policy_id
} }
if (loan_request_id) { if (loan_request_id) {
floGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = loan_request_id uiGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = loan_request_id
floGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true uiGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true
} }
if (collateral_lock_id) { if (collateral_lock_id) {
floGlobals.inProcessRequests[loan_opening_process_id].collateralLockID = collateral_lock_id uiGlobals.inProcessRequests[loan_opening_process_id].collateralLockID = collateral_lock_id
} }
switch (type) { switch (type) {
case 'type_loan_collateral_request': case 'type_loan_collateral_request':
floGlobals.inProcessRequests[loan_opening_process_id].initiationTime = time uiGlobals.inProcessRequests[loan_opening_process_id].initiationTime = time
floGlobals.inProcessRequests[loan_opening_process_id].collateralRequestID = key uiGlobals.inProcessRequests[loan_opening_process_id].collateralRequestID = key
break; break;
case 'type_loan_request': case 'type_loan_request':
floGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true uiGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true
floGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = key uiGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = key
break; break;
case 'type_loan_response': case 'type_loan_response':
floGlobals.inProcessRequests[loan_opening_process_id].loanResponseTime = time uiGlobals.inProcessRequests[loan_opening_process_id].loanResponseTime = time
floGlobals.inProcessRequests[loan_opening_process_id].hasAgreedToLend = true uiGlobals.inProcessRequests[loan_opening_process_id].hasAgreedToLend = true
floGlobals.inProcessRequests[loan_opening_process_id].loanResponseID = key uiGlobals.inProcessRequests[loan_opening_process_id].loanResponseID = key
break; break;
case "type_collateral_lock_request": case "type_collateral_lock_request":
floGlobals.inProcessRequests[loan_opening_process_id].collateralLockRequestID = key uiGlobals.inProcessRequests[loan_opening_process_id].collateralLockRequestID = key
break; break;
case 'type_collateral_lock_ack': case 'type_collateral_lock_ack':
floGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckTime = time uiGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckTime = time
floGlobals.inProcessRequests[loan_opening_process_id].hasLockedCollateral = true uiGlobals.inProcessRequests[loan_opening_process_id].hasLockedCollateral = true
floGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckID = key uiGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckID = key
break; break;
case 'type_refund_collateral_request': case 'type_refund_collateral_request':
floGlobals.inProcessRequests[loan_opening_process_id].hasRequestedCollateralRefund = true uiGlobals.inProcessRequests[loan_opening_process_id].hasRequestedCollateralRefund = true
break; break;
} }
} else if (closing_txid) { } else {
if (!floGlobals.inProcessRequests[closing_txid]) if (loan_id) {
floGlobals.inProcessRequests[closing_txid] = { if (!uiGlobals.activeLoanQueries[loan_id])
loanID: loan_id, uiGlobals.activeLoanQueries[loan_id] = {
closingTxID: closing_txid, loanID: loan_id,
hasRequestedCollateralRefund: false, }
hasRefundedCollateral: false, switch (type) {
hasPaidLoan: false, case 'type_loan_closed_ack':
type: 'loanClosing' uiGlobals.activeLoanQueries[loan_id].hasRepaidLoan = true
break;
case 'type_unlock_collateral_request':
uiGlobals.activeLoanQueries[loan_id].hasRequestedCollateralUnlock = true
break;
case 'type_unlock_collateral_ack':
uiGlobals.activeLoanQueries[loan_id].hasRefundedCollateral = true
uiGlobals.activeLoanQueries[loan_id].collateralUnlockAckTime = time
delete uiGlobals.inProcessRequests[closing_txid]
completedLoanProcess.add(closing_txid)
break;
case 'type_liquate_collateral_request':
uiGlobals.activeLoanQueries[loan_id].hasRequestedCollateralLiquidation = true
break;
case 'type_liquate_collateral_ack':
uiGlobals.activeLoanQueries[loan_id].hasLiquidatedCollateral = true
break;
case 'type_pre_liquidate_collateral_request':
uiGlobals.activeLoanQueries[loan_id].hasRequestedCollateralPreLiquidation = true
break;
} }
const { borrower, coborrower, lender } = btcMortgage.loans[loan_id]
if (borrower) {
floGlobals.inProcessRequests[closing_txid].borrower = borrower
floGlobals.inProcessRequests[closing_txid].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
} }
if (coborrower) { if (closing_txid && !completedLoanProcess.has(closing_txid)) {
floGlobals.inProcessRequests[closing_txid].coborrower = coborrower if (!uiGlobals.inProcessRequests[closing_txid])
floGlobals.inProcessRequests[closing_txid].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id) uiGlobals.inProcessRequests[closing_txid] = {
} loanID: loan_id,
if (lender) { closingTxID: closing_txid,
floGlobals.inProcessRequests[closing_txid].lender = lender hasRequestedCollateralRefund: false,
floGlobals.inProcessRequests[closing_txid].isLender = floCrypto.isSameAddr(lender, floDapps.user.id) hasRefundedCollateral: false,
} hasPaidLoan: false,
if (unlock_tx_hex) { type: 'loanClosing'
floGlobals.inProcessRequests[closing_txid].unlockTxHex = unlock_tx_hex }
} const { borrower, coborrower, lender } = btcMortgage.loans[loan_id]
switch (type) { if (borrower) {
case 'type_loan_closed_ack': uiGlobals.inProcessRequests[closing_txid].borrower = borrower
floGlobals.inProcessRequests[closing_txid].loanClosedAckTime = time uiGlobals.inProcessRequests[closing_txid].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
floGlobals.inProcessRequests[closing_txid].hasPaidLoan = true if (isInbox)
break; uiGlobals.relatedAddresses.add(borrower)
case 'type_unlock_collateral_request': }
floGlobals.inProcessRequests[closing_txid].unlockCollateralRequestTime = time if (coborrower) {
floGlobals.inProcessRequests[closing_txid].hasRequestedCollateralUnlock = true uiGlobals.inProcessRequests[closing_txid].coborrower = coborrower
break; uiGlobals.inProcessRequests[closing_txid].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
case 'type_unlock_collateral_ack': if (isInbox)
floGlobals.inProcessRequests[closing_txid].unlockCollateralAckTime = time uiGlobals.relatedAddresses.add(coborrower)
floGlobals.inProcessRequests[closing_txid].hasRefundedCollateral = true }
break; if (lender) {
uiGlobals.inProcessRequests[closing_txid].lender = lender
uiGlobals.inProcessRequests[closing_txid].isLender = floCrypto.isSameAddr(lender, floDapps.user.id)
if (isInbox)
uiGlobals.relatedAddresses.add(lender)
}
if (unlock_tx_hex) {
uiGlobals.inProcessRequests[closing_txid].unlockTxHex = unlock_tx_hex
}
switch (type) {
case 'type_loan_closed_ack':
uiGlobals.inProcessRequests[closing_txid].loanClosedAckTime = time
uiGlobals.inProcessRequests[closing_txid].hasPaidLoan = true
break;
case 'type_unlock_collateral_request':
uiGlobals.inProcessRequests[closing_txid].unlockCollateralRequestTime = time
uiGlobals.inProcessRequests[closing_txid].hasRequestedCollateralUnlock = true
break;
case 'type_unlock_collateral_ack':
uiGlobals.inProcessRequests[closing_txid].unlockCollateralAckTime = time
uiGlobals.inProcessRequests[closing_txid].hasRefundedCollateral = true
completedLoanProcess.add(closing_txid)
break;
}
} }
} }
} }
// .reduce((acc, loan) => { // group by borrower, coborrower and lender if (uiGlobals.relatedAddresses.has(floGlobals.myBtcID))
// if (loan.isBorrower) uiGlobals.relatedAddresses.delete(floGlobals.myBtcID)
// acc.borrowing.push(loan) if (uiGlobals.relatedAddresses.has(floGlobals.myFloID))
// else if (loan.isCoborrower) uiGlobals.relatedAddresses.delete(floGlobals.myFloID)
// acc.coBorrowing.push(loan) uiGlobals.sortedProcesses = Object.keys(uiGlobals.inProcessRequests).sort((a, b) => {
// else if (loan.isLender) const aTime = uiGlobals.inProcessRequests[a].initiationTime || uiGlobals.inProcessRequests[a].loanResponseTime || uiGlobals.inProcessRequests[a].loanClosedAckTime || uiGlobals.inProcessRequests[a].unlockCollateralRequestTime;
// acc.lending.push(loan) const bTime = uiGlobals.inProcessRequests[b].initiationTime || uiGlobals.inProcessRequests[b].loanResponseTime || uiGlobals.inProcessRequests[b].loanClosedAckTime || uiGlobals.inProcessRequests[b].unlockCollateralRequestTime;
// return acc return bTime - aTime
// }, { })
// borrowing: [],
// coBorrowing: [],
// lending: []
// })
} }
function groupLoans() { function groupLoans() {
return Object.values(btcMortgage.loans) return Object.values(btcMortgage.loans)
.sort((a, b) => b.open_time - a.open_time) .sort((a, b) => b.open_time - a.open_time)

View File

@ -30,8 +30,7 @@
TYPE_REFUND_COLLATERAL_ACK = "type_refund_collateral_ack", TYPE_REFUND_COLLATERAL_ACK = "type_refund_collateral_ack",
TYPE_LIQUATE_COLLATERAL_REQUEST = "type_liquate_collateral_request", TYPE_LIQUATE_COLLATERAL_REQUEST = "type_liquate_collateral_request",
TYPE_LIQUATE_COLLATERAL_ACK = "type_liquate_collateral_ack", TYPE_LIQUATE_COLLATERAL_ACK = "type_liquate_collateral_ack",
TYPE_PRELIQUATE_COLLATERAL_REQUEST = "type_preliquate_collateral_request", TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST = "type_pre_liquidate_collateral_request"
TYPE_PRELIQUATE_COLLATERAL_ACK = "type_preliquate_collateral_ack";
const POLICIES = {}, LOANS = {}; const POLICIES = {}, LOANS = {};
const owned_collateral_locks = {}; const owned_collateral_locks = {};
@ -124,7 +123,7 @@
const util = btcMortgage.util = { const util = btcMortgage.util = {
toFixedDecimal, toFixedDecimal,
encodePeriod, decodePeriod, encodePeriod, decodePeriod,
calcAllowedLoan, calcRequiredCollateral, calcDueAmount, calcAllowedLoan, calcRequiredCollateral, calcDueAmount, calcRateRatio,
findLocker, extractPubKeyFromSign findLocker, extractPubKeyFromSign
} }
@ -1495,7 +1494,7 @@
resolve(result); resolve(result);
}).catch(error => reject(error)) }).catch(error => reject(error))
}).catch(error => { }).catch(error => {
compactIDB.writeData("fail_safe", loan_blockchain_data, token_txid); //fail-safe mech if token is transfered but details not added to blockchain. this helps to retry fail-safe compactIDB.writeData("fail_safe", loan_blockchain_data, token_txid); //fail-safe mech if token is transferred but details not added to blockchain. this helps to retry fail-safe
reject({ error, fail_safe: token_txid }) reject({ error, fail_safe: token_txid })
}) })
}).catch(error => reject(error)) }).catch(error => reject(error))
@ -1516,7 +1515,7 @@
if (!floCrypto.isSameAddr(loan_details.lender, collateral_liquate_req.senderID)) if (!floCrypto.isSameAddr(loan_details.lender, collateral_liquate_req.senderID))
return reject(RequestValidationError(TYPE_LIQUATE_COLLATERAL_REQUEST, "request not sent by lender")); return reject(RequestValidationError(TYPE_LIQUATE_COLLATERAL_REQUEST, "request not sent by lender"));
if (!verify_liquidationSign(liquidation_sign, loan_details.lender, loan_id, loan_details.lender_sign, btc_liquid_rate)) if (!verify_liquidationSign(liquidation_sign, loan_details.lender, loan_id, loan_details.lender_sign, btc_liquid_rate))
return reject("Invalid liquiadtion signature"); return reject("Invalid liquidation signature");
checkIfLoanClosedFailed(loan, loan_details.borrower, loan_details.lender).then(result => { checkIfLoanClosedFailed(loan, loan_details.borrower, loan_details.lender).then(result => {
if (result) //close/fail loan data found if (result) //close/fail loan data found
return reject(RequestValidationError(TYPE_LIQUATE_COLLATERAL_REQUEST, "Loan already closed")); return reject(RequestValidationError(TYPE_LIQUATE_COLLATERAL_REQUEST, "Loan already closed"));
@ -1527,8 +1526,8 @@
}) })
} }
// L: request T (banker) to preliquidate collateral when collateral value has dropped to risk threshold // L: request T (banker) to pre-liquidate collateral when collateral value has dropped to risk threshold
btcMortgage.requestBanker.preliquateCollateral = function (loan_id, privKey) { btcMortgage.requestBanker.preLiquidateCollateral = function (loan_id, privKey) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let lender_pubKey = floDapps.user.public; let lender_pubKey = floDapps.user.public;
getLoanDetails(loan_id).then(loan_details => { getLoanDetails(loan_id).then(loan_details => {
@ -1552,7 +1551,7 @@
floCloudAPI.sendApplicationData({ floCloudAPI.sendApplicationData({
loan_id, liquidation_sign, loan_id, liquidation_sign,
liquidate_tx_hex: txHex liquidate_tx_hex: txHex
}, TYPE_PRELIQUATE_COLLATERAL_REQUEST) }, TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST)
.then(result => { .then(result => {
compactIDB.addData("outbox", result, result.vectorClock); compactIDB.addData("outbox", result, result.vectorClock);
resolve(result); resolve(result);
@ -1564,9 +1563,9 @@
}) })
} }
btcMortgage.banker.preliquateCollateral = function (collateral_preliquate_req_id, privKey) { btcMortgage.banker.preLiquidateCollateral = function (collateral_pre_liquidate_req_id, privKey) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
validate_preliquateCollateral_request(collateral_preliquate_req_id).then(result => { validate_pre_liquidateCollateral_request(collateral_pre_liquidate_req_id).then(result => {
let { loan_details, liquidate_tx_hex, btc_liquid_rate, liquidation_sign } = result; let { loan_details, liquidate_tx_hex, btc_liquid_rate, liquidation_sign } = result;
//calculate due amount //calculate due amount
let due_amount = calcDueAmount(loan_details.loan_amount, loan_details.policy_id, loan_details.open_time) let due_amount = calcDueAmount(loan_details.loan_amount, loan_details.policy_id, loan_details.open_time)
@ -1591,7 +1590,7 @@
resolve(result); resolve(result);
}).catch(error => reject(error)) }).catch(error => reject(error))
}).catch(error => { }).catch(error => {
compactIDB.writeData("fail_safe", loan_blockchain_data, token_txid); //fail-safe mech if token is transfered but details not added to blockchain. this helps to retry fail-safe compactIDB.writeData("fail_safe", loan_blockchain_data, token_txid); //fail-safe mech if token is transferred but details not added to blockchain. this helps to retry fail-safe
reject({ error, fail_safe: token_txid }) reject({ error, fail_safe: token_txid })
}) })
}).catch(error => reject(error)) }).catch(error => reject(error))
@ -1601,30 +1600,30 @@
}) })
} }
function validate_preliquateCollateral_request(collateral_preliquate_req_id) { function validate_pre_liquidateCollateral_request(collateral_pre_liquidate_req_id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
floCloudAPI.requestApplicationData(TYPE_PRELIQUATE_COLLATERAL_REQUEST, { atVectorClock: collateral_preliquate_req_id }).then(collateral_preliquate_req => { floCloudAPI.requestApplicationData(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, { atVectorClock: collateral_pre_liquidate_req_id }).then(collateral_pre_liquidate_req => {
collateral_preliquate_req = collateral_preliquate_req[collateral_preliquate_req_id]; collateral_pre_liquidate_req = collateral_pre_liquidate_req[collateral_pre_liquidate_req_id];
if (!collateral_preliquate_req) if (!collateral_pre_liquidate_req)
return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "request not found")); return reject(RequestValidationError(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, "request not found"));
let { loan_id, liquidation_sign, btc_liquid_rate, liquidate_tx_hex } = collateral_preliquate_req.message; let { loan_id, liquidation_sign, btc_liquid_rate, liquidate_tx_hex } = collateral_pre_liquidate_req.message;
getLoanDetails(loan_id).then(loan_details => { getLoanDetails(loan_id).then(loan_details => {
if (!floCrypto.isSameAddr(loan_details.lender, collateral_preliquate_req.senderID)) if (!floCrypto.isSameAddr(loan_details.lender, collateral_pre_liquidate_req.senderID))
return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "request not sent by lender")); return reject(RequestValidationError(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, "request not sent by lender"));
if (!verify_liquidationSign(liquidation_sign, loan_details.lender, loan_id, loan_details.lender_sign, btc_liquid_rate)) if (!verify_liquidationSign(liquidation_sign, loan_details.lender, loan_id, loan_details.lender_sign, btc_liquid_rate))
return reject("Invalid liquiadtion signature"); return reject("Invalid liquidation signature");
checkIfLoanClosedFailed(loan, loan_details.borrower, loan_details.lender).then(result => { checkIfLoanClosedFailed(loan, loan_details.borrower, loan_details.lender).then(result => {
if (result) //close/fail loan data found if (result) //close/fail loan data found
return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "Loan already closed")); return reject(RequestValidationError(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, "Loan already closed"));
let policy = POLICIES[loan_details.policy_id]; let policy = POLICIES[loan_details.policy_id];
if (isNaN(policy.pre_liquidation_threshold)) if (isNaN(policy.pre_liquidation_threshold))
return reject("This loan policy doesn't allow pre-liquidation"); return reject("This loan policy doesn't allow pre-liquidation");
getRate["USD"].then(cur_btc_rate => { getRate["USD"].then(cur_btc_rate => {
if (cur_btc_rate >= loan_details.btc_start_rate) if (cur_btc_rate >= loan_details.btc_start_rate)
return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "BTC rate hasn't reduced from the start rate")); return reject(RequestValidationError(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, "BTC rate hasn't reduced from the start rate"));
let current_rate_ratio = calcRateRatio(cur_btc_rate, loan_details.btc_start_rate) let current_rate_ratio = calcRateRatio(cur_btc_rate, loan_details.btc_start_rate)
if (current_rate_ratio > policy.pre_liquidation_threshold) if (current_rate_ratio > policy.pre_liquidation_threshold)
return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "BTC rate hasn't dropped beyond threshold")); return reject(RequestValidationError(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, "BTC rate hasn't dropped beyond threshold"));
resolve({ loan_details, liquidate_tx_hex, btc_liquid_rate, liquidation_sign }); resolve({ loan_details, liquidate_tx_hex, btc_liquid_rate, liquidation_sign });
}).catch(error => reject(error)) }).catch(error => reject(error))
}).catch(error => reject(error)) }).catch(error => reject(error))