Feature update
-- added loan process ID for easier grouping and UI utility -added BTC, FLO, USD balance checking
This commit is contained in:
parent
160ad50784
commit
86bfffc049
90
css/main.css
90
css/main.css
@ -128,9 +128,9 @@ button:not(:disabled),
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
filter: saturate(0);
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
color: rgba(var(--text-color), 0.5);
|
||||
}
|
||||
|
||||
.cta {
|
||||
@ -699,9 +699,29 @@ h3 {
|
||||
#main_page {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
width: min(64rem, 100%);
|
||||
width: min(72rem, 100%);
|
||||
margin: auto;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#balance_list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.7rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
}
|
||||
.balance-card span:last-of-type {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#apply_loan {
|
||||
@ -749,6 +769,61 @@ h3 {
|
||||
width: 1.2em;
|
||||
}
|
||||
|
||||
.loan-process {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
padding: max(1rem, 1.5vw);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.loan-process ul {
|
||||
display: grid;
|
||||
}
|
||||
.loan-process ul li {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
.loan-process .progress {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
isolation: isolate;
|
||||
}
|
||||
.loan-process .progress > * {
|
||||
grid-area: 1/1/2/2;
|
||||
}
|
||||
.loan-process li.done .circle {
|
||||
background-color: var(--green);
|
||||
border: solid 0.1rem var(--green);
|
||||
}
|
||||
.loan-process li.done .line {
|
||||
background-color: var(--green);
|
||||
}
|
||||
.loan-process .circle {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
z-index: 1;
|
||||
border: solid 0.1rem rgba(var(--text-color), 0.5);
|
||||
}
|
||||
.loan-process .line {
|
||||
width: 0.1rem;
|
||||
height: 100%;
|
||||
background-color: rgba(var(--text-color), 0.5);
|
||||
}
|
||||
.loan-process .details {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
.loan-process .details button {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.loan-process time {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
|
||||
#collateral_requests_list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
@ -805,6 +880,15 @@ h3 {
|
||||
.popup__header {
|
||||
padding: 1.5rem 1.5rem 0 0.75rem;
|
||||
}
|
||||
#main_page {
|
||||
grid-template-columns: 18rem 1fr;
|
||||
}
|
||||
#main_page > :first-child {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#request_loan_popup {
|
||||
--width: 32rem;
|
||||
}
|
||||
|
||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -124,9 +124,9 @@ button,
|
||||
}
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
filter: saturate(0);
|
||||
background-color: rgba(var(--text-color), 0.03);
|
||||
color: rgba(var(--text-color), 0.5);
|
||||
}
|
||||
|
||||
.cta {
|
||||
@ -658,9 +658,27 @@ h3 {
|
||||
#main_page {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
width: min(64rem, 100%);
|
||||
width: min(72rem, 100%);
|
||||
margin: auto;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: flex-start;
|
||||
}
|
||||
#balance_list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.balance-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.7rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
span:last-of-type {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
#apply_loan {
|
||||
width: min(64rem, 100%);
|
||||
@ -707,6 +725,62 @@ h3 {
|
||||
}
|
||||
}
|
||||
}
|
||||
.loan-process {
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
padding: max(1rem, 1.5vw);
|
||||
border-radius: 0.5rem;
|
||||
ul {
|
||||
display: grid;
|
||||
li {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
isolation: isolate;
|
||||
& > * {
|
||||
grid-area: 1/1/2/2;
|
||||
}
|
||||
}
|
||||
li.done {
|
||||
.circle {
|
||||
background-color: var(--green);
|
||||
border: solid 0.1rem var(--green);
|
||||
}
|
||||
.line {
|
||||
background-color: var(--green);
|
||||
}
|
||||
}
|
||||
.circle {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--foreground-color), 1);
|
||||
z-index: 1;
|
||||
border: solid 0.1rem rgba(var(--text-color), 0.5);
|
||||
}
|
||||
.line {
|
||||
width: 0.1rem;
|
||||
height: 100%;
|
||||
background-color: rgba(var(--text-color), 0.5);
|
||||
}
|
||||
.details {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
padding-bottom: 2rem;
|
||||
button {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
time {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(var(--text-color), 0.8);
|
||||
}
|
||||
}
|
||||
#collateral_requests_list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
@ -760,6 +834,14 @@ h3 {
|
||||
.popup__header {
|
||||
padding: 1.5rem 1.5rem 0 0.75rem;
|
||||
}
|
||||
#main_page {
|
||||
grid-template-columns: 18rem 1fr;
|
||||
& > :first-child {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
#request_loan_popup {
|
||||
--width: 32rem;
|
||||
}
|
||||
|
||||
293
index.html
293
index.html
@ -404,7 +404,7 @@
|
||||
}
|
||||
if (!amount)
|
||||
return '0';
|
||||
return amount.toLocaleString(currency === 'inr' ? `en-IN` : 'en-US', { style: 'currency', currency, maximumFractionDigits: currency === 'btc' ? 8 : 2 })
|
||||
return amount.toLocaleString(currency === 'inr' ? `en-IN` : 'en-US', { style: 'currency', currency, maximumFractionDigits: currency === 'usd' ? 2 : 8 })
|
||||
}
|
||||
const render = {
|
||||
policy(id, details = {}) {
|
||||
@ -538,7 +538,7 @@
|
||||
const { message: { borrower, coborrower, loan_amount, policy_id }, vectorClock } = details;
|
||||
const collateralAmount = btcMortgage.util.toFixedDecimal(btcMortgage.policies[policy_id].loan_collateral_ratio * (loan_amount / floGlobals.btcRate))
|
||||
async function approveCollateralRequest(e) {
|
||||
const confirmation = await getConfirmation('Send collateral?', { message: `You are about to send ${formatAmount(collateralAmount)} collateral to the borrower. Continue?`, confirmText: 'Send', cancelText: 'Cancel', danger: true })
|
||||
const confirmation = await getConfirmation('Send collateral?', { message: `You are about to send ${formatAmount(collateralAmount)} collateral to the borrower. Continue?`, confirmText: 'Send', cancelText: 'Cancel' })
|
||||
if (!confirmation)
|
||||
return;
|
||||
e.target.disabled = true;
|
||||
@ -557,7 +557,7 @@
|
||||
}
|
||||
}
|
||||
async function rejectCollateralRequest(e) {
|
||||
const confirmation = await getConfirmation('Reject collateral request?', { message: `You are about to reject the collateral request. Continue?`, confirmText: 'Reject', cancelText: 'Cancel', danger: true })
|
||||
const confirmation = await getConfirmation('Reject collateral request?', { message: `You are about to reject the collateral request. Continue?`, confirmText: 'Reject', cancelText: 'Cancel' })
|
||||
if (!confirmation)
|
||||
return;
|
||||
e.target.disabled = true;
|
||||
@ -606,7 +606,7 @@
|
||||
const { message: { borrower, coborrower, collateral: { btc_id, quantity, rate }, loan_amount, loan_collateral_req_id, policy_id }, vectorClock } = details;
|
||||
const { duration, interest } = btcMortgage.policies[policy_id];
|
||||
async function acceptLoanProposal() {
|
||||
const confirmation = await getConfirmation('Accept loan proposal?', { message: `You are about to accept the loan proposal. Continue?`, confirmText: 'Accept', cancelText: 'Cancel', danger: true })
|
||||
const confirmation = await getConfirmation('Accept loan proposal?', { message: `You are about to accept the loan proposal. Continue?`, confirmText: 'Accept', cancelText: 'Cancel' })
|
||||
if (!confirmation)
|
||||
return;
|
||||
btcMortgage.respondLoan(loan_collateral_req_id, borrower, coborrower).then(() => {
|
||||
@ -654,10 +654,122 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-0-5 align-center margin-left-auto">
|
||||
<button class="button button--primary" onclick=${acceptLoanProposal}>Accept loan proposal</button>
|
||||
<button class="button button--primary" onclick=${acceptLoanProposal}>Start lending</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
},
|
||||
loanProcess(details = {}) {
|
||||
const {
|
||||
time,
|
||||
hasProvidedCollateral, hasAgreedToLend, hasLockedCollateral, hasIssuedLoan,
|
||||
collateralRequestID, loanRequestID, loanResponseID, collateralLockAckID
|
||||
} = details
|
||||
const { message: { borrower, coborrower, loan_amount, policy_id } } = floGlobals.myInbox[collateralRequestID]
|
||||
return Component(() => {
|
||||
const [sendingCollateral, setSendingCollateral] = useState(false)
|
||||
const collateralAmount = btcMortgage.util.toFixedDecimal(btcMortgage.policies[policy_id].loan_collateral_ratio * (loan_amount / floGlobals.btcRate))
|
||||
async function approveCollateralRequest(e) {
|
||||
const confirmation = await getConfirmation('Send collateral?', { message: `You are about to send ${formatAmount(collateralAmount)} as collateral. Continue?`, confirmText: 'Send', cancelText: 'Cancel' })
|
||||
if (!confirmation)
|
||||
return;
|
||||
setSendingCollateral(true)
|
||||
try {
|
||||
await btcMortgage.requestLoan(collateralRequestID, borrower)
|
||||
await floCloudAPI.noteApplicationData(collateralRequestID, 'approved')
|
||||
floGlobals.myInbox[collateralRequestID].note = 'approved';
|
||||
notify('Collateral sent successfully', 'success')
|
||||
} catch (err) {
|
||||
notify(err, 'error')
|
||||
setSendingCollateral(false)
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<li class="loan-process">
|
||||
<ul>
|
||||
<li class="done">
|
||||
<div class="progress">
|
||||
<div class="circle"></div>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="details">
|
||||
<h4>Initiated loan request</h4>
|
||||
<time>${getFormattedTime(time)}</time>
|
||||
</div>
|
||||
</li>
|
||||
<li class=${`${hasProvidedCollateral ? 'done' : ''}`}>
|
||||
<div class="progress">
|
||||
<div class="circle"></div>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="details">
|
||||
${hasProvidedCollateral ? html`
|
||||
<h4>Collateral provided</h4>
|
||||
${hasAgreedToLend ? html`
|
||||
<time>Collateral time</time>
|
||||
`: html`
|
||||
<p>Collateral has been sent by the collateral provider. Waiting for borrower to accept the loan proposal.</p>
|
||||
`}
|
||||
`: html`
|
||||
<h4>Waiting for collateral</h4>
|
||||
<p>Collateral amount is <b>${formatAmount(collateralAmount)}</b></p>
|
||||
${floCrypto.isSameAddr(coborrower, floDapps.user.id) ? html`
|
||||
<button class="button button--primary margin-right-auto" disabled=${sendingCollateral} onclick=${approveCollateralRequest}>
|
||||
${sendingCollateral ? html`
|
||||
Sending collateral
|
||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||
`: html`
|
||||
Send collateral
|
||||
`}
|
||||
</button>
|
||||
`: html`
|
||||
<p>Waiting for collateral to be sent by <strong class="wrap-around">${coborrower}</strong>.</p>
|
||||
`}
|
||||
`}
|
||||
</div>
|
||||
</li>
|
||||
<li class=${`${hasAgreedToLend ? 'done' : ''}`}>
|
||||
<div class="progress">
|
||||
<div class="circle"></div>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="details">
|
||||
${hasAgreedToLend ? html`
|
||||
<h4>Lender accepted loan proposal</h4>
|
||||
${hasLockedCollateral ? html`
|
||||
<time>Loan time</time>
|
||||
`: html`
|
||||
<p>Waiting for collateral to be locked.</p>
|
||||
`}
|
||||
`: html`
|
||||
<h4>Waiting for a lender</h4>
|
||||
<p>Waiting for a lender to accept the loan request.</p>
|
||||
`}
|
||||
</div>
|
||||
</li>
|
||||
<li class=${`${hasIssuedLoan ? 'done' : ''}`}>
|
||||
<div class="progress">
|
||||
<div class="circle"></div>
|
||||
</div>
|
||||
<div class="details">
|
||||
${hasLockedCollateral ? html`
|
||||
<h4>Collateral locked</h4>
|
||||
${hasIssuedLoan ? html`
|
||||
<time>Loan time</time>
|
||||
`: html`
|
||||
<p>Collateral has been locked. Waiting for lender to issue the loan.</p>
|
||||
`}
|
||||
`: html`
|
||||
<h4>Waiting for collateral to be locked</h4>
|
||||
<p>Waiting for collateral to be locked.</p>
|
||||
`}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
`
|
||||
})()
|
||||
},
|
||||
}
|
||||
// routing logic
|
||||
const router = new Router({
|
||||
@ -819,7 +931,7 @@
|
||||
}
|
||||
let userAddressTimeInterval;
|
||||
function renderHome(appState) {
|
||||
const { lastPage, page, params: { filter = 'pending', search } = {}, wildcards: [view = 'my-loans'] = [] } = appState || {};
|
||||
const { lastPage, page, params: { filter = 'pending', search } = {}, wildcards: [section = 'my-loans'] = [] } = appState || {};
|
||||
if (floGlobals.isSubAdmin) {
|
||||
renderElem(getRef('sub_page_container'), html`
|
||||
<section>
|
||||
@ -835,28 +947,24 @@
|
||||
} else {
|
||||
// user homepage
|
||||
const inbox = Component(() => {
|
||||
const myLoanRequests = groupLoanProcess()
|
||||
const [view, setView] = useState(section)
|
||||
let loanRequests = [];
|
||||
let collateralRequests = [];
|
||||
const loans = Object.keys(btcMortgage.loans)
|
||||
for (const key in floGlobals.myInbox) {
|
||||
if (!floGlobals.myInbox[key].note)
|
||||
collateralRequests.push(key)
|
||||
}
|
||||
for (const key in floGlobals.loanRequests) {
|
||||
const { message: { borrower, coborrower } } = floGlobals.loanRequests[key]
|
||||
if (!floCrypto.isSameAddr(borrower, floGlobals.myFloID) && !floCrypto.isSameAddr(coborrower, floGlobals.myFloID))
|
||||
loanRequests.push(key)
|
||||
}
|
||||
function handleChange(e) {
|
||||
history.pushState(null, null, `#/home/${e.target.value}`)
|
||||
setView(e.target.value)
|
||||
}
|
||||
return html`
|
||||
<section class="grid gap-1">
|
||||
<sm-chips onchange=${(e) => { location.hash = `#/home/${e.target.value}` }}>
|
||||
<sm-chips onchange=${handleChange}>
|
||||
<sm-chip value="my-loans" selected=${view === 'my-loans'}>
|
||||
My Loans
|
||||
${loans.length ? html`<span class="badge">${loans.length}</span>` : ''}
|
||||
</sm-chip>
|
||||
<sm-chip value="collateral-requests" selected=${view === 'collateral-requests'}>
|
||||
Collateral requests
|
||||
${collateralRequests.length ? html`<span class="badge">${collateralRequests.length}</span>` : ''}
|
||||
${myLoanRequests.length ? html`<span class="badge">${myLoanRequests.length}</span>` : ''}
|
||||
</sm-chip>
|
||||
<sm-chip value="loan-requests" selected=${view === 'loan-requests'}>
|
||||
Loan requests
|
||||
@ -865,9 +973,9 @@
|
||||
</sm-chips>
|
||||
<div class="grid gap-1">
|
||||
${view === 'my-loans' ? html`
|
||||
${loans.length ? html`
|
||||
${myLoanRequests.length ? html`
|
||||
<ul class="grid gap-1">
|
||||
|
||||
${myLoanRequests.map(loan => render.loanProcess(loan))}
|
||||
</ul>
|
||||
`: html`
|
||||
<div class="grid gap-2 align-center justify-center" style="padding: 2vw 0;">
|
||||
@ -879,15 +987,6 @@
|
||||
</div>
|
||||
`}
|
||||
`: ''}
|
||||
${view === 'collateral-requests' ? html`
|
||||
${collateralRequests.length ? html`
|
||||
<ul id="collateral_requests_list">
|
||||
${collateralRequests.map(requestId => render.collateralRequest(requestId, floGlobals.myInbox[requestId]))}
|
||||
</ul>
|
||||
`: html`
|
||||
<p>No collateral requests</p>
|
||||
`}
|
||||
`: ''}
|
||||
${view === 'loan-requests' ? html`
|
||||
${loanRequests.length ? html`
|
||||
<ul id="loan_requests_list">
|
||||
@ -908,8 +1007,69 @@
|
||||
</section>
|
||||
`
|
||||
})
|
||||
const balanceCard = Component(() => {
|
||||
const [btcBalance, setBtcBalance] = useState(0)
|
||||
const [floBalance, setFloBalance] = useState(0)
|
||||
const [usdBalance, setUsdBalance] = useState(0)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
function refreshBalance() {
|
||||
setIsRefreshing(true)
|
||||
fetchBalance().then(() => {
|
||||
setIsRefreshing(false)
|
||||
}).catch(error => {
|
||||
setIsRefreshing(false)
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
function fetchBalance() {
|
||||
return Promise.allSettled([
|
||||
btcOperator.getBalance(floGlobals.myBtcID),
|
||||
floBlockchainAPI.getBalance(floGlobals.myFloID),
|
||||
floTokenAPI.getBalance(floGlobals.myFloID, 'usd')
|
||||
]).then(result => {
|
||||
const [btcBalance, floBalance, usdBalance] = result.map(({ value }) => value)
|
||||
setBtcBalance(btcBalance)
|
||||
setFloBalance(floBalance)
|
||||
setUsdBalance(usdBalance)
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
fetchBalance()
|
||||
}, [])
|
||||
return html`
|
||||
<div class="grid gap-1-5">
|
||||
<div class="flex align-center space-between gap-1">
|
||||
<h4>My balances</h4>
|
||||
<button class="button button--colored button--small" disabled=${isRefreshing} onclick=${refreshBalance}>
|
||||
${isRefreshing ? html`
|
||||
Refreshing <sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||
`: html`
|
||||
Refresh
|
||||
`}
|
||||
</button>
|
||||
</div>
|
||||
<ul id="balance_list">
|
||||
<li class="balance-card">
|
||||
<span>BTC</span>
|
||||
<span>${formatAmount(btcBalance)}</span>
|
||||
</li>
|
||||
<li class="balance-card">
|
||||
<span>FLO</span>
|
||||
<span>${formatAmount(floBalance, 'FLO')}</span>
|
||||
</li>
|
||||
<li class="balance-card">
|
||||
<span>USD</span>
|
||||
<span>${formatAmount(usdBalance, 'usd')}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
renderElem(getRef('sub_page_container'), html`
|
||||
<div id="main_page" class="grid gap-2">
|
||||
${balanceCard()}
|
||||
${inbox()}
|
||||
</div>
|
||||
`);
|
||||
@ -918,6 +1078,58 @@
|
||||
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 = {}
|
||||
for (const key in allMessages) {
|
||||
const { message: { loan_amount, policy_id, loan_opening_process_id } = {}, type, time } = allMessages[key]
|
||||
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,
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
case 'type_loan_collateral_request':
|
||||
loansInProcess[loan_opening_process_id].time = time
|
||||
loansInProcess[loan_opening_process_id].collateralRequestID = key
|
||||
break;
|
||||
case 'type_loan_request':
|
||||
loansInProcess.hasProvidedCollateral = true
|
||||
loansInProcess[loan_opening_process_id].loanRequestID = key
|
||||
break;
|
||||
case 'type_loan_response':
|
||||
loansInProcess.hasAgreedToLend = true
|
||||
loansInProcess[loan_opening_process_id].loanResponseID = key
|
||||
break;
|
||||
case 'type_collateral_lock_ack':
|
||||
loansInProcess.hasLockedCollateral = true
|
||||
loansInProcess[loan_opening_process_id].collateralLockAckID = key
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// sort by time
|
||||
const sortedLoansInProcess = Object.values(loansInProcess).sort((a, b) => {
|
||||
return b.time - a.time
|
||||
})
|
||||
return sortedLoansInProcess
|
||||
}
|
||||
|
||||
router.addRoute('apply-loan', async () => {
|
||||
const loanApplication = Component(() => {
|
||||
return html`
|
||||
@ -990,7 +1202,9 @@
|
||||
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(floGlobals.myFloID)
|
||||
floGlobals.isAdmin = floGlobals.myFloID === floGlobals.adminID
|
||||
floGlobals.myInbox = {}
|
||||
floGlobals.myOutbox = {}
|
||||
floGlobals.loanRequests = {}
|
||||
floGlobals.requestTypes = {}
|
||||
floGlobals.btcRate = 1
|
||||
function fetchBtcRate() {
|
||||
btcMortgage.getRate['BTC']().then(rate => {
|
||||
@ -1011,6 +1225,7 @@
|
||||
...d
|
||||
}
|
||||
console.log('INBOX', d)
|
||||
router.routeTo(location.hash)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
@ -1023,10 +1238,28 @@
|
||||
...d
|
||||
}
|
||||
console.log('LOAN REQUESTS', d)
|
||||
router.routeTo(location.hash)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
btcMortgage.viewMyOutbox(d => {
|
||||
floGlobals.myOutbox = {
|
||||
...floGlobals.myOutbox,
|
||||
...d
|
||||
}
|
||||
console.log('OUTBOX', d)
|
||||
router.routeTo(location.hash)
|
||||
resolve()
|
||||
}).then(d => {
|
||||
floGlobals.myOutbox = d;
|
||||
console.log('OUTBOX', d)
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
]).then(result => {
|
||||
console.log(result)
|
||||
|
||||
@ -415,7 +415,7 @@
|
||||
function stringifyLoanOpenData(
|
||||
borrower, loan_amount, policy_id, btc_start_rate,
|
||||
coborrower, collateral_value, collateral_lock_id,
|
||||
lender, loan_transfer_id, lender_sign
|
||||
lender, loan_transfer_id, lender_sign, loan_opening_process_id
|
||||
) {
|
||||
return [
|
||||
LOAN_DETAILS_IDENTIFIER,
|
||||
@ -428,7 +428,8 @@
|
||||
"BTC rate:" + btc_start_rate + "USD",
|
||||
"Lender:" + floCrypto.toFloID(lender),
|
||||
"TokenTransfer:" + loan_transfer_id,
|
||||
"Signature-L:" + lender_sign
|
||||
"Signature-L:" + lender_sign,
|
||||
"LoanOpeningProcessID:" + loan_opening_process_id
|
||||
].join('|');
|
||||
/*MAYDO: Maybe make it a worded sentence?
|
||||
BTC Mortgage:
|
||||
@ -457,6 +458,7 @@
|
||||
case "Lender": details.lender = d[1]; break;
|
||||
case "TokenTransfer": details.loan_transfer_id = d[1]; break;
|
||||
case "Signature-L": details.lender_sign = d[1]; break;
|
||||
case "LoanOpeningProcessID": details.loan_opening_process_id = d[1]; break;
|
||||
}
|
||||
});
|
||||
return details;
|
||||
@ -900,7 +902,6 @@
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//view responses
|
||||
btcMortgage.viewMyInbox = function (callback = undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -913,6 +914,27 @@
|
||||
})
|
||||
}
|
||||
|
||||
btcMortgage.viewMyOutbox = (callback = undefined) => { //view all inbox
|
||||
return new Promise((resolve, reject) => {
|
||||
let options = { senderID: floDapps.user.id }
|
||||
if (callback instanceof Function)
|
||||
options.callback = callback;
|
||||
floCloudAPI.requestApplicationData(null, options)
|
||||
.then(_ => {
|
||||
compactIDB.readAllData("outbox")
|
||||
.then(result => {
|
||||
for (const key in result) {
|
||||
result[key].message = floCloudAPI.util.decodeMessage(result[key].message);
|
||||
}
|
||||
resolve(result);
|
||||
})
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/*Loan Opening*/
|
||||
|
||||
@ -931,7 +953,8 @@
|
||||
//request collateral from coborrower
|
||||
floCloudAPI.sendApplicationData({
|
||||
borrower, coborrower,
|
||||
loan_amount, policy_id
|
||||
loan_amount, policy_id,
|
||||
loan_opening_process_id: floCrypto.randString(12, true)
|
||||
}, TYPE_LOAN_COLLATERAL_REQUEST, { receiverID: coborrower })
|
||||
.then(result => {
|
||||
compactIDB.addData("outbox", result, result.vectorClock);
|
||||
@ -943,20 +966,18 @@
|
||||
function validate_loanCollateral_request(loan_collateral_req_id, borrower, coborrower) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.requestApplicationData(TYPE_LOAN_COLLATERAL_REQUEST, { atVectorClock: loan_collateral_req_id, receiverID: coborrower }).then(loan_collateral_req => {
|
||||
loan_collateral_req = loan_collateral_req[loan_collateral_req_id];
|
||||
if (!loan_collateral_req)
|
||||
const { senderID, receiverID, message: { loan_amount, policy_id, loan_opening_process_id }, pubkey } = loan_collateral_req[loan_collateral_req_id];
|
||||
if (!loan_collateral_req[loan_collateral_req_id])
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "request not found"));
|
||||
if (!floCrypto.isSameAddr(loan_collateral_req.senderID, borrower))
|
||||
if (!floCrypto.isSameAddr(senderID, borrower))
|
||||
return reject(RequestValidationError(TYPE_LOAN_COLLATERAL_REQUEST, "sender is not borrower"));
|
||||
if (!floCrypto.isSameAddr(loan_collateral_req.receiverID, coborrower))
|
||||
if (!floCrypto.isSameAddr(receiverID, coborrower))
|
||||
return reject(RequestValidationError(TYPE_LOAN_COLLATERAL_REQUEST, "receiver is not coborrower"));
|
||||
let { loan_amount, policy_id } = loan_collateral_req.message;
|
||||
if (typeof loan_amount !== 'number' || loan_amount <= 0 || !VALUE_REGEX.test(loan_amount))
|
||||
return reject(RequestValidationError(TYPE_LOAN_COLLATERAL_REQUEST, "Invalid loan amount"));
|
||||
if (!(policy_id in POLICIES))
|
||||
return reject(RequestValidationError(TYPE_LOAN_COLLATERAL_REQUEST, "Invalid policy"));
|
||||
let result = { loan_amount, policy_id, borrower, coborrower };
|
||||
result.borrower_pubKey = loan_collateral_req.pubKey;
|
||||
let result = { loan_amount, policy_id, borrower, coborrower, loan_opening_process_id, borrower_pubKey: pubkey };
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
@ -967,7 +988,7 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
const coborrower = floDapps.user.id;
|
||||
//validate request
|
||||
validate_loanCollateral_request(loan_collateral_req_id, borrower, coborrower).then(({ loan_amount, policy_id }) => {
|
||||
validate_loanCollateral_request(loan_collateral_req_id, borrower, coborrower).then(({ loan_amount, policy_id, loan_opening_process_id }) => {
|
||||
//calculate required collateral
|
||||
getRate["BTC"]().then(rate => {
|
||||
let policy = POLICIES[policy_id];
|
||||
@ -981,7 +1002,7 @@
|
||||
//post request
|
||||
floCloudAPI.sendApplicationData({
|
||||
borrower, coborrower,
|
||||
loan_amount, policy_id, loan_collateral_req_id,
|
||||
loan_amount, policy_id, loan_collateral_req_id, loan_opening_process_id,
|
||||
collateral: {
|
||||
rate,
|
||||
btc_id: coborrower_btcID,
|
||||
@ -1001,12 +1022,11 @@
|
||||
function validate_loan_request(loan_req_id, borrower, coborrower) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.requestApplicationData(TYPE_LOAN_REQUEST, { atVectorClock: loan_req_id }).then(loan_req => {
|
||||
loan_req = loan_req[loan_req_id];
|
||||
if (!loan_req)
|
||||
const { senderID, message: { loan_collateral_req_id, loan_amount, policy_id, collateral }, pubKey } = loan_req[loan_req_id];
|
||||
if (!loan_req[loan_req_id])
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "request not found"));
|
||||
if (!floCrypto.isSameAddr(coborrower, loan_req.senderID))
|
||||
if (!floCrypto.isSameAddr(coborrower, senderID))
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "request not posted by coborrower"))
|
||||
let { loan_collateral_req_id, loan_amount, policy_id, collateral } = loan_req.message;
|
||||
if (!floCrypto.isSameAddr(collateral.btc_id, coborrower))
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "collateral btc id is not coborrower"));
|
||||
validate_loanCollateral_request(loan_collateral_req_id, borrower, coborrower).then(result => {
|
||||
@ -1022,7 +1042,7 @@
|
||||
if (required_collateral > collateral.quantity)
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "Insufficient collateral value"));
|
||||
result.collateral = collateral;
|
||||
result.coborrower_pubKey = loan_req.pubKey;
|
||||
result.coborrower_pubKey = pubKey;
|
||||
resolve(result)
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
@ -1035,7 +1055,7 @@
|
||||
btcMortgage.respondLoan = function (loan_req_id, borrower, coborrower) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const lender = floDapps.user.id;
|
||||
validate_loan_request(loan_req_id, borrower, coborrower).then(({ loan_amount, borrower, collateral }) => {
|
||||
validate_loan_request(loan_req_id, borrower, coborrower).then(({ loan_amount, borrower, collateral, loan_opening_process_id }) => {
|
||||
//check if collateral is available
|
||||
btcOperator.getBalance(collateral.btc_id).then(coborrower_balance => {
|
||||
if (coborrower_balance < collateral.quantity)
|
||||
@ -1047,7 +1067,8 @@
|
||||
return reject("Insufficient tokens to lend");
|
||||
floCloudAPI.sendApplicationData({
|
||||
lender, borrower, coborrower,
|
||||
loan_req_id
|
||||
loan_req_id,
|
||||
loan_opening_process_id
|
||||
}, TYPE_LENDER_RESPONSE, { receiverID: borrower })
|
||||
.then(result => {
|
||||
compactIDB.addData("outbox", result, result.vectorClock);
|
||||
@ -1062,12 +1083,11 @@
|
||||
function validate_lender_response(lender_res_id, borrower, coborrower, lender) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.requestApplicationData(TYPE_LENDER_RESPONSE, { atVectorClock: lender_res_id, receiverID: borrower }).then(lender_res => {
|
||||
lender_res = lender_res[lender_res_id];
|
||||
if (!lender_res)
|
||||
const { senderID, message: { loan_req_id }, pubKey } = lender_res[lender_res_id];
|
||||
if (!lender_res[lender_res_id])
|
||||
return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "request not found"));
|
||||
if (!floCrypto.isSameAddr(lender, lender_res.senderID))
|
||||
if (!floCrypto.isSameAddr(lender, senderID))
|
||||
return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "request not sent by lender"))
|
||||
let { loan_req_id } = lender_res.message;
|
||||
validate_loan_request(loan_req_id, borrower, coborrower).then(result => {
|
||||
let { loan_amount } = result;
|
||||
//check if loan amount (token) is available to lend
|
||||
@ -1076,7 +1096,7 @@
|
||||
if (lender_tokenBalance < loan_amount)
|
||||
return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "lender doesnot have sufficient funds to lend"));
|
||||
result.lender = lender;
|
||||
result.lender_pubKey = lender_res.pubKey;
|
||||
result.lender_pubKey = pubKey;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
@ -1088,12 +1108,13 @@
|
||||
btcMortgage.requestCollateralLock = function (lender_res_id, coborrower, lender, privKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const borrower = floDapps.user.id;
|
||||
validate_lender_response(lender_res_id, borrower, coborrower, lender).then(({ loan_amount, policy_id }) => {
|
||||
validate_lender_response(lender_res_id, borrower, coborrower, lender).then(({ loan_amount, policy_id, loan_opening_process_id }) => {
|
||||
//send request to coborrower for locking the collateral asset
|
||||
let borrower_sign = sign_borrower(privKey, loan_amount, policy_id, coborrower, lender);
|
||||
floCloudAPI.sendApplicationData({
|
||||
lender, borrower, coborrower,
|
||||
lender_res_id, borrower_sign
|
||||
lender_res_id, borrower_sign,
|
||||
loan_opening_process_id
|
||||
}, TYPE_COLLATERAL_LOCK_REQUEST, { receiverID: coborrower })
|
||||
.then(result => {
|
||||
compactIDB.addData("outbox", result, result.vectorClock);
|
||||
@ -1129,7 +1150,7 @@
|
||||
btcMortgage.lockCollateral = function (collateral_lock_req_id, borrower, lender, privKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const coborrower = floDapps.user.id;
|
||||
validate_collateralLock_request(collateral_lock_req_id, borrower, coborrower, lender).then(({ borrower_sign, collateral, lender_pubKey }) => {
|
||||
validate_collateralLock_request(collateral_lock_req_id, borrower, coborrower, lender).then(({ borrower_sign, collateral, lender_pubKey, loan_opening_process_id }) => {
|
||||
//lock collateral
|
||||
lockCollateralInBlockchain(privKey, lender_pubKey, collateral.quantity).then(collateral_txid => {
|
||||
//sign and request lender to finalize
|
||||
@ -1137,7 +1158,8 @@
|
||||
floCloudAPI.sendApplicationData({
|
||||
borrower, coborrower, lender,
|
||||
collateral_lock_id: collateral_txid,
|
||||
coborrower_sign, collateral_lock_req_id
|
||||
coborrower_sign, collateral_lock_req_id,
|
||||
loan_opening_process_id
|
||||
}, TYPE_COLLATERAL_LOCK_ACK, { receiverID: lender })
|
||||
.then(result => {
|
||||
compactIDB.addData("outbox", result, result.vectorClock);
|
||||
@ -1193,7 +1215,7 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
const lender = floDapps.user.id;
|
||||
validate_collateralLock_ack(collateral_lock_ack_id, borrower, coborrower, lender).then(result => {
|
||||
let { loan_amount, policy_id, collateral, collateral_lock_id, borrower_sign, coborrower_sign } = result;
|
||||
let { loan_amount, policy_id, collateral, collateral_lock_id, borrower_sign, coborrower_sign, loan_opening_process_id } = result;
|
||||
//check if collateral is already used on a different loan
|
||||
for (let l in LOANS)
|
||||
if (LOANS[l].collateral_lock_id === collateral_lock_id)
|
||||
@ -1212,14 +1234,14 @@
|
||||
let loan_blockchain_data = stringifyLoanOpenData(
|
||||
borrower, loan_amount, policy_id, collateral.rate,
|
||||
coborrower, collateral.quantity, collateral_lock_id,
|
||||
lender, token_txid, lender_sign
|
||||
lender, token_txid, lender_sign, loan_opening_process_id
|
||||
);
|
||||
let receivers = [borrower, coborrower].map(addr => floCrypto.toFloID(addr));
|
||||
//write loan details in blockchain
|
||||
floBlockchainAPI.writeDataMultiple([privKey], loan_blockchain_data, receivers)
|
||||
.then(loan_txid => resolve(loan_txid))
|
||||
.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))
|
||||
@ -1382,7 +1404,7 @@
|
||||
btcMortgage.banker = {};
|
||||
btcMortgage.requestBanker = {};
|
||||
|
||||
// C: request T (banker) for collateral refund when lender hasnt dispersed the loan for 24 hrs
|
||||
// C: request T (banker) for collateral refund when lender hasn't dispersed the loan for 24 hrs
|
||||
btcMortgage.requestBanker.refundCollateral = function (collateral_lock_ack_id, borrower, lender, privKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const coborrower = floDapps.user.id;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user