@kaleidorg/mind 0.6.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +345 -0
  2. package/dist/autonomy/risk.js.map +1 -1
  3. package/dist/autonomy/run-state.d.ts.map +1 -1
  4. package/dist/autonomy/run-state.js.map +1 -1
  5. package/dist/autonomy/task-store.d.ts.map +1 -1
  6. package/dist/autonomy/task-store.js.map +1 -1
  7. package/dist/bitrefill/contract.js.map +1 -1
  8. package/dist/context/budget.js.map +1 -1
  9. package/dist/context/builder.d.ts.map +1 -1
  10. package/dist/context/builder.js.map +1 -1
  11. package/dist/context/compress.d.ts.map +1 -1
  12. package/dist/context/compress.js +1 -0
  13. package/dist/context/compress.js.map +1 -1
  14. package/dist/context/rgb-units.d.ts +18 -0
  15. package/dist/context/rgb-units.d.ts.map +1 -0
  16. package/dist/context/rgb-units.js +86 -0
  17. package/dist/context/rgb-units.js.map +1 -0
  18. package/dist/engine.d.ts +53 -1
  19. package/dist/engine.d.ts.map +1 -1
  20. package/dist/engine.js +179 -17
  21. package/dist/engine.js.map +1 -1
  22. package/dist/evidence.d.ts +1 -1
  23. package/dist/evidence.d.ts.map +1 -1
  24. package/dist/evidence.js.map +1 -1
  25. package/dist/fastpath/fastpath.d.ts.map +1 -1
  26. package/dist/fastpath/fastpath.js.map +1 -1
  27. package/dist/flashnet/contract.js.map +1 -1
  28. package/dist/funnel.d.ts.map +1 -1
  29. package/dist/funnel.js +23 -4
  30. package/dist/funnel.js.map +1 -1
  31. package/dist/guards.d.ts +63 -0
  32. package/dist/guards.d.ts.map +1 -0
  33. package/dist/guards.js +284 -0
  34. package/dist/guards.js.map +1 -0
  35. package/dist/index.d.ts +10 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +9 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/kaleidoswap/contract.d.ts +3 -4
  40. package/dist/kaleidoswap/contract.d.ts.map +1 -1
  41. package/dist/kaleidoswap/contract.js +3 -17
  42. package/dist/kaleidoswap/contract.js.map +1 -1
  43. package/dist/knowledge/btc-map.js.map +1 -1
  44. package/dist/logger.d.ts.map +1 -1
  45. package/dist/logger.js.map +1 -1
  46. package/dist/lsps1/contract.js.map +1 -1
  47. package/dist/memory/store.d.ts.map +1 -1
  48. package/dist/memory/store.js.map +1 -1
  49. package/dist/providers/openai.d.ts +64 -0
  50. package/dist/providers/openai.d.ts.map +1 -0
  51. package/dist/providers/openai.js +233 -0
  52. package/dist/providers/openai.js.map +1 -0
  53. package/dist/providers/types.d.ts +20 -0
  54. package/dist/providers/types.d.ts.map +1 -1
  55. package/dist/qvac/assistant.js.map +1 -1
  56. package/dist/qvac/config.d.ts +7 -7
  57. package/dist/qvac/config.d.ts.map +1 -1
  58. package/dist/qvac/config.js +1 -1
  59. package/dist/qvac/delegate.d.ts +2 -0
  60. package/dist/qvac/delegate.d.ts.map +1 -1
  61. package/dist/qvac/delegate.js +2 -0
  62. package/dist/qvac/delegate.js.map +1 -1
  63. package/dist/qvac/index.d.ts +2 -0
  64. package/dist/qvac/index.d.ts.map +1 -1
  65. package/dist/qvac/index.js +2 -0
  66. package/dist/qvac/index.js.map +1 -1
  67. package/dist/qvac/models.d.ts +31 -0
  68. package/dist/qvac/models.d.ts.map +1 -0
  69. package/dist/qvac/models.js +80 -0
  70. package/dist/qvac/models.js.map +1 -0
  71. package/dist/qvac/parse.d.ts +16 -2
  72. package/dist/qvac/parse.d.ts.map +1 -1
  73. package/dist/qvac/parse.js +29 -7
  74. package/dist/qvac/parse.js.map +1 -1
  75. package/dist/qvac/provider.d.ts +5 -4
  76. package/dist/qvac/provider.d.ts.map +1 -1
  77. package/dist/qvac/provider.js +30 -21
  78. package/dist/qvac/provider.js.map +1 -1
  79. package/dist/qvac/stream.d.ts +3 -5
  80. package/dist/qvac/stream.d.ts.map +1 -1
  81. package/dist/qvac/stream.js +29 -1
  82. package/dist/qvac/stream.js.map +1 -1
  83. package/dist/qvac/tools.d.ts +37 -0
  84. package/dist/qvac/tools.d.ts.map +1 -0
  85. package/dist/qvac/tools.js +94 -0
  86. package/dist/qvac/tools.js.map +1 -0
  87. package/dist/qvac/voice.js.map +1 -1
  88. package/dist/rag/retriever.d.ts.map +1 -1
  89. package/dist/rag/tool.js.map +1 -1
  90. package/dist/rag/vector-store.d.ts.map +1 -1
  91. package/dist/rag/vector-store.js.map +1 -1
  92. package/dist/recipe/issue-asset.d.ts +16 -0
  93. package/dist/recipe/issue-asset.d.ts.map +1 -0
  94. package/dist/recipe/issue-asset.js +120 -0
  95. package/dist/recipe/issue-asset.js.map +1 -0
  96. package/dist/recipe/runner.d.ts.map +1 -1
  97. package/dist/recipe/runner.js +1 -0
  98. package/dist/recipe/runner.js.map +1 -1
  99. package/dist/recipe/submarine-pay.d.ts +17 -0
  100. package/dist/recipe/submarine-pay.d.ts.map +1 -0
  101. package/dist/recipe/submarine-pay.js +79 -0
  102. package/dist/recipe/submarine-pay.js.map +1 -0
  103. package/dist/skills/loader.js.map +1 -1
  104. package/dist/skills/registry.d.ts.map +1 -1
  105. package/dist/skills/registry.js +1 -1
  106. package/dist/skills/registry.js.map +1 -1
  107. package/dist/skills/select.d.ts +20 -0
  108. package/dist/skills/select.d.ts.map +1 -0
  109. package/dist/skills/select.js +32 -0
  110. package/dist/skills/select.js.map +1 -0
  111. package/dist/submarine/contract.d.ts +42 -0
  112. package/dist/submarine/contract.d.ts.map +1 -0
  113. package/dist/submarine/contract.js +73 -0
  114. package/dist/submarine/contract.js.map +1 -0
  115. package/dist/testing/index.d.ts +14 -0
  116. package/dist/testing/index.d.ts.map +1 -0
  117. package/dist/testing/index.js +12 -0
  118. package/dist/testing/index.js.map +1 -0
  119. package/dist/testing/mock-wallet.d.ts +103 -0
  120. package/dist/testing/mock-wallet.d.ts.map +1 -0
  121. package/dist/testing/mock-wallet.js +249 -0
  122. package/dist/testing/mock-wallet.js.map +1 -0
  123. package/dist/testing/scripted-provider.d.ts +17 -0
  124. package/dist/testing/scripted-provider.d.ts.map +1 -0
  125. package/dist/testing/scripted-provider.js +26 -0
  126. package/dist/testing/scripted-provider.js.map +1 -0
  127. package/dist/tools/in-process.d.ts.map +1 -1
  128. package/dist/tools/mcp.d.ts.map +1 -1
  129. package/dist/tools/registry.d.ts.map +1 -1
  130. package/dist/tools/registry.js.map +1 -1
  131. package/dist/wallet/confirm.d.ts.map +1 -1
  132. package/dist/wallet/confirm.js +43 -5
  133. package/dist/wallet/confirm.js.map +1 -1
  134. package/dist/wallet/contract.d.ts.map +1 -1
  135. package/dist/wallet/contract.js +36 -0
  136. package/dist/wallet/contract.js.map +1 -1
  137. package/package.json +15 -5
  138. package/skills/README.md +2 -1
  139. package/skills/kaleido-node/SKILL.md +63 -0
  140. package/skills/kaleido-trading/SKILL.md +34 -27
  141. package/skills/kaleido-trading/references/api.md +61 -0
  142. package/skills/kaleido-trading/references/assets.md +57 -0
  143. package/skills/kaleido-trading/references/atomic.md +93 -0
  144. package/skills/paid-data/SKILL.md +59 -6
  145. package/skills/rgb-lightning-node/SKILL.md +142 -17
  146. package/skills/spark-wallet/SKILL.md +1 -0
  147. package/skills/submarine-swaps/SKILL.md +48 -0
  148. package/src/context/compress.ts +1 -0
  149. package/src/context/rgb-units.test.ts +42 -0
  150. package/src/context/rgb-units.ts +89 -0
  151. package/src/engine.test.ts +94 -3
  152. package/src/engine.ts +234 -19
  153. package/src/funnel.mind.test.ts +4 -2
  154. package/src/funnel.ts +23 -4
  155. package/src/guards.test.ts +399 -0
  156. package/src/guards.ts +299 -0
  157. package/src/index.ts +40 -1
  158. package/src/kaleidoswap/contract.test.ts +8 -16
  159. package/src/kaleidoswap/contract.ts +4 -32
  160. package/src/providers/openai.test.ts +127 -0
  161. package/src/providers/openai.ts +282 -0
  162. package/src/providers/types.ts +22 -0
  163. package/src/qvac/config.ts +1 -1
  164. package/src/qvac/delegate.ts +2 -0
  165. package/src/qvac/index.ts +12 -0
  166. package/src/qvac/models.ts +98 -0
  167. package/src/qvac/parse.test.ts +7 -0
  168. package/src/qvac/parse.ts +38 -3
  169. package/src/qvac/provider.test.ts +72 -1
  170. package/src/qvac/provider.ts +40 -27
  171. package/src/qvac/stream.test.ts +28 -0
  172. package/src/qvac/stream.ts +33 -6
  173. package/src/qvac/tools.test.ts +86 -0
  174. package/src/qvac/tools.ts +116 -0
  175. package/src/recipe/issue-asset.test.ts +131 -0
  176. package/src/recipe/issue-asset.ts +123 -0
  177. package/src/recipe/runner.ts +1 -0
  178. package/src/recipe/submarine-pay.test.ts +87 -0
  179. package/src/recipe/submarine-pay.ts +78 -0
  180. package/src/skills/registry.ts +1 -1
  181. package/src/skills/select.ts +41 -0
  182. package/src/submarine/contract.ts +112 -0
  183. package/src/testing/index.ts +14 -0
  184. package/src/testing/mock-wallet.ts +278 -0
  185. package/src/testing/scripted-provider.ts +37 -0
  186. package/src/wallet/confirm.test.ts +16 -0
  187. package/src/wallet/confirm.ts +43 -5
  188. package/src/wallet/contract.test.ts +38 -0
  189. package/src/wallet/contract.ts +36 -0
