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:
parent
5d40939171
commit
c6affb6913
@ -50,10 +50,6 @@ strong {
|
||||
line-height: 1.7;
|
||||
color: rgba(var(--text-color), 0.9);
|
||||
}
|
||||
p:not(:last-of-type),
|
||||
strong:not(:last-of-type) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -48,10 +48,6 @@ strong {
|
||||
max-width: 65ch;
|
||||
line-height: 1.7;
|
||||
color: rgba(var(--text-color), 0.9);
|
||||
|
||||
&:not(:last-of-type) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
490
index.html
490
index.html
@ -643,7 +643,7 @@
|
||||
`: html``}
|
||||
`
|
||||
},
|
||||
loanProcess(details = {}) {
|
||||
loanProcess(processId) {
|
||||
const {
|
||||
type,
|
||||
loanOpeningProcessID,
|
||||
@ -653,7 +653,7 @@
|
||||
collateralRequestID, loanResponseID, collateralLockRequestID, collateralLockAckID, loanID, closingTxID,
|
||||
unlockCollateralRequestTime, hasRequestedCollateralUnlock, unlockCollateralAckTime, hasRefundedCollateral,
|
||||
unlockTxHex
|
||||
} = details
|
||||
} = uiGlobals.inProcessRequests[processId]
|
||||
if (type === 'loanOpening') {
|
||||
return Component(() => {
|
||||
const [verifyingCollateral, setVerifyCollateral] = useState(false)
|
||||
@ -1086,6 +1086,8 @@
|
||||
lender, lender_sign, loan_amount, loan_id, loan_opening_process_id, loan_transfer_id,
|
||||
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 [isRepayingLoan, setIsRepayingLoan] = useState(false)
|
||||
async function initLoanRepayment() {
|
||||
@ -1109,10 +1111,9 @@
|
||||
const isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
|
||||
const isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
|
||||
const isLender = floCrypto.isSameAddr(lender, floDapps.user.id)
|
||||
let hasStartedRepayment = false
|
||||
let loanStatus = '';
|
||||
let loanStatusBadge = '';
|
||||
if (close_time) {
|
||||
if (close_time || hasRefundedCollateral) {
|
||||
loanStatus = 'Closed'
|
||||
loanStatusBadge = html`
|
||||
<div class="status">
|
||||
@ -1121,12 +1122,7 @@
|
||||
</div>
|
||||
`
|
||||
} else {
|
||||
hasStartedRepayment = Object.values({
|
||||
...floGlobals.myInbox,
|
||||
...floGlobals.myOutbox
|
||||
})
|
||||
.find(request => request.message.loan_id === loanID)
|
||||
if (hasStartedRepayment) {
|
||||
if (hasRepaidLoan) {
|
||||
loanStatus = 'Repaying'
|
||||
loanStatusBadge = html`
|
||||
<div class="status">
|
||||
@ -1160,6 +1156,22 @@
|
||||
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`
|
||||
<li class="loan">
|
||||
${loanStatusBadge}
|
||||
@ -1167,40 +1179,44 @@
|
||||
<p>Loan ID: ${loan_id}</p>
|
||||
</sm-copy>
|
||||
<div class="flex flex-wrap gap-1-5 align-items-start">
|
||||
<div class="grid gap-0-3">
|
||||
<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="flex flex-wrap gap-1-5 align-items-start">
|
||||
<div class="grid gap-0-3">
|
||||
<p>Closing date</p>
|
||||
<b>${getFormattedTime(close_time)}</b>
|
||||
</div>
|
||||
`: html`
|
||||
<div class="grid gap-0-3">
|
||||
<p>Loan deadline</p>
|
||||
<b>${getFormattedTime(open_time + decodeDateStringToMilliseconds(btcMortgage.policies[policy_id]?.duration))}</b>
|
||||
<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>
|
||||
<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 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>
|
||||
</div>
|
||||
<details>
|
||||
@ -1241,20 +1257,27 @@
|
||||
</button>
|
||||
</div>
|
||||
`: ''}
|
||||
${isLender && isLoanOverdue ? html`
|
||||
${loanStatus === 'closed' && isLender && isLoanOverdue ? html`
|
||||
<div class="flex align-center space-between gap-1-5">
|
||||
<div class="grid gap-0-3">
|
||||
<h4>Loan repayment overdue</h4>
|
||||
<p>Borrower has not repaid the loan even after the deadline. You can now claim the collateral.</p>
|
||||
</div>
|
||||
<button class="button button--primary margin-left-auto" disabled=${isClaimingCollateral} onclick=${claimCollateral}>
|
||||
${isClaimingCollateral ? html`
|
||||
Claiming collateral
|
||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||
`: html`
|
||||
Claim collateral
|
||||
`}
|
||||
</button>
|
||||
${hasRequestedCollateralLiquidation ? html`
|
||||
<div class="grid gap-0-3">
|
||||
<h4>Collateral liquidation requested</h4>
|
||||
<p>Our banker will resolve your request within 48hrs.</p>
|
||||
</div>
|
||||
`: html`
|
||||
<div class="grid gap-0-3">
|
||||
<h4>Loan repayment overdue</h4>
|
||||
<p>Borrower has not repaid the loan even after the deadline. You can now claim the collateral.</p>
|
||||
</div>
|
||||
<button class="button button--primary margin-left-auto" disabled=${isClaimingCollateral} onclick=${claimCollateral}>
|
||||
${isClaimingCollateral ? html`
|
||||
Claiming collateral
|
||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||
`: html`
|
||||
Claim collateral
|
||||
`}
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
`: ''}
|
||||
</li>
|
||||
@ -1421,7 +1444,7 @@
|
||||
}
|
||||
let userAddressTimeInterval;
|
||||
function renderHome(appState) {
|
||||
const { lastPage, page, params: { } = {}, wildcards: [section = 'my-loans'] = [] } = appState || {};
|
||||
const { lastPage, page, params: { } = {}, wildcards = [] } = appState || {};
|
||||
|
||||
const balanceCard = Component(() => {
|
||||
const [btcBalance, setBtcBalance] = useState(floGlobals.memoBalance['BTC'])
|
||||
@ -1505,7 +1528,7 @@
|
||||
case 'type_liquate_collateral_request':
|
||||
|
||||
break;
|
||||
case 'type_preliquate_collateral_request':
|
||||
case 'type_pre_liquidate_collateral_request':
|
||||
|
||||
break;
|
||||
default:
|
||||
@ -1536,18 +1559,14 @@
|
||||
} else {
|
||||
// user homepage
|
||||
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 [view, setView] = useState(section)
|
||||
const defaultView = wildcards.length ? wildcards[0] : uiGlobals.sortedProcesses.length ? 'in-process' : 'my-loans'
|
||||
const [view, setView] = useState(defaultView)
|
||||
let loanRequests = [];
|
||||
for (const key in floGlobals.loanRequests) {
|
||||
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 (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)
|
||||
}
|
||||
function handleChange(e) {
|
||||
@ -1566,10 +1585,10 @@
|
||||
return html`
|
||||
<section class="grid gap-1">
|
||||
<sm-chips onchange=${handleChange}>
|
||||
${loanRequestsInProcess.length ? html`
|
||||
${uiGlobals.sortedProcesses.length ? html`
|
||||
<sm-chip value="in-process" selected=${view === 'in-process'}>
|
||||
In process
|
||||
<span class="badge">${loanRequestsInProcess.length}</span>
|
||||
<span class="badge">${uiGlobals.sortedProcesses.length}</span>
|
||||
</sm-chip>
|
||||
` : ''}
|
||||
<sm-chip value="my-loans" selected=${view === 'my-loans'}>
|
||||
@ -1612,9 +1631,9 @@
|
||||
</div>
|
||||
`}
|
||||
`: ''}
|
||||
${view === 'in-process' && loanRequestsInProcess.length ? html`
|
||||
${view === 'in-process' && uiGlobals.sortedProcesses.length ? html`
|
||||
<ul class="grid gap-1">
|
||||
${loanRequestsInProcess.map(loan => render.loanProcess(loan))}
|
||||
${uiGlobals.sortedProcesses.map(processId => render.loanProcess(processId))}
|
||||
</ul>
|
||||
`: ''}
|
||||
${view === 'coBorrowed' && coBorrowed.length ? html`
|
||||
@ -1732,10 +1751,8 @@
|
||||
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(floGlobals.myFloID)
|
||||
floGlobals.isAdmin = floGlobals.myFloID === floGlobals.adminID
|
||||
floGlobals.loaded = false
|
||||
floGlobals.myInbox = {}
|
||||
floGlobals.myOutbox = {}
|
||||
floGlobals.loanRequests = {}
|
||||
floGlobals.requestTypes = {}
|
||||
uiGlobals.activeLoanQueries = {}
|
||||
floGlobals.btcRate = 1
|
||||
floGlobals.memoBalance = {
|
||||
'FLO': 0,
|
||||
@ -1759,24 +1776,23 @@
|
||||
callback: (d, e) => {
|
||||
if (e) return
|
||||
for (const key in d) {
|
||||
if (
|
||||
d[key].type === 'type_unlock_collateral_request' ||
|
||||
d[key].type === 'type_unlock_collateral_ack' ||
|
||||
d[key].type === 'type_refund_collateral_request' ||
|
||||
d[key].type === 'type_refund_collateral_ack' ||
|
||||
d[key].type === 'type_liquate_collateral_request' ||
|
||||
d[key].type === 'type_liquate_collateral_ack' ||
|
||||
d[key].type === 'type_preliquate_collateral_request' ||
|
||||
d[key].type === 'type_preliquate_collateral_ack'
|
||||
) {
|
||||
floGlobals.bankerRequests = {
|
||||
...floGlobals.bankerRequests || {},
|
||||
[key]: d[key]
|
||||
}
|
||||
switch (d[key].type) {
|
||||
case 'type_unlock_collateral_request':
|
||||
case 'type_unlock_collateral_ack':
|
||||
case 'type_refund_collateral_request':
|
||||
case 'type_refund_collateral_ack':
|
||||
case 'type_liquate_collateral_request':
|
||||
case 'type_liquate_collateral_ack':
|
||||
case 'type_pre_liquidate_collateral_request':
|
||||
case 'type_preLiquidate_collateral_ack':
|
||||
floGlobals.bankerRequests = {
|
||||
...(floGlobals.bankerRequests || {}),
|
||||
[key]: d[key]
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log('LOAN REQUESTS', d)
|
||||
processInProcessRequests()
|
||||
if (floGlobals.loaded) {
|
||||
router.routeTo(window.location.hash)
|
||||
}
|
||||
@ -1785,14 +1801,10 @@
|
||||
} else {
|
||||
const result = await Promise.allSettled([
|
||||
new Promise((resolve, reject) => {
|
||||
btcMortgage.viewMyInbox((d, e) => {
|
||||
btcMortgage.viewMyInbox((requests, e) => {
|
||||
if (e) return
|
||||
floGlobals.myInbox = {
|
||||
...floGlobals.myInbox,
|
||||
...d
|
||||
}
|
||||
console.log('MY INBOX', d)
|
||||
processInProcessRequests()
|
||||
console.log('MY INBOX', requests)
|
||||
processInProcessRequests(requests, { isInbox: true })
|
||||
if (floGlobals.loaded) {
|
||||
router.routeTo(window.location.hash)
|
||||
}
|
||||
@ -1802,14 +1814,15 @@
|
||||
})
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
btcMortgage.listLoanRequests((d, e) => {
|
||||
btcMortgage.listLoanRequests((requests, e) => {
|
||||
if (e) return
|
||||
console.log('LOAN REQUESTS', requests)
|
||||
floGlobals.loanRequests = {
|
||||
...floGlobals.loanRequests,
|
||||
...d
|
||||
...requests
|
||||
}
|
||||
console.log('LOAN REQUESTS', d)
|
||||
processInProcessRequests()
|
||||
// if loan requests are from me, process them
|
||||
processInProcessRequests(requests, { areLoanRequests: true })
|
||||
if (floGlobals.loaded) {
|
||||
router.routeTo(window.location.hash)
|
||||
}
|
||||
@ -1820,11 +1833,10 @@
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
floCloudAPI.requestApplicationData('in_process_loan_request', {
|
||||
callback: (d, e) => {
|
||||
callback: (requests, e) => {
|
||||
if (e) return
|
||||
parseInProcessRequests(d)
|
||||
console.log('IN PROCESS LOAN REQUEST', d)
|
||||
processInProcessRequests()
|
||||
parseInProcessRequests(requests)
|
||||
console.log('IN PROCESS LOAN REQUEST', requests)
|
||||
if (floGlobals.loaded) {
|
||||
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) => {
|
||||
floCloudAPI.requestApplicationData(null, {
|
||||
senderID: floDapps.user.id,
|
||||
receiverID: address,
|
||||
callback: (d, e) => {
|
||||
callback: (requests, e) => {
|
||||
if (e) return
|
||||
floGlobals.myOutbox = {
|
||||
...floGlobals.myOutbox || {},
|
||||
...d
|
||||
}
|
||||
console.log('MY OUTBOX', d)
|
||||
processInProcessRequests()
|
||||
console.log('MY OUTBOX', requests)
|
||||
processInProcessRequests(requests)
|
||||
if (floGlobals.loaded) {
|
||||
router.routeTo(window.location.hash)
|
||||
}
|
||||
@ -1857,7 +1865,6 @@
|
||||
})
|
||||
}))
|
||||
console.log(result)
|
||||
processInProcessRequests()
|
||||
if (['#/landing', '#/sign_in', '#/sign_up'].includes(window.location.hash)) {
|
||||
history.replaceState(null, null, '#/home')
|
||||
router.routeTo('home')
|
||||
@ -1880,31 +1887,20 @@
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
/**
|
||||
* @param {Object} requests
|
||||
*/
|
||||
function parseInProcessRequests(requests) {
|
||||
if (!floGlobals.loansOpeningInProcess)
|
||||
floGlobals.loansOpeningInProcess = new Set()
|
||||
if (!floGlobals.hasAgreedToLend)
|
||||
floGlobals.hasAgreedToLend = new Set()
|
||||
for (const key in requests) {
|
||||
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()
|
||||
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
|
||||
}
|
||||
/**
|
||||
* @param {string} inputString
|
||||
*/
|
||||
function decodeDateStringToMilliseconds(inputString) {
|
||||
// Define a mapping for units to milliseconds (Y: years, M: months, D: days)
|
||||
const unitToMilliseconds = {
|
||||
@ -1931,22 +1927,18 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
const processedRequests = new Set()
|
||||
floGlobals.inProcessRequests = {}
|
||||
function processInProcessRequests() {
|
||||
let myLoanRequests = {}
|
||||
for (const key in floGlobals.loanRequests) {
|
||||
const { message: { borrower } } = floGlobals.loanRequests[key]
|
||||
if (floCrypto.isSameAddr(borrower, floGlobals.myFloID)) {
|
||||
myLoanRequests[key] = floGlobals.loanRequests[key]
|
||||
}
|
||||
}
|
||||
const allMessages = {
|
||||
...floGlobals.myInbox,
|
||||
...myLoanRequests,
|
||||
...floGlobals.myOutbox
|
||||
}
|
||||
for (const key in allMessages) {
|
||||
const completedLoanProcess = new Set()
|
||||
uiGlobals.inProcessRequests = {}
|
||||
uiGlobals.sortedProcesses = []
|
||||
uiGlobals.relatedAddresses = new Set()
|
||||
/**
|
||||
* @param {Object} newRequests
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.isInbox
|
||||
*/
|
||||
function processInProcessRequests(newRequests = {}, options = {}) {
|
||||
const { isInbox = false, areLoanRequests = false } = options
|
||||
for (const key in newRequests) {
|
||||
const {
|
||||
message: {
|
||||
borrower, coborrower, lender,
|
||||
@ -1954,16 +1946,17 @@
|
||||
loan_id, closing_txid, unlock_tx_hex, unlock_collateral_id, type_unlock_collateral_ack
|
||||
} = {},
|
||||
type, time
|
||||
} = allMessages[key];
|
||||
if (processedRequests.has(loan_opening_process_id) || Object.values(btcMortgage.loans).find(loan => loan.loan_opening_process_id === loan_opening_process_id)) {
|
||||
processedRequests.add(loan_opening_process_id)
|
||||
continue // Request has been processed
|
||||
}
|
||||
} = newRequests[key];
|
||||
if (borrower && areLoanRequests && !floCrypto.isSameAddr(borrower, floDapps.user.id)) continue // if loan request is not from me, ignore
|
||||
if (loan_id && btcMortgage.loans[loan_id] && btcMortgage.loans[loan_id].close_time)
|
||||
continue // Loan closed
|
||||
if (loan_opening_process_id) {
|
||||
if (!floGlobals.inProcessRequests[loan_opening_process_id]) {
|
||||
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)) {
|
||||
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,
|
||||
hasProvidedCollateral: false,
|
||||
hasAgreedToLend: false,
|
||||
@ -1972,113 +1965,154 @@
|
||||
}
|
||||
}
|
||||
if (borrower) {
|
||||
floGlobals.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].borrower = borrower
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
|
||||
if (isInbox)
|
||||
uiGlobals.relatedAddresses.add(borrower)
|
||||
}
|
||||
if (coborrower) {
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].coborrower = coborrower
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
|
||||
}
|
||||
if (loan_amount) {
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].loanAmount = loan_amount
|
||||
}
|
||||
if (policy_id) {
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].policyID = policy_id
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].coborrower = coborrower
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
|
||||
if (isInbox)
|
||||
uiGlobals.relatedAddresses.add(coborrower)
|
||||
}
|
||||
if (lender) {
|
||||
floGlobals.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].lender = lender
|
||||
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) {
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = loan_request_id
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = loan_request_id
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true
|
||||
}
|
||||
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) {
|
||||
case 'type_loan_collateral_request':
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].initiationTime = time
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].collateralRequestID = key
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].initiationTime = time
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].collateralRequestID = key
|
||||
break;
|
||||
case 'type_loan_request':
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = key
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = key
|
||||
break;
|
||||
case 'type_loan_response':
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].loanResponseTime = time
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].hasAgreedToLend = true
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].loanResponseID = key
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].loanResponseTime = time
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].hasAgreedToLend = true
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].loanResponseID = key
|
||||
break;
|
||||
case "type_collateral_lock_request":
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].collateralLockRequestID = key
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].collateralLockRequestID = key
|
||||
break;
|
||||
case 'type_collateral_lock_ack':
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckTime = time
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].hasLockedCollateral = true
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckID = key
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckTime = time
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].hasLockedCollateral = true
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].collateralLockAckID = key
|
||||
break;
|
||||
case 'type_refund_collateral_request':
|
||||
floGlobals.inProcessRequests[loan_opening_process_id].hasRequestedCollateralRefund = true
|
||||
uiGlobals.inProcessRequests[loan_opening_process_id].hasRequestedCollateralRefund = true
|
||||
break;
|
||||
}
|
||||
} else if (closing_txid) {
|
||||
if (!floGlobals.inProcessRequests[closing_txid])
|
||||
floGlobals.inProcessRequests[closing_txid] = {
|
||||
loanID: loan_id,
|
||||
closingTxID: closing_txid,
|
||||
hasRequestedCollateralRefund: false,
|
||||
hasRefundedCollateral: false,
|
||||
hasPaidLoan: false,
|
||||
type: 'loanClosing'
|
||||
} else {
|
||||
if (loan_id) {
|
||||
if (!uiGlobals.activeLoanQueries[loan_id])
|
||||
uiGlobals.activeLoanQueries[loan_id] = {
|
||||
loanID: loan_id,
|
||||
}
|
||||
switch (type) {
|
||||
case 'type_loan_closed_ack':
|
||||
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) {
|
||||
floGlobals.inProcessRequests[closing_txid].coborrower = coborrower
|
||||
floGlobals.inProcessRequests[closing_txid].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
|
||||
}
|
||||
if (lender) {
|
||||
floGlobals.inProcessRequests[closing_txid].lender = lender
|
||||
floGlobals.inProcessRequests[closing_txid].isLender = floCrypto.isSameAddr(lender, floDapps.user.id)
|
||||
}
|
||||
if (unlock_tx_hex) {
|
||||
floGlobals.inProcessRequests[closing_txid].unlockTxHex = unlock_tx_hex
|
||||
}
|
||||
switch (type) {
|
||||
case 'type_loan_closed_ack':
|
||||
floGlobals.inProcessRequests[closing_txid].loanClosedAckTime = time
|
||||
floGlobals.inProcessRequests[closing_txid].hasPaidLoan = true
|
||||
break;
|
||||
case 'type_unlock_collateral_request':
|
||||
floGlobals.inProcessRequests[closing_txid].unlockCollateralRequestTime = time
|
||||
floGlobals.inProcessRequests[closing_txid].hasRequestedCollateralUnlock = true
|
||||
break;
|
||||
case 'type_unlock_collateral_ack':
|
||||
floGlobals.inProcessRequests[closing_txid].unlockCollateralAckTime = time
|
||||
floGlobals.inProcessRequests[closing_txid].hasRefundedCollateral = true
|
||||
break;
|
||||
if (closing_txid && !completedLoanProcess.has(closing_txid)) {
|
||||
if (!uiGlobals.inProcessRequests[closing_txid])
|
||||
uiGlobals.inProcessRequests[closing_txid] = {
|
||||
loanID: loan_id,
|
||||
closingTxID: closing_txid,
|
||||
hasRequestedCollateralRefund: false,
|
||||
hasRefundedCollateral: false,
|
||||
hasPaidLoan: false,
|
||||
type: 'loanClosing'
|
||||
}
|
||||
const { borrower, coborrower, lender } = btcMortgage.loans[loan_id]
|
||||
if (borrower) {
|
||||
uiGlobals.inProcessRequests[closing_txid].borrower = borrower
|
||||
uiGlobals.inProcessRequests[closing_txid].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
|
||||
if (isInbox)
|
||||
uiGlobals.relatedAddresses.add(borrower)
|
||||
}
|
||||
if (coborrower) {
|
||||
uiGlobals.inProcessRequests[closing_txid].coborrower = coborrower
|
||||
uiGlobals.inProcessRequests[closing_txid].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
|
||||
if (isInbox)
|
||||
uiGlobals.relatedAddresses.add(coborrower)
|
||||
}
|
||||
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 (loan.isBorrower)
|
||||
// acc.borrowing.push(loan)
|
||||
// else if (loan.isCoborrower)
|
||||
// acc.coBorrowing.push(loan)
|
||||
// else if (loan.isLender)
|
||||
// acc.lending.push(loan)
|
||||
// return acc
|
||||
// }, {
|
||||
// borrowing: [],
|
||||
// coBorrowing: [],
|
||||
// lending: []
|
||||
// })
|
||||
if (uiGlobals.relatedAddresses.has(floGlobals.myBtcID))
|
||||
uiGlobals.relatedAddresses.delete(floGlobals.myBtcID)
|
||||
if (uiGlobals.relatedAddresses.has(floGlobals.myFloID))
|
||||
uiGlobals.relatedAddresses.delete(floGlobals.myFloID)
|
||||
uiGlobals.sortedProcesses = Object.keys(uiGlobals.inProcessRequests).sort((a, b) => {
|
||||
const aTime = uiGlobals.inProcessRequests[a].initiationTime || uiGlobals.inProcessRequests[a].loanResponseTime || uiGlobals.inProcessRequests[a].loanClosedAckTime || uiGlobals.inProcessRequests[a].unlockCollateralRequestTime;
|
||||
const bTime = uiGlobals.inProcessRequests[b].initiationTime || uiGlobals.inProcessRequests[b].loanResponseTime || uiGlobals.inProcessRequests[b].loanClosedAckTime || uiGlobals.inProcessRequests[b].unlockCollateralRequestTime;
|
||||
return bTime - aTime
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function groupLoans() {
|
||||
return Object.values(btcMortgage.loans)
|
||||
.sort((a, b) => b.open_time - a.open_time)
|
||||
|
||||
@ -30,8 +30,7 @@
|
||||
TYPE_REFUND_COLLATERAL_ACK = "type_refund_collateral_ack",
|
||||
TYPE_LIQUATE_COLLATERAL_REQUEST = "type_liquate_collateral_request",
|
||||
TYPE_LIQUATE_COLLATERAL_ACK = "type_liquate_collateral_ack",
|
||||
TYPE_PRELIQUATE_COLLATERAL_REQUEST = "type_preliquate_collateral_request",
|
||||
TYPE_PRELIQUATE_COLLATERAL_ACK = "type_preliquate_collateral_ack";
|
||||
TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST = "type_pre_liquidate_collateral_request"
|
||||
|
||||
const POLICIES = {}, LOANS = {};
|
||||
const owned_collateral_locks = {};
|
||||
@ -124,7 +123,7 @@
|
||||
const util = btcMortgage.util = {
|
||||
toFixedDecimal,
|
||||
encodePeriod, decodePeriod,
|
||||
calcAllowedLoan, calcRequiredCollateral, calcDueAmount,
|
||||
calcAllowedLoan, calcRequiredCollateral, calcDueAmount, calcRateRatio,
|
||||
findLocker, extractPubKeyFromSign
|
||||
}
|
||||
|
||||
@ -1495,7 +1494,7 @@
|
||||
resolve(result);
|
||||
}).catch(error => reject(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 })
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
@ -1516,7 +1515,7 @@
|
||||
if (!floCrypto.isSameAddr(loan_details.lender, collateral_liquate_req.senderID))
|
||||
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))
|
||||
return reject("Invalid liquiadtion signature");
|
||||
return reject("Invalid liquidation signature");
|
||||
checkIfLoanClosedFailed(loan, loan_details.borrower, loan_details.lender).then(result => {
|
||||
if (result) //close/fail loan data found
|
||||
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
|
||||
btcMortgage.requestBanker.preliquateCollateral = function (loan_id, privKey) {
|
||||
// L: request T (banker) to pre-liquidate collateral when collateral value has dropped to risk threshold
|
||||
btcMortgage.requestBanker.preLiquidateCollateral = function (loan_id, privKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let lender_pubKey = floDapps.user.public;
|
||||
getLoanDetails(loan_id).then(loan_details => {
|
||||
@ -1552,7 +1551,7 @@
|
||||
floCloudAPI.sendApplicationData({
|
||||
loan_id, liquidation_sign,
|
||||
liquidate_tx_hex: txHex
|
||||
}, TYPE_PRELIQUATE_COLLATERAL_REQUEST)
|
||||
}, TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST)
|
||||
.then(result => {
|
||||
compactIDB.addData("outbox", result, result.vectorClock);
|
||||
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) => {
|
||||
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;
|
||||
//calculate due amount
|
||||
let due_amount = calcDueAmount(loan_details.loan_amount, loan_details.policy_id, loan_details.open_time)
|
||||
@ -1591,7 +1590,7 @@
|
||||
resolve(result);
|
||||
}).catch(error => reject(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 })
|
||||
})
|
||||
}).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) => {
|
||||
floCloudAPI.requestApplicationData(TYPE_PRELIQUATE_COLLATERAL_REQUEST, { atVectorClock: collateral_preliquate_req_id }).then(collateral_preliquate_req => {
|
||||
collateral_preliquate_req = collateral_preliquate_req[collateral_preliquate_req_id];
|
||||
if (!collateral_preliquate_req)
|
||||
return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "request not found"));
|
||||
let { loan_id, liquidation_sign, btc_liquid_rate, liquidate_tx_hex } = collateral_preliquate_req.message;
|
||||
floCloudAPI.requestApplicationData(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, { atVectorClock: collateral_pre_liquidate_req_id }).then(collateral_pre_liquidate_req => {
|
||||
collateral_pre_liquidate_req = collateral_pre_liquidate_req[collateral_pre_liquidate_req_id];
|
||||
if (!collateral_pre_liquidate_req)
|
||||
return reject(RequestValidationError(TYPE_PRE_LIQUIDATE_COLLATERAL_REQUEST, "request not found"));
|
||||
let { loan_id, liquidation_sign, btc_liquid_rate, liquidate_tx_hex } = collateral_pre_liquidate_req.message;
|
||||
getLoanDetails(loan_id).then(loan_details => {
|
||||
if (!floCrypto.isSameAddr(loan_details.lender, collateral_preliquate_req.senderID))
|
||||
return reject(RequestValidationError(TYPE_PRELIQUATE_COLLATERAL_REQUEST, "request not sent by lender"));
|
||||
if (!floCrypto.isSameAddr(loan_details.lender, collateral_pre_liquidate_req.senderID))
|
||||
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))
|
||||
return reject("Invalid liquiadtion signature");
|
||||
return reject("Invalid liquidation signature");
|
||||
checkIfLoanClosedFailed(loan, loan_details.borrower, loan_details.lender).then(result => {
|
||||
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];
|
||||
if (isNaN(policy.pre_liquidation_threshold))
|
||||
return reject("This loan policy doesn't allow pre-liquidation");
|
||||
getRate["USD"].then(cur_btc_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)
|
||||
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 });
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user