@ape-church/skill 1.0.3 ā 1.0.5
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/HEARTBEAT.md +76 -37
- package/README.md +27 -42
- package/SKILL.md +108 -105
- package/assets/HEARTBEAT.md +76 -37
- package/assets/SKILL.md +108 -105
- package/bin/cli.js +613 -143
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -414,7 +414,7 @@ function getStrategyConfig(strategy) {
|
|
|
414
414
|
);
|
|
415
415
|
const configs = {
|
|
416
416
|
conservative: {
|
|
417
|
-
minBetApe:
|
|
417
|
+
minBetApe: 1,
|
|
418
418
|
targetBetPct: 0.05,
|
|
419
419
|
maxBetPct: 0.1,
|
|
420
420
|
baseCooldownMs: 60 * 1000, // 60 seconds
|
|
@@ -423,7 +423,7 @@ function getStrategyConfig(strategy) {
|
|
|
423
423
|
gameWeights: defaultWeights,
|
|
424
424
|
},
|
|
425
425
|
balanced: {
|
|
426
|
-
minBetApe:
|
|
426
|
+
minBetApe: 1,
|
|
427
427
|
targetBetPct: 0.08,
|
|
428
428
|
maxBetPct: 0.15,
|
|
429
429
|
baseCooldownMs: 30 * 1000, // 30 seconds
|
|
@@ -432,7 +432,7 @@ function getStrategyConfig(strategy) {
|
|
|
432
432
|
gameWeights: defaultWeights,
|
|
433
433
|
},
|
|
434
434
|
aggressive: {
|
|
435
|
-
minBetApe:
|
|
435
|
+
minBetApe: 1,
|
|
436
436
|
targetBetPct: 0.12,
|
|
437
437
|
maxBetPct: 0.25,
|
|
438
438
|
baseCooldownMs: 15 * 1000, // 15 seconds
|
|
@@ -441,7 +441,7 @@ function getStrategyConfig(strategy) {
|
|
|
441
441
|
gameWeights: defaultWeights,
|
|
442
442
|
},
|
|
443
443
|
degen: {
|
|
444
|
-
minBetApe:
|
|
444
|
+
minBetApe: 1,
|
|
445
445
|
targetBetPct: 0.2,
|
|
446
446
|
maxBetPct: 0.35,
|
|
447
447
|
baseCooldownMs: 10 * 1000, // 10 seconds
|
|
@@ -856,26 +856,75 @@ program
|
|
|
856
856
|
console.log('You can retry later with: apechurch register --username <NAME>');
|
|
857
857
|
}
|
|
858
858
|
|
|
859
|
-
// 4. The Handshake
|
|
860
|
-
console.log('\
|
|
861
|
-
console.log('
|
|
862
|
-
console.log(
|
|
863
|
-
console.log(`USERNAME: ${username}`);
|
|
864
|
-
if (!usernameWasProvided) {
|
|
865
|
-
console.log(' (Change anytime: apechurch register --username <YOUR_NAME>)');
|
|
866
|
-
}
|
|
867
|
-
console.log(`PERSONA: ${persona}`);
|
|
859
|
+
// 4. The Handshake
|
|
860
|
+
console.log('\n========================================');
|
|
861
|
+
console.log('š° APE CHURCH - SETUP COMPLETE');
|
|
862
|
+
console.log('========================================');
|
|
868
863
|
console.log('');
|
|
869
|
-
console.log('
|
|
870
|
-
console.log('
|
|
871
|
-
console.log(
|
|
872
|
-
|
|
873
|
-
);
|
|
874
|
-
console.log('
|
|
875
|
-
console.log('
|
|
876
|
-
console.log('
|
|
877
|
-
console.log(
|
|
878
|
-
console.log(
|
|
864
|
+
console.log('Ape Church is a FULLY on-chain, 100% decentralized casino on ApeChain.');
|
|
865
|
+
console.log('Every bet is placed and settled on-chain. Zero servers.');
|
|
866
|
+
console.log('');
|
|
867
|
+
console.log('Player vs Player: The "house" is a public staking pool where anyone');
|
|
868
|
+
console.log('can deposit or withdraw APE anytime. You\'re playing against other');
|
|
869
|
+
console.log('players, not the house. Truly PvP.');
|
|
870
|
+
console.log('');
|
|
871
|
+
console.log('YOUR AGENT:');
|
|
872
|
+
console.log(` Address: ${address}`);
|
|
873
|
+
console.log(` Username: ${username}`);
|
|
874
|
+
console.log(` Strategy: ${persona}`);
|
|
875
|
+
console.log('');
|
|
876
|
+
console.log('========================================');
|
|
877
|
+
console.log('STEP 1: FUND YOUR AGENT');
|
|
878
|
+
console.log('========================================');
|
|
879
|
+
console.log('Send APE (on ApeChain) to the address above.');
|
|
880
|
+
console.log('Bridge: https://relay.link/bridge/apechain');
|
|
881
|
+
console.log('');
|
|
882
|
+
console.log('========================================');
|
|
883
|
+
console.log('STEP 2: START PLAYING');
|
|
884
|
+
console.log('========================================');
|
|
885
|
+
console.log('Easiest way - just run:');
|
|
886
|
+
console.log('');
|
|
887
|
+
console.log(' apechurch play --loop');
|
|
888
|
+
console.log('');
|
|
889
|
+
console.log('Or tell your AI agent (copy this message):');
|
|
890
|
+
console.log('');
|
|
891
|
+
console.log('---START MESSAGE---');
|
|
892
|
+
console.log(`I've installed the Ape Church skill for you. Read the skill file at`);
|
|
893
|
+
console.log(`~/.openclaw/skills/ape-church/SKILL.md to understand how to play.`);
|
|
894
|
+
console.log(`Then run: apechurch play --loop`);
|
|
895
|
+
console.log(`This will play casino games continuously on ApeChain.`);
|
|
896
|
+
console.log('---END MESSAGE---');
|
|
897
|
+
console.log('');
|
|
898
|
+
console.log('========================================');
|
|
899
|
+
console.log('QUICK REFERENCE');
|
|
900
|
+
console.log('========================================');
|
|
901
|
+
console.log('PLAY:');
|
|
902
|
+
console.log(' apechurch play Play one game');
|
|
903
|
+
console.log(' apechurch play --loop Play continuously (2s between games)');
|
|
904
|
+
console.log('');
|
|
905
|
+
console.log('GAMES (auto-selected, or use --game):');
|
|
906
|
+
console.log(' jungle-plinko Plinko (--mode 0-4, --balls 1-100)');
|
|
907
|
+
console.log(' dino-dough Slots (--spins 1-15)');
|
|
908
|
+
console.log(' bubblegum-heist Slots (--spins 1-15)');
|
|
909
|
+
console.log('');
|
|
910
|
+
console.log('STRATEGIES (controls bet sizing):');
|
|
911
|
+
console.log(' conservative 5% of balance per bet');
|
|
912
|
+
console.log(' balanced 8% of balance per bet (default)');
|
|
913
|
+
console.log(' aggressive 12% of balance per bet');
|
|
914
|
+
console.log(' degen 20% of balance per bet');
|
|
915
|
+
console.log('');
|
|
916
|
+
console.log('CONTROL:');
|
|
917
|
+
console.log(' apechurch status Check balance');
|
|
918
|
+
console.log(' apechurch pause Pause play');
|
|
919
|
+
console.log(' apechurch resume Resume play');
|
|
920
|
+
console.log(' apechurch games List all games');
|
|
921
|
+
console.log(' apechurch commands Full command reference');
|
|
922
|
+
console.log(' apechurch help Show help');
|
|
923
|
+
console.log('');
|
|
924
|
+
console.log('CHANGE SETTINGS:');
|
|
925
|
+
console.log(' apechurch register --username <NAME>');
|
|
926
|
+
console.log(' apechurch profile set --persona <STRATEGY>');
|
|
927
|
+
console.log('========================================');
|
|
879
928
|
});
|
|
880
929
|
|
|
881
930
|
// --- COMMAND: STATUS (The Agent Experience) ---
|
|
@@ -1083,163 +1132,584 @@ program
|
|
|
1083
1132
|
// --- COMMAND: HEARTBEAT (Autonomous Loop) ---
|
|
1084
1133
|
program
|
|
1085
1134
|
.command('heartbeat')
|
|
1086
|
-
.option('--strategy <name>', 'conservative | balanced | aggressive')
|
|
1135
|
+
.option('--strategy <name>', 'conservative | balanced | aggressive | degen')
|
|
1087
1136
|
.option('--cooldown <ms>', 'Minimum ms between plays (0 = use strategy cooldown)', '0')
|
|
1088
1137
|
.option('--timeout <ms>', 'Max ms to wait for GameEnded event. Use 0 to wait indefinitely.', '0')
|
|
1138
|
+
.option('--loop', 'Run continuously until paused or stopped (Ctrl+C)')
|
|
1089
1139
|
.option('--json', 'Output JSON only')
|
|
1090
1140
|
.action(async (opts) => {
|
|
1091
1141
|
const account = getWallet();
|
|
1092
|
-
const
|
|
1093
|
-
const
|
|
1094
|
-
const
|
|
1142
|
+
const requestedCooldown = parseNonNegativeInt(opts.cooldown, 'cooldown');
|
|
1143
|
+
const timeoutMs = parseNonNegativeInt(opts.timeout, 'timeout');
|
|
1144
|
+
const loopMode = Boolean(opts.loop);
|
|
1145
|
+
|
|
1146
|
+
if (loopMode && !opts.json) {
|
|
1147
|
+
console.log('š° Starting continuous play mode (Ctrl+C to stop)...\n');
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Main play function - returns cooldown to wait (0 = no wait needed)
|
|
1151
|
+
async function runHeartbeat() {
|
|
1152
|
+
const state = loadState();
|
|
1153
|
+
const profile = loadProfile();
|
|
1154
|
+
const now = Date.now();
|
|
1155
|
+
|
|
1156
|
+
// Check if paused
|
|
1157
|
+
if (profile.paused) {
|
|
1158
|
+
state.lastHeartbeat = now;
|
|
1159
|
+
saveState(state);
|
|
1160
|
+
const response = {
|
|
1161
|
+
action: 'heartbeat',
|
|
1162
|
+
status: 'skipped',
|
|
1163
|
+
reason: 'paused',
|
|
1164
|
+
message: 'Autonomous play is paused. Run `apechurch resume` to continue.',
|
|
1165
|
+
address: account.address,
|
|
1166
|
+
paused: true,
|
|
1167
|
+
};
|
|
1168
|
+
if (opts.json) console.log(JSON.stringify(response));
|
|
1169
|
+
else console.log(JSON.stringify(response, null, 2));
|
|
1170
|
+
return { shouldStop: true, waitMs: 0 };
|
|
1171
|
+
}
|
|
1095
1172
|
|
|
1096
|
-
// Check if paused - skip gracefully without fetching balance
|
|
1097
|
-
if (profile.paused) {
|
|
1098
1173
|
state.lastHeartbeat = now;
|
|
1099
|
-
|
|
1100
|
-
|
|
1174
|
+
if (opts.strategy) state.strategy = normalizeStrategy(opts.strategy);
|
|
1175
|
+
else if (profile.persona) state.strategy = normalizeStrategy(profile.persona);
|
|
1176
|
+
if (requestedCooldown > 0) state.cooldownMs = requestedCooldown;
|
|
1177
|
+
|
|
1178
|
+
const { publicClient } = createClients();
|
|
1179
|
+
let balance;
|
|
1180
|
+
try {
|
|
1181
|
+
balance = await publicClient.getBalance({ address: account.address });
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
console.error(JSON.stringify({ error: `Failed to fetch balance: ${sanitizeError(error)}` }));
|
|
1184
|
+
return { shouldStop: true, waitMs: 0 };
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const balanceApe = parseFloat(formatEther(balance));
|
|
1188
|
+
const availableApe = Math.max(balanceApe - GAS_RESERVE_APE, 0);
|
|
1189
|
+
const strategy = normalizeStrategy(state.strategy);
|
|
1190
|
+
const strategyConfig = applyProfileOverrides(
|
|
1191
|
+
getStrategyConfig(strategy),
|
|
1192
|
+
profile.overrides
|
|
1193
|
+
);
|
|
1194
|
+
const dynamicCooldownMs = computeCooldownMs(strategyConfig, state);
|
|
1195
|
+
const cooldownMs = requestedCooldown > 0 ? requestedCooldown : dynamicCooldownMs;
|
|
1196
|
+
|
|
1197
|
+
const baseResponse = {
|
|
1101
1198
|
action: 'heartbeat',
|
|
1102
|
-
|
|
1103
|
-
reason: 'paused',
|
|
1104
|
-
message: 'Autonomous play is paused. Run `apechurch resume` to continue.',
|
|
1199
|
+
strategy,
|
|
1105
1200
|
address: account.address,
|
|
1106
|
-
|
|
1201
|
+
balance_ape: balanceApe.toFixed(6),
|
|
1202
|
+
available_ape: availableApe.toFixed(6),
|
|
1203
|
+
gas_reserve_ape: GAS_RESERVE_APE.toFixed(6),
|
|
1204
|
+
paused: false,
|
|
1205
|
+
last_play: state.lastPlay,
|
|
1206
|
+
cooldown_ms: cooldownMs,
|
|
1207
|
+
consecutive_wins: state.consecutiveWins,
|
|
1208
|
+
consecutive_losses: state.consecutiveLosses,
|
|
1107
1209
|
};
|
|
1108
|
-
if (opts.json) console.log(JSON.stringify(response));
|
|
1109
|
-
else console.log(JSON.stringify(response, null, 2));
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
1210
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1211
|
+
if (availableApe <= 0 || availableApe < strategyConfig.minBetApe) {
|
|
1212
|
+
saveState(state);
|
|
1213
|
+
const response = {
|
|
1214
|
+
...baseResponse,
|
|
1215
|
+
status: 'skipped',
|
|
1216
|
+
reason: 'insufficient_available_ape',
|
|
1217
|
+
};
|
|
1218
|
+
if (opts.json) console.log(JSON.stringify(response));
|
|
1219
|
+
else console.log(JSON.stringify(response, null, 2));
|
|
1220
|
+
return { shouldStop: true, waitMs: 0 };
|
|
1221
|
+
}
|
|
1119
1222
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1223
|
+
if (state.lastPlay && cooldownMs > 0 && now - state.lastPlay < cooldownMs) {
|
|
1224
|
+
const waitMs = Math.max(cooldownMs - (now - state.lastPlay), 0);
|
|
1225
|
+
saveState(state);
|
|
1226
|
+
const response = {
|
|
1227
|
+
...baseResponse,
|
|
1228
|
+
status: 'skipped',
|
|
1229
|
+
reason: 'cooldown',
|
|
1230
|
+
next_play_after_ms: waitMs,
|
|
1231
|
+
};
|
|
1232
|
+
if (opts.json) console.log(JSON.stringify(response));
|
|
1233
|
+
else console.log(JSON.stringify(response, null, 2));
|
|
1234
|
+
return { shouldStop: false, waitMs };
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const wagerApe = calculateWager(availableApe, strategyConfig);
|
|
1238
|
+
if (wagerApe < strategyConfig.minBetApe) {
|
|
1239
|
+
saveState(state);
|
|
1240
|
+
const response = {
|
|
1241
|
+
...baseResponse,
|
|
1242
|
+
status: 'skipped',
|
|
1243
|
+
reason: 'wager_below_minimum',
|
|
1244
|
+
wager_ape: formatApeAmount(wagerApe),
|
|
1245
|
+
};
|
|
1246
|
+
if (opts.json) console.log(JSON.stringify(response));
|
|
1247
|
+
else console.log(JSON.stringify(response, null, 2));
|
|
1248
|
+
return { shouldStop: true, waitMs: 0 };
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const selection = selectGameAndConfig(strategyConfig);
|
|
1252
|
+
const wagerApeString = formatApeAmount(wagerApe);
|
|
1253
|
+
|
|
1254
|
+
try {
|
|
1255
|
+
const playResponse = await playGame({
|
|
1256
|
+
account,
|
|
1257
|
+
game: selection.game,
|
|
1258
|
+
amountApe: wagerApeString,
|
|
1259
|
+
mode: selection.mode,
|
|
1260
|
+
balls: selection.balls,
|
|
1261
|
+
spins: selection.spins,
|
|
1262
|
+
timeoutMs,
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
state.lastPlay = Date.now();
|
|
1266
|
+
if (playResponse?.result) {
|
|
1267
|
+
const pnlWei = (BigInt(playResponse.result.payout_wei) -
|
|
1268
|
+
BigInt(playResponse.result.buy_in_wei)).toString();
|
|
1269
|
+
state.totalPnLWei = addBigIntStrings(state.totalPnLWei, pnlWei);
|
|
1270
|
+
if (BigInt(pnlWei) >= 0n) {
|
|
1271
|
+
state.sessionWins += 1;
|
|
1272
|
+
state.consecutiveWins += 1;
|
|
1273
|
+
state.consecutiveLosses = 0;
|
|
1274
|
+
} else {
|
|
1275
|
+
state.sessionLosses += 1;
|
|
1276
|
+
state.consecutiveLosses += 1;
|
|
1277
|
+
state.consecutiveWins = 0;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
saveState(state);
|
|
1282
|
+
|
|
1283
|
+
// Recalculate cooldown after state update (may change due to win/loss streaks)
|
|
1284
|
+
const newCooldownMs = requestedCooldown > 0
|
|
1285
|
+
? requestedCooldown
|
|
1286
|
+
: computeCooldownMs(strategyConfig, state);
|
|
1287
|
+
|
|
1288
|
+
const response = {
|
|
1289
|
+
...baseResponse,
|
|
1290
|
+
cooldown_ms: newCooldownMs,
|
|
1291
|
+
consecutive_wins: state.consecutiveWins,
|
|
1292
|
+
consecutive_losses: state.consecutiveLosses,
|
|
1293
|
+
status: playResponse.status,
|
|
1294
|
+
wager_ape: wagerApeString,
|
|
1295
|
+
game: playResponse.game,
|
|
1296
|
+
config: playResponse.config,
|
|
1297
|
+
tx: playResponse.tx,
|
|
1298
|
+
gameId: playResponse.gameId,
|
|
1299
|
+
game_url: playResponse.game_url,
|
|
1300
|
+
result: playResponse.result,
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
if (opts.json) console.log(JSON.stringify(response));
|
|
1304
|
+
else console.log(JSON.stringify(response, null, 2));
|
|
1305
|
+
|
|
1306
|
+
return { shouldStop: false, waitMs: newCooldownMs };
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
saveState(state);
|
|
1309
|
+
console.error(JSON.stringify({ error: error.message }));
|
|
1310
|
+
return { shouldStop: true, waitMs: 0 };
|
|
1311
|
+
}
|
|
1127
1312
|
}
|
|
1128
1313
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1314
|
+
// Run once or loop
|
|
1315
|
+
if (!loopMode) {
|
|
1316
|
+
const result = await runHeartbeat();
|
|
1317
|
+
if (result.shouldStop && result.waitMs === 0) {
|
|
1318
|
+
// Error or fatal skip - exit with appropriate code
|
|
1319
|
+
const state = loadState();
|
|
1320
|
+
const profile = loadProfile();
|
|
1321
|
+
if (profile.paused) process.exit(0);
|
|
1322
|
+
}
|
|
1323
|
+
} else {
|
|
1324
|
+
// Loop mode
|
|
1325
|
+
let running = true;
|
|
1326
|
+
process.on('SIGINT', () => {
|
|
1327
|
+
if (!opts.json) console.log('\nš Stopping continuous play...');
|
|
1328
|
+
running = false;
|
|
1329
|
+
});
|
|
1330
|
+
process.on('SIGTERM', () => {
|
|
1331
|
+
running = false;
|
|
1332
|
+
});
|
|
1139
1333
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
cooldown_ms: cooldownMs,
|
|
1150
|
-
consecutive_wins: state.consecutiveWins,
|
|
1151
|
-
consecutive_losses: state.consecutiveLosses,
|
|
1152
|
-
};
|
|
1334
|
+
while (running) {
|
|
1335
|
+
const result = await runHeartbeat();
|
|
1336
|
+
|
|
1337
|
+
if (!running) break;
|
|
1338
|
+
|
|
1339
|
+
if (result.shouldStop) {
|
|
1340
|
+
if (!opts.json) console.log('\nā¹ļø Stopped: cannot continue playing.');
|
|
1341
|
+
break;
|
|
1342
|
+
}
|
|
1153
1343
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1344
|
+
if (result.waitMs > 0) {
|
|
1345
|
+
if (!opts.json) {
|
|
1346
|
+
console.log(`\nā³ Waiting ${(result.waitMs / 1000).toFixed(0)}s until next bet...\n`);
|
|
1347
|
+
}
|
|
1348
|
+
await new Promise((resolve) => {
|
|
1349
|
+
const timeout = setTimeout(resolve, result.waitMs);
|
|
1350
|
+
const checkStop = setInterval(() => {
|
|
1351
|
+
if (!running) {
|
|
1352
|
+
clearTimeout(timeout);
|
|
1353
|
+
clearInterval(checkStop);
|
|
1354
|
+
resolve();
|
|
1355
|
+
}
|
|
1356
|
+
}, 500);
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1164
1360
|
}
|
|
1361
|
+
});
|
|
1165
1362
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1363
|
+
// --- COMMAND: PLAY (Simple play command - no cooldowns) ---
|
|
1364
|
+
program
|
|
1365
|
+
.command('play')
|
|
1366
|
+
.description('Play a game immediately (no cooldowns)')
|
|
1367
|
+
.option('--strategy <name>', 'conservative | balanced | aggressive | degen', 'balanced')
|
|
1368
|
+
.option('--loop', 'Play continuously with 2s between games')
|
|
1369
|
+
.option('--delay <seconds>', 'Seconds between games in loop mode', '2')
|
|
1370
|
+
.option('--json', 'Output JSON only')
|
|
1371
|
+
.action(async (opts) => {
|
|
1372
|
+
const account = getWallet();
|
|
1373
|
+
const loopMode = Boolean(opts.loop);
|
|
1374
|
+
const delaySeconds = Math.max(parseFloat(opts.delay) || 2, 1);
|
|
1375
|
+
const delayMs = delaySeconds * 1000;
|
|
1376
|
+
|
|
1377
|
+
// Check if paused
|
|
1378
|
+
const profile = loadProfile();
|
|
1379
|
+
if (profile.paused) {
|
|
1168
1380
|
const response = {
|
|
1169
|
-
|
|
1381
|
+
action: 'play',
|
|
1170
1382
|
status: 'skipped',
|
|
1171
|
-
reason: '
|
|
1172
|
-
|
|
1383
|
+
reason: 'paused',
|
|
1384
|
+
message: 'Play is paused. Run `apechurch resume` to continue.',
|
|
1173
1385
|
};
|
|
1174
1386
|
if (opts.json) console.log(JSON.stringify(response));
|
|
1175
1387
|
else console.log(JSON.stringify(response, null, 2));
|
|
1176
1388
|
return;
|
|
1177
1389
|
}
|
|
1178
1390
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
saveState(state);
|
|
1182
|
-
const response = {
|
|
1183
|
-
...baseResponse,
|
|
1184
|
-
status: 'skipped',
|
|
1185
|
-
reason: 'wager_below_minimum',
|
|
1186
|
-
wager_ape: formatApeAmount(wagerApe),
|
|
1187
|
-
};
|
|
1188
|
-
if (opts.json) console.log(JSON.stringify(response));
|
|
1189
|
-
else console.log(JSON.stringify(response, null, 2));
|
|
1190
|
-
return;
|
|
1391
|
+
if (loopMode && !opts.json) {
|
|
1392
|
+
console.log(`š° Starting continuous play (${delaySeconds}s between games, Ctrl+C to stop)...\n`);
|
|
1191
1393
|
}
|
|
1192
1394
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1395
|
+
async function playOnce() {
|
|
1396
|
+
const state = loadState();
|
|
1397
|
+
const freshProfile = loadProfile();
|
|
1398
|
+
|
|
1399
|
+
if (freshProfile.paused) {
|
|
1400
|
+
return { shouldStop: true, reason: 'paused' };
|
|
1401
|
+
}
|
|
1195
1402
|
|
|
1196
|
-
|
|
1197
|
-
const
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1403
|
+
const strategy = normalizeStrategy(opts.strategy);
|
|
1404
|
+
const strategyConfig = applyProfileOverrides(
|
|
1405
|
+
getStrategyConfig(strategy),
|
|
1406
|
+
freshProfile.overrides
|
|
1407
|
+
);
|
|
1408
|
+
|
|
1409
|
+
const { publicClient } = createClients();
|
|
1410
|
+
let balance;
|
|
1411
|
+
try {
|
|
1412
|
+
balance = await publicClient.getBalance({ address: account.address });
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
console.error(JSON.stringify({ error: `Failed to fetch balance: ${sanitizeError(error)}` }));
|
|
1415
|
+
return { shouldStop: true, reason: 'balance_error' };
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
const balanceApe = parseFloat(formatEther(balance));
|
|
1419
|
+
const availableApe = Math.max(balanceApe - GAS_RESERVE_APE, 0);
|
|
1420
|
+
|
|
1421
|
+
if (availableApe <= 0 || availableApe < strategyConfig.minBetApe) {
|
|
1422
|
+
const response = {
|
|
1423
|
+
action: 'play',
|
|
1424
|
+
status: 'skipped',
|
|
1425
|
+
reason: 'insufficient_balance',
|
|
1426
|
+
balance_ape: balanceApe.toFixed(6),
|
|
1427
|
+
available_ape: availableApe.toFixed(6),
|
|
1428
|
+
};
|
|
1429
|
+
if (opts.json) console.log(JSON.stringify(response));
|
|
1430
|
+
else console.log(JSON.stringify(response, null, 2));
|
|
1431
|
+
return { shouldStop: true, reason: 'insufficient_balance' };
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
const wagerApe = calculateWager(availableApe, strategyConfig);
|
|
1435
|
+
const selection = selectGameAndConfig(strategyConfig);
|
|
1436
|
+
const wagerApeString = formatApeAmount(wagerApe);
|
|
1437
|
+
|
|
1438
|
+
try {
|
|
1439
|
+
const playResponse = await playGame({
|
|
1440
|
+
account,
|
|
1441
|
+
game: selection.game,
|
|
1442
|
+
amountApe: wagerApeString,
|
|
1443
|
+
mode: selection.mode,
|
|
1444
|
+
balls: selection.balls,
|
|
1445
|
+
spins: selection.spins,
|
|
1446
|
+
timeoutMs: 0,
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
// Update state
|
|
1450
|
+
if (playResponse?.result) {
|
|
1451
|
+
const pnlWei = (BigInt(playResponse.result.payout_wei) -
|
|
1452
|
+
BigInt(playResponse.result.buy_in_wei)).toString();
|
|
1453
|
+
state.totalPnLWei = addBigIntStrings(state.totalPnLWei, pnlWei);
|
|
1454
|
+
if (BigInt(pnlWei) >= 0n) {
|
|
1455
|
+
state.sessionWins += 1;
|
|
1456
|
+
state.consecutiveWins += 1;
|
|
1457
|
+
state.consecutiveLosses = 0;
|
|
1458
|
+
} else {
|
|
1459
|
+
state.sessionLosses += 1;
|
|
1460
|
+
state.consecutiveLosses += 1;
|
|
1461
|
+
state.consecutiveWins = 0;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
state.lastPlay = Date.now();
|
|
1465
|
+
saveState(state);
|
|
1466
|
+
|
|
1467
|
+
const response = {
|
|
1468
|
+
action: 'play',
|
|
1469
|
+
status: playResponse.status,
|
|
1470
|
+
strategy,
|
|
1471
|
+
balance_ape: balanceApe.toFixed(6),
|
|
1472
|
+
wager_ape: wagerApeString,
|
|
1473
|
+
game: playResponse.game,
|
|
1474
|
+
config: playResponse.config,
|
|
1475
|
+
tx: playResponse.tx,
|
|
1476
|
+
gameId: playResponse.gameId,
|
|
1477
|
+
game_url: playResponse.game_url,
|
|
1478
|
+
result: playResponse.result,
|
|
1479
|
+
session: {
|
|
1480
|
+
wins: state.sessionWins,
|
|
1481
|
+
losses: state.sessionLosses,
|
|
1482
|
+
total_pnl_ape: formatEther(BigInt(state.totalPnLWei)),
|
|
1483
|
+
},
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1486
|
+
if (opts.json) console.log(JSON.stringify(response));
|
|
1487
|
+
else console.log(JSON.stringify(response, null, 2));
|
|
1488
|
+
|
|
1489
|
+
return { shouldStop: false };
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
console.error(JSON.stringify({ error: error.message }));
|
|
1492
|
+
return { shouldStop: true, reason: 'play_error' };
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (!loopMode) {
|
|
1497
|
+
await playOnce();
|
|
1498
|
+
} else {
|
|
1499
|
+
let running = true;
|
|
1500
|
+
process.on('SIGINT', () => {
|
|
1501
|
+
if (!opts.json) console.log('\nš Stopping...');
|
|
1502
|
+
running = false;
|
|
1205
1503
|
});
|
|
1504
|
+
process.on('SIGTERM', () => {
|
|
1505
|
+
running = false;
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
while (running) {
|
|
1509
|
+
const result = await playOnce();
|
|
1510
|
+
|
|
1511
|
+
if (!running) break;
|
|
1512
|
+
if (result.shouldStop) {
|
|
1513
|
+
if (!opts.json) console.log(`\nā¹ļø Stopped: ${result.reason}`);
|
|
1514
|
+
break;
|
|
1515
|
+
}
|
|
1206
1516
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
const pnlWei = (BigInt(playResponse.result.payout_wei) -
|
|
1210
|
-
BigInt(playResponse.result.buy_in_wei)).toString();
|
|
1211
|
-
state.totalPnLWei = addBigIntStrings(state.totalPnLWei, pnlWei);
|
|
1212
|
-
if (BigInt(pnlWei) >= 0n) {
|
|
1213
|
-
state.sessionWins += 1;
|
|
1214
|
-
state.consecutiveWins += 1;
|
|
1215
|
-
state.consecutiveLosses = 0;
|
|
1216
|
-
} else {
|
|
1217
|
-
state.sessionLosses += 1;
|
|
1218
|
-
state.consecutiveLosses += 1;
|
|
1219
|
-
state.consecutiveWins = 0;
|
|
1517
|
+
if (!opts.json) {
|
|
1518
|
+
console.log(`\nā³ Next game in ${delaySeconds}s...\n`);
|
|
1220
1519
|
}
|
|
1520
|
+
await new Promise((resolve) => {
|
|
1521
|
+
const timeout = setTimeout(resolve, delayMs);
|
|
1522
|
+
const checkStop = setInterval(() => {
|
|
1523
|
+
if (!running) {
|
|
1524
|
+
clearTimeout(timeout);
|
|
1525
|
+
clearInterval(checkStop);
|
|
1526
|
+
resolve();
|
|
1527
|
+
}
|
|
1528
|
+
}, 200);
|
|
1529
|
+
});
|
|
1221
1530
|
}
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1222
1533
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1534
|
+
// --- COMMAND: GAMES (Show available games and parameters) ---
|
|
1535
|
+
program
|
|
1536
|
+
.command('games')
|
|
1537
|
+
.description('List all available games and their parameters')
|
|
1538
|
+
.option('--json', 'Output JSON only')
|
|
1539
|
+
.action((opts) => {
|
|
1540
|
+
const games = GAME_REGISTRY.map((game) => {
|
|
1541
|
+
const params = [];
|
|
1542
|
+
if (game.type === 'plinko') {
|
|
1543
|
+
params.push({
|
|
1544
|
+
name: 'mode',
|
|
1545
|
+
type: 'integer',
|
|
1546
|
+
min: game.config.mode.min,
|
|
1547
|
+
max: game.config.mode.max,
|
|
1548
|
+
default: game.config.mode.default,
|
|
1549
|
+
description: 'Risk level (higher = riskier, bigger payouts)',
|
|
1550
|
+
});
|
|
1551
|
+
params.push({
|
|
1552
|
+
name: 'balls',
|
|
1553
|
+
type: 'integer',
|
|
1554
|
+
min: game.config.balls.min,
|
|
1555
|
+
max: game.config.balls.max,
|
|
1556
|
+
default: game.config.balls.default,
|
|
1557
|
+
description: 'Number of balls to drop',
|
|
1558
|
+
});
|
|
1559
|
+
} else if (game.type === 'slots') {
|
|
1560
|
+
params.push({
|
|
1561
|
+
name: 'spins',
|
|
1562
|
+
type: 'integer',
|
|
1563
|
+
min: game.config.spins.min,
|
|
1564
|
+
max: game.config.spins.max,
|
|
1565
|
+
default: game.config.spins.default,
|
|
1566
|
+
description: 'Number of spins per bet',
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
return {
|
|
1570
|
+
key: game.key,
|
|
1571
|
+
name: game.name,
|
|
1572
|
+
type: game.type,
|
|
1573
|
+
aliases: game.aliases || [],
|
|
1574
|
+
contract: game.contract,
|
|
1575
|
+
parameters: params,
|
|
1234
1576
|
};
|
|
1577
|
+
});
|
|
1235
1578
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1579
|
+
if (opts.json) {
|
|
1580
|
+
console.log(JSON.stringify({ games }, null, 2));
|
|
1581
|
+
} else {
|
|
1582
|
+
console.log('\nš° AVAILABLE GAMES\n');
|
|
1583
|
+
for (const game of games) {
|
|
1584
|
+
console.log(`${game.name} (${game.key})`);
|
|
1585
|
+
console.log(` Type: ${game.type}`);
|
|
1586
|
+
console.log(` Aliases: ${game.aliases.join(', ') || 'none'}`);
|
|
1587
|
+
console.log(' Parameters:');
|
|
1588
|
+
for (const param of game.parameters) {
|
|
1589
|
+
console.log(` --${param.name} <${param.min}-${param.max}> ${param.description} (default: ${param.default})`);
|
|
1590
|
+
}
|
|
1591
|
+
console.log('');
|
|
1592
|
+
}
|
|
1593
|
+
console.log('EXAMPLE BETS:');
|
|
1594
|
+
console.log(' apechurch bet --game jungle-plinko --amount 5 --mode 2 --balls 50');
|
|
1595
|
+
console.log(' apechurch bet --game dino-dough --amount 10 --spins 8');
|
|
1596
|
+
console.log(' apechurch bet --game bubblegum-heist --amount 5 --spins 12');
|
|
1597
|
+
console.log('');
|
|
1242
1598
|
}
|
|
1243
1599
|
});
|
|
1244
1600
|
|
|
1601
|
+
// --- COMMAND: COMMANDS (Show all commands overview) ---
|
|
1602
|
+
program
|
|
1603
|
+
.command('commands')
|
|
1604
|
+
.alias('help')
|
|
1605
|
+
.description('Show all available commands with examples')
|
|
1606
|
+
.action(() => {
|
|
1607
|
+
console.log(`
|
|
1608
|
+
========================================
|
|
1609
|
+
š° APE CHURCH CLI - FULL REFERENCE
|
|
1610
|
+
========================================
|
|
1611
|
+
|
|
1612
|
+
ABOUT
|
|
1613
|
+
Ape Church is a FULLY on-chain, 100% decentralized casino on ApeChain.
|
|
1614
|
+
Every bet is placed and settled on-chain. Zero servers.
|
|
1615
|
+
|
|
1616
|
+
Player vs Player: The "house" is a public staking pool - anyone can
|
|
1617
|
+
deposit or withdraw APE anytime. You're playing against other players,
|
|
1618
|
+
not the house. Truly PvP. Games use VRF for provably fair randomness.
|
|
1619
|
+
|
|
1620
|
+
========================================
|
|
1621
|
+
GETTING STARTED
|
|
1622
|
+
========================================
|
|
1623
|
+
|
|
1624
|
+
apechurch install [--username NAME] [--persona TYPE]
|
|
1625
|
+
Set up your agent wallet and register on Ape Church.
|
|
1626
|
+
|
|
1627
|
+
apechurch status [--json]
|
|
1628
|
+
Check your wallet balance and current status.
|
|
1629
|
+
|
|
1630
|
+
========================================
|
|
1631
|
+
PLAYING GAMES
|
|
1632
|
+
========================================
|
|
1633
|
+
|
|
1634
|
+
apechurch play [--strategy TYPE] [--loop] [--json]
|
|
1635
|
+
Play games automatically. Picks a random game, bets based on strategy.
|
|
1636
|
+
--loop Play continuously (2s between games)
|
|
1637
|
+
--delay <sec> Custom delay between games (default: 2)
|
|
1638
|
+
--strategy conservative | balanced | aggressive | degen
|
|
1639
|
+
|
|
1640
|
+
apechurch bet --game <NAME> --amount <APE> [options]
|
|
1641
|
+
Place a manual bet on a specific game.
|
|
1642
|
+
--game jungle-plinko | dino-dough | bubblegum-heist
|
|
1643
|
+
--amount APE to wager
|
|
1644
|
+
--mode <0-4> Plinko risk level (higher = riskier)
|
|
1645
|
+
--balls <1-100> Plinko balls to drop
|
|
1646
|
+
--spins <1-15> Slots spins per bet
|
|
1647
|
+
|
|
1648
|
+
apechurch games [--json]
|
|
1649
|
+
List all available games with parameters.
|
|
1650
|
+
|
|
1651
|
+
========================================
|
|
1652
|
+
STRATEGIES (controls how much your agent bets)
|
|
1653
|
+
========================================
|
|
1654
|
+
|
|
1655
|
+
conservative 5% of balance per bet, low-risk game configs
|
|
1656
|
+
balanced 8% of balance per bet (default)
|
|
1657
|
+
aggressive 12% of balance per bet, high-risk game configs
|
|
1658
|
+
degen 20% of balance per bet, max risk
|
|
1659
|
+
|
|
1660
|
+
Change your strategy anytime:
|
|
1661
|
+
apechurch profile set --persona aggressive
|
|
1662
|
+
|
|
1663
|
+
Or specify per-session:
|
|
1664
|
+
apechurch play --loop --strategy degen
|
|
1665
|
+
|
|
1666
|
+
========================================
|
|
1667
|
+
CONTROL
|
|
1668
|
+
========================================
|
|
1669
|
+
|
|
1670
|
+
apechurch pause Stop playing (loop will exit)
|
|
1671
|
+
apechurch resume Resume playing
|
|
1672
|
+
Ctrl+C Stop the current loop
|
|
1673
|
+
|
|
1674
|
+
========================================
|
|
1675
|
+
PROFILE & SETTINGS
|
|
1676
|
+
========================================
|
|
1677
|
+
|
|
1678
|
+
apechurch profile show [--json]
|
|
1679
|
+
View your current profile.
|
|
1680
|
+
|
|
1681
|
+
apechurch profile set [--persona TYPE] [--username NAME]
|
|
1682
|
+
Update your strategy or username.
|
|
1683
|
+
|
|
1684
|
+
apechurch register --username <NAME>
|
|
1685
|
+
Change your on-chain username.
|
|
1686
|
+
|
|
1687
|
+
========================================
|
|
1688
|
+
EXAMPLES
|
|
1689
|
+
========================================
|
|
1690
|
+
|
|
1691
|
+
# Quick start - play continuously
|
|
1692
|
+
apechurch play --loop
|
|
1693
|
+
|
|
1694
|
+
# Play aggressively
|
|
1695
|
+
apechurch play --loop --strategy aggressive
|
|
1696
|
+
|
|
1697
|
+
# Single manual bet
|
|
1698
|
+
apechurch bet --game jungle-plinko --amount 10 --mode 3 --balls 50
|
|
1699
|
+
|
|
1700
|
+
# Check status
|
|
1701
|
+
apechurch status
|
|
1702
|
+
|
|
1703
|
+
========================================
|
|
1704
|
+
FOR AI AGENTS
|
|
1705
|
+
========================================
|
|
1706
|
+
|
|
1707
|
+
To activate this skill, tell your agent:
|
|
1708
|
+
|
|
1709
|
+
"Read ~/.openclaw/skills/ape-church/SKILL.md and run: apechurch play --loop"
|
|
1710
|
+
|
|
1711
|
+
All commands support --json for machine-readable output.
|
|
1712
|
+
`);
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1245
1715
|
program.parse(process.argv);
|