@dcasegr/amm-client 0.38.2 → 0.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -24
- package/dist/client.d.ts +206 -62
- package/dist/client.js +388 -333
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +6 -3
- package/dist/index.d.ts +16 -4
- package/dist/index.js +16 -4
- package/dist/lib/overlay.d.ts +226 -19
- package/dist/lib/overlay.js +278 -49
- package/dist/lib/skein.d.ts +2 -0
- package/dist/lib/skein.js +1 -1
- package/dist/market/activeSwap.d.ts +77 -96
- package/dist/market/activeSwap.js +146 -166
- package/dist/market/plan.d.ts +2 -1
- package/dist/market/plan.js +1 -1
- package/dist/market/prices.d.ts +4 -4
- package/dist/market/prices.js +4 -4
- package/dist/market/swapAction.d.ts +150 -145
- package/dist/market/swapAction.js +292 -213
- package/dist/market/swapFlow.d.ts +3 -6
- package/dist/market/swapFlow.js +4 -22
- package/dist/pool/pool.artifact.json +760 -528
- package/dist/pool/template.d.ts +10 -2
- package/dist/pool/template.js +16 -6
- package/dist/wallet/color.d.ts +13 -9
- package/dist/wallet/color.js +13 -10
- package/dist/wallet/swapPayouts.d.ts +45 -28
- package/dist/wallet/swapPayouts.js +129 -117
- package/package.json +2 -2
package/dist/pool/template.d.ts
CHANGED
|
@@ -89,6 +89,13 @@ export interface SwapParams extends CallCommon {
|
|
|
89
89
|
* only when the commission (ceil(amountIn·CommissionBps/10000)) is nonzero.
|
|
90
90
|
*/
|
|
91
91
|
commissionPkh: string;
|
|
92
|
+
/**
|
|
93
|
+
* 0.40.0: the taker's token change (Swap's `tokenChange`, ≥ 0): one more 1Color output of this
|
|
94
|
+
* many tokens to `tokenChangePkh` (1 sat) after the commission, only when nonzero. Default 0.
|
|
95
|
+
*/
|
|
96
|
+
tokenChange?: bigint;
|
|
97
|
+
/** Hash160 (hex) of the token change's key (Swap's `tokenChangePkh`): always pushed. Default 20 zero bytes. */
|
|
98
|
+
tokenChangePkh?: string;
|
|
92
99
|
/** What the caller priced the leg against; any mismatch is refused (see PoolBuildError). */
|
|
93
100
|
expect?: SwapExpectation;
|
|
94
101
|
}
|
|
@@ -214,13 +221,14 @@ export declare class PoolTemplate {
|
|
|
214
221
|
/**
|
|
215
222
|
* Swap (method 0). Outputs: 0 pool, 1 the taker's payout, then the LP fee,
|
|
216
223
|
* the validator fee and the commission (each only when nonzero, each in
|
|
217
|
-
* the input asset), then change. Token payouts and token fees are 1-sat
|
|
224
|
+
* the input asset), then (0.40.0) the token change when nonzero, then change. Token payouts and token fees are 1-sat
|
|
218
225
|
* 1Color outputs; BSV ones are P2PKH. The validator's slot is left empty.
|
|
219
226
|
*/
|
|
220
227
|
static swap(p: SwapParams): Promise<PoolCall>;
|
|
221
228
|
/**
|
|
222
229
|
* The Swap call without its funding: the outputs the contract requires (in
|
|
223
|
-
* order: pool, payout, LP fee, validator fee, commission; no
|
|
230
|
+
* order: pool, payout, LP fee, validator fee, commission, 0.40.0 the token change; no sats
|
|
231
|
+
* change) and
|
|
224
232
|
* the method arguments, with the validator's slot empty. For a builder that
|
|
225
233
|
* does not pick the funding itself (a BRC-100 wallet's `createAction` picks
|
|
226
234
|
* inputs and change): hand these outputs over, then fill input 0 with
|
package/dist/pool/template.js
CHANGED
|
@@ -64,6 +64,7 @@ const toHex = (b) => Utils.toHex(Array.from(b));
|
|
|
64
64
|
const fromHex = (h) => Utils.toArray(h, "hex");
|
|
65
65
|
const scriptHex = (s) => (typeof s === "string" ? s.toLowerCase() : s.toHex());
|
|
66
66
|
const p2pkhHex = (pkh) => `76a914${pkh}88ac`;
|
|
67
|
+
const ZERO_PKH = "00".repeat(20);
|
|
67
68
|
const pkhOf = (pubKeyHex) => toHex(Hash.hash160(fromHex(pubKeyHex)));
|
|
68
69
|
function tokenPrefixHex(assetId, amount) {
|
|
69
70
|
return toHex(oneColorValuePrefix(fromHex(assetId), amount));
|
|
@@ -256,14 +257,14 @@ export class PoolTemplate {
|
|
|
256
257
|
/**
|
|
257
258
|
* Swap (method 0). Outputs: 0 pool, 1 the taker's payout, then the LP fee,
|
|
258
259
|
* the validator fee and the commission (each only when nonzero, each in
|
|
259
|
-
* the input asset), then change. Token payouts and token fees are 1-sat
|
|
260
|
+
* the input asset), then (0.40.0) the token change when nonzero, then change. Token payouts and token fees are 1-sat
|
|
260
261
|
* 1Color outputs; BSV ones are P2PKH. The validator's slot is left empty.
|
|
261
262
|
*/
|
|
262
263
|
static async swap(p) {
|
|
263
264
|
const plan = PoolTemplate.planSwap(p);
|
|
264
|
-
// Tokens in: the token inputs must carry exactly amountIn
|
|
265
|
-
//
|
|
266
|
-
checkTokenInputs(p.inputs, plan.pool.args.assetId, p.bsvIn ? 0n : p.amountIn);
|
|
265
|
+
// Tokens in: the token inputs must carry exactly amountIn plus the token change (0.40.0). BSV
|
|
266
|
+
// in: no token of this asset may be spent beyond the token change (it would burn).
|
|
267
|
+
checkTokenInputs(p.inputs, plan.pool.args.assetId, (p.bsvIn ? 0n : p.amountIn) + (p.tokenChange ?? 0n));
|
|
267
268
|
const call = await buildCall({
|
|
268
269
|
common: p,
|
|
269
270
|
pool: plan.pool,
|
|
@@ -279,7 +280,8 @@ export class PoolTemplate {
|
|
|
279
280
|
}
|
|
280
281
|
/**
|
|
281
282
|
* The Swap call without its funding: the outputs the contract requires (in
|
|
282
|
-
* order: pool, payout, LP fee, validator fee, commission; no
|
|
283
|
+
* order: pool, payout, LP fee, validator fee, commission, 0.40.0 the token change; no sats
|
|
284
|
+
* change) and
|
|
283
285
|
* the method arguments, with the validator's slot empty. For a builder that
|
|
284
286
|
* does not pick the funding itself (a BRC-100 wallet's `createAction` picks
|
|
285
287
|
* inputs and change): hand these outputs over, then fill input 0 with
|
|
@@ -310,6 +312,12 @@ export class PoolTemplate {
|
|
|
310
312
|
throw new PoolBuildError("invalid", "commissionPkh must be 20 bytes of hex");
|
|
311
313
|
if (!isHex(p.userPkh, 20))
|
|
312
314
|
throw new PoolBuildError("invalid", "userPkh must be 20 bytes of hex");
|
|
315
|
+
const tokenChange = p.tokenChange ?? 0n;
|
|
316
|
+
const tokenChangePkh = p.tokenChangePkh ?? ZERO_PKH;
|
|
317
|
+
if (tokenChange < 0n)
|
|
318
|
+
throw new PoolBuildError("invalid", "tokenChange must be ≥ 0");
|
|
319
|
+
if (!isHex(tokenChangePkh, 20))
|
|
320
|
+
throw new PoolBuildError("invalid", "tokenChangePkh must be 20 bytes of hex");
|
|
313
321
|
// Pool.runar.go Swap, line for line.
|
|
314
322
|
const amountIn = p.amountIn;
|
|
315
323
|
if (amountIn <= 0n)
|
|
@@ -364,12 +372,14 @@ export class PoolTemplate {
|
|
|
364
372
|
outputs.push(payout(validatorFee, validatorPkh, p.bsvIn));
|
|
365
373
|
if (commission > 0n)
|
|
366
374
|
outputs.push(payout(commission, p.commissionPkh, p.bsvIn));
|
|
375
|
+
if (tokenChange > 0n)
|
|
376
|
+
outputs.push({ satoshis: 1n, script: tokenP2pkhHex(args.assetId, tokenChange, tokenChangePkh) });
|
|
367
377
|
return {
|
|
368
378
|
pool,
|
|
369
379
|
poolUtxo: p.pool,
|
|
370
380
|
method: "swap",
|
|
371
381
|
outputs,
|
|
372
|
-
args: [EMPTY_SIG, nextValidatorPubKey, amountIn, p.bsvIn, p.userPkh, p.commissionPkh],
|
|
382
|
+
args: [EMPTY_SIG, nextValidatorPubKey, amountIn, p.bsvIn, p.userPkh, p.commissionPkh, tokenChangePkh, tokenChange],
|
|
373
383
|
sigSlots: { validator: 0 },
|
|
374
384
|
next: { pool: { ...pool, state: nextState, contractScript: pool.code + "6a" + stateHex(pool.version, nextState) }, satoshis: Number(newBsv), script: continuation },
|
|
375
385
|
swap: { amountOut: out, lpFee, validatorFee, commission },
|
package/dist/wallet/color.d.ts
CHANGED
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
*
|
|
26
26
|
* The exchange's own baskets (not `p color`; the module would refuse them):
|
|
27
27
|
* `amm-pools` (the LP's position rows) and `amm-swap` (the active swap
|
|
28
|
-
* basket:
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* basket: what a swap stages for its legs and their change between legs,
|
|
29
|
+
* 0.40.0, under the exchange's protocol `[2, "amm swap"]`, counterparty self:
|
|
30
|
+
* a staged row's keyID random, a change row's derived from what the leg spent).
|
|
31
31
|
*/
|
|
32
32
|
import { type ColorReceipt } from "@1sat/actions";
|
|
33
33
|
import { ASSETS_SELECTOR, assetBasket, assetTag, colorProtocolID, type ColorRemittance } from "@bsv/color-permission-module";
|
|
@@ -41,25 +41,29 @@ export declare const SWAP_BASKET = "amm-swap";
|
|
|
41
41
|
export declare const SWAP_PROTOCOL: WalletProtocol;
|
|
42
42
|
/** BRC-100's largest `limit`. */
|
|
43
43
|
export declare const LIST_LIMIT = 10000;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* customInstructions of an `amm-swap` token row: the asset, the amount and the key that spends it;
|
|
46
|
+
* 0.40.0, a leg's token change also `order` (the order's key input, `<txid>.<vout>`).
|
|
47
|
+
*/
|
|
45
48
|
export declare function swapInstructions(o: {
|
|
46
49
|
assetId: string;
|
|
47
50
|
amount: bigint;
|
|
48
51
|
protocolID: WalletProtocol;
|
|
49
52
|
keyID: string;
|
|
50
53
|
counterparty: string;
|
|
54
|
+
order?: string;
|
|
51
55
|
}): string;
|
|
52
56
|
/**
|
|
53
|
-
* customInstructions of an `amm-swap` SATS
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
+
* customInstructions of an `amm-swap` SATS row (0.29.0; 0.40.0, agent-env
|
|
58
|
+
* plans/swap-balance-build-2026-11-05.md: no token tag — the sats rows are the exchange's, not a
|
|
59
|
+
* token's): `sats: true` tells it from a token row; the key that spends it; a leg's sats change
|
|
60
|
+
* also `order` (the order's key input).
|
|
57
61
|
*/
|
|
58
62
|
export declare function swapSatsInstructions(o: {
|
|
59
|
-
assetId: string;
|
|
60
63
|
protocolID: WalletProtocol;
|
|
61
64
|
keyID: string;
|
|
62
65
|
counterparty: string;
|
|
66
|
+
order?: string;
|
|
63
67
|
}): string;
|
|
64
68
|
/** `p color balance <assetTag>`: the selector of one asset's value balance. */
|
|
65
69
|
export declare function balanceSelector(assetId: string): string;
|
package/dist/wallet/color.js
CHANGED
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
*
|
|
26
26
|
* The exchange's own baskets (not `p color`; the module would refuse them):
|
|
27
27
|
* `amm-pools` (the LP's position rows) and `amm-swap` (the active swap
|
|
28
|
-
* basket:
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* basket: what a swap stages for its legs and their change between legs,
|
|
29
|
+
* 0.40.0, under the exchange's protocol `[2, "amm swap"]`, counterparty self:
|
|
30
|
+
* a staged row's keyID random, a change row's derived from what the leg spent).
|
|
31
31
|
*/
|
|
32
32
|
import { colorReceiptInstructions } from "@1sat/actions";
|
|
33
33
|
import { ASSETS_SELECTOR, COLOR_KEY_ID, ColorPlan, assetBasket, assetTag, colorProtocolID } from "@bsv/color-permission-module";
|
|
@@ -42,18 +42,21 @@ export const SWAP_BASKET = "amm-swap";
|
|
|
42
42
|
export const SWAP_PROTOCOL = [2, "amm swap"];
|
|
43
43
|
/** BRC-100's largest `limit`. */
|
|
44
44
|
export const LIST_LIMIT = 10000;
|
|
45
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* customInstructions of an `amm-swap` token row: the asset, the amount and the key that spends it;
|
|
47
|
+
* 0.40.0, a leg's token change also `order` (the order's key input, `<txid>.<vout>`).
|
|
48
|
+
*/
|
|
46
49
|
export function swapInstructions(o) {
|
|
47
|
-
return JSON.stringify({ assetId: o.assetId, amount: String(o.amount), protocolID: o.protocolID, keyID: o.keyID, counterparty: o.counterparty });
|
|
50
|
+
return JSON.stringify({ assetId: o.assetId, amount: String(o.amount), protocolID: o.protocolID, keyID: o.keyID, counterparty: o.counterparty, ...(o.order ? { order: o.order } : {}) });
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
|
-
* customInstructions of an `amm-swap` SATS
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
53
|
+
* customInstructions of an `amm-swap` SATS row (0.29.0; 0.40.0, agent-env
|
|
54
|
+
* plans/swap-balance-build-2026-11-05.md: no token tag — the sats rows are the exchange's, not a
|
|
55
|
+
* token's): `sats: true` tells it from a token row; the key that spends it; a leg's sats change
|
|
56
|
+
* also `order` (the order's key input).
|
|
54
57
|
*/
|
|
55
58
|
export function swapSatsInstructions(o) {
|
|
56
|
-
return JSON.stringify({ sats: true,
|
|
59
|
+
return JSON.stringify({ sats: true, protocolID: o.protocolID, keyID: o.keyID, counterparty: o.counterparty, ...(o.order ? { order: o.order } : {}) });
|
|
57
60
|
}
|
|
58
61
|
/** `p color balance <assetTag>`: the selector of one asset's value balance. */
|
|
59
62
|
export function balanceSelector(assetId) {
|
|
@@ -1,35 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `…:sats`, src/market/activeSwap.ts `swapKeyID`). On Your tokens load (`settleStagedSwaps`),
|
|
6
|
-
* reads only, no resend, nothing shown:
|
|
2
|
+
* Resolve from the overlay (skein-amm 0.40.0, agent-env plans/swap-balance-build-2026-11-05.md
|
|
3
|
+
* Builder 2 item 5): the overlay is the truth about an `amm-swap` row, not the wallet's own flag.
|
|
4
|
+
* On load, before a swap starts, and after any refused or rejected leg (`resolveFromOverlay`):
|
|
7
5
|
*
|
|
8
|
-
* 1.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
6
|
+
* 1. every `amm-swap` row (src/market/activeSwap.ts) asked in ONE read of the host's
|
|
7
|
+
* `GET <origin>/amm/spends?outpoint=a,b` (each enabled token's `/spends`, the first naming a
|
|
8
|
+
* spender);
|
|
9
|
+
* 2. a spender: its transaction, `GET /@<token>/amm/tx?id=` (the BEEF as admitted, every input's
|
|
10
|
+
* source with it); the payout key tried with the spent row's `order` (when it has one) and with
|
|
11
|
+
* the transaction's own key input (`legDerivation`; its first token input on a sell, its first
|
|
12
|
+
* sats input on a buy);
|
|
13
|
+
* 3. a match → the leg's take-in (`legTakeInOf`: the payout, the taker's own commission, the
|
|
14
|
+
* change — kept in `amm-swap` with the order, or the last leg's to the wallet), ONE
|
|
15
|
+
* `internalizeAction` (taking in a transaction the wallet already holds is harmless and
|
|
16
|
+
* re-marks its inputs spent); no match → nothing;
|
|
17
|
+
* 4. again, until no row has a spender the wallet lacks (a take-in's change rows may be spent by
|
|
18
|
+
* a later leg whose page was lost).
|
|
16
19
|
*
|
|
17
|
-
* Any failed read or take-in: nothing, the next
|
|
20
|
+
* Any failed read or take-in: nothing, the next pass tries again. 0.34.0's pool-outpoint grouping
|
|
21
|
+
* of staged rows is gone.
|
|
18
22
|
*/
|
|
19
|
-
import { Transaction, type InternalizeOutput, type WalletInterface
|
|
23
|
+
import { Transaction, type InternalizeOutput, type WalletInterface } from "@bsv/sdk";
|
|
24
|
+
import type { SpendAll } from "../lib/overlay.js";
|
|
25
|
+
/** The swap's own key input (`legDerivation`'s): its first token input on a sell, its first sats input on a buy; null without one. */
|
|
26
|
+
export declare function ownKeyInputOf(tx: Transaction): string | null;
|
|
20
27
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
28
|
+
* The take-in of a swap leg (a pool at input 0 and output 0) under the order `order`: output 1 must
|
|
29
|
+
* pay the order's payout key (else null: another's swap); then each other output that pays one of
|
|
30
|
+
* the order's keys — the taker's own commission, the change kept in `amm-swap` (the keyID of the
|
|
31
|
+
* leg's first input of that kind, the customInstructions with `order`), or the last leg's change to
|
|
32
|
+
* the wallet (`legDerivation(order, "change")`). A 1Color output is a receipt for self (sender the
|
|
33
|
+
* user), a P2PKH one a BRC-29 payment or a basket insertion.
|
|
24
34
|
*/
|
|
25
|
-
export declare function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
export declare function legTakeInOf(wallet: WalletInterface, tx: Transaction, tokenId: string, order: string): Promise<InternalizeOutput[] | null>;
|
|
36
|
+
type FetchTx = (tokenId: string, txid: string) => Promise<number[] | null>;
|
|
37
|
+
/** A leg the pass took in: its transaction, its token, the outpoints it spends (`<txid>.<vout>`, the pool's first) and its payout output. */
|
|
38
|
+
export interface ResolvedLeg {
|
|
39
|
+
txid: string;
|
|
40
|
+
tokenId: string;
|
|
41
|
+
inputs: string[];
|
|
42
|
+
payout: {
|
|
43
|
+
satoshis: number;
|
|
44
|
+
tokens?: bigint;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** The pass (see the module comment): → the legs taken in. Never throws. */
|
|
48
|
+
export declare function resolveFromOverlay(wallet: WalletInterface, reads: {
|
|
49
|
+
spendsAll: (outpoints: string[]) => Promise<SpendAll[]>;
|
|
50
|
+
tx: FetchTx;
|
|
51
|
+
}): Promise<ResolvedLeg[]>;
|
|
35
52
|
export {};
|
|
@@ -1,146 +1,158 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `…:sats`, src/market/activeSwap.ts `swapKeyID`). On Your tokens load (`settleStagedSwaps`),
|
|
6
|
-
* reads only, no resend, nothing shown:
|
|
2
|
+
* Resolve from the overlay (skein-amm 0.40.0, agent-env plans/swap-balance-build-2026-11-05.md
|
|
3
|
+
* Builder 2 item 5): the overlay is the truth about an `amm-swap` row, not the wallet's own flag.
|
|
4
|
+
* On load, before a swap starts, and after any refused or rejected leg (`resolveFromOverlay`):
|
|
7
5
|
*
|
|
8
|
-
* 1.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
6
|
+
* 1. every `amm-swap` row (src/market/activeSwap.ts) asked in ONE read of the host's
|
|
7
|
+
* `GET <origin>/amm/spends?outpoint=a,b` (each enabled token's `/spends`, the first naming a
|
|
8
|
+
* spender);
|
|
9
|
+
* 2. a spender: its transaction, `GET /@<token>/amm/tx?id=` (the BEEF as admitted, every input's
|
|
10
|
+
* source with it); the payout key tried with the spent row's `order` (when it has one) and with
|
|
11
|
+
* the transaction's own key input (`legDerivation`; its first token input on a sell, its first
|
|
12
|
+
* sats input on a buy);
|
|
13
|
+
* 3. a match → the leg's take-in (`legTakeInOf`: the payout, the taker's own commission, the
|
|
14
|
+
* change — kept in `amm-swap` with the order, or the last leg's to the wallet), ONE
|
|
15
|
+
* `internalizeAction` (taking in a transaction the wallet already holds is harmless and
|
|
16
|
+
* re-marks its inputs spent); no match → nothing;
|
|
17
|
+
* 4. again, until no row has a spender the wallet lacks (a take-in's change rows may be spent by
|
|
18
|
+
* a later leg whose page was lost).
|
|
16
19
|
*
|
|
17
|
-
* Any failed read or take-in: nothing, the next
|
|
20
|
+
* Any failed read or take-in: nothing, the next pass tries again. 0.34.0's pool-outpoint grouping
|
|
21
|
+
* of staged rows is gone.
|
|
18
22
|
*/
|
|
19
|
-
import {
|
|
23
|
+
import { Transaction } from "@bsv/sdk";
|
|
20
24
|
import { WALLET_PAYMENT, brc29PublicKey, identityKeyOf, p2pkhHexOf } from "./brc29.js";
|
|
21
|
-
import { SWAP_BASKET, newReceivingKey, receiptOutput } from "./color.js";
|
|
25
|
+
import { SWAP_BASKET, SWAP_PROTOCOL, newReceivingKey, receiptOutput, swapInstructions, swapSatsInstructions } from "./color.js";
|
|
22
26
|
import { PoolTemplate, decodeOneColor } from "../pool/index.js";
|
|
23
|
-
import {
|
|
24
|
-
import { legDerivation,
|
|
27
|
+
import { parseOutpoint, sdkTokenId } from "../lib/tokenId.js";
|
|
28
|
+
import { changeKeyID, legDerivation, legInputsOf } from "../market/swapAction.js";
|
|
25
29
|
import { tokenP2pkhScript } from "../lp/poolDeploy.js";
|
|
26
30
|
import { basketRows } from "../lp/wallet.js";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* Whether output `vout` of `tx` (a swap: the pool at input 0 and output 0) pays this wallet's leg
|
|
30
|
-
* key, re-derived from the transaction's own inputs (`legDerivation`): the take-in for it, or null.
|
|
31
|
-
* A 1Color output is a receipt (sender the pool's validator), a P2PKH one a BRC-29 payment.
|
|
32
|
-
*/
|
|
33
|
-
export async function swapPayoutOf(wallet, tx, vout, tokenId) {
|
|
34
|
-
const pool = tx.outputs[0] ? PoolTemplate.decode(tx.outputs[0].lockingScript) : null;
|
|
35
|
-
const out = tx.outputs[vout];
|
|
36
|
-
const first = tx.outputs[1];
|
|
37
|
-
if (!pool || !out || !first || vout < 1 || tx.inputs.length < 2)
|
|
38
|
-
return null;
|
|
39
|
-
const bsvIn = decodeOneColor(first.lockingScript.toBinary()) !== null;
|
|
40
|
-
let keyInput;
|
|
31
|
+
/** A row's `order` (its customInstructions'), or undefined. */
|
|
32
|
+
function orderOf(row) {
|
|
41
33
|
try {
|
|
42
|
-
|
|
34
|
+
const ci = JSON.parse(row.customInstructions ?? "{}");
|
|
35
|
+
return typeof ci.order === "string" ? ci.order : undefined;
|
|
43
36
|
}
|
|
44
37
|
catch {
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
const d = legDerivation(keyInput, vout === 1 ? "payout" : "commission");
|
|
48
|
-
const script = out.lockingScript.toHex();
|
|
49
|
-
const token = decodeOneColor(out.lockingScript.toBinary());
|
|
50
|
-
if (token) {
|
|
51
|
-
const sdkId = sdkTokenId(tokenId);
|
|
52
|
-
const k = await newReceivingKey(wallet, sdkId, pool.state.validatorIdentity, d);
|
|
53
|
-
return tokenP2pkhScript(sdkId, token.amount, k.publicKey) === script ? receiptOutput(vout, k.receipt) : null;
|
|
38
|
+
return undefined;
|
|
54
39
|
}
|
|
55
|
-
const identity = await identityKeyOf(wallet);
|
|
56
|
-
const pub = await brc29PublicKey(wallet, d);
|
|
57
|
-
if (p2pkhHexOf(PublicKey.fromString(pub).toString()) !== script)
|
|
58
|
-
return null;
|
|
59
|
-
return { outputIndex: vout, protocol: WALLET_PAYMENT, paymentRemittance: { ...d, senderIdentityKey: identity } };
|
|
60
40
|
}
|
|
61
|
-
/** The
|
|
62
|
-
export function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
ci = JSON.parse(row.customInstructions ?? "{}");
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
41
|
+
/** The swap's own key input (`legDerivation`'s): its first token input on a sell, its first sats input on a buy; null without one. */
|
|
42
|
+
export function ownKeyInputOf(tx) {
|
|
43
|
+
const first = tx.outputs[1];
|
|
44
|
+
if (!first)
|
|
68
45
|
return null;
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
return
|
|
46
|
+
const bsvIn = decodeOneColor(first.lockingScript.toBinary()) !== null;
|
|
47
|
+
const ins = legInputsOf(tx);
|
|
48
|
+
return (bsvIn ? ins.sats[0] : ins.tokens[0]) ?? null;
|
|
72
49
|
}
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
50
|
+
/**
|
|
51
|
+
* The take-in of a swap leg (a pool at input 0 and output 0) under the order `order`: output 1 must
|
|
52
|
+
* pay the order's payout key (else null: another's swap); then each other output that pays one of
|
|
53
|
+
* the order's keys — the taker's own commission, the change kept in `amm-swap` (the keyID of the
|
|
54
|
+
* leg's first input of that kind, the customInstructions with `order`), or the last leg's change to
|
|
55
|
+
* the wallet (`legDerivation(order, "change")`). A 1Color output is a receipt for self (sender the
|
|
56
|
+
* user), a P2PKH one a BRC-29 payment or a basket insertion.
|
|
57
|
+
*/
|
|
58
|
+
export async function legTakeInOf(wallet, tx, tokenId, order) {
|
|
59
|
+
const pool = tx.outputs[0] ? PoolTemplate.decode(tx.outputs[0].lockingScript) : null;
|
|
60
|
+
if (!pool || tx.outputs.length < 2 || tx.inputs.length < 2)
|
|
86
61
|
return null;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
62
|
+
const identity = await identityKeyOf(wallet);
|
|
63
|
+
const sdkId = sdkTokenId(tokenId);
|
|
64
|
+
const ins = legInputsOf(tx);
|
|
65
|
+
const keyed = async (vout, which) => {
|
|
66
|
+
const out = tx.outputs[vout];
|
|
67
|
+
const d = legDerivation(order, which);
|
|
68
|
+
const token = decodeOneColor(out.lockingScript.toBinary());
|
|
69
|
+
if (token) {
|
|
70
|
+
const k = await newReceivingKey(wallet, sdkId, identity, d);
|
|
71
|
+
return tokenP2pkhScript(sdkId, token.amount, k.publicKey) === out.lockingScript.toHex() ? receiptOutput(vout, k.receipt) : null;
|
|
72
|
+
}
|
|
73
|
+
const pub = await brc29PublicKey(wallet, d);
|
|
74
|
+
return p2pkhHexOf(pub) === out.lockingScript.toHex() ? { outputIndex: vout, protocol: WALLET_PAYMENT, paymentRemittance: { ...d, senderIdentityKey: identity } } : null;
|
|
75
|
+
};
|
|
76
|
+
const payout = await keyed(1, "payout");
|
|
77
|
+
if (!payout)
|
|
78
|
+
return null;
|
|
79
|
+
const outs = [payout];
|
|
80
|
+
const swapKey = async (keyID) => (await wallet.getPublicKey({ protocolID: SWAP_PROTOCOL, keyID, counterparty: "self", forSelf: true })).publicKey;
|
|
81
|
+
const satsChange = ins.sats[0] ? { keyID: changeKeyID(ins.sats[0]), pub: await swapKey(changeKeyID(ins.sats[0])) } : null;
|
|
82
|
+
const tokenChange = ins.tokens[0] ? { keyID: changeKeyID(ins.tokens[0]), pub: await swapKey(changeKeyID(ins.tokens[0])) } : null;
|
|
83
|
+
for (let vout = 2; vout < tx.outputs.length; vout++) {
|
|
84
|
+
const out = tx.outputs[vout];
|
|
85
|
+
const script = out.lockingScript.toHex();
|
|
86
|
+
const token = decodeOneColor(out.lockingScript.toBinary());
|
|
87
|
+
if (token && tokenChange && tokenP2pkhScript(sdkId, token.amount, tokenChange.pub) === script) {
|
|
88
|
+
outs.push({ outputIndex: vout, protocol: "basket insertion", insertionRemittance: { basket: SWAP_BASKET, customInstructions: swapInstructions({ assetId: sdkId, amount: token.amount, protocolID: SWAP_PROTOCOL, keyID: tokenChange.keyID, counterparty: "self", order }) } });
|
|
96
89
|
continue;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
}
|
|
91
|
+
if (!token && satsChange && p2pkhHexOf(satsChange.pub) === script) {
|
|
92
|
+
outs.push({ outputIndex: vout, protocol: "basket insertion", insertionRemittance: { basket: SWAP_BASKET, customInstructions: swapSatsInstructions({ protocolID: SWAP_PROTOCOL, keyID: satsChange.keyID, counterparty: "self", order }) } });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const mine = (await keyed(vout, "commission")) ?? (await keyed(vout, "change"));
|
|
96
|
+
if (mine)
|
|
97
|
+
outs.push(mine);
|
|
100
98
|
}
|
|
101
|
-
return
|
|
99
|
+
return outs;
|
|
102
100
|
}
|
|
103
|
-
/** The
|
|
104
|
-
export async function
|
|
101
|
+
/** The pass (see the module comment): → the legs taken in. Never throws. */
|
|
102
|
+
export async function resolveFromOverlay(wallet, reads) {
|
|
105
103
|
const taken = [];
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
rows
|
|
109
|
-
|
|
110
|
-
catch {
|
|
111
|
-
return taken;
|
|
112
|
-
}
|
|
113
|
-
for (const [tokenId, pools] of stagedLegs(rows)) {
|
|
104
|
+
const tried = new Set();
|
|
105
|
+
for (let round = 0; round < 64; round++) {
|
|
106
|
+
let rows;
|
|
107
|
+
let spends;
|
|
114
108
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
109
|
+
rows = await basketRows(wallet, SWAP_BASKET);
|
|
110
|
+
if (rows.length === 0)
|
|
111
|
+
return taken;
|
|
112
|
+
spends = await reads.spendsAll(rows.map((r) => r.outpoint.replace("_", ".")));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return taken;
|
|
116
|
+
}
|
|
117
|
+
const byOutpoint = new Map(rows.map((r) => [r.outpoint.replace("_", "."), r]));
|
|
118
|
+
let took = false;
|
|
119
|
+
for (const s of spends) {
|
|
120
|
+
if (!s.spentBy || !s.tokenId || tried.has(s.spentBy))
|
|
121
|
+
continue;
|
|
122
|
+
tried.add(s.spentBy);
|
|
123
|
+
try {
|
|
124
|
+
const beef = await reads.tx(s.tokenId, s.spentBy);
|
|
125
|
+
if (!beef)
|
|
126
|
+
continue;
|
|
127
|
+
const tx = Transaction.fromAtomicBEEF(beef);
|
|
128
|
+
const op = parseOutpoint(s.outpoint);
|
|
129
|
+
const row = op ? byOutpoint.get(`${op.txid}.${op.vout}`) : undefined;
|
|
130
|
+
const orders = [...new Set([row ? orderOf(row) : undefined, ownKeyInputOf(tx) ?? undefined].filter((x) => typeof x === "string"))];
|
|
131
|
+
for (const order of orders) {
|
|
132
|
+
const outputs = await legTakeInOf(wallet, tx, s.tokenId, order);
|
|
133
|
+
if (!outputs)
|
|
131
134
|
continue;
|
|
132
135
|
const r = await wallet.internalizeAction({ tx: beef, outputs, description: "AMM swap payout", labels: ["amm-swap"] });
|
|
133
|
-
if (r.accepted)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
if (r.accepted) {
|
|
137
|
+
const out1 = tx.outputs[1];
|
|
138
|
+
const t = decodeOneColor(out1.lockingScript.toBinary());
|
|
139
|
+
taken.push({
|
|
140
|
+
txid: s.spentBy,
|
|
141
|
+
tokenId: s.tokenId,
|
|
142
|
+
inputs: tx.inputs.map((i) => `${i.sourceTXID ?? i.sourceTransaction?.id("hex")}.${i.sourceOutputIndex}`),
|
|
143
|
+
payout: { satoshis: out1.satoshis ?? 0, ...(t ? { tokens: t.amount } : {}) },
|
|
144
|
+
});
|
|
145
|
+
took = true;
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
138
148
|
}
|
|
139
149
|
}
|
|
150
|
+
catch {
|
|
151
|
+
/* the next pass */
|
|
152
|
+
}
|
|
140
153
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
154
|
+
if (!took)
|
|
155
|
+
return taken;
|
|
144
156
|
}
|
|
145
157
|
return taken;
|
|
146
158
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcasegr/amm-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.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.
|
|
33
|
+
"@dcasegr/amm-engine": "0.41.1",
|
|
34
34
|
"cbor2": "^2.3.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|