@ape-church/skill 1.0.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.
@@ -0,0 +1,113 @@
1
+ // index.js
2
+ require('dotenv').config();
3
+ const { createPublicClient, webSocket, http, formatUnits } = require('viem'); // Added http if needed, but using webSocket for transport
4
+ const { apechain } = require('viem/chains');
5
+ const { createClient } = require('@supabase/supabase-js');
6
+
7
+ // --- CONFIGURATION ---
8
+ const { APECHAIN_WSS_URL, SUPABASE_URL, SUPABASE_SERVICE_KEY } = process.env;
9
+
10
+ const HOUSE_ADDRESS = "0x2054709F89F18a4CCAC6132acE7b812E32608469"
11
+
12
+ // --- ABI DEFINITIONS ---
13
+ const houseAbi = [
14
+ {
15
+ type: 'event',
16
+ name: 'HouseWon',
17
+ anonymous: false,
18
+ inputs: [
19
+ { name: 'GAME_ID', type: 'uint256', indexed: false },
20
+ { name: 'profit', type: 'uint256', indexed: false },
21
+ ],
22
+ },
23
+ {
24
+ type: 'event',
25
+ name: 'HouseLost',
26
+ anonymous: false,
27
+ inputs: [
28
+ { name: 'GAME_ID', type: 'uint256', indexed: false },
29
+ { name: 'user', type: 'address', indexed: false },
30
+ { name: 'loss', type: 'uint256', indexed: false },
31
+ ],
32
+ },
33
+ {
34
+ type: 'function',
35
+ name: 'calculatePrice',
36
+ inputs: [],
37
+ outputs: [{ name: '', type: 'uint256' }],
38
+ stateMutability: 'view',
39
+ },
40
+ ];
41
+
42
+ // --- CLIENT INITIALIZATION ---
43
+ const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);
44
+ const publicClient = createPublicClient({
45
+ chain: apechain,
46
+ transport: webSocket(APECHAIN_WSS_URL),
47
+ });
48
+
49
+ console.log('✅ Services Initialized. Starting listeners...');
50
+
51
+ // --- SHARED HANDLER FOR EVENTS ---
52
+ const handleEvent = async (log) => {
53
+ try {
54
+ console.log(`[Event Trigger] Detected event at tx: ${log.transactionHash}`);
55
+
56
+ // Read the current price from the contract
57
+ const priceBigInt = await publicClient.readContract({
58
+ address: HOUSE_ADDRESS,
59
+ abi: houseAbi,
60
+ functionName: 'calculatePrice',
61
+ });
62
+
63
+ // Parse uint256 to number (use Number for simplicity; if value is large, consider BigInt.toString() and store as text/numeric string)
64
+ const price = parseFloat(formatUnits(priceBigInt, 18));
65
+
66
+ const record = {
67
+ price: price,
68
+ // timestamp: defaults to NOW() on insert, but can override if needed
69
+ };
70
+
71
+ console.log('Adding new price record to Supabase:', record);
72
+
73
+ const { error } = await supabase
74
+ .from('house_price')
75
+ .insert(record);
76
+
77
+ if (error) {
78
+ console.error('[Price Insert] Supabase error:', error);
79
+ } else {
80
+ console.log(`✅ [Price Insert] Added price: ${price}`);
81
+ }
82
+ } catch (e) {
83
+ console.error(`[Event Handler] Failed to process. Error: ${e.message}`);
84
+ }
85
+ };
86
+
87
+ // --- LISTENER: HouseWon and HouseLost Events ---
88
+ publicClient.watchContractEvent({
89
+ address: HOUSE_ADDRESS,
90
+ abi: houseAbi,
91
+ eventName: 'HouseWon',
92
+ onLogs: async (logs) => {
93
+ for (const log of logs) {
94
+ await handleEvent(log);
95
+ }
96
+ },
97
+ onError: (error) => console.error('[House Won] Listener error:', error.message),
98
+ });
99
+
100
+ // --- LISTENER: HouseWon and HouseLost Events ---
101
+ publicClient.watchContractEvent({
102
+ address: HOUSE_ADDRESS,
103
+ abi: houseAbi,
104
+ eventName: 'HouseLost',
105
+ onLogs: async (logs) => {
106
+ for (const log of logs) {
107
+ await handleEvent(log);
108
+ }
109
+ },
110
+ onError: (error) => console.error('[HouseLost Won] Listener error:', error.message),
111
+ });
112
+
113
+ console.log(`Listening for "HouseWon" and "HouseLost" events...`);
@@ -0,0 +1,496 @@
1
+ const { ethers } = require('ethers');
2
+ const { createClient } = require('@supabase/supabase-js');
3
+ require('dotenv').config();
4
+ const { encodeAbiParameters } = require('viem');
5
+ const { hashMessage } = require('viem');
6
+
7
+ // --- Configuration ---
8
+ const {
9
+ APECHAIN_RPC_URL,
10
+ BOT_PRIVATE_KEYS_ARRAY,
11
+ } = process.env;
12
+
13
+ // Parse BOT_PRIVATE_KEYS_ARRAY from comma-separated string to array
14
+ // Example .env format: BOT_PRIVATE_KEYS_ARRAY=key1,key2,key3
15
+ const BOT_PRIVATE_KEYS = BOT_PRIVATE_KEYS_ARRAY
16
+ ? BOT_PRIVATE_KEYS_ARRAY.split(',').map(key => key.trim()).filter(key => key.length > 0)
17
+ : [];
18
+
19
+ const ROULETTE = "0x1f48A104C1808eb4107f3999999D36aeafEC56d5";
20
+ const APESTRONG = "0x0717330c1a9e269a0e034aBB101c8d32Ac0e9600";
21
+ const GIMBO_SMASH = "0x17e219844F25F3FED6E422DdaFfD2E6557eBCEd3";
22
+ const BEAR_DICE = "0x6a48A513A46955D8622C809Fce876d2f11142003";
23
+
24
+ // Your Contract ABI - updated with the correct functions
25
+ const CONTRACT_ABI = ["function play(address player, bytes calldata gameData) external payable"];
26
+ // --- Constants ---
27
+ const POLLING_INTERVAL = 10_000; // 20 seconds
28
+
29
+ const MINIMUM_BALANCE_UNFORMATTED = "51";
30
+ const MINIMUM_BALANCE = ethers.parseUnits(MINIMUM_BALANCE_UNFORMATTED, "ether");
31
+
32
+ const MAX_BET = 150;
33
+ const MAX_BET_RISKY = 100;
34
+ const MIN_BET = 30;
35
+
36
+ const MAX_BET_PERCENTAGE = 0.25;
37
+ const MAX_BET_PERCENTAGE_RISKY = 0.15;
38
+
39
+ const RISKY_GAME_PERCENTAGE = 0.1;
40
+
41
+ // --- Validation ---
42
+ if (!APECHAIN_RPC_URL || !BOT_PRIVATE_KEYS_ARRAY || BOT_PRIVATE_KEYS.length === 0) {
43
+ console.error("❌ Missing required environment variables. Please check your .env file.");
44
+ console.error(" BOT_PRIVATE_KEYS_ARRAY should be comma-separated: key1,key2,key3");
45
+ process.exit(1);
46
+ }
47
+ /**
48
+ * The main function that initializes clients and starts the bot.
49
+ */
50
+ async function main() {
51
+ console.log("🚀 Starting Supabase Verification Bonus Bot...");
52
+
53
+ const provider = new ethers.JsonRpcProvider(APECHAIN_RPC_URL);
54
+ console.log("✅ Connected to Ape Chain RPC.");
55
+
56
+ const wallets = BOT_PRIVATE_KEYS.map(privateKey => new ethers.Wallet(privateKey, provider));
57
+
58
+ const walletGames = wallets.map(wallet => {
59
+ return {
60
+ wallet: wallet,
61
+ games: [
62
+ new ethers.Contract(ROULETTE, CONTRACT_ABI, wallet),
63
+ new ethers.Contract(APESTRONG, CONTRACT_ABI, wallet),
64
+ new ethers.Contract(GIMBO_SMASH, CONTRACT_ABI, wallet),
65
+ new ethers.Contract(BEAR_DICE, CONTRACT_ABI, wallet),
66
+ ],
67
+ gameNames: [
68
+ "Roulette",
69
+ "ApeStrong",
70
+ "GimboSmash",
71
+ "BearDice",
72
+ ]
73
+ }
74
+ });
75
+
76
+ console.log(`🔍 Starting database polling every ${POLLING_INTERVAL / 1000} seconds...`);
77
+ pollDatabaseAndProcessUsers(walletGames);
78
+ }
79
+
80
+ /**
81
+ * Polls the database for eligible users and processes them.
82
+ * @param {ethers.Contract} games - A list of ethers contract instances.
83
+ */
84
+ async function pollDatabaseAndProcessUsers(walletGames) {
85
+ console.log("\n-------------------------------------");
86
+ console.log(`[${new Date().toISOString()}] Polling for Ape balance...`);
87
+
88
+ const walletIndex = Math.floor(Math.random() * walletGames.length);
89
+ const walletToPlay = walletGames[walletIndex];
90
+ const wallet = walletToPlay.wallet;
91
+ const gameNames = walletToPlay.gameNames;
92
+ const games = walletToPlay.games;
93
+
94
+ const gameIndex = Math.floor(Math.random() * games.length);
95
+ const gameContract = games[gameIndex];
96
+ const gameName = gameNames[gameIndex];
97
+
98
+ const isRiskyGame = Math.random() < RISKY_GAME_PERCENTAGE;
99
+ if (isRiskyGame) {
100
+ console.log(`🔍 Playing risky game for wallet ${walletIndex}`);
101
+ } else {
102
+ console.log(`🔍 Playing safe game for wallet ${walletIndex}`);
103
+ }
104
+
105
+ try {
106
+ const apeBalance = await gameContract.runner.provider.getBalance(wallet.address);
107
+ const formattedBalance = parseFloat(ethers.formatUnits(apeBalance, "ether"));
108
+ console.log(`🔍 Ape balance: ${formattedBalance}`);
109
+ if (apeBalance < MINIMUM_BALANCE) {
110
+ console.log(`❌ Ape balance is less than ${MINIMUM_BALANCE_UNFORMATTED} APE`);
111
+ return;
112
+ }
113
+
114
+ const MAX_BET_PERC = isRiskyGame ? MAX_BET_PERCENTAGE_RISKY : MAX_BET_PERCENTAGE;
115
+ const MAX_BET_TO_USE = isRiskyGame ? MAX_BET_RISKY : MAX_BET;
116
+
117
+ // determine an amount to play with, below 33% of the balance
118
+ let amountToPlay = Math.floor(formattedBalance * Math.random() * MAX_BET_PERC);
119
+
120
+ if (amountToPlay < MIN_BET) {
121
+ amountToPlay = MIN_BET;
122
+ } else if (amountToPlay > MAX_BET_TO_USE) {
123
+ amountToPlay = MAX_BET_TO_USE;
124
+ }
125
+
126
+ if (amountToPlay > formattedBalance) {
127
+ console.log("Amount to play is greater than balance, reducing amount to play to balance");
128
+ amountToPlay = formattedBalance - 1;
129
+ }
130
+
131
+ console.log(`🔍 Playing ${gameName}...`);
132
+ console.log(`🔍 Amount to play: ${amountToPlay}`);
133
+
134
+ let txOk = false;
135
+ if (gameName === "Roulette") {
136
+ txOk = await executeRouletteTransaction(gameContract, wallet, amountToPlay, isRiskyGame);
137
+ } else if (gameName === "ApeStrong") {
138
+ txOk = await executeApeStrongTransaction(gameContract, wallet, amountToPlay, isRiskyGame);
139
+ } else if (gameName === "GimboSmash") {
140
+ txOk = await executeGimboSmashTransaction(gameContract, wallet, amountToPlay, isRiskyGame);
141
+ } else if (gameName === "BearDice") {
142
+ txOk = await executeBearDiceTransaction(gameContract, wallet, amountToPlay, isRiskyGame);
143
+ } else {
144
+ console.warn("❌ Unknown game");
145
+ txOk = false;
146
+ }
147
+
148
+ if (!txOk) {
149
+ console.warn("❌ Transaction failed");
150
+ }
151
+ } catch (error) {
152
+ console.error("An unexpected error occurred during the polling cycle:", error);
153
+ }
154
+
155
+ // Schedule the next poll
156
+ setTimeout(() => pollDatabaseAndProcessUsers(walletGames), POLLING_INTERVAL);
157
+ }
158
+
159
+ /**
160
+ * Executes the 'grantBonusEXP' smart contract function.
161
+ * @param {ethers.Contract} contract The ethers contract instance.
162
+ * @returns {Promise<boolean>} True if the transaction was successful, otherwise false.
163
+ */
164
+ async function executeRouletteTransaction(contract, wallet, amountToPlay, isRiskyGame) {
165
+ try {
166
+
167
+ const feeData = await contract.runner.provider.getFeeData();
168
+ console.log(" - Current Fee Data:", {
169
+ maxFeePerGas: ethers.formatUnits(feeData.maxFeePerGas, "gwei"),
170
+ maxPriorityFeePerGas: ethers.formatUnits(feeData.maxPriorityFeePerGas, "gwei"),
171
+ });
172
+
173
+ const priorityFee = feeData.maxPriorityFeePerGas + ethers.parseUnits("1", "gwei");
174
+
175
+ const vrfFee = BigInt("73211589000000001");
176
+
177
+ const BET_AMOUNT_PER = Math.floor(amountToPlay / 2).toString();
178
+
179
+ let BET_NUMBERS;
180
+ let BET_AMOUNTS;
181
+
182
+ if (isRiskyGame) {
183
+ BET_NUMBERS = [49];
184
+ BET_AMOUNTS = [ethers.parseUnits(Math.floor(amountToPlay).toString(), "ether")];
185
+ } else {
186
+ BET_NUMBERS = [49, 50];
187
+ BET_AMOUNTS = [ethers.parseUnits(BET_AMOUNT_PER, "ether"), ethers.parseUnits(BET_AMOUNT_PER, "ether")];
188
+ }
189
+
190
+ const totalValue = BET_AMOUNTS.reduce((a, b) => a + b, BigInt(0)) + vrfFee;
191
+
192
+ // generate random uint256 for the gameId, needs to be bigint
193
+ // hash something to get a random bytes32
194
+ const randomSeed = Math.random().toString(36).substring(2, 15);
195
+ const randomBytes32 = hashMessage(randomSeed);
196
+ const gameId = BigInt(randomBytes32.slice(0, 16));
197
+ const userRandomWord = hashMessage("I like big butts and I cannot lie!!");
198
+
199
+ const encodedData = encodeAbiParameters(
200
+ [
201
+ { name: "gameNumbers", type: "uint8[]" },
202
+ { name: "amounts", type: "uint256[]" },
203
+ { name: "gameId", type: "uint" },
204
+ { name: "ref", type: "address" },
205
+ { name: "randomWord", type: "bytes32" },
206
+ ],
207
+ [
208
+ BET_NUMBERS,
209
+ BET_AMOUNTS,
210
+ gameId,
211
+ "0x91cF4F24EF2234C6C3c51669D0F2fa46FA562227",
212
+ userRandomWord,
213
+ ]
214
+ );
215
+
216
+ // Estimate gas
217
+ const gasEstimate = await contract.play.estimateGas(wallet.address, encodedData, { value: totalValue });
218
+ const gasLimitWithBuffer = (gasEstimate * BigInt(110)) / BigInt(100); // 20% buffer
219
+ console.log(` - Estimated gas: ${gasEstimate}, Gas limit with buffer: ${gasLimitWithBuffer}`);
220
+
221
+ // Send the transaction
222
+ const tx = await contract.play(wallet.address, encodedData, {
223
+ value: totalValue,
224
+ gasLimit: gasLimitWithBuffer,
225
+ maxPriorityFeePerGas: priorityFee,
226
+ maxFeePerGas: feeData.maxFeePerGas
227
+ });
228
+
229
+ const receipt = await tx.wait();
230
+ console.log(`🎉 Transaction Mined! Block number: ${receipt.blockNumber}`);
231
+ return true;
232
+
233
+ } catch (error) {
234
+ if (error.code === 'CALL_EXCEPTION' || error.reason) {
235
+ console.warn(` - Transaction failed, likely already executed by another bot. Reason: ${error.reason}`);
236
+ return false;
237
+ } else if (error.code === 'INSUFFICIENT_FUNDS') {
238
+ console.error(" - Transaction failed: Insufficient funds for gas * price + value.");
239
+ process.exit(1);
240
+ return false;
241
+ } else {
242
+ console.error(" - An unexpected error occurred during transaction execution:", error);
243
+ return false;
244
+ }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Executes the 'grantBonusEXP' smart contract function.
250
+ * @param {ethers.Contract} contract The ethers contract instance.
251
+ * @returns {Promise<boolean>} True if the transaction was successful, otherwise false.
252
+ */
253
+ async function executeApeStrongTransaction(contract, wallet, amountToPlay, isRiskyGame) {
254
+ try {
255
+
256
+ const feeData = await contract.runner.provider.getFeeData();
257
+ console.log(" - Current Fee Data:", {
258
+ maxFeePerGas: ethers.formatUnits(feeData.maxFeePerGas, "gwei"),
259
+ maxPriorityFeePerGas: ethers.formatUnits(feeData.maxPriorityFeePerGas, "gwei"),
260
+ });
261
+
262
+ const priorityFee = feeData.maxPriorityFeePerGas + ethers.parseUnits("1", "gwei");
263
+
264
+ const vrfFee = BigInt("73211589000000001");
265
+
266
+ const BET_AMOUNT = ethers.parseUnits(amountToPlay.toString(), "ether");
267
+
268
+ const totalValue = BET_AMOUNT + vrfFee;
269
+
270
+ // generate random uint256 for the gameId, needs to be bigint
271
+ // hash something to get a random bytes32
272
+ const randomSeed = Math.random().toString(36).substring(2, 15);
273
+ const randomBytes32 = hashMessage(randomSeed);
274
+ const gameId = BigInt(randomBytes32.slice(0, 16));
275
+ const userRandomWord = hashMessage("I like big butts and I cannot lie!!");
276
+
277
+ const target = isRiskyGame ? 60 : 95;
278
+
279
+ const encodedData = encodeAbiParameters(
280
+ [
281
+ { name: "edgeFlipRange", type: "uint8" },
282
+ { name: "gameId", type: "uint" },
283
+ { name: "ref", type: "address" },
284
+ { name: "randomWord", type: "bytes32" },
285
+ ],
286
+ [
287
+ target,
288
+ gameId,
289
+ "0x91cF4F24EF2234C6C3c51669D0F2fa46FA562227",
290
+ userRandomWord,
291
+ ]
292
+ );
293
+
294
+ // Estimate gas
295
+ const gasEstimate = await contract.play.estimateGas(wallet.address, encodedData, { value: totalValue });
296
+ const gasLimitWithBuffer = (gasEstimate * BigInt(105)) / BigInt(100); // 5% buffer
297
+ console.log(` - Estimated gas: ${gasEstimate}, Gas limit with buffer: ${gasLimitWithBuffer}`);
298
+
299
+ // Send the transaction
300
+ const tx = await contract.play(wallet.address, encodedData, {
301
+ value: totalValue,
302
+ gasLimit: gasLimitWithBuffer,
303
+ maxPriorityFeePerGas: priorityFee,
304
+ maxFeePerGas: feeData.maxFeePerGas
305
+ });
306
+
307
+ const receipt = await tx.wait();
308
+ console.log(`🎉 Transaction Mined! Block number: ${receipt.blockNumber}`);
309
+ return true;
310
+
311
+ } catch (error) {
312
+ if (error.code === 'CALL_EXCEPTION' || error.reason) {
313
+ console.warn(` - Transaction failed, likely already executed by another bot. Reason: ${error.reason}`);
314
+ return false;
315
+ } else if (error.code === 'INSUFFICIENT_FUNDS') {
316
+ console.error(" - Transaction failed: Insufficient funds for gas * price + value.");
317
+ process.exit(1);
318
+ return false;
319
+ } else {
320
+ console.error(" - An unexpected error occurred during transaction execution:", error);
321
+ return false;
322
+ }
323
+ }
324
+ }
325
+
326
+
327
+ /**
328
+ * Executes the 'grantBonusEXP' smart contract function.
329
+ * @param {ethers.Contract} contract The ethers contract instance.
330
+ * @returns {Promise<boolean>} True if the transaction was successful, otherwise false.
331
+ */
332
+ async function executeGimboSmashTransaction(contract, wallet, amountToPlay, isRiskyGame) {
333
+ try {
334
+
335
+ const feeData = await contract.runner.provider.getFeeData();
336
+ console.log(" - Current Fee Data:", {
337
+ maxFeePerGas: ethers.formatUnits(feeData.maxFeePerGas, "gwei"),
338
+ maxPriorityFeePerGas: ethers.formatUnits(feeData.maxPriorityFeePerGas, "gwei"),
339
+ });
340
+
341
+ const priorityFee = feeData.maxPriorityFeePerGas + ethers.parseUnits("1", "gwei");
342
+
343
+ const vrfFee = BigInt("73211589000000001");
344
+
345
+ const BET_AMOUNT = ethers.parseUnits(amountToPlay.toString(), "ether");
346
+
347
+ const totalValue = BET_AMOUNT + vrfFee;
348
+
349
+ // generate random uint256 for the gameId, needs to be bigint
350
+ // hash something to get a random bytes32
351
+ const randomSeed = Math.random().toString(36).substring(2, 15);
352
+ const randomBytes32 = hashMessage(randomSeed);
353
+ const gameId = BigInt(randomBytes32.slice(0, 16));
354
+ const userRandomWord = hashMessage("I like big butts and I cannot lie!!");
355
+
356
+ const numWinIntervals = 1;
357
+ const winStarts = isRiskyGame ? [5, 0] : [40, 0];
358
+ const winEnds = [99, 0];
359
+
360
+ const encodedData = encodeAbiParameters(
361
+ [
362
+ { name: "numWinIntervals", type: "uint8" },
363
+ { name: "winStarts", type: "uint8[2]" },
364
+ { name: "winEnds", type: "uint8[2]" },
365
+ { name: "gameId", type: "uint" },
366
+ { name: "ref", type: "address" },
367
+ { name: "randomWord", type: "bytes32" },
368
+ ],
369
+ [
370
+ numWinIntervals,
371
+ winStarts,
372
+ winEnds,
373
+ gameId,
374
+ "0x91cF4F24EF2234C6C3c51669D0F2fa46FA562227",
375
+ userRandomWord,
376
+ ]
377
+ );
378
+
379
+ // Estimate gas
380
+ const gasEstimate = await contract.play.estimateGas(wallet.address, encodedData, { value: totalValue });
381
+ const gasLimitWithBuffer = (gasEstimate * BigInt(105)) / BigInt(100); // 5% buffer
382
+ console.log(` - Estimated gas: ${gasEstimate}, Gas limit with buffer: ${gasLimitWithBuffer}`);
383
+
384
+ // Send the transaction
385
+ const tx = await contract.play(wallet.address, encodedData, {
386
+ value: totalValue,
387
+ gasLimit: gasLimitWithBuffer,
388
+ maxPriorityFeePerGas: priorityFee,
389
+ maxFeePerGas: feeData.maxFeePerGas
390
+ });
391
+
392
+ const receipt = await tx.wait();
393
+ console.log(`🎉 Transaction Mined! Block number: ${receipt.blockNumber}`);
394
+ return true;
395
+
396
+ } catch (error) {
397
+ if (error.code === 'CALL_EXCEPTION' || error.reason) {
398
+ console.warn(` - Transaction failed, likely already executed by another bot. Reason: ${error.reason}`);
399
+ return false;
400
+ } else if (error.code === 'INSUFFICIENT_FUNDS') {
401
+ console.error(" - Transaction failed: Insufficient funds for gas * price + value.");
402
+ process.exit(1);
403
+ return false;
404
+ } else {
405
+ console.error(" - An unexpected error occurred during transaction execution:", error);
406
+ return false;
407
+ }
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Executes the 'grantBonusEXP' smart contract function.
413
+ * @param {ethers.Contract} contract The ethers contract instance.
414
+ * @returns {Promise<boolean>} True if the transaction was successful, otherwise false.
415
+ */
416
+ async function executeBearDiceTransaction(contract, wallet, amountToPlay, isRiskyGame) {
417
+ try {
418
+
419
+ const feeData = await contract.runner.provider.getFeeData();
420
+ console.log(" - Current Fee Data:", {
421
+ maxFeePerGas: ethers.formatUnits(feeData.maxFeePerGas, "gwei"),
422
+ maxPriorityFeePerGas: ethers.formatUnits(feeData.maxPriorityFeePerGas, "gwei"),
423
+ });
424
+
425
+ const priorityFee = feeData.maxPriorityFeePerGas + ethers.parseUnits("1", "gwei");
426
+
427
+ const vrfFee = isRiskyGame ? BigInt("117138542400000001") : BigInt("73211589000000001");
428
+
429
+ const BET_AMOUNT = ethers.parseUnits(amountToPlay.toString(), "ether");
430
+
431
+ const totalValue = BET_AMOUNT + vrfFee;
432
+
433
+ // generate random uint256 for the gameId, needs to be bigint
434
+ // hash something to get a random bytes32
435
+ const randomSeed = Math.random().toString(36).substring(2, 15);
436
+ const randomBytes32 = hashMessage(randomSeed);
437
+ const gameId = BigInt(randomBytes32.slice(0, 16));
438
+ const userRandomWord = hashMessage("I like big butts and I cannot lie!!");
439
+
440
+ const difficulty = 0;
441
+ const numRuns = isRiskyGame ? 3 : 1;
442
+
443
+ const encodedData = encodeAbiParameters(
444
+ [
445
+ { name: "difficulty", type: "uint8" },
446
+ { name: "numRuns", type: "uint8" },
447
+ { name: "gameId", type: "uint" },
448
+ { name: "ref", type: "address" },
449
+ { name: "randomWord", type: "bytes32" },
450
+ ],
451
+ [
452
+ difficulty,
453
+ numRuns,
454
+ gameId,
455
+ "0x91cF4F24EF2234C6C3c51669D0F2fa46FA562227",
456
+ userRandomWord,
457
+ ]
458
+ );
459
+
460
+ // Estimate gas
461
+ const gasEstimate = await contract.play.estimateGas(wallet.address, encodedData, { value: totalValue });
462
+ const gasLimitWithBuffer = (gasEstimate * BigInt(105)) / BigInt(100); // 5% buffer
463
+ console.log(` - Estimated gas: ${gasEstimate}, Gas limit with buffer: ${gasLimitWithBuffer}`);
464
+
465
+ // Send the transaction
466
+ const tx = await contract.play(wallet.address, encodedData, {
467
+ value: totalValue,
468
+ gasLimit: gasLimitWithBuffer,
469
+ maxPriorityFeePerGas: priorityFee,
470
+ maxFeePerGas: feeData.maxFeePerGas
471
+ });
472
+
473
+ const receipt = await tx.wait();
474
+ console.log(`🎉 Transaction Mined! Block number: ${receipt.blockNumber}`);
475
+ return true;
476
+
477
+ } catch (error) {
478
+ if (error.code === 'CALL_EXCEPTION' || error.reason) {
479
+ console.warn(` - Transaction failed, likely already executed by another bot. Reason: ${error.reason}`);
480
+ return false;
481
+ } else if (error.code === 'INSUFFICIENT_FUNDS') {
482
+ console.error(" - Transaction failed: Insufficient funds for gas * price + value.");
483
+ process.exit(1);
484
+ return false;
485
+ } else {
486
+ console.error(" - An unexpected error occurred during transaction execution:", error);
487
+ return false;
488
+ }
489
+ }
490
+ }
491
+
492
+ // --- Start the Bot ---
493
+ main().catch(error => {
494
+ console.error("A critical error occurred in the main function:", error);
495
+ process.exit(1);
496
+ });
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@ape-church/skill",
3
+ "version": "1.0.0",
4
+ "description": "Autonomous agent skill for Ape Church on ApeChain.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "bin": {
10
+ "apechurch": "./bin/cli.js"
11
+ },
12
+ "type": "module",
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "keywords": ["openclaw", "agent", "casino", "apechain", "ape", "church", "apechurch"],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "chalk": "^5.6.2",
21
+ "commander": "^14.0.3",
22
+ "dotenv": "^17.2.3",
23
+ "siwe": "^3.0.0",
24
+ "viem": "^2.45.1"
25
+ }
26
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": 1,
3
+ "persona": "balanced",
4
+ "username": "BOT_EXAMPLE_CLAWBOT",
5
+ "overrides": {
6
+ "min_bet_ape": 10,
7
+ "target_bet_pct": 0.08,
8
+ "max_bet_pct": 0.15,
9
+ "base_cooldown_ms": 600000,
10
+ "game_weights": {
11
+ "jungle-plinko": 1,
12
+ "dino-dough": 1,
13
+ "bubblegum-heist": 1
14
+ },
15
+ "plinko": { "mode": [1, 2], "balls": [50, 90] },
16
+ "slots": { "spins": [7, 12] }
17
+ },
18
+ "createdAt": "2026-02-03T00:00:00.000Z",
19
+ "updatedAt": "2026-02-03T00:00:00.000Z"
20
+ }