<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Igor Che</title><generator>teletype.in</generator><description><![CDATA[Igor Che]]></description><image><url>https://img1.teletype.in/files/cf/a1/cfa15259-682b-4c33-b8f7-7c753dae235d.png</url><title>Igor Che</title><link>https://teletype.in/@chelovek86</link></image><link>https://teletype.in/@chelovek86?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/chelovek86?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/chelovek86?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Mon, 06 Apr 2026 21:32:29 GMT</pubDate><lastBuildDate>Mon, 06 Apr 2026 21:32:29 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@chelovek86/emalation_orderbook</guid><link>https://teletype.in/@chelovek86/emalation_orderbook?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/emalation_orderbook?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>ЭМУЛЯТОР БИРЖЕВОГО СТАКАНА ЦЕН</title><pubDate>Thu, 06 Oct 2022 12:27:43 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/7a/a0/7aa0e7d1-11eb-47f1-ba68-b1ae58b83528.png"></media:content><description><![CDATA[<img src="https://img1.teletype.in/files/89/4f/894f9bff-6a41-4862-afa0-a4f64e2402dd.png"></img>В прошлой статье мы создали инструмент для выгрузки и обработки данных стакана. И закрыли вопрос касаемо, где брать эти данные. Теперь, когда данные выгружены в виде своего рода датасетов, мы можем написать эмулятор, который будет транслировать эти данные.]]></description><content:encoded><![CDATA[
  <figure id="YQNZ" class="m_original">
    <img src="https://img1.teletype.in/files/89/4f/894f9bff-6a41-4862-afa0-a4f64e2402dd.png" width="737" />
  </figure>
  <p id="9zNu">В прошлой статье мы создали инструмент для выгрузки и обработки данных стакана. И закрыли вопрос касаемо, где брать эти данные. Теперь, когда данные выгружены в виде своего рода датасетов, мы можем написать эмулятор, который будет транслировать эти данные.</p>
  <p id="U3lz">Существует разное множество баз данных для работы с временными рядами, к примеру InfluxDB и ей подобные. Я же, решил, что мне будет удобней работать с ElasticDB.</p>
  <p id="PP6P">Если посмотреть схематично на реализацию, то она будет состоять из следующих компонентов:<br />— Конфиг для docker-compose для деплоя Elasticsearch и Kibana (второе необязательно)<br />— Скрипт загрузки данных из файлов в базу<br />— WebSocketServer который собственно и будет транслировать наши данные из бд<br />— WebSockerClient для удобства и мониторинга</p>
  <p id="36GA">Первым делом задеплоим докер контейнер с Elasticsearch. В него мы загрузим все данные из файлов по иструментам, которые подготавливали в <a href="https://blog.chevich.com/python/sbor-dannyh-birzhevogo-stakana/" target="_blank">прошлой статье</a>.</p>
  <pre id="z1kV">docker-compose.ymlversion: &#x27;3.7&#x27;

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.4.0
    container_name: elasticsearch
    environment:
      - xpack.security.enabled=false
      - discovery.type=single-node
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    cap_add:
      - IPC_LOCK
    volumes:
      - elasticsearch-data:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
      - 9300:9300

  kibana:
    container_name: kibana
    image: docker.elastic.co/kibana/kibana:7.4.0
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - 5601:5601
    depends_on:
      - elasticsearch

volumes:
  elasticsearch-data:
    driver: local</pre>
  <p id="0o8v">После того как контейнер соберется и запустится, можно загрузить в бд наши подготовленные данные, для этого создайте python скрипт со следующим содержанием.</p>
  <pre id="facI">run_elastic.pyimport datetime
import os
import sys
from pprint import pprint as pp
from memory_profiler import profile
from elasticsearch import Elasticsearch
from elasticsearch.helpers import parallel_bulk, bulk
from dotenv import load_dotenv
load_dotenv()

