@dcasegr/amm-client 0.0.0-stage → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +107 -2
  2. package/dist/client.d.ts +534 -0
  3. package/dist/client.js +1319 -0
  4. package/dist/errors.d.ts +17 -0
  5. package/dist/errors.js +75 -0
  6. package/dist/index.d.ts +51 -0
  7. package/dist/index.js +53 -0
  8. package/dist/lib/config.d.ts +42 -0
  9. package/dist/lib/config.js +64 -0
  10. package/dist/lib/keys.d.ts +42 -0
  11. package/dist/lib/keys.js +48 -0
  12. package/dist/lib/overlay.d.ts +190 -0
  13. package/dist/lib/overlay.js +302 -0
  14. package/dist/lib/skein.d.ts +82 -0
  15. package/dist/lib/skein.js +142 -0
  16. package/dist/lib/tokenId.d.ts +72 -0
  17. package/dist/lib/tokenId.js +121 -0
  18. package/dist/lp/amounts.d.ts +12 -0
  19. package/dist/lp/amounts.js +38 -0
  20. package/dist/lp/close.d.ts +90 -0
  21. package/dist/lp/close.js +140 -0
  22. package/dist/lp/images.d.ts +11 -0
  23. package/dist/lp/images.js +45 -0
  24. package/dist/lp/inventory.d.ts +68 -0
  25. package/dist/lp/inventory.js +84 -0
  26. package/dist/lp/poolDeploy.d.ts +220 -0
  27. package/dist/lp/poolDeploy.js +291 -0
  28. package/dist/lp/poolRows.d.ts +13 -0
  29. package/dist/lp/poolRows.js +19 -0
  30. package/dist/lp/positions.d.ts +81 -0
  31. package/dist/lp/positions.js +144 -0
  32. package/dist/lp/rebalance.d.ts +101 -0
  33. package/dist/lp/rebalance.js +168 -0
  34. package/dist/lp/wallet.d.ts +12 -0
  35. package/dist/lp/wallet.js +19 -0
  36. package/dist/market/activeSwap.d.ts +158 -0
  37. package/dist/market/activeSwap.js +320 -0
  38. package/dist/market/plan.d.ts +85 -0
  39. package/dist/market/plan.js +149 -0
  40. package/dist/market/prices.d.ts +13 -0
  41. package/dist/market/prices.js +14 -0
  42. package/dist/market/relay.d.ts +84 -0
  43. package/dist/market/relay.js +201 -0
  44. package/dist/market/swapAction.d.ts +306 -0
  45. package/dist/market/swapAction.js +508 -0
  46. package/dist/market/swapFlow.d.ts +58 -0
  47. package/dist/market/swapFlow.js +86 -0
  48. package/dist/market/view.d.ts +58 -0
  49. package/dist/market/view.js +63 -0
  50. package/dist/pool/artifact.d.ts +23 -0
  51. package/dist/pool/artifact.js +38 -0
  52. package/dist/pool/index.d.ts +8 -0
  53. package/dist/pool/index.js +8 -0
  54. package/dist/pool/oneColor.d.ts +19 -0
  55. package/dist/pool/oneColor.js +39 -0
  56. package/dist/pool/pool.artifact.json +5069 -0
  57. package/dist/pool/runar-ir-schema.d.ts +21 -0
  58. package/dist/pool/runar-ir-schema.js +18 -0
  59. package/dist/pool/template.d.ts +295 -0
  60. package/dist/pool/template.js +715 -0
  61. package/dist/vendor/runar/LICENSE +21 -0
  62. package/dist/vendor/runar/NOTICE +15 -0
  63. package/dist/vendor/runar/runar-ir-schema/src/abi-type-encoding.d.ts +42 -0
  64. package/dist/vendor/runar/runar-ir-schema/src/abi-type-encoding.js +53 -0
  65. package/dist/vendor/runar/runar-ir-schema/src/anf-ir.d.ts +221 -0
  66. package/dist/vendor/runar/runar-ir-schema/src/anf-ir.js +33 -0
  67. package/dist/vendor/runar/runar-ir-schema/src/artifact.d.ts +288 -0
  68. package/dist/vendor/runar/runar-ir-schema/src/artifact.js +8 -0
  69. package/dist/vendor/runar/runar-ir-schema/src/canonical-json.d.ts +43 -0
  70. package/dist/vendor/runar/runar-ir-schema/src/canonical-json.js +204 -0
  71. package/dist/vendor/runar/runar-ir-schema/src/input-limits.d.ts +89 -0
  72. package/dist/vendor/runar/runar-ir-schema/src/input-limits.js +100 -0
  73. package/dist/vendor/runar/runar-ir-schema/src/stack-ir.d.ts +118 -0
  74. package/dist/vendor/runar/runar-ir-schema/src/stack-ir.js +8 -0
  75. package/dist/vendor/runar/runar-ir-schema/src/state-layout.d.ts +56 -0
  76. package/dist/vendor/runar/runar-ir-schema/src/state-layout.js +145 -0
  77. package/dist/vendor/runar/runar-ir-schema/src/unknown-anf-kind-error.d.ts +18 -0
  78. package/dist/vendor/runar/runar-ir-schema/src/unknown-anf-kind-error.js +25 -0
  79. package/dist/vendor/runar/runar-sdk/src/anf-interpreter.d.ts +160 -0
  80. package/dist/vendor/runar/runar-sdk/src/anf-interpreter.js +1149 -0
  81. package/dist/vendor/runar/runar-sdk/src/calling.d.ts +98 -0
  82. package/dist/vendor/runar/runar-sdk/src/calling.js +209 -0
  83. package/dist/vendor/runar/runar-sdk/src/contract.d.ts +443 -0
  84. package/dist/vendor/runar/runar-sdk/src/contract.js +2516 -0
  85. package/dist/vendor/runar/runar-sdk/src/deployment.d.ts +36 -0
  86. package/dist/vendor/runar/runar-sdk/src/deployment.js +119 -0
  87. package/dist/vendor/runar/runar-sdk/src/errors.d.ts +46 -0
  88. package/dist/vendor/runar/runar-sdk/src/errors.js +59 -0
  89. package/dist/vendor/runar/runar-sdk/src/oppushtx.d.ts +23 -0
  90. package/dist/vendor/runar/runar-sdk/src/oppushtx.js +95 -0
  91. package/dist/vendor/runar/runar-sdk/src/ordinals/envelope.d.ts +31 -0
  92. package/dist/vendor/runar/runar-sdk/src/ordinals/envelope.js +272 -0
  93. package/dist/vendor/runar/runar-sdk/src/ordinals/types.d.ts +21 -0
  94. package/dist/vendor/runar/runar-sdk/src/ordinals/types.js +4 -0
  95. package/dist/vendor/runar/runar-sdk/src/providers/provider.d.ts +65 -0
  96. package/dist/vendor/runar/runar-sdk/src/providers/provider.js +59 -0
  97. package/dist/vendor/runar/runar-sdk/src/providers/wallet-provider.d.ts +94 -0
  98. package/dist/vendor/runar/runar-sdk/src/providers/wallet-provider.js +297 -0
  99. package/dist/vendor/runar/runar-sdk/src/script-utils.d.ts +54 -0
  100. package/dist/vendor/runar/runar-sdk/src/script-utils.js +252 -0
  101. package/dist/vendor/runar/runar-sdk/src/signers/signer.d.ts +17 -0
  102. package/dist/vendor/runar/runar-sdk/src/signers/signer.js +4 -0
  103. package/dist/vendor/runar/runar-sdk/src/spend-safety.d.ts +31 -0
  104. package/dist/vendor/runar/runar-sdk/src/spend-safety.js +35 -0
  105. package/dist/vendor/runar/runar-sdk/src/state.d.ts +54 -0
  106. package/dist/vendor/runar/runar-sdk/src/state.js +584 -0
  107. package/dist/vendor/runar/runar-sdk/src/types.d.ts +227 -0
  108. package/dist/vendor/runar/runar-sdk/src/types.js +4 -0
  109. package/dist/vendor/runar/runar-sdk/src/unsound-primitives.d.ts +30 -0
  110. package/dist/vendor/runar/runar-sdk/src/unsound-primitives.js +42 -0
  111. package/dist/vendor/runar/runar-sdk.d.ts +9 -0
  112. package/dist/vendor/runar/runar-sdk.js +8 -0
  113. package/dist/wallet/action.d.ts +28 -0
  114. package/dist/wallet/action.js +42 -0
  115. package/dist/wallet/brc29.d.ts +83 -0
  116. package/dist/wallet/brc29.js +80 -0
  117. package/dist/wallet/color.d.ts +110 -0
  118. package/dist/wallet/color.js +134 -0
  119. package/dist/wallet/swapPayouts.d.ts +35 -0
  120. package/dist/wallet/swapPayouts.js +146 -0
  121. package/package.json +39 -4
