@dcasegr/amm-client 0.39.0 → 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 +63 -30
- package/dist/client.d.ts +157 -61
- package/dist/client.js +339 -320
- 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 +132 -50
- package/dist/lib/overlay.js +169 -160
- 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/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,16 +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=&
|
|
29
|
-
* | H2 | `GET /@<token>/amm/candles?tf=&from=&to
|
|
30
|
-
* | H3 | `GET /@<token>/amm/stats` | `{tokenId, price, change1h, change24h, volume24hSats, volume24hTokens, buys24h, sells24h, lastTradeAt, tvlSats, tvlTokens, pools}` |
|
|
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
31
|
* | H4 | `GET <origin>/amm/stats?ids=` | `[H3]`, every enabled token's (the host's) |
|
|
32
|
-
* | H5 | `GET <origin>/amm/positions?
|
|
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 |
|
|
33
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 |
|
|
34
38
|
*
|
|
35
|
-
* 0.
|
|
36
|
-
* (`pools`
|
|
37
|
-
*
|
|
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.
|
|
38
46
|
*
|
|
39
47
|
* Writes stay what they were: `POST <origin>/submit` (BRC-22, plain), `/call` and `/sendMessage`
|
|
40
48
|
* (signed, the wallet's BRC-104 client).
|
|
@@ -83,7 +91,9 @@ export function toPoolRow(raw) {
|
|
|
83
91
|
return null;
|
|
84
92
|
return {
|
|
85
93
|
...toPoolState(raw),
|
|
94
|
+
poolId: typeof raw.poolId === "string" ? raw.poolId.toLowerCase() : "",
|
|
86
95
|
lpPubKey: typeof raw.lpPubKey === "string" ? raw.lpPubKey.toLowerCase() : "",
|
|
96
|
+
swaps: Number(raw.swaps ?? 0),
|
|
87
97
|
...(typeof raw.validatorPeerId === "string" && raw.validatorPeerId ? { validatorPeerId: raw.validatorPeerId } : {}),
|
|
88
98
|
...(typeof raw.tokenId === "string" ? { tokenId: raw.tokenId } : {}),
|
|
89
99
|
};
|
|
@@ -102,7 +112,10 @@ export function parsePools(x) {
|
|
|
102
112
|
const a = x;
|
|
103
113
|
const validators = (Array.isArray(a?.validators) ? a.validators : [])
|
|
104
114
|
.filter((v) => v && typeof v.identity === "string")
|
|
105
|
-
.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
|
+
});
|
|
106
119
|
return { market: toMarket(a?.market), pools: poolRows(a?.pools), validators };
|
|
107
120
|
}
|
|
108
121
|
/** P1's JSON: every enabled token's rows, each naming its `tokenId`. */
|
|
@@ -141,7 +154,7 @@ const list = (xs) => xs.map(encodeURIComponent).join(",");
|
|
|
141
154
|
export function poolsUrl(base) {
|
|
142
155
|
return `${trim(base)}/pools`;
|
|
143
156
|
}
|
|
144
|
-
/** P2
|
|
157
|
+
/** P2: the token's pools as the host lists them now (0.41.0: a plain read only). */
|
|
145
158
|
export async function readPools(base, fetchFn) {
|
|
146
159
|
return parsePools(await getJson(poolsUrl(base), fetchFn));
|
|
147
160
|
}
|
|
@@ -149,89 +162,43 @@ function eventSourceCtor() {
|
|
|
149
162
|
const g = globalThis;
|
|
150
163
|
return typeof g.EventSource === "function" ? g.EventSource : null;
|
|
151
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
|
+
}
|
|
152
169
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
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.
|
|
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.
|
|
162
173
|
*/
|
|
163
|
-
export function
|
|
174
|
+
export function watchPoolEvents(url, onEvent, o = {}) {
|
|
164
175
|
const ES = o.EventSource === undefined ? eventSourceCtor() : o.EventSource;
|
|
176
|
+
if (!ES)
|
|
177
|
+
return () => { };
|
|
165
178
|
let stopped = false;
|
|
166
|
-
if (!ES) {
|
|
167
|
-
getJson(url, o.fetch).then((x) => {
|
|
168
|
-
if (!stopped)
|
|
169
|
-
onAnswer(parse(x));
|
|
170
|
-
}, (e) => {
|
|
171
|
-
if (!stopped)
|
|
172
|
-
o.onError?.(e);
|
|
173
|
-
});
|
|
174
|
-
return () => {
|
|
175
|
-
stopped = true;
|
|
176
|
-
};
|
|
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
|
-
};
|
|
196
179
|
const es = new ES(url);
|
|
197
|
-
es.addEventListener("
|
|
180
|
+
es.addEventListener("pool", (e) => {
|
|
198
181
|
if (stopped)
|
|
199
182
|
return;
|
|
183
|
+
let m = null;
|
|
200
184
|
try {
|
|
201
|
-
|
|
185
|
+
m = parsePoolEventMessage(JSON.parse(String(e.data)));
|
|
202
186
|
}
|
|
203
|
-
catch
|
|
204
|
-
|
|
187
|
+
catch {
|
|
188
|
+
m = null;
|
|
205
189
|
}
|
|
190
|
+
if (m)
|
|
191
|
+
onEvent(m);
|
|
206
192
|
});
|
|
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
|
-
}
|
|
225
|
-
es.onopen = () => o.onOpen?.();
|
|
226
193
|
es.onerror = (e) => o.onError?.(e);
|
|
227
194
|
return () => {
|
|
228
195
|
stopped = true;
|
|
229
196
|
es.close();
|
|
230
197
|
};
|
|
231
198
|
}
|
|
232
|
-
/** P3: `GET <base>/pools/beef?
|
|
233
|
-
export async function readPoolsBeef(base,
|
|
234
|
-
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)}` : ""}`;
|
|
235
202
|
const a = (await getJson(url, fetchFn));
|
|
236
203
|
return { pools: poolRows(a?.pools), beef: typeof a?.beef === "string" && a.beef ? Utils.toArray(a.beef, "base64") : [] };
|
|
237
204
|
}
|
|
@@ -246,24 +213,27 @@ export function poolOutputOf(pb, outpoint) {
|
|
|
246
213
|
return { beef: beef.toBinaryAtomic(m[1].toLowerCase()), outputIndex: Number(m[2]) };
|
|
247
214
|
}
|
|
248
215
|
const wireBig = (x) => (typeof x === "number" || typeof x === "string" || typeof x === "bigint" ? BigInt(x) : 0n);
|
|
249
|
-
/** P4
|
|
250
|
-
|
|
251
|
-
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)}`;
|
|
252
233
|
const a = await getJson(url, fetchFn);
|
|
253
|
-
if (!Array.isArray(a) || a.length !==
|
|
254
|
-
throw new Error(`GET ${url}: not one record per
|
|
255
|
-
return a.map((r, i) =>
|
|
256
|
-
const status = r.status === "live" || r.status === "rescinded" ? r.status : "closed";
|
|
257
|
-
return {
|
|
258
|
-
lpPubKey: typeof r.lpPubKey === "string" ? r.lpPubKey : lpPubKeys[i],
|
|
259
|
-
status,
|
|
260
|
-
outpoint: typeof r.outpoint === "string" ? r.outpoint : null,
|
|
261
|
-
current: isWirePool(r.current) ? toPoolState(r.current) : null,
|
|
262
|
-
sats: wireBig(r.sats),
|
|
263
|
-
tokens: wireBig(r.tokens),
|
|
264
|
-
swaps: Number(r.swaps ?? 0),
|
|
265
|
-
};
|
|
266
|
-
});
|
|
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]));
|
|
267
237
|
}
|
|
268
238
|
/** P7: `GET <base>/terms`, unsigned. */
|
|
269
239
|
export async function readTerms(base, fetchFn) {
|
|
@@ -283,6 +253,7 @@ export async function readTerms(base, fetchFn) {
|
|
|
283
253
|
lpFeeBps: int(v.lpFeeBps, "lpFeeBps"),
|
|
284
254
|
validatorFeeBps: int(v.validatorFeeBps, "validatorFeeBps"),
|
|
285
255
|
commissionBps: int(v.commissionBps, "commissionBps"),
|
|
256
|
+
minLegSats: typeof v.minLegSats === "number" && Number.isInteger(v.minLegSats) ? v.minLegSats : 0,
|
|
286
257
|
}
|
|
287
258
|
: null,
|
|
288
259
|
market: toMarket(a?.market),
|
|
@@ -366,7 +337,9 @@ export function toTrade(x) {
|
|
|
366
337
|
return null;
|
|
367
338
|
return {
|
|
368
339
|
txid: r.txid.toLowerCase(),
|
|
340
|
+
poolId: typeof r.poolId === "string" ? r.poolId.toLowerCase() : "",
|
|
369
341
|
lpPubKey: r.lpPubKey.toLowerCase(),
|
|
342
|
+
payoutPkh: typeof r.payoutPkh === "string" ? r.payoutPkh.toLowerCase() : "",
|
|
370
343
|
side: r.side,
|
|
371
344
|
amountIn: wireBig(r.amountIn),
|
|
372
345
|
amountOut: wireBig(r.amountOut),
|
|
@@ -396,6 +369,9 @@ export function toStats(x) {
|
|
|
396
369
|
return {
|
|
397
370
|
tokenId: r.tokenId,
|
|
398
371
|
price: toPrice(r.price),
|
|
372
|
+
buyPrice: toPrice(r.buyPrice),
|
|
373
|
+
sellPrice: toPrice(r.sellPrice),
|
|
374
|
+
lastTradePrice: toPrice(r.lastTradePrice),
|
|
399
375
|
change1h: intOrNull(r.change1h),
|
|
400
376
|
change24h: intOrNull(r.change24h),
|
|
401
377
|
volume24hSats: wireBig(r.volume24hSats),
|
|
@@ -408,59 +384,13 @@ export function toStats(x) {
|
|
|
408
384
|
pools: int(r.pools),
|
|
409
385
|
};
|
|
410
386
|
}
|
|
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
387
|
const query = (q) => {
|
|
458
388
|
const kv = Object.entries(q).filter(([, x]) => x !== undefined).map(([k, x]) => `${k}=${encodeURIComponent(String(x))}`);
|
|
459
389
|
return kv.length ? `?${kv.join("&")}` : "";
|
|
460
390
|
};
|
|
461
|
-
/** H1: `GET <base>/trades?from=&to=&limit=&before=&
|
|
391
|
+
/** H1: `GET <base>/trades?from=&to=&limit=&before=&pool=` (0.40.0: `pool` one pool's) → the page, newest first. */
|
|
462
392
|
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,
|
|
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));
|
|
464
394
|
return {
|
|
465
395
|
trades: (Array.isArray(a?.trades) ? a.trades : []).map(toTrade).filter((t) => t !== null),
|
|
466
396
|
next: typeof a?.next === "string" ? a.next : null,
|
|
@@ -476,9 +406,9 @@ export async function readTrade(base, txid, fetchFn) {
|
|
|
476
406
|
throw new Error(`GET ${url}: ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
477
407
|
return toTrade(await res.json());
|
|
478
408
|
}
|
|
479
|
-
/** H2: `GET <base>/candles?tf=&from=&to=` → one candle per non-empty bucket, oldest first. */
|
|
409
|
+
/** H2: `GET <base>/candles?tf=&from=&to=&series=` → one candle per non-empty bucket, oldest first. */
|
|
480
410
|
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);
|
|
411
|
+
const a = await getJson(`${trim(base)}/candles${query({ tf: o.tf, from: o.from, to: o.to, series: o.series })}`, fetchFn);
|
|
482
412
|
return (Array.isArray(a) ? a : []).map(toCandle).filter((c) => c !== null);
|
|
483
413
|
}
|
|
484
414
|
/** H3: `GET <base>/stats`. */
|
|
@@ -502,21 +432,100 @@ export async function readHolders(base, tokenId, o = {}, fetchFn) {
|
|
|
502
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) })),
|
|
503
433
|
};
|
|
504
434
|
}
|
|
505
|
-
/** H5: `GET <swap base>/positions?
|
|
506
|
-
export async function readAllPositions(swapBase,
|
|
507
|
-
if (
|
|
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)
|
|
508
438
|
return [];
|
|
509
|
-
const a = await getJson(`${trim(swapBase)}/positions?
|
|
439
|
+
const a = await getJson(`${trim(swapBase)}/positions?pool=${list(poolIds)}`, fetchFn);
|
|
510
440
|
return (Array.isArray(a) ? a : [])
|
|
511
|
-
.filter((r) => r && typeof r.tokenId === "string" && typeof r.
|
|
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")
|
|
512
526
|
.map((r) => ({
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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),
|
|
527
|
+
outpoint: String(r.outpoint),
|
|
528
|
+
...(typeof r.tokenId === "string" ? { tokenId: r.tokenId } : {}),
|
|
529
|
+
...(typeof r.spentBy === "string" ? { spentBy: r.spentBy.toLowerCase() } : {}),
|
|
521
530
|
}));
|
|
522
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 };
|