Перейти к основному содержимому
Блокчейн72ГЛАВА 72Клиент к Waves: приложение поверх сетичитатьсобратьотправитьдождатьсясида на странице нет

Клиент к Waves: приложение поверх сети

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

dApp написан, установлен и работает. Но пользователь не будет вызывать его командой в терминале — ему нужен интерфейс, а интерфейсу нужен клиент.

Хорошая новость: клиент к Waves — это четыре функции. Никакого SDK со своим жизненным циклом, никакой синхронизации: узел говорит по обычному HTTP, транзакции подписывает библиотека, а всё остальное вы уже умеете по веб-блоку.

Эта глава — про эти четыре функции, про то, где может лежать сид, а где нет, и про подведение итогов: одна и та же зачётка теперь написана на трёх платформах. Весь код ниже запущен на локальном узле Waves 1.6.4.

Четыре функции​

ВЕСЬ КЛИЕНТ — ЧЕТЫРЕ ФУНКЦИИчитатьGET /addresses/data/…бесплатно, мгновенно, без подписисобратьinvokeScript({…}, seed)объект в памяти; сеть о нём не знаетотправитьPOST /transactions/broadcast200 значит «принято в очередь»дождатьсяGET /transactions/info/<id>404 значит «ещё не в блоке»
Читать, собрать, отправить, дождаться — весь клиент

Читать — бесплатно, мгновенно, без подписи:

async function api(path) {
const r = await fetch(NODE + path);
return { status: r.status, body: await r.json().catch(() => null) };
}

async function pokazat(dapp, id) {
const r = await api(`/addresses/data/${dapp}/z_${id}`);
if (r.status === 404) return null;
return r.body;
}
z-1: {"key":"z_z-1","type":"string","value":"Kovalik Oleg"}
z-99: null
Совет

404 здесь — не ошибка, а «такого ключа нет». Поэтому клиент превращает его в null, а не бросает исключение. Смешивать «нет данных» и «сломалось» — верный способ получить приложение, которое падает на пустом списке.

Отправить — с честной обработкой отказа:

async function otpravit(tx) {
const r = await fetch(NODE + '/transactions/broadcast', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(tx),
});
const body = await r.json().catch(() => null);
if (r.status !== 200) {
throw new Error(`узел отказал (${body.error}): ${body.message}`);
}
return body;
}
поймали: узел отказал (306): Error while executing dApp: Zachetka z-7 uzhe zavedena
Нажми на часть выражения

Дождаться — цикл с паузой:

async function dozhdatsya(id, popytok = 40) {
for (let i = 0; i < popytok; i++) {
const r = await api('/transactions/info/' + id);
if (r.status === 200) return r.body;
await new Promise((res) => setTimeout(res, 700));
}
throw new Error('не дождались ' + id);
}
в блоке: 413 | статус: succeeded
изменено: [{"key":"z_z-7","type":"string","value":"Petrov Ivan"},
{"key":"z_z-7_ball","type":"integer","value":0}]
Важно

Ограничение числа попыток обязательно. Без него цикл будет крутиться вечно, если транзакция не пройдёт, — и пользователь увидит вечный индикатор загрузки вместо сообщения об ошибке.

Список вместо перебора​

Достать все зачётки одним запросом:

const q = encodeURIComponent('z_[^_]+$');
const vse = await api(`/addresses/data/${DAPP}?matches=${q}`);
найдено: 2 → [{"key":"z_z-1","type":"string","value":"Kovalik Oleg"},
{"key":"z_z-7","type":"string","value":"Petrov Ivan"}]

Образец z_[^_]+$ берёт ключи вида z_<что-то без подчёркиваний> и отсекает z_z-1_ball. Так составные ключи из главы про состояние и начинают работать как коллекция.

Отвечено вопросов: 0 из 3
Сколько стоит чтение состояния через клиент?
Клиент получил 404 на ключ. Что с этим делать?
Зачем ограничивать число попыток ожидания?
Отвечено верно: 0 из 3

