@dcasegr/amm-client 0.38.2 → 0.39.0
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/README.md +10 -3
- package/dist/client.d.ts +59 -11
- package/dist/client.js +64 -28
- package/dist/lib/overlay.d.ts +129 -4
- package/dist/lib/overlay.js +225 -5
- package/dist/market/prices.d.ts +4 -4
- package/dist/market/prices.js +4 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -39,9 +39,16 @@ exchange with another wallet, keeping what was read and quoted.
|
|
|
39
39
|
|
|
40
40
|
The client (each an async function unless noted):
|
|
41
41
|
|
|
42
|
-
- the exchange: `enabledList`, `registeredTokens`, `tokenBaseOf`, `hostedTokens
|
|
43
|
-
`
|
|
42
|
+
- the exchange: `enabledList`, `registeredTokens`, `tokenBaseOf`, `hostedTokens` (the token list
|
|
43
|
+
and ONE `GET /amm/stats`), `watchTokenPools` (the token's pools route as an event stream: the
|
|
44
|
+
whole answer, then each `change` — `swap`, `deploy`, `close`, `rescind` — applied to it, one it
|
|
45
|
+
cannot apply read again once; sync, returns the unsubscribe; 0.39.0: no host-wide stream),
|
|
44
46
|
`claimStatus`, `isRoot`, `claimExchange`, `discoveryBase` (sync), `onDataChange` (sync);
|
|
47
|
+
- history (0.39.0, plain GETs): `trades(tokenId, {from?, to?, limit?, before?, lp?})` →
|
|
48
|
+
`{trades, next}`, `trade(tokenId, txid)` → a `Trade` or null, `candles(tokenId, {tf, from, to})`,
|
|
49
|
+
`stats(tokenId)`, `statsAll({ids?})`, `holders(tokenId, {limit?, skip?})` → `{count, rows}`,
|
|
50
|
+
`positionsAll(lpKeys)` (every enabled token's positions); amounts bigints, prices `{sats,
|
|
51
|
+
tokens}` pairs of bigints (base units both);
|
|
45
52
|
- swap: `quoteSwap(request, poolsAnswer, {sym, dec})` (sync: the engine's plan over the pools the
|
|
46
53
|
token's pools stream gave), `executeSwap(quote, {onProgress})`, `checkSwapAgain(leg)`,
|
|
47
54
|
`walletSats`;
|
|
@@ -54,7 +61,7 @@ The client (each an async function unless noted):
|
|
|
54
61
|
`setDefaultTerms`, `clearDefaultTerms`, `setDefaultMarket`, `clearDefaultMarket`, `tokenTerms`,
|
|
55
62
|
`setTokenTerms`, `discoveryState`, `setDiscovery`.
|
|
56
63
|
|
|
57
|
-
Pure helpers beside it: `hostedFrom`, `poolViews`, `poolFees`, `withLeg`, `hasTerms`,
|
|
64
|
+
Pure helpers beside it: `hostedFrom`, `applyPoolChange`, `parseChange`, `poolViews`, `poolFees`, `withLeg`, `hasTerms`,
|
|
58
65
|
`enableCandidates`, `topicOf`, `lookupOf`, `ammLookupOf`, `walletDeployBeef`, `exchangeBase`,
|
|
59
66
|
`tokenBase`; the types (`Quote`, `SwapResult`, `LegResult`, `Position`, `WalletToken`, …).
|
|
60
67
|
|
package/dist/client.d.ts
CHANGED
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
*
|
|
12
12
|
* - **The token list** (`GET <swap>/tokens`): read ONCE per client (D4; `enabledList`), again
|
|
13
13
|
* only after its own enable or disable changed it.
|
|
14
|
-
* - **Prices** (
|
|
15
|
-
* `
|
|
14
|
+
* - **Prices** (0.39.0, D3, D7): ONE read of the host-wide stats `GET <origin>/amm/stats`
|
|
15
|
+
* (`hostedTokens`): each token's price, liquidity (TVL) and pool count; no host-wide stream.
|
|
16
|
+
* - **History** (0.39.0, D1–D6): `trades`, `trade`, `candles`, `stats`, `statsAll`, `holders`,
|
|
17
|
+
* `positionsAll` — plain GETs of the history routes (H1–H6, src/lib/overlay.ts).
|
|
16
18
|
* - **Swap** (D11–D16): the token's pools route `GET /@<token>/amm/pools` held as an event stream
|
|
17
|
-
* (`watchTokenPools
|
|
19
|
+
* (`watchTokenPools`; 0.39.0, D7: the whole answer once, then one `change` per pool change,
|
|
20
|
+
* applied to the held answer): the pools whose validator is beating and whose commission meets the
|
|
18
21
|
* market's minimum (the host's filter), the market's commission key hash; a quote is the
|
|
19
22
|
* matching engine's plan over that list (no request); a swap reads ONE `GET
|
|
20
23
|
* /@<token>/amm/pools/beef?lp=` for its legs (freshness, the pools' BEEF), then each leg's relay
|
|
@@ -40,7 +43,7 @@
|
|
|
40
43
|
import { type WalletInterface } from "@bsv/sdk";
|
|
41
44
|
import { type Send1ColorResult } from "@1sat/actions";
|
|
42
45
|
import { type PoolState, type Plan } from "@dcasegr/amm-engine";
|
|
43
|
-
import { type LiveAnswer, type PoolRow, type PoolsAnswer, type SignedFetch } from "./lib/overlay.js";
|
|
46
|
+
import { type Candle, type CandleTf, type Holders, type TokenPosition, type TokenStats, type Trade, type TradesPage, type LiveAnswer, type PoolRow, type PoolsAnswer, type SignedFetch } from "./lib/overlay.js";
|
|
44
47
|
import { type Defaults, type Discovery, type EnabledToken, type ListedToken } from "./lib/skein.js";
|
|
45
48
|
import type { AuthFetchLike } from "./market/relay.js";
|
|
46
49
|
import { type PreparedSwap } from "./market/swapAction.js";
|
|
@@ -262,7 +265,9 @@ export interface RegisteredToken {
|
|
|
262
265
|
icon?: string;
|
|
263
266
|
status: EnabledToken["status"];
|
|
264
267
|
}
|
|
265
|
-
export type { EnabledToken, Defaults, Discovery, PoolRow, PoolsAnswer };
|
|
268
|
+
export type { EnabledToken, Defaults, Discovery, PoolRow, PoolsAnswer, Candle, CandleTf, Holders, TokenPosition, TokenStats, Trade, TradesPage };
|
|
269
|
+
export type { PoolChange, Price } from "./lib/overlay.js";
|
|
270
|
+
export { applyPoolChange, parseChange } from "./lib/overlay.js";
|
|
266
271
|
/** A token's symbol, decimals and icon, read off its deploy output. */
|
|
267
272
|
export interface DeployMeta {
|
|
268
273
|
sym?: string;
|
|
@@ -287,11 +292,12 @@ export interface ClaimStatus {
|
|
|
287
292
|
claimant?: string;
|
|
288
293
|
}
|
|
289
294
|
/**
|
|
290
|
-
* The hosted tokens (the enabled 1Color tokens at output 0), each
|
|
291
|
-
*
|
|
292
|
-
* `
|
|
295
|
+
* The hosted tokens (the enabled 1Color tokens at output 0), each with its record of the host-wide
|
|
296
|
+
* stats (0.39.0, D3, D7: H4): the price (`price`, sats per whole token), the liquidity (`tvlSats`,
|
|
297
|
+
* `tvlTokens`) and the pool count. `stats` null: not read yet (no prices); a token the stats lack:
|
|
298
|
+
* no price, no pools. Pure.
|
|
293
299
|
*/
|
|
294
|
-
export declare function hostedFrom(list: EnabledToken[],
|
|
300
|
+
export declare function hostedFrom(list: EnabledToken[], stats: TokenStats[] | null): HostedToken[];
|
|
295
301
|
/** The pools the Swap page lists (D11, D13): the token's pools route's rows, every validator live (the host lists no other). */
|
|
296
302
|
export declare function poolViews(answer: PoolsAnswer | null, tokenId: TokenId): PoolView[];
|
|
297
303
|
/** The fees a pool's own record names (D13: the Swap page's fee line), basis points. */
|
|
@@ -444,8 +450,29 @@ declare function makeClient(opts: ClientOptions, s: Shared): {
|
|
|
444
450
|
isRoot: (identityKey: string | null) => Promise<boolean>;
|
|
445
451
|
claimExchange: () => Promise<boolean>;
|
|
446
452
|
hostedTokens: () => Promise<HostedToken[]>;
|
|
447
|
-
watchHostedPools: (onRows: (rows: PoolRow[]) => void, onLive?: (live: boolean) => void) => () => void;
|
|
448
453
|
watchTokenPools: (tokenId: TokenId, onAnswer: (a: PoolsAnswer) => void, onError?: (e: unknown) => void) => () => void;
|
|
454
|
+
trades: (tokenId: string, o?: {
|
|
455
|
+
from?: number;
|
|
456
|
+
to?: number;
|
|
457
|
+
limit?: number;
|
|
458
|
+
before?: string;
|
|
459
|
+
lp?: string;
|
|
460
|
+
} | undefined) => Promise<TradesPage>;
|
|
461
|
+
trade: (tokenId: string, txid: string) => Promise<Trade | null>;
|
|
462
|
+
candles: (tokenId: string, o: {
|
|
463
|
+
tf: CandleTf;
|
|
464
|
+
from: number;
|
|
465
|
+
to: number;
|
|
466
|
+
}) => Promise<Candle[]>;
|
|
467
|
+
stats: (tokenId: string) => Promise<TokenStats>;
|
|
468
|
+
statsAll: (o?: {
|
|
469
|
+
ids?: TokenId[];
|
|
470
|
+
} | undefined) => Promise<TokenStats[]>;
|
|
471
|
+
holders: (tokenId: string, o?: {
|
|
472
|
+
limit?: number;
|
|
473
|
+
skip?: number;
|
|
474
|
+
} | undefined) => Promise<Holders>;
|
|
475
|
+
positionsAll: (lpKeys: string[]) => Promise<TokenPosition[]>;
|
|
449
476
|
registeredTokens: () => Promise<RegisteredToken[]>;
|
|
450
477
|
quoteSwap: (req: QuoteRequest, held: PoolsAnswer | null, meta: {
|
|
451
478
|
sym: string;
|
|
@@ -493,8 +520,29 @@ declare function makeClient(opts: ClientOptions, s: Shared): {
|
|
|
493
520
|
isRoot: (identityKey: string | null) => Promise<boolean>;
|
|
494
521
|
claimExchange: () => Promise<boolean>;
|
|
495
522
|
hostedTokens: () => Promise<HostedToken[]>;
|
|
496
|
-
watchHostedPools: (onRows: (rows: PoolRow[]) => void, onLive?: (live: boolean) => void) => () => void;
|
|
497
523
|
watchTokenPools: (tokenId: TokenId, onAnswer: (a: PoolsAnswer) => void, onError?: (e: unknown) => void) => () => void;
|
|
524
|
+
trades: (tokenId: string, o?: {
|
|
525
|
+
from?: number;
|
|
526
|
+
to?: number;
|
|
527
|
+
limit?: number;
|
|
528
|
+
before?: string;
|
|
529
|
+
lp?: string;
|
|
530
|
+
} | undefined) => Promise<TradesPage>;
|
|
531
|
+
trade: (tokenId: string, txid: string) => Promise<Trade | null>;
|
|
532
|
+
candles: (tokenId: string, o: {
|
|
533
|
+
tf: CandleTf;
|
|
534
|
+
from: number;
|
|
535
|
+
to: number;
|
|
536
|
+
}) => Promise<Candle[]>;
|
|
537
|
+
stats: (tokenId: string) => Promise<TokenStats>;
|
|
538
|
+
statsAll: (o?: {
|
|
539
|
+
ids?: TokenId[];
|
|
540
|
+
} | undefined) => Promise<TokenStats[]>;
|
|
541
|
+
holders: (tokenId: string, o?: {
|
|
542
|
+
limit?: number;
|
|
543
|
+
skip?: number;
|
|
544
|
+
} | undefined) => Promise<Holders>;
|
|
545
|
+
positionsAll: (lpKeys: string[]) => Promise<TokenPosition[]>;
|
|
498
546
|
registeredTokens: () => Promise<RegisteredToken[]>;
|
|
499
547
|
quoteSwap: (req: QuoteRequest, held: PoolsAnswer | null, meta: {
|
|
500
548
|
sym: string;
|
package/dist/client.js
CHANGED
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
*
|
|
12
12
|
* - **The token list** (`GET <swap>/tokens`): read ONCE per client (D4; `enabledList`), again
|
|
13
13
|
* only after its own enable or disable changed it.
|
|
14
|
-
* - **Prices** (
|
|
15
|
-
* `
|
|
14
|
+
* - **Prices** (0.39.0, D3, D7): ONE read of the host-wide stats `GET <origin>/amm/stats`
|
|
15
|
+
* (`hostedTokens`): each token's price, liquidity (TVL) and pool count; no host-wide stream.
|
|
16
|
+
* - **History** (0.39.0, D1–D6): `trades`, `trade`, `candles`, `stats`, `statsAll`, `holders`,
|
|
17
|
+
* `positionsAll` — plain GETs of the history routes (H1–H6, src/lib/overlay.ts).
|
|
16
18
|
* - **Swap** (D11–D16): the token's pools route `GET /@<token>/amm/pools` held as an event stream
|
|
17
|
-
* (`watchTokenPools
|
|
19
|
+
* (`watchTokenPools`; 0.39.0, D7: the whole answer once, then one `change` per pool change,
|
|
20
|
+
* applied to the held answer): the pools whose validator is beating and whose commission meets the
|
|
18
21
|
* market's minimum (the host's filter), the market's commission key hash; a quote is the
|
|
19
22
|
* matching engine's plan over that list (no request); a swap reads ONE `GET
|
|
20
23
|
* /@<token>/amm/pools/beef?lp=` for its legs (freshness, the pools' BEEF), then each leg's relay
|
|
@@ -42,7 +45,7 @@ import { send1Color } from "@1sat/actions";
|
|
|
42
45
|
import { computeSwap } from "@dcasegr/amm-engine";
|
|
43
46
|
import { outpointText, parseOutpoint, parseTokenId, sdkTokenId, tokenIdText } from "./lib/tokenId.js";
|
|
44
47
|
import { DEFAULT_FEE_RATE_SATS_PER_KB, appName, exchangeBase, tokenBase } from "./lib/config.js";
|
|
45
|
-
import { ammLookupService,
|
|
48
|
+
import { ammLookupService, applyPoolChange, liveOfPools, parsePools, parseTokenTopic, poolOutputOf, poolsUrl, readDiscoveryDeploy, readDiscoveryIds, readDiscoveryList, readAllPositions, readAllStats, readCandles, readHolders, readPoolsBeef, readPools, readPositions, readStats, readTerms, readTrade, readTrades, readTx, watchPools, } from "./lib/overlay.js";
|
|
46
49
|
import { authFetchPerInstance, instanceBase, readDefaults, readDiscovery, readTokens, sendMessage } from "./lib/skein.js";
|
|
47
50
|
import { depthOf } from "./market/prices.js";
|
|
48
51
|
import { buildPlanRequest, goneFromLookup, quote as planQuote } from "./market/plan.js";
|
|
@@ -72,6 +75,7 @@ export const NO_TERMS_TEXT = "This exchange validates nothing: no terms are set.
|
|
|
72
75
|
export function hasTerms(t) {
|
|
73
76
|
return !!t && !("none" in t);
|
|
74
77
|
}
|
|
78
|
+
export { applyPoolChange, parseChange } from "./lib/overlay.js";
|
|
75
79
|
/** The metadata of the deploy output `vout` of an Atomic BEEF's subject; null when that output is no 1Color deploy. */
|
|
76
80
|
function deployMetaOf(beef, vout) {
|
|
77
81
|
const tx = Transaction.fromAtomicBEEF(beef);
|
|
@@ -85,11 +89,12 @@ function symOf(meta, tokenId) {
|
|
|
85
89
|
return meta.get(tokenId)?.sym ?? tokenId.slice(0, 8);
|
|
86
90
|
}
|
|
87
91
|
/**
|
|
88
|
-
* The hosted tokens (the enabled 1Color tokens at output 0), each
|
|
89
|
-
*
|
|
90
|
-
* `
|
|
92
|
+
* The hosted tokens (the enabled 1Color tokens at output 0), each with its record of the host-wide
|
|
93
|
+
* stats (0.39.0, D3, D7: H4): the price (`price`, sats per whole token), the liquidity (`tvlSats`,
|
|
94
|
+
* `tvlTokens`) and the pool count. `stats` null: not read yet (no prices); a token the stats lack:
|
|
95
|
+
* no price, no pools. Pure.
|
|
91
96
|
*/
|
|
92
|
-
export function hostedFrom(list,
|
|
97
|
+
export function hostedFrom(list, stats) {
|
|
93
98
|
const out = [];
|
|
94
99
|
for (const t of list) {
|
|
95
100
|
if (t.status !== "enabled")
|
|
@@ -97,17 +102,18 @@ export function hostedFrom(list, rows) {
|
|
|
97
102
|
const topic = parseTokenTopic(t.topic);
|
|
98
103
|
if (!topic || topic.kind !== "native")
|
|
99
104
|
continue;
|
|
100
|
-
const
|
|
101
|
-
const sats =
|
|
102
|
-
const tokens =
|
|
105
|
+
const st = (stats ?? []).find((r) => canonicalId(r.tokenId) === t.tokenId);
|
|
106
|
+
const sats = st?.tvlSats ?? 0n;
|
|
107
|
+
const tokens = st?.tvlTokens ?? 0n;
|
|
108
|
+
const price = st?.price ?? null;
|
|
103
109
|
out.push({
|
|
104
110
|
tokenId: t.tokenId,
|
|
105
111
|
sym: t.sym || t.tokenId.slice(0, 8),
|
|
106
112
|
dec: t.dec,
|
|
107
113
|
...(t.icon ? { icon: t.icon } : {}),
|
|
108
|
-
marginalPrice: tokens > 0n ? Number(sats) / (Number(tokens) / 10 ** t.dec) : null,
|
|
114
|
+
marginalPrice: price && price.tokens > 0n ? Number(price.sats) / (Number(price.tokens) / 10 ** t.dec) : null,
|
|
109
115
|
depth: depthOf(sats),
|
|
110
|
-
pools:
|
|
116
|
+
pools: st?.pools ?? 0,
|
|
111
117
|
reserves: { sats, tokens },
|
|
112
118
|
});
|
|
113
119
|
}
|
|
@@ -459,24 +465,47 @@ function makeClient(opts, s) {
|
|
|
459
465
|
throw new Error("The claim was sent, but this wallet is not the admin: someone else may have claimed this exchange first.");
|
|
460
466
|
return root;
|
|
461
467
|
}
|
|
462
|
-
/** The tokens this exchange hosts, priced: the token list (once) and ONE read of the host-wide
|
|
468
|
+
/** The tokens this exchange hosts, priced: the token list (once) and ONE read of the host-wide stats (0.39.0, H4). No wallet needed. */
|
|
463
469
|
async function hostedTokens() {
|
|
464
|
-
const [list,
|
|
465
|
-
return hostedFrom(list,
|
|
470
|
+
const [list, stats] = await Promise.all([enabledList(), statsAll().catch(() => null)]);
|
|
471
|
+
return hostedFrom(list, stats);
|
|
466
472
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
473
|
+
/**
|
|
474
|
+
* A token's pools route held as an event stream (D16; 0.39.0, D7): the whole answer (`pools`)
|
|
475
|
+
* held, each `change` applied to it (`applyPoolChange`), one it cannot apply → the route read once
|
|
476
|
+
* again; each new answer to `onAnswer`. → the unsubscribe.
|
|
477
|
+
*/
|
|
478
|
+
function watchTokenPools(tokenId, onAnswer, onError) {
|
|
479
|
+
return watchPools(poolsUrl(tokenBase(tokenId, swapBase)), parsePools, onAnswer, { ...(onError ? { onError } : {}), fetch: f, change: applyPoolChange });
|
|
472
480
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
481
|
+
// ---------------------------------------------------------------- history (0.39.0, H1–H6)
|
|
482
|
+
/** H1: a token's trades, newest first (`from ≤ at < to` ms, `limit` 1–500, `before` the cursor `next`, `lp` one LP key's). */
|
|
483
|
+
async function trades(tokenId, o = {}) {
|
|
484
|
+
return readTrades(tokenBase(tokenId, swapBase), o, ff);
|
|
476
485
|
}
|
|
477
|
-
/**
|
|
478
|
-
function
|
|
479
|
-
return
|
|
486
|
+
/** H1 with `txid`: one Swap's trade row, null when it has none. */
|
|
487
|
+
async function trade(tokenId, txid) {
|
|
488
|
+
return readTrade(tokenBase(tokenId, swapBase), txid, ff);
|
|
489
|
+
}
|
|
490
|
+
/** H2: a token's candles, one per non-empty bucket of `tf` over `from ≤ at < to` (ms). */
|
|
491
|
+
async function candles(tokenId, o) {
|
|
492
|
+
return readCandles(tokenBase(tokenId, swapBase), o, ff);
|
|
493
|
+
}
|
|
494
|
+
/** H3: a token's stats. */
|
|
495
|
+
async function stats(tokenId) {
|
|
496
|
+
return readStats(tokenBase(tokenId, swapBase), ff);
|
|
497
|
+
}
|
|
498
|
+
/** H4: every enabled token's stats (`ids`: those only, in that order). */
|
|
499
|
+
async function statsAll(o = {}) {
|
|
500
|
+
return readAllStats(swapBase, o.ids, ff);
|
|
501
|
+
}
|
|
502
|
+
/** H6: a token's holders, largest first (`limit` 1–100, `skip`). */
|
|
503
|
+
async function holders(tokenId, o = {}) {
|
|
504
|
+
return readHolders(tokenBase(tokenId, swapBase), tokenId, o, ff);
|
|
505
|
+
}
|
|
506
|
+
/** H5: the LP keys' positions on every enabled token. */
|
|
507
|
+
async function positionsAll(lpKeys) {
|
|
508
|
+
return readAllPositions(swapBase, lpKeys, ff);
|
|
480
509
|
}
|
|
481
510
|
/**
|
|
482
511
|
* A quote for one token (D12): the matching engine's plan over the pools the Swap page holds
|
|
@@ -1282,8 +1311,15 @@ function makeClient(opts, s) {
|
|
|
1282
1311
|
isRoot,
|
|
1283
1312
|
claimExchange: guard(claimExchange),
|
|
1284
1313
|
hostedTokens: guard(hostedTokens),
|
|
1285
|
-
watchHostedPools,
|
|
1286
1314
|
watchTokenPools,
|
|
1315
|
+
// history (0.39.0)
|
|
1316
|
+
trades: guard(trades),
|
|
1317
|
+
trade: guard(trade),
|
|
1318
|
+
candles: guard(candles),
|
|
1319
|
+
stats: guard(stats),
|
|
1320
|
+
statsAll: guard(statsAll),
|
|
1321
|
+
holders: guard(holders),
|
|
1322
|
+
positionsAll: guard(positionsAll),
|
|
1287
1323
|
registeredTokens: guard(registeredTokens),
|
|
1288
1324
|
// swap
|
|
1289
1325
|
quoteSwap,
|
package/dist/lib/overlay.d.ts
CHANGED
|
@@ -100,16 +100,22 @@ export interface EventSourceLike {
|
|
|
100
100
|
}
|
|
101
101
|
export type EventSourceCtor = new (url: string) => EventSourceLike;
|
|
102
102
|
/**
|
|
103
|
-
* Holds a pools route
|
|
104
|
-
* text/event-stream` (the browser's EventSource sends it)
|
|
105
|
-
* first the current one
|
|
106
|
-
*
|
|
103
|
+
* Holds a pools route as an event stream (D16; 0.39.0, D7): `GET <url>` with `Accept:
|
|
104
|
+
* text/event-stream` (the browser's EventSource sends it). The event `pools` carries the whole
|
|
105
|
+
* answer (first the current one; again when the host cannot say what changed): it replaces the held
|
|
106
|
+
* answer. With `change` (P2), the event `change` carries one pool change: `change(held, x)` applies
|
|
107
|
+
* it to the held answer (null: it cannot); one it cannot apply (no answer held yet, an unknown
|
|
108
|
+
* kind, a row it names not held) → the route is read again ONCE (a plain GET) and that answer is
|
|
109
|
+
* held (changes arriving while it is read are dropped: the answer read is the newer state). Each
|
|
110
|
+
* new held answer goes to `onAnswer`. Without an EventSource (an old browser, a test): ONE plain
|
|
111
|
+
* GET, no poll. → the unsubscribe.
|
|
107
112
|
*/
|
|
108
113
|
export declare function watchPools<T>(url: string, parse: (x: unknown) => T, onAnswer: (a: T) => void, o?: {
|
|
109
114
|
onError?: (e: unknown) => void;
|
|
110
115
|
onOpen?: () => void;
|
|
111
116
|
EventSource?: EventSourceCtor | null;
|
|
112
117
|
fetch?: FetchFn;
|
|
118
|
+
change?: (held: T, x: unknown) => T | null;
|
|
113
119
|
}): () => void;
|
|
114
120
|
/** P3's answer: the LP keys' current pools and one BEEF of their transactions. */
|
|
115
121
|
export interface PoolsBeef {
|
|
@@ -187,4 +193,123 @@ export declare function readDiscoveryIds(discovery: string, ids: string[], fetch
|
|
|
187
193
|
export declare function readDiscoveryList(discovery: string, fetchFn?: FetchFn): Promise<DiscoveryRow[]>;
|
|
188
194
|
/** P16: `GET <discovery>/<token>/deploy`: the deploy's BEEF; null when discovery does not hold it. */
|
|
189
195
|
export declare function readDiscoveryDeploy(discovery: string, tokenId: string, fetchFn?: FetchFn): Promise<number[] | null>;
|
|
196
|
+
/** A price, exact (H1–H4): `sats` satoshis per `tokens` token base units (divide in the caller). */
|
|
197
|
+
export interface Price {
|
|
198
|
+
sats: bigint;
|
|
199
|
+
tokens: bigint;
|
|
200
|
+
}
|
|
201
|
+
/** A Swap's trade row (H1; the `swap` change's `trade`): amounts in base units, fees in the input asset, the pool's reserves after it. */
|
|
202
|
+
export interface Trade {
|
|
203
|
+
txid: string;
|
|
204
|
+
lpPubKey: string;
|
|
205
|
+
/** `buy`: sats in, tokens out; `sell`: tokens in. */
|
|
206
|
+
side: "buy" | "sell";
|
|
207
|
+
amountIn: bigint;
|
|
208
|
+
amountOut: bigint;
|
|
209
|
+
feeLp: bigint;
|
|
210
|
+
feeValidator: bigint;
|
|
211
|
+
feeCommission: bigint;
|
|
212
|
+
bsvReserve: bigint;
|
|
213
|
+
tokenReserve: bigint;
|
|
214
|
+
/** The admitting step's time, ms. */
|
|
215
|
+
at: number;
|
|
216
|
+
/** The block height where the overlay has the transaction placed, else null. */
|
|
217
|
+
height: number | null;
|
|
218
|
+
}
|
|
219
|
+
/** H1's page: the trades newest first, `next` the cursor of the page after (null: the last page). */
|
|
220
|
+
export interface TradesPage {
|
|
221
|
+
trades: Trade[];
|
|
222
|
+
next: string | null;
|
|
223
|
+
}
|
|
224
|
+
/** One non-empty bucket (H2): its start (ms), the first, highest, lowest and last trade's prices, each side's amounts summed, the trade count. */
|
|
225
|
+
export interface Candle {
|
|
226
|
+
t: number;
|
|
227
|
+
o: Price;
|
|
228
|
+
h: Price;
|
|
229
|
+
l: Price;
|
|
230
|
+
c: Price;
|
|
231
|
+
volumeSats: bigint;
|
|
232
|
+
volumeTokens: bigint;
|
|
233
|
+
n: number;
|
|
234
|
+
}
|
|
235
|
+
/** A candle's resolution (H2). */
|
|
236
|
+
export type CandleTf = "1m" | "5m" | "15m" | "1h" | "4h" | "1d";
|
|
237
|
+
/** A token at the read's time (H3, H4): the changes in bps (null without a trade at or before the window's start). */
|
|
238
|
+
export interface TokenStats {
|
|
239
|
+
tokenId: string;
|
|
240
|
+
/** The last trade's, else the deepest live pool's, else null. */
|
|
241
|
+
price: Price | null;
|
|
242
|
+
change1h: number | null;
|
|
243
|
+
change24h: number | null;
|
|
244
|
+
volume24hSats: bigint;
|
|
245
|
+
volume24hTokens: bigint;
|
|
246
|
+
buys24h: number;
|
|
247
|
+
sells24h: number;
|
|
248
|
+
lastTradeAt: number | null;
|
|
249
|
+
tvlSats: bigint;
|
|
250
|
+
tvlTokens: bigint;
|
|
251
|
+
pools: number;
|
|
252
|
+
}
|
|
253
|
+
/** A token's holders (H6): `count` the addresses with a balance, `rows` largest balance first. */
|
|
254
|
+
export interface Holders {
|
|
255
|
+
count: number;
|
|
256
|
+
rows: {
|
|
257
|
+
address: string;
|
|
258
|
+
balance: bigint;
|
|
259
|
+
}[];
|
|
260
|
+
}
|
|
261
|
+
/** One LP key's position on one token (H5): P4's record with its token. */
|
|
262
|
+
export interface TokenPosition extends LpPool {
|
|
263
|
+
tokenId: string;
|
|
264
|
+
}
|
|
265
|
+
/** One pool change on P2's stream (event `change`, D7). */
|
|
266
|
+
export interface PoolChange {
|
|
267
|
+
kind: "deploy" | "swap" | "close" | "rescind";
|
|
268
|
+
/** The new pool output (deploy, swap) or the one gone (close, rescind). */
|
|
269
|
+
outpoint: string;
|
|
270
|
+
/** deploy, swap: the pool's row. */
|
|
271
|
+
pool?: PoolRow;
|
|
272
|
+
/** swap: its trade row. */
|
|
273
|
+
trade?: Trade;
|
|
274
|
+
}
|
|
275
|
+
/** A trade row, or null when it is not one. */
|
|
276
|
+
export declare function toTrade(x: unknown): Trade | null;
|
|
277
|
+
/** A stats record (H3), or null when it is not one. */
|
|
278
|
+
export declare function toStats(x: unknown): TokenStats | null;
|
|
279
|
+
/** A `change` event's data, or null when it is not one this client knows. */
|
|
280
|
+
export declare function parseChange(x: unknown): PoolChange | null;
|
|
281
|
+
/**
|
|
282
|
+
* One `change` applied to a held P2 answer (D7) → the new answer, or null when it cannot be applied
|
|
283
|
+
* (the caller reads the route again): `swap` — the row of `trade.lpPubKey` replaced by `pool` (its
|
|
284
|
+
* outpoint moved); `deploy` — `pool` added; `close` / `rescind` — the row at `outpoint` removed.
|
|
285
|
+
* The market and the validators are kept (a change of either comes as a whole `pools` answer).
|
|
286
|
+
*/
|
|
287
|
+
export declare function applyPoolChange(held: PoolsAnswer, x: unknown): PoolsAnswer | null;
|
|
288
|
+
/** H1: `GET <base>/trades?from=&to=&limit=&before=&lp=` → the page, newest first. */
|
|
289
|
+
export declare function readTrades(base: string, o?: {
|
|
290
|
+
from?: number;
|
|
291
|
+
to?: number;
|
|
292
|
+
limit?: number;
|
|
293
|
+
before?: string;
|
|
294
|
+
lp?: string;
|
|
295
|
+
}, fetchFn?: FetchFn): Promise<TradesPage>;
|
|
296
|
+
/** H1 with `txid`: one Swap's trade row; null when the transaction has none (404). */
|
|
297
|
+
export declare function readTrade(base: string, txid: string, fetchFn?: FetchFn): Promise<Trade | null>;
|
|
298
|
+
/** H2: `GET <base>/candles?tf=&from=&to=` → one candle per non-empty bucket, oldest first. */
|
|
299
|
+
export declare function readCandles(base: string, o: {
|
|
300
|
+
tf: CandleTf;
|
|
301
|
+
from: number;
|
|
302
|
+
to: number;
|
|
303
|
+
}, fetchFn?: FetchFn): Promise<Candle[]>;
|
|
304
|
+
/** H3: `GET <base>/stats`. */
|
|
305
|
+
export declare function readStats(base: string, fetchFn?: FetchFn): Promise<TokenStats>;
|
|
306
|
+
/** H4: `GET <swap base>/stats` (`ids`: those enabled tokens only, in that order) → every enabled token's stats. */
|
|
307
|
+
export declare function readAllStats(swapBase: string, ids?: string[], fetchFn?: FetchFn): Promise<TokenStats[]>;
|
|
308
|
+
/** H6: `GET <token base>/color/<token>/holders?limit=&skip=` → the count and the rows, largest first. */
|
|
309
|
+
export declare function readHolders(base: string, tokenId: string, o?: {
|
|
310
|
+
limit?: number;
|
|
311
|
+
skip?: number;
|
|
312
|
+
}, fetchFn?: FetchFn): Promise<Holders>;
|
|
313
|
+
/** H5: `GET <swap base>/positions?lp=k1,k2` → every enabled token's rows for the keys, each with its token. */
|
|
314
|
+
export declare function readAllPositions(swapBase: string, lpPubKeys: string[], fetchFn?: FetchFn): Promise<TokenPosition[]>;
|
|
190
315
|
export {};
|
package/dist/lib/overlay.js
CHANGED
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
* | P15 | `GET /@discovery/1color/<token>/icon` | the icon's bytes |
|
|
26
26
|
* | P16 | `GET /@discovery/1color/<token>/deploy` | the deploy's BEEF (bytes) |
|
|
27
27
|
* | P17 | `GET <origin>/.well-known/skein`, `/skeins?key=`, `/amm/admin`, `/amm/defaults`, `/amm/discovery` | as before |
|
|
28
|
+
* | H1 | `GET /@<token>/amm/trades?from=&to=&limit=&before=&lp=` (or `?txid=`) | `{trades: [Trade], next}` newest first (one txid: the row) |
|
|
29
|
+
* | H2 | `GET /@<token>/amm/candles?tf=&from=&to=` | `[{t, o, h, l, c, volumeSats, volumeTokens, n}]`, one per non-empty bucket |
|
|
30
|
+
* | H3 | `GET /@<token>/amm/stats` | `{tokenId, price, change1h, change24h, volume24hSats, volume24hTokens, buys24h, sells24h, lastTradeAt, tvlSats, tvlTokens, pools}` |
|
|
31
|
+
* | H4 | `GET <origin>/amm/stats?ids=` | `[H3]`, every enabled token's (the host's) |
|
|
32
|
+
* | H5 | `GET <origin>/amm/positions?lp=` | `[{tokenId, …P4 row}]`, every enabled token's (the host's) |
|
|
33
|
+
* | H6 | `GET /@<token>/amm/color/<token>/holders?limit=&skip=` | `{count, rows: [{address, balance}]}` largest first |
|
|
34
|
+
*
|
|
35
|
+
* 0.39.0 (agent-env plans/history-api-2026-11-05.md D7): P2's stream sends the whole answer once
|
|
36
|
+
* (`pools`), then ONE `change` per pool change (`{kind, outpoint, pool?, trade?}`); the host-wide
|
|
37
|
+
* stream is gone (P1, H4 and H5 are plain reads).
|
|
28
38
|
*
|
|
29
39
|
* Writes stay what they were: `POST <origin>/submit` (BRC-22, plain), `/call` and `/sendMessage`
|
|
30
40
|
* (signed, the wallet's BRC-104 client).
|
|
@@ -140,10 +150,15 @@ function eventSourceCtor() {
|
|
|
140
150
|
return typeof g.EventSource === "function" ? g.EventSource : null;
|
|
141
151
|
}
|
|
142
152
|
/**
|
|
143
|
-
* Holds a pools route
|
|
144
|
-
* text/event-stream` (the browser's EventSource sends it)
|
|
145
|
-
* first the current one
|
|
146
|
-
*
|
|
153
|
+
* Holds a pools route as an event stream (D16; 0.39.0, D7): `GET <url>` with `Accept:
|
|
154
|
+
* text/event-stream` (the browser's EventSource sends it). The event `pools` carries the whole
|
|
155
|
+
* answer (first the current one; again when the host cannot say what changed): it replaces the held
|
|
156
|
+
* answer. With `change` (P2), the event `change` carries one pool change: `change(held, x)` applies
|
|
157
|
+
* it to the held answer (null: it cannot); one it cannot apply (no answer held yet, an unknown
|
|
158
|
+
* kind, a row it names not held) → the route is read again ONCE (a plain GET) and that answer is
|
|
159
|
+
* held (changes arriving while it is read are dropped: the answer read is the newer state). Each
|
|
160
|
+
* new held answer goes to `onAnswer`. Without an EventSource (an old browser, a test): ONE plain
|
|
161
|
+
* GET, no poll. → the unsubscribe.
|
|
147
162
|
*/
|
|
148
163
|
export function watchPools(url, parse, onAnswer, o = {}) {
|
|
149
164
|
const ES = o.EventSource === undefined ? eventSourceCtor() : o.EventSource;
|
|
@@ -160,17 +175,53 @@ export function watchPools(url, parse, onAnswer, o = {}) {
|
|
|
160
175
|
stopped = true;
|
|
161
176
|
};
|
|
162
177
|
}
|
|
178
|
+
let held = null;
|
|
179
|
+
let rereading = false;
|
|
180
|
+
const hold = (a) => {
|
|
181
|
+
held = a;
|
|
182
|
+
onAnswer(a);
|
|
183
|
+
};
|
|
184
|
+
const reread = () => {
|
|
185
|
+
rereading = true;
|
|
186
|
+
getJson(url, o.fetch).then((x) => {
|
|
187
|
+
rereading = false;
|
|
188
|
+
if (!stopped)
|
|
189
|
+
hold(parse(x));
|
|
190
|
+
}, (e) => {
|
|
191
|
+
rereading = false;
|
|
192
|
+
if (!stopped)
|
|
193
|
+
o.onError?.(e);
|
|
194
|
+
});
|
|
195
|
+
};
|
|
163
196
|
const es = new ES(url);
|
|
164
197
|
es.addEventListener("pools", (e) => {
|
|
165
198
|
if (stopped)
|
|
166
199
|
return;
|
|
167
200
|
try {
|
|
168
|
-
|
|
201
|
+
hold(parse(JSON.parse(String(e.data))));
|
|
169
202
|
}
|
|
170
203
|
catch (err) {
|
|
171
204
|
o.onError?.(err);
|
|
172
205
|
}
|
|
173
206
|
});
|
|
207
|
+
const apply = o.change;
|
|
208
|
+
if (apply) {
|
|
209
|
+
es.addEventListener("change", (e) => {
|
|
210
|
+
if (stopped || rereading)
|
|
211
|
+
return;
|
|
212
|
+
let next = null;
|
|
213
|
+
try {
|
|
214
|
+
next = held === null ? null : apply(held, JSON.parse(String(e.data)));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
next = null;
|
|
218
|
+
}
|
|
219
|
+
if (next === null)
|
|
220
|
+
reread();
|
|
221
|
+
else
|
|
222
|
+
hold(next);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
174
225
|
es.onopen = () => o.onOpen?.();
|
|
175
226
|
es.onerror = (e) => o.onError?.(e);
|
|
176
227
|
return () => {
|
|
@@ -300,3 +351,172 @@ export async function readDiscoveryList(discovery, fetchFn) {
|
|
|
300
351
|
export async function readDiscoveryDeploy(discovery, tokenId, fetchFn) {
|
|
301
352
|
return getBytes(`${trim(discovery)}/${encodeURIComponent(tokenId)}/deploy`, fetchFn);
|
|
302
353
|
}
|
|
354
|
+
const intOrNull = (x) => (typeof x === "number" && Number.isFinite(x) ? x : null);
|
|
355
|
+
const int = (x) => intOrNull(x) ?? 0;
|
|
356
|
+
function toPrice(x) {
|
|
357
|
+
const p = x;
|
|
358
|
+
if (!p || typeof p !== "object" || p.sats === undefined || p.tokens === undefined)
|
|
359
|
+
return null;
|
|
360
|
+
return { sats: wireBig(p.sats), tokens: wireBig(p.tokens) };
|
|
361
|
+
}
|
|
362
|
+
/** A trade row, or null when it is not one. */
|
|
363
|
+
export function toTrade(x) {
|
|
364
|
+
const r = x;
|
|
365
|
+
if (!r || typeof r !== "object" || typeof r.txid !== "string" || typeof r.lpPubKey !== "string" || (r.side !== "buy" && r.side !== "sell"))
|
|
366
|
+
return null;
|
|
367
|
+
return {
|
|
368
|
+
txid: r.txid.toLowerCase(),
|
|
369
|
+
lpPubKey: r.lpPubKey.toLowerCase(),
|
|
370
|
+
side: r.side,
|
|
371
|
+
amountIn: wireBig(r.amountIn),
|
|
372
|
+
amountOut: wireBig(r.amountOut),
|
|
373
|
+
feeLp: wireBig(r.feeLp),
|
|
374
|
+
feeValidator: wireBig(r.feeValidator),
|
|
375
|
+
feeCommission: wireBig(r.feeCommission),
|
|
376
|
+
bsvReserve: wireBig(r.bsvReserve),
|
|
377
|
+
tokenReserve: wireBig(r.tokenReserve),
|
|
378
|
+
at: int(r.at),
|
|
379
|
+
height: intOrNull(r.height),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function toCandle(x) {
|
|
383
|
+
const r = x;
|
|
384
|
+
if (!r || typeof r !== "object")
|
|
385
|
+
return null;
|
|
386
|
+
const [o, h, l, c] = [toPrice(r.o), toPrice(r.h), toPrice(r.l), toPrice(r.c)];
|
|
387
|
+
if (!o || !h || !l || !c || typeof r.t !== "number")
|
|
388
|
+
return null;
|
|
389
|
+
return { t: r.t, o, h, l, c, volumeSats: wireBig(r.volumeSats), volumeTokens: wireBig(r.volumeTokens), n: int(r.n) };
|
|
390
|
+
}
|
|
391
|
+
/** A stats record (H3), or null when it is not one. */
|
|
392
|
+
export function toStats(x) {
|
|
393
|
+
const r = x;
|
|
394
|
+
if (!r || typeof r !== "object" || typeof r.tokenId !== "string")
|
|
395
|
+
return null;
|
|
396
|
+
return {
|
|
397
|
+
tokenId: r.tokenId,
|
|
398
|
+
price: toPrice(r.price),
|
|
399
|
+
change1h: intOrNull(r.change1h),
|
|
400
|
+
change24h: intOrNull(r.change24h),
|
|
401
|
+
volume24hSats: wireBig(r.volume24hSats),
|
|
402
|
+
volume24hTokens: wireBig(r.volume24hTokens),
|
|
403
|
+
buys24h: int(r.buys24h),
|
|
404
|
+
sells24h: int(r.sells24h),
|
|
405
|
+
lastTradeAt: intOrNull(r.lastTradeAt),
|
|
406
|
+
tvlSats: wireBig(r.tvlSats),
|
|
407
|
+
tvlTokens: wireBig(r.tvlTokens),
|
|
408
|
+
pools: int(r.pools),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** A `change` event's data, or null when it is not one this client knows. */
|
|
412
|
+
export function parseChange(x) {
|
|
413
|
+
const r = x;
|
|
414
|
+
if (!r || typeof r !== "object" || typeof r.outpoint !== "string")
|
|
415
|
+
return null;
|
|
416
|
+
if (r.kind !== "deploy" && r.kind !== "swap" && r.kind !== "close" && r.kind !== "rescind")
|
|
417
|
+
return null;
|
|
418
|
+
const pool = r.pool === undefined ? null : toPoolRow(r.pool);
|
|
419
|
+
const trade = r.trade === undefined ? null : toTrade(r.trade);
|
|
420
|
+
return { kind: r.kind, outpoint: r.outpoint, ...(pool ? { pool } : {}), ...(trade ? { trade } : {}) };
|
|
421
|
+
}
|
|
422
|
+
const sameOutpoint = (a, b) => a.toLowerCase().replace(".", "_") === b.toLowerCase().replace(".", "_");
|
|
423
|
+
/**
|
|
424
|
+
* One `change` applied to a held P2 answer (D7) → the new answer, or null when it cannot be applied
|
|
425
|
+
* (the caller reads the route again): `swap` — the row of `trade.lpPubKey` replaced by `pool` (its
|
|
426
|
+
* outpoint moved); `deploy` — `pool` added; `close` / `rescind` — the row at `outpoint` removed.
|
|
427
|
+
* The market and the validators are kept (a change of either comes as a whole `pools` answer).
|
|
428
|
+
*/
|
|
429
|
+
export function applyPoolChange(held, x) {
|
|
430
|
+
const ch = parseChange(x);
|
|
431
|
+
if (!ch)
|
|
432
|
+
return null;
|
|
433
|
+
switch (ch.kind) {
|
|
434
|
+
case "swap": {
|
|
435
|
+
if (!ch.pool || !ch.trade)
|
|
436
|
+
return null;
|
|
437
|
+
const i = held.pools.findIndex((p) => p.lpPubKey === ch.trade.lpPubKey);
|
|
438
|
+
if (i < 0)
|
|
439
|
+
return null;
|
|
440
|
+
const pools = held.pools.slice();
|
|
441
|
+
pools[i] = ch.pool;
|
|
442
|
+
return { ...held, pools };
|
|
443
|
+
}
|
|
444
|
+
case "deploy":
|
|
445
|
+
if (!ch.pool)
|
|
446
|
+
return null;
|
|
447
|
+
return { ...held, pools: [...held.pools, ch.pool] };
|
|
448
|
+
case "close":
|
|
449
|
+
case "rescind": {
|
|
450
|
+
const pools = held.pools.filter((p) => !sameOutpoint(p.outpoint, ch.outpoint));
|
|
451
|
+
if (pools.length === held.pools.length)
|
|
452
|
+
return null;
|
|
453
|
+
return { ...held, pools };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const query = (q) => {
|
|
458
|
+
const kv = Object.entries(q).filter(([, x]) => x !== undefined).map(([k, x]) => `${k}=${encodeURIComponent(String(x))}`);
|
|
459
|
+
return kv.length ? `?${kv.join("&")}` : "";
|
|
460
|
+
};
|
|
461
|
+
/** H1: `GET <base>/trades?from=&to=&limit=&before=&lp=` → the page, newest first. */
|
|
462
|
+
export async function readTrades(base, o = {}, fetchFn) {
|
|
463
|
+
const a = (await getJson(`${trim(base)}/trades${query({ from: o.from, to: o.to, limit: o.limit, before: o.before, lp: o.lp })}`, fetchFn));
|
|
464
|
+
return {
|
|
465
|
+
trades: (Array.isArray(a?.trades) ? a.trades : []).map(toTrade).filter((t) => t !== null),
|
|
466
|
+
next: typeof a?.next === "string" ? a.next : null,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/** H1 with `txid`: one Swap's trade row; null when the transaction has none (404). */
|
|
470
|
+
export async function readTrade(base, txid, fetchFn) {
|
|
471
|
+
const url = `${trim(base)}/trades?txid=${encodeURIComponent(txid)}`;
|
|
472
|
+
const res = await (fetchFn ?? plain)(url, { headers: { accept: "application/json" } });
|
|
473
|
+
if (res.status === 404)
|
|
474
|
+
return null;
|
|
475
|
+
if (!res.ok)
|
|
476
|
+
throw new Error(`GET ${url}: ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
477
|
+
return toTrade(await res.json());
|
|
478
|
+
}
|
|
479
|
+
/** H2: `GET <base>/candles?tf=&from=&to=` → one candle per non-empty bucket, oldest first. */
|
|
480
|
+
export async function readCandles(base, o, fetchFn) {
|
|
481
|
+
const a = await getJson(`${trim(base)}/candles${query({ tf: o.tf, from: o.from, to: o.to })}`, fetchFn);
|
|
482
|
+
return (Array.isArray(a) ? a : []).map(toCandle).filter((c) => c !== null);
|
|
483
|
+
}
|
|
484
|
+
/** H3: `GET <base>/stats`. */
|
|
485
|
+
export async function readStats(base, fetchFn) {
|
|
486
|
+
const url = `${trim(base)}/stats`;
|
|
487
|
+
const s = toStats(await getJson(url, fetchFn));
|
|
488
|
+
if (!s)
|
|
489
|
+
throw new Error(`GET ${url}: not a stats record`);
|
|
490
|
+
return s;
|
|
491
|
+
}
|
|
492
|
+
/** H4: `GET <swap base>/stats` (`ids`: those enabled tokens only, in that order) → every enabled token's stats. */
|
|
493
|
+
export async function readAllStats(swapBase, ids, fetchFn) {
|
|
494
|
+
const a = await getJson(`${trim(swapBase)}/stats${ids ? `?ids=${list(ids)}` : ""}`, fetchFn);
|
|
495
|
+
return (Array.isArray(a) ? a : []).map(toStats).filter((s) => s !== null);
|
|
496
|
+
}
|
|
497
|
+
/** H6: `GET <token base>/color/<token>/holders?limit=&skip=` → the count and the rows, largest first. */
|
|
498
|
+
export async function readHolders(base, tokenId, o = {}, fetchFn) {
|
|
499
|
+
const a = (await getJson(`${trim(base)}/color/${encodeURIComponent(tokenId)}/holders${query({ limit: o.limit, skip: o.skip })}`, fetchFn));
|
|
500
|
+
return {
|
|
501
|
+
count: int(a?.count),
|
|
502
|
+
rows: (Array.isArray(a?.rows) ? a.rows : []).filter((r) => r && typeof r.address === "string").map((r) => ({ address: String(r.address), balance: wireBig(r.balance) })),
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
/** H5: `GET <swap base>/positions?lp=k1,k2` → every enabled token's rows for the keys, each with its token. */
|
|
506
|
+
export async function readAllPositions(swapBase, lpPubKeys, fetchFn) {
|
|
507
|
+
if (lpPubKeys.length === 0)
|
|
508
|
+
return [];
|
|
509
|
+
const a = await getJson(`${trim(swapBase)}/positions?lp=${list(lpPubKeys)}`, fetchFn);
|
|
510
|
+
return (Array.isArray(a) ? a : [])
|
|
511
|
+
.filter((r) => r && typeof r.tokenId === "string" && typeof r.lpPubKey === "string")
|
|
512
|
+
.map((r) => ({
|
|
513
|
+
tokenId: String(r.tokenId),
|
|
514
|
+
lpPubKey: String(r.lpPubKey),
|
|
515
|
+
status: r.status === "live" || r.status === "rescinded" ? r.status : "closed",
|
|
516
|
+
outpoint: typeof r.outpoint === "string" ? r.outpoint : null,
|
|
517
|
+
current: isWirePool(r.current) ? toPoolState(r.current) : null,
|
|
518
|
+
sats: wireBig(r.sats),
|
|
519
|
+
tokens: wireBig(r.tokens),
|
|
520
|
+
swaps: Number(r.swaps ?? 0),
|
|
521
|
+
}));
|
|
522
|
+
}
|
package/dist/market/prices.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prices (0.34.0, D6, David Case 2026-11-04: one source for prices): a token's
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Prices (0.34.0, D6, David Case 2026-11-04: one source for prices; 0.39.0, D3, D7): a token's
|
|
3
|
+
* listed price comes from the host-wide stats (`GET <origin>/amm/stats`, read once per load and
|
|
4
|
+
* every 30 s while the tab is visible; no host-wide stream), the Swap page's quote from the pools
|
|
5
|
+
* its token's pools route lists. No price is derived from beats (the 0.9.0 beat prices are gone);
|
|
6
6
|
* the depth is the sats a buy needs to move the price up by 2%, x·(√1.02 − 1), over the summed
|
|
7
7
|
* sats.
|
|
8
8
|
*/
|
package/dist/market/prices.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prices (0.34.0, D6, David Case 2026-11-04: one source for prices): a token's
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Prices (0.34.0, D6, David Case 2026-11-04: one source for prices; 0.39.0, D3, D7): a token's
|
|
3
|
+
* listed price comes from the host-wide stats (`GET <origin>/amm/stats`, read once per load and
|
|
4
|
+
* every 30 s while the tab is visible; no host-wide stream), the Swap page's quote from the pools
|
|
5
|
+
* its token's pools route lists. No price is derived from beats (the 0.9.0 beat prices are gone);
|
|
6
6
|
* the depth is the sats a buy needs to move the price up by 2%, x·(√1.02 − 1), over the summed
|
|
7
7
|
* sats.
|
|
8
8
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcasegr/amm-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Open Exchange client (skein-amm): reads, swap, pool deploy / close / rebalance, move back and positions over a BRC-100 wallet, bound to one exchange by createClient. No React, no styles, no wallet provider.",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@1sat/actions": "0.1.3",
|
|
33
|
-
"@dcasegr/amm-engine": "0.
|
|
33
|
+
"@dcasegr/amm-engine": "0.39.0",
|
|
34
34
|
"cbor2": "^2.3.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|