@darksol/terminal 0.5.9 → 0.6.1
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 +7 -2
- package/src/services/oracle.js +103 -20
- package/src/web/commands.js +36 -15
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
|
@@ -29,7 +29,7 @@ const config = new Conf({
|
|
|
29
29
|
services: {
|
|
30
30
|
type: 'object',
|
|
31
31
|
default: {
|
|
32
|
-
oracle: 'https://acp.darksol.net/oracle',
|
|
32
|
+
oracle: 'https://acp.darksol.net/api/oracle',
|
|
33
33
|
casino: 'https://casino.darksol.net',
|
|
34
34
|
cards: 'https://acp.darksol.net',
|
|
35
35
|
facilitator: 'https://facilitator.darksol.net',
|
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
|
-
|
|
@@ -19,14 +19,19 @@ export async function facilitatorHealth() {
|
|
|
19
19
|
['Version', data.version || '-'],
|
|
20
20
|
['Protocol', data.protocol || 'x402'],
|
|
21
21
|
['Fee', data.fee || '0%'],
|
|
22
|
-
['Chains', Array.isArray(data.chains) ? data.chains.join(', ') : (data.chains || 'Base, Polygon')],
|
|
22
|
+
['Chains', Array.isArray(data.chains) ? data.chains.map(c => c.chain || c).join(', ') : (data.chains || 'Base, Polygon')],
|
|
23
23
|
['Status', theme.success('● Online')],
|
|
24
24
|
]);
|
|
25
25
|
if (data.description) {
|
|
26
26
|
console.log('');
|
|
27
27
|
console.log(theme.dim(` ${data.description}`));
|
|
28
28
|
}
|
|
29
|
-
if (data.
|
|
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) {
|
|
30
35
|
console.log('');
|
|
31
36
|
for (const [chain, addr] of Object.entries(data.contracts)) {
|
|
32
37
|
console.log(` ${theme.gold(chain.padEnd(10))} ${theme.dim(addr)}`);
|
package/src/services/oracle.js
CHANGED
|
@@ -1,16 +1,97 @@
|
|
|
1
1
|
import { fetchJSON } from '../utils/fetch.js';
|
|
2
2
|
import fetch from 'node-fetch';
|
|
3
|
-
import { getServiceURL } from '../config/store.js';
|
|
3
|
+
import { getServiceURL, getConfig } from '../config/store.js';
|
|
4
4
|
import { theme } from '../ui/theme.js';
|
|
5
|
-
import { spinner, kvDisplay, success, error } from '../ui/components.js';
|
|
5
|
+
import { spinner, kvDisplay, success, error, warn, info } from '../ui/components.js';
|
|
6
6
|
import { showSection } from '../ui/banner.js';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Oracle lives under acp.darksol.net/api/oracle/
|
|
9
|
+
// Endpoints: health (free), coin/dice/number/shuffle (x402 — $0.05 USDC on Base)
|
|
10
|
+
const getURL = () => {
|
|
11
|
+
const custom = getServiceURL('oracle');
|
|
12
|
+
if (custom) return custom;
|
|
13
|
+
return 'https://acp.darksol.net/api/oracle';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function handleX402(response) {
|
|
17
|
+
if (response === 402 || (response && response.status === 402)) {
|
|
18
|
+
warn('Oracle requires x402 payment ($0.05 USDC on Base)');
|
|
19
|
+
info('Use with agent signer: darksol signer start → requests auto-pay');
|
|
20
|
+
info('Or pay manually via facilitator: darksol facilitator');
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function oracleRequest(path, opts = {}) {
|
|
27
|
+
const url = `${getURL()}${path}`;
|
|
28
|
+
const resp = await fetch(url, opts);
|
|
29
|
+
|
|
30
|
+
if (resp.status === 402) {
|
|
31
|
+
// Return x402 info so caller can handle
|
|
32
|
+
const paymentHeader = resp.headers.get('payment-required');
|
|
33
|
+
let paymentInfo = null;
|
|
34
|
+
if (paymentHeader) {
|
|
35
|
+
try {
|
|
36
|
+
paymentInfo = JSON.parse(Buffer.from(paymentHeader, 'base64').toString());
|
|
37
|
+
} catch {}
|
|
38
|
+
}
|
|
39
|
+
return { x402: true, paymentInfo, status: 402 };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ct = resp.headers.get('content-type') || '';
|
|
43
|
+
if (!ct.includes('json')) {
|
|
44
|
+
throw new Error(`Oracle returned non-JSON response (${resp.status})`);
|
|
45
|
+
}
|
|
46
|
+
return await resp.json();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function oracleHealth() {
|
|
50
|
+
const spin = spinner('Checking oracle...').start();
|
|
51
|
+
try {
|
|
52
|
+
const data = await oracleRequest('/health');
|
|
53
|
+
if (data.x402) {
|
|
54
|
+
spin.succeed('Oracle online (health should be free)');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
spin.succeed('Oracle online');
|
|
58
|
+
|
|
59
|
+
showSection('ORACLE STATUS');
|
|
60
|
+
kvDisplay([
|
|
61
|
+
['Status', data.status === 'ok' ? theme.success('● Online') : theme.error('○ ' + data.status)],
|
|
62
|
+
['Contract', data.contract || '-'],
|
|
63
|
+
['Chain', data.chain || 'base'],
|
|
64
|
+
['Block', String(data.blockNumber || '-')],
|
|
65
|
+
]);
|
|
66
|
+
console.log('');
|
|
67
|
+
info('Endpoints require x402 payment ($0.05 USDC on Base)');
|
|
68
|
+
info('Games: coin flip, dice, random number, shuffle');
|
|
69
|
+
info('Docs: https://acp.darksol.net/oracle');
|
|
70
|
+
} catch (err) {
|
|
71
|
+
spin.fail('Oracle unreachable');
|
|
72
|
+
error(err.message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
9
75
|
|
|
10
76
|
export async function oracleFlip() {
|
|
11
77
|
const spin = spinner('Flipping coin...').start();
|
|
12
78
|
try {
|
|
13
|
-
const data = await
|
|
79
|
+
const data = await oracleRequest('/coin');
|
|
80
|
+
if (data.x402) {
|
|
81
|
+
spin.info('Payment required');
|
|
82
|
+
handleX402(data);
|
|
83
|
+
if (data.paymentInfo) {
|
|
84
|
+
const accepts = data.paymentInfo.accepts?.[0];
|
|
85
|
+
if (accepts) {
|
|
86
|
+
kvDisplay([
|
|
87
|
+
['Amount', `$${(parseInt(accepts.amount) / 1e6).toFixed(2)} USDC`],
|
|
88
|
+
['Network', 'Base'],
|
|
89
|
+
['Pay To', accepts.payTo || '-'],
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
14
95
|
spin.succeed('Coin flipped');
|
|
15
96
|
showSection('ORACLE — COIN FLIP');
|
|
16
97
|
kvDisplay([
|
|
@@ -26,7 +107,12 @@ export async function oracleFlip() {
|
|
|
26
107
|
export async function oracleDice(sides = 6) {
|
|
27
108
|
const spin = spinner(`Rolling d${sides}...`).start();
|
|
28
109
|
try {
|
|
29
|
-
const data = await
|
|
110
|
+
const data = await oracleRequest(`/dice?sides=${sides}`);
|
|
111
|
+
if (data.x402) {
|
|
112
|
+
spin.info('Payment required');
|
|
113
|
+
handleX402(data);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
30
116
|
spin.succeed('Dice rolled');
|
|
31
117
|
showSection(`ORACLE — D${sides}`);
|
|
32
118
|
kvDisplay([
|
|
@@ -43,7 +129,12 @@ export async function oracleDice(sides = 6) {
|
|
|
43
129
|
export async function oracleNumber(min = 1, max = 100) {
|
|
44
130
|
const spin = spinner(`Generating number ${min}-${max}...`).start();
|
|
45
131
|
try {
|
|
46
|
-
const data = await
|
|
132
|
+
const data = await oracleRequest(`/number?min=${min}&max=${max}`);
|
|
133
|
+
if (data.x402) {
|
|
134
|
+
spin.info('Payment required');
|
|
135
|
+
handleX402(data);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
47
138
|
spin.succeed('Number generated');
|
|
48
139
|
showSection('ORACLE — RANDOM NUMBER');
|
|
49
140
|
kvDisplay([
|
|
@@ -60,11 +151,16 @@ export async function oracleNumber(min = 1, max = 100) {
|
|
|
60
151
|
export async function oracleShuffle(items) {
|
|
61
152
|
const spin = spinner('Shuffling...').start();
|
|
62
153
|
try {
|
|
63
|
-
const data = await
|
|
154
|
+
const data = await oracleRequest('/shuffle', {
|
|
64
155
|
method: 'POST',
|
|
65
156
|
headers: { 'Content-Type': 'application/json' },
|
|
66
157
|
body: JSON.stringify({ items }),
|
|
67
158
|
});
|
|
159
|
+
if (data.x402) {
|
|
160
|
+
spin.info('Payment required');
|
|
161
|
+
handleX402(data);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
68
164
|
spin.succeed('Shuffled');
|
|
69
165
|
showSection('ORACLE — SHUFFLE');
|
|
70
166
|
console.log(theme.gold(' Result: ') + (data.result || data.value || []).join(', '));
|
|
@@ -73,16 +169,3 @@ export async function oracleShuffle(items) {
|
|
|
73
169
|
error(err.message);
|
|
74
170
|
}
|
|
75
171
|
}
|
|
76
|
-
|
|
77
|
-
export async function oracleHealth() {
|
|
78
|
-
const spin = spinner('Checking oracle...').start();
|
|
79
|
-
try {
|
|
80
|
-
const data = await fetchJSON(`${getURL()}/api/health`);
|
|
81
|
-
spin.succeed('Oracle online');
|
|
82
|
-
showSection('ORACLE STATUS');
|
|
83
|
-
kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
|
|
84
|
-
} catch (err) {
|
|
85
|
-
spin.fail('Oracle unreachable');
|
|
86
|
-
error(err.message);
|
|
87
|
-
}
|
|
88
|
-
}
|
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
|
}
|
|
@@ -1121,24 +1121,38 @@ async function executeCardOrder(orderMeta, ws) {
|
|
|
1121
1121
|
// ORACLE
|
|
1122
1122
|
// ══════════════════════════════════════════════════
|
|
1123
1123
|
async function cmdOracle(args, ws) {
|
|
1124
|
+
ws.sendLine(`${ANSI.gold} ◆ RANDOM ORACLE 🎲${ANSI.reset}`);
|
|
1125
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1124
1126
|
try {
|
|
1125
|
-
const resp = await fetch('https://acp.darksol.net/oracle');
|
|
1127
|
+
const resp = await fetch('https://acp.darksol.net/api/oracle/health');
|
|
1128
|
+
const ct = resp.headers.get('content-type') || '';
|
|
1129
|
+
if (!ct.includes('json')) throw new Error('not json');
|
|
1126
1130
|
const data = await resp.json();
|
|
1127
1131
|
|
|
1128
|
-
ws.sendLine(`${ANSI.
|
|
1129
|
-
ws.sendLine(
|
|
1130
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1131
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1132
|
+
ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${data.status === 'ok' ? `${ANSI.green}● Online${ANSI.reset}` : `${ANSI.red}○ ${data.status}${ANSI.reset}`}`);
|
|
1133
|
+
ws.sendLine(` ${ANSI.darkGold}Contract${ANSI.reset} ${ANSI.dim}${data.contract || '-'}${ANSI.reset}`);
|
|
1134
|
+
ws.sendLine(` ${ANSI.darkGold}Chain${ANSI.reset} ${ANSI.white}${data.chain || 'base'}${ANSI.reset}`);
|
|
1135
|
+
ws.sendLine(` ${ANSI.darkGold}Block${ANSI.reset} ${ANSI.white}${data.blockNumber || '-'}${ANSI.reset}`);
|
|
1136
|
+
ws.sendLine('');
|
|
1137
|
+
ws.sendLine(` ${ANSI.gold}ENDPOINTS${ANSI.reset} ${ANSI.dim}x402-gated ($0.05 USDC on Base)${ANSI.reset}`);
|
|
1138
|
+
ws.sendLine(` ${ANSI.white}🪙 /coin${ANSI.reset} ${ANSI.dim}Fair coin flip${ANSI.reset}`);
|
|
1139
|
+
ws.sendLine(` ${ANSI.white}🎲 /dice${ANSI.reset} ${ANSI.dim}Roll with N sides${ANSI.reset}`);
|
|
1140
|
+
ws.sendLine(` ${ANSI.white}🔢 /number${ANSI.reset} ${ANSI.dim}Random in range${ANSI.reset}`);
|
|
1141
|
+
ws.sendLine(` ${ANSI.white}🔀 /shuffle${ANSI.reset} ${ANSI.dim}Shuffle a list${ANSI.reset}`);
|
|
1142
|
+
ws.sendLine('');
|
|
1143
|
+
ws.sendLine(` ${ANSI.dim}CLI: darksol oracle flip / dice / number / shuffle${ANSI.reset}`);
|
|
1144
|
+
ws.sendLine(` ${ANSI.dim}Docs: https://acp.darksol.net/oracle${ANSI.reset}`);
|
|
1132
1145
|
ws.sendLine('');
|
|
1133
1146
|
} catch {
|
|
1134
|
-
ws.sendLine(` ${ANSI.
|
|
1147
|
+
ws.sendLine(` ${ANSI.red}○ Oracle unreachable${ANSI.reset}`);
|
|
1148
|
+
ws.sendLine(` ${ANSI.dim}Check: https://acp.darksol.net/oracle${ANSI.reset}`);
|
|
1135
1149
|
ws.sendLine('');
|
|
1136
1150
|
}
|
|
1137
1151
|
return {};
|
|
1138
1152
|
}
|
|
1139
1153
|
|
|
1140
1154
|
async function cmdCasino(args, ws) {
|
|
1141
|
-
ws.sendLine(`${ANSI.gold} ◆
|
|
1155
|
+
ws.sendLine(`${ANSI.gold} ◆ THE CLAWSINO 🎰${ANSI.reset}`);
|
|
1142
1156
|
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1143
1157
|
try {
|
|
1144
1158
|
const resp = await fetch('https://casino.darksol.net/api/stats');
|
|
@@ -1146,12 +1160,19 @@ async function cmdCasino(args, ws) {
|
|
|
1146
1160
|
if (!ct.includes('json')) throw new Error('not json');
|
|
1147
1161
|
const data = await resp.json();
|
|
1148
1162
|
|
|
1149
|
-
ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset}
|
|
1150
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1151
|
-
ws.sendLine(` ${ANSI.darkGold}Total Bets${ANSI.reset}
|
|
1152
|
-
ws.sendLine(` ${ANSI.darkGold}Win Rate${ANSI.reset}
|
|
1153
|
-
ws.sendLine(
|
|
1154
|
-
ws.sendLine(` ${ANSI.
|
|
1163
|
+
ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${data.acceptingBets ? `${ANSI.green}● Open${ANSI.reset}` : `${ANSI.red}○ Closed${ANSI.reset}`}`);
|
|
1164
|
+
ws.sendLine(` ${ANSI.darkGold}House${ANSI.reset} ${ANSI.white}$${data.houseBalanceUsdc || '0'} USDC${ANSI.reset}`);
|
|
1165
|
+
ws.sendLine(` ${ANSI.darkGold}Total Bets${ANSI.reset} ${ANSI.white}${data.totalBets || 0}${ANSI.reset}`);
|
|
1166
|
+
ws.sendLine(` ${ANSI.darkGold}Win Rate${ANSI.reset} ${ANSI.white}${data.winRate || '0%'}${ANSI.reset}`);
|
|
1167
|
+
ws.sendLine('');
|
|
1168
|
+
ws.sendLine(` ${ANSI.gold}GAMES${ANSI.reset} ${ANSI.dim}All bets $1 USDC • 5% house edge${ANSI.reset}`);
|
|
1169
|
+
ws.sendLine(` ${ANSI.white}🪙 Coin Flip${ANSI.reset} ${ANSI.dim}1.90x — heads or tails${ANSI.reset}`);
|
|
1170
|
+
ws.sendLine(` ${ANSI.white}🎲 Dice${ANSI.reset} ${ANSI.dim}variable — over/under 2-5${ANSI.reset}`);
|
|
1171
|
+
ws.sendLine(` ${ANSI.white}🃏 Hi-Lo${ANSI.reset} ${ANSI.dim}~2.06x — higher or lower${ANSI.reset}`);
|
|
1172
|
+
ws.sendLine(` ${ANSI.white}🎰 Slots${ANSI.reset} ${ANSI.dim}1.50-5.00x — match symbols${ANSI.reset}`);
|
|
1173
|
+
ws.sendLine('');
|
|
1174
|
+
ws.sendLine(` ${ANSI.dim}Play via CLI: darksol casino bet${ANSI.reset}`);
|
|
1175
|
+
ws.sendLine(` ${ANSI.dim}Or ask AI: "flip a coin" / "bet on heads"${ANSI.reset}`);
|
|
1155
1176
|
ws.sendLine('');
|
|
1156
1177
|
} catch {
|
|
1157
1178
|
ws.sendLine(` ${ANSI.red}○ Casino unreachable${ANSI.reset}`);
|
|
@@ -1174,7 +1195,7 @@ async function cmdFacilitator(args, ws) {
|
|
|
1174
1195
|
ws.sendLine(` ${ANSI.darkGold}Service${ANSI.reset} ${ANSI.white}${data.service || 'DARKSOL Facilitator'}${ANSI.reset}`);
|
|
1175
1196
|
ws.sendLine(` ${ANSI.darkGold}Protocol${ANSI.reset} ${ANSI.white}${data.protocol || 'x402'}${ANSI.reset}`);
|
|
1176
1197
|
ws.sendLine(` ${ANSI.darkGold}Fee${ANSI.reset} ${ANSI.green}${data.fee || '0%'}${ANSI.reset}`);
|
|
1177
|
-
ws.sendLine(` ${ANSI.darkGold}Chains${ANSI.reset} ${ANSI.white}${Array.isArray(data.chains) ? data.chains.join(', ') : 'Base, Polygon'}${ANSI.reset}`);
|
|
1198
|
+
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}`);
|
|
1178
1199
|
ws.sendLine(` ${ANSI.darkGold}Docs${ANSI.reset} ${ANSI.blue}acp.darksol.net/facilitator${ANSI.reset}`);
|
|
1179
1200
|
ws.sendLine('');
|
|
1180
1201
|
} catch {
|