@kaleidorg/mind 0.10.6 → 0.10.7

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.
@@ -49,10 +49,20 @@ interface QuoteResult {
49
49
  to_amount_display?: string;
50
50
  fee_display?: string;
51
51
  }
52
- /** RLN adds this to the taker's BTC HTLC in a BTC→RGB swap (rgb_htlc_min_msat). */
52
+ /** RLN's minimum HTLC for RGB payments when the node doesn't report one (rgb_htlc_min_msat). */
53
53
  export declare const RLN_HTLC_MIN_MSAT = 3000000;
54
- /** Why the node can't send this swap's BTC leg, or null when it can (or can't tell). */
55
- export declare function outboundShortfall(q: QuoteResult | undefined, channels: unknown): string | null;
54
+ /**
55
+ * Why the node can't carry this swap over its Lightning channels, or null when
56
+ * it can (or the channel data doesn't say). RLN sends each leg as one HTLC and
57
+ * adds its RGB HTLC minimum to the BTC side and to the asset payment:
58
+ * - BTC → asset: BTC outbound ≥ amount + minimum; an asset channel with inbound
59
+ * ≥ the asset received and BTC inbound ≥ minimum.
60
+ * - asset → BTC: an asset channel holding the asset with BTC outbound ≥
61
+ * minimum; BTC inbound ≥ amount + minimum.
62
+ */
63
+ export declare function swapLiquidityShortfall(q: QuoteResult | undefined, channels: unknown, htlcMinMsat?: number): string | null;
64
+ /** @deprecated Use `swapLiquidityShortfall`. */
65
+ export declare const outboundShortfall: typeof swapLiquidityShortfall;
56
66
  export declare const kaleidoswapAtomicRecipe: Recipe;
57
67
  export {};
58
68
  //# sourceMappingURL=kaleidoswap-atomic.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"kaleidoswap-atomic.d.ts","sourceRoot":"","sources":["../../src/recipe/kaleidoswap-atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,YAAY,CAAC;AAsCxD,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClG,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAqC3C,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAS9F;AASD,eAAO,MAAM,uBAAuB,EAAE,MAsHrC,CAAC"}
