2026-07-01 16:01:30 +00:00
const LOAN _PAYMENT _FEE _ATOMIC = 1000000 n ;
let loanPaymentContracts = [ ] ;
let loanPaymentLoadPromise = null ;
let loanPaymentContractsKey = "" ;
function setLoanPaymentMessage ( message , type = "" ) {
setScopedTransactionMessage ( "Loan Payment" , loanPaymentMessage , message , type ) ;
}
function selectedLoanPaymentContract ( ) {
const hash = String ( loanPaymentContractSelect ? . value || "" ) ;
return loanPaymentContracts . find ( ( contract ) => contract . contract === hash ) || null ;
}
function loanPaymentPeriodLabel ( period ) {
return {
daily : "Daily" ,
weekly : "Weekly" ,
monthly : "Monthly"
} [ String ( period || "" ) . toLowerCase ( ) ] || String ( period || "Payment" ) ;
}
function populateLoanPaymentContracts ( ) {
if ( ! loanPaymentContractSelect ) {
return ;
}
const previous = loanPaymentContractSelect . value ;
loanPaymentContractSelect . innerHTML = "" ;
if ( loanPaymentContracts . length ) {
const placeholder = document . createElement ( "option" ) ;
placeholder . value = "" ;
placeholder . textContent = "Select an active loan" ;
loanPaymentContractSelect . appendChild ( placeholder ) ;
}
loanPaymentContracts . forEach ( ( contract ) => {
const option = document . createElement ( "option" ) ;
option . value = contract . contract ;
const remaining = formatAtomicBalance ( contract . remaining _after _pending || "0" ) ;
option . textContent =
` ${ contract . loan _asset } loan from ${ truncateMiddle ( contract . lender , 8 , 8 ) } ( ${ remaining } remaining) ` ;
loanPaymentContractSelect . appendChild ( option ) ;
} ) ;
if ( ! loanPaymentContracts . length ) {
const option = document . createElement ( "option" ) ;
option . value = "" ;
option . textContent = "No active borrower loans" ;
loanPaymentContractSelect . appendChild ( option ) ;
} else if ( [ ... loanPaymentContractSelect . options ] . some ( ( option ) => option . value === previous ) ) {
loanPaymentContractSelect . value = previous ;
}
updateLoanPaymentDerivedFields ( true ) ;
}
async function loadLoanPaymentContracts ( force = false ) {
if ( loanPaymentLoadPromise ) {
return loanPaymentLoadPromise ;
}
2026-07-16 14:46:52 +00:00
if ( ! walletLoaded || ! registrationRegistered ) {
2026-07-01 16:01:30 +00:00
return ;
}
2026-07-16 14:46:52 +00:00
const walletToken = activeWalletCacheToken ( ) ;
const key = ` ${ walletToken } |local-active-loans ` ;
2026-07-01 16:01:30 +00:00
if ( ! force && loanPaymentContractsKey === key ) {
populateLoanPaymentContracts ( ) ;
return ;
}
loanPaymentLoadPromise = ( async ( ) => {
setLoanPaymentMessage ( "Loading active loans..." ) ;
2026-07-16 14:46:52 +00:00
const contracts = typeof loadLocalActiveBorrowerLoanContracts === "function"
? await loadLocalActiveBorrowerLoanContracts ( walletToken )
: [ ] ;
2026-07-01 16:01:30 +00:00
loanPaymentContracts = Array . isArray ( contracts ) ? contracts : [ ] ;
loanPaymentContractsKey = key ;
populateLoanPaymentContracts ( ) ;
setLoanPaymentMessage (
loanPaymentContracts . length ? "" : "No active loans require payment."
) ;
} ) ( ) . catch ( ( error ) => {
loanPaymentContracts = [ ] ;
populateLoanPaymentContracts ( ) ;
setLoanPaymentMessage ( String ( error ) , "error" ) ;
throw error ;
} ) . finally ( ( ) => {
loanPaymentLoadPromise = null ;
} ) ;
return loanPaymentLoadPromise ;
}
function expectedLoanPayment ( contract ) {
if ( ! contract ) {
return 0 n ;
}
const scheduled = BigInt ( contract . payment _amount || "0" ) ;
const remaining = BigInt ( contract . remaining _after _pending || "0" ) ;
return scheduled < remaining ? scheduled : remaining ;
}
function updateLoanPaymentWarning ( contract , amount ) {
if ( ! loanPaymentWarning ) {
return ;
}
const expected = expectedLoanPayment ( contract ) ;
const show = contract && amount !== null && amount > 0 n && amount < expected ;
loanPaymentWarning . classList . toggle ( "hidden" , ! show ) ;
loanPaymentWarning . textContent = show
? ` This is less than the expected ${ atomicToDecimal ( expected ) } ${ contract . loan _asset } payment. Partial payments are allowed, but the unpaid amount still counts toward the loan's overdue balance. `
: "" ;
}
function updateLoanPaymentDerivedFields ( setSuggestedAmount = false ) {
const contract = selectedLoanPaymentContract ( ) ;
if ( ! contract ) {
loanPaymentLender . textContent = "--" ;
loanPaymentAsset . textContent = "--" ;
loanPaymentScheduled . textContent = "--" ;
loanPaymentConfirmed . textContent = "--" ;
loanPaymentPending . textContent = "--" ;
loanPaymentRemaining . textContent = "--" ;
loanPaymentTipInput . value = "" ;
loanPaymentFeeInput . value = atomicToDecimal ( LOAN _PAYMENT _FEE _ATOMIC ) ;
updateLoanPaymentWarning ( null , null ) ;
return ;
}
const expected = expectedLoanPayment ( contract ) ;
if ( setSuggestedAmount || ! loanPaymentValueInput . value ) {
loanPaymentValueInput . value = atomicToDecimal ( expected ) ;
}
const amount = decimalToAtomic ( loanPaymentValueInput . value ) ;
const tip = amount !== null && amount > 0 n ? ( amount + 99 n ) / 100 n : 0 n ;
loanPaymentLender . textContent = contract . lender ;
loanPaymentAsset . textContent = contract . loan _asset ;
loanPaymentScheduled . textContent =
` ${ loanPaymentPeriodLabel ( contract . payment _period ) } : ${ atomicToDecimal ( BigInt ( contract . payment _amount || "0" ) ) } ${ contract . loan _asset } ` ;
loanPaymentConfirmed . textContent =
` ${ atomicToDecimal ( BigInt ( contract . confirmed _paid || "0" ) ) } ${ contract . loan _asset } ` ;
loanPaymentPending . textContent =
` ${ atomicToDecimal ( BigInt ( contract . pending _paid || "0" ) ) } ${ contract . loan _asset } ` ;
loanPaymentRemaining . textContent =
` ${ atomicToDecimal ( BigInt ( contract . remaining _after _pending || "0" ) ) } ${ contract . loan _asset } ` ;
loanPaymentTipInput . value = atomicToDecimal ( tip ) ;
loanPaymentFeeInput . value = atomicToDecimal ( LOAN _PAYMENT _FEE _ATOMIC ) ;
updateLoanPaymentWarning ( contract , amount ) ;
}
function loanPaymentBalance ( asset ) {
return activeWalletBalances . find ( ( balance ) => {
return String ( balance . asset || "" ) . trim ( ) . toLowerCase ( ) === String ( asset || "" ) . trim ( ) . toLowerCase ( )
&& Number ( balance . nft _series || 0 ) === 0 ;
} ) ;
}
function validateLoanPaymentForm ( ) {
if ( ! walletLoaded ) {
return "Load a wallet before making a loan payment." ;
}
if ( ! registrationRegistered ) {
return "Address registration is required before making a loan payment." ;
}
const contract = selectedLoanPaymentContract ( ) ;
if ( ! contract ) {
return "Select an active loan." ;
}
const amount = decimalToAtomic ( loanPaymentValueInput ? . value ) ;
if ( amount === null || amount <= 0 n ) {
return "Enter a valid payment amount." ;
}
const remaining = BigInt ( contract . remaining _after _pending || "0" ) ;
if ( amount > remaining ) {
return ` Payment cannot exceed the remaining ${ atomicToDecimal ( remaining ) } ${ contract . loan _asset } . ` ;
}
const tip = ( amount + 99 n ) / 100 n ;
const available = balanceAtomicValue ( loanPaymentBalance ( contract . loan _asset ) ) ;
if ( amount + tip > available ) {
return ` This wallet cannot cover the payment and 1% miner tip in ${ contract . loan _asset } . ` ;
}
const baseBalance = baseCoinBalanceAtomic ( ) ;
if ( isBaseTransferAsset ( contract . loan _asset ) ) {
if ( amount + tip + LOAN _PAYMENT _FEE _ATOMIC > available ) {
return ` This wallet cannot cover the payment, tip, and ${ atomicToDecimal ( LOAN _PAYMENT _FEE _ATOMIC ) } ${ activeBaseCoin ( ) } fee. ` ;
}
} else if ( baseBalance < LOAN _PAYMENT _FEE _ATOMIC ) {
return ` This payment requires ${ atomicToDecimal ( LOAN _PAYMENT _FEE _ATOMIC ) } ${ activeBaseCoin ( ) } for the transaction fee. ` ;
}
return "" ;
}
function loanPaymentPayload ( ) {
const contract = selectedLoanPaymentContract ( ) ;
return {
broadcastNode : activeBroadcastNode ( ) ,
contractHash : contract . contract ,
paybackAmount : String ( decimalToAtomic ( loanPaymentValueInput . value ) )
} ;
}
function setLoanPaymentButtonsDisabled ( disabled ) {
document . querySelector ( "[data-loan-payment-save]" ) ? . toggleAttribute ( "disabled" , disabled ) ;
document . querySelector ( "[data-loan-payment-broadcast]" ) ? . toggleAttribute ( "disabled" , disabled ) ;
}
function clearLoanPaymentForm ( ) {
loanPaymentContractSelect . value = "" ;
loanPaymentValueInput . value = "" ;
updateLoanPaymentDerivedFields ( false ) ;
}
async function handleLoanPaymentAction ( action ) {
const validationError = validateLoanPaymentForm ( ) ;
if ( validationError ) {
setLoanPaymentMessage ( validationError , "error" ) ;
return ;
}
const invoke = window . _ _TAURI _ _ ? . core ? . invoke ;
if ( ! invoke ) {
setLoanPaymentMessage ( "Wallet bridge is not available." , "error" ) ;
return ;
}
const walletKey = await requestSigningWalletKey ( ) ;
if ( guiRuntimeSettings . require _key _before _signing !== false && ! walletKey ) {
setLoanPaymentMessage ( "Signing cancelled." ) ;
return ;
}
setLoanPaymentButtonsDisabled ( true ) ;
try {
if ( action === "save" ) {
setLoanPaymentMessage ( "Saving loan payment transaction..." ) ;
const review = await invoke ( "save_loan_payment_transaction" , {
... loanPaymentPayload ( ) ,
walletKey
} ) ;
clearLoanPaymentForm ( ) ;
setLoanPaymentMessage ( ` Loan payment saved: ${ review . file _name } ` , "success" ) ;
if ( typeof refreshUnbroadcastTransactions === "function" ) {
refreshUnbroadcastTransactions ( ) ;
}
} else {
setLoanPaymentMessage ( "Broadcasting loan payment..." ) ;
const result = await invoke ( "broadcast_loan_payment_transaction" , {
... loanPaymentPayload ( ) ,
walletKey
} ) ;
await loadLoanPaymentContracts ( true ) ;
clearLoanPaymentForm ( ) ;
setLoanPaymentMessage ( result . response || "Loan payment broadcast." , "success" ) ;
}
} catch ( error ) {
setLoanPaymentMessage ( String ( error ) , "error" ) ;
await loadLoanPaymentContracts ( true ) . catch ( ( ) => { } ) ;
} finally {
setLoanPaymentButtonsDisabled ( false ) ;
}
}
function prepareLoanPaymentForm ( ) {
setLoanPaymentMessage ( "" ) ;
loadLoanPaymentContracts ( false ) . catch ( ( ) => {
// The form displays the lookup error.
} ) ;
}
function resetLoanPaymentSession ( ) {
loanPaymentContracts = [ ] ;
loanPaymentLoadPromise = null ;
loanPaymentContractsKey = "" ;
loanPaymentContractSelect . innerHTML = "" ;
loanPaymentValueInput . value = "" ;
updateLoanPaymentDerivedFields ( false ) ;
setLoanPaymentMessage ( "" ) ;
}
loanPaymentContractSelect ? . addEventListener ( "change" , ( ) => {
setLoanPaymentMessage ( "" ) ;
updateLoanPaymentDerivedFields ( true ) ;
} ) ;
loanPaymentValueInput ? . addEventListener ( "input" , ( ) => {
setLoanPaymentMessage ( "" ) ;
updateLoanPaymentDerivedFields ( false ) ;
} ) ;
document
. querySelector ( "[data-loan-payment-save]" )
? . addEventListener ( "click" , ( ) => handleLoanPaymentAction ( "save" ) ) ;
document
. querySelector ( "[data-loan-payment-broadcast]" )
? . addEventListener ( "click" , ( ) => handleLoanPaymentAction ( "broadcast" ) ) ;