- PVSM.RU - https://www.pvsm.ru -
Сегодня платформа Ethereum стала одним из самых узнаваемых брендов блокчейн сферы, вплотную приблизившись по популярности (и капитализации) к Bitcoin. Но из-за отсутствия "полноценного" рускоязычного гайда, отечественные разработчики все еще не очень понимают, что это за зверь и как с ним работать. Поэтому в данной статье я попытался максимально подробно охватить все аспекты разработки умных контрактов под Ethereum.
Я расскажу про инструменты разработки, сам ЯП, процесс добавления UI и еще много интересного. В конечном итоге мы получим обычный сайт-визитку, но "под капотом" он будет работать на умных контрактах Ethereum. Кого заинтересовало — прошу под кат.
Эта статья не расчитана на тех, кто совсем не знаком с Ethereum (или технологией блокчейн вообще), поэтому объяснений базовых вещей вроде блоков, транзакций или контрактов здесь не будет. Я подразумеваю, что вы хотя бы чуть-чуть в курсе происходящего. В противном случае полистайте статьи из списка ниже, а потом возвращайтесь :)
Больше ссылок на интересные статьи вы найдете в конце.
P.S. Я работаю под Ubuntu 16.04, так что весь процесс установки, разработки и деплоя будет описан под эту ОС. Тем не менее все используемые инструменты кроссплатформенны (скорее всего, не проверял), так что при желании можете поэкспериментировать на других ОС.
Работа с Ethereum возможна через огромное число клиентов, часть из которых terminal-based, часть GUI и есть несколько гибридных решений. Своего рода стандартом является [Geth](), который разрабатывается командой Ethereum. Про него я уже писал в предыдущих статьях [27], но на всякий случай повторюсь.
Клиент написан на Go, устанавливается стандартным способом [36]:
sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum
Сам Geth не имеет GUI, но работать с ним из терминала довольно приятно. Здесь [37] описан весь набор аргументов командной строки, я же опишу несколько самых популярных.
Вот команда, которую я чаще всего использую в работе: $ geth --dev --rpc --rpcaddr "0.0.0.0" --rpcapi "admin,debug,miner,shh,txpool,personal,eth,net,web3" console
--dev
запускает geth в режиме приватного блокчейна, то есть не синхронизирет основную / тестовую ветку. Вместо этого вы получаете стерильную цепочку без единого блока. Это самый удобный вариант в плане разработки, так как, например, майнинг блока занимает несколько секунд и нет никакой нагрузки на сеть или диск.
--rpc
включает RPC-HTTP сервер. По сути это API к вашей ноде — через него сторонние приложения, вроде кошельков или IDE, смогут работать с блокчейном: загружать контракты, отправлять транзакции и так далее. По дефолту запускается на localhost:8545 [38], можете изменить эти параметры с помощью --rpcaddr
и --rpcport
соответственно.--rpcapi
устанавливает что-то вроде прав доступа для приложений, подключенных к RPC серверу. Например, если вы не укажете "miner"
, то, подключив к ноде кошелек и запустив майнер, вы получите ошибку. В примере я указал все возможные права, подробнее можете почитать здесь [39].console
— как можно догадаться, эта опция запускает консоль разработчика. Она поддерживает самый обычный JS и ряд встроенных функций для работы с Ethereum, вот простой пример [27] (пункт — Поднимаем ноду).Geth довольно хорош, но в последнее время все чаще можно встретить другой клиент — Parity, написанный на Rust. Главным его отличием от Geth является встроенный web интерфейс, на мой взгляд, самый удобный среди всех ныне существующих. Установка:
sudo <(curl https://get.parity.io -Lk)
По окончании загрузки запустите в консоли parity
и по адресу localhost:8180 [41] можете найти сам кошелек.
Еще один плюс: Parity быстрее своих конкурентов. По крайней мере так утверждают авторы, но по моим ощущениям это действительно так, особенно в плане синхронизации блокчейна.
Единственный нюанс — своей консоли в parity нет. Но можно без проблем использовать для этих целей Geth:
$ parity --geth # Run parity in Geth mode
$ geth attach console # Attach Geth to the PArity node (Do it in another window)
Этот инструмент, в отличие от предыдущих, будет полезен только разработчикам. Он позволяет одной командой testrpc
поднять приватный блокчейн с включенным RPC протоколом, десятком заранее созданных аккаунтов с этерами на счету, работающим майнером и так далее. Весь список здесь [43]. По сути, testrpc
— это тот же geth --dev --rpc ...
, только на этот раз не надо тратить время на создание аккаунтов, включение / выключение майнера и прочие рутинные действия.
Установка — npm install -g ethereumjs-testrpc
.
Самый популярный кошелек для Ethereum, хотя на самом деле он умеет намного больше. Вот отличная статья [45], где step-by-step объясняется весь процесс работы с Mist. Скачать самую свежую версию можно со страницы релизов [46]. Помимо работы с кошельком, есть возможность работы с контрактами.
Самая популярная IDE для разработки контрактов. Работает в браузере по адресу ethereum.github.io/browser-solidity/ [48], поддерживает огромное число функций:
При этом нет автокомплита, что очень печально.
Еще одна IDE для разработки умных контрактов, написана на Meteor, работает из коробки. Для начала откройте новый терминал и поднимите ноду с включенным RPC интерфесом geth --rpc --rpcapi="db,eth,net,web3,personal" --rpcport "8545" --rpcaddr "127.0.0.1" --rpccorsdomain "localhost" console
. После этого можете запускать саму IDE:
$ git clone http://github.com/SilentCicero/meteor-dapp-cosmo.git
$ cd meteor-dapp-cosmo/app
$ meteor
Далее открываете localhost:3000 [51] и можете начинать работать:
Последний на сегодня инструмент для ускорения разработки умных контрактов. Это плагин для редактора Atom, устанавливается с помощью apm install atom-ethereum-interface
. Штука удобная, сам пользуюсь. Позволяет работать c JS EVM или подключиться к ноде через RPC. Компилирует контракт на CTRL + ALT + C
, деплоит в сеть на CTRL + ALT + S
. Ну и предоставляет неплохой интерфейс для работы с самим контрактом.
Если вам не нужен такой навороченный функционал внутри редактора, то для Atom есть отдельный плагин с подсветкой синтаксиса Solidity — language-ethereum [53]. Последний по сути является плагином под Sublime text [54], только конвертированный для работы в Atom.
Возможно, вы слышали про то, что можно писать контракты не только на Solidity, но и на других языках, например Serpent [55] (внешне напоминает Python). Но последний комит в develop ветке ethereum/serpent [56] был примерно полгода назад, так что, по-видимому, язык, увы, deprecated.
Поэтому писать будем только на Solidity. Пока что язык находится на относительно раннем этапе развития, так что никаких сложных конструкций или уникальных абстракций в нем нет. Поэтому отдельно рассказывать про него я не вижу смысла — любой человек с опытом в программировании сможет свободно писать на нем после 20 минут чтения документации [57]. На случай, если у вас такого опыта нет, — ниже я довольно подробно прокомментировал весь код контракта.
Для самостоятельного обучения есть несколько очень хороших примеров с максимально подробными описаниями:
Еще раз отмечу (отличную!) документацию [57] языка, местами [61] даже переведена на русский язык.
Самое время создать наш контракт. В конечном итоге это будет приложение-визитка, на которую мы поместим само "резюме":
Первым делом создадим шаблон контракта и функцию-конструктор. Она должна называться также как и сам контракт и вызывается лишь однажды — при загрузке контракта в блокчейн. Мы будем использовать ее для инициализации одной единственной переменной — address owner
. Как вы уже наверное догадались, в нее будет записан адрес того, кто залил контракт в сеть. А использоваться она будет для реализации функций администратора контракта, но об этом позже.
pragma solidity ^0.4.0;
contract EthereumCV is Structures {
address owner;
// =====================
// ==== CONSTRUCTOR ====
// =====================
function EthereumCV() {
owner = msg.sender;
}
}
Следующим шагом добавим возможность указывать базовую информацию об авторе — имя, почту, адрес и так далее. Для этого будем использовать самый обычный mapping
, который нужно объявить в начало контракта:
address owner;
mapping (string => string) basic_data;
Для того, чтобы иметь возможность "получать" от контракта эти данные, создадим следующую функцию:
function getBasicData (string arg) constant returns (string) {
return basic_data[arg];
}
Здесь все просто, стоит только отметить модификатор constant
— его можно (и нужно) использовать для тех функций, которые не изменяют state приложения. Главный плюс таких функций (sic!), в том что их можно использовать как обычные функции.
Теперь стоит задуматься о наполнении своего резюме контентом. В самом простом случае мы могли бы обойтись функцией вроде
function setBasicData (string key, string value) {
basic_data[key] = value;
}
Но в этом случае любой при желании смог бы изменить, например, наше имя, вызвав setBasicData("name", "New Name")
. К счастью, есть способ всего в одну строку пресечь любые такие попытки:
function setBasicData (string key, string value) {
if (msg.sender != owner) { throw; }
basic_data[key] = value;
}
Так как нам еще не раз придется использовать подобную конструкцию (при добавлении нового проекта, например), то стоит создать специальный модификатор:
modifier onlyOwner() {
if (msg.sender != owner) { throw; }
_; // Will be replaced with function body
}
// Now you can use it with any function
function setBasicData (string key, string value) onlyOwner() {
basic_data[key] = value;
}
При желании, можно использовать другие способы авторизации, например по паролю. Хэш будет храниться в контракте и сравниваться с введенным при каждом вызове функции. Но понятно, что этот способ не такой безопасный, благо радужные таблицы и атаки по словарю никто не отменял. С другой стороны, наш способ тоже не идеален, так как если вы потеряете доступ к адресу owner
, то ничего редактировать вы уже не сможете.
Следующим шагом создадим несколько структур для описания проектов, образования, навыков и публикаций. Здесь все просто, структуры описываются точно так же как в Си. Но вместо того, чтобы описывать их в текущем контракте, вынесем их в отдельную блиблиотеку (в новом файле). Тем самым мы сможем избежать огромных простыней кода и структурировать наш проект.
Для этого в той же директории создадим новый файл structures.sol
и библиотеку Structures
. А уже внутри нее опишем каждую из структур:
pragma solidity ^0.4.0;
library Structures {
struct Project {
string name;
string link;
string description;
}
struct Education {
string name;
string speciality;
int32 year_start;
int32 year_finish;
}
struct Publication {
string name;
string link;
string language;
}
struct Skill {
string name;
int32 level;
}
}
Теперь осталось только импортировать полученный файл
pragma solidity ^0.4.0;
import "./structures.sol";
contract EthereumCV {
mapping (string => string) basic_data;
address owner;
Structures.Project[] public projects;
Structures.Education[] public educations;
Structures.Skill[] public skills;
Structures.Publication[] public publications;
// ...
}
Самые сообразительные уже догадались, что нотация Structures.Project[] projects
означает создание динамического массива с элеметнами типа Project
. А вот с модификатором public
уже сложнее. По сути, он заменяет нам написание функции вроде get_project(int position) { return projects[position]; }
— компилятор сам создаст такую функцию. Называться она будет так же как и переменная, в нашем случае — projects
.
Вы можете спросить — почему мы в самом начале не написали mapping (string => string) public basic_data
, а вместо этого сами создавали такую функцию? Причина банальна — public
пока что не умеет работать c переменными, для которых ключом является динамический тип данных (string
именно такой тип).
Unimplemented feature (/src/libsolidity/codegen/ExpressionCompiler.cpp:105): Accessors for mapping with dynamically-sized keys not yet implemented.
Для этого нужно объявлять basic_data
как например mapping (bytes32 => string)
.
BTW На всякий случай отмечу, что кроме локального файла, Remix умеет импортировать .sol
файлы по ссылке на Github и даже с помощью протокола Swarm [62] (это что-то вроде распределенного хранилища для Ethereum, подробнее здесь [63])
Думаю многие из вас уже сами догадались, как стоит реализовать работу с новыми данными. Покажу на примере списка публикаций, в остальных случаях все аналогично:
function editPublication (bool operation, string name, string link, string language) onlyOwner() {
if (operation) {
publications.push(Structures.Publication(name, link, language));
} else {
delete publications[publications.length - 1];
}
}
С помощью параметра operation
мы избавились от написания отдельной функции для удаления последней публикации (костыльно, но мы ведь только учимся). Хотя нужно отметить, что такой способ избавления от элемента в массиве на самом деле не совсем корректный. Сам элемент конечно будет удален, но на месте индекса останется пустое место. В нашем случае это не смертельно (мы будем проверять пустоту отдельных элементов на стороне клиента), но, вообще говоря, про это не стоит забывать. Тем более что сдвинуть весь массив и уменьшить счетчик длины не так уж сложно [64].
Как я уже сказал, модификатор public
в строке Project[] public projects
обеспечил нас функцией которая по индексу i
вернет проект projects[i]
. Но мы не знаем, сколько у нас всего проектов, и здесь есть два пути. Первый — итерироваться по i
до того момента, пока мы не получим ошибку о несуществующем элементе. Второй — написать отдельную функцию, которая вернет нам размер projects
. Я пойду вторым путем, чуть позже скажу почему:
function getSize(string arg) constant returns (uint) {
if (sha3(arg) == sha3("projects")) { return projects.length; }
if (sha3(arg) == sha3("educations")) { return educations.length; }
if (sha3(arg) == sha3("publications")) { return quotes.length; }
if (sha3(arg) == sha3("skills")) { return skills.length; }
throw;
}
Заметьте, что мы не можем сравнить две строки привычным способом 'aaa' == 'bbb'
. Причина все та же, string
— это динамический тип данных, работа с ними довольно болезненна. Так что остается либо сравнивать хэши, либо использовать функцию для посимвольного сравнения. В этом случае можете использовать популярную библиотеку stringUtils.sol [65], в ней есть такая функция.
В разных средах разработки процесс компиляции и деплоя разумеется отличается, поэтому я ограничусь Remix, как самым популярным.
Сначала, само собой, заливаем весь код (финальную версию можете найти в репозитории проекта [66]). Далее в выпадающем списке Select execution environment выберите Javascript VM
— пока что протестируем контракт на JS эмуляторе блокчейна, чуть позже научимся работать и с настоящим. Если с контрактом все в порядке, то вам будет доступна кнопка Create — нажимаем и видим:
Теперь, когда контракт залит в блокчейн (его эмуляцию, но не суть), можем попробовать вызвать какую-нибудь функцию и посмотреть, что из этого выйдет. Например можно сохранить в контракте email — для этого найдите функцию setBasicData
, заполните поле и нажмите кнопку с именем функции:
Функция ничего не возвращает, поэтому result: 0x
. Теперь можно запросить у контракта email: ищем функцию getBasicData
и пробуем:
С остальными функциями предлагаю вам поэксперементировать самим.
Ниже я расскажу про самый распостраненный способ добавить UI к вашему контракту. Он позволяет с помощью JS и HTML создавать интерфейсы любой сложности, достаточно иметь доступ к рабочей ноде Ethereum (или ее аналогам).
This is the Ethereum compatible JavaScript API [68] which implements the Generic JSON RPC [69] spec. It's available on npm as a node module, for bower and component as an embeddable js and as a meteor.js package.
Это JS библиотека, позовляющая использовать API Ethereum с помощью обычного JS. По сути с ее помощью вы просто подключаетесь ноде и у вас появляется что-то вроде консоли geth в браузере. Устанавливается через npm
или bower
:
$ sudo npm install web3
$ bower install web3
Вот пример работы с web3 через node.js (предварительно запустите testrpc
или любую другую ноду с RPC интерфейсом):
$ node
> var Web3 = require('web3');
> var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
> web3.eth.accounts
[ '0x5f7aaf2199f95e1b991cb7961c49be5df1050d86',
'0x1c0131b72fa0f67ac9c46c5f4bd8fa483d7553c3',
'0x10de59faaea051b7ea889011a2d8a560a75805a7',
'0x56e71613ff0fb6a9486555325dc6bec8e6a88c78',
'0x40155a39d232a0bdb98ee9f721340197af3170c5',
'0x4b9f184b2527a3605ec8d62dca22edb4b240bbda',
'0x117a6be09f6e5fbbd373f7f460c8a74a0800c92c',
'0x111f9a2920cbf81e4236225fcbe17c8b329bacd7',
'0x01b4bfbca90cbfad6d6d2a80ee9540645c7bd55a',
'0x71be5d7d2a53597ef73d90fd558df23c37f3aac1' ]
>
Тоже самое, только из JS консоли браузера (не забудьте про <script src="path_to/web3.js"></script>
)
То есть мы уже на этом моменте можем запустить ноду, синхронизировать ее с текущей цепочкой и останется только сверстать наше приложение. Но тут есть два тонких момента: во-первых, вам нужно синхронизировать блокчейн Ethereum, а вы этого скорее всего до сих пор не сделали.
Второй нюанс — RPC не имеет никакого встроенного механизма авторизации, поэтому любой желающий может узнать адрес вашей ноды из исходников JS и пользоваться ей в свое удовольствие. Тут конечно можно писать какую-нибудь обертку на Nginx с простейшей HTTP basic auth, но это как-нибудь в другой раз.
Поэтому сейчас мы воспользуемся плагином Metamask (увы, только для Chrome). По сути это и есть та прослойка между нодой и браузером, которая позволит вам использовать web3 в браузере, но без своей ноды. Metamask работает очень просто — в каждую страницу он встраивает web3.js, который автоматически подключается к RPC серверам Metamask. После этого вы можете использовать Ethereum на полную катушку.
После установки плагина, в левом верхнем углу выберите Testnet
и получите несколько эфиров на кране Metamask [71]. На этом моменте вы должны получить что-то вроде такого (с чистой историей разумеется):
С Metamask задеплоить контракт в сеть так же просто, как и в случаем с JS EVM. Для этого снова открываем Remix и в списке Select execution environment выбираем пункт Injected Web3
(скорее всего он выбран автоматически). После этого нажимаем Create и видим всплывающее окно:
Чуть позже надпись Waiting for transaction to be mined..
. сменится на информацию об опубликованном контракте — это значит что он попал в блокчейн. Адрес контракта можете узнать, открыв Metamask и нажав на запись вида:
Однако теперь, если вы захотите, например, вызвать функцию editProject(...)
, то вам так же придется подтвержать транзакцию и ждать, пока она будет замайнена в блок.
Теперь дело за малым — надо научиться получать данные от контракта через Web3. Для этого, во-первых, надо научиться определять наличие web3 на странице:
window.addEventListener('load', function() {
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider
console.log("Web3 detected!");
window.web3 = new Web3(web3.currentProvider);
// Now you can start your app & access web3 freely:
startApp()
} else {
alert('Please use Chrome, install Metamask and then try again!')
}
})
Внутри startApp()
я определелил всю логику работы с контрактом, тем самым избегая ложных срабатываний и ошибок.
function startApp() {
var address = {
"3" : "0xf11398265f766b8941549c865d948ae0ac734561" // Ropsten
}
var current_network = web3.version.network;
// abi initialized ealier, in abi.js
var contract = web3.eth.contract(abi).at(address[current_network]);
console.log("Contract initialized successfully")
contract.getBasicData("name", function(error, data) {
console.log(data);
});
contract.getBasicData("email", function(error, data) {
console.log(data);
});
contract.getSize("skills", function(error, data) {
var skills_size = data["c"][0];
for (var i = 0; i < skills_size; ++i) {
contract.skills(i, function(error, data) {
// Don't forget to check blank elements!
if (data[0]) { console.log(data[0], data[1]["c"][0]); }
})
}
})
}
Теперь, когда вы со всем разобрались, можно браться за верстку и JS. Я использовал Vue.js [72] и Spectre.css [73], для визуализации навыков добавил Google Charts [74]. Результат можете увидеть на pavlovdog.github.io [75]:
Только что вы увидели, как можно довольно быстро создать приложение, которое самым непосредственным образом использует технологию blockchain. Хотя в погоне за простотой (все таки это обучающая статья) я допустили некоторые упрощения, которые по-хорошему допускать нельзя.
Например, мы используем чей-то шлюз (я про Metamask), вместо того, чтобы работать со своей нодой. Это удобно, но технология блокчейн в первую очередь — децентрализация и отсутствие посредников. У нас же всего этого нет — мы доверяем парням из Metamask.
Другая, не такая критичная проблема, — мы забыли про стоимость деплоя контрактов и транзакций к ним. На практике, стоит десять раз подумать, прежде чем использовать string
вместо bytes
, потому как такие вещи прежде всего влияют на затраты при работе с контрактом. Опять же, в примере я использовал Testnet
, так что никаких денег мы не потратили, но при работе с Main net
не стоит быть такими расточительными.
В любом случае, я надеюсь что статья оказалась полезной, если есть вопросы — задавайте в комментариях или пишите мне на почту.
Автор: Сергей
Источник [83]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/253981
Ссылки в тексте:
[1] Введение в Ethereum: #vvedenie-v-ethereum
[2] Инструменты: #instrumenty
[3] Geth: #gethhttpsethereumgithubiogo-ethereum
[4] Parity: #parityhttpsparityio
[5] TestRPC: #testrpchttpsgithubcomethereumjstestrpc
[6] Mist: #misthttpsgithubcomethereummist
[7] Remix: #remixhttpsethereumgithubiobrowser-solidity
[8] Cosmo: #cosmohttpsgithubcomcosmo-projectmeteor-dapp-cosmo
[9] Etheratom: #etheratomhttpsgitlabcom0mkaraetheratom
[10] Solidity: #solidity
[11] Создаем контракт-визитку: #sozdaem-kontrakt-vizitku
[12] Первый шаг: #pervyy-shag
[13] Базовая информация: #bazovaya-informaciya
[14] Администрирование: #administrirovanie
[15] Модульность: #modulnost
[16] Загружаем и удаляем данные: #zagruzhaem-i-udalyaem-dannye
[17] Отдаем данные: #otdaem-dannye
[18] Деплой: #deploy
[19] Добавляем UI: #dobavlyaem-ui
[20] Web3.js: #web3jshttpsgithubcomethereumweb3js
[21] Metamask: #metamaskhttpsmetamaskio
[22] Deploy with Metamask: #deploy-with-metamask
[23] Пример: #primer
[24] Итог: #itog
[25] Вместо заключения: #vmesto-zaklyucheniya
[26] Ссылки: #ssylki
[27] RU — Пишем умный контракт на Solidity. Часть 1 — установка и «Hello world»: https://habrahabr.ru/post/312008/
[28] RU — Malware + Blockchain = ️: https://habrahabr.ru/post/313710/
[29] RU — Как мы делали первую сделку-аккредитив на блокчейн в Альфа-Банке: https://habrahabr.ru/company/alfa/blog/323070/
[30] EN — A 101 Noob Intro to Programming Smart Contracts on Ethereum: http://consensys.github.io/developers/articles/101-noob-intro/
[31] EN — Ethereum for web developers: https://medium.com/@mvmurthy/ethereum-for-web-developers-890be23d1d0c
[32] EN — Building a smart contract using the command line: https://www.ethereum.org/greeter
[33] EN — Create your own crypto-currency: https://www.ethereum.org/token
[34] EN — Dapps for Beginners: https://dappsforbeginners.wordpress.com/
[35] Geth: https://ethereum.github.io/go-ethereum/
[36] стандартным способом: https://www.ethereum.org/cli
[37] Здесь: https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options
[38] localhost:8545: http://localhost:8545
[39] здесь: https://github.com/ethereum/go-ethereum/wiki/Management-APIs
[40] Parity: https://parity.io/
[41] localhost:8180: http://localhost:8180
[42] TestRPC: https://github.com/ethereumjs/testrpc
[43] здесь: https://github.com/ethereumjs/testrpc#usage
[44] Mist: https://github.com/ethereum/mist
[45] отличная статья: https://medium.com/@attores/step-by-step-guide-getting-started-with-ethereum-mist-wallet-772a3cc99af4
[46] страницы релизов: https://github.com/ethereum/mist/releases
[47] Remix: https://ethereum.github.io/browser-solidity/
[48] ethereum.github.io/browser-solidity/: http://ethereum.github.io/browser-solidity/
[49] многое другое: https://github.com/ethereum/remix
[50] Cosmo: https://github.com/cosmo-project/meteor-dapp-cosmo
[51] localhost:3000: http://localhost:3000
[52] Etheratom: https://gitlab.com/0mkara/etheratom
[53] language-ethereum: https://atom.io/packages/language-ethereum
[54] плагином под Sublime text: https://packagecontrol.io/packages/Ethereum
[55] Serpent: https://github.com/ethereum/wiki/wiki/Serpent
[56] ethereum/serpent: https://github.com/ethereum/serpent/tree/develop
[57] документации: https://solidity.readthedocs.io/en/develop/
[58] Voting contract: http://solidity.readthedocs.io/en/develop/solidity-by-example.html#voting
[59] Blind Auction: http://solidity.readthedocs.io/en/develop/solidity-by-example.html#blind-auction
[60] Safe Remote Purchase: http://solidity.readthedocs.io/en/develop/solidity-by-example.html#safe-remote-purchase
[61] местами: https://github.com/ethereum/wiki/wiki/%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity
[62] с помощью протокола Swarm: https://www.reddit.com/r/ethereum/comments/5vpm53/remix_can_now_import_files_via_swarm/
[63] здесь: http://ethereum.stackexchange.com/questions/375/what-is-swarm-and-what-is-it-used-for
[64] не так уж сложно: http://ethereum.stackexchange.com/questions/1527/how-to-delete-an-element-at-a-certain-index-in-an-array
[65] stringUtils.sol: https://github.com/ethereum/dapp-bin/blob/master/library/stringUtils.sol
[66] репозитории проекта: https://github.com/pavlovdog/dive_into_ethereum
[67] Web3.js: https://github.com/ethereum/web3.js
[68] JavaScript API: https://github.com/ethereum/wiki/wiki/JavaScript-API
[69] Generic JSON RPC: https://github.com/ethereum/wiki/wiki/JSON-RPC
[70] Metamask: https://metamask.io/
[71] кране Metamask: https://faucet.metamask.io/
[72] Vue.js: https://vuejs.org
[73] Spectre.css: https://picturepan2.github.io/spectre/
[74] Google Charts: https://developers.google.com/chart/
[75] pavlovdog.github.io: https://pavlovdog.github.io/
[76] MetaMask: https://medium.com/metamask/metamask-ff7d3571f331
[77] Learning Solidity Part 1: Contract Dev with MetaMask: https://karl.tech/learning-solidity-part-1-deploy-a-contract/
[78] Learning Solidity Part 2: Commit-Reveal Voting: https://karl.tech/learning-solidity-part-2-voting/
[79] How to use MetaMask: https://www.cryptocompare.com/wallets/guides/how-to-use-metamask/
[80] Building an “Oracle” for an Ethereum contract: https://medium.com/@mustwin/building-an-oracle-for-an-ethereum-contract-6096d3e39551
[81] Full Stack Hello World Voting Ethereum Dapp Tutorial — Part 1: https://medium.com/@mvmurthy/full-stack-hello-world-voting-ethereum-dapp-tutorial-part-1-40d2d0d807c2
[82] Full Stack Hello World Voting Ethereum Dapp Tutorial — Part 2: https://medium.com/@mvmurthy/full-stack-hello-world-voting-ethereum-dapp-tutorial-part-2-30b3d335aa1f
[83] Источник: https://habrahabr.ru/post/327236/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.