@kaleidorg/mind 0.6.4 → 0.7.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 (120) hide show
  1. package/README.md +257 -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.js.map +1 -1
  12. package/dist/engine.d.ts.map +1 -1
  13. package/dist/engine.js.map +1 -1
  14. package/dist/evidence.d.ts +1 -1
  15. package/dist/evidence.d.ts.map +1 -1
  16. package/dist/evidence.js.map +1 -1
  17. package/dist/fastpath/fastpath.d.ts.map +1 -1
  18. package/dist/fastpath/fastpath.js.map +1 -1
  19. package/dist/flashnet/contract.js.map +1 -1
  20. package/dist/funnel.d.ts.map +1 -1
  21. package/dist/funnel.js.map +1 -1
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/kaleidoswap/contract.js.map +1 -1
  27. package/dist/knowledge/btc-map.js.map +1 -1
  28. package/dist/logger.d.ts.map +1 -1
  29. package/dist/logger.js.map +1 -1
  30. package/dist/lsps1/contract.js.map +1 -1
  31. package/dist/memory/store.d.ts.map +1 -1
  32. package/dist/memory/store.js.map +1 -1
  33. package/dist/qvac/assistant.js.map +1 -1
  34. package/dist/qvac/config.d.ts +6 -6
  35. package/dist/qvac/config.d.ts.map +1 -1
  36. package/dist/qvac/delegate.d.ts +2 -0
  37. package/dist/qvac/delegate.d.ts.map +1 -1
  38. package/dist/qvac/delegate.js +2 -0
  39. package/dist/qvac/delegate.js.map +1 -1
  40. package/dist/qvac/index.d.ts +1 -0
  41. package/dist/qvac/index.d.ts.map +1 -1
  42. package/dist/qvac/index.js +1 -0
  43. package/dist/qvac/index.js.map +1 -1
  44. package/dist/qvac/parse.d.ts +5 -2
  45. package/dist/qvac/parse.d.ts.map +1 -1
  46. package/dist/qvac/parse.js.map +1 -1
  47. package/dist/qvac/provider.d.ts.map +1 -1
  48. package/dist/qvac/provider.js +8 -13
  49. package/dist/qvac/provider.js.map +1 -1
  50. package/dist/qvac/stream.d.ts.map +1 -1
  51. package/dist/qvac/stream.js +29 -1
  52. package/dist/qvac/stream.js.map +1 -1
  53. package/dist/qvac/tools.d.ts +32 -0
  54. package/dist/qvac/tools.d.ts.map +1 -0
  55. package/dist/qvac/tools.js +84 -0
  56. package/dist/qvac/tools.js.map +1 -0
  57. package/dist/qvac/voice.js.map +1 -1
  58. package/dist/rag/retriever.d.ts.map +1 -1
  59. package/dist/rag/tool.js.map +1 -1
  60. package/dist/rag/vector-store.d.ts.map +1 -1
  61. package/dist/rag/vector-store.js.map +1 -1
  62. package/dist/recipe/issue-asset.d.ts +16 -0
  63. package/dist/recipe/issue-asset.d.ts.map +1 -0
  64. package/dist/recipe/issue-asset.js +94 -0
  65. package/dist/recipe/issue-asset.js.map +1 -0
  66. package/dist/recipe/runner.d.ts.map +1 -1
  67. package/dist/recipe/runner.js.map +1 -1
  68. package/dist/skills/loader.js.map +1 -1
  69. package/dist/skills/registry.d.ts.map +1 -1
  70. package/dist/skills/registry.js.map +1 -1
  71. package/dist/testing/index.d.ts +14 -0
  72. package/dist/testing/index.d.ts.map +1 -0
  73. package/dist/testing/index.js +12 -0
  74. package/dist/testing/index.js.map +1 -0
  75. package/dist/testing/mock-wallet.d.ts +79 -0
  76. package/dist/testing/mock-wallet.d.ts.map +1 -0
  77. package/dist/testing/mock-wallet.js +180 -0
  78. package/dist/testing/mock-wallet.js.map +1 -0
  79. package/dist/testing/scripted-provider.d.ts +17 -0
  80. package/dist/testing/scripted-provider.d.ts.map +1 -0
  81. package/dist/testing/scripted-provider.js +26 -0
  82. package/dist/testing/scripted-provider.js.map +1 -0
  83. package/dist/tools/in-process.d.ts.map +1 -1
  84. package/dist/tools/mcp.d.ts.map +1 -1
  85. package/dist/tools/registry.d.ts.map +1 -1
  86. package/dist/tools/registry.js.map +1 -1
  87. package/dist/wallet/confirm.d.ts.map +1 -1
  88. package/dist/wallet/confirm.js +21 -1
  89. package/dist/wallet/confirm.js.map +1 -1
  90. package/dist/wallet/contract.d.ts.map +1 -1
  91. package/dist/wallet/contract.js +36 -0
  92. package/dist/wallet/contract.js.map +1 -1
  93. package/package.json +10 -5
  94. package/skills/README.md +1 -1
  95. package/skills/kaleido-node/SKILL.md +63 -0
  96. package/skills/kaleido-trading/SKILL.md +15 -2
  97. package/skills/kaleido-trading/references/api.md +61 -0
  98. package/skills/kaleido-trading/references/assets.md +57 -0
  99. package/skills/kaleido-trading/references/atomic.md +93 -0
  100. package/skills/paid-data/SKILL.md +59 -6
  101. package/skills/rgb-lightning-node/SKILL.md +116 -11
  102. package/src/index.ts +3 -0
  103. package/src/qvac/delegate.ts +2 -0
  104. package/src/qvac/index.ts +2 -0
  105. package/src/qvac/parse.ts +5 -2
  106. package/src/qvac/provider.test.ts +38 -0
  107. package/src/qvac/provider.ts +10 -14
  108. package/src/qvac/stream.test.ts +28 -0
  109. package/src/qvac/stream.ts +30 -1
  110. package/src/qvac/tools.test.ts +86 -0
  111. package/src/qvac/tools.ts +106 -0
  112. package/src/recipe/issue-asset.test.ts +92 -0
  113. package/src/recipe/issue-asset.ts +95 -0
  114. package/src/testing/index.ts +14 -0
  115. package/src/testing/mock-wallet.ts +211 -0
  116. package/src/testing/scripted-provider.ts +37 -0
  117. package/src/wallet/confirm.test.ts +16 -0
  118. package/src/wallet/confirm.ts +21 -1
  119. package/src/wallet/contract.test.ts +38 -0
  120. package/src/wallet/contract.ts +36 -0
