@kaleidorg/mind 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/bitrefill/contract.d.ts +60 -0
  2. package/dist/bitrefill/contract.d.ts.map +1 -0
  3. package/dist/bitrefill/contract.js +119 -0
  4. package/dist/bitrefill/contract.js.map +1 -0
  5. package/dist/context/compress.d.ts +65 -0
  6. package/dist/context/compress.d.ts.map +1 -0
  7. package/dist/context/compress.js +181 -0
  8. package/dist/context/compress.js.map +1 -0
  9. package/dist/engine.d.ts +20 -0
  10. package/dist/engine.d.ts.map +1 -1
  11. package/dist/engine.js +23 -4
  12. package/dist/engine.js.map +1 -1
  13. package/dist/evidence.d.ts +62 -0
  14. package/dist/evidence.d.ts.map +1 -0
  15. package/dist/evidence.js +47 -0
  16. package/dist/evidence.js.map +1 -0
  17. package/dist/flashnet/contract.d.ts +56 -0
  18. package/dist/flashnet/contract.d.ts.map +1 -0
  19. package/dist/flashnet/contract.js +100 -0
  20. package/dist/flashnet/contract.js.map +1 -0
  21. package/dist/funnel.d.ts +11 -0
  22. package/dist/funnel.d.ts.map +1 -1
  23. package/dist/funnel.js +50 -7
  24. package/dist/funnel.js.map +1 -1
  25. package/dist/index.d.ts +10 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +7 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/kaleidoswap/contract.js +1 -1
  30. package/dist/kaleidoswap/contract.js.map +1 -1
  31. package/dist/knowledge/bitcoin-copilot.d.ts.map +1 -1
  32. package/dist/knowledge/bitcoin-copilot.js +83 -0
  33. package/dist/knowledge/bitcoin-copilot.js.map +1 -1
  34. package/dist/providers/types.d.ts +17 -0
  35. package/dist/providers/types.d.ts.map +1 -1
  36. package/dist/qvac/provider.d.ts.map +1 -1
  37. package/dist/qvac/provider.js +23 -0
  38. package/dist/qvac/provider.js.map +1 -1
  39. package/dist/qvac/stream.d.ts +6 -0
  40. package/dist/qvac/stream.d.ts.map +1 -1
  41. package/dist/qvac/stream.js +12 -0
  42. package/dist/qvac/stream.js.map +1 -1
  43. package/dist/recipe/flashnet-swap.d.ts +35 -0
  44. package/dist/recipe/flashnet-swap.d.ts.map +1 -0
  45. package/dist/recipe/flashnet-swap.js +239 -0
  46. package/dist/recipe/flashnet-swap.js.map +1 -0
  47. package/dist/recipe/kaleidoswap-atomic.d.ts.map +1 -1
  48. package/dist/recipe/kaleidoswap-atomic.js +66 -32
  49. package/dist/recipe/kaleidoswap-atomic.js.map +1 -1
  50. package/dist/recipe/kaleidoswap-channel-order.d.ts.map +1 -1
  51. package/dist/recipe/kaleidoswap-channel-order.js +31 -10
  52. package/dist/recipe/kaleidoswap-channel-order.js.map +1 -1
  53. package/dist/recipe/kaleidoswap-price.d.ts.map +1 -1
  54. package/dist/recipe/kaleidoswap-price.js +7 -1
  55. package/dist/recipe/kaleidoswap-price.js.map +1 -1
  56. package/dist/recipe/runner.d.ts.map +1 -1
  57. package/dist/recipe/runner.js +5 -3
  58. package/dist/recipe/runner.js.map +1 -1
  59. package/dist/recipe/swap.d.ts.map +1 -1
  60. package/dist/recipe/swap.js +14 -1
  61. package/dist/recipe/swap.js.map +1 -1
  62. package/dist/wallet/confirm.d.ts.map +1 -1
  63. package/dist/wallet/confirm.js +1 -0
  64. package/dist/wallet/confirm.js.map +1 -1
  65. package/dist/wallet/contract.d.ts.map +1 -1
  66. package/dist/wallet/contract.js +20 -4
  67. package/dist/wallet/contract.js.map +1 -1
  68. package/package.json +4 -4
  69. package/skills/bitrefill/SKILL.md +152 -52
  70. package/skills/flashnet-swaps/SKILL.md +158 -0
  71. package/skills/kaleido-lsps/SKILL.md +25 -8
  72. package/skills/kaleido-trading/SKILL.md +36 -12
  73. package/skills/merchant-finder/SKILL.md +1 -1
  74. package/skills/rgb-lightning-node/SKILL.md +35 -8
  75. package/skills/spark-wallet/SKILL.md +235 -0
  76. package/skills/wallet-assistant/SKILL.md +2 -2
  77. package/src/bitrefill/contract.test.ts +89 -0
  78. package/src/bitrefill/contract.ts +190 -0
  79. package/src/context/compress.test.ts +120 -0
  80. package/src/context/compress.ts +230 -0
  81. package/src/engine.test.ts +34 -0
  82. package/src/engine.ts +35 -4
  83. package/src/evidence.test.ts +80 -0
  84. package/src/evidence.ts +114 -0
  85. package/src/flashnet/contract.test.ts +101 -0
  86. package/src/flashnet/contract.ts +164 -0
  87. package/src/funnel.mind.test.ts +3 -5
  88. package/src/funnel.ts +59 -8
  89. package/src/index.ts +51 -1
  90. package/src/kaleidoswap/contract.ts +1 -1
  91. package/src/knowledge/bitcoin-copilot.ts +94 -0
  92. package/src/providers/types.ts +18 -0
  93. package/src/qvac/provider.ts +25 -1
  94. package/src/qvac/stream.test.ts +11 -0
  95. package/src/qvac/stream.ts +16 -0
  96. package/src/recipe/flashnet-swap.test.ts +114 -0
  97. package/src/recipe/flashnet-swap.ts +266 -0
  98. package/src/recipe/kaleidoswap-atomic.test.ts +52 -6
  99. package/src/recipe/kaleidoswap-atomic.ts +71 -34
  100. package/src/recipe/kaleidoswap-channel-order.test.ts +38 -0
  101. package/src/recipe/kaleidoswap-channel-order.ts +27 -9
  102. package/src/recipe/kaleidoswap-price.ts +7 -1
  103. package/src/recipe/recipe.test.ts +5 -0
  104. package/src/recipe/runner.ts +5 -3
  105. package/src/recipe/swap.ts +16 -1
  106. package/src/wallet/confirm.test.ts +8 -0
  107. package/src/wallet/confirm.ts +1 -0
  108. package/src/wallet/contract.test.ts +10 -0
  109. package/src/wallet/contract.ts +26 -4
