lovewebconnection 0.0.0-stage → 0.2.1

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.
Files changed (68) hide show
  1. package/.env.example +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1937 -2
  4. package/dist/alert-queue.d.ts +72 -0
  5. package/dist/alert-queue.js +155 -0
  6. package/dist/alert-store.d.ts +29 -0
  7. package/dist/alert-store.js +75 -0
  8. package/dist/balances.d.ts +221 -0
  9. package/dist/balances.js +1232 -0
  10. package/dist/cache.d.ts +37 -0
  11. package/dist/cache.js +128 -0
  12. package/dist/call.d.ts +46 -0
  13. package/dist/call.js +377 -0
  14. package/dist/chain.d.ts +97 -0
  15. package/dist/chain.js +419 -0
  16. package/dist/cli.d.ts +2 -0
  17. package/dist/cli.js +1011 -0
  18. package/dist/compare-history.d.ts +82 -0
  19. package/dist/compare-history.js +340 -0
  20. package/dist/compare.d.ts +98 -0
  21. package/dist/compare.js +492 -0
  22. package/dist/contract.d.ts +107 -0
  23. package/dist/contract.js +162 -0
  24. package/dist/default-abi.d.ts +53 -0
  25. package/dist/default-abi.js +85 -0
  26. package/dist/history.d.ts +83 -0
  27. package/dist/history.js +422 -0
  28. package/dist/index.d.ts +54 -0
  29. package/dist/index.js +27 -0
  30. package/dist/market-page.d.ts +8 -0
  31. package/dist/market-page.js +554 -0
  32. package/dist/market-server.d.ts +46 -0
  33. package/dist/market-server.js +473 -0
  34. package/dist/market.d.ts +323 -0
  35. package/dist/market.js +835 -0
  36. package/dist/metadata-cache.d.ts +72 -0
  37. package/dist/metadata-cache.js +197 -0
  38. package/dist/metrics.d.ts +64 -0
  39. package/dist/metrics.js +138 -0
  40. package/dist/plot.d.ts +60 -0
  41. package/dist/plot.js +317 -0
  42. package/dist/presets-history.d.ts +372 -0
  43. package/dist/presets-history.js +1243 -0
  44. package/dist/presets-summary.d.ts +17 -0
  45. package/dist/presets-summary.js +55 -0
  46. package/dist/presets.d.ts +59 -0
  47. package/dist/presets.js +181 -0
  48. package/dist/prices.d.ts +63 -0
  49. package/dist/prices.js +154 -0
  50. package/dist/rotate.d.ts +19 -0
  51. package/dist/rotate.js +34 -0
  52. package/dist/scan.d.ts +81 -0
  53. package/dist/scan.js +501 -0
  54. package/dist/sessions.d.ts +104 -0
  55. package/dist/sessions.js +515 -0
  56. package/dist/signatures.d.ts +13 -0
  57. package/dist/signatures.js +43 -0
  58. package/dist/tokenfile.d.ts +50 -0
  59. package/dist/tokenfile.js +218 -0
  60. package/dist/views.d.ts +63 -0
  61. package/dist/views.js +415 -0
  62. package/dist/watch.d.ts +306 -0
  63. package/dist/watch.js +1121 -0
  64. package/dist/webhook.d.ts +17 -0
  65. package/dist/webhook.js +35 -0
  66. package/examples/erc20.abi.json +57 -0
  67. package/examples/xlayer.tokens.json +4 -0
  68. package/package.json +77 -4