@@ -0,0 +1,61 @@
1
+ # KaleidoSwap maker API (what the tools call)
2
+
3
+ Base URLs: `https://api.kaleidoswap.com` (mainnet),
4
+ `https://api.signet.kaleidoswap.com` (signet, `KALEIDO_NETWORK=signet` in
5
+ kaleido-mcp). The MCP tools wrap these endpoints; you normally never call them
6
+ directly. Amounts on the wire are raw units (see `assets.md`).
7
+
8
+ | Endpoint | Tool |
9
+ |---|---|
10
+ | `GET /api/v1/market/assets` | `kaleidoswap_get_assets` |
11
+ | `GET /api/v1/market/pairs` | `kaleidoswap_get_pairs` |
12
+ | `POST /api/v1/market/quote` | `kaleidoswap_get_quote` |
13
+ | `POST /api/v1/swaps/init` | `kaleidoswap_atomic_init` |
14
+ | `POST /api/v1/swaps/execute` | `kaleidoswap_atomic_execute` |
15
+ | `POST /api/v1/swaps/atomic/status` | `kaleidoswap_atomic_status` |
16
+ | `GET /api/v1/lsps1/get_info` | `kaleidoswap_lsp_get_info` |
17
+ | `POST /api/v1/lsps1/estimate_fees` | `kaleidoswap_lsp_estimate_fees` |
18
+ | `POST /api/v1/lsps1/create_order` | `kaleidoswap_lsp_create_order` |
19
+ | `POST /api/v1/lsps1/get_order` | `kaleidoswap_lsp_get_order` |
20
+
21
+ ## Pairs
22
+
23
+ ```json
24
+ [{
25
+ "base": { "ticker": "BTC", "precision": 11 },
26
+ "quote": { "ticker": "USDT", "precision": 6 },
27
+ "routes": [
28
+ { "from_layer": "BTC_LN", "to_layer": "RGB_LN", "min_amount": 50000, "max_amount": 10000000000 },
29
+ { "from_layer": "RGB_LN", "to_layer": "BTC_LN", "min_amount": 1000000, "max_amount": 999999000000 }
30
+ ]
31
+ }]
32
+ ```
33
+
34
+ `min_amount` / `max_amount` are raw units of the route's `from` asset.
35
+
36
+ ## Quote
37
+
38
+ ```json
39
+ {
40
+ "rfq_id": "uuid", "expires_at": "2026-01-01T00:01:00Z",
41
+ "from_asset": { "ticker": "BTC", "layer": "BTC_LN", "amount_raw": 100000000, "amount_display": 0.001 },
42
+ "to_asset": { "ticker": "USDT", "layer": "RGB_LN", "amount_raw": 65763000, "amount_display": 65.763 },
43
+ "price": 65763.0
44
+ }
45
+ ```
46
+
47
+ ## Swap
48
+
49
+ - `init` → `{ swapstring, payment_hash, access_token }` (`access_token` only here)
50
+ - `execute` takes `{ swapstring, taker_pubkey, payment_hash }`
51
+ - `status` takes `{ payment_hash, access_token }` →
52
+ `{ swap: { status: "Waiting" | "Pending" | "Succeeded" | "Expired" | "Failed" } }`
53
+
54
+ ## LSPS1 orders
55
+
56
+ `estimate_fees` / `create_order` take `{ client_pubkey, lsp_balance_sat,
57
+ client_balance_sat, channel_expiry_blocks }`. `create_order` returns
58
+ `{ order_id, bolt11_invoice, order_total_sat }`; `get_order` reports
59
+ `PENDING → CHANNEL_OPENING → COMPLETED | FAILED`. Good defaults:
60
+ `lsp_balance_sat = 500000`, `client_balance_sat = 0`,
61
+ `channel_expiry_blocks = 4320`.
@@ -0,0 +1,57 @@
1
+ # Assets, units and precision
2
+
3
+ Asset ids differ between signet and mainnet and may rotate on signet. Always
4
+ discover them with `kaleidoswap_get_assets()` (cache for at most ~5 minutes);
5
+ never hard-code them.
6
+
7
+ ```json
8
+ { "asset_id": "BTC" | "rgb:...", "ticker": "USDT", "name": "...", "precision": 6 }
9
+ ```
10
+
11
+ Some environments omit BTC from the assets list; `kaleidoswap_get_pairs()`
12
+ always includes it.
13
+
14
+ ## Units
15
+
16
+ BTC uses **millisatoshis** as its raw unit (precision 11):
17
+
18
+ ```
19
+ 1 BTC = 100,000,000 sats = 1e11 msat
20
+ 0.001 BTC = 100,000 sats = 1e8 msat
21
+ 1 sat = 1,000 msat
22
+ ```
23
+
24
+ RGB assets use their own precision:
25
+
26
+ ```
27
+ raw = round(display × 10^precision)
28
+ display = raw / 10^precision
29
+ ```
30
+
31
+ | Asset | Typical precision | Raw unit |
32
+ |---|---|---|
33
+ | BTC | 11 | msat |
34
+ | USDT | 6 | micro-USDT |
35
+ | XAUT | 9 | nano-XAUT |
36
+
37
+ Read precision from the API; the table is illustrative.
38
+
39
+ ## Which unit each tool takes
40
+
41
+ | Tool | Units |
42
+ |---|---|
43
+ | `kaleidoswap_get_quote` (kaleido-mcp) | display (`0.001` BTC, `65.0` USDT) |
44
+ | `kaleidoswap_atomic_init` (kaleido-mcp) | raw: pass `quote.*.amount_raw` unchanged |
45
+ | `kaleidoswap_get_quote` (in-app contract) | sats for BTC, asset units otherwise |
46
+ | `rln_send_asset` / `rln_create_rgb_invoice` (kaleido-mcp) | display units |
47
+
48
+ ## Mapping user words
49
+
50
+ - "BTC" over Lightning → `asset_id: "BTC"`, layer `BTC_LN`; on-chain → `BTC_L1`.
51
+ - "USDT" → the asset whose `ticker === "USDT"`. "USD" usually means USDT: confirm.
52
+ - "gold" / "XAUT" → `ticker === "XAUT"`. Confirm "gold".
53
+
54
+ ## Layers
55
+
56
+ `BTC_L1`, `BTC_LN`, `BTC_SPARK`, `BTC_ARKADE`, `RGB_L1`, `RGB_LN`. Use the
57
+ values from `kaleidoswap_get_pairs()` routes verbatim.
@@ -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.2"
9
9
  ---