@@ -2,7 +2,7 @@
2
2
  name: merchant-finder
3
3
  description: "Find Bitcoin-accepting merchants near the user using live BTC Map data and the device's real location. Triggers when the user asks where to spend Bitcoin, buy pizza/food with sats or bitcoin, eat at restaurants/cafes paying with sats, for a shop, store, restaurant, cafe, bar, or ATM that accepts Bitcoin, or for merchants nearby or in a city like turin."
4
4
  tools: find_merchant_locations, search_knowledge
5
- triggers: merchant, merchants, shop, shops, store, stores, restaurant, restaurants, cafe, cafes, bar, bars, atm, atms, accept, accepts, accepting, nearby, near me, around, place, places, spend, find, pizza, pizz, food, coffee, eat, dinner, lunch, buy, bitcoin map, btcmap
5
+ triggers: merchant, merchants, shop, shops, store, stores, restaurant, restaurants, cafe, cafes, bar, bars, atm, atms, accept, accepts, accepting, nearby, near me, around me, where can i spend, pizza, pizz, food, coffee, eat, dinner, lunch, bitcoin map, btcmap
6
6
  metadata:
7
7
  author: kaleidoswap
8
8
  version: "0.3.0"
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: rgb-lightning-node
3
- description: "Drive the user's local RGB Lightning Node (RLN) — read its pubkey/status, whitelist a swap, or create Lightning/RGB receive invoices. Triggers when the user asks about the node, 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_open_channel, rln_close_channel, rln_connect_peer, rln_get_channel_id, rln_atomic_taker, rln_list_payments, rln_create_ln_invoice, rln_create_rgb_invoice
5
- triggers: node, nodeinfo, pubkey, peer, channels, 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, 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
6
6
  metadata:
7
7
  author: kaleidoswap
8
8
  version: "0.1.0"
@@ -47,10 +47,37 @@ Call this when:
47
47
  `kaleidoswap_atomic_execute`.
48
48
 
49
49
  **Do NOT** use this tool's `local_balance_sat` to answer a question about
