Files
app.zeusln.zeus/models/Invoice.ts
T

378 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { observable, computed } from 'mobx';
import humanizeDuration from 'humanize-duration';
import BaseModel from './BaseModel';
import Base64Utils from './../utils/Base64Utils';
import DateTimeUtils from './../utils/DateTimeUtils';
import Bolt11Utils from './../utils/Bolt11Utils';
import { localeString } from './../utils/LocaleUtils';
interface HopHint {
fee_proportional_millionths: number;
chan_id: string;
fee_base_msat: number;
cltv_expiry_delta: number;
node_id: string;
}
interface RouteHint {
hop_hints: Array<HopHint>;
}
interface HTLC {
custom_records?: CustomRecords;
}
interface CustomRecords {
[key: number]: string;
}
const keySendMessageType = '34349334';
export default class Invoice extends BaseModel {
public route_hints: Array<RouteHint>;
public fallback_addr: string;
public r_hash: any;
public settle_date: string;
public expiry: string;
public memo: string;
public receipt: string;
public settle_index: string;
public add_index: string;
public payment_request: string;
public value: string;
public settled: boolean;
public amt_paid_msat: string;
public amt_paid: string;
public amt_paid_sat: string;
public private: boolean;
public creation_date: string;
public description_hash: string;
public r_preimage: any;
public cltv_expiry: string;
public htlcs: Array<HTLC>;
public is_amp?: boolean;
// c-lightning, eclair
public bolt11: string;
public label: string;
public description: string;
public msatoshi: number;
public msatoshi_received: number;
@observable public payment_hash: string;
public paid_at: number;
public expires_at: number;
public status: string;
// pay req
public timestamp?: string | number;
public destination?: string;
public num_satoshis?: string | number;
public features?: any;
// lndhub
public amt?: number;
public ispaid?: boolean;
public expire_time?: number;
public millisatoshis?: string;
public pay_req?: string;
public formattedOriginalTimeUntilExpiry: string;
public formattedTimeUntilExpiry: string;
@computed public get model(): string {
return localeString('views.Invoice.title');
}
@computed public get getRPreimage(): string {
if (!this.r_preimage) return '';
const preimage = this.r_preimage.data || this.r_preimage;
return typeof preimage === 'object'
? Base64Utils.bytesToHex(preimage)
: typeof preimage === 'string'
? preimage.includes('=')
? Base64Utils.base64ToHex(preimage)
: preimage
: '';
}
@computed public get getRHash(): string {
if (!this.r_hash) return '';
const hash = this.r_hash.data || this.r_hash;
return typeof hash === 'object'
? Base64Utils.bytesToHex(hash)
: typeof hash === 'string'
? hash.includes('=')
? Base64Utils.base64ToHex(hash)
: hash
: '';
}
@computed public get getDescriptionHash(): string {
const hash = this.description_hash;
return typeof hash === 'string'
? hash.includes('=')
? Base64Utils.base64ToHex(hash)
: hash
: '';
}
@computed public get getTimestamp(): string | number {
return (
this.paid_at ||
this.creation_date ||
this.timestamp ||
this.settle_date ||
0
);
}
@computed public get getMemo(): string {
return this.memo || this.description;
}
@computed public get isPaid(): boolean {
return this.status === 'paid' || this.settled || this.ispaid || false;
}
@computed public get key(): string {
return this.bolt11 || this.r_hash;
}
@computed public get getPaymentRequest(): string {
return this.bolt11 || this.payment_request || this.pay_req;
}
// return amount in satoshis
@computed public get getAmount(): number {
if (this.msatoshi_received) {
const msatoshi = this.msatoshi_received.toString();
return Number(msatoshi.replace('msat', '')) / 1000;
}
if (this.msatoshi) {
const msatoshi = this.msatoshi.toString();
return Number(msatoshi.replace('msat', '')) / 1000;
}
if (this.amount_received_msat) {
const msatoshi = this.amount_received_msat.toString();
return Number(msatoshi.replace('msat', '')) / 1000;
}
return this.settled
? Number(this.amt_paid_sat)
: Number(this.value) || Number(this.amt) || 0;
}
// return amount in satoshis
@computed public get getRequestAmount(): number {
if (this.msatoshi) {
const msatoshi = this.msatoshi.toString();
return Number(msatoshi.replace('msat', '')) / 1000;
}
if (this.amount_msat) {
const msatoshi = this.amount_msat.toString();
return Number(msatoshi.replace('msat', '')) / 1000;
}
if (this.millisatoshis) {
const msatoshi = this.millisatoshis;
return Number(msatoshi) / 1000;
}
return Number(this.num_satoshis || 0);
}
@computed public get getDisplayTime(): string {
return this.isPaid
? this.formattedSettleDate
: DateTimeUtils.listFormattedDate(
this.expires_at || this.creation_date || this.timestamp || 0
);
}
@computed public get getDisplayTimeOrder(): string {
return DateTimeUtils.listFormattedDateOrder(
new Date(
Number(
this.settle_date || this.paid_at || this.timestamp || 0
) * 1000
)
);
}
@computed public get getDisplayTimeShort(): string {
return this.isPaid && !this.is_amp
? DateTimeUtils.listFormattedDateShort(
this.settle_date || this.paid_at || this.timestamp || 0
)
: DateTimeUtils.listFormattedDateShort(
this.expires_at || this.creation_date || this.timestamp || 0
);
}
@computed public get getFormattedRhash(): string {
return this.r_hash
? typeof this.r_hash === 'string'
? this.r_hash.replace(/\+/g, '-').replace(/\//g, '_')
: this.r_hash.data
? Base64Utils.bytesToHex(this.r_hash.data)
: Base64Utils.bytesToHex(this.r_hash)
: '';
}
@computed public get getDate(): Date {
return this.isPaid
? this.settleDate
: DateTimeUtils.listDate(
this.expires_at || this.creation_date || this.timestamp || 0
);
}
@computed public get settleDate(): Date {
return DateTimeUtils.listDate(
this.settle_date || this.paid_at || this.timestamp || 0
);
}
@computed public get formattedSettleDate(): string {
return DateTimeUtils.listFormattedDate(
this.settle_date || this.paid_at || this.timestamp || 0
);
}
@computed public get formattedCreationDate(): string {
return DateTimeUtils.listFormattedDate(this.creation_date);
}
@computed public get isExpired(): boolean {
const getExpiryTimestamp = this.getExpiryUnixTimestamp();
if (getExpiryTimestamp == null) {
return false;
}
return getExpiryTimestamp * 1000 <= Date.now();
}
@computed public get getKeysendMessage(): string {
if (
this.htlcs &&
this.htlcs[0] &&
this.htlcs[0].custom_records &&
this.htlcs[0].custom_records[keySendMessageType]
) {
const encodedMessage =
this.htlcs[0].custom_records[keySendMessageType];
try {
return Base64Utils.base64ToUtf8(encodedMessage);
} catch (e) {
return '';
}
}
return '';
}
@computed public get isZeusPay(): boolean {
if (this.getMemo?.startsWith('ZEUS PAY')) return true;
return false;
}
@computed public get originalTimeUntilExpiryInSeconds():
| number
| undefined {
try {
const decodedPaymentRequest = Bolt11Utils.decode(
this.getPaymentRequest
);
if (this.expires_at != null) {
// expiry is missing in payment request in Core Lightning
return this.expires_at - decodedPaymentRequest.timestamp;
}
return decodedPaymentRequest.expiry;
} catch (e) {
return undefined;
}
}
public determineFormattedOriginalTimeUntilExpiry(
locale: string | undefined
): void {
const originalTimeUntilExpiryInSeconds =
this.originalTimeUntilExpiryInSeconds;
if (originalTimeUntilExpiryInSeconds == null) {
return localeString('models.Invoice.never');
}
const originalTimeUntilExpiryInMs =
originalTimeUntilExpiryInSeconds * 1000;
this.formattedOriginalTimeUntilExpiry =
this.formatHumanReadableDuration(
originalTimeUntilExpiryInMs,
locale
);
}
public determineFormattedRemainingTimeUntilExpiry(
locale: string | undefined
): void {
const millisecondsUntilExpiry =
this.getRemainingMillisecondsUntilExpiry();
if (millisecondsUntilExpiry == null) {
this.formattedTimeUntilExpiry = localeString(
'models.Invoice.never'
);
return;
}
this.formattedTimeUntilExpiry =
millisecondsUntilExpiry <= 0
? localeString('views.Activity.expired')
: this.formatHumanReadableDuration(
millisecondsUntilExpiry,
locale
);
}
private getRemainingMillisecondsUntilExpiry(): number | undefined {
const expiryTimestamp = this.getExpiryUnixTimestamp();
return expiryTimestamp != null
? expiryTimestamp * 1000 - Date.now()
: undefined;
}
private getExpiryUnixTimestamp(): number | undefined {
const originalTimeUntilExpiryInSeconds =
this.originalTimeUntilExpiryInSeconds;
if (originalTimeUntilExpiryInSeconds == null) {
return undefined;
}
try {
const paymentRequestTimestamp = Bolt11Utils.decode(
this.getPaymentRequest
).timestamp;
return paymentRequestTimestamp + originalTimeUntilExpiryInSeconds;
} catch (e) {
return undefined;
}
}
private formatHumanReadableDuration(
durationInMs: number,
locale: string | undefined
) {
return (
humanizeDuration(durationInMs, {
language: locale === 'zh' ? 'zh_CN' : locale,
fallbacks: ['en'],
round: true,
largest: 2
})
// replace spaces between digits and units with non-breaking spaces
.replace(/(\d) ([^,])/g, '$1 $2') // LTR
.replace(/([^,]) (\d)/g, '$2 $1') // RTL
);
}
}