10
10
 
11
11
  # RGB Lightning Node (taker-side)
@@ -79,6 +79,58 @@ hold / what's my USDT balance".
79
79
  Balance for one RGB asset by id. Use after `rln_list_assets` gave you the id,
80
80
  or when the user names a specific asset.
81
81
 
82
+ | Field | Meaning |
83
+ |---|---|
84
+ | `settled` / `spendable` | On-chain (RGB_L1), UTXO-bound |
85
+ | `future` | Pending settlement |
86
+ | `offchain_outbound` | In a Lightning channel: what you can **send** |
87
+ | `offchain_inbound` | Channel receive capacity for this asset |
88
+
89
+ For USDT/XAUT held in an RGB channel, report `offchain_outbound`; `spendable`
90
+ is 0 and that is normal.
91
+
92
+ ### `rln_refresh_transfers` — no args
93
+ Syncs pending RGB transfers. Call it before re-reading a balance or transfer
94
+ status that looks stale (e.g. right after an invoice was paid or a swap filled).
95
+
96
+ ### `rln_get_address` — no args
97
+ An on-chain BTC address of the node, for funding it. Not an RGB invoice and not
98
+ a Lightning invoice.
99
+
100
+ ### `rln_send_btc` — { address, amount_sat, fee_rate? } — 🔒 confirm-gated
101
+ Sends on-chain BTC from the node. Only when the user gave both the address and
102
+ the amount.
103
+
104
+ ### `rln_send_asset` — 🔒 confirm-gated
105
+ Sends an RGB asset to the recipient encoded in an RGB invoice. Use the argument
106
+ names of the schema you were given: in-app wallets take `{ asset, amount, to }`
107
+ (ticker or asset_id, units, the invoice); kaleido-mcp takes
108
+ `{ asset_id, amount, recipient_id }`. Never invent a recipient.
109
+
110
+ ### `rln_pay_invoice` — { invoice } — 🔒 confirm-gated
111
+ Pays a BOLT11 Lightning invoice from the node. Pass the full invoice string.
112
+
113
+ ### Channels and peers
114
+ - `rln_connect_peer { peer_pubkey_and_addr }` — `pubkey@host:port`. Needed
115
+ before opening a channel to a peer the node has never seen.
116
+ - `rln_open_channel { peer_pubkey_and_addr, capacity_sat, asset_id?, asset_amount?, push_msat?, is_public? }`
117
+ — 🔒 confirm-gated. Locks on-chain BTC (and optionally an RGB asset) into a
118
+ channel. Opening is asynchronous: report the `temporary_channel_id` and
119
+ suggest checking `rln_list_channels`.
120
+ - `rln_get_channel_id { temporary_channel_id }` — the final `channel_id` once
121
+ the channel is established.
122
+ - `rln_close_channel { channel_id, peer_pubkey, force? }` — 🔒 confirm-gated.
123
+ Both values come from `rln_list_channels`. `force` only for an unresponsive
124
+ peer.
125
+
126
+ ### `rln_list_payments` — { limit? }
127
+ Recent Lightning payments, sent and received. Use for "did I get paid" /
128
+ "what did I pay" over Lightning.
129
+
130
+ ### `rln_list_swaps` / `rln_get_swap { payment_hash, taker? }`
131
+ Atomic swaps as the node sees them (HTLC status). For the maker-side status use
132
+ `kaleidoswap_atomic_status`.
133
+
82
134
  ### `rln_atomic_taker` — { swapstring } — 🔒 confirm-gated
