@darksol/terminal 0.5.8 → 0.6.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/package.json +1 -1
- package/src/cli.js +24 -8
- package/src/config/store.js +1 -1
- package/src/llm/intent.js +24 -2
- package/src/services/casino.js +244 -45
- package/src/services/facilitator.js +32 -6
- package/src/web/commands.js +34 -13
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { snipeToken, watchSnipe } from './trading/snipe.js';
|
|
|
15
15
|
import { createDCA, listDCA, cancelDCA, runDCA } from './trading/dca.js';
|
|
16
16
|
import { topMovers, tokenDetail, compareTokens } from './services/market.js';
|
|
17
17
|
import { oracleFlip, oracleDice, oracleNumber, oracleShuffle, oracleHealth } from './services/oracle.js';
|
|
18
|
-
import { casinoBet, casinoTables, casinoStats, casinoReceipt } from './services/casino.js';
|
|
18
|
+
import { casinoBet, casinoTables, casinoStats, casinoReceipt, casinoHealth, casinoVerify } from './services/casino.js';
|
|
19
19
|
import { cardsCatalog, cardsOrder, cardsStatus } from './services/cards.js';
|
|
20
20
|
import { facilitatorHealth, facilitatorVerify, facilitatorSettle } from './services/facilitator.js';
|
|
21
21
|
import { buildersLeaderboard, buildersLookup, buildersFeed } from './services/builders.js';
|
|
@@ -238,15 +238,26 @@ export function cli(argv) {
|
|
|
238
238
|
.description('The Clawsino — on-chain betting');
|
|
239
239
|
|
|
240
240
|
casino
|
|
241
|
-
.command('
|
|
242
|
-
.description('
|
|
243
|
-
.
|
|
244
|
-
|
|
245
|
-
|
|
241
|
+
.command('status')
|
|
242
|
+
.description('House status, balance, games')
|
|
243
|
+
.action(() => casinoHealth());
|
|
244
|
+
|
|
245
|
+
casino
|
|
246
|
+
.command('bet [game]')
|
|
247
|
+
.description('Place a $1 USDC bet (interactive if game omitted)')
|
|
248
|
+
.option('-c, --choice <choice>', 'heads/tails, higher/lower')
|
|
249
|
+
.option('-d, --direction <dir>', 'over/under (dice)')
|
|
250
|
+
.option('-t, --threshold <n>', 'Dice threshold 2-5')
|
|
251
|
+
.option('-w, --wallet <addr>', 'Payout wallet address')
|
|
252
|
+
.action((game, opts) => casinoBet(game, {
|
|
253
|
+
choice: opts.choice,
|
|
254
|
+
direction: opts.direction,
|
|
255
|
+
threshold: opts.threshold ? parseInt(opts.threshold) : undefined,
|
|
256
|
+
}, { wallet: opts.wallet }));
|
|
246
257
|
|
|
247
258
|
casino
|
|
248
259
|
.command('tables')
|
|
249
|
-
.description('
|
|
260
|
+
.description('Recent bets')
|
|
250
261
|
.action(() => casinoTables());
|
|
251
262
|
|
|
252
263
|
casino
|
|
@@ -256,9 +267,14 @@ export function cli(argv) {
|
|
|
256
267
|
|
|
257
268
|
casino
|
|
258
269
|
.command('receipt <id>')
|
|
259
|
-
.description('
|
|
270
|
+
.description('Get bet receipt')
|
|
260
271
|
.action((id) => casinoReceipt(id));
|
|
261
272
|
|
|
273
|
+
casino
|
|
274
|
+
.command('verify <id>')
|
|
275
|
+
.description('Verify bet on-chain')
|
|
276
|
+
.action((id) => casinoVerify(id));
|
|
277
|
+
|
|
262
278
|
// ═══════════════════════════════════════
|
|
263
279
|
// CARDS COMMANDS
|
|
264
280
|
// ═══════════════════════════════════════
|
package/src/config/store.js
CHANGED
|
@@ -31,7 +31,7 @@ const config = new Conf({
|
|
|
31
31
|
default: {
|
|
32
32
|
oracle: 'https://acp.darksol.net/oracle',
|
|
33
33
|
casino: 'https://casino.darksol.net',
|
|
34
|
-
cards: 'https://acp.darksol.net
|
|
34
|
+
cards: 'https://acp.darksol.net',
|
|
35
35
|
facilitator: 'https://facilitator.darksol.net',
|
|
36
36
|
builders: 'https://builders.darksol.net',
|
|
37
37
|
market: 'https://acp.darksol.net/market',
|
package/src/llm/intent.js
CHANGED
|
@@ -65,8 +65,16 @@ ACTIONS (use the most specific one):
|
|
|
65
65
|
- "analyze" — deep analysis of a token
|
|
66
66
|
- "gas" — gas price check
|
|
67
67
|
- "cards" — order a prepaid Visa/Mastercard with crypto (e.g. "order a $50 card", "get me a prepaid card")
|
|
68
|
+
- "casino" — play a casino game (coinflip, dice, hilo, slots). All bets are $1 USDC. (e.g. "flip a coin", "bet on heads", "play slots", "roll dice over 3")
|
|
68
69
|
- "unknown" — can't determine what the user wants
|
|
69
70
|
|
|
71
|
+
CASINO GAMES:
|
|
72
|
+
- coinflip: { "choice": "heads" | "tails" } → 1.90x payout
|
|
73
|
+
- dice: { "direction": "over"|"under", "threshold": 2-5 } → variable payout
|
|
74
|
+
- hilo: { "choice": "higher" | "lower" } → ~2.06x payout
|
|
75
|
+
- slots: {} → Match-3: 5.00x, Match-2: 1.50x
|
|
76
|
+
All bets are exactly $1 USDC. House edge: 5%. Results verified on-chain.
|
|
77
|
+
|
|
70
78
|
CARDS ORDERING:
|
|
71
79
|
When the user wants to order a prepaid card, you MUST collect:
|
|
72
80
|
1. amount — ONLY these denominations: $10, $25, $50, $100, $250, $500, $1000
|
|
@@ -91,7 +99,7 @@ If they mention AgentMail or "my email", suggest using their configured agent em
|
|
|
91
99
|
|
|
92
100
|
When parsing, respond with ONLY valid JSON:
|
|
93
101
|
{
|
|
94
|
-
"action": "swap|send|snipe|dca|price|balance|info|analyze|gas|cards|unknown",
|
|
102
|
+
"action": "swap|send|snipe|dca|price|balance|info|analyze|gas|cards|casino|unknown",
|
|
95
103
|
"tokenIn": "symbol or address (for swaps)",
|
|
96
104
|
"tokenOut": "symbol or address (for swaps)",
|
|
97
105
|
"token": "symbol (for send/price/analyze)",
|
|
@@ -100,6 +108,8 @@ When parsing, respond with ONLY valid JSON:
|
|
|
100
108
|
"email": "delivery email (for cards)",
|
|
101
109
|
"provider": "card provider (for cards, default: swype)",
|
|
102
110
|
"ticker": "payment crypto (for cards, default: usdc)",
|
|
111
|
+
"gameType": "casino game: coinflip|dice|hilo|slots",
|
|
112
|
+
"betParams": "casino bet parameters object",
|
|
103
113
|
"chain": "chain name if specified, null if not",
|
|
104
114
|
"interval": "for DCA: 1h, 4h, 1d, etc.",
|
|
105
115
|
"orders": "for DCA: number of orders",
|
|
@@ -147,6 +157,7 @@ COMMAND MAPPING:
|
|
|
147
157
|
- balance → darksol wallet balance
|
|
148
158
|
- gas → darksol gas <chain>
|
|
149
159
|
- cards → darksol cards order -p <provider> -a <amount> -e <email> --ticker <crypto>
|
|
160
|
+
- casino → darksol casino bet <game> -c <choice>
|
|
150
161
|
- analyze → darksol ai analyze <token>`;
|
|
151
162
|
|
|
152
163
|
// ──────────────────────────────────────────────────
|
|
@@ -292,7 +303,7 @@ export async function startChat(opts = {}) {
|
|
|
292
303
|
}
|
|
293
304
|
|
|
294
305
|
// Try to detect actionable intent
|
|
295
|
-
const actionKeywords = /\b(swap|send|transfer|buy|sell|snipe|dca|price|balance|gas|card|cards|order|prepaid|visa|mastercard)\b/i;
|
|
306
|
+
const actionKeywords = /\b(swap|send|transfer|buy|sell|snipe|dca|price|balance|gas|card|cards|order|prepaid|visa|mastercard|casino|bet|coinflip|coin|flip|dice|slots|hilo|gamble|play)\b/i;
|
|
296
307
|
const isActionable = actionKeywords.test(input);
|
|
297
308
|
|
|
298
309
|
let result;
|
|
@@ -628,6 +639,17 @@ export async function executeIntent(intent, opts = {}) {
|
|
|
628
639
|
return { success: true, action: 'gas' };
|
|
629
640
|
}
|
|
630
641
|
|
|
642
|
+
case 'casino': {
|
|
643
|
+
const { casinoBet } = await import('../services/casino.js');
|
|
644
|
+
const gameType = intent.gameType || 'coinflip';
|
|
645
|
+
const betParams = intent.betParams || {};
|
|
646
|
+
// Map common AI outputs
|
|
647
|
+
if (intent.choice) betParams.choice = intent.choice;
|
|
648
|
+
if (intent.direction) betParams.direction = intent.direction;
|
|
649
|
+
if (intent.threshold) betParams.threshold = intent.threshold;
|
|
650
|
+
return await casinoBet(gameType, betParams, { wallet: opts.wallet });
|
|
651
|
+
}
|
|
652
|
+
|
|
631
653
|
case 'cards': {
|
|
632
654
|
if (!intent.amount) {
|
|
633
655
|
info('What denomination? We have $10, $25, $50, $100, $250, $500, $1000');
|
package/src/services/casino.js
CHANGED
|
@@ -1,92 +1,291 @@
|
|
|
1
1
|
import { fetchJSON } from '../utils/fetch.js';
|
|
2
2
|
import fetch from 'node-fetch';
|
|
3
3
|
import { getServiceURL } from '../config/store.js';
|
|
4
|
+
import { getConfig } from '../config/store.js';
|
|
4
5
|
import { theme } from '../ui/theme.js';
|
|
5
|
-
import { spinner, kvDisplay, success, error, table } from '../ui/components.js';
|
|
6
|
+
import { spinner, kvDisplay, success, error, warn, info, table } from '../ui/components.js';
|
|
6
7
|
import { showSection } from '../ui/banner.js';
|
|
7
8
|
|
|
8
9
|
const getURL = () => getServiceURL('casino') || 'https://casino.darksol.net';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
// Available games with their bet params
|
|
12
|
+
const GAMES = {
|
|
13
|
+
coinflip: {
|
|
14
|
+
name: '🪙 Coin Flip',
|
|
15
|
+
payout: '1.90x',
|
|
16
|
+
params: { choice: ['heads', 'tails'] },
|
|
17
|
+
desc: 'Call heads or tails',
|
|
18
|
+
},
|
|
19
|
+
dice: {
|
|
20
|
+
name: '🎲 Dice',
|
|
21
|
+
payout: 'variable',
|
|
22
|
+
params: { direction: ['over', 'under'], threshold: [2, 3, 4, 5] },
|
|
23
|
+
desc: 'Over/under a number (2-5)',
|
|
24
|
+
},
|
|
25
|
+
hilo: {
|
|
26
|
+
name: '🃏 Hi-Lo',
|
|
27
|
+
payout: '~2.06x',
|
|
28
|
+
params: { choice: ['higher', 'lower'] },
|
|
29
|
+
desc: 'Higher or lower',
|
|
30
|
+
},
|
|
31
|
+
slots: {
|
|
32
|
+
name: '🎰 Slots',
|
|
33
|
+
payout: '1.50-5.00x',
|
|
34
|
+
params: {},
|
|
35
|
+
desc: 'Match symbols — no params needed',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { GAMES };
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check casino health + x402 payment info
|
|
43
|
+
*/
|
|
44
|
+
export async function casinoHealth() {
|
|
45
|
+
const spin = spinner('Checking casino...').start();
|
|
46
|
+
try {
|
|
47
|
+
const stats = await fetchJSON(`${getURL()}/api/stats`);
|
|
48
|
+
spin.succeed('Casino online');
|
|
49
|
+
|
|
50
|
+
showSection('THE CLAWSINO 🎰');
|
|
51
|
+
kvDisplay([
|
|
52
|
+
['Status', stats.acceptingBets ? theme.success('● Open') : theme.error('○ Closed')],
|
|
53
|
+
['House Balance', `$${stats.houseBalanceUsdc || '0'} USDC`],
|
|
54
|
+
['Total Bets', String(stats.totalBets || 0)],
|
|
55
|
+
['Total Wagered', `$${stats.totalWageredUsdc || '0'} USDC`],
|
|
56
|
+
['Total Payouts', `$${stats.totalPayoutsUsdc || '0'} USDC`],
|
|
57
|
+
['Win Rate', stats.winRate || '0%'],
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
console.log('');
|
|
61
|
+
showSection('GAMES');
|
|
62
|
+
for (const [id, g] of Object.entries(GAMES)) {
|
|
63
|
+
console.log(` ${theme.gold(g.name.padEnd(20))} ${theme.dim(g.payout.padEnd(12))} ${g.desc}`);
|
|
64
|
+
}
|
|
65
|
+
console.log('');
|
|
66
|
+
info('All bets are $1 USDC. House edge: 5%.');
|
|
67
|
+
info('Docs: https://casino.darksol.net/docs');
|
|
68
|
+
} catch (err) {
|
|
69
|
+
spin.fail('Casino unreachable');
|
|
70
|
+
error(err.message);
|
|
71
|
+
info('Check: https://casino.darksol.net/docs');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Place a bet — requires wallet address for payouts
|
|
77
|
+
*/
|
|
78
|
+
export async function casinoBet(gameType, betParams = {}, opts = {}) {
|
|
79
|
+
const inquirer = (await import('inquirer')).default;
|
|
80
|
+
const game = GAMES[gameType];
|
|
81
|
+
|
|
82
|
+
// Validate or prompt for game type
|
|
83
|
+
if (!game) {
|
|
84
|
+
if (gameType) warn(`Unknown game: ${gameType}`);
|
|
85
|
+
const { picked } = await inquirer.prompt([{
|
|
86
|
+
type: 'list',
|
|
87
|
+
name: 'picked',
|
|
88
|
+
message: theme.gold('Pick a game:'),
|
|
89
|
+
choices: Object.entries(GAMES).map(([id, g]) => ({ name: `${g.name} ${g.payout} — ${g.desc}`, value: id })),
|
|
90
|
+
}]);
|
|
91
|
+
gameType = picked;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const gameInfo = GAMES[gameType];
|
|
95
|
+
|
|
96
|
+
// Collect bet params based on game
|
|
97
|
+
if (gameType === 'coinflip' && !betParams.choice) {
|
|
98
|
+
const { choice } = await inquirer.prompt([{
|
|
99
|
+
type: 'list',
|
|
100
|
+
name: 'choice',
|
|
101
|
+
message: theme.gold('Heads or tails?'),
|
|
102
|
+
choices: ['heads', 'tails'],
|
|
103
|
+
}]);
|
|
104
|
+
betParams.choice = choice;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (gameType === 'dice') {
|
|
108
|
+
if (!betParams.direction) {
|
|
109
|
+
const { direction } = await inquirer.prompt([{
|
|
110
|
+
type: 'list',
|
|
111
|
+
name: 'direction',
|
|
112
|
+
message: theme.gold('Over or under?'),
|
|
113
|
+
choices: ['over', 'under'],
|
|
114
|
+
}]);
|
|
115
|
+
betParams.direction = direction;
|
|
116
|
+
}
|
|
117
|
+
if (!betParams.threshold) {
|
|
118
|
+
const { threshold } = await inquirer.prompt([{
|
|
119
|
+
type: 'list',
|
|
120
|
+
name: 'threshold',
|
|
121
|
+
message: theme.gold('Threshold (2-5):'),
|
|
122
|
+
choices: ['2', '3', '4', '5'],
|
|
123
|
+
}]);
|
|
124
|
+
betParams.threshold = parseInt(threshold);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (gameType === 'hilo' && !betParams.choice) {
|
|
129
|
+
const { choice } = await inquirer.prompt([{
|
|
130
|
+
type: 'list',
|
|
131
|
+
name: 'choice',
|
|
132
|
+
message: theme.gold('Higher or lower?'),
|
|
133
|
+
choices: ['higher', 'lower'],
|
|
134
|
+
}]);
|
|
135
|
+
betParams.choice = choice;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get wallet address
|
|
139
|
+
let agentWallet = opts.wallet;
|
|
140
|
+
if (!agentWallet) {
|
|
141
|
+
const activeWallet = getConfig('activeWallet');
|
|
142
|
+
if (activeWallet) {
|
|
143
|
+
try {
|
|
144
|
+
const { loadWallet } = await import('../wallet/keystore.js');
|
|
145
|
+
const w = loadWallet(activeWallet);
|
|
146
|
+
agentWallet = w.address;
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!agentWallet) {
|
|
151
|
+
const { addr } = await inquirer.prompt([{
|
|
152
|
+
type: 'input',
|
|
153
|
+
name: 'addr',
|
|
154
|
+
message: theme.gold('Your wallet address (for payouts):'),
|
|
155
|
+
validate: v => v.startsWith('0x') && v.length === 42 ? true : 'Enter a valid 0x address',
|
|
156
|
+
}]);
|
|
157
|
+
agentWallet = addr;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Confirm
|
|
161
|
+
console.log('');
|
|
162
|
+
kvDisplay([
|
|
163
|
+
['Game', gameInfo.name],
|
|
164
|
+
['Bet', '$1 USDC'],
|
|
165
|
+
['Params', JSON.stringify(betParams)],
|
|
166
|
+
['Payout Wallet', agentWallet],
|
|
167
|
+
['Payout', gameInfo.payout],
|
|
168
|
+
]);
|
|
169
|
+
console.log('');
|
|
170
|
+
|
|
171
|
+
const { confirm } = await inquirer.prompt([{
|
|
172
|
+
type: 'confirm',
|
|
173
|
+
name: 'confirm',
|
|
174
|
+
message: theme.gold('Place $1 USDC bet?'),
|
|
175
|
+
default: false,
|
|
176
|
+
}]);
|
|
177
|
+
|
|
178
|
+
if (!confirm) {
|
|
179
|
+
warn('Bet cancelled');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const spin = spinner(`Playing ${gameInfo.name}...`).start();
|
|
12
184
|
try {
|
|
13
185
|
const data = await fetchJSON(`${getURL()}/api/bet`, {
|
|
14
186
|
method: 'POST',
|
|
15
187
|
headers: { 'Content-Type': 'application/json' },
|
|
16
|
-
body: JSON.stringify({
|
|
17
|
-
game,
|
|
18
|
-
choice: opts.choice,
|
|
19
|
-
number: opts.number,
|
|
20
|
-
wallet: opts.wallet,
|
|
21
|
-
}),
|
|
188
|
+
body: JSON.stringify({ gameType, betParams, agentWallet }),
|
|
22
189
|
});
|
|
23
|
-
spin.succeed('Bet placed');
|
|
24
190
|
|
|
25
|
-
|
|
191
|
+
if (data.won) {
|
|
192
|
+
spin.succeed(theme.success(`YOU WON! $${data.payoutAmount} USDC`));
|
|
193
|
+
} else {
|
|
194
|
+
spin.fail('You lost.');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log('');
|
|
198
|
+
showSection(`${gameInfo.name} RESULT`);
|
|
26
199
|
kvDisplay([
|
|
27
|
-
['
|
|
28
|
-
['
|
|
29
|
-
['
|
|
30
|
-
['
|
|
31
|
-
['
|
|
32
|
-
['TX', data.
|
|
200
|
+
['Bet ID', data.id || '-'],
|
|
201
|
+
['Result', data.result || '-'],
|
|
202
|
+
['Won', data.won ? theme.success('YES! 🎉') : theme.error('No')],
|
|
203
|
+
['Payout', data.won ? `$${data.payoutAmount} USDC` : '$0'],
|
|
204
|
+
['Oracle TX', data.oracleTxHash ? data.oracleTxHash.slice(0, 20) + '...' : '-'],
|
|
205
|
+
['Payout TX', data.payoutTxHash ? data.payoutTxHash.slice(0, 20) + '...' : '-'],
|
|
33
206
|
]);
|
|
207
|
+
|
|
208
|
+
if (data.id) {
|
|
209
|
+
console.log('');
|
|
210
|
+
info(`Verify on-chain: darksol casino verify ${data.id}`);
|
|
211
|
+
}
|
|
212
|
+
console.log('');
|
|
34
213
|
} catch (err) {
|
|
35
214
|
spin.fail('Bet failed');
|
|
36
215
|
error(err.message);
|
|
216
|
+
if (err.message.includes('not accepting') || err.message.includes('closed')) {
|
|
217
|
+
info('The casino may be temporarily closed. Check: darksol casino status');
|
|
218
|
+
}
|
|
37
219
|
}
|
|
38
220
|
}
|
|
39
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Show recent bets
|
|
224
|
+
*/
|
|
40
225
|
export async function casinoTables() {
|
|
41
|
-
const spin = spinner('Loading
|
|
226
|
+
const spin = spinner('Loading recent bets...').start();
|
|
42
227
|
try {
|
|
43
228
|
const data = await fetchJSON(`${getURL()}/api/tables`);
|
|
44
|
-
spin.succeed('
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
theme.gold(t.name || t.game),
|
|
51
|
-
t.multiplier || 'N/A',
|
|
52
|
-
t.minBet || '$1',
|
|
53
|
-
t.status || 'Open',
|
|
54
|
-
]);
|
|
55
|
-
table(['Game', 'Multiplier', 'Min Bet', 'Status'], rows);
|
|
56
|
-
} else {
|
|
57
|
-
kvDisplay(Object.entries(tables).map(([k, v]) => [k, String(v)]));
|
|
229
|
+
spin.succeed('Loaded');
|
|
230
|
+
|
|
231
|
+
const items = data.items || [];
|
|
232
|
+
if (items.length === 0) {
|
|
233
|
+
info('No recent bets');
|
|
234
|
+
return;
|
|
58
235
|
}
|
|
236
|
+
|
|
237
|
+
showSection('RECENT BETS');
|
|
238
|
+
const rows = items.map(b => [
|
|
239
|
+
b.gameType || '-',
|
|
240
|
+
b.result || '-',
|
|
241
|
+
b.won ? theme.success('Won') : theme.error('Lost'),
|
|
242
|
+
b.payoutAmount ? `$${b.payoutAmount}` : '$0',
|
|
243
|
+
b.agentWallet ? b.agentWallet.slice(0, 8) + '...' : '-',
|
|
244
|
+
]);
|
|
245
|
+
table(['Game', 'Result', 'Won', 'Payout', 'Wallet'], rows);
|
|
59
246
|
} catch (err) {
|
|
60
|
-
spin.fail('Failed
|
|
247
|
+
spin.fail('Failed');
|
|
61
248
|
error(err.message);
|
|
62
249
|
}
|
|
63
250
|
}
|
|
64
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Get stats
|
|
254
|
+
*/
|
|
65
255
|
export async function casinoStats() {
|
|
66
|
-
|
|
256
|
+
return await casinoHealth();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get receipt for a bet
|
|
261
|
+
*/
|
|
262
|
+
export async function casinoReceipt(id) {
|
|
263
|
+
const spin = spinner(`Loading receipt ${id}...`).start();
|
|
67
264
|
try {
|
|
68
|
-
const data = await fetchJSON(`${getURL()}/api/
|
|
69
|
-
spin.succeed('
|
|
265
|
+
const data = await fetchJSON(`${getURL()}/api/receipt/${id}`);
|
|
266
|
+
spin.succeed('Receipt loaded');
|
|
70
267
|
|
|
71
|
-
showSection(
|
|
268
|
+
showSection(`BET RECEIPT — ${id}`);
|
|
72
269
|
kvDisplay(Object.entries(data).map(([k, v]) => [k, typeof v === 'object' ? JSON.stringify(v) : String(v)]));
|
|
73
270
|
} catch (err) {
|
|
74
|
-
spin.fail('
|
|
271
|
+
spin.fail('Receipt not found');
|
|
75
272
|
error(err.message);
|
|
76
273
|
}
|
|
77
274
|
}
|
|
78
275
|
|
|
79
|
-
|
|
80
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Verify a bet on-chain
|
|
278
|
+
*/
|
|
279
|
+
export async function casinoVerify(id) {
|
|
280
|
+
const spin = spinner(`Verifying ${id}...`).start();
|
|
81
281
|
try {
|
|
82
|
-
const data = await fetchJSON(`${getURL()}/api/
|
|
83
|
-
spin.succeed('
|
|
282
|
+
const data = await fetchJSON(`${getURL()}/api/verify/${id}`);
|
|
283
|
+
spin.succeed('Verified');
|
|
84
284
|
|
|
85
|
-
showSection(`
|
|
285
|
+
showSection(`ON-CHAIN VERIFICATION — ${id}`);
|
|
86
286
|
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
87
287
|
} catch (err) {
|
|
88
|
-
spin.fail('
|
|
288
|
+
spin.fail('Verification failed');
|
|
89
289
|
error(err.message);
|
|
90
290
|
}
|
|
91
291
|
}
|
|
92
|
-
|
|
@@ -1,29 +1,55 @@
|
|
|
1
1
|
import { fetchJSON } from '../utils/fetch.js';
|
|
2
2
|
import { getServiceURL } from '../config/store.js';
|
|
3
3
|
import { theme } from '../ui/theme.js';
|
|
4
|
-
import { spinner, kvDisplay, error } from '../ui/components.js';
|
|
4
|
+
import { spinner, kvDisplay, error, info } from '../ui/components.js';
|
|
5
5
|
import { showSection } from '../ui/banner.js';
|
|
6
6
|
|
|
7
|
+
// Facilitator root returns service info (no /api/health path)
|
|
7
8
|
const getURL = () => getServiceURL('facilitator') || 'https://facilitator.darksol.net';
|
|
8
9
|
|
|
9
10
|
export async function facilitatorHealth() {
|
|
10
11
|
const spin = spinner('Checking facilitator...').start();
|
|
11
12
|
try {
|
|
12
|
-
const data = await fetchJSON(`${getURL()}
|
|
13
|
+
const data = await fetchJSON(`${getURL()}/`);
|
|
13
14
|
spin.succeed('Facilitator online');
|
|
14
15
|
|
|
15
|
-
showSection('FACILITATOR
|
|
16
|
-
kvDisplay(
|
|
16
|
+
showSection('x402 FACILITATOR');
|
|
17
|
+
kvDisplay([
|
|
18
|
+
['Service', data.service || 'DARKSOL Facilitator'],
|
|
19
|
+
['Version', data.version || '-'],
|
|
20
|
+
['Protocol', data.protocol || 'x402'],
|
|
21
|
+
['Fee', data.fee || '0%'],
|
|
22
|
+
['Chains', Array.isArray(data.chains) ? data.chains.map(c => c.chain || c).join(', ') : (data.chains || 'Base, Polygon')],
|
|
23
|
+
['Status', theme.success('● Online')],
|
|
24
|
+
]);
|
|
25
|
+
if (data.description) {
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(theme.dim(` ${data.description}`));
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(data.chains)) {
|
|
30
|
+
console.log('');
|
|
31
|
+
for (const c of data.chains) {
|
|
32
|
+
console.log(` ${theme.gold((c.chain || '').padEnd(10))} ${theme.dim(c.facilitator || '')} ${c.status === 'operational' ? theme.success('●') : theme.error('○')} ${c.settlements || 0} settlements, $${c.volumeUSDC || '0'} USDC`);
|
|
33
|
+
}
|
|
34
|
+
} else if (data.contracts) {
|
|
35
|
+
console.log('');
|
|
36
|
+
for (const [chain, addr] of Object.entries(data.contracts)) {
|
|
37
|
+
console.log(` ${theme.gold(chain.padEnd(10))} ${theme.dim(addr)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
console.log('');
|
|
41
|
+
info('Docs: https://acp.darksol.net/facilitator');
|
|
17
42
|
} catch (err) {
|
|
18
43
|
spin.fail('Facilitator unreachable');
|
|
19
44
|
error(err.message);
|
|
45
|
+
info('Check: https://facilitator.darksol.net');
|
|
20
46
|
}
|
|
21
47
|
}
|
|
22
48
|
|
|
23
49
|
export async function facilitatorVerify(payment) {
|
|
24
50
|
const spin = spinner('Verifying payment...').start();
|
|
25
51
|
try {
|
|
26
|
-
const data = await fetchJSON(`${getURL()}/
|
|
52
|
+
const data = await fetchJSON(`${getURL()}/verify`, {
|
|
27
53
|
method: 'POST',
|
|
28
54
|
headers: { 'Content-Type': 'application/json' },
|
|
29
55
|
body: JSON.stringify(typeof payment === 'string' ? JSON.parse(payment) : payment),
|
|
@@ -41,7 +67,7 @@ export async function facilitatorVerify(payment) {
|
|
|
41
67
|
export async function facilitatorSettle(payment) {
|
|
42
68
|
const spin = spinner('Settling on-chain...').start();
|
|
43
69
|
try {
|
|
44
|
-
const data = await fetchJSON(`${getURL()}/
|
|
70
|
+
const data = await fetchJSON(`${getURL()}/settle`, {
|
|
45
71
|
method: 'POST',
|
|
46
72
|
headers: { 'Content-Type': 'application/json' },
|
|
47
73
|
body: JSON.stringify(typeof payment === 'string' ? JSON.parse(payment) : payment),
|
package/src/web/commands.js
CHANGED
|
@@ -457,7 +457,7 @@ export async function handleCommand(cmd, ws) {
|
|
|
457
457
|
return await cmdChatLogs(args, ws);
|
|
458
458
|
default: {
|
|
459
459
|
// Fuzzy: if it looks like natural language, route to AI
|
|
460
|
-
const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca|order|card|prepaid|visa|mastercard)\b/i;
|
|
460
|
+
const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca|order|card|prepaid|visa|mastercard|bet|coinflip|flip|dice|slots|hilo|gamble|play|casino)\b/i;
|
|
461
461
|
if (nlKeywords.test(cmd)) {
|
|
462
462
|
return await cmdAI(cmd.split(/\s+/), ws);
|
|
463
463
|
}
|
|
@@ -1138,34 +1138,55 @@ async function cmdOracle(args, ws) {
|
|
|
1138
1138
|
}
|
|
1139
1139
|
|
|
1140
1140
|
async function cmdCasino(args, ws) {
|
|
1141
|
+
ws.sendLine(`${ANSI.gold} ◆ THE CLAWSINO 🎰${ANSI.reset}`);
|
|
1142
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1141
1143
|
try {
|
|
1142
|
-
const resp = await fetch('https://casino.darksol.net/
|
|
1144
|
+
const resp = await fetch('https://casino.darksol.net/api/stats');
|
|
1145
|
+
const ct = resp.headers.get('content-type') || '';
|
|
1146
|
+
if (!ct.includes('json')) throw new Error('not json');
|
|
1143
1147
|
const data = await resp.json();
|
|
1144
1148
|
|
|
1145
|
-
ws.sendLine(`${ANSI.
|
|
1146
|
-
ws.sendLine(
|
|
1147
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1148
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1149
|
+
ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${data.acceptingBets ? `${ANSI.green}● Open${ANSI.reset}` : `${ANSI.red}○ Closed${ANSI.reset}`}`);
|
|
1150
|
+
ws.sendLine(` ${ANSI.darkGold}House${ANSI.reset} ${ANSI.white}$${data.houseBalanceUsdc || '0'} USDC${ANSI.reset}`);
|
|
1151
|
+
ws.sendLine(` ${ANSI.darkGold}Total Bets${ANSI.reset} ${ANSI.white}${data.totalBets || 0}${ANSI.reset}`);
|
|
1152
|
+
ws.sendLine(` ${ANSI.darkGold}Win Rate${ANSI.reset} ${ANSI.white}${data.winRate || '0%'}${ANSI.reset}`);
|
|
1153
|
+
ws.sendLine('');
|
|
1154
|
+
ws.sendLine(` ${ANSI.gold}GAMES${ANSI.reset} ${ANSI.dim}All bets $1 USDC • 5% house edge${ANSI.reset}`);
|
|
1155
|
+
ws.sendLine(` ${ANSI.white}🪙 Coin Flip${ANSI.reset} ${ANSI.dim}1.90x — heads or tails${ANSI.reset}`);
|
|
1156
|
+
ws.sendLine(` ${ANSI.white}🎲 Dice${ANSI.reset} ${ANSI.dim}variable — over/under 2-5${ANSI.reset}`);
|
|
1157
|
+
ws.sendLine(` ${ANSI.white}🃏 Hi-Lo${ANSI.reset} ${ANSI.dim}~2.06x — higher or lower${ANSI.reset}`);
|
|
1158
|
+
ws.sendLine(` ${ANSI.white}🎰 Slots${ANSI.reset} ${ANSI.dim}1.50-5.00x — match symbols${ANSI.reset}`);
|
|
1159
|
+
ws.sendLine('');
|
|
1160
|
+
ws.sendLine(` ${ANSI.dim}Play via CLI: darksol casino bet${ANSI.reset}`);
|
|
1161
|
+
ws.sendLine(` ${ANSI.dim}Or ask AI: "flip a coin" / "bet on heads"${ANSI.reset}`);
|
|
1149
1162
|
ws.sendLine('');
|
|
1150
1163
|
} catch {
|
|
1151
|
-
ws.sendLine(` ${ANSI.
|
|
1164
|
+
ws.sendLine(` ${ANSI.red}○ Casino unreachable${ANSI.reset}`);
|
|
1165
|
+
ws.sendLine(` ${ANSI.dim}Check: https://casino.darksol.net/docs${ANSI.reset}`);
|
|
1152
1166
|
ws.sendLine('');
|
|
1153
1167
|
}
|
|
1154
1168
|
return {};
|
|
1155
1169
|
}
|
|
1156
1170
|
|
|
1157
1171
|
async function cmdFacilitator(args, ws) {
|
|
1172
|
+
ws.sendLine(`${ANSI.gold} ◆ x402 FACILITATOR${ANSI.reset}`);
|
|
1173
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1158
1174
|
try {
|
|
1159
|
-
const resp = await fetch('https://facilitator.darksol.net/
|
|
1175
|
+
const resp = await fetch('https://facilitator.darksol.net/');
|
|
1176
|
+
const ct = resp.headers.get('content-type') || '';
|
|
1177
|
+
if (!ct.includes('json')) throw new Error('not json');
|
|
1160
1178
|
const data = await resp.json();
|
|
1161
1179
|
|
|
1162
|
-
ws.sendLine(
|
|
1163
|
-
ws.sendLine(
|
|
1164
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1165
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1180
|
+
ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${ANSI.green}● Online${ANSI.reset}`);
|
|
1181
|
+
ws.sendLine(` ${ANSI.darkGold}Service${ANSI.reset} ${ANSI.white}${data.service || 'DARKSOL Facilitator'}${ANSI.reset}`);
|
|
1182
|
+
ws.sendLine(` ${ANSI.darkGold}Protocol${ANSI.reset} ${ANSI.white}${data.protocol || 'x402'}${ANSI.reset}`);
|
|
1183
|
+
ws.sendLine(` ${ANSI.darkGold}Fee${ANSI.reset} ${ANSI.green}${data.fee || '0%'}${ANSI.reset}`);
|
|
1184
|
+
ws.sendLine(` ${ANSI.darkGold}Chains${ANSI.reset} ${ANSI.white}${Array.isArray(data.chains) ? data.chains.map(c => `${c.chain} (${c.status})`) .join(', ') : 'Base, Polygon'}${ANSI.reset}`);
|
|
1185
|
+
ws.sendLine(` ${ANSI.darkGold}Docs${ANSI.reset} ${ANSI.blue}acp.darksol.net/facilitator${ANSI.reset}`);
|
|
1166
1186
|
ws.sendLine('');
|
|
1167
1187
|
} catch {
|
|
1168
|
-
ws.sendLine(` ${ANSI.
|
|
1188
|
+
ws.sendLine(` ${ANSI.red}○ Facilitator unreachable${ANSI.reset}`);
|
|
1189
|
+
ws.sendLine(` ${ANSI.dim}Check: https://acp.darksol.net/facilitator${ANSI.reset}`);
|
|
1169
1190
|
ws.sendLine('');
|
|
1170
1191
|
}
|
|
1171
1192
|
return {};
|