@elizaos/autonomous 2.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +270 -0
- package/src/actions/emote.ts +101 -0
- package/src/actions/restart.ts +101 -0
- package/src/actions/send-message.ts +168 -0
- package/src/actions/stream-control.ts +439 -0
- package/src/actions/switch-stream-source.ts +126 -0
- package/src/actions/terminal.ts +186 -0
- package/src/api/agent-admin-routes.ts +178 -0
- package/src/api/agent-lifecycle-routes.ts +129 -0
- package/src/api/agent-model.ts +143 -0
- package/src/api/agent-transfer-routes.ts +211 -0
- package/src/api/apps-routes.ts +210 -0
- package/src/api/auth-routes.ts +90 -0
- package/src/api/bsc-trade.ts +736 -0
- package/src/api/bug-report-routes.ts +161 -0
- package/src/api/character-routes.ts +421 -0
- package/src/api/cloud-billing-routes.ts +598 -0
- package/src/api/cloud-compat-routes.ts +192 -0
- package/src/api/cloud-routes.ts +529 -0
- package/src/api/cloud-status-routes.ts +234 -0
- package/src/api/compat-utils.ts +154 -0
- package/src/api/connector-health.ts +135 -0
- package/src/api/coordinator-wiring.ts +179 -0
- package/src/api/credit-detection.ts +47 -0
- package/src/api/database.ts +1357 -0
- package/src/api/diagnostics-routes.ts +389 -0
- package/src/api/drop-service.ts +205 -0
- package/src/api/early-logs.ts +111 -0
- package/src/api/http-helpers.ts +252 -0
- package/src/api/index.ts +85 -0
- package/src/api/knowledge-routes.ts +1189 -0
- package/src/api/knowledge-service-loader.ts +92 -0
- package/src/api/memory-bounds.ts +121 -0
- package/src/api/memory-routes.ts +349 -0
- package/src/api/merkle-tree.ts +239 -0
- package/src/api/models-routes.ts +72 -0
- package/src/api/nfa-routes.ts +169 -0
- package/src/api/nft-verify.ts +188 -0
- package/src/api/og-tracker.ts +72 -0
- package/src/api/parse-action-block.ts +145 -0
- package/src/api/permissions-routes.ts +222 -0
- package/src/api/plugin-validation.ts +355 -0
- package/src/api/provider-switch-config.ts +455 -0
- package/src/api/registry-routes.ts +165 -0
- package/src/api/registry-service.ts +292 -0
- package/src/api/route-helpers.ts +21 -0
- package/src/api/sandbox-routes.ts +1480 -0
- package/src/api/server.ts +17674 -0
- package/src/api/signal-routes.ts +265 -0
- package/src/api/stream-persistence.ts +297 -0
- package/src/api/stream-route-state.ts +48 -0
- package/src/api/stream-routes.ts +1046 -0
- package/src/api/stream-voice-routes.ts +208 -0
- package/src/api/streaming-text.ts +129 -0
- package/src/api/streaming-types.ts +23 -0
- package/src/api/subscription-routes.ts +283 -0
- package/src/api/terminal-run-limits.ts +31 -0
- package/src/api/training-backend-check.ts +40 -0
- package/src/api/training-routes.ts +314 -0
- package/src/api/training-service-like.ts +46 -0
- package/src/api/trajectory-routes.ts +714 -0
- package/src/api/trigger-routes.ts +438 -0
- package/src/api/twitter-verify.ts +226 -0
- package/src/api/tx-service.ts +193 -0
- package/src/api/wallet-dex-prices.ts +206 -0
- package/src/api/wallet-evm-balance.ts +989 -0
- package/src/api/wallet-routes.ts +505 -0
- package/src/api/wallet-rpc.ts +523 -0
- package/src/api/wallet-trading-profile.ts +694 -0
- package/src/api/wallet.ts +745 -0
- package/src/api/whatsapp-routes.ts +282 -0
- package/src/api/zip-utils.ts +130 -0
- package/src/auth/anthropic.ts +63 -0
- package/src/auth/apply-stealth.ts +38 -0
- package/src/auth/claude-code-stealth.ts +141 -0
- package/src/auth/credentials.ts +226 -0
- package/src/auth/index.ts +18 -0
- package/src/auth/openai-codex.ts +94 -0
- package/src/auth/types.ts +24 -0
- package/src/awareness/registry.ts +220 -0
- package/src/bin.ts +10 -0
- package/src/cli/index.ts +36 -0
- package/src/cli/parse-duration.ts +43 -0
- package/src/cloud/auth.test.ts +370 -0
- package/src/cloud/auth.ts +176 -0
- package/src/cloud/backup.test.ts +150 -0
- package/src/cloud/backup.ts +50 -0
- package/src/cloud/base-url.ts +45 -0
- package/src/cloud/bridge-client.test.ts +481 -0
- package/src/cloud/bridge-client.ts +307 -0
- package/src/cloud/cloud-manager.test.ts +223 -0
- package/src/cloud/cloud-manager.ts +151 -0
- package/src/cloud/cloud-proxy.test.ts +122 -0
- package/src/cloud/cloud-proxy.ts +52 -0
- package/src/cloud/index.ts +23 -0
- package/src/cloud/reconnect.test.ts +178 -0
- package/src/cloud/reconnect.ts +108 -0
- package/src/cloud/validate-url.test.ts +147 -0
- package/src/cloud/validate-url.ts +176 -0
- package/src/config/character-schema.ts +44 -0
- package/src/config/config.ts +149 -0
- package/src/config/env-vars.ts +86 -0
- package/src/config/includes.ts +196 -0
- package/src/config/index.ts +15 -0
- package/src/config/object-utils.ts +10 -0
- package/src/config/paths.ts +92 -0
- package/src/config/plugin-auto-enable.ts +520 -0
- package/src/config/schema.ts +1342 -0
- package/src/config/telegram-custom-commands.ts +99 -0
- package/src/config/types.agent-defaults.ts +342 -0
- package/src/config/types.agents.ts +112 -0
- package/src/config/types.gateway.ts +243 -0
- package/src/config/types.hooks.ts +124 -0
- package/src/config/types.messages.ts +201 -0
- package/src/config/types.milady.ts +791 -0
- package/src/config/types.tools.ts +416 -0
- package/src/config/types.ts +7 -0
- package/src/config/zod-schema.agent-runtime.ts +777 -0
- package/src/config/zod-schema.core.ts +778 -0
- package/src/config/zod-schema.hooks.ts +139 -0
- package/src/config/zod-schema.providers-core.ts +1126 -0
- package/src/config/zod-schema.session.ts +98 -0
- package/src/config/zod-schema.ts +865 -0
- package/src/contracts/apps.ts +46 -0
- package/src/contracts/awareness.ts +56 -0
- package/src/contracts/config.ts +172 -0
- package/src/contracts/drop.ts +21 -0
- package/src/contracts/index.ts +8 -0
- package/src/contracts/onboarding.ts +592 -0
- package/src/contracts/permissions.ts +52 -0
- package/src/contracts/verification.ts +9 -0
- package/src/contracts/wallet.ts +503 -0
- package/src/diagnostics/integration-observability.ts +132 -0
- package/src/emotes/catalog.ts +655 -0
- package/src/external-modules.d.ts +7 -0
- package/src/hooks/discovery.test.ts +357 -0
- package/src/hooks/discovery.ts +231 -0
- package/src/hooks/eligibility.ts +146 -0
- package/src/hooks/hooks.test.ts +320 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/loader.test.ts +418 -0
- package/src/hooks/loader.ts +256 -0
- package/src/hooks/registry.test.ts +168 -0
- package/src/hooks/registry.ts +74 -0
- package/src/hooks/types.ts +121 -0
- package/src/index.ts +19 -0
- package/src/onboarding-presets.ts +828 -0
- package/src/plugins/custom-rtmp/index.ts +40 -0
- package/src/providers/admin-trust.ts +76 -0
- package/src/providers/session-bridge.ts +143 -0
- package/src/providers/session-utils.ts +42 -0
- package/src/providers/simple-mode.ts +113 -0
- package/src/providers/ui-catalog.ts +135 -0
- package/src/providers/workspace-provider.ts +213 -0
- package/src/providers/workspace.ts +497 -0
- package/src/runtime/agent-event-service.ts +57 -0
- package/src/runtime/cloud-onboarding.test.ts +489 -0
- package/src/runtime/cloud-onboarding.ts +408 -0
- package/src/runtime/core-plugins.ts +53 -0
- package/src/runtime/custom-actions.ts +605 -0
- package/src/runtime/eliza.ts +4941 -0
- package/src/runtime/embedding-presets.ts +73 -0
- package/src/runtime/index.ts +8 -0
- package/src/runtime/milady-plugin.ts +180 -0
- package/src/runtime/onboarding-names.ts +76 -0
- package/src/runtime/release-plugin-policy.ts +119 -0
- package/src/runtime/restart.ts +59 -0
- package/src/runtime/trajectory-persistence.ts +2584 -0
- package/src/runtime/version.ts +6 -0
- package/src/security/audit-log.ts +222 -0
- package/src/security/network-policy.ts +91 -0
- package/src/server/index.ts +6 -0
- package/src/services/agent-export.ts +976 -0
- package/src/services/app-manager.ts +755 -0
- package/src/services/browser-capture.ts +215 -0
- package/src/services/coding-agent-context.ts +355 -0
- package/src/services/fallback-training-service.ts +196 -0
- package/src/services/index.ts +17 -0
- package/src/services/mcp-marketplace.ts +327 -0
- package/src/services/plugin-manager-types.ts +185 -0
- package/src/services/privy-wallets.ts +352 -0
- package/src/services/registry-client-app-meta.ts +201 -0
- package/src/services/registry-client-endpoints.ts +253 -0
- package/src/services/registry-client-local.ts +485 -0
- package/src/services/registry-client-network.ts +173 -0
- package/src/services/registry-client-queries.ts +176 -0
- package/src/services/registry-client-types.ts +104 -0
- package/src/services/registry-client.ts +366 -0
- package/src/services/remote-signing-service.ts +261 -0
- package/src/services/sandbox-engine.ts +753 -0
- package/src/services/sandbox-manager.ts +503 -0
- package/src/services/self-updater.ts +213 -0
- package/src/services/signal-pairing.ts +189 -0
- package/src/services/signing-policy.ts +230 -0
- package/src/services/skill-catalog-client.ts +195 -0
- package/src/services/skill-marketplace.ts +909 -0
- package/src/services/stream-manager.ts +707 -0
- package/src/services/tts-stream-bridge.ts +465 -0
- package/src/services/update-checker.ts +163 -0
- package/src/services/version-compat.ts +367 -0
- package/src/services/whatsapp-pairing.ts +279 -0
- package/src/shared/ui-catalog-prompt.ts +1158 -0
- package/src/test-support/process-helpers.ts +35 -0
- package/src/test-support/route-test-helpers.ts +113 -0
- package/src/test-support/test-helpers.ts +304 -0
- package/src/testing/index.ts +3 -0
- package/src/triggers/action.ts +342 -0
- package/src/triggers/runtime.ts +432 -0
- package/src/triggers/scheduling.ts +472 -0
- package/src/triggers/types.ts +133 -0
- package/src/types/app-hyperscape-routes-shim.d.ts +29 -0
- package/src/types/external-modules.d.ts +7 -0
- package/src/utils/exec-safety.ts +23 -0
- package/src/utils/number-parsing.ts +112 -0
- package/src/utils/spoken-text.ts +65 -0
- package/src/version-resolver.ts +60 -0
- package/test/api/agent-admin-routes.test.ts +160 -0
- package/test/api/agent-lifecycle-routes.test.ts +164 -0
- package/test/api/agent-transfer-routes.test.ts +136 -0
- package/test/api/apps-routes.test.ts +140 -0
- package/test/api/auth-routes.test.ts +160 -0
- package/test/api/bug-report-routes.test.ts +88 -0
- package/test/api/knowledge-routes.test.ts +73 -0
- package/test/api/lifecycle.test.ts +342 -0
- package/test/api/memory-routes.test.ts +74 -0
- package/test/api/models-routes.test.ts +112 -0
- package/test/api/nfa-routes.test.ts +78 -0
- package/test/api/permissions-routes.test.ts +185 -0
- package/test/api/registry-routes.test.ts +157 -0
- package/test/api/signal-routes.test.ts +113 -0
- package/test/api/subscription-routes.test.ts +90 -0
- package/test/api/trigger-routes.test.ts +87 -0
- package/test/api/wallet-routes.observability.test.ts +191 -0
- package/test/api/wallet-routes.test.ts +502 -0
- package/test/diagnostics/integration-observability.test.ts +135 -0
- package/test/security/audit-log.test.ts +229 -0
- package/test/security/network-policy.test.ts +143 -0
- package/test/services/version-compat.test.ts +127 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveStateDir } from "../config/paths.js";
|
|
4
|
+
import type {
|
|
5
|
+
BscTradeSide,
|
|
6
|
+
BscTradeTxStatus,
|
|
7
|
+
WalletTradeLedgerEntry,
|
|
8
|
+
WalletTradeLedgerQuoteLeg,
|
|
9
|
+
WalletTradeSource,
|
|
10
|
+
WalletTradingProfileRecentSwap,
|
|
11
|
+
WalletTradingProfileResponse,
|
|
12
|
+
WalletTradingProfileSourceFilter,
|
|
13
|
+
WalletTradingProfileTokenBreakdown,
|
|
14
|
+
WalletTradingProfileWindow,
|
|
15
|
+
} from "../contracts/wallet.js";
|
|
16
|
+
|
|
17
|
+
const WALLET_PROFILE_LEDGER_VERSION = 1;
|
|
18
|
+
const MAX_WALLET_PROFILE_LEDGER_ENTRIES = 2000;
|
|
19
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
20
|
+
const FLOAT_EPSILON = 1e-12;
|
|
21
|
+
|
|
22
|
+
interface WalletTradeLedgerStore {
|
|
23
|
+
version: 1;
|
|
24
|
+
updatedAt: string;
|
|
25
|
+
entries: WalletTradeLedgerEntry[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WalletTradeLedgerRecordInput {
|
|
29
|
+
hash: string;
|
|
30
|
+
source: WalletTradeSource;
|
|
31
|
+
side: BscTradeSide;
|
|
32
|
+
tokenAddress: string;
|
|
33
|
+
slippageBps: number;
|
|
34
|
+
route: string[];
|
|
35
|
+
quoteIn: WalletTradeLedgerQuoteLeg;
|
|
36
|
+
quoteOut: WalletTradeLedgerQuoteLeg;
|
|
37
|
+
status: BscTradeTxStatus;
|
|
38
|
+
confirmations: number;
|
|
39
|
+
nonce: number | null;
|
|
40
|
+
blockNumber: number | null;
|
|
41
|
+
gasUsed: string | null;
|
|
42
|
+
effectiveGasPriceWei: string | null;
|
|
43
|
+
reason?: string;
|
|
44
|
+
explorerUrl: string;
|
|
45
|
+
createdAt?: string;
|
|
46
|
+
updatedAt?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WalletTradeLedgerStatusPatch {
|
|
50
|
+
status: BscTradeTxStatus;
|
|
51
|
+
confirmations: number;
|
|
52
|
+
nonce: number | null;
|
|
53
|
+
blockNumber: number | null;
|
|
54
|
+
gasUsed: string | null;
|
|
55
|
+
effectiveGasPriceWei: string | null;
|
|
56
|
+
reason?: string;
|
|
57
|
+
explorerUrl?: string;
|
|
58
|
+
updatedAt?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WalletTradingProfileOptions {
|
|
62
|
+
window?: WalletTradingProfileWindow;
|
|
63
|
+
source?: WalletTradingProfileSourceFilter;
|
|
64
|
+
stateDir?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface TokenLot {
|
|
68
|
+
qty: number;
|
|
69
|
+
costBnb: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface TokenAccumulator {
|
|
73
|
+
tokenAddress: string;
|
|
74
|
+
symbol: string;
|
|
75
|
+
buyCount: number;
|
|
76
|
+
sellCount: number;
|
|
77
|
+
realizedPnlBnb: number;
|
|
78
|
+
volumeBnb: number;
|
|
79
|
+
winningTrades: number;
|
|
80
|
+
evaluatedTrades: number;
|
|
81
|
+
lots: TokenLot[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface SeriesAccumulator {
|
|
85
|
+
day: string;
|
|
86
|
+
realizedPnlBnb: number;
|
|
87
|
+
volumeBnb: number;
|
|
88
|
+
swaps: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const TRADE_STATUS_SET = new Set<BscTradeTxStatus>([
|
|
92
|
+
"pending",
|
|
93
|
+
"success",
|
|
94
|
+
"reverted",
|
|
95
|
+
"not_found",
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
const TRADE_SIDE_SET = new Set<BscTradeSide>(["buy", "sell"]);
|
|
99
|
+
|
|
100
|
+
function nowIso(): string {
|
|
101
|
+
return new Date().toISOString();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toFiniteNumber(value: unknown): number {
|
|
105
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : 0;
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
const parsed = Number.parseFloat(value.trim());
|
|
108
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
109
|
+
}
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function toBnbString(value: number): string {
|
|
114
|
+
if (!Number.isFinite(value) || Math.abs(value) < FLOAT_EPSILON) return "0";
|
|
115
|
+
const fixed = value.toFixed(8).replace(/\.?0+$/, "");
|
|
116
|
+
return fixed === "-0" ? "0" : fixed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function toRatePercent(numerator: number, denominator: number): number | null {
|
|
120
|
+
if (!Number.isFinite(denominator) || denominator <= 0) return null;
|
|
121
|
+
if (!Number.isFinite(numerator) || numerator <= 0) return 0;
|
|
122
|
+
return (numerator / denominator) * 100;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeStatus(value: unknown): BscTradeTxStatus {
|
|
126
|
+
if (typeof value !== "string") return "pending";
|
|
127
|
+
const normalized = value.trim().toLowerCase() as BscTradeTxStatus;
|
|
128
|
+
if (!TRADE_STATUS_SET.has(normalized)) return "pending";
|
|
129
|
+
return normalized;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeSide(value: unknown): BscTradeSide {
|
|
133
|
+
if (typeof value !== "string") return "buy";
|
|
134
|
+
const normalized = value.trim().toLowerCase() as BscTradeSide;
|
|
135
|
+
if (!TRADE_SIDE_SET.has(normalized)) return "buy";
|
|
136
|
+
return normalized;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeSource(value: unknown): WalletTradeSource {
|
|
140
|
+
if (typeof value !== "string") return "manual";
|
|
141
|
+
return value.trim().toLowerCase() === "agent" ? "agent" : "manual";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizeIsoDate(value: unknown): string {
|
|
145
|
+
if (typeof value !== "string") return nowIso();
|
|
146
|
+
const trimmed = value.trim();
|
|
147
|
+
if (!trimmed) return nowIso();
|
|
148
|
+
const timestamp = Date.parse(trimmed);
|
|
149
|
+
if (!Number.isFinite(timestamp)) return nowIso();
|
|
150
|
+
return new Date(timestamp).toISOString();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function normalizeNullableInteger(value: unknown): number | null {
|
|
154
|
+
if (value === null || value === undefined) return null;
|
|
155
|
+
if (typeof value === "string" && !value.trim()) return null;
|
|
156
|
+
const parsed = toFiniteNumber(value);
|
|
157
|
+
if (!Number.isFinite(parsed)) return null;
|
|
158
|
+
return Math.floor(parsed);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function normalizeAddress(value: unknown): string {
|
|
162
|
+
if (typeof value !== "string") return "";
|
|
163
|
+
return value.trim().toLowerCase();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeLeg(value: unknown): WalletTradeLedgerQuoteLeg | null {
|
|
167
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
168
|
+
const record = value as Record<string, unknown>;
|
|
169
|
+
const symbol = typeof record.symbol === "string" ? record.symbol.trim() : "";
|
|
170
|
+
const amount =
|
|
171
|
+
typeof record.amount === "string"
|
|
172
|
+
? record.amount.trim()
|
|
173
|
+
: toFiniteNumber(record.amount).toString();
|
|
174
|
+
const amountWei =
|
|
175
|
+
typeof record.amountWei === "string" ? record.amountWei.trim() : "";
|
|
176
|
+
if (!symbol || !amount || !amountWei) return null;
|
|
177
|
+
return { symbol, amount, amountWei };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function normalizeLedgerEntry(value: unknown): WalletTradeLedgerEntry | null {
|
|
181
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
182
|
+
const record = value as Record<string, unknown>;
|
|
183
|
+
const hash = typeof record.hash === "string" ? record.hash.trim() : "";
|
|
184
|
+
const tokenAddress = normalizeAddress(record.tokenAddress);
|
|
185
|
+
const quoteIn = normalizeLeg(record.quoteIn);
|
|
186
|
+
const quoteOut = normalizeLeg(record.quoteOut);
|
|
187
|
+
if (!hash || !tokenAddress || !quoteIn || !quoteOut) return null;
|
|
188
|
+
|
|
189
|
+
const routeRaw = Array.isArray(record.route) ? record.route : [];
|
|
190
|
+
const route = routeRaw
|
|
191
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
192
|
+
.filter((item) => item.length > 0);
|
|
193
|
+
|
|
194
|
+
const explorerUrl =
|
|
195
|
+
typeof record.explorerUrl === "string"
|
|
196
|
+
? record.explorerUrl.trim()
|
|
197
|
+
: `https://bscscan.com/tx/${hash}`;
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
hash,
|
|
201
|
+
createdAt: normalizeIsoDate(record.createdAt),
|
|
202
|
+
updatedAt: normalizeIsoDate(record.updatedAt),
|
|
203
|
+
source: normalizeSource(record.source),
|
|
204
|
+
side: normalizeSide(record.side),
|
|
205
|
+
tokenAddress,
|
|
206
|
+
slippageBps: Math.max(0, Math.round(toFiniteNumber(record.slippageBps))),
|
|
207
|
+
route,
|
|
208
|
+
quoteIn,
|
|
209
|
+
quoteOut,
|
|
210
|
+
status: normalizeStatus(record.status),
|
|
211
|
+
confirmations: Math.max(
|
|
212
|
+
0,
|
|
213
|
+
Math.floor(toFiniteNumber(record.confirmations)),
|
|
214
|
+
),
|
|
215
|
+
nonce: (() => {
|
|
216
|
+
const nonce = normalizeNullableInteger(record.nonce);
|
|
217
|
+
return nonce === null ? null : Math.max(0, nonce);
|
|
218
|
+
})(),
|
|
219
|
+
blockNumber: (() => {
|
|
220
|
+
const blockNumber = normalizeNullableInteger(record.blockNumber);
|
|
221
|
+
return blockNumber === null ? null : Math.max(0, blockNumber);
|
|
222
|
+
})(),
|
|
223
|
+
gasUsed:
|
|
224
|
+
typeof record.gasUsed === "string" && record.gasUsed.trim()
|
|
225
|
+
? record.gasUsed.trim()
|
|
226
|
+
: null,
|
|
227
|
+
effectiveGasPriceWei:
|
|
228
|
+
typeof record.effectiveGasPriceWei === "string" &&
|
|
229
|
+
record.effectiveGasPriceWei.trim()
|
|
230
|
+
? record.effectiveGasPriceWei.trim()
|
|
231
|
+
: null,
|
|
232
|
+
...(typeof record.reason === "string" && record.reason.trim()
|
|
233
|
+
? { reason: record.reason.trim() }
|
|
234
|
+
: {}),
|
|
235
|
+
explorerUrl,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function sortAndTrimEntries(
|
|
240
|
+
entries: WalletTradeLedgerEntry[],
|
|
241
|
+
): WalletTradeLedgerEntry[] {
|
|
242
|
+
const sorted = [...entries].sort((a, b) => {
|
|
243
|
+
const aMs = Date.parse(a.createdAt);
|
|
244
|
+
const bMs = Date.parse(b.createdAt);
|
|
245
|
+
if (Number.isFinite(aMs) && Number.isFinite(bMs) && aMs !== bMs) {
|
|
246
|
+
return aMs - bMs;
|
|
247
|
+
}
|
|
248
|
+
return a.hash.localeCompare(b.hash);
|
|
249
|
+
});
|
|
250
|
+
if (sorted.length <= MAX_WALLET_PROFILE_LEDGER_ENTRIES) return sorted;
|
|
251
|
+
return sorted.slice(sorted.length - MAX_WALLET_PROFILE_LEDGER_ENTRIES);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function ensureLedgerDir(filePath: string): void {
|
|
255
|
+
const dir = path.dirname(filePath);
|
|
256
|
+
if (!fs.existsSync(dir)) {
|
|
257
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function atomicWriteLedger(
|
|
262
|
+
filePath: string,
|
|
263
|
+
store: WalletTradeLedgerStore,
|
|
264
|
+
): void {
|
|
265
|
+
ensureLedgerDir(filePath);
|
|
266
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
267
|
+
try {
|
|
268
|
+
fs.writeFileSync(tmpPath, JSON.stringify(store, null, 2), {
|
|
269
|
+
encoding: "utf-8",
|
|
270
|
+
mode: 0o600,
|
|
271
|
+
});
|
|
272
|
+
fs.renameSync(tmpPath, filePath);
|
|
273
|
+
} finally {
|
|
274
|
+
if (fs.existsSync(tmpPath)) {
|
|
275
|
+
fs.rmSync(tmpPath, { force: true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function defaultLedgerStore(): WalletTradeLedgerStore {
|
|
281
|
+
return {
|
|
282
|
+
version: WALLET_PROFILE_LEDGER_VERSION,
|
|
283
|
+
updatedAt: nowIso(),
|
|
284
|
+
entries: [],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function resolveWalletTradingProfileFilePath(
|
|
289
|
+
stateDir: string = resolveStateDir(),
|
|
290
|
+
): string {
|
|
291
|
+
return path.join(stateDir, "wallet", "trading-profile.v1.json");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function readWalletTradeLedgerStore(
|
|
295
|
+
stateDir: string = resolveStateDir(),
|
|
296
|
+
): WalletTradeLedgerStore {
|
|
297
|
+
const filePath = resolveWalletTradingProfileFilePath(stateDir);
|
|
298
|
+
if (!fs.existsSync(filePath)) return defaultLedgerStore();
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
302
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
303
|
+
const rawEntries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
304
|
+
const entries = rawEntries
|
|
305
|
+
.map((item) => normalizeLedgerEntry(item))
|
|
306
|
+
.filter((item): item is WalletTradeLedgerEntry => item !== null);
|
|
307
|
+
return {
|
|
308
|
+
version: WALLET_PROFILE_LEDGER_VERSION,
|
|
309
|
+
updatedAt: normalizeIsoDate(parsed.updatedAt),
|
|
310
|
+
entries: sortAndTrimEntries(entries),
|
|
311
|
+
};
|
|
312
|
+
} catch {
|
|
313
|
+
try {
|
|
314
|
+
const corruptPath = `${filePath}.corrupt-${Date.now()}.json`;
|
|
315
|
+
fs.renameSync(filePath, corruptPath);
|
|
316
|
+
} catch {
|
|
317
|
+
// Best effort backup; continue with empty store.
|
|
318
|
+
}
|
|
319
|
+
return defaultLedgerStore();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function writeWalletTradeLedgerStore(
|
|
324
|
+
store: WalletTradeLedgerStore,
|
|
325
|
+
stateDir: string = resolveStateDir(),
|
|
326
|
+
): WalletTradeLedgerStore {
|
|
327
|
+
const filePath = resolveWalletTradingProfileFilePath(stateDir);
|
|
328
|
+
const normalized: WalletTradeLedgerStore = {
|
|
329
|
+
version: WALLET_PROFILE_LEDGER_VERSION,
|
|
330
|
+
updatedAt: nowIso(),
|
|
331
|
+
entries: sortAndTrimEntries(store.entries),
|
|
332
|
+
};
|
|
333
|
+
atomicWriteLedger(filePath, normalized);
|
|
334
|
+
return normalized;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function recordWalletTradeLedgerEntry(
|
|
338
|
+
input: WalletTradeLedgerRecordInput,
|
|
339
|
+
stateDir: string = resolveStateDir(),
|
|
340
|
+
): WalletTradeLedgerEntry {
|
|
341
|
+
const store = readWalletTradeLedgerStore(stateDir);
|
|
342
|
+
const entry: WalletTradeLedgerEntry = {
|
|
343
|
+
hash: input.hash.trim(),
|
|
344
|
+
createdAt: normalizeIsoDate(input.createdAt ?? nowIso()),
|
|
345
|
+
updatedAt: normalizeIsoDate(input.updatedAt ?? nowIso()),
|
|
346
|
+
source: normalizeSource(input.source),
|
|
347
|
+
side: normalizeSide(input.side),
|
|
348
|
+
tokenAddress: normalizeAddress(input.tokenAddress),
|
|
349
|
+
slippageBps: Math.max(0, Math.round(toFiniteNumber(input.slippageBps))),
|
|
350
|
+
route: (input.route ?? [])
|
|
351
|
+
.map((item) => item.trim())
|
|
352
|
+
.filter((item) => item.length > 0),
|
|
353
|
+
quoteIn: {
|
|
354
|
+
symbol: input.quoteIn.symbol.trim(),
|
|
355
|
+
amount: input.quoteIn.amount.trim(),
|
|
356
|
+
amountWei: input.quoteIn.amountWei.trim(),
|
|
357
|
+
},
|
|
358
|
+
quoteOut: {
|
|
359
|
+
symbol: input.quoteOut.symbol.trim(),
|
|
360
|
+
amount: input.quoteOut.amount.trim(),
|
|
361
|
+
amountWei: input.quoteOut.amountWei.trim(),
|
|
362
|
+
},
|
|
363
|
+
status: normalizeStatus(input.status),
|
|
364
|
+
confirmations: Math.max(0, Math.floor(toFiniteNumber(input.confirmations))),
|
|
365
|
+
nonce:
|
|
366
|
+
input.nonce === null ? null : Math.floor(toFiniteNumber(input.nonce)),
|
|
367
|
+
blockNumber:
|
|
368
|
+
input.blockNumber === null
|
|
369
|
+
? null
|
|
370
|
+
: Math.max(0, Math.floor(toFiniteNumber(input.blockNumber))),
|
|
371
|
+
gasUsed: input.gasUsed?.trim() || null,
|
|
372
|
+
effectiveGasPriceWei: input.effectiveGasPriceWei?.trim() || null,
|
|
373
|
+
...(input.reason?.trim() ? { reason: input.reason.trim() } : {}),
|
|
374
|
+
explorerUrl:
|
|
375
|
+
input.explorerUrl.trim() || `https://bscscan.com/tx/${input.hash.trim()}`,
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const existingIndex = store.entries.findIndex(
|
|
379
|
+
(item) => item.hash === entry.hash,
|
|
380
|
+
);
|
|
381
|
+
if (existingIndex >= 0) {
|
|
382
|
+
store.entries[existingIndex] = entry;
|
|
383
|
+
} else {
|
|
384
|
+
store.entries.push(entry);
|
|
385
|
+
}
|
|
386
|
+
writeWalletTradeLedgerStore(store, stateDir);
|
|
387
|
+
return entry;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function updateWalletTradeLedgerEntryStatus(
|
|
391
|
+
hash: string,
|
|
392
|
+
patch: WalletTradeLedgerStatusPatch,
|
|
393
|
+
stateDir: string = resolveStateDir(),
|
|
394
|
+
): WalletTradeLedgerEntry | null {
|
|
395
|
+
const normalizedHash = hash.trim();
|
|
396
|
+
if (!normalizedHash) return null;
|
|
397
|
+
|
|
398
|
+
const store = readWalletTradeLedgerStore(stateDir);
|
|
399
|
+
const index = store.entries.findIndex(
|
|
400
|
+
(entry) => entry.hash === normalizedHash,
|
|
401
|
+
);
|
|
402
|
+
if (index < 0) return null;
|
|
403
|
+
|
|
404
|
+
const current = store.entries[index];
|
|
405
|
+
const updated: WalletTradeLedgerEntry = {
|
|
406
|
+
...current,
|
|
407
|
+
status: normalizeStatus(patch.status),
|
|
408
|
+
confirmations: Math.max(0, Math.floor(toFiniteNumber(patch.confirmations))),
|
|
409
|
+
nonce:
|
|
410
|
+
patch.nonce === null ? null : Math.floor(toFiniteNumber(patch.nonce)),
|
|
411
|
+
blockNumber:
|
|
412
|
+
patch.blockNumber === null
|
|
413
|
+
? null
|
|
414
|
+
: Math.max(0, Math.floor(toFiniteNumber(patch.blockNumber))),
|
|
415
|
+
gasUsed: patch.gasUsed?.trim() || null,
|
|
416
|
+
effectiveGasPriceWei: patch.effectiveGasPriceWei?.trim() || null,
|
|
417
|
+
...(patch.explorerUrl?.trim()
|
|
418
|
+
? { explorerUrl: patch.explorerUrl.trim() }
|
|
419
|
+
: {}),
|
|
420
|
+
updatedAt: normalizeIsoDate(patch.updatedAt ?? nowIso()),
|
|
421
|
+
};
|
|
422
|
+
if (typeof patch.reason === "string") {
|
|
423
|
+
const reason = patch.reason.trim();
|
|
424
|
+
if (reason) {
|
|
425
|
+
updated.reason = reason;
|
|
426
|
+
} else {
|
|
427
|
+
delete updated.reason;
|
|
428
|
+
}
|
|
429
|
+
} else if (updated.status === "success" || updated.status === "pending") {
|
|
430
|
+
delete updated.reason;
|
|
431
|
+
}
|
|
432
|
+
store.entries[index] = updated;
|
|
433
|
+
writeWalletTradeLedgerStore(store, stateDir);
|
|
434
|
+
return updated;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function resolveWindow(
|
|
438
|
+
window: WalletTradingProfileWindow | undefined,
|
|
439
|
+
): WalletTradingProfileWindow {
|
|
440
|
+
if (window === "7d" || window === "30d" || window === "all") return window;
|
|
441
|
+
return "30d";
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function resolveSource(
|
|
445
|
+
source: WalletTradingProfileSourceFilter | undefined,
|
|
446
|
+
): WalletTradingProfileSourceFilter {
|
|
447
|
+
if (source === "all" || source === "agent" || source === "manual")
|
|
448
|
+
return source;
|
|
449
|
+
return "all";
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function resolveTokenSymbol(entry: WalletTradeLedgerEntry): string {
|
|
453
|
+
return entry.side === "buy" ? entry.quoteOut.symbol : entry.quoteIn.symbol;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function toDayBucket(isoDate: string): string {
|
|
457
|
+
const timestamp = Date.parse(isoDate);
|
|
458
|
+
if (!Number.isFinite(timestamp)) return new Date().toISOString().slice(0, 10);
|
|
459
|
+
return new Date(timestamp).toISOString().slice(0, 10);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function consumeLots(lots: TokenLot[], qtyToSell: number): number {
|
|
463
|
+
if (!Number.isFinite(qtyToSell) || qtyToSell <= FLOAT_EPSILON) return 0;
|
|
464
|
+
let remaining = qtyToSell;
|
|
465
|
+
let matchedCost = 0;
|
|
466
|
+
|
|
467
|
+
while (remaining > FLOAT_EPSILON && lots.length > 0) {
|
|
468
|
+
const lot = lots[0];
|
|
469
|
+
if (lot.qty <= FLOAT_EPSILON) {
|
|
470
|
+
lots.shift();
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const take = Math.min(remaining, lot.qty);
|
|
474
|
+
const ratio = take / lot.qty;
|
|
475
|
+
const consumedCost = lot.costBnb * ratio;
|
|
476
|
+
matchedCost += consumedCost;
|
|
477
|
+
lot.qty -= take;
|
|
478
|
+
lot.costBnb -= consumedCost;
|
|
479
|
+
remaining -= take;
|
|
480
|
+
|
|
481
|
+
if (lot.qty <= FLOAT_EPSILON || lot.costBnb <= FLOAT_EPSILON) {
|
|
482
|
+
lots.shift();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return matchedCost;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function toRecentSwap(
|
|
489
|
+
entry: WalletTradeLedgerEntry,
|
|
490
|
+
): WalletTradingProfileRecentSwap {
|
|
491
|
+
return {
|
|
492
|
+
hash: entry.hash,
|
|
493
|
+
createdAt: entry.createdAt,
|
|
494
|
+
source: entry.source,
|
|
495
|
+
side: entry.side,
|
|
496
|
+
status: entry.status,
|
|
497
|
+
tokenAddress: entry.tokenAddress,
|
|
498
|
+
tokenSymbol: resolveTokenSymbol(entry),
|
|
499
|
+
inputAmount: entry.quoteIn.amount,
|
|
500
|
+
inputSymbol: entry.quoteIn.symbol,
|
|
501
|
+
outputAmount: entry.quoteOut.amount,
|
|
502
|
+
outputSymbol: entry.quoteOut.symbol,
|
|
503
|
+
explorerUrl: entry.explorerUrl || `https://bscscan.com/tx/${entry.hash}`,
|
|
504
|
+
confirmations: entry.confirmations,
|
|
505
|
+
...(entry.reason ? { reason: entry.reason } : {}),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export function buildWalletTradingProfile(
|
|
510
|
+
entries: WalletTradeLedgerEntry[],
|
|
511
|
+
options: Pick<WalletTradingProfileOptions, "window" | "source"> = {},
|
|
512
|
+
): WalletTradingProfileResponse {
|
|
513
|
+
const window = resolveWindow(options.window);
|
|
514
|
+
const source = resolveSource(options.source);
|
|
515
|
+
const now = Date.now();
|
|
516
|
+
const cutoffMs =
|
|
517
|
+
window === "7d"
|
|
518
|
+
? now - 7 * ONE_DAY_MS
|
|
519
|
+
: window === "30d"
|
|
520
|
+
? now - 30 * ONE_DAY_MS
|
|
521
|
+
: 0;
|
|
522
|
+
|
|
523
|
+
const filtered = entries
|
|
524
|
+
.filter((entry) => {
|
|
525
|
+
if (source !== "all" && entry.source !== source) return false;
|
|
526
|
+
if (cutoffMs > 0) {
|
|
527
|
+
const createdAtMs = Date.parse(entry.createdAt);
|
|
528
|
+
if (!Number.isFinite(createdAtMs) || createdAtMs < cutoffMs)
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
return true;
|
|
532
|
+
})
|
|
533
|
+
.sort((a, b) => {
|
|
534
|
+
const aMs = Date.parse(a.createdAt);
|
|
535
|
+
const bMs = Date.parse(b.createdAt);
|
|
536
|
+
if (Number.isFinite(aMs) && Number.isFinite(bMs) && aMs !== bMs) {
|
|
537
|
+
return aMs - bMs;
|
|
538
|
+
}
|
|
539
|
+
return a.hash.localeCompare(b.hash);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
let buyCount = 0;
|
|
543
|
+
let sellCount = 0;
|
|
544
|
+
let successCount = 0;
|
|
545
|
+
let revertedCount = 0;
|
|
546
|
+
let settledCount = 0;
|
|
547
|
+
let winningTrades = 0;
|
|
548
|
+
let evaluatedTrades = 0;
|
|
549
|
+
let realizedPnlBnb = 0;
|
|
550
|
+
let volumeBnb = 0;
|
|
551
|
+
|
|
552
|
+
const tokenMap = new Map<string, TokenAccumulator>();
|
|
553
|
+
const seriesMap = new Map<string, SeriesAccumulator>();
|
|
554
|
+
|
|
555
|
+
for (const entry of filtered) {
|
|
556
|
+
if (entry.side === "buy") buyCount += 1;
|
|
557
|
+
else sellCount += 1;
|
|
558
|
+
|
|
559
|
+
if (entry.status === "success") successCount += 1;
|
|
560
|
+
if (entry.status === "reverted") revertedCount += 1;
|
|
561
|
+
if (entry.status === "success" || entry.status === "reverted")
|
|
562
|
+
settledCount += 1;
|
|
563
|
+
|
|
564
|
+
if (entry.status !== "success") continue;
|
|
565
|
+
|
|
566
|
+
const tokenAddress = entry.tokenAddress;
|
|
567
|
+
const tokenSymbol = resolveTokenSymbol(entry);
|
|
568
|
+
let token = tokenMap.get(tokenAddress);
|
|
569
|
+
if (!token) {
|
|
570
|
+
token = {
|
|
571
|
+
tokenAddress,
|
|
572
|
+
symbol: tokenSymbol,
|
|
573
|
+
buyCount: 0,
|
|
574
|
+
sellCount: 0,
|
|
575
|
+
realizedPnlBnb: 0,
|
|
576
|
+
volumeBnb: 0,
|
|
577
|
+
winningTrades: 0,
|
|
578
|
+
evaluatedTrades: 0,
|
|
579
|
+
lots: [],
|
|
580
|
+
};
|
|
581
|
+
tokenMap.set(tokenAddress, token);
|
|
582
|
+
} else if (!token.symbol && tokenSymbol) {
|
|
583
|
+
token.symbol = tokenSymbol;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const day = toDayBucket(entry.createdAt);
|
|
587
|
+
let series = seriesMap.get(day);
|
|
588
|
+
if (!series) {
|
|
589
|
+
series = {
|
|
590
|
+
day,
|
|
591
|
+
realizedPnlBnb: 0,
|
|
592
|
+
volumeBnb: 0,
|
|
593
|
+
swaps: 0,
|
|
594
|
+
};
|
|
595
|
+
seriesMap.set(day, series);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (entry.side === "buy") {
|
|
599
|
+
const qtyBought = toFiniteNumber(entry.quoteOut.amount);
|
|
600
|
+
const spendBnb = toFiniteNumber(entry.quoteIn.amount);
|
|
601
|
+
token.buyCount += 1;
|
|
602
|
+
token.volumeBnb += spendBnb;
|
|
603
|
+
if (qtyBought > FLOAT_EPSILON && spendBnb >= 0) {
|
|
604
|
+
token.lots.push({ qty: qtyBought, costBnb: spendBnb });
|
|
605
|
+
}
|
|
606
|
+
volumeBnb += spendBnb;
|
|
607
|
+
series.volumeBnb += spendBnb;
|
|
608
|
+
series.swaps += 1;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const qtySold = toFiniteNumber(entry.quoteIn.amount);
|
|
613
|
+
const proceedsBnb = toFiniteNumber(entry.quoteOut.amount);
|
|
614
|
+
const matchedCostBnb = consumeLots(token.lots, qtySold);
|
|
615
|
+
const tradePnlBnb = proceedsBnb - matchedCostBnb;
|
|
616
|
+
|
|
617
|
+
token.sellCount += 1;
|
|
618
|
+
token.volumeBnb += proceedsBnb;
|
|
619
|
+
token.realizedPnlBnb += tradePnlBnb;
|
|
620
|
+
token.evaluatedTrades += 1;
|
|
621
|
+
if (tradePnlBnb > 0) token.winningTrades += 1;
|
|
622
|
+
|
|
623
|
+
evaluatedTrades += 1;
|
|
624
|
+
if (tradePnlBnb > 0) winningTrades += 1;
|
|
625
|
+
realizedPnlBnb += tradePnlBnb;
|
|
626
|
+
volumeBnb += proceedsBnb;
|
|
627
|
+
|
|
628
|
+
series.realizedPnlBnb += tradePnlBnb;
|
|
629
|
+
series.volumeBnb += proceedsBnb;
|
|
630
|
+
series.swaps += 1;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const tokenBreakdown: WalletTradingProfileTokenBreakdown[] = [
|
|
634
|
+
...tokenMap.values(),
|
|
635
|
+
]
|
|
636
|
+
.map((token) => ({
|
|
637
|
+
tokenAddress: token.tokenAddress,
|
|
638
|
+
symbol: token.symbol || "TOKEN",
|
|
639
|
+
buyCount: token.buyCount,
|
|
640
|
+
sellCount: token.sellCount,
|
|
641
|
+
realizedPnlBnb: toBnbString(token.realizedPnlBnb),
|
|
642
|
+
volumeBnb: toBnbString(token.volumeBnb),
|
|
643
|
+
tradeWinRate: toRatePercent(token.winningTrades, token.evaluatedTrades),
|
|
644
|
+
winningTrades: token.winningTrades,
|
|
645
|
+
evaluatedTrades: token.evaluatedTrades,
|
|
646
|
+
}))
|
|
647
|
+
.sort(
|
|
648
|
+
(a, b) => Number.parseFloat(b.volumeBnb) - Number.parseFloat(a.volumeBnb),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const pnlSeries = [...seriesMap.values()]
|
|
652
|
+
.sort((a, b) => a.day.localeCompare(b.day))
|
|
653
|
+
.map((point) => ({
|
|
654
|
+
day: point.day,
|
|
655
|
+
realizedPnlBnb: toBnbString(point.realizedPnlBnb),
|
|
656
|
+
volumeBnb: toBnbString(point.volumeBnb),
|
|
657
|
+
swaps: point.swaps,
|
|
658
|
+
}));
|
|
659
|
+
|
|
660
|
+
const recentSwaps = [...filtered]
|
|
661
|
+
.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt))
|
|
662
|
+
.slice(0, 20)
|
|
663
|
+
.map(toRecentSwap);
|
|
664
|
+
|
|
665
|
+
return {
|
|
666
|
+
window,
|
|
667
|
+
source,
|
|
668
|
+
generatedAt: nowIso(),
|
|
669
|
+
summary: {
|
|
670
|
+
totalSwaps: filtered.length,
|
|
671
|
+
buyCount,
|
|
672
|
+
sellCount,
|
|
673
|
+
settledCount,
|
|
674
|
+
successCount,
|
|
675
|
+
revertedCount,
|
|
676
|
+
tradeWinRate: toRatePercent(winningTrades, evaluatedTrades),
|
|
677
|
+
txSuccessRate: toRatePercent(successCount, settledCount),
|
|
678
|
+
winningTrades,
|
|
679
|
+
evaluatedTrades,
|
|
680
|
+
realizedPnlBnb: toBnbString(realizedPnlBnb),
|
|
681
|
+
volumeBnb: toBnbString(volumeBnb),
|
|
682
|
+
},
|
|
683
|
+
pnlSeries,
|
|
684
|
+
tokenBreakdown,
|
|
685
|
+
recentSwaps,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export function loadWalletTradingProfile(
|
|
690
|
+
options: WalletTradingProfileOptions = {},
|
|
691
|
+
): WalletTradingProfileResponse {
|
|
692
|
+
const store = readWalletTradeLedgerStore(options.stateDir);
|
|
693
|
+
return buildWalletTradingProfile(store.entries, options);
|
|
694
|
+
}
|