@@ -0,0 +1,93 @@
1
+ # Atomic swap over kaleido-mcp
2
+
3
+ Every KaleidoSwap swap settles as an atomic HTLC swap between the maker and
4
+ the user's RGB Lightning Node (RLN). There is no deposit or order flow on
5
+ kaleido-mcp 0.3.0+. If either side fails, nothing settles. Typical end-to-end
6
+ time: 2–15 s.
7
+
8
+ These are kaleido-mcp's argument names. In-app wallets that bind the
9
+ `@kaleidorg/mind` KaleidoSwap contract use the simpler `from_asset` /
10
+ `to_asset` / `amount` quote and run the chain through a recipe instead.
11
+
12
+ ## Steps
13
+
14
+ ```
15
+ 0. kaleidoswap_get_pairs() → pairs + routes[{ from_layer, to_layer, min_amount, max_amount }]
16
+ kaleidoswap_get_assets() → asset_id + precision per ticker (ids differ per network)
17
+
18
+ 1. kaleidoswap_get_quote({
19
+ from_asset_id: "BTC", from_layer: "BTC_LN", from_amount: 0.001, // display units
20
+ to_asset_id: "<USDT asset_id>", to_layer: "RGB_LN"
21
+ })
22
+ → { rfq_id, expires_at, from_asset: { amount_raw, amount_display },
23
+ to_asset: { amount_raw, amount_display }, price }
24
+ Pass exactly one of from_amount (sell a fixed input) or to_amount (buy a fixed output).
25
+ Show the user amount in → amount out → rate, and wait for a yes.
26
+
27
+ 2. kaleidoswap_atomic_init({ rfq_id,
28
+ from_asset_id, from_amount_raw: quote.from_asset.amount_raw,
29
+ to_asset_id, to_amount_raw: quote.to_asset.amount_raw })
30
+ → { swapstring, payment_hash, access_token }
31
+ access_token is returned only here; keep it for status.
32
+
33
+ 3. rln_atomic_taker({ swapstring }) ← whitelist the incoming HTLC. MUST precede execute.
34
+ rln_get_node_info() → { pubkey } (the taker_pubkey)
35
+
36
+ 4. kaleidoswap_atomic_execute({ swapstring, taker_pubkey, payment_hash })
37
+
38
+ 5. kaleidoswap_atomic_status({ payment_hash, access_token }) ← poll every 2 s
39
+ Waiting → Pending → Succeeded | Failed | Expired
40
+
41
+ 6. rln_refresh_transfers() ← after an RGB leg, so balances update
42
+ ```
43
+
44
+ `amount_raw` values are already in the maker's raw units (msat for BTC, atomic
45
+ units for RGB). Never convert them again. `payment_hash` identifies the swap
46
+ and is not an order id.
47
+
48
+ ## Errors
49
+
50
+ | Situation | What to do |
51
+ |---|---|
52
+ | Quote expired (~60 s) | Re-quote. Never reuse an rfq_id or swapstring. |
53
+ | `rln_atomic_taker` fails | Do NOT execute. Re-quote and restart. |
54
+ | Status `Expired` / `Failed` | Nothing settled. Check liquidity on both legs (`rln_list_channels`), then re-quote. |
55
+ | Polling > 120 s | Check `rln_get_swap({ payment_hash, taker: true })` before retrying. Never start a second swap for the same trade while one may still settle. |
56
+
57
+ ## Liquidity prerequisites
58
+
59
+ The swap needs outbound capacity on the asset you send and inbound capacity on
60
+ the asset you receive. If either side is short, `atomic_execute` fails. Buy a
61
+ channel first (skill: `kaleido-lsps`).
62
+
63
+ ## Reading the result
64
+
65
+ An RGB asset received into a Lightning channel shows up in
66
+ `rln_get_asset_balance({ asset_id }).offchain_outbound`, not in `spendable`
67
+ (which is on-chain only). `offchain_outbound + offchain_inbound` is the
68
+ channel's capacity for that asset. The node's own view of swaps:
69
+ `rln_list_swaps()` / `rln_get_swap({ payment_hash, taker: true })`.
70
+
71
+ ## Cross-layer moves
72
+
73
+ | From | To | How |
74
+ |---|---|---|
75
+ | BTC_LN | RGB_LN (USDT/XAUT) | atomic swap |
76
+ | RGB_LN | BTC_LN | atomic swap |
77
+ | RGB_LN | RGB_LN (e.g. XAUT → USDT) | atomic swap |
78
+ | BTC_LN | BTC_SPARK | not a swap: create a Spark Lightning invoice, pay it from the node (`rln_pay_invoice`) |
79
+ | BTC_SPARK | BTC_LN | create an invoice on the node (`rln_create_ln_invoice`), pay it from Spark |
80
+ | BTC_LN | BTC_L1 | close a channel (`rln_close_channel`), slow |
81
+
82
+ Other layers can appear in `kaleidoswap_get_pairs()` routes but are not
83
+ executable through these tools.
84
+
85
+ ## Safety
86
+
87
+ 1. Confirm from / to / amount / rate before `atomic_init`.
88
+ 2. Never hard-code asset ids or layers; read them from the API.
89
+ 3. Re-quote if execution would start more than ~30 s after the quote.
90
+ 4. Check `rln_list_swaps()` for a pending taker swap before starting another.
91
+ 5. Dry run: when asked for a dry run, quote and report only; never call
92
+ `kaleidoswap_atomic_init`, `rln_atomic_taker`, `kaleidoswap_atomic_execute`,
93
+ `rln_send_asset` or `rln_pay_invoice`.
@@ -1,12 +1,65 @@
1
1
  ---
2
2
  name: paid-data
3
- description: Fetch premium or paywalled data that requires a small Lightning (L402) payment — paid feeds, gated APIs, unlockable resources. Triggers when the user wants premium, paid, or unlockable data behind an L402 paywall.
4
- tools: fetch_paid_resource
5
- triggers: premium, paid, l402, feed, subscription, unlock, paywall
3
+ description: Fetch premium or paywalled data behind an HTTP 402 Lightning payment (L402 or MPP, the Machine Payments Protocol) — paid feeds, pay-per-call APIs, unlockable resources — without signing up or holding an API key. Also finds paid APIs in the public 402index registry. Triggers when the user wants premium, paid, gated or unlockable data, or asks for a paid API.
4
+ tools: fetch_paid_resource, search_paid_apis, mpp_request_challenge, mpp_parse_challenge_header, mpp_submit_credential, rln_mpp_pay, spark_mpp_pay, l402_request_challenge, l402_fetch_resource
5
+ triggers: premium, paid, l402, mpp, 402, feed, subscription, unlock, paywall, pay per call, paid api, gated
6
+ metadata:
7
+ author: kaleidoswap
8
+ version: "0.2.0"
6
9
  ---
7
10
 
8
11
  # Paid data
9
12
 
10
- Fetch L402-paywalled resources, paying small Lightning invoices automatically
11
- with `fetch_paid_resource`. Small amounts pay without prompting (capped);
12
- anything larger is declined. Tell the user what was paid and what was returned.
13
+ Servers gate a resource behind an HTTP 402 Lightning challenge; you pay the
14
+ invoice and present the proof. L402 is the Lightning-only subset of MPP. Tell
15
+ the user what was paid and what came back.
16
+
17
+ Use whichever tools you have:
18
+
19
+ ## In-app: one call
20
+
21
+ `fetch_paid_resource({ url })` fetches, pays small invoices automatically
22
+ (capped by the host) and returns the data. Anything above the cap is declined;
23
+ report that instead of retrying.
24
+
25
+ ## Over kaleido-mcp: three steps
26
+
27
+ ```
28
+ 1. mpp_request_challenge({ url })
29
+ → { challenge_id, invoice, amount_sats, intent, expires_at, macaroon? }
30
+
31
+ 2. rln_mpp_pay({ invoice, challenge_id, macaroon? }) ← pays from the RGB Lightning Node
32
+ (or spark_mpp_pay with the same arguments, from the Spark wallet; better for
33
+ tiny amounts where Lightning routing may fail)
34
+ → { paid, payment_hash, preimage?, credential: "<JSON string>" }
35
+
36
+ 3. mpp_submit_credential({ url, credential }) ← pass credential verbatim
37
+ → { ok, status, data, receipt }
38
+ ```
39
+
40
+ Finish all three before `expires_at` (~60 s); a credential is single-use, so
41
+ restart from step 1 if it expires. Keep the `receipt` as proof of payment.
42
+
43
+ - **Discovery:** `search_paid_apis({ query, protocol?, health: "healthy" })`
44
+ lists registered endpoints with their price; pick a healthy one, then run the
45
+ flow on its `url`.
46
+ - **Own fetch:** with a `WWW-Authenticate` header already in hand, use
47
+ `mpp_parse_challenge_header({ url, www_authenticate })` instead of step 1.
48
+ - **Legacy L402-only servers:** `l402_request_challenge` /
49
+ `l402_fetch_resource`. Prefer the `mpp_*` tools; they handle both.
50
+ - **Sessions:** `intent: "session"` challenges allow pay-once, then cheap
51
+ repeat calls. Not every server offers them; fall back to `charge`.
52
+
53
+ | Error | Meaning | Fix |
54
+ |---|---|---|
55
+ | `Expected HTTP 402` | URL is not payment-gated | Fetch it normally |
56
+ | `payment failed` | Route or balance problem | Check balances; try `spark_mpp_pay` |
57
+ | `401 after submit` | Bad or reused credential | Redo steps 1–3 |
58
+ | `challenge expired` | Too slow between steps | Restart from step 1 |
59
+
60
+ ## Rules
61
+
62
+ 1. Show `amount_sats` before paying; ask for a yes above 1,000 sats.
63
+ 2. Only pay challenges for URLs the user asked for.
64
+ 3. Stop after two failures in a row and report; don't loop.
65
+ 4. Dry run: report the price and what you would fetch; never pay.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: rgb-lightning-node
3
- description: "Drive the user's local RGB Lightning Node (RLN) — read its pubkey/status, list channels and their capacities, check RGB asset balances, manage channels/peers, whitelist a swap, or create Lightning/RGB receive invoices. Triggers when the user asks about the node, their channels or capacities, needs an invoice, or is mid-atomic-swap and the maker needs the node pubkey or a swapstring whitelisted."
4
- tools: rln_get_node_info, rln_get_balances, rln_list_channels, rln_list_assets, rln_get_asset_balance, rln_open_channel, rln_close_channel, rln_connect_peer, rln_get_channel_id, rln_whitelist_swap, rln_atomic_taker, rln_list_payments, rln_create_ln_invoice, rln_create_rgb_invoice
5
- triggers: node, nodeinfo, pubkey, peer, channels, channel capacity, list channels, inbound, capacity, asset balance, whitelist, taker, swapstring, invoice, receive, rgb invoice, ln invoice
3
+ description: "Drive the user's local RGB Lightning Node (RLN) — read its pubkey/status, list channels and their capacities, check RGB asset balances, manage channels/peers, whitelist a swap, or create Lightning/RGB receive invoices. Triggers when the user asks about the node, their channels or capacities, needs an invoice, wants to issue/mint a new RGB token or NFT, or is mid-atomic-swap and the maker needs the node pubkey or a swapstring whitelisted."
4
+ tools: rln_get_node_info, rln_get_balances, rln_list_channels, rln_list_assets, rln_get_asset_balance, rln_refresh_transfers, rln_get_address, rln_send_btc, rln_send_asset, rln_pay_invoice, rln_open_channel, rln_close_channel, rln_connect_peer, rln_get_channel_id, rln_atomic_taker, rln_list_swaps, rln_get_swap, rln_list_payments, rln_create_ln_invoice, rln_create_rgb_invoice, rln_list_transfers, rln_create_utxos, rln_issue_asset
5
+ triggers: node, nodeinfo, pubkey, peer, channels, channel capacity, list channels, open channel, close channel, inbound, capacity, asset balance, whitelist, taker, swapstring, swaps, payments, invoice, receive, send asset, send rgb, on-chain address, deposit, rgb invoice, ln invoice, issue, mint, new token, nft, utxos, transfers
6
6
  metadata:
7
7
  author: kaleidoswap
8
- version: "0.1.0"
8
+ version: "0.3.3"
9
9
  ---
10
10
 
11
11
  # RGB Lightning Node (taker-side)
@@ -70,14 +70,79 @@ ASYNCHRONOUSLY (seconds to minutes after payment). If the new channel isn't
70
70
  listed yet, say it's still opening and suggest checking again — don't claim
71
71
  failure.
72
72
 
73
- ### `rln_list_assets` — no args
74
- Lists RGB assets known to the node with per-asset balances (settled, future,
75
- spendable, offchain_outbound, offchain_inbound). Use for "what assets do I
76
- hold / what's my USDT balance".
73
+ ### `rln_list_assets` — { schemas? }
74
+ Lists RGB assets known to the node: `asset_id`, `ticker`, `name`, `precision`
75
+ (in-app wallets also return balances). `schemas` is an optional filter — an
76
+ array of `"Nia"`, `"Uda"`, `"Cfa"`; call it with `{}` to list everything.
77
+ Use for "what assets do I hold / what's my USDT balance".
78
+
79
+ Call it **once** per question. An empty list is a real answer: the node holds
80
+ no RGB assets yet — tell the user that (and offer to issue one) instead of
81
+ calling it again.
82
+
83
+ ### Tickers are not asset ids
84
+ `asset_id` arguments take the full RGB id (`rgb:…`), never a ticker like
85
+ `USDT` or `HCK`. When the user names an asset by ticker or name, first call
86
+ `rln_list_assets`, find the entry whose `ticker`/`name` matches, and pass its
87
+ `asset_id`. If nothing matches, say the node has no such asset — do not guess
88
+ an id.
77
89
 