1
+ {"version":3,"file":"kaleidoswap-atomic.d.ts","sourceRoot":"","sources":["../../src/recipe/kaleidoswap-atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,YAAY,CAAC;AAsCxD,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClG,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAwC3C;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,CAAC,EAAE,WAAW,GAAG,SAAS,EAC1B,QAAQ,EAAE,OAAO,EACjB,WAAW,GAAE,MAA0B,GACtC,MAAM,GAAG,IAAI,CAkDf;AAED,gDAAgD;AAChD,eAAO,MAAM,iBAAiB,+BAAyB,CAAC;AASxD,eAAO,MAAM,uBAAuB,EAAE,MAyHrC,CAAC"}
@@ -72,14 +72,13 @@ const SWAP_INTENT = (t) => {
72
72
  // KaleidoSwap atomic is a BTC ↔ RGB venue: BTC settles on Lightning, RGB
73
73
  // assets on RGB-over-Lightning. The maker/MCP layer is derived from the asset.
74
74
  const layerFor = (asset) => /^btc$/i.test(String(asset)) ? 'BTC_LN' : 'RGB_LN';
75
- /** RLN adds this to the taker's BTC HTLC in a BTC→RGB swap (rgb_htlc_min_msat). */
75
+ /** RLN's minimum HTLC for RGB payments when the node doesn't report one (rgb_htlc_min_msat). */
76
76
  export const RLN_HTLC_MIN_MSAT = 3_000_000;
77
- function paysBtcOverLightning(q) {
78
- const leg = q?.from_asset;
79
- return leg?.layer === 'BTC_LN' || (!leg?.layer && String(leg?.ticker ?? '').toUpperCase() === 'BTC');
80
- }
81
- /** The most one usable channel can send now, in msat; undefined when the shape is unknown. */
82
- function maxOutboundMsat(channels) {
77
+ const isBtc = (leg) => leg?.layer === 'BTC_LN' || (!leg?.layer && String(leg?.ticker ?? '').toUpperCase() === 'BTC');
78
+ const num = (v) => (v == null || v === '' || !Number.isFinite(Number(v)) ? undefined : Number(v));
79
+ const fmtSats = (msat) => `${Math.floor(msat / 1000).toLocaleString('en-US')} sats`;
80
+ const legText = (leg) => (leg?.amount_display ? `${leg.amount_display}${leg.ticker && !/sats?$/i.test(leg.amount_display) ? ` ${leg.ticker}` : ''}` : (leg?.ticker ?? 'the asset'));
81
+ function channelRows(channels) {
83
82
  let list = channels;
84
83
  if (typeof list === 'string') {
85
84
  try {
@@ -90,40 +89,84 @@ function maxOutboundMsat(channels) {
90
89
  }
91
90
  }
92
91
  const rows = Array.isArray(list) ? list : list?.channels;
93
- if (!Array.isArray(rows))
94
- return undefined;
95
- // No channels at all: nothing can send. Channels without readable amounts:
96
- // unknown, so don't block.
97
- if (!rows.length)
98
- return 0;
92
+ return Array.isArray(rows) ? rows : undefined;
93
+ }
94
+ /** Largest value of `pick` over usable channels; undefined when no channel reports it. */
95
+ function maxOver(rows, pick) {
99
96
  let best;
100
97
  let readable = false;
101
98
  for (const c of rows) {
102
- const msat = Number(c.next_outbound_htlc_limit_msat ?? c.outbound_balance_msat);
103
- if (!Number.isFinite(msat))
99
+ const v = pick(c);
100
+ if (v === undefined)
104
101
  continue;
105
102
  readable = true;
106
103
  if (c.is_usable === false || c.ready === false)
107
104
  continue;
108
- best = Math.max(best ?? 0, msat);
105
+ best = Math.max(best ?? 0, v);
109
106
  }
110
- // Readable channels, none usable: nothing can send.
111
107
  return best ?? (readable ? 0 : undefined);
112
108
  }
113
- const fmtSats = (msat) => `${Math.floor(msat / 1000).toLocaleString('en-US')} sats`;
114
- /** Why the node can't send this swap's BTC leg, or null when it can (or can't tell). */
115
- export function outboundShortfall(q, channels) {
116
- const amount = Number(q?.from_asset?.amount_raw);
117
- const max = maxOutboundMsat(channels);
118
- if (!Number.isFinite(amount) || max === undefined)
109
+ const outbound = (c) => num(c.next_outbound_htlc_limit_msat ?? c.outbound_balance_msat);
110
+ const inbound = (c) => num(c.inbound_balance_msat);
111
+ /**
112
+ * Why the node can't carry this swap over its Lightning channels, or null when
113
+ * it can (or the channel data doesn't say). RLN sends each leg as one HTLC and
114
+ * adds its RGB HTLC minimum to the BTC side and to the asset payment:
115
+ * - BTC → asset: BTC outbound ≥ amount + minimum; an asset channel with inbound
116
+ * ≥ the asset received and BTC inbound ≥ minimum.
117
+ * - asset → BTC: an asset channel holding the asset with BTC outbound ≥
118
+ * minimum; BTC inbound ≥ amount + minimum.
119
+ */
120
+ export function swapLiquidityShortfall(q, channels, htlcMinMsat = RLN_HTLC_MIN_MSAT) {
121
+ const from = q?.from_asset;
122
+ const to = q?.to_asset;
123
+ const fromBtc = isBtc(from);
124
+ const toBtc = isBtc(to);
125
+ if (fromBtc === toBtc)
119
126
  return null;
120
- const need = amount + RLN_HTLC_MIN_MSAT;
121
- if (max >= need)
127
+ const rows = channelRows(channels);
128
+ if (!rows)
122
129
  return null;
123
- return max === 0
124
- ? `this swap needs a Lightning channel that can send ${fmtSats(need)} (the amount plus RLN's 3,000-sat HTLC minimum), and none can send right now. Buy a channel with at least that much outbound first.`
125
- : `your channels can send at most ${fmtSats(max)}, and this swap needs ${fmtSats(need)} (the amount plus RLN's 3,000-sat HTLC minimum). Swap at most ${fmtSats(Math.max(0, max - RLN_HTLC_MIN_MSAT))}, or buy a bigger channel.`;
130
+ const min = fmtSats(htlcMinMsat);
131
+ if (!rows.length)
132
+ return `this swap runs over Lightning and you have no channels. Buy a channel first.`;
133
+ const usable = rows.filter((c) => c.is_usable !== false && c.ready !== false);
134
+ const hasAssetData = rows.some((c) => 'asset_id' in c);
135
+ if (fromBtc) {
136
+ const amount = num(from?.amount_raw);
137
+ const maxOut = maxOver(rows, outbound);
138
+ if (amount !== undefined && maxOut !== undefined && maxOut < amount + htlcMinMsat) {
139
+ const need = amount + htlcMinMsat;
140
+ return maxOut === 0
141
+ ? `this swap needs a Lightning channel that can send ${fmtSats(need)} (the amount plus the ${min} HTLC minimum), and none can send right now. Buy a channel with at least that much outbound first.`
142
+ : `your channels can send at most ${fmtSats(maxOut)}, and this swap needs ${fmtSats(need)} (the amount plus the ${min} HTLC minimum). Swap at most ${fmtSats(Math.max(0, maxOut - htlcMinMsat))}, or buy a bigger channel.`;
143
+ }
144
+ const want = num(to?.amount_raw);
145
+ if (hasAssetData && want !== undefined) {
146
+ const ok = usable.some((c) => c.asset_id === to?.asset_id && (num(c.asset_remote_amount) ?? 0) >= want && (inbound(c) ?? Infinity) >= htlcMinMsat);
147
+ if (!ok) {
148
+ return `no channel can receive ${legText(to)}: you need a ${to?.ticker ?? 'asset'} channel with at least that much inbound and ${min} of BTC inbound. Buy an asset channel from the LSP first.`;
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ // asset → BTC
154
+ const have = num(from?.amount_raw);
155
+ if (hasAssetData && have !== undefined) {
156
+ const ok = usable.some((c) => c.asset_id === from?.asset_id && (num(c.asset_local_amount) ?? 0) >= have && (outbound(c) ?? Infinity) >= htlcMinMsat);
157
+ if (!ok) {
158
+ return `no channel can send ${legText(from)}: you need a ${from?.ticker ?? 'asset'} channel holding at least that much, with ${min} of BTC outbound.`;
159
+ }
160
+ }
161
+ const amount = num(to?.amount_raw);
162
+ const maxIn = maxOver(rows, inbound);
163
+ if (amount !== undefined && maxIn !== undefined && maxIn < amount + htlcMinMsat) {
164
+ return `your channels can receive at most ${fmtSats(maxIn)}, and this swap pays you ${fmtSats(amount + htlcMinMsat)} (the amount plus the ${min} HTLC minimum). Swap for less BTC, or get more inbound.`;
165
+ }
166
+ return null;
126
167
  }
168
+ /** @deprecated Use `swapLiquidityShortfall`. */
169
+ export const outboundShortfall = swapLiquidityShortfall;
127
170
  // Render a quote leg as "<amount> <TICKER>" from the MCP quote echo, or undefined
128
171
  // if the leg is missing (callers fall back to the user's slot values).
129
172
  const quoteLeg = (leg) => leg?.amount_display != null ? `${leg.amount_display}${leg.ticker ? ` ${leg.ticker}` : ''}` : undefined;
@@ -167,17 +210,21 @@ export const kaleidoswapAtomicRecipe = {
167
210
  : { ...base, from_amount: ctx.slots.amount };
168
211
  },
169
212
  },
170
- // 1b. NODE: the BTC leg goes out as one Lightning HTLC of the amount plus
171
- // RLN's 3,000-sat HTLC minimum, so a channel must be able to send that
172
- // much. Stop here, before the confirmation, instead of failing with
173
- // NoRoute after the maker has locked the swap.
213
+ // 1b. NODE: pubkey for execute, and the node's RGB HTLC minimum.
214
+ {
215
+ tool: 'rln_get_node_info',
216
+ as: 'node',
217
+ args: () => ({}),
218
+ },
219
+ // 1c. NODE: can the channels carry both legs? RLN sends each leg as one
220
+ // HTLC and adds its HTLC minimum. Stop here, before the confirmation,
221
+ // instead of failing with NoRoute after the maker has locked the swap.
174
222
  {
175
223
  tool: 'rln_list_channels',
176
224
  as: 'channels',
177
225
  optional: true,
178
226
  args: () => ({}),
179
- skipIf: (ctx) => !paysBtcOverLightning(ctx.results.quote),
180
- check: (ctx) => outboundShortfall(ctx.results.quote, ctx.results.channels),
227
+ check: (ctx) => swapLiquidityShortfall(ctx.results.quote, ctx.results.channels, num(ctx.results.node?.rgb_htlc_min_msat) ?? RLN_HTLC_MIN_MSAT),
181
228
  },
182
229
  // 2. MAKER locks the swap. SwapRequest is flat (asset ids + maker-unit
183
230
  // amounts) — sourced straight from the quote result, no re-scaling.
@@ -196,12 +243,6 @@ export const kaleidoswapAtomicRecipe = {
196
243
  };
197
244
  },
198
245
  },
199
- // 3. NODE: read our pubkey — the maker needs it as taker_pubkey for execute.
200
- {
201
- tool: 'rln_get_node_info',
202
- as: 'node',
203
- args: () => ({}),
204
- },
205
246
  // 4. NODE: the taker whitelists the maker's swapstring (accept the swap).
206
247
  // Exposed by kaleido-mcp as `rln_atomic_taker` (calls rln.whitelistSwap).
207
248
  // Ungated — covered by the single confirm above.
@@ -1 +1 @@
1
- {"version":3,"file":"kaleidoswap-atomic.js","sourceRoot":"","sources":["../../src/recipe/kaleidoswap-atomic.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"kaleidoswap-atomic.js","sourceRoot":"","sources":["../../src/recipe/kaleidoswap-atomic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,2EAA2E;AAC3E,8EAA8E;AAC9E,6CAA6C;AAC7C,kEAAkE;AAClE,8EAA8E;AAC9E,+EAA+E;AAC/E,oDAAoD;AACpD,MAAM,OAAO,GAAG,6DAA6D,CAAC;AAC9E,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAClD,MAAM,WAAW,GAAG,CAAC,CAAS,EAAE,EAAE;IAChC,4EAA4E;IAC5E,qCAAqC;IACrC,IAAI,6EAA6E,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACxG,+DAA+D;IAC/D,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,0EAA0E;IAC1E,6EAA6E;IAC7E,yEAAyE;IACzE,IAAI,4DAA4D,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACvF,IAAI,4FAA4F,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACvH,MAAM,QAAQ,GAAG,oCAAoC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,OAAO,GACX,sCAAsC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,wEAAwE;QACxE,yEAAyE;QACzE,CAAC,2FAA2F,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvG,4EAA4E;IAC5E,2EAA2E;IAC3E,IAAI,QAAQ,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAaF,yEAAyE;AACzE,+EAA+E;AAC/E,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAU,EAAE,CAC1C,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAErD,gGAAgG;AAChG,MAAM,CAAC,MAAM,iBAAiB,GAAG,SAAS,CAAC;AAK3C,MAAM,KAAK,GAAG,CAAC,GAAS,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;AAC3H,MAAM,GAAG,GAAG,CAAC,CAAU,EAAsB,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/H,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC;AAC5F,MAAM,OAAO,GAAG,CAAC,GAAS,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,cAAc,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC;AAE1L,SAAS,WAAW,CAAC,QAAiB;IACpC,IAAI,IAAI,GAAY,QAAQ,CAAC;IAC7B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,IAA+B,EAAE,QAAQ,CAAC;IACrF,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAED,0FAA0F;AAC1F,SAAS,OAAO,CAAC,IAAe,EAAE,IAAwC;IACxE,IAAI,IAAwB,CAAC;IAC7B,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QAC9B,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK;YAAE,SAAS;QACzD,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,6BAA6B,IAAI,CAAC,CAAC,qBAAqB,CAAC,CAAC;AACjG,MAAM,OAAO,GAAG,CAAC,CAAU,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACpC,CAA0B,EAC1B,QAAiB,EACjB,WAAW,GAAW,iBAAiB;IAEvC,MAAM,IAAI,GAAG,CAAC,EAAE,UAA6B,CAAC;IAC9C,MAAM,EAAE,GAAG,CAAC,EAAE,QAA2B,CAAC;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;IACxB,IAAI,OAAO,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,8EAA8E,CAAC;IACxG,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;IAC9E,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;IAEvD,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,GAAG,MAAM,GAAG,WAAW,EAAE,CAAC;YAClF,MAAM,IAAI,GAAG,MAAM,GAAG,WAAW,CAAC;YAClC,OAAO,MAAM,KAAK,CAAC;gBACjB,CAAC,CAAC,qDAAqD,OAAO,CAAC,IAAI,CAAC,yBAAyB,GAAG,oGAAoG;gBACpM,CAAC,CAAC,kCAAkC,OAAO,CAAC,MAAM,CAAC,yBAAyB,OAAO,CAAC,IAAI,CAAC,yBAAyB,GAAG,gCAAgC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,4BAA4B,CAAC;QAChO,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QACjC,IAAI,YAAY,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CACpB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,EAAE,EAAE,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,WAAW,CAC3H,CAAC;YACF,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,0BAA0B,OAAO,CAAC,EAAE,CAAC,gBAAgB,EAAE,EAAE,MAAM,IAAI,OAAO,gDAAgD,GAAG,2DAA2D,CAAC;YAClM,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,cAAc;IACd,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACnC,IAAI,YAAY,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CACpB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,EAAE,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,WAAW,CAC7H,CAAC;QACF,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,OAAO,uBAAuB,OAAO,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,MAAM,IAAI,OAAO,6CAA6C,GAAG,mBAAmB,CAAC;QACxJ,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,GAAG,MAAM,GAAG,WAAW,EAAE,CAAC;QAChF,OAAO,qCAAqC,OAAO,CAAC,KAAK,CAAC,4BAA4B,OAAO,CAAC,MAAM,GAAG,WAAW,CAAC,yBAAyB,GAAG,yDAAyD,CAAC;IAC3M,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAExD,kFAAkF;AAClF,uEAAuE;AACvE,MAAM,QAAQ,GAAG,CAAC,GAAkD,EAAsB,EAAE,CAC1F,GAAG,EAAE,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAIzG,MAAM,CAAC,MAAM,uBAAuB,GAAW;IAC7C,IAAI,EAAE,oBAAoB;IAC1B,WAAW,EACT,gIAAgI;IAClI,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC;IACjE,KAAK,EAAE;QACL,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sFAAsF,EAAE,QAAQ,EAAE,IAAI,EAAE;QAC3J,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6EAA6E,EAAE,QAAQ,EAAE,IAAI,EAAE;QAChJ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0IAA0I,EAAE;QAC3L,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0HAA0H,EAAE;KACjL;IACD,6EAA6E;IAC7E,yEAAyE;IACzE,0EAA0E;IAC1E,uEAAuE;IACvE,OAAO,EAAE,WAAW;IACpB,iBAAiB,EAAE,IAAI;IACvB,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM;IAC9D,KAAK,EAAE;QACL,0EAA0E;QAC1E,6EAA6E;QAC7E;YACE,IAAI,EAAE,uBAAuB;YAC7B,EAAE,EAAE,OAAO;YACX,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;gBACZ,uEAAuE;gBACvE,oEAAoE;gBACpE,yDAAyD;gBACzD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,MAAM,CAAC;gBAC7C,MAAM,IAAI,GAAG;oBACX,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU;oBACnC,WAAW,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ;oBAC/B,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC;oBAC1C,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;iBACvC,CAAC;gBACF,OAAO,IAAI,KAAK,IAAI;oBAClB,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE;oBAC1C,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACjD,CAAC;SACF;QACD,iEAAiE;QACjE;YACE,IAAI,EAAE,mBAAmB;YACzB,EAAE,EAAE,MAAM;YACV,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;SACjB;QACD,wEAAwE;QACxE,0EAA0E;QAC1E,2EAA2E;QAC3E;YACE,IAAI,EAAE,mBAAmB;YACzB,EAAE,EAAE,UAAU;YACd,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;YAChB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CACb,sBAAsB,CACpB,GAAG,CAAC,OAAO,CAAC,KAAgC,EAC5C,GAAG,CAAC,OAAO,CAAC,QAAQ,EACpB,GAAG,CAAE,GAAG,CAAC,OAAO,CAAC,IAAoD,EAAE,iBAAiB,CAAC,IAAI,iBAAiB,CAC/G;SACJ;QACD,uEAAuE;QACvE,uEAAuE;QACvE,4EAA4E;QAC5E;YACE,IAAI,EAAE,yBAAyB;YAC/B,EAAE,EAAE,MAAM;YACV,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;gBACZ,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,KAAgC,CAAC;gBACvD,OAAO;oBACL,MAAM,EAAE,CAAC,EAAE,MAAM;oBACjB,aAAa,EAAE,CAAC,EAAE,UAAU,EAAE,QAAQ;oBACtC,eAAe,EAAE,CAAC,EAAE,UAAU,EAAE,UAAU;oBAC1C,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ;oBAClC,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU;iBACvC,CAAC;YACJ,CAAC;SACF;QACD,0EAA0E;QAC1E,6EAA6E;QAC7E,oDAAoD;QACpD;YACE,IAAI,EAAE,kBAAkB;YACxB,EAAE,EAAE,WAAW;YACf,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;gBACZ,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAA8B,CAAC;gBACxD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;YAC1C,CAAC;SACF;KACF;IACD,6EAA6E;IAC7E,KAAK,EAAE;QACL,IAAI,EAAE,4BAA4B;QAClC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;YACZ,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAA8B,CAAC;YACxD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAA4B,CAAC;YACtD,OAAO;gBACL,UAAU,EAAE,IAAI,EAAE,UAAU;gBAC5B,YAAY,EAAE,IAAI,EAAE,MAAM;gBAC1B,YAAY,EAAE,IAAI,EAAE,YAAY;aACjC,CAAC;QACJ,CAAC;KACF;IACD,gFAAgF;IAChF,OAAO,EAAE,CAAC,GAAkB,EAAE,EAAE;QAC9B,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,KAAgC,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACtF,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/D,MAAM,GAAG,GAAG,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,QAAQ,IAAI,MAAM,EAAE,GAAG,GAAG,2BAA2B,CAAC;IAC/D,CAAC;IACD,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACf,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,KAAgC,CAAC;QACvD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACtF,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/D,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAA8B,CAAC;QACxD,MAAM,EAAE,GAAG,IAAI,EAAE,SAAS,IAAI,IAAI,EAAE,YAAY,IAAI,GAAG,CAAC;QACxD,OAAO,mCAAmC,EAAE;kBAC9B,IAAI,MAAM,EAAE,sEAAsE,EAAE,uEAAuE,CAAC;IAC5K,CAAC;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaleidorg/mind",
3
- "version": "0.10.6",
3
+ "version": "0.10.7",
4
4
  "description": "Local-first reasoning + function-calling engine for KaleidoSwap. QVAC-powered.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -17,10 +17,10 @@ ticker (`BTC`, `USDT`, `XAUT`) or an `rgb:…` id.
17
17
  A swap runs over Lightning: it needs a channel with enough outbound in the
18
18
  asset you send and enough inbound in the asset you receive. If
19
19
  `rln_list_channels` shows none, buy one first (see `channel-manager`).
20
- Sending BTC, the node sends the amount plus 3,000 sats (the RGB node's HTLC
21
- minimum) in one payment, so one usable channel needs outbound of at least
22
- amount + 3,000 sats. Less than that: offer a smaller swap (outbound − 3,000)
23
- or a bigger channel.
20
+ Each leg is one payment plus the node's 3,000-sat HTLC minimum: sending BTC
21
+ needs one channel with outbound ≥ amount + 3,000 sats, receiving BTC inbound ≥
22
+ amount + 3,000; the asset side needs an asset channel holding the asset (or
23
+ inbound for it). Short: offer a smaller swap or a bigger channel.
24
24
 
25
25
  ## Do
26
26
  - Quote with exactly one amount: `from_amount` to sell a fixed input,
@@ -212,9 +212,9 @@ describe('desktop mind — buy assets via atomic swap', () => {
212
212
  // The full deterministic chain, in order.
213
213
  expect(calls.map((c) => c.name)).toEqual([
214
214
  'kaleidoswap_get_quote',
215
+ 'rln_get_node_info',
215
216
  'rln_list_channels',
216
217
  'kaleidoswap_atomic_init',
217
- 'rln_get_node_info',
218
218
  'rln_atomic_taker',
219
219
  'kaleidoswap_atomic_execute',
220
220
  ]);
@@ -257,9 +257,9 @@ describe('desktop mind — buy assets via atomic swap', () => {
257
257
  expect(res.route).toBe('kaleidoswap-atomic');
258
258
  expect(calls.map((c) => c.name)).toEqual([
259
259
  'kaleidoswap_get_quote',
260
+ 'rln_get_node_info',
260
261
  'rln_list_channels',
261
262
  'kaleidoswap_atomic_init',
262
- 'rln_get_node_info',
263
263
  'rln_atomic_taker',
264
264
  'kaleidoswap_atomic_execute',
265
265
  ]);
@@ -204,8 +204,8 @@ describe('kaleidoswapAtomicRecipe — full chain', () => {
204
204
  expect(res.inferences).toBe(0);
205
205
  expect(captured.map((c) => c.name)).toEqual([
206
206
  'kaleidoswap_get_quote',
207
- 'kaleidoswap_atomic_init',
208
207
  'rln_get_node_info',
208
+ 'kaleidoswap_atomic_init',
209
209
  'rln_atomic_taker',
210
210
  'kaleidoswap_atomic_execute',
211
211
  ]);
@@ -307,7 +307,68 @@ describe('kaleidoswapAtomicRecipe — single confirmation', () => {
307
307
 
308
308
  expect(res.status).toBe('cancelled');
309
309
  // Quote ran (read-only), but NOTHING after the declined gate.
310
- expect(captured.map((c) => c.name)).toEqual(['kaleidoswap_get_quote']);
310
+ expect(captured.map((c) => c.name)).toEqual(['kaleidoswap_get_quote', 'rln_get_node_info']);
311
+ });
312
+ });
313
+
314
+ describe('kaleidoswapAtomicRecipe — channel liquidity preflight', () => {
315
+ const btcToUsdt = {
316
+ rfq_id: 'rfq-2',
317
+ from_asset: { asset_id: 'BTC', ticker: 'BTC', layer: 'BTC_LN', amount_raw: 50_000_000, amount_display: '50,000 sats' },
318
+ to_asset: { asset_id: 'rgb:usdt', ticker: 'USDT', layer: 'RGB_LN', amount_raw: 30_000_000, amount_display: '30' },
319
+ };
320
+ const usdtToBtc = {
321
+ rfq_id: 'rfq-3',
322
+ from_asset: { asset_id: 'rgb:usdt', ticker: 'USDT', layer: 'RGB_LN', amount_raw: 10_000_000, amount_display: '10' },
323
+ to_asset: { asset_id: 'BTC', ticker: 'BTC', layer: 'BTC_LN', amount_raw: 15_000_000, amount_display: '15,000 sats' },
324
+ };
325
+ const usdtChannel = (over: Record<string, unknown> = {}) => ({
326
+ ready: true,
327
+ is_usable: true,
328
+ asset_id: 'rgb:usdt',
329
+ asset_local_amount: 20_000_000,
330
+ asset_remote_amount: 40_000_000,
331
+ next_outbound_htlc_limit_msat: 60_000_000,
332
+ inbound_balance_msat: 50_000_000,
333
+ ...over,
334
+ });
335
+
336
+ it('BTC -> asset: BTC outbound must cover the amount plus the HTLC minimum', async () => {
337
+ const { swapLiquidityShortfall } = await import('./kaleidoswap-atomic.js');
338
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [usdtChannel({ next_outbound_htlc_limit_msat: 51_000_000 })] })).toBe(
339
+ 'your channels can send at most 51,000 sats, and this swap needs 53,000 sats (the amount plus the 3,000 sats HTLC minimum). Swap at most 48,000 sats, or buy a bigger channel.',
340
+ );
341
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [usdtChannel()] })).toBeNull();
342
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [] })).toMatch(/no channels/);
343
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [usdtChannel({ ready: false })] })).toMatch(/none can send right now/);
344
+ });
345
+
346
+ it('BTC -> asset: an asset channel must be able to receive the asset', async () => {
347
+ const { swapLiquidityShortfall } = await import('./kaleidoswap-atomic.js');
348
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [usdtChannel({ asset_remote_amount: 5_000_000 })] })).toMatch(
349
+ /^no channel can receive 30 USDT/,
350
+ );
351
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [usdtChannel({ inbound_balance_msat: 1_000_000 })] })).toMatch(
352
+ /^no channel can receive 30 USDT/,
353
+ );
354
+ });
355
+
356
+ it('asset -> BTC: the asset must be on our side and BTC inbound must cover amount + minimum', async () => {
357
+ const { swapLiquidityShortfall } = await import('./kaleidoswap-atomic.js');
358
+ expect(swapLiquidityShortfall(usdtToBtc as never, { channels: [usdtChannel()] })).toBeNull();
359
+ expect(swapLiquidityShortfall(usdtToBtc as never, { channels: [usdtChannel({ asset_local_amount: 1_000_000 })] })).toMatch(
360
+ /^no channel can send 10 USDT/,
361
+ );
362
+ expect(swapLiquidityShortfall(usdtToBtc as never, { channels: [usdtChannel({ inbound_balance_msat: 10_000_000 })] })).toBe(
363
+ 'your channels can receive at most 10,000 sats, and this swap pays you 18,000 sats (the amount plus the 3,000 sats HTLC minimum). Swap for less BTC, or get more inbound.',
364
+ );
365
+ });
366
+
367
+ it("uses the node's HTLC minimum and doesn't block on unreadable data", async () => {
368
+ const { swapLiquidityShortfall } = await import('./kaleidoswap-atomic.js');
369
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [usdtChannel({ next_outbound_htlc_limit_msat: 51_000_000 })] }, 1_000_000)).toBeNull();
370
+ expect(swapLiquidityShortfall(btcToUsdt as never, { channels: [{ channel_id: 'x' }] })).toBeNull();
371
+ expect(swapLiquidityShortfall(btcToUsdt as never, 'not json')).toBeNull();
311
372
  });