@@ -0,0 +1,422 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { formatTokenAmount, shortAddress } from "./balances.js";
4
+ import { csvCell } from "./scan.js";
5
+ export class HistoryArgError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "HistoryArgError";
9
+ }
10
+ }
11
+ export function parseHistoryArgs(argv) {
12
+ const positional = [];
13
+ let csvPath = null;
14
+ let session = null;
15
+ let wide = false;
16
+ let usd = false;
17
+ let usdOnly = false;
18
+ for (let index = 0; index < argv.length; index += 1) {
19
+ const arg = argv[index] ?? "";
20
+ if (arg === "--csv") {
21
+ const value = argv[index + 1];
22
+ if (!value || (value.startsWith("-") && value !== "-")) {
23
+ throw new HistoryArgError("--csv requires a path, or - for stdout.");
24
+ }
25
+ csvPath = value;
26
+ index += 1;
27
+ }
28
+ else if (arg.startsWith("--csv=")) {
29
+ const value = arg.slice("--csv=".length);
30
+ if (!value)
31
+ throw new HistoryArgError("--csv requires a path, or - for stdout.");
32
+ csvPath = value;
33
+ }
34
+ else if (arg === "--wide") {
35
+ wide = true;
36
+ }
37
+ else if (arg === "--usd") {
38
+ usd = true;
39
+ }
40
+ else if (arg === "--usd-only") {
41
+ usdOnly = true;
42
+ }
43
+ else if (arg === "--session") {
44
+ const value = argv[index + 1];
45
+ if (!value || value.startsWith("-"))
46
+ throw new HistoryArgError("--session requires an id.");
47
+ session = value;
48
+ index += 1;
49
+ }
50
+ else if (arg.startsWith("--session=")) {
51
+ const value = arg.slice("--session=".length);
52
+ if (!value)
53
+ throw new HistoryArgError("--session requires an id.");
54
+ session = value;
55
+ }
56
+ else if (arg.startsWith("-")) {
57
+ continue; // --json and other global flags are handled by the CLI.
58
+ }
59
+ else {
60
+ positional.push(arg);
61
+ }
62
+ }
63
+ const [path] = positional;
64
+ if (!path) {
65
+ throw new HistoryArgError("A watch log path is required: lwc history <log.ndjson> [--csv out.csv]");
66
+ }
67
+ if (positional.length > 1) {
68
+ throw new HistoryArgError(`Unexpected extra argument: ${positional[1]}.`);
69
+ }
70
+ if (wide && csvPath === null) {
71
+ throw new HistoryArgError("--wide needs CSV output: use --csv <path> or --csv -.");
72
+ }
73
+ if (usd && !wide) {
74
+ throw new HistoryArgError("--usd needs --wide so there are asset columns to value.");
75
+ }
76
+ if (usdOnly && !wide) {
77
+ throw new HistoryArgError("--usd-only needs --wide so there is a pivot to value.");
78
+ }
79
+ return { path, csvPath, session, wide, usd, usdOnly };
80
+ }
81
+ export function readHistoryLog(path) {
82
+ if (!existsSync(path)) {
83
+ throw new HistoryArgError(`watch log not found: ${path}`);
84
+ }
85
+ return readFileSync(path, "utf8");
86
+ }
87
+ function objectOf(value) {
88
+ return value !== null && typeof value === "object" && !Array.isArray(value)
89
+ ? value
90
+ : null;
91
+ }
92
+ function numberField(value) {
93
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
94
+ }
95
+ function bigintField(value) {
96
+ if (typeof value === "string" && /^-?\d+$/.test(value))
97
+ return BigInt(value);
98
+ if (typeof value === "number" && Number.isSafeInteger(value))
99
+ return BigInt(value);
100
+ return null;
101
+ }
102
+ function timeField(value) {
103
+ if (typeof value !== "string")
104
+ return null;
105
+ const ms = Date.parse(value);
106
+ return Number.isFinite(ms) ? ms : null;
107
+ }
108
+ /**
109
+ * Turn the `portfolio` events of a watch NDJSON log into one row per asset per
110
+ * sample. A log holds every event type, so anything else is ignored; an errored
111
+ * sample has no balances and is counted instead of emitting blank rows.
112
+ */
113
+ export function extractBalanceHistory(text, options = {}) {
114
+ const rows = [];
115
+ const sessions = [];
116
+ let skippedSamples = 0;
117
+ let skippedLines = 0;
118
+ for (const raw of text.split(/\r?\n/)) {
119
+ const line = raw.trim();
120
+ if (line.length === 0)
121
+ continue;
122
+ let parsed;
123
+ try {
124
+ parsed = JSON.parse(line);
125
+ }
126
+ catch {
127
+ skippedLines += 1;
128
+ continue;
129
+ }
130
+ const record = objectOf(parsed);
131
+ const session = record !== null && typeof record.session === "string" ? record.session : null;
132
+ if (record === null || session === null) {
133
+ skippedLines += 1;
134
+ continue;
135
+ }
136
+ if (options.session && session !== options.session)
137
+ continue;
138
+ if (record.event !== "portfolio")
139
+ continue;
140
+ const at = timeField(record.at);
141
+ if (at === null) {
142
+ skippedLines += 1;
143
+ continue;
144
+ }
145
+ if (record.error !== null && record.error !== undefined) {
146
+ skippedSamples += 1;
147
+ continue;
148
+ }
149
+ const blockNumber = bigintField(record.blockNumber);
150
+ const usdTotal = numberField(record.usdTotal);
151
+ const native = objectOf(record.native);
152
+ if (native !== null && typeof native.symbol === "string") {
153
+ rows.push({
154
+ session,
155
+ at,
156
+ blockNumber,
157
+ asset: "native",
158
+ address: null,
159
+ symbol: native.symbol,
160
+ label: null,
161
+ decimals: numberField(native.decimals),
162
+ balance: bigintField(native.balance),
163
+ delta: bigintField(native.delta),
164
+ usd: numberField(native.usd),
165
+ usdTotal,
166
+ });
167
+ }
168
+ const tokens = Array.isArray(record.tokens) ? record.tokens : [];
169
+ for (const value of tokens) {
170
+ const token = objectOf(value);
171
+ if (token === null || typeof token.address !== "string")
172
+ continue;
173
+ rows.push({
174
+ session,
175
+ at,
176
+ blockNumber,
177
+ asset: "token",
178
+ address: token.address,
179
+ symbol: typeof token.symbol === "string" ? token.symbol : token.address,
180
+ label: typeof token.label === "string" ? token.label : null,
181
+ decimals: numberField(token.decimals),
182
+ balance: bigintField(token.balance),
183
+ delta: bigintField(token.delta),
184
+ usd: numberField(token.usd),
185
+ usdTotal,
186
+ });
187
+ }
188
+ if (!sessions.includes(session))
189
+ sessions.push(session);
190
+ }
191
+ return { rows, sessions, skippedSamples, skippedLines };
192
+ }
193
+ function amount(value, decimals) {
194
+ return decimals === null ? value.toString() : formatTokenAmount(value, decimals);
195
+ }
196
+ function usdCell(value) {
197
+ return value === null ? "" : value.toFixed(2);
198
+ }
199
+ export const HISTORY_CSV_HEADER = [
200
+ "session",
201
+ "at",
202
+ "block",
203
+ "asset",
204
+ "address",
205
+ "symbol",
206
+ "label",
207
+ "decimals",
208
+ "balance",
209
+ "delta",
210
+ "usd",
211
+ "usd_total",
212
+ ];
213
+ /**
214
+ * Long-format CSV: one row per asset per sample, with an ISO timestamp, the
215
+ * human-readable balance, and the change since the previous sample. Long format
216
+ * keeps the columns stable as tokens come and go, so a chart can group by
217
+ * `symbol` or `address`.
218
+ */
219
+ export function historyCsv(report) {
220
+ const lines = [HISTORY_CSV_HEADER.join(",")];
221
+ for (const row of report.rows) {
222
+ lines.push([
223
+ row.session,
224
+ new Date(row.at).toISOString(),
225
+ row.blockNumber === null ? "" : row.blockNumber.toString(),
226
+ row.asset,
227
+ row.address ?? "",
228
+ row.symbol,
229
+ row.label ?? "",
230
+ row.decimals === null ? "" : String(row.decimals),
231
+ row.balance === null ? "" : amount(row.balance, row.decimals),
232
+ row.delta === null ? "" : amount(row.delta, row.decimals),
233
+ usdCell(row.usd),
234
+ usdCell(row.usdTotal),
235
+ ]
236
+ .map(csvCell)
237
+ .join(","));
238
+ }
239
+ return `${lines.join("\n")}\n`;
240
+ }
241
+ function assetKey(row) {
242
+ return row.asset === "native" ? "native" : `token:${(row.address ?? "").toLowerCase()}`;
243
+ }
244
+ /**
245
+ * One column per asset, in first-appearance order. A symbol shared by more than
246
+ * one asset is disambiguated with its short contract address (or `native`) so
247
+ * each holding keeps its own series.
248
+ */
249
+ function wideColumns(rows) {
250
+ const seen = new Map();
251
+ for (const row of rows) {
252
+ const key = assetKey(row);
253
+ if (seen.has(key))
254
+ continue;
255
+ seen.set(key, {
256
+ symbol: row.symbol,
257
+ suffix: row.asset === "native" ? "(native)" : `(${shortAddress(row.address ?? "")})`,
258
+ });
259
+ }
260
+ const counts = new Map();
261
+ for (const { symbol } of seen.values()) {
262
+ counts.set(symbol, (counts.get(symbol) ?? 0) + 1);
263
+ }
264
+ return [...seen].map(([key, { symbol, suffix }]) => ({
265
+ key,
266
+ name: (counts.get(symbol) ?? 0) > 1 ? `${symbol} ${suffix}` : symbol,
267
+ }));
268
+ }
269
+ /**
270
+ * Wide-format CSV: one row per portfolio sample and one column per asset, for
271
+ * tools that expect a single time series per column. `session`, `at`, and
272
+ * `block` stay first so samples from different runs remain distinguishable; an
273
+ * asset missing from a sample leaves its cell empty rather than repeating it.
274
+ * `usd_total` closes every row. With `options.usd`, a `SYMBOL (USD)` column
275
+ * follows each asset column and uses the values priced by `watch --usd`. With
276
+ * `options.usdOnly`, the raw balance columns are dropped and only those
277
+ * `SYMBOL (USD)` columns remain, for a purely value-based series.
278
+ */
279
+ export function historyWideCsv(report, options = {}) {
280
+ const columns = wideColumns(report.rows);
281
+ const usdOnly = options.usdOnly === true;
282
+ const withUsd = usdOnly || options.usd === true;
283
+ const header = ["session", "at", "block"];
284
+ if (!usdOnly)
285
+ header.push(...columns.map((column) => column.name));
286
+ if (withUsd) {
287
+ header.push(...columns.map((column) => `${column.name} (USD)`));
288
+ }
289
+ header.push("usd_total");
290
+ const lines = [header.map(csvCell).join(",")];
291
+ const samples = new Map();
292
+ for (const row of report.rows) {
293
+ const sampleKey = `${row.session}\u0000${row.at}`;
294
+ let sample = samples.get(sampleKey);
295
+ if (sample === undefined) {
296
+ sample = {
297
+ session: row.session,
298
+ at: row.at,
299
+ blockNumber: row.blockNumber,
300
+ cells: new Map(),
301
+ usdCells: new Map(),
302
+ usdTotal: row.usdTotal,
303
+ };
304
+ samples.set(sampleKey, sample);
305
+ }
306
+ if (sample.blockNumber === null)
307
+ sample.blockNumber = row.blockNumber;
308
+ if (sample.usdTotal === null)
309
+ sample.usdTotal = row.usdTotal;
310
+ sample.cells.set(assetKey(row), row.balance === null ? "" : amount(row.balance, row.decimals));
311
+ sample.usdCells.set(assetKey(row), usdCell(row.usd));
312
+ }
313
+ for (const sample of samples.values()) {
314
+ lines.push([
315
+ sample.session,
316
+ new Date(sample.at).toISOString(),
317
+ sample.blockNumber === null ? "" : sample.blockNumber.toString(),
318
+ ...(usdOnly
319
+ ? []
320
+ : columns.map((column) => sample.cells.get(column.key) ?? "")),
321
+ ...(withUsd
322
+ ? columns.map((column) => sample.usdCells.get(column.key) ?? "")
323
+ : []),
324
+ usdCell(sample.usdTotal),
325
+ ]
326
+ .map(csvCell)
327
+ .join(","));
328
+ }
329
+ return `${lines.join("\n")}\n`;
330
+ }
331
+ /** Write the CSV, creating parent directories; a derived file is overwritten. */
332
+ export function saveHistoryCsv(path, csv) {
333
+ try {
334
+ mkdirSync(dirname(path), { recursive: true });
335
+ writeFileSync(path, csv);
336
+ }
337
+ catch (error) {
338
+ const reason = error instanceof Error ? error.message : String(error);
339
+ throw new HistoryArgError(`cannot write ${path}: ${reason}`);
340
+ }
341
+ }
342
+ function clockTime(at) {
343
+ return new Date(at).toISOString().slice(11, 19);
344
+ }
345
+ function assetName(row) {
346
+ if (row.asset === "native")
347
+ return `native ${row.symbol}`;
348
+ return row.label ? `${row.symbol} [${row.label}]` : row.symbol;
349
+ }
350
+ export function formatHistory(report) {
351
+ const samples = new Set(report.rows.map((row) => `${row.session}\u0000${row.at}`)).size;
352
+ const lines = [
353
+ "lwc — balance history",
354
+ "",
355
+ ` ${"log".padEnd(14)}${report.path}`,
356
+ ` ${"rows".padEnd(14)}${report.rows.length} (${samples} portfolio sample${samples === 1 ? "" : "s"}, ${report.sessions.length} session${report.sessions.length === 1 ? "" : "s"})`,
357
+ ];
358
+ if (report.skippedSamples > 0) {
359
+ lines.push(` ${"skipped".padEnd(14)}${report.skippedSamples} sample(s) without balances`);
360
+ }
361
+ if (report.skippedLines > 0) {
362
+ lines.push(` ${"malformed".padEnd(14)}${report.skippedLines} line(s)`);
363
+ }
364
+ if (report.rows.length === 0) {
365
+ lines.push("", " no portfolio samples here (run watch with --owner and --tokens-file)");
366
+ return lines.join("\n");
367
+ }
368
+ const timeWidth = 10;
369
+ const blockWidth = Math.max(5, ...report.rows.map((row) => (row.blockNumber ?? "").toString().length));
370
+ const assetWidth = Math.max(5, ...report.rows.map((row) => assetName(row).length));
371
+ const balanceWidth = Math.max(7, ...report.rows.map((row) => (row.balance === null ? 3 : amount(row.balance, row.decimals).length)));
372
+ const deltaWidth = Math.max(5, ...report.rows.map((row) => (row.delta === null ? 3 : amount(row.delta, row.decimals).length)));
373
+ const usdWidth = Math.max(3, ...report.rows.map((row) => (row.usd === null ? 3 : `$${row.usd.toFixed(2)}`.length)));
374
+ const totalWidth = Math.max(5, ...report.rows.map((row) => (row.usdTotal === null ? 3 : `$${row.usdTotal.toFixed(2)}`.length)));
375
+ const header = [
376
+ "time (UTC)".padEnd(timeWidth),
377
+ "block".padEnd(blockWidth),
378
+ "asset".padEnd(assetWidth),
379
+ "balance".padStart(balanceWidth),
380
+ "delta".padStart(deltaWidth),
381
+ "usd".padStart(usdWidth),
382
+ "total".padStart(totalWidth),
383
+ ].join(" ");
384
+ lines.push("", header);
385
+ for (const row of report.rows) {
386
+ lines.push([
387
+ clockTime(row.at).padEnd(timeWidth),
388
+ (row.blockNumber === null ? "n/a" : row.blockNumber.toString()).padEnd(blockWidth),
389
+ assetName(row).padEnd(assetWidth),
390
+ (row.balance === null ? "n/a" : amount(row.balance, row.decimals)).padStart(balanceWidth),
391
+ (row.delta === null ? "n/a" : amount(row.delta, row.decimals)).padStart(deltaWidth),
392
+ (row.usd === null ? "n/a" : `$${row.usd.toFixed(2)}`).padStart(usdWidth),
393
+ (row.usdTotal === null ? "n/a" : `$${row.usdTotal.toFixed(2)}`).padStart(totalWidth),
394
+ ].join(" "));
395
+ }
396
+ return lines.join("\n");
397
+ }
398
+ export function toHistoryJson(report) {
399
+ return {
400
+ ok: true,
401
+ log: report.path,
402
+ sessions: report.sessions,
403
+ rows: report.rows.length,
404
+ skippedSamples: report.skippedSamples,
405
+ skippedLines: report.skippedLines,
406
+ balances: report.rows.map((row) => ({
407
+ session: row.session,
408
+ at: new Date(row.at).toISOString(),
409
+ blockNumber: row.blockNumber === null ? null : row.blockNumber.toString(),
410
+ asset: row.asset,
411
+ address: row.address,
412
+ symbol: row.symbol,
413
+ label: row.label,
414
+ decimals: row.decimals,
415
+ balance: row.balance === null ? null : row.balance.toString(),
416
+ delta: row.delta === null ? null : row.delta.toString(),
417
+ formatted: row.balance === null ? null : amount(row.balance, row.decimals),
418
+ usd: row.usd,
419
+ usdTotal: row.usdTotal,
420
+ })),
421
+ };
422
+ }
@@ -0,0 +1,54 @@
1
+ export { BalancesArgError, CACHED_TOKENS_PER_BATCH, MAX_BATCH_SIZE, MAX_SUB_CALLS, checkTokenAddresses, checkTokenAddressesReport, formatBalances, formatCacheAge, formatTokenAmount, isPrecompileAddress, loadAddressList, loadTokenList, parseBalanceArgs, readBalances, readOwnerBatch, shortAddress, staleMetadataWarnings, toBalancesJson, } from "./balances.ts";
2
+ export type { BalanceArgs, BalancesOptions, BalancesReport, CacheStats, ChunkProgress, ChunkReport, ChunkStatus, RecoverySummary, TokenBalance, TokenCheck, TokenCheckOptions, TokenCheckReport, TokenListEntry, } from "./balances.ts";
3
+ export { DEFAULT_CACHE_PATH, MetadataCache, parseCacheAge, resolveCachePath, } from "./metadata-cache.ts";
4
+ export { CacheArgError, formatMetadataCache, inspectMetadataCache, parseCacheArgs, toCacheJson, } from "./cache.ts";
5
+ export type { CacheArgs, CacheEntryReport, CacheReport } from "./cache.ts";
6
+ export { DEFAULT_NATIVE_PRICE_ID, DEFAULT_PRICE_BASE_URL, DEFAULT_PRICE_TTL_MS, PriceSource, priceChainSlug, usdValue, } from "./prices.ts";
7
+ export type { PriceBook, PriceQuote, PriceSourceOptions } from "./prices.ts";
8
+ export { HISTORY_CSV_HEADER, HistoryArgError, extractBalanceHistory, formatHistory, historyCsv, historyWideCsv, parseHistoryArgs, readHistoryLog, saveHistoryCsv, toHistoryJson, } from "./history.ts";
9
+ export type { HistoryArgs, HistoryReport, HistoryRow } from "./history.ts";
10
+ export { DEFAULT_PLOT_WIDTH, PlotArgError, buildPlotReport, formatPlot, parseCsv, parsePlotArgs, readHistoryCsv, sparkline, toPlotJson, } from "./plot.ts";
11
+ export type { PlotArgs, PlotReport, PlotSample } from "./plot.ts";
12
+ export { SESSIONS_CSV_HEADER, SessionsArgError, formatSessions, parseSessionsArgs, readWatchLog, saveSessionsCsv, sessionsCsv, summarizeWatchLog, toSessionsJson, } from "./sessions.ts";
13
+ export type { EndpointSummary, SessionBlockTimeStats, SessionLatencyStats, SessionState, SessionSummary, SessionsArgs, SessionsFilter, SessionsReport, } from "./sessions.ts";
14
+ export { TokenFileArgError, buildTokenListJson, parseTokenFileArgs, saveTokenList, tokenListItems, } from "./tokenfile.ts";
15
+ export type { TokenFileArgs, TokenFileEntry } from "./tokenfile.ts";
16
+ export { ViewsArgError, formatViews, listViewFunctions, parseViewsArgs, runViewFunctions, toViewsJson, } from "./views.ts";
17
+ export type { ViewCallResult, ViewCallStatus, ViewsArgs, ViewsArgsSpec, ViewsReport, } from "./views.ts";
18
+ export type { CachedTokenMetadata, TokenMetadata } from "./metadata-cache.ts";
19
+ export { ScanArgError, csvCell, formatScan, parseScanArgs, scanBalances, scanCsv, toScanJson, } from "./scan.ts";
20
+ export type { ScanArgs, ScanReport, ScanRow } from "./scan.ts";
21
+ export { CompareArgError, compareEndpoints, endpointAuthEnvName, formatCompare, parseCompareArgs, summarizeLatency, toCompareJson, } from "./compare.ts";
22
+ export type { CompareArgs, CompareEndpoint, CompareReport, EndpointStats, HeadDeliveryStats, LatencySummary, } from "./compare.ts";
23
+ export { COMPARE_HISTORY_CSV_HEADER, CompareHistoryArgError, compareHistoryCsv, formatCompareHistory, parseCompareHistoryArgs, saveCompareHistoryCsv, readCompareLog, summarizeCompareLog, toCompareHistoryJson, } from "./compare-history.ts";
24
+ export type { CompareHistoryReport, CompareRun, CompareRunProvider, ProviderHistory, } from "./compare-history.ts";
25
+ export { CHAIN_PRESETS, ChainConfigError, ChainMismatchError, buildClient, chainPresetNames, connectionWarnings, formatHealth, parseChainConfig, probe, probeWithFallback, publicEndpoint, resolveChainPreset, toJsonPayload, } from "./chain.ts";
26
+ export type { ChainConfig, ChainHealth, ChainPreset, ProbeOutcome } from "./chain.ts";
27
+ export { ContractCallError, MULTICALL3_ADDRESS, assertContractChain, createReader, formatExampleCall, multicall3Abi, multicall3BatchAbi, readExampleCall, readTyped, toExampleCallJson, } from "./contract.ts";
28
+ export type { ExampleCallResult } from "./contract.ts";
29
+ export { CallArgError, callViewFunction, coerceArgs, extractAbi, findFunction, formatCall, formatValue, loadAbi, parseCallArgs, resolveAbi, signatureOf, toCallJson, } from "./call.ts";
30
+ export type { CallArgs, CallDisplay, CallOutcome, ResolvedAbi } from "./call.ts";
31
+ export { SignaturesArgError, callableSignatures, formatSignatures, parseSignaturesArgs, toSignaturesJson, } from "./signatures.ts";
32
+ export type { SignaturesArgs } from "./signatures.ts";
33
+ export { MarketArgError, MarketError, MarketPool, fixedPrice, formatLiquidity, formatMarketAmount, formatMarketState, formatSwapQuote, humanPrice, loadPoolFile, parseMarketAmount, parseMarketArgs, parseShares, poolFromJson, rationalToDecimal, savePoolFile, toMarketJson, toSwapQuoteJson, } from "./market.ts";
34
+ export type { LiquidityReceipt, MarketArgs, MarketCapacity, MarketConfig, MarketErrorCode, MarketIssue, MarketOperation, MarketPoolJson, MarketPoolState, MarketServeOptions, MarketSide, MarketState, ReservePair, SwapQuote, } from "./market.ts";
35
+ export { MarketApiError, MarketServer } from "./market-server.ts";
36
+ export type { MarketServerInfo } from "./market-server.ts";
37
+ export { DEFAULT_ABI_LABEL, DEFAULT_ERC20_ABI } from "./default-abi.ts";
38
+ export { AlertStore, DEFAULT_ALERT_QUEUE_PATH, resolveAlertQueuePath, } from "./alert-store.ts";
39
+ export type { StoredAlert } from "./alert-store.ts";
40
+ export { DEFAULT_WATCH_OPTIONS, WatchArgError, createTracker, describeBalanceChange, evaluateSample, formatWatchEvent, formatWatchSummary, parseLogSize, parseWatchArgs, toWatchEventJson, toWatchSummaryJson, watchChain, } from "./watch.ts";
41
+ export { RotatingLog } from "./rotate.ts";
42
+ export { AlertQueue } from "./alert-queue.ts";
43
+ export type { AlertQueueOptions, AlertTarget } from "./alert-queue.ts";
44
+ export { postWebhook } from "./webhook.ts";
45
+ export type { WebhookOptions, WebhookResult } from "./webhook.ts";
46
+ export type { RotatingLogOptions } from "./rotate.ts";
47
+ export type { BalanceChange, PortfolioHolding, PortfolioNative, PortfolioSummary, WatchEvent, WatchOptions, WatchSample, WatchSummary, WatchTrackerState, } from "./watch.ts";
48
+ export { DEFAULT_PROBE_TIMEOUT_MS, PresetsArgError, formatPresets, parsePresetsArgs, probePresets, toPresetsJson, } from "./presets.ts";
49
+ export type { PresetProbeDeps, PresetProbeResult, PresetsReport, } from "./presets.ts";
50
+ export { escapeAnnotation, summarizePresets } from "./presets-summary.ts";
51
+ export type { PresetsSummary } from "./presets-summary.ts";
52
+ export { PresetsHistoryArgError, appendPresetHistory, comparePresetHistory, formatPresetHistory, formatPresetHistoryCheck, formatPresetHistoryComparison, mergePresetHistory, parsePresetHistoryArgs, parseFreshnessRows, parseMaxAgeMs, parseRetentionMs, presetFreshnessMarkdown, presetHistoryCheck, presetHistoryCsv, presetHistoryFailureReasons, presetHistoryTimelineCsv, presetRegressionWarnings, presetsBelowUptime, narrowestRetentionMs, providerRegressionWarnings, readPresetHistoryFile, retentionWindowWarnings, readPresetHistory, savePresetHistory, savePresetHistoryCsv, selectPresetHistory, summarizePresetGate, summarizePresetHistory, summarizeProviderHistory, toPresetHistoryJson, } from "./presets-history.ts";
53
+ export type { PresetsHistoryCheck } from "./presets-history.ts";
54
+ export type { PresetHistoryMerge, PresetHistoryRecord, PresetHistoryRow, PresetUptimeChange, PresetsGateReport, ProviderHistoryRow, ProviderUptimeChange, PresetsHistoryComparison, PresetsHistorySummary, PresetsHistoryWindow, UptimeShortfall, } from "./presets-history.ts";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ export { BalancesArgError, CACHED_TOKENS_PER_BATCH, MAX_BATCH_SIZE, MAX_SUB_CALLS, checkTokenAddresses, checkTokenAddressesReport, formatBalances, formatCacheAge, formatTokenAmount, isPrecompileAddress, loadAddressList, loadTokenList, parseBalanceArgs, readBalances, readOwnerBatch, shortAddress, staleMetadataWarnings, toBalancesJson, } from "./balances.js";
2
+ export { DEFAULT_CACHE_PATH, MetadataCache, parseCacheAge, resolveCachePath, } from "./metadata-cache.js";
3
+ export { CacheArgError, formatMetadataCache, inspectMetadataCache, parseCacheArgs, toCacheJson, } from "./cache.js";
4
+ export { DEFAULT_NATIVE_PRICE_ID, DEFAULT_PRICE_BASE_URL, DEFAULT_PRICE_TTL_MS, PriceSource, priceChainSlug, usdValue, } from "./prices.js";
5
+ export { HISTORY_CSV_HEADER, HistoryArgError, extractBalanceHistory, formatHistory, historyCsv, historyWideCsv, parseHistoryArgs, readHistoryLog, saveHistoryCsv, toHistoryJson, } from "./history.js";
6
+ export { DEFAULT_PLOT_WIDTH, PlotArgError, buildPlotReport, formatPlot, parseCsv, parsePlotArgs, readHistoryCsv, sparkline, toPlotJson, } from "./plot.js";
7
+ export { SESSIONS_CSV_HEADER, SessionsArgError, formatSessions, parseSessionsArgs, readWatchLog, saveSessionsCsv, sessionsCsv, summarizeWatchLog, toSessionsJson, } from "./sessions.js";
8
+ export { TokenFileArgError, buildTokenListJson, parseTokenFileArgs, saveTokenList, tokenListItems, } from "./tokenfile.js";
9
+ export { ViewsArgError, formatViews, listViewFunctions, parseViewsArgs, runViewFunctions, toViewsJson, } from "./views.js";
10
+ export { ScanArgError, csvCell, formatScan, parseScanArgs, scanBalances, scanCsv, toScanJson, } from "./scan.js";
11
+ export { CompareArgError, compareEndpoints, endpointAuthEnvName, formatCompare, parseCompareArgs, summarizeLatency, toCompareJson, } from "./compare.js";
12
+ export { COMPARE_HISTORY_CSV_HEADER, CompareHistoryArgError, compareHistoryCsv, formatCompareHistory, parseCompareHistoryArgs, saveCompareHistoryCsv, readCompareLog, summarizeCompareLog, toCompareHistoryJson, } from "./compare-history.js";
13
+ export { CHAIN_PRESETS, ChainConfigError, ChainMismatchError, buildClient, chainPresetNames, connectionWarnings, formatHealth, parseChainConfig, probe, probeWithFallback, publicEndpoint, resolveChainPreset, toJsonPayload, } from "./chain.js";
14
+ export { ContractCallError, MULTICALL3_ADDRESS, assertContractChain, createReader, formatExampleCall, multicall3Abi, multicall3BatchAbi, readExampleCall, readTyped, toExampleCallJson, } from "./contract.js";
15
+ export { CallArgError, callViewFunction, coerceArgs, extractAbi, findFunction, formatCall, formatValue, loadAbi, parseCallArgs, resolveAbi, signatureOf, toCallJson, } from "./call.js";
16
+ export { SignaturesArgError, callableSignatures, formatSignatures, parseSignaturesArgs, toSignaturesJson, } from "./signatures.js";
17
+ export { MarketArgError, MarketError, MarketPool, fixedPrice, formatLiquidity, formatMarketAmount, formatMarketState, formatSwapQuote, humanPrice, loadPoolFile, parseMarketAmount, parseMarketArgs, parseShares, poolFromJson, rationalToDecimal, savePoolFile, toMarketJson, toSwapQuoteJson, } from "./market.js";
18
+ export { MarketApiError, MarketServer } from "./market-server.js";
19
+ export { DEFAULT_ABI_LABEL, DEFAULT_ERC20_ABI } from "./default-abi.js";
20
+ export { AlertStore, DEFAULT_ALERT_QUEUE_PATH, resolveAlertQueuePath, } from "./alert-store.js";
21
+ export { DEFAULT_WATCH_OPTIONS, WatchArgError, createTracker, describeBalanceChange, evaluateSample, formatWatchEvent, formatWatchSummary, parseLogSize, parseWatchArgs, toWatchEventJson, toWatchSummaryJson, watchChain, } from "./watch.js";
22
+ export { RotatingLog } from "./rotate.js";
23
+ export { AlertQueue } from "./alert-queue.js";
24
+ export { postWebhook } from "./webhook.js";
25
+ export { DEFAULT_PROBE_TIMEOUT_MS, PresetsArgError, formatPresets, parsePresetsArgs, probePresets, toPresetsJson, } from "./presets.js";
26
+ export { escapeAnnotation, summarizePresets } from "./presets-summary.js";
27
+ export { PresetsHistoryArgError, appendPresetHistory, comparePresetHistory, formatPresetHistory, formatPresetHistoryCheck, formatPresetHistoryComparison, mergePresetHistory, parsePresetHistoryArgs, parseFreshnessRows, parseMaxAgeMs, parseRetentionMs, presetFreshnessMarkdown, presetHistoryCheck, presetHistoryCsv, presetHistoryFailureReasons, presetHistoryTimelineCsv, presetRegressionWarnings, presetsBelowUptime, narrowestRetentionMs, providerRegressionWarnings, readPresetHistoryFile, retentionWindowWarnings, readPresetHistory, savePresetHistory, savePresetHistoryCsv, selectPresetHistory, summarizePresetGate, summarizePresetHistory, summarizeProviderHistory, toPresetHistoryJson, } from "./presets-history.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The minimal swap page served at `GET /` by `lwc market --serve`.
3
+ *
4
+ * It is one self-contained document: inline CSS, inline JavaScript, no external
5
+ * assets, no build step. Every value it renders comes from the market API as
6
+ * text via `textContent`, never as HTML.
7
+ */
8
+ export declare const MARKET_PAGE_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Swap</title>\n<style>\n :root {\n color-scheme: light dark;\n --bg: #f6f7f9;\n --panel: #ffffff;\n --line: #dfe3e8;\n --text: #10151c;\n --muted: #5d6b7a;\n --accent: #0a7a55;\n --accent-text: #ffffff;\n --good: #0a7a55;\n --bad: #b3261e;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #0a0e13;\n --panel: #121821;\n --line: #222d3a;\n --text: #e6edf3;\n --muted: #8b9bab;\n --accent: #2fbf8b;\n --accent-text: #05100c;\n --good: #37d399;\n --bad: #ff8b82;\n }\n }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n padding: 32px 20px 56px;\n background: var(--bg);\n color: var(--text);\n font: 15px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n }\n main { width: 100%; max-width: 480px; margin: 0 auto; }\n h1 { margin: 0 0 4px; font-size: 20px; letter-spacing: -0.01em; }\n h2 {\n margin: 0 0 10px; font-size: 12px; font-weight: 600; color: var(--muted);\n text-transform: uppercase; letter-spacing: 0.08em;\n }\n .sub { margin: 0 0 22px; color: var(--muted); font-size: 13px; min-height: 20px; }\n .card { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 18px; }\n .card + .card { margin-top: 16px; }\n .tabs { display: flex; gap: 6px; margin-bottom: 16px; }\n .tabs button {\n flex: 1; padding: 9px 12px; border-radius: 9px; border: 1px solid var(--line);\n background: transparent; color: var(--muted); font: inherit; font-weight: 600; cursor: pointer;\n }\n .tabs button[aria-pressed=\"true\"] { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }\n label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }\n input {\n width: 100%; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--line);\n background: var(--bg); color: var(--text);\n font: 600 18px/1.2 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n }\n input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n input::placeholder { color: var(--muted); opacity: 1; }\n .out {\n margin: 12px 0 0; font: 600 16px/1.3 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n }\n .note { margin: 8px 0 0; min-height: 20px; font-size: 13px; color: var(--muted); }\n .note[data-tone=\"good\"] { color: var(--good); }\n .note[data-tone=\"bad\"] { color: var(--bad); }\n button.primary {\n width: 100%; margin-top: 14px; padding: 12px 16px; border: 0; border-radius: 10px;\n background: var(--accent); color: var(--accent-text); font: inherit; font-weight: 700; cursor: pointer;\n }\n button.primary:disabled {\n background: var(--line); color: var(--muted); cursor: not-allowed;\n }\n dl { margin: 0; font-size: 13px; }\n .row {\n display: flex; flex-wrap: wrap; align-items: baseline;\n justify-content: space-between; gap: 0 12px;\n }\n .row + .row { margin-top: 6px; }\n dt { color: var(--muted); }\n dd {\n margin: 0 0 0 auto; text-align: right;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n }\n ol { margin: 0; padding: 0; list-style: none; font-size: 13px; }\n li { padding: 7px 0; border-top: 1px solid var(--line); display: flex; justify-content: space-between; gap: 12px; }\n li:first-child { border-top: 0; }\n li time { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }\n li span { font-variant-numeric: tabular-nums; }\n .empty { color: var(--muted); }\n .amount-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }\n .amount-head label { margin-bottom: 6px; }\n button.linkish {\n padding: 0; border: 0; background: transparent; color: var(--accent);\n font: inherit; font-weight: 600; cursor: pointer;\n }\n button.linkish:disabled { color: var(--muted); cursor: not-allowed; }\n #liquidity-note { color: var(--bad); }\n #liquidity-note[hidden] { display: none; }\n .cover {\n margin: 0 0 16px; padding: 10px 12px; border: 1px solid var(--bad);\n border-radius: 10px; color: var(--bad); font-size: 13px;\n }\n .cover[hidden] { display: none; }\n</style>\n</head>\n<body>\n<main>\n <h1>Swap</h1>\n <p class=\"sub\" id=\"sub\">loading the pool\u2026</p>\n <p class=\"cover\" id=\"cover-warning\" role=\"status\" aria-live=\"polite\" hidden></p>\n\n <section class=\"card\" id=\"unlock\" hidden>\n <h2>Access key</h2>\n <label for=\"key\">This market asks for a key before it will quote or swap.</label>\n <input id=\"key\" type=\"password\" autocomplete=\"off\" spellcheck=\"false\" placeholder=\"access key\" />\n <button class=\"primary\" id=\"unlock-go\" type=\"button\">Unlock</button>\n <p class=\"note\" id=\"unlock-note\" role=\"status\" aria-live=\"polite\"></p>\n </section>\n\n <form class=\"card\" id=\"form\">\n <div class=\"tabs\" role=\"group\" aria-label=\"Side\">\n <button type=\"button\" id=\"buy\" aria-pressed=\"true\">Buy <span id=\"buy-sym\">base</span></button>\n <button type=\"button\" id=\"sell\" aria-pressed=\"false\">Sell <span id=\"sell-sym\">base</span></button>\n </div>\n <div class=\"amount-head\">\n <label for=\"amount\">You pay (<span id=\"pay-sym\">quote</span>)</label>\n <button type=\"button\" class=\"linkish\" id=\"max\" disabled>Max</button>\n </div>\n <input id=\"amount\" inputmode=\"decimal\" autocomplete=\"off\" spellcheck=\"false\" placeholder=\"0.0\" />\n <p class=\"out\" id=\"quote-out\">You receive \u2014</p>\n <p class=\"note\" id=\"quote-note\" role=\"status\" aria-live=\"polite\"></p>\n <p class=\"note\" id=\"liquidity-note\" role=\"status\" aria-live=\"polite\" hidden></p>\n <button class=\"primary\" id=\"swap\" type=\"submit\" disabled>Swap</button>\n </form>\n\n <section class=\"card\" aria-labelledby=\"pool-h\">\n <h2 id=\"pool-h\">Pool</h2>\n <dl>\n <div class=\"row\"><dt>Price</dt><dd id=\"price\">\u2014</dd></div>\n <div class=\"row\"><dt>Net flow</dt><dd id=\"net-flow\">\u2014</dd></div>\n <div class=\"row\"><dt>Backing</dt><dd id=\"backing\">\u2014</dd></div>\n <div class=\"row\"><dt>Liquidity</dt><dd id=\"liquidity\">\u2014</dd></div>\n <div class=\"row\"><dt>Reserves</dt><dd id=\"reserves\">\u2014</dd></div>\n <div class=\"row\"><dt>Volume</dt><dd id=\"volume\">\u2014</dd></div>\n <div class=\"row\"><dt>Turnover</dt><dd id=\"turnover\">\u2014</dd></div>\n <div class=\"row\"><dt>One trade up to</dt><dd id=\"depth\">\u2014</dd></div>\n <div class=\"row\"><dt>After this trade</dt><dd id=\"depth-after\">\u2014</dd></div>\n </dl>\n </section>\n\n <section class=\"card\" aria-labelledby=\"fills-h\">\n <h2 id=\"fills-h\">Fills</h2>\n <ol id=\"fills\"><li class=\"empty\" id=\"no-fills\">Nothing yet.</li></ol>\n </section>\n</main>\n<script>\n(function () {\n var $ = function (id) { return document.getElementById(id); };\n var base = (new URLSearchParams(location.search).get(\"api\") || \"\").replace(/\\/+$/, \"\");\n var state = null;\n var side = \"buy\";\n var quote = null;\n var timer = null;\n var seq = 0;\n var KEY_STORE = \"lwc-market-key\";\n var key = \"\";\n try {\n key = new URLSearchParams(location.search).get(\"key\") || sessionStorage.getItem(KEY_STORE) || \"\";\n } catch (error) {\n key = \"\";\n }\n\n function authHeaders(extra) {\n var headers = extra || {};\n if (key) headers[\"x-market-key\"] = key;\n return headers;\n }\n\n /** Keep a working key for this tab only, and take it out of the address bar. */\n function rememberKey(value) {\n key = value;\n try { sessionStorage.setItem(KEY_STORE, value); } catch (error) {}\n var url;\n try { url = new URL(location.href); } catch (error) { return; }\n if (url.searchParams.has(\"key\")) {\n url.searchParams.delete(\"key\");\n history.replaceState(null, \"\", url.toString());\n }\n }\n\n function askForKey(message) {\n $(\"unlock\").hidden = false;\n $(\"unlock-note\").textContent = message || \"\";\n $(\"key\").focus();\n }\n\n function unlock() {\n var typed = $(\"key\").value.trim();\n if (!typed) {\n $(\"unlock-note\").textContent = \"enter the key the api was started with\";\n return;\n }\n rememberKey(typed);\n $(\"unlock\").hidden = true;\n $(\"sub\").textContent = \"checking the key\u2026\";\n loadState().then(function (ok) { if (ok) setSide(\"buy\"); });\n }\n\n function fmt(raw, decimals) {\n var negative = raw.charAt(0) === \"-\";\n var digits = negative ? raw.slice(1) : raw;\n while (digits.length <= decimals) digits = \"0\" + digits;\n var cut = digits.length - decimals;\n var whole = digits.slice(0, cut);\n var fraction = digits.slice(cut).replace(/0+$/, \"\");\n return (negative ? \"-\" : \"\") + whole + (fraction ? \".\" + fraction : \"\");\n }\n\n /** A flow with its direction made explicit, and no sign noise on zero. */\n function signed(raw, decimals) {\n var text = fmt(raw, decimals);\n if (Number(text) === 0) return text;\n return text.charAt(0) === \"-\" ? text : \"+\" + text;\n }\n\n function setNote(text, tone) {\n var note = $(\"quote-note\");\n note.textContent = text;\n if (tone) note.setAttribute(\"data-tone\", tone);\n else note.removeAttribute(\"data-tone\");\n }\n\n function setLiquidity(text) {\n var note = $(\"liquidity-note\");\n note.textContent = text || \"\";\n if (note.textContent) note.removeAttribute(\"hidden\");\n else note.setAttribute(\"hidden\", \"\");\n }\n\n function setDepthAfter(text) {\n $(\"depth-after\").textContent = text || \"\u2014\";\n }\n\n function setCover(text) {\n var warning = $(\"cover-warning\");\n warning.textContent = text || \"\";\n if (warning.textContent) warning.removeAttribute(\"hidden\");\n else warning.setAttribute(\"hidden\", \"\");\n }\n\n /** A decimal string that stays one unit below the target, so it cannot be refused. */\n function decimalFrom(value, decimals) {\n if (!isFinite(value) || value <= 0) return \"\";\n var units = Math.floor(value * Math.pow(10, decimals));\n if (units > 1) units -= 1;\n if (units <= 0) return \"\";\n return fmt(String(units), decimals);\n }\n\n /**\n * The most this side can pay right now. A buy is limited by the base reserve,\n * so its quote cost is the reserve at the fixed price plus the fee; a sell is\n * limited by the quote reserve, which the API already reports as maxSellBase.\n */\n function maxInput(capacity) {\n capacity = capacity || (state && state.capacity);\n if (!capacity) return \"\";\n if (side === \"sell\") {\n var sell = fmt(capacity.maxSellBase, state.base.decimals);\n return Number(sell) > 0 ? sell : \"\";\n }\n\n var base = Number(fmt(capacity.maxBuyBase, state.base.decimals));\n var price = Number(state.price && state.price.value);\n var fee = Number(state.feeBps || 0) / 10000;\n if (base > 0 && isFinite(price) && price > 0 && fee < 1) {\n return decimalFrom((base * price) / (1 - fee), state.quote.decimals);\n }\n var fallback = fmt(capacity.payQuote, state.quote.decimals);\n return Number(fallback) > 0 ? fallback : \"\";\n }\n\n function updateMax() {\n var button = $(\"max\");\n var value = maxInput();\n button.disabled = value === \"\";\n button.title = value\n ? \"fill \" + value + \" \" + (side === \"buy\" ? state.quote.symbol : state.base.symbol)\n : \"the pool has no depth on this side\";\n }\n\n function renderState() {\n if (!state) return;\n var q = state.quote;\n var b = state.base;\n $(\"sub\").textContent = \"Fixed price, no price impact. Every quote is answered.\";\n $(\"price\").textContent = state.price.value + \" \" + q.symbol + \" per \" + b.symbol;\n $(\"liquidity\").textContent = fmt(state.liquidity.valueQuote, q.decimals) + \" \" + q.symbol;\n $(\"reserves\").textContent =\n fmt(q.reserve, q.decimals) + \" \" + q.symbol + \" + \" + fmt(b.reserve, b.decimals) + \" \" + b.symbol;\n $(\"volume\").textContent =\n fmt(state.volume.quote, q.decimals) + \" \" + q.symbol + \" in \" + state.volume.swaps + \" swaps\";\n $(\"turnover\").textContent =\n Number(state.volume.turnover).toLocaleString(undefined, { maximumFractionDigits: 2 }) + \"\u00D7 liquidity\";\n var net = state.netFlow;\n $(\"net-flow\").textContent = net\n ? signed(net.base, b.decimals) + \" \" + b.symbol + \" \u00B7 \" +\n signed(net.quote, q.decimals) + \" \" + q.symbol\n : \"\u2014\";\n\n var capacity = state.capacity;\n $(\"depth\").textContent = capacity\n ? fmt(capacity.maxBuyBase, b.decimals) + \" \" + b.symbol + \" / \" +\n fmt(capacity.payQuote, q.decimals) + \" \" + q.symbol\n : \"\u2014\";\n $(\"backing\").textContent = capacity\n ? fmt(q.reserve, q.decimals) + \" \" + q.symbol + \" \u00B7 covers \" +\n fmt(capacity.maxSellBase, b.decimals) + \" \" + b.symbol + \" of sells\"\n : \"\u2014\";\n\n // Base the pool has sold into circulation has to be bought back at this\n // price, and only the quote reserve can pay for it.\n var issued = net && net.base.charAt(0) === \"-\" ? -Number(fmt(net.base, b.decimals)) : 0;\n var cover = capacity ? Number(fmt(capacity.maxSellBase, b.decimals)) : 0;\n if (capacity && issued > cover) {\n setCover(\n \"net flow: the pool sold \" + fmt(net.base, b.decimals).slice(1) + \" \" + b.symbol +\n \" but can buy back only \" + fmt(capacity.maxSellBase, b.decimals) + \" \" + b.symbol +\n \" at \" + state.price.value + \" \" + q.symbol + \" \u2014 larger sells are refused\"\n );\n } else {\n setCover(\"\");\n }\n $(\"buy-sym\").textContent = b.symbol;\n $(\"sell-sym\").textContent = b.symbol;\n $(\"pay-sym\").textContent = side === \"buy\" ? q.symbol : b.symbol;\n updateMax();\n }\n\n function refreshQuote() {\n var amount = $(\"amount\").value.trim();\n var mine = ++seq;\n quote = null;\n $(\"swap\").disabled = true;\n\n if (!state || !/^\\d+(\\.\\d+)?$/.test(amount) || Number(amount) <= 0) {\n $(\"quote-out\").textContent = \"You receive \u2014\";\n setNote(\"\", null);\n setLiquidity(\"\");\n setDepthAfter(\"\");\n return;\n }\n\n setNote(\"quoting\u2026\", null);\n fetch(base + \"/quote?side=\" + side + \"&amount=\" + encodeURIComponent(amount), {\n cache: \"no-store\",\n headers: authHeaders()\n })\n .then(function (response) {\n return response.json().then(function (body) {\n return { ok: response.ok, status: response.status, body: body };\n });\n })\n .then(function (result) {\n if (mine !== seq) return;\n if (result.status === 401) {\n askForKey(\"that key was not accepted\");\n setNote(\"access key required\", \"bad\");\n setLiquidity(\"\");\n setDepthAfter(\"\");\n return;\n }\n if (!result.ok) throw new Error(result.body.error || \"quote failed\");\n var filled = result.body;\n quote = filled;\n if (!filled.executable) {\n $(\"quote-out\").textContent = \"You receive \u2014\";\n setNote(filled.reason || \"this trade cannot be settled right now\", \"bad\");\n setLiquidity(\"\");\n setDepthAfter(\"\");\n return;\n }\n $(\"quote-out\").textContent = \"You receive \" + filled.amountOutFormatted + \" \" + filled.outSymbol;\n setNote(\n \"fee \" + filled.feeFormatted + \" \" + filled.inSymbol + \" \u00B7 \" + filled.price + \" \" +\n state.quote.symbol + \" per \" + state.base.symbol + \" \u00B7 no price impact\",\n null\n );\n var depth = Number(maxInput());\n var used = depth > 0 ? Number(filled.amountInFormatted) / depth : 0;\n var unit = side === \"buy\" ? state.quote.symbol : state.base.symbol;\n var left = filled.capacityAfter ? maxInput(filled.capacityAfter) : \"\";\n setDepthAfter(left ? left + \" \" + unit : \"nothing on this side\");\n var tail = left ? \" \u2014 \" + left + \" \" + unit + \" left after it\" : \" \u2014 nothing left after it\";\n if (used >= 0.995) {\n setLiquidity(\"low liquidity: this fills the pool's whole depth on this side\" + tail);\n } else if (used >= 0.5) {\n setLiquidity(\n \"low liquidity: this trade uses \" + Math.round(used * 100) + \"% of the pool's depth\" + tail\n );\n } else {\n setLiquidity(\"\");\n }\n $(\"swap\").disabled = false;\n })\n .catch(function (error) {\n if (mine !== seq) return;\n setNote(String(error.message || error), \"bad\");\n });\n }\n\n function addFill(fill) {\n var empty = $(\"no-fills\");\n if (empty) empty.remove();\n\n var item = document.createElement(\"li\");\n var text = document.createElement(\"span\");\n text.textContent =\n fill.side + \" \" + fill.amountInFormatted + \" \" + fill.inSymbol +\n \" \u2192 \" + fill.amountOutFormatted + \" \" + fill.outSymbol;\n var when = document.createElement(\"time\");\n when.textContent = new Date().toLocaleTimeString();\n item.appendChild(text);\n item.appendChild(when);\n\n var list = $(\"fills\");\n list.insertBefore(item, list.firstChild);\n while (list.children.length > 20) list.removeChild(list.lastChild);\n }\n\n function loadState() {\n return fetch(base + \"/market\", { cache: \"no-store\", headers: authHeaders() })\n .then(function (response) {\n if (response.status === 401) {\n $(\"sub\").textContent = \"This market asks for an access key.\";\n askForKey(\"\");\n return null;\n }\n if (!response.ok) throw new Error(\"market answered \" + response.status);\n return response.json();\n })\n .then(function (body) {\n if (body === null) return false;\n state = body;\n renderState();\n return true;\n })\n .catch(function (error) {\n $(\"sub\").textContent =\n \"Cannot reach the market API\" + (base ? \" at \" + base : \"\") + \": \" + (error.message || error);\n return false;\n });\n }\n\n function setSide(next) {\n side = next;\n $(\"buy\").setAttribute(\"aria-pressed\", String(next === \"buy\"));\n $(\"sell\").setAttribute(\"aria-pressed\", String(next === \"sell\"));\n $(\"pay-sym\").textContent = state ? (next === \"buy\" ? state.quote.symbol : state.base.symbol) : \"\u2014\";\n $(\"amount\").value = \"\";\n refreshQuote();\n updateMax();\n }\n\n $(\"max\").addEventListener(\"click\", function () {\n var value = maxInput();\n if (!value) return;\n $(\"amount\").value = value;\n refreshQuote();\n });\n\n $(\"buy\").addEventListener(\"click\", function () { setSide(\"buy\"); });\n $(\"sell\").addEventListener(\"click\", function () { setSide(\"sell\"); });\n\n $(\"amount\").addEventListener(\"input\", function () {\n clearTimeout(timer);\n timer = setTimeout(refreshQuote, 200);\n });\n\n $(\"form\").addEventListener(\"submit\", function (event) {\n event.preventDefault();\n if (!quote) return;\n\n var sending = { side: side, amount: $(\"amount\").value.trim() };\n $(\"swap\").disabled = true;\n setNote(\"submitting\u2026\", null);\n\n fetch(base + \"/swap\", {\n method: \"POST\",\n headers: authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(sending)\n })\n .then(function (response) {\n return response.json().then(function (body) {\n return { ok: response.ok, status: response.status, body: body };\n });\n })\n .then(function (result) {\n if (result.status === 401) {\n askForKey(\"that key was not accepted\");\n setNote(\"access key required\", \"bad\");\n return;\n }\n if (!result.ok) throw new Error(result.body.error || \"swap failed\");\n var filled = result.body;\n addFill(filled);\n if (filled.state) state = filled.state;\n setLiquidity(\"\");\n renderState();\n $(\"amount\").value = \"\";\n quote = null;\n setDepthAfter(\"\");\n $(\"quote-out\").textContent = \"You receive \u2014\";\n setNote(\n \"filled \" + filled.amountInFormatted + \" \" + filled.inSymbol +\n \" \u2192 \" + filled.amountOutFormatted + \" \" + filled.outSymbol,\n \"good\"\n );\n })\n .catch(function (error) {\n setNote(String(error.message || error), \"bad\");\n $(\"swap\").disabled = !quote;\n });\n });\n\n $(\"unlock-go\").addEventListener(\"click\", unlock);\n $(\"key\").addEventListener(\"keydown\", function (event) {\n if (event.key === \"Enter\") { event.preventDefault(); unlock(); }\n });\n\n if (key) rememberKey(key);\n loadState().then(function (ok) { if (ok) setSide(\"buy\"); });\n\n setInterval(function () {\n if (document.visibilityState === \"visible\") loadState();\n }, 5000);\n})();\n</script>\n</body>\n</html>\n";