←︎ irccex :: 54783c8


1
commit 54783c8b7cef920a020ec5a093d20ac006aa178c
2
Author: acidvegas <acid.vegas@acid.vegas>
3
Date:   Fri Feb 21 22:10:37 2020 -0500
4
5
    Initial commit
6
---
7
 LICENSE                     |  15 ++
8
 README.md                   | 100 ++++++++
9
 funding.yml                 |   1 +
10
 irccex/core/cmc.py          |  56 +++++
11
 irccex/core/config.py       |  49 ++++
12
 irccex/core/constants.py    | 213 ++++++++++++++++
13
 irccex/core/functions.py    | 133 ++++++++++
14
 irccex/core/irc.py          | 600 ++++++++++++++++++++++++++++++++++++++++++++
15
 irccex/data/cert/.gitignore |   4 +
16
 irccex/data/dbx.py          |  12 +
17
 irccex/data/logs/.gitignore |   4 +
18
 irccex/irccex.py            |  20 ++
19
 12 files changed, 1207 insertions(+)
20
21
diff --git a/LICENSE b/LICENSE
22
new file mode 100644
23
index 0000000..d521bd0
24
--- /dev/null
25
+++ b/LICENSE
26
@@ -0,0 +1,15 @@
27
+ISC License
28
+
29
+Copyright (c) 2020, acidvegas <acid.vegas@acid.vegas>
30
+
31
+Permission to use, copy, modify, and/or distribute this software for any
32
+purpose with or without fee is hereby granted, provided that the above
33
+copyright notice and this permission notice appear in all copies.
34
+
35
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
36
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
37
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
38
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
39
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
40
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
41
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
42
diff --git a/README.md b/README.md
43
new file mode 100644
44
index 0000000..49e4cc3
45
--- /dev/null
46
+++ b/README.md
47
@@ -0,0 +1,100 @@
48
+# irccex
49
+A fantasy cryptocurrency exchange for the Internet Relay Chat (IRC) protocol.
50
+
51
+*"You can pretend trade currencies, instead of trade pretend currencies!"* ~ some guy on reddit
52
+
53
+*"this bot is begging to be used for not playing"* ~ yuritrue
54
+
55
+*"its a game, trade hard & get rich or die tryin`"* ~ contra
56
+
57
+###### Requirments
58
+* [Python](https://www.python.org/downloads/) *(**Note:** This script was developed to be used with the latest version of Python.)*
59
+
60
+###### Information
61
+This bot lets users of an IRC channel "pretend" trade cryptocurrencies in a competitive round-based game manner. Real time market data from [CoinMarketCap](https://coinmarketcap.com/) is used to obtain the values and other information of the cryptocurrencies traded.
62
+
63
+Users can make an exchange account with the `!register` command, and after 24 hours, are given `init_funds` to start trading with. There is no authentication required for interacting with exchange accounts. Everything is regulated based on the nick of whoever issues a command. Users can `/nick` to any nick with an account to access it. This means it is possible to steal cryptocurrency using the `!send` command.
64
+
65
+Bank accounts can be added to with the `!cashout` command, which deposits all of your USD profits from your wallet. Once money is in the bank, it can not be withdrawn. Maintaining your nick and doing frequent cashouts to your bank is the only way to protect your money. The goal is to have the largest bank account by the end of the round.
66
+
67
+Every round starts a new "game mode", which affects how the round will end or how the scoring is done for the winner(s). The main goal of the entire game is to collect points from winning game rounds, which can be seen using the `!scores` command.
68
+
69
+###### Strategy
70
+There are many ways to become skilled in this game. Making *legit* trades that you would do in the market with real money is the last fucking on that list.
71
+
72
+I know what you're thinking... "Can't I just register 500 accounts & have them all !trade and !send money and get RICH?" Yes, you can do that! But just know you are not going to be the first or the last to ever think of that.
73
+
74
+This game can introduce lots of trolling & botting. Get creative & figure out ways to make money, secure money, steal money, and more!
75
+
76
+###### Loops
77
+* The database will backup to a pickle file every hour. The last backup time can be seen in the `@stats` reply. Make note of this before restarting the bot for some reason.
78
+
79
+* The exchange will random enter "maintenance mode" once every 3 days, which locks the use of all exchange commands. Maintenance can last an hour to a full day.
80
+
81
+* All fees are collected & stored in the "reward pool". The bot will make an announcement randomly before the round ends & anyone who types `!bang` after that will get a reward taken from the pool. It takes 25 to 50 `!bang`'s to completely empty the pool. The final person to `!bang` will get the largest reward.
82
+
83
+###### Trading Pair Rules
84
+- USD can only be used for buying or selling BTC, ETH, & LTC.
85
+
86
+- BTC & ETH are the only major trading pairs between all other cryptocurrencies.
87
+
88
+###### Fees & Minimums
89
+| Command | Fee | Minimum |
90
+| --- | --- | --- |
91
+| cashout | 2% | $10000 USD Balance |
92
+| send | 1% | $5000 Balance |
93
+| trade | 0.1% | $5 |
94
+
95
+###### Exchange Commands
96
+| Command | Description |
97
+| --- | --- |
98
+| @irccex | Information about the bot. |
99
+| @stats | Statistics on the exchange, market, and more. |
100
+| $\<symbol> | Return information for the \<symbol> cryptocurrency. *(\<symbol> can also be a comma seperated list)* |
101
+| !bang | Grab a reward when the reward pool is triggered. |
102
+| !bank | Return your total bank account balance. |
103
+| !bottom \<1h/24h/7d/value/volume> | Return information for the bottom 10 cryptocurrencies based on the \<1h/24h/7d/value>. |
104
+| !cashout [msg] | Deposit all your USD to your bank account and optionally leave the [msg] message for the !rich list. |
105
+| !portfolio | Total USD value of your wallet. |
106
+| !register | Register an exchange account. |
107
+| !rich | Return the top 10 richest bank accounts. |
108
+| !score | Return your score & rank on the leaderboard. |
109
+| !scores | Return the top 10 players on the leaderboard. |
110
+| !send \<nick> \<amount> \<symbol> | Send \<amount> of \<symbol> to \<nick>. |
111
+| !top [\<1h/24h/7d/value/volume>] | Return information for the top 10 cryptocurrencies, optionally based on \<1h/24h/7d/value/volume>. |
112
+| !trade \<pair> \<amount> | Trade \<amount> between \<pair>. |
113
+| !value \<amount> \<name> | Convert \<amount> of the \<name> cryptocurrency to it's value. |
114
+| !wallet | View your exchange wallet. |
115
+
116
+- \<amount> can be the symbols amount, USD amount if prefixed with a $, or the total amount you hold if * is used.
117
+	* `!send acidvegas 0.05 BTC` sends 0.05 BTC to acidvegas.
118
+	* `!send chrono $10.00 BTC` sends $10.00 worth of BTC to chrono.
119
+	* `!send mikejonez * BTC` sends all of your BTC to mikejonez.
120
+	* `!send vap0r 1,000,000 USD` commas can also be used in the amount.
121
+
122
+- \<pair> is the from_symbol/to_symbol you are wanting to make trades with.
123
+	* `!trade ETH/NANO 0.14` trades 0.14 ETH to NANO.
124
+	* `!trade XRP/BTC $100` trades $100 USD worth of XRP to BTC.
125
+	* `!trade ETH/DOGE *` trades all of your ETH to DOGE.
126
+
127
+###### Patreons
128
+Support the project development if you like it: [Patreon.com/irccex](https://patreon.com/irccex)
129
+
130
+The IRCCEX project is completely open source & non-profit, though any support/pledges will help in motivation towards more development and adding new features!
131
+
132
+###### Future Concepts & Ideas
133
+* IRCCEX BlockChain - Keep an on-going ledger of every single transaction ever made in the channel. *(No idea what use it would have. Maybe a `!trades` command for recent history. The idea makes me laugh)*
134
+* Buying options - Spend a large sum of money on features like locking someone from trading for X amount of time (Charge Y per hour and max it to 24 hours), wallet spying, wallet bombing (sending a bunch of shitcoins), hindsight where you get private message alerts on a coins price changing (can be used whenever only once).
135
+* Double fees for 1-3 days randomly in round!
136
+* Post reward pool bangs will make you lose money to fuck with people spamming hard with bots to rack up the pool
137
+* Crate Drops - A "crate" will drop randomly in the channel that requires multiple `!break`'s to open it. Once opened, there will be 4 items you can get by typing the ! command under it. Items will include money, extra privlegges like holding more coins, and other items you can win.
138
+* **Suicide Round** - There is no bank in this mode, and if you lose your nick through a NICK or QUIT, you lose your wallet. Round can last 3-7 days and the top 10 wallets will score.
139
+* **Bank Round** - Round lasts a week and the top 10 players in the bank will score.
140
+* **Flash Match** - Round lasts a day and the top 10 players in the bank will score.
141
+
142
+We are running IRCCEX actively in **#exchange** on **EFNet** & **SuperNETs**, come chat with us, make some money, and share ideas!
143
+
144
+###### Mirrors
145
+- [acid.vegas](https://acid.vegas/irccex) *(main)*
146
+- [GitHub](https://github.com/acidvegas/irccex)
147
+- [GitLab](https://gitlab.com/acidvegas/irccex)
148
diff --git a/funding.yml b/funding.yml
149
new file mode 100644
150
index 0000000..6bec539
151
--- /dev/null
152
+++ b/funding.yml
153
@@ -0,0 +1 @@
154
+patreon: irccex
155
diff --git a/irccex/core/cmc.py b/irccex/core/cmc.py
156
new file mode 100644
157
index 0000000..7a2a2d9
158
--- /dev/null
159
+++ b/irccex/core/cmc.py
160
@@ -0,0 +1,56 @@
161
+#!/usr/bin/env python
162
+# IRC Cryptocurrency Exchange (IRCCEX) - Developed by acidvegas in Python (https://acid.vegas/irccex)
163
+# cmc.py
164
+
165
+import json
166
+import time
167
+
168
+import requests
169
+
170
+import config
171
+
172
+class CoinMarketCap(object):
173
+	def __init__(self):
174
+		self.cache = {'global':dict(), 'ticker':dict()}
175
+		self.last  = {'global':0     , 'ticker':0     }
176
+
177
+	def _api(self, _endpoint, _params={}):
178
+		session = requests.Session()
179
+		session.headers.update({'Accept':'application/json', 'Accept-Encoding':'deflate, gzip', 'X-CMC_PRO_API_KEY':config.CMC_API_KEY})
180
+		response = session.get('https://pro-api.coinmarketcap.com/v1/' + _endpoint, params=_params, timeout=15)
181
+		return json.loads(response.text.replace(': null', ': "0"'))['data']
182
+
183
+	def _global(self):
184
+		if time.time() - self.last['global'] < 300:
185
+			return self.cache['global']
186
+		else:
187
+			data = self._api('global-metrics/quotes/latest')
188
+			self.cache['global'] = {
189
+				'cryptocurrencies' : data['active_cryptocurrencies'],
190
+				'exchanges'        : data['active_exchanges'],
191
+				'btc_dominance'    : int(data['btc_dominance']),
192
+				'eth_dominance'    : int(data['eth_dominance']),
193
+				'market_cap'       : int(data['quote']['USD']['total_market_cap']),
194
+				'volume'           : int(data['quote']['USD']['total_volume_24h'])
195
+			}
196
+			self.last['global'] = time.time()
197
+			return self.cache['global']
198
+
199
+	def _ticker(self):
200
+		if time.time() - self.last['ticker'] < 300:
201
+			return self.cache['ticker']
202
+		else:
203
+			data = self._api('cryptocurrency/listings/latest', {'limit':'5000'})
204
+			self.cache['ticker'] = dict()
205
+			for item in data:
206
+				self.cache['ticker'][item['symbol']] = {
207
+					'name'       : item['name'],
208
+					'symbol'     : item['symbol'],
209
+					'rank'       : item['cmc_rank'],
210
+					'price'      : float(item['quote']['USD']['price']),
211
+					'percent'    : {'1h':float(item['quote']['USD']['percent_change_1h']), '24h':float(item['quote']['USD']['percent_change_24h']), '7d':float(item['quote']['USD']['percent_change_7d'])},
212
+					'volume'     : int(float(item['quote']['USD']['volume_24h'])),
213
+					'market_cap' : int(float(item['quote']['USD']['market_cap']))
214
+				}
215
+			self.last['ticker'] = time.time()
216
+			return self.cache['ticker']
217
diff --git a/irccex/core/config.py b/irccex/core/config.py
218
new file mode 100644
219
index 0000000..81e4b3c
220
--- /dev/null
221
+++ b/irccex/core/config.py
222
@@ -0,0 +1,49 @@
223
+#!/usr/bin/env python
224
+# IRC Cryptocurrency Exchange (IRCCEX) - Developed by acidvegas in Python (https://acid.vegas/irccex)
225
+# config.py
226
+
227
+class connection:
228
+	server     = 'irc.server.com'
229
+	port       = 6667
230
+	ipv6       = False
231
+	ssl        = False
232
+	ssl_verify = False
233
+	vhost      = None
234
+	channel    = '#exchange'
235
+	key        = None
236
+	modes      = None
237
+
238
+class cert:
239
+	key      = None
240
+	file     = None
241
+	password = None
242
+
243
+class ident:
244
+	nickname = 'IRCCEX'
245
+	username = 'exchange'
246
+	realname = 'acid.vegas/irccex'
247
+
248
+class login:
249
+	network  = None
250
+	nickserv = None
251
+	operator = None
252
+
253
+class throttle:
254
+	cmd       = 3
255
+	msg       = 0.5
256
+	reconnect = 10
257
+	rejoin    = 3
258
+
259
+class limits:
260
+	assets  = 10   # Maximum number of assets held
261
+	cashout = 5000 # Minimum USD required to !cashout
262
+	init    = 1000 # Initial USD for people who !register
263
+	send    = 2500 # Minimum balance required for !send
264
+	trade   = 5    # Minimum USD amount for !trade
265
+
266
+class fees:
267
+	cashout = 0.02  # 2%
268
+	send    = 0.01  # 1%
269
+	trade   = 0.001 # 0.1%
270
+
271
+CMC_API_KEY = 'CHANGEME' # https://pro.coinmarketcap.com/signup
272
diff --git a/irccex/core/constants.py b/irccex/core/constants.py
273
new file mode 100644
274
index 0000000..ce10895
275
--- /dev/null
276
+++ b/irccex/core/constants.py
277
@@ -0,0 +1,213 @@
278
+#!/usr/bin/env python
279
+# IRC Cryptocurrency Exchange (IRCCEX) - Developed by acidvegas in Python (https://acid.vegas/irccex)
280
+# constants.py
281
+
282
+# Control Characters
283
+bold      = '\x02'
284
+color     = '\x03'
285
+italic    = '\x1D'
286
+underline = '\x1F'
287
+reverse   = '\x16'
288
+reset     = '\x0f'
289
+
290
+# Color Codes
291
+white       = '00'
292
+black       = '01'
293
+blue        = '02'
294
+green       = '03'
295
+red         = '04'
296
+brown       = '05'
297
+purple      = '06'
298
+orange      = '07'
299
+yellow      = '08'
300
+light_green = '09'
301
+cyan        = '10'
302
+light_cyan  = '11'
303
+light_blue  = '12'
304
+pink        = '13'
305
+grey        = '14'
306
+light_grey  = '15'
307
+
308
+# Events
309
+PASS     = 'PASS'
310
+NICK     = 'NICK'
311
+USER     = 'USER'
312
+OPER     = 'OPER'
313
+MODE     = 'MODE'
314
+SERVICE  = 'SERVICE'
315
+QUIT     = 'QUIT'
316
+SQUIT    = 'SQUIT'
317
+JOIN     = 'JOIN'
318
+PART     = 'PART'
319
+TOPIC    = 'TOPIC'
320
+NAMES    = 'NAMES'
321
+LIST     = 'LIST'
322
+INVITE   = 'INVITE'
323
+KICK     = 'KICK'
324
+PRIVMSG  = 'PRIVMSG'
325
+NOTICE   = 'NOTICE'
326
+MOTD     = 'MOTD'
327
+LUSERS   = 'LUSERS'
328
+VERSION  = 'VERSION'
329
+STATS    = 'STATS'
330
+LINKS    = 'LINKS'
331
+TIME     = 'TIME'
332
+CONNECT  = 'CONNECT'
333
+TRACE    = 'TRACE'
334
+ADMIN    = 'ADMIN'
335
+INFO     = 'INFO'
336
+SERVLIST = 'SERVLIST'
337
+SQUERY   = 'SQUERY'
338
+WHO      = 'WHO'
339
+WHOIS    = 'WHOIS'
340
+WHOWAS   = 'WHOWAS'
341
+KILL     = 'KILL'
342
+PING     = 'PING'
343
+PONG     = 'PONG'
344
+ERROR    = 'ERROR'
345
+AWAY     = 'AWAY'
346
+REHASH   = 'REHASH'
347
+DIE      = 'DIE'
348
+RESTART  = 'RESTART'
349
+SUMMON   = 'SUMMON'
350
+USERS    = 'USERS'
351
+WALLOPS  = 'WALLOPS'
352
+USERHOST = 'USERHOST'
353
+ISON     = 'ISON'
354
+
355
+# Event Numerics
356
+RPL_WELCOME          = '001'
357
+RPL_YOURHOST         = '002'
358
+RPL_CREATED          = '003'
359
+RPL_MYINFO           = '004'
360
+RPL_ISUPPORT         = '005'
361
+RPL_TRACELINK        = '200'
362
+RPL_TRACECONNECTING  = '201'
363
+RPL_TRACEHANDSHAKE   = '202'
364
+RPL_TRACEUNKNOWN     = '203'
365
+RPL_TRACEOPERATOR    = '204'
366
+RPL_TRACEUSER        = '205'
367
+RPL_TRACESERVER      = '206'
368
+RPL_TRACESERVICE     = '207'
369
+RPL_TRACENEWTYPE     = '208'
370
+RPL_TRACECLASS       = '209'
371
+RPL_STATSLINKINFO    = '211'
372
+RPL_STATSCOMMANDS    = '212'
373
+RPL_STATSCLINE       = '213'
374
+RPL_STATSILINE       = '215'
375
+RPL_STATSKLINE       = '216'
376
+RPL_STATSYLINE       = '218'
377
+RPL_ENDOFSTATS       = '219'
378
+RPL_UMODEIS          = '221'
379
+RPL_SERVLIST         = '234'
380
+RPL_SERVLISTEND      = '235'
381
+RPL_STATSLLINE       = '241'
382
+RPL_STATSUPTIME      = '242'
383
+RPL_STATSOLINE       = '243'
384
+RPL_STATSHLINE       = '244'
385
+RPL_LUSERCLIENT      = '251'
386
+RPL_LUSEROP          = '252'
387
+RPL_LUSERUNKNOWN     = '253'
388
+RPL_LUSERCHANNELS    = '254'
389
+RPL_LUSERME          = '255'
390
+RPL_ADMINME          = '256'
391
+RPL_ADMINLOC1        = '257'
392
+RPL_ADMINLOC2        = '258'
393
+RPL_ADMINEMAIL       = '259'
394
+RPL_TRACELOG         = '261'
395
+RPL_TRYAGAIN         = '263'
396
+RPL_NONE             = '300'
397
+RPL_AWAY             = '301'
398
+RPL_USERHOST         = '302'
399
+RPL_ISON             = '303'
400
+RPL_UNAWAY           = '305'
401
+RPL_NOWAWAY          = '306'
402
+RPL_WHOISUSER        = '311'
403
+RPL_WHOISSERVER      = '312'
404
+RPL_WHOISOPERATOR    = '313'
405
+RPL_WHOWASUSER       = '314'
406
+RPL_ENDOFWHO         = '315'
407
+RPL_WHOISIDLE        = '317'
408
+RPL_ENDOFWHOIS       = '318'
409
+RPL_WHOISCHANNELS    = '319'
410
+RPL_LIST             = '322'
411
+RPL_LISTEND          = '323'
412
+RPL_CHANNELMODEIS    = '324'
413
+RPL_NOTOPIC          = '331'
414
+RPL_TOPIC            = '332'
415
+RPL_INVITING         = '341'
416
+RPL_INVITELIST       = '346'
417
+RPL_ENDOFINVITELIST  = '347'
418
+RPL_EXCEPTLIST       = '348'
419
+RPL_ENDOFEXCEPTLIST  = '349'
420
+RPL_VERSION          = '351'
421
+RPL_WHOREPLY         = '352'
422
+RPL_NAMREPLY         = '353'
423
+RPL_LINKS            = '364'
424
+RPL_ENDOFLINKS       = '365'
425
+RPL_ENDOFNAMES       = '366'
426
+RPL_BANLIST          = '367'
427
+RPL_ENDOFBANLIST     = '368'
428
+RPL_ENDOFWHOWAS      = '369'
429
+RPL_INFO             = '371'
430
+RPL_MOTD             = '372'
431
+RPL_ENDOFINFO        = '374'
432
+RPL_MOTDSTART        = '375'
433
+RPL_ENDOFMOTD        = '376'
434
+RPL_YOUREOPER        = '381'
435
+RPL_REHASHING        = '382'
436
+RPL_YOURESERVICE     = '383'
437
+RPL_TIME             = '391'
438
+RPL_USERSSTART       = '392'
439
+RPL_USERS            = '393'
440
+RPL_ENDOFUSERS       = '394'
441
+RPL_NOUSERS          = '395'
442
+ERR_NOSUCHNICK       = '401'
443
+ERR_NOSUCHSERVER     = '402'
444
+ERR_NOSUCHCHANNEL    = '403'
445
+ERR_CANNOTSENDTOCHAN = '404'
446
+ERR_TOOMANYCHANNELS  = '405'
447
+ERR_WASNOSUCHNICK    = '406'
448
+ERR_TOOMANYTARGETS   = '407'
449
+ERR_NOSUCHSERVICE    = '408'
450
+ERR_NOORIGIN         = '409'
451
+ERR_NORECIPIENT      = '411'
452
+ERR_NOTEXTTOSEND     = '412'
453
+ERR_NOTOPLEVEL       = '413'
454
+ERR_WILDTOPLEVEL     = '414'
455
+ERR_BADMASK          = '415'
456
+ERR_UNKNOWNCOMMAND   = '421'
457
+ERR_NOMOTD           = '422'
458
+ERR_NOADMININFO      = '423'
459
+ERR_FILEERROR        = '424'
460
+ERR_NONICKNAMEGIVEN  = '431'
461
+ERR_ERRONEUSNICKNAME = '432'
462
+ERR_NICKNAMEINUSE    = '433'
463
+ERR_NICKCOLLISION    = '436'
464
+ERR_USERNOTINCHANNEL = '441'
465
+ERR_NOTONCHANNEL     = '442'
466
+ERR_USERONCHANNEL    = '443'
467
+ERR_NOLOGIN          = '444'
468
+ERR_SUMMONDISABLED   = '445'
469
+ERR_USERSDISABLED    = '446'
470
+ERR_NOTREGISTERED    = '451'
471
+ERR_NEEDMOREPARAMS   = '461'
472
+ERR_ALREADYREGISTRED = '462'
473
+ERR_NOPERMFORHOST    = '463'
474
+ERR_PASSWDMISMATCH   = '464'
475
+ERR_YOUREBANNEDCREEP = '465'
476
+ERR_KEYSET           = '467'
477
+ERR_CHANNELISFULL    = '471'
478
+ERR_UNKNOWNMODE      = '472'
479
+ERR_INVITEONLYCHAN   = '473'
480
+ERR_BANNEDFROMCHAN   = '474'
481
+ERR_BADCHANNELKEY    = '475'
482
+ERR_BADCHANMASK      = '476'
483
+ERR_BANLISTFULL      = '478'
484
+ERR_NOPRIVILEGES     = '481'
485
+ERR_CHANOPRIVSNEEDED = '482'
486
+ERR_CANTKILLSERVER   = '483'
487
+ERR_UNIQOPRIVSNEEDED = '485'
488
+ERR_NOOPERHOST       = '491'
489
+ERR_UMODEUNKNOWNFLAG = '501'
490
+ERR_USERSDONTMATCH   = '502'
491
diff --git a/irccex/core/functions.py b/irccex/core/functions.py
492
new file mode 100644
493
index 0000000..c2a1488
494
--- /dev/null
495
+++ b/irccex/core/functions.py
496
@@ -0,0 +1,133 @@
497
+#!/usr/bin/env python
498
+# IRC Cryptocurrency Exchange (IRCCEX) - Developed by acidvegas in Python (https://acid.vegas/irccex)
499
+# functions.py
500
+
501
+import calendar
502
+import datetime
503
+import random
504
+import time
505
+
506
+import constants
507
+
508
+def check_pair(from_symbol, to_symbol):
509
+	if from_symbol == 'USD':
510
+		return True if to_symbol in ('BTC','ETH','LTC') else False
511
+	elif to_symbol == 'USD':
512
+		return True if from_symbol in ('BTC','ETH','LTC') else False
513
+	elif from_symbol == to_symbol:
514
+		return False
515
+	elif from_symbol in ('BTC','ETH') or to_symbol in ('BTC','ETH'):
516
+		return True
517
+	else:
518
+		return False
519
+
520
+def clean_float(value):
521
+	if value > 24.99:
522
+		return int(value)
523
+	elif 'e-' in str(value):
524
+		return '{0:.8f}'.format(float(value))
525
+	else:
526
+		return '{0:.8g}'.format(float(value))
527
+
528
+def clean_value(value):
529
+	value = float(value)
530
+	if value < 0.01:
531
+		return '${0:,.8f}'.format(value)
532
+	elif value < 24.99:
533
+		return '${0:,.2f}'.format(value)
534
+	else:
535
+		return '${:,}'.format(int(value))
536
+
537
+def coin_info(data):
538
+	sep      = color('|', constants.grey)
539
+	sep2     = color('/', constants.grey)
540
+	rank     = color(data['rank'], constants.pink)
541
+	name     = '{0} ({1})'.format(color(data['name'], constants.white), data['symbol'])
542
+	value    = clean_value(data['price'])
543
+	perc_1h  = color('{:,.2f}%'.format(data['percent']['1h']), percent_color(data['percent']['1h']))
544
+	perc_24h = color('{:,.2f}%'.format(data['percent']['24h']), percent_color(data['percent']['24h']))
545
+	perc_7d  = color('{:,.2f}%'.format(data['percent']['7d']), percent_color(data['percent']['7d']))
546
+	percent  = sep2.join((perc_1h,perc_24h,perc_7d))
547
+	volume   = '{0} {1}'.format(color('Volume:', constants.white), '${:,}'.format(data['volume']))
548
+	cap      = '{0} {1}'.format(color('Market Cap:', constants.white), '${:,}'.format(data['market_cap']))
549
+	return f'[{rank}] {name} {sep} {value} ({percent}) {sep} {volume} {sep} {cap}'
550
+
551
+def coin_matrix(data): # very retarded way of calculating the longest strings per-column
552
+	results = {'symbol':list(),'value':list(),'perc_1h':list(),'perc_24h':list(),'perc_7d':list(),'volume':list(),'cap':list()}
553
+	for item in data:
554
+		results['symbol'].append(item['symbol'])
555
+		results['value'].append(clean_value(item['price']))
556
+		for perc in ('1h','24h','7d'):
557
+			results['perc_' + perc].append('{:,.2f}%'.format(item['percent'][perc]))
558
+		results['volume'].append('${:,}'.format(item['volume']))
559
+		results['cap'].append('${:,}'.format(item['market_cap']))
560
+	for item in results:
561
+		results[item] = len(max(results[item], key=len))
562
+	if results['symbol'] < len('Symbol'):
563
+		results['symbol'] = len('Symbol')
564
+	if results['value'] < len('Value'):
565
+		results['value'] = len('Value')
566
+	if results['volume'] < len('Volume'):
567
+		results['volume'] = len('Volume')
568
+	if results['cap'] < len('Market Cap'):
569
+		results['cap'] = len('Market Cap')
570
+	return results
571
+
572
+def coin_table(data):
573
+	matrix = coin_matrix(data)
574
+	header = color(' {0}   {1}   {2} {3} {4}   {5}   {6} '.format('Symbol'.center(matrix['symbol']), 'Value'.center(matrix['value']), '1H'.center(matrix['perc_1h']), '24H'.center(matrix['perc_24h']), '7D'.center(matrix['perc_7d']), 'Volume'.center(matrix['volume']), 'Market Cap'.center(matrix['cap'])), constants.black, constants.light_grey)
575
+	lines  = [header,]
576
+	for item in data:
577
+		symbol   = item['symbol'].ljust(matrix['symbol'])
578
+		value    = clean_value(item['price']).rjust(matrix['value'])
579
+		perc_1h  = color('{:,.2f}%'.format(item['percent']['1h']).rjust(matrix['perc_1h']), percent_color(item['percent']['1h']))
580
+		perc_24h = color('{:,.2f}%'.format(item['percent']['24h']).rjust(matrix['perc_24h']), percent_color(item['percent']['24h']))
581
+		perc_7d  = color('{:,.2f}%'.format(item['percent']['7d']).rjust(matrix['perc_7d']), percent_color(item['percent']['7d']))
582
+		volume   = '${:,}'.format(item['volume']).rjust(matrix['volume'])
583
+		cap      = '${:,}'.format(item['market_cap']).rjust(matrix['cap'])
584
+		lines.append(' {0} | {1} | {2} {3} {4} | {5} | {6} '.format(symbol,value,perc_1h,perc_24h,perc_7d,volume,cap))
585
+	return lines
586
+
587
+def color(msg, foreground, background=None):
588
+	if background:
589
+		return f'\x03{foreground},{background}{msg}{constants.reset}'
590
+	else:
591
+		return f'\x03{foreground}{msg}{constants.reset}'
592
+
593
+def days(date_obj):
594
+	return (date_obj-datetime.date.today()).days
595
+
596
+def fee(amount, percent):
597
+	return amount-(amount*percent)
598
+
599
+def is_amount(amount, star=True):
600
+	if amount[:1] == '$':
601
+		amount = amount[1:]
602
+	if amount.isdigit():
603
+		return True if int(amount) > 0.0 else False
604
+	else:
605
+		try:
606
+			float(amount)
607
+			return True if float(amount) > 0.0 else False
608
+		except ValueError:
609
+			return True if star and amount == '*' else False
610
+
611
+def month_days():
612
+	now = datetime.datetime.now()
613
+	return calendar.monthrange(now.year, now.month)[1]
614
+
615
+def percent_color(percent):
616
+	percent = float(percent)
617
+	if percent == 0.0:
618
+		return constants.grey
619
+	elif percent < 0.0:
620
+		return constants.brown if percent > -10.0 else constants.red
621
+	else:
622
+		return constants.green if percent < 10.0 else constants.light_green
623
+
624
+def random_int(min, max):
625
+	return random.randint(min, max)
626
+
627
+def uptime(start):
628
+	uptime = datetime.datetime(1,1,1) + datetime.timedelta(seconds=time.time() - start)
629
+	return f'{uptime.day-1} Days, {uptime.hour} Hours, {uptime.minute} Minutes, {uptime.second} Seconds'
630
diff --git a/irccex/core/irc.py b/irccex/core/irc.py
631
new file mode 100644
632
index 0000000..09d5ea8
633
--- /dev/null
634
+++ b/irccex/core/irc.py
635
@@ -0,0 +1,600 @@
636
+#!/usr/bin/env python
637
+# IRC Cryptocurrency Exchange (IRCCEX) - Developed by acidvegas in Python (https://acid.vegas/irccex)
638
+# irc.py
639
+
640
+'''
641
+if using_too_many_if_statements == True:
642
+	use_more = True
643
+else:
644
+	use_alot_more = True
645
+'''
646
+
647
+import datetime
648
+import os
649
+import pickle
650
+import random
651
+import socket
652
+import threading
653
+import time
654
+
655
+import config
656
+import constants
657
+import functions
658
+from cmc import CoinMarketCap
659
+
660
+if config.connection.ssl:
661
+	import ssl
662
+
663
+def color(msg, foreground, background=None):
664
+	if background:
665
+		return f'{constants.color}{foreground},{background}{msg}{constants.reset}'
666
+	else:
667
+		return f'{constants.color}{foreground}{msg}{constants.reset}'
668
+
669
+class IRC(object):
670
+	def __init__(self):
671
+		self.db          = {'bank':dict(),'pool':0.0,'round':1,'score':dict(),'start':datetime.date.today(),'verify':dict(),'wallet':dict()}
672
+		self.last        = 0
673
+		self.last_backup = time.strftime('%I:%M')
674
+		self.maintenance = False
675
+		self.reward      = {'reward':0,'rewards':0,'status':False}
676
+		self.slow        = False
677
+		self.sock        = None
678
+		self.start       = time.time()
679
+
680
+	def run(self):
681
+		if os.path.isfile('data/db.pkl'):
682
+			with open('data/db.pkl', 'rb') as db_file:
683
+				self.db = pickle.load(db_file)
684
+			print('[+] - Restored database!')
685
+		Loops.start_loops()
686
+		self.connect()
687
+
688
+	def connect(self):
689
+		try:
690
+			self.create_socket()
691
+			self.sock.connect((config.connection.server, config.connection.port))
692
+			self.register()
693
+		except socket.error as ex:
694
+			print('[!] - Failed to connect to IRC server! (' + str(ex) + ')')
695
+			Events.disconnect()
696
+		else:
697
+			self.listen()
698
+
699
+	def create_socket(self):
700
+		family = socket.AF_INET6 if config.connection.ipv6 else socket.AF_INET
701
+		self.sock = socket.socket(family, socket.SOCK_STREAM)
702
+		if config.connection.vhost:
703
+			self.sock.bind((config.connection.vhost, 0))
704
+		if config.connection.ssl:
705
+			ctx = ssl.SSLContext()
706
+			if config.cert.file:
707
+				ctx.load_cert_chain(config.cert.file, config.cert.key, config.cert.password)
708
+			if config.connection.ssl_verify:
709
+				ctx.verify_mode = ssl.CERT_REQUIRED
710
+				ctx.load_default_certs()
711
+			else:
712
+				ctx.check_hostname = False
713
+				ctx.verify_mode = ssl.CERT_NONE
714
+			self.sock = ctx.wrap_socket(self.sock)
715
+
716
+	def listen(self):
717
+		while True:
718
+			try:
719
+				data = self.sock.recv(2048).decode('utf-8')
720
+				for line in (line for line in data.split('\r\n') if line):
721
+					print('[~] - ' + line)
722
+					if len(line.split()) >= 2:
723
+						Events.handle(line)
724
+			except (UnicodeDecodeError,UnicodeEncodeError):
725
+				pass
726
+			except Exception as ex:
727
+				print('[!] - Unexpected error occured. (' + str(ex) + ')')
728
+				break
729
+		Events.disconnect()
730
+
731
+	def register(self):
732
+		if config.login.network:
733
+			Commands.raw('PASS ' + config.login.network)
734
+		Commands.raw(f'USER {config.ident.username} 0 * :{config.ident.realname}')
735
+		Commands.nick(config.ident.nickname)
736
+
737
+class Commands:
738
+	def action(chan, msg):
739
+		Commands.sendmsg(chan, f'\x01ACTION {msg}\x01')
740
+
741
+	def check_nick(nick, chan):
742
+		if nick in Bot.db['wallet']:
743
+			return True
744
+		else:
745
+			if nick in Bot.db['verify']:
746
+				Commands.error(chan, 'Your account is not verified!', 'try again later')
747
+			else:
748
+				Commands.error(chan, 'You don\'t have an account!', 'use !register to make an account')
749
+			return False
750
+
751
+	def cleanup(nick):
752
+		for symbol in [symbol for symbol in Bot.db['wallet'][nick] if not Bot.db['wallet'][nick][symbol]]:
753
+			del Bot.db['wallet'][nick][symbol]
754
+		if not Bot.db['wallet'][nick]:
755
+			del Bot.db['wallet'][nick]
756
+
757
+	def error(target, data, reason=None):
758
+		if reason:
759
+			Commands.sendmsg(target, '[{0}] {1} {2}'.format(color('!', constants.red), data, color('({0})'.format(reason), constants.grey)))
760
+		else:
761
+			Commands.sendmsg(target, '[{0}] {1}'.format(color('!', constants.red), data))
762
+
763
+	def identify(nick, password):
764
+		Commands.sendmsg('NickServ', f'identify {nick} {password}')
765
+
766
+	def join_channel(chan, key=None):
767
+		Commands.raw(f'JOIN {chan} {key}') if key else Commands.raw('JOIN ' + chan)
768
+
769
+	def mode(target, mode):
770
+		Commands.raw(f'MODE {target} {mode}')
771
+
772
+	def nick(nick):
773
+		Commands.raw('NICK ' + nick)
774
+
775
+	def notice(target, msg):
776
+		Commands.raw(f'NOTICE {target} :{msg}')
777
+
778
+	def oper(user, password):
779
+		Commands.raw(f'OPER {user} {password}')
780
+
781
+	def raw(msg):
782
+		Bot.sock.send(bytes(msg + '\r\n', 'utf-8'))
783
+
784
+	def sendmsg(target, msg):
785
+		Commands.raw(f'PRIVMSG {target} :{msg}')
786
+		time.sleep(config.throttle.msg)
787
+
788
+class Events:
789
+	def connect():
790
+		if config.connection.modes:
791
+			Commands.mode(config.ident.nickname, '+' + config.connection.modes)
792
+		if config.login.nickserv:
793
+			Commands.identify(config.ident.nickname, config.login.nickserv)
794
+		if config.login.operator:
795
+			Commands.oper(config.ident.username, config.login.operator)
796
+		Commands.join_channel(config.connection.channel, config.connection.key)
797
+
798
+	def disconnect():
799
+		Bot.sock.close()
800
+		time.sleep(config.throttle.reconnect)
801
+		Bot.connect()
802
+
803
+	def invite(chan):
804
+		if chan == config.connection.channel:
805
+			Commands.join_channel(config.connection.channel, config.connection.key)
806
+
807
+	def kick(chan, kicked):
808
+		if kicked == config.ident.nickname and chan == config.connection.channel:
809
+			time.sleep(config.throttle.rejoin)
810
+			Commands.join_channel(chan, config.connection.key)
811
+
812
+	def message(nick, chan, msg):
813
+		if chan == config.connection.channel:
814
+			try:
815
+				if msg[:1] in '!@$':
816
+					if time.time() - Bot.last < config.throttle.cmd:
817
+						if not Bot.slow:
818
+							Bot.slow = True
819
+							Commands.sendmsg(chan, color('Slow down nerd!', constants.red))
820
+					else:
821
+						Bot.slow = False
822
+						args = msg.split()
823
+						if Bot.maintenance and args[0] in ('!cashout','!send','!trade'):
824
+							Commands.error(chan, 'Exchange is down for scheduled maintenance!', 'try again later')
825
+						else:
826
+							if args[0] == '!cashout':
827
+								if Commands.check_nick(nick, chan):
828
+									if 'USD' not in Bot.db['wallet'][nick]:
829
+										Commands.error(chan, 'Insufficent funds.', 'you have no USD in your account')
830
+									elif Bot.db['wallet'][nick]['USD'] < config.limits.cashout:
831
+										Commands.error(chan, 'Insufficent funds.', f'${config.limits.cashout:,} minimum')
832
+									else:
833
+										profit = Bot.db['wallet'][nick]['USD']-config.limits.init
834
+										amount = functions.fee(profit, config.fees.cashout)
835
+										cashout_msg = msg[9:][:100] if len(args) > 1 else Bot.db['bank'][nick][1] if nick in Bot.db['bank'] else 'IM RICH BITCH !!!'
836
+										Bot.db['bank'][nick] = (Bot.db['bank'][nick][0]+amount, cashout_msg) if nick in Bot.db['bank'] else (amount, cashout_msg)
837
+										Bot.db['pool'] += profit-amount
838
+										Bot.db['wallet'][nick]['USD'] = config.limits.init
839
+										Commands.sendmsg(chan, 'Cashed out {0} to your bank account! {1}'.format(color('${:,}'.format(int(amount)), constants.green), color('(current balance: ${:,})'.format(int(Bot.db['bank'][nick][0])), constants.grey)))
840
+							elif len(args) == 1:
841
+								if msg == '@irccex':
842
+									Commands.sendmsg(chan, constants.bold + 'IRC Cryptocurrency Exchange (IRCCEX) - Developed by acidvegas in Python - https://acid.vegas/irccex')
843
+								elif msg == '@stats':
844
+									bank_total = 0
845
+									global_data = CMC._global()
846
+									ticker_data = CMC._ticker()
847
+									wallet_total = 0
848
+									for item in Bot.db['bank']:
849
+										bank_total += Bot.db['bank'][item][0]
850
+									for user in Bot.db['wallet']:
851
+										for symbol in Bot.db['wallet'][user]:
852
+											value = Bot.db['wallet'][user][symbol] if symbol == 'USD' else ticker_data[symbol]['price']*Bot.db['wallet'][user][symbol]
853
+											wallet_total += value
854
+									Commands.sendmsg(chan, '[{0}]'.format(color('Bot', constants.cyan)))
855
+									Commands.sendmsg(chan, '  {0} {1}'.format(color('Backup :', constants.white), Bot.last_backup))
856
+									Commands.sendmsg(chan, '  {0} {1}'.format(color('Round  :', constants.white), Bot.db['round']))
857
+									Commands.sendmsg(chan, '  {0} {1}'.format(color('Uptime :', constants.white), functions.uptime(Bot.start)))
858
+									Commands.sendmsg(chan, '[{0}]'.format(color('Market', constants.cyan)))
859
+									Commands.sendmsg(chan, '  {0} {1}%'.format(color('BTC Dominance    :', constants.white), global_data['btc_dominance']))
860
+									Commands.sendmsg(chan, '  {0} {1}%'.format(color('ETH Dominance    :', constants.white), global_data['eth_dominance']))
861
+									Commands.sendmsg(chan, '  {0} {1:,}'.format(color('Cryptocurrencies :', constants.white), global_data['cryptocurrencies']))
862
+									Commands.sendmsg(chan, '  {0} {1:,}'.format(color('Exchanges        :', constants.white), global_data['exchanges']))
863
+									Commands.sendmsg(chan, '  {0} ${1:,}'.format(color('Market Cap       :', constants.white), global_data['market_cap']))
864
+									Commands.sendmsg(chan, '  {0} ${1:,}'.format(color('Volume           :', constants.white), global_data['volume']))
865
+									Commands.sendmsg(chan, '[{0}]'.format(color('Round', constants.cyan)))
866
+									Commands.sendmsg(chan, '  {0} {1} {2}'.format(color('Accounts    :', constants.white), '{:,}'.format(len(Bot.db['wallet'])), color('(${:,})'.format(int(wallet_total)), constants.grey)))
867
+									Commands.sendmsg(chan, '  {0} {1} {2}'.format(color('Bank        :', constants.white), '{:,}'.format(len(Bot.db['bank'])), color('(${:,})'.format(int(bank_total)), constants.grey)))
868
+									Commands.sendmsg(chan, '  {0} ${1:,}'.format(color('Reward Pool :', constants.white), int(Bot.db['pool'])))
869
+									Commands.sendmsg(chan, '  {0} {1:,}'.format(color('Unverified  :', constants.white), len(Bot.db['verify'])))
870
+								elif msg.startswith('$'):
871
+									msg = msg.upper()
872
+									if ',' in msg:
873
+										seen  = set()
874
+										coins = [x for x in list(msg[1:].split(','))[:10] if not (x in seen or seen.add(x))]
875
+										data  = [CMC._ticker()[coin] for coin in coins if coin in CMC._ticker()]
876
+										if data:
877
+											if len(data) == 1:
878
+												Commands.sendmsg(chan, functions.coin_info(data[0]))
879
+											else:
880
+												for line in functions.coin_table(data):
881
+													Commands.sendmsg(chan, line)
882
+										else:
883
+											Commands.error(chan, 'Invalid cryptocurrency names!')
884
+									else:
885
+										coin = msg[1:]
886
+										if not coin.split('.')[0].isdigit():
887
+											if coin in CMC._ticker():
888
+												Commands.sendmsg(chan, functions.coin_info(CMC._ticker()[coin]))
889
+											else:
890
+												Commands.error(chan, 'Invalid cryptocurrency name!')
891
+								elif msg == '!bang' and Bot.reward['status']:
892
+									if Commands.check_nick(nick, chan):
893
+										amount = functions.fee(Bot.reward['reward'], float('0.{0:02}'.format(functions.random_int(5,15)))) if Bot.reward['rewards'] else Bot.db['pool']
894
+										Bot.db['wallet'][nick]['USD'] = Bot.db['wallet'][nick]['USD']+amount if 'USD' in Bot.db['wallet'][nick] else amount
895
+										Bot.db['pool'] -= amount
896
+										if Bot.db['pool']:
897
+											Commands.sendmsg(chan, 'You won {0}!'.format(color('${:,}'.format(amount), constants.green)))
898
+											Bot.reward['rewards'] -= 1
899
+										else:
900
+											Commands.sendmsg(chan, 'You won the big {0}!'.format(color('${:,}'.format(amount), constants.green)))
901
+											Bot.reward = {'reward':0,'rewards':0,'status':False}
902
+								elif msg == '!bank':
903
+									if nick in Bot.db['bank']:
904
+										clean_bank = dict()
905
+										for item in Bot.db['bank']:
906
+											clean_bank[item] = Bot.db['bank'][item][0]
907
+										richest = sorted(clean_bank, key=clean_bank.get, reverse=True)
908
+										Commands.sendmsg(chan, '[{0}] {1} {2} {3}'.format(color('{:02}'.format(richest.index(nick)+1), constants.pink), nick, color('${:,}'.format(int(Bot.db['bank'][nick][0])), constants.green), Bot.db['bank'][nick][1]))
909
+									else:
910
+										Commands.error(chan, 'You don\'t have any money in the bank!', 'use !cashout to put money in the bank')
911
+								elif msg == '!portfolio':
912
+									if Commands.check_nick(nick, chan):
913
+										total = 0
914
+										for symbol in Bot.db['wallet'][nick]:
915
+											value = Bot.db['wallet'][nick][symbol] if symbol == 'USD' else CMC._ticker()[symbol]['price']*Bot.db['wallet'][nick][symbol]
916
+											total += value
917
+										Commands.sendmsg(chan, color('${:,}'.format(int(total)), constants.green))
918
+								elif msg == '!register':
919
+									if nick not in Bot.db['verify'] and nick not in Bot.db['wallet']:
920
+										Bot.db['verify'][nick] = time.time()
921
+										Commands.sendmsg(chan, 'Welcome to the IRC Cryptocurrency Exchange! ' + color('(please wait 24 hours while we verify your documents)', constants.grey))
922
+									else:
923
+										Commands.error(chan, 'Failed to register an account!', 'you already have an account')
924
+								elif msg == '!rich':
925
+									if Bot.db['bank']:
926
+										clean_bank = dict()
927
+										for item in Bot.db['bank']:
928
+											clean_bank[item] = Bot.db['bank'][item][0]
929
+										richest = sorted(clean_bank, key=clean_bank.get, reverse=True)[:10]
930
+										for user in richest:
931
+											_user = f'{user[:1]}{constants.reset}{user[1:]}'
932
+											Commands.sendmsg(chan, '[{0}] {1} {2} {3}'.format(color('{:02}'.format(richest.index(user)+1), constants.pink), _user.ljust(15), color('${:,}'.format(int(Bot.db['bank'][user][0])).ljust(13), constants.green), Bot.db['bank'][user][1]))
933
+										Commands.sendmsg(chan, '^ this could be u but u playin...')
934
+									else:
935
+										Commands.error(chan, 'Yall broke...')
936
+								elif msg == '!score':
937
+									if nick in Bot.db['score']:
938
+										clean_bank = dict()
939
+										for item in Bot.db['score']:
940
+											clean_bank[item] = Bot.db['score'][item][0]
941
+										top = sorted(clean_bank, key=clean_bank.get, reverse=True)
942
+										Commands.sendmsg(chan, '[{0}] {1} {2} {3}'.format(color('{:02}'.format(top.index(nick)+1), constants.pink), nick, color(str(Bot.db['score'][nick][0]), constants.cyan), color('${:,}'.format(int(Bot.db['score'][nick][1])), constants.green)))
943
+									else:
944
+										Commands.error(chan, 'You don\'t have any points!', 'be in the top 10 at the end of the month when the current round ends to get points')
945
+								elif msg == '!scores':
946
+									if Bot.db['score']:
947
+										clean_score = dict()
948
+										for item in Bot.db['score']:
949
+											clean_score[item] = Bot.db['score'][item][0]
950
+										top = sorted(clean_score, key=clean_score.get, reverse=True)[:10]
951
+										for user in top:
952
+											Commands.sendmsg(chan, '[{0}] {1} {2} {3}'.format(color('{:02}'.format(top.index(user)+1), constants.pink), user.ljust(15), color(str(Bot.db['score'][user][0]).ljust(5), constants.cyan), color('${:,}'.format(int(Bot.db['score'][nick][1])), constants.green)))
953
+										Commands.sendmsg(chan, '^ this could be u but u playin...')
954
+									else:
955
+										Commands.error(chan, 'Yall broke...')
956
+								elif msg == '!top':
957
+									data = list(CMC._ticker().values())[:10]
958
+									for line in functions.coin_table(data):
959
+										Commands.sendmsg(chan, line)
960
+								elif msg == '!wallet':
961
+									if Commands.check_nick(nick, chan):
962
+										Commands.sendmsg(chan, color('  Symbol          Amount                  Value        ', constants.black, constants.light_grey))
963
+										total = 0
964
+										for symbol in Bot.db['wallet'][nick]:
965
+											amount = Bot.db['wallet'][nick][symbol]
966
+											value = amount if symbol == 'USD' else CMC._ticker()[symbol]['price']*amount
967
+											Commands.sendmsg(chan, f' {symbol.ljust(8)} | {str(functions.clean_float(amount)).rjust(20)} | {str(functions.clean_value(value)).rjust(20)}')
968
+											total += float(value)
969
+										Commands.sendmsg(chan, color(f'                      Total: {str(functions.clean_value(total)).rjust(27)}', constants.black, constants.light_grey))
970
+							elif len(args) == 2:
971
+								if args[0] in ('!bottom','!top'):
972
+									option  = args[1].lower()
973
+									if option not in ('1h','24h','7d','value','volume'):
974
+										Commands.error(chan, 'Invalid option!', 'valid options are 1h, 24h, 7d, value & volume')
975
+									else:
976
+										data = dict()
977
+										for item in CMC._ticker():
978
+											data[item] = float(CMC._ticker()[item]['percent'][option])
979
+										data = sorted(data, key=data.get, reverse=True)
980
+										data = data[-10:] if args[0] == '!bottom' else data[:10]
981
+										data = [CMC._ticker()[coin] for coin in data]
982
+										for line in functions.coin_table(data):
983
+											Commands.sendmsg(chan, line)
984
+							elif len(args) == 3:
985
+								if args[0] == '!trade':
986
+									if Commands.check_nick(nick, chan):
987
+										pair = args[1].upper()
988
+										if len(pair.split('/')) == 2:
989
+											from_symbol, to_symbol = pair.split('/')
990
+											if from_symbol in Bot.db['wallet'][nick]:
991
+												amount = args[2].replace(',','')
992
+												if functions.is_amount(amount):
993
+													if amount == '*':
994
+														amount = Bot.db['wallet'][nick][from_symbol]
995
+													elif amount.startswith('$'):
996
+														amount = float(amount[1:]) if from_symbol == 'USD' else float(amount[1:])/CMC._ticker()[from_symbol]['price']
997
+													else:
998
+														amount = float(amount)
999
+													usd_value = amount if from_symbol == 'USD' else CMC._ticker()[from_symbol]['price']*amount