Adding functions for loan-closing

This commit is contained in:
sairajzero 2023-07-21 05:29:07 +05:30
parent 8032aee802
commit 50305bde4c

View File

@ -16,7 +16,10 @@
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"
TYPE_COLLATERAL_LOCK_ACK = "type_collateral_lock_ack",
TYPE_LOAN_CLOSED_ACK = "type_loan_closed_ack",
TYPE_UNLOCK_COLLATERAL_REQUEST = "type_unlock_collateral_request",
TYPE_UNLOCK_COLLATERAL_ACK = "type_unlock_collateral_ack";
const POLICIES = {}
@ -316,6 +319,40 @@
else return false;
}
//Signed by borrower when closing the loan
const CLOSING_IDENTIFIER = "closing";
function sign_closing(privKey, loan_id, lender_sign) {
let borrower_floID = floCrypto.toFloID(floDapps.user.id)
//validate values before signing
if (!floCrypto.verifyPrivKey(privKey, borrower_floID))
throw "Invalid Private key for borrower";
if (typeof loan_id !== 'string' || !TXID_REGEX.test(loan_id))
throw "Invalid loan id";
//sign the value-data
let timestamp = Date.now();
let doc_array = [timestamp, CLOSING_IDENTIFIER, loan_id, lender_sign];
let sign_part = floCrypto.signData(doc_array.join("|"));
let pubKey = floCrypto.getPubKeyHex(privKey);
let closing_sign = [pubKey, sign_part, timestamp].join(".")
return closing_sign;
}
function verify_closingSign(closing_sign, borrower, loan_id, lender_sign) {
//split the signature part
let sign_splits = closing_sign.split('.');
let borrower_pubKey = sign_splits[0],
sign_part = sign_splits[1],
timestamp = sign_splits[2];
//validate values
if (!floCrypto.verifyPubKey(borrower_pubKey, borrower))
throw "Invalid public key";
//verify the signature
let doc_array = [timestamp, CLOSING_IDENTIFIER, loan_id, lender_sign];
if (floCrypto.verifySign(doc_array.join("|"), sign_part, borrower_pubKey))
return timestamp;
else return false;
}
/*Loan Opening*/
//1. B: requests collateral from coborrower
@ -604,4 +641,135 @@
})
}
/*Loan Closing*/
//1. B: sends amount (PA + interest) to L (via USD tokens)
btcMortgage.repayLoan = function (loan_id, privKey) {
return new Promise((resolve, reject) => {
getLoanDetails(loan_id).then(loan_details => {
//calculate repayment amount
let policy = POLICIES[loan_details.policy_id],
current_time = Date.now(),
duration = yearDiff(current_time, loan_details.open_time);
let interest_amount = loan_details.loan_amount * policy.interest * duration;
let repay_amount = loan_details.loan_amount + interest_amount;
//repay and close the loan
let closing_sign = sign_closing(privKey, loan_id, loan_details.lender_sign);
var closing_data = stringifyLoanCloseData(loan_id, loan_details.borrower, closing_sign);
floTokenAPI.sendToken(privKey, repay_amount, loan_details.lender, closing_data, CURRENCY).then(closing_txid => {
//send message to coborrower as reminder to unlock collateral
floCloudAPI.sendApplicationData({ loan_id, closing_txid }, TYPE_LOAN_CLOSED_ACK, { receiverID: loan_details.coborrower })
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
//2. C: requests L or T to free collateral
btcMortgage.requestUnlockCollateral = function (loan_id, closing_txid, privKey) {
return new Promise((resolve, reject) => {
let coborrower_pubKey = floDapps.user.public;
validateLoanClosing(loan_id, closing_txid).then(loan_details => {
//find locker
let lender_pubKey = extractPubKeyFromSign(loan_details.lender_sign);
let locker = findLocker(coborrower_pubKey, lender_pubKey)
//create the tx hex and sign it
createUnlockCollateralTxHex(locker, loan_details.collateral_lock_id, privKey).then(unlock_tx_hex => {
floCloudAPI.sendApplicationData({
loan_id, closing_txid, unlock_tx_hex
}, TYPE_UNLOCK_COLLATERAL_REQUEST, { receiverID: loan_details.lender })
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
function createUnlockCollateralTxHex(locker, collateral_lock_id, privKey) {
return new Promise((resolve, reject) => {
btcOperator.getUTXOs(locker.address).then(utxos => {
let collateral_utxos = utxos.filter(u => u.txid == collateral_lock_id);
if (!collateral_utxos.length)
return reject("Collateral already unlocked");
btcOperator.getTx(collateral_lock_id).then(collateral_tx => {
if (collateral_tx.confirmations == 0)
return reject("Collateral not confirmed in blockchain"); //This should not happen, as loan will not be issued until collateral is locked with confirmations
let collateral_owner = collateral_tx.inputs[0].address; //this will be coborrower's BTC id
//create the tx
const tx = coinjs.transaction();
//estimate the fee
let estimate_tx_size = BASE_TX_SIZE;
estimate_tx_size += collateral_utxos.length * btcOperator.util.sizePerInput(locker.address, locker.redeemScript)
estimate_tx_size += btcOperator.util.sizePerOutput(collateral_owner);
btcOperator.util.get_fee_rate().then(fee_rate => {
let fee_estimate = fee_rate * estimate_tx_size;
//add inputs
let total_input_value = 0;
collateral_utxos.forEach(u => {
//locker is btc bech32 multisig
let s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(rs));
s.writeOp(0);
s.writeBytes(coinjs.numToBytes(u.value.toFixed(0), 8));
script = Crypto.util.bytesToHex(s.buffer);
tx.addinput(u.txid, u.vout, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
total_input_value += u.value;
});
total_input_value = btcOperator.util.Sat_to_BTC; //convert from satoshi to BTC
//add output
let receiver_amount = total_input_value - fee_estimate;
console.debug("FEE calc", total_input_value, fee_estimate, receiver_amount);
tx.addoutput(collateral_owner, receiver_amount);
tx.sign(privKey, 1 /*sighashtype*/);
resolve(tx.serialize())
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
//3. L: unlock collateral
btcMortgage.unlockCollateral = function (loan_id, closing_txid, unlock_tx_hex, privKey) {
let lender_pubKey = floDapps.user.public;
validateLoanClosing(loan_id, closing_txid).then(loan_details => {
//find locker
let coborrower_pubKey = extractPubKeyFromSign(loan_details.coborrower_sign);
let locker = findLocker(coborrower_pubKey, lender_pubKey)
//verify and sign the tx
signUnlockCollateralTxHex(locker, loan_details.collateral_lock_id, unlock_tx_hex, privKey).then(signed_tx_hex => {
btcOperator.broadcastTx(signed_tx_hex).then(txid => {
floCloudAPI.sendApplicationData({
loan_id, closing_txid, unlock_collateral_id: txid
}, TYPE_UNLOCK_COLLATERAL_ACK, { receiverID: loan_details.coborrower })
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}
function signUnlockCollateralTxHex(locker, collateral_lock_id, unlock_tx_hex, privKey) {
return new Promise((resolve, reject) => {
btcOperator.getUTXOs(locker.address).then(utxos => {
let collateral_utxos = utxos.filter(u => u.txid == collateral_lock_id);
if (!collateral_utxos.length)
return reject("Collateral already unlocked");
btcOperator.getTx(collateral_lock_id).then(collateral_tx => {
if (collateral_tx.confirmations == 0)
return reject("Collateral not confirmed in blockchain"); //This should not happen, as loan will not be issued until collateral is locked with confirmations
//create the tx
let tx = coinjs.transaction().deserialize(unlock_tx_hex);
//check inputs
if (tx.ins.some(i => i.outpoint.hash !== collateral_lock_id))//vin other than this collateral is present in tx, ABORT
return reject("Transaction Hex contains other/non collateral inputs");
//sign the tx hex
tx.sign(privKey, 1 /*sighashtype*/);
resolve(tx.serialize())
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
})(window.btcMortgage = {})