483 lines
15 KiB
TypeScript
483 lines
15 KiB
TypeScript
import { action, observable, runInAction } from 'mobx';
|
|
|
|
// LN
|
|
import Payment from './../models/Payment';
|
|
import Invoice from './../models/Invoice';
|
|
|
|
import Swap, { SwapState } from './../models/Swap';
|
|
|
|
//on-chain
|
|
import Transaction from './../models/Transaction';
|
|
|
|
import { LSPActivity, LSPOrderState } from './../models/LSP';
|
|
|
|
import SettingsStore from './SettingsStore';
|
|
import PaymentsStore from './PaymentsStore';
|
|
import InvoicesStore from './InvoicesStore';
|
|
import TransactionsStore from './TransactionsStore';
|
|
import CashuStore from './CashuStore';
|
|
import SwapStore from './SwapStore';
|
|
import NodeInfoStore from './NodeInfoStore';
|
|
|
|
import BackendUtils from './../utils/BackendUtils';
|
|
import ActivityFilterUtils from '../utils/ActivityFilterUtils';
|
|
import DateTimeUtils from '../utils/DateTimeUtils';
|
|
|
|
import Storage from '../storage';
|
|
|
|
import { LSPS_ORDERS_KEY } from './LSPStore';
|
|
|
|
export const LEGACY_ACTIVITY_FILTERS_KEY = 'zeus-activity-filters';
|
|
export const ACTIVITY_FILTERS_KEY = 'zeus-activity-filters-v2';
|
|
|
|
export const SERVICES_CONFIG = {
|
|
swaps: 'swapFilter',
|
|
lsps1: 'lsps1State',
|
|
lsps7: 'lsps7State'
|
|
};
|
|
|
|
const createSwapStateRecord = (
|
|
initialValue: boolean
|
|
): Record<SwapState, boolean> => {
|
|
return Object.values(SwapState).reduce((acc, state) => {
|
|
acc[state] = initialValue;
|
|
return acc;
|
|
}, {} as Record<SwapState, boolean>);
|
|
};
|
|
|
|
type ActivityItem = Invoice | Payment | Transaction | Swap | LSPActivity;
|
|
|
|
export interface Filter {
|
|
[index: string]: any;
|
|
lightning: boolean;
|
|
onChain: boolean;
|
|
cashu: boolean;
|
|
sent: boolean;
|
|
swaps: boolean;
|
|
submarine: boolean;
|
|
reverse: boolean;
|
|
lsps1: boolean;
|
|
lsps7: boolean;
|
|
received: boolean;
|
|
unpaid: boolean;
|
|
inTransit: boolean;
|
|
isFailed: boolean;
|
|
unconfirmed: boolean;
|
|
zeusPay: boolean;
|
|
keysend: boolean;
|
|
circularRebalance: boolean;
|
|
minimumAmount: number;
|
|
maximumAmount?: number;
|
|
startDate?: Date;
|
|
endDate?: Date;
|
|
memo: string;
|
|
swapFilter: {
|
|
submarine: Record<SwapState, boolean>;
|
|
reverse: Record<SwapState, boolean>;
|
|
};
|
|
lsps1State: Record<LSPOrderState, boolean>;
|
|
lsps7State: Record<LSPOrderState, boolean>;
|
|
}
|
|
|
|
export const DEFAULT_FILTERS = {
|
|
lightning: true,
|
|
onChain: true,
|
|
cashu: true,
|
|
sent: true,
|
|
swaps: true,
|
|
submarine: true,
|
|
reverse: true,
|
|
lsps1: true,
|
|
lsps7: true,
|
|
received: true,
|
|
unpaid: true,
|
|
inTransit: false,
|
|
isFailed: false,
|
|
unconfirmed: true,
|
|
standardInvoices: true,
|
|
ampInvoices: true,
|
|
zeusPay: true,
|
|
keysend: true,
|
|
circularRebalance: true,
|
|
minimumAmount: 0,
|
|
maximumAmount: undefined,
|
|
startDate: undefined,
|
|
endDate: undefined,
|
|
memo: '',
|
|
swapFilter: {
|
|
submarine: createSwapStateRecord(true),
|
|
reverse: createSwapStateRecord(true)
|
|
},
|
|
lsps1State: {
|
|
[LSPOrderState.CREATED]: true,
|
|
[LSPOrderState.COMPLETED]: true,
|
|
[LSPOrderState.FAILED]: true
|
|
},
|
|
lsps7State: {
|
|
[LSPOrderState.CREATED]: true,
|
|
[LSPOrderState.COMPLETED]: true,
|
|
[LSPOrderState.FAILED]: true
|
|
}
|
|
};
|
|
|
|
export default class ActivityStore {
|
|
@observable public error = false;
|
|
@observable public activity: Array<ActivityItem> = [];
|
|
@observable public filteredActivity: Array<ActivityItem> = [];
|
|
@observable public filters: Filter = DEFAULT_FILTERS;
|
|
settingsStore: SettingsStore;
|
|
paymentsStore: PaymentsStore;
|
|
invoicesStore: InvoicesStore;
|
|
transactionsStore: TransactionsStore;
|
|
cashuStore: CashuStore;
|
|
swapStore: SwapStore;
|
|
nodeInfoStore: NodeInfoStore;
|
|
|
|
constructor(
|
|
settingsStore: SettingsStore,
|
|
paymentsStore: PaymentsStore,
|
|
invoicesStore: InvoicesStore,
|
|
transactionsStore: TransactionsStore,
|
|
cashuStore: CashuStore,
|
|
swapStore: SwapStore,
|
|
nodeInfoStore: NodeInfoStore
|
|
) {
|
|
this.settingsStore = settingsStore;
|
|
this.paymentsStore = paymentsStore;
|
|
this.transactionsStore = transactionsStore;
|
|
this.invoicesStore = invoicesStore;
|
|
this.cashuStore = cashuStore;
|
|
this.swapStore = swapStore;
|
|
this.nodeInfoStore = nodeInfoStore;
|
|
}
|
|
|
|
public resetFilters = async () => {
|
|
this.filters = DEFAULT_FILTERS;
|
|
await Storage.setItem(ACTIVITY_FILTERS_KEY, this.filters);
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
public setFiltersPos = async () => {
|
|
this.filters = {
|
|
lightning: true,
|
|
onChain: true,
|
|
cashu: true,
|
|
swaps: true,
|
|
submarine: true,
|
|
reverse: true,
|
|
lsps1: true,
|
|
lsps7: true,
|
|
sent: false,
|
|
received: true,
|
|
unpaid: false,
|
|
inTransit: false,
|
|
isFailed: false,
|
|
unconfirmed: true,
|
|
zeusPay: true,
|
|
keysend: true,
|
|
circularRebalance: true,
|
|
minimumAmount: 0,
|
|
maximumAmount: undefined,
|
|
startDate: undefined,
|
|
endDate: undefined,
|
|
memo: '',
|
|
swapFilter: { ...DEFAULT_FILTERS.swapFilter },
|
|
lsps1State: { ...DEFAULT_FILTERS.lsps1State },
|
|
lsps7State: { ...DEFAULT_FILTERS.lsps7State }
|
|
};
|
|
await Storage.setItem(ACTIVITY_FILTERS_KEY, this.filters);
|
|
};
|
|
|
|
@action
|
|
public setAmountFilter = (filter: any) => {
|
|
this.filters.minimumAmount = filter;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
@action
|
|
public setKeysendFilter = (filter: any) => {
|
|
this.filters.keysend = filter;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
@action
|
|
public setMaximumAmountFilter = (filter: any) => {
|
|
this.filters.maximumAmount = filter;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
@action
|
|
public setStartDateFilter = (filter: any) => {
|
|
this.filters.startDate = filter;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
@action
|
|
public setEndDateFilter = (filter: any) => {
|
|
this.filters.endDate = filter;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
@action
|
|
public clearStartDateFilter = () => {
|
|
this.filters.startDate = undefined;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
@action
|
|
public clearEndDateFilter = () => {
|
|
this.filters.endDate = undefined;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
@action
|
|
public setMemoFilter = (filter: any) => {
|
|
this.filters.memo = filter;
|
|
this.setFilters(this.filters);
|
|
};
|
|
|
|
private async getLSPOrders(): Promise<any[]> {
|
|
try {
|
|
const responseArrayString = await Storage.getItem(LSPS_ORDERS_KEY);
|
|
if (!responseArrayString) return [];
|
|
|
|
const responseArray = JSON.parse(responseArrayString);
|
|
const decodedResponses = responseArray.map((res: string) =>
|
|
JSON.parse(res)
|
|
);
|
|
|
|
const currentNodeId = this.nodeInfoStore.nodeInfo.nodeId;
|
|
const selectedOrders = decodedResponses.filter(
|
|
(res: any) => res.clientPubkey === currentNodeId
|
|
);
|
|
|
|
return selectedOrders.map((res: any) => {
|
|
const orderData = res?.order?.result || res?.order;
|
|
const createdAt = orderData?.created_at;
|
|
const timestamp = createdAt
|
|
? new Date(createdAt).getTime() / 1000
|
|
: 0;
|
|
|
|
if (res.service === 'LSPS7') {
|
|
const payment = orderData?.payment;
|
|
const fee =
|
|
payment?.bolt11?.order_total_sat ||
|
|
payment?.fee_total_sat ||
|
|
0;
|
|
return {
|
|
model: 'LSPS7Order',
|
|
id: orderData?.order_id,
|
|
state: orderData?.order_state,
|
|
getAmount: Number(fee),
|
|
getTimestamp: timestamp,
|
|
getDate: new Date(createdAt),
|
|
getDisplayTimeShort:
|
|
DateTimeUtils.listFormattedDateShort(timestamp)
|
|
};
|
|
}
|
|
|
|
return {
|
|
model: 'LSPS1Order',
|
|
id: orderData?.order_id,
|
|
state: orderData?.order_state,
|
|
getAmount: Number(orderData?.lsp_balance_sat) || 0,
|
|
getTimestamp: timestamp,
|
|
getDate: new Date(createdAt),
|
|
getDisplayTimeShort:
|
|
DateTimeUtils.listFormattedDateShort(timestamp)
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error(
|
|
'Error fetching LSP Orders for activity list:',
|
|
error
|
|
);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
getSortedActivity = async () => {
|
|
const activity: any[] = [];
|
|
const payments = this.paymentsStore.payments;
|
|
const transactions = this.transactionsStore.transactions;
|
|
const invoices = this.invoicesStore.invoices;
|
|
const swaps = this.swapStore.swaps;
|
|
const lspOrders = await this.getLSPOrders();
|
|
|
|
let additions = payments.concat(invoices);
|
|
|
|
additions = additions.concat(swaps);
|
|
additions = additions.concat(lspOrders);
|
|
|
|
if (BackendUtils.supportsOnchainSends()) {
|
|
additions = additions.concat(transactions);
|
|
}
|
|
|
|
if (
|
|
BackendUtils.supportsCashuWallet() &&
|
|
this.settingsStore.settings?.ecash?.enableCashu
|
|
) {
|
|
// CDK transaction history (completed)
|
|
additions = additions.concat(this.cashuStore.cdkInvoices);
|
|
additions = additions.concat(this.cashuStore.payments || []);
|
|
|
|
// Include pending/unpaid Cashu invoices (from local storage)
|
|
const pendingCashuInvoices = this.cashuStore.invoices?.filter(
|
|
(invoice) => !invoice.isPaid
|
|
);
|
|
if (pendingCashuInvoices) {
|
|
additions = additions.concat(pendingCashuInvoices);
|
|
}
|
|
|
|
// Include sent Cashu tokens (received tokens are tracked
|
|
// via CDK transaction history in cdkInvoices above)
|
|
if (this.cashuStore.sentTokens) {
|
|
additions = additions.concat(this.cashuStore.sentTokens);
|
|
}
|
|
if (this.cashuStore.offlinePendingTokens) {
|
|
additions = additions.concat(
|
|
this.cashuStore.offlinePendingTokens
|
|
);
|
|
}
|
|
}
|
|
|
|
// push payments, txs, invoices to one array
|
|
activity.push.apply(activity, additions);
|
|
// sort activity by timestamp
|
|
const resolvedTuples = await Promise.all(
|
|
activity.map(async (item: any) => {
|
|
let timestamp = item.getTimestamp;
|
|
return [item, timestamp] as const;
|
|
})
|
|
);
|
|
|
|
const sortedActivity = resolvedTuples
|
|
.sort((a, b) => b[1] - a[1])
|
|
.map(([item]) => item);
|
|
|
|
return sortedActivity;
|
|
};
|
|
|
|
private getActivity = async () => {
|
|
this.activity = [];
|
|
await this.paymentsStore.getPayments();
|
|
if (BackendUtils.supportsOnchainSends())
|
|
await this.transactionsStore.getTransactions();
|
|
await this.invoicesStore.getInvoices();
|
|
|
|
await this.swapStore.fetchAndUpdateSwaps();
|
|
const sortedActivity = await this.getSortedActivity();
|
|
|
|
runInAction(() => {
|
|
this.activity = sortedActivity;
|
|
this.filteredActivity = this.activity;
|
|
});
|
|
};
|
|
|
|
public updateInvoices = async (locale: string | undefined) => {
|
|
await this.invoicesStore.getInvoices();
|
|
await runInAction(async () => {
|
|
this.activity = await this.getSortedActivity();
|
|
await this.setFilters(this.filters, locale);
|
|
});
|
|
};
|
|
|
|
public updateTransactions = async (locale: string | undefined) => {
|
|
if (BackendUtils.supportsOnchainSends())
|
|
await this.transactionsStore.getTransactions();
|
|
await runInAction(async () => {
|
|
this.activity = await this.getSortedActivity();
|
|
await this.setFilters(this.filters, locale);
|
|
});
|
|
};
|
|
|
|
public async getFilters() {
|
|
try {
|
|
const filters = await Storage.getItem(ACTIVITY_FILTERS_KEY);
|
|
if (filters) {
|
|
const parsedFilters = JSON.parse(filters, (key, value) =>
|
|
(key === 'startDate' || key === 'endDate') && value
|
|
? new Date(value)
|
|
: value
|
|
);
|
|
this.filters = {
|
|
...DEFAULT_FILTERS,
|
|
...parsedFilters,
|
|
swapFilter: {
|
|
submarine: {
|
|
...DEFAULT_FILTERS.swapFilter.submarine,
|
|
...(parsedFilters.swapFilter?.submarine || {})
|
|
},
|
|
reverse: {
|
|
...DEFAULT_FILTERS.swapFilter.reverse,
|
|
...(parsedFilters.swapFilter?.reverse || {})
|
|
}
|
|
},
|
|
lsps1State: {
|
|
...DEFAULT_FILTERS.lsps1State,
|
|
...(parsedFilters.lsps1State || {})
|
|
},
|
|
lsps7State: {
|
|
...DEFAULT_FILTERS.lsps7State,
|
|
...(parsedFilters.lsps7State || {})
|
|
}
|
|
};
|
|
} else {
|
|
console.log('No activity filters stored');
|
|
this.filters = DEFAULT_FILTERS;
|
|
}
|
|
} catch (error) {
|
|
console.log('Loading activity filters failed', error);
|
|
}
|
|
|
|
return this.filters;
|
|
}
|
|
|
|
@action
|
|
public setFilters = async (filters: Filter, locale?: string) => {
|
|
this.filters = {
|
|
...DEFAULT_FILTERS,
|
|
...filters,
|
|
swapFilter: {
|
|
submarine: {
|
|
...DEFAULT_FILTERS.swapFilter.submarine,
|
|
...filters.swapFilter?.submarine
|
|
},
|
|
reverse: {
|
|
...DEFAULT_FILTERS.swapFilter.reverse,
|
|
...filters.swapFilter?.reverse
|
|
}
|
|
},
|
|
lsps1State: {
|
|
...DEFAULT_FILTERS.lsps1State,
|
|
...filters.lsps1State
|
|
},
|
|
lsps7State: {
|
|
...DEFAULT_FILTERS.lsps7State,
|
|
...filters.lsps7State
|
|
}
|
|
};
|
|
this.filteredActivity = ActivityFilterUtils.filterActivities(
|
|
this.activity,
|
|
this.filters
|
|
);
|
|
this.filteredActivity.forEach((activity) => {
|
|
if (activity instanceof Invoice) {
|
|
activity.determineFormattedRemainingTimeUntilExpiry(locale);
|
|
}
|
|
});
|
|
Storage.setItem(ACTIVITY_FILTERS_KEY, this.filters);
|
|
};
|
|
|
|
public getActivityAndFilter = async (
|
|
locale: string | undefined,
|
|
filters: Filter = this.filters
|
|
) => {
|
|
// Check pending Cashu items (invoices and tokens) in background
|
|
this.cashuStore.checkPendingItems();
|
|
|
|
await this.getActivity();
|
|
await this.setFilters(filters, locale);
|
|
};
|
|
}
|