@dcasegr/amm-client 0.0.0-stage → 0.38.2

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,144 @@
1
+ /**
2
+ * 0.34.0 (D9): the rows are read here; their pools' state and fees come from ONE `GET
3
+ * /@<token>/amm/positions?lp=k1,k2` per token (src/client.ts `myPositions`), not from
4
+ * `/spends` per row as below; the pool output's BEEF is read only when the LP acts (Close: `GET
5
+ * /@<token>/amm/pools/beef?lp=<its key>`).
6
+ *
7
+ * The connected wallet's positions (skein-amm 0.9.0, David Case 2026-10-09;
8
+ * 0.12.0: the rows in the exchange's basket `amm-pools`, no claim): the pool
9
+ * rows its deploys filed (op `amm-pool`, the LP key in their
10
+ * customInstructions), each read from the CHAIN STATE by outpoint —
11
+ * `GET <base>/spends` (amm-p2p reads.zig, the chain app's `spent` as the
12
+ * overlay reads it): the pool followed from the deploy's output to its
13
+ * current output (each swap's continuation), or closed (by the LP or by the
14
+ * validator, docs/AMM.md "The validator's Close").
15
+ *
16
+ * 0.29.3: the deploy HISTORY is the first source (`historyPoolRows`): 0.29.4,
17
+ * each `amm-pool-deploy` action's pool output names its LP keyID in its
18
+ * description (`… · amm-lp <hex>`) and its script is the pool (no
19
+ * customInstructions needed: BRC-100's listActions does not answer them). 0.28.0: the rows also come from the wallet's HISTORY (`historyPoolRows`):
20
+ * `listOutputs` answers spendable outputs only, so a pool output the wallet
21
+ * itself spent (the LP swapped against its own pool) left no row and the
22
+ * position vanished; the deploys' actions (label `amm-pool-deploy`) still
23
+ * carry the output filed in `amm-pools` with its customInstructions. Both
24
+ * sets, deduped by outpoint, are followed by `/spends` as before.
25
+ *
26
+ * The current output's BEEF comes from the token's 1Color lookup
27
+ * (0.20.1: BRC-207's `color-spendability` at `ls_color_<tokenId>`: the
28
+ * output if admitted and unspent, with its BEEF; `ls_color {txid,
29
+ * outputIndex}` until 0.19.0).
30
+ */
31
+ import { Utils } from "@bsv/sdk";
32
+ import { PoolTemplate } from "../pool/index.js";
33
+ import { parseOutpoint, sdkTokenId, tokenIdOfWire, tokenIdText } from "../lib/tokenId.js";
34
+ import { POOLS_BASKET } from "../wallet/color.js";
35
+ import { POOL_TAG, isPoolRow } from "./poolRows.js";
36
+ import { LP_KEY_PROTOCOL, lpKeyIdOfDescription, poolCustomInstructions } from "./poolDeploy.js";
37
+ /** The label a pool deploy gives its action (src/lp/poolDeploy.ts). */
38
+ export const POOL_DEPLOY_LABEL = "amm-pool-deploy";
39
+ /**
40
+ * The pool rows of the wallet's deploy actions (0.28.0): every output of an action labelled
41
+ * `amm-pool-deploy` filed in `amm-pools` as a pool row (tag `amm-pool` or op `amm-pool` in its
42
+ * customInstructions), spent or not. `listActions` with `includeOutputs` (and, where the wallet
43
+ * takes it, `includeCustomInstructions`), paged; a wallet that refuses the read: none.
44
+ */
45
+ export async function historyPoolRows(wallet) {
46
+ const out = [];
47
+ const limit = 1000;
48
+ try {
49
+ for (let offset = 0; offset < 100_000; offset += limit) {
50
+ const r = await wallet.listActions({ labels: [POOL_DEPLOY_LABEL], includeOutputs: true, includeOutputLockingScripts: true, includeCustomInstructions: true, limit, offset });
51
+ for (const a of r.actions ?? []) {
52
+ for (const o of a.outputs ?? []) {
53
+ const outpoint = `${a.txid}.${o.outputIndex}`;
54
+ // 0.29.4: the LP keyID from the pool output's description (`… · amm-lp <hex>`), the pool from its script.
55
+ const keyID = lpKeyIdOfDescription(o.outputDescription);
56
+ const pool = keyID && o.lockingScript ? PoolTemplate.decode(o.lockingScript) : null;
57
+ if (pool && keyID) {
58
+ const tokenId = sdkTokenId(tokenIdOfWire(Utils.toArray(pool.args.assetId, "hex")));
59
+ out.push({
60
+ outpoint,
61
+ satoshis: o.satoshis,
62
+ lockingScript: o.lockingScript,
63
+ tags: [POOL_TAG],
64
+ customInstructions: poolCustomInstructions({ tokenId, protocolID: LP_KEY_PROTOCOL, keyID, args: pool.args, validatorIdentity: pool.state.validatorIdentity, ...(pool.state.nonce ? { nonce: pool.state.nonce } : {}) }),
65
+ });
66
+ continue;
67
+ }
68
+ // A wallet that answers the output's customInstructions (0.28.0): the filed row as it is.
69
+ if (o.basket !== POOLS_BASKET)
70
+ continue;
71
+ const row = { outpoint, satoshis: o.satoshis, ...(o.lockingScript ? { lockingScript: o.lockingScript } : {}), ...(o.tags ? { tags: o.tags } : {}), ...(o.customInstructions ? { customInstructions: o.customInstructions } : {}) };
72
+ if (isPoolRow(row))
73
+ out.push(row);
74
+ }
75
+ }
76
+ if ((r.actions?.length ?? 0) < limit || offset + limit >= (r.totalActions ?? 0))
77
+ break;
78
+ }
79
+ }
80
+ catch {
81
+ /* no history read: the basket's rows alone */
82
+ }
83
+ return out;
84
+ }
85
+ /** Rows deduped by outpoint (`<txid>.<vout>`), the first kept (the basket's before the history's). */
86
+ export function mergePoolRows(...sets) {
87
+ const seen = new Set();
88
+ const out = [];
89
+ for (const r of sets.flat()) {
90
+ const op = parseOutpoint(r.outpoint);
91
+ const k = op ? `${op.txid}.${op.vout}` : r.outpoint;
92
+ if (seen.has(k))
93
+ continue;
94
+ seen.add(k);
95
+ out.push(r);
96
+ }
97
+ return out;
98
+ }
99
+ /** The wallet's pool rows (`isPoolRow`). */
100
+ export function positionRowsOf(rows) {
101
+ const out = [];
102
+ for (const r of rows) {
103
+ if (!isPoolRow(r))
104
+ continue;
105
+ const op = parseOutpoint(r.outpoint);
106
+ if (!op)
107
+ continue;
108
+ try {
109
+ const ci = JSON.parse(r.customInstructions ?? "{}");
110
+ if (!Array.isArray(ci.protocolID) || typeof ci.keyID !== "string" || typeof ci.id !== "string")
111
+ continue;
112
+ const tid = parseOutpoint(ci.id.replace("_", "."));
113
+ const filed = r.lockingScript ? PoolTemplate.decode(r.lockingScript) : null;
114
+ out.push({
115
+ deployOutpoint: `${op.txid}.${op.vout}`,
116
+ tokenId: tid ? tokenIdText(tid) : ci.id,
117
+ ...(ci.sym ? { sym: ci.sym } : {}),
118
+ ...(ci.dec !== undefined ? { dec: Number(ci.dec) } : {}),
119
+ lpKey: { protocolID: ci.protocolID, keyID: ci.keyID, counterparty: typeof ci.counterparty === "string" ? ci.counterparty : "self" },
120
+ ...(typeof ci.amm?.validatorIdentity === "string" ? { validatorIdentity: ci.amm.validatorIdentity } : {}),
121
+ ...(filed ? { lpPubKey: filed.state.lpPubKey } : {}),
122
+ });
123
+ }
124
+ catch {
125
+ /* skip */
126
+ }
127
+ }
128
+ return out;
129
+ }
130
+ /** Positions (live and closed) summed per token, in the order each token first appears; fees not told count as 0. */
131
+ export function feesByToken(ps) {
132
+ const by = new Map();
133
+ for (const p of ps) {
134
+ let t = by.get(p.tokenId);
135
+ if (!t) {
136
+ t = { tokenId: p.tokenId, sym: p.sym, dec: p.dec, positions: 0, sats: 0n, tokens: 0n };
137
+ by.set(p.tokenId, t);
138
+ }
139
+ t.positions += 1;
140
+ t.sats += p.feesEarnedSats ?? 0n;
141
+ t.tokens += p.feesEarnedTokens ?? 0n;
142
+ }
143
+ return [...by.values()];
144
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Rebalance (skein-amm 0.38.0, D11, David Case 2026-11-05): a position's pool closed and a new
3
+ * one deployed in ONE wallet createAction — what Close (src/lp/close.ts) and the deploy
4
+ * (src/lp/poolDeploy.ts) do, in the same transaction. A position is still only deployed or
5
+ * closed (the pool A's Close, a new pool B with a new LP key); wallet-built, so nothing is
6
+ * broadcast-then-taken-in (that is the page-built swap's).
7
+ *
8
+ * 1. The old pool A's current output and its BEEF (the caller's: `GET
9
+ * /@<token>/amm/pools/beef?lp=<A's LP key>`), A's LP key checked against the wallet's.
10
+ * 2. The new pool B as the deploy makes it: a new LP key (`newLpKey`), the proposed pool
11
+ * (`planPoolDeploy`), the validator's affirmation (`amm.pool.affirm`, `affirmPool`: the same
12
+ * call a deploy makes, with B's sats and tokens).
13
+ * 3. ONE createAction through the wallet's BRC-207 module, a `p color plan` transfer:
14
+ * - input 0: A, explicit (its BEEF in `inputBEEF`, `unlockingScriptLength`
15
+ * `PoolTemplate.maxCallUnlockLength`) — to the module a FOREIGN token input, checked
16
+ * admitted and unspent on the token's overlay, its tokens counted;
17
+ * - output 0: B (a foreign script to the module; its description the deploy's, naming B's
18
+ * LP keyID: `lpKeyDescription`);
19
+ * - the token difference through the module: B holding MORE tokens than A — the module
20
+ * selects the wallet's token inputs for the rest and adds token change (the deploy's
21
+ * path); FEWER — output 1, the rest to a NEW module receiving key of the user's own, the
22
+ * plan's owned output 1 (the Close's path; the module files it in `p color <assetTag>`);
23
+ * - the sats difference: the wallet funds what B needs beyond A's sats, its change takes
24
+ * what A had beyond B's (and pays the fee);
25
+ * - labels: the plan's, `amm-pool-deploy` (the deploy's: the positions reader finds B in the
26
+ * history) and `amm-close` (the Close's); `randomizeOutputs: false`.
27
+ * The page fills A's input over the wallet's transaction at signAction (`callUnlock` + the
28
+ * LP's `createSignature`, checked with `Spend`; Close's outputs are free, so the preimage
29
+ * covers B and every output the module and the wallet added); the module signs its inputs,
30
+ * approves and submits; the wallet broadcasts.
31
+ * 4. The page's own submit at the host root (`tm_color_<assetId>`), then B's position row filed
32
+ * in `amm-pools` (basket insertion, as the deploy files it).
33
+ */
34
+ import { type WalletInterface } from "@bsv/sdk";
35
+ import type { LookupOutput } from "../lib/overlay.js";
36
+ import type { AuthFetchLike } from "../market/relay.js";
37
+ import type { LpKeyRef } from "./positions.js";
38
+ import { type HeldOutput } from "../wallet/color.js";
39
+ import { newLpKey, type Affirmation, type BasketFiling, type PoolDeployPlan, type ValidatorChoice } from "./poolDeploy.js";
40
+ export declare class RebalanceShapeError extends Error {
41
+ constructor(message: string);
42
+ }
43
+ export interface RebalanceInput {
44
+ wallet: WalletInterface;
45
+ /** The wallet's BRC-104 client for the token instance (`amm.pool.affirm` on `/call`). */
46
+ af: AuthFetchLike;
47
+ /** The token instance's AMM base URL. */
48
+ base: string;
49
+ tokenId: string;
50
+ /** The old pool A's current output (step 1). */
51
+ poolOutput: LookupOutput;
52
+ /** A's LP key in the wallet. */
53
+ lpKey: LpKeyRef;
54
+ /** The new pool B's reserves: token base units and sats. */
55
+ tokens: bigint;
56
+ sats: bigint;
57
+ /** B's fees: the validator's terms now, as a deploy takes them. */
58
+ fees: {
59
+ lpFeeBps: bigint;
60
+ validatorFeeBps: bigint;
61
+ commissionBps: bigint;
62
+ };
63
+ validator: ValidatorChoice;
64
+ /** The wallet's held token outputs (`p color assets`): B holding more than A needs the rest there. */
65
+ held: HeldOutput[];
66
+ meta?: {
67
+ sym?: string;
68
+ dec?: number;
69
+ };
70
+ /** The submit to this skein (default: `POST <origin>/submit`, plain fetch). */
71
+ submit?: (topic: string, beef: number[]) => Promise<{
72
+ steak?: unknown;
73
+ }>;
74
+ }
75
+ export interface RebalancedPool {
76
+ /** A's output spent, `txid.vout`. */
77
+ closed: string;
78
+ plan: PoolDeployPlan;
79
+ lpKey: Awaited<ReturnType<typeof newLpKey>>;
80
+ affirmation: Affirmation;
81
+ /** The createAction arguments the wallet was given. */
82
+ createArgs: Parameters<WalletInterface["createAction"]>[0];
83
+ txid: string;
84
+ /** The transaction as the wallet's AtomicBEEF. */
85
+ tx: number[];
86
+ /** B, `<txid>.0`. */
87
+ outpoint: string;
88
+ /** B's token difference against A: positive, taken from the wallet; negative, returned to it (output 1). */
89
+ tokenDelta: bigint;
90
+ /** B's sats difference against A: positive, funded by the wallet; negative, into its change. */
91
+ satsDelta: bigint;
92
+ /** B's position row (`amm-pools`). */
93
+ filing: BasketFiling;
94
+ filed: boolean;
95
+ /** The page's own submit, in words (src/lp/close.ts `steakText`), or its error. */
96
+ submitted: string;
97
+ /** Filing or submit errors (the transaction itself is broadcast). */
98
+ errors: string[];
99
+ }
100
+ /** Steps 1-4 (see the module comment). Throws before the wallet's action exists or when it is refused; after the broadcast, errors are collected. */
101
+ export declare function rebalancePool(i: RebalanceInput): Promise<RebalancedPool>;
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Rebalance (skein-amm 0.38.0, D11, David Case 2026-11-05): a position's pool closed and a new
3
+ * one deployed in ONE wallet createAction — what Close (src/lp/close.ts) and the deploy
4
+ * (src/lp/poolDeploy.ts) do, in the same transaction. A position is still only deployed or
5
+ * closed (the pool A's Close, a new pool B with a new LP key); wallet-built, so nothing is
6
+ * broadcast-then-taken-in (that is the page-built swap's).
7
+ *
8
+ * 1. The old pool A's current output and its BEEF (the caller's: `GET
9
+ * /@<token>/amm/pools/beef?lp=<A's LP key>`), A's LP key checked against the wallet's.
10
+ * 2. The new pool B as the deploy makes it: a new LP key (`newLpKey`), the proposed pool
11
+ * (`planPoolDeploy`), the validator's affirmation (`amm.pool.affirm`, `affirmPool`: the same
12
+ * call a deploy makes, with B's sats and tokens).
13
+ * 3. ONE createAction through the wallet's BRC-207 module, a `p color plan` transfer:
14
+ * - input 0: A, explicit (its BEEF in `inputBEEF`, `unlockingScriptLength`
15
+ * `PoolTemplate.maxCallUnlockLength`) — to the module a FOREIGN token input, checked
16
+ * admitted and unspent on the token's overlay, its tokens counted;
17
+ * - output 0: B (a foreign script to the module; its description the deploy's, naming B's
18
+ * LP keyID: `lpKeyDescription`);
19
+ * - the token difference through the module: B holding MORE tokens than A — the module
20
+ * selects the wallet's token inputs for the rest and adds token change (the deploy's
21
+ * path); FEWER — output 1, the rest to a NEW module receiving key of the user's own, the
22
+ * plan's owned output 1 (the Close's path; the module files it in `p color <assetTag>`);
23
+ * - the sats difference: the wallet funds what B needs beyond A's sats, its change takes
24
+ * what A had beyond B's (and pays the fee);
25
+ * - labels: the plan's, `amm-pool-deploy` (the deploy's: the positions reader finds B in the
26
+ * history) and `amm-close` (the Close's); `randomizeOutputs: false`.
27
+ * The page fills A's input over the wallet's transaction at signAction (`callUnlock` + the
28
+ * LP's `createSignature`, checked with `Spend`; Close's outputs are free, so the preimage
29
+ * covers B and every output the module and the wallet added); the module signs its inputs,
30
+ * approves and submits; the wallet broadcasts.
31
+ * 4. The page's own submit at the host root (`tm_color_<assetId>`), then B's position row filed
32
+ * in `amm-pools` (basket insertion, as the deploy files it).
33
+ */
34
+ import { Beef, PublicKey, Transaction } from "@bsv/sdk";
35
+ import { PoolTemplate } from "../pool/index.js";
36
+ import { poolUtxoFrom, spendValid } from "../market/swapAction.js";
37
+ import { identityKeyOf, isBrc29Protocol } from "../wallet/brc29.js";
38
+ import { sdkTokenId } from "../lib/tokenId.js";
39
+ import { POOLS_BASKET, newReceivingKey, transferPlanLabels } from "../wallet/color.js";
40
+ import { inputIndexOf, runAppSignedAction } from "../wallet/action.js";
41
+ import { submitToOverlay, steakText } from "./close.js";
42
+ import { POOL_TAG, PoolDeployError, affirmPool, lpKeyDescription, newLpKey, planPoolDeploy, poolCustomInstructions, tokenP2pkhScript, valueOutputsOf } from "./poolDeploy.js";
43
+ import { POOL_DEPLOY_LABEL } from "./positions.js";
44
+ export class RebalanceShapeError extends Error {
45
+ constructor(message) {
46
+ super(message);
47
+ this.name = "RebalanceShapeError";
48
+ }
49
+ }
50
+ function describe(text) {
51
+ return text.length <= 50 ? text : `${text.slice(0, 49)}…`;
52
+ }
53
+ /** Steps 1-4 (see the module comment). Throws before the wallet's action exists or when it is refused; after the broadcast, errors are collected. */
54
+ export async function rebalancePool(i) {
55
+ const { wallet } = i;
56
+ const meta = i.meta ?? {};
57
+ const sdkId = sdkTokenId(i.tokenId);
58
+ const sym = meta.sym ?? "token";
59
+ // 1. A, and its LP key.
60
+ const poolUtxo = poolUtxoFrom(i.poolOutput);
61
+ const old = PoolTemplate.decode(poolUtxo.script);
62
+ if (!old)
63
+ throw new RebalanceShapeError("the output is not a pool");
64
+ if (!isBrc29Protocol(i.lpKey.protocolID) || i.lpKey.counterparty !== "self")
65
+ throw new RebalanceShapeError(`the LP key ${i.lpKey.keyID} is not a BRC-29 key to self`);
66
+ const lpArgs = { protocolID: i.lpKey.protocolID, keyID: i.lpKey.keyID, counterparty: i.lpKey.counterparty };
67
+ const { publicKey: lpPub } = await wallet.getPublicKey({ ...lpArgs, forSelf: true });
68
+ if (PublicKey.fromString(lpPub).toString() !== old.state.lpPubKey)
69
+ throw new RebalanceShapeError(`the wallet key ${i.lpKey.keyID} is not this pool's LP key`);
70
+ const tokenDelta = i.tokens - old.state.tokenReserve;
71
+ const satsDelta = i.sats - BigInt(poolUtxo.satoshis);
72
+ if (tokenDelta > 0n) {
73
+ const held = valueOutputsOf(i.held, i.tokenId).reduce((a, o) => a + o.amount, 0n);
74
+ if (held < tokenDelta)
75
+ throw new PoolDeployError(`Your wallet holds fewer than ${tokenDelta} base units of that token`);
76
+ }
77
+ // 2. B: a new LP key, the proposed pool, the validator's affirmation.
78
+ const lpKey = await newLpKey(wallet);
79
+ const plan = planPoolDeploy({
80
+ tokenId: i.tokenId,
81
+ tokens: i.tokens,
82
+ sats: i.sats,
83
+ ...i.fees,
84
+ lpPubKey: lpKey.publicKey,
85
+ lpKeyId: lpKey.keyID,
86
+ validator: i.validator,
87
+ ...(meta.dec !== undefined ? { dec: meta.dec } : {}),
88
+ });
89
+ const affirmation = await affirmPool(i.af, i.base, plan);
90
+ const poolScript = affirmation.lockingScript;
91
+ // 3. The one action: A in, B at output 0, the token difference through the module.
92
+ const outputs = [{ lockingScript: poolScript, satoshis: Number(plan.sats), outputDescription: lpKeyDescription(describe(`AMM pool: ${sym} / BSV`), lpKey.keyID) }];
93
+ let owned = {};
94
+ if (tokenDelta < 0n) {
95
+ const me = await identityKeyOf(wallet);
96
+ const k = await newReceivingKey(wallet, sdkId, me);
97
+ outputs.push({ lockingScript: tokenP2pkhScript(sdkId, -tokenDelta, k.publicKey), satoshis: 1, outputDescription: describe(`${sym} pool tokens to you`) });
98
+ owned = { ownedOutputs: [{ outputIndex: 1, ...k.receipt }] };
99
+ }
100
+ const first = PoolTemplate.planClose({ pool: poolUtxo, outputs: outputs.map((o) => ({ satoshis: BigInt(o.satoshis), script: o.lockingScript })) });
101
+ const poolOutpoint = `${poolUtxo.txid}.${poolUtxo.vout}`;
102
+ const lpSigner = async (sighash) => (await wallet.createSignature({ ...lpArgs, hashToDirectlySign: sighash })).signature;
103
+ const createArgs = {
104
+ description: describe(`AMM close + pool deploy: ${sym}`),
105
+ inputBEEF: Beef.fromBinary(i.poolOutput.beef).toBinary(),
106
+ inputs: [{ outpoint: poolOutpoint, unlockingScriptLength: PoolTemplate.maxCallUnlockLength(first), inputDescription: describe(`The ${sym} pool`) }],
107
+ outputs,
108
+ labels: [...transferPlanLabels(sdkId, owned), POOL_DEPLOY_LABEL, "amm-close"],
109
+ options: { randomizeOutputs: false, acceptDelayedBroadcast: false },
110
+ };
111
+ const signed = await runAppSignedAction(wallet, createArgs, async (tx) => {
112
+ // The contract reads input 0 (the preimage is input 0's); the wallet places named inputs first.
113
+ if (inputIndexOf(tx, poolUtxo.txid, poolUtxo.vout) !== 0)
114
+ throw new RebalanceShapeError("the wallet did not place the pool as input 0");
115
+ outputs.forEach((o, n) => {
116
+ if (tx.outputs[n]?.lockingScript.toHex() !== o.lockingScript || tx.outputs[n]?.satoshis !== o.satoshis)
117
+ throw new RebalanceShapeError(`the wallet moved output ${n} of the rebalance`);
118
+ });
119
+ // Close's outputs are free: the call's preimage covers B and every output the wallet added.
120
+ const callPlan = PoolTemplate.planClose({ pool: poolUtxo, outputs: tx.outputs.map((o) => ({ satoshis: BigInt(o.satoshis), script: o.lockingScript.toHex() })) });
121
+ tx.inputs[0].unlockingScript = PoolTemplate.callUnlock(callPlan, tx, null).unlockingScript;
122
+ await PoolTemplate.signPoolInput({ tx, pool: callPlan.pool, method: "close", unsigned: ["lp"], sourceSatoshis: poolUtxo.satoshis }, "lp", lpSigner);
123
+ if (!spendValid(tx, 0))
124
+ throw new RebalanceShapeError("input 0 of the rebalance does not validate");
125
+ return { 0: { unlockingScript: tx.inputs[0].unlockingScript.toHex() } };
126
+ });
127
+ const out = {
128
+ closed: poolOutpoint,
129
+ plan,
130
+ lpKey,
131
+ affirmation,
132
+ createArgs,
133
+ txid: signed.txid,
134
+ tx: signed.tx,
135
+ outpoint: `${signed.txid}.0`,
136
+ tokenDelta,
137
+ satsDelta,
138
+ filing: {
139
+ outputIndex: 0,
140
+ basket: POOLS_BASKET,
141
+ tags: [POOL_TAG],
142
+ customInstructions: poolCustomInstructions({ tokenId: sdkId, ...meta, protocolID: lpKey.protocolID, keyID: lpKey.keyID, args: plan.args, validatorIdentity: plan.state.validatorIdentity, nonce: affirmation.nonce }),
143
+ },
144
+ filed: false,
145
+ submitted: "not submitted",
146
+ errors: [],
147
+ };
148
+ if (Transaction.fromAtomicBEEF(signed.tx).outputs[0]?.lockingScript.toHex() !== poolScript)
149
+ out.errors.push("the wallet's transaction has another output 0 than the affirmed pool");
150
+ // 4. The page's own submit, then B's position row.
151
+ try {
152
+ const submit = i.submit ?? ((topic, beef) => submitToOverlay(i.base, topic, beef));
153
+ out.submitted = steakText((await submit(plan.topic, signed.tx)));
154
+ }
155
+ catch (err) {
156
+ out.submitted = "the submit failed";
157
+ out.errors.push(`submit: ${err instanceof Error ? err.message : String(err)}`);
158
+ }
159
+ const row = { outputIndex: 0, protocol: "basket insertion", insertionRemittance: { basket: out.filing.basket, tags: out.filing.tags, customInstructions: out.filing.customInstructions } };
160
+ try {
161
+ const r = await wallet.internalizeAction({ tx: signed.tx, outputs: [row], description: describe(`AMM pool deploy: ${sym}`), labels: [POOL_DEPLOY_LABEL] });
162
+ out.filed = r.accepted;
163
+ }
164
+ catch (err) {
165
+ out.errors.push(`internalizeAction: ${err instanceof Error ? err.message : String(err)}`);
166
+ }
167
+ return out;
168
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The wallet reads of the exchange's own baskets (skein-amm 0.12.0): the
3
+ * LP's position rows (`amm-pools`) and the active swap basket (`amm-swap`),
4
+ * src/wallet/color.ts. The user's tokens themselves are the BRC-207
5
+ * module's (`p color assets`, `p color balance <assetTag>`), read there.
6
+ * Nothing here talks to an overlay.
7
+ */
8
+ import { Beef, type WalletInterface, type WalletOutput } from "@bsv/sdk";
9
+ /** A basket's outputs with locking scripts, tags and customInstructions. */
10
+ export declare function basketRows(wallet: WalletInterface, basket: string): Promise<WalletOutput[]>;
11
+ /** A basket's source transactions (for a spend's BEEF), or null when the wallet answers none. */
12
+ export declare function basketBeef(wallet: WalletInterface, basket: string): Promise<Beef | null>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The wallet reads of the exchange's own baskets (skein-amm 0.12.0): the
3
+ * LP's position rows (`amm-pools`) and the active swap basket (`amm-swap`),
4
+ * src/wallet/color.ts. The user's tokens themselves are the BRC-207
5
+ * module's (`p color assets`, `p color balance <assetTag>`), read there.
6
+ * Nothing here talks to an overlay.
7
+ */
8
+ import { Beef } from "@bsv/sdk";
9
+ import { LIST_LIMIT } from "../wallet/color.js";
10
+ /** A basket's outputs with locking scripts, tags and customInstructions. */
11
+ export async function basketRows(wallet, basket) {
12
+ const r = await wallet.listOutputs({ basket, include: "locking scripts", includeTags: true, includeCustomInstructions: true, limit: LIST_LIMIT });
13
+ return r.outputs;
14
+ }
15
+ /** A basket's source transactions (for a spend's BEEF), or null when the wallet answers none. */
16
+ export async function basketBeef(wallet, basket) {
17
+ const r = await wallet.listOutputs({ basket, include: "entire transactions", limit: LIST_LIMIT });
18
+ return r.BEEF ? Beef.fromBinary(Array.from(r.BEEF)) : null;
19
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The active swap basket (skein-amm 0.12.0, David Case 2026-10-21/22; 0.29.0,
3
+ * 2026-11-02): everything the swap legs spend, held outside the BRC-207 module
4
+ * in the exchange's own basket `amm-swap` (src/wallet/color.ts): the token
5
+ * outputs and each leg's sats.
6
+ *
7
+ * - **In** (`sendToSwapBasket`): before the legs of a tokens-in swap, ONE
8
+ * wallet-built send through the module: one 1Color value output per leg
9
+ * not covered by what `amm-swap` already holds, each exactly the leg's
10
+ * amount in (the pool takes no token change, so a leg needs inputs that
11
+ * sum to its amount exactly), locked to a key derived from the leg's pool outpoint (`swapKeyID`) under `[2, "amm
12
+ * swap"]` (counterparty self), the plan naming them a transfer out; and
13
+ * (0.29.0) one P2PKH sats output per leg not covered (the leg's exact
14
+ * sats, `swapFunding`), to that outpoint's `:sats` key, filed in
15
+ * `amm-swap` by the createAction itself (customInstructions `{sats: true,
16
+ * assetId, protocolID, keyID, counterparty}`). The module passes the sats outputs
17
+ * through, selects the token inputs, adds change, asks once, signs and
18
+ * submits to `tm_color_<assetId>`; the wallet funds the sats; the page
19
+ * submits it to this skein too (a repeat is harmless), then files each
20
+ * token output in `amm-swap` (basket insertion, customInstructions
21
+ * `{assetId, amount, protocolID, keyID, counterparty}`). A sats-in swap,
22
+ * or a tokens-in swap whose tokens are all staged already, sends only the
23
+ * sats (`sendSatsToSwapBasket`: a plain createAction, no `p color` labels).
24
+ * - **Spent** by the legs (src/market/swapAction.ts): app-built,
25
+ * `createSignature` under the key the customInstructions name; the
26
+ * wallet marks them spent when it takes the accepted swap in (0.29.0: no
27
+ * relinquishOutput). An output a failed leg did not spend stays for a
28
+ * later swap.
29
+ * - **Move back** (`moveBack`, the recovery path; 0.29.3: a module plan,
30
+ * David Case 2026-11-02): ONE createAction through the wallet's BRC-207
31
+ * module, a `p color plan` transfer: every `amm-swap` token output of the
32
+ * token and (0.29.1) the sats rows staged for its legs (their `assetId`)
33
+ * as explicit inputs (their source transactions in `inputBEEF`) — to the
34
+ * module FOREIGN inputs (not in a `p color` basket): the token rows
35
+ * checked admitted and unspent on the token's overlay, the sats rows
36
+ * adding nothing; one output, the whole amount to a NEW module receiving
37
+ * key of the user's own, the plan's owned output 0 (the module checks it
38
+ * pays the remittance's key and files it in `p color <assetTag>` with its
39
+ * custody in the same call). The page unlocks its inputs at signAction;
40
+ * the wallet funds the fee and takes the sats rows into its change; the
41
+ * module approves and submits, the wallet broadcasts. The page submits it
42
+ * to this skein's token topic too. Nothing is taken in afterwards and
43
+ * nothing is kept in the browser. * (0.29.6) With no token row and only sats rows staged for the token
44
+ * (a sats-in leg that failed): one plain createAction (no `p color`
45
+ * labels), the sats rows as explicit inputs and no outputs — the wallet
46
+ * keeps them as its change and pays the fee (`moveBackSats`).
47
+ */
48
+ import { Beef, Transaction, type WalletInterface } from "@bsv/sdk";
49
+ import { type Steak } from "../lp/close.js";
50
+ import { type Spends } from "../wallet/action.js";
51
+ import { type SatsInput, type TokenInput } from "./swapAction.js";
52
+ /** The keyID prefix of an `amm-swap` key (`SWAP_PROTOCOL`, counterparty self). */
53
+ export declare const SWAP_KEY_PREFIX = "amm-swap-";
54
+ /**
55
+ * The keyID of a staged `amm-swap` output (0.29.2, David Case 2026-11-02: derived, not random):
56
+ * `amm-swap-<txid>.<vout>` for a leg's token row and `amm-swap-<txid>.<vout>:sats` for its sats
57
+ * row, `<txid>.<vout>` the leg's pool outpoint as quoted (`leg.outpoint`, `_` written `.`).
58
+ * Protocol `[2, "amm swap"]`, counterparty self, as before. A leg refused and retried against the
59
+ * same pool outpoint stages to the same key again, which is fine.
60
+ */
61
+ export declare function swapKeyID(poolOutpoint: string, kind: "token" | "sats"): string;
62
+ /** The `amm-swap` outputs of `tokenId` the legs can spend. */
63
+ export declare function swapHoldings(wallet: WalletInterface, tokenId: string): Promise<TokenInput[]>;
64
+ /** The `amm-swap` sats outputs the legs can spend (0.29.0). */
65
+ export declare function swapSatsHoldings(wallet: WalletInterface): Promise<SatsInput[]>;
66
+ /**
67
+ * What `amm-swap` holds, per token id: the token amount and the number of token outputs, and
68
+ * (0.29.6) the sats staged for the token's legs (the sats rows whose customInstructions name its
69
+ * `assetId`). A token with only sats staged has amount 0n and outputs 0.
70
+ */
71
+ export declare function swapTotals(wallet: WalletInterface): Promise<Map<string, {
72
+ amount: bigint;
73
+ outputs: number;
74
+ sats: number;
75
+ }>>;
76
+ /** The source transactions of `amm-swap`'s outputs (a leg's BEEF), with `extra` merged in. */
77
+ export declare function swapHoldingBeef(wallet: WalletInterface, extra?: number[]): Promise<Beef>;
78
+ export interface SentToSwapBasket {
79
+ txid: string;
80
+ /** The send as the wallet's AtomicBEEF. */
81
+ tx: number[];
82
+ /** The new `amm-swap` token outputs, in the order of `stage.tokens`. */
83
+ holdings: TokenInput[];
84
+ /** The new `amm-swap` sats outputs, in the order of `stage.sats` (0.29.0). */
85
+ sats: SatsInput[];
86
+ filed: boolean;
87
+ submitted: string;
88
+ errors: string[];
89
+ }
90
+ /** A staged sats output: the leg's exact sats, keyed by the leg's pool outpoint (`swapKeyID`). */
91
+ export interface StagedSats {
92
+ satoshis: number;
93
+ /** The leg's pool outpoint, `<txid>_<vout>` or `<txid>.<vout>`. */
94
+ pool: string;
95
+ }
96
+ /** What one send stages: a token output per leg (its amount in), then a sats output per leg. */
97
+ export interface SwapStage {
98
+ tokens: {
99
+ amount: bigint;
100
+ pool: string;
101
+ }[];
102
+ sats: StagedSats[];
103
+ }
104
+ /**
105
+ * "In" (see the module comment): one module send of `stage.tokens` (each one
106
+ * output) to `amm-swap` keys derived from each leg's pool (`swapKeyID`), filed in `amm-swap`, and of `stage.sats`
107
+ * (each one output) after them. Throws when the wallet refuses or fails the
108
+ * send; filing and the page's own submit are reported in `errors`.
109
+ */
110
+ export declare function sendToSwapBasket(wallet: WalletInterface, base: string, tokenId: string, stage: SwapStage, meta?: {
111
+ sym?: string;
112
+ }, submit?: (topic: string, beef: number[]) => Promise<{
113
+ steak?: Steak;
114
+ }>): Promise<SentToSwapBasket>;
115
+ export interface SentSats {
116
+ txid: string;
117
+ /** The send as the wallet's AtomicBEEF. */
118
+ tx: number[];
119
+ /** The new `amm-swap` sats outputs, in the order of `sats`. */
120
+ sats: SatsInput[];
121
+ errors: string[];
122
+ }
123
+ /**
124
+ * The sats-only send (0.29.0): one plain createAction (no `p color` labels: the module is not in
125
+ * the path) with one P2PKH output per amount to the leg's "amm swap" `:sats` key (`swapKeyID`), filed in `amm-swap`; the
126
+ * wallet funds it, adds change and broadcasts. Throws when the wallet refuses or fails it.
127
+ */
128
+ export declare function sendSatsToSwapBasket(wallet: WalletInterface, tokenId: string, sats: StagedSats[], meta?: {
129
+ sym?: string;
130
+ }): Promise<SentSats>;
131
+ export interface MovedBack {
132
+ txid: string;
133
+ amount: bigint;
134
+ /** The transaction as the wallet's AtomicBEEF. */
135
+ beef: number[];
136
+ submitted: string;
137
+ errors: string[];
138
+ }
139
+ /** The page's unlocking scripts for the `amm-swap` outputs `holdings` in the wallet's transaction `tx`, each checked with `Spend`. */
140
+ export declare function swapBasketSpends(wallet: WalletInterface, tx: Transaction, holdings: (TokenInput | SatsInput)[], what: string): Promise<Spends>;
141
+ /**
142
+ * "Move back" (see the module comment; 0.29.3: a module plan). Throws before anything is
143
+ * broadcast (no outputs, a wallet or module refusal, an input that does not validate: the action
144
+ * is aborted). The module files the output in `p color <assetTag>` with its custody in the same
145
+ * createAction: no take-in afterwards, nothing kept in the browser. The page's own submit to this
146
+ * skein's token topic (the module submitted at the wallet's overlay too) is reported in `errors`.
147
+ */
148
+ export declare function moveBack(i: {
149
+ wallet: WalletInterface;
150
+ base: string;
151
+ tokenId: string;
152
+ meta?: {
153
+ sym?: string;
154
+ };
155
+ submit?: (topic: string, beef: number[]) => Promise<{
156
+ steak?: Steak;
157
+ }>;
158
+ }): Promise<MovedBack>;