@dcasegr/amm-client 0.42.3 → 0.44.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 CHANGED
@@ -103,7 +103,7 @@ last-leg change keys), `swapInstructions` / `swapSatsInstructions` (their custom
103
103
  change row's with `order`), `LP_KEY_PROTOCOL` (BRC-29) + `LP_KEY_MARK` +
104
104
  `lpKeyDescription` / `lpKeyIdOfDescription` (the deploy's pool output description naming the LP
105
105
  keyID), `POOL_TAG` / `POOL_OP` / `isPoolRow` / `poolCustomInstructions` (an `amm-pools` row),
106
- `POOL_DEPLOY_LABEL` (`amm-pool-deploy`), `SWAP_TTL_MS` (15 s), `transferPlanLabels` (the `p color
106
+ `POOL_DEPLOY_LABEL` (`amm-pool-deploy`), `LEG_PATIENCE_MS` (15 s: how long a swap leg waits for the host's answer before closing the request; 0.44.0, was the swap's expiry), `transferPlanLabels` (the `p color
107
107
  plan` labels), the receipt helpers `newReceivingKey` / `receiptOutput`, and `xTopics` (the BRC-22
108
108
  `x-topics` header: a JSON array).
109
109
 
package/dist/client.d.ts CHANGED
@@ -20,9 +20,10 @@
20
20
  * market's minimum (the host's filter), each validator's minimum leg, the market's commission key
21
21
  * hash; a quote is the matching engine's plan over that list (no request); a swap (`executeSwap`)
22
22
  * stages what the swap basket lacks by ONE wallet action, then per leg the pools route read fresh,
23
- * ONE `GET /@<token>/amm/pools/beef?pool=` and the leg's relay request (`amm.swap.submit`, held by
24
- * the host until the relay rests; 15 s expiry); no answer → the host's `GET /amm/spends`, once
25
- * more, a race. The pool events (`watchPoolEvents`, 0.41.0: the host's feeds) are signals for
23
+ * ONE `GET /@<token>/amm/pools/beef?pool=` and the leg's ONE plain `POST /@<token>/amm/swap`
24
+ * (0.44.0: the host is the relay, answering the validator's reply; the page's patience,
25
+ * `LEG_PATIENCE_MS`, is the only timer); no answer → the host's `GET /amm/spends`, once more, a
26
+ * race. The pool events (`watchPoolEvents`, 0.41.0: the host's feeds) are signals for
26
27
  * display: a page reads the route again on one.
27
28
  * - **Liquidity** (D9, D23; 0.40.0: by pool id): the wallet's pool rows and deploy history, then
28
29
  * ONE `GET /@<token>/amm/positions?pool=` per token; a deploy the history has but the basket
package/dist/client.js CHANGED
@@ -20,9 +20,10 @@
20
20
  * market's minimum (the host's filter), each validator's minimum leg, the market's commission key
21
21
  * hash; a quote is the matching engine's plan over that list (no request); a swap (`executeSwap`)
22
22
  * stages what the swap basket lacks by ONE wallet action, then per leg the pools route read fresh,
23
- * ONE `GET /@<token>/amm/pools/beef?pool=` and the leg's relay request (`amm.swap.submit`, held by
24
- * the host until the relay rests; 15 s expiry); no answer → the host's `GET /amm/spends`, once
25
- * more, a race. The pool events (`watchPoolEvents`, 0.41.0: the host's feeds) are signals for
23
+ * ONE `GET /@<token>/amm/pools/beef?pool=` and the leg's ONE plain `POST /@<token>/amm/swap`
24
+ * (0.44.0: the host is the relay, answering the validator's reply; the page's patience,
25
+ * `LEG_PATIENCE_MS`, is the only timer); no answer → the host's `GET /amm/spends`, once more, a
26
+ * race. The pool events (`watchPoolEvents`, 0.41.0: the host's feeds) are signals for
26
27
  * display: a page reads the route again on one.
27
28
  * - **Liquidity** (D9, D23; 0.40.0: by pool id): the wallet's pool rows and deploy history, then
28
29
  * ONE `GET /@<token>/amm/positions?pool=` per token; a deploy the history has but the basket
@@ -633,7 +634,8 @@ function makeClient(opts, s) {
633
634
  * (`relaying`):
634
635
  * - accepted → taken in (ONE internalizeAction) before the next leg;
635
636
  * - refused `pool_spent` (`repricing`) → the list read again: the relaying host took the
636
- * validator's state in before it answered (0.41.0);
637
+ * validator's state in before it answered (0.41.0); the list still showing the pool at the
638
+ * outpoint the leg spent → that pool excluded for the rest of this swap (0.43.0);
637
639
  * - refused otherwise (`below_minimum` included) → that outpoint excluded;
638
640
  * - no answer → (a) the overlay: a staged input spent → the leg landed (taken in by the
639
641
  * pass), the pool spent by another's swap → continue with the same rows; (b) else the
@@ -672,19 +674,20 @@ function makeClient(opts, s) {
672
674
  return result;
673
675
  }
674
676
  async function runSwap(quote, progress, signal) {
675
- const { wallet, signedFor } = needWallet();
677
+ const { wallet } = needWallet();
676
678
  const p = planned.get(quote);
677
679
  if (!p)
678
680
  throw new Error("Quote again: this quote was not planned here");
679
681
  const tokenId = quote.request.tokenId;
680
682
  const base = tokenBase(tokenId, swapBase);
681
- const af = signedFor(base);
682
683
  const buy = quote.request.side === "buy";
683
684
  const direction = buy ? "bsvToToken" : "tokenToBsv";
684
685
  const minerFee = legMinerFeeSats(feeRate);
685
686
  const meta = { sym: p.sym, dec: p.dec };
686
687
  // 0.41.0: the server's list, read fresh before each leg; the outpoints excluded (a pool that refused other than pool_spent, or did not answer).
687
688
  const excluded = new Set();
689
+ /** 0.43.0 (D4): the outpoint a leg was just refused `pool_spent` on: still listed there at the next read → excluded. */
690
+ let spentAt;
688
691
  /** 0.40.2: the legs the order may still fill (the request's maxSources less the legs filled). */
689
692
  let budget = quote.request.maxSources;
690
693
  const legs = [];
@@ -727,6 +730,11 @@ function makeClient(opts, s) {
727
730
  }
728
731
  const remaining = quote.request.amountIn - filledIn;
729
732
  const answer = await tokenPools(tokenId);
733
+ // 0.43.0 (D4): refused pool_spent, yet the server's fresh list still shows the pool at the outpoint the leg spent:
734
+ // the pool is not asked again in this swap.
735
+ if (spentAt !== undefined && answer.pools.some((r) => r.outpoint === spentAt))
736
+ excluded.add(spentAt);
737
+ spentAt = undefined;
730
738
  const pools = poolsWithMinimums(answer).filter((r) => !excluded.has(r.outpoint));
731
739
  const minOut = quote.minAmountOut > filledOut ? quote.minAmountOut - filledOut : 0n;
732
740
  const next = nextLeg({ pools, direction, remaining, fixedCost: { minerFeeSats: minerFee }, minOut, maxSources: budget });
@@ -760,8 +768,8 @@ function makeClient(opts, s) {
760
768
  const prepared = await prepareSwap({ ...common, inputs, ...(order ? { order } : {}), last: leg.amountIn === remaining, holdingBeef: await swapHoldingBeef(wallet) });
761
769
  const at = { leg: outpointText(leg.outpoint), amountIn: leg.amountIn, amountOut: leg.amountOut };
762
770
  progress({ phase: "relaying", ...at, filled: [...legs] });
763
- let outcome = await relayLeg(wallet, af, base, prepared, peerId);
764
- if (outcome.status === "unknown" || outcome.status === "timeout") {
771
+ let outcome = await relayLeg(wallet, base, prepared, peerId);
772
+ if (outcome.status === "unknown") {
765
773
  // (a) The overlay: what the leg spends.
766
774
  const spent = await readSpendsAll(swapBase, [outpointText(leg.outpoint), ...inputs.sats.map((r) => r.outpoint), ...inputs.tokens.map((r) => r.outpoint)], freshFetch).catch(() => []);
767
775
  const ours = spent.find((x) => x.spentBy && x.outpoint !== outpointText(leg.outpoint));
@@ -773,8 +781,8 @@ function makeClient(opts, s) {
773
781
  if (spent.some((x) => x.spentBy && x.outpoint === outpointText(leg.outpoint)))
774
782
  continue;
775
783
  // (b) Once more to the same validator.
776
- outcome = await relayLeg(wallet, af, base, prepared, peerId);
777
- if (outcome.status === "unknown" || outcome.status === "timeout") {
784
+ outcome = await relayLeg(wallet, base, prepared, peerId);
785
+ if (outcome.status === "unknown") {
778
786
  // (c) Race: the next leg to another pool with the same rows.
779
787
  pending.push({ prepared, leg, poolId: row.poolId });
780
788
  excluded.add(row.outpoint);
@@ -797,6 +805,7 @@ function makeClient(opts, s) {
797
805
  if (outcome.status === "refused" && outcome.reason.startsWith("pool_spent")) {
798
806
  // The relaying host took the validator's state in before it answered: the next read lists it.
799
807
  progress({ phase: "repricing", ...at, filled: [...legs] });
808
+ spentAt = row.outpoint;
800
809
  }
801
810
  else {
802
811
  excluded.add(row.outpoint);
@@ -820,9 +829,9 @@ function makeClient(opts, s) {
820
829
  }
821
830
  return out;
822
831
  }
823
- /** One relay of a prepared leg (0.34.0: no browser note); a thrown error is no answer. */
824
- function relayLeg(wallet, af, base, prepared, peerId) {
825
- return relaySwap({ wallet, authFetch: af, base }, prepared, peerId).catch((e) => ({ status: "unknown", id: swapIdOf(prepared), reason: errText(e) }));
832
+ /** One leg to the host's swap route (0.44.0: a plain POST, the page's patience its only timer); a thrown error is no answer. */
833
+ function relayLeg(wallet, base, prepared, peerId) {
834
+ return relaySwap({ wallet, fetch: f, base }, prepared, peerId).catch((e) => ({ status: "unknown", id: swapIdOf(prepared), reason: errText(e) }));
826
835
  }
827
836
  /** A plain GET past the browser's short cache (`state` class: max-age 5): what a swap is about to spend must be read now. */
828
837
  const freshFetch = (url, init) => f(url, { ...init, cache: "no-cache" });
package/dist/errors.js CHANGED
@@ -14,7 +14,8 @@
14
14
  * pool_spent the pool was spent since the quote (the quote's freshness read; the validator's
15
15
  * `pool_spent` refusal)
16
16
  * user_rejected the user declined in the wallet (the wallet's `ERR_PERMISSION_DENIED`, "denied")
17
- * no_answer the relay gave no settled answer yet ("no answer yet"; a timed-out swap record)
17
+ * no_answer no answer to a leg yet ("no answer yet": the host could not reach the validator,
18
+ * the request failed, or the page's patience ran out)
18
19
  * below_minimum (0.40.0) under a validator's minimum leg (its `below_minimum` refusal; a quote no
19
20
  * pool takes for its size)
20
21
  * refused the validator, the relay or the host refused it (a refusal other than pool_spent
@@ -69,7 +70,6 @@ export function kindOfOutcome(o) {
69
70
  return undefined;
70
71
  case "refused":
71
72
  return o.reason.startsWith("pool_spent") ? "pool_spent" : o.reason.startsWith("below_minimum") ? "below_minimum" : "refused";
72
- case "timeout":
73
73
  case "unknown":
74
74
  return "no_answer";
75
75
  case "failed":
package/dist/index.d.ts CHANGED
@@ -51,8 +51,8 @@ export { isPoolRow } from "./lp/poolDeploy.js";
51
51
  export { poolCustomInstructions } from "./lp/poolDeploy.js";
52
52
  /** The label of a pool deploy's action (`amm-pool-deploy`): the positions reader's history. */
53
53
  export { POOL_DEPLOY_LABEL } from "./lp/positions.js";
54
- /** How long the relay may take a swap leg (the swap's `expires`), ms. */
55
- export { SWAP_TTL_MS } from "./market/swapAction.js";
54
+ /** How long the page waits for one swap leg's answer before it closes the request (0.44.0: the host's relay stops with it), ms. */
55
+ export { LEG_PATIENCE_MS } from "./market/swapFlow.js";
56
56
  /** The `p color plan` labels of a transfer of one asset (BRC-207 plan version 1): every token action's. */
57
57
  export { transferPlanLabels } from "./wallet/color.js";
58
58
  /** A module receiving key for one output of an asset from a sender: its receipt, key and hash160. */
package/dist/index.js CHANGED
@@ -53,8 +53,8 @@ export { isPoolRow } from "./lp/poolDeploy.js";
53
53
  export { poolCustomInstructions } from "./lp/poolDeploy.js";
54
54
  /** The label of a pool deploy's action (`amm-pool-deploy`): the positions reader's history. */
55
55
  export { POOL_DEPLOY_LABEL } from "./lp/positions.js";
56
- /** How long the relay may take a swap leg (the swap's `expires`), ms. */
57
- export { SWAP_TTL_MS } from "./market/swapAction.js";
56
+ /** How long the page waits for one swap leg's answer before it closes the request (0.44.0: the host's relay stops with it), ms. */
57
+ export { LEG_PATIENCE_MS } from "./market/swapFlow.js";
58
58
  /** The `p color plan` labels of a transfer of one asset (BRC-207 plan version 1): every token action's. */
59
59
  export { transferPlanLabels } from "./wallet/color.js";
60
60
  /** A module receiving key for one output of an asset from a sender: its receipt, key and hash160. */
@@ -178,8 +178,6 @@ export interface Spend {
178
178
  export declare function readSpends(base: string, outpoints: string[], fetchFn?: FetchFn): Promise<Spend[]>;
179
179
  /** P9: `GET <base>/tx?id=<txid>`: the transaction's BEEF (every input's source with it); null when the instance does not hold it. */
180
180
  export declare function readTx(base: string, txid: string, fetchFn?: FetchFn): Promise<number[] | null>;
181
- /** P10: `GET <base>/swaps?id=<id>`: the relay record (dag-json, as `amm.swap.status` answers it); null when unknown. */
182
- export declare function readSwap(base: string, id: string, fetchFn?: FetchFn): Promise<unknown | null>;
183
181
  /** A token on discovery (P13, P14): its metadata; `iconUrl` absolute (P13 gives it relative to the list). */
184
182
  export interface DiscoveryRow {
185
183
  tokenId: string;
@@ -17,7 +17,7 @@
17
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
- * | P10 | `GET /@<token>/amm/swaps?id=<id>` | the relay record, as `amm.swap.status` answers it |
20
+ * | (P10) | gone in 0.44.0: the host relays a leg (`POST /@<token>/amm/swap`, src/market/relay.ts), nothing is recorded |
21
21
  * | P11 | `GET /@<token>/amm/token` | the token's record |
22
22
  * | P12 | `GET <origin>/amm/tokens` | the exchange's token list (src/lib/skein.ts `readTokens`) |
23
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}` |
@@ -270,15 +270,6 @@ export async function readSpends(base, outpoints, fetchFn) {
270
270
  export async function readTx(base, txid, fetchFn) {
271
271
  return getBytes(`${trim(base)}/tx?id=${encodeURIComponent(txid)}`, fetchFn);
272
272
  }
273
- /** P10: `GET <base>/swaps?id=<id>`: the relay record (dag-json, as `amm.swap.status` answers it); null when unknown. */
274
- export async function readSwap(base, id, fetchFn) {
275
- const res = await (fetchFn ?? plain)(`${trim(base)}/swaps?id=${encodeURIComponent(id)}`, { headers: { accept: "application/json" } });
276
- if (res.status === 404)
277
- return null;
278
- if (!res.ok)
279
- throw new Error(`GET ${trim(base)}/swaps: ${res.status} ${(await res.text()).slice(0, 200)}`);
280
- return res.json();
281
- }
282
273
  function discoveryRow(x, listUrl) {
283
274
  const listed = x.listed !== false;
284
275
  const dec = typeof x.dec === "number" ? x.dec : typeof x.dec === "string" && /^\d+$/.test(x.dec) ? Number(x.dec) : undefined;
@@ -1,6 +1,4 @@
1
1
  import type { PoolState } from "@dcasegr/amm-engine";
2
- export declare const SWAP_SUBMIT_FN = "amm.swap.submit";
3
- export declare const SWAP_STATUS_FN = "amm.swap.status";
4
2
  export declare const SWAP_TERMS_FN = "amm.swap.terms";
5
3
  /** The relay's terms: where it takes the commission (hash160 hex), or null when it names none; this market's minimum commission (bps, 0 when unset). */
6
4
  export interface SwapTerms {
@@ -15,7 +13,8 @@ export interface AuthFetchLike {
15
13
  body?: string;
16
14
  }): Promise<Pick<Response, "status" | "text">>;
17
15
  }
18
- export interface SwapSubmit {
16
+ /** A leg as the host's swap route takes it. */
17
+ export interface SwapRequest {
19
18
  /** The funding transaction as AtomicBEEF (the swap's unproven parent, with its ancestry). */
20
19
  funding: number[];
21
20
  /** Raw swap transaction, the validator's slot empty. */
@@ -24,32 +23,32 @@ export interface SwapSubmit {
24
23
  pool: string;
25
24
  /** The pool's validator identity key (hex; sent as 33 bytes). */
26
25
  validator: string;
27
- /** The validator's libp2p peer ID (text), as the liveness read gave it: what the relay dials. */
26
+ /** The validator's libp2p peer ID (text), as the pools route gave it: what the host dials. */
28
27
  peerId: string;
29
- /** Unix ms after which the relay gives up. */
30
- expires: number;
31
28
  }
32
- export type SwapRecord = {
33
- id: string;
34
- status: "pending";
35
- } | {
36
- id: string;
37
- status: "accepted";
29
+ /** The host's answer to a leg: the validator's reply, or the host's own refusal / break (`unreachable`). */
30
+ export type SwapReply = {
31
+ ok: true;
38
32
  tx: number[];
39
33
  txid: string;
40
34
  } | {
41
- id: string;
42
- status: "refused";
35
+ ok: false;
43
36
  reason: string;
44
37
  pool?: PoolState;
45
- } | {
46
- id: string;
47
- status: "timeout";
48
- } | {
49
- id: string;
50
- status: "failed";
38
+ }
39
+ /** Any break in the call (dial refused, protocol not served, write failed, stream closed without an answer): no answer, the swap may or may not stand. */
40
+ | {
41
+ ok: false;
42
+ unreachable: true;
51
43
  reason: string;
52
44
  };
45
+ /** What the swap route needs of `fetch` (and what the tests fake): a plain fetch that honours `signal`. */
46
+ export type FetchLike = (url: string, init: {
47
+ method: string;
48
+ headers: Record<string, string>;
49
+ body: string;
50
+ signal?: AbortSignal;
51
+ }) => Promise<Pick<Response, "status" | "text">>;
53
52
  export declare class RelayError extends Error {
54
53
  readonly code: string;
55
54
  readonly httpStatus: number;
@@ -63,10 +62,10 @@ export declare function dagBytes(bytes: number[]): {
63
62
  /** DAG-JSON bytes, hex, or a byte array → bytes; null otherwise. */
64
63
  export declare function readBytes(x: unknown): number[] | null;
65
64
  export declare function callUrl(base: string): string;
66
- export declare function submitBody(s: SwapSubmit): {
67
- fn: string;
68
- args: Record<string, unknown>;
69
- };
65
+ export declare const SWAP_ROUTE = "/swap";
66
+ /** The host's swap route of a token instance's AMM base (`<origin>/@<tokenId>/amm`). */
67
+ export declare function swapUrl(base: string): string;
68
+ export declare function swapBody(s: SwapRequest): Record<string, unknown>;
70
69
  /** One `{fn, args}` call on the app's `/call` route. Throws `RelayError` on an error answer. */
71
70
  export declare function callApp(af: AuthFetchLike, base: string, fn: string, args: unknown): Promise<unknown>;
72
71
  /**
@@ -76,9 +75,14 @@ export declare function callApp(af: AuthFetchLike, base: string, fn: string, arg
76
75
  * missing a field, is undefined: nothing left to re-price.
77
76
  */
78
77
  export declare function toPoolState(raw: unknown): PoolState | undefined;
79
- export declare function parseSwapRecord(x: unknown): SwapRecord;
78
+ /** The host's answer (200, JSON) to a leg, as a `SwapReply`; throws when it is no reply. */
79
+ export declare function parseSwapReply(x: unknown): SwapReply;
80
80
  /** `amm.swap.terms`'s answer: `commissionPkh` as DAG-JSON bytes (or hex, or a byte array) of 20 bytes, or null / absent; `minCommissionBps` an integer 0 to 10 000, absent 0 (0.16.0). */
81
81
  export declare function parseSwapTerms(x: unknown): SwapTerms;
82
82
  export declare function swapTerms(af: AuthFetchLike, base: string): Promise<SwapTerms>;
83
- export declare function submitSwap(af: AuthFetchLike, base: string, s: SwapSubmit): Promise<SwapRecord>;
84
- export declare function swapStatus(af: AuthFetchLike, base: string, id: string): Promise<SwapRecord>;
83
+ /**
84
+ * One leg to the host's swap route (a plain POST; `signal` closes the request — the page's patience,
85
+ * the host's only timer). Throws `RelayError` on an answer that is not 200 (the host relayed nothing)
86
+ * and the fetch's own error on a network failure or an abort (no answer: the swap may stand).
87
+ */
88
+ export declare function postSwap(fetchFn: FetchLike, base: string, s: SwapRequest, signal?: AbortSignal): Promise<SwapReply>;
@@ -1,61 +1,41 @@
1
1
  /**
2
- * The marketplace relay (docs/notes.md, "Marketplace relay"): the user's own
3
- * skein instance takes the funding and swap transactions and carries them to
4
- * the pool's validator, so the browser needs no libp2p. Two functions on the
5
- * AMM app's box, hosted by programs/amm-p2p:
2
+ * The swap relay and the app's `/call` functions, as the pages reach them.
6
3
  *
7
- * amm.swap.terms (reads) {} → {commissionPkh: bytes(20) | null, minCommissionBps}
8
- * amm.swap.submit (writes) {funding, swap, pool, validator, peerId, expires} → the record
9
- * (0.34.0, D15: the host holds the request until the relay thread
10
- * rests, so the answer is the settled record; past the host's bound,
11
- * 503: no answer yet)
12
- * amm.swap.status (reads) {id} → the record (0.34.0: the pages read it plain, `GET
13
- * <base>/swaps?id=`, src/lib/overlay.ts `readSwap`, behind "Check again")
4
+ * 0.44.0 (David Case 2026-11-06, "the instance logs facts, never attempts"): the HOST is the relay.
5
+ * A leg is one plain POST to the open-exchange host's route
14
6
  *
15
- * commissionPkh the address the relay takes the pool's commission at
16
- * (Swap's `commissionPkh`; 0.16.0: hash160 of the instance's
17
- * own derived key, no config); null when the instance names
18
- * none, in which case a pool with a nonzero CommissionBps pays
19
- * the commission to the taker's own key
20
- * (src/market/swapAction.ts, "the commission")
21
- * minCommissionBps this market's minimum commission (0.16.0: the record
22
- * root sets, `amm/market`; absent or unset, 0): a listing
23
- * preference, the pools the Swap page lists, not a rule
7
+ * POST <origin>/@<tokenId>/amm/swap (`swapUrl(base)`; no BRC-104 session, as /submit is plain)
8
+ * body {funding, swap, pool, validator, peerId} JSON, bytes as DAG-JSON bytes
9
+ * answer the validator's reply as it is: {ok: true, tx, txid, beef?}
10
+ * {ok: false, reason, detail?, pool?, beef?}
11
+ * or the host's own: {ok: false, reason: "unreachable", detail} any break in the call
12
+ * {ok: false, reason: "commission_missing", detail} the one rule it checks
24
13
  *
25
- * funding bytes: the funding transaction as the wallet's AtomicBEEF (its
26
- * ancestry with it: the validator's overlay verifies the unproven
27
- * funding against parents in the BEEF); 0.29.0: the transaction
28
- * of the leg's `amm-swap` sats output, the swap's input 1
14
+ * funding bytes: the funding transaction as the wallet's AtomicBEEF (0.40.1 `fundingBeef`: every
15
+ * staged input's source with its ancestry)
29
16
  * swap bytes: the raw swap, the validator's slot `OP_0`
30
- * pool text `<txid>_<vout>`; validator bytes(33); expires unix ms
31
- * peerId text: the validator's libp2p peer ID, from the pools route's row
32
- * (`validatorPeerId`, 0.34.0: the host joined it from the beat): the
33
- * relay dials it (or, when it is the relay's own node, hands the
34
- * swap to its own validator program); it looks at no liveness
17
+ * pool text `<txid>_<vout>`; validator bytes(33)
18
+ * peerId text: the validator's libp2p peer ID, from the pools route's row (`validatorPeerId`)
19
+ *
20
+ * The host checks that the swap's commission pays this exchange's key (amm-p2p README "The
21
+ * commission rule") and nothing else, signs the validator's package with the token instance's key,
22
+ * carries it on one libp2p stream (or to a validator on its own node as an inbound frame), submits a
23
+ * `beef` the reply carries to the token instance's overlay and then answers. Nothing is recorded:
24
+ * no swap id to ask about, no status, no expiry. THE PAGE'S CONNECTION IS THE ONLY TIMER: closing
25
+ * it (`LEG_PATIENCE_MS`, src/market/swapFlow.ts) stops the host — a dial in progress cancelled, a
26
+ * request not yet written never written. Once written, the validator answers on that stream; a
27
+ * signed swap lands on the overlay and the swap loop's settle pass finds it.
35
28
  *
36
- * record (programs/amm-p2p relay.zig `Record.answer`; id = sha256 of the swap, hex):
37
- * {id, status: "pending" | "accepted" | "refused" | "timeout" | "failed", pool, expires, …,
38
- * tx?, txid? accepted: the validator's signed swap
39
- * reason?, detail?, refused / timeout / failed
40
- * poolState?} refused: the pool's current state (the validator's)
41
- * "failed" is the relay's transport failure (retryable by submitting again):
42
- * the validator may or may not have the swap, so the page treats it as no
43
- * answer, not as a refusal.
29
+ * The app's `/call` route (skein docs/APPS.md §4) stays for `amm.swap.terms`, `amm.pool.terms`
30
+ * and `amm.pool.affirm`: `POST <base>/call`, JSON body `{fn, args}`, answer `{fn, result}` (200) or
31
+ * `{fn, error: {code, message}}`; BRC-104 authenticated (the wallet-backed AuthFetch).
44
32
  *
45
- * Transport: the app's `/call` route (skein docs/APPS.md §4): `POST
46
- * <VITE_AMM_OVERLAY>/call`, JSON body `{fn, args}`, answer `{fn, result}`
47
- * (200) or `{fn, error: {code, message}}` (400 bad-request / bad-args, 403
48
- * not-admitted, 404 unknown-fn, 409 read-only, 500 failed). The route is
49
- * BRC-104 authenticated: the caller must be admitted to the app's box, so
50
- * both calls go through the wallet-backed AuthFetch (src/wallet/authFetch.ts).
33
+ * amm.swap.terms (reads) {} → {commissionPkh: bytes(20) | null, minCommissionBps}
51
34
  *
52
- * Bytes in `args` are DAG-JSON bytes (`{"/": {"bytes": "<base64, unpadded>"}}`),
53
- * how skein reads JSON bodies (docs/MESSAGES.md "The messagebox"); answers are
35
+ * Bytes in bodies are DAG-JSON bytes (`{"/": {"bytes": "<base64, unpadded>"}}`); answers are
54
36
  * read as DAG-JSON bytes, hex or a byte array.
55
37
  */
56
38
  import { Utils } from "@bsv/sdk";
57
- export const SWAP_SUBMIT_FN = "amm.swap.submit";
58
- export const SWAP_STATUS_FN = "amm.swap.status";
59
39
  export const SWAP_TERMS_FN = "amm.swap.terms";
60
40
  export class RelayError extends Error {
61
41
  code;
@@ -86,11 +66,13 @@ export function readBytes(x) {
86
66
  export function callUrl(base) {
87
67
  return `${base.replace(/\/+$/, "")}/call`;
88
68
  }
89
- export function submitBody(s) {
90
- return {
91
- fn: SWAP_SUBMIT_FN,
92
- args: { funding: dagBytes(s.funding), swap: dagBytes(s.swap), pool: s.pool, validator: dagBytes(Utils.toArray(s.validator, "hex")), peerId: s.peerId, expires: s.expires },
93
- };
69
+ export const SWAP_ROUTE = "/swap";
70
+ /** The host's swap route of a token instance's AMM base (`<origin>/@<tokenId>/amm`). */
71
+ export function swapUrl(base) {
72
+ return `${base.replace(/\/+$/, "")}${SWAP_ROUTE}`;
73
+ }
74
+ export function swapBody(s) {
75
+ return { funding: dagBytes(s.funding), swap: dagBytes(s.swap), pool: s.pool, validator: dagBytes(Utils.toArray(s.validator, "hex")), peerId: s.peerId };
94
76
  }
95
77
  /** One `{fn, args}` call on the app's `/call` route. Throws `RelayError` on an error answer. */
96
78
  export async function callApp(af, base, fn, args) {
@@ -147,32 +129,22 @@ export function toPoolState(raw) {
147
129
  validatorIdentityKey: identity,
148
130
  };
149
131
  }
150
- export function parseSwapRecord(x) {
132
+ /** The host's answer (200, JSON) to a leg, as a `SwapReply`; throws when it is no reply. */
133
+ export function parseSwapReply(x) {
151
134
  const r = x;
152
- const id = r?.id ?? r?.swapId;
153
- const status = r?.status ?? r?.state;
154
- if (!r || typeof id !== "string")
155
- throw new Error(`relay: not a swap record: ${JSON.stringify(x)?.slice(0, 200)}`);
156
- switch (status) {
157
- case "pending":
158
- case "timeout":
159
- return { id, status };
160
- case "failed":
161
- return { id, status, reason: (typeof r.reason === "string" ? r.reason : "failed") + (typeof r.detail === "string" && r.detail ? `: ${r.detail}` : "") };
162
- case "accepted": {
163
- const tx = readBytes(r.tx);
164
- if (!tx || typeof r.txid !== "string")
165
- throw new Error("relay: accepted without tx / txid");
166
- return { id, status, tx, txid: r.txid };
167
- }
168
- case "refused": {
169
- const pool = toPoolState(r.poolState ?? (typeof r.pool === "object" ? r.pool : undefined));
170
- const reason = (typeof r.reason === "string" ? r.reason : "refused") + (typeof r.detail === "string" && r.detail ? `: ${r.detail}` : "");
171
- return pool ? { id, status, reason, pool } : { id, status, reason };
172
- }
173
- default:
174
- throw new Error(`relay: unknown swap status ${JSON.stringify(status)}`);
135
+ if (!r || typeof r !== "object" || typeof r.ok !== "boolean")
136
+ throw new Error(`relay: not a swap reply: ${JSON.stringify(x)?.slice(0, 200)}`);
137
+ if (r.ok) {
138
+ const tx = readBytes(r.tx);
139
+ if (!tx || typeof r.txid !== "string")
140
+ throw new Error("relay: accepted without tx / txid");
141
+ return { ok: true, tx, txid: r.txid };
175
142
  }
143
+ const reason = (typeof r.reason === "string" ? r.reason : "refused") + (typeof r.detail === "string" && r.detail ? `: ${r.detail}` : "");
144
+ if (r.reason === "unreachable")
145
+ return { ok: false, unreachable: true, reason };
146
+ const pool = toPoolState(typeof r.pool === "object" ? r.pool : undefined);
147
+ return pool ? { ok: false, reason, pool } : { ok: false, reason };
176
148
  }
177
149
  /** `amm.swap.terms`'s answer: `commissionPkh` as DAG-JSON bytes (or hex, or a byte array) of 20 bytes, or null / absent; `minCommissionBps` an integer 0 to 10 000, absent 0 (0.16.0). */
178
150
  export function parseSwapTerms(x) {
@@ -192,10 +164,28 @@ export function parseSwapTerms(x) {
192
164
  export async function swapTerms(af, base) {
193
165
  return parseSwapTerms(await callApp(af, base, SWAP_TERMS_FN, {}));
194
166
  }
195
- export async function submitSwap(af, base, s) {
196
- const { fn, args } = submitBody(s);
197
- return parseSwapRecord(await callApp(af, base, fn, args));
198
- }
199
- export async function swapStatus(af, base, id) {
200
- return parseSwapRecord(await callApp(af, base, SWAP_STATUS_FN, { id }));
167
+ /**
168
+ * One leg to the host's swap route (a plain POST; `signal` closes the request — the page's patience,
169
+ * the host's only timer). Throws `RelayError` on an answer that is not 200 (the host relayed nothing)
170
+ * and the fetch's own error on a network failure or an abort (no answer: the swap may stand).
171
+ */
172
+ export async function postSwap(fetchFn, base, s, signal) {
173
+ const res = await fetchFn(swapUrl(base), {
174
+ method: "POST",
175
+ headers: { "content-type": "application/json", accept: "application/json" },
176
+ body: JSON.stringify(swapBody(s)),
177
+ ...(signal ? { signal } : {}),
178
+ });
179
+ const text = await res.text();
180
+ let answer = null;
181
+ try {
182
+ answer = JSON.parse(text);
183
+ }
184
+ catch {
185
+ /* not JSON */
186
+ }
187
+ if (res.status === 200 && answer && typeof answer === "object" && "ok" in answer)
188
+ return parseSwapReply(answer);
189
+ const e = answer;
190
+ throw new RelayError(typeof e?.code === "string" ? e.code : `HTTP ${res.status}`, typeof e?.description === "string" ? e.description : text.slice(0, 200), res.status);
201
191
  }
@@ -20,7 +20,7 @@
20
20
  * into `amm-swap` with the order's key input in its customInstructions; the LAST leg (it takes
21
21
  * all that remains) → the WALLET: sats to a BRC-29 key of the user's, tokens to a module
22
22
  * receiving key for self, both `legDerivation(order, "change")`.
23
- * 4. The relay (src/market/relay.ts) carries it to the validator with the first sats input's
23
+ * 4. The host's relay (src/market/relay.ts, `POST /@<tokenId>/amm/swap`, 0.44.0) carries it to the validator with the first sats input's
24
24
  * transaction as the swap's funding transaction: ONE BEEF of every staged input's source,
25
25
  * its subject that transaction (0.40.1, `fundingBeef`).
26
26
  * 5. Accepted: `completeSwap` takes in, in ONE `internalizeAction`, the payout, the commission
@@ -35,11 +35,6 @@ import { type PaymentRemittance } from "../wallet/brc29.js";
35
35
  import { type ColorReceipt } from "../wallet/color.js";
36
36
  /** A P2PKH unlocking script's upper bound: push(DER ≤ 72 + sighash byte) + push(33-byte key). */
37
37
  export declare const P2PKH_UNLOCK_LENGTH = 108;
38
- /**
39
- * How long the relay may take (the swap's `expires`): 15 s (0.34.0, D15, David Case 2026-11-04;
40
- * was 120 s, a 10-05 builder number): the held submit returns inside it.
41
- */
42
- export declare const SWAP_TTL_MS = 15000;
43
38
  export declare class LegShapeError extends Error {
44
39
  constructor(message: string);
45
40
  }
@@ -199,8 +194,6 @@ export interface PrepareSwapInput extends PlanLegInput {
199
194
  last: boolean;
200
195
  /** The swap basket's source transactions: the rows' (default: `amm-swap`'s, from the wallet). */
201
196
  holdingBeef?: Beef;
202
- now?: number;
203
- ttlMs?: number;
204
197
  }
205
198
  export type SwapPayout = {
206
199
  kind: "color";
@@ -228,7 +221,6 @@ export interface PreparedSwap {
228
221
  pool: string;
229
222
  validator: string;
230
223
  bsvIn: boolean;
231
- expires: number;
232
224
  plan: CallPlan;
233
225
  /** The order's key input (`<txid>.<vout>`): what the payout keys are derived from. */
234
226
  order: string;
@@ -20,7 +20,7 @@
20
20
  * into `amm-swap` with the order's key input in its customInstructions; the LAST leg (it takes
21
21
  * all that remains) → the WALLET: sats to a BRC-29 key of the user's, tokens to a module
22
22
  * receiving key for self, both `legDerivation(order, "change")`.
23
- * 4. The relay (src/market/relay.ts) carries it to the validator with the first sats input's
23
+ * 4. The host's relay (src/market/relay.ts, `POST /@<tokenId>/amm/swap`, 0.44.0) carries it to the validator with the first sats input's
24
24
  * transaction as the swap's funding transaction: ONE BEEF of every staged input's source,
25
25
  * its subject that transaction (0.40.1, `fundingBeef`).
26
26
  * 5. Accepted: `completeSwap` takes in, in ONE `internalizeAction`, the payout, the commission
@@ -36,11 +36,6 @@ import { WALLET_PAYMENT, identityKeyOf, newBrc29Payout, p2pkhHexOf } from "../wa
36
36
  import { SWAP_BASKET, SWAP_PROTOCOL, newReceivingKey, receiptOutput, swapInstructions, swapSatsInstructions } from "../wallet/color.js";
37
37
  /** A P2PKH unlocking script's upper bound: push(DER ≤ 72 + sighash byte) + push(33-byte key). */
38
38
  export const P2PKH_UNLOCK_LENGTH = 108;
39
- /**
40
- * How long the relay may take (the swap's `expires`): 15 s (0.34.0, D15, David Case 2026-11-04;
41
- * was 120 s, a 10-05 builder number): the held submit returns inside it.
42
- */
43
- export const SWAP_TTL_MS = 15_000;
44
39
  const toHex = (b) => Utils.toHex(b);
45
40
  export class LegShapeError extends Error {
46
41
  constructor(message) {
@@ -462,7 +457,6 @@ export async function prepareSwap(i) {
462
457
  pool: i.leg.outpoint,
463
458
  validator: i.pool.validatorIdentityKey,
464
459
  bsvIn,
465
- expires: (i.now ?? Date.now()) + (i.ttlMs ?? SWAP_TTL_MS),
466
460
  plan,
467
461
  order,
468
462
  last: i.last,
@@ -1,24 +1,27 @@
1
1
  /**
2
- * A prepared swap through the relay to its end (pure over the wallet and
3
- * AuthFetch, tested with fakes). 0.34.0 (D15, David Case 2026-11-04): the submit's answer is
4
- * the settled record — the host holds the `/call` request until the relay thread rests (the
5
- * validator's answer; a dial failure settles `failed` at once) — so nothing polls:
2
+ * A prepared swap through the host's relay to its end (pure over the wallet and fetch, tested with
3
+ * fakes). 0.44.0 (David Case 2026-11-06): the host is the relay — one plain POST per leg to
4
+ * `<origin>/@<tokenId>/amm/swap`, answered with the validator's reply (src/market/relay.ts); no
5
+ * record, no status, no expiry:
6
6
  *
7
- * submit (amm.swap.submit) →
8
- * accepted: completeSwap (internalizeAction; 0.29.0: no relinquishOutput)
9
- * refused / timeout / an error answer to submit (nothing recorded):
7
+ * POST /swap →
8
+ * {ok: true, tx, txid}: completeSwap (internalizeAction; 0.29.0: no relinquishOutput) → accepted
9
+ * {ok: false, reason, pool?} (the validator's refusal, or the host's commission rule): refused —
10
10
  * nothing to undo: the leg's `amm-swap` outputs stay there
11
- * no answer (a network failure, the host's 503 past its bound, a `pending` record, the
12
- * relay's transport `failed`): "no answer yet", left alone — the relay may still carry it
13
- * (0.40.0: the swap's loop resolves it from the overlay, sends it once more, or races it;
14
- * src/client.ts `executeSwap`).
11
+ * {ok: false, reason: "unreachable"} (any break in the host's call), a network failure, or no
12
+ * answer within LEG_PATIENCE_MS (the request is closed: the host stops): unknown, "no answer
13
+ * yet" — the swap may stand (0.40.0: the swap's loop resolves it from the overlay, asks once
14
+ * more, or races it; src/client.ts `executeSwap`)
15
+ * an answer that is not 200 (a bad request, an unknown token): failed — the host relayed nothing
15
16
  *
16
- * The swap's id is the relay's (amm-p2p relay.zig `idOf`): sha256 of the swap as submitted (the
17
- * validator's slot empty), hex — known to the page, so a lost answer can be checked again.
17
+ * The page's patience is the only timer: the AbortController's signal closes the request, and with
18
+ * it the host's attempt (a plain fetch honours `signal`; AuthFetch does not, which is why the leg is
19
+ * a plain fetch). The swap's id is sha256 of the swap as sent (the validator's slot empty), hex: the
20
+ * page's own name for a leg.
18
21
  */
19
22
  import { type WalletInterface } from "@bsv/sdk";
20
23
  import type { PoolState } from "@dcasegr/amm-engine";
21
- import { type AuthFetchLike } from "./relay.js";
24
+ import { type FetchLike } from "./relay.js";
22
25
  import { type CompletedSwap, type PreparedSwap } from "./swapAction.js";
23
26
  export type SwapOutcome = {
24
27
  status: "accepted";
@@ -30,9 +33,6 @@ export type SwapOutcome = {
30
33
  id: string;
31
34
  reason: string;
32
35
  pool?: PoolState;
33
- } | {
34
- status: "timeout";
35
- id: string;
36
36
  } | {
37
37
  status: "failed";
38
38
  reason: string;
@@ -41,15 +41,20 @@ export type SwapOutcome = {
41
41
  id: string;
42
42
  reason: string;
43
43
  };
44
+ /** How long the page waits for one leg's answer before it closes the request (the host then stops), ms. */
45
+ export declare const LEG_PATIENCE_MS = 15000;
44
46
  export interface RelayContext {
45
47
  wallet: WalletInterface;
46
- authFetch: AuthFetchLike;
47
- /** The token instance's AMM base. */
48
+ /** A plain fetch (it must honour `signal`). Default: the global fetch. */
49
+ fetch?: FetchLike;
50
+ /** The token instance's AMM base, `<origin>/@<tokenId>/amm`. */
48
51
  base: string;
52
+ /** The page's patience for this leg (default LEG_PATIENCE_MS). */
53
+ patienceMs?: number;
49
54
  }
50
- /** The relay's id of a prepared swap: sha256 of the swap's bytes as submitted, hex. */
55
+ /** A prepared swap's id: sha256 of the swap's bytes as sent, hex. */
51
56
  export declare function swapIdOf(p: PreparedSwap): string;
52
- /** "no answer yet": what a held request that came back without the settled record means. */
57
+ /** "no answer yet": what a leg that came back without the validator's reply means. */
53
58
  export declare const NO_ANSWER_YET = "no answer yet";
54
- /** `peerId`: the chosen validator's libp2p peer ID, from the pools route's row (the relay dials it). */
59
+ /** `peerId`: the chosen validator's libp2p peer ID, from the pools route's row (the host dials it). */
55
60
  export declare function relaySwap(c: RelayContext, p: PreparedSwap, peerId: string): Promise<SwapOutcome>;
@@ -1,68 +1,65 @@
1
1
  /**
2
- * A prepared swap through the relay to its end (pure over the wallet and
3
- * AuthFetch, tested with fakes). 0.34.0 (D15, David Case 2026-11-04): the submit's answer is
4
- * the settled record — the host holds the `/call` request until the relay thread rests (the
5
- * validator's answer; a dial failure settles `failed` at once) — so nothing polls:
2
+ * A prepared swap through the host's relay to its end (pure over the wallet and fetch, tested with
3
+ * fakes). 0.44.0 (David Case 2026-11-06): the host is the relay — one plain POST per leg to
4
+ * `<origin>/@<tokenId>/amm/swap`, answered with the validator's reply (src/market/relay.ts); no
5
+ * record, no status, no expiry:
6
6
  *
7
- * submit (amm.swap.submit) →
8
- * accepted: completeSwap (internalizeAction; 0.29.0: no relinquishOutput)
9
- * refused / timeout / an error answer to submit (nothing recorded):
7
+ * POST /swap →
8
+ * {ok: true, tx, txid}: completeSwap (internalizeAction; 0.29.0: no relinquishOutput) → accepted
9
+ * {ok: false, reason, pool?} (the validator's refusal, or the host's commission rule): refused —
10
10
  * nothing to undo: the leg's `amm-swap` outputs stay there
11
- * no answer (a network failure, the host's 503 past its bound, a `pending` record, the
12
- * relay's transport `failed`): "no answer yet", left alone — the relay may still carry it
13
- * (0.40.0: the swap's loop resolves it from the overlay, sends it once more, or races it;
14
- * src/client.ts `executeSwap`).
11
+ * {ok: false, reason: "unreachable"} (any break in the host's call), a network failure, or no
12
+ * answer within LEG_PATIENCE_MS (the request is closed: the host stops): unknown, "no answer
13
+ * yet" — the swap may stand (0.40.0: the swap's loop resolves it from the overlay, asks once
14
+ * more, or races it; src/client.ts `executeSwap`)
15
+ * an answer that is not 200 (a bad request, an unknown token): failed — the host relayed nothing
15
16
  *
16
- * The swap's id is the relay's (amm-p2p relay.zig `idOf`): sha256 of the swap as submitted (the
17
- * validator's slot empty), hex — known to the page, so a lost answer can be checked again.
17
+ * The page's patience is the only timer: the AbortController's signal closes the request, and with
18
+ * it the host's attempt (a plain fetch honours `signal`; AuthFetch does not, which is why the leg is
19
+ * a plain fetch). The swap's id is sha256 of the swap as sent (the validator's slot empty), hex: the
20
+ * page's own name for a leg.
18
21
  */
19
22
  import { Hash, Utils } from "@bsv/sdk";
20
- import { RelayError, submitSwap } from "./relay.js";
23
+ import { RelayError, postSwap } from "./relay.js";
21
24
  import { completeSwap } from "./swapAction.js";
25
+ /** How long the page waits for one leg's answer before it closes the request (the host then stops), ms. */
26
+ export const LEG_PATIENCE_MS = 15_000;
22
27
  const errText = (e) => (e instanceof Error ? e.message : String(e));
23
- /** The relay's id of a prepared swap: sha256 of the swap's bytes as submitted, hex. */
28
+ /** A prepared swap's id: sha256 of the swap's bytes as sent, hex. */
24
29
  export function swapIdOf(p) {
25
30
  return Utils.toHex(Hash.sha256(p.swap.toBinary()));
26
31
  }
27
- /** "no answer yet": what a held request that came back without the settled record means. */
32
+ /** "no answer yet": what a leg that came back without the validator's reply means. */
28
33
  export const NO_ANSWER_YET = "no answer yet";
29
- /** `peerId`: the chosen validator's libp2p peer ID, from the pools route's row (the relay dials it). */
34
+ /** `peerId`: the chosen validator's libp2p peer ID, from the pools route's row (the host dials it). */
30
35
  export async function relaySwap(c, p, peerId) {
31
36
  const id = swapIdOf(p);
32
- let record;
37
+ const patience = c.patienceMs ?? LEG_PATIENCE_MS;
38
+ const ac = new AbortController();
39
+ const timer = setTimeout(() => ac.abort(new Error(`no answer within ${patience} ms`)), patience);
40
+ let reply;
33
41
  try {
34
- record = await submitSwap(c.authFetch, c.base, {
42
+ reply = await postSwap(c.fetch ?? globalThis.fetch, c.base, {
35
43
  funding: p.funding.atomicBeef,
36
44
  swap: p.swap.toBinary(),
37
45
  pool: p.pool,
38
46
  validator: p.validator,
39
47
  peerId,
40
- expires: p.expires,
41
- });
48
+ }, ac.signal);
42
49
  }
43
50
  catch (err) {
44
- // The host's bound passed with the relay still out: no answer yet (the swap stands).
45
- if (err instanceof RelayError && err.httpStatus === 503)
46
- return { status: "unknown", id, reason: NO_ANSWER_YET };
47
- // The relay answered with an error: it recorded nothing and sent nothing on.
51
+ // An answer that is not 200: the host relayed nothing.
48
52
  if (err instanceof RelayError)
49
- return { status: "failed", reason: `amm.swap.submit: ${err.message}` };
50
- return { status: "unknown", id, reason: `${NO_ANSWER_YET}: amm.swap.submit: ${errText(err)}` };
53
+ return { status: "failed", reason: `swap: ${err.message}` };
54
+ // A network failure, or the page's patience ran out (the request closed: the host stopped).
55
+ return { status: "unknown", id, reason: `${NO_ANSWER_YET}: ${ac.signal.aborted ? errText(ac.signal.reason) : errText(err)}` };
51
56
  }
52
- return act(c, p, record);
53
- }
54
- /** A record's outcome, acted on (accepted: the wallet takes the payout in). */
55
- async function act(c, p, r) {
56
- switch (r.status) {
57
- case "pending":
58
- return { status: "unknown", id: r.id, reason: NO_ANSWER_YET };
59
- case "accepted":
60
- return { status: "accepted", id: r.id, txid: r.txid, completed: await completeSwap(c.wallet, p, r.tx, r.txid) };
61
- case "refused":
62
- return r.pool ? { status: "refused", id: r.id, reason: r.reason, pool: r.pool } : { status: "refused", id: r.id, reason: r.reason };
63
- case "timeout":
64
- return { status: "timeout", id: r.id };
65
- case "failed":
66
- return { status: "unknown", id: r.id, reason: `the relay could not reach the validator (${r.reason})` };
57
+ finally {
58
+ clearTimeout(timer);
67
59
  }
60
+ if (reply.ok)
61
+ return { status: "accepted", id, txid: reply.txid, completed: await completeSwap(c.wallet, p, reply.tx, reply.txid) };
62
+ if ("unreachable" in reply)
63
+ return { status: "unknown", id, reason: `${NO_ANSWER_YET}: the host could not reach the validator (${reply.reason})` };
64
+ return reply.pool ? { status: "refused", id, reason: reply.reason, pool: reply.pool } : { status: "refused", id, reason: reply.reason };
68
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcasegr/amm-client",
3
- "version": "0.42.3",
3
+ "version": "0.44.1",
4
4
  "type": "module",
5
5
  "description": "The Open Exchange client (skein-amm): reads, swap, pool deploy / close / rebalance, move back and positions over a BRC-100 wallet, bound to one exchange by createClient. No React, no styles, no wallet provider.",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@1sat/actions": "0.1.3",
33
- "@dcasegr/amm-engine": "0.42.3",
33
+ "@dcasegr/amm-engine": "0.44.1",
34
34
  "cbor2": "^2.3.0"
35
35
  },
36
36
  "peerDependencies": {