Где может лежать сид​

ГДЕ МОЖЕТ ЛЕЖАТЬ СИД, А ГДЕ НЕТМожнопеременная окружения на серверехранилище секретоврасширение браузера — подпись у пользователяНельзяв коде страницы — виден всемв репозитории — навсегда в историив localStorage — забирают одним скриптомво фронтенде сида нет вообще: транзакцию подписывает кошелёк пользователяна сервере сид нужен только своему аккаунту приложения — и только ему
Во фронтенде сида нет вообще

Это самое важное место главы, и ошибаются здесь чаще всего.

На сервере сид приложения может лежать в переменной окружения или в хранилище секретов. Он нужен, чтобы подписывать транзакции своего аккаунта — например, начислять баллы.

Во фронтенде сида нет вообще. Транзакцию пользователя подписывает его кошелёк — расширение браузера или мобильное приложение. Страница собирает объект транзакции и отдаёт его на подпись; закрытый ключ её не покидает.

// на странице: объект собран, подпись — у кошелька
const tx = { type: 16, dApp, call: { function: 'zavesti', args: [...] } };
const podpisannaya = await signer.invoke(tx); // подписывает кошелёк
Важно

Сид в коде страницы виден всем: исходники фронтенда открыты по определению. Сид в репозитории остаётся в истории навсегда, даже после удаления файла. Сид в localStorage забирают одним посторонним скриптом. Это не перестраховка — это три способа, которыми деньги теряют чаще всего.

Что ещё пригодится клиенту​

ЗапросЗачем
GET /addresses/balance/<адрес>остаток в WAVES
GET /assets/balance/<адрес>все токены адреса разом
GET /transactions/address/<адрес>/limit/10последние транзакции — история для интерфейса
GET /blocks/heightжив ли узел вообще
POST /transactions/calculateFeeузнать комиссию заранее, не угадывая
GET /addresses/scriptInfo/<адрес>есть ли скрипт и нужна ли доплата
Совет

calculateFee стоит вызывать перед каждой транзакцией в настоящем приложении. Комиссия зависит и от типа, и от размера, и от скрипта на счёте — угадывать её в коде значит однажды получить отказ у пользователя.

Итог: одна задача, три платформы​

ОДНА ЗАЧЁТКА, ТРИ ПЛАТФОРМЫ — ИТОГ ТРЕКАSolidityFabricWavesязыкSolidityTypeScriptRIDEгде данныеполя контрактаобщее хранилищесостояние аккаунтакто вызвалmsg.senderclientIdentityi.callerцена вызова~96 000 газанет платы0,005 WAVESвходсвой ключсертификатсид-фразаистория полячерез событияgetHistoryForKeyчерез транзакциилогика везде одна, различаются вход, цена и способ вернуть результат
Логика везде одна; различаются вход, цена и способ вернуть результат

Зачётка теперь написана трижды, и сравнение получается не на словах.

SolidityЧейнкод FabricdApp на RIDE
ЯзыкSolidityTypeScriptRIDE
Где данныеполя контрактаобщее хранилищесостояние аккаунта
Проверкаrequirethrowthrow
Кто вызвалmsg.senderctx.clientIdentityi.caller
Цена вызова~96 000 газанет платы0,005 WAVES
Вход в сетьсвой ключвыданный сертификатсид-фраза
Обновление кодановый адрес или проксиномер правки +1SetScript заново
«Всё или ничего»revertнабор измененийсписок не вернулся

Три разных ответа на один вопрос — «как несколько сторон ведут общую запись, не доверяя друг другу». Выбор между ними — это выбор из главы про Fabric и Ethereum: можно ли назвать участников заранее, нужна ли плата, кто и как получает доступ.

Отвечено вопросов: 0 из 3
Где во фронтенде хранят сид пользователя?
Зачем вызывать calculateFee?
Что общего у всех трёх платформ?
Отвечено верно: 0 из 3

Типичные ошибки​

