diff --git a/components/loading-summary-left.tsx b/components/loading-summary-left.tsx
new file mode 100644
index 0000000..4ba0f11
--- /dev/null
+++ b/components/loading-summary-left.tsx
@@ -0,0 +1,83 @@
+import { Card, CardBody, Progress } from "@nextui-org/react";
+
+import { LoadingItem } from "./loading-status-item";
+
+import { type PositionLoadingState } from "@/pages/wallet/[walletAddress]";
+import { MeteoraPosition } from "@/services/MeteoraPosition";
+import { MeteoraPositionTransaction } from "@/services/ParseMeteoraTransactions";
+
+export const LoadingSummaryLeft = (props: {
+ filteredPositions: MeteoraPosition[];
+ filteredTransactions: MeteoraPositionTransaction[];
+ loading: boolean;
+ positionLoadingState: PositionLoadingState;
+ usd: boolean;
+ updatedUsdValueCount: number;
+}) => {
+ return (
+
+
+
+
+
+
+
+
+
+ {props.positionLoadingState.apiDataLoaded ? (
+ <>>
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/components/loading-summary-no-usd.tsx b/components/loading-summary-no-usd.tsx
new file mode 100644
index 0000000..cfb820b
--- /dev/null
+++ b/components/loading-summary-no-usd.tsx
@@ -0,0 +1,48 @@
+import { Button, Card, CardBody } from "@nextui-org/react";
+
+import { type PositionLoadingState } from "@/pages/wallet/[walletAddress]";
+
+export const LoadingSummaryNoUsd = (props: {
+ positionLoadingState: PositionLoadingState;
+}) => {
+ const oldestTransaction =
+ props.positionLoadingState.transactions.length > 0
+ ? props.positionLoadingState.transactions.sort(
+ (a, b) => a.timestamp_ms - b.timestamp_ms,
+ )[0]
+ : null;
+
+ const oldestTransactionDate = oldestTransaction
+ ? new Date(oldestTransaction.timestamp_ms).toLocaleDateString() +
+ " " +
+ new Date(oldestTransaction.timestamp_ms).toLocaleTimeString()
+ : "";
+
+ return (
+
+
+
+ Oldest position transaction: {oldestTransactionDate}
+
+
+ If you know you have no DLMM transactions prior to the date above or
+ you do not want to analyze on older transactions/positions, you can
+ safely stop loading more transactions.
+
+ {
+ if (props.positionLoadingState.cancel) {
+ props.positionLoadingState.cancel();
+ }
+ }}
+ >
+ Stop Loading Wallet Transactions
+
+
+
+ );
+};
diff --git a/components/loading-summary-usd.tsx b/components/loading-summary-usd.tsx
new file mode 100644
index 0000000..795be6d
--- /dev/null
+++ b/components/loading-summary-usd.tsx
@@ -0,0 +1,84 @@
+import { Card, CardBody } from "@nextui-org/react";
+
+import { LoadingItem } from "./loading-status-item";
+
+import { type PositionLoadingState } from "@/pages/wallet/[walletAddress]";
+
+export const LoadingSummaryUsd = (props: {
+ positionLoadingState: PositionLoadingState;
+ estimatedPointsFromFeesAndRewards: number;
+ usdFeesAndRewards: number;
+ usdDivergenceLoss: number;
+ usdProfit: number;
+ positionsWithErrorsCount: number;
+}) => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/components/loading-summary.tsx b/components/loading-summary.tsx
index 73138be..03b0e87 100644
--- a/components/loading-summary.tsx
+++ b/components/loading-summary.tsx
@@ -1,6 +1,6 @@
-import { Card, CardBody, Progress } from "@nextui-org/react";
-
-import { LoadingItem } from "./loading-status-item";
+import { LoadingSummaryLeft } from "./loading-summary-left";
+import { LoadingSummaryNoUsd } from "./loading-summary-no-usd";
+import { LoadingSummaryUsd } from "./loading-summary-usd";
import { type PositionLoadingState } from "@/pages/wallet/[walletAddress]";
import { MeteoraPosition } from "@/services/MeteoraPosition";
@@ -59,135 +59,23 @@ export const LoadingSummary = (props: {
return (
-
-
-
-
-
-
-
-
-
- {props.positionLoadingState.apiDataLoaded ? (
- <>>
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
);
};
diff --git a/pages/wallet/[walletAddress].tsx b/pages/wallet/[walletAddress].tsx
index a453599..fd89aef 100644
--- a/pages/wallet/[walletAddress].tsx
+++ b/pages/wallet/[walletAddress].tsx
@@ -35,6 +35,7 @@ export interface PositionLoadingState {
updatedUsdPercent: number;
usdUpdateStartTime: number;
estimatedCompletionString: string;
+ cancel?: () => any;
}
export default function IndexPage() {
@@ -100,7 +101,7 @@ export default function IndexPage() {
return { ...currentState, tokenMap };
});
- new MeteoraPositionStream(
+ const meteoraPositionStream = new MeteoraPositionStream(
appState.connection,
walletAddress,
undefined,
@@ -156,49 +157,56 @@ export default function IndexPage() {
}
})
.on("end", () => {
- setPositionLoadingState((currentState) => {
- currentState.allPositionsFound = true;
- currentState.rpcDataLoaded = true;
- loadApiData(currentState.positions);
-
- return updateElapsedTime(currentState);
- });
+ loadApiData();
});
+
+ setPositionLoadingState((currentState) => {
+ return {
+ ...currentState,
+ cancel: () => {
+ meteoraPositionStream!.cancel();
+ loadApiData();
+ },
+ };
+ });
}
}
- async function loadApiData(positions: MeteoraPosition[]) {
+ async function loadApiData() {
setPositionLoadingState((currentState) => {
+ currentState.allSignaturesFound = true;
+ currentState.allPositionsFound = true;
+ currentState.rpcDataLoaded = true;
currentState.usdUpdateStartTime = new Date().getTime();
currentState.updatingUsdValues = true;
- return updateElapsedTime(currentState);
- });
+ new UsdMeteoraPositionStream(currentState.positions)
+ .on("data", (data) => {
+ setPositionLoadingState((currentState) => {
+ const oldPosition = currentState.positions.find(
+ (position) => position.position == data.updatedPosition.position,
+ )!;
+ const index = currentState.positions.indexOf(oldPosition);
- new UsdMeteoraPositionStream(positions)
- .on("data", (data) => {
- setPositionLoadingState((currentState) => {
- const oldPosition = currentState.positions.find(
- (position) => position.position == data.updatedPosition.position,
- )!;
- const index = currentState.positions.indexOf(oldPosition);
+ currentState.positions[index] = data.updatedPosition;
+ currentState.updatedUsdValueCount = data.updatedPositionCount;
- currentState.positions[index] = data.updatedPosition;
- currentState.updatedUsdValueCount = data.updatedPositionCount;
+ currentState.updatedUsdPercent =
+ (100 * data.updatedPositionCount) / currentState.positions.length;
- currentState.updatedUsdPercent =
- (100 * data.updatedPositionCount) / positions.length;
+ return updateElapsedTime(currentState);
+ });
+ })
+ .on("end", () => {
+ setPositionLoadingState((currentState) => {
+ currentState.apiDataLoaded = true;
- return updateElapsedTime(currentState);
+ return updateElapsedTime(currentState);
+ });
});
- })
- .on("end", () => {
- setPositionLoadingState((currentState) => {
- currentState.apiDataLoaded = true;
- return updateElapsedTime(currentState);
- });
- });
+ return updateElapsedTime(currentState);
+ });
}
useEffect(() => {
diff --git a/services/MeteoraPositionStream.ts b/services/MeteoraPositionStream.ts
index 1742e68..d1b156b 100644
--- a/services/MeteoraPositionStream.ts
+++ b/services/MeteoraPositionStream.ts
@@ -53,6 +53,7 @@ interface MeteoraPositionStreamEvents {
export class MeteoraPositionStream extends Transform {
private _pairs: Map = new Map();
private _tokenList: Map = new Map();
+ private _transactionStream!: ParsedTransactionStream;
private _receivedAllTransactions = false;
private _transactionsReceivedCount = 0;
private _transactionsProcessedCount = 0;
@@ -62,6 +63,8 @@ export class MeteoraPositionStream extends Transform {
MeteoraPositionTransaction
>;
private _done = false;
+ private _cancelling = false;
+ private _cancelled = false;
constructor(
connection: Connection,
@@ -74,6 +77,11 @@ export class MeteoraPositionStream extends Transform {
this._init(connection, walletAddress, before, until, minDate);
}
+ cancel() {
+ this._transactionStream.cancel();
+ this._cancelling = true;
+ }
+
private async _init(
connection: Connection,
walletAddress: string,
@@ -94,7 +102,7 @@ export class MeteoraPositionStream extends Transform {
),
);
- new ParsedTransactionStream(
+ this._transactionStream = new ParsedTransactionStream(
connection,
walletAddress,
before,
@@ -114,28 +122,35 @@ export class MeteoraPositionStream extends Transform {
}
private async _processBatch(connection: Connection) {
- const { inputCount, output } = await this._processor.next();
+ if (!this._cancelling && !this._cancelled) {
+ const { inputCount, output } = await this._processor.next();
- if (inputCount > 0) {
- if (output.length > 0) {
- this._transactions = this._transactions.concat(output);
- this.push({
- type: "transactionCount",
- meteoraTransactionCount: this._transactions.length,
- });
+ if (inputCount > 0) {
+ if (output.length > 0) {
+ this._transactions = this._transactions.concat(output);
+ if (!this._cancelling && !this._cancelled) {
+ this.push({
+ type: "transactionCount",
+ meteoraTransactionCount: this._transactions.length,
+ });
+ }
- await Promise.all(
- output.map(async (positionTransaction) => {
- if (positionTransaction.open) {
- await this._createPosition(connection, positionTransaction);
- }
- }),
- );
- }
+ await Promise.all(
+ output.map(async (positionTransaction) => {
+ if (positionTransaction.open) {
+ await this._createPosition(connection, positionTransaction);
+ }
+ }),
+ );
+ }
- this._transactionsProcessedCount += inputCount;
+ this._transactionsProcessedCount += inputCount;
+ }
+ this._finish();
+ } else if (!this._cancelled) {
+ this._cancelled = true;
+ this.push(null);
}
- this._finish();
}
private async _parseTransactions(
@@ -151,7 +166,9 @@ export class MeteoraPositionStream extends Transform {
this._transactionsReceivedCount = data.signatureCount;
break;
}
- this.push(data);
+ if (!this._cancelling && !this._cancelled) {
+ this.push(data);
+ }
return;
}
@@ -172,11 +189,13 @@ export class MeteoraPositionStream extends Transform {
const newPosition = new MeteoraPosition(newPositionTransactions);
if (newPosition.isClosed) {
- this.push({
- type: "positionAndTransactions",
- transactions: newPositionTransactions,
- position: newPosition,
- });
+ if (!this._cancelling && !this._cancelled) {
+ this.push({
+ type: "positionAndTransactions",
+ transactions: newPositionTransactions,
+ position: newPosition,
+ });
+ }
} else {
await this._updateOpenPosition(
connection,
@@ -192,11 +211,13 @@ export class MeteoraPositionStream extends Transform {
position: MeteoraPosition,
) {
await updateOpenPosition(connection, position);
- this.push({
- type: "positionAndTransactions",
- transactions,
- position,
- });
+ if (!this._cancelling && !this._cancelled) {
+ this.push({
+ type: "positionAndTransactions",
+ transactions,
+ position,
+ });
+ }
}
private async _finish() {
@@ -206,7 +227,9 @@ export class MeteoraPositionStream extends Transform {
!this._done
) {
this._done = true;
- this.push(null);
+ if (!this._cancelling && !this._cancelled) {
+ this.push(null);
+ }
}
}
diff --git a/services/ParsedTransactionStream.ts b/services/ParsedTransactionStream.ts
index d48e30e..e7011b1 100644
--- a/services/ParsedTransactionStream.ts
+++ b/services/ParsedTransactionStream.ts
@@ -44,6 +44,9 @@ export class ParsedTransactionStream extends Transform {
string,
ParsedTransactionWithMeta | null
>;
+ private _signatureStream: SignatureStream;
+ private _cancelling = false;
+ private _cancelled = false;
constructor(
connection: Connection,
@@ -60,13 +63,21 @@ export class ParsedTransactionStream extends Transform {
commitment: "confirmed",
}),
);
- new SignatureStream(connection, walletAddress, before, until, minDate)
+ this._signatureStream = new SignatureStream(
+ connection,
+ walletAddress,
+ before,
+ until,
+ minDate,
+ )
.on("data", (signatures: ConfirmedSignatureInfo[]) =>
this._processSignatures(signatures),
)
.on("end", async () => {
this._allSignaturesFound = { type: "allSignaturesFound" };
- this.push(this._allSignaturesFound);
+ if (!this._cancelling && !this._cancelled) {
+ this.push(this._allSignaturesFound);
+ }
while (!this._processor.isComplete) {
await this._processBatch();
@@ -76,30 +87,44 @@ export class ParsedTransactionStream extends Transform {
.on("error", (error) => this.emit("error", error));
}
+ cancel() {
+ this._signatureStream.cancel();
+ this._cancelling = true;
+ }
+
private async _processBatch() {
- const { inputCount, output } = await this._processor.next();
+ if (!this._cancelling && !this._cancelled) {
+ const { inputCount, output } = await this._processor.next();
- const parsedTransactionsWithMeta = output.filter(
- (parsedTransaction) => parsedTransaction != null,
- ) as ParsedTransactionWithMeta[];
+ const parsedTransactionsWithMeta = output.filter(
+ (parsedTransaction) => parsedTransaction != null,
+ ) as ParsedTransactionWithMeta[];
- this._processedCount += inputCount;
+ this._processedCount += inputCount;
- if (parsedTransactionsWithMeta.length > 0) {
- this.push({
- type: "parsedTransaction",
- parsedTransactionsWithMeta,
- });
+ if (parsedTransactionsWithMeta.length > 0) {
+ if (!this._cancelling && !this._cancelled) {
+ this.push({
+ type: "parsedTransaction",
+ parsedTransactionsWithMeta,
+ });
+ }
+ }
+ this._finish();
+ } else if (!this._cancelled) {
+ this._cancelled = true;
+ this.push(null);
}
- this._finish();
}
private async _processSignatures(signatures: ConfirmedSignatureInfo[]) {
this._signatureCount += signatures.length;
- this.push({
- type: "signatureCount",
- signatureCount: this._signatureCount,
- });
+ if (!this._cancelling && !this._cancelled) {
+ this.push({
+ type: "signatureCount",
+ signatureCount: this._signatureCount,
+ });
+ }
const signatureStrings = signatures.map((signature) => signature.signature);
this._processor.addBatch(signatureStrings);
@@ -111,7 +136,9 @@ export class ParsedTransactionStream extends Transform {
this._allSignaturesFound &&
this._signatureCount == this._processedCount
) {
- this.push(null);
+ if (!this._cancelling && !this._cancelled) {
+ this.push(null);
+ }
}
}
diff --git a/services/SignatureStream.ts b/services/SignatureStream.ts
index 30e2398..0b547ea 100644
--- a/services/SignatureStream.ts
+++ b/services/SignatureStream.ts
@@ -20,6 +20,7 @@ export class SignatureStream extends Transform {
private _before?: string;
private _until?: string;
private _minDate?: Date;
+ private _cancel = false;
constructor(
connection: Connection,
@@ -37,6 +38,10 @@ export class SignatureStream extends Transform {
this._streamSignatures().catch((error) => this.emit("error", error));
}
+ cancel() {
+ this._cancel = true;
+ }
+
private async _streamSignatures() {
let newSignatures: ConfirmedSignatureInfo[] = [];
let lastDate = new Date();
@@ -65,6 +70,7 @@ export class SignatureStream extends Transform {
);
}
} while (
+ !this._cancel &&
newSignatures.length > 0 &&
(!this._minDate || (this._minDate && lastDate > this._minDate))
);