83
135
  Tell the node "I accept this swap." Args: the `swapstring` returned by
84
136
  `kaleidoswap_atomic_init`. The node validates and stores it; **no funds move
@@ -92,22 +144,67 @@ Call this **after** `kaleidoswap_atomic_init` and **before**
92
144
  ### `rln_create_ln_invoice` — Lightning invoice for receiving sats
93
145
  Args:
94
146
  - `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.
147
+ - `expiry_sec` (default 3600, kaleido-mcp only) — invoice TTL in seconds.
97
148
 
98
149
  Use when the user wants to **receive** a Lightning payment. Do NOT call inside
99
150
  an atomic swap flow unless the user explicitly asked to invoice someone.
100
151
 
101
152
  ### `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).
153
+ Args (use the names in your schema):
154
+ - in-app wallets: `asset` (ticker or asset_id) + `amount`.
155
+ - kaleido-mcp: `asset_id` (omit for an any-asset invoice), `amount` (display
156
+ units), `duration_seconds` (default 86400).
157
+
158
+ Reply with the full `invoice` string; the payer needs all of it.
107
159
 
108
160
  Use when the user wants to **receive** an RGB asset directly (not over
109
161
  Lightning). Outside the atomic swap flow.
110
162
 
163
+ ### `rln_list_transfers` — { asset_id }
164
+ Transfers for one asset (issuance, sends, receives) with `status`
165
+ (`WaitingCounterparty`, `WaitingConfirmations`, `Settled`, `Failed`). Use it to
166
+ answer "has my RGB invoice been paid?". Pass the `asset_id` (`rgb:…`) from
167
+ `rln_list_assets` or `rln_issue_asset`; in-app wallets also accept a ticker.
168
+
169
+ ### `rln_create_utxos` — { num?, size?, up_to?, fee_rate? } — 🔒 confirm-gated
170
+ Creates colorable UTXOs (spends a little on-chain BTC). A fresh node needs
171
+ these before it can **issue** or **receive** RGB assets. Call it when an RGB
172
+ call fails with "no available UTXOs", then retry the original action. The
173
+ defaults (`num` 5, `fee_rate` 1) are fine; set `up_to: true` to only top up to
174
+ `num` free UTXOs.
175
+
176
+ ### `rln_issue_asset` — { name, ticker?, amount?, precision?, schema?, details? } — 🔒 confirm-gated
177
+ Creates a **new** RGB asset owned by this node. `schema`: `NIA` fungible token
178
+ (default), `CFA` collectible, `UDA` unique asset / NFT (supply 1).
179
+ - `ticker` (uppercase, 1–8 letters/digits) is required for `NIA` and `UDA`.
180
+ - `amount` is the total supply in display units, required for `NIA` and `CFA`;
181
+ the raw supply is `amount × 10^precision` (`precision` defaults to 0).
182
+ - `details` is an optional description for `CFA` and `UDA`.
183
+
184
+ Reply with the returned `asset_id` — the user needs it to invoice or send the
185
+ new asset.
186
+
187
+ Examples:
188
+ - "issue 1000 TICKET tokens called Hackathon Ticket" →
189
+ `rln_issue_asset { name: "Hackathon Ticket", ticker: "TICKET", amount: 1000 }`
190
+ - "mint an NFT called Genesis Badge" →
191
+ `rln_issue_asset { name: "Genesis Badge", ticker: "GENESISB", schema: "UDA" }`
192
+ - "has anyone paid my TICKET invoice?" → `rln_refresh_transfers` →
193
+ `rln_list_transfers { asset_id: "<TICKET asset_id from rln_list_assets>" }`
194
+
195
+ kaleido-mcp exposes these three as `wdk_issue_asset`, `wdk_create_utxos` and
196
+ `wdk_list_transfers`, with the same arguments and `rln_*` aliases.
197
+
198
+ ### Recipe: issue your own RGB asset
199
+ 1. `rln_get_node_info` — the node is reachable and unlocked.
200
+ 2. `rln_create_utxos {}` — skip if the node already has free colored UTXOs;
201
+ wait for the funding tx to confirm before issuing.
202
+ 3. `rln_issue_asset { name, ticker, amount }` — confirm with the user first.
203
+ 4. `rln_create_rgb_invoice` on the receiver's node, then `rln_send_asset` with
204
+ the new `asset_id` to distribute it.
205
+ 5. `rln_refresh_transfers` → `rln_list_transfers { asset_id }` until the
206
+ transfer is `Settled`.
207
+
111
208
  ## The maker / node split