@@ -0,0 +1,302 @@
1
+ /**
2
+ * The exchange's public read API (skein-amm 0.34.0; agent-env plans/exchange-api-2026-11-04.md §0,
3
+ * D1–D25; docs/AMM.md "The public read API"): plain GETs (D7), JSON unless the row says bytes, list
4
+ * parameters comma-separated, one record per key in query order (D1). The pages read these and
5
+ * nothing else of the overlay: they send `/lookup` nowhere (D8: `/submit` and `/lookup` are the
6
+ * overlay protocol other overlays and the wallet's module speak; the pages submit at the host
7
+ * root, `<origin>/submit` + `X-Topics`, src/lp/close.ts `submitToOverlay`).
8
+ *
9
+ * | # | route | answer |
10
+ * |---|---|---|
11
+ * | P1 | `GET <origin>/amm/pools` (and as an event stream) | `[{tokenId, …P2 row}]`, every enabled token's live pools (the host's) |
12
+ * | P2 | `GET <origin>/@<token>/amm/pools` (and as an event stream) | `{market: {commissionPkh, minCommissionBps}, validators: [{identity, peerId}] (0.34.1: beating now), pools: [{lpPubKey, outpoint, bsvReserve, tokenReserve, liquidityFeeBps, validationFeeBps, commissionBps, validatorIdentityKey, validatorPeerId}]}`: 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?lp=k1,k2` | `{pools: [P2 row without validatorPeerId], beef: <base64 BEEF of their transactions>}` |
14
+ * | P4 | `GET /@<token>/amm/positions?lp=k1,k2` | `[{lpPubKey, status: live|rescinded|closed, outpoint?, current?, sats, tokens, swaps}]` |
15
+ * | P5 | `GET /@<token>/amm/fees/validator?id=k1,k2` | `[{validatorIdentityKey, sats, tokens, swaps, pools}]` |
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}}` |
18
+ * | P8 | `GET /@<token>/amm/spends?outpoint=a,b` | `[{outpoint, spentBy?, current?, hops, closed}]` (one outpoint: the object) |
19
+ * | P9 | `GET /@<token>/amm/tx?id=<txid>` | the transaction's BEEF (bytes) |
20
+ * | P10 | `GET /@<token>/amm/swaps?id=<id>` | the relay record, as `amm.swap.status` answers it |
21
+ * | P11 | `GET /@<token>/amm/token` | the token's record |
22
+ * | P12 | `GET <origin>/amm/tokens` | the exchange's token list (src/lib/skein.ts `readTokens`) |
23
+ * | P13 | `GET /@discovery/1color/tokens?ids=a,b` (or paged `?limit=&skip=`) | `[{tokenId, topic, sym, dec, iconUrl?, txid, vout}]`; an id not on discovery `{tokenId, listed: false}` |
24
+ * | P14 | `GET /@discovery/1color/<token>` | `{tokenId, topic, sym, dec, txid, vout, iconUrl?}` |
25
+ * | P15 | `GET /@discovery/1color/<token>/icon` | the icon's bytes |
26
+ * | P16 | `GET /@discovery/1color/<token>/deploy` | the deploy's BEEF (bytes) |
27
+ * | P17 | `GET <origin>/.well-known/skein`, `/skeins?key=`, `/amm/admin`, `/amm/defaults`, `/amm/discovery` | as before |
28
+ *
29
+ * Writes stay what they were: `POST <origin>/submit` (BRC-22, plain), `/call` and `/sendMessage`
30
+ * (signed, the wallet's BRC-104 client).
31
+ *
32
+ * Names (skein-1color docs/1COLOR.md "The topic"): a token's topic is `tm_color_<assetId>`
33
+ * (BRC-207), the asset id `<txid>_<vout>`, `_0` included.
34
+ */
35
+ import { Beef, Utils } from "@bsv/sdk";
36
+ import { parseTokenId, tokenIdText } from "./tokenId.js";
37
+ /** The AMM pool lookup service of a token (programs/amm-lookup; 0.21.0): `ls_amm_<assetId>`. A name only: the pages send it no query. */
38
+ export function ammLookupService(tokenId) {
39
+ const ref = parseTokenId(tokenId);
40
+ if (!ref)
41
+ throw new Error(`not a token id: ${tokenId}`);
42
+ return `ls_amm_${tokenIdText(ref)}`;
43
+ }
44
+ const TOPIC = /^tm_color_([0-9a-f]{64})_(0|[1-9]\d*)$/;
45
+ /** A token topic name, `tm_color_<txid>_<vout>` (BRC-207), or null for anything else. */
46
+ export function parseTokenTopic(name) {
47
+ const m = TOPIC.exec(name);
48
+ if (!m)
49
+ return null;
50
+ const txid = m[1];
51
+ const vout = Number(m[2]);
52
+ return { topic: name, txid, vout, kind: vout === 0 ? "native" : "legacy", tokenId: tokenIdText({ txid, vout }) };
53
+ }
54
+ /** The engine's PoolState from a wire row (the reserves and fees as bigints). */
55
+ export function toPoolState(raw) {
56
+ return {
57
+ outpoint: raw.outpoint,
58
+ bsvReserve: BigInt(raw.bsvReserve),
59
+ tokenReserve: BigInt(raw.tokenReserve),
60
+ liquidityFeeBps: BigInt(raw.liquidityFeeBps),
61
+ validationFeeBps: BigInt(raw.validationFeeBps),
62
+ commissionBps: BigInt(raw.commissionBps ?? 0),
63
+ validatorIdentityKey: raw.validatorIdentityKey,
64
+ };
65
+ }
66
+ function isWirePool(x) {
67
+ const r = x;
68
+ return !!r && typeof r === "object" && typeof r.outpoint === "string" && typeof r.validatorIdentityKey === "string" && r.bsvReserve !== undefined && r.tokenReserve !== undefined;
69
+ }
70
+ /** One pool row, or null when it is not one. */
71
+ export function toPoolRow(raw) {
72
+ if (!isWirePool(raw))
73
+ return null;
74
+ return {
75
+ ...toPoolState(raw),
76
+ lpPubKey: typeof raw.lpPubKey === "string" ? raw.lpPubKey.toLowerCase() : "",
77
+ ...(typeof raw.validatorPeerId === "string" && raw.validatorPeerId ? { validatorPeerId: raw.validatorPeerId } : {}),
78
+ ...(typeof raw.tokenId === "string" ? { tokenId: raw.tokenId } : {}),
79
+ };
80
+ }
81
+ function poolRows(x) {
82
+ return (Array.isArray(x) ? x : []).map(toPoolRow).filter((r) => r !== null);
83
+ }
84
+ function toMarket(x) {
85
+ const m = x;
86
+ const pkh = typeof m?.commissionPkh === "string" && /^[0-9a-fA-F]{40}$/.test(m.commissionPkh) ? m.commissionPkh.toLowerCase() : null;
87
+ const min = typeof m?.minCommissionBps === "number" && Number.isInteger(m.minCommissionBps) ? m.minCommissionBps : 0;
88
+ return { commissionPkh: pkh, minCommissionBps: min };
89
+ }
90
+ /** P2's JSON. */
91
+ export function parsePools(x) {
92
+ const a = x;
93
+ const validators = (Array.isArray(a?.validators) ? a.validators : [])
94
+ .filter((v) => v && typeof v.identity === "string")
95
+ .map((v) => ({ identity: String(v.identity).toLowerCase(), peerId: typeof v.peerId === "string" ? v.peerId : "" }));
96
+ return { market: toMarket(a?.market), pools: poolRows(a?.pools), validators };
97
+ }
98
+ /** P1's JSON: every enabled token's rows, each naming its `tokenId`. */
99
+ export function parseAllPools(x) {
100
+ return poolRows(x).filter((r) => typeof r.tokenId === "string");
101
+ }
102
+ /** The validators of the pools route's rows (every one live: the host listed only those), as the plan reads liveness. */
103
+ export function liveOfPools(rows, now = Date.now()) {
104
+ const byKey = new Map();
105
+ for (const r of rows) {
106
+ const k = r.validatorIdentityKey.toLowerCase();
107
+ if (!r.validatorPeerId || byKey.has(k))
108
+ continue;
109
+ byKey.set(k, { identityKey: k, peerId: r.validatorPeerId, at: 0, ageMs: 0, live: true });
110
+ }
111
+ return { now, validators: [...byKey.values()], kept: true };
112
+ }
113
+ const plain = (url, init) => fetch(url, init);
114
+ async function getJson(url, fetchFn = plain) {
115
+ const res = await fetchFn(url, { headers: { accept: "application/json" } });
116
+ if (!res.ok)
117
+ throw new Error(`GET ${url}: ${res.status} ${(await res.text()).slice(0, 200)}`);
118
+ return res.json();
119
+ }
120
+ async function getBytes(url, fetchFn = plain) {
121
+ const res = await fetchFn(url, { headers: { accept: "application/octet-stream" } });
122
+ if (res.status === 404)
123
+ return null;
124
+ if (!res.ok)
125
+ throw new Error(`GET ${url}: ${res.status} ${(await res.text()).slice(0, 200)}`);
126
+ return Array.from(new Uint8Array(await res.arrayBuffer()));
127
+ }
128
+ const trim = (base) => base.replace(/\/+$/, "");
129
+ const list = (xs) => xs.map(encodeURIComponent).join(",");
130
+ /** P1 / P2's URL: `<base>/pools` (the swap instance's app base: every token; a token instance's: that token). */
131
+ export function poolsUrl(base) {
132
+ return `${trim(base)}/pools`;
133
+ }
134
+ /** P2 once (a one-shot reader; the Swap page holds the stream, `watchPools`). */
135
+ export async function readPools(base, fetchFn) {
136
+ return parsePools(await getJson(poolsUrl(base), fetchFn));
137
+ }
138
+ function eventSourceCtor() {
139
+ const g = globalThis;
140
+ return typeof g.EventSource === "function" ? g.EventSource : null;
141
+ }
142
+ /**
143
+ * Holds a pools route (P1 or P2) as an event stream (D16, D22): `GET <url>` with `Accept:
144
+ * text/event-stream` (the browser's EventSource sends it), the event `pools` carrying the answer,
145
+ * first the current one, then again each time it changes; `onAnswer` gets each. Without an
146
+ * EventSource (an old browser, a test): ONE plain GET, no poll. → the unsubscribe.
147
+ */
148
+ export function watchPools(url, parse, onAnswer, o = {}) {
149
+ const ES = o.EventSource === undefined ? eventSourceCtor() : o.EventSource;
150
+ 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
+ const es = new ES(url);
164
+ es.addEventListener("pools", (e) => {
165
+ if (stopped)
166
+ return;
167
+ try {
168
+ onAnswer(parse(JSON.parse(String(e.data))));
169
+ }
170
+ catch (err) {
171
+ o.onError?.(err);
172
+ }
173
+ });
174
+ es.onopen = () => o.onOpen?.();
175
+ es.onerror = (e) => o.onError?.(e);
176
+ return () => {
177
+ stopped = true;
178
+ es.close();
179
+ };
180
+ }
181
+ /** P3: `GET <base>/pools/beef?lp=k1,k2` (no `lpKeys`: every live pool). */
182
+ export async function readPoolsBeef(base, lpKeys, fetchFn) {
183
+ const url = `${trim(base)}/pools/beef${lpKeys ? `?lp=${list(lpKeys)}` : ""}`;
184
+ const a = (await getJson(url, fetchFn));
185
+ return { pools: poolRows(a?.pools), beef: typeof a?.beef === "string" && a.beef ? Utils.toArray(a.beef, "base64") : [] };
186
+ }
187
+ /** The pool `outpoint` (`<txid>_<vout>`) out of P3's BEEF, as an Atomic BEEF of its transaction; null when the BEEF lacks it. */
188
+ export function poolOutputOf(pb, outpoint) {
189
+ const m = /^([0-9a-f]{64})[._](\d+)$/i.exec(outpoint);
190
+ if (!m || pb.beef.length === 0)
191
+ return null;
192
+ const beef = Beef.fromBinary(pb.beef);
193
+ if (!beef.findTxid(m[1].toLowerCase()))
194
+ return null;
195
+ return { beef: beef.toBinaryAtomic(m[1].toLowerCase()), outputIndex: Number(m[2]) };
196
+ }
197
+ const wireBig = (x) => (typeof x === "number" || typeof x === "string" || typeof x === "bigint" ? BigInt(x) : 0n);
198
+ /** P4: `GET <base>/positions?lp=k1,k2` → one record per key in the same order. */
199
+ export async function readPositions(base, lpPubKeys, fetchFn) {
200
+ const url = `${trim(base)}/positions?lp=${list(lpPubKeys)}`;
201
+ const a = await getJson(url, fetchFn);
202
+ if (!Array.isArray(a) || a.length !== lpPubKeys.length)
203
+ throw new Error(`GET ${url}: not one record per key`);
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
+ });
216
+ }
217
+ /** P7: `GET <base>/terms`, unsigned. */
218
+ export async function readTerms(base, fetchFn) {
219
+ const url = `${trim(base)}/terms`;
220
+ const a = (await getJson(url, fetchFn));
221
+ const v = a?.validator;
222
+ const int = (x, name) => {
223
+ if (typeof x !== "number" || !Number.isInteger(x))
224
+ throw new Error(`GET ${url}: no ${name}`);
225
+ return x;
226
+ };
227
+ return {
228
+ validator: v && typeof v === "object"
229
+ ? {
230
+ identity: String(v.identity ?? "").toLowerCase(),
231
+ peerId: typeof v.peerId === "string" ? v.peerId : null,
232
+ lpFeeBps: int(v.lpFeeBps, "lpFeeBps"),
233
+ validatorFeeBps: int(v.validatorFeeBps, "validatorFeeBps"),
234
+ commissionBps: int(v.commissionBps, "commissionBps"),
235
+ }
236
+ : null,
237
+ market: toMarket(a?.market),
238
+ };
239
+ }
240
+ /** P8: `GET <base>/spends?outpoint=a,b` → one record per outpoint in query order (one outpoint: its object, wrapped). */
241
+ export async function readSpends(base, outpoints, fetchFn) {
242
+ if (outpoints.length === 0)
243
+ return [];
244
+ const a = await getJson(`${trim(base)}/spends?outpoint=${list(outpoints)}`, fetchFn);
245
+ return (Array.isArray(a) ? a : [a]);
246
+ }
247
+ /** P9: `GET <base>/tx?id=<txid>`: the transaction's BEEF (every input's source with it); null when the instance does not hold it. */
248
+ export async function readTx(base, txid, fetchFn) {
249
+ return getBytes(`${trim(base)}/tx?id=${encodeURIComponent(txid)}`, fetchFn);
250
+ }
251
+ /** P10: `GET <base>/swaps?id=<id>`: the relay record (dag-json, as `amm.swap.status` answers it); null when unknown. */
252
+ export async function readSwap(base, id, fetchFn) {
253
+ const res = await (fetchFn ?? plain)(`${trim(base)}/swaps?id=${encodeURIComponent(id)}`, { headers: { accept: "application/json" } });
254
+ if (res.status === 404)
255
+ return null;
256
+ if (!res.ok)
257
+ throw new Error(`GET ${trim(base)}/swaps: ${res.status} ${(await res.text()).slice(0, 200)}`);
258
+ return res.json();
259
+ }
260
+ function discoveryRow(x, listUrl) {
261
+ const listed = x.listed !== false;
262
+ const dec = typeof x.dec === "number" ? x.dec : typeof x.dec === "string" && /^\d+$/.test(x.dec) ? Number(x.dec) : undefined;
263
+ const icon = typeof x.iconUrl === "string" && x.iconUrl ? new URL(x.iconUrl, listUrl).href : undefined;
264
+ return {
265
+ tokenId: String(x.tokenId),
266
+ listed,
267
+ ...(typeof x.topic === "string" ? { topic: x.topic } : {}),
268
+ ...(typeof x.sym === "string" ? { sym: x.sym } : {}),
269
+ ...(dec !== undefined ? { dec } : {}),
270
+ ...(icon ? { iconUrl: icon } : {}),
271
+ ...(typeof x.txid === "string" ? { txid: x.txid } : {}),
272
+ ...(typeof x.vout === "number" ? { vout: x.vout } : {}),
273
+ };
274
+ }
275
+ /** P13 with `ids`: `GET <discovery>/tokens?ids=a,b` → one row per id in query order (`listed: false` for one not on discovery). */
276
+ export async function readDiscoveryIds(discovery, ids, fetchFn) {
277
+ if (ids.length === 0)
278
+ return [];
279
+ const url = `${trim(discovery)}/tokens?ids=${list(ids)}`;
280
+ const a = await getJson(url, fetchFn);
281
+ return (Array.isArray(a) ? a : []).filter((t) => typeof t?.tokenId === "string").map((t) => discoveryRow(t, url));
282
+ }
283
+ /** The route's page size (skein-1color color-lookup `tokens`: `limit` 1 to 100, `skip` up to 100000). */
284
+ const PAGE = 100;
285
+ const MAX_SKIP = 100_000;
286
+ /** P13 paged: discovery's whole token list, newest first, a page at a time. */
287
+ export async function readDiscoveryList(discovery, fetchFn) {
288
+ const out = [];
289
+ for (let skip = 0; skip <= MAX_SKIP; skip += PAGE) {
290
+ const url = `${trim(discovery)}/tokens?limit=${PAGE}&skip=${skip}`;
291
+ const a = await getJson(url, fetchFn);
292
+ const page = (Array.isArray(a) ? a : []).filter((t) => typeof t?.tokenId === "string").map((t) => discoveryRow(t, url));
293
+ out.push(...page);
294
+ if (page.length < PAGE)
295
+ break;
296
+ }
297
+ return out;
298
+ }
299
+ /** P16: `GET <discovery>/<token>/deploy`: the deploy's BEEF; null when discovery does not hold it. */
300
+ export async function readDiscoveryDeploy(discovery, tokenId, fetchFn) {
301
+ return getBytes(`${trim(discovery)}/${encodeURIComponent(tokenId)}/deploy`, fetchFn);
302
+ }
@@ -0,0 +1,82 @@
1
+ import { AuthFetch, type WalletInterface } from "@bsv/sdk";
2
+ export interface SignedPost {
3
+ fetch(url: string, config?: {
4
+ method?: string;
5
+ headers?: Record<string, string>;
6
+ body?: unknown;
7
+ }): Promise<Response>;
8
+ }
9
+ /** The instance's base under which `/sendMessage` lives: the AMM base without its app segment. */
10
+ export declare function instanceBase(base: string): string;
11
+ /**
12
+ * The fetch an instance's BRC-104 client uses (skein `src/client/raw.ts`, copied: the pages do not
13
+ * depend on the skein package). `@bsv/sdk`'s AuthFetch keeps one session per ORIGIN and shakes
14
+ * hands at `<origin>/.well-known/auth`, which the router gives to the root instance; an instance
15
+ * under a path prefix (`<origin>/@<handle>`) takes its handshake at `<prefix>/.well-known/auth`
16
+ * (each instance holds its own sessions). So for a base with a prefix the handshake is sent there;
17
+ * every other request goes as it is. `fetchFn`: the browser's fetch, bound (it must be called on
18
+ * the window), unless a test passes its own.
19
+ */
20
+ export declare function instanceFetch(base: string, fetchFn?: typeof fetch): typeof fetch;
21
+ /** The instance's key for its BRC-104 session: its origin and path prefix (`instanceBase`). */
22
+ export declare function sessionKey(base: string): string;
23
+ /**
24
+ * The wallet's BRC-104 clients, one per instance (its origin + path prefix): `<origin>/amm` and
25
+ * `<origin>/@<tokenId>/amm` are two instances with two sessions. The same instance gets the same
26
+ * client back.
27
+ */
28
+ export declare function authFetchPerInstance(wallet: WalletInterface, fetchFn?: typeof fetch): (base: string) => AuthFetch;
29
+ /** dag-cbor (CDE: canonical key order, shortest forms) of a plain value. */
30
+ export declare function dagCbor(v: unknown): Uint8Array;
31
+ /**
32
+ * A message from the connected wallet's key to the instance's `box` (`<app>/<box>`): its id. The
33
+ * instance's identity is `recipient` (hex).
34
+ */
35
+ export declare function sendMessage(af: SignedPost, base: string, recipient: string, box: string, body: unknown): Promise<string>;
36
+ /** A token's id, topic and metadata, as a token list names it. */
37
+ export interface ListedToken {
38
+ tokenId: string;
39
+ topic: string;
40
+ sym?: string;
41
+ dec?: number;
42
+ icon?: string;
43
+ }
44
+ /**
45
+ * A token on the swap instance's enable list (`GET <swap>/tokens`; Open Exchange, shruggr/open-exchange#1:
46
+ * root's decision as a fact, enabled or disabled; the host makes its instance, `<origin>/@<tokenId>`).
47
+ */
48
+ export interface EnabledToken {
49
+ tokenId: string;
50
+ topic: string;
51
+ sym: string;
52
+ dec: number;
53
+ icon?: string;
54
+ status: "enabled" | "disabled";
55
+ /** When root last enabled or disabled it, ms. */
56
+ since: number;
57
+ }
58
+ /**
59
+ * The config instance's enable list, newest first as the read lists it. A row's icon: its `icon`
60
+ * (a data URL, open-exchange before 0.11.0), else its `iconUrl` (0.34.0, P12: origin-relative,
61
+ * `/@discovery/1color/<token>/icon`) made absolute against the list's URL.
62
+ */
63
+ export declare function readTokens(base: string, fetchFn?: typeof fetch): Promise<EnabledToken[]>;
64
+ /** The defaults a new token instance is created with (0.17.0, `GET <config>/defaults`): each absent when unset. */
65
+ export interface Defaults {
66
+ terms?: {
67
+ lpFeeBps: number;
68
+ validatorFeeBps: number;
69
+ commissionBps: number;
70
+ };
71
+ market?: {
72
+ minCommissionBps: number;
73
+ };
74
+ }
75
+ /** Discovery (`GET <swap>/discovery`): root's last decision, on or off, and since when (ms); the host starts or stops the discovery instance. */
76
+ export interface Discovery {
77
+ on: boolean;
78
+ since?: number;
79
+ }
80
+ /** `GET <swap>/discovery`: `{on, since?}`, or `{on: false}` (none, or another shape). */
81
+ export declare function readDiscovery(base: string, fetchFn?: typeof fetch): Promise<Discovery>;
82
+ export declare function readDefaults(base: string, fetchFn?: typeof fetch): Promise<Defaults>;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The skein's own doors the Open Exchange pages use beside the AMM's routes
3
+ * (skein-amm 0.9.0):
4
+ *
5
+ * - **A message to a box** (`sendMessage`): the BRC-104-signed `POST
6
+ * <instance>/sendMessage`, BRC-231 CBOR `{message: {recipient, messageBox,
7
+ * body}}` — what `skein send`, skein-1color's Token topics page and any
8
+ * BRC-100 wallet send (skein docs/MESSAGES.md "The messagebox"). `body` is
9
+ * dag-cbor. The instance is the AMM base's origin (or its `/@<handle>`
10
+ * prefix); the recipient its identity key. Root's register messages
11
+ * (`amm/register`).
12
+ * - **The token list** (0.17.0, shruggr/skein-amm#4: `readTokens`): `GET
13
+ * <config>/tokens`, the config instance's enable list — each token's id,
14
+ * topic, metadata, status and its own instance's `url`; and the defaults a
15
+ * new token instance is created with (`readDefaults`, `GET <config>/defaults`).
16
+ * - The public read API (0.34.0: the pools, positions, terms, spends, transactions, swaps, and
17
+ * discovery's token reads) is src/lib/overlay.ts.
18
+ */
19
+ import { encode, decode } from "cbor2";
20
+ import { AuthFetch, Utils } from "@bsv/sdk";
21
+ import { appName } from "./config.js";
22
+ /** The instance's base under which `/sendMessage` lives: the AMM base without its app segment. */
23
+ export function instanceBase(base) {
24
+ const name = appName(base);
25
+ return name ? base.replace(new RegExp(`/${name}/?$`), "") : base;
26
+ }
27
+ /**
28
+ * The fetch an instance's BRC-104 client uses (skein `src/client/raw.ts`, copied: the pages do not
29
+ * depend on the skein package). `@bsv/sdk`'s AuthFetch keeps one session per ORIGIN and shakes
30
+ * hands at `<origin>/.well-known/auth`, which the router gives to the root instance; an instance
31
+ * under a path prefix (`<origin>/@<handle>`) takes its handshake at `<prefix>/.well-known/auth`
32
+ * (each instance holds its own sessions). So for a base with a prefix the handshake is sent there;
33
+ * every other request goes as it is. `fetchFn`: the browser's fetch, bound (it must be called on
34
+ * the window), unless a test passes its own.
35
+ */
36
+ export function instanceFetch(base, fetchFn) {
37
+ const u = new URL(instanceBase(base));
38
+ const prefix = u.pathname.replace(/\/+$/, "");
39
+ const f = fetchFn ?? ((input, init) => globalThis.fetch(input, init));
40
+ if (!prefix)
41
+ return f;
42
+ return ((input, init) => {
43
+ const s = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
44
+ const w = new URL(s);
45
+ if (w.origin === u.origin && w.pathname === "/.well-known/auth")
46
+ w.pathname = `${prefix}/.well-known/auth`;
47
+ return f(input instanceof Request ? new Request(w, input) : w.href, init);
48
+ });
49
+ }
50
+ /** The instance's key for its BRC-104 session: its origin and path prefix (`instanceBase`). */
51
+ export function sessionKey(base) {
52
+ return instanceBase(base).replace(/\/+$/, "");
53
+ }
54
+ /**
55
+ * The wallet's BRC-104 clients, one per instance (its origin + path prefix): `<origin>/amm` and
56
+ * `<origin>/@<tokenId>/amm` are two instances with two sessions. The same instance gets the same
57
+ * client back.
58
+ */
59
+ export function authFetchPerInstance(wallet, fetchFn) {
60
+ const clients = new Map();
61
+ return (base) => {
62
+ const key = sessionKey(base);
63
+ let af = clients.get(key);
64
+ if (!af) {
65
+ af = new AuthFetch(wallet, undefined, undefined, undefined, {}, instanceFetch(base, fetchFn));
66
+ clients.set(key, af);
67
+ }
68
+ return af;
69
+ };
70
+ }
71
+ /** dag-cbor (CDE: canonical key order, shortest forms) of a plain value. */
72
+ export function dagCbor(v) {
73
+ return encode(v, { cde: true });
74
+ }
75
+ /**
76
+ * A message from the connected wallet's key to the instance's `box` (`<app>/<box>`): its id. The
77
+ * instance's identity is `recipient` (hex).
78
+ */
79
+ export async function sendMessage(af, base, recipient, box, body) {
80
+ const url = `${instanceBase(base)}/sendMessage`;
81
+ const res = await af.fetch(url, {
82
+ method: "POST",
83
+ headers: { "content-type": "application/cbor" },
84
+ body: dagCbor({ message: { recipient: Uint8Array.from(Utils.toArray(recipient, "hex")), messageBox: box, body: dagCbor(body) } }),
85
+ });
86
+ const bytes = new Uint8Array(await res.arrayBuffer());
87
+ let v = {};
88
+ try {
89
+ v = decode(bytes);
90
+ }
91
+ catch {
92
+ v = { description: new TextDecoder().decode(bytes) };
93
+ }
94
+ if (res.status !== 200)
95
+ throw new Error(`sendMessage ${box}: HTTP ${res.status} ${String(v.code ?? "")} ${String(v.description ?? "")}`.trim());
96
+ return String(v.id ?? v.messageId ?? "");
97
+ }
98
+ async function getJson(url, fetchFn = fetch) {
99
+ const res = await fetchFn(url, { headers: { accept: "application/json" } });
100
+ if (!res.ok)
101
+ throw new Error(`GET ${url}: ${res.status} ${(await res.text()).slice(0, 200)}`);
102
+ return res.json();
103
+ }
104
+ const STATUSES = new Set(["enabled", "disabled"]);
105
+ /**
106
+ * The config instance's enable list, newest first as the read lists it. A row's icon: its `icon`
107
+ * (a data URL, open-exchange before 0.11.0), else its `iconUrl` (0.34.0, P12: origin-relative,
108
+ * `/@discovery/1color/<token>/icon`) made absolute against the list's URL.
109
+ */
110
+ export async function readTokens(base, fetchFn = fetch) {
111
+ const url = `${base}/tokens`;
112
+ const a = await getJson(url, fetchFn);
113
+ return (Array.isArray(a) ? a : [])
114
+ .filter((t) => typeof t.tokenId === "string" && typeof t.status === "string" && STATUSES.has(t.status))
115
+ .map((t) => ({
116
+ tokenId: String(t.tokenId),
117
+ topic: typeof t.topic === "string" ? t.topic : `tm_color_${String(t.tokenId)}`,
118
+ sym: typeof t.sym === "string" ? t.sym : "",
119
+ dec: typeof t.dec === "number" ? t.dec : 0,
120
+ ...(typeof t.icon === "string" ? { icon: t.icon } : typeof t.iconUrl === "string" && t.iconUrl ? { icon: new URL(t.iconUrl, url).href } : {}),
121
+ status: t.status,
122
+ since: typeof t.since === "number" ? t.since : 0,
123
+ }));
124
+ }
125
+ /** `GET <swap>/discovery`: `{on, since?}`, or `{on: false}` (none, or another shape). */
126
+ export async function readDiscovery(base, fetchFn = fetch) {
127
+ const a = (await getJson(`${base}/discovery`, fetchFn));
128
+ if (!a || typeof a.on !== "boolean")
129
+ return { on: false };
130
+ return { on: a.on, ...(typeof a.since === "number" ? { since: a.since } : {}) };
131
+ }
132
+ export async function readDefaults(base, fetchFn = fetch) {
133
+ const a = (await getJson(`${base}/defaults`, fetchFn));
134
+ const out = {};
135
+ const t = a?.terms;
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 };
138
+ const m = a?.market;
139
+ if (m && typeof m.minCommissionBps === "number")
140
+ out.market = { minCommissionBps: m.minCommissionBps };
141
+ return out;
142
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Token ids and outpoints, written and read in one place (skein-amm 0.6.3,
3
+ * 0.7.1).
4
+ *
5
+ * A token id is written `<txid>_<vout>` for every token, 1Color (BRC-162
6
+ * binary) and legacy BSV-21 (BRC-161 JSON) alike, `_0` included: BRC-162
7
+ * "Token identification" ("For display and APIs, the string form is
8
+ * `<txid>_<vout>` ... fixed for BSV-21 and Mandala ... for a 32-byte id,
9
+ * appends `_0`"). David, 2026-10-07 (shruggr/skein#120; supersedes 0.6.3's
10
+ * bare txid). The bare 32-byte txid is the wire form only.
11
+ * On input every form names the same token: `<txid>`, `<txid>_<vout>`,
12
+ * `<txid>.<vout>` (no vout = vout 0).
13
+ *
14
+ * An outpoint is shown `<txid>.<vout>`. David, 2026-10-08: "Outpoints are
15
+ * always to be shown in the txid.vout format, not underscores. The ONLY
16
+ * remnant of underscores is for legacy tokenIds".
17
+ *
18
+ * The wallet's BRC-207 module's asset id, 1sat-sdk's `OneColor` string id and
19
+ * the exchange's own filings take the same `<txid>_<vout>` (`sdkTokenId`); the
20
+ * module's baskets name it by its asset tag, `p color <txid>v<vout>`
21
+ * (src/wallet/color.ts); the Zig programs' outpoint fields
22
+ * (amm-lookup's `{outpoint}`, the relay's) and the LP key derivations keep
23
+ * `<txid>_<vout>` as before.
24
+ */
25
+ export interface TokenRef {
26
+ /** 64 lowercase hex, display order. */
27
+ txid: string;
28
+ vout: number;
29
+ }
30
+ /** Any token id form (`<txid>`, `<txid>_<vout>`, `<txid>.<vout>`) → its deploy outpoint; null for anything else. */
31
+ export declare function parseTokenId(text: string): TokenRef | null;
32
+ /** An outpoint (`<txid>.<vout>` or `<txid>_<vout>`) → txid and vout; null for anything else. */
33
+ export declare function parseOutpoint(text: string): TokenRef | null;
34
+ /** BRC-207's two lookup queries (wallet/0207.md "Overlay spendability contract"). */
35
+ export type Brc207Type = "color-spendability" | "color-admission";
36
+ /** A BRC-24 lookup request: its service and query. */
37
+ export interface LookupRequest {
38
+ service: string;
39
+ query: Record<string, unknown>;
40
+ }
41
+ /**
42
+ * A BRC-207 query for outpoints of token `tokenId` at a token instance (0.20.1): service
43
+ * `ls_color_<assetId>`, topic `tm_color_<assetId>`, exactly BRC-207's shape. A token instance
44
+ * serves no `ls_color`.
45
+ */
46
+ export declare function colorQuery(type: Brc207Type, tokenId: string, outpoints: string[]): LookupRequest;
47
+ /** The discovery lookup (0.20.0): `ls_color`, color-lookup over the discovery topic `tm_color`. */
48
+ export declare const DISCOVERY_LOOKUP = "ls_color";
49
+ /**
50
+ * A token's deploy asked of the discovery lookup (0.20.1; David, 2026-10-29: BRC-207's form):
51
+ * `{service: "ls_color", query: {type: "color-admission", version: 1, assetId: "<txid>_<vout>",
52
+ * topic: "tm_color", outpoints: ["<txid>.<vout>"]}}` — the token id IS the deploy's outpoint.
53
+ */
54
+ export declare function deployQuery(tokenId: string): LookupRequest;
55
+ /** The token id of a deploy outpoint: `<txid>_<vout>`, `_0` included (BRC-162 "Token identification"). */
56
+ export declare function tokenIdText(ref: TokenRef): string;
57
+ /**
58
+ * The token id a 1Color output names by its BRC-162 wire id (internal byte
59
+ * order): 32 bytes is `<txid>_0`, 36 bytes (a legacy BRC-161 token at
60
+ * vout > 0) `<txid>_<vout>`; as 1sat-sdk's `OneColor` prints it.
61
+ */
62
+ export declare function tokenIdOfWire(idBytes: Uint8Array | number[]): string;
63
+ /** Any token id form → the token id string, `<txid>_<vout>` (a `<txid>` as `<txid>_0`): the form shown and the BRC-207 asset id (the module's receipts, `OneColor`'s string id). */
64
+ export declare function sdkTokenId(text: string): string;
65
+ /** Whether two token id strings, in any form, name the same token. */
66
+ export declare function sameToken(a: string, b: string): boolean;
67
+ /** An outpoint for display: `<txid>.<vout>`; anything that is not an outpoint as given. */
68
+ export declare function outpointText(op: string): string;
69
+ /** An outpoint shortened for display (the txid to its first and last 8, as skein-1color 0.7.4 shows ids): `abcdef01…456789ab.0`. */
70
+ export declare function shortOutpoint(op: string): string;
71
+ /** A token id shortened for display, in the form it is written: `abcdef01…456789ab_0`; a bare txid (not a token id) as `abcdef01…456789ab`. */
72
+ export declare function shortTokenId(id: string): string;