company_banner

Применение смарт-аккаунтов Waves: от аукционов до бонусных программ

    image

    Блокчейн часто ассоциируется лишь с криптовалютами, но области применения технологии DLT значительно шире. Одно из самых перспективных направлений для применения блокчейна – смарт-контракт, выполняющийся автоматически и не требующий доверия между сторонами, его заключившими.

    RIDE – язык для смарт-контрактов

    Waves разработала специальный язык для смарт-контрактов – RIDE. Его полная документация находится здесь. А вот – статья на эту тему на Хабре.

    Контракт на RIDE является предикатом и возвращает на выходе «true» или «false». Соответственно, транзакция либо записывается в блокчейн, либо отвергается. Смарт-контракт полностью гарантирует исполнение заданных условий. Генерация транзакций из контракта в RIDE на данный момент невозможна.

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

    match tx {
      case t: TransferTransaction | MassTransferTransaction => false
      case _ => true
    }
    

    tx — обрабатываемая транзакция, которую мы разрешаем, используя механизм паттерн-матчинга, только в случае, если она не является транзакцией перевода. Паттерн-матчинг в RIDE используется для проверки типа транзакции. В скрипте смарт-аккаунта могут быть обработаны все существующие типы транзакций.

    Также в скрипте могут объявляться переменные, использоваться конструкции «if-then-else» и другие методы полноценной проверки условий. Чтобы контракты имели доказуемую завершаемость и сложность (стоимость), которую легко предсказать до начала выполнения контракта, RIDE не содержит циклов и операторов типа jump.

    Среди других особенностей аккаунтов Waves — наличие «стейта», то есть состояния аккаунта. В стейт аккаунта можно записать бесконечное количество пар (ключ, значение) с помощью дата-транзакций (DataTransaction). Далее эту информацию можно обрабатывать как через REST API, так и напрямую в смарт-контракте.

    Каждая транзакция может содержать массив пруфов (proofs), в который можно внести подпись участника, ID необходимой транзакции и т.д.

    Работа с RIDE через IDE позволяет видеть скомпилированный вид контракта (если он компилируется), создавать новые аккаунты и задать для него скрипты, а также посылать транзакции через командную строку.

    Для полноценного цикла, включающего создание аккаунта, установку на него смарт-контракта и отправку транзакций, можно также использовать библиотеку для взаимодействия с REST API (например, C#, C, Java, JavaScript, Python, Rust, Elixir). Для начала работы с IDE достаточно нажать кнопку NEW.

    Возможности применения смарт-контрактов широки: от запрета транзакций на определенные адреса («черный список») до сложносоставных dApps.

    Теперь рассмотрим конкретные примеры применения смарт-контрактов в бизнесе: при проведении аукционов, страховании и создании программ лояльности.

    Аукционы

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

    На блокчейне Waves ставки могут записываться в стейт аккаунта аукциона посредством DataTransaction.

    Также можно задать время начала и окончания аукциона с помощью номеров блоков: частота генерации блока в блокчейне Waves примерно равна 60 секундам.

    1. Английский аукцион повышающейся цены

    Участники английского аукциона делают ставки, соревнуясь друг с другом. Каждая новая ставка должна превышать предыдущую. Аукцион заканчивается, когда больше нет желающих превысить последнюю ставку. В этом случае участник, сделавший самую высокую ставку, должен предоставить заявленную сумму.

    Существует и вариант аукциона, в котором продавец устанавливает минимальную цену лота, и финальная цена должна ее превышать. В противном случае лот остается непроданным.

    В этом примере мы работаем с аккаунтом, специально созданным для проведения аукциона. Длительность аукциона составляет 3000 блоков, а начальная цена лота — 0,001 WAVES. Участник может сделать ставку, отправив DataTransaction с ключом «price» и значением своей ставки, в пруфы транзакции необходимо добавить публичный ключ и подпись отправителя.

    Цена новой ставки должна быть выше, чем текущая цена по этому ключу, и участник должен иметь на счету как минимум [новая_ставка + комиссия] токенов. Адрес участника должен быть записан в поле «sender» в DataTransaction, а текущая высота блока ставки должна находиться в границах периода аукциона.

    Если по итогам аукциона участник назначил самую высокую цену, он может отправить ExchangeTransaction для оплаты соответствующего лота по указанной цене и валютной паре.

    let startHeight = 384120
    let finishHeight = startHeight + 3000
    let startPrice = 100000
    
    #извлекаем из транзакции адрес отправителя
    let this = extract(tx.sender)
    
    let token = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
    
    match tx {
    case d : DataTransaction =>
    
      #проверяем, задана ли в стейте цена
    
      let currentPrice = if isDefined(getInteger(this, "price"))
                          #извлекаем цену из стейта
                          then extract(getInteger(this, "price"))
                          else startPrice
      #извлекаем цену из транзакции
      let newPrice = extract(getInteger(d.data, "price"))
      #извлекаем из пруфов транзакции публичный ключ аккаунта
      let pk = d.proofs[1]
      let address = addressFromPublicKey(pk)
      let priceIsBigger = newPrice > currentPrice
      let fee = 700000
      let hasMoney = wavesBalance(address) + fee >= newPrice
    
      let correctFields = size(d.data) == 2
        && extract(getString(d.data, "sender")) == toBase58String(address.bytes)
      startHeight <= height && height <= finishHeight 
        && priceIsBigger && hasMoney && correctFields 
        && sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1])
    case o : Order =>
     #извлекаем из пруфов транзакции публичный ключ аккаунта
       let pk = o.proofs[1]
       let address = addressFromPublicKey(pk)
    
      let senderIsWinner = address == addressFromString(extract(getString(this, "sender"))) 
    
      #убеждаемся, что лот обменивает тот, кто его выиграл
      let correctAssetPair = o.assetPair.amountAsset == token && ! isDefined(o.assetPair.priceAsset)
      let correctAmount = o.amount == 1
      let correctPrice = o.price == extract(getInteger(this, "price"))
    
      height > finishHeight && senderIsWinner && correctAssetPair && correctAmount && correctPrice && sigVerify(o.bodyBytes, o.proofs[0], o.proofs[1]) 
    
    case _ => false
    }
    

    2. Голландский аукцион снижающейся цены

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

    В этом примере мы используем те же константы, что и в предыдущем, а также шаг цены при понижении delta. Скрипт аккаунта проверяет, действительно ли участник является первым, кто сделал ставку. В пруфы транзакции необходимо добавить публичный ключ и подпись отправителя.В противном случае DataTransaction не принимается блокчейном.

    let startHeight = 384120
    let finishHeight = startHeight + 3000
    let startPrice = 100000000
    let delta = 100
     
    #извлекаем из транзакции адрес отправителя
    let this = extract(tx.sender)
    
    let token = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
    match tx {
    case d : DataTransaction =>
      let currentPrice = startPrice - delta * (height - startHeight)
     
      #извлекаем из поступившей дата-транзакции поле "price"
      let newPrice = extract(getInteger(d.data, "price"))
    
       #извлекаем из пруфов транзакции публичный ключ аккаунта
       let pk = d.proofs[1]
       let address = addressFromPublicKey(pk)
    
       let correctFields = extract(getString(d.data, "sender")) == toBase58String(address.bytes) 
        && size(d.data) == 2 && newPrice == currentPrice
     
      #убеждаемся, что в стейте текущего аккаунта не содержится поля "sender"
      let noBetsBefore = !isDefined(getInteger(this, "sender"))
      let fee = 700000
      let hasMoney = wavesBalance(address) - fee >= newPrice
     
      startHeight <= height && height <= finishHeight && noBetsBefore 
        && hasMoney && correctFields && sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1])
    case o : Order =>
     #извлекаем из пруфов транзакции публичный ключ аккаунта
       let pk = o.proofs[1]
       let address = addressFromPublicKey(pk)
      #убеждаемся, что отправитель текущей транзакции указан в стейте аккаунта по ключу sender
      let senderIsWinner = address == addressFromString(extract(getString(this, "sender")))
     
      #убеждаемся, что аmount ассета указан корректно, и что прайс-ассет - waves
      let correctAssetPair = o.assetPair.amountAsset == token && ! isDefined(o.assetPair.priceAsset)
      let correctAmount = o.amount == 1
      let correctPrice = o.price == extract(getInteger(this, "price"))
      height > finishHeight && senderIsWinner && correctAssetPair && correctAmount && correctPrice
        && sigVerify(o.bodyBytes, o.proofs[0], o.proofs[1]) 
    case _ => false
    }

    3. Аукцион «all-pay»

    «All-pay» – аукцион, все участники которого оплачивают ставку, платят, независимо от того, кто выигрывает лот. Каждый новый участник оплачивает ставку, а выигрывает лот участник, сделавший максимальную ставку.

    В нашем примере каждый участник аукциона делает ставку через DataTransaction с (key, value)* = («winner», address),(«price», price). Такая DataTransaction одобряется только в случае, если для этого участника уже имеется TransferTransaction с его подписью и его ставка выше всех предыдущих. Аукцион продолжается до достижения высоты endHeight.

    let startHeight = 1000
    let endHeight = 2000
    let token = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
    match tx {
     case d: DataTransaction =>
       #извлекаем из поступившей дата-транзакции поле "price"
       let newPrice = extract(getInteger(d.data, "price"))
     
       #извлекаем из пруфов транзакции публичный ключ аккаунта
       let pk = d.proofs[1]
       let address = addressFromPublicKey(pk)
     
       #извлекаем транзакцию доказательство из пруфов поступившей дата транзакции
       let proofTx = extract(transactionById(d.proofs[2]))
       
       height > startHeight && height < endHeight
       && size(d.data) == 2
       #убеждаемся, что адрес победителя, извлеченный из текущей транзакции, совпадает с адресом, извлеченным из пруфов
       && extract(getString(d.data, "winner")) == toBase58String(address.bytes)
       && newPrice > extract(getInteger(this, "price"))
       #проверяем, что транзакция подписана
       && sigVerify(d.bodyBytes, d.proofs[0], d.proofs[1])
       #проверяем корректность транзакции, указанной в пруфах
       && match proofTx {
         case tr : TransferTransaction =>
           tr.sender == address &&
           tr.amount == newPrice
         case _ => false
       }
     case t: TransferTransaction =>
     sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
     || (
       height > endHeight
       && extract(getString(this, "winner")) == toBase58String((addressFromRecipient(t.recipient)).bytes)
       && t.assetId == token
       && t.amount == 1
     )
     case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
    }

    Страхование / Краудфандинг

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

    Для реализации этого нужно выпустить «страховые токены». Затем на аккаунт страхователя устанавливается скрипт, позволяющий исполнять только те ExchangeTransactions, которые удовлетворяют определенным условиям.

    Чтобы предотвратить двойную трату, нужно запросить у пользователя заблаговременную отправку DataTransaction на аккаунт страхователя с (key, value) = (purchaseTransactionId, sellOrderId) и запретить отправку DataTransactions с уже использованным ключом.

    Поэтому пруфы пользователя должны содержать ID транзакции покупки страхового токена. Валютная пара должна быть такой же, как и в транзакции покупки. Стоимость также должна быть равна той, что зафиксирована при покупке, за вычетом цены страховки.

    Подразумевается, что впоследствии страховой аккаунт выкупает страховые токены у пользователя по цене не ниже той, по которой он их приобрел: страховой аккаунт создает ExchangeTransaction, пользователь подписывает ордер (если транзакция составлена корректно), страховой аккаунт подписывает второй ордер и всю транзакцию и отправляет в блокчейн.

    Если покупки не происходит, пользователь может создать Order в соответствии с правилами, описанными в скрипте, и отправить транзакцию в блокчейн. Так пользователь может вернуть деньги, потраченные на покупку застрахованных токенов.

    let insuranceToken = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
     
    #извлекаем из транзакции адрес отправителя
    let this = extract(tx.sender)
    
    let freezePeriod = 150000
    let insurancePrice = 10000
    match tx {
     
     #убеждаемся, что, если поступила дата-транзакция, то у нее ровно одно поле и в стейте еще нет такого ключа
     case d : DataTransaction => size(d.data) == 1 && !isDefined(getBinary(this, d.data[0].key))
     case o : Order =>
     
       #если у транзакции нет седьмого пруфа, проверяем корректность подписи
       if !isDefined(o.proofs[7]) then
         sigVerify(o.bodyBytes, o.proofs[0], o.senderPublicKey)
       else
         #если у транзакции есть седьмой пруф, извлекаем из него транзакцию и узнаём её высоту
         let purchaseTx = transactionById(o.proofs[7])
         let purchaseTxHeight = extract(transactionHeightById(o.proofs[7]))
        
         #обрабатываем транзакцию из пруфа
         match purchaseTx {
           case purchase : ExchangeTransaction =>
             let correctSender = purchase.sender == o.sender
             let correctAssetPair = o.assetPair.amountAsset == insuranceToken &&
                                    purchase.sellOrder.assetPair.amountAsset == insuranceToken &&
                                    o.assetPair.priceAsset == purchase.sellOrder.assetPair.priceAsset
             let correctPrice = o.price == purchase.price - insurancePrice && o.amount == purchase.amount
             let correctHeight = height > purchaseTxHeight + freezePeriod
     
             #убеждаемся, что в транзакции-пруфе указан верный ID текущей транзакции
             let correctProof = extract(getBinary(this, toBase58String(purchase.id))) == o.id
             correctSender && correctAssetPair && correctPrice && correctHeight && correctProof
         case _ => false
       }
     case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
    }
    

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

    Эта схема может быть реализована и для токенов краудфандинга, которые возвращаются владельцам, если необходимая сумма не была собрана.

    Налоги с транзакций

    Смарт-контракты применимы и в случаях, когда надо собирать налог с каждой транзакции с несколькими типами активов. Это можно сделать через новый ассет с установленным спонсорством для транзакций со смарт-ассетами:

    1. Выпускаем FeeCoin, который будет отправлен пользователям по фиксированной цене: 0,01 WAVES = 0,001 FeeCoin.

    2. Задаем спонсорство для FeeCoin и курс обмена: 0,001 WAVES = 0,001 FeeCoin.

    3. Задаем следующий скрипт для смарт-ассета:

    let feeAssetId = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
    let taxDivisor = 10
     
    match tx {
      case t: TransferTransaction =>
        t.feeAssetId == feeAssetId && t.fee == t.amount / taxDivisor
      case e: ExchangeTransaction| MassTransferTransaction => false
      case _ => true
    }

    Теперь каждый раз, когда кто-либо переводит N смарт-ассетов, он даст вам FeeCoin в сумме N / taxDivisor (который может быть куплен у вас по 10 *N / taxDivisor WAVES), а вы отдадите майнеру N / taxDivisor WAVES. В результате ваша прибыль (налог) составит 9*N / taxDivisor WAVES.

    Также можно осуществлять налогообложение с помощью скрипта смарт-ассета и MassTransferTransaction:

    let taxDivisor = 10
     
    match tx {
      case t : MassTransferTransaction =>
        let twoTransfers = size(t.transfers) == 2
        let issuerIsRecipient = t.transfers[0].recipient == addressFromString("3MgkTXzD72BTfYpd9UW42wdqTVg8HqnXEfc")
        let taxesPaid = t.transfers[0].amount >= t.transfers[1].amount / taxDivisor
        twoTransfers && issuerIsRecipient && taxesPaid
      case _ => false
    }

    Кэшбэк и программы лояльности

    Кэшбэк — тип программы лояльности, в котором покупателю возвращается часть суммы, потраченной на товар или услугу.

    При реализации этого кейса с помощью смарт-аккаунта мы должны проверить пруфы так же, как делали это в кейсе страхования. Для предотвращения двойной траты перед получением кэшбэка пользователь должен отправить DataTransaction с (key, value) = (purchaseTransactionId, cashbackTransactionId).

    Также мы должны установить запрет на уже существующие ключи с помощью DataTransaction. cashbackDivisor — единица, деленная на долю кэшбэка. Т.е. в случае, если доля кэшбека равна 0.1, то cashbackDivisor 1 / 0.1 = 10.

    let cashbackToken = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
     
    #извлекаем из транзакции адрес отправителя
    let this = extract(tx.sender)
    
    let cashbackDivisor = 10
    match tx {
     
     #убеждаемся, что, если поступила дата-транзакция, то у нее ровно одно поле и в стейте еще нет такого ключа
     case d : DataTransaction => size(d.data) == 1 && !isDefined(getBinary(this, d.data[0].key))
     case e : TransferTransaction =>
     
       #если у транзакции нет седьмого пруфа, проверяем корректность подписи
       if !isDefined(e.proofs[7]) then
         sigVerify(e.bodyBytes, e.proofs[0], e.senderPublicKey)
       else
     
         #если у транзакции есть седьмой пруф, извлекаем из него транзакцию и узнаём её высоту
         let purchaseTx = transactionById(e.proofs[7])
         let purchaseTxHeight = extract(transactionHeightById(e.proofs[7]))
        
         #обрабатываем транзакцию из пруфа
         match purchaseTx {
           case purchase : TransferTransaction =>
             let correctSender = purchase.sender == e.sender
             let correctAsset = e.assetId == cashbackToken
             let correctPrice = e.amount == purchase.amount / cashbackDivisor
     
             #убеждаемся, что в транзакции-пруфе указан верный ID текущей транзакции
             let correctProof = extract(getBinary(this, toBase58String(purchase.id))) == e.id
             correctSender && correctAsset && correctPrice && correctProof
         case _ => false
       }
     case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
    }

    Атомарный своп

    Атомарный своп позволяет пользователям обмениваться ассетами без помощи биржи. При атомарном свопе от обоих участников сделки требуется ее подтверждение в течение определенного промежутка времени.

    Если хотя бы один из участников не предоставляет корректного подтверждения транзакции в течение отведенного на сделку времени, транзакция отменяется и обмена не происходит.

    В нашем примере мы будем использовать такой скрипт смарт-аккаунта:

    let Bob = Address(base58'3NBVqYXrapgJP9atQccdBPAgJPwHDKkh6A8')
    let Alice = Address(base58'3PNX6XwMeEXaaP1rf5MCk8weYeF7z2vJZBg')
     
    let beforeHeight = 100000
     
    let secret = base58'BN6RTYGWcwektQfSFzH8raYo9awaLgQ7pLyWLQY4S4F5'
    match tx {
      case t: TransferTransaction =>
        let txToBob = t.recipient == Bob && sha256(t.proofs[0]) == secret && 20 + beforeHeight >= height
        let backToAliceAfterHeight = height >= 21 + beforeHeight && t.recipient == Alice
        txToBob || backToAliceAfterHeight
      case _ => false
    }
    

    В следующей статье мы рассмотрим применение смарт-аккаунтов в финансовых инструментах – таких, как опционы, фьючерсы и векселя.
    Waves
    88,25
    Компания
    Поделиться публикацией

    Комментарии 13

      0
      Спасибо. Как раз следующая статья интересно, так как вы заявили, что инициировать транзакции с контракта нельзя, а ведь суть производных как раз в потоке операций (ну как я это вижу). Интересно, как решается эта задача у вас.
        0
        В ближайшем релизе, который уже в марте будет на Тестнете, такая возможность появиться.
          0
          Контракт на самом деле является гарантией правильных операций (на самом деле правильных переходов между состояниями). С текущим функционалом (без возможности инициирования транзакций) тоже можно валидировать правильность перехода между состояниями.
          Да, транзакции надо будет генерировать офчейн и отправлять в сеть, а контракт уже будет валидировать их.
          0
          Добрый день.
          Я совсем запутался с примерами(((
          «Английский аукцион»: Участник может сделать ставку, отправив DataTransaction с ключом «price» и значением своей ставки. Кому отправить, на аккаунт аукциона?

          Обработка транзакций match tx {} — это ведь обработка только исходящих транзакций или можно входящие проверять?

          Заранее спасибо.
            0
            1) Поставив адрес аукциона как sender, таким образом ставка попадет в стейт аукциона.
            2) Обработка исходящих транзакций, так как «входящих» транзакций в общем-то нет как факта, исходящая транзакция трансфера так и остается исходящим трансфером средств, у которых есть получатель. Никто не получает транзакции, все их могут только посылать. Таким образом да, скрипт в паттерн матчинге обрабатывает только исходящие транзакции, то есть транзакции, в поле sender у которого указан владелец смарт-аккаунта с данным скриптом.
            0
            Антонина большое спасибо ))) Если еще возникнут «глупые» вопросы можно обращаться?
              0
              Антонина добрый день. Получается что запрос должен выглядеть таким образом?
              {
                  "version": 1,
                  "senderPublicKey": "Публичный ключ участника",
                  "data": [
                      {
                          "key": "price",
                          "type": "integer",
                          "value": 100001
                      },
                      {
                          "key": "sender",
                          "type": "string",
                          "value": "Адрес укциона"
                      }
                  ],
                  "fee": 100000,
                  "type": 12,
                  "timestamp": 1553851367512,
                  "proofs": [
                      "Подпись участника"
                  ]
              }
                0
                1. Из описания: Адрес участника должен быть записан в поле «sender» в DataTransaction
                2. Из комментария: Поставив адрес аукциона как sender, таким образом ставка попадет в стейт аукциона

                Рассмотрим транзакцию
                {
                	data:
                	(2)[{
                			key: 'price'
                			type: 'integer'
                			value: 65
                		}{
                			key: 'sender'
                			type: 'string'
                			value: 'UserAddress'
                		}
                	]
                	fee: 500000
                	feeAssetId: null
                	id: 'BjemVnQ31yYKFFmHN.......VtwXcme8EFeUxUxq9Ci'
                	proofs:
                	(0)[
                	]
                	sender: 'AuctionAddress'
                	senderPublicKey: 'AuctionPK'
                	timestamp: 15539...28172
                	type: 12
                	version: 1
                }


                В смарт контракте:
                 ... && d.sender == addressFromString(extract(getString(d.data,"sender")))

                По условию смарт контракта адреса должны совпадать и это будет адрес аукциона, а не адрес участника.
                Чтобы зафиксировать адрес участника, транзакция должна быть от имени участника… (!) (?)

                Как можно создать транзакцию от имени участника, чтобы она смогла внести изменения в стейт аукциона?

                Может я что то не понял, но понять не могу «что я не понял» (((
                Подскажите пожалуйста.
                  0
                  Кажется, что эта проверка про отправителя и правда просто лишняя. Как я и писала выше, участник может послать транзакцию только если от имени аккаунта аукциона, но указать там свой адрес как поле дата транзакции ему никто не мешает.
                    0
                    Поправила в тексте оба контракта на их корректные версии, с проверкой подписи и публичного ключа того, кто транзакцию отправляет, а не того, от чьего имени она попадает в блокчейн
                    0
                    Игнорирование?
                    Очень жаль что компания WAVES так, не корректно себя ведет. Александр (Иванов) не упускаем возможности рассказать о платформе о разработках о перспективах, а поддержку обеспечить некому. Вы надеетесь что WAVES победит с таким подходом? Я не уверен…
                      0
                      Спасибо, что нашли ошибку и разобрались, прошу прощения за задержку с ответом, честно говоря, пропустила уведомления от хабра на почте и узнала о ваших комментариях совершенно случайно. Больше готовых примеров на RIDE можно найти вот здесь: github.com/wavesplatform/ride-examples
                      0
                      Антонита добрый день. Спасибо за ответ.
                      Извините за небольшую резкость с моей стороны. Я не только к вам обращался за поддержкой, но и на других ресурсах, где есть специалисты WavesPlatform и практически везде молчат. Мне показалось что в компании Waves «так принято» и я отложил попытку понять RIDE и Waves до лучших времен)))

                      По вашим примерам. Конечно вместе с кодом хотелось бы видеть действующую модель на тестнете, т.к. элемент игры привлекает и помогает в обучении (в любом возрасте). Если у меня появится свободное время я сам постараюсь сделать действующую модель, но чуть позже…

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое