diff --git a/index.html b/index.html index d869e88..95ae149 100644 --- a/index.html +++ b/index.html @@ -1144,6 +1144,22 @@ ` } } + const isLoanOverdue = Date.now() > (open_time + decodeDateStringToMilliseconds(btcMortgage.policies[policy_id]?.duration)) && loanStatus === 'Active'; + const [isClaimingCollateral, setIsClaimingCollateral] = useState(false) + async function claimCollateral() { + const confirmation = await getConfirmation('Claim collateral?', { message: `Your claim request will be handled by our banker within 48hrs. Continue?`, confirmText: 'Claim', cancelText: 'Cancel' }) + if (!confirmation) + return; + setIsClaimingCollateral(true) + try { + await btcMortgage.requestBanker.liquateCollateral(loan_id, await floDapps.user.private) + notify('Collateral claim request sent successfully', 'success') + } catch (err) { + notify(err, 'error') + } finally { + setIsClaimingCollateral(false) + } + } return html`
  • ${loanStatusBadge} @@ -1176,7 +1192,12 @@

    Closing date

    ${getFormattedTime(close_time)} - `: ``} + `: html` +
    +

    Loan deadline

    + ${getFormattedTime(open_time + decodeDateStringToMilliseconds(btcMortgage.policies[policy_id]?.duration))} +
    + `}

    Interest ${isLender ? 'earned' : close_time ? 'paid' : 'accrued'}

    @@ -1220,6 +1241,22 @@
    `: ''} + ${isLender && isLoanOverdue ? html` +
    +
    +

    Loan repayment overdue

    +

    Borrower has not repaid the loan even after the deadline. You can now claim the collateral.

    +
    + +
    + `: ''}
  • ` } @@ -1499,7 +1536,11 @@ } else { // user homepage const inbox = Component(() => { - const loanRequestsInProcess = groupLoanProcess() + 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) let loanRequests = []; @@ -1618,188 +1659,6 @@ router.addRoute('', renderHome) router.addRoute('home', renderHome) - function groupLoanProcess() { - 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 - } - const loansInProcess = {} - const processedRequests = new Set() - for (const key in allMessages) { - const { - message: { - borrower, coborrower, lender, - loan_amount, policy_id, loan_opening_process_id, loan_request_id, collateral_lock_id, - 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 - } - if (loan_id && btcMortgage.loans[loan_id] && btcMortgage.loans[loan_id].close_time) - continue // Loan closed - if (loan_opening_process_id) { - if (!loansInProcess[loan_opening_process_id]) { - loansInProcess[loan_opening_process_id] = { - loanOpeningProcessID: loan_opening_process_id, - hasProvidedCollateral: false, - hasAgreedToLend: false, - hasLockedCollateral: false, - type: 'loanOpening' - } - } - if (borrower) { - loansInProcess[loan_opening_process_id].borrower = borrower - loansInProcess[loan_opening_process_id].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id) - } - if (coborrower) { - loansInProcess[loan_opening_process_id].coborrower = coborrower - loansInProcess[loan_opening_process_id].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id) - } - if (loan_amount) { - loansInProcess[loan_opening_process_id].loanAmount = loan_amount - } - if (policy_id) { - loansInProcess[loan_opening_process_id].policyID = policy_id - } - if (lender) { - loansInProcess[loan_opening_process_id].lender = lender - loansInProcess[loan_opening_process_id].isLender = floCrypto.isSameAddr(lender, floDapps.user.id) - } - if (loan_request_id) { - loansInProcess[loan_opening_process_id].loanRequestID = loan_request_id - loansInProcess[loan_opening_process_id].hasProvidedCollateral = true - } - if (collateral_lock_id) { - loansInProcess[loan_opening_process_id].collateralLockID = collateral_lock_id - } - switch (type) { - case 'type_loan_collateral_request': - loansInProcess[loan_opening_process_id].initiationTime = time - loansInProcess[loan_opening_process_id].collateralRequestID = key - break; - case 'type_loan_request': - loansInProcess[loan_opening_process_id].loanRequestTime = time - loansInProcess[loan_opening_process_id].hasProvidedCollateral = true - loansInProcess[loan_opening_process_id].loanRequestID = key - break; - case 'type_loan_response': - loansInProcess[loan_opening_process_id].loanResponseTime = time - loansInProcess[loan_opening_process_id].hasAgreedToLend = true - loansInProcess[loan_opening_process_id].loanResponseID = key - break; - case "type_collateral_lock_request": - loansInProcess[loan_opening_process_id].collateralLockRequestTime = time - loansInProcess[loan_opening_process_id].collateralLockRequestID = key - loansInProcess[loan_opening_process_id].hasRequestedCollateralLock = true - break; - case 'type_collateral_lock_ack': - loansInProcess[loan_opening_process_id].collateralLockAckTime = time - loansInProcess[loan_opening_process_id].hasLockedCollateral = true - loansInProcess[loan_opening_process_id].collateralLockAckID = key - break; - case 'type_refund_collateral_request': - loansInProcess[loan_opening_process_id].refundCollateralRequestTime = time - loansInProcess[loan_opening_process_id].refundCollateralRequestID = key - loansInProcess[loan_opening_process_id].hasRequestedCollateralRefund = true - break; - } - } else if (closing_txid) { - if (!loansInProcess[closing_txid]) - loansInProcess[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) { - loansInProcess[closing_txid].borrower = borrower - loansInProcess[closing_txid].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id) - } - if (coborrower) { - loansInProcess[closing_txid].coborrower = coborrower - loansInProcess[closing_txid].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id) - } - if (lender) { - loansInProcess[closing_txid].lender = lender - loansInProcess[closing_txid].isLender = floCrypto.isSameAddr(lender, floDapps.user.id) - } - if (unlock_tx_hex) { - loansInProcess[closing_txid].unlockTxHex = unlock_tx_hex - } - switch (type) { - case 'type_loan_closed_ack': - loansInProcess[closing_txid].loanClosedAckTime = time - loansInProcess[closing_txid].loanClosedAckID = key - loansInProcess[closing_txid].hasPaidLoan = true - break; - case 'type_unlock_collateral_request': - loansInProcess[closing_txid].unlockCollateralRequestTime = time - loansInProcess[closing_txid].unlockCollateralRequestID = key - loansInProcess[closing_txid].hasRequestedCollateralUnlock = true - break; - case 'type_unlock_collateral_ack': - loansInProcess[closing_txid].unlockCollateralAckTime = time - loansInProcess[closing_txid].unlockCollateralAckID = key - loansInProcess[closing_txid].hasRefundedCollateral = true - break; - } - } - } - // sort by time - return Object.values(loansInProcess).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 - }) - // .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: [] - // }) - } - - function groupLoans() { - console.log(btcMortgage.loans) - return Object.values(btcMortgage.loans) - .sort((a, b) => b.open_time - a.open_time) - .reduce((acc, loan) => { - const { loan_id, borrower, coborrower, lender } = loan - if (floCrypto.isSameAddr(borrower, floDapps.user.id) && floCrypto.isSameAddr(coborrower, floDapps.user.id)) - acc.borrowed.push(loan_id) - else if (floCrypto.isSameAddr(coborrower, floDapps.user.id)) - acc.coBorrowed.push(loan_id) - else if (floCrypto.isSameAddr(lender, floDapps.user.id)) - acc.lent.push(loan_id) - return acc - }, { - borrowed: [], - coBorrowed: [], - lent: [] - }) - } - router.addRoute('apply-loan', async () => { const loanApplication = Component(() => { return html` @@ -1917,6 +1776,7 @@ } } console.log('LOAN REQUESTS', d) + processInProcessRequests() if (floGlobals.loaded) { router.routeTo(window.location.hash) } @@ -1932,6 +1792,7 @@ ...d } console.log('MY INBOX', d) + processInProcessRequests() if (floGlobals.loaded) { router.routeTo(window.location.hash) } @@ -1948,6 +1809,7 @@ ...d } console.log('LOAN REQUESTS', d) + processInProcessRequests() if (floGlobals.loaded) { router.routeTo(window.location.hash) } @@ -1962,6 +1824,7 @@ if (e) return parseInProcessRequests(d) console.log('IN PROCESS LOAN REQUEST', d) + processInProcessRequests() if (floGlobals.loaded) { router.routeTo(window.location.hash) } @@ -1984,6 +1847,7 @@ ...d } console.log('MY OUTBOX', d) + processInProcessRequests() if (floGlobals.loaded) { router.routeTo(window.location.hash) } @@ -1993,6 +1857,7 @@ }) })) console.log(result) + processInProcessRequests() if (['#/landing', '#/sign_in', '#/sign_up'].includes(window.location.hash)) { history.replaceState(null, null, '#/home') router.routeTo('home') @@ -2040,6 +1905,198 @@ addresses.delete(floGlobals.myBtcID) return addresses } + function decodeDateStringToMilliseconds(inputString) { + // Define a mapping for units to milliseconds (Y: years, M: months, D: days) + const unitToMilliseconds = { + Y: 31536000000, // 1 year = 365 days + M: 2592000000, // 1 month = 30 days (approximate) + D: 86400000 // 1 day = 24 hours + }; + + // Use a regular expression to match and capture the numeric part and unit + const match = inputString.match(/^([1-9]\d{0,4})(Y|M|D)$/); + + if (match) { + const numericPart = parseInt(match[1], 10); + const unit = match[2]; + + if (unitToMilliseconds.hasOwnProperty(unit)) { + // Convert the numeric part to milliseconds using the unit + const milliseconds = numericPart * unitToMilliseconds[unit]; + return milliseconds; + } + } + + // Return null for invalid input + 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 { + message: { + borrower, coborrower, lender, + loan_amount, policy_id, loan_opening_process_id, loan_request_id, collateral_lock_id, + 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 + } + 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] = { + loanOpeningProcessID: loan_opening_process_id, + hasProvidedCollateral: false, + hasAgreedToLend: false, + hasLockedCollateral: false, + type: 'loanOpening' + } + } + if (borrower) { + floGlobals.inProcessRequests[loan_opening_process_id].borrower = borrower + floGlobals.inProcessRequests[loan_opening_process_id].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id) + } + 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 + } + if (lender) { + floGlobals.inProcessRequests[loan_opening_process_id].lender = lender + floGlobals.inProcessRequests[loan_opening_process_id].isLender = floCrypto.isSameAddr(lender, floDapps.user.id) + } + if (loan_request_id) { + floGlobals.inProcessRequests[loan_opening_process_id].loanRequestID = loan_request_id + floGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true + } + if (collateral_lock_id) { + floGlobals.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 + break; + case 'type_loan_request': + floGlobals.inProcessRequests[loan_opening_process_id].hasProvidedCollateral = true + floGlobals.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 + break; + case "type_collateral_lock_request": + floGlobals.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 + break; + case 'type_refund_collateral_request': + floGlobals.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' + } + 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; + } + } + } + // .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: [] + // }) + } + + function groupLoans() { + return Object.values(btcMortgage.loans) + .sort((a, b) => b.open_time - a.open_time) + .reduce((acc, loan) => { + const { loan_id, borrower, coborrower, lender } = loan + if (floCrypto.isSameAddr(borrower, floDapps.user.id) && floCrypto.isSameAddr(coborrower, floDapps.user.id)) + acc.borrowed.push(loan_id) + else if (floCrypto.isSameAddr(coborrower, floDapps.user.id)) + acc.coBorrowed.push(loan_id) + else if (floCrypto.isSameAddr(lender, floDapps.user.id)) + acc.lent.push(loan_id) + return acc + }, { + borrowed: [], + coBorrowed: [], + lent: [] + }) + } diff --git a/scripts/btcMortgage.js b/scripts/btcMortgage.js index 190f882..5d842be 100644 --- a/scripts/btcMortgage.js +++ b/scripts/btcMortgage.js @@ -12,7 +12,7 @@ const CURRENCY = "rupee"; const ALLOWED_DEVIATION = 0.98, //ie, upto 2% of decrease in rate can be accepted in processing stage WAIT_TIME = 24 * 60 * 60 * 1000;//24 hrs - const PERIOD_REGEX = /^\d{1,5}(Y|M|D)$/, + const PERIOD_REGEX = /^[1-9]\d{0,4}(Y|M|D)$/, TXID_REGEX = /^[0-9a-f]{64}$/i, PERCENT_REGEX = /^(100|\d{1,2}(\.\d{1,8})?)$/, VALUE_REGEX = /^\d+(\.\d{1,8})?$/; @@ -83,10 +83,6 @@ } - const dateFormat = (date = null) => { - let d = (date ? new Date(date) : new Date()).toDateString(); - return [d.substring(8, 10), d.substring(4, 7), d.substring(11, 15)].join(" "); - } const yearDiff = (d1 = null, d2 = null) => { d1 = d1 ? new Date(d1) : new Date(); d2 = d2 ? new Date(d2) : new Date(); @@ -96,20 +92,6 @@ return y + m / 12 + d / 365; } - const dateAdder = function (start_date, duration) { - let date = new Date(start_date); - let y = parseInt(duration.match(/\d+Y/)), - m = parseInt(duration.match(/\d+M/)), - d = parseInt(duration.match(/\d+D/)); - if (!isNaN(y)) - date.setFullYear(date.getFullYear() + y); - if (!isNaN(m)) - date.setMonth(date.getMonth() + m); - if (!isNaN(d)) - date.setDate(date.getDate() + d); - return date; - } - function calcAllowedLoan(collateralQuantity, loan_collateral_ratio) { return toFixedDecimal(collateralQuantity * loan_collateral_ratio); }