@dcasegr/amm-client 0.38.2 → 0.41.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.
- package/README.md +64 -24
- package/dist/client.d.ts +206 -62
- package/dist/client.js +388 -333
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +6 -3
- package/dist/index.d.ts +16 -4
- package/dist/index.js +16 -4
- package/dist/lib/overlay.d.ts +226 -19
- package/dist/lib/overlay.js +278 -49
- package/dist/lib/skein.d.ts +2 -0
- package/dist/lib/skein.js +1 -1
- package/dist/market/activeSwap.d.ts +77 -96
- package/dist/market/activeSwap.js +146 -166
- package/dist/market/plan.d.ts +2 -1
- package/dist/market/plan.js +1 -1
- package/dist/market/prices.d.ts +4 -4
- package/dist/market/prices.js +4 -4
- package/dist/market/swapAction.d.ts +150 -145
- package/dist/market/swapAction.js +292 -213
- package/dist/market/swapFlow.d.ts +3 -6
- package/dist/market/swapFlow.js +4 -22
- package/dist/pool/pool.artifact.json +760 -528
- package/dist/pool/template.d.ts +10 -2
- package/dist/pool/template.js +16 -6
- package/dist/wallet/color.d.ts +13 -9
- package/dist/wallet/color.js +13 -10
- package/dist/wallet/swapPayouts.d.ts +45 -28
- package/dist/wallet/swapPayouts.js +129 -117
- package/package.json +2 -2
package/dist/lib/overlay.js
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* | # | route | answer |
|
|
10
10
|
* |---|---|---|
|
|
11
|
-
* | P1 | `GET <origin>/amm/pools`
|
|
12
|
-
* | P2 | `GET <origin>/@<token>/amm/pools` (
|
|
13
|
-
* | P3 | `GET /@<token>/amm/pools/beef?
|
|
14
|
-
* | P4 | `GET /@<token>/amm/positions?
|
|
11
|
+
* | P1 | `GET <origin>/amm/pools` | `[{tokenId, …P2 row}]`, every enabled token's live pools (the host's) |
|
|
12
|
+
* | P2 | `GET <origin>/@<token>/amm/pools` (0.41.0: a plain read only) | `{market: {commissionPkh, minCommissionBps}, validators: [{identity, peerId, minLegSats?}] (0.34.1: beating now; 0.40.0: the minimum leg its terms announce), pools: [{poolId, lpPubKey, outpoint, bsvReserve, tokenReserve, liquidityFeeBps, validationFeeBps, commissionBps, validatorIdentityKey, validatorPeerId, swaps}]}`: only pools whose validator beats now and whose commission is at or above the market's minimum (the host's) |
|
|
13
|
+
* | P3 | `GET /@<token>/amm/pools/beef?pool=id1,id2` (0.40.0: by pool id) | `{pools: [P2 row without validatorPeerId], beef: <base64 BEEF of their transactions>}` |
|
|
14
|
+
* | P4 | `GET /@<token>/amm/positions?pool=id1,id2` (0.40.0) | `[{poolId, lpPubKey, status: live|rescinded|closed, outpoint?, current?, sats, tokens, swaps}]` |
|
|
15
15
|
* | P5 | `GET /@<token>/amm/fees/validator?id=k1,k2` | `[{validatorIdentityKey, sats, tokens, swaps, pools}]` |
|
|
16
16
|
* | P6 | `GET /@<token>/amm/fees/commission?pkh=h1,h2` | `[{commissionPkh, sats, tokens, swaps}]` |
|
|
17
|
-
* | P7 | `GET /@<token>/amm/terms` | `{validator: {identity, peerId, lpFeeBps, validatorFeeBps, commissionBps} | null, market: {commissionPkh, minCommissionBps}}` |
|
|
17
|
+
* | P7 | `GET /@<token>/amm/terms` | `{validator: {identity, peerId, lpFeeBps, validatorFeeBps, commissionBps, minLegSats} | null, market: {commissionPkh, minCommissionBps}}` |
|
|
18
18
|
* | P8 | `GET /@<token>/amm/spends?outpoint=a,b` | `[{outpoint, spentBy?, current?, hops, closed}]` (one outpoint: the object) |
|
|
19
19
|
* | P9 | `GET /@<token>/amm/tx?id=<txid>` | the transaction's BEEF (bytes) |
|
|
20
20
|
* | P10 | `GET /@<token>/amm/swaps?id=<id>` | the relay record, as `amm.swap.status` answers it |
|
|
@@ -25,6 +25,24 @@
|
|
|
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=&pool=` (or `?txid=`) | `{trades: [Trade], next}` newest first (one txid: the row) |
|
|
29
|
+
* | H2 | `GET /@<token>/amm/candles?tf=&from=&to=&series=fill|market` | `[{t, o, h, l, c, volumeSats, volumeTokens, n}]`, one per non-empty bucket (0.40.0: `market`, the host's: the price after each pool event) |
|
|
30
|
+
* | H3 | `GET /@<token>/amm/stats` | `{tokenId, price, buyPrice, sellPrice, lastTradePrice, 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?pool=` | `[{tokenId, …P4 row}]`, every enabled token's (the host's) |
|
|
33
|
+
* | H7 | `GET /@<token>/amm/pools/at?t=<ms>` (0.40.0) | `[{poolId, outpoint, bsvReserve, tokenReserve, lpPubKey, …the three bps, validatorIdentityKey, at, swaps}]`: every pool listed at `t` |
|
|
34
|
+
* | H8 | `GET /@<token>/amm/pools/history?from=&to=&pool=&limit=&after=` (0.40.0) | `{events: [PoolEvent], next}` oldest first; `next` → `after` |
|
|
35
|
+
* | H9 | `GET <origin>/amm/spends?outpoint=a,b` (0.40.0, the host's) | `[{outpoint, tokenId?, spentBy?}]`: each enabled token's `/spends`, the first that names a spender |
|
|
36
|
+
* | H6 | `GET /@<token>/amm/color/<token>/holders?limit=&skip=` | `{count, rows: [{address, balance}]}` largest first |
|
|
37
|
+
* | E1 | `GET /@<token>/amm/events`, `GET <origin>/amm/events` (0.41.0, the host's) | Server-Sent Events, each `event: pool`, `data: {tokenId, …row}`: the AMM lookup's pool event (a deploy, swap, close or rescind: the H8 row; a `revert`), one token's or every enabled token's; nothing on connect, nothing replayed |
|
|
38
|
+
*
|
|
39
|
+
* 0.41.0 (agent-env plans/swap-balance-build-2026-11-05.md §5): P2's event-stream form is gone
|
|
40
|
+
* (0.39.0's `pools` / `change` events); the pool events are the lookup's, passed through by the
|
|
41
|
+
* host's feeds (E1): signals for display, the pools route the truth.
|
|
42
|
+
*
|
|
43
|
+
* 0.40.0 (agent-env plans/swap-balance-build-2026-11-05.md §0 3): a pool's id is its deploy outpoint
|
|
44
|
+
* `<txid>_<vout>` (`poolId`, copied forward along its swaps); every read by LP key is by pool id
|
|
45
|
+
* now (`?pool=`), the LP key a field. A clean break: no `?lp=` here.
|
|
28
46
|
*
|
|
29
47
|
* Writes stay what they were: `POST <origin>/submit` (BRC-22, plain), `/call` and `/sendMessage`
|
|
30
48
|
* (signed, the wallet's BRC-104 client).
|
|
@@ -73,7 +91,9 @@ export function toPoolRow(raw) {
|
|
|
73
91
|
return null;
|
|
74
92
|
return {
|
|
75
93
|
...toPoolState(raw),
|
|
94
|
+
poolId: typeof raw.poolId === "string" ? raw.poolId.toLowerCase() : "",
|
|
76
95
|
lpPubKey: typeof raw.lpPubKey === "string" ? raw.lpPubKey.toLowerCase() : "",
|
|
96
|
+
swaps: Number(raw.swaps ?? 0),
|
|
77
97
|
...(typeof raw.validatorPeerId === "string" && raw.validatorPeerId ? { validatorPeerId: raw.validatorPeerId } : {}),
|
|
78
98
|
...(typeof raw.tokenId === "string" ? { tokenId: raw.tokenId } : {}),
|
|
79
99
|
};
|
|
@@ -92,7 +112,10 @@ export function parsePools(x) {
|
|
|
92
112
|
const a = x;
|
|
93
113
|
const validators = (Array.isArray(a?.validators) ? a.validators : [])
|
|
94
114
|
.filter((v) => v && typeof v.identity === "string")
|
|
95
|
-
.map((v) =>
|
|
115
|
+
.map((v) => {
|
|
116
|
+
const min = typeof v.minLegSats === "number" || typeof v.minLegSats === "string" ? BigInt(v.minLegSats) : 0n;
|
|
117
|
+
return { identity: String(v.identity).toLowerCase(), peerId: typeof v.peerId === "string" ? v.peerId : "", ...(min > 0n ? { minLegSats: min } : {}) };
|
|
118
|
+
});
|
|
96
119
|
return { market: toMarket(a?.market), pools: poolRows(a?.pools), validators };
|
|
97
120
|
}
|
|
98
121
|
/** P1's JSON: every enabled token's rows, each naming its `tokenId`. */
|
|
@@ -131,7 +154,7 @@ const list = (xs) => xs.map(encodeURIComponent).join(",");
|
|
|
131
154
|
export function poolsUrl(base) {
|
|
132
155
|
return `${trim(base)}/pools`;
|
|
133
156
|
}
|
|
134
|
-
/** P2
|
|
157
|
+
/** P2: the token's pools as the host lists them now (0.41.0: a plain read only). */
|
|
135
158
|
export async function readPools(base, fetchFn) {
|
|
136
159
|
return parsePools(await getJson(poolsUrl(base), fetchFn));
|
|
137
160
|
}
|
|
@@ -139,48 +162,43 @@ function eventSourceCtor() {
|
|
|
139
162
|
const g = globalThis;
|
|
140
163
|
return typeof g.EventSource === "function" ? g.EventSource : null;
|
|
141
164
|
}
|
|
165
|
+
/** E1's URL: `<base>/events` (a token instance's app base: that token's; the swap app's: every token's). */
|
|
166
|
+
export function eventsUrl(base) {
|
|
167
|
+
return `${trim(base)}/events`;
|
|
168
|
+
}
|
|
142
169
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* EventSource (an old browser, a test): ONE plain GET, no poll. → the unsubscribe.
|
|
170
|
+
* Listens to a host's pool event feed (E1, 0.41.0): each `pool` message parsed
|
|
171
|
+
* (`parsePoolEventMessage`) to `onEvent`; one it cannot parse is passed over. Reconnecting is the
|
|
172
|
+
* browser's EventSource's. Without an EventSource: nothing. → the unsubscribe.
|
|
147
173
|
*/
|
|
148
|
-
export function
|
|
174
|
+
export function watchPoolEvents(url, onEvent, o = {}) {
|
|
149
175
|
const ES = o.EventSource === undefined ? eventSourceCtor() : o.EventSource;
|
|
176
|
+
if (!ES)
|
|
177
|
+
return () => { };
|
|
150
178
|
let stopped = false;
|
|
151
|
-
if (!ES) {
|
|
152
|
-
getJson(url, o.fetch).then((x) => {
|
|
153
|
-
if (!stopped)
|
|
154
|
-
onAnswer(parse(x));
|
|
155
|
-
}, (e) => {
|
|
156
|
-
if (!stopped)
|
|
157
|
-
o.onError?.(e);
|
|
158
|
-
});
|
|
159
|
-
return () => {
|
|
160
|
-
stopped = true;
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
179
|
const es = new ES(url);
|
|
164
|
-
es.addEventListener("
|
|
180
|
+
es.addEventListener("pool", (e) => {
|
|
165
181
|
if (stopped)
|
|
166
182
|
return;
|
|
183
|
+
let m = null;
|
|
167
184
|
try {
|
|
168
|
-
|
|
185
|
+
m = parsePoolEventMessage(JSON.parse(String(e.data)));
|
|
169
186
|
}
|
|
170
|
-
catch
|
|
171
|
-
|
|
187
|
+
catch {
|
|
188
|
+
m = null;
|
|
172
189
|
}
|
|
190
|
+
if (m)
|
|
191
|
+
onEvent(m);
|
|
173
192
|
});
|
|
174
|
-
es.onopen = () => o.onOpen?.();
|
|
175
193
|
es.onerror = (e) => o.onError?.(e);
|
|
176
194
|
return () => {
|
|
177
195
|
stopped = true;
|
|
178
196
|
es.close();
|
|
179
197
|
};
|
|
180
198
|
}
|
|
181
|
-
/** P3: `GET <base>/pools/beef?
|
|
182
|
-
export async function readPoolsBeef(base,
|
|
183
|
-
const url = `${trim(base)}/pools/beef${
|
|
199
|
+
/** P3: `GET <base>/pools/beef?pool=id1,id2` (0.40.0: by pool id; no `poolIds`: every live pool). */
|
|
200
|
+
export async function readPoolsBeef(base, poolIds, fetchFn) {
|
|
201
|
+
const url = `${trim(base)}/pools/beef${poolIds ? `?pool=${list(poolIds)}` : ""}`;
|
|
184
202
|
const a = (await getJson(url, fetchFn));
|
|
185
203
|
return { pools: poolRows(a?.pools), beef: typeof a?.beef === "string" && a.beef ? Utils.toArray(a.beef, "base64") : [] };
|
|
186
204
|
}
|
|
@@ -195,24 +213,27 @@ export function poolOutputOf(pb, outpoint) {
|
|
|
195
213
|
return { beef: beef.toBinaryAtomic(m[1].toLowerCase()), outputIndex: Number(m[2]) };
|
|
196
214
|
}
|
|
197
215
|
const wireBig = (x) => (typeof x === "number" || typeof x === "string" || typeof x === "bigint" ? BigInt(x) : 0n);
|
|
198
|
-
/** P4
|
|
199
|
-
|
|
200
|
-
const
|
|
216
|
+
/** One P4 / H5 record. */
|
|
217
|
+
function lpPoolOf(r, poolId) {
|
|
218
|
+
const status = r.status === "live" || r.status === "rescinded" ? r.status : "closed";
|
|
219
|
+
return {
|
|
220
|
+
poolId: typeof r.poolId === "string" ? r.poolId.toLowerCase() : poolId,
|
|
221
|
+
lpPubKey: typeof r.lpPubKey === "string" ? r.lpPubKey.toLowerCase() : null,
|
|
222
|
+
status,
|
|
223
|
+
outpoint: typeof r.outpoint === "string" ? r.outpoint : null,
|
|
224
|
+
current: isWirePool(r.current) ? toPoolState(r.current) : null,
|
|
225
|
+
sats: wireBig(r.sats),
|
|
226
|
+
tokens: wireBig(r.tokens),
|
|
227
|
+
swaps: Number(r.swaps ?? 0),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** P4: `GET <base>/positions?pool=id1,id2` (0.40.0) → one record per pool id in the same order. */
|
|
231
|
+
export async function readPositions(base, poolIds, fetchFn) {
|
|
232
|
+
const url = `${trim(base)}/positions?pool=${list(poolIds)}`;
|
|
201
233
|
const a = await getJson(url, fetchFn);
|
|
202
|
-
if (!Array.isArray(a) || a.length !==
|
|
203
|
-
throw new Error(`GET ${url}: not one record per
|
|
204
|
-
return a.map((r, i) =>
|
|
205
|
-
const status = r.status === "live" || r.status === "rescinded" ? r.status : "closed";
|
|
206
|
-
return {
|
|
207
|
-
lpPubKey: typeof r.lpPubKey === "string" ? r.lpPubKey : lpPubKeys[i],
|
|
208
|
-
status,
|
|
209
|
-
outpoint: typeof r.outpoint === "string" ? r.outpoint : null,
|
|
210
|
-
current: isWirePool(r.current) ? toPoolState(r.current) : null,
|
|
211
|
-
sats: wireBig(r.sats),
|
|
212
|
-
tokens: wireBig(r.tokens),
|
|
213
|
-
swaps: Number(r.swaps ?? 0),
|
|
214
|
-
};
|
|
215
|
-
});
|
|
234
|
+
if (!Array.isArray(a) || a.length !== poolIds.length)
|
|
235
|
+
throw new Error(`GET ${url}: not one record per pool`);
|
|
236
|
+
return a.map((r, i) => lpPoolOf(r, poolIds[i]));
|
|
216
237
|
}
|
|
217
238
|
/** P7: `GET <base>/terms`, unsigned. */
|
|
218
239
|
export async function readTerms(base, fetchFn) {
|
|
@@ -232,6 +253,7 @@ export async function readTerms(base, fetchFn) {
|
|
|
232
253
|
lpFeeBps: int(v.lpFeeBps, "lpFeeBps"),
|
|
233
254
|
validatorFeeBps: int(v.validatorFeeBps, "validatorFeeBps"),
|
|
234
255
|
commissionBps: int(v.commissionBps, "commissionBps"),
|
|
256
|
+
minLegSats: typeof v.minLegSats === "number" && Number.isInteger(v.minLegSats) ? v.minLegSats : 0,
|
|
235
257
|
}
|
|
236
258
|
: null,
|
|
237
259
|
market: toMarket(a?.market),
|
|
@@ -300,3 +322,210 @@ export async function readDiscoveryList(discovery, fetchFn) {
|
|
|
300
322
|
export async function readDiscoveryDeploy(discovery, tokenId, fetchFn) {
|
|
301
323
|
return getBytes(`${trim(discovery)}/${encodeURIComponent(tokenId)}/deploy`, fetchFn);
|
|
302
324
|
}
|
|
325
|
+
const intOrNull = (x) => (typeof x === "number" && Number.isFinite(x) ? x : null);
|
|
326
|
+
const int = (x) => intOrNull(x) ?? 0;
|
|
327
|
+
function toPrice(x) {
|
|
328
|
+
const p = x;
|
|
329
|
+
if (!p || typeof p !== "object" || p.sats === undefined || p.tokens === undefined)
|
|
330
|
+
return null;
|
|
331
|
+
return { sats: wireBig(p.sats), tokens: wireBig(p.tokens) };
|
|
332
|
+
}
|
|
333
|
+
/** A trade row, or null when it is not one. */
|
|
334
|
+
export function toTrade(x) {
|
|
335
|
+
const r = x;
|
|
336
|
+
if (!r || typeof r !== "object" || typeof r.txid !== "string" || typeof r.lpPubKey !== "string" || (r.side !== "buy" && r.side !== "sell"))
|
|
337
|
+
return null;
|
|
338
|
+
return {
|
|
339
|
+
txid: r.txid.toLowerCase(),
|
|
340
|
+
poolId: typeof r.poolId === "string" ? r.poolId.toLowerCase() : "",
|
|
341
|
+
lpPubKey: r.lpPubKey.toLowerCase(),
|
|
342
|
+
payoutPkh: typeof r.payoutPkh === "string" ? r.payoutPkh.toLowerCase() : "",
|
|
343
|
+
side: r.side,
|
|
344
|
+
amountIn: wireBig(r.amountIn),
|
|
345
|
+
amountOut: wireBig(r.amountOut),
|
|
346
|
+
feeLp: wireBig(r.feeLp),
|
|
347
|
+
feeValidator: wireBig(r.feeValidator),
|
|
348
|
+
feeCommission: wireBig(r.feeCommission),
|
|
349
|
+
bsvReserve: wireBig(r.bsvReserve),
|
|
350
|
+
tokenReserve: wireBig(r.tokenReserve),
|
|
351
|
+
at: int(r.at),
|
|
352
|
+
height: intOrNull(r.height),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function toCandle(x) {
|
|
356
|
+
const r = x;
|
|
357
|
+
if (!r || typeof r !== "object")
|
|
358
|
+
return null;
|
|
359
|
+
const [o, h, l, c] = [toPrice(r.o), toPrice(r.h), toPrice(r.l), toPrice(r.c)];
|
|
360
|
+
if (!o || !h || !l || !c || typeof r.t !== "number")
|
|
361
|
+
return null;
|
|
362
|
+
return { t: r.t, o, h, l, c, volumeSats: wireBig(r.volumeSats), volumeTokens: wireBig(r.volumeTokens), n: int(r.n) };
|
|
363
|
+
}
|
|
364
|
+
/** A stats record (H3), or null when it is not one. */
|
|
365
|
+
export function toStats(x) {
|
|
366
|
+
const r = x;
|
|
367
|
+
if (!r || typeof r !== "object" || typeof r.tokenId !== "string")
|
|
368
|
+
return null;
|
|
369
|
+
return {
|
|
370
|
+
tokenId: r.tokenId,
|
|
371
|
+
price: toPrice(r.price),
|
|
372
|
+
buyPrice: toPrice(r.buyPrice),
|
|
373
|
+
sellPrice: toPrice(r.sellPrice),
|
|
374
|
+
lastTradePrice: toPrice(r.lastTradePrice),
|
|
375
|
+
change1h: intOrNull(r.change1h),
|
|
376
|
+
change24h: intOrNull(r.change24h),
|
|
377
|
+
volume24hSats: wireBig(r.volume24hSats),
|
|
378
|
+
volume24hTokens: wireBig(r.volume24hTokens),
|
|
379
|
+
buys24h: int(r.buys24h),
|
|
380
|
+
sells24h: int(r.sells24h),
|
|
381
|
+
lastTradeAt: intOrNull(r.lastTradeAt),
|
|
382
|
+
tvlSats: wireBig(r.tvlSats),
|
|
383
|
+
tvlTokens: wireBig(r.tvlTokens),
|
|
384
|
+
pools: int(r.pools),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const query = (q) => {
|
|
388
|
+
const kv = Object.entries(q).filter(([, x]) => x !== undefined).map(([k, x]) => `${k}=${encodeURIComponent(String(x))}`);
|
|
389
|
+
return kv.length ? `?${kv.join("&")}` : "";
|
|
390
|
+
};
|
|
391
|
+
/** H1: `GET <base>/trades?from=&to=&limit=&before=&pool=` (0.40.0: `pool` one pool's) → the page, newest first. */
|
|
392
|
+
export async function readTrades(base, o = {}, fetchFn) {
|
|
393
|
+
const a = (await getJson(`${trim(base)}/trades${query({ from: o.from, to: o.to, limit: o.limit, before: o.before, pool: o.pool })}`, fetchFn));
|
|
394
|
+
return {
|
|
395
|
+
trades: (Array.isArray(a?.trades) ? a.trades : []).map(toTrade).filter((t) => t !== null),
|
|
396
|
+
next: typeof a?.next === "string" ? a.next : null,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/** H1 with `txid`: one Swap's trade row; null when the transaction has none (404). */
|
|
400
|
+
export async function readTrade(base, txid, fetchFn) {
|
|
401
|
+
const url = `${trim(base)}/trades?txid=${encodeURIComponent(txid)}`;
|
|
402
|
+
const res = await (fetchFn ?? plain)(url, { headers: { accept: "application/json" } });
|
|
403
|
+
if (res.status === 404)
|
|
404
|
+
return null;
|
|
405
|
+
if (!res.ok)
|
|
406
|
+
throw new Error(`GET ${url}: ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
407
|
+
return toTrade(await res.json());
|
|
408
|
+
}
|
|
409
|
+
/** H2: `GET <base>/candles?tf=&from=&to=&series=` → one candle per non-empty bucket, oldest first. */
|
|
410
|
+
export async function readCandles(base, o, fetchFn) {
|
|
411
|
+
const a = await getJson(`${trim(base)}/candles${query({ tf: o.tf, from: o.from, to: o.to, series: o.series })}`, fetchFn);
|
|
412
|
+
return (Array.isArray(a) ? a : []).map(toCandle).filter((c) => c !== null);
|
|
413
|
+
}
|
|
414
|
+
/** H3: `GET <base>/stats`. */
|
|
415
|
+
export async function readStats(base, fetchFn) {
|
|
416
|
+
const url = `${trim(base)}/stats`;
|
|
417
|
+
const s = toStats(await getJson(url, fetchFn));
|
|
418
|
+
if (!s)
|
|
419
|
+
throw new Error(`GET ${url}: not a stats record`);
|
|
420
|
+
return s;
|
|
421
|
+
}
|
|
422
|
+
/** H4: `GET <swap base>/stats` (`ids`: those enabled tokens only, in that order) → every enabled token's stats. */
|
|
423
|
+
export async function readAllStats(swapBase, ids, fetchFn) {
|
|
424
|
+
const a = await getJson(`${trim(swapBase)}/stats${ids ? `?ids=${list(ids)}` : ""}`, fetchFn);
|
|
425
|
+
return (Array.isArray(a) ? a : []).map(toStats).filter((s) => s !== null);
|
|
426
|
+
}
|
|
427
|
+
/** H6: `GET <token base>/color/<token>/holders?limit=&skip=` → the count and the rows, largest first. */
|
|
428
|
+
export async function readHolders(base, tokenId, o = {}, fetchFn) {
|
|
429
|
+
const a = (await getJson(`${trim(base)}/color/${encodeURIComponent(tokenId)}/holders${query({ limit: o.limit, skip: o.skip })}`, fetchFn));
|
|
430
|
+
return {
|
|
431
|
+
count: int(a?.count),
|
|
432
|
+
rows: (Array.isArray(a?.rows) ? a.rows : []).filter((r) => r && typeof r.address === "string").map((r) => ({ address: String(r.address), balance: wireBig(r.balance) })),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
/** H5: `GET <swap base>/positions?pool=id1,id2` (0.40.0) → every enabled token's rows for the pools, each with its token. */
|
|
436
|
+
export async function readAllPositions(swapBase, poolIds, fetchFn) {
|
|
437
|
+
if (poolIds.length === 0)
|
|
438
|
+
return [];
|
|
439
|
+
const a = await getJson(`${trim(swapBase)}/positions?pool=${list(poolIds)}`, fetchFn);
|
|
440
|
+
return (Array.isArray(a) ? a : [])
|
|
441
|
+
.filter((r) => r && typeof r.tokenId === "string" && typeof r.poolId === "string")
|
|
442
|
+
.map((r) => ({ tokenId: String(r.tokenId), ...lpPoolOf(r, String(r.poolId)) }));
|
|
443
|
+
}
|
|
444
|
+
/** H7: `GET <base>/pools/at?t=<ms>` → every pool listed at `t`. */
|
|
445
|
+
export async function readPoolsAt(base, t, fetchFn) {
|
|
446
|
+
const a = await getJson(`${trim(base)}/pools/at${query({ t })}`, fetchFn);
|
|
447
|
+
return (Array.isArray(a) ? a : []).flatMap((r) => {
|
|
448
|
+
const row = toPoolRow(r);
|
|
449
|
+
return row ? [{ ...row, at: int(r.at) }] : [];
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
function toPoolEvent(x) {
|
|
453
|
+
const r = x;
|
|
454
|
+
if (!r || typeof r !== "object" || typeof r.txid !== "string" || typeof r.poolId !== "string")
|
|
455
|
+
return null;
|
|
456
|
+
if (r.kind !== "deploy" && r.kind !== "swap" && r.kind !== "close" && r.kind !== "rescind")
|
|
457
|
+
return null;
|
|
458
|
+
const big = (k) => (r[k] === undefined || r[k] === null ? {} : { [k]: wireBig(r[k]) });
|
|
459
|
+
const text = (k) => (typeof r[k] === "string" ? { [k]: String(r[k]).toLowerCase() } : {});
|
|
460
|
+
return {
|
|
461
|
+
kind: r.kind,
|
|
462
|
+
txid: r.txid.toLowerCase(),
|
|
463
|
+
poolId: r.poolId.toLowerCase(),
|
|
464
|
+
...(typeof r.outpoint === "string" ? { outpoint: r.outpoint } : {}),
|
|
465
|
+
...big("bsvReserve"),
|
|
466
|
+
...big("tokenReserve"),
|
|
467
|
+
lpPubKey: typeof r.lpPubKey === "string" ? r.lpPubKey.toLowerCase() : "",
|
|
468
|
+
liquidityFeeBps: wireBig(r.liquidityFeeBps),
|
|
469
|
+
validationFeeBps: wireBig(r.validationFeeBps),
|
|
470
|
+
commissionBps: wireBig(r.commissionBps),
|
|
471
|
+
validatorIdentityKey: typeof r.validatorIdentityKey === "string" ? r.validatorIdentityKey.toLowerCase() : "",
|
|
472
|
+
at: int(r.at),
|
|
473
|
+
swaps: int(r.swaps),
|
|
474
|
+
...(r.side === "buy" || r.side === "sell" ? { side: r.side } : {}),
|
|
475
|
+
...big("amountIn"),
|
|
476
|
+
...big("amountOut"),
|
|
477
|
+
...big("feeLp"),
|
|
478
|
+
...big("feeValidator"),
|
|
479
|
+
...big("feeCommission"),
|
|
480
|
+
...text("commissionPkh"),
|
|
481
|
+
...text("payoutPkh"),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function toPoolRevert(r) {
|
|
485
|
+
if (r.kind !== "revert" || typeof r.txid !== "string" || typeof r.poolId !== "string")
|
|
486
|
+
return null;
|
|
487
|
+
const base = { kind: "revert", txid: r.txid.toLowerCase(), poolId: r.poolId.toLowerCase(), at: int(r.at) };
|
|
488
|
+
if (typeof r.outpoint !== "string")
|
|
489
|
+
return base;
|
|
490
|
+
return {
|
|
491
|
+
...base,
|
|
492
|
+
outpoint: r.outpoint,
|
|
493
|
+
bsvReserve: wireBig(r.bsvReserve),
|
|
494
|
+
tokenReserve: wireBig(r.tokenReserve),
|
|
495
|
+
lpPubKey: typeof r.lpPubKey === "string" ? r.lpPubKey.toLowerCase() : "",
|
|
496
|
+
liquidityFeeBps: wireBig(r.liquidityFeeBps),
|
|
497
|
+
validationFeeBps: wireBig(r.validationFeeBps),
|
|
498
|
+
commissionBps: wireBig(r.commissionBps),
|
|
499
|
+
validatorIdentityKey: typeof r.validatorIdentityKey === "string" ? r.validatorIdentityKey.toLowerCase() : "",
|
|
500
|
+
swaps: int(r.swaps),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
/** A pool event feed message's data, or null when it is not one. */
|
|
504
|
+
export function parsePoolEventMessage(x) {
|
|
505
|
+
const r = x;
|
|
506
|
+
if (!r || typeof r !== "object" || typeof r.tokenId !== "string")
|
|
507
|
+
return null;
|
|
508
|
+
const row = r.kind === "revert" ? toPoolRevert(r) : toPoolEvent(r);
|
|
509
|
+
return row ? { tokenId: r.tokenId, ...row } : null;
|
|
510
|
+
}
|
|
511
|
+
/** H8: `GET <base>/pools/history?from=&to=&pool=&limit=&after=` → the page, oldest first. */
|
|
512
|
+
export async function readPoolHistory(base, o = {}, fetchFn) {
|
|
513
|
+
const a = (await getJson(`${trim(base)}/pools/history${query({ from: o.from, to: o.to, pool: o.pool, limit: o.limit, after: o.after })}`, fetchFn));
|
|
514
|
+
return {
|
|
515
|
+
events: (Array.isArray(a?.events) ? a.events : []).map(toPoolEvent).filter((e) => e !== null),
|
|
516
|
+
next: typeof a?.next === "string" ? a.next : null,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/** H9: `GET <swap base>/spends?outpoint=a,b` (the host's: every enabled token's `/spends`, the first naming a spender) → one record per outpoint. */
|
|
520
|
+
export async function readSpendsAll(swapBase, outpoints, fetchFn) {
|
|
521
|
+
if (outpoints.length === 0)
|
|
522
|
+
return [];
|
|
523
|
+
const a = await getJson(`${trim(swapBase)}/spends?outpoint=${list(outpoints)}`, fetchFn);
|
|
524
|
+
return (Array.isArray(a) ? a : [a])
|
|
525
|
+
.filter((r) => r && typeof r.outpoint === "string")
|
|
526
|
+
.map((r) => ({
|
|
527
|
+
outpoint: String(r.outpoint),
|
|
528
|
+
...(typeof r.tokenId === "string" ? { tokenId: r.tokenId } : {}),
|
|
529
|
+
...(typeof r.spentBy === "string" ? { spentBy: r.spentBy.toLowerCase() } : {}),
|
|
530
|
+
}));
|
|
531
|
+
}
|
package/dist/lib/skein.d.ts
CHANGED
|
@@ -63,10 +63,12 @@ export interface EnabledToken {
|
|
|
63
63
|
export declare function readTokens(base: string, fetchFn?: typeof fetch): Promise<EnabledToken[]>;
|
|
64
64
|
/** The defaults a new token instance is created with (0.17.0, `GET <config>/defaults`): each absent when unset. */
|
|
65
65
|
export interface Defaults {
|
|
66
|
+
/** 0.40.0: `minLegSats` present when the defaults set one. */
|
|
66
67
|
terms?: {
|
|
67
68
|
lpFeeBps: number;
|
|
68
69
|
validatorFeeBps: number;
|
|
69
70
|
commissionBps: number;
|
|
71
|
+
minLegSats?: number;
|
|
70
72
|
};
|
|
71
73
|
market?: {
|
|
72
74
|
minCommissionBps: number;
|
package/dist/lib/skein.js
CHANGED
|
@@ -134,7 +134,7 @@ export async function readDefaults(base, fetchFn = fetch) {
|
|
|
134
134
|
const out = {};
|
|
135
135
|
const t = a?.terms;
|
|
136
136
|
if (t && typeof t.lpFeeBps === "number" && typeof t.validatorFeeBps === "number" && typeof t.commissionBps === "number")
|
|
137
|
-
out.terms = { lpFeeBps: t.lpFeeBps, validatorFeeBps: t.validatorFeeBps, commissionBps: t.commissionBps };
|
|
137
|
+
out.terms = { lpFeeBps: t.lpFeeBps, validatorFeeBps: t.validatorFeeBps, commissionBps: t.commissionBps, ...(typeof t.minLegSats === "number" && t.minLegSats > 0 ? { minLegSats: t.minLegSats } : {}) };
|
|
138
138
|
const m = a?.market;
|
|
139
139
|
if (m && typeof m.minCommissionBps === "number")
|
|
140
140
|
out.market = { minCommissionBps: m.minCommissionBps };
|