@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.
- package/CHANGELOG.md +43 -0
- package/HEARTBEAT.md +50 -0
- package/PUBLISHING.md +142 -0
- package/SKILL.md +177 -0
- package/STRATEGY.md +69 -0
- package/agent_nodes.md +144 -0
- package/assets/HEARTBEAT.md +50 -0
- package/assets/SKILL.md +177 -0
- package/assets/STRATEGY.md +69 -0
- package/assets/skill.json +13 -0
- package/bin/cli.js +1247 -0
- package/example_log_filtering.js +113 -0
- package/example_play_via_contract.js +496 -0
- package/package.json +26 -0
- package/profile.example.json +20 -0
- package/registry.js +68 -0
- package/skill.json +13 -0
|
@@ -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
|
+
}
|