78
90
  ### `rln_get_asset_balance` — { asset_id }
79
- Balance for one RGB asset by id. Use after `rln_list_assets` gave you the id,
80
- or when the user names a specific asset.
91
+ Balance for one RGB asset by id. Resolve a ticker to its `asset_id` with
92
+ `rln_list_assets` first.
93
+
94
+ | Field | Meaning |
95
+ |---|---|
96
+ | `settled` / `spendable` | On-chain (RGB_L1), UTXO-bound |
97
+ | `future` | Pending settlement |
98
+ | `offchain_outbound` | In a Lightning channel: what you can **send** |
99
+ | `offchain_inbound` | Channel receive capacity for this asset |
100
+
101
+ For USDT/XAUT held in an RGB channel, report `offchain_outbound`; `spendable`
102
+ is 0 and that is normal.
103
+
104
+ ### `rln_refresh_transfers` — no args
105
+ Syncs pending RGB transfers. Call it before re-reading a balance or transfer
106
+ status that looks stale (e.g. right after an invoice was paid or a swap filled).
107
+
108
+ ### `rln_get_address` — no args
109
+ An on-chain BTC address of the node, for funding it. Not an RGB invoice and not
110
+ a Lightning invoice.
111
+
112
+ ### `rln_send_btc` — { address, amount_sat, fee_rate? } — 🔒 confirm-gated
113
+ Sends on-chain BTC from the node. Only when the user gave both the address and
114
+ the amount.
115
+
116
+ ### `rln_send_asset` — 🔒 confirm-gated
117
+ Sends an RGB asset to the recipient encoded in an RGB invoice. Use the argument
118
+ names of the schema you were given: in-app wallets take `{ asset, amount, to }`
119
+ (ticker or asset_id, units, the invoice); kaleido-mcp takes
120
+ `{ asset_id, amount, recipient_id }`, where `asset_id` is the `rgb:…` id from
121
+ `rln_list_assets`. Never invent a recipient.
122
+
123
+ ### `rln_pay_invoice` — { invoice } — 🔒 confirm-gated
124
+ Pays a BOLT11 Lightning invoice from the node. Pass the full invoice string.
125
+
126
+ ### Channels and peers
127
+ - `rln_connect_peer { peer_pubkey_and_addr }` — `pubkey@host:port`. Needed
128
+ before opening a channel to a peer the node has never seen.
129
+ - `rln_open_channel { peer_pubkey_and_addr, capacity_sat, asset_id?, asset_amount?, push_msat?, is_public? }`
130
+ — 🔒 confirm-gated. Locks on-chain BTC (and optionally an RGB asset) into a
131
+ channel. Opening is asynchronous: report the `temporary_channel_id` and
132
+ suggest checking `rln_list_channels`.
133
+ - `rln_get_channel_id { temporary_channel_id }` — the final `channel_id` once
134
+ the channel is established.
135
+ - `rln_close_channel { channel_id, peer_pubkey, force? }` — 🔒 confirm-gated.
136
+ Both values come from `rln_list_channels`. `force` only for an unresponsive
137
+ peer.
138
+
139
+ ### `rln_list_payments` — { limit? }
140
+ Recent Lightning payments, sent and received. Use for "did I get paid" /
141
+ "what did I pay" over Lightning.
142
+
143
+ ### `rln_list_swaps` / `rln_get_swap { payment_hash, taker? }`
144
+ Atomic swaps as the node sees them (HTLC status). For the maker-side status use
145
+ `kaleidoswap_atomic_status`.
81
146
 
82
147
  ### `rln_atomic_taker` — { swapstring } — 🔒 confirm-gated
83
148
  Tell the node "I accept this swap." Args: the `swapstring` returned by
@@ -92,22 +157,74 @@ Call this **after** `kaleidoswap_atomic_init` and **before**
92
157
  ### `rln_create_ln_invoice` — Lightning invoice for receiving sats
93
158
  Args:
94
159
  - `amount_sats` (optional) — omit for an amountless invoice.
95
- - `expiry_sec` (default 3600) — invoice TTL in seconds.
96
- - `asset_id` + `asset_amount` — optional, for RGB-over-Lightning.
160
+ - `description` (optional, kaleido-mcp) — memo shown to the payer.
161
+ - `expiry_sec` (default 3600, kaleido-mcp only) — invoice TTL in seconds.
162
+
163
+ Reply with the full `invoice` string from the result — never write an invoice
164
+ yourself.
97
165
 