312
373
  });
313
374
 
@@ -321,10 +382,10 @@ describe('kaleidoswapAtomicRecipe — outbound preflight (BTC leg + RLN HTLC min
321
382
  it('reports the shortfall in plain numbers', async () => {
322
383
  const { outboundShortfall } = await import('./kaleidoswap-atomic.js');
323
384
  expect(outboundShortfall(btcQuote as never, { channels: [{ ready: true, is_usable: true, outbound_balance_msat: 51_000_000 }] })).toBe(
324
- 'your channels can send at most 51,000 sats, and this swap needs 53,000 sats (the amount plus RLN\'s 3,000-sat HTLC minimum). Swap at most 48,000 sats, or buy a bigger channel.',
385
+ 'your channels can send at most 51,000 sats, and this swap needs 53,000 sats (the amount plus the 3,000 sats HTLC minimum). Swap at most 48,000 sats, or buy a bigger channel.',
325
386
  );
326
387
  expect(outboundShortfall(btcQuote as never, { channels: [{ ready: true, next_outbound_htlc_limit_msat: 60_000_000 }] })).toBeNull();
327
- expect(outboundShortfall(btcQuote as never, { channels: [] })).toMatch(/none can send right now/);
388
+ expect(outboundShortfall(btcQuote as never, { channels: [] })).toMatch(/no channels/);
328
389
  expect(outboundShortfall(btcQuote as never, { something: 'else' })).toBeNull();
329
390
  expect(outboundShortfall(btcQuote as never, { channels: [{ ready: false, outbound_balance_msat: 900_000_000 }] })).toMatch(/none can send right now/);
330
391
  expect(outboundShortfall(btcQuote as never, { channels: [{ channel_id: 'x' }] })).toBeNull();
@@ -367,7 +428,7 @@ describe('kaleidoswapAtomicRecipe — outbound preflight (BTC leg + RLN HTLC min
367
428
  });
368
429
  expect(res.status).toBe('error');
369
430
  expect(res.text).toMatch(/can send at most 3,000 sats.*needs 53,000 sats/);
370
- expect(calls).toEqual(['kaleidoswap_get_quote', 'rln_list_channels']);
431
+ expect(calls).toEqual(['kaleidoswap_get_quote', 'rln_get_node_info', 'rln_list_channels']);
371
432
  expect(confirms).toEqual([]);
372
433
  });
