Adding functions for loan-opening steps
This commit is contained in:
parent
376ff0e7da
commit
5853b13c49
@ -11,6 +11,13 @@
|
||||
TXID_REGEX = /^[0-9a-f]{64}$/i,
|
||||
VALUE_REGEX = /^\d+(.\d{1,8})?$/;
|
||||
|
||||
const
|
||||
TYPE_LOAN_COLLATERAL_REQUEST = "type_loan_collateral_request",
|
||||
TYPE_LOAN_REQUEST = "type_loan_request",
|
||||
TYPE_LENDER_RESPONSE = "type_loan_response",
|
||||
TYPE_COLLATERAL_LOCK_REQUEST = "type_collateral_lock_request",
|
||||
TYPE_COLLATERAL_LOCK_ACK = "type_collateral_lock_ack"
|
||||
|
||||
const POLICIES = {}
|
||||
|
||||
const toFixedDecimal = value => parseFloat((value).toFixed(8));
|
||||
@ -183,4 +190,292 @@
|
||||
return details;
|
||||
}
|
||||
|
||||
/*Loan taking*/
|
||||
|
||||
//1. B: requests collateral from coborrower
|
||||
btcMortgage.requestLoanCollateral = function (loan_amount, policy_id, coborrower) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const borrower = floDapps.user.id;
|
||||
//Input validation
|
||||
if (typeof loan_amount !== 'number' && loan_amount <= 0)
|
||||
return reject("Invalid loan amount: " + loan_amount);
|
||||
loan_amount = toFixedDecimal(loan_amount); //decimal allowed upto 8 decimal places
|
||||
if (!(policy_id in POLICIES))
|
||||
return reject("Invalid policy: " + policy_id);
|
||||
if (!floCrypto.validateAddr(coborrower))
|
||||
return reject("Invalid coborrower id")
|
||||
//request collateral from coborrower
|
||||
floCloudAPI.sendApplicationData({
|
||||
borrower, coborrower,
|
||||
loan_amount, policy_id
|
||||
}, TYPE_LOAN_COLLATERAL_REQUEST, { receiverID: coborrower })
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
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 }).then(loan_collateral_req => {
|
||||
if (!loan_collateral_req.length)
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "request not found"));
|
||||
loan_collateral_req = loan_collateral_req[0];
|
||||
if (!floCrypto.isSameAddr(loan_collateral_req.senderID, borrower))
|
||||
return reject(RequestValidationError(TYPE_LOAN_COLLATERAL_REQUEST, "sender is not borrower"));
|
||||
if (!floCrypto.isSameAddr(loan_collateral_req.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;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//2. B: post loan request (with proof of collateral)
|
||||
btcMortgage.requestLoan = function (loan_collateral_req_id, borrower) {
|
||||
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 }) => {
|
||||
//calculate required collateral
|
||||
getRate["BTC"].then(rate => {
|
||||
let policy = POLICIES[policy_id];
|
||||
let collateral_value = calcRequiredCollateral(loan_amount * rate, policy.security_percent)
|
||||
//check if collateral is available
|
||||
let coborrower_floID = floCrypto.toFloID(coborrower);
|
||||
let coborrower_btcID = btcOperator.convert.legacy2bech(coborrower_floID);
|
||||
btcOperator.getBalance(coborrower_btcID).then(coborrower_balance => {
|
||||
if (coborrower_balance < collateral_value)
|
||||
return reject("Insufficient collateral available");
|
||||
//post request
|
||||
floCloudAPI.sendApplicationData({
|
||||
borrower, coborrower,
|
||||
loan_amount, policy_id, loan_collateral_req_id,
|
||||
collateral: {
|
||||
btc_id: coborrower_btcID,
|
||||
quantity: collateral_value
|
||||
}
|
||||
}, TYPE_LOAN_REQUEST)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
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 => {
|
||||
if (!loan_req.length)
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "request not found"));
|
||||
loan_req = loan_req[0];
|
||||
if (!floCrypto.isSameAddr(coborrower, loan_req.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 => {
|
||||
if (result.loan_amount !== loan_amount)
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "loan amount mismatch"));
|
||||
if (policy_id !== result.policy_id)
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "policy id mismatch"));
|
||||
getRate["BTC"].then(rate => {
|
||||
let policy = POLICIES[policy_id];
|
||||
let required_collateral = calcRequiredCollateral(loan_amount * rate, policy.security_percent)
|
||||
if (required_collateral > collateral.quantity)
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "Insufficient collateral value"));
|
||||
//check if collateral is available
|
||||
btcOperator.getBalance(collateral.btc_id).then(coborrower_balance => {
|
||||
if (coborrower_balance < collateral.quantity)
|
||||
return reject(RequestValidationError(TYPE_LOAN_REQUEST, "Insufficient collateral available"));
|
||||
result.collateral = collateral;
|
||||
result.coborrower_pubKey = loan_req.pubKey;
|
||||
resolve(result)
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
//3. L: respond to loan request
|
||||
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 }) => {
|
||||
//check if loan amount (token) is available to lend
|
||||
let lender_floID = floCrypto.toFloID(lender);
|
||||
floTokenAPI.getBalance(lender_floID).then(lender_tokenBalance => {
|
||||
if (lender_tokenBalance < loan_amount)
|
||||
return reject("Insufficient tokens to lend");
|
||||
floCloudAPI.sendApplicationData({
|
||||
lender, borrower, coborrower,
|
||||
loan_req_id
|
||||
}, TYPE_LENDER_RESPONSE, { receiverID: borrower })
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
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 => {
|
||||
if (!lender_res.length)
|
||||
return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "response not found"));
|
||||
lender_res = lender_res[0];
|
||||
if (!floCrypto.isSameAddr(lender, lender_res.senderID))
|
||||
return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "response 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
|
||||
let lender_floID = floCrypto.toFloID(lender);
|
||||
floTokenAPI.getBalance(lender_floID).then(lender_tokenBalance => {
|
||||
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;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//4. B: requests C to lock the collateral
|
||||
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 }) => {
|
||||
//send request to coborrower for locking the collateral asset
|
||||
let borrower_sign = sign_borrower(privKey, loan_amount, policy_id, lender);
|
||||
floCloudAPI.sendApplicationData({
|
||||
lender, borrower, coborrower,
|
||||
lender_res_id, borrower_sign
|
||||
}, TYPE_COLLATERAL_LOCK_REQUEST, { receiverID: collateral.provider })
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}
|
||||
|
||||
function validate_collateralLock_request(collateral_lock_req_id, borrower, coborrower, lender) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.requestApplicationData(TYPE_COLLATERAL_LOCK_REQUEST, { atVectorClock: collateral_lock_req_id, receiverID: coborrower }).then(collateral_lock_req => {
|
||||
if (!collateral_lock_req.length)
|
||||
return reject(RequestValidationError(TYPE_COLLATERAL_LOCK_REQUEST, "request not found"));
|
||||
collateral_lock_req = collateral_lock_req[0];
|
||||
if (!floCrypto.isSameAddr(borrower, collateral_lock_req.senderID))
|
||||
return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "request not sent by borrower"));
|
||||
let { lender_res_id, borrower_sign } = collateral_lock_req.message;
|
||||
validate_lender_response(lender_res_id, borrower, coborrower, lender).then(result => {
|
||||
let { loan_amount, policy_id } = result;
|
||||
//verify borrower_sign
|
||||
let borrower_sign_time = verify_borrowerSign(borrower_sign, borrower, loan_amount, policy_id, lender)
|
||||
if (!borrower_sign_time) //MAYDO: expire signatures?
|
||||
return reject("Invalid borrower signature");
|
||||
result.borrower_sign = borrower_sign;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//5. C: locks required collateral in multisig (C, L, T)
|
||||
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 }) => {
|
||||
//lock collateral
|
||||
lockCollateralInBlockchain(privKey, lender_pubKey, collateral.quantity).then(collateral_txid => {
|
||||
//sign and request lender to finalize
|
||||
let coborrower_sign = sign_coborrower(privKey, borrower_sign, collateral.quantity, collateral_txid)
|
||||
floCloudAPI.sendApplicationData({
|
||||
borrower, coborrower, lender,
|
||||
collateral_lock_id: collateral_txid,
|
||||
coborrower_sign, collateral_lock_req_id
|
||||
}, TYPE_COLLATERAL_LOCK_ACK, { receiverID: lender })
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function lockCollateralInBlockchain(privKey, lenderPubKey, collateral_value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const locker_id = findLocker(floDapps.user.public, lenderPubKey).address;
|
||||
btcOperator.sendTx(floDapps.user.id, privKey, locker_id, collateral_value)
|
||||
.then(txid => resolve(txid))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function validate_collateralLock_ack(collateral_lock_ack_id, borrower, coborrower, lender) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.requestApplicationData(TYPE_COLLATERAL_LOCK_ACK, { atVectorClock: collateral_lock_ack_id, receiverID: lender }).then(collateral_lock_ack => {
|
||||
if (!collateral_lock_ack.length)
|
||||
return reject(RequestValidationError(TYPE_COLLATERAL_LOCK_REQUEST, "request not found"));
|
||||
collateral_lock_ack = collateral_lock_ack[0];
|
||||
if (!floCrypto.isSameAddr(borrower, collateral_lock_ack.senderID))
|
||||
return reject(RequestValidationError(TYPE_LENDER_RESPONSE, "request not sent by borrower"));
|
||||
let { collateral_lock_req_id, coborrower_sign, collateral_lock_id } = collateral_lock_ack.message;
|
||||
validate_collateralLock_request(collateral_lock_req_id, borrower, coborrower, lender).then(result => {
|
||||
let { borrower_sign, collateral } = result;
|
||||
let coborrower_sign_time = verify_coborrowerSign(coborrower_sign, coborrower, borrower_sign, collateral.quantity, collateral_lock_id)
|
||||
if (!coborrower_sign_time) //MAYDO: expire signatures?
|
||||
return reject(RequestValidationError(TYPE_COLLATERAL_LOCK_ACK, "Invalid coborrower signature"));
|
||||
btcOperator.getTx(collateral_lock_id).then(collateral_tx => {
|
||||
if (!collateral_tx.confirmations)
|
||||
return reject(RequestValidationError(TYPE_COLLATERAL_LOCK_ACK, "Collateral lock transaction not confirmed yet"));
|
||||
let locker_id = findLocker(finalize_request.pubKey, lender_response.pubKey).address;
|
||||
let locked_amt = collateral_tx.outputs.filter(o => o.address == locker_id).reduce((a, o) => a += o.value, 0);
|
||||
if (locked_amt < collateral.quantity)
|
||||
return reject(RequestValidationError(TYPE_COLLATERAL_LOCK_ACK, "Insufficient Collateral locked"));
|
||||
//TODO: make sure same collateral is not reused?
|
||||
result.coborrower_sign = coborrower_sign;
|
||||
result.collateral_lock_id = collateral_lock_id;
|
||||
resolve(result);
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//6. L: sends loan amount (USD tokens) to B and writes loan details in flo blockchain
|
||||
btcMortgage.sendLoanAmount = function (collateral_lock_ack_id, borrower, coborrower, privKey) {
|
||||
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, borrower_sign, coborrower_sign } = result;
|
||||
//transfer tokens for loan amount
|
||||
floTokenAPI.sendToken(privKey, loan_amount, borrower, "as loan", CURRENCY).then(token_txid => {
|
||||
//construct the blockchain data
|
||||
let lender_sign = sign_lender(privKey, coborrower_sign, token_txid);
|
||||
let blockchainData = stringifyLoanOpenData(
|
||||
borrower, loan_amount, policy_id,
|
||||
coborrower, collateral.quantity, collateral_lock_id,
|
||||
lender, token_txid,
|
||||
borrower_sign, coborrower_sign, lender_sign
|
||||
);
|
||||
let receivers = [borrower, coborrower].map(addr => floCrypto.toFloID(addr));
|
||||
//write loan details in blockchain
|
||||
floBlockchainAPI.writeDataMultiple([privKey], blockchainData, receivers)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
})(window.btcMortgage = {})
|
||||
Loading…
Reference in New Issue
Block a user