class Parser:
    es = Elasticsearch(os.environ[&quot;DB&quot;])
    symbol = None
    iterator = 1
    bulk_data = list()

    def __init__(self, symbol):
        self.symbol = symbol

    def add(self, data: list, send: int = 0):
        self.bulk_data.append({
                &quot;_op_type&quot;: &quot;index&quot;,
                &quot;_index&quot;: self.symbol,
                &quot;_source&quot;: data,
            })
        if len(self.bulk_data) == 1000 or send == 1:
            bulk(self.es, self.bulk_data)
            self.bulk_data = list()
        self.iterator+=1

    def prepare(self, fl: str):
        with open(fl, &quot;r&quot;) as f:
            i = 0; _data = dict({&quot;ts&quot;:0,&quot;a&quot;:[],&quot;b&quot;:[],&quot;delay&quot;:0})
            for n in f:
                if i==0: i+=1; continue
                if (i%1000000)==0: print(i)
                nd = n.split(&quot;,&quot;)
                if int(nd[0]) == _data[&quot;ts&quot;]:
                    if nd[1] == &#x27;a&#x27;:
                        _data[&#x27;a&#x27;].append( [float(nd[2]), float(nd[3])] )
                    elif nd[1] == &#x27;b&#x27;:
                        _data[&#x27;b&#x27;].append( [float(nd[2]), float(nd[3])] )
                elif not _data[&quot;ts&quot;]:
                    if nd[1] == &#x27;a&#x27;:
                        _data[&#x27;a&#x27;].append( [float(nd[2]), float(nd[3])] )
                    elif nd[1] == &#x27;b&#x27;:
                        _data[&#x27;b&#x27;].append( [float(nd[2]), float(nd[3])] )
                    _data[&quot;ts&quot;] = int(nd[0])
                    _data[&#x27;delay&#x27;] = float(nd[4].replace(&quot;\n&quot;, &quot;&quot;))
                else:
                    self.add(_data)
                    _data = dict({&quot;ts&quot;:0,&quot;a&quot;:[],&quot;b&quot;:[],&quot;delay&quot;:0})
                i+=1
            if _data: self.add(_data,1); del _data

name = os.environ[&quot;SYMBOL&quot;]
p = Parser(name)

directory = os.environ[&quot;PATH_TO_FILES&quot;]
for n in  os.listdir(directory):
    nd = os.path.join(directory,n)
    if os.path.isdir(nd) and name.upper() in n:
        for f in os.listdir(nd):
            print(nd + &quot;/&quot;+ f)
            s = datetime.datetime.now()
            p.prepare(nd + &quot;/&quot;+ f)
            delay = datetime.datetime.now() - s
            print(&quot;Time execute: &quot; + str(delay))
            print(&quot;Rows in DB: &quot; + str(p.iterator))</pre>
  <p id="0Zey">И сразу же файл конфига</p>
  <pre id="8mZs">.envDB=&quot;http://localhost:9200&quot;
PATH_TO_FILES=&quot;Полный путь до директории с файлами&quot;
SYMBOL=&quot;Наименование инструмента, к примеру atomusdt&quot;
WS_URL=&quot;ws://localhost:5678&quot;
WS_HOST=&quot;127.0.0.1&quot;
WS_PORT=&quot;5678&quot;
EMU_SYMBOL=&quot;atomusdt&quot;
EMU_FROM=&quot;Дата и время с которого будет трансляция к примеру 01.01.2022 10:29:20.221&quot;
</pre>
  <p id="H7EW">Теперь это можно запустить, и загрузить все файлы в бд. Это может занят достаточно продолжительное время, поэтому наберитесь терпения.</p>
  <p id="T0WK">Следующим шагом будет реализация вебсокет сервера, это сам механизм отвечающий за трансляцию данных.</p>
  <pre id="jgHM">ws_server.pyfrom elasticsearch import Elasticsearch
from datetime import datetime, timezone
from pprint import pprint as pp
import asyncio
import random
import websockets
import json
import sys
import threading
from dotenv import load_dotenv
load_dotenv()
es = Elasticsearch()


class Emulator:
    data = None
    ts_from = None
    index = None
    delta = 60

    def __init__(self,symbol, dt):
        self.ts_from = int(datetime.strptime(dt, &quot;%d.%m.%Y %H:%M:%S.%f&quot;).replace(tzinfo=timezone.utc).timestamp() * 1000)
        self.index = symbol

        self._getData()

    def _ts_to(self, ts_from):
        return ts_from + self.delta * 1000

    def _getData(self):
        q = {
            &quot;query&quot;: {
                &quot;range&quot;: {
                    &quot;ts&quot;: {
                        &quot;gte&quot;: self.ts_from,
                        &quot;lt&quot;: self._ts_to(self.ts_from)
                    }
                }
            },
            &quot;size&quot;: 1000,
        }
        resp = es.search(index=self.index, body=q)

        if resp[&#x27;hits&#x27;][&#x27;total&#x27;][&#x27;value&#x27;] &gt; 0:
            self.data = [hit[&#x27;_source&#x27;] for hit in resp[&#x27;hits&#x27;][&#x27;hits&#x27;]]
        print(&quot;Got %d Hits&quot; % resp[&#x27;hits&#x27;][&#x27;total&#x27;][&#x27;value&#x27;])
        print(&quot;In self.data: %d&quot;% len(self.data))
    
    def _append(self, ts_from):
        q = {
            &quot;query&quot;: {
                &quot;range&quot;: {
                    &quot;ts&quot;: {
                        &quot;gte&quot;: ts_from,
                        &quot;lt&quot;: self._ts_to(ts_from)
                    }
                }
            },
            &quot;size&quot;: 1000,
        }
        resp = es.search(index=self.index, body=q)

        if resp[&#x27;hits&#x27;][&#x27;total&#x27;][&#x27;value&#x27;] &gt; 0:
            self.data.extend([hit[&#x27;_source&#x27;] for hit in resp[&#x27;hits&#x27;][&#x27;hits&#x27;]])
        print(&quot;Got %d Hits&quot; % resp[&#x27;hits&#x27;][&#x27;total&#x27;][&#x27;value&#x27;])
        print(&quot;In self.data after append: %d&quot;% len(self.data))

    async def translation(self, websocket, path):
        while True:
            if self.data:
                current = self.data.pop(0)
                sys.stdout.write(&quot;\rCurrent length: %d &quot;% len(self.data))    
                sys.stdout.flush()
                await websocket.send(json.dumps(current))
                if len(self.data) &lt; 500:
                    threading.Thread(target=self._append, args=[self.data[len(self.data)-1][&quot;ts&quot;]]).start()

            await asyncio.sleep(int(current[&#x27;delay&#x27;])/1000)
    
    def run(self):
        start_server = websockets.serve(self.translation, &#x27;127.0.0.1&#x27;, 5678)
        asyncio.get_event_loop().run_until_complete(start_server)
        asyncio.get_event_loop().run_forever()


em = Emulator(&quot;atomusdt&quot;,&quot;01.01.2022 10:29:20.221&quot;)
em.run()</pre>
  <p id="50wF">Для начала работы его достаточно запустить, особых настроек у него нет. Стоит упомянуть, что как таковых данных в бд большое множество и было бы не целесообразно выгружать все, поэтому в данном коде предусмотрена динамическая подгрузка данных при достижении определенного лимита, а так же реализована задержка, чтобы максимально приблизиться к боевым условиям.</p>
  <p id="YrGT">После запуска websocket сервера, при подключении к нему клиента, он начнет транслировать аск и бид цены, для подключения можно использоваться сторонние программы для работы с сокетами, либо проверить через плагины для хром браузера.</p>
  <p id="BKvG">Но для полноты картины напишем клиент для сервера.</p>
  <pre id="ZIdq">ws_client.pyimport asyncio
import websockets
from pprint import pprint as pp
import json
import os
import sys
from collections import OrderedDict
from dotenv import load_dotenv
load_dotenv()

class WSClient:
    asks = dict()
    bids = dict()
    best_bid = None
    best_ask = None

    async def connect(self):
        uri = os.environ[&#x27;WS_URL&#x27;]
        async with websockets.connect(uri) as websocket:
            while 1:
                obj = await websocket.recv()
                obj = json.loads(obj)
                
                best_bid = .0
                for n in obj[&#x27;b&#x27;]: 
                    if best_bid &lt; n[0] and n[1] &gt; .0:
                        best_bid = n[0]
                self.best_bid = best_bid

                best_ask = 999999999
                for n in obj[&#x27;a&#x27;]: 
                    if best_ask &gt; n[0] and n[1] &gt; .0:
                        best_ask = n[0]
                self.best_ask = best_ask

                self._pp()

    def _pp(self):
        sys.stdout.write(&quot;\rASK: %f BID: %f SPREAD: %f\t\t\t&quot;% (self.best_ask,self.best_bid, (self.best_ask-self.best_bid)))
        sys.stdout.flush()

try:
    ws = WSClient()
    asyncio.run(ws.connect())
except KeyboardInterrupt:
    print(&quot;\n Stop&quot;)</pre>
  <p id="AGvM">Теперь запустив клиент и сервер, можно посмотреть как работает наша сборка</p>
  <figure id="b9kh" class="m_original">
    <video src="https://blog.chevich.com/wp-content/uploads/2022/10/consoleoutput_3.mp4" width="774"></video>
    <figcaption>Отображение сервер и клиента из консоли</figcaption>
  </figure>
  <p id="sXSX">Или проверив работу запустив клиента в виде плагина chrome</p>
  <figure id="aXBJ" class="m_original">
    <video src="https://blog.chevich.com/wp-content/uploads/2022/10/screenrecorderproject2.mp4" width="736"></video>
    <figcaption>Отображение из плагина Simple Web Socket Plugin</figcaption>
  </figure>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/load_depth_binance</guid><link>https://teletype.in/@chelovek86/load_depth_binance?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/load_depth_binance?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>Сбор данных биржевого стакана на python</title><pubDate>Mon, 29 Aug 2022 10:50:53 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/e8/0f/e80f5921-ca91-45a7-8ffe-3e84e027469d.png"></media:content><description><![CDATA[<img src="https://blog.chevich.com/wp-content/uploads/2022/08/depthbinancearchive_before.png"></img>Давно хотелось обучить нейросеть для работы в биржевом стакане цен, но как-то все откладывалось и переносилось. Вообще, данный кейс открывает ряд вопросов, таких как:]]></description><content:encoded><![CDATA[
  <p id="zAbd">Давно хотелось обучить нейросеть для работы в биржевом стакане цен, но как-то все откладывалось и переносилось. Вообще, данный кейс открывает ряд вопросов, таких как:</p>
  <ul id="35xC">
    <li id="Wuiz">Как хранить данные ?</li>
    <li id="cjrg">Где взять такие наборы данных ?</li>
    <li id="9dpo">Как эмулировать не только данные, но и интервалы между ними ?</li>
  </ul>
  <p id="3R7e">Я произвел поверхностный ресёрч по данному вопросу и понял, что бесплатно, в нужном объеме и нужные данные я не получу.</p>
  <p id="fgqK">Вариант собирать в реал-тайм показался мне очень долгим и не гарантированным, ведь если будет даун-тайм, то ничего писаться не будет и это плохо отразится на итоговых данных. Есть еще вариант выгрузить с биржи и обработать так как мне нужно. Выбор очевиден.</p>
  <p id="48h5">Полный процесс от сбора данных до обучения нейросети достаточно длинный, поэтому правильнее будет разбить на части. В этой части будет реализация выгрузки и подготовка данных.</p>
  <p id="SKwX">Для выгрузки я выбрал биржу Binance, но думаю она не единственная, кто предоставляет подобные данные.</p>
  <p id="OhUz">Первым делом необходимо подать заявку <a href="https://www.binance.com/en/landing/data" target="_blank">тут</a>. Нас интересует “Futures Order Book Data”. Форма простая и не должна вызывать сложности. Как только заявку одобрят на почту упадет письмо.</p>
  <p id="LfDq">В <a href="https://github.com/binance/binance-public-data/tree/master/Futures_Order_Book_Download" target="_blank">официальном репозитории</a>, есть пример кода на python, где показывается как можно выгрузить нужный инструмент за дельту время.</p>
  <p id="0qZv">На этом можно было бы закончить, если бы не нюансы.</p>
  <p id="zYKg">Выгрузка может формироваться несколько часов, то есть синхронно выгрузить ее не получится.</p>
  <p id="UAAt">Нужно сделать запрос на выгрузку, указав инструмент и дельту по времени. В ответ будет присвоен идентификатор заказа, а дальше по этому идентификатору мы получаем ссылку в случае сформированного файла, либо сообщение с просьбой повторить запрос позднее.</p>
  <p id="KtRs">Так же процесс выгрузки усложняет тот факт, что мы не можем указать диапазон больше 2 месяцев. А это означает, что большие по времени выгрузки придется разбивать на более мелкие.</p>
  <p id="krof">Еще мне не понравилось, что в выгрузках есть ненужные мне колонки и дублирующие значения, не то, чтобы они сильно портили картинку, но за счет этого размер сильно увеличивался. К примеру, распакованная выгрузка одного дня вполне может достигать 10Gb, так что - да, место тут критично.</p>
  <figure id="zXr2" class="m_original">
    <img src="https://blog.chevich.com/wp-content/uploads/2022/08/depthbinancearchive_before.png" width="820" />
    <figcaption>Выгрузка за один месяц до оптимизации</figcaption>
  </figure>
  <p id="BsLE">В связи с этим, хотелось немного автоматизировать процесс выгрузки и обработки данных, чтобы упростить себе жизнь и немного сократить потребляемое место.</p>
  <p id="Mw0F">За основу выгрузки я взял скрипт с официального репозитория, добавив туда статусную модель и конфигурации для удобства.</p>
  <p id="lBUv">Так же добавлен класс для обработки загруженного файла. Если не вдаваться в детали, то мы распаковываем основной архив и все вложенные архивы, открываем поочередно файлы со стаканами и удаляем из него ненужные колонки. Под ненужными колонками я имею в виду данные с идентификаторами обновлений, по ним можно выстроить цепочку обновлений, а также проверить пропуски. Но в аннотации у них написано, что фьючерсы в паре с USDT априори без пропусков, так что перепроверять биржу я не хочу и поверю на слово.</p>
  <p id="0rHd">Еще мы рассчитываем задержку до следующего обновления стакана. Если вкратце, то за одну секунду может произойти несколько обновлений объема по позициям в стакане, и если дельту времени рассчитывать в момент стриминга объема, то можно просто не успевать рассчитывать, поэтому, чтобы минимизировать вычисления и нагрузку в момент трансляции, все расчеты по задержке мы рассчитаем в момент первичной обработки данных. И последним штрихом будет очистка данных от повторяющихся значений, так как они абсолютно бесполезные и занимают в совокупности очень много места.</p>
  <p id="yhAL">Весь цикл, я не беру в расчет время ожидания формирования файла на стороне сервера, по загрузке и обработке архива на примере месячной выгрузке по инструменту ATOMUSDT может занять пару часов. Но по итогу мы получим не плохие данные для эмулятора стакана, при этом сильно сэкономив место</p>
  <figure id="bKc6" class="m_original">
    <img src="https://blog.chevich.com/wp-content/uploads/2022/08/depthbinancearchive_after.png" width="810" />
    <figcaption>Выгрузка за один месяц после оптимизации</figcaption>
  </figure>
  <p id="EERT">Исходники можно взять из <a href="https://github.com/shmeller86/binance_load_depth_ticker" target="_blank">github</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/discord_info_bot</guid><link>https://teletype.in/@chelovek86/discord_info_bot?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/discord_info_bot?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>DISCORD.PY ИНФО БОТ</title><pubDate>Wed, 13 Jul 2022 12:25:49 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/0a/87/0a876aa3-c78b-4bd7-996f-8584f02fc8ef.png"></media:content><category>PYTHON</category><description><![CDATA[<img src="https://blog.chevich.com/wp-content/uploads/2022/07/pydis.jpeg"></img>Мне нужно в дискорд вывести некоторую информацию в виде среза статистики. Как вариант создать несколько каналов, залочить их и средствами бота, обновлять имя канала с какой-то периодикой новыми метриками. Обновлять я буду инфу по количеству нод в проекте noder.one. Сам проект на джанго, но связку именно в среде джанго делать наверное нет смысла, поэтому опишу здесь обособленный вариант.]]></description><content:encoded><![CDATA[
  <figure id="rxQd" class="m_column">
    <img src="https://blog.chevich.com/wp-content/uploads/2022/07/pydis.jpeg" width="1280" />
  </figure>
  <p id="YU2Y">Мне нужно в дискорд вывести некоторую информацию в виде среза статистики. Как вариант создать несколько каналов, залочить их и средствами бота, обновлять имя канала с какой-то периодикой новыми метриками. Обновлять я буду инфу по количеству нод в проекте <a href="https://noder.one/" target="_blank">noder.one</a>. Сам проект на джанго, но связку именно в среде джанго делать наверное нет смысла, поэтому опишу здесь обособленный вариант.</p>
  <p id="Ls4i"><strong>Регистрация приложения и бота</strong></p>
  <p id="Emo9">Первым делом идем <a href="https://discord.com/developers/applications" target="_blank">https://discord.com/developers</a> и создаем приложение.</p>
  <figure id="w6MZ" class="m_column">
    <img src="https://blog.chevich.com/wp-content/uploads/2022/07/screenshot-2022-07-13-at-19.45.20.png" width="910" />
  </figure>
  <p id="wu5o">Далее, проваливаемся в наше приложение, и слева, в разделе Bot, добавляем бота.</p>
  <figure id="4dtC" class="m_column">
    <img src="https://blog.chevich.com/wp-content/uploads/2022/07/screenshot-2022-07-13-at-19.54.24-1024x238.png" width="1024" />
  </figure>
  <p id="F8oe">Сохраняем токен к себе, через него мы будем взаимодействовать с каналом. Если токен не появился при первичном добавлении, то жмем на reset token.</p>
  <p id="rW95">Теперь нам осталось авторизовать нашего бота у себя на канале, для этого заходим в раздел OAuth2 в левом меню и в подменю выбираем URL Generator. В окне, в разделе Scopes выбираем чекбокс bot, в разделе Bot permission выбираем Administrator. Если ваш бот будет выполнять строго определенные функции и вы не собираетесь расширять функционал, то я настоятельно рекомендую выбрать точечно для него права. После проделанной операции, в поле Generated url будет ссылка по которой необходимо пройти и авторизовать вашего бота на канале.</p>
  <p id="Z7KK"><strong>Пишем функционал</strong></p>
  <p id="zbap">Создаем проект, окружение и устанавливаем небходимые пакеты (эта часть может отличаться вашей)</p>
  <pre id="wRxX" data-lang="bash">mkdir ./discord_bot
cd discord_bot
python -m venv .venv
source .venv/bin/activate
pip install discord.py</pre>
  <p id="vHgU">Создаем наш файл с ботом, и записываем следующий код:</p>
  <pre id="6JDI" data-lang="python">import discord
import asyncio

discord_channel_id = 0000000000000
discord_bot_token = &quot;TOKEN&quot;
discord_timeout = 60

class Client(discord.Client):
    def __init__(self, *, loop=None, **options):
        super().__init__(loop=loop, **options)
        self.loop.create_task(self.update_count_user_task())

    async def on_ready(self):
        print(&#x27;Loged in as: {0}&#x27;.format(self.user))
        

    async def update_count_user_task(self):
        await self.wait_until_ready()
        while not self.is_closed():
            info_channel = self.get_channel(discord_channel_id)
            await info_channel.edit(name=&quot;new_name_channel&quot;)
            await asyncio.sleep(discord_timeout)

client = Client()
client.run(discord_bot_token)

</pre>
  <p id="gV8m">discord_channel_id = идентификатор канала который будем обновлять<br />discord_bot_token = токен бота, сохраненный ранее<br />discord_timeout = сколько секунд курим между обновлениями</p>
  <p id="pWJu">Перед тем как запускать, необходимо создать канал на своем сервере и получить его идентификатор (ID можно получить нажав правой кнопкой мыши на канале с включенным режимом разработчика в настройках). Данный пример демонстрирует, как легко можно собрать простенького бота для иформирования пользователей на своем сервере дискорда. <a href="https://github.com/shmeller86/discord_channel_name_updater" target="_blank">github</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/merkletree</guid><link>https://teletype.in/@chelovek86/merkletree?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/merkletree?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>Деревья Меркла</title><pubDate>Tue, 12 Jul 2022 08:45:25 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/eb/28/eb2892e5-c2c0-47ad-9cc6-0cefa752f655.png"></media:content><category>PYTHON</category><description><![CDATA[<img src="https://4trading.app/wp-content/uploads/2022/06/merkle1.png"></img>Деревья Меркла представляют собой древовидную структуру, в которой каждый узел дерева представлен значением, являющимся результатом криптографической хеш-функции. Такие деревья имеют 3 типа узлов:
1. Листовые узлы (листья) - данные узлы находятся в самом низу и их значения это результат хеширования исходных данных. Количество листовых узлов ровняется количеству значений исходных данных.
2. Родительские узлы (ветви) - результат выполнения хеш-функции над двумя узлами ниже, это может быть как листовые узлы, так и родительские, в зависимости от размера дерева.
3. Корневой узел (корень) - находится в самом верху дерева и получается из хеша конкатенированных хешей двух родительских узлов, которые находятся под ним.]]></description><content:encoded><![CDATA[
  <p id="BNzO">Деревья Меркла представляют собой древовидную структуру, в которой каждый узел дерева представлен значением, являющимся результатом криптографической хеш-функции. Такие деревья имеют 3 типа узлов:<br />1. Листовые узлы (листья) - данные узлы находятся в самом низу и их значения это результат хеширования исходных данных. Количество листовых узлов ровняется количеству значений исходных данных.<br />2. Родительские узлы (ветви) - результат выполнения хеш-функции над двумя узлами ниже, это может быть как листовые узлы, так и родительские, в зависимости от размера дерева.<br />3. Корневой узел (корень) - находится в самом верху дерева и получается из хеша конкатенированных хешей двух родительских узлов, которые находятся под ним.</p>
  <p id="4OOG"><strong>Применение</strong></p>
  <p id="uyVf">Деревья Меркла используются в разных сферах, если говорить про блокчейн, то яркий пример это сеть Bitcoin, в ней все хешируется в SHA-256. И применяется для проверки упрощенной верификации платежей, ведь для проверки не нужно вычислять все хеши, достаточно получить хеш корневого узла включающего все узлы до нужной транзакции. Такой подход значительно увеличивает производительность и пропускную способность сети.</p>
  <figure id="RT4Q" class="m_original">
    <img src="https://4trading.app/wp-content/uploads/2022/06/merkle1.png" width="657" />
    <figcaption>Для проверки hD необходимы только хеши красного цвета</figcaption>
  </figure>
  <p id="x4Pl"><strong>Как использовать в Solidity ?</strong></p>
  <p id="eMEV">Если наш смарт-контракт подразумевает хранение большого кол-ва данных, которые необходимо будет валидировать, то использовать деревья Меркла не плохая мысль. К примеру в нашем проекте предусмотрен вайтлист, хранить тысячи адресов в смарт-контракте не самая оптимальная идея. Мы могли бы хранить корневой хеш всего в контракте, в контракт передавать только адрес и необходимые родительские узлы для вычислений, смарт-контракт сам будет высчитывать рут из переданных данных и сравнивать его с сохраненным корневым. Таким образом мы могли бы реализовать проверку валидности адреса кошелька на предмет минта в нужный момент. Давайте напишем на python функционал генерации такой структуры для вайтлиста.</p>
  <p id="2biq">По идее, для правильного формирования корневого дерева у нас должно быть 2ⁿ входящих параметров. Потому для корректной работы, недостающие узлы мы будем просто дублировать.</p>
  <pre id="yLGa">
import json
import os
import secrets
from coincurve import PublicKey
from pprint import pprint as pp
from sha3 import keccak_256
os.system(&quot;clear&quot;)

def getPK() -&gt; None:
    return PublicKey.from_valid_secret(keccak_256(secrets.token_bytes(32)).digest()).format(compressed=False)[1:]

def addressGenerate(total: int) -&gt; None:
    address_list = dict()
    address_list = {
        &quot;addresses&quot;: list(&quot;0x&quot; + keccak_256(getPK()).digest()[-20:].hex() for x in range(0,total)),
        &quot;hashes&quot;: []
    } 
    with open(&quot;./address_list.json&quot;, &quot;w&quot;) as fp:
        json.dump(address_list, fp,indent=4)
    print(f&quot;\nADDRESS LIST:\n&quot; + &quot;\n&quot;.join(address_list[&#x27;addresses&#x27;]))

def getAddressList() -&gt; None:
    with open(&quot;./address_list.json&quot;, &quot;r&quot;, encoding=&#x27;utf-8&#x27;) as fp:
        return json.load(fp)

def keccak256(hc: list) -&gt; str:
    hs = keccak_256()
    hs.update(&quot;&quot;.join(hc).encode(&#x27;utf-8&#x27;))
    return hs.hexdigest()

def merkleTreeEncode(al: list) -&gt; list:
    al = al[&#x27;addresses&#x27;]
    hash_candidate, hash_tree, loop = list(), list(), 0

    while(True):
        if loop == len(hash_tree):
            hash_tree.append(list())
        hash_candidate.append(al.pop())
        if (len(hash_candidate) == 1 and loop == 0) or (len(hash_candidate) == 2 and loop &gt; 0):
            hash_tree[loop].append(keccak256(hash_candidate))
            hash_candidate = list()
        elif len(hash_candidate) == 1 and len(al) == 0 and loop &gt; 0:
            hash_candidate.append(hash_candidate[0])
            hash_tree[loop].append(keccak256(hash_candidate))
        if len(al) == 0:
            if len(hash_tree[loop]) &gt; 1:
                al = list(hash_tree[loop])
                hash_candidate = list()
                loop += 1
            else:
                break
    
    data = json.load(open(&quot;address_list.json&quot;, &quot;r&quot;))
    data[&#x27;hashes&#x27;] = hash_tree
    json.dump(data, open(&quot;address_list.json&quot;, &quot;w&quot;), indent=4)
    return hash_tree

def merkleTreeDependens(address: str)-&gt; dict:
    address_list = getAddressList()
    try:
        index = address_list[&#x27;addresses&#x27;].index(address)
    except Exception as e:
        print(&#x27;invalid address&#x27;)
        return []

    data = {}
    data[&#x27;index&#x27;] = index
    data[&#x27;hash&#x27;] = address_list[&#x27;hashes&#x27;][0][index]
    data[&#x27;root&#x27;] = address_list[&#x27;hashes&#x27;][len(address_list[&#x27;hashes&#x27;])-1]

    out = []
    first = True
    total = len(address_list[&#x27;hashes&#x27;][0])

    for level in address_list[&#x27;hashes&#x27;]:
        if len(level) == 1:
            break
        if len(level) % 2 != 0:
            level.append(level[len(level)-1])
        if first:
            if (index+1) % 2 == 0:    
                out.append(level[index-1])
            else:
                out.append(level[index+1])
            first = False
        else:
            if (index+1) % 2 == 0:    
                out.append(level[index-1])
            else:
                out.append(level[index+1])
        index = int((index) / 2)
        total = total/2
    data[&#x27;proof&#x27;] = out
    return data



len_address = 15
try:
    adress_list = getAddressList()
    hashes = merkleTreeEncode(adress_list)
except Exception as e:
    print(e)
    addressGenerate(len_address)
    adress_list = getAddressList()
    hashes = merkleTreeEncode(adress_list)

hash_root = hashes[len(hashes)-1][0]
print(f&quot;root hash: {hash_root} \n&quot;)


dep = merkleTreeDependens(&quot;0xcc28d2781aff9db54861f4a29624eefdfff0d6ec&quot;)
pp(dep)</pre>
  <p id="ycaZ">Данный пример сформирует json файл со структурой дерева Меркла, который можно использовать для тестирования валидации на стороне смарт-контракта. По сути все что нам остается это отправить из сформированного дерева лишь проверяемый адрес и узлы необходимые для вычислений корневого хеша.</p>
  <figure id="oUvc" class="m_column">
    <img src="https://blog.chevich.com/wp-content/uploads/2022/07/merkltree-1000x1024.png" width="1000" />
  </figure>
  <p id="mLnJ"><a href="https://github.com/shmeller86/merkle_tree" target="_blank">github</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/check_network</guid><link>https://teletype.in/@chelovek86/check_network?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/check_network?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>БЕЗОПАСНО НАСТРАИВАЕМ /ETC/NETWORK/INTERFACES</title><pubDate>Fri, 10 Jun 2022 12:37:23 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/61/15/6115c2ce-238d-49e9-82bc-ba7ed644a4be.png"></media:content><category>Linux🐧</category><description><![CDATA[<img src="https://img3.teletype.in/files/e0/b7/e0b7a009-3ae9-47c4-b7a9-42194a4bf295.jpeg"></img>При настройке сети, в том же proxmox может возникнуть ситуация, когда теряется доступность сервера и это может быть большой головной болью, если у вас нет возможности подойти физически к серверу и исправить.]]></description><content:encoded><![CDATA[
  <h2 id="zdFT">ПРОБЛЕМА</h2>
  <p id="uQ7v">При настройке сети, в том же proxmox может возникнуть ситуация, когда теряется доступность сервера и это может быть большой головной болью, если у вас нет возможности подойти физически к серверу и исправить.</p>
  <h2 id="Njzq">ИДЕЯ</h2>
  <p id="PIG5">Обойтись небольшим скриптом, который бы проверял через какое-то время изменения в конфигурации интерфейсов сети и в случае изменения пробовал проверить доступность сети. В случае если сеть после изменений оставалась работоспособной, то создавал бэкап конфига, в случае если сеть падала, ресторил бы из последнего удачного конфига.</p>
  <h2 id="mInN">РЕШЕНЕНИЕ</h2>
  <p id="YYk7">Создадим файл и сделаем исполняемым</p>
  <pre id="ub6j">cd $HOME &amp;&amp; touch ./check_network.sh &amp;&amp; chmod +x ./check_network.sh</pre>
  <p id="3xga">И добавим следующий код, в котором нам необходимо изменить SERVER_IP на свой публичный внешний адрес, по нему будет проверяться работоспособность сети и OLDTIME который отвечает сколько секунд после изменения файла конфига мы ничего не проверяем. Так как мы будем это вешать на планировщик, на каждую минуту, то есть смысла выставлять значения только наверное больше 2 минут — то-есть значения 120</p>
  <pre id="xBvT" data-lang="bash">check_network.sh#/bin/bash

SERVER_IP=127.0.0.1
FILE=/etc/network/interfaces
BACKUP_FILE=$HOME/interfaces.backup
LAST_GOOD_UPDATE=0
OLDTIME=10

CURTIME=$(date +%s)
FILETIME=$(stat $FILE -c %Y)
TIMEDIFF=$(expr $CURTIME - $FILETIME)

if [ $FILETIME -gt $LAST_GOOD_UPDATE ]; then
    if [ $TIMEDIFF -gt $OLDTIME ]; then
        ping $SERVER_IP -c 5 2&gt;&amp;1 &gt; /dev/null &amp;&amp; RESULT=1 || RESULT=0
        if [ $RESULT -eq 1 ]; then
            echo -e &#x27;\033[1;32m&#x27;
            echo &quot;The interface has been changed and after a while it works. We backup it&quot;
            echo -e &#x27;\033[0m&#x27;
            sed -i &quot;6s/LAST_GOOD_UPDATE=.*/LAST_GOOD_UPDATE=${FILETIME}/&quot; ./check_network.sh
            cp $FILE $BACKUP_FILE
        else
            echo -e &#x27;\033[1;31m&#x27;
            echo &quot;The interface has been changed and after a while it NOT works. We restore it&quot;
            echo -e &#x27;\033[0m&#x27;
            cp $BACKUP_FILE $FILE
            FILETIME=$(stat $FILE -c %Y)
            sed -i &quot;6s/LAST_GOOD_UPDATE=.*/LAST_GOOD_UPDATE=${FILETIME}/&quot; ./check_network.sh
            systemctl restart networking.service
        fi
    fi
fi</pre>
  <p id="QatX">Разберем по порядку:<br />:4 — путь до нашего конфига<br />:5 — путь до бэкапа конфига<br />:6 — таймштамп изменения конфига<br />:13 — проверяем есть ли изменения<br />:14 — выдержали ли мы паузу (задается в секундах строка :7)<br />:15 — пингуем сами себя через внешний публичный ip<br />:20-21 — сеть есть, обновляем время изменения конфига в 6 строке и БЭКАПИМ сам конфиг<br />:26-28 — сети нет, обновляем время изменения файла в 6 строке и РЕСТОРИМ конфиг</p>
  <p id="yoUD">На этом все, теперь можно протестировать и закинуть в крон с повторениями на каждую минуту</p>
  <figure id="6dkh" class="m_column">
    <img src="https://img3.teletype.in/files/e0/b7/e0b7a009-3ae9-47c4-b7a9-42194a4bf295.jpeg" width="647" />
  </figure>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/hetzner_vds_proxmox</guid><link>https://teletype.in/@chelovek86/hetzner_vds_proxmox?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/hetzner_vds_proxmox?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>СОЗДАЕМ ВИРТУАЛКИ НА VDS HETZNER</title><pubDate>Thu, 09 Jun 2022 07:47:06 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/d2/bd/d2bde08c-2d85-4119-8acc-a6c9441e2bf8.png"></media:content><category>guide</category><description><![CDATA[<img src="https://4trading.app/wp-content/uploads/2022/06/hetzner_auction-1024x545.png"></img>Я потратил целый день с настройкой дедика, а потому решил написать инструкцию, чтобы не забыть.]]></description><content:encoded><![CDATA[
  <p id="xtJx">Я потратил целый день с настройкой дедика, а потому решил написать инструкцию, чтобы не забыть.</p>
  <p id="H3sm">Итак, нам необходимо заказать на аукционе в Hetzner (немецкий хостер) VDS сервер, так же, для того чтобы на этом сервере поднимать свои независимые VPS нам понадобится публичные ip адреса и да, они тоже стоят денег.</p>
  <p id="22kh">Летим по рефералке и регаемся на <a href="https://hetzner.cloud/?ref=olEYz99DV8zW" target="_blank">hetzner</a> — получаем 20 евро в виде облачных кредитов, либо без рефералки <a href="https://hetzner.cloud/" target="_blank">здесь</a>. В разделе <a href="https://www.hetzner.com/sb" target="_blank">аукциона</a> выбираем подходящий сервер</p>
  <figure id="pYB4" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/hetzner_auction-1024x545.png" width="1024" />
  </figure>
  <p id="FanZ">После, понадобится какое-то время, в зависимости от того что вы выбрали, в моем случае установка была произведена за сутки, хотя по таблице в которой указывается планируемое время установки, стояло несколько минут. Когда придет на почту уведомление об успешной установке, можно двигаться дальше.</p>
  <figure id="66w5" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/859e61bcf3.jpeg" width="654" />
  </figure>
  <p id="gKtV">Отлично, теперь летим в <a href="https://robot.your-server.de/server" target="_blank">лк</a> во вкладку IPs и берем ip адреса в подсети /29. Это будет 8 штук., но не спешите радоваться, первый в подсети это сетевой адрес, а последний broadcast адрес и их нельзя использовать. Просто имейте это в виду.</p>
  <figure id="zrLD" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654702517288.jpg" width="932" />
  </figure>
  <p id="EcBN">IP адреса готовы и мы можем заняться установкой ПО. Для управления нашими виртуалками мы будем использовать Proxmox v.7</p>
  <p id="PY70">Переходим на вкладку Rescue и запускаем систему. Для подключения к серверу я использую ранее загруженный ключ, но можно и по логину паролю зайти. Я пользуюсь стандартным терминалом.</p>
  <figure id="U66V" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654703052933-1024x299.jpg" width="1024" />
  </figure>
  <p id="dQ28">Вводим из терминала <em>ssh root@&lt;SERVER IP&gt;</em> и попадаем на сервер</p>
  <figure id="Lsc9" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654703411756.jpg" width="872" />
  </figure>
  <p id="ObcE">Не теряя времени набираем в командной строке <strong>installimage</strong>, в списке выбираем <strong>Other</strong> и далее выбираем последнюю версию Proxmox Bullseye, он идет с 11 версией debian. Все предупреждения читаем и скипаем, они ни на что не влияют.</p>
  <figure id="5cEa" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654703663813.jpg" width="593" />
  </figure>
  <p id="rm15">В пред установочном конфиге я изменил только SWRAIDLEVEL с 6 на 0, т.к. скорость и свободное место для меня приоритетнее, нежели отказоусточивость. Так же измените HOSTNAME, во всем остальном меня все устраивает. Сохраняем нажатием F2 с подтверждением и выходим F10. При выходе будет несколько подтверждений о том что диски затрутся, соглашаемся и идем пить чай, установка началась.</p>
  <figure id="fGj2" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654703930180.jpg" width="766" />
  </figure>
  <p id="gbm7">После установки отправляем сервер на перезагрузку командой <strong>reboot</strong>, если заходили по ssh с помощью ключей, не забудьте сбросить fingerprint командой ssh-keygen -R &lt;SERVER IP&gt;. После перезагрузки снова подключаемся к серверу и открываем конфиг с нашими сетевыми интерфейсами <strong>nano /etc/network/interfaces</strong></p>
  <figure id="uLZs" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654704732226.jpg" width="779" />
  </figure>
  <p id="WRu6">У вас конечно будет по другому, но сути не меняет, на этом этапе нам нужно настроить с учетом наших купленных адресов</p>
  <pre id="86w9">source /etc/network/interfaces.d/*

auto lo
iface lo inet loopback

iface enp98s0f0 inet manual
iface enp97s0f0 inet manual
iface enp97s0f1 inet manual
iface enp98s0f1 inet manual

auto vmbr0
iface vmbr0 inet static
        address 195.xxx.xxx.43/32
        gateway 195.xxx.xxx.1
        bridge-ports enp98s0f0
        bridge-stp off
        bridge-fd 1
        pointopoint 195.xxx.xxx.1

auto vmbr100
iface vmbr100 inet static
        address 78.xxx.xxx.30/29
        bridge-ports none
        bridge-stp off
        bridge-fd 0</pre>
  <p id="DprR">Тут мы создали 2 моста, vmbr0 для нашей основной сети, в address указываем ip сервера с указанием подсети /32, так же указываем шлюзы, эти данные есть во вкладке IPs в лк.</p>
  <p id="ZYFr">Второй мост для нашей подсети с купленными ip называется vmbr100, указываем в адресе ip но маской подсети, имейте в виду, что ip не должен быть первым и последним. Запомните свой выбор, потому что на гостевой машине мы будем на него ссылаться в шлюзе.</p>
  <p id="CUzT">Сохраняем и применяем наши изменения командой <strong>ifup -a</strong>, теперь командой <strong>passwd</strong> зададим пароль и отправимся в вебморду https://&lt;SERVER IP&gt;:8006</p>
  <figure id="0HSv" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654705672165.jpg" width="446" />
  </figure>
  <p id="4OWw">Сейчас у нас нет подключенных образов ОС и мы не сможем ничего установить, давайте это исправим, переходим в раздел как ISO и вставляем ссылку на дистрибутив Ubuntu 20.04 взятый с официального сайта <strong>https://releases.ubuntu.com/20.04.4/ubuntu-20.04.4-live-server-amd64.iso</strong></p>
  <figure id="ysTt" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1654705841934.jpg" width="1019" />
  </figure>
  <p id="SUqP">Процесс создания виртуалки очень простой, основной момент это выбрать образ диска и установить наш мост.</p>
  <figure id="199" class="m_custom">
    <img src="https://4trading.app/wp-content/uploads/2022/06/1.jpg" width="714" />
  </figure>
  <figure id="196" class="m_custom">
    <img src="https://4trading.app/wp-content/uploads/2022/06/2.jpg" width="712" />
  </figure>
  <figure id="200" class="m_custom">
    <img src="https://4trading.app/wp-content/uploads/2022/06/3.jpg" width="718" />
  </figure>
  <figure id="198" class="m_custom">
    <img src="https://4trading.app/wp-content/uploads/2022/06/4.jpg" width="715" />
  </figure>
  <figure id="195" class="m_custom">
    <img src="https://4trading.app/wp-content/uploads/2022/06/5.jpg" width="714" />
  </figure>
  <figure id="194" class="m_custom">
    <img src="https://4trading.app/wp-content/uploads/2022/06/6.jpg" width="716" />
  </figure>
  <figure id="197" class="m_custom">
    <img src="https://4trading.app/wp-content/uploads/2022/06/7.jpg" width="718" />
  </figure>
  <p id="upxR">После создания, запускаем нашу виртуалку и приступаем к процессу установки. Установка не должна вызывать никаких сложностей, основной момент в процессе установки указать настройки сети</p>
  <figure id="sBsK" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/06/14.jpg" width="990" />
  </figure>
  <p id="DBD9">Прописываем нашу подсеть, задаем явно ip адрес и указываем адрес шлюза, который ранее указывали при создании моста.</p>
  <p id="ggnt">Если удача была на вашей стороне, то после установки, все будет работать. В нашей подсети осталось еще 4 свободных адреса, их так же по аналогии задаем на новых виртуалках.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/massanode</guid><link>https://teletype.in/@chelovek86/massanode?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/massanode?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>MASSA NODE</title><pubDate>Thu, 09 Jun 2022 07:14:03 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/66/e5/66e54eab-4da6-403d-bc88-8ecfd66a81bc.png"></media:content><description><![CDATA[<img src="https://img4.teletype.in/files/7e/cf/7ecfdf2d-582c-4b4c-9f83-f231cd0bac5e.png"></img>после установки заходим в клиент]]></description><content:encoded><![CDATA[
  <figure id="lOHp" class="m_column">
    <img src="https://img4.teletype.in/files/7e/cf/7ecfdf2d-582c-4b4c-9f83-f231cd0bac5e.png" width="1200" />
  </figure>
  <pre id="bpgS" data-lang="bash">wget -O massa.sh https://raw.githubusercontent.com/shmeller86/guides/master/massa.sh &amp;&amp; chmod +x massa.sh &amp;&amp; ./massa.sh</pre>
  <p id="GNbR">после установки заходим в клиент</p>
  <pre id="FL5Y" data-lang="bash">source ~/.bash_profile &amp;&amp; cd ~/massa/massa-client &amp;&amp; cargo run --release</pre>
  <p id="yWZE">создаем или восстанавливаем по ключу кошелек</p>
  <pre id="ZJbz" data-lang="bash">wallet_generate_private_key # создаем
wallet_add_private_keys &lt;your_private_key&gt; # восстанавливаем</pre>
  <p id="r3UD">проверяем</p>
  <pre id="nK9b" data-lang="bash">wallet_info</pre>
  <p id="H0Ui">отправляем в дис канал <strong>#testnet-faucet </strong>свой адрес кошелька и покупаем ролл</p>
  <pre id="9lgq" data-lang="bash">buy_rolls &lt;ADDRESS&gt; 1 0</pre>
  <p id="DNjW">как появятся роллы в кошельке делаем возможность стейкинга</p>
  <pre id="Yf7U" data-lang="bash">node_add_staking_private_keys &lt;PRIVAT KEY&gt;</pre>
  <p id="nOI5">регаемся на фазу тестнета в боте MassaBot, отправляем ему ip сервера, в ответ пришлет твой discord id и вставляем его в лиенте</p>
  <pre id="88Co" data-lang="bash">node_testnet_rewards_program_ownership_proof &lt;ADDRESS&gt; &lt;DISCORD ID&gt;</pre>
  <p id="cyQg">На этом все. Следим, переживаем, исправляем.</p>
  <p id="gsjF"><a href="https://github.com/shmeller86/guides" target="_blank">Github</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/subspace_node_gemini</guid><link>https://teletype.in/@chelovek86/subspace_node_gemini?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/subspace_node_gemini?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>SUBSPACE (Gemini)</title><pubDate>Tue, 31 May 2022 15:45:35 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/9a/ff/9afffe58-9bb9-49e3-8f20-d00c19b959a2.png"></media:content><category>guide</category><description><![CDATA[<img src="https://img1.teletype.in/files/82/fb/82fb3adc-9a89-4058-a1fe-b10a49bb07f0.png"></img>Установка ноды и фармера для Subspace. 
github
источник ]]></description><content:encoded><![CDATA[
  <figure id="C4aa" class="m_column">
    <img src="https://img1.teletype.in/files/82/fb/82fb3adc-9a89-4058-a1fe-b10a49bb07f0.png" width="682" />
  </figure>
  <p id="0NQO">Установка ноды и фармера для Subspace. <br /><a href="https://github.com/shmeller86/guides/blob/master/subspace.sh" target="_blank">github</a><br /><a href="https://github.com/subspace/subspace/blob/38eb757fe63825cbc4d182697f442894d00d190c/docs/farming.md" target="_blank">источник</a> </p>
  <p id="hla7">В переменных SUBSPACE_WALLET_ADDRESS и SUBSPACE_NODE_NAME необходимо указать название ноды и свой адрес кошелька.</p>
  <pre id="aNVF">SUBSPACE_WALLET_ADDRESS=&quot;&quot;
SUBSPACE_NODE_NAME=&quot;&quot;
cd ~
wget -O subspace-node https://github.com/subspace/subspace/releases/download/gemini-1a-2022-may-31/subspace-node-ubuntu-x86_64-gemini-1a-2022-may-31
wget -O subspace-farmer https://github.com/subspace/subspace/releases/download/gemini-1a-2022-may-31/subspace-farmer-ubuntu-x86_64-gemini-1a-2022-may-31
mv subspace* /usr/local/bin/
chmod +x /usr/local/bin/subspace*
adduser --system --home=/var/lib/subspace subspace
tee &lt;&lt;EOF &gt;/dev/null /etc/systemd/system/subspace-node.service
[Unit]
Description=Subspace Node
After=network.target
[Service]
Type=simple
User=subspace
ExecStart=/usr/local/bin/subspace-node \
--chain gemini-1 \
--execution wasm \
--pruning 1024 \
--keep-blocks 1024 \
--validator \
--name $SUBSPACE_NODE_NAME
Restart=alwa
EOF
tee &lt;&lt;EOF &gt;/dev/null /etc/systemd/system/subspace-farmer.service
[Unit]
Description=Subspace Farmer
Requires=subspace-node.service
After=network.target
After=subspace-node.service
[Service]
Type=simpleUser=subspace
ExecStart=/usr/local/bin/subspace-farmer farm --reward-address=$SUBSPACE_WALLET_ADDRESS --plot-size=200G
Restart=always
RestartSec=10
LimitNOFILE=10000
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl start subspace-node
systemctl start subspace-farmer</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/tg_bot_game_kmn</guid><link>https://teletype.in/@chelovek86/tg_bot_game_kmn?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/tg_bot_game_kmn?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>ИНТЕГРАЦИЯ СМАРТ-КОНТРАКТА В ТЕЛЕГРАМ БОТ</title><pubDate>Fri, 27 May 2022 15:07:03 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/71/12/71127266-a5b6-4620-abf6-606f04dd42c1.png"></media:content><category>PYTHON</category><description><![CDATA[<img src="https://4trading.app/wp-content/uploads/2022/05/bot-telegram.jpeg"></img>READ TIME:14 MINUTE, 0 SECOND]]></description><content:encoded><![CDATA[
  <figure id="76LO" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/05/bot-telegram.jpeg" width="660" />
  </figure>
  <p id="kzSV">READ TIME:14 MINUTE, 0 SECOND</p>
  <p id="V5vI">Для web3 есть одноименная библиотека написанная для разных языков, в том числе для python, называется <a href="https://web3py.readthedocs.io/" target="_blank">web3py</a>.<br />Здесь мы интегрируем наши 2 проекта в наш телеграм бот. У нас будет игра «Камень, ножницы, бумага» на внутренние токены, смарт-контракт на solidity писали <a href="https://4trading.app/solidity/pishem-blokchejn-igru-na-solidity/" target="_blank">тут</a> и реализуем генерацию ethereum кошельков <a href="https://4trading.app/python/generacziya-ethereum-adresov-na-python/" target="_blank">тык</a>.<br />Взаиподействие с telegram api переложим на <a href="https://docs.python-telegram-bot.org/en/stable/index.html" target="_blank">python-telegram-bot</a>. Создадим проект и склонируем репозиторий телеграм бота.</p>
  <pre id="jmCz" data-lang="bash">cd ~ &amp;&amp; mkdir tgbot &amp;&amp; cd tgbot
python -m venv .venv
source .venv/bin/activate
pip install python-telegram-bot coincurve pysha3 web3
git clone https://github.com/python-telegram-bot/python-telegram-bot --recursive
cd python-telegram-bot
python setup.py install &amp;&amp; cd .. &amp;&amp; mv -f * ../ &amp;&amp; touch start.py</pre>
  <p id="unup">Нам нужно будет хранить немного данных, не будем заморачиваться и используем sqllite, можете создать сами, либо взять просто готувую из гита</p>
  <pre id="7wP1" data-lang="sql">CREATE TABLE &quot;users&quot; (
	&quot;id&quot;	INTEGER NOT NULL UNIQUE,
	&quot;id_tg&quot;	INTEGER NOT NULL UNIQUE,
	&quot;address&quot;	TEXT UNIQUE,
	&quot;private_key&quot;	TEXT UNIQUE,
	&quot;balance&quot;	INTEGER,
	&quot;win&quot;	INTEGER,
	&quot;lose&quot;	INTEGER,
	&quot;dt_create&quot;	TEXT,
	&quot;first_name&quot;	TEXT,
	&quot;last_name&quot;	TEXT,
	&quot;user_name&quot;	TEXT,
	&quot;last_game&quot;	INTEGER,
	PRIMARY KEY(&quot;id&quot; AUTOINCREMENT)
)</pre>
  <p id="5Qlk">Так же создадим конфиг и запишем в него кошельки, токен нашел бота и другие параметры</p>
  <pre id="8RtO" data-lang="jsx">{
    &quot;proxy&quot;: {
        &quot;http&quot;: &quot;http://login:password@address:port&quot;,
        &quot;https&quot;: &quot;http://login:password@address:port&quot;
    },
    &quot;rpc&quot;: &quot;https://polygon-rpc.com&quot;,
    &quot;contract&quot;: &quot;0x000.... CONTRACT ADDRESS&quot;,
    &quot;bank_wallet_from&quot;: &quot;0x000... WALLET&quot;,
    &quot;bank_private_key&quot;: &quot;... PRIVATE KEY&quot;,
    &quot;url_addr&quot;: &quot;https://polygonscan.com/tx/&quot;,
    &quot;bot_token&quot;: &quot;TOKEN TELEGRAM BOT&quot;
}</pre>
  <p id="BWkt">Импортируем нужные библиотеки для работы</p>
  <pre id="ROYk" data-lang="python">import json
import logging
import secrets
import sqlite3
from coincurve import PublicKey
from sha3 import keccak_256
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, constants
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes
from web3 import Web3
from web3.logs import DISCARD</pre>
  <p id="SpRC">Подключим базу данных с конфигом, создадим наши будущие кнопки для управления</p>
  <pre id="Q348" data-lang="python">con = sqlite3.connect(&#x27;db.db&#x27;)
cur = con.cursor()

logging.basicConfig(
    format=&quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&quot;, level=logging.INFO
)
logger = logging.getLogger(__name__)

k_1 = [
    [
        InlineKeyboardButton(&quot;Play&quot;, callback_data=&quot;play_rsp&quot;),
        InlineKeyboardButton(&quot;Balance&quot;, callback_data=&quot;get_balance&quot;),
        InlineKeyboardButton(&quot;Deposit&quot;, callback_data=&quot;deposite&quot;),
        InlineKeyboardButton(&quot;Make wallets&quot;, callback_data=&quot;gen_wal&quot;)
    ]
]

k_2 = [
    [
        InlineKeyboardButton(&quot;1&quot;, callback_data=&quot;1&quot;),
        InlineKeyboardButton(&quot;5&quot;, callback_data=&quot;5&quot;),
        InlineKeyboardButton(&quot;10&quot;, callback_data=&quot;10&quot;),
        InlineKeyboardButton(&quot;Return&quot;, callback_data=&quot;menu&quot;)
    ],
]

k_3 = [
    [
        InlineKeyboardButton(&quot; Rock&quot;, callback_data=&quot;rock&quot;),
        InlineKeyboardButton(&quot; Scissors&quot;, callback_data=&quot;scissors&quot;),
        InlineKeyboardButton(&quot; Paper&quot;, callback_data=&quot;paper&quot;),
    ],
    [
        InlineKeyboardButton(&quot;Return&quot;, callback_data=&quot;menu&quot;)
    ]
]

k_4 = [
    [
        InlineKeyboardButton(&quot;Check winner&quot;, callback_data=&quot;reload&quot;)
    ]
]

reply_markup_main = InlineKeyboardMarkup(k_1)
reply_markup_numbers = InlineKeyboardMarkup(k_2)
reply_markup_play = InlineKeyboardMarkup(k_3)
reply_markup_reload = InlineKeyboardMarkup(k_4)

with open(&#x27;./config.json&#x27;, &#x27;r&#x27;) as f:
    config = json.load(f)

proxies = config[&#x27;proxy&#x27;]

RPC = config[&#x27;rpc&#x27;]
contract = config[&#x27;contract&#x27;]
bank_wallet_from = config[&#x27;bank_wallet_from&#x27;]
bank_private_key = config[&#x27;bank_private_key&#x27;]
url_addr = config[&#x27;url_addr&#x27;]
BOT_TOKEN = config[&#x27;bot_token&#x27;]
ERC20_ABI = json.loads(&#x27;&#x27;&#x27;[{&quot;inputs&quot;: [],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;constructor&quot;},{&quot;anonymous&quot;: false,&quot;inputs&quot;: [{&quot;indexed&quot;: true,&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;owner&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;indexed&quot;: true,&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;spender&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;indexed&quot;: false,&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;value&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;Approval&quot;,&quot;type&quot;: &quot;event&quot;},{&quot;anonymous&quot;: false,&quot;inputs&quot;: [{&quot;indexed&quot;: false,&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;number&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;NumberPlay&quot;,&quot;type&quot;: &quot;event&quot;},{&quot;anonymous&quot;: false,&quot;inputs&quot;: [{&quot;indexed&quot;: true,&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;from&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;indexed&quot;: true,&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;to&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;indexed&quot;: false,&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;value&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;Transfer&quot;,&quot;type&quot;: &quot;event&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;owner&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;spender&quot;,&quot;type&quot;: &quot;address&quot;}],&quot;name&quot;: &quot;allowance&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;spender&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;amount&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;approve&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;bool&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;bool&quot;}],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;account&quot;,&quot;type&quot;: &quot;address&quot;}],&quot;name&quot;: &quot;balanceOf&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;decimals&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint8&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint8&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;spender&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;subtractedValue&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;decreaseAllowance&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;bool&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;bool&quot;}],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;gameCost&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;id&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;gameHistory&quot;,&quot;outputs&quot;: [{&quot;components&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;player_1&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;player_2&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;int8&quot;,&quot;name&quot;: &quot;player_1_action&quot;,&quot;type&quot;: &quot;int8&quot;},{&quot;internalType&quot;: &quot;int8&quot;,&quot;name&quot;: &quot;player_2_action&quot;,&quot;type&quot;: &quot;int8&quot;},{&quot;internalType&quot;: &quot;int8&quot;,&quot;name&quot;: &quot;player_win&quot;,&quot;type&quot;: &quot;int8&quot;}],&quot;internalType&quot;: &quot;struct RSP._game[]&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;tuple[]&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;gameId&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;spender&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;addedValue&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;increaseAllowance&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;bool&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;bool&quot;}],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;jackPot&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;name&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;string&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;string&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;players&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;address payable[]&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;address[]&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;_modulus&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;randMod&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;cost&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;setGameCost&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;bool&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;bool&quot;}],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;symbol&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;string&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;string&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [],&quot;name&quot;: &quot;totalSupply&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;view&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;to&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;amount&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;transfer&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;bool&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;bool&quot;}],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;from&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;address&quot;,&quot;name&quot;: &quot;to&quot;,&quot;type&quot;: &quot;address&quot;},{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;amount&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;name&quot;: &quot;transferFrom&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;bool&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;bool&quot;}],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;function&quot;},{&quot;inputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;amount&quot;,&quot;type&quot;: &quot;uint256&quot;},{&quot;internalType&quot;: &quot;int8&quot;,&quot;name&quot;: &quot;action&quot;,&quot;type&quot;: &quot;int8&quot;}],&quot;name&quot;: &quot;transferPerGame&quot;,&quot;outputs&quot;: [{&quot;internalType&quot;: &quot;uint256&quot;,&quot;name&quot;: &quot;&quot;,&quot;type&quot;: &quot;uint256&quot;}],&quot;stateMutability&quot;: &quot;nonpayable&quot;,&quot;type&quot;: &quot;function&quot;}]&#x27;&#x27;&#x27;)

w3 = Web3(Web3.HTTPProvider(RPC,request_kwargs={&quot;proxies&quot;:proxies}))
coin = w3.eth.contract(contract, abi=ERC20_ABI)
gasLimit = 210000</pre>
  <p id="dlEB">Теперь создадим первую функцию, она будет вызываться при первом взаимодействии пользователя с боток, в ней будет механизм регистрации нового пользователя, так же мы там реализуем функции перевода внутренних токенов, а так же небольщой перевод MATIC для оплаты транзакций, т.к. мы используем сеть polygon</p>
  <pre id="yp07" data-lang="python">async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -&gt; None:
    try:
        first_name = update.message.chat.first_name
        last_name = update.message.chat.last_name
        user_name = update.message.chat.username
        user_id = update.message.chat.id
        row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
        if row:
            await update.message.reply_text(f&quot;You are already registered!&quot;, parse_mode=constants.ParseMode.HTML)
            await update.message.reply_text(&quot;Please choose:&quot;, reply_markup=reply_markup_main)
            return False
        private_key = keccak_256(secrets.token_bytes(32)).digest()
        public_key = PublicKey.from_valid_secret(private_key).format(compressed=False)[1:]
        address = keccak_256(public_key).digest()[-20:]
        new_address = Web3.toChecksumAddress(&quot;0x&quot;+str(address.hex()))
        await update.message.reply_text(f&quot;Hi Bro\! you are here for the first time, \
    so I created a wallet for you, it will be useful for you to interact\. Write \
    down your private key and don&#x27;t share it with anyone\!\n\n\
        *Wallet address\:* {new_address}\n\
        *Private key\:* ||{private_key.hex()}||&quot;, parse_mode=constants.ParseMode.MARKDOWN_V2)
        await update.message.reply_text(f&quot;And now I will transfer 10 tokens to your wallet so that you can start spending them...\n\n Just wait 5 second...&quot;, 
        parse_mode=constants.ParseMode.HTML)
        transaction = {
            &#x27;chainId&#x27;: 137,
            &#x27;to&#x27;: new_address,
            &#x27;from&#x27;: bank_wallet_from,
            &#x27;value&#x27;: Web3.toWei(0.1, &#x27;ether&#x27;),
            &#x27;nonce&#x27;: w3.eth.getTransactionCount(bank_wallet_from),
            &#x27;gas&#x27;: gasLimit,
            &#x27;gasPrice&#x27;: w3.eth.gas_price * 2
        }
        signed_txn = w3.eth.account.sign_transaction(transaction, bank_private_key)
        txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
        native_hash = txn_hash.hex()
        dict_transaction = {
            &#x27;chainId&#x27;: 137,
            &#x27;from&#x27;: bank_wallet_from,
            &#x27;gas&#x27;: gasLimit,
            &#x27;gasPrice&#x27;: w3.eth.gas_price * 2,
            &#x27;nonce&#x27;: w3.eth.getTransactionCount(bank_wallet_from) + 1,
        }
        coin_decimals = coin.functions.decimals().call()
        one_coin = 10 * 10 ** coin_decimals
        transaction = coin.functions.transfer(new_address, one_coin).buildTransaction(dict_transaction)
        signed_txn = w3.eth.account.sign_transaction(transaction, bank_private_key)
        txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
        await update.message.reply_text(f&quot;Alright, bro! I sent you &lt;b&gt;10 RSP&lt;/b&gt; native tokens and &lt;b&gt;0.1 MATIC&lt;/b&gt; for transaction.\n\n\n\
RSP transaction hash: &lt;a href=&#x27;{url_addr}{txn_hash.hex()}&#x27;&gt;{txn_hash.hex()}&lt;/a&gt;\n\n\
MATIC transaction hash: &lt;a href=&#x27;{url_addr}{native_hash}&#x27;&gt;{native_hash}&lt;/a&gt;&quot;, 
        parse_mode=constants.ParseMode.HTML, disable_web_page_preview=True)
        cur.execute(f&quot;INSERT INTO users (first_name, last_name, user_name, id_tg, address, private_key, balance, win, lose, dt_create) VALUES (&#x27;{first_name}&#x27;,&#x27;{last_name}&#x27;,&#x27;{user_name}&#x27;,&#x27;{user_id}&#x27; ,&#x27;{new_address}&#x27;, &#x27;{private_key.hex()}&#x27;, 10, 0,0,&#x27;2022-05-19&#x27;);&quot;)
        con.commit()
        await update.message.reply_text(&quot;Please choose:&quot;, reply_markup=reply_markup_main)
    except Exception as e:
        print(e)
        await update.message.reply_text(&quot;Something wrong... Try again command /start&quot;)
</pre>
  <p id="WAV9">Вторая функция будет обрабатывать нажатия наших кнопок, в ней реализованы такие вещи как собственно сама игра с выбором 3 действий, проверка баланса, кран который закинет монет на счет и генерация наших кошелько.</p>
  <pre id="RrxE" data-lang="python">
async def button(update: Update, context: ContextTypes.DEFAULT_TYPE) -&gt; None:
    &quot;&quot;&quot;Parses the CallbackQuery and updates the message text.&quot;&quot;&quot;
    query = update.callback_query
    user_id = update.callback_query.from_user.id
    if query.data == &quot;play_rsp&quot;:
        await query.answer()
        row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
        if row:
            cost_play = coin.functions.gameCost().call() / (10**18)
            reward = cost_play * 2 * .9
            if row[4] &gt;= cost_play:
                await query.edit_message_text(f&quot;The game costs {str(cost_play)} RSP tokens, if you win, you will take {str(reward)} RSP tokens&quot;, reply_markup=reply_markup_play)
                return None
            else:
                await query.edit_message_text(f&quot;Sorry, but need more RSP tokens&quot;, reply_markup=reply_markup_main)
                return None
    elif query.data == &quot;get_balance&quot;:
        row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
        if row:
            msg = f&quot;*Your balance:* {row[4]} RSP&quot;
        else:
            msg = f&quot;*Your balance:* \-\-\- RSP&quot;
    elif query.data == &quot;deposite&quot;:
        row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
        if row:
            await query.edit_message_text(f&quot;I will transfer 5 tokens to your address\n Just wait 5 second...\n&quot;, 
            parse_mode=constants.ParseMode.HTML)
            dict_transaction = {
                &#x27;chainId&#x27;: 137,
                &#x27;from&#x27;: bank_wallet_from,
                &#x27;gas&#x27;: gasLimit,
                &#x27;gasPrice&#x27;: w3.eth.gas_price * 2,
                &#x27;nonce&#x27;: w3.eth.getTransactionCount(bank_wallet_from),
            }
            coin_decimals = coin.functions.decimals().call()
            one_coin = 5 * 10 ** coin_decimals
            transaction = coin.functions.transfer(row[2], one_coin).buildTransaction(dict_transaction)
            signed_txn = w3.eth.account.sign_transaction(transaction, bank_private_key)
            txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
            await query.message.reply_text(f&quot;Alright! I sent you 5 RSP. Transaction hash &lt;a href=&#x27;{url_addr}{txn_hash.hex()}&#x27;&gt;{txn_hash.hex()}&lt;/a&gt;&quot;, 
            parse_mode=constants.ParseMode.HTML)
            new_balance = row[4] + 5
            cur.execute(f&quot;UPDATE users SET balance={new_balance} where id={row[0]};&quot;)
            con.commit()
        await query.answer()
        await query.message.reply_text(&quot;Please choose:&quot;, reply_markup=reply_markup_main)
        return True
    elif query.data == &quot;gen_wal&quot;:
        await query.answer()
        await query.edit_message_text(&quot;How many?:&quot;, reply_markup=reply_markup_numbers)
        return None
    elif query.data == &quot;menu&quot;:
        msg=&#x27;&#x27;
    elif query.data == &quot;rock&quot;:
        try:
            await query.answer()
            await query.edit_message_text(text=&quot;Your choice is accepted, the transaction is being executed...&quot;)
            row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
            if row:
                cost_play = coin.functions.gameCost().call()
                dict_transaction = coin.functions.transferPerGame(cost_play,1).buildTransaction({
                    &#x27;chainId&#x27;: 137,
                    &#x27;from&#x27;: row[2],
                    &#x27;gas&#x27;: gasLimit,
                    &#x27;gasPrice&#x27;: w3.eth.gas_price * 2,
                    &#x27;nonce&#x27;: w3.eth.getTransactionCount(row[2])
                }) 
                signed_txn = w3.eth.account.signTransaction(dict_transaction, private_key=row[3])
                txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
                receipt = w3.eth.wait_for_transaction_receipt(txn_hash,timeout=120, poll_latency=0.1)
                logs = coin.events.NumberPlay().processReceipt(receipt, errors=DISCARD)
                number = logs[0][&#x27;args&#x27;][&#x27;number&#x27;]
                msg = f&quot;Your playID: {number}\n Push the button and check winner!&quot;
        except Exception as e:
            msg = e
        finally:
            new_balance = row[4] - (cost_play / 10**18)
            cur.execute(f&quot;UPDATE users SET balance={new_balance}, last_game={int(number)} where id={row[0]};&quot;)
            con.commit()
            await query.edit_message_text(msg, reply_markup=reply_markup_reload)
            return None
    elif query.data == &quot;scissors&quot;:
        try:
            await query.answer()
            await query.edit_message_text(text=&quot;Your choice is accepted, the transaction is being executed...&quot;)
            row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
            if row:
                cost_play = coin.functions.gameCost().call()
                dict_transaction = coin.functions.transferPerGame(cost_play,2).buildTransaction({
                    &#x27;chainId&#x27;: 137,
                    &#x27;from&#x27;: row[2],
                    &#x27;gas&#x27;: gasLimit,
                    &#x27;gasPrice&#x27;: w3.eth.gas_price * 2,
                    &#x27;nonce&#x27;: w3.eth.getTransactionCount(row[2])
                }) 
                signed_txn = w3.eth.account.signTransaction(dict_transaction, private_key=row[3])
                txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
                receipt = w3.eth.wait_for_transaction_receipt(txn_hash,timeout=120, poll_latency=0.1)
                logs = coin.events.NumberPlay().processReceipt(receipt, errors=DISCARD)
                number = logs[0][&#x27;args&#x27;][&#x27;number&#x27;]
                msg = f&quot;Your playID: {number}\n Push the button and check winner!&quot;
        except Exception as e:
            msg = e
        finally:
            new_balance = row[4] - (cost_play / 10**18)
            cur.execute(f&quot;UPDATE users SET balance={new_balance}, last_game={int(number)} where id={row[0]};&quot;)
            con.commit()
            await query.edit_message_text(msg, reply_markup=reply_markup_reload)
            return None
    elif query.data == &quot;paper&quot;:
        try:
            await query.answer()
            await query.edit_message_text(text=&quot;Your choice is accepted, the transaction is being executed...&quot;)
            row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
            if row:
                cost_play = coin.functions.gameCost().call()
                dict_transaction = coin.functions.transferPerGame(cost_play,3).buildTransaction({
                    &#x27;chainId&#x27;: 137,
                    &#x27;from&#x27;: row[2],
                    &#x27;gas&#x27;: gasLimit,
                    &#x27;gasPrice&#x27;: w3.eth.gas_price * 2,
                    &#x27;nonce&#x27;: w3.eth.getTransactionCount(row[2])
                }) 
                signed_txn = w3.eth.account.signTransaction(dict_transaction, private_key=row[3])
                txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
                receipt = w3.eth.wait_for_transaction_receipt(txn_hash,timeout=120, poll_latency=0.1)
                logs = coin.events.NumberPlay().processReceipt(receipt, errors=DISCARD)
                number = logs[0][&#x27;args&#x27;][&#x27;number&#x27;]
                msg = f&quot;Your playID: {number}\n Push the button and check winner!&quot;
        except Exception as e:
            msg = e
        finally:
            new_balance = row[4] - (cost_play / 10**18)
            cur.execute(f&quot;UPDATE users SET balance={new_balance}, last_game={int(number)} where id={row[0]};&quot;)
            con.commit()
            await query.edit_message_text(msg, reply_markup=reply_markup_reload)
            return None
    elif query.data == &quot;reload&quot;:
        await query.answer()
        row = cur.execute(&quot;SELECT * FROM users WHERE id_tg = &#x27;%s&#x27;&quot; % user_id).fetchone()
        if row:
            history = coin.functions.gameHistory(row[12]).call()
            if row[2] == history[0][0]:
                if history[0][4] == 0:
                    cost_play = coin.functions.gameCost().call()
                    new_balance = row[4] + ((cost_play / 10**18) * 2 / 100 * 90)
                    msg = f&quot;Congratulations!!!\nYou Won!!! Your balance is increased and now equal {new_balance} RSP&quot;
                    cur.execute(f&quot;UPDATE users SET balance={new_balance} where id={row[0]};&quot;)
                elif history[0][4] == 1:
                    msg = &quot;You lost, but next time you will be lucky!!!&quot;
                else:
                    msg = &quot;No one won in this game, the cost is returned&quot;
            elif row[2] == history[0][1]:
                if history[0][4] == 1:
                    cost_play = coin.functions.gameCost().call()
                    new_balance = row[4] + ((cost_play / 10**18) * 2 / 100 * 90)
                    msg = f&quot;Congratulations!!!\nYou Won!!! Your balance is increased and now equal {new_balance} RSP&quot;
                    cur.execute(f&quot;UPDATE users SET balance={new_balance} where id={row[0]};&quot;)
                elif history[0][4] == 0:
                    msg = &quot;You lost, but next time you will be lucky!!!&quot;
                else:
                    msg = &quot;No one won in this game, the cost is returned&quot;
            print(history)
    elif int(query.data) &gt; 0:
        msg = &quot;&lt;b&gt;ADDRESS:PRIVATE_KEY&lt;/b&gt;\n\n&quot;
        for i in range(0,int(query.data)):
            private_key = keccak_256(secrets.token_bytes(32)).digest()
            public_key = PublicKey.from_valid_secret(private_key).format(compressed=False)[1:]
            address = keccak_256(public_key).digest()[-20:]
            msg += f&quot;&lt;code&gt;{address.hex()}:{private_key.hex()}&lt;/code&gt;\n\n&quot;

    await query.answer()
    if msg:
        await query.edit_message_text(text=msg, parse_mode=constants.ParseMode.HTML)
        await query.message.reply_text(&quot;Please choose:&quot;, reply_markup=reply_markup_main)
    else:
        await query.edit_message_text(&quot;Please choose:&quot;, reply_markup=reply_markup_main)
</pre>
  <p id="l0i7">Теперь допишем обработчкики и стартанем наш скрипт</p>
  <pre id="mQOU" data-lang="python">def main() -&gt; None:
    application = Application.builder().token(BOT_TOKEN).build()
    application.add_handler(CommandHandler(&quot;start&quot;, start))
    application.add_handler(CallbackQueryHandler(button))
    application.run_polling()

if __name__ == &quot;__main__&quot;:
    main()</pre>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@chelovek86/rsp_game_etherium</guid><link>https://teletype.in/@chelovek86/rsp_game_etherium?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86</link><comments>https://teletype.in/@chelovek86/rsp_game_etherium?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=chelovek86#comments</comments><dc:creator>chelovek86</dc:creator><title>ПИШЕМ БЛОКЧЕЙН ИГРУ НА SOLIDITY</title><pubDate>Wed, 25 May 2022 11:47:50 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/47/d0/47d014dd-cca3-4044-974b-7d25e87e5c3b.png"></media:content><description><![CDATA[<img src="https://4trading.app/wp-content/uploads/2022/05/smartcontract2-1024x555.jpg"></img>Напишем смарт-контракт всем известной игры «Камень, ножницы, бумага». Контракт будет иметь свой нативный токен, которым будут вестись все расчеты между участниками, игра будет на внутренние токены, смарт-контракт напишем для сети Polygon исключительно из соображений экономии комиссии за деплой контракта, это обойдется нам меньше 1$, в то время как в Ethereum комса встать может нам в 200$.]]></description><content:encoded><![CDATA[
  <figure id="6qRw" class="m_column">
    <img src="https://4trading.app/wp-content/uploads/2022/05/smartcontract2-1024x555.jpg" width="1024" />
  </figure>
  <p id="VEbC">Напишем смарт-контракт всем известной игры «Камень, ножницы, бумага». Контракт будет иметь свой нативный токен, которым будут вестись все расчеты между участниками, игра будет на внутренние токены, смарт-контракт напишем для сети Polygon исключительно из соображений экономии комиссии за деплой контракта, это обойдется нам меньше 1$, в то время как в Ethereum комса встать может нам в 200$.</p>
  <p id="cj2F">Тут мы напишем сам смарт-контракт без его последующей интеграции в какой-то UI, то-есть взаимодействовать с ним можно будет только по средствам вызова нужных функций. Часть где мы будем использовать данный смарт контракт вместе с например телеграм ботом или какой-то вебкой я опишу отдельно.</p>
  <p id="xU1z"><strong>Вкратце о контракте</strong></p>
  <p id="HNxm">Будем наследоваться от <a href="https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20" target="_blank">смарт-конракта токена ERC20</a> предоставленным командой OpenZeppelin. Будет функции установки стоимости игры, просмотр истории игр, с каждого купленного взноса будет удерживаться 10% на джекпот или комсу) по итоку победитель возвращает <em>ticket price * 2 player * 0.8</em>.</p>
  <p id="IlbS">Сам контракт можно писать в разных IDE, например в VSCode, но лично я предпочитаю <a href="https://remix.ethereum.org/" target="_blank">remix</a>, это online IDE в которой можно сразу деплоить наш контракт как в тестнет так и мэиннет, дебажить и использовать разные версии компилятора.</p>
  <p id="YKqq">Создадим новый файл RSP.sol, и пропишем версию solidity, импортируем контракт ERC20 для нашего нативного токена и создадим контракт унаследовав ERC20</p>
  <pre id="J5Qp">// SPDX-License-Identifier: MIT

pragma solidity ^0.8.11;

import &quot;https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol&quot;;

contract RSP is ERC20 {
}</pre>
  <p id="YHEp">Объявим наши переменные для дальнейшей работы</p>
  <pre id="uCHJ">// Список из сыгранных игр
mapping(uint256 =&gt; _game[]) private _gameHistory;

// Кол-во выпущенных токенов
uint256 private _totalSupply;
// Идентификатор актуальной игры
uint256 private _gameId;
// Кубышка
uint256 private _jackPot;
// Стоимость одной игры
uint256 private _gameCost;
// 10**18 для упрощения
uint256 private _x;

// Действие игрока
int8[] private _playerAction;

// Список адресов игроков
address payable[] _players;
// Владелец контракта
address private _contractOwner;

// Название токена
string private _name;
// Короткое символичное название токена
string private _symbol;

// Событие, которое будет вызываться при входе участника
event NumberPlay(uint256 number);

// Структура в срезе 1 игры, для истории
struct _game {
  address player_1;
  address player_2;
  int8 player_1_action;
  int8 player_2_action;
  int8 player_win;
}</pre>
  <p id="cV7W">Создадим конструктор, по сути это функция которая выполняется один раз при деплое смарт-контракта и полезна нам тем, что в ней мы задами стартовые дефолтные настройки для игры, так же там укажем нашего владельца контракта, который в дальнейшем сможет, например изменить стоимость входа в игру.<br />И заминтим от общего кол-ва токенов, а это у нас 1000 RSP токенов, на два адреса. Первый адрес контракта на него мы выпустим 20% от общего кол-ва, а второй адрес, это адрес владельца контракта, на него мы отправим 80% всех токенов RSP.</p>
  <pre id="T0oO">constructor() ERC20 (&quot;Rock Scissors Paper&quot;, &quot;RSP&quot;){
        _x = 10 ** 18;
        _totalSupply = 1000 * _x;
        _jackPot = 0;
        _gameCost = 3 * _x;
        _gameId = 1;
        _contractOwner = msg.sender;

        _mint(address(this), _totalSupply/100*20);
        _mint(_contractOwner, _totalSupply/100*80);
 
    }</pre>
  <p id="Ww5C">Данный контракт можно уже задеплоить в сеть, и обнаружить что на наш адрес кошелька уже упали наши токены, но наша ведь задача не в этом, поэтому продолжаем накидывать г*вно на вентилятор и расширять наш функционал.</p>
  <p id="7PIu">Создадим простые геттеры, чтобы можно было получить из нашего будущего приложения какие-то данные, например стоимость игры или историю игр, да на крайний случай проверить себя в списке игроков в случае участия по идентификатору игры. Все функции имеют public модификатор, что дает возможность делать запросы из вне.</p>
  <pre id="GuPs">/**
* @dev Returns the game identificator 
*/
function gameId() public view returns (uint256){
    return _gameId;
}

/**
* @dev Returns the total jackpot pot 
*/
function jackPot() public view returns (uint256){
    return _jackPot;
}

/**
* @dev Returns the game cost 
*/
function gameCost() public view returns (uint256){
    return _gameCost;
}    
/**
* @dev Returns current players 
*/
function players() public view returns(address payable[] memory) {
    return _players;
}

/**
* @dev Returns the game history by id
*/
function gameHistory(uint256 id) public view returns (_game[] memory) {
    return _gameHistory[id];
}</pre>
  <p id="Z8wn">Возможно нам потребуется изменить стоимость игры, не создавать же нам новый смарт-контракт для этого. Напишем сеттер для стоимости входного билета</p>
  <pre id="ZKVH">/**
* @dev Set the game cost 
* Integer must beetwen 1 and 999
*/
function setGameCost(uint256 cost) public onlyContractOwner returns (bool){
    require(cost &gt;= 1 &amp;&amp; cost &lt; 1000, &quot;Invalid cost, 1-999&quot;);
    _gameCost = cost * _x;
    return true;
}</pre>
  <p id="xxsg">Теперь напишем функцию приема платежа за игру и передачи в нее, собственно, действия, а действий у нас 3: это камень, ножницы или бумага.<br />В данную функцию будут передаваться 2 параметра, это сумма и действие. Из проверок после вызова это то, что передаваемая сумма соответствует стоимости билета, нам лишнего не надо, но и если денег нет — простите). Так же проверка на передаваемое действие, ограничимся условными 1, 2 или 3. После на адрес контракта от игрока мы переводим стоимость игры заданную ранее, и проверяем на количество игроков на данный момент в принимающих участие. Наша игра подразумевает 2 игрока, поэтому когда игрок только 1, то необходимо просто выйти из функции зарегистрировав игрока. В случае, если вы уже второй игрок, то логично было бы сразу инициализировать процесс выбора победителя и расчета ревардов.</p>
  <pre id="51Sq">/**
    * @dev See {IERC20-transfer}.
    *
    * Requirements:
    *
    * - the caller must have a balance of at least &#x60;amount&#x60;.
    */
function transferPerGame(uint256 amount, int8 action) external returns (uint256) {
    require(amount == gameCost(), &quot;Sent token are not equal to the cost of the game&quot;);
    require(0 &lt; action &amp;&amp; action &lt; 4, &quot;Invalid value for action (1-3)&quot;);

    address to = address(this);
    address owner = _msgSender();
    uint256 curGameId = gameId();
    _transfer(owner, to, amount);
    if (players().length &lt; 2) {  
        _players.push(payable(owner));
        _playerAction.push(action);
        if (players().length == 2) {
            int8 indexWinner = calculateWinner(_playerAction[0], _playerAction[1]);
            _gameHistory[curGameId].push(_game(_players[0],_players[1],_playerAction[0],_playerAction[1],indexWinner));
            setWinner(indexWinner);
        }
    }
    emit NumberPlay(curGameId);
    return curGameId;
}</pre>
  <p id="atNs">Реализуем функцию calculateWinner(), которая будет рассчитывать кто же все таки победил в этом раунде. Передаем в нее действие первого игрока и действие второго игрока, а дальше все просто:<br /><em>— камень бьет ножницы— ножницы бьют бумагу— бумага бьет камень</em></p>
  <p id="c05u">и возвращаем число 0 в случае победы игрока под номером 1, число 1 в случае победы игрока под номером 2, ну а когда будет ничья мы будем возвращать -1</p>
  <pre id="UmCA">/* @dev calculate the winner
* Requirements:
*   - Action of the first player 
*   - Action of the second player
*   1 - Stone | 2 - Scissor | 3 - Paper
*   1 =&gt; 2 =&gt; 3 =&gt; 1 =&gt; 2 =&gt; 3
*/
function calculateWinner(int8 p1, int8 p2) private pure returns(int8) {
    // 1 - k; 2 - n; 3 - b
    if (p1 &gt; p2){
        //return (p1 - p2) &gt; 1 ? 0 : 1;
        if ((p1 - p2) &gt; 1){
            return 0;
        }
        else {
            return 1;
        }
    }
    else if (p1 &lt; p2){
        //return (p2 - p1) &gt; 1 ? 1 : 0;
        if ((p2 - p1) &gt; 1) {
            return 1;
        }
        else {
            return 0;
        }
    }
    return -1;
}</pre>
  <p id="aFHt">После того как определили победителя у нас идет вызов функции распределения ревардов setWinner(). Если в данной игре есть победитель, то на его счет упадут токены рассчитанные по формуле <em>(gameCost() * 2) / 100 * 90</em>, это 90% от стоимости двух входных билетов, оставшиеся 10% летят в наш банк, можно реализовать супер лотерею на которой будет один или несколько счастливчиков, либо оставить в виде комиссии. Реализации в данном контексте нет, остаток просто оседает на адресе контракта. В случае когда победителей нет, мы честно возвращаем стоимости билетов без издержек.</p>
  <pre id="EQ9F">/* @dev calculate the prize and transfer to the participants
*
*/
function setWinner(int indexWinner) private {

    if (indexWinner &gt;= 0) {
        _jackPot = jackPot() + ((gameCost() * 2) / 100 * 10 );
        uint256 reward = (gameCost() * 2) / 100 * 90;
        if (indexWinner == 0) {
            _transfer(address(this),_players[0], reward);
        }
        else if (indexWinner == 1) {
            _transfer(address(this),_players[1], reward);
        }
    }
    else if (indexWinner &lt; 0) {
        _transfer(address(this),_players[0], gameCost());
        _transfer(address(this),_players[1], gameCost());
    }

    _gameId++;
    _players = new address payable[](0);
    _playerAction = new int8[](0);
}</pre>
  <p id="j473">Напоследок создадим модификатор. Модификатор хорош тем что мы в нем можем прописать кучу различных условий и применить к функции не засоряя код и делая его более френдли. Данный модификатор как пример мы используем на функции установки стоимости за участие в игре, к функции после модификатора public мы добавим название своего модификатора onlyContractOwner. По сути при вызове функции установки стоимости будет проверяться является ли адрес владельцем контракта, но вы можете прописать любые правила.</p>
  <pre id="WHsH">modifier onlyContractOwner() {
    require(_contractOwner == _msgSender());
    _;
}</pre>
  <p id="vY56">Наш смарт-контракт готов, осталось сделать деплой и проверить все.</p>
  <p id="n4R9">Код на <a href="https://github.com/shmeller86/kmn_game" target="_blank">github</a> </p>

]]></content:encoded></item></channel></rss>