373
434
  });
@@ -85,16 +85,18 @@ interface QuoteResult {
85
85
  const layerFor = (asset: unknown): string =>
86
86
  /^btc$/i.test(String(asset)) ? 'BTC_LN' : 'RGB_LN';
87
87
 
88
- /** RLN adds this to the taker's BTC HTLC in a BTC→RGB swap (rgb_htlc_min_msat). */
88
+ /** RLN's minimum HTLC for RGB payments when the node doesn't report one (rgb_htlc_min_msat). */
89
89
  export const RLN_HTLC_MIN_MSAT = 3_000_000;
90
90
 
91
- function paysBtcOverLightning(q?: QuoteResult): boolean {
92
- const leg = q?.from_asset as { layer?: string; ticker?: string } | undefined;
93
- return leg?.layer === 'BTC_LN' || (!leg?.layer && String(leg?.ticker ?? '').toUpperCase() === 'BTC');
94
- }
91
+ type Leg = { asset_id?: string; ticker?: string; layer?: string; amount_raw?: number; amount_display?: string };
92
+ type Channel = Record<string, unknown>;
93
+
94
+ const isBtc = (leg?: Leg) => leg?.layer === 'BTC_LN' || (!leg?.layer && String(leg?.ticker ?? '').toUpperCase() === 'BTC');
95
+ const num = (v: unknown): number | undefined => (v == null || v === '' || !Number.isFinite(Number(v)) ? undefined : Number(v));
96
+ const fmtSats = (msat: number) => `${Math.floor(msat / 1000).toLocaleString('en-US')} sats`;
97
+ const legText = (leg?: Leg) => (leg?.amount_display ? `${leg.amount_display}${leg.ticker && !/sats?$/i.test(leg.amount_display) ? ` ${leg.ticker}` : ''}` : (leg?.ticker ?? 'the asset'));
95
98
 
96
- /** The most one usable channel can send now, in msat; undefined when the shape is unknown. */
97
- function maxOutboundMsat(channels: unknown): number | undefined {
99
+ function channelRows(channels: unknown): Channel[] | undefined {
98
100
  let list: unknown = channels;
99
101
  if (typeof list === 'string') {
100
102
  try {
@@ -104,37 +106,94 @@ function maxOutboundMsat(channels: unknown): number | undefined {
104
106
  }
105
107
  }
106
108
  const rows = Array.isArray(list) ? list : (list as { channels?: unknown })?.channels;
107
- if (!Array.isArray(rows)) return undefined;
108
- // No channels at all: nothing can send. Channels without readable amounts:
109
- // unknown, so don't block.
110
- if (!rows.length) return 0;
109
+ return Array.isArray(rows) ? (rows as Channel[]) : undefined;
110
+ }
111
+
112
+ /** Largest value of `pick` over usable channels; undefined when no channel reports it. */
113
+ function maxOver(rows: Channel[], pick: (c: Channel) => number | undefined): number | undefined {
111
114
  let best: number | undefined;
112
115
  let readable = false;
113
- for (const c of rows as Array<Record<string, unknown>>) {
114
- const msat = Number(c.next_outbound_htlc_limit_msat ?? c.outbound_balance_msat);
115
- if (!Number.isFinite(msat)) continue;
116
+ for (const c of rows) {
117
+ const v = pick(c);
118
+ if (v === undefined) continue;
116
119
  readable = true;
117
120
  if (c.is_usable === false || c.ready === false) continue;
118
- best = Math.max(best ?? 0, msat);
121
+ best = Math.max(best ?? 0, v);
119
122
  }
120
- // Readable channels, none usable: nothing can send.
121
123
  return best ?? (readable ? 0 : undefined);
122
124
  }
123
125
 
124
- const fmtSats = (msat: number) => `${Math.floor(msat / 1000).toLocaleString('en-US')} sats`;
126
+ const outbound = (c: Channel) => num(c.next_outbound_htlc_limit_msat ?? c.outbound_balance_msat);
127
+ const inbound = (c: Channel) => num(c.inbound_balance_msat);
128
+
129
+ /**
130
+ * Why the node can't carry this swap over its Lightning channels, or null when
131
+ * it can (or the channel data doesn't say). RLN sends each leg as one HTLC and
132
+ * adds its RGB HTLC minimum to the BTC side and to the asset payment:
133
+ * - BTC → asset: BTC outbound ≥ amount + minimum; an asset channel with inbound
134
+ * ≥ the asset received and BTC inbound ≥ minimum.
135
+ * - asset → BTC: an asset channel holding the asset with BTC outbound ≥
136
+ * minimum; BTC inbound ≥ amount + minimum.
137
+ */
138
+ export function swapLiquidityShortfall(
139
+ q: QuoteResult | undefined,
140
+ channels: unknown,
141
+ htlcMinMsat: number = RLN_HTLC_MIN_MSAT,
142
+ ): string | null {
143
+ const from = q?.from_asset as Leg | undefined;
144
+ const to = q?.to_asset as Leg | undefined;
145
+ const fromBtc = isBtc(from);
146
+ const toBtc = isBtc(to);
147
+ if (fromBtc === toBtc) return null;
148
+ const rows = channelRows(channels);
149
+ if (!rows) return null;
150
+ const min = fmtSats(htlcMinMsat);
151
+ if (!rows.length) return `this swap runs over Lightning and you have no channels. Buy a channel first.`;
152
+ const usable = rows.filter((c) => c.is_usable !== false && c.ready !== false);
153
+ const hasAssetData = rows.some((c) => 'asset_id' in c);
125
154
 
126
- /** Why the node can't send this swap's BTC leg, or null when it can (or can't tell). */
127
- export function outboundShortfall(q: QuoteResult | undefined, channels: unknown): string | null {
128
- const amount = Number(q?.from_asset?.amount_raw);
129
- const max = maxOutboundMsat(channels);
130
- if (!Number.isFinite(amount) || max === undefined) return null;
131
- const need = amount + RLN_HTLC_MIN_MSAT;
132
- if (max >= need) return null;
133
- return max === 0
134
- ? `this swap needs a Lightning channel that can send ${fmtSats(need)} (the amount plus RLN's 3,000-sat HTLC minimum), and none can send right now. Buy a channel with at least that much outbound first.`
135
- : `your channels can send at most ${fmtSats(max)}, and this swap needs ${fmtSats(need)} (the amount plus RLN's 3,000-sat HTLC minimum). Swap at most ${fmtSats(Math.max(0, max - RLN_HTLC_MIN_MSAT))}, or buy a bigger channel.`;
155
+ if (fromBtc) {
156
+ const amount = num(from?.amount_raw);
157
+ const maxOut = maxOver(rows, outbound);
158
+ if (amount !== undefined && maxOut !== undefined && maxOut < amount + htlcMinMsat) {
159
+ const need = amount + htlcMinMsat;
160
+ return maxOut === 0
161
+ ? `this swap needs a Lightning channel that can send ${fmtSats(need)} (the amount plus the ${min} HTLC minimum), and none can send right now. Buy a channel with at least that much outbound first.`
162
+ : `your channels can send at most ${fmtSats(maxOut)}, and this swap needs ${fmtSats(need)} (the amount plus the ${min} HTLC minimum). Swap at most ${fmtSats(Math.max(0, maxOut - htlcMinMsat))}, or buy a bigger channel.`;
163
+ }
164
+ const want = num(to?.amount_raw);
165
+ if (hasAssetData && want !== undefined) {
166
+ const ok = usable.some(
167
+ (c) => c.asset_id === to?.asset_id && (num(c.asset_remote_amount) ?? 0) >= want && (inbound(c) ?? Infinity) >= htlcMinMsat,
168
+ );
169
+ if (!ok) {
170
+ return `no channel can receive ${legText(to)}: you need a ${to?.ticker ?? 'asset'} channel with at least that much inbound and ${min} of BTC inbound. Buy an asset channel from the LSP first.`;
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+
176
+ // asset → BTC
177
+ const have = num(from?.amount_raw);
178
+ if (hasAssetData && have !== undefined) {
179
+ const ok = usable.some(
180
+ (c) => c.asset_id === from?.asset_id && (num(c.asset_local_amount) ?? 0) >= have && (outbound(c) ?? Infinity) >= htlcMinMsat,
181
+ );
182
+ if (!ok) {
183
+ return `no channel can send ${legText(from)}: you need a ${from?.ticker ?? 'asset'} channel holding at least that much, with ${min} of BTC outbound.`;
184
+ }
185
+ }
186
+ const amount = num(to?.amount_raw);
187
+ const maxIn = maxOver(rows, inbound);
188
+ if (amount !== undefined && maxIn !== undefined && maxIn < amount + htlcMinMsat) {
189
+ return `your channels can receive at most ${fmtSats(maxIn)}, and this swap pays you ${fmtSats(amount + htlcMinMsat)} (the amount plus the ${min} HTLC minimum). Swap for less BTC, or get more inbound.`;
190
+ }
191
+ return null;
136
192
  }
137
193
 
194
+ /** @deprecated Use `swapLiquidityShortfall`. */
195
+ export const outboundShortfall = swapLiquidityShortfall;
196
+
138
197
  // Render a quote leg as "<amount> <TICKER>" from the MCP quote echo, or undefined
139
198
  // if the leg is missing (callers fall back to the user's slot values).
140
199
  const quoteLeg = (leg?: { ticker?: string; amount_display?: string }): string | undefined =>
@@ -183,17 +242,26 @@ export const kaleidoswapAtomicRecipe: Recipe = {
183
242
  : { ...base, from_amount: ctx.slots.amount };
184
243
  },
185
244
  },
186
- // 1b. NODE: the BTC leg goes out as one Lightning HTLC of the amount plus
187
- // RLN's 3,000-sat HTLC minimum, so a channel must be able to send that
188
- // much. Stop here, before the confirmation, instead of failing with
189
- // NoRoute after the maker has locked the swap.
245
+ // 1b. NODE: pubkey for execute, and the node's RGB HTLC minimum.
246
+ {
247
+ tool: 'rln_get_node_info',
248
+ as: 'node',
249
+ args: () => ({}),
250
+ },
251
+ // 1c. NODE: can the channels carry both legs? RLN sends each leg as one
252
+ // HTLC and adds its HTLC minimum. Stop here, before the confirmation,
253
+ // instead of failing with NoRoute after the maker has locked the swap.
190
254
  {
191
255
  tool: 'rln_list_channels',
192
256
  as: 'channels',
193
257
  optional: true,
194
258
  args: () => ({}),
195
- skipIf: (ctx) => !paysBtcOverLightning(ctx.results.quote as QuoteResult | undefined),
196
- check: (ctx) => outboundShortfall(ctx.results.quote as QuoteResult | undefined, ctx.results.channels),
259
+ check: (ctx) =>
260
+ swapLiquidityShortfall(
261
+ ctx.results.quote as QuoteResult | undefined,
262
+ ctx.results.channels,
263
+ num((ctx.results.node as { rgb_htlc_min_msat?: unknown } | undefined)?.rgb_htlc_min_msat) ?? RLN_HTLC_MIN_MSAT,
264
+ ),
197
265
  },
198
266
  // 2. MAKER locks the swap. SwapRequest is flat (asset ids + maker-unit
199
267
  // amounts) — sourced straight from the quote result, no re-scaling.
@@ -212,12 +280,6 @@ export const kaleidoswapAtomicRecipe: Recipe = {
212
280
  };
213
281
  },
214
282
  },
215
- // 3. NODE: read our pubkey — the maker needs it as taker_pubkey for execute.
216
- {
217
- tool: 'rln_get_node_info',
218
- as: 'node',
219
- args: () => ({}),
220
- },
221
283
  // 4. NODE: the taker whitelists the maker's swapstring (accept the swap).
222
284
  // Exposed by kaleido-mcp as `rln_atomic_taker` (calls rln.whitelistSwap).
223
285
  // Ungated — covered by the single confirm above.