112
209
 
113
210
  A user-driven swap on KaleidoSwap is a two-service flow. Keep them straight:
@@ -125,6 +222,14 @@ The node's two contributions to the swap are the **pubkey** and the
125
222
  **whitelist ack** — nothing more. Don't reach for `/makerinit` or
126
223
  `/makerexecute`; those are for nodes that act AS the maker, which is not us.
127
224
 
225
+ ## Safety
226
+
227
+ - Show amount, recipient and asset before any send; the engine also asks for
228
+ confirmation on every 🔒 tool.
229
+ - Flag a send above half of the available balance.
230
+ - If the node is unreachable or the wallet is locked, use the `kaleido-node`
231
+ skill (start / unlock) before retrying.
232
+
128
233
  ## Reply style
129
234
 
130
235
  - One short sentence built from the tool result.
package/src/index.ts CHANGED
@@ -129,6 +129,9 @@ export {
129
129
  // ── Buy-an-asset-channel recipe (opt-in — register via Funnel.recipes) ─────
130
130
  export { buyAssetChannelRecipe, extractBuyAsset } from './recipe/buy-asset-channel.js';
131
131
 
132
+ // ── Issue-an-RGB-asset recipe (opt-in — register via Funnel.recipes) ───────
133
+ export { issueAssetRecipe, extractIssueAsset } from './recipe/issue-asset.js';
134
+
132
135
  // ── Recipes (mobile multi-step: "recipes, not planning") ───────────────────
133
136
  export { runRecipe, extractSlots, RecipeRegistry } from './recipe/runner.js';
134
137
  export type { RunRecipeOptions } from './recipe/runner.js';
@@ -4,6 +4,8 @@
4
4
  * they stay shared + testable; the host passes the result to
5
5
  * `startQVACProvider({ firewall })` / `loadModel({ delegate })`.
6
6
  *
7
+ * P2P delegation exists in @qvac/sdk 0.13–0.18 only; 0.19 removed both calls.
8
+ *
7
9
  * Security note: a QVAC provider is reachable by anyone who learns its
8
10
  * Hyperswarm public key. Advertising with no firewall means any such peer can
9
11
  * run inference on your machine. Use {@link allowListFirewall} so a desktop
package/src/qvac/index.ts CHANGED
@@ -38,6 +38,8 @@ export {
38
38
  type ConsumedTurn,
39
39
  } from './stream.js';
40
40
 
41
+ export { toQvacTools, type QvacTool, type QvacToolProp } from './tools.js';
42
+
41
43
  export {
42
44
  createQvacProvider,
43
45
  type QvacProviderOptions,
package/src/qvac/parse.ts CHANGED
@@ -17,6 +17,9 @@ export interface QvacTurnStats {
17
17
  tokensPerSecond?: number;
18
18
  totalTokens?: number;
19
19
  promptTokens?: number;
20
+ /** Tokens generated this turn (what the SDK reports; `totalTokens` is not emitted). */
21
+ generatedTokens?: number;
22
+ timeToFirstToken?: number;
20
23
  contextSize?: number;
21
24
  totalTime?: number;
22
25
  }
@@ -30,8 +33,8 @@ export interface QvacFinalLike {
30
33
  /** Tool calls the model requested this turn (empty ⇒ final answer). */
31
34
  toolCalls?: Array<{ id?: string; name: string; arguments?: Record<string, unknown> }>;
32
35
  /**
33
- * Why generation stopped. QVAC 0.13 emits `"length"` when the token budget is
34
- * exhausted, `"cancelled"` on abort, `undefined` on a natural stop. We surface
36
+ * Why generation stopped: `"length"` when the token budget is exhausted,
37
+ * `"cancelled"` on abort, `"eos"`/`"stopSequence"`/`undefined` on a natural stop. We surface
35
38
  * it so the funnel can tell a truncated tool-call from a complete one.
36
39
  */
37
40
  stopReason?: 'length' | 'cancelled' | string;
@@ -77,6 +77,18 @@ describe('createQvacProvider.runTurn', () => {
77
77
  expect(params.generationParams).toEqual({ temp: 0.9, predict: 99 });
78
78
  });
79
79
 
80
+ it('sends JSON-Schema tools in the SDK Tool shape so arguments survive', async () => {
81
+ const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
82
+ const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1' });
83
+ await p.runTurn({
84
+ messages: [{ role: 'user', content: 'x' }],
85
+ tools: [{ name: 'echo', description: 'e', parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } }],
86
+ });
87
+ expect(calls[0].tools).toEqual([
88
+ { type: 'function', name: 'echo', description: 'e', parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } },
89
+ ]);
90
+ });
91
+
80
92
  it('omits generationParams when no temperature/maxTokens is set (keeps SDK defaults)', async () => {
81
93
  const { fn, calls } = fakeCompletion({ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' } });
82
94
  const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1' });
@@ -101,6 +113,32 @@ describe('createQvacProvider.runTurn', () => {
101
113
  expect(out.text).toMatch(/thinking budget/i);
102
114
  });
103
115
 
116
+ it('returns a cancelled turn when the SDK rejects final on abort', async () => {
117
+ const cancel = vi.fn(async () => {});
118
+ const fn = () => ({
119
+ requestId: 'req-a',
120
+ events: (async function* () {})(),
121
+ final: Promise.reject(Object.assign(new Error('cancelled'), { requestId: 'req-a', partial: {} })),
122
+ });
123
+ const ac = new AbortController();
124
+ ac.abort();
125
+ const p = createQvacProvider({ completion: fn as any, cancel: cancel as any, getModelId: () => 'm1' });
126
+ const out = await p.runTurn({ messages: [{ role: 'user', content: 'x' }], tools: [], signal: ac.signal });
127
+ expect(cancel).toHaveBeenCalledWith({ requestId: 'req-a' });
128
+ expect(out.inference?.status).toBe('cancelled');
129
+ expect(out.toolCalls).toEqual([]);
130
+ });
131
+
132
+ it('derives token counts from the SDK generatedTokens stat', async () => {
133
+ const { fn } = fakeCompletion({
134
+ contentText: 'ok', toolCalls: [], raw: { fullText: 'ok' },
135
+ stats: { promptTokens: 100, generatedTokens: 20, tokensPerSecond: 12, backendDevice: 'gpu' },
136
+ });
137
+ const p = createQvacProvider({ completion: fn as any, cancel: noopCancel, getModelId: () => 'm1' });
138
+ const out = await p.runTurn({ messages: [{ role: 'user', content: 'x' }], tools: [] });
139
+ expect(out.inference).toMatchObject({ promptTokens: 100, totalTokens: 120, completionTokens: 20, backendDevice: 'gpu' });
140
+ });
141
+
104
142
  it('streams visible content tokens to onToken', async () => {
105
143
  const { fn } = fakeCompletion(
106
144
  { contentText: 'Hi there', toolCalls: [], raw: { fullText: 'Hi there' } },
@@ -20,6 +20,7 @@ import type * as QvacSdk from '@qvac/sdk';
20
20
  import type { InferenceMetrics, LLMProvider, TurnInput, TurnOutput } from '../providers/types.js';
21
21
  import type { QvacTurnStats } from './parse.js';
22
22
  import { consumeRun } from './stream.js';
23
+ import { toQvacTools } from './tools.js';
23
24
 
24
25
  type CompletionFn = typeof QvacSdk.completion;
25
26
  type CancelFn = typeof QvacSdk.cancel;
@@ -82,19 +83,11 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
82
83
  ? [{ role: 'system', content: input.system }, ...input.messages]
83
84
  : input.messages;
84
85
 
85
- // Tools are forwarded by schema only (name/description/parameters). We
86
- // carry `parameters` through verbatim (Zod for in-process tools, JSON
87
- // Schema for MCP) — the model only needs the shape to pick a call; the
88
- // Engine validates + executes.
89
- const tools = input.tools.length
90
- ? input.tools.map((t) => ({
91
- name: t.name,
92
- description: t.description,
93
- parameters: t.parameters,
94
- }))
95
- : undefined;
96
-
97
- // QVAC 0.13 nests sampling under `generationParams`; top-level
86
+ // Tools are forwarded by schema only — the Engine validates + executes.
87
+ // JSON-Schema tools are normalised to the SDK's Tool shape (see tools.ts).
88
+ const tools = toQvacTools(input.tools);
89
+
90
+ // QVAC (0.13+) nests sampling under `generationParams`; top-level
98
91
  // `temperature`/`max_tokens` (as older rate code passed) are dropped by
99
92
  // validation, so the cap silently no-op'd. Build it here, and only send it
100
93
  // when a value is set so a host that passes neither keeps SDK defaults.
@@ -155,8 +148,11 @@ export function createQvacProvider(options: QvacProviderOptions): LLMProvider {
155
148
  // instead of an empty bubble so the agentic loop ends cleanly.
156
149
  const text =
157
150
  result.text || (result.thinkingBudgetExceeded ? THINKING_BUDGET_FALLBACK : result.text);
158
- const totalTokens = result.stats?.totalTokens;
159
151
  const promptTokens = result.stats?.promptTokens;
152
+ const generated = result.stats?.generatedTokens;
153
+ const totalTokens =
154
+ result.stats?.totalTokens ??
155
+ (typeof generated === 'number' && typeof promptTokens === 'number' ? promptTokens + generated : undefined);
160
156
  const inference: InferenceMetrics = {
161
157
  requestId: result.requestId,
162
158
  durationMs: result.timing.durationMs,
@@ -112,4 +112,32 @@ describe('consumeRun', () => {
112
112
  );
113
113
  expect(out.timing).toEqual({ ttftMs: 45, durationMs: 90 });
114
114
  });
115
+
116
+ it('folds a rejected final (InferenceCancelledError) into a cancelled turn', async () => {
117
+ class InferenceCancelledError extends Error {
118
+ constructor(readonly requestId: string, readonly partial: { text?: string }) {
119
+ super('cancelled');
120
+ }
121
+ }
122
+ const run: CompletionRunLike = {
123
+ requestId: 'req-c',
124
+ events: (async function* () {
125
+ yield { type: 'contentDelta', text: 'Partial' };
126
+ })(),
127
+ final: Promise.reject(new InferenceCancelledError('req-c', { text: 'Partial' })),
128
+ };
129
+ const out = await consumeRun(run);
130
+ expect(out.stopReason).toBe('cancelled');
131
+ expect(out.text).toBe('Partial');
132
+ expect(out.toolCalls).toEqual([]);
133
+ });
134
+
135
+ it('re-throws a rejected final that is not a cancellation', async () => {
136
+ const run: CompletionRunLike = {
137
+ requestId: 'req-e',
138
+ events: (async function* () {})(),
139
+ final: Promise.reject(new Error('boom')),
140
+ };
141
+ await expect(consumeRun(run)).rejects.toThrow('boom');
142
+ });
115
143
  });