import type { FeeDescriptor } from "../../../bridge/descriptor/types"; import { BigNumber } from "bignumber.js"; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isBigNumber(value: unknown): value is BigNumber { return BigNumber.isBigNumber(value); } type BitcoinFeeItem = { speed: string; feePerByte: BigNumber; }; function getSuggestedFeePerByteRange( transaction: Record, ): { min: string; max: string } | null { const networkInfo = transaction.networkInfo; if (!isRecord(networkInfo)) return null; const feeItems = networkInfo.feeItems; if (!isRecord(feeItems) || !Array.isArray(feeItems.items)) return null; const items = feeItems.items .filter((item): item is BitcoinFeeItem => { if (!isRecord(item)) return false; return typeof item.speed === "string" && isBigNumber(item.feePerByte); }) .map(item => item.feePerByte); if (items.length === 0) return null; const min = BigNumber.min(...items); const max = BigNumber.max(...items); return { min: min.toFixed(), max: max.toFixed(), }; } export const fees: FeeDescriptor = { hasPresets: true, hasCustom: true, hasCoinControl: true, presets: { legend: { type: "feeRate", unit: "sat/vbyte", valueFrom: "presetAmount" }, strategyLabelInAmount: "legend", getOptions: transaction => { if (!isRecord(transaction)) { return []; } const networkInfo = transaction.networkInfo; if (!isRecord(networkInfo)) { return []; } const feeItems = networkInfo.feeItems; if (!isRecord(feeItems) || !Array.isArray(feeItems.items)) { return []; } const items = feeItems.items .filter((item): item is BitcoinFeeItem => { if (!isRecord(item)) return false; return typeof item.speed === "string" && isBigNumber(item.feePerByte); }) .map(item => ({ speed: item.speed, feePerByte: item.feePerByte })); if (items.length === 0) { return []; } return [...items].reverse().map(item => ({ id: item.speed, amount: item.feePerByte, })); }, shouldEstimateWithBridge: () => true, }, custom: { inputs: [ { key: "feePerByte", type: "number", unitLabel: "sat/vbyte", suggestedRange: { getRange: transaction => { if (!isRecord(transaction)) return null; return getSuggestedFeePerByteRange(transaction); }, }, }, ], getInitialValues: (transaction): Record => { const empty: Record = {}; if (!isRecord(transaction)) return empty; const feePerByte = transaction.feePerByte; if (isBigNumber(feePerByte) && feePerByte.gt(0)) { return { feePerByte: feePerByte.toFixed(), }; } const networkInfo = transaction.networkInfo; if (isRecord(networkInfo) && isRecord(networkInfo.feeItems)) { const medium = networkInfo.feeItems.medium; if (isBigNumber(medium) && medium.gt(0)) { return { feePerByte: medium.toFixed(), }; } } return { feePerByte: "", }; }, buildTransactionPatch: values => { const feePerByte = new BigNumber(values.feePerByte); return { feesStrategy: "custom", feePerByte: feePerByte.isNaN() || feePerByte.isNegative() ? new BigNumber(0) : feePerByte.integerValue(BigNumber.ROUND_DOWN), }; }, }, };