diff --git a/index.html b/index.html
index 91adb85..ac341f3 100644
--- a/index.html
+++ b/index.html
@@ -421,7 +421,8 @@
const formattedAmount = amount.toLocaleString(currency === 'inr' ? `en-IN` : 'en-US', {
style: 'currency',
currency,
- maximumFractionDigits: currency === 'usd' ? 2 : 8,
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 8,
currencyDisplay: 'code'
}).slice(4)
if (currency === 'usd')
@@ -698,6 +699,8 @@
}
const [isIssuingLoan, setIsIssuingLoan] = useState(false)
+ const [failSafe, setFailSafe] = useState(null)
+ const [retringFailSafe, setRetryingFailSafe] = useState(false)
async function issueLoan() {
try {
const confirmation = await getConfirmation('Issue loan?', { message: `You are about to issue ${formatAmount(loanAmount)} as loan. Continue?`, confirmText: 'Issue', cancelText: 'Cancel' })
@@ -709,11 +712,23 @@
} catch (err) {
notify(err.message || err, 'error')
console.error(err)
+ if (err.fail_safe)
+ setFailSafe(err.fail_safe)
} finally {
setIsIssuingLoan(false)
}
}
-
+ async function retryFailSafe() {
+ try {
+ setRetryingFailSafe(true)
+ await btcOperator.retryFailSafe(failSafe, await floDapps.user.private)
+ notify('Loan issued successfully', 'success')
+ } catch (err) {
+ notify(err, 'error')
+ } finally {
+ setRetryingFailSafe(false)
+ }
+ }
// check if loan hasn't been issued even after 24 hours of collateral lock
let hasLockedCollateralForMoreThan24Hours = false
useEffect(() => {
@@ -891,14 +906,27 @@
`: html`
${hasLockedCollateral && isLender ? html`
-
+ ${failSafe ? html`
+
Loan failed to issue
+
+ `: html`
+ Loan is ready to be issued
+
+ `}
`: html`
Waiting for loan to be issued
`}
@@ -916,7 +944,25 @@
blocktime, borrower, borrower_sign, btc_start_rate,
coborrower, coborrower_sign, collateral_lock_id, collateral_value,
lender, lender_sign, loan_amount, loan_id, loan_opening_process_id, loan_transfer_id,
- open_time, policy_id, } = btcMortgage.loans[loanID]
+ open_time, policy_id, } = btcMortgage.loans[loanID];
+
+ const amountDue = btcMortgage.util.calcDueAmount(loan_amount, policy_id, open_time)
+ const [isRepayingLoan, setIsRepayingLoan] = useState(false)
+ async function initLoanRepayment() {
+ const confirmation = await getConfirmation('Repay loan?', { message: `You are about to repay ${formatAmount(amountDue)} as loan. Continue?`, confirmText: 'Repay', cancelText: 'Cancel' })
+ if (!confirmation)
+ return;
+ setIsRepayingLoan(true)
+ try {
+ await btcMortgage.repayLoan(loan_id, await floDapps.user.private)
+ notify('Loan repaid successfully', 'success')
+ } catch (err) {
+ notify(err, 'error')
+ } finally {
+ setIsRepayingLoan(false)
+ }
+ }
+
return html`
@@ -966,6 +1012,22 @@
`: ''}
+ ${floCrypto.isSameAddr(borrower, floGlobals.myFloID) ? html`
+
+
+
Amount due (If paid now)
+
${formatAmount(amountDue, 'usd')}
+
+
+
+ `: ''}
`
}
@@ -1236,14 +1298,14 @@
} else {
// user homepage
const inbox = Component(() => {
- const inProcessRequests = groupLoanProcess()
+ const [inProcessLoanOpeningRequests, inProcessLoanClosingRequests] = groupLoanProcess()
const { borrowed, coBorrowed, lent } = groupLoans()
const [view, setView] = useState(section)
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.inProcessRequests.has(loan_opening_process_id)) continue // if loan request is in process, don't show loan request
+ if (floGlobals.inProcessLoanOpeningRequests.has(loan_opening_process_id)) continue // if loan request is in process, don't show loan request
loanRequests.push(key)
}
function handleChange(e) {
@@ -1264,7 +1326,7 @@
In process
- ${inProcessRequests.length ? html`${inProcessRequests.length}` : ''}
+ ${inProcessLoanOpeningRequests.length ? html`${inProcessLoanOpeningRequests.length}` : ''}
My loans
@@ -1307,9 +1369,9 @@
`}
`: ''}
${view === 'in-process' ? html`
- ${inProcessRequests.length ? html`
+ ${inProcessLoanOpeningRequests.length ? html`
- ${inProcessRequests.map(loan => render.loanProcess(loan))}
+ ${inProcessLoanOpeningRequests.map(loan => render.loanProcess(loan))}
Apply for a new loan
`: html`
@@ -1378,80 +1440,120 @@
...floGlobals.myOutbox
}
const loansInProcess = {}
+ const loanClosingInProcess = {}
for (const key in allMessages) {
- const { message: { borrower, coborrower, loan_amount, policy_id, loan_opening_process_id, lender, loan_request_id, collateral_lock_id } = {}, type, time } = allMessages[key];
- if (!loan_opening_process_id) continue
- if (Object.values(btcMortgage.loans).find(loan => loan.loan_opening_process_id === loan_opening_process_id))
- continue // Loan is already open
- if (!loansInProcess[loan_opening_process_id]) {
- loansInProcess[loan_opening_process_id] = {
- loanOpeningProcessID: loan_opening_process_id,
- hasProvidedCollateral: false,
- hasAgreedToLend: false,
- hasLockedCollateral: false,
+ 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, time
+ } = allMessages[key];
+ if (loan_opening_process_id) {
+ if (Object.values(btcMortgage.loans).find(loan => loan.loan_opening_process_id === loan_opening_process_id))
+ continue // Loan is already open
+ if (!loansInProcess[loan_opening_process_id]) {
+ loansInProcess[loan_opening_process_id] = {
+ loanOpeningProcessID: loan_opening_process_id,
+ hasProvidedCollateral: false,
+ hasAgreedToLend: false,
+ hasLockedCollateral: false,
+ }
}
- }
- 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
+ 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
- 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;
+ }
+ 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 (!loanClosingInProcess[closing_txid])
+ loanClosingInProcess[closing_txid] = {
+ closingTxID: closing_txid,
+ hasRequestedCollateralRefund: false,
+ hasRefundedCollateral: false,
+ hasPaidLoan: false,
+ }
+
+ switch (type) {
+ case 'type_loan_closed_ack':
+ loanClosingInProcess[closing_txid].loanClosedAckTime = time
+ loanClosingInProcess[closing_txid].loanClosedAckID = key
+ loanClosingInProcess[closing_txid].hasPaidLoan = true
+ break;
+ case 'type_unlock_collateral_request':
+ loanClosingInProcess[closing_txid].unlockCollateralRequestTime = time
+ loanClosingInProcess[closing_txid].unlockCollateralRequestID = key
+ loanClosingInProcess[closing_txid].hasRequestedCollateralUnlock = true
+ break;
+ case 'type_unlock_collateral_ack':
+ loanClosingInProcess[closing_txid].unlockCollateralAckTime = time
+ loanClosingInProcess[closing_txid].unlockCollateralAckID = key
+ loanClosingInProcess[closing_txid].hasRefundedCollateral = true
+ break;
+ }
}
}
// sort by time
- return Object.values(loansInProcess).sort((a, b) => {
- return (b.initiationTime || b.loanResponseTime) - (a.initiationTime || a.loanResponseTime)
- })
+ return [
+ Object.values(loansInProcess).sort((a, b) => {
+ return (b.initiationTime || b.loanResponseTime) - (a.initiationTime || a.loanResponseTime)
+ }),
+ Object.values(loanClosingInProcess).sort((a, b) => {
+ return b.loanClosedAckTime - a.loanClosedAckTime
+ })
+ ]
// .reduce((acc, loan) => { // group by borrower, coborrower and lender
// if (loan.isBorrower)
// acc.borrowing.push(loan)
@@ -1702,11 +1804,11 @@
}).catch(error => console.error(error))
}
function parseInProcessRequests(requests) {
- if (!floGlobals.inProcessRequests)
- floGlobals.inProcessRequests = new Set()
+ if (!floGlobals.inProcessLoanOpeningRequests)
+ floGlobals.inProcessLoanOpeningRequests = new Set()
for (const key in requests) {
const { message: { loan_opening_process_id } } = requests[key]
- floGlobals.inProcessRequests.add(loan_opening_process_id)
+ floGlobals.inProcessLoanOpeningRequests.add(loan_opening_process_id)
}
}
function getAllInvolvedAddresses() { // get all addresses involved in loan process (except my address)
diff --git a/scripts/btcMortgage.js b/scripts/btcMortgage.js
index 2a8ea6d..190f882 100644
--- a/scripts/btcMortgage.js
+++ b/scripts/btcMortgage.js
@@ -142,7 +142,7 @@
const util = btcMortgage.util = {
toFixedDecimal,
encodePeriod, decodePeriod,
- calcAllowedLoan, calcRequiredCollateral,
+ calcAllowedLoan, calcRequiredCollateral, calcDueAmount,
findLocker, extractPubKeyFromSign
}