98
166
  Use when the user wants to **receive** a Lightning payment. Do NOT call inside
99
167
  an atomic swap flow unless the user explicitly asked to invoice someone.
100
168
 
101
169
  ### `rln_create_rgb_invoice` — on-chain RGB receive invoice
102
- Args:
103
- - `min_confirmations` (default 1).
104
- - `witness` (default false).
105
- - `asset_id` (optional — omit for an any-asset invoice).
106
- - `expiration_timestamp` (optional, Unix seconds).
170
+ Args (use the names in your schema):
171
+ - in-app wallets: `asset` (ticker or asset_id) + `amount`.
172
+ - kaleido-mcp: `asset_id` (the `rgb:…` id from `rln_list_assets` — never a
173
+ ticker; omit for an any-asset invoice), `amount` (display units),
174
+ `duration_seconds` (default 86400).
175
+
176
+ Reply with the full `invoice` string; the payer needs all of it.
107
177
 
108
178
  Use when the user wants to **receive** an RGB asset directly (not over
109
179
  Lightning). Outside the atomic swap flow.
110
180
 
181
+ ### `rln_list_transfers` — { asset_id }
182
+ Transfers for one asset (issuance, sends, receives) with `status`
183
+ (`WaitingCounterparty`, `WaitingConfirmations`, `Settled`, `Failed`). Use it to
184
+ answer "has my RGB invoice been paid?". Pass the `asset_id` (`rgb:…`) from
185
+ `rln_list_assets` or `rln_issue_asset`; in-app wallets also accept a ticker.
186
+
187
+ ### `rln_create_utxos` — { num?, size?, up_to?, fee_rate? } — 🔒 confirm-gated
188
+ Creates colorable UTXOs (spends a little on-chain BTC). A fresh node needs
189
+ these before it can **issue** or **receive** RGB assets. Call it when an RGB
190
+ call fails with "no available UTXOs", then retry the original action. The
191
+ defaults (`num` 5, `fee_rate` 1) are fine; set `up_to: true` to only top up to
192
+ `num` free UTXOs.
193
+
194
+ ### `rln_issue_asset` — { name, ticker?, amount?, precision?, schema?, details? } — 🔒 confirm-gated
195
+ Creates a **new** RGB asset owned by this node. `schema`: `NIA` fungible token
196
+ (default), `CFA` collectible, `UDA` unique asset / NFT (supply 1).
197
+ - `ticker` (uppercase, 1–8 letters/digits, nothing else) is required for `NIA`
198
+ and `UDA`.
199
+ - `amount` is the total supply in display units (a number), required for `NIA`
200
+ and `CFA` — if the user did not say how many, ask before calling;
201
+ the raw supply is `amount × 10^precision` (`precision` defaults to 0).
202
+ - `details` is an optional description for `CFA` and `UDA`.
203
+
204
+ Reply with the returned `asset_id` — the user needs it to invoice or send the
205
+ new asset.
206
+
207
+ Examples:
208
+ - "issue 1000 TICKET tokens called Hackathon Ticket" →
209
+ `rln_issue_asset { name: "Hackathon Ticket", ticker: "TICKET", amount: 1000 }`
210
+ - "mint an NFT called Genesis Badge" →
211
+ `rln_issue_asset { name: "Genesis Badge", ticker: "GENESISB", schema: "UDA" }`
212
+ - "has anyone paid my TICKET invoice?" → `rln_refresh_transfers` →
213
+ `rln_list_transfers { asset_id: "<TICKET asset_id from rln_list_assets>" }`
214
+
215
+ kaleido-mcp exposes these three as `wdk_issue_asset`, `wdk_create_utxos` and
216
+ `wdk_list_transfers`, with the same arguments and `rln_*` aliases.
217
+
218
+ ### Recipe: issue your own RGB asset
219
+ 1. `rln_get_node_info` — the node is reachable and unlocked.
220
+ 2. `rln_create_utxos {}` — skip if the node already has free colored UTXOs;
221
+ wait for the funding tx to confirm before issuing.
222
+ 3. `rln_issue_asset { name, ticker, amount }` — confirm with the user first.
223
+ 4. `rln_create_rgb_invoice` on the receiver's node, then `rln_send_asset` with
224
+ the new `asset_id` to distribute it.
225
+ 5. `rln_refresh_transfers` → `rln_list_transfers { asset_id }` until the
226
+ transfer is `Settled`.
227
+
111
228
  ## The maker / node split
112
229
 
113
230
  A user-driven swap on KaleidoSwap is a two-service flow. Keep them straight:
@@ -125,6 +242,14 @@ The node's two contributions to the swap are the **pubkey** and the
125
242
  **whitelist ack** — nothing more. Don't reach for `/makerinit` or
126
243
  `/makerexecute`; those are for nodes that act AS the maker, which is not us.
127
244
 
245
+ ## Safety
246
+
247
+ - Show amount, recipient and asset before any send; the engine also asks for
248
+ confirmation on every 🔒 tool.
249
+ - Flag a send above half of the available balance.
250
+ - If the node is unreachable or the wallet is locked, use the `kaleido-node`
251
+ skill (start / unlock) before retrying.
252
+
128
253
  ## Reply style
129
254
 
130
255
  - One short sentence built from the tool result.
@@ -3,6 +3,7 @@ name: spark-wallet
3
3
  description: "Operate the user's Spark BTC wallet on this device — check Spark balance, get a Spark deposit address, create a Spark Lightning invoice to receive, pay any BOLT11 Lightning invoice with Spark, or send BTC on-chain from Spark. Use this when the user names Spark explicitly OR when paying a Lightning invoice on a phone where Spark is the connected layer. Pairs with the bitrefill skill: a Bitrefill purchase that returns a Lightning invoice is paid with `spark_pay_invoice`."
4
4
  tools: spark_get_balance, spark_get_address, spark_get_onchain_address, spark_create_invoice, spark_pay_invoice, spark_send, get_price, fiat_to_sats, bitrefill_search, bitrefill_get_product, bitrefill_get_balance, bitrefill_create_invoice, bitrefill_get_invoice, bitrefill_get_order
