Contractless-GUI-Wallet/frontend/js/loans_page.js

735 lines
26 KiB
JavaScript

let loansContracts = [];
let loansLoadedForKey = "";
let loansLoadInFlight = false;
let selectedLoanContractHash = "";
let activeLoansStatusFilter = "all";
let loansStateMessage = "";
function loanFormatAtomic(value) {
if (typeof atomicToDecimal === "function") {
return atomicToDecimal(BigInt(value || "0"));
}
if (typeof formatAtomicBalance === "function") {
return formatAtomicBalance(value || "0");
}
return String(value || "0");
}
function loanShort(value, left = 8, right = 8) {
if (typeof truncateMiddle === "function") {
return truncateMiddle(value, left, right);
}
const text = String(value || "");
return text.length > left + right + 3 ? `${text.slice(0, left)}...${text.slice(-right)}` : text;
}
function escapeLoanText(value) {
return String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function activeLoanRole(contract) {
const active = String(activeWalletAddressShort || "").trim().toLowerCase();
if (String(contract.lender || "").trim().toLowerCase() === active) {
return "lender";
}
if (String(contract.borrower || "").trim().toLowerCase() === active) {
return "borrower";
}
return "unknown";
}
function loanRoleLabel(role) {
return role === "lender" ? "Lending" : role === "borrower" ? "Borrowing" : "Related";
}
function loanCounterparty(contract) {
return activeLoanRole(contract) === "lender" ? contract.borrower : contract.lender;
}
function loanStatusClass(status) {
const normalized = String(status || "").toLowerCase();
if (normalized === "delinquent") {
return "due";
}
if (normalized === "inactive") {
return "claimable";
}
return "active";
}
function loanStatusLabel(status) {
const normalized = String(status || "").toLowerCase();
if (normalized === "inactive") {
return "Completed";
}
return normalized ? normalized.charAt(0).toUpperCase() + normalized.slice(1) : "Unknown";
}
function loanPaymentCount(contract) {
return Array.isArray(contract.payments) ? contract.payments.length : 0;
}
function loanPeriodLabel(period) {
const normalized = String(period || "").trim().toLowerCase();
if (normalized === "d" || normalized === "daily") {
return "daily";
}
if (normalized === "w" || normalized === "weekly") {
return "weekly";
}
if (normalized === "m" || normalized === "monthly") {
return "monthly";
}
return normalized || "unknown";
}
function loanAmountLabel(value, asset) {
return `${loanFormatAtomic(value)} ${String(asset || "").trim()}`.trim();
}
function addUtcMonths(timestampSeconds, monthsToAdd) {
const start = new Date(Number(timestampSeconds || 0) * 1000);
if (Number.isNaN(start.getTime())) {
return null;
}
const year = start.getUTCFullYear();
const month = start.getUTCMonth() + Number(monthsToAdd || 0);
const day = start.getUTCDate();
const lastDay = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
return new Date(Date.UTC(
year,
month,
Math.min(day, lastDay),
start.getUTCHours(),
start.getUTCMinutes(),
start.getUTCSeconds()
));
}
function loanDueDateForPayment(contract, paymentIndex) {
const startTimestamp = Number(contract.start_timestamp || 0);
if (!startTimestamp || !paymentIndex) {
return null;
}
const period = String(contract.payment_period || "").trim().toLowerCase();
if (period === "daily") {
return new Date((startTimestamp + paymentIndex * 86_400) * 1000);
}
if (period === "weekly") {
return new Date((startTimestamp + paymentIndex * 7 * 86_400) * 1000);
}
if (period === "monthly") {
return addUtcMonths(startTimestamp, paymentIndex);
}
return null;
}
function formatLoanDueDate(date) {
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
return "--";
}
return date.toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZoneName: "short"
});
}
function nextLoanPaymentDueLabel(contract) {
if (String(contract.status || "").toLowerCase() === "inactive") {
return "No remaining payments";
}
const paymentAmount = BigInt(contract.payment_amount || "0");
const confirmedPaid = BigInt(contract.confirmed_paid || "0");
const paymentNumber = Number(contract.payment_number || 0);
if (!paymentNumber || paymentAmount <= 0n) {
return "--";
}
const completedPayments = Number(confirmedPaid / paymentAmount);
if (completedPayments >= paymentNumber) {
return "No remaining payments";
}
return formatLoanDueDate(loanDueDateForPayment(contract, completedPayments + 1));
}
function loanTransactionTime(transaction) {
return Number(
transaction?.unsigned_loan_contract?.time ??
transaction?.unsigned_loan_contract?.timestamp ??
transaction?.unsigned_contract_payment?.time ??
transaction?.unsigned_contract_payment?.timestamp ??
transaction?.unsigned_collateral_claim?.time ??
transaction?.unsigned_collateral_claim?.timestamp ??
0
) || 0;
}
function loanContractRecordFromCache(txid, record) {
const tx = record?.transaction?.unsigned_loan_contract;
if (!tx) {
return null;
}
const contractHash = txid || record?.txid || "";
const totalDue = BigInt(tx.payment_amount || 0) * BigInt(tx.payment_number || 0);
return {
contract: contractHash,
status: "active",
start_timestamp: Number(tx.time ?? tx.timestamp ?? 0) || 0,
lender: tx.lender || "",
borrower: tx.borrower || "",
loan_asset: tx.loan_coin || activeBaseCoin(),
loan_amount: String(tx.loan_amount || 0),
collateral: tx.collateral || activeBaseCoin(),
collateral_amount: String(tx.collateral_amount || 0),
payment_period: loanPeriodLabel(tx.payment_period),
payment_number: Number(tx.payment_number || 0),
payment_amount: String(tx.payment_amount || 0),
grace_period: Number(tx.grace_period || 0),
max_late_value: String(tx.max_late_value || 0),
confirmed_paid: "0",
pending_paid: "0",
remaining_balance: String(totalDue),
remaining_after_pending: String(totalDue),
payments: [],
collateral_claimed_by: ""
};
}
function loanPaymentsDueCount(contract, nowSeconds = Math.floor(Date.now() / 1000)) {
const startTimestamp = Number(contract?.start_timestamp || 0);
const paymentNumber = Number(contract?.payment_number || 0);
if (!startTimestamp || !paymentNumber || nowSeconds <= startTimestamp) {
return 0;
}
const period = String(contract.payment_period || "").trim().toLowerCase();
let due = 0;
if (period === "daily") {
due = Math.floor((nowSeconds - startTimestamp) / 86_400);
} else if (period === "weekly") {
due = Math.floor((nowSeconds - startTimestamp) / (7 * 86_400));
} else if (period === "monthly") {
const start = new Date(startTimestamp * 1000);
const now = new Date(nowSeconds * 1000);
due = (now.getUTCFullYear() - start.getUTCFullYear()) * 12
+ (now.getUTCMonth() - start.getUTCMonth());
const dueDate = addUtcMonths(startTimestamp, due);
if (dueDate && now.getTime() < dueDate.getTime()) {
due -= 1;
}
}
return Math.max(0, Math.min(paymentNumber, due));
}
function loanFinalGracePeriodPassed(contract, nowSeconds = Math.floor(Date.now() / 1000)) {
const paymentNumber = Number(contract?.payment_number || 0);
const gracePeriod = Number(contract?.grace_period || 0);
if (!paymentNumber) {
return false;
}
const dueDate = loanDueDateForPayment(contract, paymentNumber + gracePeriod);
if (!dueDate) {
return false;
}
return nowSeconds >= Math.floor(dueDate.getTime() / 1000) + 86_400;
}
function finalizeLocalLoanContract(contract) {
const totalPaid = (contract.payments || []).reduce(
(sum, payment) => sum + BigInt(payment.amount_atomic || payment.amount || 0),
0n
);
const paymentAmount = BigInt(contract.payment_amount || 0);
const totalDue = paymentAmount * BigInt(contract.payment_number || 0);
const remaining = totalDue > totalPaid ? totalDue - totalPaid : 0n;
const shouldHavePaid = BigInt(loanPaymentsDueCount(contract)) * paymentAmount;
const claimed = Boolean(contract.collateral_claimed_by);
contract.payments.sort((left, right) => Number(left.timestamp || 0) - Number(right.timestamp || 0));
contract.confirmed_paid = String(totalPaid);
contract.pending_paid = "0";
contract.remaining_balance = String(remaining);
contract.remaining_after_pending = String(remaining);
contract.status = claimed ? "inactive" : (shouldHavePaid > totalPaid ? "delinquent" : "active");
return contract;
}
async function applyRemoteCollateralStatuses(contracts) {
const invoke = window.__TAURI__?.core?.invoke;
const broadcastNode = typeof activeBroadcastNode === "function" ? activeBroadcastNode() : "";
if (!invoke || !broadcastNode || !Array.isArray(contracts) || !contracts.length) {
return contracts;
}
await Promise.all(contracts.map(async (contract) => {
if (!contract?.contract || contract.collateral_claimed_by) {
return;
}
try {
const status = await invoke("collateral_status", {
broadcastNode,
contractHash: contract.contract
});
if (status?.claimed) {
contract.collateral_claimed_by = "remote";
contract.status = "inactive";
}
} catch (_) {
// Keep the locally synced view usable if the lightweight status check is unavailable.
}
}));
return contracts;
}
async function loadLocalLoanContractsFromCache(walletToken = activeWalletCacheToken()) {
if (typeof readTransactionIndexObject !== "function" || typeof readTransactionCacheRecords !== "function") {
return [];
}
const index = await readTransactionIndexObject(walletToken);
let typeTxids = [7, 8, 9].flatMap((type) => Array.isArray(index.type_to_txids?.[String(type)])
? index.type_to_txids[String(type)]
: []);
if (!typeTxids.length) {
typeTxids = Array.isArray(index.ordered_txids) ? index.ordered_txids : [];
}
const uniqueTxids = Array.from(new Set(typeTxids.filter(Boolean)));
if (!uniqueTxids.length) {
return [];
}
const cache = await readTransactionCacheRecords(index, uniqueTxids, walletToken);
const contracts = new Map();
Object.entries(cache.transactions || {}).forEach(([txid, record]) => {
const contract = loanContractRecordFromCache(txid, record);
if (contract && (isActiveWalletAddress(contract.lender) || isActiveWalletAddress(contract.borrower))) {
contracts.set(contract.contract, contract);
}
});
Object.entries(cache.transactions || {}).forEach(([txid, record]) => {
const payment = record?.transaction?.unsigned_contract_payment;
if (payment?.contract_hash && contracts.has(payment.contract_hash)) {
const contract = contracts.get(payment.contract_hash);
contract.payments.push({
txid,
amount: String(payment.payback_amount || 0),
amount_atomic: String(payment.payback_amount || 0),
payer: payment.address || "",
timestamp: Number(payment.time ?? payment.timestamp ?? 0) || 0,
date: formatHistoryDate(loanTransactionTime(record.transaction))
});
}
const claim = record?.transaction?.unsigned_collateral_claim;
if (claim?.contract_hash && contracts.has(claim.contract_hash)) {
contracts.get(claim.contract_hash).collateral_claimed_by = claim.address || "";
}
});
return Array.from(contracts.values()).map(finalizeLocalLoanContract);
}
async function loadLocalActiveBorrowerLoanContracts(walletToken = activeWalletCacheToken()) {
const activeAddress = String(activeWalletAddressShort || "").trim().toLowerCase();
const contracts = await applyRemoteCollateralStatuses(await loadLocalLoanContractsFromCache(walletToken));
return contracts.filter((contract) => (
String(contract.borrower || "").trim().toLowerCase() === activeAddress &&
String(contract.status || "").toLowerCase() !== "inactive" &&
BigInt(contract.remaining_after_pending || 0) > 0n
));
}
function localCollateralClaimReason(contract) {
if (!contract || String(contract.status || "").toLowerCase() === "inactive") {
return "";
}
const role = activeLoanRole(contract);
const remainingBalance = BigInt(contract.remaining_balance || "0");
if (role === "borrower") {
return remainingBalance === 0n
? "Loan is fully repaid, so the borrower can reclaim collateral."
: "";
}
if (role !== "lender") {
return "";
}
if (remainingBalance > 0n && loanFinalGracePeriodPassed(contract)) {
return "The full loan term plus allowed grace has passed and an unpaid balance remains.";
}
const paymentsDue = loanPaymentsDueCount(contract);
const gracePeriod = Number(contract.grace_period || 0);
if (paymentsDue <= gracePeriod) {
return "";
}
const confirmedPaid = BigInt(contract.confirmed_paid || "0");
const paymentAmount = BigInt(contract.payment_amount || "0");
const maxLateValue = BigInt(contract.max_late_value || "0");
const shouldHavePaid = BigInt(paymentsDue) * paymentAmount;
const overdueValue = shouldHavePaid > confirmedPaid ? shouldHavePaid - confirmedPaid : 0n;
return overdueValue > maxLateValue
? `Borrower is late by ${overdueValue - maxLateValue} atomic units beyond the allowed threshold.`
: "";
}
async function loadLocalClaimableCollateralContracts(walletToken = activeWalletCacheToken()) {
const contracts = await applyRemoteCollateralStatuses(await loadLocalLoanContractsFromCache(walletToken));
return contracts
.map((contract) => {
const reason = localCollateralClaimReason(contract);
if (!reason) {
return null;
}
const role = activeLoanRole(contract);
return {
contract: contract.contract,
role,
reason,
lender: contract.lender,
borrower: contract.borrower,
loan_asset: contract.loan_asset,
loan_amount: contract.loan_amount,
collateral: contract.collateral,
collateral_amount: contract.collateral_amount,
confirmed_paid: contract.confirmed_paid,
pending_paid: contract.pending_paid,
remaining_balance: contract.remaining_balance,
remaining_after_pending: contract.remaining_after_pending,
fee: String(COLLATERAL_CLAIM_FEE_ATOMIC || 300000000n)
};
})
.filter(Boolean)
.sort((left, right) => left.contract.localeCompare(right.contract));
}
function sortedLoans() {
const query = String(loansSearch?.value || "").trim().toLowerCase();
const roleFilter = loansRoleFilter?.value || "all";
return loansContracts
.filter((contract) => {
const role = activeLoanRole(contract);
const status = String(contract.status || "").toLowerCase();
const searchText = [
contract.contract,
contract.lender,
contract.borrower,
contract.loan_asset,
contract.collateral,
status,
role
].join(" ").toLowerCase();
if (roleFilter !== "all" && role !== roleFilter) {
return false;
}
if (activeLoansStatusFilter !== "all" && status !== activeLoansStatusFilter) {
return false;
}
return !query || searchText.includes(query);
})
.sort((left, right) => Number(right.start_timestamp || 0) - Number(left.start_timestamp || 0));
}
function renderLoanCard(contract) {
const role = activeLoanRole(contract);
const selected = contract.contract === selectedLoanContractHash;
const counterparty = loanCounterparty(contract);
const principal = loanAmountLabel(contract.loan_amount, contract.loan_asset);
const remaining = loanAmountLabel(contract.remaining_after_pending, contract.loan_asset);
const payments = `${loanPaymentCount(contract)} / ${contract.payment_number || 0}`;
const nextDue = nextLoanPaymentDueLabel(contract);
return `
<button class="loan-card ${selected ? "active" : ""}" type="button" data-loan-contract="${escapeLoanText(contract.contract)}">
<div class="loan-card-top">
<div>
<strong>Loan ${escapeLoanText(loanShort(contract.contract, 6, 6))}</strong>
<span>${activeLoanRole(contract) === "lender" ? "Borrower" : "Lender"} ${escapeLoanText(loanShort(counterparty, 8, 8))}</span>
</div>
<div class="loan-badges">
<span class="role-badge ${role === "lender" ? "lending" : "borrowing"}">${escapeLoanText(loanRoleLabel(role))}</span>
<span class="loan-status ${loanStatusClass(contract.status)}">${escapeLoanText(loanStatusLabel(contract.status))}</span>
</div>
</div>
<div class="loan-metrics">
<div><span>Principal</span><b>${escapeLoanText(principal)}</b></div>
<div><span>Remaining</span><b>${escapeLoanText(remaining)}</b></div>
<div><span>Payments</span><b>${escapeLoanText(payments)}</b></div>
</div>
<div class="loan-next-due">
<span>Next payment due by:</span>
<b>${escapeLoanText(nextDue)}</b>
</div>
</button>
`;
}
function selectedLoanContract() {
return loansContracts.find((contract) => contract.contract === selectedLoanContractHash) || null;
}
function detailItem(label, value) {
return `<div><span>${escapeLoanText(label)}</span><b>${escapeLoanText(value || "--")}</b></div>`;
}
function renderLoanPaymentHistory(contract) {
if (!loanPaymentHistory) {
return;
}
const payments = Array.isArray(contract?.payments) ? contract.payments : [];
if (!payments.length) {
loanPaymentHistory.innerHTML = '<div class="subtle-state">No confirmed payments recorded for this loan.</div>';
return;
}
loanPaymentHistory.innerHTML = `
<table>
<thead>
<tr>
<th>Date</th>
<th>Amount</th>
<th>Payer</th>
<th>Tx Hash</th>
</tr>
</thead>
<tbody>
${payments.map((payment) => `
<tr>
<td>${escapeLoanText(payment.date || "--")}</td>
<td>${escapeLoanText(payment.amount || "0")} ${escapeLoanText(contract.loan_asset || "")}</td>
<td>${escapeLoanText(loanShort(payment.payer || "", 8, 8))}</td>
<td>${escapeLoanText(loanShort(payment.txid || "", 8, 8))}</td>
</tr>
`).join("")}
</tbody>
</table>
`;
}
function renderLoanDetail() {
const contract = selectedLoanContract();
if (!loanDetailGrid) {
return;
}
if (!contract) {
loanDetailGrid.innerHTML = detailItem("Selected Loan", "Select a loan to view details.");
if (loanPaymentHistory) {
loanPaymentHistory.innerHTML = '<div class="subtle-state">No loan selected.</div>';
}
loanCopyContractButton?.toggleAttribute("disabled", true);
loanMakePaymentButton?.toggleAttribute("disabled", true);
loanClaimCollateralButton?.toggleAttribute("disabled", true);
return;
}
const role = activeLoanRole(contract);
const collateral = loanAmountLabel(contract.collateral_amount, contract.collateral);
const payment = loanAmountLabel(contract.payment_amount, contract.loan_asset);
const confirmed = loanAmountLabel(contract.confirmed_paid, contract.loan_asset);
const pending = loanAmountLabel(contract.pending_paid, contract.loan_asset);
const remaining = loanAmountLabel(contract.remaining_after_pending, contract.loan_asset);
loanDetailGrid.innerHTML = [
detailItem("Contract Hash", contract.contract),
detailItem("Role", loanRoleLabel(role)),
detailItem("Status", loanStatusLabel(contract.status)),
detailItem("Principal", loanAmountLabel(contract.loan_amount, contract.loan_asset)),
detailItem("Payment Amount", payment),
detailItem("Collateral", collateral),
detailItem("Payment Schedule", `${contract.payment_number || 0} ${contract.payment_period || "payments"}`),
detailItem("Confirmed Paid", confirmed),
detailItem("Pending Total", pending),
detailItem("Remaining", remaining),
detailItem("Lender", contract.lender),
detailItem("Borrower", contract.borrower)
].join("");
renderLoanPaymentHistory(contract);
loanCopyContractButton?.toggleAttribute("disabled", false);
loanMakePaymentButton?.toggleAttribute(
"disabled",
!(role === "borrower" && String(contract.status || "").toLowerCase() !== "inactive")
);
loanClaimCollateralButton?.toggleAttribute("disabled", String(contract.status || "").toLowerCase() === "inactive");
}
function renderLoansPage() {
if (!loansList) {
return;
}
const rows = sortedLoans();
if (!selectedLoanContractHash && rows.length) {
selectedLoanContractHash = rows[0].contract;
}
if (selectedLoanContractHash && !loansContracts.some((contract) => contract.contract === selectedLoanContractHash)) {
selectedLoanContractHash = rows[0]?.contract || "";
}
loansList.innerHTML = rows.map(renderLoanCard).join("");
if (!walletLoaded) {
loansState && (loansState.textContent = "Load a wallet to view loans.");
} else if (loansLoadInFlight && !loansContracts.length) {
loansState && (loansState.textContent = "Loading loans...");
} else if (loansStateMessage) {
loansState && (loansState.textContent = loansStateMessage);
} else if (!loansContracts.length) {
loansState && (loansState.textContent = "No loan contracts found for this wallet.");
} else if (!rows.length) {
loansState && (loansState.textContent = "No loans match the current filters.");
} else {
loansState && (loansState.textContent = `${rows.length} loan${rows.length === 1 ? "" : "s"} shown`);
}
renderLoanDetail();
}
async function loadLoansPage({ force = false } = {}) {
if (!walletLoaded) {
loansContracts = [];
selectedLoanContractHash = "";
loansStateMessage = "Load a wallet to view loans.";
renderLoansPage();
return;
}
const walletToken = activeWalletCacheToken();
const key = `${walletToken}|local-loans`;
if (!force && loansLoadedForKey === key) {
renderLoansPage();
return;
}
if (loansLoadInFlight) {
return;
}
loansLoadInFlight = true;
loansStateMessage = "Loading loans...";
renderLoansPage();
try {
const contracts = await loadLocalLoanContractsFromCache(walletToken);
loansContracts = await applyRemoteCollateralStatuses(Array.isArray(contracts) ? contracts : []);
loansLoadedForKey = key;
selectedLoanContractHash = loansContracts[0]?.contract || "";
loansStateMessage = loansContracts.length
? ""
: "No locally synced loan contracts found for this wallet.";
} catch (error) {
loansContracts = [];
selectedLoanContractHash = "";
loansStateMessage = `Local loan cache lookup failed: ${String(error)}`;
} finally {
loansLoadInFlight = false;
renderLoansPage();
}
}
function prepareLoansPage({ force = false } = {}) {
renderLoansPage();
loadLoansPage({ force });
}
function resetLoansPage() {
loansContracts = [];
loansLoadedForKey = "";
loansLoadInFlight = false;
selectedLoanContractHash = "";
activeLoansStatusFilter = "all";
loansStateMessage = "";
loansSearch && (loansSearch.value = "");
loansRoleFilter && (loansRoleFilter.value = "all");
loansStatusButtons.forEach((button) => {
button.classList.toggle("active", button.dataset.loansStatusFilter === "all");
});
renderLoansPage();
}
loansSearch?.addEventListener("input", renderLoansPage);
loansRoleFilter?.addEventListener("change", renderLoansPage);
loansRefreshButton?.addEventListener("click", () => loadLoansPage({ force: true }));
loansCreateButton?.addEventListener("click", () => {
setActiveView("send");
setActiveTransactionType("Create Loan");
});
loansStatusButtons.forEach((button) => {
button.addEventListener("click", () => {
activeLoansStatusFilter = button.dataset.loansStatusFilter || "all";
loansStatusButtons.forEach((item) => item.classList.toggle("active", item === button));
selectedLoanContractHash = "";
renderLoansPage();
});
});
loansList?.addEventListener("click", (event) => {
const card = event.target.closest("[data-loan-contract]");
if (!card) {
return;
}
selectedLoanContractHash = card.dataset.loanContract || "";
renderLoansPage();
});
loanCopyContractButton?.addEventListener("click", async () => {
const contract = selectedLoanContract();
if (!contract) {
return;
}
const copied = typeof writeClipboardText === "function"
? await writeClipboardText(contract.contract)
: false;
if (!copied) {
return;
}
const original = loanCopyContractButton.textContent;
loanCopyContractButton.textContent = "Copied";
setTimeout(() => {
loanCopyContractButton.textContent = original;
}, 900);
});
loanMakePaymentButton?.addEventListener("click", () => {
const contract = selectedLoanContract();
if (typeof openTransactionType === "function") {
openTransactionType("Loan Payment");
} else {
setActiveView("send");
setActiveTransactionType("Loan Payment");
showTransactionForm("Loan Payment");
}
const selectContract = () => {
if (contract?.contract && loanPaymentContractSelect) {
loanPaymentContractSelect.value = contract.contract;
updateLoanPaymentDerivedFields(true);
}
};
if (typeof loadLoanPaymentContracts === "function") {
loadLoanPaymentContracts(false).then(selectContract).catch(() => {});
} else {
setTimeout(selectContract, 0);
}
});
loanClaimCollateralButton?.addEventListener("click", () => {
const contract = selectedLoanContract();
if (typeof openTransactionType === "function") {
openTransactionType("Collateral Claim");
} else {
setActiveView("send");
setActiveTransactionType("Collateral Claim");
showTransactionForm("Collateral Claim");
}
const selectContract = () => {
if (contract?.contract && collateralClaimContractSelect) {
collateralClaimContractSelect.value = contract.contract;
updateCollateralClaimDetails();
}
};
if (typeof loadCollateralClaimContracts === "function") {
loadCollateralClaimContracts(false).then(selectContract).catch(() => {});
} else {
setTimeout(selectContract, 0);
}
});