Ошибка 1. Сид во фронтенде.

Разобрана выше. Самая дорогая ошибка всего трека.

Ошибка 2. Считать 200 подтверждением.

Ответ broadcast говорит «принято в очередь». Без ожидания интерфейс покажет успех раньше, чем запись состоится.

Ошибка 3. Цикл ожидания без потолка.

Вечный индикатор загрузки вместо честной ошибки.

Ошибка 4. Терять message при отказе.

if (r.status !== 200) throw new Error('ошибка сети'); // потеряли причину

В body.message лежит текст из вашего же throw в RIDE. Выбросить его — значит отлаживать вслепую.

Ошибка 5. Читать состояние платным вызовом.

Вызов dApp ради чтения стоит 0,005 WAVES и попадает в блок. То же самое читается бесплатно запросом /addresses/data/.

Отвечено вопросов: 0 из 3
Что произойдёт, если показать успех сразу после broadcast?
Почему нельзя заменять message на «ошибка сети»?
Как правильно прочитать состояние dApp из интерфейса?
Отвечено верно: 0 из 3

Шпаргалка по главе​

Что нужноКак
Прочитать ключGET /addresses/data/<dapp>/<ключ> — 404 значит «нет»
Прочитать по образцуGET /addresses/data/<dapp>?matches=<регулярка в URL>
Собрать вызовinvokeScript({ dApp, call, payment, chainId }, seed)
ОтправитьPOST /transactions/broadcast
Дождатьсяцикл по GET /transactions/info/<id> с потолком попыток
Узнать комиссиюPOST /transactions/calculateFee
История адресаGET /transactions/address/<адрес>/limit/10
Подпись во фронтендекошелёк пользователя, сида в коде нет

Словарь главы​

ТерминЧто это
КлиентОбёртка над REST узла: читать, собрать, отправить, дождаться
broadcastОтправка транзакции; 200 означает «принято в очередь»
ОжиданиеЦикл по /transactions/info/<id> с ограничением попыток
calculateFeeЗапрос комиссии до отправки
КошелёкРасширение или приложение, подписывающее транзакции пользователя
matchesВыборка ключей состояния по образцу
error: 306Упал скрипт dApp; message — ваш текст

Цель: скорость ≥ 95 зн/мин, точность ≥ 88%

const r = await fetch(NODE + path);

Что должен уметь после главы​

  • написать клиент из четырёх функций: читать, собрать, отправить, дождаться;
  • отличить «нет данных» от «сломалось» и не смешивать их в коде;
  • превратить отказ узла в исключение, не потеряв причину;
  • достать коллекцию записей выборкой по образцу;
  • сказать, где сид лежать может, а где — никогда;
  • сравнить три платформы трека и обосновать выбор под задачу.

Проверка по главе​

Сводная проверка по всей главе: 10 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Экзамен по блоку: Waves: сеть, язык RIDE и приложения

Сводная проверка по 10 главам блока: 30 вопросов, 10:00 на всё, по одному вопросу, без возврата. Пересдавать можно сколько угодно раз.

Готовность: экзамены глав сданы 0 из 10. Блок можно сдавать и так, но шанс выше после всех глав.

Куда дальше: проверенные ресурсы​

  • Waves node REST API — все запросы с примерами.
  • @waves/waves-transactions — сборка и подпись транзакций.
  • Waves Signer — подпись во фронтенде без сида на странице.
  • Waves Keeper — кошелёк-расширение, которым подписывают пользователи.

Видео по теме

ВидеоWaves SDK, connecting blockchain to blockchain. (multilingual subtitles)Crypto Crave Dude
ВидеоWaves Protocol 101 architecture: Setting up a node and executing API callsDoraHacks
ВидеоWaves Explorer: user guideWaves Tech
ВидеоWaves Platform воркшоп для веб-разработчиков | Kyiv BlockchainHubCRYPTODEALERS
ВидеоCrash Course to Blockchain Tech with Waves (Part I)Openhack 2020 Australia

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.