@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.
- package/README.md +257 -0
- package/dist/autonomy/risk.js.map +1 -1
- package/dist/autonomy/run-state.d.ts.map +1 -1
- package/dist/autonomy/run-state.js.map +1 -1
- package/dist/autonomy/task-store.d.ts.map +1 -1
- package/dist/autonomy/task-store.js.map +1 -1
- package/dist/bitrefill/contract.js.map +1 -1
- package/dist/context/budget.js.map +1 -1
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/context/builder.js.map +1 -1
- package/dist/context/compress.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js.map +1 -1
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js.map +1 -1
- package/dist/fastpath/fastpath.d.ts.map +1 -1
- package/dist/fastpath/fastpath.js.map +1 -1
- package/dist/flashnet/contract.js.map +1 -1
- package/dist/funnel.d.ts.map +1 -1
- package/dist/funnel.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/kaleidoswap/contract.js.map +1 -1
- package/dist/knowledge/btc-map.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/lsps1/contract.js.map +1 -1
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js.map +1 -1
- package/dist/qvac/assistant.js.map +1 -1
- package/dist/qvac/config.d.ts +6 -6
- package/dist/qvac/config.d.ts.map +1 -1
- package/dist/qvac/delegate.d.ts +2 -0
- package/dist/qvac/delegate.d.ts.map +1 -1
- package/dist/qvac/delegate.js +2 -0
- package/dist/qvac/delegate.js.map +1 -1
- package/dist/qvac/index.d.ts +1 -0
- package/dist/qvac/index.d.ts.map +1 -1
- package/dist/qvac/index.js +1 -0
- package/dist/qvac/index.js.map +1 -1
- package/dist/qvac/parse.d.ts +5 -2
- package/dist/qvac/parse.d.ts.map +1 -1
- package/dist/qvac/parse.js.map +1 -1
- package/dist/qvac/provider.d.ts.map +1 -1
- package/dist/qvac/provider.js +8 -13
- package/dist/qvac/provider.js.map +1 -1
- package/dist/qvac/stream.d.ts.map +1 -1
- package/dist/qvac/stream.js +29 -1
- package/dist/qvac/stream.js.map +1 -1
- package/dist/qvac/tools.d.ts +32 -0
- package/dist/qvac/tools.d.ts.map +1 -0
- package/dist/qvac/tools.js +84 -0
- package/dist/qvac/tools.js.map +1 -0
- package/dist/qvac/voice.js.map +1 -1
- package/dist/rag/retriever.d.ts.map +1 -1
- package/dist/rag/tool.js.map +1 -1
- package/dist/rag/vector-store.d.ts.map +1 -1
- package/dist/rag/vector-store.js.map +1 -1
- package/dist/recipe/issue-asset.d.ts +16 -0
- package/dist/recipe/issue-asset.d.ts.map +1 -0
- package/dist/recipe/issue-asset.js +94 -0
- package/dist/recipe/issue-asset.js.map +1 -0
- package/dist/recipe/runner.d.ts.map +1 -1
- package/dist/recipe/runner.js.map +1 -1
- package/dist/skills/loader.js.map +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-wallet.d.ts +79 -0
- package/dist/testing/mock-wallet.d.ts.map +1 -0
- package/dist/testing/mock-wallet.js +180 -0
- package/dist/testing/mock-wallet.js.map +1 -0
- package/dist/testing/scripted-provider.d.ts +17 -0
- package/dist/testing/scripted-provider.d.ts.map +1 -0
- package/dist/testing/scripted-provider.js +26 -0
- package/dist/testing/scripted-provider.js.map +1 -0
- package/dist/tools/in-process.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/wallet/confirm.d.ts.map +1 -1
- package/dist/wallet/confirm.js +21 -1
- package/dist/wallet/confirm.js.map +1 -1
- package/dist/wallet/contract.d.ts.map +1 -1
- package/dist/wallet/contract.js +36 -0
- package/dist/wallet/contract.js.map +1 -1
- package/package.json +10 -5
- package/skills/README.md +1 -1
- package/skills/kaleido-node/SKILL.md +63 -0
- package/skills/kaleido-trading/SKILL.md +15 -2
- package/skills/kaleido-trading/references/api.md +61 -0
- package/skills/kaleido-trading/references/assets.md +57 -0
- package/skills/kaleido-trading/references/atomic.md +93 -0
- package/skills/paid-data/SKILL.md +59 -6
- package/skills/rgb-lightning-node/SKILL.md +116 -11
- package/src/index.ts +3 -0
- package/src/qvac/delegate.ts +2 -0
- package/src/qvac/index.ts +2 -0
- package/src/qvac/parse.ts +5 -2
- package/src/qvac/provider.test.ts +38 -0
- package/src/qvac/provider.ts +10 -14
- package/src/qvac/stream.test.ts +28 -0
- package/src/qvac/stream.ts +30 -1
- package/src/qvac/tools.test.ts +86 -0
- package/src/qvac/tools.ts +106 -0
- package/src/recipe/issue-asset.test.ts +92 -0
- package/src/recipe/issue-asset.ts +95 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/mock-wallet.ts +211 -0
- package/src/testing/scripted-provider.ts +37 -0
- package/src/wallet/confirm.test.ts +16 -0
- package/src/wallet/confirm.ts +21 -1
- package/src/wallet/contract.test.ts +38 -0
- package/src/wallet/contract.ts +36 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scripted LLMProvider — replays planned turns instead of running a model.
|
|
3
|
+
*
|
|
4
|
+
* Lets hosts wire the full Engine / Funnel (skills, recipes, confirm gate) in
|
|
5
|
+
* unit tests, CI and demos without loading a QVAC model. Deterministic tiers
|
|
6
|
+
* (fast-path, recipes with regex extraction) never call it at all.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ToolCall } from '../types.js';
|
|
10
|
+
import type { LLMProvider } from '../providers/types.js';
|
|
11
|
+
|
|
12
|
+
export interface ScriptedTurn {
|
|
13
|
+
/** Assistant text for this turn. */
|
|
14
|
+
text?: string;
|
|
15
|
+
/** Tool to call — ignored when the tool is not in the turn's scoped tool list. */
|
|
16
|
+
tool?: string;
|
|
17
|
+
args?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function scriptedProvider(turns: ScriptedTurn[] = [], fallback = 'I need more information.'): LLMProvider {
|
|
21
|
+
let index = 0;
|
|
22
|
+
return {
|
|
23
|
+
name: 'scripted',
|
|
24
|
+
async runTurn(input) {
|
|
25
|
+
const planned = turns[index++] ?? { text: fallback };
|
|
26
|
+
const call: ToolCall | undefined = planned.tool && input.tools.some((t) => t.name === planned.tool)
|
|
27
|
+
? { id: `scripted-${index}`, name: planned.tool, arguments: planned.args ?? {} }
|
|
28
|
+
: undefined;
|
|
29
|
+
return {
|
|
30
|
+
text: planned.text ?? '',
|
|
31
|
+
rawContent: planned.text ?? '',
|
|
32
|
+
toolCalls: call ? [call] : [],
|
|
33
|
+
inference: { durationMs: 0, status: 'completed' },
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -48,6 +48,22 @@ describe('confirmReadback', () => {
|
|
|
48
48
|
expect(line).toBe('Pay Lightning invoice lnbc1p…abcd over Spark. Confirm?');
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
it('rln_send_asset: kaleido-mcp argument names', () => {
|
|
52
|
+
expect(confirmReadback({ name: 'rln_send_asset', arguments: { asset_id: 'rgb:abcdefgh-1234567890-xyz', amount: 5, recipient_id: 'utxob:ab12cd34ef56gh78ij90' } }))
|
|
53
|
+
.toBe('Send 5 rgb:ab…-xyz to utxob:…ij90 over RLN. Confirm?');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('rln_send_btc / channel / atomic taker readbacks', () => {
|
|
57
|
+
expect(confirmReadback({ name: 'rln_send_btc', arguments: { address: 'tb1qexampleaddress0000000000xyz', amount_sat: 25000 } }))
|
|
58
|
+
.toBe('Send 25,000 sats on-chain to tb1qex…0xyz over RLN. Confirm?');
|
|
59
|
+
expect(confirmReadback({ name: 'rln_open_channel', arguments: { peer_pubkey_and_addr: '03abcdef0123456789abcdef0123456789@1.2.3.4:9735', capacity_sat: 100000 } }))
|
|
60
|
+
.toBe('Open a 100,000 sats channel to 03abcd…6789. Confirm?');
|
|
61
|
+
expect(confirmReadback({ name: 'rln_close_channel', arguments: { channel_id: 'chan0123456789abcdef0123', peer_pubkey: 'x', force: true } }))
|
|
62
|
+
.toBe('Force-close channel chan01…0123. Confirm?');
|
|
63
|
+
expect(confirmReadback({ name: 'rln_atomic_taker', arguments: { swapstring: '30/rgb:asset/10/btc/3600/abcdef0123456789' } }))
|
|
64
|
+
.toBe('Accept atomic swap 30/rgb…6789 on your node. Confirm?');
|
|
65
|
+
});
|
|
66
|
+
|
|
51
67
|
it('execute_swap: from → to with amount', () => {
|
|
52
68
|
expect(confirmReadback({ name: 'execute_swap', arguments: { from_asset: 'BTC', to_asset: 'USDT', amount: 0.01 } }))
|
|
53
69
|
.toBe('Swap 0.01 BTC for USDT. Confirm?');
|
package/src/wallet/confirm.ts
CHANGED
|
@@ -62,10 +62,30 @@ export function confirmReadback(call: { name: string; arguments: Record<string,
|
|
|
62
62
|
return ask(`Send ${sats(a.amount_sats)} to ${to()}${over(name, a)}`);
|
|
63
63
|
case 'rln_send_asset':
|
|
64
64
|
case 'liquid_send':
|
|
65
|
-
|
|
65
|
+
// kaleido-mcp names these asset_id / recipient_id.
|
|
66
|
+
return ask(`Send ${asset(a.amount, a.asset ?? shortRef(String(a.asset_id ?? '')))} to ${to(a.to != null ? 'to' : 'recipient_id')}${over(name, a)}`);
|
|
66
67
|
case 'rln_pay_invoice':
|
|
67
68
|
case 'spark_pay_invoice':
|
|
68
69
|
return ask(`Pay Lightning invoice ${shortRef(String(a.invoice ?? ''))}${over(name, a)}`);
|
|
70
|
+
case 'rln_issue_asset': {
|
|
71
|
+
const label = a.name != null && a.ticker != null && String(a.name) !== String(a.ticker) ? ` (${String(a.name)})` : '';
|
|
72
|
+
return a.schema === 'UDA'
|
|
73
|
+
? ask(`Issue unique asset ${String(a.ticker ?? a.name)}${label} on RGB`)
|
|
74
|
+
: ask(`Issue ${asset(a.amount, a.ticker ?? a.name)}${label}, a new RGB asset`);
|
|
75
|
+
}
|
|
76
|
+
case 'rln_create_utxos':
|
|
77
|
+
return ask(`Create ${fmtNum(Number(a.num ?? 5))} RGB UTXOs on-chain${over(name, a)}`);
|
|
78
|
+
case 'rln_send_btc':
|
|
79
|
+
return ask(`Send ${sats(a.amount_sat)} on-chain to ${to('address')}${over(name, a)}`);
|
|
80
|
+
case 'rln_open_channel': {
|
|
81
|
+
const peer = shortRef(String(a.peer_pubkey_and_addr ?? '').split('@')[0] ?? '');
|
|
82
|
+
const withAsset = a.asset_id != null && a.asset_amount != null ? ` with ${fmtNum(Number(a.asset_amount))} of ${shortRef(String(a.asset_id))}` : '';
|
|
83
|
+
return ask(`Open a ${sats(a.capacity_sat)} channel to ${peer}${withAsset}`);
|
|
84
|
+
}
|
|
85
|
+
case 'rln_close_channel':
|
|
86
|
+
return ask(`${a.force ? 'Force-close' : 'Close'} channel ${shortRef(String(a.channel_id ?? ''))}`);
|
|
87
|
+
case 'rln_atomic_taker':
|
|
88
|
+
return ask(`Accept atomic swap ${shortRef(String(a.swapstring ?? ''))} on your node`);
|
|
69
89
|
case 'execute_swap':
|
|
70
90
|
return ask(`Swap ${fmtNum(Number(a.amount))} ${String(a.from_asset)} for ${String(a.to_asset)}`);
|
|
71
91
|
default:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Wallet contract tests — integrity of the single source of truth. */
|
|
2
2
|
|
|
3
3
|
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { parseSkill } from '../skills/registry.js';
|
|
4
6
|
import {
|
|
5
7
|
WALLET_TOOLS,
|
|
6
8
|
SPEND_TOOLS,
|
|
@@ -40,9 +42,13 @@ describe('WALLET_TOOLS contract', () => {
|
|
|
40
42
|
expect(isSpendTool('execute_swap')).toBe(true);
|
|
41
43
|
expect(isSpendTool('spark_send')).toBe(true);
|
|
42
44
|
expect(isSpendTool('spark_pay_invoice')).toBe(true);
|
|
45
|
+
expect(isSpendTool('rln_issue_asset')).toBe(true);
|
|
46
|
+
expect(isSpendTool('rln_create_utxos')).toBe(true);
|
|
43
47
|
// reads are not
|
|
44
48
|
expect(isSpendTool('get_balances')).toBe(false);
|
|
45
49
|
expect(isSpendTool('get_price')).toBe(false);
|
|
50
|
+
expect(isSpendTool('rln_list_assets')).toBe(false);
|
|
51
|
+
expect(isSpendTool('rln_list_transfers')).toBe(false);
|
|
46
52
|
expect([...SPEND_TOOLS].length).toBeGreaterThanOrEqual(5);
|
|
47
53
|
});
|
|
48
54
|
|
|
@@ -97,3 +103,35 @@ describe('bindWalletTools', () => {
|
|
|
97
103
|
expect(src.listTools().map((t) => t.name)).toEqual(['spark_get_balance']);
|
|
98
104
|
});
|
|
99
105
|
});
|
|
106
|
+
|
|
107
|
+
describe('RLN tools vs kaleido-mcp and the rgb-lightning-node skill', () => {
|
|
108
|
+
// The rln_* tools kaleido-mcp registers (rln_mpp_pay belongs to paid-data).
|
|
109
|
+
const MCP_RLN = [
|
|
110
|
+
'rln_get_node_info', 'rln_get_balances', 'rln_get_asset_balance', 'rln_list_assets',
|
|
111
|
+
'rln_get_address', 'rln_create_rgb_invoice', 'rln_create_ln_invoice', 'rln_pay_invoice',
|
|
112
|
+
'rln_send_btc', 'rln_send_asset', 'rln_list_channels', 'rln_connect_peer', 'rln_open_channel',
|
|
113
|
+
'rln_close_channel', 'rln_get_channel_id', 'rln_list_payments', 'rln_refresh_transfers',
|
|
114
|
+
'rln_atomic_taker', 'rln_list_swaps', 'rln_get_swap',
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
it('covers every rln_* tool kaleido-mcp exposes', () => {
|
|
118
|
+
const missing = MCP_RLN.filter((n) => !getWalletTool(n));
|
|
119
|
+
expect(missing).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('gates every fund-moving or committing RLN tool', () => {
|
|
123
|
+
for (const n of ['rln_send_btc', 'rln_open_channel', 'rln_close_channel', 'rln_atomic_taker']) {
|
|
124
|
+
expect(isSpendTool(n)).toBe(true);
|
|
125
|
+
}
|
|
126
|
+
for (const n of ['rln_get_asset_balance', 'rln_refresh_transfers', 'rln_list_swaps', 'rln_get_swap']) {
|
|
127
|
+
expect(isSpendTool(n)).toBe(false);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('the skill only declares tools that exist in the contract', () => {
|
|
132
|
+
const md = readFileSync(new URL('../../skills/rgb-lightning-node/SKILL.md', import.meta.url), 'utf8');
|
|
133
|
+
const skill = parseSkill(md);
|
|
134
|
+
const unknown = (skill.tools ?? []).filter((n) => !getWalletTool(n));
|
|
135
|
+
expect(unknown).toEqual([]);
|
|
136
|
+
});
|
|
137
|
+
});
|
package/src/wallet/contract.ts
CHANGED
|
@@ -91,6 +91,42 @@ export const WALLET_TOOLS: WalletToolDef[] = [
|
|
|
91
91
|
t('rln', 'rln_create_rgb_invoice', 'Create an RGB asset invoice to receive an asset (e.g. USDT).', { asset, amount: { type: 'number', description: 'Asset amount' } }, ['asset', 'amount']),
|
|
92
92
|
t('rln', 'rln_pay_invoice', 'Pay a Lightning invoice from the RLN node.', { invoice: { type: 'string' } }, ['invoice'], true),
|
|
93
93
|
t('rln', 'rln_send_asset', 'Send an RGB asset (e.g. USDT) to a recipient.', { asset, amount: { type: 'number' }, to: { type: 'string' } }, ['asset', 'amount', 'to'], true),
|
|
94
|
+
t('rln', 'rln_list_assets', 'List RGB assets held by the RLN node (asset_id, ticker, name, precision, balances).'),
|
|
95
|
+
t('rln', 'rln_get_asset_balance', 'Get the balance of one RGB asset by asset_id (settled, future, spendable, off-chain).', { asset_id: { type: 'string', description: "RGB asset id, e.g. 'rgb:2JEUOrsc-…'" } }, ['asset_id']),
|
|
96
|
+
t('rln', 'rln_refresh_transfers', 'Refresh pending RGB transfers so balances and transfer status are up to date.'),
|
|
97
|
+
t('rln', 'rln_get_address', 'Get an on-chain BTC address of the RLN node for deposits.'),
|
|
98
|
+
t('rln', 'rln_send_btc', 'Send on-chain BTC from the RLN node.', { address: { type: 'string', description: 'Destination Bitcoin address' }, amount_sat: { type: 'number', description: 'Amount in satoshis' }, fee_rate: { type: 'number', description: 'sat/vbyte (default 3)' } }, ['address', 'amount_sat'], true),
|
|
99
|
+
t('rln', 'rln_list_payments', 'List recent Lightning payments (sent and received) on the RLN node.', { limit: { type: 'number', description: 'Max payments (default 20)' } }),
|
|
100
|
+
t('rln', 'rln_connect_peer', 'Connect the RLN node to a Lightning peer.', { peer_pubkey_and_addr: { type: 'string', description: 'pubkey@host:port' } }, ['peer_pubkey_and_addr']),
|
|
101
|
+
t('rln', 'rln_open_channel', 'Open a Lightning channel, optionally allocating an RGB asset to it.', {
|
|
102
|
+
peer_pubkey_and_addr: { type: 'string', description: 'pubkey@host:port' },
|
|
103
|
+
capacity_sat: { type: 'number', description: 'Channel capacity in satoshis' },
|
|
104
|
+
push_msat: { type: 'number', description: 'Millisatoshis pushed to the peer (default 0)' },
|
|
105
|
+
asset_id: { type: 'string', description: 'RGB asset id to allocate' },
|
|
106
|
+
asset_amount: { type: 'number', description: 'RGB asset amount to allocate' },
|
|
107
|
+
is_public: { type: 'boolean', description: 'Announce the channel (default false)' },
|
|
108
|
+
}, ['peer_pubkey_and_addr', 'capacity_sat'], true),
|
|
109
|
+
t('rln', 'rln_close_channel', 'Close a Lightning channel (force only for an unresponsive peer).', { channel_id: { type: 'string' }, peer_pubkey: { type: 'string' }, force: { type: 'boolean', description: 'Unilateral close (default false)' } }, ['channel_id', 'peer_pubkey'], true),
|
|
110
|
+
t('rln', 'rln_get_channel_id', 'Resolve a temporary_channel_id from rln_open_channel to the final channel_id.', { temporary_channel_id: { type: 'string' } }, ['temporary_channel_id']),
|
|
111
|
+
t('rln', 'rln_atomic_taker', 'Whitelist a maker swap on the node (taker side). Pass the swapstring from kaleidoswap_atomic_init, before kaleidoswap_atomic_execute.', { swapstring: { type: 'string' } }, ['swapstring'], true),
|
|
112
|
+
t('rln', 'rln_list_swaps', 'List atomic swaps on the RLN node (maker and taker sides).'),
|
|
113
|
+
t('rln', 'rln_get_swap', 'Get an atomic swap by payment_hash.', { payment_hash: { type: 'string' }, taker: { type: 'boolean', description: 'Taker-side swap' } }, ['payment_hash']),
|
|
114
|
+
t('rln', 'rln_list_transfers', 'List RGB transfers for one asset (issuance, sends, receives) with their status — use to check if an RGB invoice was paid.', { asset_id: { type: 'string', description: 'RGB asset id (in-app wallets also accept a ticker)' } }, ['asset_id']),
|
|
115
|
+
// Issuance + UTXO prep spend on-chain BTC and are irreversible → gated like any spend.
|
|
116
|
+
t('rln', 'rln_create_utxos', 'Create colorable UTXOs on the RLN node — needed before issuing or receiving RGB assets on a fresh node.', {
|
|
117
|
+
num: { type: 'number', description: 'How many UTXOs (default 5)' },
|
|
118
|
+
size: { type: 'number', description: 'Size of each UTXO in sats (default: node decides)' },
|
|
119
|
+
up_to: { type: 'boolean', description: 'Only top up to `num` free UTXOs (default false)' },
|
|
120
|
+
fee_rate: { type: 'number', description: 'sat/vbyte (default 1)' },
|
|
121
|
+
}, [], true),
|
|
122
|
+
t('rln', 'rln_issue_asset', 'Issue a NEW RGB asset owned by this node (e.g. an event ticket or loyalty token).', {
|
|
123
|
+
schema: { type: 'string', enum: ['NIA', 'CFA', 'UDA'], description: 'NIA = token (default), CFA = collectible, UDA = unique/NFT' },
|
|
124
|
+
name: { type: 'string', description: 'Asset name, e.g. "Hackathon Ticket"' },
|
|
125
|
+
ticker: { type: 'string', description: 'Uppercase ticker, e.g. "TICKET". Required for NIA and UDA' },
|
|
126
|
+
amount: { type: 'number', description: 'Total supply in display units. Required for NIA and CFA; UDA is always 1' },
|
|
127
|
+
precision: { type: 'number', description: 'Decimal places (default 0)' },
|
|
128
|
+
details: { type: 'string', description: 'Optional description (CFA and UDA)' },
|
|
129
|
+
}, ['name'], true),
|
|
94
130
|
|
|
95
131
|
// ── Arkade ─────────────────────────────────────────────────────────────
|
|
96
132
|
t('arkade', 'arkade_get_balance', 'Get the Arkade wallet balance.'),
|