@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.
- package/README.md +107 -2
- package/dist/client.d.ts +534 -0
- package/dist/client.js +1319 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +75 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +53 -0
- package/dist/lib/config.d.ts +42 -0
- package/dist/lib/config.js +64 -0
- package/dist/lib/keys.d.ts +42 -0
- package/dist/lib/keys.js +48 -0
- package/dist/lib/overlay.d.ts +190 -0
- package/dist/lib/overlay.js +302 -0
- package/dist/lib/skein.d.ts +82 -0
- package/dist/lib/skein.js +142 -0
- package/dist/lib/tokenId.d.ts +72 -0
- package/dist/lib/tokenId.js +121 -0
- package/dist/lp/amounts.d.ts +12 -0
- package/dist/lp/amounts.js +38 -0
- package/dist/lp/close.d.ts +90 -0
- package/dist/lp/close.js +140 -0
- package/dist/lp/images.d.ts +11 -0
- package/dist/lp/images.js +45 -0
- package/dist/lp/inventory.d.ts +68 -0
- package/dist/lp/inventory.js +84 -0
- package/dist/lp/poolDeploy.d.ts +220 -0
- package/dist/lp/poolDeploy.js +291 -0
- package/dist/lp/poolRows.d.ts +13 -0
- package/dist/lp/poolRows.js +19 -0
- package/dist/lp/positions.d.ts +81 -0
- package/dist/lp/positions.js +144 -0
- package/dist/lp/rebalance.d.ts +101 -0
- package/dist/lp/rebalance.js +168 -0
- package/dist/lp/wallet.d.ts +12 -0
- package/dist/lp/wallet.js +19 -0
- package/dist/market/activeSwap.d.ts +158 -0
- package/dist/market/activeSwap.js +320 -0
- package/dist/market/plan.d.ts +85 -0
- package/dist/market/plan.js +149 -0
- package/dist/market/prices.d.ts +13 -0
- package/dist/market/prices.js +14 -0
- package/dist/market/relay.d.ts +84 -0
- package/dist/market/relay.js +201 -0
- package/dist/market/swapAction.d.ts +306 -0
- package/dist/market/swapAction.js +508 -0
- package/dist/market/swapFlow.d.ts +58 -0
- package/dist/market/swapFlow.js +86 -0
- package/dist/market/view.d.ts +58 -0
- package/dist/market/view.js +63 -0
- package/dist/pool/artifact.d.ts +23 -0
- package/dist/pool/artifact.js +38 -0
- package/dist/pool/index.d.ts +8 -0
- package/dist/pool/index.js +8 -0
- package/dist/pool/oneColor.d.ts +19 -0
- package/dist/pool/oneColor.js +39 -0
- package/dist/pool/pool.artifact.json +5069 -0
- package/dist/pool/runar-ir-schema.d.ts +21 -0
- package/dist/pool/runar-ir-schema.js +18 -0
- package/dist/pool/template.d.ts +295 -0
- package/dist/pool/template.js +715 -0
- package/dist/vendor/runar/LICENSE +21 -0
- package/dist/vendor/runar/NOTICE +15 -0
- package/dist/vendor/runar/runar-ir-schema/src/abi-type-encoding.d.ts +42 -0
- package/dist/vendor/runar/runar-ir-schema/src/abi-type-encoding.js +53 -0
- package/dist/vendor/runar/runar-ir-schema/src/anf-ir.d.ts +221 -0
- package/dist/vendor/runar/runar-ir-schema/src/anf-ir.js +33 -0
- package/dist/vendor/runar/runar-ir-schema/src/artifact.d.ts +288 -0
- package/dist/vendor/runar/runar-ir-schema/src/artifact.js +8 -0
- package/dist/vendor/runar/runar-ir-schema/src/canonical-json.d.ts +43 -0
- package/dist/vendor/runar/runar-ir-schema/src/canonical-json.js +204 -0
- package/dist/vendor/runar/runar-ir-schema/src/input-limits.d.ts +89 -0
- package/dist/vendor/runar/runar-ir-schema/src/input-limits.js +100 -0
- package/dist/vendor/runar/runar-ir-schema/src/stack-ir.d.ts +118 -0
- package/dist/vendor/runar/runar-ir-schema/src/stack-ir.js +8 -0
- package/dist/vendor/runar/runar-ir-schema/src/state-layout.d.ts +56 -0
- package/dist/vendor/runar/runar-ir-schema/src/state-layout.js +145 -0
- package/dist/vendor/runar/runar-ir-schema/src/unknown-anf-kind-error.d.ts +18 -0
- package/dist/vendor/runar/runar-ir-schema/src/unknown-anf-kind-error.js +25 -0
- package/dist/vendor/runar/runar-sdk/src/anf-interpreter.d.ts +160 -0
- package/dist/vendor/runar/runar-sdk/src/anf-interpreter.js +1149 -0
- package/dist/vendor/runar/runar-sdk/src/calling.d.ts +98 -0
- package/dist/vendor/runar/runar-sdk/src/calling.js +209 -0
- package/dist/vendor/runar/runar-sdk/src/contract.d.ts +443 -0
- package/dist/vendor/runar/runar-sdk/src/contract.js +2516 -0
- package/dist/vendor/runar/runar-sdk/src/deployment.d.ts +36 -0
- package/dist/vendor/runar/runar-sdk/src/deployment.js +119 -0
- package/dist/vendor/runar/runar-sdk/src/errors.d.ts +46 -0
- package/dist/vendor/runar/runar-sdk/src/errors.js +59 -0
- package/dist/vendor/runar/runar-sdk/src/oppushtx.d.ts +23 -0
- package/dist/vendor/runar/runar-sdk/src/oppushtx.js +95 -0
- package/dist/vendor/runar/runar-sdk/src/ordinals/envelope.d.ts +31 -0
- package/dist/vendor/runar/runar-sdk/src/ordinals/envelope.js +272 -0
- package/dist/vendor/runar/runar-sdk/src/ordinals/types.d.ts +21 -0
- package/dist/vendor/runar/runar-sdk/src/ordinals/types.js +4 -0
- package/dist/vendor/runar/runar-sdk/src/providers/provider.d.ts +65 -0
- package/dist/vendor/runar/runar-sdk/src/providers/provider.js +59 -0
- package/dist/vendor/runar/runar-sdk/src/providers/wallet-provider.d.ts +94 -0
- package/dist/vendor/runar/runar-sdk/src/providers/wallet-provider.js +297 -0
- package/dist/vendor/runar/runar-sdk/src/script-utils.d.ts +54 -0
- package/dist/vendor/runar/runar-sdk/src/script-utils.js +252 -0
- package/dist/vendor/runar/runar-sdk/src/signers/signer.d.ts +17 -0
- package/dist/vendor/runar/runar-sdk/src/signers/signer.js +4 -0
- package/dist/vendor/runar/runar-sdk/src/spend-safety.d.ts +31 -0
- package/dist/vendor/runar/runar-sdk/src/spend-safety.js +35 -0
- package/dist/vendor/runar/runar-sdk/src/state.d.ts +54 -0
- package/dist/vendor/runar/runar-sdk/src/state.js +584 -0
- package/dist/vendor/runar/runar-sdk/src/types.d.ts +227 -0
- package/dist/vendor/runar/runar-sdk/src/types.js +4 -0
- package/dist/vendor/runar/runar-sdk/src/unsound-primitives.d.ts +30 -0
- package/dist/vendor/runar/runar-sdk/src/unsound-primitives.js +42 -0
- package/dist/vendor/runar/runar-sdk.d.ts +9 -0
- package/dist/vendor/runar/runar-sdk.js +8 -0
- package/dist/wallet/action.d.ts +28 -0
- package/dist/wallet/action.js +42 -0
- package/dist/wallet/brc29.d.ts +83 -0
- package/dist/wallet/brc29.js +80 -0
- package/dist/wallet/color.d.ts +110 -0
- package/dist/wallet/color.js +134 -0
- package/dist/wallet/swapPayouts.d.ts +35 -0
- package/dist/wallet/swapPayouts.js +146 -0
- package/package.json +39 -4
|
@@ -0,0 +1,320 @@
|
|
|
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, P2PKH, PublicKey, Transaction, Utils } from "@bsv/sdk";
|
|
49
|
+
import { decodeOneColor } from "../pool/index.js";
|
|
50
|
+
import { parseOutpoint, parseTokenId, sdkTokenId, tokenIdOfWire, tokenIdText } from "../lib/tokenId.js";
|
|
51
|
+
import { basketBeef, basketRows } from "../lp/wallet.js";
|
|
52
|
+
import { tokenP2pkhScript } from "../lp/poolDeploy.js";
|
|
53
|
+
import { steakText, submitToOverlay } from "../lp/close.js";
|
|
54
|
+
import { SWAP_BASKET, SWAP_PROTOCOL, newReceivingKey, swapInstructions, swapSatsInstructions, runTokenAction, transferPlanLabels } from "../wallet/color.js";
|
|
55
|
+
import { identityKeyOf } from "../wallet/brc29.js";
|
|
56
|
+
import { inputIndexOf, runAppSignedAction } from "../wallet/action.js";
|
|
57
|
+
import { P2PKH_UNLOCK_LENGTH, satsInputsOf, signP2pkhWithWallet, spendValid, tokenInputsOf } from "./swapAction.js";
|
|
58
|
+
/** The keyID prefix of an `amm-swap` key (`SWAP_PROTOCOL`, counterparty self). */
|
|
59
|
+
export const SWAP_KEY_PREFIX = "amm-swap-";
|
|
60
|
+
function describe(text) {
|
|
61
|
+
return text.length <= 50 ? text : `${text.slice(0, 49)}…`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The keyID of a staged `amm-swap` output (0.29.2, David Case 2026-11-02: derived, not random):
|
|
65
|
+
* `amm-swap-<txid>.<vout>` for a leg's token row and `amm-swap-<txid>.<vout>:sats` for its sats
|
|
66
|
+
* row, `<txid>.<vout>` the leg's pool outpoint as quoted (`leg.outpoint`, `_` written `.`).
|
|
67
|
+
* Protocol `[2, "amm swap"]`, counterparty self, as before. A leg refused and retried against the
|
|
68
|
+
* same pool outpoint stages to the same key again, which is fine.
|
|
69
|
+
*/
|
|
70
|
+
export function swapKeyID(poolOutpoint, kind) {
|
|
71
|
+
const op = parseOutpoint(poolOutpoint);
|
|
72
|
+
if (!op)
|
|
73
|
+
throw new Error(`not a pool outpoint: ${poolOutpoint}`);
|
|
74
|
+
return `${SWAP_KEY_PREFIX}${op.txid}.${op.vout}${kind === "sats" ? ":sats" : ""}`;
|
|
75
|
+
}
|
|
76
|
+
/** The `amm-swap` outputs of `tokenId` the legs can spend. */
|
|
77
|
+
export async function swapHoldings(wallet, tokenId) {
|
|
78
|
+
return tokenInputsOf(await basketRows(wallet, SWAP_BASKET), tokenId);
|
|
79
|
+
}
|
|
80
|
+
/** The `amm-swap` sats outputs the legs can spend (0.29.0). */
|
|
81
|
+
export async function swapSatsHoldings(wallet) {
|
|
82
|
+
return satsInputsOf(await basketRows(wallet, SWAP_BASKET));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* What `amm-swap` holds, per token id: the token amount and the number of token outputs, and
|
|
86
|
+
* (0.29.6) the sats staged for the token's legs (the sats rows whose customInstructions name its
|
|
87
|
+
* `assetId`). A token with only sats staged has amount 0n and outputs 0.
|
|
88
|
+
*/
|
|
89
|
+
export async function swapTotals(wallet) {
|
|
90
|
+
const totals = new Map();
|
|
91
|
+
const rows = await basketRows(wallet, SWAP_BASKET);
|
|
92
|
+
for (const r of rows) {
|
|
93
|
+
if (!r.lockingScript)
|
|
94
|
+
continue;
|
|
95
|
+
const t = decodeOneColor(Utils.toArray(r.lockingScript, "hex"));
|
|
96
|
+
if (!t || t.role !== "value" || !t.idBytes)
|
|
97
|
+
continue;
|
|
98
|
+
const id = tokenIdOfWire(t.idBytes);
|
|
99
|
+
const s = totals.get(id) ?? { amount: 0n, outputs: 0, sats: 0 };
|
|
100
|
+
s.amount += t.amount;
|
|
101
|
+
s.outputs++;
|
|
102
|
+
totals.set(id, s);
|
|
103
|
+
}
|
|
104
|
+
for (const r of satsInputsOf(rows)) {
|
|
105
|
+
const ref = r.assetId ? parseTokenId(r.assetId) : null;
|
|
106
|
+
if (!ref)
|
|
107
|
+
continue;
|
|
108
|
+
const id = tokenIdText(ref);
|
|
109
|
+
const s = totals.get(id) ?? { amount: 0n, outputs: 0, sats: 0 };
|
|
110
|
+
s.sats += r.satoshis;
|
|
111
|
+
totals.set(id, s);
|
|
112
|
+
}
|
|
113
|
+
return totals;
|
|
114
|
+
}
|
|
115
|
+
/** The source transactions of `amm-swap`'s outputs (a leg's BEEF), with `extra` merged in. */
|
|
116
|
+
export async function swapHoldingBeef(wallet, extra) {
|
|
117
|
+
const beef = (await basketBeef(wallet, SWAP_BASKET)) ?? new Beef();
|
|
118
|
+
if (extra)
|
|
119
|
+
beef.mergeBeef(Beef.fromBinary(extra));
|
|
120
|
+
return beef;
|
|
121
|
+
}
|
|
122
|
+
/** The "amm swap" keys (`swapKeyID`) for `sats` and their outputs, filed in `amm-swap` by the createAction itself. */
|
|
123
|
+
async function satsOutputs(wallet, sats, sym, assetId) {
|
|
124
|
+
return await Promise.all(sats.map(async ({ satoshis, pool }) => {
|
|
125
|
+
if (!Number.isSafeInteger(satoshis) || satoshis <= 0)
|
|
126
|
+
throw new Error("nothing to send to the swap basket");
|
|
127
|
+
const keyID = swapKeyID(pool, "sats");
|
|
128
|
+
const { publicKey } = await wallet.getPublicKey({ protocolID: SWAP_PROTOCOL, keyID, counterparty: "self", forSelf: true });
|
|
129
|
+
const lockingScript = new P2PKH().lock(PublicKey.fromString(publicKey).toAddress()).toHex();
|
|
130
|
+
return {
|
|
131
|
+
keyID,
|
|
132
|
+
assetId,
|
|
133
|
+
satoshis,
|
|
134
|
+
lockingScript,
|
|
135
|
+
output: {
|
|
136
|
+
lockingScript,
|
|
137
|
+
satoshis,
|
|
138
|
+
outputDescription: describe(`Sats for a ${sym} swap leg`),
|
|
139
|
+
basket: SWAP_BASKET,
|
|
140
|
+
customInstructions: swapSatsInstructions({ assetId, protocolID: SWAP_PROTOCOL, keyID, counterparty: "self" }),
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
/** The send's sats outputs from `first` on, as `SatsInput`s (an output the wallet moved is an error). */
|
|
146
|
+
function satsHeld(final, txid, keys, first, errors) {
|
|
147
|
+
const out = [];
|
|
148
|
+
keys.forEach((k, n) => {
|
|
149
|
+
const vout = first + n;
|
|
150
|
+
const o = final.outputs[vout];
|
|
151
|
+
if (!o || o.lockingScript.toHex() !== k.lockingScript || o.satoshis !== k.satoshis) {
|
|
152
|
+
errors.push(`the wallet's send has no output ${vout} to the swap key`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
out.push({ outpoint: `${txid}.${vout}`, txid, vout, satoshis: k.satoshis, lockingScript: k.lockingScript, protocolID: SWAP_PROTOCOL, keyID: k.keyID, counterparty: "self", assetId: k.assetId });
|
|
156
|
+
});
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* "In" (see the module comment): one module send of `stage.tokens` (each one
|
|
161
|
+
* output) to `amm-swap` keys derived from each leg's pool (`swapKeyID`), filed in `amm-swap`, and of `stage.sats`
|
|
162
|
+
* (each one output) after them. Throws when the wallet refuses or fails the
|
|
163
|
+
* send; filing and the page's own submit are reported in `errors`.
|
|
164
|
+
*/
|
|
165
|
+
export async function sendToSwapBasket(wallet, base, tokenId, stage, meta = {}, submit = (topic, beef) => submitToOverlay(base, topic, beef)) {
|
|
166
|
+
const amounts = stage.tokens;
|
|
167
|
+
if (amounts.length === 0 || amounts.some((a) => a.amount <= 0n))
|
|
168
|
+
throw new Error("nothing to send to the swap basket");
|
|
169
|
+
const assetId = sdkTokenId(tokenId);
|
|
170
|
+
const sym = meta.sym ?? "token";
|
|
171
|
+
const keys = await Promise.all(amounts.map(async ({ amount, pool }) => {
|
|
172
|
+
const keyID = swapKeyID(pool, "token");
|
|
173
|
+
const { publicKey } = await wallet.getPublicKey({ protocolID: SWAP_PROTOCOL, keyID, counterparty: "self", forSelf: true });
|
|
174
|
+
return { amount, keyID, publicKey, lockingScript: tokenP2pkhScript(assetId, amount, publicKey) };
|
|
175
|
+
}));
|
|
176
|
+
const satsKeys = await satsOutputs(wallet, stage.sats, sym, assetId);
|
|
177
|
+
const { txid, tx } = await runTokenAction(wallet, {
|
|
178
|
+
description: describe(`AMM swap: ${sym} into the swap basket`),
|
|
179
|
+
labels: [...transferPlanLabels(assetId), "amm-swap"],
|
|
180
|
+
outputs: [...keys.map((k) => ({ lockingScript: k.lockingScript, satoshis: 1, outputDescription: describe(`${sym} for a swap leg`) })), ...satsKeys.map((k) => k.output)],
|
|
181
|
+
options: { randomizeOutputs: false, acceptDelayedBroadcast: false },
|
|
182
|
+
});
|
|
183
|
+
const out = { txid, tx, holdings: [], sats: [], filed: false, submitted: "not submitted", errors: [] };
|
|
184
|
+
const final = Transaction.fromAtomicBEEF(tx);
|
|
185
|
+
keys.forEach((k, vout) => {
|
|
186
|
+
const o = final.outputs[vout];
|
|
187
|
+
if (!o || o.lockingScript.toHex() !== k.lockingScript) {
|
|
188
|
+
out.errors.push(`the wallet's send has no output ${vout} to the swap key`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
out.holdings.push({ outpoint: `${txid}.${vout}`, txid, vout, satoshis: o.satoshis, lockingScript: k.lockingScript, amount: k.amount, protocolID: SWAP_PROTOCOL, keyID: k.keyID, counterparty: "self" });
|
|
192
|
+
});
|
|
193
|
+
out.sats = satsHeld(final, txid, satsKeys, keys.length, out.errors);
|
|
194
|
+
try {
|
|
195
|
+
out.submitted = steakText(await submit(`tm_color_${assetId}`, tx));
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
out.submitted = "the submit failed";
|
|
199
|
+
out.errors.push(`submit: ${err instanceof Error ? err.message : String(err)}`);
|
|
200
|
+
}
|
|
201
|
+
const rows = out.holdings.map((h) => ({
|
|
202
|
+
outputIndex: h.vout,
|
|
203
|
+
protocol: "basket insertion",
|
|
204
|
+
insertionRemittance: { basket: SWAP_BASKET, customInstructions: swapInstructions({ assetId, amount: h.amount, protocolID: SWAP_PROTOCOL, keyID: h.keyID, counterparty: "self" }) },
|
|
205
|
+
}));
|
|
206
|
+
try {
|
|
207
|
+
const r = await wallet.internalizeAction({ tx, outputs: rows, description: describe(`AMM swap: ${sym} into the swap basket`), labels: ["amm-swap"] });
|
|
208
|
+
out.filed = r.accepted;
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
out.errors.push(`internalizeAction: ${err instanceof Error ? err.message : String(err)}`);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The sats-only send (0.29.0): one plain createAction (no `p color` labels: the module is not in
|
|
217
|
+
* the path) with one P2PKH output per amount to the leg's "amm swap" `:sats` key (`swapKeyID`), filed in `amm-swap`; the
|
|
218
|
+
* wallet funds it, adds change and broadcasts. Throws when the wallet refuses or fails it.
|
|
219
|
+
*/
|
|
220
|
+
export async function sendSatsToSwapBasket(wallet, tokenId, sats, meta = {}) {
|
|
221
|
+
if (sats.length === 0)
|
|
222
|
+
throw new Error("nothing to send to the swap basket");
|
|
223
|
+
const sym = meta.sym ?? "token";
|
|
224
|
+
const keys = await satsOutputs(wallet, sats, sym, sdkTokenId(tokenId));
|
|
225
|
+
const { txid, tx } = await runTokenAction(wallet, {
|
|
226
|
+
description: describe(`AMM swap: sats into the swap basket`),
|
|
227
|
+
labels: ["amm-swap"],
|
|
228
|
+
outputs: keys.map((k) => k.output),
|
|
229
|
+
options: { randomizeOutputs: false, acceptDelayedBroadcast: false },
|
|
230
|
+
});
|
|
231
|
+
const errors = [];
|
|
232
|
+
return { txid, tx, sats: satsHeld(Transaction.fromAtomicBEEF(tx), txid, keys, 0, errors), errors };
|
|
233
|
+
}
|
|
234
|
+
/** The page's unlocking scripts for the `amm-swap` outputs `holdings` in the wallet's transaction `tx`, each checked with `Spend`. */
|
|
235
|
+
export async function swapBasketSpends(wallet, tx, holdings, what) {
|
|
236
|
+
const spends = {};
|
|
237
|
+
for (const h of holdings) {
|
|
238
|
+
const n = inputIndexOf(tx, h.txid, h.vout);
|
|
239
|
+
if (n < 0)
|
|
240
|
+
throw new Error(`the wallet's ${what} does not spend the swap basket output ${h.outpoint}`);
|
|
241
|
+
tx.inputs[n].unlockingScript = await signP2pkhWithWallet(wallet, tx, n, h);
|
|
242
|
+
if (!spendValid(tx, n))
|
|
243
|
+
throw new Error(`input ${n} of the ${what} does not validate`);
|
|
244
|
+
spends[n] = { unlockingScript: tx.inputs[n].unlockingScript.toHex() };
|
|
245
|
+
}
|
|
246
|
+
return spends;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* "Move back" (see the module comment; 0.29.3: a module plan). Throws before anything is
|
|
250
|
+
* broadcast (no outputs, a wallet or module refusal, an input that does not validate: the action
|
|
251
|
+
* is aborted). The module files the output in `p color <assetTag>` with its custody in the same
|
|
252
|
+
* createAction: no take-in afterwards, nothing kept in the browser. The page's own submit to this
|
|
253
|
+
* skein's token topic (the module submitted at the wallet's overlay too) is reported in `errors`.
|
|
254
|
+
*/
|
|
255
|
+
export async function moveBack(i) {
|
|
256
|
+
const { wallet } = i;
|
|
257
|
+
const assetId = sdkTokenId(i.tokenId);
|
|
258
|
+
const sym = i.meta?.sym ?? "token";
|
|
259
|
+
const topic = `tm_color_${assetId}`;
|
|
260
|
+
const rows = await basketRows(wallet, SWAP_BASKET);
|
|
261
|
+
const holdings = tokenInputsOf(rows, assetId);
|
|
262
|
+
// 0.29.1 (David): the token's sats rows (staged for its legs) go in too; the wallet's change takes their sats.
|
|
263
|
+
const satsRows = satsInputsOf(rows).filter((r) => r.assetId === assetId);
|
|
264
|
+
if (holdings.length === 0 && satsRows.length > 0)
|
|
265
|
+
return await moveBackSats(wallet, sym, satsRows);
|
|
266
|
+
if (holdings.length === 0)
|
|
267
|
+
throw new Error(`the swap basket holds no ${sym}`);
|
|
268
|
+
const amount = holdings.reduce((a, h) => a + h.amount, 0n);
|
|
269
|
+
const sources = await swapHoldingBeef(wallet);
|
|
270
|
+
for (const h of [...holdings, ...satsRows])
|
|
271
|
+
if (!sources.findTxid(h.txid))
|
|
272
|
+
throw new Error(`the wallet's BEEF lacks the swap basket output ${h.outpoint}`);
|
|
273
|
+
// The output: the whole amount to a module receiving key of the user's own, the plan's owned output 0.
|
|
274
|
+
const me = await identityKeyOf(wallet);
|
|
275
|
+
const key = await newReceivingKey(wallet, assetId, me);
|
|
276
|
+
const lockingScript = tokenP2pkhScript(assetId, amount, key.publicKey);
|
|
277
|
+
const { txid, tx: beef } = await runAppSignedAction(wallet, {
|
|
278
|
+
description: describe(`AMM move back: ${sym}`),
|
|
279
|
+
inputBEEF: sources.toBinary(),
|
|
280
|
+
inputs: [
|
|
281
|
+
...holdings.map((h) => ({ outpoint: h.outpoint, unlockingScriptLength: P2PKH_UNLOCK_LENGTH, inputDescription: describe(`${sym} in the swap basket`) })),
|
|
282
|
+
...satsRows.map((h) => ({ outpoint: h.outpoint, unlockingScriptLength: P2PKH_UNLOCK_LENGTH, inputDescription: describe(`Sats staged for a ${sym} swap`) })),
|
|
283
|
+
],
|
|
284
|
+
outputs: [{ lockingScript, satoshis: 1, outputDescription: describe(`${sym} back to your wallet`) }],
|
|
285
|
+
labels: [...transferPlanLabels(assetId, { ownedOutputs: [{ outputIndex: 0, ...key.receipt }] }), "amm-swap"],
|
|
286
|
+
options: { randomizeOutputs: false, acceptDelayedBroadcast: false },
|
|
287
|
+
}, (tx) => swapBasketSpends(wallet, tx, [...holdings, ...satsRows], "move back"));
|
|
288
|
+
const out = { txid, amount, beef, submitted: "not submitted", errors: [] };
|
|
289
|
+
try {
|
|
290
|
+
out.submitted = steakText(await (i.submit ?? ((t, b) => submitToOverlay(i.base, t, b)))(topic, beef));
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
out.submitted = "the submit failed";
|
|
294
|
+
out.errors.push(`submit: ${err instanceof Error ? err.message : String(err)}`);
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Move back with only sats staged for the token (0.29.6, David Case 2026-11-03: a sats-in leg that
|
|
300
|
+
* failed leaves sats and no token): ONE plain wallet createAction (no `p color` labels, the module
|
|
301
|
+
* is not in the path) with the token's `amm-swap` sats rows as explicit inputs (their sources in
|
|
302
|
+
* `inputBEEF`) and NO outputs: the wallet keeps everything as its change and pays the fee from it.
|
|
303
|
+
* The page unlocks the rows at signAction; the wallet broadcasts and marks the rows spent itself
|
|
304
|
+
* (no relinquish, no take-in, nothing kept). Nothing is submitted to an overlay (no token moves).
|
|
305
|
+
*/
|
|
306
|
+
async function moveBackSats(wallet, sym, satsRows) {
|
|
307
|
+
const sources = await swapHoldingBeef(wallet);
|
|
308
|
+
for (const h of satsRows)
|
|
309
|
+
if (!sources.findTxid(h.txid))
|
|
310
|
+
throw new Error(`the wallet's BEEF lacks the swap basket output ${h.outpoint}`);
|
|
311
|
+
const { txid, tx: beef } = await runAppSignedAction(wallet, {
|
|
312
|
+
description: describe(`AMM move back: ${sym} sats`),
|
|
313
|
+
inputBEEF: sources.toBinary(),
|
|
314
|
+
inputs: satsRows.map((h) => ({ outpoint: h.outpoint, unlockingScriptLength: P2PKH_UNLOCK_LENGTH, inputDescription: describe(`Sats staged for a ${sym} swap`) })),
|
|
315
|
+
outputs: [],
|
|
316
|
+
labels: ["amm-swap"],
|
|
317
|
+
options: { randomizeOutputs: false, acceptDelayedBroadcast: false },
|
|
318
|
+
}, (tx) => swapBasketSpends(wallet, tx, satsRows, "move back"));
|
|
319
|
+
return { txid, amount: 0n, beef, submitted: "not submitted (no token moved)", errors: [] };
|
|
320
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Direction, Plan, PlanRequest, PoolState } from "@dcasegr/amm-engine";
|
|
2
|
+
import type { LiveAnswer } from "../lib/overlay.js";
|
|
3
|
+
import { type ValidatorStatus } from "./view.js";
|
|
4
|
+
/**
|
|
5
|
+
* Estimated miner fee per leg, in sats, for the engine's per-leg cost
|
|
6
|
+
* heuristic: a swap is ~13 KB (docs/notes.md, "Publishing is part of
|
|
7
|
+
* submit") at the client's fee rate (`createClient`'s `feeRateSatsPerKb`; the pages':
|
|
8
|
+
* `VITE_FEE_RATE`). The exact fee of a built swap is `swapFunding` (src/market/swapAction.ts).
|
|
9
|
+
*/
|
|
10
|
+
export declare function legMinerFeeSats(satsPerKb: number): bigint;
|
|
11
|
+
/** `legMinerFeeSats` at the default rate (100 sats/kB). */
|
|
12
|
+
export declare const LEG_MINER_FEE_SATS: bigint;
|
|
13
|
+
export interface SwapForm {
|
|
14
|
+
direction: Direction;
|
|
15
|
+
/** Sats (bsvToToken) or tokens in display units (tokenToBsv). */
|
|
16
|
+
amount: string;
|
|
17
|
+
slippageBps: string;
|
|
18
|
+
allowPartial: boolean;
|
|
19
|
+
}
|
|
20
|
+
export type PlanInput = {
|
|
21
|
+
ok: true;
|
|
22
|
+
request: PlanRequest;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The pools the planner may use (0.4.0, shruggr/skein#120): those whose validator is in the token's
|
|
29
|
+
* liveness (0.34.0: the pools route's rows, src/lib/overlay.ts `liveOfPools`: the host listed only the pools whose validator beats; before, the `.live` read), which holds only the beats within the
|
|
30
|
+
* instance's own window. No read, none.
|
|
31
|
+
*/
|
|
32
|
+
export declare function livePools(pools: PoolState[], live: LiveAnswer | null): PoolState[];
|
|
33
|
+
/**
|
|
34
|
+
* Parses the form against one token's pools; only the pools whose validator is live (`livePools`)
|
|
35
|
+
* are planned. `dec` scales a token amount (base units when unknown).
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildPlanRequest(tokenId: string, form: SwapForm, pools: PoolState[], live: LiveAnswer | null, dec: number | undefined,
|
|
38
|
+
/** The miner fee rate the per-leg cost is estimated at, sats per 1000 bytes (default 100). */
|
|
39
|
+
satsPerKb?: number): PlanInput;
|
|
40
|
+
export interface LegView {
|
|
41
|
+
outpoint: string;
|
|
42
|
+
pool: PoolState;
|
|
43
|
+
amountIn: bigint;
|
|
44
|
+
amountOut: bigint;
|
|
45
|
+
lpFee: bigint;
|
|
46
|
+
validatorFee: bigint;
|
|
47
|
+
/** The pool's commission (CommissionBps of amountIn), in the input asset; 0 for a pool with none. */
|
|
48
|
+
commission: bigint;
|
|
49
|
+
validator: ValidatorStatus;
|
|
50
|
+
}
|
|
51
|
+
export interface PlanView {
|
|
52
|
+
direction: Direction;
|
|
53
|
+
legs: LegView[];
|
|
54
|
+
totalIn: bigint;
|
|
55
|
+
totalOut: bigint;
|
|
56
|
+
/** In the input asset (LP and validator fees and the commission are taken from amountIn). */
|
|
57
|
+
lpFees: bigint;
|
|
58
|
+
validatorFees: bigint;
|
|
59
|
+
commissions: bigint;
|
|
60
|
+
/** Sats per display token paid (bsvToToken) or received (tokenToBsv). */
|
|
61
|
+
effectivePrice: string;
|
|
62
|
+
/** The best pool's marginal price, same unit. */
|
|
63
|
+
midPrice: string;
|
|
64
|
+
/** What the order would get at the mid price, minus what the plan gets, in bps of the former (fees included). */
|
|
65
|
+
slippageVsMidBps: bigint;
|
|
66
|
+
/** The engine's absolute bound (from toleranceBps against its quoted mid). */
|
|
67
|
+
minAmountOut: bigint;
|
|
68
|
+
meetsSlippageBound: boolean;
|
|
69
|
+
filledCompletely: boolean;
|
|
70
|
+
/** amountIn the plan leaves unfilled. */
|
|
71
|
+
unfilled: bigint;
|
|
72
|
+
}
|
|
73
|
+
export declare function planView(p: Plan, inventory: PoolState[], live: LiveAnswer | null, dec: number | undefined): PlanView;
|
|
74
|
+
/** Plans with the engine and shapes the result. */
|
|
75
|
+
export declare function quote(request: PlanRequest, live: LiveAnswer | null, dec: number | undefined): {
|
|
76
|
+
plan: Plan;
|
|
77
|
+
view: PlanView;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Races (docs/notes.md "Market UI and matching engine"): the plan's pools
|
|
81
|
+
* that are no longer in a fresh lookup answer (spent by someone else's swap
|
|
82
|
+
* since the plan was made). A pool's outpoint changes on every spend, so
|
|
83
|
+
* "gone" is exactly "not listed".
|
|
84
|
+
*/
|
|
85
|
+
export declare function goneFromLookup(planned: Pick<Plan, "legs">, fresh: PoolState[]): string[];
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The swap form → the engine's `PlanRequest`, and the engine's `Plan` → what
|
|
3
|
+
* the page shows (pure). The planning itself is the engine's (`plan`, partial
|
|
4
|
+
* fills across several pools when allowed); nothing is re-derived here except
|
|
5
|
+
* display numbers: per-leg fees (LP, validator, commission), effective price
|
|
6
|
+
* and slippage against the mid price.
|
|
7
|
+
*/
|
|
8
|
+
import { plan as enginePlan } from "@dcasegr/amm-engine";
|
|
9
|
+
import { DEFAULT_FEE_RATE_SATS_PER_KB } from "../lib/config.js";
|
|
10
|
+
import { parseAmount } from "../lp/amounts.js";
|
|
11
|
+
import { priceOf, validatorStatus, withLastSeen } from "./view.js";
|
|
12
|
+
/**
|
|
13
|
+
* Estimated miner fee per leg, in sats, for the engine's per-leg cost
|
|
14
|
+
* heuristic: a swap is ~13 KB (docs/notes.md, "Publishing is part of
|
|
15
|
+
* submit") at the client's fee rate (`createClient`'s `feeRateSatsPerKb`; the pages':
|
|
16
|
+
* `VITE_FEE_RATE`). The exact fee of a built swap is `swapFunding` (src/market/swapAction.ts).
|
|
17
|
+
*/
|
|
18
|
+
export function legMinerFeeSats(satsPerKb) {
|
|
19
|
+
return BigInt(Math.ceil(13 * satsPerKb));
|
|
20
|
+
}
|
|
21
|
+
/** `legMinerFeeSats` at the default rate (100 sats/kB). */
|
|
22
|
+
export const LEG_MINER_FEE_SATS = legMinerFeeSats(DEFAULT_FEE_RATE_SATS_PER_KB);
|
|
23
|
+
/**
|
|
24
|
+
* The pools the planner may use (0.4.0, shruggr/skein#120): those whose validator is in the token's
|
|
25
|
+
* liveness (0.34.0: the pools route's rows, src/lib/overlay.ts `liveOfPools`: the host listed only the pools whose validator beats; before, the `.live` read), which holds only the beats within the
|
|
26
|
+
* instance's own window. No read, none.
|
|
27
|
+
*/
|
|
28
|
+
export function livePools(pools, live) {
|
|
29
|
+
if (!live)
|
|
30
|
+
return [];
|
|
31
|
+
return pools.filter((p) => validatorStatus(p.validatorIdentityKey, live).live);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parses the form against one token's pools; only the pools whose validator is live (`livePools`)
|
|
35
|
+
* are planned. `dec` scales a token amount (base units when unknown).
|
|
36
|
+
*/
|
|
37
|
+
export function buildPlanRequest(tokenId, form, pools, live, dec,
|
|
38
|
+
/** The miner fee rate the per-leg cost is estimated at, sats per 1000 bytes (default 100). */
|
|
39
|
+
satsPerKb = DEFAULT_FEE_RATE_SATS_PER_KB) {
|
|
40
|
+
if (!form.amount.trim())
|
|
41
|
+
return { ok: false, error: "enter an amount" };
|
|
42
|
+
let amountIn;
|
|
43
|
+
try {
|
|
44
|
+
amountIn = parseAmount(form.amount, form.direction === "bsvToToken" ? 0 : (dec ?? 0));
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
48
|
+
}
|
|
49
|
+
if (amountIn <= 0n)
|
|
50
|
+
return { ok: false, error: "the amount must be positive" };
|
|
51
|
+
let toleranceBps;
|
|
52
|
+
try {
|
|
53
|
+
toleranceBps = BigInt(form.slippageBps.trim() || "0");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return { ok: false, error: "slippage: whole basis points" };
|
|
57
|
+
}
|
|
58
|
+
if (toleranceBps < 0n || toleranceBps > 10000n)
|
|
59
|
+
return { ok: false, error: "slippage: 0-10000 bps" };
|
|
60
|
+
if (pools.length === 0)
|
|
61
|
+
return { ok: false, error: "no pools for this token" };
|
|
62
|
+
const usable = livePools(pools, live);
|
|
63
|
+
if (usable.length === 0) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: !live
|
|
67
|
+
? "no liveness read for this token: no pool can be planned"
|
|
68
|
+
: !live.kept
|
|
69
|
+
? "this instance keeps no liveness for this token (its market role is off for it): no validator is known live"
|
|
70
|
+
: "no pool's validator is live on this token",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const fixedCost = { minerFeeSats: legMinerFeeSats(satsPerKb) };
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
request: {
|
|
77
|
+
tokenId,
|
|
78
|
+
direction: form.direction,
|
|
79
|
+
amountIn,
|
|
80
|
+
slippage: { toleranceBps },
|
|
81
|
+
allowPartial: form.allowPartial,
|
|
82
|
+
fixedCost,
|
|
83
|
+
inventory: withLastSeen(usable, live),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function planView(p, inventory, live, dec) {
|
|
88
|
+
const byOutpoint = new Map(inventory.map((x) => [x.outpoint, x]));
|
|
89
|
+
const legs = p.legs.map((l) => {
|
|
90
|
+
const pool = byOutpoint.get(l.outpoint);
|
|
91
|
+
return {
|
|
92
|
+
outpoint: l.outpoint,
|
|
93
|
+
pool,
|
|
94
|
+
amountIn: l.amountIn,
|
|
95
|
+
amountOut: l.amountOut,
|
|
96
|
+
lpFee: l.liquidityFee,
|
|
97
|
+
validatorFee: l.validationFee,
|
|
98
|
+
commission: l.commission,
|
|
99
|
+
validator: validatorStatus(pool.validatorIdentityKey, live),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
const sum = (f) => legs.reduce((a, l) => a + f(l), 0n);
|
|
103
|
+
const totalIn = sum((l) => l.amountIn);
|
|
104
|
+
const totalOut = sum((l) => l.amountOut);
|
|
105
|
+
const bsvToToken = p.direction === "bsvToToken";
|
|
106
|
+
// Mid: the best pool's marginal price (most output per unit in), pre-fee.
|
|
107
|
+
let best;
|
|
108
|
+
for (const x of inventory) {
|
|
109
|
+
if (!best)
|
|
110
|
+
best = x;
|
|
111
|
+
else if (bsvToToken ? x.tokenReserve * best.bsvReserve > best.tokenReserve * x.bsvReserve : x.bsvReserve * best.tokenReserve > best.bsvReserve * x.tokenReserve)
|
|
112
|
+
best = x;
|
|
113
|
+
}
|
|
114
|
+
let midOut = 0n;
|
|
115
|
+
if (best && totalIn > 0n)
|
|
116
|
+
midOut = bsvToToken ? (totalIn * best.tokenReserve) / best.bsvReserve : (totalIn * best.bsvReserve) / best.tokenReserve;
|
|
117
|
+
const slippageVsMidBps = midOut > 0n ? ((midOut - totalOut) * 10000n) / midOut : 0n;
|
|
118
|
+
return {
|
|
119
|
+
direction: p.direction,
|
|
120
|
+
legs,
|
|
121
|
+
totalIn,
|
|
122
|
+
totalOut,
|
|
123
|
+
lpFees: sum((l) => l.lpFee),
|
|
124
|
+
validatorFees: sum((l) => l.validatorFee),
|
|
125
|
+
commissions: sum((l) => l.commission),
|
|
126
|
+
effectivePrice: bsvToToken ? priceOf(totalIn, totalOut, dec) : priceOf(totalOut, totalIn, dec),
|
|
127
|
+
midPrice: best ? priceOf(best.bsvReserve, best.tokenReserve, dec) : "—",
|
|
128
|
+
slippageVsMidBps,
|
|
129
|
+
minAmountOut: p.resolvedMinAmountOut,
|
|
130
|
+
meetsSlippageBound: p.meetsSlippageBound,
|
|
131
|
+
filledCompletely: p.filledCompletely,
|
|
132
|
+
unfilled: p.originalAmountIn - totalIn,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/** Plans with the engine and shapes the result. */
|
|
136
|
+
export function quote(request, live, dec) {
|
|
137
|
+
const p = enginePlan(request);
|
|
138
|
+
return { plan: p, view: planView(p, request.inventory, live, dec) };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Races (docs/notes.md "Market UI and matching engine"): the plan's pools
|
|
142
|
+
* that are no longer in a fresh lookup answer (spent by someone else's swap
|
|
143
|
+
* since the plan was made). A pool's outpoint changes on every spend, so
|
|
144
|
+
* "gone" is exactly "not listed".
|
|
145
|
+
*/
|
|
146
|
+
export function goneFromLookup(planned, fresh) {
|
|
147
|
+
const live = new Set(fresh.map((p) => p.outpoint));
|
|
148
|
+
return planned.legs.map((l) => l.outpoint).filter((op) => !live.has(op));
|
|
149
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prices (0.34.0, D6, David Case 2026-11-04: one source for prices): a token's price comes from
|
|
3
|
+
* its pool list — the pools route the host answers (`GET <origin>/amm/pools`, held as an event
|
|
4
|
+
* stream on every page) — Σsats / Σtokens over the pools listed there (the host lists only pools
|
|
5
|
+
* whose validator is beating). No price is derived from beats (the 0.9.0 beat prices are gone);
|
|
6
|
+
* the depth is the sats a buy needs to move the price up by 2%, x·(√1.02 − 1), over the summed
|
|
7
|
+
* sats.
|
|
8
|
+
*/
|
|
9
|
+
/** The sats a buy needs to move the marginal price up by `bps`, over `sats` summed reserves; null with none. */
|
|
10
|
+
export declare function depthOf(sats: bigint, bps?: number): {
|
|
11
|
+
bps: number;
|
|
12
|
+
sats: bigint;
|
|
13
|
+
} | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prices (0.34.0, D6, David Case 2026-11-04: one source for prices): a token's price comes from
|
|
3
|
+
* its pool list — the pools route the host answers (`GET <origin>/amm/pools`, held as an event
|
|
4
|
+
* stream on every page) — Σsats / Σtokens over the pools listed there (the host lists only pools
|
|
5
|
+
* whose validator is beating). No price is derived from beats (the 0.9.0 beat prices are gone);
|
|
6
|
+
* the depth is the sats a buy needs to move the price up by 2%, x·(√1.02 − 1), over the summed
|
|
7
|
+
* sats.
|
|
8
|
+
*/
|
|
9
|
+
/** The sats a buy needs to move the marginal price up by `bps`, over `sats` summed reserves; null with none. */
|
|
10
|
+
export function depthOf(sats, bps = 200) {
|
|
11
|
+
if (sats === 0n)
|
|
12
|
+
return null;
|
|
13
|
+
return { bps, sats: BigInt(Math.round(Number(sats) * (Math.sqrt(1 + bps / 10_000) - 1))) };
|
|
14
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
export declare const SWAP_TERMS_FN = "amm.swap.terms";
|
|
5
|
+
/** 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
|
+
export interface SwapTerms {
|
|
7
|
+
commissionPkh: string | null;
|
|
8
|
+
minCommissionBps: number;
|
|
9
|
+
}
|
|
10
|
+
/** What the relay needs of `AuthFetch` (and what the tests fake). */
|
|
11
|
+
export interface AuthFetchLike {
|
|
12
|
+
fetch(url: string, config?: {
|
|
13
|
+
method?: string;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
body?: string;
|
|
16
|
+
}): Promise<Pick<Response, "status" | "text">>;
|
|
17
|
+
}
|
|
18
|
+
export interface SwapSubmit {
|
|
19
|
+
/** The funding transaction as AtomicBEEF (the swap's unproven parent, with its ancestry). */
|
|
20
|
+
funding: number[];
|
|
21
|
+
/** Raw swap transaction, the validator's slot empty. */
|
|
22
|
+
swap: number[];
|
|
23
|
+
/** The pool outpoint the swap spends, `<txid>_<vout>`. */
|
|
24
|
+
pool: string;
|
|
25
|
+
/** The pool's validator identity key (hex; sent as 33 bytes). */
|
|
26
|
+
validator: string;
|
|
27
|
+
/** The validator's libp2p peer ID (text), as the liveness read gave it: what the relay dials. */
|
|
28
|
+
peerId: string;
|
|
29
|
+
/** Unix ms after which the relay gives up. */
|
|
30
|
+
expires: number;
|
|
31
|
+
}
|
|
32
|
+
export type SwapRecord = {
|
|
33
|
+
id: string;
|
|
34
|
+
status: "pending";
|
|
35
|
+
} | {
|
|
36
|
+
id: string;
|
|
37
|
+
status: "accepted";
|
|
38
|
+
tx: number[];
|
|
39
|
+
txid: string;
|
|
40
|
+
} | {
|
|
41
|
+
id: string;
|
|
42
|
+
status: "refused";
|
|
43
|
+
reason: string;
|
|
44
|
+
pool?: PoolState;
|
|
45
|
+
} | {
|
|
46
|
+
id: string;
|
|
47
|
+
status: "timeout";
|
|
48
|
+
} | {
|
|
49
|
+
id: string;
|
|
50
|
+
status: "failed";
|
|
51
|
+
reason: string;
|
|
52
|
+
};
|
|
53
|
+
export declare class RelayError extends Error {
|
|
54
|
+
readonly code: string;
|
|
55
|
+
readonly httpStatus: number;
|
|
56
|
+
constructor(code: string, message: string, httpStatus: number);
|
|
57
|
+
}
|
|
58
|
+
export declare function dagBytes(bytes: number[]): {
|
|
59
|
+
"/": {
|
|
60
|
+
bytes: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/** DAG-JSON bytes, hex, or a byte array → bytes; null otherwise. */
|
|
64
|
+
export declare function readBytes(x: unknown): number[] | null;
|
|
65
|
+
export declare function callUrl(base: string): string;
|
|
66
|
+
export declare function submitBody(s: SwapSubmit): {
|
|
67
|
+
fn: string;
|
|
68
|
+
args: Record<string, unknown>;
|
|
69
|
+
};
|
|
70
|
+
/** One `{fn, args}` call on the app's `/call` route. Throws `RelayError` on an error answer. */
|
|
71
|
+
export declare function callApp(af: AuthFetchLike, base: string, fn: string, args: unknown): Promise<unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* A refusal's pool, as the validator writes it (messages.zig poolValue:
|
|
74
|
+
* `lpFeeBps`, `validatorFeeBps`, `validatorIdentity`) or as the lookup does
|
|
75
|
+
* (the engine's names), into the engine's PoolState. A closed pool, or one
|
|
76
|
+
* missing a field, is undefined: nothing left to re-price.
|
|
77
|
+
*/
|
|
78
|
+
export declare function toPoolState(raw: unknown): PoolState | undefined;
|
|
79
|
+
export declare function parseSwapRecord(x: unknown): SwapRecord;
|
|
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
|
+
export declare function parseSwapTerms(x: unknown): SwapTerms;
|
|
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>;
|