5
5
  triggers: spark, sprak, spakr, spark wallet, pay with spark, send with spark, spark balance, spark address, spark invoice, lightning invoice, pay invoice, bolt11, ln invoice, pay this invoice, on-chain address, onchain address, deposit address, deposit btc, fund spark
6
+ requires-tools: spark_get_balance
6
7
  metadata:
7
8
  author: kaleidoswap
8
9
  version: "1.0.0"
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: submarine-swaps
3
+ description: "Pay a Lightning invoice from Liquid funds (L-USDT or L-BTC) through a KaleidoSwap submarine swap on the /v2 maker, and follow its status. Triggers when the user wants to pay a Lightning invoice with Liquid USDT / L-USDT / L-BTC, asks what Liquid assets can pay Lightning, or asks about a submarine swap."
4
+ tools: kaleidoswap_submarine_pairs, kaleidoswap_submarine_create, kaleidoswap_submarine_fund, kaleidoswap_submarine_status
5
+ triggers: l-usdt, liquid usdt, usdt on liquid, l-btc, liquid bitcoin, submarine, submarine swap, pay invoice with liquid, swap status
6
+ metadata:
7
+ author: kaleidoswap
8
+ version: "0.1.0"
9
+ ---
10
+
11
+ # Submarine swaps — pay Lightning from Liquid
12
+
13
+ A submarine swap pays a Lightning (BOLT11) invoice with funds the user locks on
14
+ Liquid. The KaleidoSwap maker pays the invoice, then claims the lockup. It is
15
+ atomic: if the maker can't pay, the lockup is refunded to the user.
16
+
17
+ ## Critical rules
18
+
19
+ - Every amount, fee and status in your reply MUST come from a tool result in the
20
+ CURRENT turn. Never guess the amount to lock — `kaleidoswap_submarine_create`
21
+ returns it.
22
+ - `kaleidoswap_submarine_fund` moves money. Call it ONLY after the user has
23
+ confirmed the exact `expected_amount` returned by `kaleidoswap_submarine_create`.
24
+ - `kaleidoswap_submarine_fund` takes only `swap_id`. Never try to pass an amount
25
+ or an address.
26
+ - A bare "USDT" means RGB USDT on the Lightning node, which uses the atomic swap
27
+ tools, not this skill. Use this skill only for Liquid assets (L-USDT, L-BTC).
28
+
29
+ ## Flow
30
+
31
+ 1. `kaleidoswap_submarine_create { invoice, from_asset }` — from_asset is
32
+ `L-USDT` (default) or `L-BTC`. Returns `swap_id`, `expected_amount` (smallest
33
+ unit: L-USDT has 8 decimals, L-BTC is sats) and the fees.
34
+ 2. Tell the user the amount and ask to confirm.
35
+ 3. `kaleidoswap_submarine_fund { swap_id }`.
36
+ 4. `kaleidoswap_submarine_status { swap_id }` — `transaction.claimed` means the
37
+ invoice was paid. `failed: true` on a funded swap means the funds need a refund;
38
+ say so and quote the `refund` field.
39
+
40
+ Use `kaleidoswap_submarine_pairs` when the user asks what can pay a Lightning
41
+ invoice, or for the limits and fees.
42
+
43
+ ## Examples
44
+
45
+ - "pay lntbs10u1p… with L-USDT" →
46
+ `kaleidoswap_submarine_create { invoice: "lntbs10u1p…", from_asset: "L-USDT" }`
47
+ - "did my Liquid payment go through? swap sub1" →
48
+ `kaleidoswap_submarine_status { swap_id: "sub1" }`
@@ -48,6 +48,7 @@ export const DEFAULT_PRESERVE_KEYS: readonly string[] = [
48
48
  'total',
49
49
  'total_sats',
50
50
  'balance',
51
+ 'balance_display',
51
52
  'balance_sat',
52
53
  'address',
53
54
  'invoice',
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { annotateRgbBalances, fixRgbBalanceUnits, formatRgbAmount } from './rgb-units.js';
3
+
4
+ const LIST = {
5
+ assets: [
6
+ { asset_id: 'rgb:usdt', ticker: 'USDT', name: 'Tether USD', precision: 0, balance: { settled: 1000, spendable: 1000 } },
7
+ { asset_id: 'rgb:xaut', ticker: 'XAUT', name: 'Tether Gold', precision: 0, balance: { spendable: 2 } },
8
+ { asset_id: 'rgb:dec', ticker: 'DEC', precision: 6, balance: { spendable: 1_500_000 } },
9
+ ],
10
+ };
11
+
12
+ describe('RGB balance units', () => {
13
+ it('formats raw amounts with the asset precision', () => {
14
+ expect(formatRgbAmount(1_500_000, 6)).toBe('1.5');
15
+ expect(formatRgbAmount(1_000_000, 0)).toBe('1,000,000');
16
+ });
17
+
18
+ it('adds balance_display next to each asset balance without touching the rest', () => {
19
+ const out = annotateRgbBalances(LIST) as typeof LIST & { assets: Array<{ balance_display: Record<string, string> }> };
20
+ expect(out.assets[0]!.balance_display).toEqual({ settled: '1,000 USDT', spendable: '1,000 USDT' });
21
+ expect(out.assets[2]!.balance_display).toEqual({ spendable: '1.5 DEC' });
22
+ expect(out.assets[0]!.balance).toEqual({ settled: 1000, spendable: 1000 });
23
+ expect(annotateRgbBalances({ vanilla: { spendable: 4277 } })).toEqual({ vanilla: { spendable: 4277 } });
24
+ });
25
+
26
+ it('relabels the asset balances the 2B model called satoshis', () => {
27
+ const answer =
28
+ 'You hold two RGB assets:\n1. **USDT** (Tether USD)\n - Balance: 1,000 satoshis (spendable)\n' +
29
+ '2. **XAUT** (Tether Gold)\n - Balance: 2 satoshis (spendable)';
30
+ expect(fixRgbBalanceUnits(answer, [LIST])).toBe(
31
+ 'You hold two RGB assets:\n1. **USDT** (Tether USD)\n - Balance: 1,000 USDT (spendable)\n' +
32
+ '2. **XAUT** (Tether Gold)\n - Balance: 2 XAUT (spendable)',
33
+ );
34
+ expect(fixRgbBalanceUnits('DEC balance: 1,500,000 sats', [LIST])).toBe('DEC balance: 1.5 DEC');
35
+ });
36
+
37
+ it('leaves real sats and unmatched numbers alone', () => {
38
+ const btc = { btc: { vanilla: { spendable: 4277 } } };
39
+ expect(fixRgbBalanceUnits('Your on-chain balance is 4,277 sats.', [btc, LIST])).toBe('Your on-chain balance is 4,277 sats.');
40
+ expect(fixRgbBalanceUnits('USDT channel fee: 300 sats', [LIST])).toBe('USDT channel fee: 300 sats');
41
+ });
42
+ });
@@ -0,0 +1,89 @@
1
+ /**
2
+ * RGB asset balances are raw integers in the asset's own unit, scaled by its
3
+ * `precision`. Nothing in the tool result says so, and small models fill the
4
+ * gap with "satoshis" ("USDT — Balance: 1,000 satoshis").
5
+ *
6
+ * `annotateRgbBalances` adds a `balance_display` next to each asset's
7
+ * `balance` before the result reaches the model ("1,000 USDT").
8
+ * `fixRgbBalanceUnits` corrects a final answer that still labels an asset
9
+ * balance as sats: the number must equal one of that asset's balances and the
10
+ * nearest ticker before it must be that asset's.
11
+ */
12
+
13
+ type Obj = Record<string, unknown>;
14
+
15
+ interface RgbAsset {
16
+ ticker: string;
17
+ precision: number;
18
+ amounts: number[];
19
+ }
20
+
21
+ const BALANCE_FIELDS = ['settled', 'spendable', 'future'] as const;
22
+
23
+ const isObj = (v: unknown): v is Obj => !!v && typeof v === 'object' && !Array.isArray(v);
24
+
25
+ function asAsset(v: Obj): RgbAsset | null {
26
+ if (typeof v.ticker !== 'string' || !v.ticker || !isObj(v.balance)) return null;
27
+ const amounts = BALANCE_FIELDS.map((k) => (v.balance as Obj)[k]).filter((n): n is number => typeof n === 'number');
28
+ if (!amounts.length) return null;
29
+ const precision = typeof v.precision === 'number' && v.precision > 0 ? v.precision : 0;
30
+ return { ticker: v.ticker, precision, amounts };
31
+ }
32
+
33
+ /** A raw RGB amount in display units, e.g. 1500000 at precision 6 → "1.5". */
34
+ export function formatRgbAmount(raw: number, precision: number): string {
35
+ const value = raw / 10 ** precision;
36
+ return value.toLocaleString('en-US', { maximumFractionDigits: precision });
37
+ }
38
+
39
+ /** Return a copy of `result` where every RGB asset carries `balance_display`. */
40
+ export function annotateRgbBalances(result: unknown): unknown {
41
+ if (Array.isArray(result)) return result.map(annotateRgbBalances);
42
+ if (!isObj(result)) return result;
43
+ const out: Obj = {};
44
+ for (const [k, v] of Object.entries(result)) out[k] = annotateRgbBalances(v);
45
+ const asset = asAsset(result);
46
+ if (asset) {
47
+ const balance = result.balance as Obj;
48
+ const display: Obj = {};
49
+ for (const k of BALANCE_FIELDS) {
50
+ if (typeof balance[k] === 'number') display[k] = `${formatRgbAmount(balance[k] as number, asset.precision)} ${asset.ticker}`;
51
+ }
52
+ out.balance_display = display;
53
+ }
54
+ return out;
55
+ }
56
+
57
+ function collectAssets(value: unknown, into: RgbAsset[]): void {
58
+ if (Array.isArray(value)) {
59
+ for (const v of value) collectAssets(v, into);
60
+ } else if (isObj(value)) {
61
+ const asset = asAsset(value);
62
+ if (asset) into.push(asset);
63
+ for (const v of Object.values(value)) collectAssets(v, into);
64
+ }
65
+ }
66
+
67
+ const AMOUNT_IN_SATS = /(\d[\d,]*(?:\.\d+)?)\s*(?:satoshis|sats?)\b/gi;
68
+ const escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
69
+
70
+ /** Relabel asset balances that the answer calls sats, using the tool results it was built from. */
71
+ export function fixRgbBalanceUnits(text: string, toolResults: unknown[]): string {
72
+ const assets: RgbAsset[] = [];
73
+ for (const r of toolResults) collectAssets(r, assets);
74
+ if (!assets.length) return text;
75
+ const tickerRe = new RegExp(`\\b(${[...new Set(assets.map((a) => escapeRe(a.ticker)))].join('|')})\\b`, 'g');
76
+
77
+ return text.replace(AMOUNT_IN_SATS, (match: string, num: string, offset: number) => {
78
+ let nearest: string | undefined;
79
+ for (const m of text.slice(Math.max(0, offset - 300), offset).matchAll(tickerRe)) nearest = m[1];
80
+ if (!nearest) return match;
81
+ const value = Number(num.replace(/,/g, ''));
82
+ const asset = assets.find(
83
+ (a) => a.ticker === nearest && a.amounts.some((raw) => raw === value || raw / 10 ** a.precision === value),
84
+ );
85
+ if (!asset) return match;
86
+ const raw = asset.amounts.find((r) => r === value) ?? value * 10 ** asset.precision;
87
+ return `${formatRgbAmount(raw, asset.precision)} ${asset.ticker}`;
88
+ });
89
+ }