50
- **inbound liquidity / receive capacity** — that is a different quantity
51
- (the peer's side of each channel, not yours). For "how much can I receive?",
52
- the LSPS skill answers what's available to BUY (`lsp_get_info`); the current
53
- remote-balance breakdown isn't exposed by this skill's tools.
50
+ **inbound liquidity / receive capacity** — that is a different quantity (the
51
+ peer's side of each channel). For per-channel inbound/outbound and total
52
+ capacity, use `rln_list_channels` (below), NOT this tool.
53
+
54
+ ### `rln_list_channels` — no args
55
+ Returns `{ channels: [...], count }`. Each channel carries:
56
+ - `channel_id`, `peer_alias`, `status`, `ready`, `is_usable`
57
+ - `capacity_sat` — total channel size.
58
+ - `outbound_sat` — what YOU can send (your local balance).
59
+ - `inbound_sat` — what you can RECEIVE on this channel (the peer's side).
60
+ - `asset_id`, `asset_local_amount`, `asset_remote_amount` — for RGB asset
61
+ channels: the asset and how much is on each side.
62
+
63
+ Call this when the user asks to **list channels**, asks about **per-channel
64
+ capacity**, **inbound/receive capacity**, or wants to **verify a channel they
65
+ just bought** opened with the requested size. Report each channel as one line:
66
+ `capacity_sat total — outbound_sat / inbound_sat (asset if present), status`.
67
+
68
+ When verifying a freshly-bought channel: a channel order opens
69
+ ASYNCHRONOUSLY (seconds to minutes after payment). If the new channel isn't
70
+ listed yet, say it's still opening and suggest checking again — don't claim
71
+ failure.
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".
77
+
78
+ ### `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.
54
81
 
55
82
  ### `rln_atomic_taker` — { swapstring } — 🔒 confirm-gated
56
83
  Tell the node "I accept this swap." Args: the `swapstring` returned by
@@ -92,7 +119,7 @@ A user-driven swap on KaleidoSwap is a two-service flow. Keep them straight:
92
119
  | Pubkey | **node** | `rln_get_node_info` (read `pubkey`) |
93
120
  | Whitelist | **node** | `rln_atomic_taker` (pass the swapstring) |
94
121
  | Execute | maker | `kaleidoswap_atomic_execute` (needs swapstring + taker_pubkey + payment_hash) |
95
- | Status | maker | `kaleidoswap_atomic_status` |
122
+ | Status | maker | `kaleidoswap_atomic_status` (pass atomic_id or payment_hash from the atomic recipe summary or prior init result; see "remember" line in history) |
96
123
 
97
124
  The node's two contributions to the swap are the **pubkey** and the
98
125
  **whitelist ack** — nothing more. Don't reach for `/makerinit` or
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: spark-wallet
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
+ 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
+ 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
+ metadata:
7
+ author: kaleidoswap
8
+ version: "1.0.0"
9
+ layer: spark
10
+ ---
11
+
12
+ # Spark wallet
13
+
14
+ Spark is one of the user's connected BTC layers (alongside RLN/RGB and Arkade
15
+ on some hosts). It speaks Lightning natively — receive via `spark_create_invoice`,
16
+ send via `spark_pay_invoice` (BOLT11) or `spark_send` (on-chain). All numbers
17
+ are in **satoshis** unless stated otherwise.
18
+
19
+ ## Three "addresses", three different tools (read this carefully)
20
+
21
+ The word "address" can mean three completely different things on Spark.
22
+ Each has a DIFFERENT tool, a DIFFERENT shape, and a DIFFERENT use. If you
23
+ return the wrong one, the user loses money on a bad deposit. Pick by what
24
+ the user is trying to DO, not just by the word "address":
25
+
26
+ | User intent | Tool | What you get | Looks like |
27
+ |---|---|---|---|
28
+ | Receive a **Spark-to-Spark** transfer (off-chain, within Spark) | `spark_get_address` | Spark identity / pubkey | `sparkrt1…` / `spark1…` |
29
+ | Deposit **L1 Bitcoin** into Spark from the on-chain world | `spark_get_onchain_address` | Real Bitcoin on-chain address | `bc1…` / `tb1…` / `bcrt1…` |
30
+ | Receive over **Lightning** (BOLT11) | `spark_create_invoice` | A Lightning invoice string | `lnbc…` / `lntb…` / `lnbcrt…` |
31
+
32
+ **Disambiguation by phrasing — examples:**
33
+
34
+ - "give me my **on-chain address**" / "**bitcoin address** to fund Spark"
35
+ / "**deposit address**" / "where do I send BTC from my hardware wallet
36
+ / mainnet" → `spark_get_onchain_address`. Result starts with bc1/tb1/
37
+ bcrt1 — verify before replying.
38
+ - "my **Spark address**" / "give me a **Spark address**" / "where do I
39
+ receive a **Spark transfer**" → `spark_get_address`. Result starts with
40
+ spark1/sparkrt1. **DO NOT label this as an on-chain address — it is
41
+ off-chain.**
42
+ - "give me an **invoice for N sats**" / "an **LN invoice**" / "**pay me**
43
+ N sats" → `spark_create_invoice({amount_sats: N})`. Result is a `lnbc…`
44
+ string.
45
+
46
+ **Critical: when the user says "on-chain", they mean L1 Bitcoin.** Never
47
+ return a `sparkrt1…` and call it "on-chain". If you return a
48
+ `spark_get_address` result, you MUST describe it as a Spark (off-chain)
49
+ address — never as an on-chain or Bitcoin address. If you return a
50
+ `spark_get_onchain_address` result, you can call it an on-chain Bitcoin
51
+ deposit address.
52
+
53
+ A useful sanity check before you send the reply: glance at the
54
+ `address` string's prefix. If it begins with `spark`, it's the off-chain
55
+ Spark identity. If it begins with `bc1`/`tb1`/`bcrt1`, it's an on-chain
56
+ BTC address. If it begins with `lnbc`/`lntb`/`lnbcrt`, it's a Lightning
57
+ invoice. Whatever you call it in your reply MUST match its actual prefix.
58
+
59
+ ## What Spark holds (and what it does NOT)
60
+
61
+ This is the single most important thing to get right when the user asks
62
+ about "assets on Spark" or "what can I trade on Spark".
63
+
64
+ **Spark holds:**
65
+ - **BTC** (sats) — Spark's native on-chain-pegged BTC.
66
+ - **Spark-native tokens** — e.g. **USDB**. These are tokens issued on the
67
+ Spark protocol itself, traded on **Flashnet** (Spark-native AMM).
68
+
69
+ **Spark does NOT hold:**
70
+ - **RGB assets** (USDT, XAUT, …). RGB assets are a DIFFERENT protocol on
71
+ Bitcoin/Lightning — they live on the user's **RLN** (RGB Lightning Node),
72
+ NOT Spark. A USDT balance, if the user has one, is on RLN — never on
73
+ Spark.
74
+ - Tokens from any other chain (Ethereum USDT, Tron USDT, Solana, …). The
75
+ wallet does not custody those at all.
76
+
77
+ **Asset → which skill / venue:**
78
+
79
+ | Asset | Layer | Swap venue | Skill |
80
+ |---|---|---|---|
81
+ | BTC / sats | Spark / RLN / on-chain | Either (depends on direction) | spark-wallet (Spark side), wallet-assistant |
82
+ | USDB (and other Spark tokens) | Spark | **Flashnet** (AMM) | **flashnet-swaps** |
83
+ | USDT, XAUT (RGB assets) | RLN/RGB | **KaleidoSwap maker** | **kaleido-trading** |
84
+
85
+ If the user asks "what can I trade on Spark?", the correct answer lists
86
+ Spark-native tokens (BTC + USDB and anything else
87
+ `flashnet_list_pools` shows). **Never** answer USDT/XAUT for Spark.
88
+ Conversely, if asked "what can I trade on KaleidoSwap?", that's RGB
89
+ assets (USDT, XAUT) — **not** USDB.
90
+
91
+ When in doubt about what's actually tradeable, the source of truth is the
92
+ TOOL, not your training data — call `flashnet_list_pools` (Spark side) or
93
+ `kaleidoswap_get_assets` (RGB side) and report what comes back.
94
+
95
+ ## Critical rules (read first)
96
+
97
+ 1. **Always re-fetch volatile state — every turn, every time.** Balance,
98
+ address, invoice status, and any number that can change MUST come from a
99
+ tool call THIS turn. Do NOT reuse a value from a previous turn, even if
100
+ the user asked the exact same question 30 seconds ago. The user wouldn't
101
+ ask twice if they didn't want a fresh check.
102
+
103
+ - "what's my balance?" → ALWAYS call `spark_get_balance`. Yes, even if
104
+ you just called it. The whole point of asking again is to get a new
105
+ reading.
106
+ - "give me my address" → ALWAYS call `spark_get_address`. Spark may
107
+ rotate or surface a fresh address.
108
+ - "did my invoice settle?" → ALWAYS re-fetch the invoice/order status.
109
+
110
+ The ONLY thing you can reuse from history is the user's own input
111
+ (e.g. "the invoice I just made" → look up its id in history and call
112
+ the status tool on it).
113
+
114
+ 2. **Never invent a balance, invoice or address.** Every BTC number, BOLT11
115
+ string, and address in your reply MUST come from a Spark tool result
116
+ returned in the CURRENT turn — never guessed, never quoted from memory.
117
+
118
+ 3. **Read the tool result exactly.** `spark_get_balance` returns
119
+ `{ total, layer, network, connected }`. `connected: true` means the
120
+ Spark wallet IS active and reachable; `total: 0` with `connected: true`
121
+ simply means the user has no sats yet (perfectly normal for a fresh
122
+ wallet on regtest). Do NOT say "your wallet isn't connected" unless
123
+ `connected: false` or the tool threw an error. Say "your Spark wallet
124
+ is connected but empty — fund it with `spark_get_address`" when
125
+ `total: 0, connected: true`.
126
+
127
+ 4. **Choose the right send tool by destination shape.**
128
+ - Starts with `lnbc…` / `lntb…` / `lnbcrt…` → BOLT11 Lightning invoice → use
129
+ **`spark_pay_invoice`**.
130
+ - Starts with `bc1…` / `tb1…` / `bcrt1…` → on-chain Bitcoin address → use
131
+ **`spark_send`**.
132
+ - Looks like `name@domain` (a Lightning address) → not a Spark target;
133
+ either ask the host to resolve it first or use the cross-cutting
134
+ `send_payment` router. Spark itself doesn't dereference LNURL.
135
+
136
+ 5. **BOLT11 invoices encode their amount.** Don't pass `amount_sats` to
137
+ `spark_pay_invoice` unless the invoice is amount-less. Re-stating the
138
+ amount can produce silently-wrong sends on amount-less invoices.
139
+
140
+ 6. **Confirm before spending.** `spark_pay_invoice` and `spark_send` are
141
+ confirmation-gated by the contract — the host fires the gate
142
+ automatically. Before the call, summarize in one line:
143
+ `Paying 12,540 sats to lnbc12540n… from Spark. Confirm?`
144
+
145
+ 7. **Spark genuinely unavailable = stop, don't guess.** If the tool
146
+ THROWS with "Your SPARK wallet isn't connected yet" (an actual error,
147
+ not a 0 balance), say so plainly and stop — don't substitute RLN or
148
+ Arkade silently. The user may genuinely want Spark.
149
+
150
+ 8. **Never refuse an action a listed tool performs.** If a tool in your
151
+ set does what the user asked, CALL IT — do not reason about whether
152
+ "get" means "create", whether the wording is an exact match, or
153
+ whether some other tool would be "more correct". The user asking to
154
+ "create an address" when you have `spark_get_address` means: call
155
+ `spark_get_address`. Refusing to use an available tool is always wrong.
156
+
157
+ ## How to call the tools
158
+
159
+ ### Reads
160
+
161
+ - **`spark_get_balance({})`** — current Spark balances. Returns the BTC
162
+ balance (`total` in sats) AND every Spark-native **token** the wallet
163
+ holds (`tokens[]` — each with `address`, `balance`, optional `symbol`
164
+ and `decimals`). When the user asks "what do I have on Spark" or
165
+ "what's my Spark balance", surface BOTH the sats AND any non-zero
166
+ token balances; an answer that only mentions BTC when tokens are
167
+ present is incomplete.
168
+ - `flashnet_get_balance` returns the same numbers (it's the AMM-client
169
+ view of the same wallet) — there's no need to call it just to learn
170
+ the token balance.
171
+ - **`spark_get_address({})`** — the user's **Spark identity**
172
+ (`sparkrt1…`/`spark1…`), an OFF-CHAIN Spark-to-Spark receive target.
173
+ Reusable — getting and creating are the same operation, so the right
174
+ response to "create a Spark address" is ALSO this tool. NEVER call its
175
+ result an on-chain address or a Bitcoin address; it is neither. NEVER
176
+ reply "I cannot create an address" — this tool IS how you create one.
177
+ - **`spark_get_onchain_address({})`** — a real Bitcoin **on-chain
178
+ deposit** address (`bc1…`/`tb1…`/`bcrt1…`) for funding Spark from L1.
179
+ Use whenever the user says "on-chain", "bitcoin address", "deposit
180
+ address", "fund Spark", or otherwise indicates they want to send L1
181
+ BTC. NEVER substitute `spark_get_address` here.
182
+
183
+ ### Receive
184
+
185
+ - **`spark_create_invoice({ amount_sats? })`** — Spark Lightning invoice.
186
+ - Omit `amount_sats` for an "any amount" invoice.
187
+ - Returns `{ invoice: "lnbc…", … }`.
188
+ - This is the tool for "give me an invoice for N sats", NOT for any
189
+ "address" ask.
190
+
191
+ ### Send — pick by destination
192
+
193
+ - **`spark_pay_invoice({ invoice, amount_sats? })`** — pay a BOLT11 invoice.
194
+ - The model's default Lightning spend tool. Use this for invoices from
195
+ Bitrefill, a contact's invoice paste, or any `ln…` string.
196
+ - Pass `amount_sats` ONLY when the invoice is amount-less (a 0-amount
197
+ invoice). For ordinary amount-bound invoices, OMIT it.
198
+ - **`spark_send({ amount_sats, to })`** — on-chain Bitcoin send.
199
+ - Use only when `to` is an on-chain address (`bc1…`). Never pass a BOLT11
200
+ invoice here.
201
+
202
+ ### Helpers
203
+
204
+ - **`get_price({ fiat? })`** / **`fiat_to_sats({ amount, currency })`** —
205
+ for "how many sats is €10" style sub-questions before a Spark spend.
206
+
207
+ ## Cross-skill flow with Bitrefill
208
+
209
+ When the user wants to buy something from Bitrefill and pay with Spark, the
210
+ typical chain is:
211
+
212
+ 1. Call `bitrefill_search` / `bitrefill_get_product` to confirm the product +
213
+ the right `package_id` (see the `bitrefill` skill).
214
+ 2. Call `bitrefill_create_invoice({ products, payment_method: "lightning",
215
+ refund_address: <a Spark or on-chain address> })` — Bitrefill returns a
216
+ BOLT11 invoice on the response under `payment.lightning_invoice` (or
217
+ similar — relay whatever the host surfaces).
218
+ 3. **Pay with Spark**: `spark_pay_invoice({ invoice: <that BOLT11> })`. One
219
+ confirmation gate; Spark settles the invoice in seconds.
220
+ 4. Poll `bitrefill_get_invoice` until `status:"complete"`, then
221
+ `bitrefill_get_order` for the redemption code.
222
+
223
+ If the user pre-funded a Bitrefill account, prefer
224
+ `payment_method:"balance"` instead — no Spark spend, instant settlement. Use
225
+ the Lightning path when the user explicitly says "pay with Spark/Lightning"
226
+ or has no Bitrefill balance.
227
+
228
+ ## Reply style
229
+
230
+ - One short sentence per fact ("Spark holds 124,500 sats.").
231
+ - For invoices: show the invoice on its own line so the user can copy it.
232
+ - For sends: one-line pre-spend summary (amount + destination + "from
233
+ Spark"), then the result.
234
+ - When `spark_get_balance` says zero and the user asked to spend, stop and
235
+ say so — don't try to source funds from another layer silently.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: wallet-assistant
3
3
  description: Everyday wallet tasks on this phone — check the BTC/asset balance, create an invoice to receive, send a payment, look up a contact, or quote a swap (e.g. "how many sats is 10 USDT?"). Triggers when the user asks about their balance, wants to receive or send money, pay an invoice, pay a contact, or convert between BTC and supported assets.
4
- tools: get_balances, rln_get_balances, wdk_get_balances, spark_get_balance, rln_get_asset_balance, wdk_get_asset_balance, rln_list_assets, wdk_list_assets, rln_get_address, wdk_get_address, spark_get_address, resolve_contact, send_payment, rln_send_btc, wdk_send_btc, rln_send_asset, wdk_send_asset, spark_send_sats, rln_pay_invoice, wdk_pay_invoice, spark_pay_lightning_invoice, rln_create_ln_invoice, wdk_create_ln_invoice, spark_create_lightning_invoice, rln_create_rgb_invoice, wdk_create_rgb_invoice, rln_list_payments, wdk_list_payments, kaleidoswap_get_quote
5
- triggers: balance, pay, send, receive, address, invoice, transactions, contact, funds, money, sats
4
+ tools: get_balances, rln_get_balances, wdk_get_balances, spark_get_balance, rln_get_asset_balance, wdk_get_asset_balance, rln_list_assets, wdk_list_assets, rln_get_address, wdk_get_address, spark_get_address, spark_get_onchain_address, resolve_contact, send_payment, rln_send_btc, wdk_send_btc, rln_send_asset, wdk_send_asset, spark_send, spark_send_sats, rln_pay_invoice, wdk_pay_invoice, spark_pay_invoice, spark_pay_lightning_invoice, rln_create_ln_invoice, wdk_create_ln_invoice, spark_create_invoice, spark_create_lightning_invoice, rln_create_rgb_invoice, wdk_create_rgb_invoice, rln_list_payments, wdk_list_payments, kaleidoswap_get_quote
5
+ triggers: balance, pay, send, receive, address, invoice, transactions, contact, funds, money
6
6
  ---
7
7
 
8
8
  # Wallet assistant
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ BITREFILL_TOOLS,
4
+ BITREFILL_SPEND_TOOLS,
5
+ isBitrefillSpendTool,
6
+ getBitrefillTool,
7
+ bindBitrefillTools,
8
+ type BitrefillHandler,
9
+ } from './contract.js';
10
+
11
+ describe('BITREFILL_TOOLS — shape invariants', () => {
12
+ it('exposes the expected tool names in order', () => {
13
+ expect(BITREFILL_TOOLS.map((t) => t.name)).toEqual([
14
+ 'bitrefill_search',
15
+ 'bitrefill_get_product',
16
+ 'bitrefill_get_balance',
17
+ 'bitrefill_create_invoice',
18
+ 'bitrefill_get_invoice',
19
+ 'bitrefill_get_order',
20
+ ]);
21
+ });
22
+
23
+ it('every tool has an object parameters schema', () => {
24
+ for (const t of BITREFILL_TOOLS) {
25
+ expect((t.parameters as any)?.type).toBe('object');
26
+ }
27
+ });
28
+
29
+ it('aligns spend ↔ requiresConfirmation', () => {
30
+ for (const t of BITREFILL_TOOLS) {
31
+ expect(!!t.spend).toBe(!!t.requiresConfirmation);
32
+ }
33
+ });
34
+
35
+ it('marks only bitrefill_create_invoice as spend', () => {
36
+ expect([...BITREFILL_SPEND_TOOLS]).toEqual(['bitrefill_create_invoice']);
37
+ expect(isBitrefillSpendTool('bitrefill_create_invoice')).toBe(true);
38
+ expect(isBitrefillSpendTool('bitrefill_search')).toBe(false);
39
+ expect(isBitrefillSpendTool('bitrefill_get_balance')).toBe(false);
40
+ });
41
+
42
+ it('getBitrefillTool returns by name', () => {
43
+ expect(getBitrefillTool('bitrefill_get_product')?.name).toBe('bitrefill_get_product');
44
+ expect(getBitrefillTool('nope')).toBeUndefined();
45
+ });
46
+
47
+ it('create_invoice requires products + payment_method', () => {
48
+ const def = getBitrefillTool('bitrefill_create_invoice')!;
49
+ expect((def.parameters as any).required).toEqual(['products', 'payment_method']);
50
+ });
51
+ });
52
+
53
+ describe('bindBitrefillTools', () => {
54
+ const echoHandlers = (): Record<string, BitrefillHandler> => ({
55
+ bitrefill_search: async (a) => ({ ok: true, t: 'search', args: a }),
56
+ bitrefill_get_product: async (a) => ({ ok: true, t: 'get_product', args: a }),
57
+ bitrefill_get_balance: async () => ({ balance: 100, currency: 'USD' }),
58
+ bitrefill_create_invoice: async (a) => ({ ok: true, t: 'create_invoice', args: a }),
59
+ bitrefill_get_invoice: async (a) => ({ ok: true, t: 'get_invoice', args: a }),
60
+ bitrefill_get_order: async (a) => ({ ok: true, t: 'get_order', args: a }),
61
+ });
62
+
63
+ it('binds every tool and preserves the spend gate', () => {
64
+ const src = bindBitrefillTools(echoHandlers());
65
+ expect(src.listTools().length).toBe(6);
66
+ const create = src.listTools().find((t) => t.name === 'bitrefill_create_invoice');
67
+ expect(create?.requiresConfirmation).toBe(true);
68
+ const search = src.listTools().find((t) => t.name === 'bitrefill_search');
69
+ expect(search?.requiresConfirmation).toBeFalsy();
70
+ });
71
+
72
+ it('dispatches with args', async () => {
73
+ const src = bindBitrefillTools(echoHandlers());
74
+ const r = await src.execute('bitrefill_search', { query: 'amazon', country: 'US' });
75
+ expect(r).toMatchObject({ ok: true, t: 'search', args: { query: 'amazon', country: 'US' } });
76
+ });
77
+
78
+ it('throws on a missing handler unless allowMissing', () => {
79
+ const partial = { bitrefill_search: echoHandlers().bitrefill_search };
80
+ expect(() => bindBitrefillTools(partial)).toThrow(/no handler/);
81
+ const src = bindBitrefillTools(partial, { allowMissing: true });
82
+ expect(src.listTools().map((t) => t.name)).toEqual(['bitrefill_search']);
83
+ });
84
+
85
+ it('uses opts.id for the ToolSource id', () => {
86
+ const src = bindBitrefillTools(echoHandlers(), { id: 'bitrefill-personal' });
87
+ expect(src.id).toBe('bitrefill-personal');
88
+ });
89
+ });
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Canonical Bitrefill tool contract — gift cards, mobile top-ups, eSIMs.
3
+ *
4
+ * Same pattern as the LSPS1 contract: the tool *names + schemas* live here so
5
+ * every host (CLI REST adapter, desktop MCP, mobile WDK adapter) exposes the
6
+ * exact same surface to the agent. Only the transport differs.
7
+ *
8
+ * - CLI / desktop server → REST against `https://api.bitrefill.com/v2`
9
+ * (see `apps/cli/src/bitrefillTools.ts`).
10
+ * - Desktop sidecar → the remote MCP at `api.bitrefill.com/mcp` already
11
+ * exposes equivalent tools under different names; a binder there can
12
+ * rename them to this contract for parity.
13
+ *
14
+ * `bitrefill_create_invoice` is the spend — confirmation-gated by the contract.
15
+ * Everything else is read-only (search, product details, balance, invoice/order
16
+ * status). Invoice creation supports `payment_method:"balance"` (instant, pulls
17
+ * from pre-funded account) or `lightning|bitcoin|usdc_base|...` (the response
18
+ * carries a payment URI/invoice; the user pays out-of-band, then the order is
19
+ * fulfilled).
20
+ *
21
+ * Pure data — no deps, no fetch, RN-safe.
22
+ */
23
+
24
+ import type { ToolDef } from '../types.js';
25
+ import { InProcessToolSource } from '../tools/in-process.js';
26
+ import type { InProcessTool } from '../tools/in-process.js';
27
+
28
+ export interface BitrefillToolDef extends ToolDef {
29
+ /** Moves real money → confirmation-gated. */
30
+ spend?: boolean;
31
+ }
32
+
33
+ type Props = Record<
34
+ string,
35
+ { type: string; description?: string; enum?: string[]; items?: unknown }
36
+ >;
37
+
38
+ function t(
39
+ name: string,
40
+ description: string,
41
+ properties: Props = {},
42
+ required: string[] = [],
43
+ spend = false,
44
+ ): BitrefillToolDef {
45
+ return {
46
+ name,
47
+ description,
48
+ spend,
49
+ requiresConfirmation: spend,
50
+ parameters: { type: 'object', properties, required },
51
+ };
52
+ }
53
+
54
+ /**
55
+ * The canonical Bitrefill tool list. Each host's binder translates these
56
+ * args into the Bitrefill REST body (CLI) or MCP/CLI call (other hosts).
57
+ */
58
+ export const BITREFILL_TOOLS: BitrefillToolDef[] = [
59
+ t(
60
+ 'bitrefill_search',
61
+ "Search Bitrefill's product catalog by keyword (brand, country, type). Returns up to ~20 matches with `id`, `name`, `country`, `category` and `denominations`. The model picks the right product id and then calls `bitrefill_get_product` for the package list.",
62
+ {
63
+ query: { type: 'string', description: 'Search keyword. e.g. "amazon", "steam", "vodafone uk", "esim europe".' },
64
+ country: { type: 'string', description: 'OPTIONAL — ISO country code to scope results (e.g. "US", "GB", "DE"). Many brands are country-specific.' },
65
+ limit: { type: 'number', description: 'OPTIONAL — max results (1–25, default 10).' },
66
+ },
67
+ ['query'],
68
+ ),
69
+
70
+ t(
71
+ 'bitrefill_get_product',
72
+ "Get full details for one product, including its `packages` array (each package = a denomination with `id`, `value`, `price`, `currency`). Use the package `id` (NOT the bare value) when creating an invoice.",
73
+ {
74
+ product_id: { type: 'string', description: 'Product slug from bitrefill_search, e.g. "amazon-us", "steam-us".' },
75
+ },
76
+ ['product_id'],
77
+ ),
78
+
79
+ t(
80
+ 'bitrefill_get_balance',
81
+ "Get the user's Bitrefill account balance (the pre-funded pool used by `payment_method:\"balance\"`). Returns `{ balance, currency }`. No args.",
82
+ ),
83
+
84
+ t(
85
+ 'bitrefill_create_invoice',
86
+ "SPEND: confirmation-gated. Create an invoice for one or more products. Pass `payment_method:\"balance\"` + `auto_pay:true` for instant fulfillment from the account balance (lowest blast radius). For Lightning/on-chain, omit `auto_pay`, set `payment_method:\"lightning\"` (etc.) and `refund_address` — the response carries the payment URI; poll `bitrefill_get_invoice` until status=\"complete\" and then read the order. Up to 20 line items per invoice.",
87
+ {
88
+ products: {
89
+ type: 'array',
90
+ description: 'Line items. Each: { product_id, package_id, quantity }. Get `package_id` from bitrefill_get_product (NOT the bare denomination value).',
91
+ items: {
92
+ type: 'object',
93
+ properties: {
94
+ product_id: { type: 'string' },
95
+ package_id: { type: 'string' },
96
+ quantity: { type: 'number' },
97
+ },
98
+ required: ['product_id', 'package_id', 'quantity'],
99
+ },
100
+ },
101
+ payment_method: {
102
+ type: 'string',
103
+ description: 'How to pay: "balance" (account balance, instant), "lightning", "bitcoin", "usdc_base" (x402), "usdc_polygon", "usdt_tron", etc.',
104
+ enum: ['balance', 'lightning', 'bitcoin', 'usdc_base', 'usdc_polygon', 'usdc_ethereum', 'usdt_tron', 'usdt_ethereum'],
105
+ },
106
+ auto_pay: { type: 'boolean', description: 'Required true with `payment_method:"balance"` for instant settlement. Omit for crypto methods.' },
107
+ refund_address: { type: 'string', description: 'REQUIRED for non-balance crypto methods — refund destination if the invoice expires or partially pays.' },
108
+ email: { type: 'string', description: 'OPTIONAL — delivery / receipt email. Defaults to the account email when authenticated.' },
109
+ webhook_url: { type: 'string', description: 'OPTIONAL — URL Bitrefill calls when the order is delivered.' },
110
+ },
111
+ ['products', 'payment_method'],
112
+ /* spend */ true,
113
+ ),
114
+
115
+ t(
116
+ 'bitrefill_get_invoice',
117
+ "Get the invoice's current status: `unpaid`, `pending`, `paid`, `complete`, `expired`, `failed`. For crypto payment methods, poll this until `complete`; then call `bitrefill_get_order` for redemption details.",
118
+ {
119
+ invoice_id: { type: 'string', description: 'Invoice id returned by bitrefill_create_invoice.' },
120
+ },
121
+ ['invoice_id'],
122
+ ),
123
+
124
+ t(
125
+ 'bitrefill_get_order',
126
+ "Get an order's redemption details once delivered. Returns `redemption_info` containing the code, PIN (for prepaid cards), redemption link, instructions. ONLY call after the corresponding invoice status is `complete`. Treat the returned code as cash — never paste it in shared chats.",
127
+ {
128
+ order_id: { type: 'string', description: 'Order id from a completed invoice (`order_id` on the invoice or in its `orders[]`).' },
129
+ },
130
+ ['order_id'],
131
+ ),
132
+ ];
133
+
134
+ /** All Bitrefill tool names that move money (confirmation-gated). */
135
+ export const BITREFILL_SPEND_TOOLS: Set<string> = new Set(
136
+ BITREFILL_TOOLS.filter((t) => t.spend).map((t) => t.name),
137
+ );
138
+
139
+ export function isBitrefillSpendTool(name: string): boolean {
140
+ return BITREFILL_SPEND_TOOLS.has(name);
141
+ }
142
+
143
+ export function getBitrefillTool(name: string): BitrefillToolDef | undefined {
144
+ return BITREFILL_TOOLS.find((t) => t.name === name);
145
+ }
146
+
147
+ /** A handler bound to one Bitrefill tool. */
148
+ export type BitrefillHandler = (args: Record<string, unknown>) => Promise<unknown>;
149
+
150
+ export interface BindBitrefillOptions {
151
+ /** Skip tools without a handler instead of throwing (default false). */
152
+ allowMissing?: boolean;
153
+ /** ToolSource id for the registry (default 'bitrefill'). */
154
+ id?: string;
155
+ }
156
+
157
+ /**
158
+ * Bind Bitrefill contract tools to in-process handlers → an InProcessToolSource.
159
+ *
160
+ * const source = bindBitrefillTools({
161
+ * bitrefill_search: async (args) => api.search(args),
162
+ * bitrefill_get_product: async ({ product_id }) => api.product(product_id),
163
+ * bitrefill_get_balance: async () => api.balance(),
164
+ * bitrefill_create_invoice: async (args) => api.createInvoice(args),
165
+ * bitrefill_get_invoice: async ({ invoice_id }) => api.invoice(invoice_id),
166
+ * bitrefill_get_order: async ({ order_id }) => api.order(order_id),
167
+ * });
168
+ * tools.register(source);
169
+ */
170
+ export function bindBitrefillTools(
171
+ handlers: Record<string, BitrefillHandler>,
172
+ opts: BindBitrefillOptions = {},
173
+ ): InProcessToolSource {
174
+ const bound: InProcessTool[] = [];
175
+ for (const def of BITREFILL_TOOLS) {
176
+ const handler = handlers[def.name];
177
+ if (!handler) {
178
+ if (opts.allowMissing) continue;
179
+ throw new Error(`bindBitrefillTools: no handler for "${def.name}"`);
180
+ }
181
+ bound.push({
182
+ name: def.name,
183
+ description: def.description,
184
+ parameters: def.parameters,
185
+ requiresConfirmation: def.requiresConfirmation,
186
+ handler,
187
+ });
188
+ }
189
+ return new InProcessToolSource(opts.id ?? 'bitrefill', bound);
190
+ }