cherry-shared2 0.1.26__py3-none-any.whl

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,369 @@
1
+ import json
2
+ import logging
3
+ from typing import Any, Dict, Literal, Optional
4
+ import aiohttp
5
+ from cherry_shared.launchpads import LaunchPad
6
+ from cherry_shared.types.dexscreener import Pair
7
+ from cherry_shared.types.launchpad import LaunchpadToken
8
+
9
+
10
+ class InfoService:
11
+
12
+ def __init__(self, info_url: str, helper_url: str, logger: logging.Logger = None):
13
+ self.info_url = info_url
14
+ self.helper_url = helper_url
15
+ if logger is None:
16
+ self._logger = logging.getLogger(__name__)
17
+ else:
18
+ self._logger = logger
19
+
20
+ async def process_request(
21
+ self,
22
+ url: str,
23
+ body: Dict[str, Any] = None,
24
+ method: str = "GET",
25
+ query: Dict[str, str] = None,
26
+ headers: Dict[str, str] = None,
27
+ timeout: int = 60,
28
+ ):
29
+ if headers is None:
30
+ headers = {"Content-Type": "application/json"}
31
+ # Define a custom timeout
32
+ custom_timeout = aiohttp.ClientTimeout(total=timeout)
33
+
34
+ async with aiohttp.ClientSession(
35
+ headers=headers, timeout=custom_timeout
36
+ ) as session:
37
+ try:
38
+ if method == "GET":
39
+ async with session.get(
40
+ url, params=query, data=json.dumps(body) if body else None
41
+ ) as resp:
42
+ if resp.status >= 400:
43
+ return False, resp.reason
44
+ if "application/json" in resp.headers.get("Content-Type", ""):
45
+ res = await resp.json()
46
+ else:
47
+ res = await resp.text()
48
+ return (
49
+ False,
50
+ f"Unexpected Content-Type: {resp.headers.get('Content-Type')}, Response: {res}",
51
+ )
52
+ elif method == "POST":
53
+ async with session.post(url, data=json.dumps(body)) as resp:
54
+ if resp.status >= 400:
55
+ return False, resp.reason
56
+ if "application/json" in resp.headers.get("Content-Type", ""):
57
+ res = await resp.json()
58
+ else:
59
+ res = await resp.text()
60
+ return (
61
+ False,
62
+ f"Unexpected Content-Type: {resp.headers.get('Content-Type')}, Response: {res}",
63
+ )
64
+
65
+ return True, res
66
+ except Exception as e:
67
+ self._logger.error(f"Error in InfoService.process_request(): {e}")
68
+ return False, "Exception: " + str(e)
69
+
70
+ async def search_address(self, address: str):
71
+ url = f"{self.info_url}/info/{address}"
72
+ success, data = await self.process_request(url)
73
+ if success:
74
+ return [Pair.from_dexsc_dict(item) for item in data["data"][:10]]
75
+
76
+ async def get_address_type(self, address: str):
77
+ url = f"{self.info_url}/address"
78
+ body = {"address": address}
79
+ success, res = await self.process_request(url, body)
80
+ if success:
81
+ return res["data"]
82
+
83
+ async def get_pair_by_pair_address(self, address: str, chainId: str):
84
+ url = f"{self.info_url}/info/pair"
85
+ success, res = await self.process_request(
86
+ url, {"address": address, "chainId": chainId}, "POST"
87
+ )
88
+
89
+ if not success:
90
+ self._logger.error(res)
91
+ return None
92
+ return Pair.from_dexsc_dict(res["data"])
93
+
94
+ async def get_chains_data(self):
95
+ url = f"{self.info_url}/chains"
96
+ success, data = await self.process_request(url)
97
+ if success:
98
+ return data
99
+
100
+ async def get_total_supply(self, chain: str, address: str):
101
+ url = self.helper_url + "/totalsupply"
102
+ body = {"chain": chain, "address": address}
103
+ success, data = await self.process_request(url, body, "POST")
104
+ if success:
105
+ return data["supply"], data["decimals"]
106
+ return None, None
107
+
108
+ async def get_trending(self):
109
+ url = self.helper_url + "/trending"
110
+ success, data = await self.process_request(url)
111
+ if success:
112
+ return data
113
+ return None
114
+
115
+ async def send_confirm_req(
116
+ self, pair_address: str, contract_address: str, chain_id: str, chat_id: int
117
+ ):
118
+ url = self.helper_url + "/confirm"
119
+ self._logger.debug(f"{pair_address}, {chain_id}, {chat_id}")
120
+ body = {
121
+ "pairAddress": pair_address,
122
+ "tokenAddress": contract_address,
123
+ "chainId": chain_id,
124
+ "chatId": chat_id,
125
+ }
126
+ self._logger.debug(f"send_confirm_req: {body}")
127
+ success, data = await self.process_request(url, body, "POST")
128
+ if not success:
129
+ self._logger.error(f"failed to send confirm req: {data}")
130
+
131
+ async def send_delete_req(self, tokenAddress: str, chat_id: int):
132
+ url = self.helper_url + "/delete"
133
+ body = {
134
+ "tokenAddress": tokenAddress,
135
+ "chatId": chat_id,
136
+ }
137
+ success, data = await self.process_request(url, body, "POST")
138
+ if not success:
139
+ self._logger.error(f"failed delete token req: {data}")
140
+
141
+ async def send_update_supply(self, tokenAddress: str, chat_id: int, supply: int):
142
+ self._logger.debug(f"{tokenAddress}, {chat_id}, {supply}")
143
+ url = self.helper_url + "/updatesupply"
144
+ body = {
145
+ "address": tokenAddress,
146
+ "chatId": chat_id,
147
+ "supply": supply,
148
+ }
149
+ success, data = await self.process_request(url, body, "POST")
150
+ if not success:
151
+ self._logger.error(f"failed update supply req: {data}")
152
+
153
+ async def generate_wallet(self, wallet_type: str):
154
+ wallet_endpoints = {
155
+ "solana": "/generatesolanawallet",
156
+ "tron": "/generatetronwallet",
157
+ "sui": "/generatesuiawallet",
158
+ }
159
+
160
+ if wallet_type not in wallet_endpoints:
161
+ self._logger.error(f"wallet endpoint not provided: {wallet_type}")
162
+ return
163
+
164
+ url = self.helper_url + wallet_endpoints[wallet_type]
165
+ success, data = await self.process_request(url)
166
+
167
+ if not success:
168
+ self._logger.error(f"Failed {wallet_type} wallet request: {data}")
169
+ return
170
+ self._logger.debug(f"generated new wallet {data['publicKey'][:10]}")
171
+ return data["publicKey"], data["privateKey"]
172
+
173
+ async def get_balance(self, wallet_address: str, chain: str):
174
+ url = self.helper_url + "/balance"
175
+ success, data = await self.process_request(
176
+ url, query={"tokenAddress": wallet_address, "chain": chain}
177
+ )
178
+ if not success:
179
+ self._logger.error(f"failed to get balance: {data}")
180
+ return None
181
+ return float(data["balance"])
182
+
183
+ async def buyNburn(
184
+ self,
185
+ raid_id: int,
186
+ chat_id: int,
187
+ amount: float,
188
+ token_address: str,
189
+ pair_address: str,
190
+ chain: str,
191
+ pk: str,
192
+ ):
193
+ url = self.helper_url + "/burn"
194
+ body = {
195
+ "raidId": raid_id,
196
+ "chatId": chat_id,
197
+ "amount": amount,
198
+ "tokenAddress": token_address,
199
+ "pairAddress": pair_address,
200
+ "chain": chain,
201
+ "privateKey": pk,
202
+ }
203
+ success, data = await self.process_request(url, body, "POST")
204
+ if not success:
205
+ self._logger.error(f"failed to send confirm req: {data}")
206
+ return False, data
207
+
208
+ status: bool = data["status"]
209
+ self._logger.debug(f"buyNburn status = {status}")
210
+ return status, None
211
+
212
+ async def check_status(self, raid_id: int):
213
+ try:
214
+ url = self.helper_url + f"/status?raidId={raid_id}"
215
+ success, data = await self.process_request(url)
216
+ if not success:
217
+ self._logger.error(f"failed to send confirm req: {data}")
218
+ return "error", data
219
+ res = data["status"], (
220
+ data["buyTx"],
221
+ data["burnTx"],
222
+ data["finalBountyAmount"],
223
+ )
224
+ return res
225
+ except Exception as e:
226
+ self._logger.error(
227
+ f"Error in InfoService.check_status(): {e}", exc_info=True
228
+ )
229
+ return "error", str(e)
230
+
231
+ async def withdraw(self, chain: str, toAddress: str, userId: int):
232
+ url = self.helper_url + "/withdraw"
233
+ query = {"chain": chain, "toAddress": toAddress, "userId": userId}
234
+ success, res = await self.process_request(url, query=query)
235
+ if not success:
236
+ self._logger.error(f"Info Withdraw: {res}")
237
+ return False, res
238
+ tx_hash = res["txHash"]
239
+ return True, tx_hash
240
+
241
+ async def validate_purchase(
242
+ self,
243
+ user_id: int,
244
+ value: float,
245
+ chain: str,
246
+ promo_code: str = None,
247
+ payout_value: float = None,
248
+ token_id: int = None,
249
+ trendingId: int = None,
250
+ volume_bot=0,
251
+ status: Literal["normal", "volume", "holder"] = "normal",
252
+ hours=None,
253
+ base_amount: float = 0.01,
254
+ **kwargs,
255
+ ):
256
+ url = self.helper_url + "/trendingslot/verify"
257
+ body = {
258
+ "chain": chain,
259
+ "value": value,
260
+ "userId": user_id,
261
+ "promoCode": promo_code,
262
+ "payoutValue": payout_value,
263
+ "tokenId": token_id,
264
+ "trendingId": trendingId,
265
+ "volumeBot": 1 if status == "holder" else volume_bot,
266
+ "status": status,
267
+ "hours": hours,
268
+ "baseAmount": base_amount,
269
+ }
270
+
271
+ # Add any additional key-value arguments to the body
272
+ body.update(kwargs)
273
+
274
+ success, res = await self.process_request(url, body, "POST")
275
+ if not success:
276
+ self._logger.error(f"Info validate_purchase: {res}")
277
+ return -1, res
278
+
279
+ code = int(res["code"])
280
+ message = res["message"] if code == 0 else res["error"]
281
+
282
+ return code, message
283
+
284
+ async def get_token_info(self, address: str, chain: str):
285
+ url = self.info_url + "/info/tokeninfo"
286
+ body = {"address": address, "chain": chain}
287
+ success, res = await self.process_request(url, body=body, method="POST")
288
+ if not success:
289
+ self._logger.error(f"get_token_info: {res}")
290
+ return None
291
+
292
+ code = int(res["code"])
293
+ if code == 0:
294
+ self._logger.debug(f"get_token_info: {res['data']}")
295
+ return res["data"]
296
+
297
+ async def get_launchpad_data(self, launchpad: LaunchPad, address: str):
298
+ url = self.info_url + f"/{launchpad.info_route}"
299
+ body = {"address": address, "chatId": 0}
300
+ success, res = await self.process_request(url, body=body, method="POST")
301
+ if not success:
302
+ self._logger.error(f"get {launchpad.name} info: {res}")
303
+ return None
304
+
305
+ code = int(res["code"])
306
+ self._logger.debug(f"get_launchpad_data: {res['data']}")
307
+ if code == 0:
308
+ return LaunchpadToken.create_token_info(res["data"])
309
+
310
+ async def get_fees(self, address: str):
311
+ try:
312
+ url = self.helper_url + f"/getFees"
313
+ body = {"address": address}
314
+ success, res = await self.process_request(url, body=body, method="POST")
315
+ if not success:
316
+ self._logger.error(f"get_fees info for address {address}: {res}")
317
+ return None
318
+ self._logger.debug(f"get_fees: {res}")
319
+ return round(float(res.get("fee", 1)), 2)
320
+ except Exception as e:
321
+ self._logger.error(f"Error in InfoService.get_fees(): {e}", exc_info=True)
322
+ return 1
323
+
324
+ async def send_message(self, chat_id: int, message: str, thread_id: str = None):
325
+ url = self.helper_url + "/sendtrendingmessage"
326
+ body = {
327
+ "chat_id": chat_id,
328
+ "text": message,
329
+ "thread_id": thread_id,
330
+ }
331
+ success, res = await self.process_request(url, body=body, method="POST")
332
+ if not success:
333
+ self._logger.error(f"send_message: {res}")
334
+ return False, res
335
+ return True, res
336
+
337
+ async def edit_message(
338
+ self, chat_id: int, message_id: int, message: str, thread_id: str = None
339
+ ):
340
+ url = self.helper_url + "/sendtrendingmessage"
341
+ body = {
342
+ "chat_id": chat_id,
343
+ "msg_id": message_id,
344
+ "text": message,
345
+ "thread_id": thread_id,
346
+ }
347
+
348
+ success, res = await self.process_request(url, body=body, method="POST")
349
+ if not success:
350
+ self._logger.error(f"edit_message: {res}")
351
+ return False, res
352
+ return True, res
353
+
354
+ async def check_rate_limit(
355
+ self,
356
+ chat_id: Optional[int] = None,
357
+ priority: Literal["high", "normal", "low"] = "normal",
358
+ ):
359
+ url = self.helper_url + "/check-rate-limit"
360
+ body = {
361
+ "chat_id": chat_id,
362
+ "priority": priority,
363
+ }
364
+ success, res = await self.process_request(url, body=body, method="POST")
365
+ if not success:
366
+ self._logger.error(f"check_rate_limit: {res}")
367
+ return 0
368
+ delay = int(res.get("delay_ms", 0)) / 1000.0
369
+ return delay
@@ -0,0 +1,5 @@
1
+ from .blockchains import Blockchains, BlockchainId, ChainType
2
+ from .bot_strings import BotStrings
3
+ from .constants import Constants
4
+ from .emojis import Emojis, state_emoji
5
+ from .launchpads import LaunchPads, LaunchPadId, LaunchPad
@@ -0,0 +1,265 @@
1
+ from typing import Dict, List, Set
2
+ from cherry_shared.types.blockchain import Blockchain, SupportedFeatures
3
+ from functools import lru_cache
4
+ from enum import Enum
5
+
6
+
7
+ class ChainType(Enum):
8
+ EVM = "evm"
9
+ SOLANA = "solana"
10
+ TRON = "tron"
11
+ TON = "ton"
12
+ SUI = "sui"
13
+
14
+
15
+ class BlockchainId:
16
+ SOL = "solana"
17
+ ETH = "ethereum"
18
+ BSC = "bsc"
19
+ TRX = "tron"
20
+ BASE = "base"
21
+ SUI = "sui"
22
+ MANTA = "manta"
23
+ MATIC = "polygon"
24
+ XPL = "plasma"
25
+
26
+
27
+ class Blockchains:
28
+ SOL = Blockchain(
29
+ BlockchainId.SOL,
30
+ "SOL",
31
+ "Solana",
32
+ "SOL",
33
+ "https://solscan.io/token",
34
+ False,
35
+ {
36
+ 3: {3: 0, 6: 0, 12: 6, 24: 9},
37
+ 10: {3: 1.6 , 6: 2.5, 12: 4, 24: 7},
38
+ },
39
+ ["SOL", "USDC"],
40
+ {
41
+ 150: 0.2,
42
+ 300: 0.4,
43
+ },
44
+ SupportedFeatures(custom_token=True, wallet=True, payment=True),
45
+ 0.1,
46
+ extras={
47
+ "volume_price": {
48
+ 50000: 3.2,
49
+ 100_000: 5.8,
50
+ 250_000: 13.0,
51
+ 500_000: 24.6,
52
+ 1_000_000: 45.8,
53
+ 5_000_000: 214.6,
54
+ },
55
+ "holders_price": {
56
+ 1000: 2.2,
57
+ 2500: 5.5,
58
+ 5000: 11,
59
+ 10000: 22,
60
+ 25000: 55,
61
+ 100_000: 220,
62
+ },
63
+ "boost_price": {1000: 0.4, 2000: 0.7, 4000: 1.3, 8000: 2.5},
64
+ },
65
+ )
66
+
67
+ ETH = Blockchain(
68
+ BlockchainId.ETH,
69
+ "ETH",
70
+ "Ethereum",
71
+ "ETH",
72
+ "https://etherscan.io/token",
73
+ True,
74
+ {
75
+ 3: {3: 0.2, 6: 0.4, 12: 0.6, 24: 0.9},
76
+ 10: {3: 0.1, 6: 0.2, 12: 0.4, 24: 0.6},
77
+ },
78
+ ["WETH", "USDT", "USDC"],
79
+ {150: 0.02, 300: 0.04, 950: 0.1, 2500: 0.2},
80
+ SupportedFeatures(custom_token=True, wallet=True, payment=True),
81
+ 0.05,
82
+ )
83
+
84
+ BSC = Blockchain(
85
+ BlockchainId.BSC,
86
+ "BSC",
87
+ "Binance Smart Chain",
88
+ "BNB",
89
+ "https://bscscan.com/token",
90
+ True,
91
+ {
92
+ 3: {3: 0.7, 6: 1.2, 12: 2.0, 24: 3.5},
93
+ 10: {3: 0.6, 6: 0.8, 12: 1.5, 24: 2.5},
94
+ },
95
+ ["WBNB", "USDT", "USDC"],
96
+ {150: 0.1, 300: 0.2, 950: 0.5, 2500: 1},
97
+ SupportedFeatures(custom_token=False, wallet=True, payment=True),
98
+ 0.1,
99
+ extras={
100
+ "boost_price": {1000: 0.7, 2000: 1.4, 4000: 2.5, 8000: 4.6},
101
+ },
102
+ )
103
+
104
+ TRX = Blockchain(
105
+ BlockchainId.TRX,
106
+ "TRX",
107
+ "Tron",
108
+ "TRX",
109
+ "https://tronscan.org/#/token20",
110
+ False,
111
+ {
112
+ 3: {3: 1260, 6: 2270, 12: 3530, 24: 6050},
113
+ 10: {3: 1000, 6: 1770, 12: 3020, 24: 4540},
114
+ },
115
+ ["WTRX", "USDT", "USDC"],
116
+ {150: 0.02, 300: 0.04, 950: 0.1, 2500: 0.2},
117
+ SupportedFeatures(custom_token=False, wallet=True, payment=True),
118
+ 10,
119
+ )
120
+
121
+ BASE = Blockchain(
122
+ BlockchainId.BASE,
123
+ "BASE",
124
+ "Base",
125
+ "ETH",
126
+ "https://basescan.org/token",
127
+ True,
128
+ {
129
+ 3: {3: 0.2, 6: 0.4, 12: 0.6, 24: 0.9},
130
+ 10: {3: 0.1, 6: 0.2, 12: 0.4, 24: 0.6},
131
+ },
132
+ ["WETH", "USDT", "USDC"],
133
+ {150: 0.02, 300: 0.04, 950: 0.1, 2500: 0.2},
134
+ SupportedFeatures(custom_token=False, wallet=True, payment=True),
135
+ 0.1,
136
+ )
137
+
138
+ MATIC = Blockchain(
139
+ BlockchainId.MATIC,
140
+ "POL",
141
+ "Polygon",
142
+ "POL",
143
+ "https://polygonscan.com/token",
144
+ True,
145
+ {
146
+ 3: {3: 800, 6: 1330, 12: 2100, 24: 3430},
147
+ 10: {3: 520, 6: 800, 12: 1330, 24: 2100},
148
+ },
149
+ ["WPOL", "USDT", "USDC"],
150
+ {150: 0.02, 300: 0.04, 950: 0.1, 2500: 0.2},
151
+ SupportedFeatures(custom_token=False, wallet=True, payment=True),
152
+ 10,
153
+ )
154
+
155
+ MANTA = Blockchain(
156
+ BlockchainId.MANTA,
157
+ "MANTA",
158
+ "MANTA",
159
+ "ETH",
160
+ "https://pacific-explorer.manta.network/token",
161
+ True,
162
+ {
163
+ 3: {3: 0.2, 6: 0.4, 12: 0.6, 24: 0.9},
164
+ 10: {3: 0.1, 6: 0.2, 12: 0.4, 24: 0.6},
165
+ },
166
+ ["WETH", "USDT", "USDC", "MANTA"],
167
+ {150: 0.02, 300: 0.04, 950: 0.1, 2500: 0.2},
168
+ SupportedFeatures(custom_token=False, wallet=True, payment=True),
169
+ 0.05,
170
+ )
171
+
172
+ SUI = Blockchain(
173
+ "sui",
174
+ "SUI",
175
+ "SUI Network",
176
+ "SUI",
177
+ "https://suiscan.xyz/mainnet/coin",
178
+ False,
179
+ {
180
+ 3: {3: 170, 6: 295, 12: 505, 24: 845},
181
+ 10: {3: 125, 6: 230, 12: 395, 24: 675},
182
+ },
183
+ ["SUI", "USDC"],
184
+ {150: 0.02, 300: 0.04, 950: 0.1, 2500: 0.2},
185
+ SupportedFeatures(custom_token=False, wallet=True, payment=True),
186
+ 10,
187
+ )
188
+
189
+ XPL = Blockchain(
190
+ "plasma",
191
+ "XPL",
192
+ "Plasma",
193
+ "XPL",
194
+ "https://plasmascan.to/token",
195
+ True,
196
+ {
197
+ 3: {3: 518, 6: 951, 12: 1677, 24: 2715},
198
+ 10: {3: 415, 6: 726, 12: 1487, 24: 1989},
199
+ },
200
+ ["WXPL", "USDT0", "USDC", "XPL"],
201
+ {150: 0.02, 300: 0.04, 950: 0.1, 2500: 0.2},
202
+ SupportedFeatures(custom_token=False, wallet=True, payment=True),
203
+ 10,
204
+ )
205
+
206
+ @staticmethod
207
+ @lru_cache()
208
+ def dict() -> Dict[str, Blockchain]:
209
+ return {
210
+ BlockchainId.SOL: Blockchains.SOL,
211
+ BlockchainId.ETH: Blockchains.ETH,
212
+ BlockchainId.BSC: Blockchains.BSC,
213
+ BlockchainId.TRX: Blockchains.TRX,
214
+ BlockchainId.BASE: Blockchains.BASE,
215
+ BlockchainId.SUI: Blockchains.SUI,
216
+ BlockchainId.MANTA: Blockchains.MANTA,
217
+ BlockchainId.MATIC: Blockchains.MATIC,
218
+ BlockchainId.XPL: Blockchains.XPL,
219
+ }
220
+
221
+ @staticmethod
222
+ def all() -> List[Blockchain]:
223
+ return Blockchains.dict().values()
224
+
225
+ @lru_cache()
226
+ def get_by_id(id: str) -> Blockchain:
227
+ """Retrieve a blockchain by its ID."""
228
+ return Blockchains.dict().get(id)
229
+
230
+ @lru_cache()
231
+ def get_all_ids() -> List[str]:
232
+ """Return a list of all blockchain IDs."""
233
+ return Blockchains.dict().keys()
234
+
235
+ @lru_cache()
236
+ def all_chain_types() -> Set[str]:
237
+ """Return a list of all blockchain IDs."""
238
+ types = {c.chain_type for c in Blockchains.all()}
239
+ return types
240
+
241
+ @lru_cache()
242
+ def evms() -> List[Blockchain]:
243
+ """Retrieve all EVM-compatible blockchains."""
244
+ return [chain for chain in Blockchains.all() if chain.evm]
245
+
246
+ @staticmethod
247
+ @lru_cache()
248
+ def get_by_chain_type(chain_type: str) -> List[Blockchain]:
249
+ return [chain for chain in Blockchains.all() if chain.chain_type == chain_type]
250
+
251
+ @staticmethod
252
+ @lru_cache()
253
+ def get_chain_symbols() -> List[str]:
254
+ return [c.symbol for c in Blockchains.all()]
255
+
256
+ @staticmethod
257
+ @lru_cache()
258
+ def get_active_wallet_chains() -> List[str]:
259
+ chain_types = Blockchains.all_chain_types()
260
+ active_chain_types = []
261
+ for chain_type in chain_types:
262
+ chain = Blockchains.get_by_chain_type(chain_type)[0]
263
+ if chain and chain.supported_features.wallet:
264
+ active_chain_types.append(chain_type)
265
+ return active_chain_types